Skip to content

Commit

Permalink
Updated PullAudioWorkletProcessor to support stereo.
Browse files Browse the repository at this point in the history
  • Loading branch information
KristofferStrube committed May 7, 2024
1 parent d6032c1 commit 16874d2
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
<div style="display:flex; flex-direction:column; grid-gap: 5px;">
<button class="btn btn-success" @onclick=PlayWhiteSound>Play Sound from static JS White Noise Generator</button>
<hr />
<button class="btn btn-success" @onclick=PlayPullProcessedSound>Play Sound that is pulled from Blazor</button>
<button class="btn btn-success" @onclick=PlayMonoPullProcessedSound>Play Mono Sound that is pulled from Blazor</button>
<button class="btn btn-success" @onclick=PlayStereoPullProcessedSound>Play Stereo Sound that is pulled from Blazor</button>
<div>
<label for="lowTide">Low Tide</label>
<input id="lowTide" @bind=options.LowTide />
Expand All @@ -37,9 +38,8 @@ else

@code {
PullAudioWorkletProcessor.Options options = new()
{
Produce = () => Random.Shared.NextDouble() * 2 - 1,
};
{
};
PullAudioWorkletProcessor? processor;
AudioWorkletNode? worktletNode;

Expand Down Expand Up @@ -67,11 +67,32 @@ else
await gainNode.ConnectAsync(destination);
}

public async Task PlayPullProcessedSound()
public async Task PlayMonoPullProcessedSound()
{
// Get context.
context = await AudioContext.CreateAsync(JSRuntime);

options.ProduceMono = () => Random.Shared.NextDouble() * 2 - 1;
options.ProduceStereo = null;

// Create and register node.
processor = await PullAudioWorkletProcessor.CreateAsync(context, options);

// Get destination and connect worklet node through gainNode
destination = await context.GetDestinationAsync();
gainNode = await GainNode.CreateAsync(JSRuntime, context, new() { Gain = 0.05f });
await processor.Node.ConnectAsync(gainNode);
await gainNode.ConnectAsync(destination);
}

public async Task PlayStereoPullProcessedSound()
{
// Get context.
context = await AudioContext.CreateAsync(JSRuntime);

options.ProduceMono = null;
options.ProduceStereo = () => (left: Random.Shared.NextDouble() * 2 - 1, right: Random.Shared.NextDouble() * 2 - 1);

// Create and register node.
processor = await PullAudioWorkletProcessor.CreateAsync(context, options);

Expand All @@ -89,12 +110,6 @@ else
await context.DisposeAsync();
}

if (gainNode is not null)
{
await gainNode.DisposeAsync();
gainNode = null;
}

