Merge pull request #8048 from jepler/synthio-biquad

Synthio: switch to per-note biquad filtering
This commit is contained in:
Jeff Epler 2023-06-07 14:48:31 -05:00 committed by GitHub
commit c408193ffe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 5648 additions and 5353 deletions

View File

@ -45,6 +45,7 @@ SRC_BITMAP := \
shared-bindings/synthio/MidiTrack.c \
shared-bindings/synthio/LFO.c \
shared-bindings/synthio/Note.c \
shared-bindings/synthio/Biquad.c \
shared-bindings/synthio/Synthesizer.c \
shared-bindings/traceback/__init__.c \
shared-bindings/util.c \
@ -70,6 +71,7 @@ SRC_BITMAP := \
shared-module/synthio/MidiTrack.c \
shared-module/synthio/LFO.c \
shared-module/synthio/Note.c \
shared-module/synthio/Biquad.c \
shared-module/synthio/Synthesizer.c \
shared-module/traceback/__init__.c \
shared-module/zlib/__init__.c \

View File

@ -650,6 +650,7 @@ SRC_SHARED_MODULE_ALL = \
struct/__init__.c \
supervisor/__init__.c \
supervisor/StatusBar.c \
synthio/Biquad.c \
synthio/LFO.c \
synthio/Math.c \
synthio/MidiTrack.c \

View File

@ -0,0 +1,110 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2021 Artyom Skrobov
*
* 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 <string.h>
#include "py/enum.h"
#include "py/mperrno.h"
#include "py/obj.h"
#include "py/objnamedtuple.h"
#include "py/runtime.h"
#include "shared-bindings/synthio/__init__.h"
#include "shared-bindings/synthio/LFO.h"
#include "shared-bindings/synthio/Math.h"
#include "shared-bindings/synthio/MidiTrack.h"
#include "shared-bindings/synthio/Note.h"
#include "shared-bindings/synthio/Synthesizer.h"
#include "shared-module/synthio/LFO.h"
#define default_attack_time (MICROPY_FLOAT_CONST(0.1))
#define default_decay_time (MICROPY_FLOAT_CONST(0.05))
#define default_release_time (MICROPY_FLOAT_CONST(0.2))
#define default_attack_level (MICROPY_FLOAT_CONST(1.))
#define default_sustain_level (MICROPY_FLOAT_CONST(0.8))
static const mp_arg_t biquad_properties[] = {
{ MP_QSTR_a1, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_ROM_NONE} },
{ MP_QSTR_a2, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_ROM_NONE} },
{ MP_QSTR_b0, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_ROM_NONE} },
{ MP_QSTR_b1, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_ROM_NONE} },
{ MP_QSTR_b2, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_ROM_NONE} },
};
//| class Biquad:
//| def __init__(self, b0: float, b1: float, b2: float, a1: float, a2: float) -> None:
//| """Construct a normalized biquad filter object.
//|
//| This implements the "direct form 1" biquad filter, where each coefficient
//| has been pre-divided by a0.
//|
//| Biquad objects are usually constructed via one of the related methods on a `Synthesizer` object
//| rather than directly from coefficients.
//|
//| https://github.com/WebAudio/Audio-EQ-Cookbook/blob/main/Audio-EQ-Cookbook.txt
//| """
//|
STATIC mp_obj_t synthio_biquad_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
mp_arg_val_t args[MP_ARRAY_SIZE(biquad_properties)];
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(biquad_properties), biquad_properties, args);
for (size_t i = 0; i < MP_ARRAY_SIZE(biquad_properties); i++) {
args[i].u_obj = mp_obj_new_float(mp_arg_validate_type_float(args[i].u_obj, biquad_properties[i].qst));
}
MP_STATIC_ASSERT(sizeof(mp_arg_val_t) == sizeof(mp_obj_t));
return namedtuple_make_new(type_in, MP_ARRAY_SIZE(args), 0, &args[0].u_obj);
}
const mp_obj_namedtuple_type_t synthio_biquad_type_obj = {
.base = {
.base = {
.type = &mp_type_type
},
.flags = MP_TYPE_FLAG_EXTENDED,
.name = MP_QSTR_Biquad,
.print = namedtuple_print,
.parent = &mp_type_tuple,
.make_new = synthio_biquad_make_new,
.attr = namedtuple_attr,
MP_TYPE_EXTENDED_FIELDS(
.unary_op = mp_obj_tuple_unary_op,
.binary_op = mp_obj_tuple_binary_op,
.subscr = mp_obj_tuple_subscr,
.getiter = mp_obj_tuple_getiter,
),
},
.n_fields = 5,
.fields = {
MP_QSTR_a1,
MP_QSTR_a2,
MP_QSTR_b0,
MP_QSTR_b1,
MP_QSTR_b2,
},
};

View File

@ -0,0 +1,9 @@
#pragma once
#include "py/obj.h"
#include "py/objnamedtuple.h"
extern const mp_obj_namedtuple_type_t synthio_biquad_type_obj;
mp_obj_t common_hal_synthio_new_lpf(mp_float_t w0, mp_float_t Q);
mp_obj_t common_hal_synthio_new_hpf(mp_float_t w0, mp_float_t Q);
mp_obj_t common_hal_synthio_new_bpf(mp_float_t w0, mp_float_t Q);

View File

@ -41,7 +41,7 @@ static const mp_arg_t note_properties[] = {
{ MP_QSTR_bend, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(0) } },
{ 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 } },
{ MP_QSTR_filter, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(1) } },
{ MP_QSTR_filter, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_NONE } },
{ MP_QSTR_ring_frequency, MP_ARG_OBJ, {.u_obj = MP_ROM_INT(0) } },
{ MP_QSTR_ring_bend, MP_ARG_OBJ, {.u_obj = MP_ROM_INT(0) } },
{ MP_QSTR_ring_waveform, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_NONE } },
@ -56,6 +56,7 @@ static const mp_arg_t note_properties[] = {
//| envelope: Optional[Envelope] = None,
//| amplitude: BlockInput = 0.0,
//| bend: BlockInput = 0.0,
//| filter: Optional[Biquad] = None,
//| ring_frequency: float = 0.0,
//| ring_bend: float = 0.0,
//| ring_waveform: Optional[ReadableBuffer] = 0.0,
@ -97,17 +98,21 @@ MP_PROPERTY_GETSET(synthio_note_frequency_obj,
(mp_obj_t)&synthio_note_get_frequency_obj,
(mp_obj_t)&synthio_note_set_frequency_obj);
//| filter: bool
//| """True if the note should be processed via the synthesizer's FIR filter."""
//| filter: Optional[Biquad]
//| """If not None, the output of this Note is filtered according to the provided coefficients.
//|
//| Construct an appropriate filter by calling a filter-making method on the
//| `Synthesizer` object where you plan to play the note, as filter coefficients depend
//| on the sample rate"""
STATIC mp_obj_t synthio_note_get_filter(mp_obj_t self_in) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
return mp_obj_new_bool(common_hal_synthio_note_get_filter(self));
return common_hal_synthio_note_get_filter_obj(self);
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_filter_obj, synthio_note_get_filter);
STATIC mp_obj_t synthio_note_set_filter(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_filter(self, mp_obj_is_true(arg));
common_hal_synthio_note_set_filter(self, arg);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_filter_obj, synthio_note_set_filter);

