From 45657ff8619e3c39b9843d44e2b37bf9d9b46d08 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Fri, 13 Dec 2024 00:54:19 +0000 Subject: [PATCH 1/5] Stash stuff here --- .../Components/Audio/ButterworthFilter.cs | 1 + .../Components/Audio/EMA_IIR_SmoothSignal.cs | 1 + ProjectObsidian/ProtoFlux/Audio/TestAudio.cs | 138 ++++++++++++++++++ 3 files changed, 140 insertions(+) create mode 100644 ProjectObsidian/ProtoFlux/Audio/TestAudio.cs diff --git a/ProjectObsidian/Components/Audio/ButterworthFilter.cs b/ProjectObsidian/Components/Audio/ButterworthFilter.cs index cf5a70f..ca59140 100644 --- a/ProjectObsidian/Components/Audio/ButterworthFilter.cs +++ b/ProjectObsidian/Components/Audio/ButterworthFilter.cs @@ -52,6 +52,7 @@ public void Read(Span buffer) where S : unmanaged, IAudioSample { if (!IsActive) { + buffer.Fill(default(S)); return; } diff --git a/ProjectObsidian/Components/Audio/EMA_IIR_SmoothSignal.cs b/ProjectObsidian/Components/Audio/EMA_IIR_SmoothSignal.cs index 3cf78b2..7d49be5 100644 --- a/ProjectObsidian/Components/Audio/EMA_IIR_SmoothSignal.cs +++ b/ProjectObsidian/Components/Audio/EMA_IIR_SmoothSignal.cs @@ -26,6 +26,7 @@ public void Read(Span buffer) where S : unmanaged, IAudioSample { if (!IsActive) { + buffer.Fill(default(S)); return; } diff --git a/ProjectObsidian/ProtoFlux/Audio/TestAudio.cs b/ProjectObsidian/ProtoFlux/Audio/TestAudio.cs new file mode 100644 index 0000000..0d7a49b --- /dev/null +++ b/ProjectObsidian/ProtoFlux/Audio/TestAudio.cs @@ -0,0 +1,138 @@ +//using System; +//using Elements.Assets; +//using Elements.Core; +//using FrooxEngine; +//using FrooxEngine.ProtoFlux; +//using Obsidian.Components.Audio; +//using ProtoFlux.Core; +//using ProtoFlux.Runtimes.Execution; + +//namespace ProtoFlux.Runtimes.Execution.Nodes.Obsidian.Playback +//{ +// [NodeCategory("Obsidian/Audio")] +// public class TestAudio : ProxyVoidNode +// { +// public class Proxy : ProtoFluxEngineProxy, IAudioSource +// { +// public readonly SyncRef Source; +// public readonly Sync SmoothingFactor; + +// public bool IsActive => Source.Target != null && Source.Target.IsActive; + +// public int ChannelCount => Source.Target?.ChannelCount ?? 0; + +// public void Read(Span buffer) where S : unmanaged, IAudioSample +// { +// if (!IsActive) +// { +// return; +// } + +// Span span = stackalloc S[buffer.Length]; + +// span = buffer; + +// Source.Target.Read(span); + +// EMA_IIR_SmoothSignal.EMAIIRSmoothSignal(ref span, span.Length, SmoothingFactor); +// } +// } + +// public readonly ObjectInput Source; +// public readonly ValueInput SmoothingFactor; +// public readonly ObjectOutput Result; + +// public bool SourceListensToChanges { get; private set; } + +// private ObjectStore> _enabledChangedHandler; + +// private ObjectStore _activeChangedHandler; + +// private bool ShouldListen(Proxy proxy) +// { +// if (proxy.Enabled && proxy.Slot.IsActive) +// { +// return proxy.Source.Target != null; +// } +// return false; +// } + +// protected void UpdateListenerState(FrooxEngineContext context) +// { +// Proxy proxy = GetProxy(context); +// if (proxy != null) +// { +// bool flag = ShouldListen(proxy); +// if (flag != SourceListensToChanges) +// { +// SourceListensToChanges = flag; +// context.Group.MarkChangeTrackingDirty(); +// } +// } +// } + +// protected override void ProxyAdded(Proxy proxy, FrooxEngineContext context) +// { +// NodeContextPath path = context.CaptureContextPath(); +// ProtoFluxNodeGroup group = context.Group; +// context.GetEventDispatcher(out var dispatcher); +// Action value2 = delegate +// { +// dispatcher.ScheduleEvent(path, delegate (FrooxEngineContext c) +// { +// UpdateListenerState(c); +// }); +// }; +// SlotEvent value3 = delegate +// { +// dispatcher.ScheduleEvent(path, delegate (FrooxEngineContext c) +// { +// UpdateListenerState(c); +// }); +// }; +// proxy.EnabledField.Changed += value2; +// proxy.Slot.ActiveChanged += value3; +// _enabledChangedHandler.Write(value2, context); +// _activeChangedHandler.Write(value3, context); +// SourceListensToChanges = ShouldListen(proxy); +// } + +// protected override void ProxyRemoved(Proxy 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); +// } +// } + +// public void Changed(FrooxEngineContext context) +// { +// Proxy proxy = GetProxy(context); +// if (proxy == null || proxy.IsRemoved) +// { +// return; +// } +// IAudioSource source = proxy.Source.Target; +// if (source == null || !proxy.Source.Target.IsActive) +// { +// return; +// } +// try +// { +// context.World.UpdateManager.NestCurrentlyUpdating(proxy); +// } +// finally +// { +// context.World.UpdateManager.PopCurrentlyUpdating(proxy); +// } +// } + +// protected override void ComputeOutputs(FrooxEngineContext context) +// { +// Proxy proxy = GetProxy(context); +// } +// } +//} From 42a1d8fbb9bb4b0fb6eeb73a1db99878aacfab5b Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Fri, 27 Dec 2024 08:52:03 +0000 Subject: [PATCH 2/5] Add audio processing nodes and fixup binding generator to handle ProxyVoidNode --- .../BindingGenerator.cs | 9 +- ProjectObsidian/ProjectObsidian.csproj | 1 + ProjectObsidian/ProtoFlux/Audio/AudioAdder.cs | 159 ++++++++++ .../ProtoFlux/Audio/AudioSubtractor.cs | 159 ++++++++++ .../ProtoFlux/Audio/SineGenerator.cs | 172 +++++++++++ ProjectObsidian/ProtoFlux/Audio/Speaker.cs | 143 +++++++++ ProjectObsidian/ProtoFlux/Audio/TestAudio.cs | 287 +++++++++--------- 7 files changed, 791 insertions(+), 139 deletions(-) create mode 100644 ProjectObsidian/ProtoFlux/Audio/AudioAdder.cs create mode 100644 ProjectObsidian/ProtoFlux/Audio/AudioSubtractor.cs create mode 100644 ProjectObsidian/ProtoFlux/Audio/SineGenerator.cs create mode 100644 ProjectObsidian/ProtoFlux/Audio/Speaker.cs diff --git a/ProjectObsidian.SourceGenerators/BindingGenerator.cs b/ProjectObsidian.SourceGenerators/BindingGenerator.cs index e4b3d25..93fe080 100644 --- a/ProjectObsidian.SourceGenerators/BindingGenerator.cs +++ b/ProjectObsidian.SourceGenerators/BindingGenerator.cs @@ -107,6 +107,8 @@ public OrderedCount(string countVariableName, string methodName, string methodRe "AsyncActionNode", "AsyncActionFlowNode", "AsyncActionBreakableFlowNode", + + "ProxyVoidNode" }; private string UsingEnumerate => @@ -150,7 +152,7 @@ namespace {BindingPrefix}{_currentNameSpace}; {_genericTypesAttribute} {_oldTypeNameAttribute} [Category(new string[] {{""ProtoFlux/Runtimes/Execution/Nodes/{_category}""}})] -public partial class {_fullName} : global::FrooxEngine.ProtoFlux.Runtimes.Execution.{_baseType} {_constraints} +public partial class {_fullName} : global::{_baseTypeNamespace}{_baseType} {_constraints} {{ {(string.IsNullOrEmpty(_debug) ? "" : "//")}{_debug} {Declarations} @@ -182,6 +184,7 @@ public override N Instantiate() private string _additionalName = ""; public string BaseName; private string _baseType; + private string _baseTypeNamespace = "FrooxEngine.ProtoFlux.Runtimes.Execution."; private string _fullBaseType; private string _match; private string _category; @@ -307,6 +310,10 @@ public override void VisitClassDeclaration(ClassDeclarationSyntax node) var baseTypeName = firstBaseType.Type.ToString(); _baseType = baseTypeName; + if (baseTypeName.Contains("ProxyVoidNode")) + { + _baseTypeNamespace = "FrooxEngine.FrooxEngine.ProtoFlux."; + } if (!node.AttributeLists.Any()) { diff --git a/ProjectObsidian/ProjectObsidian.csproj b/ProjectObsidian/ProjectObsidian.csproj index 28ee510..6f42d64 100644 --- a/ProjectObsidian/ProjectObsidian.csproj +++ b/ProjectObsidian/ProjectObsidian.csproj @@ -73,6 +73,7 @@ + diff --git a/ProjectObsidian/ProtoFlux/Audio/AudioAdder.cs b/ProjectObsidian/ProtoFlux/Audio/AudioAdder.cs new file mode 100644 index 0000000..36f0708 --- /dev/null +++ b/ProjectObsidian/ProtoFlux/Audio/AudioAdder.cs @@ -0,0 +1,159 @@ +using System; +using ProtoFlux.Core; +using ProtoFlux.Runtimes.Execution; +using FrooxEngine.ProtoFlux; +using FrooxEngine; +using Elements.Assets; + +namespace ProtoFlux.Runtimes.Execution.Nodes.Obsidian.Audio +{ + public class AudioAdderProxy : ProtoFluxEngineProxy, IAudioSource + { + public IAudioSource AudioInput; + + public IAudioSource AudioInput2; + + public bool IsActive => true; + + public int ChannelCount => 1; + + public void Read(Span buffer) where S : unmanaged, IAudioSample + { + Span newBuffer = stackalloc S[buffer.Length]; + newBuffer = buffer; + Span newBuffer2 = stackalloc S[buffer.Length]; + if (AudioInput != null) + { + AudioInput.Read(newBuffer); + } + else + { + newBuffer.Fill(default); + } + if (AudioInput2 != null) + { + AudioInput2.Read(newBuffer2); + } + else + { + newBuffer2.Fill(default); + } + for (int i = 0; i < buffer.Length; i++) + { + newBuffer[i] = newBuffer[i].Add(newBuffer2[i]); + } + } + } + [NodeCategory("Obsidian/Audio")] + public class AudioAdder : ProxyVoidNode, IExecutionChangeListener + { + [ChangeListener] + public readonly ObjectInput AudioInput; + + [ChangeListener] + public readonly ObjectInput AudioInput2; + + public readonly ObjectOutput AudioOutput; + + private ObjectStore> _enabledChangedHandler; + + private ObjectStore _activeChangedHandler; + + public bool ValueListensToChanges { get; private set; } + + private bool ShouldListen(AudioAdderProxy proxy) + { + if (proxy.Enabled) + { + return proxy.Slot.IsActive; + } + return false; + } + + protected override void ProxyAdded(AudioAdderProxy 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); + } + + protected override void ProxyRemoved(AudioAdderProxy 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); + } + } + + protected void UpdateListenerState(FrooxEngineContext context) + { + AudioAdderProxy proxy = GetProxy(context); + if (proxy != null) + { + bool shouldListen = ShouldListen(proxy); + if (shouldListen != ValueListensToChanges) + { + ValueListensToChanges = shouldListen; + context.Group.MarkChangeTrackingDirty(); + } + } + } + + public void Changed(FrooxEngineContext context) + { + AudioAdderProxy proxy = GetProxy(context); + if (proxy == null) + { + return; + } + if (!proxy.IsValid) + { + return; + } + try + { + context.World.UpdateManager.NestCurrentlyUpdating(proxy); + proxy.AudioInput = AudioInput.Evaluate(context); + proxy.AudioInput2 = AudioInput2.Evaluate(context); + } + finally + { + context.World.UpdateManager.PopCurrentlyUpdating(proxy); + } + } + + protected override void ComputeOutputs(FrooxEngineContext context) + { + AudioAdderProxy proxy = GetProxy(context); + AudioOutput.Write(proxy, context); + } + + public AudioAdder() + { + AudioOutput = new ObjectOutput(this); + } + } +} \ No newline at end of file diff --git a/ProjectObsidian/ProtoFlux/Audio/AudioSubtractor.cs b/ProjectObsidian/ProtoFlux/Audio/AudioSubtractor.cs new file mode 100644 index 0000000..7954717 --- /dev/null +++ b/ProjectObsidian/ProtoFlux/Audio/AudioSubtractor.cs @@ -0,0 +1,159 @@ +using System; +using ProtoFlux.Core; +using ProtoFlux.Runtimes.Execution; +using FrooxEngine.ProtoFlux; +using FrooxEngine; +using Elements.Assets; + +namespace ProtoFlux.Runtimes.Execution.Nodes.Obsidian.Audio +{ + public class AudioSubtractorProxy : ProtoFluxEngineProxy, IAudioSource + { + public IAudioSource AudioInput; + + public IAudioSource AudioInput2; + + public bool IsActive => true; + + public int ChannelCount => 1; + + public void Read(Span buffer) where S : unmanaged, IAudioSample + { + Span newBuffer = stackalloc S[buffer.Length]; + newBuffer = buffer; + Span newBuffer2 = stackalloc S[buffer.Length]; + if (AudioInput != null) + { + AudioInput.Read(newBuffer); + } + else + { + newBuffer.Fill(default); + } + if (AudioInput2 != null) + { + AudioInput2.Read(newBuffer2); + } + else + { + newBuffer2.Fill(default); + } + for (int i = 0; i < buffer.Length; i++) + { + newBuffer[i] = newBuffer[i].Subtract(newBuffer2[i]); + } + } + } + [NodeCategory("Obsidian/Audio")] + public class AudioSubtractor : ProxyVoidNode, IExecutionChangeListener + { + [ChangeListener] + public readonly ObjectInput AudioInput; + + [ChangeListener] + public readonly ObjectInput AudioInput2; + + public readonly ObjectOutput AudioOutput; + + private ObjectStore> _enabledChangedHandler; + + private ObjectStore _activeChangedHandler; + + public bool ValueListensToChanges { get; private set; } + + private bool ShouldListen(AudioSubtractorProxy proxy) + { + if (proxy.Enabled) + { + return proxy.Slot.IsActive; + } + return false; + } + + protected override void ProxyAdded(AudioSubtractorProxy 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); + } + + protected override void ProxyRemoved(AudioSubtractorProxy 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); + } + } + + protected void UpdateListenerState(FrooxEngineContext context) + { + AudioSubtractorProxy proxy = GetProxy(context); + if (proxy != null) + { + bool shouldListen = ShouldListen(proxy); + if (shouldListen != ValueListensToChanges) + { + ValueListensToChanges = shouldListen; + context.Group.MarkChangeTrackingDirty(); + } + } + } + + public void Changed(FrooxEngineContext context) + { + AudioSubtractorProxy proxy = GetProxy(context); + if (proxy == null) + { + return; + } + if (!proxy.IsValid) + { + return; + } + try + { + context.World.UpdateManager.NestCurrentlyUpdating(proxy); + proxy.AudioInput = AudioInput.Evaluate(context); + proxy.AudioInput2 = AudioInput2.Evaluate(context); + } + finally + { + context.World.UpdateManager.PopCurrentlyUpdating(proxy); + } + } + + protected override void ComputeOutputs(FrooxEngineContext context) + { + AudioSubtractorProxy proxy = GetProxy(context); + AudioOutput.Write(proxy, context); + } + + public AudioSubtractor() + { + AudioOutput = new ObjectOutput(this); + } + } +} \ No newline at end of file diff --git a/ProjectObsidian/ProtoFlux/Audio/SineGenerator.cs b/ProjectObsidian/ProtoFlux/Audio/SineGenerator.cs new file mode 100644 index 0000000..9dc86cb --- /dev/null +++ b/ProjectObsidian/ProtoFlux/Audio/SineGenerator.cs @@ -0,0 +1,172 @@ +using System; +using ProtoFlux.Core; +using ProtoFlux.Runtimes.Execution; +using FrooxEngine.ProtoFlux; +using FrooxEngine; +using Elements.Assets; +using Elements.Core; +using System.Runtime.InteropServices; +using System.Linq.Expressions; +using FrooxEngine.FrooxEngine.ProtoFlux.CoreNodes; + +namespace ProtoFlux.Runtimes.Execution.Nodes.Obsidian.Audio +{ + public class SineGeneratorProxy : ProtoFluxEngineProxy, IAudioSource + { + public float Frequency; + + public float Amplitude; + + public float Phase; + + private float time; + + private float[] tempBuffer; + + public bool IsActive => true; + + public int ChannelCount => 1; + + protected override void OnAwake() + { + base.OnAwake(); + Frequency = 440f; + Amplitude = 1f; + Phase = 0f; + } + + public void Read(Span buffer) where S : unmanaged, IAudioSample + { + tempBuffer = tempBuffer.EnsureSize(buffer.Length); + time %= MathX.PI * 2f + Phase; + float advance = 1f / (float)base.Engine.AudioSystem.SampleRate * (MathX.PI * 2f) * (float)Frequency; + for (int i = 0; i < buffer.Length; i++) + { + tempBuffer[i] = MathX.Sin(time) * MathX.Clamp01(Amplitude); + time += advance; + } + double position = 0.0; + MonoSample lastSample = default(MonoSample); + MemoryMarshal.Cast(MemoryExtensions.AsSpan(tempBuffer)).CopySamples(buffer, ref position, ref lastSample); + } + } + [NodeCategory("Obsidian/Audio")] + public class SineGenerator : ProxyVoidNode, IExecutionChangeListener + { + [ChangeListener] + [@DefaultValue(440f)] + public readonly ValueInput Frequency; + + [ChangeListener] + [@DefaultValue(1f)] + public readonly ValueInput Amplitude; + + [ChangeListener] + [@DefaultValue(0f)] + public readonly ValueInput Phase; + + public readonly ObjectOutput AudioOutput; + + private ObjectStore> _enabledChangedHandler; + + private ObjectStore _activeChangedHandler; + + public bool ValueListensToChanges { get; private set; } + + private bool ShouldListen(SineGeneratorProxy proxy) + { + if (proxy.Enabled) + { + return proxy.Slot.IsActive; + } + return false; + } + + protected override void ProxyAdded(SineGeneratorProxy 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); + } + + protected override void ProxyRemoved(SineGeneratorProxy 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); + } + } + + protected void UpdateListenerState(FrooxEngineContext context) + { + SineGeneratorProxy proxy = GetProxy(context); + if (proxy != null) + { + bool shouldListen = ShouldListen(proxy); + if (shouldListen != ValueListensToChanges) + { + ValueListensToChanges = shouldListen; + context.Group.MarkChangeTrackingDirty(); + } + } + } + + public void Changed(FrooxEngineContext context) + { + SineGeneratorProxy proxy = GetProxy(context); + if (proxy == null) + { + return; + } + if (!proxy.IsValid) + { + return; + } + try + { + context.World.UpdateManager.NestCurrentlyUpdating(proxy); + proxy.Frequency = Frequency.Evaluate(context); + proxy.Amplitude = Amplitude.Evaluate(context); + proxy.Phase = Phase.Evaluate(context); + } + finally + { + context.World.UpdateManager.PopCurrentlyUpdating(proxy); + } + } + + protected override void ComputeOutputs(FrooxEngineContext context) + { + SineGeneratorProxy proxy = GetProxy(context); + AudioOutput.Write(proxy, context); + } + + public SineGenerator() + { + AudioOutput = new ObjectOutput(this); + } + } +} \ No newline at end of file diff --git a/ProjectObsidian/ProtoFlux/Audio/Speaker.cs b/ProjectObsidian/ProtoFlux/Audio/Speaker.cs new file mode 100644 index 0000000..1f519c4 --- /dev/null +++ b/ProjectObsidian/ProtoFlux/Audio/Speaker.cs @@ -0,0 +1,143 @@ +using System; +using ProtoFlux.Core; +using ProtoFlux.Runtimes.Execution; +using FrooxEngine.ProtoFlux; +using FrooxEngine; + +namespace ProtoFlux.Runtimes.Execution.Nodes.Obsidian.Audio +{ + public class SpeakerProxy : ProtoFluxEngineProxy + { + private AudioOutput _output; + public bool Active + { + get { return Output?.Enabled ?? false; } + set { if (Output != null) Output.Enabled = value; } + } + public float Volume + { + get { return Output?.Volume.Value ?? 0f; } + set { if (Output != null) Output.Volume.Value = value; } + } + public IAudioSource Source + { + get { return Output?.Source.Target; } + set { if (Output != null) Output.Source.Target = value; } + } + public AudioOutput Output + { + get + { + if (_output == null) + { + _output = Slot.GetComponent(); + } + return _output; + } + } + protected override void OnAttach() + { + _output = Slot.AttachComponent(); + } + } + [NodeCategory("Obsidian/Audio")] + public class Speaker : ProxyVoidNode, IExecutionChangeListener + { + [ChangeListener] + public readonly ObjectInput Source; + + [ChangeListener] + public readonly ValueInput Volume; + + private ObjectStore> _enabledChangedHandler; + + private ObjectStore _activeChangedHandler; + + public bool ValueListensToChanges { get; private set; } + + private bool ShouldListen(SpeakerProxy proxy) + { + if (proxy.Enabled) + { + return proxy.Slot.IsActive; + } + return false; + } + + protected override void ProxyAdded(SpeakerProxy 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); + } + + protected override void ProxyRemoved(SpeakerProxy 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); + } + } + + protected void UpdateListenerState(FrooxEngineContext context) + { + SpeakerProxy 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) + { + SpeakerProxy proxy = GetProxy(context); + if (proxy == null) + { + return; + } + if (!proxy.IsValid) + { + return; + } + try + { + context.World.UpdateManager.NestCurrentlyUpdating(proxy); + proxy.Source = Source.Evaluate(context); + proxy.Volume = Volume.Evaluate(context); + } + finally + { + context.World.UpdateManager.PopCurrentlyUpdating(proxy); + } + } + } +} \ No newline at end of file diff --git a/ProjectObsidian/ProtoFlux/Audio/TestAudio.cs b/ProjectObsidian/ProtoFlux/Audio/TestAudio.cs index 0d7a49b..f2ed1f2 100644 --- a/ProjectObsidian/ProtoFlux/Audio/TestAudio.cs +++ b/ProjectObsidian/ProtoFlux/Audio/TestAudio.cs @@ -1,138 +1,149 @@ -//using System; -//using Elements.Assets; -//using Elements.Core; -//using FrooxEngine; -//using FrooxEngine.ProtoFlux; -//using Obsidian.Components.Audio; -//using ProtoFlux.Core; -//using ProtoFlux.Runtimes.Execution; - -//namespace ProtoFlux.Runtimes.Execution.Nodes.Obsidian.Playback -//{ -// [NodeCategory("Obsidian/Audio")] -// public class TestAudio : ProxyVoidNode -// { -// public class Proxy : ProtoFluxEngineProxy, IAudioSource -// { -// public readonly SyncRef Source; -// public readonly Sync SmoothingFactor; - -// public bool IsActive => Source.Target != null && Source.Target.IsActive; - -// public int ChannelCount => Source.Target?.ChannelCount ?? 0; - -// public void Read(Span buffer) where S : unmanaged, IAudioSample -// { -// if (!IsActive) -// { -// return; -// } - -// Span span = stackalloc S[buffer.Length]; - -// span = buffer; - -// Source.Target.Read(span); - -// EMA_IIR_SmoothSignal.EMAIIRSmoothSignal(ref span, span.Length, SmoothingFactor); -// } -// } - -// public readonly ObjectInput Source; -// public readonly ValueInput SmoothingFactor; -// public readonly ObjectOutput Result; - -// public bool SourceListensToChanges { get; private set; } - -// private ObjectStore> _enabledChangedHandler; - -// private ObjectStore _activeChangedHandler; - -// private bool ShouldListen(Proxy proxy) -// { -// if (proxy.Enabled && proxy.Slot.IsActive) -// { -// return proxy.Source.Target != null; -// } -// return false; -// } - -// protected void UpdateListenerState(FrooxEngineContext context) -// { -// Proxy proxy = GetProxy(context); -// if (proxy != null) -// { -// bool flag = ShouldListen(proxy); -// if (flag != SourceListensToChanges) -// { -// SourceListensToChanges = flag; -// context.Group.MarkChangeTrackingDirty(); -// } -// } -// } - -// protected override void ProxyAdded(Proxy proxy, FrooxEngineContext context) -// { -// NodeContextPath path = context.CaptureContextPath(); -// ProtoFluxNodeGroup group = context.Group; -// context.GetEventDispatcher(out var dispatcher); -// Action value2 = delegate -// { -// dispatcher.ScheduleEvent(path, delegate (FrooxEngineContext c) -// { -// UpdateListenerState(c); -// }); -// }; -// SlotEvent value3 = delegate -// { -// dispatcher.ScheduleEvent(path, delegate (FrooxEngineContext c) -// { -// UpdateListenerState(c); -// }); -// }; -// proxy.EnabledField.Changed += value2; -// proxy.Slot.ActiveChanged += value3; -// _enabledChangedHandler.Write(value2, context); -// _activeChangedHandler.Write(value3, context); -// SourceListensToChanges = ShouldListen(proxy); -// } - -// protected override void ProxyRemoved(Proxy 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); -// } -// } - -// public void Changed(FrooxEngineContext context) -// { -// Proxy proxy = GetProxy(context); -// if (proxy == null || proxy.IsRemoved) -// { -// return; -// } -// IAudioSource source = proxy.Source.Target; -// if (source == null || !proxy.Source.Target.IsActive) -// { -// return; -// } -// try -// { -// context.World.UpdateManager.NestCurrentlyUpdating(proxy); -// } -// finally -// { -// context.World.UpdateManager.PopCurrentlyUpdating(proxy); -// } -// } - -// protected override void ComputeOutputs(FrooxEngineContext context) -// { -// Proxy proxy = GetProxy(context); -// } -// } -//} +using System; +using ProtoFlux.Core; +using ProtoFlux.Runtimes.Execution; +using FrooxEngine.ProtoFlux; +using FrooxEngine; + +namespace ProtoFlux.Runtimes.Execution.Nodes.Obsidian.Audio +{ + public class TestAudioProxy : ProtoFluxEngineProxy + { + private AudioOutput _output; + private SineWaveClip _clip; + private AudioClipPlayer _player; + public readonly FieldDrive FreqDrive; + public readonly FieldDrive AmpDrive; + public bool Active + { + get { return _output.Enabled; } + set { _output.Enabled = value; } + } + public bool IsValid => _clip.FilterWorldElement() != null && FreqDrive.Target != null && FreqDrive.IsLinkValid && AmpDrive.Target != null && AmpDrive.IsLinkValid; + public float Frequency + { + get { return FreqDrive.Target.Value; } + set { FreqDrive.Target.Value = value; } + } + public float Amplitude + { + get { return AmpDrive.Target.Value; } + set { AmpDrive.Target.Value = value; } + } + protected override void OnAttach() + { + _output = Slot.AttachComponent(); + _clip = Slot.AttachComponent(); + _player = Slot.AttachComponent(); + _player.Play(); + _player.Loop = true; + _player.Clip.Target = _clip; + _output.Source.Target = _player; + FreqDrive.Target = _clip.Frequency; + FreqDrive.Target.Value = 440f; + AmpDrive.Target = _clip.Amplitude; + AmpDrive.Target.Value = 1f; + } + } + [NodeCategory("Obsidian/Audio")] + public class TestAudio : ProxyVoidNode, IExecutionChangeListener + { + [ChangeListener] + [@DefaultValue(440f)] + public readonly ValueInput Frequency; + + [ChangeListener] + [@DefaultValue(1f)] + public readonly ValueInput Amplitude; + + private ObjectStore> _enabledChangedHandler; + + private ObjectStore _activeChangedHandler; + + public bool ValueListensToChanges { get; private set; } + + private bool ShouldListen(TestAudioProxy proxy) + { + if (proxy.Enabled) + { + return proxy.Slot.IsActive; + } + return false; + } + + protected override void ProxyAdded(TestAudioProxy 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); + } + + protected override void ProxyRemoved(TestAudioProxy 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); + } + } + + protected void UpdateListenerState(FrooxEngineContext context) + { + TestAudioProxy 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) + { + TestAudioProxy proxy = GetProxy(context); + if (proxy == null) + { + return; + } + if (!proxy.IsValid) + { + return; + } + try + { + context.World.UpdateManager.NestCurrentlyUpdating(proxy); + proxy.Frequency = Frequency.Evaluate(context); + proxy.Amplitude = Amplitude.Evaluate(context); + } + finally + { + context.World.UpdateManager.PopCurrentlyUpdating(proxy); + } + } + } +} \ No newline at end of file From 970b5eed59b495eaa4d44f832425fddd301c817b Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Fri, 27 Dec 2024 09:02:11 +0000 Subject: [PATCH 3/5] Fill buffer with default samples if not active --- ProjectObsidian/Components/Audio/BandPassFilter.cs | 1 + ProjectObsidian/Components/Audio/FrequencyModulator.cs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/ProjectObsidian/Components/Audio/BandPassFilter.cs b/ProjectObsidian/Components/Audio/BandPassFilter.cs index aa162d4..a7ccc93 100644 --- a/ProjectObsidian/Components/Audio/BandPassFilter.cs +++ b/ProjectObsidian/Components/Audio/BandPassFilter.cs @@ -36,6 +36,7 @@ public void Read(Span buffer) where S : unmanaged, IAudioSample { if (!IsActive) { + buffer.Fill(default(S)); return; } diff --git a/ProjectObsidian/Components/Audio/FrequencyModulator.cs b/ProjectObsidian/Components/Audio/FrequencyModulator.cs index ddc5b03..3cece20 100644 --- a/ProjectObsidian/Components/Audio/FrequencyModulator.cs +++ b/ProjectObsidian/Components/Audio/FrequencyModulator.cs @@ -42,12 +42,14 @@ public void Read(Span buffer) where S : unmanaged, IAudioSample { if (!IsActive) { + buffer.Fill(default(S)); return; } int channelCount = ChannelCount; if (channelCount == 0) { + buffer.Fill(default(S)); return; } From 1f0fde9bf1cfd574301c8bf326bb7dfe8bc9d520 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Fri, 27 Dec 2024 09:09:21 +0000 Subject: [PATCH 4/5] Use listener state --- ProjectObsidian/ProtoFlux/Audio/AudioAdder.cs | 5 ++++- ProjectObsidian/ProtoFlux/Audio/AudioSubtractor.cs | 5 ++++- ProjectObsidian/ProtoFlux/Audio/SineGenerator.cs | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/ProjectObsidian/ProtoFlux/Audio/AudioAdder.cs b/ProjectObsidian/ProtoFlux/Audio/AudioAdder.cs index 36f0708..478990f 100644 --- a/ProjectObsidian/ProtoFlux/Audio/AudioAdder.cs +++ b/ProjectObsidian/ProtoFlux/Audio/AudioAdder.cs @@ -13,7 +13,9 @@ public class AudioAdderProxy : ProtoFluxEngineProxy, IAudioSource public IAudioSource AudioInput2; - public bool IsActive => true; + public bool Active; + + public bool IsActive => Active; public int ChannelCount => 1; @@ -118,6 +120,7 @@ protected void UpdateListenerState(FrooxEngineContext context) { ValueListensToChanges = shouldListen; context.Group.MarkChangeTrackingDirty(); + proxy.Active = shouldListen; } } } diff --git a/ProjectObsidian/ProtoFlux/Audio/AudioSubtractor.cs b/ProjectObsidian/ProtoFlux/Audio/AudioSubtractor.cs index 7954717..39218e2 100644 --- a/ProjectObsidian/ProtoFlux/Audio/AudioSubtractor.cs +++ b/ProjectObsidian/ProtoFlux/Audio/AudioSubtractor.cs @@ -13,7 +13,9 @@ public class AudioSubtractorProxy : ProtoFluxEngineProxy, IAudioSource public IAudioSource AudioInput2; - public bool IsActive => true; + public bool Active; + + public bool IsActive => Active; public int ChannelCount => 1; @@ -118,6 +120,7 @@ protected void UpdateListenerState(FrooxEngineContext context) { ValueListensToChanges = shouldListen; context.Group.MarkChangeTrackingDirty(); + proxy.Active = shouldListen; } } } diff --git a/ProjectObsidian/ProtoFlux/Audio/SineGenerator.cs b/ProjectObsidian/ProtoFlux/Audio/SineGenerator.cs index 9dc86cb..2f7af80 100644 --- a/ProjectObsidian/ProtoFlux/Audio/SineGenerator.cs +++ b/ProjectObsidian/ProtoFlux/Audio/SineGenerator.cs @@ -23,7 +23,9 @@ public class SineGeneratorProxy : ProtoFluxEngineProxy, IAudioSource private float[] tempBuffer; - public bool IsActive => true; + public bool Active; + + public bool IsActive => Active; public int ChannelCount => 1; @@ -130,6 +132,7 @@ protected void UpdateListenerState(FrooxEngineContext context) { ValueListensToChanges = shouldListen; context.Group.MarkChangeTrackingDirty(); + proxy.Active = shouldListen; } } } From 2f59e4355d515213ad6c3aca682458020cd38684 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Fri, 27 Dec 2024 09:21:28 +0000 Subject: [PATCH 5/5] Fix active state --- ProjectObsidian/ProtoFlux/Audio/AudioAdder.cs | 1 + ProjectObsidian/ProtoFlux/Audio/AudioSubtractor.cs | 1 + ProjectObsidian/ProtoFlux/Audio/SineGenerator.cs | 1 + ProjectObsidian/ProtoFlux/Audio/Speaker.cs | 1 + 4 files changed, 4 insertions(+) diff --git a/ProjectObsidian/ProtoFlux/Audio/AudioAdder.cs b/ProjectObsidian/ProtoFlux/Audio/AudioAdder.cs index 478990f..8374002 100644 --- a/ProjectObsidian/ProtoFlux/Audio/AudioAdder.cs +++ b/ProjectObsidian/ProtoFlux/Audio/AudioAdder.cs @@ -97,6 +97,7 @@ protected override void ProxyAdded(AudioAdderProxy proxy, FrooxEngineContext con _enabledChangedHandler.Write(enabledHandler, context); _activeChangedHandler.Write(activeHandler, context); ValueListensToChanges = ShouldListen(proxy); + proxy.Active = ValueListensToChanges; } protected override void ProxyRemoved(AudioAdderProxy proxy, FrooxEngineContext context, bool inUseByAnotherInstance) diff --git a/ProjectObsidian/ProtoFlux/Audio/AudioSubtractor.cs b/ProjectObsidian/ProtoFlux/Audio/AudioSubtractor.cs index 39218e2..ef46f5d 100644 --- a/ProjectObsidian/ProtoFlux/Audio/AudioSubtractor.cs +++ b/ProjectObsidian/ProtoFlux/Audio/AudioSubtractor.cs @@ -97,6 +97,7 @@ protected override void ProxyAdded(AudioSubtractorProxy proxy, FrooxEngineContex _enabledChangedHandler.Write(enabledHandler, context); _activeChangedHandler.Write(activeHandler, context); ValueListensToChanges = ShouldListen(proxy); + proxy.Active = ValueListensToChanges; } protected override void ProxyRemoved(AudioSubtractorProxy proxy, FrooxEngineContext context, bool inUseByAnotherInstance) diff --git a/ProjectObsidian/ProtoFlux/Audio/SineGenerator.cs b/ProjectObsidian/ProtoFlux/Audio/SineGenerator.cs index 2f7af80..9561730 100644 --- a/ProjectObsidian/ProtoFlux/Audio/SineGenerator.cs +++ b/ProjectObsidian/ProtoFlux/Audio/SineGenerator.cs @@ -109,6 +109,7 @@ protected override void ProxyAdded(SineGeneratorProxy proxy, FrooxEngineContext _enabledChangedHandler.Write(enabledHandler, context); _activeChangedHandler.Write(activeHandler, context); ValueListensToChanges = ShouldListen(proxy); + proxy.Active = ValueListensToChanges; } protected override void ProxyRemoved(SineGeneratorProxy proxy, FrooxEngineContext context, bool inUseByAnotherInstance) diff --git a/ProjectObsidian/ProtoFlux/Audio/Speaker.cs b/ProjectObsidian/ProtoFlux/Audio/Speaker.cs index 1f519c4..cab8cb7 100644 --- a/ProjectObsidian/ProtoFlux/Audio/Speaker.cs +++ b/ProjectObsidian/ProtoFlux/Audio/Speaker.cs @@ -89,6 +89,7 @@ protected override void ProxyAdded(SpeakerProxy proxy, FrooxEngineContext contex _enabledChangedHandler.Write(enabledHandler, context); _activeChangedHandler.Write(activeHandler, context); ValueListensToChanges = ShouldListen(proxy); + proxy.Active = ValueListensToChanges; } protected override void ProxyRemoved(SpeakerProxy proxy, FrooxEngineContext context, bool inUseByAnotherInstance)