Added Distortion and DistortionMode classes to audiofilters module based on Godot's AudioEffectDistortion class.
This commit is contained in:
parent
38779cd668
commit
16575fc0ef
8 changed files with 914 additions and 0 deletions
|
|
@ -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 \
|
||||
|
|
|
|||
362
shared-bindings/audiofilters/Distortion.c
Normal file
362
shared-bindings/audiofilters/Distortion.c
Normal file
|
|
@ -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 <stdint.h>
|
||||
|
||||
#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
|
||||
);
|
||||
44
shared-bindings/audiofilters/Distortion.h
Normal file
44
shared-bindings/audiofilters/Distortion.h
Normal file
|
|
@ -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);
|
||||
50
shared-bindings/audiofilters/DistortionMode.c
Normal file
50
shared-bindings/audiofilters/DistortionMode.c
Normal file
|
|
@ -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);
|
||||
}
|
||||
22
shared-bindings/audiofilters/DistortionMode.h
Normal file
22
shared-bindings/audiofilters/DistortionMode.h
Normal file
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
374
shared-module/audiofilters/Distortion.c
Normal file
374
shared-module/audiofilters/Distortion.c
Normal file
|
|
@ -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 <stdint.h>
|
||||
#include "py/runtime.h"
|
||||
#include <math.h>
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
54
shared-module/audiofilters/Distortion.h
Normal file
54
shared-module/audiofilters/Distortion.h
Normal file
|
|
@ -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);
|
||||
Loading…
Reference in a new issue