Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DO NOT MERGE] Sim changes for forthcoming beta MicroPython release #113

Draft
wants to merge 33 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
699a2d7
Work towards building on the audio-recording branch
microbit-matt-hillsdon Mar 20, 2024
202c7fd
The simulator compiles (with my local mpy change)
microbit-matt-hillsdon Mar 21, 2024
41ff6b0
Add pin touches sample
microbit-grace Mar 21, 2024
292addc
Update pin_touches sample to include logo
microbit-grace Mar 21, 2024
b73637d
WIP record audio
microbit-grace Mar 22, 2024
1b9465a
Initial steps towards microphone HAL
microbit-matt-hillsdon Mar 22, 2024
9cc4b2b
Microphone: get as far as reading samples
microbit-matt-hillsdon Mar 22, 2024
c971228
WIP convertToUnit8Array
microbit-grace Mar 25, 2024
3c4a79c
Update to latest from audio-recording branch
microbit-matt-hillsdon Mar 27, 2024
7aaa800
Remove unused HAL method
microbit-matt-hillsdon Mar 27, 2024
c562955
Reinstate clear on display for non-HAL use (reset)
microbit-matt-hillsdon Mar 27, 2024
88e2baf
Safari 13 compatible speech option
microbit-matt-hillsdon Mar 27, 2024
41e7ce3
Merge branch 'beta-updates' of https://github.com/microbit-foundation…
microbit-grace Mar 27, 2024
2916e18
WIP
microbit-matt-hillsdon Mar 27, 2024
62a338e
WIP playing recorded audio
microbit-matt-hillsdon Mar 27, 2024
8eb6fa7
Update
microbit-matt-hillsdon Apr 2, 2024
26a4ee4
Roughly works
microbit-matt-hillsdon Apr 2, 2024
2cda4d2
Sample program
microbit-matt-hillsdon Apr 2, 2024
d232569
Fix set rate in record.py
microbit-grace Apr 2, 2024
19b095b
Remove debug
microbit-matt-hillsdon Apr 2, 2024
90292b6
Merge branch 'beta-updates' of https://github.com/microbit-foundation…
microbit-grace Apr 3, 2024
3c30c59
Give older Safari a chance
microbit-matt-hillsdon Apr 3, 2024
4f1baa3
Tweak Safari workaround
microbit-matt-hillsdon Apr 3, 2024
ef44717
Fix PR feedback
microbit-matt-hillsdon Apr 3, 2024
5907c05
Tweak sample to allow on-the-fly rate change
microbit-matt-hillsdon Apr 3, 2024
4ed8c93
Remove browser tab mic indicator
microbit-grace Apr 11, 2024
aea7937
Activate sim mic light when recording
microbit-grace Apr 11, 2024
8798367
Update simulator micropython lib
microbit-grace May 3, 2024
4da3f33
Add microphone.set_sensitivity example and js hal
microbit-grace May 3, 2024
6042086
Update to latest
microbit-matt-hillsdon May 23, 2024
06b4549
Tweak AUDIO_OUTPUT_BUFFER_SIZE and document
microbit-matt-hillsdon May 23, 2024
b887f22
Audio fixes
microbit-matt-hillsdon May 24, 2024
34e45b3
Update MicroPython to fix silent frames issue
microbit-matt-hillsdon May 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ Tagged releases with a `v` prefix are deployed to https://python-simulator.userm

1. Update the lib/micropython-microbit-v2 to the relevant hash. Make sure that its lib/micropython submodule is updated (see checkout instructions above).
2. Review the full diff for micropython-microbit-v2. In particular, note changes to:
1. main.c, src/Makefile and mpconfigport.h all which have simulator versions that may need updates
1. main.c, src/Makefile and mpconfigport.h, microbitfs.c, drv_radio.c all which have simulator versions that may need updates
2. the HAL, which may require implementing in the simulator
3. the filesystem, which has a JavaScript implementation.

