Skip to content

Commit a405019

Browse files
committed
Initial commit
1 parent 14eb89a commit a405019

File tree

7 files changed

+485
-0
lines changed

7 files changed

+485
-0
lines changed

locale/circuitpython.pot

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2575,6 +2575,11 @@ msgstr ""
25752575

25762576
#: shared-bindings/audiodelays/Echo.c shared-bindings/audiodelays/PitchShift.c
25772577
#: shared-bindings/audiofilters/Distortion.c
2578+
#: shared-bindings/audiodelays/Reverb.c
2579+
msgid "bits_per_sample must be 16"
2580+
msgstr ""
2581+
2582+
#: shared-bindings/audiodelays/Echo.c shared-bindings/audiofilters/Distortion.c
25782583
#: shared-bindings/audiofilters/Filter.c shared-bindings/audiomixer/Mixer.c
25792584
msgid "bits_per_sample must be 8 or 16"
25802585
msgstr ""
@@ -3992,6 +3997,10 @@ msgstr ""
39923997
msgid "rsplit(None,n)"
39933998
msgstr ""
39943999

4000+
#: shared-bindings/audiodelays/Reverb.c
4001+
msgid "samples_signed must be true"
4002+
msgstr ""
4003+
39954004
#: ports/atmel-samd/common-hal/audiobusio/PDMIn.c
39964005
#: ports/raspberrypi/common-hal/audiobusio/PDMIn.c
39974006
msgid "sampling rate out of range"

py/circuitpy_defns.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,7 @@ SRC_SHARED_MODULE_ALL = \
633633
audiocore/__init__.c \
634634
audiodelays/Echo.c \
635635
audiodelays/PitchShift.c \
636+
audiodelays/Reverb.c \
636637
audiodelays/__init__.c \
637638
audiofilters/Distortion.c \
638639
audiofilters/Filter.c \

