Skip to content

Commit

Permalink
Merge pull request #71 from Nytra/audioWork
Browse files Browse the repository at this point in the history
Clamp audio buffer values to prevent audio breaking, Use IsActive, update filters to handle different types, use audio update callback
  • Loading branch information
Xlinka authored Dec 31, 2024
2 parents 6e7b125 + a851a5f commit 6d69152
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 32 deletions.
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

0 comments on commit 6d69152

Please sign in to comment.