8000 Merge pull request #9776 from relic-se/audiofilters_distortion · adafruit/circuitpython@10cd4f0 · GitHub
[go: up one dir, main page]

Skip to content

Commit 10cd4f0

Browse files
authored
Merge pull request #9776 from relic-se/audiofilters_distortion
audiofilters: Add Distortion effect and implement LFO ticking
2 parents 943a2f3 + 815f829 commit 10cd4f0

File tree

16 files changed

+995
-120
lines changed

16 files changed

+995
-120
lines changed

ports/unix/variants/coverage/mpconfigvariant.mk

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ SRC_BITMAP := \
3535
shared-bindings/audiocore/WaveFile.c \
3636
shared-bindings/audiodelays/Echo.c \
3737
shared-bindings/audiodelays/__init__.c \
38+
shared-bindings/audiofilters/Distortion.c \
3839
shared-bindings/audiofilters/Filter.c \
3940
shared-bindings/audiofilters/__init__.c \
4041
shared-bindings/audiomixer/__init__.c \
@@ -77,6 +78,7 @@ SRC_BITMAP := \
7778
shared-module/audiocore/WaveFile.c \
7879
shared-module/audiodelays/Echo.c \
7980
shared-module/audiodelays/__init__.c \
81+
shared-module/audiofilters/Distortion.c \
8082
shared-module/audiofilters/Filter.c \
8183
shared-module/audiofilters/__init__.c \
8284
shared-module/audiomixer/__init__.c \

py/circuitpy_defns.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,7 @@ SRC_SHARED_MODULE_ALL = \
625625
audiocore/__init__.c \
626626
audiodelays/Echo.c \
627627
audiodelays/__init__.c \
628+
audiofilters/Distortion.c \
628629
audiofilters/Filter.c \
629630
audiofilters/__init__.c \
630631
audioio/__init__.c \

shared-bindings/audiofilters/Distortion.c

Lines changed: 385 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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/Distortion.h"
10+
11+
extern const mp_obj_type_t audiofilters_distortion_type;
12+
extern const mp_obj_type_t audiofilters_distortion_mode_type;
13+
14+
void common_hal_audiofilters_distortion_construct(audiofilters_distortion_obj_t *self,
15+
mp_obj_t drive, mp_obj_t pre_gain, mp_obj_t post_gain,
16+
audiofilters_distortion_mode mode, bool soft_clip, mp_obj_t mix,
17+
uint32_t buffer_size, uint8_t bits_per_sample, bool samples_signed,
18+
uint8_t channel_count, uint32_t sample_rate);
19+
20+
void common_hal_audiofilters_distortion_deinit(audiofilters_distortion_obj_t *self);
21+
bool common_hal_audiofilters_distortion_deinited(audiofilters_distortion_obj_t *self);
22+
23+
uint32_t common_hal_audiofilters_distortion_get_sample_rate(audiofilters_distortion_obj_t *self);
24+
uint8_t common_hal_audiofilters_distortion_get_channel_count(audiofilters_distortion_obj_t *self);
25+
uint8_t common_hal_audiofilters_distortion_get_bits_per_sample(audiofilters_distortion_obj_t *self);
26+
27+
mp_obj_t common_hal_audiofilters_distortion_get_drive(audiofilters_distortion_obj_t *self);
28+
void common_hal_audiofilters_distortion_set_drive(audiofilters_distortion_obj_t *self, mp_obj_t arg);
29+
30+
mp_obj_t common_hal_audiofilters_distortion_get_pre_gain(audiofilters_distortion_obj_t *self);
31+
void common_hal_audiofilters_distortion_set_pre_gain(audiofilters_distortion_obj_t *self, mp_obj_t arg);
32+
33+
mp_obj_t common_hal_audiofilters_distortion_get_post_gain(audiofilters_distortion_obj_t *self);
34+
void common_hal_audiofilters_distortion_set_post_gain(audiofilters_distortion_obj_t *self, mp_obj_t arg);
35+
36+
audiofilters_distortion_mode common_hal_audiofilters_distortion_get_mode(audiofilters_distortion_obj_t *self);
37+
void common_hal_audiofilters_distortion_set_mode(audiofilters_distortion_obj_t *self, audiofilters_distortion_mode mode);
38+
39+
bool common_hal_audiofilters_distortion_get_soft_clip(audiofilters_distortion_obj_t *self);
40+
void common_hal_audiofilters_distortion_set_soft_clip(audiofilters_distortion_obj_t *self, bool soft_clip);
41+
42+
mp_obj_t common_hal_audiofilters_distortion_get_mix(audiofilters_distortion_obj_t *self);
43+
void common_hal_audiofilters_distortion_set_mix(audiofilters_distortion_obj_t *self, mp_obj_t arg);
44+
45+
bool common_hal_audiofilters_distortion_get_playing(audiofilters_distortion_obj_t *self);
46+
void common_hal_audiofilters_distortion_play(audiofilters_distortion_obj_t *self, mp_obj_t sample, bool loop);
47+
void common_hal_audiofilters_distortion_stop(audiofilters_distortion_obj_t *self);

