Skip to content

Commit 33f1a4e

Browse files
Added implementation of PannerNode.
1 parent 2ae2c29 commit 33f1a4e

File tree

7 files changed

+521
-8
lines changed

7 files changed

+521
-8
lines changed

samples/KristofferStrube.Blazor.WebAudio.WasmExample/Pages/Status.razor

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,30 @@
99
@($"{percentImplemented:00%}") Covered!
1010
</div>
1111

12-
<pre><code>@((MarkupString)compareText)</code></pre>
12+
<pre><code>
13+
@for (int i = 0; i < compareLines.Count; i++)
14+
{
15+
<span style="background-color:@(compareLines[i].color);display:block;min-height:21px;">@($"{new string(' ', 3 - i.ToString().Length)}{i}") @compareLines[i].text</span>
16+
}
17+
</code></pre>
1318

1419
@code {
15-
private string compareText = "";
20+
private List<(string text, string color)> compareLines = [];
1621
private double percentImplemented = 0;
1722
private string color => $"#{(int)(255 - 111 * percentImplemented):X2}{(int)(192 + 46 * percentImplemented):X2}{(int)(203 - 59 * percentImplemented):X2}";
1823

1924
protected override async Task OnInitializedAsync()
2025
{
21-
var compareLines = new List<string>();
22-
var lines = webIDL.Replace("<", "&lt;").Split('\n');
26+
compareLines = [];
27+
var lines = webIDL.Split('\n');
2328
for (int i = 0; i < lines.Count(); i++)
2429
{
2530
var color = supportedRows.Any(interval => i >= interval.start && i <= interval.end) ? "lightgreen" : "pink";
26-
compareLines.Add($"""<span style="background-color:{color};display:block;min-height:21px;">{lines[i]}</span>""");
31+
compareLines.Add((lines[i], color));
2732
}
2833

29-
compareText = string.Join("", compareLines);
3034
var percentImplementedTotal = supportedRows.Sum(r => r.end - r.start + 1) / (float)webIDL.Split('\n').Count();
31-
double delta = 0.00005;
35+
double delta = 0.0005;
3236
while (percentImplemented < percentImplementedTotal)
3337
{
3438
await Task.Delay(1);
@@ -46,7 +50,8 @@
4650
(46, 266),
4751
(269, 269),
4852
(285, 396),
49-
(421, 468),
53+
(421, 497),
54+
(500, 517),
5055
(582, 583),
5156
(585, 592),
5257
(594, 602)

src/KristofferStrube.Blazor.WebAudio/AudioNodes/PannerNode.cs

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using KristofferStrube.Blazor.WebIDL;
2+
using KristofferStrube.Blazor.WebIDL.Exceptions;
23
using Microsoft.JSInterop;
34

45
namespace KristofferStrube.Blazor.WebAudio;
@@ -27,4 +28,262 @@ public class PannerNode : AudioNode, IJSCreatable<PannerNode>
2728
protected PannerNode(IJSRuntime jSRuntime, IJSObjectReference jSReference, CreationOptions options) : base(jSRuntime, jSReference, options)
2829
{
2930
}
31+
32+
/// <summary>
33+
/// Specifies the panning model used by this <see cref="PannerNode"/>. Defaults to <see cref="PanningModelType.EqualPower"/>.
34+
/// </summary>
35+
public async Task<PanningModelType> GetPanningModelAsync()
36+
{
37+
IJSObjectReference helper = await webAudioHelperTask.Value;
38+
return await helper.InvokeAsync<PanningModelType>("getAttribute", JSReference, "panningModel");
39+
}
40+
41+
/// <summary>
42+
/// The x-coordinate position of the audio source in a 3D Cartesian system.
43+
/// </summary>
44+
public async Task<AudioParam> GetPositionXAsync()
45+
{
46+
IJSObjectReference helper = await webAudioHelperTask.Value;
47+
IJSObjectReference jSInstance = await helper.InvokeAsync<IJSObjectReference>("getAttribute", JSReference, "positionX");
48+
return await AudioParam.CreateAsync(JSRuntime, jSInstance, new() { DisposesJSReference = true });
49+
}
50+
51+
/// <summary>
52+
/// The y-coordinate position of the audio source in a 3D Cartesian system.
53+
/// </summary>
54+
public async Task<AudioParam> GetPositionYAsync()
55+
{
56+
IJSObjectReference helper = await webAudioHelperTask.Value;
57+
IJSObjectReference jSInstance = await helper.InvokeAsync<IJSObjectReference>("getAttribute", JSReference, "positionY");
58+
return await AudioParam.CreateAsync(JSRuntime, jSInstance, new() { DisposesJSReference = true });
59+
}
60+
61+
/// <summary>
62+
/// The z-coordinate position of the audio source in a 3D Cartesian system.
63+
/// </summary>
64+
public async Task<AudioParam> GetPositionZAsync()
65+
{
66+
IJSObjectReference helper = await webAudioHelperTask.Value;
67+
IJSObjectReference jSInstance = await helper.InvokeAsync<IJSObjectReference>("getAttribute", JSReference, "positionZ");
68+
return await AudioParam.CreateAsync(JSRuntime, jSInstance, new() { DisposesJSReference = true });
69+
}
70+
71+
/// <summary>
72+
/// Describes the x-component of the vector of the direction the audio source is pointing in 3D Cartesian coordinate space.
73+
/// </summary>
74+
public async Task<AudioParam> GetOrientationXAsync()
75+
{
76+
IJSObjectReference helper = await webAudioHelperTask.Value;
77+
IJSObjectReference jSInstance = await helper.InvokeAsync<IJSObjectReference>("getAttribute", JSReference, "orientationX");
78+
return await AudioParam.CreateAsync(JSRuntime, jSInstance, new() { DisposesJSReference = true });
79+
}
80+
81+
/// <summary>
82+
/// Describes the y-component of the vector of the direction the audio source is pointing in 3D Cartesian coordinate space.
83+
/// </summary>
84+
public async Task<AudioParam> GetOrientationYAsync()
85+
{
86+
IJSObjectReference helper = await webAudioHelperTask.Value;
87+
IJSObjectReference jSInstance = await helper.InvokeAsync<IJSObjectReference>("getAttribute", JSReference, "orientationY");
88+
return await AudioParam.CreateAsync(JSRuntime, jSInstance, new() { DisposesJSReference = true });
89+
}
90+
91+
/// <summary>
92+
/// Describes the z-component of the vector of the direction the audio source is pointing in 3D Cartesian coordinate space.
93+
/// </summary>
94+
public async Task<AudioParam> GetOrientationZAsync()
95+
{
96+
IJSObjectReference helper = await webAudioHelperTask.Value;
97+
IJSObjectReference jSInstance = await helper.InvokeAsync<IJSObjectReference>("getAttribute", JSReference, "orientationZ");
98+
return await AudioParam.CreateAsync(JSRuntime, jSInstance, new() { DisposesJSReference = true });
99+
}
100+
101+
/// <summary>
102+
/// Specifies the distance model used by this <see cref="PannerNode"/>. Defaults to <see cref="DistanceModelType.Inverse"/>.
103+
/// </summary>
104+
public async Task<DistanceModelType> GetDistanceModelAsync()
105+
{
106+
IJSObjectReference helper = await webAudioHelperTask.Value;
107+
return await helper.InvokeAsync<DistanceModelType>("getAttribute", JSReference, "distanceModel");
108+
}
109+
110+
/// <summary>
111+
/// Gets the reference distance for reducing volume as source moves further from the listener.
112+
/// For distances less than this, the volume is not reduced.
113+
/// The default value is <c>1</c>.
114+
/// </summary>
115+
public async Task<double> GetRefDistanceAsync()
116+
{
117+
IJSObjectReference helper = await webAudioHelperTask.Value;
118+
return await helper.InvokeAsync<double>("getAttribute", JSReference, "refDistance");
119+
}
120+
121+
/// <summary>
122+
/// Sets the reference distance for reducing volume as source moves further from the listener.
123+
/// For distances less than this, the volume is not reduced.
124+
/// </summary>
125+
/// <remarks>
126+
/// It will throw a <see cref="RangeErrorException"/> if this is set to a negative value.
127+
/// </remarks>
128+
/// <exception cref="RangeErrorException"/>
129+
public async Task SetRefDistanceAsync(double value)
130+
{
131+
IJSObjectReference helper = await webAudioHelperTask.Value;
132+
ErrorHandlingJSObjectReference errorHandlingHelper = new(JSRuntime, helper);
133+
await errorHandlingHelper.InvokeVoidAsync("setAttribute", JSReference, "refDistance", value);
134+
}
135+
136+
/// <summary>
137+
/// Gets the maximum distance between source and listener, after which the volume will not be reduced any further.
138+
/// The default value is <c>10000</c>.
139+
/// </summary>
140+
public async Task<double> GetMaxDistanceAsync()
141+
{
142+
IJSObjectReference helper = await webAudioHelperTask.Value;
143+
return await helper.InvokeAsync<double>("getAttribute", JSReference, "maxDistance");
144+
}
145+
146+
/// <summary>
147+
/// Sets the maximum distance between source and listener, after which the volume will not be reduced any further.
148+
/// </summary>
149+
/// <remarks>
150+
/// It will throw a <see cref="RangeErrorException"/> if this is set to a negative value.
151+
/// </remarks>
152+
/// <exception cref="RangeErrorException"/>
153+
public async Task SetMaxDistanceAsync(double value)
154+
{
155+
IJSObjectReference helper = await webAudioHelperTask.Value;
156+
ErrorHandlingJSObjectReference errorHandlingHelper = new(JSRuntime, helper);
157+
await errorHandlingHelper.InvokeVoidAsync("setAttribute", JSReference, "maxDistance", value);
158+
}
159+
160+
/// <summary>
161+
/// Describes how quickly the volume is reduced as source moves away from listener.
162+
/// The default value is <c>1</c>.
163+
/// The nominal range for the rolloffFactor specifies the minimum and maximum values the rolloffFactor can have.
164+
/// Values outside the range are clamped to lie within this range. The nominal range depends on <see cref="GetDistanceModelAsync"/> as follows:
165+
/// <list type="table">
166+
/// <item>
167+
/// <term><see cref="DistanceModelType.Linear"/></term>
168+
/// <description>The nominal range is <c>[0,1]</c></description>
169+
/// </item>
170+
/// <item>
171+
/// <term><see cref="DistanceModelType.Inverse"/></term>
172+
/// <description>The nominal range is <c>[0,∞)</c></description>
173+
/// </item>
174+
/// <item>
175+
/// <term><see cref="DistanceModelType.Exponential"/></term>
176+
/// <description>The nominal range is <c>[0,∞)</c></description>
177+
/// </item>
178+
/// </list>
179+
/// Note that the clamping happens as part of the processing of the distance computation.
180+
/// The attribute reflects the value that was set and is not modified.
181+
/// </summary>
182+
public async Task<double> GetRolloffFactorAsync()
183+
{
184+
IJSObjectReference helper = await webAudioHelperTask.Value;
185+
return await helper.InvokeAsync<double>("getAttribute", JSReference, "rolloffFactor");
186+
}
187+
188+
/// <summary>
189+
/// Describes how quickly the volume is reduced as source moves away from listener.
190+
/// The nominal range for the rolloffFactor specifies the minimum and maximum values the rolloffFactor can have.
191+
/// Values outside the range are clamped to lie within this range. The nominal range depends on <see cref="GetDistanceModelAsync"/> as follows:
192+
/// <list type="table">
193+
/// <item>
194+
/// <term><see cref="DistanceModelType.Linear"/></term>
195+
/// <description>The nominal range is <c>[0,1]</c></description>
196+
/// </item>
197+
/// <item>
198+
/// <term><see cref="DistanceModelType.Inverse"/></term>
199+
/// <description>The nominal range is <c>[0,∞)</c></description>
200+
/// </item>
201+
/// <item>
202+
/// <term><see cref="DistanceModelType.Exponential"/></term>
203+
/// <description>The nominal range is <c>[0,∞)</c></description>
204+
/// </item>
205+
/// </list>
206+
/// Note that the clamping happens as part of the processing of the distance computation.
207+
/// The attribute reflects the value that was set and is not modified.
208+
/// </summary>
209+
/// <remarks>
210+
/// It will throw a <see cref="RangeErrorException"/> if this is set to a negative value.
211+
/// </remarks>
212+
/// <exception cref="RangeErrorException"/>
213+
public async Task SetRolloffFactorAsync(double value)
214+
{
215+
IJSObjectReference helper = await webAudioHelperTask.Value;
216+
ErrorHandlingJSObjectReference errorHandlingHelper = new(JSRuntime, helper);
217+
await errorHandlingHelper.InvokeVoidAsync("setAttribute", JSReference, "rolloffFactor", value);
218+
}
219+
220+
/// <summary>
221+
/// Get the parameter for directional audio sources that is an angle, in degrees, inside of which there will be no volume reduction.
222+
/// The default value is <c>360</c>.
223+
/// The behavior is undefined if the angle is outside the interval <c>[0, 360]</c>.
224+
/// </summary>
225+
public async Task<double> GetConeInnerAngleAsync()
226+
{
227+
IJSObjectReference helper = await webAudioHelperTask.Value;
228+
return await helper.InvokeAsync<double>("getAttribute", JSReference, "coneInnerAngle");
229+
}
230+
231+
/// <summary>
232+
/// Sets the parameter for directional audio sources that is an angle, in degrees, inside of which there will be no volume reduction.
233+
/// The behavior is undefined if the angle is outside the interval <c>[0, 360]</c>.
234+
/// </summary>
235+
public async Task SetConeInnerAngleAsync(double value)
236+
{
237+
IJSObjectReference helper = await webAudioHelperTask.Value;
238+
ErrorHandlingJSObjectReference errorHandlingHelper = new(JSRuntime, helper);
239+
await errorHandlingHelper.InvokeVoidAsync("setAttribute", JSReference, "coneInnerAngle", value);
240+
}
241+
242+
/// <summary>
243+
/// Gets the parameter for directional audio sources that is an angle, in degrees, outside of which the volume will be reduced to a constant value of <see cref="GetConeOuterGain"/>.
244+
/// The default value is <c>360</c>.
245+
/// The behavior is undefined if the angle is outside the interval <c>[0, 360]</c>.
246+
/// </summary>
247+
public async Task<double> GetConeOuterAngleAsync()
248+
{
249+
IJSObjectReference helper = await webAudioHelperTask.Value;
250+
return await helper.InvokeAsync<double>("getAttribute", JSReference, "coneOuterAngle");
251+
}
252+
253+
/// <summary>
254+
/// Sets the parameter for directional audio sources that is an angle, in degrees, outside of which the volume will be reduced to a constant value of <see cref="GetConeOuterGain"/>.
255+
/// The default value is <c>360</c>.
256+
/// The behavior is undefined if the angle is outside the interval <c>[0, 360]</c>.
257+
/// </summary>
258+
public async Task SetConeOuterAngleAsync(double value)
259+
{
260+
IJSObjectReference helper = await webAudioHelperTask.Value;
261+
ErrorHandlingJSObjectReference errorHandlingHelper = new(JSRuntime, helper);
262+
await errorHandlingHelper.InvokeVoidAsync("setAttribute", JSReference, "coneOuterAngle", value);
263+
}
264+
265+
/// <summary>
266+
/// A parameter for directional audio sources that is the gain outside of <see cref="GetConeOuterAngleAsync"/>.
267+
/// The default value is <c>0</c>.
268+
/// It is a linear value (not dB) in the range <c>[0, 1]</c>.
269+
/// </summary>
270+
public async Task<double> GetConeOuterGainAsync()
271+
{
272+
IJSObjectReference helper = await webAudioHelperTask.Value;
273+
return await helper.InvokeAsync<double>("getAttribute", JSReference, "coneOuterGain");
274+
}
275+
276+
/// <summary>
277+
/// A parameter for directional audio sources that is the gain outside of the <see cref="GetConeOuterAngleAsync"/>.
278+
/// It is a linear value (not dB) in the range <c>[0, 1]</c>.
279+
/// </summary>
280+
/// <remarks>
281+
/// It will throw a <see cref="InvalidStateErrorException"/> if this is set to a value outside the range <c>[0, 1]</c>
282+
/// </remarks>
283+
public async Task SetConeOuterGainAsync(double value)
284+
{
285+
IJSObjectReference helper = await webAudioHelperTask.Value;
286+
ErrorHandlingJSObjectReference errorHandlingHelper = new(JSRuntime, helper);
287+
await errorHandlingHelper.InvokeVoidAsync("setAttribute", JSReference, "coneOuterGain", value);
288+
}
30289
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System.Text.Json;
2+
using System.Text.Json.Serialization;
3+
4+
namespace KristofferStrube.Blazor.WebAudio.Converters;
5+
6+
internal class DistanceModelTypeConverter : JsonConverter<DistanceModelType>
7+
{
8+
public override DistanceModelType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
9+
{
10+
return reader.GetString() switch
11+
{
12+
"linear" => DistanceModelType.Linear,
13+
"inverse" => DistanceModelType.Inverse,
14+
"exponential" => DistanceModelType.Exponential,
15+
var value => throw new ArgumentException($"Value '{value}' was not a valid {nameof(DistanceModelType)}.")
16+
};
17+
}
18+
19+
public override void Write(Utf8JsonWriter writer, DistanceModelType value, JsonSerializerOptions options)
20+
{
21+
writer.WriteStringValue(value switch
22+
{
23+
DistanceModelType.Linear => "linear",
24+
DistanceModelType.Inverse => "inverse",
25+
DistanceModelType.Exponential => "exponential",
26+
_ => throw new ArgumentException($"Value '{value}' was not a valid {nameof(DistanceModelType)}.")
27+
});
28+
}
29+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System.Text.Json;
2+
using System.Text.Json.Serialization;
3+
4+
namespace KristofferStrube.Blazor.WebAudio.Converters;
5+
6+
internal class PanningModelTypeConverter : JsonConverter<PanningModelType>
7+
{
8+
public override PanningModelType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
9+
{
10+
return reader.GetString() switch
11+
{
12+
"equalpower" => PanningModelType.EqualPower,
13+
"HRTF" => PanningModelType.HRTF,
14+
var value => throw new ArgumentException($"Value '{value}' was not a valid {nameof(PanningModelType)}.")
15+
};
16+
}
17+
18+
public override void Write(Utf8JsonWriter writer, PanningModelType value, JsonSerializerOptions options)
19+
{
20+
writer.WriteStringValue(value switch
21+
{
22+
PanningModelType.EqualPower => "equalpower",
23+
PanningModelType.HRTF => "HRTF",
24+
_ => throw new ArgumentException($"Value '{value}' was not a valid {nameof(PanningModelType)}.")
25+
});
26+
}
27+
}

0 commit comments

Comments
 (0)