using System; using System.Runtime.InteropServices; using MoonWorks.Math; namespace MoonWorks.Audio { public abstract class SoundInstance : AudioResource { internal IntPtr Handle; internal FAudio.FAudioWaveFormatEx Format; public bool Loop { get; protected set; } = false; protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings; protected bool is3D; public abstract SoundState State { get; protected set; } private float _pan = 0; public float Pan { get => _pan; set { _pan = value; if (_pan < -1f) { _pan = -1f; } if (_pan > 1f) { _pan = 1f; } if (is3D) { return; } SetPanMatrixCoefficients(); FAudio.FAudioVoice_SetOutputMatrix( Handle, Device.MasteringVoice, dspSettings.SrcChannelCount, dspSettings.DstChannelCount, dspSettings.pMatrixCoefficients, 0 ); } } private float _pitch = 1; public float Pitch { get => _pitch; set { _pitch = MathHelper.Clamp(value, -1f, 1f); UpdatePitch(); } } private float _volume = 1; public float Volume { get => _volume; set { _volume = value; FAudio.FAudioVoice_SetVolume(Handle, _volume, 0); } } private float _reverb; public unsafe float Reverb { get => _reverb; set { _reverb = value; float* outputMatrix = (float*) dspSettings.pMatrixCoefficients; outputMatrix[0] = _reverb; if (dspSettings.SrcChannelCount == 2) { outputMatrix[1] = _reverb; } FAudio.FAudioVoice_SetOutputMatrix( Handle, Device.ReverbVoice, dspSettings.SrcChannelCount, 1, dspSettings.pMatrixCoefficients, 0 ); } } private float _lowPassFilter; public float LowPassFilter { get => _lowPassFilter; set { _lowPassFilter = value; FAudio.FAudioFilterParameters p = new FAudio.FAudioFilterParameters { Type = FAudio.FAudioFilterType.FAudioLowPassFilter, Frequency = _lowPassFilter, OneOverQ = 1f }; FAudio.FAudioVoice_SetFilterParameters( Handle, ref p, 0 ); } } private float _highPassFilter; public float HighPassFilter { get => _highPassFilter; set { _highPassFilter = value; FAudio.FAudioFilterParameters p = new FAudio.FAudioFilterParameters { Type = FAudio.FAudioFilterType.FAudioHighPassFilter, Frequency = _highPassFilter, OneOverQ = 1f }; FAudio.FAudioVoice_SetFilterParameters( Handle, ref p, 0 ); } } private float _bandPassFilter; public float BandPassFilter { get => _bandPassFilter; set { _bandPassFilter = value; FAudio.FAudioFilterParameters p = new FAudio.FAudioFilterParameters { Type = FAudio.FAudioFilterType.FAudioBandPassFilter, Frequency = _bandPassFilter, OneOverQ = 1f }; FAudio.FAudioVoice_SetFilterParameters( Handle, ref p, 0 ); } } public SoundInstance( AudioDevice device, ushort formatTag, ushort bitsPerSample, ushort blockAlign, ushort channels, uint samplesPerSecond, bool is3D ) : base(device) { var format = new FAudio.FAudioWaveFormatEx { wFormatTag = formatTag, wBitsPerSample = bitsPerSample, nChannels = channels, nBlockAlign = blockAlign, nSamplesPerSec = samplesPerSecond, nAvgBytesPerSec = blockAlign * samplesPerSecond }; Format = format; FAudio.FAudio_CreateSourceVoice( Device.Handle, out Handle, ref Format, FAudio.FAUDIO_VOICE_USEFILTER, FAudio.FAUDIO_DEFAULT_FREQ_RATIO, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero ); if (Handle == IntPtr.Zero) { Logger.LogError("SoundInstance failed to initialize!"); return; } this.is3D = is3D; InitDSPSettings(Format.nChannels); // FIXME: not everything should be running through reverb... /* FAudio.FAudioVoice_SetOutputVoices( Handle, ref Device.ReverbSends ); */ State = SoundState.Stopped; } public void Apply3D(AudioListener listener, AudioEmitter emitter) { is3D = true; emitter.emitterData.CurveDistanceScaler = Device.CurveDistanceScalar; emitter.emitterData.ChannelCount = dspSettings.SrcChannelCount; FAudio.F3DAudioCalculate( Device.Handle3D, ref listener.listenerData, ref emitter.emitterData, FAudio.F3DAUDIO_CALCULATE_MATRIX | FAudio.F3DAUDIO_CALCULATE_DOPPLER, ref dspSettings ); UpdatePitch(); FAudio.FAudioVoice_SetOutputMatrix( Handle, Device.MasteringVoice, dspSettings.SrcChannelCount, dspSettings.DstChannelCount, dspSettings.pMatrixCoefficients, 0 ); } public abstract void Play(bool loop); public abstract void Pause(); public abstract void Stop(bool immediate); private void InitDSPSettings(uint srcChannels) { dspSettings = new FAudio.F3DAUDIO_DSP_SETTINGS(); dspSettings.DopplerFactor = 1f; dspSettings.SrcChannelCount = srcChannels; dspSettings.DstChannelCount = Device.DeviceDetails.OutputFormat.Format.nChannels; int memsize = ( 4 * (int) dspSettings.SrcChannelCount * (int) dspSettings.DstChannelCount ); dspSettings.pMatrixCoefficients = Marshal.AllocHGlobal(memsize); unsafe { byte* memPtr = (byte*) dspSettings.pMatrixCoefficients; for (int i = 0; i < memsize; i += 1) { memPtr[i] = 0; } } SetPanMatrixCoefficients(); } private void UpdatePitch() { float doppler; float dopplerScale = Device.DopplerScale; if (!is3D || dopplerScale == 0.0f) { doppler = 1.0f; } else { doppler = dspSettings.DopplerFactor * dopplerScale; } FAudio.FAudioSourceVoice_SetFrequencyRatio( Handle, (float) System.Math.Pow(2.0, _pitch) * doppler, 0 ); } // Taken from https://github.com/FNA-XNA/FNA/blob/master/src/Audio/SoundEffectInstance.cs private unsafe void SetPanMatrixCoefficients() { /* Two major things to notice: * 1. The spec assumes any speaker count >= 2 has Front Left/Right. * 2. Stereo panning is WAY more complicated than you think. * The main thing is that hard panning does NOT eliminate an * entire channel; the two channels are blended on each side. * -flibit */ float* outputMatrix = (float*) dspSettings.pMatrixCoefficients; if (dspSettings.SrcChannelCount == 1) { if (dspSettings.DstChannelCount == 1) { outputMatrix[0] = 1.0f; } else { outputMatrix[0] = (_pan > 0.0f) ? (1.0f - _pan) : 1.0f; outputMatrix[1] = (_pan < 0.0f) ? (1.0f + _pan) : 1.0f; } } else { if (dspSettings.DstChannelCount == 1) { outputMatrix[0] = 1.0f; outputMatrix[1] = 1.0f; } else { if (_pan <= 0.0f) { // Left speaker blends left/right channels outputMatrix[0] = 0.5f * _pan + 1.0f; outputMatrix[1] = 0.5f * -_pan; // Right speaker gets less of the right channel outputMatrix[2] = 0.0f; outputMatrix[3] = _pan + 1.0f; } else { // Left speaker gets less of the left channel outputMatrix[0] = -_pan + 1.0f; outputMatrix[1] = 0.0f; // Right speaker blends right/left channels outputMatrix[2] = 0.5f * _pan; outputMatrix[3] = 0.5f * -_pan + 1.0f; } } } } protected override void Destroy() { Stop(true); FAudio.FAudioVoice_DestroyVoice(Handle); Marshal.FreeHGlobal(dspSettings.pMatrixCoefficients); } } }