From 01dfee7daa868646a7f1667e8ac82990fb0bfe9a Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Tue, 31 Dec 2024 10:14:28 +0000 Subject: [PATCH 1/2] Clamp audio buffer values to prevent audio breaking --- .../Components/Audio/BandPassFilter.cs | 1 + .../Components/Audio/ButterworthFilter.cs | 6 ++++++ .../Components/Audio/FrequencyModulator.cs | 17 ++++++++++------- ProjectObsidian/ProtoFlux/Audio/AudioAdder.cs | 6 ++++++ .../ProtoFlux/Audio/AudioMultiply.cs | 6 ++++++ .../ProtoFlux/Audio/AudioSubtractor.cs | 6 ++++++ 6 files changed, 35 insertions(+), 7 deletions(-) diff --git a/ProjectObsidian/Components/Audio/BandPassFilter.cs b/ProjectObsidian/Components/Audio/BandPassFilter.cs index a7ccc93..582652a 100644 --- a/ProjectObsidian/Components/Audio/BandPassFilter.cs +++ b/ProjectObsidian/Components/Audio/BandPassFilter.cs @@ -42,6 +42,7 @@ public void Read(Span buffer) where S : unmanaged, IAudioSample Span tempBuffer = stackalloc S[buffer.Length]; tempBuffer = buffer; + Source.Target.Read(tempBuffer); if (lowFilter == null) diff --git a/ProjectObsidian/Components/Audio/ButterworthFilter.cs b/ProjectObsidian/Components/Audio/ButterworthFilter.cs index ca59140..bdc0df5 100644 --- a/ProjectObsidian/Components/Audio/ButterworthFilter.cs +++ b/ProjectObsidian/Components/Audio/ButterworthFilter.cs @@ -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] > 1) final = final.SetChannel(i, 1f); + if (final[i] < -1) 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..58be3ec 100644 --- a/ProjectObsidian/ProtoFlux/Audio/AudioAdder.cs +++ b/ProjectObsidian/ProtoFlux/Audio/AudioAdder.cs @@ -43,6 +43,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); + 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..cda5382 100644 --- a/ProjectObsidian/ProtoFlux/Audio/AudioMultiply.cs +++ b/ProjectObsidian/ProtoFlux/Audio/AudioMultiply.cs @@ -35,6 +35,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..e0bf207 100644 --- a/ProjectObsidian/ProtoFlux/Audio/AudioSubtractor.cs +++ b/ProjectObsidian/ProtoFlux/Audio/AudioSubtractor.cs @@ -43,6 +43,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); + if (newBuffer[i][j] < -1f) newBuffer[i] = newBuffer[i].SetChannel(j, -1f); + } } } } From a851a5f73f67b6625c48184aeff84c583390b73b Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Tue, 31 Dec 2024 15:58:04 +0000 Subject: [PATCH 2/2] Use IsActive, update filters to handle different types, use audio update callback --- .../Components/Audio/BandPassFilter.cs | 21 ++++++---- .../Components/Audio/ButterworthFilter.cs | 18 ++++----- ProjectObsidian/ProtoFlux/Audio/AudioAdder.cs | 8 +++- .../ProtoFlux/Audio/AudioMultiply.cs | 6 +++ .../ProtoFlux/Audio/AudioSubtractor.cs | 8 +++- .../ProtoFlux/Audio/SineGenerator.cs | 39 ++++++++++++++----- 6 files changed, 71 insertions(+), 29 deletions(-) diff --git a/ProjectObsidian/Components/Audio/BandPassFilter.cs b/ProjectObsidian/Components/Audio/BandPassFilter.cs index 582652a..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,6 +38,8 @@ public void Read(Span buffer) where S : unmanaged, IAudioSample if (!IsActive) { buffer.Fill(default(S)); + lowFilters.Clear(); + highFilters.Clear(); return; } @@ -45,17 +48,19 @@ public void Read(Span buffer) where S : unmanaged, IAudioSample 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++) { @@ -80,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 bdc0df5..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 @@ -140,8 +140,8 @@ public void Update(ref S newInput) for (int i = 0; i < final.ChannelCount; i++) { - if (final[i] > 1) final = final.SetChannel(i, 1f); - if (final[i] < -1) final = final.SetChannel(i, -1f); + 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]; diff --git a/ProjectObsidian/ProtoFlux/Audio/AudioAdder.cs b/ProjectObsidian/ProtoFlux/Audio/AudioAdder.cs index 58be3ec..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]; @@ -47,7 +53,7 @@ public void Read(Span buffer) where S : unmanaged, IAudioSample 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); + 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 cda5382..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) diff --git a/ProjectObsidian/ProtoFlux/Audio/AudioSubtractor.cs b/ProjectObsidian/ProtoFlux/Audio/AudioSubtractor.cs index e0bf207..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]; @@ -47,7 +53,7 @@ public void Read(Span buffer) where S : unmanaged, IAudioSample 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); + 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)