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/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/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/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; } 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..8374002 --- /dev/null +++ b/ProjectObsidian/ProtoFlux/Audio/AudioAdder.cs @@ -0,0 +1,163 @@ +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 Active; + + public bool IsActive => Active; + + 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); + proxy.Active = ValueListensToChanges; + } + + 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(); + proxy.Active = shouldListen; + } + } + } + + 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..ef46f5d --- /dev/null +++ b/ProjectObsidian/ProtoFlux/Audio/AudioSubtractor.cs @@ -0,0 +1,163 @@ +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 Active; + + public bool IsActive => Active; + + 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); + proxy.Active = ValueListensToChanges; + } + + 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(); + proxy.Active = shouldListen; + } + } + } + + 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..9561730 --- /dev/null +++ b/ProjectObsidian/ProtoFlux/Audio/SineGenerator.cs @@ -0,0 +1,176 @@ +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 Active; + + public bool IsActive => Active; + + 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); + proxy.Active = ValueListensToChanges; + } + + 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(); + proxy.Active = shouldListen; + } + } + } + + 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..cab8cb7 --- /dev/null +++ b/ProjectObsidian/ProtoFlux/Audio/Speaker.cs @@ -0,0 +1,144 @@ +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); + proxy.Active = ValueListensToChanges; + } + + 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 new file mode 100644 index 0000000..f2ed1f2 --- /dev/null +++ b/ProjectObsidian/ProtoFlux/Audio/TestAudio.cs @@ -0,0 +1,149 @@ +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