From 16575fc0efd9b235463d100f6b68f440a13b4ca5 Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Wed, 23 Oct 2024 17:19:48 -0500 Subject: [PATCH 1/5] 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 2/5] 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 3/5] 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 4/5] 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 5/5] 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);