Skip to content

Commit b3dffa0

Browse files
committed
Initial commit
1 parent c0a905c commit b3dffa0

File tree

6 files changed

+724
-0
lines changed

6 files changed

+724
-0
lines changed

Diff for: py/circuitpy_defns.mk

+1
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,7 @@ SRC_SHARED_MODULE_ALL = \
624624
audiocore/WaveFile.c \
625625
audiocore/__init__.c \
626626
audiodelays/Echo.c \
627+
audiodelays/Chorus.c \
627628
audiodelays/__init__.c \
628629
audiofilters/Distortion.c \
629630
audiofilters/Filter.c \

Diff for: shared-bindings/audiodelays/Chorus.c

+260
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
// This file is part of the CircuitPython project: https://circuitpython.org
2+
//
3+
// SPDX-FileCopyrightText: Copyright (c) 2025 Mark Komus
4+
//
5+
// SPDX-License-Identifier: MIT
6+
7+
#include <stdint.h>
8+
9+
#include "shared-bindings/audiodelays/Chorus.h"
10+
#include "shared-module/audiodelays/Chorus.h"
11+
12+
#include "shared/runtime/context_manager_helpers.h"
13+
#include "py/binary.h"
14+
#include "py/objproperty.h"
15+
#include "py/runtime.h"
16+
#include "shared-bindings/util.h"
17+
#include "shared-module/synthio/block.h"
18+
19+
//| class Chorus:
20+
//| """An Chorus effect"""
21+
//|
22+
//| def __init__(
23+
//| self,
24+
//| max_delay_ms: int = 50,
25+
//| delay_ms: BlockInput = 50.0,
26+
//| voices: synthio.BlockInput = 1.0,
27+
//| buffer_size: int = 512,
28+
//| sample_rate: int = 8000,
29+
//| bits_per_sample: int = 16,
30+
//| samples_signed: bool = True,
31+
//| channel_count: int = 1,
32+
//| ) -> None:
33+
//| """Create a Chorus effect by playing the current sample along with one or more samples
34+
//| (the voices) from the delay buffer. The voices played are evenly spaced across the delay
35+
//| buffer. So for 2 voices you would hear the current sample and the one delay milliseconds back.
36+
//| The delay timing of the chorus can be changed at runtime with the delay_ms parameter but the delay
37+
//| can never exceed the max_delay_ms parameter. The maximum delay is 100ms.
38+
//|
39+
//| :param int max_delay_ms: The maximum time the chorus can be in milliseconds
40+
//| :param synthio.BlockInput delay_ms: The current time of the chorus delay in milliseconds. Must be less the max_delay_ms.
41+
//| :param synthio.BlockInput voices: The number of voices playing split evenly over the delay buffer.
42+
//| :param int buffer_size: The total size in bytes of each of the two playback buffers to use
43+
//| :param int sample_rate: The sample rate to be used
44+
//| :param int channel_count: The number of channels the source samples contain. 1 = mono; 2 = stereo.
45+
//| :param int bits_per_sample: The bits per sample of the effect
46+
//| :param bool samples_signed: Effect is signed (True) or unsigned (False)
47+
//|
48+
//| Playing adding an chorus to a synth::
49+
//|
50+
//| import time
51+
//| import board
52+
//| import audiobusio
53+
//| import synthio
54+
//| import audiodelays
55+
//|
56+
//| audio = audiobusio.I2SOut(bit_clock=board.GP20, word_select=board.GP21, data=board.GP22)
57+
//| synth = synthio.Synthesizer(channel_count=1, sample_rate=44100)
58+
//| chorus = audiodelays.Chorus(max_delay_ms=50, delay_ms=5, buffer_size=1024, channel_count=1, sample_rate=44100)
59+
//| chorus.play(synth)
60+
//| audio.play(chorus)
61+
//|
62+
//| note = synthio.Note(261)
63+
//| while True:
64+
//| synth.press(note)
65+
//| time.sleep(0.25)
66+
//| synth.release(note)
67+
//| time.sleep(5)"""
68+
//| ...
69+
//|
70+
static mp_obj_t audiodelays_chorus_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
71+
enum { ARG_max_delay_ms, ARG_delay_ms, ARG_voices, ARG_buffer_size, ARG_sample_rate, ARG_bits_per_sample, ARG_samples_signed, ARG_channel_count, };
72+
static const mp_arg_t allowed_args[] = {
73+
{ MP_QSTR_max_delay_ms, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 50 } },
74+
{ MP_QSTR_delay_ms, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} },
75+
{ MP_QSTR_voices, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} },
76+
{ MP_QSTR_buffer_size, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 512} },
77+
{ MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 8000} },
78+
{ MP_QSTR_bits_per_sample, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 16} },
79+
{ MP_QSTR_samples_signed, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = true} },
80+
{ MP_QSTR_channel_count, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 1 } },
81+
};
82+
83+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
84+
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
85+
86+
mp_int_t max_delay_ms = mp_arg_validate_int_range(args[ARG_max_delay_ms].u_int, 1, 100, MP_QSTR_max_delay_ms);
87+
88+
mp_int_t channel_count = mp_arg_validate_int_range(args[ARG_channel_count].u_int, 1, 2, MP_QSTR_channel_count);
89+
mp_int_t sample_rate = mp_arg_validate_int_min(args[ARG_sample_rate].u_int, 1, MP_QSTR_sample_rate);
90+
mp_int_t bits_per_sample = args[ARG_bits_per_sample].u_int;
91+
if (bits_per_sample != 8 && bits_per_sample != 16) {
92+
mp_raise_ValueError(MP_ERROR_TEXT("bits_per_sample must be 8 or 16"));
93+
}
94+
95+
audiodelays_chorus_obj_t *self = mp_obj_malloc(audiodelays_chorus_obj_t, &audiodelays_chorus_type);
96+
common_hal_audiodelays_chorus_construct(self, max_delay_ms, args[ARG_delay_ms].u_obj, args[ARG_voices].u_obj, args[ARG_buffer_size].u_int, bits_per_sample, args[ARG_samples_signed].u_bool, channel_count, sample_rate);
97+
98+
return MP_OBJ_FROM_PTR(self);
99+
}
100+
101+
//| def deinit(self) -> None:
102+
//| """Deinitialises the Chorus."""
103+
//| ...
104+
//|
105+
static mp_obj_t audiodelays_chorus_deinit(mp_obj_t self_in) {
106+
audiodelays_chorus_obj_t *self = MP_OBJ_TO_PTR(self_in);
107+
common_hal_audiodelays_chorus_deinit(self);
108+
return mp_const_none;
109+
}
110+
static MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_chorus_deinit_obj, audiodelays_chorus_deinit);
111+
112+
static void check_for_deinit(audiodelays_chorus_obj_t *self) {
113+
if (common_hal_audiodelays_chorus_deinited(self)) {
114+
raise_deinited_error();
115+
}
116+
}
117+
118+
//| def __enter__(self) -> Chorus:
119+
//| """No-op used by Context Managers."""
120+
//| ...
121+
//|
122+
// Provided by context manager helper.
123+
124+
//| def __exit__(self) -> None:
125+
//| """Automatically deinitializes when exiting a context. See
126+
//| :ref:`lifetime-and-contextmanagers` for more info."""
127+
//| ...
128+
//|
129+
static mp_obj_t audiodelays_chorus_obj___exit__(size_t n_args, const mp_obj_t *args) {
130+
(void)n_args;
131+
common_hal_audiodelays_chorus_deinit(args[0]);
132+
return mp_const_none;
133+
}
134+
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audiodelays_chorus___exit___obj, 4, 4, audiodelays_chorus_obj___exit__);
135+
136+
137+
//| delay_ms: synthio.BlockInput
138+
//| """The current time of the chorus delay in milliseconds. Must be less the max_delay_ms."""
139+
//|
140+
static mp_obj_t audiodelays_chorus_obj_get_delay_ms(mp_obj_t self_in) {
141+
audiodelays_chorus_obj_t *self = MP_OBJ_TO_PTR(self_in);
142+
143+
return common_hal_audiodelays_chorus_get_delay_ms(self);
144+
}
145+
MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_chorus_get_delay_ms_obj, audiodelays_chorus_obj_get_delay_ms);
146+
147+
static mp_obj_t audiodelays_chorus_obj_set_delay_ms(mp_obj_t self_in, mp_obj_t delay_ms_in) {
148+
audiodelays_chorus_obj_t *self = MP_OBJ_TO_PTR(self_in);
149+
common_hal_audiodelays_chorus_set_delay_ms(self, delay_ms_in);
150+
return mp_const_none;
151+
}
152+
MP_DEFINE_CONST_FUN_OBJ_2(audiodelays_chorus_set_delay_ms_obj, audiodelays_chorus_obj_set_delay_ms);
153+
154+
MP_PROPERTY_GETSET(audiodelays_chorus_delay_ms_obj,
155+
(mp_obj_t)&audiodelays_chorus_get_delay_ms_obj,
156+
(mp_obj_t)&audiodelays_chorus_set_delay_ms_obj);
157+
158+
//| voices: synthio.BlockInput
159+
//| """The number of voices playing split evenly over the delay buffer."""
160+
static mp_obj_t audiodelays_chorus_obj_get_voices(mp_obj_t self_in) {
161+
return common_hal_audiodelays_chorus_get_voices(self_in);
162+
}
163+
MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_chorus_get_voices_obj, audiodelays_chorus_obj_get_voices);
164+
165+
static mp_obj_t audiodelays_chorus_obj_set_voices(mp_obj_t self_in, mp_obj_t voices_in) {
166+
audiodelays_chorus_obj_t *self = MP_OBJ_TO_PTR(self_in);
167+
common_hal_audiodelays_chorus_set_voices(self, voices_in);
168+
return mp_const_none;
169+
}
170+
MP_DEFINE_CONST_FUN_OBJ_2(audiodelays_chorus_set_voices_obj, audiodelays_chorus_obj_set_voices);
171+
172+
MP_PROPERTY_GETSET(audiodelays_chorus_voices_obj,
173+
(mp_obj_t)&audiodelays_chorus_get_voices_obj,
174+
(mp_obj_t)&audiodelays_chorus_set_voices_obj);
175+
176+
//| playing: bool
177+
//| """True when the effect is playing a sample. (read-only)"""
178+
//|
179+
static mp_obj_t audiodelays_chorus_obj_get_playing(mp_obj_t self_in) {
180+
audiodelays_chorus_obj_t *self = MP_OBJ_TO_PTR(self_in);
181+
check_for_deinit(self);
182+
return mp_obj_new_bool(common_hal_audiodelays_chorus_get_playing(self));
183+
}
184+
MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_chorus_get_playing_obj, audiodelays_chorus_obj_get_playing);
185+
186+
MP_PROPERTY_GETTER(audiodelays_chorus_playing_obj,
187+
(mp_obj_t)&audiodelays_chorus_get_playing_obj);
188+
189+
//| def play(self, sample: circuitpython_typing.AudioSample, *, loop: bool = False) -> None:
190+
//| """Plays the sample once when loop=False and continuously when loop=True.
191+
//| Does not block. Use `playing` to block.
192+
//|
193+
//| The sample must match the encoding settings given in the constructor."""
194+
//| ...
195+
//|
196+
static mp_obj_t audiodelays_chorus_obj_play(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
197+
enum { ARG_sample, ARG_loop };
198+
static const mp_arg_t allowed_args[] = {
199+
{ MP_QSTR_sample, MP_ARG_OBJ | MP_ARG_REQUIRED, {} },
200+
{ MP_QSTR_loop, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = false} },
201+
};
202+
audiodelays_chorus_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
203+
check_for_deinit(self);
204+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
205+
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
206+
207+
208+
mp_obj_t sample = args[ARG_sample].u_obj;
209+
common_hal_audiodelays_chorus_play(self, sample, args[ARG_loop].u_bool);
210+
211+
return mp_const_none;
212+
}
213+
MP_DEFINE_CONST_FUN_OBJ_KW(audiodelays_chorus_play_obj, 1, audiodelays_chorus_obj_play);
214+
215+
//| def stop(self) -> None:
216+
//| """Stops playback of the sample."""
217+
//| ...
218+
//|
219+
//|
220+
static mp_obj_t audiodelays_chorus_obj_stop(mp_obj_t self_in) {
221+
audiodelays_chorus_obj_t *self = MP_OBJ_TO_PTR(self_in);
222+
223+
common_hal_audiodelays_chorus_stop(self);
224+
return mp_const_none;
225+
}
226+
MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_chorus_stop_obj, audiodelays_chorus_obj_stop);
227+
228+
static const mp_rom_map_elem_t audiodelays_chorus_locals_dict_table[] = {
229+
// Methods
230+
{ MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiodelays_chorus_deinit_obj) },
231+
{ MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) },
232+
{ MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&audiodelays_chorus___exit___obj) },
233+
{ MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audiodelays_chorus_play_obj) },
234+
{ MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&audiodelays_chorus_stop_obj) },
235+
236+
// Properties
237+
{ MP_ROM_QSTR(MP_QSTR_playing), MP_ROM_PTR(&audiodelays_chorus_playing_obj) },
238+
{ MP_ROM_QSTR(MP_QSTR_delay_ms), MP_ROM_PTR(&audiodelays_chorus_delay_ms_obj) },
239+
{ MP_ROM_QSTR(MP_QSTR_voices), MP_ROM_PTR(&audiodelays_chorus_voices_obj) },
240+
};
241+
static MP_DEFINE_CONST_DICT(audiodelays_chorus_locals_dict, audiodelays_chorus_locals_dict_table);
242+
243+
static const audiosample_p_t audiodelays_chorus_proto = {
244+
MP_PROTO_IMPLEMENT(MP_QSTR_protocol_audiosample)
245+
.sample_rate = (audiosample_sample_rate_fun)common_hal_audiodelays_chorus_get_sample_rate,
246+
.bits_per_sample = (audiosample_bits_per_sample_fun)common_hal_audiodelays_chorus_get_bits_per_sample,
247+
.channel_count = (audiosample_channel_count_fun)common_hal_audiodelays_chorus_get_channel_count,
248+
.reset_buffer = (audiosample_reset_buffer_fun)audiodelays_chorus_reset_buffer,
249+
.get_buffer = (audiosample_get_buffer_fun)audiodelays_chorus_get_buffer,
250+
.get_buffer_structure = (audiosample_get_buffer_structure_fun)audiodelays_chorus_get_buffer_structure,
251+
};
252+
253+
MP_DEFINE_CONST_OBJ_TYPE(
254+
audiodelays_chorus_type,
255+
MP_QSTR_Chorus,
256+
MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS,
257+
make_new, audiodelays_chorus_make_new,
258+
locals_dict, &audiodelays_chorus_locals_dict,
259+
protocol, &audiodelays_chorus_proto
260+
);

