diff --git a/src/board/audio/index.ts b/src/board/audio/index.ts index 7a7a297..e779365 100644 --- a/src/board/audio/index.ts +++ b/src/board/audio/index.ts @@ -20,6 +20,7 @@ interface AudioOptions { speechAudioCallback: () => void; speechResampler: SRC; soundExpressionResampler: SRC; + recordingResampler: SRC; } export class BoardAudio { @@ -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; @@ -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, @@ -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); @@ -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; }; @@ -273,7 +274,6 @@ export class BoardAudio { class BufferedAudio { nextStartTime: number = -1; - private sampleRate: number = -1; constructor( private context: AudioContext, @@ -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); diff --git a/src/board/index.ts b/src/board/index.ts index 96d0b7a..e3752c2 100644 --- a/src/board/index.ts +++ b/src/board/index.ts @@ -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 @@ -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 diff --git a/src/demo.html b/src/demo.html index a887cd8..8f3cd3f 100644 --- a/src/demo.html +++ b/src/demo.html @@ -99,6 +99,7 @@