diff --git a/ProjectObsidian/Components/Audio/BandPassFilter.cs b/ProjectObsidian/Components/Audio/BandPassFilter.cs new file mode 100644 index 0000000..aa162d4 --- /dev/null +++ b/ProjectObsidian/Components/Audio/BandPassFilter.cs @@ -0,0 +1,86 @@ +using System; +using FrooxEngine; +using Elements.Assets; +using System.Net; +using ProtoFlux.Runtimes.Execution; + +namespace Obsidian.Components.Audio +{ + [Category(new string[] { "Obsidian/Audio" })] + public class BandPassFilter : Component, IAudioSource, IWorldElement + { + [Range(0.1f, 1.41f, "0.00")] + public readonly Sync Resonance; + + [Range(20f, 20000f, "0.00")] + public readonly Sync LowFrequency; + + [Range(20f, 20000f, "0.00")] + public readonly Sync HighFrequency; + + public readonly SyncRef Source; + + private double lastTime; + + private object lowFilter = null; + private object highFilter = null; + + public bool IsActive + { + get => Source.Target != null && Source.Target.IsActive; + } + + public int ChannelCount => Source.Target?.ChannelCount ?? 0; + + public void Read(Span buffer) where S : unmanaged, IAudioSample + { + if (!IsActive) + { + return; + } + + Span tempBuffer = stackalloc S[buffer.Length]; + tempBuffer = buffer; + Source.Target.Read(tempBuffer); + + if (lowFilter == null) + { + lowFilter = new ButterworthFilter.FilterButterworth(); + } + if (highFilter == null) + { + highFilter = new ButterworthFilter.FilterButterworth(); + } + + ((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); + + for (int i = 0; i < tempBuffer.Length; i++) + { + ((ButterworthFilter.FilterButterworth)lowFilter).Update(ref tempBuffer[i]); + ((ButterworthFilter.FilterButterworth)highFilter).Update(ref tempBuffer[i]); + } + + lastTime = Engine.Current.AudioSystem.DSPTime; + } + + protected override void OnAwake() + { + base.OnAwake(); + Resonance.Value = 1.41f; + LowFrequency.Value = 20f; + HighFrequency.Value = 20000f; + lastTime = Engine.Current.AudioSystem.DSPTime; + } + + protected override void OnChanges() + { + base.OnChanges(); + if (Source.GetWasChangedAndClear()) + { + lowFilter = null; + highFilter = null; + } + } + } +} \ No newline at end of file diff --git a/ProjectObsidian/Components/Audio/ButterworthFilter.cs b/ProjectObsidian/Components/Audio/ButterworthFilter.cs index 68ee19f..cf5a70f 100644 --- a/ProjectObsidian/Components/Audio/ButterworthFilter.cs +++ b/ProjectObsidian/Components/Audio/ButterworthFilter.cs @@ -7,7 +7,7 @@ namespace Obsidian.Components.Audio; [Category(new string[] { "Obsidian/Audio" })] public class ButterworthFilter : Component, IAudioSource, IWorldElement { - [Range(0f, 10000f, "0.00")] + [Range(20f, 20000f, "0.00")] public readonly Sync Frequency; [Range(0.1f, 1.41f, "0.00")] @@ -19,6 +19,8 @@ public class ButterworthFilter : Component, IAudioSource, IWorldElement private double lastTime; + private object filter = null; + public bool IsActive { get @@ -30,7 +32,18 @@ public bool IsActive protected override void OnAwake() { base.OnAwake(); - lastTime = -1; + lastTime = Engine.Current.AudioSystem.DSPTime; + Frequency.Value = 20f; + Resonance.Value = 1.41f; + } + + protected override void OnChanges() + { + base.OnChanges(); + if (Source.GetWasChangedAndClear()) + { + filter = null; + } } public int ChannelCount => Source.Target?.ChannelCount ?? 0; @@ -48,11 +61,16 @@ public void Read(Span buffer) where S : unmanaged, IAudioSample Source.Target.Read(span); - var filter = new FilterButterworth(Frequency, (int)(span.Length / (Engine.Current.AudioSystem.DSPTime - lastTime)), LowPass ? FilterButterworth.PassType.Lowpass : FilterButterworth.PassType.Highpass, Resonance); + if (filter == null) + { + filter = new FilterButterworth(); + } + + ((FilterButterworth)filter).UpdateCoefficients(Frequency, (int)(span.Length / (Engine.Current.AudioSystem.DSPTime - lastTime)), LowPass ? FilterButterworth.PassType.Lowpass : FilterButterworth.PassType.Highpass, Resonance); for (int i = 0; i < span.Length; i++) { - filter.Update(ref span[i]); + ((FilterButterworth)filter).Update(ref span[i]); } lastTime = Engine.Current.AudioSystem.DSPTime; @@ -69,7 +87,7 @@ public class FilterButterworth where S: unmanaged, IAudioSample private readonly int sampleRate; private readonly PassType passType; - private readonly float c, a1, a2, a3, b1, b2; + private float c, a1, a2, a3, b1, b2; /// /// Array of input values, latest are in front @@ -81,13 +99,8 @@ public class FilterButterworth where S: unmanaged, IAudioSample /// private S[] outputHistory = new S[3]; - public FilterButterworth(float frequency, int sampleRate, PassType passType, float resonance) + public void UpdateCoefficients(float frequency, int sampleRate, PassType passType, float resonance) { - this.resonance = resonance; - this.frequency = frequency; - this.sampleRate = sampleRate; - this.passType = passType; - switch (passType) { case PassType.Lowpass: diff --git a/ProjectObsidian/Components/Audio/FrequencyModulator.cs b/ProjectObsidian/Components/Audio/FrequencyModulator.cs new file mode 100644 index 0000000..ddc5b03 --- /dev/null +++ b/ProjectObsidian/Components/Audio/FrequencyModulator.cs @@ -0,0 +1,79 @@ +using System; +using FrooxEngine; +using Elements.Assets; + +namespace Obsidian.Components.Audio +{ + [Category(new string[] { "Obsidian/Audio" })] + public class FrequencyModulator : Component, IAudioSource, IWorldElement + { + [Range(0f, 1000f, "0.00")] + public readonly Sync ModulationIndex; + + public readonly SyncRef CarrierSource; + public readonly SyncRef ModulatorSource; + + public bool IsActive + { + get + { + return CarrierSource.Target != null && + ModulatorSource.Target != null && + CarrierSource.Target.IsActive && + ModulatorSource.Target.IsActive; + } + } + + public int ChannelCount + { + get + { + return CarrierSource.Target?.ChannelCount ?? 0; + } + } + + protected override void OnAwake() + { + base.OnAwake(); + ModulationIndex.Value = 100f; // Default modulation index + } + + public void Read(Span buffer) where S : unmanaged, IAudioSample + { + if (!IsActive) + { + return; + } + + int channelCount = ChannelCount; + if (channelCount == 0) + { + return; + } + + // Temporary buffers for carrier and modulator sources + Span carrierBuffer = stackalloc S[buffer.Length]; + Span modulatorBuffer = stackalloc S[buffer.Length]; + + // Read data from sources + CarrierSource.Target.Read(carrierBuffer); + ModulatorSource.Target.Read(modulatorBuffer); + + float modulationIndex = ModulationIndex.Value; + + // 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; + + // Compute frequency modulation + 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); + } + } + } +} \ No newline at end of file