diff --git a/ProjectObsidian/ProtoFlux/Audio/SawtoothGenerator.cs b/ProjectObsidian/ProtoFlux/Audio/SawtoothGenerator.cs new file mode 100644 index 0000000..e288a05 --- /dev/null +++ b/ProjectObsidian/ProtoFlux/Audio/SawtoothGenerator.cs @@ -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(Span buffer) where S : unmanaged, IAudioSample + { + 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(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, 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(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 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(this); + Reset = new Operation(this, 0); + } + } +} \ No newline at end of file diff --git a/ProjectObsidian/ProtoFlux/Math/FrequencyQuantize.cs b/ProjectObsidian/ProtoFlux/Math/FrequencyQuantize.cs index 37bf747..7d2fc94 100644 --- a/ProjectObsidian/ProtoFlux/Math/FrequencyQuantize.cs +++ b/ProjectObsidian/ProtoFlux/Math/FrequencyQuantize.cs @@ -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 scaleList;