From ff0c7384f4be8f1dbfa95b58730d6ecd8ff36549 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Sat, 8 Feb 2025 11:45:22 -0600 Subject: [PATCH 01/10] Initial commit --- py/circuitpy_defns.mk | 1 + shared-bindings/audiodelays/Chorus.c | 260 ++++++++++++++++++ shared-bindings/audiodelays/Chorus.h | 33 +++ shared-bindings/audiodelays/__init__.c | 2 + shared-module/audiodelays/Chorus.c | 367 +++++++++++++++++++++++++ shared-module/audiodelays/Chorus.h | 61 ++++ 6 files changed, 724 insertions(+) create mode 100644 shared-bindings/audiodelays/Chorus.c create mode 100644 shared-bindings/audiodelays/Chorus.h create mode 100644 shared-module/audiodelays/Chorus.c create mode 100644 shared-module/audiodelays/Chorus.h diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index f4335f4b2ae5e..f3f4b904d6b98 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -624,6 +624,7 @@ SRC_SHARED_MODULE_ALL = \ audiocore/WaveFile.c \ audiocore/__init__.c \ audiodelays/Echo.c \ + audiodelays/Chorus.c \ audiodelays/__init__.c \ audiofilters/Distortion.c \ audiofilters/Filter.c \ diff --git a/shared-bindings/audiodelays/Chorus.c b/shared-bindings/audiodelays/Chorus.c new file mode 100644 index 0000000000000..04c674182468d --- /dev/null +++ b/shared-bindings/audiodelays/Chorus.c @@ -0,0 +1,260 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Mark Komus +// +// SPDX-License-Identifier: MIT + +#include + +#include "shared-bindings/audiodelays/Chorus.h" +#include "shared-module/audiodelays/Chorus.h" + +#include "shared/runtime/context_manager_helpers.h" +#include "py/binary.h" +#include "py/objproperty.h" +#include "py/runtime.h" +#include "shared-bindings/util.h" +#include "shared-module/synthio/block.h" + +//| class Chorus: +//| """An Chorus effect""" +//| +//| def __init__( +//| self, +//| max_delay_ms: int = 50, +//| delay_ms: BlockInput = 50.0, +//| voices: synthio.BlockInput = 1.0, +//| buffer_size: int = 512, +//| sample_rate: int = 8000, +//| bits_per_sample: int = 16, +//| samples_signed: bool = True, +//| channel_count: int = 1, +//| ) -> None: +//| """Create a Chorus effect by playing the current sample along with one or more samples +//| (the voices) from the delay buffer. The voices played are evenly spaced across the delay +//| buffer. So for 2 voices you would hear the current sample and the one delay milliseconds back. +//| The delay timing of the chorus can be changed at runtime with the delay_ms parameter but the delay +//| can never exceed the max_delay_ms parameter. The maximum delay is 100ms. +//| +//| :param int max_delay_ms: The maximum time the chorus can be in milliseconds +//| :param synthio.BlockInput delay_ms: The current time of the chorus delay in milliseconds. Must be less the max_delay_ms. +//| :param synthio.BlockInput voices: The number of voices playing split evenly over the delay buffer. +//| :param int buffer_size: The total size in bytes of each of the two playback buffers to use +//| :param int sample_rate: The sample rate to be used +//| :param int channel_count: The number of channels the source samples contain. 1 = mono; 2 = stereo. +//| :param int bits_per_sample: The bits per sample of the effect +//| :param bool samples_signed: Effect is signed (True) or unsigned (False) +//| +//| Playing adding an chorus to a synth:: +//| +//| import time +//| import board +//| import audiobusio +//| import synthio +//| import audiodelays +//| +//| audio = audiobusio.I2SOut(bit_clock=board.GP20, word_select=board.GP21, data=board.GP22) +//| synth = synthio.Synthesizer(channel_count=1, sample_rate=44100) +//| chorus = audiodelays.Chorus(max_delay_ms=50, delay_ms=5, buffer_size=1024, channel_count=1, sample_rate=44100) +//| chorus.play(synth) +//| audio.play(chorus) +//| +//| note = synthio.Note(261) +//| while True: +//| synth.press(note) +//| time.sleep(0.25) +//| synth.release(note) +//| time.sleep(5)""" +//| ... +//| +static mp_obj_t audiodelays_chorus_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_max_delay_ms, ARG_delay_ms, ARG_voices, ARG_buffer_size, ARG_sample_rate, ARG_bits_per_sample, ARG_samples_signed, ARG_channel_count, }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_max_delay_ms, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 50 } }, + { MP_QSTR_delay_ms, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_voices, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_buffer_size, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 512} }, + { MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 8000} }, + { MP_QSTR_bits_per_sample, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 16} }, + { MP_QSTR_samples_signed, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = true} }, + { MP_QSTR_channel_count, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 1 } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_int_t max_delay_ms = mp_arg_validate_int_range(args[ARG_max_delay_ms].u_int, 1, 100, MP_QSTR_max_delay_ms); + + mp_int_t channel_count = mp_arg_validate_int_range(args[ARG_channel_count].u_int, 1, 2, MP_QSTR_channel_count); + mp_int_t sample_rate = mp_arg_validate_int_min(args[ARG_sample_rate].u_int, 1, MP_QSTR_sample_rate); + mp_int_t bits_per_sample = args[ARG_bits_per_sample].u_int; + if (bits_per_sample != 8 && bits_per_sample != 16) { + mp_raise_ValueError(MP_ERROR_TEXT("bits_per_sample must be 8 or 16")); + } + + audiodelays_chorus_obj_t *self = mp_obj_malloc(audiodelays_chorus_obj_t, &audiodelays_chorus_type); + common_hal_audiodelays_chorus_construct(self, max_delay_ms, args[ARG_delay_ms].u_obj, args[ARG_voices].u_obj, args[ARG_buffer_size].u_int, bits_per_sample, args[ARG_samples_signed].u_bool, channel_count, sample_rate); + + return MP_OBJ_FROM_PTR(self); +} + +//| def deinit(self) -> None: +//| """Deinitialises the Chorus.""" +//| ... +//| +static mp_obj_t audiodelays_chorus_deinit(mp_obj_t self_in) { + audiodelays_chorus_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_audiodelays_chorus_deinit(self); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_chorus_deinit_obj, audiodelays_chorus_deinit); + +static void check_for_deinit(audiodelays_chorus_obj_t *self) { + if (common_hal_audiodelays_chorus_deinited(self)) { + raise_deinited_error(); + } +} + +//| def __enter__(self) -> Chorus: +//| """No-op used by Context Managers.""" +//| ... +//| +// Provided by context manager helper. + +//| def __exit__(self) -> None: +//| """Automatically deinitializes when exiting a context. See +//| :ref:`lifetime-and-contextmanagers` for more info.""" +//| ... +//| +static mp_obj_t audiodelays_chorus_obj___exit__(size_t n_args, const mp_obj_t *args) { + (void)n_args; + common_hal_audiodelays_chorus_deinit(args[0]); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audiodelays_chorus___exit___obj, 4, 4, audiodelays_chorus_obj___exit__); + + +//| delay_ms: synthio.BlockInput +//| """The current time of the chorus delay in milliseconds. Must be less the max_delay_ms.""" +//| +static mp_obj_t audiodelays_chorus_obj_get_delay_ms(mp_obj_t self_in) { + audiodelays_chorus_obj_t *self = MP_OBJ_TO_PTR(self_in); + + return common_hal_audiodelays_chorus_get_delay_ms(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_chorus_get_delay_ms_obj, audiodelays_chorus_obj_get_delay_ms); + +static mp_obj_t audiodelays_chorus_obj_set_delay_ms(mp_obj_t self_in, mp_obj_t delay_ms_in) { + audiodelays_chorus_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_audiodelays_chorus_set_delay_ms(self, delay_ms_in); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(audiodelays_chorus_set_delay_ms_obj, audiodelays_chorus_obj_set_delay_ms); + +MP_PROPERTY_GETSET(audiodelays_chorus_delay_ms_obj, + (mp_obj_t)&audiodelays_chorus_get_delay_ms_obj, + (mp_obj_t)&audiodelays_chorus_set_delay_ms_obj); + +//| voices: synthio.BlockInput +//| """The number of voices playing split evenly over the delay buffer.""" +static mp_obj_t audiodelays_chorus_obj_get_voices(mp_obj_t self_in) { + return common_hal_audiodelays_chorus_get_voices(self_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_chorus_get_voices_obj, audiodelays_chorus_obj_get_voices); + +static mp_obj_t audiodelays_chorus_obj_set_voices(mp_obj_t self_in, mp_obj_t voices_in) { + audiodelays_chorus_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_audiodelays_chorus_set_voices(self, voices_in); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(audiodelays_chorus_set_voices_obj, audiodelays_chorus_obj_set_voices); + +MP_PROPERTY_GETSET(audiodelays_chorus_voices_obj, + (mp_obj_t)&audiodelays_chorus_get_voices_obj, + (mp_obj_t)&audiodelays_chorus_set_voices_obj); + +//| playing: bool +//| """True when the effect is playing a sample. (read-only)""" +//| +static mp_obj_t audiodelays_chorus_obj_get_playing(mp_obj_t self_in) { + audiodelays_chorus_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + return mp_obj_new_bool(common_hal_audiodelays_chorus_get_playing(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_chorus_get_playing_obj, audiodelays_chorus_obj_get_playing); + +MP_PROPERTY_GETTER(audiodelays_chorus_playing_obj, + (mp_obj_t)&audiodelays_chorus_get_playing_obj); + +//| def play(self, sample: circuitpython_typing.AudioSample, *, loop: bool = False) -> None: +//| """Plays the sample once when loop=False and continuously when loop=True. +//| Does not block. Use `playing` to block. +//| +//| The sample must match the encoding settings given in the constructor.""" +//| ... +//| +static mp_obj_t audiodelays_chorus_obj_play(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_sample, ARG_loop }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_sample, MP_ARG_OBJ | MP_ARG_REQUIRED, {} }, + { MP_QSTR_loop, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = false} }, + }; + audiodelays_chorus_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + check_for_deinit(self); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + + mp_obj_t sample = args[ARG_sample].u_obj; + common_hal_audiodelays_chorus_play(self, sample, args[ARG_loop].u_bool); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(audiodelays_chorus_play_obj, 1, audiodelays_chorus_obj_play); + +//| def stop(self) -> None: +//| """Stops playback of the sample.""" +//| ... +//| +//| +static mp_obj_t audiodelays_chorus_obj_stop(mp_obj_t self_in) { + audiodelays_chorus_obj_t *self = MP_OBJ_TO_PTR(self_in); + + common_hal_audiodelays_chorus_stop(self); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_chorus_stop_obj, audiodelays_chorus_obj_stop); + +static const mp_rom_map_elem_t audiodelays_chorus_locals_dict_table[] = { + // Methods + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiodelays_chorus_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, + { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&audiodelays_chorus___exit___obj) }, + { MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audiodelays_chorus_play_obj) }, + { MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&audiodelays_chorus_stop_obj) }, + + // Properties + { MP_ROM_QSTR(MP_QSTR_playing), MP_ROM_PTR(&audiodelays_chorus_playing_obj) }, + { MP_ROM_QSTR(MP_QSTR_delay_ms), MP_ROM_PTR(&audiodelays_chorus_delay_ms_obj) }, + { MP_ROM_QSTR(MP_QSTR_voices), MP_ROM_PTR(&audiodelays_chorus_voices_obj) }, +}; +static MP_DEFINE_CONST_DICT(audiodelays_chorus_locals_dict, audiodelays_chorus_locals_dict_table); + +static const audiosample_p_t audiodelays_chorus_proto = { + MP_PROTO_IMPLEMENT(MP_QSTR_protocol_audiosample) + .sample_rate = (audiosample_sample_rate_fun)common_hal_audiodelays_chorus_get_sample_rate, + .bits_per_sample = (audiosample_bits_per_sample_fun)common_hal_audiodelays_chorus_get_bits_per_sample, + .channel_count = (audiosample_channel_count_fun)common_hal_audiodelays_chorus_get_channel_count, + .reset_buffer = (audiosample_reset_buffer_fun)audiodelays_chorus_reset_buffer, + .get_buffer = (audiosample_get_buffer_fun)audiodelays_chorus_get_buffer, + .get_buffer_structure = (audiosample_get_buffer_structure_fun)audiodelays_chorus_get_buffer_structure, +}; + +MP_DEFINE_CONST_OBJ_TYPE( + audiodelays_chorus_type, + MP_QSTR_Chorus, + MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS, + make_new, audiodelays_chorus_make_new, + locals_dict, &audiodelays_chorus_locals_dict, + protocol, &audiodelays_chorus_proto + ); diff --git a/shared-bindings/audiodelays/Chorus.h b/shared-bindings/audiodelays/Chorus.h new file mode 100644 index 0000000000000..489ae644d7e14 --- /dev/null +++ b/shared-bindings/audiodelays/Chorus.h @@ -0,0 +1,33 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Mark Komus +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "shared-module/audiodelays/Chorus.h" + +extern const mp_obj_type_t audiodelays_chorus_type; + +void common_hal_audiodelays_chorus_construct(audiodelays_chorus_obj_t *self, uint32_t max_delay_ms, + mp_obj_t delay_ms, mp_obj_t voices, + uint32_t buffer_size, uint8_t bits_per_sample, + bool samples_signed, uint8_t channel_count, uint32_t sample_rate); + +void common_hal_audiodelays_chorus_deinit(audiodelays_chorus_obj_t *self); +bool common_hal_audiodelays_chorus_deinited(audiodelays_chorus_obj_t *self); + +uint32_t common_hal_audiodelays_chorus_get_sample_rate(audiodelays_chorus_obj_t *self); +uint8_t common_hal_audiodelays_chorus_get_channel_count(audiodelays_chorus_obj_t *self); +uint8_t common_hal_audiodelays_chorus_get_bits_per_sample(audiodelays_chorus_obj_t *self); + +mp_obj_t common_hal_audiodelays_chorus_get_delay_ms(audiodelays_chorus_obj_t *self); +void common_hal_audiodelays_chorus_set_delay_ms(audiodelays_chorus_obj_t *self, mp_obj_t delay_ms); + +mp_obj_t common_hal_audiodelays_chorus_get_voices(audiodelays_chorus_obj_t *self); +void common_hal_audiodelays_chorus_set_voices(audiodelays_chorus_obj_t *self, mp_obj_t voices); + +bool common_hal_audiodelays_chorus_get_playing(audiodelays_chorus_obj_t *self); +void common_hal_audiodelays_chorus_play(audiodelays_chorus_obj_t *self, mp_obj_t sample, bool loop); +void common_hal_audiodelays_chorus_stop(audiodelays_chorus_obj_t *self); diff --git a/shared-bindings/audiodelays/__init__.c b/shared-bindings/audiodelays/__init__.c index 4b0be7b75ad86..f6962fc4b6e93 100644 --- a/shared-bindings/audiodelays/__init__.c +++ b/shared-bindings/audiodelays/__init__.c @@ -11,6 +11,7 @@ #include "shared-bindings/audiodelays/__init__.h" #include "shared-bindings/audiodelays/Echo.h" +#include "shared-bindings/audiodelays/Chorus.h" //| """Support for audio delay effects //| @@ -21,6 +22,7 @@ static const mp_rom_map_elem_t audiodelays_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audiodelays) }, { MP_ROM_QSTR(MP_QSTR_Echo), MP_ROM_PTR(&audiodelays_echo_type) }, + { MP_ROM_QSTR(MP_QSTR_Chorus), MP_ROM_PTR(&audiodelays_chorus_type) }, }; static MP_DEFINE_CONST_DICT(audiodelays_module_globals, audiodelays_module_globals_table); diff --git a/shared-module/audiodelays/Chorus.c b/shared-module/audiodelays/Chorus.c new file mode 100644 index 0000000000000..9380c2694acfc --- /dev/null +++ b/shared-module/audiodelays/Chorus.c @@ -0,0 +1,367 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Mark Komus +// +// SPDX-License-Identifier: MIT +#include "shared-bindings/audiodelays/Chorus.h" + +#include +#include +#include "py/runtime.h" + +void common_hal_audiodelays_chorus_construct(audiodelays_chorus_obj_t *self, uint32_t max_delay_ms, + mp_obj_t delay_ms, mp_obj_t voices, + uint32_t buffer_size, uint8_t bits_per_sample, + bool samples_signed, uint8_t channel_count, uint32_t sample_rate) { + + // Basic settings every effect and audio sample has + // These are the effects values, not the source sample(s) + self->bits_per_sample = bits_per_sample; // Most common is 16, but 8 is also supported in many places + self->samples_signed = samples_signed; // Are the samples we provide signed (common is true) + self->channel_count = channel_count; // Channels can be 1 for mono or 2 for stereo + self->sample_rate = sample_rate; // Sample rate for the effect, this generally needs to match all audio objects + + // To smooth things out as CircuitPython is doing other tasks most audio objects have a buffer + // A double buffer is set up here so the audio output can use DMA on buffer 1 while we + // write to and create buffer 2. + // This buffer is what is passed to the audio component that plays the effect. + // Samples are set sequentially. For stereo audio they are passed L/R/L/R/... + self->buffer_len = buffer_size; // in bytes + + self->buffer[0] = m_malloc(self->buffer_len); + if (self->buffer[0] == NULL) { + common_hal_audiodelays_chorus_deinit(self); + m_malloc_fail(self->buffer_len); + } + memset(self->buffer[0], 0, self->buffer_len); + + self->buffer[1] = m_malloc(self->buffer_len); + if (self->buffer[1] == NULL) { + common_hal_audiodelays_chorus_deinit(self); + m_malloc_fail(self->buffer_len); + } + memset(self->buffer[1], 0, self->buffer_len); + + self->last_buf_idx = 1; // Which buffer to use first, toggle between 0 and 1 + + // Initialize other values most effects will need. + self->sample = NULL; // The current playing sample + self->sample_remaining_buffer = NULL; // Pointer to the start of the sample buffer we have not played + self->sample_buffer_length = 0; // How many samples do we have left to play (these may be 16 bit!) + self->loop = false; // When the sample is done do we loop to the start again or stop (e.g. in a wav file) + self->more_data = false; // Is there still more data to read from the sample or did we finish + + // The below section sets up the chorus effect's starting values. For a different effect this section will change + + // If we did not receive a BlockInput we need to create a default float value + if (voices == MP_OBJ_NULL) { + voices = mp_obj_new_float(MICROPY_FLOAT_CONST(1.0)); + } + synthio_block_assign_slot(voices, &self->voices, MP_QSTR_voices); + + if (delay_ms == MP_OBJ_NULL) { + delay_ms = mp_obj_new_float(MICROPY_FLOAT_CONST(50.0)); + } + synthio_block_assign_slot(delay_ms, &self->delay_ms, MP_QSTR_delay_ms); + + // Many effects may need buffers of what was played this shows how it was done for the chorus + // A maximum length buffer was created and then the current chorus length can be dynamically changes + // without having to reallocate a large chunk of memory. + + // Allocate the chorus buffer for the max possible delay, chorus is always 16-bit + self->max_delay_ms = max_delay_ms; + self->max_chorus_buffer_len = self->sample_rate / MICROPY_FLOAT_CONST(1000.0) * max_delay_ms * (self->channel_count * sizeof(uint16_t)); // bytes + self->chorus_buffer = m_malloc(self->max_chorus_buffer_len); + if (self->chorus_buffer == NULL) { + common_hal_audiodelays_chorus_deinit(self); + m_malloc_fail(self->max_chorus_buffer_len); + } + memset(self->chorus_buffer, 0, self->max_chorus_buffer_len); + + // calculate the length of a single sample in milliseconds + self->sample_ms = MICROPY_FLOAT_CONST(1000.0) / self->sample_rate; + + // calculate everything needed for the current delay + mp_float_t f_delay_ms = synthio_block_slot_get(&self->delay_ms); + chorus_recalculate_delay(self, f_delay_ms); + + // where we are storing the next chorus sample + self->chorus_buffer_pos = 0; +} + +bool common_hal_audiodelays_chorus_deinited(audiodelays_chorus_obj_t *self) { + if (self->chorus_buffer == NULL) { + return true; + } + return false; +} + +void common_hal_audiodelays_chorus_deinit(audiodelays_chorus_obj_t *self) { + if (common_hal_audiodelays_chorus_deinited(self)) { + return; + } + self->chorus_buffer = NULL; + self->buffer[0] = NULL; + self->buffer[1] = NULL; +} + +mp_obj_t common_hal_audiodelays_chorus_get_delay_ms(audiodelays_chorus_obj_t *self) { + return self->delay_ms.obj; +} + +void common_hal_audiodelays_chorus_set_delay_ms(audiodelays_chorus_obj_t *self, mp_obj_t delay_ms) { + synthio_block_assign_slot(delay_ms, &self->delay_ms, MP_QSTR_delay_ms); + + mp_float_t f_delay_ms = synthio_block_slot_get(&self->delay_ms); + + chorus_recalculate_delay(self, f_delay_ms); +} + +void chorus_recalculate_delay(audiodelays_chorus_obj_t *self, mp_float_t f_delay_ms) { + // Require that delay is at least 1 sample long + f_delay_ms = MAX(f_delay_ms, self->sample_ms); + + // Calculate the current chorus buffer length in bytes + uint32_t new_chorus_buffer_len = (uint32_t)(self->sample_rate / MICROPY_FLOAT_CONST(1000.0) * f_delay_ms) * (self->channel_count * sizeof(uint16_t)); + + if (new_chorus_buffer_len < 0) { // or too short! + return; + } + + self->chorus_buffer_len = new_chorus_buffer_len; + + self->current_delay_ms = f_delay_ms; +} + +mp_obj_t common_hal_audiodelays_chorus_get_voices(audiodelays_chorus_obj_t *self) { + return self->voices.obj; +} + +void common_hal_audiodelays_chorus_set_voices(audiodelays_chorus_obj_t *self, mp_obj_t voices) { + synthio_block_assign_slot(voices, &self->voices, MP_QSTR_voices); +} + +uint32_t common_hal_audiodelays_chorus_get_sample_rate(audiodelays_chorus_obj_t *self) { + return self->sample_rate; +} + +uint8_t common_hal_audiodelays_chorus_get_channel_count(audiodelays_chorus_obj_t *self) { + return self->channel_count; +} + +uint8_t common_hal_audiodelays_chorus_get_bits_per_sample(audiodelays_chorus_obj_t *self) { + return self->bits_per_sample; +} + +void audiodelays_chorus_reset_buffer(audiodelays_chorus_obj_t *self, + bool single_channel_output, + uint8_t channel) { + + memset(self->buffer[0], 0, self->buffer_len); + memset(self->buffer[1], 0, self->buffer_len); + memset(self->chorus_buffer, 0, self->chorus_buffer_len); +} + +bool common_hal_audiodelays_chorus_get_playing(audiodelays_chorus_obj_t *self) { + return self->sample != NULL; +} + +void common_hal_audiodelays_chorus_play(audiodelays_chorus_obj_t *self, mp_obj_t sample, bool loop) { + // When a sample is to be played we must ensure the samples values matches what we expect + // Then we reset the sample and get the first buffer to play + // The get_buffer function will actually process that data + + if (audiosample_sample_rate(sample) != self->sample_rate) { + mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_sample_rate); + } + if (audiosample_channel_count(sample) != self->channel_count) { + mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_channel_count); + } + if (audiosample_bits_per_sample(sample) != self->bits_per_sample) { + mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_bits_per_sample); + } + bool single_buffer; + bool samples_signed; + uint32_t max_buffer_length; + uint8_t spacing; + audiosample_get_buffer_structure(sample, false, &single_buffer, &samples_signed, &max_buffer_length, &spacing); + if (samples_signed != self->samples_signed) { + mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_signedness); + } + + self->sample = sample; + self->loop = loop; + + audiosample_reset_buffer(self->sample, false, 0); + audioio_get_buffer_result_t result = audiosample_get_buffer(self->sample, false, 0, (uint8_t **)&self->sample_remaining_buffer, &self->sample_buffer_length); + + // Track remaining sample length in terms of bytes per sample + self->sample_buffer_length /= (self->bits_per_sample / 8); + // Store if we have more data in the sample to retrieve + self->more_data = result == GET_BUFFER_MORE_DATA; + + return; +} + +void common_hal_audiodelays_chorus_stop(audiodelays_chorus_obj_t *self) { + // When the sample is set to stop playing do any cleanup here + // For chorus we clear the sample but the chorus continues until the object reading our effect stops + self->sample = NULL; + return; +} + +audioio_get_buffer_result_t audiodelays_chorus_get_buffer(audiodelays_chorus_obj_t *self, bool single_channel_output, uint8_t channel, + uint8_t **buffer, uint32_t *buffer_length) { + + // Switch our buffers to the other buffer + self->last_buf_idx = !self->last_buf_idx; + + // If we are using 16 bit samples we need a 16 bit pointer, 8 bit needs an 8 bit pointer + int16_t *word_buffer = (int16_t *)self->buffer[self->last_buf_idx]; + int8_t *hword_buffer = self->buffer[self->last_buf_idx]; + uint32_t length = self->buffer_len / (self->bits_per_sample / 8); + + // The chorus buffer is always stored as a 16-bit value internally + int16_t *chorus_buffer = (int16_t *)self->chorus_buffer; + uint32_t chorus_buf_len = self->chorus_buffer_len / sizeof(uint16_t); + + // Loop over the entire length of our buffer to fill it, this may require several calls to get data from the sample + while (length != 0) { + // 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 + if (self->sample_buffer_length == 0) { + if (!self->more_data) { // The sample has indicated it has no more data to play + if (self->loop && self->sample) { // If we are supposed to loop reset the sample to the start + audiosample_reset_buffer(self->sample, false, 0); + } else { // If we were not supposed to loop the sample, stop playing it but we still need to play the chorus + self->sample = NULL; + } + } + if (self->sample) { + // Load another sample buffer to play + audioio_get_buffer_result_t result = audiosample_get_buffer(self->sample, false, 0, (uint8_t **)&self->sample_remaining_buffer, &self->sample_buffer_length); + // Track length in terms of words. + self->sample_buffer_length /= (self->bits_per_sample / 8); + self->more_data = result == GET_BUFFER_MORE_DATA; + } + } + + // Determine how many bytes we can process to our buffer, the less of the sample we have left and our buffer remaining + uint32_t n; + if (self->sample == NULL) { + n = MIN(length, SYNTHIO_MAX_DUR * self->channel_count); + } else { + n = MIN(MIN(self->sample_buffer_length, length), SYNTHIO_MAX_DUR * self->channel_count); + } + + // get the effect values we need from the BlockInput. These may change at run time so you need to do bounds checking if required + shared_bindings_synthio_lfo_tick(self->sample_rate, n / self->channel_count); + + int32_t voices = MAX(synthio_block_slot_get(&self->voices), 1.0); + + mp_float_t f_delay_ms = synthio_block_slot_get(&self->delay_ms); + if (MICROPY_FLOAT_C_FUN(fabs)(self->current_delay_ms - f_delay_ms) >= self->sample_ms) { + chorus_recalculate_delay(self, f_delay_ms); + } + + if (self->sample == NULL) { + if (self->samples_signed) { + memset(word_buffer, 0, n * (self->bits_per_sample / 8)); + } else { + // For unsigned samples set to the middle which is "quiet" + if (MP_LIKELY(self->bits_per_sample == 16)) { + uint16_t *uword_buffer = (uint16_t *)word_buffer; + for (uint32_t i = 0; i < n; i++) { + *uword_buffer++ = 32768; + } + } else { + memset(hword_buffer, 128, n * (self->bits_per_sample / 8)); + } + } + } else { + int16_t *sample_src = (int16_t *)self->sample_remaining_buffer; // for 16-bit samples + int8_t *sample_hsrc = (int8_t *)self->sample_remaining_buffer; // for 8-bit samples + + for (uint32_t i = 0; i < n; i++) { + int32_t sample_word = 0; + if (MP_LIKELY(self->bits_per_sample == 16)) { + sample_word = sample_src[i]; + } else { + if (self->samples_signed) { + sample_word = sample_hsrc[i]; + } else { + // Be careful here changing from an 8 bit unsigned to signed into a 32-bit signed + sample_word = (int8_t)(((uint8_t)sample_hsrc[i]) ^ 0x80); + } + } + + chorus_buffer[self->chorus_buffer_pos++] = (int16_t)sample_word; + + int32_t word = 0; + if (voices == 1) { + word = sample_word; + } else { + int32_t step = chorus_buf_len / (voices - 1) - 1; + int32_t c_pos = self->chorus_buffer_pos - 1; + + for (int32_t v = 0; v < voices; v++) { + word += chorus_buffer[c_pos]; + + c_pos -= step; + if (c_pos < 0) { + c_pos += chorus_buf_len; + } + } + word = word / voices; + + word = synthio_mix_down_sample(word, SYNTHIO_MIX_DOWN_SCALE(2)); + } + + if (MP_LIKELY(self->bits_per_sample == 16)) { + word_buffer[i] = word; + if (!self->samples_signed) { + word_buffer[i] ^= 0x8000; + } + } else { + int8_t out = word; + if (self->samples_signed) { + hword_buffer[i] = out; + } else { + hword_buffer[i] = (uint8_t)out ^ 0x80; + } + } + + if (self->chorus_buffer_pos >= chorus_buf_len) { + self->chorus_buffer_pos = 0; + } + } + self->sample_remaining_buffer += (n * (self->bits_per_sample / 8)); + self->sample_buffer_length -= n; + } + // Update the remaining length and the buffer positions based on how much we wrote into our buffer + length -= n; + word_buffer += n; + hword_buffer += n; + } + + // Finally pass our buffer and length to the calling audio function + *buffer = (uint8_t *)self->buffer[self->last_buf_idx]; + *buffer_length = self->buffer_len; + + // Chorus always returns more data but some effects may return GET_BUFFER_DONE or GET_BUFFER_ERROR (see audiocore/__init__.h) + return GET_BUFFER_MORE_DATA; +} + +void audiodelays_chorus_get_buffer_structure(audiodelays_chorus_obj_t *self, bool single_channel_output, + bool *single_buffer, bool *samples_signed, uint32_t *max_buffer_length, uint8_t *spacing) { + + // Return information about the effect's buffer (not the sample's) + // These are used by calling audio objects to determine how to handle the effect's buffer + *single_buffer = false; + *samples_signed = self->samples_signed; + *max_buffer_length = self->buffer_len; + if (single_channel_output) { + *spacing = self->channel_count; + } else { + *spacing = 1; + } +} diff --git a/shared-module/audiodelays/Chorus.h b/shared-module/audiodelays/Chorus.h new file mode 100644 index 0000000000000..a5ead0922d7d5 --- /dev/null +++ b/shared-module/audiodelays/Chorus.h @@ -0,0 +1,61 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Mark Komus +// +// SPDX-License-Identifier: MIT +#pragma once + +#include "py/obj.h" + +#include "shared-module/audiocore/__init__.h" +#include "shared-module/synthio/block.h" + +extern const mp_obj_type_t audiodelays_chorus_type; + +typedef struct { + mp_obj_base_t base; + uint32_t max_delay_ms; + synthio_block_slot_t delay_ms; + mp_float_t current_delay_ms; + mp_float_t sample_ms; + synthio_block_slot_t voices; + + uint8_t bits_per_sample; + bool samples_signed; + uint8_t channel_count; + uint32_t sample_rate; + + int8_t *buffer[2]; + uint8_t last_buf_idx; + uint32_t buffer_len; // max buffer in bytes + + uint8_t *sample_remaining_buffer; + uint32_t sample_buffer_length; + + bool loop; + bool more_data; + + int8_t *chorus_buffer; + uint32_t chorus_buffer_len; // bytes + uint32_t max_chorus_buffer_len; // bytes + + uint32_t chorus_buffer_pos; // words + + mp_obj_t sample; +} audiodelays_chorus_obj_t; + +void chorus_recalculate_delay(audiodelays_chorus_obj_t *self, mp_float_t f_delay_ms); + +void audiodelays_chorus_reset_buffer(audiodelays_chorus_obj_t *self, + bool single_channel_output, + uint8_t channel); + +audioio_get_buffer_result_t audiodelays_chorus_get_buffer(audiodelays_chorus_obj_t *self, + bool single_channel_output, + uint8_t channel, + uint8_t **buffer, + uint32_t *buffer_length); // length in bytes + +void audiodelays_chorus_get_buffer_structure(audiodelays_chorus_obj_t *self, bool single_channel_output, + bool *single_buffer, bool *samples_signed, + uint32_t *max_buffer_length, uint8_t *spacing); From 23a4df4eff8fef01f4ba7af1d4378c9916dacd00 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Sun, 9 Feb 2025 09:23:39 -0600 Subject: [PATCH 02/10] Increase max_delay limit and a less then zero bug --- shared-bindings/audiodelays/Chorus.c | 6 +++--- shared-module/audiodelays/Chorus.c | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/shared-bindings/audiodelays/Chorus.c b/shared-bindings/audiodelays/Chorus.c index 04c674182468d..5fa1357c1ceea 100644 --- a/shared-bindings/audiodelays/Chorus.c +++ b/shared-bindings/audiodelays/Chorus.c @@ -34,8 +34,8 @@ //| (the voices) from the delay buffer. The voices played are evenly spaced across the delay //| buffer. So for 2 voices you would hear the current sample and the one delay milliseconds back. //| The delay timing of the chorus can be changed at runtime with the delay_ms parameter but the delay -//| can never exceed the max_delay_ms parameter. The maximum delay is 100ms. -//| +//| can never exceed the max_delay_ms parameter. The maximum delay you can set is limited by available +//| memory. //| :param int max_delay_ms: The maximum time the chorus can be in milliseconds //| :param synthio.BlockInput delay_ms: The current time of the chorus delay in milliseconds. Must be less the max_delay_ms. //| :param synthio.BlockInput voices: The number of voices playing split evenly over the delay buffer. @@ -83,7 +83,7 @@ static mp_obj_t audiodelays_chorus_make_new(const mp_obj_type_t *type, size_t n_ mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - mp_int_t max_delay_ms = mp_arg_validate_int_range(args[ARG_max_delay_ms].u_int, 1, 100, MP_QSTR_max_delay_ms); + mp_int_t max_delay_ms = mp_arg_validate_int_range(args[ARG_max_delay_ms].u_int, 1, 4000, MP_QSTR_max_delay_ms); mp_int_t channel_count = mp_arg_validate_int_range(args[ARG_channel_count].u_int, 1, 2, MP_QSTR_channel_count); mp_int_t sample_rate = mp_arg_validate_int_min(args[ARG_sample_rate].u_int, 1, MP_QSTR_sample_rate); diff --git a/shared-module/audiodelays/Chorus.c b/shared-module/audiodelays/Chorus.c index 9380c2694acfc..ae8a17bf16b5c 100644 --- a/shared-module/audiodelays/Chorus.c +++ b/shared-module/audiodelays/Chorus.c @@ -304,12 +304,12 @@ audioio_get_buffer_result_t audiodelays_chorus_get_buffer(audiodelays_chorus_obj int32_t c_pos = self->chorus_buffer_pos - 1; for (int32_t v = 0; v < voices; v++) { - word += chorus_buffer[c_pos]; - - c_pos -= step; if (c_pos < 0) { c_pos += chorus_buf_len; } + word += chorus_buffer[c_pos]; + + c_pos -= step; } word = word / voices; From 2455002b94708c6968e259e1db9fae783756fcb4 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Sun, 9 Feb 2025 09:46:24 -0600 Subject: [PATCH 03/10] Changes for PR #10036 --- shared-bindings/audiodelays/Chorus.c | 10 +-- shared-module/audiodelays/Chorus.c | 97 ++++++++-------------------- shared-module/audiodelays/Chorus.h | 11 +--- 3 files changed, 30 insertions(+), 88 deletions(-) diff --git a/shared-bindings/audiodelays/Chorus.c b/shared-bindings/audiodelays/Chorus.c index 5fa1357c1ceea..0721513018da9 100644 --- a/shared-bindings/audiodelays/Chorus.c +++ b/shared-bindings/audiodelays/Chorus.c @@ -8,6 +8,7 @@ #include "shared-bindings/audiodelays/Chorus.h" #include "shared-module/audiodelays/Chorus.h" +#include "shared-bindings/audiocore/__init__.h" #include "shared/runtime/context_manager_helpers.h" #include "py/binary.h" @@ -110,9 +111,7 @@ static mp_obj_t audiodelays_chorus_deinit(mp_obj_t self_in) { static MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_chorus_deinit_obj, audiodelays_chorus_deinit); static void check_for_deinit(audiodelays_chorus_obj_t *self) { - if (common_hal_audiodelays_chorus_deinited(self)) { - raise_deinited_error(); - } + audiosample_check_for_deinit(&self->base); } //| def __enter__(self) -> Chorus: @@ -237,17 +236,14 @@ static const mp_rom_map_elem_t audiodelays_chorus_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_playing), MP_ROM_PTR(&audiodelays_chorus_playing_obj) }, { MP_ROM_QSTR(MP_QSTR_delay_ms), MP_ROM_PTR(&audiodelays_chorus_delay_ms_obj) }, { MP_ROM_QSTR(MP_QSTR_voices), MP_ROM_PTR(&audiodelays_chorus_voices_obj) }, + AUDIOSAMPLE_FIELDS, }; static MP_DEFINE_CONST_DICT(audiodelays_chorus_locals_dict, audiodelays_chorus_locals_dict_table); static const audiosample_p_t audiodelays_chorus_proto = { MP_PROTO_IMPLEMENT(MP_QSTR_protocol_audiosample) - .sample_rate = (audiosample_sample_rate_fun)common_hal_audiodelays_chorus_get_sample_rate, - .bits_per_sample = (audiosample_bits_per_sample_fun)common_hal_audiodelays_chorus_get_bits_per_sample, - .channel_count = (audiosample_channel_count_fun)common_hal_audiodelays_chorus_get_channel_count, .reset_buffer = (audiosample_reset_buffer_fun)audiodelays_chorus_reset_buffer, .get_buffer = (audiosample_get_buffer_fun)audiodelays_chorus_get_buffer, - .get_buffer_structure = (audiosample_get_buffer_structure_fun)audiodelays_chorus_get_buffer_structure, }; MP_DEFINE_CONST_OBJ_TYPE( diff --git a/shared-module/audiodelays/Chorus.c b/shared-module/audiodelays/Chorus.c index ae8a17bf16b5c..269981d18863f 100644 --- a/shared-module/audiodelays/Chorus.c +++ b/shared-module/audiodelays/Chorus.c @@ -16,10 +16,12 @@ void common_hal_audiodelays_chorus_construct(audiodelays_chorus_obj_t *self, uin // Basic settings every effect and audio sample has // These are the effects values, not the source sample(s) - self->bits_per_sample = bits_per_sample; // Most common is 16, but 8 is also supported in many places - self->samples_signed = samples_signed; // Are the samples we provide signed (common is true) - self->channel_count = channel_count; // Channels can be 1 for mono or 2 for stereo - self->sample_rate = sample_rate; // Sample rate for the effect, this generally needs to match all audio objects + self->base.bits_per_sample = bits_per_sample; // Most common is 16, but 8 is also supported in many places + self->base.samples_signed = samples_signed; // Are the samples we provide signed (common is true) + self->base.channel_count = channel_count; // Channels can be 1 for mono or 2 for stereo + self->base.sample_rate = sample_rate; // Sample rate for the effect, this generally needs to match all audio objects + self->base.single_buffer = false; + self->base.max_buffer_length = buffer_size; // To smooth things out as CircuitPython is doing other tasks most audio objects have a buffer // A double buffer is set up here so the audio output can use DMA on buffer 1 while we @@ -70,7 +72,7 @@ void common_hal_audiodelays_chorus_construct(audiodelays_chorus_obj_t *self, uin // Allocate the chorus buffer for the max possible delay, chorus is always 16-bit self->max_delay_ms = max_delay_ms; - self->max_chorus_buffer_len = self->sample_rate / MICROPY_FLOAT_CONST(1000.0) * max_delay_ms * (self->channel_count * sizeof(uint16_t)); // bytes + self->max_chorus_buffer_len = self->base.sample_rate / MICROPY_FLOAT_CONST(1000.0) * max_delay_ms * (self->base.channel_count * sizeof(uint16_t)); // bytes self->chorus_buffer = m_malloc(self->max_chorus_buffer_len); if (self->chorus_buffer == NULL) { common_hal_audiodelays_chorus_deinit(self); @@ -79,7 +81,7 @@ void common_hal_audiodelays_chorus_construct(audiodelays_chorus_obj_t *self, uin memset(self->chorus_buffer, 0, self->max_chorus_buffer_len); // calculate the length of a single sample in milliseconds - self->sample_ms = MICROPY_FLOAT_CONST(1000.0) / self->sample_rate; + self->sample_ms = MICROPY_FLOAT_CONST(1000.0) / self->base.sample_rate; // calculate everything needed for the current delay mp_float_t f_delay_ms = synthio_block_slot_get(&self->delay_ms); @@ -122,7 +124,7 @@ void chorus_recalculate_delay(audiodelays_chorus_obj_t *self, mp_float_t f_delay f_delay_ms = MAX(f_delay_ms, self->sample_ms); // Calculate the current chorus buffer length in bytes - uint32_t new_chorus_buffer_len = (uint32_t)(self->sample_rate / MICROPY_FLOAT_CONST(1000.0) * f_delay_ms) * (self->channel_count * sizeof(uint16_t)); + uint32_t new_chorus_buffer_len = (uint32_t)(self->base.sample_rate / MICROPY_FLOAT_CONST(1000.0) * f_delay_ms) * (self->base.channel_count * sizeof(uint16_t)); if (new_chorus_buffer_len < 0) { // or too short! return; @@ -141,18 +143,6 @@ void common_hal_audiodelays_chorus_set_voices(audiodelays_chorus_obj_t *self, mp synthio_block_assign_slot(voices, &self->voices, MP_QSTR_voices); } -uint32_t common_hal_audiodelays_chorus_get_sample_rate(audiodelays_chorus_obj_t *self) { - return self->sample_rate; -} - -uint8_t common_hal_audiodelays_chorus_get_channel_count(audiodelays_chorus_obj_t *self) { - return self->channel_count; -} - -uint8_t common_hal_audiodelays_chorus_get_bits_per_sample(audiodelays_chorus_obj_t *self) { - return self->bits_per_sample; -} - void audiodelays_chorus_reset_buffer(audiodelays_chorus_obj_t *self, bool single_channel_output, uint8_t channel) { @@ -167,27 +157,7 @@ bool common_hal_audiodelays_chorus_get_playing(audiodelays_chorus_obj_t *self) { } void common_hal_audiodelays_chorus_play(audiodelays_chorus_obj_t *self, mp_obj_t sample, bool loop) { - // When a sample is to be played we must ensure the samples values matches what we expect - // Then we reset the sample and get the first buffer to play - // The get_buffer function will actually process that data - - if (audiosample_sample_rate(sample) != self->sample_rate) { - mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_sample_rate); - } - if (audiosample_channel_count(sample) != self->channel_count) { - mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_channel_count); - } - if (audiosample_bits_per_sample(sample) != self->bits_per_sample) { - mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_bits_per_sample); - } - bool single_buffer; - bool samples_signed; - uint32_t max_buffer_length; - uint8_t spacing; - audiosample_get_buffer_structure(sample, false, &single_buffer, &samples_signed, &max_buffer_length, &spacing); - if (samples_signed != self->samples_signed) { - mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_signedness); - } + audiosample_must_match(&self->base, sample); self->sample = sample; self->loop = loop; @@ -196,7 +166,7 @@ void common_hal_audiodelays_chorus_play(audiodelays_chorus_obj_t *self, mp_obj_t audioio_get_buffer_result_t result = audiosample_get_buffer(self->sample, false, 0, (uint8_t **)&self->sample_remaining_buffer, &self->sample_buffer_length); // Track remaining sample length in terms of bytes per sample - self->sample_buffer_length /= (self->bits_per_sample / 8); + self->sample_buffer_length /= (self->base.bits_per_sample / 8); // Store if we have more data in the sample to retrieve self->more_data = result == GET_BUFFER_MORE_DATA; @@ -219,7 +189,7 @@ audioio_get_buffer_result_t audiodelays_chorus_get_buffer(audiodelays_chorus_obj // If we are using 16 bit samples we need a 16 bit pointer, 8 bit needs an 8 bit pointer int16_t *word_buffer = (int16_t *)self->buffer[self->last_buf_idx]; int8_t *hword_buffer = self->buffer[self->last_buf_idx]; - uint32_t length = self->buffer_len / (self->bits_per_sample / 8); + uint32_t length = self->buffer_len / (self->base.bits_per_sample / 8); // The chorus buffer is always stored as a 16-bit value internally int16_t *chorus_buffer = (int16_t *)self->chorus_buffer; @@ -240,7 +210,7 @@ audioio_get_buffer_result_t audiodelays_chorus_get_buffer(audiodelays_chorus_obj // Load another sample buffer to play audioio_get_buffer_result_t result = audiosample_get_buffer(self->sample, false, 0, (uint8_t **)&self->sample_remaining_buffer, &self->sample_buffer_length); // Track length in terms of words. - self->sample_buffer_length /= (self->bits_per_sample / 8); + self->sample_buffer_length /= (self->base.bits_per_sample / 8); self->more_data = result == GET_BUFFER_MORE_DATA; } } @@ -248,13 +218,13 @@ audioio_get_buffer_result_t audiodelays_chorus_get_buffer(audiodelays_chorus_obj // Determine how many bytes we can process to our buffer, the less of the sample we have left and our buffer remaining uint32_t n; if (self->sample == NULL) { - n = MIN(length, SYNTHIO_MAX_DUR * self->channel_count); + n = MIN(length, SYNTHIO_MAX_DUR * self->base.channel_count); } else { - n = MIN(MIN(self->sample_buffer_length, length), SYNTHIO_MAX_DUR * self->channel_count); + n = MIN(MIN(self->sample_buffer_length, length), SYNTHIO_MAX_DUR * self->base.channel_count); } // get the effect values we need from the BlockInput. These may change at run time so you need to do bounds checking if required - shared_bindings_synthio_lfo_tick(self->sample_rate, n / self->channel_count); + shared_bindings_synthio_lfo_tick(self->base.sample_rate, n / self->base.channel_count); int32_t voices = MAX(synthio_block_slot_get(&self->voices), 1.0); @@ -264,17 +234,17 @@ audioio_get_buffer_result_t audiodelays_chorus_get_buffer(audiodelays_chorus_obj } if (self->sample == NULL) { - if (self->samples_signed) { - memset(word_buffer, 0, n * (self->bits_per_sample / 8)); + if (self->base.samples_signed) { + memset(word_buffer, 0, n * (self->base.bits_per_sample / 8)); } else { // For unsigned samples set to the middle which is "quiet" - if (MP_LIKELY(self->bits_per_sample == 16)) { + if (MP_LIKELY(self->base.bits_per_sample == 16)) { uint16_t *uword_buffer = (uint16_t *)word_buffer; for (uint32_t i = 0; i < n; i++) { *uword_buffer++ = 32768; } } else { - memset(hword_buffer, 128, n * (self->bits_per_sample / 8)); + memset(hword_buffer, 128, n * (self->base.bits_per_sample / 8)); } } } else { @@ -283,10 +253,10 @@ audioio_get_buffer_result_t audiodelays_chorus_get_buffer(audiodelays_chorus_obj for (uint32_t i = 0; i < n; i++) { int32_t sample_word = 0; - if (MP_LIKELY(self->bits_per_sample == 16)) { + if (MP_LIKELY(self->base.bits_per_sample == 16)) { sample_word = sample_src[i]; } else { - if (self->samples_signed) { + if (self->base.samples_signed) { sample_word = sample_hsrc[i]; } else { // Be careful here changing from an 8 bit unsigned to signed into a 32-bit signed @@ -316,14 +286,14 @@ audioio_get_buffer_result_t audiodelays_chorus_get_buffer(audiodelays_chorus_obj word = synthio_mix_down_sample(word, SYNTHIO_MIX_DOWN_SCALE(2)); } - if (MP_LIKELY(self->bits_per_sample == 16)) { + if (MP_LIKELY(self->base.bits_per_sample == 16)) { word_buffer[i] = word; - if (!self->samples_signed) { + if (!self->base.samples_signed) { word_buffer[i] ^= 0x8000; } } else { int8_t out = word; - if (self->samples_signed) { + if (self->base.samples_signed) { hword_buffer[i] = out; } else { hword_buffer[i] = (uint8_t)out ^ 0x80; @@ -334,7 +304,7 @@ audioio_get_buffer_result_t audiodelays_chorus_get_buffer(audiodelays_chorus_obj self->chorus_buffer_pos = 0; } } - self->sample_remaining_buffer += (n * (self->bits_per_sample / 8)); + self->sample_remaining_buffer += (n * (self->base.bits_per_sample / 8)); self->sample_buffer_length -= n; } // Update the remaining length and the buffer positions based on how much we wrote into our buffer @@ -350,18 +320,3 @@ audioio_get_buffer_result_t audiodelays_chorus_get_buffer(audiodelays_chorus_obj // Chorus always returns more data but some effects may return GET_BUFFER_DONE or GET_BUFFER_ERROR (see audiocore/__init__.h) return GET_BUFFER_MORE_DATA; } - -void audiodelays_chorus_get_buffer_structure(audiodelays_chorus_obj_t *self, bool single_channel_output, - bool *single_buffer, bool *samples_signed, uint32_t *max_buffer_length, uint8_t *spacing) { - - // Return information about the effect's buffer (not the sample's) - // These are used by calling audio objects to determine how to handle the effect's buffer - *single_buffer = false; - *samples_signed = self->samples_signed; - *max_buffer_length = self->buffer_len; - if (single_channel_output) { - *spacing = self->channel_count; - } else { - *spacing = 1; - } -} diff --git a/shared-module/audiodelays/Chorus.h b/shared-module/audiodelays/Chorus.h index a5ead0922d7d5..6e40a8c199db6 100644 --- a/shared-module/audiodelays/Chorus.h +++ b/shared-module/audiodelays/Chorus.h @@ -13,18 +13,13 @@ extern const mp_obj_type_t audiodelays_chorus_type; typedef struct { - mp_obj_base_t base; + audiosample_base_t base; uint32_t max_delay_ms; synthio_block_slot_t delay_ms; mp_float_t current_delay_ms; mp_float_t sample_ms; synthio_block_slot_t voices; - uint8_t bits_per_sample; - bool samples_signed; - uint8_t channel_count; - uint32_t sample_rate; - int8_t *buffer[2]; uint8_t last_buf_idx; uint32_t buffer_len; // max buffer in bytes @@ -55,7 +50,3 @@ audioio_get_buffer_result_t audiodelays_chorus_get_buffer(audiodelays_chorus_obj uint8_t channel, uint8_t **buffer, uint32_t *buffer_length); // length in bytes - -void audiodelays_chorus_get_buffer_structure(audiodelays_chorus_obj_t *self, bool single_channel_output, - bool *single_buffer, bool *samples_signed, - uint32_t *max_buffer_length, uint8_t *spacing); From c3085f594c84e2231a59b795a56866ba832128b7 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Sun, 9 Feb 2025 10:54:29 -0600 Subject: [PATCH 04/10] Fixed docs --- shared-bindings/audiodelays/Chorus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared-bindings/audiodelays/Chorus.c b/shared-bindings/audiodelays/Chorus.c index 0721513018da9..bca70b58f85de 100644 --- a/shared-bindings/audiodelays/Chorus.c +++ b/shared-bindings/audiodelays/Chorus.c @@ -23,7 +23,7 @@ //| def __init__( //| self, //| max_delay_ms: int = 50, -//| delay_ms: BlockInput = 50.0, +//| delay_ms: synthio.BlockInput = 50.0, //| voices: synthio.BlockInput = 1.0, //| buffer_size: int = 512, //| sample_rate: int = 8000, From 06446b5bccc71dc87460ddae98602fb6b430a59c Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Sat, 22 Feb 2025 10:36:48 -0600 Subject: [PATCH 05/10] Use full buffer for storing delay and remove division of average --- shared-module/audiodelays/Chorus.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/shared-module/audiodelays/Chorus.c b/shared-module/audiodelays/Chorus.c index 269981d18863f..6ab5679df2980 100644 --- a/shared-module/audiodelays/Chorus.c +++ b/shared-module/audiodelays/Chorus.c @@ -194,6 +194,7 @@ audioio_get_buffer_result_t audiodelays_chorus_get_buffer(audiodelays_chorus_obj // The chorus buffer is always stored as a 16-bit value internally int16_t *chorus_buffer = (int16_t *)self->chorus_buffer; uint32_t chorus_buf_len = self->chorus_buffer_len / sizeof(uint16_t); + uint32_t max_chorus_buf_len = self->max_chorus_buffer_len / sizeof(uint16_t); // Loop over the entire length of our buffer to fill it, this may require several calls to get data from the sample while (length != 0) { @@ -227,6 +228,7 @@ audioio_get_buffer_result_t audiodelays_chorus_get_buffer(audiodelays_chorus_obj shared_bindings_synthio_lfo_tick(self->base.sample_rate, n / self->base.channel_count); int32_t voices = MAX(synthio_block_slot_get(&self->voices), 1.0); + int32_t mix_down_scale = SYNTHIO_MIX_DOWN_SCALE(voices); mp_float_t f_delay_ms = synthio_block_slot_get(&self->delay_ms); if (MICROPY_FLOAT_C_FUN(fabs)(self->current_delay_ms - f_delay_ms) >= self->sample_ms) { @@ -275,15 +277,18 @@ audioio_get_buffer_result_t audiodelays_chorus_get_buffer(audiodelays_chorus_obj for (int32_t v = 0; v < voices; v++) { if (c_pos < 0) { - c_pos += chorus_buf_len; + c_pos += max_chorus_buf_len; } word += chorus_buffer[c_pos]; c_pos -= step; } - word = word / voices; - word = synthio_mix_down_sample(word, SYNTHIO_MIX_DOWN_SCALE(2)); + // Dividing would get an average but does not sound as good + // Leaving this here in case someone wants to try an average instead + // word = word / voices; + + word = synthio_mix_down_sample(word, mix_down_scale); } if (MP_LIKELY(self->base.bits_per_sample == 16)) { @@ -300,7 +305,7 @@ audioio_get_buffer_result_t audiodelays_chorus_get_buffer(audiodelays_chorus_obj } } - if (self->chorus_buffer_pos >= chorus_buf_len) { + if (self->chorus_buffer_pos >= max_chorus_buf_len) { self->chorus_buffer_pos = 0; } } From 5ef8ea3a4c095e531f105d0dd0f940f67193638d Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Sat, 22 Feb 2025 11:09:05 -0600 Subject: [PATCH 06/10] Fix unix build and unit conversion errors --- shared-module/audiodelays/Chorus.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/shared-module/audiodelays/Chorus.c b/shared-module/audiodelays/Chorus.c index 6ab5679df2980..c298c50f1e089 100644 --- a/shared-module/audiodelays/Chorus.c +++ b/shared-module/audiodelays/Chorus.c @@ -72,7 +72,7 @@ void common_hal_audiodelays_chorus_construct(audiodelays_chorus_obj_t *self, uin // Allocate the chorus buffer for the max possible delay, chorus is always 16-bit self->max_delay_ms = max_delay_ms; - self->max_chorus_buffer_len = self->base.sample_rate / MICROPY_FLOAT_CONST(1000.0) * max_delay_ms * (self->base.channel_count * sizeof(uint16_t)); // bytes + self->max_chorus_buffer_len = (uint32_t)(self->base.sample_rate / MICROPY_FLOAT_CONST(1000.0) * max_delay_ms * (self->base.channel_count * sizeof(uint16_t))); // bytes self->chorus_buffer = m_malloc(self->max_chorus_buffer_len); if (self->chorus_buffer == NULL) { common_hal_audiodelays_chorus_deinit(self); @@ -126,10 +126,6 @@ void chorus_recalculate_delay(audiodelays_chorus_obj_t *self, mp_float_t f_delay // Calculate the current chorus buffer length in bytes uint32_t new_chorus_buffer_len = (uint32_t)(self->base.sample_rate / MICROPY_FLOAT_CONST(1000.0) * f_delay_ms) * (self->base.channel_count * sizeof(uint16_t)); - if (new_chorus_buffer_len < 0) { // or too short! - return; - } - self->chorus_buffer_len = new_chorus_buffer_len; self->current_delay_ms = f_delay_ms; @@ -227,7 +223,7 @@ audioio_get_buffer_result_t audiodelays_chorus_get_buffer(audiodelays_chorus_obj // get the effect values we need from the BlockInput. These may change at run time so you need to do bounds checking if required shared_bindings_synthio_lfo_tick(self->base.sample_rate, n / self->base.channel_count); - int32_t voices = MAX(synthio_block_slot_get(&self->voices), 1.0); + int32_t voices = (int32_t)MAX(synthio_block_slot_get(&self->voices), 1.0); int32_t mix_down_scale = SYNTHIO_MIX_DOWN_SCALE(voices); mp_float_t f_delay_ms = synthio_block_slot_get(&self->delay_ms); From 4f928c987684b9220affe77b9565fcb6c7c74fce Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Sat, 22 Feb 2025 11:15:33 -0600 Subject: [PATCH 07/10] Doc fix --- shared-bindings/audiodelays/Chorus.c | 1 + 1 file changed, 1 insertion(+) diff --git a/shared-bindings/audiodelays/Chorus.c b/shared-bindings/audiodelays/Chorus.c index bca70b58f85de..81ad43e6a35ab 100644 --- a/shared-bindings/audiodelays/Chorus.c +++ b/shared-bindings/audiodelays/Chorus.c @@ -37,6 +37,7 @@ //| The delay timing of the chorus can be changed at runtime with the delay_ms parameter but the delay //| can never exceed the max_delay_ms parameter. The maximum delay you can set is limited by available //| memory. +//| //| :param int max_delay_ms: The maximum time the chorus can be in milliseconds //| :param synthio.BlockInput delay_ms: The current time of the chorus delay in milliseconds. Must be less the max_delay_ms. //| :param synthio.BlockInput voices: The number of voices playing split evenly over the delay buffer. From 702c33f58ee24d870efeb72dd8ce423fa0337fb6 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Sat, 22 Feb 2025 11:43:33 -0600 Subject: [PATCH 08/10] Actually commit unix build changes --- ports/unix/variants/coverage/mpconfigvariant.mk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/unix/variants/coverage/mpconfigvariant.mk b/ports/unix/variants/coverage/mpconfigvariant.mk index 96dfcd68bcc3e..f49e830ca4b98 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.mk +++ b/ports/unix/variants/coverage/mpconfigvariant.mk @@ -34,6 +34,7 @@ SRC_BITMAP := \ shared-bindings/audiocore/RawSample.c \ shared-bindings/audiocore/WaveFile.c \ shared-bindings/audiodelays/Echo.c \ + shared-bindings/audiodelays/Chorus.c \ shared-bindings/audiodelays/__init__.c \ shared-bindings/audiofilters/Distortion.c \ shared-bindings/audiofilters/Filter.c \ @@ -77,6 +78,7 @@ SRC_BITMAP := \ shared-module/audiocore/RawSample.c \ shared-module/audiocore/WaveFile.c \ shared-module/audiodelays/Echo.c \ + shared-module/audiodelays/Chorus.c \ shared-module/audiodelays/__init__.c \ shared-module/audiofilters/Distortion.c \ shared-module/audiofilters/Filter.c \ From 6255c467f21dff3925cee05e6ae2953535710b82 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Tue, 1 Apr 2025 18:38:16 -0500 Subject: [PATCH 09/10] Add in mix --- shared-bindings/audiodelays/Chorus.c | 25 +++++++++++++++++++++++-- shared-bindings/audiodelays/Chorus.h | 5 ++++- shared-module/audiodelays/Chorus.c | 20 +++++++++++++++++++- shared-module/audiodelays/Chorus.h | 1 + 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/shared-bindings/audiodelays/Chorus.c b/shared-bindings/audiodelays/Chorus.c index 81ad43e6a35ab..c6bfa2ffa7eb1 100644 --- a/shared-bindings/audiodelays/Chorus.c +++ b/shared-bindings/audiodelays/Chorus.c @@ -41,6 +41,7 @@ //| :param int max_delay_ms: The maximum time the chorus can be in milliseconds //| :param synthio.BlockInput delay_ms: The current time of the chorus delay in milliseconds. Must be less the max_delay_ms. //| :param synthio.BlockInput voices: The number of voices playing split evenly over the delay buffer. +//| :param synthio.BlockInput mix: How much of the wet audio to include along with the original signal. //| :param int buffer_size: The total size in bytes of each of the two playback buffers to use //| :param int sample_rate: The sample rate to be used //| :param int channel_count: The number of channels the source samples contain. 1 = mono; 2 = stereo. @@ -70,11 +71,12 @@ //| ... //| static mp_obj_t audiodelays_chorus_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - enum { ARG_max_delay_ms, ARG_delay_ms, ARG_voices, ARG_buffer_size, ARG_sample_rate, ARG_bits_per_sample, ARG_samples_signed, ARG_channel_count, }; + enum { ARG_max_delay_ms, ARG_delay_ms, ARG_voices, ARG_mix, ARG_buffer_size, ARG_sample_rate, ARG_bits_per_sample, ARG_samples_signed, ARG_channel_count, }; static const mp_arg_t allowed_args[] = { { MP_QSTR_max_delay_ms, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 50 } }, { MP_QSTR_delay_ms, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_voices, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_mix, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_buffer_size, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 512} }, { MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 8000} }, { MP_QSTR_bits_per_sample, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 16} }, @@ -95,7 +97,7 @@ static mp_obj_t audiodelays_chorus_make_new(const mp_obj_type_t *type, size_t n_ } audiodelays_chorus_obj_t *self = mp_obj_malloc(audiodelays_chorus_obj_t, &audiodelays_chorus_type); - common_hal_audiodelays_chorus_construct(self, max_delay_ms, args[ARG_delay_ms].u_obj, args[ARG_voices].u_obj, args[ARG_buffer_size].u_int, bits_per_sample, args[ARG_samples_signed].u_bool, channel_count, sample_rate); + common_hal_audiodelays_chorus_construct(self, max_delay_ms, args[ARG_delay_ms].u_obj, args[ARG_voices].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); return MP_OBJ_FROM_PTR(self); } @@ -173,6 +175,24 @@ MP_PROPERTY_GETSET(audiodelays_chorus_voices_obj, (mp_obj_t)&audiodelays_chorus_get_voices_obj, (mp_obj_t)&audiodelays_chorus_set_voices_obj); +//| mix: synthio.BlockInput +//| """The rate the echo mix between 0 and 1 where 0 is only sample and 1 is all effect.""" +static mp_obj_t audiodelays_chorus_obj_get_mix(mp_obj_t self_in) { + return common_hal_audiodelays_chorus_get_mix(self_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_chorus_get_mix_obj, audiodelays_chorus_obj_get_mix); + +static mp_obj_t audiodelays_chorus_obj_set_mix(mp_obj_t self_in, mp_obj_t mix_in) { + audiodelays_chorus_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_audiodelays_chorus_set_mix(self, mix_in); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(audiodelays_chorus_set_mix_obj, audiodelays_chorus_obj_set_mix); + +MP_PROPERTY_GETSET(audiodelays_chorus_mix_obj, + (mp_obj_t)&audiodelays_chorus_get_mix_obj, + (mp_obj_t)&audiodelays_chorus_set_mix_obj); + //| playing: bool //| """True when the effect is playing a sample. (read-only)""" //| @@ -237,6 +257,7 @@ static const mp_rom_map_elem_t audiodelays_chorus_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_playing), MP_ROM_PTR(&audiodelays_chorus_playing_obj) }, { MP_ROM_QSTR(MP_QSTR_delay_ms), MP_ROM_PTR(&audiodelays_chorus_delay_ms_obj) }, { MP_ROM_QSTR(MP_QSTR_voices), MP_ROM_PTR(&audiodelays_chorus_voices_obj) }, + { MP_ROM_QSTR(MP_QSTR_mix), MP_ROM_PTR(&audiodelays_chorus_mix_obj) }, AUDIOSAMPLE_FIELDS, }; static MP_DEFINE_CONST_DICT(audiodelays_chorus_locals_dict, audiodelays_chorus_locals_dict_table); diff --git a/shared-bindings/audiodelays/Chorus.h b/shared-bindings/audiodelays/Chorus.h index 489ae644d7e14..10c6448df8955 100644 --- a/shared-bindings/audiodelays/Chorus.h +++ b/shared-bindings/audiodelays/Chorus.h @@ -11,7 +11,7 @@ extern const mp_obj_type_t audiodelays_chorus_type; void common_hal_audiodelays_chorus_construct(audiodelays_chorus_obj_t *self, uint32_t max_delay_ms, - mp_obj_t delay_ms, mp_obj_t voices, + mp_obj_t delay_ms, mp_obj_t voices, mp_obj_t mix, uint32_t buffer_size, uint8_t bits_per_sample, bool samples_signed, uint8_t channel_count, uint32_t sample_rate); @@ -28,6 +28,9 @@ void common_hal_audiodelays_chorus_set_delay_ms(audiodelays_chorus_obj_t *self, mp_obj_t common_hal_audiodelays_chorus_get_voices(audiodelays_chorus_obj_t *self); void common_hal_audiodelays_chorus_set_voices(audiodelays_chorus_obj_t *self, mp_obj_t voices); +mp_obj_t common_hal_audiodelays_chorus_get_mix(audiodelays_chorus_obj_t *self); +void common_hal_audiodelays_chorus_set_mix(audiodelays_chorus_obj_t *self, mp_obj_t arg); + bool common_hal_audiodelays_chorus_get_playing(audiodelays_chorus_obj_t *self); void common_hal_audiodelays_chorus_play(audiodelays_chorus_obj_t *self, mp_obj_t sample, bool loop); void common_hal_audiodelays_chorus_stop(audiodelays_chorus_obj_t *self); diff --git a/shared-module/audiodelays/Chorus.c b/shared-module/audiodelays/Chorus.c index c298c50f1e089..20356248d9f68 100644 --- a/shared-module/audiodelays/Chorus.c +++ b/shared-module/audiodelays/Chorus.c @@ -10,7 +10,7 @@ #include "py/runtime.h" void common_hal_audiodelays_chorus_construct(audiodelays_chorus_obj_t *self, uint32_t max_delay_ms, - mp_obj_t delay_ms, mp_obj_t voices, + mp_obj_t delay_ms, mp_obj_t voices, mp_obj_t mix, uint32_t buffer_size, uint8_t bits_per_sample, bool samples_signed, uint8_t channel_count, uint32_t sample_rate) { @@ -66,6 +66,11 @@ void common_hal_audiodelays_chorus_construct(audiodelays_chorus_obj_t *self, uin } synthio_block_assign_slot(delay_ms, &self->delay_ms, MP_QSTR_delay_ms); + if (mix == MP_OBJ_NULL) { + mix = mp_obj_new_float(MICROPY_FLOAT_CONST(0.5)); + } + synthio_block_assign_slot(mix, &self->mix, MP_QSTR_mix); + // Many effects may need buffers of what was played this shows how it was done for the chorus // A maximum length buffer was created and then the current chorus length can be dynamically changes // without having to reallocate a large chunk of memory. @@ -148,6 +153,14 @@ void audiodelays_chorus_reset_buffer(audiodelays_chorus_obj_t *self, memset(self->chorus_buffer, 0, self->chorus_buffer_len); } +mp_obj_t common_hal_audiodelays_chorus_get_mix(audiodelays_chorus_obj_t *self) { + return self->mix.obj; +} + +void common_hal_audiodelays_chorus_set_mix(audiodelays_chorus_obj_t *self, mp_obj_t arg) { + synthio_block_assign_slot(arg, &self->mix, MP_QSTR_mix); +} + bool common_hal_audiodelays_chorus_get_playing(audiodelays_chorus_obj_t *self) { return self->sample != NULL; } @@ -225,6 +238,7 @@ audioio_get_buffer_result_t audiodelays_chorus_get_buffer(audiodelays_chorus_obj int32_t voices = (int32_t)MAX(synthio_block_slot_get(&self->voices), 1.0); int32_t mix_down_scale = SYNTHIO_MIX_DOWN_SCALE(voices); + mp_float_t mix = synthio_block_slot_get_limited(&self->mix, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.0)); mp_float_t f_delay_ms = synthio_block_slot_get(&self->delay_ms); if (MICROPY_FLOAT_C_FUN(fabs)(self->current_delay_ms - f_delay_ms) >= self->sample_ms) { @@ -287,6 +301,10 @@ audioio_get_buffer_result_t audiodelays_chorus_get_buffer(audiodelays_chorus_obj word = synthio_mix_down_sample(word, mix_down_scale); } + // Add original sample + effect + word = sample_word + (word * mix); + word = synthio_mix_down_sample(word, 2); + if (MP_LIKELY(self->base.bits_per_sample == 16)) { word_buffer[i] = word; if (!self->base.samples_signed) { diff --git a/shared-module/audiodelays/Chorus.h b/shared-module/audiodelays/Chorus.h index 6e40a8c199db6..c2602866ef7c5 100644 --- a/shared-module/audiodelays/Chorus.h +++ b/shared-module/audiodelays/Chorus.h @@ -19,6 +19,7 @@ typedef struct { mp_float_t current_delay_ms; mp_float_t sample_ms; synthio_block_slot_t voices; + synthio_block_slot_t mix; int8_t *buffer[2]; uint8_t last_buf_idx; From 3e7a36c46cf9c07a6081e821274572e216fd4a38 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Wed, 2 Apr 2025 19:10:06 -0500 Subject: [PATCH 10/10] Fixed typecast missing error --- shared-module/audiodelays/Chorus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared-module/audiodelays/Chorus.c b/shared-module/audiodelays/Chorus.c index 20356248d9f68..9a8c55fa958e7 100644 --- a/shared-module/audiodelays/Chorus.c +++ b/shared-module/audiodelays/Chorus.c @@ -302,7 +302,7 @@ audioio_get_buffer_result_t audiodelays_chorus_get_buffer(audiodelays_chorus_obj } // Add original sample + effect - word = sample_word + (word * mix); + word = sample_word + (int32_t)(word * mix); word = synthio_mix_down_sample(word, 2); if (MP_LIKELY(self->base.bits_per_sample == 16)) {