shared-bindings/audiofilters/Filter.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ static mp_obj_t audiofilters_filter_make_new(const mp_obj_type_t *type, size_t n
7272
enum { ARG_filter, ARG_mix, ARG_buffer_size, ARG_sample_rate, ARG_bits_per_sample, ARG_samples_signed, ARG_channel_count, };
7373
static const mp_arg_t allowed_args[] = {
7474
{ MP_QSTR_filter, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} },
75-
{ MP_QSTR_mix, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} },
75+
{ MP_QSTR_mix, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(1)} },
7676
{ MP_QSTR_buffer_size, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 512} },
7777
{ MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 8000} },
7878
{ MP_QSTR_bits_per_sample, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 16} },

shared-bindings/audiofilters/__init__.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "py/runtime.h"
1111

1212
#include "shared-bindings/audiofilters/__init__.h"
13+
#include "shared-bindings/audiofilters/Distortion.h"
1314
#include "shared-bindings/audiofilters/Filter.h"
1415

1516
//| """Support for audio filter effects
@@ -21,6 +22,10 @@
2122
static const mp_rom_map_elem_t audiofilters_module_globals_table[] = {
2223
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audiofilters) },
2324
{ MP_ROM_QSTR(MP_QSTR_Filter), MP_ROM_PTR(&audiofilters_filter_type) },
25+
{ MP_ROM_QSTR(MP_QSTR_Distortion), MP_ROM_PTR(&audiofilters_distortion_type) },
26+
27+
// Enum-like Classes.
28+
{ MP_ROM_QSTR(MP_QSTR_DistortionMode), MP_ROM_PTR(&audiofilters_distortion_mode_type) },
2429
};
2530

2631
static MP_DEFINE_CONST_DICT(audiofilters_module_globals, audiofilters_module_globals_table);

shared-bindings/synthio/__init__.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(synthio_voct_to_hz_obj, voct_to_hz);
292292

