Unified · Split
| @@ -1 +1,2 @@ | ||
| 1 | −# sndkit | |
| 1 | +# sndkit | |
| 2 | +sndkit is a sonic toolkit for everywhere. | |
| @@ -0,0 +1,1035 @@ | ||
| 1 | +#+TITLE: Bigverb | |
| 2 | +* Introduction | |
| 3 | +Bigverb is an implementation of a classic waveguide | |
| 4 | +reverberator. It is composed up | |
| 5 | +of 8 feedback delay lines running in parallel, in what is | |
| 6 | +known as a [[https://ccrma.stanford.edu/~jos/cfdn/Feedback_Delay_Networks.html][Feedback Delay Network]]. It is largely inspired by | |
| 7 | +the works Julius O. Smith, and the work he did in using | |
| 8 | +waveguide networks. In particular, this algorithm is based | |
| 9 | +on ideas in a 1985 paper called "A New Approach to Digital | |
| 10 | +Reverberation using Waveguide Networks". | |
| 11 | + | |
| 12 | +Bigverb, as the name implies, has a very nice warm sound | |
| 13 | +when set to be a very large space, and is ideal for use in | |
| 14 | +ambient style music. Bigverb is not designed to sound | |
| 15 | +realistic, and is not an ideal choice for anything that | |
| 16 | +needs room modelling. | |
| 17 | +* Distinct Characterstics | |
| 18 | +The "sound" of this algorithm comes from a few major | |
| 19 | +things. | |
| 20 | +** Use of Jitter Modulation | |
| 21 | +Every delay line has a bit of jitter applied to | |
| 22 | +the delay time, which causes a little of pitch modulation. | |
| 23 | +Several schroeder and FDN-style reverb designs use this | |
| 24 | +kind of modulation, such as the Mutable instruments | |
| 25 | +clouds reverb, and the tank reverb described in the Dattorro | |
| 26 | +paper "Effect design, part 1: reverberator and other filters". | |
| 27 | +Such modulation creates the illusion of the reverb being | |
| 28 | +a higher-order system than it actual (aka: a more complex | |
| 29 | +reverb algorithm with more delay lines). | |
| 30 | +While these designs tend to use a sinusoidal LFO on only a | |
| 31 | +few delay lines, the Costello topology applies non-correlated | |
| 32 | +modulation to every single delay line. As a result, you get | |
| 33 | +a very dense-sounding reverb output. | |
| 34 | +** Parameter Tunings | |
| 35 | +The parameter tunings of this reverb add to the | |
| 36 | +particular character of this sound. All units are set in | |
| 37 | +such a way so that they can be stored as integer values. | |
| 38 | + | |
| 39 | +The tuning consists of 8 parameter sets, corresponding to | |
| 40 | +each delay line "unit". A set has the following parameters: delay time | |
| 41 | +(=delay=) (in units of samples with a samplerate of | |
| 42 | +44.1kHz), the amount of variation in delay time to apply | |
| 43 | +(=drift=) (in units of tenth milliseconds, in order to use | |
| 44 | +integer values), the random variation frequency (=randfreq=) | |
| 45 | +(in Hz multiplied by 1000 to avoid decimals), | |
| 46 | +as well as a starting seed for the internal RNG. | |
| 47 | + | |
| 48 | +The credit for these parameter tunings (and this topology) | |
| 49 | +go to Sean Costello, who also originally designed this | |
| 50 | +reverb algorithm in 1999. This work would later pave the way | |
| 51 | +for his famous commercial reverb plugins and algorithms | |
| 52 | +published by his company ValhallaDSP. | |
| 53 | + | |
| 54 | +#+NAME: parameter_set | |
| 55 | +#+BEGIN_SRC c | |
| 56 | +struct bigverb_paramset { | |
| 57 | + int delay; /* in samples, 44.1 kHz */ | |
| 58 | + int drift; /* 1/10 milliseconds */ | |
| 59 | + int randfreq; /* Hertz * 1000 */ | |
| 60 | + int seed; | |
| 61 | +}; | |
| 62 | + | |
| 63 | +static const struct bigverb_paramset params[8] = { | |
| 64 | + {0x09a9, 0x0a, 0xc1c, 0x07ae}, | |
| 65 | + {0x0acf, 0x0b, 0xdac, 0x7333}, | |
| 66 | + {0x0c91, 0x11, 0x456, 0x5999}, | |
| 67 | + {0x0de5, 0x06, 0xf85, 0x2666}, | |
| 68 | + {0x0f43, 0x0a, 0x925, 0x50a3}, | |
| 69 | + {0x101f, 0x0b, 0x769, 0x5999}, | |
| 70 | + {0x085f, 0x11, 0x37b, 0x7333}, | |
| 71 | + {0x078d, 0x06, 0xc95, 0x3851} | |
| 72 | +}; | |
| 73 | +#+END_SRC | |
| 74 | +** Delay Line Configuration | |
| 75 | +Normally, this family of reverbs is a combination of allpass | |
| 76 | +filters and comb filters in various series/parallel | |
| 77 | +combinations. This implementation has a far simpler | |
| 78 | +topology: it is essentially (but not *quite*) 8 feedback delay | |
| 79 | +lines in parallel with a 1-pole lowpass IIR filter for tone | |
| 80 | +control. If it weren't for the massive amount delay | |
| 81 | +modulation, this would be a pretty metallic sounding reverb! | |
| 82 | +* Top Level Files and Structs | |
| 83 | +** Files to Tangle To | |
| 84 | +This Bigverb document tanlges to two files: | |
| 85 | +=bigverb.c= and =bigverb.h=. | |
| 86 | + | |
| 87 | +=bigverb.h= contains typedefs, structs, and forward | |
| 88 | +function declarations. To enable structs, define the | |
| 89 | +macro =SK_BIGVERB_PRIV=. | |
| 90 | + | |
| 91 | +#+NAME: bigverb.c | |
| 92 | +#+BEGIN_SRC c :tangle bigverb.c | |
| 93 | +#include <stdlib.h> | |
| 94 | +#include <stdio.h> | |
| 95 | +#include <math.h> | |
| 96 | +#define SK_BIGVERB_PRIV | |
| 97 | +#include "bigverb.h" | |
| 98 | +<<parameter_set>> | |
| 99 | +<<fdelay_constants>> | |
| 100 | +<<static_funcdefs>> | |
| 101 | +<<funcs>> | |
| 102 | +#+END_SRC | |
| 103 | + | |
| 104 | +#+NAME: bigverb.h | |
| 105 | +#+BEGIN_SRC c :tangle bigverb.h | |
| 106 | +#ifndef SK_BIGVERB_H | |
| 107 | +#define SK_BIGVERB_H | |
| 108 | + | |
| 109 | +#ifndef SKFLT | |
| 110 | +#define SKFLT float | |
| 111 | +#endif | |
| 112 | +<<typedefs>> | |
| 113 | +<<funcdefs>> | |
| 114 | + | |
| 115 | +#ifdef SK_BIGVERB_PRIV | |
| 116 | +<<main_struct>> | |
| 117 | +#endif | |
| 118 | +#endif | |
| 119 | +#+END_SRC | |
| 120 | +** The Bigverb Struct | |
| 121 | +An instance of Bigverb is contained inside of a struct | |
| 122 | +called =sk_bigverb=. | |
| 123 | +#+NAME: typedefs | |
| 124 | +#+BEGIN_SRC c | |
| 125 | +typedef struct sk_bigverb sk_bigverb; | |
| 126 | +#+END_SRC | |
| 127 | + | |
| 128 | +#+NAME: main_struct | |
| 129 | +#+BEGIN_SRC c | |
| 130 | +<<delay_struct>> | |
| 131 | +struct sk_bigverb { | |
| 132 | + int sr; | |
| 133 | + <<sk_bigverb>> | |
| 134 | +}; | |
| 135 | +#+END_SRC | |
| 136 | +* Setup and Cleanup | |
| 137 | +A new instance of bigverb is created with | |
| 138 | +=sk_bigverb_new=. The only argument required is the sampling | |
| 139 | +rate. If something goes wrong, this will return =NULL=. | |
| 140 | + | |
| 141 | +#+NAME: funcdefs | |
| 142 | +#+BEGIN_SRC c | |
| 143 | +sk_bigverb * sk_bigverb_new(int sr); | |
| 144 | +#+END_SRC | |
| 145 | + | |
| 146 | +#+NAME: funcs | |
| 147 | +#+BEGIN_SRC c | |
| 148 | +sk_bigverb * sk_bigverb_new(int sr) | |
| 149 | +{ | |
| 150 | + sk_bigverb *bv; | |
| 151 | + | |
| 152 | + bv = calloc(1, sizeof(sk_bigverb)); | |
| 153 | + | |
| 154 | + bv->sr = sr; | |
| 155 | + <<init_variables>> | |
| 156 | + <<setup_delay_lines>> | |
| 157 | + | |
| 158 | + return bv; | |
| 159 | +} | |
| 160 | +#+END_SRC | |
| 161 | + | |
| 162 | +When it is done being used, bigverb must be cleanly freed | |
| 163 | +with =sk_bigverb_del=. | |
| 164 | + | |
| 165 | +#+NAME: funcdefs | |
| 166 | +#+BEGIN_SRC c | |
| 167 | +void sk_bigverb_del(sk_bigverb *bv); | |
| 168 | +#+END_SRC | |
| 169 | + | |
| 170 | +#+NAME: funcs | |
| 171 | +#+BEGIN_SRC c | |
| 172 | +void sk_bigverb_del(sk_bigverb *bv) | |
| 173 | +{ | |
| 174 | + <<cleanup>> | |
| 175 | + free(bv); | |
| 176 | + bv = NULL; | |
| 177 | +} | |
| 178 | +#+END_SRC | |
| 179 | +* High level parameters | |
| 180 | +High level parametric control of bigverb includes | |
| 181 | +"size" and "cutoff". Set parameters before computing | |
| 182 | +audio. These are are just floating point values contained | |
| 183 | +in the struct that can be indirectly set with setters in | |
| 184 | +situations where the struct is opaque. | |
| 185 | +** Size | |
| 186 | +Set the reverb size with =sk_bigverb_size= | |
| 187 | + | |
| 188 | +#+NAME: funcdefs | |
| 189 | +#+BEGIN_SRC c | |
| 190 | +void sk_bigverb_size(sk_bigverb *bv, SKFLT size); | |
| 191 | +#+END_SRC | |
| 192 | + | |
| 193 | +Size is a variable between 0-1, which | |
| 194 | +controls the feedback level for the delay line. | |
| 195 | + | |
| 196 | +The =size= parameter is stored as a variable called | |
| 197 | +=size=, and is set to be a pretty sounding value of | |
| 198 | +=0.93=. | |
| 199 | + | |
| 200 | +#+NAME: sk_bigverb | |
| 201 | +#+BEGIN_SRC c | |
| 202 | +SKFLT size; | |
| 203 | +#+END_SRC | |
| 204 | + | |
| 205 | +#+NAME: init_variables | |
| 206 | +#+BEGIN_SRC c | |
| 207 | +sk_bigverb_size(bv, 0.93); | |
| 208 | +#+END_SRC | |
| 209 | + | |
| 210 | +#+NAME: funcs | |
| 211 | +#+BEGIN_SRC c | |
| 212 | +void sk_bigverb_size(sk_bigverb *bv, SKFLT size) | |
| 213 | +{ | |
| 214 | + bv->size = size; | |
| 215 | +} | |
| 216 | +#+END_SRC | |
| 217 | +** Cutoff | |
| 218 | +The tone of bigverb can be set with =sk_bigverb_cutoff=. | |
| 219 | + | |
| 220 | +#+NAME: funcdefs | |
| 221 | +#+BEGIN_SRC c | |
| 222 | +void sk_bigverb_cutoff(sk_bigverb *bv, SKFLT cutoff); | |
| 223 | +#+END_SRC | |
| 224 | + | |
| 225 | +=cutoff= is a parameter in Hz that determines the overall | |
| 226 | +timbre of the reverb. This controls the cutoff frequency of | |
| 227 | +the one pole lowpass filter applied to the reverb. | |
| 228 | + | |
| 229 | +It is set to be a default value of 10kHz, or 10,000 hz. | |
| 230 | + | |
| 231 | +#+NAME: init_variables | |
| 232 | +#+BEGIN_SRC c | |
| 233 | +sk_bigverb_cutoff(bv, 10000.0); | |
| 234 | +#+END_SRC | |
| 235 | + | |
| 236 | +Cutoff uses caching in order to monitor if the parameter | |
| 237 | +has changed. It does this in order to prevent needing to | |
| 238 | +compute filter coefficients every sample. The main variable | |
| 239 | +to be set is =cutoff=, and the cached variable is =pcutoff=. | |
| 240 | +At the beginning, =pcutoff= is set to be a negative value, | |
| 241 | +which will cause bigverb to calculate coefficients in the | |
| 242 | +first call to the tick function after initialization. | |
| 243 | + | |
| 244 | +#+NAME: sk_bigverb | |
| 245 | +#+BEGIN_SRC c | |
| 246 | +SKFLT cutoff; | |
| 247 | +SKFLT pcutoff; | |
| 248 | +#+END_SRC | |
| 249 | + | |
| 250 | +#+NAME: init_variables | |
| 251 | +#+BEGIN_SRC c | |
| 252 | +bv->pcutoff = -1; | |
| 253 | +#+END_SRC | |
| 254 | + | |
| 255 | +#+NAME: funcs | |
| 256 | +#+BEGIN_SRC c | |
| 257 | +void sk_bigverb_cutoff(sk_bigverb *bv, SKFLT cutoff) | |
| 258 | +{ | |
| 259 | + bv->cutoff = cutoff; | |
| 260 | +} | |
| 261 | +#+END_SRC | |
| 262 | +* Filter | |
| 263 | +State in a constant called =filt=. | |
| 264 | + | |
| 265 | +#+NAME: sk_bigverb | |
| 266 | +#+BEGIN_SRC c | |
| 267 | +SKFLT filt; | |
| 268 | +#+END_SRC | |
| 269 | + | |
| 270 | +#+NAME: init_variables | |
| 271 | +#+BEGIN_SRC c | |
| 272 | +bv->filt = 1.0; | |
| 273 | +#+END_SRC | |
| 274 | +* Computing Audio | |
| 275 | +After bigverb has been initialized, it is ready to | |
| 276 | +process audio. This implementation uses what is known | |
| 277 | +as a =tick= function, or a function that computes audio | |
| 278 | +one sample at a time instead of one block at a time. This | |
| 279 | +simplifies the implementation at the cost of a little bit | |
| 280 | +of performance overhead, depending on the compiler and | |
| 281 | +optimization settings. | |
| 282 | +** Top-Level Tick Function | |
| 283 | +The function to tick one sample unit of audio is done with | |
| 284 | +=sk_bigverb_tick=. It takes in two stereo input | |
| 285 | +values, and returns two stereo output values. | |
| 286 | + | |
| 287 | +#+NAME: funcdefs | |
| 288 | +#+BEGIN_SRC c | |
| 289 | +void sk_bigverb_tick(sk_bigverb *bv, | |
| 290 | + SKFLT inL, SKFLT inR, | |
| 291 | + SKFLT *outL, SKFLT *outR); | |
| 292 | +#+END_SRC | |
| 293 | + | |
| 294 | +#+NAME: funcs | |
| 295 | +#+BEGIN_SRC c | |
| 296 | +void sk_bigverb_tick(sk_bigverb *bv, | |
| 297 | + SKFLT inL, SKFLT inR, | |
| 298 | + SKFLT *outL, SKFLT *outR) | |
| 299 | +{ | |
| 300 | + /* TODO: implement */ | |
| 301 | + SKFLT lsum, rsum; | |
| 302 | + | |
| 303 | + lsum = 0; | |
| 304 | + rsum = 0; | |
| 305 | + | |
| 306 | + <<update_filter_coefficients>> | |
| 307 | + <<calculate_junction_pressure>> | |
| 308 | + <<compute_delay_bank>> | |
| 309 | + | |
| 310 | + *outL = lsum; | |
| 311 | + *outR = rsum; | |
| 312 | +} | |
| 313 | +#+END_SRC | |
| 314 | +** Updating filter coefficients | |
| 315 | +Bigverb uses parameter caching for the =cutoff= parameter in | |
| 316 | +order to save on computation time. | |
| 317 | + | |
| 318 | +Any time =cutoff= changes, the filter coefficients must be | |
| 319 | +updated. This happens in the tick function, before any | |
| 320 | +computation happens. | |
| 321 | + | |
| 322 | +The filter is a simple 1-pole IIR lowpass filter whose | |
| 323 | +difference equation been reduced to only require a single | |
| 324 | +parameter. This in turn then gets used in each filter delay | |
| 325 | +line. | |
| 326 | + | |
| 327 | +#+NAME: update_filter_coefficients | |
| 328 | +#+BEGIN_SRC c | |
| 329 | +if (bv->pcutoff != bv->cutoff) { | |
| 330 | + bv->pcutoff = bv->cutoff; | |
| 331 | + bv->filt = 2.0 - cos(bv->pcutoff * 2 * M_PI / bv->sr); | |
| 332 | + bv->filt = bv->filt - sqrt(bv->filt * bv->filt - 1.0); | |
| 333 | +} | |
| 334 | +#+END_SRC | |
| 335 | +** Calculating Resultant Junction Pressure Amount | |
| 336 | +The resultant junction pressure amount is calculated from | |
| 337 | +the delay bank, and then factored into the input signals. | |
| 338 | + | |
| 339 | +Sum of all the delay line signals, and scaled by 0.25, or | |
| 340 | +2/N, where N is the number of delay lines (8). | |
| 341 | + | |
| 342 | +#+NAME: calculate_junction_pressure | |
| 343 | +#+BEGIN_SRC c | |
| 344 | +{ | |
| 345 | + int i; | |
| 346 | + SKFLT jp; | |
| 347 | + | |
| 348 | + jp = 0; | |
| 349 | + | |
| 350 | + for (i = 0; i < 8; i++) { | |
| 351 | + jp += bv->delay[i].y; | |
| 352 | + } | |
| 353 | + | |
| 354 | + jp *= 0.25; | |
| 355 | + | |
| 356 | + inL = jp + inL; | |
| 357 | + inR = jp + inR; | |
| 358 | +} | |
| 359 | +#+END_SRC | |
| 360 | +** Computing the delay bank | |
| 361 | +The delay bank is then computed. Each delay line is computed | |
| 362 | +and summed with either the left or right input signal, and | |
| 363 | +then sent to a corresponding left or right channel. | |
| 364 | + | |
| 365 | +At the end, a final scaling out of the output happens. This | |
| 366 | +is hard coded to be 35 percent. | |
| 367 | + | |
| 368 | +#+NAME: compute_delay_bank | |
| 369 | +#+BEGIN_SRC c | |
| 370 | +{ | |
| 371 | + int i; | |
| 372 | + for (i = 0; i < 8; i++) { | |
| 373 | + if (i & 1) { | |
| 374 | + rsum += delay_compute(&bv->delay[i], | |
| 375 | + inR, | |
| 376 | + bv->size, | |
| 377 | + bv->filt, | |
| 378 | + bv->sr); | |
| 379 | + } else { | |
| 380 | + lsum += delay_compute(&bv->delay[i], | |
| 381 | + inL, | |
| 382 | + bv->size, | |
| 383 | + bv->filt, | |
| 384 | + bv->sr); | |
| 385 | + } | |
| 386 | + } | |
| 387 | +} | |
| 388 | +rsum *= 0.35f; | |
| 389 | +lsum *= 0.35f; | |
| 390 | +#+END_SRC | |
| 391 | +* The Feedback Delay Line Bank | |
| 392 | +8 delay units come together to make the delay line bank. | |
| 393 | +Each is initialized using one of the parameter sets. | |
| 394 | +** Memory Allocation + Setup | |
| 395 | +#+NAME: sk_bigverb | |
| 396 | +#+BEGIN_SRC c | |
| 397 | +SKFLT *buf; | |
| 398 | +#+END_SRC | |
| 399 | + | |
| 400 | +#+NAME: init_variables | |
| 401 | +#+BEGIN_SRC c | |
| 402 | +bv->buf = NULL; | |
| 403 | +#+END_SRC | |
| 404 | + | |
| 405 | +#+NAME: setup_delay_lines | |
| 406 | +#+BEGIN_SRC c | |
| 407 | +{ | |
| 408 | +unsigned long total_size; | |
| 409 | +int i; | |
| 410 | +SKFLT *buf; | |
| 411 | + | |
| 412 | +total_size = 0; | |
| 413 | +buf = NULL; | |
| 414 | +<<calculate_pool_size>> | |
| 415 | +<<allocate_memory>> | |
| 416 | +<<initialize_delay_banks>> | |
| 417 | +} | |
| 418 | +#+END_SRC | |
| 419 | +The delay bank is the abstraction in charge of properly | |
| 420 | +allocating all the memory needed for the buffers. | |
| 421 | + | |
| 422 | +Memory is allocated in one giant chunk, and then divied up | |
| 423 | +to each delay line. | |
| 424 | + | |
| 425 | +The total memory size is obtained by summing all the delay | |
| 426 | +times. These times are stored as fixed delay times in units | |
| 427 | +of samples. These parameters assume a sampling rate of | |
| 428 | +44.1kHz. If this is not the case, this value must be scaled | |
| 429 | +accordingly, and then truncated to be an integer. This value | |
| 430 | +is used again to properly slice up the big memory chunk. | |
| 431 | + | |
| 432 | +#+NAME: static_funcdefs | |
| 433 | +#+BEGIN_SRC c | |
| 434 | +static int get_delay_size(const struct bigverb_paramset *p, int sr); | |
| 435 | +#+END_SRC | |
| 436 | + | |
| 437 | +#+NAME: funcs | |
| 438 | +#+BEGIN_SRC c | |
| 439 | +static int get_delay_size(const struct bigverb_paramset *p, int sr) | |
| 440 | +{ | |
| 441 | + SKFLT sz; | |
| 442 | + sz = (SKFLT)p->delay/44100 + (p->drift * 0.0001) * 1.125; | |
| 443 | + return floor(16 + sz*sr); | |
| 444 | +} | |
| 445 | +#+END_SRC | |
| 446 | + | |
| 447 | +#+NAME: calculate_pool_size | |
| 448 | +#+BEGIN_SRC c | |
| 449 | +for (i = 0; i < 8; i++) { | |
| 450 | + total_size += get_delay_size(¶ms[i], sr); | |
| 451 | +} | |
| 452 | +#+END_SRC | |
| 453 | + | |
| 454 | +Allocation is done with =calloc=, which zeros out the memory | |
| 455 | +as well. This memory will eventually be freed in | |
| 456 | +=sk_bigverb_del=. | |
| 457 | + | |
| 458 | +#+NAME: allocate_memory | |
| 459 | +#+BEGIN_SRC c | |
| 460 | +buf = calloc(1, sizeof(SKFLT) * total_size); | |
| 461 | +bv->buf = buf; | |
| 462 | +#+END_SRC | |
| 463 | + | |
| 464 | +#+NAME: cleanup | |
| 465 | +#+BEGIN_SRC c | |
| 466 | +free(bv->buf); | |
| 467 | +#+END_SRC | |
| 468 | + | |
| 469 | +#+NAME: sk_bigverb | |
| 470 | +#+BEGIN_SRC c | |
| 471 | +sk_bigverb_delay delay[8]; | |
| 472 | +#+END_SRC | |
| 473 | + | |
| 474 | +#+NAME: initialize_delay_banks | |
| 475 | +#+BEGIN_SRC c | |
| 476 | +{ | |
| 477 | + unsigned long bufpos; | |
| 478 | + bufpos = 0; | |
| 479 | + for (i = 0; i < 8; i++) { | |
| 480 | + unsigned int sz; | |
| 481 | + sz = get_delay_size(¶ms[i], sr); | |
| 482 | + | |
| 483 | + delay_init(&bv->delay[i], ¶ms[i], | |
| 484 | + &buf[bufpos], sz, sr); | |
| 485 | + bufpos += sz; | |
| 486 | + } | |
| 487 | +} | |
| 488 | +#+END_SRC | |
| 489 | +** A Single Delay Line Unit | |
| 490 | +A delay unit in a bank consists of variable delay line with | |
| 491 | +cubic interpolation with a 1 pole low-pass filter for tone | |
| 492 | +control, whose frequency is determined using a master | |
| 493 | +parameter, as well as a jitter generator. Feedback as well. | |
| 494 | +*** Struct Declaration | |
| 495 | +A delay unit is known as a struct called =sk_bigverb_delay=. | |
| 496 | + | |
| 497 | +#+NAME: typedefs | |
| 498 | +#+BEGIN_SRC c | |
| 499 | +typedef struct sk_bigverb_delay sk_bigverb_delay; | |
| 500 | +#+END_SRC | |
| 501 | + | |
| 502 | +#+NAME: delay_struct | |
| 503 | +#+BEGIN_SRC c | |
| 504 | +struct sk_bigverb_delay { | |
| 505 | + <<bigverb_delay>> | |
| 506 | +}; | |
| 507 | +#+END_SRC | |
| 508 | +*** Initialization | |
| 509 | +#+NAME: static_funcdefs | |
| 510 | +#+BEGIN_SRC c | |
| 511 | +static void delay_init(sk_bigverb_delay *d, | |
| 512 | + const struct bigverb_paramset *p, | |
| 513 | + SKFLT *buf, | |
| 514 | + size_t sz, | |
| 515 | + int sr); | |
| 516 | +#+END_SRC | |
| 517 | + | |
| 518 | +#+NAME: funcs | |
| 519 | +#+BEGIN_SRC c | |
| 520 | +static void delay_init(sk_bigverb_delay *d, | |
| 521 | + const struct bigverb_paramset *p, | |
| 522 | + SKFLT *buf, | |
| 523 | + size_t sz, | |
| 524 | + int sr) | |
| 525 | +{ | |
| 526 | + SKFLT readpos; | |
| 527 | + <<delay_init>> | |
| 528 | +} | |
| 529 | +#+END_SRC | |
| 530 | + | |
| 531 | +Set up buffer + sz | |
| 532 | + | |
| 533 | +#+NAME: bigverb_delay | |
| 534 | +#+BEGIN_SRC c | |
| 535 | +SKFLT *buf; | |
| 536 | +size_t sz; | |
| 537 | +#+END_SRC | |
| 538 | + | |
| 539 | +#+NAME: delay_init | |
| 540 | +#+BEGIN_SRC c | |
| 541 | +d->buf = buf; | |
| 542 | +d->sz = sz; | |
| 543 | +#+END_SRC | |
| 544 | + | |
| 545 | +Initialize write position (0), abbreviated as =wpos=. | |
| 546 | + | |
| 547 | +#+NAME: bigverb_delay | |
| 548 | +#+BEGIN_SRC c | |
| 549 | +int wpos; | |
| 550 | +#+END_SRC | |
| 551 | + | |
| 552 | +#+NAME: delay_init | |
| 553 | +#+BEGIN_SRC c | |
| 554 | +d->wpos = 0; | |
| 555 | +#+END_SRC | |
| 556 | + | |
| 557 | +Initialize read position. Based on delay time, =drift= and | |
| 558 | +initial seed. Read position has to components, an integer | |
| 559 | +read position, and a floating point read position. These | |
| 560 | +will be abbreviated =irpos= and =frpos=. | |
| 561 | + | |
| 562 | +#+NAME: bigverb_delay | |
| 563 | +#+BEGIN_SRC c | |
| 564 | +int irpos; | |
| 565 | +int frpos; | |
| 566 | +#+END_SRC | |
| 567 | + | |
| 568 | +Seed value is multiplied by the initial drift value, and then | |
| 569 | +divided by 32767. | |
| 570 | + | |
| 571 | +#+NAME: bigverb_delay | |
| 572 | +#+BEGIN_SRC c | |
| 573 | +int rng; | |
| 574 | +#+END_SRC | |
| 575 | + | |
| 576 | +#+NAME: delay_init | |
| 577 | +#+BEGIN_SRC c | |
| 578 | +d->rng = p->seed; | |
| 579 | +<<setup_readpos>> | |
| 580 | +#+END_SRC | |
| 581 | + | |
| 582 | + | |
| 583 | +The initial time is added to this. | |
| 584 | + | |
| 585 | +=bufsize - (readpos * sr)= <-- this puts the read position at | |
| 586 | +the end of the buffer. | |
| 587 | + | |
| 588 | +Truncate (using integer cast).' | |
| 589 | + | |
| 590 | +#+NAME: setup_readpos | |
| 591 | +#+BEGIN_SRC c | |
| 592 | +readpos = ((SKFLT)p->delay / 44100); | |
| 593 | +readpos += d->rng * (p->drift * 0.0001) / 32768.0; | |
| 594 | +readpos = sz - (readpos * sr); | |
| 595 | +d->irpos = floor(readpos); | |
| 596 | +d->frpos = floor((readpos - d->irpos) * FRACSCALE); | |
| 597 | +#+END_SRC | |
| 598 | + | |
| 599 | +Create first random segments. | |
| 600 | + | |
| 601 | +#+NAME: delay_init | |
| 602 | +#+BEGIN_SRC c | |
| 603 | +<<init_jitter>> | |
| 604 | +generate_next_line(d, sr); | |
| 605 | +#+END_SRC | |
| 606 | +*** Top-Level Compute | |
| 607 | +The delay line computation is done in a tick function. It | |
| 608 | +takes in an input sample, returns an output sample. In | |
| 609 | +addition to delay, filtering, feedback, and jittering | |
| 610 | +happens as well. | |
| 611 | + | |
| 612 | +Because feedback + filtering are global options, these | |
| 613 | +are passed in as parameters on the stack. What is required | |
| 614 | +is the feedback amount, and the calculated filter | |
| 615 | +coeffecient used in the filter. | |
| 616 | + | |
| 617 | +#+NAME: static_funcdefs | |
| 618 | +#+BEGIN_SRC c | |
| 619 | +static SKFLT delay_compute(sk_bigverb_delay *d, | |
| 620 | + SKFLT in, | |
| 621 | + SKFLT fdbk, | |
| 622 | + SKFLT filt, | |
| 623 | + int sr); | |
| 624 | +#+END_SRC | |
| 625 | + | |
| 626 | +#+NAME: funcs | |
| 627 | +#+BEGIN_SRC c | |
| 628 | +static SKFLT delay_compute(sk_bigverb_delay *del, | |
| 629 | + SKFLT in, | |
| 630 | + SKFLT fdbk, | |
| 631 | + SKFLT filt, | |
| 632 | + int sr) | |
| 633 | +{ | |
| 634 | + SKFLT out; | |
| 635 | + SKFLT frac_norm; | |
| 636 | + SKFLT a, b, c, d; | |
| 637 | + SKFLT s[4]; | |
| 638 | + out = 0; | |
| 639 | + <<write_to_buffer>> | |
| 640 | + <<increment_write_position>> | |
| 641 | + <<update_fractional_read_position>> | |
| 642 | + <<update_integer_read_position>> | |
| 643 | + <<normalize_fractional_component>> | |
| 644 | + <<calculate_interpolation_coefficients>> | |
| 645 | + <<read_from_buffer>> | |
| 646 | + <<compute_interpolation>> | |
| 647 | + <<increment_fractional_read_position>> | |
| 648 | + <<apply_feedback_and_filter>> | |
| 649 | + <<update_jitter>> | |
| 650 | + return out; | |
| 651 | +} | |
| 652 | +#+END_SRC | |
| 653 | + | |
| 654 | +# since filter is the same cutoff... probably | |
| 655 | +# only need to filter once to get same result. | |
| 656 | +# this is the cummutative property I think? | |
| 657 | +# May do this later. | |
| 658 | + | |
| 659 | +The following things happen: | |
| 660 | + | |
| 661 | +Write the to delay buffer and pre-filter the input by | |
| 662 | +subtracting the filter state =y=. | |
| 663 | + | |
| 664 | +#+NAME: write_to_buffer | |
| 665 | +#+BEGIN_SRC c | |
| 666 | +del->buf[del->wpos] = in - del->y; | |
| 667 | +#+END_SRC | |
| 668 | + | |
| 669 | +Increment the write position. If this is greater than the | |
| 670 | +buffer size, wrap around. | |
| 671 | + | |
| 672 | +#+NAME: increment_write_position | |
| 673 | +#+BEGIN_SRC c | |
| 674 | +del->wpos++; | |
| 675 | +if (del->wpos >= del->sz) del->wpos -= del->sz; | |
| 676 | +#+END_SRC | |
| 677 | + | |
| 678 | +Update the fractional read position. If the read position | |
| 679 | +exceeds the maximum fractional scale amount, it means it has | |
| 680 | +bits that must carry over to the integer read position. | |
| 681 | +After these bits have been carried over, mask out the upper | |
| 682 | +bits to keep the range in bounds. | |
| 683 | + | |
| 684 | +#+NAME: update_fractional_read_position | |
| 685 | +#+BEGIN_SRC c | |
| 686 | +if (del->frpos >= FRACSCALE) { | |
| 687 | + del->irpos += del->frpos >> FRACNBITS; | |
| 688 | + del->frpos &= FRACMASK; | |
| 689 | +} | |
| 690 | +#+END_SRC | |
| 691 | + | |
| 692 | +If needed, update the read position with wrap-around. | |
| 693 | + | |
| 694 | +#+NAME: update_integer_read_position | |
| 695 | +#+BEGIN_SRC c | |
| 696 | +if (del->irpos >= del->sz) del->irpos -= del->sz; | |
| 697 | +#+END_SRC | |
| 698 | + | |
| 699 | +Normalize the fractional component so that it is in range | |
| 700 | +0 and 1. This is done by dividing the amount by fractional | |
| 701 | +scaling factor =FRACSCALE=. | |
| 702 | + | |
| 703 | +#+NAME: normalize_fractional_component | |
| 704 | +#+BEGIN_SRC c | |
| 705 | +frac_norm = del->frpos / (SKFLT)FRACSCALE; | |
| 706 | +#+END_SRC | |
| 707 | + | |
| 708 | +Calculate interpolation coefficients. These are 4 | |
| 709 | +pre-derived coefficents used to compute third-order | |
| 710 | +lagrangian interpolation. | |
| 711 | +Derivation of these is currently beyond the scope of this | |
| 712 | +document. These will be called =a=, =b=, =c=, and =d=, | |
| 713 | +respectively, and will correspond to =x(n - 1)=, =x(n)=, | |
| 714 | +=x(n + 1)=, and =x(n + 2)=, respectively. | |
| 715 | + | |
| 716 | +#+NAME: calculate_interpolation_coefficients | |
| 717 | +#+BEGIN_SRC c | |
| 718 | +{ | |
| 719 | + SKFLT tmp[2]; | |
| 720 | + d = ((frac_norm * frac_norm) - 1) / 6.0; | |
| 721 | + tmp[0] = ((frac_norm + 1.0) * 0.5); | |
| 722 | + tmp[1] = 3.0 * d; | |
| 723 | + a = tmp[0] - 1.0 - d; | |
| 724 | + c = tmp[0] - tmp[1]; | |
| 725 | + b = tmp[1] - frac_norm; | |
| 726 | +} | |
| 727 | +#+END_SRC | |
| 728 | + | |
| 729 | +Read the samples needed, based on the current playhead | |
| 730 | +position. When the read position is in regular bounds, this | |
| 731 | +means reading the previous, current, two next samples. | |
| 732 | +Otherwise, this means the same thing, but with wrap-around | |
| 733 | +and bounds checks. | |
| 734 | + | |
| 735 | +#+NAME: read_from_buffer | |
| 736 | +#+BEGIN_SRC c | |
| 737 | +{ | |
| 738 | + int n; | |
| 739 | + SKFLT *x; | |
| 740 | + n = del->irpos; | |
| 741 | + x = del->buf; | |
| 742 | + | |
| 743 | + if (n > 0 && n < (del->sz - 2)) { | |
| 744 | + s[0] = x[n - 1]; | |
| 745 | + s[1] = x[n]; | |
| 746 | + s[2] = x[n + 1]; | |
| 747 | + s[3] = x[n + 2]; | |
| 748 | + } else { | |
| 749 | + int k; | |
| 750 | + n--; | |
| 751 | + if (n < 0) n += del->sz; | |
| 752 | + s[0] = x[n]; | |
| 753 | + for (k = 0; k < 3; k++) { | |
| 754 | + n++; | |
| 755 | + if (n >= del->sz) n -= del->sz; | |
| 756 | + s[k + 1] = x[n]; | |
| 757 | + } | |
| 758 | + } | |
| 759 | +} | |
| 760 | +#+END_SRC | |
| 761 | + | |
| 762 | +Calculate interpolation. Using the coefficents | |
| 763 | +described above and the fractional component =f=, one can compute | |
| 764 | +cubic interpolation with the following expression: | |
| 765 | + | |
| 766 | +$$ | |
| 767 | +y(n) = (a x(n - 1) + b x(n) + c x(n + 1) + d x(n + 2)) \cdot f + x(n) | |
| 768 | +$$ | |
| 769 | + | |
| 770 | +#+NAME: compute_interpolation | |
| 771 | +#+BEGIN_SRC c | |
| 772 | +out = (a*s[0] + b*s[1] + c*s[2] + d*s[3]) * frac_norm + s[1]; | |
| 773 | +#+END_SRC | |
| 774 | + | |
| 775 | +Increment fractional read position, as determined by the | |
| 776 | +jitter. | |
| 777 | + | |
| 778 | +#+NAME: increment_fractional_read_position | |
| 779 | +#+BEGIN_SRC c | |
| 780 | +del->frpos += del->inc; | |
| 781 | +#+END_SRC | |
| 782 | + | |
| 783 | +Apply feedback and filter. The feedback will scale the delay | |
| 784 | +output. The filtering is a difference equation, optimized | |
| 785 | +and factored to use a minimum number of arithmetic | |
| 786 | +operations. | |
| 787 | + | |
| 788 | +#+NAME: apply_feedback_and_filter | |
| 789 | +#+BEGIN_SRC c | |
| 790 | +out *= fdbk; | |
| 791 | +out += (del->y - out) * filt; | |
| 792 | +del->y = out; | |
| 793 | +#+END_SRC | |
| 794 | + | |
| 795 | +# Original difference equation | |
| 796 | +# | |
| 797 | +# y(n) = c1 * x(n) + c2 * y(n - 1) | |
| 798 | +# | |
| 799 | +# c2 = b - sqrt(b^2 - 1.0) | |
| 800 | +# c1 = (1 - c2) | |
| 801 | +# b = 2 - cos(\omega)) | |
| 802 | +# \omega = freq * 2 * PI / sr | |
| 803 | +# | |
| 804 | +# can be derived from tone in the following way: | |
| 805 | +# y(n) = c1 * x(n) + c2 * Y(n - 1) | |
| 806 | +# = (1 - c2)*x(n) + c2 * y(n - 1) | |
| 807 | +# = x(n) - c2*x(n) + c2*y(n-1) | |
| 808 | +# = c2*y(n - 1) - c2*x(n) + x(n) | |
| 809 | +# = c2*(y(n - 1) - x(n)) + x(n) | |
| 810 | + | |
| 811 | +Update jitter, if needed. When the counter zeros out | |
| 812 | +(or worse), it is time to find a new random target to lerp | |
| 813 | +to. | |
| 814 | + | |
| 815 | +#+NAME: update_jitter | |
| 816 | +#+BEGIN_SRC c | |
| 817 | +del->counter--; | |
| 818 | +if (del->counter <= 0) { | |
| 819 | + generate_next_line(del, sr); | |
| 820 | +} | |
| 821 | +#+END_SRC | |
| 822 | +*** Feedback Fractional Delay Line | |
| 823 | +A delay line is initialized with a pre-allocated zeroed | |
| 824 | +buffer its size. Memory will be managed outside of this | |
| 825 | +abstraction. | |
| 826 | + | |
| 827 | +Being a fractional delay line means the read position has | |
| 828 | +two components: an integer component and a fractional | |
| 829 | +component. The integer component is the current position | |
| 830 | +in the delay buffer. The fractional component tells how much | |
| 831 | +it goes over into the next discrete sample position. | |
| 832 | +In a way, interpolation can be thought of as the process of | |
| 833 | +using these two values to make a really good guess of what | |
| 834 | +lies in between the samples. | |
| 835 | + | |
| 836 | +The fractional delay component is maintained as a 28 bit | |
| 837 | +integer. This is done to avoid some of the weirdness found | |
| 838 | +in floating point operations. The remaining upper bits | |
| 839 | +are "carry-over" samples, that get accumulated in integer | |
| 840 | +component of the read position. | |
| 841 | + | |
| 842 | +A few constants are used to conveniently work with this | |
| 843 | +fractional delay component. | |
| 844 | + | |
| 845 | +=FRACSCALE= is the fractional scaling amount, | |
| 846 | +which is $1^28$, or =0x10000000=. Multiplied with a uniform | |
| 847 | +scalar, this is used to calculate the increment. | |
| 848 | + | |
| 849 | +#+NAME: fdelay_constants | |
| 850 | +#+BEGIN_SRC c | |
| 851 | +#define FRACSCALE 0x10000000 | |
| 852 | +#+END_SRC | |
| 853 | + | |
| 854 | +=FRACMASK= is the bitmask used to keep the fractional | |
| 855 | +position in 28-bit range. It is $1^28 - 1$, or =0xFFFFFFF=. | |
| 856 | +This is in particular is used to filter out upper bits that | |
| 857 | +get carried over to the integer read position. | |
| 858 | + | |
| 859 | +#+NAME: fdelay_constants | |
| 860 | +#+BEGIN_SRC c | |
| 861 | +#define FRACMASK 0xFFFFFFF | |
| 862 | +#+END_SRC | |
| 863 | + | |
| 864 | +=FRACNBITS= is the number of bits in the number. set to be | |
| 865 | +28. | |
| 866 | + | |
| 867 | +#+NAME: fdelay_constants | |
| 868 | +#+BEGIN_SRC c | |
| 869 | +#define FRACNBITS 28 | |
| 870 | +#+END_SRC | |
| 871 | +*** Jitter | |
| 872 | +Jitter in this context, is a random line segment | |
| 873 | +generator. It linearly interpolates between random values in | |
| 874 | +a given range, using random durations in a given range. | |
| 875 | + | |
| 876 | +A line generator stores a counter and increment amount. | |
| 877 | + | |
| 878 | +#+NAME: bigverb_delay | |
| 879 | +#+BEGIN_SRC c | |
| 880 | +int inc; | |
| 881 | +int counter; | |
| 882 | +#+END_SRC | |
| 883 | + | |
| 884 | +#+NAME: init_jitter | |
| 885 | +#+BEGIN_SRC c | |
| 886 | +d->inc = 0; | |
| 887 | +d->counter = 0; | |
| 888 | +#+END_SRC | |
| 889 | + | |
| 890 | +The most significant thing to happen in the jitter signal is | |
| 891 | +calculating the next random segment. This is done in a | |
| 892 | +static function called =generate_next_line=. | |
| 893 | + | |
| 894 | +#+NAME: static_funcdefs | |
| 895 | +#+BEGIN_SRC c | |
| 896 | +static void generate_next_line(sk_bigverb_delay *d, int sr); | |
| 897 | +#+END_SRC | |
| 898 | + | |
| 899 | +To begin, another random value is created based on the | |
| 900 | +previous random value. | |
| 901 | + | |
| 902 | +The RNG algorithm used is quite simple, and is used to | |
| 903 | +produce a 16-bit value. | |
| 904 | + | |
| 905 | +$$x(n) = (5^{6}x(n - 1) + 1) \& 0xFFFF$$ | |
| 906 | + | |
| 907 | +Before and after this equation, the value is balanced so that | |
| 908 | +is a 16-bit bipolar signal. | |
| 909 | +Before, it adds 0x10000 if the | |
| 910 | +value is less than 0. After, it substracts 0x10000 if the | |
| 911 | +seed value is greater than 0x8000. | |
| 912 | + | |
| 913 | +#+NAME: generate_random_number | |
| 914 | +#+BEGIN_SRC c | |
| 915 | +if (d->rng < 0) d->rng += 0x10000; | |
| 916 | +/* 5^6 = 15625 */ | |
| 917 | +d->rng = (1 + d->rng * 0x3d09); | |
| 918 | +d->rng &= 0xFFFF; | |
| 919 | +if (d->rng >= 0x8000) d->rng -= 0x10000; | |
| 920 | +#+END_SRC | |
| 921 | + | |
| 922 | +This new random value is used to produce the next random | |
| 923 | +value in seconds. | |
| 924 | + | |
| 925 | +The line counter is reset. This value comes from the | |
| 926 | +high-level parameter. | |
| 927 | + | |
| 928 | +#+NAME: bigverb_delay | |
| 929 | +#+BEGIN_SRC c | |
| 930 | +int maxcount; | |
| 931 | +#+END_SRC | |
| 932 | + | |
| 933 | +#+NAME: init_jitter | |
| 934 | +#+BEGIN_SRC c | |
| 935 | +d->maxcount = round((sr / ((SKFLT)p->randfreq * 0.001))); | |
| 936 | +#+END_SRC | |
| 937 | + | |
| 938 | +#+NAME: reset_counter | |
| 939 | +#+BEGIN_SRC c | |
| 940 | +d->counter = d->maxcount; | |
| 941 | +#+END_SRC | |
| 942 | + | |
| 943 | +Compute delay time values. The current delay time, | |
| 944 | +=curdel=, is obtained by subtracting the write + integer | |
| 945 | +read positions, then adding in the fractional component. | |
| 946 | +Wraparound is applied. | |
| 947 | + | |
| 948 | +#+NAME: compute_delay_values | |
| 949 | +#+BEGIN_SRC c | |
| 950 | +curdel = d->wpos - | |
| 951 | + (d->irpos + (d->frpos/(SKFLT)FRACSCALE)); | |
| 952 | +while (curdel < 0) curdel += d->sz; | |
| 953 | +curdel /= sr; | |
| 954 | +#+END_SRC | |
| 955 | + | |
| 956 | +The next delay time to lerp to is derived from the RNG | |
| 957 | +and drift amount. | |
| 958 | + | |
| 959 | +#+NAME: compute_delay_values | |
| 960 | +#+BEGIN_SRC c | |
| 961 | +nxtdel = (d->rng * (d->drift * 0.0001) / 32768.0) + d->dels; | |
| 962 | +#+END_SRC | |
| 963 | + | |
| 964 | +The delay time, in seconds (=dels=). | |
| 965 | + | |
| 966 | +#+NAME: bigverb_delay | |
| 967 | +#+BEGIN_SRC c | |
| 968 | +SKFLT dels; | |
| 969 | +#+END_SRC | |
| 970 | + | |
| 971 | +#+NAME: init_jitter | |
| 972 | +#+BEGIN_SRC c | |
| 973 | +d->dels = p->delay / 44100.0; | |
| 974 | +#+END_SRC | |
| 975 | + | |
| 976 | +#+NAME: bigverb_delay | |
| 977 | +#+BEGIN_SRC c | |
| 978 | +SKFLT drift; | |
| 979 | +#+END_SRC | |
| 980 | + | |
| 981 | +#+NAME: init_jitter | |
| 982 | +#+BEGIN_SRC c | |
| 983 | +d->drift = p->drift; | |
| 984 | +#+END_SRC | |
| 985 | + | |
| 986 | +The linear increment value is the difference between the current | |
| 987 | +and next delay times, divided by the number of steps needed | |
| 988 | +to draw a line between them (=counter=). This value is then | |
| 989 | +converted into samples. An extra sample is tacked on to | |
| 990 | +prevent nil values. | |
| 991 | + | |
| 992 | +#+NAME: compute_increment | |
| 993 | +#+BEGIN_SRC c | |
| 994 | +inc = ((curdel - nxtdel) / (SKFLT)d->counter)*sr; | |
| 995 | +inc += 1; | |
| 996 | +#+END_SRC | |
| 997 | + | |
| 998 | +This increment value is truncated and converted to the | |
| 999 | +fractional read. | |
| 1000 | + | |
| 1001 | +#+NAME: set_fractional_read | |
| 1002 | +#+BEGIN_SRC c | |
| 1003 | +d->inc = floor(inc * FRACSCALE); | |
| 1004 | +#+END_SRC | |
| 1005 | + | |
| 1006 | +#+NAME: funcs | |
| 1007 | +#+BEGIN_SRC c | |
| 1008 | +static void generate_next_line(sk_bigverb_delay *d, int sr) | |
| 1009 | +{ | |
| 1010 | + SKFLT curdel; | |
| 1011 | + SKFLT nxtdel; | |
| 1012 | + SKFLT inc; | |
| 1013 | + <<generate_random_number>> | |
| 1014 | + <<reset_counter>> | |
| 1015 | + <<compute_delay_values>> | |
| 1016 | + <<compute_increment>> | |
| 1017 | + <<set_fractional_read>> | |
| 1018 | +} | |
| 1019 | +#+END_SRC | |
| 1020 | +*** Filter Memory | |
| 1021 | +A one-pole lowpass filter such as the one used in the delay | |
| 1022 | +line requires one sample of memory, which stores the output | |
| 1023 | +of the previous filter. In a difference equation, this would | |
| 1024 | +be known as $y(n - 1)$. In C code, we abbreviate this as | |
| 1025 | +=y=. | |
| 1026 | + | |
| 1027 | +#+NAME: bigverb_delay | |
| 1028 | +#+BEGIN_SRC c | |
| 1029 | +SKFLT y; | |
| 1030 | +#+END_SRC | |
| 1031 | + | |
| 1032 | +#+NAME: delay_init | |
| 1033 | +#+BEGIN_SRC c | |
| 1034 | +d->y = 0.0; | |
| 1035 | +#+END_SRC | |
| @@ -0,0 +1,118 @@ | ||
| 1 | +#+TITLE: DC Blocker | |
| 2 | +* Introduction | |
| 3 | +This document outlines an implementation of a DC Blocker, a | |
| 4 | +typical utility used to block out what is known as DC offset. | |
| 5 | +DC Blockers are occasionally used to clean up recorded | |
| 6 | +audio, but they are more often seen handling digital | |
| 7 | +algorithms that tend to naturally introduce a lot of DC | |
| 8 | +into their signal. This is common in the physical modelling | |
| 9 | +family of DSP algorithms (Karplus-Strongs, waveguides, | |
| 10 | +FDN reverberators, etc). | |
| 11 | + | |
| 12 | +Without knowing how a DC blocker works, it would seem to have | |
| 13 | +magical characteristics. A DC blocker has the ability to | |
| 14 | +"drag down" a waveform of audio with DC offset so that it is | |
| 15 | +centered. But how does it know how much to drag? | |
| 16 | + | |
| 17 | +In truth, a DC blocker is typically nothing more than | |
| 18 | +a recursive highpass filter whose cutoff set to be a | |
| 19 | +sub-audio frequency. This has the effect of removing any | |
| 20 | +slow moving waveforms that often are the culrpits of | |
| 21 | +unwanted DC biasing. | |
| 22 | +* Difference Equation | |
| 23 | +The difference equation for a | |
| 24 | +[[https://ccrma.stanford.edu/~jos/filters/DC_Blocker.html][DC blocker]] looks like this: | |
| 25 | + | |
| 26 | +$$ | |
| 27 | +y(n) = x(n) - x(n - 1) + Ry(n - 1) | |
| 28 | +$$ | |
| 29 | + | |
| 30 | +Where $R$ is usually a constant between 0.9 and 1. | |
| 31 | + | |
| 32 | +This difference equation outlines the whole darn algorithm! | |
| 33 | +What will be needed is a function that takes in an input, | |
| 34 | +and stores the previous input, output, as well as the | |
| 35 | +constant $R$. And that's it. | |
| 36 | +* Tangled Files | |
| 37 | +This program outputs two files: =dcblocker.c= and | |
| 38 | +=dcblocker.h=. Using the macro =SK_DCBLOCKER_PRIV= will | |
| 39 | +expose the main struct. Otherwise, it is opaque. | |
| 40 | + | |
| 41 | +#+NAME: dcblocker.c | |
| 42 | +#+BEGIN_SRC c :tangle dcblocker.c | |
| 43 | +#define SK_DCBLOCKER_PRIV | |
| 44 | +#include "dcblocker.h" | |
| 45 | +<<funcs>> | |
| 46 | +#+END_SRC | |
| 47 | + | |
| 48 | +#+NAME: dcblocker.h | |
| 49 | +#+BEGIN_SRC c :tangle dcblocker.h | |
| 50 | +#ifndef SK_DCBLOCKER_H | |
| 51 | +#define SK_DCBLOCKER_H | |
| 52 | +#ifndef SKFLT | |
| 53 | +#define SKFLT float | |
| 54 | +#endif | |
| 55 | +<<typedefs>> | |
| 56 | +#ifdef SK_DCBLOCKER_PRIV | |
| 57 | +<<structs>> | |
| 58 | +#endif | |
| 59 | +<<funcdefs>> | |
| 60 | +#endif | |
| 61 | +#+END_SRC | |
| 62 | +* Struct Definition | |
| 63 | +The main struct for this DC blocker will be called | |
| 64 | +=sk_dcblocker=. It only needs three parameters: an | |
| 65 | +=x= value storing the previous input value, a =y= value | |
| 66 | +storing the previous output value, and an =R= value | |
| 67 | +storing the DC filter constant. | |
| 68 | + | |
| 69 | +#+NAME: typedefs | |
| 70 | +#+BEGIN_SRC c | |
| 71 | +typedef struct sk_dcblocker sk_dcblocker; | |
| 72 | +#+END_SRC | |
| 73 | +#+NAME: structs | |
| 74 | +#+BEGIN_SRC c | |
| 75 | +struct sk_dcblocker { | |
| 76 | + SKFLT x, y, R; | |
| 77 | +}; | |
| 78 | +#+END_SRC | |
| 79 | +* Initialization | |
| 80 | +The =sk_dcblocker= can be initialized with | |
| 81 | +=sk_dcblocker_init=. It zeros out =x= and =y=, and sets | |
| 82 | +=R= to be a value of =0.99=, which is a reasonable DC | |
| 83 | +coefficent value. | |
| 84 | + | |
| 85 | +#+NAME: funcdefs | |
| 86 | +#+BEGIN_SRC c | |
| 87 | +void sk_dcblocker_init(sk_dcblocker *dcblk); | |
| 88 | +#+END_SRC | |
| 89 | + | |
| 90 | +#+NAME: funcs | |
| 91 | +#+BEGIN_SRC c | |
| 92 | +void sk_dcblocker_init(sk_dcblocker *dcblk) | |
| 93 | +{ | |
| 94 | + dcblk->x = 0; | |
| 95 | + dcblk->y = 0; | |
| 96 | + dcblk->R = 0.99; /* quite reasonable, indeed! */ | |
| 97 | +} | |
| 98 | +#+END_SRC | |
| 99 | +* Computation | |
| 100 | +A single sample of audio is processed with the function | |
| 101 | +=sk_dcblocker_tick=. It performs the arithmetic for the | |
| 102 | +difference equation, updates the internal values, then | |
| 103 | +returns the output. | |
| 104 | + | |
| 105 | +#+NAME: funcdefs | |
| 106 | +#+BEGIN_SRC c | |
| 107 | +SKFLT sk_dcblocker_tick(sk_dcblocker *dcblk, SKFLT in); | |
| 108 | +#+END_SRC | |
| 109 | + | |
| 110 | +#+NAME: funcs | |
| 111 | +#+BEGIN_SRC c | |
| 112 | +SKFLT sk_dcblocker_tick(sk_dcblocker *dcblk, SKFLT in) | |
| 113 | +{ | |
| 114 | + dcblk->y = in - dcblk->x + dcblk->R*dcblk->y; | |
| 115 | + dcblk->x = in; | |
| 116 | + return dcblk->y; | |
| 117 | +} | |
| 118 | +#+END_SRC | |
| @@ -0,0 +1,576 @@ | ||
| 1 | +#+TITLE: FM Pair Oscillator | |
| 2 | +* Introduction | |
| 3 | +This document outlines an implementation of a classic | |
| 4 | +FM oscillator with frequency, C:M ratio, and modulation | |
| 5 | +index control. | |
| 6 | + | |
| 7 | +There is quite a lot to be said about FM synthesis, but a | |
| 8 | +classic FM oscillator implementation is poetic tribute to | |
| 9 | +the technique. Requiring only two oscillators (usually sine | |
| 10 | +waves), an FM operator pair is capable of generating very | |
| 11 | +rich and dynamic spectrums. For how little computation is | |
| 12 | +required, these suckers can pack a punch! | |
| 13 | + | |
| 14 | +The FM pair is the Bechamel sauce of the FM world. Creating | |
| 15 | +one from scratch provides great sonic intution into the | |
| 16 | +mostly unintuitive world of FM synthesis programming. It can | |
| 17 | +be the building block for grokking more complex modulation | |
| 18 | +configurations, such as those found on the DX7. | |
| 19 | +* Some words on FM synthesis | |
| 20 | +(I can't help but ramble a little bit about the topic of | |
| 21 | +FM synthesis. Feel free to scroll/skip/page ahead.) | |
| 22 | + | |
| 23 | +FM synthesis, or Frequently Modulation synthesis, was first | |
| 24 | +implemented by John Chowning while doing research at the | |
| 25 | +Stanford Artificial Intelligence Laboratory (SAIL, whose | |
| 26 | +spritual successor would become CCRMA, or the Center for | |
| 27 | +Computer Research in Music and Acoustics). As the legend | |
| 28 | +goes, Chowning stumbled upon the technique by accident. | |
| 29 | +Experimenting with different vibrato speeds, he found that | |
| 30 | +when vibrato got fast enough, it started changing timbre | |
| 31 | +and spectrum. | |
| 32 | + | |
| 33 | +The humble beginings of FM synthesis started out with an FM | |
| 34 | +pair configuration similar to this one. But can you imagine | |
| 35 | +the surprise he got when he first heard that new FM sound? | |
| 36 | + | |
| 37 | +To paint a picture: | |
| 38 | + | |
| 39 | +At the time, computers were about the | |
| 40 | +size of fridges. They were expensive devices owned by the | |
| 41 | +University, and time spent using the computer was precious. | |
| 42 | +Chowing no doubt had to fight to get his time slot. Being | |
| 43 | +a composer amongst scientists and engineers, he probably | |
| 44 | +could only work in late hours. | |
| 45 | + | |
| 46 | +Typing away at a mainframe 3am, Chowning would have to had | |
| 47 | +to wait at least a day for the computer to run his FM | |
| 48 | +synthesis program. Assuming it successfully ran without any | |
| 49 | +errors, Chowning would then have to ship the output of that | |
| 50 | +program off somewhere to get it converted to magnetic | |
| 51 | +reel-to-reel tape. Back then, | |
| 52 | +digital-to-analog converers (DACs) were things that you had | |
| 53 | +to get in a car and drive to. Now-a-days, audio DACs are | |
| 54 | +things found on pretty much every electronic device you own. | |
| 55 | + | |
| 56 | +So, if you're keeping track, that's at least a week from | |
| 57 | +code to sound. That's an enormous amount of round-trip | |
| 58 | +latency! To go that long, and to finally hear these new | |
| 59 | +sounds must have been such a rewarding experience. | |
| 60 | + | |
| 61 | +Chowing's original FM synthesis algorithm would eventually | |
| 62 | +become one of Stanford University's highest grossing patents | |
| 63 | +of all time. To this day, the royalties of this patent help | |
| 64 | +to sustain the CCRMA facilities. | |
| 65 | + | |
| 66 | +Famously, the patent was sold to Yamaha, and that gave us | |
| 67 | +things like the DX7 synthesizer (and all the cheesy music | |
| 68 | +that came with it). Due to the efficiency of | |
| 69 | +the algorithm, it also found itself in a whole generation of | |
| 70 | +sound cards at the time, used in video game consoles and | |
| 71 | +arcade machines. The most famous of these chips was the | |
| 72 | +[[https://en.wikipedia.org/wiki/Yamaha_YM2151][YM2151]], also known as the OPM chip. | |
| 73 | +* Overview of Algorithm and Implementation | |
| 74 | +An FM Pair uses two oscillators, sometimes referred two as | |
| 75 | +=operators=. One of these operators synthesizes audble sound. | |
| 76 | +This known as the =carrier=, and can be remembered because | |
| 77 | +it *carries* the sound signal to the speaker. The other | |
| 78 | +operator *modulates* the frequency of the carrier, and it | |
| 79 | +is known as the =modulator=. | |
| 80 | + | |
| 81 | +If you set the modulator to any frequency that is audio rate | |
| 82 | +(give or take 20hz or higher), you will technically get some | |
| 83 | +kind of FM synthesis. However, depending on the frequency of | |
| 84 | +the carrier, the spectrum can range from being very pitched | |
| 85 | +with harmonically related overtones, to something quite | |
| 86 | +clangorous and not at all something you could easily play | |
| 87 | +"Hot Cross Buns" on. So, to make an FM pair easier to use, | |
| 88 | +three unit-less parameters are used to control the | |
| 89 | +FM characteristics of the sound that work evenly across | |
| 90 | +the spectrum: a carrier-to-modulator frequency ratio known | |
| 91 | +as the C:M ratio, and something known as the modulation | |
| 92 | +index, which can kind of be thought of as a thing that | |
| 93 | +controls the amount of modulation. | |
| 94 | + | |
| 95 | +The C and M parameters are values that primarily multiply | |
| 96 | +the frequency value of the carrier and modulator operators, | |
| 97 | +respectively. | |
| 98 | + | |
| 99 | +The modulation index can be defined as having the following | |
| 100 | +relationship (taken from the Computer Music Tutorial's | |
| 101 | +chapter on Modulation synthesis, page 229): | |
| 102 | + | |
| 103 | +$$ I = D/M $$ | |
| 104 | + | |
| 105 | +Where $I$ is the modulation index, $M$ is the modulation | |
| 106 | +frequency (which would be the oscillator frequency multiplied | |
| 107 | +by the modulator ratio), and $D$ is the amount of modulation | |
| 108 | +=depth=. The equation can be reworked in terms of $D$: | |
| 109 | + | |
| 110 | +$$ D = MI $$ | |
| 111 | + | |
| 112 | +This depth value can be thought of as the "amplitude" amount | |
| 113 | +the modulator operator (in Hz). This will produce a signal | |
| 114 | +that modulates the carrier frequency up the depth amount, | |
| 115 | +and then down that same amount. | |
| 116 | + | |
| 117 | +In this implemetation, two table-lookup oscillators will be | |
| 118 | +used for the operators, using an algorithm similar to the | |
| 119 | +@!(ref "osc" "table lookup oscillator")!@, which employ a | |
| 120 | +fixed-point phasor with linear interpolation. Details for | |
| 121 | +the gory details of these oscillators are beyond the scope | |
| 122 | +of this article, but are described in the implementation | |
| 123 | +referenced above. | |
| 124 | +* Tangled Files | |
| 125 | +=fmpair.c= contains the ANSI C code required for the | |
| 126 | +implementation. | |
| 127 | + | |
| 128 | +#+NAME: fmpair.c | |
| 129 | +#+BEGIN_SRC c :tangle fmpair.c | |
| 130 | +#include <math.h> | |
| 131 | +#define SK_FMPAIR_PRIV | |
| 132 | +#include "fmpair.h" | |
| 133 | +<<constants>> | |
| 134 | +<<funcs>> | |
| 135 | +#+END_SRC | |
| 136 | + | |
| 137 | +=fmpair.h= is the corresponding header file for the C file. | |
| 138 | +It contains all the forward declarations. If | |
| 139 | +=SK_FMPAIR_PRIV= is defined, it exposes the structs. | |
| 140 | + | |
| 141 | +#+NAME: fmpair.h | |
| 142 | +#+BEGIN_SRC c :tangle fmpair.h | |
| 143 | +#ifndef SK_FMPAIR_H | |
| 144 | +#define SK_FMPAIR_H | |
| 145 | +#ifndef SKFLT | |
| 146 | +#define SKFLT float | |
| 147 | +#endif | |
| 148 | +<<typedefs>> | |
| 149 | +<<funcdefs>> | |
| 150 | +#ifdef SK_FMPAIR_PRIV | |
| 151 | +<<structs>> | |
| 152 | +#endif | |
| 153 | +#endif | |
| 154 | +#+END_SRC | |
| 155 | +* Struct | |
| 156 | +#+NAME: typedefs | |
| 157 | +#+BEGIN_SRC c | |
| 158 | +typedef struct sk_fmpair sk_fmpair; | |
| 159 | +#+END_SRC | |
| 160 | + | |
| 161 | +#+NAME: structs | |
| 162 | +#+BEGIN_SRC c | |
| 163 | +struct sk_fmpair { | |
| 164 | + <<sk_fmpair>> | |
| 165 | +}; | |
| 166 | +#+END_SRC | |
| 167 | +* Table-lookup oscillator data and constants | |
| 168 | +** Constants | |
| 169 | +The constant =SK_FMPAIR_MAXLEN= is the maximum length a | |
| 170 | +lookup table can be. | |
| 171 | + | |
| 172 | +The constant =SK_FMPAIR_PHASEMASK= is a bitmask used by | |
| 173 | +the fixed point phasor. | |
| 174 | +#+NAME: constants | |
| 175 | +#+BEGIN_SRC c | |
| 176 | +#define SK_FMPAIR_MAXLEN 0x1000000L | |
| 177 | +#define SK_FMPAIR_PHASEMASK 0x0FFFFFFL | |
| 178 | +#+END_SRC | |
| 179 | +** Struct Data | |
| 180 | +The FM oscillator implements two independent table-[ookup | |
| 181 | +oscillators. Each one needs their own set of wavetables, | |
| 182 | +incrementors, sizes, and cached phase positions. | |
| 183 | + | |
| 184 | +The convention used will this: any variables used for the | |
| 185 | +carrier oscillator will begin with 'c': | |
| 186 | + | |
| 187 | +#+NAME: sk_fmpair | |
| 188 | +#+BEGIN_SRC c | |
| 189 | +SKFLT *ctab; | |
| 190 | +int csz; | |
| 191 | +int clphs; | |
| 192 | +#+END_SRC | |
| 193 | + | |
| 194 | +#+NAME: init | |
| 195 | +#+BEGIN_SRC c | |
| 196 | +fmp->ctab = ctab; | |
| 197 | +fmp->csz = msz; | |
| 198 | +#+END_SRC | |
| 199 | + | |
| 200 | +Any variables used for the modulator oscillator will begin | |
| 201 | +with 'm': | |
| 202 | + | |
| 203 | +#+NAME: sk_fmpair | |
| 204 | +#+BEGIN_SRC c | |
| 205 | +SKFLT *mtab; | |
| 206 | +int msz; | |
| 207 | +int mlphs; | |
| 208 | +#+END_SRC | |
| 209 | + | |
| 210 | +#+NAME: init | |
| 211 | +#+BEGIN_SRC c | |
| 212 | +fmp->mtab = mtab; | |
| 213 | +fmp->msz = msz; | |
| 214 | +#+END_SRC | |
| 215 | + | |
| 216 | +In order to be used by the fixed point phasor, | |
| 217 | +the initial phases need to be rescaled by =SK_FMPAIR_MAXLEN= | |
| 218 | +and then converted to an integer value. These are | |
| 219 | +stored in each respective variable keeping track | |
| 220 | +of phase. | |
| 221 | + | |
| 222 | +#+NAME: init | |
| 223 | +#+BEGIN_SRC c | |
| 224 | +fmp->clphs = floor(ciphs * SK_FMPAIR_MAXLEN); | |
| 225 | +fmp->mlphs = floor(miphs * SK_FMPAIR_MAXLEN); | |
| 226 | +#+END_SRC | |
| 227 | + | |
| 228 | +A number of constants are used with these tables: =nlb=, | |
| 229 | +=inlb=, =mask=, and =maxlens=. These are beyond the scope | |
| 230 | +of this document, but are touched upon in @!(ref "osc")!@. | |
| 231 | +Since these are all based on table size, two sets of | |
| 232 | +constants are derived for the carrier and modulator. | |
| 233 | + | |
| 234 | +#+NAME: sk_fmpair | |
| 235 | +#+BEGIN_SRC c | |
| 236 | +/* carrier constants */ | |
| 237 | +int cnlb; | |
| 238 | +SKFLT cinlb; | |
| 239 | +unsigned long cmask; | |
| 240 | + | |
| 241 | +/* modulator constants */ | |
| 242 | +int mnlb; | |
| 243 | +SKFLT minlb; | |
| 244 | +unsigned long mmask; | |
| 245 | + | |
| 246 | +SKFLT maxlens; | |
| 247 | +#+END_SRC | |
| 248 | + | |
| 249 | +#+NAME: init | |
| 250 | +#+BEGIN_SRC c | |
| 251 | +{ | |
| 252 | + int tmp; | |
| 253 | + | |
| 254 | + /* carrier */ | |
| 255 | + tmp = SK_FMPAIR_MAXLEN / csz; | |
| 256 | + fmp->cnlb = 0; | |
| 257 | + while (tmp >>= 1) fmp->cnlb++; | |
| 258 | + | |
| 259 | + /* modulator */ | |
| 260 | + tmp = SK_FMPAIR_MAXLEN / msz; | |
| 261 | + fmp->mnlb = 0; | |
| 262 | + while (tmp >>= 1) fmp->mnlb++; | |
| 263 | +} | |
| 264 | + | |
| 265 | +/* phase mask for dividing lower/upper bits */ | |
| 266 | + | |
| 267 | +fmp->cmask = (1<<fmp->cnlb) - 1; | |
| 268 | +fmp->mmask = (1<<fmp->mnlb) - 1; | |
| 269 | + | |
| 270 | +/* constant used to convert to floating point */ | |
| 271 | + | |
| 272 | +fmp->cinlb = 1.0 / (1<<fmp->cnlb); | |
| 273 | +fmp->minlb = 1.0 / (1<<fmp->mnlb); | |
| 274 | + | |
| 275 | +/* max table length in seconds */ | |
| 276 | +/* used to convert cycles-per-second units to cycles */ | |
| 277 | + | |
| 278 | +fmp->maxlens = 1.0 * SK_FMPAIR_MAXLEN / sr; | |
| 279 | +#+END_SRC | |
| 280 | +* Initialization | |
| 281 | +The FM oscillator is initialize with =sk_fmpair_init=. | |
| 282 | +It needs the sampling rate =sr=, and two sets of | |
| 283 | +wavetables (=cwt= and =mwt=), | |
| 284 | +their sizes (=csz=) and (=msz=), and | |
| 285 | +initial phases (=ciphs= and =miphs=) for the carrier (c) | |
| 286 | +and modulator (m) oscillators. | |
| 287 | + | |
| 288 | +In a classic FM pair, these would be identical, | |
| 289 | +and the wavetable would contain a sampled period of a | |
| 290 | +sine wave. | |
| 291 | + | |
| 292 | +#+NAME: funcdefs | |
| 293 | +#+BEGIN_SRC c | |
| 294 | +void sk_fmpair_init(sk_fmpair *fmp, int sr, | |
| 295 | + SKFLT *ctab, int csz, SKFLT ciphs, | |
| 296 | + SKFLT *mtab, int msz, SKFLT miphs); | |
| 297 | +#+END_SRC | |
| 298 | + | |
| 299 | +#+NAME: funcs | |
| 300 | +#+BEGIN_SRC c | |
| 301 | +void sk_fmpair_init(sk_fmpair *fmp, int sr, | |
| 302 | + SKFLT *ctab, int csz, SKFLT ciphs, | |
| 303 | + SKFLT *mtab, int msz, SKFLT miphs) | |
| 304 | +{ | |
| 305 | + <<init>> | |
| 306 | +} | |
| 307 | +#+END_SRC | |
| 308 | +* Parameter Control | |
| 309 | +** Frequency | |
| 310 | +Set with =sk_fmpair_freq=. | |
| 311 | + | |
| 312 | +#+NAME: funcdefs | |
| 313 | +#+BEGIN_SRC c | |
| 314 | +void sk_fmpair_freq(sk_fmpair *fmp, SKFLT freq); | |
| 315 | +#+END_SRC | |
| 316 | + | |
| 317 | +#+NAME: funcs | |
| 318 | +#+BEGIN_SRC c | |
| 319 | +void sk_fmpair_freq(sk_fmpair *fmp, SKFLT freq) | |
| 320 | +{ | |
| 321 | + fmp->freq = freq; | |
| 322 | +} | |
| 323 | +#+END_SRC | |
| 324 | + | |
| 325 | +#+NAME: sk_fmpair | |
| 326 | +#+BEGIN_SRC c | |
| 327 | +SKFLT freq; | |
| 328 | +#+END_SRC | |
| 329 | + | |
| 330 | +A sensible default of A440. | |
| 331 | + | |
| 332 | +#+NAME: init | |
| 333 | +#+BEGIN_SRC c | |
| 334 | +sk_fmpair_freq(fmp, 440); | |
| 335 | +#+END_SRC | |
| 336 | +** Carrier/Modulator Ratios | |
| 337 | +Set with =sk_fmpair_modulator= and =sk_fmpair_carrier=. | |
| 338 | + | |
| 339 | +#+NAME: funcdefs | |
| 340 | +#+BEGIN_SRC c | |
| 341 | +void sk_fmpair_modulator(sk_fmpair *fmp, SKFLT mod); | |
| 342 | +void sk_fmpair_carrier(sk_fmpair *fmp, SKFLT car); | |
| 343 | +#+END_SRC | |
| 344 | + | |
| 345 | +#+NAME: funcs | |
| 346 | +#+BEGIN_SRC c | |
| 347 | +void sk_fmpair_modulator(sk_fmpair *fmp, SKFLT mod) | |
| 348 | +{ | |
| 349 | + fmp->mod = mod; | |
| 350 | +} | |
| 351 | + | |
| 352 | +void sk_fmpair_carrier(sk_fmpair *fmp, SKFLT car) | |
| 353 | +{ | |
| 354 | + fmp->car = car; | |
| 355 | +} | |
| 356 | +#+END_SRC | |
| 357 | + | |
| 358 | +#+NAME: sk_fmpair | |
| 359 | +#+BEGIN_SRC c | |
| 360 | +SKFLT car; | |
| 361 | +SKFLT mod; | |
| 362 | +#+END_SRC | |
| 363 | + | |
| 364 | +A 1:1 ratio is a good strong and sensible default. | |
| 365 | + | |
| 366 | +#+NAME: init | |
| 367 | +#+BEGIN_SRC c | |
| 368 | +sk_fmpair_carrier(fmp, 1); | |
| 369 | +sk_fmpair_modulator(fmp, 1); | |
| 370 | +#+END_SRC | |
| 371 | +** Modulation Index | |
| 372 | +Set with =sk_fmpair_modindex=. | |
| 373 | + | |
| 374 | +#+NAME: funcdefs | |
| 375 | +#+BEGIN_SRC c | |
| 376 | +void sk_fmpair_modindex(sk_fmpair *fmp, SKFLT index); | |
| 377 | +#+END_SRC | |
| 378 | + | |
| 379 | +#+NAME: funcs | |
| 380 | +#+BEGIN_SRC c | |
| 381 | +void sk_fmpair_modindex(sk_fmpair *fmp, SKFLT index) | |
| 382 | +{ | |
| 383 | + fmp->index = index; | |
| 384 | +} | |
| 385 | +#+END_SRC | |
| 386 | + | |
| 387 | +#+NAME: sk_fmpair | |
| 388 | +#+BEGIN_SRC c | |
| 389 | +SKFLT index; | |
| 390 | +#+END_SRC | |
| 391 | + | |
| 392 | +A modulation index of 1 on with 1:1 C-to-M ratio produces | |
| 393 | +a very warm and versatile initial sound. | |
| 394 | + | |
| 395 | +#+NAME: init | |
| 396 | +#+BEGIN_SRC c | |
| 397 | +sk_fmpair_modindex(fmp, 1); | |
| 398 | +#+END_SRC | |
| 399 | +* Computing a Sample | |
| 400 | +A single sample of audio is computed with =sk_fmpair_tick=. | |
| 401 | + | |
| 402 | +#+NAME: funcdefs | |
| 403 | +#+BEGIN_SRC c | |
| 404 | +SKFLT sk_fmpair_tick(sk_fmpair *fmp); | |
| 405 | +#+END_SRC | |
| 406 | + | |
| 407 | +#+NAME: funcs | |
| 408 | +#+BEGIN_SRC c | |
| 409 | +SKFLT sk_fmpair_tick(sk_fmpair *fmp) | |
| 410 | +{ | |
| 411 | + SKFLT out; | |
| 412 | + SKFLT cfreq, mfreq; | |
| 413 | + SKFLT modout; | |
| 414 | + int ipos; | |
| 415 | + SKFLT frac; | |
| 416 | + SKFLT x[2]; | |
| 417 | + out = 0; | |
| 418 | + <<calculate_frequencies>> | |
| 419 | + <<table_lookup_for_modulator>> | |
| 420 | + <<scale_modulator_output>> | |
| 421 | + <<modulate_carrier_frequency>> | |
| 422 | + <<table_lookup_for_carrier>> | |
| 423 | + <<update_phase_positions>> | |
| 424 | + return out; | |
| 425 | +} | |
| 426 | +#+END_SRC | |
| 427 | + | |
| 428 | +The carrier and modulator frequencies are calculated by | |
| 429 | +multiplying their respective ratio values with | |
| 430 | +the oscillator frequency. | |
| 431 | + | |
| 432 | +#+NAME: calculate_frequencies | |
| 433 | +#+BEGIN_SRC c | |
| 434 | +cfreq = fmp->freq * fmp->car; | |
| 435 | +mfreq = fmp->freq * fmp->mod; | |
| 436 | +#+END_SRC | |
| 437 | + | |
| 438 | +The modulator oscillator does its table-lookup and | |
| 439 | +linear interpolation with some bitwise magic. The details of | |
| 440 | +this can be found in @!(ref "osc")!@. | |
| 441 | + | |
| 442 | +#+NAME: table_lookup_for_modulator | |
| 443 | +#+BEGIN_SRC c | |
| 444 | +fmp->mlphs &= SK_FMPAIR_PHASEMASK; | |
| 445 | +ipos = fmp->mlphs >> fmp->mnlb; | |
| 446 | +x[0] = fmp->mtab[ipos]; | |
| 447 | + | |
| 448 | +if (ipos == fmp->msz - 1) { | |
| 449 | + x[1] = fmp->mtab[0]; | |
| 450 | +} else { | |
| 451 | + x[1] = fmp->mtab[ipos + 1]; | |
| 452 | +} | |
| 453 | + | |
| 454 | +frac = (fmp->mlphs & fmp->mmask) * fmp->minlb; | |
| 455 | +modout = (x[0] + (x[1] - x[0]) * frac); | |
| 456 | +#+END_SRC | |
| 457 | + | |
| 458 | +Before it can modulate the carrier, the output of the | |
| 459 | +modulator needs to be appropriately scaled. This amplitude | |
| 460 | +amount is the modulator frequency and modulation | |
| 461 | +index multiplied together. | |
| 462 | + | |
| 463 | +#+NAME: scale_modulator_output | |
| 464 | +#+BEGIN_SRC c | |
| 465 | +modout *= mfreq * fmp->index; | |
| 466 | +#+END_SRC | |
| 467 | + | |
| 468 | +The frequency of the carrier oscillator is now ready to be | |
| 469 | +modulated. Modulation is an addition operation; The output | |
| 470 | +of the scaled modulator is tacked on the carrier frequency. | |
| 471 | + | |
| 472 | +#+NAME: modulate_carrier_frequency | |
| 473 | +#+BEGIN_SRC c | |
| 474 | +cfreq += modout; | |
| 475 | +#+END_SRC | |
| 476 | + | |
| 477 | +A table-lookup operation computation happens for the | |
| 478 | +carrier oscillator, similar to what happened with the | |
| 479 | +modulator oscillator. This output is what gets returned. | |
| 480 | + | |
| 481 | +#+NAME: table_lookup_for_carrier | |
| 482 | +#+BEGIN_SRC c | |
| 483 | +fmp->clphs &= SK_FMPAIR_PHASEMASK; | |
| 484 | +ipos = (fmp->clphs) >> fmp->cnlb; | |
| 485 | +x[0] = fmp->ctab[ipos]; | |
| 486 | + | |
| 487 | +if (ipos == fmp->csz - 1) { | |
| 488 | + x[1] = fmp->ctab[0]; | |
| 489 | +} else { | |
| 490 | + x[1] = fmp->ctab[ipos + 1]; | |
| 491 | +} | |
| 492 | + | |
| 493 | +frac = (fmp->clphs & fmp->cmask) * fmp->cinlb; | |
| 494 | +out = (x[0] + (x[1] - x[0]) * frac); | |
| 495 | +#+END_SRC | |
| 496 | + | |
| 497 | +To wrap things up, the phase increments and positions | |
| 498 | +of both oscillators are updated, based on their respective | |
| 499 | +frequencies. | |
| 500 | + | |
| 501 | +increment value is derived by multiplying oscillator | |
| 502 | +frequency (in units of cycles-per-second) to the maximum | |
| 503 | +length of the phasor (in units of seconds). The seconds unit | |
| 504 | +cancels, and the resulting output is cycles. This value, | |
| 505 | +truncated to an integer value, becomes the phase increment | |
| 506 | +value. | |
| 507 | + | |
| 508 | +#+NAME: update_phase_positions | |
| 509 | +#+BEGIN_SRC c | |
| 510 | +fmp->clphs += floor(cfreq * fmp->maxlens); | |
| 511 | +fmp->mlphs += floor(mfreq * fmp->maxlens); | |
| 512 | +#+END_SRC | |
| 513 | +* C:M Ratio tips | |
| 514 | +Some suggestions to get started with picking out good C,M | |
| 515 | +and I parameters. For those starting out, these should help | |
| 516 | +build some sonic intuition in FM. | |
| 517 | + | |
| 518 | +Generally speaking, the more rational C:M ratios are | |
| 519 | +(1:1, 1:2, 3:2, etc), the more harmonic and pitched the | |
| 520 | +spectrum will be. The more irrational they are | |
| 521 | +(1:1.6180339..., 1:3.1415926..., etc) the more clangorous | |
| 522 | +and unpitched the spectrum will be. | |
| 523 | + | |
| 524 | +Fractional or strange looking ratios that look irrational | |
| 525 | +can sometimes be simple ratios in disguise. For example, | |
| 526 | +1:0.5 is harmonically similar to 2:1. | |
| 527 | + | |
| 528 | +The carrier ratio value determines what the pitch will be. | |
| 529 | +Whole integer values correspond with the harmonic series. | |
| 530 | +A value of 1 is the base frequency, 2 is an octave, 3 is | |
| 531 | +an octave and a fifth, etc. | |
| 532 | + | |
| 533 | +The modulator ratio can be thought of as how spread-out | |
| 534 | +the harmonic spectrum will be. A 1:1 ratio will give you | |
| 535 | +a denser spectrum than a 1:7 ratio. These spread out | |
| 536 | +spectrums were what made classic DX7 keyboard tine sounds | |
| 537 | +work so well. Using ratios with high M values, they were | |
| 538 | +able to cut through a mix while also leaving room for other | |
| 539 | +instruments. | |
| 540 | + | |
| 541 | +Adding small fractional values to the M ratio (ex: 1:1.007) can | |
| 542 | +add some very unique spectral beating. FM pairs can be | |
| 543 | +layered together with different fractional amounts to create | |
| 544 | +thicker sounds. | |
| 545 | + | |
| 546 | +In an FM pair, the behavior of the modulation index can be | |
| 547 | +thought of as a kind of tone control, similar to a filter | |
| 548 | +cutoff control that you'd find in a subtractive synthesizer. | |
| 549 | +It's a gross simplification for all the amazing things | |
| 550 | +happening to the spectrum, but it's close enough. | |
| 551 | + | |
| 552 | +Controlling the modulation index of a 1:1 FM pair kind of | |
| 553 | +feels like controlling a subtractive synthesizer, especially | |
| 554 | +for lower modulation index values. | |
| 555 | + | |
| 556 | +FM sounds can alias like crazy, especially when frequency, | |
| 557 | +modulator, and modulation index values are high. When you | |
| 558 | +crank up the modulation index crazy high, you can get some | |
| 559 | +very interesting alias noises, but finding the sweet spots | |
| 560 | +can be a bit of a treasure hunt. The plugin | |
| 561 | +[[https://www.kvraudio.com/product/mr-alias-by-insert-piz-here][Mr.Alias]] is built around this concept. | |
| 562 | + | |
| 563 | +Keyboard/frequency scaling the modulation index is a Good | |
| 564 | +Idea. As frequencies get lower, the modulation index can | |
| 565 | +get larger without risk of aliasing. More modulation index | |
| 566 | +means more harmonic content, means more sonic beef for | |
| 567 | +lower notes. | |
| 568 | + | |
| 569 | +The modulation index spectrum behavior is determined by | |
| 570 | +[[https://en.wikipedia.org/wiki/Bessel_function#Bessel_functions_of_the_first_kind][Bessel Functions of the First kind]]. | |
| 571 | +The wiki page on this has a good chart of this that | |
| 572 | +plots the amplitudes first 3 harmonics. You can see how | |
| 573 | +ahey dip in and out. One should at least be passively aware | |
| 574 | +that this happens. Changing the modulation index on a bass | |
| 575 | +sound, for example, can sometimes cause the fundamental | |
| 576 | +to drop out, which can produces thin patches of sound. | |
| @@ -0,0 +1,21 @@ | ||
| 1 | +#+TITLE: sndkit | |
| 2 | +sndkit is a sonic toolkit for everywhere. | |
| 3 | + | |
| 4 | +It is a collection of highly portable ANSI-C implementations | |
| 5 | +of audio DSP algorithms commonly used in computer music, all | |
| 6 | +wrapped up inside of a statically generated wiki. | |
| 7 | + | |
| 8 | +Each DSP algorithm is written in a literate programming | |
| 9 | +style, a technique pioneered by Donald Knuth. Surrounding | |
| 10 | +the implementation itself are words and equations used to | |
| 11 | +help explain and contextualize what every single line of | |
| 12 | +code is doing. | |
| 13 | + | |
| 14 | +The text in this wiki is licensed under the | |
| 15 | +[[https://creativecommons.org/share-your-work/public-domain/cc0/][Creatives Commons Zero]] license, otherwise | |
| 16 | +known as "CC0". | |
| 17 | + | |
| 18 | +The tangled output is dual-licensed under MIT or | |
| 19 | +the [[https://unlicense.org/][Unlicense]]. | |
| 20 | + | |
| 21 | +Happy Hacking Humans. | |
| @@ -0,0 +1,306 @@ | ||
| 1 | +#+TITLE: ModalRes | |
| 2 | +* Overview | |
| 3 | +ModalRes is an implementation of a 2nd-order resonator | |
| 4 | +filter suitable for use in modal synthesis. | |
| 5 | + | |
| 6 | +This filter is particularly useful in constructing bell or | |
| 7 | +other impact-based sounds. In order to do this, you'd need | |
| 8 | +to know the modal ratios pre-measured of time. This is what | |
| 9 | +is known as black-box modelling. A similar example of this | |
| 10 | +is formant-based speech synthesis. | |
| 11 | + | |
| 12 | +Some good modal ratios and measurements can be found [[https://csound.com/docs/manual/MiscModalFreq.html][here]]. | |
| 13 | +* Tangled Files | |
| 14 | +=modalres.c= and =modalres.h= are the two files that are | |
| 15 | +tangled. | |
| 16 | + | |
| 17 | +#+NAME: modalres.c | |
| 18 | +#+BEGIN_SRC c :tangle modalres.c | |
| 19 | +#include <math.h> | |
| 20 | +#define SK_MODALRES_PRIV | |
| 21 | +#include "modalres.h" | |
| 22 | + | |
| 23 | +#ifndef M_PI | |
| 24 | +#define M_PI 3.14159265358979323846 | |
| 25 | +#endif | |
| 26 | + | |
| 27 | +<<funcs>> | |
| 28 | +#+END_SRC | |
| 29 | + | |
| 30 | +#+NAME: modalres.h | |
| 31 | +#+BEGIN_SRC c :tangle modalres.h | |
| 32 | +#ifndef SK_MODALRES_H | |
| 33 | +#define SK_MODALRES_H | |
| 34 | + | |
| 35 | +#ifndef SKFLT | |
| 36 | +#define SKFLT float | |
| 37 | +#endif | |
| 38 | + | |
| 39 | +<<typedefs>> | |
| 40 | +<<funcdefs>> | |
| 41 | + | |
| 42 | +#ifdef SK_MODALRES_PRIV | |
| 43 | +<<structs>> | |
| 44 | +#endif | |
| 45 | +#endif | |
| 46 | +#+END_SRC | |
| 47 | +* Initiazation | |
| 48 | +Initialized with =sk_modalres_init=. Only the sampling rate | |
| 49 | +needs to be supplied. | |
| 50 | + | |
| 51 | +#+NAME: funcdefs | |
| 52 | +#+BEGIN_SRC c | |
| 53 | +void sk_modalres_init(sk_modalres *mr, int sr); | |
| 54 | +#+END_SRC | |
| 55 | + | |
| 56 | +#+NAME: funcs | |
| 57 | +#+BEGIN_SRC c | |
| 58 | +void sk_modalres_init(sk_modalres *mr, int sr) | |
| 59 | +{ | |
| 60 | + <<init>> | |
| 61 | +} | |
| 62 | +#+END_SRC | |
| 63 | +* Struct | |
| 64 | +The encapsulating struct is called =sk_modalres=. | |
| 65 | + | |
| 66 | +#+NAME: typedefs | |
| 67 | +#+BEGIN_SRC c | |
| 68 | +typedef struct sk_modalres sk_modalres; | |
| 69 | +#+END_SRC | |
| 70 | + | |
| 71 | +#+NAME: structs | |
| 72 | +#+BEGIN_SRC c | |
| 73 | +struct sk_modalres { | |
| 74 | + <<sk_modalres>> | |
| 75 | +}; | |
| 76 | +#+END_SRC | |
| 77 | +* Filter State Variables and Coefficients | |
| 78 | +The filter state parameters are the typical things you'd | |
| 79 | +need in a biquadratic filter difference equation. | |
| 80 | + | |
| 81 | +Variables are created to store the previous x value and | |
| 82 | +2 y values. | |
| 83 | + | |
| 84 | +#+NAME: sk_modalres | |
| 85 | +#+BEGIN_SRC c | |
| 86 | +SKFLT x; | |
| 87 | +SKFLT y[2]; | |
| 88 | +#+END_SRC | |
| 89 | + | |
| 90 | +#+NAME: init | |
| 91 | +#+BEGIN_SRC c | |
| 92 | +mr->x = 0; | |
| 93 | +mr->y[0] = 0; | |
| 94 | +mr->y[1] = 0; | |
| 95 | +#+END_SRC | |
| 96 | + | |
| 97 | +Coefficients for the biquad are =b1= for the x value, and | |
| 98 | +=a1= and =a2= for the y values. These coefficents get | |
| 99 | +computed in the computation section. | |
| 100 | + | |
| 101 | +#+NAME: sk_modalres | |
| 102 | +#+BEGIN_SRC c | |
| 103 | +SKFLT b1; | |
| 104 | +SKFLT a1; | |
| 105 | +SKFLT a2; | |
| 106 | +#+END_SRC | |
| 107 | + | |
| 108 | +#+NAME: init | |
| 109 | +#+BEGIN_SRC c | |
| 110 | +mr->b1 = 0; | |
| 111 | +mr->a1 = 0; | |
| 112 | +mr->a2 = 0; | |
| 113 | +#+END_SRC | |
| 114 | + | |
| 115 | +As per usual, a copy of the sampling rate is stored, as it | |
| 116 | +is needed to calculate filter coefficients. | |
| 117 | + | |
| 118 | +#+NAME: sk_modalres | |
| 119 | +#+BEGIN_SRC c | |
| 120 | +int sr; | |
| 121 | +#+END_SRC | |
| 122 | + | |
| 123 | +#+NAME: init | |
| 124 | +#+BEGIN_SRC c | |
| 125 | +mr->sr = sr; | |
| 126 | +#+END_SRC | |
| 127 | + | |
| 128 | +An unusual part of this filter design is the scaler | |
| 129 | +variable, which is used to scale the audio output, but | |
| 130 | +is not part of the actual transer function. We'll call | |
| 131 | +this one =s=. | |
| 132 | + | |
| 133 | +#+NAME: sk_modalres | |
| 134 | +#+BEGIN_SRC c | |
| 135 | +SKFLT s; | |
| 136 | +#+END_SRC | |
| 137 | + | |
| 138 | +#+NAME: init | |
| 139 | +#+BEGIN_SRC c | |
| 140 | +mr->s = 0; | |
| 141 | +#+END_SRC | |
| 142 | +* Parameters | |
| 143 | +** Frequency | |
| 144 | +Set with =sk_modalres_freq=. | |
| 145 | + | |
| 146 | +#+NAME: funcdefs | |
| 147 | +#+BEGIN_SRC c | |
| 148 | +void sk_modalres_freq(sk_modalres *mr, SKFLT freq); | |
| 149 | +#+END_SRC | |
| 150 | + | |
| 151 | +#+NAME: funcs | |
| 152 | +#+BEGIN_SRC c | |
| 153 | +void sk_modalres_freq(sk_modalres *mr, SKFLT freq) | |
| 154 | +{ | |
| 155 | + mr->freq = freq; | |
| 156 | +} | |
| 157 | +#+END_SRC | |
| 158 | + | |
| 159 | +#+NAME: sk_modalres | |
| 160 | +#+BEGIN_SRC c | |
| 161 | +SKFLT freq; | |
| 162 | +SKFLT pfreq; | |
| 163 | +#+END_SRC | |
| 164 | + | |
| 165 | +The frequency parameter uses caching to check if filter | |
| 166 | +coefficients needs to be updated. The cached value is set | |
| 167 | +to be negative in order to force computation on the first | |
| 168 | +tick. | |
| 169 | + | |
| 170 | +#+NAME: init | |
| 171 | +#+BEGIN_SRC c | |
| 172 | +sk_modalres_freq(mr, 1000); | |
| 173 | +mr->pfreq = -1; | |
| 174 | +#+END_SRC | |
| 175 | +** Q value | |
| 176 | +Set with =sk_modalres_q=. | |
| 177 | + | |
| 178 | +#+NAME: funcdefs | |
| 179 | +#+BEGIN_SRC c | |
| 180 | +void sk_modalres_q(sk_modalres *mr, SKFLT q); | |
| 181 | +#+END_SRC | |
| 182 | + | |
| 183 | +#+NAME: funcs | |
| 184 | +#+BEGIN_SRC c | |
| 185 | +void sk_modalres_q(sk_modalres *mr, SKFLT q) | |
| 186 | +{ | |
| 187 | + mr->q = q; | |
| 188 | +} | |
| 189 | +#+END_SRC | |
| 190 | + | |
| 191 | +The Q parameter uses caching to check if filter | |
| 192 | +coefficients needs to be updated. The cached value | |
| 193 | +is set to be negative in order to force computation on | |
| 194 | +the initial tick. | |
| 195 | + | |
| 196 | +#+NAME: sk_modalres | |
| 197 | +#+BEGIN_SRC c | |
| 198 | +SKFLT q; | |
| 199 | +SKFLT pq; | |
| 200 | +#+END_SRC | |
| 201 | + | |
| 202 | +#+NAME: init | |
| 203 | +#+BEGIN_SRC c | |
| 204 | +sk_modalres_q(mr, 1); | |
| 205 | +mr->pq = -1; | |
| 206 | +#+END_SRC | |
| 207 | +* Computation | |
| 208 | +A single sample is computed with =sk_modalres_tick=. | |
| 209 | + | |
| 210 | +#+NAME: funcdefs | |
| 211 | +#+BEGIN_SRC c | |
| 212 | +SKFLT sk_modalres_tick(sk_modalres *mr, SKFLT in); | |
| 213 | +#+END_SRC | |
| 214 | + | |
| 215 | +#+NAME: funcs | |
| 216 | +#+BEGIN_SRC c | |
| 217 | +SKFLT sk_modalres_tick(sk_modalres *mr, SKFLT in) | |
| 218 | +{ | |
| 219 | + SKFLT out; | |
| 220 | + | |
| 221 | + out = 0; | |
| 222 | + <<update_coefficients>> | |
| 223 | + <<compute_difference_equation>> | |
| 224 | + <<update_filter_state>> | |
| 225 | + <<scale_output>> | |
| 226 | + return out; | |
| 227 | +} | |
| 228 | +#+END_SRC | |
| 229 | + | |
| 230 | +Update the coefficients, if needed. | |
| 231 | +First, frequency is converted to radians. | |
| 232 | +While traditionally represented as the | |
| 233 | +lowercase $\omega$ in mathematical notation, the variable | |
| 234 | +=w= will be used in C as crude approximation. | |
| 235 | + | |
| 236 | +From there the following coefficients are computed: | |
| 237 | + | |
| 238 | +#+BEGIN_SRC tex | |
| 239 | +b1 = {1 \over \omega^2 + {\omega \over 2Q}} | |
| 240 | +a1 = {1 - 2\omega^2}b1 | |
| 241 | +a2 = {\omega^2 - {\omega \over 2Q}} | |
| 242 | +#+END_SRC | |
| 243 | + | |
| 244 | +#+NAME: update_coefficients | |
| 245 | +#+BEGIN_SRC c | |
| 246 | +if (mr->freq != mr->pfreq || mr->q != mr->pq) { | |
| 247 | + SKFLT w; | |
| 248 | + SKFLT a, b, d; | |
| 249 | + | |
| 250 | + w = mr->freq * 2.0 * M_PI; | |
| 251 | + | |
| 252 | + a = mr->sr / w; | |
| 253 | + b = a*a; | |
| 254 | + d = 0.5*a; | |
| 255 | + | |
| 256 | + mr->pfreq = mr->freq; | |
| 257 | + mr->pq = mr->q; | |
| 258 | + | |
| 259 | + mr->b1 = 1.0 / (b + d/mr->q); | |
| 260 | + mr->a1 = (1.0 - 2.0*b) * mr->b1; | |
| 261 | + mr->a2 = (b - d/mr->q) * mr->b1; | |
| 262 | + mr->s = d; | |
| 263 | +} | |
| 264 | +#+END_SRC | |
| 265 | + | |
| 266 | +Compute difference equation. This computes the difference | |
| 267 | +equation: | |
| 268 | + | |
| 269 | +#+BEGIN_SRC tex | |
| 270 | +$$ | |
| 271 | +y(n) = b0 x(n) + b1 x(n - 1) - a1 y(n - 1) - a2 y(n - 2) | |
| 272 | +$$ | |
| 273 | +#+END_SRC | |
| 274 | + | |
| 275 | +Where $b0$ is 0, effectively cancelling out this term. | |
| 276 | + | |
| 277 | +What's interesting about this is that it adds an implicit | |
| 278 | +one-sample delay to the filter. | |
| 279 | +This is done because more accurately simulates the impulse | |
| 280 | +response in an ideal mass-spring-damper system. The produces | |
| 281 | +is, after all, a *response* to the impulse. | |
| 282 | + | |
| 283 | +#+NAME: compute_difference_equation | |
| 284 | +#+BEGIN_SRC c | |
| 285 | +out = mr->b1*mr->x - mr->a1*mr->y[0] - mr->a2*mr->y[1]; | |
| 286 | +#+END_SRC | |
| 287 | + | |
| 288 | +Update filter state. Shift things forward in time one | |
| 289 | +sample. What was once =y[0]= is now =y[1]=. What is now | |
| 290 | +the current output becomes =y[0]=, what is now the current | |
| 291 | +input value because the previous input value =x=. | |
| 292 | + | |
| 293 | +#+NAME: update_filter_state | |
| 294 | +#+BEGIN_SRC c | |
| 295 | +mr->y[1] = mr->y[0]; | |
| 296 | +mr->y[0] = out; | |
| 297 | +mr->x = in; | |
| 298 | +#+END_SRC | |
| 299 | + | |
| 300 | +The output is scaled by the variable =s= before it is | |
| 301 | +returned. It is not part of the recursive filter function. | |
| 302 | + | |
| 303 | +#+NAME: scale_output | |
| 304 | +#+BEGIN_SRC c | |
| 305 | +out *= mr->s; | |
| 306 | +#+END_SRC | |
| @@ -0,0 +1,409 @@ | ||
| 1 | +#+TITLE: The Oscillator | |
| 2 | +* Introduction. | |
| 3 | +This document will describe an implementation of a classic | |
| 4 | +table-lookup oscillator with linear interpolation. | |
| 5 | + | |
| 6 | +The algorithm for this is an interesting mix of numerical | |
| 7 | +processing, with the phasor being implemented in fixed | |
| 8 | +point, and everything else being done in floating point. | |
| 9 | +A big advantage to doing it this way is numerical stability: | |
| 10 | +there is no risk of any phase acculumation or truncation | |
| 11 | +like you'd get with floating-point. This | |
| 12 | +is an important characteristic needed for phasor to avoid | |
| 13 | +things like drift over time. As will be seen later, the | |
| 14 | +fixed-point approach can be a little bit harder to | |
| 15 | +understand, especially for those unfamiliar with fixed-point | |
| 16 | +DSP. The author has put extra effort to explain these | |
| 17 | +portions as clearly as possible. | |
| 18 | + | |
| 19 | +The implementation also has a famous limitation of only | |
| 20 | +being able to take in tables with sizes that are a power of | |
| 21 | +2. | |
| 22 | +* Generated Files | |
| 23 | +Header: | |
| 24 | + | |
| 25 | +#+NAME: osc.h | |
| 26 | +#+BEGIN_SRC c :tangle osc.h | |
| 27 | +#ifndef SK_OSC_H | |
| 28 | +#define SK_OSC_H | |
| 29 | +#ifndef SKFLT | |
| 30 | +#define SKFLT float | |
| 31 | +#endif | |
| 32 | +<<typedefs>> | |
| 33 | +#ifdef SK_OSC_PRIV | |
| 34 | +<<structs>> | |
| 35 | +#endif | |
| 36 | +<<funcdefs>> | |
| 37 | +#endif | |
| 38 | +#+END_SRC | |
| 39 | + | |
| 40 | +C file: | |
| 41 | + | |
| 42 | +#+NAME: osc.c | |
| 43 | +#+BEGIN_SRC c :tangle osc.c | |
| 44 | +#include <stdint.h> | |
| 45 | +#include <stdlib.h> | |
| 46 | +#include <math.h> | |
| 47 | +#define SK_OSC_PRIV | |
| 48 | +#include "osc.h" | |
| 49 | +<<constants>> | |
| 50 | +<<funcs>> | |
| 51 | +#+END_SRC | |
| 52 | +* Top-level functions | |
| 53 | +The table-lookup oscillator is initialized with | |
| 54 | +=sk_osc_init=. The following arguments must be provided: | |
| 55 | + | |
| 56 | +=osc= is a pre-allocated struct of =sk_osc=. | |
| 57 | + | |
| 58 | +=sr= is the sampling rate. | |
| 59 | + | |
| 60 | +=tab= is a pre-allocated wavetable, an array of =SKFLT= | |
| 61 | +floating-point values. | |
| 62 | + | |
| 63 | +=sz= is the array size of the wavetable =wt=. | |
| 64 | + | |
| 65 | +=iphs= provides the initial phase of the oscillator. It is | |
| 66 | +a value between 0-1. | |
| 67 | + | |
| 68 | +#+NAME: funcdefs | |
| 69 | +#+BEGIN_SRC c | |
| 70 | +void sk_osc_init(sk_osc *osc, int sr, SKFLT *wt, int sz, SKFLT iphs); | |
| 71 | +#+END_SRC | |
| 72 | + | |
| 73 | +To compute a sample of audio, use =sk_osc_tick=. | |
| 74 | + | |
| 75 | +#+NAME: funcdefs | |
| 76 | +#+BEGIN_SRC c | |
| 77 | +SKFLT sk_osc_tick(sk_osc *osc); | |
| 78 | +#+END_SRC | |
| 79 | + | |
| 80 | +This oscillator has 2 main parameters: frequency (freq), | |
| 81 | +and amplitude (amp). They can be | |
| 82 | +set with the following functions. | |
| 83 | + | |
| 84 | +#+NAME: funcdefs | |
| 85 | +#+BEGIN_SRC c | |
| 86 | +void sk_osc_freq(sk_osc *osc, SKFLT freq); | |
| 87 | +void sk_osc_amp(sk_osc *osc, SKFLT amp); | |
| 88 | +#+END_SRC | |
| 89 | + | |
| 90 | +#+NAME: funcs | |
| 91 | +#+BEGIN_SRC c | |
| 92 | +void sk_osc_freq(sk_osc *osc, SKFLT freq) | |
| 93 | +{ | |
| 94 | + osc->freq = freq; | |
| 95 | +} | |
| 96 | + | |
| 97 | +void sk_osc_amp(sk_osc *osc, SKFLT amp) | |
| 98 | +{ | |
| 99 | + osc->amp = amp; | |
| 100 | +} | |
| 101 | +#+END_SRC | |
| 102 | +* Constants | |
| 103 | +#+NAME: constants | |
| 104 | +#+BEGIN_SRC c | |
| 105 | +#define SK_OSC_MAXLEN 0x1000000L | |
| 106 | +#define SK_OSC_PHASEMASK 0x0FFFFFFL | |
| 107 | +#+END_SRC | |
| 108 | +* Struct and Constants | |
| 109 | +The main struct of this oscillator is called =sk_osc=. | |
| 110 | +#+NAME: typedefs | |
| 111 | +#+BEGIN_SRC c | |
| 112 | +typedef struct sk_osc sk_osc; | |
| 113 | +#+END_SRC | |
| 114 | +#+NAME: structs | |
| 115 | +#+BEGIN_SRC c | |
| 116 | +struct sk_osc { | |
| 117 | + <<sk_osc>> | |
| 118 | +}; | |
| 119 | +#+END_SRC | |
| 120 | + | |
| 121 | +#+NAME: sk_osc | |
| 122 | +#+BEGIN_SRC c | |
| 123 | +SKFLT freq, amp; | |
| 124 | +SKFLT *tab; | |
| 125 | +int inc; | |
| 126 | +size_t sz; | |
| 127 | +uint32_t nlb; | |
| 128 | +SKFLT inlb; | |
| 129 | +uint32_t mask; | |
| 130 | +SKFLT maxlens; | |
| 131 | +int32_t lphs; | |
| 132 | +#+END_SRC | |
| 133 | + | |
| 134 | +The oscillator stores it's main parameters =freq= and =amp= | |
| 135 | +as floating point parameters. They are set to be values | |
| 136 | +440 and 0.2 by default. | |
| 137 | + | |
| 138 | +#+NAME: osc_init | |
| 139 | +#+BEGIN_SRC c | |
| 140 | +osc->freq = 440; | |
| 141 | +osc->amp = 0.2; | |
| 142 | +#+END_SRC | |
| 143 | + | |
| 144 | +A reference to the table is stored in the variable =tab=, | |
| 145 | +with its size =sz=. | |
| 146 | + | |
| 147 | +#+NAME: osc_init | |
| 148 | +#+BEGIN_SRC c | |
| 149 | +osc->tab = wt; | |
| 150 | +osc->sz = sz; | |
| 151 | +#+END_SRC | |
| 152 | + | |
| 153 | +A table lookup oscillator indexes through the table using | |
| 154 | +the increment rate stored in the integer value =inc=. This | |
| 155 | +value can be positive or negative. Is is zeroed out at | |
| 156 | +init-time. | |
| 157 | + | |
| 158 | +#+NAME: osc_init | |
| 159 | +#+BEGIN_SRC c | |
| 160 | +osc->inc = 0; | |
| 161 | +#+END_SRC | |
| 162 | + | |
| 163 | +The variable =lphs= stores the phase position of the | |
| 164 | +previous sample. The initial phase value =iphs= is | |
| 165 | +multiplied with the the maximum table value, and then | |
| 166 | +masked to keep values in range. | |
| 167 | + | |
| 168 | +#+NAME: osc_init | |
| 169 | +#+BEGIN_SRC c | |
| 170 | +osc->lphs = ((int32_t)(iphs * SK_OSC_MAXLEN)) & SK_OSC_PHASEMASK; | |
| 171 | +#+END_SRC | |
| 172 | + | |
| 173 | +For the fixed point table-lookup, some constants are derived | |
| 174 | +and stored. | |
| 175 | + | |
| 176 | +Phasor position is stored by splitting the bits of an | |
| 177 | +N-bit integer number into two parts. The upper bits store the | |
| 178 | +integer portion, while the lower bits store fractional | |
| 179 | +portion. The maximum number of bits is arbitrary, but the | |
| 180 | +underlying architecture must be able to accomodate for the | |
| 181 | +width. In this implementation, the phasor uses 28 bits | |
| 182 | +inside of a 32-bit number. This implicitely means the largest | |
| 183 | +value can be | |
| 184 | + | |
| 185 | +Split in the phasor position is measured by counting the | |
| 186 | +number of lower bits. This value is stored in the variable | |
| 187 | +=nlb=. This value is calculated with the equation | |
| 188 | + | |
| 189 | +@! | |
| 190 | +#$$n = log2(M/s)$$ | |
| 191 | +(img "/res/wiki/osc/osc_log2.png") | |
| 192 | +!@ | |
| 193 | + | |
| 194 | +Where =n= is the number of lower bits, =M= is the maximum | |
| 195 | +wavetable size, and =s= is the size of the wavetable. | |
| 196 | + | |
| 197 | +To calculate =nlb=, and hand-rolled =log2= function is | |
| 198 | +created. | |
| 199 | + | |
| 200 | +Smaller values of =s= mean more bits in the fractional | |
| 201 | +component of the number. | |
| 202 | + | |
| 203 | +#+NAME: osc_init | |
| 204 | +#+BEGIN_SRC c | |
| 205 | +{ | |
| 206 | + uint32_t tmp; | |
| 207 | + tmp = SK_OSC_MAXLEN / sz; | |
| 208 | + osc->nlb = 0; | |
| 209 | + while (tmp >>= 1) osc->nlb++; | |
| 210 | +} | |
| 211 | +<<calculate_mask>> | |
| 212 | +<<calculate_inlb>> | |
| 213 | +<<calculate_maxlens>> | |
| 214 | +#+END_SRC | |
| 215 | + | |
| 216 | +The =mask= is the lower-bits masking variable. When an AND | |
| 217 | +operation is used against this constant, it filters out all | |
| 218 | +the upper bits, so only the lower bits can pass through. | |
| 219 | +This constant is necessary for being able to extract the | |
| 220 | +lower bits from the fixed-point phase value representation. | |
| 221 | +This sort of value is known in the bit-twiddling world as a | |
| 222 | +=mask=. In binary, all the lower bits up to the number of | |
| 223 | +lower bits are set to be on, with the remaining bits set | |
| 224 | +to be 0. | |
| 225 | + | |
| 226 | +#+NAME: calculate_mask | |
| 227 | +#+BEGIN_SRC c | |
| 228 | +osc->mask = (1<<osc->nlb) - 1; | |
| 229 | +#+END_SRC | |
| 230 | + | |
| 231 | +The inverse of max lower bits value, or $1/2^{nlb}$, is | |
| 232 | +stored as a constant. This cached value is used to replace | |
| 233 | +and divide operation with a multiply operation, which has | |
| 234 | +traditionally been a cheaper operation to do on a computer. | |
| 235 | + | |
| 236 | +#+NAME: calculate_inlb | |
| 237 | +#+BEGIN_SRC c | |
| 238 | +osc->inlb = 1.0 / (1<<osc->nlb); | |
| 239 | +#+END_SRC | |
| 240 | + | |
| 241 | +The constant =maxlens= is the maximum table length in units | |
| 242 | +of seconds. This is a value used to efficiently convert the | |
| 243 | +frequency parameter to sample increment value. | |
| 244 | + | |
| 245 | +#+NAME: calculate_maxlens | |
| 246 | +#+BEGIN_SRC c | |
| 247 | +osc->maxlens = 1.0 * SK_OSC_MAXLEN / sr; | |
| 248 | +#+END_SRC | |
| 249 | +* Initialization | |
| 250 | +In addition to setting variables, the init function will | |
| 251 | +also set the starting phase of oscillator. | |
| 252 | +#+NAME: funcs | |
| 253 | +#+BEGIN_SRC c | |
| 254 | +void sk_osc_init(sk_osc *osc, int sr, SKFLT *wt, int sz, SKFLT iphs) | |
| 255 | +{ | |
| 256 | + <<osc_init>> | |
| 257 | +} | |
| 258 | +#+END_SRC | |
| 259 | +* Computation | |
| 260 | +The meat of the algorithm is here. Here outlines the | |
| 261 | +=tick= function, where a single sample of an oscillator | |
| 262 | +is computer. | |
| 263 | + | |
| 264 | +#+NAME: funcs | |
| 265 | +#+BEGIN_SRC c | |
| 266 | +SKFLT sk_osc_tick(sk_osc *osc) | |
| 267 | +{ | |
| 268 | + SKFLT out; | |
| 269 | + SKFLT fract; | |
| 270 | + SKFLT x1, x2; | |
| 271 | + int32_t phs; | |
| 272 | + int pos; | |
| 273 | + | |
| 274 | + out = 0; | |
| 275 | + <<update_increment_amount>> | |
| 276 | + <<lookup_values>> | |
| 277 | + <<obtain_fractional_component>> | |
| 278 | + <<interpolate_values>> | |
| 279 | + <<update_the_state>> | |
| 280 | + return out; | |
| 281 | +} | |
| 282 | +#+END_SRC | |
| 283 | + | |
| 284 | +First, the increment amount =inc= is updated. | |
| 285 | +The increment amount tells how much further to move the read | |
| 286 | +pointer in the table. This increment amount is based on the | |
| 287 | +current oscillator frequency =freq= and the variable | |
| 288 | +=maxlens=. =lrintf= is used to round to the nearest integer. | |
| 289 | + | |
| 290 | +This is kind of a baffling operation. How could multiplying | |
| 291 | +the frequency by some arbitrary duration yield a phasor | |
| 292 | +increment amount? And where is the sampling rate in all | |
| 293 | +this? | |
| 294 | + | |
| 295 | +The thing that throws everything off is this fixed point | |
| 296 | +business. Things make a lot more sense if you wanted to | |
| 297 | +do this the floating-point way. | |
| 298 | + | |
| 299 | +Recall that a phasor is a repeating line that ramps from | |
| 300 | +0 to 1 over a given period of time. If we call this period | |
| 301 | +of time =t=, the increment value =I= needs to work so that | |
| 302 | +=t * sr * I = 1=. In other words, it's the slope of a line | |
| 303 | +discretised. | |
| 304 | + | |
| 305 | +Linear slope is found using good-ol rise over run, change in | |
| 306 | +value over change in time. Inverting the frequency =1/F= | |
| 307 | +gives it's period length in seconds. Multiplying by the | |
| 308 | +sampling rate =sr= converts that value to samples. This | |
| 309 | +gives us =sr/f=, our change in time. Because of the | |
| 310 | +normalized range, change in value is just 1. Putting it all | |
| 311 | +together This gives us a slope of =1/(sr/f)=, or =f/sr=. | |
| 312 | +That is the normalized increment value. | |
| 313 | + | |
| 314 | +If we wanted this to work with our fixed point phasor, | |
| 315 | +we'd need to scale it by the maximum length of the phasor. | |
| 316 | +That looks like =(f/sr)*maxlen)=, or =(f * maxlen)/sr)=. | |
| 317 | +The frequency =f= can be pulled out of the numerator to | |
| 318 | +get =f * (maxlen/sr)=, which can be reduced to the operation | |
| 319 | +similarly seen below of =f * maxlens=. | |
| 320 | + | |
| 321 | +Before frequency units were measured in Hertz, they were | |
| 322 | +measured in cycles-per-second. If you ever took a highschool | |
| 323 | +chemistry or physics class, you may recall that units can | |
| 324 | +"cancel out" one another like a fraction. When | |
| 325 | +cycles-per-second (cycles / seconds) gets multiplied by | |
| 326 | +a value in seconds, the seconds cancel. What you are left | |
| 327 | +with is a unit called *cycles*. | |
| 328 | + | |
| 329 | +#+NAME: update_increment_amount | |
| 330 | +#+BEGIN_SRC c | |
| 331 | +osc->inc = floor(lrintf(osc->freq * osc->maxlens)); | |
| 332 | +#+END_SRC | |
| 333 | + | |
| 334 | +Look up values A =n= and B =n + 1= samples from wavetable. | |
| 335 | +Perform table lookup. Both the current position, and it's | |
| 336 | +neighor are needed. | |
| 337 | +This position is found by looking at the upper bits of | |
| 338 | +the current phase. | |
| 339 | + | |
| 340 | +#+NAME: lookup_values | |
| 341 | +#+BEGIN_SRC c | |
| 342 | +phs = osc->lphs; | |
| 343 | +pos = phs >> osc->nlb; | |
| 344 | +x1 = osc->tab[pos]; | |
| 345 | +x2 = osc->tab[(pos + 1) % osc->sz]; | |
| 346 | +#+END_SRC | |
| 347 | + | |
| 348 | +Now, it's time to interpolate between points A and B. This | |
| 349 | +oscillator uses linear interpolation, which can be thought | |
| 350 | +of as a crossfade between two values. The equation for | |
| 351 | +linear interpolation is commonly shown as: | |
| 352 | + | |
| 353 | +@! | |
| 354 | +#$$ | |
| 355 | +#y = (1 - \alpha) x_1 + \alpha x_2 | |
| 356 | +#$$ | |
| 357 | +(img "/res/wiki/osc/osc_lerp.png") | |
| 358 | +!@ | |
| 359 | + | |
| 360 | +Where $x_1$ and $x_2$ are two values, and $\alpha$ is a | |
| 361 | +fractional value between 0 and 1. The $\alpha$ values | |
| 362 | +determines the distribution balance of | |
| 363 | +the two values. When $\alpha = 0$, it is entirely $x_1$, and | |
| 364 | +when $\alpha = 1$, the value is $x_2$. | |
| 365 | + | |
| 366 | +The fractional (alpha) value is obtained by taking the lower | |
| 367 | +bits portion of the current fixed-point phase position, and | |
| 368 | +normalizing it to be a floating-point value between 0 and 1. | |
| 369 | + | |
| 370 | +#+NAME: obtain_fractional_component | |
| 371 | +#+BEGIN_SRC c | |
| 372 | +fract = (phs & osc->mask) * osc->inlb; | |
| 373 | +#+END_SRC | |
| 374 | + | |
| 375 | +There are now all the parts to do the interpolation. It | |
| 376 | +turns out the equation above can be simplified further to | |
| 377 | +shave off a multiply operation. | |
| 378 | + | |
| 379 | +@! | |
| 380 | +# $$ | |
| 381 | +# \eqalign{ | |
| 382 | +# y &= (1 - \alpha)x_1 + \alpha x_2 \cr | |
| 383 | +# &= x_1 - \alpha x_1 + \alpha x_2 \cr | |
| 384 | +# &= x_1 + \alpha x_2 - \alpha x_1 \cr | |
| 385 | +# &= x_1 + (x_2 - x_1)\alpha | |
| 386 | +# } | |
| 387 | +# $$ | |
| 388 | +(img "/res/wiki/osc/osc_lerp_simp.png") | |
| 389 | +!@ | |
| 390 | + | |
| 391 | +Which then gets translated to the following C code below. | |
| 392 | +In this step, the output is also being scaled by the | |
| 393 | +amplitude =amp=. | |
| 394 | + | |
| 395 | +#+NAME: interpolate_values | |
| 396 | +#+BEGIN_SRC c | |
| 397 | +out = (x1 + (x2 - x1) * fract) * osc->amp; | |
| 398 | +#+END_SRC | |
| 399 | + | |
| 400 | +And now the sample has been computed! To wrap up, the | |
| 401 | +internal phase amount =lphs= is updated and masked to | |
| 402 | +prevent overflow. | |
| 403 | + | |
| 404 | +#+NAME: update_the_state | |
| 405 | +#+BEGIN_SRC c | |
| 406 | +phs += osc->inc; | |
| 407 | +phs &= SK_OSC_PHASEMASK; | |
| 408 | +osc->lphs = phs; | |
| 409 | +#+END_SRC | |
| @@ -0,0 +1,358 @@ | ||
| 1 | +#+TITLE: PeakEQ | |
| 2 | +* Overview | |
| 3 | +PeakEQ is an implementation of a single | |
| 4 | +[[https://ccrma.stanford.edu/~jos//filters/Peaking_Equalizers.html][peaking equalizer filter]], with sweepable | |
| 5 | +frequency, bandwidth, and gain. | |
| 6 | +When used in series, the PeakEQ filter can be used | |
| 7 | +to construct parametric and graphic equalizers commonly | |
| 8 | +found in DAWs. | |
| 9 | +* Algorithm Details | |
| 10 | +The filter design based on the 2nd-order equalization filter | |
| 11 | +design by Reglia and Mitra, from the paper | |
| 12 | +"Tunable Digital Frequency Response Equalization Filters", | |
| 13 | +IEEE Trans. on Ac., Sp. and Sig Proc., 35 (1), 1987 | |
| 14 | + | |
| 15 | +This implementation takes the original filter design and | |
| 16 | +converts it to a direct-form 2 filter configuration, which | |
| 17 | +has better numerical stability. | |
| 18 | +* Conversion to Direct Form 2 | |
| 19 | +We begin with the transfer function, described in the 1987 | |
| 20 | +paper: | |
| 21 | + | |
| 22 | +#+BEGIN_SRC tex | |
| 23 | +$$ | |
| 24 | +{ | |
| 25 | +\alpha + \beta(1 + \alpha)z^{-1} + z^{-2} | |
| 26 | +\over | |
| 27 | +1 + \beta(1 + \alpha)z^{-1} + \alpha z^{-2} | |
| 28 | +} | |
| 29 | +$$ | |
| 30 | +#+END_SRC | |
| 31 | + | |
| 32 | +This has the following difference equation: | |
| 33 | + | |
| 34 | +#+BEGIN_SRC tex | |
| 35 | +$$ | |
| 36 | +y(n) = \alpha x(n) + \beta(1 + \alpha)x(n - 1) + x(n - 2) | |
| 37 | +- \beta(1 + \alpha)z^{-1} y(n - 1) - \alpha y(n - 2) | |
| 38 | +$$ | |
| 39 | +#+END_SRC | |
| 40 | + | |
| 41 | +The | |
| 42 | +[[https://ccrma.stanford.edu/~jos/filters/Direct_Form_II.html][Direct Form II Structure]]. can be | |
| 43 | +represented in this generalized difference equation: | |
| 44 | + | |
| 45 | +#+BEGIN_SRC tex | |
| 46 | +$$ | |
| 47 | +v(n) = x(n) - a_1 v(n - 1) - a_2 v(n - 2) | |
| 48 | + | |
| 49 | +y(n) = b_0 v(n) + b_1 v(n - 1) + b_2 v(n - 2) | |
| 50 | +$$ | |
| 51 | +#+END_SRC | |
| 52 | + | |
| 53 | +Converting to direct form II is a matter of putting | |
| 54 | +coefficients in their correct places. Placing in the | |
| 55 | +coefficients from the Reglia and Mitra design yields the | |
| 56 | +following difference equation: | |
| 57 | + | |
| 58 | +#+BEGIN_SRC tex | |
| 59 | +$$ | |
| 60 | +v(n) = x(n) - \beta(1 + \alpha)v(n - 1) - \alpha v(n - 2) | |
| 61 | + | |
| 62 | +y(n) = \alpha v(n) + \beta(1 + \alpha) v(n - 1) + v(n - 2) | |
| 63 | +$$ | |
| 64 | +#+END_SRC | |
| 65 | + | |
| 66 | +Which can be translated to C code. | |
| 67 | +* Tangled Files | |
| 68 | +Tangles to files =peakeq.c= and =peakeq.h=. | |
| 69 | + | |
| 70 | +#+NAME: peakeq.c | |
| 71 | +#+BEGIN_SRC c :tangle peakeq.c | |
| 72 | +#include <math.h> | |
| 73 | +#define SK_PEAKEQ_PRIV | |
| 74 | +#include "peakeq.h" | |
| 75 | + | |
| 76 | +#ifndef M_PI | |
| 77 | +#define M_PI 3.14159265358979323846 | |
| 78 | +#endif | |
| 79 | + | |
| 80 | +<<funcs>> | |
| 81 | +#+END_SRC | |
| 82 | + | |
| 83 | +When =SK_PEAKEQ_PRIV= is defined, it exposes the main | |
| 84 | +struct. | |
| 85 | + | |
| 86 | +#+NAME: peakeq.h | |
| 87 | +#+BEGIN_SRC c :tangle peakeq.h | |
| 88 | +#ifndef SK_PEAKEQ_H | |
| 89 | +#define SK_PEAKEQ_H | |
| 90 | + | |
| 91 | +#ifndef SKFLT | |
| 92 | +#define SKFLT float | |
| 93 | +#endif | |
| 94 | + | |
| 95 | +<<typedefs>> | |
| 96 | +<<funcdefs>> | |
| 97 | + | |
| 98 | +#ifdef SK_PEAKEQ_PRIV | |
| 99 | +<<structs>> | |
| 100 | +#endif | |
| 101 | +#endif | |
| 102 | +#+END_SRC | |
| 103 | +* Struct | |
| 104 | +Everything is stored in a struct called =sk_peakeq=. | |
| 105 | + | |
| 106 | +#+NAME: typedefs | |
| 107 | +#+BEGIN_SRC c | |
| 108 | +typedef struct sk_peakeq sk_peakeq; | |
| 109 | +#+END_SRC | |
| 110 | + | |
| 111 | +#+NAME: structs | |
| 112 | +#+BEGIN_SRC c | |
| 113 | +struct sk_peakeq { | |
| 114 | + <<sk_peakeq>> | |
| 115 | +}; | |
| 116 | +#+END_SRC | |
| 117 | +* Init | |
| 118 | +PeakEQ is initialized with =sk_peakeq_init=. The sampling | |
| 119 | +rate is required here. | |
| 120 | + | |
| 121 | +#+NAME: funcdefs | |
| 122 | +#+BEGIN_SRC c | |
| 123 | +void sk_peakeq_init(sk_peakeq *eq, int sr); | |
| 124 | +#+END_SRC | |
| 125 | + | |
| 126 | +#+NAME: funcs | |
| 127 | +#+BEGIN_SRC c | |
| 128 | +void sk_peakeq_init(sk_peakeq *eq, int sr) | |
| 129 | +{ | |
| 130 | + <<init>> | |
| 131 | +} | |
| 132 | +#+END_SRC | |
| 133 | +* Filter State Memory and Constants | |
| 134 | +The direct-form 2 form can be thought of as a 2-pole filter, | |
| 135 | +followed by a 2-zero filter. | |
| 136 | + | |
| 137 | +The memory for the 2-pole filter is stored in a variable | |
| 138 | +called =v=. | |
| 139 | + | |
| 140 | +#+NAME: sk_peakeq | |
| 141 | +#+BEGIN_SRC c | |
| 142 | +SKFLT v[2]; | |
| 143 | +#+END_SRC | |
| 144 | + | |
| 145 | +#+NAME: init | |
| 146 | +#+BEGIN_SRC c | |
| 147 | +eq->v[0] = 0; | |
| 148 | +eq->v[1] = 0; | |
| 149 | +#+END_SRC | |
| 150 | + | |
| 151 | +The alpha and beta coefficients are stored as variables | |
| 152 | +called =a= and =b=. | |
| 153 | + | |
| 154 | +#+NAME: sk_peakeq | |
| 155 | +#+BEGIN_SRC c | |
| 156 | +SKFLT a; | |
| 157 | +SKFLT b; | |
| 158 | +#+END_SRC | |
| 159 | + | |
| 160 | +#+NAME: init | |
| 161 | +#+BEGIN_SRC c | |
| 162 | +eq->a = 0; | |
| 163 | +eq->b = 0; | |
| 164 | +#+END_SRC | |
| 165 | + | |
| 166 | +The sampling rate is required to calculate new coefficients | |
| 167 | +on-the-fly. A copy is stored in this struct. | |
| 168 | + | |
| 169 | +#+NAME: sk_peakeq | |
| 170 | +#+BEGIN_SRC c | |
| 171 | +int sr; | |
| 172 | +#+END_SRC | |
| 173 | + | |
| 174 | +#+NAME: init | |
| 175 | +#+BEGIN_SRC c | |
| 176 | +eq->sr = sr; | |
| 177 | +#+END_SRC | |
| 178 | +* Parameters | |
| 179 | +** Frequency | |
| 180 | +Set with =sk_peakeq_freq=. Used to set the center frequency | |
| 181 | +of the filter, in units of Hz. | |
| 182 | + | |
| 183 | +#+NAME: funcdefs | |
| 184 | +#+BEGIN_SRC c | |
| 185 | +void sk_peakeq_freq(sk_peakeq *eq, SKFLT freq); | |
| 186 | +#+END_SRC | |
| 187 | + | |
| 188 | +#+NAME: funcs | |
| 189 | +#+BEGIN_SRC c | |
| 190 | +void sk_peakeq_freq(sk_peakeq *eq, SKFLT freq) | |
| 191 | +{ | |
| 192 | + eq->freq = freq; | |
| 193 | +} | |
| 194 | +#+END_SRC | |
| 195 | + | |
| 196 | +This parameter uses caching to detect if the parameter | |
| 197 | +changes. | |
| 198 | + | |
| 199 | +#+NAME: sk_peakeq | |
| 200 | +#+BEGIN_SRC c | |
| 201 | +SKFLT freq; | |
| 202 | +SKFLT pfreq; | |
| 203 | +#+END_SRC | |
| 204 | + | |
| 205 | +=pfreq= is set to be a negative value to force initial | |
| 206 | +coefficient calculation. | |
| 207 | + | |
| 208 | +#+NAME: init | |
| 209 | +#+BEGIN_SRC c | |
| 210 | +sk_peakeq_freq(eq, 1000); | |
| 211 | +eq->pfreq = -1; | |
| 212 | +#+END_SRC | |
| 213 | +** Bandwidth | |
| 214 | +Set with =sk_peakeq_bandwidth=. This parameter sets the | |
| 215 | +bandwidth of the EQ filter, in units of Hz. | |
| 216 | + | |
| 217 | +#+NAME: funcdefs | |
| 218 | +#+BEGIN_SRC c | |
| 219 | +void sk_peakeq_bandwidth(sk_peakeq *eq, SKFLT bw); | |
| 220 | +#+END_SRC | |
| 221 | + | |
| 222 | +#+NAME: funcs | |
| 223 | +#+BEGIN_SRC c | |
| 224 | +void sk_peakeq_bandwidth(sk_peakeq *eq, SKFLT bw) | |
| 225 | +{ | |
| 226 | + eq->bw = bw; | |
| 227 | +} | |
| 228 | +#+END_SRC | |
| 229 | + | |
| 230 | +This parameter uses caching in order to detect if the | |
| 231 | +parameter changes. | |
| 232 | + | |
| 233 | +#+NAME: sk_peakeq | |
| 234 | +#+BEGIN_SRC c | |
| 235 | +SKFLT bw; | |
| 236 | +SKFLT pbw; | |
| 237 | +#+END_SRC | |
| 238 | + | |
| 239 | +Like with freq, =pbw= is set to be a negative value to force | |
| 240 | +coefficient calculation at the initial computation. | |
| 241 | + | |
| 242 | +#+NAME: init | |
| 243 | +#+BEGIN_SRC c | |
| 244 | +sk_peakeq_bandwidth(eq, 1000); | |
| 245 | +eq->pbw = -1; | |
| 246 | +#+END_SRC | |
| 247 | +** Gain | |
| 248 | +This sets the gain of the filter. Positive values will cause | |
| 249 | +a boost. Negative values will create a cut. | |
| 250 | + | |
| 251 | +#+NAME: funcdefs | |
| 252 | +#+BEGIN_SRC c | |
| 253 | +void sk_peakeq_gain(sk_peakeq *eq, SKFLT gain); | |
| 254 | +#+END_SRC | |
| 255 | + | |
| 256 | +#+NAME: funcs | |
| 257 | +#+BEGIN_SRC c | |
| 258 | +void sk_peakeq_gain(sk_peakeq *eq, SKFLT gain) | |
| 259 | +{ | |
| 260 | + eq->gain = gain; | |
| 261 | +} | |
| 262 | +#+END_SRC | |
| 263 | + | |
| 264 | +#+NAME: sk_peakeq | |
| 265 | +#+BEGIN_SRC c | |
| 266 | +SKFLT gain; | |
| 267 | +#+END_SRC | |
| 268 | + | |
| 269 | +#+NAME: init | |
| 270 | +#+BEGIN_SRC c | |
| 271 | +sk_peakeq_gain(eq, 1.0); | |
| 272 | +#+END_SRC | |
| 273 | +* Compute | |
| 274 | +=sk_peakeq_tick=. | |
| 275 | + | |
| 276 | +#+NAME: funcdefs | |
| 277 | +#+BEGIN_SRC c | |
| 278 | +SKFLT sk_peakeq_tick(sk_peakeq *eq, SKFLT in); | |
| 279 | +#+END_SRC | |
| 280 | + | |
| 281 | +#+NAME: funcs | |
| 282 | +#+BEGIN_SRC c | |
| 283 | +SKFLT sk_peakeq_tick(sk_peakeq *eq, SKFLT in) | |
| 284 | +{ | |
| 285 | + SKFLT out; | |
| 286 | + SKFLT v; | |
| 287 | + SKFLT y; | |
| 288 | + out = 0; | |
| 289 | + | |
| 290 | + <<update_coefficients>> | |
| 291 | + <<compute_difference_equations>> | |
| 292 | + <<compute_gain>> | |
| 293 | + <<update_filter_state>> | |
| 294 | + | |
| 295 | + return out; | |
| 296 | +} | |
| 297 | +#+END_SRC | |
| 298 | + | |
| 299 | +Update coefficients, if needed. This happens at init time | |
| 300 | +or when any of the parameters change. | |
| 301 | + | |
| 302 | +The coefficents computed are alpha and beta. The beta | |
| 303 | +coefficient is the negative cosine of the center frequency | |
| 304 | +in units of radians. The alpha coefficient is the expression | |
| 305 | +=(1 - c) / (1 + c)=, where =c= is the tangent of the | |
| 306 | +bandwidth, times =PI=, divided by the sampling rate. More | |
| 307 | +details on the both coefficient derivations can be found in | |
| 308 | +the original Reglia and Mitra paper. | |
| 309 | + | |
| 310 | +#+NAME: update_coefficients | |
| 311 | +#+BEGIN_SRC c | |
| 312 | +if (eq->bw != eq->pbw || eq->freq != eq->pfreq) { | |
| 313 | + SKFLT c; | |
| 314 | + eq->b = -cos(2 * M_PI * eq->freq / eq->sr); | |
| 315 | + c = tan(M_PI * eq->bw / eq->sr); | |
| 316 | + eq->a = (1.0 - c) / (1.0 + c); | |
| 317 | + | |
| 318 | + eq->pbw = eq->bw; | |
| 319 | + eq->pfreq = eq->freq; | |
| 320 | +} | |
| 321 | +#+END_SRC | |
| 322 | + | |
| 323 | +Compute difference equations. First the 2-pole filter, | |
| 324 | +followed by the 2-zero filter. The output of the 2-pole | |
| 325 | +filter goes into the 2-zero filter. See the previous section | |
| 326 | +on conversion to direct-form 2 for more information. | |
| 327 | + | |
| 328 | +#+NAME: compute_difference_equations | |
| 329 | +#+BEGIN_SRC c | |
| 330 | +v = in - eq->b*(1.0 + eq->a)*eq->v[0] - eq->a*eq->v[1]; | |
| 331 | +y = eq->a*v + eq->b*(1.0 + eq->a)*eq->v[0] + eq->v[1]; | |
| 332 | +#+END_SRC | |
| 333 | + | |
| 334 | +Compute the gain. This is done with the following equation: | |
| 335 | + | |
| 336 | +#+BEGIN_SRC tex | |
| 337 | +$$ | |
| 338 | +y = {((x + f) + g (x - f) \over 2} | |
| 339 | +$$ | |
| 340 | +#+END_SRC | |
| 341 | + | |
| 342 | +#+NAME: compute_gain | |
| 343 | +#+BEGIN_SRC c | |
| 344 | +out = ((in + y) + eq->gain*(in - y)) * 0.5; | |
| 345 | +#+END_SRC | |
| 346 | + | |
| 347 | +Where $y$ is the output, $x$ is the input signal, $g$ is | |
| 348 | +the gain amount, and =f= is the filtered version of =x=. | |
| 349 | + | |
| 350 | +Update filter state. The output of the 2-pole filter becomes | |
| 351 | +=v[0]=, or $v(n - 1)$, and the previous =v[0]= becomes | |
| 352 | +=v[1]=, or $v(n - 2)$. | |
| 353 | + | |
| 354 | +#+NAME: update_filter_state | |
| 355 | +#+BEGIN_SRC c | |
| 356 | +eq->v[1] = eq->v[0]; | |
| 357 | +eq->v[0] = v; | |
| 358 | +#+END_SRC | |
| @@ -0,0 +1,104 @@ | ||
| 1 | +#+TITLE: PhaseWarp | |
| 2 | +* Overview | |
| 3 | +PhaseWarp can be thought of as a special kind of filter for | |
| 4 | +normalized phasor signals. When used with a phasor and a | |
| 5 | +table-lookup oscillator this produces what is known as | |
| 6 | +[[https://en.wikipedia.org/wiki/Phase_distortion_synthesis][phase distortion synthesis]], famously used on Casio | |
| 7 | +synthesizers like the VZ-1. | |
| 8 | +* Algorithm | |
| 9 | +The algorithm for PhaseWarp works by adjusting the midpoint | |
| 10 | +position where the phasor reaches 0.5. The adjustment amount | |
| 11 | +is usually a normalized bipolar signal in the range -1 to 1. | |
| 12 | +When the position value is positive, it warps the midpoint | |
| 13 | +towards the upper bounds, causing a greater slope in the | |
| 14 | +upper half, while decreasing the slope in the lower half. | |
| 15 | +A negative position does opposite, creating a higher slope | |
| 16 | +in the lower half, and a lower slope in the upper half. | |
| 17 | + | |
| 18 | +(The whole thing makes a lot more sense when you graph it. | |
| 19 | +Someday, I'll do that here. For now, you'll just have to | |
| 20 | +take my word for it.) | |
| 21 | +* Tangled Files | |
| 22 | +=phasewarp.c= and =phasewarp.h=. | |
| 23 | + | |
| 24 | +#+NAME: phasewarp.c | |
| 25 | +#+BEGIN_SRC c :tangle phasewarp.c | |
| 26 | +#include "phasewarp.h" | |
| 27 | +<<funcs>> | |
| 28 | +#+END_SRC | |
| 29 | + | |
| 30 | +#+NAME: phasewarp.h | |
| 31 | +#+BEGIN_SRC c :tangle phasewarp.h | |
| 32 | +#ifndef SK_PHASEWARP_H | |
| 33 | +#define SK_PHASEWARP_H | |
| 34 | + | |
| 35 | +#ifndef SKFLT | |
| 36 | +#define SKFLT float | |
| 37 | +#endif | |
| 38 | + | |
| 39 | +<<funcdefs>> | |
| 40 | +#endif | |
| 41 | +#+END_SRC | |
| 42 | +* Compute | |
| 43 | +This algorithm is so simple, it can be done in a stateless | |
| 44 | +function called =sk_phasewarp_tick=. Its arguments are | |
| 45 | +the input phasor signal and the warp factor. | |
| 46 | + | |
| 47 | +#+NAME: funcdefs | |
| 48 | +#+BEGIN_SRC c | |
| 49 | +SKFLT sk_phasewarp_tick(SKFLT in, SKFLT warp); | |
| 50 | +#+END_SRC | |
| 51 | + | |
| 52 | +#+NAME: funcs | |
| 53 | +#+BEGIN_SRC c | |
| 54 | +SKFLT sk_phasewarp_tick(SKFLT in, SKFLT warp) | |
| 55 | +{ | |
| 56 | + SKFLT out; | |
| 57 | + SKFLT wmp; | |
| 58 | + | |
| 59 | + out = 0; | |
| 60 | + | |
| 61 | + <<calculate_warped_midpoint>> | |
| 62 | + <<determine_side_and_warp>> | |
| 63 | + return out; | |
| 64 | +} | |
| 65 | +#+END_SRC | |
| 66 | + | |
| 67 | +Calculate warped midpoint. Call it =wmp=. | |
| 68 | + | |
| 69 | +#+NAME: calculate_warped_midpoint | |
| 70 | +#+BEGIN_SRC c | |
| 71 | +wmp = (warp + 1.0) * 0.5; | |
| 72 | +#+END_SRC | |
| 73 | + | |
| 74 | +Determine side and compute. If the phasor position is | |
| 75 | +less than the warped midpoint, than compute | |
| 76 | +with lefthand slope. Otherwise, compute righthand slope. | |
| 77 | + | |
| 78 | +#+NAME: determine_side_and_warp | |
| 79 | +#+BEGIN_SRC c | |
| 80 | +if (in < wmp) { | |
| 81 | + <<compute_with_lefthand_slope>> | |
| 82 | +} else { | |
| 83 | + <<compute_with_righthand_slope>> | |
| 84 | +} | |
| 85 | +#+END_SRC | |
| 86 | + | |
| 87 | +Compute With Lefthand Slope. This slope is equal to | |
| 88 | +=0.5/wmp=. This value scales the phasor input =slope * in=. | |
| 89 | + | |
| 90 | +#+NAME: compute_with_lefthand_slope | |
| 91 | +#+BEGIN_SRC c | |
| 92 | +if (wmp != 0) out = ((SKFLT)0.5 / wmp) * in; | |
| 93 | +#+END_SRC | |
| 94 | + | |
| 95 | +Compute Righthand Slope. The righthand slope is computed as | |
| 96 | +=0.5/(1 - wmp)=. The output is computed as | |
| 97 | +=slope * (in - wmp) + 0.5=. | |
| 98 | + | |
| 99 | +#+NAME: compute_with_righthand_slope | |
| 100 | +#+BEGIN_SRC c | |
| 101 | +if (wmp != 1.0) { | |
| 102 | + out = ((SKFLT)0.5 / (SKFLT)(1.0 - wmp)) * (in - wmp) + 0.5; | |
| 103 | +} | |
| 104 | +#+END_SRC | |
| @@ -0,0 +1,403 @@ | ||
| 1 | +#+TITLE: R-Line | |
| 2 | +* Introduction | |
| 3 | +R-Line is a random line segment generator. It | |
| 4 | +produces random values at regular rate, and then | |
| 5 | +linearly interpolates between them. The algorithm has | |
| 6 | +audio-rate parametric control of the range of values, as | |
| 7 | +well as the frequency that values change. | |
| 8 | + | |
| 9 | +This algorithm is a great little control signal generator | |
| 10 | +that can add a bit of zest to a otherwise | |
| 11 | +static parameter in a patch. It's a very efficient way to | |
| 12 | +add a warm organic feel to a patch. | |
| 13 | +* Notable Implementation Details | |
| 14 | +This particular algorithm uses a fixed-point phasor similar | |
| 15 | +to the ones seen in @!(ref "osc")!@ and @!(ref "fmpair")!@. | |
| 16 | +Fixed point has very good numerical stability, which makes | |
| 17 | +it ideal for a phasor algorithm. | |
| 18 | + | |
| 19 | +To keep things more portable, R-Line implements its own | |
| 20 | +pseudo-Random Number Generator (pRNG), using a common | |
| 21 | +algorithm generating random values known as | |
| 22 | +[[https://en.wikipedia.org/wiki/Linear_congruential_generator][Linear Congruential Generator]], or an LCG. This is | |
| 23 | +what most =rand= function | |
| 24 | +implementations use under the hood. The modulus, multiplier, | |
| 25 | +and and increment values come from the Microsoft | |
| 26 | +Visual/Quick C/C++ library (as found on Wikipedia). | |
| 27 | +* Tangled Files | |
| 28 | +R-Line tangles to a C file and a header file. | |
| 29 | + | |
| 30 | +The C file =rline.c= contains the core implementation | |
| 31 | +code. | |
| 32 | + | |
| 33 | +#+NAME: rline.c | |
| 34 | +#+BEGIN_SRC c :tangle rline.c | |
| 35 | +#include <math.h> | |
| 36 | +#define SK_RLINE_PRIV | |
| 37 | +#include "rline.h" | |
| 38 | +<<macros>> | |
| 39 | +<<funcs>> | |
| 40 | +#+END_SRC | |
| 41 | + | |
| 42 | +The header file =rline.h= contains forward function | |
| 43 | +declarations. When =SK_RLINE_PRIV= is enabled, the structs | |
| 44 | +are exposed. Otherwise, they are left as opaque. | |
| 45 | + | |
| 46 | +#+NAME: rline.h | |
| 47 | +#+BEGIN_SRC c :tangle rline.h | |
| 48 | +#ifndef SK_RLINE_H | |
| 49 | +#define SK_RLINE_H | |
| 50 | + | |
| 51 | +#ifndef SKFLT | |
| 52 | +#define SKFLT float | |
| 53 | +#endif | |
| 54 | + | |
| 55 | +<<typedefs>> | |
| 56 | +<<funcdefs>> | |
| 57 | + | |
| 58 | +#ifdef SK_RLINE_PRIV | |
| 59 | +<<structs>> | |
| 60 | +#endif | |
| 61 | +#endif | |
| 62 | +#+END_SRC | |
| 63 | +* Core Struct | |
| 64 | +The core struct is called =sk_rline=. | |
| 65 | + | |
| 66 | +#+NAME: typedefs | |
| 67 | +#+BEGIN_SRC c | |
| 68 | +typedef struct sk_rline sk_rline; | |
| 69 | +#+END_SRC | |
| 70 | + | |
| 71 | +#+NAME: structs | |
| 72 | +#+BEGIN_SRC c | |
| 73 | +struct sk_rline { | |
| 74 | + <<sk_rline>> | |
| 75 | +}; | |
| 76 | +#+END_SRC | |
| 77 | +* Constants and Variables | |
| 78 | +The value from the LCG is often scaled to be normalized | |
| 79 | +between 0 and 1. The constant required for this is | |
| 80 | +$1/(2^31 - 1$, or =1/((1L<<31) - 1)=. This value is | |
| 81 | +stored in a constant. | |
| 82 | + | |
| 83 | +#+NAME: sk_rline | |
| 84 | +#+BEGIN_SRC c | |
| 85 | +SKFLT rngscale; | |
| 86 | +#+END_SRC | |
| 87 | + | |
| 88 | +#+NAME: set_random_scaler | |
| 89 | +#+BEGIN_SRC c | |
| 90 | +rl->rngscale = 1.0 / ((1L<<31) - 1); | |
| 91 | +#+END_SRC | |
| 92 | + | |
| 93 | +The current state of the random number generator is stored | |
| 94 | +as an integer =rng=. It is initialized to be =seed=. | |
| 95 | + | |
| 96 | +#+NAME: sk_rline | |
| 97 | +#+BEGIN_SRC c | |
| 98 | +int rng; | |
| 99 | +#+END_SRC | |
| 100 | + | |
| 101 | +#+NAME: init | |
| 102 | +#+BEGIN_SRC c | |
| 103 | +rl->rng = seed; | |
| 104 | +<<set_random_scaler>> | |
| 105 | +<<generate_initial_values>> | |
| 106 | +#+END_SRC | |
| 107 | + | |
| 108 | +The fixed point phasor has two major constants: the maximum | |
| 109 | +length, as well as the masking value. Detailed explanation | |
| 110 | +of these are beyond the scope of this document. | |
| 111 | + | |
| 112 | +#+NAME: macros | |
| 113 | +#+BEGIN_SRC c | |
| 114 | +#define SK_RLINE_PHSLEN 0x1000000L | |
| 115 | +#define SK_RLINE_PHSMSK 0xFFFFFFL | |
| 116 | +#+END_SRC | |
| 117 | + | |
| 118 | +The variable =maxlens= is maximum phasor length converted to | |
| 119 | +units of seconds by dviding it by the sampling rate. | |
| 120 | +Multipyling this value with a frequency will return an | |
| 121 | +increment value in units of phasor, which is used | |
| 122 | +as the phasor increment value. | |
| 123 | + | |
| 124 | +#+NAME: sk_rline | |
| 125 | +#+BEGIN_SRC c | |
| 126 | +SKFLT maxlens; | |
| 127 | +#+END_SRC | |
| 128 | + | |
| 129 | +#+NAME: init | |
| 130 | +#+BEGIN_SRC c | |
| 131 | +rl->maxlens = (SKFLT)SK_RLINE_PHSLEN / sr; | |
| 132 | +#+END_SRC | |
| 133 | + | |
| 134 | +Phase position is stored in an unsigned long called | |
| 135 | +=phspos=. When it needs to be converted for use in floating | |
| 136 | +point operations , the constant =scale= is used. | |
| 137 | + | |
| 138 | +#+NAME: sk_rline | |
| 139 | +#+BEGIN_SRC c | |
| 140 | +unsigned long phasepos; | |
| 141 | +SKFLT scale; | |
| 142 | +#+END_SRC | |
| 143 | + | |
| 144 | +#+NAME: init | |
| 145 | +#+BEGIN_SRC c | |
| 146 | +rl->phasepos = 0; | |
| 147 | +#+END_SRC | |
| 148 | + | |
| 149 | +The scale value works by normalizing to be in the range 0-1 | |
| 150 | +by dividing by the max phase length =SK_RLINE_PHSLEN=, then | |
| 151 | +scaling it to be the difference between the start/end point | |
| 152 | +values. Doing this shaves off a divide operation later. | |
| 153 | + | |
| 154 | +#+NAME: calculate_initial_scale | |
| 155 | +#+BEGIN_SRC c | |
| 156 | +rl->scale = (rl->end - rl->start) / SK_RLINE_PHSLEN; | |
| 157 | +#+END_SRC | |
| 158 | + | |
| 159 | +A line has two points: a start point, and an end point. | |
| 160 | +These are stored as normalized floating point variables | |
| 161 | +=start= and =end=, and then are dynamically scaled to the | |
| 162 | +=min= and =max= values during computation. | |
| 163 | + | |
| 164 | +#+NAME: sk_rline | |
| 165 | +#+BEGIN_SRC c | |
| 166 | +SKFLT start; | |
| 167 | +SKFLT end; | |
| 168 | +#+END_SRC | |
| 169 | + | |
| 170 | +#+NAME: generate_initial_values | |
| 171 | +#+BEGIN_SRC c | |
| 172 | +rl->rng = LCG(rl->rng); | |
| 173 | +rl->start = RNG(rl->rng) * rl->rngscale; | |
| 174 | +rl->rng = LCG(rl->rng); | |
| 175 | +rl->end = RNG(rl->rng) * rl->rngscale; | |
| 176 | +<<calculate_initial_scale>> | |
| 177 | +#+END_SRC | |
| 178 | +* Linear Congruential Generator | |
| 179 | +An internal | |
| 180 | +[[https://en.wikipedia.org/wiki/Linear_congruential_generator][Linear Congruential Generator]] is | |
| 181 | +used to generate sequences of pseudo-random numbers. | |
| 182 | + | |
| 183 | +It is the following equation | |
| 184 | + | |
| 185 | +$$ | |
| 186 | +y(n) = ((y(n - 1) * a + c) \gg 1) | |
| 187 | +\& m | |
| 188 | +$$ | |
| 189 | + | |
| 190 | +Where $m$ is the masking value =0x7ffffff=, $a$ is a | |
| 191 | +=multiplier=, and $c$ is the =increment=. In this | |
| 192 | +implementation, $a$ will be =0x343fd=, and $c$ will be | |
| 193 | +=0x2693ec3=. These constants come from the wikipedia page | |
| 194 | +on LCGs, and originate the Microsoft Visual C/C++ standard | |
| 195 | +library. | |
| 196 | + | |
| 197 | +The LCG here can be implemented as a stateless function or | |
| 198 | +macro. In this case, we will go with the macro. | |
| 199 | + | |
| 200 | +#+NAME: macros | |
| 201 | +#+BEGIN_SRC c | |
| 202 | +#define LCG(y) (y * 0x343fd + 0x269ec3) | |
| 203 | +#+END_SRC | |
| 204 | + | |
| 205 | +The =LCG= operation only computes the next state state of | |
| 206 | +the random-number generator. To actually get it within the | |
| 207 | +correct bounds for this sytem, it has to be right-shifted | |
| 208 | +to knock it down 1 bit, then masked by =0x7ffffff= as a kind | |
| 209 | +of modulo operation. | |
| 210 | + | |
| 211 | +This macro operation =RNG= assumes that =y= is the current | |
| 212 | +state of the LCG. | |
| 213 | +#+NAME: macros | |
| 214 | +#+BEGIN_SRC c | |
| 215 | +#define RNG(y) ((y >> 1) & 0x7fffffff) | |
| 216 | +#+END_SRC | |
| 217 | +* Initialization | |
| 218 | +Initialization is done with =sk_rline_init=. | |
| 219 | + | |
| 220 | +The main things needed for initialization are the sampling | |
| 221 | +rate =sr=, as well as the initial seed value for the random | |
| 222 | +number generator. | |
| 223 | + | |
| 224 | +#+NAME: funcdefs | |
| 225 | +#+BEGIN_SRC c | |
| 226 | +void sk_rline_init(sk_rline *rl, int sr, int seed); | |
| 227 | +#+END_SRC | |
| 228 | + | |
| 229 | +#+NAME: funcs | |
| 230 | +#+BEGIN_SRC c | |
| 231 | +void sk_rline_init(sk_rline *rl, int sr, int seed) | |
| 232 | +{ | |
| 233 | + <<init>> | |
| 234 | +} | |
| 235 | +#+END_SRC | |
| 236 | +* Parameters | |
| 237 | +** Minimum value | |
| 238 | +Set with =sk_rline_min=. | |
| 239 | + | |
| 240 | +#+NAME: funcdefs | |
| 241 | +#+BEGIN_SRC c | |
| 242 | +void sk_rline_min(sk_rline *rl, SKFLT min); | |
| 243 | +#+END_SRC | |
| 244 | + | |
| 245 | +#+NAME: funcs | |
| 246 | +#+BEGIN_SRC c | |
| 247 | +void sk_rline_min(sk_rline *rl, SKFLT min) | |
| 248 | +{ | |
| 249 | + rl->min = min; | |
| 250 | +} | |
| 251 | +#+END_SRC | |
| 252 | + | |
| 253 | +#+NAME: sk_rline | |
| 254 | +#+BEGIN_SRC c | |
| 255 | +SKFLT min; | |
| 256 | +#+END_SRC | |
| 257 | + | |
| 258 | +Initialized to be 0. | |
| 259 | + | |
| 260 | +#+NAME: init | |
| 261 | +#+BEGIN_SRC c | |
| 262 | +sk_rline_min(rl, 0); | |
| 263 | +#+END_SRC | |
| 264 | +** Maximum value | |
| 265 | +Set with =sk_rline_max=. | |
| 266 | + | |
| 267 | +#+NAME: funcdefs | |
| 268 | +#+BEGIN_SRC c | |
| 269 | +void sk_rline_max(sk_rline *rl, SKFLT max); | |
| 270 | +#+END_SRC | |
| 271 | + | |
| 272 | +#+NAME: funcs | |
| 273 | +#+BEGIN_SRC c | |
| 274 | +void sk_rline_max(sk_rline *rl, SKFLT max) | |
| 275 | +{ | |
| 276 | + rl->max= max; | |
| 277 | +} | |
| 278 | +#+END_SRC | |
| 279 | + | |
| 280 | +#+NAME: sk_rline | |
| 281 | +#+BEGIN_SRC c | |
| 282 | +SKFLT max; | |
| 283 | +#+END_SRC | |
| 284 | + | |
| 285 | +Initialized to be 1. | |
| 286 | + | |
| 287 | +#+NAME: init | |
| 288 | +#+BEGIN_SRC c | |
| 289 | +sk_rline_max(rl, 1); | |
| 290 | +#+END_SRC | |
| 291 | +** Rate | |
| 292 | +Set with =sk_rline_rate=. | |
| 293 | + | |
| 294 | +#+NAME: funcdefs | |
| 295 | +#+BEGIN_SRC c | |
| 296 | +void sk_rline_rate(sk_rline *rl, SKFLT rate); | |
| 297 | +#+END_SRC | |
| 298 | + | |
| 299 | +#+NAME: funcs | |
| 300 | +#+BEGIN_SRC c | |
| 301 | +void sk_rline_rate(sk_rline *rl, SKFLT rate) | |
| 302 | +{ | |
| 303 | + rl->rate= rate; | |
| 304 | +} | |
| 305 | +#+END_SRC | |
| 306 | + | |
| 307 | +#+NAME: sk_rline | |
| 308 | +#+BEGIN_SRC c | |
| 309 | +SKFLT rate; | |
| 310 | +#+END_SRC | |
| 311 | + | |
| 312 | +Initialized to be 1. | |
| 313 | + | |
| 314 | +#+NAME: init | |
| 315 | +#+BEGIN_SRC c | |
| 316 | +sk_rline_rate(rl, 1); | |
| 317 | +#+END_SRC | |
| 318 | +* Computing a sample | |
| 319 | +A single sample is computed with =sk_rline_tick=. | |
| 320 | + | |
| 321 | +#+NAME: funcdefs | |
| 322 | +#+BEGIN_SRC c | |
| 323 | +SKFLT sk_rline_tick(sk_rline *rl); | |
| 324 | +#+END_SRC | |
| 325 | + | |
| 326 | +#+NAME: funcs | |
| 327 | +#+BEGIN_SRC c | |
| 328 | +SKFLT sk_rline_tick(sk_rline *rl) | |
| 329 | +{ | |
| 330 | + SKFLT out; | |
| 331 | + | |
| 332 | + out = 0; | |
| 333 | + | |
| 334 | + <<compute_current_sample>> | |
| 335 | + <<update_phase>> | |
| 336 | + <<generate_next_line_segment>> | |
| 337 | + | |
| 338 | + return out; | |
| 339 | +} | |
| 340 | +#+END_SRC | |
| 341 | + | |
| 342 | +Compute the current sample. The line interpolation is | |
| 343 | +calculated in a normalized space, then scaled to be | |
| 344 | +in the min/max range. Doing it this way allows the min/max | |
| 345 | +values to be dynamically changed over time without having | |
| 346 | +to wait for the next line. | |
| 347 | + | |
| 348 | +The normalized output can be computed with the expression: | |
| 349 | + | |
| 350 | +$$ | |
| 351 | +y = x_1 + pc | |
| 352 | +$$ | |
| 353 | + | |
| 354 | +Where $x_1$ is the starting point of the line, $p$ is | |
| 355 | +the current phase increment, represented in fixed point, and | |
| 356 | +$c$ is the constant that normalizes and scales the phase to | |
| 357 | +be the amount of progress to value $x_2$. | |
| 358 | + | |
| 359 | +#+NAME: compute_current_sample | |
| 360 | +#+BEGIN_SRC c | |
| 361 | +out = rl->start + rl->phasepos*rl->scale; | |
| 362 | +out = out * (rl->max - rl->min) + rl->min; | |
| 363 | +#+END_SRC | |
| 364 | + | |
| 365 | + | |
| 366 | +Update phase position. The phase is updated by incrementing | |
| 367 | +it by a amount obtained by multilying the frequency by the | |
| 368 | +maximum phase length in units of seconds. How this works is | |
| 369 | +beyond the scope of this document, but is explained in @!(ref | |
| 370 | +"osc")!@. | |
| 371 | + | |
| 372 | +#+NAME: update_phase | |
| 373 | +#+BEGIN_SRC c | |
| 374 | +rl->phasepos += floor(rl->rate * rl->maxlens); | |
| 375 | +#+END_SRC | |
| 376 | + | |
| 377 | +Generate next line segment. Preparation for a new line | |
| 378 | +segment happens when the phase of the phasor reaches the | |
| 379 | +end, and is greater than or equal to the max length. | |
| 380 | +The phasor is masked in order to filter out upper | |
| 381 | +bits and allow the lower bits to roll over. The the starting | |
| 382 | +value is set to be the current end value, and a new end | |
| 383 | +value is obtained using the random number generator. | |
| 384 | + | |
| 385 | +After the new points have been obtained, the constant used | |
| 386 | +to normalize + scale the phasor value is computed. Dividing | |
| 387 | +by the maximum phasor length normalizes the phasor to be in | |
| 388 | +range 0 and 1. Multiplying by the difference of the two | |
| 389 | +segment values scales this value to be in the correct range. | |
| 390 | +This constant is useful because it shaves off a divide | |
| 391 | +operation, which has traditionally been costly arithmetic | |
| 392 | +computatoin compared to a multiply. | |
| 393 | + | |
| 394 | +#+NAME: generate_next_line_segment | |
| 395 | +#+BEGIN_SRC c | |
| 396 | +if (rl->phasepos >= SK_RLINE_PHSLEN) { | |
| 397 | + rl->phasepos &= SK_RLINE_PHSMSK; | |
| 398 | + rl->start = rl->end; | |
| 399 | + rl->rng = LCG(rl->rng); | |
| 400 | + rl->end = RNG(rl->rng) * rl->rngscale; | |
| 401 | + rl->scale = (rl->end - rl->start) / SK_RLINE_PHSLEN; | |
| 402 | +} | |
| 403 | +#+END_SRC | |
| @@ -0,0 +1,56 @@ | ||
| 1 | +#+TITLE: Scrubber | |
| 2 | +* Overview | |
| 3 | +An FFT based in-memory sample scrubber with pitch control. | |
| 4 | +* Tangled Files | |
| 5 | +* FFT procedures, and Soundpipe | |
| 6 | +One could spend a whole lifetime building FFT algorithms, | |
| 7 | +so an existing FFT implementation is used. For FFT and | |
| 8 | +inverse-FFT operations, the Soundpipe fft utilities are | |
| 9 | +used. So yes, Soundpipe is a hard dependency for this | |
| 10 | +algorithm. Or at least, bits of Soundpipe are. The FFT | |
| 11 | +portions can easily be extracted from the Soundpipe | |
| 12 | +codebase. | |
| 13 | + | |
| 14 | +The soundpipe FFT utility is a wrapper around a public | |
| 15 | +domain FFT implementation by John Green. | |
| 16 | +* Initialization | |
| 17 | +Done with =sk_scrubber_init=. | |
| 18 | +* Cleanup | |
| 19 | +Done with =sk_scrubber_cleanup=. Mainly done for the sake | |
| 20 | +of the =sp_fft=. Future iterations will try to remove any | |
| 21 | +mandatory use of =malloc= and =free=. | |
| 22 | +* Constants | |
| 23 | +Scrubber uses a fixed FFT size, which greatly simplifies | |
| 24 | +things. The FFT size chosen is 2048, or =2^11=. It's a good | |
| 25 | +value that equal parts effeciency and fidelity. | |
| 26 | + | |
| 27 | +The LOG2 of this FFT size is 11, and this is what is passed | |
| 28 | +into the soundpipe FFT interface. | |
| 29 | + | |
| 30 | +The hopsize determines how quickly to slide along the FFT | |
| 31 | +window. In this case it's 512, or the FFT size divided up | |
| 32 | +into 4 bits. | |
| 33 | + | |
| 34 | +This divisor is a significant constant that will be | |
| 35 | +stored as a constant as well. | |
| 36 | +* Parameters | |
| 37 | +** Position | |
| 38 | +Sets the current read position of the buffer, in samples. | |
| 39 | +** Playback | |
| 40 | +Sets the relative playback speed. 1 is normal, 2 is 2x | |
| 41 | +speed, and 0.5 is 2x slower. | |
| 42 | +* Computation | |
| 43 | +Read interpolated samples from buffer forward/backward | |
| 44 | +frames. | |
| 45 | + | |
| 46 | +Take FFT of frames | |
| 47 | + | |
| 48 | +Phase Vocode. | |
| 49 | + | |
| 50 | +Inverse FFT operation. | |
| 51 | + | |
| 52 | +Overlap-add to output frames. | |
| 53 | + | |
| 54 | +Write to output and scale; | |
| 55 | + | |
| 56 | +Update counter and frame position. | |
| @@ -0,0 +1,376 @@ | ||
| 1 | +#+TITLE: Vardelay | |
| 2 | +* Overview | |
| 3 | +Vardelay is an implementation of a variable-length delay | |
| 4 | +line, using third order [[https://ccrma.stanford.edu/~jos/pasp/Lagrange_Interpolation.html][Langrange Interpolation]]. | |
| 5 | +This delay time of this delay line can be adjusted over | |
| 6 | +time. Because it uses interpolation, any adjustments can | |
| 7 | +cause pitch shifting to occur. This itself is often a | |
| 8 | +desirable artifact, and is the basis for audio effects | |
| 9 | +like flanging and chorusing. | |
| 10 | + | |
| 11 | +In addition to delay time, a feedback parameter will also be | |
| 12 | +supplied, as it is so often used with delay lines. | |
| 13 | +* Tangled Files | |
| 14 | +=vardelay.c= and =vardelay.h= are the tangled files. | |
| 15 | + | |
| 16 | +#+NAME: vardelay.c | |
| 17 | +#+BEGIN_SRC c :tangle vardelay.c | |
| 18 | +#include <math.h> | |
| 19 | +#include <stdlib.h> | |
| 20 | +#define SK_VARDELAY_PRIV | |
| 21 | +#include "vardelay.h" | |
| 22 | +<<funcs>> | |
| 23 | +#+END_SRC | |
| 24 | + | |
| 25 | +If =SK_VARDELAY_PRIV= is defined, private structs are made | |
| 26 | +public. | |
| 27 | + | |
| 28 | +#+NAME: vardelay.h | |
| 29 | +#+BEGIN_SRC c :tangle vardelay.h | |
| 30 | +#ifndef SK_VARDELAY_H | |
| 31 | +#define SK_VARDELAY_H | |
| 32 | + | |
| 33 | +#ifndef SKFLT | |
| 34 | +#define SKFLT float | |
| 35 | +#endif | |
| 36 | + | |
| 37 | +<<typedefs>> | |
| 38 | +<<funcdefs>> | |
| 39 | +#ifdef SK_VARDELAY_PRIV | |
| 40 | +<<structs>> | |
| 41 | +#endif | |
| 42 | +#endif | |
| 43 | +#+END_SRC | |
| 44 | +* Initialization | |
| 45 | +Initialized with =sk_vardelay_init=. must supply your own | |
| 46 | +buffer, with buffer size. This way, no internal memory | |
| 47 | +allocation is required. | |
| 48 | + | |
| 49 | +#+NAME: funcdefs | |
| 50 | +#+BEGIN_SRC c | |
| 51 | +void sk_vardelay_init(sk_vardelay *vd, int sr, | |
| 52 | + SKFLT *buf, unsigned long sz); | |
| 53 | +#+END_SRC | |
| 54 | + | |
| 55 | +#+NAME: funcs | |
| 56 | +#+BEGIN_SRC c | |
| 57 | +void sk_vardelay_init(sk_vardelay *vd, int sr, | |
| 58 | + SKFLT *buf, unsigned long sz) | |
| 59 | +{ | |
| 60 | + <<init>> | |
| 61 | +} | |
| 62 | +#+END_SRC | |
| 63 | + | |
| 64 | +Anything buffer size less than 4 samples is not enough | |
| 65 | +to do third-order interpolation, so it returns an error. | |
| 66 | +* Struct | |
| 67 | +Called =sk_vardelay=. | |
| 68 | + | |
| 69 | +#+NAME: typedefs | |
| 70 | +#+BEGIN_SRC c | |
| 71 | +typedef struct sk_vardelay sk_vardelay; | |
| 72 | +#+END_SRC | |
| 73 | + | |
| 74 | +#+NAME: structs | |
| 75 | +#+BEGIN_SRC c | |
| 76 | +struct sk_vardelay { | |
| 77 | + <<sk_vardelay>> | |
| 78 | +}; | |
| 79 | +#+END_SRC | |
| 80 | +* Constants and Variables | |
| 81 | +** Sample rate | |
| 82 | +a copy of the sampling rate is stored in the struct. | |
| 83 | +It is used to convert delay time seconds to samples. | |
| 84 | + | |
| 85 | +#+NAME: sk_vardelay | |
| 86 | +#+BEGIN_SRC c | |
| 87 | +int sr; | |
| 88 | +#+END_SRC | |
| 89 | + | |
| 90 | +#+NAME: init | |
| 91 | +#+BEGIN_SRC c | |
| 92 | +vd->sr = sr; | |
| 93 | +#+END_SRC | |
| 94 | +** Buffer, Buffer Size | |
| 95 | +The delay buffer is the chunk of memory where samples will | |
| 96 | +be read/written. It is assumed to be neatly pre-allocated | |
| 97 | +and zeroed out externally. The size is also assumed to be | |
| 98 | +known ahead of time and correct. | |
| 99 | + | |
| 100 | +If the buffer size is less than 4, the delay line is | |
| 101 | +disabled, as there aren't enough samples to do the | |
| 102 | +interpolation. | |
| 103 | + | |
| 104 | +#+NAME: sk_vardelay | |
| 105 | +#+BEGIN_SRC c | |
| 106 | +SKFLT *buf; | |
| 107 | +unsigned long sz; | |
| 108 | +#+END_SRC | |
| 109 | + | |
| 110 | +#+NAME: init | |
| 111 | +#+BEGIN_SRC c | |
| 112 | +if (sz < 4) { | |
| 113 | + vd->buf = NULL; | |
| 114 | + vd->buf = 0; | |
| 115 | +} else { | |
| 116 | + vd->buf = buf; | |
| 117 | + vd->sz = sz; | |
| 118 | +} | |
| 119 | +#+END_SRC | |
| 120 | +** Previous output | |
| 121 | +A variable is used to store the output of the previous delay | |
| 122 | +in what is known as a single-sample delay or a unit delay. | |
| 123 | +This delay is used to introduce feedback into the system. | |
| 124 | +In discrete digital systems like this one, unit delays are | |
| 125 | +inevitable for feedback, which is why they are referred to | |
| 126 | +as *implicit* delays. | |
| 127 | + | |
| 128 | +#+NAME: sk_vardelay | |
| 129 | +#+BEGIN_SRC c | |
| 130 | +SKFLT prev; | |
| 131 | +#+END_SRC | |
| 132 | + | |
| 133 | +#+NAME: init | |
| 134 | +#+BEGIN_SRC c | |
| 135 | +vd->prev = 0; | |
| 136 | +#+END_SRC | |
| 137 | +** Write Position | |
| 138 | +The write position is used to store the current index | |
| 139 | +position in the buffer being written to. The write position | |
| 140 | +starts at zero, moves forward until it reaches the end of | |
| 141 | +the buffer, and then goes back to the beginning again. The | |
| 142 | +delayed samples start the write position, and move backwards | |
| 143 | +in time. If that position is a negative, it wraps around. | |
| 144 | + | |
| 145 | +Notably, the write position is a signed =long= to compensate | |
| 146 | +for negative values. This is needed for the delay to read | |
| 147 | +stuff back in time. | |
| 148 | + | |
| 149 | +#+NAME: sk_vardelay | |
| 150 | +#+BEGIN_SRC c | |
| 151 | +long writepos; | |
| 152 | +#+END_SRC | |
| 153 | + | |
| 154 | +#+NAME: init | |
| 155 | +#+BEGIN_SRC c | |
| 156 | +vd->writepos = 0; | |
| 157 | +#+END_SRC | |
| 158 | +* Parameters | |
| 159 | +** Delay Time | |
| 160 | +Set with =sk_vardelay_delay=, In units of seconds. | |
| 161 | + | |
| 162 | +#+NAME: funcdefs | |
| 163 | +#+BEGIN_SRC c | |
| 164 | +void sk_vardelay_delay(sk_vardelay *vd, SKFLT delay); | |
| 165 | +#+END_SRC | |
| 166 | + | |
| 167 | +#+NAME: funcs | |
| 168 | +#+BEGIN_SRC c | |
| 169 | +void sk_vardelay_delay(sk_vardelay *vd, SKFLT delay) | |
| 170 | +{ | |
| 171 | + vd->delay = delay; | |
| 172 | +} | |
| 173 | +#+END_SRC | |
| 174 | + | |
| 175 | +#+NAME: sk_vardelay | |
| 176 | +#+BEGIN_SRC c | |
| 177 | +SKFLT delay; | |
| 178 | +#+END_SRC | |
| 179 | + | |
| 180 | +The initial delay time is set to be half the delay time, in | |
| 181 | +units of seconds. | |
| 182 | + | |
| 183 | +#+NAME: init | |
| 184 | +#+BEGIN_SRC c | |
| 185 | +sk_vardelay_delay(vd, ((SKFLT)sz / sr) * 0.5); | |
| 186 | +#+END_SRC | |
| 187 | +** Feedback | |
| 188 | +Set with =sk_vardelay_feedback=. Should be a value between | |
| 189 | +0 and 1. | |
| 190 | + | |
| 191 | +#+NAME: funcdefs | |
| 192 | +#+BEGIN_SRC c | |
| 193 | +void sk_vardelay_feedback(sk_vardelay *vd, SKFLT feedback); | |
| 194 | +#+END_SRC | |
| 195 | + | |
| 196 | +#+NAME: funcs | |
| 197 | +#+BEGIN_SRC c | |
| 198 | +void sk_vardelay_feedback(sk_vardelay *vd, SKFLT feedback) | |
| 199 | +{ | |
| 200 | + vd->feedback = feedback; | |
| 201 | +} | |
| 202 | +#+END_SRC | |
| 203 | + | |
| 204 | +#+NAME: sk_vardelay | |
| 205 | +#+BEGIN_SRC c | |
| 206 | +SKFLT feedback; | |
| 207 | +#+END_SRC | |
| 208 | + | |
| 209 | +No feedback by default. | |
| 210 | + | |
| 211 | +#+NAME: init | |
| 212 | +#+BEGIN_SRC c | |
| 213 | +sk_vardelay_feedback(vd, 0); | |
| 214 | +#+END_SRC | |
| 215 | +* Computation | |
| 216 | +Done with =sk_vardelay_tick=. | |
| 217 | + | |
| 218 | +#+NAME: funcdefs | |
| 219 | +#+BEGIN_SRC c | |
| 220 | +SKFLT sk_vardelay_tick(sk_vardelay *vd, SKFLT in); | |
| 221 | +#+END_SRC | |
| 222 | + | |
| 223 | +#+NAME: funcs | |
| 224 | +#+BEGIN_SRC c | |
| 225 | +SKFLT sk_vardelay_tick(sk_vardelay *vd, SKFLT in) | |
| 226 | +{ | |
| 227 | + SKFLT out; | |
| 228 | + SKFLT dels; | |
| 229 | + SKFLT f; | |
| 230 | + long i; | |
| 231 | + SKFLT s[4]; | |
| 232 | + unsigned long n[4]; | |
| 233 | + SKFLT a, b, c, d; | |
| 234 | + | |
| 235 | + out = 0; | |
| 236 | + <<return_if_empty>> | |
| 237 | + <<write_to_buffer>> | |
| 238 | + <<calculate_read_position>> | |
| 239 | + <<wrap_and_flip>> | |
| 240 | + <<read_samples>> | |
| 241 | + <<calculate_interpolation_coefficients>> | |
| 242 | + <<interpolate>> | |
| 243 | + <<update_position>> | |
| 244 | + <<update_feedback>> | |
| 245 | + | |
| 246 | + return out; | |
| 247 | +} | |
| 248 | +#+END_SRC | |
| 249 | + | |
| 250 | +If buffer is NULL or size is 0, return 0. | |
| 251 | + | |
| 252 | +#+NAME: return_if_empty | |
| 253 | +#+BEGIN_SRC c | |
| 254 | +if (vd->buf == NULL || vd->sz == 0) return 0; | |
| 255 | +#+END_SRC | |
| 256 | + | |
| 257 | +Write to buffer with feedback. | |
| 258 | + | |
| 259 | +#+NAME: write_to_buffer | |
| 260 | +#+BEGIN_SRC c | |
| 261 | +vd->buf[vd->writepos] = in + vd->prev * vd->feedback; | |
| 262 | +#+END_SRC | |
| 263 | + | |
| 264 | +Calculate the read position. This is two parts: a fractional | |
| 265 | +component, and an integer component. | |
| 266 | + | |
| 267 | +Get floating point + integer components of delay time. | |
| 268 | +The delay time position is set to be the write position | |
| 269 | +=dels= samples in the past, where =dels= is the delay time | |
| 270 | +in samples. | |
| 271 | + | |
| 272 | +#+NAME: calculate_read_position | |
| 273 | +#+BEGIN_SRC c | |
| 274 | +dels = vd->delay * vd->sr; | |
| 275 | +i = floor(dels); | |
| 276 | +f = i - dels; | |
| 277 | +i = vd->writepos - i; | |
| 278 | +#+END_SRC | |
| 279 | + | |
| 280 | +Because we're looking backwards, the fractional component is | |
| 281 | +backwards too. A fractional value here is set to be | |
| 282 | +negative (reaching back in time). | |
| 283 | + | |
| 284 | +If there is a fractional component that is negative, or the | |
| 285 | +integer component is negative, these must be corrected. | |
| 286 | + | |
| 287 | +The fractional value is flipped to be positive by adding 1 | |
| 288 | +to itself. The integer position is set back in time one | |
| 289 | +sample. This sets the interpolation up so that instead of | |
| 290 | +taking a sample and interpolating backwards, you start with | |
| 291 | +the previous sample and move forwards. The integer position | |
| 292 | +is then set to be in bounds. | |
| 293 | + | |
| 294 | +By default, the position is wrapped around using addition | |
| 295 | +until the sample is in bounds. | |
| 296 | + | |
| 297 | +#+NAME: wrap_and_flip | |
| 298 | +#+BEGIN_SRC c | |
| 299 | +if ((f < 0.0) || (i < 0)) { | |
| 300 | + /* flip fractional component */ | |
| 301 | + f = f + 1.0; | |
| 302 | + /* go backwards one sample */ | |
| 303 | + i = i - 1; | |
| 304 | + while (i < 0) i += vd->sz; | |
| 305 | +} else while(i >= vd->sz) i -= vd->sz; | |
| 306 | +#+END_SRC | |
| 307 | + | |
| 308 | +Read samples. This includes the current sample, the previous | |
| 309 | +sample, and two samples in the future. | |
| 310 | + | |
| 311 | +#+NAME: read_samples | |
| 312 | +#+BEGIN_SRC c | |
| 313 | +/* x(n) */ | |
| 314 | +n[1] = i; | |
| 315 | + | |
| 316 | +/* x(n + 1) */ | |
| 317 | +if (i == (vd->sz - 1)) n[2] = 0; | |
| 318 | +else n[2] = n[1] + 1; | |
| 319 | + | |
| 320 | +/* x(n - 1) */ | |
| 321 | +if (i == 0) n[0] = vd->sz - 1; | |
| 322 | +else n[0] = i - 1; | |
| 323 | + | |
| 324 | +if (n[2] == vd->sz - 1) n[3] = 0; | |
| 325 | +else n[3] = n[2] + 1; | |
| 326 | + | |
| 327 | +{ | |
| 328 | + int j; | |
| 329 | + for (j = 0; j < 4; j++) s[j] = vd->buf[n[j]]; | |
| 330 | +} | |
| 331 | +#+END_SRC | |
| 332 | + | |
| 333 | +Calculate interpolation coefficients. These four | |
| 334 | +coefficients correspond with the four samples read. | |
| 335 | + | |
| 336 | +#+NAME: calculate_interpolation_coefficients | |
| 337 | +#+BEGIN_SRC c | |
| 338 | +{ | |
| 339 | + SKFLT tmp[2]; | |
| 340 | + | |
| 341 | + d = ((f * f) - 1) * 0.1666666667; | |
| 342 | + tmp[0] = (f + 1.0) * 0.5; | |
| 343 | + tmp[1] = 3.0 * d; | |
| 344 | + a = tmp[0] - 1.0 - d; | |
| 345 | + c = tmp[0] - tmp[1]; | |
| 346 | + b = tmp[1] - f; | |
| 347 | +} | |
| 348 | +#+END_SRC | |
| 349 | + | |
| 350 | +Interpolate. This follows the following equation: | |
| 351 | + | |
| 352 | +$$ | |
| 353 | +y(n) = (a x(n - 1) + b x(n) + c x(n + 1) + d x(n + 2)) \cdot f + x(n) | |
| 354 | +$$ | |
| 355 | + | |
| 356 | +#+NAME: interpolate | |
| 357 | +#+BEGIN_SRC c | |
| 358 | +out = (a*s[0] + b*s[1] + c*s[2] + d*s[3]) * f + s[1]; | |
| 359 | +#+END_SRC | |
| 360 | + | |
| 361 | +Update position. Increment the write position, and wrap | |
| 362 | +back to zero if it reaches the end of the delay buffer. | |
| 363 | + | |
| 364 | +#+NAME: update_position | |
| 365 | +#+BEGIN_SRC c | |
| 366 | +vd->writepos++; | |
| 367 | +if (vd->writepos == vd->sz) vd->writepos = 0; | |
| 368 | +#+END_SRC | |
| 369 | + | |
| 370 | +Update feedback. The current output is set to be the =prev= | |
| 371 | +value in the delay line. | |
| 372 | + | |
| 373 | +#+NAME: update_feedback | |
| 374 | +#+BEGIN_SRC c | |
| 375 | +vd->prev = out; | |
| 376 | +#+END_SRC | |