Expand Down
2 changes: 1 addition & 1 deletion lib/micropython-microbit-v2
Submodule micropython-microbit-v2 updated 52 files
+19 −16 .github/workflows/build.yml
+0 −1 .gitignore
+1 −1 lib/codal
+1 −1 lib/micropython
+6 −6 src/addlayouttable.py
+1 −1 src/codal.patch
+1 −1 src/codal_app/codal.json
+3 −0 src/codal_app/main.cpp
+43 −17 src/codal_app/microbithal.cpp
+10 −7 src/codal_app/microbithal.h
+22 −17 src/codal_app/microbithal_audio.cpp
+90 −0 src/codal_app/microbithal_microphone.cpp
+9 −6 src/codal_port/Makefile
+4 −0 src/codal_port/drv_display.c
+1 −1 src/codal_port/drv_image.c
+2 −0 src/codal_port/drv_radio.c
+2 −0 src/codal_port/drv_softtimer.c
+6 −6 src/codal_port/iters.c
+6 −6 src/codal_port/main.c
+120 −0 src/codal_port/make_microbit_version_hdr.py
+6 −5 src/codal_port/microbit_accelerometer.c
+6 −5 src/codal_port/microbit_button.c
+6 −5 src/codal_port/microbit_compass.c
+7 −7 src/codal_port/microbit_display.c
+7 −6 src/codal_port/microbit_i2c.c
+35 −33 src/codal_port/microbit_image.c
+90 −5 src/codal_port/microbit_microphone.c
+49 −23 src/codal_port/microbit_pin.c
+7 −6 src/codal_port/microbit_sound.c
+9 −8 src/codal_port/microbit_soundeffect.c
+7 −6 src/codal_port/microbit_soundevent.c
+6 −5 src/codal_port/microbit_speaker.c
+7 −6 src/codal_port/microbit_spi.c
+7 −6 src/codal_port/microbit_uart.c
+49 −53 src/codal_port/microbitfs.c
+2 −0 src/codal_port/modantigravity.c
+181 −73 src/codal_port/modaudio.c
+7 −5 src/codal_port/modaudio.h
+2 −0 src/codal_port/modlog.c
+2 −0 src/codal_port/modlove.c
+10 −9 src/codal_port/modmachine.c
+10 −6 src/codal_port/modmicrobit.c
+5 −0 src/codal_port/modmusic.c
+8 −6 src/codal_port/modos.c
+2 −0 src/codal_port/modpower.c
+3 −1 src/codal_port/modradio.c
+17 −1 src/codal_port/modspeech.c
+2 −0 src/codal_port/modthis.c
+0 −46 src/codal_port/modutime.c
+7 −66 src/codal_port/mpconfigport.h
+25 −2 src/codal_port/mphalport.h
+53 −0 src/test_record.py
15 changes: 10 additions & 5 deletions src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ QSTR_DEFS = $(CODAL_PORT)/qstrdefsport.h

# Include py core make definitions.
include $(TOP)/py/py.mk
include $(TOP)/extmod/extmod.mk

# The micropython-lib submodule is not needed by this project, but the MicroPython
# build system requires it if FROZEN_MANIFEST is set (which it is below). To avoid
# needing to check out the micropython-lib submodule, point MPY_LIB_DIR to a dummy
# location that has a README.md file.
MPY_LIB_DIR = $(TOP)

CC = emcc
LD = emcc
Expand Down Expand Up @@ -49,7 +56,7 @@ JSFLAGS += -s ASYNCIFY_STACK_SIZE=262144
JSFLAGS += -s EXIT_RUNTIME
JSFLAGS += -s MODULARIZE=1
JSFLAGS += -s EXPORT_NAME=createModule
JSFLAGS += -s EXPORTED_FUNCTIONS="['_mp_js_main','_microbit_hal_audio_ready_callback','_microbit_hal_audio_speech_ready_callback','_microbit_hal_gesture_callback','_microbit_hal_level_detector_callback','_microbit_radio_rx_buffer','_mp_js_force_stop','_mp_js_request_stop']"
JSFLAGS += -s EXPORTED_FUNCTIONS="['_mp_js_main','_microbit_hal_audio_raw_ready_callback','_microbit_hal_audio_speech_ready_callback','_microbit_hal_gesture_callback','_microbit_hal_level_detector_callback','_microbit_radio_rx_buffer','_mp_js_force_stop','_mp_js_request_stop']"
JSFLAGS += -s EXPORTED_RUNTIME_METHODS="['ccall', 'cwrap']" --js-library jshal.js