293293
#if CIRCUITPY_AUDIOCORE_DEBUG
294294
static mp_obj_t synthio_lfo_tick(size_t n, const mp_obj_t *args) {
295-
shared_bindings_synthio_lfo_tick(48000);
295+
shared_bindings_synthio_lfo_tick(48000, SYNTHIO_MAX_DUR);
296296
mp_obj_t result[n];
297297
for (size_t i = 0; i < n; i++) {
298298
synthio_block 558 _slot_t slot;

shared-module/audiodelays/Echo.c

Lines changed: 53 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <stdint.h>
99
#include "py/runtime.h"
10+
#include <math.h>
1011

1112
void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_t max_delay_ms,
1213
mp_obj_t delay_ms, mp_obj_t decay, mp_obj_t mix,
@@ -57,17 +58,17 @@ void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_
5758

5859
// If we did not receive a BlockInput we need to create a default float value
5960
if (decay == MP_OBJ_NULL) {
60-
decay = mp_obj_new_float(0.7);
61+
decay = mp_obj_new_float(MICROPY_FLOAT_CONST(0.7));
6162
}
6263
synthio_block_assign_slot(decay, &self->decay, MP_QSTR_decay);
6364

6465
if (delay_ms == MP_OBJ_NULL) {
65-
delay_ms = mp_obj_new_float(250.0);
66+
delay_ms = mp_obj_new_float(MICROPY_FLOAT_CONST(250.0));
6667
}
6768
synthio_block_assign_slot(delay_ms, &self->delay_ms, MP_QSTR_delay_ms);
6869

6970
if (mix == MP_OBJ_NULL) {
70-
mix = mp_obj_new_float(0.5);
71+
mix = mp_obj_new_float(MICROPY_FLOAT_CONST(0.5));
7172
}
7273
synthio_block_assign_slot(mix, &self->mix, MP_QSTR_mix);
7374

@@ -77,14 +78,17 @@ void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_
7778

7879
// Allocate the echo buffer for the max possible delay, echo is always 16-bit
7980
self->max_delay_ms = max_delay_ms;
80-
self->max_echo_buffer_len = (uint32_t)(self->sample_rate / 1000.0f * max_delay_ms) * (self->channel_count * sizeof(uint16_t)); // bytes
81+
self->max_echo_buffer_len = (uint32_t)(self->sample_rate / MICROPY_FLOAT_CONST(1000.0) * max_delay_ms) * (self->channel_count * sizeof(uint16_t)); // bytes
8182
self->echo_buffer = m_malloc(self->max_echo_buffer_len);
8283
if (self->echo_buffer == NULL) {
8384
common_hal_audiodelays_echo_deinit(self);
8485
m_malloc_fail(self->max_echo_buffer_len);
8586
}
8687
memset(self->echo_buffer, 0, self->max_echo_buffer_len);
8788

89+
// calculate the length of a single sample in milliseconds
90+
self->sample_ms = MICROPY_FLOAT_CONST(1000.0) / self->sample_rate;
91+
8892
// calculate everything needed for the current delay
8993
mp_float_t f_delay_ms = synthio_block_slot_get(&self->delay_ms);
9094
recalculate_delay(self, f_delay_ms);
@@ -127,6 +131,9 @@ void common_hal_audiodelays_echo_set_delay_ms(audiodelays_echo_obj_t *self, mp_o
127131
}
128132

129133
void recalculate_delay(audiodelays_echo_obj_t *self, mp_float_t f_delay_ms) {
134+
// Require that delay is at least 1 sample long
135+
f_delay_ms = MAX(f_delay_ms, self->sample_ms);
136+
130137
if (self->freq_shift) {
131138
// Calculate the rate of iteration over the echo buffer with 8 sub-bits
132139
self->echo_buffer_rate = (uint32_t)MAX(self->max_delay_ms / f_delay_ms * MICROPY_FLOAT_CONST(256.0), MICROPY_FLOAT_CONST(1.0));
@@ -153,7 +160,7 @@ void recalculate_delay(audiodelays_echo_obj_t *self, mp_float_t f_delay_ms) {
153160
memset(self->echo_buffer + self->echo_buffer_len, 0, self->max_echo_buffer_len - self->echo_buffer_len);
154161
}
155162

156-
self->current_delay_ms = (uint32_t)f_delay_ms;
163+
self->current_delay_ms = f_delay_ms;
157164
}
158165

159166
mp_obj_t common_hal_audiodelays_echo_get_decay(audiodelays_echo_obj_t *self) {
@@ -251,48 +258,13 @@ void common_hal_audiodelays_echo_stop(audiodelays_echo_obj_t *self) {
251258
return;
252259
}
253260

254-
#define RANGE_LOW_16 (-28000)
255-
#define RANGE_HIGH_16 (28000)
256-
#define RANGE_SHIFT_16 (16)
257-
#define RANGE_SCALE_16 (0xfffffff / (32768 * 2 - RANGE_HIGH_16)) // 2 for echo+sample
258-
259-
// dynamic range compression via a downward compressor with hard knee
260-
//
261-
// When the output value is within the range +-28000 (about 85% of full scale),
262-
// it is unchanged. Otherwise, it undergoes a gain reduction so that the
263-
// largest possible values, (+32768,-32767) * 2 (2 for echo and sample),
264-
// still fit within the output range
265-
//
266-
// This produces a much louder overall volume with multiple voices, without
267-
// much additional processing.
268-
//
269-
// https://en.wikipedia.org/wiki/Dynamic_range_compression
270-
static
271-
int16_t mix_down_sample(int32_t sample) {
272-
if (sample < RANGE_LOW_16) {
273-
sample = (((sample - RANGE_LOW_16) * RANGE_SCALE_16) >> RANGE_SHIFT_16) + RANGE_LOW_16;
274-
} else if (sample > RANGE_HIGH_16) {
275-
sample = (((sample - RANGE_HIGH_16) * RANGE_SCALE_16) >> RANGE_SHIFT_16) + RANGE_HIGH_16;
276-
}
277-
return sample;
278-
}
279-
280261
audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t *self, bool single_channel_output, uint8_t channel,
281262
uint8_t **buffer, uint32_t *buffer_length) {
282263

283264
if (!single_channel_output) {
284265
channel = 0;
285266
}
286267

287-
// get the effect values we need from the BlockInput. These may change at run time so you need to do bounds checking if required
288-
mp_float_t mix = MIN(1.0, MAX(synthio_block_slot_get(&self->mix), 0.0));
289-
mp_float_t decay = MIN(1.0, MAX(synthio_block_slot_get(&self->decay), 0.0));
290-
291-
uint32_t delay_ms = (uint32_t)synthio_block_slot_get(&self->delay_ms);
292-
if (self->current_delay_ms != delay_ms) {
293-
recalculate_delay(self, delay_ms);
294-
}
295-
296268
// Switch our buffers to the other buffer
297269
self->last_buf_idx = !self->last_buf_idx;
298270

@@ -303,16 +275,6 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t *
303275

304276
// The echo buffer is always stored as a 16-bit value internally
305277
int16_t *echo_buffer = (int16_t *)self->echo_buffer;
306-
uint32_t echo_buf_len = self->echo_buffer_len / sizeof(uint16_t);
307-
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-
}
316278

317279
// Loop over the entire length of our buffer to fill it, this may require several calls to get data from the sample
318280
while (length != 0) {
@@ -334,9 +296,38 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t *
334296
}
335297
}
336298

299+
// Determine how many bytes we can process to our buffer, the less of the sample we have left and our buffer remaining
300+
uint32_t n;
301+
if (self->sample == NULL) {
302+
n = MIN(length, SYNTHIO_MAX_DUR * self->channel_count);
303+
} else {
304+
n = MIN(MIN(self->sample_buffer_length, length), SYNTHIO_MAX_DUR * self->channel_count);
305+
}
306+
307+
// get the effect values we need from the BlockInput. These may change at run time so you need to do bounds checking if required
308+
shared_bindings_synthio_lfo_tick(self->sample_rate, n / self->channel_count);
309+
mp_float_t mix = synthio_block_slot_get_limited(&self->mix, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.0));
310+
mp_float_t decay = synthio_block_slot_get_limited(&self->decay, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.0));
311+
312+
mp_float_t f_delay_ms = synthio_block_slot_get(&self->delay_ms);
313+
if (MICROPY_FLOAT_C_FUN(fabs)(self->current_delay_ms - f_delay_ms) >= self->sample_ms) {
314+
recalculate_delay(self, f_delay_ms);
315+
}
316+
317+
uint32_t echo_buf_len = self->echo_buffer_len / sizeof(uint16_t);
318+
319+
// Set our echo buffer position accounting for stereo
320+
uint32_t echo_buffer_pos = 0;
321+
if (self->freq_shift) {
322+
echo_buffer_pos = self->echo_buffer_left_pos;
323+
if (channel == 1) {
324+
echo_buffer_pos = self->echo_buffer_right_pos;
325+
}
326+
}
327+
337328
// If we have no sample keep the echo echoing
338329
if (self->sample == NULL) {
339-
if (mix <= 0.01) { // Mix of 0 is pure sample sound. We have no sample so no sound
330+
if (mix <= MICROPY_FLOAT_CONST(0.01)) { // Mix of 0 is pure sample sound. We have no sample so no sound
340331
if (self->samples_signed) {
341332
memset(word_buffer, 0, length * (self->bits_per_sample / 8));
342333
} else {
@@ -400,13 +391,10 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t *
400391
length = 0;
401392
} else {
402393
// we have a sample to play and echo
403-
// Determine how many bytes we can process to our buffer, the less of the sample we have left and our buffer remaining
404-
uint32_t n = MIN(self->sample_buffer_length, length);
405-
406394
int16_t *sample_src = (int16_t *)self->sample_remaining_buffer; // for 16-bit samples
407395
int8_t *sample_hsrc = (int8_t *)self->sample_remaining_buffer; // for 8-bit samples
408396

409-
if (mix <= 0.01) { // if mix is zero pure sample only
397+
if (mix <= MICROPY_FLOAT_CONST(0.01)) { // if mix is zero pure sample only
410398
for (uint32_t i = 0; i < n; i++) {
411399
if (MP_LIKELY(self->bits_per_sample == 16)) {
412400
word_buffer[i] = sample_src[i];
@@ -440,7 +428,7 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t *
440428
}
441429

442430
if (MP_LIKELY(self->bits_per_sample == 16)) {
443-
word = mix_down_sample(word);
431+
word = synthio_mix_down_sample(word, SYNTHIO_MIX_DOWN_SCALE(2));
444432
if (self->freq_shift) {
445433
for (uint32_t j = echo_buffer_pos >> 8; j < next_buffer_pos >> 8; j++) {
446434
echo_buffer[j % echo_buf_len] = (int16_t)word;
@@ -465,14 +453,15 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t *
465453
}
466454

467455
word = echo + sample_word;
456+
word = synthio_mix_down_sample(word, SYNTHIO_MIX_DOWN_SCALE(2));
468457

469458
if (MP_LIKELY(self->bits_per_sample == 16)) {
470-
word_buffer[i] = (int16_t)((sample_word * (1.0 - mix)) + (word * mix));
459+
word_buffer[i] = (int16_t)((sample_word * (MICROPY_FLOAT_CONST(1.0) - mix)) + (word * mix));
471460
if (!self->samples_signed) {
472461
word_buffer[i] ^= 0x8000;
473462
}
474463
} else {
475-
int8_t mixed = (int16_t)((sample_word * (1.0 - mix)) + (word * mix));
464+
int8_t mixed = (int16_t)((sample_word * (MICROPY_FLOAT_CONST(1.0) - mix)) + (word * mix));
476465
if (self->samples_signed) {
477466
hword_buffer[i] = mixed;
478467
} else {
@@ -500,13 +489,13 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t *
500489
self->sample_remaining_buffer += (n * (self->bits_per_sample / 8));
501490
self->sample_buffer_length -= n;
502491
}
503-
}
504492

505-
if (self->freq_shift) {
506-
if (channel == 0) {
507-
self->echo_buffer_left_pos = echo_buffer_pos;
508-
} else if (channel == 1) {
509-
self->echo_buffer_right_pos = echo_buffer_pos;
493+
if (self->freq_shift) {
494+
if (channel == 0) {
495+
self->echo_buffer_left_pos = echo_buffer_pos;
496+
} else if (channel == 1) {
497+
self->echo_buffer_right_pos = echo_buffer_pos;
498+
}
510499
}
511500
}
512501

shared-module/audiodelays/Echo.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "py/obj.h"
99

1010
#include "shared-module/audiocore/__init__.h"
11+
#include "shared-module/synthio/__init__.h"
1112
#include "shared-module/synthio/block.h"
1213

1314
extern const mp_obj_type_t audiodelays_echo_type;
@@ -16,7 +17,8 @@ typedef struct {
1617
mp_obj_base_t base;
1718
uint32_t max_delay_ms;
1819
synthio_block_slot_t delay_ms;
19-
uint32_t current_delay_ms;
20+
mp_float_t current_delay_ms;
21+
mp_float_t sample_ms;
2022
synthio_block_slot_t decay;
2123
synthio_block_slot_t mix;
2224

0 commit comments

Comments
 (0)
0