Skip to content

Commit 38779cd

Browse files
authored
Merge pull request #9744 from dcooperdalrymple/audiofilters
Add Audio effects: Filters
2 parents aa28570 + 8503318 commit 38779cd

File tree

10 files changed

+744
-0
lines changed

10 files changed

+744
-0
lines changed

py/circuitpy_defns.mk

+5
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ endif
134134
ifeq ($(CIRCUITPY_AUDIODELAYS),1)
135135
SRC_PATTERNS += audiodelays/%
136136
endif
137+
ifeq ($(CIRCUITPY_AUDIOFILTERS),1)
138+
SRC_PATTERNS += audiofilters/%
139+
endif
137140
ifeq ($(CIRCUITPY_AUDIOMIXER),1)
138141
SRC_PATTERNS += audiomixer/%
139142
endif
@@ -622,6 +625,8 @@ SRC_SHARED_MODULE_ALL = \
622625
audiocore/__init__.c \
623626
audiodelays/Echo.c \
624627
audiodelays/__init__.c \
628+
audiofilters/Filter.c \
629+
audiofilters/__init__.c \
625630
audioio/__init__.c \
626631
audiomixer/Mixer.c \
627632
audiomixer/MixerVoice.c \

py/circuitpy_mpconfig.mk

+2
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ CFLAGS += -DCIRCUITPY_AUDIOMP3=$(CIRCUITPY_AUDIOMP3)
144144
CIRCUITPY_AUDIOEFFECTS ?= 0
145145
CIRCUITPY_AUDIODELAYS ?= $(CIRCUITPY_AUDIOEFFECTS)
146146
CFLAGS += -DCIRCUITPY_AUDIODELAYS=$(CIRCUITPY_AUDIODELAYS)
147+
CIRCUITPY_AUDIOFILTERS ?= $(CIRCUITPY_AUDIOEFFECTS)
148+
CFLAGS += -DCIRCUITPY_AUDIOFILTERS=$(CIRCUITPY_AUDIOFILTERS)
147149

148150
CIRCUITPY_AURORA_EPAPER ?= 0
149151
CFLAGS += -DCIRCUITPY_AURORA_EPAPER=$(CIRCUITPY_AURORA_EPAPER)

shared-bindings/audiofilters/Filter.c

