diff --git a/ProjectObsidian/Components/Audio/BandPassFilter.cs b/ProjectObsidian/Components/Audio/BandPassFilter.cs index a7ccc93..b5f04fc 100644 --- a/ProjectObsidian/Components/Audio/BandPassFilter.cs +++ b/ProjectObsidian/Components/Audio/BandPassFilter.cs @@ -3,6 +3,7 @@ using Elements.Assets; using System.Net; using ProtoFlux.Runtimes.Execution; +using System.Collections.Generic; namespace Obsidian.Components.Audio { @@ -22,8 +23,8 @@ public class BandPassFilter : Component, IAudioSource, IWorldElement private double lastTime; - private object lowFilter = null; - private object highFilter = null; + private Dictionary lowFilters = new(); + private Dictionary highFilters = new(); public bool IsActive { @@ -37,24 +38,29 @@ public void Read(Span buffer) where S : unmanaged, IAudioSample if (!IsActive) { buffer.Fill(default(S)); + lowFilters.Clear(); + highFilters.Clear(); return; } Span tempBuffer = stackalloc S[buffer.Length]; tempBuffer = buffer; + Source.Target.Read(tempBuffer); - if (lowFilter == null) + if (!lowFilters.TryGetValue(typeof(S), out object lowFilter)) { lowFilter = new ButterworthFilter.FilterButterworth(); + lowFilters.Add(typeof(S), lowFilter); } - if (highFilter == null) + if (!highFilters.TryGetValue(typeof(S), out object highFilter)) { highFilter = new ButterworthFilter.FilterButterworth(); + highFilters.Add(typeof(S), highFilter); } - ((ButterworthFilter.FilterButterworth)lowFilter).UpdateCoefficients(HighFrequency, (int)(tempBuffer.Length / (Engine.Current.AudioSystem.DSPTime - lastTime)), ButterworthFilter.FilterButterworth.PassType.Lowpass, Resonance); - ((ButterworthFilter.FilterButterworth)highFilter).UpdateCoefficients(LowFrequency, (int)(tempBuffer.Length / (Engine.Current.AudioSystem.DSPTime - lastTime)), ButterworthFilter.FilterButterworth.PassType.Highpass, Resonance); + ((ButterworthFilter.FilterButterworth)lowFilter).UpdateCoefficients(HighFrequency, Engine.AudioSystem.SampleRate, ButterworthFilter.FilterButterworth.PassType.Lowpass, Resonance); + ((ButterworthFilter.FilterButterworth)highFilter).UpdateCoefficients(LowFrequency, Engine.AudioSystem.SampleRate, ButterworthFilter.FilterButterworth.PassType.Highpass, Resonance); for (int i = 0; i < tempBuffer.Length; i++) { @@ -79,8 +85,8 @@ protected override void OnChanges() base.OnChanges(); if (Source.GetWasChangedAndClear()) { - lowFilter = null; - highFilter = null; + lowFilters.Clear(); + highFilters.Clear(); } } } diff --git a/ProjectObsidian/Components/Audio/ButterworthFilter.cs b/ProjectObsidian/Components/Audio/ButterworthFilter.cs index ca59140..64a34e4 100644 --- a/ProjectObsidian/Components/Audio/ButterworthFilter.cs +++ b/ProjectObsidian/Components/Audio/ButterworthFilter.cs @@ -1,6 +1,7 @@ using System; using FrooxEngine; using Elements.Assets; +using System.Collections.Generic; namespace Obsidian.Components.Audio; @@ -19,7 +20,7 @@ public class ButterworthFilter : Component, IAudioSource, IWorldElement private double lastTime; - private object filter = null; + private Dictionary filters = new(); public bool IsActive { @@ -42,7 +43,7 @@ protected override void OnChanges() base.OnChanges(); if (Source.GetWasChangedAndClear()) { - filter = null; + filters.Clear(); } } @@ -53,6 +54,7 @@ public void Read(Span buffer) where S : unmanaged, IAudioSample if (!IsActive) { buffer.Fill(default(S)); + filters.Clear(); return; } @@ -62,19 +64,17 @@ public void Read(Span buffer) where S : unmanaged, IAudioSample Source.Target.Read(span); - if (filter == null) - { + if (!filters.TryGetValue(typeof(S), out object filter)) { filter = new FilterButterworth(); + filters.Add(typeof(S), filter); } - ((FilterButterworth)filter).UpdateCoefficients(Frequency, (int)(span.Length / (Engine.Current.AudioSystem.DSPTime - lastTime)), LowPass ? FilterButterworth.PassType.Lowpass : FilterButterworth.PassType.Highpass, Resonance); + ((FilterButterworth)filter).UpdateCoefficients(Frequency, Engine.AudioSystem.SampleRate, LowPass ? FilterButterworth.PassType.Lowpass : FilterButterworth.PassType.Highpass, Resonance); for (int i = 0; i < span.Length; i++) { ((FilterButterworth)filter).Update(ref span[i]); } - - lastTime = Engine.Current.AudioSystem.DSPTime; } public class FilterButterworth where S: unmanaged, IAudioSample @@ -138,6 +138,12 @@ public void Update(ref S newInput) S fifth = this.outputHistory[1].Multiply(b2); S final = first.Add(second).Add(third).Subtract(fourth).Subtract(fifth); + for (int i = 0; i < final.ChannelCount; i++) + { + if (final[i] > 1f) final = final.SetChannel(i, 1f); + else if (final[i] < -1f) final = final.SetChannel(i, -1f); + } + this.inputHistory[1] = this.inputHistory[0]; this.inputHistory[0] = newInput; diff --git a/ProjectObsidian/Components/Audio/FrequencyModulator.cs b/ProjectObsidian/Components/Audio/FrequencyModulator.cs index 48cdbe0..8336c8f 100644 --- a/ProjectObsidian/Components/Audio/FrequencyModulator.cs +++ b/ProjectObsidian/Components/Audio/FrequencyModulator.cs @@ -66,15 +66,18 @@ public void Read(Span buffer) where S : unmanaged, IAudioSample // Apply FM synthesis for (int i = 0; i < buffer.Length; i++) { - // Get carrier and modulator values - float carrierValue = carrierBuffer[i].AbsoluteAmplitude; - float modulatorValue = modulatorBuffer[i].AbsoluteAmplitude; + for (int j = 0; j < buffer[i].ChannelCount; j++) + { + float carrierValue = carrierBuffer[i][j]; + float modulatorValue = modulatorBuffer[i][j]; - // Compute frequency modulation - float modulatedValue = (float)(carrierValue * Math.Sin(2 * Math.PI * modulationIndex * modulatorValue)); + float modulatedValue = (float)(carrierValue * Math.Sin(2 * Math.PI * modulationIndex * modulatorValue)); - // Write modulated value to the buffer - buffer[i] = buffer[i].Bias(buffer[i].AbsoluteAmplitude - modulatedValue); + buffer[i] = buffer[i].SetChannel(j, modulatedValue); + + if (buffer[i][j] > 1f) buffer[i] = buffer[i].SetChannel(j, 1f); + if (buffer[i][j] < -1f) buffer[i] = buffer[i].SetChannel(j, -1f); + } } } } diff --git a/ProjectObsidian/ProtoFlux/Audio/AudioAdder.cs b/ProjectObsidian/ProtoFlux/Audio/AudioAdder.cs index fb6479d..bae1beb 100644 --- a/ProjectObsidian/ProtoFlux/Audio/AudioAdder.cs +++ b/ProjectObsidian/ProtoFlux/Audio/AudioAdder.cs @@ -21,6 +21,12 @@ public class AudioAdderProxy : ProtoFluxEngineProxy, IAudioSource public void Read(Span buffer) where S : unmanaged, IAudioSample { + if (!IsActive) + { + buffer.Fill(default(S)); + return; + } + Span newBuffer = stackalloc S[buffer.Length]; newBuffer = buffer; Span newBuffer2 = stackalloc S[buffer.Length]; @@ -43,6 +49,12 @@ public void Read(Span buffer) where S : unmanaged, IAudioSample for (int i = 0; i < buffer.Length; i++) { newBuffer[i] = newBuffer[i].Add(newBuffer2[i]); + + for (int j = 0; j < newBuffer[i].ChannelCount; j++) + { + if (newBuffer[i][j] > 1f) newBuffer[i] = newBuffer[i].SetChannel(j, 1f); + else if (newBuffer[i][j] < -1f) newBuffer[i] = newBuffer[i].SetChannel(j, -1f); + } } } } diff --git a/ProjectObsidian/ProtoFlux/Audio/AudioMultiply.cs b/ProjectObsidian/ProtoFlux/Audio/AudioMultiply.cs index 8688c5b..5221353 100644 --- a/ProjectObsidian/ProtoFlux/Audio/AudioMultiply.cs +++ b/ProjectObsidian/ProtoFlux/Audio/AudioMultiply.cs @@ -22,6 +22,12 @@ public class AudioMultiplyProxy : ProtoFluxEngineProxy, IAudioSource public void Read(Span buffer) where S : unmanaged, IAudioSample { + if (!IsActive) + { + buffer.Fill(default(S)); + return; + } + Span newBuffer = stackalloc S[buffer.Length]; newBuffer = buffer; if (AudioInput != null) @@ -35,6 +41,12 @@ public void Read(Span buffer) where S : unmanaged, IAudioSample for (int i = 0; i < buffer.Length; i++) { newBuffer[i] = newBuffer[i].Multiply(Value); + + for (int j = 0; j < newBuffer[i].ChannelCount; j++) + { + if (newBuffer[i][j] > 1f) newBuffer[i] = newBuffer[i].SetChannel(j, 1f); + if (newBuffer[i][j] < -1f) newBuffer[i] = newBuffer[i].SetChannel(j, -1f); + } } } } diff --git a/ProjectObsidian/ProtoFlux/Audio/AudioSubtractor.cs b/ProjectObsidian/ProtoFlux/Audio/AudioSubtractor.cs index 25aed94..3bd0e5b 100644 --- a/ProjectObsidian/ProtoFlux/Audio/AudioSubtractor.cs +++ b/ProjectObsidian/ProtoFlux/Audio/AudioSubtractor.cs @@ -21,6 +21,12 @@ public class AudioSubtractorProxy : ProtoFluxEngineProxy, IAudioSource public void Read(Span buffer) where S : unmanaged, IAudioSample { + if (!IsActive) + { + buffer.Fill(default(S)); + return; + } + Span newBuffer = stackalloc S[buffer.Length]; newBuffer = buffer; Span newBuffer2 = stackalloc S[buffer.Length]; @@ -43,6 +49,12 @@ public void Read(Span buffer) where S : unmanaged, IAudioSample for (int i = 0; i < buffer.Length; i++) { newBuffer[i] = newBuffer[i].Subtract(newBuffer2[i]); + + for (int j = 0; j < newBuffer[i].ChannelCount; j++) + { + if (newBuffer[i][j] > 1f) newBuffer[i] = newBuffer[i].SetChannel(j, 1f); + else if (newBuffer[i][j] < -1f) newBuffer[i] = newBuffer[i].SetChannel(j, -1f); + } } } } diff --git a/ProjectObsidian/ProtoFlux/Audio/SineGenerator.cs b/ProjectObsidian/ProtoFlux/Audio/SineGenerator.cs index abdc688..649d872 100644 --- a/ProjectObsidian/ProtoFlux/Audio/SineGenerator.cs +++ b/ProjectObsidian/ProtoFlux/Audio/SineGenerator.cs @@ -6,8 +6,6 @@ using Elements.Assets; using Elements.Core; using System.Runtime.InteropServices; -using System.Linq.Expressions; -using FrooxEngine.FrooxEngine.ProtoFlux.CoreNodes; namespace ProtoFlux.Runtimes.Execution.Nodes.Obsidian.Audio { @@ -19,7 +17,7 @@ public class SineGeneratorProxy : ProtoFluxEngineProxy, IAudioSource public float Phase; - public float time; + public double time; private float[] tempBuffer; @@ -29,22 +27,43 @@ public class SineGeneratorProxy : ProtoFluxEngineProxy, IAudioSource public int ChannelCount => 1; - // TODO: Make this not advance time on each read - // If two things are reading this generator, it advances twice as fast + private bool updateTime; + public void Read(Span buffer) where S : unmanaged, IAudioSample { + if (!IsActive) + { + buffer.Fill(default(S)); + return; + } + tempBuffer = tempBuffer.EnsureSize(buffer.Length); - time %= MathX.PI * 2f; - float advance = 1f / (float)base.Engine.AudioSystem.SampleRate * (MathX.PI * 2f) * (float)Frequency; + var temptime = time; + temptime %= MathX.PI * 2f; + var clampedAmplitude = MathX.Clamp01(Amplitude); + float advance = (1f / (float)base.Engine.AudioSystem.SampleRate) * (MathX.PI * 2f) * (float)Frequency; for (int i = 0; i < buffer.Length; i++) { - tempBuffer[i] = MathX.Sin(time + Phase) * MathX.Clamp01(Amplitude); - time += advance; + tempBuffer[i] = (float)MathX.Sin(temptime + Phase) * clampedAmplitude; + temptime += advance; + } + if (updateTime) + { + time = temptime; + updateTime = false; } double position = 0.0; MonoSample lastSample = default(MonoSample); MemoryMarshal.Cast(MemoryExtensions.AsSpan(tempBuffer)).CopySamples(buffer, ref position, ref lastSample); } + + protected override void OnStart() + { + Engine.AudioSystem.AudioUpdate += () => + { + updateTime = true; + }; + } } [NodeCategory("Obsidian/Audio")] public class SineGenerator : ProxyVoidNode, IExecutionChangeListener @@ -145,9 +164,9 @@ public void Changed(FrooxEngineContext context) { return; } - proxy.Frequency = Frequency.Evaluate(context, 440f); proxy.Amplitude = Amplitude.Evaluate(context, 1f); proxy.Phase = Phase.Evaluate(context, 0f); + proxy.Frequency = Frequency.Evaluate(context, 440f); } protected override void ComputeOutputs(FrooxEngineContext context)