View File

@ -9,8 +9,8 @@ typedef enum synthio_bend_mode_e synthio_bend_mode_t;
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);
bool common_hal_synthio_note_get_filter(synthio_note_obj_t *self);
void common_hal_synthio_note_set_filter(synthio_note_obj_t *self, bool value);
mp_obj_t common_hal_synthio_note_get_filter_obj(synthio_note_obj_t *self);
void common_hal_synthio_note_set_filter(synthio_note_obj_t *self, mp_obj_t biquad);
mp_obj_t common_hal_synthio_note_get_panning(synthio_note_obj_t *self);
void common_hal_synthio_note_set_panning(synthio_note_obj_t *self, mp_obj_t value);

View File

@ -32,6 +32,7 @@
#include "py/objproperty.h"
#include "py/runtime.h"
#include "shared-bindings/util.h"
#include "shared-bindings/synthio/Biquad.h"
#include "shared-bindings/synthio/Synthesizer.h"
#include "shared-bindings/synthio/LFO.h"
#include "shared-bindings/synthio/__init__.h"
@ -52,7 +53,6 @@
//| channel_count: int = 1,
//| waveform: Optional[ReadableBuffer] = None,
//| envelope: Optional[Envelope] = None,
//| filter: Optional[ReadableBuffer] = None,
//| ) -> None:
//| """Create a synthesizer object.
//|
@ -65,17 +65,15 @@
//| :param int sample_rate: The desired playback sample rate; higher sample rate requires more memory
//| :param int channel_count: The number of output channels (1=mono, 2=stereo)
//| :param ReadableBuffer waveform: A single-cycle waveform. Default is a 50% duty cycle square wave. If specified, must be a ReadableBuffer of type 'h' (signed 16 bit)
//| :param ReadableBuffer filter: Coefficients of an FIR filter to apply to notes with ``filter=True``. If specified, must be a ReadableBuffer of type 'h' (signed 16 bit)
//| :param Optional[Envelope] envelope: An object that defines the loudness of a note over time. The default envelope, `None` provides no ramping, voices turn instantly on and off.
//| """
STATIC mp_obj_t synthio_synthesizer_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
enum { ARG_sample_rate, ARG_channel_count, ARG_waveform, ARG_envelope, ARG_filter };
enum { ARG_sample_rate, ARG_channel_count, ARG_waveform, ARG_envelope };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 11025} },
{ MP_QSTR_channel_count, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 1} },
{ MP_QSTR_waveform, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none } },
{ MP_QSTR_envelope, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none } },
{ MP_QSTR_filter, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none } },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
@ -87,7 +85,6 @@ STATIC mp_obj_t synthio_synthesizer_make_new(const mp_obj_type_t *type, size_t n
args[ARG_sample_rate].u_int,
args[ARG_channel_count].u_int,
args[ARG_waveform].u_obj,
args[ARG_filter].u_obj,
args[ARG_envelope].u_obj);
return MP_OBJ_FROM_PTR(self);
@ -292,6 +289,114 @@ MP_PROPERTY_GETTER(synthio_synthesizer_blocks_obj,
//| """Maximum polyphony of the synthesizer (read-only class property)"""
//|
//| def low_pass_filter(cls, frequency: float, q_factor: float = 0.7071067811865475) -> Biquad:
//| """Construct a low-pass filter with the given parameters.
//|
//| ``frequency``, called f0 in the cookbook, is the corner frequency in Hz
//| of the filter.
//|
//| ``q_factor``, called ``Q`` in the cookbook. Controls how peaked the response will be at the cutoff frequency. A large value makes the response more peaked.
//| """
enum passfilter_arg_e { ARG_f0, ARG_Q };
// M_PI is not part of the math.h standard and may not be defined
// And by defining our own we can ensure it uses the correct const format.
#define MP_PI MICROPY_FLOAT_CONST(3.14159265358979323846)
static const mp_arg_t passfilter_properties[] = {
{ MP_QSTR_frequency, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_ROM_NONE} },
{ MP_QSTR_Q, MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL } },
};
STATIC mp_obj_t synthio_synthesizer_lpf(size_t n_pos, const mp_obj_t *pos_args, mp_map_t *kw_args) {
mp_arg_val_t args[MP_ARRAY_SIZE(passfilter_properties)];
mp_obj_t self_in = pos_args[0];
synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in);
mp_arg_parse_all(n_pos - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(passfilter_properties), passfilter_properties, args);
mp_float_t f0 = mp_arg_validate_type_float(args[ARG_f0].u_obj, MP_QSTR_f0);
mp_float_t Q =
args[ARG_Q].u_obj == MP_OBJ_NULL ? MICROPY_FLOAT_CONST(0.7071067811865475) :
mp_arg_validate_type_float(args[ARG_Q].u_obj, MP_QSTR_Q);
mp_float_t w0 = f0 / self->synth.sample_rate * 2 * MP_PI;
return common_hal_synthio_new_lpf(w0, Q);
}
MP_DEFINE_CONST_FUN_OBJ_KW(synthio_synthesizer_lpf_fun_obj, 1, synthio_synthesizer_lpf);
//| def high_pass_filter(
//| cls, frequency: float, q_factor: float = 0.7071067811865475
//| ) -> Biquad:
//| """Construct a high-pass filter with the given parameters.
//|
//| ``frequency``, called f0 in the cookbook, is the corner frequency in Hz
//| of the filter.
//|
//| ``q_factor``, called ``Q`` in the cookbook. Controls how peaked the response will be at the cutoff frequency. A large value makes the response more peaked.
//| """
STATIC mp_obj_t synthio_synthesizer_hpf(size_t n_pos, const mp_obj_t *pos_args, mp_map_t *kw_args) {
mp_arg_val_t args[MP_ARRAY_SIZE(passfilter_properties)];
mp_obj_t self_in = pos_args[0];
synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in);
mp_arg_parse_all(n_pos - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(passfilter_properties), passfilter_properties, args);
mp_float_t f0 = mp_arg_validate_type_float(args[ARG_f0].u_obj, MP_QSTR_f0);
mp_float_t Q =
args[ARG_Q].u_obj == MP_OBJ_NULL ? MICROPY_FLOAT_CONST(0.7071067811865475) :
mp_arg_validate_type_float(args[ARG_Q].u_obj, MP_QSTR_Q);
mp_float_t w0 = f0 / self->synth.sample_rate * 2 * MP_PI;
return common_hal_synthio_new_hpf(w0, Q);
}
//| def band_pass_filter(
//| cls, frequency: float, q_factor: float = 0.7071067811865475
//| ) -> Biquad:
//| """Construct a band-pass filter with the given parameters.
//|
//| ``frequency``, called f0 in the cookbook, is the center frequency in Hz
//| of the filter.
//|
//| ``q_factor``, called ``Q`` in the cookbook. Controls how peaked the response will be at the cutoff frequency. A large value makes the response more peaked.
//|
//| The coefficients are scaled such that the filter has a 0dB peak gain.
//| """
//|
MP_DEFINE_CONST_FUN_OBJ_KW(synthio_synthesizer_hpf_fun_obj, 1, synthio_synthesizer_hpf);
STATIC mp_obj_t synthio_synthesizer_bpf(size_t n_pos, const mp_obj_t *pos_args, mp_map_t *kw_args) {
mp_arg_val_t args[MP_ARRAY_SIZE(passfilter_properties)];
mp_obj_t self_in = pos_args[0];
synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in);
mp_arg_parse_all(n_pos - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(passfilter_properties), passfilter_properties, args);
mp_float_t f0 = mp_arg_validate_type_float(args[ARG_f0].u_obj, MP_QSTR_f0);
mp_float_t Q =
args[ARG_Q].u_obj == MP_OBJ_NULL ? MICROPY_FLOAT_CONST(0.7071067811865475) :
mp_arg_validate_type_float(args[ARG_Q].u_obj, MP_QSTR_Q);
mp_float_t w0 = f0 / self->synth.sample_rate * 2 * MP_PI;
return common_hal_synthio_new_bpf(w0, Q);
}
MP_DEFINE_CONST_FUN_OBJ_KW(synthio_synthesizer_bpf_fun_obj, 1, synthio_synthesizer_bpf);
STATIC const mp_rom_map_elem_t synthio_synthesizer_locals_dict_table[] = {
// Methods
{ MP_ROM_QSTR(MP_QSTR_press), MP_ROM_PTR(&synthio_synthesizer_press_obj) },
@ -304,6 +409,9 @@ STATIC const mp_rom_map_elem_t synthio_synthesizer_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) },
{ MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&synthio_synthesizer___exit___obj) },
{ MP_ROM_QSTR(MP_QSTR_low_pass_filter), MP_ROM_PTR(&synthio_synthesizer_lpf_fun_obj) },
{ MP_ROM_QSTR(MP_QSTR_high_pass_filter), MP_ROM_PTR(&synthio_synthesizer_hpf_fun_obj) },
{ MP_ROM_QSTR(MP_QSTR_band_pass_filter), MP_ROM_PTR(&synthio_synthesizer_bpf_fun_obj) },
// Properties
{ MP_ROM_QSTR(MP_QSTR_envelope), MP_ROM_PTR(&synthio_synthesizer_envelope_obj) },
{ MP_ROM_QSTR(MP_QSTR_sample_rate), MP_ROM_PTR(&synthio_synthesizer_sample_rate_obj) },

