From 16575fc0efd9b235463d100f6b68f440a13b4ca5 Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Wed, 23 Oct 2024 17:19:48 -0500 Subject: [PATCH 01/16] Added `Distortion` and `DistortionMode` classes to `audiofilters` module based on Godot's AudioEffectDistortion class. --- py/circuitpy_defns.mk | 2 + shared-bindings/audiofilters/Distortion.c | 362 +++++++++++++++++ shared-bindings/audiofilters/Distortion.h | 44 +++ shared-bindings/audiofilters/DistortionMode.c | 50 +++ shared-bindings/audiofilters/DistortionMode.h | 22 ++ shared-bindings/audiofilters/__init__.c | 6 + shared-module/audiofilters/Distortion.c | 374 ++++++++++++++++++ shared-module/audiofilters/Distortion.h | 54 +++ 8 files changed, 914 insertions(+) create mode 100644 shared-bindings/audiofilters/Distortion.c create mode 100644 shared-bindings/audiofilters/Distortion.h create mode 100644 shared-bindings/audiofilters/DistortionMode.c create mode 100644 shared-bindings/audiofilters/DistortionMode.h create mode 100644 shared-module/audiofilters/Distortion.c create mode 100644 shared-module/audiofilters/Distortion.h diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index 61516561822c..a5d5f5f84dc4 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -570,6 +570,7 @@ $(filter $(SRC_PATTERNS), \ _bleio/ScanEntry.c \ _eve/__init__.c \ __future__/__init__.c \ + audiofilters/DistortionMode.c \ camera/ImageFormat.c \ canio/Match.c \ codeop/__init__.c \ @@ -626,6 +627,7 @@ SRC_SHARED_MODULE_ALL = \ audiodelays/Echo.c \ audiodelays/__init__.c \ audiofilters/Filter.c \ + audiofilters/Distortion.c \ audiofilters/__init__.c \ audioio/__init__.c \ audiomixer/Mixer.c \ diff --git a/shared-bindings/audiofilters/Distortion.c b/shared-bindings/audiofilters/Distortion.c new file mode 100644 index 000000000000..2d1a138b4633 --- /dev/null +++ b/shared-bindings/audiofilters/Distortion.c @@ -0,0 +1,362 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +#include + +#include "shared-bindings/audiofilters/Distortion.h" +#include "shared-module/audiofilters/Distortion.h" + +#include "shared/runtime/context_manager_helpers.h" +#include "py/binary.h" +#include "py/objproperty.h" +#include "py/runtime.h" +#include "shared-bindings/util.h" +#include "shared-module/synthio/block.h" + +//| class Distortion: +//| """A Distortion effect""" +//| +//| def __init__( +//| self, +//| drive: synthio.BlockInput = 0.0, +//| pre_gain: synthio.BlockInput = 0.0, +//| post_gain: synthio.BlockInput = 0.0, +//| mode: DistortionMode = DistortionMode.CLIP, +//| mix: synthio.BlockInput = 1.0, +//| buffer_size: int = 512, +//| sample_rate: int = 8000, +//| bits_per_sample: int = 16, +//| samples_signed: bool = True, +//| channel_count: int = 1, +//| ) -> None: +//| """Create a Distortion effect where the original sample is processed through a biquad filter +//| created by a synthio.Synthesizer object. This can be used to generate a low-pass, +//| high-pass, or band-pass filter. +//| +//| 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 synthio.BlockInput drive: Distortion power. Value can range from 0.0 to 1.0. +//| :param synthio.BlockInput pre_gain: Increases or decreases the volume before the effect, in decibels. Value can range from -60 to 60. +//| :param synthio.BlockInput post_gain: Increases or decreases the volume after the effect, in decibels. Value can range from -80 to 24. +//| :param DistortionMode mode: Distortion type. +//| :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. +//| :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 a distortion to a synth:: +//| +//| import time +//| import board +//| import audiobusio +//| import synthio +//| import audiofilters +//| +//| audio = audiobusio.I2SOut(bit_clock=board.GP20, word_select=board.GP21, data=board.GP22) +//| synth = synthio.Synthesizer(channel_count=1, sample_rate=44100) +//| effect = audiofilters.Distortion(drive=0.5, mix=1.0, buffer_size=1024, channel_count=1, sample_rate=44100) +//| effect.play(synth) +//| audio.play(effect) +//| +//| note = synthio.Note(261) +//| while True: +//| synth.press(note) +//| time.sleep(0.25) +//| synth.release(note) +//| time.sleep(5)""" +//| ... +static mp_obj_t audiofilters_distortion_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_drive, ARG_pre_gain, ARG_post_gain, ARG_mode, 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_drive, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_pre_gain, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_post_gain, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_mode, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_PTR((void *)&distortion_mode_CLIP_obj)} }, + { MP_QSTR_mix, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_buffer_size, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 512} }, + { MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 8000} }, + { MP_QSTR_bits_per_sample, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 16} }, + { 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 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")); + } + + audiofilters_distortion_mode_t mode = validate_distortion_mode(args[ARG_mode].u_obj, MP_QSTR_mode); + + audiofilters_distortion_obj_t *self = mp_obj_malloc(audiofilters_distortion_obj_t, &audiofilters_distortion_type); + common_hal_audiofilters_distortion_construct(self, args[ARG_drive].u_obj, args[ARG_pre_gain].u_obj, args[ARG_post_gain].u_obj, mode, 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); +} + +//| def deinit(self) -> None: +//| """Deinitialises the Distortion.""" +//| ... +static mp_obj_t audiofilters_distortion_deinit(mp_obj_t self_in) { + audiofilters_distortion_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_audiofilters_distortion_deinit(self); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(audiofilters_distortion_deinit_obj, audiofilters_distortion_deinit); + +static void check_for_deinit(audiofilters_distortion_obj_t *self) { + if (common_hal_audiofilters_distortion_deinited(self)) { + raise_deinited_error(); + } +} + +//| def __enter__(self) -> Distortion: +//| """No-op used by Context Managers.""" +//| ... +// Provided by context manager helper. + +//| def __exit__(self) -> None: +//| """Automatically deinitializes when exiting a context. See +//| :ref:`lifetime-and-contextmanagers` for more info.""" +//| ... +static mp_obj_t audiofilters_distortion_obj___exit__(size_t n_args, const mp_obj_t *args) { + (void)n_args; + common_hal_audiofilters_distortion_deinit(args[0]); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audiofilters_distortion___exit___obj, 4, 4, audiofilters_distortion_obj___exit__); + + +//| drive: synthio.BlockInput +//| """Distortion power. Value can range from 0.0 to 1.0.""" +static mp_obj_t audiofilters_distortion_obj_get_drive(mp_obj_t self_in) { + return common_hal_audiofilters_distortion_get_drive(self_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(audiofilters_distortion_get_drive_obj, audiofilters_distortion_obj_get_drive); + +static mp_obj_t audiofilters_distortion_obj_set_drive(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_drive }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_drive, MP_ARG_OBJ | MP_ARG_REQUIRED, {} }, + }; + audiofilters_distortion_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_audiofilters_distortion_set_drive(self, args[ARG_drive].u_obj); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(audiofilters_distortion_set_drive_obj, 1, audiofilters_distortion_obj_set_drive); + +MP_PROPERTY_GETSET(audiofilters_distortion_drive_obj, + (mp_obj_t)&audiofilters_distortion_get_drive_obj, + (mp_obj_t)&audiofilters_distortion_set_drive_obj); + + +//| pre_gain: synthio.BlockInput +//| """Increases or decreases the volume before the effect, in decibels. Value can range from -60 to 60.""" +static mp_obj_t audiofilters_distortion_obj_get_pre_gain(mp_obj_t self_in) { + return common_hal_audiofilters_distortion_get_pre_gain(self_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(audiofilters_distortion_get_pre_gain_obj, audiofilters_distortion_obj_get_pre_gain); + +static mp_obj_t audiofilters_distortion_obj_set_pre_gain(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_pre_gain }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_pre_gain, MP_ARG_OBJ | MP_ARG_REQUIRED, {} }, + }; + audiofilters_distortion_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_audiofilters_distortion_set_pre_gain(self, args[ARG_pre_gain].u_obj); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(audiofilters_distortion_set_pre_gain_obj, 1, audiofilters_distortion_obj_set_pre_gain); + +MP_PROPERTY_GETSET(audiofilters_distortion_pre_gain_obj, + (mp_obj_t)&audiofilters_distortion_get_pre_gain_obj, + (mp_obj_t)&audiofilters_distortion_set_pre_gain_obj); + + +//| post_gain: synthio.BlockInput +//| """Increases or decreases the volume after the effect, in decibels. Value can range from -80 to 24.""" +static mp_obj_t audiofilters_distortion_obj_get_post_gain(mp_obj_t self_in) { + return common_hal_audiofilters_distortion_get_post_gain(self_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(audiofilters_distortion_get_post_gain_obj, audiofilters_distortion_obj_get_post_gain); + +static mp_obj_t audiofilters_distortion_obj_set_post_gain(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_post_gain }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_post_gain, MP_ARG_OBJ | MP_ARG_REQUIRED, {} }, + }; + audiofilters_distortion_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_audiofilters_distortion_set_post_gain(self, args[ARG_post_gain].u_obj); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(audiofilters_distortion_set_post_gain_obj, 1, audiofilters_distortion_obj_set_post_gain); + +MP_PROPERTY_GETSET(audiofilters_distortion_post_gain_obj, + (mp_obj_t)&audiofilters_distortion_get_post_gain_obj, + (mp_obj_t)&audiofilters_distortion_set_post_gain_obj); + + +//| mode: DistortionMode +//| """Distortion type.""" +static mp_obj_t audiofilters_distortion_obj_get_mode(mp_obj_t self_in) { + audiofilters_distortion_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + return cp_enum_find(&audiofilters_distortion_mode_type, common_hal_audiofilters_distortion_get_mode(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(audiofilters_distortion_get_mode_obj, audiofilters_distortion_obj_get_mode); + +static mp_obj_t audiofilters_distortion_obj_set_mode(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_mode }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_mode, MP_ARG_OBJ | MP_ARG_REQUIRED, {} }, + }; + audiofilters_distortion_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); + + audiofilters_distortion_mode_t mode = validate_distortion_mode(args[ARG_mode].u_obj, MP_QSTR_mode); + common_hal_audiofilters_distortion_set_mode(self, mode); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(audiofilters_distortion_set_mode_obj, 1, audiofilters_distortion_obj_set_mode); + +MP_PROPERTY_GETSET(audiofilters_distortion_mode_obj, + (mp_obj_t)&audiofilters_distortion_get_mode_obj, + (mp_obj_t)&audiofilters_distortion_set_mode_obj); + + +//| mix: synthio.BlockInput +//| """The rate the filtered signal mix between 0 and 1 where 0 is only sample and 1 is all effect.""" +static mp_obj_t audiofilters_distortion_obj_get_mix(mp_obj_t self_in) { + return common_hal_audiofilters_distortion_get_mix(self_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(audiofilters_distortion_get_mix_obj, audiofilters_distortion_obj_get_mix); + +static mp_obj_t audiofilters_distortion_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, {} }, + }; + audiofilters_distortion_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_audiofilters_distortion_set_mix(self, args[ARG_mix].u_obj); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(audiofilters_distortion_set_mix_obj, 1, audiofilters_distortion_obj_set_mix); + +MP_PROPERTY_GETSET(audiofilters_distortion_mix_obj, + (mp_obj_t)&audiofilters_distortion_get_mix_obj, + (mp_obj_t)&audiofilters_distortion_set_mix_obj); + + +//| playing: bool +//| """True when the effect is playing a sample. (read-only)""" +static mp_obj_t audiofilters_distortion_obj_get_playing(mp_obj_t self_in) { + audiofilters_distortion_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + return mp_obj_new_bool(common_hal_audiofilters_distortion_get_playing(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(audiofilters_distortion_get_playing_obj, audiofilters_distortion_obj_get_playing); + +MP_PROPERTY_GETTER(audiofilters_distortion_playing_obj, + (mp_obj_t)&audiofilters_distortion_get_playing_obj); + +//| def play(self, sample: circuitpython_typing.AudioSample, *, loop: bool = False) -> None: +//| """Plays the sample once when loop=False and continuously when loop=True. +//| Does not block. Use `playing` to block. +//| +//| The sample must match the encoding settings given in the constructor.""" +//| ... +static mp_obj_t audiofilters_distortion_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} }, + }; + audiofilters_distortion_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_audiofilters_distortion_play(self, sample, args[ARG_loop].u_bool); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(audiofilters_distortion_play_obj, 1, audiofilters_distortion_obj_play); + +//| def stop(self) -> None: +//| """Stops playback of the sample.""" +//| ... +//| +static mp_obj_t audiofilters_distortion_obj_stop(mp_obj_t self_in) { + audiofilters_distortion_obj_t *self = MP_OBJ_TO_PTR(self_in); + + common_hal_audiofilters_distortion_stop(self); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(audiofilters_distortion_stop_obj, audiofilters_distortion_obj_stop); + +static const mp_rom_map_elem_t audiofilters_distortion_locals_dict_table[] = { + // Methods + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiofilters_distortion_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, + { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&audiofilters_distortion___exit___obj) }, + { MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audiofilters_distortion_play_obj) }, + { MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&audiofilters_distortion_stop_obj) }, + + // Properties + { MP_ROM_QSTR(MP_QSTR_playing), MP_ROM_PTR(&audiofilters_distortion_playing_obj) }, + { MP_ROM_QSTR(MP_QSTR_drive), MP_ROM_PTR(&audiofilters_distortion_drive_obj) }, + { MP_ROM_QSTR(MP_QSTR_pre_gain), MP_ROM_PTR(&audiofilters_distortion_pre_gain_obj) }, + { MP_ROM_QSTR(MP_QSTR_post_gain), MP_ROM_PTR(&audiofilters_distortion_post_gain_obj) }, + { MP_ROM_QSTR(MP_QSTR_mode), MP_ROM_PTR(&audiofilters_distortion_mode_obj) }, + { MP_ROM_QSTR(MP_QSTR_mix), MP_ROM_PTR(&audiofilters_distortion_mix_obj) }, +}; +static MP_DEFINE_CONST_DICT(audiofilters_distortion_locals_dict, audiofilters_distortion_locals_dict_table); + +static const audiosample_p_t audiofilters_distortion_proto = { + MP_PROTO_IMPLEMENT(MP_QSTR_protocol_audiosample) + .sample_rate = (audiosample_sample_rate_fun)common_hal_audiofilters_distortion_get_sample_rate, + .bits_per_sample = (audiosample_bits_per_sample_fun)common_hal_audiofilters_distortion_get_bits_per_sample, + .channel_count = (audiosample_channel_count_fun)common_hal_audiofilters_distortion_get_channel_count, + .reset_buffer = (audiosample_reset_buffer_fun)audiofilters_distortion_reset_buffer, + .get_buffer = (audiosample_get_buffer_fun)audiofilters_distortion_get_buffer, + .get_buffer_structure = (audiosample_get_buffer_structure_fun)audiofilters_distortion_get_buffer_structure, +}; + +MP_DEFINE_CONST_OBJ_TYPE( + audiofilters_distortion_type, + MP_QSTR_Distortion, + MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS, + make_new, audiofilters_distortion_make_new, + locals_dict, &audiofilters_distortion_locals_dict, + protocol, &audiofilters_distortion_proto + ); diff --git a/shared-bindings/audiofilters/Distortion.h b/shared-bindings/audiofilters/Distortion.h new file mode 100644 index 000000000000..fc48c58b40cd --- /dev/null +++ b/shared-bindings/audiofilters/Distortion.h @@ -0,0 +1,44 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "shared-bindings/audiofilters/DistortionMode.h" +#include "shared-module/audiofilters/Distortion.h" + +extern const mp_obj_type_t audiofilters_distortion_type; + +void common_hal_audiofilters_distortion_construct(audiofilters_distortion_obj_t *self, + mp_obj_t drive, mp_obj_t pre_gain, mp_obj_t post_gain, + audiofilters_distortion_mode_t mode, mp_obj_t mix, + uint32_t buffer_size, uint8_t bits_per_sample, bool samples_signed, + uint8_t channel_count, uint32_t sample_rate); + +void common_hal_audiofilters_distortion_deinit(audiofilters_distortion_obj_t *self); +bool common_hal_audiofilters_distortion_deinited(audiofilters_distortion_obj_t *self); + +uint32_t common_hal_audiofilters_distortion_get_sample_rate(audiofilters_distortion_obj_t *self); +uint8_t common_hal_audiofilters_distortion_get_channel_count(audiofilters_distortion_obj_t *self); +uint8_t common_hal_audiofilters_distortion_get_bits_per_sample(audiofilters_distortion_obj_t *self); + +mp_obj_t common_hal_audiofilters_distortion_get_drive(audiofilters_distortion_obj_t *self); +void common_hal_audiofilters_distortion_set_drive(audiofilters_distortion_obj_t *self, mp_obj_t arg); + +mp_obj_t common_hal_audiofilters_distortion_get_pre_gain(audiofilters_distortion_obj_t *self); +void common_hal_audiofilters_distortion_set_pre_gain(audiofilters_distortion_obj_t *self, mp_obj_t arg); + +mp_obj_t common_hal_audiofilters_distortion_get_post_gain(audiofilters_distortion_obj_t *self); +void common_hal_audiofilters_distortion_set_post_gain(audiofilters_distortion_obj_t *self, mp_obj_t arg); + +audiofilters_distortion_mode_t common_hal_audiofilters_distortion_get_mode(audiofilters_distortion_obj_t *self); +void common_hal_audiofilters_distortion_set_mode(audiofilters_distortion_obj_t *self, audiofilters_distortion_mode_t mode); + +mp_obj_t common_hal_audiofilters_distortion_get_mix(audiofilters_distortion_obj_t *self); +void common_hal_audiofilters_distortion_set_mix(audiofilters_distortion_obj_t *self, mp_obj_t arg); + +bool common_hal_audiofilters_distortion_get_playing(audiofilters_distortion_obj_t *self); +void common_hal_audiofilters_distortion_play(audiofilters_distortion_obj_t *self, mp_obj_t sample, bool loop); +void common_hal_audiofilters_distortion_stop(audiofilters_distortion_obj_t *self); diff --git a/shared-bindings/audiofilters/DistortionMode.c b/shared-bindings/audiofilters/DistortionMode.c new file mode 100644 index 000000000000..c0deeaa46931 --- /dev/null +++ b/shared-bindings/audiofilters/DistortionMode.c @@ -0,0 +1,50 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +#include "py/enum.h" + +#include "shared-bindings/audiofilters/DistortionMode.h" + +MAKE_ENUM_VALUE(audiofilters_distortion_mode_type, distortion_mode, CLIP, DISTORTION_MODE_CLIP); +MAKE_ENUM_VALUE(audiofilters_distortion_mode_type, distortion_mode, ATAN, DISTORTION_MODE_ATAN); +MAKE_ENUM_VALUE(audiofilters_distortion_mode_type, distortion_mode, LOFI, DISTORTION_MODE_LOFI); +MAKE_ENUM_VALUE(audiofilters_distortion_mode_type, distortion_mode, OVERDRIVE, DISTORTION_MODE_OVERDRIVE); +MAKE_ENUM_VALUE(audiofilters_distortion_mode_type, distortion_mode, WAVESHAPE, DISTORTION_MODE_WAVESHAPE); + +//| class DistortionMode: +//| """The method of distortion used by the `audiofilters.Distortion` effect.""" +//| +//| CLIP: DistortionMode +//| """Digital distortion effect which cuts off peaks at the top and bottom of the waveform.""" +//| +//| ATAN: DistortionMode +//| """""" +//| +//| LOFI: DistortionMode +//| """Low-resolution digital distortion effect (bit depth reduction). You can use it to emulate the sound of early digital audio devices.""" +//| +//| OVERDRIVE: DistortionMode +//| """Emulates the warm distortion produced by a field effect transistor, which is commonly used in solid-state musical instrument amplifiers. The `audiofilters.Distortion.drive` property has no effect in this mode.""" +//| +//| WAVESHAPE: DistortionMode +//| """Waveshaper distortions are used mainly by electronic musicians to achieve an extra-abrasive sound.""" +//| +MAKE_ENUM_MAP(audiofilters_distortion_mode) { + MAKE_ENUM_MAP_ENTRY(distortion_mode, CLIP), + MAKE_ENUM_MAP_ENTRY(distortion_mode, ATAN), + MAKE_ENUM_MAP_ENTRY(distortion_mode, LOFI), + MAKE_ENUM_MAP_ENTRY(distortion_mode, OVERDRIVE), + MAKE_ENUM_MAP_ENTRY(distortion_mode, WAVESHAPE), +}; +static MP_DEFINE_CONST_DICT(audiofilters_distortion_mode_locals_dict, audiofilters_distortion_mode_locals_table); + +MAKE_PRINTER(audiofilters, audiofilters_distortion_mode); + +MAKE_ENUM_TYPE(audiofilters, DistortionMode, audiofilters_distortion_mode); + +audiofilters_distortion_mode_t validate_distortion_mode(mp_obj_t obj, qstr arg_name) { + return cp_enum_value(&audiofilters_distortion_mode_type, obj, arg_name); +} diff --git a/shared-bindings/audiofilters/DistortionMode.h b/shared-bindings/audiofilters/DistortionMode.h new file mode 100644 index 000000000000..954fa7e38743 --- /dev/null +++ b/shared-bindings/audiofilters/DistortionMode.h @@ -0,0 +1,22 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "py/enum.h" + +typedef enum audiofilters_distortion_mode_e { + DISTORTION_MODE_CLIP, + DISTORTION_MODE_ATAN, + DISTORTION_MODE_LOFI, + DISTORTION_MODE_OVERDRIVE, + DISTORTION_MODE_WAVESHAPE, +} audiofilters_distortion_mode_t; + +extern const cp_enum_obj_t distortion_mode_CLIP_obj; +extern const mp_obj_type_t audiofilters_distortion_mode_type; + +extern audiofilters_distortion_mode_t validate_distortion_mode(mp_obj_t obj, qstr arg_name); diff --git a/shared-bindings/audiofilters/__init__.c b/shared-bindings/audiofilters/__init__.c index c4124515b7d3..fa122f9382b3 100644 --- a/shared-bindings/audiofilters/__init__.c +++ b/shared-bindings/audiofilters/__init__.c @@ -10,6 +10,8 @@ #include "py/runtime.h" #include "shared-bindings/audiofilters/__init__.h" +#include "shared-bindings/audiofilters/DistortionMode.h" +#include "shared-bindings/audiofilters/Distortion.h" #include "shared-bindings/audiofilters/Filter.h" //| """Support for audio filter effects @@ -21,6 +23,10 @@ static const mp_rom_map_elem_t audiofilters_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audiofilters) }, { MP_ROM_QSTR(MP_QSTR_Filter), MP_ROM_PTR(&audiofilters_filter_type) }, + { MP_ROM_QSTR(MP_QSTR_Distortion), MP_ROM_PTR(&audiofilters_distortion_type) }, + + // Enum-like Classes. + { MP_ROM_QSTR(MP_QSTR_DistortionMode), MP_ROM_PTR(&audiofilters_distortion_mode_type) }, }; static MP_DEFINE_CONST_DICT(audiofilters_module_globals, audiofilters_module_globals_table); diff --git a/shared-module/audiofilters/Distortion.c b/shared-module/audiofilters/Distortion.c new file mode 100644 index 000000000000..2dfd76d203d3 --- /dev/null +++ b/shared-module/audiofilters/Distortion.c @@ -0,0 +1,374 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT +#include "shared-bindings/audiofilters/Distortion.h" + +#include +#include "py/runtime.h" +#include + +/** + * Based on Godot's AudioEffectDistortion + * - https://docs.godotengine.org/en/stable/classes/class_audioeffectdistortion.html + * - https://github.com/godotengine/godot/blob/master/servers/audio/effects/audio_effect_distortion.cpp + */ + +void common_hal_audiofilters_distortion_construct(audiofilters_distortion_obj_t *self, + mp_obj_t drive, mp_obj_t pre_gain, mp_obj_t post_gain, audiofilters_distortion_mode_t mode, mp_obj_t mix, + uint32_t buffer_size, uint8_t bits_per_sample, + bool samples_signed, uint8_t channel_count, uint32_t sample_rate) { + + // Basic settings every effect and audio sample has + // These are the effects values, not the source sample(s) + self->bits_per_sample = bits_per_sample; // Most common is 16, but 8 is also supported in many places + self->samples_signed = samples_signed; // Are the samples we provide signed (common is true) + self->channel_count = channel_count; // Channels can be 1 for mono or 2 for stereo + self->sample_rate = sample_rate; // Sample rate for the effect, this generally needs to match all audio objects + + // To smooth things out as CircuitPython is doing other tasks most audio objects have a buffer + // A double buffer is set up here so the audio output can use DMA on buffer 1 while we + // write to and create buffer 2. + // This buffer is what is passed to the audio component that plays the effect. + // Samples are set sequentially. For stereo audio they are passed L/R/L/R/... + self->buffer_len = buffer_size; // in bytes + + self->buffer[0] = m_malloc(self->buffer_len); + if (self->buffer[0] == NULL) { + common_hal_audiofilters_distortion_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_audiofilters_distortion_deinit(self); + m_malloc_fail(self->buffer_len); + } + memset(self->buffer[1], 0, self->buffer_len); + + self->last_buf_idx = 1; // Which buffer to use first, toggle between 0 and 1 + + // Initialize other values most effects will need. + self->sample = NULL; // The current playing sample + self->sample_remaining_buffer = NULL; // Pointer to the start of the sample buffer we have not played + self->sample_buffer_length = 0; // How many samples do we have left to play (these may be 16 bit!) + self->loop = false; // When the sample is done do we loop to the start again or stop (e.g. in a wav file) + self->more_data = false; // Is there still more data to read from the sample or did we finish + + // The below section sets up the effect's starting values. + + // If we did not receive a BlockInput we need to create a default float value + if (drive == MP_OBJ_NULL) { + drive = mp_obj_new_float(0.0); + } + synthio_block_assign_slot(drive, &self->drive, MP_QSTR_drive); + + // If we did not receive a BlockInput we need to create a default float value + if (pre_gain == MP_OBJ_NULL) { + pre_gain = mp_obj_new_float(0.0); + } + synthio_block_assign_slot(pre_gain, &self->pre_gain, MP_QSTR_pre_gain); + + // If we did not receive a BlockInput we need to create a default float value + if (post_gain == MP_OBJ_NULL) { + post_gain = mp_obj_new_float(0.0); + } + synthio_block_assign_slot(post_gain, &self->post_gain, MP_QSTR_post_gain); + + self->mode = mode; + + // If we did not receive a BlockInput we need to create a default float value + if (mix == MP_OBJ_NULL) { + mix = mp_obj_new_float(1.0); + } + synthio_block_assign_slot(mix, &self->mix, MP_QSTR_mix); +} + +bool common_hal_audiofilters_distortion_deinited(audiofilters_distortion_obj_t *self) { + if (self->buffer[0] == NULL) { + return true; + } + return false; +} + +void common_hal_audiofilters_distortion_deinit(audiofilters_distortion_obj_t *self) { + if (common_hal_audiofilters_distortion_deinited(self)) { + return; + } + self->buffer[0] = NULL; + self->buffer[1] = NULL; +} + +mp_obj_t common_hal_audiofilters_distortion_get_drive(audiofilters_distortion_obj_t *self) { + return self->drive.obj; +} + +void common_hal_audiofilters_distortion_set_drive(audiofilters_distortion_obj_t *self, mp_obj_t arg) { + synthio_block_assign_slot(arg, &self->drive, MP_QSTR_drive); +} + +mp_obj_t common_hal_audiofilters_distortion_get_pre_gain(audiofilters_distortion_obj_t *self) { + return self->pre_gain.obj; +} + +void common_hal_audiofilters_distortion_set_pre_gain(audiofilters_distortion_obj_t *self, mp_obj_t arg) { + synthio_block_assign_slot(arg, &self->pre_gain, MP_QSTR_pre_gain); +} + +mp_obj_t common_hal_audiofilters_distortion_get_post_gain(audiofilters_distortion_obj_t *self) { + return self->post_gain.obj; +} + +void common_hal_audiofilters_distortion_set_post_gain(audiofilters_distortion_obj_t *self, mp_obj_t arg) { + synthio_block_assign_slot(arg, &self->post_gain, MP_QSTR_post_gain); +} + +audiofilters_distortion_mode_t common_hal_audiofilters_distortion_get_mode(audiofilters_distortion_obj_t *self) { + return self->mode; +} + +void common_hal_audiofilters_distortion_set_mode(audiofilters_distortion_obj_t *self, audiofilters_distortion_mode_t arg) { + self->mode = arg; +} + +mp_obj_t common_hal_audiofilters_distortion_get_mix(audiofilters_distortion_obj_t *self) { + return self->mix.obj; +} + +void common_hal_audiofilters_distortion_set_mix(audiofilters_distortion_obj_t *self, mp_obj_t arg) { + synthio_block_assign_slot(arg, &self->mix, MP_QSTR_mix); +} + +uint32_t common_hal_audiofilters_distortion_get_sample_rate(audiofilters_distortion_obj_t *self) { + return self->sample_rate; +} + +uint8_t common_hal_audiofilters_distortion_get_channel_count(audiofilters_distortion_obj_t *self) { + return self->channel_count; +} + +uint8_t common_hal_audiofilters_distortion_get_bits_per_sample(audiofilters_distortion_obj_t *self) { + return self->bits_per_sample; +} + +void audiofilters_distortion_reset_buffer(audiofilters_distortion_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); +} + +bool common_hal_audiofilters_distortion_get_playing(audiofilters_distortion_obj_t *self) { + return self->sample != NULL; +} + +void common_hal_audiofilters_distortion_play(audiofilters_distortion_obj_t *self, mp_obj_t sample, bool loop) { + // When a sample is to be played we must ensure the samples values matches what we expect + // Then we reset the sample and get the first buffer to play + // The get_buffer function will actually process that data + + if (audiosample_sample_rate(sample) != self->sample_rate) { + mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_sample_rate); + } + if (audiosample_channel_count(sample) != self->channel_count) { + mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_channel_count); + } + if (audiosample_bits_per_sample(sample) != self->bits_per_sample) { + mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_bits_per_sample); + } + bool single_buffer; + bool samples_signed; + uint32_t max_buffer_length; + uint8_t spacing; + audiosample_get_buffer_structure(sample, false, &single_buffer, &samples_signed, &max_buffer_length, &spacing); + if (samples_signed != self->samples_signed) { + mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_signedness); + } + + self->sample = sample; + self->loop = loop; + + audiosample_reset_buffer(self->sample, false, 0); + audioio_get_buffer_result_t result = audiosample_get_buffer(self->sample, false, 0, (uint8_t **)&self->sample_remaining_buffer, &self->sample_buffer_length); + + // Track remaining sample length in terms of bytes per sample + self->sample_buffer_length /= (self->bits_per_sample / 8); + // Store if we have more data in the sample to retrieve + self->more_data = result == GET_BUFFER_MORE_DATA; + + return; +} + +void common_hal_audiofilters_distortion_stop(audiofilters_distortion_obj_t *self) { + // When the sample is set to stop playing do any cleanup here + self->sample = NULL; + return; +} + +static mp_float_t db_to_linear(mp_float_t value) { + return expf(value * MICROPY_FLOAT_CONST(0.11512925464970228420089957273422)); +} + +audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_distortion_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 drive = MIN(MAX(synthio_block_slot_get(&self->drive), 0.0), 1.0); + mp_float_t pre_gain = db_to_linear(MIN(MAX(synthio_block_slot_get(&self->pre_gain), -60.0), 60.0)); + mp_float_t post_gain = db_to_linear(MIN(MAX(synthio_block_slot_get(&self->post_gain), -80.0), 24.0)); + mp_float_t mix = MIN(MAX(synthio_block_slot_get(&self->mix), 0.0), 1.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, 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); + + // Loop over the entire length of our buffer to fill it, this may require several calls to get data from the sample + while (length != 0) { + // Check if there is no more sample to play, we will either load more data, reset the sample if loop is on or clear the sample + if (self->sample_buffer_length == 0) { + if (!self->more_data) { // The sample has indicated it has no more data to play + if (self->loop && self->sample) { // If we are supposed to loop reset the sample to the start + audiosample_reset_buffer(self->sample, false, 0); + } else { // If we were not supposed to loop the sample, stop playing it + self->sample = NULL; + } + } + if (self->sample) { + // Load another sample buffer to play + audioio_get_buffer_result_t result = audiosample_get_buffer(self->sample, false, 0, (uint8_t **)&self->sample_remaining_buffer, &self->sample_buffer_length); + // Track length in terms of words. + self->sample_buffer_length /= (self->bits_per_sample / 8); + self->more_data = result == GET_BUFFER_MORE_DATA; + } + } + + // If we have a sample, filter it + if (self->sample != NULL) { + // 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; // 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++) { + if (MP_LIKELY(self->bits_per_sample == 16)) { + word_buffer[i] = sample_src[i]; + } else { + hword_buffer[i] = sample_hsrc[i]; + } + } + } else { + + // Pre-calculate drive-based constants if needed by effect mode + mp_float_t word_mult = 0; + mp_float_t word_div = 0; + switch (self->mode) { + case DISTORTION_MODE_ATAN: + word_mult = powf(10.0, drive * drive * 3.0) - 1.0 + 0.001; + word_div = 1.0 / (atanf(word_mult) * (1.0 + drive * 8)); + break; + case DISTORTION_MODE_LOFI: + word_mult = powf(2.0, 2.0 + (1.0 - drive) * 14); // goes from 16 to 2 bits + break; + default: + break; + } + + for (uint32_t i = 0; i < n; i++) { + int32_t sample_word = 0; + if (MP_LIKELY(self->bits_per_sample == 16)) { + sample_word = sample_src[i]; + } else { + if (self->samples_signed) { + sample_word = sample_hsrc[i]; + } else { + // Be careful here changing from an 8 bit unsigned to signed into a 32-bit signed + sample_word = (int8_t)(((uint8_t)sample_hsrc[i]) ^ 0x80); + } + } + + int32_t word = sample_word * pre_gain; + switch (self->mode) { + case DISTORTION_MODE_CLIP: { + mp_float_t word_sign = word < 0 ? -1.0f : 1.0f; + word = powf(fabs(word / 32768.0), 1.0001 - drive) * word_sign * 32767.0; + word = MIN(MAX(word, -32767), 32768); // Hard clip + } break; + case DISTORTION_MODE_ATAN: { + word = atanf(word / 32768.0 * word_mult) * word_div * 32767.0; + } break; + case DISTORTION_MODE_LOFI: { + word = floorf(word / 32768.0 * word_mult + 0.5) / word_mult * 32767.0; + } break; + case DISTORTION_MODE_OVERDRIVE: { + mp_float_t x = word / 32768.0 * 0.686306; + mp_float_t z = 1 + expf(sqrtf(fabs(x)) * -0.75); + word = (expf(x) - expf(-x * z)) / (expf(x) + expf(-x)) * 32767.0; + } break; + case DISTORTION_MODE_WAVESHAPE: { + mp_float_t x = word / 32768.0; + mp_float_t k = 2 * drive / (1.00001 - drive); + word = (1.0 + k) * x / (1.0 + k * fabsf(x)) * 32767.0; + } break; + } + word = word * post_gain; + + 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; + } + } 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; + } + } + } + } + + // 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; + self->sample_remaining_buffer += (n * (self->bits_per_sample / 8)); + self->sample_buffer_length -= n; + } + } + + // Finally pass our buffer and length to the calling audio function + *buffer = (uint8_t *)self->buffer[self->last_buf_idx]; + *buffer_length = self->buffer_len; + + // Distortion 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 audiofilters_distortion_get_buffer_structure(audiofilters_distortion_obj_t *self, bool single_channel_output, + bool *single_buffer, bool *samples_signed, uint32_t *max_buffer_length, uint8_t *spacing) { + + // Return information about the effect's buffer (not the sample's) + // These are used by calling audio objects to determine how to handle the effect's buffer + *single_buffer = false; + *samples_signed = self->samples_signed; + *max_buffer_length = self->buffer_len; + if (single_channel_output) { + *spacing = self->channel_count; + } else { + *spacing = 1; + } +} diff --git a/shared-module/audiofilters/Distortion.h b/shared-module/audiofilters/Distortion.h new file mode 100644 index 000000000000..a59ca6a241ab --- /dev/null +++ b/shared-module/audiofilters/Distortion.h @@ -0,0 +1,54 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT +#pragma once + +#include "py/obj.h" + +#include "shared-module/audiocore/__init__.h" +#include "shared-module/synthio/block.h" +#include "shared-bindings/audiofilters/DistortionMode.h" + +extern const mp_obj_type_t audiofilters_distortion_type; + +typedef struct { + mp_obj_base_t base; + synthio_block_slot_t drive; + synthio_block_slot_t pre_gain; + synthio_block_slot_t post_gain; + audiofilters_distortion_mode_t mode; + synthio_block_slot_t mix; + + uint8_t bits_per_sample; + bool samples_signed; + uint8_t channel_count; + uint32_t sample_rate; + + int8_t *buffer[2]; + uint8_t last_buf_idx; + uint32_t buffer_len; // max buffer in bytes + + uint8_t *sample_remaining_buffer; + uint32_t sample_buffer_length; + + bool loop; + bool more_data; + + mp_obj_t sample; +} audiofilters_distortion_obj_t; + +void audiofilters_distortion_reset_buffer(audiofilters_distortion_obj_t *self, + bool single_channel_output, + uint8_t channel); + +audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_distortion_obj_t *self, + bool single_channel_output, + uint8_t channel, + uint8_t **buffer, + uint32_t *buffer_length); // length in bytes + +void audiofilters_distortion_get_buffer_structure(audiofilters_distortion_obj_t *self, bool single_channel_output, + bool *single_buffer, bool *samples_signed, + uint32_t *max_buffer_length, uint8_t *spacing); From 46ebae15981861a5203aaea9782b0b4f0ac3583d Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Thu, 31 Oct 2024 10:33:09 -0500 Subject: [PATCH 02/16] Remove separate DistortionMode code files. --- py/circuitpy_defns.mk | 1 - shared-bindings/audiofilters/Distortion.c | 56 +++++++++++++++++-- shared-bindings/audiofilters/Distortion.h | 8 +-- shared-bindings/audiofilters/DistortionMode.c | 50 ----------------- shared-bindings/audiofilters/DistortionMode.h | 22 -------- shared-bindings/audiofilters/__init__.c | 1 - shared-module/audiofilters/Distortion.c | 15 +++-- shared-module/audiofilters/Distortion.h | 22 +++++--- 8 files changed, 78 insertions(+), 97 deletions(-) delete mode 100644 shared-bindings/audiofilters/DistortionMode.c delete mode 100644 shared-bindings/audiofilters/DistortionMode.h diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index a5d5f5f84dc4..9b6d6aefb757 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -570,7 +570,6 @@ $(filter $(SRC_PATTERNS), \ _bleio/ScanEntry.c \ _eve/__init__.c \ __future__/__init__.c \ - audiofilters/DistortionMode.c \ camera/ImageFormat.c \ canio/Match.c \ codeop/__init__.c \ diff --git a/shared-bindings/audiofilters/Distortion.c b/shared-bindings/audiofilters/Distortion.c index 2d1a138b4633..9c9dbef540cb 100644 --- a/shared-bindings/audiofilters/Distortion.c +++ b/shared-bindings/audiofilters/Distortion.c @@ -7,15 +7,58 @@ #include #include "shared-bindings/audiofilters/Distortion.h" -#include "shared-module/audiofilters/Distortion.h" #include "shared/runtime/context_manager_helpers.h" #include "py/binary.h" +#include "py/enum.h" #include "py/objproperty.h" #include "py/runtime.h" #include "shared-bindings/util.h" #include "shared-module/synthio/block.h" +//| class DistortionMode: +//| """The method of distortion used by the `audiofilters.Distortion` effect.""" +//| +//| CLIP: DistortionMode +//| """Digital distortion effect which cuts off peaks at the top and bottom of the waveform.""" +//| +//| ATAN: DistortionMode +//| """""" +//| +//| LOFI: DistortionMode +//| """Low-resolution digital distortion effect (bit depth reduction). You can use it to emulate the sound of early digital audio devices.""" +//| +//| OVERDRIVE: DistortionMode +//| """Emulates the warm distortion produced by a field effect transistor, which is commonly used in solid-state musical instrument amplifiers. The `audiofilters.Distortion.drive` property has no effect in this mode.""" +//| +//| WAVESHAPE: DistortionMode +//| """Waveshaper distortions are used mainly by electronic musicians to achieve an extra-abrasive sound.""" +//| + +MAKE_ENUM_VALUE(audiofilters_distortion_mode_type, distortion_mode, CLIP, DISTORTION_MODE_CLIP); +MAKE_ENUM_VALUE(audiofilters_distortion_mode_type, distortion_mode, ATAN, DISTORTION_MODE_ATAN); +MAKE_ENUM_VALUE(audiofilters_distortion_mode_type, distortion_mode, LOFI, DISTORTION_MODE_LOFI); +MAKE_ENUM_VALUE(audiofilters_distortion_mode_type, distortion_mode, OVERDRIVE, DISTORTION_MODE_OVERDRIVE); +MAKE_ENUM_VALUE(audiofilters_distortion_mode_type, distortion_mode, WAVESHAPE, DISTORTION_MODE_WAVESHAPE); + +MAKE_ENUM_MAP(audiofilters_distortion_mode) { + MAKE_ENUM_MAP_ENTRY(distortion_mode, CLIP), + MAKE_ENUM_MAP_ENTRY(distortion_mode, ATAN), + MAKE_ENUM_MAP_ENTRY(distortion_mode, LOFI), + MAKE_ENUM_MAP_ENTRY(distortion_mode, OVERDRIVE), + MAKE_ENUM_MAP_ENTRY(distortion_mode, WAVESHAPE), +}; + +static MP_DEFINE_CONST_DICT(audiofilters_distortion_mode_locals_dict, audiofilters_distortion_mode_locals_table); + +MAKE_PRINTER(audiofilters, audiofilters_distortion_mode); + +MAKE_ENUM_TYPE(audiofilters, DistortionMode, audiofilters_distortion_mode); + +static audiofilters_distortion_mode validate_distortion_mode(mp_obj_t obj, qstr arg_name) { + return cp_enum_value(&audiofilters_distortion_mode_type, obj, arg_name); +} + //| class Distortion: //| """A Distortion effect""" //| @@ -71,13 +114,14 @@ //| synth.release(note) //| time.sleep(5)""" //| ... + static mp_obj_t audiofilters_distortion_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { enum { ARG_drive, ARG_pre_gain, ARG_post_gain, ARG_mode, 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_drive, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_pre_gain, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_post_gain, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} }, - { MP_QSTR_mode, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_PTR((void *)&distortion_mode_CLIP_obj)} }, + { MP_QSTR_mode, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_mix, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_buffer_size, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 512} }, { MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 8000} }, @@ -96,11 +140,13 @@ static mp_obj_t audiofilters_distortion_make_new(const mp_obj_type_t *type, size mp_raise_ValueError(MP_ERROR_TEXT("bits_per_sample must be 8 or 16")); } - audiofilters_distortion_mode_t mode = validate_distortion_mode(args[ARG_mode].u_obj, MP_QSTR_mode); + audiofilters_distortion_mode mode = DISTORTION_MODE_CLIP; + if (args[ARG_mode].u_obj != MP_OBJ_NULL) { + mode = validate_distortion_mode(args[ARG_mode].u_obj, MP_QSTR_mode); + } audiofilters_distortion_obj_t *self = mp_obj_malloc(audiofilters_distortion_obj_t, &audiofilters_distortion_type); common_hal_audiofilters_distortion_construct(self, args[ARG_drive].u_obj, args[ARG_pre_gain].u_obj, args[ARG_post_gain].u_obj, mode, 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); } @@ -236,7 +282,7 @@ static mp_obj_t audiofilters_distortion_obj_set_mode(size_t n_args, const mp_obj 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); - audiofilters_distortion_mode_t mode = validate_distortion_mode(args[ARG_mode].u_obj, MP_QSTR_mode); + audiofilters_distortion_mode mode = validate_distortion_mode(args[ARG_mode].u_obj, MP_QSTR_mode); common_hal_audiofilters_distortion_set_mode(self, mode); return mp_const_none; diff --git a/shared-bindings/audiofilters/Distortion.h b/shared-bindings/audiofilters/Distortion.h index fc48c58b40cd..a1fded3a4fb4 100644 --- a/shared-bindings/audiofilters/Distortion.h +++ b/shared-bindings/audiofilters/Distortion.h @@ -6,14 +6,14 @@ #pragma once -#include "shared-bindings/audiofilters/DistortionMode.h" #include "shared-module/audiofilters/Distortion.h" extern const mp_obj_type_t audiofilters_distortion_type; +extern const mp_obj_type_t audiofilters_distortion_mode_type; void common_hal_audiofilters_distortion_construct(audiofilters_distortion_obj_t *self, mp_obj_t drive, mp_obj_t pre_gain, mp_obj_t post_gain, - audiofilters_distortion_mode_t mode, mp_obj_t mix, + audiofilters_distortion_mode mode, mp_obj_t mix, uint32_t buffer_size, uint8_t bits_per_sample, bool samples_signed, uint8_t channel_count, uint32_t sample_rate); @@ -33,8 +33,8 @@ void common_hal_audiofilters_distortion_set_pre_gain(audiofilters_distortion_obj mp_obj_t common_hal_audiofilters_distortion_get_post_gain(audiofilters_distortion_obj_t *self); void common_hal_audiofilters_distortion_set_post_gain(audiofilters_distortion_obj_t *self, mp_obj_t arg); -audiofilters_distortion_mode_t common_hal_audiofilters_distortion_get_mode(audiofilters_distortion_obj_t *self); -void common_hal_audiofilters_distortion_set_mode(audiofilters_distortion_obj_t *self, audiofilters_distortion_mode_t mode); +audiofilters_distortion_mode common_hal_audiofilters_distortion_get_mode(audiofilters_distortion_obj_t *self); +void common_hal_audiofilters_distortion_set_mode(audiofilters_distortion_obj_t *self, audiofilters_distortion_mode mode); mp_obj_t common_hal_audiofilters_distortion_get_mix(audiofilters_distortion_obj_t *self); void common_hal_audiofilters_distortion_set_mix(audiofilters_distortion_obj_t *self, mp_obj_t arg); diff --git a/shared-bindings/audiofilters/DistortionMode.c b/shared-bindings/audiofilters/DistortionMode.c deleted file mode 100644 index c0deeaa46931..000000000000 --- a/shared-bindings/audiofilters/DistortionMode.c +++ /dev/null @@ -1,50 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple -// -// SPDX-License-Identifier: MIT - -#include "py/enum.h" - -#include "shared-bindings/audiofilters/DistortionMode.h" - -MAKE_ENUM_VALUE(audiofilters_distortion_mode_type, distortion_mode, CLIP, DISTORTION_MODE_CLIP); -MAKE_ENUM_VALUE(audiofilters_distortion_mode_type, distortion_mode, ATAN, DISTORTION_MODE_ATAN); -MAKE_ENUM_VALUE(audiofilters_distortion_mode_type, distortion_mode, LOFI, DISTORTION_MODE_LOFI); -MAKE_ENUM_VALUE(audiofilters_distortion_mode_type, distortion_mode, OVERDRIVE, DISTORTION_MODE_OVERDRIVE); -MAKE_ENUM_VALUE(audiofilters_distortion_mode_type, distortion_mode, WAVESHAPE, DISTORTION_MODE_WAVESHAPE); - -//| class DistortionMode: -//| """The method of distortion used by the `audiofilters.Distortion` effect.""" -//| -//| CLIP: DistortionMode -//| """Digital distortion effect which cuts off peaks at the top and bottom of the waveform.""" -//| -//| ATAN: DistortionMode -//| """""" -//| -//| LOFI: DistortionMode -//| """Low-resolution digital distortion effect (bit depth reduction). You can use it to emulate the sound of early digital audio devices.""" -//| -//| OVERDRIVE: DistortionMode -//| """Emulates the warm distortion produced by a field effect transistor, which is commonly used in solid-state musical instrument amplifiers. The `audiofilters.Distortion.drive` property has no effect in this mode.""" -//| -//| WAVESHAPE: DistortionMode -//| """Waveshaper distortions are used mainly by electronic musicians to achieve an extra-abrasive sound.""" -//| -MAKE_ENUM_MAP(audiofilters_distortion_mode) { - MAKE_ENUM_MAP_ENTRY(distortion_mode, CLIP), - MAKE_ENUM_MAP_ENTRY(distortion_mode, ATAN), - MAKE_ENUM_MAP_ENTRY(distortion_mode, LOFI), - MAKE_ENUM_MAP_ENTRY(distortion_mode, OVERDRIVE), - MAKE_ENUM_MAP_ENTRY(distortion_mode, WAVESHAPE), -}; -static MP_DEFINE_CONST_DICT(audiofilters_distortion_mode_locals_dict, audiofilters_distortion_mode_locals_table); - -MAKE_PRINTER(audiofilters, audiofilters_distortion_mode); - -MAKE_ENUM_TYPE(audiofilters, DistortionMode, audiofilters_distortion_mode); - -audiofilters_distortion_mode_t validate_distortion_mode(mp_obj_t obj, qstr arg_name) { - return cp_enum_value(&audiofilters_distortion_mode_type, obj, arg_name); -} diff --git a/shared-bindings/audiofilters/DistortionMode.h b/shared-bindings/audiofilters/DistortionMode.h deleted file mode 100644 index 954fa7e38743..000000000000 --- a/shared-bindings/audiofilters/DistortionMode.h +++ /dev/null @@ -1,22 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple -// -// SPDX-License-Identifier: MIT - -#pragma once - -#include "py/enum.h" - -typedef enum audiofilters_distortion_mode_e { - DISTORTION_MODE_CLIP, - DISTORTION_MODE_ATAN, - DISTORTION_MODE_LOFI, - DISTORTION_MODE_OVERDRIVE, - DISTORTION_MODE_WAVESHAPE, -} audiofilters_distortion_mode_t; - -extern const cp_enum_obj_t distortion_mode_CLIP_obj; -extern const mp_obj_type_t audiofilters_distortion_mode_type; - -extern audiofilters_distortion_mode_t validate_distortion_mode(mp_obj_t obj, qstr arg_name); diff --git a/shared-bindings/audiofilters/__init__.c b/shared-bindings/audiofilters/__init__.c index fa122f9382b3..7a17ec655e62 100644 --- a/shared-bindings/audiofilters/__init__.c +++ b/shared-bindings/audiofilters/__init__.c @@ -10,7 +10,6 @@ #include "py/runtime.h" #include "shared-bindings/audiofilters/__init__.h" -#include "shared-bindings/audiofilters/DistortionMode.h" #include "shared-bindings/audiofilters/Distortion.h" #include "shared-bindings/audiofilters/Filter.h" diff --git a/shared-module/audiofilters/Distortion.c b/shared-module/audiofilters/Distortion.c index 2dfd76d203d3..1ff226d25bda 100644 --- a/shared-module/audiofilters/Distortion.c +++ b/shared-module/audiofilters/Distortion.c @@ -3,11 +3,13 @@ // SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple // // SPDX-License-Identifier: MIT -#include "shared-bindings/audiofilters/Distortion.h" #include +#include "py/obj.h" #include "py/runtime.h" #include +#include "shared-bindings/audiofilters/Distortion.h" +#include "shared-module/audiofilters/Distortion.h" /** * Based on Godot's AudioEffectDistortion @@ -16,9 +18,10 @@ */ void common_hal_audiofilters_distortion_construct(audiofilters_distortion_obj_t *self, - mp_obj_t drive, mp_obj_t pre_gain, mp_obj_t post_gain, audiofilters_distortion_mode_t mode, mp_obj_t mix, - uint32_t buffer_size, uint8_t bits_per_sample, - bool samples_signed, uint8_t channel_count, uint32_t sample_rate) { + mp_obj_t drive, mp_obj_t pre_gain, mp_obj_t post_gain, + audiofilters_distortion_mode mode, mp_obj_t mix, + uint32_t buffer_size, uint8_t bits_per_sample, bool samples_signed, + uint8_t channel_count, uint32_t sample_rate) { // Basic settings every effect and audio sample has // These are the effects values, not the source sample(s) @@ -125,11 +128,11 @@ void common_hal_audiofilters_distortion_set_post_gain(audiofilters_distortion_ob synthio_block_assign_slot(arg, &self->post_gain, MP_QSTR_post_gain); } -audiofilters_distortion_mode_t common_hal_audiofilters_distortion_get_mode(audiofilters_distortion_obj_t *self) { +audiofilters_distortion_mode common_hal_audiofilters_distortion_get_mode(audiofilters_distortion_obj_t *self) { return self->mode; } -void common_hal_audiofilters_distortion_set_mode(audiofilters_distortion_obj_t *self, audiofilters_distortion_mode_t arg) { +void common_hal_audiofilters_distortion_set_mode(audiofilters_distortion_obj_t *self, audiofilters_distortion_mode arg) { self->mode = arg; } diff --git a/shared-module/audiofilters/Distortion.h b/shared-module/audiofilters/Distortion.h index a59ca6a241ab..95750b7bf135 100644 --- a/shared-module/audiofilters/Distortion.h +++ b/shared-module/audiofilters/Distortion.h @@ -7,9 +7,17 @@ #include "py/obj.h" +#include "shared-bindings/audiofilters/Distortion.h" #include "shared-module/audiocore/__init__.h" #include "shared-module/synthio/block.h" -#include "shared-bindings/audiofilters/DistortionMode.h" + +typedef enum { + DISTORTION_MODE_CLIP, + DISTORTION_MODE_ATAN, + DISTORTION_MODE_LOFI, + DISTORTION_MODE_OVERDRIVE, + DISTORTION_MODE_WAVESHAPE, +} audiofilters_distortion_mode; extern const mp_obj_type_t audiofilters_distortion_type; @@ -18,7 +26,7 @@ typedef struct { synthio_block_slot_t drive; synthio_block_slot_t pre_gain; synthio_block_slot_t post_gain; - audiofilters_distortion_mode_t mode; + audiofilters_distortion_mode mode; synthio_block_slot_t mix; uint8_t bits_per_sample; @@ -44,11 +52,9 @@ void audiofilters_distortion_reset_buffer(audiofilters_distortion_obj_t *self, uint8_t channel); audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_distortion_obj_t *self, - bool single_channel_output, - uint8_t channel, - uint8_t **buffer, - uint32_t *buffer_length); // length in bytes + bool single_channel_output, uint8_t channel, + uint8_t **buffer, uint32_t *buffer_length); -void audiofilters_distortion_get_buffer_structure(audiofilters_distortion_obj_t *self, bool single_channel_output, - bool *single_buffer, bool *samples_signed, +void audiofilters_distortion_get_buffer_structure(audiofilters_distortion_obj_t *self, + bool single_channel_output, bool *single_buffer, bool *samples_signed, uint32_t *max_buffer_length, uint8_t *spacing); From 3a16dafef8974b0824b5e19579f87fa48ffcddea Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Thu, 31 Oct 2024 10:44:05 -0500 Subject: [PATCH 03/16] Remove `audiofilters.DistortionMode.ATAN` --- shared-bindings/audiofilters/Distortion.c | 5 ----- shared-module/audiofilters/Distortion.c | 8 -------- shared-module/audiofilters/Distortion.h | 1 - 3 files changed, 14 deletions(-) diff --git a/shared-bindings/audiofilters/Distortion.c b/shared-bindings/audiofilters/Distortion.c index 9c9dbef540cb..d7f7914a7bf7 100644 --- a/shared-bindings/audiofilters/Distortion.c +++ b/shared-bindings/audiofilters/Distortion.c @@ -22,9 +22,6 @@ //| CLIP: DistortionMode //| """Digital distortion effect which cuts off peaks at the top and bottom of the waveform.""" //| -//| ATAN: DistortionMode -//| """""" -//| //| LOFI: DistortionMode //| """Low-resolution digital distortion effect (bit depth reduction). You can use it to emulate the sound of early digital audio devices.""" //| @@ -36,14 +33,12 @@ //| MAKE_ENUM_VALUE(audiofilters_distortion_mode_type, distortion_mode, CLIP, DISTORTION_MODE_CLIP); -MAKE_ENUM_VALUE(audiofilters_distortion_mode_type, distortion_mode, ATAN, DISTORTION_MODE_ATAN); MAKE_ENUM_VALUE(audiofilters_distortion_mode_type, distortion_mode, LOFI, DISTORTION_MODE_LOFI); MAKE_ENUM_VALUE(audiofilters_distortion_mode_type, distortion_mode, OVERDRIVE, DISTORTION_MODE_OVERDRIVE); MAKE_ENUM_VALUE(audiofilters_distortion_mode_type, distortion_mode, WAVESHAPE, DISTORTION_MODE_WAVESHAPE); MAKE_ENUM_MAP(audiofilters_distortion_mode) { MAKE_ENUM_MAP_ENTRY(distortion_mode, CLIP), - MAKE_ENUM_MAP_ENTRY(distortion_mode, ATAN), MAKE_ENUM_MAP_ENTRY(distortion_mode, LOFI), MAKE_ENUM_MAP_ENTRY(distortion_mode, OVERDRIVE), MAKE_ENUM_MAP_ENTRY(distortion_mode, WAVESHAPE), diff --git a/shared-module/audiofilters/Distortion.c b/shared-module/audiofilters/Distortion.c index 1ff226d25bda..aac914025c40 100644 --- a/shared-module/audiofilters/Distortion.c +++ b/shared-module/audiofilters/Distortion.c @@ -276,12 +276,7 @@ audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_dist // Pre-calculate drive-based constants if needed by effect mode mp_float_t word_mult = 0; - mp_float_t word_div = 0; switch (self->mode) { - case DISTORTION_MODE_ATAN: - word_mult = powf(10.0, drive * drive * 3.0) - 1.0 + 0.001; - word_div = 1.0 / (atanf(word_mult) * (1.0 + drive * 8)); - break; case DISTORTION_MODE_LOFI: word_mult = powf(2.0, 2.0 + (1.0 - drive) * 14); // goes from 16 to 2 bits break; @@ -309,9 +304,6 @@ audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_dist word = powf(fabs(word / 32768.0), 1.0001 - drive) * word_sign * 32767.0; word = MIN(MAX(word, -32767), 32768); // Hard clip } break; - case DISTORTION_MODE_ATAN: { - word = atanf(word / 32768.0 * word_mult) * word_div * 32767.0; - } break; case DISTORTION_MODE_LOFI: { word = floorf(word / 32768.0 * word_mult + 0.5) / word_mult * 32767.0; } break; diff --git a/shared-module/audiofilters/Distortion.h b/shared-module/audiofilters/Distortion.h index 95750b7bf135..f701fb17c4ae 100644 --- a/shared-module/audiofilters/Distortion.h +++ b/shared-module/audiofilters/Distortion.h @@ -13,7 +13,6 @@ typedef enum { DISTORTION_MODE_CLIP, - DISTORTION_MODE_ATAN, DISTORTION_MODE_LOFI, DISTORTION_MODE_OVERDRIVE, DISTORTION_MODE_WAVESHAPE, From 1008dd5a1bf22bf16f7f8905131413c47d2be439 Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Thu, 31 Oct 2024 11:11:45 -0500 Subject: [PATCH 04/16] Simplify `audiofilters.DistortionMode.LOFI` sample processing with bit mask. --- shared-bindings/audiofilters/Distortion.c | 2 +- shared-module/audiofilters/Distortion.c | 16 ++++------------ 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/shared-bindings/audiofilters/Distortion.c b/shared-bindings/audiofilters/Distortion.c index d7f7914a7bf7..8241131e7946 100644 --- a/shared-bindings/audiofilters/Distortion.c +++ b/shared-bindings/audiofilters/Distortion.c @@ -134,7 +134,7 @@ static mp_obj_t audiofilters_distortion_make_new(const mp_obj_type_t *type, size if (bits_per_sample != 8 && bits_per_sample != 16) { mp_raise_ValueError(MP_ERROR_TEXT("bits_per_sample must be 8 or 16")); } - + audiofilters_distortion_mode mode = DISTORTION_MODE_CLIP; if (args[ARG_mode].u_obj != MP_OBJ_NULL) { mode = validate_distortion_mode(args[ARG_mode].u_obj, MP_QSTR_mode); diff --git a/shared-module/audiofilters/Distortion.c b/shared-module/audiofilters/Distortion.c index aac914025c40..89613e9e1b78 100644 --- a/shared-module/audiofilters/Distortion.c +++ b/shared-module/audiofilters/Distortion.c @@ -212,7 +212,7 @@ void common_hal_audiofilters_distortion_stop(audiofilters_distortion_obj_t *self } static mp_float_t db_to_linear(mp_float_t value) { - return expf(value * MICROPY_FLOAT_CONST(0.11512925464970228420089957273422)); + return expf(value * MICROPY_FLOAT_CONST(0.11512925464970228420089957273422)); } audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_distortion_obj_t *self, bool single_channel_output, uint8_t channel, @@ -274,15 +274,7 @@ audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_dist } } else { - // Pre-calculate drive-based constants if needed by effect mode - mp_float_t word_mult = 0; - switch (self->mode) { - case DISTORTION_MODE_LOFI: - word_mult = powf(2.0, 2.0 + (1.0 - drive) * 14); // goes from 16 to 2 bits - break; - default: - break; - } + uint32_t word_mask = 0xFFFFFFFF ^ ((1 << (uint32_t)roundf(drive * 14.0)) - 1); // LOFI mode bit mask for (uint32_t i = 0; i < n; i++) { int32_t sample_word = 0; @@ -305,7 +297,7 @@ audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_dist word = MIN(MAX(word, -32767), 32768); // Hard clip } break; case DISTORTION_MODE_LOFI: { - word = floorf(word / 32768.0 * word_mult + 0.5) / word_mult * 32767.0; + word = word & word_mask; } break; case DISTORTION_MODE_OVERDRIVE: { mp_float_t x = word / 32768.0 * 0.686306; @@ -319,7 +311,7 @@ audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_dist } break; } word = word * post_gain; - + if (MP_LIKELY(self->bits_per_sample == 16)) { word_buffer[i] = (sample_word * (1.0 - mix)) + (word * mix); if (!self->samples_signed) { From 37b6b70b08fcd5781394536b4af025071cfc9543 Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Sun, 3 Nov 2024 18:43:55 -0600 Subject: [PATCH 05/16] Fix error with null sample handling in `audiofilters.Distortion`. --- shared-module/audiofilters/Distortion.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/shared-module/audiofilters/Distortion.c b/shared-module/audiofilters/Distortion.c index 89613e9e1b78..2edb2018ad48 100644 --- a/shared-module/audiofilters/Distortion.c +++ b/shared-module/audiofilters/Distortion.c @@ -256,8 +256,21 @@ audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_dist } } - // If we have a sample, filter it - if (self->sample != NULL) { + if (self->sample == NULL) { + if (self->samples_signed) { + memset(word_buffer, 0, length * (self->bits_per_sample / 8)); + } else { + // 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)); + } + } + + length = 0; + } else { + // we have a sample to play and apply effect // 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); From 31c9095fcd4c708a2d39ae85a50b4e61d423fd60 Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Tue, 10 Dec 2024 08:35:39 -0600 Subject: [PATCH 06/16] Implement `synthio_block_slot_get_limited`. --- shared-module/audiofilters/Distortion.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shared-module/audiofilters/Distortion.c b/shared-module/audiofilters/Distortion.c index 2edb2018ad48..eb7ffe9a8c1d 100644 --- a/shared-module/audiofilters/Distortion.c +++ b/shared-module/audiofilters/Distortion.c @@ -223,10 +223,10 @@ audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_dist } // 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 drive = MIN(MAX(synthio_block_slot_get(&self->drive), 0.0), 1.0); - mp_float_t pre_gain = db_to_linear(MIN(MAX(synthio_block_slot_get(&self->pre_gain), -60.0), 60.0)); - mp_float_t post_gain = db_to_linear(MIN(MAX(synthio_block_slot_get(&self->post_gain), -80.0), 24.0)); - mp_float_t mix = MIN(MAX(synthio_block_slot_get(&self->mix), 0.0), 1.0); + mp_float_t drive = synthio_block_slot_get_limited(&self->drive, 0.0, 1.0); + mp_float_t pre_gain = db_to_linear(synthio_block_slot_get_limited(&self->pre_gain, -60.0, 60.0)); + mp_float_t post_gain = db_to_linear(synthio_block_slot_get_limited(&self->post_gain, -80.0, 24.0)); + mp_float_t mix = synthio_block_slot_get_limited(&self->mix, 0.0, 1.0); // Switch our buffers to the other buffer self->last_buf_idx = !self->last_buf_idx; From 155f197fe608cb0520fbf9ace426b65d79bc9b0f Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Tue, 10 Dec 2024 08:44:07 -0600 Subject: [PATCH 07/16] Convert default float values from null checks to MP_ROM_INT. --- shared-bindings/audiofilters/Distortion.c | 8 ++++---- shared-module/audiofilters/Distortion.c | 21 +-------------------- 2 files changed, 5 insertions(+), 24 deletions(-) diff --git a/shared-bindings/audiofilters/Distortion.c b/shared-bindings/audiofilters/Distortion.c index 8241131e7946..f6906203b019 100644 --- a/shared-bindings/audiofilters/Distortion.c +++ b/shared-bindings/audiofilters/Distortion.c @@ -113,11 +113,11 @@ static audiofilters_distortion_mode validate_distortion_mode(mp_obj_t obj, qstr static mp_obj_t audiofilters_distortion_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { enum { ARG_drive, ARG_pre_gain, ARG_post_gain, ARG_mode, 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_drive, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} }, - { MP_QSTR_pre_gain, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} }, - { MP_QSTR_post_gain, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_drive, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(0)} }, + { MP_QSTR_pre_gain, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(0)} }, + { MP_QSTR_post_gain, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(0)} }, { MP_QSTR_mode, 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_mix, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(1)} }, { 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} }, diff --git a/shared-module/audiofilters/Distortion.c b/shared-module/audiofilters/Distortion.c index eb7ffe9a8c1d..73e32635ffbe 100644 --- a/shared-module/audiofilters/Distortion.c +++ b/shared-module/audiofilters/Distortion.c @@ -62,31 +62,12 @@ void common_hal_audiofilters_distortion_construct(audiofilters_distortion_obj_t // The below section sets up the effect's starting values. - // If we did not receive a BlockInput we need to create a default float value - if (drive == MP_OBJ_NULL) { - drive = mp_obj_new_float(0.0); - } synthio_block_assign_slot(drive, &self->drive, MP_QSTR_drive); - - // If we did not receive a BlockInput we need to create a default float value - if (pre_gain == MP_OBJ_NULL) { - pre_gain = mp_obj_new_float(0.0); - } synthio_block_assign_slot(pre_gain, &self->pre_gain, MP_QSTR_pre_gain); - - // If we did not receive a BlockInput we need to create a default float value - if (post_gain == MP_OBJ_NULL) { - post_gain = mp_obj_new_float(0.0); - } synthio_block_assign_slot(post_gain, &self->post_gain, MP_QSTR_post_gain); + synthio_block_assign_slot(mix, &self->mix, MP_QSTR_mix); self->mode = mode; - - // If we did not receive a BlockInput we need to create a default float value - if (mix == MP_OBJ_NULL) { - mix = mp_obj_new_float(1.0); - } - synthio_block_assign_slot(mix, &self->mix, MP_QSTR_mix); } bool common_hal_audiofilters_distortion_deinited(audiofilters_distortion_obj_t *self) { From 89f2ae10a343cf5e5285f5ac97b1af6f1c344158 Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Tue, 10 Dec 2024 08:50:19 -0600 Subject: [PATCH 08/16] Remove unnecessary kwarg setters. --- shared-bindings/audiofilters/Distortion.c | 80 ++++++----------------- 1 file changed, 20 insertions(+), 60 deletions(-) diff --git a/shared-bindings/audiofilters/Distortion.c b/shared-bindings/audiofilters/Distortion.c index f6906203b019..551703220c71 100644 --- a/shared-bindings/audiofilters/Distortion.c +++ b/shared-bindings/audiofilters/Distortion.c @@ -185,20 +185,12 @@ static mp_obj_t audiofilters_distortion_obj_get_drive(mp_obj_t self_in) { } MP_DEFINE_CONST_FUN_OBJ_1(audiofilters_distortion_get_drive_obj, audiofilters_distortion_obj_get_drive); -static mp_obj_t audiofilters_distortion_obj_set_drive(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_drive }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_drive, MP_ARG_OBJ | MP_ARG_REQUIRED, {} }, - }; - audiofilters_distortion_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_audiofilters_distortion_set_drive(self, args[ARG_drive].u_obj); - +static mp_obj_t audiofilters_distortion_obj_set_drive(mp_obj_t self_in, mp_obj_t drive_in) { + audiofilters_distortion_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_audiofilters_distortion_set_drive(self, drive_in); return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_KW(audiofilters_distortion_set_drive_obj, 1, audiofilters_distortion_obj_set_drive); +MP_DEFINE_CONST_FUN_OBJ_2(audiofilters_distortion_set_drive_obj, audiofilters_distortion_obj_set_drive); MP_PROPERTY_GETSET(audiofilters_distortion_drive_obj, (mp_obj_t)&audiofilters_distortion_get_drive_obj, @@ -212,20 +204,12 @@ static mp_obj_t audiofilters_distortion_obj_get_pre_gain(mp_obj_t self_in) { } MP_DEFINE_CONST_FUN_OBJ_1(audiofilters_distortion_get_pre_gain_obj, audiofilters_distortion_obj_get_pre_gain); -static mp_obj_t audiofilters_distortion_obj_set_pre_gain(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_pre_gain }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_pre_gain, MP_ARG_OBJ | MP_ARG_REQUIRED, {} }, - }; - audiofilters_distortion_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_audiofilters_distortion_set_pre_gain(self, args[ARG_pre_gain].u_obj); - +static mp_obj_t audiofilters_distortion_obj_set_pre_gain(mp_obj_t self_in, mp_obj_t pre_gain_in) { + audiofilters_distortion_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_audiofilters_distortion_set_pre_gain(self, pre_gain_in); return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_KW(audiofilters_distortion_set_pre_gain_obj, 1, audiofilters_distortion_obj_set_pre_gain); +MP_DEFINE_CONST_FUN_OBJ_2(audiofilters_distortion_set_pre_gain_obj, audiofilters_distortion_obj_set_pre_gain); MP_PROPERTY_GETSET(audiofilters_distortion_pre_gain_obj, (mp_obj_t)&audiofilters_distortion_get_pre_gain_obj, @@ -239,20 +223,12 @@ static mp_obj_t audiofilters_distortion_obj_get_post_gain(mp_obj_t self_in) { } MP_DEFINE_CONST_FUN_OBJ_1(audiofilters_distortion_get_post_gain_obj, audiofilters_distortion_obj_get_post_gain); -static mp_obj_t audiofilters_distortion_obj_set_post_gain(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_post_gain }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_post_gain, MP_ARG_OBJ | MP_ARG_REQUIRED, {} }, - }; - audiofilters_distortion_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_audiofilters_distortion_set_post_gain(self, args[ARG_post_gain].u_obj); - +static mp_obj_t audiofilters_distortion_obj_set_post_gain(mp_obj_t self_in, mp_obj_t post_gain_in) { + audiofilters_distortion_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_audiofilters_distortion_set_post_gain(self, post_gain_in); return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_KW(audiofilters_distortion_set_post_gain_obj, 1, audiofilters_distortion_obj_set_post_gain); +MP_DEFINE_CONST_FUN_OBJ_2(audiofilters_distortion_set_post_gain_obj, audiofilters_distortion_obj_set_post_gain); MP_PROPERTY_GETSET(audiofilters_distortion_post_gain_obj, (mp_obj_t)&audiofilters_distortion_get_post_gain_obj, @@ -268,21 +244,13 @@ static mp_obj_t audiofilters_distortion_obj_get_mode(mp_obj_t self_in) { } MP_DEFINE_CONST_FUN_OBJ_1(audiofilters_distortion_get_mode_obj, audiofilters_distortion_obj_get_mode); -static mp_obj_t audiofilters_distortion_obj_set_mode(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_mode }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_mode, MP_ARG_OBJ | MP_ARG_REQUIRED, {} }, - }; - audiofilters_distortion_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); - - audiofilters_distortion_mode mode = validate_distortion_mode(args[ARG_mode].u_obj, MP_QSTR_mode); +static mp_obj_t audiofilters_distortion_obj_set_mode(mp_obj_t self_in, mp_obj_t mode_in) { + audiofilters_distortion_obj_t *self = MP_OBJ_TO_PTR(self_in); + audiofilters_distortion_mode mode = validate_distortion_mode(mode_in, MP_QSTR_mode); common_hal_audiofilters_distortion_set_mode(self, mode); - return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_KW(audiofilters_distortion_set_mode_obj, 1, audiofilters_distortion_obj_set_mode); +MP_DEFINE_CONST_FUN_OBJ_2(audiofilters_distortion_set_mode_obj, audiofilters_distortion_obj_set_mode); MP_PROPERTY_GETSET(audiofilters_distortion_mode_obj, (mp_obj_t)&audiofilters_distortion_get_mode_obj, @@ -296,20 +264,12 @@ static mp_obj_t audiofilters_distortion_obj_get_mix(mp_obj_t self_in) { } MP_DEFINE_CONST_FUN_OBJ_1(audiofilters_distortion_get_mix_obj, audiofilters_distortion_obj_get_mix); -static mp_obj_t audiofilters_distortion_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, {} }, - }; - audiofilters_distortion_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_audiofilters_distortion_set_mix(self, args[ARG_mix].u_obj); - +static mp_obj_t audiofilters_distortion_obj_set_mix(mp_obj_t self_in, mp_obj_t mix_in) { + audiofilters_distortion_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_audiofilters_distortion_set_mix(self, mix_in); return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_KW(audiofilters_distortion_set_mix_obj, 1, audiofilters_distortion_obj_set_mix); +MP_DEFINE_CONST_FUN_OBJ_2(audiofilters_distortion_set_mix_obj, audiofilters_distortion_obj_set_mix); MP_PROPERTY_GETSET(audiofilters_distortion_mix_obj, (mp_obj_t)&audiofilters_distortion_get_mix_obj, From 5c981f032f775ae8382ffae7849ff34af2443a8e Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Tue, 10 Dec 2024 09:16:17 -0600 Subject: [PATCH 09/16] Use `MICROPY_FLOAT_CONST` and `MICROPY_FLOAT_C_FUN` within floating point calculations. --- shared-module/audiofilters/Distortion.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/shared-module/audiofilters/Distortion.c b/shared-module/audiofilters/Distortion.c index 73e32635ffbe..93ae22c90b59 100644 --- a/shared-module/audiofilters/Distortion.c +++ b/shared-module/audiofilters/Distortion.c @@ -193,7 +193,7 @@ void common_hal_audiofilters_distortion_stop(audiofilters_distortion_obj_t *self } static mp_float_t db_to_linear(mp_float_t value) { - return expf(value * MICROPY_FLOAT_CONST(0.11512925464970228420089957273422)); + return MICROPY_FLOAT_C_FUN(exp)(value * MICROPY_FLOAT_CONST(0.11512925464970228420089957273422)); } audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_distortion_obj_t *self, bool single_channel_output, uint8_t channel, @@ -286,22 +286,23 @@ audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_dist int32_t word = sample_word * pre_gain; switch (self->mode) { case DISTORTION_MODE_CLIP: { - mp_float_t word_sign = word < 0 ? -1.0f : 1.0f; - word = powf(fabs(word / 32768.0), 1.0001 - drive) * word_sign * 32767.0; + mp_float_t word_sign = word < 0 ? MICROPY_FLOAT_CONST(-1.0) : MICROPY_FLOAT_CONST(1.0); + word = MICROPY_FLOAT_C_FUN(pow)(MICROPY_FLOAT_C_FUN(fabs)(word / MICROPY_FLOAT_CONST(32768.0)), MICROPY_FLOAT_CONST(1.0001) - drive) * word_sign * MICROPY_FLOAT_CONST(32767.0); word = MIN(MAX(word, -32767), 32768); // Hard clip } break; case DISTORTION_MODE_LOFI: { word = word & word_mask; } break; case DISTORTION_MODE_OVERDRIVE: { - mp_float_t x = word / 32768.0 * 0.686306; - mp_float_t z = 1 + expf(sqrtf(fabs(x)) * -0.75); - word = (expf(x) - expf(-x * z)) / (expf(x) + expf(-x)) * 32767.0; + mp_float_t x = word / MICROPY_FLOAT_CONST(32768.0) * MICROPY_FLOAT_CONST(0.686306); + mp_float_t z = MICROPY_FLOAT_CONST(1.0) + MICROPY_FLOAT_C_FUN(exp)(MICROPY_FLOAT_C_FUN(sqrt)(MICROPY_FLOAT_C_FUN(fabs)(x)) * MICROPY_FLOAT_CONST(-0.75)); + mp_float_t exp_x = MICROPY_FLOAT_C_FUN(exp)(x); + word = (exp_x - MICROPY_FLOAT_C_FUN(exp)(-x * z)) / (exp_x + MICROPY_FLOAT_C_FUN(exp)(-x)) * MICROPY_FLOAT_CONST(32767.0); } break; case DISTORTION_MODE_WAVESHAPE: { - mp_float_t x = word / 32768.0; - mp_float_t k = 2 * drive / (1.00001 - drive); - word = (1.0 + k) * x / (1.0 + k * fabsf(x)) * 32767.0; + mp_float_t x = word / MICROPY_FLOAT_CONST(32768.0); + mp_float_t k = MICROPY_FLOAT_CONST(2.0) * drive / (MICROPY_FLOAT_CONST(1.00001) - drive); + word = (MICROPY_FLOAT_CONST(1.0) + k) * x / (MICROPY_FLOAT_CONST(1.0) + k * MICROPY_FLOAT_C_FUN(fabs)(x)) * MICROPY_FLOAT_CONST(32767.0); } break; } word = word * post_gain; From 222ce2c7827652714a42d18462e4551ddb2c8e06 Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Tue, 10 Dec 2024 09:33:35 -0600 Subject: [PATCH 10/16] Apply similar updates to audiofilters.Filter and audiodelays.Echo: MICROPY_FLOAT_CONST, MP_ROM_INT, and synthio_block_slot_get_limited. --- shared-bindings/audiofilters/Filter.c | 2 +- shared-module/audiodelays/Echo.c | 20 ++++++++++---------- shared-module/audiofilters/Distortion.c | 8 ++++---- shared-module/audiofilters/Filter.c | 10 +++------- 4 files changed, 18 insertions(+), 22 deletions(-) diff --git a/shared-bindings/audiofilters/Filter.c b/shared-bindings/audiofilters/Filter.c index 34c8cf0c8da9..07a5739dcb27 100644 --- a/shared-bindings/audiofilters/Filter.c +++ b/shared-bindings/audiofilters/Filter.c @@ -72,7 +72,7 @@ static mp_obj_t audiofilters_filter_make_new(const mp_obj_type_t *type, size_t n enum { ARG_filter, 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_filter, 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_mix, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(1)} }, { 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} }, diff --git a/shared-module/audiodelays/Echo.c b/shared-module/audiodelays/Echo.c index e4eeff48f4c3..9ec322875798 100644 --- a/shared-module/audiodelays/Echo.c +++ b/shared-module/audiodelays/Echo.c @@ -57,17 +57,17 @@ void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_ // 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); + decay = mp_obj_new_float(MICROPY_FLOAT_CONST(0.7)); } synthio_block_assign_slot(decay, &self->decay, MP_QSTR_decay); if (delay_ms == MP_OBJ_NULL) { - delay_ms = mp_obj_new_float(250.0); + delay_ms = mp_obj_new_float(MICROPY_FLOAT_CONST(250.0)); } 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); + mix = mp_obj_new_float(MICROPY_FLOAT_CONST(0.5)); } synthio_block_assign_slot(mix, &self->mix, MP_QSTR_mix); @@ -77,7 +77,7 @@ void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_ // 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 = (uint32_t)(self->sample_rate / 1000.0f * max_delay_ms) * (self->channel_count * sizeof(uint16_t)); // bytes + self->max_echo_buffer_len = (uint32_t)(self->sample_rate / MICROPY_FLOAT_CONST(1000.0) * 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); @@ -285,8 +285,8 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * } // 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)); + mp_float_t mix = synthio_block_slot_get_limited(&self->mix, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.0)); + mp_float_t decay = synthio_block_slot_get_limited(&self->decay, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.0)); uint32_t delay_ms = (uint32_t)synthio_block_slot_get(&self->delay_ms); if (self->current_delay_ms != delay_ms) { @@ -336,7 +336,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 (mix <= 0.01) { // Mix of 0 is pure sample sound. We have no sample so no sound + if (mix <= MICROPY_FLOAT_CONST(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 { @@ -406,7 +406,7 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * 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 + if (mix <= MICROPY_FLOAT_CONST(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]; @@ -467,12 +467,12 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * word = echo + sample_word; if (MP_LIKELY(self->bits_per_sample == 16)) { - word_buffer[i] = (int16_t)((sample_word * (1.0 - mix)) + (word * mix)); + word_buffer[i] = (int16_t)((sample_word * (MICROPY_FLOAT_CONST(1.0) - mix)) + (word * mix)); if (!self->samples_signed) { word_buffer[i] ^= 0x8000; } } else { - int8_t mixed = (int16_t)((sample_word * (1.0 - mix)) + (word * mix)); + int8_t mixed = (int16_t)((sample_word * (MICROPY_FLOAT_CONST(1.0) - mix)) + (word * mix)); if (self->samples_signed) { hword_buffer[i] = mixed; } else { diff --git a/shared-module/audiofilters/Distortion.c b/shared-module/audiofilters/Distortion.c index 93ae22c90b59..68b4ae5a20ff 100644 --- a/shared-module/audiofilters/Distortion.c +++ b/shared-module/audiofilters/Distortion.c @@ -204,10 +204,10 @@ audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_dist } // 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 drive = synthio_block_slot_get_limited(&self->drive, 0.0, 1.0); - mp_float_t pre_gain = db_to_linear(synthio_block_slot_get_limited(&self->pre_gain, -60.0, 60.0)); - mp_float_t post_gain = db_to_linear(synthio_block_slot_get_limited(&self->post_gain, -80.0, 24.0)); - mp_float_t mix = synthio_block_slot_get_limited(&self->mix, 0.0, 1.0); + mp_float_t drive = synthio_block_slot_get_limited(&self->drive, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.0)); + mp_float_t pre_gain = db_to_linear(synthio_block_slot_get_limited(&self->pre_gain, MICROPY_FLOAT_CONST(-60.0), MICROPY_FLOAT_CONST(60.0))); + mp_float_t post_gain = db_to_linear(synthio_block_slot_get_limited(&self->post_gain, MICROPY_FLOAT_CONST(-80.0), MICROPY_FLOAT_CONST(24.0))); + mp_float_t mix = synthio_block_slot_get_limited(&self->mix, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.0)); // Switch our buffers to the other buffer self->last_buf_idx = !self->last_buf_idx; diff --git a/shared-module/audiofilters/Filter.c b/shared-module/audiofilters/Filter.c index dc3efe18ef49..f9ec92e78490 100644 --- a/shared-module/audiofilters/Filter.c +++ b/shared-module/audiofilters/Filter.c @@ -53,10 +53,6 @@ void common_hal_audiofilters_filter_construct(audiofilters_filter_obj_t *self, } common_hal_audiofilters_filter_set_filter(self, filter); - // If we did not receive a BlockInput we need to create a default float value - if (mix == MP_OBJ_NULL) { - mix = mp_obj_new_float(1.0); - } synthio_block_assign_slot(mix, &self->mix, MP_QSTR_mix); } @@ -251,7 +247,7 @@ audioio_get_buffer_result_t audiofilters_filter_get_buffer(audiofilters_filter_o } // 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 mix = synthio_block_slot_get_limited(&self->mix, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.0)); // Switch our buffers to the other buffer self->last_buf_idx = !self->last_buf_idx; @@ -305,7 +301,7 @@ audioio_get_buffer_result_t audiofilters_filter_get_buffer(audiofilters_filter_o 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 || !self->filter_states) { // if mix is zero pure sample only or no biquad filter objects are provided + if (mix <= MICROPY_FLOAT_CONST(0.01) || !self->filter_states) { // if mix is zero pure sample only or no biquad filter objects are provided for (uint32_t i = 0; i < n; i++) { if (MP_LIKELY(self->bits_per_sample == 16)) { word_buffer[i] = sample_src[i]; @@ -346,7 +342,7 @@ audioio_get_buffer_result_t audiofilters_filter_get_buffer(audiofilters_filter_o } } else { if (self->samples_signed) { - hword_buffer[i + j] = (int8_t)((sample_hsrc[i + j] * (1.0 - mix)) + (self->filter_buffer[j] * mix)); + hword_buffer[i + j] = (int8_t)((sample_hsrc[i + j] * (MICROPY_FLOAT_CONST(1.0) - mix)) + (self->filter_buffer[j] * mix)); } else { hword_buffer[i + j] = (uint8_t)(((int8_t)(((uint8_t)sample_hsrc[i + j]) ^ 0x80) * (MICROPY_FLOAT_CONST(1.0) - mix)) + (self->filter_buffer[j] * mix)) ^ 0x80; } From 0410d2260125f5b97ddbc3fbb52db1d6ef81741c Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Wed, 11 Dec 2024 12:17:01 -0600 Subject: [PATCH 11/16] Added `soft_clip` property to toggle between hard clipping (default) and soft clipping. --- shared-bindings/audiofilters/Distortion.c | 27 +++++++++++++++++++++-- shared-bindings/audiofilters/Distortion.h | 5 ++++- shared-module/audiofilters/Distortion.c | 11 ++++++++- shared-module/audiofilters/Distortion.h | 1 + 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/shared-bindings/audiofilters/Distortion.c b/shared-bindings/audiofilters/Distortion.c index 551703220c71..d3b7d6655746 100644 --- a/shared-bindings/audiofilters/Distortion.c +++ b/shared-bindings/audiofilters/Distortion.c @@ -63,6 +63,7 @@ static audiofilters_distortion_mode validate_distortion_mode(mp_obj_t obj, qstr //| pre_gain: synthio.BlockInput = 0.0, //| post_gain: synthio.BlockInput = 0.0, //| mode: DistortionMode = DistortionMode.CLIP, +//| soft_clip: bool = False, //| mix: synthio.BlockInput = 1.0, //| buffer_size: int = 512, //| sample_rate: int = 8000, @@ -81,6 +82,7 @@ static audiofilters_distortion_mode validate_distortion_mode(mp_obj_t obj, qstr //| :param synthio.BlockInput pre_gain: Increases or decreases the volume before the effect, in decibels. Value can range from -60 to 60. //| :param synthio.BlockInput post_gain: Increases or decreases the volume after the effect, in decibels. Value can range from -80 to 24. //| :param DistortionMode mode: Distortion type. +//| :param bool soft_clip: Whether or not to soft clip (True) or hard clip (False) the output. //| :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 @@ -111,12 +113,13 @@ static audiofilters_distortion_mode validate_distortion_mode(mp_obj_t obj, qstr //| ... static mp_obj_t audiofilters_distortion_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - enum { ARG_drive, ARG_pre_gain, ARG_post_gain, ARG_mode, ARG_mix, ARG_buffer_size, ARG_sample_rate, ARG_bits_per_sample, ARG_samples_signed, ARG_channel_count, }; + enum { ARG_drive, ARG_pre_gain, ARG_post_gain, ARG_mode, ARG_soft_clip, 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_drive, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(0)} }, { MP_QSTR_pre_gain, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(0)} }, { MP_QSTR_post_gain, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(0)} }, { MP_QSTR_mode, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_soft_clip, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = false} }, { MP_QSTR_mix, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(1)} }, { 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} }, @@ -141,7 +144,7 @@ static mp_obj_t audiofilters_distortion_make_new(const mp_obj_type_t *type, size } audiofilters_distortion_obj_t *self = mp_obj_malloc(audiofilters_distortion_obj_t, &audiofilters_distortion_type); - common_hal_audiofilters_distortion_construct(self, args[ARG_drive].u_obj, args[ARG_pre_gain].u_obj, args[ARG_post_gain].u_obj, mode, 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_audiofilters_distortion_construct(self, args[ARG_drive].u_obj, args[ARG_pre_gain].u_obj, args[ARG_post_gain].u_obj, mode, args[ARG_soft_clip].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); } @@ -257,6 +260,25 @@ MP_PROPERTY_GETSET(audiofilters_distortion_mode_obj, (mp_obj_t)&audiofilters_distortion_set_mode_obj); +//| soft_clip: bool +//| """Whether or not to soft clip (True) or hard clip (False) the output.""" +static mp_obj_t audiofilters_distortion_obj_get_soft_clip(mp_obj_t self_in) { + return mp_obj_new_bool(common_hal_audiofilters_distortion_get_soft_clip(self_in)); +} +MP_DEFINE_CONST_FUN_OBJ_1(audiofilters_distortion_get_soft_clip_obj, audiofilters_distortion_obj_get_soft_clip); + +static mp_obj_t audiofilters_distortion_obj_set_soft_clip(mp_obj_t self_in, mp_obj_t soft_clip_in) { + audiofilters_distortion_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_audiofilters_distortion_set_soft_clip(self, mp_obj_is_true(soft_clip_in)); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(audiofilters_distortion_set_soft_clip_obj, audiofilters_distortion_obj_set_soft_clip); + +MP_PROPERTY_GETSET(audiofilters_distortion_soft_clip_obj, + (mp_obj_t)&audiofilters_distortion_get_soft_clip_obj, + (mp_obj_t)&audiofilters_distortion_set_soft_clip_obj); + + //| mix: synthio.BlockInput //| """The rate the filtered signal mix between 0 and 1 where 0 is only sample and 1 is all effect.""" static mp_obj_t audiofilters_distortion_obj_get_mix(mp_obj_t self_in) { @@ -339,6 +361,7 @@ static const mp_rom_map_elem_t audiofilters_distortion_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_pre_gain), MP_ROM_PTR(&audiofilters_distortion_pre_gain_obj) }, { MP_ROM_QSTR(MP_QSTR_post_gain), MP_ROM_PTR(&audiofilters_distortion_post_gain_obj) }, { MP_ROM_QSTR(MP_QSTR_mode), MP_ROM_PTR(&audiofilters_distortion_mode_obj) }, + { MP_ROM_QSTR(MP_QSTR_soft_clip), MP_ROM_PTR(&audiofilters_distortion_soft_clip_obj) }, { MP_ROM_QSTR(MP_QSTR_mix), MP_ROM_PTR(&audiofilters_distortion_mix_obj) }, }; static MP_DEFINE_CONST_DICT(audiofilters_distortion_locals_dict, audiofilters_distortion_locals_dict_table); diff --git a/shared-bindings/audiofilters/Distortion.h b/shared-bindings/audiofilters/Distortion.h index a1fded3a4fb4..5e4fadfd28b7 100644 --- a/shared-bindings/audiofilters/Distortion.h +++ b/shared-bindings/audiofilters/Distortion.h @@ -13,7 +13,7 @@ extern const mp_obj_type_t audiofilters_distortion_mode_type; void common_hal_audiofilters_distortion_construct(audiofilters_distortion_obj_t *self, mp_obj_t drive, mp_obj_t pre_gain, mp_obj_t post_gain, - audiofilters_distortion_mode mode, mp_obj_t mix, + audiofilters_distortion_mode mode, bool soft_clip, mp_obj_t mix, uint32_t buffer_size, uint8_t bits_per_sample, bool samples_signed, uint8_t channel_count, uint32_t sample_rate); @@ -36,6 +36,9 @@ void common_hal_audiofilters_distortion_set_post_gain(audiofilters_distortion_ob audiofilters_distortion_mode common_hal_audiofilters_distortion_get_mode(audiofilters_distortion_obj_t *self); void common_hal_audiofilters_distortion_set_mode(audiofilters_distortion_obj_t *self, audiofilters_distortion_mode mode); +bool common_hal_audiofilters_distortion_get_soft_clip(audiofilters_distortion_obj_t *self); +void common_hal_audiofilters_distortion_set_soft_clip(audiofilters_distortion_obj_t *self, bool soft_clip); + mp_obj_t common_hal_audiofilters_distortion_get_mix(audiofilters_distortion_obj_t *self); void common_hal_audiofilters_distortion_set_mix(audiofilters_distortion_obj_t *self, mp_obj_t arg); diff --git a/shared-module/audiofilters/Distortion.c b/shared-module/audiofilters/Distortion.c index 68b4ae5a20ff..09dfc6aa76b6 100644 --- a/shared-module/audiofilters/Distortion.c +++ b/shared-module/audiofilters/Distortion.c @@ -19,7 +19,7 @@ void common_hal_audiofilters_distortion_construct(audiofilters_distortion_obj_t *self, mp_obj_t drive, mp_obj_t pre_gain, mp_obj_t post_gain, - audiofilters_distortion_mode mode, mp_obj_t mix, + audiofilters_distortion_mode mode, bool soft_clip, mp_obj_t mix, uint32_t buffer_size, uint8_t bits_per_sample, bool samples_signed, uint8_t channel_count, uint32_t sample_rate) { @@ -68,6 +68,7 @@ void common_hal_audiofilters_distortion_construct(audiofilters_distortion_obj_t synthio_block_assign_slot(mix, &self->mix, MP_QSTR_mix); self->mode = mode; + self->soft_clip = soft_clip; } bool common_hal_audiofilters_distortion_deinited(audiofilters_distortion_obj_t *self) { @@ -117,6 +118,14 @@ void common_hal_audiofilters_distortion_set_mode(audiofilters_distortion_obj_t * self->mode = arg; } +bool common_hal_audiofilters_distortion_get_soft_clip(audiofilters_distortion_obj_t *self) { + return self->soft_clip; +} + +void common_hal_audiofilters_distortion_set_soft_clip(audiofilters_distortion_obj_t *self, bool soft_clip) { + self->soft_clip = soft_clip; +} + mp_obj_t common_hal_audiofilters_distortion_get_mix(audiofilters_distortion_obj_t *self) { return self->mix.obj; } diff --git a/shared-module/audiofilters/Distortion.h b/shared-module/audiofilters/Distortion.h index f701fb17c4ae..86f5c71cf42a 100644 --- a/shared-module/audiofilters/Distortion.h +++ b/shared-module/audiofilters/Distortion.h @@ -26,6 +26,7 @@ typedef struct { synthio_block_slot_t pre_gain; synthio_block_slot_t post_gain; audiofilters_distortion_mode mode; + bool soft_clip; synthio_block_slot_t mix; uint8_t bits_per_sample; From 57022f9e9272c76abb81fb28343abf2a3b8280ee Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Wed, 11 Dec 2024 12:17:54 -0600 Subject: [PATCH 12/16] Implemented soft clipping and continued optimization of distortion algorithms. --- shared-module/audiofilters/Distortion.c | 94 ++++++++++++++++++------- 1 file changed, 67 insertions(+), 27 deletions(-) diff --git a/shared-module/audiofilters/Distortion.c b/shared-module/audiofilters/Distortion.c index 09dfc6aa76b6..9b90f25a27af 100644 --- a/shared-module/audiofilters/Distortion.c +++ b/shared-module/audiofilters/Distortion.c @@ -218,6 +218,16 @@ audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_dist mp_float_t post_gain = db_to_linear(synthio_block_slot_get_limited(&self->post_gain, MICROPY_FLOAT_CONST(-80.0), MICROPY_FLOAT_CONST(24.0))); mp_float_t mix = synthio_block_slot_get_limited(&self->mix, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.0)); + // LOFI mode bit mask + uint32_t word_mask = 0xFFFFFFFF ^ ((1 << (uint32_t)MICROPY_FLOAT_C_FUN(round)(drive * MICROPY_FLOAT_CONST(14.0))) - 1); + + // Modify drive value depending on mode + if (self->mode == DISTORTION_MODE_CLIP) { + drive = MICROPY_FLOAT_CONST(1.0001) - drive; + } else if (self->mode == DISTORTION_MODE_WAVESHAPE) { + drive = MICROPY_FLOAT_CONST(2.0) * drive / (MICROPY_FLOAT_CONST(1.0001) - drive); + } + // Switch our buffers to the other buffer self->last_buf_idx = !self->last_buf_idx; @@ -267,7 +277,7 @@ audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_dist 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 + if (mix <= MICROPY_FLOAT_CONST(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]; @@ -276,9 +286,6 @@ audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_dist } } } else { - - uint32_t word_mask = 0xFFFFFFFF ^ ((1 << (uint32_t)roundf(drive * 14.0)) - 1); // LOFI mode bit mask - for (uint32_t i = 0; i < n; i++) { int32_t sample_word = 0; if (MP_LIKELY(self->bits_per_sample == 16)) { @@ -292,37 +299,70 @@ audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_dist } } + // Apply pre-gain int32_t word = sample_word * pre_gain; - switch (self->mode) { - case DISTORTION_MODE_CLIP: { - mp_float_t word_sign = word < 0 ? MICROPY_FLOAT_CONST(-1.0) : MICROPY_FLOAT_CONST(1.0); - word = MICROPY_FLOAT_C_FUN(pow)(MICROPY_FLOAT_C_FUN(fabs)(word / MICROPY_FLOAT_CONST(32768.0)), MICROPY_FLOAT_CONST(1.0001) - drive) * word_sign * MICROPY_FLOAT_CONST(32767.0); - word = MIN(MAX(word, -32767), 32768); // Hard clip - } break; - case DISTORTION_MODE_LOFI: { - word = word & word_mask; - } break; - case DISTORTION_MODE_OVERDRIVE: { - mp_float_t x = word / MICROPY_FLOAT_CONST(32768.0) * MICROPY_FLOAT_CONST(0.686306); - mp_float_t z = MICROPY_FLOAT_CONST(1.0) + MICROPY_FLOAT_C_FUN(exp)(MICROPY_FLOAT_C_FUN(sqrt)(MICROPY_FLOAT_C_FUN(fabs)(x)) * MICROPY_FLOAT_CONST(-0.75)); - mp_float_t exp_x = MICROPY_FLOAT_C_FUN(exp)(x); - word = (exp_x - MICROPY_FLOAT_C_FUN(exp)(-x * z)) / (exp_x + MICROPY_FLOAT_C_FUN(exp)(-x)) * MICROPY_FLOAT_CONST(32767.0); - } break; - case DISTORTION_MODE_WAVESHAPE: { - mp_float_t x = word / MICROPY_FLOAT_CONST(32768.0); - mp_float_t k = MICROPY_FLOAT_CONST(2.0) * drive / (MICROPY_FLOAT_CONST(1.00001) - drive); - word = (MICROPY_FLOAT_CONST(1.0) + k) * x / (MICROPY_FLOAT_CONST(1.0) + k * MICROPY_FLOAT_C_FUN(fabs)(x)) * MICROPY_FLOAT_CONST(32767.0); - } break; + + // Apply bit mask before converting to float + if (self->mode == DISTORTION_MODE_LOFI) { + word = word & word_mask; + } + + if (self->mode != DISTORTION_MODE_LOFI || self->soft_clip) { + // Convert sample to float + mp_float_t wordf = word / MICROPY_FLOAT_CONST(32768.0); + + switch (self->mode) { + case DISTORTION_MODE_CLIP: { + wordf = MICROPY_FLOAT_C_FUN(pow)(MICROPY_FLOAT_C_FUN(fabs)(wordf), drive); + if (word < 0) { + wordf *= MICROPY_FLOAT_CONST(-1.0); + } + } break; + case DISTORTION_MODE_LOFI: + break; + case DISTORTION_MODE_OVERDRIVE: { + wordf *= MICROPY_FLOAT_CONST(0.686306); + mp_float_t z = MICROPY_FLOAT_CONST(1.0) + MICROPY_FLOAT_C_FUN(exp)(MICROPY_FLOAT_C_FUN(sqrt)(MICROPY_FLOAT_C_FUN(fabs)(wordf)) * MICROPY_FLOAT_CONST(-0.75)); + mp_float_t word_exp = MICROPY_FLOAT_C_FUN(exp)(wordf); + wordf *= MICROPY_FLOAT_CONST(-1.0); + wordf = (word_exp - MICROPY_FLOAT_C_FUN(exp)(wordf * z)) / (word_exp + MICROPY_FLOAT_C_FUN(exp)(wordf)); + } break; + case DISTORTION_MODE_WAVESHAPE: { + wordf = (MICROPY_FLOAT_CONST(1.0) + drive) * wordf / (MICROPY_FLOAT_CONST(1.0) + drive * MICROPY_FLOAT_C_FUN(fabs)(wordf)); + } break; + } + + // Apply post-gain + wordf = wordf * post_gain; + + // Soft clip + if (self->soft_clip) { + if (wordf > 0) { + wordf = MICROPY_FLOAT_CONST(1.0) - MICROPY_FLOAT_C_FUN(exp)(-wordf); + } else { + wordf = MICROPY_FLOAT_CONST(-1.0) + MICROPY_FLOAT_C_FUN(exp)(wordf); + } + } + + // Convert sample back to signed integer + word = wordf * MICROPY_FLOAT_CONST(32767.0); + } else { + // Apply post-gain + word = word * post_gain; + } + + // Hard clip + if (!self->soft_clip) { + word = MIN(MAX(word, -32767), 32768); } - word = word * post_gain; if (MP_LIKELY(self->bits_per_sample == 16)) { - word_buffer[i] = (sample_word * (1.0 - mix)) + (word * mix); + word_buffer[i] = (sample_word * (MICROPY_FLOAT_CONST(1.0) - mix)) + (word * mix); if (!self->samples_signed) { word_buffer[i] ^= 0x8000; } } else { - int8_t mixed = (sample_word * (1.0 - mix)) + (word * mix); + int8_t mixed = (sample_word * (MICROPY_FLOAT_CONST(1.0) - mix)) + (word * mix); if (self->samples_signed) { hword_buffer[i] = mixed; } else { From 48ca21d9a1fc8cb9dc54fcabc1f94f2c272a2477 Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Wed, 11 Dec 2024 12:35:23 -0600 Subject: [PATCH 13/16] Add Distortion to unix port and make type conversions explicit. --- ports/unix/variants/coverage/mpconfigvariant.mk | 2 ++ py/circuitpy_defns.mk | 2 +- shared-module/audiofilters/Distortion.c | 14 +++++--------- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/ports/unix/variants/coverage/mpconfigvariant.mk b/ports/unix/variants/coverage/mpconfigvariant.mk index 3dd3f334081c..246156f3564a 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.mk +++ b/ports/unix/variants/coverage/mpconfigvariant.mk @@ -35,6 +35,7 @@ SRC_BITMAP := \ shared-bindings/audiocore/WaveFile.c \ shared-bindings/audiodelays/Echo.c \ shared-bindings/audiodelays/__init__.c \ + shared-bindings/audiofilters/Distortion.c \ shared-bindings/audiofilters/Filter.c \ shared-bindings/audiofilters/__init__.c \ shared-bindings/audiomixer/__init__.c \ @@ -77,6 +78,7 @@ SRC_BITMAP := \ shared-module/audiocore/WaveFile.c \ shared-module/audiodelays/Echo.c \ shared-module/audiodelays/__init__.c \ + shared-module/audiofilters/Distortion.c \ shared-module/audiofilters/Filter.c \ shared-module/audiofilters/__init__.c \ shared-module/audiomixer/__init__.c \ diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index 31fbc5c80640..f4335f4b2ae5 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -625,8 +625,8 @@ SRC_SHARED_MODULE_ALL = \ audiocore/__init__.c \ audiodelays/Echo.c \ audiodelays/__init__.c \ - audiofilters/Filter.c \ audiofilters/Distortion.c \ + audiofilters/Filter.c \ audiofilters/__init__.c \ audioio/__init__.c \ audiomixer/Mixer.c \ diff --git a/shared-module/audiofilters/Distortion.c b/shared-module/audiofilters/Distortion.c index 9b90f25a27af..16ba4959231b 100644 --- a/shared-module/audiofilters/Distortion.c +++ b/shared-module/audiofilters/Distortion.c @@ -208,10 +208,6 @@ static mp_float_t db_to_linear(mp_float_t value) { audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_distortion_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 drive = synthio_block_slot_get_limited(&self->drive, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.0)); mp_float_t pre_gain = db_to_linear(synthio_block_slot_get_limited(&self->pre_gain, MICROPY_FLOAT_CONST(-60.0), MICROPY_FLOAT_CONST(60.0))); @@ -300,7 +296,7 @@ audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_dist } // Apply pre-gain - int32_t word = sample_word * pre_gain; + int32_t word = (int32_t)(sample_word * pre_gain); // Apply bit mask before converting to float if (self->mode == DISTORTION_MODE_LOFI) { @@ -345,10 +341,10 @@ audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_dist } // Convert sample back to signed integer - word = wordf * MICROPY_FLOAT_CONST(32767.0); + word = (int32_t)(wordf * MICROPY_FLOAT_CONST(32767.0)); } else { // Apply post-gain - word = word * post_gain; + word = (int32_t)(word * post_gain); } // Hard clip @@ -357,12 +353,12 @@ audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_dist } if (MP_LIKELY(self->bits_per_sample == 16)) { - word_buffer[i] = (sample_word * (MICROPY_FLOAT_CONST(1.0) - mix)) + (word * mix); + word_buffer[i] = (int16_t)((sample_word * (MICROPY_FLOAT_CONST(1.0) - mix)) + (word * mix)); if (!self->samples_signed) { word_buffer[i] ^= 0x8000; } } else { - int8_t mixed = (sample_word * (MICROPY_FLOAT_CONST(1.0) - mix)) + (word * mix); + int8_t mixed = (int8_t)((sample_word * (MICROPY_FLOAT_CONST(1.0) - mix)) + (word * mix)); if (self->samples_signed) { hword_buffer[i] = mixed; } else { From 4257c62a8aaa40b9f8c8ead5c1e3db51922a1998 Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Thu, 12 Dec 2024 10:10:29 -0600 Subject: [PATCH 14/16] Variable number of samples within `shared_bindings_synthio_lfo_tick`. --- shared-bindings/synthio/__init__.c | 2 +- shared-module/synthio/__init__.c | 6 +++--- shared-module/synthio/__init__.h | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/shared-bindings/synthio/__init__.c b/shared-bindings/synthio/__init__.c index 207d7afafa89..71b5a8ee732a 100644 --- a/shared-bindings/synthio/__init__.c +++ b/shared-bindings/synthio/__init__.c @@ -292,7 +292,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(synthio_voct_to_hz_obj, voct_to_hz); #if CIRCUITPY_AUDIOCORE_DEBUG static mp_obj_t synthio_lfo_tick(size_t n, const mp_obj_t *args) { - shared_bindings_synthio_lfo_tick(48000); + shared_bindings_synthio_lfo_tick(48000, SYNTHIO_MAX_DUR); mp_obj_t result[n]; for (size_t i = 0; i < n; i++) { synthio_block_slot_t slot; diff --git a/shared-module/synthio/__init__.c b/shared-module/synthio/__init__.c index c0fe6b16366b..2dd8e403b064 100644 --- a/shared-module/synthio/__init__.c +++ b/shared-module/synthio/__init__.c @@ -294,7 +294,7 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t return; } - shared_bindings_synthio_lfo_tick(synth->sample_rate); + shared_bindings_synthio_lfo_tick(synth->sample_rate, SYNTHIO_MAX_DUR); synth->buffer_index = !synth->buffer_index; synth->other_channel = 1 - channel; @@ -487,9 +487,9 @@ uint32_t synthio_frequency_convert_scaled_to_dds(uint64_t frequency_scaled, int3 return (sample_rate / 2 + frequency_scaled) / sample_rate; } -void shared_bindings_synthio_lfo_tick(uint32_t sample_rate) { +void shared_bindings_synthio_lfo_tick(uint32_t sample_rate, uint16_t num_samples) { mp_float_t recip_sample_rate = MICROPY_FLOAT_CONST(1.) / sample_rate; - synthio_global_rate_scale = SYNTHIO_MAX_DUR * recip_sample_rate; + synthio_global_rate_scale = num_samples * recip_sample_rate; synthio_global_W_scale = (2 * MP_PI) * recip_sample_rate; synthio_global_tick++; } diff --git a/shared-module/synthio/__init__.h b/shared-module/synthio/__init__.h index d40e1df86854..fed78402fd8b 100644 --- a/shared-module/synthio/__init__.h +++ b/shared-module/synthio/__init__.h @@ -90,4 +90,4 @@ int synthio_sweep_in_step(synthio_lfo_state_t *state, uint16_t dur); extern mp_float_t synthio_global_rate_scale, synthio_global_W_scale; extern uint8_t synthio_global_tick; -void shared_bindings_synthio_lfo_tick(uint32_t sample_rate); +void shared_bindings_synthio_lfo_tick(uint32_t sample_rate, uint16_t num_samples); From 0e64e1cf75b4d6e212494b58c454a29d3ac065b0 Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Thu, 12 Dec 2024 11:01:30 -0600 Subject: [PATCH 15/16] Implement block ticking within audio effects. --- shared-module/audiodelays/Echo.c | 17 +++++++++-------- shared-module/audiofilters/Distortion.c | 17 +++++++++-------- shared-module/audiofilters/Filter.c | 7 ++++--- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/shared-module/audiodelays/Echo.c b/shared-module/audiodelays/Echo.c index 9ec322875798..1ba323a6e95c 100644 --- a/shared-module/audiodelays/Echo.c +++ b/shared-module/audiodelays/Echo.c @@ -284,7 +284,16 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * channel = 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, 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); + // get the effect values we need from the BlockInput. These may change at run time so you need to do bounds checking if required + shared_bindings_synthio_lfo_tick(self->sample_rate, length / self->channel_count); mp_float_t mix = synthio_block_slot_get_limited(&self->mix, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.0)); mp_float_t decay = synthio_block_slot_get_limited(&self->decay, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.0)); @@ -293,14 +302,6 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * recalculate_delay(self, delay_ms); } - // Switch our buffers to the other buffer - self->last_buf_idx = !self->last_buf_idx; - - // If we are using 16 bit samples we need a 16 bit pointer, 8 bit needs an 8 bit pointer - int16_t *word_buffer = (int16_t *)self->buffer[self->last_buf_idx]; - int8_t *hword_buffer = self->buffer[self->last_buf_idx]; - uint32_t length = self->buffer_len / (self->bits_per_sample / 8); - // The 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 / sizeof(uint16_t); diff --git a/shared-module/audiofilters/Distortion.c b/shared-module/audiofilters/Distortion.c index 16ba4959231b..029130c7ea8f 100644 --- a/shared-module/audiofilters/Distortion.c +++ b/shared-module/audiofilters/Distortion.c @@ -208,7 +208,16 @@ static mp_float_t db_to_linear(mp_float_t value) { audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_distortion_obj_t *self, bool single_channel_output, uint8_t channel, uint8_t **buffer, uint32_t *buffer_length) { + // Switch our buffers to the other buffer + self->last_buf_idx = !self->last_buf_idx; + + // If we are using 16 bit samples we need a 16 bit pointer, 8 bit needs an 8 bit pointer + int16_t *word_buffer = (int16_t *)self->buffer[self->last_buf_idx]; + int8_t *hword_buffer = self->buffer[self->last_buf_idx]; + uint32_t length = self->buffer_len / (self->bits_per_sample / 8); + // get the effect values we need from the BlockInput. These may change at run time so you need to do bounds checking if required + shared_bindings_synthio_lfo_tick(self->sample_rate, length / self->channel_count); mp_float_t drive = synthio_block_slot_get_limited(&self->drive, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.0)); mp_float_t pre_gain = db_to_linear(synthio_block_slot_get_limited(&self->pre_gain, MICROPY_FLOAT_CONST(-60.0), MICROPY_FLOAT_CONST(60.0))); mp_float_t post_gain = db_to_linear(synthio_block_slot_get_limited(&self->post_gain, MICROPY_FLOAT_CONST(-80.0), MICROPY_FLOAT_CONST(24.0))); @@ -224,14 +233,6 @@ audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_dist drive = MICROPY_FLOAT_CONST(2.0) * drive / (MICROPY_FLOAT_CONST(1.0001) - drive); } - // Switch our buffers to the other buffer - self->last_buf_idx = !self->last_buf_idx; - - // If we are using 16 bit samples we need a 16 bit pointer, 8 bit needs an 8 bit pointer - int16_t *word_buffer = (int16_t *)self->buffer[self->last_buf_idx]; - int8_t *hword_buffer = self->buffer[self->last_buf_idx]; - uint32_t length = self->buffer_len / (self->bits_per_sample / 8); - // 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 diff --git a/shared-module/audiofilters/Filter.c b/shared-module/audiofilters/Filter.c index f9ec92e78490..33659fdbea47 100644 --- a/shared-module/audiofilters/Filter.c +++ b/shared-module/audiofilters/Filter.c @@ -246,9 +246,6 @@ audioio_get_buffer_result_t audiofilters_filter_get_buffer(audiofilters_filter_o 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 = synthio_block_slot_get_limited(&self->mix, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.0)); - // Switch our buffers to the other buffer self->last_buf_idx = !self->last_buf_idx; @@ -257,6 +254,10 @@ audioio_get_buffer_result_t audiofilters_filter_get_buffer(audiofilters_filter_o int8_t *hword_buffer = self->buffer[self->last_buf_idx]; uint32_t length = self->buffer_len / (self->bits_per_sample / 8); + // get the effect values we need from the BlockInput. These may change at run time so you need to do bounds checking if required + shared_bindings_synthio_lfo_tick(self->sample_rate, length / self->channel_count); + mp_float_t mix = synthio_block_slot_get_limited(&self->mix, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.0)); + // 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 From 5fbbeed716e70d496f02f60b44caf962d0b34750 Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Thu, 12 Dec 2024 11:45:54 -0600 Subject: [PATCH 16/16] Call `shared_bindings_synthio_lfo_tick` on audioeffects in `SYNTHIO_MAX_DUR` intervals. --- shared-module/audiodelays/Echo.c | 64 ++++++++++++++----------- shared-module/audiofilters/Distortion.c | 43 ++++++++++------- shared-module/audiofilters/Filter.c | 14 ++++-- 3 files changed, 69 insertions(+), 52 deletions(-) diff --git a/shared-module/audiodelays/Echo.c b/shared-module/audiodelays/Echo.c index 1ba323a6e95c..f0bb85ebbcbe 100644 --- a/shared-module/audiodelays/Echo.c +++ b/shared-module/audiodelays/Echo.c @@ -292,28 +292,8 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * int8_t *hword_buffer = self->buffer[self->last_buf_idx]; uint32_t length = self->buffer_len / (self->bits_per_sample / 8); - // get the effect values we need from the BlockInput. These may change at run time so you need to do bounds checking if required - shared_bindings_synthio_lfo_tick(self->sample_rate, length / self->channel_count); - mp_float_t mix = synthio_block_slot_get_limited(&self->mix, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.0)); - mp_float_t decay = synthio_block_slot_get_limited(&self->decay, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.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); - } - // 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 / 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) { @@ -335,6 +315,35 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * } } + // Determine how many bytes we can process to our buffer, the less of the sample we have left and our buffer remaining + uint32_t n; + if (self->sample == NULL) { + n = MIN(length, SYNTHIO_MAX_DUR * self->channel_count); + } else { + n = MIN(MIN(self->sample_buffer_length, length), SYNTHIO_MAX_DUR * self->channel_count); + } + + // get the effect values we need from the BlockInput. These may change at run time so you need to do bounds checking if required + shared_bindings_synthio_lfo_tick(self->sample_rate, n / self->channel_count); + mp_float_t mix = synthio_block_slot_get_limited(&self->mix, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.0)); + mp_float_t decay = synthio_block_slot_get_limited(&self->decay, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.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); + } + + 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; + } + } + // If we have no sample keep the echo echoing if (self->sample == NULL) { if (mix <= MICROPY_FLOAT_CONST(0.01)) { // Mix of 0 is pure sample sound. We have no sample so no sound @@ -401,9 +410,6 @@ 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; // for 16-bit samples int8_t *sample_hsrc = (int8_t *)self->sample_remaining_buffer; // for 8-bit samples @@ -501,13 +507,13 @@ audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t * self->sample_remaining_buffer += (n * (self->bits_per_sample / 8)); self->sample_buffer_length -= n; } - } - 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; + 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; + } } } diff --git a/shared-module/audiofilters/Distortion.c b/shared-module/audiofilters/Distortion.c index 029130c7ea8f..73cf96dd7d50 100644 --- a/shared-module/audiofilters/Distortion.c +++ b/shared-module/audiofilters/Distortion.c @@ -216,23 +216,6 @@ audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_dist int8_t *hword_buffer = self->buffer[self->last_buf_idx]; uint32_t length = self->buffer_len / (self->bits_per_sample / 8); - // get the effect values we need from the BlockInput. These may change at run time so you need to do bounds checking if required - shared_bindings_synthio_lfo_tick(self->sample_rate, length / self->channel_count); - mp_float_t drive = synthio_block_slot_get_limited(&self->drive, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.0)); - mp_float_t pre_gain = db_to_linear(synthio_block_slot_get_limited(&self->pre_gain, MICROPY_FLOAT_CONST(-60.0), MICROPY_FLOAT_CONST(60.0))); - mp_float_t post_gain = db_to_linear(synthio_block_slot_get_limited(&self->post_gain, MICROPY_FLOAT_CONST(-80.0), MICROPY_FLOAT_CONST(24.0))); - mp_float_t mix = synthio_block_slot_get_limited(&self->mix, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.0)); - - // LOFI mode bit mask - uint32_t word_mask = 0xFFFFFFFF ^ ((1 << (uint32_t)MICROPY_FLOAT_C_FUN(round)(drive * MICROPY_FLOAT_CONST(14.0))) - 1); - - // Modify drive value depending on mode - if (self->mode == DISTORTION_MODE_CLIP) { - drive = MICROPY_FLOAT_CONST(1.0001) - drive; - } else if (self->mode == DISTORTION_MODE_WAVESHAPE) { - drive = MICROPY_FLOAT_CONST(2.0) * drive / (MICROPY_FLOAT_CONST(1.0001) - drive); - } - // 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 @@ -265,15 +248,39 @@ audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_dist } } + // tick all block inputs + shared_bindings_synthio_lfo_tick(self->sample_rate, length / self->channel_count); + (void)synthio_block_slot_get(&self->drive); + (void)synthio_block_slot_get(&self->pre_gain); + (void)synthio_block_slot_get(&self->post_gain); + (void)synthio_block_slot_get(&self->mix); + length = 0; } else { // we have a sample to play and apply effect // 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); + uint32_t n = MIN(MIN(self->sample_buffer_length, length), SYNTHIO_MAX_DUR * self->channel_count); 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 + // get the effect values we need from the BlockInput. These may change at run time so you need to do bounds checking if required + shared_bindings_synthio_lfo_tick(self->sample_rate, n / self->channel_count); + mp_float_t drive = synthio_block_slot_get_limited(&self->drive, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.0)); + mp_float_t pre_gain = db_to_linear(synthio_block_slot_get_limited(&self->pre_gain, MICROPY_FLOAT_CONST(-60.0), MICROPY_FLOAT_CONST(60.0))); + mp_float_t post_gain = db_to_linear(synthio_block_slot_get_limited(&self->post_gain, MICROPY_FLOAT_CONST(-80.0), MICROPY_FLOAT_CONST(24.0))); + mp_float_t mix = synthio_block_slot_get_limited(&self->mix, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.0)); + + // LOFI mode bit mask + uint32_t word_mask = 0xFFFFFFFF ^ ((1 << (uint32_t)MICROPY_FLOAT_C_FUN(round)(drive * MICROPY_FLOAT_CONST(14.0))) - 1); + + // Modify drive value depending on mode + if (self->mode == DISTORTION_MODE_CLIP) { + drive = MICROPY_FLOAT_CONST(1.0001) - drive; + } else if (self->mode == DISTORTION_MODE_WAVESHAPE) { + drive = MICROPY_FLOAT_CONST(2.0) * drive / (MICROPY_FLOAT_CONST(1.0001) - drive); + } + if (mix <= MICROPY_FLOAT_CONST(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)) { diff --git a/shared-module/audiofilters/Filter.c b/shared-module/audiofilters/Filter.c index 33659fdbea47..5f8bb5e5e64b 100644 --- a/shared-module/audiofilters/Filter.c +++ b/shared-module/audiofilters/Filter.c @@ -254,10 +254,6 @@ audioio_get_buffer_result_t audiofilters_filter_get_buffer(audiofilters_filter_o int8_t *hword_buffer = self->buffer[self->last_buf_idx]; uint32_t length = self->buffer_len / (self->bits_per_sample / 8); - // get the effect values we need from the BlockInput. These may change at run time so you need to do bounds checking if required - shared_bindings_synthio_lfo_tick(self->sample_rate, length / self->channel_count); - mp_float_t mix = synthio_block_slot_get_limited(&self->mix, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.0)); - // 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 @@ -293,15 +289,23 @@ audioio_get_buffer_result_t audiofilters_filter_get_buffer(audiofilters_filter_o } } + // tick all block inputs + shared_bindings_synthio_lfo_tick(self->sample_rate, length / self->channel_count); + (void)synthio_block_slot_get(&self->mix); + length = 0; } else { // we have a sample to play and filter // 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); + uint32_t n = MIN(MIN(self->sample_buffer_length, length), SYNTHIO_MAX_DUR * self->channel_count); 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 + // get the effect values we need from the BlockInput. These may change at run time so you need to do bounds checking if required + shared_bindings_synthio_lfo_tick(self->sample_rate, n / self->channel_count); + mp_float_t mix = synthio_block_slot_get_limited(&self->mix, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.0)); + if (mix <= MICROPY_FLOAT_CONST(0.01) || !self->filter_states) { // if mix is zero pure sample only or no biquad filter objects are provided for (uint32_t i = 0; i < n; i++) { if (MP_LIKELY(self->bits_per_sample == 16)) {