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 SawtoothGenerator and fix a problem with Frequency Quantize when the input is zero #72

Merged
merged 4 commits into from
Jan 1, 2025
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
197 changes: 197 additions & 0 deletions ProjectObsidian/ProtoFlux/Audio/SawtoothGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
using System;
using ProtoFlux.Core;
using ProtoFlux.Runtimes.Execution;
using FrooxEngine.ProtoFlux;
using FrooxEngine;
using Elements.Assets;
using Elements.Core;
using System.Runtime.InteropServices;

namespace ProtoFlux.Runtimes.Execution.Nodes.Obsidian.Audio
{
public class SawtoothGeneratorProxy : ProtoFluxEngineProxy, IAudioSource
{
public float Frequency;

public float Amplitude;

public float Phase;

public double time;

private float[] tempBuffer;

public bool Active;

public bool IsActive => Active;

public int ChannelCount => 1;

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);
var temptime = time;
temptime %= (1f / Frequency);
var clampedAmplitude = MathX.Clamp01(Amplitude);
float advance = (1f / (float)base.Engine.AudioSystem.SampleRate);
for (int i = 0; i < buffer.Length; i++)
{
tempBuffer[i] = (2.0f * ((((float)temptime / (1f / Frequency)) + Phase) % 1.0f) - 1.0f) * clampedAmplitude;
if (tempBuffer[i] > 1f) tempBuffer[i] = 1f;
else if (tempBuffer[i] < -1f) tempBuffer[i] = -1f;
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 SawtoothGenerator : ProxyVoidNode<FrooxEngineContext, SawtoothGeneratorProxy>, IExecutionChangeListener<FrooxEngineContext>
{
[ChangeListener]
[DefaultValueAttribute(440f)]
public readonly ValueInput<float> Frequency;

[ChangeListener]
[DefaultValueAttribute(1f)]
public readonly ValueInput<float> Amplitude;

[ChangeListener]
[DefaultValueAttribute(0f)]
public readonly ValueInput<float> Phase;

[PossibleContinuations(new string[] { "OnReset" })]
public readonly Operation Reset;

public Continuation OnReset;

public readonly ObjectOutput<IAudioSource> AudioOutput;

private ObjectStore<Action<IChangeable>> _enabledChangedHandler;

private ObjectStore<SlotEvent> _activeChangedHandler;

public bool ValueListensToChanges { get; private set; }

private bool ShouldListen(SawtoothGeneratorProxy proxy)
{
if (proxy.Enabled)
{
return proxy.Slot.IsActive;
}
return false;
}

protected override void ProxyAdded(SawtoothGeneratorProxy proxy, FrooxEngineContext context)
{
base.ProxyAdded(proxy, context);
NodeContextPath path = context.CaptureContextPath();
ProtoFluxNodeGroup group = context.Group;
context.GetEventDispatcher(out var dispatcher);
Action<IChangeable> enabledHandler = delegate
{
dispatcher.ScheduleEvent(path, delegate (FrooxEngineContext c)
{
UpdateListenerState(c);
});
};
SlotEvent activeHandler = delegate
{
dispatcher.ScheduleEvent(path, delegate (FrooxEngineContext c)
{
UpdateListenerState(c);
});
};
proxy.EnabledField.Changed += enabledHandler;
proxy.Slot.ActiveChanged += activeHandler;
_enabledChangedHandler.Write(enabledHandler, context);
_activeChangedHandler.Write(activeHandler, context);
ValueListensToChanges = ShouldListen(proxy);
proxy.Active = ValueListensToChanges;
}

protected override void ProxyRemoved(SawtoothGeneratorProxy proxy, FrooxEngineContext context, bool inUseByAnotherInstance)
{
if (!inUseByAnotherInstance)
{
proxy.EnabledField.Changed -= _enabledChangedHandler.Read(context);
proxy.Slot.ActiveChanged -= _activeChangedHandler.Read(context);
_enabledChangedHandler.Clear(context);
_activeChangedHandler.Clear(context);
proxy.Active = false;
}
}

protected void UpdateListenerState(FrooxEngineContext context)
{
SawtoothGeneratorProxy proxy = GetProxy(context);
if (proxy != null)
{
bool shouldListen = ShouldListen(proxy);
if (shouldListen != ValueListensToChanges)
{
ValueListensToChanges = shouldListen;
context.Group.MarkChangeTrackingDirty();
proxy.Active = shouldListen;
}
}
}

public void Changed(FrooxEngineContext context)
{
SawtoothGeneratorProxy proxy = GetProxy(context);
if (proxy == null)
{
return;
}
proxy.Amplitude = Amplitude.Evaluate(context, 1f);
proxy.Phase = Phase.Evaluate(context, 0f);
proxy.Frequency = Frequency.Evaluate(context, 440f);
}

protected override void ComputeOutputs(FrooxEngineContext context)
{
SawtoothGeneratorProxy proxy = GetProxy(context);
AudioOutput.Write(proxy, context);
}

private IOperation DoReset(FrooxEngineContext context)
{
SawtoothGeneratorProxy proxy = GetProxy(context);
if (proxy == null)
{
return null;
}
proxy.time = 0f;
return OnReset.Target;
}

public SawtoothGenerator()
{
AudioOutput = new ObjectOutput<IAudioSource>(this);
Reset = new Operation(this, 0);
}
}
}
4 changes: 3 additions & 1 deletion ProjectObsidian/ProtoFlux/Math/FrequencyQuantize.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ protected override void ComputeOutputs(FrooxEngineContext context)
var offset = Offset.Evaluate(context);
var rootNote = 69 + offset;

var midi = FreqToNearestMidi(freq);
int midi;
if (freq == 0) midi = 0;
else midi = FreqToNearestMidi(freq);

bool inScale = false;
List<int> scaleList;
Expand Down
Loading