View File

@ -32,7 +32,7 @@
extern const mp_obj_type_t synthio_synthesizer_type;
void common_hal_synthio_synthesizer_construct(synthio_synthesizer_obj_t *self,
uint32_t sample_rate, int channel_count, mp_obj_t waveform_obj, mp_obj_t filter_obj,
uint32_t sample_rate, int channel_count, mp_obj_t waveform_obj,
mp_obj_t envelope_obj);
void common_hal_synthio_synthesizer_deinit(synthio_synthesizer_obj_t *self);
bool common_hal_synthio_synthesizer_deinited(synthio_synthesizer_obj_t *self);

View File

@ -36,6 +36,7 @@
#include "extmod/vfs_posix.h"
#include "shared-bindings/synthio/__init__.h"
#include "shared-bindings/synthio/Biquad.h"
#include "shared-bindings/synthio/LFO.h"
#include "shared-bindings/synthio/Math.h"
#include "shared-bindings/synthio/MidiTrack.h"
@ -310,6 +311,7 @@ MP_DEFINE_CONST_FUN_OBJ_VAR(synthio_lfo_tick_obj, 1, synthio_lfo_tick);
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_Biquad), MP_ROM_PTR(&synthio_biquad_type_obj) },
{ MP_ROM_QSTR(MP_QSTR_Math), MP_ROM_PTR(&synthio_math_type) },
{ MP_ROM_QSTR(MP_QSTR_MathOperation), MP_ROM_PTR(&synthio_math_operation_type) },
{ MP_ROM_QSTR(MP_QSTR_MidiTrack), MP_ROM_PTR(&synthio_miditrack_type) },

View File

