Skip to content

Commit f832123

Browse files
committed
synthio: Add LFOs
1 parent 827eaeb commit f832123

18 files changed

Lines changed: 721 additions & 304 deletions

File tree

ports/unix/variants/coverage/mpconfigvariant.mk

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ SRC_BITMAP := \
4242
shared-bindings/struct/__init__.c \
4343
shared-bindings/synthio/__init__.c \
4444
shared-bindings/synthio/MidiTrack.c \
45+
shared-bindings/synthio/LFO.c \
4546
shared-bindings/synthio/Note.c \
4647
shared-bindings/synthio/Synthesizer.c \
4748
shared-bindings/traceback/__init__.c \
@@ -65,6 +66,7 @@ SRC_BITMAP := \
6566
shared-module/struct/__init__.c \
6667
shared-module/synthio/__init__.c \
6768
shared-module/synthio/MidiTrack.c \
69+
shared-module/synthio/LFO.c \
6870
shared-module/synthio/Note.c \
6971
shared-module/synthio/Synthesizer.c \
7072
shared-module/traceback/__init__.c \

py/circuitpy_defns.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,7 @@ SRC_SHARED_MODULE_ALL = \
650650
struct/__init__.c \
651651
supervisor/__init__.c \
652652
supervisor/StatusBar.c \
653+
synthio/LFO.c \
653654
synthio/MidiTrack.c \
654655
synthio/Note.c \
655656
synthio/Synthesizer.c \

