2
2
sidebar_position : 3
3
3
---
4
4
5
+ import InteractiveExample from ' @site/src/components/InteractiveExample' ;
6
+
5
7
# Making a piano keyboard
6
8
7
9
In this section, we will use some of the core audio api interfaces to create a simple piano keyboard. We will learn what is an ` AudioParam ` , how to use it to change the pitch of the sound.
@@ -71,13 +73,8 @@ Like previously, we will need to preload the audio files in order to be able to
71
73
First comes the import section and list of the sources we will use, also lets help ourselves with type shorthand for partial record:
72
74
73
75
``` tsx
74
- import {
75
- GainNode ,
76
- AudioBuffer ,
77
- AudioContext ,
78
- AudioBufferSourceNode ,
79
- } from ' react-native-audio-api' ;
80
76
import * as FileSystem from ' expo-file-system' ;
77
+ import { AudioBuffer , AudioContext } from ' react-native-audio-api' ;
81
78
82
79
/* ... */
83
80
@@ -103,7 +100,7 @@ export default function SimplePiano() {
103
100
audioContextRef .current = new AudioContext ();
104
101
}
105
102
106
- Object .entries (sourceList ).forEach (async ([key , source ]) => {
103
+ Object .entries (sourceList ).forEach (async ([key , url ]) => {
107
104
bufferListRef .current [key as KeyName ] = await FileSystem .downloadAsync (
108
105
url ,
109
106
` ${FileSystem .documentDirectory }/${key }.mp3 `
@@ -120,9 +117,13 @@ Now it is finally time to play the sounds, but still nothing new here. We will u
120
117
``` tsx
121
118
export default function SimplePiano() {
122
119
const onKeyPressIn = (which : KeyName ) => {
123
- const audioContext = audioContextRef .current ! ;
120
+ const audioContext = audioContextRef .current ;
124
121
const buffer = bufferMapRef .current [which ];
125
122
123
+ if (! audioContext || ! buffer ) {
124
+ return ;
125
+ }
126
+
126
127
const source = new AudioBufferSourceNode (audioContext , {
127
128
buffer ,
128
129
});
@@ -133,6 +134,13 @@ export default function SimplePiano() {
133
134
}
134
135
```
135
136
137
+ When we put everything all together, we will get something like this:
138
+
139
+ import ItHangs from ' @site/src/examples/SimplePiano/ItHangsComponent' ;
140
+ import ItHangsSrc from ' !!raw-loader!@site/src/examples/SimplePiano/ItHangsSource' ;
141
+
142
+ <InteractiveExample component = { ItHangs } src = { ItHangsSrc } />
143
+
136
144
Great! But a lot of things are a bit off here:
137
145
138
146
- We are not stopping the sound when the button is released, which is kind of the way piano should work, right? 🙃
@@ -171,12 +179,20 @@ And finally we can implement the `onKeyPressOut` function
171
179
``` tsx
172
180
const onKeyPressOut = (which : KeyName ) => {
173
181
const source = playingNotesRef .current [which ];
182
+
174
183
if (source ) {
175
184
source .stop ();
176
185
}
177
186
};
178
187
```
179
188
189
+ Putting it all together again we get:
190
+
191
+ import PressOutComponent from ' @site/src/examples/SimplePiano/PressOutComponent' ;
192
+ import PressOutSrc from ' !!raw-loader!@site/src/examples/SimplePiano/PressOutSource' ;
193
+
194
+ <InteractiveExample component = { PressOutComponent } src = { PressOutSrc } />
195
+
180
196
And they stop on release, just as we wanted. But if we hold the keys for a short time, it sounds a bit strange. Also have You noticed that the sound is simply cut off when we release the key? 🤔
181
197
It leave a bit unpleasant feeling, right? So let's try to make it a bit more smooth.
182
198
@@ -237,10 +253,14 @@ const onKeyPressIn = (which: KeyName) => {
237
253
const buffer = bufferMapRef .current [which ];
238
254
const tNow = audioContext .currentTime ;
239
255
240
- const source = aCtx .createBufferSource ();
256
+ if (! audioContext || ! buffer ) {
257
+ return ;
258
+ }
259
+
260
+ const source = audioContext .createBufferSource ();
241
261
source .buffer = buffer ;
242
262
243
- const envelope = aCtx .createGain ();
263
+ const envelope = audioContext .createGain ();
244
264
245
265
source .connect (envelope );
246
266
envelope .connect (audioContext .destination );
@@ -258,7 +278,13 @@ and the `onKeyPressOut` function:
258
278
``` tsx
259
279
const onKeyPressOut = (which : KeyName ) => {
260
280
const audioContext = audioContextRef .current ! ;
261
- const { source, envelope, startedAt } = playingNotesRef .current [which ];
281
+ const playingNote = playingNotesRef .current [which ];
282
+
283
+ if (! playingNote || ! audioContext ) {
284
+ return ;
285
+ }
286
+
287
+ const { source, envelope, startedAt } = playingNote ;
262
288
263
289
const tStop = Math .max (audioContext .currentTime , startedAt + 1 );
264
290
@@ -270,6 +296,13 @@ const onKeyPressOut = (which: KeyName) => {
270
296
};
271
297
```
272
298
299
+ In result we can hear something like this:
300
+
301
+ import EnvelopesComponent from ' @site/src/examples/SimplePiano/EnvelopesComponent' ;
302
+ import EnvelopesSrc from ' !!raw-loader!@site/src/examples/SimplePiano/EnvelopesSource' ;
303
+
304
+ <InteractiveExample component = { EnvelopesComponent } src = { EnvelopesSrc } />
305
+
273
306
And it finally sounds smooth and nice. But what about decay and sustain phases? Both are done by the audio samples themselves, so we don't need to worry about them. To be honest, same goes for the attack phase, but we have implemented it for the sake of this guide. 🙂 <br /><br />
274
307
So the only missing piece left is doing something about the missing sample files for 'B' and 'D' keys. What we can do about that?
275
308
@@ -332,18 +365,15 @@ const onKeyPressIn = (which: KeyName) => {
332
365
const source = aCtx .createBufferSource ();
333
366
const envelope = aCtx .createGain ();
334
367
source .buffer = buffer ;
335
-
336
- // rest of the code remains the same
337
368
};
338
369
```
339
370
340
371
## Final results
341
372
342
373
As previously, you can see the final results in the live example below with full source code.
343
374
344
- import InteractiveExample from ' @site/src/components/InteractiveExample' ;
345
- import SimplePiano from ' @site/src/examples/SimplePiano/Component' ;
346
- import SimplePianoSrc from ' !!raw-loader!@site/src/examples/SimplePiano/Source' ;
375
+ import SimplePiano from ' @site/src/examples/SimplePiano/FinalComponent' ;
376
+ import SimplePianoSrc from ' !!raw-loader!@site/src/examples/SimplePiano/FinalSource' ;
347
377
348
378
<InteractiveExample component = { SimplePiano } src = { SimplePianoSrc } />
349
379
0 commit comments