shared-bindings/audiodelays/Reverb.c

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
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/audiodelays/Reverb.h"
10+
#include "shared-bindings/audiocore/__init__.h"
11+
#include "shared-module/audiodelays/Reverb.h"
12+
13+
#include "shared/runtime/context_manager_helpers.h"
14+
#include "py/binary.h"
15+
#include "py/objproperty.h"
16+
#include "py/runtime.h"
17+
#include "shared-bindings/util.h"
18+
#include "shared-module/synthio/block.h"
19+
20+
#define DECAY_DEFAULT 0.7f
21+
#define MIX_DEFAULT 0.5f
22+
23+
//| class Reverb:
24+
//| """An Reverb effect"""
25+
//|
26+
//| def __init__(
27+
//| self,
28+
//| max_delay_ms: int = 500,
29+
//| delay_ms: synthio.BlockInput = 250.0,
30+
//| decay: synthio.BlockInput = 0.7,
31+
//| mix: synthio.BlockInput = 0.5,
32+
//| buffer_size: int = 512,
33+
//| sample_rate: int = 8000,
34+
//| bits_per_sample: int = 16,
35+
//| samples_signed: bool = True,
36+
//| channel_count: int = 1,
37+
//| ) -> None:
38+
//| """Create a Reverb effect where you hear the original sample play back, at a lesser volume after
39+
//| a set number of millisecond delay. The delay timing of the reverb can be changed at runtime
40+
//| with the delay_ms parameter but the delay can never exceed the max_delay_ms parameter. The
41+
//| maximum delay you can set is limited by available memory.
42+
//|
43+
//| Each time the reverb plays back the volume is reduced by the decay setting (reverb * decay).
44+
//|
45+
//| The mix parameter allows you to change how much of the unchanged sample passes through to
46+
//| the output to how much of the effect audio you hear as the output.
47+
//|
48+
//| :param int max_delay_ms: The maximum time the reverb can be in milliseconds
49+
//| :param synthio.BlockInput delay_ms: The current time of the reverb delay in milliseconds. Must be less the max_delay_ms
50+
//| :param synthio.BlockInput decay: The rate the reverb fades. 0.0 = instant; 1.0 = never.
51+
//| :param synthio.BlockInput mix: The mix as a ratio of the sample (0.0) to the effect (1.0).
52+
//| :param int buffer_size: The total size in bytes of each of the two playback buffers to use
53+
//| :param int sample_rate: The sample rate to be used
54+
//| :param int channel_count: The number of channels the source samples contain. 1 = mono; 2 = stereo.
55+
//| :param int bits_per_sample: The bits per sample of the effect
56+
//| :param bool samples_signed: Effect is signed (True) or unsigned (False)
57+
//| :param bool freq_shift: Do reverbs change frequency as the reverb delay changes
58+
//|
59+
//| Playing adding an reverb to a synth::
60+
//|
61+
//| import time
62+
//| import board
63+
//| import audiobusio
64+
//| import synthio
65+
//| import audiodelays
66+
//|
67+
//| audio = audiobusio.I2SOut(bit_clock=board.GP20, word_select=board.GP21, data=board.GP22)
68+
//| synth = synthio.Synthesizer(channel_count=1, sample_rate=44100)
69+
//| reverb = audiodelays.Reverb(max_delay_ms=1000, delay_ms=850, decay=0.65, buffer_size=1024, channel_count=1, sample_rate=44100, mix=0.7, freq_shift=False)
70+
//| reverb.play(synth)
71+
//| audio.play(reverb)
72+
//|
73+
//| note = synthio.Note(261)
74+
//| while True:
75+
//| synth.press(note)
76+
//| time.sleep(0.25)
77+
//| synth.release(note)
78+
//| time.sleep(5)"""
79+
//| ...
80+
//|
81+
static mp_obj_t audiodelays_reverb_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
82+
enum { ARG_buffer_size, ARG_sample_rate, ARG_bits_per_sample, ARG_samples_signed, ARG_channel_count, ARG_freq_shift, };
83+
static const mp_arg_t allowed_args[] = {
84+
{ MP_QSTR_buffer_size, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 512} },
85+
{ MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 8000} },
86+
{ MP_QSTR_bits_per_sample, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 16} },
87+
{ MP_QSTR_samples_signed, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = true} },
88+
{ MP_QSTR_channel_count, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 1 } },
89+
{ MP_QSTR_freq_shift, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = true } },
90+
};
91+
92+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
93+
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
94+
95+
mp_int_t channel_count = mp_arg_validate_int_range(args[ARG_channel_count].u_int, 1, 2, MP_QSTR_channel_count);
96+
mp_int_t sample_rate = mp_arg_validate_int_min(args[ARG_sample_rate].u_int, 1, MP_QSTR_sample_rate);
97+
if (args[ARG_samples_signed].u_bool != true) {
98+
mp_raise_ValueError(MP_ERROR_TEXT("samples_signed must be true"));
99+
}
100+
mp_int_t bits_per_sample = args[ARG_bits_per_sample].u_int;
101+
if (bits_per_sample != 16) {
102+
mp_raise_ValueError(MP_ERROR_TEXT("bits_per_sample must be 16"));
103+
}
104+
105+
audiodelays_reverb_obj_t *self = mp_obj_malloc(audiodelays_reverb_obj_t, &audiodelays_reverb_type);
106+
common_hal_audiodelays_reverb_construct(self, args[ARG_buffer_size].u_int, bits_per_sample, args[ARG_samples_signed].u_bool, channel_count, sample_rate, args[ARG_freq_shift].u_bool);
107+
108+
return MP_OBJ_FROM_PTR(self);
109+
}
110+
111+
//| def deinit(self) -> None:
112+
//| """Deinitialises the Reverb."""
113+
//| ...
114+
//|
115+
static mp_obj_t audiodelays_reverb_deinit(mp_obj_t self_in) {
116+
audiodelays_reverb_obj_t *self = MP_OBJ_TO_PTR(self_in);
117+
common_hal_audiodelays_reverb_deinit(self);
118+
return mp_const_none;
119+
}
120+
static MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_reverb_deinit_obj, audiodelays_reverb_deinit);
121+
122+
static void check_for_deinit(audiodelays_reverb_obj_t *self) {
123+
audiosample_check_for_deinit(&self->base);
124+
}
125+
126+
//| def __enter__(self) -> Reverb:
127+
//| """No-op used by Context Managers."""
128+
//| ...
129+
//|
130+
// Provided by context manager helper.
131+
132+
//| def __exit__(self) -> None:
133+
//| """Automatically deinitializes when exiting a context. See
134+
//| :ref:`lifetime-and-contextmanagers` for more info."""
135+
//| ...
136+
//|
137+
// Provided by context manager helper.
138+
139+
//| playing: bool
140+
//| """True when the effect is playing a sample. (read-only)"""
141+
//|
142+
static mp_obj_t audiodelays_reverb_obj_get_playing(mp_obj_t self_in) {
143+
audiodelays_reverb_obj_t *self = MP_OBJ_TO_PTR(self_in);
144+
check_for_deinit(self);
145+
return mp_obj_new_bool(common_hal_audiodelays_reverb_get_playing(self));
146+
}
147+
MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_reverb_get_playing_obj, audiodelays_reverb_obj_get_playing);
148+
149+
MP_PROPERTY_GETTER(audiodelays_reverb_playing_obj,
150+
(mp_obj_t)&audiodelays_reverb_get_playing_obj);
151+
152+
//| def play(self, sample: circuitpython_typing.AudioSample, *, loop: bool = False) -> None:
153+
//| """Plays the sample once when loop=False and continuously when loop=True.
154+
//| Does not block. Use `playing` to block.
155+
//|
156+
//| The sample must match the encoding settings given in the constructor."""
157+
//| ...
158+
//|
159+
static mp_obj_t audiodelays_reverb_obj_play(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
160+
enum { ARG_sample, ARG_loop };
161+
static const mp_arg_t allowed_args[] = {
162+
{ MP_QSTR_sample, MP_ARG_OBJ | MP_ARG_REQUIRED, {} },
163+
{ MP_QSTR_loop, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = false} },
164+
};
165+
audiodelays_reverb_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
166+
check_for_deinit(self);
167+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
168+
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
169+
170+
171+
mp_obj_t sample = args[ARG_sample].u_obj;
172+
common_hal_audiodelays_reverb_play(self, sample, args[ARG_loop].u_bool);
173+
174+
return mp_const_none;
175+
}
176+
MP_DEFINE_CONST_FUN_OBJ_KW(audiodelays_reverb_play_obj, 1, audiodelays_reverb_obj_play);
177+
178+
//| def stop(self) -> None:
179+
//| """Stops playback of the sample. The reverb continues playing."""
180+
//| ...
181+
//|
182+
//|
183+
static mp_obj_t audiodelays_reverb_obj_stop(mp_obj_t self_in) {
184+
audiodelays_reverb_obj_t *self = MP_OBJ_TO_PTR(self_in);
185+
186+
common_hal_audiodelays_reverb_stop(self);
187+
return mp_const_none;
188+
}
189+
MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_reverb_stop_obj, audiodelays_reverb_obj_stop);
190+
191+
static const mp_rom_map_elem_t audiodelays_reverb_locals_dict_table[] = {
192+
// Methods
193+
{ MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiodelays_reverb_deinit_obj) },
194+
{ MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) },
195+
{ MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) },
196+
{ MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audiodelays_reverb_play_obj) },
197+
{ MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&audiodelays_reverb_stop_obj) },
198+
199+
// Properties
200+
{ MP_ROM_QSTR(MP_QSTR_playing), MP_ROM_PTR(&audiodelays_reverb_playing_obj) },
201+
AUDIOSAMPLE_FIELDS,
202+
};
203+
static MP_DEFINE_CONST_DICT(audiodelays_reverb_locals_dict, audiodelays_reverb_locals_dict_table);
204+
205+
static const audiosample_p_t audiodelays_reverb_proto = {
206+
MP_PROTO_IMPLEMENT(MP_QSTR_protocol_audiosample)
207+
.reset_buffer = (audiosample_reset_buffer_fun)audiodelays_reverb_reset_buffer,
208+
.get_buffer = (audiosample_get_buffer_fun)audiodelays_reverb_get_buffer,
209+
};
210+
211+
MP_DEFINE_CONST_OBJ_TYPE(
212+
audiodelays_reverb_type,
213+
MP_QSTR_Reverb,
214+
MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS,
215+
make_new, audiodelays_reverb_make_new,
216+
locals_dict, &audiodelays_reverb_locals_dict,
217+
protocol, &audiodelays_reverb_proto
218+
);

