diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 77d5aab1d3..f87fc02975 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -193,6 +193,10 @@ msgstr "" msgid "%q must be array of type 'H'" msgstr "" +#: shared-bindings/synthio/MidiTrack.c shared-bindings/synthio/__init__.c +msgid "%q must be array of type 'h'" +msgstr "" + #: ports/raspberrypi/bindings/cyw43/__init__.c py/argcheck.c py/objexcept.c #: shared-bindings/canio/CAN.c shared-bindings/digitalio/Pull.c msgid "%q must be of type %q or %q, not %q" diff --git a/ports/atmel-samd/boards/pewpew_m4/mpconfigboard.mk b/ports/atmel-samd/boards/pewpew_m4/mpconfigboard.mk index f7e7be88d5..49be084ddf 100644 --- a/ports/atmel-samd/boards/pewpew_m4/mpconfigboard.mk +++ b/ports/atmel-samd/boards/pewpew_m4/mpconfigboard.mk @@ -44,6 +44,8 @@ CIRCUITPY_AUDIOIO = 1 CIRCUITPY_AUDIOMIXER = 1 CIRCUITPY_DISPLAYIO = 1 CIRCUITPY_KEYPAD = 1 +CIRCUITPY_KEYPAD_SHIFTREGISTERKEYS = 0 +CIRCUITPY_KEYPAD_KEYMATRIX = 0 CIRCUITPY_MATH = 1 CIRCUITPY_STAGE = 1 CIRCUITPY_SYNTHIO = 1 diff --git a/ports/mimxrt10xx/Makefile b/ports/mimxrt10xx/Makefile index ff2b6c35c1..72f4d191ef 100644 --- a/ports/mimxrt10xx/Makefile +++ b/ports/mimxrt10xx/Makefile @@ -38,7 +38,6 @@ INC += \ -Iboards/$(BOARD) \ -Iperipherals/ \ -Iperipherals/mimxrt10xx/ \ - -Isdk/CMSIS/Include \ -Isdk/devices/$(CHIP_FAMILY) \ -Isdk/devices/$(CHIP_FAMILY)/drivers \ -Isdk/drivers/common diff --git a/ports/mimxrt10xx/mpconfigport.mk b/ports/mimxrt10xx/mpconfigport.mk index b03535b6b2..ae22cb69f4 100644 --- a/ports/mimxrt10xx/mpconfigport.mk +++ b/ports/mimxrt10xx/mpconfigport.mk @@ -18,6 +18,7 @@ CIRCUITPY_AUDIOIO = 0 CIRCUITPY_AUDIOMIXER = 1 CIRCUITPY_AUDIOMP3 = 1 CIRCUITPY_AUDIOPWMIO = 1 +CIRCUITPY_SYNTHIO_MAX_CHANNELS = 12 CIRCUITPY_BUSDEVICE = 1 CIRCUITPY_COUNTIO = 0 CIRCUITPY_FREQUENCYIO = 0 diff --git a/ports/raspberrypi/mpconfigport.mk b/ports/raspberrypi/mpconfigport.mk index 21dde6d239..671d4669bf 100644 --- a/ports/raspberrypi/mpconfigport.mk +++ b/ports/raspberrypi/mpconfigport.mk @@ -19,6 +19,7 @@ CIRCUITPY_PWMIO ?= 1 CIRCUITPY_RGBMATRIX ?= $(CIRCUITPY_DISPLAYIO) CIRCUITPY_ROTARYIO ?= 1 CIRCUITPY_ROTARYIO_SOFTENCODER = 1 +CIRCUITPY_SYNTHIO_MAX_CHANNELS = 12 # Things that need to be implemented. # Use PWM internally diff --git a/ports/unix/variants/coverage/mpconfigvariant.mk b/ports/unix/variants/coverage/mpconfigvariant.mk index 20320916bc..854d052c6c 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.mk +++ b/ports/unix/variants/coverage/mpconfigvariant.mk @@ -30,15 +30,30 @@ SRC_BITMAP := \ displayio_min.c \ shared-bindings/aesio/aes.c \ shared-bindings/aesio/__init__.c \ + shared-bindings/audiocore/__init__.c \ + shared-bindings/audiocore/RawSample.c \ + shared-bindings/audiocore/WaveFile.c \ + shared-bindings/audiomixer/__init__.c \ + shared-bindings/audiomixer/Mixer.c \ + shared-bindings/audiomixer/MixerVoice.c \ shared-bindings/bitmaptools/__init__.c \ shared-bindings/displayio/Bitmap.c \ shared-bindings/rainbowio/__init__.c \ shared-bindings/struct/__init__.c \ + shared-bindings/synthio/__init__.c \ + shared-bindings/synthio/MidiTrack.c \ + shared-bindings/synthio/Synthesizer.c \ shared-bindings/traceback/__init__.c \ shared-bindings/util.c \ shared-bindings/zlib/__init__.c \ shared-module/aesio/aes.c \ shared-module/aesio/__init__.c \ + shared-module/audiocore/__init__.c \ + shared-module/audiocore/RawSample.c \ + shared-module/audiocore/WaveFile.c \ + shared-module/audiomixer/__init__.c \ + shared-module/audiomixer/Mixer.c \ + shared-module/audiomixer/MixerVoice.c \ shared-module/bitmaptools/__init__.c \ shared-module/displayio/area.c \ shared-module/displayio/Bitmap.c \ @@ -47,6 +62,9 @@ SRC_BITMAP := \ shared-module/os/getenv.c \ shared-module/rainbowio/__init__.c \ shared-module/struct/__init__.c \ + shared-module/synthio/__init__.c \ + shared-module/synthio/MidiTrack.c \ + shared-module/synthio/Synthesizer.c \ shared-module/traceback/__init__.c \ shared-module/zlib/__init__.c \ @@ -54,12 +72,17 @@ SRC_C += $(SRC_BITMAP) CFLAGS += \ -DCIRCUITPY_AESIO=1 \ + -DCIRCUITPY_AUDIOCORE=1 \ + -DCIRCUITPY_AUDIOMIXER=1 \ + -DCIRCUITPY_AUDIOCORE_DEBUG=1 \ -DCIRCUITPY_BITMAPTOOLS=1 \ -DCIRCUITPY_DISPLAYIO_UNIX=1 \ - -DCIRCUITPY_OS_GETENV=1 \ -DCIRCUITPY_GIFIO=1 \ + -DCIRCUITPY_OS_GETENV=1 \ -DCIRCUITPY_RAINBOWIO=1 \ -DCIRCUITPY_STRUCT=1 \ + -DCIRCUITPY_SYNTHIO=1 \ + -DCIRCUITPY_SYNTHIO_MAX_CHANNELS=14 \ -DCIRCUITPY_TRACEBACK=1 \ -DCIRCUITPY_ZLIB=1 diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index 94377238a0..fb5d745ebb 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -648,6 +648,7 @@ SRC_SHARED_MODULE_ALL = \ supervisor/__init__.c \ supervisor/StatusBar.c \ synthio/MidiTrack.c \ + synthio/Synthesizer.c \ synthio/__init__.c \ terminalio/Terminal.c \ terminalio/__init__.c \ diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index b8e2178870..0aec2ebe74 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -107,6 +107,11 @@ CFLAGS += -DCIRCUITPY_AUDIOCORE=$(CIRCUITPY_AUDIOCORE) CIRCUITPY_AUDIOMIXER ?= $(CIRCUITPY_AUDIOIO) CFLAGS += -DCIRCUITPY_AUDIOMIXER=$(CIRCUITPY_AUDIOMIXER) +ifndef CIRCUITPY_AUDIOCORE_DEBUG +CIRCUITPY_AUDIOCORE_DEBUG ?= 0 +endif +CFLAGS += -DCIRCUITPY_AUDIOCORE_DEBUG=$(CIRCUITPY_AUDIOCORE_DEBUG) + ifndef CIRCUITPY_AUDIOMP3 ifeq ($(CIRCUITPY_FULL_BUILD),1) CIRCUITPY_AUDIOMP3 = $(CIRCUITPY_AUDIOCORE) @@ -286,6 +291,15 @@ CFLAGS += -DCIRCUITPY_JSON=$(CIRCUITPY_JSON) CIRCUITPY_KEYPAD ?= $(CIRCUITPY_FULL_BUILD) CFLAGS += -DCIRCUITPY_KEYPAD=$(CIRCUITPY_KEYPAD) +CIRCUITPY_KEYPAD_KEYS ?= $(CIRCUITPY_KEYPAD) +CFLAGS += -DCIRCUITPY_KEYPAD_KEYS=$(CIRCUITPY_KEYPAD_KEYS) + +CIRCUITPY_KEYPAD_KEYMATRIX ?= $(CIRCUITPY_KEYPAD) +CFLAGS += -DCIRCUITPY_KEYPAD_KEYMATRIX=$(CIRCUITPY_KEYPAD_KEYMATRIX) + +CIRCUITPY_KEYPAD_SHIFTREGISTERKEYS ?= $(CIRCUITPY_KEYPAD) +CFLAGS += -DCIRCUITPY_KEYPAD_SHIFTREGISTERKEYS=$(CIRCUITPY_KEYPAD_SHIFTREGISTERKEYS) + CIRCUITPY_MATH ?= 1 CFLAGS += -DCIRCUITPY_MATH=$(CIRCUITPY_MATH) @@ -437,6 +451,10 @@ CFLAGS += -DCIRCUITPY_SUPERVISOR=$(CIRCUITPY_SUPERVISOR) CIRCUITPY_SYNTHIO ?= $(CIRCUITPY_AUDIOCORE) CFLAGS += -DCIRCUITPY_SYNTHIO=$(CIRCUITPY_SYNTHIO) +CIRCUITPY_SYNTHIO_MAX_CHANNELS ?= 2 +CFLAGS += -DCIRCUITPY_SYNTHIO_MAX_CHANNELS=$(CIRCUITPY_SYNTHIO_MAX_CHANNELS) + + CIRCUITPY_SYS ?= 1 CFLAGS += -DCIRCUITPY_SYS=$(CIRCUITPY_SYS) diff --git a/shared-bindings/audiobusio/I2SOut.c b/shared-bindings/audiobusio/I2SOut.c index 3713caeb7b..8c6f1a3a7a 100644 --- a/shared-bindings/audiobusio/I2SOut.c +++ b/shared-bindings/audiobusio/I2SOut.c @@ -97,7 +97,7 @@ //| ... STATIC mp_obj_t audiobusio_i2sout_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { #if !CIRCUITPY_AUDIOBUSIO_I2SOUT - mp_raise_NotImplementedError(translate("I2SOut not available")); + mp_raise_NotImplementedError_varg(translate("%q"), MP_QSTR_I2SOut); return NULL; // Not reachable. #else enum { ARG_bit_clock, ARG_word_select, ARG_data, ARG_left_justified }; diff --git a/shared-bindings/audiobusio/PDMIn.c b/shared-bindings/audiobusio/PDMIn.c index b6a316fadb..1f0e500292 100644 --- a/shared-bindings/audiobusio/PDMIn.c +++ b/shared-bindings/audiobusio/PDMIn.c @@ -94,7 +94,7 @@ //| ... STATIC mp_obj_t audiobusio_pdmin_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { #if !CIRCUITPY_AUDIOBUSIO_PDMIN - mp_raise_NotImplementedError(translate("PDMIn not available")); + mp_raise_NotImplementedError_varg(translate("%q"), MP_QSTR_PDMIn); #else enum { ARG_clock_pin, ARG_data_pin, ARG_sample_rate, ARG_bit_depth, ARG_mono, ARG_oversample, ARG_startup_delay }; static const mp_arg_t allowed_args[] = { diff --git a/shared-bindings/audiocore/RawSample.c b/shared-bindings/audiocore/RawSample.c index d185476f03..39992f87c1 100644 --- a/shared-bindings/audiocore/RawSample.c +++ b/shared-bindings/audiocore/RawSample.c @@ -40,7 +40,7 @@ //| def __init__( //| self, buffer: ReadableBuffer, *, channel_count: int = 1, sample_rate: int = 8000 //| ) -> None: -//| """Create a RawSample based on the given buffer of signed values. If channel_count is more than +//| """Create a RawSample based on the given buffer of values. If channel_count is more than //| 1 then each channel's samples should alternate. In other words, for a two channel buffer, the //| first sample will be for channel 1, the second sample will be for channel two, the third for //| channel 1 and so on. diff --git a/shared-bindings/audiocore/WaveFile.c b/shared-bindings/audiocore/WaveFile.c index 36204fa2b2..3a13db72a0 100644 --- a/shared-bindings/audiocore/WaveFile.c +++ b/shared-bindings/audiocore/WaveFile.c @@ -32,6 +32,7 @@ #include "shared-bindings/audiocore/WaveFile.h" #include "shared-bindings/util.h" #include "supervisor/shared/translate/translate.h" +#include "extmod/vfs_posix.h" //| class WaveFile: //| """Load a wave file for audio playback diff --git a/shared-bindings/audiocore/__init__.c b/shared-bindings/audiocore/__init__.c index 2e3b479cf6..6f32979099 100644 --- a/shared-bindings/audiocore/__init__.c +++ b/shared-bindings/audiocore/__init__.c @@ -29,7 +29,6 @@ #include "py/obj.h" #include "py/runtime.h" -#include "shared-bindings/microcontroller/Pin.h" #include "shared-bindings/audiocore/__init__.h" #include "shared-bindings/audiocore/RawSample.h" #include "shared-bindings/audiocore/WaveFile.h" @@ -37,10 +36,57 @@ //| """Support for audio samples""" +#if CIRCUITPY_AUDIOCORE_DEBUG +// (no docstrings so that the debug functions are not shown on docs.circuitpython.org) +STATIC mp_obj_t audiocore_get_buffer(mp_obj_t sample_in) { + uint8_t *buffer = NULL; + uint32_t buffer_length = 0; + audioio_get_buffer_result_t gbr = audiosample_get_buffer(sample_in, false, 0, &buffer, &buffer_length); + + mp_obj_t result[2] = {mp_obj_new_int_from_uint(gbr), mp_const_none}; + + if (gbr != GET_BUFFER_ERROR) { + // copies the data because the gc semantics of get_buffer are unclear + result[1] = mp_obj_new_bytes(buffer, buffer_length); + } + + return mp_obj_new_tuple(2, result); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(audiocore_get_buffer_obj, audiocore_get_buffer); + +STATIC mp_obj_t audiocore_get_structure(mp_obj_t sample_in) { + bool single_buffer, samples_signed; + uint32_t max_buffer_length; + uint8_t spacing; + + audiosample_get_buffer_structure(sample_in, false, &single_buffer, &samples_signed, &max_buffer_length, &spacing); + mp_obj_t result[4] = { + mp_obj_new_int_from_uint(single_buffer), + mp_obj_new_int_from_uint(samples_signed), + mp_obj_new_int_from_uint(max_buffer_length), + mp_obj_new_int_from_uint(spacing), + }; + return mp_obj_new_tuple(4, result); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(audiocore_get_structure_obj, audiocore_get_structure); + +STATIC mp_obj_t audiocore_reset_buffer(mp_obj_t sample_in) { + audiosample_reset_buffer(sample_in, false, 0); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(audiocore_reset_buffer_obj, audiocore_reset_buffer); + +#endif + STATIC const mp_rom_map_elem_t audiocore_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audiocore) }, { MP_ROM_QSTR(MP_QSTR_RawSample), MP_ROM_PTR(&audioio_rawsample_type) }, { MP_ROM_QSTR(MP_QSTR_WaveFile), MP_ROM_PTR(&audioio_wavefile_type) }, + #if CIRCUITPY_AUDIOCORE_DEBUG + { MP_ROM_QSTR(MP_QSTR_get_buffer), MP_ROM_PTR(&audiocore_get_buffer_obj) }, + { MP_ROM_QSTR(MP_QSTR_reset_buffer), MP_ROM_PTR(&audiocore_reset_buffer_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_structure), MP_ROM_PTR(&audiocore_get_structure_obj) }, + #endif }; STATIC MP_DEFINE_CONST_DICT(audiocore_module_globals, audiocore_module_globals_table); diff --git a/shared-bindings/audiomixer/Mixer.c b/shared-bindings/audiomixer/Mixer.c index 58c618fc11..83fcc06712 100644 --- a/shared-bindings/audiomixer/Mixer.c +++ b/shared-bindings/audiomixer/Mixer.c @@ -33,8 +33,6 @@ #include "py/binary.h" #include "py/objproperty.h" #include "py/runtime.h" -#include "shared-bindings/microcontroller/Pin.h" -#include "shared-bindings/audiocore/RawSample.h" #include "shared-bindings/util.h" #include "supervisor/shared/translate/translate.h" diff --git a/shared-bindings/audiomixer/Mixer.h b/shared-bindings/audiomixer/Mixer.h index 88592a1cb3..d04f793dde 100644 --- a/shared-bindings/audiomixer/Mixer.h +++ b/shared-bindings/audiomixer/Mixer.h @@ -27,9 +27,7 @@ #ifndef MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOMIXER_MIXER_H #define MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOMIXER_MIXER_H -#include "common-hal/microcontroller/Pin.h" #include "shared-module/audiomixer/Mixer.h" -#include "shared-bindings/audiocore/RawSample.h" extern const mp_obj_type_t audiomixer_mixer_type; extern const mp_obj_type_t audiomixer_mixervoice_type; diff --git a/shared-bindings/audiomixer/MixerVoice.c b/shared-bindings/audiomixer/MixerVoice.c index 16a1be520f..1d6cd7f39a 100644 --- a/shared-bindings/audiomixer/MixerVoice.c +++ b/shared-bindings/audiomixer/MixerVoice.c @@ -32,8 +32,6 @@ #include "py/binary.h" #include "py/objproperty.h" #include "py/runtime.h" -#include "shared-bindings/microcontroller/Pin.h" -#include "shared-bindings/audiocore/RawSample.h" #include "shared-bindings/util.h" #include "supervisor/shared/translate/translate.h" @@ -115,7 +113,7 @@ STATIC mp_obj_t audiomixer_mixervoice_obj_set_level(size_t n_args, const mp_obj_ mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - float level = mp_obj_get_float(args[ARG_level].u_obj); + mp_float_t level = mp_obj_get_float(args[ARG_level].u_obj); if (level > 1 || level < 0) { mp_raise_ValueError(translate("level must be between 0 and 1")); diff --git a/shared-bindings/audiomixer/MixerVoice.h b/shared-bindings/audiomixer/MixerVoice.h index bce4632760..0ff8accee4 100644 --- a/shared-bindings/audiomixer/MixerVoice.h +++ b/shared-bindings/audiomixer/MixerVoice.h @@ -26,9 +26,6 @@ #ifndef SHARED_BINDINGS_AUDIOMIXER_MIXERVOICE_H_ #define SHARED_BINDINGS_AUDIOMIXER_MIXERVOICE_H_ -#include "common-hal/microcontroller/Pin.h" -#include "shared-bindings/audiocore/RawSample.h" - #include "shared-module/audiomixer/MixerVoice.h" #include "shared-module/audiomixer/Mixer.h" @@ -39,8 +36,8 @@ void common_hal_audiomixer_mixervoice_construct(audiomixer_mixervoice_obj_t *sel void common_hal_audiomixer_mixervoice_set_parent(audiomixer_mixervoice_obj_t *self, audiomixer_mixer_obj_t *parent); void common_hal_audiomixer_mixervoice_play(audiomixer_mixervoice_obj_t *self, mp_obj_t sample, bool loop); void common_hal_audiomixer_mixervoice_stop(audiomixer_mixervoice_obj_t *self); -float common_hal_audiomixer_mixervoice_get_level(audiomixer_mixervoice_obj_t *self); -void common_hal_audiomixer_mixervoice_set_level(audiomixer_mixervoice_obj_t *self, float gain); +mp_float_t common_hal_audiomixer_mixervoice_get_level(audiomixer_mixervoice_obj_t *self); +void common_hal_audiomixer_mixervoice_set_level(audiomixer_mixervoice_obj_t *self, mp_float_t gain); bool common_hal_audiomixer_mixervoice_get_playing(audiomixer_mixervoice_obj_t *self); diff --git a/shared-bindings/audiomixer/__init__.c b/shared-bindings/audiomixer/__init__.c index 8292be9e95..4c8068172b 100644 --- a/shared-bindings/audiomixer/__init__.c +++ b/shared-bindings/audiomixer/__init__.c @@ -29,7 +29,6 @@ #include "py/obj.h" #include "py/runtime.h" -#include "shared-bindings/microcontroller/Pin.h" #include "shared-bindings/audiomixer/Mixer.h" //| """Support for audio mixing""" diff --git a/shared-bindings/keypad/Event.c b/shared-bindings/keypad/Event.c index 8264ebe612..52c574e692 100644 --- a/shared-bindings/keypad/Event.c +++ b/shared-bindings/keypad/Event.c @@ -167,9 +167,9 @@ STATIC mp_obj_t keypad_event_unary_op(mp_unary_op_t op, mp_obj_t self_in) { STATIC void keypad_event_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { keypad_event_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "", + mp_printf(print, "", common_hal_keypad_event_get_key_number(self), - common_hal_keypad_event_get_pressed(self) ? "pressed" : "released"); + common_hal_keypad_event_get_pressed(self) ? MP_QSTR_pressed : MP_QSTR_released); } STATIC const mp_rom_map_elem_t keypad_event_locals_dict_table[] = { diff --git a/shared-bindings/keypad/KeyMatrix.c b/shared-bindings/keypad/KeyMatrix.c index 1c623f4bed..2209f3f68f 100644 --- a/shared-bindings/keypad/KeyMatrix.c +++ b/shared-bindings/keypad/KeyMatrix.c @@ -71,6 +71,7 @@ //| ... STATIC mp_obj_t keypad_keymatrix_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + #if CIRCUITPY_KEYPAD_KEYMATRIX keypad_keymatrix_obj_t *self = m_new_obj(keypad_keymatrix_obj_t); self->base.type = &keypad_keymatrix_type; enum { ARG_row_pins, ARG_column_pins, ARG_columns_to_anodes, ARG_interval, ARG_max_events }; @@ -114,8 +115,13 @@ STATIC mp_obj_t keypad_keymatrix_make_new(const mp_obj_type_t *type, size_t n_ar common_hal_keypad_keymatrix_construct(self, num_row_pins, row_pins_array, num_column_pins, column_pins_array, args[ARG_columns_to_anodes].u_bool, interval, max_events); return MP_OBJ_FROM_PTR(self); + #else + mp_raise_NotImplementedError_varg(translate("%q"), MP_QSTR_KeyMatrix); + + #endif } +#if CIRCUITPY_KEYPAD_KEYMATRIX //| def deinit(self) -> None: //| """Stop scanning and release the pins.""" //| ... @@ -228,9 +234,13 @@ STATIC const mp_rom_map_elem_t keypad_keymatrix_locals_dict_table[] = { STATIC MP_DEFINE_CONST_DICT(keypad_keymatrix_locals_dict, keypad_keymatrix_locals_dict_table); +#endif + const mp_obj_type_t keypad_keymatrix_type = { { &mp_type_type }, .name = MP_QSTR_KeyMatrix, .make_new = keypad_keymatrix_make_new, + #if CIRCUITPY_KEYPAD_KEYMATRIX .locals_dict = (mp_obj_t)&keypad_keymatrix_locals_dict, + #endif }; diff --git a/shared-bindings/keypad/Keys.c b/shared-bindings/keypad/Keys.c index 10519958c7..2481fedc6b 100644 --- a/shared-bindings/keypad/Keys.c +++ b/shared-bindings/keypad/Keys.c @@ -73,6 +73,7 @@ //| ... STATIC mp_obj_t keypad_keys_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + #if CIRCUITPY_KEYPAD_KEYS keypad_keys_obj_t *self = m_new_obj(keypad_keys_obj_t); self->base.type = &keypad_keys_type; enum { ARG_pins, ARG_value_when_pressed, ARG_pull, ARG_interval, ARG_max_events }; @@ -106,8 +107,13 @@ STATIC mp_obj_t keypad_keys_make_new(const mp_obj_type_t *type, size_t n_args, s common_hal_keypad_keys_construct(self, num_pins, pins_array, value_when_pressed, args[ARG_pull].u_bool, interval, max_events); return MP_OBJ_FROM_PTR(self); + #else + mp_raise_NotImplementedError_varg(translate("%q"), MP_QSTR_Keys); + + #endif } +#if CIRCUITPY_KEYPAD_KEYS //| def deinit(self) -> None: //| """Stop scanning and release the pins.""" //| ... @@ -162,10 +168,13 @@ STATIC const mp_rom_map_elem_t keypad_keys_locals_dict_table[] = { }; STATIC MP_DEFINE_CONST_DICT(keypad_keys_locals_dict, keypad_keys_locals_dict_table); +#endif const mp_obj_type_t keypad_keys_type = { { &mp_type_type }, .name = MP_QSTR_Keys, .make_new = keypad_keys_make_new, + #if CIRCUITPY_KEYPAD_KEYS .locals_dict = (mp_obj_t)&keypad_keys_locals_dict, + #endif }; diff --git a/shared-bindings/keypad/ShiftRegisterKeys.c b/shared-bindings/keypad/ShiftRegisterKeys.c index 9113be33d2..1fcf517ac3 100644 --- a/shared-bindings/keypad/ShiftRegisterKeys.c +++ b/shared-bindings/keypad/ShiftRegisterKeys.c @@ -82,6 +82,7 @@ //| ... STATIC mp_obj_t keypad_shiftregisterkeys_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + #if CIRCUITPY_KEYPAD_SHIFTREGISTERKEYS keypad_shiftregisterkeys_obj_t *self = m_new_obj(keypad_shiftregisterkeys_obj_t); self->base.type = &keypad_shiftregisterkeys_type; enum { ARG_clock, ARG_data, ARG_latch, ARG_value_to_latch, ARG_key_count, ARG_value_when_pressed, ARG_interval, ARG_max_events }; @@ -113,8 +114,12 @@ STATIC mp_obj_t keypad_shiftregisterkeys_make_new(const mp_obj_type_t *type, siz self, clock, data, latch, value_to_latch, key_count, value_when_pressed, interval, max_events); return MP_OBJ_FROM_PTR(self); + #else + mp_raise_NotImplementedError_varg(translate("%q"), MP_QSTR_ShiftRegisterKeys); + #endif } +#if CIRCUITPY_KEYPAD_SHIFTREGISTERKEYS //| def deinit(self) -> None: //| """Stop scanning and release the pins.""" //| ... @@ -169,10 +174,13 @@ STATIC const mp_rom_map_elem_t keypad_shiftregisterkeys_locals_dict_table[] = { }; STATIC MP_DEFINE_CONST_DICT(keypad_shiftregisterkeys_locals_dict, keypad_shiftregisterkeys_locals_dict_table); +#endif const mp_obj_type_t keypad_shiftregisterkeys_type = { { &mp_type_type }, .name = MP_QSTR_ShiftRegisterKeys, .make_new = keypad_shiftregisterkeys_make_new, + #if CIRCUITPY_KEYPAD_SHIFTREGISTERKEYS .locals_dict = (mp_obj_t)&keypad_shiftregisterkeys_locals_dict, + #endif }; diff --git a/shared-bindings/synthio/MidiTrack.c b/shared-bindings/synthio/MidiTrack.c index b987337eaa..20a203ce9c 100644 --- a/shared-bindings/synthio/MidiTrack.c +++ b/shared-bindings/synthio/MidiTrack.c @@ -32,13 +32,19 @@ #include "py/runtime.h" #include "shared-bindings/util.h" #include "shared-bindings/synthio/MidiTrack.h" +#include "shared-bindings/synthio/__init__.h" #include "supervisor/shared/translate/translate.h" //| class MidiTrack: -//| """Simple square-wave MIDI synth""" +//| """Simple MIDI synth""" //| //| def __init__( -//| self, buffer: ReadableBuffer, tempo: int, *, sample_rate: int = 11025 +//| self, +//| buffer: ReadableBuffer, +//| tempo: int, +//| *, +//| sample_rate: int = 11025, +//| waveform: ReadableBuffer = None //| ) -> None: //| """Create a MidiTrack from the given stream of MIDI events. Only "Note On" and "Note Off" events //| are supported; channel numbers and key velocities are ignored. Up to two notes may be on at the @@ -47,6 +53,7 @@ //| :param ~circuitpython_typing.ReadableBuffer buffer: Stream of MIDI events, as stored in a MIDI file track chunk //| :param int tempo: Tempo of the streamed events, in MIDI ticks per second //| :param int sample_rate: The desired playback sample rate; higher sample rate requires more memory +//| :param ReadableBuffer waveform: A single-cycle waveform. Default is a 50% duty cycle square wave. If specified, must be a ReadableBuffer of type 'h' (signed 16 bit) //| //| Simple melody:: //| @@ -65,11 +72,12 @@ //| print("stopped")""" //| ... STATIC mp_obj_t synthio_miditrack_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - enum { ARG_buffer, ARG_tempo, ARG_sample_rate }; + enum { ARG_buffer, ARG_tempo, ARG_sample_rate, ARG_waveform }; static const mp_arg_t allowed_args[] = { { MP_QSTR_buffer, MP_ARG_OBJ | MP_ARG_REQUIRED }, { MP_QSTR_tempo, MP_ARG_INT | MP_ARG_REQUIRED }, { MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 11025} }, + { MP_QSTR_waveform, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none } }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); @@ -77,13 +85,18 @@ STATIC mp_obj_t synthio_miditrack_make_new(const mp_obj_type_t *type, size_t n_a mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_READ); + mp_buffer_info_t bufinfo_waveform; + synthio_synth_parse_waveform(&bufinfo_waveform, args[ARG_waveform].u_obj); + synthio_miditrack_obj_t *self = m_new_obj(synthio_miditrack_obj_t); self->base.type = &synthio_miditrack_type; common_hal_synthio_miditrack_construct(self, (uint8_t *)bufinfo.buf, bufinfo.len, args[ARG_tempo].u_int, - args[ARG_sample_rate].u_int); + args[ARG_sample_rate].u_int, + bufinfo_waveform.buf, + bufinfo_waveform.len / 2); return MP_OBJ_FROM_PTR(self); } diff --git a/shared-bindings/synthio/MidiTrack.h b/shared-bindings/synthio/MidiTrack.h index d44d4c3bb7..90ddab8728 100644 --- a/shared-bindings/synthio/MidiTrack.h +++ b/shared-bindings/synthio/MidiTrack.h @@ -32,7 +32,7 @@ extern const mp_obj_type_t synthio_miditrack_type; void common_hal_synthio_miditrack_construct(synthio_miditrack_obj_t *self, - const uint8_t *buffer, uint32_t len, uint32_t tempo, uint32_t sample_rate); + const uint8_t *buffer, uint32_t len, uint32_t tempo, uint32_t sample_rate, const int16_t *waveform, uint16_t waveform_len); void common_hal_synthio_miditrack_deinit(synthio_miditrack_obj_t *self); bool common_hal_synthio_miditrack_deinited(synthio_miditrack_obj_t *self); diff --git a/shared-bindings/synthio/Synthesizer.c b/shared-bindings/synthio/Synthesizer.c new file mode 100644 index 0000000000..2ad5cbcd42 --- /dev/null +++ b/shared-bindings/synthio/Synthesizer.c @@ -0,0 +1,211 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Artyom Skrobov + * Copyright (c) 2023 Jeff Epler for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "shared/runtime/context_manager_helpers.h" +#include "py/binary.h" +#include "py/objproperty.h" +#include "py/runtime.h" +#include "shared-bindings/util.h" +#include "shared-bindings/synthio/Synthesizer.h" +#include "shared-bindings/synthio/__init__.h" +#include "supervisor/shared/translate/translate.h" + +//| class Synth: +//| def __init__(self, *, sample_rate: int = 11025, waveform: ReadableBuffer = None) -> None: +//| """Create a synthesizer object.""" +STATIC mp_obj_t synthio_synthesizer_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_sample_rate, ARG_waveform }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 11025} }, + { MP_QSTR_waveform, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none } }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_buffer_info_t bufinfo_waveform; + synthio_synth_parse_waveform(&bufinfo_waveform, args[ARG_waveform].u_obj); + + synthio_synthesizer_obj_t *self = m_new_obj(synthio_synthesizer_obj_t); + self->base.type = &synthio_synthesizer_type; + + common_hal_synthio_synthesizer_construct(self, + args[ARG_sample_rate].u_int, + bufinfo_waveform.buf, + bufinfo_waveform.len / 2); + + return MP_OBJ_FROM_PTR(self); +} + +STATIC void check_for_deinit(synthio_synthesizer_obj_t *self) { + if (common_hal_synthio_synthesizer_deinited(self)) { + raise_deinited_error(); + } +} + +//| def press(self, /, press: Sequence[int] = ()) -> None: +//| """Turn some notes on. Notes use MIDI numbering, with 60 being middle C.""" +STATIC mp_obj_t synthio_synthesizer_press(mp_obj_t self_in, mp_obj_t press) { + synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + common_hal_synthio_synthesizer_press(self, press); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(synthio_synthesizer_press_obj, synthio_synthesizer_press); +// +//| def release_then_press( +//| self, release: Sequence[int] = (), press: Sequence[int] = () +//| ) -> None: +//| """Turn some notes on and/or off. Notes use MIDI numbering, with 60 being middle C.""" +STATIC mp_obj_t synthio_synthesizer_release_then_press(mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_release, ARG_press }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_release, MP_ARG_OBJ, {.u_obj = mp_const_empty_tuple } }, + { MP_QSTR_press, MP_ARG_OBJ, {.u_obj = mp_const_empty_tuple } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + check_for_deinit(self); + common_hal_synthio_synthesizer_release(self, args[ARG_release].u_obj); + common_hal_synthio_synthesizer_press(self, args[ARG_press].u_obj); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(synthio_synthesizer_release_then_press_obj, 1, synthio_synthesizer_release_then_press); + +// +//| def release_all_then_press(self, /, press: Sequence[int]) -> None: +//| """Turn any currently-playing notes off, then turn on the given notes""" +STATIC mp_obj_t synthio_synthesizer_release_all_then_press(mp_obj_t self_in, mp_obj_t press) { + synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + common_hal_synthio_synthesizer_release_all(self); + common_hal_synthio_synthesizer_press(self, press); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(synthio_synthesizer_release_all_then_press_obj, synthio_synthesizer_release_all_then_press); + +// +//| def release_all(self) -> None: +//| """Turn any currently-playing notes off""" +STATIC mp_obj_t synthio_synthesizer_release_all(mp_obj_t self_in) { + synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + common_hal_synthio_synthesizer_release_all(self); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(synthio_synthesizer_release_all_obj, synthio_synthesizer_release_all); + +//| def deinit(self) -> None: +//| """Deinitialises the object and releases any memory resources for reuse.""" +//| ... +STATIC mp_obj_t synthio_synthesizer_deinit(mp_obj_t self_in) { + synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_synthio_synthesizer_deinit(self); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(synthio_synthesizer_deinit_obj, synthio_synthesizer_deinit); + +//| def __enter__(self) -> MidiTrack: +//| """No-op used by Context Managers.""" +//| ... +// Provided by context manager helper. +//| +//| def __exit__(self) -> None: +//| """Automatically deinitializes the hardware when exiting a context. See +//| :ref:`lifetime-and-contextmanagers` for more info.""" +//| ... +STATIC mp_obj_t synthio_synthesizer_obj___exit__(size_t n_args, const mp_obj_t *args) { + (void)n_args; + common_hal_synthio_synthesizer_deinit(args[0]); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(synthio_synthesizer___exit___obj, 4, 4, synthio_synthesizer_obj___exit__); +//| sample_rate: int +//| """32 bit value that tells how quickly samples are played in Hertz (cycles per second).""" +STATIC mp_obj_t synthio_synthesizer_obj_get_sample_rate(mp_obj_t self_in) { + synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + return MP_OBJ_NEW_SMALL_INT(common_hal_synthio_synthesizer_get_sample_rate(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(synthio_synthesizer_get_sample_rate_obj, synthio_synthesizer_obj_get_sample_rate); + +MP_PROPERTY_GETTER(synthio_synthesizer_sample_rate_obj, + (mp_obj_t)&synthio_synthesizer_get_sample_rate_obj); + +//| pressed: Tuple[int] +//| """A sequence of the currently pressed notes (read-only property)""" +//| +STATIC mp_obj_t synthio_synthesizer_obj_get_pressed(mp_obj_t self_in) { + synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + return common_hal_synthio_synthesizer_get_pressed_notes(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(synthio_synthesizer_get_pressed_obj, synthio_synthesizer_obj_get_pressed); + +MP_PROPERTY_GETTER(synthio_synthesizer_pressed_obj, + (mp_obj_t)&synthio_synthesizer_get_pressed_obj); + +STATIC const mp_rom_map_elem_t synthio_synthesizer_locals_dict_table[] = { + // Methods + { MP_ROM_QSTR(MP_QSTR_press), MP_ROM_PTR(&synthio_synthesizer_press_obj) }, + { MP_ROM_QSTR(MP_QSTR_release_all), MP_ROM_PTR(&synthio_synthesizer_release_all_obj) }, + { MP_ROM_QSTR(MP_QSTR_release_then_press), MP_ROM_PTR(&synthio_synthesizer_release_then_press_obj) }, + { MP_ROM_QSTR(MP_QSTR_release_all_then_press), MP_ROM_PTR(&synthio_synthesizer_release_all_then_press_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&synthio_synthesizer_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, + { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&synthio_synthesizer___exit___obj) }, + + // Properties + { MP_ROM_QSTR(MP_QSTR_sample_rate), MP_ROM_PTR(&synthio_synthesizer_sample_rate_obj) }, + { MP_ROM_QSTR(MP_QSTR_pressed), MP_ROM_PTR(&synthio_synthesizer_pressed_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(synthio_synthesizer_locals_dict, synthio_synthesizer_locals_dict_table); + +STATIC const audiosample_p_t synthio_synthesizer_proto = { + MP_PROTO_IMPLEMENT(MP_QSTR_protocol_audiosample) + .sample_rate = (audiosample_sample_rate_fun)common_hal_synthio_synthesizer_get_sample_rate, + .bits_per_sample = (audiosample_bits_per_sample_fun)common_hal_synthio_synthesizer_get_bits_per_sample, + .channel_count = (audiosample_channel_count_fun)common_hal_synthio_synthesizer_get_channel_count, + .reset_buffer = (audiosample_reset_buffer_fun)synthio_synthesizer_reset_buffer, + .get_buffer = (audiosample_get_buffer_fun)synthio_synthesizer_get_buffer, + .get_buffer_structure = (audiosample_get_buffer_structure_fun)synthio_synthesizer_get_buffer_structure, +}; + +const mp_obj_type_t synthio_synthesizer_type = { + { &mp_type_type }, + .name = MP_QSTR_Synthesizer, + .flags = MP_TYPE_FLAG_EXTENDED, + .make_new = synthio_synthesizer_make_new, + .locals_dict = (mp_obj_dict_t *)&synthio_synthesizer_locals_dict, + MP_TYPE_EXTENDED_FIELDS( + .protocol = &synthio_synthesizer_proto, + ), +}; diff --git a/shared-bindings/synthio/Synthesizer.h b/shared-bindings/synthio/Synthesizer.h new file mode 100644 index 0000000000..92e75a1c23 --- /dev/null +++ b/shared-bindings/synthio/Synthesizer.h @@ -0,0 +1,45 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Artyom Skrobov + * Copyright (c) 2023 Jeff Epler for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include "shared-module/synthio/Synthesizer.h" + +extern const mp_obj_type_t synthio_synthesizer_type; + +void common_hal_synthio_synthesizer_construct(synthio_synthesizer_obj_t *self, + uint32_t sample_rate, const int16_t *waveform, uint16_t waveform_len); + +void common_hal_synthio_synthesizer_deinit(synthio_synthesizer_obj_t *self); +bool common_hal_synthio_synthesizer_deinited(synthio_synthesizer_obj_t *self); +uint32_t common_hal_synthio_synthesizer_get_sample_rate(synthio_synthesizer_obj_t *self); +uint8_t common_hal_synthio_synthesizer_get_bits_per_sample(synthio_synthesizer_obj_t *self); +uint8_t common_hal_synthio_synthesizer_get_channel_count(synthio_synthesizer_obj_t *self); +void common_hal_synthio_synthesizer_release(synthio_synthesizer_obj_t *self, mp_obj_t to_release); +void common_hal_synthio_synthesizer_press(synthio_synthesizer_obj_t *self, mp_obj_t to_press); +void common_hal_synthio_synthesizer_release_all(synthio_synthesizer_obj_t *self); +mp_obj_t common_hal_synthio_synthesizer_get_pressed_notes(synthio_synthesizer_obj_t *self); diff --git a/shared-bindings/synthio/__init__.c b/shared-bindings/synthio/__init__.c index 106a073535..3c50377ff8 100644 --- a/shared-bindings/synthio/__init__.c +++ b/shared-bindings/synthio/__init__.c @@ -30,9 +30,11 @@ #include "py/obj.h" #include "py/runtime.h" #include "extmod/vfs_fat.h" +#include "extmod/vfs_posix.h" #include "shared-bindings/synthio/__init__.h" #include "shared-bindings/synthio/MidiTrack.h" +#include "shared-bindings/synthio/Synthesizer.h" //| """Support for MIDI synthesis""" //| @@ -42,6 +44,7 @@ //| //| :param typing.BinaryIO file: Already opened MIDI file //| :param int sample_rate: The desired playback sample rate; higher sample rate requires more memory +//| :param ReadableBuffer waveform: A single-cycle waveform. Default is a 50% duty cycle square wave. If specified, must be a ReadableBuffer of type 'h' (signed 16 bit) //| //| //| Playing a MIDI file from flash:: @@ -62,10 +65,11 @@ //| ... //| STATIC mp_obj_t synthio_from_file(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_file, ARG_sample_rate }; + enum { ARG_file, ARG_sample_rate, ARG_waveform }; static const mp_arg_t allowed_args[] = { { MP_QSTR_file, MP_ARG_OBJ | MP_ARG_REQUIRED }, { MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 11025} }, + { MP_QSTR_waveform, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none } }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); @@ -74,6 +78,10 @@ STATIC mp_obj_t synthio_from_file(size_t n_args, const mp_obj_t *pos_args, mp_ma } pyb_file_obj_t *file = MP_OBJ_TO_PTR(args[ARG_file].u_obj); + + mp_buffer_info_t bufinfo_waveform; + synthio_synth_parse_waveform(&bufinfo_waveform, args[ARG_waveform].u_obj); + uint8_t chunk_header[14]; f_rewind(&file->fp); UINT bytes_read; @@ -113,9 +121,13 @@ STATIC mp_obj_t synthio_from_file(size_t n_args, const mp_obj_t *pos_args, mp_ma result->base.type = &synthio_miditrack_type; common_hal_synthio_miditrack_construct(result, buffer, track_size, - tempo, args[ARG_sample_rate].u_int); + tempo, args[ARG_sample_rate].u_int, bufinfo_waveform.buf, bufinfo_waveform.len / 2); + #if MICROPY_MALLOC_USES_ALLOCATED_SIZE + m_free(buffer, track_size); + #else m_free(buffer); + #endif return MP_OBJ_FROM_PTR(result); } @@ -125,6 +137,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(synthio_from_file_obj, 1, synthio_from_file); STATIC const mp_rom_map_elem_t synthio_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_synthio) }, { MP_ROM_QSTR(MP_QSTR_MidiTrack), MP_ROM_PTR(&synthio_miditrack_type) }, + { MP_ROM_QSTR(MP_QSTR_Synthesizer), MP_ROM_PTR(&synthio_synthesizer_type) }, { MP_ROM_QSTR(MP_QSTR_from_file), MP_ROM_PTR(&synthio_from_file_obj) }, }; diff --git a/shared-bindings/synthio/__init__.h b/shared-bindings/synthio/__init__.h index 14af1a5805..3b7d47db28 100644 --- a/shared-bindings/synthio/__init__.h +++ b/shared-bindings/synthio/__init__.h @@ -24,11 +24,6 @@ * THE SOFTWARE. */ -#ifndef MICROPY_INCLUDED_SHARED_BINDINGS_SYNTHIO___INIT___H -#define MICROPY_INCLUDED_SHARED_BINDINGS_SYNTHIO___INIT___H +#pragma once -#include "py/obj.h" - -// Nothing now. - -#endif // MICROPY_INCLUDED_SHARED_BINDINGS_SYNTHIO___INIT___H +extern int16_t shared_bindings_synthio_square_wave[]; diff --git a/shared-module/audiomixer/Mixer.c b/shared-module/audiomixer/Mixer.c index 1c5de4d934..397a837133 100644 --- a/shared-module/audiomixer/Mixer.c +++ b/shared-module/audiomixer/Mixer.c @@ -33,7 +33,10 @@ #include "py/runtime.h" #include "shared-module/audiocore/__init__.h" -#include "shared-module/audiocore/RawSample.h" + +#if defined(__arm__) && __arm__ +#include "cmsis_compiler.h" +#endif void common_hal_audiomixer_mixer_construct(audiomixer_mixer_obj_t *self, uint8_t voice_count, @@ -140,7 +143,7 @@ static inline uint32_t mult16signed(uint32_t val, int32_t mul) { float mod_mul = (float)mul / (float)((1 << 15) - 1); for (int8_t i = 0; i < 2; i++) { int16_t ai = (val >> (sizeof(uint16_t) * 8 * i)); - int32_t intermediate = ai * mod_mul; + int32_t intermediate = (int32_t)(ai * mod_mul); if (intermediate > SHRT_MAX) { intermediate = SHRT_MAX; } else if (intermediate < SHRT_MIN) { diff --git a/shared-module/audiomixer/MixerVoice.c b/shared-module/audiomixer/MixerVoice.c index bb58b0c111..f4d2a6514e 100644 --- a/shared-module/audiomixer/MixerVoice.c +++ b/shared-module/audiomixer/MixerVoice.c @@ -42,12 +42,12 @@ void common_hal_audiomixer_mixervoice_set_parent(audiomixer_mixervoice_obj_t *se self->parent = parent; } -float common_hal_audiomixer_mixervoice_get_level(audiomixer_mixervoice_obj_t *self) { - return (float)self->level / (1 << 15); +mp_float_t common_hal_audiomixer_mixervoice_get_level(audiomixer_mixervoice_obj_t *self) { + return (mp_float_t)self->level / (1 << 15); } -void common_hal_audiomixer_mixervoice_set_level(audiomixer_mixervoice_obj_t *self, float level) { - self->level = level * (1 << 15); +void common_hal_audiomixer_mixervoice_set_level(audiomixer_mixervoice_obj_t *self, mp_float_t level) { + self->level = (uint16_t)(level * (1 << 15)); } void common_hal_audiomixer_mixervoice_play(audiomixer_mixervoice_obj_t *self, mp_obj_t sample, bool loop) { diff --git a/shared-module/synthio/MidiTrack.c b/shared-module/synthio/MidiTrack.c index f02747d734..f8b39c29e3 100644 --- a/shared-module/synthio/MidiTrack.c +++ b/shared-module/synthio/MidiTrack.c @@ -27,10 +27,6 @@ #include "py/runtime.h" #include "shared-bindings/synthio/MidiTrack.h" -#define LOUDNESS 0x4000 // 0.5 -#define BITS_PER_SAMPLE 16 -#define BYTES_PER_SAMPLE (BITS_PER_SAMPLE / 8) -#define SILENCE 0x80 STATIC NORETURN void raise_midi_stream_error(uint32_t pos) { mp_raise_ValueError_varg(translate("Error in MIDI stream at position %d"), pos); @@ -47,36 +43,42 @@ STATIC uint8_t parse_note(const uint8_t *buffer, uint32_t len, uint32_t *pos) { return note; } -STATIC void terminate_span(synthio_miditrack_obj_t *self, uint16_t *dur, uint16_t *max_dur) { +STATIC void terminate_span(synthio_miditrack_obj_t *self, uint16_t *dur) { if (*dur) { self->track[self->total_spans - 1].dur = *dur; - if (*dur > *max_dur) { - *max_dur = *dur; - } *dur = 0; } else { self->total_spans--; } } -STATIC void add_span(synthio_miditrack_obj_t *self, uint8_t note1, uint8_t note2) { - synthio_midi_span_t span = { 0, {note1, note2} }; - self->track = m_realloc(self->track, - (self->total_spans + 1) * sizeof(synthio_midi_span_t)); - self->track[self->total_spans++] = span; +STATIC void add_span(synthio_miditrack_obj_t *self, const synthio_midi_span_t *span) { + self->track = m_renew(synthio_midi_span_t, self->track, self->total_spans, self->total_spans + 1); + self->track[self->total_spans++] = *span; +} + +STATIC void change_span_note(synthio_miditrack_obj_t *self, uint8_t old_note, uint8_t new_note, uint16_t *dur) { + synthio_midi_span_t span = self->track[self->total_spans - 1]; + if (synthio_span_change_note(&span, old_note, new_note)) { + terminate_span(self, dur); + add_span(self, &span); + *dur = 0; + } } void common_hal_synthio_miditrack_construct(synthio_miditrack_obj_t *self, - const uint8_t *buffer, uint32_t len, uint32_t tempo, uint32_t sample_rate) { + const uint8_t *buffer, uint32_t len, uint32_t tempo, uint32_t sample_rate, + const int16_t *waveform, uint16_t waveform_length) { - synthio_midi_span_t initial = { 0, {SILENCE, SILENCE} }; - self->sample_rate = sample_rate; + self->synth.sample_rate = sample_rate; self->track = m_malloc(sizeof(synthio_midi_span_t), false); + synthio_span_init(self->track); self->next_span = 0; self->total_spans = 1; - *self->track = initial; + self->synth.waveform = waveform; + self->synth.waveform_length = waveform_length; - uint16_t dur = 0, max_dur = 0; + uint16_t dur = 0; uint32_t pos = 0; while (pos < len) { uint8_t c; @@ -91,37 +93,19 @@ void common_hal_synthio_miditrack_construct(synthio_miditrack_obj_t *self, raise_midi_stream_error(pos); } + // dur is carried over here so that if a note on/off message doesn't actually produce a change, the + // underlying "span" is extended. Otherwise, it is zeroed out in the call to `terminate_span`. dur += delta * sample_rate / tempo; switch (buffer[pos++] >> 4) { case 8: { // Note Off uint8_t note = parse_note(buffer, len, &pos); - - // Ignore if not a note which is playing - synthio_midi_span_t last_span = self->track[self->total_spans - 1]; - if (last_span.note[0] == note || last_span.note[1] == note) { - terminate_span(self, &dur, &max_dur); - if (last_span.note[0] == note) { - add_span(self, last_span.note[1], SILENCE); - } else { - add_span(self, last_span.note[0], SILENCE); - } - } + change_span_note(self, note, SYNTHIO_SILENCE, &dur); break; } case 9: { // Note On uint8_t note = parse_note(buffer, len, &pos); - - // Ignore if two notes are already playing - synthio_midi_span_t last_span = self->track[self->total_spans - 1]; - if (last_span.note[1] == SILENCE) { - terminate_span(self, &dur, &max_dur); - if (last_span.note[0] == SILENCE) { - add_span(self, note, SILENCE); - } else { - add_span(self, last_span.note[0], note); - } - } + change_span_note(self, SYNTHIO_SILENCE, note, &dur); break; } case 10: @@ -142,27 +126,29 @@ void common_hal_synthio_miditrack_construct(synthio_miditrack_obj_t *self, raise_midi_stream_error(pos); } } - terminate_span(self, &dur, &max_dur); + terminate_span(self, &dur); - self->buffer_length = max_dur * BYTES_PER_SAMPLE; - self->buffer = m_malloc(self->buffer_length, false); + uint16_t max_dur = 0; + for (int i = 0; i < self->total_spans; i++) { + max_dur = MAX(self->track[i].dur, max_dur); + } + synthio_synth_init(&self->synth, max_dur); } void common_hal_synthio_miditrack_deinit(synthio_miditrack_obj_t *self) { - m_free(self->buffer); - self->buffer = NULL; - m_free(self->track); + synthio_synth_deinit(&self->synth); + m_del(synthio_midi_span_t, self->track, self->total_spans + 1); self->track = NULL; } bool common_hal_synthio_miditrack_deinited(synthio_miditrack_obj_t *self) { - return self->buffer == NULL; + return synthio_synth_deinited(&self->synth); } uint32_t common_hal_synthio_miditrack_get_sample_rate(synthio_miditrack_obj_t *self) { - return self->sample_rate; + return self->synth.sample_rate; } uint8_t common_hal_synthio_miditrack_get_bits_per_sample(synthio_miditrack_obj_t *self) { - return BITS_PER_SAMPLE; + return SYNTHIO_BITS_PER_SAMPLE; } uint8_t common_hal_synthio_miditrack_get_channel_count(synthio_miditrack_obj_t *self) { return 1; @@ -170,47 +156,29 @@ uint8_t common_hal_synthio_miditrack_get_channel_count(synthio_miditrack_obj_t * void synthio_miditrack_reset_buffer(synthio_miditrack_obj_t *self, bool single_channel_output, uint8_t channel) { - + synthio_synth_reset_buffer(&self->synth, single_channel_output, channel); + self->synth.span.dur = 0; self->next_span = 0; } -STATIC const uint16_t notes[] = {8372, 8870, 9397, 9956, 10548, 11175, 11840, - 12544, 13290, 14080, 14917, 15804}; // 9th octave - audioio_get_buffer_result_t synthio_miditrack_get_buffer(synthio_miditrack_obj_t *self, bool single_channel_output, uint8_t channel, uint8_t **buffer, uint32_t *buffer_length) { - if (self->next_span >= self->total_spans) { - *buffer_length = 0; - return GET_BUFFER_DONE; + if (self->synth.span.dur == 0) { + if (self->next_span >= self->total_spans) { + *buffer_length = 0; + return GET_BUFFER_DONE; + } + self->synth.span = self->track[self->next_span++]; } - synthio_midi_span_t span = self->track[self->next_span++]; - *buffer_length = span.dur * BYTES_PER_SAMPLE; - uint8_t octave1 = span.note[0] / 12; // 0..10 - uint8_t octave2 = span.note[1] / 12; // 0..10 - int32_t base_freq1 = notes[span.note[0] % 12]; - int32_t base_freq2 = notes[span.note[1] % 12]; - int32_t sample_rate = self->sample_rate; + synthio_synth_synthesize(&self->synth, buffer, buffer_length, single_channel_output ? 0 : channel); - for (uint16_t i = 0; i < span.dur; i++) { - int16_t semiperiod1 = span.note[0] == SILENCE ? 0 : - ((base_freq1 * i * 2) / sample_rate) >> (10 - octave1); - int16_t semiperiod2 = span.note[1] == SILENCE ? semiperiod1 : - ((base_freq2 * i * 2) / sample_rate) >> (10 - octave2); - self->buffer[i] = ((semiperiod1 % 2 + semiperiod2 % 2) - 1) * LOUDNESS; - } - *buffer = (uint8_t *)self->buffer; - - return self->next_span >= self->total_spans ? + return (self->synth.span.dur == 0 && self->next_span >= self->total_spans) ? GET_BUFFER_DONE : GET_BUFFER_MORE_DATA; } void synthio_miditrack_get_buffer_structure(synthio_miditrack_obj_t *self, bool single_channel_output, bool *single_buffer, bool *samples_signed, uint32_t *max_buffer_length, uint8_t *spacing) { - - *single_buffer = true; - *samples_signed = true; - *max_buffer_length = self->buffer_length; - *spacing = 1; + return synthio_synth_get_buffer_structure(&self->synth, single_channel_output, single_buffer, samples_signed, max_buffer_length, spacing); } diff --git a/shared-module/synthio/MidiTrack.h b/shared-module/synthio/MidiTrack.h index 0af174ebc1..e301ef355f 100644 --- a/shared-module/synthio/MidiTrack.h +++ b/shared-module/synthio/MidiTrack.h @@ -31,19 +31,12 @@ #include "shared-module/synthio/__init__.h" -typedef struct { - uint16_t dur; - uint8_t note[2]; -} synthio_midi_span_t; - typedef struct { mp_obj_base_t base; - uint32_t sample_rate; - uint16_t *buffer; - uint16_t buffer_length; - synthio_midi_span_t *track; + synthio_synth_t synth; uint16_t next_span; uint16_t total_spans; + synthio_midi_span_t *track; } synthio_miditrack_obj_t; diff --git a/shared-module/synthio/Synthesizer.c b/shared-module/synthio/Synthesizer.c new file mode 100644 index 0000000000..25c91ecbe0 --- /dev/null +++ b/shared-module/synthio/Synthesizer.c @@ -0,0 +1,105 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Artyom Skrobov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "shared-bindings/synthio/Synthesizer.h" + + + +void common_hal_synthio_synthesizer_construct(synthio_synthesizer_obj_t *self, + uint32_t sample_rate, const int16_t *waveform, uint16_t waveform_length) { + + self->synth.sample_rate = sample_rate; + self->synth.waveform = waveform; + self->synth.waveform_length = waveform_length; + synthio_synth_init(&self->synth, SYNTHIO_MAX_DUR); + common_hal_synthio_synthesizer_release_all(self); +} + +void common_hal_synthio_synthesizer_deinit(synthio_synthesizer_obj_t *self) { + synthio_synth_deinit(&self->synth); +} +bool common_hal_synthio_synthesizer_deinited(synthio_synthesizer_obj_t *self) { + return synthio_synth_deinited(&self->synth); +} + +uint32_t common_hal_synthio_synthesizer_get_sample_rate(synthio_synthesizer_obj_t *self) { + return self->synth.sample_rate; +} +uint8_t common_hal_synthio_synthesizer_get_bits_per_sample(synthio_synthesizer_obj_t *self) { + return SYNTHIO_BITS_PER_SAMPLE; +} +uint8_t common_hal_synthio_synthesizer_get_channel_count(synthio_synthesizer_obj_t *self) { + return 1; +} + +void synthio_synthesizer_reset_buffer(synthio_synthesizer_obj_t *self, + bool single_channel_output, uint8_t channel) { + synthio_synth_reset_buffer(&self->synth, single_channel_output, channel); +} + +audioio_get_buffer_result_t synthio_synthesizer_get_buffer(synthio_synthesizer_obj_t *self, + bool single_channel_output, uint8_t channel, uint8_t **buffer, uint32_t *buffer_length) { + self->synth.span.dur = SYNTHIO_MAX_DUR; + synthio_synth_synthesize(&self->synth, buffer, buffer_length, single_channel_output ? channel : 0); + return GET_BUFFER_MORE_DATA; +} + +void synthio_synthesizer_get_buffer_structure(synthio_synthesizer_obj_t *self, bool single_channel_output, + bool *single_buffer, bool *samples_signed, uint32_t *max_buffer_length, uint8_t *spacing) { + return synthio_synth_get_buffer_structure(&self->synth, single_channel_output, single_buffer, samples_signed, max_buffer_length, spacing); +} + +void common_hal_synthio_synthesizer_release_all(synthio_synthesizer_obj_t *self) { + synthio_span_init(&self->synth.span); +} +void common_hal_synthio_synthesizer_release(synthio_synthesizer_obj_t *self, mp_obj_t to_release) { + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(to_release, &iter_buf); + mp_obj_t item; + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + synthio_span_change_note(&self->synth.span, mp_arg_validate_int_range(mp_obj_get_int(item), 0, 127, MP_QSTR_note), SYNTHIO_SILENCE); + } +} + +void common_hal_synthio_synthesizer_press(synthio_synthesizer_obj_t *self, mp_obj_t to_press) { + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(to_press, &iter_buf); + mp_obj_t item; + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + synthio_span_change_note(&self->synth.span, SYNTHIO_SILENCE, mp_arg_validate_int_range(mp_obj_get_int(item), 0, 127, MP_QSTR_note)); + } +} + +mp_obj_t common_hal_synthio_synthesizer_get_pressed_notes(synthio_synthesizer_obj_t *self) { + mp_obj_tuple_t *result = MP_OBJ_TO_PTR(mp_obj_new_tuple(synthio_span_count_active_channels(&self->synth.span), NULL)); + for (size_t i = 0, j = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) { + if (self->synth.span.note[i] != SYNTHIO_SILENCE) { + result->items[j++] = MP_OBJ_NEW_SMALL_INT(self->synth.span.note[i]); + } + } + return MP_OBJ_FROM_PTR(result); +} diff --git a/shared-module/synthio/Synthesizer.h b/shared-module/synthio/Synthesizer.h new file mode 100644 index 0000000000..256b6137f2 --- /dev/null +++ b/shared-module/synthio/Synthesizer.h @@ -0,0 +1,53 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Artyom Skrobov + * Copyright (c) 2023 Jeff Epler for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include "py/obj.h" + +#include "shared-module/synthio/__init__.h" + +typedef struct { + mp_obj_base_t base; + synthio_synth_t synth; +} synthio_synthesizer_obj_t; + + +// These are not available from Python because it may be called in an interrupt. +void synthio_synthesizer_reset_buffer(synthio_synthesizer_obj_t *self, + bool single_channel_output, + uint8_t channel); + +audioio_get_buffer_result_t synthio_synthesizer_get_buffer(synthio_synthesizer_obj_t *self, + bool single_channel_output, + uint8_t channel, + uint8_t **buffer, + uint32_t *buffer_length); // length in bytes + +void synthio_synthesizer_get_buffer_structure(synthio_synthesizer_obj_t *self, bool single_channel_output, + bool *single_buffer, bool *samples_signed, + uint32_t *max_buffer_length, uint8_t *spacing); diff --git a/shared-module/synthio/__init__.c b/shared-module/synthio/__init__.c index e69de29bb2..7ac8e47746 100644 --- a/shared-module/synthio/__init__.c +++ b/shared-module/synthio/__init__.c @@ -0,0 +1,169 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Artyom Skrobov + * Copyright (c) 2023 Jeff Epler for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "shared-module/synthio/__init__.h" +#include "py/runtime.h" + +STATIC const int16_t square_wave[] = {-32768, 32767}; + +STATIC const uint16_t notes[] = {8372, 8870, 9397, 9956, 10548, 11175, 11840, + 12544, 13290, 14080, 14917, 15804}; // 9th octave + +int synthio_span_count_active_channels(synthio_midi_span_t *span) { + int result = 0; + for (int i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) { + if (span->note[i] != SYNTHIO_SILENCE) { + result += 1; + } + } + return result; +} + + +void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t *buffer_length, uint8_t channel) { + + if (channel == synth->other_channel) { + *buffer_length = synth->last_buffer_length; + *bufptr = (uint8_t *)(synth->buffers[synth->other_buffer_index] + channel); + return; + } + + synth->buffer_index = !synth->buffer_index; + synth->other_channel = 1 - channel; + synth->other_buffer_index = synth->buffer_index; + int16_t *out_buffer = (int16_t *)(void *)synth->buffers[synth->buffer_index]; + + uint16_t dur = MIN(SYNTHIO_MAX_DUR, synth->span.dur); + synth->span.dur -= dur; + memset(out_buffer, 0, synth->buffer_length); + + int32_t sample_rate = synth->sample_rate; + int active_channels = synthio_span_count_active_channels(&synth->span); + const int16_t *waveform = synth->waveform; + uint32_t waveform_length = synth->waveform_length; + if (active_channels) { + int16_t loudness = 0x3fff / (1 + active_channels); + for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) { + if (synth->span.note[chan] == SYNTHIO_SILENCE) { + synth->accum[chan] = 0; + continue; + } + uint8_t octave = synth->span.note[chan] / 12; + uint16_t base_freq = notes[synth->span.note[chan] % 12]; + uint32_t accum = synth->accum[chan]; +#define SHIFT (16) + // rate = base_freq * waveform_length + // den = sample_rate * 2 ^ (10 - octave) + // den = sample_rate * 2 ^ 10 / 2^octave + // dds_rate = 2^SHIFT * rate / den + // dds_rate = 2^(SHIFT-10+octave) * base_freq * waveform_length / sample_rate + uint32_t dds_rate = (sample_rate / 2 + ((uint64_t)(base_freq * waveform_length) << (SHIFT - 10 + octave))) / sample_rate; + + for (uint16_t i = 0; i < dur; i++) { + accum += dds_rate; + if (accum > waveform_length << SHIFT) { + accum -= waveform_length << SHIFT; + } + int16_t idx = accum >> SHIFT; + out_buffer[i] += (waveform[idx] * loudness) / 65536; + } + synth->accum[chan] = accum; + } + } + + *buffer_length = synth->last_buffer_length = dur * SYNTHIO_BYTES_PER_SAMPLE; + *bufptr = (uint8_t *)out_buffer; +} + +void synthio_synth_reset_buffer(synthio_synth_t *synth, bool single_channel_output, uint8_t channel) { + if (single_channel_output && channel == 1) { + return; + } + synth->other_channel = -1; +} + +bool synthio_synth_deinited(synthio_synth_t *synth) { + return synth->buffers[0] == NULL; +} + +void synthio_synth_deinit(synthio_synth_t *synth) { + m_del(uint8_t, synth->buffers[0], synth->buffer_length); + m_del(uint8_t, synth->buffers[1], synth->buffer_length); + synth->buffers[0] = NULL; + synth->buffers[1] = NULL; +} + +void synthio_synth_init(synthio_synth_t *synth, uint16_t max_dur) { + synth->buffer_length = MIN(SYNTHIO_MAX_DUR, max_dur) * SYNTHIO_BYTES_PER_SAMPLE; + synth->buffers[0] = m_malloc(synth->buffer_length, false); + synth->buffers[1] = m_malloc(synth->buffer_length, false); + synth->other_channel = -1; +} + +void synthio_synth_get_buffer_structure(synthio_synth_t *synth, bool single_channel_output, + bool *single_buffer, bool *samples_signed, uint32_t *max_buffer_length, uint8_t *spacing) { + *single_buffer = false; + *samples_signed = true; + *max_buffer_length = synth->buffer_length; + *spacing = 1; +} + +void synthio_synth_parse_waveform(mp_buffer_info_t *bufinfo_waveform, mp_obj_t waveform_obj) { + *bufinfo_waveform = ((mp_buffer_info_t) { .buf = (void *)square_wave, .len = 4 }); + + if (waveform_obj != mp_const_none) { + mp_get_buffer_raise(waveform_obj, bufinfo_waveform, MP_BUFFER_READ); + if (bufinfo_waveform->typecode != 'h') { + mp_raise_ValueError_varg(translate("%q must be array of type 'h'"), MP_QSTR_waveform); + } + } + mp_arg_validate_length_range(bufinfo_waveform->len / 2, 2, 1024, MP_QSTR_waveform); +} + +void synthio_span_init(synthio_midi_span_t *span) { + span->dur = 0; + for (size_t i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) { span->note[i] = SYNTHIO_SILENCE; + } +} + +STATIC int find_channel_with_note(const synthio_midi_span_t *span, uint8_t note) { + for (int i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) { + if (span->note[i] == note) { + return i; + } + } + return -1; +} + +bool synthio_span_change_note(synthio_midi_span_t *span, uint8_t old_note, uint8_t new_note) { + int channel = find_channel_with_note(span, old_note); + if (channel != -1) { + span->note[channel] = new_note; + return true; + } + return false; +} diff --git a/shared-module/synthio/__init__.h b/shared-module/synthio/__init__.h index d9d98a5341..e14e5ead10 100644 --- a/shared-module/synthio/__init__.h +++ b/shared-module/synthio/__init__.h @@ -24,9 +24,41 @@ * THE SOFTWARE. */ -#ifndef MICROPY_INCLUDED_SHARED_MODULE_SYNTHIO__INIT__H -#define MICROPY_INCLUDED_SHARED_MODULE_SYNTHIO__INIT__H +#pragma once + +#define SYNTHIO_BITS_PER_SAMPLE (16) +#define SYNTHIO_BYTES_PER_SAMPLE (SYNTHIO_BITS_PER_SAMPLE / 8) +#define SYNTHIO_MAX_DUR (256) +#define SYNTHIO_SILENCE (0x80) #include "shared-module/audiocore/__init__.h" -#endif // MICROPY_INCLUDED_SHARED_MODULE_SYNTHIO__INIT__H +typedef struct { + uint16_t dur; + uint8_t note[CIRCUITPY_SYNTHIO_MAX_CHANNELS]; +} synthio_midi_span_t; + +typedef struct { + uint32_t sample_rate; + int16_t *buffers[2]; + const int16_t *waveform; + uint16_t buffer_length; + uint16_t last_buffer_length; + uint8_t other_channel, buffer_index, other_buffer_index; + uint16_t waveform_length; + synthio_midi_span_t span; + uint32_t accum[CIRCUITPY_SYNTHIO_MAX_CHANNELS]; +} synthio_synth_t; + +void synthio_span_init(synthio_midi_span_t *span); +void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **buffer, uint32_t *buffer_length, uint8_t channel); +void synthio_synth_deinit(synthio_synth_t *synth); +bool synthio_synth_deinited(synthio_synth_t *synth); +void synthio_synth_init(synthio_synth_t *synth, uint16_t max_dur); +void synthio_synth_get_buffer_structure(synthio_synth_t *synth, bool single_channel_output, + bool *single_buffer, bool *samples_signed, uint32_t *max_buffer_length, uint8_t *spacing); +void synthio_synth_reset_buffer(synthio_synth_t *synth, bool single_channel_output, uint8_t channel); +void synthio_synth_parse_waveform(mp_buffer_info_t *bufinfo_waveform, mp_obj_t waveform_obj); + +bool synthio_span_change_note(synthio_midi_span_t *span, uint8_t old_note, uint8_t new_note); +int synthio_span_count_active_channels(synthio_midi_span_t *span); diff --git a/tests/circuitpython/miditrack.py b/tests/circuitpython/miditrack.py new file mode 100644 index 0000000000..53a8ca631d --- /dev/null +++ b/tests/circuitpython/miditrack.py @@ -0,0 +1,20 @@ +import array + +try: + from synthio import MidiTrack + from audiocore import get_buffer, get_structure +except ImportError: + print("SKIP") + raise SystemExit + +SCORE = b"\0\x90@\0\x20\x90b\0\x20\x80@\0\0\x80\b\0" + +with MidiTrack(SCORE, sample_rate=8000, tempo=640) as m: + print(get_structure(m)) + print(get_buffer(m)) + +with MidiTrack( + SCORE, sample_rate=8000, tempo=640, waveform=array.array("h", [0, 32767, 0, -32768]) +) as m: + print(get_structure(m)) + print(get_buffer(m)) diff --git a/tests/circuitpython/miditrack.py.exp b/tests/circuitpython/miditrack.py.exp new file mode 100644 index 0000000000..be5eef1d0b --- /dev/null +++ b/tests/circuitpython/miditrack.py.exp @@ -0,0 +1,4 @@ +(0, 1, 512, 1) +(1, b'\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f') +(0, 1, 512, 1) +(1, b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00') diff --git a/tests/circuitpython/synthesizer.py b/tests/circuitpython/synthesizer.py new file mode 100644 index 0000000000..bdb273f861 --- /dev/null +++ b/tests/circuitpython/synthesizer.py @@ -0,0 +1,24 @@ +import struct +import synthio +import audiocore + + +def dump_samples(): + print(struct.unpack("12h", audiocore.get_buffer(s)[1][:24])) + + +s = synthio.Synthesizer(sample_rate=8000) +print(s.pressed) +dump_samples() + +s.press((80,)) +print(s.pressed) +dump_samples() + +s.press((91,)) +print(s.pressed) +dump_samples() + +s.release_then_press((80,)) +print(s.pressed) +dump_samples() diff --git a/tests/circuitpython/synthesizer.py.exp b/tests/circuitpython/synthesizer.py.exp new file mode 100644 index 0000000000..4296662d60 --- /dev/null +++ b/tests/circuitpython/synthesizer.py.exp @@ -0,0 +1,8 @@ +() +(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) +(80,) +(-4095, -4095, -4095, -4095, 4095, 4095, 4095, 4095, 4095, -4095, -4095, -4095) +(80, 91) +(0, 0, 5460, 5460, 0, -5460, -5460, 0, 5460, 5460, 0, 0) +(91,) +(-4095, 4095, 4095, 4095, -4095, -4095, 4095, 4095, 4095, -4095, -4095, 4095) diff --git a/tests/unix/extra_coverage.py.exp b/tests/unix/extra_coverage.py.exp index 8014dd3010..c750ca0e95 100644 --- a/tests/unix/extra_coverage.py.exp +++ b/tests/unix/extra_coverage.py.exp @@ -30,20 +30,21 @@ ame__ mport builtins micropython _asyncio _thread -_uasyncio aesio array binascii -bitmaptools btree cexample cmath -collections cppexample displayio errno -ffi framebuf gc hashlib -json math qrio rainbowio -re struct sys termios -traceback ubinascii uctypes uerrno -uheapq uio ujson ulab -ulab.numpy ulab.numpy.fft ulab.numpy.linalg -ulab.scipy ulab.scipy.linalg -ulab.scipy.optimize ulab.scipy.signal -ulab.scipy.special ulab.utils uos -urandom ure uselect utime -utimeq uzlib zlib +_uasyncio aesio array audiocore +audiomixer binascii bitmaptools btree +cexample cmath collections cppexample +displayio errno ffi framebuf +gc hashlib json math +qrio rainbowio re struct +synthio sys termios traceback +ubinascii uctypes uerrno uheapq +uio ujson ulab ulab.numpy +ulab.numpy.fft ulab.numpy.linalg ulab.scipy +ulab.scipy.linalg ulab.scipy.optimize +ulab.scipy.signal ulab.scipy.special +ulab.utils uos urandom ure +uselect utime utimeq uzlib +zlib ime utime utimeq