synthio: add Synthesizer.lfo, retrigger option

This commit is contained in:
Jeff Epler 2023-05-16 10:07:07 -05:00
parent f83212314e
commit b2c6f9b1a4
No known key found for this signature in database
GPG Key ID: D5BF15AB975AB4DE
4 changed files with 65 additions and 7 deletions

View File

@ -33,6 +33,7 @@
#include "py/runtime.h"
#include "shared-bindings/util.h"
#include "shared-bindings/synthio/Synthesizer.h"
#include "shared-bindings/synthio/LFO.h"
#include "shared-bindings/synthio/__init__.h"
#include "supervisor/shared/translate/translate.h"
@ -120,23 +121,29 @@ STATIC mp_obj_t synthio_synthesizer_release(mp_obj_t self_in, mp_obj_t release)
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(synthio_synthesizer_release_obj, synthio_synthesizer_release);
//| def release_then_press(self, release: NoteSequence = (), press: NoteSequence = ()) -> None:
//| def release_then_press(
//| self, release: NoteSequence = (), press: NoteSequence = (), retrigger=LFOSequence
//| ) -> None:
//| """Turn some notes on and/or off.
//|
//| It is OK to release note that was not actually turned on.
//|
//| Pressing a note that was already pressed has no effect.
//| Pressing a note that was already pressed returns it to the attack phase
//| but without resetting its amplitude. Releasing a note and immediately
//| pressing it again returns it to the attack phase with an initial
//| amplitude of 0.
//|
//| Releasing and pressing the note again has little effect, but does reset the phase
//| of the note, which may be perceptible as a small glitch.
//| At the same time, the passed LFOs (if any) are retriggered.
//|
//| :param NoteSequence release: Any sequence of notes.
//| :param NoteSequence press: Any sequence of notes."""
//| :param NoteSequence press: Any sequence of notes.
//| :param LFOSequence retrigger: Any sequence of LFOs."""
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 };
enum { ARG_release, ARG_press, ARG_retrigger };
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_QSTR_retrigger, MP_ARG_OBJ, {.u_obj = mp_const_empty_tuple } },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
@ -146,6 +153,7 @@ STATIC mp_obj_t synthio_synthesizer_release_then_press(mp_uint_t n_args, const m
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);
common_hal_synthio_synthesizer_retrigger(self, args[ARG_retrigger].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);
@ -250,6 +258,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);
//| lfos: List[LFO]
//| """A list of LFOs to advance whether or not they are associated with a playing note.
//|
//| This can be used to implement 'free-running' LFOs. LFOs associated with playing notes are advanced whether or not they are in this list.
//|
//| This attribute is read-only but its contents may be modified by e.g., calling append() or remove(). It is initially an empty list."""
//|
STATIC mp_obj_t synthio_synthesizer_obj_get_lfos(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_lfos(self);
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_synthesizer_get_lfos_obj, synthio_synthesizer_obj_get_lfos);
MP_PROPERTY_GETTER(synthio_synthesizer_lfos_obj,
(mp_obj_t)&synthio_synthesizer_get_lfos_obj);
//| max_polyphony: int
//| """Maximum polyphony of the synthesizer (read-only class property)"""
//|
@ -270,6 +295,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_lfos), MP_ROM_PTR(&synthio_synthesizer_lfos_obj) },
};
STATIC MP_DEFINE_CONST_DICT(synthio_synthesizer_locals_dict, synthio_synthesizer_locals_dict_table);

View File

@ -41,5 +41,7 @@ uint8_t common_hal_synthio_synthesizer_get_bits_per_sample(synthio_synthesizer_o
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_retrigger(synthio_synthesizer_obj_t *self, mp_obj_t to_retrigger);
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_lfos(synthio_synthesizer_obj_t *self);

View File

@ -25,8 +25,9 @@
*/
#include "py/runtime.h"
#include "shared-bindings/synthio/Synthesizer.h"
#include "shared-bindings/synthio/LFO.h"
#include "shared-bindings/synthio/Note.h"
#include "shared-bindings/synthio/Synthesizer.h"
#include "shared-module/synthio/Note.h"
@ -36,6 +37,7 @@ void common_hal_synthio_synthesizer_construct(synthio_synthesizer_obj_t *self,
mp_obj_t envelope_obj) {
synthio_synth_init(&self->synth, sample_rate, channel_count, waveform_obj, filter_obj, envelope_obj);
self->lfos = mp_obj_new_list(0, NULL);
}
void common_hal_synthio_synthesizer_deinit(synthio_synthesizer_obj_t *self) {
@ -67,7 +69,20 @@ audioio_get_buffer_result_t synthio_synthesizer_get_buffer(synthio_synthesizer_o
return GET_BUFFER_ERROR;
}
self->synth.span.dur = SYNTHIO_MAX_DUR;
synthio_synth_synthesize(&self->synth, buffer, buffer_length, single_channel_output ? channel : 0);
// free-running LFOs
mp_obj_iter_buf_t iter_buf;
mp_obj_t iterable = mp_getiter(self->lfos, &iter_buf);
mp_obj_t item;
while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
synthio_lfo_slot_t slot = { .obj = item };
// does not error for invalid type, so it's OK that we don't police
// list contents
(void)synthio_lfo_obj_tick(&slot);
}
return GET_BUFFER_MORE_DATA;
}
@ -119,6 +134,16 @@ void common_hal_synthio_synthesizer_press(synthio_synthesizer_obj_t *self, mp_ob
}
}
void common_hal_synthio_synthesizer_retrigger(synthio_synthesizer_obj_t *self, mp_obj_t to_retrigger) {
mp_obj_iter_buf_t iter_buf;
mp_obj_t iterable = mp_getiter(to_retrigger, &iter_buf);
mp_obj_t lfo_obj;
while ((lfo_obj = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
synthio_lfo_obj_t *lfo = MP_OBJ_TO_PTR(mp_arg_validate_type(lfo_obj, &synthio_lfo_type, MP_QSTR_retrigger));
common_hal_synthio_lfo_retrigger(lfo);
}
}
mp_obj_t common_hal_synthio_synthesizer_get_pressed_notes(synthio_synthesizer_obj_t *self) {
int count = 0;
for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
@ -134,3 +159,7 @@ mp_obj_t common_hal_synthio_synthesizer_get_pressed_notes(synthio_synthesizer_ob
}
return MP_OBJ_FROM_PTR(result);
}
mp_obj_t common_hal_synthio_synthesizer_get_lfos(synthio_synthesizer_obj_t *self) {
return self->lfos;
}

View File

@ -34,6 +34,7 @@
typedef struct {
mp_obj_base_t base;
synthio_synth_t synth;
mp_obj_t lfos;
} synthio_synthesizer_obj_t;