synthio: Add synthio.Note

This class allows much more expressive sound synthesis:
 * tremolo & vibrato
 * arbitrary frequency
 * different evelope & waveform per note
 * all properties dynamically settable from Python code
This commit is contained in:
Jeff Epler 2023-05-01 09:41:05 -05:00
parent bc03e03b9e
commit a7da245ad0
No known key found for this signature in database
GPG Key ID: D5BF15AB975AB4DE
20 changed files with 863 additions and 62 deletions

View File

@ -42,6 +42,7 @@ SRC_BITMAP := \
shared-bindings/struct/__init__.c \
shared-bindings/synthio/__init__.c \
shared-bindings/synthio/MidiTrack.c \
shared-bindings/synthio/Note.c \
shared-bindings/synthio/Synthesizer.c \
shared-bindings/traceback/__init__.c \
shared-bindings/util.c \
@ -64,6 +65,7 @@ SRC_BITMAP := \
shared-module/struct/__init__.c \
shared-module/synthio/__init__.c \
shared-module/synthio/MidiTrack.c \
shared-module/synthio/Note.c \
shared-module/synthio/Synthesizer.c \
shared-module/traceback/__init__.c \
shared-module/zlib/__init__.c \

View File

@ -651,6 +651,7 @@ SRC_SHARED_MODULE_ALL = \
supervisor/__init__.c \
supervisor/StatusBar.c \
synthio/MidiTrack.c \
synthio/Note.c \
synthio/Synthesizer.c \
synthio/__init__.c \
terminalio/Terminal.c \

View File

