Merge pull request #7933 from jepler/synthio-note
synthio: add 'Note' with arbitrary frequency and more
This commit is contained in:
commit
9e4dea7b15
@ -12,5 +12,6 @@ LONGINT_IMPL = MPZ
|
||||
|
||||
CIRCUITPY__EVE = 1
|
||||
CIRCUITPY_CANIO = 1
|
||||
CIRCUITPY_SYNTHIO = 0
|
||||
|
||||
CIRCUITPY_LTO_PARTITION = one
|
||||
|
@ -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
|
||||
|
@ -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 \
|
||||
|
@ -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) {
|
||||
|
@ -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 \
|
||||
|
@ -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);
|
||||
|
263
shared-bindings/synthio/Note.c
Normal file
263
shared-bindings/synthio/Note.c
Normal 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,
|
||||
};
|
30
shared-bindings/synthio/Note.h
Normal file
30
shared-bindings/synthio/Note.h
Normal 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);
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
185
shared-module/synthio/Note.c
Normal file
185
shared-module/synthio/Note.c
Normal 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;
|
||||
}
|
53
shared-module/synthio/Note.h
Normal file
53
shared-module/synthio/Note.h
Normal 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);
|
@ -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);
|
||||
|
@ -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 = ¬e->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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
5
tests/circuitpython-manual/synthio/note/README.md
Normal file
5
tests/circuitpython-manual/synthio/note/README.md
Normal 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.
|
1
tests/circuitpython-manual/synthio/note/audioop.py
Symbolic link
1
tests/circuitpython-manual/synthio/note/audioop.py
Symbolic link
@ -0,0 +1 @@
|
||||
../wave/audioop.py
|
1
tests/circuitpython-manual/synthio/note/chunk.py
Symbolic link
1
tests/circuitpython-manual/synthio/note/chunk.py
Symbolic link
@ -0,0 +1 @@
|
||||
../wave/chunk.py
|
119
tests/circuitpython-manual/synthio/note/code.py
Normal file
119
tests/circuitpython-manual/synthio/note/code.py
Normal 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)
|
1
tests/circuitpython-manual/synthio/note/wave.py
Symbolic link
1
tests/circuitpython-manual/synthio/note/wave.py
Symbolic link
@ -0,0 +1 @@
|
||||
../wave/wave.py
|
@ -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.
|
||||
|
@ -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]
|
||||
|
@ -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)
|
||||
|
41
tests/circuitpython/synthesizer_note.py
Normal file
41
tests/circuitpython/synthesizer_note.py
Normal 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)))
|
32
tests/circuitpython/synthesizer_note.py.exp
Normal file
32
tests/circuitpython/synthesizer_note.py.exp
Normal 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)
|
Loading…
Reference in New Issue
Block a user