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:
Jeff Epler 2023-04-12 20:26:46 -05:00
parent 375a9cd4c5
commit 12c1a72f03
No known key found for this signature in database
GPG Key ID: D5BF15AB975AB4DE
16 changed files with 535 additions and 176 deletions

View File

@ -27,6 +27,7 @@
#include <stdint.h>
#include "py/obj.h"
#include "py/gc.h"
#include "py/runtime.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};
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
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);

View File

@ -44,7 +44,8 @@
//| tempo: int,
//| *,
//| sample_rate: int = 11025,
//| waveform: ReadableBuffer = None
//| waveform: Optional[ReadableBuffer] = None,
//| envelope: Optional[Envelope] = None,
//| ) -> None:
//| """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
@ -54,6 +55,7 @@
//| :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 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::
//|
@ -72,12 +74,13 @@
//| 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) {
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[] = {
{ MP_QSTR_buffer, MP_ARG_OBJ | 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_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_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_sample_rate].u_int,
bufinfo_waveform.buf,
bufinfo_waveform.len / 2);
bufinfo_waveform.len / 2,
args[ARG_envelope].u_obj
);
return MP_OBJ_FROM_PTR(self);
}

View File

@ -24,20 +24,18 @@
* THE SOFTWARE.
*/
#ifndef MICROPY_INCLUDED_SHARED_BINDINGS_SYNTHIO_MIDITRACK_H
#define MICROPY_INCLUDED_SHARED_BINDINGS_SYNTHIO_MIDITRACK_H
#pragma once
#include "shared-module/synthio/MidiTrack.h"
extern const mp_obj_type_t synthio_miditrack_type;
void common_hal_synthio_miditrack_construct(synthio_miditrack_obj_t *self,
const uint8_t *buffer, uint32_t len, uint32_t tempo, uint32_t sample_rate, const int16_t *waveform, uint16_t waveform_len);
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);
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);
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);
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_SYNTHIO_MIDITRACK_H

View File

@ -37,24 +37,29 @@
#include "supervisor/shared/translate/translate.h"
//| 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.
//|
//| 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.
//|
//| :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) {
enum { ARG_sample_rate, ARG_waveform };
enum { ARG_sample_rate, ARG_waveform, ARG_envelope };
static const mp_arg_t allowed_args[] = {
{ 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_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_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,
args[ARG_sample_rate].u_int,
bufinfo_waveform.buf,
bufinfo_waveform.len / 2);
bufinfo_waveform.len / 2,
args[ARG_envelope].u_obj);
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;
}
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
//| """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) {
@ -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) },
// 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_max_polyphony), MP_ROM_INT(CIRCUITPY_SYNTHIO_MAX_CHANNELS) },
{ MP_ROM_QSTR(MP_QSTR_pressed), MP_ROM_PTR(&synthio_synthesizer_pressed_obj) },

View File

@ -32,8 +32,8 @@
extern const mp_obj_type_t synthio_synthesizer_type;
void common_hal_synthio_synthesizer_construct(synthio_synthesizer_obj_t *self,
uint32_t sample_rate, const int16_t *waveform, uint16_t waveform_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);
bool common_hal_synthio_synthesizer_deinited(synthio_synthesizer_obj_t *self);
uint32_t common_hal_synthio_synthesizer_get_sample_rate(synthio_synthesizer_obj_t *self);

View File

