synthio: Add LFOs

This commit is contained in:
Jeff Epler 2023-05-15 10:49:42 -05:00
parent 827eaeb1f9
commit f83212314e
No known key found for this signature in database
GPG Key ID: D5BF15AB975AB4DE
18 changed files with 722 additions and 305 deletions

View File

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

View File

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

View File

@ -0,0 +1,234 @@
/*
* 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 "py/obj.h"
#include "py/objproperty.h"
#include "py/runtime.h"
#include "shared-bindings/util.h"
#include "shared-bindings/synthio/LFO.h"
#include "shared-module/synthio/LFO.h"
//| class LFO:
//| """A low-frequency oscillator
//|
//| Every `rate` seconds, the output of the LFO cycles through its `waveform`.
//| The output at any particular moment is ``waveform[idx] * scale + offset``.
//| Internally, the calculation takes place in fixed point for speed.
//|
//| `rate`, `offset`, `scale`, and `once` can be changed at run-time.
//|
//| An LFO only updates if it is actually associated with a playing Note,
//| including if it is indirectly associated with the Note via an intermediate
//| LFO.
//|
//| Using the same LFO as an input to multiple other LFOs or Notes is OK, but
//| the result if an LFO is tied to multiple Synthtesizer objects is undefined."""
//|
//| def __init__(
//| self,
//| waveform: ReadableBuffer,
//| *,
//| rate: BlockInput = 1.0,
//| scale: BlockInput = 1.0,
//| offset: BlockInput = 0,
//| once=False
//| ):
//| pass
static const mp_arg_t lfo_properties[] = {
{ MP_QSTR_waveform, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = NULL } },
{ MP_QSTR_rate, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(1) } },
{ MP_QSTR_scale, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(1) } },
{ MP_QSTR_offset, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(0) } },
{ MP_QSTR_once, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(0) } },
};
STATIC mp_obj_t synthio_lfo_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_waveform }; // others never directly referred to by argument number
mp_arg_val_t args[MP_ARRAY_SIZE(lfo_properties)];
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(lfo_properties), lfo_properties, args);
synthio_lfo_obj_t *self = m_new_obj(synthio_lfo_obj_t);
self->base.type = &synthio_lfo_type;
synthio_synth_parse_waveform(&self->waveform_bufinfo, args[ARG_waveform].u_obj);
self->waveform_obj = args[ARG_waveform].u_obj;
self->last_tick = synthio_global_tick;
mp_obj_t result = MP_OBJ_FROM_PTR(self);
properties_construct_helper(result, lfo_properties + 1, args + 1, MP_ARRAY_SIZE(lfo_properties) - 1);
return result;
};
//| waveform: Optional[ReadableBuffer]
//| """The waveform of this lfo. (read-only, but the values in the buffer may be modified dynamically)"""
STATIC mp_obj_t synthio_lfo_get_waveform(mp_obj_t self_in) {
synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in);
return common_hal_synthio_lfo_get_waveform_obj(self);
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_lfo_get_waveform_obj, synthio_lfo_get_waveform);
MP_PROPERTY_GETTER(synthio_lfo_waveform_obj,
(mp_obj_t)&synthio_lfo_get_waveform_obj);
//| rate: BlockInput
//| """The rate (in Hz) at which the LFO cycles through its waveform"""
STATIC mp_obj_t synthio_lfo_get_rate(mp_obj_t self_in) {
synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in);
return common_hal_synthio_lfo_get_rate_obj(self);
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_lfo_get_rate_obj, synthio_lfo_get_rate);
STATIC mp_obj_t synthio_lfo_set_rate(mp_obj_t self_in, mp_obj_t arg) {
synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_lfo_set_rate_obj(self, arg);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_lfo_set_rate_obj, synthio_lfo_set_rate);
MP_PROPERTY_GETSET(synthio_lfo_rate_obj,
(mp_obj_t)&synthio_lfo_get_rate_obj,
(mp_obj_t)&synthio_lfo_set_rate_obj);
//| offset: BlockInput
//| """An additive value applied to the LFO's output"""
STATIC mp_obj_t synthio_lfo_get_offset(mp_obj_t self_in) {
synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in);
return common_hal_synthio_lfo_get_offset_obj(self);
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_lfo_get_offset_obj, synthio_lfo_get_offset);
STATIC mp_obj_t synthio_lfo_set_offset(mp_obj_t self_in, mp_obj_t arg) {
synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_lfo_set_offset_obj(self, arg);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_lfo_set_offset_obj, synthio_lfo_set_offset);
MP_PROPERTY_GETSET(synthio_lfo_offset_obj,
(mp_obj_t)&synthio_lfo_get_offset_obj,
(mp_obj_t)&synthio_lfo_set_offset_obj);
//| scale: BlockInput
//| """An additive value applied to the LFO's output"""
STATIC mp_obj_t synthio_lfo_get_scale(mp_obj_t self_in) {
synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in);
return common_hal_synthio_lfo_get_scale_obj(self);
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_lfo_get_scale_obj, synthio_lfo_get_scale);
STATIC mp_obj_t synthio_lfo_set_scale(mp_obj_t self_in, mp_obj_t arg) {
synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_lfo_set_scale_obj(self, arg);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_lfo_set_scale_obj, synthio_lfo_set_scale);
MP_PROPERTY_GETSET(synthio_lfo_scale_obj,
(mp_obj_t)&synthio_lfo_get_scale_obj,
(mp_obj_t)&synthio_lfo_set_scale_obj);
//|
//| once: bool
//| """True if the waveform should stop when it reaches its last output value, false if it should re-start at the beginning of its waveform"""
STATIC mp_obj_t synthio_lfo_get_once(mp_obj_t self_in) {
synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in);
return mp_obj_new_bool(common_hal_synthio_lfo_get_once(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_lfo_get_once_obj, synthio_lfo_get_once);
STATIC mp_obj_t synthio_lfo_set_once(mp_obj_t self_in, mp_obj_t arg) {
synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_lfo_set_once(self, mp_obj_is_true(arg));
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_lfo_set_once_obj, synthio_lfo_set_once);
MP_PROPERTY_GETSET(synthio_lfo_once_obj,
(mp_obj_t)&synthio_lfo_get_once_obj,
(mp_obj_t)&synthio_lfo_set_once_obj);
//|
//| phase: float
//| """The phase of the oscillator, in the range 0 to 1 (read-only)"""
STATIC mp_obj_t synthio_lfo_get_phase(mp_obj_t self_in) {
synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in);
return mp_obj_new_float(common_hal_synthio_lfo_get_phase(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_lfo_get_phase_obj, synthio_lfo_get_phase);
MP_PROPERTY_GETTER(synthio_lfo_phase_obj,
(mp_obj_t)&synthio_lfo_get_phase_obj);
//|
//| value: float
//| """The value of the oscillator (read-only)"""
STATIC mp_obj_t synthio_lfo_get_value(mp_obj_t self_in) {
synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in);
return mp_obj_new_float(common_hal_synthio_lfo_get_value(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_lfo_get_value_obj, synthio_lfo_get_value);
MP_PROPERTY_GETTER(synthio_lfo_value_obj,
(mp_obj_t)&synthio_lfo_get_value_obj);
//|
//| def retrigger(self):
//| """Reset the LFO's internal index to the start of the waveform. Most useful when it its `once` property is `True`."""
//|
STATIC mp_obj_t synthio_lfo_retrigger(mp_obj_t self_in) {
synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_lfo_retrigger(self);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_lfo_retrigger_obj, synthio_lfo_retrigger);
static void lfo_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
(void)kind;
properties_print_helper(print, self_in, lfo_properties, MP_ARRAY_SIZE(lfo_properties));
}
STATIC const mp_rom_map_elem_t synthio_lfo_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_waveform), MP_ROM_PTR(&synthio_lfo_waveform_obj) },
{ MP_ROM_QSTR(MP_QSTR_rate), MP_ROM_PTR(&synthio_lfo_rate_obj) },
{ MP_ROM_QSTR(MP_QSTR_scale), MP_ROM_PTR(&synthio_lfo_scale_obj) },
{ MP_ROM_QSTR(MP_QSTR_offset), MP_ROM_PTR(&synthio_lfo_offset_obj) },
{ MP_ROM_QSTR(MP_QSTR_once), MP_ROM_PTR(&synthio_lfo_once_obj) },
{ MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&synthio_lfo_value_obj) },
{ MP_ROM_QSTR(MP_QSTR_phase), MP_ROM_PTR(&synthio_lfo_phase_obj) },
{ MP_ROM_QSTR(MP_QSTR_retrigger), MP_ROM_PTR(&synthio_lfo_retrigger_obj) },
};
STATIC MP_DEFINE_CONST_DICT(synthio_lfo_locals_dict, synthio_lfo_locals_dict_table);
const mp_obj_type_t synthio_lfo_type = {
{ &mp_type_type },
.name = MP_QSTR_LFO,
.make_new = synthio_lfo_make_new,
.locals_dict = (mp_obj_dict_t *)&synthio_lfo_locals_dict,
.print = lfo_print,
};

View File

@ -0,0 +1,53 @@
/*
* 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 synthio_lfo_obj synthio_lfo_obj_t;
extern const mp_obj_type_t synthio_lfo_type;
mp_obj_t common_hal_synthio_lfo_get_waveform_obj(synthio_lfo_obj_t *self);
void common_hal_synthio_lfo_set_waveform_obj(synthio_lfo_obj_t *self, mp_obj_t arg);
mp_obj_t common_hal_synthio_lfo_get_rate_obj(synthio_lfo_obj_t *self);
void common_hal_synthio_lfo_set_rate_obj(synthio_lfo_obj_t *self, mp_obj_t arg);
mp_obj_t common_hal_synthio_lfo_get_scale_obj(synthio_lfo_obj_t *self);
void common_hal_synthio_lfo_set_scale_obj(synthio_lfo_obj_t *self, mp_obj_t arg);
mp_obj_t common_hal_synthio_lfo_get_offset_obj(synthio_lfo_obj_t *self);
void common_hal_synthio_lfo_set_offset_obj(synthio_lfo_obj_t *self, mp_obj_t arg);
bool common_hal_synthio_lfo_get_once(synthio_lfo_obj_t *self);
void common_hal_synthio_lfo_set_once(synthio_lfo_obj_t *self, bool arg);
mp_float_t common_hal_synthio_lfo_get_value(synthio_lfo_obj_t *self);
mp_float_t common_hal_synthio_lfo_get_phase(synthio_lfo_obj_t *self);
void common_hal_synthio_lfo_retrigger(synthio_lfo_obj_t *self);

View File

@ -37,15 +37,13 @@
static const mp_arg_t note_properties[] = {
{ MP_QSTR_frequency, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = NULL } },
{ MP_QSTR_panning, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(0) } },
{ 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_bend_rate, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = NULL } },
{ MP_QSTR_bend_depth, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = NULL } },
{ MP_QSTR_bend_mode, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = (mp_obj_t)MP_ROM_PTR(&bend_mode_VIBRATO_obj) } },
{ MP_QSTR_amplitude, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(1) } },
{ 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_ring_frequency, MP_ARG_OBJ, {.u_obj = NULL } },
{ 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 } },
};
//| class Note:
@ -53,14 +51,11 @@ static const mp_arg_t note_properties[] = {
//| self,
//| *,
//| frequency: float,
//| panning: float = 0.0,
//| panning: BlockInput = 0.0,
//| waveform: Optional[ReadableBuffer] = None,
//| envelope: Optional[Envelope] = None,
//| tremolo_depth: float = 0.0,
//| tremolo_rate: float = 0.0,
//| bend_depth: float = 0.0,
//| bend_rate: float = 0.0,
//| bend_mode: "BendMode" = BendMode.VIBRATO,
//| amplitude: BlockInput = 0.0,
//| bend: BlockInput = 0.0,
//| ) -> None:
//| """Construct a Note object, with a frequency in Hz, and optional panning, waveform, envelope, tremolo (volume change) and bend (frequency change).
//|
@ -76,16 +71,12 @@ STATIC mp_obj_t synthio_note_make_new(const mp_obj_type_t *type_in, size_t n_arg
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);
}
}
properties_construct_helper(result, note_properties, args, MP_ARRAY_SIZE(note_properties));
return result;
};
//| frequency: float
//| frequency: BlockInput
//| """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);
@ -121,7 +112,7 @@ MP_PROPERTY_GETSET(synthio_note_filter_obj,
(mp_obj_t)&synthio_note_get_filter_obj,
(mp_obj_t)&synthio_note_set_filter_obj);
//| panning: float
//| panning: BlockInput
//| """Defines the channel(s) in which the note appears.
//|
//| -1 is left channel only, 0 is both channels, and 1 is right channel.
@ -130,13 +121,13 @@ MP_PROPERTY_GETSET(synthio_note_filter_obj,
//| amplitude in the left channel and 1/2 amplitude in the right channel."""
STATIC mp_obj_t synthio_note_get_panning(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_panning(self));
return common_hal_synthio_note_get_panning(self);
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_panning_obj, synthio_note_get_panning);
STATIC mp_obj_t synthio_note_set_panning(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_panning(self, mp_obj_get_float(arg));
common_hal_synthio_note_set_panning(self, arg);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_panning_obj, synthio_note_set_panning);
@ -145,108 +136,54 @@ MP_PROPERTY_GETSET(synthio_note_panning_obj,
(mp_obj_t)&synthio_note_set_panning_obj);
//| tremolo_depth: float
//| """The tremolo depth of the note, from 0 to 1
//| amplitude: BlockInput
//| """The relative amplitude 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) {
//| An amplitude of 0 makes the note inaudible. It is combined multiplicatively with
//| the value from the note's envelope.
//|
//| To achieve a tremolo effect, attach an LFO here."""
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_tremolo_depth(self));
return common_hal_synthio_note_get_amplitude(self);
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_tremolo_depth_obj, synthio_note_get_tremolo_depth);
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_amplitude_obj, synthio_note_get_amplitude);
STATIC mp_obj_t synthio_note_set_tremolo_depth(mp_obj_t self_in, mp_obj_t arg) {
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_tremolo_depth(self, mp_obj_get_float(arg));
common_hal_synthio_note_set_amplitude(self, 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);
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);
//|
//| bend_mode: BendMode
//| """The type of bend operation"""
STATIC mp_obj_t synthio_note_get_bend_mode(mp_obj_t self_in) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
return cp_enum_find(&synthio_bend_mode_type, common_hal_synthio_note_get_bend_mode(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_bend_mode_obj, synthio_note_get_bend_mode);
STATIC mp_obj_t synthio_note_set_bend_mode(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_bend_mode(self, cp_enum_value(&synthio_bend_mode_type, arg, MP_QSTR_bend_mode));
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_bend_mode_obj, synthio_note_set_bend_mode);
MP_PROPERTY_GETSET(synthio_note_bend_mode_obj,
(mp_obj_t)&synthio_note_get_bend_mode_obj,
(mp_obj_t)&synthio_note_set_bend_mode_obj);
//
//| bend: BlockInput
//| """The pitch bend depth of the note, from -1 to +1
//|
//| bend_depth: float
//| """The bend depth of the note, from -1 to +1
//|
//| A depth of 0 disables bend. A depth of 1 corresponds to a bend of 1
//| A depth of 0 plays the programmed frequency. A depth of 1 corresponds to a bend of 1
//| octave. A depth of (1/12) = 0.833 corresponds to a bend of 1 semitone,
//| and a depth of .00833 corresponds to one musical cent.
//| and a depth of .00833 corresponds to one musical cent. Values of ±1 octave are supported.
//|
//| To achieve a vibrato or sweep effect, attach an LFO here.
//| """
STATIC mp_obj_t synthio_note_get_bend_depth(mp_obj_t self_in) {
STATIC mp_obj_t synthio_note_get_bend(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_bend_depth(self));
return common_hal_synthio_note_get_bend(self);
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_bend_depth_obj, synthio_note_get_bend_depth);
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_bend_obj, synthio_note_get_bend);
STATIC mp_obj_t synthio_note_set_bend_depth(mp_obj_t self_in, mp_obj_t arg) {
STATIC mp_obj_t synthio_note_set_bend(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_bend_depth(self, mp_obj_get_float(arg));
common_hal_synthio_note_set_bend(self, arg);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_bend_depth_obj, synthio_note_set_bend_depth);
MP_PROPERTY_GETSET(synthio_note_bend_depth_obj,
(mp_obj_t)&synthio_note_get_bend_depth_obj,
(mp_obj_t)&synthio_note_set_bend_depth_obj);
//| bend_rate: float
//| """The bend rate of the note, in Hz."""
STATIC mp_obj_t synthio_note_get_bend_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_bend_rate(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_bend_rate_obj, synthio_note_get_bend_rate);
STATIC mp_obj_t synthio_note_set_bend_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_bend_rate(self, mp_obj_get_float(arg));
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_bend_rate_obj, synthio_note_set_bend_rate);
MP_PROPERTY_GETSET(synthio_note_bend_rate_obj,
(mp_obj_t)&synthio_note_get_bend_rate_obj,
(mp_obj_t)&synthio_note_set_bend_rate_obj);
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_bend_obj, synthio_note_set_bend);
MP_PROPERTY_GETSET(synthio_note_bend_obj,
(mp_obj_t)&synthio_note_get_bend_obj,
(mp_obj_t)&synthio_note_set_bend_obj);
//| waveform: Optional[ReadableBuffer]
//| """The waveform of this note. Setting the waveform to a buffer of a different size resets the note's phase."""
@ -306,6 +243,31 @@ MP_PROPERTY_GETSET(synthio_note_ring_frequency_obj,
(mp_obj_t)&synthio_note_get_ring_frequency_obj,
(mp_obj_t)&synthio_note_set_ring_frequency_obj);
//| ring_bend: float
//| """The pitch bend depth of the note's ring waveform, from -1 to +1
//|
//| A depth of 0 plays the programmed frequency. A depth of 1 corresponds to a bend of 1
//| octave. A depth of (1/12) = 0.833 corresponds to a bend of 1 semitone,
//| and a depth of .00833 corresponds to one musical cent. Values of ±1 octave are supported.
//|
//| To achieve a vibrato or sweep effect on the ring waveform, attach an LFO here.
//| """
STATIC mp_obj_t synthio_note_get_ring_bend(mp_obj_t self_in) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
return common_hal_synthio_note_get_ring_bend(self);
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_ring_bend_obj, synthio_note_get_ring_bend);
STATIC mp_obj_t synthio_note_set_ring_bend(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_ring_bend(self, arg);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_ring_bend_obj, synthio_note_set_ring_bend);
MP_PROPERTY_GETSET(synthio_note_ring_bend_obj,
(mp_obj_t)&synthio_note_get_ring_bend_obj,
(mp_obj_t)&synthio_note_set_ring_bend_obj);
//| ring_waveform: Optional[ReadableBuffer]
//| """The ring waveform of this note. Setting the ring_waveform to a buffer of a different size resets the note's phase.
//|
@ -340,12 +302,10 @@ STATIC const mp_rom_map_elem_t synthio_note_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_panning), MP_ROM_PTR(&synthio_note_panning_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_bend_depth), MP_ROM_PTR(&synthio_note_bend_depth_obj) },
{ MP_ROM_QSTR(MP_QSTR_bend_rate), MP_ROM_PTR(&synthio_note_bend_rate_obj) },
{ MP_ROM_QSTR(MP_QSTR_bend_mode), MP_ROM_PTR(&synthio_note_bend_mode_obj) },
{ MP_ROM_QSTR(MP_QSTR_amplitude), MP_ROM_PTR(&synthio_note_amplitude_obj) },
{ MP_ROM_QSTR(MP_QSTR_bend), MP_ROM_PTR(&synthio_note_bend_obj) },
{ MP_ROM_QSTR(MP_QSTR_ring_frequency), MP_ROM_PTR(&synthio_note_ring_frequency_obj) },
{ MP_ROM_QSTR(MP_QSTR_ring_bend), MP_ROM_PTR(&synthio_note_ring_bend_obj) },
{ MP_ROM_QSTR(MP_QSTR_ring_waveform), MP_ROM_PTR(&synthio_note_ring_waveform_obj) },
};
STATIC MP_DEFINE_CONST_DICT(synthio_note_locals_dict, synthio_note_locals_dict_table);

View File

@ -12,30 +12,24 @@ void common_hal_synthio_note_set_frequency(synthio_note_obj_t *self, mp_float_t
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_float_t common_hal_synthio_note_get_ring_frequency(synthio_note_obj_t *self);
void common_hal_synthio_note_set_ring_frequency(synthio_note_obj_t *self, mp_float_t value);
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);
mp_float_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_float_t value);
mp_obj_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_obj_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);
synthio_bend_mode_t common_hal_synthio_note_get_bend_mode(synthio_note_obj_t *self);
void common_hal_synthio_note_set_bend_mode(synthio_note_obj_t *self, synthio_bend_mode_t value);
mp_float_t common_hal_synthio_note_get_bend_rate(synthio_note_obj_t *self);
void common_hal_synthio_note_set_bend_rate(synthio_note_obj_t *self, mp_float_t value);
mp_float_t common_hal_synthio_note_get_bend_depth(synthio_note_obj_t *self);
void common_hal_synthio_note_set_bend_depth(synthio_note_obj_t *self, mp_float_t value);
mp_obj_t common_hal_synthio_note_get_bend(synthio_note_obj_t *self);
void common_hal_synthio_note_set_bend(synthio_note_obj_t *self, mp_obj_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_float_t common_hal_synthio_note_get_ring_frequency(synthio_note_obj_t *self);
void common_hal_synthio_note_set_ring_frequency(synthio_note_obj_t *self, mp_float_t value);
mp_obj_t common_hal_synthio_note_get_ring_bend(synthio_note_obj_t *self);
void common_hal_synthio_note_set_ring_bend(synthio_note_obj_t *self, mp_obj_t value);
mp_obj_t common_hal_synthio_note_get_ring_waveform_obj(synthio_note_obj_t *self);
void common_hal_synthio_note_set_ring_waveform(synthio_note_obj_t *self, mp_obj_t value);

View File

@ -24,6 +24,7 @@
* THE SOFTWARE.
*/
#include <math.h>
#include <string.h>
#include "py/enum.h"
@ -35,10 +36,13 @@
#include "extmod/vfs_posix.h"
#include "shared-bindings/synthio/__init__.h"
#include "shared-bindings/synthio/LFO.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))
@ -57,9 +61,11 @@ static const mp_arg_t envelope_properties[] = {
//| """Support for multi-channel audio synthesis
//|
//| At least 2 simultaneous notes are supported. samd5x, mimxrt10xx and rp2040 platforms support up to 12 notes.
//|
//| """
//|
//| BlockInput = Union["LFO", float]
//| """LFOs and Notes can take any of these types as inputs on certain attributes"""
//|
//| class Envelope:
//| def __init__(
//| self,
@ -283,51 +289,34 @@ STATIC mp_obj_t onevo_to_hz(mp_obj_t arg) {
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_onevo_to_hz_obj, onevo_to_hz);
MAKE_ENUM_VALUE(synthio_bend_mode_type, bend_mode, STATIC, SYNTHIO_BEND_MODE_STATIC);
MAKE_ENUM_VALUE(synthio_bend_mode_type, bend_mode, VIBRATO, SYNTHIO_BEND_MODE_VIBRATO);
MAKE_ENUM_VALUE(synthio_bend_mode_type, bend_mode, SWEEP, SYNTHIO_BEND_MODE_SWEEP);
MAKE_ENUM_VALUE(synthio_bend_mode_type, bend_mode, SWEEP_IN, SYNTHIO_BEND_MODE_SWEEP_IN);
//|
//| class BendMode:
//| """Controls the way the ``Note.pitch_bend_depth`` and ``Note.pitch_bend_rate`` properties are interpreted."""
//|
//| STATIC: "BendMode"
//| """The Note's pitch is modified by its ``pitch_bend_depth``. ``pitch_bend_rate`` is ignored."""
//|
//| VIBRATO: "BendMode"
//| """The Note's pitch varies by ``±pitch_bend_depth`` at a rate of ``pitch_bend_rate`` Hz."""
//|
//| SWEEP: "BendMode"
//| """The Note's pitch starts at ``Note.frequency`` then sweeps up or down by ``pitch_bend_depth`` over ``1/pitch_bend_rate`` seconds."""
//|
//| SWEEP_IN: "BendMode"
//| """The Note's pitch sweep is the reverse of ``SWEEP`` mode, starting at the bent pitch and arriving at the tuned pitch."""
//|
MAKE_ENUM_MAP(synthio_bend_mode) {
MAKE_ENUM_MAP_ENTRY(bend_mode, STATIC),
MAKE_ENUM_MAP_ENTRY(bend_mode, VIBRATO),
MAKE_ENUM_MAP_ENTRY(bend_mode, SWEEP),
MAKE_ENUM_MAP_ENTRY(bend_mode, SWEEP_IN),
};
STATIC MP_DEFINE_CONST_DICT(synthio_bend_mode_locals_dict, synthio_bend_mode_locals_table);
MAKE_PRINTER(synthio, synthio_bend_mode);
MAKE_ENUM_TYPE(synthio, BendMode, synthio_bend_mode);
#if CIRCUITPY_AUDIOCORE_DEBUG
STATIC mp_obj_t synthio_lfo_tick(size_t n, const mp_obj_t *args) {
shared_bindings_synthio_lfo_tick(48000);
mp_obj_t result[n];
for (size_t i = 0; i < n; i++) {
synthio_lfo_slot_t slot;
synthio_lfo_assign_input(args[i], &slot, MP_QSTR_arg);
mp_float_t value = synthio_lfo_obj_tick(&slot);
result[i] = mp_obj_new_float(value);
}
return mp_obj_new_tuple(n, result);
}
MP_DEFINE_CONST_FUN_OBJ_VAR(synthio_lfo_tick_obj, 1, synthio_lfo_tick);
#endif
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_BendMode), MP_ROM_PTR(&synthio_bend_mode_type) },
{ 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_LFO), MP_ROM_PTR(&synthio_lfo_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) },
#if CIRCUITPY_AUDIOCORE_DEBUG
{ MP_ROM_QSTR(MP_QSTR_lfo_tick), MP_ROM_PTR(&synthio_lfo_tick_obj) },
#endif
};
STATIC MP_DEFINE_CONST_DICT(synthio_module_globals, synthio_module_globals_table);