@ -0,0 +1,143 @@
/*
* 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 "shared-bindings/synthio/Biquad.h"
#include "shared-module/synthio/Biquad.h"
mp_obj_t common_hal_synthio_new_lpf(mp_float_t w0, mp_float_t Q) {
mp_float_t s = MICROPY_FLOAT_C_FUN(sin)(w0);
mp_float_t c = MICROPY_FLOAT_C_FUN(cos)(w0);
mp_float_t alpha = s / (2 * Q);
mp_float_t a0 = 1 + alpha;
mp_float_t a1 = -2 * c;
mp_float_t a2 = 1 - alpha;
mp_float_t b0 = (1 - c) / 2;
mp_float_t b1 = 1 - c;
mp_float_t b2 = (1 - c) / 2;
mp_obj_t out_args[] = {
mp_obj_new_float(a1 / a0),
mp_obj_new_float(a2 / a0),
mp_obj_new_float(b0 / a0),
mp_obj_new_float(b1 / a0),
mp_obj_new_float(b2 / a0),
};
return namedtuple_make_new((const mp_obj_type_t *)&synthio_biquad_type_obj, MP_ARRAY_SIZE(out_args), 0, out_args);
}
mp_obj_t common_hal_synthio_new_hpf(mp_float_t w0, mp_float_t Q) {
mp_float_t s = MICROPY_FLOAT_C_FUN(sin)(w0);
mp_float_t c = MICROPY_FLOAT_C_FUN(cos)(w0);
mp_float_t alpha = s / (2 * Q);
mp_float_t a0 = 1 + alpha;
mp_float_t a1 = -2 * c;
mp_float_t a2 = 1 - alpha;
mp_float_t b0 = (1 + c) / 2;
mp_float_t b1 = -(1 + c);
mp_float_t b2 = (1 + c) / 2;
mp_obj_t out_args[] = {
mp_obj_new_float(a1 / a0),
mp_obj_new_float(a2 / a0),
mp_obj_new_float(b0 / a0),
mp_obj_new_float(b1 / a0),
mp_obj_new_float(b2 / a0),
};
return namedtuple_make_new((const mp_obj_type_t *)&synthio_biquad_type_obj, MP_ARRAY_SIZE(out_args), 0, out_args);
}
mp_obj_t common_hal_synthio_new_bpf(mp_float_t w0, mp_float_t Q) {
mp_float_t s = MICROPY_FLOAT_C_FUN(sin)(w0);
mp_float_t c = MICROPY_FLOAT_C_FUN(cos)(w0);
mp_float_t alpha = s / (2 * Q);
mp_float_t a0 = 1 + alpha;
mp_float_t a1 = -2 * c;
mp_float_t a2 = 1 - alpha;
mp_float_t b0 = alpha;
mp_float_t b1 = 0;
mp_float_t b2 = -alpha;
mp_obj_t out_args[] = {
mp_obj_new_float(a1 / a0),
mp_obj_new_float(a2 / a0),
mp_obj_new_float(b0 / a0),
mp_obj_new_float(b1 / a0),
mp_obj_new_float(b2 / a0),
};
return namedtuple_make_new((const mp_obj_type_t *)&synthio_biquad_type_obj, MP_ARRAY_SIZE(out_args), 0, out_args);
}
#define BIQUAD_SHIFT (15)
STATIC int32_t biquad_scale_arg_obj(mp_obj_t arg) {
return (int32_t)MICROPY_FLOAT_C_FUN(round)(MICROPY_FLOAT_C_FUN(ldexp)(mp_obj_get_float(arg), BIQUAD_SHIFT));
}
void synthio_biquad_filter_assign(biquad_filter_state *st, mp_obj_t biquad_obj) {
if (biquad_obj != mp_const_none) {
mp_arg_validate_type(biquad_obj, (const mp_obj_type_t *)&synthio_biquad_type_obj, MP_QSTR_filter);
mp_obj_tuple_t *biquad = (mp_obj_tuple_t *)MP_OBJ_TO_PTR(biquad_obj);
st->a1 = biquad_scale_arg_obj(biquad->items[0]);
st->a2 = biquad_scale_arg_obj(biquad->items[1]);
st->b0 = biquad_scale_arg_obj(biquad->items[2]);
st->b1 = biquad_scale_arg_obj(biquad->items[3]);
st->b2 = biquad_scale_arg_obj(biquad->items[4]);
}
}
void synthio_biquad_filter_reset(biquad_filter_state *st) {
memset(&st->x, 0, 4 * sizeof(int16_t));
}
void synthio_biquad_filter_samples(biquad_filter_state *st, int32_t *buffer, size_t n_samples) {
int32_t a1 = st->a1;
int32_t a2 = st->a2;
int32_t b0 = st->b0;
int32_t b1 = st->b1;
int32_t b2 = st->b2;
int32_t x0 = st->x[0];
int32_t x1 = st->x[1];
int32_t y0 = st->y[0];
int32_t y1 = st->y[1];
for (size_t n = n_samples; n; --n, ++buffer) {
int32_t input = *buffer;
int32_t output = (b0 * input + b1 * x0 + b2 * x1 - a1 * y0 - a2 * y1 + (1 << (BIQUAD_SHIFT - 1))) >> BIQUAD_SHIFT;
x1 = x0;
x0 = input;
y1 = y0;
y0 = output;
*buffer = output;
}
st->x[0] = x0;
st->x[1] = x1;
st->y[0] = y0;
st->y[1] = y1;
}

View File

@ -0,0 +1,38 @@
/*
* 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.
*/
#pragma once
#include "py/obj.h"
typedef struct {
int32_t a1, a2, b0, b1, b2;
int32_t x[2], y[2];
} biquad_filter_state;
void synthio_biquad_filter_assign(biquad_filter_state *st, mp_obj_t biquad_obj);
void synthio_biquad_filter_reset(biquad_filter_state *st);
void synthio_biquad_filter_samples(biquad_filter_state *st, int32_t *buffer, size_t n_samples);

View File

@ -122,7 +122,7 @@ void common_hal_synthio_miditrack_construct(synthio_miditrack_obj_t *self,
self->track.buf = (void *)buffer;
self->track.len = len;
synthio_synth_init(&self->synth, sample_rate, 1, waveform_obj, mp_const_none, envelope_obj);
synthio_synth_init(&self->synth, sample_rate, 1, waveform_obj, envelope_obj);
start_parse(self);
}

View File