@ -0,0 +1,252 @@
/*
* This file is part of the Micro Python project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2021 Artyom Skrobov
* Copyright (c) 2023 Jeff Epler for Adafruit Industries
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <stdint.h>
#include "py/objproperty.h"
#include "py/runtime.h"
#include "shared-bindings/util.h"
#include "shared-bindings/synthio/Note.h"
#include "shared-module/synthio/Note.h"
static const mp_arg_t note_properties[] = {
{ MP_QSTR_frequency, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = NULL } },
{ MP_QSTR_amplitude, MP_ARG_OBJ, {.u_obj = MP_ROM_INT(1) } },
{ MP_QSTR_tremolo_rate, MP_ARG_OBJ, {.u_obj = NULL } },
{ MP_QSTR_tremolo_depth, MP_ARG_OBJ, {.u_obj = NULL } },
{ MP_QSTR_vibrato_rate, MP_ARG_OBJ, {.u_obj = NULL } },
{ MP_QSTR_vibrato_depth, MP_ARG_OBJ, {.u_obj = NULL } },
{ MP_QSTR_waveform, MP_ARG_OBJ, {.u_obj = MP_ROM_NONE } },
{ MP_QSTR_envelope, MP_ARG_OBJ, {.u_obj = MP_ROM_NONE } },
};
//| class Note:
//| def __init__(
//| self,
//| frequency: float,
//| amplitude: float = 1.0,
//| waveform: Optional[ReadableBuffer] = None,
//| envelope: Optional[Envelope] = None,
//| tremolo_depth: float = 0.0,
//| tremolo_rate: float = 0.0,
//| vibrato_depth: float = 0.0,
//| vibrato_rate: float = 0.0,
//| ) -> None:
//| """Construct a Note object, with a frequency in Hz, and optional amplitude (volume), waveform, envelope, tremolo (volume change) and vibrato (frequency change).
//|
//| If waveform or envelope are `None` the synthesizer object's default waveform or envelope are used.
//|
//| If the same Note object is played on multiple Synthesizer objects, the result is undefined.
//| """
STATIC mp_obj_t synthio_note_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
enum { ARG_frequency, ARG_amplitude, ARG_waveform, ARG_envelope, ARG_tremolo_rate, ARG_tremolo_depth, ARG_vibrato_rate, ARG_vibrato_depth };
mp_arg_val_t args[MP_ARRAY_SIZE(note_properties)];
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(note_properties), note_properties, args);
synthio_note_obj_t *self = m_new_obj(synthio_note_obj_t);
self->base.type = &synthio_note_type;
mp_obj_t result = MP_OBJ_FROM_PTR(self);
for (size_t i = 0; i < MP_ARRAY_SIZE(note_properties); i++) {
if (args[i].u_obj != NULL) {
mp_store_attr(result, note_properties[i].qst, args[i].u_obj);
}
}
return result;
};
//| frequency: float
//| """The base frequency of the note, in Hz."""
STATIC mp_obj_t synthio_note_get_frequency(mp_obj_t self_in) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
return mp_obj_new_float(common_hal_synthio_note_get_frequency(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_frequency_obj, synthio_note_get_frequency);
STATIC mp_obj_t synthio_note_set_frequency(mp_obj_t self_in, mp_obj_t arg) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_note_set_frequency(self, mp_obj_get_float(arg));
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_frequency_obj, synthio_note_set_frequency);
MP_PROPERTY_GETSET(synthio_note_frequency_obj,
(mp_obj_t)&synthio_note_get_frequency_obj,
(mp_obj_t)&synthio_note_set_frequency_obj);
//| amplitude: float
//| """The base amplitude of the note, from 0 to 1"""
STATIC mp_obj_t synthio_note_get_amplitude(mp_obj_t self_in) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
return mp_obj_new_float(common_hal_synthio_note_get_amplitude(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_amplitude_obj, synthio_note_get_amplitude);
STATIC mp_obj_t synthio_note_set_amplitude(mp_obj_t self_in, mp_obj_t arg) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_note_set_amplitude(self, mp_obj_get_float(arg));
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_amplitude_obj, synthio_note_set_amplitude);
MP_PROPERTY_GETSET(synthio_note_amplitude_obj,
(mp_obj_t)&synthio_note_get_amplitude_obj,
(mp_obj_t)&synthio_note_set_amplitude_obj);
//| tremolo_depth: float
//| """The tremolo depth of the note, from 0 to 1"""
STATIC mp_obj_t synthio_note_get_tremolo_depth(mp_obj_t self_in) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
return mp_obj_new_float(common_hal_synthio_note_get_tremolo_depth(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_tremolo_depth_obj, synthio_note_get_tremolo_depth);
STATIC mp_obj_t synthio_note_set_tremolo_depth(mp_obj_t self_in, mp_obj_t arg) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_note_set_tremolo_depth(self, mp_obj_get_float(arg));
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_tremolo_depth_obj, synthio_note_set_tremolo_depth);
MP_PROPERTY_GETSET(synthio_note_tremolo_depth_obj,
(mp_obj_t)&synthio_note_get_tremolo_depth_obj,
(mp_obj_t)&synthio_note_set_tremolo_depth_obj);
//| tremolo_rate: float
//| """The tremolo rate of the note, in Hz."""
STATIC mp_obj_t synthio_note_get_tremolo_rate(mp_obj_t self_in) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
return mp_obj_new_float(common_hal_synthio_note_get_tremolo_rate(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_tremolo_rate_obj, synthio_note_get_tremolo_rate);
STATIC mp_obj_t synthio_note_set_tremolo_rate(mp_obj_t self_in, mp_obj_t arg) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_note_set_tremolo_rate(self, mp_obj_get_float(arg));
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_tremolo_rate_obj, synthio_note_set_tremolo_rate);
MP_PROPERTY_GETSET(synthio_note_tremolo_rate_obj,
(mp_obj_t)&synthio_note_get_tremolo_rate_obj,
(mp_obj_t)&synthio_note_set_tremolo_rate_obj);
//| vibrato_depth: float
//| """The vibrato depth of the note, from 0 to 1"""
STATIC mp_obj_t synthio_note_get_vibrato_depth(mp_obj_t self_in) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
return mp_obj_new_float(common_hal_synthio_note_get_vibrato_depth(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_vibrato_depth_obj, synthio_note_get_vibrato_depth);
STATIC mp_obj_t synthio_note_set_vibrato_depth(mp_obj_t self_in, mp_obj_t arg) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_note_set_vibrato_depth(self, mp_obj_get_float(arg));
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_vibrato_depth_obj, synthio_note_set_vibrato_depth);
MP_PROPERTY_GETSET(synthio_note_vibrato_depth_obj,
(mp_obj_t)&synthio_note_get_vibrato_depth_obj,
(mp_obj_t)&synthio_note_set_vibrato_depth_obj);
//| vibrato_rate: float
//| """The vibrato rate of the note, in Hz."""
STATIC mp_obj_t synthio_note_get_vibrato_rate(mp_obj_t self_in) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
return mp_obj_new_float(common_hal_synthio_note_get_vibrato_rate(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_vibrato_rate_obj, synthio_note_get_vibrato_rate);
STATIC mp_obj_t synthio_note_set_vibrato_rate(mp_obj_t self_in, mp_obj_t arg) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_note_set_vibrato_rate(self, mp_obj_get_float(arg));
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_vibrato_rate_obj, synthio_note_set_vibrato_rate);
MP_PROPERTY_GETSET(synthio_note_vibrato_rate_obj,
(mp_obj_t)&synthio_note_get_vibrato_rate_obj,
(mp_obj_t)&synthio_note_set_vibrato_rate_obj);
//| waveform: Optional[ReadableBuffer]
//| """The waveform of this note. Setting the waveform to a buffer of a different size resets the note's phase."""
STATIC mp_obj_t synthio_note_get_waveform(mp_obj_t self_in) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
return common_hal_synthio_note_get_waveform_obj(self);
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_waveform_obj, synthio_note_get_waveform);
STATIC mp_obj_t synthio_note_set_waveform(mp_obj_t self_in, mp_obj_t arg) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_note_set_waveform(self, arg);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_waveform_obj, synthio_note_set_waveform);
MP_PROPERTY_GETSET(synthio_note_waveform_obj,
(mp_obj_t)&synthio_note_get_waveform_obj,
(mp_obj_t)&synthio_note_set_waveform_obj);
//| envelope: Envelope
//| """The envelope of this note"""
//|
STATIC mp_obj_t synthio_note_get_envelope(mp_obj_t self_in) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
return common_hal_synthio_note_get_envelope_obj(self);
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_envelope_obj, synthio_note_get_envelope);
STATIC mp_obj_t synthio_note_set_envelope(mp_obj_t self_in, mp_obj_t arg) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_note_set_envelope(self, arg);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_envelope_obj, synthio_note_set_envelope);
MP_PROPERTY_GETSET(synthio_note_envelope_obj,
(mp_obj_t)&synthio_note_get_envelope_obj,
(mp_obj_t)&synthio_note_set_envelope_obj);
static void note_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
(void)kind;
properties_print_helper(print, self_in, note_properties, MP_ARRAY_SIZE(note_properties));
}
STATIC const mp_rom_map_elem_t synthio_note_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_frequency), MP_ROM_PTR(&synthio_note_frequency_obj) },
{ MP_ROM_QSTR(MP_QSTR_amplitude), MP_ROM_PTR(&synthio_note_amplitude_obj) },
{ MP_ROM_QSTR(MP_QSTR_waveform), MP_ROM_PTR(&synthio_note_waveform_obj) },
{ MP_ROM_QSTR(MP_QSTR_envelope), MP_ROM_PTR(&synthio_note_envelope_obj) },
{ MP_ROM_QSTR(MP_QSTR_tremolo_depth), MP_ROM_PTR(&synthio_note_tremolo_depth_obj) },
{ MP_ROM_QSTR(MP_QSTR_tremolo_rate), MP_ROM_PTR(&synthio_note_tremolo_rate_obj) },
{ MP_ROM_QSTR(MP_QSTR_vibrato_depth), MP_ROM_PTR(&synthio_note_vibrato_depth_obj) },
{ MP_ROM_QSTR(MP_QSTR_vibrato_rate), MP_ROM_PTR(&synthio_note_vibrato_rate_obj) },
};
STATIC MP_DEFINE_CONST_DICT(synthio_note_locals_dict, synthio_note_locals_dict_table);
const mp_obj_type_t synthio_note_type = {
{ &mp_type_type },
.name = MP_QSTR_Note,
.make_new = synthio_note_make_new,
.locals_dict = (mp_obj_dict_t *)&synthio_note_locals_dict,
.print = note_print,
};

View File

@ -0,0 +1,30 @@
#pragma once
#include "py/obj.h"
typedef struct synthio_note_obj synthio_note_obj_t;
extern const mp_obj_type_t synthio_note_type;
mp_float_t common_hal_synthio_note_get_frequency(synthio_note_obj_t *self);
void common_hal_synthio_note_set_frequency(synthio_note_obj_t *self, mp_float_t value);
mp_float_t common_hal_synthio_note_get_amplitude(synthio_note_obj_t *self);
void common_hal_synthio_note_set_amplitude(synthio_note_obj_t *self, mp_float_t value);
mp_float_t common_hal_synthio_note_get_tremolo_rate(synthio_note_obj_t *self);
void common_hal_synthio_note_set_tremolo_rate(synthio_note_obj_t *self, mp_float_t value);
mp_float_t common_hal_synthio_note_get_tremolo_depth(synthio_note_obj_t *self);
void common_hal_synthio_note_set_tremolo_depth(synthio_note_obj_t *self, mp_float_t value);
mp_float_t common_hal_synthio_note_get_vibrato_rate(synthio_note_obj_t *self);
void common_hal_synthio_note_set_vibrato_rate(synthio_note_obj_t *self, mp_float_t value);
mp_float_t common_hal_synthio_note_get_vibrato_depth(synthio_note_obj_t *self);
void common_hal_synthio_note_set_vibrato_depth(synthio_note_obj_t *self, mp_float_t value);
mp_obj_t common_hal_synthio_note_get_waveform_obj(synthio_note_obj_t *self);
void common_hal_synthio_note_set_waveform(synthio_note_obj_t *self, mp_obj_t value);
mp_obj_t common_hal_synthio_note_get_envelope_obj(synthio_note_obj_t *self);
void common_hal_synthio_note_set_envelope(synthio_note_obj_t *self, mp_obj_t value);

View File

@ -36,6 +36,9 @@
#include "shared-bindings/synthio/__init__.h"
#include "supervisor/shared/translate/translate.h"
//| NoteSequence = Sequence[Union[int, Note]]
//| """A sequence of notes, which can each be integer MIDI notes or `Note` objects"""
//|
//| class Synthesizer:
//| def __init__(
//| self,
@ -48,7 +51,9 @@
//|
//| This API is experimental.
//|
//| Notes use MIDI note numbering, with 60 being C4 or Middle C, approximately 262Hz.
//| Integer notes use MIDI note numbering, with 60 being C4 or Middle C,
//| approximately 262Hz. Integer notes use the given waveform & envelope,
//| and do not support advanced features like tremolo or vibrato.
//|
//| :param int sample_rate: The desired playback sample rate; higher sample rate requires more memory
//| :param 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)
@ -76,7 +81,6 @@ STATIC mp_obj_t synthio_synthesizer_make_new(const mp_obj_type_t *type, size_t n
bufinfo_waveform.len / 2,
args[ARG_envelope].u_obj);
return MP_OBJ_FROM_PTR(self);
}
@ -86,12 +90,12 @@ STATIC void check_for_deinit(synthio_synthesizer_obj_t *self) {
}
}
//| def press(self, /, press: Sequence[int] = ()) -> None:
//| """Turn some notes on. Notes use MIDI numbering, with 60 being middle C, approximately 262Hz.
//| def press(self, /, press: NoteSequence = ()) -> None:
//| """Turn some notes on.
//|
//| Pressing a note that was already pressed has no effect.
//|
//| :param Sequence[int] press: Any sequence of integer notes."""
//| :param NoteSequence press: Any sequence of notes."""
STATIC mp_obj_t synthio_synthesizer_press(mp_obj_t self_in, mp_obj_t press) {
synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in);
check_for_deinit(self);
@ -99,12 +103,12 @@ STATIC mp_obj_t synthio_synthesizer_press(mp_obj_t self_in, mp_obj_t press) {
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(synthio_synthesizer_press_obj, synthio_synthesizer_press);
//| def release(self, /, release: Sequence[int] = ()) -> None:
//| """Turn some notes off. Notes use MIDI numbering, with 60 being middle C, approximately 262Hz.
//| def release(self, /, release: NoteSequence = ()) -> None:
//| """Turn some notes off.
//|
//| Releasing a note that was already released has no effect.
//|
//| :param Sequence[int] release: Any sequence of integer notes."""
//| :param NoteSequence release: Any sequence of notes."""
STATIC mp_obj_t synthio_synthesizer_release(mp_obj_t self_in, mp_obj_t release) {
synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in);
check_for_deinit(self);
@ -113,10 +117,8 @@ STATIC mp_obj_t synthio_synthesizer_release(mp_obj_t self_in, mp_obj_t release)
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(synthio_synthesizer_release_obj, synthio_synthesizer_release);
//| def release_then_press(
//| self, release: Sequence[int] = (), press: Sequence[int] = ()
//| ) -> None:
//| """Turn some notes on and/or off. Notes use MIDI numbering, with 60 being middle C.
//| def release_then_press(self, release: NoteSequence = (), press: NoteSequence = ()) -> None:
//| """Turn some notes on and/or off.
//|
//| It is OK to release note that was not actually turned on.
//|
@ -125,8 +127,8 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(synthio_synthesizer_release_obj, synthio_synthe
//| Releasing and pressing the note again has little effect, but does reset the phase
//| of the note, which may be perceptible as a small glitch.
//|
//| :param Sequence[int] release: Any sequence of integer notes.
//| :param Sequence[int] press: Any sequence of integer notes."""
//| :param NoteSequence release: Any sequence of notes.
//| :param NoteSequence press: Any sequence of notes."""
STATIC mp_obj_t synthio_synthesizer_release_then_press(mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_release, ARG_press };
static const mp_arg_t allowed_args[] = {
@ -146,13 +148,13 @@ STATIC mp_obj_t synthio_synthesizer_release_then_press(mp_uint_t n_args, const m
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(synthio_synthesizer_release_then_press_obj, 1, synthio_synthesizer_release_then_press);
//
//| def release_all_then_press(self, /, press: Sequence[int]) -> None:
//| def release_all_then_press(self, /, press: NoteSequence) -> None:
//| """Turn any currently-playing notes off, then turn on the given notes
//|
//| Releasing and pressing the note again has little effect, but does reset the phase
//| of the note, which may be perceptible as a small glitch.
//|
//| :param Sequence[int] press: Any sequence of integer notes."""
//| :param NoteSequence press: Any sequence of notes."""
STATIC mp_obj_t synthio_synthesizer_release_all_then_press(mp_obj_t self_in, mp_obj_t press) {
synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in);
check_for_deinit(self);
@ -183,7 +185,7 @@ STATIC mp_obj_t synthio_synthesizer_deinit(mp_obj_t self_in) {
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(synthio_synthesizer_deinit_obj, synthio_synthesizer_deinit);
//| def __enter__(self) -> MidiTrack:
//| def __enter__(self) -> Synthesizer:
//| """No-op used by Context Managers."""
//| ...
// Provided by context manager helper.
@ -232,7 +234,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(synthio_synthesizer_get_sample_rate_obj, synthio_synth
MP_PROPERTY_GETTER(synthio_synthesizer_sample_rate_obj,
(mp_obj_t)&synthio_synthesizer_get_sample_rate_obj);
//| pressed: Tuple[int]
//| pressed: NoteSequence
//| """A sequence of the currently pressed notes (read-only property)"""
//|
STATIC mp_obj_t synthio_synthesizer_obj_get_pressed(mp_obj_t self_in) {

View File

@ -35,6 +35,7 @@
#include "shared-bindings/synthio/__init__.h"
#include "shared-bindings/synthio/MidiTrack.h"
#include "shared-bindings/synthio/Note.h"
#include "shared-bindings/synthio/Synthesizer.h"
#define default_attack_time (MICROPY_FLOAT_CONST(0.1))
@ -157,7 +158,6 @@ const mp_obj_namedtuple_type_t synthio_envelope_type_obj = {
},
};
//| def from_file(
//| file: typing.BinaryIO,
//| *,
@ -271,6 +271,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(synthio_midi_to_hz_obj, midi_to_hz);
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_Note), MP_ROM_PTR(&synthio_note_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) },

View File

@ -33,7 +33,7 @@ STATIC void print_midi_stream_error(synthio_miditrack_obj_t *self) {
self->pos = self->track.len;
}
STATIC uint8_t parse_note(synthio_miditrack_obj_t *self) {
STATIC mp_obj_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) {
@ -43,7 +43,7 @@ STATIC uint8_t parse_note(synthio_miditrack_obj_t *self) {
if (note > 127 || buffer[(self->pos)++] > 127) {
print_midi_stream_error(self);
}
return note;
return MP_OBJ_NEW_SMALL_INT(note);
}
static int decode_duration(synthio_miditrack_obj_t *self) {
@ -72,12 +72,12 @@ static void decode_until_pause(synthio_miditrack_obj_t *self) {
do {
switch (buffer[self->pos++] >> 4) {
case 8: { // Note Off
uint8_t note = parse_note(self);
mp_obj_t note = parse_note(self);
synthio_span_change_note(&self->synth, note, SYNTHIO_SILENCE);
break;
}
case 9: { // Note On
uint8_t note = parse_note(self);
mp_obj_t note = parse_note(self);
synthio_span_change_note(&self->synth, SYNTHIO_SILENCE, note);
break;
}

View File

@ -0,0 +1,158 @@
/*
* This file is part of the Micro Python project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2023 Jeff Epler for Adafruit Industries
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <math.h>
#include "py/runtime.h"
#include "shared-module/synthio/Note.h"
#include "shared-bindings/synthio/Note.h"
#include "shared-bindings/synthio/__init__.h"
static int32_t round_float_to_int(mp_float_t f) {
return (int32_t)(f + MICROPY_FLOAT_CONST(0.5));
}
mp_float_t common_hal_synthio_note_get_frequency(synthio_note_obj_t *self) {
return self->frequency;
}
void common_hal_synthio_note_set_frequency(synthio_note_obj_t *self, mp_float_t value_in) {
mp_float_t val = mp_arg_validate_float_range(value_in, 0, 32767, MP_QSTR_frequency);
self->frequency = val;
self->frequency_scaled = synthio_frequency_convert_float_to_scaled(val);
}
mp_float_t common_hal_synthio_note_get_amplitude(synthio_note_obj_t *self) {
return self->amplitude;
}
void common_hal_synthio_note_set_amplitude(synthio_note_obj_t *self, mp_float_t value_in) {
mp_float_t val = mp_arg_validate_float_range(value_in, 0, 1, MP_QSTR_amplitude);
self->amplitude = val;
self->amplitude_scaled = round_float_to_int(val * 32767);
}
mp_float_t common_hal_synthio_note_get_tremolo_depth(synthio_note_obj_t *self) {
return self->tremolo_descr.amplitude;
}
void common_hal_synthio_note_set_tremolo_depth(synthio_note_obj_t *self, mp_float_t value_in) {
mp_float_t val = mp_arg_validate_float_range(value_in, 0, 1, MP_QSTR_tremolo_depth);
self->tremolo_descr.amplitude = val;
self->tremolo_state.amplitude_scaled = round_float_to_int(val * 32767);
}
mp_float_t common_hal_synthio_note_get_tremolo_rate(synthio_note_obj_t *self) {
return self->tremolo_descr.frequency;
}
void common_hal_synthio_note_set_tremolo_rate(synthio_note_obj_t *self, mp_float_t value_in) {
mp_float_t val = mp_arg_validate_float_range(value_in, 0, 60, MP_QSTR_tremolo_rate);
self->tremolo_descr.frequency = val;
if (self->sample_rate != 0) {
self->tremolo_state.dds = synthio_frequency_convert_float_to_dds(val, self->sample_rate);
}
}
mp_float_t common_hal_synthio_note_get_vibrato_depth(synthio_note_obj_t *self) {
return self->vibrato_descr.amplitude;
}
void common_hal_synthio_note_set_vibrato_depth(synthio_note_obj_t *self, mp_float_t value_in) {
mp_float_t val = mp_arg_validate_float_range(value_in, 0, 1, MP_QSTR_vibrato_depth);
self->vibrato_descr.amplitude = val;
self->vibrato_state.amplitude_scaled = round_float_to_int(val * 32767);
}
mp_float_t common_hal_synthio_note_get_vibrato_rate(synthio_note_obj_t *self) {
return self->vibrato_descr.frequency;
}
void common_hal_synthio_note_set_vibrato_rate(synthio_note_obj_t *self, mp_float_t value_in) {
mp_float_t val = mp_arg_validate_float_range(value_in, 0, 60, MP_QSTR_vibrato_rate);
self->vibrato_descr.frequency = val;
if (self->sample_rate != 0) {
self->vibrato_state.dds = synthio_frequency_convert_float_to_dds(val, self->sample_rate);
}
}
mp_obj_t common_hal_synthio_note_get_envelope_obj(synthio_note_obj_t *self) {
return self->envelope_obj;
}
void common_hal_synthio_note_set_envelope(synthio_note_obj_t *self, mp_obj_t envelope_in) {
if (envelope_in != mp_const_none) {
mp_arg_validate_type(envelope_in, (mp_obj_type_t *)&synthio_envelope_type_obj, MP_QSTR_envelope);
}
self->envelope_obj = envelope_in;
}
mp_obj_t common_hal_synthio_note_get_waveform_obj(synthio_note_obj_t *self) {
return self->waveform_obj;
}
void common_hal_synthio_note_set_waveform(synthio_note_obj_t *self, mp_obj_t waveform_in) {
if (waveform_in == mp_const_none) {
memset(&self->waveform_buf, 0, sizeof(self->waveform_buf));
} else {
mp_buffer_info_t bufinfo_waveform;
synthio_synth_parse_waveform(&bufinfo_waveform, waveform_in);
self->waveform_buf = bufinfo_waveform;
}
self->waveform_obj = waveform_in;
}
void synthio_note_recalculate(synthio_note_obj_t *self, int32_t sample_rate) {
if (sample_rate == self->sample_rate) {
return;
}
self->sample_rate = sample_rate;
if (self->envelope_obj != mp_const_none) {
synthio_envelope_definition_set(&self->envelope_def, self->envelope_obj, sample_rate);
}
synthio_lfo_set(&self->tremolo_state, &self->tremolo_descr, sample_rate);
self->tremolo_state.offset_scaled = 32768 - self->tremolo_state.amplitude_scaled;
synthio_lfo_set(&self->vibrato_state, &self->vibrato_descr, sample_rate);
self->vibrato_state.offset_scaled = 32768;
}
void synthio_note_start(synthio_note_obj_t *self, int32_t sample_rate) {
synthio_note_recalculate(self, sample_rate);
self->phase = 0;
}
uint32_t synthio_note_envelope(synthio_note_obj_t *self) {
return self->amplitude_scaled;
}
uint32_t synthio_note_step(synthio_note_obj_t *self, int32_t sample_rate, int16_t dur, uint16_t *loudness) {
int tremolo_value = synthio_lfo_step(&self->tremolo_state, dur);
int vibrato_value = synthio_lfo_step(&self->vibrato_state, dur);
*loudness = (*loudness * tremolo_value) >> 15;
uint32_t frequency_scaled = ((uint64_t)self->frequency_scaled * vibrato_value) >> 15;
return frequency_scaled;
}

View File

@ -0,0 +1,54 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2023 Jeff Epler for Adafruit Industries
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include "shared-module/synthio/__init__.h"
typedef struct synthio_note_obj {
mp_obj_base_t base;
mp_float_t frequency;
mp_float_t amplitude;
mp_obj_t waveform_obj, envelope_obj;
int32_t sample_rate;
int32_t phase;
int32_t frequency_scaled;
int32_t amplitude_scaled;
synthio_lfo_descr_t tremolo_descr, vibrato_descr;
synthio_lfo_state_t tremolo_state, vibrato_state;
mp_buffer_info_t waveform_buf;
synthio_envelope_definition_t envelope_def;
} synthio_note_obj_t;
void synthio_note_recalculate(synthio_note_obj_t *self, int32_t sample_rate);
uint32_t synthio_note_step(synthio_note_obj_t *self, int32_t sample_rate, int16_t dur, uint16_t *loudness);
void synthio_note_start(synthio_note_obj_t *self, int32_t sample_rate);
bool synthio_note_playing(synthio_note_obj_t *self);
uint32_t synthio_note_envelope(synthio_note_obj_t *self);

View File

@ -26,6 +26,8 @@
#include "py/runtime.h"
#include "shared-bindings/synthio/Synthesizer.h"
#include "shared-bindings/synthio/Note.h"
#include "shared-module/synthio/Note.h"
@ -72,40 +74,58 @@ void synthio_synthesizer_get_buffer_structure(synthio_synthesizer_obj_t *self, b
void common_hal_synthio_synthesizer_release_all(synthio_synthesizer_obj_t *self) {
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);
if (self->synth.span.note_obj[i] != SYNTHIO_SILENCE) {
synthio_span_change_note(&self->synth, self->synth.span.note_obj[i], SYNTHIO_SILENCE);
}
}
}
STATIC mp_obj_t validate_note(mp_obj_t note_in) {
if (mp_obj_is_small_int(note_in)) {
mp_arg_validate_int_range(mp_obj_get_int(note_in), 0, 127, MP_QSTR_note);
} else {
const mp_obj_type_t *note_type = mp_obj_get_type(note_in);
if (note_type != &synthio_note_type) {
mp_raise_TypeError_varg(translate("%q must be of type %q or %q, not %q"), MP_QSTR_note, MP_QSTR_int, MP_QSTR_Note, note_type->name);
}
}
return note_in;
}
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, mp_arg_validate_int_range(mp_obj_get_int(item), 0, 127, MP_QSTR_note), SYNTHIO_SILENCE);
synthio_span_change_note(&self->synth, validate_note(item), SYNTHIO_SILENCE);
}
}
void common_hal_synthio_synthesizer_press(synthio_synthesizer_obj_t *self, mp_obj_t to_press) {
mp_obj_iter_buf_t iter_buf;
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, SYNTHIO_SILENCE, mp_arg_validate_int_range(mp_obj_get_int(item), 0, 127, MP_QSTR_note));
mp_obj_t note_obj;
while ((note_obj = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
if (synthio_span_change_note(&self->synth, SYNTHIO_SILENCE, validate_note(note_obj))) {
if (!mp_obj_is_small_int(note_obj)) {
synthio_note_obj_t *note = MP_OBJ_TO_PTR(note_obj);
synthio_note_start(note, self->synth.sample_rate);
}
}
}
}
mp_obj_t common_hal_synthio_synthesizer_get_pressed_notes(synthio_synthesizer_obj_t *self) {
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) {
if (self->synth.span.note_obj[chan] != SYNTHIO_SILENCE && SYNTHIO_NOTE_IS_PLAYING(&self->synth, chan)) {
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]);
if (self->synth.span.note_obj[chan] != SYNTHIO_SILENCE && SYNTHIO_NOTE_IS_PLAYING(&self->synth, chan)) {
result->items[j++] = self->synth.span.note_obj[chan];
}
}
return MP_OBJ_FROM_PTR(result);

View File

@ -27,6 +27,7 @@
#include "shared-module/synthio/__init__.h"
#include "shared-bindings/synthio/__init__.h"
#include "shared-module/synthio/Note.h"
#include "py/runtime.h"
#include <math.h>
#include <stdlib.h>
@ -36,6 +37,14 @@ STATIC const int16_t square_wave[] = {-32768, 32767};
STATIC const uint16_t notes[] = {8372, 8870, 9397, 9956, 10548, 11175, 11840,
12544, 13290, 14080, 14917, 15804}; // 9th octave
static int32_t round_float_to_int(mp_float_t f) {
return (int32_t)(f + MICROPY_FLOAT_CONST(0.5));
}
static int64_t round_float_to_int64(mp_float_t f) {
return (int64_t)(f + MICROPY_FLOAT_CONST(0.5));
}
mp_float_t common_hal_synthio_midi_to_hz_float(mp_int_t arg) {
uint8_t octave = arg / 12;
uint16_t base_freq = notes[arg % 12];
@ -52,7 +61,7 @@ STATIC int16_t convert_time_to_rate(uint32_t sample_rate, mp_obj_t time_in, int1
return (difference < 0) ? -result : result;
}
STATIC void synthio_envelope_definition_set(synthio_envelope_definition_t *envelope, mp_obj_t obj, uint32_t sample_rate) {
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;
@ -141,7 +150,7 @@ STATIC void synthio_envelope_state_release(synthio_envelope_state_t *state, synt
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) {
if (synth->span.note_obj[chan] != SYNTHIO_SILENCE) {
result += synth->envelope_state[chan].level;
}
}
@ -168,12 +177,10 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
int32_t sample_rate = synth->sample_rate;
uint32_t total_envelope = synthio_synth_sum_envelope(synth);
const int16_t *waveform = synth->waveform;
uint32_t waveform_length = synth->waveform_length;
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) {
if (synth->span.note_obj[chan] == SYNTHIO_SILENCE) {
synth->accum[chan] = 0;
continue;
}
@ -182,25 +189,52 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
if (synth->envelope_state[chan].level == 0) {
// note is truly finished
synth->span.note[chan] = SYNTHIO_SILENCE;
synth->span.note_obj[chan] = SYNTHIO_SILENCE;
}
uint8_t octave = synth->span.note[chan] / 12;
uint16_t base_freq = notes[synth->span.note[chan] % 12];
uint32_t dds_rate;
const int16_t *waveform = synth->waveform;
uint32_t waveform_length = synth->waveform_length;
mp_obj_t note_obj = synth->span.note_obj[chan];
if (mp_obj_is_small_int(note_obj)) {
uint8_t note = mp_obj_get_int(note_obj);
uint8_t octave = note / 12;
uint16_t base_freq = notes[note % 12];
// rate = base_freq * waveform_length
// den = sample_rate * 2 ^ (10 - octave)
// den = sample_rate * 2 ^ 10 / 2^octave
// dds_rate = 2^SHIFT * rate / den
// dds_rate = 2^(SHIFT-10+octave) * base_freq * waveform_length / sample_rate
dds_rate = (sample_rate / 2 + ((uint64_t)(base_freq * waveform_length) << (SYNTHIO_FREQUENCY_SHIFT - 10 + octave))) / sample_rate;
} else {
synthio_note_obj_t *note = MP_OBJ_TO_PTR(note_obj);
int32_t frequency_scaled = synthio_note_step(note, sample_rate, dur, &loudness);
if (note->waveform_buf.buf) {
waveform = note->waveform_buf.buf;
waveform_length = note->waveform_buf.len / 2;
}
dds_rate = synthio_frequency_convert_scaled_to_dds((uint64_t)frequency_scaled * waveform_length, sample_rate);
}
uint32_t accum = synth->accum[chan];
#define SHIFT (16)
// rate = base_freq * waveform_length
// den = sample_rate * 2 ^ (10 - octave)
// den = sample_rate * 2 ^ 10 / 2^octave
// dds_rate = 2^SHIFT * rate / den
// dds_rate = 2^(SHIFT-10+octave) * base_freq * waveform_length / sample_rate
uint32_t dds_rate = (sample_rate / 2 + ((uint64_t)(base_freq * waveform_length) << (SHIFT - 10 + octave))) / sample_rate;
uint32_t lim = waveform_length << SYNTHIO_FREQUENCY_SHIFT;
if (dds_rate > lim / 2) {
// beyond nyquist, can't play note
continue;
}
// can happen if note waveform gets set mid-note, but the expensive modulo is usually avoided
if (accum > lim) {
accum %= lim;
}
for (uint16_t i = 0; i < dur; i++) {
accum += dds_rate;
if (accum > waveform_length << SHIFT) {
accum -= waveform_length << SHIFT;
// because dds_rate is low enough, the subtraction is guaranteed to go back into range, no expensive modulo needed
if (accum > lim) {
accum -= lim;
}
int16_t idx = accum >> SHIFT;
int16_t idx = accum >> SYNTHIO_FREQUENCY_SHIFT;
out_buffer[i] += (waveform[idx] * loudness) / 65536;
}
synth->accum[chan] = accum;
@ -209,7 +243,18 @@ 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);
mp_obj_t note_obj = synth->span.note_obj[chan];
if (note_obj == SYNTHIO_SILENCE) {
continue;
}
synthio_envelope_definition_t *def = &synth->envelope_definition;
if (!mp_obj_is_small_int(note_obj)) {
synthio_note_obj_t *note = MP_OBJ_TO_PTR(note_obj);
if (note->envelope_obj != mp_const_none) {
def = &note->envelope_def;
}
}
synthio_envelope_state_step(&synth->envelope_state[chan], def, dur);
}
*buffer_length = synth->last_buffer_length = dur * SYNTHIO_BYTES_PER_SAMPLE;
@ -254,7 +299,7 @@ void synthio_synth_init(synthio_synth_t *synth, uint32_t sample_rate, const int1
synthio_synth_envelope_set(synth, envelope_obj);
for (size_t i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) {
synth->span.note[i] = SYNTHIO_SILENCE;
synth->span.note_obj[i] = SYNTHIO_SILENCE;
}
}
@ -283,16 +328,16 @@ void synthio_synth_parse_waveform(mp_buffer_info_t *bufinfo_waveform, mp_obj_t w
parse_common(bufinfo_waveform, waveform_obj, MP_QSTR_waveform);
}
STATIC int find_channel_with_note(synthio_synth_t *synth, uint8_t note) {
STATIC int find_channel_with_note(synthio_synth_t *synth, mp_obj_t note) {
for (int i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) {
if (synth->span.note[i] == note) {
if (synth->span.note_obj[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)) {
if (!SYNTHIO_NOTE_IS_PLAYING(synth, i)) {
return i;
}
}
@ -300,7 +345,7 @@ STATIC int find_channel_with_note(synthio_synth_t *synth, uint8_t note) {
return -1;
}
bool synthio_span_change_note(synthio_synth_t *synth, uint8_t old_note, uint8_t new_note) {
bool synthio_span_change_note(synthio_synth_t *synth, mp_obj_t old_note, mp_obj_t new_note) {
int channel;
if (new_note != SYNTHIO_SILENCE && (channel = find_channel_with_note(synth, new_note)) != -1) {
// note already playing, re-strike
@ -313,7 +358,7 @@ bool synthio_span_change_note(synthio_synth_t *synth, uint8_t old_note, uint8_t
if (new_note == SYNTHIO_SILENCE) {
synthio_envelope_state_release(&synth->envelope_state[channel], &synth->envelope_definition);
} else {
synth->span.note[channel] = new_note;
synth->span.note_obj[channel] = new_note;
synthio_envelope_state_init(&synth->envelope_state[channel], &synth->envelope_definition);
synth->accum[channel] = 0;
}
@ -321,3 +366,39 @@ bool synthio_span_change_note(synthio_synth_t *synth, uint8_t old_note, uint8_t
}
return false;
}
uint64_t synthio_frequency_convert_float_to_scaled(mp_float_t val) {
return round_float_to_int64(val * (1 << SYNTHIO_FREQUENCY_SHIFT));
}
uint32_t synthio_frequency_convert_float_to_dds(mp_float_t frequency_hz, int32_t sample_rate) {
return synthio_frequency_convert_scaled_to_dds(synthio_frequency_convert_float_to_scaled(frequency_hz), sample_rate);
}
uint32_t synthio_frequency_convert_scaled_to_dds(uint64_t frequency_scaled, int32_t sample_rate) {
return (sample_rate / 2 + frequency_scaled) / sample_rate;
}
void synthio_lfo_set(synthio_lfo_state_t *state, const synthio_lfo_descr_t *descr, uint32_t sample_rate) {
state->amplitude_scaled = round_float_to_int(descr->amplitude * 32768);
state->dds = synthio_frequency_convert_float_to_dds(descr->frequency * 65536, sample_rate);
}
int synthio_lfo_step(synthio_lfo_state_t *state, uint16_t dur) {
uint32_t phase = state->phase;
uint16_t whole_phase = phase >> 16;
// advance the phase accumulator
state->phase = phase + state->dds * dur;
// create a triangle wave, it's quick and easy
int v;
if (whole_phase < 16384) { // ramp from 0 to amplitude
v = (state->amplitude_scaled * whole_phase);
} else if (whole_phase < 49152) { // ramp from +amplitude to -amplitude
v = (state->amplitude_scaled * (32768 - whole_phase));
} else { // from -amplitude to 0
v = (state->amplitude_scaled * (whole_phase - 65536));
}
return v / 16384 + state->offset_scaled;
}

View File

@ -29,14 +29,16 @@
#define SYNTHIO_BITS_PER_SAMPLE (16)
#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)
#define SYNTHIO_SILENCE (mp_const_none)
#define SYNTHIO_NOTE_IS_SIMPLE(note) (mp_obj_is_small_int(note))
#define SYNTHIO_NOTE_IS_PLAYING(synth, i) ((synth)->envelope_state[(i)].state != SYNTHIO_ENVELOPE_STATE_RELEASE)
#define SYNTHIO_FREQUENCY_SHIFT (16)
#include "shared-module/audiocore/__init__.h"
typedef struct {
uint16_t dur;
uint8_t note[CIRCUITPY_SYNTHIO_MAX_CHANNELS];
mp_obj_t note_obj[CIRCUITPY_SYNTHIO_MAX_CHANNELS];
} synthio_midi_span_t;
typedef struct {
@ -73,6 +75,15 @@ typedef struct synthio_synth {
synthio_envelope_state_t envelope_state[CIRCUITPY_SYNTHIO_MAX_CHANNELS];
} synthio_synth_t;
typedef struct {
mp_float_t amplitude, frequency;
} synthio_lfo_descr_t;
typedef struct {
uint32_t amplitude_scaled, offset_scaled, dds, phase;
} synthio_lfo_state_t;
void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **buffer, uint32_t *buffer_length, uint8_t channel);
void synthio_synth_deinit(synthio_synth_t *synth);
bool synthio_synth_deinited(synthio_synth_t *synth);
@ -84,6 +95,14 @@ void synthio_synth_reset_buffer(synthio_synth_t *synth, bool single_channel_outp
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_synth_t *synth, uint8_t old_note, uint8_t new_note);
bool synthio_span_change_note(synthio_synth_t *synth, mp_obj_t old_note, mp_obj_t new_note);
void synthio_envelope_step(synthio_envelope_definition_t *definition, synthio_envelope_state_t *state, int n_samples);
void synthio_envelope_definition_set(synthio_envelope_definition_t *envelope, mp_obj_t obj, uint32_t sample_rate);
uint64_t synthio_frequency_convert_float_to_scaled(mp_float_t frequency_hz);
uint32_t synthio_frequency_convert_float_to_dds(mp_float_t frequency_hz, int32_t sample_rate);
uint32_t synthio_frequency_convert_scaled_to_dds(uint64_t frequency_scaled, int32_t sample_rate);
void synthio_lfo_set(synthio_lfo_state_t *state, const synthio_lfo_descr_t *descr, uint32_t sample_rate);
int synthio_lfo_step(synthio_lfo_state_t *state, uint16_t dur);

View File

@ -0,0 +1,5 @@
# Test synthio without hardware
Build the unix port then run `....../ports/unix/micropython-coverage code.py`.
This will create `tune.wav` as output, which you can listen to using any old audio player.

View File

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

View File

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

View File

@ -0,0 +1,100 @@
import random
import audiocore
import synthio
from ulab import numpy as np
import wave
SAMPLE_SIZE = 1024
VOLUME = 14700
sine = np.array(
np.sin(np.linspace(0, 2 * np.pi, SAMPLE_SIZE, endpoint=False)) * VOLUME,
dtype=np.int16,
)
envelope = synthio.Envelope(
attack_time=0.1, decay_time=0.05, release_time=0.2, attack_level=1, sustain_level=0.8
)
synth = synthio.Synthesizer(sample_rate=48000)
def synthesize(synth):
random.seed(3)
target_notes = [synthio.midi_to_hz(n + o) for n in (60, 64, 67, 70) for o in (-12, 12, 0)]
print(target_notes)
notes = [
synthio.Note(frequency=random.randint(60, 20000), waveform=sine, envelope=envelope)
for note in target_notes
]
synth.press(notes)
target = synthio.midi_to_hz(72)
factor = 0.98
for i in range(600):
yield 1
for ni, ti in zip(notes, target_notes):
print(ni.frequency, ti)
break
for ni, ti in zip(notes, target_notes):
ni.frequency = (ni.frequency * factor) + (ti * (1 - factor))
synth.release_all()
yield 36
def synthesize2(synth):
n = synthio.Note(
frequency=synthio.midi_to_hz(60),
tremolo_depth=0.2,
tremolo_rate=2,
waveform=sine,
envelope=envelope,
)
synth.press((n,))
yield 360
synth.release_all()
yield 36
def synthesize3(synth):
n = synthio.Note(
frequency=synthio.midi_to_hz(60),
vibrato_depth=0.1,
vibrato_rate=8,
waveform=sine,
envelope=envelope,
)
synth.press((n,))
yield 360
synth.release_all()
yield 36
def synthesize4(synth):
n = synthio.Note(
frequency=synthio.midi_to_hz(60),
tremolo_depth=0.1,
tremolo_rate=1.5,
vibrato_depth=0.1,
vibrato_rate=3,
waveform=sine,
envelope=envelope,
)
synth.press((n,))
yield 720
synth.release_all()
yield 36
def chain(*args):
for a in args:
yield from a
# sox -r 48000 -e signed -b 16 -c 1 tune.raw tune.wav
with wave.open("tune-noenv.wav", "w") as f:
f.setnchannels(1)
f.setsampwidth(2)
f.setframerate(48000)
for n in chain(synthesize2(synth), synthesize3(synth), synthesize4(synth)):
for i in range(n):
result, data = audiocore.get_buffer(synth)
f.writeframes(data)

View File

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

View File

@ -1,5 +1,5 @@
# Test synthio without hardware
Build the uninx port then run `....../ports/unix/micropython-coverage midi2wav.py`.
Build the unix port then run `....../ports/unix/micropython-coverage midi2wav.py`.
This will create `tune.wav` as output, which you can listen to using any old audio player.

View File

@ -0,0 +1,41 @@
import struct
import synthio
import audiocore
def dump_samples():
print([i for i in audiocore.get_buffer(s)[1][:24]])
n80 = synthio.Note(synthio.midi_to_hz(80))
n91 = synthio.Note(synthio.midi_to_hz(80))
s = synthio.Synthesizer(sample_rate=8000)
print(s.pressed)
dump_samples()
s.press((n80,))
print(s.pressed)
dump_samples()
s.press((n91,))
print(s.pressed)
dump_samples()
s.release_then_press((n80,))
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
)
n60 = synthio.Note(synthio.midi_to_hz(60), envelope=envelope)
s = synthio.Synthesizer(sample_rate=8000, envelope=envelope)
s.press((n60,))
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

@ -0,0 +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]
(Note(frequency=830.625, amplitude=1.0, tremolo_rate=0.0, tremolo_depth=0.0, vibrato_rate=0.0, vibrato_depth=0.0, waveform=None, envelope=None),)
[-16383, -16383, -16383, -16383, 16382, 16382, 16382, 16382, 16382, -16383, -16383, -16383, -16383, -16383, 16382, 16382, 16382, 16382, 16382, -16383, -16383, -16383, -16383, -16383]
(Note(frequency=830.625, amplitude=1.0, tremolo_rate=0.0, tremolo_depth=0.0, vibrato_rate=0.0, vibrato_depth=0.0, waveform=None, envelope=None), Note(frequency=830.625, amplitude=1.0, tremolo_rate=0.0, tremolo_depth=0.0, vibrato_rate=0.0, vibrato_depth=0.0, waveform=None, envelope=None))
[0, 0, 0, 0, 0, 0, 0, 0, 16382, 0, 0, 0, 0, -16382, 0, 0, 0, 0, 16382, 0, 0, 0, 0, -16382]
(Note(frequency=830.625, amplitude=1.0, tremolo_rate=0.0, tremolo_depth=0.0, vibrato_rate=0.0, vibrato_depth=0.0, waveform=None, envelope=None),)
[0, 0, 0, 16382, 0, 0, 0, 0, 0, 0, 0, 0, 16382, 0, 0, 0, 0, -16382, 0, 0, 0, 0, 16382, 0]
(-5242, 5241)
(-10484, 10484)
(-15727, 15726)
(-16383, 16382)
(-14286, 14285)
(-13106, 13105)
(-13106, 13105)
(-13106, 13105)
(-13106, 13105)
(-13106, 13105)
(-13106, 13105)
(-13106, 13105)
(-13106, 13105)
(-11009, 11008)
(-8912, 8911)
(-6815, 6814)
(-4718, 4717)
(-2621, 2620)
(-524, 523)
(0, 0)
(0, 0)
(0, 0)
(0, 0)
(0, 0)