shared-bindings/audiodelays/Reverb.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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/audiodelays/Reverb.h"
10+
11+
extern const mp_obj_type_t audiodelays_reverb_type;
12+
13+
void common_hal_audiodelays_reverb_construct(audiodelays_reverb_obj_t *self,
14+
uint32_t buffer_size, uint8_t bits_per_sample, bool samples_signed,
15+
uint8_t channel_count, uint32_t sample_rate, bool freq_shift);
16+
17+
void common_hal_audiodelays_reverb_deinit(audiodelays_reverb_obj_t *self);
18+
bool common_hal_audiodelays_reverb_deinited(audiodelays_reverb_obj_t *self);
19+
20+
uint32_t common_hal_audiodelays_reverb_get_sample_rate(audiodelays_reverb_obj_t *self);
21+
uint8_t common_hal_audiodelays_reverb_get_channel_count(audiodelays_reverb_obj_t *self);
22+
uint8_t common_hal_audiodelays_reverb_get_bits_per_sample(audiodelays_reverb_obj_t *self);
23+
24+
bool common_hal_audiodelays_reverb_get_playing(audiodelays_reverb_obj_t *self);
25+
void common_hal_audiodelays_reverb_play(audiodelays_reverb_obj_t *self, mp_obj_t sample, bool loop);
26+
void common_hal_audiodelays_reverb_stop(audiodelays_reverb_obj_t *self);

shared-bindings/audiodelays/__init__.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
#include "shared-bindings/audiodelays/__init__.h"
1313
#include "shared-bindings/audiodelays/Echo.h"
1414
#include "shared-bindings/audiodelays/PitchShift.h"
15+
#include "shared-bindings/audiodelays/Reverb.h"
16+
1517

1618
//| """Support for audio delay effects
1719
//|
@@ -23,6 +25,7 @@ static const mp_rom_map_elem_t audiodelays_module_globals_table[] = {
2325
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audiodelays) },
2426
{ MP_ROM_QSTR(MP_QSTR_Echo), MP_ROM_PTR(&audiodelays_echo_type) },
2527
{ MP_ROM_QSTR(MP_QSTR_PitchShift), MP_ROM_PTR(&audiodelays_pitch_shift_type) },
28+
{ MP_ROM_QSTR(MP_QSTR_Reverb), MP_ROM_PTR(&audiodelays_reverb_type) },
2629
};
2730

2831
static MP_DEFINE_CONST_DICT(audiodelays_module_globals, audiodelays_module_globals_table);

0 commit comments

Comments
 (0)