Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clamp audio buffer values to prevent audio breaking, Use IsActive, update filters to handle different types, use audio update callback #71

Merged
merged 2 commits into from
Dec 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions ProjectObsidian/Components/Audio/BandPassFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Elements.Assets;
using System.Net;
using ProtoFlux.Runtimes.Execution;
using System.Collections.Generic;

namespace Obsidian.Components.Audio
{
Expand All @@ -22,8 +23,8 @@ public class BandPassFilter : Component, IAudioSource, IWorldElement

private double lastTime;

private object lowFilter = null;
private object highFilter = null;
private Dictionary<Type, object> lowFilters = new();
private Dictionary<Type, object> highFilters = new();

public bool IsActive
{
Expand All @@ -37,24 +38,29 @@ public void Read<S>(Span<S> buffer) where S : unmanaged, IAudioSample<S>
if (!IsActive)
{
buffer.Fill(default(S));
lowFilters.Clear();
highFilters.Clear();
return;
}

Span<S> 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<S>();
lowFilters.Add(typeof(S), lowFilter);
}
if (highFilter == null)
if (!highFilters.TryGetValue(typeof(S), out object highFilter))
{
highFilter = new ButterworthFilter.FilterButterworth<S>();
highFilters.Add(typeof(S), highFilter);
}

((ButterworthFilter.FilterButterworth<S>)lowFilter).UpdateCoefficients(HighFrequency, (int)(tempBuffer.Length / (Engine.Current.AudioSystem.DSPTime - lastTime)), ButterworthFilter.FilterButterworth<S>.PassType.Lowpass, Resonance);
((ButterworthFilter.FilterButterworth<S>)highFilter).UpdateCoefficients(LowFrequency, (int)(tempBuffer.Length / (Engine.Current.AudioSystem.DSPTime - lastTime)), ButterworthFilter.FilterButterworth<S>.PassType.Highpass, Resonance);
((ButterworthFilter.FilterButterworth<S>)lowFilter).UpdateCoefficients(HighFrequency, Engine.AudioSystem.SampleRate, ButterworthFilter.FilterButterworth<S>.PassType.Lowpass, Resonance);
((ButterworthFilter.FilterButterworth<S>)highFilter).UpdateCoefficients(LowFrequency, Engine.AudioSystem.SampleRate, ButterworthFilter.FilterButterworth<S>.PassType.Highpass, Resonance);

for (int i = 0; i < tempBuffer.Length; i++)
{
Expand All @@ -79,8 +85,8 @@ protected override void OnChanges()
base.OnChanges();
if (Source.GetWasChangedAndClear())
{
lowFilter = null;
highFilter = null;
lowFilters.Clear();
highFilters.Clear();
}
}
}
Expand Down
20 changes: 13 additions & 7 deletions ProjectObsidian/Components/Audio/ButterworthFilter.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using FrooxEngine;
using Elements.Assets;
using System.Collections.Generic;

namespace Obsidian.Components.Audio;

Expand All @@ -19,7 +20,7 @@ public class ButterworthFilter : Component, IAudioSource, IWorldElement

private double lastTime;

private object filter = null;
private Dictionary<Type, object> filters = new();

public bool IsActive
{
Expand All @@ -42,7 +43,7 @@ protected override void OnChanges()
base.OnChanges();
if (Source.GetWasChangedAndClear())
{
filter = null;
filters.Clear();
}
}

Expand All @@ -53,6 +54,7 @@ public void Read<S>(Span<S> buffer) where S : unmanaged, IAudioSample<S>
if (!IsActive)
{
buffer.Fill(default(S));
filters.Clear();
return;
}

Expand All @@ -62,19 +64,17 @@ public void Read<S>(Span<S> buffer) where S : unmanaged, IAudioSample<S>

Source.Target.Read(span);