shared-bindings/synthio/LFO.c

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
/*
2+
* This file is part of the Micro Python project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2021 Artyom Skrobov
7+
* Copyright (c) 2023 Jeff Epler for Adafruit Industries
8+
*
9+
* Permission is hereby granted, free of charge, to any person obtaining a copy
10+
* of this software and associated documentation files (the "Software"), to deal
11+
* in the Software without restriction, including without limitation the rights
12+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
* copies of the Software, and to permit persons to whom the Software is
14+
* furnished to do so, subject to the following conditions:
15+
*
16+
* The above copyright notice and this permission notice shall be included in
17+
* all copies or substantial portions of the Software.
18+
*
19+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
* THE SOFTWARE.
26+
*/
27+
28+
#include "py/obj.h"
29+
#include "py/objproperty.h"
30+
#include "py/runtime.h"
31+
#include "shared-bindings/util.h"
32+
#include "shared-bindings/synthio/LFO.h"
33+
#include "shared-module/synthio/LFO.h"
34+
35+
//| class LFO:
36+
//| """A low-frequency oscillator
37+
//|
38+
//| Every `rate` seconds, the output of the LFO cycles through its `waveform`.
39+
//| The output at any particular moment is ``waveform[idx] * scale + offset``.
40+
//| Internally, the calculation takes place in fixed point for speed.
41+
//|
42+
//| `rate`, `offset`, `scale`, and `once` can be changed at run-time.
43+
//|
44+
//| An LFO only updates if it is actually associated with a playing Note,
45+
//| including if it is indirectly associated with the Note via an intermediate
46+
//| LFO.
47+
//|
48+
//| Using the same LFO as an input to multiple other LFOs or Notes is OK, but
49+
//| the result if an LFO is tied to multiple Synthtesizer objects is undefined."""
50+
//|
51+
//| def __init__(
52+
//| self,
53+
//| waveform: ReadableBuffer,
54+
//| *,
55+
//| rate: BlockInput = 1.0,
56+
//| scale: BlockInput = 1.0,
57+
//| offset: BlockInput = 0,
58+
//| once=False
59+
//| ):
60+
//| pass
61+
static const mp_arg_t lfo_properties[] = {
62+
{ MP_QSTR_waveform, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = NULL } },
63+
{ MP_QSTR_rate, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(1) } },
64+
{ MP_QSTR_scale, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(1) } },
65+
{ MP_QSTR_offset, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(0) } },
66+
{ MP_QSTR_once, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(0) } },
67+
};
68+
69+
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) {
70+
enum { ARG_waveform }; // others never directly referred to by argument number
71+
72+
mp_arg_val_t args[MP_ARRAY_SIZE(lfo_properties)];
73+
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(lfo_properties), lfo_properties, args);
74+
75+
synthio_lfo_obj_t *self = m_new_obj(synthio_lfo_obj_t);
76+
self->base.type = &synthio_lfo_type;
77+
78+
synthio_synth_parse_waveform(&self->waveform_bufinfo, args[ARG_waveform].u_obj);
79+
self->waveform_obj = args[ARG_waveform].u_obj;
80+
self->last_tick = synthio_global_tick;
81+
82+
mp_obj_t result = MP_OBJ_FROM_PTR(self);
83+
properties_construct_helper(result, lfo_properties + 1, args + 1, MP_ARRAY_SIZE(lfo_properties) - 1);
84+
85+
return result;
86+
};
87+
88+
//| waveform: Optional[ReadableBuffer]
89+
//| """The waveform of this lfo. (read-only, but the values in the buffer may be modified dynamically)"""
90+
STATIC mp_obj_t synthio_lfo_get_waveform(mp_obj_t self_in) {
91+
synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in);
92+
return common_hal_synthio_lfo_get_waveform_obj(self);
93+
}
94+
MP_DEFINE_CONST_FUN_OBJ_1(synthio_lfo_get_waveform_obj, synthio_lfo_get_waveform);
95+
96+
MP_PROPERTY_GETTER(synthio_lfo_waveform_obj,
97+
(mp_obj_t)&synthio_lfo_get_waveform_obj);
98+
99+
//| rate: BlockInput
100+
//| """The rate (in Hz) at which the LFO cycles through its waveform"""
101+
STATIC mp_obj_t synthio_lfo_get_rate(mp_obj_t self_in) {
102+
synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in);
103+
return common_hal_synthio_lfo_get_rate_obj(self);
104+
}
105+
MP_DEFINE_CONST_FUN_OBJ_1(synthio_lfo_get_rate_obj, synthio_lfo_get_rate);
106+
107+
STATIC mp_obj_t synthio_lfo_set_rate(mp_obj_t self_in, mp_obj_t arg) {
108+
synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in);
109+
common_hal_synthio_lfo_set_rate_obj(self, arg);
110+
return mp_const_none;
111+
}
112+
MP_DEFINE_CONST_FUN_OBJ_2(synthio_lfo_set_rate_obj, synthio_lfo_set_rate);
113+
MP_PROPERTY_GETSET(synthio_lfo_rate_obj,
114+
(mp_obj_t)&synthio_lfo_get_rate_obj,
115+
(mp_obj_t)&synthio_lfo_set_rate_obj);
116+
117+
118+
//| offset: BlockInput
119+
//| """An additive value applied to the LFO's output"""
120+
STATIC mp_obj_t synthio_lfo_get_offset(mp_obj_t self_in) {
121+
synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in);
122+
return common_hal_synthio_lfo_get_offset_obj(self);
123+
}
124+
MP_DEFINE_CONST_FUN_OBJ_1(synthio_lfo_get_offset_obj, synthio_lfo_get_offset);
125+
126+
STATIC mp_obj_t synthio_lfo_set_offset(mp_obj_t self_in, mp_obj_t arg) {
127+
synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in);
128+
common_hal_synthio_lfo_set_offset_obj(self, arg);
129+
return mp_const_none;
130+
}
131+
MP_DEFINE_CONST_FUN_OBJ_2(synthio_lfo_set_offset_obj, synthio_lfo_set_offset);
132+
MP_PROPERTY_GETSET(synthio_lfo_offset_obj,
133+
(mp_obj_t)&synthio_lfo_get_offset_obj,
134+
(mp_obj_t)&synthio_lfo_set_offset_obj);
135+
136+
//| scale: BlockInput
137+
//| """An additive value applied to the LFO's output"""
138+
STATIC mp_obj_t synthio_lfo_get_scale(mp_obj_t self_in) {
139+
synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in);
140+
return common_hal_synthio_lfo_get_scale_obj(self);
141+
}
142+
MP_DEFINE_CONST_FUN_OBJ_1(synthio_lfo_get_scale_obj, synthio_lfo_get_scale);
143+
144+
STATIC mp_obj_t synthio_lfo_set_scale(mp_obj_t self_in, mp_obj_t arg) {
145+
synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in);
146+
common_hal_synthio_lfo_set_scale_obj(self, arg);
147+
return mp_const_none;
148+
}
149+
MP_DEFINE_CONST_FUN_OBJ_2(synthio_lfo_set_scale_obj, synthio_lfo_set_scale);
150+
MP_PROPERTY_GETSET(synthio_lfo_scale_obj,
151+
(mp_obj_t)&synthio_lfo_get_scale_obj,
152+
(mp_obj_t)&synthio_lfo_set_scale_obj);
153+
154+
//|
155+
//| once: bool
156+
//| """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"""
157+
STATIC mp_obj_t synthio_lfo_get_once(mp_obj_t self_in) {
158+
synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in);
159+
return mp_obj_new_bool(common_hal_synthio_lfo_get_once(self));
160+
}
161+
MP_DEFINE_CONST_FUN_OBJ_1(synthio_lfo_get_once_obj, synthio_lfo_get_once);
162+
163+
STATIC mp_obj_t synthio_lfo_set_once(mp_obj_t self_in, mp_obj_t arg) {
164+
synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in);
165+
common_hal_synthio_lfo_set_once(self, mp_obj_is_true(arg));
166+
return mp_const_none;
167+
}
168+
MP_DEFINE_CONST_FUN_OBJ_2(synthio_lfo_set_once_obj, synthio_lfo_set_once);
169+
MP_PROPERTY_GETSET(synthio_lfo_once_obj,
170+
(mp_obj_t)&synthio_lfo_get_once_obj,
171+
(mp_obj_t)&synthio_lfo_set_once_obj);
172+
173+
174+
//|
175+
//| phase: float
176+
//| """The phase of the oscillator, in the range 0 to 1 (read-only)"""
177+
STATIC mp_obj_t synthio_lfo_get_phase(mp_obj_t self_in) {
178+
synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in);
179+
return mp_obj_new_float(common_hal_synthio_lfo_get_phase(self));
180+
}
181+
MP_DEFINE_CONST_FUN_OBJ_1(synthio_lfo_get_phase_obj, synthio_lfo_get_phase);
182+
183+
MP_PROPERTY_GETTER(synthio_lfo_phase_obj,
184+
(mp_obj_t)&synthio_lfo_get_phase_obj);
185+
186+
187+
//|
188+
//| value: float
189+
//| """The value of the oscillator (read-only)"""
190+
STATIC mp_obj_t synthio_lfo_get_value(mp_obj_t self_in) {
191+
synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in);
192+
return mp_obj_new_float(common_hal_synthio_lfo_get_value(self));
193+
}
194+
MP_DEFINE_CONST_FUN_OBJ_1(synthio_lfo_get_value_obj, synthio_lfo_get_value);
195+
196+
MP_PROPERTY_GETTER(synthio_lfo_value_obj,
197+
(mp_obj_t)&synthio_lfo_get_value_obj);
198+
199+
200+
//|
201+
//| def retrigger(self):
202+
//| """Reset the LFO's internal index to the start of the waveform. Most useful when it its `once` property is `True`."""
203+
//|
204+
STATIC mp_obj_t synthio_lfo_retrigger(mp_obj_t self_in) {
205+
synthio_lfo_obj_t *self = MP_OBJ_TO_PTR(self_in);
206+
common_hal_synthio_lfo_retrigger(self);
207+
return mp_const_none;
208+
}
209+
MP_DEFINE_CONST_FUN_OBJ_1(synthio_lfo_retrigger_obj, synthio_lfo_retrigger);
210+
211+
static void lfo_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
212+
(void)kind;
213+
properties_print_helper(print, self_in, lfo_properties, MP_ARRAY_SIZE(lfo_properties));
214+
}
215+
216+
STATIC const mp_rom_map_elem_t synthio_lfo_locals_dict_table[] = {
217+
{ MP_ROM_QSTR(MP_QSTR_waveform), MP_ROM_PTR(&synthio_lfo_waveform_obj) },
218+
{ MP_ROM_QSTR(MP_QSTR_rate), MP_ROM_PTR(&synthio_lfo_rate_obj) },
219+
{ MP_ROM_QSTR(MP_QSTR_scale), MP_ROM_PTR(&synthio_lfo_scale_obj) },
220+
{ MP_ROM_QSTR(MP_QSTR_offset), MP_ROM_PTR(&synthio_lfo_offset_obj) },
221+
{ MP_ROM_QSTR(MP_QSTR_once), MP_ROM_PTR(&synthio_lfo_once_obj) },
222+
{ MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&synthio_lfo_value_obj) },
223+
{ MP_ROM_QSTR(MP_QSTR_phase), MP_ROM_PTR(&synthio_lfo_phase_obj) },
224+
{ MP_ROM_QSTR(MP_QSTR_retrigger), MP_ROM_PTR(&synthio_lfo_retrigger_obj) },
225+
};
226+
STATIC MP_DEFINE_CONST_DICT(synthio_lfo_locals_dict, synthio_lfo_locals_dict_table);
227+
228+
const mp_obj_type_t synthio_lfo_type = {
229+
{ &mp_type_type },
230+
.name = MP_QSTR_LFO,
231+
.make_new = synthio_lfo_make_new,
232+
.locals_dict = (mp_obj_dict_t *)&synthio_lfo_locals_dict,
233+
.print = lfo_print,
234+
};

