Merge pull request #7959 from jepler/synthio-bend-pan-ring

Synthio: next round of features
This commit is contained in:
Scott Shawcroft 2023-05-12 08:54:49 -07:00 committed by GitHub
commit 0a3faf8c9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 982 additions and 942 deletions

3
.gitmodules vendored
View File

@ -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

@ -0,0 +1 @@
Subproject commit 02b748f2e6826dc442c842885e58b07ad10d9287

View File

@ -11,3 +11,4 @@ EXTERNAL_FLASH_DEVICES = GD25Q16C
LONGINT_IMPL = MPZ
CIRCUITPY__EVE = 1
CIRCUITPY_SYNTHIO = 0

View File

@ -11,3 +11,4 @@ EXTERNAL_FLASH_DEVICES = "S25FL116K, S25FL216K, GD25Q16C"
LONGINT_IMPL = MPZ
CIRCUITPY__EVE = 1
CIRCUITPY_SYNTHIO = 0

View File

@ -11,3 +11,4 @@ EXTERNAL_FLASH_DEVICES = "S25FL116K, S25FL216K, GD25Q16C"
LONGINT_IMPL = MPZ
CIRCUITPY__EVE = 1
CIRCUITPY_SYNTHIO = 0

View File

@ -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
);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -26,6 +26,7 @@
#include <string.h>
#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) },

View File

@ -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;

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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,

View File

@ -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));
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 (!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;
synth_note_into_buffer(synth, chan, filter_start, dur);
}
// 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;
run_fir(synth, out_buffer32, dur);
} 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);
memset(out_buffer32, 0, sizeof(out_buffer32));
}
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
for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
mp_obj_t note_obj = synth->span.note_obj[chan];
if (synth->filter_buffer && synthio_synth_get_note_filtered(note_obj)) {
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;
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

View File

@ -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);

View File

@ -1 +0,0 @@
../wave/audioop.py

View File

@ -1 +0,0 @@
../wave/chunk.py

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -1 +0,0 @@
../wave/wave.py

View File

@ -1,11 +0,0 @@
# SPDX-FileCopyrightText: 2023 Guido van Rossum <guido@cwi.nl> and others.
#
# SPDX-License-Identifier: PSF-2.0
import struct
def byteswap(data, sampwidth):
print(data)
raise
ch = "I" if sampwidth == 16 else "H"

View File

@ -1,173 +0,0 @@
# SPDX-FileCopyrightText: 2023 Guido van Rossum <guido@cwi.nl> 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

View File

@ -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

View File

@ -1,550 +0,0 @@
# SPDX-FileCopyrightText: 2023 Guido van Rossum <guido@cwi.nl> 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("<HHLLH", chunk.read(14))
except struct.error:
raise EOFError from None
if wFormatTag == WAVE_FORMAT_PCM:
try:
sampwidth = struct.unpack_from("<H", chunk.read(2))[0]
except struct.error:
raise EOFError from None
self._sampwidth = (sampwidth + 7) // 8
if not self._sampwidth:
raise Error("bad sample width")
else:
raise Error("unknown format: %r" % (wFormatTag,))
if not self._nchannels:
raise Error("bad # of channels")
self._framesize = self._nchannels * self._sampwidth
self._comptype = "NONE"
self._compname = "not compressed"
class Wave_write:
"""Variables used in this class:
These variables are user settable through appropriate methods
of this class:
_file -- the open file with methods write(), close(), tell(), seek()
set through the __init__() method
_comptype -- the AIFF-C compression type ('NONE' in AIFF)
set through the setcomptype() or setparams() method
_compname -- the human-readable AIFF-C compression type
set through the setcomptype() or setparams() method
_nchannels -- the number of audio channels
set through the setnchannels() or setparams() method
_sampwidth -- the number of bytes per audio sample
set through the setsampwidth() or setparams() method
_framerate -- the sampling frequency
set through the setframerate() or setparams() method
_nframes -- the number of audio frames written to the header
set through the setnframes() or setparams() method
These variables are used internally only:
_datalength -- the size of the audio samples written to the header
_nframeswritten -- the number of frames actually written
_datawritten -- the size of the audio samples actually written
"""
def __init__(self, f):
self._i_opened_the_file = None
if isinstance(f, str):
f = builtins.open(f, "wb")
self._i_opened_the_file = f
try:
self.initfp(f)
except:
if self._i_opened_the_file:
f.close()
raise
def initfp(self, file):
self._file = file
self._convert = None
self._nchannels = 0
self._sampwidth = 0
self._framerate = 0
self._nframes = 0
self._nframeswritten = 0
self._datawritten = 0
self._datalength = 0
self._headerwritten = False
def __del__(self):
self.close()
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
#
# User visible methods.
#
def setnchannels(self, nchannels):
if self._datawritten:
raise Error("cannot change parameters after starting to write")
if nchannels < 1:
raise Error("bad # of channels")
self._nchannels = nchannels
def getnchannels(self):
if not self._nchannels:
raise Error("number of channels not set")
return self._nchannels
def setsampwidth(self, sampwidth):
if self._datawritten:
raise Error("cannot change parameters after starting to write")
if sampwidth < 1 or sampwidth > 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(
"<L4s4sLHHLLHH4s",
36 + self._datalength,
b"WAVE",
b"fmt ",
16,
WAVE_FORMAT_PCM,
self._nchannels,
self._framerate,
self._nchannels * self._framerate * self._sampwidth,
self._nchannels * self._sampwidth,
self._sampwidth * 8,
b"data",
)
)
if self._form_length_pos is not None:
self._data_length_pos = self._file.tell()
self._file.write(struct.pack("<L", self._datalength))
self._headerwritten = True
def _patchheader(self):
assert self._headerwritten
if self._datawritten == self._datalength:
return
curpos = self._file.tell()
self._file.seek(self._form_length_pos, 0)
self._file.write(struct.pack("<L", 36 + self._datawritten))
self._file.seek(self._data_length_pos, 0)
self._file.write(struct.pack("<L", self._datawritten))
self._file.seek(curpos, 0)
self._datalength = self._datawritten
def open(f, mode=None):
if mode is None:
if hasattr(f, "mode"):
mode = f.mode
else:
mode = "rb"
if mode in ("r", "rb"):
return Wave_read(f)
elif mode in ("w", "wb"):
return Wave_write(f)
else:
raise Error("mode must be 'r', 'rb', 'w', or 'wb'")

View File

@ -1,10 +1,10 @@
()
[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, amplitude=1.0, tremolo_rate=0.0, tremolo_depth=0.0, vibrato_rate=0.0, vibrato_depth=0.0, waveform=None, envelope=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),)
[-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, amplitude=1.0, tremolo_rate=0.0, tremolo_depth=0.0, vibrato_rate=0.0, vibrato_depth=0.0, waveform=None, envelope=None), Note(frequency=830.6076004423605, amplitude=1.0, tremolo_rate=0.0, tremolo_depth=0.0, vibrato_rate=0.0, vibrato_depth=0.0, waveform=None, envelope=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), 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, amplitude=1.0, tremolo_rate=0.0, tremolo_depth=0.0, vibrato_rate=0.0, vibrato_depth=0.0, waveform=None, envelope=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, 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)