diff --git a/samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/Connector.cs b/samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/Connector.cs index 223de0b..2916edc 100644 --- a/samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/Connector.cs +++ b/samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/Connector.cs @@ -30,7 +30,11 @@ public Connector(IElement element, SVGEditor.SVGEditor svg) : base(element, svg) var fromNode = (Node?)SVG.Elements.FirstOrDefault(e => e is Node && e.Id == Element.GetAttribute("data-from-node")); ulong fromPort = (ulong)Element.GetAttributeOrZero("data-from-port"); _ = fromNode?.OutgoingConnectors.Add((this, fromPort)); - if (fromNode is null) return null; + if (fromNode is null) + { + return null; + } + if (To is { } to) { QueuedTasks.Enqueue(async context => await (await fromNode.AudioNode(context)).ConnectAsync(await to.node.AudioNode(context), fromPort, to.port)); diff --git a/samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/Nodes/BiquadFilter.cs b/samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/Nodes/BiquadFilter.cs index 00ba03d..6a697ac 100644 --- a/samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/Nodes/BiquadFilter.cs +++ b/samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/Nodes/BiquadFilter.cs @@ -30,18 +30,12 @@ public BiquadFilter(IElement element, SVGEditor.SVGEditor svg) : base(element, s public new float Height { get => 280; - set - { - base.Height = 280; - } + set => base.Height = 280; } public BiquadFilterType? Type { - get - { - return Element.GetAttribute("data-type") is { } value ? Deserialize($"\"{value}\"") : null; - } + get => Element.GetAttribute("data-type") is { } value ? Deserialize($"\"{value}\"") : null; set { if (value is null) @@ -58,10 +52,7 @@ public BiquadFilterType? Type public float? Q { - get - { - return Element.GetAttribute("data-Q") is { } value ? float.Parse(value, CultureInfo.InvariantCulture) : null; - } + get => Element.GetAttribute("data-Q") is { } value ? float.Parse(value, CultureInfo.InvariantCulture) : null; set { if (value is null) @@ -78,10 +69,7 @@ public float? Q public float? Detune { - get - { - return Element.GetAttribute("data-detune") is { } value ? float.Parse(value, CultureInfo.InvariantCulture) : null; - } + get => Element.GetAttribute("data-detune") is { } value ? float.Parse(value, CultureInfo.InvariantCulture) : null; set { if (value is null) @@ -98,10 +86,7 @@ public float? Detune public float? Frequency { - get - { - return Element.GetAttribute("data-frequency") is { } value ? float.Parse(value, CultureInfo.InvariantCulture) : null; - } + get => Element.GetAttribute("data-frequency") is { } value ? float.Parse(value, CultureInfo.InvariantCulture) : null; set { if (value is null) @@ -118,10 +103,7 @@ public float? Frequency public float? Gain { - get - { - return Element.GetAttribute("data-gain") is { } value ? float.Parse(value, CultureInfo.InvariantCulture) : null; - } + get => Element.GetAttribute("data-gain") is { } value ? float.Parse(value, CultureInfo.InvariantCulture) : null; set { if (value is null) diff --git a/samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/Nodes/Gain.cs b/samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/Nodes/Gain.cs index ee5d997..4091ad2 100644 --- a/samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/Nodes/Gain.cs +++ b/samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/Nodes/Gain.cs @@ -26,10 +26,7 @@ public Gain(IElement element, SVGEditor.SVGEditor svg) : base(element, svg) { } public float? GainValue { - get - { - return Element.GetAttribute("data-gain") is { } value ? float.Parse(value, CultureInfo.InvariantCulture) : null; - } + get => Element.GetAttribute("data-gain") is { } value ? float.Parse(value, CultureInfo.InvariantCulture) : null; set { if (value is null) diff --git a/samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/Nodes/Node.cs b/samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/Nodes/Node.cs index e8b56a7..e761635 100644 --- a/samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/Nodes/Node.cs +++ b/samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/Nodes/Node.cs @@ -29,19 +29,13 @@ public override string Fill public new float Width { get => 250; - set - { - base.Width = 250; - } + set => base.Width = 250; } public new float Height { get => 100; - set - { - base.Height = 100; - } + set => base.Height = 100; } public abstract Func> AudioNode { get; } diff --git a/samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/Nodes/Oscillator.cs b/samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/Nodes/Oscillator.cs index d263983..b5d91dd 100644 --- a/samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/Nodes/Oscillator.cs +++ b/samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/Nodes/Oscillator.cs @@ -27,10 +27,7 @@ public Oscillator(IElement element, SVGEditor.SVGEditor svg) : base(element, svg public float? Frequency { - get - { - return Element.GetAttribute("data-frequency") is { } value ? float.Parse(value, CultureInfo.InvariantCulture) : null; - } + get => Element.GetAttribute("data-frequency") is { } value ? float.Parse(value, CultureInfo.InvariantCulture) : null; set { if (value is null) diff --git a/samples/KristofferStrube.Blazor.WebAudio.WasmExample/Pages/Status.razor b/samples/KristofferStrube.Blazor.WebAudio.WasmExample/Pages/Status.razor index a1d0212..b76d398 100644 --- a/samples/KristofferStrube.Blazor.WebAudio.WasmExample/Pages/Status.razor +++ b/samples/KristofferStrube.Blazor.WebAudio.WasmExample/Pages/Status.razor @@ -30,10 +30,9 @@ (19, 41), (46, 142), (157, 200), - (202, 252), - (254, 254), - (285, 366), - (388, 396), + (202, 266), + (269, 269), + (285, 396), (446, 456), (461, 468) }; @@ -83,7 +82,7 @@ interface BaseAudioContext : EventTarget { ScriptProcessorNode createScriptProcessor( optional unsigned long bufferSize = 0, optional unsigned long numberOfInputChannels = 2, - optional unsigned long numberOfOutputChannels = 2); + optional unsigned long numberOfOutputChannels = 2); // Deprecated StereoPannerNode createStereoPanner (); WaveShaperNode createWaveShaper (); @@ -305,8 +304,8 @@ interface AudioListener { readonly attribute AudioParam upX; readonly attribute AudioParam upY; readonly attribute AudioParam upZ; - undefined setPosition (float x, float y, float z); - undefined setOrientation (float x, float y, float z, float xUp, float yUp, float zUp); + undefined setPosition (float x, float y, float z); // Deprecated + undefined setOrientation (float x, float y, float z, float xUp, float yUp, float zUp); // Deprecated }; [Exposed=Window] diff --git a/src/KristofferStrube.Blazor.WebAudio/AudioListener.cs b/src/KristofferStrube.Blazor.WebAudio/AudioListener.cs index 5c617c1..5e79a85 100644 --- a/src/KristofferStrube.Blazor.WebAudio/AudioListener.cs +++ b/src/KristofferStrube.Blazor.WebAudio/AudioListener.cs @@ -3,7 +3,15 @@ namespace KristofferStrube.Blazor.WebAudio; /// -/// This interface represents the position and orientation of the person listening to the audio scene. All objects spatialize in relation to the 's listener. +/// This interface represents the position and orientation of the person listening to the audio scene. +/// All objects spatialize in relation to the 's listener.
+/// The positionX, positionY, and positionZ represent the location of the listener in 3D Cartesian coordinate space. +/// objects use this position relative to individual audio sources for spatialization.
+/// The forwardX, forwardY, and forwardZ parameters represent a direction vector in 3D space. +/// Both a forward vector and an up vector are used to determine the orientation of the listener. +/// In simple human terms, the forward vector represents which direction the person’s nose is pointing.
+/// The up vector represents the direction the top of a person’s head is pointing. +/// These two vectors are expected to be linearly independent. ///
/// See the API definition here. public class AudioListener : BaseJSWrapper @@ -27,4 +35,94 @@ public static Task CreateAsync(IJSRuntime jSRuntime, IJSObjectRef protected AudioListener(IJSRuntime jSRuntime, IJSObjectReference jSReference) : base(jSRuntime, jSReference) { } + + /// + /// Sets the x coordinate position of the audio listener in a 3D Cartesian coordinate space. + /// + /// + public async Task GetPositionXAsync() + { + IJSObjectReference jSInstance = await JSReference.InvokeAsync("positionX"); + return await AudioParam.CreateAsync(JSRuntime, jSInstance); + } + + /// + /// Sets the y coordinate position of the audio listener in a 3D Cartesian coordinate space. + /// + /// + public async Task GetPositionYAsync() + { + IJSObjectReference jSInstance = await JSReference.InvokeAsync("positionY"); + return await AudioParam.CreateAsync(JSRuntime, jSInstance); + } + + /// + /// Sets the z coordinate position of the audio listener in a 3D Cartesian coordinate space. + /// + /// + public async Task GetPositionZAsync() + { + IJSObjectReference jSInstance = await JSReference.InvokeAsync("positionZ"); + return await AudioParam.CreateAsync(JSRuntime, jSInstance); + } + + /// + /// Sets the x coordinate component of the forward direction the listener is pointing in 3D Cartesian coordinate space. + /// + /// + public async Task GetForwardXAsync() + { + IJSObjectReference jSInstance = await JSReference.InvokeAsync("forwardX"); + return await AudioParam.CreateAsync(JSRuntime, jSInstance); + } + + /// + /// Sets the y coordinate component of the forward direction the listener is pointing in 3D Cartesian coordinate space. + /// + /// + public async Task GetForwardYAsync() + { + IJSObjectReference jSInstance = await JSReference.InvokeAsync("forwardY"); + return await AudioParam.CreateAsync(JSRuntime, jSInstance); + } + + /// + /// Sets the z coordinate component of the forward direction the listener is pointing in 3D Cartesian coordinate space. + /// + /// + public async Task GetForwardZAsync() + { + IJSObjectReference jSInstance = await JSReference.InvokeAsync("forwardZ"); + return await AudioParam.CreateAsync(JSRuntime, jSInstance); + } + + /// + /// Sets the x coordinate component of the up direction the listener is pointing in 3D Cartesian coordinate space. + /// + /// + public async Task GetUpXAsync() + { + IJSObjectReference jSInstance = await JSReference.InvokeAsync("upX"); + return await AudioParam.CreateAsync(JSRuntime, jSInstance); + } + + /// + /// Sets the y coordinate component of the up direction the listener is pointing in 3D Cartesian coordinate space. + /// + /// + public async Task GetUpYAsync() + { + IJSObjectReference jSInstance = await JSReference.InvokeAsync("upY"); + return await AudioParam.CreateAsync(JSRuntime, jSInstance); + } + + /// + /// Sets the z coordinate component of the up direction the listener is pointing in 3D Cartesian coordinate space. + /// + /// + public async Task GetUpZAsync() + { + IJSObjectReference jSInstance = await JSReference.InvokeAsync("upZ"); + return await AudioParam.CreateAsync(JSRuntime, jSInstance); + } } diff --git a/src/KristofferStrube.Blazor.WebAudio/AudioNodes/AudioDestinationNode.cs b/src/KristofferStrube.Blazor.WebAudio/AudioNodes/AudioDestinationNode.cs index 1f9aeb6..269a976 100644 --- a/src/KristofferStrube.Blazor.WebAudio/AudioNodes/AudioDestinationNode.cs +++ b/src/KristofferStrube.Blazor.WebAudio/AudioNodes/AudioDestinationNode.cs @@ -30,4 +30,16 @@ public class AudioDestinationNode : AudioNode protected AudioDestinationNode(IJSRuntime jSRuntime, IJSObjectReference jSReference) : base(jSRuntime, jSReference) { } + + /// + /// The maximum number of channels that the channelCount attribute can be set to. + /// An representing the audio hardware end-point (the normal case) can potentially output more than 2 channels of audio if the audio hardware is multi-channel. + /// maxChannelCount is the maximum number of channels that this hardware is capable of supporting. + /// + /// + public async Task GetMaxChannelCountAsync() + { + IJSObjectReference helper = await webAudioHelperTask.Value; + return await helper.InvokeAsync("getAttribute", JSReference, "maxChannelCount"); + } } diff --git a/src/KristofferStrube.Blazor.WebAudio/AudioNodes/DynamicsCompressorNode.cs b/src/KristofferStrube.Blazor.WebAudio/AudioNodes/DynamicsCompressorNode.cs index f21c075..11c6f96 100644 --- a/src/KristofferStrube.Blazor.WebAudio/AudioNodes/DynamicsCompressorNode.cs +++ b/src/KristofferStrube.Blazor.WebAudio/AudioNodes/DynamicsCompressorNode.cs @@ -1,4 +1,5 @@ -using Microsoft.JSInterop; +using KristofferStrube.Blazor.WebAudio.Extensions; +using Microsoft.JSInterop; namespace KristofferStrube.Blazor.WebAudio; @@ -23,10 +24,89 @@ public class DynamicsCompressorNode : AudioNode return Task.FromResult(new DynamicsCompressorNode(jSRuntime, jSReference)); } + /// + /// Creates an using the standard constructor. + /// + /// An instance. + /// The this new will be associated with. + /// Optional initial parameter value for this . + /// A new instance of an . + public static async Task CreateAsync(IJSRuntime jSRuntime, BaseAudioContext context, DynamicsCompressorOptions? options = null) + { + IJSObjectReference helper = await jSRuntime.GetHelperAsync(); + IJSObjectReference jSInstance = await helper.InvokeAsync("constructDynamicsCompressorNode", context.JSReference, options); + return new DynamicsCompressorNode(jSRuntime, jSInstance); + } + /// /// Constructs a wrapper instance for a given JS Instance of a . /// /// An instance. /// A JS reference to an existing . protected DynamicsCompressorNode(IJSRuntime jSRuntime, IJSObjectReference jSReference) : base(jSRuntime, jSReference) { } + + /// + /// The decibel value above which the compression will start taking effect. + /// Default is -24 and it must be between -100 and 0. + /// + public async Task GetThresholdAsync() + { + IJSObjectReference helper = await webAudioHelperTask.Value; + IJSObjectReference jSInstance = await helper.InvokeAsync("getAttribute", JSReference, "threshold"); + return await AudioParam.CreateAsync(JSRuntime, jSInstance); + } + + /// + /// A decibel value representing the range above the threshold where the curve smoothly transitions to the "ratio" portion. + /// Default is 30 and it must be between 0 and 40. + /// + public async Task GetKneeAsync() + { + IJSObjectReference helper = await webAudioHelperTask.Value; + IJSObjectReference jSInstance = await helper.InvokeAsync("getAttribute", JSReference, "knee"); + return await AudioParam.CreateAsync(JSRuntime, jSInstance); + } + + /// + /// The amount of dB change in input for a 1 dB change in output. + /// Default is 12 and it must be between 1 and 20. + /// + public async Task GetRatioAsync() + { + IJSObjectReference helper = await webAudioHelperTask.Value; + IJSObjectReference jSInstance = await helper.InvokeAsync("getAttribute", JSReference, "ratio"); + return await AudioParam.CreateAsync(JSRuntime, jSInstance); + } + + /// + /// A read-only decibel value for metering purposes, representing the current amount of gain reduction that the compressor is applying to the signal. + /// If fed no signal the value will be 0 (no gain reduction). + /// + public async Task GetReductionAsync() + { + IJSObjectReference helper = await webAudioHelperTask.Value; + return await helper.InvokeAsync("getAttribute", JSReference, "reduction"); + } + + /// + /// The amount of time (in seconds) to reduce the gain by 10dB. + /// Default is 0.003 and it must be between 0 and 1. + /// + public async Task GetAttackAsync() + { + IJSObjectReference helper = await webAudioHelperTask.Value; + IJSObjectReference jSInstance = await helper.InvokeAsync("getAttribute", JSReference, "attack"); + return await AudioParam.CreateAsync(JSRuntime, jSInstance); + } + + /// + /// The amount of time (in seconds) to increase the gain by 10dB. + /// Default is 0.25 and it must be between 0 and 1. + /// + public async Task GetReleaseAsync() + { + IJSObjectReference helper = await webAudioHelperTask.Value; + IJSObjectReference jSInstance = await helper.InvokeAsync("getAttribute", JSReference, "release"); + return await AudioParam.CreateAsync(JSRuntime, jSInstance); + } } diff --git a/src/KristofferStrube.Blazor.WebAudio/Options/DynamicsCompressorOptions.cs b/src/KristofferStrube.Blazor.WebAudio/Options/DynamicsCompressorOptions.cs new file mode 100644 index 0000000..f7c884b --- /dev/null +++ b/src/KristofferStrube.Blazor.WebAudio/Options/DynamicsCompressorOptions.cs @@ -0,0 +1,41 @@ + +using System.Text.Json.Serialization; + +namespace KristofferStrube.Blazor.WebAudio; + +/// +/// This specifies the options to use in constructing a . +/// +/// See the API definition here. +public class DynamicsCompressorOptions : AudioNodeOptions +{ + /// + /// The initial value for the AudioParam. + /// + [JsonPropertyName("attack")] + public float Attack { get; set; } = 0.003f; + + /// + /// The initial value for the AudioParam. + /// + [JsonPropertyName("knee")] + public float Knee { get; set; } = 30f; + + /// + /// The initial value for the AudioParam. + /// + [JsonPropertyName("ratio")] + public float Ratio { get; set; } = 12f; + + /// + /// The initial value for the AudioParam. + /// + [JsonPropertyName("release")] + public float Release { get; set; } = 0.25f; + + /// + /// The initial value for the AudioParam. + /// + [JsonPropertyName("threshold")] + public float Threshold { get; set; } = -24f; +} diff --git a/src/KristofferStrube.Blazor.WebAudio/wwwroot/KristofferStrube.Blazor.WebAudio.js b/src/KristofferStrube.Blazor.WebAudio/wwwroot/KristofferStrube.Blazor.WebAudio.js index 1e2598e..90be321 100644 --- a/src/KristofferStrube.Blazor.WebAudio/wwwroot/KristofferStrube.Blazor.WebAudio.js +++ b/src/KristofferStrube.Blazor.WebAudio/wwwroot/KristofferStrube.Blazor.WebAudio.js @@ -58,6 +58,10 @@ export function constructConvolverNode(context, options) { return new ConvolverNode(context, options); } +export function constructDynamicsCompressorNode(context, options) { + return new DynamicsCompressorNode(context, options); +} + export function constructAudioBuffer(options) { return new AudioBuffer(options); }