Skip to content

Commit e3228d0

Browse files
committed
Added frequency shift on echo option from @dcooperdalrymple
1 parent 4464531 commit e3228d0

File tree

4 files changed

+172
-42
lines changed

4 files changed

+172
-42
lines changed

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

+36-3
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
//| :param int channel_count: The number of channels the source samples contain. 1 = mono; 2 = stereo.
5454
//| :param int bits_per_sample: The bits per sample of the effect
5555
//| :param bool samples_signed: Effect is signed (True) or unsigned (False)
56+
//| :param bool freq_shift: Do echos change frequency as the echo delay changes
5657
//|
5758
//| Playing adding an echo to a synth::
5859
//|
@@ -64,7 +65,7 @@
6465
//|
6566
//| audio = audiobusio.I2SOut(bit_clock=board.GP20, word_select=board.GP21, data=board.GP22)
6667
//| synth = synthio.Synthesizer(channel_count=1, sample_rate=44100)
67-
//| echo = audiodelays.Echo(max_delay_ms=1000, delay_ms=850, decay=0.65, buffer_size=1024, channel_count=1, sample_rate=44100, mix=0.7)
68+
//| echo = audiodelays.Echo(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)
6869
//| echo.play(synth)
6970
//| audio.play(echo)
7071
//|
@@ -76,7 +77,7 @@
7677
//| time.sleep(5)"""
7778
//| ...
7879
static mp_obj_t audiodelays_echo_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
79-
enum { ARG_max_delay_ms, ARG_delay_ms, ARG_decay, ARG_mix, ARG_buffer_size, ARG_sample_rate, ARG_bits_per_sample, ARG_samples_signed, ARG_channel_count, };
80+
enum { ARG_max_delay_ms, ARG_delay_ms, ARG_decay, ARG_mix, ARG_buffer_size, ARG_sample_rate, ARG_bits_per_sample, ARG_samples_signed, ARG_channel_count, ARG_freq_shift, };
8081
static const mp_arg_t allowed_args[] = {
8182
{ MP_QSTR_max_delay_ms, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 500 } },
8283
{ MP_QSTR_delay_ms, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} },
@@ -87,6 +88,7 @@ static mp_obj_t audiodelays_echo_make_new(const mp_obj_type_t *type, size_t n_ar
8788
{ MP_QSTR_bits_per_sample, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 16} },
8889
{ MP_QSTR_samples_signed, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = true} },
8990
{ MP_QSTR_channel_count, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 1 } },
91+
{ MP_QSTR_freq_shift, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = true } },
9092
};
9193

9294
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
@@ -102,7 +104,7 @@ static mp_obj_t audiodelays_echo_make_new(const mp_obj_type_t *type, size_t n_ar
102104
}
103105

104106
audiodelays_echo_obj_t *self = mp_obj_malloc(audiodelays_echo_obj_t, &audiodelays_echo_type);
105-
common_hal_audiodelays_echo_construct(self, max_delay_ms, args[ARG_delay_ms].u_obj, args[ARG_decay].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);
107+
common_hal_audiodelays_echo_construct(self, max_delay_ms, args[ARG_delay_ms].u_obj, args[ARG_decay].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, args[ARG_freq_shift].u_bool);
106108

107109
return MP_OBJ_FROM_PTR(self);
108110
}
@@ -222,6 +224,36 @@ MP_PROPERTY_GETSET(audiodelays_echo_mix_obj,
222224
(mp_obj_t)&audiodelays_echo_get_mix_obj,
223225
(mp_obj_t)&audiodelays_echo_set_mix_obj);
224226

