using System; using System.Runtime.InteropServices; using EasingFunction = System.Func; namespace MoonWorks.Audio { public unsafe class SendableVoice : Voice { private IReceivableVoice OutputVoice; private ReverbEffect ReverbEffect; byte* pMatrixCoefficients; protected float pan = 0; public float Pan { get => pan; internal set { value = Math.MathHelper.Clamp(value, -1f, 1f); if (pan != value) { pan = value; if (pan < -1f) { pan = -1f; } if (pan > 1f) { pan = 1f; } if (Is3D) { return; } SetPanMatrixCoefficients(); FAudio.FAudioVoice_SetOutputMatrix( Handle, OutputVoice.Handle, SourceChannelCount, DestinationChannelCount, (nint) pMatrixCoefficients, 0 ); } } } private float reverb; public unsafe float Reverb { get => reverb; internal set { if (ReverbEffect != null) { value = MathF.Max(0, value); if (reverb != value) { reverb = value; float* outputMatrix = (float*) pMatrixCoefficients; outputMatrix[0] = reverb; if (SourceChannelCount == 2) { outputMatrix[1] = reverb; } FAudio.FAudioVoice_SetOutputMatrix( Handle, ReverbEffect.Handle, SourceChannelCount, 1, (nint) pMatrixCoefficients, 0 ); } } #if DEBUG if (ReverbEffect == null) { Logger.LogWarn("Tried to set reverb value before applying a reverb effect"); } #endif } } public SendableVoice(AudioDevice device, uint sourceChannelCount, uint destinationChannelCount) : base(device, sourceChannelCount, destinationChannelCount) { OutputVoice = device.MasteringVoice; nuint memsize = (nuint) (4 * sourceChannelCount * destinationChannelCount); pMatrixCoefficients = (byte*) NativeMemory.AllocZeroed(memsize); SetPanMatrixCoefficients(); } public virtual void SetPan(float targetValue) { Pan = targetValue; Device.ClearTweens(this, AudioTweenProperty.Pan); } public virtual void SetPan(float targetValue, float duration, EasingFunction easingFunction) { Device.CreateTween(this, AudioTweenProperty.Pan, easingFunction, Pan, targetValue, duration, 0); } public virtual void SetPan(float targetValue, float delayTime, float duration, EasingFunction easingFunction) { Device.CreateTween(this, AudioTweenProperty.Pan, easingFunction, Pan, targetValue, duration, delayTime); } public virtual void SetReverb(float targetValue) { Reverb = targetValue; Device.ClearTweens(this, AudioTweenProperty.Reverb); } public virtual void SetReverb(float targetValue, float duration, EasingFunction easingFunction) { Device.CreateTween(this, AudioTweenProperty.Reverb, easingFunction, Volume, targetValue, duration, 0); } public virtual void SetReverb(float targetValue, float delayTime, float duration, EasingFunction easingFunction) { Device.CreateTween(this, AudioTweenProperty.Reverb, easingFunction, Volume, targetValue, duration, delayTime); } public unsafe void SetOutputVoice(IReceivableVoice send) { FAudio.FAudioSendDescriptor* sendDesc = stackalloc FAudio.FAudioSendDescriptor[1]; sendDesc[0].Flags = 0; sendDesc[0].pOutputVoice = send.Handle; var sends = new FAudio.FAudioVoiceSends(); sends.SendCount = 1; sends.pSends = (nint) sendDesc; FAudio.FAudioVoice_SetOutputVoices( Handle, ref sends ); OutputVoice = send; } public virtual unsafe void SetReverbEffectChain(ReverbEffect reverbEffect) { var sendDesc = stackalloc FAudio.FAudioSendDescriptor[2]; sendDesc[0].Flags = 0; sendDesc[0].pOutputVoice = OutputVoice.Handle; sendDesc[1].Flags = 0; sendDesc[1].pOutputVoice = reverbEffect.Handle; var sends = new FAudio.FAudioVoiceSends(); sends.SendCount = 2; sends.pSends = (nint) sendDesc; FAudio.FAudioVoice_SetOutputVoices( Handle, ref sends ); ReverbEffect = reverbEffect; } // 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*) pMatrixCoefficients; if (SourceChannelCount == 1) { if (DestinationChannelCount == 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 (DestinationChannelCount == 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 unsafe void Destroy() { NativeMemory.Free(pMatrixCoefficients); base.Destroy(); } } }