+266
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
// This file is part of the CircuitPython project: https://circuitpython.org
2+
//
3+
// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple
4+
//
5+
// SPDX-License-Identifier: MIT
6+
7+
#include <stdint.h>
8+
9+
#include "shared-bindings/audiofilters/Filter.h"
10+
#include "shared-module/audiofilters/Filter.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+
#define MIX_DEFAULT 1.0f
20+
21+
//| class Filter:
22+
//| """A Filter effect"""
23+
//|
24+
//| def __init__(
25+
//| self,
26+
//| filter: Optional[synthio.Biquad] = None,
27+
//| mix: synthio.BlockInput = 1.0,
28+
//| buffer_size: int = 512,
29+
//| sample_rate: int = 8000,
30+
//| bits_per_sample: int = 16,
31+
//| samples_signed: bool = True,
32+
//| channel_count: int = 1,
33+
//| ) -> None:
34+
//| """Create a Filter effect where the original sample is processed through a biquad filter
35+
//| created by a synthio.Synthesizer object. This can be used to generate a low-pass,
36+
//| high-pass, or band-pass filter.
37+
//|
38+
//| The mix parameter allows you to change how much of the unchanged sample passes through to
39+
//| the output to how much of the effect audio you hear as the output.
40+
//|
41+
//| :param Optional[synthio.Biquad] filter: The normalized biquad filter object used to process the signal.
42+
//| :param synthio.BlockInput mix: The mix as a ratio of the sample (0.0) to the effect (1.0).
43+
//| :param int buffer_size: The total size in bytes of each of the two playback buffers to use
44+
//| :param int sample_rate: The sample rate to be used
45+
//| :param int channel_count: The number of channels the source samples contain. 1 = mono; 2 = stereo.
46+
//| :param int bits_per_sample: The bits per sample of the effect
47+
//| :param bool samples_signed: Effect is signed (True) or unsigned (False)
48+
//|
49+
//| Playing adding a filter to a synth::
50+
//|
51+
//| import time
52+
//| import board
53+
//| import audiobusio
54+
//| import synthio
55+
//| import audiofilters
56+
//|
57+
//| audio = audiobusio.I2SOut(bit_clock=board.GP20, word_select=board.GP21, data=board.GP22)
58+
//| synth = synthio.Synthesizer(channel_count=1, sample_rate=44100)
59+
//| filter = audiofilters.Filter(filter=synth.low_pass_filter(frequency=2000, Q=1.25), buffer_size=1024, channel_count=1, sample_rate=44100, mix=1.0)
60+
//| filter.play(synth)
61+
//| audio.play(filter)
62+
//|
63+
//| note = synthio.Note(261)
64+
//| while True:
65+
//| synth.press(note)
66+
//| time.sleep(0.25)
67+
//| synth.release(note)
68+
//| time.sleep(5)"""
69+
//| ...
70+
static mp_obj_t audiofilters_filter_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_filter, ARG_mix, 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_filter, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} },
74+
{ MP_QSTR_mix, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} },
75+
{ MP_QSTR_buffer_size, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 512} },
76+
{ MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 8000} },
77+
{ MP_QSTR_bits_per_sample, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 16} },
78+
{ MP_QSTR_samples_signed, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = true} },
79+
{ MP_QSTR_channel_count, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 1 } },
80+
};
81+
82+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
83+
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
84+
85+
mp_int_t channel_count = mp_arg_validate_int_range(args[ARG_channel_count].u_int, 1, 2, MP_QSTR_channel_count);
86+
mp_int_t sample_rate = mp_arg_validate_int_min(args[ARG_sample_rate].u_int, 1, MP_QSTR_sample_rate);
87+
mp_int_t bits_per_sample = args[ARG_bits_per_sample].u_int;
88+
if (bits_per_sample != 8 && bits_per_sample != 16) {
89+
mp_raise_ValueError(MP_ERROR_TEXT("bits_per_sample must be 8 or 16"));
90+
}
91+
92+
audiofilters_filter_obj_t *self = mp_obj_malloc(audiofilters_filter_obj_t, &audiofilters_filter_type);
93+
common_hal_audiofilters_filter_construct(self, args[ARG_filter].u_obj, args[ARG_mix].u_obj, args[ARG_buffer_size].u_int, bits_per_sample, args[ARG_samples_signed].u_bool, channel_count, sample_rate);
94+
95+
return MP_OBJ_FROM_PTR(self);
96+
}
97+
98+
//| def deinit(self) -> None:
99+
//| """Deinitialises the Filter."""
100+
//| ...
101+
static mp_obj_t audiofilters_filter_deinit(mp_obj_t self_in) {
102+
audiofilters_filter_obj_t *self = MP_OBJ_TO_PTR(self_in);
103+
common_hal_audiofilters_filter_deinit(self);
104+
return mp_const_none;
105+
}
106+
static MP_DEFINE_CONST_FUN_OBJ_1(audiofilters_filter_deinit_obj, audiofilters_filter_deinit);
107+
108+
static void check_for_deinit(audiofilters_filter_obj_t *self) {
109+
if (common_hal_audiofilters_filter_deinited(self)) {
110+
raise_deinited_error();
111+
}
112+
}
113+
114+
//| def __enter__(self) -> Filter:
115+
//| """No-op used by Context Managers."""
116+
//| ...
117+
// Provided by context manager helper.
118+
119+
//| def __exit__(self) -> None:
120+
//| """Automatically deinitializes when exiting a context. See
121+
//| :ref:`lifetime-and-contextmanagers` for more info."""
122+
//| ...
123+
static mp_obj_t audiofilters_filter_obj___exit__(size_t n_args, const mp_obj_t *args) {
124+
(void)n_args;
125+
common_hal_audiofilters_filter_deinit(args[0]);
126+
return mp_const_none;
127+
}
128+
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audiofilters_filter___exit___obj, 4, 4, audiofilters_filter_obj___exit__);
129+
130+
131+
//| filter: Optional[synthio.Biquad]
132+
//| """The normalized biquad filter object used to process the signal."""
133+
static mp_obj_t audiofilters_filter_obj_get_filter(mp_obj_t self_in) {
134+
return common_hal_audiofilters_filter_get_filter(self_in);
135+
}
136+
MP_DEFINE_CONST_FUN_OBJ_1(audiofilters_filter_get_filter_obj, audiofilters_filter_obj_get_filter);
137+
138+
static mp_obj_t audiofilters_filter_obj_set_filter(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
139+
enum { ARG_filter };
140+
static const mp_arg_t allowed_args[] = {
141+
{ MP_QSTR_filter, MP_ARG_OBJ | MP_ARG_REQUIRED, {} },
142+
};
143+
audiofilters_filter_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
144+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
145+
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
146+
147+
common_hal_audiofilters_filter_set_filter(self, args[ARG_filter].u_obj);
148+
149+
return mp_const_none;
150+
}
151+
MP_DEFINE_CONST_FUN_OBJ_KW(audiofilters_filter_set_filter_obj, 1, audiofilters_filter_obj_set_filter);
152+
153+
MP_PROPERTY_GETSET(audiofilters_filter_filter_obj,
154+
(mp_obj_t)&audiofilters_filter_get_filter_obj,
155+
(mp_obj_t)&audiofilters_filter_set_filter_obj);
156+
157+
158+
//| mix: synthio.BlockInput
159+
//| """The rate the filtered signal mix between 0 and 1 where 0 is only sample and 1 is all effect."""
160+
static mp_obj_t audiofilters_filter_obj_get_mix(mp_obj_t self_in) {
161+
return common_hal_audiofilters_filter_get_mix(self_in);
162+
}
163+
MP_DEFINE_CONST_FUN_OBJ_1(audiofilters_filter_get_mix_obj, audiofilters_filter_obj_get_mix);
164+
165+
static mp_obj_t audiofilters_filter_obj_set_mix(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
166+
enum { ARG_mix };
167+
static const mp_arg_t allowed_args[] = {
168+
{ MP_QSTR_mix, MP_ARG_OBJ | MP_ARG_REQUIRED, {} },
169+
};
170+
audiofilters_filter_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
171+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
172+
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
173+
174+
common_hal_audiofilters_filter_set_mix(self, args[ARG_mix].u_obj);
175+
176+
return mp_const_none;
177+
}
178+
MP_DEFINE_CONST_FUN_OBJ_KW(audiofilters_filter_set_mix_obj, 1, audiofilters_filter_obj_set_mix);
179+
180+
MP_PROPERTY_GETSET(audiofilters_filter_mix_obj,
181+
(mp_obj_t)&audiofilters_filter_get_mix_obj,
182+
(mp_obj_t)&audiofilters_filter_set_mix_obj);
183+
184+
185+
//| playing: bool
186+
//| """True when the effect is playing a sample. (read-only)"""
187+
static mp_obj_t audiofilters_filter_obj_get_playing(mp_obj_t self_in) {
188+
audiofilters_filter_obj_t *self = MP_OBJ_TO_PTR(self_in);
189+
check_for_deinit(self);
190+
return mp_obj_new_bool(common_hal_audiofilters_filter_get_playing(self));
191+
}
192+
MP_DEFINE_CONST_FUN_OBJ_1(audiofilters_filter_get_playing_obj, audiofilters_filter_obj_get_playing);
193+
194+
MP_PROPERTY_GETTER(audiofilters_filter_playing_obj,
195+
(mp_obj_t)&audiofilters_filter_get_playing_obj);
196+
197+
//| def play(self, sample: circuitpython_typing.AudioSample, *, loop: bool = False) -> None:
198+
//| """Plays the sample once when loop=False and continuously when loop=True.
199+
//| Does not block. Use `playing` to block.
200+
//|
201+
//| The sample must match the encoding settings given in the constructor."""
202+
//| ...
203+
static mp_obj_t audiofilters_filter_obj_play(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
204+
enum { ARG_sample, ARG_loop };
205+
static const mp_arg_t allowed_args[] = {
206+
{ MP_QSTR_sample, MP_ARG_OBJ | MP_ARG_REQUIRED, {} },
207+
{ MP_QSTR_loop, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = false} },
208+
};
209+
audiofilters_filter_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
210+
check_for_deinit(self);
211+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
212+
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
213+
214+
215+
mp_obj_t sample = args[ARG_sample].u_obj;
216+
common_hal_audiofilters_filter_play(self, sample, args[ARG_loop].u_bool);
217+
218+
return mp_const_none;
219+
}
220+
MP_DEFINE_CONST_FUN_OBJ_KW(audiofilters_filter_play_obj, 1, audiofilters_filter_obj_play);
221+
222+
//| def stop(self) -> None:
223+
//| """Stops playback of the sample."""
224+
//| ...
225+
//|
226+
static mp_obj_t audiofilters_filter_obj_stop(mp_obj_t self_in) {
227+
audiofilters_filter_obj_t *self = MP_OBJ_TO_PTR(self_in);
228+
229+
common_hal_audiofilters_filter_stop(self);
230+
return mp_const_none;
231+
}
232+
MP_DEFINE_CONST_FUN_OBJ_1(audiofilters_filter_stop_obj, audiofilters_filter_obj_stop);
233+
234+
static const mp_rom_map_elem_t audiofilters_filter_locals_dict_table[] = {
235+
// Methods
236+
{ MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiofilters_filter_deinit_obj) },
237+
{ MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) },
238+
{ MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&audiofilters_filter___exit___obj) },
239+
{ MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audiofilters_filter_play_obj) },
240+
{ MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&audiofilters_filter_stop_obj) },
241+
242+
// Properties
243+
{ MP_ROM_QSTR(MP_QSTR_playing), MP_ROM_PTR(&audiofilters_filter_playing_obj) },
244+
{ MP_ROM_QSTR(MP_QSTR_filter), MP_ROM_PTR(&audiofilters_filter_filter_obj) },
245+
{ MP_ROM_QSTR(MP_QSTR_mix), MP_ROM_PTR(&audiofilters_filter_mix_obj) },
246+
};
247+
static MP_DEFINE_CONST_DICT(audiofilters_filter_locals_dict, audiofilters_filter_locals_dict_table);
248+
249+
static const audiosample_p_t audiofilters_filter_proto = {
250+
MP_PROTO_IMPLEMENT(MP_QSTR_protocol_audiosample)
251+
.sample_rate = (audiosample_sample_rate_fun)common_hal_audiofilters_filter_get_sample_rate,
252+
.bits_per_sample = (audiosample_bits_per_sample_fun)common_hal_audiofilters_filter_get_bits_per_sample,
253+
.channel_count = (audiosample_channel_count_fun)common_hal_audiofilters_filter_get_channel_count,
254+
.reset_buffer = (audiosample_reset_buffer_fun)audiofilters_filter_reset_buffer,
255+
.get_buffer = (audiosample_get_buffer_fun)audiofilters_filter_get_buffer,
256+
.get_buffer_structure = (audiosample_get_buffer_structure_fun)audiofilters_filter_get_buffer_structure,
257+
};
258+
259+
MP_DEFINE_CONST_OBJ_TYPE(
260+
audiofilters_filter_type,
261+
MP_QSTR_Filter,
262+
MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS,
263+
make_new, audiofilters_filter_make_new,
264+
locals_dict, &audiofilters_filter_locals_dict,
265+
protocol, &audiofilters_filter_proto
266+
);

