diff --git a/.gitmodules b/.gitmodules index c690e714e8..91afffe6a6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -338,3 +338,6 @@ [submodule "frozen/circuitpython-pcf85063a"] path = frozen/circuitpython-pcf85063a url = https://github.com/bablokb/circuitpython-pcf85063a +[submodule "frozen/Adafruit_CircuitPython_Wave"] + path = frozen/Adafruit_CircuitPython_Wave + url = http://github.com/adafruit/Adafruit_CircuitPython_Wave.git diff --git a/frozen/Adafruit_CircuitPython_Wave b/frozen/Adafruit_CircuitPython_Wave new file mode 160000 index 0000000000..02b748f2e6 --- /dev/null +++ b/frozen/Adafruit_CircuitPython_Wave @@ -0,0 +1 @@ +Subproject commit 02b748f2e6826dc442c842885e58b07ad10d9287 diff --git a/ports/atmel-samd/boards/feather_m4_express/mpconfigboard.mk b/ports/atmel-samd/boards/feather_m4_express/mpconfigboard.mk index 2606572d8c..9a5008be85 100644 --- a/ports/atmel-samd/boards/feather_m4_express/mpconfigboard.mk +++ b/ports/atmel-samd/boards/feather_m4_express/mpconfigboard.mk @@ -11,3 +11,4 @@ EXTERNAL_FLASH_DEVICES = GD25Q16C LONGINT_IMPL = MPZ CIRCUITPY__EVE = 1 +CIRCUITPY_SYNTHIO = 0 diff --git a/ports/atmel-samd/boards/metro_m4_airlift_lite/mpconfigboard.mk b/ports/atmel-samd/boards/metro_m4_airlift_lite/mpconfigboard.mk index 1e63476596..ed1cd2455c 100644 --- a/ports/atmel-samd/boards/metro_m4_airlift_lite/mpconfigboard.mk +++ b/ports/atmel-samd/boards/metro_m4_airlift_lite/mpconfigboard.mk @@ -11,3 +11,4 @@ EXTERNAL_FLASH_DEVICES = "S25FL116K, S25FL216K, GD25Q16C" LONGINT_IMPL = MPZ CIRCUITPY__EVE = 1 +CIRCUITPY_SYNTHIO = 0 diff --git a/ports/atmel-samd/boards/metro_m4_express/mpconfigboard.mk b/ports/atmel-samd/boards/metro_m4_express/mpconfigboard.mk index 553bf14f2e..50780cff07 100644 --- a/ports/atmel-samd/boards/metro_m4_express/mpconfigboard.mk +++ b/ports/atmel-samd/boards/metro_m4_express/mpconfigboard.mk @@ -11,3 +11,4 @@ EXTERNAL_FLASH_DEVICES = "S25FL116K, S25FL216K, GD25Q16C" LONGINT_IMPL = MPZ CIRCUITPY__EVE = 1 +CIRCUITPY_SYNTHIO = 0 diff --git a/shared-bindings/synthio/MidiTrack.c b/shared-bindings/synthio/MidiTrack.c index 31d8cbc7f0..2d0d2ab69f 100644 --- a/shared-bindings/synthio/MidiTrack.c +++ b/shared-bindings/synthio/MidiTrack.c @@ -88,9 +88,6 @@ 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; @@ -98,8 +95,8 @@ STATIC mp_obj_t synthio_miditrack_make_new(const mp_obj_type_t *type, size_t n_a (uint8_t *)bufinfo.buf, bufinfo.len, args[ARG_tempo].u_int, args[ARG_sample_rate].u_int, - bufinfo_waveform.buf, - bufinfo_waveform.len / 2, + args[ARG_waveform].u_obj, + mp_const_none, args[ARG_envelope].u_obj ); diff --git a/shared-bindings/synthio/MidiTrack.h b/shared-bindings/synthio/MidiTrack.h index 1a76ed36f4..5c6bce46cd 100644 --- a/shared-bindings/synthio/MidiTrack.h +++ b/shared-bindings/synthio/MidiTrack.h @@ -27,12 +27,11 @@ #pragma once #include "shared-module/synthio/MidiTrack.h" +#include "py/obj.h" 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 int16_t *waveform, uint16_t waveform_len, - mp_obj_t envelope); +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, mp_obj_t waveform_obj, mp_obj_t filter_obj, mp_obj_t envelope_obj); 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/Note.c b/shared-bindings/synthio/Note.c index 4cd0abe0d4..f058b16d2e 100644 --- a/shared-bindings/synthio/Note.c +++ b/shared-bindings/synthio/Note.c @@ -30,40 +30,45 @@ #include "py/objproperty.h" #include "py/runtime.h" #include "shared-bindings/util.h" +#include "shared-bindings/synthio/__init__.h" #include "shared-bindings/synthio/Note.h" #include "shared-module/synthio/Note.h" static const mp_arg_t note_properties[] = { { MP_QSTR_frequency, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = NULL } }, - { MP_QSTR_amplitude, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(1) } }, + { 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_vibrato_rate, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = NULL } }, - { MP_QSTR_vibrato_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_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_waveform, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_NONE } }, }; //| class Note: //| def __init__( //| self, //| *, //| frequency: float, -//| amplitude: float = 1.0, +//| panning: float = 0.0, //| waveform: Optional[ReadableBuffer] = None, //| envelope: Optional[Envelope] = None, //| tremolo_depth: float = 0.0, //| tremolo_rate: float = 0.0, -//| vibrato_depth: float = 0.0, -//| vibrato_rate: float = 0.0, +//| bend_depth: float = 0.0, +//| bend_rate: float = 0.0, +//| bend_mode: "BendMode" = BendMode.VIBRATO, //| ) -> None: -//| """Construct a Note object, with a frequency in Hz, and optional amplitude (volume), waveform, envelope, tremolo (volume change) and vibrato (frequency change). +//| """Construct a Note object, with a frequency in Hz, and optional panning, waveform, envelope, tremolo (volume change) and bend (frequency change). //| //| If waveform or envelope are `None` the synthesizer object's default waveform or envelope are used. //| //| If the same Note object is played on multiple Synthesizer objects, the result is undefined. //| """ STATIC mp_obj_t synthio_note_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_frequency, ARG_amplitude, ARG_waveform, ARG_envelope, ARG_tremolo_rate, ARG_tremolo_depth, ARG_vibrato_rate, ARG_vibrato_depth }; mp_arg_val_t args[MP_ARRAY_SIZE(note_properties)]; mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(note_properties), note_properties, args); @@ -98,23 +103,46 @@ MP_PROPERTY_GETSET(synthio_note_frequency_obj, (mp_obj_t)&synthio_note_get_frequency_obj, (mp_obj_t)&synthio_note_set_frequency_obj); -//| amplitude: float -//| """The base amplitude of the note, from 0 to 1""" -STATIC mp_obj_t synthio_note_get_amplitude(mp_obj_t self_in) { +//| filter: bool +//| """True if the note should be processed via the synthesizer's FIR filter.""" +STATIC mp_obj_t synthio_note_get_filter(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_amplitude(self)); + return mp_obj_new_bool(common_hal_synthio_note_get_filter(self)); } -MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_amplitude_obj, synthio_note_get_amplitude); +MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_filter_obj, synthio_note_get_filter); -STATIC mp_obj_t synthio_note_set_amplitude(mp_obj_t self_in, mp_obj_t arg) { +STATIC mp_obj_t synthio_note_set_filter(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_amplitude(self, mp_obj_get_float(arg)); + common_hal_synthio_note_set_filter(self, mp_obj_is_true(arg)); return mp_const_none; } -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); +MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_filter_obj, synthio_note_set_filter); +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 +//| """Defines the channel(s) in which the note appears. +//| +//| -1 is left channel only, 0 is both channels, and 1 is right channel. +//| For fractional values, the note plays at full amplitude in one channel +//| and partial amplitude in the other channel. For instance -.5 plays at full +//| 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)); +} +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)); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_panning_obj, synthio_note_set_panning); +MP_PROPERTY_GETSET(synthio_note_panning_obj, + (mp_obj_t)&synthio_note_get_panning_obj, + (mp_obj_t)&synthio_note_set_panning_obj); //| tremolo_depth: float @@ -158,46 +186,67 @@ 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); -//| vibrato_depth: float -//| """The vibrato depth of the note, from 0 to 1 //| -//| A depth of 0 disables vibrato. A depth of 1 corresponds to a vibrato of ±1 -//| octave. A depth of (1/12) = 0.833 corresponds to a vibrato of ±1 semitone, +//| 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_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 +//| 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. //| """ -STATIC mp_obj_t synthio_note_get_vibrato_depth(mp_obj_t self_in) { +STATIC mp_obj_t synthio_note_get_bend_depth(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_vibrato_depth(self)); + return mp_obj_new_float(common_hal_synthio_note_get_bend_depth(self)); } -MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_vibrato_depth_obj, synthio_note_get_vibrato_depth); +MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_bend_depth_obj, synthio_note_get_bend_depth); -STATIC mp_obj_t synthio_note_set_vibrato_depth(mp_obj_t self_in, mp_obj_t arg) { +STATIC mp_obj_t synthio_note_set_bend_depth(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_vibrato_depth(self, mp_obj_get_float(arg)); + common_hal_synthio_note_set_bend_depth(self, mp_obj_get_float(arg)); return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_vibrato_depth_obj, synthio_note_set_vibrato_depth); -MP_PROPERTY_GETSET(synthio_note_vibrato_depth_obj, - (mp_obj_t)&synthio_note_get_vibrato_depth_obj, - (mp_obj_t)&synthio_note_set_vibrato_depth_obj); +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); -//| vibrato_rate: float -//| """The vibrato rate of the note, in Hz.""" -STATIC mp_obj_t synthio_note_get_vibrato_rate(mp_obj_t self_in) { +//| 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_vibrato_rate(self)); + return mp_obj_new_float(common_hal_synthio_note_get_bend_rate(self)); } -MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_vibrato_rate_obj, synthio_note_get_vibrato_rate); +MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_bend_rate_obj, synthio_note_get_bend_rate); -STATIC mp_obj_t synthio_note_set_vibrato_rate(mp_obj_t self_in, mp_obj_t arg) { +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_vibrato_rate(self, mp_obj_get_float(arg)); + 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_vibrato_rate_obj, synthio_note_set_vibrato_rate); -MP_PROPERTY_GETSET(synthio_note_vibrato_rate_obj, - (mp_obj_t)&synthio_note_get_vibrato_rate_obj, - (mp_obj_t)&synthio_note_set_vibrato_rate_obj); +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); //| waveform: Optional[ReadableBuffer] //| """The waveform of this note. Setting the waveform to a buffer of a different size resets the note's phase.""" @@ -237,6 +286,49 @@ MP_PROPERTY_GETSET(synthio_note_envelope_obj, (mp_obj_t)&synthio_note_get_envelope_obj, (mp_obj_t)&synthio_note_set_envelope_obj); +//| ring_frequency: float +//| """The ring frequency of the note, in Hz. Zero disables. +//| +//| For ring to take effect, both ``ring_frequency`` and ``ring_waveform`` must be set.""" +STATIC mp_obj_t synthio_note_get_ring_frequency(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_ring_frequency(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_ring_frequency_obj, synthio_note_get_ring_frequency); + +STATIC mp_obj_t synthio_note_set_ring_frequency(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_frequency(self, mp_obj_get_float(arg)); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_ring_frequency_obj, synthio_note_set_ring_frequency); +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_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. +//| +//| For ring to take effect, both ``ring_frequency`` and ``ring_waveform`` must be set.""" +//| +STATIC mp_obj_t synthio_note_get_ring_waveform(mp_obj_t self_in) { + synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_synthio_note_get_ring_waveform_obj(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_ring_waveform_obj, synthio_note_get_ring_waveform); + +STATIC mp_obj_t synthio_note_set_ring_waveform(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_waveform(self, arg); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_ring_waveform_obj, synthio_note_set_ring_waveform); +MP_PROPERTY_GETSET(synthio_note_ring_waveform_obj, + (mp_obj_t)&synthio_note_get_ring_waveform_obj, + (mp_obj_t)&synthio_note_set_ring_waveform_obj); + + + static void note_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; properties_print_helper(print, self_in, note_properties, MP_ARRAY_SIZE(note_properties)); @@ -244,13 +336,17 @@ static void note_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_ STATIC const mp_rom_map_elem_t synthio_note_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_frequency), MP_ROM_PTR(&synthio_note_frequency_obj) }, - { MP_ROM_QSTR(MP_QSTR_amplitude), MP_ROM_PTR(&synthio_note_amplitude_obj) }, + { MP_ROM_QSTR(MP_QSTR_filter), MP_ROM_PTR(&synthio_note_filter_obj) }, + { 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_vibrato_depth), MP_ROM_PTR(&synthio_note_vibrato_depth_obj) }, - { MP_ROM_QSTR(MP_QSTR_vibrato_rate), MP_ROM_PTR(&synthio_note_vibrato_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_ring_frequency), MP_ROM_PTR(&synthio_note_ring_frequency_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 ab9d925f03..b87310576a 100644 --- a/shared-bindings/synthio/Note.h +++ b/shared-bindings/synthio/Note.h @@ -4,12 +4,19 @@ typedef struct synthio_note_obj synthio_note_obj_t; extern const mp_obj_type_t synthio_note_type; +typedef enum synthio_bend_mode_e synthio_bend_mode_t; mp_float_t common_hal_synthio_note_get_frequency(synthio_note_obj_t *self); void common_hal_synthio_note_set_frequency(synthio_note_obj_t *self, mp_float_t value); -mp_float_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_float_t value); +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_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_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); @@ -17,14 +24,20 @@ void common_hal_synthio_note_set_tremolo_rate(synthio_note_obj_t *self, mp_float 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); -mp_float_t common_hal_synthio_note_get_vibrato_rate(synthio_note_obj_t *self); -void common_hal_synthio_note_set_vibrato_rate(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_vibrato_depth(synthio_note_obj_t *self); -void common_hal_synthio_note_set_vibrato_depth(synthio_note_obj_t *self, mp_float_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_waveform_obj(synthio_note_obj_t *self); void common_hal_synthio_note_set_waveform(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); + mp_obj_t common_hal_synthio_note_get_envelope_obj(synthio_note_obj_t *self); void common_hal_synthio_note_set_envelope(synthio_note_obj_t *self, mp_obj_t value); diff --git a/shared-bindings/synthio/Synthesizer.c b/shared-bindings/synthio/Synthesizer.c index 5d9be09f70..67707a763d 100644 --- a/shared-bindings/synthio/Synthesizer.c +++ b/shared-bindings/synthio/Synthesizer.c @@ -44,6 +44,7 @@ //| self, //| *, //| sample_rate: int = 11025, +//| channel_count: int = 1, //| waveform: Optional[ReadableBuffer] = None, //| envelope: Optional[Envelope] = None, //| ) -> None: @@ -56,29 +57,31 @@ //| and do not support advanced features like tremolo or vibrato. //| //| :param int sample_rate: The desired playback sample rate; higher sample rate requires more memory +//| :param int channel_count: The number of output channels (1=mono, 2=stereo) //| :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) +//| :param ReadableBuffer filter: Coefficients of an FIR filter to apply to notes with ``filter=True``. If specified, must be a ReadableBuffer of type 'h' (signed 16 bit) //| :param Optional[Envelope] envelope: An object that defines the loudness of a note over time. The default envelope, `None` provides no ramping, voices turn instantly on and off. //| """ 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, ARG_envelope }; + enum { ARG_sample_rate, ARG_channel_count, ARG_waveform, ARG_envelope, ARG_filter }; static const mp_arg_t allowed_args[] = { { MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 11025} }, + { MP_QSTR_channel_count, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 1} }, { MP_QSTR_waveform, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none } }, { MP_QSTR_envelope, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none } }, + { MP_QSTR_filter, 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, + args[ARG_channel_count].u_int, + args[ARG_waveform].u_obj, + args[ARG_filter].u_obj, args[ARG_envelope].u_obj); return MP_OBJ_FROM_PTR(self); diff --git a/shared-bindings/synthio/Synthesizer.h b/shared-bindings/synthio/Synthesizer.h index ba6c3b99e3..d8000681b8 100644 --- a/shared-bindings/synthio/Synthesizer.h +++ b/shared-bindings/synthio/Synthesizer.h @@ -32,8 +32,8 @@ 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_length, - mp_obj_t envelope); + uint32_t sample_rate, int channel_count, mp_obj_t waveform_obj, mp_obj_t filter_obj, + mp_obj_t envelope_obj); 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); diff --git a/shared-bindings/synthio/__init__.c b/shared-bindings/synthio/__init__.c index 2e4fd7b8a4..132b254942 100644 --- a/shared-bindings/synthio/__init__.c +++ b/shared-bindings/synthio/__init__.c @@ -26,6 +26,7 @@ #include +#include "py/enum.h" #include "py/mperrno.h" #include "py/obj.h" #include "py/objnamedtuple.h" @@ -52,6 +53,7 @@ static const mp_arg_t envelope_properties[] = { { MP_QSTR_sustain_level, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL } }, }; +//| //| """Support for multi-channel audio synthesis //| //| At least 2 simultaneous notes are supported. samd5x, mimxrt10xx and rp2040 platforms support up to 12 notes. @@ -78,7 +80,7 @@ static const mp_arg_t envelope_properties[] = { //| //| :param float attack_time: The time in seconds it takes to ramp from 0 volume to attack_volume //| :param float decay_time: The time in seconds it takes to ramp from attack_volume to sustain_volume -//| :param float release_time: The time in seconds it takes to ramp from sustain_volume to release_volume. When a note is released before it has reached the sustain phase, the release is done with the same slope indicated by ``release_time`` and ``sustain_level`` +//| :param float release_time: The time in seconds it takes to ramp from sustain_volume to release_volume. When a note is released before it has reached the sustain phase, the release is done with the same slope indicated by ``release_time`` and ``sustain_level``. If the ``sustain_level`` is ``0.0`` then the release slope calculations use the ``attack_level`` instead. //| :param float attack_level: The level, in the range ``0.0`` to ``1.0`` of the peak volume of the attack phase //| :param float sustain_level: The level, in the range ``0.0`` to ``1.0`` of the volume of the sustain phase relative to the attack level //| """ @@ -205,10 +207,6 @@ 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; @@ -248,7 +246,8 @@ 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, bufinfo_waveform.buf, bufinfo_waveform.len / 2, + tempo, args[ARG_sample_rate].u_int, args[ARG_waveform].u_obj, + mp_const_none, args[ARG_envelope].u_obj ); @@ -284,9 +283,44 @@ 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); + 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_Synthesizer), MP_ROM_PTR(&synthio_synthesizer_type) }, diff --git a/shared-bindings/synthio/__init__.h b/shared-bindings/synthio/__init__.h index 37a5c77b34..6f3aed0124 100644 --- a/shared-bindings/synthio/__init__.h +++ b/shared-bindings/synthio/__init__.h @@ -27,7 +27,14 @@ #pragma once #include "py/objnamedtuple.h" +#include "py/enum.h" +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 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; extern int16_t shared_bindings_synthio_square_wave[]; extern const mp_obj_namedtuple_type_t synthio_envelope_type_obj; diff --git a/shared-module/synthio/MidiTrack.c b/shared-module/synthio/MidiTrack.c index e22753cb26..50c7151811 100644 --- a/shared-module/synthio/MidiTrack.c +++ b/shared-module/synthio/MidiTrack.c @@ -116,14 +116,13 @@ STATIC void start_parse(synthio_miditrack_obj_t *self) { 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 int16_t *waveform, uint16_t waveform_length, - mp_obj_t envelope) { + mp_obj_t waveform_obj, mp_obj_t filter_obj, mp_obj_t envelope_obj) { self->tempo = tempo; self->track.buf = (void *)buffer; self->track.len = len; - synthio_synth_init(&self->synth, sample_rate, waveform, waveform_length, envelope); + synthio_synth_init(&self->synth, sample_rate, 1, waveform_obj, mp_const_none, envelope_obj); start_parse(self); } diff --git a/shared-module/synthio/Note.c b/shared-module/synthio/Note.c index 84949163a7..16727b2152 100644 --- a/shared-module/synthio/Note.c +++ b/shared-module/synthio/Note.c @@ -44,14 +44,38 @@ void common_hal_synthio_note_set_frequency(synthio_note_obj_t *self, mp_float_t self->frequency_scaled = synthio_frequency_convert_float_to_scaled(val); } -mp_float_t common_hal_synthio_note_get_amplitude(synthio_note_obj_t *self) { - return self->amplitude; +bool common_hal_synthio_note_get_filter(synthio_note_obj_t *self) { + return self->filter; } -void common_hal_synthio_note_set_amplitude(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_amplitude); - self->amplitude = val; - self->amplitude_scaled = round_float_to_int(val * 32767); +void common_hal_synthio_note_set_filter(synthio_note_obj_t *self, bool value_in) { + self->filter = value_in; +} + +mp_float_t common_hal_synthio_note_get_ring_frequency(synthio_note_obj_t *self) { + return self->ring_frequency; +} + +void common_hal_synthio_note_set_ring_frequency(synthio_note_obj_t *self, mp_float_t value_in) { + mp_float_t val = mp_arg_validate_float_range(value_in, 0, 32767, MP_QSTR_ring_frequency); + self->ring_frequency = val; + 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; +} + +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); + } } mp_float_t common_hal_synthio_note_get_tremolo_depth(synthio_note_obj_t *self) { @@ -76,25 +100,34 @@ void common_hal_synthio_note_set_tremolo_rate(synthio_note_obj_t *self, mp_float } } -mp_float_t common_hal_synthio_note_get_vibrato_depth(synthio_note_obj_t *self) { - return self->vibrato_descr.amplitude; +mp_float_t common_hal_synthio_note_get_bend_depth(synthio_note_obj_t *self) { + return self->bend_descr.amplitude; } -void common_hal_synthio_note_set_vibrato_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_vibrato_depth); - self->vibrato_descr.amplitude = val; - self->vibrato_state.amplitude_scaled = round_float_to_int(val * 32767); +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 * 32767); } -mp_float_t common_hal_synthio_note_get_vibrato_rate(synthio_note_obj_t *self) { - return self->vibrato_descr.frequency; +mp_float_t common_hal_synthio_note_get_bend_rate(synthio_note_obj_t *self) { + return self->bend_descr.frequency; } -void common_hal_synthio_note_set_vibrato_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_vibrato_rate); - self->vibrato_descr.frequency = val; +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->vibrato_state.dds = synthio_frequency_convert_float_to_dds(val, self->sample_rate); + self->bend_state.dds = synthio_frequency_convert_float_to_dds(val, self->sample_rate); } } @@ -127,6 +160,21 @@ void common_hal_synthio_note_set_waveform(synthio_note_obj_t *self, mp_obj_t wav self->waveform_obj = waveform_in; } +mp_obj_t common_hal_synthio_note_get_ring_waveform_obj(synthio_note_obj_t *self) { + return self->ring_waveform_obj; +} + +void common_hal_synthio_note_set_ring_waveform(synthio_note_obj_t *self, mp_obj_t ring_waveform_in) { + if (ring_waveform_in == mp_const_none) { + memset(&self->ring_waveform_buf, 0, sizeof(self->ring_waveform_buf)); + } else { + mp_buffer_info_t bufinfo_ring_waveform; + synthio_synth_parse_waveform(&bufinfo_ring_waveform, ring_waveform_in); + self->ring_waveform_buf = bufinfo_ring_waveform; + } + self->ring_waveform_obj = ring_waveform_in; +} + void synthio_note_recalculate(synthio_note_obj_t *self, int32_t sample_rate) { if (sample_rate == self->sample_rate) { return; @@ -139,12 +187,15 @@ void synthio_note_recalculate(synthio_note_obj_t *self, int32_t 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->vibrato_state, &self->vibrato_descr, sample_rate); - self->vibrato_state.offset_scaled = 32768; + 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) { @@ -176,10 +227,26 @@ STATIC uint32_t pitch_bend(uint32_t frequency_scaled, uint16_t bend_value) { return (frequency_scaled * (uint64_t)f) >> (15 + down); } -uint32_t synthio_note_step(synthio_note_obj_t *self, int32_t sample_rate, int16_t dur, uint16_t *loudness) { +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; + } +} + +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); - int vibrato_value = synthio_lfo_step(&self->vibrato_state, dur); - *loudness = (*loudness * tremolo_value) >> 15; - uint32_t frequency_scaled = pitch_bend(self->frequency_scaled, vibrato_value); + 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); + 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 4efb197c07..d7b9a6f67f 100644 --- a/shared-module/synthio/Note.h +++ b/shared-module/synthio/Note.h @@ -27,27 +27,33 @@ #pragma once #include "shared-module/synthio/__init__.h" +#include "shared-bindings/synthio/__init__.h" typedef struct synthio_note_obj { mp_obj_base_t base; - mp_float_t frequency; - mp_float_t amplitude; - mp_obj_t waveform_obj, envelope_obj; + 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; - synthio_lfo_descr_t tremolo_descr, vibrato_descr; - synthio_lfo_state_t tremolo_state, vibrato_state; + int32_t left_panning_scaled, right_panning_scaled; + 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; synthio_envelope_definition_t envelope_def; } synthio_note_obj_t; 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); +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/Synthesizer.c b/shared-module/synthio/Synthesizer.c index ad37cccce5..c18c067976 100644 --- a/shared-module/synthio/Synthesizer.c +++ b/shared-module/synthio/Synthesizer.c @@ -32,10 +32,10 @@ void common_hal_synthio_synthesizer_construct(synthio_synthesizer_obj_t *self, - uint32_t sample_rate, const int16_t *waveform, uint16_t waveform_length, - mp_obj_t envelope) { + uint32_t sample_rate, int channel_count, mp_obj_t waveform_obj, mp_obj_t filter_obj, + mp_obj_t envelope_obj) { - synthio_synth_init(&self->synth, sample_rate, waveform, waveform_length, envelope); + synthio_synth_init(&self->synth, sample_rate, channel_count, waveform_obj, filter_obj, envelope_obj); } void common_hal_synthio_synthesizer_deinit(synthio_synthesizer_obj_t *self) { @@ -52,7 +52,7 @@ uint8_t common_hal_synthio_synthesizer_get_bits_per_sample(synthio_synthesizer_o return SYNTHIO_BITS_PER_SAMPLE; } uint8_t common_hal_synthio_synthesizer_get_channel_count(synthio_synthesizer_obj_t *self) { - return 1; + return self->synth.channel_count; } void synthio_synthesizer_reset_buffer(synthio_synthesizer_obj_t *self, diff --git a/shared-module/synthio/__init__.c b/shared-module/synthio/__init__.c index 5ee72aeb79..2fed9a5122 100644 --- a/shared-module/synthio/__init__.c +++ b/shared-module/synthio/__init__.c @@ -89,7 +89,7 @@ void synthio_envelope_definition_set(synthio_envelope_definition_t *envelope, mp envelope->release_step = -convert_time_to_rate( sample_rate, fields[2], - envelope->decay_step + envelope->sustain_level ? envelope->sustain_level : envelope->attack_level); } @@ -171,6 +171,175 @@ int16_t mix_down_sample(int32_t sample) { return sample; } +static void synth_note_into_buffer(synthio_synth_t *synth, int chan, int32_t *out_buffer32, int16_t dur) { + mp_obj_t note_obj = synth->span.note_obj[chan]; + + if (note_obj == SYNTHIO_SILENCE) { + synth->accum[chan] = 0; + return; + } + + if (synth->envelope_state[chan].level == 0) { + // note is truly finished, but we only just noticed + synth->span.note_obj[chan] = SYNTHIO_SILENCE; + return; + } + + int32_t sample_rate = synth->sample_rate; + + // adjust loudness by envelope + uint16_t loudness[2] = {synth->envelope_state[chan].level,synth->envelope_state[chan].level}; + + uint32_t dds_rate; + const int16_t *waveform = synth->waveform_bufinfo.buf; + uint32_t waveform_length = synth->waveform_bufinfo.len / sizeof(int16_t); + + uint32_t ring_dds_rate = 0; + const int16_t *ring_waveform = NULL; + uint32_t ring_waveform_length = 0; + + if (mp_obj_is_small_int(note_obj)) { + uint8_t note = mp_obj_get_int(note_obj); + uint8_t octave = note / 12; + uint16_t base_freq = notes[note % 12]; + // 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 + dds_rate = (sample_rate / 2 + ((uint64_t)(base_freq * waveform_length) << (SYNTHIO_FREQUENCY_SHIFT - 10 + octave))) / sample_rate; + } else { + synthio_note_obj_t *note = MP_OBJ_TO_PTR(note_obj); + int32_t frequency_scaled = synthio_note_step(note, sample_rate, dur, loudness); + if (note->waveform_buf.buf) { + waveform = note->waveform_buf.buf; + waveform_length = note->waveform_buf.len / sizeof(int16_t); + } + dds_rate = synthio_frequency_convert_scaled_to_dds((uint64_t)frequency_scaled * waveform_length, sample_rate); + 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); + 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 + } + } + } + + int synth_chan = synth->channel_count; + if (ring_dds_rate) { + uint32_t lim = waveform_length << SYNTHIO_FREQUENCY_SHIFT; + uint32_t accum = synth->accum[chan]; + + if (dds_rate > lim / 2) { + // beyond nyquist, can't play note + return; + } + + // can happen if note waveform gets set mid-note, but the expensive modulo is usually avoided + if (accum > lim) { + accum %= lim; + } + + int32_t ring_buffer[dur]; + // first, fill with waveform + for (uint16_t i = 0; i < dur; i++) { + accum += dds_rate; + // because dds_rate is low enough, the subtraction is guaranteed to go back into range, no expensive modulo needed + if (accum > lim) { + accum -= lim; + } + int16_t idx = accum >> SYNTHIO_FREQUENCY_SHIFT; + ring_buffer[i] = waveform[idx]; + } + synth->accum[chan] = accum; + + // now modulate by ring and accumulate + accum = synth->ring_accum[chan]; + lim = ring_waveform_length << SYNTHIO_FREQUENCY_SHIFT; + + // can happen if note waveform gets set mid-note, but the expensive modulo is usually avoided + if (accum > lim) { + accum %= lim; + } + + for (uint16_t i = 0, j = 0; i < dur; i++) { + accum += ring_dds_rate; + // because dds_rate is low enough, the subtraction is guaranteed to go back into range, no expensive modulo needed + if (accum > lim) { + accum -= lim; + } + int16_t idx = accum >> SYNTHIO_FREQUENCY_SHIFT; + int16_t wi = (ring_waveform[idx] * ring_buffer[i]) / 32768; + for (int c = 0; c < synth_chan; c++) { + out_buffer32[j] += (wi * loudness[c]) / 32768; + j++; + } + } + synth->ring_accum[chan] = accum; + } else { + uint32_t lim = waveform_length << SYNTHIO_FREQUENCY_SHIFT; + uint32_t accum = synth->accum[chan]; + + if (dds_rate > lim / 2) { + // beyond nyquist, can't play note + return; + } + + // can happen if note waveform gets set mid-note, but the expensive modulo is usually avoided + if (accum > lim) { + accum %= lim; + } + + for (uint16_t i = 0, j = 0; i < dur; i++) { + accum += dds_rate; + // because dds_rate is low enough, the subtraction is guaranteed to go back into range, no expensive modulo needed + if (accum > lim) { + accum -= lim; + } + int16_t idx = accum >> SYNTHIO_FREQUENCY_SHIFT; + int16_t wi = waveform[idx]; + for (int c = 0; c < synth_chan; c++) { + out_buffer32[j] += (wi * loudness[c]) / 65536; + j++; + } + } + synth->accum[chan] = accum; + } +} + +STATIC void run_fir(synthio_synth_t *synth, int32_t *out_buffer32, uint16_t dur) { + int16_t *coeff = (int16_t *)synth->filter_bufinfo.buf; + size_t fir_len = synth->filter_bufinfo.len / sizeof(int16_t); + int32_t *in_buf = synth->filter_buffer; + + // FIR and copy values to output buffer + for (int16_t i = 0; i < dur; i++) { + int32_t acc = 0; + for (size_t j = 0; j < fir_len; j++) { + // shift 5 here is good for up to 32 filtered voices, else might wrap + acc = acc + (in_buf[j] * (coeff[j] >> 5)); + } + *out_buffer32++ = acc >> 10; + in_buf++; + } + + // Move values down so that they get filtered next time + memmove(synth->filter_buffer, &synth->filter_buffer[dur], fir_len * sizeof(int32_t)); +} + +STATIC bool synthio_synth_get_note_filtered(mp_obj_t note_obj) { + if (note_obj == mp_const_none) { + return false; + } + if (!mp_obj_is_small_int(note_obj)) { + synthio_note_obj_t *note = MP_OBJ_TO_PTR(note_obj); + return note->filter; + } + return true; +} + void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t *buffer_length, uint8_t channel) { if (channel == synth->other_channel) { @@ -186,77 +355,37 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t uint16_t dur = MIN(SYNTHIO_MAX_DUR, synth->span.dur); synth->span.dur -= dur; - int32_t sample_rate = synth->sample_rate; - int32_t out_buffer32[dur]; + int32_t out_buffer32[dur * synth->channel_count]; + + if (synth->filter_buffer) { + int32_t *filter_start = &synth->filter_buffer[synth->filter_bufinfo.len * synth->channel_count / sizeof(int16_t)]; + memset(filter_start, 0, dur * sizeof(int32_t)); + + for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) { + mp_obj_t note_obj = synth->span.note_obj[chan]; + if (!synthio_synth_get_note_filtered(note_obj)) { + continue; + } + synth_note_into_buffer(synth, chan, filter_start, dur); + } + + run_fir(synth, out_buffer32, dur); + } else { + memset(out_buffer32, 0, sizeof(out_buffer32)); + } - memset(out_buffer32, 0, sizeof(out_buffer32)); for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) { mp_obj_t note_obj = synth->span.note_obj[chan]; - if (note_obj == SYNTHIO_SILENCE) { - synth->accum[chan] = 0; + if (synth->filter_buffer && synthio_synth_get_note_filtered(note_obj)) { continue; } - - if (synth->envelope_state[chan].level == 0) { - // note is truly finished, but we only just noticed - synth->span.note_obj[chan] = SYNTHIO_SILENCE; - continue; - } - - // adjust loudness by envelope - uint16_t loudness = synth->envelope_state[chan].level; - - uint32_t dds_rate; - const int16_t *waveform = synth->waveform; - uint32_t waveform_length = synth->waveform_length; - if (mp_obj_is_small_int(note_obj)) { - uint8_t note = mp_obj_get_int(note_obj); - uint8_t octave = note / 12; - uint16_t base_freq = notes[note % 12]; - // 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 - dds_rate = (sample_rate / 2 + ((uint64_t)(base_freq * waveform_length) << (SYNTHIO_FREQUENCY_SHIFT - 10 + octave))) / sample_rate; - } else { - synthio_note_obj_t *note = MP_OBJ_TO_PTR(note_obj); - int32_t frequency_scaled = synthio_note_step(note, sample_rate, dur, &loudness); - if (note->waveform_buf.buf) { - waveform = note->waveform_buf.buf; - waveform_length = note->waveform_buf.len / 2; - } - dds_rate = synthio_frequency_convert_scaled_to_dds((uint64_t)frequency_scaled * waveform_length, sample_rate); - } - - uint32_t accum = synth->accum[chan]; - uint32_t lim = waveform_length << SYNTHIO_FREQUENCY_SHIFT; - if (dds_rate > lim / 2) { - // beyond nyquist, can't play note - continue; - } - - // can happen if note waveform gets set mid-note, but the expensive modulo is usually avoided - if (accum > lim) { - accum %= lim; - } - - for (uint16_t i = 0; i < dur; i++) { - accum += dds_rate; - // because dds_rate is low enough, the subtraction is guaranteed to go back into range, no expensive modulo needed - if (accum > lim) { - accum -= lim; - } - int16_t idx = accum >> SYNTHIO_FREQUENCY_SHIFT; - out_buffer32[i] += (waveform[idx] * loudness) / 65536; - } - synth->accum[chan] = accum; + synth_note_into_buffer(synth, chan, out_buffer32, dur); } int16_t *out_buffer16 = (int16_t *)(void *)synth->buffers[synth->buffer_index]; // mix down audio - for (size_t i = 0; i < dur; i++) { + for (size_t i = 0; i < MP_ARRAY_SIZE(out_buffer32); i++) { int32_t sample = out_buffer32[i]; out_buffer16[i] = mix_down_sample(sample); } @@ -270,7 +399,7 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t synthio_envelope_state_step(&synth->envelope_state[chan], synthio_synth_get_note_envelope(synth, note_obj), dur); } - *buffer_length = synth->last_buffer_length = dur * SYNTHIO_BYTES_PER_SAMPLE; + *buffer_length = synth->last_buffer_length = dur * SYNTHIO_BYTES_PER_SAMPLE * synth->channel_count; *bufptr = (uint8_t *)out_buffer16; } @@ -286,8 +415,7 @@ bool synthio_synth_deinited(synthio_synth_t *synth) { } 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->filter_buffer = NULL; synth->buffers[0] = NULL; synth->buffers[1] = NULL; } @@ -301,13 +429,20 @@ mp_obj_t synthio_synth_envelope_get(synthio_synth_t *synth) { return synth->envelope_obj; } -void synthio_synth_init(synthio_synth_t *synth, uint32_t sample_rate, const int16_t *waveform, uint16_t waveform_length, mp_obj_t envelope_obj) { - synth->buffer_length = SYNTHIO_MAX_DUR * SYNTHIO_BYTES_PER_SAMPLE; +void synthio_synth_init(synthio_synth_t *synth, uint32_t sample_rate, int channel_count, mp_obj_t waveform_obj, mp_obj_t filter_obj, mp_obj_t envelope_obj) { + synthio_synth_parse_waveform(&synth->waveform_bufinfo, waveform_obj); + synthio_synth_parse_filter(&synth->filter_bufinfo, filter_obj); + mp_arg_validate_int_range(channel_count, 1, 2, MP_QSTR_channel_count); + synth->buffer_length = SYNTHIO_MAX_DUR * SYNTHIO_BYTES_PER_SAMPLE * channel_count; synth->buffers[0] = m_malloc(synth->buffer_length, false); synth->buffers[1] = m_malloc(synth->buffer_length, false); + if (synth->filter_bufinfo.len) { + synth->filter_buffer_length = (synth->filter_bufinfo.len / 2 + SYNTHIO_MAX_DUR) * channel_count * sizeof(int32_t); + synth->filter_buffer = m_malloc(synth->filter_buffer_length, false); + } + synth->channel_count = channel_count; synth->other_channel = -1; - synth->waveform = waveform; - synth->waveform_length = waveform_length; + synth->waveform_obj = waveform_obj; synth->sample_rate = sample_rate; synthio_synth_envelope_set(synth, envelope_obj); @@ -321,24 +456,31 @@ void synthio_synth_get_buffer_structure(synthio_synth_t *synth, bool single_chan *single_buffer = false; *samples_signed = true; *max_buffer_length = synth->buffer_length; - *spacing = 1; + if (single_channel_output) { + *spacing = synth->channel_count; + } else { + *spacing = 1; + } } -STATIC bool parse_common(mp_buffer_info_t *bufinfo, mp_obj_t o, int16_t what) { +STATIC void parse_common(mp_buffer_info_t *bufinfo, mp_obj_t o, int16_t what, mp_int_t max_len) { if (o != mp_const_none) { mp_get_buffer_raise(o, bufinfo, MP_BUFFER_READ); if (bufinfo->typecode != 'h') { mp_raise_ValueError_varg(translate("%q must be array of type 'h'"), what); } - mp_arg_validate_length_range(bufinfo->len / 2, 2, 1024, what); - return true; + mp_arg_validate_length_range(bufinfo->len / sizeof(int16_t), 2, max_len, what); } - return false; } 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 }); - parse_common(bufinfo_waveform, waveform_obj, MP_QSTR_waveform); + parse_common(bufinfo_waveform, waveform_obj, MP_QSTR_waveform, 16384); +} + +void synthio_synth_parse_filter(mp_buffer_info_t *bufinfo_filter, mp_obj_t filter_obj) { + *bufinfo_filter = ((mp_buffer_info_t) { .buf = NULL, .len = 0 }); + parse_common(bufinfo_filter, filter_obj, MP_QSTR_filter, 128); } STATIC int find_channel_with_note(synthio_synth_t *synth, mp_obj_t note) { @@ -402,13 +544,36 @@ void synthio_lfo_set(synthio_lfo_state_t *state, const synthio_lfo_descr_t *desc state->dds = synthio_frequency_convert_float_to_dds(descr->frequency * 65536, sample_rate); } -int synthio_lfo_step(synthio_lfo_state_t *state, uint16_t dur) { +STATIC int synthio_lfo_step_common(synthio_lfo_state_t *state, uint16_t dur) { uint32_t phase = state->phase; uint16_t whole_phase = phase >> 16; // advance the phase accumulator state->phase = phase + state->dds * dur; + return whole_phase; +} +STATIC int synthio_lfo_sweep_common(synthio_lfo_state_t *state, uint16_t dur) { + uint32_t old_phase = state->phase; + uint16_t whole_phase = synthio_lfo_step_common(state, dur); + if (state->phase < old_phase) { + state->phase = 0xffffffff; + } + return whole_phase; +} + +int synthio_sweep_step(synthio_lfo_state_t *state, uint16_t dur) { + uint16_t whole_phase = synthio_lfo_sweep_common(state, dur); + return (state->amplitude_scaled * whole_phase) / 65536 + state->offset_scaled; +} + +int synthio_sweep_in_step(synthio_lfo_state_t *state, uint16_t dur) { + uint16_t whole_phase = 65535 - synthio_lfo_sweep_common(state, dur); + return (state->amplitude_scaled * whole_phase) / 65536 + state->offset_scaled; +} + +int synthio_lfo_step(synthio_lfo_state_t *state, uint16_t dur) { + uint16_t whole_phase = synthio_lfo_step_common(state, dur); // create a triangle wave, it's quick and easy int v; if (whole_phase < 16384) { // ramp from 0 to amplitude diff --git a/shared-module/synthio/__init__.h b/shared-module/synthio/__init__.h index 4ea04a09e3..a3d0ca57fd 100644 --- a/shared-module/synthio/__init__.h +++ b/shared-module/synthio/__init__.h @@ -64,15 +64,17 @@ typedef struct synthio_synth { uint32_t sample_rate; uint32_t total_envelope; int16_t *buffers[2]; - const int16_t *waveform; - uint16_t buffer_length; + int32_t *filter_buffer; + uint8_t channel_count; + uint16_t buffer_length, filter_buffer_length; uint16_t last_buffer_length; uint8_t other_channel, buffer_index, other_buffer_index; - uint16_t waveform_length; + mp_buffer_info_t waveform_bufinfo, filter_bufinfo; synthio_envelope_definition_t global_envelope_definition; - mp_obj_t envelope_obj; + mp_obj_t waveform_obj, filter_obj, envelope_obj; synthio_midi_span_t span; uint32_t accum[CIRCUITPY_SYNTHIO_MAX_CHANNELS]; + uint32_t ring_accum[CIRCUITPY_SYNTHIO_MAX_CHANNELS]; synthio_envelope_state_t envelope_state[CIRCUITPY_SYNTHIO_MAX_CHANNELS]; } synthio_synth_t; @@ -81,19 +83,20 @@ typedef struct { } synthio_lfo_descr_t; typedef struct { - uint32_t amplitude_scaled, offset_scaled, dds, phase; + int32_t amplitude_scaled; + uint32_t offset_scaled, dds, phase; } synthio_lfo_state_t; 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, uint32_t sample_rate, const int16_t *waveform, uint16_t waveform_length, - mp_obj_t envelope); +void synthio_synth_init(synthio_synth_t *synth, uint32_t sample_rate, int channel_count, mp_obj_t waveform_obj, mp_obj_t filter_obj, mp_obj_t envelope); 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); +void synthio_synth_parse_filter(mp_buffer_info_t *bufinfo_filter, mp_obj_t filter_obj); void synthio_synth_parse_envelope(uint16_t *envelope_sustain_index, mp_buffer_info_t *bufinfo_envelope, mp_obj_t envelope_obj, mp_obj_t envelope_hold_obj); bool synthio_span_change_note(synthio_synth_t *synth, mp_obj_t old_note, mp_obj_t new_note); @@ -107,3 +110,5 @@ uint32_t synthio_frequency_convert_scaled_to_dds(uint64_t frequency_scaled, int3 void synthio_lfo_set(synthio_lfo_state_t *state, const synthio_lfo_descr_t *descr, uint32_t sample_rate); 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); diff --git a/tests/circuitpython-manual/synthio/note/audioop.py b/tests/circuitpython-manual/synthio/note/audioop.py deleted file mode 120000 index 31896fe265..0000000000 --- a/tests/circuitpython-manual/synthio/note/audioop.py +++ /dev/null @@ -1 +0,0 @@ -../wave/audioop.py \ No newline at end of file diff --git a/tests/circuitpython-manual/synthio/note/chunk.py b/tests/circuitpython-manual/synthio/note/chunk.py deleted file mode 120000 index 00983c1f72..0000000000 --- a/tests/circuitpython-manual/synthio/note/chunk.py +++ /dev/null @@ -1 +0,0 @@ -../wave/chunk.py \ No newline at end of file diff --git a/tests/circuitpython-manual/synthio/note/code.py b/tests/circuitpython-manual/synthio/note/code.py index 3814c57279..43f5dfb8a5 100644 --- a/tests/circuitpython-manual/synthio/note/code.py +++ b/tests/circuitpython-manual/synthio/note/code.py @@ -1,8 +1,14 @@ +import sys + +sys.path.insert( + 0, f"{__file__.rpartition('/')[0] or '.'}/../../../../frozen/Adafruit_CircuitPython_Wave" +) + import random import audiocore import synthio from ulab import numpy as np -import wave +import adafruit_wave as wave SAMPLE_SIZE = 1024 VOLUME = 14700 @@ -12,7 +18,7 @@ sine = np.array( ) envelope = synthio.Envelope( - attack_time=0.1, decay_time=0.05, release_time=0.2, attack_level=1, sustain_level=0.8 + attack_time=0.1, decay_time=0.05, release_time=0.2, attack_level=0.8, sustain_level=0.8 ) synth = synthio.Synthesizer(sample_rate=48000) @@ -57,8 +63,8 @@ def synthesize2(synth): def synthesize3(synth): n = synthio.Note( frequency=synthio.midi_to_hz(60), - vibrato_depth=0.1, - vibrato_rate=8, + bend_depth=0.1, + bend_rate=8, waveform=sine, envelope=envelope, ) @@ -73,8 +79,8 @@ def synthesize4(synth): frequency=synthio.midi_to_hz(60), tremolo_depth=0.1, tremolo_rate=1.5, - vibrato_depth=0.1, - vibrato_rate=3, + bend_depth=0.1, + bend_rate=3, waveform=sine, envelope=envelope, ) @@ -103,6 +109,23 @@ def synthesize5(synth): yield 36 +def synthesize6(synth): + n = synthio.Note( + frequency=synthio.midi_to_hz(60), + tremolo_depth=0.1, + tremolo_rate=1.5, + bend_depth=-5 / 12, + bend_rate=1 / 2, + bend_mode=synthio.BendMode.SWEEP, + waveform=sine, + envelope=envelope, + ) + synth.press((n,)) + yield 720 + synth.release_all() + yield 36 + + def chain(*args): for a in args: yield from a @@ -113,7 +136,7 @@ with wave.open("tune-noenv.wav", "w") as f: f.setnchannels(1) f.setsampwidth(2) f.setframerate(48000) - for n in chain(synthesize5(synth)): + for n in chain(synthesize6(synth)): for i in range(n): result, data = audiocore.get_buffer(synth) f.writeframes(data) diff --git a/tests/circuitpython-manual/synthio/note/envelope.py b/tests/circuitpython-manual/synthio/note/envelope.py new file mode 100644 index 0000000000..9421cf8c1c --- /dev/null +++ b/tests/circuitpython-manual/synthio/note/envelope.py @@ -0,0 +1,58 @@ +import sys + +sys.path.insert( + 0, f"{__file__.rpartition('/')[0] or '.'}/../../../../frozen/Adafruit_CircuitPython_Wave" +) + +import random +import audiocore +import synthio +from ulab import numpy as np +import adafruit_wave as wave + +SAMPLE_SIZE = 1024 +VOLUME = 32767 +sine = np.array( + np.sin(np.linspace(0, 2 * np.pi, SAMPLE_SIZE, endpoint=False)) * VOLUME, + dtype=np.int16, +) + +envelope = synthio.Envelope(attack_time=0.05, decay_time=8, release_time=0.25, sustain_level=0) +fast_decay_envelope = synthio.Envelope( + attack_time=0.05, decay_time=0.25, release_time=0.25, sustain_level=0 +) + +synth = synthio.Synthesizer(sample_rate=48000) + + +def synthesize(synth): + notes = (synthio.Note(frequency=440, waveform=sine, envelope=envelope),) + synth.press(notes) + yield 360 + notes[0].envelope = fast_decay_envelope + yield 180 + synth.release_all() + + +def synthesize2(synth): + notes = (synthio.Note(frequency=440, waveform=sine, envelope=envelope),) + synth.press(notes) + yield 360 + synth.release_all() + yield 180 + + +def chain(*args): + for a in args: + yield from a + + +# sox -r 48000 -e signed -b 16 -c 1 tune.raw tune.wav +with wave.open("envelope.wav", "w") as f: + f.setnchannels(1) + f.setsampwidth(2) + f.setframerate(48000) + for n in chain(synthesize(synth), synthesize2(synth)): + for i in range(n): + result, data = audiocore.get_buffer(synth) + f.writeframes(data) diff --git a/tests/circuitpython-manual/synthio/note/fir.py b/tests/circuitpython-manual/synthio/note/fir.py new file mode 100644 index 0000000000..9d40b584be --- /dev/null +++ b/tests/circuitpython-manual/synthio/note/fir.py @@ -0,0 +1,104 @@ +import sys + +sys.path.insert( + 0, f"{__file__.rpartition('/')[0] or '.'}/../../../../frozen/Adafruit_CircuitPython_Wave" +) + +import random +import audiocore +import synthio +from ulab import numpy as np +import adafruit_wave as wave + +random.seed(9) + +envelope = synthio.Envelope( + attack_time=0.1, decay_time=0.05, release_time=0.2, attack_level=0.8, sustain_level=0.8 +) + +h = np.array( + [ + -0.001229734800309099, + -0.008235561806605458, + -0.015082497016061390, + -0.020940136918319988, + -0.024981800822463429, + -0.026464233332370746, + -0.024803890156806906, + -0.019642276775473012, + -0.010893620860173042, + 0.001230341899766145, + 0.016221637398855598, + 0.033304135659230648, + 0.051486665261155681, + 0.069636961761409016, + 0.086570197432542767, + 0.101144354207918147, + 0.112353938422488253, + 0.119413577288191297, + 0.121823886314051028, + 0.119413577288191297, + 0.112353938422488253, + 0.101144354207918147, + 0.086570197432542767, + 0.069636961761409016, + 0.051486665261155681, + 0.033304135659230648, + 0.016221637398855598, + 0.001230341899766145, + -0.010893620860173042, + -0.019642276775473012, + -0.024803890156806906, + -0.026464233332370746, + -0.024981800822463429, + -0.020940136918319988, + -0.015082497016061390, + -0.008235561806605458, + -0.001229734800309099, + ] +) + +filter_coeffs = np.array(h[::-1] * 32768, dtype=np.int16) + +synth = synthio.Synthesizer(sample_rate=48000, filter=filter_coeffs) + + +def synthesize(synth): + n = synthio.Note( + frequency=120, + envelope=envelope, + filter=False, + ) + + print(synth, n) + synth.press((n,)) + for _ in range(20): + n.frequency *= 1.0595 + yield 36 + synth.release_all() + yield 36 + + n.filter = True + n.frequency = 120 + synth.press((n,)) + for _ in range(20): + n.frequency *= 1.0595 + yield 36 + synth.release_all() + yield 36 + + +def chain(*args): + for a in args: + yield from a + + +# sox -r 48000 -e signed -b 16 -c 1 tune.raw tune.wav +with wave.open("fir.wav", "w") as f: + f.setnchannels(1) + f.setsampwidth(2) + f.setframerate(48000) + for n in chain(synthesize(synth)): + for i in range(n): + result, data = audiocore.get_buffer(synth) + f.writeframes(data) diff --git a/tests/circuitpython-manual/synthio/note/noise.py b/tests/circuitpython-manual/synthio/note/noise.py new file mode 100644 index 0000000000..b31658b818 --- /dev/null +++ b/tests/circuitpython-manual/synthio/note/noise.py @@ -0,0 +1,71 @@ +import sys + +sys.path.insert( + 0, f"{__file__.rpartition('/')[0] or '.'}/../../../../frozen/Adafruit_CircuitPython_Wave" +) + +import random +import audiocore +import synthio +from ulab import numpy as np +import adafruit_wave as wave + +random.seed(9) +SAMPLE_SIZE = 1024 +VOLUME = 14700 +noise = np.array( + [random.randint(-32768, 32767) for i in range(SAMPLE_SIZE)], + dtype=np.int16, +) + +envelope = synthio.Envelope( + attack_time=0, + decay_time=0.2, + sustain_level=0, +) + +synth = synthio.Synthesizer(sample_rate=48000) + + +def randf(lo, hi): + return random.random() * (hi - lo) + lo + + +def synthesize(synth): + notes = [ + synthio.Note( + frequency=synthio.midi_to_hz(1 + i), + waveform=noise, + envelope=envelope, + bend_mode=synthio.BendMode.SWEEP, + bend_depth=random.choice((-1, 1)), + bend_rate=randf(4, 12), + ) + for i in range(12) + ] + + random.seed(9) + for _ in range(16): + n = random.choice(notes) + d = random.randint(30, 60) + print(n, d) + synth.press((n,)) + yield d + synth.release_all() + yield 36 + + +def chain(*args): + for a in args: + yield from a + + +# sox -r 48000 -e signed -b 16 -c 1 tune.raw tune.wav +with wave.open("noise.wav", "w") as f: + f.setnchannels(1) + f.setsampwidth(2) + f.setframerate(48000) + for n in chain(synthesize(synth)): + for i in range(n): + result, data = audiocore.get_buffer(synth) + f.writeframes(data) diff --git a/tests/circuitpython-manual/synthio/note/panning.py b/tests/circuitpython-manual/synthio/note/panning.py new file mode 100644 index 0000000000..0b7fb1e2a6 --- /dev/null +++ b/tests/circuitpython-manual/synthio/note/panning.py @@ -0,0 +1,57 @@ +import sys + +sys.path.insert( + 0, f"{__file__.rpartition('/')[0] or '.'}/../../../../frozen/Adafruit_CircuitPython_Wave" +) + +import random +import audiocore +import synthio +from ulab import numpy as np +import adafruit_wave as wave + +random.seed(9) +SAMPLE_SIZE = 1024 +VOLUME = 14700 +sine = np.array( + np.sin(np.linspace(0, 2 * np.pi, SAMPLE_SIZE, endpoint=False)) * VOLUME, + dtype=np.int16, +) + +envelope = synthio.Envelope( + attack_time=0.1, decay_time=0.05, release_time=0.2, attack_level=0.8, sustain_level=0.8 +) + +synth = synthio.Synthesizer(sample_rate=48000, channel_count=2) + + +def synthesize(synth): + n = synthio.Note( + frequency=440, + waveform=sine, + envelope=envelope, + ) + + print(synth, n) + synth.press((n,)) + for p in range(-10, 11, 1): + n.panning = p / 10 + yield 36 + synth.release_all() + yield 36 + + +def chain(*args): + for a in args: + yield from a + + +# sox -r 48000 -e signed -b 16 -c 1 tune.raw tune.wav +with wave.open("panning.wav", "w") as f: + f.setnchannels(2) + f.setsampwidth(2) + f.setframerate(48000) + for n in chain(synthesize(synth)): + for i in range(n): + result, data = audiocore.get_buffer(synth) + f.writeframes(data) diff --git a/tests/circuitpython-manual/synthio/note/ring.py b/tests/circuitpython-manual/synthio/note/ring.py new file mode 100644 index 0000000000..7a418da3af --- /dev/null +++ b/tests/circuitpython-manual/synthio/note/ring.py @@ -0,0 +1,60 @@ +import sys + +sys.path.insert( + 0, f"{__file__.rpartition('/')[0] or '.'}/../../../../frozen/Adafruit_CircuitPython_Wave" +) + +import random +import audiocore +import synthio +from ulab import numpy as np +import adafruit_wave as wave + +random.seed(9) +SAMPLE_SIZE = 1024 +VOLUME = 32767 +sine = np.array( + np.sin(np.linspace(0, 2 * np.pi, SAMPLE_SIZE, endpoint=False)) * VOLUME, + dtype=np.int16, +) + +envelope = synthio.Envelope( + attack_time=0.1, decay_time=0.05, release_time=0.2, attack_level=0.8, sustain_level=0.8 +) + +synth = synthio.Synthesizer(sample_rate=48000) + + +def synthesize(synth): + n = synthio.Note( + frequency=120, + waveform=sine, + ring_waveform=sine, + ring_frequency=769, + envelope=envelope, + bend_mode=synthio.BendType.VIBRATO, + bend_depth=50 / 1200, + bend_rate=7, + ) + + print(synth, n) + synth.press((n,)) + yield 720 + synth.release_all() + yield 36 + + +def chain(*args): + for a in args: + yield from a + + +# sox -r 48000 -e signed -b 16 -c 1 tune.raw tune.wav +with wave.open("ring.wav", "w") as f: + f.setnchannels(1) + f.setsampwidth(2) + f.setframerate(48000) + for n in chain(synthesize(synth)): + for i in range(n): + result, data = audiocore.get_buffer(synth) + f.writeframes(data) diff --git a/tests/circuitpython-manual/synthio/note/wave.py b/tests/circuitpython-manual/synthio/note/wave.py deleted file mode 120000 index 45884bd29a..0000000000 --- a/tests/circuitpython-manual/synthio/note/wave.py +++ /dev/null @@ -1 +0,0 @@ -../wave/wave.py \ No newline at end of file diff --git a/tests/circuitpython-manual/synthio/wave/audioop.py b/tests/circuitpython-manual/synthio/wave/audioop.py deleted file mode 100644 index cab2fb4a7a..0000000000 --- a/tests/circuitpython-manual/synthio/wave/audioop.py +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-FileCopyrightText: 2023 Guido van Rossum and others. -# -# SPDX-License-Identifier: PSF-2.0 - -import struct - - -def byteswap(data, sampwidth): - print(data) - raise - ch = "I" if sampwidth == 16 else "H" diff --git a/tests/circuitpython-manual/synthio/wave/chunk.py b/tests/circuitpython-manual/synthio/wave/chunk.py deleted file mode 100644 index c1e6364b0f..0000000000 --- a/tests/circuitpython-manual/synthio/wave/chunk.py +++ /dev/null @@ -1,173 +0,0 @@ -# SPDX-FileCopyrightText: 2023 Guido van Rossum and others. -# -# SPDX-License-Identifier: PSF-2.0 - -"""Simple class to read IFF chunks. - -An IFF chunk (used in formats such as AIFF, TIFF, RMFF (RealMedia File -Format)) has the following structure: - -+----------------+ -| ID (4 bytes) | -+----------------+ -| size (4 bytes) | -+----------------+ -| data | -| ... | -+----------------+ - -The ID is a 4-byte string which identifies the type of chunk. - -The size field (a 32-bit value, encoded using big-endian byte order) -gives the size of the whole chunk, including the 8-byte header. - -Usually an IFF-type file consists of one or more chunks. The proposed -usage of the Chunk class defined here is to instantiate an instance at -the start of each chunk and read from the instance until it reaches -the end, after which a new instance can be instantiated. At the end -of the file, creating a new instance will fail with an EOFError -exception. - -Usage: -while True: - try: - chunk = Chunk(file) - except EOFError: - break - chunktype = chunk.getname() - while True: - data = chunk.read(nbytes) - if not data: - pass - # do something with data - -The interface is file-like. The implemented methods are: -read, close, seek, tell, isatty. -Extra methods are: skip() (called by close, skips to the end of the chunk), -getname() (returns the name (ID) of the chunk) - -The __init__ method has one required argument, a file-like object -(including a chunk instance), and one optional argument, a flag which -specifies whether or not chunks are aligned on 2-byte boundaries. The -default is 1, i.e. aligned. -""" - - -class Chunk: - def __init__(self, file, align=True, bigendian=True, inclheader=False): - import struct - - self.closed = False - self.align = align # whether to align to word (2-byte) boundaries - if bigendian: - strflag = ">" - else: - strflag = "<" - self.file = file - self.chunkname = file.read(4) - if len(self.chunkname) < 4: - raise EOFError - try: - self.chunksize = struct.unpack_from(strflag + "L", file.read(4))[0] - except struct.error: - raise EOFError from None - if inclheader: - self.chunksize = self.chunksize - 8 # subtract header - self.size_read = 0 - try: - self.offset = self.file.tell() - except (AttributeError, OSError): - self.seekable = False - else: - self.seekable = True - - def getname(self): - """Return the name (ID) of the current chunk.""" - return self.chunkname - - def getsize(self): - """Return the size of the current chunk.""" - return self.chunksize - - def close(self): - if not self.closed: - try: - self.skip() - finally: - self.closed = True - - def isatty(self): - if self.closed: - raise ValueError("I/O operation on closed file") - return False - - def seek(self, pos, whence=0): - """Seek to specified position into the chunk. - Default position is 0 (start of chunk). - If the file is not seekable, this will result in an error. - """ - - if self.closed: - raise ValueError("I/O operation on closed file") - if not self.seekable: - raise OSError("cannot seek") - if whence == 1: - pos = pos + self.size_read - elif whence == 2: - pos = pos + self.chunksize - if pos < 0 or pos > self.chunksize: - raise RuntimeError - self.file.seek(self.offset + pos, 0) - self.size_read = pos - - def tell(self): - if self.closed: - raise ValueError("I/O operation on closed file") - return self.size_read - - def read(self, size=-1): - """Read at most size bytes from the chunk. - If size is omitted or negative, read until the end - of the chunk. - """ - - if self.closed: - raise ValueError("I/O operation on closed file") - if self.size_read >= self.chunksize: - return b"" - if size < 0: - size = self.chunksize - self.size_read - if size > self.chunksize - self.size_read: - size = self.chunksize - self.size_read - data = self.file.read(size) - self.size_read = self.size_read + len(data) - if self.size_read == self.chunksize and self.align and (self.chunksize & 1): - dummy = self.file.read(1) - self.size_read = self.size_read + len(dummy) - return data - - def skip(self): - """Skip the rest of the chunk. - If you are not interested in the contents of the chunk, - this method should be called so that the file points to - the start of the next chunk. - """ - - if self.closed: - raise ValueError("I/O operation on closed file") - if self.seekable: - try: - n = self.chunksize - self.size_read - # maybe fix alignment - if self.align and (self.chunksize & 1): - n = n + 1 - self.file.seek(n, 1) - self.size_read = self.size_read + n - return - except OSError: - pass - while self.size_read < self.chunksize: - n = min(8192, self.chunksize - self.size_read) - dummy = self.read(n) - if not dummy: - raise EOFError diff --git a/tests/circuitpython-manual/synthio/wave/midi2wav.py b/tests/circuitpython-manual/synthio/wave/midi2wav.py index daa46a28c0..5d626e7baa 100644 --- a/tests/circuitpython-manual/synthio/wave/midi2wav.py +++ b/tests/circuitpython-manual/synthio/wave/midi2wav.py @@ -1,7 +1,13 @@ +import sys + +sys.path.insert( + 0, f"{__file__.rpartition('/')[0] or '.'}/../../../../frozen/Adafruit_CircuitPython_Wave" +) + import audiocore import synthio from ulab import numpy as np -import wave +import adafruit_wave as wave SAMPLE_SIZE = 1024 VOLUME = 32700 diff --git a/tests/circuitpython-manual/synthio/wave/wave.py b/tests/circuitpython-manual/synthio/wave/wave.py deleted file mode 100644 index dc6d5407a7..0000000000 --- a/tests/circuitpython-manual/synthio/wave/wave.py +++ /dev/null @@ -1,550 +0,0 @@ -# SPDX-FileCopyrightText: 2023 Guido van Rossum and others. -# -# SPDX-License-Identifier: PSF-2.0 - -"""Stuff to parse WAVE files. - -Usage. - -Reading WAVE files: - f = wave.open(file, 'r') -where file is either the name of a file or an open file pointer. -The open file pointer must have methods read(), seek(), and close(). -When the setpos() and rewind() methods are not used, the seek() -method is not necessary. - -This returns an instance of a class with the following public methods: - getnchannels() -- returns number of audio channels (1 for - mono, 2 for stereo) - getsampwidth() -- returns sample width in bytes - getframerate() -- returns sampling frequency - getnframes() -- returns number of audio frames - getcomptype() -- returns compression type ('NONE' for linear samples) - getcompname() -- returns human-readable version of - compression type ('not compressed' linear samples) - getparams() -- returns a namedtuple consisting of all of the - above in the above order - getmarkers() -- returns None (for compatibility with the - aifc module) - getmark(id) -- raises an error since the mark does not - exist (for compatibility with the aifc module) - readframes(n) -- returns at most n frames of audio - rewind() -- rewind to the beginning of the audio stream - setpos(pos) -- seek to the specified position - tell() -- return the current position - close() -- close the instance (make it unusable) -The position returned by tell() and the position given to setpos() -are compatible and have nothing to do with the actual position in the -file. -The close() method is called automatically when the class instance -is destroyed. - -Writing WAVE files: - f = wave.open(file, 'w') -where file is either the name of a file or an open file pointer. -The open file pointer must have methods write(), tell(), seek(), and -close(). - -This returns an instance of a class with the following public methods: - setnchannels(n) -- set the number of channels - setsampwidth(n) -- set the sample width - setframerate(n) -- set the frame rate - setnframes(n) -- set the number of frames - setcomptype(type, name) - -- set the compression type and the - human-readable compression type - setparams(tuple) - -- set all parameters at once - tell() -- return current position in output file - writeframesraw(data) - -- write audio frames without patching up the - file header - writeframes(data) - -- write audio frames and patch up the file header - close() -- patch up the file header and close the - output file -You should set the parameters before the first writeframesraw or -writeframes. The total number of frames does not need to be set, -but when it is set to the correct value, the header does not have to -be patched up. -It is best to first set all parameters, perhaps possibly the -compression type, and then write audio frames using writeframesraw. -When all frames have been written, either call writeframes(b'') or -close() to patch up the sizes in the header. -The close() method is called automatically when the class instance -is destroyed. -""" - -from chunk import Chunk -from collections import namedtuple -import audioop -import builtins -import struct -import sys - - -__all__ = ["open", "Error", "Wave_read", "Wave_write"] - - -class Error(Exception): - pass - - -WAVE_FORMAT_PCM = 0x0001 - -_array_fmts = None, "b", "h", None, "i" - -_wave_params = namedtuple( - "_wave_params", "nchannels sampwidth framerate nframes comptype compname" -) - - -class Wave_read: - """Variables used in this class: - - These variables are available to the user though appropriate - methods of this class: - _file -- the open file with methods read(), close(), and seek() - set through the __init__() method - _nchannels -- the number of audio channels - available through the getnchannels() method - _nframes -- the number of audio frames - available through the getnframes() method - _sampwidth -- the number of bytes per audio sample - available through the getsampwidth() method - _framerate -- the sampling frequency - available through the getframerate() method - _comptype -- the AIFF-C compression type ('NONE' if AIFF) - available through the getcomptype() method - _compname -- the human-readable AIFF-C compression type - available through the getcomptype() method - _soundpos -- the position in the audio stream - available through the tell() method, set through the - setpos() method - - These variables are used internally only: - _fmt_chunk_read -- 1 iff the FMT chunk has been read - _data_seek_needed -- 1 iff positioned correctly in audio - file for readframes() - _data_chunk -- instantiation of a chunk class for the DATA chunk - _framesize -- size of one frame in the file - """ - - def initfp(self, file): - self._convert = None - self._soundpos = 0 - self._file = Chunk(file, bigendian=0) - if self._file.getname() != b"RIFF": - raise Error("file does not start with RIFF id") - if self._file.read(4) != b"WAVE": - raise Error("not a WAVE file") - self._fmt_chunk_read = 0 - self._data_chunk = None - while 1: - self._data_seek_needed = 1 - try: - chunk = Chunk(self._file, bigendian=0) - except EOFError: - break - chunkname = chunk.getname() - if chunkname == b"fmt ": - self._read_fmt_chunk(chunk) - self._fmt_chunk_read = 1 - elif chunkname == b"data": - if not self._fmt_chunk_read: - raise Error("data chunk before fmt chunk") - self._data_chunk = chunk - self._nframes = chunk.chunksize // self._framesize - self._data_seek_needed = 0 - break - chunk.skip() - if not self._fmt_chunk_read or not self._data_chunk: - raise Error("fmt chunk and/or data chunk missing") - - def __init__(self, f): - self._i_opened_the_file = None - if isinstance(f, str): - f = builtins.open(f, "rb") - self._i_opened_the_file = f - # else, assume it is an open file object already - try: - self.initfp(f) - except: - if self._i_opened_the_file: - f.close() - raise - - def __del__(self): - self.close() - - def __enter__(self): - return self - - def __exit__(self, *args): - self.close() - - # - # User visible methods. - # - def getfp(self): - return self._file - - def rewind(self): - self._data_seek_needed = 1 - self._soundpos = 0 - - def close(self): - self._file = None - file = self._i_opened_the_file - if file: - self._i_opened_the_file = None - file.close() - - def tell(self): - return self._soundpos - - def getnchannels(self): - return self._nchannels - - def getnframes(self): - return self._nframes - - def getsampwidth(self): - return self._sampwidth - - def getframerate(self): - return self._framerate - - def getcomptype(self): - return self._comptype - - def getcompname(self): - return self._compname - - def getparams(self): - return _wave_params( - self.getnchannels(), - self.getsampwidth(), - self.getframerate(), - self.getnframes(), - self.getcomptype(), - self.getcompname(), - ) - - def getmarkers(self): - return None - - def getmark(self, id): - raise Error("no marks") - - def setpos(self, pos): - if pos < 0 or pos > self._nframes: - raise Error("position not in range") - self._soundpos = pos - self._data_seek_needed = 1 - - def readframes(self, nframes): - if self._data_seek_needed: - self._data_chunk.seek(0, 0) - pos = self._soundpos * self._framesize - if pos: - self._data_chunk.seek(pos, 0) - self._data_seek_needed = 0 - if nframes == 0: - return b"" - data = self._data_chunk.read(nframes * self._framesize) - if self._sampwidth != 1 and sys.byteorder == "big": - data = audioop.byteswap(data, self._sampwidth) - if self._convert and data: - data = self._convert(data) - self._soundpos = self._soundpos + len(data) // (self._nchannels * self._sampwidth) - return data - - # - # Internal methods. - # - - def _read_fmt_chunk(self, chunk): - try: - ( - wFormatTag, - self._nchannels, - self._framerate, - dwAvgBytesPerSec, - wBlockAlign, - ) = struct.unpack_from(" 4: - raise Error("bad sample width") - self._sampwidth = sampwidth - - def getsampwidth(self): - if not self._sampwidth: - raise Error("sample width not set") - return self._sampwidth - - def setframerate(self, framerate): - if self._datawritten: - raise Error("cannot change parameters after starting to write") - if framerate <= 0: - raise Error("bad frame rate") - self._framerate = int(round(framerate)) - - def getframerate(self): - if not self._framerate: - raise Error("frame rate not set") - return self._framerate - - def setnframes(self, nframes): - if self._datawritten: - raise Error("cannot change parameters after starting to write") - self._nframes = nframes - - def getnframes(self): - return self._nframeswritten - - def setcomptype(self, comptype, compname): - if self._datawritten: - raise Error("cannot change parameters after starting to write") - if comptype not in ("NONE",): - raise Error("unsupported compression type") - self._comptype = comptype - self._compname = compname - - def getcomptype(self): - return self._comptype - - def getcompname(self): - return self._compname - - def setparams(self, params): - nchannels, sampwidth, framerate, nframes, comptype, compname = params - if self._datawritten: - raise Error("cannot change parameters after starting to write") - self.setnchannels(nchannels) - self.setsampwidth(sampwidth) - self.setframerate(framerate) - self.setnframes(nframes) - self.setcomptype(comptype, compname) - - def getparams(self): - if not self._nchannels or not self._sampwidth or not self._framerate: - raise Error("not all parameters set") - return _wave_params( - self._nchannels, - self._sampwidth, - self._framerate, - self._nframes, - self._comptype, - self._compname, - ) - - def setmark(self, id, pos, name): - raise Error("setmark() not supported") - - def getmark(self, id): - raise Error("no marks") - - def getmarkers(self): - return None - - def tell(self): - return self._nframeswritten - - def writeframesraw(self, data): - if not isinstance(data, (bytes, bytearray)): - data = memoryview(data).cast("B") - self._ensure_header_written(len(data)) - nframes = len(data) // (self._sampwidth * self._nchannels) - if self._convert: - data = self._convert(data) - if self._sampwidth != 1 and sys.byteorder == "big": - data = audioop.byteswap(data, self._sampwidth) - self._file.write(data) - self._datawritten += len(data) - self._nframeswritten = self._nframeswritten + nframes - - def writeframes(self, data): - self.writeframesraw(data) - if self._datalength != self._datawritten: - self._patchheader() - - def close(self): - try: - if self._file: - self._ensure_header_written(0) - if self._datalength != self._datawritten: - self._patchheader() - self._file.flush() - finally: - self._file = None - file = self._i_opened_the_file - if file: - self._i_opened_the_file = None - file.close() - - # - # Internal methods. - # - - def _ensure_header_written(self, datasize): - if not self._headerwritten: - if not self._nchannels: - raise Error("# channels not specified") - if not self._sampwidth: - raise Error("sample width not specified") - if not self._framerate: - raise Error("sampling rate not specified") - self._write_header(datasize) - - def _write_header(self, initlength): - assert not self._headerwritten - self._file.write(b"RIFF") - if not self._nframes: - self._nframes = initlength // (self._nchannels * self._sampwidth) - self._datalength = self._nframes * self._nchannels * self._sampwidth - try: - self._form_length_pos = self._file.tell() - except (AttributeError, OSError): - self._form_length_pos = None - self._file.write( - struct.pack( - "