synthio: implement envelope
This works for me (tested playing midi to raw files on host computer, as well as a variant of the nunchuk instrument on pygamer) it has to re-factor how/when MIDI reading occurs, because reasons. endorse new test results .. and allow `-1` to specify a note with no sustain (plucked)
This commit is contained in:
parent
375a9cd4c5
commit
12c1a72f03
@ -27,6 +27,7 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "py/obj.h"
|
#include "py/obj.h"
|
||||||
|
#include "py/gc.h"
|
||||||
#include "py/runtime.h"
|
#include "py/runtime.h"
|
||||||
|
|
||||||
#include "shared-bindings/audiocore/__init__.h"
|
#include "shared-bindings/audiocore/__init__.h"
|
||||||
@ -46,8 +47,23 @@ STATIC mp_obj_t audiocore_get_buffer(mp_obj_t sample_in) {
|
|||||||
mp_obj_t result[2] = {mp_obj_new_int_from_uint(gbr), mp_const_none};
|
mp_obj_t result[2] = {mp_obj_new_int_from_uint(gbr), mp_const_none};
|
||||||
|
|
||||||
if (gbr != GET_BUFFER_ERROR) {
|
if (gbr != GET_BUFFER_ERROR) {
|
||||||
|
bool single_buffer, samples_signed;
|
||||||
|
uint32_t max_buffer_length;
|
||||||
|
uint8_t spacing;
|
||||||
|
|
||||||
|
uint8_t bits_per_sample = audiosample_bits_per_sample(sample_in);
|
||||||
|
audiosample_get_buffer_structure(sample_in, false, &single_buffer, &samples_signed, &max_buffer_length, &spacing);
|
||||||
// copies the data because the gc semantics of get_buffer are unclear
|
// copies the data because the gc semantics of get_buffer are unclear
|
||||||
result[1] = mp_obj_new_bytes(buffer, buffer_length);
|
void *result_buf = gc_alloc(buffer_length, 0, false);
|
||||||
|
memcpy(result_buf, buffer, buffer_length);
|
||||||
|
char typecode =
|
||||||
|
(bits_per_sample == 8 && samples_signed) ? 'b' :
|
||||||
|
(bits_per_sample == 8 && !samples_signed) ? 'B' :
|
||||||
|
(bits_per_sample == 16 && samples_signed) ? 'h' :
|
||||||
|
(bits_per_sample == 16 && !samples_signed) ? 'H' :
|
||||||
|
'b';
|
||||||
|
size_t nitems = buffer_length / (bits_per_sample / 8);
|
||||||
|
result[1] = mp_obj_new_memoryview(typecode, nitems, result_buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
return mp_obj_new_tuple(2, result);
|
return mp_obj_new_tuple(2, result);
|
||||||
|
@ -44,7 +44,8 @@
|
|||||||
//| tempo: int,
|
//| tempo: int,
|
||||||
//| *,
|
//| *,
|
||||||
//| sample_rate: int = 11025,
|
//| sample_rate: int = 11025,
|
||||||
//| waveform: ReadableBuffer = None
|
//| waveform: Optional[ReadableBuffer] = None,
|
||||||
|
//| envelope: Optional[Envelope] = None,
|
||||||
//| ) -> None:
|
//| ) -> None:
|
||||||
//| """Create a MidiTrack from the given stream of MIDI events. Only "Note On" and "Note Off" events
|
//| """Create a MidiTrack from the given stream of MIDI events. Only "Note On" and "Note Off" events
|
||||||
//| are supported; channel numbers and key velocities are ignored. Up to two notes may be on at the
|
//| are supported; channel numbers and key velocities are ignored. Up to two notes may be on at the
|
||||||
@ -54,6 +55,7 @@
|
|||||||
//| :param int tempo: Tempo of the streamed events, in MIDI ticks per second
|
//| :param int tempo: Tempo of the streamed events, in MIDI ticks per second
|
||||||
//| :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 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 Envelope envelope: An object that defines the loudness of a note over time. The default envelope provides no ramping, voices turn instantly on and off.
|
||||||
//|
|
//|
|
||||||
//| Simple melody::
|
//| Simple melody::
|
||||||
//|
|
//|
|
||||||
@ -72,12 +74,13 @@
|
|||||||
//| print("stopped")"""
|
//| print("stopped")"""
|
||||||
//| ...
|
//| ...
|
||||||
STATIC mp_obj_t synthio_miditrack_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_miditrack_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
|
||||||
enum { ARG_buffer, ARG_tempo, ARG_sample_rate, ARG_waveform };
|
enum { ARG_buffer, ARG_tempo, ARG_sample_rate, ARG_waveform, ARG_envelope };
|
||||||
static const mp_arg_t allowed_args[] = {
|
static const mp_arg_t allowed_args[] = {
|
||||||
{ MP_QSTR_buffer, MP_ARG_OBJ | MP_ARG_REQUIRED },
|
{ MP_QSTR_buffer, MP_ARG_OBJ | MP_ARG_REQUIRED },
|
||||||
{ MP_QSTR_tempo, MP_ARG_INT | MP_ARG_REQUIRED },
|
{ MP_QSTR_tempo, MP_ARG_INT | MP_ARG_REQUIRED },
|
||||||
{ 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_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_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);
|
||||||
@ -96,7 +99,9 @@ STATIC mp_obj_t synthio_miditrack_make_new(const mp_obj_type_t *type, size_t n_a
|
|||||||
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,
|
bufinfo_waveform.buf,
|
||||||
bufinfo_waveform.len / 2);
|
bufinfo_waveform.len / 2,
|
||||||
|
args[ARG_envelope].u_obj
|
||||||
|
);
|
||||||
|
|
||||||
return MP_OBJ_FROM_PTR(self);
|
return MP_OBJ_FROM_PTR(self);
|
||||||
}
|
}
|
||||||
|
@ -24,20 +24,18 @@
|
|||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef MICROPY_INCLUDED_SHARED_BINDINGS_SYNTHIO_MIDITRACK_H
|
#pragma once
|
||||||
#define MICROPY_INCLUDED_SHARED_BINDINGS_SYNTHIO_MIDITRACK_H
|
|
||||||
|
|
||||||
#include "shared-module/synthio/MidiTrack.h"
|
#include "shared-module/synthio/MidiTrack.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, const int16_t *waveform, uint16_t waveform_len);
|
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);
|
||||||
uint32_t common_hal_synthio_miditrack_get_sample_rate(synthio_miditrack_obj_t *self);
|
uint32_t common_hal_synthio_miditrack_get_sample_rate(synthio_miditrack_obj_t *self);
|
||||||
uint8_t common_hal_synthio_miditrack_get_bits_per_sample(synthio_miditrack_obj_t *self);
|
uint8_t common_hal_synthio_miditrack_get_bits_per_sample(synthio_miditrack_obj_t *self);
|
||||||
uint8_t common_hal_synthio_miditrack_get_channel_count(synthio_miditrack_obj_t *self);
|
uint8_t common_hal_synthio_miditrack_get_channel_count(synthio_miditrack_obj_t *self);
|
||||||
|
|
||||||
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_SYNTHIO_MIDITRACK_H
|
|
||||||
|
@ -37,24 +37,29 @@
|
|||||||
#include "supervisor/shared/translate/translate.h"
|
#include "supervisor/shared/translate/translate.h"
|
||||||
|
|
||||||
//| class Synthesizer:
|
//| class Synthesizer:
|
||||||
//| def __init__(self, *, sample_rate: int = 11025, waveform: ReadableBuffer = None) -> None:
|
//| def __init__(
|
||||||
|
//| self,
|
||||||
|
//| *,
|
||||||
|
//| sample_rate: int = 11025,
|
||||||
|
//| waveform: Optional[ReadableBuffer] = None,
|
||||||
|
//| envelope: Optional[Envelope] = None,
|
||||||
|
//| ) -> None:
|
||||||
//| """Create a synthesizer object.
|
//| """Create a synthesizer object.
|
||||||
//|
|
//|
|
||||||
//| This API is experimental.
|
//| This API is experimental.
|
||||||
//|
|
//|
|
||||||
//| At least 2 simultaneous notes are supported. mimxrt10xx and rp2040 platforms support up to
|
|
||||||
//| 12 notes.
|
|
||||||
//|
|
|
||||||
//| Notes use MIDI note numbering, with 60 being C4 or Middle C, approximately 262Hz.
|
//| Notes use MIDI note numbering, with 60 being C4 or Middle C, approximately 262Hz.
|
||||||
//|
|
//|
|
||||||
//| :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 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). It is permitted to modify this buffer during synthesis. This can be used, for instance, to control the overall volume or timbre of the notes.
|
//| :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 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 };
|
enum { ARG_sample_rate, ARG_waveform, ARG_envelope };
|
||||||
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_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_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);
|
||||||
@ -68,7 +73,9 @@ STATIC mp_obj_t synthio_synthesizer_make_new(const mp_obj_type_t *type, size_t n
|
|||||||
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,
|
bufinfo_waveform.buf,
|
||||||
bufinfo_waveform.len / 2);
|
bufinfo_waveform.len / 2,
|
||||||
|
args[ARG_envelope].u_obj);
|
||||||
|
|
||||||
|
|
||||||
return MP_OBJ_FROM_PTR(self);
|
return MP_OBJ_FROM_PTR(self);
|
||||||
}
|
}
|
||||||
@ -191,6 +198,28 @@ STATIC mp_obj_t synthio_synthesizer_obj___exit__(size_t n_args, const mp_obj_t *
|
|||||||
return mp_const_none;
|
return mp_const_none;
|
||||||
}
|
}
|
||||||
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(synthio_synthesizer___exit___obj, 4, 4, synthio_synthesizer_obj___exit__);
|
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(synthio_synthesizer___exit___obj, 4, 4, synthio_synthesizer_obj___exit__);
|
||||||
|
|
||||||
|
//| envelope: Optional[Envelope]
|
||||||
|
//| """The envelope to apply to all notes. `None`, the default envelope, instantly turns notes on and off. The envelope may be changed dynamically, but it affects all notes (even currently playing notes)"""
|
||||||
|
STATIC mp_obj_t synthio_synthesizer_obj_get_envelope(mp_obj_t self_in) {
|
||||||
|
synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in);
|
||||||
|
check_for_deinit(self);
|
||||||
|
return synthio_synth_envelope_get(&self->synth);
|
||||||
|
}
|
||||||
|
MP_DEFINE_CONST_FUN_OBJ_1(synthio_synthesizer_get_envelope_obj, synthio_synthesizer_obj_get_envelope);
|
||||||
|
|
||||||
|
STATIC mp_obj_t synthio_synthesizer_obj_set_envelope(mp_obj_t self_in, mp_obj_t envelope) {
|
||||||
|
synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in);
|
||||||
|
check_for_deinit(self);
|
||||||
|
synthio_synth_envelope_set(&self->synth, envelope);
|
||||||
|
return mp_const_none;
|
||||||
|
}
|
||||||
|
MP_DEFINE_CONST_FUN_OBJ_2(synthio_synthesizer_set_envelope_obj, synthio_synthesizer_obj_set_envelope);
|
||||||
|
|
||||||
|
MP_PROPERTY_GETSET(synthio_synthesizer_envelope_obj,
|
||||||
|
(mp_obj_t)&synthio_synthesizer_get_envelope_obj,
|
||||||
|
(mp_obj_t)&synthio_synthesizer_set_envelope_obj);
|
||||||
|
|
||||||
//| sample_rate: int
|
//| sample_rate: int
|
||||||
//| """32 bit value that tells how quickly samples are played in Hertz (cycles per second)."""
|
//| """32 bit value that tells how quickly samples are played in Hertz (cycles per second)."""
|
||||||
STATIC mp_obj_t synthio_synthesizer_obj_get_sample_rate(mp_obj_t self_in) {
|
STATIC mp_obj_t synthio_synthesizer_obj_get_sample_rate(mp_obj_t self_in) {
|
||||||
@ -232,6 +261,7 @@ STATIC const mp_rom_map_elem_t synthio_synthesizer_locals_dict_table[] = {
|
|||||||
{ MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&synthio_synthesizer___exit___obj) },
|
{ MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&synthio_synthesizer___exit___obj) },
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
|
{ MP_ROM_QSTR(MP_QSTR_envelope), MP_ROM_PTR(&synthio_synthesizer_envelope_obj) },
|
||||||
{ MP_ROM_QSTR(MP_QSTR_sample_rate), MP_ROM_PTR(&synthio_synthesizer_sample_rate_obj) },
|
{ MP_ROM_QSTR(MP_QSTR_sample_rate), MP_ROM_PTR(&synthio_synthesizer_sample_rate_obj) },
|
||||||
{ MP_ROM_QSTR(MP_QSTR_max_polyphony), MP_ROM_INT(CIRCUITPY_SYNTHIO_MAX_CHANNELS) },
|
{ MP_ROM_QSTR(MP_QSTR_max_polyphony), MP_ROM_INT(CIRCUITPY_SYNTHIO_MAX_CHANNELS) },
|
||||||
{ MP_ROM_QSTR(MP_QSTR_pressed), MP_ROM_PTR(&synthio_synthesizer_pressed_obj) },
|
{ MP_ROM_QSTR(MP_QSTR_pressed), MP_ROM_PTR(&synthio_synthesizer_pressed_obj) },
|
||||||
|
@ -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_len);
|
uint32_t sample_rate, const int16_t *waveform, uint16_t waveform_length,
|
||||||
|
mp_obj_t envelope);
|
||||||
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);
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
#include "py/mperrno.h"
|
#include "py/mperrno.h"
|
||||||
#include "py/obj.h"
|
#include "py/obj.h"
|
||||||
|
#include "py/objnamedtuple.h"
|
||||||
#include "py/runtime.h"
|
#include "py/runtime.h"
|
||||||
#include "extmod/vfs_fat.h"
|
#include "extmod/vfs_fat.h"
|
||||||
#include "extmod/vfs_posix.h"
|
#include "extmod/vfs_posix.h"
|
||||||
@ -36,16 +37,111 @@
|
|||||||
#include "shared-bindings/synthio/MidiTrack.h"
|
#include "shared-bindings/synthio/MidiTrack.h"
|
||||||
#include "shared-bindings/synthio/Synthesizer.h"
|
#include "shared-bindings/synthio/Synthesizer.h"
|
||||||
|
|
||||||
//| """Support for MIDI synthesis"""
|
//| """Support for multi-channel audio synthesis
|
||||||
//|
|
//|
|
||||||
//| def from_file(file: typing.BinaryIO, *, sample_rate: int = 11025) -> MidiTrack:
|
//| At least 2 simultaneous notes are supported. samd5x, mimxrt10xx and rp2040 platforms support up to 12 notes.
|
||||||
|
//|
|
||||||
|
//| I'm a little teapot. I'm not on line 11, but I don't know what is.
|
||||||
|
//| """
|
||||||
|
//|
|
||||||
|
//| class Envelope:
|
||||||
|
//| def __init__(
|
||||||
|
//| self,
|
||||||
|
//| attack_time: float,
|
||||||
|
//| decay_time: float,
|
||||||
|
//| release_time: float,
|
||||||
|
//| attack_level: float,
|
||||||
|
//| sustain_level: float,
|
||||||
|
//| ) -> None:
|
||||||
|
//| """Construct an Envelope object
|
||||||
|
//|
|
||||||
|
//| The Envelope defines an ADSR (Attack, Decay, Sustain, Release) envelope with linear amplitude ramping. A note starts at 0 volume, then increases to ``attack_level`` over ``attack_time`` seconds; then it decays to ``sustain_level`` over ``decay_time`` seconds. Finally, when the note is released, it decreases to ``0`` volume over ``release_time``.
|
||||||
|
//|
|
||||||
|
//| If the ``sustain_level`` of an envelope is 0, then the decay and sustain phases of the note are always omitted. The note is considered to be released as soon as the envelope reaches the end of the attack phase. The ``decay_time`` is ignored. This is similar to how a plucked or struck instrument behaves.
|
||||||
|
//|
|
||||||
|
//| If a note is released before it reaches its sustain phase, it decays with the same slope indicated by ``sustain_level/release_time`` (or ``attack_level/release_time`` for plucked envelopes)
|
||||||
|
//|
|
||||||
|
//| :param float attack_time: The time in seconds it takes to ramp from 0 volume to attack_volume
|
||||||
|
//| :param float decay_time: The time in seconds it takes to ramp from attack_volume to sustain_volume
|
||||||
|
//| :param float release_time: The time in seconds it takes to ramp from sustain_volume to release_volume. When a note is released before it has reached the sustain phase, the release is done with the same slope indicated by ``release_time`` and ``sustain_level``
|
||||||
|
//| :param float attack_level: The relative level, in the range ``0.0`` to ``1.0`` of the peak volume of the attack phase
|
||||||
|
//| :param float sustain_level: The relative level, in the range ``0.0`` to ``1.0`` of the volume of the sustain phase
|
||||||
|
//| """
|
||||||
|
//| attack_time: float
|
||||||
|
//| """The time in seconds it takes to ramp from 0 volume to attack_volume"""
|
||||||
|
//|
|
||||||
|
//| decay_time: float
|
||||||
|
//| """The time in seconds it takes to ramp from attack_volume to sustain_volume"""
|
||||||
|
//|
|
||||||
|
//| release_time: float
|
||||||
|
//| """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``"""
|
||||||
|
//|
|
||||||
|
//| attack_level: float
|
||||||
|
//| """The relative level, in the range ``0.0`` to ``1.0`` of the peak volume of the attack phase"""
|
||||||
|
//|
|
||||||
|
//| sustain_level: float
|
||||||
|
//| """The relative level, in the range ``0.0`` to ``1.0`` of the volume of the sustain phase"""
|
||||||
|
//|
|
||||||
|
|
||||||
|
STATIC mp_obj_t synthio_envelope_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) {
|
||||||
|
mp_obj_t new_obj = namedtuple_make_new(type_in, n_args, n_kw, args);
|
||||||
|
mp_obj_t *fields;
|
||||||
|
size_t len;
|
||||||
|
mp_obj_tuple_get(new_obj, &len, &fields);
|
||||||
|
|
||||||
|
mp_arg_validate_obj_float_non_negative(fields[0], 0., MP_QSTR_attack_time);
|
||||||
|
mp_arg_validate_obj_float_non_negative(fields[1], 0., MP_QSTR_decay_time);
|
||||||
|
mp_arg_validate_obj_float_non_negative(fields[2], 0., MP_QSTR_release_time);
|
||||||
|
|
||||||
|
mp_arg_validate_obj_float_range(fields[3], 0, 1, MP_QSTR_attack_level);
|
||||||
|
mp_arg_validate_obj_float_range(fields[4], 0, 1, MP_QSTR_sustain_level);
|
||||||
|
|
||||||
|
return new_obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mp_obj_namedtuple_type_t synthio_envelope_type_obj = {
|
||||||
|
.base = {
|
||||||
|
.base = {
|
||||||
|
.type = &mp_type_type
|
||||||
|
},
|
||||||
|
.flags = MP_TYPE_FLAG_EXTENDED,
|
||||||
|
.name = MP_QSTR_Envelope,
|
||||||
|
.print = namedtuple_print,
|
||||||
|
.parent = &mp_type_tuple,
|
||||||
|
.make_new = synthio_envelope_make_new,
|
||||||
|
.attr = namedtuple_attr,
|
||||||
|
MP_TYPE_EXTENDED_FIELDS(
|
||||||
|
.unary_op = mp_obj_tuple_unary_op,
|
||||||
|
.binary_op = mp_obj_tuple_binary_op,
|
||||||
|
.subscr = mp_obj_tuple_subscr,
|
||||||
|
.getiter = mp_obj_tuple_getiter,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
.n_fields = 5,
|
||||||
|
.fields = {
|
||||||
|
MP_QSTR_attack_time,
|
||||||
|
MP_QSTR_decay_time,
|
||||||
|
MP_QSTR_release_time,
|
||||||
|
MP_QSTR_attack_level,
|
||||||
|
MP_QSTR_sustain_level,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//| def from_file(
|
||||||
|
//| file: typing.BinaryIO,
|
||||||
|
//| *,
|
||||||
|
//| sample_rate: int = 11025,
|
||||||
|
//| waveform: Optional[ReadableBuffer] = None,
|
||||||
|
//| envelope: Optional[ReadableBuffer] = None,
|
||||||
|
//| ) -> MidiTrack:
|
||||||
//| """Create an AudioSample from an already opened MIDI file.
|
//| """Create an AudioSample from an already opened MIDI file.
|
||||||
//| Currently, only single-track MIDI (type 0) is supported.
|
//| Currently, only single-track MIDI (type 0) is supported.
|
||||||
//|
|
//|
|
||||||
//| :param typing.BinaryIO file: Already opened MIDI file
|
//| :param typing.BinaryIO file: Already opened MIDI file
|
||||||
//| :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 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 Envelope envelope: An object that defines the loudness of a note over time. The default envelope provides no ramping, voices turn instantly on and off.
|
||||||
//|
|
//|
|
||||||
//| Playing a MIDI file from flash::
|
//| Playing a MIDI file from flash::
|
||||||
//|
|
//|
|
||||||
@ -65,11 +161,12 @@
|
|||||||
//| ...
|
//| ...
|
||||||
//|
|
//|
|
||||||
STATIC mp_obj_t synthio_from_file(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
|
STATIC mp_obj_t synthio_from_file(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
|
||||||
enum { ARG_file, ARG_sample_rate, ARG_waveform };
|
enum { ARG_file, ARG_sample_rate, ARG_waveform, ARG_envelope };
|
||||||
static const mp_arg_t allowed_args[] = {
|
static const mp_arg_t allowed_args[] = {
|
||||||
{ MP_QSTR_file, MP_ARG_OBJ | MP_ARG_REQUIRED },
|
{ MP_QSTR_file, MP_ARG_OBJ | MP_ARG_REQUIRED },
|
||||||
{ 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_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_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
|
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
|
||||||
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
|
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
|
||||||
@ -121,7 +218,9 @@ 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, bufinfo_waveform.buf, bufinfo_waveform.len / 2,
|
||||||
|
args[ARG_envelope].u_obj
|
||||||
|
);
|
||||||
|
|
||||||
#if MICROPY_MALLOC_USES_ALLOCATED_SIZE
|
#if MICROPY_MALLOC_USES_ALLOCATED_SIZE
|
||||||
m_free(buffer, track_size);
|
m_free(buffer, track_size);
|
||||||
@ -133,12 +232,12 @@ STATIC mp_obj_t synthio_from_file(size_t n_args, const mp_obj_t *pos_args, mp_ma
|
|||||||
}
|
}
|
||||||
MP_DEFINE_CONST_FUN_OBJ_KW(synthio_from_file_obj, 1, synthio_from_file);
|
MP_DEFINE_CONST_FUN_OBJ_KW(synthio_from_file_obj, 1, synthio_from_file);
|
||||||
|
|
||||||
|
|
||||||
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_MidiTrack), MP_ROM_PTR(&synthio_miditrack_type) },
|
{ MP_ROM_QSTR(MP_QSTR_MidiTrack), MP_ROM_PTR(&synthio_miditrack_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) },
|
||||||
{ MP_ROM_QSTR(MP_QSTR_from_file), MP_ROM_PTR(&synthio_from_file_obj) },
|
{ MP_ROM_QSTR(MP_QSTR_from_file), MP_ROM_PTR(&synthio_from_file_obj) },
|
||||||
|
{ MP_ROM_QSTR(MP_QSTR_Envelope), MP_ROM_PTR(&synthio_envelope_type_obj) },
|
||||||
};
|
};
|
||||||
|
|
||||||
STATIC MP_DEFINE_CONST_DICT(synthio_module_globals, synthio_module_globals_table);
|
STATIC MP_DEFINE_CONST_DICT(synthio_module_globals, synthio_module_globals_table);
|
||||||
|
@ -26,4 +26,10 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "py/objnamedtuple.h"
|
||||||
|
|
||||||
|
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;
|
||||||
|
void synthio_synth_envelope_set(synthio_synth_t *synth, mp_obj_t envelope_obj);
|
||||||
|
mp_obj_t synthio_synth_envelope_get(synthio_synth_t *synth);
|
||||||
|
@ -28,118 +28,109 @@
|
|||||||
#include "shared-bindings/synthio/MidiTrack.h"
|
#include "shared-bindings/synthio/MidiTrack.h"
|
||||||
|
|
||||||
|
|
||||||
STATIC NORETURN void raise_midi_stream_error(uint32_t pos) {
|
STATIC void print_midi_stream_error(synthio_miditrack_obj_t *self) {
|
||||||
mp_raise_ValueError_varg(translate("Error in MIDI stream at position %d"), pos);
|
mp_cprintf(&mp_plat_print, translate("Error in MIDI stream at position %d"), self->pos);
|
||||||
|
self->pos = self->track.len;
|
||||||
}
|
}
|
||||||
|
|
||||||
STATIC uint8_t parse_note(const uint8_t *buffer, uint32_t len, uint32_t *pos) {
|
STATIC uint8_t parse_note(synthio_miditrack_obj_t *self) {
|
||||||
if (*pos + 1 >= len) {
|
uint8_t *buffer = self->track.buf;
|
||||||
raise_midi_stream_error(*pos);
|
size_t len = self->track.len;
|
||||||
|
if (self->pos + 1 >= len) {
|
||||||
|
print_midi_stream_error(self);
|
||||||
}
|
}
|
||||||
uint8_t note = buffer[(*pos)++];
|
uint8_t note = buffer[(self->pos)++];
|
||||||
if (note > 127 || buffer[(*pos)++] > 127) {
|
if (note > 127 || buffer[(self->pos)++] > 127) {
|
||||||
raise_midi_stream_error(*pos);
|
print_midi_stream_error(self);
|
||||||
}
|
}
|
||||||
return note;
|
return note;
|
||||||
}
|
}
|
||||||
|
|
||||||
STATIC void terminate_span(synthio_miditrack_obj_t *self, uint16_t *dur) {
|
static int decode_duration(synthio_miditrack_obj_t *self) {
|
||||||
if (*dur) {
|
uint8_t *buffer = self->track.buf;
|
||||||
self->track[self->total_spans - 1].dur = *dur;
|
size_t len = self->track.len;
|
||||||
*dur = 0;
|
uint8_t c;
|
||||||
} else {
|
uint32_t delta = 0;
|
||||||
self->total_spans--;
|
do {
|
||||||
|
c = buffer[self->pos++];
|
||||||
|
delta <<= 7;
|
||||||
|
delta |= c & 0x7f;
|
||||||
|
} while ((c & 0x80) && (self->pos < len));
|
||||||
|
|
||||||
|
// errors cannot be raised from the background task, so simply end the track.
|
||||||
|
if (c & 0x80) {
|
||||||
|
self->pos = self->track.len;
|
||||||
|
print_midi_stream_error(self);
|
||||||
}
|
}
|
||||||
|
return delta * self->synth.sample_rate / self->tempo;
|
||||||
}
|
}
|
||||||
|
|
||||||
STATIC void add_span(synthio_miditrack_obj_t *self, const synthio_midi_span_t *span) {
|
// invariant: pointing at a MIDI message
|
||||||
self->track = m_renew(synthio_midi_span_t, self->track, self->total_spans, self->total_spans + 1);
|
static void decode_until_pause(synthio_miditrack_obj_t *self) {
|
||||||
self->track[self->total_spans++] = *span;
|
uint8_t *buffer = self->track.buf;
|
||||||
}
|
size_t len = self->track.len;
|
||||||
|
do {
|
||||||
STATIC void change_span_note(synthio_miditrack_obj_t *self, uint8_t old_note, uint8_t new_note, uint16_t *dur) {
|
switch (buffer[self->pos++] >> 4) {
|
||||||
synthio_midi_span_t span = self->track[self->total_spans - 1];
|
|
||||||
if (synthio_span_change_note(&span, old_note, new_note)) {
|
|
||||||
terminate_span(self, dur);
|
|
||||||
add_span(self, &span);
|
|
||||||
*dur = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void common_hal_synthio_miditrack_construct(synthio_miditrack_obj_t *self,
|
|
||||||
const uint8_t *buffer, uint32_t len, uint32_t tempo, uint32_t sample_rate,
|
|
||||||
const int16_t *waveform, uint16_t waveform_length) {
|
|
||||||
|
|
||||||
self->synth.sample_rate = sample_rate;
|
|
||||||
self->track = m_malloc(sizeof(synthio_midi_span_t), false);
|
|
||||||
synthio_span_init(self->track);
|
|
||||||
self->next_span = 0;
|
|
||||||
self->total_spans = 1;
|
|
||||||
self->synth.waveform = waveform;
|
|
||||||
self->synth.waveform_length = waveform_length;
|
|
||||||
|
|
||||||
uint16_t dur = 0;
|
|
||||||
uint32_t pos = 0;
|
|
||||||
while (pos < len) {
|
|
||||||
uint8_t c;
|
|
||||||
uint32_t delta = 0;
|
|
||||||
do {
|
|
||||||
c = buffer[pos++];
|
|
||||||
delta <<= 7;
|
|
||||||
delta |= c & 0x7f;
|
|
||||||
} while ((c & 0x80) && (pos < len));
|
|
||||||
|
|
||||||
if (c & 0x80) {
|
|
||||||
raise_midi_stream_error(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
// dur is carried over here so that if a note on/off message doesn't actually produce a change, the
|
|
||||||
// underlying "span" is extended. Otherwise, it is zeroed out in the call to `terminate_span`.
|
|
||||||
dur += delta * sample_rate / tempo;
|
|
||||||
|
|
||||||
switch (buffer[pos++] >> 4) {
|
|
||||||
case 8: { // Note Off
|
case 8: { // Note Off
|
||||||
uint8_t note = parse_note(buffer, len, &pos);
|
uint8_t note = parse_note(self);
|
||||||
change_span_note(self, note, SYNTHIO_SILENCE, &dur);
|
synthio_span_change_note(&self->synth, note, SYNTHIO_SILENCE);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 9: { // Note On
|
case 9: { // Note On
|
||||||
uint8_t note = parse_note(buffer, len, &pos);
|
uint8_t note = parse_note(self);
|
||||||
change_span_note(self, SYNTHIO_SILENCE, note, &dur);
|
synthio_span_change_note(&self->synth, SYNTHIO_SILENCE, note);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 10:
|
case 10:
|
||||||
case 11:
|
case 11:
|
||||||
case 14: // two data bytes to ignore
|
case 14: // two data bytes to ignore
|
||||||
parse_note(buffer, len, &pos);
|
parse_note(self);
|
||||||
break;
|
break;
|
||||||
case 12:
|
case 12:
|
||||||
case 13: // one data byte to ignore
|
case 13: // one data byte to ignore
|
||||||
if (pos >= len || buffer[pos++] > 127) {
|
if (self->pos >= len || buffer[self->pos++] > 127) {
|
||||||
raise_midi_stream_error(pos);
|
print_midi_stream_error(self);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 15: // the full syntax is too complicated, just assume it's "End of Track" event
|
case 15: // the full syntax is too complicated, just assume it's "End of Track" event
|
||||||
pos = len;
|
self->pos = len;
|
||||||
break;
|
break;
|
||||||
default: // invalid event
|
default: // invalid event
|
||||||
raise_midi_stream_error(pos);
|
print_midi_stream_error(self);
|
||||||
}
|
}
|
||||||
}
|
if (self->pos < len) {
|
||||||
terminate_span(self, &dur);
|
self->synth.span.dur = decode_duration(self);
|
||||||
|
}
|
||||||
|
} while (self->pos < len && self->synth.span.dur == 0);
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t max_dur = 0;
|
STATIC void start_parse(synthio_miditrack_obj_t *self) {
|
||||||
for (int i = 0; i < self->total_spans; i++) {
|
self->pos = 0;
|
||||||
max_dur = MAX(self->track[i].dur, max_dur);
|
self->synth.span.dur = decode_duration(self);
|
||||||
|
if (self->synth.span.dur == 0) {
|
||||||
|
// the usual case: the file starts with some MIDI event, not a delay
|
||||||
|
decode_until_pause(self);
|
||||||
}
|
}
|
||||||
synthio_synth_init(&self->synth, max_dur);
|
}
|
||||||
|
|
||||||
|
void common_hal_synthio_miditrack_construct(synthio_miditrack_obj_t *self,
|
||||||
|
const uint8_t *buffer, uint32_t len, uint32_t tempo, uint32_t sample_rate,
|
||||||
|
const int16_t *waveform, uint16_t waveform_length,
|
||||||
|
mp_obj_t envelope) {
|
||||||
|
|
||||||
|
self->tempo = tempo;
|
||||||
|
self->track.buf = (void *)buffer;
|
||||||
|
self->track.len = len;
|
||||||
|
|
||||||
|
synthio_synth_init(&self->synth, sample_rate, waveform, waveform_length, envelope);
|
||||||
|
|
||||||
|
start_parse(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
void common_hal_synthio_miditrack_deinit(synthio_miditrack_obj_t *self) {
|
void common_hal_synthio_miditrack_deinit(synthio_miditrack_obj_t *self) {
|
||||||
synthio_synth_deinit(&self->synth);
|
synthio_synth_deinit(&self->synth);
|
||||||
m_del(synthio_midi_span_t, self->track, self->total_spans + 1);
|
|
||||||
self->track = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool common_hal_synthio_miditrack_deinited(synthio_miditrack_obj_t *self) {
|
bool common_hal_synthio_miditrack_deinited(synthio_miditrack_obj_t *self) {
|
||||||
return synthio_synth_deinited(&self->synth);
|
return synthio_synth_deinited(&self->synth);
|
||||||
}
|
}
|
||||||
@ -157,25 +148,21 @@ uint8_t common_hal_synthio_miditrack_get_channel_count(synthio_miditrack_obj_t *
|
|||||||
void synthio_miditrack_reset_buffer(synthio_miditrack_obj_t *self,
|
void synthio_miditrack_reset_buffer(synthio_miditrack_obj_t *self,
|
||||||
bool single_channel_output, uint8_t channel) {
|
bool single_channel_output, uint8_t channel) {
|
||||||
synthio_synth_reset_buffer(&self->synth, single_channel_output, channel);
|
synthio_synth_reset_buffer(&self->synth, single_channel_output, channel);
|
||||||
self->synth.span.dur = 0;
|
start_parse(self);
|
||||||
self->next_span = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
audioio_get_buffer_result_t synthio_miditrack_get_buffer(synthio_miditrack_obj_t *self,
|
audioio_get_buffer_result_t synthio_miditrack_get_buffer(synthio_miditrack_obj_t *self,
|
||||||
bool single_channel_output, uint8_t channel, uint8_t **buffer, uint32_t *buffer_length) {
|
bool single_channel_output, uint8_t channel, uint8_t **buffer, uint32_t *buffer_length) {
|
||||||
|
|
||||||
if (self->synth.span.dur == 0) {
|
|
||||||
if (self->next_span >= self->total_spans) {
|
|
||||||
*buffer_length = 0;
|
|
||||||
return GET_BUFFER_DONE;
|
|
||||||
}
|
|
||||||
self->synth.span = self->track[self->next_span++];
|
|
||||||
}
|
|
||||||
|
|
||||||
synthio_synth_synthesize(&self->synth, buffer, buffer_length, single_channel_output ? 0 : channel);
|
synthio_synth_synthesize(&self->synth, buffer, buffer_length, single_channel_output ? 0 : channel);
|
||||||
|
if (self->synth.span.dur == 0) {
|
||||||
return (self->synth.span.dur == 0 && self->next_span >= self->total_spans) ?
|
if (self->pos == self->track.len) {
|
||||||
GET_BUFFER_DONE : GET_BUFFER_MORE_DATA;
|
return GET_BUFFER_DONE;
|
||||||
|
} else {
|
||||||
|
decode_until_pause(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return GET_BUFFER_MORE_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
void synthio_miditrack_get_buffer_structure(synthio_miditrack_obj_t *self, bool single_channel_output,
|
void synthio_miditrack_get_buffer_structure(synthio_miditrack_obj_t *self, bool single_channel_output,
|
||||||
|
@ -34,9 +34,10 @@
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
mp_obj_base_t base;
|
mp_obj_base_t base;
|
||||||
synthio_synth_t synth;
|
synthio_synth_t synth;
|
||||||
uint16_t next_span;
|
mp_buffer_info_t track;
|
||||||
uint16_t total_spans;
|
// invariant: after initial startup, pos always points just after an encoded duration, i.e., at a midi message (or at EOF)
|
||||||
synthio_midi_span_t *track;
|
size_t pos;
|
||||||
|
uint32_t tempo;
|
||||||
} synthio_miditrack_obj_t;
|
} synthio_miditrack_obj_t;
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,13 +30,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, const int16_t *waveform, uint16_t waveform_length,
|
||||||
|
mp_obj_t envelope) {
|
||||||
|
|
||||||
self->synth.sample_rate = sample_rate;
|
synthio_synth_init(&self->synth, sample_rate, waveform, waveform_length, envelope);
|
||||||
self->synth.waveform = waveform;
|
|
||||||
self->synth.waveform_length = waveform_length;
|
|
||||||
synthio_synth_init(&self->synth, SYNTHIO_MAX_DUR);
|
|
||||||
common_hal_synthio_synthesizer_release_all(self);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void common_hal_synthio_synthesizer_deinit(synthio_synthesizer_obj_t *self) {
|
void common_hal_synthio_synthesizer_deinit(synthio_synthesizer_obj_t *self) {
|
||||||
@ -74,14 +71,18 @@ void synthio_synthesizer_get_buffer_structure(synthio_synthesizer_obj_t *self, b
|
|||||||
}
|
}
|
||||||
|
|
||||||
void common_hal_synthio_synthesizer_release_all(synthio_synthesizer_obj_t *self) {
|
void common_hal_synthio_synthesizer_release_all(synthio_synthesizer_obj_t *self) {
|
||||||
synthio_span_init(&self->synth.span);
|
for (size_t i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) {
|
||||||
|
if (self->synth.span.note[i] != SYNTHIO_SILENCE) {
|
||||||
|
synthio_span_change_note(&self->synth, self->synth.span.note[i], SYNTHIO_SILENCE);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void common_hal_synthio_synthesizer_release(synthio_synthesizer_obj_t *self, mp_obj_t to_release) {
|
void common_hal_synthio_synthesizer_release(synthio_synthesizer_obj_t *self, mp_obj_t to_release) {
|
||||||
mp_obj_iter_buf_t iter_buf;
|
mp_obj_iter_buf_t iter_buf;
|
||||||
mp_obj_t iterable = mp_getiter(to_release, &iter_buf);
|
mp_obj_t iterable = mp_getiter(to_release, &iter_buf);
|
||||||
mp_obj_t item;
|
mp_obj_t item;
|
||||||
while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
|
while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
|
||||||
synthio_span_change_note(&self->synth.span, mp_arg_validate_int_range(mp_obj_get_int(item), 0, 127, MP_QSTR_note), SYNTHIO_SILENCE);
|
synthio_span_change_note(&self->synth, mp_arg_validate_int_range(mp_obj_get_int(item), 0, 127, MP_QSTR_note), SYNTHIO_SILENCE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,15 +91,21 @@ void common_hal_synthio_synthesizer_press(synthio_synthesizer_obj_t *self, mp_ob
|
|||||||
mp_obj_t iterable = mp_getiter(to_press, &iter_buf);
|
mp_obj_t iterable = mp_getiter(to_press, &iter_buf);
|
||||||
mp_obj_t item;
|
mp_obj_t item;
|
||||||
while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
|
while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
|
||||||
synthio_span_change_note(&self->synth.span, SYNTHIO_SILENCE, mp_arg_validate_int_range(mp_obj_get_int(item), 0, 127, MP_QSTR_note));
|
synthio_span_change_note(&self->synth, SYNTHIO_SILENCE, mp_arg_validate_int_range(mp_obj_get_int(item), 0, 127, MP_QSTR_note));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mp_obj_t common_hal_synthio_synthesizer_get_pressed_notes(synthio_synthesizer_obj_t *self) {
|
mp_obj_t common_hal_synthio_synthesizer_get_pressed_notes(synthio_synthesizer_obj_t *self) {
|
||||||
mp_obj_tuple_t *result = MP_OBJ_TO_PTR(mp_obj_new_tuple(synthio_span_count_active_channels(&self->synth.span), NULL));
|
int count = 0;
|
||||||
for (size_t i = 0, j = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) {
|
for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
|
||||||
if (self->synth.span.note[i] != SYNTHIO_SILENCE) {
|
if (self->synth.span.note[chan] != SYNTHIO_SILENCE && self->synth.envelope_state[chan].state != SYNTHIO_ENVELOPE_STATE_RELEASE) {
|
||||||
result->items[j++] = MP_OBJ_NEW_SMALL_INT(self->synth.span.note[i]);
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mp_obj_tuple_t *result = MP_OBJ_TO_PTR(mp_obj_new_tuple(count, NULL));
|
||||||
|
for (size_t chan = 0, j = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
|
||||||
|
if (self->synth.span.note[chan] != SYNTHIO_SILENCE && self->synth.envelope_state[chan].state != SYNTHIO_ENVELOPE_STATE_RELEASE) {
|
||||||
|
result->items[j++] = MP_OBJ_NEW_SMALL_INT(self->synth.span.note[chan]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return MP_OBJ_FROM_PTR(result);
|
return MP_OBJ_FROM_PTR(result);
|
||||||
|
@ -26,18 +26,117 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "shared-module/synthio/__init__.h"
|
#include "shared-module/synthio/__init__.h"
|
||||||
|
#include "shared-bindings/synthio/__init__.h"
|
||||||
#include "py/runtime.h"
|
#include "py/runtime.h"
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
STATIC const int16_t square_wave[] = {-32768, 32767};
|
STATIC const int16_t square_wave[] = {-32768, 0};
|
||||||
|
|
||||||
STATIC const uint16_t notes[] = {8372, 8870, 9397, 9956, 10548, 11175, 11840,
|
STATIC const uint16_t notes[] = {8372, 8870, 9397, 9956, 10548, 11175, 11840,
|
||||||
12544, 13290, 14080, 14917, 15804}; // 9th octave
|
12544, 13290, 14080, 14917, 15804}; // 9th octave
|
||||||
|
|
||||||
int synthio_span_count_active_channels(synthio_midi_span_t *span) {
|
STATIC int16_t convert_time_to_rate(uint32_t sample_rate, mp_obj_t time_in, int16_t difference) {
|
||||||
int result = 0;
|
mp_float_t time = mp_obj_get_float(time_in);
|
||||||
for (int i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) {
|
int num_samples = (int)MICROPY_FLOAT_C_FUN(round)(time * sample_rate);
|
||||||
if (span->note[i] != SYNTHIO_SILENCE) {
|
if (num_samples == 0) {
|
||||||
result += 1;
|
return 0;
|
||||||
|
}
|
||||||
|
int16_t result = MIN(32767, MAX(1, abs(difference * SYNTHIO_MAX_DUR) / num_samples));
|
||||||
|
return (difference < 0) ? -result : result;
|
||||||
|
}
|
||||||
|
|
||||||
|
STATIC void synthio_envelope_definition_set(synthio_envelope_definition_t *envelope, mp_obj_t obj, uint32_t sample_rate) {
|
||||||
|
if (obj == mp_const_none) {
|
||||||
|
envelope->attack_level = 32767;
|
||||||
|
envelope->sustain_level = 32767;
|
||||||
|
envelope->attack_step = 32767;
|
||||||
|
envelope->decay_step = -32767;
|
||||||
|
envelope->release_step = -32767;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mp_arg_validate_type(obj, (mp_obj_type_t *)&synthio_envelope_type_obj, MP_QSTR_envelope);
|
||||||
|
|
||||||
|
size_t len;
|
||||||
|
mp_obj_t *fields;
|
||||||
|
mp_obj_tuple_get(obj, &len, &fields);
|
||||||
|
|
||||||
|
envelope->attack_level = (int)(32767 * mp_obj_get_float(fields[3]));
|
||||||
|
envelope->sustain_level = (int)(32767 * mp_obj_get_float(fields[4]));
|
||||||
|
|
||||||
|
envelope->attack_step = convert_time_to_rate(
|
||||||
|
sample_rate, fields[0], envelope->attack_level);
|
||||||
|
|
||||||
|
envelope->decay_step = -convert_time_to_rate(
|
||||||
|
sample_rate, fields[1], envelope->attack_level - envelope->sustain_level);
|
||||||
|
|
||||||
|
envelope->release_step = -convert_time_to_rate(
|
||||||
|
sample_rate, fields[2],
|
||||||
|
envelope->decay_step
|
||||||
|
? envelope->sustain_level
|
||||||
|
: envelope->attack_level);
|
||||||
|
}
|
||||||
|
|
||||||
|
STATIC void synthio_envelope_state_step(synthio_envelope_state_t *state, synthio_envelope_definition_t *def, size_t n_steps) {
|
||||||
|
state->substep += n_steps;
|
||||||
|
while (state->substep >= SYNTHIO_MAX_DUR) {
|
||||||
|
// max n_steps should be SYNTHIO_MAX_DUR so this loop executes at most
|
||||||
|
// once
|
||||||
|
state->substep -= SYNTHIO_MAX_DUR;
|
||||||
|
switch (state->state) {
|
||||||
|
case SYNTHIO_ENVELOPE_STATE_SUSTAIN:
|
||||||
|
break;
|
||||||
|
case SYNTHIO_ENVELOPE_STATE_ATTACK:
|
||||||
|
if (def->attack_step != 0) {
|
||||||
|
state->level = MIN(state->level + def->attack_step, def->attack_level);
|
||||||
|
if (state->level == def->attack_level) {
|
||||||
|
state->state = SYNTHIO_ENVELOPE_STATE_DECAY;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
state->state = SYNTHIO_ENVELOPE_STATE_DECAY;
|
||||||
|
MP_FALLTHROUGH;
|
||||||
|
case SYNTHIO_ENVELOPE_STATE_DECAY:
|
||||||
|
if (def->decay_step != 0) {
|
||||||
|
state->level = MAX(state->level + def->decay_step, def->sustain_level);
|
||||||
|
assert(state->level >= 0);
|
||||||
|
if (state->level == def->sustain_level) {
|
||||||
|
state->state = SYNTHIO_ENVELOPE_STATE_SUSTAIN;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
state->state = SYNTHIO_ENVELOPE_STATE_RELEASE;
|
||||||
|
MP_FALLTHROUGH;
|
||||||
|
case SYNTHIO_ENVELOPE_STATE_RELEASE:
|
||||||
|
if (def->release_step != 0) {
|
||||||
|
int delta = def->release_step;
|
||||||
|
state->level = MAX(state->level + delta, 0);
|
||||||
|
} else {
|
||||||
|
state->level = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
STATIC void synthio_envelope_state_init(synthio_envelope_state_t *state, synthio_envelope_definition_t *def) {
|
||||||
|
state->level = 0;
|
||||||
|
state->substep = 0;
|
||||||
|
state->state = SYNTHIO_ENVELOPE_STATE_ATTACK;
|
||||||
|
|
||||||
|
synthio_envelope_state_step(state, def, SYNTHIO_MAX_DUR);
|
||||||
|
}
|
||||||
|
|
||||||
|
STATIC void synthio_envelope_state_release(synthio_envelope_state_t *state, synthio_envelope_definition_t *def) {
|
||||||
|
state->state = SYNTHIO_ENVELOPE_STATE_RELEASE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
STATIC uint32_t synthio_synth_sum_envelope(synthio_synth_t *synth) {
|
||||||
|
uint32_t result = 0;
|
||||||
|
for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
|
||||||
|
if (synth->span.note[chan] != SYNTHIO_SILENCE) {
|
||||||
|
result += synth->envelope_state[chan].level;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@ -62,16 +161,23 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
|
|||||||
memset(out_buffer, 0, synth->buffer_length);
|
memset(out_buffer, 0, synth->buffer_length);
|
||||||
|
|
||||||
int32_t sample_rate = synth->sample_rate;
|
int32_t sample_rate = synth->sample_rate;
|
||||||
int active_channels = synthio_span_count_active_channels(&synth->span);
|
uint32_t total_envelope = synthio_synth_sum_envelope(synth);
|
||||||
const int16_t *waveform = synth->waveform;
|
const int16_t *waveform = synth->waveform;
|
||||||
uint32_t waveform_length = synth->waveform_length;
|
uint32_t waveform_length = synth->waveform_length;
|
||||||
if (active_channels) {
|
if (total_envelope > 0) {
|
||||||
int16_t loudness = 0xffff / (1 + 2 * active_channels);
|
uint16_t ovl_loudness = 0x7fffffff / MAX(0x8000, total_envelope);
|
||||||
for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
|
for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
|
||||||
if (synth->span.note[chan] == SYNTHIO_SILENCE) {
|
if (synth->span.note[chan] == SYNTHIO_SILENCE) {
|
||||||
synth->accum[chan] = 0;
|
synth->accum[chan] = 0;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// adjust loudness by envelope
|
||||||
|
uint16_t loudness = (ovl_loudness * synth->envelope_state[chan].level) >> 16;
|
||||||
|
|
||||||
|
if (synth->envelope_state[chan].level == 0) {
|
||||||
|
// note is truly finished
|
||||||
|
synth->span.note[chan] = SYNTHIO_SILENCE;
|
||||||
|
}
|
||||||
uint8_t octave = synth->span.note[chan] / 12;
|
uint8_t octave = synth->span.note[chan] / 12;
|
||||||
uint16_t base_freq = notes[synth->span.note[chan] % 12];
|
uint16_t base_freq = notes[synth->span.note[chan] % 12];
|
||||||
uint32_t accum = synth->accum[chan];
|
uint32_t accum = synth->accum[chan];
|
||||||
@ -95,6 +201,11 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// advance envelope states
|
||||||
|
for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
|
||||||
|
synthio_envelope_state_step(&synth->envelope_state[chan], &synth->envelope_definition, dur);
|
||||||
|
}
|
||||||
|
|
||||||
*buffer_length = synth->last_buffer_length = dur * SYNTHIO_BYTES_PER_SAMPLE;
|
*buffer_length = synth->last_buffer_length = dur * SYNTHIO_BYTES_PER_SAMPLE;
|
||||||
*bufptr = (uint8_t *)out_buffer;
|
*bufptr = (uint8_t *)out_buffer;
|
||||||
}
|
}
|
||||||
@ -117,11 +228,28 @@ void synthio_synth_deinit(synthio_synth_t *synth) {
|
|||||||
synth->buffers[1] = NULL;
|
synth->buffers[1] = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void synthio_synth_init(synthio_synth_t *synth, uint16_t max_dur) {
|
void synthio_synth_envelope_set(synthio_synth_t *synth, mp_obj_t envelope_obj) {
|
||||||
synth->buffer_length = MIN(SYNTHIO_MAX_DUR, max_dur) * SYNTHIO_BYTES_PER_SAMPLE;
|
synthio_envelope_definition_set(&synth->envelope_definition, envelope_obj, synth->sample_rate);
|
||||||
|
synth->envelope_obj = envelope_obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
mp_obj_t synthio_synth_envelope_get(synthio_synth_t *synth) {
|
||||||
|
return synth->envelope_obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
void synthio_synth_init(synthio_synth_t *synth, uint32_t sample_rate, const int16_t *waveform, uint16_t waveform_length, mp_obj_t envelope_obj) {
|
||||||
|
synth->buffer_length = SYNTHIO_MAX_DUR * SYNTHIO_BYTES_PER_SAMPLE;
|
||||||
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);
|
||||||
synth->other_channel = -1;
|
synth->other_channel = -1;
|
||||||
|
synth->waveform = waveform;
|
||||||
|
synth->waveform_length = waveform_length;
|
||||||
|
synth->sample_rate = sample_rate;
|
||||||
|
synthio_synth_envelope_set(synth, envelope_obj);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) {
|
||||||
|
synth->span.note[i] = SYNTHIO_SILENCE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
@ -132,40 +260,57 @@ void synthio_synth_get_buffer_structure(synthio_synth_t *synth, bool single_chan
|
|||||||
*spacing = 1;
|
*spacing = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool parse_common(mp_buffer_info_t *bufinfo, mp_obj_t o, int16_t what) {
|
||||||
|
if (o != mp_const_none) {
|
||||||
|
mp_get_buffer_raise(o, bufinfo, MP_BUFFER_READ);
|
||||||
|
if (bufinfo->typecode != 'h') {
|
||||||
|
mp_raise_ValueError_varg(translate("%q must be array of type 'h'"), what);
|
||||||
|
}
|
||||||
|
mp_arg_validate_length_range(bufinfo->len / 2, 2, 1024, what);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
if (waveform_obj != mp_const_none) {
|
STATIC int find_channel_with_note(synthio_synth_t *synth, uint8_t note) {
|
||||||
mp_get_buffer_raise(waveform_obj, bufinfo_waveform, MP_BUFFER_READ);
|
for (int i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) {
|
||||||
if (bufinfo_waveform->typecode != 'h') {
|
if (synth->span.note[i] == note) {
|
||||||
mp_raise_ValueError_varg(translate("%q must be array of type 'h'"), MP_QSTR_waveform);
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mp_arg_validate_length_range(bufinfo_waveform->len / 2, 2, 1024, MP_QSTR_waveform);
|
if (note == SYNTHIO_SILENCE) {
|
||||||
}
|
// we need a victim note that is releasing. simple algorithm: lowest numbered slot
|
||||||
|
for (int i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) {
|
||||||
void synthio_span_init(synthio_midi_span_t *span) {
|
if (SYNTHIO_VOICE_IS_RELEASING(synth, i)) {
|
||||||
span->dur = 0;
|
return i;
|
||||||
for (size_t i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) { span->note[i] = SYNTHIO_SILENCE;
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
STATIC int find_channel_with_note(const synthio_midi_span_t *span, uint8_t note) {
|
|
||||||
for (int i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) {
|
|
||||||
if (span->note[i] == note) {
|
|
||||||
return i;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool synthio_span_change_note(synthio_midi_span_t *span, uint8_t old_note, uint8_t new_note) {
|
bool synthio_span_change_note(synthio_synth_t *synth, uint8_t old_note, uint8_t new_note) {
|
||||||
if (new_note != SYNTHIO_SILENCE && find_channel_with_note(span, new_note) != -1) {
|
int channel;
|
||||||
return false; // note already pressed, do nothing
|
if (new_note != SYNTHIO_SILENCE && (channel = find_channel_with_note(synth, new_note)) != -1) {
|
||||||
|
// note already playing, re-strike
|
||||||
|
synthio_envelope_state_init(&synth->envelope_state[channel], &synth->envelope_definition);
|
||||||
|
synth->accum[channel] = 0;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
int channel = find_channel_with_note(span, old_note);
|
channel = find_channel_with_note(synth, old_note);
|
||||||
if (channel != -1) {
|
if (channel != -1) {
|
||||||
span->note[channel] = new_note;
|
if (new_note == SYNTHIO_SILENCE) {
|
||||||
|
synthio_envelope_state_release(&synth->envelope_state[channel], &synth->envelope_definition);
|
||||||
|
} else {
|
||||||
|
synth->span.note[channel] = new_note;
|
||||||
|
synthio_envelope_state_init(&synth->envelope_state[channel], &synth->envelope_definition);
|
||||||
|
synth->accum[channel] = 0;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
#define SYNTHIO_BYTES_PER_SAMPLE (SYNTHIO_BITS_PER_SAMPLE / 8)
|
#define SYNTHIO_BYTES_PER_SAMPLE (SYNTHIO_BITS_PER_SAMPLE / 8)
|
||||||
#define SYNTHIO_MAX_DUR (256)
|
#define SYNTHIO_MAX_DUR (256)
|
||||||
#define SYNTHIO_SILENCE (0x80)
|
#define SYNTHIO_SILENCE (0x80)
|
||||||
|
#define SYNTHIO_VOICE_IS_RELEASING(synth, i) (synth->envelope_state[i].state == SYNTHIO_ENVELOPE_STATE_RELEASE)
|
||||||
|
|
||||||
#include "shared-module/audiocore/__init__.h"
|
#include "shared-module/audiocore/__init__.h"
|
||||||
|
|
||||||
@ -39,6 +40,25 @@ typedef struct {
|
|||||||
} synthio_midi_span_t;
|
} synthio_midi_span_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
// the number of attack or decay steps (signed) per sample
|
||||||
|
// therefore the maximum time is 32767 samples or 0.68s at 48kHz
|
||||||
|
// provided the level is maximum (this should be increased!)
|
||||||
|
int16_t attack_step, decay_step, release_step;
|
||||||
|
uint16_t attack_level, sustain_level;
|
||||||
|
} synthio_envelope_definition_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
SYNTHIO_ENVELOPE_STATE_ATTACK, SYNTHIO_ENVELOPE_STATE_DECAY,
|
||||||
|
SYNTHIO_ENVELOPE_STATE_SUSTAIN, SYNTHIO_ENVELOPE_STATE_RELEASE
|
||||||
|
} envelope_state_e;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int16_t level;
|
||||||
|
uint16_t substep;
|
||||||
|
envelope_state_e state;
|
||||||
|
} synthio_envelope_state_t;
|
||||||
|
|
||||||
|
typedef struct synthio_synth {
|
||||||
uint32_t sample_rate;
|
uint32_t sample_rate;
|
||||||
int16_t *buffers[2];
|
int16_t *buffers[2];
|
||||||
const int16_t *waveform;
|
const int16_t *waveform;
|
||||||
@ -46,19 +66,24 @@ typedef struct {
|
|||||||
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;
|
uint16_t waveform_length;
|
||||||
|
synthio_envelope_definition_t envelope_definition;
|
||||||
|
mp_obj_t 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];
|
||||||
|
synthio_envelope_state_t envelope_state[CIRCUITPY_SYNTHIO_MAX_CHANNELS];
|
||||||
} synthio_synth_t;
|
} synthio_synth_t;
|
||||||
|
|
||||||
void synthio_span_init(synthio_midi_span_t *span);
|
|
||||||
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, uint16_t max_dur);
|
void synthio_synth_init(synthio_synth_t *synth, uint32_t sample_rate, const int16_t *waveform, uint16_t waveform_length,
|
||||||
|
mp_obj_t envelope);
|
||||||
void synthio_synth_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_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_midi_span_t *span, uint8_t old_note, uint8_t new_note);
|
bool synthio_span_change_note(synthio_synth_t *synth, uint8_t old_note, uint8_t new_note);
|
||||||
int synthio_span_count_active_channels(synthio_midi_span_t *span);
|
|
||||||
|
void synthio_envelope_step(synthio_envelope_definition_t *definition, synthio_envelope_state_t *state, int n_samples);
|
||||||
|
@ -11,10 +11,12 @@ SCORE = b"\0\x90@\0\x20\x90b\0\x20\x80@\0\0\x80\b\0"
|
|||||||
|
|
||||||
with MidiTrack(SCORE, sample_rate=8000, tempo=640) as m:
|
with MidiTrack(SCORE, sample_rate=8000, tempo=640) as m:
|
||||||
print(get_structure(m))
|
print(get_structure(m))
|
||||||
print(get_buffer(m))
|
p, q = get_buffer(m)
|
||||||
|
print(p, list(q))
|
||||||
|
|
||||||
with MidiTrack(
|
with MidiTrack(
|
||||||
SCORE, sample_rate=8000, tempo=640, waveform=array.array("h", [0, 32767, 0, -32768])
|
SCORE, sample_rate=8000, tempo=640, waveform=array.array("h", [0, 32767, 0, -32768])
|
||||||
) as m:
|
) as m:
|
||||||
print(get_structure(m))
|
print(get_structure(m))
|
||||||
print(get_buffer(m))
|
p, q = get_buffer(m)
|
||||||
|
print(p, list(q))
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
(0, 1, 512, 1)
|
(0, 1, 512, 1)
|
||||||
(1, b'V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5\xaa*\xaa*')
|
1 [-16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0]
|
||||||
(0, 1, 512, 1)
|
(0, 1, 512, 1)
|
||||||
(1, b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00V\xd5V\xd5V\xd5V\xd5V\xd5V\xd5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa*\xaa*\xaa*\xaa*\xaa*\xaa*\x00\x00\x00\x00')
|
1 [0, 0, 0, 0, 0, 0, 16382, 16382, 16382, 16382, 16382, 16382, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16382, 16382, 16382, 16382, 16382, 16382, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16382, 16382, 16382, 16382, 16382, 16382, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16382, 16382, 16382, 16382, 16382, 16382, 0, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16382, 16382, 16382, 16382, 16382, 16382, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16382, 16382, 16382, 16382, 16382, 16382, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16382, 16382, 16382, 16382, 16382, 16382, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16382, 16382, 16382, 16382, 16382, 16382, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16382, 16382, 16382, 16382, 16382, 16382, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16382, 16382, 16382, 16382, 16382, 16382, 0, 0]
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import struct
|
import struct
|
||||||
import synthio
|
import synthio
|
||||||
import audiocore
|
import audiocore
|
||||||
|
import ulab.numpy as np
|
||||||
|
|
||||||
|
|
||||||
def dump_samples():
|
def dump_samples():
|
||||||
print(struct.unpack("12h", audiocore.get_buffer(s)[1][:24]))
|
print([i for i in audiocore.get_buffer(s)[1][:24]])
|
||||||
|
|
||||||
|
|
||||||
s = synthio.Synthesizer(sample_rate=8000)
|
s = synthio.Synthesizer(sample_rate=8000)
|
||||||
@ -22,3 +23,16 @@ dump_samples()
|
|||||||
s.release_then_press((80,))
|
s.release_then_press((80,))
|
||||||
print(s.pressed)
|
print(s.pressed)
|
||||||
dump_samples()
|
dump_samples()
|
||||||
|
|
||||||
|
envelope = synthio.Envelope(
|
||||||
|
attack_time=0.1, decay_time=0.05, release_time=0.2, attack_level=1, sustain_level=0.8
|
||||||
|
)
|
||||||
|
s = synthio.Synthesizer(sample_rate=8000, envelope=envelope)
|
||||||
|
s.press((60,))
|
||||||
|
for _ in range(12):
|
||||||
|
buf = audiocore.get_buffer(s)[1]
|
||||||
|
print((min(buf), max(buf)))
|
||||||
|
s.release_all()
|
||||||
|
for _ in range(12):
|
||||||
|
buf = audiocore.get_buffer(s)[1]
|
||||||
|
print((min(buf), max(buf)))
|
||||||
|
@ -1,8 +1,32 @@
|
|||||||
()
|
()
|
||||||
(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]
|
||||||
(80,)
|
(80,)
|
||||||
(-10922, -10922, -10922, -10922, 10922, 10922, 10922, 10922, 10922, -10922, -10922, -10922)
|
[-16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383]
|
||||||
(80, 91)
|
(80, 91)
|
||||||
(0, 0, 13106, 13106, 0, -13106, -13106, 0, 13106, 13106, 0, 0)
|
[-8191, -8191, 0, 0, -8191, -16382, -16382, -8191, 0, 0, -8191, -8191, 0, -8191, -8191, -16382, -16382, -8191, 0, 0, -8191, -8191, 0, -8191]
|
||||||
(91,)
|
(91,)
|
||||||
(-10922, 10922, 10922, 10922, -10922, -10922, 10922, 10922, 10922, -10922, -10922, 10922)
|
[-16382, -8191, -8191, 0, -8191, -8191, 0, 0, -8191, -16382, -16382, -8191, 0, 0, -8191, -8191, 0, -8191, -8191, -16382, -16382, -16382, 0, 0]
|
||||||
|
(-5242, 0)
|
||||||
|
(-10484, 0)
|
||||||
|
(-15727, 0)
|
||||||
|
(-16383, 0)
|
||||||
|
(-14286, 0)
|
||||||
|
(-13106, 0)
|
||||||
|
(-13106, 0)
|
||||||
|
(-13106, 0)
|
||||||
|
(-13106, 0)
|
||||||
|
(-13106, 0)
|
||||||
|
(-13106, 0)
|
||||||
|
(-13106, 0)
|
||||||
|
(-13106, 0)
|
||||||
|
(-11009, 0)
|
||||||
|
(-8912, 0)
|
||||||
|
(-6815, 0)
|
||||||
|
(-4718, 0)
|
||||||
|
(-2621, 0)
|
||||||
|
(-524, 0)
|
||||||
|
(0, 0)
|
||||||
|
(0, 0)
|
||||||
|
(0, 0)
|
||||||
|
(0, 0)
|
||||||
|
(0, 0)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user