Skip to content

Commit c7c47e1

Browse files
Updated keyboard related demos to probably work on iPhone as well.
1 parent a92237c commit c7c47e1

3 files changed

Lines changed: 71 additions & 69 deletions

File tree

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

Lines changed: 42 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,16 @@ Status:
2121
{
2222
<button class="btn btn-warning" @onclick="() => context.SuspendAsync()">Suspend</button>
2323
<button class="btn btn-danger" @onclick="() => context.CloseAsync()">Close</button>
24-
25-
@if (oscillator is null)
26-
{
27-
<button class="btn btn-success" @onclick=PlaySound>Play Sound 🔊</button>
28-
}
29-
else
30-
{
31-
<button class="btn btn-warning" @onclick=StopSound>Stop Sound 🔊</button>
32-
}
33-
<EnumSelector @bind-Value=oscillatorType Disabled=@(oscillator is not null) />
3424
}
25+
@if (oscillator is null)
26+
{
27+
<button class="btn btn-success" @onclick=PlaySound>Play Sound 🔊</button>
28+
}
29+
else
30+
{
31+
<button class="btn btn-warning" @onclick=StopSound>Stop Sound 🔊</button>
32+
}
33+
<EnumSelector @bind-Value=oscillatorType Disabled=@(oscillator is not null) />
3534
</div>
3635
@if (oscillatorType is OscillatorType.Custom)
3736
{
@@ -87,7 +86,7 @@ Status:
8786
GainNode gainNode = default!;
8887
AnalyserNode analyser = default!;
8988
EventListener<Event> stateChangeListener = default!;
90-
AudioContextState state = AudioContextState.Closed;
89+
AudioContextState? state;
9190
OscillatorNode? oscillator;
9291
OscillatorType oscillatorType = OscillatorType.Sine;
9392
PeriodicWave? customWave;
@@ -101,32 +100,10 @@ Status:
101100
float[] imagCoefficients = new float[10];
102101
int coefficientSeriesLength = 10;
103102

104-
protected override async Task OnInitializedAsync()
105-
{
106-
nIdentifier = new Identifier("n", () => n);
107-
identifiers = [nIdentifier];
108-
CustomWavePresetUpdated();
109-
CalculateCoefficients();
110-
111-
context = await AudioContext.CreateAsync(JSRuntime);
112-
113-
AudioDestinationNode destination = await context.GetDestinationAsync();
114-
gainNode = await GainNode.CreateAsync(JSRuntime, context, new() { Gain = 0.1f });
115-
analyser = await AnalyserNode.CreateAsync(JSRuntime, context);
116-
117-
await gainNode.ConnectAsync(destination);
118-
await gainNode.ConnectAsync(analyser);
119-
120-
stateChangeListener = await context.AddOnStateChangeEventListener(async (e) =>
121-
{
122-
state = await context.GetStateAsync();
123-
StateHasChanged();
124-
});
125-
state = await context.GetStateAsync();
126-
}
127-
128103
public async Task PlaySound()
129104
{
105+
await InitializeContext();
106+
130107
if (oscillatorType is OscillatorType.Custom)
131108
{
132109
customWave = await PeriodicWave.CreateAsync(JSRuntime, context, new()
@@ -165,6 +142,33 @@ Status:
165142
}
166143
}
167144

145+
public async Task InitializeContext()
146+
{
147+
if (context is not null)
148+
return;
149+
150+
nIdentifier = new Identifier("n", () => n);
151+
identifiers = [nIdentifier];
152+
CustomWavePresetUpdated();
153+
CalculateCoefficients();
154+
155+
context = await AudioContext.CreateAsync(JSRuntime);
156+
157+
AudioDestinationNode destination = await context.GetDestinationAsync();
158+
gainNode = await GainNode.CreateAsync(JSRuntime, context, new() { Gain = 0.1f });
159+
analyser = await AnalyserNode.CreateAsync(JSRuntime, context);
160+
161+
await gainNode.ConnectAsync(destination);
162+
await gainNode.ConnectAsync(analyser);
163+
164+
stateChangeListener = await context.AddOnStateChangeEventListener(async (e) =>
165+
{
166+
state = await context.GetStateAsync();
167+
StateHasChanged();
168+
});
169+
state = await context.GetStateAsync();
170+
}
171+
168172
public void CustomWavePresetUpdated()
169173
{
170174
customFormula = selectedFormulaPreset switch
@@ -196,6 +200,9 @@ Status:
196200

197201
public async ValueTask DisposeAsync()
198202
{
203+
if (context is null)
204+
return;
205+
199206
await context.RemoveOnStateChangeEventListener(stateChangeListener);
200207
await StopSound();
201208
if (context is not null)

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

Lines changed: 29 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
<svg width="100%"
1010
height="40vh"
11-
viewBox="0 0 @(keys.Count(k => k.Color == "white")*100) 600"
11+
viewBox="0 -1 @(keys.Count(k => k.Color == "white") * 100) 602"
1212
style="user-select:none;touch-action:none;">
1313
@foreach (var i in whiteKeyIndices)
1414
{
@@ -19,15 +19,15 @@
1919
@onmouseout="End"
2020
@ontouchstart="e => Start(keys[k].Octave, keys[k].Pitch, e)"
2121
@ontouchend="End"
22-
x="@(keys.GetRange(0, i).Count(k => k.Color == "white")*100)"
22+
x="@(keys.GetRange(0, i).Count(k => k.Color == "white") * 100)"
2323
y="0"
2424
width="100"
2525
height="600"
2626
fill="white"
2727
stroke="black"
2828
stroke-width="1"></rect>
2929
<text>
30-
<text x="@(keys.GetRange(0, i).Count(k => k.Color == "white")*100+20)"
30+
<text x="@(keys.GetRange(0, i).Count(k => k.Color == "white") * 100 + 20)"
3131
y="580"
3232
width="100"
3333
height="600"
@@ -48,7 +48,7 @@
4848
@onmouseout="End"
4949
@ontouchstart="e => Start(keys[k].Octave, keys[k].Pitch, e)"
5050
@ontouchend="End"
51-
x="@(keys.GetRange(0, i).Count(k => k.Color == "white")*100-30 - (keys[k].Pitch is 2 or 7 ? 10 : 0) + (keys[k].Pitch is 4 or 11 ? 10 : 0))"
51+
x="@(keys.GetRange(0, i).Count(k => k.Color == "white") * 100 - 30 - (keys[k].Pitch is 2 or 7 ? 10 : 0) + (keys[k].Pitch is 4 or 11 ? 10 : 0))"
5252
y="0"
5353
width="60"
5454
height="400"
@@ -80,22 +80,18 @@
8080
GainNode? keyboardMain;
8181
AudioScheduledSourceNode? audioSource;
8282

83-
double decayEndTime;
84-
8583
List<(string Name, int Octave, int Pitch, string Color)> keys = new();
8684
int[] whiteKeyIndices = Array.Empty<int>();
8785
int[] blackKeyIndices = Array.Empty<int>();
8886

8987
(string name, int attack, int decay, int sustain, int release)[] adsrPresets =
9088
[("Glockenspiel", 2, 2, 50, 100),
91-
("Piano", 2, 40, 0, 0),
89+
("Piano", 2, 40, 0, 20),
9290
("Organ", 2, 2, 100, 2),
9391
("Flute", 40, 2, 70, 20)];
9492

95-
protected override async Task OnInitializedAsync()
93+
protected override void OnInitialized()
9694
{
97-
await InitializeContext();
98-
9995
var letters = new List<string>() { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" };
10096
keys = Enumerable
10197
.Range(3, 3)
@@ -124,15 +120,15 @@
124120

125121
public async Task InitializeContext()
126122
{
127-
if (context is null || mainNode is null || compressor is null)
128-
{
129-
context = await AudioContext.CreateAsync(JSRuntime);
130-
mainNode = await GainNode.CreateAsync(JSRuntime, context, new() { Gain = 0.1f });
131-
compressor = await DynamicsCompressorNode.CreateAsync(JSRuntime, context);
132-
await using AudioDestinationNode destination = await context.GetDestinationAsync();
133-
await compressor.ConnectAsync(mainNode);
134-
await mainNode.ConnectAsync(destination);
135-
}
123+
if (context is not null)
124+
return;
125+
126+
context = await AudioContext.CreateAsync(JSRuntime);
127+
mainNode = await GainNode.CreateAsync(JSRuntime, context, new() { Gain = 0.1f });
128+
compressor = await DynamicsCompressorNode.CreateAsync(JSRuntime, context);
129+
await using AudioDestinationNode destination = await context.GetDestinationAsync();
130+
await compressor.ConnectAsync(mainNode);
131+
await mainNode.ConnectAsync(destination);
136132
}
137133

138134
private SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);
@@ -163,8 +159,7 @@
163159
await using var amplifierGain = await keyboardMain.GetGainAsync();
164160
var attackEndTime = time + ADSREditor.Attack;
165161
await amplifierGain.LinearRampToValueAtTimeAsync(1f, attackEndTime);
166-
decayEndTime = attackEndTime + ADSREditor.Decay;
167-
await amplifierGain.LinearRampToValueAtTimeAsync(ADSREditor.Sustain, decayEndTime);
162+
await amplifierGain.SetTargetAtTimeAsync(ADSREditor.Sustain, attackEndTime, (float)ADSREditor.Decay);
168163

169164
semaphoreSlim.Release();
170165
}
@@ -176,20 +171,20 @@
176171
if (AudioBufferSource is { } recordedBufferSource)
177172
{
178173
AudioBufferSourceNode bufferSource = await AudioBufferSourceNode.CreateAsync(JSRuntime, context, new()
179-
{
180-
Buffer = recordedBufferSource.buffer,
181-
PlaybackRate = recordedBufferSource.playbackRateToMatch440 * frequency / 440
182-
});
174+
{
175+
Buffer = recordedBufferSource.buffer,
176+
PlaybackRate = recordedBufferSource.playbackRateToMatch440 * frequency / 440
177+
});
183178

184179
await bufferSource.StartAsync(when: 0, recordedBufferSource.offset, recordedBufferSource.duration);
185180
return bufferSource;
186181
}
187182

188183
var oscillator = await OscillatorNode.CreateAsync(JSRuntime, context, new()
189-
{
190-
Type = OscillatorType.Sine,
191-
Frequency = frequency
192-
});
184+
{
185+
Type = OscillatorType.Sine,
186+
Frequency = frequency
187+
});
193188
await oscillator.StartAsync();
194189
return oscillator;
195190
}
@@ -204,10 +199,12 @@
204199
return;
205200
}
206201

202+
await InitializeContext();
203+
207204
var time = await context.GetCurrentTimeAsync();
208205
await using var amplifierGain = await keyboardMain.GetGainAsync();
209-
await amplifierGain.CancelScheduledValuesAsync(0);
210-
await amplifierGain.LinearRampToValueAtTimeAsync(0, Math.Max(time, decayEndTime) + ADSREditor.Release);
206+
await amplifierGain.CancelAndHoldAtTimeAsync(0);
207+
await amplifierGain.SetTargetAtTimeAsync(0, 0, (float)ADSREditor.Release);
211208

212209
var localKeyboardMain = keyboardMain;
213210
keyboardMain = null;
@@ -217,7 +214,7 @@
217214

218215
semaphoreSlim.Release();
219216

220-
await Task.Delay((int)((Math.Max(0, decayEndTime - time) + ADSREditor.Release) * 1000) + 100);
217+
await Task.Delay(5000);
221218
await localKeyboardMain.DisconnectAsync();
222219
await localKeyboardMain.DisposeAsync();
223220
await localOscillator.DisconnectAsync();

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,6 @@ else
103103
{
104104
await StopAudioTrack();
105105

106-
await InvokeAsync(StateHasChanged);
107-
108106
try
109107
{
110108
if (context is null)

0 commit comments

Comments
 (0)