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)) { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy