Skip to content

Commit

Permalink
Implemented DynamicsCompressorNode and AudioListener.
Browse files Browse the repository at this point in the history
  • Loading branch information
KristofferStrube committed Oct 28, 2023
1 parent 1f31d55 commit c2ddb0c
Show file tree
Hide file tree
Showing 11 changed files with 258 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<BiquadFilterType>($"\"{value}\"") : null;
}
get => Element.GetAttribute("data-type") is { } value ? Deserialize<BiquadFilterType>($"\"{value}\"") : null;
set
{
if (value is null)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<AudioContext, Task<AudioNode>> AudioNode { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
};
Expand Down Expand Up @@ -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 ();
Expand Down Expand Up @@ -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]
Expand Down
100 changes: 99 additions & 1 deletion src/KristofferStrube.Blazor.WebAudio/AudioListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@
namespace KristofferStrube.Blazor.WebAudio;

/// <summary>
/// This interface represents the position and orientation of the person listening to the audio scene. All <see cref="PannerNode"/> objects spatialize in relation to the <see cref="BaseAudioContext"/>'s listener.
/// This interface represents the position and orientation of the person listening to the audio scene.
/// All <see cref="PannerNode"/> objects spatialize in relation to the <see cref="BaseAudioContext"/>'s listener.<br />
/// The positionX, positionY, and positionZ <see cref="AudioParam"/> represent the location of the listener in 3D Cartesian coordinate space.
/// <see cref="PannerNode"/> objects use this position relative to individual audio sources for spatialization.<br />
/// 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.<br />
/// The up vector represents the direction the top of a person’s head is pointing.
/// These two vectors are expected to be linearly independent.
/// </summary>
/// <remarks><see href="https://www.w3.org/TR/webaudio/#AudioListener">See the API definition here</see>.</remarks>
public class AudioListener : BaseJSWrapper
Expand All @@ -27,4 +35,94 @@ public static Task<AudioListener> CreateAsync(IJSRuntime jSRuntime, IJSObjectRef
protected AudioListener(IJSRuntime jSRuntime, IJSObjectReference jSReference) : base(jSRuntime, jSReference)
{
}

/// <summary>
/// Sets the x coordinate position of the audio listener in a 3D Cartesian coordinate space.
/// </summary>
/// <returns></returns>
public async Task<AudioParam> GetPositionXAsync()
{
IJSObjectReference jSInstance = await JSReference.InvokeAsync<IJSObjectReference>("positionX");
return await AudioParam.CreateAsync(JSRuntime, jSInstance);
}

/// <summary>
/// Sets the y coordinate position of the audio listener in a 3D Cartesian coordinate space.
/// </summary>
/// <returns></returns>
public async Task<AudioParam> GetPositionYAsync()
{
IJSObjectReference jSInstance = await JSReference.InvokeAsync<IJSObjectReference>("positionY");
return await AudioParam.CreateAsync(JSRuntime, jSInstance);
}

/// <summary>
/// Sets the z coordinate position of the audio listener in a 3D Cartesian coordinate space.
/// </summary>
/// <returns></returns>
public async Task<AudioParam> GetPositionZAsync()
{
IJSObjectReference jSInstance = await JSReference.InvokeAsync<IJSObjectReference>("positionZ");
return await AudioParam.CreateAsync(JSRuntime, jSInstance);
}

/// <summary>
/// Sets the x coordinate component of the forward direction the listener is pointing in 3D Cartesian coordinate space.
/// </summary>
/// <returns></returns>
public async Task<AudioParam> GetForwardXAsync()
{
IJSObjectReference jSInstance = await JSReference.InvokeAsync<IJSObjectReference>("forwardX");
return await AudioParam.CreateAsync(JSRuntime, jSInstance);
}

/// <summary>
/// Sets the y coordinate component of the forward direction the listener is pointing in 3D Cartesian coordinate space.
/// </summary>
/// <returns></returns>
public async Task<AudioParam> GetForwardYAsync()
{
IJSObjectReference jSInstance = await JSReference.InvokeAsync<IJSObjectReference>("forwardY");
return await AudioParam.CreateAsync(JSRuntime, jSInstance);
}

/// <summary>
/// Sets the z coordinate component of the forward direction the listener is pointing in 3D Cartesian coordinate space.
/// </summary>
/// <returns></returns>
public async Task<AudioParam> GetForwardZAsync()
{
IJSObjectReference jSInstance = await JSReference.InvokeAsync<IJSObjectReference>("forwardZ");
return await AudioParam.CreateAsync(JSRuntime, jSInstance);
}

/// <summary>
/// Sets the x coordinate component of the up direction the listener is pointing in 3D Cartesian coordinate space.
/// </summary>
/// <returns></returns>
public async Task<AudioParam> GetUpXAsync()
{
IJSObjectReference jSInstance = await JSReference.InvokeAsync<IJSObjectReference>("upX");
return await AudioParam.CreateAsync(JSRuntime, jSInstance);
}

/// <summary>
/// Sets the y coordinate component of the up direction the listener is pointing in 3D Cartesian coordinate space.
/// </summary>
/// <returns></returns>
public async Task<AudioParam> GetUpYAsync()
{
IJSObjectReference jSInstance = await JSReference.InvokeAsync<IJSObjectReference>("upY");
return await AudioParam.CreateAsync(JSRuntime, jSInstance);
}

/// <summary>
/// Sets the z coordinate component of the up direction the listener is pointing in 3D Cartesian coordinate space.
/// </summary>
/// <returns></returns>
public async Task<AudioParam> GetUpZAsync()
{
IJSObjectReference jSInstance = await JSReference.InvokeAsync<IJSObjectReference>("upZ");
return await AudioParam.CreateAsync(JSRuntime, jSInstance);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,16 @@ public class AudioDestinationNode : AudioNode
protected AudioDestinationNode(IJSRuntime jSRuntime, IJSObjectReference jSReference) : base(jSRuntime, jSReference)
{
}

/// <summary>
/// The maximum number of channels that the channelCount attribute can be set to.
/// An <see cref="AudioDestinationNode"/> representing the audio hardware end-point (the normal case) can potentially output more than <c>2</c> channels of audio if the audio hardware is multi-channel.
/// maxChannelCount is the maximum number of channels that this hardware is capable of supporting.
/// </summary>
/// <returns></returns>
public async Task<ulong> GetMaxChannelCountAsync()
{
IJSObjectReference helper = await webAudioHelperTask.Value;
return await helper.InvokeAsync<ulong>("getAttribute", JSReference, "maxChannelCount");
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.JSInterop;
using KristofferStrube.Blazor.WebAudio.Extensions;
using Microsoft.JSInterop;

namespace KristofferStrube.Blazor.WebAudio;

Expand All @@ -23,10 +24,89 @@ public class DynamicsCompressorNode : AudioNode
return Task.FromResult(new DynamicsCompressorNode(jSRuntime, jSReference));
}

/// <summary>
/// Creates an <see cref="DynamicsCompressorNode"/> using the standard constructor.
/// </summary>
/// <param name="jSRuntime">An <see cref="IJSRuntime"/> instance.</param>
/// <param name="context">The <see cref="BaseAudioContext"/> this new <see cref="DynamicsCompressorNode"/> will be associated with.</param>
/// <param name="options">Optional initial parameter value for this <see cref="DynamicsCompressorNode"/>.</param>
/// <returns>A new instance of an <see cref="DynamicsCompressorNode"/>.</returns>
public static async Task<DynamicsCompressorNode> CreateAsync(IJSRuntime jSRuntime, BaseAudioContext context, DynamicsCompressorOptions? options = null)
{
IJSObjectReference helper = await jSRuntime.GetHelperAsync();
IJSObjectReference jSInstance = await helper.InvokeAsync<IJSObjectReference>("constructDynamicsCompressorNode", context.JSReference, options);
return new DynamicsCompressorNode(jSRuntime, jSInstance);
}

/// <summary>
/// Constructs a wrapper instance for a given JS Instance of a <see cref="DynamicsCompressorNode"/>.
/// </summary>
/// <param name="jSRuntime">An <see cref="IJSRuntime"/> instance.</param>
/// <param name="jSReference">A JS reference to an existing <see cref="DynamicsCompressorNode"/>.</param>
protected DynamicsCompressorNode(IJSRuntime jSRuntime, IJSObjectReference jSReference) : base(jSRuntime, jSReference) { }

/// <summary>
/// The decibel value above which the compression will start taking effect.
/// Default is <c>-24</c> and it must be between <c>-100</c> and <c>0</c>.
/// </summary>
public async Task<AudioParam> GetThresholdAsync()
{
IJSObjectReference helper = await webAudioHelperTask.Value;
IJSObjectReference jSInstance = await helper.InvokeAsync<IJSObjectReference>("getAttribute", JSReference, "threshold");
return await AudioParam.CreateAsync(JSRuntime, jSInstance);
}

/// <summary>
/// A decibel value representing the range above the threshold where the curve smoothly transitions to the "ratio" portion.
/// Default is <c>30</c> and it must be between <c>0</c> and <c>40</c>.
/// </summary>
public async Task<AudioParam> GetKneeAsync()
{
IJSObjectReference helper = await webAudioHelperTask.Value;
IJSObjectReference jSInstance = await helper.InvokeAsync<IJSObjectReference>("getAttribute", JSReference, "knee");
return await AudioParam.CreateAsync(JSRuntime, jSInstance);
}

/// <summary>
/// The amount of dB change in input for a 1 dB change in output.
/// Default is <c>12</c> and it must be between <c>1</c> and <c>20</c>.
/// </summary>
public async Task<AudioParam> GetRatioAsync()
{
IJSObjectReference helper = await webAudioHelperTask.Value;
IJSObjectReference jSInstance = await helper.InvokeAsync<IJSObjectReference>("getAttribute", JSReference, "ratio");
return await AudioParam.CreateAsync(JSRuntime, jSInstance);
}

/// <summary>
/// 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).
/// </summary>
public async Task<float> GetReductionAsync()
{
IJSObjectReference helper = await webAudioHelperTask.Value;
return await helper.InvokeAsync<float>("getAttribute", JSReference, "reduction");
}

/// <summary>
/// The amount of time (in seconds) to reduce the gain by 10dB.
/// Default is <c>0.003</c> and it must be between <c>0</c> and <c>1</c>.
/// </summary>
public async Task<AudioParam> GetAttackAsync()
{
IJSObjectReference helper = await webAudioHelperTask.Value;
IJSObjectReference jSInstance = await helper.InvokeAsync<IJSObjectReference>("getAttribute", JSReference, "attack");
return await AudioParam.CreateAsync(JSRuntime, jSInstance);
}

/// <summary>
/// The amount of time (in seconds) to increase the gain by 10dB.
/// Default is <c>0.25</c> and it must be between <c>0</c> and <c>1</c>.
/// </summary>
public async Task<AudioParam> GetReleaseAsync()
{
IJSObjectReference helper = await webAudioHelperTask.Value;
IJSObjectReference jSInstance = await helper.InvokeAsync<IJSObjectReference>("getAttribute", JSReference, "release");
return await AudioParam.CreateAsync(JSRuntime, jSInstance);
}
}
Loading

0 comments on commit c2ddb0c

Please sign in to comment.