View File

@ -24,9 +24,6 @@
* THE SOFTWARE.
*/
#ifndef MICROPY_INCLUDED_SHARED_BINDINGS_UTIL_H
#define MICROPY_INCLUDED_SHARED_BINDINGS_UTIL_H
#include "py/runtime.h"
#include "shared-bindings/util.h"
@ -50,4 +47,10 @@ void properties_print_helper(const mp_print_t *print, mp_obj_t self_in, const mp
mp_print_str(print, ")");
}
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_UTIL_H
void properties_construct_helper(mp_obj_t self_in, const mp_arg_t *args, const mp_arg_val_t *vals, size_t n_properties) {
for (size_t i = 0; i < n_properties; i++) {
if (vals[i].u_obj != NULL) {
mp_store_attr(self_in, args[i].qst, vals[i].u_obj);
}
}
}

View File

@ -24,14 +24,11 @@
* THE SOFTWARE.
*/
#ifndef MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_UTIL_H
#define MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_UTIL_H
#pragma once
#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
void properties_construct_helper(mp_obj_t self_in, const mp_arg_t *args, const mp_arg_val_t *vals, size_t n_properties);

155
shared-module/synthio/LFO.c Normal file
View File

@ -0,0 +1,155 @@
/*
* 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.
*/
#include "shared-bindings/synthio/LFO.h"
#include "shared-module/synthio/LFO.h"
#include <math.h>
#define ONE (MICROPY_FLOAT_CONST(1.))
#define ZERO (MICROPY_FLOAT_CONST(0.))
mp_float_t synthio_lfo_obj_tick(synthio_lfo_slot_t *slot) {
mp_obj_t obj = slot->obj;
if (!mp_obj_is_type(obj, &synthio_lfo_type)) {
return slot->value;
}
synthio_lfo_obj_t *lfo = MP_OBJ_TO_PTR(obj);
if (lfo->last_tick == synthio_global_tick) {
slot->value = lfo->value;
return slot->value;
}
lfo->last_tick = synthio_global_tick;
mp_float_t rate = synthio_lfo_obj_tick(&lfo->rate) * synthio_global_rate_scale;
;
mp_float_t accum = lfo->accum + rate;
int len = lfo->waveform_bufinfo.len / 2;
mp_float_t frac = accum - MICROPY_FLOAT_C_FUN(floor)(accum);
size_t idx = (int)(frac * len);
if (lfo->once) {
if (rate > 0) {
if (accum >= ONE) {
frac = ONE;
idx = len - 1;
}
} else if (rate < 0 && accum < ZERO) {
frac = ZERO;
idx = 0;
}
}
lfo->accum = frac;
int16_t *waveform = lfo->waveform_bufinfo.buf;
assert(idx < lfo->waveform_bufinfo.len / 2);
mp_float_t scale = synthio_lfo_obj_tick(&lfo->scale);
mp_float_t offset = synthio_lfo_obj_tick(&lfo->offset);
mp_float_t value = MICROPY_FLOAT_C_FUN(ldexp)(waveform[idx], -15) * scale + offset;
lfo->value = slot->value = value;
return value;
}
mp_float_t synthio_lfo_obj_tick_limited(synthio_lfo_slot_t *lfo_slot, mp_float_t lo, mp_float_t hi) {
mp_float_t value = synthio_lfo_obj_tick(lfo_slot);
if (value < lo) {
return lo;
}
if (value > hi) {
return hi;
}
return value;
}
int32_t synthio_lfo_obj_tick_scaled(synthio_lfo_slot_t *lfo_slot, mp_float_t lo, mp_float_t hi, int shift) {
mp_float_t value = synthio_lfo_obj_tick_limited(lfo_slot, lo, hi);
return (int32_t)MICROPY_FLOAT_C_FUN(round)(MICROPY_FLOAT_C_FUN(ldexp)(value, shift));
}
void synthio_lfo_assign_input(mp_obj_t obj, synthio_lfo_slot_t *slot, qstr arg_name) {
if (mp_obj_is_type(obj, &synthio_lfo_type)) {
slot->obj = obj;
return;
}
if (!mp_obj_get_float_maybe(obj, &slot->value)) {
mp_raise_TypeError_varg(translate("%q must be of type %q or %q, not %q"), arg_name, MP_QSTR_Lfo, MP_QSTR_float, mp_obj_get_type_qstr(obj));
}
slot->value = mp_obj_get_float(obj);
slot->obj = obj;
}
mp_obj_t common_hal_synthio_lfo_get_waveform_obj(synthio_lfo_obj_t *self) {
return self->waveform_obj;
}
mp_obj_t common_hal_synthio_lfo_get_rate_obj(synthio_lfo_obj_t *self) {
return self->rate.obj;
}
void common_hal_synthio_lfo_set_rate_obj(synthio_lfo_obj_t *self, mp_obj_t arg) {
synthio_lfo_assign_input(arg, &self->rate, MP_QSTR_rate);
}
mp_obj_t common_hal_synthio_lfo_get_scale_obj(synthio_lfo_obj_t *self) {
return self->scale.obj;
}
void common_hal_synthio_lfo_set_scale_obj(synthio_lfo_obj_t *self, mp_obj_t arg) {
synthio_lfo_assign_input(arg, &self->scale, MP_QSTR_scale);
}
mp_obj_t common_hal_synthio_lfo_get_offset_obj(synthio_lfo_obj_t *self) {
return self->offset.obj;
}
void common_hal_synthio_lfo_set_offset_obj(synthio_lfo_obj_t *self, mp_obj_t arg) {
synthio_lfo_assign_input(arg, &self->offset, MP_QSTR_offset);
}
bool common_hal_synthio_lfo_get_once(synthio_lfo_obj_t *self) {
return self->once;
}
void common_hal_synthio_lfo_set_once(synthio_lfo_obj_t *self, bool arg) {
self->once = arg;
}
mp_float_t common_hal_synthio_lfo_get_value(synthio_lfo_obj_t *self) {
return self->value;
}
mp_float_t common_hal_synthio_lfo_get_phase(synthio_lfo_obj_t *self) {
return self->accum;
}
void common_hal_synthio_lfo_retrigger(synthio_lfo_obj_t *self) {
self->accum = 0;
}

