From 346f08f8b9aa7a71e41096339018197f3d649eee Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 20 Jul 2023 13:16:00 -0500 Subject: [PATCH 1/5] synthio: Add Synthesizer.note_state This enables the specific use case of checking whether a note's release phase has ended, but is also potentially useful to implement a sort of "voice stealing" algorithm in Python code, which can take account of the note's envelope state as well as other factors specific to the program. --- shared-bindings/synthio/Synthesizer.c | 23 +++++++++++++++++++- shared-bindings/synthio/Synthesizer.h | 1 + shared-bindings/synthio/__init__.c | 17 +++++++++++++++ shared-bindings/synthio/__init__.h | 6 +++++ shared-module/synthio/Synthesizer.c | 11 ++++++++++ shared-module/synthio/__init__.h | 6 +---- tests/circuitpython/synthio_note_info.py | 17 +++++++++++++++ tests/circuitpython/synthio_note_info.py.exp | 23 ++++++++++++++++++++ 8 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 tests/circuitpython/synthio_note_info.py create mode 100644 tests/circuitpython/synthio_note_info.py.exp diff --git a/shared-bindings/synthio/Synthesizer.c b/shared-bindings/synthio/Synthesizer.c index 8e186573cd..148e773b6b 100644 --- a/shared-bindings/synthio/Synthesizer.c +++ b/shared-bindings/synthio/Synthesizer.c @@ -31,6 +31,7 @@ #include "py/binary.h" #include "py/objproperty.h" #include "py/runtime.h" +#include "py/enum.h" #include "shared-bindings/util.h" #include "shared-bindings/synthio/Biquad.h" #include "shared-bindings/synthio/Synthesizer.h" @@ -256,7 +257,9 @@ MP_PROPERTY_GETTER(synthio_synthesizer_sample_rate_obj, (mp_obj_t)&synthio_synthesizer_get_sample_rate_obj); //| pressed: NoteSequence -//| """A sequence of the currently pressed notes (read-only property)""" +//| """A sequence of the currently pressed notes (read-only property). +//| +//| This does not include notes in the release phase of the envelope.""" //| 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); @@ -268,6 +271,23 @@ MP_DEFINE_CONST_FUN_OBJ_1(synthio_synthesizer_get_pressed_obj, synthio_synthesiz MP_PROPERTY_GETTER(synthio_synthesizer_pressed_obj, (mp_obj_t)&synthio_synthesizer_get_pressed_obj); +//| def note_info(note: Note) -> Tuple[Optional[EnvelopeState], float]: +//| """Get info about a note's current envelope state +//| +//| If the note is currently playing (including in the release phase), the returned value gives the current envelope state and the current envelope value. +//| +//| If the note is not playing on this synthesizer, returns the tuple ``(None, 0.0)``.""" +STATIC mp_obj_t synthio_synthesizer_obj_note_info(mp_obj_t self_in, mp_obj_t note) { + synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + mp_float_t vol = MICROPY_FLOAT_CONST(0.0); + envelope_state_e state = common_hal_synthio_synthesizer_note_info(self, note, &vol); + return MP_OBJ_NEW_TUPLE( + cp_enum_find(&synthio_note_state_type, state), + mp_obj_new_float(vol)); +} +MP_DEFINE_CONST_FUN_OBJ_2(synthio_synthesizer_note_info_obj, synthio_synthesizer_obj_note_info); + //| blocks: List[BlockInput] //| """A list of blocks to advance whether or not they are associated with a playing note. //| @@ -417,6 +437,7 @@ STATIC const mp_rom_map_elem_t synthio_synthesizer_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_sample_rate), MP_ROM_PTR(&synthio_synthesizer_sample_rate_obj) }, { MP_ROM_QSTR(MP_QSTR_max_polyphony), MP_ROM_INT(CIRCUITPY_SYNTHIO_MAX_CHANNELS) }, { MP_ROM_QSTR(MP_QSTR_pressed), MP_ROM_PTR(&synthio_synthesizer_pressed_obj) }, + { MP_ROM_QSTR(MP_QSTR_note_info), MP_ROM_PTR(&synthio_synthesizer_note_info_obj) }, { MP_ROM_QSTR(MP_QSTR_blocks), MP_ROM_PTR(&synthio_synthesizer_blocks_obj) }, }; STATIC MP_DEFINE_CONST_DICT(synthio_synthesizer_locals_dict, synthio_synthesizer_locals_dict_table); diff --git a/shared-bindings/synthio/Synthesizer.h b/shared-bindings/synthio/Synthesizer.h index 16543ce2c3..8ae0af3bf3 100644 --- a/shared-bindings/synthio/Synthesizer.h +++ b/shared-bindings/synthio/Synthesizer.h @@ -45,3 +45,4 @@ void common_hal_synthio_synthesizer_retrigger(synthio_synthesizer_obj_t *self, m 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); mp_obj_t common_hal_synthio_synthesizer_get_blocks(synthio_synthesizer_obj_t *self); +envelope_state_e common_hal_synthio_synthesizer_note_info(synthio_synthesizer_obj_t *self, mp_obj_t note, mp_float_t *vol_out); diff --git a/shared-bindings/synthio/__init__.c b/shared-bindings/synthio/__init__.c index b295430b09..8eba79d2a0 100644 --- a/shared-bindings/synthio/__init__.c +++ b/shared-bindings/synthio/__init__.c @@ -45,6 +45,22 @@ #include "shared-module/synthio/LFO.h" +MAKE_ENUM_VALUE(synthio_note_state_type, note_state, ATTACK, SYNTHIO_ENVELOPE_STATE_ATTACK); +MAKE_ENUM_VALUE(synthio_note_state_type, note_state, DECAY, SYNTHIO_ENVELOPE_STATE_DECAY); +MAKE_ENUM_VALUE(synthio_note_state_type, note_state, SUSTAIN, SYNTHIO_ENVELOPE_STATE_SUSTAIN); +MAKE_ENUM_VALUE(synthio_note_state_type, note_state, RELEASE, SYNTHIO_ENVELOPE_STATE_RELEASE); + +MAKE_ENUM_MAP(synthio_note_state) { + MAKE_ENUM_MAP_ENTRY(note_state, ATTACK), + MAKE_ENUM_MAP_ENTRY(note_state, DECAY), + MAKE_ENUM_MAP_ENTRY(note_state, SUSTAIN), + MAKE_ENUM_MAP_ENTRY(note_state, RELEASE), +}; + +STATIC MP_DEFINE_CONST_DICT(synthio_note_state_locals_dict, synthio_note_state_locals_table); +MAKE_PRINTER(synthio, synthio_note_state); +MAKE_ENUM_TYPE(synthio, NoteState, synthio_note_state); + #define default_attack_time (MICROPY_FLOAT_CONST(0.1)) #define default_decay_time (MICROPY_FLOAT_CONST(0.05)) #define default_release_time (MICROPY_FLOAT_CONST(0.2)) @@ -316,6 +332,7 @@ STATIC const mp_rom_map_elem_t synthio_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_MathOperation), MP_ROM_PTR(&synthio_math_operation_type) }, { MP_ROM_QSTR(MP_QSTR_MidiTrack), MP_ROM_PTR(&synthio_miditrack_type) }, { MP_ROM_QSTR(MP_QSTR_Note), MP_ROM_PTR(&synthio_note_type) }, + { MP_ROM_QSTR(MP_QSTR_NoteState), MP_ROM_PTR(&synthio_note_state_type) }, { MP_ROM_QSTR(MP_QSTR_LFO), MP_ROM_PTR(&synthio_lfo_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 2315d92e58..4a44277e85 100644 --- a/shared-bindings/synthio/__init__.h +++ b/shared-bindings/synthio/__init__.h @@ -29,10 +29,16 @@ #include "py/objnamedtuple.h" #include "py/enum.h" +typedef enum { + SYNTHIO_ENVELOPE_STATE_ATTACK, SYNTHIO_ENVELOPE_STATE_DECAY, + SYNTHIO_ENVELOPE_STATE_SUSTAIN, SYNTHIO_ENVELOPE_STATE_RELEASE +} envelope_state_e; + typedef enum synthio_bend_mode_e { SYNTHIO_BEND_MODE_STATIC, SYNTHIO_BEND_MODE_VIBRATO, SYNTHIO_BEND_MODE_SWEEP, SYNTHIO_BEND_MODE_SWEEP_IN } synthio_bend_mode_t; +extern const mp_obj_type_t synthio_note_state_type; extern const cp_enum_obj_t bend_mode_VIBRATO_obj; extern const mp_obj_type_t synthio_bend_mode_type; typedef struct synthio_synth synthio_synth_t; diff --git a/shared-module/synthio/Synthesizer.c b/shared-module/synthio/Synthesizer.c index 7dc888dfd0..672ac5b9e6 100644 --- a/shared-module/synthio/Synthesizer.c +++ b/shared-module/synthio/Synthesizer.c @@ -185,6 +185,17 @@ mp_obj_t common_hal_synthio_synthesizer_get_pressed_notes(synthio_synthesizer_ob return MP_OBJ_FROM_PTR(result); } +envelope_state_e common_hal_synthio_synthesizer_note_info(synthio_synthesizer_obj_t *self, mp_obj_t note, mp_float_t *vol_out) { + for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) { + if (self->synth.span.note_obj[chan] == note) { + *vol_out = self->synth.envelope_state[chan].level / 32767.; + return self->synth.envelope_state[chan].state; + } + } + return (envelope_state_e) - 1; +} + + mp_obj_t common_hal_synthio_synthesizer_get_blocks(synthio_synthesizer_obj_t *self) { return self->blocks; } diff --git a/shared-module/synthio/__init__.h b/shared-module/synthio/__init__.h index 9e4db96d50..178e4fb866 100644 --- a/shared-module/synthio/__init__.h +++ b/shared-module/synthio/__init__.h @@ -35,6 +35,7 @@ #define SYNTHIO_FREQUENCY_SHIFT (16) #include "shared-module/audiocore/__init__.h" +#include "shared-bindings/synthio/__init__.h" typedef struct { uint16_t dur; @@ -49,11 +50,6 @@ typedef struct { uint16_t attack_level, sustain_level; } synthio_envelope_definition_t; -typedef enum { - SYNTHIO_ENVELOPE_STATE_ATTACK, SYNTHIO_ENVELOPE_STATE_DECAY, - SYNTHIO_ENVELOPE_STATE_SUSTAIN, SYNTHIO_ENVELOPE_STATE_RELEASE -} envelope_state_e; - typedef struct { int16_t level; uint16_t substep; diff --git a/tests/circuitpython/synthio_note_info.py b/tests/circuitpython/synthio_note_info.py new file mode 100644 index 0000000000..4d4e7a59ae --- /dev/null +++ b/tests/circuitpython/synthio_note_info.py @@ -0,0 +1,17 @@ +from synthio import Synthesizer, Note, Envelope +from audiocore import get_buffer + +s = Synthesizer() +n = Note(440, envelope=Envelope()) +print("{} {:.2f}".format(*s.note_info(n))) +s.press(n) +print("press") +for _ in range(9): + print("{} {:.2f}".format(*s.note_info(n))) + get_buffer(s) + +s.release(n) +print("release") +for _ in range(11): + print("{} {:.2f}".format(*s.note_info(n))) + get_buffer(s) diff --git a/tests/circuitpython/synthio_note_info.py.exp b/tests/circuitpython/synthio_note_info.py.exp new file mode 100644 index 0000000000..a69fb3c8c0 --- /dev/null +++ b/tests/circuitpython/synthio_note_info.py.exp @@ -0,0 +1,23 @@ +None 0.00 +press +synthio.NoteState.ATTACK 0.23 +synthio.NoteState.ATTACK 0.46 +synthio.NoteState.ATTACK 0.70 +synthio.NoteState.ATTACK 0.93 +synthio.NoteState.DECAY 1.00 +synthio.NoteState.DECAY 0.91 +synthio.NoteState.DECAY 0.81 +synthio.NoteState.SUSTAIN 0.80 +synthio.NoteState.SUSTAIN 0.80 +release +synthio.NoteState.RELEASE 0.80 +synthio.NoteState.RELEASE 0.71 +synthio.NoteState.RELEASE 0.61 +synthio.NoteState.RELEASE 0.52 +synthio.NoteState.RELEASE 0.43 +synthio.NoteState.RELEASE 0.34 +synthio.NoteState.RELEASE 0.24 +synthio.NoteState.RELEASE 0.15 +synthio.NoteState.RELEASE 0.06 +synthio.NoteState.RELEASE 0.00 +None 0.00 From 29a4364ba77e11348a7958296ea55ebfc7b69826 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 20 Jul 2023 14:05:56 -0500 Subject: [PATCH 2/5] fix method signature --- shared-bindings/synthio/Synthesizer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared-bindings/synthio/Synthesizer.c b/shared-bindings/synthio/Synthesizer.c index 148e773b6b..25d26fc661 100644 --- a/shared-bindings/synthio/Synthesizer.c +++ b/shared-bindings/synthio/Synthesizer.c @@ -271,7 +271,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(synthio_synthesizer_get_pressed_obj, synthio_synthesiz MP_PROPERTY_GETTER(synthio_synthesizer_pressed_obj, (mp_obj_t)&synthio_synthesizer_get_pressed_obj); -//| def note_info(note: Note) -> Tuple[Optional[EnvelopeState], float]: +//| def note_info(self, note: Note) -> Tuple[Optional[EnvelopeState], float]: //| """Get info about a note's current envelope state //| //| If the note is currently playing (including in the release phase), the returned value gives the current envelope state and the current envelope value. From f71831dea93b9e84a8d476025851cea46a71d29f Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 20 Jul 2023 14:10:44 -0500 Subject: [PATCH 3/5] skip all the byecode hex data, it's adequately checked ... by the disassembly just below This was tripped up because in exactly the right conditions some qstr could be of the form 'xx 63' and make the expression `\.\+63` match something other than what was intended. This test was re-worked upstream for mpy version 6 so it'll be a conflict to resolve when we get to that. :-/ --- tests/cmdline/cmd_showbc.py.exp | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/cmdline/cmd_showbc.py.exp b/tests/cmdline/cmd_showbc.py.exp index 22712b79ee..c3dbfd74f8 100644 --- a/tests/cmdline/cmd_showbc.py.exp +++ b/tests/cmdline/cmd_showbc.py.exp @@ -1,7 +1,6 @@ File cmdline/cmd_showbc.py, code block '' (descriptor: \.\+, bytecode @\.\+ bytes) Raw bytecode (code_info_size=\\d\+, bytecode_size=\\d\+): ######## -\.\+63 arg names: (N_STATE 3) (N_EXC_STACK 0) From 70cf0610cc397b0dd71ff64d259a246822f0cf56 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 20 Jul 2023 14:18:03 -0500 Subject: [PATCH 4/5] disable synthio on this board, it's very full --- ports/atmel-samd/boards/mini_sam_m4/mpconfigboard.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/atmel-samd/boards/mini_sam_m4/mpconfigboard.mk b/ports/atmel-samd/boards/mini_sam_m4/mpconfigboard.mk index c3e21a4c36..fcbbbd5bf9 100644 --- a/ports/atmel-samd/boards/mini_sam_m4/mpconfigboard.mk +++ b/ports/atmel-samd/boards/mini_sam_m4/mpconfigboard.mk @@ -12,6 +12,7 @@ LONGINT_IMPL = MPZ # No I2S on SAMD51G CIRCUITPY_AUDIOBUSIO = 0 +CIRCUITPY_SYNTHIO = 0 CIRCUITPY_BITBANG_APA102 = 1 From 60b233f160e0ee30f1abf412c0798bce50e7425c Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 20 Jul 2023 16:25:23 -0500 Subject: [PATCH 5/5] document EnvelopeState --- shared-bindings/synthio/__init__.c | 14 ++++++-- tests/circuitpython/synthio_note_info.py.exp | 38 ++++++++++---------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/shared-bindings/synthio/__init__.c b/shared-bindings/synthio/__init__.c index 8eba79d2a0..52d30cd2c4 100644 --- a/shared-bindings/synthio/__init__.c +++ b/shared-bindings/synthio/__init__.c @@ -45,6 +45,16 @@ #include "shared-module/synthio/LFO.h" +//| class EnvelopeState: +//| ATTACK: EnvelopeState +//| """The note is in its attack phase""" +//| DECAY: EnvelopeState +//| """The note is in its decay phase""" +//| SUSTAIN: EnvelopeState +//| """The note is in its sustain phase""" +//| RELEASE: EnvelopeState +//| """The note is in its release phase""" +//| MAKE_ENUM_VALUE(synthio_note_state_type, note_state, ATTACK, SYNTHIO_ENVELOPE_STATE_ATTACK); MAKE_ENUM_VALUE(synthio_note_state_type, note_state, DECAY, SYNTHIO_ENVELOPE_STATE_DECAY); MAKE_ENUM_VALUE(synthio_note_state_type, note_state, SUSTAIN, SYNTHIO_ENVELOPE_STATE_SUSTAIN); @@ -59,7 +69,7 @@ MAKE_ENUM_MAP(synthio_note_state) { STATIC MP_DEFINE_CONST_DICT(synthio_note_state_locals_dict, synthio_note_state_locals_table); MAKE_PRINTER(synthio, synthio_note_state); -MAKE_ENUM_TYPE(synthio, NoteState, synthio_note_state); +MAKE_ENUM_TYPE(synthio, EnvelopeState, synthio_note_state); #define default_attack_time (MICROPY_FLOAT_CONST(0.1)) #define default_decay_time (MICROPY_FLOAT_CONST(0.05)) @@ -332,7 +342,7 @@ STATIC const mp_rom_map_elem_t synthio_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_MathOperation), MP_ROM_PTR(&synthio_math_operation_type) }, { MP_ROM_QSTR(MP_QSTR_MidiTrack), MP_ROM_PTR(&synthio_miditrack_type) }, { MP_ROM_QSTR(MP_QSTR_Note), MP_ROM_PTR(&synthio_note_type) }, - { MP_ROM_QSTR(MP_QSTR_NoteState), MP_ROM_PTR(&synthio_note_state_type) }, + { MP_ROM_QSTR(MP_QSTR_EnvelopeState), MP_ROM_PTR(&synthio_note_state_type) }, { MP_ROM_QSTR(MP_QSTR_LFO), MP_ROM_PTR(&synthio_lfo_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/tests/circuitpython/synthio_note_info.py.exp b/tests/circuitpython/synthio_note_info.py.exp index a69fb3c8c0..cd75174a11 100644 --- a/tests/circuitpython/synthio_note_info.py.exp +++ b/tests/circuitpython/synthio_note_info.py.exp @@ -1,23 +1,23 @@ None 0.00 press -synthio.NoteState.ATTACK 0.23 -synthio.NoteState.ATTACK 0.46 -synthio.NoteState.ATTACK 0.70 -synthio.NoteState.ATTACK 0.93 -synthio.NoteState.DECAY 1.00 -synthio.NoteState.DECAY 0.91 -synthio.NoteState.DECAY 0.81 -synthio.NoteState.SUSTAIN 0.80 -synthio.NoteState.SUSTAIN 0.80 +synthio.EnvelopeState.ATTACK 0.23 +synthio.EnvelopeState.ATTACK 0.46 +synthio.EnvelopeState.ATTACK 0.70 +synthio.EnvelopeState.ATTACK 0.93 +synthio.EnvelopeState.DECAY 1.00 +synthio.EnvelopeState.DECAY 0.91 +synthio.EnvelopeState.DECAY 0.81 +synthio.EnvelopeState.SUSTAIN 0.80 +synthio.EnvelopeState.SUSTAIN 0.80 release -synthio.NoteState.RELEASE 0.80 -synthio.NoteState.RELEASE 0.71 -synthio.NoteState.RELEASE 0.61 -synthio.NoteState.RELEASE 0.52 -synthio.NoteState.RELEASE 0.43 -synthio.NoteState.RELEASE 0.34 -synthio.NoteState.RELEASE 0.24 -synthio.NoteState.RELEASE 0.15 -synthio.NoteState.RELEASE 0.06 -synthio.NoteState.RELEASE 0.00 +synthio.EnvelopeState.RELEASE 0.80 +synthio.EnvelopeState.RELEASE 0.71 +synthio.EnvelopeState.RELEASE 0.61 +synthio.EnvelopeState.RELEASE 0.52 +synthio.EnvelopeState.RELEASE 0.43 +synthio.EnvelopeState.RELEASE 0.34 +synthio.EnvelopeState.RELEASE 0.24 +synthio.EnvelopeState.RELEASE 0.15 +synthio.EnvelopeState.RELEASE 0.06 +synthio.EnvelopeState.RELEASE 0.00 None 0.00