From d78b6d8ae7a678f606266b5c635d85a54c2afe3f Mon Sep 17 00:00:00 2001 From: Cooper Dalrymple Date: Wed, 17 Jun 2026 13:49:42 -0500 Subject: [PATCH 1/4] Initial build of resampler --- py/circuitpy_defns.mk | 1 + shared-bindings/audiospeed/Resampler.c | 156 ++++++++++++++++++ shared-bindings/audiospeed/Resampler.h | 22 +++ shared-bindings/audiospeed/SpeedChanger.c | 23 +-- shared-bindings/audiospeed/SpeedChanger.h | 7 +- shared-bindings/audiospeed/__init__.c | 2 + shared-module/audiocore/__init__.c | 9 +- shared-module/audiospeed/Resampler.c | 57 +++++++ shared-module/audiospeed/Resampler.h | 22 +++ shared-module/audiospeed/SpeedChanger.c | 154 ++---------------- shared-module/audiospeed/SpeedChanger.h | 20 +-- shared-module/audiospeed/__init__.c | 188 ++++++++++++++++++++++ shared-module/audiospeed/__init__.h | 66 ++++++++ 13 files changed, 543 insertions(+), 184 deletions(-) create mode 100644 shared-bindings/audiospeed/Resampler.c create mode 100644 shared-bindings/audiospeed/Resampler.h create mode 100644 shared-module/audiospeed/Resampler.c create mode 100644 shared-module/audiospeed/Resampler.h diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index 67be45325d565..a505d5c86d9cf 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -701,6 +701,7 @@ SRC_SHARED_MODULE_ALL = \ audiocore/RawSample.c \ audiocore/WaveFile.c \ audiocore/__init__.c \ + audiospeed/Resampler.c \ audiospeed/SpeedChanger.c \ audiospeed/__init__.c \ audiodelays/Echo.c \ diff --git a/shared-bindings/audiospeed/Resampler.c b/shared-bindings/audiospeed/Resampler.c new file mode 100644 index 0000000000000..db559cd12128d --- /dev/null +++ b/shared-bindings/audiospeed/Resampler.c @@ -0,0 +1,156 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +#include + +#include "shared/runtime/context_manager_helpers.h" +#include "py/objproperty.h" +#include "py/runtime.h" +#include "shared-bindings/audiospeed/Resampler.h" +#include "shared-bindings/audiocore/__init__.h" +#include "shared-bindings/util.h" +#include "shared-module/audiospeed/Resampler.h" + +//| class Resampler: +//| """Wraps an audio sample to match it to the destination sample rate.""" +//| +//| def __init__(self) -> None: +//| """Create a Resampler that wraps ``source``. +//| +//| :param audiosample source: The audio source to resample. +//| +//| Playing a wave file through a mixer with half the sample rate:: +//| +//| import board +//| import audiocore +//| import audiomixer +//| import audiospeed +//| import audioio +//| +//| wav = audiocore.WaveFile("sample.wav") +//| resampler = audiospeed.Resampler(wav) +//| mixer = audiomixer.Mixer( +//| channel_count=wav.channel_count, +//| bits_per_sample=wav.bits_per_sample, +//| sample_rate=wav.sample_rate // 2, +//| ) +//| audio = audioio.AudioOut(board.A0) +//| audio.play(mixer) +//| mixer.play(resampler) +//| """ +//| ... +//| +static mp_obj_t audiospeed_resampler_make_new(const mp_obj_type_t *type, + size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + audiospeed_resampler_obj_t *self = mp_obj_malloc(audiospeed_resampler_obj_t, &audiospeed_resampler_type); + common_hal_audiospeed_resampler_construct(self); + return MP_OBJ_FROM_PTR(self); +} + +//| def deinit(self) -> None: +//| """Deinitialises the Resampler and releases all memory resources for reuse.""" +//| ... +//| +static mp_obj_t audiospeed_resampler_deinit(mp_obj_t self_in) { + audiospeed_resampler_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_audiospeed_resampler_deinit(self); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(audiospeed_resampler_deinit_obj, audiospeed_resampler_deinit); + +//| playing: bool +//| """True when the resampler is playing a sample. (read-only)""" +//| +static mp_obj_t audiospeed_resampler_obj_get_playing(mp_obj_t self_in) { + audiospeed_resampler_obj_t *self = MP_OBJ_TO_PTR(self_in); + audiosample_check_for_deinit(&self->base.base); + return mp_obj_new_bool(common_hal_audiospeed_resampler_get_playing(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(audiospeed_resampler_get_playing_obj, audiospeed_resampler_obj_get_playing); + +MP_PROPERTY_GETTER(audiospeed_resampler_playing_obj, + (mp_obj_t)&audiospeed_resampler_get_playing_obj); + +//| def play(self, sample: circuitpython_typing.AudioSample, *, loop: bool = False) -> Resampler: +//| """Plays the sample. Use an `audiomixer.Mixer` object to enable looping. +//| Does not block. Use `playing` to block. +//| +//| :return: The resampler object itself. Can be used for chaining, ie: +//| ``mixer.play(resampler.play(sample))``. +//| :rtype: Resampler""" +//| ... +//| +static mp_obj_t audiospeed_resampler_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, {} }, + }; + audiospeed_resampler_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + audiosample_check_for_deinit(&self->base.base); + 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_audiospeed_resampler_play(self, sample); + + return MP_OBJ_FROM_PTR(self); +} +MP_DEFINE_CONST_FUN_OBJ_KW(audiospeed_resampler_play_obj, 1, audiospeed_resampler_obj_play); + +//| def stop(self) -> None: +//| """Stops playback of the sample.""" +//| ... +//| +//| +static mp_obj_t audiospeed_resampler_obj_stop(mp_obj_t self_in) { + audiospeed_resampler_obj_t *self = MP_OBJ_TO_PTR(self_in); + + common_hal_audiospeed_resampler_stop(self); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(audiospeed_resampler_stop_obj, audiospeed_resampler_obj_stop); + +//| rate: float +//| """Playback speed multiplier.""" +//| +static mp_obj_t audiospeed_resampler_obj_get_rate(mp_obj_t self_in) { + audiospeed_resampler_obj_t *self = MP_OBJ_TO_PTR(self_in); + audiosample_check_for_deinit(&self->base.base); + return common_hal_audiospeed_resampler_get_rate(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(audiospeed_resampler_get_rate_obj, audiospeed_resampler_obj_get_rate); + +MP_PROPERTY_GETTER(audiospeed_resampler_rate_obj, + (mp_obj_t)&audiospeed_resampler_get_rate_obj); + +static const mp_rom_map_elem_t audiospeed_resampler_locals_dict_table[] = { + // Methods + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiospeed_resampler_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, + { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, + { MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audiospeed_resampler_play_obj) }, + { MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&audiospeed_resampler_stop_obj) }, + + // Properties + { MP_ROM_QSTR(MP_QSTR_playing), MP_ROM_PTR(&audiospeed_resampler_playing_obj) }, + { MP_ROM_QSTR(MP_QSTR_rate), MP_ROM_PTR(&audiospeed_resampler_rate_obj) }, +}; +static MP_DEFINE_CONST_DICT(audiospeed_resampler_locals_dict, audiospeed_resampler_locals_dict_table); + +static const audiosample_p_t audiospeed_resampler_proto = { + MP_PROTO_IMPLEMENT(MP_QSTR_protocol_audiosample) + .reset_buffer = (audiosample_reset_buffer_fun)audiospeed_resampler_reset_buffer, + .get_buffer = (audiosample_get_buffer_fun)audiospeed_resampler_get_buffer, +}; + +MP_DEFINE_CONST_OBJ_TYPE( + audiospeed_resampler_type, + MP_QSTR_Resampler, + MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS, + make_new, audiospeed_resampler_make_new, + locals_dict, &audiospeed_resampler_locals_dict, + protocol, &audiospeed_resampler_proto + ); diff --git a/shared-bindings/audiospeed/Resampler.h b/shared-bindings/audiospeed/Resampler.h new file mode 100644 index 0000000000000..60425f4f8d360 --- /dev/null +++ b/shared-bindings/audiospeed/Resampler.h @@ -0,0 +1,22 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "shared-module/audiospeed/Resampler.h" + +extern const mp_obj_type_t audiospeed_resampler_type; + +void common_hal_audiospeed_resampler_construct(audiospeed_resampler_obj_t *self); +void common_hal_audiospeed_resampler_deinit(audiospeed_resampler_obj_t *self); + +bool common_hal_audiospeed_resampler_get_playing(audiospeed_resampler_obj_t *self); +void common_hal_audiospeed_resampler_play(audiospeed_resampler_obj_t *self, mp_obj_t sample); +void common_hal_audiospeed_resampler_stop(audiospeed_resampler_obj_t *self); + +mp_obj_t common_hal_audiospeed_resampler_get_rate(audiospeed_resampler_obj_t *self); + +void audiospeed_resampler_set_sample_rate(audiospeed_resampler_obj_t *self, uint32_t sample_rate); diff --git a/shared-bindings/audiospeed/SpeedChanger.c b/shared-bindings/audiospeed/SpeedChanger.c index 34efa1516b035..bcac6b7ff20a3 100644 --- a/shared-bindings/audiospeed/SpeedChanger.c +++ b/shared-bindings/audiospeed/SpeedChanger.c @@ -12,19 +12,9 @@ #include "shared-bindings/audiospeed/SpeedChanger.h" #include "shared-bindings/audiocore/__init__.h" #include "shared-bindings/util.h" +#include "shared-module/audiospeed/__init__.h" #include "shared-module/audiospeed/SpeedChanger.h" -// Convert a Python float to 16.16 fixed-point rate -static uint32_t rate_to_fp(mp_obj_t rate_obj) { - mp_float_t rate = mp_arg_validate_obj_float_range(rate_obj, 0.001, 1000.0, MP_QSTR_rate); - return (uint32_t)(rate * (1 << 16)); -} - -// Convert 16.16 fixed-point rate to Python float -static mp_obj_t fp_to_rate(uint32_t rate_fp) { - return mp_obj_new_float((mp_float_t)rate_fp / (1 << 16)); -} - //| class SpeedChanger: //| """Wraps an audio sample to play it back at a different speed. //| @@ -70,13 +60,8 @@ static mp_obj_t audiospeed_speedchanger_make_new(const mp_obj_type_t *type, mp_obj_t source = args[ARG_source].u_obj; audiosample_check(source); - uint32_t rate_fp = 1 << 16; // default 1.0 - if (args[ARG_rate].u_obj != mp_const_none) { - rate_fp = rate_to_fp(args[ARG_rate].u_obj); - } - audiospeed_speedchanger_obj_t *self = mp_obj_malloc(audiospeed_speedchanger_obj_t, &audiospeed_speedchanger_type); - common_hal_audiospeed_speedchanger_construct(self, source, rate_fp); + common_hal_audiospeed_speedchanger_construct(self, source, args[ARG_rate].u_obj); return MP_OBJ_FROM_PTR(self); } @@ -97,14 +82,14 @@ static MP_DEFINE_CONST_FUN_OBJ_1(audiospeed_speedchanger_deinit_obj, audiospeed_ static mp_obj_t audiospeed_speedchanger_obj_get_rate(mp_obj_t self_in) { audiospeed_speedchanger_obj_t *self = MP_OBJ_TO_PTR(self_in); audiosample_check_for_deinit(&self->base); - return fp_to_rate(common_hal_audiospeed_speedchanger_get_rate(self)); + return common_hal_audiospeed_speedchanger_get_rate(self); } MP_DEFINE_CONST_FUN_OBJ_1(audiospeed_speedchanger_get_rate_obj, audiospeed_speedchanger_obj_get_rate); static mp_obj_t audiospeed_speedchanger_obj_set_rate(mp_obj_t self_in, mp_obj_t rate_obj) { audiospeed_speedchanger_obj_t *self = MP_OBJ_TO_PTR(self_in); audiosample_check_for_deinit(&self->base); - common_hal_audiospeed_speedchanger_set_rate(self, rate_to_fp(rate_obj)); + common_hal_audiospeed_speedchanger_set_rate(self, rate_obj); return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_2(audiospeed_speedchanger_set_rate_obj, audiospeed_speedchanger_obj_set_rate); diff --git a/shared-bindings/audiospeed/SpeedChanger.h b/shared-bindings/audiospeed/SpeedChanger.h index 64a126a7a61fb..bc0b166b6121f 100644 --- a/shared-bindings/audiospeed/SpeedChanger.h +++ b/shared-bindings/audiospeed/SpeedChanger.h @@ -11,7 +11,8 @@ extern const mp_obj_type_t audiospeed_speedchanger_type; void common_hal_audiospeed_speedchanger_construct(audiospeed_speedchanger_obj_t *self, - mp_obj_t source, uint32_t rate_fp); + mp_obj_t source, mp_obj_t rate_obj); void common_hal_audiospeed_speedchanger_deinit(audiospeed_speedchanger_obj_t *self); -void common_hal_audiospeed_speedchanger_set_rate(audiospeed_speedchanger_obj_t *self, uint32_t rate_fp); -uint32_t common_hal_audiospeed_speedchanger_get_rate(audiospeed_speedchanger_obj_t *self); + +void common_hal_audiospeed_speedchanger_set_rate(audiospeed_speedchanger_obj_t *self, mp_obj_t rate_obj); +mp_obj_t common_hal_audiospeed_speedchanger_get_rate(audiospeed_speedchanger_obj_t *self); diff --git a/shared-bindings/audiospeed/__init__.c b/shared-bindings/audiospeed/__init__.c index b12e6db7e6bc7..3eca41e64675b 100644 --- a/shared-bindings/audiospeed/__init__.c +++ b/shared-bindings/audiospeed/__init__.c @@ -9,12 +9,14 @@ #include "py/obj.h" #include "py/runtime.h" +#include "shared-bindings/audiospeed/Resampler.h" #include "shared-bindings/audiospeed/SpeedChanger.h" //| """Audio processing tools""" static const mp_rom_map_elem_t audiospeed_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audiospeed) }, + { MP_ROM_QSTR(MP_QSTR_Resampler), MP_ROM_PTR(&audiospeed_resampler_type) }, { MP_ROM_QSTR(MP_QSTR_SpeedChanger), MP_ROM_PTR(&audiospeed_speedchanger_type) }, }; diff --git a/shared-module/audiocore/__init__.c b/shared-module/audiocore/__init__.c index 2ee683d75e1cd..0767f93b811c7 100644 --- a/shared-module/audiocore/__init__.c +++ b/shared-module/audiocore/__init__.c @@ -13,6 +13,8 @@ #include "shared-module/audiocore/RawSample.h" #include "shared-module/audiocore/WaveFile.h" +#include "shared-bindings/audiospeed/Resampler.h" + #include "shared-bindings/audiomixer/Mixer.h" #include "shared-module/audiomixer/Mixer.h" @@ -198,7 +200,7 @@ void audiosample_convert_s16s_u8s(uint8_t *buffer_out, const int16_t *buffer_in, void audiosample_must_match(audiosample_base_t *self, mp_obj_t other_in, bool allow_mono_to_stereo) { const audiosample_base_t *other = audiosample_check(other_in); - if (other->sample_rate != self->sample_rate) { + if (other->sample_rate != self->sample_rate && !mp_obj_is_type(other_in, &audiospeed_resampler_type)) { mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_sample_rate); } if ((!allow_mono_to_stereo || (allow_mono_to_stereo && self->channel_count != 2)) && other->channel_count != self->channel_count) { @@ -210,4 +212,9 @@ void audiosample_must_match(audiosample_base_t *self, mp_obj_t other_in, bool al if (other->samples_signed != self->samples_signed) { mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_signedness); } + + if (mp_obj_is_type(other_in, &audiospeed_resampler_type)) { + audiospeed_resampler_obj_t *other_resampler = MP_OBJ_TO_PTR(other_in); + audiospeed_resampler_set_sample_rate(other_resampler, self->sample_rate); + } } diff --git a/shared-module/audiospeed/Resampler.c b/shared-module/audiospeed/Resampler.c new file mode 100644 index 0000000000000..47b721834d153 --- /dev/null +++ b/shared-module/audiospeed/Resampler.c @@ -0,0 +1,57 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +#include "shared-bindings/audiospeed/Resampler.h" + +static void calculate_rate(audiospeed_base_t *self, uint32_t sample_rate) { + if (self->source != NULL && sample_rate) { + self->speed.rate_fp = (uint32_t)((mp_float_t)self->base.sample_rate / sample_rate * (1 << SPEED_SHIFT)); + } else { + audiospeed_set_rate(&self->speed, mp_const_none); + } +} + +void common_hal_audiospeed_resampler_construct(audiospeed_resampler_obj_t *self) { + audiospeed_construct(&self->base, mp_const_none); // default rate 1.0 + self->sample_rate = 0; +} + +void common_hal_audiospeed_resampler_deinit(audiospeed_resampler_obj_t *self) { + audiospeed_deinit(&self->base); +} + +bool common_hal_audiospeed_resampler_get_playing(audiospeed_resampler_obj_t *self) { + return self->base.source != NULL; +} + +void common_hal_audiospeed_resampler_play(audiospeed_resampler_obj_t *self, mp_obj_t sample) { + audiospeed_assign_source(&self->base, sample); + calculate_rate(&self->base, self->sample_rate); +} + +void common_hal_audiospeed_resampler_stop(audiospeed_resampler_obj_t *self) { + self->base.source = NULL; +} + +mp_obj_t common_hal_audiospeed_resampler_get_rate(audiospeed_resampler_obj_t *self) { + return audiospeed_get_rate(&self->base.speed); +} + +void audiospeed_resampler_set_sample_rate(audiospeed_resampler_obj_t *self, uint32_t sample_rate) { + self->sample_rate = sample_rate; + calculate_rate(&self->base, self->sample_rate); +} + +void audiospeed_resampler_reset_buffer(audiospeed_resampler_obj_t *self, + bool single_channel_output, uint8_t channel) { + audiospeed_reset_buffer(&self->base, single_channel_output, channel); +} + +audioio_get_buffer_result_t audiospeed_resampler_get_buffer(audiospeed_resampler_obj_t *self, + bool single_channel_output, uint8_t channel, + uint8_t **buffer, uint32_t *buffer_length) { + return audiospeed_get_buffer(&self->base, single_channel_output, channel, buffer, buffer_length); +} diff --git a/shared-module/audiospeed/Resampler.h b/shared-module/audiospeed/Resampler.h new file mode 100644 index 0000000000000..a105c5c2ab9a7 --- /dev/null +++ b/shared-module/audiospeed/Resampler.h @@ -0,0 +1,22 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "py/obj.h" +#include "shared-module/audiocore/__init__.h" +#include "shared-module/audiospeed/__init__.h" + +typedef struct { + audiospeed_base_t base; + uint32_t sample_rate; +} audiospeed_resampler_obj_t; + +void audiospeed_resampler_reset_buffer(audiospeed_resampler_obj_t *self, + bool single_channel_output, uint8_t channel); +audioio_get_buffer_result_t audiospeed_resampler_get_buffer(audiospeed_resampler_obj_t *self, + bool single_channel_output, uint8_t channel, + uint8_t **buffer, uint32_t *buffer_length); diff --git a/shared-module/audiospeed/SpeedChanger.c b/shared-module/audiospeed/SpeedChanger.c index 6f417e9d7b1c4..813132e4f147d 100644 --- a/shared-module/audiospeed/SpeedChanger.c +++ b/shared-module/audiospeed/SpeedChanger.c @@ -10,166 +10,34 @@ #include "py/runtime.h" #include "py/gc.h" -#include "shared-module/audiocore/WaveFile.h" +#include "shared-module/audiocore/__init__.h" #include "shared-bindings/audiocore/__init__.h" -#define OUTPUT_BUFFER_FRAMES 128 - void common_hal_audiospeed_speedchanger_construct(audiospeed_speedchanger_obj_t *self, - mp_obj_t source, uint32_t rate_fp) { - audiosample_base_t *src_base = audiosample_check(source); - - self->source = source; - self->rate_fp = rate_fp; - self->phase = 0; - self->src_buffer = NULL; - self->src_buffer_length = 0; - self->src_sample_count = 0; - self->source_done = false; - self->source_exhausted = false; - - // Copy format from source - self->base.sample_rate = src_base->sample_rate; - self->base.channel_count = src_base->channel_count; - self->base.bits_per_sample = src_base->bits_per_sample; - self->base.samples_signed = src_base->samples_signed; - self->base.single_buffer = false; - - uint8_t bytes_per_frame = (src_base->bits_per_sample / 8) * src_base->channel_count; - self->output_buffer_length = OUTPUT_BUFFER_FRAMES * bytes_per_frame; - self->base.max_buffer_length = self->output_buffer_length; - - self->output_buffer = m_malloc_without_collect(self->output_buffer_length); - if (self->output_buffer == NULL) { - m_malloc_fail(self->output_buffer_length); - } + mp_obj_t source, mp_obj_t rate_obj) { + audiospeed_construct(self, rate_obj); + audiospeed_assign_source(self, source); } void common_hal_audiospeed_speedchanger_deinit(audiospeed_speedchanger_obj_t *self) { - self->output_buffer = NULL; - self->source = MP_OBJ_NULL; - audiosample_mark_deinit(&self->base); -} - -void common_hal_audiospeed_speedchanger_set_rate(audiospeed_speedchanger_obj_t *self, uint32_t rate_fp) { - self->rate_fp = rate_fp; + audiospeed_deinit(self); } -uint32_t common_hal_audiospeed_speedchanger_get_rate(audiospeed_speedchanger_obj_t *self) { - return self->rate_fp; +void common_hal_audiospeed_speedchanger_set_rate(audiospeed_speedchanger_obj_t *self, mp_obj_t rate_obj) { + audiospeed_set_rate(&self->speed, rate_obj); } -// Fetch the next buffer from the source. Returns false if no data available. -static bool fetch_source_buffer(audiospeed_speedchanger_obj_t *self) { - if (self->source_exhausted) { - return false; - } - uint8_t *buf = NULL; - uint32_t len = 0; - audioio_get_buffer_result_t result = audiosample_get_buffer(self->source, false, 0, &buf, &len); - if (result == GET_BUFFER_ERROR) { - self->source_exhausted = true; - return false; - } - if (len == 0) { - self->source_exhausted = true; - return false; - } - self->src_buffer = buf; - self->src_buffer_length = len; - uint8_t bytes_per_frame = (self->base.bits_per_sample / 8) * self->base.channel_count; - self->src_sample_count = len / bytes_per_frame; - self->source_done = (result == GET_BUFFER_DONE); - // Reset phase to index within this new buffer - self->phase = 0; - return true; +mp_obj_t common_hal_audiospeed_speedchanger_get_rate(audiospeed_speedchanger_obj_t *self) { + return audiospeed_get_rate(&self->speed); } void audiospeed_speedchanger_reset_buffer(audiospeed_speedchanger_obj_t *self, bool single_channel_output, uint8_t channel) { - if (single_channel_output && channel == 1) { - return; - } - audiosample_reset_buffer(self->source, false, 0); - self->phase = 0; - self->src_buffer = NULL; - self->src_buffer_length = 0; - self->src_sample_count = 0; - self->source_done = false; - self->source_exhausted = false; + audiospeed_reset_buffer(self, single_channel_output, channel); } audioio_get_buffer_result_t audiospeed_speedchanger_get_buffer(audiospeed_speedchanger_obj_t *self, bool single_channel_output, uint8_t channel, uint8_t **buffer, uint32_t *buffer_length) { - - // Ensure we have a source buffer - if (self->src_buffer == NULL) { - if (!fetch_source_buffer(self)) { - *buffer = NULL; - *buffer_length = 0; - return GET_BUFFER_DONE; - } - } - - uint8_t bytes_per_sample = self->base.bits_per_sample / 8; - uint8_t channels = self->base.channel_count; - uint8_t bytes_per_frame = bytes_per_sample * channels; - uint32_t out_frames = 0; - uint32_t max_out_frames = self->output_buffer_length / bytes_per_frame; - - if (bytes_per_sample == 1) { - // 8-bit samples - uint8_t *out = self->output_buffer; - while (out_frames < max_out_frames) { - uint32_t src_index = self->phase >> SPEED_SHIFT; - // Advance to next source buffer if needed - if (src_index >= self->src_sample_count) { - if (self->source_done) { - self->source_exhausted = true; - break; - } - if (!fetch_source_buffer(self)) { - break; - } - src_index = 0; // phase was reset by fetch - } - uint8_t *src = self->src_buffer + src_index * bytes_per_frame; - for (uint8_t c = 0; c < channels; c++) { - *out++ = src[c]; - } - out_frames++; - self->phase += self->rate_fp; - } - } else { - // 16-bit samples - int16_t *out = (int16_t *)self->output_buffer; - while (out_frames < max_out_frames) { - uint32_t src_index = self->phase >> SPEED_SHIFT; - if (src_index >= self->src_sample_count) { - if (self->source_done) { - self->source_exhausted = true; - break; - } - if (!fetch_source_buffer(self)) { - break; - } - src_index = 0; - } - int16_t *src = (int16_t *)(self->src_buffer + src_index * bytes_per_frame); - for (uint8_t c = 0; c < channels; c++) { - *out++ = src[c]; - } - out_frames++; - self->phase += self->rate_fp; - } - } - - *buffer = self->output_buffer; - *buffer_length = out_frames * bytes_per_frame; - - if (out_frames == 0) { - return GET_BUFFER_DONE; - } - return self->source_exhausted ? GET_BUFFER_DONE : GET_BUFFER_MORE_DATA; + return audiospeed_get_buffer(self, single_channel_output, channel, buffer, buffer_length); } diff --git a/shared-module/audiospeed/SpeedChanger.h b/shared-module/audiospeed/SpeedChanger.h index e920c72caf461..34969e42bd6c4 100644 --- a/shared-module/audiospeed/SpeedChanger.h +++ b/shared-module/audiospeed/SpeedChanger.h @@ -8,25 +8,9 @@ #include "py/obj.h" #include "shared-module/audiocore/__init__.h" +#include "shared-module/audiospeed/__init__.h" -// Fixed-point 16.16 format -#define SPEED_SHIFT 16 - -typedef struct { - audiosample_base_t base; - mp_obj_t source; - uint8_t *output_buffer; - uint32_t output_buffer_length; // in bytes, allocated size - // Source buffer cache - uint8_t *src_buffer; - uint32_t src_buffer_length; // in bytes - uint32_t src_sample_count; // in frames - // Phase accumulator and rate in 16.16 fixed-point (units: source frames) - uint32_t phase; - uint32_t rate_fp; // 16.16 fixed-point rate - bool source_done; // source returned DONE on last get_buffer - bool source_exhausted; // source DONE and we consumed all of it -} audiospeed_speedchanger_obj_t; +typedef audiospeed_base_t audiospeed_speedchanger_obj_t; void audiospeed_speedchanger_reset_buffer(audiospeed_speedchanger_obj_t *self, bool single_channel_output, uint8_t channel); diff --git a/shared-module/audiospeed/__init__.c b/shared-module/audiospeed/__init__.c index 7c9b271a4b501..5f1254b8dd79b 100644 --- a/shared-module/audiospeed/__init__.c +++ b/shared-module/audiospeed/__init__.c @@ -3,3 +3,191 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 Tod Kurt // // SPDX-License-Identifier: MIT + +#include + +#include "py/gc.h" +#include "py/objproperty.h" +#include "py/runtime.h" + +#include "shared-module/audiocore/__init__.h" +#include "shared-module/audiospeed/__init__.h" +#include "shared-bindings/audiocore/__init__.h" + +// Convert a Python float to 16.16 fixed-point rate +uint32_t audiospeed_rate_to_fp(mp_obj_t rate_obj) { + mp_float_t rate = mp_arg_validate_obj_float_range(rate_obj, 0.001, 1000.0, MP_QSTR_rate); + return (uint32_t)(rate * (1 << SPEED_SHIFT)); +} + +// Convert 16.16 fixed-point rate to Python float +mp_obj_t audiospeed_fp_to_rate(uint32_t rate_fp) { + return mp_obj_new_float((mp_float_t)rate_fp / (1 << SPEED_SHIFT)); +} + +void audiospeed_construct(audiospeed_base_t *self, mp_obj_t rate_obj) { + self->src_buffer = NULL; + self->src_buffer_length = 0; + self->src_sample_count = 0; + self->source_done = false; + self->source_exhausted = false; + + self->base.channel_count = 1; + self->base.single_buffer = false; + + audiospeed_set_rate(&self->speed, rate_obj); + audiospeed_reset_phase(&self->speed); + + self->output_buffer_length = 0; + self->output_buffer = NULL; +} + +void audiospeed_assign_source(audiospeed_base_t *self, mp_obj_t source) { + audiosample_base_t *src_base = audiosample_check(source); + + self->source = source; + + // Copy format from source + self->base.sample_rate = src_base->sample_rate; + self->base.channel_count = src_base->channel_count; + self->base.bits_per_sample = src_base->bits_per_sample; + self->base.samples_signed = src_base->samples_signed; + + uint8_t bytes_per_frame = (src_base->bits_per_sample / 8) * src_base->channel_count; + uint32_t output_buffer_length = OUTPUT_BUFFER_FRAMES * bytes_per_frame; + + if (self->output_buffer != NULL && output_buffer_length != self->output_buffer_length) { + self->output_buffer = m_realloc(self->output_buffer, + #if MICROPY_MALLOC_USES_ALLOCATED_SIZE + self->output_buffer_length, + #endif + output_buffer_length); + } else if (self->output_buffer == NULL) { + self->output_buffer = m_malloc_without_collect(output_buffer_length); + } + if (self->output_buffer == NULL) { + m_malloc_fail(output_buffer_length); + } + + self->output_buffer_length = output_buffer_length; + self->base.max_buffer_length = self->output_buffer_length; +} + +void audiospeed_deinit(audiospeed_base_t *self) { + self->output_buffer = NULL; + self->source = MP_OBJ_NULL; + audiosample_mark_deinit(&self->base); +} + +// Fetch the next buffer from the source. Returns false if no data available. +bool audiospeed_fetch_source_buffer(audiospeed_base_t *self) { + if (self->source_exhausted || self->source == NULL) { + return false; + } + uint8_t *buf = NULL; + uint32_t len = 0; + audioio_get_buffer_result_t result = audiosample_get_buffer(self->source, false, 0, &buf, &len); + if (result == GET_BUFFER_ERROR) { + self->source_exhausted = true; + return false; + } + if (len == 0) { + self->source_exhausted = true; + return false; + } + self->src_buffer = buf; + self->src_buffer_length = len; + uint8_t bytes_per_frame = (self->base.bits_per_sample / 8) * self->base.channel_count; + self->src_sample_count = len / bytes_per_frame; + self->source_done = (result == GET_BUFFER_DONE); + // Reset phase to index within this new buffer + audiospeed_reset_phase(&self->speed); + return true; +} + +void audiospeed_reset_buffer(audiospeed_base_t *self, bool single_channel_output, uint8_t channel) { + if (single_channel_output && channel == 1) { + return; + } + audiosample_reset_buffer(self->source, false, 0); + audiospeed_reset_phase(&self->speed); + self->src_buffer = NULL; + self->src_buffer_length = 0; + self->src_sample_count = 0; + self->source_done = false; + self->source_exhausted = false; +} + +audioio_get_buffer_result_t audiospeed_get_buffer(audiospeed_base_t *self, bool single_channel_output, uint8_t channel, + uint8_t **buffer, uint32_t *buffer_length) { + + // Ensure we have a source buffer + if (self->src_buffer == NULL) { + if (!audiospeed_fetch_source_buffer(self)) { + *buffer = NULL; + *buffer_length = 0; + return GET_BUFFER_DONE; + } + } + + uint8_t bytes_per_sample = self->base.bits_per_sample / 8; + uint8_t channels = self->base.channel_count; + uint8_t bytes_per_frame = bytes_per_sample * channels; + uint32_t out_frames = 0; + uint32_t max_out_frames = self->output_buffer_length / bytes_per_frame; + + if (bytes_per_sample == 1) { + // 8-bit samples + uint8_t *out = self->output_buffer; + while (out_frames < max_out_frames) { + uint32_t src_index = audiospeed_get_index(&self->speed); + // Advance to next source buffer if needed + if (src_index >= self->src_sample_count) { + if (self->source_done) { + self->source_exhausted = true; + break; + } + if (!audiospeed_fetch_source_buffer(self)) { + break; + } + src_index = 0; // phase was reset by fetch + } + uint8_t *src = self->src_buffer + src_index * bytes_per_frame; + for (uint8_t c = 0; c < channels; c++) { + *out++ = src[c]; + } + out_frames++; + audiospeed_increment_phase(&self->speed); + } + } else { + // 16-bit samples + int16_t *out = (int16_t *)self->output_buffer; + while (out_frames < max_out_frames) { + uint32_t src_index = audiospeed_get_index(&self->speed); + if (src_index >= self->src_sample_count) { + if (self->source_done) { + self->source_exhausted = true; + break; + } + if (!audiospeed_fetch_source_buffer(self)) { + break; + } + src_index = 0; + } + int16_t *src = (int16_t *)(self->src_buffer + src_index * bytes_per_frame); + for (uint8_t c = 0; c < channels; c++) { + *out++ = src[c]; + } + out_frames++; + audiospeed_increment_phase(&self->speed); + } + } + + *buffer = self->output_buffer; + *buffer_length = out_frames * bytes_per_frame; + + if (out_frames == 0) { + return GET_BUFFER_DONE; + } + return self->source_exhausted ? GET_BUFFER_DONE : GET_BUFFER_MORE_DATA; +} diff --git a/shared-module/audiospeed/__init__.h b/shared-module/audiospeed/__init__.h index c4a52e5819d12..8094778eba4d5 100644 --- a/shared-module/audiospeed/__init__.h +++ b/shared-module/audiospeed/__init__.h @@ -5,3 +5,69 @@ // SPDX-License-Identifier: MIT #pragma once + +#include + +#include "py/objproperty.h" +#include "shared-module/audiocore/__init__.h" + +// Fixed-point 16.16 format +#define SPEED_SHIFT 16 +#define SPEED_DEFAULT (1 << SPEED_SHIFT) // default 1.0 + +typedef struct { + // Phase accumulator and rate in 16.16 fixed-point (units: source frames) + uint32_t phase; + uint32_t rate_fp; // 16.16 fixed-point rate +} audiospeed_speed_t; + +uint32_t audiospeed_rate_to_fp(mp_obj_t rate_obj); +mp_obj_t audiospeed_fp_to_rate(uint32_t rate_fp); + +static inline void audiospeed_set_rate(audiospeed_speed_t *self, mp_obj_t rate_obj) { + if (rate_obj != mp_const_none) { + self->rate_fp = audiospeed_rate_to_fp(rate_obj); + } else { + self->rate_fp = SPEED_DEFAULT; + } +} + +static inline mp_obj_t audiospeed_get_rate(audiospeed_speed_t *self) { + return audiospeed_fp_to_rate(self->rate_fp); +} + +static inline void audiospeed_reset_phase(audiospeed_speed_t *self) { + self->phase = 0; +} + +static inline void audiospeed_increment_phase(audiospeed_speed_t *self) { + self->phase += self->rate_fp; +} + +static inline uint32_t audiospeed_get_index(audiospeed_speed_t *self) { + return self->phase >> SPEED_SHIFT; +} + +#define OUTPUT_BUFFER_FRAMES 128 + +typedef struct { + audiosample_base_t base; + mp_obj_t source; + uint8_t *output_buffer; + uint32_t output_buffer_length; // in bytes, allocated size + // Source buffer cache + uint8_t *src_buffer; + uint32_t src_buffer_length; // in bytes + uint32_t src_sample_count; // in frames + audiospeed_speed_t speed; + bool source_done; // source returned DONE on last get_buffer + bool source_exhausted; // source DONE and we consumed all of it +} audiospeed_base_t; + +void audiospeed_construct(audiospeed_base_t *self, mp_obj_t rate_obj); +void audiospeed_deinit(audiospeed_base_t *self); +void audiospeed_assign_source(audiospeed_base_t *self, mp_obj_t source); +bool audiospeed_fetch_source_buffer(audiospeed_base_t *self); +void audiospeed_reset_buffer(audiospeed_base_t *self, bool single_channel_output, uint8_t channel); +audioio_get_buffer_result_t audiospeed_get_buffer(audiospeed_base_t *self, bool single_channel_output, uint8_t channel, + uint8_t **buffer, uint32_t *buffer_length); From cfd0a18f34e20c08e63e0029f57636890ef95d8c Mon Sep 17 00:00:00 2001 From: Cooper Dalrymple Date: Wed, 17 Jun 2026 14:15:59 -0500 Subject: [PATCH 2/4] Fix audiocore build for other ports --- py/circuitpy_defns.mk | 1 + shared-module/audiocore/__init__.c | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index a505d5c86d9cf..e5698c77cc330 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -151,6 +151,7 @@ SRC_PATTERNS += audiomp3/% endif ifeq ($(CIRCUITPY_AUDIOSPEED),1) SRC_PATTERNS += audiospeed/% +CFLAGS += -DCIRCUITPY_AUDIOSPEED endif ifeq ($(CIRCUITPY_AURORA_EPAPER),1) SRC_PATTERNS += aurora_epaper/% diff --git a/shared-module/audiocore/__init__.c b/shared-module/audiocore/__init__.c index 0767f93b811c7..945ff823ac62b 100644 --- a/shared-module/audiocore/__init__.c +++ b/shared-module/audiocore/__init__.c @@ -13,7 +13,9 @@ #include "shared-module/audiocore/RawSample.h" #include "shared-module/audiocore/WaveFile.h" +#if defined(CIRCUITPY_AUDIOSPEED) #include "shared-bindings/audiospeed/Resampler.h" +#endif #include "shared-bindings/audiomixer/Mixer.h" #include "shared-module/audiomixer/Mixer.h" @@ -200,8 +202,14 @@ void audiosample_convert_s16s_u8s(uint8_t *buffer_out, const int16_t *buffer_in, void audiosample_must_match(audiosample_base_t *self, mp_obj_t other_in, bool allow_mono_to_stereo) { const audiosample_base_t *other = audiosample_check(other_in); - if (other->sample_rate != self->sample_rate && !mp_obj_is_type(other_in, &audiospeed_resampler_type)) { - mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_sample_rate); + if (other->sample_rate != self->sample_rate) { + #if defined(CIRCUITPY_AUDIOSPEED) + if (!mp_obj_is_type(other_in, &audiospeed_resampler_type)) { + #endif + mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_sample_rate); + #if defined(CIRCUITPY_AUDIOSPEED) + } + #endif } if ((!allow_mono_to_stereo || (allow_mono_to_stereo && self->channel_count != 2)) && other->channel_count != self->channel_count) { mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_channel_count); @@ -213,8 +221,10 @@ void audiosample_must_match(audiosample_base_t *self, mp_obj_t other_in, bool al mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_signedness); } + #if defined(CIRCUITPY_AUDIOSPEED) if (mp_obj_is_type(other_in, &audiospeed_resampler_type)) { audiospeed_resampler_obj_t *other_resampler = MP_OBJ_TO_PTR(other_in); audiospeed_resampler_set_sample_rate(other_resampler, self->sample_rate); } + #endif } From cae3c81c339e45564879bbc5bf76e39409782a6f Mon Sep 17 00:00:00 2001 From: Cooper Dalrymple Date: Wed, 17 Jun 2026 14:28:09 -0500 Subject: [PATCH 3/4] Assign source to resampler in constructor --- shared-bindings/audiospeed/Resampler.c | 69 +++++-------------------- shared-bindings/audiospeed/Resampler.h | 6 +-- shared-module/audiospeed/Resampler.c | 17 +----- shared-module/audiospeed/SpeedChanger.c | 3 +- shared-module/audiospeed/__init__.c | 43 +++++---------- shared-module/audiospeed/__init__.h | 3 +- 6 files changed, 31 insertions(+), 110 deletions(-) diff --git a/shared-bindings/audiospeed/Resampler.c b/shared-bindings/audiospeed/Resampler.c index db559cd12128d..8abf52905d418 100644 --- a/shared-bindings/audiospeed/Resampler.c +++ b/shared-bindings/audiospeed/Resampler.c @@ -45,8 +45,19 @@ //| static mp_obj_t audiospeed_resampler_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_source }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_source, MP_ARG_REQUIRED | MP_ARG_OBJ }, + }; + 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); + + // Validate source implements audiosample protocol + mp_obj_t source = args[ARG_source].u_obj; + audiosample_check(source); + audiospeed_resampler_obj_t *self = mp_obj_malloc(audiospeed_resampler_obj_t, &audiospeed_resampler_type); - common_hal_audiospeed_resampler_construct(self); + common_hal_audiospeed_resampler_construct(self, source); return MP_OBJ_FROM_PTR(self); } @@ -61,58 +72,6 @@ static mp_obj_t audiospeed_resampler_deinit(mp_obj_t self_in) { } static MP_DEFINE_CONST_FUN_OBJ_1(audiospeed_resampler_deinit_obj, audiospeed_resampler_deinit); -//| playing: bool -//| """True when the resampler is playing a sample. (read-only)""" -//| -static mp_obj_t audiospeed_resampler_obj_get_playing(mp_obj_t self_in) { - audiospeed_resampler_obj_t *self = MP_OBJ_TO_PTR(self_in); - audiosample_check_for_deinit(&self->base.base); - return mp_obj_new_bool(common_hal_audiospeed_resampler_get_playing(self)); -} -MP_DEFINE_CONST_FUN_OBJ_1(audiospeed_resampler_get_playing_obj, audiospeed_resampler_obj_get_playing); - -MP_PROPERTY_GETTER(audiospeed_resampler_playing_obj, - (mp_obj_t)&audiospeed_resampler_get_playing_obj); - -//| def play(self, sample: circuitpython_typing.AudioSample, *, loop: bool = False) -> Resampler: -//| """Plays the sample. Use an `audiomixer.Mixer` object to enable looping. -//| Does not block. Use `playing` to block. -//| -//| :return: The resampler object itself. Can be used for chaining, ie: -//| ``mixer.play(resampler.play(sample))``. -//| :rtype: Resampler""" -//| ... -//| -static mp_obj_t audiospeed_resampler_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, {} }, - }; - audiospeed_resampler_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); - audiosample_check_for_deinit(&self->base.base); - 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_audiospeed_resampler_play(self, sample); - - return MP_OBJ_FROM_PTR(self); -} -MP_DEFINE_CONST_FUN_OBJ_KW(audiospeed_resampler_play_obj, 1, audiospeed_resampler_obj_play); - -//| def stop(self) -> None: -//| """Stops playback of the sample.""" -//| ... -//| -//| -static mp_obj_t audiospeed_resampler_obj_stop(mp_obj_t self_in) { - audiospeed_resampler_obj_t *self = MP_OBJ_TO_PTR(self_in); - - common_hal_audiospeed_resampler_stop(self); - return mp_const_none; -} -MP_DEFINE_CONST_FUN_OBJ_1(audiospeed_resampler_stop_obj, audiospeed_resampler_obj_stop); - //| rate: float //| """Playback speed multiplier.""" //| @@ -131,12 +90,10 @@ static const mp_rom_map_elem_t audiospeed_resampler_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiospeed_resampler_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, - { MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audiospeed_resampler_play_obj) }, - { MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&audiospeed_resampler_stop_obj) }, // Properties - { MP_ROM_QSTR(MP_QSTR_playing), MP_ROM_PTR(&audiospeed_resampler_playing_obj) }, { MP_ROM_QSTR(MP_QSTR_rate), MP_ROM_PTR(&audiospeed_resampler_rate_obj) }, + AUDIOSAMPLE_FIELDS, }; static MP_DEFINE_CONST_DICT(audiospeed_resampler_locals_dict, audiospeed_resampler_locals_dict_table); diff --git a/shared-bindings/audiospeed/Resampler.h b/shared-bindings/audiospeed/Resampler.h index 60425f4f8d360..7a744474bd1ad 100644 --- a/shared-bindings/audiospeed/Resampler.h +++ b/shared-bindings/audiospeed/Resampler.h @@ -10,13 +10,9 @@ extern const mp_obj_type_t audiospeed_resampler_type; -void common_hal_audiospeed_resampler_construct(audiospeed_resampler_obj_t *self); +void common_hal_audiospeed_resampler_construct(audiospeed_resampler_obj_t *self, mp_obj_t source); void common_hal_audiospeed_resampler_deinit(audiospeed_resampler_obj_t *self); -bool common_hal_audiospeed_resampler_get_playing(audiospeed_resampler_obj_t *self); -void common_hal_audiospeed_resampler_play(audiospeed_resampler_obj_t *self, mp_obj_t sample); -void common_hal_audiospeed_resampler_stop(audiospeed_resampler_obj_t *self); - mp_obj_t common_hal_audiospeed_resampler_get_rate(audiospeed_resampler_obj_t *self); void audiospeed_resampler_set_sample_rate(audiospeed_resampler_obj_t *self, uint32_t sample_rate); diff --git a/shared-module/audiospeed/Resampler.c b/shared-module/audiospeed/Resampler.c index 47b721834d153..cd1ac6812467f 100644 --- a/shared-module/audiospeed/Resampler.c +++ b/shared-module/audiospeed/Resampler.c @@ -14,8 +14,8 @@ static void calculate_rate(audiospeed_base_t *self, uint32_t sample_rate) { } } -void common_hal_audiospeed_resampler_construct(audiospeed_resampler_obj_t *self) { - audiospeed_construct(&self->base, mp_const_none); // default rate 1.0 +void common_hal_audiospeed_resampler_construct(audiospeed_resampler_obj_t *self, mp_obj_t source) { + audiospeed_construct(&self->base, source, mp_const_none); // default rate 1.0 self->sample_rate = 0; } @@ -23,19 +23,6 @@ void common_hal_audiospeed_resampler_deinit(audiospeed_resampler_obj_t *self) { audiospeed_deinit(&self->base); } -bool common_hal_audiospeed_resampler_get_playing(audiospeed_resampler_obj_t *self) { - return self->base.source != NULL; -} - -void common_hal_audiospeed_resampler_play(audiospeed_resampler_obj_t *self, mp_obj_t sample) { - audiospeed_assign_source(&self->base, sample); - calculate_rate(&self->base, self->sample_rate); -} - -void common_hal_audiospeed_resampler_stop(audiospeed_resampler_obj_t *self) { - self->base.source = NULL; -} - mp_obj_t common_hal_audiospeed_resampler_get_rate(audiospeed_resampler_obj_t *self) { return audiospeed_get_rate(&self->base.speed); } diff --git a/shared-module/audiospeed/SpeedChanger.c b/shared-module/audiospeed/SpeedChanger.c index 813132e4f147d..1bf21dfab7a2c 100644 --- a/shared-module/audiospeed/SpeedChanger.c +++ b/shared-module/audiospeed/SpeedChanger.c @@ -15,8 +15,7 @@ void common_hal_audiospeed_speedchanger_construct(audiospeed_speedchanger_obj_t *self, mp_obj_t source, mp_obj_t rate_obj) { - audiospeed_construct(self, rate_obj); - audiospeed_assign_source(self, source); + audiospeed_construct(self, source, rate_obj); } void common_hal_audiospeed_speedchanger_deinit(audiospeed_speedchanger_obj_t *self) { diff --git a/shared-module/audiospeed/__init__.c b/shared-module/audiospeed/__init__.c index 5f1254b8dd79b..19c4c9bf20dc2 100644 --- a/shared-module/audiospeed/__init__.c +++ b/shared-module/audiospeed/__init__.c @@ -25,52 +25,35 @@ mp_obj_t audiospeed_fp_to_rate(uint32_t rate_fp) { return mp_obj_new_float((mp_float_t)rate_fp / (1 << SPEED_SHIFT)); } -void audiospeed_construct(audiospeed_base_t *self, mp_obj_t rate_obj) { +void audiospeed_construct(audiospeed_base_t *self, mp_obj_t source, mp_obj_t rate_obj) { + audiosample_base_t *src_base = audiosample_check(source); + + self->source = source; self->src_buffer = NULL; self->src_buffer_length = 0; self->src_sample_count = 0; self->source_done = false; self->source_exhausted = false; - self->base.channel_count = 1; - self->base.single_buffer = false; - - audiospeed_set_rate(&self->speed, rate_obj); - audiospeed_reset_phase(&self->speed); - - self->output_buffer_length = 0; - self->output_buffer = NULL; -} - -void audiospeed_assign_source(audiospeed_base_t *self, mp_obj_t source) { - audiosample_base_t *src_base = audiosample_check(source); - - self->source = source; // Copy format from source self->base.sample_rate = src_base->sample_rate; self->base.channel_count = src_base->channel_count; self->base.bits_per_sample = src_base->bits_per_sample; self->base.samples_signed = src_base->samples_signed; + self->base.single_buffer = false; + + audiospeed_set_rate(&self->speed, rate_obj); + audiospeed_reset_phase(&self->speed); uint8_t bytes_per_frame = (src_base->bits_per_sample / 8) * src_base->channel_count; - uint32_t output_buffer_length = OUTPUT_BUFFER_FRAMES * bytes_per_frame; - - if (self->output_buffer != NULL && output_buffer_length != self->output_buffer_length) { - self->output_buffer = m_realloc(self->output_buffer, - #if MICROPY_MALLOC_USES_ALLOCATED_SIZE - self->output_buffer_length, - #endif - output_buffer_length); - } else if (self->output_buffer == NULL) { - self->output_buffer = m_malloc_without_collect(output_buffer_length); - } + self->output_buffer_length = OUTPUT_BUFFER_FRAMES * bytes_per_frame; + self->base.max_buffer_length = self->output_buffer_length; + + self->output_buffer = m_malloc_without_collect(self->output_buffer_length); if (self->output_buffer == NULL) { - m_malloc_fail(output_buffer_length); + m_malloc_fail(self->output_buffer_length); } - - self->output_buffer_length = output_buffer_length; - self->base.max_buffer_length = self->output_buffer_length; } void audiospeed_deinit(audiospeed_base_t *self) { diff --git a/shared-module/audiospeed/__init__.h b/shared-module/audiospeed/__init__.h index 8094778eba4d5..4a36a8b79b7e8 100644 --- a/shared-module/audiospeed/__init__.h +++ b/shared-module/audiospeed/__init__.h @@ -64,9 +64,8 @@ typedef struct { bool source_exhausted; // source DONE and we consumed all of it } audiospeed_base_t; -void audiospeed_construct(audiospeed_base_t *self, mp_obj_t rate_obj); +void audiospeed_construct(audiospeed_base_t *self, mp_obj_t source, mp_obj_t rate_obj); void audiospeed_deinit(audiospeed_base_t *self); -void audiospeed_assign_source(audiospeed_base_t *self, mp_obj_t source); bool audiospeed_fetch_source_buffer(audiospeed_base_t *self); void audiospeed_reset_buffer(audiospeed_base_t *self, bool single_channel_output, uint8_t channel); audioio_get_buffer_result_t audiospeed_get_buffer(audiospeed_base_t *self, bool single_channel_output, uint8_t channel, From cc5020bcb0479765cb16f79c311ead6f73c8fb93 Mon Sep 17 00:00:00 2001 From: Cooper Dalrymple Date: Wed, 17 Jun 2026 14:44:05 -0500 Subject: [PATCH 4/4] Fix formatting --- shared-module/audiocore/__init__.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/shared-module/audiocore/__init__.c b/shared-module/audiocore/__init__.c index 945ff823ac62b..24f838c766f68 100644 --- a/shared-module/audiocore/__init__.c +++ b/shared-module/audiocore/__init__.c @@ -13,7 +13,7 @@ #include "shared-module/audiocore/RawSample.h" #include "shared-module/audiocore/WaveFile.h" -#if defined(CIRCUITPY_AUDIOSPEED) +#ifdef CIRCUITPY_AUDIOSPEED #include "shared-bindings/audiospeed/Resampler.h" #endif @@ -202,14 +202,12 @@ void audiosample_convert_s16s_u8s(uint8_t *buffer_out, const int16_t *buffer_in, void audiosample_must_match(audiosample_base_t *self, mp_obj_t other_in, bool allow_mono_to_stereo) { const audiosample_base_t *other = audiosample_check(other_in); + #ifndef CIRCUITPY_AUDIOSPEED if (other->sample_rate != self->sample_rate) { - #if defined(CIRCUITPY_AUDIOSPEED) - if (!mp_obj_is_type(other_in, &audiospeed_resampler_type)) { - #endif - mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_sample_rate); - #if defined(CIRCUITPY_AUDIOSPEED) - } + #else + if (other->sample_rate != self->sample_rate && !mp_obj_is_type(other_in, &audiospeed_resampler_type)) { #endif + mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_sample_rate); } if ((!allow_mono_to_stereo || (allow_mono_to_stereo && self->channel_count != 2)) && other->channel_count != self->channel_count) { mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_channel_count); @@ -221,7 +219,7 @@ void audiosample_must_match(audiosample_base_t *self, mp_obj_t other_in, bool al mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_signedness); } - #if defined(CIRCUITPY_AUDIOSPEED) + #ifdef CIRCUITPY_AUDIOSPEED if (mp_obj_is_type(other_in, &audiospeed_resampler_type)) { audiospeed_resampler_obj_t *other_resampler = MP_OBJ_TO_PTR(other_in); audiospeed_resampler_set_sample_rate(other_resampler, self->sample_rate);