@ -28,6 +28,7 @@
#include "py/mperrno.h"
#include "py/obj.h"
#include "py/objnamedtuple.h"
#include "py/runtime.h"
#include "extmod/vfs_fat.h"
#include "extmod/vfs_posix.h"
@ -36,16 +37,111 @@
#include "shared-bindings/synthio/MidiTrack.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.
//| Currently, only single-track MIDI (type 0) is supported.
//|
//| :param typing.BinaryIO file: Already opened MIDI file
//| :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 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::
//|
@ -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) {
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[] = {
{ 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_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_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;
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
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);
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_MidiTrack), MP_ROM_PTR(&synthio_miditrack_type) },
{ MP_ROM_QSTR(MP_QSTR_Synthesizer), MP_ROM_PTR(&synthio_synthesizer_type) },
{ MP_ROM_QSTR(MP_QSTR_from_file), MP_ROM_PTR(&synthio_from_file_obj) },
{ MP_ROM_QSTR(MP_QSTR_Envelope), MP_ROM_PTR(&synthio_envelope_type_obj) },
};
STATIC MP_DEFINE_CONST_DICT(synthio_module_globals, synthio_module_globals_table);

View File

@ -26,4 +26,10 @@
#pragma once
#include "py/objnamedtuple.h"
typedef struct synthio_synth synthio_synth_t;
extern int16_t shared_bindings_synthio_square_wave[];
extern const mp_obj_namedtuple_type_t synthio_envelope_type_obj;
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);

View File