if (filter == null)
{
if (!filters.TryGetValue(typeof(S), out object filter)) {
filter = new FilterButterworth<S>();
filters.Add(typeof(S), filter);
}

((FilterButterworth<S>)filter).UpdateCoefficients(Frequency, (int)(span.Length / (Engine.Current.AudioSystem.DSPTime - lastTime)), LowPass ? FilterButterworth<S>.PassType.Lowpass : FilterButterworth<S>.PassType.Highpass, Resonance);
((FilterButterworth<S>)filter).UpdateCoefficients(Frequency, Engine.AudioSystem.SampleRate, LowPass ? FilterButterworth<S>.PassType.Lowpass : FilterButterworth<S>.PassType.Highpass, Resonance);

for (int i = 0; i < span.Length; i++)
{
((FilterButterworth<S>)filter).Update(ref span[i]);
}

lastTime = Engine.Current.AudioSystem.DSPTime;
}

public class FilterButterworth<S> where S: unmanaged, IAudioSample<S>
Expand Down Expand Up @@ -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;

Expand Down
17 changes: 10 additions & 7 deletions ProjectObsidian/Components/Audio/FrequencyModulator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,18 @@ public void Read<S>(Span<S> buffer) where S : unmanaged, IAudioSample<S>
// 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);
}
}
}
}
Expand Down
12 changes: 12 additions & 0 deletions ProjectObsidian/ProtoFlux/Audio/AudioAdder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ public class AudioAdderProxy : ProtoFluxEngineProxy, IAudioSource

public void Read<S>(Span<S> buffer) where S : unmanaged, IAudioSample<S>
{
if (!IsActive)
{
buffer.Fill(default(S));
return;
}

Span<S> newBuffer = stackalloc S[buffer.Length];
newBuffer = buffer;
Span<S> newBuffer2 = stackalloc S[buffer.Length];
Expand All @@ -43,6 +49,12 @@ public void Read<S>(Span<S> buffer) where S : unmanaged, IAudioSample<S>
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);
}
}
}
}
Expand Down
12 changes: 12 additions & 0 deletions ProjectObsidian/ProtoFlux/Audio/AudioMultiply.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ public class AudioMultiplyProxy : ProtoFluxEngineProxy, IAudioSource

public void Read<S>(Span<S> buffer) where S : unmanaged, IAudioSample<S>
{
if (!IsActive)
{
buffer.Fill(default(S));
return;
}

Span<S> newBuffer = stackalloc S[buffer.Length];
newBuffer = buffer;
if (AudioInput != null)
Expand All @@ -35,6 +41,12 @@ public void Read<S>(Span<S> buffer) where S : unmanaged, IAudioSample<S>
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);
}
}
}
}
Expand Down
12 changes: 12 additions & 0 deletions ProjectObsidian/ProtoFlux/Audio/AudioSubtractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ public class AudioSubtractorProxy : ProtoFluxEngineProxy, IAudioSource

public void Read<S>(Span<S> buffer) where S : unmanaged, IAudioSample<S>
{
if (!IsActive)
{
buffer.Fill(default(S));
return;
}

Span<S> newBuffer = stackalloc S[buffer.Length];
newBuffer = buffer;
Span<S> newBuffer2 = stackalloc S[buffer.Length];
Expand All @@ -43,6 +49,12 @@ public void Read<S>(Span<S> buffer) where S : unmanaged, IAudioSample<S>
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);
}
}
}
}
Expand Down
39 changes: 29 additions & 10 deletions ProjectObsidian/ProtoFlux/Audio/SineGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -19,7 +17,7 @@ public class SineGeneratorProxy : ProtoFluxEngineProxy, IAudioSource

public float Phase;

public float time;
public double time;

private float[] tempBuffer;

Expand All @@ -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<S>(Span<S> buffer) where S : unmanaged, IAudioSample<S>
{
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<float, MonoSample>(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<FrooxEngineContext, SineGeneratorProxy>, IExecutionChangeListener<FrooxEngineContext>
Expand Down Expand Up @@ -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)
Expand Down
Loading