From f83212314e6d255bce757e0ab5b0df5f77a6b122 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Mon, 15 May 2023 10:49:42 -0500 Subject: [PATCH] synthio: Add LFOs --- .../unix/variants/coverage/mpconfigvariant.mk | 2 + py/circuitpy_defns.mk | 1 + shared-bindings/synthio/LFO.c | 234 ++++++++++++++++++ shared-bindings/synthio/LFO.h | 53 ++++ shared-bindings/synthio/Note.c | 180 ++++++-------- shared-bindings/synthio/Note.h | 30 +-- shared-bindings/synthio/__init__.c | 61 ++--- shared-bindings/util.c | 11 +- shared-bindings/util.h | 7 +- shared-module/synthio/LFO.c | 155 ++++++++++++ shared-module/synthio/LFO.h | 62 +++++ shared-module/synthio/Note.c | 137 ++++------ shared-module/synthio/Note.h | 12 +- shared-module/synthio/__init__.c | 12 +- shared-module/synthio/__init__.h | 4 + .../circuitpython-manual/synthio/note/demo.py | 13 +- .../synthio/note/envelope.py | 3 + tests/circuitpython/synthesizer_note.py.exp | 50 ++-- 18 files changed, 722 insertions(+), 305 deletions(-) create mode 100644 shared-bindings/synthio/LFO.c create mode 100644 shared-bindings/synthio/LFO.h create mode 100644 shared-module/synthio/LFO.c create mode 100644 shared-module/synthio/LFO.h diff --git a/ports/unix/variants/coverage/mpconfigvariant.mk b/ports/unix/variants/coverage/mpconfigvariant.mk index 41f44df2b4..8af5eea172 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.mk +++ b/ports/unix/variants/coverage/mpconfigvariant.mk @@ -42,6 +42,7 @@ SRC_BITMAP := \ shared-bindings/struct/__init__.c \ shared-bindings/synthio/__init__.c \ shared-bindings/synthio/MidiTrack.c \ + shared-bindings/synthio/LFO.c \ shared-bindings/synthio/Note.c \ shared-bindings/synthio/Synthesizer.c \ shared-bindings/traceback/__init__.c \ @@ -65,6 +66,7 @@ SRC_BITMAP := \ shared-module/struct/__init__.c \ shared-module/synthio/__init__.c \ shared-module/synthio/MidiTrack.c \ + shared-module/synthio/LFO.c \ shared-module/synthio/Note.c \ shared-module/synthio/Synthesizer.c \ shared-module/traceback/__init__.c \ diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index 553490d99b..44f896cb7b 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -650,6 +650,7 @@ SRC_SHARED_MODULE_ALL = \ struct/__init__.c \ supervisor/__init__.c \ supervisor/StatusBar.c \ + synthio/LFO.c \ synthio/MidiTrack.c \ synthio/Note.c \ synthio/Synthesizer.c \ diff --git a/shared-bindings/synthio/LFO.c b/shared-bindings/synthio/LFO.c new file mode 100644 index 0000000000..ce64881f7d --- /dev/null +++ b/shared-bindings/synthio/LFO.c @@ -0,0 +1,234 @@ +/* + * 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 "py/obj.h" +#include "py/objproperty.h" +#include "py/runtime.h" +#include "shared-bindings/util.h" +#include "shared-bindings/synthio/LFO.h" +#include "shared-module/synthio/LFO.h" + +//| class LFO: +//| """A low-frequency oscillator +//| +//| Every `rate` seconds, the output of the LFO cycles through its `waveform`. +//| The output at any particular moment is ``waveform[idx] * scale + offset``. +//| Internally, the calculation takes place in fixed point for speed. +//| +//| `rate`, `offset`, `scale`, and `once` can be changed at run-time. +//| +//| An LFO only updates if it is actually associated with a playing Note, +//| including if it is indirectly associated with the Note via an intermediate +//| LFO. +//| +//| Using the same LFO as an input to multiple other LFOs or Notes is OK, but +//| the result if an LFO is tied to multiple Synthtesizer objects is undefined.""" +//| +//| def __init__( +//| self, +//| waveform: ReadableBuffer, +//| *, +//| rate: BlockInput = 1.0, +//| scale: BlockInput = 1.0, +//| offset: BlockInput = 0, +//| once=False +//| ): +//| pass +static const mp_arg_t lfo_properties[] = { + { MP_QSTR_waveform, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = NULL } }, + { MP_QSTR_rate, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(1) } }, + { MP_QSTR_scale, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(1) } }, + { MP_QSTR_offset, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(0) } }, + { MP_QSTR_once, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(0) } }, +}; + +STATIC mp_obj_t synthio_lfo_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_waveform }; // others never directly referred to by argument number + + mp_arg_val_t args[MP_ARRAY_SIZE(lfo_properties)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(lfo_properties), lfo_properties, args); + + synthio_lfo_obj_t *self = m_new_obj(synthio_lfo_obj_t); + self->base.type = &synthio_lfo_type; + + synthio_synth_parse_waveform(&self->waveform_bufinfo, args[ARG_waveform].u_obj); + self->waveform_obj = args[ARG_waveform].u_obj; + self->last_tick = synthio_global_tick; + + mp_obj_t result = MP_OBJ_FROM_PTR(self); + properties_construct_helper(result, lfo_properties + 1, args + 1, MP_ARRAY_SIZE(lfo_properties) - 1); + + return result; +}; + +//| waveform: Optional[ReadableBuffer] +//| """The waveform of this lfo. (read-only, but the values in the buffer may be modified dynamically)""" +STATIC mp_obj_t synthio_lfo_get_waveform(mp_obj_t self_in) { + synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_synthio_lfo_get_waveform_obj(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(synthio_lfo_get_waveform_obj, synthio_lfo_get_waveform); + +MP_PROPERTY_GETTER(synthio_lfo_waveform_obj, + (mp_obj_t)&synthio_lfo_get_waveform_obj); + +//| rate: BlockInput +//| """The rate (in Hz) at which the LFO cycles through its waveform""" +STATIC mp_obj_t synthio_lfo_get_rate(mp_obj_t self_in) { + synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_synthio_lfo_get_rate_obj(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(synthio_lfo_get_rate_obj, synthio_lfo_get_rate); + +STATIC mp_obj_t synthio_lfo_set_rate(mp_obj_t self_in, mp_obj_t arg) { + synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_synthio_lfo_set_rate_obj(self, arg); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(synthio_lfo_set_rate_obj, synthio_lfo_set_rate); +MP_PROPERTY_GETSET(synthio_lfo_rate_obj, + (mp_obj_t)&synthio_lfo_get_rate_obj, + (mp_obj_t)&synthio_lfo_set_rate_obj); + + +//| offset: BlockInput +//| """An additive value applied to the LFO's output""" +STATIC mp_obj_t synthio_lfo_get_offset(mp_obj_t self_in) { + synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_synthio_lfo_get_offset_obj(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(synthio_lfo_get_offset_obj, synthio_lfo_get_offset); + +STATIC mp_obj_t synthio_lfo_set_offset(mp_obj_t self_in, mp_obj_t arg) { + synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_synthio_lfo_set_offset_obj(self, arg); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(synthio_lfo_set_offset_obj, synthio_lfo_set_offset); +MP_PROPERTY_GETSET(synthio_lfo_offset_obj, + (mp_obj_t)&synthio_lfo_get_offset_obj, + (mp_obj_t)&synthio_lfo_set_offset_obj); + +//| scale: BlockInput +//| """An additive value applied to the LFO's output""" +STATIC mp_obj_t synthio_lfo_get_scale(mp_obj_t self_in) { + synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_synthio_lfo_get_scale_obj(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(synthio_lfo_get_scale_obj, synthio_lfo_get_scale); + +STATIC mp_obj_t synthio_lfo_set_scale(mp_obj_t self_in, mp_obj_t arg) { + synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_synthio_lfo_set_scale_obj(self, arg); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(synthio_lfo_set_scale_obj, synthio_lfo_set_scale); +MP_PROPERTY_GETSET(synthio_lfo_scale_obj, + (mp_obj_t)&synthio_lfo_get_scale_obj, + (mp_obj_t)&synthio_lfo_set_scale_obj); + +//| +//| once: bool +//| """True if the waveform should stop when it reaches its last output value, false if it should re-start at the beginning of its waveform""" +STATIC mp_obj_t synthio_lfo_get_once(mp_obj_t self_in) { + synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_bool(common_hal_synthio_lfo_get_once(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(synthio_lfo_get_once_obj, synthio_lfo_get_once); + +STATIC mp_obj_t synthio_lfo_set_once(mp_obj_t self_in, mp_obj_t arg) { + synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_synthio_lfo_set_once(self, mp_obj_is_true(arg)); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(synthio_lfo_set_once_obj, synthio_lfo_set_once); +MP_PROPERTY_GETSET(synthio_lfo_once_obj, + (mp_obj_t)&synthio_lfo_get_once_obj, + (mp_obj_t)&synthio_lfo_set_once_obj); + + +//| +//| phase: float +//| """The phase of the oscillator, in the range 0 to 1 (read-only)""" +STATIC mp_obj_t synthio_lfo_get_phase(mp_obj_t self_in) { + synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_float(common_hal_synthio_lfo_get_phase(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(synthio_lfo_get_phase_obj, synthio_lfo_get_phase); + +MP_PROPERTY_GETTER(synthio_lfo_phase_obj, + (mp_obj_t)&synthio_lfo_get_phase_obj); + + +//| +//| value: float +//| """The value of the oscillator (read-only)""" +STATIC mp_obj_t synthio_lfo_get_value(mp_obj_t self_in) { + synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_float(common_hal_synthio_lfo_get_value(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(synthio_lfo_get_value_obj, synthio_lfo_get_value); + +MP_PROPERTY_GETTER(synthio_lfo_value_obj, + (mp_obj_t)&synthio_lfo_get_value_obj); + + +//| +//| def retrigger(self): +//| """Reset the LFO's internal index to the start of the waveform. Most useful when it its `once` property is `True`.""" +//| +STATIC mp_obj_t synthio_lfo_retrigger(mp_obj_t self_in) { + synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_synthio_lfo_retrigger(self); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(synthio_lfo_retrigger_obj, synthio_lfo_retrigger); + +static void lfo_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + properties_print_helper(print, self_in, lfo_properties, MP_ARRAY_SIZE(lfo_properties)); +} + +STATIC const mp_rom_map_elem_t synthio_lfo_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_waveform), MP_ROM_PTR(&synthio_lfo_waveform_obj) }, + { MP_ROM_QSTR(MP_QSTR_rate), MP_ROM_PTR(&synthio_lfo_rate_obj) }, + { MP_ROM_QSTR(MP_QSTR_scale), MP_ROM_PTR(&synthio_lfo_scale_obj) }, + { MP_ROM_QSTR(MP_QSTR_offset), MP_ROM_PTR(&synthio_lfo_offset_obj) }, + { MP_ROM_QSTR(MP_QSTR_once), MP_ROM_PTR(&synthio_lfo_once_obj) }, + { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&synthio_lfo_value_obj) }, + { MP_ROM_QSTR(MP_QSTR_phase), MP_ROM_PTR(&synthio_lfo_phase_obj) }, + { MP_ROM_QSTR(MP_QSTR_retrigger), MP_ROM_PTR(&synthio_lfo_retrigger_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(synthio_lfo_locals_dict, synthio_lfo_locals_dict_table); + +const mp_obj_type_t synthio_lfo_type = { + { &mp_type_type }, + .name = MP_QSTR_LFO, + .make_new = synthio_lfo_make_new, + .locals_dict = (mp_obj_dict_t *)&synthio_lfo_locals_dict, + .print = lfo_print, +}; diff --git a/shared-bindings/synthio/LFO.h b/shared-bindings/synthio/LFO.h new file mode 100644 index 0000000000..e0123dc5b2 --- /dev/null +++ b/shared-bindings/synthio/LFO.h @@ -0,0 +1,53 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * 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" + +typedef struct synthio_lfo_obj synthio_lfo_obj_t; +extern const mp_obj_type_t synthio_lfo_type; + +mp_obj_t common_hal_synthio_lfo_get_waveform_obj(synthio_lfo_obj_t *self); +void common_hal_synthio_lfo_set_waveform_obj(synthio_lfo_obj_t *self, mp_obj_t arg); + +mp_obj_t common_hal_synthio_lfo_get_rate_obj(synthio_lfo_obj_t *self); +void common_hal_synthio_lfo_set_rate_obj(synthio_lfo_obj_t *self, mp_obj_t arg); + +mp_obj_t common_hal_synthio_lfo_get_scale_obj(synthio_lfo_obj_t *self); +void common_hal_synthio_lfo_set_scale_obj(synthio_lfo_obj_t *self, mp_obj_t arg); + +mp_obj_t common_hal_synthio_lfo_get_offset_obj(synthio_lfo_obj_t *self); +void common_hal_synthio_lfo_set_offset_obj(synthio_lfo_obj_t *self, mp_obj_t arg); + +bool common_hal_synthio_lfo_get_once(synthio_lfo_obj_t *self); +void common_hal_synthio_lfo_set_once(synthio_lfo_obj_t *self, bool arg); + +mp_float_t common_hal_synthio_lfo_get_value(synthio_lfo_obj_t *self); + +mp_float_t common_hal_synthio_lfo_get_phase(synthio_lfo_obj_t *self); + +void common_hal_synthio_lfo_retrigger(synthio_lfo_obj_t *self); diff --git a/shared-bindings/synthio/Note.c b/shared-bindings/synthio/Note.c index f058b16d2e..a0b258653f 100644 --- a/shared-bindings/synthio/Note.c +++ b/shared-bindings/synthio/Note.c @@ -37,15 +37,13 @@ static const mp_arg_t note_properties[] = { { MP_QSTR_frequency, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = NULL } }, { MP_QSTR_panning, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(0) } }, - { MP_QSTR_tremolo_rate, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = NULL } }, - { MP_QSTR_tremolo_depth, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = NULL } }, - { MP_QSTR_bend_rate, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = NULL } }, - { MP_QSTR_bend_depth, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = NULL } }, - { MP_QSTR_bend_mode, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = (mp_obj_t)MP_ROM_PTR(&bend_mode_VIBRATO_obj) } }, + { MP_QSTR_amplitude, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(1) } }, + { MP_QSTR_bend, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(0) } }, { MP_QSTR_waveform, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_NONE } }, { MP_QSTR_envelope, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_NONE } }, { MP_QSTR_filter, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(1) } }, - { MP_QSTR_ring_frequency, MP_ARG_OBJ, {.u_obj = NULL } }, + { MP_QSTR_ring_frequency, MP_ARG_OBJ, {.u_obj = MP_ROM_INT(0) } }, + { MP_QSTR_ring_bend, MP_ARG_OBJ, {.u_obj = MP_ROM_INT(0) } }, { MP_QSTR_ring_waveform, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_NONE } }, }; //| class Note: @@ -53,14 +51,11 @@ static const mp_arg_t note_properties[] = { //| self, //| *, //| frequency: float, -//| panning: float = 0.0, +//| panning: BlockInput = 0.0, //| waveform: Optional[ReadableBuffer] = None, //| envelope: Optional[Envelope] = None, -//| tremolo_depth: float = 0.0, -//| tremolo_rate: float = 0.0, -//| bend_depth: float = 0.0, -//| bend_rate: float = 0.0, -//| bend_mode: "BendMode" = BendMode.VIBRATO, +//| amplitude: BlockInput = 0.0, +//| bend: BlockInput = 0.0, //| ) -> None: //| """Construct a Note object, with a frequency in Hz, and optional panning, waveform, envelope, tremolo (volume change) and bend (frequency change). //| @@ -76,16 +71,12 @@ STATIC mp_obj_t synthio_note_make_new(const mp_obj_type_t *type_in, size_t n_arg self->base.type = &synthio_note_type; mp_obj_t result = MP_OBJ_FROM_PTR(self); - for (size_t i = 0; i < MP_ARRAY_SIZE(note_properties); i++) { - if (args[i].u_obj != NULL) { - mp_store_attr(result, note_properties[i].qst, args[i].u_obj); - } - } + properties_construct_helper(result, note_properties, args, MP_ARRAY_SIZE(note_properties)); return result; }; -//| frequency: float +//| frequency: BlockInput //| """The base frequency of the note, in Hz.""" STATIC mp_obj_t synthio_note_get_frequency(mp_obj_t self_in) { synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in); @@ -121,7 +112,7 @@ MP_PROPERTY_GETSET(synthio_note_filter_obj, (mp_obj_t)&synthio_note_get_filter_obj, (mp_obj_t)&synthio_note_set_filter_obj); -//| panning: float +//| panning: BlockInput //| """Defines the channel(s) in which the note appears. //| //| -1 is left channel only, 0 is both channels, and 1 is right channel. @@ -130,13 +121,13 @@ MP_PROPERTY_GETSET(synthio_note_filter_obj, //| amplitude in the left channel and 1/2 amplitude in the right channel.""" STATIC mp_obj_t synthio_note_get_panning(mp_obj_t self_in) { synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_float(common_hal_synthio_note_get_panning(self)); + return common_hal_synthio_note_get_panning(self); } MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_panning_obj, synthio_note_get_panning); STATIC mp_obj_t synthio_note_set_panning(mp_obj_t self_in, mp_obj_t arg) { synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in); - common_hal_synthio_note_set_panning(self, mp_obj_get_float(arg)); + common_hal_synthio_note_set_panning(self, arg); return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_panning_obj, synthio_note_set_panning); @@ -145,108 +136,54 @@ MP_PROPERTY_GETSET(synthio_note_panning_obj, (mp_obj_t)&synthio_note_set_panning_obj); -//| tremolo_depth: float -//| """The tremolo depth of the note, from 0 to 1 +//| amplitude: BlockInput +//| """The relative amplitude of the note, from 0 to 1 //| -//| A depth of 0 disables tremolo. A nonzero value enables tremolo, -//| with the maximum decrease in amplitude being equal to the tremolo -//| depth. A note with a tremolo depth of 1 will fade out to nothing, while -//| a tremolo depth of 0.1 will give a minimum amplitude of 0.9.""" -STATIC mp_obj_t synthio_note_get_tremolo_depth(mp_obj_t self_in) { +//| An amplitude of 0 makes the note inaudible. It is combined multiplicatively with +//| the value from the note's envelope. +//| +//| To achieve a tremolo effect, attach an LFO here.""" +STATIC mp_obj_t synthio_note_get_amplitude(mp_obj_t self_in) { synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_float(common_hal_synthio_note_get_tremolo_depth(self)); + return common_hal_synthio_note_get_amplitude(self); } -MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_tremolo_depth_obj, synthio_note_get_tremolo_depth); +MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_amplitude_obj, synthio_note_get_amplitude); -STATIC mp_obj_t synthio_note_set_tremolo_depth(mp_obj_t self_in, mp_obj_t arg) { +STATIC mp_obj_t synthio_note_set_amplitude(mp_obj_t self_in, mp_obj_t arg) { synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in); - common_hal_synthio_note_set_tremolo_depth(self, mp_obj_get_float(arg)); + common_hal_synthio_note_set_amplitude(self, arg); return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_tremolo_depth_obj, synthio_note_set_tremolo_depth); -MP_PROPERTY_GETSET(synthio_note_tremolo_depth_obj, - (mp_obj_t)&synthio_note_get_tremolo_depth_obj, - (mp_obj_t)&synthio_note_set_tremolo_depth_obj); - -//| tremolo_rate: float -//| """The tremolo rate of the note, in Hz.""" -STATIC mp_obj_t synthio_note_get_tremolo_rate(mp_obj_t self_in) { - synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_float(common_hal_synthio_note_get_tremolo_rate(self)); -} -MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_tremolo_rate_obj, synthio_note_get_tremolo_rate); - -STATIC mp_obj_t synthio_note_set_tremolo_rate(mp_obj_t self_in, mp_obj_t arg) { - synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in); - common_hal_synthio_note_set_tremolo_rate(self, mp_obj_get_float(arg)); - return mp_const_none; -} -MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_tremolo_rate_obj, synthio_note_set_tremolo_rate); -MP_PROPERTY_GETSET(synthio_note_tremolo_rate_obj, - (mp_obj_t)&synthio_note_get_tremolo_rate_obj, - (mp_obj_t)&synthio_note_set_tremolo_rate_obj); +MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_amplitude_obj, synthio_note_set_amplitude); +MP_PROPERTY_GETSET(synthio_note_amplitude_obj, + (mp_obj_t)&synthio_note_get_amplitude_obj, + (mp_obj_t)&synthio_note_set_amplitude_obj); //| -//| bend_mode: BendMode -//| """The type of bend operation""" -STATIC mp_obj_t synthio_note_get_bend_mode(mp_obj_t self_in) { - synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in); - return cp_enum_find(&synthio_bend_mode_type, common_hal_synthio_note_get_bend_mode(self)); -} -MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_bend_mode_obj, synthio_note_get_bend_mode); - -STATIC mp_obj_t synthio_note_set_bend_mode(mp_obj_t self_in, mp_obj_t arg) { - synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in); - common_hal_synthio_note_set_bend_mode(self, cp_enum_value(&synthio_bend_mode_type, arg, MP_QSTR_bend_mode)); - return mp_const_none; -} -MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_bend_mode_obj, synthio_note_set_bend_mode); -MP_PROPERTY_GETSET(synthio_note_bend_mode_obj, - (mp_obj_t)&synthio_note_get_bend_mode_obj, - (mp_obj_t)&synthio_note_set_bend_mode_obj); - -// +//| bend: BlockInput +//| """The pitch bend depth of the note, from -1 to +1 //| -//| bend_depth: float -//| """The bend depth of the note, from -1 to +1 -//| -//| A depth of 0 disables bend. A depth of 1 corresponds to a bend of 1 +//| A depth of 0 plays the programmed frequency. A depth of 1 corresponds to a bend of 1 //| octave. A depth of (1/12) = 0.833 corresponds to a bend of 1 semitone, -//| and a depth of .00833 corresponds to one musical cent. +//| and a depth of .00833 corresponds to one musical cent. Values of ±1 octave are supported. +//| +//| To achieve a vibrato or sweep effect, attach an LFO here. //| """ -STATIC mp_obj_t synthio_note_get_bend_depth(mp_obj_t self_in) { +STATIC mp_obj_t synthio_note_get_bend(mp_obj_t self_in) { synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_float(common_hal_synthio_note_get_bend_depth(self)); + return common_hal_synthio_note_get_bend(self); } -MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_bend_depth_obj, synthio_note_get_bend_depth); +MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_bend_obj, synthio_note_get_bend); -STATIC mp_obj_t synthio_note_set_bend_depth(mp_obj_t self_in, mp_obj_t arg) { +STATIC mp_obj_t synthio_note_set_bend(mp_obj_t self_in, mp_obj_t arg) { synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in); - common_hal_synthio_note_set_bend_depth(self, mp_obj_get_float(arg)); + common_hal_synthio_note_set_bend(self, arg); return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_bend_depth_obj, synthio_note_set_bend_depth); -MP_PROPERTY_GETSET(synthio_note_bend_depth_obj, - (mp_obj_t)&synthio_note_get_bend_depth_obj, - (mp_obj_t)&synthio_note_set_bend_depth_obj); - -//| bend_rate: float -//| """The bend rate of the note, in Hz.""" -STATIC mp_obj_t synthio_note_get_bend_rate(mp_obj_t self_in) { - synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_float(common_hal_synthio_note_get_bend_rate(self)); -} -MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_bend_rate_obj, synthio_note_get_bend_rate); - -STATIC mp_obj_t synthio_note_set_bend_rate(mp_obj_t self_in, mp_obj_t arg) { - synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in); - common_hal_synthio_note_set_bend_rate(self, mp_obj_get_float(arg)); - return mp_const_none; -} -MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_bend_rate_obj, synthio_note_set_bend_rate); -MP_PROPERTY_GETSET(synthio_note_bend_rate_obj, - (mp_obj_t)&synthio_note_get_bend_rate_obj, - (mp_obj_t)&synthio_note_set_bend_rate_obj); +MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_bend_obj, synthio_note_set_bend); +MP_PROPERTY_GETSET(synthio_note_bend_obj, + (mp_obj_t)&synthio_note_get_bend_obj, + (mp_obj_t)&synthio_note_set_bend_obj); //| waveform: Optional[ReadableBuffer] //| """The waveform of this note. Setting the waveform to a buffer of a different size resets the note's phase.""" @@ -306,6 +243,31 @@ MP_PROPERTY_GETSET(synthio_note_ring_frequency_obj, (mp_obj_t)&synthio_note_get_ring_frequency_obj, (mp_obj_t)&synthio_note_set_ring_frequency_obj); +//| ring_bend: float +//| """The pitch bend depth of the note's ring waveform, from -1 to +1 +//| +//| A depth of 0 plays the programmed frequency. A depth of 1 corresponds to a bend of 1 +//| octave. A depth of (1/12) = 0.833 corresponds to a bend of 1 semitone, +//| and a depth of .00833 corresponds to one musical cent. Values of ±1 octave are supported. +//| +//| To achieve a vibrato or sweep effect on the ring waveform, attach an LFO here. +//| """ +STATIC mp_obj_t synthio_note_get_ring_bend(mp_obj_t self_in) { + synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_synthio_note_get_ring_bend(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_ring_bend_obj, synthio_note_get_ring_bend); + +STATIC mp_obj_t synthio_note_set_ring_bend(mp_obj_t self_in, mp_obj_t arg) { + synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_synthio_note_set_ring_bend(self, arg); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_ring_bend_obj, synthio_note_set_ring_bend); +MP_PROPERTY_GETSET(synthio_note_ring_bend_obj, + (mp_obj_t)&synthio_note_get_ring_bend_obj, + (mp_obj_t)&synthio_note_set_ring_bend_obj); + //| ring_waveform: Optional[ReadableBuffer] //| """The ring waveform of this note. Setting the ring_waveform to a buffer of a different size resets the note's phase. //| @@ -340,12 +302,10 @@ STATIC const mp_rom_map_elem_t synthio_note_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_panning), MP_ROM_PTR(&synthio_note_panning_obj) }, { MP_ROM_QSTR(MP_QSTR_waveform), MP_ROM_PTR(&synthio_note_waveform_obj) }, { MP_ROM_QSTR(MP_QSTR_envelope), MP_ROM_PTR(&synthio_note_envelope_obj) }, - { MP_ROM_QSTR(MP_QSTR_tremolo_depth), MP_ROM_PTR(&synthio_note_tremolo_depth_obj) }, - { MP_ROM_QSTR(MP_QSTR_tremolo_rate), MP_ROM_PTR(&synthio_note_tremolo_rate_obj) }, - { MP_ROM_QSTR(MP_QSTR_bend_depth), MP_ROM_PTR(&synthio_note_bend_depth_obj) }, - { MP_ROM_QSTR(MP_QSTR_bend_rate), MP_ROM_PTR(&synthio_note_bend_rate_obj) }, - { MP_ROM_QSTR(MP_QSTR_bend_mode), MP_ROM_PTR(&synthio_note_bend_mode_obj) }, + { MP_ROM_QSTR(MP_QSTR_amplitude), MP_ROM_PTR(&synthio_note_amplitude_obj) }, + { MP_ROM_QSTR(MP_QSTR_bend), MP_ROM_PTR(&synthio_note_bend_obj) }, { MP_ROM_QSTR(MP_QSTR_ring_frequency), MP_ROM_PTR(&synthio_note_ring_frequency_obj) }, + { MP_ROM_QSTR(MP_QSTR_ring_bend), MP_ROM_PTR(&synthio_note_ring_bend_obj) }, { MP_ROM_QSTR(MP_QSTR_ring_waveform), MP_ROM_PTR(&synthio_note_ring_waveform_obj) }, }; STATIC MP_DEFINE_CONST_DICT(synthio_note_locals_dict, synthio_note_locals_dict_table); diff --git a/shared-bindings/synthio/Note.h b/shared-bindings/synthio/Note.h index b87310576a..271268dc33 100644 --- a/shared-bindings/synthio/Note.h +++ b/shared-bindings/synthio/Note.h @@ -12,30 +12,24 @@ void common_hal_synthio_note_set_frequency(synthio_note_obj_t *self, mp_float_t bool common_hal_synthio_note_get_filter(synthio_note_obj_t *self); void common_hal_synthio_note_set_filter(synthio_note_obj_t *self, bool value); -mp_float_t common_hal_synthio_note_get_ring_frequency(synthio_note_obj_t *self); -void common_hal_synthio_note_set_ring_frequency(synthio_note_obj_t *self, mp_float_t value); +mp_obj_t common_hal_synthio_note_get_panning(synthio_note_obj_t *self); +void common_hal_synthio_note_set_panning(synthio_note_obj_t *self, mp_obj_t value); -mp_float_t common_hal_synthio_note_get_panning(synthio_note_obj_t *self); -void common_hal_synthio_note_set_panning(synthio_note_obj_t *self, mp_float_t value); +mp_obj_t common_hal_synthio_note_get_amplitude(synthio_note_obj_t *self); +void common_hal_synthio_note_set_amplitude(synthio_note_obj_t *self, mp_obj_t value); -mp_float_t common_hal_synthio_note_get_tremolo_rate(synthio_note_obj_t *self); -void common_hal_synthio_note_set_tremolo_rate(synthio_note_obj_t *self, mp_float_t value); - -mp_float_t common_hal_synthio_note_get_tremolo_depth(synthio_note_obj_t *self); -void common_hal_synthio_note_set_tremolo_depth(synthio_note_obj_t *self, mp_float_t value); - -synthio_bend_mode_t common_hal_synthio_note_get_bend_mode(synthio_note_obj_t *self); -void common_hal_synthio_note_set_bend_mode(synthio_note_obj_t *self, synthio_bend_mode_t value); - -mp_float_t common_hal_synthio_note_get_bend_rate(synthio_note_obj_t *self); -void common_hal_synthio_note_set_bend_rate(synthio_note_obj_t *self, mp_float_t value); - -mp_float_t common_hal_synthio_note_get_bend_depth(synthio_note_obj_t *self); -void common_hal_synthio_note_set_bend_depth(synthio_note_obj_t *self, mp_float_t value); +mp_obj_t common_hal_synthio_note_get_bend(synthio_note_obj_t *self); +void common_hal_synthio_note_set_bend(synthio_note_obj_t *self, mp_obj_t value); mp_obj_t common_hal_synthio_note_get_waveform_obj(synthio_note_obj_t *self); void common_hal_synthio_note_set_waveform(synthio_note_obj_t *self, mp_obj_t value); +mp_float_t common_hal_synthio_note_get_ring_frequency(synthio_note_obj_t *self); +void common_hal_synthio_note_set_ring_frequency(synthio_note_obj_t *self, mp_float_t value); + +mp_obj_t common_hal_synthio_note_get_ring_bend(synthio_note_obj_t *self); +void common_hal_synthio_note_set_ring_bend(synthio_note_obj_t *self, mp_obj_t value); + mp_obj_t common_hal_synthio_note_get_ring_waveform_obj(synthio_note_obj_t *self); void common_hal_synthio_note_set_ring_waveform(synthio_note_obj_t *self, mp_obj_t value); diff --git a/shared-bindings/synthio/__init__.c b/shared-bindings/synthio/__init__.c index 132b254942..eb9df13a25 100644 --- a/shared-bindings/synthio/__init__.c +++ b/shared-bindings/synthio/__init__.c @@ -24,6 +24,7 @@ * THE SOFTWARE. */ +#include #include #include "py/enum.h" @@ -35,10 +36,13 @@ #include "extmod/vfs_posix.h" #include "shared-bindings/synthio/__init__.h" +#include "shared-bindings/synthio/LFO.h" #include "shared-bindings/synthio/MidiTrack.h" #include "shared-bindings/synthio/Note.h" #include "shared-bindings/synthio/Synthesizer.h" +#include "shared-module/synthio/LFO.h" + #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)) @@ -57,9 +61,11 @@ static const mp_arg_t envelope_properties[] = { //| """Support for multi-channel audio synthesis //| //| At least 2 simultaneous notes are supported. samd5x, mimxrt10xx and rp2040 platforms support up to 12 notes. -//| //| """ //| +//| BlockInput = Union["LFO", float] +//| """LFOs and Notes can take any of these types as inputs on certain attributes""" +//| //| class Envelope: //| def __init__( //| self, @@ -283,51 +289,34 @@ STATIC mp_obj_t onevo_to_hz(mp_obj_t arg) { } MP_DEFINE_CONST_FUN_OBJ_1(synthio_onevo_to_hz_obj, onevo_to_hz); -MAKE_ENUM_VALUE(synthio_bend_mode_type, bend_mode, STATIC, SYNTHIO_BEND_MODE_STATIC); -MAKE_ENUM_VALUE(synthio_bend_mode_type, bend_mode, VIBRATO, SYNTHIO_BEND_MODE_VIBRATO); -MAKE_ENUM_VALUE(synthio_bend_mode_type, bend_mode, SWEEP, SYNTHIO_BEND_MODE_SWEEP); -MAKE_ENUM_VALUE(synthio_bend_mode_type, bend_mode, SWEEP_IN, SYNTHIO_BEND_MODE_SWEEP_IN); - -//| -//| class BendMode: -//| """Controls the way the ``Note.pitch_bend_depth`` and ``Note.pitch_bend_rate`` properties are interpreted.""" -//| -//| STATIC: "BendMode" -//| """The Note's pitch is modified by its ``pitch_bend_depth``. ``pitch_bend_rate`` is ignored.""" -//| -//| VIBRATO: "BendMode" -//| """The Note's pitch varies by ``±pitch_bend_depth`` at a rate of ``pitch_bend_rate`` Hz.""" -//| -//| SWEEP: "BendMode" -//| """The Note's pitch starts at ``Note.frequency`` then sweeps up or down by ``pitch_bend_depth`` over ``1/pitch_bend_rate`` seconds.""" -//| -//| SWEEP_IN: "BendMode" -//| """The Note's pitch sweep is the reverse of ``SWEEP`` mode, starting at the bent pitch and arriving at the tuned pitch.""" -//| -MAKE_ENUM_MAP(synthio_bend_mode) { - MAKE_ENUM_MAP_ENTRY(bend_mode, STATIC), - MAKE_ENUM_MAP_ENTRY(bend_mode, VIBRATO), - MAKE_ENUM_MAP_ENTRY(bend_mode, SWEEP), - MAKE_ENUM_MAP_ENTRY(bend_mode, SWEEP_IN), -}; - -STATIC MP_DEFINE_CONST_DICT(synthio_bend_mode_locals_dict, synthio_bend_mode_locals_table); - -MAKE_PRINTER(synthio, synthio_bend_mode); - -MAKE_ENUM_TYPE(synthio, BendMode, synthio_bend_mode); - +#if CIRCUITPY_AUDIOCORE_DEBUG +STATIC mp_obj_t synthio_lfo_tick(size_t n, const mp_obj_t *args) { + shared_bindings_synthio_lfo_tick(48000); + mp_obj_t result[n]; + for (size_t i = 0; i < n; i++) { + synthio_lfo_slot_t slot; + synthio_lfo_assign_input(args[i], &slot, MP_QSTR_arg); + mp_float_t value = synthio_lfo_obj_tick(&slot); + result[i] = mp_obj_new_float(value); + } + return mp_obj_new_tuple(n, result); +} +MP_DEFINE_CONST_FUN_OBJ_VAR(synthio_lfo_tick_obj, 1, synthio_lfo_tick); +#endif 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_BendMode), MP_ROM_PTR(&synthio_bend_mode_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_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) }, { MP_ROM_QSTR(MP_QSTR_Envelope), MP_ROM_PTR(&synthio_envelope_type_obj) }, { MP_ROM_QSTR(MP_QSTR_midi_to_hz), MP_ROM_PTR(&synthio_midi_to_hz_obj) }, { MP_ROM_QSTR(MP_QSTR_onevo_to_hz), MP_ROM_PTR(&synthio_midi_to_hz_obj) }, + #if CIRCUITPY_AUDIOCORE_DEBUG + { MP_ROM_QSTR(MP_QSTR_lfo_tick), MP_ROM_PTR(&synthio_lfo_tick_obj) }, + #endif }; STATIC MP_DEFINE_CONST_DICT(synthio_module_globals, synthio_module_globals_table); diff --git a/shared-bindings/util.c b/shared-bindings/util.c index 78960a7552..7cb0d51176 100644 --- a/shared-bindings/util.c +++ b/shared-bindings/util.c @@ -24,9 +24,6 @@ * THE SOFTWARE. */ -#ifndef MICROPY_INCLUDED_SHARED_BINDINGS_UTIL_H -#define MICROPY_INCLUDED_SHARED_BINDINGS_UTIL_H - #include "py/runtime.h" #include "shared-bindings/util.h" @@ -50,4 +47,10 @@ void properties_print_helper(const mp_print_t *print, mp_obj_t self_in, const mp mp_print_str(print, ")"); } -#endif // MICROPY_INCLUDED_SHARED_BINDINGS_UTIL_H +void properties_construct_helper(mp_obj_t self_in, const mp_arg_t *args, const mp_arg_val_t *vals, size_t n_properties) { + for (size_t i = 0; i < n_properties; i++) { + if (vals[i].u_obj != NULL) { + mp_store_attr(self_in, args[i].qst, vals[i].u_obj); + } + } +} diff --git a/shared-bindings/util.h b/shared-bindings/util.h index b35e903d38..b03c5b67bb 100644 --- a/shared-bindings/util.h +++ b/shared-bindings/util.h @@ -24,14 +24,11 @@ * THE SOFTWARE. */ -#ifndef MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_UTIL_H -#define MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_UTIL_H +#pragma once #include "py/mpprint.h" #include "py/runtime.h" void raise_deinited_error(void); void properties_print_helper(const mp_print_t *print, mp_obj_t self_in, const mp_arg_t *properties, size_t n_properties); - - -#endif // MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_UTIL_H +void properties_construct_helper(mp_obj_t self_in, const mp_arg_t *args, const mp_arg_val_t *vals, size_t n_properties); diff --git a/shared-module/synthio/LFO.c b/shared-module/synthio/LFO.c new file mode 100644 index 0000000000..b3f31e48f2 --- /dev/null +++ b/shared-module/synthio/LFO.c @@ -0,0 +1,155 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * 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-bindings/synthio/LFO.h" +#include "shared-module/synthio/LFO.h" +#include + +#define ONE (MICROPY_FLOAT_CONST(1.)) +#define ZERO (MICROPY_FLOAT_CONST(0.)) + + +mp_float_t synthio_lfo_obj_tick(synthio_lfo_slot_t *slot) { + mp_obj_t obj = slot->obj; + + if (!mp_obj_is_type(obj, &synthio_lfo_type)) { + return slot->value; + } + + synthio_lfo_obj_t *lfo = MP_OBJ_TO_PTR(obj); + if (lfo->last_tick == synthio_global_tick) { + slot->value = lfo->value; + return slot->value; + } + + lfo->last_tick = synthio_global_tick; + + mp_float_t rate = synthio_lfo_obj_tick(&lfo->rate) * synthio_global_rate_scale; + ; + + mp_float_t accum = lfo->accum + rate; + int len = lfo->waveform_bufinfo.len / 2; + mp_float_t frac = accum - MICROPY_FLOAT_C_FUN(floor)(accum); + size_t idx = (int)(frac * len); + + if (lfo->once) { + if (rate > 0) { + if (accum >= ONE) { + frac = ONE; + idx = len - 1; + } + } else if (rate < 0 && accum < ZERO) { + frac = ZERO; + idx = 0; + } + } + + lfo->accum = frac; + + int16_t *waveform = lfo->waveform_bufinfo.buf; + assert(idx < lfo->waveform_bufinfo.len / 2); + mp_float_t scale = synthio_lfo_obj_tick(&lfo->scale); + mp_float_t offset = synthio_lfo_obj_tick(&lfo->offset); + mp_float_t value = MICROPY_FLOAT_C_FUN(ldexp)(waveform[idx], -15) * scale + offset; + + lfo->value = slot->value = value; + return value; +} + +mp_float_t synthio_lfo_obj_tick_limited(synthio_lfo_slot_t *lfo_slot, mp_float_t lo, mp_float_t hi) { + mp_float_t value = synthio_lfo_obj_tick(lfo_slot); + if (value < lo) { + return lo; + } + if (value > hi) { + return hi; + } + return value; +} + +int32_t synthio_lfo_obj_tick_scaled(synthio_lfo_slot_t *lfo_slot, mp_float_t lo, mp_float_t hi, int shift) { + mp_float_t value = synthio_lfo_obj_tick_limited(lfo_slot, lo, hi); + return (int32_t)MICROPY_FLOAT_C_FUN(round)(MICROPY_FLOAT_C_FUN(ldexp)(value, shift)); +} + +void synthio_lfo_assign_input(mp_obj_t obj, synthio_lfo_slot_t *slot, qstr arg_name) { + if (mp_obj_is_type(obj, &synthio_lfo_type)) { + slot->obj = obj; + return; + } + + if (!mp_obj_get_float_maybe(obj, &slot->value)) { + mp_raise_TypeError_varg(translate("%q must be of type %q or %q, not %q"), arg_name, MP_QSTR_Lfo, MP_QSTR_float, mp_obj_get_type_qstr(obj)); + } + + slot->value = mp_obj_get_float(obj); + slot->obj = obj; +} + +mp_obj_t common_hal_synthio_lfo_get_waveform_obj(synthio_lfo_obj_t *self) { + return self->waveform_obj; +} + +mp_obj_t common_hal_synthio_lfo_get_rate_obj(synthio_lfo_obj_t *self) { + return self->rate.obj; +} + +void common_hal_synthio_lfo_set_rate_obj(synthio_lfo_obj_t *self, mp_obj_t arg) { + synthio_lfo_assign_input(arg, &self->rate, MP_QSTR_rate); +} + +mp_obj_t common_hal_synthio_lfo_get_scale_obj(synthio_lfo_obj_t *self) { + return self->scale.obj; +} +void common_hal_synthio_lfo_set_scale_obj(synthio_lfo_obj_t *self, mp_obj_t arg) { + synthio_lfo_assign_input(arg, &self->scale, MP_QSTR_scale); +} + +mp_obj_t common_hal_synthio_lfo_get_offset_obj(synthio_lfo_obj_t *self) { + return self->offset.obj; +} +void common_hal_synthio_lfo_set_offset_obj(synthio_lfo_obj_t *self, mp_obj_t arg) { + synthio_lfo_assign_input(arg, &self->offset, MP_QSTR_offset); +} + +bool common_hal_synthio_lfo_get_once(synthio_lfo_obj_t *self) { + return self->once; +} +void common_hal_synthio_lfo_set_once(synthio_lfo_obj_t *self, bool arg) { + self->once = arg; +} + +mp_float_t common_hal_synthio_lfo_get_value(synthio_lfo_obj_t *self) { + return self->value; +} + +mp_float_t common_hal_synthio_lfo_get_phase(synthio_lfo_obj_t *self) { + return self->accum; +} + +void common_hal_synthio_lfo_retrigger(synthio_lfo_obj_t *self) { + self->accum = 0; +} diff --git a/shared-module/synthio/LFO.h b/shared-module/synthio/LFO.h new file mode 100644 index 0000000000..8012d4c433 --- /dev/null +++ b/shared-module/synthio/LFO.h @@ -0,0 +1,62 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * 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" +#include "shared-bindings/synthio/__init__.h" + +typedef struct synthio_lfo_slot { + mp_obj_t obj; + mp_float_t value; +} synthio_lfo_slot_t; + +typedef struct synthio_lfo_obj { + mp_obj_base_t base; + + int32_t sample_rate; + + uint8_t last_tick; + bool once; + + synthio_lfo_slot_t rate, scale, offset; + mp_float_t accum, value; + + mp_obj_t waveform_obj; + mp_buffer_info_t waveform_bufinfo; +} synthio_lfo_obj_t; + +// Update the value inside the lfo slot if the value is an LFO, returning the new value +mp_float_t synthio_lfo_obj_tick(synthio_lfo_slot_t *lfo_slot); +// the same, but the output is constrained to be between lo and hi +mp_float_t synthio_lfo_obj_tick_limited(synthio_lfo_slot_t *lfo_slot, mp_float_t lo, mp_float_t hi); +// the same, but the output is constrained to be between lo and hi and converted to an integer with `shift` fractional bits +int32_t synthio_lfo_obj_tick_scaled(synthio_lfo_slot_t *lfo_slot, mp_float_t lo, mp_float_t hi, int shift); + +// Assign an object (which may be a float or a synthio_lfo_obj_t) to an lfo slot +void synthio_lfo_assign_input(mp_obj_t obj, synthio_lfo_slot_t *lfo_slot, qstr arg_name); diff --git a/shared-module/synthio/Note.c b/shared-module/synthio/Note.c index 680c1c1a1d..6f12166020 100644 --- a/shared-module/synthio/Note.c +++ b/shared-module/synthio/Note.c @@ -30,10 +30,6 @@ #include "shared-bindings/synthio/Note.h" #include "shared-bindings/synthio/__init__.h" -static int32_t round_float_to_int(mp_float_t f) { - return (int32_t)(f + MICROPY_FLOAT_CONST(0.5)); -} - mp_float_t common_hal_synthio_note_get_frequency(synthio_note_obj_t *self) { return self->frequency; } @@ -62,73 +58,36 @@ void common_hal_synthio_note_set_ring_frequency(synthio_note_obj_t *self, mp_flo self->ring_frequency_scaled = synthio_frequency_convert_float_to_scaled(val); } -mp_float_t common_hal_synthio_note_get_panning(synthio_note_obj_t *self) { - return self->panning; +mp_obj_t common_hal_synthio_note_get_panning(synthio_note_obj_t *self) { + return self->panning.obj; } -void common_hal_synthio_note_set_panning(synthio_note_obj_t *self, mp_float_t value_in) { - mp_float_t val = mp_arg_validate_float_range(value_in, -1, 1, MP_QSTR_panning); - self->panning = val; - if (val >= 0) { - self->left_panning_scaled = 32768; - self->right_panning_scaled = 32768 - round_float_to_int(val * 32768); - } else { - self->right_panning_scaled = 32768; - self->left_panning_scaled = 32768 + round_float_to_int(val * 32768); - } +void common_hal_synthio_note_set_panning(synthio_note_obj_t *self, mp_obj_t value_in) { + synthio_lfo_assign_input(value_in, &self->panning, MP_QSTR_panning); } -mp_float_t common_hal_synthio_note_get_tremolo_depth(synthio_note_obj_t *self) { - return self->tremolo_descr.amplitude; +mp_obj_t common_hal_synthio_note_get_amplitude(synthio_note_obj_t *self) { + return self->amplitude.obj; } -void common_hal_synthio_note_set_tremolo_depth(synthio_note_obj_t *self, mp_float_t value_in) { - mp_float_t val = mp_arg_validate_float_range(value_in, 0, 1, MP_QSTR_tremolo_depth); - self->tremolo_descr.amplitude = val; - self->tremolo_state.amplitude_scaled = round_float_to_int(val * 32768); +void common_hal_synthio_note_set_amplitude(synthio_note_obj_t *self, mp_obj_t value_in) { + synthio_lfo_assign_input(value_in, &self->amplitude, MP_QSTR_amplitude); } -mp_float_t common_hal_synthio_note_get_tremolo_rate(synthio_note_obj_t *self) { - return self->tremolo_descr.frequency; +mp_obj_t common_hal_synthio_note_get_bend(synthio_note_obj_t *self) { + return self->bend.obj; } -void common_hal_synthio_note_set_tremolo_rate(synthio_note_obj_t *self, mp_float_t value_in) { - mp_float_t val = mp_arg_validate_float_range(value_in, 0, 60, MP_QSTR_tremolo_rate); - self->tremolo_descr.frequency = val; - if (self->sample_rate != 0) { - self->tremolo_state.dds = synthio_frequency_convert_float_to_dds(val * 65536, self->sample_rate); - } +void common_hal_synthio_note_set_bend(synthio_note_obj_t *self, mp_obj_t value_in) { + synthio_lfo_assign_input(value_in, &self->bend, MP_QSTR_bend); } -mp_float_t common_hal_synthio_note_get_bend_depth(synthio_note_obj_t *self) { - return self->bend_descr.amplitude; +mp_obj_t common_hal_synthio_note_get_ring_bend(synthio_note_obj_t *self) { + return self->ring_bend.obj; } -void common_hal_synthio_note_set_bend_depth(synthio_note_obj_t *self, mp_float_t value_in) { - mp_float_t val = mp_arg_validate_float_range(value_in, -1, 1, MP_QSTR_bend_depth); - self->bend_descr.amplitude = val; - self->bend_state.amplitude_scaled = round_float_to_int(val * 32768); -} - -mp_float_t common_hal_synthio_note_get_bend_rate(synthio_note_obj_t *self) { - return self->bend_descr.frequency; -} - -synthio_bend_mode_t common_hal_synthio_note_get_bend_mode(synthio_note_obj_t *self) { - return self->bend_mode; -} - -void common_hal_synthio_note_set_bend_mode(synthio_note_obj_t *self, synthio_bend_mode_t value) { - self->bend_mode = value; -} - - -void common_hal_synthio_note_set_bend_rate(synthio_note_obj_t *self, mp_float_t value_in) { - mp_float_t val = mp_arg_validate_float_range(value_in, 0, 60, MP_QSTR_bend_rate); - self->bend_descr.frequency = val; - if (self->sample_rate != 0) { - self->bend_state.dds = synthio_frequency_convert_float_to_dds(val * 65536, self->sample_rate); - } +void common_hal_synthio_note_set_ring_bend(synthio_note_obj_t *self, mp_obj_t value_in) { + synthio_lfo_assign_input(value_in, &self->ring_bend, MP_QSTR_ring_bend); } mp_obj_t common_hal_synthio_note_get_envelope_obj(synthio_note_obj_t *self) { @@ -184,22 +143,10 @@ void synthio_note_recalculate(synthio_note_obj_t *self, int32_t sample_rate) { if (self->envelope_obj != mp_const_none) { synthio_envelope_definition_set(&self->envelope_def, self->envelope_obj, sample_rate); } - - synthio_lfo_set(&self->tremolo_state, &self->tremolo_descr, sample_rate); - self->tremolo_state.offset_scaled = 32768 - self->tremolo_state.amplitude_scaled; - synthio_lfo_set(&self->bend_state, &self->bend_descr, sample_rate); - self->bend_state.offset_scaled = 32768; } void synthio_note_start(synthio_note_obj_t *self, int32_t sample_rate) { synthio_note_recalculate(self, sample_rate); - if (self->bend_mode != SYNTHIO_BEND_MODE_VIBRATO) { - self->bend_state.phase = 0; - } -} - -uint32_t synthio_note_envelope(synthio_note_obj_t *self) { - return self->amplitude_scaled; } // Perform a pitch bend operation @@ -213,10 +160,10 @@ uint32_t synthio_note_envelope(synthio_note_obj_t *self) { STATIC uint16_t pitch_bend_table[] = { 0, 1948, 4013, 6200, 8517, 10972, 13573, 16329, 19248, 22341, 25618, 29090, 32768 }; -STATIC uint32_t pitch_bend(uint32_t frequency_scaled, uint16_t bend_value) { - bool down = (bend_value < 32768); - if (!down) { - bend_value -= 32768; +STATIC uint32_t pitch_bend(uint32_t frequency_scaled, int16_t bend_value) { + bool down = (bend_value < 0); + if (down) { + bend_value += 32768; } uint32_t bend_value_semitone = (uint32_t)bend_value * 24; // 65536/semitone uint32_t semitone = bend_value_semitone >> 16; @@ -227,26 +174,34 @@ STATIC uint32_t pitch_bend(uint32_t frequency_scaled, uint16_t bend_value) { return (frequency_scaled * (uint64_t)f) >> (15 + down); } -STATIC int synthio_bend_value(synthio_note_obj_t *self, int16_t dur) { - switch (self->bend_mode) { - case SYNTHIO_BEND_MODE_STATIC: - return self->bend_state.amplitude_scaled + self->bend_state.offset_scaled; - case SYNTHIO_BEND_MODE_VIBRATO: - return synthio_lfo_step(&self->bend_state, dur); - case SYNTHIO_BEND_MODE_SWEEP: - return synthio_sweep_step(&self->bend_state, dur); - case SYNTHIO_BEND_MODE_SWEEP_IN: - return synthio_sweep_in_step(&self->bend_state, dur); - default: - return 32768; - } -} +#define ZERO MICROPY_FLOAT_CONST(0.) +#define ONE MICROPY_FLOAT_CONST(1.) +#define ALMOST_ONE (MICROPY_FLOAT_CONST(32767.) / 32768) uint32_t synthio_note_step(synthio_note_obj_t *self, int32_t sample_rate, int16_t dur, uint16_t loudness[2]) { - int tremolo_value = synthio_lfo_step(&self->tremolo_state, dur); - loudness[0] = (((loudness[0] * tremolo_value) >> 15) * self->left_panning_scaled) >> 15; - loudness[1] = (((loudness[1] * tremolo_value) >> 15) * self->right_panning_scaled) >> 15; - int bend_value = synthio_bend_value(self, dur); + int panning = synthio_lfo_obj_tick_scaled(&self->panning, -ALMOST_ONE, ALMOST_ONE, 15); + int left_panning_scaled, right_panning_scaled; + if (panning >= 0) { + left_panning_scaled = 32768; + right_panning_scaled = 32767 - panning; + } else { + right_panning_scaled = 32768; + left_panning_scaled = 32767 + panning; + } + + int amplitude = synthio_lfo_obj_tick_scaled(&self->amplitude, ZERO, ALMOST_ONE, 15); + left_panning_scaled = (left_panning_scaled * amplitude) >> 15; + right_panning_scaled = (right_panning_scaled * amplitude) >> 15; + loudness[0] = (loudness[0] * left_panning_scaled) >> 15; + loudness[1] = (loudness[1] * right_panning_scaled) >> 15; + + if (self->ring_frequency_scaled != 0) { + int ring_bend_value = synthio_lfo_obj_tick_scaled(&self->ring_bend, -ONE, ALMOST_ONE, 15); + self->ring_frequency_bent = pitch_bend(self->ring_frequency_scaled, ring_bend_value); + } + + int bend_value = synthio_lfo_obj_tick_scaled(&self->bend, -ONE, ALMOST_ONE, 15); uint32_t frequency_scaled = pitch_bend(self->frequency_scaled, bend_value); return frequency_scaled; + } diff --git a/shared-module/synthio/Note.h b/shared-module/synthio/Note.h index d7b9a6f67f..6586e396a7 100644 --- a/shared-module/synthio/Note.h +++ b/shared-module/synthio/Note.h @@ -27,25 +27,22 @@ #pragma once #include "shared-module/synthio/__init__.h" +#include "shared-module/synthio/LFO.h" #include "shared-bindings/synthio/__init__.h" typedef struct synthio_note_obj { mp_obj_base_t base; + synthio_lfo_slot_t panning, bend, amplitude, ring_bend; + mp_float_t frequency, ring_frequency; - mp_float_t panning; mp_obj_t waveform_obj, envelope_obj, ring_waveform_obj; int32_t sample_rate; int32_t frequency_scaled; - int32_t ring_frequency_scaled; - int32_t amplitude_scaled; - int32_t left_panning_scaled, right_panning_scaled; + int32_t ring_frequency_scaled, ring_frequency_bent; bool filter; - synthio_bend_mode_t bend_mode; - synthio_lfo_descr_t tremolo_descr, bend_descr; - synthio_lfo_state_t tremolo_state, bend_state; mp_buffer_info_t waveform_buf; mp_buffer_info_t ring_waveform_buf; @@ -56,4 +53,3 @@ void synthio_note_recalculate(synthio_note_obj_t *self, int32_t sample_rate); uint32_t synthio_note_step(synthio_note_obj_t *self, int32_t sample_rate, int16_t dur, uint16_t loudness[2]); void synthio_note_start(synthio_note_obj_t *self, int32_t sample_rate); bool synthio_note_playing(synthio_note_obj_t *self); -uint32_t synthio_note_envelope(synthio_note_obj_t *self); diff --git a/shared-module/synthio/__init__.c b/shared-module/synthio/__init__.c index 6b6a45f446..37ac87465e 100644 --- a/shared-module/synthio/__init__.c +++ b/shared-module/synthio/__init__.c @@ -32,6 +32,9 @@ #include #include +mp_float_t synthio_global_rate_scale; +uint8_t synthio_global_tick; + STATIC const int16_t square_wave[] = {-32768, 32767}; STATIC const uint16_t notes[] = {8372, 8870, 9397, 9956, 10548, 11175, 11840, @@ -219,7 +222,7 @@ static void synth_note_into_buffer(synthio_synth_t *synth, int chan, int32_t *ou if (note->ring_frequency_scaled != 0 && note->ring_waveform_buf.buf) { ring_waveform = note->ring_waveform_buf.buf; ring_waveform_length = note->ring_waveform_buf.len / sizeof(int16_t); - ring_dds_rate = synthio_frequency_convert_scaled_to_dds((uint64_t)note->ring_frequency_scaled * ring_waveform_length, sample_rate); + ring_dds_rate = synthio_frequency_convert_scaled_to_dds((uint64_t)note->ring_frequency_bent * ring_waveform_length, sample_rate); uint32_t lim = ring_waveform_length << SYNTHIO_FREQUENCY_SHIFT; if (ring_dds_rate > lim / sizeof(int16_t)) { ring_dds_rate = 0; // can't ring at that frequency @@ -350,6 +353,8 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t return; } + shared_bindings_synthio_lfo_tick(synth->sample_rate); + synth->buffer_index = !synth->buffer_index; synth->other_channel = 1 - channel; synth->other_buffer_index = synth->buffer_index; @@ -587,3 +592,8 @@ int synthio_lfo_step(synthio_lfo_state_t *state, uint16_t dur) { } return v / 16384 + state->offset_scaled; } + +void shared_bindings_synthio_lfo_tick(uint32_t sample_rate) { + synthio_global_rate_scale = (mp_float_t)SYNTHIO_MAX_DUR / sample_rate; + synthio_global_tick++; +} diff --git a/shared-module/synthio/__init__.h b/shared-module/synthio/__init__.h index a3d0ca57fd..5586ce87f9 100644 --- a/shared-module/synthio/__init__.h +++ b/shared-module/synthio/__init__.h @@ -112,3 +112,7 @@ void synthio_lfo_set(synthio_lfo_state_t *state, const synthio_lfo_descr_t *desc int synthio_lfo_step(synthio_lfo_state_t *state, uint16_t dur); int synthio_sweep_step(synthio_lfo_state_t *state, uint16_t dur); int synthio_sweep_in_step(synthio_lfo_state_t *state, uint16_t dur); + +extern mp_float_t synthio_global_rate_scale; +extern uint8_t synthio_global_tick; +void shared_bindings_synthio_lfo_tick(uint32_t sample_rate); diff --git a/tests/circuitpython-manual/synthio/note/demo.py b/tests/circuitpython-manual/synthio/note/demo.py index 49a811fd43..8320613f22 100644 --- a/tests/circuitpython-manual/synthio/note/demo.py +++ b/tests/circuitpython-manual/synthio/note/demo.py @@ -65,6 +65,8 @@ noise = np.array( dtype=np.int16, ) +bend_in = np.linspace(32767, 0, num=SAMPLE_SIZE, endpoint=True, dtype=np.int16) + envelope = synthio.Envelope( attack_time=0.1, decay_time=0.05, release_time=0.2, attack_level=1, sustain_level=0.8 ) @@ -157,8 +159,8 @@ def synthesize4(synth): synth.press(chord) for i in range(16): for c in chord: - c.tremolo_depth = i / 50 - c.tremolo_rate = (i + 1) / 4 + d = i / 50 + c.amplitude = synthio.LFO(scale=d / 2, offset=1 - d, rate=(i + 1) / 4, waveform=sine) yield 48 yield 36 @@ -171,17 +173,14 @@ def synthesize5(synth): synth.press(chord) for i in range(16): for c in chord: - c.bend_depth = 1 / 24 - c.bend_rate = (i + 1) / 2 + c.bend = synthio.LFO(scale=1 / 24, rate=(i + 1) / 2, waveform=sine) yield 24 synth.release_all() yield 100 for c in chord: synth.release_all() - c.bend_mode = synthio.BendMode.SWEEP_IN - c.bend_depth = randf(-1, 1) - c.bend_rate = 1 / 2 + c.bend = synthio.LFO(scale=randf(-1, 1), rate=1 / 2, waveform=bend_in) synth.press(chord) yield 320 diff --git a/tests/circuitpython-manual/synthio/note/envelope.py b/tests/circuitpython-manual/synthio/note/envelope.py index 9421cf8c1c..e5a9e957c8 100644 --- a/tests/circuitpython-manual/synthio/note/envelope.py +++ b/tests/circuitpython-manual/synthio/note/envelope.py @@ -25,6 +25,9 @@ fast_decay_envelope = synthio.Envelope( synth = synthio.Synthesizer(sample_rate=48000) +# for a note without sustain phase, +# switching to an envelope with decay_time 0.25 is about the same as +# releasing with a release time of 0.25 def synthesize(synth): notes = (synthio.Note(frequency=440, waveform=sine, envelope=envelope),) synth.press(notes) diff --git a/tests/circuitpython/synthesizer_note.py.exp b/tests/circuitpython/synthesizer_note.py.exp index 801f97131e..7b834870e9 100644 --- a/tests/circuitpython/synthesizer_note.py.exp +++ b/tests/circuitpython/synthesizer_note.py.exp @@ -1,30 +1,30 @@ () [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] -(Note(frequency=830.6076004423605, panning=0.0, tremolo_rate=0.0, tremolo_depth=0.0, bend_rate=0.0, bend_depth=0.0, bend_mode=synthio.BendMode.VIBRATO, waveform=None, envelope=None, filter=True, ring_frequency=0.0, ring_waveform=None),) -[-16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383] -(Note(frequency=830.6076004423605, panning=0.0, tremolo_rate=0.0, tremolo_depth=0.0, bend_rate=0.0, bend_depth=0.0, bend_mode=synthio.BendMode.VIBRATO, waveform=None, envelope=None, filter=True, ring_frequency=0.0, ring_waveform=None), Note(frequency=830.6076004423605, panning=0.0, tremolo_rate=0.0, tremolo_depth=0.0, bend_rate=0.0, bend_depth=0.0, bend_mode=synthio.BendMode.VIBRATO, waveform=None, envelope=None, filter=True, ring_frequency=0.0, ring_waveform=None)) -[0, 0, 0, 0, 0, 0, 0, 0, 28045, 0, 0, 0, 0, -28046, 0, 0, 0, 0, 28045, 0, 0, 0, 0, -28046] -(Note(frequency=830.6076004423605, panning=0.0, tremolo_rate=0.0, tremolo_depth=0.0, bend_rate=0.0, bend_depth=0.0, bend_mode=synthio.BendMode.VIBRATO, waveform=None, envelope=None, filter=True, ring_frequency=0.0, ring_waveform=None),) -[0, 0, 0, 28045, 0, 0, 0, 0, 0, 0, 0, 0, 28045, 0, 0, 0, 0, -28046, 0, 0, 0, 0, 28045, 0] -(-5242, 5242) -(-10485, 10484) -(-15727, 15727) -(-16383, 16383) -(-14286, 14286) -(-13106, 13106) -(-13106, 13106) -(-13106, 13106) -(-13106, 13106) -(-13106, 13106) -(-13106, 13106) -(-13106, 13106) -(-13106, 13106) -(-11009, 11009) -(-8912, 8912) -(-6815, 6815) -(-4718, 4718) -(-2621, 2621) -(-524, 524) +(Note(frequency=830.6076004423605, panning=0, amplitude=1, bend=0, waveform=None, envelope=None, filter=True, ring_frequency=0.0, ring_bend=0, ring_waveform=None),) +[-16383, -16383, -16383, -16383, 16382, 16382, 16382, 16382, 16382, -16383, -16383, -16383, -16383, -16383, 16382, 16382, 16382, 16382, 16382, -16383, -16383, -16383, -16383, -16383] +(Note(frequency=830.6076004423605, panning=0, amplitude=1, bend=0, waveform=None, envelope=None, filter=True, ring_frequency=0.0, ring_bend=0, ring_waveform=None), Note(frequency=830.6076004423605, panning=0, amplitude=1, bend=0, waveform=None, envelope=None, filter=True, ring_frequency=0.0, ring_bend=0, ring_waveform=None)) +[-1, -1, -1, -1, -1, -1, -1, -1, 28045, -1, -1, -1, -1, -28046, -1, -1, -1, -1, 28045, -1, -1, -1, -1, -28046] +(Note(frequency=830.6076004423605, panning=0, amplitude=1, bend=0, waveform=None, envelope=None, filter=True, ring_frequency=0.0, ring_bend=0, ring_waveform=None),) +[-1, -1, -1, 28045, -1, -1, -1, -1, -1, -1, -1, -1, 28045, -1, -1, -1, -1, -28046, -1, -1, -1, -1, 28045, -1] +(-5242, 5241) +(-10484, 10484) +(-15727, 15726) +(-16383, 16382) +(-14286, 14285) +(-13106, 13105) +(-13106, 13105) +(-13106, 13105) +(-13106, 13105) +(-13106, 13105) +(-13106, 13105) +(-13106, 13105) +(-13106, 13105) +(-11009, 11008) +(-8912, 8911) +(-6815, 6814) +(-4718, 4717) +(-2621, 2620) +(-524, 523) (0, 0) (0, 0) (0, 0)