From 699a2d767696ad18c7ac0d31dda7817d9a897506 Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Wed, 20 Mar 2024 16:03:36 +0000 Subject: [PATCH 01/34] Work towards building on the audio-recording branch On the basis that's as close as we can get to the beta for now. Still has HAL issues. One local change to MicroPython was needed to include mphal.h in modspeech - need to review how this works for the non-sim build as it seems like it should be included. --- README.md | 2 +- lib/micropython-microbit-v2 | 2 +- src/Makefile | 13 ++++-- src/drv_radio.c | 2 + src/jshal.h | 2 +- src/main.c | 3 +- src/microbitfs.c | 84 +++++++++++++++++++------------------ src/microbithal_js.c | 2 +- src/mpconfigport.h | 54 +++--------------------- src/mphalport.c | 3 +- 10 files changed, 68 insertions(+), 99 deletions(-) diff --git a/README.md b/README.md index cd861fb1..091bccde 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/lib/micropython-microbit-v2 b/lib/micropython-microbit-v2 index 8aaa36b8..87f726ce 160000 --- a/lib/micropython-microbit-v2 +++ b/lib/micropython-microbit-v2 @@ -1 +1 @@ -Subproject commit 8aaa36b8d50211f5902d50e050632a88358d369f +Subproject commit 87f726cec9feeffcaee6e953d85fea14b28c404f diff --git a/src/Makefile b/src/Makefile index 66dbdcba..fc936a6f 100644 --- a/src/Makefile +++ b/src/Makefile @@ -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 @@ -101,7 +108,6 @@ SRC_C += $(addprefix $(CODAL_PORT)/, \ modradio.c \ modspeech.c \ modthis.c \ - modutime.c \ mphalport.c \ ) @@ -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)) @@ -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" diff --git a/src/drv_radio.c b/src/drv_radio.c index eb6d42ab..43db5dbe 100644 --- a/src/drv_radio.c +++ b/src/drv_radio.c @@ -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); \ No newline at end of file diff --git a/src/jshal.h b/src/jshal.h index 65c81024..e851a46c 100644 --- a/src/jshal.h +++ b/src/jshal.h @@ -42,7 +42,7 @@ 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); diff --git a/src/main.c b/src/main.c index 482e9696..fa190c93 100644 --- a/src/main.c +++ b/src/main.c @@ -40,6 +40,7 @@ #include "drv_display.h" #include "modmicrobit.h" #include "microbithal_js.h" +#include "ports/nrf/modules/os/microbitfs.h" // Set to true if a soft-timer callback can use mp_sched_exception to propagate out an exception. bool microbit_outer_nlr_will_handle_soft_timer_exceptions; @@ -149,7 +150,7 @@ void microbit_pyexec_file(const char *filename) { nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { // Parse and comple the file. - mp_lexer_t *lex = mp_lexer_new_from_file(filename); + mp_lexer_t *lex = mp_lexer_new_from_file(qstr_from_str(filename)); qstr source_name = lex->source_name; mp_parse_tree_t parse_tree = mp_parse(lex, MP_PARSE_FILE_INPUT); mp_obj_t module_fun = mp_compile(&parse_tree, source_name, false); diff --git a/src/microbitfs.c b/src/microbitfs.c index fc685e57..133dd147 100644 --- a/src/microbitfs.c +++ b/src/microbitfs.c @@ -29,20 +29,20 @@ #include "py/stream.h" #include "py/runtime.h" #include "extmod/vfs.h" -#include "ports/nrf/modules/uos/microbitfs.h" +#include "ports/nrf/modules/os/microbitfs.h" #include "jshal.h" #if MICROPY_MBFS // This is a version of microbitfs for the simulator. -// File data is stored externally in JavaScripti and access via mp_js_hal_filesystem_xxx() functions. +// File data is stored externally in JavaScript and access via mp_js_hal_filesystem_xxx() functions. #define MAX_FILENAME_LENGTH (120) /******************************************************************************/ // os-level functions -STATIC mp_obj_t uos_mbfs_listdir(void) { +STATIC mp_obj_t os_mbfs_listdir(void) { mp_obj_t res = mp_obj_new_list(0, NULL); char buf[MAX_FILENAME_LENGTH]; for (size_t i = 0;; ++i) { @@ -57,16 +57,16 @@ STATIC mp_obj_t uos_mbfs_listdir(void) { } return res; } -MP_DEFINE_CONST_FUN_OBJ_0(uos_mbfs_listdir_obj, uos_mbfs_listdir); +MP_DEFINE_CONST_FUN_OBJ_0(os_mbfs_listdir_obj, os_mbfs_listdir); typedef struct { mp_obj_base_t base; mp_fun_1_t iternext; uint8_t idx; -} uos_mbfs_ilistdir_it_t; +} os_mbfs_ilistdir_it_t; -STATIC mp_obj_t uos_mbfs_ilistdir_it_iternext(mp_obj_t self_in) { - uos_mbfs_ilistdir_it_t *self = MP_OBJ_TO_PTR(self_in); +STATIC mp_obj_t os_mbfs_ilistdir_it_iternext(mp_obj_t self_in) { + os_mbfs_ilistdir_it_t *self = MP_OBJ_TO_PTR(self_in); for (;;) { char buf[MAX_FILENAME_LENGTH]; int len = mp_js_hal_filesystem_name(self->idx, buf); @@ -85,16 +85,15 @@ STATIC mp_obj_t uos_mbfs_ilistdir_it_iternext(mp_obj_t self_in) { } } -STATIC mp_obj_t uos_mbfs_ilistdir(void) { - uos_mbfs_ilistdir_it_t *iter = m_new_obj(uos_mbfs_ilistdir_it_t); - iter->base.type = &mp_type_polymorph_iter; - iter->iternext = uos_mbfs_ilistdir_it_iternext; +STATIC mp_obj_t os_mbfs_ilistdir(void) { + os_mbfs_ilistdir_it_t *iter = mp_obj_malloc(os_mbfs_ilistdir_it_t, &mp_type_polymorph_iter); + iter->iternext = os_mbfs_ilistdir_it_iternext; iter->idx = 0; return MP_OBJ_FROM_PTR(iter); } -MP_DEFINE_CONST_FUN_OBJ_0(uos_mbfs_ilistdir_obj, uos_mbfs_ilistdir); +MP_DEFINE_CONST_FUN_OBJ_0(os_mbfs_ilistdir_obj, os_mbfs_ilistdir); -STATIC mp_obj_t uos_mbfs_remove(mp_obj_t filename_in) { +STATIC mp_obj_t os_mbfs_remove(mp_obj_t filename_in) { size_t name_len; const char *name = mp_obj_str_get_data(filename_in, &name_len); int idx = mp_js_hal_filesystem_find(name, name_len); @@ -104,9 +103,9 @@ STATIC mp_obj_t uos_mbfs_remove(mp_obj_t filename_in) { mp_js_hal_filesystem_remove(idx); return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_1(uos_mbfs_remove_obj, uos_mbfs_remove); +MP_DEFINE_CONST_FUN_OBJ_1(os_mbfs_remove_obj, os_mbfs_remove); -STATIC mp_obj_t uos_mbfs_stat(mp_obj_t filename_in) { +STATIC mp_obj_t os_mbfs_stat(mp_obj_t filename_in) { size_t name_len; const char *name = mp_obj_str_get_data(filename_in, &name_len); int idx = mp_js_hal_filesystem_find(name, name_len); @@ -128,7 +127,7 @@ STATIC mp_obj_t uos_mbfs_stat(mp_obj_t filename_in) { t->items[9] = MP_OBJ_NEW_SMALL_INT(0); // st_ctime return MP_OBJ_FROM_PTR(t); } -MP_DEFINE_CONST_FUN_OBJ_1(uos_mbfs_stat_obj, uos_mbfs_stat); +MP_DEFINE_CONST_FUN_OBJ_1(os_mbfs_stat_obj, os_mbfs_stat); /******************************************************************************/ // File object @@ -142,29 +141,29 @@ typedef struct _mbfs_file_obj_t { bool binary; } mbfs_file_obj_t; -STATIC mp_obj_t uos_mbfs_file___exit__(size_t n_args, const mp_obj_t *args) { +STATIC mp_obj_t os_mbfs_file___exit__(size_t n_args, const mp_obj_t *args) { (void)n_args; return mp_stream_close(args[0]); } -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(uos_mbfs_file___exit___obj, 4, 4, uos_mbfs_file___exit__); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(os_mbfs_file___exit___obj, 4, 4, os_mbfs_file___exit__); -STATIC mp_obj_t uos_mbfs_file_name(mp_obj_t self_in) { +STATIC mp_obj_t os_mbfs_file_name(mp_obj_t self_in) { mbfs_file_obj_t *self = MP_OBJ_TO_PTR(self_in); char buf[MAX_FILENAME_LENGTH]; int len = mp_js_hal_filesystem_name(self->idx, buf); return mp_obj_new_str(buf, len); } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(uos_mbfs_file_name_obj, uos_mbfs_file_name); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(os_mbfs_file_name_obj, os_mbfs_file_name); STATIC mp_obj_t microbit_file_writable(mp_obj_t self) { return mp_obj_new_bool(((mbfs_file_obj_t *)MP_OBJ_TO_PTR(self))->writable); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(microbit_file_writable_obj, microbit_file_writable); -STATIC const mp_rom_map_elem_t uos_mbfs_file_locals_dict_table[] = { +STATIC const mp_rom_map_elem_t os_mbfs_file_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&mp_identity_obj) }, - { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&uos_mbfs_file___exit___obj) }, - { MP_ROM_QSTR(MP_QSTR_name), MP_ROM_PTR(&uos_mbfs_file_name_obj) }, + { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&os_mbfs_file___exit___obj) }, + { MP_ROM_QSTR(MP_QSTR_name), MP_ROM_PTR(&os_mbfs_file_name_obj) }, { MP_ROM_QSTR(MP_QSTR_writable), MP_ROM_PTR(µbit_file_writable_obj) }, { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_stream_close_obj) }, @@ -173,7 +172,7 @@ STATIC const mp_rom_map_elem_t uos_mbfs_file_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) }, { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, }; -STATIC MP_DEFINE_CONST_DICT(uos_mbfs_file_locals_dict, uos_mbfs_file_locals_dict_table); +STATIC MP_DEFINE_CONST_DICT(os_mbfs_file_locals_dict, os_mbfs_file_locals_dict_table); STATIC void check_file_open(mbfs_file_obj_t *self) { if (!self->open) { @@ -236,12 +235,13 @@ STATIC const mp_stream_p_t textio_stream_p = { .is_text = true, }; -const mp_obj_type_t uos_mbfs_textio_type = { - { &mp_type_type }, - .name = MP_QSTR_TextIO, - .protocol = &textio_stream_p, - .locals_dict = (mp_obj_dict_t *)&uos_mbfs_file_locals_dict, -}; +MP_DEFINE_CONST_OBJ_TYPE( + os_mbfs_textio_type, + MP_QSTR_TextIO, + MP_TYPE_FLAG_NONE, + protocol, &textio_stream_p, + locals_dict, &os_mbfs_file_locals_dict + ); STATIC const mp_stream_p_t fileio_stream_p = { @@ -250,12 +250,13 @@ STATIC const mp_stream_p_t fileio_stream_p = { .ioctl = microbit_file_ioctl, }; -const mp_obj_type_t uos_mbfs_fileio_type = { - { &mp_type_type }, - .name = MP_QSTR_FileIO, - .protocol = &fileio_stream_p, - .locals_dict = (mp_obj_dict_t *)&uos_mbfs_file_locals_dict, -}; +MP_DEFINE_CONST_OBJ_TYPE( + os_mbfs_fileio_type, + MP_QSTR_FileIO, + MP_TYPE_FLAG_NONE, + protocol, &fileio_stream_p, + locals_dict, &os_mbfs_file_locals_dict + ); STATIC mbfs_file_obj_t *microbit_file_open(const char *name, size_t name_len, bool write, bool binary) { if (name_len > MAX_FILENAME_LENGTH) { @@ -274,9 +275,9 @@ STATIC mbfs_file_obj_t *microbit_file_open(const char *name, size_t name_len, bo mbfs_file_obj_t *res = m_new_obj(mbfs_file_obj_t); if (binary) { - res->base.type = &uos_mbfs_fileio_type; + res->base.type = &os_mbfs_fileio_type; } else { - res->base.type = &uos_mbfs_textio_type; + res->base.type = &os_mbfs_textio_type; } res->idx = idx; res->writable = write; @@ -312,8 +313,9 @@ STATIC void file_close(void *self_in) { self->open = false; } -mp_lexer_t *mp_lexer_new_from_file(const char *filename) { - mbfs_file_obj_t *file = microbit_file_open(filename, strlen(filename), false, false); +mp_lexer_t *mp_lexer_new_from_file(qstr filename) { + const char *filename_str = qstr_str(filename); + mbfs_file_obj_t *file = microbit_file_open(filename_str, strlen(filename_str), false, false); if (file == NULL) { mp_raise_OSError(MP_ENOENT); } @@ -321,7 +323,7 @@ mp_lexer_t *mp_lexer_new_from_file(const char *filename) { reader.data = file; reader.readbyte = file_readbyte; reader.close = file_close; - return mp_lexer_new(qstr_from_str(filename), reader); + return mp_lexer_new(filename, reader); } /******************************************************************************/ diff --git a/src/microbithal_js.c b/src/microbithal_js.c index 4d656341..fcf94e23 100644 --- a/src/microbithal_js.c +++ b/src/microbithal_js.c @@ -149,7 +149,7 @@ void microbit_hal_pin_write_analog_u10(int pin, int value) { } int microbit_hal_pin_is_touched(int pin) { - if (pin == MICROBIT_HAL_PIN_FACE || pin == MICROBIT_HAL_PIN_P0 || pin == MICROBIT_HAL_PIN_P1 || pin == MICROBIT_HAL_PIN_P2) { + if (pin == MICROBIT_HAL_PIN_LOGO || pin == MICROBIT_HAL_PIN_P0 || pin == MICROBIT_HAL_PIN_P1 || pin == MICROBIT_HAL_PIN_P2) { return mp_js_hal_pin_is_touched(pin); } /* diff --git a/src/mpconfigport.h b/src/mpconfigport.h index 96592812..a5064046 100644 --- a/src/mpconfigport.h +++ b/src/mpconfigport.h @@ -59,9 +59,6 @@ #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) #define MICROPY_STREAMS_NON_BLOCK (1) #define MICROPY_MODULE_BUILTIN_INIT (1) -#define MICROPY_MODULE_WEAK_LINKS (1) -#define MICROPY_MODULE_FROZEN_MPY (1) -#define MICROPY_QSTR_EXTRA_POOL mp_qstr_frozen_const_pool #define MICROPY_USE_INTERNAL_ERRNO (1) #define MICROPY_USE_INTERNAL_PRINTF (0) #define MICROPY_ENABLE_PYSTACK (1) @@ -83,12 +80,11 @@ #define MICROPY_PY_SYS_PLATFORM "microbit" // Extended modules -#define MICROPY_PY_UERRNO (1) -#define MICROPY_PY_UTIME_MP_HAL (1) -#define MICROPY_PY_URANDOM (1) -#define MICROPY_PY_URANDOM_SEED_INIT_FUNC (rng_generate_random_word()) -#define MICROPY_PY_URANDOM_EXTRA_FUNCS (1) -#define MICROPY_PY_MACHINE (1) +#define MICROPY_PY_ERRNO (1) +#define MICROPY_PY_RANDOM (1) +#define MICROPY_PY_RANDOM_SEED_INIT_FUNC (rng_generate_random_word()) +#define MICROPY_PY_RANDOM_EXTRA_FUNCS (1) +#define MICROPY_PY_TIME (1) #define MICROPY_PY_MACHINE_PULSE (1) #define MICROPY_HW_ENABLE_RNG (1) @@ -109,44 +105,6 @@ #define MP_STATE_PORT MP_STATE_VM -extern const struct _mp_obj_module_t antigravity_module; -extern const struct _mp_obj_module_t audio_module; -extern const struct _mp_obj_module_t log_module; -extern const struct _mp_obj_module_t love_module; -extern const struct _mp_obj_module_t machine_module; -extern const struct _mp_obj_module_t microbit_module; -extern const struct _mp_obj_module_t music_module; -extern const struct _mp_obj_module_t os_module; -extern const struct _mp_obj_module_t power_module; -extern const struct _mp_obj_module_t radio_module; -extern const struct _mp_obj_module_t speech_module; -extern const struct _mp_obj_module_t this_module; -extern const struct _mp_obj_module_t utime_module; - -#define MICROPY_PORT_BUILTIN_MODULES \ - { MP_ROM_QSTR(MP_QSTR_antigravity), MP_ROM_PTR(&antigravity_module) }, \ - { MP_ROM_QSTR(MP_QSTR_audio), MP_ROM_PTR(&audio_module) }, \ - { MP_ROM_QSTR(MP_QSTR_log), MP_ROM_PTR(&log_module) }, \ - { MP_ROM_QSTR(MP_QSTR_love), MP_ROM_PTR(&love_module) }, \ - { MP_ROM_QSTR(MP_QSTR_machine), MP_ROM_PTR(&machine_module) }, \ - { MP_ROM_QSTR(MP_QSTR_microbit), MP_ROM_PTR(µbit_module) }, \ - { MP_ROM_QSTR(MP_QSTR_music), MP_ROM_PTR(&music_module) }, \ - { MP_ROM_QSTR(MP_QSTR_os), MP_ROM_PTR(&os_module) }, \ - { MP_ROM_QSTR(MP_QSTR_power), MP_ROM_PTR(&power_module) }, \ - { MP_ROM_QSTR(MP_QSTR_radio), MP_ROM_PTR(&radio_module) }, \ - { MP_ROM_QSTR(MP_QSTR_speech), MP_ROM_PTR(&speech_module) }, \ - { MP_ROM_QSTR(MP_QSTR_this), MP_ROM_PTR(&this_module) }, \ - { MP_ROM_QSTR(MP_QSTR_utime), MP_ROM_PTR(&utime_module) }, \ - -#define MICROPY_PORT_ROOT_POINTERS \ - const char *readline_hist[8]; \ - void *display_data; \ - uint8_t *radio_buf; \ - void *audio_source; \ - void *speech_data; \ - struct _music_data_t *music_data; \ - struct _microbit_soft_timer_entry_t *soft_timer_heap; \ - #define MP_SSIZE_MAX (0x7fffffff) // Type definitions for the specific machine @@ -157,7 +115,7 @@ typedef long mp_off_t; // We need to provide a declaration/definition of alloca() #include -// Needed for MICROPY_PY_URANDOM_SEED_INIT_FUNC. +// Needed for MICROPY_PY_RANDOM_SEED_INIT_FUNC. extern uint32_t rng_generate_random_word(void); // Intercept modmachine memory access. diff --git a/src/mphalport.c b/src/mphalport.c index 1d2771f2..d6b03a4e 100644 --- a/src/mphalport.c +++ b/src/mphalport.c @@ -15,8 +15,9 @@ uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) { return ret; } -void mp_hal_stdout_tx_strn(const char *str, size_t len) { +mp_uint_t mp_hal_stdout_tx_strn(const char *str, size_t len) { mp_js_hal_stdout_tx_strn(str, len); + return len; } int mp_hal_stdin_rx_chr(void) { From 202c7fdfa40b18fe2826e9eb7b1e47361e6ae657 Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Thu, 21 Mar 2024 13:20:25 +0000 Subject: [PATCH 02/34] The simulator compiles (with my local mpy change) --- src/Makefile | 2 +- src/board/index.ts | 2 +- src/board/pins.ts | 19 ++++++++++++++- src/board/wasm.ts | 2 +- src/jshal.h | 1 + src/jshal.js | 4 ++++ src/microbithal_js.c | 57 ++++++++++++++++++++++++++++++++++---------- src/mphalport.h | 2 ++ 8 files changed, 72 insertions(+), 17 deletions(-) diff --git a/src/Makefile b/src/Makefile index fc936a6f..a40bee29 100644 --- a/src/Makefile +++ b/src/Makefile @@ -56,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 diff --git a/src/board/index.ts b/src/board/index.ts index deff7fa7..8a736413 100644 --- a/src/board/index.ts +++ b/src/board/index.ts @@ -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( diff --git a/src/board/pins.ts b/src/board/pins.ts index da5d4435..aebc324b 100644 --- a/src/board/pins.ts +++ b/src/board/pins.ts @@ -58,6 +58,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; @@ -128,8 +130,21 @@ export class TouchPin extends BasePin { this.setValueInternal(value, false); } + getAndClearTouches() { + const touches = this._touches; + this._touches = 0; + console.log("got touch ", touches); + 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({ @@ -179,5 +194,7 @@ export class TouchPin extends BasePin { } } - boardStopped() {} + boardStopped() { + this._touches = 0; + } } diff --git a/src/board/wasm.ts b/src/board/wasm.ts index 6d5bbfbf..dba2d55f 100644 --- a/src/board/wasm.ts +++ b/src/board/wasm.ts @@ -9,7 +9,7 @@ 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; diff --git a/src/jshal.h b/src/jshal.h index e851a46c..9e8eff46 100644 --- a/src/jshal.h +++ b/src/jshal.h @@ -50,6 +50,7 @@ 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); diff --git a/src/jshal.js b/src/jshal.js index 3cc18cd1..203f1ca8 100644 --- a/src/jshal.js +++ b/src/jshal.js @@ -131,6 +131,10 @@ mergeInto(LibraryManager.library, { return Module.board.pins[pin].isTouched(); }, + mp_js_hal_pin_get_touches: function (/** @type {number} */ pin) { + return Module.board.pins[pin].getAndClearTouches(); + }, + mp_js_hal_pin_get_analog_period_us: function (/** @type {number} */ pin) { return Module.board.pins[pin].getAnalogPeriodUs(); }, diff --git a/src/microbithal_js.c b/src/microbithal_js.c index fcf94e23..aac0cf07 100644 --- a/src/microbithal_js.c +++ b/src/microbithal_js.c @@ -18,6 +18,8 @@ const unsigned char pendolino3[475] = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x8, 0x8, 0x0, 0x8, 0xa, 0x4a, 0x40, 0x0, 0x0, 0xa, 0x5f, 0xea, 0x5f, 0xea, 0xe, 0xd9, 0x2e, 0xd3, 0x6e, 0x19, 0x32, 0x44, 0x89, 0x33, 0xc, 0x92, 0x4c, 0x92, 0x4d, 0x8, 0x8, 0x0, 0x0, 0x0, 0x4, 0x88, 0x8, 0x8, 0x4, 0x8, 0x4, 0x84, 0x84, 0x88, 0x0, 0xa, 0x44, 0x8a, 0x40, 0x0, 0x4, 0x8e, 0xc4, 0x80, 0x0, 0x0, 0x0, 0x4, 0x88, 0x0, 0x0, 0xe, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x1, 0x22, 0x44, 0x88, 0x10, 0xc, 0x92, 0x52, 0x52, 0x4c, 0x4, 0x8c, 0x84, 0x84, 0x8e, 0x1c, 0x82, 0x4c, 0x90, 0x1e, 0x1e, 0xc2, 0x44, 0x92, 0x4c, 0x6, 0xca, 0x52, 0x5f, 0xe2, 0x1f, 0xf0, 0x1e, 0xc1, 0x3e, 0x2, 0x44, 0x8e, 0xd1, 0x2e, 0x1f, 0xe2, 0x44, 0x88, 0x10, 0xe, 0xd1, 0x2e, 0xd1, 0x2e, 0xe, 0xd1, 0x2e, 0xc4, 0x88, 0x0, 0x8, 0x0, 0x8, 0x0, 0x0, 0x4, 0x80, 0x4, 0x88, 0x2, 0x44, 0x88, 0x4, 0x82, 0x0, 0xe, 0xc0, 0xe, 0xc0, 0x8, 0x4, 0x82, 0x44, 0x88, 0xe, 0xd1, 0x26, 0xc0, 0x4, 0xe, 0xd1, 0x35, 0xb3, 0x6c, 0xc, 0x92, 0x5e, 0xd2, 0x52, 0x1c, 0x92, 0x5c, 0x92, 0x5c, 0xe, 0xd0, 0x10, 0x10, 0xe, 0x1c, 0x92, 0x52, 0x52, 0x5c, 0x1e, 0xd0, 0x1c, 0x90, 0x1e, 0x1e, 0xd0, 0x1c, 0x90, 0x10, 0xe, 0xd0, 0x13, 0x71, 0x2e, 0x12, 0x52, 0x5e, 0xd2, 0x52, 0x1c, 0x88, 0x8, 0x8, 0x1c, 0x1f, 0xe2, 0x42, 0x52, 0x4c, 0x12, 0x54, 0x98, 0x14, 0x92, 0x10, 0x10, 0x10, 0x10, 0x1e, 0x11, 0x3b, 0x75, 0xb1, 0x31, 0x11, 0x39, 0x35, 0xb3, 0x71, 0xc, 0x92, 0x52, 0x52, 0x4c, 0x1c, 0x92, 0x5c, 0x90, 0x10, 0xc, 0x92, 0x52, 0x4c, 0x86, 0x1c, 0x92, 0x5c, 0x92, 0x51, 0xe, 0xd0, 0xc, 0x82, 0x5c, 0x1f, 0xe4, 0x84, 0x84, 0x84, 0x12, 0x52, 0x52, 0x52, 0x4c, 0x11, 0x31, 0x31, 0x2a, 0x44, 0x11, 0x31, 0x35, 0xbb, 0x71, 0x12, 0x52, 0x4c, 0x92, 0x52, 0x11, 0x2a, 0x44, 0x84, 0x84, 0x1e, 0xc4, 0x88, 0x10, 0x1e, 0xe, 0xc8, 0x8, 0x8, 0xe, 0x10, 0x8, 0x4, 0x82, 0x41, 0xe, 0xc2, 0x42, 0x42, 0x4e, 0x4, 0x8a, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0x8, 0x4, 0x80, 0x0, 0x0, 0x0, 0xe, 0xd2, 0x52, 0x4f, 0x10, 0x10, 0x1c, 0x92, 0x5c, 0x0, 0xe, 0xd0, 0x10, 0xe, 0x2, 0x42, 0x4e, 0xd2, 0x4e, 0xc, 0x92, 0x5c, 0x90, 0xe, 0x6, 0xc8, 0x1c, 0x88, 0x8, 0xe, 0xd2, 0x4e, 0xc2, 0x4c, 0x10, 0x10, 0x1c, 0x92, 0x52, 0x8, 0x0, 0x8, 0x8, 0x8, 0x2, 0x40, 0x2, 0x42, 0x4c, 0x10, 0x14, 0x98, 0x14, 0x92, 0x8, 0x8, 0x8, 0x8, 0x6, 0x0, 0x1b, 0x75, 0xb1, 0x31, 0x0, 0x1c, 0x92, 0x52, 0x52, 0x0, 0xc, 0x92, 0x52, 0x4c, 0x0, 0x1c, 0x92, 0x5c, 0x90, 0x0, 0xe, 0xd2, 0x4e, 0xc2, 0x0, 0xe, 0xd0, 0x10, 0x10, 0x0, 0x6, 0xc8, 0x4, 0x98, 0x8, 0x8, 0xe, 0xc8, 0x7, 0x0, 0x12, 0x52, 0x52, 0x4f, 0x0, 0x11, 0x31, 0x2a, 0x44, 0x0, 0x11, 0x31, 0x35, 0xbb, 0x0, 0x12, 0x4c, 0x8c, 0x92, 0x0, 0x11, 0x2a, 0x44, 0x98, 0x0, 0x1e, 0xc4, 0x88, 0x1e, 0x6, 0xc4, 0x8c, 0x84, 0x86, 0x8, 0x8, 0x8, 0x8, 0x8, 0x18, 0x8, 0xc, 0x88, 0x18, 0x0, 0x0, 0xc, 0x83, 0x60}; static uint16_t button_state[2]; +static uint16_t touch_state[4]; + void microbit_hal_init(void) { mp_js_hal_init(); @@ -148,18 +150,33 @@ void microbit_hal_pin_write_analog_u10(int pin, int value) { */ } -int microbit_hal_pin_is_touched(int pin) { - if (pin == MICROBIT_HAL_PIN_LOGO || pin == MICROBIT_HAL_PIN_P0 || pin == MICROBIT_HAL_PIN_P1 || pin == MICROBIT_HAL_PIN_P2) { - return mp_js_hal_pin_is_touched(pin); - } - /* - if (pin == MICROBIT_HAL_PIN_FACE) { - // For touch on the face/logo, delegate to the TouchButton instance. - return uBit.logo.buttonActive(); +int microbit_hal_pin_touch_state(int pin, int *was_touched, int *num_touches) { + if (was_touched != NULL || num_touches != NULL) { + int pin_state_index; + if (pin == MICROBIT_HAL_PIN_LOGO) { + pin_state_index = 3; + } else { + pin_state_index = pin; // pin0/1/2 + } + int t = mp_js_hal_pin_get_touches(pin); + uint16_t state = touch_state[pin_state_index]; + if (t) { + // Update state based on number of touches since last call. + // Low bit is "was touched at least once", upper bits are "number of touches". + state = (state + (t << 1)) | 1; + } + if (was_touched != NULL) { + *was_touched = state & 1; + state &= ~1; + } + if (num_touches != NULL) { + *num_touches = state >> 1; + state &= 1; + } + touch_state[pin_state_index] = state; } - return pin_obj[pin]->isTouched(); - */ - return 0; + + return mp_js_hal_pin_is_touched(pin); } void microbit_hal_pin_write_ws2812(int pin, const uint8_t *buf, size_t len) { @@ -433,11 +450,15 @@ void microbit_hal_audio_stop_expression(void) { mp_js_hal_audio_stop_expression(); } -void microbit_hal_audio_init(uint32_t sample_rate) { +void microbit_hal_audio_raw_init(uint32_t sample_rate) { mp_js_hal_audio_init(sample_rate); } -void microbit_hal_audio_write_data(const uint8_t *buf, size_t num_samples) { +void microbit_hal_audio_raw_set_rate(uint32_t sample_rate) { + // TODO +} + +void microbit_hal_audio_raw_write_data(const uint8_t *buf, size_t num_samples) { mp_js_hal_audio_write_data(buf, num_samples); } @@ -491,3 +512,13 @@ int microbit_hal_microphone_get_level(void) { } */ } + +void microbit_hal_microphone_start_recording(uint8_t *buf, size_t max_len, size_t *cur_len, int rate) { +} + +bool microbit_hal_microphone_is_recording(void) { + return false; +} + +void microbit_hal_microphone_stop_recording(void) { +} diff --git a/src/mphalport.h b/src/mphalport.h index 6dd47a27..4b154dd0 100644 --- a/src/mphalport.h +++ b/src/mphalport.h @@ -9,6 +9,8 @@ extern ringbuf_t stdin_ringbuf; +#define mp_hal_ticks_cpu() (0) + void mp_hal_set_interrupt_char(int c); static inline uint32_t mp_hal_disable_irq(void) { From 41ff6b0ac96bd51aea744e12e2f5c84e98b2ddc4 Mon Sep 17 00:00:00 2001 From: Grace Date: Thu, 21 Mar 2024 14:38:41 +0000 Subject: [PATCH 03/34] Add pin touches sample --- src/demo.html | 1 + src/examples/pin_touches.py | 12 ++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 src/examples/pin_touches.py diff --git a/src/demo.html b/src/demo.html index 83dabe83..75036482 100644 --- a/src/demo.html +++ b/src/demo.html @@ -92,6 +92,7 @@