227+
228+
229+
//| freq_shift: bool
230+
//| """Does the echo change frequencies as the delay changes."""
231+
static mp_obj_t audiodelays_echo_obj_get_freq_shift(mp_obj_t self_in) {
232+
return mp_obj_new_bool(common_hal_audiodelays_echo_get_freq_shift(self_in));
233+
}
234+
MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_echo_get_freq_shift_obj, audiodelays_echo_obj_get_freq_shift);
235+
236+
static mp_obj_t audiodelays_echo_obj_set_freq_shift(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
237+
enum { ARG_freq_shift };
238+
static const mp_arg_t allowed_args[] = {
239+
{ MP_QSTR_freq_shift, MP_ARG_BOOL | MP_ARG_REQUIRED, {} },
240+
};
241+
audiodelays_echo_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
242+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
243+
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
244+
245+
common_hal_audiodelays_echo_set_freq_shift(self, args[ARG_freq_shift].u_bool);
246+
247+
return mp_const_none;
248+
}
249+
MP_DEFINE_CONST_FUN_OBJ_KW(audiodelays_echo_set_freq_shift_obj, 1, audiodelays_echo_obj_set_freq_shift);
250+
251+
MP_PROPERTY_GETSET(audiodelays_echo_freq_shift_obj,
252+
(mp_obj_t)&audiodelays_echo_get_freq_shift_obj,
253+
(mp_obj_t)&audiodelays_echo_set_freq_shift_obj);
254+
255+
256+
225257
//| playing: bool
226258
//| """True when the effect is playing a sample. (read-only)"""
227259
static mp_obj_t audiodelays_echo_obj_get_playing(mp_obj_t self_in) {
@@ -284,6 +316,7 @@ static const mp_rom_map_elem_t audiodelays_echo_locals_dict_table[] = {
284316
{ MP_ROM_QSTR(MP_QSTR_delay_ms), MP_ROM_PTR(&audiodelays_echo_delay_ms_obj) },
285317
{ MP_ROM_QSTR(MP_QSTR_decay), MP_ROM_PTR(&audiodelays_echo_decay_obj) },
286318
{ MP_ROM_QSTR(MP_QSTR_mix), MP_ROM_PTR(&audiodelays_echo_mix_obj) },
319+
{ MP_ROM_QSTR(MP_QSTR_freq_shift), MP_ROM_PTR(&audiodelays_echo_freq_shift_obj) },
287320
};
288321
static MP_DEFINE_CONST_DICT(audiodelays_echo_locals_dict, audiodelays_echo_locals_dict_table);
289322

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ extern const mp_obj_type_t audiodelays_echo_type;
1313
void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_t max_delay_ms,
1414
mp_obj_t delay_ms, mp_obj_t decay, mp_obj_t mix,
1515
uint32_t buffer_size, uint8_t bits_per_sample, bool samples_signed,
16-
uint8_t channel_count, uint32_t sample_rate);
16+
uint8_t channel_count, uint32_t sample_rate, bool freq_shift);
1717

1818
void common_hal_audiodelays_echo_deinit(audiodelays_echo_obj_t *self);
1919
bool common_hal_audiodelays_echo_deinited(audiodelays_echo_obj_t *self);
@@ -25,6 +25,9 @@ uint8_t common_hal_audiodelays_echo_get_bits_per_sample(audiodelays_echo_obj_t *
2525
mp_obj_t common_hal_audiodelays_echo_get_delay_ms(audiodelays_echo_obj_t *self);
2626
void common_hal_audiodelays_echo_set_delay_ms(audiodelays_echo_obj_t *self, mp_obj_t delay_ms);
2727

28+
bool common_hal_audiodelays_echo_get_freq_shift(audiodelays_echo_obj_t *self);
29+
void common_hal_audiodelays_echo_set_freq_shift(audiodelays_echo_obj_t *self, bool freq_shift);
30+
2831
mp_obj_t common_hal_audiodelays_echo_get_decay(audiodelays_echo_obj_t *self);
2932
void common_hal_audiodelays_echo_set_decay(audiodelays_echo_obj_t *self, mp_obj_t decay);
3033

Diff for: shared-module/audiodelays/Echo.c

+126-37
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// This file is part of the CircuitPython project: https://circuitpython.org
22
//
3-
// SPDX-FileCopyrightText: Copyright (c) 2024 Mark Komus
3+
// SPDX-FileCopyrightText: Copyright (c) 2024 Mark Komus, Cooper Dalrymple
44
//
55
// SPDX-License-Identifier: MIT
66
#include "shared-bindings/audiodelays/Echo.h"
@@ -11,7 +11,10 @@
1111
void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_t max_delay_ms,
1212
mp_obj_t delay_ms, mp_obj_t decay, mp_obj_t mix,
1313
uint32_t buffer_size, uint8_t bits_per_sample,
14-
bool samples_signed, uint8_t channel_count, uint32_t sample_rate) {
14+
bool samples_signed, uint8_t channel_count, uint32_t sample_rate, bool freq_shift) {
15+
16+
// Set whether the echo shifts frequencies as the delay changes like a doppler effect
17+
self->freq_shift = freq_shift;
1518

1619
// Basic settings every effect and audio sample has
1720
// These are the effects values, not the source sample(s)
@@ -82,15 +85,17 @@ void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_
8285
}
8386
memset(self->echo_buffer, 0, self->max_echo_buffer_len);
8487