shared-bindings/synthio/LFO.h

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* This file is part of the Micro Python project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2023 Jeff Epler for Adafruit Industries
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
*/
26+
27+
#pragma once
28+
29+
#include "py/obj.h"
30+
31+
typedef struct synthio_lfo_obj synthio_lfo_obj_t;
32+
extern const mp_obj_type_t synthio_lfo_type;
33+
34+
mp_obj_t common_hal_synthio_lfo_get_waveform_obj(synthio_lfo_obj_t *self);
35+
void common_hal_synthio_lfo_set_waveform_obj(synthio_lfo_obj_t *self, mp_obj_t arg);
36+
37+
mp_obj_t common_hal_synthio_lfo_get_rate_obj(synthio_lfo_obj_t *self);
38+
void common_hal_synthio_lfo_set_rate_obj(synthio_lfo_obj_t *self, mp_obj_t arg);
39+
40+
mp_obj_t common_hal_synthio_lfo_get_scale_obj(synthio_lfo_obj_t *self);
41+
void common_hal_synthio_lfo_set_scale_obj(synthio_lfo_obj_t *self, mp_obj_t arg);
42+
43+
mp_obj_t common_hal_synthio_lfo_get_offset_obj(synthio_lfo_obj_t *self);
44+
void common_hal_synthio_lfo_set_offset_obj(synthio_lfo_obj_t *self, mp_obj_t arg);
45+
46+
bool common_hal_synthio_lfo_get_once(synthio_lfo_obj_t *self);
47+
void common_hal_synthio_lfo_set_once(synthio_lfo_obj_t *self, bool arg);
48+
49+
mp_float_t common_hal_synthio_lfo_get_value(synthio_lfo_obj_t *self);
50+
51+
mp_float_t common_hal_synthio_lfo_get_phase(synthio_lfo_obj_t *self);
52+
53+
void common_hal_synthio_lfo_retrigger(synthio_lfo_obj_t *self);

0 commit comments

Comments
 (0)