Skip to content

Commit 5e92303

Browse files
committed
Initial audio effects commit
1 parent 8dc7102 commit 5e92303

File tree

14 files changed

+648
-3
lines changed

14 files changed

+648
-3
lines changed

locale/circuitpython.pot

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2500,7 +2500,7 @@ msgstr ""
25002500
msgid "bits must be 32 or less"
25012501
msgstr ""
25022502

2503-
#: shared-bindings/audiomixer/Mixer.c
2503+
#: shared-bindings/audioeffects/Echo.c shared-bindings/audiomixer/Mixer.c
25042504
msgid "bits_per_sample must be 8 or 16"
25052505
msgstr ""
25062506

@@ -2847,6 +2847,10 @@ msgstr ""
28472847
msgid "data type not understood"
28482848
msgstr ""
28492849

2850+
#: shared-bindings/audioeffects/Echo.c
2851+
msgid "decay must be between 0 and 1"
2852+
msgstr ""
2853+
28502854
#: py/parsenum.c
28512855
msgid "decimal numbers not supported"
28522856
msgstr ""
@@ -3242,7 +3246,7 @@ msgstr ""
32423246
msgid "input must be a dense ndarray"
32433247
msgstr ""
32443248

3245-
#: extmod/ulab/code/user/user.c
3249+
#: extmod/ulab/code/user/user.c shared-bindings/_eve/__init__.c
32463250
msgid "input must be an ndarray"
32473251
msgstr ""
32483252

ports/raspberrypi/mpconfigport.mk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ CIRCUITPY_PWMIO ?= 1
2222
CIRCUITPY_RGBMATRIX ?= $(CIRCUITPY_DISPLAYIO)
2323
CIRCUITPY_ROTARYIO ?= 1
2424
CIRCUITPY_ROTARYIO_SOFTENCODER = 1
25-
CIRCUITPY_SYNTHIO_MAX_CHANNELS = 12
25+
CIRCUITPY_SYNTHIO_MAX_CHANNELS = 24
2626
CIRCUITPY_USB_HOST ?= 1
2727
CIRCUITPY_USB_VIDEO ?= 1
2828

py/circuitpy_defns.mk

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ endif
131131
ifeq ($(CIRCUITPY_AUDIOCORE),1)
132132
SRC_PATTERNS += audiocore/%
133133
endif
134+
ifeq ($(CIRCUITPY_AUDIOEFFECTS),1)
135+
SRC_PATTERNS += audioeffects/%
136+
endif
134137
ifeq ($(CIRCUITPY_AUDIOMIXER),1)
135138
SRC_PATTERNS += audiomixer/%
136139
endif
@@ -617,6 +620,8 @@ SRC_SHARED_MODULE_ALL = \
617620
audiocore/RawSample.c \
618621
audiocore/WaveFile.c \
619622
audiocore/__init__.c \
623+
audioeffects/Echo.c \
624+
audioeffects/__init__.c \
620625
audioio/__init__.c \
621626
audiomixer/Mixer.c \
622627
audiomixer/MixerVoice.c \
@@ -857,6 +862,7 @@ $(filter $(SRC_PATTERNS), \
857862
displayio/display_core.c \
858863
os/getenv.c \
859864
usb/utf16le.c \
865+
audioeffects/effects_utils.c \
860866
)
861867

862868
SRC_COMMON_HAL_INTERNAL = \

py/circuitpy_mpconfig.mk

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,9 @@ CFLAGS += -DCIRCUITPY_AUDIOCORE_DEBUG=$(CIRCUITPY_AUDIOCORE_DEBUG)
141141
CIRCUITPY_AUDIOMP3 ?= $(call enable-if-all,$(CIRCUITPY_FULL_BUILD) $(CIRCUITPY_AUDIOCORE))
142142
CFLAGS += -DCIRCUITPY_AUDIOMP3=$(CIRCUITPY_AUDIOMP3)
143143

144+
CIRCUITPY_AUDIOEFFECTS ?= 0
145+
CFLAGS += -DCIRCUITPY_AUDIOEFFECTS=$(CIRCUITPY_AUDIOEFFECTS)
146+
144147
CIRCUITPY_AURORA_EPAPER ?= 0
145148
CFLAGS += -DCIRCUITPY_AURORA_EPAPER=$(CIRCUITPY_AURORA_EPAPER)
146149