85-
// calculate current echo buffer size we use for the given delay
88+
// calculate everything needed for the current delay
8689
mp_float_t f_delay_ms = synthio_block_slot_get(&self->delay_ms);
87-
self->current_delay_ms = f_delay_ms;
88-
self->echo_buffer_len = self->sample_rate / 1000.0f * f_delay_ms * (self->channel_count * sizeof(uint16_t));
90+
recalculate_delay(self, f_delay_ms);
8991

9092
// read is where we read previous echo from delay_ms ago to play back now
9193
// write is where the store the latest playing sample to echo back later
9294
self->echo_buffer_read_pos = self->buffer_len / sizeof(uint16_t);
9395
self->echo_buffer_write_pos = 0;
96+
97+
// where we read the previous echo from delay_ms ago to play back now (for freq shift)
98+
self->echo_buffer_left_pos = self->echo_buffer_right_pos = 0;
9499
}
95100

96101
bool common_hal_audiodelays_echo_deinited(audiodelays_echo_obj_t *self) {
@@ -109,7 +114,6 @@ void common_hal_audiodelays_echo_deinit(audiodelays_echo_obj_t *self) {
109114
self->buffer[1] = NULL;
110115
}
111116

112-
113117
mp_obj_t common_hal_audiodelays_echo_get_delay_ms(audiodelays_echo_obj_t *self) {
114118
return self->delay_ms.obj;
115119
}
@@ -123,23 +127,32 @@ void common_hal_audiodelays_echo_set_delay_ms(audiodelays_echo_obj_t *self, mp_o
123127
}
124128

125129
void recalculate_delay(audiodelays_echo_obj_t *self, mp_float_t f_delay_ms) {
126-
// Calculate the current echo buffer length in bytes
130+
if (self->freq_shift) {
131+
// Calculate the rate of iteration over the echo buffer with 8 sub-bits
132+
self->echo_buffer_rate = MAX(self->max_delay_ms / f_delay_ms * 256.0f, 1.0);
133+
self->echo_buffer_len = self->max_echo_buffer_len;
134+
} else {
135+
// Calculate the current echo buffer length in bytes
136+
uint32_t new_echo_buffer_len = self->sample_rate / 1000.0f * f_delay_ms * (self->channel_count * sizeof(uint16_t));
137+
138+
// Check if our new echo is too long for our maximum buffer
139+
if (new_echo_buffer_len > self->max_echo_buffer_len) {
140+
return;
141+
} else if (new_echo_buffer_len < 0.0) { // or too short!
142+
return;
143+
}
127144

128-
uint32_t new_echo_buffer_len = self->sample_rate / 1000.0f * f_delay_ms * (self->channel_count * sizeof(uint16_t));
145+
// If the echo buffer is larger then our audio buffer weird things happen
146+
if (new_echo_buffer_len < self->buffer_len) {
147+
return;
148+
}
129149

130-
// Check if our new echo is too long for our maximum buffer
131-
if (new_echo_buffer_len > self->max_echo_buffer_len) {
132-
return;
133-
} else if (new_echo_buffer_len < 0.0) { // or too short!
134-
return;
135-
}
150+
self->echo_buffer_len = new_echo_buffer_len;
136151

137-
// If the echo buffer is larger then our audio buffer weird things happen
138-
if (new_echo_buffer_len < self->buffer_len) {
139-
return;
152+
// Clear the now unused part of the buffer or some weird artifacts appear
153+
memset(self->echo_buffer + self->echo_buffer_len, 0, self->max_echo_buffer_len - self->echo_buffer_len);
140154
}
141155

142-
self->echo_buffer_len = new_echo_buffer_len;
143156
self->current_delay_ms = f_delay_ms;
144157
}
145158

@@ -159,6 +172,16 @@ void common_hal_audiodelays_echo_set_mix(audiodelays_echo_obj_t *self, mp_obj_t
159172
synthio_block_assign_slot(arg, &self->mix, MP_QSTR_mix);
160173
}
161174

175+
bool common_hal_audiodelays_echo_get_freq_shift(audiodelays_echo_obj_t *self) {
176+
return self->freq_shift;
177+
}
178+
179+
void common_hal_audiodelays_echo_set_freq_shift(audiodelays_echo_obj_t *self, bool freq_shift) {
180+
self->freq_shift = freq_shift;
181+
uint32_t delay_ms = (uint32_t)synthio_block_slot_get(&self->delay_ms);
182+
recalculate_delay(self, delay_ms);
183+
}
184+
162185
uint32_t common_hal_audiodelays_echo_get_sample_rate(audiodelays_echo_obj_t *self) {
163186
return self->sample_rate;
164187
}
@@ -257,6 +280,10 @@ int16_t mix_down_sample(int32_t sample) {
257280
audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t *self, bool single_channel_output, uint8_t channel,
258281
uint8_t **buffer, uint32_t *buffer_length) {
259282

283+
if (!single_channel_output) {
284+
channel = 0;
285+
}
286+
260287
// get the effect values we need from the BlockInput. These may change at run time so you need to do bounds checking if required
261288
mp_float_t mix = MIN(1.0, MAX(synthio_block_slot_get(&self->mix), 0.0));
262289
mp_float_t decay = MIN(1.0, MAX(synthio_block_slot_get(&self->decay), 0.0));
@@ -278,6 +305,15 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t *
278305
int16_t *echo_buffer = (int16_t *)self->echo_buffer;
279306
uint32_t echo_buf_len = self->echo_buffer_len / sizeof(uint16_t);
280307

308+
// Set our echo buffer position accounting for stereo
309+
uint32_t echo_buffer_pos = 0;
310+
if (self->freq_shift) {
311+
echo_buffer_pos = self->echo_buffer_left_pos;
312+
if (channel == 1) {
313+
echo_buffer_pos = self->echo_buffer_right_pos;
314+
}
315+
}
316+
281317
// Loop over the entire length of our buffer to fill it, this may require several calls to get data from the sample
282318
while (length != 0) {
283319
// Check if there is no more sample to play, we will either load more data, reset the sample if loop is on or clear the sample
@@ -314,27 +350,46 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t *
314350
} else {
315351
// Since we have no sample we can just iterate over the our entire remaining buffer and finish
316352
for (uint32_t i = 0; i < length; i++) {
317-
int16_t echo = echo_buffer[self->echo_buffer_read_pos++] * decay;
318-
echo_buffer[self->echo_buffer_write_pos++] = echo;
353+
int16_t echo, word = 0;
354+
uint32_t next_buffer_pos = 0;
355+
356+
if (self->freq_shift) {
357+
echo = echo_buffer[echo_buffer_pos >> 8];
358+
next_buffer_pos = echo_buffer_pos + self->echo_buffer_rate;
359+
360+
word = echo * decay;
361+
for (uint32_t j = echo_buffer_pos >> 8; j < next_buffer_pos >> 8; j++) {
362+
echo_buffer[j % echo_buf_len] = word;
363+
}
364+
} else {
365+
echo = echo_buffer[self->echo_buffer_read_pos++];
366+
word = echo * decay;
367+
echo_buffer[self->echo_buffer_write_pos++] = word;
368+
}
369+
370+
word = echo * mix;
319371

320372
if (MP_LIKELY(self->bits_per_sample == 16)) {
321-
word_buffer[i] = echo * mix;
373+
word_buffer[i] = word;
322374
if (!self->samples_signed) {
323375
word_buffer[i] ^= 0x8000;
324376
}
325377
} else {
326-
echo = echo * mix;
327-
hword_buffer[i] = echo;
378+
hword_buffer[i] = (int8_t)word;
328379
if (!self->samples_signed) {
329380
hword_buffer[i] ^= 0x80;
330381
}
331382
}
332383

333-
if (self->echo_buffer_read_pos >= echo_buf_len) {
334-
self->echo_buffer_read_pos = 0;
335-
}
336-
if (self->echo_buffer_write_pos >= echo_buf_len) {
337-
self->echo_buffer_write_pos = 0;
384+
if (self->freq_shift) {
385+
echo_buffer_pos = next_buffer_pos % (echo_buf_len << 8);
386+
} else {
387+
if (self->echo_buffer_read_pos >= echo_buf_len) {
388+
self->echo_buffer_read_pos = 0;
389+
}
390+
if (self->echo_buffer_write_pos >= echo_buf_len) {
391+
self->echo_buffer_write_pos = 0;
392+
}
338393
}
339394
}
340395
}
@@ -370,22 +425,44 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t *
370425
}
371426
}
372427

373-
int32_t echo = echo_buffer[self->echo_buffer_read_pos++] * decay;
374-
int32_t word = echo + sample_word;
428+
int32_t echo, word = 0;
429+
uint32_t next_buffer_pos = 0;
430+
if (self->freq_shift) {
431+
echo = echo_buffer[echo_buffer_pos >> 8];
432+
next_buffer_pos = echo_buffer_pos + self->echo_buffer_rate;
433+
word = echo * decay + sample_word;
434+
} else {
435+
echo = echo_buffer[self->echo_buffer_read_pos++];
436+
word = echo * decay + sample_word;
437+
}
375438

376439
if (MP_LIKELY(self->bits_per_sample == 16)) {
377440
word = mix_down_sample(word);
378-
echo_buffer[self->echo_buffer_write_pos++] = (int16_t)word;
441+
if (self->freq_shift) {
442+
for (uint32_t j = echo_buffer_pos >> 8; j < next_buffer_pos >> 8; j++) {
443+
echo_buffer[j % echo_buf_len] = (int16_t)word;
444+
}
445+
} else {
446+
echo_buffer[self->echo_buffer_write_pos++] = (int16_t)word;
447+
}
379448
} else {
380449
// Do not have mix_down for 8 bit so just hard cap samples into 1 byte
381450
if (word > 127) {
382451
word = 127;
383452
} else if (word < -128) {
384453
word = -128;
385454
}
386-
echo_buffer[self->echo_buffer_write_pos++] = (int8_t)word;
455+
if (self->freq_shift) {
456+
for (uint32_t j = echo_buffer_pos >> 8; j < next_buffer_pos >> 8; j++) {
457+
echo_buffer[j % echo_buf_len] = (int8_t)word;
458+
}
459+
} else {
460+
echo_buffer[self->echo_buffer_write_pos++] = (int8_t)word;
461+
}
387462
}
388463

464+
word = echo + sample_word;
465+
389466
if (MP_LIKELY(self->bits_per_sample == 16)) {
390467
word_buffer[i] = (sample_word * (1.0 - mix)) + (word * mix);
391468
if (!self->samples_signed) {
@@ -400,11 +477,15 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t *
400477
}
401478
}
402479

403-
if (self->echo_buffer_read_pos >= echo_buf_len) {
404-
self->echo_buffer_read_pos = 0;
405-
}
406-
if (self->echo_buffer_write_pos >= echo_buf_len) {
407-
self->echo_buffer_write_pos = 0;
480+
if (self->freq_shift) {
481+
echo_buffer_pos = next_buffer_pos % (echo_buf_len << 8);
482+
} else {
483+
if (self->echo_buffer_read_pos >= echo_buf_len) {
484+
self->echo_buffer_read_pos = 0;
485+
}
486+
if (self->echo_buffer_write_pos >= echo_buf_len) {
487+
self->echo_buffer_write_pos = 0;
488+
}
408489
}
409490
}
410491
}
@@ -418,6 +499,14 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t *
418499
}
419500
}
420501

502+
if (self->freq_shift) {
503+
if (channel == 0) {
504+
self->echo_buffer_left_pos = echo_buffer_pos;
505+
} else if (channel == 1) {
506+
self->echo_buffer_right_pos = echo_buffer_pos;
507+
}
508+
}
509+
421510
// Finally pass our buffer and length to the calling audio function
422511
*buffer = (uint8_t *)self->buffer[self->last_buf_idx];
423512
*buffer_length = self->buffer_len;

0 commit comments

Comments
 (0)