Skip to content

Commit c1f389c

Browse files
Tidy up and add a background recording demo
There are still Firefox/Safari issues that we're trying to understand.
1 parent 3365d06 commit c1f389c

File tree

4 files changed

+39
-22
lines changed

4 files changed

+39
-22
lines changed

Diff for: src/board/audio/index.ts

+17-22
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ interface AudioOptions {
2020
speechAudioCallback: () => void;
2121
speechResampler: SRC;
2222
soundExpressionResampler: SRC;
23+
recordingResampler: SRC;
2324
}
2425

2526
export class BoardAudio {
@@ -32,6 +33,8 @@ export class BoardAudio {
3233
private muteNode: GainNode | undefined;
3334
private sensitivityNode: GainNode | undefined;
3435

36+
private recordingResampler: SRC | undefined;
37+
3538
default: BufferedAudio | undefined;
3639
speech: BufferedAudio | undefined;
3740
soundExpression: BufferedAudio | undefined;
@@ -46,10 +49,13 @@ export class BoardAudio {
4649
speechAudioCallback,
4750
speechResampler,
4851
soundExpressionResampler,
52+
recordingResampler,
4953
}: AudioOptions) {
5054
if (!this.context) {
5155
throw new Error("Context must be pre-created from a user event");
5256
}
57+
this.recordingResampler = recordingResampler;
58+
5359
this.muteNode = this.context.createGain();
5460
this.muteNode.gain.setValueAtTime(
5561
this.muted ? 0 : 1,
@@ -221,17 +227,13 @@ export class BoardAudio {
221227
source.connect(this.sensitivityNode!);
222228

223229
const recorder = this.context!.createScriptProcessor(2048, 1, 1);
224-
const inputSampleRate = 44100;
225-
const sampleRateConverter = await createSampleRateConverter(
226-
1,
227-
inputSampleRate,
228-
sampleRate,
229-
{
230-
converterType: ConverterType.SRC_SINC_FASTEST,
231-
}
232-
);
230+
231+
const inputSampleRate = this.context!.sampleRate;
232+
this.recordingResampler!.inputSampleRate = inputSampleRate;
233+
this.recordingResampler!.outputSampleRate = sampleRate;
234+
233235
recorder.onaudioprocess = (e) => {
234-
const resampled = sampleRateConverter.full(
236+
const resampled = this.recordingResampler!.full(
235237
e.inputBuffer.getChannelData(0)
236238
);
237239
onChunk(resampled);
@@ -248,8 +250,7 @@ export class BoardAudio {
248250
recorder.disconnect();
249251
this.sensitivityNode!.disconnect();
250252
source.disconnect();
251-
micStream.getTracks().forEach((track) => track.stop());
252-
sampleRateConverter.destroy();
253+
micStream?.getTracks().forEach((track) => track.stop());
253254
this.microphoneEl.style.display = "none";
254255
this.stopActiveRecording = undefined;
255256
};
@@ -273,7 +274,6 @@ export class BoardAudio {
273274

274275
class BufferedAudio {
275276
nextStartTime: number = -1;
276-
private sampleRate: number = -1;
277277

278278
constructor(
279279
private context: AudioContext,
@@ -291,19 +291,14 @@ class BufferedAudio {
291291
}
292292

293293
setSampleRate(sampleRate: number) {
294-
this.sampleRate = sampleRate;
295294
this.resampler.inputSampleRate = sampleRate;
296295
}
297296

298297
writeData(data: Float32Array) {
299-
let sampleRate = this.sampleRate;
300-
// In practice the supported range is less than the 8k..96k required by the spec
301-
let alwaysResample = true;
302-
if (alwaysResample || sampleRate < 8_000 || sampleRate > 96_000) {
303-
// We need to resample
304-
sampleRate = this.context.sampleRate;
305-
data = this.resampler.full(data);
306-
}
298+
// In practice the supported range is less than the 8k..96k required by the spec and varies by browser
299+
// for a consistent performance profile we're always resampling for now rather than letting Web Audio do it
300+
let sampleRate = this.context.sampleRate;
301+
data = this.resampler.full(data);
307302

308303
// Use createXXX instead to support Safari 14.0.
309304
const buffer = this.context.createBuffer(1, data.length, sampleRate);

Diff for: src/board/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ export class Board {
250250
});
251251

252252
// We update the sample rates before use.
253+
const recordingResampler = await createResampler(1, 48000, 48000);
253254
const defaultResampler = await createResampler(1, 48000, 48000);
254255
const speechResampler = await createResampler(1, 48000, 48000);
255256
// Probably this one is never used so would be nice to avoid
@@ -262,6 +263,7 @@ export class Board {
262263
speechAudioCallback: wrapped._microbit_hal_audio_speech_ready_callback,
263264
speechResampler,
264265
soundExpressionResampler,
266+
recordingResampler,
265267
});
266268
this.accelerometer.initializeCallbacks(
267269
wrapped._microbit_hal_gesture_callback

Diff for: src/demo.html

+1
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ <h1>MicroPython-micro:bit simulator example embedding</h1>
9999
<option value="radio">Radio</option>
100100
<option value="random">Random</option>
101101
<option value="record">Record</option>
102+
<option value="record_background">Record (background)</option>
102103
<option value="test_record">
103104
Record (MicroPython test file)
104105
</option>

Diff for: src/examples/record_background.py

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from microbit import microphone, audio, button_a, button_b, sleep
2+
3+
rates = [7812, 3906, 15624]
4+
rate_index = 0
5+
6+
print("Recording...")
7+
my_recording = audio.AudioRecording(3000)
8+
my_track = microphone.record_into(my_recording, wait=False)
9+
sleep(3000)
10+
print("Button A to play")
11+
while True:
12+
if button_a.was_pressed():
13+
audio.play(my_track, wait=False)
14+
print("Rate playing", rates[rate_index])
15+
16+
if button_b.was_pressed():
17+
rate_index = (rate_index + 1) % len(rates)
18+
print("Rate change to", rates[rate_index])
19+
my_track.set_rate(rates[rate_index])

0 commit comments

Comments
 (0)