|
8 | 8 |
|
9 | 9 | <svg width="100%" |
10 | 10 | 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" |
12 | 12 | style="user-select:none;touch-action:none;"> |
13 | 13 | @foreach (var i in whiteKeyIndices) |
14 | 14 | { |
|
19 | 19 | @onmouseout="End" |
20 | 20 | @ontouchstart="e => Start(keys[k].Octave, keys[k].Pitch, e)" |
21 | 21 | @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)" |
23 | 23 | y="0" |
24 | 24 | width="100" |
25 | 25 | height="600" |
26 | 26 | fill="white" |
27 | 27 | stroke="black" |
28 | 28 | stroke-width="1"></rect> |
29 | 29 | <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)" |
31 | 31 | y="580" |
32 | 32 | width="100" |
33 | 33 | height="600" |
|
48 | 48 | @onmouseout="End" |
49 | 49 | @ontouchstart="e => Start(keys[k].Octave, keys[k].Pitch, e)" |
50 | 50 | @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))" |
52 | 52 | y="0" |
53 | 53 | width="60" |
54 | 54 | height="400" |
|
80 | 80 | GainNode? keyboardMain; |
81 | 81 | AudioScheduledSourceNode? audioSource; |
82 | 82 |
|
83 | | - double decayEndTime; |
84 | | - |
85 | 83 | List<(string Name, int Octave, int Pitch, string Color)> keys = new(); |
86 | 84 | int[] whiteKeyIndices = Array.Empty<int>(); |
87 | 85 | int[] blackKeyIndices = Array.Empty<int>(); |
88 | 86 |
|
89 | 87 | (string name, int attack, int decay, int sustain, int release)[] adsrPresets = |
90 | 88 | [("Glockenspiel", 2, 2, 50, 100), |
91 | | - ("Piano", 2, 40, 0, 0), |
| 89 | + ("Piano", 2, 40, 0, 20), |
92 | 90 | ("Organ", 2, 2, 100, 2), |
93 | 91 | ("Flute", 40, 2, 70, 20)]; |
94 | 92 |
|
95 | | - protected override async Task OnInitializedAsync() |
| 93 | + protected override void OnInitialized() |
96 | 94 | { |
97 | | - await InitializeContext(); |
98 | | - |
99 | 95 | var letters = new List<string>() { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; |
100 | 96 | keys = Enumerable |
101 | 97 | .Range(3, 3) |
|
124 | 120 |
|
125 | 121 | public async Task InitializeContext() |
126 | 122 | { |
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); |
136 | 132 | } |
137 | 133 |
|
138 | 134 | private SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1); |
|
163 | 159 | await using var amplifierGain = await keyboardMain.GetGainAsync(); |
164 | 160 | var attackEndTime = time + ADSREditor.Attack; |
165 | 161 | 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); |
168 | 163 |
|
169 | 164 | semaphoreSlim.Release(); |
170 | 165 | } |
|
176 | 171 | if (AudioBufferSource is { } recordedBufferSource) |
177 | 172 | { |
178 | 173 | 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 | + }); |
183 | 178 |
|
184 | 179 | await bufferSource.StartAsync(when: 0, recordedBufferSource.offset, recordedBufferSource.duration); |
185 | 180 | return bufferSource; |
186 | 181 | } |
187 | 182 |
|
188 | 183 | 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 | + }); |
193 | 188 | await oscillator.StartAsync(); |
194 | 189 | return oscillator; |
195 | 190 | } |
|
204 | 199 | return; |
205 | 200 | } |
206 | 201 |
|
| 202 | + await InitializeContext(); |
| 203 | + |
207 | 204 | var time = await context.GetCurrentTimeAsync(); |
208 | 205 | 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); |
211 | 208 |
|
212 | 209 | var localKeyboardMain = keyboardMain; |
213 | 210 | keyboardMain = null; |
|
217 | 214 |
|
218 | 215 | semaphoreSlim.Release(); |
219 | 216 |
|
220 | | - await Task.Delay((int)((Math.Max(0, decayEndTime - time) + ADSREditor.Release) * 1000) + 100); |
| 217 | + await Task.Delay(5000); |
221 | 218 | await localKeyboardMain.DisconnectAsync(); |
222 | 219 | await localKeyboardMain.DisposeAsync(); |
223 | 220 | await localOscillator.DisconnectAsync(); |
|
0 commit comments