|
1 | 1 | // This file is part of the CircuitPython project: https://circuitpython.org
|
2 | 2 | //
|
3 |
| -// SPDX-FileCopyrightText: Copyright (c) 2021 Artyom Skrobov |
| 3 | +// SPDX-FileCopyrightText: Copyright (c) 2023 Jeff Epler for Adafruit Industries |
4 | 4 | //
|
5 | 5 | // SPDX-License-Identifier: MIT
|
6 | 6 |
|
7 |
| -#include <math.h> |
8 |
| -#include <string.h> |
9 |
| - |
10 | 7 | #include "py/enum.h"
|
11 |
| -#include "py/mperrno.h" |
12 |
| -#include "py/obj.h" |
13 |
| -#include "py/objnamedtuple.h" |
| 8 | +#include "py/objproperty.h" |
14 | 9 | #include "py/runtime.h"
|
| 10 | +#include "shared-bindings/synthio/Biquad.h" |
| 11 | +#include "shared-bindings/util.h" |
15 | 12 |
|
16 |
| -#include "shared-bindings/synthio/__init__.h" |
17 |
| -#include "shared-bindings/synthio/LFO.h" |
18 |
| -#include "shared-bindings/synthio/Math.h" |
19 |
| -#include "shared-bindings/synthio/MidiTrack.h" |
20 |
| -#include "shared-bindings/synthio/Note.h" |
21 |
| -#include "shared-bindings/synthio/Synthesizer.h" |
22 |
| - |
23 |
| -#include "shared-module/synthio/LFO.h" |
| 13 | +//| class FilterMode: |
| 14 | +//| """The type of filter""" |
| 15 | +//| |
| 16 | +//| LOW_PASS: FilterMode |
| 17 | +//| """A low-pass filter""" |
| 18 | +//| HIGH_PASS: FilterMode |
| 19 | +//| """A high-pass filter""" |
| 20 | +//| BAND_PASS: FilterMode |
| 21 | +//| """A band-pass filter""" |
| 22 | +//| NOTCH: FilterMode |
| 23 | +//| """A notch filter""" |
| 24 | +//| LOW_SHELF: FilterMode |
| 25 | +//| """A low shelf filter""" |
| 26 | +//| HIGH_SHELF: FilterMode |
| 27 | +//| """A high shelf filter""" |
| 28 | +//| PEAKING_EQ: FilterMode |
| 29 | +//| """A peaking equalizer filter""" |
| 30 | +//| |
| 31 | +//| |
24 | 32 |
|
25 |
| -#define default_attack_time (MICROPY_FLOAT_CONST(0.1)) |
26 |
| -#define default_decay_time (MICROPY_FLOAT_CONST(0.05)) |
27 |
| -#define default_release_time (MICROPY_FLOAT_CONST(0.2)) |
28 |
| -#define default_attack_level (MICROPY_FLOAT_CONST(1.)) |
29 |
| -#define default_sustain_level (MICROPY_FLOAT_CONST(0.8)) |
| 33 | +MAKE_ENUM_VALUE(synthio_filter_mode_type, mode, LOW_PASS, SYNTHIO_LOW_PASS); |
| 34 | +MAKE_ENUM_VALUE(synthio_filter_mode_type, mode, HIGH_PASS, SYNTHIO_HIGH_PASS); |
| 35 | +MAKE_ENUM_VALUE(synthio_filter_mode_type, mode, BAND_PASS, SYNTHIO_BAND_PASS); |
| 36 | +MAKE_ENUM_VALUE(synthio_filter_mode_type, mode, NOTCH, SYNTHIO_NOTCH); |
| 37 | +MAKE_ENUM_VALUE(synthio_filter_mode_type, mode, LOW_SHELF, SYNTHIO_LOW_SHELF); |
| 38 | +MAKE_ENUM_VALUE(synthio_filter_mode_type, mode, HIGH_SHELF, SYNTHIO_HIGH_SHELF); |
| 39 | +MAKE_ENUM_VALUE(synthio_filter_mode_type, mode, PEAKING_EQ, SYNTHIO_PEAKING_EQ); |
30 | 40 |
|
31 |
| -static const mp_arg_t biquad_properties[] = { |
32 |
| - { MP_QSTR_a1, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_ROM_NONE} }, |
33 |
| - { MP_QSTR_a2, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_ROM_NONE} }, |
34 |
| - { MP_QSTR_b0, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_ROM_NONE} }, |
35 |
| - { MP_QSTR_b1, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_ROM_NONE} }, |
36 |
| - { MP_QSTR_b2, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_ROM_NONE} }, |
| 41 | +MAKE_ENUM_MAP(synthio_filter_mode) { |
| 42 | + MAKE_ENUM_MAP_ENTRY(mode, LOW_PASS), |
| 43 | + MAKE_ENUM_MAP_ENTRY(mode, HIGH_PASS), |
| 44 | + MAKE_ENUM_MAP_ENTRY(mode, BAND_PASS), |
| 45 | + MAKE_ENUM_MAP_ENTRY(mode, NOTCH), |
| 46 | + MAKE_ENUM_MAP_ENTRY(mode, LOW_SHELF), |
| 47 | + MAKE_ENUM_MAP_ENTRY(mode, HIGH_SHELF), |
| 48 | + MAKE_ENUM_MAP_ENTRY(mode, PEAKING_EQ), |
37 | 49 | };
|
38 | 50 |
|
| 51 | +static MP_DEFINE_CONST_DICT(synthio_filter_mode_locals_dict, synthio_filter_mode_locals_table); |
| 52 | + |
| 53 | +MAKE_PRINTER(synthio, synthio_filter_mode); |
| 54 | + |
| 55 | +MAKE_ENUM_TYPE(synthio, FilterMode, synthio_filter_mode); |
| 56 | + |
| 57 | +static synthio_filter_mode validate_synthio_filter_mode(mp_obj_t obj, qstr arg_name) { |
| 58 | + return cp_enum_value(&synthio_filter_mode_type, obj, arg_name); |
| 59 | +} |
| 60 | + |
39 | 61 | //| class Biquad:
|
40 |
| -//| def __init__(self, b0: float, b1: float, b2: float, a1: float, a2: float) -> None: |
41 |
| -//| """Construct a normalized biquad filter object. |
| 62 | +//| def __init__( |
| 63 | +//| self, |
| 64 | +//| mode: FilterMode, |
| 65 | +//| frequency: BlockInput, |
| 66 | +//| Q: BlockInput = 0.7071067811865475, |
| 67 | +//| A: BlockInput = None, |
| 68 | +//| ) -> None: |
| 69 | +//| """Construct a biquad filter object with given settings. |
42 | 70 | //|
|
43 |
| -//| This implements the "direct form 1" biquad filter, where each coefficient |
44 |
| -//| has been pre-divided by a0. |
| 71 | +//| ``frequency`` gives the center frequency or corner frequency of the filter, |
| 72 | +//| depending on the mode. |
45 | 73 | //|
|
46 |
| -//| Biquad objects are usually constructed via one of the related methods on a `Synthesizer` object |
47 |
| -//| rather than directly from coefficients. |
| 74 | +//| ``Q`` gives the gain or sharpness of the filter. |
48 | 75 | //|
|
49 |
| -//| https://github.com/WebAudio/Audio-EQ-Cookbook/blob/main/Audio-EQ-Cookbook.txt |
| 76 | +//| ``A`` controls the gain of peaking and shelving filters according to the |
| 77 | +//| formula ``A = 10^(dBgain/40)``. For other filter types it is ignored. |
50 | 78 | //|
|
51 |
| -//| .. note:: This is deprecated in ``9.x.x`` and will be removed in ``10.0.0``. Use `BlockBiquad` objects instead. |
52 |
| -//| """ |
| 79 | +//| Since ``frequency`` and ``Q`` are `BlockInput` objects, they can |
| 80 | +//| be varied dynamically. Internally, this is evaluated as "direct form 1" |
| 81 | +//| biquad filter. |
53 | 82 | //|
|
| 83 | +//| The internal filter state x[] and y[] is not updated when the filter |
| 84 | +//| coefficients change, and there is no theoretical justification for why |
| 85 | +//| this should result in a stable filter output. However, in practice, |
| 86 | +//| slowly varying the filter's characteristic frequency and sharpness |
| 87 | +//| appears to work as you'd expect.""" |
54 | 88 | //|
|
| 89 | + |
| 90 | +static const mp_arg_t biquad_properties[] = { |
| 91 | + { MP_QSTR_mode, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_OBJ_NULL } }, |
| 92 | + { MP_QSTR_frequency, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_OBJ_NULL } }, |
| 93 | + { MP_QSTR_Q, MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL } }, |
| 94 | + { MP_QSTR_A, MP_ARG_OBJ, {.u_obj = MP_ROM_NONE } }, |
| 95 | +}; |
| 96 | + |
55 | 97 | static mp_obj_t synthio_biquad_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
|
| 98 | + enum { ARG_mode, ARG_frequency, ARG_Q }; |
| 99 | + |
56 | 100 | mp_arg_val_t args[MP_ARRAY_SIZE(biquad_properties)];
|
57 | 101 | mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(biquad_properties), biquad_properties, args);
|
58 | 102 |
|
59 |
| - for (size_t i = 0; i < MP_ARRAY_SIZE(biquad_properties); i++) { |
60 |
| - args[i].u_obj = mp_obj_new_float(mp_arg_validate_type_float(args[i].u_obj, biquad_properties[i].qst)); |
| 103 | + if (args[ARG_Q].u_obj == MP_OBJ_NULL) { |
| 104 | + args[ARG_Q].u_obj = mp_obj_new_float(MICROPY_FLOAT_CONST(0.7071067811865475)); |
61 | 105 | }
|
62 | 106 |
|
63 |
| - MP_STATIC_ASSERT(sizeof(mp_arg_val_t) == sizeof(mp_obj_t)); |
64 |
| - return namedtuple_make_new(type_in, MP_ARRAY_SIZE(args), 0, &args[0].u_obj); |
| 107 | + synthio_filter_mode mode = validate_synthio_filter_mode(args[ARG_mode].u_obj, MP_QSTR_mode); |
| 108 | + mp_obj_t result = common_hal_synthio_biquad_new(mode); |
| 109 | + properties_construct_helper(result, biquad_properties + 1, args + 1, MP_ARRAY_SIZE(biquad_properties) - 1); |
| 110 | + return result; |
65 | 111 | }
|
66 | 112 |
|
67 |
| -const mp_obj_namedtuple_type_t synthio_biquad_type_obj = { |
68 |
| - NAMEDTUPLE_TYPE_BASE_AND_SLOTS_MAKE_NEW(MP_QSTR_Biquad, synthio_biquad_make_new), |
69 |
| - .n_fields = 5, |
70 |
| - .fields = { |
71 |
| - MP_QSTR_a1, |
72 |
| - MP_QSTR_a2, |
73 |
| - MP_QSTR_b0, |
74 |
| - MP_QSTR_b1, |
75 |
| - MP_QSTR_b2, |
76 |
| - }, |
| 113 | +//| |
| 114 | +//| mode: FilterMode |
| 115 | +//| """The mode of filter (read-only)""" |
| 116 | +static mp_obj_t synthio_biquad_get_mode(mp_obj_t self_in) { |
| 117 | + synthio_biquad_t *self = MP_OBJ_TO_PTR(self_in); |
| 118 | + return cp_enum_find(&synthio_filter_mode_type, common_hal_synthio_biquad_get_mode(self)); |
| 119 | +} |
| 120 | +MP_DEFINE_CONST_FUN_OBJ_1(synthio_biquad_get_mode_obj, synthio_biquad_get_mode); |
| 121 | + |
| 122 | +MP_PROPERTY_GETTER(synthio_biquad_mode_obj, |
| 123 | + (mp_obj_t)&synthio_biquad_get_mode_obj); |
| 124 | + |
| 125 | +//| |
| 126 | +//| frequency: BlockInput |
| 127 | +//| """The central frequency (in Hz) of the filter""" |
| 128 | +static mp_obj_t synthio_biquad_get_frequency(mp_obj_t self_in) { |
| 129 | + synthio_biquad_t *self = MP_OBJ_TO_PTR(self_in); |
| 130 | + return common_hal_synthio_biquad_get_frequency(self); |
| 131 | +} |
| 132 | +MP_DEFINE_CONST_FUN_OBJ_1(synthio_biquad_get_frequency_obj, synthio_biquad_get_frequency); |
| 133 | + |
| 134 | +static mp_obj_t synthio_biquad_set_frequency(mp_obj_t self_in, mp_obj_t arg) { |
| 135 | + synthio_biquad_t *self = MP_OBJ_TO_PTR(self_in); |
| 136 | + common_hal_synthio_biquad_set_frequency(self, arg); |
| 137 | + return mp_const_none; |
| 138 | +} |
| 139 | +MP_DEFINE_CONST_FUN_OBJ_2(synthio_biquad_set_frequency_obj, synthio_biquad_set_frequency); |
| 140 | +MP_PROPERTY_GETSET(synthio_biquad_frequency_obj, |
| 141 | + (mp_obj_t)&synthio_biquad_get_frequency_obj, |
| 142 | + (mp_obj_t)&synthio_biquad_set_frequency_obj); |
| 143 | + |
| 144 | + |
| 145 | +//| |
| 146 | +//| Q: BlockInput |
| 147 | +//| """The sharpness (Q) of the filter""" |
| 148 | +//| |
| 149 | +static mp_obj_t synthio_biquad_get_Q(mp_obj_t self_in) { |
| 150 | + synthio_biquad_t *self = MP_OBJ_TO_PTR(self_in); |
| 151 | + return common_hal_synthio_biquad_get_Q(self); |
| 152 | +} |
| 153 | +MP_DEFINE_CONST_FUN_OBJ_1(synthio_biquad_get_Q_obj, synthio_biquad_get_Q); |
| 154 | + |
| 155 | +static mp_obj_t synthio_biquad_set_Q(mp_obj_t self_in, mp_obj_t arg) { |
| 156 | + synthio_biquad_t *self = MP_OBJ_TO_PTR(self_in); |
| 157 | + common_hal_synthio_biquad_set_Q(self, arg); |
| 158 | + return mp_const_none; |
| 159 | +} |
| 160 | +MP_DEFINE_CONST_FUN_OBJ_2(synthio_biquad_set_Q_obj, synthio_biquad_set_Q); |
| 161 | +MP_PROPERTY_GETSET(synthio_biquad_Q_obj, |
| 162 | + (mp_obj_t)&synthio_biquad_get_Q_obj, |
| 163 | + (mp_obj_t)&synthio_biquad_set_Q_obj); |
| 164 | + |
| 165 | +//| |
| 166 | +//| A: BlockInput |
| 167 | +//| """The gain (A) of the filter |
| 168 | +//| |
| 169 | +//| This setting only has an effect for peaking and shelving EQ filters. It is related |
| 170 | +//| to the filter gain according to the formula ``A = 10^(dBgain/40)``. |
| 171 | +//| """ |
| 172 | +//| |
| 173 | +//| |
| 174 | +static mp_obj_t synthio_biquad_get_A(mp_obj_t self_in) { |
| 175 | + synthio_biquad_t *self = MP_OBJ_TO_PTR(self_in); |
| 176 | + return common_hal_synthio_biquad_get_A(self); |
| 177 | +} |
| 178 | +MP_DEFINE_CONST_FUN_OBJ_1(synthio_biquad_get_A_obj, synthio_biquad_get_A); |
| 179 | + |
| 180 | +static mp_obj_t synthio_biquad_set_A(mp_obj_t self_in, mp_obj_t arg) { |
| 181 | + synthio_biquad_t *self = MP_OBJ_TO_PTR(self_in); |
| 182 | + common_hal_synthio_biquad_set_A(self, arg); |
| 183 | + return mp_const_none; |
| 184 | +} |
| 185 | +MP_DEFINE_CONST_FUN_OBJ_2(synthio_biquad_set_A_obj, synthio_biquad_set_A); |
| 186 | +MP_PROPERTY_GETSET(synthio_biquad_A_obj, |
| 187 | + (mp_obj_t)&synthio_biquad_get_A_obj, |
| 188 | + (mp_obj_t)&synthio_biquad_set_A_obj); |
| 189 | + |
| 190 | +static const mp_rom_map_elem_t synthio_biquad_locals_dict_table[] = { |
| 191 | + { MP_ROM_QSTR(MP_QSTR_mode), MP_ROM_PTR(&synthio_biquad_mode_obj) }, |
| 192 | + { MP_ROM_QSTR(MP_QSTR_frequency), MP_ROM_PTR(&synthio_biquad_frequency_obj) }, |
| 193 | + { MP_ROM_QSTR(MP_QSTR_Q), MP_ROM_PTR(&synthio_biquad_Q_obj) }, |
| 194 | + { MP_ROM_QSTR(MP_QSTR_A), MP_ROM_PTR(&synthio_biquad_A_obj) }, |
77 | 195 | };
|
| 196 | +static MP_DEFINE_CONST_DICT(synthio_biquad_locals_dict, synthio_biquad_locals_dict_table); |
| 197 | + |
| 198 | +static void biquad_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { |
| 199 | + (void)kind; |
| 200 | + properties_print_helper(print, self_in, biquad_properties, MP_ARRAY_SIZE(biquad_properties)); |
| 201 | +} |
| 202 | + |
| 203 | +MP_DEFINE_CONST_OBJ_TYPE( |
| 204 | + synthio_biquad_type_obj, |
| 205 | + MP_QSTR_Biquad, |
| 206 | + MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS, |
| 207 | + make_new, synthio_biquad_make_new, |
| 208 | + locals_dict, &synthio_biquad_locals_dict, |
| 209 | + print, biquad_print |
| 210 | + ); |
0 commit comments