ifdef DEBUG
Expand Down Expand Up @@ -101,7 +108,6 @@ SRC_C += $(addprefix $(CODAL_PORT)/, \
modradio.c \
modspeech.c \
modthis.c \
modutime.c \
mphalport.c \
)

Expand All @@ -117,7 +123,7 @@ SRC_C += \
$(abspath $(LOCAL_LIB_DIR)/sam/debug.c) \

SRC_O += \
lib/utils/gchelper_m3.o \
lib/utils/gchelper_thumb2.o \

OBJ = $(PY_O)
OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o))
Expand All @@ -138,8 +144,7 @@ all: $(MBIT_VER_FILE) $(BUILD)/micropython.js
$(MBIT_VER_FILE): FORCE
$(Q)mkdir -p $(HEADER_BUILD)
(cd $(TOP) && $(PYTHON) py/makeversionhdr.py $(abspath $(MP_VER_FILE)))
$(PYTHON) $(TOP)/py/makeversionhdr.py $(MBIT_VER_FILE).pre
$(CAT) $(MBIT_VER_FILE).pre | $(SED) s/MICROPY_/MICROBIT_/ > $(MBIT_VER_FILE)
(cd ../lib/micropython-microbit-v2 && $(PYTHON) src/codal_port/make_microbit_version_hdr.py $(abspath $(MBIT_VER_FILE)))

$(BUILD)/micropython.js: $(OBJ) jshal.js simulator-js
$(ECHO) "LINK $(BUILD)/firmware.js"
Expand Down
80 changes: 79 additions & 1 deletion src/board/audio/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface AudioOptions {
speechAudioCallback: () => void;
}

