diff --git a/shared-bindings/synthio/Note.c b/shared-bindings/synthio/Note.c index 651bf8f33a..4dcc66666c 100644 --- a/shared-bindings/synthio/Note.c +++ b/shared-bindings/synthio/Note.c @@ -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); diff --git a/shared-bindings/synthio/Note.h b/shared-bindings/synthio/Note.h index 271268dc33..263ca701e3 100644 --- a/shared-bindings/synthio/Note.h +++ b/shared-bindings/synthio/Note.h @@ -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); diff --git a/shared-module/synthio/Biquad.c b/shared-module/synthio/Biquad.c index 9457316b3a..29e680fd7c 100644 --- a/shared-module/synthio/Biquad.c +++ b/shared-module/synthio/Biquad.c @@ -26,6 +26,7 @@ #include #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); @@ -92,3 +93,47 @@ mp_obj_t common_hal_synthio_new_bpf(mp_float_t w0, mp_float_t Q) { return namedtuple_make_new((const mp_obj_type_t *)&synthio_biquad_type_obj, MP_ARRAY_SIZE(out_args), 0, out_args); } + +#define BIQUAD_SHIFT (16) +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_samples(biquad_filter_state *st, int32_t *out, const int32_t *in, size_t n, size_t stride) { + 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 (; n; --n, in += stride, out += stride) { + int16_t input = *in; + int32_t output = (b0 * input + b1 * x0 + b2 * x1 - a1 * y0 - a2 * y1) >> BIQUAD_SHIFT; + + x1 = x0; + x0 = input; + y1 = y0; + y0 = output; + *out = output; + } + st->x[0] = x0; + st->x[1] = x1; + st->y[0] = y0; + st->y[1] = y1; +} diff --git a/shared-module/synthio/Biquad.h b/shared-module/synthio/Biquad.h new file mode 100644 index 0000000000..047fd2b6c2 --- /dev/null +++ b/shared-module/synthio/Biquad.h @@ -0,0 +1,37 @@ +/* + * 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_samples(biquad_filter_state *st, int32_t *out, const int32_t *in, size_t n, size_t stride); diff --git a/shared-module/synthio/Note.c b/shared-module/synthio/Note.c index 6a8982f6b2..2cd391679d 100644 --- a/shared-module/synthio/Note.c +++ b/shared-module/synthio/Note.c @@ -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) { diff --git a/shared-module/synthio/Note.h b/shared-module/synthio/Note.h index ba777c237c..8ae4fd910e 100644 --- a/shared-module/synthio/Note.h +++ b/shared-module/synthio/Note.h @@ -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; diff --git a/shared-module/synthio/__init__.c b/shared-module/synthio/__init__.c index 7c8e627404..64af797e03 100644 --- a/shared-module/synthio/__init__.c +++ b/shared-module/synthio/__init__.c @@ -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 @@ -309,37 +310,15 @@ static void synth_note_into_buffer(synthio_synth_t *synth, int chan, int32_t *ou } } -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 true; + return mp_const_none; } void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t *buffer_length, uint8_t channel) { @@ -360,30 +339,24 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t 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)); - } + 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 (synth->filter_buffer && synthio_synth_get_note_filtered(note_obj)) { - continue; + mp_obj_t filter_obj = synthio_synth_get_note_filter(note_obj); + if (filter_obj == mp_const_none) { + synth_note_into_buffer(synth, chan, out_buffer32, dur); + } else { + synthio_note_obj_t *note = MP_OBJ_TO_PTR(note_obj); + int32_t filter_buffer32[dur * synth->channel_count]; + memset(filter_buffer32, 0, sizeof(filter_buffer32)); + + synth_note_into_buffer(synth, chan, filter_buffer32, dur); + int synth_chan = synth->channel_count; + for (int i = 0; i < synth_chan; i++) { + synthio_biquad_filter_samples(¬e->filter_state, &out_buffer32[i], &filter_buffer32[i], dur, i); + } } - synth_note_into_buffer(synth, chan, out_buffer32, dur); } int16_t *out_buffer16 = (int16_t *)(void *)synth->buffers[synth->buffer_index]; diff --git a/tests/circuitpython-manual/synthio/note/biquad.py b/tests/circuitpython-manual/synthio/note/biquad.py new file mode 100644 index 0000000000..59a3f74541 --- /dev/null +++ b/tests/circuitpython-manual/synthio/note/biquad.py @@ -0,0 +1,63 @@ +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, decay_time=0, release_time=0, attack_level=0.8, 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(330), + synth.low_pass_filter(660), + synth.high_pass_filter(330), + synth.high_pass_filter(660), + synth.band_pass_filter(330), + synth.band_pass_filter(660), + ): + n = synthio.Note( + frequency=80, + envelope=envelope, + filter=biquad, + waveform=waveform, + 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("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) diff --git a/tests/circuitpython-manual/synthio/note/fir.py b/tests/circuitpython-manual/synthio/note/fir.py deleted file mode 100644 index 4593dcb376..0000000000 --- a/tests/circuitpython-manual/synthio/note/fir.py +++ /dev/null @@ -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) diff --git a/tests/circuitpython-manual/synthio/note/mkfilter.py b/tests/circuitpython-manual/synthio/note/mkfilter.py deleted file mode 100644 index 20a4203af2..0000000000 --- a/tests/circuitpython-manual/synthio/note/mkfilter.py +++ /dev/null @@ -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)) diff --git a/tests/circuitpython/synthesizer_note.py.exp b/tests/circuitpython/synthesizer_note.py.exp index f9173c6470..0c2af86681 100644 --- a/tests/circuitpython/synthesizer_note.py.exp +++ b/tests/circuitpython/synthesizer_note.py.exp @@ -1,10 +1,10 @@ () [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)