Merge pull request #7933 from jepler/synthio-note

synthio: add 'Note' with arbitrary frequency and more
This commit is contained in:
Scott Shawcroft 2023-05-08 13:32:37 -07:00 committed by GitHub
commit 9e4dea7b15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1104 additions and 167 deletions

View File

@ -12,5 +12,6 @@ LONGINT_IMPL = MPZ
CIRCUITPY__EVE = 1
CIRCUITPY_CANIO = 1
CIRCUITPY_SYNTHIO = 0
CIRCUITPY_LTO_PARTITION = one

View File

@ -95,14 +95,16 @@ void port_internal_flash_flush(void) {
if (_cache_lba == NO_CACHE) {
return;
}
// Make sure we don't have an interrupt while we do flash operations.
common_hal_mcu_disable_interrupts();
flash_range_erase(CIRCUITPY_CIRCUITPY_DRIVE_START_ADDR + _cache_lba, SECTOR_SIZE);
flash_range_program(CIRCUITPY_CIRCUITPY_DRIVE_START_ADDR + _cache_lba, _cache, SECTOR_SIZE);
common_hal_mcu_enable_interrupts();
_cache_lba = NO_CACHE;
common_hal_mcu_enable_interrupts();
}
mp_uint_t supervisor_flash_read_blocks(uint8_t *dest, uint32_t block, uint32_t num_blocks) {
port_internal_flash_flush(); // we never read out of the cache, so we have to write it if dirty
memcpy(dest,
(void *)(XIP_BASE + CIRCUITPY_CIRCUITPY_DRIVE_START_ADDR + block * FILESYSTEM_BLOCK_SIZE),
num_blocks * FILESYSTEM_BLOCK_SIZE);
@ -118,6 +120,7 @@ mp_uint_t supervisor_flash_write_blocks(const uint8_t *src, uint32_t lba, uint32
uint8_t block_offset = block_address % blocks_per_sector;
if (_cache_lba != block_address) {
port_internal_flash_flush();
memcpy(_cache,
(void *)(XIP_BASE + CIRCUITPY_CIRCUITPY_DRIVE_START_ADDR + sector_offset),
SECTOR_SIZE);
@ -133,11 +136,6 @@ mp_uint_t supervisor_flash_write_blocks(const uint8_t *src, uint32_t lba, uint32
FILESYSTEM_BLOCK_SIZE);
block++;
}
// Make sure we don't have an interrupt while we do flash operations.
common_hal_mcu_disable_interrupts();
flash_range_erase(CIRCUITPY_CIRCUITPY_DRIVE_START_ADDR + sector_offset, SECTOR_SIZE);
flash_range_program(CIRCUITPY_CIRCUITPY_DRIVE_START_ADDR + sector_offset, _cache, SECTOR_SIZE);
common_hal_mcu_enable_interrupts();
}
return 0; // success

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

@ -194,11 +194,16 @@ mp_float_t mp_arg_validate_type_float(mp_obj_t obj, qstr arg_name) {
return a_float;
}
void mp_arg_validate_obj_float_range(mp_obj_t float_in, mp_int_t min, mp_int_t max, qstr arg_name) {
mp_float_t mp_arg_validate_obj_float_range(mp_obj_t float_in, mp_int_t min, mp_int_t max, qstr arg_name) {
const mp_float_t f = mp_arg_validate_type_float(float_in, arg_name);
return mp_arg_validate_float_range(f, min, max, arg_name);
}
mp_float_t mp_arg_validate_float_range(mp_float_t f, mp_int_t min, mp_int_t max, qstr arg_name) {
if (f < (mp_float_t)min || f > (mp_float_t)max) {
mp_raise_ValueError_varg(translate("%q must be %d-%d"), arg_name, min, max);
}
return f;
}
mp_float_t mp_arg_validate_obj_float_non_negative(mp_obj_t float_in, mp_float_t default_for_null, qstr arg_name) {

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

@ -103,7 +103,8 @@ mp_int_t mp_arg_validate_int_max(mp_int_t i, mp_int_t j, qstr arg_name);
mp_int_t mp_arg_validate_int_range(mp_int_t i, mp_int_t min, mp_int_t max, qstr arg_name);
#if MICROPY_PY_BUILTINS_FLOAT
mp_float_t mp_arg_validate_obj_float_non_negative(mp_obj_t float_in, mp_float_t default_for_null, qstr arg_name);
void mp_arg_validate_obj_float_range(mp_obj_t float_in, mp_int_t min, mp_int_t max, qstr arg_name);
mp_float_t mp_arg_validate_obj_float_range(mp_obj_t float_in, mp_int_t min, mp_int_t max, qstr arg_name);
mp_float_t mp_arg_validate_float_range(mp_float_t float_in, mp_int_t min, mp_int_t max, qstr arg_name);
mp_float_t mp_arg_validate_type_float(mp_obj_t obj, qstr arg_name);
#endif
mp_uint_t mp_arg_validate_length_min(mp_uint_t length, mp_uint_t min, qstr arg_name);

View File

@ -0,0 +1,263 @@
/*
* 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 | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(1) } },
{ MP_QSTR_tremolo_rate, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = NULL } },
{ MP_QSTR_tremolo_depth, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = NULL } },
{ MP_QSTR_vibrato_rate, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = NULL } },
{ MP_QSTR_vibrato_depth, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = NULL } },
{ MP_QSTR_waveform, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_NONE } },
{ MP_QSTR_envelope, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_NONE } },
};
//| 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
//|
//| A depth of 0 disables tremolo. A nonzero value enables tremolo,
//| with the maximum decrease in amplitude being equal to the tremolo
//| depth. A note with a tremolo depth of 1 will fade out to nothing, while
//| a tremolo depth of 0.1 will give a minimum amplitude of 0.9."""
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
//|
//| A depth of 0 disables vibrato. A depth of 1 corresponds to a vibrato of ±1
//| octave. A depth of (1/12) = 0.833 corresponds to a vibrato of ±1 semitone,
//| and a depth of .00833 corresponds to one musical cent.
//| """
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))
@ -78,8 +79,8 @@ static const mp_arg_t envelope_properties[] = {
//| :param float attack_time: The time in seconds it takes to ramp from 0 volume to attack_volume
//| :param float decay_time: The time in seconds it takes to ramp from attack_volume to sustain_volume
//| :param float release_time: The time in seconds it takes to ramp from sustain_volume to release_volume. When a note is released before it has reached the sustain phase, the release is done with the same slope indicated by ``release_time`` and ``sustain_level``
//| :param float 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
//| :param float attack_level: The level, in the range ``0.0`` to ``1.0`` of the peak volume of the attack phase
//| :param float sustain_level: The level, in the range ``0.0`` to ``1.0`` of the volume of the sustain phase relative to the attack level
//| """
//| attack_time: float
//| """The time in seconds it takes to ramp from 0 volume to attack_volume"""
@ -91,10 +92,10 @@ static const mp_arg_t envelope_properties[] = {
//| """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"""
//| """The 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"""
//| """The level, in the range ``0.0`` to ``1.0`` of the volume of the sustain phase relative to the attack level"""
//|
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 *all_args) {
@ -157,7 +158,6 @@ const mp_obj_namedtuple_type_t synthio_envelope_type_obj = {
},
};
//| def from_file(
//| file: typing.BinaryIO,
//| *,
@ -262,12 +262,38 @@ 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);
//| def midi_to_hz(midi_note: float) -> float:
//| """Converts the given midi note (60 = middle C, 69 = concert A) to Hz"""
//|
STATIC mp_obj_t midi_to_hz(mp_obj_t arg) {
mp_float_t note = mp_arg_validate_obj_float_range(arg, 1, 127, MP_QSTR_note);
return mp_obj_new_float(common_hal_synthio_midi_to_hz_float(note));
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_midi_to_hz_obj, midi_to_hz);
//| def onevo_to_hz(ctrl: float) -> float:
//| """Converts a 1v/octave signal to Hz.
//|
//| 60/12 (5.0) corresponds to middle C, 69/12 is concert A."""
//|
STATIC mp_obj_t onevo_to_hz(mp_obj_t arg) {
mp_float_t note = mp_arg_validate_obj_float_range(arg, 0, 11, MP_QSTR_ctrl);
return mp_obj_new_float(common_hal_synthio_onevo_to_hz_float(note));
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_onevo_to_hz_obj, onevo_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) },
{ MP_ROM_QSTR(MP_QSTR_midi_to_hz), MP_ROM_PTR(&synthio_midi_to_hz_obj) },
{ MP_ROM_QSTR(MP_QSTR_onevo_to_hz), MP_ROM_PTR(&synthio_midi_to_hz_obj) },
};
STATIC MP_DEFINE_CONST_DICT(synthio_module_globals, synthio_module_globals_table);

View File

@ -33,3 +33,5 @@ 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);
mp_float_t common_hal_synthio_midi_to_hz_float(mp_float_t note);
mp_float_t common_hal_synthio_onevo_to_hz_float(mp_float_t note);

View File

@ -37,5 +37,17 @@ void raise_deinited_error(void) {
mp_raise_ValueError(translate("Object has been deinitialized and can no longer be used. Create a new object."));
}
void properties_print_helper(const mp_print_t *print, mp_obj_t self_in, const mp_arg_t *properties, size_t n_properties) {
const mp_obj_type_t *type = mp_obj_get_type(self_in);
mp_printf(print, "%q(", type->name);
for (size_t i = 0; i < n_properties; i++) {
if (i > 0) {
mp_print_str(print, ", ");
}
mp_printf(print, "%q=", properties[i].qst);
mp_obj_print_helper(print, mp_load_attr(self_in, properties[i].qst), PRINT_REPR);
}
mp_print_str(print, ")");
}
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_UTIL_H

View File

@ -27,7 +27,11 @@
#ifndef MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_UTIL_H
#define MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_UTIL_H
#include "py/mpprint.h"
#include "py/runtime.h"
void raise_deinited_error(void);
void properties_print_helper(const mp_print_t *print, mp_obj_t self_in, const mp_arg_t *properties, size_t n_properties);
#endif // MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_UTIL_H

View File

@ -28,22 +28,22 @@
#include "shared-bindings/synthio/MidiTrack.h"
STATIC void print_midi_stream_error(synthio_miditrack_obj_t *self) {
STATIC void record_midi_stream_error(synthio_miditrack_obj_t *self) {
self->error_location = self->pos;
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) {
print_midi_stream_error(self);
record_midi_stream_error(self);
}
uint8_t note = buffer[(self->pos)++];
if (note > 127 || buffer[(self->pos)++] > 127) {
print_midi_stream_error(self);
record_midi_stream_error(self);
}
return note;
return MP_OBJ_NEW_SMALL_INT(note);
}
static int decode_duration(synthio_miditrack_obj_t *self) {
@ -60,7 +60,7 @@ static int decode_duration(synthio_miditrack_obj_t *self) {
// 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);
record_midi_stream_error(self);
}
return delta * self->synth.sample_rate / self->tempo;
}
@ -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;
}
@ -89,14 +89,14 @@ static void decode_until_pause(synthio_miditrack_obj_t *self) {
case 12:
case 13: // one data byte to ignore
if (self->pos >= len || buffer[self->pos++] > 127) {
print_midi_stream_error(self);
record_midi_stream_error(self);
}
break;
case 15: // the full syntax is too complicated, just assume it's "End of Track" event
self->pos = len;
break;
default: // invalid event
print_midi_stream_error(self);
record_midi_stream_error(self);
}
if (self->pos < len) {
self->synth.span.dur = decode_duration(self);
@ -158,6 +158,10 @@ void synthio_miditrack_reset_buffer(synthio_miditrack_obj_t *self,
audioio_get_buffer_result_t synthio_miditrack_get_buffer(synthio_miditrack_obj_t *self,
bool single_channel_output, uint8_t channel, uint8_t **buffer, uint32_t *buffer_length) {
if (common_hal_synthio_miditrack_deinited(self)) {
*buffer_length = 0;
return GET_BUFFER_ERROR;
}
synthio_synth_synthesize(&self->synth, buffer, buffer_length, single_channel_output ? 0 : channel);
if (self->synth.span.dur == 0) {

View File

@ -0,0 +1,185 @@
/*
* 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);
if (self->sample_rate != 0) {
synthio_envelope_definition_set(&self->envelope_def, envelope_in, self->sample_rate);
}
}
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);
}
uint32_t synthio_note_envelope(synthio_note_obj_t *self) {
return self->amplitude_scaled;
}
// Perform a pitch bend operation
//
// bend_value is in the range [0, 65535]. "no change" is 32768. The bend unit is 32768/octave.
//
// compare to (frequency_scaled * pow(2, (bend_value-32768)/32768))
// a 13-entry pitch table
#define BEND_SCALE (32768)
#define BEND_OFFSET (BEND_SCALE)
STATIC uint16_t pitch_bend_table[] = { 0, 1948, 4013, 6200, 8517, 10972, 13573, 16329, 19248, 22341, 25618, 29090, 32768 };
STATIC uint32_t pitch_bend(uint32_t frequency_scaled, uint16_t bend_value) {
bool down = (bend_value < 32768);
if (!down) {
bend_value -= 32768;
}
uint32_t bend_value_semitone = (uint32_t)bend_value * 24; // 65536/semitone
uint32_t semitone = bend_value_semitone >> 16;
uint32_t fractone = bend_value_semitone & 0xffff;
uint32_t f_lo = pitch_bend_table[semitone];
uint32_t f_hi = pitch_bend_table[semitone + 1]; // table has 13 entries, indexing with semitone=12 is OK
uint32_t f = ((f_lo * (65535 - fractone) + f_hi * fractone) >> 16) + BEND_OFFSET;
return (frequency_scaled * (uint64_t)f) >> (15 + down);
}
uint32_t synthio_note_step(synthio_note_obj_t *self, int32_t sample_rate, int16_t dur, uint16_t *loudness) {
int tremolo_value = synthio_lfo_step(&self->tremolo_state, dur);
int vibrato_value = synthio_lfo_step(&self->vibrato_state, dur);
*loudness = (*loudness * tremolo_value) >> 15;
uint32_t frequency_scaled = pitch_bend(self->frequency_scaled, vibrato_value);
return frequency_scaled;
}

View File

@ -0,0 +1,53 @@
/*
* 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 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"
@ -60,6 +62,10 @@ void synthio_synthesizer_reset_buffer(synthio_synthesizer_obj_t *self,
audioio_get_buffer_result_t synthio_synthesizer_get_buffer(synthio_synthesizer_obj_t *self,
bool single_channel_output, uint8_t channel, uint8_t **buffer, uint32_t *buffer_length) {
if (common_hal_synthio_synthesizer_deinited(self)) {
*buffer_length = 0;
return GET_BUFFER_ERROR;
}
self->synth.span.dur = SYNTHIO_MAX_DUR;
synthio_synth_synthesize(&self->synth, buffer, buffer_length, single_channel_output ? channel : 0);
return GET_BUFFER_MORE_DATA;
@ -72,40 +78,57 @@ 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 (!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);
}
synthio_span_change_note(&self->synth, SYNTHIO_SILENCE, validate_note(note_obj));
}
}
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,17 +37,33 @@ 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_float_t arg) {
return common_hal_synthio_onevo_to_hz_float(arg / 12.);
}
mp_float_t common_hal_synthio_onevo_to_hz_float(mp_float_t octave) {
return notes[0] * MICROPY_FLOAT_C_FUN(pow)(2., octave - 10);
}
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;
return 32767;
}
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) {
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;
@ -62,7 +79,7 @@ STATIC void synthio_envelope_definition_set(synthio_envelope_definition_t *envel
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->sustain_level = (int)(32767 * mp_obj_get_float(fields[4]) * mp_obj_get_float(fields[3]));
envelope->attack_step = convert_time_to_rate(
sample_rate, fields[0], envelope->attack_level);
@ -87,34 +104,19 @@ STATIC void synthio_envelope_state_step(synthio_envelope_state_t *state, synthio
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;
state->level = MIN(state->level + def->attack_step, def->attack_level);
if (state->level == def->attack_level) {
state->state = SYNTHIO_ENVELOPE_STATE_DECAY;
}
break;
case SYNTHIO_ENVELOPE_STATE_DECAY:
state->level = MAX(state->level + def->decay_step, def->sustain_level);
if (state->level == def->sustain_level) {
state->state = SYNTHIO_ENVELOPE_STATE_SUSTAIN;
}
break;
case SYNTHIO_ENVELOPE_STATE_RELEASE:
state->level = MAX(state->level + def->release_step, 0);
}
}
}
@ -131,18 +133,44 @@ STATIC void synthio_envelope_state_release(synthio_envelope_state_t *state, synt
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;
STATIC synthio_envelope_definition_t *synthio_synth_get_note_envelope(synthio_synth_t *synth, mp_obj_t note_obj) {
synthio_envelope_definition_t *def = &synth->global_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;
}
}
return result;
return def;
}
#define RANGE_LOW (-28000)
#define RANGE_HIGH (28000)
#define RANGE_SHIFT (16)
#define RANGE_SCALE (0xfffffff / (32768 * CIRCUITPY_SYNTHIO_MAX_CHANNELS - RANGE_HIGH))
// dynamic range compression via a downward compressor with hard knee
//
// When the output value is within the range +-28000 (about 85% of full scale),
// it is unchanged. Otherwise, it undergoes a gain reduction so that the
// largest possible values, (+32768,-32767) * CIRCUITPY_SYNTHIO_MAX_CHANNELS,
// still fit within the output range
//
// This produces a much louder overall volume with multiple voices, without
// much additional processing.
//
// https://en.wikipedia.org/wiki/Dynamic_range_compression
STATIC
int16_t mix_down_sample(int32_t sample) {
if (sample < RANGE_LOW) {
sample = (((sample - RANGE_LOW) * RANGE_SCALE) >> RANGE_SHIFT) + RANGE_LOW;
} else if (sample > RANGE_HIGH) {
sample = (((sample - RANGE_HIGH) * RANGE_SCALE) >> RANGE_SHIFT) + RANGE_HIGH;
}
return sample;
}
void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t *buffer_length, uint8_t channel) {
if (channel == synth->other_channel) {
@ -154,60 +182,96 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
synth->buffer_index = !synth->buffer_index;
synth->other_channel = 1 - channel;
synth->other_buffer_index = synth->buffer_index;
int16_t *out_buffer = (int16_t *)(void *)synth->buffers[synth->buffer_index];
uint16_t dur = MIN(SYNTHIO_MAX_DUR, synth->span.dur);
synth->span.dur -= dur;
memset(out_buffer, 0, synth->buffer_length);
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) {
synth->accum[chan] = 0;
continue;
}
// adjust loudness by envelope
uint16_t loudness = (ovl_loudness * synth->envelope_state[chan].level) >> 16;
int32_t out_buffer32[dur];
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];
#define SHIFT (16)
memset(out_buffer32, 0, sizeof(out_buffer32));
for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
mp_obj_t note_obj = synth->span.note_obj[chan];
if (note_obj == SYNTHIO_SILENCE) {
synth->accum[chan] = 0;
continue;
}
if (synth->envelope_state[chan].level == 0) {
// note is truly finished, but we only just noticed
synth->span.note_obj[chan] = SYNTHIO_SILENCE;
continue;
}
// adjust loudness by envelope
uint16_t loudness = synth->envelope_state[chan].level;
uint32_t dds_rate;
const int16_t *waveform = synth->waveform;
uint32_t waveform_length = synth->waveform_length;
if (mp_obj_is_small_int(note_obj)) {
uint8_t note = mp_obj_get_int(note_obj);
uint8_t octave = note / 12;
uint16_t base_freq = notes[note % 12];
// rate = base_freq * waveform_length
// den = sample_rate * 2 ^ (10 - octave)
// den = sample_rate * 2 ^ 10 / 2^octave
// dds_rate = 2^SHIFT * rate / den
// dds_rate = 2^(SHIFT-10+octave) * base_freq * waveform_length / sample_rate
uint32_t dds_rate = (sample_rate / 2 + ((uint64_t)(base_freq * waveform_length) << (SHIFT - 10 + octave))) / sample_rate;
for (uint16_t i = 0; i < dur; i++) {
accum += dds_rate;
if (accum > waveform_length << SHIFT) {
accum -= waveform_length << SHIFT;
}
int16_t idx = accum >> SHIFT;
out_buffer[i] += (waveform[idx] * loudness) / 65536;
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;
}
synth->accum[chan] = accum;
dds_rate = synthio_frequency_convert_scaled_to_dds((uint64_t)frequency_scaled * waveform_length, sample_rate);
}
uint32_t accum = synth->accum[chan];
uint32_t lim = waveform_length << SYNTHIO_FREQUENCY_SHIFT;
if (dds_rate > lim / 2) {
// beyond nyquist, can't play note
continue;
}
// can happen if note waveform gets set mid-note, but the expensive modulo is usually avoided
if (accum > lim) {
accum %= lim;
}
for (uint16_t i = 0; i < dur; i++) {
accum += dds_rate;
// because dds_rate is low enough, the subtraction is guaranteed to go back into range, no expensive modulo needed
if (accum > lim) {
accum -= lim;
}
int16_t idx = accum >> SYNTHIO_FREQUENCY_SHIFT;
out_buffer32[i] += (waveform[idx] * loudness) / 65536;
}
synth->accum[chan] = accum;
}
int16_t *out_buffer16 = (int16_t *)(void *)synth->buffers[synth->buffer_index];
// mix down audio
for (size_t i = 0; i < dur; i++) {
int32_t sample = out_buffer32[i];
out_buffer16[i] = mix_down_sample(sample);
}
// 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_state_step(&synth->envelope_state[chan], synthio_synth_get_note_envelope(synth, note_obj), dur);
}
*buffer_length = synth->last_buffer_length = dur * SYNTHIO_BYTES_PER_SAMPLE;
*bufptr = (uint8_t *)out_buffer;
*bufptr = (uint8_t *)out_buffer16;
}
void synthio_synth_reset_buffer(synthio_synth_t *synth, bool single_channel_output, uint8_t channel) {
@ -229,7 +293,7 @@ void synthio_synth_deinit(synthio_synth_t *synth) {
}
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);
synthio_envelope_definition_set(&synth->global_envelope_definition, envelope_obj, synth->sample_rate);
synth->envelope_obj = envelope_obj;
}
@ -248,7 +312,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;
}
}
@ -260,7 +324,7 @@ 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) {
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') {
@ -277,41 +341,82 @@ 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;
}
}
int result = -1;
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;
// replace the releasing note with lowest volume level
int level = 32768;
for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
if (!SYNTHIO_NOTE_IS_PLAYING(synth, chan)) {
synthio_envelope_state_t *state = &synth->envelope_state[chan];
if (state->level < level) {
result = chan;
level = state->level;
}
}
}
}
return -1;
return result;
}
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
synthio_envelope_state_init(&synth->envelope_state[channel], &synth->envelope_definition);
synth->accum[channel] = 0;
// note already playing, re-enter attack phase
synth->envelope_state[channel].state = SYNTHIO_ENVELOPE_STATE_ATTACK;
return true;
}
channel = find_channel_with_note(synth, old_note);
if (channel != -1) {
if (new_note == SYNTHIO_SILENCE) {
synthio_envelope_state_release(&synth->envelope_state[channel], &synth->envelope_definition);
synthio_envelope_state_release(&synth->envelope_state[channel], synthio_synth_get_note_envelope(synth, old_note));
} else {
synth->span.note[channel] = new_note;
synthio_envelope_state_init(&synth->envelope_state[channel], &synth->envelope_definition);
synth->span.note_obj[channel] = new_note;
synthio_envelope_state_init(&synth->envelope_state[channel], synthio_synth_get_note_envelope(synth, new_note));
synth->accum[channel] = 0;
}
return true;
}
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 {
@ -60,19 +62,29 @@ typedef struct {
typedef struct synthio_synth {
uint32_t sample_rate;
uint32_t total_envelope;
int16_t *buffers[2];
const int16_t *waveform;
uint16_t buffer_length;
uint16_t last_buffer_length;
uint8_t other_channel, buffer_index, other_buffer_index;
uint16_t waveform_length;
synthio_envelope_definition_t envelope_definition;
synthio_envelope_definition_t global_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;
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 +96,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,119 @@
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 synthesize5(synth):
notes = [
synthio.Note(
frequency=synthio.midi_to_hz(60 + i + o),
waveform=sine,
envelope=envelope,
)
for i in [0, 4, 7]
for o in [0, -12, 12]
]
for n in notes:
print(n)
synth.press((n,))
yield 120
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(synthesize5(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

@ -1,4 +1,4 @@
(0, 1, 512, 1)
1 [-16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 16382, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16382, 16382]
1 [-16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383]
(0, 1, 512, 1)
1 [0, 0, 0, 0, 0, 0, 16382, 16382, 16382, 16382, 16382, 16382, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16382, 16382, 16382, 16382, 16382, 16382, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16382, 16382, 16382, 16382, 16382, 16382, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16382, 16382, 16382, 16382, 16382, 16382, 0, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16382, 16382, 16382, 16382, 16382, 16382, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16382, 16382, 16382, 16382, 16382, 16382, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16382, 16382, 16382, 16382, 16382, 16382, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16382, 16382, 16382, 16382, 16382, 16382, 16382, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16382, 16382, 16382, 16382, 16382, 16382, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16382, 16382, 16382, 16382, 16382, 16382, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16382, 16382, 16382, 16382, 16382, 16382, 0, 0]
1 [0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0]

View File

@ -1,30 +1,30 @@
()
[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,)
[-16383, -16383, -16383, -16383, 16382, 16382, 16382, 16382, 16382, -16383, -16383, -16383, -16383, -16383, 16382, 16382, 16382, 16382, 16382, -16383, -16383, -16383, -16383, -16383]
[-16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383]
(80, 91)
[0, 0, 16382, 16382, 0, -16382, -16382, 0, 16382, 16382, 0, 0, 16382, 0, 0, -16382, -16382, 0, 16382, 16382, 0, 0, 16382, 0]
[0, 0, 28045, 28045, 0, -28046, -28046, 0, 28045, 28045, 0, 0, 28045, 0, 0, -28046, -28046, 0, 28045, 28045, 0, 0, 28045, 0]
(91,)
[-16382, 0, 0, 16382, 0, 0, 16382, 16382, 0, -16382, -16382, 0, 16382, 16382, 0, 0, 16382, 0, 0, -16382, -16382, -16382, 16382, 16382]
(-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)
[-28046, 0, 0, 28045, 0, 0, 28045, 28045, 0, -28046, -28046, 0, 28045, 28045, 0, 0, 28045, 0, 0, -28046, -28046, -28046, 28045, 28045]
(-5242, 5242)
(-10485, 10484)
(-15727, 15727)
(-16383, 16383)
(-14286, 14286)
(-13106, 13106)
(-13106, 13106)
(-13106, 13106)
(-13106, 13106)
(-13106, 13106)
(-13106, 13106)
(-13106, 13106)
(-13106, 13106)
(-11009, 11009)
(-8912, 8912)
(-6815, 6815)
(-4718, 4718)
(-2621, 2621)
(-524, 524)
(0, 0)
(0, 0)
(0, 0)

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.6076004423605, 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, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383]
(Note(frequency=830.6076004423605, amplitude=1.0, tremolo_rate=0.0, tremolo_depth=0.0, vibrato_rate=0.0, vibrato_depth=0.0, waveform=None, envelope=None), Note(frequency=830.6076004423605, amplitude=1.0, tremolo_rate=0.0, tremolo_depth=0.0, vibrato_rate=0.0, vibrato_depth=0.0, waveform=None, envelope=None))
[0, 0, 0, 0, 0, 0, 0, 0, 28045, 0, 0, 0, 0, -28046, 0, 0, 0, 0, 28045, 0, 0, 0, 0, -28046]
(Note(frequency=830.6076004423605, amplitude=1.0, tremolo_rate=0.0, tremolo_depth=0.0, vibrato_rate=0.0, vibrato_depth=0.0, waveform=None, envelope=None),)
[0, 0, 0, 28045, 0, 0, 0, 0, 0, 0, 0, 0, 28045, 0, 0, 0, 0, -28046, 0, 0, 0, 0, 28045, 0]
(-5242, 5242)
(-10485, 10484)
(-15727, 15727)
(-16383, 16383)
(-14286, 14286)
(-13106, 13106)
(-13106, 13106)
(-13106, 13106)
(-13106, 13106)
(-13106, 13106)
(-13106, 13106)
(-13106, 13106)
(-13106, 13106)
(-11009, 11009)
(-8912, 8912)
(-6815, 6815)
(-4718, 4718)
(-2621, 2621)
(-524, 524)
(0, 0)
(0, 0)
(0, 0)
(0, 0)
(0, 0)