@ -28,118 +28,109 @@
#include "shared-bindings/synthio/MidiTrack.h"
STATIC NORETURN void raise_midi_stream_error(uint32_t pos) {
mp_raise_ValueError_varg(translate("Error in MIDI stream at position %d"), pos);
STATIC void print_midi_stream_error(synthio_miditrack_obj_t *self) {
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) {
if (*pos + 1 >= len) {
raise_midi_stream_error(*pos);
STATIC uint8_t parse_note(synthio_miditrack_obj_t *self) {
uint8_t *buffer = self->track.buf;
size_t len = self->track.len;
if (self->pos + 1 >= len) {
print_midi_stream_error(self);
}
uint8_t note = buffer[(*pos)++];
if (note > 127 || buffer[(*pos)++] > 127) {
raise_midi_stream_error(*pos);
uint8_t note = buffer[(self->pos)++];
if (note > 127 || buffer[(self->pos)++] > 127) {
print_midi_stream_error(self);
}
return note;
}
STATIC void terminate_span(synthio_miditrack_obj_t *self, uint16_t *dur) {
if (*dur) {
self->track[self->total_spans - 1].dur = *dur;
*dur = 0;
} else {
self->total_spans--;
static int decode_duration(synthio_miditrack_obj_t *self) {
uint8_t *buffer = self->track.buf;
size_t len = self->track.len;
uint8_t c;
uint32_t delta = 0;
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) {
self->track = m_renew(synthio_midi_span_t, self->track, self->total_spans, self->total_spans + 1);
self->track[self->total_spans++] = *span;
}
STATIC void change_span_note(synthio_miditrack_obj_t *self, uint8_t old_note, uint8_t new_note, uint16_t *dur) {
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) {
// invariant: pointing at a MIDI message
static void decode_until_pause(synthio_miditrack_obj_t *self) {
uint8_t *buffer = self->track.buf;
size_t len = self->track.len;
do {
switch (buffer[self->pos++] >> 4) {
case 8: { // Note Off
uint8_t note = parse_note(buffer, len, &pos);
change_span_note(self, note, SYNTHIO_SILENCE, &dur);
uint8_t note = parse_note(self);
synthio_span_change_note(&self->synth, note, SYNTHIO_SILENCE);
break;
}
case 9: { // Note On
uint8_t note = parse_note(buffer, len, &pos);
change_span_note(self, SYNTHIO_SILENCE, note, &dur);
uint8_t note = parse_note(self);
synthio_span_change_note(&self->synth, SYNTHIO_SILENCE, note);
break;
}
case 10:
case 11:
case 14: // two data bytes to ignore
parse_note(buffer, len, &pos);
parse_note(self);
break;
case 12:
case 13: // one data byte to ignore
if (pos >= len || buffer[pos++] > 127) {
raise_midi_stream_error(pos);
if (self->pos >= len || buffer[self->pos++] > 127) {
print_midi_stream_error(self);
}
break;
case 15: // the full syntax is too complicated, just assume it's "End of Track" event
pos = len;
self->pos = len;
break;
default: // invalid event
raise_midi_stream_error(pos);
print_midi_stream_error(self);
}
}
terminate_span(self, &dur);
if (self->pos < len) {
self->synth.span.dur = decode_duration(self);
}
} while (self->pos < len && self->synth.span.dur == 0);
}
uint16_t max_dur = 0;
for (int i = 0; i < self->total_spans; i++) {
max_dur = MAX(self->track[i].dur, max_dur);
STATIC void start_parse(synthio_miditrack_obj_t *self) {
self->pos = 0;
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) {
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) {
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,
bool single_channel_output, uint8_t channel) {
synthio_synth_reset_buffer(&self->synth, single_channel_output, channel);
self->synth.span.dur = 0;
self->next_span = 0;
start_parse(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) {
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);
return (self->synth.span.dur == 0 && self->next_span >= self->total_spans) ?
GET_BUFFER_DONE : GET_BUFFER_MORE_DATA;
if (self->synth.span.dur == 0) {
if (self->pos == self->track.len) {
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,

View File

@ -34,9 +34,10 @@
typedef struct {
mp_obj_base_t base;
synthio_synth_t synth;
uint16_t next_span;
uint16_t total_spans;
synthio_midi_span_t *track;
mp_buffer_info_t track;
// invariant: after initial startup, pos always points just after an encoded duration, i.e., at a midi message (or at EOF)
size_t pos;
uint32_t tempo;
} synthio_miditrack_obj_t;

View File

@ -30,13 +30,10 @@
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;
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);
synthio_synth_init(&self->synth, sample_rate, waveform, waveform_length, envelope);
}
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) {
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) {
mp_obj_iter_buf_t iter_buf;
mp_obj_t iterable = mp_getiter(to_release, &iter_buf);
mp_obj_t item;
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 item;
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_tuple_t *result = MP_OBJ_TO_PTR(mp_obj_new_tuple(synthio_span_count_active_channels(&self->synth.span), NULL));
for (size_t i = 0, j = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) {
if (self->synth.span.note[i] != SYNTHIO_SILENCE) {
result->items[j++] = MP_OBJ_NEW_SMALL_INT(self->synth.span.note[i]);
int count = 0;
for (int chan = 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) {
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);

View File

@ -26,18 +26,117 @@
*/
#include "shared-module/synthio/__init__.h"
#include "shared-bindings/synthio/__init__.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,
12544, 13290, 14080, 14917, 15804}; // 9th octave
int synthio_span_count_active_channels(synthio_midi_span_t *span) {
int result = 0;
for (int i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) {
if (span->note[i] != SYNTHIO_SILENCE) {
result += 1;
STATIC int16_t convert_time_to_rate(uint32_t sample_rate, mp_obj_t time_in, int16_t difference) {
mp_float_t time = mp_obj_get_float(time_in);
int num_samples = (int)MICROPY_FLOAT_C_FUN(round)(time * sample_rate);
if (num_samples == 0) {
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;
@ -62,16 +161,23 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
memset(out_buffer, 0, synth->buffer_length);
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;
uint32_t waveform_length = synth->waveform_length;
if (active_channels) {
int16_t loudness = 0xffff / (1 + 2 * active_channels);
if (total_envelope > 0) {
uint16_t ovl_loudness = 0x7fffffff / MAX(0x8000, total_envelope);
for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
if (synth->span.note[chan] == SYNTHIO_SILENCE) {
synth->accum[chan] = 0;
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;
uint16_t base_freq = notes[synth->span.note[chan] % 12];
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;
*bufptr = (uint8_t *)out_buffer;
}
@ -117,11 +228,28 @@ void synthio_synth_deinit(synthio_synth_t *synth) {
synth->buffers[1] = NULL;
}
void synthio_synth_init(synthio_synth_t *synth, uint16_t max_dur) {
synth->buffer_length = MIN(SYNTHIO_MAX_DUR, max_dur) * SYNTHIO_BYTES_PER_SAMPLE;
void synthio_synth_envelope_set(synthio_synth_t *synth, mp_obj_t envelope_obj) {
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[1] = m_malloc(synth->buffer_length, false);
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,
@ -132,40 +260,57 @@ void synthio_synth_get_buffer_structure(synthio_synth_t *synth, bool single_chan
*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) {
*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) {
mp_get_buffer_raise(waveform_obj, bufinfo_waveform, MP_BUFFER_READ);
if (bufinfo_waveform->typecode != 'h') {
mp_raise_ValueError_varg(translate("%q must be array of type 'h'"), MP_QSTR_waveform);
STATIC int find_channel_with_note(synthio_synth_t *synth, uint8_t note) {
for (int i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) {
if (synth->span.note[i] == note) {
return i;
}
}
mp_arg_validate_length_range(bufinfo_waveform->len / 2, 2, 1024, MP_QSTR_waveform);
}
void synthio_span_init(synthio_midi_span_t *span) {
span->dur = 0;
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;
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++) {
if (SYNTHIO_VOICE_IS_RELEASING(synth, i)) {
return i;
}
}
}
return -1;
}
bool synthio_span_change_note(synthio_midi_span_t *span, uint8_t old_note, uint8_t new_note) {
if (new_note != SYNTHIO_SILENCE && find_channel_with_note(span, new_note) != -1) {
return false; // note already pressed, do nothing
bool synthio_span_change_note(synthio_synth_t *synth, uint8_t old_note, uint8_t new_note) {
int channel;
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) {
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 false;

View File

@ -30,6 +30,7 @@
#define SYNTHIO_BYTES_PER_SAMPLE (SYNTHIO_BITS_PER_SAMPLE / 8)
#define SYNTHIO_MAX_DUR (256)
#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"
@ -39,6 +40,25 @@ typedef struct {
} synthio_midi_span_t;
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;
int16_t *buffers[2];
const int16_t *waveform;
@ -46,19 +66,24 @@ typedef struct {
uint16_t last_buffer_length;
uint8_t other_channel, buffer_index, other_buffer_index;
uint16_t waveform_length;
synthio_envelope_definition_t envelope_definition;
mp_obj_t envelope_obj;
synthio_midi_span_t span;
uint32_t accum[CIRCUITPY_SYNTHIO_MAX_CHANNELS];
synthio_envelope_state_t envelope_state[CIRCUITPY_SYNTHIO_MAX_CHANNELS];
} 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_deinit(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,
bool *single_buffer, bool *samples_signed, uint32_t *max_buffer_length, uint8_t *spacing);
void synthio_synth_reset_buffer(synthio_synth_t *synth, bool single_channel_output, uint8_t channel);
void synthio_synth_parse_waveform(mp_buffer_info_t *bufinfo_waveform, mp_obj_t waveform_obj);
void synthio_synth_parse_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);
int synthio_span_count_active_channels(synthio_midi_span_t *span);
bool synthio_span_change_note(synthio_synth_t *synth, uint8_t old_note, uint8_t new_note);
void synthio_envelope_step(synthio_envelope_definition_t *definition, synthio_envelope_state_t *state, int n_samples);

View File

@ -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:
print(get_structure(m))
print(get_buffer(m))
p, q = get_buffer(m)
print(p, list(q))
with MidiTrack(
SCORE, sample_rate=8000, tempo=640, waveform=array.array("h", [0, 32767, 0, -32768])
) as m:
print(get_structure(m))
print(get_buffer(m))
p, q = get_buffer(m)
print(p, list(q))

View File

@ -1,4 +1,4 @@
(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)
(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]

View File

@ -1,10 +1,11 @@
import struct
import synthio
import audiocore
import ulab.numpy as np
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)
@ -22,3 +23,16 @@ dump_samples()
s.release_then_press((80,))
print(s.pressed)
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)))

View File

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