Unified · Split
| @@ -23,29 +23,267 @@ Tangles to files =swell.c= and =swell.h=. · 22 unmodified lines | ||
| 23 | 23 | |
| 24 | 24 | Defining =SK_SWELL_PRIV= exposes the contents =sk_swell= |
| 25 | 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 | 53 | * Initialization |
| 27 | 54 | The struct for swell is called =sk_swell=. It is |
| 28 | 55 | initialized with =sk_swell_init=. The sampling rate is |
| 29 | 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 | 82 | * Filter Components |
| 31 | 83 | There are two recursive one-pole IIR filters known as the |
| 32 | 84 | smoothing filter and the switching filter. Both require 1 |
| 33 | −sample of filter memory, as well as a coefficient used to | |
| 34 | −compute the filter. | |
| 85 | +sample of filter memory (which will be called =y=), as well | |
| 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 | 170 | * Parameters |
| 36 | 171 | Swell takes in three parameters: smooth factor A, smooth |
| 37 | 172 | factor B, and inertia. |
| 38 | 173 | |
| 39 | 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 | 199 | Set smoothing factor B with =sk_swell_smoothb=. |
| 42 | 200 | |
| 43 | −Set inertia with =sk_swell_inertia=. This parameter uses | |
| 44 | −@!(ref "parameter_caching" "parameter caching")!@. | |
| 201 | +#+NAME: funcdefs | |
| 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 | 262 | * Computation |
| 46 | 263 | The function =sk_swell_tick= computes one sample of audio |
| 47 | 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 | 287 | The direction is determined by comparing the current input |
| 50 | 288 | signal with the previous input signal. A positive change |
| 51 | 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 | 291 | last selected factor. This will be referred to as the |
| 54 | 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 | 306 | The switching signal is put through a 1-pole filter known |
| 57 | 307 | as the inertia filter. Before computing this filter, the |
| 58 | 308 | inertia parameter is checked to see if it needs to be |
| 59 | −updated via @!(ref "parameter_caching" "parameter | |
| 60 | −caching")!@. | |
| 309 | +updated via @!(ref "parameter_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 | 326 | The filtered switching signal is then snapped to the A/B |
| 63 | 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 | 330 | math functions like =exp= can be expensive, and should be |
| 67 | 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 | 338 | Using the filtered-and-snapped switching signal, the main |
| 70 | 339 | smoothing filter can now be computed on the input signal. |
| 71 | 340 | Similar to the inertia filter, parameter caching is also |
| 72 | 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 | |