From 5e923038f3591a44364946edbe9c6164371b3f21 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Fri, 13 Sep 2024 18:24:47 -0500 Subject: [PATCH 01/23] Initial audio effects commit --- locale/circuitpython.pot | 8 +- ports/raspberrypi/mpconfigport.mk | 2 +- py/circuitpy_defns.mk | 6 + py/circuitpy_mpconfig.mk | 3 + shared-bindings/audioeffects/Echo.c | 245 +++++++++++++++++++++ shared-bindings/audioeffects/Echo.h | 33 +++ shared-bindings/audioeffects/__init__.c | 33 +++ shared-bindings/audioeffects/__init__.h | 7 + shared-module/audioeffects/Echo.c | 189 ++++++++++++++++ shared-module/audioeffects/Echo.h | 47 ++++ shared-module/audioeffects/__init__.c | 5 + shared-module/audioeffects/__init__.h | 7 + shared-module/audioeffects/effects_utils.c | 8 + shared-module/audioeffects/effects_utils.h | 58 +++++ 14 files changed, 648 insertions(+), 3 deletions(-) create mode 100644 shared-bindings/audioeffects/Echo.c create mode 100644 shared-bindings/audioeffects/Echo.h create mode 100644 shared-bindings/audioeffects/__init__.c create mode 100644 shared-bindings/audioeffects/__init__.h create mode 100644 shared-module/audioeffects/Echo.c create mode 100644 shared-module/audioeffects/Echo.h create mode 100644 shared-module/audioeffects/__init__.c create mode 100644 shared-module/audioeffects/__init__.h create mode 100644 shared-module/audioeffects/effects_utils.c create mode 100644 shared-module/audioeffects/effects_utils.h diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 25d4a28312287..f2dbe39b4ead5 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -2500,7 +2500,7 @@ msgstr "" msgid "bits must be 32 or less" msgstr "" -#: shared-bindings/audiomixer/Mixer.c +#: shared-bindings/audioeffects/Echo.c shared-bindings/audiomixer/Mixer.c msgid "bits_per_sample must be 8 or 16" msgstr "" @@ -2847,6 +2847,10 @@ msgstr "" msgid "data type not understood" msgstr "" +#: shared-bindings/audioeffects/Echo.c +msgid "decay must be between 0 and 1" +msgstr "" + #: py/parsenum.c msgid "decimal numbers not supported" msgstr "" @@ -3242,7 +3246,7 @@ msgstr "" msgid "input must be a dense ndarray" msgstr "" -#: extmod/ulab/code/user/user.c +#: extmod/ulab/code/user/user.c shared-bindings/_eve/__init__.c msgid "input must be an ndarray" msgstr "" diff --git a/ports/raspberrypi/mpconfigport.mk b/ports/raspberrypi/mpconfigport.mk index ab72ddc4f05bc..ebf9cf5e3a506 100644 --- a/ports/raspberrypi/mpconfigport.mk +++ b/ports/raspberrypi/mpconfigport.mk @@ -22,7 +22,7 @@ CIRCUITPY_PWMIO ?= 1 CIRCUITPY_RGBMATRIX ?= $(CIRCUITPY_DISPLAYIO) CIRCUITPY_ROTARYIO ?= 1 CIRCUITPY_ROTARYIO_SOFTENCODER = 1 -CIRCUITPY_SYNTHIO_MAX_CHANNELS = 12 +CIRCUITPY_SYNTHIO_MAX_CHANNELS = 24 CIRCUITPY_USB_HOST ?= 1 CIRCUITPY_USB_VIDEO ?= 1 diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index 6d7848997cac2..5386ce74d892b 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -131,6 +131,9 @@ endif ifeq ($(CIRCUITPY_AUDIOCORE),1) SRC_PATTERNS += audiocore/% endif +ifeq ($(CIRCUITPY_AUDIOEFFECTS),1) +SRC_PATTERNS += audioeffects/% +endif ifeq ($(CIRCUITPY_AUDIOMIXER),1) SRC_PATTERNS += audiomixer/% endif @@ -617,6 +620,8 @@ SRC_SHARED_MODULE_ALL = \ audiocore/RawSample.c \ audiocore/WaveFile.c \ audiocore/__init__.c \ + audioeffects/Echo.c \ + audioeffects/__init__.c \ audioio/__init__.c \ audiomixer/Mixer.c \ audiomixer/MixerVoice.c \ @@ -857,6 +862,7 @@ $(filter $(SRC_PATTERNS), \ displayio/display_core.c \ os/getenv.c \ usb/utf16le.c \ + audioeffects/effects_utils.c \ ) SRC_COMMON_HAL_INTERNAL = \ diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index 81d46270f8f9c..de130b3fdbdfa 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -141,6 +141,9 @@ CFLAGS += -DCIRCUITPY_AUDIOCORE_DEBUG=$(CIRCUITPY_AUDIOCORE_DEBUG) CIRCUITPY_AUDIOMP3 ?= $(call enable-if-all,$(CIRCUITPY_FULL_BUILD) $(CIRCUITPY_AUDIOCORE)) CFLAGS += -DCIRCUITPY_AUDIOMP3=$(CIRCUITPY_AUDIOMP3) +CIRCUITPY_AUDIOEFFECTS ?= 0 +CFLAGS += -DCIRCUITPY_AUDIOEFFECTS=$(CIRCUITPY_AUDIOEFFECTS) + CIRCUITPY_AURORA_EPAPER ?= 0 CFLAGS += -DCIRCUITPY_AURORA_EPAPER=$(CIRCUITPY_AURORA_EPAPER) diff --git a/shared-bindings/audioeffects/Echo.c b/shared-bindings/audioeffects/Echo.c new file mode 100644 index 0000000000000..bc37b59b64f2d --- /dev/null +++ b/shared-bindings/audioeffects/Echo.c @@ -0,0 +1,245 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Mark Komus +// +// SPDX-License-Identifier: MIT + +#include + +#include "shared-bindings/audioeffects/Echo.h" +#include "shared-module/audioeffects/Echo.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" + +#define DECAY_DEFAULT 0.7f + +//| class Echo: +//| """An Echo effect""" +//| +static mp_obj_t audioeffects_echo_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_delay_ms, ARG_decay, 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_delay_ms, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 50 } }, + { MP_QSTR_decay, 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 = 1024} }, + { 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 delay_ms = mp_arg_validate_int_range(args[ARG_delay_ms].u_int, 1, 4000, MP_QSTR_delay_ms); + + mp_float_t decay = (args[ARG_decay].u_obj == MP_OBJ_NULL) + ? (mp_float_t)DECAY_DEFAULT + : mp_obj_get_float(args[ARG_decay].u_obj); + mp_arg_validate_float_range(decay, 0.0f, 1.0f, MP_QSTR_decay); + + 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")); + } + + audioeffects_echo_obj_t *self = mp_obj_malloc(audioeffects_echo_obj_t, &audioeffects_echo_type); + common_hal_audioeffects_echo_construct(self, delay_ms, decay, 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 Echo and releases any hardware resources for reuse.""" +//| ... +static mp_obj_t audioeffects_echo_deinit(mp_obj_t self_in) { + audioeffects_echo_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_audioeffects_echo_deinit(self); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(audioeffects_echo_deinit_obj, audioeffects_echo_deinit); + +static void check_for_deinit(audioeffects_echo_obj_t *self) { + if (common_hal_audioeffects_echo_deinited(self)) { + raise_deinited_error(); + } +} + +//| def __enter__(self) -> Echo: +//| """No-op used by Context Managers.""" +//| ... +// Provided by context manager helper. + +//| def __exit__(self) -> None: +//| """Automatically deinitializes the hardware when exiting a context. See +//| :ref:`lifetime-and-contextmanagers` for more info.""" +//| ... +static mp_obj_t audioeffects_echo_obj___exit__(size_t n_args, const mp_obj_t *args) { + (void)n_args; + common_hal_audioeffects_echo_deinit(args[0]); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audioeffects_echo___exit___obj, 4, 4, audioeffects_echo_obj___exit__); + + +//| delay_ms: int +//| """Delay of the echo in microseconds. (read-only)""" +//| +static mp_obj_t audioeffects_echo_obj_get_delay_ms(mp_obj_t self_in) { + audioeffects_echo_obj_t *self = MP_OBJ_TO_PTR(self_in); + + return mp_obj_new_float(common_hal_audioeffects_echo_get_delay_ms(self)); + +} +MP_DEFINE_CONST_FUN_OBJ_1(audioeffects_echo_get_delay_ms_obj, audioeffects_echo_obj_get_delay_ms); + +static mp_obj_t audioeffects_echo_obj_set_delay_ms(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_delay_ms }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_delay_ms, MP_ARG_INT | MP_ARG_REQUIRED, {} }, + }; + audioeffects_echo_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + 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_int_t delay_ms = mp_arg_validate_int_range(args[ARG_delay_ms].u_int, 1, 4000, MP_QSTR_delay_ms); + + common_hal_audioeffects_echo_set_delay_ms(self, delay_ms); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(audioeffects_echo_set_delay_ms_obj, 1, audioeffects_echo_obj_set_delay_ms); + +MP_PROPERTY_GETSET(audioeffects_echo_delay_ms_obj, + (mp_obj_t)&audioeffects_echo_get_delay_ms_obj, + (mp_obj_t)&audioeffects_echo_set_delay_ms_obj); + + + +//| decay: float +//| """The rate the echo decays between 0 and 1.""" +static mp_obj_t audioeffects_echo_obj_get_decay(mp_obj_t self_in) { + return mp_obj_new_float(common_hal_audioeffects_echo_get_decay(self_in)); +} +MP_DEFINE_CONST_FUN_OBJ_1(audioeffects_echo_get_decay_obj, audioeffects_echo_obj_get_decay); + +static mp_obj_t audioeffects_echo_obj_set_decay(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_decay }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_decay, MP_ARG_OBJ | MP_ARG_REQUIRED, {} }, + }; + audioeffects_echo_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + 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_float_t decay = mp_obj_get_float(args[ARG_decay].u_obj); + + if (decay > 1 || decay < 0) { + mp_raise_ValueError(MP_ERROR_TEXT("decay must be between 0 and 1")); + } + + common_hal_audioeffects_echo_set_decay(self, decay); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(audioeffects_echo_set_decay_obj, 1, audioeffects_echo_obj_set_decay); + +MP_PROPERTY_GETSET(audioeffects_echo_decay_obj, + (mp_obj_t)&audioeffects_echo_get_decay_obj, + (mp_obj_t)&audioeffects_echo_set_decay_obj); + + +//| playing: bool +//| """True when any voice is being output. (read-only)""" +static mp_obj_t audioeffects_echo_obj_get_playing(mp_obj_t self_in) { + audioeffects_echo_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + return mp_obj_new_bool(common_hal_audioeffects_echo_get_playing(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(audioeffects_echo_get_playing_obj, audioeffects_echo_obj_get_playing); + +MP_PROPERTY_GETTER(audioeffects_echo_playing_obj, + (mp_obj_t)&audioeffects_echo_get_playing_obj); + +//| def play( +//| self, sample: circuitpython_typing.AudioSample, *, voice: int = 0, loop: bool = False +//| ) -> None: +//| """Plays the sample once when loop=False and continuously when loop=True. +//| Does not block. Use `playing` to block. +//| +//| Sample must be an `audiocore.WaveFile`, `audiocore.RawSample`, `audiomixer.Mixer` or `audiomp3.MP3Decoder`. +//| +//| The sample must match the Effect's encoding settings given in the constructor.""" +//| ... +static mp_obj_t audioeffects_echo_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} }, + }; + audioeffects_echo_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_audioeffects_echo_play(self, sample, args[ARG_loop].u_bool); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(audioeffects_echo_play_obj, 1, audioeffects_echo_obj_play); + +//| def stop_voice(self, voice: int = 0) -> None: +//| """Stops playback of the sample.""" +//| ... +//| +static mp_obj_t audioeffects_echo_obj_stop(mp_obj_t self_in) { + audioeffects_echo_obj_t *self = MP_OBJ_TO_PTR(self_in); + + common_hal_audioeffects_echo_stop(self); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(audioeffects_echo_stop_obj, audioeffects_echo_obj_stop); + + + +static const mp_rom_map_elem_t audioeffects_echo_locals_dict_table[] = { + // Methods + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audioeffects_echo_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, + { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&audioeffects_echo___exit___obj) }, + { MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audioeffects_echo_play_obj) }, + { MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&audioeffects_echo_stop_obj) }, + + // Properties + { MP_ROM_QSTR(MP_QSTR_playing), MP_ROM_PTR(&audioeffects_echo_playing_obj) }, + { MP_ROM_QSTR(MP_QSTR_delay_ms), MP_ROM_PTR(&audioeffects_echo_delay_ms_obj) }, + { MP_ROM_QSTR(MP_QSTR_decay), MP_ROM_PTR(&audioeffects_echo_decay_obj) }, +}; +static MP_DEFINE_CONST_DICT(audioeffects_echo_locals_dict, audioeffects_echo_locals_dict_table); + +static const audiosample_p_t audioeffects_echo_proto = { + MP_PROTO_IMPLEMENT(MP_QSTR_protocol_audiosample) + .sample_rate = (audiosample_sample_rate_fun)common_hal_audioeffects_echo_get_sample_rate, + .bits_per_sample = (audiosample_bits_per_sample_fun)common_hal_audioeffects_echo_get_bits_per_sample, + .channel_count = (audiosample_channel_count_fun)common_hal_audioeffects_echo_get_channel_count, + .reset_buffer = (audiosample_reset_buffer_fun)audioeffects_echo_reset_buffer, + .get_buffer = (audiosample_get_buffer_fun)audioeffects_echo_get_buffer, + .get_buffer_structure = (audiosample_get_buffer_structure_fun)audioeffects_echo_get_buffer_structure, +}; + +MP_DEFINE_CONST_OBJ_TYPE( + audioeffects_echo_type, + MP_QSTR_Echo, + MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS, + make_new, audioeffects_echo_make_new, + locals_dict, &audioeffects_echo_locals_dict, + protocol, &audioeffects_echo_proto + ); diff --git a/shared-bindings/audioeffects/Echo.h b/shared-bindings/audioeffects/Echo.h new file mode 100644 index 0000000000000..7a6fa9ee3c4f1 --- /dev/null +++ b/shared-bindings/audioeffects/Echo.h @@ -0,0 +1,33 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Mark Komus +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "shared-module/audioeffects/Echo.h" + +extern const mp_obj_type_t audioeffects_echo_type; + +void common_hal_audioeffects_echo_construct(audioeffects_echo_obj_t *self, + uint32_t delay_ms, mp_float_t decay, + uint32_t buffer_size, uint8_t bits_per_sample, bool samples_signed, + uint8_t channel_count, uint32_t sample_rate); + +void common_hal_audioeffects_echo_deinit(audioeffects_echo_obj_t *self); +bool common_hal_audioeffects_echo_deinited(audioeffects_echo_obj_t *self); + +uint32_t common_hal_audioeffects_echo_get_sample_rate(audioeffects_echo_obj_t *self); +uint8_t common_hal_audioeffects_echo_get_channel_count(audioeffects_echo_obj_t *self); +uint8_t common_hal_audioeffects_echo_get_bits_per_sample(audioeffects_echo_obj_t *self); + +uint32_t common_hal_audioeffects_echo_get_delay_ms(audioeffects_echo_obj_t *self); +void common_hal_audioeffects_echo_set_delay_ms(audioeffects_echo_obj_t *self, uint32_t delay_ms); + +mp_float_t common_hal_audioeffects_echo_get_decay(audioeffects_echo_obj_t *self); +void common_hal_audioeffects_echo_set_decay(audioeffects_echo_obj_t *self, mp_float_t decay); + +bool common_hal_audioeffects_echo_get_playing(audioeffects_echo_obj_t *self); +void common_hal_audioeffects_echo_play(audioeffects_echo_obj_t *self, mp_obj_t sample, bool loop); +void common_hal_audioeffects_echo_stop(audioeffects_echo_obj_t *self); diff --git a/shared-bindings/audioeffects/__init__.c b/shared-bindings/audioeffects/__init__.c new file mode 100644 index 0000000000000..f1ead918c0d27 --- /dev/null +++ b/shared-bindings/audioeffects/__init__.c @@ -0,0 +1,33 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Mark Komus +// +// SPDX-License-Identifier: MIT + +#include + +#include "py/obj.h" +#include "py/runtime.h" + +#include "shared-bindings/audioeffects/__init__.h" +#include "shared-bindings/audioeffects/Echo.h" + +//| """Support for audio effects +//| +//| The `audioeffects` module contains classes to provide access to audio effects. +//| +//| """ + +static const mp_rom_map_elem_t audioeffects_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audioeffects) }, + { MP_ROM_QSTR(MP_QSTR_Echo), MP_ROM_PTR(&audioeffects_echo_type) }, +}; + +static MP_DEFINE_CONST_DICT(audioeffects_module_globals, audioeffects_module_globals_table); + +const mp_obj_module_t audioeffects_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&audioeffects_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_audioeffects, audioeffects_module); diff --git a/shared-bindings/audioeffects/__init__.h b/shared-bindings/audioeffects/__init__.h new file mode 100644 index 0000000000000..3bc9246e5bbf0 --- /dev/null +++ b/shared-bindings/audioeffects/__init__.h @@ -0,0 +1,7 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Mark Komus +// +// SPDX-License-Identifier: MIT + +#pragma once diff --git a/shared-module/audioeffects/Echo.c b/shared-module/audioeffects/Echo.c new file mode 100644 index 0000000000000..302fd72a9f818 --- /dev/null +++ b/shared-module/audioeffects/Echo.c @@ -0,0 +1,189 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Mark Komus +// +// SPDX-License-Identifier: MIT +#include "shared-bindings/audioeffects/Echo.h" + +#include +#include "py/runtime.h" +#include "effects_utils.h" + + +void common_hal_audioeffects_echo_construct(audioeffects_echo_obj_t *self, uint32_t delay_ms, mp_float_t decay, uint32_t buffer_size, uint8_t bits_per_sample, + bool samples_signed, uint8_t channel_count, uint32_t sample_rate) { + + self->bits_per_sample = bits_per_sample; + self->samples_signed = samples_signed; + self->channel_count = channel_count; + self->sample_rate = sample_rate; + + // check that buffer_size <= echo_buffer_size + self->buffer_len = buffer_size; + self->buffer = m_malloc(self->buffer_len); + if (self->buffer == NULL) { + common_hal_audioeffects_echo_deinit(self); + m_malloc_fail(self->buffer_len); + } + memset(self->buffer, 0, self->buffer_len); + + self->decay = (uint16_t)(decay * (1 << 15)); + + // calculate buffer size for the set delay + self->delay_ms = delay_ms; + self->echo_buffer_len = self->sample_rate / 1000.0f * self->delay_ms * sizeof(uint32_t); + + self->echo_buffer = m_malloc(self->echo_buffer_len); + if (self->echo_buffer == NULL) { + common_hal_audioeffects_echo_deinit(self); + m_malloc_fail(self->echo_buffer_len); + } + memset(self->echo_buffer, 0, self->echo_buffer_len); + + // read is where we store the incoming sample + // write is what we send to the outgoing buffer + self->echo_buffer_read_pos = self->buffer_len / sizeof(uint32_t); + self->echo_buffer_write_pos = 0; +} + +bool common_hal_audioeffects_echo_deinited(audioeffects_echo_obj_t *self) { + if (self->echo_buffer == NULL) { + return true; + } + return false; +} + +void common_hal_audioeffects_echo_deinit(audioeffects_echo_obj_t *self) { + if (common_hal_audioeffects_echo_deinited(self)) { + return; + } + self->echo_buffer = NULL; + self->buffer = NULL; +} + + +uint32_t common_hal_audioeffects_echo_get_delay_ms(audioeffects_echo_obj_t *self) { + return self->delay_ms; +} + +void common_hal_audioeffects_echo_set_delay_ms(audioeffects_echo_obj_t *self, uint32_t delay_ms) { + self->delay_ms = delay_ms; + self->echo_buffer_len = self->sample_rate / 1000.0f * self->delay_ms * sizeof(uint32_t); + + self->echo_buffer = m_realloc(self->echo_buffer, self->echo_buffer_len); + if (self->echo_buffer == NULL) { + common_hal_audioeffects_echo_deinit(self); + m_malloc_fail(self->echo_buffer_len); + } + + uint32_t max_ebuf_length = self->echo_buffer_len / sizeof(uint32_t); + + if (self->echo_buffer_read_pos > max_ebuf_length) { + self->echo_buffer_read_pos = 0; + self->echo_buffer_write_pos = max_ebuf_length - (self->buffer_len / sizeof(uint32_t)); + } else if (self->echo_buffer_write_pos > max_ebuf_length) { + self->echo_buffer_read_pos = self->buffer_len / sizeof(uint32_t); + self->echo_buffer_write_pos = 0; + } +} + +mp_float_t common_hal_audioeffects_echo_get_decay(audioeffects_echo_obj_t *self) { + return (mp_float_t)self->decay / (1 << 15); +} + +void common_hal_audioeffects_echo_set_decay(audioeffects_echo_obj_t *self, mp_float_t decay) { + self->decay = (uint16_t)(decay * (1 << 15)); +} + +uint32_t common_hal_audioeffects_echo_get_sample_rate(audioeffects_echo_obj_t *self) { + return self->sample_rate; +} + +uint8_t common_hal_audioeffects_echo_get_channel_count(audioeffects_echo_obj_t *self) { + return self->channel_count; +} + +uint8_t common_hal_audioeffects_echo_get_bits_per_sample(audioeffects_echo_obj_t *self) { + return self->bits_per_sample; +} + +void audioeffects_echo_reset_buffer(audioeffects_echo_obj_t *self, + bool single_channel_output, + uint8_t channel) { + +} + +bool common_hal_audioeffects_echo_get_playing(audioeffects_echo_obj_t *self) { + return self->sample != NULL; +} + +void common_hal_audioeffects_echo_play(audioeffects_echo_obj_t *self, mp_obj_t sample, bool loop) { + // check buffer size vs our buffer length + self->sample = sample; + return; +} + +void common_hal_audioeffects_echo_stop(audioeffects_echo_obj_t *self) { + self->sample = NULL; + memset(self->echo_buffer, 0, self->echo_buffer_len); // clear echo + return; +} + +audioio_get_buffer_result_t audioeffects_echo_get_buffer(audioeffects_echo_obj_t *self, bool single_channel_output, uint8_t channel, + uint8_t **buffer, uint32_t *buffer_length) { + + *buffer_length = self->buffer_len; + *buffer = (uint8_t *)self->buffer; + + if (self->sample == NULL) { + + } else { + // Get the sample's buffer + uint32_t *sample_buffer; + uint32_t sample_buffer_len; + + // audioio_get_buffer_result_t result = + audiosample_get_buffer(self->sample, false, 0, (uint8_t **)&sample_buffer, &sample_buffer_len); + + uint32_t n = MIN(sample_buffer_len / sizeof(uint32_t), (self->buffer_len / sizeof(uint32_t))); + *buffer_length = n * sizeof(uint32_t); + + uint32_t echo_buf_len = self->echo_buffer_len / sizeof(uint32_t); + + // pass the sample thru to our buffer adding in the echo + for (uint32_t i = 0; i < n; i++) { + if (self->echo_buffer_read_pos >= echo_buf_len) { + self->echo_buffer_read_pos = 0; + } + + uint32_t echo = self->echo_buffer[self->echo_buffer_read_pos++]; + self->buffer[i] = add16signed(mult16signed(echo, self->decay), sample_buffer[i]); + } + + // copy sample buffer to echo buf to play back later + // This now includes our current sound + previous echos + for (uint32_t i = 0; i < n; i++) { + if (self->echo_buffer_write_pos >= echo_buf_len) { + self->echo_buffer_write_pos = 0; + } + + self->echo_buffer[self->echo_buffer_write_pos++] = add16signed(sample_buffer[i], self->buffer[i]); + } + } + + return GET_BUFFER_MORE_DATA; +} + +void audioeffects_echo_get_buffer_structure(audioeffects_echo_obj_t *self, bool single_channel_output, + bool *single_buffer, bool *samples_signed, uint32_t *max_buffer_length, uint8_t *spacing) { + + if (self->sample != NULL) { + audiosample_get_buffer_structure(self->sample, single_channel_output, single_buffer, samples_signed, max_buffer_length, spacing); + *max_buffer_length = self->buffer_len; + } else { + *single_buffer = false; + *samples_signed = true; + *max_buffer_length = self->buffer_len; + *spacing = 1; + } +} diff --git a/shared-module/audioeffects/Echo.h b/shared-module/audioeffects/Echo.h new file mode 100644 index 0000000000000..ec2b0e331725b --- /dev/null +++ b/shared-module/audioeffects/Echo.h @@ -0,0 +1,47 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Mark Komus +// +// SPDX-License-Identifier: MIT +#pragma once + +#include "py/obj.h" + +#include "shared-module/audiocore/__init__.h" + +extern const mp_obj_type_t audioeffects_echo_type; + +typedef struct { + mp_obj_base_t base; + uint32_t delay_ms; + uint16_t decay; + uint8_t bits_per_sample; + bool samples_signed; + uint8_t channel_count; + uint32_t sample_rate; + + uint32_t *buffer; + uint32_t buffer_len; // buffer in bytes + + uint32_t *echo_buffer; + uint32_t echo_buffer_len; // bytes + + uint32_t echo_buffer_read_pos; // words + uint32_t echo_buffer_write_pos; // words + + mp_obj_t sample; +} audioeffects_echo_obj_t; + +void audioeffects_echo_reset_buffer(audioeffects_echo_obj_t *self, + bool single_channel_output, + uint8_t channel); + +audioio_get_buffer_result_t audioeffects_echo_get_buffer(audioeffects_echo_obj_t *self, + bool single_channel_output, + uint8_t channel, + uint8_t **buffer, + uint32_t *buffer_length); // length in bytes + +void audioeffects_echo_get_buffer_structure(audioeffects_echo_obj_t *self, bool single_channel_output, + bool *single_buffer, bool *samples_signed, + uint32_t *max_buffer_length, uint8_t *spacing); diff --git a/shared-module/audioeffects/__init__.c b/shared-module/audioeffects/__init__.c new file mode 100644 index 0000000000000..7b5abdb4ff317 --- /dev/null +++ b/shared-module/audioeffects/__init__.c @@ -0,0 +1,5 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Mark Komus +// +// SPDX-License-Identifier: MIT diff --git a/shared-module/audioeffects/__init__.h b/shared-module/audioeffects/__init__.h new file mode 100644 index 0000000000000..3bc9246e5bbf0 --- /dev/null +++ b/shared-module/audioeffects/__init__.h @@ -0,0 +1,7 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Mark Komus +// +// SPDX-License-Identifier: MIT + +#pragma once diff --git a/shared-module/audioeffects/effects_utils.c b/shared-module/audioeffects/effects_utils.c new file mode 100644 index 0000000000000..742ab820b47f7 --- /dev/null +++ b/shared-module/audioeffects/effects_utils.c @@ -0,0 +1,8 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Mark Komus +// +// SPDX-License-Identifier: MIT + +#include +#include "py/runtime.h" diff --git a/shared-module/audioeffects/effects_utils.h b/shared-module/audioeffects/effects_utils.h new file mode 100644 index 0000000000000..142ea779f330f --- /dev/null +++ b/shared-module/audioeffects/effects_utils.h @@ -0,0 +1,58 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Mark Komus +// +// SPDX-License-Identifier: MIT + + +__attribute__((always_inline)) +static inline uint32_t add16signed(uint32_t a, uint32_t b) { + #if (defined(__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) + return __QADD16(a, b); + #else + uint32_t result = 0; + for (int8_t i = 0; i < 2; i++) { + int16_t ai = a >> (sizeof(int16_t) * 8 * i); + int16_t bi = b >> (sizeof(int16_t) * 8 * i); + int32_t intermediate = (int32_t)ai + bi; + if (intermediate > SHRT_MAX) { + intermediate = SHRT_MAX; + } else if (intermediate < SHRT_MIN) { + intermediate = SHRT_MIN; + } + result |= (((uint32_t)intermediate) & 0xffff) << (sizeof(int16_t) * 8 * i); + } + return result; + #endif +} + +__attribute__((always_inline)) +static inline uint32_t mult16signed(uint32_t val, int32_t mul) { + #if (defined(__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) + mul <<= 16; + int32_t hi, lo; + enum { bits = 16 }; // saturate to 16 bits + enum { shift = 15 }; // shift is done automatically + asm volatile ("smulwb %0, %1, %2" : "=r" (lo) : "r" (mul), "r" (val)); + asm volatile ("smulwt %0, %1, %2" : "=r" (hi) : "r" (mul), "r" (val)); + asm volatile ("ssat %0, %1, %2, asr %3" : "=r" (lo) : "I" (bits), "r" (lo), "I" (shift)); + asm volatile ("ssat %0, %1, %2, asr %3" : "=r" (hi) : "I" (bits), "r" (hi), "I" (shift)); + asm volatile ("pkhbt %0, %1, %2, lsl #16" : "=r" (val) : "r" (lo), "r" (hi)); // pack + return val; + #else + uint32_t result = 0; + float mod_mul = (float)mul / (float)((1 << 15) - 1); + for (int8_t i = 0; i < 2; i++) { + int16_t ai = (val >> (sizeof(uint16_t) * 8 * i)); + int32_t intermediate = (int32_t)(ai * mod_mul); + if (intermediate > SHRT_MAX) { + intermediate = SHRT_MAX; + } else if (intermediate < SHRT_MIN) { + intermediate = SHRT_MIN; + } + intermediate &= 0x0000FFFF; + result |= (((uint32_t)intermediate)) << (sizeof(int16_t) * 8 * i); + } + return result; + #endif +} From a950349180d97d7255b899377a0e3933f420e1ec Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Sun, 15 Sep 2024 17:39:36 -0500 Subject: [PATCH 02/23] Changes to properly handle samples --- locale/circuitpython.pot | 16 ++ .../raspberry_pi_pico2/mpconfigboard.mk | 1 + shared-module/audioeffects/Echo.c | 166 ++++++++++++++---- shared-module/audioeffects/Echo.h | 6 + shared-module/audioeffects/effects_utils.h | 33 ++++ 5 files changed, 184 insertions(+), 38 deletions(-) diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index f2dbe39b4ead5..07ed2443d3393 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -1956,18 +1956,34 @@ msgstr "" msgid "The length of rgb_pins must be 6, 12, 18, 24, or 30" msgstr "" +#: shared-module/audioeffects/Echo.c +msgid "The sample's bits_per_sample does not match the effect's" +msgstr "" + #: shared-module/audiomixer/MixerVoice.c msgid "The sample's bits_per_sample does not match the mixer's" msgstr "" +#: shared-module/audioeffects/Echo.c +msgid "The sample's channel count does not match the effect's" +msgstr "" + #: shared-module/audiomixer/MixerVoice.c msgid "The sample's channel count does not match the mixer's" msgstr "" +#: shared-module/audioeffects/Echo.c +msgid "The sample's sample rate does not match the effect's" +msgstr "" + #: shared-module/audiomixer/MixerVoice.c msgid "The sample's sample rate does not match the mixer's" msgstr "" +#: shared-module/audioeffects/Echo.c +msgid "The sample's signedness does not match the effect's" +msgstr "" + #: shared-module/audiomixer/MixerVoice.c msgid "The sample's signedness does not match the mixer's" msgstr "" diff --git a/ports/raspberrypi/boards/raspberry_pi_pico2/mpconfigboard.mk b/ports/raspberrypi/boards/raspberry_pi_pico2/mpconfigboard.mk index 7a684716674db..4d31166875f1a 100644 --- a/ports/raspberrypi/boards/raspberry_pi_pico2/mpconfigboard.mk +++ b/ports/raspberrypi/boards/raspberry_pi_pico2/mpconfigboard.mk @@ -10,4 +10,5 @@ CHIP_FAMILY = rp2 EXTERNAL_FLASH_DEVICES = "W25Q32JVxQ" CIRCUITPY__EVE = 1 +CIRCUITPY_AUDIOEFFECTS = 1 CIRCUITPY_ALARM = 0 diff --git a/shared-module/audioeffects/Echo.c b/shared-module/audioeffects/Echo.c index 302fd72a9f818..4817e0d0d24a3 100644 --- a/shared-module/audioeffects/Echo.c +++ b/shared-module/audioeffects/Echo.c @@ -31,7 +31,7 @@ void common_hal_audioeffects_echo_construct(audioeffects_echo_obj_t *self, uint3 // calculate buffer size for the set delay self->delay_ms = delay_ms; - self->echo_buffer_len = self->sample_rate / 1000.0f * self->delay_ms * sizeof(uint32_t); + self->echo_buffer_len = self->sample_rate / 1000.0f * self->delay_ms * (self->channel_count * (self->bits_per_sample / 8)); self->echo_buffer = m_malloc(self->echo_buffer_len); if (self->echo_buffer == NULL) { @@ -44,6 +44,12 @@ void common_hal_audioeffects_echo_construct(audioeffects_echo_obj_t *self, uint3 // write is what we send to the outgoing buffer self->echo_buffer_read_pos = self->buffer_len / sizeof(uint32_t); self->echo_buffer_write_pos = 0; + + self->sample = NULL; + self->sample_remaining_buffer = NULL; + self->sample_buffer_length = 0; + self->loop = false; + self->more_data = false; } bool common_hal_audioeffects_echo_deinited(audioeffects_echo_obj_t *self) { @@ -68,7 +74,7 @@ uint32_t common_hal_audioeffects_echo_get_delay_ms(audioeffects_echo_obj_t *self void common_hal_audioeffects_echo_set_delay_ms(audioeffects_echo_obj_t *self, uint32_t delay_ms) { self->delay_ms = delay_ms; - self->echo_buffer_len = self->sample_rate / 1000.0f * self->delay_ms * sizeof(uint32_t); + self->echo_buffer_len = self->sample_rate / 1000.0f * self->delay_ms * (self->channel_count * (self->bits_per_sample / 8)); self->echo_buffer = m_realloc(self->echo_buffer, self->echo_buffer_len); if (self->echo_buffer == NULL) { @@ -118,59 +124,138 @@ bool common_hal_audioeffects_echo_get_playing(audioeffects_echo_obj_t *self) { } void common_hal_audioeffects_echo_play(audioeffects_echo_obj_t *self, mp_obj_t sample, bool loop) { - // check buffer size vs our buffer length + if (audiosample_sample_rate(sample) != self->sample_rate) { + mp_raise_ValueError(MP_ERROR_TEXT("The sample's sample rate does not match the effect's")); + } + if (audiosample_channel_count(sample) != self->channel_count) { + mp_raise_ValueError(MP_ERROR_TEXT("The sample's channel count does not match the effect's")); + } + if (audiosample_bits_per_sample(sample) != self->bits_per_sample) { + mp_raise_ValueError(MP_ERROR_TEXT("The sample's bits_per_sample does not match the effect's")); + } + 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(MP_ERROR_TEXT("The sample's signedness does not match the effect's")); + } 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 length in terms of words. + self->sample_buffer_length /= sizeof(uint32_t); + self->more_data = result == GET_BUFFER_MORE_DATA; + return; } void common_hal_audioeffects_echo_stop(audioeffects_echo_obj_t *self) { self->sample = NULL; - memset(self->echo_buffer, 0, self->echo_buffer_len); // clear echo + // memset(self->echo_buffer, 0, self->echo_buffer_len); // clear echo + // memset(self->buffer, 0, self->buffer_len); return; } audioio_get_buffer_result_t audioeffects_echo_get_buffer(audioeffects_echo_obj_t *self, bool single_channel_output, uint8_t channel, uint8_t **buffer, uint32_t *buffer_length) { - *buffer_length = self->buffer_len; - *buffer = (uint8_t *)self->buffer; - - if (self->sample == NULL) { - - } else { - // Get the sample's buffer - uint32_t *sample_buffer; - uint32_t sample_buffer_len; - - // audioio_get_buffer_result_t result = - audiosample_get_buffer(self->sample, false, 0, (uint8_t **)&sample_buffer, &sample_buffer_len); - - uint32_t n = MIN(sample_buffer_len / sizeof(uint32_t), (self->buffer_len / sizeof(uint32_t))); - *buffer_length = n * sizeof(uint32_t); - - uint32_t echo_buf_len = self->echo_buffer_len / sizeof(uint32_t); - - // pass the sample thru to our buffer adding in the echo - for (uint32_t i = 0; i < n; i++) { - if (self->echo_buffer_read_pos >= echo_buf_len) { - self->echo_buffer_read_pos = 0; + uint32_t *word_buffer = (uint32_t *)self->buffer; + uint32_t length = self->buffer_len / sizeof(uint32_t); + uint32_t echo_buf_len = self->echo_buffer_len / sizeof(uint32_t); + + while (length != 0) { + if (self->sample_buffer_length == 0) { + if (!self->more_data) { + if (self->loop) { + if (self->sample) { + audiosample_reset_buffer(self->sample, false, 0); + } + } else { + self->sample = NULL; + } + } + if (self->sample) { + // Load another buffer + 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 /= sizeof(uint32_t); + self->more_data = result == GET_BUFFER_MORE_DATA; } - - uint32_t echo = self->echo_buffer[self->echo_buffer_read_pos++]; - self->buffer[i] = add16signed(mult16signed(echo, self->decay), sample_buffer[i]); } - // copy sample buffer to echo buf to play back later - // This now includes our current sound + previous echos - for (uint32_t i = 0; i < n; i++) { - if (self->echo_buffer_write_pos >= echo_buf_len) { - self->echo_buffer_write_pos = 0; + // If we have no sample keep the echo echoing + if (self->sample == NULL) { + if (MP_LIKELY(self->bits_per_sample == 16)) { + if (MP_LIKELY(self->samples_signed)) { + for (uint32_t i = 0; i < length; i++) { + if (self->echo_buffer_read_pos >= echo_buf_len) { + self->echo_buffer_read_pos = 0; + } + + uint32_t echo = self->echo_buffer[self->echo_buffer_read_pos++]; + word_buffer[i] = mult16signed(echo, self->decay); + + if (self->echo_buffer_write_pos >= echo_buf_len) { + self->echo_buffer_write_pos = 0; + } + + self->echo_buffer[self->echo_buffer_write_pos++] = word_buffer[i]; + } + } + } + length = 0; + } else { + uint32_t n = MIN(self->sample_buffer_length, length); + uint32_t *sample_src = self->sample_remaining_buffer; + + if (MP_LIKELY(self->bits_per_sample == 16)) { + if (MP_LIKELY(self->samples_signed)) { + for (uint32_t i = 0; i < n; i++) { + uint32_t sample_word = sample_src[i]; + uint32_t echo = self->echo_buffer[self->echo_buffer_read_pos++]; + word_buffer[i] = add16signed(mult16signed(echo, self->decay), sample_word); + self->echo_buffer[self->echo_buffer_write_pos++] = add16signed(sample_word, word_buffer[i]); + + if (self->echo_buffer_read_pos >= echo_buf_len) { + self->echo_buffer_read_pos = 0; + } + if (self->echo_buffer_write_pos >= echo_buf_len) { + self->echo_buffer_write_pos = 0; + } + } + } else { + // for (uint32_t i = 0; i < n; i++) { + // uint32_t sample_word = sample_src[i]; + // sample_word = tosigned16(sample_word); + // word_buffer[i] = add16signed(mult16signed(sample_word, self->decay), word_buffer[i]); + // } + } + } else { + // uint16_t *hword_buffer = (uint16_t *)word_buffer; + // uint16_t *sample_hsrc = (uint16_t *)sample_src; + // for (uint32_t i = 0; i < n * 2; i++) { + // uint32_t sample_word = unpack8(sample_hsrc[i]); + // if (MP_LIKELY(!self->samples_signed)) { + // sample_word = tosigned16(sample_word); + // } + // sample_word = mult16signed(sample_word, self->decay); + // sample_word = add16signed(sample_word, unpack8(hword_buffer[i])); + // hword_buffer[i] = pack8(sample_word); + // } } - self->echo_buffer[self->echo_buffer_write_pos++] = add16signed(sample_buffer[i], self->buffer[i]); + length -= n; + word_buffer += n; + self->sample_remaining_buffer += n; + self->sample_buffer_length -= n; } } - + *buffer = (uint8_t *)self->buffer; + *buffer_length = self->buffer_len; return GET_BUFFER_MORE_DATA; } @@ -179,11 +264,16 @@ void audioeffects_echo_get_buffer_structure(audioeffects_echo_obj_t *self, bool if (self->sample != NULL) { audiosample_get_buffer_structure(self->sample, single_channel_output, single_buffer, samples_signed, max_buffer_length, spacing); + *single_buffer = false; *max_buffer_length = self->buffer_len; } else { - *single_buffer = false; + *single_buffer = true; *samples_signed = true; *max_buffer_length = self->buffer_len; - *spacing = 1; + if (single_channel_output) { + *spacing = self->channel_count; + } else { + *spacing = 1; + } } } diff --git a/shared-module/audioeffects/Echo.h b/shared-module/audioeffects/Echo.h index ec2b0e331725b..a3931d23506e5 100644 --- a/shared-module/audioeffects/Echo.h +++ b/shared-module/audioeffects/Echo.h @@ -23,6 +23,12 @@ typedef struct { uint32_t *buffer; uint32_t buffer_len; // buffer in bytes + uint32_t *sample_remaining_buffer; + uint32_t sample_buffer_length; + + bool loop; + bool more_data; + uint32_t *echo_buffer; uint32_t echo_buffer_len; // bytes diff --git a/shared-module/audioeffects/effects_utils.h b/shared-module/audioeffects/effects_utils.h index 142ea779f330f..40bea1931dfe3 100644 --- a/shared-module/audioeffects/effects_utils.h +++ b/shared-module/audioeffects/effects_utils.h @@ -56,3 +56,36 @@ static inline uint32_t mult16signed(uint32_t val, int32_t mul) { return result; #endif } + + +static inline uint32_t tounsigned8(uint32_t val) { + #if (defined(__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) + return __UADD8(val, 0x80808080); + #else + return val ^ 0x80808080; + #endif +} + +static inline uint32_t tounsigned16(uint32_t val) { + #if (defined(__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) + return __UADD16(val, 0x80008000); + #else + return val ^ 0x80008000; + #endif +} + +static inline uint32_t tosigned16(uint32_t val) { + #if (defined(__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) + return __UADD16(val, 0x80008000); + #else + return val ^ 0x80008000; + #endif +} + +static inline uint32_t unpack8(uint16_t val) { + return ((val & 0xff00) << 16) | ((val & 0x00ff) << 8); +} + +static inline uint32_t pack8(uint32_t val) { + return ((val & 0xff000000) >> 16) | ((val & 0xff00) >> 8); +} From ac5ccdba7a4f2a883b6397209b60c4a033b3399a Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Tue, 17 Sep 2024 17:00:44 -0500 Subject: [PATCH 03/23] Renamed module --- py/circuitpy_defns.mk | 7 +- py/circuitpy_mpconfig.mk | 4 +- .../{audioeffects => audiodelays}/Echo.c | 138 ++++++++-------- shared-bindings/audiodelays/Echo.h | 33 ++++ shared-bindings/audiodelays/__init__.c | 33 ++++ .../{audioeffects => audiodelays}/__init__.h | 0 shared-bindings/audioeffects/Echo.h | 33 ---- shared-bindings/audioeffects/__init__.c | 33 ---- .../{audioeffects => audiodelays}/Echo.c | 153 ++++++++++-------- .../{audioeffects => audiodelays}/Echo.h | 10 +- .../{audioeffects => audiodelays}/__init__.c | 0 .../{audioeffects => audiodelays}/__init__.h | 0 shared-module/audioeffects/effects_utils.c | 8 - .../effects_utils.h => audiomixer/utils.h} | 3 +- 14 files changed, 232 insertions(+), 223 deletions(-) rename shared-bindings/{audioeffects => audiodelays}/Echo.c (53%) create mode 100644 shared-bindings/audiodelays/Echo.h create mode 100644 shared-bindings/audiodelays/__init__.c rename shared-bindings/{audioeffects => audiodelays}/__init__.h (100%) delete mode 100644 shared-bindings/audioeffects/Echo.h delete mode 100644 shared-bindings/audioeffects/__init__.c rename shared-module/{audioeffects => audiodelays}/Echo.c (56%) rename shared-module/{audioeffects => audiodelays}/Echo.h (76%) rename shared-module/{audioeffects => audiodelays}/__init__.c (100%) rename shared-module/{audioeffects => audiodelays}/__init__.h (100%) delete mode 100644 shared-module/audioeffects/effects_utils.c rename shared-module/{audioeffects/effects_utils.h => audiomixer/utils.h} (96%) diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index 5386ce74d892b..19f7cfabf044d 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -132,7 +132,7 @@ ifeq ($(CIRCUITPY_AUDIOCORE),1) SRC_PATTERNS += audiocore/% endif ifeq ($(CIRCUITPY_AUDIOEFFECTS),1) -SRC_PATTERNS += audioeffects/% +SRC_PATTERNS += audiodelays/% endif ifeq ($(CIRCUITPY_AUDIOMIXER),1) SRC_PATTERNS += audiomixer/% @@ -620,8 +620,8 @@ SRC_SHARED_MODULE_ALL = \ audiocore/RawSample.c \ audiocore/WaveFile.c \ audiocore/__init__.c \ - audioeffects/Echo.c \ - audioeffects/__init__.c \ + audiodelays/Echo.c \ + audiodelays/__init__.c \ audioio/__init__.c \ audiomixer/Mixer.c \ audiomixer/MixerVoice.c \ @@ -862,7 +862,6 @@ $(filter $(SRC_PATTERNS), \ displayio/display_core.c \ os/getenv.c \ usb/utf16le.c \ - audioeffects/effects_utils.c \ ) SRC_COMMON_HAL_INTERNAL = \ diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index de130b3fdbdfa..6a3b29baf32c2 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -141,8 +141,8 @@ CFLAGS += -DCIRCUITPY_AUDIOCORE_DEBUG=$(CIRCUITPY_AUDIOCORE_DEBUG) CIRCUITPY_AUDIOMP3 ?= $(call enable-if-all,$(CIRCUITPY_FULL_BUILD) $(CIRCUITPY_AUDIOCORE)) CFLAGS += -DCIRCUITPY_AUDIOMP3=$(CIRCUITPY_AUDIOMP3) -CIRCUITPY_AUDIOEFFECTS ?= 0 -CFLAGS += -DCIRCUITPY_AUDIOEFFECTS=$(CIRCUITPY_AUDIOEFFECTS) +CIRCUITPY_AUDIODELAYS ?= 0 +CFLAGS += -DCIRCUITPY_AUDIODELAYS=$(CIRCUITPY_AUDIODELAYS) CIRCUITPY_AURORA_EPAPER ?= 0 CFLAGS += -DCIRCUITPY_AURORA_EPAPER=$(CIRCUITPY_AURORA_EPAPER) diff --git a/shared-bindings/audioeffects/Echo.c b/shared-bindings/audiodelays/Echo.c similarity index 53% rename from shared-bindings/audioeffects/Echo.c rename to shared-bindings/audiodelays/Echo.c index bc37b59b64f2d..58f4600d6df7e 100644 --- a/shared-bindings/audioeffects/Echo.c +++ b/shared-bindings/audiodelays/Echo.c @@ -6,8 +6,8 @@ #include -#include "shared-bindings/audioeffects/Echo.h" -#include "shared-module/audioeffects/Echo.h" +#include "shared-bindings/audiodelays/Echo.h" +#include "shared-module/audiodelays/Echo.h" #include "shared/runtime/context_manager_helpers.h" #include "py/binary.h" @@ -20,7 +20,7 @@ //| class Echo: //| """An Echo effect""" //| -static mp_obj_t audioeffects_echo_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { +static mp_obj_t audiodelays_echo_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { enum { ARG_delay_ms, ARG_decay, 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_delay_ms, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 50 } }, @@ -49,8 +49,8 @@ static mp_obj_t audioeffects_echo_make_new(const mp_obj_type_t *type, size_t n_a mp_raise_ValueError(MP_ERROR_TEXT("bits_per_sample must be 8 or 16")); } - audioeffects_echo_obj_t *self = mp_obj_malloc(audioeffects_echo_obj_t, &audioeffects_echo_type); - common_hal_audioeffects_echo_construct(self, delay_ms, decay, args[ARG_buffer_size].u_int, bits_per_sample, args[ARG_samples_signed].u_bool, channel_count, sample_rate); + audiodelays_echo_obj_t *self = mp_obj_malloc(audiodelays_echo_obj_t, &audiodelays_echo_type); + common_hal_audiodelays_echo_construct(self, delay_ms, decay, 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); } @@ -58,15 +58,15 @@ static mp_obj_t audioeffects_echo_make_new(const mp_obj_type_t *type, size_t n_a //| def deinit(self) -> None: //| """Deinitialises the Echo and releases any hardware resources for reuse.""" //| ... -static mp_obj_t audioeffects_echo_deinit(mp_obj_t self_in) { - audioeffects_echo_obj_t *self = MP_OBJ_TO_PTR(self_in); - common_hal_audioeffects_echo_deinit(self); +static mp_obj_t audiodelays_echo_deinit(mp_obj_t self_in) { + audiodelays_echo_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_audiodelays_echo_deinit(self); return mp_const_none; } -static MP_DEFINE_CONST_FUN_OBJ_1(audioeffects_echo_deinit_obj, audioeffects_echo_deinit); +static MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_echo_deinit_obj, audiodelays_echo_deinit); -static void check_for_deinit(audioeffects_echo_obj_t *self) { - if (common_hal_audioeffects_echo_deinited(self)) { +static void check_for_deinit(audiodelays_echo_obj_t *self) { + if (common_hal_audiodelays_echo_deinited(self)) { raise_deinited_error(); } } @@ -80,61 +80,61 @@ static void check_for_deinit(audioeffects_echo_obj_t *self) { //| """Automatically deinitializes the hardware when exiting a context. See //| :ref:`lifetime-and-contextmanagers` for more info.""" //| ... -static mp_obj_t audioeffects_echo_obj___exit__(size_t n_args, const mp_obj_t *args) { +static mp_obj_t audiodelays_echo_obj___exit__(size_t n_args, const mp_obj_t *args) { (void)n_args; - common_hal_audioeffects_echo_deinit(args[0]); + common_hal_audiodelays_echo_deinit(args[0]); return mp_const_none; } -static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audioeffects_echo___exit___obj, 4, 4, audioeffects_echo_obj___exit__); +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audiodelays_echo___exit___obj, 4, 4, audiodelays_echo_obj___exit__); //| delay_ms: int //| """Delay of the echo in microseconds. (read-only)""" //| -static mp_obj_t audioeffects_echo_obj_get_delay_ms(mp_obj_t self_in) { - audioeffects_echo_obj_t *self = MP_OBJ_TO_PTR(self_in); +static mp_obj_t audiodelays_echo_obj_get_delay_ms(mp_obj_t self_in) { + audiodelays_echo_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_float(common_hal_audioeffects_echo_get_delay_ms(self)); + return mp_obj_new_float(common_hal_audiodelays_echo_get_delay_ms(self)); } -MP_DEFINE_CONST_FUN_OBJ_1(audioeffects_echo_get_delay_ms_obj, audioeffects_echo_obj_get_delay_ms); +MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_echo_get_delay_ms_obj, audiodelays_echo_obj_get_delay_ms); -static mp_obj_t audioeffects_echo_obj_set_delay_ms(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { +static mp_obj_t audiodelays_echo_obj_set_delay_ms(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_delay_ms }; static const mp_arg_t allowed_args[] = { { MP_QSTR_delay_ms, MP_ARG_INT | MP_ARG_REQUIRED, {} }, }; - audioeffects_echo_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + audiodelays_echo_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); 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_int_t delay_ms = mp_arg_validate_int_range(args[ARG_delay_ms].u_int, 1, 4000, MP_QSTR_delay_ms); - common_hal_audioeffects_echo_set_delay_ms(self, delay_ms); + common_hal_audiodelays_echo_set_delay_ms(self, delay_ms); return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_KW(audioeffects_echo_set_delay_ms_obj, 1, audioeffects_echo_obj_set_delay_ms); +MP_DEFINE_CONST_FUN_OBJ_KW(audiodelays_echo_set_delay_ms_obj, 1, audiodelays_echo_obj_set_delay_ms); -MP_PROPERTY_GETSET(audioeffects_echo_delay_ms_obj, - (mp_obj_t)&audioeffects_echo_get_delay_ms_obj, - (mp_obj_t)&audioeffects_echo_set_delay_ms_obj); +MP_PROPERTY_GETSET(audiodelays_echo_delay_ms_obj, + (mp_obj_t)&audiodelays_echo_get_delay_ms_obj, + (mp_obj_t)&audiodelays_echo_set_delay_ms_obj); //| decay: float //| """The rate the echo decays between 0 and 1.""" -static mp_obj_t audioeffects_echo_obj_get_decay(mp_obj_t self_in) { - return mp_obj_new_float(common_hal_audioeffects_echo_get_decay(self_in)); +static mp_obj_t audiodelays_echo_obj_get_decay(mp_obj_t self_in) { + return mp_obj_new_float(common_hal_audiodelays_echo_get_decay(self_in)); } -MP_DEFINE_CONST_FUN_OBJ_1(audioeffects_echo_get_decay_obj, audioeffects_echo_obj_get_decay); +MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_echo_get_decay_obj, audiodelays_echo_obj_get_decay); -static mp_obj_t audioeffects_echo_obj_set_decay(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { +static mp_obj_t audiodelays_echo_obj_set_decay(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_decay }; static const mp_arg_t allowed_args[] = { { MP_QSTR_decay, MP_ARG_OBJ | MP_ARG_REQUIRED, {} }, }; - audioeffects_echo_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + audiodelays_echo_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); 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); @@ -144,28 +144,28 @@ static mp_obj_t audioeffects_echo_obj_set_decay(size_t n_args, const mp_obj_t *p mp_raise_ValueError(MP_ERROR_TEXT("decay must be between 0 and 1")); } - common_hal_audioeffects_echo_set_decay(self, decay); + common_hal_audiodelays_echo_set_decay(self, decay); return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_KW(audioeffects_echo_set_decay_obj, 1, audioeffects_echo_obj_set_decay); +MP_DEFINE_CONST_FUN_OBJ_KW(audiodelays_echo_set_decay_obj, 1, audiodelays_echo_obj_set_decay); -MP_PROPERTY_GETSET(audioeffects_echo_decay_obj, - (mp_obj_t)&audioeffects_echo_get_decay_obj, - (mp_obj_t)&audioeffects_echo_set_decay_obj); +MP_PROPERTY_GETSET(audiodelays_echo_decay_obj, + (mp_obj_t)&audiodelays_echo_get_decay_obj, + (mp_obj_t)&audiodelays_echo_set_decay_obj); //| playing: bool //| """True when any voice is being output. (read-only)""" -static mp_obj_t audioeffects_echo_obj_get_playing(mp_obj_t self_in) { - audioeffects_echo_obj_t *self = MP_OBJ_TO_PTR(self_in); +static mp_obj_t audiodelays_echo_obj_get_playing(mp_obj_t self_in) { + audiodelays_echo_obj_t *self = MP_OBJ_TO_PTR(self_in); check_for_deinit(self); - return mp_obj_new_bool(common_hal_audioeffects_echo_get_playing(self)); + return mp_obj_new_bool(common_hal_audiodelays_echo_get_playing(self)); } -MP_DEFINE_CONST_FUN_OBJ_1(audioeffects_echo_get_playing_obj, audioeffects_echo_obj_get_playing); +MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_echo_get_playing_obj, audiodelays_echo_obj_get_playing); -MP_PROPERTY_GETTER(audioeffects_echo_playing_obj, - (mp_obj_t)&audioeffects_echo_get_playing_obj); +MP_PROPERTY_GETTER(audiodelays_echo_playing_obj, + (mp_obj_t)&audiodelays_echo_get_playing_obj); //| def play( //| self, sample: circuitpython_typing.AudioSample, *, voice: int = 0, loop: bool = False @@ -177,69 +177,69 @@ MP_PROPERTY_GETTER(audioeffects_echo_playing_obj, //| //| The sample must match the Effect's encoding settings given in the constructor.""" //| ... -static mp_obj_t audioeffects_echo_obj_play(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { +static mp_obj_t audiodelays_echo_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} }, }; - audioeffects_echo_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + audiodelays_echo_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_audioeffects_echo_play(self, sample, args[ARG_loop].u_bool); + common_hal_audiodelays_echo_play(self, sample, args[ARG_loop].u_bool); return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_KW(audioeffects_echo_play_obj, 1, audioeffects_echo_obj_play); +MP_DEFINE_CONST_FUN_OBJ_KW(audiodelays_echo_play_obj, 1, audiodelays_echo_obj_play); //| def stop_voice(self, voice: int = 0) -> None: //| """Stops playback of the sample.""" //| ... //| -static mp_obj_t audioeffects_echo_obj_stop(mp_obj_t self_in) { - audioeffects_echo_obj_t *self = MP_OBJ_TO_PTR(self_in); +static mp_obj_t audiodelays_echo_obj_stop(mp_obj_t self_in) { + audiodelays_echo_obj_t *self = MP_OBJ_TO_PTR(self_in); - common_hal_audioeffects_echo_stop(self); + common_hal_audiodelays_echo_stop(self); return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_1(audioeffects_echo_stop_obj, audioeffects_echo_obj_stop); +MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_echo_stop_obj, audiodelays_echo_obj_stop); -static const mp_rom_map_elem_t audioeffects_echo_locals_dict_table[] = { +static const mp_rom_map_elem_t audiodelays_echo_locals_dict_table[] = { // Methods - { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audioeffects_echo_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiodelays_echo_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, - { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&audioeffects_echo___exit___obj) }, - { MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audioeffects_echo_play_obj) }, - { MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&audioeffects_echo_stop_obj) }, + { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&audiodelays_echo___exit___obj) }, + { MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audiodelays_echo_play_obj) }, + { MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&audiodelays_echo_stop_obj) }, // Properties - { MP_ROM_QSTR(MP_QSTR_playing), MP_ROM_PTR(&audioeffects_echo_playing_obj) }, - { MP_ROM_QSTR(MP_QSTR_delay_ms), MP_ROM_PTR(&audioeffects_echo_delay_ms_obj) }, - { MP_ROM_QSTR(MP_QSTR_decay), MP_ROM_PTR(&audioeffects_echo_decay_obj) }, + { MP_ROM_QSTR(MP_QSTR_playing), MP_ROM_PTR(&audiodelays_echo_playing_obj) }, + { MP_ROM_QSTR(MP_QSTR_delay_ms), MP_ROM_PTR(&audiodelays_echo_delay_ms_obj) }, + { MP_ROM_QSTR(MP_QSTR_decay), MP_ROM_PTR(&audiodelays_echo_decay_obj) }, }; -static MP_DEFINE_CONST_DICT(audioeffects_echo_locals_dict, audioeffects_echo_locals_dict_table); +static MP_DEFINE_CONST_DICT(audiodelays_echo_locals_dict, audiodelays_echo_locals_dict_table); -static const audiosample_p_t audioeffects_echo_proto = { +static const audiosample_p_t audiodelays_echo_proto = { MP_PROTO_IMPLEMENT(MP_QSTR_protocol_audiosample) - .sample_rate = (audiosample_sample_rate_fun)common_hal_audioeffects_echo_get_sample_rate, - .bits_per_sample = (audiosample_bits_per_sample_fun)common_hal_audioeffects_echo_get_bits_per_sample, - .channel_count = (audiosample_channel_count_fun)common_hal_audioeffects_echo_get_channel_count, - .reset_buffer = (audiosample_reset_buffer_fun)audioeffects_echo_reset_buffer, - .get_buffer = (audiosample_get_buffer_fun)audioeffects_echo_get_buffer, - .get_buffer_structure = (audiosample_get_buffer_structure_fun)audioeffects_echo_get_buffer_structure, + .sample_rate = (audiosample_sample_rate_fun)common_hal_audiodelays_echo_get_sample_rate, + .bits_per_sample = (audiosample_bits_per_sample_fun)common_hal_audiodelays_echo_get_bits_per_sample, + .channel_count = (audiosample_channel_count_fun)common_hal_audiodelays_echo_get_channel_count, + .reset_buffer = (audiosample_reset_buffer_fun)audiodelays_echo_reset_buffer, + .get_buffer = (audiosample_get_buffer_fun)audiodelays_echo_get_buffer, + .get_buffer_structure = (audiosample_get_buffer_structure_fun)audiodelays_echo_get_buffer_structure, }; MP_DEFINE_CONST_OBJ_TYPE( - audioeffects_echo_type, + audiodelays_echo_type, MP_QSTR_Echo, MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS, - make_new, audioeffects_echo_make_new, - locals_dict, &audioeffects_echo_locals_dict, - protocol, &audioeffects_echo_proto + make_new, audiodelays_echo_make_new, + locals_dict, &audiodelays_echo_locals_dict, + protocol, &audiodelays_echo_proto ); diff --git a/shared-bindings/audiodelays/Echo.h b/shared-bindings/audiodelays/Echo.h new file mode 100644 index 0000000000000..a303529c677ed --- /dev/null +++ b/shared-bindings/audiodelays/Echo.h @@ -0,0 +1,33 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Mark Komus +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "shared-module/audiodelays/Echo.h" + +extern const mp_obj_type_t audiodelays_echo_type; + +void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, + uint32_t delay_ms, mp_float_t decay, + uint32_t buffer_size, uint8_t bits_per_sample, bool samples_signed, + uint8_t channel_count, uint32_t sample_rate); + +void common_hal_audiodelays_echo_deinit(audiodelays_echo_obj_t *self); +bool common_hal_audiodelays_echo_deinited(audiodelays_echo_obj_t *self); + +uint32_t common_hal_audiodelays_echo_get_sample_rate(audiodelays_echo_obj_t *self); +uint8_t common_hal_audiodelays_echo_get_channel_count(audiodelays_echo_obj_t *self); +uint8_t common_hal_audiodelays_echo_get_bits_per_sample(audiodelays_echo_obj_t *self); + +uint32_t common_hal_audiodelays_echo_get_delay_ms(audiodelays_echo_obj_t *self); +void common_hal_audiodelays_echo_set_delay_ms(audiodelays_echo_obj_t *self, uint32_t delay_ms); + +mp_float_t common_hal_audiodelays_echo_get_decay(audiodelays_echo_obj_t *self); +void common_hal_audiodelays_echo_set_decay(audiodelays_echo_obj_t *self, mp_float_t decay); + +bool common_hal_audiodelays_echo_get_playing(audiodelays_echo_obj_t *self); +void common_hal_audiodelays_echo_play(audiodelays_echo_obj_t *self, mp_obj_t sample, bool loop); +void common_hal_audiodelays_echo_stop(audiodelays_echo_obj_t *self); diff --git a/shared-bindings/audiodelays/__init__.c b/shared-bindings/audiodelays/__init__.c new file mode 100644 index 0000000000000..4b0be7b75ad86 --- /dev/null +++ b/shared-bindings/audiodelays/__init__.c @@ -0,0 +1,33 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Mark Komus +// +// SPDX-License-Identifier: MIT + +#include + +#include "py/obj.h" +#include "py/runtime.h" + +#include "shared-bindings/audiodelays/__init__.h" +#include "shared-bindings/audiodelays/Echo.h" + +//| """Support for audio delay effects +//| +//| The `audiodelays` module contains classes to provide access to audio delay effects. +//| +//| """ + +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) }, +}; + +static MP_DEFINE_CONST_DICT(audiodelays_module_globals, audiodelays_module_globals_table); + +const mp_obj_module_t audiodelays_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&audiodelays_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_audiodelays, audiodelays_module); diff --git a/shared-bindings/audioeffects/__init__.h b/shared-bindings/audiodelays/__init__.h similarity index 100% rename from shared-bindings/audioeffects/__init__.h rename to shared-bindings/audiodelays/__init__.h diff --git a/shared-bindings/audioeffects/Echo.h b/shared-bindings/audioeffects/Echo.h deleted file mode 100644 index 7a6fa9ee3c4f1..0000000000000 --- a/shared-bindings/audioeffects/Echo.h +++ /dev/null @@ -1,33 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2024 Mark Komus -// -// SPDX-License-Identifier: MIT - -#pragma once - -#include "shared-module/audioeffects/Echo.h" - -extern const mp_obj_type_t audioeffects_echo_type; - -void common_hal_audioeffects_echo_construct(audioeffects_echo_obj_t *self, - uint32_t delay_ms, mp_float_t decay, - uint32_t buffer_size, uint8_t bits_per_sample, bool samples_signed, - uint8_t channel_count, uint32_t sample_rate); - -void common_hal_audioeffects_echo_deinit(audioeffects_echo_obj_t *self); -bool common_hal_audioeffects_echo_deinited(audioeffects_echo_obj_t *self); - -uint32_t common_hal_audioeffects_echo_get_sample_rate(audioeffects_echo_obj_t *self); -uint8_t common_hal_audioeffects_echo_get_channel_count(audioeffects_echo_obj_t *self); -uint8_t common_hal_audioeffects_echo_get_bits_per_sample(audioeffects_echo_obj_t *self); - -uint32_t common_hal_audioeffects_echo_get_delay_ms(audioeffects_echo_obj_t *self); -void common_hal_audioeffects_echo_set_delay_ms(audioeffects_echo_obj_t *self, uint32_t delay_ms); - -mp_float_t common_hal_audioeffects_echo_get_decay(audioeffects_echo_obj_t *self); -void common_hal_audioeffects_echo_set_decay(audioeffects_echo_obj_t *self, mp_float_t decay); - -bool common_hal_audioeffects_echo_get_playing(audioeffects_echo_obj_t *self); -void common_hal_audioeffects_echo_play(audioeffects_echo_obj_t *self, mp_obj_t sample, bool loop); -void common_hal_audioeffects_echo_stop(audioeffects_echo_obj_t *self); diff --git a/shared-bindings/audioeffects/__init__.c b/shared-bindings/audioeffects/__init__.c deleted file mode 100644 index f1ead918c0d27..0000000000000 --- a/shared-bindings/audioeffects/__init__.c +++ /dev/null @@ -1,33 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2024 Mark Komus -// -// SPDX-License-Identifier: MIT - -#include - -#include "py/obj.h" -#include "py/runtime.h" - -#include "shared-bindings/audioeffects/__init__.h" -#include "shared-bindings/audioeffects/Echo.h" - -//| """Support for audio effects -//| -//| The `audioeffects` module contains classes to provide access to audio effects. -//| -//| """ - -static const mp_rom_map_elem_t audioeffects_module_globals_table[] = { - { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audioeffects) }, - { MP_ROM_QSTR(MP_QSTR_Echo), MP_ROM_PTR(&audioeffects_echo_type) }, -}; - -static MP_DEFINE_CONST_DICT(audioeffects_module_globals, audioeffects_module_globals_table); - -const mp_obj_module_t audioeffects_module = { - .base = { &mp_type_module }, - .globals = (mp_obj_dict_t *)&audioeffects_module_globals, -}; - -MP_REGISTER_MODULE(MP_QSTR_audioeffects, audioeffects_module); diff --git a/shared-module/audioeffects/Echo.c b/shared-module/audiodelays/Echo.c similarity index 56% rename from shared-module/audioeffects/Echo.c rename to shared-module/audiodelays/Echo.c index 4817e0d0d24a3..5212d8214a606 100644 --- a/shared-module/audioeffects/Echo.c +++ b/shared-module/audiodelays/Echo.c @@ -3,14 +3,14 @@ // SPDX-FileCopyrightText: Copyright (c) 2024 Mark Komus // // SPDX-License-Identifier: MIT -#include "shared-bindings/audioeffects/Echo.h" +#include "shared-bindings/audiodelays/Echo.h" #include #include "py/runtime.h" -#include "effects_utils.h" +#include "shared-module/audiomixer/utils.h" -void common_hal_audioeffects_echo_construct(audioeffects_echo_obj_t *self, uint32_t delay_ms, mp_float_t decay, uint32_t buffer_size, uint8_t bits_per_sample, +void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_t delay_ms, mp_float_t decay, uint32_t buffer_size, uint8_t bits_per_sample, bool samples_signed, uint8_t channel_count, uint32_t sample_rate) { self->bits_per_sample = bits_per_sample; @@ -22,7 +22,7 @@ void common_hal_audioeffects_echo_construct(audioeffects_echo_obj_t *self, uint3 self->buffer_len = buffer_size; self->buffer = m_malloc(self->buffer_len); if (self->buffer == NULL) { - common_hal_audioeffects_echo_deinit(self); + common_hal_audiodelays_echo_deinit(self); m_malloc_fail(self->buffer_len); } memset(self->buffer, 0, self->buffer_len); @@ -35,7 +35,7 @@ void common_hal_audioeffects_echo_construct(audioeffects_echo_obj_t *self, uint3 self->echo_buffer = m_malloc(self->echo_buffer_len); if (self->echo_buffer == NULL) { - common_hal_audioeffects_echo_deinit(self); + common_hal_audiodelays_echo_deinit(self); m_malloc_fail(self->echo_buffer_len); } memset(self->echo_buffer, 0, self->echo_buffer_len); @@ -52,15 +52,15 @@ void common_hal_audioeffects_echo_construct(audioeffects_echo_obj_t *self, uint3 self->more_data = false; } -bool common_hal_audioeffects_echo_deinited(audioeffects_echo_obj_t *self) { +bool common_hal_audiodelays_echo_deinited(audiodelays_echo_obj_t *self) { if (self->echo_buffer == NULL) { return true; } return false; } -void common_hal_audioeffects_echo_deinit(audioeffects_echo_obj_t *self) { - if (common_hal_audioeffects_echo_deinited(self)) { +void common_hal_audiodelays_echo_deinit(audiodelays_echo_obj_t *self) { + if (common_hal_audiodelays_echo_deinited(self)) { return; } self->echo_buffer = NULL; @@ -68,17 +68,17 @@ void common_hal_audioeffects_echo_deinit(audioeffects_echo_obj_t *self) { } -uint32_t common_hal_audioeffects_echo_get_delay_ms(audioeffects_echo_obj_t *self) { +uint32_t common_hal_audiodelays_echo_get_delay_ms(audiodelays_echo_obj_t *self) { return self->delay_ms; } -void common_hal_audioeffects_echo_set_delay_ms(audioeffects_echo_obj_t *self, uint32_t delay_ms) { +void common_hal_audiodelays_echo_set_delay_ms(audiodelays_echo_obj_t *self, uint32_t delay_ms) { self->delay_ms = delay_ms; self->echo_buffer_len = self->sample_rate / 1000.0f * self->delay_ms * (self->channel_count * (self->bits_per_sample / 8)); self->echo_buffer = m_realloc(self->echo_buffer, self->echo_buffer_len); if (self->echo_buffer == NULL) { - common_hal_audioeffects_echo_deinit(self); + common_hal_audiodelays_echo_deinit(self); m_malloc_fail(self->echo_buffer_len); } @@ -93,37 +93,37 @@ void common_hal_audioeffects_echo_set_delay_ms(audioeffects_echo_obj_t *self, ui } } -mp_float_t common_hal_audioeffects_echo_get_decay(audioeffects_echo_obj_t *self) { +mp_float_t common_hal_audiodelays_echo_get_decay(audiodelays_echo_obj_t *self) { return (mp_float_t)self->decay / (1 << 15); } -void common_hal_audioeffects_echo_set_decay(audioeffects_echo_obj_t *self, mp_float_t decay) { +void common_hal_audiodelays_echo_set_decay(audiodelays_echo_obj_t *self, mp_float_t decay) { self->decay = (uint16_t)(decay * (1 << 15)); } -uint32_t common_hal_audioeffects_echo_get_sample_rate(audioeffects_echo_obj_t *self) { +uint32_t common_hal_audiodelays_echo_get_sample_rate(audiodelays_echo_obj_t *self) { return self->sample_rate; } -uint8_t common_hal_audioeffects_echo_get_channel_count(audioeffects_echo_obj_t *self) { +uint8_t common_hal_audiodelays_echo_get_channel_count(audiodelays_echo_obj_t *self) { return self->channel_count; } -uint8_t common_hal_audioeffects_echo_get_bits_per_sample(audioeffects_echo_obj_t *self) { +uint8_t common_hal_audiodelays_echo_get_bits_per_sample(audiodelays_echo_obj_t *self) { return self->bits_per_sample; } -void audioeffects_echo_reset_buffer(audioeffects_echo_obj_t *self, +void audiodelays_echo_reset_buffer(audiodelays_echo_obj_t *self, bool single_channel_output, uint8_t channel) { } -bool common_hal_audioeffects_echo_get_playing(audioeffects_echo_obj_t *self) { +bool common_hal_audiodelays_echo_get_playing(audiodelays_echo_obj_t *self) { return self->sample != NULL; } -void common_hal_audioeffects_echo_play(audioeffects_echo_obj_t *self, mp_obj_t sample, bool loop) { +void common_hal_audiodelays_echo_play(audiodelays_echo_obj_t *self, mp_obj_t sample, bool loop) { if (audiosample_sample_rate(sample) != self->sample_rate) { mp_raise_ValueError(MP_ERROR_TEXT("The sample's sample rate does not match the effect's")); } @@ -153,14 +153,14 @@ void common_hal_audioeffects_echo_play(audioeffects_echo_obj_t *self, mp_obj_t s return; } -void common_hal_audioeffects_echo_stop(audioeffects_echo_obj_t *self) { +void common_hal_audiodelays_echo_stop(audiodelays_echo_obj_t *self) { self->sample = NULL; // memset(self->echo_buffer, 0, self->echo_buffer_len); // clear echo // memset(self->buffer, 0, self->buffer_len); return; } -audioio_get_buffer_result_t audioeffects_echo_get_buffer(audioeffects_echo_obj_t *self, bool single_channel_output, uint8_t channel, +audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t *self, bool single_channel_output, uint8_t channel, uint8_t **buffer, uint32_t *buffer_length) { uint32_t *word_buffer = (uint32_t *)self->buffer; @@ -190,62 +190,81 @@ audioio_get_buffer_result_t audioeffects_echo_get_buffer(audioeffects_echo_obj_t // If we have no sample keep the echo echoing if (self->sample == NULL) { if (MP_LIKELY(self->bits_per_sample == 16)) { - if (MP_LIKELY(self->samples_signed)) { - for (uint32_t i = 0; i < length; i++) { - if (self->echo_buffer_read_pos >= echo_buf_len) { - self->echo_buffer_read_pos = 0; - } - - uint32_t echo = self->echo_buffer[self->echo_buffer_read_pos++]; - word_buffer[i] = mult16signed(echo, self->decay); - - if (self->echo_buffer_write_pos >= echo_buf_len) { - self->echo_buffer_write_pos = 0; - } + // sample signed/unsigned won't matter as we have no sample + for (uint32_t i = 0; i < length; i++) { + uint32_t echo = self->echo_buffer[self->echo_buffer_read_pos++]; + word_buffer[i] = mult16signed(echo, self->decay); + + self->echo_buffer[self->echo_buffer_write_pos++] = word_buffer[i]; + if (self->echo_buffer_read_pos >= echo_buf_len) { + self->echo_buffer_read_pos = 0; + } + if (self->echo_buffer_write_pos >= echo_buf_len) { + self->echo_buffer_write_pos = 0; + } - self->echo_buffer[self->echo_buffer_write_pos++] = word_buffer[i]; + } + } else { // bits per sample is 8 + uint16_t *hword_buffer = (uint16_t *)word_buffer; + uint16_t *echo_hsrc = (uint16_t *)self->echo_buffer; + for (uint32_t i = 0; i < length * 2; i++) { + uint32_t echo_word = unpack8(echo_hsrc[i]); + echo_word = mult16signed(echo_word, self->decay); + hword_buffer[i] = pack8(echo_word); + + echo_hsrc[self->echo_buffer_write_pos++] = hword_buffer[i]; + if (self->echo_buffer_read_pos >= echo_buf_len) { + self->echo_buffer_read_pos = 0; + } + if (self->echo_buffer_write_pos >= echo_buf_len) { + self->echo_buffer_write_pos = 0; } } } length = 0; - } else { + } else { // we have a sample uint32_t n = MIN(self->sample_buffer_length, length); uint32_t *sample_src = self->sample_remaining_buffer; if (MP_LIKELY(self->bits_per_sample == 16)) { - if (MP_LIKELY(self->samples_signed)) { - for (uint32_t i = 0; i < n; i++) { - uint32_t sample_word = sample_src[i]; - uint32_t echo = self->echo_buffer[self->echo_buffer_read_pos++]; - word_buffer[i] = add16signed(mult16signed(echo, self->decay), sample_word); - self->echo_buffer[self->echo_buffer_write_pos++] = add16signed(sample_word, word_buffer[i]); - - if (self->echo_buffer_read_pos >= echo_buf_len) { - self->echo_buffer_read_pos = 0; - } - if (self->echo_buffer_write_pos >= echo_buf_len) { - self->echo_buffer_write_pos = 0; - } + for (uint32_t i = 0; i < n; i++) { + uint32_t sample_word = sample_src[i]; + if (MP_LIKELY(!self->samples_signed)) { + sample_word = tosigned16(sample_word); + } + uint32_t echo = self->echo_buffer[self->echo_buffer_read_pos++]; + word_buffer[i] = add16signed(mult16signed(echo, self->decay), sample_word); + self->echo_buffer[self->echo_buffer_write_pos++] = word_buffer[i]; + + if (self->echo_buffer_read_pos >= echo_buf_len) { + self->echo_buffer_read_pos = 0; + } + if (self->echo_buffer_write_pos >= echo_buf_len) { + self->echo_buffer_write_pos = 0; + } + } + } else { // bits per sample is 8 + uint16_t *hword_buffer = (uint16_t *)word_buffer; + uint16_t *sample_hsrc = (uint16_t *)sample_src; + uint16_t *echo_hsrc = (uint16_t *)self->echo_buffer; + for (uint32_t i = 0; i < n * 2; i++) { + uint32_t sample_word = unpack8(sample_hsrc[i]); + uint32_t echo_word = unpack8(echo_hsrc[i]); + if (MP_LIKELY(!self->samples_signed)) { + sample_word = tosigned16(sample_word); + } + echo_word = mult16signed(echo_word, self->decay); + sample_word = add16signed(sample_word, echo_word); + hword_buffer[i] = pack8(sample_word); + + echo_hsrc[self->echo_buffer_write_pos++] = pack8(add16signed(sample_word, unpack8(hword_buffer[i]))); + if (self->echo_buffer_read_pos >= echo_buf_len) { + self->echo_buffer_read_pos = 0; + } + if (self->echo_buffer_write_pos >= echo_buf_len) { + self->echo_buffer_write_pos = 0; } - } else { - // for (uint32_t i = 0; i < n; i++) { - // uint32_t sample_word = sample_src[i]; - // sample_word = tosigned16(sample_word); - // word_buffer[i] = add16signed(mult16signed(sample_word, self->decay), word_buffer[i]); - // } } - } else { - // uint16_t *hword_buffer = (uint16_t *)word_buffer; - // uint16_t *sample_hsrc = (uint16_t *)sample_src; - // for (uint32_t i = 0; i < n * 2; i++) { - // uint32_t sample_word = unpack8(sample_hsrc[i]); - // if (MP_LIKELY(!self->samples_signed)) { - // sample_word = tosigned16(sample_word); - // } - // sample_word = mult16signed(sample_word, self->decay); - // sample_word = add16signed(sample_word, unpack8(hword_buffer[i])); - // hword_buffer[i] = pack8(sample_word); - // } } length -= n; @@ -259,7 +278,7 @@ audioio_get_buffer_result_t audioeffects_echo_get_buffer(audioeffects_echo_obj_t return GET_BUFFER_MORE_DATA; } -void audioeffects_echo_get_buffer_structure(audioeffects_echo_obj_t *self, bool single_channel_output, +void audiodelays_echo_get_buffer_structure(audiodelays_echo_obj_t *self, bool single_channel_output, bool *single_buffer, bool *samples_signed, uint32_t *max_buffer_length, uint8_t *spacing) { if (self->sample != NULL) { diff --git a/shared-module/audioeffects/Echo.h b/shared-module/audiodelays/Echo.h similarity index 76% rename from shared-module/audioeffects/Echo.h rename to shared-module/audiodelays/Echo.h index a3931d23506e5..262fdf8d3a3ea 100644 --- a/shared-module/audioeffects/Echo.h +++ b/shared-module/audiodelays/Echo.h @@ -9,7 +9,7 @@ #include "shared-module/audiocore/__init__.h" -extern const mp_obj_type_t audioeffects_echo_type; +extern const mp_obj_type_t audiodelays_echo_type; typedef struct { mp_obj_base_t base; @@ -36,18 +36,18 @@ typedef struct { uint32_t echo_buffer_write_pos; // words mp_obj_t sample; -} audioeffects_echo_obj_t; +} audiodelays_echo_obj_t; -void audioeffects_echo_reset_buffer(audioeffects_echo_obj_t *self, +void audiodelays_echo_reset_buffer(audiodelays_echo_obj_t *self, bool single_channel_output, uint8_t channel); -audioio_get_buffer_result_t audioeffects_echo_get_buffer(audioeffects_echo_obj_t *self, +audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t *self, bool single_channel_output, uint8_t channel, uint8_t **buffer, uint32_t *buffer_length); // length in bytes -void audioeffects_echo_get_buffer_structure(audioeffects_echo_obj_t *self, bool single_channel_output, +void audiodelays_echo_get_buffer_structure(audiodelays_echo_obj_t *self, bool single_channel_output, bool *single_buffer, bool *samples_signed, uint32_t *max_buffer_length, uint8_t *spacing); diff --git a/shared-module/audioeffects/__init__.c b/shared-module/audiodelays/__init__.c similarity index 100% rename from shared-module/audioeffects/__init__.c rename to shared-module/audiodelays/__init__.c diff --git a/shared-module/audioeffects/__init__.h b/shared-module/audiodelays/__init__.h similarity index 100% rename from shared-module/audioeffects/__init__.h rename to shared-module/audiodelays/__init__.h diff --git a/shared-module/audioeffects/effects_utils.c b/shared-module/audioeffects/effects_utils.c deleted file mode 100644 index 742ab820b47f7..0000000000000 --- a/shared-module/audioeffects/effects_utils.c +++ /dev/null @@ -1,8 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2024 Mark Komus -// -// SPDX-License-Identifier: MIT - -#include -#include "py/runtime.h" diff --git a/shared-module/audioeffects/effects_utils.h b/shared-module/audiomixer/utils.h similarity index 96% rename from shared-module/audioeffects/effects_utils.h rename to shared-module/audiomixer/utils.h index 40bea1931dfe3..f7dbdac7dd382 100644 --- a/shared-module/audioeffects/effects_utils.h +++ b/shared-module/audiomixer/utils.h @@ -1,10 +1,9 @@ // This file is part of the CircuitPython project: https://circuitpython.org // -// SPDX-FileCopyrightText: Copyright (c) 2024 Mark Komus +// SPDX-FileCopyrightText: Copyright (c) 2018 Scott Shawcroft for Adafruit Industries, 2024 Mark Komus // // SPDX-License-Identifier: MIT - __attribute__((always_inline)) static inline uint32_t add16signed(uint32_t a, uint32_t b) { #if (defined(__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) From 4da22c5634011d3f1a9d5f03fce058a1cf0e60b4 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Wed, 18 Sep 2024 20:17:18 -0500 Subject: [PATCH 04/23] Add mix parameter --- shared-bindings/audiodelays/Echo.c | 54 +++++++++++++++++---- shared-bindings/audiodelays/Echo.h | 5 +- shared-module/audiodelays/Echo.c | 75 ++++++++++++++++++++---------- shared-module/audiodelays/Echo.h | 1 + 4 files changed, 101 insertions(+), 34 deletions(-) diff --git a/shared-bindings/audiodelays/Echo.c b/shared-bindings/audiodelays/Echo.c index 58f4600d6df7e..ee4fbb2ec3268 100644 --- a/shared-bindings/audiodelays/Echo.c +++ b/shared-bindings/audiodelays/Echo.c @@ -16,15 +16,17 @@ #include "shared-bindings/util.h" #define DECAY_DEFAULT 0.7f +#define MIX_DEFAULT 0.5f //| class Echo: //| """An Echo effect""" //| static mp_obj_t audiodelays_echo_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - enum { ARG_delay_ms, ARG_decay, ARG_buffer_size, ARG_sample_rate, ARG_bits_per_sample, ARG_samples_signed, ARG_channel_count, }; + enum { ARG_delay_ms, ARG_decay, 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_delay_ms, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 50 } }, { MP_QSTR_decay, 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 = 1024} }, { 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} }, @@ -42,6 +44,11 @@ static mp_obj_t audiodelays_echo_make_new(const mp_obj_type_t *type, size_t n_ar : mp_obj_get_float(args[ARG_decay].u_obj); mp_arg_validate_float_range(decay, 0.0f, 1.0f, MP_QSTR_decay); + mp_float_t mix = (args[ARG_mix].u_obj == MP_OBJ_NULL) + ? (mp_float_t)MIX_DEFAULT + : mp_obj_get_float(args[ARG_mix].u_obj); + mp_arg_validate_float_range(mix, 0.0f, 1.0f, MP_QSTR_mix); + 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; @@ -50,7 +57,7 @@ static mp_obj_t audiodelays_echo_make_new(const mp_obj_type_t *type, size_t n_ar } audiodelays_echo_obj_t *self = mp_obj_malloc(audiodelays_echo_obj_t, &audiodelays_echo_type); - common_hal_audiodelays_echo_construct(self, delay_ms, decay, args[ARG_buffer_size].u_int, bits_per_sample, args[ARG_samples_signed].u_bool, channel_count, sample_rate); + common_hal_audiodelays_echo_construct(self, delay_ms, decay, mix, 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); } @@ -120,8 +127,6 @@ MP_PROPERTY_GETSET(audiodelays_echo_delay_ms_obj, (mp_obj_t)&audiodelays_echo_get_delay_ms_obj, (mp_obj_t)&audiodelays_echo_set_delay_ms_obj); - - //| decay: float //| """The rate the echo decays between 0 and 1.""" static mp_obj_t audiodelays_echo_obj_get_decay(mp_obj_t self_in) { @@ -139,10 +144,7 @@ static mp_obj_t audiodelays_echo_obj_set_decay(size_t n_args, const mp_obj_t *po mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); mp_float_t decay = mp_obj_get_float(args[ARG_decay].u_obj); - - if (decay > 1 || decay < 0) { - mp_raise_ValueError(MP_ERROR_TEXT("decay must be between 0 and 1")); - } + mp_arg_validate_float_range(decay, 0.0f, 1.0f, MP_QSTR_decay); common_hal_audiodelays_echo_set_decay(self, decay); @@ -155,6 +157,39 @@ MP_PROPERTY_GETSET(audiodelays_echo_decay_obj, (mp_obj_t)&audiodelays_echo_set_decay_obj); + + +//| mix: float +//| """The rate the echo mix between 0 and 1.""" +static mp_obj_t audiodelays_echo_obj_get_mix(mp_obj_t self_in) { + return mp_obj_new_float(common_hal_audiodelays_echo_get_mix(self_in)); +} +MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_echo_get_mix_obj, audiodelays_echo_obj_get_mix); + +static mp_obj_t audiodelays_echo_obj_set_mix(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_mix }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_mix, MP_ARG_OBJ | MP_ARG_REQUIRED, {} }, + }; + audiodelays_echo_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + 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_float_t mix = mp_obj_get_float(args[ARG_mix].u_obj); + mp_arg_validate_float_range(mix, 0.0f, 1.0f, MP_QSTR_mix); + + common_hal_audiodelays_echo_set_mix(self, mix); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(audiodelays_echo_set_mix_obj, 1, audiodelays_echo_obj_set_mix); + +MP_PROPERTY_GETSET(audiodelays_echo_mix_obj, + (mp_obj_t)&audiodelays_echo_get_mix_obj, + (mp_obj_t)&audiodelays_echo_set_mix_obj); + + + //| playing: bool //| """True when any voice is being output. (read-only)""" static mp_obj_t audiodelays_echo_obj_get_playing(mp_obj_t self_in) { @@ -196,7 +231,7 @@ static mp_obj_t audiodelays_echo_obj_play(size_t n_args, const mp_obj_t *pos_arg } MP_DEFINE_CONST_FUN_OBJ_KW(audiodelays_echo_play_obj, 1, audiodelays_echo_obj_play); -//| def stop_voice(self, voice: int = 0) -> None: +//| def stop(self) -> None: //| """Stops playback of the sample.""" //| ... //| @@ -222,6 +257,7 @@ static const mp_rom_map_elem_t audiodelays_echo_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_playing), MP_ROM_PTR(&audiodelays_echo_playing_obj) }, { MP_ROM_QSTR(MP_QSTR_delay_ms), MP_ROM_PTR(&audiodelays_echo_delay_ms_obj) }, { MP_ROM_QSTR(MP_QSTR_decay), MP_ROM_PTR(&audiodelays_echo_decay_obj) }, + { MP_ROM_QSTR(MP_QSTR_mix), MP_ROM_PTR(&audiodelays_echo_mix_obj) }, }; static MP_DEFINE_CONST_DICT(audiodelays_echo_locals_dict, audiodelays_echo_locals_dict_table); diff --git a/shared-bindings/audiodelays/Echo.h b/shared-bindings/audiodelays/Echo.h index a303529c677ed..470e68a753201 100644 --- a/shared-bindings/audiodelays/Echo.h +++ b/shared-bindings/audiodelays/Echo.h @@ -11,7 +11,7 @@ extern const mp_obj_type_t audiodelays_echo_type; void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, - uint32_t delay_ms, mp_float_t decay, + uint32_t delay_ms, mp_float_t decay, mp_float_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_echo_set_delay_ms(audiodelays_echo_obj_t *self, uint mp_float_t common_hal_audiodelays_echo_get_decay(audiodelays_echo_obj_t *self); void common_hal_audiodelays_echo_set_decay(audiodelays_echo_obj_t *self, mp_float_t decay); +mp_float_t common_hal_audiodelays_echo_get_mix(audiodelays_echo_obj_t *self); +void common_hal_audiodelays_echo_set_mix(audiodelays_echo_obj_t *self, mp_float_t decay); + bool common_hal_audiodelays_echo_get_playing(audiodelays_echo_obj_t *self); void common_hal_audiodelays_echo_play(audiodelays_echo_obj_t *self, mp_obj_t sample, bool loop); void common_hal_audiodelays_echo_stop(audiodelays_echo_obj_t *self); diff --git a/shared-module/audiodelays/Echo.c b/shared-module/audiodelays/Echo.c index 5212d8214a606..b4540423e4edf 100644 --- a/shared-module/audiodelays/Echo.c +++ b/shared-module/audiodelays/Echo.c @@ -10,7 +10,8 @@ #include "shared-module/audiomixer/utils.h" -void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_t delay_ms, mp_float_t decay, uint32_t buffer_size, uint8_t bits_per_sample, +void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_t delay_ms, mp_float_t decay, mp_float_t mix, + uint32_t buffer_size, uint8_t bits_per_sample, bool samples_signed, uint8_t channel_count, uint32_t sample_rate) { self->bits_per_sample = bits_per_sample; @@ -28,6 +29,7 @@ void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_ memset(self->buffer, 0, self->buffer_len); self->decay = (uint16_t)(decay * (1 << 15)); + self->mix = (uint16_t)(mix * (1 << 15)); // calculate buffer size for the set delay self->delay_ms = delay_ms; @@ -101,6 +103,15 @@ void common_hal_audiodelays_echo_set_decay(audiodelays_echo_obj_t *self, mp_floa self->decay = (uint16_t)(decay * (1 << 15)); } +mp_float_t common_hal_audiodelays_echo_get_mix(audiodelays_echo_obj_t *self) { + return (mp_float_t)self->mix / (1 << 15); +} + +void common_hal_audiodelays_echo_set_mix(audiodelays_echo_obj_t *self, mp_float_t mix) { + self->mix = (uint16_t)(mix * (1 << 15)); + mp_printf(&mp_plat_print, "mix %d\n", self->mix); +} + uint32_t common_hal_audiodelays_echo_get_sample_rate(audiodelays_echo_obj_t *self) { return self->sample_rate; } @@ -190,17 +201,25 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * // If we have no sample keep the echo echoing if (self->sample == NULL) { if (MP_LIKELY(self->bits_per_sample == 16)) { - // sample signed/unsigned won't matter as we have no sample - for (uint32_t i = 0; i < length; i++) { - uint32_t echo = self->echo_buffer[self->echo_buffer_read_pos++]; - word_buffer[i] = mult16signed(echo, self->decay); - - self->echo_buffer[self->echo_buffer_write_pos++] = word_buffer[i]; - if (self->echo_buffer_read_pos >= echo_buf_len) { - self->echo_buffer_read_pos = 0; + if (self->mix == 0) { // no effect and no sample sound + for (uint32_t i = 0; i < length; i++) { + word_buffer[i] = 0; } - if (self->echo_buffer_write_pos >= echo_buf_len) { - self->echo_buffer_write_pos = 0; + } else { + // sample signed/unsigned won't matter as we have no sample + for (uint32_t i = 0; i < length; i++) { + uint32_t echo = self->echo_buffer[self->echo_buffer_read_pos++]; + word_buffer[i] = mult16signed(echo, self->decay); + self->echo_buffer[self->echo_buffer_write_pos++] = word_buffer[i]; + + word_buffer[i] = mult16signed(word_buffer[i], self->mix); + + if (self->echo_buffer_read_pos >= echo_buf_len) { + self->echo_buffer_read_pos = 0; + } + if (self->echo_buffer_write_pos >= echo_buf_len) { + self->echo_buffer_write_pos = 0; + } } } @@ -227,20 +246,28 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * uint32_t *sample_src = self->sample_remaining_buffer; if (MP_LIKELY(self->bits_per_sample == 16)) { - for (uint32_t i = 0; i < n; i++) { - uint32_t sample_word = sample_src[i]; - if (MP_LIKELY(!self->samples_signed)) { - sample_word = tosigned16(sample_word); + if (self->mix == 0) { // sample only + for (uint32_t i = 0; i < n; i++) { + word_buffer[i] = sample_src[i]; } - uint32_t echo = self->echo_buffer[self->echo_buffer_read_pos++]; - word_buffer[i] = add16signed(mult16signed(echo, self->decay), sample_word); - self->echo_buffer[self->echo_buffer_write_pos++] = word_buffer[i]; - - if (self->echo_buffer_read_pos >= echo_buf_len) { - self->echo_buffer_read_pos = 0; - } - if (self->echo_buffer_write_pos >= echo_buf_len) { - self->echo_buffer_write_pos = 0; + } else { + for (uint32_t i = 0; i < n; i++) { + uint32_t sample_word = sample_src[i]; + if (MP_LIKELY(!self->samples_signed)) { + sample_word = tosigned16(sample_word); + } + uint32_t echo = self->echo_buffer[self->echo_buffer_read_pos++]; + word_buffer[i] = add16signed(mult16signed(echo, self->decay), sample_word); + self->echo_buffer[self->echo_buffer_write_pos++] = word_buffer[i]; + + word_buffer[i] = add16signed(mult16signed(sample_word, 32768 - self->mix), mult16signed(word_buffer[i], self->mix)); + + if (self->echo_buffer_read_pos >= echo_buf_len) { + self->echo_buffer_read_pos = 0; + } + if (self->echo_buffer_write_pos >= echo_buf_len) { + self->echo_buffer_write_pos = 0; + } } } } else { // bits per sample is 8 diff --git a/shared-module/audiodelays/Echo.h b/shared-module/audiodelays/Echo.h index 262fdf8d3a3ea..1660c837c89ff 100644 --- a/shared-module/audiodelays/Echo.h +++ b/shared-module/audiodelays/Echo.h @@ -15,6 +15,7 @@ typedef struct { mp_obj_base_t base; uint32_t delay_ms; uint16_t decay; + uint16_t mix; uint8_t bits_per_sample; bool samples_signed; uint8_t channel_count; From d88b0c7840373588587fc0b9900428c272f62720 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Sat, 21 Sep 2024 15:51:38 -0500 Subject: [PATCH 05/23] Change mix to BlockInput --- shared-bindings/audiodelays/Echo.c | 14 +++---------- shared-bindings/audiodelays/Echo.h | 6 +++--- shared-module/audiodelays/Echo.c | 32 ++++++++++++++++++++---------- shared-module/audiodelays/Echo.h | 3 ++- 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/shared-bindings/audiodelays/Echo.c b/shared-bindings/audiodelays/Echo.c index ee4fbb2ec3268..1ef5bfc1e7b13 100644 --- a/shared-bindings/audiodelays/Echo.c +++ b/shared-bindings/audiodelays/Echo.c @@ -44,11 +44,6 @@ static mp_obj_t audiodelays_echo_make_new(const mp_obj_type_t *type, size_t n_ar : mp_obj_get_float(args[ARG_decay].u_obj); mp_arg_validate_float_range(decay, 0.0f, 1.0f, MP_QSTR_decay); - mp_float_t mix = (args[ARG_mix].u_obj == MP_OBJ_NULL) - ? (mp_float_t)MIX_DEFAULT - : mp_obj_get_float(args[ARG_mix].u_obj); - mp_arg_validate_float_range(mix, 0.0f, 1.0f, MP_QSTR_mix); - 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; @@ -57,7 +52,7 @@ static mp_obj_t audiodelays_echo_make_new(const mp_obj_type_t *type, size_t n_ar } audiodelays_echo_obj_t *self = mp_obj_malloc(audiodelays_echo_obj_t, &audiodelays_echo_type); - common_hal_audiodelays_echo_construct(self, delay_ms, decay, mix, args[ARG_buffer_size].u_int, bits_per_sample, args[ARG_samples_signed].u_bool, channel_count, sample_rate); + common_hal_audiodelays_echo_construct(self, delay_ms, decay, 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); } @@ -162,7 +157,7 @@ MP_PROPERTY_GETSET(audiodelays_echo_decay_obj, //| mix: float //| """The rate the echo mix between 0 and 1.""" static mp_obj_t audiodelays_echo_obj_get_mix(mp_obj_t self_in) { - return mp_obj_new_float(common_hal_audiodelays_echo_get_mix(self_in)); + return common_hal_audiodelays_echo_get_mix(self_in); } MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_echo_get_mix_obj, audiodelays_echo_obj_get_mix); @@ -175,10 +170,7 @@ static mp_obj_t audiodelays_echo_obj_set_mix(size_t n_args, const mp_obj_t *pos_ 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_float_t mix = mp_obj_get_float(args[ARG_mix].u_obj); - mp_arg_validate_float_range(mix, 0.0f, 1.0f, MP_QSTR_mix); - - common_hal_audiodelays_echo_set_mix(self, mix); + common_hal_audiodelays_echo_set_mix(self, args[ARG_mix].u_obj); return mp_const_none; } diff --git a/shared-bindings/audiodelays/Echo.h b/shared-bindings/audiodelays/Echo.h index 470e68a753201..df9be6407cb69 100644 --- a/shared-bindings/audiodelays/Echo.h +++ b/shared-bindings/audiodelays/Echo.h @@ -11,7 +11,7 @@ extern const mp_obj_type_t audiodelays_echo_type; void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, - uint32_t delay_ms, mp_float_t decay, mp_float_t mix, + uint32_t delay_ms, mp_float_t decay, 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,8 +28,8 @@ void common_hal_audiodelays_echo_set_delay_ms(audiodelays_echo_obj_t *self, uint mp_float_t common_hal_audiodelays_echo_get_decay(audiodelays_echo_obj_t *self); void common_hal_audiodelays_echo_set_decay(audiodelays_echo_obj_t *self, mp_float_t decay); -mp_float_t common_hal_audiodelays_echo_get_mix(audiodelays_echo_obj_t *self); -void common_hal_audiodelays_echo_set_mix(audiodelays_echo_obj_t *self, mp_float_t decay); +mp_obj_t common_hal_audiodelays_echo_get_mix(audiodelays_echo_obj_t *self); +void common_hal_audiodelays_echo_set_mix(audiodelays_echo_obj_t *self, mp_obj_t arg); bool common_hal_audiodelays_echo_get_playing(audiodelays_echo_obj_t *self); void common_hal_audiodelays_echo_play(audiodelays_echo_obj_t *self, mp_obj_t sample, bool loop); diff --git a/shared-module/audiodelays/Echo.c b/shared-module/audiodelays/Echo.c index b4540423e4edf..da60eb4a580e9 100644 --- a/shared-module/audiodelays/Echo.c +++ b/shared-module/audiodelays/Echo.c @@ -10,7 +10,7 @@ #include "shared-module/audiomixer/utils.h" -void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_t delay_ms, mp_float_t decay, mp_float_t mix, +void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_t delay_ms, mp_float_t decay, mp_obj_t mix, uint32_t buffer_size, uint8_t bits_per_sample, bool samples_signed, uint8_t channel_count, uint32_t sample_rate) { @@ -29,7 +29,11 @@ void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_ memset(self->buffer, 0, self->buffer_len); self->decay = (uint16_t)(decay * (1 << 15)); - self->mix = (uint16_t)(mix * (1 << 15)); + + if (mix == MP_OBJ_NULL) { + mix = mp_obj_new_float(0.5); + } + synthio_block_assign_slot(mix, &self->mix, MP_QSTR_mix); // calculate buffer size for the set delay self->delay_ms = delay_ms; @@ -103,13 +107,12 @@ void common_hal_audiodelays_echo_set_decay(audiodelays_echo_obj_t *self, mp_floa self->decay = (uint16_t)(decay * (1 << 15)); } -mp_float_t common_hal_audiodelays_echo_get_mix(audiodelays_echo_obj_t *self) { - return (mp_float_t)self->mix / (1 << 15); +mp_obj_t common_hal_audiodelays_echo_get_mix(audiodelays_echo_obj_t *self) { + return self->mix.obj; } -void common_hal_audiodelays_echo_set_mix(audiodelays_echo_obj_t *self, mp_float_t mix) { - self->mix = (uint16_t)(mix * (1 << 15)); - mp_printf(&mp_plat_print, "mix %d\n", self->mix); +void common_hal_audiodelays_echo_set_mix(audiodelays_echo_obj_t *self, mp_obj_t arg) { + synthio_block_assign_slot(arg, &self->mix, MP_QSTR_mix); } uint32_t common_hal_audiodelays_echo_get_sample_rate(audiodelays_echo_obj_t *self) { @@ -177,6 +180,13 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * uint32_t *word_buffer = (uint32_t *)self->buffer; uint32_t length = self->buffer_len / sizeof(uint32_t); uint32_t echo_buf_len = self->echo_buffer_len / sizeof(uint32_t); + mp_float_t f_mix = synthio_block_slot_get(&self->mix); + if (f_mix > 1.0) { + f_mix = 1.0; + } else if (f_mix < 0.0) { + f_mix = 0.0; + } + uint16_t mix = (uint16_t)(f_mix * (1 << 15)); while (length != 0) { if (self->sample_buffer_length == 0) { @@ -201,7 +211,7 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * // If we have no sample keep the echo echoing if (self->sample == NULL) { if (MP_LIKELY(self->bits_per_sample == 16)) { - if (self->mix == 0) { // no effect and no sample sound + if (mix == 0) { // no effect and no sample sound for (uint32_t i = 0; i < length; i++) { word_buffer[i] = 0; } @@ -212,7 +222,7 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * word_buffer[i] = mult16signed(echo, self->decay); self->echo_buffer[self->echo_buffer_write_pos++] = word_buffer[i]; - word_buffer[i] = mult16signed(word_buffer[i], self->mix); + word_buffer[i] = mult16signed(word_buffer[i], mix); if (self->echo_buffer_read_pos >= echo_buf_len) { self->echo_buffer_read_pos = 0; @@ -246,7 +256,7 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * uint32_t *sample_src = self->sample_remaining_buffer; if (MP_LIKELY(self->bits_per_sample == 16)) { - if (self->mix == 0) { // sample only + if (mix == 0) { // sample only for (uint32_t i = 0; i < n; i++) { word_buffer[i] = sample_src[i]; } @@ -260,7 +270,7 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * word_buffer[i] = add16signed(mult16signed(echo, self->decay), sample_word); self->echo_buffer[self->echo_buffer_write_pos++] = word_buffer[i]; - word_buffer[i] = add16signed(mult16signed(sample_word, 32768 - self->mix), mult16signed(word_buffer[i], self->mix)); + word_buffer[i] = add16signed(mult16signed(sample_word, 32768 - mix), mult16signed(word_buffer[i], mix)); if (self->echo_buffer_read_pos >= echo_buf_len) { self->echo_buffer_read_pos = 0; diff --git a/shared-module/audiodelays/Echo.h b/shared-module/audiodelays/Echo.h index 1660c837c89ff..a3bf1d484be58 100644 --- a/shared-module/audiodelays/Echo.h +++ b/shared-module/audiodelays/Echo.h @@ -8,6 +8,7 @@ #include "py/obj.h" #include "shared-module/audiocore/__init__.h" +#include "shared-module/synthio/block.h" extern const mp_obj_type_t audiodelays_echo_type; @@ -15,7 +16,7 @@ typedef struct { mp_obj_base_t base; uint32_t delay_ms; uint16_t decay; - uint16_t mix; + synthio_block_slot_t mix; uint8_t bits_per_sample; bool samples_signed; uint8_t channel_count; From 477480705a6d038de13a3023a96c6cf435b04d95 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Sun, 22 Sep 2024 13:12:17 -0500 Subject: [PATCH 06/23] Delay and Decay to BlockInput --- shared-bindings/audiodelays/Echo.c | 40 ++++++----------- shared-bindings/audiodelays/Echo.h | 12 +++--- shared-module/audiodelays/Echo.c | 69 ++++++++++++++++++------------ shared-module/audiodelays/Echo.h | 9 ++-- 4 files changed, 67 insertions(+), 63 deletions(-) diff --git a/shared-bindings/audiodelays/Echo.c b/shared-bindings/audiodelays/Echo.c index 1ef5bfc1e7b13..0119b15690f61 100644 --- a/shared-bindings/audiodelays/Echo.c +++ b/shared-bindings/audiodelays/Echo.c @@ -22,12 +22,13 @@ //| """An Echo effect""" //| static mp_obj_t audiodelays_echo_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - enum { ARG_delay_ms, ARG_decay, ARG_mix, 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_decay, 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_delay_ms, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 50 } }, + { 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_decay, 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 = 1024} }, + { 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} }, @@ -37,12 +38,7 @@ static mp_obj_t audiodelays_echo_make_new(const mp_obj_type_t *type, size_t n_ar 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 delay_ms = mp_arg_validate_int_range(args[ARG_delay_ms].u_int, 1, 4000, MP_QSTR_delay_ms); - - mp_float_t decay = (args[ARG_decay].u_obj == MP_OBJ_NULL) - ? (mp_float_t)DECAY_DEFAULT - : mp_obj_get_float(args[ARG_decay].u_obj); - mp_arg_validate_float_range(decay, 0.0f, 1.0f, MP_QSTR_decay); + 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); @@ -52,7 +48,7 @@ static mp_obj_t audiodelays_echo_make_new(const mp_obj_type_t *type, size_t n_ar } audiodelays_echo_obj_t *self = mp_obj_malloc(audiodelays_echo_obj_t, &audiodelays_echo_type); - common_hal_audiodelays_echo_construct(self, delay_ms, decay, args[ARG_mix].u_obj, args[ARG_buffer_size].u_int, bits_per_sample, args[ARG_samples_signed].u_bool, channel_count, sample_rate); + common_hal_audiodelays_echo_construct(self, max_delay_ms, args[ARG_delay_ms].u_obj, args[ARG_decay].u_obj, args[ARG_mix].u_obj, args[ARG_buffer_size].u_int, bits_per_sample, args[ARG_samples_signed].u_bool, channel_count, sample_rate); return MP_OBJ_FROM_PTR(self); } @@ -90,13 +86,13 @@ static mp_obj_t audiodelays_echo_obj___exit__(size_t n_args, const mp_obj_t *arg static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audiodelays_echo___exit___obj, 4, 4, audiodelays_echo_obj___exit__); -//| delay_ms: int +//| delay_ms: BlockInput //| """Delay of the echo in microseconds. (read-only)""" //| static mp_obj_t audiodelays_echo_obj_get_delay_ms(mp_obj_t self_in) { audiodelays_echo_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_float(common_hal_audiodelays_echo_get_delay_ms(self)); + return common_hal_audiodelays_echo_get_delay_ms(self); } MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_echo_get_delay_ms_obj, audiodelays_echo_obj_get_delay_ms); @@ -104,15 +100,13 @@ MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_echo_get_delay_ms_obj, audiodelays_echo_ob static mp_obj_t audiodelays_echo_obj_set_delay_ms(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_delay_ms }; static const mp_arg_t allowed_args[] = { - { MP_QSTR_delay_ms, MP_ARG_INT | MP_ARG_REQUIRED, {} }, + { MP_QSTR_delay_ms, MP_ARG_OBJ | MP_ARG_REQUIRED, {} }, }; audiodelays_echo_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); 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_int_t delay_ms = mp_arg_validate_int_range(args[ARG_delay_ms].u_int, 1, 4000, MP_QSTR_delay_ms); - - common_hal_audiodelays_echo_set_delay_ms(self, delay_ms); + common_hal_audiodelays_echo_set_delay_ms(self, args[ARG_delay_ms].u_obj); return mp_const_none; } @@ -122,10 +116,10 @@ MP_PROPERTY_GETSET(audiodelays_echo_delay_ms_obj, (mp_obj_t)&audiodelays_echo_get_delay_ms_obj, (mp_obj_t)&audiodelays_echo_set_delay_ms_obj); -//| decay: float +//| decay: BlockInput //| """The rate the echo decays between 0 and 1.""" static mp_obj_t audiodelays_echo_obj_get_decay(mp_obj_t self_in) { - return mp_obj_new_float(common_hal_audiodelays_echo_get_decay(self_in)); + return common_hal_audiodelays_echo_get_decay(self_in); } MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_echo_get_decay_obj, audiodelays_echo_obj_get_decay); @@ -138,10 +132,7 @@ static mp_obj_t audiodelays_echo_obj_set_decay(size_t n_args, const mp_obj_t *po 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_float_t decay = mp_obj_get_float(args[ARG_decay].u_obj); - mp_arg_validate_float_range(decay, 0.0f, 1.0f, MP_QSTR_decay); - - common_hal_audiodelays_echo_set_decay(self, decay); + common_hal_audiodelays_echo_set_decay(self, args[ARG_decay].u_obj); return mp_const_none; } @@ -151,10 +142,7 @@ MP_PROPERTY_GETSET(audiodelays_echo_decay_obj, (mp_obj_t)&audiodelays_echo_get_decay_obj, (mp_obj_t)&audiodelays_echo_set_decay_obj); - - - -//| mix: float +//| mix: BlockInput //| """The rate the echo mix between 0 and 1.""" static mp_obj_t audiodelays_echo_obj_get_mix(mp_obj_t self_in) { return common_hal_audiodelays_echo_get_mix(self_in); diff --git a/shared-bindings/audiodelays/Echo.h b/shared-bindings/audiodelays/Echo.h index df9be6407cb69..a1035a7ea7a03 100644 --- a/shared-bindings/audiodelays/Echo.h +++ b/shared-bindings/audiodelays/Echo.h @@ -10,8 +10,8 @@ extern const mp_obj_type_t audiodelays_echo_type; -void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, - uint32_t delay_ms, mp_float_t decay, mp_obj_t mix, +void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_t max_delay_ms, + mp_obj_t delay_ms, mp_obj_t decay, mp_obj_t mix, uint32_t buffer_size, uint8_t bits_per_sample, bool samples_signed, uint8_t channel_count, uint32_t sample_rate); @@ -22,11 +22,11 @@ uint32_t common_hal_audiodelays_echo_get_sample_rate(audiodelays_echo_obj_t *sel uint8_t common_hal_audiodelays_echo_get_channel_count(audiodelays_echo_obj_t *self); uint8_t common_hal_audiodelays_echo_get_bits_per_sample(audiodelays_echo_obj_t *self); -uint32_t common_hal_audiodelays_echo_get_delay_ms(audiodelays_echo_obj_t *self); -void common_hal_audiodelays_echo_set_delay_ms(audiodelays_echo_obj_t *self, uint32_t delay_ms); +mp_obj_t common_hal_audiodelays_echo_get_delay_ms(audiodelays_echo_obj_t *self); +void common_hal_audiodelays_echo_set_delay_ms(audiodelays_echo_obj_t *self, mp_obj_t delay_ms); -mp_float_t common_hal_audiodelays_echo_get_decay(audiodelays_echo_obj_t *self); -void common_hal_audiodelays_echo_set_decay(audiodelays_echo_obj_t *self, mp_float_t decay); +mp_obj_t common_hal_audiodelays_echo_get_decay(audiodelays_echo_obj_t *self); +void common_hal_audiodelays_echo_set_decay(audiodelays_echo_obj_t *self, mp_obj_t decay); mp_obj_t common_hal_audiodelays_echo_get_mix(audiodelays_echo_obj_t *self); void common_hal_audiodelays_echo_set_mix(audiodelays_echo_obj_t *self, mp_obj_t arg); diff --git a/shared-module/audiodelays/Echo.c b/shared-module/audiodelays/Echo.c index da60eb4a580e9..fcb664f6ce4fc 100644 --- a/shared-module/audiodelays/Echo.c +++ b/shared-module/audiodelays/Echo.c @@ -10,7 +10,8 @@ #include "shared-module/audiomixer/utils.h" -void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_t delay_ms, mp_float_t decay, mp_obj_t mix, +void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_t max_delay_ms, + mp_obj_t delay_ms, mp_obj_t decay, mp_obj_t mix, uint32_t buffer_size, uint8_t bits_per_sample, bool samples_signed, uint8_t channel_count, uint32_t sample_rate) { @@ -19,7 +20,6 @@ void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_ self->channel_count = channel_count; self->sample_rate = sample_rate; - // check that buffer_size <= echo_buffer_size self->buffer_len = buffer_size; self->buffer = m_malloc(self->buffer_len); if (self->buffer == NULL) { @@ -28,7 +28,15 @@ void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_ } memset(self->buffer, 0, self->buffer_len); - self->decay = (uint16_t)(decay * (1 << 15)); + if (decay == MP_OBJ_NULL) { + decay = mp_obj_new_float(0.7); + } + synthio_block_assign_slot(decay, &self->decay, MP_QSTR_decay); + + if (delay_ms == MP_OBJ_NULL) { + delay_ms = mp_obj_new_float(0.05); + } + synthio_block_assign_slot(delay_ms, &self->delay_ms, MP_QSTR_delay_ms); if (mix == MP_OBJ_NULL) { mix = mp_obj_new_float(0.5); @@ -36,15 +44,18 @@ void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_ synthio_block_assign_slot(mix, &self->mix, MP_QSTR_mix); // calculate buffer size for the set delay - self->delay_ms = delay_ms; - self->echo_buffer_len = self->sample_rate / 1000.0f * self->delay_ms * (self->channel_count * (self->bits_per_sample / 8)); + mp_float_t f_delay_ms = synthio_block_slot_get(&self->delay_ms); + self->echo_buffer_len = self->sample_rate / 1000.0f * f_delay_ms * (self->channel_count * (self->bits_per_sample / 8)); - self->echo_buffer = m_malloc(self->echo_buffer_len); + // Set the echo buffer for the max possible delay + self->max_delay_ms = max_delay_ms; + self->max_echo_buffer_len = self->sample_rate / 1000.0f * max_delay_ms * (self->channel_count * (self->bits_per_sample / 8)); + self->echo_buffer = m_malloc(self->max_echo_buffer_len); if (self->echo_buffer == NULL) { common_hal_audiodelays_echo_deinit(self); - m_malloc_fail(self->echo_buffer_len); + m_malloc_fail(self->max_echo_buffer_len); } - memset(self->echo_buffer, 0, self->echo_buffer_len); + memset(self->echo_buffer, 0, self->max_echo_buffer_len); // read is where we store the incoming sample // write is what we send to the outgoing buffer @@ -74,19 +85,15 @@ void common_hal_audiodelays_echo_deinit(audiodelays_echo_obj_t *self) { } -uint32_t common_hal_audiodelays_echo_get_delay_ms(audiodelays_echo_obj_t *self) { - return self->delay_ms; +mp_obj_t common_hal_audiodelays_echo_get_delay_ms(audiodelays_echo_obj_t *self) { + return self->delay_ms.obj; } -void common_hal_audiodelays_echo_set_delay_ms(audiodelays_echo_obj_t *self, uint32_t delay_ms) { - self->delay_ms = delay_ms; - self->echo_buffer_len = self->sample_rate / 1000.0f * self->delay_ms * (self->channel_count * (self->bits_per_sample / 8)); +void common_hal_audiodelays_echo_set_delay_ms(audiodelays_echo_obj_t *self, mp_obj_t delay_ms) { + synthio_block_assign_slot(delay_ms, &self->delay_ms, MP_QSTR_delay_ms); - self->echo_buffer = m_realloc(self->echo_buffer, self->echo_buffer_len); - if (self->echo_buffer == NULL) { - common_hal_audiodelays_echo_deinit(self); - m_malloc_fail(self->echo_buffer_len); - } + mp_float_t f_delay_ms = synthio_block_slot_get(&self->delay_ms); + self->echo_buffer_len = self->sample_rate / 1000.0f * f_delay_ms * (self->channel_count * (self->bits_per_sample / 8)); uint32_t max_ebuf_length = self->echo_buffer_len / sizeof(uint32_t); @@ -99,12 +106,12 @@ void common_hal_audiodelays_echo_set_delay_ms(audiodelays_echo_obj_t *self, uint } } -mp_float_t common_hal_audiodelays_echo_get_decay(audiodelays_echo_obj_t *self) { - return (mp_float_t)self->decay / (1 << 15); +mp_obj_t common_hal_audiodelays_echo_get_decay(audiodelays_echo_obj_t *self) { + return self->decay.obj; } -void common_hal_audiodelays_echo_set_decay(audiodelays_echo_obj_t *self, mp_float_t decay) { - self->decay = (uint16_t)(decay * (1 << 15)); +void common_hal_audiodelays_echo_set_decay(audiodelays_echo_obj_t *self, mp_obj_t decay) { + synthio_block_assign_slot(decay, &self->decay, MP_QSTR_decay); } mp_obj_t common_hal_audiodelays_echo_get_mix(audiodelays_echo_obj_t *self) { @@ -169,8 +176,6 @@ void common_hal_audiodelays_echo_play(audiodelays_echo_obj_t *self, mp_obj_t sam void common_hal_audiodelays_echo_stop(audiodelays_echo_obj_t *self) { self->sample = NULL; - // memset(self->echo_buffer, 0, self->echo_buffer_len); // clear echo - // memset(self->buffer, 0, self->buffer_len); return; } @@ -188,6 +193,14 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * } uint16_t mix = (uint16_t)(f_mix * (1 << 15)); + mp_float_t f_decay = synthio_block_slot_get(&self->decay); + if (f_decay > 1.0) { + f_decay = 1.0; + } else if (f_decay < 0.0) { + f_decay = 0.0; + } + uint16_t decay = (uint16_t)(f_decay * (1 << 15)); + while (length != 0) { if (self->sample_buffer_length == 0) { if (!self->more_data) { @@ -219,7 +232,7 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * // sample signed/unsigned won't matter as we have no sample for (uint32_t i = 0; i < length; i++) { uint32_t echo = self->echo_buffer[self->echo_buffer_read_pos++]; - word_buffer[i] = mult16signed(echo, self->decay); + word_buffer[i] = mult16signed(echo, decay); self->echo_buffer[self->echo_buffer_write_pos++] = word_buffer[i]; word_buffer[i] = mult16signed(word_buffer[i], mix); @@ -238,7 +251,7 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * uint16_t *echo_hsrc = (uint16_t *)self->echo_buffer; for (uint32_t i = 0; i < length * 2; i++) { uint32_t echo_word = unpack8(echo_hsrc[i]); - echo_word = mult16signed(echo_word, self->decay); + echo_word = mult16signed(echo_word, decay); hword_buffer[i] = pack8(echo_word); echo_hsrc[self->echo_buffer_write_pos++] = hword_buffer[i]; @@ -267,7 +280,7 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * sample_word = tosigned16(sample_word); } uint32_t echo = self->echo_buffer[self->echo_buffer_read_pos++]; - word_buffer[i] = add16signed(mult16signed(echo, self->decay), sample_word); + word_buffer[i] = add16signed(mult16signed(echo, decay), sample_word); self->echo_buffer[self->echo_buffer_write_pos++] = word_buffer[i]; word_buffer[i] = add16signed(mult16signed(sample_word, 32768 - mix), mult16signed(word_buffer[i], mix)); @@ -290,7 +303,7 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * if (MP_LIKELY(!self->samples_signed)) { sample_word = tosigned16(sample_word); } - echo_word = mult16signed(echo_word, self->decay); + echo_word = mult16signed(echo_word, decay); sample_word = add16signed(sample_word, echo_word); hword_buffer[i] = pack8(sample_word); diff --git a/shared-module/audiodelays/Echo.h b/shared-module/audiodelays/Echo.h index a3bf1d484be58..aafb40f182e25 100644 --- a/shared-module/audiodelays/Echo.h +++ b/shared-module/audiodelays/Echo.h @@ -14,16 +14,18 @@ extern const mp_obj_type_t audiodelays_echo_type; typedef struct { mp_obj_base_t base; - uint32_t delay_ms; - uint16_t decay; + uint32_t max_delay_ms; + synthio_block_slot_t delay_ms; + synthio_block_slot_t decay; synthio_block_slot_t mix; + uint8_t bits_per_sample; bool samples_signed; uint8_t channel_count; uint32_t sample_rate; uint32_t *buffer; - uint32_t buffer_len; // buffer in bytes + uint32_t buffer_len; // max buffer in bytes uint32_t *sample_remaining_buffer; uint32_t sample_buffer_length; @@ -33,6 +35,7 @@ typedef struct { uint32_t *echo_buffer; uint32_t echo_buffer_len; // bytes + uint32_t max_echo_buffer_len; // bytes uint32_t echo_buffer_read_pos; // words uint32_t echo_buffer_write_pos; // words From 01a220af0f0bb9f90feebdc875d889860d5c6b68 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Mon, 23 Sep 2024 21:24:30 -0500 Subject: [PATCH 07/23] Started simplifying getbuffer --- shared-module/audiodelays/Echo.c | 129 +++++++++++++++++-------------- 1 file changed, 69 insertions(+), 60 deletions(-) diff --git a/shared-module/audiodelays/Echo.c b/shared-module/audiodelays/Echo.c index fcb664f6ce4fc..7caa6c79f84e9 100644 --- a/shared-module/audiodelays/Echo.c +++ b/shared-module/audiodelays/Echo.c @@ -43,10 +43,6 @@ void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_ } synthio_block_assign_slot(mix, &self->mix, MP_QSTR_mix); - // calculate buffer size for the set delay - mp_float_t f_delay_ms = synthio_block_slot_get(&self->delay_ms); - self->echo_buffer_len = self->sample_rate / 1000.0f * f_delay_ms * (self->channel_count * (self->bits_per_sample / 8)); - // Set the echo buffer for the max possible delay self->max_delay_ms = max_delay_ms; self->max_echo_buffer_len = self->sample_rate / 1000.0f * max_delay_ms * (self->channel_count * (self->bits_per_sample / 8)); @@ -57,9 +53,13 @@ void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_ } memset(self->echo_buffer, 0, self->max_echo_buffer_len); + // calculate current echo buffer size for the set delay + mp_float_t f_delay_ms = synthio_block_slot_get(&self->delay_ms); + self->echo_buffer_len = self->sample_rate / 1000.0f * f_delay_ms * (self->channel_count * (self->bits_per_sample / 8)); + // read is where we store the incoming sample // write is what we send to the outgoing buffer - self->echo_buffer_read_pos = self->buffer_len / sizeof(uint32_t); + self->echo_buffer_read_pos = self->buffer_len / sizeof(uint16_t); self->echo_buffer_write_pos = 0; self->sample = NULL; @@ -168,7 +168,7 @@ void common_hal_audiodelays_echo_play(audiodelays_echo_obj_t *self, mp_obj_t sam 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 length in terms of words. - self->sample_buffer_length /= sizeof(uint32_t); + self->sample_buffer_length /= sizeof(uint16_t); self->more_data = result == GET_BUFFER_MORE_DATA; return; @@ -179,35 +179,48 @@ void common_hal_audiodelays_echo_stop(audiodelays_echo_obj_t *self) { return; } +#define RANGE_LOW (-28000) +#define RANGE_HIGH (28000) +#define RANGE_SHIFT (16) +#define RANGE_SCALE (0xfffffff / (32768 * 2 - RANGE_HIGH)) + +// dynamic range compression via a downward compressor with hard knee +// +// When the output value is within the range +-28000 (about 85% of full scale), +// it is unchanged. Otherwise, it undergoes a gain reduction so that the +// largest possible values, (+32768,-32767) * 2 (2 for echo and sample), +// still fit within the output range +// +// This produces a much louder overall volume with multiple voices, without +// much additional processing. +// +// https://en.wikipedia.org/wiki/Dynamic_range_compression +static +int16_t mix_down_sample(int32_t sample) { + if (sample < RANGE_LOW) { + sample = (((sample - RANGE_LOW) * RANGE_SCALE) >> RANGE_SHIFT) + RANGE_LOW; + } else if (sample > RANGE_HIGH) { + sample = (((sample - RANGE_HIGH) * RANGE_SCALE) >> RANGE_SHIFT) + RANGE_HIGH; + } + return sample; +} + audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t *self, bool single_channel_output, uint8_t channel, uint8_t **buffer, uint32_t *buffer_length) { - uint32_t *word_buffer = (uint32_t *)self->buffer; - uint32_t length = self->buffer_len / sizeof(uint32_t); - uint32_t echo_buf_len = self->echo_buffer_len / sizeof(uint32_t); - mp_float_t f_mix = synthio_block_slot_get(&self->mix); - if (f_mix > 1.0) { - f_mix = 1.0; - } else if (f_mix < 0.0) { - f_mix = 0.0; - } - uint16_t mix = (uint16_t)(f_mix * (1 << 15)); + mp_float_t mix = MIN(1.0, MAX(synthio_block_slot_get(&self->mix), 0.0)); + mp_float_t decay = MIN(1.0, MAX(synthio_block_slot_get(&self->decay), 0.0)); - mp_float_t f_decay = synthio_block_slot_get(&self->decay); - if (f_decay > 1.0) { - f_decay = 1.0; - } else if (f_decay < 0.0) { - f_decay = 0.0; - } - uint16_t decay = (uint16_t)(f_decay * (1 << 15)); + int16_t *word_buffer = (int16_t *)self->buffer; + uint32_t length = self->buffer_len / sizeof(uint16_t); + int16_t *echo_buffer = (int16_t *)self->echo_buffer; + uint32_t echo_buf_len = self->echo_buffer_len / sizeof(uint16_t); while (length != 0) { if (self->sample_buffer_length == 0) { if (!self->more_data) { - if (self->loop) { - if (self->sample) { - audiosample_reset_buffer(self->sample, false, 0); - } + if (self->loop && self->sample) { + audiosample_reset_buffer(self->sample, false, 0); } else { self->sample = NULL; } @@ -216,7 +229,7 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * // Load another buffer 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 /= sizeof(uint32_t); + self->sample_buffer_length /= sizeof(uint16_t); // assuming 16 bit samples self->more_data = result == GET_BUFFER_MORE_DATA; } } @@ -224,18 +237,16 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * // If we have no sample keep the echo echoing if (self->sample == NULL) { if (MP_LIKELY(self->bits_per_sample == 16)) { - if (mix == 0) { // no effect and no sample sound - for (uint32_t i = 0; i < length; i++) { - word_buffer[i] = 0; - } + if (mix <= 0.01) { // no sample sound and with no mix, no echo + memset(word_buffer, 0, length * sizeof(uint16_t)); } else { // sample signed/unsigned won't matter as we have no sample for (uint32_t i = 0; i < length; i++) { - uint32_t echo = self->echo_buffer[self->echo_buffer_read_pos++]; - word_buffer[i] = mult16signed(echo, decay); - self->echo_buffer[self->echo_buffer_write_pos++] = word_buffer[i]; + word_buffer[i] = echo_buffer[self->echo_buffer_read_pos++] * decay; - word_buffer[i] = mult16signed(word_buffer[i], mix); + echo_buffer[self->echo_buffer_write_pos++] = word_buffer[i]; + + word_buffer[i] = word_buffer[i] * mix; if (self->echo_buffer_read_pos >= echo_buf_len) { self->echo_buffer_read_pos = 0; @@ -251,7 +262,7 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * uint16_t *echo_hsrc = (uint16_t *)self->echo_buffer; for (uint32_t i = 0; i < length * 2; i++) { uint32_t echo_word = unpack8(echo_hsrc[i]); - echo_word = mult16signed(echo_word, decay); + echo_word = echo_word * decay; hword_buffer[i] = pack8(echo_word); echo_hsrc[self->echo_buffer_write_pos++] = hword_buffer[i]; @@ -263,27 +274,28 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * } } } + length = 0; } else { // we have a sample uint32_t n = MIN(self->sample_buffer_length, length); - uint32_t *sample_src = self->sample_remaining_buffer; + int16_t *sample_src = (int16_t *)self->sample_remaining_buffer; if (MP_LIKELY(self->bits_per_sample == 16)) { - if (mix == 0) { // sample only + if (mix <= 0.01) { // sample only for (uint32_t i = 0; i < n; i++) { word_buffer[i] = sample_src[i]; } } else { for (uint32_t i = 0; i < n; i++) { - uint32_t sample_word = sample_src[i]; - if (MP_LIKELY(!self->samples_signed)) { + int32_t sample_word = sample_src[i]; + if (!self->samples_signed) { sample_word = tosigned16(sample_word); } - uint32_t echo = self->echo_buffer[self->echo_buffer_read_pos++]; - word_buffer[i] = add16signed(mult16signed(echo, decay), sample_word); - self->echo_buffer[self->echo_buffer_write_pos++] = word_buffer[i]; - word_buffer[i] = add16signed(mult16signed(sample_word, 32768 - mix), mult16signed(word_buffer[i], mix)); + int32_t word = (echo_buffer[self->echo_buffer_read_pos++] * decay) + sample_word; + word_buffer[i] = mix_down_sample(word); + + echo_buffer[self->echo_buffer_write_pos++] = word_buffer[i]; if (self->echo_buffer_read_pos >= echo_buf_len) { self->echo_buffer_read_pos = 0; @@ -291,6 +303,8 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * if (self->echo_buffer_write_pos >= echo_buf_len) { self->echo_buffer_write_pos = 0; } + + word_buffer[i] = (sample_word * (1.0 - mix)) + (word_buffer[i] * mix); } } } else { // bits per sample is 8 @@ -303,11 +317,11 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * if (MP_LIKELY(!self->samples_signed)) { sample_word = tosigned16(sample_word); } - echo_word = mult16signed(echo_word, decay); - sample_word = add16signed(sample_word, echo_word); + echo_word = echo_word * decay; + sample_word = sample_word + echo_word; hword_buffer[i] = pack8(sample_word); - echo_hsrc[self->echo_buffer_write_pos++] = pack8(add16signed(sample_word, unpack8(hword_buffer[i]))); + echo_hsrc[self->echo_buffer_write_pos++] = pack8(sample_word + unpack8(hword_buffer[i])); if (self->echo_buffer_read_pos >= echo_buf_len) { self->echo_buffer_read_pos = 0; } @@ -323,6 +337,7 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * self->sample_buffer_length -= n; } } + *buffer = (uint8_t *)self->buffer; *buffer_length = self->buffer_len; return GET_BUFFER_MORE_DATA; @@ -331,18 +346,12 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * void audiodelays_echo_get_buffer_structure(audiodelays_echo_obj_t *self, bool single_channel_output, bool *single_buffer, bool *samples_signed, uint32_t *max_buffer_length, uint8_t *spacing) { - if (self->sample != NULL) { - audiosample_get_buffer_structure(self->sample, single_channel_output, single_buffer, samples_signed, max_buffer_length, spacing); - *single_buffer = false; - *max_buffer_length = self->buffer_len; + *single_buffer = true; + *samples_signed = true; + *max_buffer_length = self->buffer_len; + if (single_channel_output) { + *spacing = self->channel_count; } else { - *single_buffer = true; - *samples_signed = true; - *max_buffer_length = self->buffer_len; - if (single_channel_output) { - *spacing = self->channel_count; - } else { - *spacing = 1; - } + *spacing = 1; } } From 98b360a436daec7af3c40951f224fa4cb6c29a36 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Tue, 24 Sep 2024 21:25:43 -0500 Subject: [PATCH 08/23] Double buffering --- shared-module/audiodelays/Echo.c | 27 ++++++++++++++++++--------- shared-module/audiodelays/Echo.h | 5 +++-- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/shared-module/audiodelays/Echo.c b/shared-module/audiodelays/Echo.c index 7caa6c79f84e9..159a9fcffd9eb 100644 --- a/shared-module/audiodelays/Echo.c +++ b/shared-module/audiodelays/Echo.c @@ -21,12 +21,19 @@ void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_ self->sample_rate = sample_rate; self->buffer_len = buffer_size; - self->buffer = m_malloc(self->buffer_len); - if (self->buffer == NULL) { + self->buffer[0] = m_malloc(self->buffer_len); + if (self->buffer[0] == NULL) { common_hal_audiodelays_echo_deinit(self); m_malloc_fail(self->buffer_len); } - memset(self->buffer, 0, 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_echo_deinit(self); + m_malloc_fail(self->buffer_len); + } + memset(self->buffer[1], 0, self->buffer_len); + self->last_buf_idx = 1; if (decay == MP_OBJ_NULL) { decay = mp_obj_new_float(0.7); @@ -81,7 +88,8 @@ void common_hal_audiodelays_echo_deinit(audiodelays_echo_obj_t *self) { return; } self->echo_buffer = NULL; - self->buffer = NULL; + self->buffer[0] = NULL; + self->buffer[1] = NULL; } @@ -99,9 +107,9 @@ void common_hal_audiodelays_echo_set_delay_ms(audiodelays_echo_obj_t *self, mp_o if (self->echo_buffer_read_pos > max_ebuf_length) { self->echo_buffer_read_pos = 0; - self->echo_buffer_write_pos = max_ebuf_length - (self->buffer_len / sizeof(uint32_t)); + self->echo_buffer_write_pos = max_ebuf_length - (self->buffer_len / sizeof(uint16_t)); } else if (self->echo_buffer_write_pos > max_ebuf_length) { - self->echo_buffer_read_pos = self->buffer_len / sizeof(uint32_t); + self->echo_buffer_read_pos = self->buffer_len / sizeof(uint16_t); self->echo_buffer_write_pos = 0; } } @@ -211,7 +219,8 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * mp_float_t mix = MIN(1.0, MAX(synthio_block_slot_get(&self->mix), 0.0)); mp_float_t decay = MIN(1.0, MAX(synthio_block_slot_get(&self->decay), 0.0)); - int16_t *word_buffer = (int16_t *)self->buffer; + self->last_buf_idx = !self->last_buf_idx; + int16_t *word_buffer = (int16_t *)self->buffer[self->last_buf_idx]; uint32_t length = self->buffer_len / sizeof(uint16_t); int16_t *echo_buffer = (int16_t *)self->echo_buffer; uint32_t echo_buf_len = self->echo_buffer_len / sizeof(uint16_t); @@ -338,7 +347,7 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * } } - *buffer = (uint8_t *)self->buffer; + *buffer = (uint8_t *)self->buffer[self->last_buf_idx]; *buffer_length = self->buffer_len; return GET_BUFFER_MORE_DATA; } @@ -346,7 +355,7 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * void audiodelays_echo_get_buffer_structure(audiodelays_echo_obj_t *self, bool single_channel_output, bool *single_buffer, bool *samples_signed, uint32_t *max_buffer_length, uint8_t *spacing) { - *single_buffer = true; + *single_buffer = false; *samples_signed = true; *max_buffer_length = self->buffer_len; if (single_channel_output) { diff --git a/shared-module/audiodelays/Echo.h b/shared-module/audiodelays/Echo.h index aafb40f182e25..b538716c45fd7 100644 --- a/shared-module/audiodelays/Echo.h +++ b/shared-module/audiodelays/Echo.h @@ -24,7 +24,8 @@ typedef struct { uint8_t channel_count; uint32_t sample_rate; - uint32_t *buffer; + int8_t *buffer[2]; + uint8_t last_buf_idx; uint32_t buffer_len; // max buffer in bytes uint32_t *sample_remaining_buffer; @@ -33,7 +34,7 @@ typedef struct { bool loop; bool more_data; - uint32_t *echo_buffer; + int8_t *echo_buffer; uint32_t echo_buffer_len; // bytes uint32_t max_echo_buffer_len; // bytes From a851f102feb93a4253864bcf3d615500532409ba Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Sat, 28 Sep 2024 09:38:20 -0500 Subject: [PATCH 09/23] Error message for sample note matching made consistent --- locale/circuitpython.pot | 38 +++++++-------------------- shared-module/audiodelays/Echo.c | 10 +++---- shared-module/audiomixer/MixerVoice.c | 8 +++--- 3 files changed, 18 insertions(+), 38 deletions(-) diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 07ed2443d3393..f0dfa2cc07b67 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -1956,36 +1956,20 @@ msgstr "" msgid "The length of rgb_pins must be 6, 12, 18, 24, or 30" msgstr "" -#: shared-module/audioeffects/Echo.c -msgid "The sample's bits_per_sample does not match the effect's" +#: shared-module/audiodelays/Echo.c shared-module/audiomixer/MixerVoice.c +msgid "The sample's bits_per_sample does not match" msgstr "" -#: shared-module/audiomixer/MixerVoice.c -msgid "The sample's bits_per_sample does not match the mixer's" +#: shared-module/audiodelays/Echo.c shared-module/audiomixer/MixerVoice.c +msgid "The sample's channel count does not match" msgstr "" -#: shared-module/audioeffects/Echo.c -msgid "The sample's channel count does not match the effect's" +#: shared-module/audiodelays/Echo.c shared-module/audiomixer/MixerVoice.c +msgid "The sample's sample rate does not match" msgstr "" -#: shared-module/audiomixer/MixerVoice.c -msgid "The sample's channel count does not match the mixer's" -msgstr "" - -#: shared-module/audioeffects/Echo.c -msgid "The sample's sample rate does not match the effect's" -msgstr "" - -#: shared-module/audiomixer/MixerVoice.c -msgid "The sample's sample rate does not match the mixer's" -msgstr "" - -#: shared-module/audioeffects/Echo.c -msgid "The sample's signedness does not match the effect's" -msgstr "" - -#: shared-module/audiomixer/MixerVoice.c -msgid "The sample's signedness does not match the mixer's" +#: shared-module/audiodelays/Echo.c shared-module/audiomixer/MixerVoice.c +msgid "The sample's signedness does not match" msgstr "" #: supervisor/shared/safe_mode.c @@ -2516,7 +2500,7 @@ msgstr "" msgid "bits must be 32 or less" msgstr "" -#: shared-bindings/audioeffects/Echo.c shared-bindings/audiomixer/Mixer.c +#: shared-bindings/audiodelays/Echo.c shared-bindings/audiomixer/Mixer.c msgid "bits_per_sample must be 8 or 16" msgstr "" @@ -2863,10 +2847,6 @@ msgstr "" msgid "data type not understood" msgstr "" -#: shared-bindings/audioeffects/Echo.c -msgid "decay must be between 0 and 1" -msgstr "" - #: py/parsenum.c msgid "decimal numbers not supported" msgstr "" diff --git a/shared-module/audiodelays/Echo.c b/shared-module/audiodelays/Echo.c index 159a9fcffd9eb..294e1db5eb721 100644 --- a/shared-module/audiodelays/Echo.c +++ b/shared-module/audiodelays/Echo.c @@ -154,13 +154,13 @@ bool common_hal_audiodelays_echo_get_playing(audiodelays_echo_obj_t *self) { void common_hal_audiodelays_echo_play(audiodelays_echo_obj_t *self, mp_obj_t sample, bool loop) { if (audiosample_sample_rate(sample) != self->sample_rate) { - mp_raise_ValueError(MP_ERROR_TEXT("The sample's sample rate does not match the effect's")); + mp_raise_ValueError(MP_ERROR_TEXT("The sample's sample rate does not match")); } if (audiosample_channel_count(sample) != self->channel_count) { - mp_raise_ValueError(MP_ERROR_TEXT("The sample's channel count does not match the effect's")); + mp_raise_ValueError(MP_ERROR_TEXT("The sample's channel count does not match")); } if (audiosample_bits_per_sample(sample) != self->bits_per_sample) { - mp_raise_ValueError(MP_ERROR_TEXT("The sample's bits_per_sample does not match the effect's")); + mp_raise_ValueError(MP_ERROR_TEXT("The sample's bits_per_sample does not match")); } bool single_buffer; bool samples_signed; @@ -168,7 +168,7 @@ void common_hal_audiodelays_echo_play(audiodelays_echo_obj_t *self, mp_obj_t sam 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(MP_ERROR_TEXT("The sample's signedness does not match the effect's")); + mp_raise_ValueError(MP_ERROR_TEXT("The sample's signedness does not match")); } self->sample = sample; self->loop = loop; @@ -190,7 +190,7 @@ void common_hal_audiodelays_echo_stop(audiodelays_echo_obj_t *self) { #define RANGE_LOW (-28000) #define RANGE_HIGH (28000) #define RANGE_SHIFT (16) -#define RANGE_SCALE (0xfffffff / (32768 * 2 - RANGE_HIGH)) +#define RANGE_SCALE (0xfffffff / (32768 * 4 - RANGE_HIGH)) // dynamic range compression via a downward compressor with hard knee // diff --git a/shared-module/audiomixer/MixerVoice.c b/shared-module/audiomixer/MixerVoice.c index c32d37cdec288..f43a261d89685 100644 --- a/shared-module/audiomixer/MixerVoice.c +++ b/shared-module/audiomixer/MixerVoice.c @@ -32,13 +32,13 @@ void common_hal_audiomixer_mixervoice_set_level(audiomixer_mixervoice_obj_t *sel void common_hal_audiomixer_mixervoice_play(audiomixer_mixervoice_obj_t *self, mp_obj_t sample, bool loop) { if (audiosample_sample_rate(sample) != self->parent->sample_rate) { - mp_raise_ValueError(MP_ERROR_TEXT("The sample's sample rate does not match the mixer's")); + mp_raise_ValueError(MP_ERROR_TEXT("The sample's sample rate does not match")); } if (audiosample_channel_count(sample) != self->parent->channel_count) { - mp_raise_ValueError(MP_ERROR_TEXT("The sample's channel count does not match the mixer's")); + mp_raise_ValueError(MP_ERROR_TEXT("The sample's channel count does not match")); } if (audiosample_bits_per_sample(sample) != self->parent->bits_per_sample) { - mp_raise_ValueError(MP_ERROR_TEXT("The sample's bits_per_sample does not match the mixer's")); + mp_raise_ValueError(MP_ERROR_TEXT("The sample's bits_per_sample does not match")); } bool single_buffer; bool samples_signed; @@ -47,7 +47,7 @@ void common_hal_audiomixer_mixervoice_play(audiomixer_mixervoice_obj_t *self, mp audiosample_get_buffer_structure(sample, false, &single_buffer, &samples_signed, &max_buffer_length, &spacing); if (samples_signed != self->parent->samples_signed) { - mp_raise_ValueError(MP_ERROR_TEXT("The sample's signedness does not match the mixer's")); + mp_raise_ValueError(MP_ERROR_TEXT("The sample's signedness does not match")); } self->sample = sample; self->loop = loop; From bbd3e016655587c1030a530230eee8900f3c72b5 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Sat, 28 Sep 2024 11:22:05 -0500 Subject: [PATCH 10/23] Comments and couple tweaks --- shared-module/audiodelays/Echo.c | 168 +++++++++++++++++++------------ shared-module/audiodelays/Echo.h | 2 +- 2 files changed, 106 insertions(+), 64 deletions(-) diff --git a/shared-module/audiodelays/Echo.c b/shared-module/audiodelays/Echo.c index 294e1db5eb721..9d8f0d8660152 100644 --- a/shared-module/audiodelays/Echo.c +++ b/shared-module/audiodelays/Echo.c @@ -15,26 +15,46 @@ void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_ uint32_t buffer_size, uint8_t bits_per_sample, bool samples_signed, uint8_t channel_count, uint32_t sample_rate) { - self->bits_per_sample = bits_per_sample; - self->samples_signed = samples_signed; - self->channel_count = channel_count; - self->sample_rate = 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_len = buffer_size; self->buffer[0] = m_malloc(self->buffer_len); if (self->buffer[0] == NULL) { common_hal_audiodelays_echo_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_echo_deinit(self); m_malloc_fail(self->buffer_len); } memset(self->buffer[1], 0, self->buffer_len); - self->last_buf_idx = 1; + 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 echo 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 (decay == MP_OBJ_NULL) { decay = mp_obj_new_float(0.7); } @@ -50,9 +70,13 @@ void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_ } synthio_block_assign_slot(mix, &self->mix, MP_QSTR_mix); - // Set the echo buffer for the max possible delay + // Many effects may need buffers of what was played this shows how it was done for the echo + // A maximum length buffer was created and then the current echo length can be dynamically changes + // without having to reallocate a large chunk of memory. + + // Allocate the echo buffer for the max possible delay self->max_delay_ms = max_delay_ms; - self->max_echo_buffer_len = self->sample_rate / 1000.0f * max_delay_ms * (self->channel_count * (self->bits_per_sample / 8)); + self->max_echo_buffer_len = self->sample_rate / 1000.0f * max_delay_ms * (self->channel_count * (self->bits_per_sample / 8)); // bytes self->echo_buffer = m_malloc(self->max_echo_buffer_len); if (self->echo_buffer == NULL) { common_hal_audiodelays_echo_deinit(self); @@ -60,20 +84,14 @@ void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_ } memset(self->echo_buffer, 0, self->max_echo_buffer_len); - // calculate current echo buffer size for the set delay + // calculate current echo buffer size we use for the given delay mp_float_t f_delay_ms = synthio_block_slot_get(&self->delay_ms); self->echo_buffer_len = self->sample_rate / 1000.0f * f_delay_ms * (self->channel_count * (self->bits_per_sample / 8)); - // read is where we store the incoming sample + // read is where we store the incoming sample + previous echo // write is what we send to the outgoing buffer - self->echo_buffer_read_pos = self->buffer_len / sizeof(uint16_t); + self->echo_buffer_read_pos = self->buffer_len / (self->bits_per_sample / 8); self->echo_buffer_write_pos = 0; - - self->sample = NULL; - self->sample_remaining_buffer = NULL; - self->sample_buffer_length = 0; - self->loop = false; - self->more_data = false; } bool common_hal_audiodelays_echo_deinited(audiodelays_echo_obj_t *self) { @@ -153,6 +171,10 @@ bool common_hal_audiodelays_echo_get_playing(audiodelays_echo_obj_t *self) { } void common_hal_audiodelays_echo_play(audiodelays_echo_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(MP_ERROR_TEXT("The sample's sample rate does not match")); } @@ -170,19 +192,23 @@ void common_hal_audiodelays_echo_play(audiodelays_echo_obj_t *self, mp_obj_t sam if (samples_signed != self->samples_signed) { mp_raise_ValueError(MP_ERROR_TEXT("The sample's signedness does not match")); } + 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 length in terms of words. - self->sample_buffer_length /= sizeof(uint16_t); + + // Track remaining sample length in terms of bytes per sample + self->sample_buffer_length /= (self->bits_per_sample / 8); self->more_data = result == GET_BUFFER_MORE_DATA; return; } void common_hal_audiodelays_echo_stop(audiodelays_echo_obj_t *self) { + // When the sample is set to stop playing do any cleanup here + // For echo we clear the sample but the echo continues until the object reading our effect stops self->sample = NULL; return; } @@ -216,29 +242,37 @@ int16_t mix_down_sample(int32_t sample) { audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t *self, bool single_channel_output, uint8_t channel, uint8_t **buffer, uint32_t *buffer_length) { + // get the effect values we need from the BlockInput. These may change at run time so you need to do bounds checking if required mp_float_t mix = MIN(1.0, MAX(synthio_block_slot_get(&self->mix), 0.0)); mp_float_t decay = MIN(1.0, MAX(synthio_block_slot_get(&self->decay), 0.0)); + // 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, for 8 bit we can just use the buffer int16_t *word_buffer = (int16_t *)self->buffer[self->last_buf_idx]; - uint32_t length = self->buffer_len / sizeof(uint16_t); + uint32_t length = self->buffer_len / (self->bits_per_sample / 8); + int16_t *echo_buffer = (int16_t *)self->echo_buffer; - uint32_t echo_buf_len = self->echo_buffer_len / sizeof(uint16_t); + uint32_t echo_buf_len = self->echo_buffer_len / (self->bits_per_sample / 8); + // 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 if (self->sample_buffer_length == 0) { - if (!self->more_data) { - if (self->loop && self->sample) { + 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 { + } else { // If we were not supposed to loop the sample, stop playing it but we still need to play the echo self->sample = NULL; } } if (self->sample) { - // Load another buffer + // 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 /= sizeof(uint16_t); // assuming 16 bit samples + self->sample_buffer_length /= (self->bits_per_sample / 8); self->more_data = result == GET_BUFFER_MORE_DATA; } } @@ -246,10 +280,10 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * // If we have no sample keep the echo echoing if (self->sample == NULL) { if (MP_LIKELY(self->bits_per_sample == 16)) { - if (mix <= 0.01) { // no sample sound and with no mix, no echo + if (mix <= 0.01) { // Mix of 0 is pure sample sound. We have no sample so no sound memset(word_buffer, 0, length * sizeof(uint16_t)); } else { - // sample signed/unsigned won't matter as we have no sample + // Since we have no sample we can just iterate over the our entire buffer for (uint32_t i = 0; i < length; i++) { word_buffer[i] = echo_buffer[self->echo_buffer_read_pos++] * decay; @@ -267,30 +301,36 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * } } else { // bits per sample is 8 - uint16_t *hword_buffer = (uint16_t *)word_buffer; - uint16_t *echo_hsrc = (uint16_t *)self->echo_buffer; - for (uint32_t i = 0; i < length * 2; i++) { - uint32_t echo_word = unpack8(echo_hsrc[i]); - echo_word = echo_word * decay; - hword_buffer[i] = pack8(echo_word); - - echo_hsrc[self->echo_buffer_write_pos++] = hword_buffer[i]; - if (self->echo_buffer_read_pos >= echo_buf_len) { - self->echo_buffer_read_pos = 0; - } - if (self->echo_buffer_write_pos >= echo_buf_len) { - self->echo_buffer_write_pos = 0; + /* Still to be updated + uint16_t *hword_buffer = (uint16_t *)word_buffer; + uint16_t *echo_hsrc = (uint16_t *)self->echo_buffer; + for (uint32_t i = 0; i < length * 2; i++) { + uint32_t echo_word = unpack8(echo_hsrc[i]); + echo_word = echo_word * decay; + hword_buffer[i] = pack8(echo_word); + + echo_hsrc[self->echo_buffer_write_pos++] = hword_buffer[i]; + if (self->echo_buffer_read_pos >= echo_buf_len) { + self->echo_buffer_read_pos = 0; + } + if (self->echo_buffer_write_pos >= echo_buf_len) { + self->echo_buffer_write_pos = 0; + } } - } + */ } length = 0; - } else { // we have a sample + } else { + // we have a sample to play and echo + + // 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 = MIN(self->sample_buffer_length, length); + int16_t *sample_src = (int16_t *)self->sample_remaining_buffer; if (MP_LIKELY(self->bits_per_sample == 16)) { - if (mix <= 0.01) { // sample only + if (mix <= 0.01) { // if mix is zero pure sample only for (uint32_t i = 0; i < n; i++) { word_buffer[i] = sample_src[i]; } @@ -317,27 +357,29 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * } } } else { // bits per sample is 8 - uint16_t *hword_buffer = (uint16_t *)word_buffer; - uint16_t *sample_hsrc = (uint16_t *)sample_src; - uint16_t *echo_hsrc = (uint16_t *)self->echo_buffer; - for (uint32_t i = 0; i < n * 2; i++) { - uint32_t sample_word = unpack8(sample_hsrc[i]); - uint32_t echo_word = unpack8(echo_hsrc[i]); - if (MP_LIKELY(!self->samples_signed)) { - sample_word = tosigned16(sample_word); - } - echo_word = echo_word * decay; - sample_word = sample_word + echo_word; - hword_buffer[i] = pack8(sample_word); + /* to be updated + uint16_t *hword_buffer = (uint16_t *)word_buffer; + uint16_t *sample_hsrc = (uint16_t *)sample_src; + uint16_t *echo_hsrc = (uint16_t *)self->echo_buffer; + for (uint32_t i = 0; i < n * 2; i++) { + uint32_t sample_word = unpack8(sample_hsrc[i]); + uint32_t echo_word = unpack8(echo_hsrc[i]); + if (MP_LIKELY(!self->samples_signed)) { + sample_word = tosigned16(sample_word); + } + echo_word = echo_word * decay; + sample_word = sample_word + echo_word; + hword_buffer[i] = pack8(sample_word); - echo_hsrc[self->echo_buffer_write_pos++] = pack8(sample_word + unpack8(hword_buffer[i])); - if (self->echo_buffer_read_pos >= echo_buf_len) { - self->echo_buffer_read_pos = 0; - } - if (self->echo_buffer_write_pos >= echo_buf_len) { - self->echo_buffer_write_pos = 0; + echo_hsrc[self->echo_buffer_write_pos++] = pack8(sample_word + unpack8(hword_buffer[i])); + if (self->echo_buffer_read_pos >= echo_buf_len) { + self->echo_buffer_read_pos = 0; + } + if (self->echo_buffer_write_pos >= echo_buf_len) { + self->echo_buffer_write_pos = 0; + } } - } + */ } length -= n; diff --git a/shared-module/audiodelays/Echo.h b/shared-module/audiodelays/Echo.h index b538716c45fd7..f699b0934f4e2 100644 --- a/shared-module/audiodelays/Echo.h +++ b/shared-module/audiodelays/Echo.h @@ -28,7 +28,7 @@ typedef struct { uint8_t last_buf_idx; uint32_t buffer_len; // max buffer in bytes - uint32_t *sample_remaining_buffer; + uint8_t *sample_remaining_buffer; uint32_t sample_buffer_length; bool loop; From 70c0bf9254308526cab079e80c54ec0e989abd67 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Mon, 30 Sep 2024 10:25:59 -0500 Subject: [PATCH 11/23] 8 bit unsigned samples working --- shared-module/audiodelays/Echo.c | 172 ++++++++++++++++--------------- 1 file changed, 90 insertions(+), 82 deletions(-) diff --git a/shared-module/audiodelays/Echo.c b/shared-module/audiodelays/Echo.c index 9d8f0d8660152..d8e17c87b61ac 100644 --- a/shared-module/audiodelays/Echo.c +++ b/shared-module/audiodelays/Echo.c @@ -76,7 +76,7 @@ void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_ // Allocate the echo buffer for the max possible delay self->max_delay_ms = max_delay_ms; - self->max_echo_buffer_len = self->sample_rate / 1000.0f * max_delay_ms * (self->channel_count * (self->bits_per_sample / 8)); // bytes + self->max_echo_buffer_len = self->sample_rate / 1000.0f * max_delay_ms * (self->channel_count * 2);// (self->bits_per_sample / 8)); // bytes self->echo_buffer = m_malloc(self->max_echo_buffer_len); if (self->echo_buffer == NULL) { common_hal_audiodelays_echo_deinit(self); @@ -86,11 +86,11 @@ void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_ // calculate current echo buffer size we use for the given delay mp_float_t f_delay_ms = synthio_block_slot_get(&self->delay_ms); - self->echo_buffer_len = self->sample_rate / 1000.0f * f_delay_ms * (self->channel_count * (self->bits_per_sample / 8)); + self->echo_buffer_len = self->sample_rate / 1000.0f * f_delay_ms * (self->channel_count * 2);// (self->bits_per_sample / 8)); // read is where we store the incoming sample + previous echo // write is what we send to the outgoing buffer - self->echo_buffer_read_pos = self->buffer_len / (self->bits_per_sample / 8); + self->echo_buffer_read_pos = self->buffer_len / 2;// (self->bits_per_sample / 8); self->echo_buffer_write_pos = 0; } @@ -119,9 +119,9 @@ void common_hal_audiodelays_echo_set_delay_ms(audiodelays_echo_obj_t *self, mp_o 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); - self->echo_buffer_len = self->sample_rate / 1000.0f * f_delay_ms * (self->channel_count * (self->bits_per_sample / 8)); + self->echo_buffer_len = self->sample_rate / 1000.0f * f_delay_ms * (self->channel_count * 2);// (self->bits_per_sample / 8)); - uint32_t max_ebuf_length = self->echo_buffer_len / sizeof(uint32_t); + uint32_t max_ebuf_length = self->echo_buffer_len / sizeof(uint16_t); if (self->echo_buffer_read_pos > max_ebuf_length) { self->echo_buffer_read_pos = 0; @@ -249,12 +249,15 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * // 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, for 8 bit we can just use the buffer + // If we are using 16 bit samples we need a 16 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 echo buffer is always stored as a 16-bit value internally int16_t *echo_buffer = (int16_t *)self->echo_buffer; - uint32_t echo_buf_len = self->echo_buffer_len / (self->bits_per_sample / 8); + uint32_t echo_buf_len = self->echo_buffer_len / 2;// (self->bits_per_sample / 8); + // 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) { @@ -279,45 +282,38 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * // If we have no sample keep the echo echoing if (self->sample == NULL) { - if (MP_LIKELY(self->bits_per_sample == 16)) { - if (mix <= 0.01) { // Mix of 0 is pure sample sound. We have no sample so no sound - memset(word_buffer, 0, length * sizeof(uint16_t)); + if (mix <= 0.01) { // Mix of 0 is pure sample sound. We have no sample so no sound + if (self->samples_signed) { + memset(word_buffer, 0, length * (self->bits_per_sample / 8)); } else { - // Since we have no sample we can just iterate over the our entire buffer - for (uint32_t i = 0; i < length; i++) { - word_buffer[i] = echo_buffer[self->echo_buffer_read_pos++] * decay; - - echo_buffer[self->echo_buffer_write_pos++] = word_buffer[i]; - - word_buffer[i] = word_buffer[i] * mix; - - if (self->echo_buffer_read_pos >= echo_buf_len) { - self->echo_buffer_read_pos = 0; + memset(hword_buffer, 128, length * (self->bits_per_sample / 8)); + } + } else { + // Since we have no sample we can just iterate over the our entire remaining buffer + for (uint32_t i = 0; i < length; i++) { + int16_t echo = echo_buffer[self->echo_buffer_read_pos++] * decay; + echo_buffer[self->echo_buffer_write_pos++] = echo; + + if (MP_LIKELY(self->bits_per_sample == 16)) { + word_buffer[i] = echo * mix; + if (!self->samples_signed) { + word_buffer[i] ^= 0x8000; } - if (self->echo_buffer_write_pos >= echo_buf_len) { - self->echo_buffer_write_pos = 0; + } else { + echo = echo * mix; + hword_buffer[i] = echo; + if (!self->samples_signed) { + hword_buffer[i] ^= 0x80; } } - } - } else { // bits per sample is 8 - /* Still to be updated - uint16_t *hword_buffer = (uint16_t *)word_buffer; - uint16_t *echo_hsrc = (uint16_t *)self->echo_buffer; - for (uint32_t i = 0; i < length * 2; i++) { - uint32_t echo_word = unpack8(echo_hsrc[i]); - echo_word = echo_word * decay; - hword_buffer[i] = pack8(echo_word); - - echo_hsrc[self->echo_buffer_write_pos++] = hword_buffer[i]; - if (self->echo_buffer_read_pos >= echo_buf_len) { - self->echo_buffer_read_pos = 0; - } - if (self->echo_buffer_write_pos >= echo_buf_len) { - self->echo_buffer_write_pos = 0; - } + if (self->echo_buffer_read_pos >= echo_buf_len) { + self->echo_buffer_read_pos = 0; + } + if (self->echo_buffer_write_pos >= echo_buf_len) { + self->echo_buffer_write_pos = 0; } - */ + } } length = 0; @@ -328,62 +324,74 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * uint32_t n = MIN(self->sample_buffer_length, length); int16_t *sample_src = (int16_t *)self->sample_remaining_buffer; + int8_t *sample_hsrc = (int8_t *)self->sample_remaining_buffer; - if (MP_LIKELY(self->bits_per_sample == 16)) { - if (mix <= 0.01) { // if mix is zero pure sample only - for (uint32_t i = 0; i < n; i++) { + if (mix <= 0.01) { // if mix is zero pure sample only + for (uint32_t i = 0; i < n; i++) { + if (MP_LIKELY(self->bits_per_sample == 16)) { word_buffer[i] = sample_src[i]; + } else { + hword_buffer[i] = sample_hsrc[i]; } - } else { - for (uint32_t i = 0; i < n; i++) { - int32_t sample_word = sample_src[i]; - if (!self->samples_signed) { - sample_word = tosigned16(sample_word); + } + } else { + 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 { + // uint8_t s1 = sample_hsrc[i]; + // int8_t s2 = s1^0x80; + // sample_word = s2; + + sample_word = (int8_t)(((uint8_t)sample_hsrc[i]) ^ 0x80); } + } - int32_t word = (echo_buffer[self->echo_buffer_read_pos++] * decay) + sample_word; - word_buffer[i] = mix_down_sample(word); - - echo_buffer[self->echo_buffer_write_pos++] = word_buffer[i]; - - if (self->echo_buffer_read_pos >= echo_buf_len) { - self->echo_buffer_read_pos = 0; + int32_t echo = echo_buffer[self->echo_buffer_read_pos++] * decay; + int32_t word = echo + sample_word; + if (MP_LIKELY(self->bits_per_sample == 16)) { + word = mix_down_sample(word); + echo_buffer[self->echo_buffer_write_pos++] = (int16_t)word; + } else { + if (word > 127) { + word = 127; + } else if (word < -128) { + word = -128; } - if (self->echo_buffer_write_pos >= echo_buf_len) { - self->echo_buffer_write_pos = 0; - } - - word_buffer[i] = (sample_word * (1.0 - mix)) + (word_buffer[i] * mix); + echo_buffer[self->echo_buffer_write_pos++] = (int8_t)word; } - } - } else { // bits per sample is 8 - /* to be updated - uint16_t *hword_buffer = (uint16_t *)word_buffer; - uint16_t *sample_hsrc = (uint16_t *)sample_src; - uint16_t *echo_hsrc = (uint16_t *)self->echo_buffer; - for (uint32_t i = 0; i < n * 2; i++) { - uint32_t sample_word = unpack8(sample_hsrc[i]); - uint32_t echo_word = unpack8(echo_hsrc[i]); - if (MP_LIKELY(!self->samples_signed)) { - sample_word = tosigned16(sample_word); - } - echo_word = echo_word * decay; - sample_word = sample_word + echo_word; - hword_buffer[i] = pack8(sample_word); - echo_hsrc[self->echo_buffer_write_pos++] = pack8(sample_word + unpack8(hword_buffer[i])); - if (self->echo_buffer_read_pos >= echo_buf_len) { - self->echo_buffer_read_pos = 0; + if (MP_LIKELY(self->bits_per_sample == 16)) { + word_buffer[i] = (sample_word * (1.0 - mix)) + (word * mix); + if (!self->samples_signed) { + word_buffer[i] ^= 0x8000; } - if (self->echo_buffer_write_pos >= echo_buf_len) { - self->echo_buffer_write_pos = 0; + } else { + int8_t mixed = (sample_word * (1.0 - mix)) + (word * mix); + ; + if (self->samples_signed) { + hword_buffer[i] = mixed; + } else { + hword_buffer[i] = (uint8_t)mixed ^ 0x80; } } - */ + + if (self->echo_buffer_read_pos >= echo_buf_len) { + self->echo_buffer_read_pos = 0; + } + if (self->echo_buffer_write_pos >= echo_buf_len) { + self->echo_buffer_write_pos = 0; + } + } } length -= n; word_buffer += n; + hword_buffer += n; self->sample_remaining_buffer += n; self->sample_buffer_length -= n; } @@ -398,7 +406,7 @@ void audiodelays_echo_get_buffer_structure(audiodelays_echo_obj_t *self, bool si bool *single_buffer, bool *samples_signed, uint32_t *max_buffer_length, uint8_t *spacing) { *single_buffer = false; - *samples_signed = true; + *samples_signed = self->samples_signed; *max_buffer_length = self->buffer_len; if (single_channel_output) { *spacing = self->channel_count; From 00a149a2c137d1442f1c0264d315f4307a1a9b5b Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Mon, 30 Sep 2024 14:04:43 -0500 Subject: [PATCH 12/23] More comments and cleanup --- shared-module/audiodelays/Echo.c | 66 ++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/shared-module/audiodelays/Echo.c b/shared-module/audiodelays/Echo.c index d8e17c87b61ac..eb488ba9ed26a 100644 --- a/shared-module/audiodelays/Echo.c +++ b/shared-module/audiodelays/Echo.c @@ -61,7 +61,7 @@ void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_ synthio_block_assign_slot(decay, &self->decay, MP_QSTR_decay); if (delay_ms == MP_OBJ_NULL) { - delay_ms = mp_obj_new_float(0.05); + delay_ms = mp_obj_new_float(500.0); } synthio_block_assign_slot(delay_ms, &self->delay_ms, MP_QSTR_delay_ms); @@ -74,9 +74,9 @@ void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_ // A maximum length buffer was created and then the current echo length can be dynamically changes // without having to reallocate a large chunk of memory. - // Allocate the echo buffer for the max possible delay + // Allocate the echo buffer for the max possible delay, echo is always 16-bit self->max_delay_ms = max_delay_ms; - self->max_echo_buffer_len = self->sample_rate / 1000.0f * max_delay_ms * (self->channel_count * 2);// (self->bits_per_sample / 8)); // bytes + self->max_echo_buffer_len = self->sample_rate / 1000.0f * max_delay_ms * (self->channel_count * sizeof(uint16_t)); // bytes self->echo_buffer = m_malloc(self->max_echo_buffer_len); if (self->echo_buffer == NULL) { common_hal_audiodelays_echo_deinit(self); @@ -86,11 +86,11 @@ void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_ // calculate current echo buffer size we use for the given delay mp_float_t f_delay_ms = synthio_block_slot_get(&self->delay_ms); - self->echo_buffer_len = self->sample_rate / 1000.0f * f_delay_ms * (self->channel_count * 2);// (self->bits_per_sample / 8)); + self->echo_buffer_len = self->sample_rate / 1000.0f * f_delay_ms * (self->channel_count * sizeof(uint16_t)); // read is where we store the incoming sample + previous echo // write is what we send to the outgoing buffer - self->echo_buffer_read_pos = self->buffer_len / 2;// (self->bits_per_sample / 8); + self->echo_buffer_read_pos = self->buffer_len / sizeof(uint16_t); self->echo_buffer_write_pos = 0; } @@ -164,6 +164,9 @@ void audiodelays_echo_reset_buffer(audiodelays_echo_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->echo_buffer, 0, self->max_echo_buffer_len); } bool common_hal_audiodelays_echo_get_playing(audiodelays_echo_obj_t *self) { @@ -201,6 +204,7 @@ void common_hal_audiodelays_echo_play(audiodelays_echo_obj_t *self, mp_obj_t sam // 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; @@ -213,10 +217,10 @@ void common_hal_audiodelays_echo_stop(audiodelays_echo_obj_t *self) { return; } -#define RANGE_LOW (-28000) -#define RANGE_HIGH (28000) -#define RANGE_SHIFT (16) -#define RANGE_SCALE (0xfffffff / (32768 * 4 - RANGE_HIGH)) +#define RANGE_LOW_16 (-28000) +#define RANGE_HIGH_16 (28000) +#define RANGE_SHIFT_16 (16) +#define RANGE_SCALE_16 (0xfffffff / (32768 * 2 - RANGE_HIGH_16)) // 2 for echo+sample // dynamic range compression via a downward compressor with hard knee // @@ -231,10 +235,10 @@ void common_hal_audiodelays_echo_stop(audiodelays_echo_obj_t *self) { // https://en.wikipedia.org/wiki/Dynamic_range_compression static int16_t mix_down_sample(int32_t sample) { - if (sample < RANGE_LOW) { - sample = (((sample - RANGE_LOW) * RANGE_SCALE) >> RANGE_SHIFT) + RANGE_LOW; - } else if (sample > RANGE_HIGH) { - sample = (((sample - RANGE_HIGH) * RANGE_SCALE) >> RANGE_SHIFT) + RANGE_HIGH; + if (sample < RANGE_LOW_16) { + sample = (((sample - RANGE_LOW_16) * RANGE_SCALE_16) >> RANGE_SHIFT_16) + RANGE_LOW_16; + } else if (sample > RANGE_HIGH_16) { + sample = (((sample - RANGE_HIGH_16) * RANGE_SCALE_16) >> RANGE_SHIFT_16) + RANGE_HIGH_16; } return sample; } @@ -249,20 +253,18 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * // 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 + // 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 echo buffer is always stored as a 16-bit value internally int16_t *echo_buffer = (int16_t *)self->echo_buffer; - uint32_t echo_buf_len = self->echo_buffer_len / 2;// (self->bits_per_sample / 8); - + uint32_t echo_buf_len = self->echo_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 + // 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 @@ -286,10 +288,15 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * if (self->samples_signed) { memset(word_buffer, 0, length * (self->bits_per_sample / 8)); } else { - memset(hword_buffer, 128, length * (self->bits_per_sample / 8)); + // For unsigned samples set to the middle which is "quiet" + if (MP_LIKELY(self->bits_per_sample == 16)) { + memset(word_buffer, 32768, length * (self->bits_per_sample / 8)); + } else { + memset(hword_buffer, 128, length * (self->bits_per_sample / 8)); + } } } else { - // Since we have no sample we can just iterate over the our entire remaining buffer + // Since we have no sample we can just iterate over the our entire remaining buffer and finish for (uint32_t i = 0; i < length; i++) { int16_t echo = echo_buffer[self->echo_buffer_read_pos++] * decay; echo_buffer[self->echo_buffer_write_pos++] = echo; @@ -319,12 +326,11 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * length = 0; } else { // we have a sample to play and echo - // 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 = MIN(self->sample_buffer_length, length); - int16_t *sample_src = (int16_t *)self->sample_remaining_buffer; - int8_t *sample_hsrc = (int8_t *)self->sample_remaining_buffer; + 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 if (mix <= 0.01) { // if mix is zero pure sample only for (uint32_t i = 0; i < n; i++) { @@ -343,20 +349,19 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * if (self->samples_signed) { sample_word = sample_hsrc[i]; } else { - // uint8_t s1 = sample_hsrc[i]; - // int8_t s2 = s1^0x80; - // sample_word = s2; - + // 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); } } int32_t echo = echo_buffer[self->echo_buffer_read_pos++] * decay; int32_t word = echo + sample_word; + if (MP_LIKELY(self->bits_per_sample == 16)) { word = mix_down_sample(word); echo_buffer[self->echo_buffer_write_pos++] = (int16_t)word; } else { + // Do not have mix_down for 8 bit so just hard cap samples into 1 byte if (word > 127) { word = 127; } else if (word < -128) { @@ -372,7 +377,6 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * } } else { int8_t mixed = (sample_word * (1.0 - mix)) + (word * mix); - ; if (self->samples_signed) { hword_buffer[i] = mixed; } else { @@ -389,6 +393,7 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * } } + // 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; @@ -397,14 +402,19 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * } } + // 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; + + // Echo 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_echo_get_buffer_structure(audiodelays_echo_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; From 3c204608f6dca6cea5147403c74b9205d6aed5e3 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Mon, 30 Sep 2024 21:23:45 -0500 Subject: [PATCH 13/23] Fixed incorrectly updating sample remaining pointer --- shared-module/audiodelays/Echo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared-module/audiodelays/Echo.c b/shared-module/audiodelays/Echo.c index eb488ba9ed26a..6607ff2aaacd9 100644 --- a/shared-module/audiodelays/Echo.c +++ b/shared-module/audiodelays/Echo.c @@ -397,7 +397,7 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * length -= n; word_buffer += n; hword_buffer += n; - self->sample_remaining_buffer += n; + self->sample_remaining_buffer += (n * (self->bits_per_sample / 8)); self->sample_buffer_length -= n; } } From af9321777d97d93712ba80cda4501410e0ea3f4f Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Tue, 1 Oct 2024 19:44:55 -0500 Subject: [PATCH 14/23] Documentation --- shared-bindings/audiodelays/Echo.c | 67 +++++++++++++++++++++++------- shared-module/audiodelays/Echo.c | 2 +- 2 files changed, 54 insertions(+), 15 deletions(-) diff --git a/shared-bindings/audiodelays/Echo.c b/shared-bindings/audiodelays/Echo.c index 0119b15690f61..2704a2972fcf4 100644 --- a/shared-bindings/audiodelays/Echo.c +++ b/shared-bindings/audiodelays/Echo.c @@ -21,10 +21,55 @@ //| class Echo: //| """An Echo effect""" //| +//| def __init__( +//| self, +//| max_delay_ms: int = 500, +//| delay_ms: BlockInput = 250.0, +//| decay: BlockInput = 0.7, +//| mix: BlockInput = 0.5, +//| buffer_size: int = 512, +//| sample_rate: int = 8000, +//| bits_per_sample: int = 16, +//| samples_signed: bool = True, +//| channel_count: int = 1, +//| ) -> None: +//| """Create a Echo effect that echos an audio sample every set number of microseconds. +//| +//| :param int max_delay_ms: The maximum delay the echo can be +//| :param BlockInput delay_ms: The current echo delay +//| :param BlockInput decay: The rate the echo fades. 0.0 = instant; 1.0 = never. +//| :param BlockInput mix: The mix as a ratio of the sample (0.0) to the effect (1.0). +//| :param int buffer_size: The total size in bytes of the 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 echo 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) +//| echo = audiodelays.Echo(max_delay_ms=1000, delay_ms=850, decay=0.65, buffer_size=1024, channel_count=1, sample_rate=44100, mix=0.7) +//| echo.play(synth) +//| audio.play(echo) +//| +//| note = synthio.Note(261) +//| while True: +//| synth.press(note) +//| time.sleep(0.25) +//| synth.release(note) +//| time.sleep(5)""" +//| ... static mp_obj_t audiodelays_echo_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { enum { ARG_max_delay_ms, ARG_delay_ms, ARG_decay, ARG_mix, ARG_buffer_size, ARG_sample_rate, ARG_bits_per_sample, ARG_samples_signed, ARG_channel_count, }; static const mp_arg_t allowed_args[] = { - { MP_QSTR_max_delay_ms, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 50 } }, + { MP_QSTR_max_delay_ms, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 500 } }, { MP_QSTR_delay_ms, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_decay, 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} }, @@ -54,7 +99,7 @@ static mp_obj_t audiodelays_echo_make_new(const mp_obj_type_t *type, size_t n_ar } //| def deinit(self) -> None: -//| """Deinitialises the Echo and releases any hardware resources for reuse.""" +//| """Deinitialises the Echo.""" //| ... static mp_obj_t audiodelays_echo_deinit(mp_obj_t self_in) { audiodelays_echo_obj_t *self = MP_OBJ_TO_PTR(self_in); @@ -75,7 +120,7 @@ static void check_for_deinit(audiodelays_echo_obj_t *self) { // Provided by context manager helper. //| def __exit__(self) -> None: -//| """Automatically deinitializes the hardware when exiting a context. See +//| """Automatically deinitializes when exiting a context. See //| :ref:`lifetime-and-contextmanagers` for more info.""" //| ... static mp_obj_t audiodelays_echo_obj___exit__(size_t n_args, const mp_obj_t *args) { @@ -117,7 +162,7 @@ MP_PROPERTY_GETSET(audiodelays_echo_delay_ms_obj, (mp_obj_t)&audiodelays_echo_set_delay_ms_obj); //| decay: BlockInput -//| """The rate the echo decays between 0 and 1.""" +//| """The rate the echo decays between 0 and 1 where 1 is forever and 0 is no echo.""" static mp_obj_t audiodelays_echo_obj_get_decay(mp_obj_t self_in) { return common_hal_audiodelays_echo_get_decay(self_in); } @@ -143,7 +188,7 @@ MP_PROPERTY_GETSET(audiodelays_echo_decay_obj, (mp_obj_t)&audiodelays_echo_set_decay_obj); //| mix: BlockInput -//| """The rate the echo mix between 0 and 1.""" +//| """The rate the echo mix between 0 and 1 where 0 is only sample and 1 is all effect.""" static mp_obj_t audiodelays_echo_obj_get_mix(mp_obj_t self_in) { return common_hal_audiodelays_echo_get_mix(self_in); } @@ -168,10 +213,8 @@ MP_PROPERTY_GETSET(audiodelays_echo_mix_obj, (mp_obj_t)&audiodelays_echo_get_mix_obj, (mp_obj_t)&audiodelays_echo_set_mix_obj); - - //| playing: bool -//| """True when any voice is being output. (read-only)""" +//| """True when the effect is playing a sample. (read-only)""" static mp_obj_t audiodelays_echo_obj_get_playing(mp_obj_t self_in) { audiodelays_echo_obj_t *self = MP_OBJ_TO_PTR(self_in); check_for_deinit(self); @@ -188,9 +231,7 @@ MP_PROPERTY_GETTER(audiodelays_echo_playing_obj, //| """Plays the sample once when loop=False and continuously when loop=True. //| Does not block. Use `playing` to block. //| -//| Sample must be an `audiocore.WaveFile`, `audiocore.RawSample`, `audiomixer.Mixer` or `audiomp3.MP3Decoder`. -//| -//| The sample must match the Effect's encoding settings given in the constructor.""" +//| The sample must match the encoding settings given in the constructor.""" //| ... static mp_obj_t audiodelays_echo_obj_play(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_sample, ARG_loop }; @@ -212,7 +253,7 @@ static mp_obj_t audiodelays_echo_obj_play(size_t n_args, const mp_obj_t *pos_arg MP_DEFINE_CONST_FUN_OBJ_KW(audiodelays_echo_play_obj, 1, audiodelays_echo_obj_play); //| def stop(self) -> None: -//| """Stops playback of the sample.""" +//| """Stops playback of the sample. The echo continues playing.""" //| ... //| static mp_obj_t audiodelays_echo_obj_stop(mp_obj_t self_in) { @@ -223,8 +264,6 @@ static mp_obj_t audiodelays_echo_obj_stop(mp_obj_t self_in) { } MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_echo_stop_obj, audiodelays_echo_obj_stop); - - static const mp_rom_map_elem_t audiodelays_echo_locals_dict_table[] = { // Methods { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiodelays_echo_deinit_obj) }, diff --git a/shared-module/audiodelays/Echo.c b/shared-module/audiodelays/Echo.c index 6607ff2aaacd9..515317fb7cb8a 100644 --- a/shared-module/audiodelays/Echo.c +++ b/shared-module/audiodelays/Echo.c @@ -61,7 +61,7 @@ void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_ synthio_block_assign_slot(decay, &self->decay, MP_QSTR_decay); if (delay_ms == MP_OBJ_NULL) { - delay_ms = mp_obj_new_float(500.0); + delay_ms = mp_obj_new_float(250.0); } synthio_block_assign_slot(delay_ms, &self->delay_ms, MP_QSTR_delay_ms); From f5a8e3d1387a3c8da9dda5f7c82a1610f29024e9 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Thu, 3 Oct 2024 20:13:58 -0500 Subject: [PATCH 15/23] Calculate current delay for a BlockInput correctly --- shared-bindings/audiodelays/Echo.c | 4 ++-- shared-module/audiodelays/Echo.c | 33 +++++++++++++++++++++--------- shared-module/audiodelays/Echo.h | 3 +++ 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/shared-bindings/audiodelays/Echo.c b/shared-bindings/audiodelays/Echo.c index 2704a2972fcf4..8be7f2a4b6de8 100644 --- a/shared-bindings/audiodelays/Echo.c +++ b/shared-bindings/audiodelays/Echo.c @@ -33,7 +33,7 @@ //| samples_signed: bool = True, //| channel_count: int = 1, //| ) -> None: -//| """Create a Echo effect that echos an audio sample every set number of microseconds. +//| """Create a Echo effect that echos an audio sample every set number of milliseconds. //| //| :param int max_delay_ms: The maximum delay the echo can be //| :param BlockInput delay_ms: The current echo delay @@ -132,7 +132,7 @@ static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audiodelays_echo___exit___obj, 4, 4, //| delay_ms: BlockInput -//| """Delay of the echo in microseconds. (read-only)""" +//| """Delay of the echo in milliseconds. (read-only)""" //| static mp_obj_t audiodelays_echo_obj_get_delay_ms(mp_obj_t self_in) { audiodelays_echo_obj_t *self = MP_OBJ_TO_PTR(self_in); diff --git a/shared-module/audiodelays/Echo.c b/shared-module/audiodelays/Echo.c index 515317fb7cb8a..b66c682ef1b7c 100644 --- a/shared-module/audiodelays/Echo.c +++ b/shared-module/audiodelays/Echo.c @@ -86,10 +86,11 @@ void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_ // calculate current echo buffer size we use for the given delay mp_float_t f_delay_ms = synthio_block_slot_get(&self->delay_ms); + self->current_delay_ms = f_delay_ms; self->echo_buffer_len = self->sample_rate / 1000.0f * f_delay_ms * (self->channel_count * sizeof(uint16_t)); - // read is where we store the incoming sample + previous echo - // write is what we send to the outgoing buffer + // read is where we read previous echo from delay_ms ago to play back now + // write is where the store the latest playing sample to echo back later self->echo_buffer_read_pos = self->buffer_len / sizeof(uint16_t); self->echo_buffer_write_pos = 0; } @@ -119,17 +120,24 @@ void common_hal_audiodelays_echo_set_delay_ms(audiodelays_echo_obj_t *self, mp_o 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); - self->echo_buffer_len = self->sample_rate / 1000.0f * f_delay_ms * (self->channel_count * 2);// (self->bits_per_sample / 8)); - uint32_t max_ebuf_length = self->echo_buffer_len / sizeof(uint16_t); + recalculate_delay(self, f_delay_ms); +} + +void recalculate_delay(audiodelays_echo_obj_t *self, mp_float_t f_delay_ms) { + // Calculate the current echo buffer length in bytes - if (self->echo_buffer_read_pos > max_ebuf_length) { - self->echo_buffer_read_pos = 0; - self->echo_buffer_write_pos = max_ebuf_length - (self->buffer_len / sizeof(uint16_t)); - } else if (self->echo_buffer_write_pos > max_ebuf_length) { - self->echo_buffer_read_pos = self->buffer_len / sizeof(uint16_t); - self->echo_buffer_write_pos = 0; + uint32_t new_echo_buffer_len = self->sample_rate / 1000.0f * f_delay_ms * sizeof(uint16_t); + + // Check if our new echo is too long for our maximum buffer + if (new_echo_buffer_len > self->max_echo_buffer_len) { + return; + } else if (new_echo_buffer_len < 0.0) { // or too short! + return; } + + self->echo_buffer_len = new_echo_buffer_len; + self->current_delay_ms = f_delay_ms; } mp_obj_t common_hal_audiodelays_echo_get_decay(audiodelays_echo_obj_t *self) { @@ -250,6 +258,11 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * mp_float_t mix = MIN(1.0, MAX(synthio_block_slot_get(&self->mix), 0.0)); mp_float_t decay = MIN(1.0, MAX(synthio_block_slot_get(&self->decay), 0.0)); + uint32_t delay_ms = (uint32_t)synthio_block_slot_get(&self->delay_ms); + if (self->current_delay_ms != delay_ms) { + recalculate_delay(self, delay_ms); + } + // Switch our buffers to the other buffer self->last_buf_idx = !self->last_buf_idx; diff --git a/shared-module/audiodelays/Echo.h b/shared-module/audiodelays/Echo.h index f699b0934f4e2..42f039f343592 100644 --- a/shared-module/audiodelays/Echo.h +++ b/shared-module/audiodelays/Echo.h @@ -16,6 +16,7 @@ typedef struct { mp_obj_base_t base; uint32_t max_delay_ms; synthio_block_slot_t delay_ms; + uint32_t current_delay_ms; synthio_block_slot_t decay; synthio_block_slot_t mix; @@ -44,6 +45,8 @@ typedef struct { mp_obj_t sample; } audiodelays_echo_obj_t; +void recalculate_delay(audiodelays_echo_obj_t *self, mp_float_t f_delay_ms); + void audiodelays_echo_reset_buffer(audiodelays_echo_obj_t *self, bool single_channel_output, uint8_t channel); From cca8d00f6b5c04d1ee40afac507b963d81737267 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Sat, 5 Oct 2024 10:08:42 -0500 Subject: [PATCH 16/23] Doc and error messaging changes --- locale/circuitpython.pot | 14 +------------- shared-bindings/audiodelays/Echo.c | 7 +++---- shared-module/audiodelays/Echo.c | 8 ++++---- shared-module/audiomixer/MixerVoice.c | 8 ++++---- 4 files changed, 12 insertions(+), 25 deletions(-) diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index f0dfa2cc07b67..eca8fbf722775 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -1957,19 +1957,7 @@ msgid "The length of rgb_pins must be 6, 12, 18, 24, or 30" msgstr "" #: shared-module/audiodelays/Echo.c shared-module/audiomixer/MixerVoice.c -msgid "The sample's bits_per_sample does not match" -msgstr "" - -#: shared-module/audiodelays/Echo.c shared-module/audiomixer/MixerVoice.c -msgid "The sample's channel count does not match" -msgstr "" - -#: shared-module/audiodelays/Echo.c shared-module/audiomixer/MixerVoice.c -msgid "The sample's sample rate does not match" -msgstr "" - -#: shared-module/audiodelays/Echo.c shared-module/audiomixer/MixerVoice.c -msgid "The sample's signedness does not match" +msgid "The sample's %q does not match" msgstr "" #: supervisor/shared/safe_mode.c diff --git a/shared-bindings/audiodelays/Echo.c b/shared-bindings/audiodelays/Echo.c index 8be7f2a4b6de8..647f4dd818771 100644 --- a/shared-bindings/audiodelays/Echo.c +++ b/shared-bindings/audiodelays/Echo.c @@ -14,6 +14,7 @@ #include "py/objproperty.h" #include "py/runtime.h" #include "shared-bindings/util.h" +#include "shared-module/synthio/block.h" #define DECAY_DEFAULT 0.7f #define MIX_DEFAULT 0.5f @@ -39,7 +40,7 @@ //| :param BlockInput delay_ms: The current echo delay //| :param BlockInput decay: The rate the echo fades. 0.0 = instant; 1.0 = never. //| :param BlockInput mix: The mix as a ratio of the sample (0.0) to the effect (1.0). -//| :param int buffer_size: The total size in bytes of the buffers to use +//| :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 @@ -225,9 +226,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_echo_get_playing_obj, audiodelays_echo_obj MP_PROPERTY_GETTER(audiodelays_echo_playing_obj, (mp_obj_t)&audiodelays_echo_get_playing_obj); -//| def play( -//| self, sample: circuitpython_typing.AudioSample, *, voice: int = 0, loop: bool = False -//| ) -> None: +//| 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. //| diff --git a/shared-module/audiodelays/Echo.c b/shared-module/audiodelays/Echo.c index b66c682ef1b7c..df6c2e8b05513 100644 --- a/shared-module/audiodelays/Echo.c +++ b/shared-module/audiodelays/Echo.c @@ -187,13 +187,13 @@ void common_hal_audiodelays_echo_play(audiodelays_echo_obj_t *self, mp_obj_t sam // The get_buffer function will actually process that data if (audiosample_sample_rate(sample) != self->sample_rate) { - mp_raise_ValueError(MP_ERROR_TEXT("The sample's sample rate does not match")); + 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(MP_ERROR_TEXT("The sample's channel count does not match")); + 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(MP_ERROR_TEXT("The sample's bits_per_sample does not match")); + 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; @@ -201,7 +201,7 @@ void common_hal_audiodelays_echo_play(audiodelays_echo_obj_t *self, mp_obj_t sam 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(MP_ERROR_TEXT("The sample's signedness does not match")); + mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_signedness); } self->sample = sample; diff --git a/shared-module/audiomixer/MixerVoice.c b/shared-module/audiomixer/MixerVoice.c index f43a261d89685..cba7544b1e65b 100644 --- a/shared-module/audiomixer/MixerVoice.c +++ b/shared-module/audiomixer/MixerVoice.c @@ -32,13 +32,13 @@ void common_hal_audiomixer_mixervoice_set_level(audiomixer_mixervoice_obj_t *sel void common_hal_audiomixer_mixervoice_play(audiomixer_mixervoice_obj_t *self, mp_obj_t sample, bool loop) { if (audiosample_sample_rate(sample) != self->parent->sample_rate) { - mp_raise_ValueError(MP_ERROR_TEXT("The sample's sample rate does not match")); + mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_sample_rate); } if (audiosample_channel_count(sample) != self->parent->channel_count) { - mp_raise_ValueError(MP_ERROR_TEXT("The sample's channel count does not match")); + 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->parent->bits_per_sample) { - mp_raise_ValueError(MP_ERROR_TEXT("The sample's bits_per_sample does not match")); + 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; @@ -47,7 +47,7 @@ void common_hal_audiomixer_mixervoice_play(audiomixer_mixervoice_obj_t *self, mp audiosample_get_buffer_structure(sample, false, &single_buffer, &samples_signed, &max_buffer_length, &spacing); if (samples_signed != self->parent->samples_signed) { - mp_raise_ValueError(MP_ERROR_TEXT("The sample's signedness does not match")); + mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_signedness); } self->sample = sample; self->loop = loop; From 146c13e6cecbe201c96529e1b4e51a9759cb9eeb Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Sat, 5 Oct 2024 11:25:27 -0500 Subject: [PATCH 17/23] Improved documentation --- shared-bindings/audiodelays/Echo.c | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/shared-bindings/audiodelays/Echo.c b/shared-bindings/audiodelays/Echo.c index 647f4dd818771..d5371ba788512 100644 --- a/shared-bindings/audiodelays/Echo.c +++ b/shared-bindings/audiodelays/Echo.c @@ -25,21 +25,29 @@ //| def __init__( //| self, //| max_delay_ms: int = 500, -//| delay_ms: BlockInput = 250.0, -//| decay: BlockInput = 0.7, -//| mix: BlockInput = 0.5, +//| delay_ms: synthio.BlockInput = 250.0, +//| decay: synthio.BlockInput = 0.7, +//| mix: synthio.BlockInput = 0.5, //| buffer_size: int = 512, //| sample_rate: int = 8000, //| bits_per_sample: int = 16, //| samples_signed: bool = True, //| channel_count: int = 1, //| ) -> None: -//| """Create a Echo effect that echos an audio sample every set number of milliseconds. +//| """Create a Echo effect where you hear the original sample play back, at a lesser volume after +//| a set number of millisecond delay. The delay timing of the echo 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 delay the echo can be -//| :param BlockInput delay_ms: The current echo delay -//| :param BlockInput decay: The rate the echo fades. 0.0 = instant; 1.0 = never. -//| :param BlockInput mix: The mix as a ratio of the sample (0.0) to the effect (1.0). +//| Each time the echo plays back the volume is reduced by the decay setting (echo * decay). +//| +//| The mix parameter allows you to change how much of the unchanged sample passes through to +//| the output to how much of the effect audio you hear as the output. +//| +//| :param int max_delay_ms: The maximum time the echo can be in milliseconds +//| :param synthio.BlockInput delay_ms: The current time of the echo delay in milliseconds. Must be less the max_delay_ms +//| :param synthio.BlockInput decay: The rate the echo fades. 0.0 = instant; 1.0 = never. +//| :param synthio.BlockInput mix: The mix as a ratio of the sample (0.0) to the effect (1.0). //| :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. @@ -132,7 +140,7 @@ static mp_obj_t audiodelays_echo_obj___exit__(size_t n_args, const mp_obj_t *arg static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audiodelays_echo___exit___obj, 4, 4, audiodelays_echo_obj___exit__); -//| delay_ms: BlockInput +//| delay_ms: synthio.BlockInput //| """Delay of the echo in milliseconds. (read-only)""" //| static mp_obj_t audiodelays_echo_obj_get_delay_ms(mp_obj_t self_in) { @@ -162,7 +170,7 @@ MP_PROPERTY_GETSET(audiodelays_echo_delay_ms_obj, (mp_obj_t)&audiodelays_echo_get_delay_ms_obj, (mp_obj_t)&audiodelays_echo_set_delay_ms_obj); -//| decay: BlockInput +//| decay: synthio.BlockInput //| """The rate the echo decays between 0 and 1 where 1 is forever and 0 is no echo.""" static mp_obj_t audiodelays_echo_obj_get_decay(mp_obj_t self_in) { return common_hal_audiodelays_echo_get_decay(self_in); @@ -188,7 +196,7 @@ MP_PROPERTY_GETSET(audiodelays_echo_decay_obj, (mp_obj_t)&audiodelays_echo_get_decay_obj, (mp_obj_t)&audiodelays_echo_set_decay_obj); -//| mix: BlockInput +//| 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_echo_obj_get_mix(mp_obj_t self_in) { return common_hal_audiodelays_echo_get_mix(self_in); From 0fa3cd8632f0cf7cdcedd89f6566f6e2909591cf Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Sat, 5 Oct 2024 18:03:03 -0500 Subject: [PATCH 18/23] Removed utils.h which was not required --- shared-module/audiodelays/Echo.c | 2 - shared-module/audiomixer/utils.h | 90 -------------------------------- 2 files changed, 92 deletions(-) delete mode 100644 shared-module/audiomixer/utils.h diff --git a/shared-module/audiodelays/Echo.c b/shared-module/audiodelays/Echo.c index df6c2e8b05513..c234b10531c96 100644 --- a/shared-module/audiodelays/Echo.c +++ b/shared-module/audiodelays/Echo.c @@ -7,8 +7,6 @@ #include #include "py/runtime.h" -#include "shared-module/audiomixer/utils.h" - void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_t max_delay_ms, mp_obj_t delay_ms, mp_obj_t decay, mp_obj_t mix, diff --git a/shared-module/audiomixer/utils.h b/shared-module/audiomixer/utils.h deleted file mode 100644 index f7dbdac7dd382..0000000000000 --- a/shared-module/audiomixer/utils.h +++ /dev/null @@ -1,90 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2018 Scott Shawcroft for Adafruit Industries, 2024 Mark Komus -// -// SPDX-License-Identifier: MIT - -__attribute__((always_inline)) -static inline uint32_t add16signed(uint32_t a, uint32_t b) { - #if (defined(__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) - return __QADD16(a, b); - #else - uint32_t result = 0; - for (int8_t i = 0; i < 2; i++) { - int16_t ai = a >> (sizeof(int16_t) * 8 * i); - int16_t bi = b >> (sizeof(int16_t) * 8 * i); - int32_t intermediate = (int32_t)ai + bi; - if (intermediate > SHRT_MAX) { - intermediate = SHRT_MAX; - } else if (intermediate < SHRT_MIN) { - intermediate = SHRT_MIN; - } - result |= (((uint32_t)intermediate) & 0xffff) << (sizeof(int16_t) * 8 * i); - } - return result; - #endif -} - -__attribute__((always_inline)) -static inline uint32_t mult16signed(uint32_t val, int32_t mul) { - #if (defined(__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) - mul <<= 16; - int32_t hi, lo; - enum { bits = 16 }; // saturate to 16 bits - enum { shift = 15 }; // shift is done automatically - asm volatile ("smulwb %0, %1, %2" : "=r" (lo) : "r" (mul), "r" (val)); - asm volatile ("smulwt %0, %1, %2" : "=r" (hi) : "r" (mul), "r" (val)); - asm volatile ("ssat %0, %1, %2, asr %3" : "=r" (lo) : "I" (bits), "r" (lo), "I" (shift)); - asm volatile ("ssat %0, %1, %2, asr %3" : "=r" (hi) : "I" (bits), "r" (hi), "I" (shift)); - asm volatile ("pkhbt %0, %1, %2, lsl #16" : "=r" (val) : "r" (lo), "r" (hi)); // pack - return val; - #else - uint32_t result = 0; - float mod_mul = (float)mul / (float)((1 << 15) - 1); - for (int8_t i = 0; i < 2; i++) { - int16_t ai = (val >> (sizeof(uint16_t) * 8 * i)); - int32_t intermediate = (int32_t)(ai * mod_mul); - if (intermediate > SHRT_MAX) { - intermediate = SHRT_MAX; - } else if (intermediate < SHRT_MIN) { - intermediate = SHRT_MIN; - } - intermediate &= 0x0000FFFF; - result |= (((uint32_t)intermediate)) << (sizeof(int16_t) * 8 * i); - } - return result; - #endif -} - - -static inline uint32_t tounsigned8(uint32_t val) { - #if (defined(__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) - return __UADD8(val, 0x80808080); - #else - return val ^ 0x80808080; - #endif -} - -static inline uint32_t tounsigned16(uint32_t val) { - #if (defined(__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) - return __UADD16(val, 0x80008000); - #else - return val ^ 0x80008000; - #endif -} - -static inline uint32_t tosigned16(uint32_t val) { - #if (defined(__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) - return __UADD16(val, 0x80008000); - #else - return val ^ 0x80008000; - #endif -} - -static inline uint32_t unpack8(uint16_t val) { - return ((val & 0xff00) << 16) | ((val & 0x00ff) << 8); -} - -static inline uint32_t pack8(uint32_t val) { - return ((val & 0xff000000) >> 16) | ((val & 0xff00) >> 8); -} From 48e1327c0eb94decaf209c76d30cc2f8fcc18f8d Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Sun, 6 Oct 2024 20:47:13 -0500 Subject: [PATCH 19/23] Fix about incorrectly recalculating delay --- shared-module/audiodelays/Echo.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/shared-module/audiodelays/Echo.c b/shared-module/audiodelays/Echo.c index c234b10531c96..a5330098352a3 100644 --- a/shared-module/audiodelays/Echo.c +++ b/shared-module/audiodelays/Echo.c @@ -125,7 +125,7 @@ void common_hal_audiodelays_echo_set_delay_ms(audiodelays_echo_obj_t *self, mp_o void recalculate_delay(audiodelays_echo_obj_t *self, mp_float_t f_delay_ms) { // Calculate the current echo buffer length in bytes - uint32_t new_echo_buffer_len = self->sample_rate / 1000.0f * f_delay_ms * sizeof(uint16_t); + uint32_t new_echo_buffer_len = self->sample_rate / 1000.0f * f_delay_ms * (self->channel_count * sizeof(uint16_t)); // Check if our new echo is too long for our maximum buffer if (new_echo_buffer_len > self->max_echo_buffer_len) { @@ -134,6 +134,11 @@ void recalculate_delay(audiodelays_echo_obj_t *self, mp_float_t f_delay_ms) { return; } + // If the echo buffer is larger then our audio buffer weird things happen + if (new_echo_buffer_len < self->buffer_len) { + return; + } + self->echo_buffer_len = new_echo_buffer_len; self->current_delay_ms = f_delay_ms; } From af947a50845ebdd1b574e9d2fac02e8c114251ed Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Mon, 7 Oct 2024 20:10:55 -0500 Subject: [PATCH 20/23] Enable for all 2350 boards --- ports/raspberrypi/boards/raspberry_pi_pico2/mpconfigboard.mk | 1 - ports/raspberrypi/mpconfigport.mk | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ports/raspberrypi/boards/raspberry_pi_pico2/mpconfigboard.mk b/ports/raspberrypi/boards/raspberry_pi_pico2/mpconfigboard.mk index dbb6801d6a3b2..43328dd47094d 100644 --- a/ports/raspberrypi/boards/raspberry_pi_pico2/mpconfigboard.mk +++ b/ports/raspberrypi/boards/raspberry_pi_pico2/mpconfigboard.mk @@ -10,4 +10,3 @@ CHIP_FAMILY = rp2 EXTERNAL_FLASH_DEVICES = "W25Q32JVxQ" CIRCUITPY__EVE = 1 -CIRCUITPY_AUDIOEFFECTS = 1 diff --git a/ports/raspberrypi/mpconfigport.mk b/ports/raspberrypi/mpconfigport.mk index 6c8b7551e7892..d619e78bd97a8 100644 --- a/ports/raspberrypi/mpconfigport.mk +++ b/ports/raspberrypi/mpconfigport.mk @@ -64,6 +64,9 @@ CIRCUITPY_PICODVI ?= 1 # Our generic touchio uses a pull down and RP2350 A2 hardware doesn't work correctly. # So, turn touchio off because it doesn't work. CIRCUITPY_TOUCHIO = 0 + +# Audio effects +CIRCUITPY_AUDIOEFFECTS ?= 1 endif INTERNAL_LIBM = 1 From 44645312795a7808236d71f03d5c3804a3d294d0 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Sat, 12 Oct 2024 10:30:42 -0500 Subject: [PATCH 21/23] Rename defn to add audiodelays --- ports/raspberrypi/mpconfigport.mk | 2 +- py/circuitpy_defns.mk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/raspberrypi/mpconfigport.mk b/ports/raspberrypi/mpconfigport.mk index d619e78bd97a8..4f900821cf1f5 100644 --- a/ports/raspberrypi/mpconfigport.mk +++ b/ports/raspberrypi/mpconfigport.mk @@ -66,7 +66,7 @@ CIRCUITPY_PICODVI ?= 1 CIRCUITPY_TOUCHIO = 0 # Audio effects -CIRCUITPY_AUDIOEFFECTS ?= 1 +CIRCUITPY_AUDIODELAYS ?= 1 endif INTERNAL_LIBM = 1 diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index 19f7cfabf044d..c2c9ffa6736e8 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -131,7 +131,7 @@ endif ifeq ($(CIRCUITPY_AUDIOCORE),1) SRC_PATTERNS += audiocore/% endif -ifeq ($(CIRCUITPY_AUDIOEFFECTS),1) +ifeq ($(CIRCUITPY_AUDIODELAYS),1) SRC_PATTERNS += audiodelays/% endif ifeq ($(CIRCUITPY_AUDIOMIXER),1) From e3228d00a94c9e15e20d5caa5cd77b6c6cd65d63 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Mon, 14 Oct 2024 10:00:08 -0500 Subject: [PATCH 22/23] Added frequency shift on echo option from @dcooperdalrymple --- shared-bindings/audiodelays/Echo.c | 39 ++++++- shared-bindings/audiodelays/Echo.h | 5 +- shared-module/audiodelays/Echo.c | 163 ++++++++++++++++++++++------- shared-module/audiodelays/Echo.h | 7 +- 4 files changed, 172 insertions(+), 42 deletions(-) diff --git a/shared-bindings/audiodelays/Echo.c b/shared-bindings/audiodelays/Echo.c index d5371ba788512..c0f1556ed75cc 100644 --- a/shared-bindings/audiodelays/Echo.c +++ b/shared-bindings/audiodelays/Echo.c @@ -53,6 +53,7 @@ //| :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) +//| :param bool freq_shift: Do echos change frequency as the echo delay changes //| //| Playing adding an echo to a synth:: //| @@ -64,7 +65,7 @@ //| //| audio = audiobusio.I2SOut(bit_clock=board.GP20, word_select=board.GP21, data=board.GP22) //| synth = synthio.Synthesizer(channel_count=1, sample_rate=44100) -//| echo = audiodelays.Echo(max_delay_ms=1000, delay_ms=850, decay=0.65, buffer_size=1024, channel_count=1, sample_rate=44100, mix=0.7) +//| echo = audiodelays.Echo(max_delay_ms=1000, delay_ms=850, decay=0.65, buffer_size=1024, channel_count=1, sample_rate=44100, mix=0.7, freq_shift=False) //| echo.play(synth) //| audio.play(echo) //| @@ -76,7 +77,7 @@ //| time.sleep(5)""" //| ... static mp_obj_t audiodelays_echo_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - enum { ARG_max_delay_ms, ARG_delay_ms, ARG_decay, ARG_mix, ARG_buffer_size, ARG_sample_rate, ARG_bits_per_sample, ARG_samples_signed, ARG_channel_count, }; + enum { ARG_max_delay_ms, ARG_delay_ms, ARG_decay, ARG_mix, ARG_buffer_size, ARG_sample_rate, ARG_bits_per_sample, ARG_samples_signed, ARG_channel_count, ARG_freq_shift, }; static const mp_arg_t allowed_args[] = { { MP_QSTR_max_delay_ms, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 500 } }, { MP_QSTR_delay_ms, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} }, @@ -87,6 +88,7 @@ static mp_obj_t audiodelays_echo_make_new(const mp_obj_type_t *type, size_t n_ar { 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_QSTR_freq_shift, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = true } }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; @@ -102,7 +104,7 @@ static mp_obj_t audiodelays_echo_make_new(const mp_obj_type_t *type, size_t n_ar } audiodelays_echo_obj_t *self = mp_obj_malloc(audiodelays_echo_obj_t, &audiodelays_echo_type); - common_hal_audiodelays_echo_construct(self, max_delay_ms, args[ARG_delay_ms].u_obj, args[ARG_decay].u_obj, args[ARG_mix].u_obj, args[ARG_buffer_size].u_int, bits_per_sample, args[ARG_samples_signed].u_bool, channel_count, sample_rate); + common_hal_audiodelays_echo_construct(self, max_delay_ms, args[ARG_delay_ms].u_obj, args[ARG_decay].u_obj, args[ARG_mix].u_obj, args[ARG_buffer_size].u_int, bits_per_sample, args[ARG_samples_signed].u_bool, channel_count, sample_rate, args[ARG_freq_shift].u_bool); return MP_OBJ_FROM_PTR(self); } @@ -222,6 +224,36 @@ MP_PROPERTY_GETSET(audiodelays_echo_mix_obj, (mp_obj_t)&audiodelays_echo_get_mix_obj, (mp_obj_t)&audiodelays_echo_set_mix_obj); + + +//| freq_shift: bool +//| """Does the echo change frequencies as the delay changes.""" +static mp_obj_t audiodelays_echo_obj_get_freq_shift(mp_obj_t self_in) { + return mp_obj_new_bool(common_hal_audiodelays_echo_get_freq_shift(self_in)); +} +MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_echo_get_freq_shift_obj, audiodelays_echo_obj_get_freq_shift); + +static mp_obj_t audiodelays_echo_obj_set_freq_shift(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_freq_shift }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_freq_shift, MP_ARG_BOOL | MP_ARG_REQUIRED, {} }, + }; + audiodelays_echo_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + 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); + + common_hal_audiodelays_echo_set_freq_shift(self, args[ARG_freq_shift].u_bool); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(audiodelays_echo_set_freq_shift_obj, 1, audiodelays_echo_obj_set_freq_shift); + +MP_PROPERTY_GETSET(audiodelays_echo_freq_shift_obj, + (mp_obj_t)&audiodelays_echo_get_freq_shift_obj, + (mp_obj_t)&audiodelays_echo_set_freq_shift_obj); + + + //| playing: bool //| """True when the effect is playing a sample. (read-only)""" static mp_obj_t audiodelays_echo_obj_get_playing(mp_obj_t self_in) { @@ -284,6 +316,7 @@ static const mp_rom_map_elem_t audiodelays_echo_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_delay_ms), MP_ROM_PTR(&audiodelays_echo_delay_ms_obj) }, { MP_ROM_QSTR(MP_QSTR_decay), MP_ROM_PTR(&audiodelays_echo_decay_obj) }, { MP_ROM_QSTR(MP_QSTR_mix), MP_ROM_PTR(&audiodelays_echo_mix_obj) }, + { MP_ROM_QSTR(MP_QSTR_freq_shift), MP_ROM_PTR(&audiodelays_echo_freq_shift_obj) }, }; static MP_DEFINE_CONST_DICT(audiodelays_echo_locals_dict, audiodelays_echo_locals_dict_table); diff --git a/shared-bindings/audiodelays/Echo.h b/shared-bindings/audiodelays/Echo.h index a1035a7ea7a03..b276a328b3a21 100644 --- a/shared-bindings/audiodelays/Echo.h +++ b/shared-bindings/audiodelays/Echo.h @@ -13,7 +13,7 @@ extern const mp_obj_type_t audiodelays_echo_type; void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_t max_delay_ms, mp_obj_t delay_ms, mp_obj_t decay, mp_obj_t mix, uint32_t buffer_size, uint8_t bits_per_sample, bool samples_signed, - uint8_t channel_count, uint32_t sample_rate); + uint8_t channel_count, uint32_t sample_rate, bool freq_shift); void common_hal_audiodelays_echo_deinit(audiodelays_echo_obj_t *self); bool common_hal_audiodelays_echo_deinited(audiodelays_echo_obj_t *self); @@ -25,6 +25,9 @@ uint8_t common_hal_audiodelays_echo_get_bits_per_sample(audiodelays_echo_obj_t * mp_obj_t common_hal_audiodelays_echo_get_delay_ms(audiodelays_echo_obj_t *self); void common_hal_audiodelays_echo_set_delay_ms(audiodelays_echo_obj_t *self, mp_obj_t delay_ms); +bool common_hal_audiodelays_echo_get_freq_shift(audiodelays_echo_obj_t *self); +void common_hal_audiodelays_echo_set_freq_shift(audiodelays_echo_obj_t *self, bool freq_shift); + mp_obj_t common_hal_audiodelays_echo_get_decay(audiodelays_echo_obj_t *self); void common_hal_audiodelays_echo_set_decay(audiodelays_echo_obj_t *self, mp_obj_t decay); diff --git a/shared-module/audiodelays/Echo.c b/shared-module/audiodelays/Echo.c index a5330098352a3..3f45162cb5e93 100644 --- a/shared-module/audiodelays/Echo.c +++ b/shared-module/audiodelays/Echo.c @@ -1,6 +1,6 @@ // This file is part of the CircuitPython project: https://circuitpython.org // -// SPDX-FileCopyrightText: Copyright (c) 2024 Mark Komus +// SPDX-FileCopyrightText: Copyright (c) 2024 Mark Komus, Cooper Dalrymple // // SPDX-License-Identifier: MIT #include "shared-bindings/audiodelays/Echo.h" @@ -11,7 +11,10 @@ void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_t max_delay_ms, mp_obj_t delay_ms, mp_obj_t decay, mp_obj_t mix, uint32_t buffer_size, uint8_t bits_per_sample, - bool samples_signed, uint8_t channel_count, uint32_t sample_rate) { + bool samples_signed, uint8_t channel_count, uint32_t sample_rate, bool freq_shift) { + + // Set whether the echo shifts frequencies as the delay changes like a doppler effect + self->freq_shift = freq_shift; // Basic settings every effect and audio sample has // These are the effects values, not the source sample(s) @@ -82,15 +85,17 @@ void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_ } memset(self->echo_buffer, 0, self->max_echo_buffer_len); - // calculate current echo buffer size we use for the given delay + // calculate everything needed for the current delay mp_float_t f_delay_ms = synthio_block_slot_get(&self->delay_ms); - self->current_delay_ms = f_delay_ms; - self->echo_buffer_len = self->sample_rate / 1000.0f * f_delay_ms * (self->channel_count * sizeof(uint16_t)); + recalculate_delay(self, f_delay_ms); // read is where we read previous echo from delay_ms ago to play back now // write is where the store the latest playing sample to echo back later self->echo_buffer_read_pos = self->buffer_len / sizeof(uint16_t); self->echo_buffer_write_pos = 0; + + // where we read the previous echo from delay_ms ago to play back now (for freq shift) + self->echo_buffer_left_pos = self->echo_buffer_right_pos = 0; } bool common_hal_audiodelays_echo_deinited(audiodelays_echo_obj_t *self) { @@ -109,7 +114,6 @@ void common_hal_audiodelays_echo_deinit(audiodelays_echo_obj_t *self) { self->buffer[1] = NULL; } - mp_obj_t common_hal_audiodelays_echo_get_delay_ms(audiodelays_echo_obj_t *self) { return self->delay_ms.obj; } @@ -123,23 +127,32 @@ void common_hal_audiodelays_echo_set_delay_ms(audiodelays_echo_obj_t *self, mp_o } void recalculate_delay(audiodelays_echo_obj_t *self, mp_float_t f_delay_ms) { - // Calculate the current echo buffer length in bytes + if (self->freq_shift) { + // Calculate the rate of iteration over the echo buffer with 8 sub-bits + self->echo_buffer_rate = MAX(self->max_delay_ms / f_delay_ms * 256.0f, 1.0); + self->echo_buffer_len = self->max_echo_buffer_len; + } else { + // Calculate the current echo buffer length in bytes + uint32_t new_echo_buffer_len = self->sample_rate / 1000.0f * f_delay_ms * (self->channel_count * sizeof(uint16_t)); + + // Check if our new echo is too long for our maximum buffer + if (new_echo_buffer_len > self->max_echo_buffer_len) { + return; + } else if (new_echo_buffer_len < 0.0) { // or too short! + return; + } - uint32_t new_echo_buffer_len = self->sample_rate / 1000.0f * f_delay_ms * (self->channel_count * sizeof(uint16_t)); + // If the echo buffer is larger then our audio buffer weird things happen + if (new_echo_buffer_len < self->buffer_len) { + return; + } - // Check if our new echo is too long for our maximum buffer - if (new_echo_buffer_len > self->max_echo_buffer_len) { - return; - } else if (new_echo_buffer_len < 0.0) { // or too short! - return; - } + self->echo_buffer_len = new_echo_buffer_len; - // If the echo buffer is larger then our audio buffer weird things happen - if (new_echo_buffer_len < self->buffer_len) { - return; + // Clear the now unused part of the buffer or some weird artifacts appear + memset(self->echo_buffer + self->echo_buffer_len, 0, self->max_echo_buffer_len - self->echo_buffer_len); } - self->echo_buffer_len = new_echo_buffer_len; self->current_delay_ms = f_delay_ms; } @@ -159,6 +172,16 @@ void common_hal_audiodelays_echo_set_mix(audiodelays_echo_obj_t *self, mp_obj_t synthio_block_assign_slot(arg, &self->mix, MP_QSTR_mix); } +bool common_hal_audiodelays_echo_get_freq_shift(audiodelays_echo_obj_t *self) { + return self->freq_shift; +} + +void common_hal_audiodelays_echo_set_freq_shift(audiodelays_echo_obj_t *self, bool freq_shift) { + self->freq_shift = freq_shift; + uint32_t delay_ms = (uint32_t)synthio_block_slot_get(&self->delay_ms); + recalculate_delay(self, delay_ms); +} + uint32_t common_hal_audiodelays_echo_get_sample_rate(audiodelays_echo_obj_t *self) { return self->sample_rate; } @@ -257,6 +280,10 @@ int16_t mix_down_sample(int32_t sample) { audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t *self, bool single_channel_output, uint8_t channel, uint8_t **buffer, uint32_t *buffer_length) { + if (!single_channel_output) { + channel = 0; + } + // get the effect values we need from the BlockInput. These may change at run time so you need to do bounds checking if required mp_float_t mix = MIN(1.0, MAX(synthio_block_slot_get(&self->mix), 0.0)); mp_float_t decay = MIN(1.0, MAX(synthio_block_slot_get(&self->decay), 0.0)); @@ -278,6 +305,15 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * int16_t *echo_buffer = (int16_t *)self->echo_buffer; uint32_t echo_buf_len = self->echo_buffer_len / sizeof(uint16_t); + // Set our echo buffer position accounting for stereo + uint32_t echo_buffer_pos = 0; + if (self->freq_shift) { + echo_buffer_pos = self->echo_buffer_left_pos; + if (channel == 1) { + echo_buffer_pos = self->echo_buffer_right_pos; + } + } + // 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 @@ -314,27 +350,46 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * } else { // Since we have no sample we can just iterate over the our entire remaining buffer and finish for (uint32_t i = 0; i < length; i++) { - int16_t echo = echo_buffer[self->echo_buffer_read_pos++] * decay; - echo_buffer[self->echo_buffer_write_pos++] = echo; + int16_t echo, word = 0; + uint32_t next_buffer_pos = 0; + + if (self->freq_shift) { + echo = echo_buffer[echo_buffer_pos >> 8]; + next_buffer_pos = echo_buffer_pos + self->echo_buffer_rate; + + word = echo * decay; + for (uint32_t j = echo_buffer_pos >> 8; j < next_buffer_pos >> 8; j++) { + echo_buffer[j % echo_buf_len] = word; + } + } else { + echo = echo_buffer[self->echo_buffer_read_pos++]; + word = echo * decay; + echo_buffer[self->echo_buffer_write_pos++] = word; + } + + word = echo * mix; if (MP_LIKELY(self->bits_per_sample == 16)) { - word_buffer[i] = echo * mix; + word_buffer[i] = word; if (!self->samples_signed) { word_buffer[i] ^= 0x8000; } } else { - echo = echo * mix; - hword_buffer[i] = echo; + hword_buffer[i] = (int8_t)word; if (!self->samples_signed) { hword_buffer[i] ^= 0x80; } } - if (self->echo_buffer_read_pos >= echo_buf_len) { - self->echo_buffer_read_pos = 0; - } - if (self->echo_buffer_write_pos >= echo_buf_len) { - self->echo_buffer_write_pos = 0; + if (self->freq_shift) { + echo_buffer_pos = next_buffer_pos % (echo_buf_len << 8); + } else { + if (self->echo_buffer_read_pos >= echo_buf_len) { + self->echo_buffer_read_pos = 0; + } + if (self->echo_buffer_write_pos >= echo_buf_len) { + self->echo_buffer_write_pos = 0; + } } } } @@ -370,12 +425,26 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * } } - int32_t echo = echo_buffer[self->echo_buffer_read_pos++] * decay; - int32_t word = echo + sample_word; + int32_t echo, word = 0; + uint32_t next_buffer_pos = 0; + if (self->freq_shift) { + echo = echo_buffer[echo_buffer_pos >> 8]; + next_buffer_pos = echo_buffer_pos + self->echo_buffer_rate; + word = echo * decay + sample_word; + } else { + echo = echo_buffer[self->echo_buffer_read_pos++]; + word = echo * decay + sample_word; + } if (MP_LIKELY(self->bits_per_sample == 16)) { word = mix_down_sample(word); - echo_buffer[self->echo_buffer_write_pos++] = (int16_t)word; + if (self->freq_shift) { + for (uint32_t j = echo_buffer_pos >> 8; j < next_buffer_pos >> 8; j++) { + echo_buffer[j % echo_buf_len] = (int16_t)word; + } + } else { + echo_buffer[self->echo_buffer_write_pos++] = (int16_t)word; + } } else { // Do not have mix_down for 8 bit so just hard cap samples into 1 byte if (word > 127) { @@ -383,9 +452,17 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * } else if (word < -128) { word = -128; } - echo_buffer[self->echo_buffer_write_pos++] = (int8_t)word; + if (self->freq_shift) { + for (uint32_t j = echo_buffer_pos >> 8; j < next_buffer_pos >> 8; j++) { + echo_buffer[j % echo_buf_len] = (int8_t)word; + } + } else { + echo_buffer[self->echo_buffer_write_pos++] = (int8_t)word; + } } + word = echo + sample_word; + if (MP_LIKELY(self->bits_per_sample == 16)) { word_buffer[i] = (sample_word * (1.0 - mix)) + (word * mix); if (!self->samples_signed) { @@ -400,11 +477,15 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * } } - if (self->echo_buffer_read_pos >= echo_buf_len) { - self->echo_buffer_read_pos = 0; - } - if (self->echo_buffer_write_pos >= echo_buf_len) { - self->echo_buffer_write_pos = 0; + if (self->freq_shift) { + echo_buffer_pos = next_buffer_pos % (echo_buf_len << 8); + } else { + if (self->echo_buffer_read_pos >= echo_buf_len) { + self->echo_buffer_read_pos = 0; + } + if (self->echo_buffer_write_pos >= echo_buf_len) { + self->echo_buffer_write_pos = 0; + } } } } @@ -418,6 +499,14 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * } } + if (self->freq_shift) { + if (channel == 0) { + self->echo_buffer_left_pos = echo_buffer_pos; + } else if (channel == 1) { + self->echo_buffer_right_pos = echo_buffer_pos; + } + } + // 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; diff --git a/shared-module/audiodelays/Echo.h b/shared-module/audiodelays/Echo.h index 42f039f343592..8742f83898a75 100644 --- a/shared-module/audiodelays/Echo.h +++ b/shared-module/audiodelays/Echo.h @@ -1,6 +1,6 @@ // This file is part of the CircuitPython project: https://circuitpython.org // -// SPDX-FileCopyrightText: Copyright (c) 2024 Mark Komus +// SPDX-FileCopyrightText: Copyright (c) 2024 Mark Komus, Cooper Dalrymple // // SPDX-License-Identifier: MIT #pragma once @@ -34,6 +34,7 @@ typedef struct { bool loop; bool more_data; + bool freq_shift; // does the echo shift frequencies if delay changes int8_t *echo_buffer; uint32_t echo_buffer_len; // bytes @@ -42,6 +43,10 @@ typedef struct { uint32_t echo_buffer_read_pos; // words uint32_t echo_buffer_write_pos; // words + uint32_t echo_buffer_rate; // words << 8 + uint32_t echo_buffer_left_pos; // words << 8 + uint32_t echo_buffer_right_pos; // words << 8 + mp_obj_t sample; } audiodelays_echo_obj_t; From 064c2d8fff9696da9891954b7b071bf43f751378 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Mon, 14 Oct 2024 10:06:06 -0500 Subject: [PATCH 23/23] Fixed renaming the defines for audioeffects incorrectly --- ports/raspberrypi/mpconfigport.mk | 2 +- py/circuitpy_mpconfig.mk | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ports/raspberrypi/mpconfigport.mk b/ports/raspberrypi/mpconfigport.mk index 4f900821cf1f5..d619e78bd97a8 100644 --- a/ports/raspberrypi/mpconfigport.mk +++ b/ports/raspberrypi/mpconfigport.mk @@ -66,7 +66,7 @@ CIRCUITPY_PICODVI ?= 1 CIRCUITPY_TOUCHIO = 0 # Audio effects -CIRCUITPY_AUDIODELAYS ?= 1 +CIRCUITPY_AUDIOEFFECTS ?= 1 endif INTERNAL_LIBM = 1 diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index 6a3b29baf32c2..a8e44a12c9418 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -141,7 +141,8 @@ CFLAGS += -DCIRCUITPY_AUDIOCORE_DEBUG=$(CIRCUITPY_AUDIOCORE_DEBUG) CIRCUITPY_AUDIOMP3 ?= $(call enable-if-all,$(CIRCUITPY_FULL_BUILD) $(CIRCUITPY_AUDIOCORE)) CFLAGS += -DCIRCUITPY_AUDIOMP3=$(CIRCUITPY_AUDIOMP3) -CIRCUITPY_AUDIODELAYS ?= 0 +CIRCUITPY_AUDIOEFFECTS ?= 0 +CIRCUITPY_AUDIODELAYS ?= $(CIRCUITPY_AUDIOEFFECTS) CFLAGS += -DCIRCUITPY_AUDIODELAYS=$(CIRCUITPY_AUDIODELAYS) CIRCUITPY_AURORA_EPAPER ?= 0