View File

@ -0,0 +1,62 @@
/*
* 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 "py/obj.h"
#include "shared-module/synthio/__init__.h"
#include "shared-bindings/synthio/__init__.h"
typedef struct synthio_lfo_slot {
mp_obj_t obj;
mp_float_t value;
} synthio_lfo_slot_t;
typedef struct synthio_lfo_obj {
mp_obj_base_t base;
int32_t sample_rate;
uint8_t last_tick;
bool once;
synthio_lfo_slot_t rate, scale, offset;
mp_float_t accum, value;
mp_obj_t waveform_obj;
mp_buffer_info_t waveform_bufinfo;
} synthio_lfo_obj_t;
// Update the value inside the lfo slot if the value is an LFO, returning the new value
mp_float_t synthio_lfo_obj_tick(synthio_lfo_slot_t *lfo_slot);
// the same, but the output is constrained to be between lo and hi
mp_float_t synthio_lfo_obj_tick_limited(synthio_lfo_slot_t *lfo_slot, mp_float_t lo, mp_float_t hi);
// the same, but the output is constrained to be between lo and hi and converted to an integer with `shift` fractional bits
int32_t synthio_lfo_obj_tick_scaled(synthio_lfo_slot_t *lfo_slot, mp_float_t lo, mp_float_t hi, int shift);
// Assign an object (which may be a float or a synthio_lfo_obj_t) to an lfo slot
void synthio_lfo_assign_input(mp_obj_t obj, synthio_lfo_slot_t *lfo_slot, qstr arg_name);

View File

@ -30,10 +30,6 @@
#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;
}
@ -62,73 +58,36 @@ void common_hal_synthio_note_set_ring_frequency(synthio_note_obj_t *self, mp_flo
self->ring_frequency_scaled = synthio_frequency_convert_float_to_scaled(val);
}
mp_float_t common_hal_synthio_note_get_panning(synthio_note_obj_t *self) {
return self->panning;
mp_obj_t common_hal_synthio_note_get_panning(synthio_note_obj_t *self) {
return self->panning.obj;
}
void common_hal_synthio_note_set_panning(synthio_note_obj_t *self, mp_float_t value_in) {
mp_float_t val = mp_arg_validate_float_range(value_in, -1, 1, MP_QSTR_panning);
self->panning = val;
if (val >= 0) {
self->left_panning_scaled = 32768;
self->right_panning_scaled = 32768 - round_float_to_int(val * 32768);
} else {
self->right_panning_scaled = 32768;
self->left_panning_scaled = 32768 + round_float_to_int(val * 32768);
}
void common_hal_synthio_note_set_panning(synthio_note_obj_t *self, mp_obj_t value_in) {
synthio_lfo_assign_input(value_in, &self->panning, MP_QSTR_panning);
}
mp_float_t common_hal_synthio_note_get_tremolo_depth(synthio_note_obj_t *self) {
return self->tremolo_descr.amplitude;
mp_obj_t common_hal_synthio_note_get_amplitude(synthio_note_obj_t *self) {
return self->amplitude.obj;
}
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 * 32768);
void common_hal_synthio_note_set_amplitude(synthio_note_obj_t *self, mp_obj_t value_in) {
synthio_lfo_assign_input(value_in, &self->amplitude, MP_QSTR_amplitude);
}
mp_float_t common_hal_synthio_note_get_tremolo_rate(synthio_note_obj_t *self) {
return self->tremolo_descr.frequency;
mp_obj_t common_hal_synthio_note_get_bend(synthio_note_obj_t *self) {
return self->bend.obj;
}
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 * 65536, self->sample_rate);
}
void common_hal_synthio_note_set_bend(synthio_note_obj_t *self, mp_obj_t value_in) {
synthio_lfo_assign_input(value_in, &self->bend, MP_QSTR_bend);
}
mp_float_t common_hal_synthio_note_get_bend_depth(synthio_note_obj_t *self) {
return self->bend_descr.amplitude;
mp_obj_t common_hal_synthio_note_get_ring_bend(synthio_note_obj_t *self) {
return self->ring_bend.obj;
}
void common_hal_synthio_note_set_bend_depth(synthio_note_obj_t *self, mp_float_t value_in) {
mp_float_t val = mp_arg_validate_float_range(value_in, -1, 1, MP_QSTR_bend_depth);
self->bend_descr.amplitude = val;
self->bend_state.amplitude_scaled = round_float_to_int(val * 32768);
}
mp_float_t common_hal_synthio_note_get_bend_rate(synthio_note_obj_t *self) {
return self->bend_descr.frequency;
}
synthio_bend_mode_t common_hal_synthio_note_get_bend_mode(synthio_note_obj_t *self) {
return self->bend_mode;
}
void common_hal_synthio_note_set_bend_mode(synthio_note_obj_t *self, synthio_bend_mode_t value) {
self->bend_mode = value;
}
void common_hal_synthio_note_set_bend_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_bend_rate);
self->bend_descr.frequency = val;
if (self->sample_rate != 0) {
self->bend_state.dds = synthio_frequency_convert_float_to_dds(val * 65536, self->sample_rate);
}
void common_hal_synthio_note_set_ring_bend(synthio_note_obj_t *self, mp_obj_t value_in) {
synthio_lfo_assign_input(value_in, &self->ring_bend, MP_QSTR_ring_bend);
}
mp_obj_t common_hal_synthio_note_get_envelope_obj(synthio_note_obj_t *self) {
@ -184,22 +143,10 @@ void synthio_note_recalculate(synthio_note_obj_t *self, int32_t 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->bend_state, &self->bend_descr, sample_rate);
self->bend_state.offset_scaled = 32768;
}
void synthio_note_start(synthio_note_obj_t *self, int32_t sample_rate) {
synthio_note_recalculate(self, sample_rate);
if (self->bend_mode != SYNTHIO_BEND_MODE_VIBRATO) {
self->bend_state.phase = 0;
}
}
uint32_t synthio_note_envelope(synthio_note_obj_t *self) {
return self->amplitude_scaled;
}
// Perform a pitch bend operation
@ -213,10 +160,10 @@ uint32_t synthio_note_envelope(synthio_note_obj_t *self) {
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;
STATIC uint32_t pitch_bend(uint32_t frequency_scaled, int16_t bend_value) {
bool down = (bend_value < 0);
if (down) {
bend_value += 32768;
}
uint32_t bend_value_semitone = (uint32_t)bend_value * 24; // 65536/semitone
uint32_t semitone = bend_value_semitone >> 16;
@ -227,26 +174,34 @@ STATIC uint32_t pitch_bend(uint32_t frequency_scaled, uint16_t bend_value) {
return (frequency_scaled * (uint64_t)f) >> (15 + down);
}
STATIC int synthio_bend_value(synthio_note_obj_t *self, int16_t dur) {
switch (self->bend_mode) {
case SYNTHIO_BEND_MODE_STATIC:
return self->bend_state.amplitude_scaled + self->bend_state.offset_scaled;
case SYNTHIO_BEND_MODE_VIBRATO:
return synthio_lfo_step(&self->bend_state, dur);
case SYNTHIO_BEND_MODE_SWEEP:
return synthio_sweep_step(&self->bend_state, dur);
case SYNTHIO_BEND_MODE_SWEEP_IN:
return synthio_sweep_in_step(&self->bend_state, dur);
default:
return 32768;
}
}
#define ZERO MICROPY_FLOAT_CONST(0.)
#define ONE MICROPY_FLOAT_CONST(1.)
#define ALMOST_ONE (MICROPY_FLOAT_CONST(32767.) / 32768)
uint32_t synthio_note_step(synthio_note_obj_t *self, int32_t sample_rate, int16_t dur, uint16_t loudness[2]) {
int tremolo_value = synthio_lfo_step(&self->tremolo_state, dur);
loudness[0] = (((loudness[0] * tremolo_value) >> 15) * self->left_panning_scaled) >> 15;
loudness[1] = (((loudness[1] * tremolo_value) >> 15) * self->right_panning_scaled) >> 15;
int bend_value = synthio_bend_value(self, dur);
int panning = synthio_lfo_obj_tick_scaled(&self->panning, -ALMOST_ONE, ALMOST_ONE, 15);
int left_panning_scaled, right_panning_scaled;
if (panning >= 0) {
left_panning_scaled = 32768;
right_panning_scaled = 32767 - panning;
} else {
right_panning_scaled = 32768;
left_panning_scaled = 32767 + panning;
}
int amplitude = synthio_lfo_obj_tick_scaled(&self->amplitude, ZERO, ALMOST_ONE, 15);
left_panning_scaled = (left_panning_scaled * amplitude) >> 15;
right_panning_scaled = (right_panning_scaled * amplitude) >> 15;
loudness[0] = (loudness[0] * left_panning_scaled) >> 15;
loudness[1] = (loudness[1] * right_panning_scaled) >> 15;
if (self->ring_frequency_scaled != 0) {
int ring_bend_value = synthio_lfo_obj_tick_scaled(&self->ring_bend, -ONE, ALMOST_ONE, 15);
self->ring_frequency_bent = pitch_bend(self->ring_frequency_scaled, ring_bend_value);
}
int bend_value = synthio_lfo_obj_tick_scaled(&self->bend, -ONE, ALMOST_ONE, 15);
uint32_t frequency_scaled = pitch_bend(self->frequency_scaled, bend_value);
return frequency_scaled;
}

View File

@ -27,25 +27,22 @@
#pragma once
#include "shared-module/synthio/__init__.h"
#include "shared-module/synthio/LFO.h"
#include "shared-bindings/synthio/__init__.h"
typedef struct synthio_note_obj {
mp_obj_base_t base;
synthio_lfo_slot_t panning, bend, amplitude, ring_bend;
mp_float_t frequency, ring_frequency;
mp_float_t panning;
mp_obj_t waveform_obj, envelope_obj, ring_waveform_obj;
int32_t sample_rate;
int32_t frequency_scaled;
int32_t ring_frequency_scaled;
int32_t amplitude_scaled;
int32_t left_panning_scaled, right_panning_scaled;
int32_t ring_frequency_scaled, ring_frequency_bent;
bool filter;
synthio_bend_mode_t bend_mode;
synthio_lfo_descr_t tremolo_descr, bend_descr;
synthio_lfo_state_t tremolo_state, bend_state;
mp_buffer_info_t waveform_buf;
mp_buffer_info_t ring_waveform_buf;
@ -56,4 +53,3 @@ 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[2]);
void synthio_note_start(synthio_note_obj_t *self, int32_t sample_rate);
bool synthio_note_playing(synthio_note_obj_t *self);
uint32_t synthio_note_envelope(synthio_note_obj_t *self);

View File

@ -32,6 +32,9 @@
#include <math.h>
#include <stdlib.h>
mp_float_t synthio_global_rate_scale;
uint8_t synthio_global_tick;
STATIC const int16_t square_wave[] = {-32768, 32767};
STATIC const uint16_t notes[] = {8372, 8870, 9397, 9956, 10548, 11175, 11840,
@ -219,7 +222,7 @@ static void synth_note_into_buffer(synthio_synth_t *synth, int chan, int32_t *ou
if (note->ring_frequency_scaled != 0 && note->ring_waveform_buf.buf) {
ring_waveform = note->ring_waveform_buf.buf;
ring_waveform_length = note->ring_waveform_buf.len / sizeof(int16_t);
ring_dds_rate = synthio_frequency_convert_scaled_to_dds((uint64_t)note->ring_frequency_scaled * ring_waveform_length, sample_rate);
ring_dds_rate = synthio_frequency_convert_scaled_to_dds((uint64_t)note->ring_frequency_bent * ring_waveform_length, sample_rate);
uint32_t lim = ring_waveform_length << SYNTHIO_FREQUENCY_SHIFT;
if (ring_dds_rate > lim / sizeof(int16_t)) {
ring_dds_rate = 0; // can't ring at that frequency
@ -350,6 +353,8 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
return;
}
shared_bindings_synthio_lfo_tick(synth->sample_rate);
synth->buffer_index = !synth->buffer_index;
synth->other_channel = 1 - channel;
synth->other_buffer_index = synth->buffer_index;
@ -587,3 +592,8 @@ int synthio_lfo_step(synthio_lfo_state_t *state, uint16_t dur) {
}
return v / 16384 + state->offset_scaled;
}
void shared_bindings_synthio_lfo_tick(uint32_t sample_rate) {
synthio_global_rate_scale = (mp_float_t)SYNTHIO_MAX_DUR / sample_rate;
synthio_global_tick++;
}

View File

@ -112,3 +112,7 @@ void synthio_lfo_set(synthio_lfo_state_t *state, const synthio_lfo_descr_t *desc
int synthio_lfo_step(synthio_lfo_state_t *state, uint16_t dur);
int synthio_sweep_step(synthio_lfo_state_t *state, uint16_t dur);
int synthio_sweep_in_step(synthio_lfo_state_t *state, uint16_t dur);
extern mp_float_t synthio_global_rate_scale;
extern uint8_t synthio_global_tick;
void shared_bindings_synthio_lfo_tick(uint32_t sample_rate);

View File

@ -65,6 +65,8 @@ noise = np.array(
dtype=np.int16,
)
bend_in = np.linspace(32767, 0, num=SAMPLE_SIZE, endpoint=True, 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
)
@ -157,8 +159,8 @@ def synthesize4(synth):
synth.press(chord)
for i in range(16):
for c in chord:
c.tremolo_depth = i / 50
c.tremolo_rate = (i + 1) / 4
d = i / 50
c.amplitude = synthio.LFO(scale=d / 2, offset=1 - d, rate=(i + 1) / 4, waveform=sine)
yield 48
yield 36
@ -171,17 +173,14 @@ def synthesize5(synth):
synth.press(chord)
for i in range(16):
for c in chord:
c.bend_depth = 1 / 24
c.bend_rate = (i + 1) / 2
c.bend = synthio.LFO(scale=1 / 24, rate=(i + 1) / 2, waveform=sine)
yield 24
synth.release_all()
yield 100
for c in chord:
synth.release_all()
c.bend_mode = synthio.BendMode.SWEEP_IN
c.bend_depth = randf(-1, 1)
c.bend_rate = 1 / 2
c.bend = synthio.LFO(scale=randf(-1, 1), rate=1 / 2, waveform=bend_in)
synth.press(chord)
yield 320

View File

@ -25,6 +25,9 @@ fast_decay_envelope = synthio.Envelope(
synth = synthio.Synthesizer(sample_rate=48000)
# for a note without sustain phase,
# switching to an envelope with decay_time 0.25 is about the same as
# releasing with a release time of 0.25
def synthesize(synth):
notes = (synthio.Note(frequency=440, waveform=sine, envelope=envelope),)
synth.press(notes)

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]
(Note(frequency=830.6076004423605, panning=0.0, tremolo_rate=0.0, tremolo_depth=0.0, bend_rate=0.0, bend_depth=0.0, bend_mode=synthio.BendMode.VIBRATO, waveform=None, envelope=None, filter=True, ring_frequency=0.0, ring_waveform=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, panning=0.0, tremolo_rate=0.0, tremolo_depth=0.0, bend_rate=0.0, bend_depth=0.0, bend_mode=synthio.BendMode.VIBRATO, waveform=None, envelope=None, filter=True, ring_frequency=0.0, ring_waveform=None), Note(frequency=830.6076004423605, panning=0.0, tremolo_rate=0.0, tremolo_depth=0.0, bend_rate=0.0, bend_depth=0.0, bend_mode=synthio.BendMode.VIBRATO, waveform=None, envelope=None, filter=True, ring_frequency=0.0, ring_waveform=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, panning=0.0, tremolo_rate=0.0, tremolo_depth=0.0, bend_rate=0.0, bend_depth=0.0, bend_mode=synthio.BendMode.VIBRATO, waveform=None, envelope=None, filter=True, ring_frequency=0.0, ring_waveform=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)
(Note(frequency=830.6076004423605, panning=0, amplitude=1, bend=0, waveform=None, envelope=None, filter=True, ring_frequency=0.0, ring_bend=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, amplitude=1, bend=0, waveform=None, envelope=None, filter=True, ring_frequency=0.0, ring_bend=0, ring_waveform=None), Note(frequency=830.6076004423605, panning=0, amplitude=1, bend=0, waveform=None, envelope=None, filter=True, ring_frequency=0.0, ring_bend=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, amplitude=1, bend=0, waveform=None, envelope=None, filter=True, ring_frequency=0.0, ring_bend=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)
(-15727, 15726)
(-16383, 16382)
(-14286, 14285)
(-13106, 13105)
(-13106, 13105)
(-13106, 13105)
(-13106, 13105)
(-13106, 13105)
(-13106, 13105)
(-13106, 13105)
(-13106, 13105)
(-11009, 11008)
(-8912, 8911)
(-6815, 6814)
(-4718, 4717)
(-2621, 2620)
(-524, 523)
(0, 0)
(0, 0)
(0, 0)