From 9cc4b2bb13cb8f7e5d14538606795f6c55202309 Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Fri, 22 Mar 2024 15:28:28 +0000 Subject: [PATCH] Microphone: get as far as reading samples We'll still need to resample them and write them to the buffer. --- src/board/audio/index.ts | 58 ++++++++++++++++++++++++++++++++++++++++ src/board/microphone.ts | 51 ----------------------------------- src/board/pins.ts | 6 +++++ src/demo.html | 1 + src/examples/record.py | 3 +++ src/jshal.js | 15 +++++------ 6 files changed, 75 insertions(+), 59 deletions(-) create mode 100644 src/examples/record.py diff --git a/src/board/audio/index.ts b/src/board/audio/index.ts index b03c39ec..fbcdb686 100644 --- a/src/board/audio/index.ts +++ b/src/board/audio/index.ts @@ -26,6 +26,7 @@ export class BoardAudio { speech: BufferedAudio | undefined; soundExpression: BufferedAudio | undefined; currentSoundExpressionCallback: undefined | (() => void); + private stopActiveRecording: (() => void) | undefined; constructor() {} @@ -155,7 +156,64 @@ export class BoardAudio { } } + isRecording(): boolean { + return !!this.stopActiveRecording; + } + + stopRecording() { + if (this.stopActiveRecording) { + this.stopActiveRecording(); + } + } + + async startRecording( + onChunk: (chunk: Float32Array, sampleRate: number) => void + ) { + if (!navigator?.mediaDevices?.getUserMedia) { + return; + } + this.stopRecording(); + + this.stopActiveRecording = () => {}; + let micStream: MediaStream | undefined; + try { + micStream = await navigator.mediaDevices.getUserMedia({ + video: false, + audio: true, + }); + } catch (e) { + console.error(e); + this.stopRecording(); + return; + } + + const source = this.context!.createMediaStreamSource(micStream); + // TODO: wire up microphone sensitivity to this gain node + const gain = this.context!.createGain(); + source.connect(gain); + // TODO: consider AudioWorklet - worth it? Browser support? + const recorder = this.context!.createScriptProcessor(2048, 1, 1); + recorder.onaudioprocess = (e) => { + const samples = e.inputBuffer.getChannelData(0); + onChunk(samples, this.context!.sampleRate); + }; + gain.connect(recorder); + recorder.connect(this.context!.destination); + + this.stopActiveRecording = () => { + recorder.disconnect(); + gain.disconnect(); + source.disconnect(); + this.stopActiveRecording = undefined; + }; + + setTimeout(() => { + this.stopRecording(); + }, 5000); + } + boardStopped() { + this.stopRecording(); this.stopOscillator(); this.speech?.dispose(); this.soundExpression?.dispose(); diff --git a/src/board/microphone.ts b/src/board/microphone.ts index b72167ae..435d274e 100644 --- a/src/board/microphone.ts +++ b/src/board/microphone.ts @@ -17,7 +17,6 @@ export class Microphone { 150 ); private soundLevelCallback: SoundLevelCallback | undefined; - private _isRecording: boolean = false; constructor( private element: SVGElement, @@ -67,54 +66,4 @@ export class Microphone { boardStopped() { this.microphoneOff(); } - - isRecording() { - return this._isRecording; - } - - stopRecording() { - // TODO - } - - async startRecording(onChunk: (chunk: ArrayBuffer) => void) { - if (!navigator?.mediaDevices?.getUserMedia) { - return; - } - if (this.isRecording()) { - this.stopRecording(); - // Wait for it if needed - } - this._isRecording = true; - - // This might not be the right recording approach as we want 8 bit PCM for AudioFrame - // and we're getting a fancy codec. - let mediaRecorder: MediaRecorder | undefined; - try { - const stream = await navigator.mediaDevices.getUserMedia({ - video: false, - audio: true, - }); - mediaRecorder = new MediaRecorder(stream); - mediaRecorder.start(); - - setTimeout(() => { - if (mediaRecorder) { - mediaRecorder.stop(); - } - }, 5000); - - mediaRecorder.ondataavailable = async (e: BlobEvent) => { - const buffer = await e.data.arrayBuffer(); - onChunk(buffer); - }; - mediaRecorder.onstop = async () => { - this._isRecording = false; - }; - } catch (error) { - if (mediaRecorder) { - mediaRecorder.stop(); - } - this._isRecording = false; - } - } } diff --git a/src/board/pins.ts b/src/board/pins.ts index aebc324b..2810ed1e 100644 --- a/src/board/pins.ts +++ b/src/board/pins.ts @@ -9,6 +9,8 @@ export interface Pin { isTouched(): boolean; + getAndClearTouches(): number; + boardStopped(): void; setAnalogPeriodUs(period: number): number; @@ -44,6 +46,10 @@ abstract class BasePin implements Pin { return this.analogPeriodUs; } + getAndClearTouches(): number { + return 0; + } + isTouched(): boolean { return false; } diff --git a/src/demo.html b/src/demo.html index 75036482..f293bc29 100644 --- a/src/demo.html +++ b/src/demo.html @@ -95,6 +95,7 @@

MicroPython-micro:bit simulator example embedding

+