Skip to content

Commit 9cc4b2b

Browse files
Microphone: get as far as reading samples
We'll still need to resample them and write them to the buffer.
1 parent 1b9465a commit 9cc4b2b

File tree

6 files changed

+75
-59
lines changed

6 files changed

+75
-59
lines changed

src/board/audio/index.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export class BoardAudio {
2626
speech: BufferedAudio | undefined;
2727
soundExpression: BufferedAudio | undefined;
2828
currentSoundExpressionCallback: undefined | (() => void);
29+
private stopActiveRecording: (() => void) | undefined;
2930

3031
constructor() {}
3132

@@ -155,7 +156,64 @@ export class BoardAudio {
155156
}
156157
}
157158

159+
isRecording(): boolean {
160+
return !!this.stopActiveRecording;
161+
}
162+
163+
stopRecording() {
164+
if (this.stopActiveRecording) {
165+
this.stopActiveRecording();
166+
}
167+
}
168+
169+
async startRecording(
170+
onChunk: (chunk: Float32Array, sampleRate: number) => void
171+
) {
172+
if (!navigator?.mediaDevices?.getUserMedia) {
173+
return;
174+
}
175+
this.stopRecording();
176+
177+
this.stopActiveRecording = () => {};
178+
let micStream: MediaStream | undefined;
179+
try {
180+
micStream = await navigator.mediaDevices.getUserMedia({
181+
video: false,
182+
audio: true,
183+
});
184+
} catch (e) {
185+
console.error(e);
186+
this.stopRecording();
187+
return;
188+
}
189+
190+
const source = this.context!.createMediaStreamSource(micStream);
191+
// TODO: wire up microphone sensitivity to this gain node
192+
const gain = this.context!.createGain();
193+
source.connect(gain);
194+
// TODO: consider AudioWorklet - worth it? Browser support?
195+
const recorder = this.context!.createScriptProcessor(2048, 1, 1);
196+
recorder.onaudioprocess = (e) => {
197+
const samples = e.inputBuffer.getChannelData(0);
198+
onChunk(samples, this.context!.sampleRate);
199+
};
200+
gain.connect(recorder);
201+
recorder.connect(this.context!.destination);
202+
203+
this.stopActiveRecording = () => {
204+
recorder.disconnect();
205+
gain.disconnect();
206+
source.disconnect();
207+
this.stopActiveRecording = undefined;
208+
};
209+
210+
setTimeout(() => {
211+
this.stopRecording();
212+
}, 5000);
213+
}
214+
158215
boardStopped() {
216+
this.stopRecording();
159217
this.stopOscillator();
160218
this.speech?.dispose();
161219
this.soundExpression?.dispose();

src/board/microphone.ts

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ export class Microphone {
1717
150
1818
);
1919
private soundLevelCallback: SoundLevelCallback | undefined;
20-
private _isRecording: boolean = false;
2120

2221
constructor(
2322
private element: SVGElement,
@@ -67,54 +66,4 @@ export class Microphone {
6766
boardStopped() {
6867
this.microphoneOff();
6968
}
70-
71-
isRecording() {
72-
return this._isRecording;
73-
}
74-
75-
stopRecording() {
76-
// TODO
77-
}
78-
79-
async startRecording(onChunk: (chunk: ArrayBuffer) => void) {
80-
if (!navigator?.mediaDevices?.getUserMedia) {
81-
return;
82-
}
83-
if (this.isRecording()) {
84-
this.stopRecording();
85-
// Wait for it if needed
86-
}
87-
this._isRecording = true;
88-
89-
// This might not be the right recording approach as we want 8 bit PCM for AudioFrame
90-
// and we're getting a fancy codec.
91-
let mediaRecorder: MediaRecorder | undefined;
92-
try {
93-
const stream = await navigator.mediaDevices.getUserMedia({
94-
video: false,
95-
audio: true,
96-
});
97-
mediaRecorder = new MediaRecorder(stream);
98-
mediaRecorder.start();
99-
100-
setTimeout(() => {
101-
if (mediaRecorder) {
102-
mediaRecorder.stop();
103-
}
104-
}, 5000);
105-
106-
mediaRecorder.ondataavailable = async (e: BlobEvent) => {
107-
const buffer = await e.data.arrayBuffer();
108-
onChunk(buffer);
109-
};
110-
mediaRecorder.onstop = async () => {
111-
this._isRecording = false;
112-
};
113-
} catch (error) {
114-
if (mediaRecorder) {
115-
mediaRecorder.stop();
116-
}
117-
this._isRecording = false;
118-
}
119-
}
12069
}

src/board/pins.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ export interface Pin {
99

1010
isTouched(): boolean;
1111

12+
getAndClearTouches(): number;
13+
1214
boardStopped(): void;
1315

1416
setAnalogPeriodUs(period: number): number;
@@ -44,6 +46,10 @@ abstract class BasePin implements Pin {
4446
return this.analogPeriodUs;
4547
}
4648

49+
getAndClearTouches(): number {
50+
return 0;
51+
}
52+
4753
isTouched(): boolean {
4854
return false;
4955
}

src/demo.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ <h1>MicroPython-micro:bit simulator example embedding</h1>
9595
<option value="pin_touches">Pin touches</option>
9696
<option value="radio">Radio</option>
9797
<option value="random">Random</option>
98+
<option value="record">Record</option>
9899
<option value="sensors">Sensors</option>
99100
<option value="sound_effects_builtin">
100101
Sound effects (builtin)

src/examples/record.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from microbit import microphone
2+
3+
microphone.record(5000);

src/jshal.js

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -286,25 +286,24 @@ mergeInto(LibraryManager.library, {
286286
value
287287
);
288288
},
289-
290289
mp_js_hal_microphone_start_recording: function (
291290
/** @type {number} */ buf,
292291
/** @type {number} */ max_len,
293292
/** @type {number} */ cur_len,
294293
/** @type {number} */ rate
295294
) {
296-
Module.board.microphone.startRecording(function (
297-
chunk
298-
) /** @type {ArrayBuffer} */
299-
{
300-
console.log("Chunk", chunk);
295+
Module.board.audio.startRecording(function (
296+
/** @type {Float32Array} */ chunk,
297+
/** @type {number} */ actualSampleRate
298+
) {
299+
// TODO: convert from float to int and resample here
301300
});
302301
},
303302
mp_js_hal_microphone_is_recording: function () {
304-
return Module.board.microphone.isRecording();
303+
return Module.board.audio.isRecording();
305304
},
306305
mp_js_hal_microphone_stop_recording: function () {
307-
Module.board.microphone.stopRecording();
306+
Module.board.audio.stopRecording();
308307
},
309308
mp_js_hal_microphone_get_level: function () {
310309
return Module.board.microphone.soundLevel.value;

0 commit comments

Comments
 (0)