Skip to content

Commit

Permalink
Tidy up and add a background recording demo
Browse files Browse the repository at this point in the history
There are still Firefox/Safari issues that we're trying to understand.
  • Loading branch information
microbit-matt-hillsdon committed Aug 27, 2024
1 parent 3365d06 commit c1f389c
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 22 deletions.
39 changes: 17 additions & 22 deletions src/board/audio/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ interface AudioOptions {
speechAudioCallback: () => void;
speechResampler: SRC;
soundExpressionResampler: SRC;
recordingResampler: SRC;
}

export class BoardAudio {
Expand All @@ -32,6 +33,8 @@ export class BoardAudio {
private muteNode: GainNode | undefined;
private sensitivityNode: GainNode | undefined;

private recordingResampler: SRC | undefined;

default: BufferedAudio | undefined;
speech: BufferedAudio | undefined;
soundExpression: BufferedAudio | undefined;
Expand All @@ -46,10 +49,13 @@ export class BoardAudio {
speechAudioCallback,
speechResampler,
soundExpressionResampler,
recordingResampler,
}: AudioOptions) {
if (!this.context) {
throw new Error("Context must be pre-created from a user event");
}
this.recordingResampler = recordingResampler;

this.muteNode = this.context.createGain();
this.muteNode.gain.setValueAtTime(
this.muted ? 0 : 1,
Expand Down Expand Up @@ -221,17 +227,13 @@ export class BoardAudio {
source.connect(this.sensitivityNode!);

const recorder = this.context!.createScriptProcessor(2048, 1, 1);
const inputSampleRate = 44100;
const sampleRateConverter = await createSampleRateConverter(
1,
inputSampleRate,
sampleRate,
{
converterType: ConverterType.SRC_SINC_FASTEST,
}
);

const inputSampleRate = this.context!.sampleRate;
this.recordingResampler!.inputSampleRate = inputSampleRate;
this.recordingResampler!.outputSampleRate = sampleRate;

recorder.onaudioprocess = (e) => {
const resampled = sampleRateConverter.full(
const resampled = this.recordingResampler!.full(
e.inputBuffer.getChannelData(0)
);
onChunk(resampled);
Expand All @@ -248,8 +250,7 @@ export class BoardAudio {
recorder.disconnect();
this.sensitivityNode!.disconnect();
source.disconnect();
micStream.getTracks().forEach((track) => track.stop());
sampleRateConverter.destroy();
micStream?.getTracks().forEach((track) => track.stop());
this.microphoneEl.style.display = "none";
this.stopActiveRecording = undefined;
};
Expand All @@ -273,7 +274,6 @@ export class BoardAudio {

class BufferedAudio {
nextStartTime: number = -1;
private sampleRate: number = -1;

constructor(
private context: AudioContext,
Expand All @@ -291,19 +291,14 @@ class BufferedAudio {
}

setSampleRate(sampleRate: number) {
this.sampleRate = sampleRate;
this.resampler.inputSampleRate = sampleRate;
}

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

// Use createXXX instead to support Safari 14.0.
const buffer = this.context.createBuffer(1, data.length, sampleRate);
Expand Down
2 changes: 2 additions & 0 deletions src/board/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ export class Board {
});

// We update the sample rates before use.
const recordingResampler = await createResampler(1, 48000, 48000);
const defaultResampler = await createResampler(1, 48000, 48000);
const speechResampler = await createResampler(1, 48000, 48000);
// Probably this one is never used so would be nice to avoid
Expand All @@ -262,6 +263,7 @@ export class Board {
speechAudioCallback: wrapped._microbit_hal_audio_speech_ready_callback,
speechResampler,
soundExpressionResampler,
recordingResampler,
});
this.accelerometer.initializeCallbacks(
wrapped._microbit_hal_gesture_callback
Expand Down
1 change: 1 addition & 0 deletions src/demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ <h1>MicroPython-micro:bit simulator example embedding</h1>
<option value="radio">Radio</option>
<option value="random">Random</option>
<option value="record">Record</option>
<option value="record_background">Record (background)</option>
<option value="test_record">
Record (MicroPython test file)
</option>
Expand Down
19 changes: 19 additions & 0 deletions src/examples/record_background.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from microbit import microphone, audio, button_a, button_b, sleep

rates = [7812, 3906, 15624]
rate_index = 0

print("Recording...")
my_recording = audio.AudioRecording(3000)
my_track = microphone.record_into(my_recording, wait=False)
sleep(3000)
print("Button A to play")
while True:
if button_a.was_pressed():
audio.play(my_track, wait=False)
print("Rate playing", rates[rate_index])

if button_b.was_pressed():
rate_index = (rate_index + 1) % len(rates)
print("Rate change to", rates[rate_index])
my_track.set_rate(rates[rate_index])

0 comments on commit c1f389c

Please sign in to comment.