shared-bindings/audioeffects/Echo.c

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
// This file is part of the CircuitPython project: https://circuitpython.org
2+
//
3+
// SPDX-FileCopyrightText: Copyright (c) 2024 Mark Komus
4+
//
5+
// SPDX-License-Identifier: MIT
6+
7+
#include <stdint.h>
8+
9+
#include "shared-bindings/audioeffects/Echo.h"
10+
#include "shared-module/audioeffects/Echo.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+
18+
#define DECAY_DEFAULT 0.7f
19+
20+
//| class Echo:
21+
//| """An Echo effect"""
22+
//|
23+
static mp_obj_t audioeffects_echo_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
24+
enum { ARG_delay_ms, ARG_decay, ARG_buffer_size, ARG_sample_rate, ARG_bits_per_sample, ARG_samples_signed, ARG_channel_count, };
25+
static const mp_arg_t allowed_args[] = {
26+
{ MP_QSTR_delay_ms, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 50 } },
27+
{ MP_QSTR_decay, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} },
28+
{ MP_QSTR_buffer_size, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 1024} },
29+
{ MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 8000} },
30+
{ MP_QSTR_bits_per_sample, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 16} },
31+
{ MP_QSTR_samples_signed, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = true} },
32+
{ MP_QSTR_channel_count, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 1 } },
33+
};
34+
35+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
36+
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
37+
38+
mp_int_t delay_ms = mp_arg_validate_int_range(args[ARG_delay_ms].u_int, 1, 4000, MP_QSTR_delay_ms);
39+
40+
mp_float_t decay = (args[ARG_decay].u_obj == MP_OBJ_NULL)
41+
? (mp_float_t)DECAY_DEFAULT
42+
: mp_obj_get_float(args[ARG_decay].u_obj);
43+
mp_arg_validate_float_range(decay, 0.0f, 1.0f, MP_QSTR_decay);
44+
45+
mp_int_t channel_count = mp_arg_validate_int_range(args[ARG_channel_count].u_int, 1, 2, MP_QSTR_channel_count);
46+
mp_int_t sample_rate = mp_arg_validate_int_min(args[ARG_sample_rate].u_int, 1, MP_QSTR_sample_rate);
47+
mp_int_t bits_per_sample = args[ARG_bits_per_sample].u_int;
48+
if (bits_per_sample != 8 && bits_per_sample != 16) {
49+
mp_raise_ValueError(MP_ERROR_TEXT("bits_per_sample must be 8 or 16"));
50+
}
51+
52+
audioeffects_echo_obj_t *self = mp_obj_malloc(audioeffects_echo_obj_t, &audioeffects_echo_type);
53+
common_hal_audioeffects_echo_construct(self, delay_ms, decay, args[ARG_buffer_size].u_int, bits_per_sample, args[ARG_samples_signed].u_bool, channel_count, sample_rate);
54+
55+
return MP_OBJ_FROM_PTR(self);
56+
}
57+
58+
//| def deinit(self) -> None:
59+
//| """Deinitialises the Echo and releases any hardware resources for reuse."""
60+
//| ...
61+
static mp_obj_t audioeffects_echo_deinit(mp_obj_t self_in) {
62+
audioeffects_echo_obj_t *self = MP_OBJ_TO_PTR(self_in);
63+
common_hal_audioeffects_echo_deinit(self);
64+
return mp_const_none;
65+
}
66+
static MP_DEFINE_CONST_FUN_OBJ_1(audioeffects_echo_deinit_obj, audioeffects_echo_deinit);
67+
68+
static void check_for_deinit(audioeffects_echo_obj_t *self) {
69+
if (common_hal_audioeffects_echo_deinited(self)) {
70+
raise_deinited_error();
71+
}
72+
}
73+
74+
//| def __enter__(self) -> Echo:
75+
//| """No-op used by Context Managers."""
76+
//| ...
77+
// Provided by context manager helper.
78+
79+
//| def __exit__(self) -> None:
80+
//| """Automatically deinitializes the hardware when exiting a context. See
81+
//| :ref:`lifetime-and-contextmanagers` for more info."""
82+
//| ...
83+
static mp_obj_t audioeffects_echo_obj___exit__(size_t n_args, const mp_obj_t *args) {
84+
(void)n_args;
85+
common_hal_audioeffects_echo_deinit(args[0]);
86+
return mp_const_none;
87+
}
88+
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audioeffects_echo___exit___obj, 4, 4, audioeffects_echo_obj___exit__);
89+
90+
91+
//| delay_ms: int
92+
//| """Delay of the echo in microseconds. (read-only)"""
93+
//|
94+
static mp_obj_t audioeffects_echo_obj_get_delay_ms(mp_obj_t self_in) {
95+
audioeffects_echo_obj_t *self = MP_OBJ_TO_PTR(self_in);
96+
97+
return mp_obj_new_float(common_hal_audioeffects_echo_get_delay_ms(self));
98+
99+
}
100+
MP_DEFINE_CONST_FUN_OBJ_1(audioeffects_echo_get_delay_ms_obj, audioeffects_echo_obj_get_delay_ms);
101+
102+
static mp_obj_t audioeffects_echo_obj_set_delay_ms(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
103+
enum { ARG_delay_ms };
104+
static const mp_arg_t allowed_args[] = {
105+
{ MP_QSTR_delay_ms, MP_ARG_INT | MP_ARG_REQUIRED, {} },
106+
};
107+
audioeffects_echo_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
108+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
109+
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
110+
111+
mp_int_t delay_ms = mp_arg_validate_int_range(args[ARG_delay_ms].u_int, 1, 4000, MP_QSTR_delay_ms);
112+
113+
common_hal_audioeffects_echo_set_delay_ms(self, delay_ms);
114+
115+
return mp_const_none;
116+
}
117+
MP_DEFINE_CONST_FUN_OBJ_KW(audioeffects_echo_set_delay_ms_obj, 1, audioeffects_echo_obj_set_delay_ms);
118+
119+
MP_PROPERTY_GETSET(audioeffects_echo_delay_ms_obj,
120+
(mp_obj_t)&audioeffects_echo_get_delay_ms_obj,
121+
(mp_obj_t)&audioeffects_echo_set_delay_ms_obj);
122+
123+
124+
125+
//| decay: float
126+
//| """The rate the echo decays between 0 and 1."""
127+
static mp_obj_t audioeffects_echo_obj_get_decay(mp_obj_t self_in) {
128+
return mp_obj_new_float(common_hal_audioeffects_echo_get_decay(self_in));
129+
}
130+
MP_DEFINE_CONST_FUN_OBJ_1(audioeffects_echo_get_decay_obj, audioeffects_echo_obj_get_decay);
131+
132+
static mp_obj_t audioeffects_echo_obj_set_decay(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
133+
enum { ARG_decay };
134+
static const mp_arg_t allowed_args[] = {
135+
{ MP_QSTR_decay, MP_ARG_OBJ | MP_ARG_REQUIRED, {} },
136+
};
137+
audioeffects_echo_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
138+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
139+
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
140+
141+
mp_float_t decay = mp_obj_get_float(args[ARG_decay].u_obj);
142+
143+
if (decay > 1 || decay < 0) {
144+
mp_raise_ValueError(MP_ERROR_TEXT("decay must be between 0 and 1"));
145+
}
146+
147+
common_hal_audioeffects_echo_set_decay(self, decay);
148+
149+
return mp_const_none;
150+
}
151+
MP_DEFINE_CONST_FUN_OBJ_KW(audioeffects_echo_set_decay_obj, 1, audioeffects_echo_obj_set_decay);
152+
153+
MP_PROPERTY_GETSET(audioeffects_echo_decay_obj,
154+
(mp_obj_t)&audioeffects_echo_get_decay_obj,
155+
(mp_obj_t)&audioeffects_echo_set_decay_obj);
156+
157+
158+
//| playing: bool
159+
//| """True when any voice is being output. (read-only)"""
160+
static mp_obj_t audioeffects_echo_obj_get_playing(mp_obj_t self_in) {
161+
audioeffects_echo_obj_t *self = MP_OBJ_TO_PTR(self_in);
162+
check_for_deinit(self);
163+
return mp_obj_new_bool(common_hal_audioeffects_echo_get_playing(self));
164+
}
165+
MP_DEFINE_CONST_FUN_OBJ_1(audioeffects_echo_get_playing_obj, audioeffects_echo_obj_get_playing);
166+
167+
MP_PROPERTY_GETTER(audioeffects_echo_playing_obj,
168+
(mp_obj_t)&audioeffects_echo_get_playing_obj);
169+
170+
//| def play(
171+
//| self, sample: circuitpython_typing.AudioSample, *, voice: int = 0, loop: bool = False
172+
//| ) -> None:
173+
//| """Plays the sample once when loop=False and continuously when loop=True.
174+
//| Does not block. Use `playing` to block.
175+
//|
176+
//| Sample must be an `audiocore.WaveFile`, `audiocore.RawSample`, `audiomixer.Mixer` or `audiomp3.MP3Decoder`.
177+
//|
178+
//| The sample must match the Effect's encoding settings given in the constructor."""
179+
//| ...
180+
static mp_obj_t audioeffects_echo_obj_play(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
181+
enum { ARG_sample, ARG_loop };
182+
static const mp_arg_t allowed_args[] = {
183+
{ MP_QSTR_sample, MP_ARG_OBJ | MP_ARG_REQUIRED, {} },
184+
{ MP_QSTR_loop, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = false} },
185+
};
186+
audioeffects_echo_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
187+
check_for_deinit(self);
188+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
189+
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
190+
191+
192+
mp_obj_t sample = args[ARG_sample].u_obj;
193+
common_hal_audioeffects_echo_play(self, sample, args[ARG_loop].u_bool);
194+
195+
return mp_const_none;
196+
}
197+
MP_DEFINE_CONST_FUN_OBJ_KW(audioeffects_echo_play_obj, 1, audioeffects_echo_obj_play);
198+
199+
//| def stop_voice(self, voice: int = 0) -> None:
200+
//| """Stops playback of the sample."""
201+
//| ...
202+
//|
203+
static mp_obj_t audioeffects_echo_obj_stop(mp_obj_t self_in) {
204+
audioeffects_echo_obj_t *self = MP_OBJ_TO_PTR(self_in);
205+
206+
common_hal_audioeffects_echo_stop(self);
207+
return mp_const_none;
208+
}
209+
MP_DEFINE_CONST_FUN_OBJ_1(audioeffects_echo_stop_obj, audioeffects_echo_obj_stop);
210+
211+
212+
213+
static const mp_rom_map_elem_t audioeffects_echo_locals_dict_table[] = {
214+
// Methods
215+
{ MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audioeffects_echo_deinit_obj) },
216+
{ MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) },
217+
{ MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&audioeffects_echo___exit___obj) },
218+
{ MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audioeffects_echo_play_obj) },
219+
{ MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&audioeffects_echo_stop_obj) },
220+
221+
// Properties
222+
{ MP_ROM_QSTR(MP_QSTR_playing), MP_ROM_PTR(&audioeffects_echo_playing_obj) },
223+
{ MP_ROM_QSTR(MP_QSTR_delay_ms), MP_ROM_PTR(&audioeffects_echo_delay_ms_obj) },
224+
{ MP_ROM_QSTR(MP_QSTR_decay), MP_ROM_PTR(&audioeffects_echo_decay_obj) },
225+
};
226+
static MP_DEFINE_CONST_DICT(audioeffects_echo_locals_dict, audioeffects_echo_locals_dict_table);
227+
228+
static const audiosample_p_t audioeffects_echo_proto = {
229+
MP_PROTO_IMPLEMENT(MP_QSTR_protocol_audiosample)
230+
.sample_rate = (audiosample_sample_rate_fun)common_hal_audioeffects_echo_get_sample_rate,
231+
.bits_per_sample = (audiosample_bits_per_sample_fun)common_hal_audioeffects_echo_get_bits_per_sample,
232+
.channel_count = (audiosample_channel_count_fun)common_hal_audioeffects_echo_get_channel_count,
233+
.reset_buffer = (audiosample_reset_buffer_fun)audioeffects_echo_reset_buffer,
234+
.get_buffer = (audiosample_get_buffer_fun)audioeffects_echo_get_buffer,
235+
.get_buffer_structure = (audiosample_get_buffer_structure_fun)audioeffects_echo_get_buffer_structure,
236+
};
237+
238+
MP_DEFINE_CONST_OBJ_TYPE(
239+
audioeffects_echo_type,
240+
MP_QSTR_Echo,
241+
MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS,
242+
make_new, audioeffects_echo_make_new,
243+
locals_dict, &audioeffects_echo_locals_dict,
244+
protocol, &audioeffects_echo_proto
245+
);

