Merge pull request #7959 from jepler/synthio-bend-pan-ring
Synthio: next round of features
This commit is contained in:
commit
0a3faf8c9d
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -338,3 +338,6 @@
|
|||||||
[submodule "frozen/circuitpython-pcf85063a"]
|
[submodule "frozen/circuitpython-pcf85063a"]
|
||||||
path = frozen/circuitpython-pcf85063a
|
path = frozen/circuitpython-pcf85063a
|
||||||
url = https://github.com/bablokb/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
|
||||||
|
1
frozen/Adafruit_CircuitPython_Wave
Submodule
1
frozen/Adafruit_CircuitPython_Wave
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 02b748f2e6826dc442c842885e58b07ad10d9287
|
@ -11,3 +11,4 @@ EXTERNAL_FLASH_DEVICES = GD25Q16C
|
|||||||
LONGINT_IMPL = MPZ
|
LONGINT_IMPL = MPZ
|
||||||
|
|
||||||
CIRCUITPY__EVE = 1
|
CIRCUITPY__EVE = 1
|
||||||
|
CIRCUITPY_SYNTHIO = 0
|
||||||
|
@ -11,3 +11,4 @@ EXTERNAL_FLASH_DEVICES = "S25FL116K, S25FL216K, GD25Q16C"
|
|||||||
LONGINT_IMPL = MPZ
|
LONGINT_IMPL = MPZ
|
||||||
|
|
||||||
CIRCUITPY__EVE = 1
|
CIRCUITPY__EVE = 1
|
||||||
|
CIRCUITPY_SYNTHIO = 0
|
||||||
|
@ -11,3 +11,4 @@ EXTERNAL_FLASH_DEVICES = "S25FL116K, S25FL216K, GD25Q16C"
|
|||||||
LONGINT_IMPL = MPZ
|
LONGINT_IMPL = MPZ
|
||||||
|
|
||||||
CIRCUITPY__EVE = 1
|
CIRCUITPY__EVE = 1
|
||||||
|
CIRCUITPY_SYNTHIO = 0
|
||||||
|
@ -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_buffer_info_t bufinfo;
|
||||||
mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_READ);
|
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);
|
synthio_miditrack_obj_t *self = m_new_obj(synthio_miditrack_obj_t);
|
||||||
self->base.type = &synthio_miditrack_type;
|
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,
|
(uint8_t *)bufinfo.buf, bufinfo.len,
|
||||||
args[ARG_tempo].u_int,
|
args[ARG_tempo].u_int,
|
||||||
args[ARG_sample_rate].u_int,
|
args[ARG_sample_rate].u_int,
|
||||||
bufinfo_waveform.buf,
|
args[ARG_waveform].u_obj,
|
||||||
bufinfo_waveform.len / 2,
|
mp_const_none,
|
||||||
args[ARG_envelope].u_obj
|
args[ARG_envelope].u_obj
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -27,12 +27,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "shared-module/synthio/MidiTrack.h"
|
#include "shared-module/synthio/MidiTrack.h"
|
||||||
|
#include "py/obj.h"
|
||||||
|
|
||||||
extern const mp_obj_type_t synthio_miditrack_type;
|
extern const mp_obj_type_t synthio_miditrack_type;
|
||||||
|
|
||||||
void common_hal_synthio_miditrack_construct(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, mp_obj_t waveform_obj, mp_obj_t filter_obj, mp_obj_t envelope_obj);
|
||||||
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_deinit(synthio_miditrack_obj_t *self);
|
void common_hal_synthio_miditrack_deinit(synthio_miditrack_obj_t *self);
|
||||||
bool common_hal_synthio_miditrack_deinited(synthio_miditrack_obj_t *self);
|
bool common_hal_synthio_miditrack_deinited(synthio_miditrack_obj_t *self);
|
||||||
|
@ -30,40 +30,45 @@
|
|||||||
#include "py/objproperty.h"
|
#include "py/objproperty.h"
|
||||||
#include "py/runtime.h"
|
#include "py/runtime.h"
|
||||||
#include "shared-bindings/util.h"
|
#include "shared-bindings/util.h"
|
||||||
|
#include "shared-bindings/synthio/__init__.h"
|
||||||
#include "shared-bindings/synthio/Note.h"
|
#include "shared-bindings/synthio/Note.h"
|
||||||
#include "shared-module/synthio/Note.h"
|
#include "shared-module/synthio/Note.h"
|
||||||
|
|
||||||
static const mp_arg_t note_properties[] = {
|
static const mp_arg_t note_properties[] = {
|
||||||
{ MP_QSTR_frequency, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = NULL } },
|
{ 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_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_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_bend_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_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_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_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:
|
//| class Note:
|
||||||
//| def __init__(
|
//| def __init__(
|
||||||
//| self,
|
//| self,
|
||||||
//| *,
|
//| *,
|
||||||
//| frequency: float,
|
//| frequency: float,
|
||||||
//| amplitude: float = 1.0,
|
//| panning: float = 0.0,
|
||||||
//| waveform: Optional[ReadableBuffer] = None,
|
//| waveform: Optional[ReadableBuffer] = None,
|
||||||
//| envelope: Optional[Envelope] = None,
|
//| envelope: Optional[Envelope] = None,
|
||||||
//| tremolo_depth: float = 0.0,
|
//| tremolo_depth: float = 0.0,
|
||||||
//| tremolo_rate: float = 0.0,
|
//| tremolo_rate: float = 0.0,
|
||||||
//| vibrato_depth: float = 0.0,
|
//| bend_depth: float = 0.0,
|
||||||
//| vibrato_rate: float = 0.0,
|
//| bend_rate: float = 0.0,
|
||||||
|
//| bend_mode: "BendMode" = BendMode.VIBRATO,
|
||||||
//| ) -> None:
|
//| ) -> 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 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.
|
//| 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) {
|
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_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);
|
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_get_frequency_obj,
|
||||||
(mp_obj_t)&synthio_note_set_frequency_obj);
|
(mp_obj_t)&synthio_note_set_frequency_obj);
|
||||||
|
|
||||||
//| amplitude: float
|
//| filter: bool
|
||||||
//| """The base amplitude of the note, from 0 to 1"""
|
//| """True if the note should be processed via the synthesizer's FIR filter."""
|
||||||
STATIC mp_obj_t synthio_note_get_amplitude(mp_obj_t self_in) {
|
STATIC mp_obj_t synthio_note_get_filter(mp_obj_t self_in) {
|
||||||
synthio_note_obj_t *self = MP_OBJ_TO_PTR(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);
|
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;
|
return mp_const_none;
|
||||||
}
|
}
|
||||||
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_amplitude_obj, synthio_note_set_amplitude);
|
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_filter_obj, synthio_note_set_filter);
|
||||||
MP_PROPERTY_GETSET(synthio_note_amplitude_obj,
|
MP_PROPERTY_GETSET(synthio_note_filter_obj,
|
||||||
(mp_obj_t)&synthio_note_get_amplitude_obj,
|
(mp_obj_t)&synthio_note_get_filter_obj,
|
||||||
(mp_obj_t)&synthio_note_set_amplitude_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
|
//| 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_get_tremolo_rate_obj,
|
||||||
(mp_obj_t)&synthio_note_set_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
|
//| bend_mode: BendMode
|
||||||
//| octave. A depth of (1/12) = 0.833 corresponds to a vibrato of ±1 semitone,
|
//| """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.
|
//| 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);
|
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);
|
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;
|
return mp_const_none;
|
||||||
}
|
}
|
||||||
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_vibrato_depth_obj, synthio_note_set_vibrato_depth);
|
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_bend_depth_obj, synthio_note_set_bend_depth);
|
||||||
MP_PROPERTY_GETSET(synthio_note_vibrato_depth_obj,
|
MP_PROPERTY_GETSET(synthio_note_bend_depth_obj,
|
||||||
(mp_obj_t)&synthio_note_get_vibrato_depth_obj,
|
(mp_obj_t)&synthio_note_get_bend_depth_obj,
|
||||||
(mp_obj_t)&synthio_note_set_vibrato_depth_obj);
|
(mp_obj_t)&synthio_note_set_bend_depth_obj);
|
||||||
|
|
||||||
//| vibrato_rate: float
|
//| bend_rate: float
|
||||||
//| """The vibrato rate of the note, in Hz."""
|
//| """The bend rate of the note, in Hz."""
|
||||||
STATIC mp_obj_t synthio_note_get_vibrato_rate(mp_obj_t self_in) {
|
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);
|
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);
|
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;
|
return mp_const_none;
|
||||||
}
|
}
|
||||||
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_vibrato_rate_obj, synthio_note_set_vibrato_rate);
|
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_bend_rate_obj, synthio_note_set_bend_rate);
|
||||||
MP_PROPERTY_GETSET(synthio_note_vibrato_rate_obj,
|
MP_PROPERTY_GETSET(synthio_note_bend_rate_obj,
|
||||||
(mp_obj_t)&synthio_note_get_vibrato_rate_obj,
|
(mp_obj_t)&synthio_note_get_bend_rate_obj,
|
||||||
(mp_obj_t)&synthio_note_set_vibrato_rate_obj);
|
(mp_obj_t)&synthio_note_set_bend_rate_obj);
|
||||||
|
|
||||||
//| waveform: Optional[ReadableBuffer]
|
//| waveform: Optional[ReadableBuffer]
|
||||||
//| """The waveform of this note. Setting the waveform to a buffer of a different size resets the note's phase."""
|
//| """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_get_envelope_obj,
|
||||||
(mp_obj_t)&synthio_note_set_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) {
|
static void note_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
|
||||||
(void)kind;
|
(void)kind;
|
||||||
properties_print_helper(print, self_in, note_properties, MP_ARRAY_SIZE(note_properties));
|
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[] = {
|
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_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_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_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_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_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_bend_depth), MP_ROM_PTR(&synthio_note_bend_depth_obj) },
|
||||||
{ MP_ROM_QSTR(MP_QSTR_vibrato_rate), MP_ROM_PTR(&synthio_note_vibrato_rate_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);
|
STATIC MP_DEFINE_CONST_DICT(synthio_note_locals_dict, synthio_note_locals_dict_table);
|
||||||
|
|
||||||
|
@ -4,12 +4,19 @@
|
|||||||
|
|
||||||
typedef struct synthio_note_obj synthio_note_obj_t;
|
typedef struct synthio_note_obj synthio_note_obj_t;
|
||||||
extern const mp_obj_type_t synthio_note_type;
|
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);
|
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);
|
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);
|
bool common_hal_synthio_note_get_filter(synthio_note_obj_t *self);
|
||||||
void common_hal_synthio_note_set_amplitude(synthio_note_obj_t *self, mp_float_t value);
|
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);
|
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);
|
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);
|
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);
|
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);
|
synthio_bend_mode_t common_hal_synthio_note_get_bend_mode(synthio_note_obj_t *self);
|
||||||
void common_hal_synthio_note_set_vibrato_rate(synthio_note_obj_t *self, mp_float_t value);
|
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);
|
mp_float_t common_hal_synthio_note_get_bend_rate(synthio_note_obj_t *self);
|
||||||
void common_hal_synthio_note_set_vibrato_depth(synthio_note_obj_t *self, mp_float_t value);
|
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);
|
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);
|
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);
|
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);
|
void common_hal_synthio_note_set_envelope(synthio_note_obj_t *self, mp_obj_t value);
|
||||||
|
@ -44,6 +44,7 @@
|
|||||||
//| self,
|
//| self,
|
||||||
//| *,
|
//| *,
|
||||||
//| sample_rate: int = 11025,
|
//| sample_rate: int = 11025,
|
||||||
|
//| channel_count: int = 1,
|
||||||
//| waveform: Optional[ReadableBuffer] = None,
|
//| waveform: Optional[ReadableBuffer] = None,
|
||||||
//| envelope: Optional[Envelope] = None,
|
//| envelope: Optional[Envelope] = None,
|
||||||
//| ) -> None:
|
//| ) -> None:
|
||||||
@ -56,29 +57,31 @@
|
|||||||
//| and do not support advanced features like tremolo or vibrato.
|
//| 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 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 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.
|
//| :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) {
|
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[] = {
|
static const mp_arg_t allowed_args[] = {
|
||||||
{ MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 11025} },
|
{ 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_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_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_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_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);
|
synthio_synthesizer_obj_t *self = m_new_obj(synthio_synthesizer_obj_t);
|
||||||
self->base.type = &synthio_synthesizer_type;
|
self->base.type = &synthio_synthesizer_type;
|
||||||
|
|
||||||
common_hal_synthio_synthesizer_construct(self,
|
common_hal_synthio_synthesizer_construct(self,
|
||||||
args[ARG_sample_rate].u_int,
|
args[ARG_sample_rate].u_int,
|
||||||
bufinfo_waveform.buf,
|
args[ARG_channel_count].u_int,
|
||||||
bufinfo_waveform.len / 2,
|
args[ARG_waveform].u_obj,
|
||||||
|
args[ARG_filter].u_obj,
|
||||||
args[ARG_envelope].u_obj);
|
args[ARG_envelope].u_obj);
|
||||||
|
|
||||||
return MP_OBJ_FROM_PTR(self);
|
return MP_OBJ_FROM_PTR(self);
|
||||||
|
@ -32,8 +32,8 @@
|
|||||||
extern const mp_obj_type_t synthio_synthesizer_type;
|
extern const mp_obj_type_t synthio_synthesizer_type;
|
||||||
|
|
||||||
void common_hal_synthio_synthesizer_construct(synthio_synthesizer_obj_t *self,
|
void common_hal_synthio_synthesizer_construct(synthio_synthesizer_obj_t *self,
|
||||||
uint32_t sample_rate, const int16_t *waveform, uint16_t waveform_length,
|
uint32_t sample_rate, int channel_count, mp_obj_t waveform_obj, mp_obj_t filter_obj,
|
||||||
mp_obj_t envelope);
|
mp_obj_t envelope_obj);
|
||||||
void common_hal_synthio_synthesizer_deinit(synthio_synthesizer_obj_t *self);
|
void common_hal_synthio_synthesizer_deinit(synthio_synthesizer_obj_t *self);
|
||||||
bool common_hal_synthio_synthesizer_deinited(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);
|
uint32_t common_hal_synthio_synthesizer_get_sample_rate(synthio_synthesizer_obj_t *self);
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "py/enum.h"
|
||||||
#include "py/mperrno.h"
|
#include "py/mperrno.h"
|
||||||
#include "py/obj.h"
|
#include "py/obj.h"
|
||||||
#include "py/objnamedtuple.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 } },
|
{ MP_QSTR_sustain_level, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL } },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//|
|
||||||
//| """Support for multi-channel audio synthesis
|
//| """Support for multi-channel audio synthesis
|
||||||
//|
|
//|
|
||||||
//| At least 2 simultaneous notes are supported. samd5x, mimxrt10xx and rp2040 platforms support up to 12 notes.
|
//| 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 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 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 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
|
//| :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);
|
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];
|
uint8_t chunk_header[14];
|
||||||
f_rewind(&file->fp);
|
f_rewind(&file->fp);
|
||||||
UINT bytes_read;
|
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;
|
result->base.type = &synthio_miditrack_type;
|
||||||
|
|
||||||
common_hal_synthio_miditrack_construct(result, buffer, track_size,
|
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
|
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);
|
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[] = {
|
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___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_MidiTrack), MP_ROM_PTR(&synthio_miditrack_type) },
|
||||||
{ MP_ROM_QSTR(MP_QSTR_Note), MP_ROM_PTR(&synthio_note_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) },
|
{ MP_ROM_QSTR(MP_QSTR_Synthesizer), MP_ROM_PTR(&synthio_synthesizer_type) },
|
||||||
|
@ -27,7 +27,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "py/objnamedtuple.h"
|
#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;
|
typedef struct synthio_synth synthio_synth_t;
|
||||||
extern int16_t shared_bindings_synthio_square_wave[];
|
extern int16_t shared_bindings_synthio_square_wave[];
|
||||||
extern const mp_obj_namedtuple_type_t synthio_envelope_type_obj;
|
extern const mp_obj_namedtuple_type_t synthio_envelope_type_obj;
|
||||||
|
@ -116,14 +116,13 @@ STATIC void start_parse(synthio_miditrack_obj_t *self) {
|
|||||||
|
|
||||||
void common_hal_synthio_miditrack_construct(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 uint8_t *buffer, uint32_t len, uint32_t tempo, uint32_t sample_rate,
|
||||||
const int16_t *waveform, uint16_t waveform_length,
|
mp_obj_t waveform_obj, mp_obj_t filter_obj, mp_obj_t envelope_obj) {
|
||||||
mp_obj_t envelope) {
|
|
||||||
|
|
||||||
self->tempo = tempo;
|
self->tempo = tempo;
|
||||||
self->track.buf = (void *)buffer;
|
self->track.buf = (void *)buffer;
|
||||||
self->track.len = len;
|
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);
|
start_parse(self);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
self->frequency_scaled = synthio_frequency_convert_float_to_scaled(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
mp_float_t common_hal_synthio_note_get_amplitude(synthio_note_obj_t *self) {
|
bool common_hal_synthio_note_get_filter(synthio_note_obj_t *self) {
|
||||||
return self->amplitude;
|
return self->filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
void common_hal_synthio_note_set_amplitude(synthio_note_obj_t *self, mp_float_t value_in) {
|
void common_hal_synthio_note_set_filter(synthio_note_obj_t *self, bool value_in) {
|
||||||
mp_float_t val = mp_arg_validate_float_range(value_in, 0, 1, MP_QSTR_amplitude);
|
self->filter = value_in;
|
||||||
self->amplitude = val;
|
}
|
||||||
self->amplitude_scaled = round_float_to_int(val * 32767);
|
|
||||||
|
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) {
|
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) {
|
mp_float_t common_hal_synthio_note_get_bend_depth(synthio_note_obj_t *self) {
|
||||||
return self->vibrato_descr.amplitude;
|
return self->bend_descr.amplitude;
|
||||||
}
|
}
|
||||||
|
|
||||||
void common_hal_synthio_note_set_vibrato_depth(synthio_note_obj_t *self, mp_float_t value_in) {
|
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, 0, 1, MP_QSTR_vibrato_depth);
|
mp_float_t val = mp_arg_validate_float_range(value_in, -1, 1, MP_QSTR_bend_depth);
|
||||||
self->vibrato_descr.amplitude = val;
|
self->bend_descr.amplitude = val;
|
||||||
self->vibrato_state.amplitude_scaled = round_float_to_int(val * 32767);
|
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) {
|
mp_float_t common_hal_synthio_note_get_bend_rate(synthio_note_obj_t *self) {
|
||||||
return self->vibrato_descr.frequency;
|
return self->bend_descr.frequency;
|
||||||
}
|
}
|
||||||
|
|
||||||
void common_hal_synthio_note_set_vibrato_rate(synthio_note_obj_t *self, mp_float_t value_in) {
|
synthio_bend_mode_t common_hal_synthio_note_get_bend_mode(synthio_note_obj_t *self) {
|
||||||
mp_float_t val = mp_arg_validate_float_range(value_in, 0, 60, MP_QSTR_vibrato_rate);
|
return self->bend_mode;
|
||||||
self->vibrato_descr.frequency = val;
|
}
|
||||||
|
|
||||||
|
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) {
|
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;
|
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) {
|
void synthio_note_recalculate(synthio_note_obj_t *self, int32_t sample_rate) {
|
||||||
if (sample_rate == self->sample_rate) {
|
if (sample_rate == self->sample_rate) {
|
||||||
return;
|
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);
|
synthio_lfo_set(&self->tremolo_state, &self->tremolo_descr, sample_rate);
|
||||||
self->tremolo_state.offset_scaled = 32768 - self->tremolo_state.amplitude_scaled;
|
self->tremolo_state.offset_scaled = 32768 - self->tremolo_state.amplitude_scaled;
|
||||||
synthio_lfo_set(&self->vibrato_state, &self->vibrato_descr, sample_rate);
|
synthio_lfo_set(&self->bend_state, &self->bend_descr, sample_rate);
|
||||||
self->vibrato_state.offset_scaled = 32768;
|
self->bend_state.offset_scaled = 32768;
|
||||||
}
|
}
|
||||||
|
|
||||||
void synthio_note_start(synthio_note_obj_t *self, int32_t sample_rate) {
|
void synthio_note_start(synthio_note_obj_t *self, int32_t sample_rate) {
|
||||||
synthio_note_recalculate(self, 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) {
|
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);
|
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 tremolo_value = synthio_lfo_step(&self->tremolo_state, dur);
|
||||||
int vibrato_value = synthio_lfo_step(&self->vibrato_state, dur);
|
loudness[0] = (((loudness[0] * tremolo_value) >> 15) * self->left_panning_scaled) >> 15;
|
||||||
*loudness = (*loudness * tremolo_value) >> 15;
|
loudness[1] = (((loudness[1] * tremolo_value) >> 15) * self->right_panning_scaled) >> 15;
|
||||||
uint32_t frequency_scaled = pitch_bend(self->frequency_scaled, vibrato_value);
|
int bend_value = synthio_bend_value(self, dur);
|
||||||
|
uint32_t frequency_scaled = pitch_bend(self->frequency_scaled, bend_value);
|
||||||
return frequency_scaled;
|
return frequency_scaled;
|
||||||
}
|
}
|
||||||
|
@ -27,27 +27,33 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "shared-module/synthio/__init__.h"
|
#include "shared-module/synthio/__init__.h"
|
||||||
|
#include "shared-bindings/synthio/__init__.h"
|
||||||
|
|
||||||
typedef struct synthio_note_obj {
|
typedef struct synthio_note_obj {
|
||||||
mp_obj_base_t base;
|
mp_obj_base_t base;
|
||||||
|
|
||||||
mp_float_t frequency;
|
mp_float_t frequency, ring_frequency;
|
||||||
mp_float_t amplitude;
|
mp_float_t panning;
|
||||||
mp_obj_t waveform_obj, envelope_obj;
|
mp_obj_t waveform_obj, envelope_obj, ring_waveform_obj;
|
||||||
|
|
||||||
int32_t sample_rate;
|
int32_t sample_rate;
|
||||||
|
|
||||||
int32_t frequency_scaled;
|
int32_t frequency_scaled;
|
||||||
|
int32_t ring_frequency_scaled;
|
||||||
int32_t amplitude_scaled;
|
int32_t amplitude_scaled;
|
||||||
synthio_lfo_descr_t tremolo_descr, vibrato_descr;
|
int32_t left_panning_scaled, right_panning_scaled;
|
||||||
synthio_lfo_state_t tremolo_state, vibrato_state;
|
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 waveform_buf;
|
||||||
|
mp_buffer_info_t ring_waveform_buf;
|
||||||
synthio_envelope_definition_t envelope_def;
|
synthio_envelope_definition_t envelope_def;
|
||||||
} synthio_note_obj_t;
|
} synthio_note_obj_t;
|
||||||
|
|
||||||
void synthio_note_recalculate(synthio_note_obj_t *self, int32_t sample_rate);
|
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);
|
void synthio_note_start(synthio_note_obj_t *self, int32_t sample_rate);
|
||||||
bool synthio_note_playing(synthio_note_obj_t *self);
|
bool synthio_note_playing(synthio_note_obj_t *self);
|
||||||
uint32_t synthio_note_envelope(synthio_note_obj_t *self);
|
uint32_t synthio_note_envelope(synthio_note_obj_t *self);
|
||||||
|
@ -32,10 +32,10 @@
|
|||||||
|
|
||||||
|
|
||||||
void common_hal_synthio_synthesizer_construct(synthio_synthesizer_obj_t *self,
|
void common_hal_synthio_synthesizer_construct(synthio_synthesizer_obj_t *self,
|
||||||
uint32_t sample_rate, const int16_t *waveform, uint16_t waveform_length,
|
uint32_t sample_rate, int channel_count, mp_obj_t waveform_obj, mp_obj_t filter_obj,
|
||||||
mp_obj_t envelope) {
|
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) {
|
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;
|
return SYNTHIO_BITS_PER_SAMPLE;
|
||||||
}
|
}
|
||||||
uint8_t common_hal_synthio_synthesizer_get_channel_count(synthio_synthesizer_obj_t *self) {
|
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,
|
void synthio_synthesizer_reset_buffer(synthio_synthesizer_obj_t *self,
|
||||||
|
@ -89,7 +89,7 @@ void synthio_envelope_definition_set(synthio_envelope_definition_t *envelope, mp
|
|||||||
|
|
||||||
envelope->release_step = -convert_time_to_rate(
|
envelope->release_step = -convert_time_to_rate(
|
||||||
sample_rate, fields[2],
|
sample_rate, fields[2],
|
||||||
envelope->decay_step
|
envelope->sustain_level
|
||||||
? envelope->sustain_level
|
? envelope->sustain_level
|
||||||
: envelope->attack_level);
|
: envelope->attack_level);
|
||||||
}
|
}
|
||||||
@ -171,6 +171,175 @@ int16_t mix_down_sample(int32_t sample) {
|
|||||||
return 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) {
|
void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t *buffer_length, uint8_t channel) {
|
||||||
|
|
||||||
if (channel == synth->other_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);
|
uint16_t dur = MIN(SYNTHIO_MAX_DUR, synth->span.dur);
|
||||||
synth->span.dur -= dur;
|
synth->span.dur -= dur;
|
||||||
|
|
||||||
int32_t sample_rate = synth->sample_rate;
|
int32_t out_buffer32[dur * synth->channel_count];
|
||||||
int32_t out_buffer32[dur];
|
|
||||||
|
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++) {
|
for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
|
||||||
mp_obj_t note_obj = synth->span.note_obj[chan];
|
mp_obj_t note_obj = synth->span.note_obj[chan];
|
||||||
if (note_obj == SYNTHIO_SILENCE) {
|
if (!synthio_synth_get_note_filtered(note_obj)) {
|
||||||
synth->accum[chan] = 0;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
synth_note_into_buffer(synth, chan, filter_start, dur);
|
||||||
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
|
run_fir(synth, out_buffer32, dur);
|
||||||
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 {
|
} else {
|
||||||
synthio_note_obj_t *note = MP_OBJ_TO_PTR(note_obj);
|
memset(out_buffer32, 0, sizeof(out_buffer32));
|
||||||
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];
|
for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
|
||||||
uint32_t lim = waveform_length << SYNTHIO_FREQUENCY_SHIFT;
|
mp_obj_t note_obj = synth->span.note_obj[chan];
|
||||||
if (dds_rate > lim / 2) {
|
if (synth->filter_buffer && synthio_synth_get_note_filtered(note_obj)) {
|
||||||
// beyond nyquist, can't play note
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
synth_note_into_buffer(synth, chan, out_buffer32, dur);
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int16_t *out_buffer16 = (int16_t *)(void *)synth->buffers[synth->buffer_index];
|
int16_t *out_buffer16 = (int16_t *)(void *)synth->buffers[synth->buffer_index];
|
||||||
|
|
||||||
// mix down audio
|
// 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];
|
int32_t sample = out_buffer32[i];
|
||||||
out_buffer16[i] = mix_down_sample(sample);
|
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);
|
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;
|
*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) {
|
void synthio_synth_deinit(synthio_synth_t *synth) {
|
||||||
m_del(uint8_t, synth->buffers[0], synth->buffer_length);
|
synth->filter_buffer = NULL;
|
||||||
m_del(uint8_t, synth->buffers[1], synth->buffer_length);
|
|
||||||
synth->buffers[0] = NULL;
|
synth->buffers[0] = NULL;
|
||||||
synth->buffers[1] = NULL;
|
synth->buffers[1] = NULL;
|
||||||
}
|
}
|
||||||
@ -301,13 +429,20 @@ mp_obj_t synthio_synth_envelope_get(synthio_synth_t *synth) {
|
|||||||
return synth->envelope_obj;
|
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) {
|
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) {
|
||||||
synth->buffer_length = SYNTHIO_MAX_DUR * SYNTHIO_BYTES_PER_SAMPLE;
|
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[0] = m_malloc(synth->buffer_length, false);
|
||||||
synth->buffers[1] = 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->other_channel = -1;
|
||||||
synth->waveform = waveform;
|
synth->waveform_obj = waveform_obj;
|
||||||
synth->waveform_length = waveform_length;
|
|
||||||
synth->sample_rate = sample_rate;
|
synth->sample_rate = sample_rate;
|
||||||
synthio_synth_envelope_set(synth, envelope_obj);
|
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;
|
*single_buffer = false;
|
||||||
*samples_signed = true;
|
*samples_signed = true;
|
||||||
*max_buffer_length = synth->buffer_length;
|
*max_buffer_length = synth->buffer_length;
|
||||||
|
if (single_channel_output) {
|
||||||
|
*spacing = synth->channel_count;
|
||||||
|
} else {
|
||||||
*spacing = 1;
|
*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) {
|
if (o != mp_const_none) {
|
||||||
mp_get_buffer_raise(o, bufinfo, MP_BUFFER_READ);
|
mp_get_buffer_raise(o, bufinfo, MP_BUFFER_READ);
|
||||||
if (bufinfo->typecode != 'h') {
|
if (bufinfo->typecode != 'h') {
|
||||||
mp_raise_ValueError_varg(translate("%q must be array of type 'h'"), what);
|
mp_raise_ValueError_varg(translate("%q must be array of type 'h'"), what);
|
||||||
}
|
}
|
||||||
mp_arg_validate_length_range(bufinfo->len / 2, 2, 1024, what);
|
mp_arg_validate_length_range(bufinfo->len / sizeof(int16_t), 2, max_len, what);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void synthio_synth_parse_waveform(mp_buffer_info_t *bufinfo_waveform, mp_obj_t waveform_obj) {
|
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 });
|
*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) {
|
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);
|
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;
|
uint32_t phase = state->phase;
|
||||||
uint16_t whole_phase = phase >> 16;
|
uint16_t whole_phase = phase >> 16;
|
||||||
|
|
||||||
// advance the phase accumulator
|
// advance the phase accumulator
|
||||||
state->phase = phase + state->dds * dur;
|
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
|
// create a triangle wave, it's quick and easy
|
||||||
int v;
|
int v;
|
||||||
if (whole_phase < 16384) { // ramp from 0 to amplitude
|
if (whole_phase < 16384) { // ramp from 0 to amplitude
|
||||||
|
@ -64,15 +64,17 @@ typedef struct synthio_synth {
|
|||||||
uint32_t sample_rate;
|
uint32_t sample_rate;
|
||||||
uint32_t total_envelope;
|
uint32_t total_envelope;
|
||||||
int16_t *buffers[2];
|
int16_t *buffers[2];
|
||||||
const int16_t *waveform;
|
int32_t *filter_buffer;
|
||||||
uint16_t buffer_length;
|
uint8_t channel_count;
|
||||||
|
uint16_t buffer_length, filter_buffer_length;
|
||||||
uint16_t last_buffer_length;
|
uint16_t last_buffer_length;
|
||||||
uint8_t other_channel, buffer_index, other_buffer_index;
|
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;
|
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;
|
synthio_midi_span_t span;
|
||||||
uint32_t accum[CIRCUITPY_SYNTHIO_MAX_CHANNELS];
|
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_envelope_state_t envelope_state[CIRCUITPY_SYNTHIO_MAX_CHANNELS];
|
||||||
} synthio_synth_t;
|
} synthio_synth_t;
|
||||||
|
|
||||||
@ -81,19 +83,20 @@ typedef struct {
|
|||||||
} synthio_lfo_descr_t;
|
} synthio_lfo_descr_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t amplitude_scaled, offset_scaled, dds, phase;
|
int32_t amplitude_scaled;
|
||||||
|
uint32_t offset_scaled, dds, phase;
|
||||||
} synthio_lfo_state_t;
|
} 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_synthesize(synthio_synth_t *synth, uint8_t **buffer, uint32_t *buffer_length, uint8_t channel);
|
||||||
void synthio_synth_deinit(synthio_synth_t *synth);
|
void synthio_synth_deinit(synthio_synth_t *synth);
|
||||||
bool synthio_synth_deinited(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,
|
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);
|
||||||
mp_obj_t envelope);
|
|
||||||
void synthio_synth_get_buffer_structure(synthio_synth_t *synth, bool single_channel_output,
|
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);
|
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_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_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);
|
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);
|
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);
|
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_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);
|
||||||
|
@ -1 +0,0 @@
|
|||||||
../wave/audioop.py
|
|
@ -1 +0,0 @@
|
|||||||
../wave/chunk.py
|
|
@ -1,8 +1,14 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(
|
||||||
|
0, f"{__file__.rpartition('/')[0] or '.'}/../../../../frozen/Adafruit_CircuitPython_Wave"
|
||||||
|
)
|
||||||
|
|
||||||
import random
|
import random
|
||||||
import audiocore
|
import audiocore
|
||||||
import synthio
|
import synthio
|
||||||
from ulab import numpy as np
|
from ulab import numpy as np
|
||||||
import wave
|
import adafruit_wave as wave
|
||||||
|
|
||||||
SAMPLE_SIZE = 1024
|
SAMPLE_SIZE = 1024
|
||||||
VOLUME = 14700
|
VOLUME = 14700
|
||||||
@ -12,7 +18,7 @@ sine = np.array(
|
|||||||
)
|
)
|
||||||
|
|
||||||
envelope = synthio.Envelope(
|
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)
|
synth = synthio.Synthesizer(sample_rate=48000)
|
||||||
@ -57,8 +63,8 @@ def synthesize2(synth):
|
|||||||
def synthesize3(synth):
|
def synthesize3(synth):
|
||||||
n = synthio.Note(
|
n = synthio.Note(
|
||||||
frequency=synthio.midi_to_hz(60),
|
frequency=synthio.midi_to_hz(60),
|
||||||
vibrato_depth=0.1,
|
bend_depth=0.1,
|
||||||
vibrato_rate=8,
|
bend_rate=8,
|
||||||
waveform=sine,
|
waveform=sine,
|
||||||
envelope=envelope,
|
envelope=envelope,
|
||||||
)
|
)
|
||||||
@ -73,8 +79,8 @@ def synthesize4(synth):
|
|||||||
frequency=synthio.midi_to_hz(60),
|
frequency=synthio.midi_to_hz(60),
|
||||||
tremolo_depth=0.1,
|
tremolo_depth=0.1,
|
||||||
tremolo_rate=1.5,
|
tremolo_rate=1.5,
|
||||||
vibrato_depth=0.1,
|
bend_depth=0.1,
|
||||||
vibrato_rate=3,
|
bend_rate=3,
|
||||||
waveform=sine,
|
waveform=sine,
|
||||||
envelope=envelope,
|
envelope=envelope,
|
||||||
)
|
)
|
||||||
@ -103,6 +109,23 @@ def synthesize5(synth):
|
|||||||
yield 36
|
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):
|
def chain(*args):
|
||||||
for a in args:
|
for a in args:
|
||||||
yield from a
|
yield from a
|
||||||
@ -113,7 +136,7 @@ with wave.open("tune-noenv.wav", "w") as f:
|
|||||||
f.setnchannels(1)
|
f.setnchannels(1)
|
||||||
f.setsampwidth(2)
|
f.setsampwidth(2)
|
||||||
f.setframerate(48000)
|
f.setframerate(48000)
|
||||||
for n in chain(synthesize5(synth)):
|
for n in chain(synthesize6(synth)):
|
||||||
for i in range(n):
|
for i in range(n):
|
||||||
result, data = audiocore.get_buffer(synth)
|
result, data = audiocore.get_buffer(synth)
|
||||||
f.writeframes(data)
|
f.writeframes(data)
|
||||||
|
58
tests/circuitpython-manual/synthio/note/envelope.py
Normal file
58
tests/circuitpython-manual/synthio/note/envelope.py
Normal 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)
|
104
tests/circuitpython-manual/synthio/note/fir.py
Normal file
104
tests/circuitpython-manual/synthio/note/fir.py
Normal 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)
|
71
tests/circuitpython-manual/synthio/note/noise.py
Normal file
71
tests/circuitpython-manual/synthio/note/noise.py
Normal 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)
|
57
tests/circuitpython-manual/synthio/note/panning.py
Normal file
57
tests/circuitpython-manual/synthio/note/panning.py
Normal 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)
|
60
tests/circuitpython-manual/synthio/note/ring.py
Normal file
60
tests/circuitpython-manual/synthio/note/ring.py
Normal 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)
|
@ -1 +0,0 @@
|
|||||||
../wave/wave.py
|
|
@ -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"
|
|
@ -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
|
|
@ -1,7 +1,13 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(
|
||||||
|
0, f"{__file__.rpartition('/')[0] or '.'}/../../../../frozen/Adafruit_CircuitPython_Wave"
|
||||||
|
)
|
||||||
|
|
||||||
import audiocore
|
import audiocore
|
||||||
import synthio
|
import synthio
|
||||||
from ulab import numpy as np
|
from ulab import numpy as np
|
||||||
import wave
|
import adafruit_wave as wave
|
||||||
|
|
||||||
SAMPLE_SIZE = 1024
|
SAMPLE_SIZE = 1024
|
||||||
VOLUME = 32700
|
VOLUME = 32700
|
||||||
|
@ -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'")
|
|
@ -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]
|
[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]
|
[-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]
|
[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]
|
[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)
|
(-5242, 5242)
|
||||||
(-10485, 10484)
|
(-10485, 10484)
|
||||||
|
Loading…
Reference in New Issue
Block a user