export class Audio {
export class BoardAudio {
private frequency: number = 440;
// You can mute the sim before it's running so we can't immediately write to the muteNode.
private muted: boolean = false;
Expand All @@ -26,6 +26,7 @@ export class Audio {
speech: BufferedAudio | undefined;
soundExpression: BufferedAudio | undefined;
currentSoundExpressionCallback: undefined | (() => void);
private stopActiveRecording: (() => void) | undefined;

constructor() {}

Expand Down Expand Up @@ -155,7 +156,80 @@ export class Audio {
}
}

isRecording(): boolean {
return !!this.stopActiveRecording;
}

stopRecording() {
if (this.stopActiveRecording) {
this.stopActiveRecording();
}
}

async startRecording(
sampleRate: number,
samplesNeeded: number,
onChunk: (chunk: Float32Array) => void
) {
let samplesSent = 0;
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?
// consider alternative resampling approaches
// what sample rates are actually supported this way?
Comment on lines +211 to +213
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ScriptProcessorNode support vs AudioWorklet support so we'd lose early Safari 14.x and Safari 13 (for 13 I think limited support is OK).

See also the techniques used in this project.

const recorder = this.context!.createScriptProcessor(2048, 1, 1);
recorder.onaudioprocess = (e) => {
const offlineContext = new OfflineAudioContext({
sampleRate,
length: sampleRate * (e.inputBuffer.length / e.inputBuffer.sampleRate),
numberOfChannels: 1,
});
const source = offlineContext.createBufferSource();
source.buffer = e.inputBuffer;
source.connect(offlineContext.destination);
source.start();
offlineContext.addEventListener("complete", (e) => {
onChunk(e.renderedBuffer.getChannelData(0));
samplesSent += e.renderedBuffer.length;
if (samplesSent >= samplesNeeded) {
this.stopRecording();
}
});
offlineContext.startRendering();
};
gain.connect(recorder);
recorder.connect(this.context!.destination);

this.stopActiveRecording = () => {
recorder.disconnect();
gain.disconnect();
source.disconnect();
this.stopActiveRecording = undefined;
};
}

boardStopped() {
this.stopRecording();
this.stopOscillator();
this.speech?.dispose();
this.soundExpression?.dispose();
Expand Down Expand Up @@ -190,6 +264,10 @@ class BufferedAudio {
return this.context.createBuffer(1, length, this.sampleRate);
}

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

writeData(buffer: AudioBuffer) {
// Use createBufferSource instead of new AudioBufferSourceNode to support Safari 14.0.
const source = this.context.createBufferSource();
Expand Down
8 changes: 4 additions & 4 deletions src/board/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import svgText from "../microbit-drawing.svg";
import { Accelerometer } from "./accelerometer";
import { Audio } from "./audio";
import { BoardAudio } from "./audio";
import { Button } from "./buttons";
import { Compass } from "./compass";
import {
Expand Down Expand Up @@ -92,7 +92,7 @@ export class Board {
display: Display;
buttons: Button[];
pins: Pin[];
audio: Audio;
audio: BoardAudio;
temperature: RangeSensor;
microphone: Microphone;
accelerometer: Accelerometer;
Expand Down Expand Up @@ -202,7 +202,7 @@ export class Board {
this.pins[MICROBIT_HAL_PIN_P19] = new StubPin("pin19");
this.pins[MICROBIT_HAL_PIN_P20] = new StubPin("pin20");

this.audio = new Audio();
this.audio = new BoardAudio();
this.temperature = new RangeSensor("temperature", -5, 50, 21, "°C");
this.accelerometer = new Accelerometer(onChange);
this.compass = new Compass();
Expand Down Expand Up @@ -249,7 +249,7 @@ export class Board {
});
const module = new ModuleWrapper(wrapped);
this.audio.initializeCallbacks({
defaultAudioCallback: wrapped._microbit_hal_audio_ready_callback,
defaultAudioCallback: wrapped._microbit_hal_audio_raw_ready_callback,
speechAudioCallback: wrapped._microbit_hal_audio_speech_ready_callback,
});
this.accelerometer.initializeCallbacks(
Expand Down
24 changes: 23 additions & 1 deletion src/board/pins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export interface Pin {

isTouched(): boolean;

getAndClearTouches(): number;

boardStopped(): void;

setAnalogPeriodUs(period: number): number;
Expand Down Expand Up @@ -44,6 +46,10 @@ abstract class BasePin implements Pin {
return this.analogPeriodUs;
}

getAndClearTouches(): number {
return 0;
}

isTouched(): boolean {
return false;
}
Expand All @@ -58,6 +64,8 @@ export class StubPin extends BasePin {}
export class TouchPin extends BasePin {
private _mouseDown: boolean = false;

private _touches: number = 0;

private keyListener: (e: KeyboardEvent) => void;
private mouseDownListener: (e: MouseEvent) => void;
private touchStartListener: (e: TouchEvent) => void;
Expand Down Expand Up @@ -128,8 +136,20 @@ export class TouchPin extends BasePin {
this.setValueInternal(value, false);
}

getAndClearTouches() {
const touches = this._touches;
this._touches = 0;
return touches;
}

private setValueInternal(value: any, internalChange: boolean) {
const previous = this.state.value;
super.setValue(value);
// If this value is transitioning from high to low then count a touch.
// Do it after setValue because the input can be converted from a string.
if (previous === this.state.max && this.state.value === this.state.min) {
this._touches++;
}

if (internalChange) {
this.onChange({
Expand Down Expand Up @@ -179,5 +199,7 @@ export class TouchPin extends BasePin {
}
}

boardStopped() {}
boardStopped() {
this._touches = 0;
}
}
3 changes: 2 additions & 1 deletion src/board/wasm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ export interface EmscriptenModule {
// See EXPORTED_FUNCTIONS in the Makefile.
_mp_js_request_stop(): void;
_mp_js_force_stop(): void;
_microbit_hal_audio_ready_callback(): void;
_microbit_hal_audio_raw_ready_callback(): void;
_microbit_hal_audio_speech_ready_callback(): void;
_microbit_hal_gesture_callback(gesture: number): void;
_microbit_hal_level_detector_callback(level: number): void;
_microbit_radio_rx_buffer(): number;

HEAPU8: Uint8Array;
HEAPU32: Uint32Array;
microbit-matt-hillsdon marked this conversation as resolved.
Show resolved Hide resolved

// Added by us at module creation time for jshal to access.
board: Board;
Expand Down
2 changes: 2 additions & 0 deletions src/demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,10 @@ <h1>MicroPython-micro:bit simulator example embedding</h1>
<option value="microphone">Microphone</option>
<option value="music">Music</option>
<option value="pin_logo">Pin logo</option>
<option value="pin_touches">Pin touches</option>
<option value="radio">Radio</option>
<option value="random">Random</option>
<option value="record">Record</option>
<option value="sensors">Sensors</option>
<option value="sound_effects_builtin">
Sound effects (builtin)
Expand Down
2 changes: 2 additions & 0 deletions src/drv_radio.c
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,5 @@ const uint8_t *microbit_radio_peek(void) {
void microbit_radio_pop(void) {
mp_js_radio_pop();
}

MP_REGISTER_ROOT_POINTER(uint8_t *radio_buf);
13 changes: 13 additions & 0 deletions src/examples/pin_touches.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from microbit import *

def report_pin_touches(pin, pin_type):
if pin.was_touched():
print('pin', pin_type, 'was touched', pin.get_touches(), 'time(s)')

while True:
if button_a.is_pressed():
report_pin_touches(pin0, "0")
report_pin_touches(pin1, "1")
report_pin_touches(pin2, "2")
report_pin_touches(pin_logo, "logo")
break
14 changes: 14 additions & 0 deletions src/examples/record.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from microbit import microphone, audio, button_a, button_b

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

print("Recording...")
frame = microphone.record(3000)
print("Button A to play")
while True:
if button_a.was_pressed():
audio.play(frame)
if button_b.was_pressed():
rate_index = (rate_index + 1) % len(rates);
frame.rate = rates[rate_index]
8 changes: 6 additions & 2 deletions src/jshal.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,20 @@ int mp_js_hal_filesystem_readbyte(int idx, size_t offset);
bool mp_js_hal_filesystem_write(int idx, const char *buf, size_t len);

void mp_js_hal_panic(int code);
void mp_js_hal_reset(void);
__attribute__((noreturn)) void mp_js_hal_reset(void);

int mp_js_hal_temperature(void);

int mp_js_hal_button_get_presses(int button);
bool mp_js_hal_button_is_pressed(int button);

bool mp_js_hal_pin_is_touched(int pin);
int mp_js_hal_pin_get_touches(int pin);
int mp_js_hal_pin_get_analog_period_us(int pin);
int mp_js_hal_pin_set_analog_period_us(int pin, int period);

int mp_js_hal_display_get_pixel(int x, int y);
void mp_js_hal_display_set_pixel(int x, int y, int value);
void mp_js_hal_display_clear(void);
int mp_js_hal_display_read_light_level(void);

int mp_js_hal_accelerometer_get_x(void);
Expand All @@ -72,6 +72,7 @@ int mp_js_hal_compass_get_heading(void);

void mp_js_hal_audio_set_volume(int value);
void mp_js_hal_audio_init(uint32_t sample_rate);
void mp_js_hal_audio_set_rate(const uint32_t sample_rate);
void mp_js_hal_audio_write_data(const uint8_t *buf, size_t num_samples);
void mp_js_hal_audio_speech_init(uint32_t sample_rate);
void mp_js_hal_audio_speech_write_data(const uint8_t *buf, size_t num_samples);
Expand All @@ -84,6 +85,9 @@ bool mp_js_hal_audio_is_expression_active(void);
void mp_js_hal_microphone_init(void);
void mp_js_hal_microphone_set_threshold(int kind, int value);
int mp_js_hal_microphone_get_level(void);
void mp_js_hal_microphone_start_recording(uint8_t *buf, size_t max_len, size_t *cur_len, int rate);
bool mp_js_hal_microphone_is_recording(void);
void mp_js_hal_microphone_stop_recording(void);

void mp_js_radio_enable(uint8_t group, uint8_t max_payload, uint8_t queue);
void mp_js_radio_disable(void);
Expand Down
Loading
Loading