Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions py/circuitpy_defns.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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/%
Expand Down Expand Up @@ -701,6 +702,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 \
Expand Down
113 changes: 113 additions & 0 deletions shared-bindings/audiospeed/Resampler.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// This file is part of the CircuitPython project: https://circuitpython.org
//
// SPDX-FileCopyrightText: Copyright (c) 2026 Cooper Dalrymple
//
// SPDX-License-Identifier: MIT

#include <stdint.h>

#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) {
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, source);
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);

//| 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) },

// Properties
{ 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);

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
);
18 changes: 18 additions & 0 deletions shared-bindings/audiospeed/Resampler.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// 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, mp_obj_t source);
void common_hal_audiospeed_resampler_deinit(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);
23 changes: 4 additions & 19 deletions shared-bindings/audiospeed/SpeedChanger.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//|
Expand Down Expand Up @@ -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);
}

Expand All @@ -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);
Expand Down
7 changes: 4 additions & 3 deletions shared-bindings/audiospeed/SpeedChanger.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
2 changes: 2 additions & 0 deletions shared-bindings/audiospeed/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) },
};

Expand Down
15 changes: 15 additions & 0 deletions shared-module/audiocore/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
#include "shared-module/audiocore/RawSample.h"
#include "shared-module/audiocore/WaveFile.h"

#ifdef CIRCUITPY_AUDIOSPEED
#include "shared-bindings/audiospeed/Resampler.h"
#endif

#include "shared-bindings/audiomixer/Mixer.h"
#include "shared-module/audiomixer/Mixer.h"

Expand Down Expand Up @@ -198,7 +202,11 @@ 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) {
#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) {
Expand All @@ -210,4 +218,11 @@ 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);
}

#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);
}
#endif
}
44 changes: 44 additions & 0 deletions shared-module/audiospeed/Resampler.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// 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, mp_obj_t source) {
audiospeed_construct(&self->base, source, 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);
}

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);
}
22 changes: 22 additions & 0 deletions shared-module/audiospeed/Resampler.h
Original file line number Diff line number Diff line change
@@ -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);
Loading
Loading