From 8f4b14c1cb543b3a52a84d385c9d5732f73462f1 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Fri, 3 Jan 2025 20:56:32 +0000 Subject: [PATCH] Add SquareGenerator and TriangleGenerator --- .../ProtoFlux/Audio/SquareGenerator.cs | 211 ++++++++++++++++++ .../ProtoFlux/Audio/TriangleGenerator.cs | 202 +++++++++++++++++ 2 files changed, 413 insertions(+) create mode 100644 ProjectObsidian/ProtoFlux/Audio/SquareGenerator.cs create mode 100644 ProjectObsidian/ProtoFlux/Audio/TriangleGenerator.cs diff --git a/ProjectObsidian/ProtoFlux/Audio/SquareGenerator.cs b/ProjectObsidian/ProtoFlux/Audio/SquareGenerator.cs new file mode 100644 index 0000000..d483e95 --- /dev/null +++ b/ProjectObsidian/ProtoFlux/Audio/SquareGenerator.cs @@ -0,0 +1,211 @@ +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 SquareGeneratorProxy : ProtoFluxEngineProxy, IAudioSource + { + public float Frequency; + + public float Amplitude; + + public float Phase; + + public float PulseWidth; + + public double time; + + private float[] tempBuffer; + + public bool Active; + + public bool IsActive => Active; + + public int ChannelCount => 1; + + private bool updateTime; + + public void Read(Span buffer) where S : unmanaged, IAudioSample + { + if (!IsActive) + { + buffer.Fill(default(S)); + return; + } + + tempBuffer = tempBuffer.EnsureSize(buffer.Length); + var temptime = time; + float period = (1f / Frequency); + temptime %= period; + var clampedAmplitude = MathX.Clamp01(Amplitude); + float advance = (1f / (float)base.Engine.AudioSystem.SampleRate); + + for (int i = 0; i < buffer.Length; i++) + { + if ((temptime + (Phase * period)) % period <= PulseWidth / Frequency) + { + tempBuffer[i] = 1f * clampedAmplitude; + } + else + { + tempBuffer[i] = -1f * clampedAmplitude; + } + temptime += advance; + } + if (updateTime) + { + time = temptime; + updateTime = false; + } + double position = 0.0; + MonoSample lastSample = default(MonoSample); + MemoryMarshal.Cast(MemoryExtensions.AsSpan(tempBuffer)).CopySamples(buffer, ref position, ref lastSample); + } + + protected override void OnStart() + { + Engine.AudioSystem.AudioUpdate += () => + { + updateTime = true; + }; + } + } + [NodeCategory("Obsidian/Audio")] + public class SquareGenerator : ProxyVoidNode, IExecutionChangeListener + { + [ChangeListener] + [DefaultValueAttribute(440f)] + public readonly ValueInput Frequency; + + [ChangeListener] + [DefaultValueAttribute(1f)] + public readonly ValueInput Amplitude; + + [ChangeListener] + [DefaultValueAttribute(0f)] + public readonly ValueInput Phase; + + [ChangeListener] + [DefaultValueAttribute(0.5f)] + public readonly ValueInput PulseWidth; + + [PossibleContinuations(new string[] { "OnReset" })] + public readonly Operation Reset; + + public Continuation OnReset; + + public readonly ObjectOutput AudioOutput; + + private ObjectStore> _enabledChangedHandler; + + private ObjectStore _activeChangedHandler; + + public bool ValueListensToChanges { get; private set; } + + private bool ShouldListen(SquareGeneratorProxy proxy) + { + if (proxy.Enabled) + { + return proxy.Slot.IsActive; + } + return false; + } + + protected override void ProxyAdded(SquareGeneratorProxy proxy, FrooxEngineContext context) + { + base.ProxyAdded(proxy, context); + NodeContextPath path = context.CaptureContextPath(); + ProtoFluxNodeGroup group = context.Group; + context.GetEventDispatcher(out var dispatcher); + Action 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(SquareGeneratorProxy 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) + { + SquareGeneratorProxy 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) + { + SquareGeneratorProxy 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); + proxy.PulseWidth = PulseWidth.Evaluate(context, 0.5f); + } + + protected override void ComputeOutputs(FrooxEngineContext context) + { + SquareGeneratorProxy proxy = GetProxy(context); + AudioOutput.Write(proxy, context); + } + + private IOperation DoReset(FrooxEngineContext context) + { + SquareGeneratorProxy proxy = GetProxy(context); + if (proxy == null) + { + return null; + } + proxy.time = 0f; + return OnReset.Target; + } + + public SquareGenerator() + { + AudioOutput = new ObjectOutput(this); + Reset = new Operation(this, 0); + } + } +} \ No newline at end of file diff --git a/ProjectObsidian/ProtoFlux/Audio/TriangleGenerator.cs b/ProjectObsidian/ProtoFlux/Audio/TriangleGenerator.cs new file mode 100644 index 0000000..35433be --- /dev/null +++ b/ProjectObsidian/ProtoFlux/Audio/TriangleGenerator.cs @@ -0,0 +1,202 @@ +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 TriangleGeneratorProxy : 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(Span buffer) where S : unmanaged, IAudioSample + { + if (!IsActive) + { + buffer.Fill(default(S)); + return; + } + + tempBuffer = tempBuffer.EnsureSize(buffer.Length); + var temptime = time; + float period = (1f / Frequency); + temptime %= period; + var clampedAmplitude = MathX.Clamp01(Amplitude); + float advance = (1f / (float)base.Engine.AudioSystem.SampleRate); + + for (int i = 0; i < buffer.Length; i++) + { + double t = temptime * Frequency + Phase; + tempBuffer[i] = clampedAmplitude * (2f * (float)MathX.Abs(2f * (t - MathX.Floor(t + 0.5f))) - 1f); + + 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(MemoryExtensions.AsSpan(tempBuffer)).CopySamples(buffer, ref position, ref lastSample); + } + + protected override void OnStart() + { + Engine.AudioSystem.AudioUpdate += () => + { + updateTime = true; + }; + } + } + [NodeCategory("Obsidian/Audio")] + public class TriangleGenerator : ProxyVoidNode, IExecutionChangeListener + { + [ChangeListener] + [DefaultValueAttribute(440f)] + public readonly ValueInput Frequency; + + [ChangeListener] + [DefaultValueAttribute(1f)] + public readonly ValueInput Amplitude; + + [ChangeListener] + [DefaultValueAttribute(0f)] + public readonly ValueInput Phase; + + [PossibleContinuations(new string[] { "OnReset" })] + public readonly Operation Reset; + + public Continuation OnReset; + + public readonly ObjectOutput AudioOutput; + + private ObjectStore> _enabledChangedHandler; + + private ObjectStore _activeChangedHandler; + + public bool ValueListensToChanges { get; private set; } + + private bool ShouldListen(TriangleGeneratorProxy proxy) + { + if (proxy.Enabled) + { + return proxy.Slot.IsActive; + } + return false; + } + + protected override void ProxyAdded(TriangleGeneratorProxy proxy, FrooxEngineContext context) + { + base.ProxyAdded(proxy, context); + NodeContextPath path = context.CaptureContextPath(); + ProtoFluxNodeGroup group = context.Group; + context.GetEventDispatcher(out var dispatcher); + Action 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(TriangleGeneratorProxy 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) + { + TriangleGeneratorProxy 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) + { + TriangleGeneratorProxy 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) + { + TriangleGeneratorProxy proxy = GetProxy(context); + AudioOutput.Write(proxy, context); + } + + private IOperation DoReset(FrooxEngineContext context) + { + TriangleGeneratorProxy proxy = GetProxy(context); + if (proxy == null) + { + return null; + } + proxy.time = 0f; + return OnReset.Target; + } + + public TriangleGenerator() + { + AudioOutput = new ObjectOutput(this); + Reset = new Operation(this, 0); + } + } +} \ No newline at end of file