Unified · Split
| @@ -23,29 +23,267 @@ Tangles to files =swell.c= and =swell.h=. · 22 unmodified lines | |||
| 23 | 23 | ||
| 24 | Defining =SK_SWELL_PRIV= exposes the contents =sk_swell= | 24 | Defining =SK_SWELL_PRIV= exposes the contents =sk_swell= |
| 25 | struct. Otherwise, it is opaque. | 25 | struct. Otherwise, it is opaque. |
| 26 | |||
| 27 | #+NAME: swell.h | ||
| 28 | #+BEGIN_SRC c :tangle swell.h | ||
| 29 | #ifndef SK_SWELL_H | ||
| 30 | #define SK_SWELL_H | ||
| 31 | |||
| 32 | #ifndef SKFLT | ||
| 33 | #define SKFLT float | ||
| 34 | #endif | ||
| 35 | |||
| 36 | <<typedefs>> | ||
| 37 | |||
| 38 | #ifdef SK_SWELL_PRIV | ||
| 39 | <<structs>> | ||
| 40 | #endif | ||
| 41 | |||
| 42 | <<funcdefs>> | ||
| 43 | #endif | ||
| 44 | #+END_SRC | ||
| 45 | |||
| 46 | #+NAME: swell.c | ||
| 47 | #+BEGIN_SRC c :tangle swell.c | ||
| 48 | #include <math.h> | ||
| 49 | #define SK_SWELL_PRIV | ||
| 50 | #include "swell.h" | ||
| 51 | <<funcs>> | ||
| 52 | #+END_SRC | ||
| 26 | * Initialization | 53 | * Initialization |
| 27 | The struct for swell is called =sk_swell=. It is | 54 | The struct for swell is called =sk_swell=. It is |
| 28 | initialized with =sk_swell_init=. The sampling rate is | 55 | initialized with =sk_swell_init=. The sampling rate is |
| 29 | required. | 56 | required. |
| 57 | |||
| 58 | #+NAME: typedefs | ||
| 59 | #+BEGIN_SRC c | ||
| 60 | typedef struct sk_swell sk_swell; | ||
| 61 | #+END_SRC | ||
| 62 | |||
| 63 | #+NAME: structs | ||
| 64 | #+BEGIN_SRC c | ||
| 65 | struct sk_swell { | ||
| 66 | <<sk_swell>> | ||
| 67 | }; | ||
| 68 | #+END_SRC | ||
| 69 | |||
| 70 | #+NAME: funcdefs | ||
| 71 | #+BEGIN_SRC c | ||
| 72 | void sk_swell_init(sk_swell *sw, int sr); | ||
| 73 | #+END_SRC | ||
| 74 | |||
| 75 | #+NAME: funcs | ||
| 76 | #+BEGIN_SRC c | ||
| 77 | void sk_swell_init(sk_swell *sw, int sr) | ||
| 78 | { | ||
| 79 | <<init>> | ||
| 80 | } | ||
| 81 | #+END_SRC | ||
| 30 | * Filter Components | 82 | * Filter Components |
| 31 | There are two recursive one-pole IIR filters known as the | 83 | There are two recursive one-pole IIR filters known as the |
| 32 | smoothing filter and the switching filter. Both require 1 | 84 | smoothing filter and the switching filter. Both require 1 |
| 33 | sample of filter memory, as well as a coefficient used to | 85 | sample of filter memory (which will be called =y=), as well |
| 34 | compute the filter. | 86 | as the coefficients used to |
| 87 | compute the filter, which in a DSP context are known | ||
| 88 | as =a1= and =b0=. | ||
| 89 | |||
| 90 | The strength of the filters are each controlled by a | ||
| 91 | variable in units of seconds, known as the half-time. This | ||
| 92 | is time it takes for the function to slur itself over to | ||
| 93 | the new value. This will be referred to as =amt=. This | ||
| 94 | variable will be =cached= so that filter coefficients | ||
| 95 | don't need to be updated every sample, called =pamt=. | ||
| 96 | |||
| 97 | Here are the smoothing filter variables, prefixed with | ||
| 98 | =smooth_=. | ||
| 99 | |||
| 100 | #+NAME: sk_swell | ||
| 101 | #+BEGIN_SRC c | ||
| 102 | SKFLT smooth_y; | ||
| 103 | SKFLT smooth_a1; | ||
| 104 | SKFLT smooth_b0; | ||
| 105 | SKFLT smooth_pamt; | ||
| 106 | #+END_SRC | ||
| 107 | |||
| 108 | #+NAME: init | ||
| 109 | #+BEGIN_SRC c | ||
| 110 | sw->smooth_y = 0; | ||
| 111 | sw->smooth_b0 = 0; | ||
| 112 | sw->smooth_a1 = 0; | ||
| 113 | sw->smooth_pamt = -100; | ||
| 114 | #+END_SRC | ||
| 115 | |||
| 116 | Here are the inertia filter parameters, prefixed with = | ||
| 117 | |||
| 118 | #+NAME: sk_swell | ||
| 119 | #+BEGIN_SRC c | ||
| 120 | SKFLT inertia_y; | ||
| 121 | SKFLT inertia_a1; | ||
| 122 | SKFLT inertia_b0; | ||
| 123 | SKFLT inertia_pamt; | ||
| 124 | #+END_SRC | ||
| 125 | |||
| 126 | #+NAME: init | ||
| 127 | #+BEGIN_SRC c | ||
| 128 | sw->inertia_y = 0; | ||
| 129 | sw->inertia_b0 = 0; | ||
| 130 | sw->inertia_a1 = 0; | ||
| 131 | sw->inertia_pamt = -100; | ||
| 132 | #+END_SRC | ||
| 133 | |||
| 134 | one divided by samplerate constant. Used to update filter | ||
| 135 | coefficients. | ||
| 136 | |||
| 137 | #+NAME: sk_swell | ||
| 138 | #+BEGIN_SRC c | ||
| 139 | SKFLT onedsr; | ||
| 140 | #+END_SRC | ||
| 141 | |||
| 142 | #+NAME: init | ||
| 143 | #+BEGIN_SRC c | ||
| 144 | sw->onedsr = 1.0 / (SKFLT) sr; | ||
| 145 | #+END_SRC | ||
| 146 | * Unit Delay For Direction Detection | ||
| 147 | In order to the direction of a sample, the previous | ||
| 148 | sample is stored in what could be considered a | ||
| 149 | 1-sample delay, or =unit delay=. | ||
| 150 | |||
| 151 | A more robust way to determine direction would be to | ||
| 152 | examine more than just the previous sample. But this | ||
| 153 | approach is faster, simpler, and good enough for the | ||
| 154 | kinds of slow moving control signals that will be | ||
| 155 | expected. Also, since there's also inertia filtering on | ||
| 156 | top of this, having a flickering direction isn't too | ||
| 157 | big of a deal. | ||
| 158 | |||
| 159 | The variable, called =prev=, will be set to be an unlikely | ||
| 160 | starting value of =-123.456=. | ||
| 161 | |||
| 162 | #+NAME: sk_swell | ||
| 163 | #+BEGIN_SRC c | ||
| 164 | SKFLT prev; | ||
| 165 | #+END_SRC | ||
| 166 | |||
| 167 | #+BEGIN_SRC c | ||
| 168 | sw->prev = -123.456; | ||
| 169 | #+END_SRC | ||
| 35 | * Parameters | 170 | * Parameters |
| 36 | Swell takes in three parameters: smooth factor A, smooth | 171 | Swell takes in three parameters: smooth factor A, smooth |
| 37 | factor B, and inertia. | 172 | factor B, and inertia. |
| 38 | 173 | ||
| 39 | Set smoothing factor A with =sk_swell_smootha=. | 174 | Set smoothing factor A with =sk_swell_smootha=. |
| 40 | 175 | ||
| 176 | #+NAME: funcdefs | ||
| 177 | #+BEGIN_SRC c | ||
| 178 | void sk_swell_smootha(sk_swell *sw, SKFLT smooth); | ||
| 179 | #+END_SRC | ||
| 180 | |||
| 181 | #+NAME: funcs | ||
| 182 | #+BEGIN_SRC c | ||
| 183 | void sk_swell_smootha(sk_swell *sw, SKFLT smooth) | ||
| 184 | { | ||
| 185 | sw->sm_a = smooth; | ||
| 186 | } | ||
| 187 | #+END_SRC | ||
| 188 | |||
| 189 | #+NAME: sk_swell | ||
| 190 | #+BEGIN_SRC c | ||
| 191 | SKFLT sm_a; | ||
| 192 | #+END_SRC | ||
| 193 | |||
| 194 | #+NAME: init | ||
| 195 | #+BEGIN_SRC c | ||
| 196 | sk_swell_smootha(sw, 10); | ||
| 197 | #+END_SRC | ||
| 198 | |||
| 41 | Set smoothing factor B with =sk_swell_smoothb=. | 199 | Set smoothing factor B with =sk_swell_smoothb=. |
| 42 | 200 | ||
| 43 | Set inertia with =sk_swell_inertia=. This parameter uses | 201 | #+NAME: funcdefs |
| 44 | @!(ref "parameter_caching" "parameter caching")!@. | 202 | #+BEGIN_SRC c |
| 203 | void sk_swell_smoothb(sk_swell *sw, SKFLT smooth); | ||
| 204 | #+END_SRC | ||
| 205 | |||
| 206 | #+NAME: funcs | ||
| 207 | #+BEGIN_SRC c | ||
| 208 | void sk_swell_smoothb(sk_swell *sw, SKFLT smooth) | ||
| 209 | { | ||
| 210 | sw->sm_b = smooth; | ||
| 211 | } | ||
| 212 | #+END_SRC | ||
| 213 | |||
| 214 | #+NAME: sk_swell | ||
| 215 | #+BEGIN_SRC c | ||
| 216 | SKFLT sm_b; | ||
| 217 | #+END_SRC | ||
| 218 | |||
| 219 | #+NAME: init | ||
| 220 | #+BEGIN_SRC c | ||
| 221 | sk_swell_smoothb(sw, 0.1); | ||
| 222 | #+END_SRC | ||
| 223 | |||
| 224 | Set inertia with =sk_swell_inertia=. | ||
| 225 | |||
| 226 | #+NAME: funcdefs | ||
| 227 | #+BEGIN_SRC c | ||
| 228 | void sk_swell_inertia(sk_swell *sw, SKFLT inertia); | ||
| 229 | #+END_SRC | ||
| 230 | |||
| 231 | #+NAME: funcs | ||
| 232 | #+BEGIN_SRC c | ||
| 233 | void sk_swell_inertia(sk_swell *sw, SKFLT inertia) | ||
| 234 | { | ||
| 235 | sw->inertia_amt = inertia; | ||
| 236 | } | ||
| 237 | #+END_SRC | ||
| 238 | |||
| 239 | #+NAME: sk_swell | ||
| 240 | #+BEGIN_SRC c | ||
| 241 | SKFLT inertia_amt; | ||
| 242 | #+END_SRC | ||
| 243 | |||
| 244 | #+NAME: init | ||
| 245 | #+BEGIN_SRC c | ||
| 246 | sk_swell_inertia(sw, 0.001); | ||
| 247 | #+END_SRC | ||
| 248 | * Direction Storage Pointer | ||
| 249 | At any point in time, the smoothing amount can either be | ||
| 250 | parameter A or B. A pointer =dir= is used to store the | ||
| 251 | address of one of these values. | ||
| 252 | |||
| 253 | #+NAME: sk_swell | ||
| 254 | #+BEGIN_SRC c | ||
| 255 | SKFLT *dir; | ||
| 256 | #+END_SRC | ||
| 257 | |||
| 258 | #+NAME: init | ||
| 259 | #+BEGIN_SRC c | ||
| 260 | sw->dir = &sw->sm_a; | ||
| 261 | #+END_SRC | ||
| 45 | * Computation | 262 | * Computation |
| 46 | The function =sk_swell_tick= computes one sample of audio | 263 | The function =sk_swell_tick= computes one sample of audio |
| 47 | from an input signal =in=. | 264 | from an input signal =in=. |
| 48 | 265 | ||
| 266 | #+NAME: funcdefs | ||
| 267 | #+BEGIN_SRC c | ||
| 268 | SKFLT sk_swell_tick(sk_swell *sw, SKFLT in); | ||
| 269 | #+END_SRC | ||
| 270 | |||
| 271 | #+NAME: funcs | ||
| 272 | #+BEGIN_SRC c | ||
| 273 | SKFLT sk_swell_tick(sk_swell *sw, SKFLT in) | ||
| 274 | { | ||
| 275 | SKFLT out; | ||
| 276 | SKFLT switching; | ||
| 277 | SKFLT dir; | ||
| 278 | const SKFLT eps = 0.0001; | ||
| 279 | out = 0; | ||
| 280 | <<determine_direction>> | ||
| 281 | <<filter_switch_signal>> | ||
| 282 | <<snap_signal>> | ||
| 283 | return out; | ||
| 284 | } | ||
| 285 | #+END_SRC | ||
| 286 | |||
| 49 | The direction is determined by comparing the current input | 287 | The direction is determined by comparing the current input |
| 50 | signal with the previous input signal. A positive change | 288 | signal with the previous input signal. A positive change |
| 51 | will select the A smoothing factor. A negative change will | 289 | will select the A smoothing factor. A negative change will |
| @@ -53,11 +291,37 @@ select the B smoothing factor. No change will hold onto the · 1 unmodified line | |||
| 53 | last selected factor. This will be referred to as the | 291 | last selected factor. This will be referred to as the |
| 54 | =switching= signal. | 292 | =switching= signal. |
| 55 | 293 | ||
| 294 | #+NAME: determine_direction | ||
| 295 | #+BEGIN_SRC c | ||
| 296 | if (in > sw->prev) { | ||
| 297 | sw->dir = &sw->sm_a; | ||
| 298 | } else if (in < sw->prev) { | ||
| 299 | sw->dir = &sw->sm_b; | ||
| 300 | } | ||
| 301 | sw->prev = in; | ||
| 302 | dir = *sw->dir; | ||
| 303 | switching = dir; | ||
| 304 | #+END_SRC | ||
| 305 | |||
| 56 | The switching signal is put through a 1-pole filter known | 306 | The switching signal is put through a 1-pole filter known |
| 57 | as the inertia filter. Before computing this filter, the | 307 | as the inertia filter. Before computing this filter, the |
| 58 | inertia parameter is checked to see if it needs to be | 308 | inertia parameter is checked to see if it needs to be |
| 59 | updated via @!(ref "parameter_caching" "parameter | 309 | updated via @!(ref "parameter_caching" |
| 60 | caching")!@. | 310 | "parameter caching")!@. |
| 311 | |||
| 312 | #+NAME: filter_switch_signal | ||
| 313 | #+BEGIN_SRC c | ||
| 314 | if (sw->inertia_amt != sw->inertia_pamt) { | ||
| 315 | sw->inertia_pamt = sw->inertia_amt; | ||
| 316 | sw->inertia_a1 = pow(0.5, sw->onedsr/sw->inertia_amt); | ||
| 317 | sw->inertia_b0 = 1.0 - sw->inertia_a1; | ||
| 318 | } | ||
| 319 | |||
| 320 | switching = | ||
| 321 | sw->inertia_b0*switching + | ||
| 322 | sw->inertia_a1*sw->inertia_y; | ||
| 323 | sw->inertia_y = switching; | ||
| 324 | #+END_SRC | ||
| 61 | 325 | ||
| 62 | The filtered switching signal is then snapped to the A/B | 326 | The filtered switching signal is then snapped to the A/B |
| 63 | smoothing parameter if it is within an epsilon boundary. | 327 | smoothing parameter if it is within an epsilon boundary. |
| @@ -66,7 +330,26 @@ constantly re-calculating filter coefficients. Calls to · 2 unmodified lines | |||
| 66 | math functions like =exp= can be expensive, and should be | 330 | math functions like =exp= can be expensive, and should be |
| 67 | avoided if possible. | 331 | avoided if possible. |
| 68 | 332 | ||
| 333 | #+NAME: snap_signal | ||
| 334 | #+BEGIN_SRC c | ||
| 335 | if (fabs(switching - dir) < eps) switching = dir; | ||
| 336 | #+END_SRC | ||
| 337 | |||
| 69 | Using the filtered-and-snapped switching signal, the main | 338 | Using the filtered-and-snapped switching signal, the main |
| 70 | smoothing filter can now be computed on the input signal. | 339 | smoothing filter can now be computed on the input signal. |
| 71 | Similar to the inertia filter, parameter caching is also | 340 | Similar to the inertia filter, parameter caching is also |
| 72 | used. | 341 | used. |
| 342 | |||
| 343 | #+NAME: compute_smoothing_filter | ||
| 344 | #+BEGIN_SRC c | ||
| 345 | if (switching != sw->smooth_pamt) { | ||
| 346 | sw->smooth_pamt = switching; | ||
| 347 | sw->smooth_a1 = pow(0.5, sw->onedsr/switching); | ||
| 348 | sw->smooth_b0 = 1.0 - sw->smooth_a1; | ||
| 349 | } | ||
| 350 | |||
| 351 | out = | ||
| 352 | sw->smooth_b0*switching + | ||
| 353 | sw->smooth_a1*sw->smooth_y; | ||
| 354 | sw->smooth_y = out; | ||
| 355 | #+END_SRC | ||