@ -40,12 +40,13 @@ void common_hal_synthio_note_set_frequency(synthio_note_obj_t *self, mp_float_t
self->frequency_scaled = synthio_frequency_convert_float_to_scaled(val);
}
bool common_hal_synthio_note_get_filter(synthio_note_obj_t *self) {
return self->filter;
mp_obj_t common_hal_synthio_note_get_filter_obj(synthio_note_obj_t *self) {
return self->filter_obj;
}
void common_hal_synthio_note_set_filter(synthio_note_obj_t *self, bool value_in) {
self->filter = value_in;
void common_hal_synthio_note_set_filter(synthio_note_obj_t *self, mp_obj_t filter_in) {
synthio_biquad_filter_assign(&self->filter_state, filter_in);
self->filter_obj = filter_in;
}
mp_float_t common_hal_synthio_note_get_ring_frequency(synthio_note_obj_t *self) {
@ -147,6 +148,7 @@ void synthio_note_recalculate(synthio_note_obj_t *self, int32_t sample_rate) {
void synthio_note_start(synthio_note_obj_t *self, int32_t sample_rate) {
synthio_note_recalculate(self, sample_rate);
synthio_biquad_filter_reset(&self->filter_state);
}
// Perform a pitch bend operation

View File

@ -27,6 +27,7 @@
#pragma once
#include "shared-module/synthio/__init__.h"
#include "shared-module/synthio/Biquad.h"
#include "shared-module/synthio/LFO.h"
#include "shared-bindings/synthio/__init__.h"
@ -37,12 +38,14 @@ typedef struct synthio_note_obj {
mp_float_t frequency, ring_frequency;
mp_obj_t waveform_obj, envelope_obj, ring_waveform_obj;
mp_obj_t filter_obj;
biquad_filter_state filter_state;
int32_t sample_rate;
int32_t frequency_scaled;
int32_t ring_frequency_scaled, ring_frequency_bent;
bool filter;
mp_buffer_info_t waveform_buf;
mp_buffer_info_t ring_waveform_buf;

View File

@ -33,10 +33,10 @@
void common_hal_synthio_synthesizer_construct(synthio_synthesizer_obj_t *self,
uint32_t sample_rate, int channel_count, mp_obj_t waveform_obj, mp_obj_t filter_obj,
uint32_t sample_rate, int channel_count, mp_obj_t waveform_obj,
mp_obj_t envelope_obj) {
synthio_synth_init(&self->synth, sample_rate, channel_count, waveform_obj, filter_obj, envelope_obj);
synthio_synth_init(&self->synth, sample_rate, channel_count, waveform_obj, envelope_obj);
self->blocks = mp_obj_new_list(0, NULL);
}

View File

@ -27,6 +27,7 @@
#include "shared-module/synthio/__init__.h"
#include "shared-bindings/synthio/__init__.h"
#include "shared-module/synthio/Biquad.h"
#include "shared-module/synthio/Note.h"
#include "py/runtime.h"
#include <math.h>
@ -171,24 +172,11 @@ int16_t mix_down_sample(int32_t sample) {
return sample;
}
static void synth_note_into_buffer(synthio_synth_t *synth, int chan, int32_t *out_buffer32, int16_t dur) {
static bool synth_note_into_buffer(synthio_synth_t *synth, int chan, int32_t *out_buffer32, int16_t dur, uint16_t loudness[2]) {
mp_obj_t note_obj = synth->span.note_obj[chan];
if (note_obj == SYNTHIO_SILENCE) {
synth->accum[chan] = 0;
return;
}
if (synth->envelope_state[chan].level == 0) {
// note is truly finished, but we only just noticed
synth->span.note_obj[chan] = SYNTHIO_SILENCE;
return;
}
int32_t sample_rate = synth->sample_rate;
// adjust loudness by envelope
uint16_t loudness[2] = {synth->envelope_state[chan].level,synth->envelope_state[chan].level};
uint32_t dds_rate;
const int16_t *waveform = synth->waveform_bufinfo.buf;
@ -227,33 +215,37 @@ static void synth_note_into_buffer(synthio_synth_t *synth, int chan, int32_t *ou
}
}
int synth_chan = synth->channel_count;
if (ring_dds_rate) {
uint32_t lim = waveform_length << SYNTHIO_FREQUENCY_SHIFT;
uint32_t accum = synth->accum[chan];
uint32_t lim = waveform_length << SYNTHIO_FREQUENCY_SHIFT;
uint32_t accum = synth->accum[chan];
if (dds_rate > lim / 2) {
// beyond nyquist, can't play note
return;
}
if (dds_rate > lim / 2) {
// beyond nyquist, can't play note
return false;
}
// can happen if note waveform gets set mid-note, but the expensive modulo is usually avoided
// can happen if note waveform gets set mid-note, but the expensive modulo is usually avoided
if (accum > lim) {
accum %= lim;
}
// first, fill with waveform
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;
accum -= lim;
}
int16_t idx = accum >> SYNTHIO_FREQUENCY_SHIFT;
out_buffer32[i] = waveform[idx];
}
synth->accum[chan] = accum;
int32_t ring_buffer[dur];
// first, fill with waveform
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;
ring_buffer[i] = waveform[idx];
if (ring_dds_rate) {
if (ring_dds_rate > lim / 2) {
// beyond nyquist, can't play ring (but did synth main sound so
// return true)
return true;
}
synth->accum[chan] = accum;
// now modulate by ring and accumulate
accum = synth->ring_accum[chan];
@ -264,82 +256,43 @@ static void synth_note_into_buffer(synthio_synth_t *synth, int chan, int32_t *ou
accum %= lim;
}
for (uint16_t i = 0, j = 0; i < dur; i++) {
for (uint16_t i = 0; i < dur; i++) {
accum += ring_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;
int16_t wi = (ring_waveform[idx] * ring_buffer[i]) / 32768;
for (int c = 0; c < synth_chan; c++) {
out_buffer32[j] += (wi * loudness[c]) / 32768;
j++;
}
int16_t wi = (ring_waveform[idx] * out_buffer32[i]) / 32768;
out_buffer32[i] = wi;
}
synth->ring_accum[chan] = accum;
} else {
uint32_t lim = waveform_length << SYNTHIO_FREQUENCY_SHIFT;
uint32_t accum = synth->accum[chan];
if (dds_rate > lim / 2) {
// beyond nyquist, can't play note
return;
}
// 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, j = 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;
int16_t wi = waveform[idx];
for (int c = 0; c < synth_chan; c++) {
out_buffer32[j] += (wi * loudness[c]) / 65536;
j++;
}
}
synth->accum[chan] = accum;
}
return true;
}
STATIC void run_fir(synthio_synth_t *synth, int32_t *out_buffer32, uint16_t dur) {
int16_t *coeff = (int16_t *)synth->filter_bufinfo.buf;
size_t fir_len = synth->filter_bufinfo.len;
int32_t *in_buf = synth->filter_buffer;
int synth_chan = synth->channel_count;
// FIR and copy values to output buffer
for (int16_t i = 0; i < dur * synth_chan; i++) {
int32_t acc = 0;
for (size_t j = 0; j < fir_len; j++) {
// shift 5 here is good for up to 32 filtered voices, else might wrap
acc = acc + (in_buf[j * synth_chan] * (coeff[j] >> 5));
}
*out_buffer32++ = acc >> 10;
in_buf++;
}
// Move values down so that they get filtered next time
memmove(synth->filter_buffer, &synth->filter_buffer[dur * synth_chan], fir_len * sizeof(int32_t) * synth_chan);
}
STATIC bool synthio_synth_get_note_filtered(mp_obj_t note_obj) {
STATIC mp_obj_t synthio_synth_get_note_filter(mp_obj_t note_obj) {
if (note_obj == mp_const_none) {
return false;
return mp_const_none;
}
if (!mp_obj_is_small_int(note_obj)) {
synthio_note_obj_t *note = MP_OBJ_TO_PTR(note_obj);
return note->filter;
return note->filter_obj;
}
return mp_const_none;
}
STATIC void sum_with_loudness(int32_t *out_buffer32, int32_t *tmp_buffer32, uint16_t loudness[2], size_t dur, int synth_chan) {
if (synth_chan == 1) {
for (size_t i = 0; i < dur; i++) {
*out_buffer32++ += (*tmp_buffer32++ *loudness[0]) >> 16;
}
} else {
for (size_t i = 0; i < dur; i++) {
*out_buffer32++ += (*tmp_buffer32 * loudness[0]) >> 16;
*out_buffer32++ += (*tmp_buffer32++ *loudness[1]) >> 16;
}
}
return true;
}
void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t *buffer_length, uint8_t channel) {
@ -359,37 +312,44 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
uint16_t dur = MIN(SYNTHIO_MAX_DUR, synth->span.dur);
synth->span.dur -= dur;
int32_t out_buffer32[dur * synth->channel_count];
if (synth->filter_buffer) {
int32_t *filter_start = &synth->filter_buffer[synth->filter_bufinfo.len * synth->channel_count];
memset(filter_start, 0, dur * synth->channel_count * sizeof(int32_t));
for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
mp_obj_t note_obj = synth->span.note_obj[chan];
if (!synthio_synth_get_note_filtered(note_obj)) {
continue;
}
synth_note_into_buffer(synth, chan, filter_start, dur);
}
run_fir(synth, out_buffer32, dur);
} else {
memset(out_buffer32, 0, sizeof(out_buffer32));
}
int32_t out_buffer32[SYNTHIO_MAX_DUR * synth->channel_count];
int32_t tmp_buffer32[SYNTHIO_MAX_DUR];
memset(out_buffer32, 0, synth->channel_count * dur * sizeof(int32_t));
for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
mp_obj_t note_obj = synth->span.note_obj[chan];
if (synth->filter_buffer && synthio_synth_get_note_filtered(note_obj)) {
if (note_obj == SYNTHIO_SILENCE) {
continue;
}
synth_note_into_buffer(synth, chan, out_buffer32, dur);
if (synth->envelope_state[chan].level == 0) {
// note is truly finished, but we only just noticed
synth->span.note_obj[chan] = SYNTHIO_SILENCE;
continue;
}
uint16_t loudness[2] = {synth->envelope_state[chan].level,synth->envelope_state[chan].level};
if (!synth_note_into_buffer(synth, chan, tmp_buffer32, dur, loudness)) {
// for some other reason, such as being above nyquist, note
// couldn't be synthed, so don't filter or sum it in
continue;
}
mp_obj_t filter_obj = synthio_synth_get_note_filter(note_obj);
if (filter_obj != mp_const_none) {
synthio_note_obj_t *note = MP_OBJ_TO_PTR(note_obj);
synthio_biquad_filter_samples(&note->filter_state, tmp_buffer32, dur);
}
// adjust loudness by envelope
sum_with_loudness(out_buffer32, tmp_buffer32, loudness, dur, synth->channel_count);
}
int16_t *out_buffer16 = (int16_t *)(void *)synth->buffers[synth->buffer_index];
// mix down audio
for (size_t i = 0; i < MP_ARRAY_SIZE(out_buffer32); i++) {
for (size_t i = 0; i < dur * synth->channel_count; i++) {
int32_t sample = out_buffer32[i];
out_buffer16[i] = mix_down_sample(sample);
}
@ -419,7 +379,6 @@ bool synthio_synth_deinited(synthio_synth_t *synth) {
}
void synthio_synth_deinit(synthio_synth_t *synth) {
synth->filter_buffer = NULL;
synth->buffers[0] = NULL;
synth->buffers[1] = NULL;
}
@ -433,17 +392,12 @@ mp_obj_t synthio_synth_envelope_get(synthio_synth_t *synth) {
return synth->envelope_obj;
}
void synthio_synth_init(synthio_synth_t *synth, uint32_t sample_rate, int channel_count, mp_obj_t waveform_obj, mp_obj_t filter_obj, mp_obj_t envelope_obj) {
void synthio_synth_init(synthio_synth_t *synth, uint32_t sample_rate, int channel_count, mp_obj_t waveform_obj, mp_obj_t envelope_obj) {
synthio_synth_parse_waveform(&synth->waveform_bufinfo, waveform_obj);
synthio_synth_parse_filter(&synth->filter_bufinfo, filter_obj);
mp_arg_validate_int_range(channel_count, 1, 2, MP_QSTR_channel_count);
synth->buffer_length = SYNTHIO_MAX_DUR * SYNTHIO_BYTES_PER_SAMPLE * channel_count;
synth->buffers[0] = m_malloc(synth->buffer_length, false);
synth->buffers[1] = m_malloc(synth->buffer_length, false);
if (synth->filter_bufinfo.len) {
synth->filter_buffer_length = (synth->filter_bufinfo.len + SYNTHIO_MAX_DUR) * channel_count * sizeof(int32_t);
synth->filter_buffer = m_malloc(synth->filter_buffer_length, false);
}
synth->channel_count = channel_count;
synth->other_channel = -1;
synth->waveform_obj = waveform_obj;
@ -483,11 +437,6 @@ void synthio_synth_parse_waveform(mp_buffer_info_t *bufinfo_waveform, mp_obj_t w
parse_common(bufinfo_waveform, waveform_obj, MP_QSTR_waveform, 16384);
}
void synthio_synth_parse_filter(mp_buffer_info_t *bufinfo_filter, mp_obj_t filter_obj) {
*bufinfo_filter = ((mp_buffer_info_t) { .buf = NULL, .len = 0 });
parse_common(bufinfo_filter, filter_obj, MP_QSTR_filter, 128);
}
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_obj[i] == note) {

View File

@ -64,12 +64,11 @@ typedef struct synthio_synth {
uint32_t sample_rate;
uint32_t total_envelope;
int16_t *buffers[2];
int32_t *filter_buffer;
uint8_t channel_count;
uint16_t buffer_length, filter_buffer_length;
uint16_t buffer_length;
uint16_t last_buffer_length;
uint8_t other_channel, buffer_index, other_buffer_index;
mp_buffer_info_t waveform_bufinfo, filter_bufinfo;
mp_buffer_info_t waveform_bufinfo;
synthio_envelope_definition_t global_envelope_definition;
mp_obj_t waveform_obj, filter_obj, envelope_obj;
synthio_midi_span_t span;
@ -91,7 +90,7 @@ typedef struct {
void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **buffer, uint32_t *buffer_length, uint8_t channel);
void synthio_synth_deinit(synthio_synth_t *synth);
bool synthio_synth_deinited(synthio_synth_t *synth);
void synthio_synth_init(synthio_synth_t *synth, uint32_t sample_rate, int channel_count, mp_obj_t waveform_obj, mp_obj_t filter_obj, mp_obj_t envelope);
void synthio_synth_init(synthio_synth_t *synth, uint32_t sample_rate, int channel_count, mp_obj_t waveform_obj, mp_obj_t envelope);
void synthio_synth_get_buffer_structure(synthio_synth_t *synth, bool single_channel_output,
bool *single_buffer, bool *samples_signed, uint32_t *max_buffer_length, uint8_t *spacing);
void synthio_synth_reset_buffer(synthio_synth_t *synth, bool single_channel_output, uint8_t channel);

View File

@ -0,0 +1,61 @@
import sys
sys.path.insert(
0, f"{__file__.rpartition('/')[0] or '.'}/../../../../frozen/Adafruit_CircuitPython_Wave"
)
import random
import audiocore
import synthio
from ulab import numpy as np
import adafruit_wave as wave
random.seed(9)
envelope = synthio.Envelope(
attack_time=0.15, decay_time=0, release_time=0.08, attack_level=1.0, sustain_level=1.0
)
SAMPLE_SIZE = 1024
VOLUME = 14700
sine = np.array(
np.sin(np.linspace(0, 2 * np.pi, SAMPLE_SIZE, endpoint=False)) * VOLUME,
dtype=np.int16,
)
noise = np.array([random.randint(-VOLUME, VOLUME) for i in range(SAMPLE_SIZE)], dtype=np.int16)
bend_out = np.linspace(0, 32767, num=SAMPLE_SIZE, endpoint=True, dtype=np.int16)
def synthesize(synth):
for waveform in (sine, None, noise):
for biquad in (
None,
synth.low_pass_filter(120),
):
for midi_note in range(24, 90, 3):
n = synthio.Note(
frequency=synthio.midi_to_hz(midi_note),
envelope=envelope,
filter=biquad,
waveform=waveform,
bend=synthio.LFO(bend_out, once=True, rate=1 / 2, scale=5),
)
synth.press(n)
print(n.frequency)
yield 24
synth.release_all()
yield 16
yield 24
yield 48
with wave.open("biquad.wav", "w") as f:
f.setnchannels(1)
f.setsampwidth(2)
f.setframerate(48000)
synth = synthio.Synthesizer(sample_rate=48000)
for n in synthesize(synth):
for i in range(n):
result, data = audiocore.get_buffer(synth)
f.writeframes(data)

View File

@ -1,53 +0,0 @@
import sys
sys.path.insert(
0, f"{__file__.rpartition('/')[0] or '.'}/../../../../frozen/Adafruit_CircuitPython_Wave"
)
import random
import audiocore
import synthio
from ulab import numpy as np
import adafruit_wave as wave
import mkfilter
random.seed(9)
envelope = synthio.Envelope(
attack_time=0.1, decay_time=0.05, release_time=0.2, attack_level=0.8, sustain_level=0.8
)
SAMPLE_SIZE = 1024
bend_out = np.linspace(0, 32767, num=SAMPLE_SIZE, endpoint=True, dtype=np.int16)
filter_rectangular = mkfilter.LPF(48000, 800, 13)
filter_rectangular_big = mkfilter.LPF(48000, 800, 59)
filter_blackman = mkfilter.LPF(48000, 800, 59, win=mkfilter.blackman)
print(filter_blackman)
def synthesize(synth):
n = synthio.Note(
frequency=120,
envelope=envelope,
filter=True,
bend=synthio.LFO(bend_out, once=True, rate=1 / 2, scale=5),
)
synth.press(n)
print(synth, n)
yield 2 * 48000 // 256
synth.release_all()
yield 36
with wave.open("fir.wav", "w") as f:
f.setnchannels(1)
f.setsampwidth(2)
f.setframerate(48000)
for filter_coeffs in [None, filter_rectangular, filter_rectangular_big, filter_blackman]:
synth = synthio.Synthesizer(sample_rate=48000, filter=filter_coeffs)
for n in synthesize(synth):
for i in range(n):
result, data = audiocore.get_buffer(synth)
f.writeframes(data)

View File

@ -1,105 +0,0 @@
try:
from ulab import numpy as np
except ImportError:
import numpy as np
def lpf(fS, f, N, win=lambda N: 1):
if not (N & 1):
raise ValueError("filter length must be odd")
h = np.sinc(2 * f / fS * (np.arange(N) - (N - 1) / 2))
h = h * win(N)
return h * (1 / np.sum(h))
def hpf(fS, f, N, win=lambda N: 1):
if not (N & 1):
raise ValueError("filter length must be odd")
h = -lpf(fS, f, N)
h = h * win(N)
h[(N - 1) // 2] += 1
return h
def brf(fS, fL, NL, fH, NH, win=lambda N: 1):
hlpf = lpf(fS, fL, NL, win)
hhpf = hpf(fS, fH, NH, win)
if NH > NL:
h = hhpf
h[(NH - NL) // 2 : (NH - NL) // 2 + NL] += hlpf
else:
h = hlpf
h[(NL - NH) // 2 : (NL - NH) // 2 + NH] += hhpf
return h
def bpf(fS, fL, NL, fH, NH, win=lambda N: 1):
hlpf = lpf(fS, fL, NL, win)
hhpf = hpf(fS, fH, NH, win)
return np.convolve(hlpf, hhpf)
def blackman(M):
n = np.arange(1 - M, M, 2)
return 0.42 + 0.5 * np.cos(np.pi * n / (M - 1)) + 0.08 * np.cos(2.0 * np.pi * n / (M - 1))
def tosynthio(coeffs):
result = np.array(coeffs * 32767, dtype=np.int16)
return trim_zeros(result)
def trim_zeros(arr):
i = 0
j = len(arr) - 1
while i < len(arr) and arr[i] == 0:
i += 1
while j > i and arr[j] == 0:
j -= 1
return arr[i : j + 1]
# fiiir.com uses factor 4.6 for blackman window, 0.91 for rectangular
def ntaps(fS, fB, factor=4.6):
b = fB / fS
return round(factor / b) | 1
def LPF(*args, **kw):
return tosynthio(lpf(*args, **kw))
def HPF(*args, **kw):
return tosynthio(hpf(*args, **kw))
def BRF(*args, **kw):
return tosynthio(brf(*args, **kw))
def BPF(*args, **kw):
return tosynthio(bpf(*args, **kw))
if __name__ == "__main__":
print("lpf(24000, 2040, 13) # 1920Hz transition window")
print(list(lpf(24000, 2040, 13)))
print("hpf(24000, 9600, 13) # 960Hz transition window")
print(list(hpf(24000, 9600, 23)))
print("bpf(24000, 1200, 11, 3960, 15) # 2400Hz, 1600Hz transition windows")
print(list(bpf(24000, 1200, 11, 3960, 15)))
print("brf(24000, 960, 19, 2400, 13) # 1200, 1800Hz transition windows")
brf_tst = brf(24000, 960, 19, 2400, 13)
print(brf_tst)
print("brf(24000, 960, 13, 2400, 19) # 1200, 1800Hz transition windows")
brf_tst = brf(24000, 960, 13, 2400, 19)
print(brf_tst)
print("lpf(1, 0.1, 59, blackman) # 1920Hz transition window, blackman")
print(lpf(1, 0.1, 59, blackman))

View File

@ -23,18 +23,17 @@ envelope = synthio.Envelope(
)
synth = synthio.Synthesizer(sample_rate=48000)
bend_out = np.linspace(0, 32767, num=SAMPLE_SIZE, endpoint=True, dtype=np.int16)
def synthesize(synth):
n = synthio.Note(
frequency=120,
frequency=440,
waveform=sine,
ring_waveform=sine,
ring_frequency=769,
envelope=envelope,
bend_mode=synthio.BendType.VIBRATO,
bend_depth=50 / 1200,
bend_rate=7,
bend=synthio.LFO(bend_out, scale=50 / 1200, rate=7),
)
print(synth, n)
@ -43,6 +42,13 @@ def synthesize(synth):
synth.release_all()
yield 36
n.ring_frequency = 0
print(synth, n)
synth.press((n,))
yield 720
synth.release_all()
yield 36
def chain(*args):
for a in args:

View File

@ -1,4 +1,4 @@
(0, 1, 512, 1)
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]
1 [-16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383]
(0, 1, 512, 1)
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 [0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383]
[-16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384]
(80, 91)
[0, 0, 28045, 28045, 0, -28046, -28046, 0, 28045, 28045, 0, 0, 28045, 0, 0, -28046, -28046, 0, 28045, 28045, 0, 0, 28045, 0]
[-1, -1, 28045, 28045, -1, -28046, -28046, -1, 28045, 28045, -1, -1, 28045, -1, -1, -28046, -28046, -1, 28045, 28045, -1, -1, 28045, -1]
(91,)
[-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)
[-28046, -1, -1, 28045, -1, -1, 28045, 28045, -1, -28046, -28046, -1, 28045, 28045, -1, -1, 28045, -1, -1, -28046, -28046, -28046, 28045, 28045]
(-5243, 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)
(-15728, 15727)
(-16384, 16383)
(-14287, 14286)
(-13107, 13106)
(-13107, 13106)
(-13107, 13106)
(-13107, 13106)
(-13107, 13106)
(-13107, 13106)
(-13107, 13106)
(-13107, 13106)
(-11010, 11009)
(-8913, 8912)
(-6816, 6815)
(-4719, 4718)
(-2622, 2621)
(-525, 524)
(0, 0)
(0, 0)
(0, 0)

View File

@ -1,13 +1,13 @@
()
[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, panning=0.0, amplitude=1.0, bend=0.0, waveform=None, envelope=None, filter=True, ring_frequency=0.0, ring_bend=0.0, ring_waveform=None),)
(Note(frequency=830.6076004423605, panning=0.0, amplitude=1.0, bend=0.0, waveform=None, envelope=None, filter=None, ring_frequency=0.0, ring_bend=0.0, ring_waveform=None),)
[-16383, -16383, -16383, -16383, 16382, 16382, 16382, 16382, 16382, -16383, -16383, -16383, -16383, -16383, 16382, 16382, 16382, 16382, 16382, -16383, -16383, -16383, -16383, -16383]
(Note(frequency=830.6076004423605, panning=0.0, amplitude=1.0, bend=0.0, waveform=None, envelope=None, filter=True, ring_frequency=0.0, ring_bend=0.0, ring_waveform=None), Note(frequency=830.6076004423605, panning=0.0, amplitude=1.0, bend=0.0, waveform=None, envelope=None, filter=True, ring_frequency=0.0, ring_bend=0.0, ring_waveform=None))
(Note(frequency=830.6076004423605, panning=0.0, amplitude=1.0, bend=0.0, waveform=None, envelope=None, filter=None, ring_frequency=0.0, ring_bend=0.0, ring_waveform=None), Note(frequency=830.6076004423605, panning=0.0, amplitude=1.0, bend=0.0, waveform=None, envelope=None, filter=None, ring_frequency=0.0, ring_bend=0.0, ring_waveform=None))
[-1, -1, -1, -1, -1, -1, -1, -1, 28045, -1, -1, -1, -1, -28046, -1, -1, -1, -1, 28045, -1, -1, -1, -1, -28046]
(Note(frequency=830.6076004423605, panning=0.0, amplitude=1.0, bend=0.0, waveform=None, envelope=None, filter=True, ring_frequency=0.0, ring_bend=0.0, ring_waveform=None),)
(Note(frequency=830.6076004423605, panning=0.0, amplitude=1.0, bend=0.0, waveform=None, envelope=None, filter=None, ring_frequency=0.0, ring_bend=0.0, ring_waveform=None),)
[-1, -1, -1, 28045, -1, -1, -1, -1, -1, -1, -1, -1, 28045, -1, -1, -1, -1, -28046, -1, -1, -1, -1, 28045, -1]
(-5242, 5241)
(-10484, 10484)
(-10485, 10484)
(-15727, 15726)
(-16383, 16382)
(-14286, 14285)

View File

@ -0,0 +1,12 @@
from synthio import Synthesizer
s = Synthesizer(sample_rate=48000)
def print_filter(x):
print(" ".join(f"{v:.4g}" for v in x))
print_filter(s.low_pass_filter(330))
print_filter(s.high_pass_filter(330))
print_filter(s.band_pass_filter(330))

View File

@ -0,0 +1,3 @@
-1.939 0.9407 0.0004526 0.0009052 0.0004526
-1.939 0.9407 0.9699 -1.94 0.9699
-1.939 0.9407 0.02963 0 -0.02963