if (destination is not null)
{
await destination.DisposeAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ public class Options
public int LowTide { get; set; } = 100;

Check warning on line 7 in src/KristofferStrube.Blazor.WebAudio/AudioWorklet/AudioWorkletProcessors/PullAudioWorkletProcessor.Options.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'PullAudioWorkletProcessor.Options.LowTide'
public int HighTide { get; set; } = 500;

Check warning on line 8 in src/KristofferStrube.Blazor.WebAudio/AudioWorklet/AudioWorkletProcessors/PullAudioWorkletProcessor.Options.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'PullAudioWorkletProcessor.Options.HighTide'
public int BufferRequestSize { get; set; } = 100;

Check warning on line 9 in src/KristofferStrube.Blazor.WebAudio/AudioWorklet/AudioWorkletProcessors/PullAudioWorkletProcessor.Options.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'PullAudioWorkletProcessor.Options.BufferRequestSize'
public required Func<double> Produce { get; set; }

/// <summary>
/// A functions that will be used to pull data to play in the audio processor as mono audio
/// </summary>
/// <remarks>
/// If <see cref="ProduceStereo"/> is supplied then this will be ignored.
/// </remarks>
public Func<double>? ProduceMono { get; set; }

/// <summary>
/// A functions that will be used to pull data to play in the audio processor as stereo audio.
/// </summary>
public Func<(double left, double right)>? ProduceStereo { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,52 @@ public static async Task<PullAudioWorkletProcessor> CreateAsync(BaseAudioContext
await using AudioWorklet audioWorklet = await audioContext.GetAudioWorkletAsync();
await audioWorklet.AddModuleAsync("./_content/KristofferStrube.Blazor.WebAudio/KristofferStrube.Blazor.WebAudio.PullAudioProcessor.js");

ulong channels = (ulong)(options.ProduceStereo is not null ? 2 : 1);

double[] ProduceArray(int chunks)
{
double[] result;
if (options.ProduceStereo is not null)
{
result = new double[chunks * 128 * 2];
for (int i = 0; i < chunks; i++)
{
for (int j = 0; j < 128; j++)
{
(double left, double right) = options.ProduceStereo();
result[(i * 128 * 2) + j] = left;
result[(i * 128 * 2) + 128 + j] = right;
}
}
}
else if (options.ProduceMono is not null)
{
result = new double[chunks * 128];
for (int i = 0; i < chunks; i++)
{
for (int j = 0; j < 128; j++)
{
double monoSound = options.ProduceMono();
result[(i * 128) + j] = monoSound;
}
}
}
else
{
result = Enumerable.Range(0, 128 * chunks).Select(_ => 0.0).ToArray();
}
return result;
}

AudioWorkletNodeOptions nodeOptions = new()
{
ParameterData = new()
{
["lowTide"] = options.LowTide,
["highTide"] = options.HighTide,
["bufferRequestSize"] = options.BufferRequestSize,
}
},
OutputChannelCount = new ulong[] { channels }
};

AudioWorkletNode audioWorkletNode = await AudioWorkletNode.CreateAsync(audioContext.JSRuntime, audioContext, "kristoffer-strube-webaudio-pull-audio-processor", nodeOptions);
Expand All @@ -42,25 +80,10 @@ public static async Task<PullAudioWorkletProcessor> CreateAsync(BaseAudioContext
await messagePort.StartAsync();
EventListener<MessageEvent> messageEventListener = await EventListener<MessageEvent>.CreateAsync(audioContext.JSRuntime, async (e) =>

Check warning on line 81 in src/KristofferStrube.Blazor.WebAudio/AudioWorklet/AudioWorkletProcessors/PullAudioWorkletProcessor.cs

View workflow job for this annotation

GitHub Actions / build

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
int dataNeededToReachLowTide = await e.GetDataAsync<int>();
messagePort.PostMessage(
Enumerable
.Range(0, dataNeededToReachLowTide)
.Select(
_ => Enumerable.Range(0, 128).Select(_ => options.Produce()).ToArray()
).ToArray()
);
messagePort.PostMessage(ProduceArray(options.BufferRequestSize));
});
await messagePort.AddOnMessageEventListenerAsync(messageEventListener);

await messagePort.PostMessageAsync(
Enumerable
.Range(0, 100)
.Select(
_ => Enumerable.Range(0, 128).Select(_ => options.Produce()).ToArray()
).ToArray()
);

return new PullAudioWorkletProcessor(audioWorkletNode, messagePort, messageEventListener);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
queue = [];
backIndex = 0;
frontIndex = 0;
dataRequested = 100;
dataRequested = 0;

static get parameterDescriptors() {
return [{
Expand Down Expand Up @@ -32,10 +32,11 @@
super(...args);
this.queue = [];
this.port.onmessage = (e) => {
e.data.forEach(data => this.queue.push(data));
this.frontIndex += e.data.length;
this.dataRequested -= e.data.length;
//this.port.postMessage((this.frontIndex - this.backIndex).toString());
for (let i = 0; i < e.data.length / 128; i++) {
this.queue.push(e.data.slice(i * 128, (i + 1) * 128));
this.frontIndex++;
this.dataRequested--;
}
};
}

Expand All @@ -47,15 +48,18 @@

try {
const count = this.frontIndex - this.backIndex;
this.port.postMessage((count).toString());
if (count != 0) {
let data = this.queue[this.backIndex];
this.backIndex++;
for (let i = 0; i < output.length; i++)
{
let data = this.queue[this.backIndex];
this.backIndex++;

output.forEach((channel) => {
for (let i = 0; i < channel.length; i++) {
channel[i] = data[i];
let channel = output[i];
for (let j = 0; j < channel.length; j++) {
channel[j] = data[j];
}
});
}
}
if (count < lowTide && this.dataRequested + bufferRequestSize < highTide) {
this.dataRequested += bufferRequestSize;
Expand Down

0 comments on commit 16874d2

Please sign in to comment.