Skip to content

Commit

Permalink
Merge pull request #57 from Nytra/testing2
Browse files Browse the repository at this point in the history
Add audio filtering components
  • Loading branch information
Xlinka authored Nov 25, 2024
2 parents 49269bb + ae4e659 commit e68f0b0
Show file tree
Hide file tree
Showing 3 changed files with 203 additions and 0 deletions.
142 changes: 142 additions & 0 deletions ProjectObsidian/Components/Audio/ButterworthFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
using System;
using FrooxEngine;
using Elements.Assets;

namespace Obsidian.Components.Audio;

[Category(new string[] { "Obsidian/Audio" })]
public class ButterworthFilter : Component, IAudioSource, IWorldElement
{
[Range(0f, 10000f, "0.00")]
public readonly Sync<float> Frequency;

[Range(0.1f, 1.41f, "0.00")]
public readonly Sync<float> Resonance;

public readonly Sync<bool> LowPass;

public readonly SyncRef<IAudioSource> Source;

private double lastTime;

public bool IsActive
{
get
{
return Source.Target != null && Source.Target.IsActive;
}
}

protected override void OnAwake()
{
base.OnAwake();
lastTime = -1;
}

public int ChannelCount => Source.Target?.ChannelCount ?? 0;

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

Span<S> span = stackalloc S[buffer.Length];

span = buffer;

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

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

lastTime = Engine.Current.AudioSystem.DSPTime;
}

public class FilterButterworth<S> where S: unmanaged, IAudioSample<S>
{
/// <summary>
/// rez amount, from sqrt(2) to ~ 0.1
/// </summary>
private readonly float resonance;

private readonly float frequency;
private readonly int sampleRate;
private readonly PassType passType;

private readonly float c, a1, a2, a3, b1, b2;

/// <summary>
/// Array of input values, latest are in front
/// </summary>
private S[] inputHistory = new S[2];

/// <summary>
/// Array of output values, latest are in front
/// </summary>
private S[] outputHistory = new S[3];

public FilterButterworth(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:
c = 1.0f / (float)Math.Tan(Math.PI * frequency / sampleRate);
a1 = 1.0f / (1.0f + resonance * c + c * c);
a2 = 2f * a1;
a3 = a1;
b1 = 2.0f * (1.0f - c * c) * a1;
b2 = (1.0f - resonance * c + c * c) * a1;
break;
case PassType.Highpass:
c = (float)Math.Tan(Math.PI * frequency / sampleRate);
a1 = 1.0f / (1.0f + resonance * c + c * c);
a2 = -2f * a1;
a3 = a1;
b1 = 2.0f * (c * c - 1.0f) * a1;
b2 = (1.0f - resonance * c + c * c) * a1;
break;
}
}

public enum PassType
{
Highpass,
Lowpass,
}

public void Update(ref S newInput)
{
S first = newInput.Multiply(a1);
S second = this.inputHistory[0].Multiply(a2);
S third = this.inputHistory[1].Multiply(a3);
S fourth = this.outputHistory[0].Multiply(b1);
S fifth = this.outputHistory[1].Multiply(b2);
S final = first.Add(second).Add(third).Subtract(fourth).Subtract(fifth);

this.inputHistory[1] = this.inputHistory[0];
this.inputHistory[0] = newInput;

this.outputHistory[2] = this.outputHistory[1];
this.outputHistory[1] = this.outputHistory[0];
this.outputHistory[0] = final;

newInput = final;
}

public S Value
{
get { return this.outputHistory[0]; }
}
}
}
60 changes: 60 additions & 0 deletions ProjectObsidian/Components/Audio/EMA_IIR_SmoothSignal.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System;
using FrooxEngine;
using Elements.Assets;

namespace Obsidian.Components.Audio;

[Category(new string[] { "Obsidian/Audio" })]
public class EMA_IIR_SmoothSignal : Component, IAudioSource, IWorldElement
{
[Range(0f, 1f, "0.00")]
public readonly Sync<float> SmoothingFactor;

public readonly SyncRef<IAudioSource> Source;

public bool IsActive
{
get
{
return 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> span = stackalloc S[buffer.Length];

span = buffer;

Source.Target.Read(span);

EMAIIRSmoothSignal(ref span, span.Length, SmoothingFactor);
}

// smoothingFactor is between 0.0 (no smoothing) and 0.9999.. (almost smoothing to DC) - *kind* of the inverse of cutoff frequency
public void EMAIIRSmoothSignal<S>(ref Span<S> input, int N, float smoothingFactor = 0.8f) where S : unmanaged, IAudioSample<S>
{
// forward EMA IIR
S acc = input[0];
for (int i = 0; i < N; ++i)
{
acc = input[i].LerpTo(acc, smoothingFactor);
input[i] = acc;
}

// backward EMA IIR - required only if we need to preserve the phase (aka make the filter symetric) - we usually want this
acc = input[N - 1];
for (int i = N - 1; i >= 0; --i)
{
acc = input[i].LerpTo(acc, smoothingFactor);
input[i] = acc;
}
}
}
1 change: 1 addition & 0 deletions ProjectObsidian/ProjectObsidian.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
<ProjectReference Include="..\ProjectObsidian.SourceGenerators\SourceGenerators.csproj" OutputItemType="Analyzer" />
<PackageReference Include="managed-midi" Version="1.10.1" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3"> </PackageReference>
<PackageReference Include="System.Memory" Version="4.6.0" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Copy SourceFiles="$(TargetPath)" DestinationFolder="$(ResonitePath)Libraries" />
Expand Down

0 comments on commit e68f0b0

Please sign in to comment.