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

Add BandPassFilter, FrequencyModulator, update ButterworthFilter defaults, improve filter sound #58

Merged
merged 5 commits into from
Nov 26, 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
86 changes: 86 additions & 0 deletions ProjectObsidian/Components/Audio/BandPassFilter.cs
Original file line number Diff line number Diff line change
@@ -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<float> Resonance;

[Range(20f, 20000f, "0.00")]
public readonly Sync<float> LowFrequency;

[Range(20f, 20000f, "0.00")]
public readonly Sync<float> HighFrequency;

public readonly SyncRef<IAudioSource> 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<S>(Span<S> buffer) where S : unmanaged, IAudioSample<S>
{
if (!IsActive)
{
return;
}

Span<S> tempBuffer = stackalloc S[buffer.Length];
tempBuffer = buffer;
Source.Target.Read(tempBuffer);

if (lowFilter == null)
{
lowFilter = new ButterworthFilter.FilterButterworth<S>();
}
if (highFilter == null)
{
highFilter = new ButterworthFilter.FilterButterworth<S>();
}

((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);

for (int i = 0; i < tempBuffer.Length; i++)
{
((ButterworthFilter.FilterButterworth<S>)lowFilter).Update(ref tempBuffer[i]);
((ButterworthFilter.FilterButterworth<S>)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;
}
}
}
}
35 changes: 24 additions & 11 deletions ProjectObsidian/Components/Audio/ButterworthFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<float> Frequency;

[Range(0.1f, 1.41f, "0.00")]
Expand All @@ -19,6 +19,8 @@ public class ButterworthFilter : Component, IAudioSource, IWorldElement

private double lastTime;

private object filter = null;

public bool IsActive
{
get
Expand All @@ -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;
Expand All @@ -48,11 +61,16 @@ public void Read<S>(Span<S> buffer) where S : unmanaged, IAudioSample<S>

Source.Target.Read(span);

var filter = new FilterButterworth<S>(Frequency, (int)(span.Length / (Engine.Current.AudioSystem.DSPTime - lastTime)), LowPass ? FilterButterworth<S>.PassType.Lowpass : FilterButterworth<S>.PassType.Highpass, Resonance);
if (filter == null)
{
filter = new FilterButterworth<S>();
}

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

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

lastTime = Engine.Current.AudioSystem.DSPTime;
Expand All @@ -69,7 +87,7 @@ public class FilterButterworth<S> where S: unmanaged, IAudioSample<S>
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;

/// <summary>
/// Array of input values, latest are in front
Expand All @@ -81,13 +99,8 @@ public class FilterButterworth<S> where S: unmanaged, IAudioSample<S>
/// </summary>
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:
Expand Down
79 changes: 79 additions & 0 deletions ProjectObsidian/Components/Audio/FrequencyModulator.cs
Original file line number Diff line number Diff line change
@@ -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<float> ModulationIndex;

public readonly SyncRef<IAudioSource> CarrierSource;
public readonly SyncRef<IAudioSource> 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<S>(Span<S> buffer) where S : unmanaged, IAudioSample<S>
{
if (!IsActive)
{
return;
}

int channelCount = ChannelCount;
if (channelCount == 0)
{
return;
}

// Temporary buffers for carrier and modulator sources
Span<S> carrierBuffer = stackalloc S[buffer.Length];
Span<S> 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);
}
}
}
}