MicroPython-micro:bit simulator example embedding

+ diff --git a/src/examples/pin_touches.py b/src/examples/pin_touches.py new file mode 100644 index 00000000..a9c33cc2 --- /dev/null +++ b/src/examples/pin_touches.py @@ -0,0 +1,12 @@ +from microbit import * + +def report_pin_touches(pin, p_num): + if pin.was_touched(): + print('pin', p_num, 'was touched', pin.get_touches(), 'time(s)') + +while True: + if pin_logo.is_touched(): + report_pin_touches(pin0, "0") + report_pin_touches(pin1, "1") + report_pin_touches(pin2, "2") + break From 292addc6948637b3bfae42767ec78b083f28bb48 Mon Sep 17 00:00:00 2001 From: Grace Date: Thu, 21 Mar 2024 14:47:23 +0000 Subject: [PATCH 04/34] Update pin_touches sample to include logo --- src/examples/pin_touches.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/examples/pin_touches.py b/src/examples/pin_touches.py index a9c33cc2..acf458ac 100644 --- a/src/examples/pin_touches.py +++ b/src/examples/pin_touches.py @@ -1,12 +1,13 @@ from microbit import * -def report_pin_touches(pin, p_num): +def report_pin_touches(pin, pin_type): if pin.was_touched(): - print('pin', p_num, 'was touched', pin.get_touches(), 'time(s)') + print('pin', pin_type, 'was touched', pin.get_touches(), 'time(s)') while True: - if pin_logo.is_touched(): + 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 From b73637da333078aed67687451077b1ec944d969c Mon Sep 17 00:00:00 2001 From: Grace Date: Fri, 22 Mar 2024 10:45:09 +0000 Subject: [PATCH 05/34] WIP record audio --- src/board/audio/index.ts | 42 +++++++++++++++++++++++++++++++++++++++- src/board/index.ts | 14 +++++++++++--- src/demo.html | 10 ++++++++++ 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/src/board/audio/index.ts b/src/board/audio/index.ts index 9bb7f5f9..9aae3089 100644 --- a/src/board/audio/index.ts +++ b/src/board/audio/index.ts @@ -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; @@ -66,6 +66,46 @@ export class Audio { ); } + async record() { + let mediaRecorder: MediaRecorder | undefined; + if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { + try { + const stream = await navigator.mediaDevices.getUserMedia({ video: false, audio: true }); + mediaRecorder = new MediaRecorder(stream); + mediaRecorder.start(); + + setTimeout(() => { + if (mediaRecorder) { + mediaRecorder.stop(); + } + }, 5000) + + const chunks:Blob[] = [] + mediaRecorder.ondataavailable = (e: BlobEvent) => { + chunks.push(e.data); + } + + mediaRecorder.onstop = async () => { + if (chunks.length > 0) { + const recordingType = 'audio/wav; codecs=MS_PCM' + const blob = new Blob(chunks, { type: recordingType }); + const audioURL = window.URL.createObjectURL(blob); + const recording = new Audio(audioURL); + await recording.play() + } + } + + } catch (error) { + console.log("An error occurred, could not get microphone access"); + if (mediaRecorder) { + mediaRecorder.stop(); + } + } + } else { + console.log("getUserMedia not supported on your browser!"); + } + } + async createAudioContextFromUserInteraction(): Promise { this.context = this.context ?? diff --git a/src/board/index.ts b/src/board/index.ts index 8a736413..b416279b 100644 --- a/src/board/index.ts +++ b/src/board/index.ts @@ -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 { @@ -92,7 +92,7 @@ export class Board { display: Display; buttons: Button[]; pins: Pin[]; - audio: Audio; + audio: BoardAudio; temperature: RangeSensor; microphone: Microphone; accelerometer: Accelerometer; @@ -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(); @@ -513,6 +513,10 @@ export class Board { return this.runningPromise; } + async record(): Promise { + await this.audio.record() + } + /** * An external reset. */ @@ -782,6 +786,10 @@ export const createMessageListener = (board: Board) => (e: MessageEvent) => { board.stop(); break; } + case "record": { + board.record(); + break; + } case "reset": { board.reset(); break; diff --git a/src/demo.html b/src/demo.html index 75036482..4901e1d9 100644 --- a/src/demo.html +++ b/src/demo.html @@ -115,6 +115,7 @@

MicroPython-micro:bit simulator example embedding

+
@@ -241,6 +242,15 @@

MicroPython-micro:bit simulator example embedding

); }); + document.querySelector("#record").addEventListener("click", async () => { + simulator.postMessage( + { + kind: "record", + }, + "*" + ); + }); + document.querySelector("#reset").addEventListener("click", async () => { simulator.postMessage( { From 1b9465a2d61cde5dda8a1b13204fa2ad94807f9e Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Fri, 22 Mar 2024 12:27:19 +0000 Subject: [PATCH 06/34] Initial steps towards microphone HAL Audio format needs some investigation - MediaRecorder might not be the right approach. --- src/board/audio/index.ts | 40 ------------------------------- src/board/index.ts | 8 ------- src/board/microphone.ts | 51 ++++++++++++++++++++++++++++++++++++++++ src/demo.html | 10 -------- src/jshal.h | 3 +++ src/jshal.js | 19 +++++++++++++++ src/microbithal_js.c | 4 +++- 7 files changed, 76 insertions(+), 59 deletions(-) diff --git a/src/board/audio/index.ts b/src/board/audio/index.ts index 9aae3089..b03c39ec 100644 --- a/src/board/audio/index.ts +++ b/src/board/audio/index.ts @@ -66,46 +66,6 @@ export class BoardAudio { ); } - async record() { - let mediaRecorder: MediaRecorder | undefined; - if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { - try { - const stream = await navigator.mediaDevices.getUserMedia({ video: false, audio: true }); - mediaRecorder = new MediaRecorder(stream); - mediaRecorder.start(); - - setTimeout(() => { - if (mediaRecorder) { - mediaRecorder.stop(); - } - }, 5000) - - const chunks:Blob[] = [] - mediaRecorder.ondataavailable = (e: BlobEvent) => { - chunks.push(e.data); - } - - mediaRecorder.onstop = async () => { - if (chunks.length > 0) { - const recordingType = 'audio/wav; codecs=MS_PCM' - const blob = new Blob(chunks, { type: recordingType }); - const audioURL = window.URL.createObjectURL(blob); - const recording = new Audio(audioURL); - await recording.play() - } - } - - } catch (error) { - console.log("An error occurred, could not get microphone access"); - if (mediaRecorder) { - mediaRecorder.stop(); - } - } - } else { - console.log("getUserMedia not supported on your browser!"); - } - } - async createAudioContextFromUserInteraction(): Promise { this.context = this.context ?? diff --git a/src/board/index.ts b/src/board/index.ts index b416279b..6bd607c3 100644 --- a/src/board/index.ts +++ b/src/board/index.ts @@ -513,10 +513,6 @@ export class Board { return this.runningPromise; } - async record(): Promise { - await this.audio.record() - } - /** * An external reset. */ @@ -786,10 +782,6 @@ export const createMessageListener = (board: Board) => (e: MessageEvent) => { board.stop(); break; } - case "record": { - board.record(); - break; - } case "reset": { board.reset(); break; diff --git a/src/board/microphone.ts b/src/board/microphone.ts index 435d274e..b72167ae 100644 --- a/src/board/microphone.ts +++ b/src/board/microphone.ts @@ -17,6 +17,7 @@ export class Microphone { 150 ); private soundLevelCallback: SoundLevelCallback | undefined; + private _isRecording: boolean = false; constructor( private element: SVGElement, @@ -66,4 +67,54 @@ 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/demo.html b/src/demo.html index 4901e1d9..75036482 100644 --- a/src/demo.html +++ b/src/demo.html @@ -115,7 +115,6 @@

MicroPython-micro:bit simulator example embedding

-
@@ -242,15 +241,6 @@

MicroPython-micro:bit simulator example embedding

); }); - document.querySelector("#record").addEventListener("click", async () => { - simulator.postMessage( - { - kind: "record", - }, - "*" - ); - }); - document.querySelector("#reset").addEventListener("click", async () => { simulator.postMessage( { diff --git a/src/jshal.h b/src/jshal.h index 9e8eff46..320af1a5 100644 --- a/src/jshal.h +++ b/src/jshal.h @@ -85,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); diff --git a/src/jshal.js b/src/jshal.js index 203f1ca8..67dd602d 100644 --- a/src/jshal.js +++ b/src/jshal.js @@ -287,6 +287,25 @@ mergeInto(LibraryManager.library, { ); }, + mp_js_hal_microphone_start_recording: function ( + /** @type {number} */ buf, + /** @type {number} */ max_len, + /** @type {number} */ cur_len, + /** @type {number} */ rate + ) { + Module.board.microphone.startRecording(function ( + chunk + ) /** @type {ArrayBuffer} */ + { + console.log("Chunk", chunk); + }); + }, + mp_js_hal_microphone_is_recording: function () { + return Module.board.microphone.isRecording(); + }, + mp_js_hal_microphone_stop_recording: function () { + Module.board.microphone.stopRecording(); + }, mp_js_hal_microphone_get_level: function () { return Module.board.microphone.soundLevel.value; }, diff --git a/src/microbithal_js.c b/src/microbithal_js.c index aac0cf07..a938352f 100644 --- a/src/microbithal_js.c +++ b/src/microbithal_js.c @@ -514,11 +514,13 @@ int microbit_hal_microphone_get_level(void) { } void microbit_hal_microphone_start_recording(uint8_t *buf, size_t max_len, size_t *cur_len, int rate) { + mp_js_hal_microphone_start_recording(buf, max_len, cur_len, rate); } bool microbit_hal_microphone_is_recording(void) { - return false; + return mp_js_hal_microphone_is_recording(); } void microbit_hal_microphone_stop_recording(void) { + mp_js_hal_microphone_stop_recording(); } From 9cc4b2bb13cb8f7e5d14538606795f6c55202309 Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Fri, 22 Mar 2024 15:28:28 +0000 Subject: [PATCH 07/34] 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

+