shared-bindings/audioeffects/Echo.h

Lines changed: 33 additions & 0 deletions
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) 2024 Mark Komus
4+
//
5+
// SPDX-License-Identifier: MIT
6+
7+
#pragma once
8+
9+
#include "shared-module/audioeffects/Echo.h"
10+
11+
extern const mp_obj_type_t audioeffects_echo_type;
12+
13+
void common_hal_audioeffects_echo_construct(audioeffects_echo_obj_t *self,
14+
uint32_t delay_ms, mp_float_t decay,
15+
uint32_t buffer_size, uint8_t bits_per_sample, bool samples_signed,
16+
uint8_t channel_count, uint32_t sample_rate);
17+
18+
void common_hal_audioeffects_echo_deinit(audioeffects_echo_obj_t *self);
19+
bool common_hal_audioeffects_echo_deinited(audioeffects_echo_obj_t *self);
20+
21+
uint32_t common_hal_audioeffects_echo_get_sample_rate(audioeffects_echo_obj_t *self);
22+
uint8_t common_hal_audioeffects_echo_get_channel_count(audioeffects_echo_obj_t *self);
23+
uint8_t common_hal_audioeffects_echo_get_bits_per_sample(audioeffects_echo_obj_t *self);
24+
25+
uint32_t common_hal_audioeffects_echo_get_delay_ms(audioeffects_echo_obj_t *self);
26+
void common_hal_audioeffects_echo_set_delay_ms(audioeffects_echo_obj_t *self, uint32_t delay_ms);
27+
28+
mp_float_t common_hal_audioeffects_echo_get_decay(audioeffects_echo_obj_t *self);
29+
void common_hal_audioeffects_echo_set_decay(audioeffects_echo_obj_t *self, mp_float_t decay);
30+
31+
bool common_hal_audioeffects_echo_get_playing(audioeffects_echo_obj_t *self);
32+
void common_hal_audioeffects_echo_play(audioeffects_echo_obj_t *self, mp_obj_t sample, bool loop);
33+
void common_hal_audioeffects_echo_stop(audioeffects_echo_obj_t *self);
Lines changed: 33 additions & 0 deletions
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) 2024 Mark Komus
4+
//
5+
// SPDX-License-Identifier: MIT
6+
7+
#include <stdint.h>
8+
9+
#include "py/obj.h"
10+
#include "py/runtime.h"
11+
12+
#include "shared-bindings/audioeffects/__init__.h"
13+
#include "shared-bindings/audioeffects/Echo.h"
14+
15+
//| """Support for audio effects
16+
//|
17+
//| The `audioeffects` module contains classes to provide access to audio effects.
18+
//|
19+
//| """
20+
21+
static const mp_rom_map_elem_t audioeffects_module_globals_table[] = {
22+
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audioeffects) },
23+
{ MP_ROM_QSTR(MP_QSTR_Echo), MP_ROM_PTR(&audioeffects_echo_type) },
24+
};
25+
26+
static MP_DEFINE_CONST_DICT(audioeffects_module_globals, audioeffects_module_globals_table);
27+
28+
const mp_obj_module_t audioeffects_module = {
29+
.base = { &mp_type_module },
30+
.globals = (mp_obj_dict_t *)&audioeffects_module_globals,
31+
};
32+
33+
MP_REGISTER_MODULE(MP_QSTR_audioeffects, audioeffects_module);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// This file is part of the CircuitPython project: https://circuitpython.org
2+
//
3+
// SPDX-FileCopyrightText: Copyright (c) 2024 Mark Komus
4+
//
5+
// SPDX-License-Identifier: MIT
6+
7+
#pragma once

0 commit comments

Comments
 (0)