Diff for: shared-bindings/audiodelays/Chorus.h

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// This file is part of the CircuitPython project: https://circuitpython.org
2+
//
3+
// SPDX-FileCopyrightText: Copyright (c) 2025 Mark Komus
4+
//
5+
// SPDX-License-Identifier: MIT
6+
7+
#pragma once
8+
9+
#include "shared-module/audiodelays/Chorus.h"
10+
11+
extern const mp_obj_type_t audiodelays_chorus_type;
12+
13+
void common_hal_audiodelays_chorus_construct(audiodelays_chorus_obj_t *self, uint32_t max_delay_ms,
14+
mp_obj_t delay_ms, mp_obj_t voices,
15+
uint32_t buffer_size, uint8_t bits_per_sample,
16+
bool samples_signed, uint8_t channel_count, uint32_t sample_rate);
17+
18+
void common_hal_audiodelays_chorus_deinit(audiodelays_chorus_obj_t *self);
19+
bool common_hal_audiodelays_chorus_deinited(audiodelays_chorus_obj_t *self);
20+
21+
uint32_t common_hal_audiodelays_chorus_get_sample_rate(audiodelays_chorus_obj_t *self);
22+
uint8_t common_hal_audiodelays_chorus_get_channel_count(audiodelays_chorus_obj_t *self);
23+
uint8_t common_hal_audiodelays_chorus_get_bits_per_sample(audiodelays_chorus_obj_t *self);
24+
25+
mp_obj_t common_hal_audiodelays_chorus_get_delay_ms(audiodelays_chorus_obj_t *self);
26+
void common_hal_audiodelays_chorus_set_delay_ms(audiodelays_chorus_obj_t *self, mp_obj_t delay_ms);
27+
28+
mp_obj_t common_hal_audiodelays_chorus_get_voices(audiodelays_chorus_obj_t *self);
29+
void common_hal_audiodelays_chorus_set_voices(audiodelays_chorus_obj_t *self, mp_obj_t voices);
30+
31+
bool common_hal_audiodelays_chorus_get_playing(audiodelays_chorus_obj_t *self);
32+
void common_hal_audiodelays_chorus_play(audiodelays_chorus_obj_t *self, mp_obj_t sample, bool loop);
33+
void common_hal_audiodelays_chorus_stop(audiodelays_chorus_obj_t *self);

Diff for: shared-bindings/audiodelays/__init__.c

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
#include "shared-bindings/audiodelays/__init__.h"
1313
#include "shared-bindings/audiodelays/Echo.h"
14+
#include "shared-bindings/audiodelays/Chorus.h"
1415

1516
//| """Support for audio delay effects
1617
//|
@@ -21,6 +22,7 @@
2122
static const mp_rom_map_elem_t audiodelays_module_globals_table[] = {
2223
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audiodelays) },
2324
{ MP_ROM_QSTR(MP_QSTR_Echo), MP_ROM_PTR(&audiodelays_echo_type) },
25+
{ MP_ROM_QSTR(MP_QSTR_Chorus), MP_ROM_PTR(&audiodelays_chorus_type) },
2426
};
2527

2628
static MP_DEFINE_CONST_DICT(audiodelays_module_globals, audiodelays_module_globals_table);

0 commit comments

Comments
 (0)