shared-bindings/audiofilters/Filter.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) 2024 Cooper Dalrymple
4+
//
5+
// SPDX-License-Identifier: MIT
6+
7+
#pragma once
8+
9+
#include "shared-module/audiofilters/Filter.h"
10+
11+
extern const mp_obj_type_t audiofilters_filter_type;
12+
13+
void common_hal_audiofilters_filter_construct(audiofilters_filter_obj_t *self,
14+
mp_obj_t filter, mp_obj_t mix,
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_audiofilters_filter_deinit(audiofilters_filter_obj_t *self);
19+
bool common_hal_audiofilters_filter_deinited(audiofilters_filter_obj_t *self);
20+
21+
uint32_t common_hal_audiofilters_filter_get_sample_rate(audiofilters_filter_obj_t *self);
22+
uint8_t common_hal_audiofilters_filter_get_channel_count(audiofilters_filter_obj_t *self);
23+
uint8_t common_hal_audiofilters_filter_get_bits_per_sample(audiofilters_filter_obj_t *self);
24+
25+
mp_obj_t common_hal_audiofilters_filter_get_filter(audiofilters_filter_obj_t *self);
26+
void common_hal_audiofilters_filter_set_filter(audiofilters_filter_obj_t *self, mp_obj_t arg);
27+
28+
mp_obj_t common_hal_audiofilters_filter_get_mix(audiofilters_filter_obj_t *self);
29+
void common_hal_audiofilters_filter_set_mix(audiofilters_filter_obj_t *self, mp_obj_t arg);
30+
31+
bool common_hal_audiofilters_filter_get_playing(audiofilters_filter_obj_t *self);
32+
void common_hal_audiofilters_filter_play(audiofilters_filter_obj_t *self, mp_obj_t sample, bool loop);
33+
void common_hal_audiofilters_filter_stop(audiofilters_filter_obj_t *self);
+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) 2024 Cooper Dalrymple
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/audiofilters/__init__.h"
13+
#include "shared-bindings/audiofilters/Filter.h"
14+
15+
//| """Support for audio filter effects
16+
//|
17+
//| The `audiofilters` module contains classes to provide access to audio filter effects.
18+
//|
19+
//| """
20+
21+
static const mp_rom_map_elem_t audiofilters_module_globals_table[] = {
22+
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audiofilters) },
23+
{ MP_ROM_QSTR(MP_QSTR_Filter), MP_ROM_PTR(&audiofilters_filter_type) },
24+
};
25+
26+
static MP_DEFINE_CONST_DICT(audiofilters_module_globals, audiofilters_module_globals_table);
27+
28+
const mp_obj_module_t audiofilters_module = {
29+
.base = { &mp_type_module },
30+
.globals = (mp_obj_dict_t *)&audiofilters_module_globals,
31+
};
32+
33+
MP_REGISTER_MODULE(MP_QSTR_audiofilters, audiofilters_module);
+7
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 Cooper Dalrymple
4+
//
5+
// SPDX-License-Identifier: MIT
6+
7+
#pragma once

0 commit comments

Comments
 (0)