From d6b9eb15578f1f4eb08d67fb3a9f9f8dbd5c85b4 Mon Sep 17 00:00:00 2001 From: TiagoLr Date: Thu, 28 Dec 2023 14:50:54 +0000 Subject: [PATCH] Release JSAdditiv v1.3 (#359) replace zdf with rbj filters fix note clicking fix volume knob clicking --- Synth/tilr_JSAdditiv.jsfx | 118 ++--- Synth/tilr_JSAdditiv/add.gfxlib.jsfx-inc | 1 - Synth/tilr_JSAdditiv/add.rbj_filter.jsfx-inc | 433 +++++++++++++++++++ Synth/tilr_JSAdditiv/add.zdf_filter.jsfx-inc | 403 ----------------- 4 files changed, 476 insertions(+), 479 deletions(-) create mode 100644 Synth/tilr_JSAdditiv/add.rbj_filter.jsfx-inc delete mode 100644 Synth/tilr_JSAdditiv/add.zdf_filter.jsfx-inc diff --git a/Synth/tilr_JSAdditiv.jsfx b/Synth/tilr_JSAdditiv.jsfx index 57a07be..2e163ac 100644 --- a/Synth/tilr_JSAdditiv.jsfx +++ b/Synth/tilr_JSAdditiv.jsfx @@ -1,10 +1,10 @@ desc: JSAdditiv author: tilr -version: 1.2 +version: 1.3 changelog: - Fix support large wave files - Fix import wave samplerate - Support file drag and drop + replace zdf with rbj filters + fix note clicking + fix volume knob clicking provides: tilr_JSAdditiv/add.adsr.jsfx-inc tilr_JSAdditiv/add.array.jsfx-inc @@ -12,7 +12,6 @@ provides: tilr_JSAdditiv/add.gfxlib.jsfx-inc tilr_JSAdditiv/add.mouselib.jsfx-inc tilr_JSAdditiv/add.wavetable.jsfx-inc - tilr_JSAdditiv/add.zdf_filter.jsfx-inc [data] tilr_JSAdditiv/Complex 1.wav [data] tilr_JSAdditiv/Complex 2.wav [data] tilr_JSAdditiv/Complex 3.wav @@ -38,6 +37,7 @@ provides: [data] tilr_JSAdditiv/Square 2.wav [data] tilr_JSAdditiv/Stairs.wav [data] tilr_JSAdditiv/Triangle.wav + tilr_JSAdditiv/add.rbj_filter.jsfx-inc screenshot: https://raw.githubusercontent.com/tiagolr/JSAdditiv/master/doc/ss.png about: # JSAdditiv @@ -84,7 +84,7 @@ slider26:pitch_rel=500<1, 5000, 1:log>-Pitch Release import add.wavetable.jsfx-inc import add.array.jsfx-inc import add.adsr.jsfx-inc -import add.zdf_filter.jsfx-inc +import add.rbj_filter.jsfx-inc import add.gfxlib.jsfx-inc import add.mouselib.jsfx-inc import add.fft_real_synth.jsfx-inc @@ -120,6 +120,20 @@ function db2gain (db) local (val) ( function normalize_vol_slider(val) ( val * 60 / 100 - 60 ); function note2freq(n) ( 440 * pow(2, (n - 69) / 12); ); +function rc_set(rc) + instance(a) ( + a = 1 / (rc * srate + 1); +); +function rc_lp(sample) + instance(lp, a) ( + lp += a * (sample - lp); +); +function smooth() + instance (lp, smooth) ( + lp = smooth; + smooth = this.rc_lp(this); +); + function normalize_wave(buf, len) local (_min, _max, i) ( @@ -189,7 +203,7 @@ function resynthesize() ( semi_normalize_wave(wavebuf, four.size * 2); // correct signal strenght after summing frequencies ); -function read_file(filehandle) +function read_file(filehandle) local (len) ( filehandle > 0 ? ( @@ -226,58 +240,12 @@ function read_file_string(str) ( read_file(filehandle); ); -// copy filter coeficients from buffer1 to buffer2 -function filter_copy_coefs(buf1, buf2) ( - buf2[2] = buf1[2]; - buf2[3] = buf1[3]; - buf2[4] = buf1[4]; -); - -// wraps filter function using buffers -function filter_setf(buf, freq, q) ( - filter.zdf_setf(freq, q); - buf[2] = filter.g; - buf[3] = filter.r2; - buf[4] = filter.h; -); - -// wraps filter function using buffers -function filter_lp(buf, sample) local(lp) ( - filter.s1 = buf[0]; - filter.s2 = buf[1]; - filter.g = buf[2]; - filter.r2 = buf[3]; - filter.h = buf[4]; - lp = filter.zdf_svf_lp(sample); - buf[0] = filter.s1; - buf[1] = filter.s2; - lp; -); - -// wraps filter function using buffers -function filter_bp(buf, sample) local(bp) ( - filter.s1 = buf[0]; - filter.s2 = buf[1]; - filter.g = buf[2]; - filter.r2 = buf[3]; - filter.h = buf[4]; - bp = filter.zdf_svf_bp(sample); - buf[0] = filter.s1; - buf[1] = filter.s2; - bp; -); - -// wraps filter function using buffers -function filter_hp(buf, sample) local(hp) ( - filter.s1 = buf[0]; - filter.s2 = buf[1]; - filter.g = buf[2]; - filter.r2 = buf[3]; - filter.h = buf[4]; - hp = filter.zdf_svf_hp(sample); - buf[0] = filter.s1; - buf[1] = filter.s2; - hp; +function copy_filter_coefs (f1, f2) ( + f2[0] = f1[0]; // a1 + f2[1] = f1[1]; // a2 + f2[2] = f1[2]; // b0 + f2[3] = f1[3]; // b1 + f2[4] = f1[4]; // b2 ); function on_slider() ( @@ -296,6 +264,10 @@ function on_slider() ( // FIX - read initial wave from string instead of slider read_file_string("tilr_JSAdditiv/Sine 1.wav"); +// Init smoothing +gain.rc_set(0.0033); +gain.smooth = db2gain(normalize_vol_slider(vol)); + @serialize file_var(0, osc_free); file_mem(0, wavebuf, wavelen + four.size * 4); @@ -395,6 +367,7 @@ while (midirecv(offset, msg1, note, vel)) ( ); @sample +gain.smooth(); ptr = poly.array_first(); while(ptr >= 0) ( // for each note/voice @@ -430,34 +403,29 @@ while(ptr >= 0) ( // for each note/voice flt_shape != 0 ? ( filterbuf = filter_env + ptr[0] * 7; // envelope buffer adsr_process(filterbuf); - filterbuf_l = filter_arr_l + ptr[0] * 11; // filter buffer left - filterbuf_r = filter_arr_r + ptr[0] * 11; // filter buffer right multiplier = pow(20000/flt_freq, filterbuf[0] * flt_amt / 100); - filter_setf(filterbuf_l, flt_freq * multiplier, flt_q); - filter_copy_coefs(filterbuf_l, filterbuf_r); + filterbuf_l = filter_arr_l + ptr[0] * 11; // filter buffer left + filterbuf_r = filter_arr_r + ptr[0] * 11; // filter buffer left - flt_shape == 1 ? ( - outl = filter_lp(filterbuf_l, outl); - outr = filter_lp(filterbuf_r, outr); + flt_shape == 1 ? ( + rbj_lp(filterbuf_l, flt_freq * multiplier, flt_q); ) : flt_shape == 2 ? ( - outl = filter_bp(filterbuf_l, outl); - outr = filter_bp(filterbuf_r, outr); + rbj_bp(filterbuf_l, flt_freq * multiplier, flt_q); ) : ( - outl = filter_hp(filterbuf_l, outl); - outr = filter_hp(filterbuf_r, outr); + rbj_hp(filterbuf_l, flt_freq * multiplier, flt_q); ); + copy_filter_coefs(filterbuf_l, filterbuf_r); + outl = rbj_df1(filterbuf_l, outl); + outr = rbj_df1(filterbuf_r, outr); ); - spl0 += outl * gain; - spl1 += outr * gain; + spl0 += outl * gain.smooth; + spl1 += outr * gain.smooth; ptr = poly.array_next(ptr); ); -spl0 += outl * gain; -spl1 += outr * gain; - @gfx 520 270 gfx_clear = COLOR_BG; diff --git a/Synth/tilr_JSAdditiv/add.gfxlib.jsfx-inc b/Synth/tilr_JSAdditiv/add.gfxlib.jsfx-inc index ee89f28..df20794 100644 --- a/Synth/tilr_JSAdditiv/add.gfxlib.jsfx-inc +++ b/Synth/tilr_JSAdditiv/add.gfxlib.jsfx-inc @@ -202,7 +202,6 @@ local (harm_w, amp, i, mouse_active, hx, ph, yamp, phase, real, img, yphase) ); phase_mode && mouse_active && mouse_in_rect(hx, y, harm_w, h) ? ( yphase = mouse.control ? 0 : (mouse.y - y) / h * -2 - 1; - yphasee = yphase; amp = harmonic_amplitude(buf[i], buf[i+1]); buf[i+1] = amp * cos(yphase*$pi); // real buf[i] = amp * sin(yphase*$pi); // img diff --git a/Synth/tilr_JSAdditiv/add.rbj_filter.jsfx-inc b/Synth/tilr_JSAdditiv/add.rbj_filter.jsfx-inc new file mode 100644 index 0000000..981c874 --- /dev/null +++ b/Synth/tilr_JSAdditiv/add.rbj_filter.jsfx-inc @@ -0,0 +1,433 @@ +desc:2nd-order RBJ filter + +// Copyright (C) 2012-2022 Theo Niessink +// This work is free. You can redistribute it and/or modify it under the +// terms of the Do What The Fuck You Want To Public License, Version 2, +// as published by Sam Hocevar. See http://www.wtfpl.net/ for more details. + +// Based on "Cookbook formulae for audio EQ biquad filter coefficients" by +// Robert Bristow-Johnson. +// http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt + +/* Example + + desc:Low-pass filter + + slider1:1000<20,20000,1>Cutoff (Hz) + slider2:0.5<0.01,4.0,0.01>Q + + import Tale/rbj_filter.jsfx-inc + + @slider + lp.rbj_lp(slider1, slider2); + lp.rbj_gain(0.5); + + @sample + spl0 = spl1 = lp.rbj_df1(spl0 + spl1); + + Setting Functions + + * rbj_lp(freq, q) -- Low-pass + * rbj_hp(freq, q) -- High-pass + * rbj_bp(freq, q) -- Band-pass (constant skirt gain) + * rbj_bp2(freq, q) -- Band-pass (constant peak gain) + * rbj_bs(freq, q) -- Band-stop + * rbj_ap(freq, q) -- All-pass + * rbj_eq(freq, q, gain) -- Peaking EQ + * rbj_ls(freq, q, gain) -- Low-shelf + * rbj_hs(freq, q, gain) -- High-shelf + Example: lp.rbj_lp(1000, 0.5); + Sets up the filter for the specified cutoff frequency (in Hz), and Q + and gain factors, and returns the a0 coefficient. + + (To convert from dB to gain: gain=10^(db/20).) + + * rbj_gain(gain) + * rbj_dry_wet(dry, wet) + Example: lp.rbj_lp(1000, 0.5); lp.rbj_gain(-2.0); + Example: lp.rbj_lp(1000, 0.5); lp.rbj_dry_wet(0.1, 0.9); + Modifies the filter by applying the specified output gain or dry/wet + mix. + + Note: You should always first setup the filter, and then modify it. If + you change the filter frequency/Q afterwards, then this will reset the + gain and dry/wet values, and so you will have to modify them again. + + Filter Functions + + * rbj_df1(sample) -- Direct Form 1 + * rbj_df2(sample) -- Direct Form 2 + * rbj_tdf2(sample) -- Transposed Direct Form 2 + Example: output = lp.rbj_tdf2(input); + Sends a sample through the filter, and returns its output. + + Miscellaneous Functions + + * rbj_reset_df1([input]) -- Direct Form 1 + * rbj_reset_df2([input]) -- Direct Form 2 + * rbj_reset_tdf2([input]) -- Transposed Direct Form 2 + Example: lp.rbj_reset_tdf2(); + Resets the filter state to the specified input value, or to zero if + the value is omitted. + + * rbj_bwtoq(bw) + * rbj_qtobw(q) + Example: q = rbj_bwtoq(2.0); + Converts bandwidth (in octaves) to Q factor, or vice versa. + + Instance Variables + + * a1 + * a2 + * b0 + * b1 + * b2 + Example: lp2.a1 = lp1.a1; lp2.a2 = lp1.a2; lp2.b0 = lp1.b0; lp2.b1 = lp1.b1; lp2.b2 = lp1.b2; + Filter coefficients. + + Note: The first coefficient (a0) is not included here, because all + coefficients are scaled (i.e. divided) by a0, after which a0 itself + would always be 1. The setting functions return the original a0 value, + should you need it (e.g. to get the original, non-scaled + coefficients). + + * x0 + * x1 + * y0 + * y1 + Example: current_input = lp.x0; + Example: previous_output = lp.y1; + Direct Form 1 inputs/outputs. + + * w0 + * w1 + Example: lp2.w0 = lp1.w0; lp2.w1 = lp1.w1; + Direct Form 2 filter state. + + * s0 + * s1 + Example: lp2.s0 = lp1.s0; lp2.s1 = lp1.s1; + Transposed Direct Form 2 filter state. + +*/ + +/* + Modified lib to work with buffers + + 0 = a1 + 1 = a2 + 2 = b0 + 3 = b1 + 4 = b2 + 5 = x0 + 6 = x1 + 7 = y0 + 8 = y1 + 9 = w0 + 10 = w1 +*/ + +@init + +function rbj_bwtoq(bw) + local(x) +( + // q = 1/(2 * sinh(log(2) / 2 * bw)) + x = exp(0.5*log(2) * bw); + x/(sqr(x) - 1); +); + +function rbj_qtobw(q) + local(x) +( + // bw = 2 * asinh(1/(2 * q)) / log(2) + x = 0.5 / q; + 2/log(2) * log(x + sqrt(sqr(x) + 1)); +); + +// Low-pass + +function rbj_lp(buf, freq, q) + // global(srate) + //instance(a1, a2, b0, b1, b2) + local(w0, alpha, a0, scale) +( + w0 = 2*$pi * min(freq / srate, 0.49); + alpha = sin(w0) / (2*q); + + scale = 1/(a0 = 1 + alpha); + buf[0] = cos(w0) * -2 * scale; + buf[1] = (1 - alpha) * scale; + + buf[4] = buf[2] = (1 + buf[0] + buf[1]) * 0.25; + buf[3] = buf[2] * 2; + + a0; +); + +// High-pass + +function rbj_hp(buf, freq, q) + // global(srate) + // instance(a1, a2, b0, b1, b2) + local(w0, alpha, a0, scale) +( + w0 = 2*$pi * min(freq / srate, 0.49); + alpha = sin(w0) / (2*q); + + scale = 1/(a0 = 1 + alpha); + buf[0] = cos(w0) * -2 * scale; + buf[1] = (1 - alpha) * scale; + + buf[4] = buf[2] = (1 - buf[0] + buf[1]) * 0.25; + buf[3] = buf[2] * -2; + + a0; +); + +// Band-pass (constant skirt gain, peak gain = Q) + +function rbj_bp(buf, freq, q) + // global(srate) + // instance(a1, a2, b0, b1, b2) + local(w0, alpha, a0, scale) +( + w0 = 2*$pi * min(freq / srate, 0.49); + alpha = sin(w0) / (2*q); + + scale = 1/(a0 = 1 + alpha); + buf[0] = cos(w0) * -2 * scale; + buf[1] = (1 - alpha) * scale; + + buf[4] = -(buf[2] = (1 - buf[1]) * 0.5 * q); + buf[3] = 0; + + a0; +); + +// Band-pass (constant 0 dB peak gain) +/* +function rbj_bp2(freq, q) + // global(srate) + instance(a1, a2, b0, b1, b2) + local(w0, alpha, a0, scale) +( + w0 = 2*$pi * min(freq / srate, 0.49); + alpha = sin(w0) / (2*q); + + scale = 1/(a0 = 1 + alpha); + a1 = cos(w0) * -2 * scale; + a2 = (1 - alpha) * scale; + + b2 = -(b0 = (1 - a2) * 0.5); + b1 = 0; + + a0; +); +*/ +// Band-stop +/* +function rbj_bs(freq, q) + // global(srate) + instance(a1, a2, b0, b1, b2) + local(w0, alpha, a0, scale) +( + w0 = 2*$pi * min(freq / srate, 0.49); + alpha = sin(w0) / (2*q); + + scale = 1/(a0 = 1 + alpha); + a1 = cos(w0) * -2 * scale; + a2 = (1 - alpha) * scale; + + b2 = b0 = scale; + b1 = a1; + + a0; +); +*/ +// All-pass +/* +function rbj_ap(freq, q) + // global(srate) + instance(a1, a2, b0, b1, b2) + local(w0, alpha, a0, scale) +( + w0 = 2*$pi * min(freq / srate, 0.49); + alpha = sin(w0) / (2*q); + + scale = 1/(a0 = 1 + alpha); + b1 = a1 = cos(w0) * -2 * scale; + b0 = a2 = (1 - alpha) * scale; + + b2 = 1; + + a0; +); +*/ +// Peaking EQ +/* +function rbj_eq(freq, q, gain) + // global(srate) + instance(a1, a2, b0, b1, b2) + local(w0, alpha, a, tmp, a0, scale) +( + w0 = 2*$pi * min(freq / srate, 0.49); + alpha = sin(w0) / (2*q); + a = sqrt(gain); + + tmp = alpha / a; + + scale = 1/(a0 = 1 + tmp); + b1 = a1 = cos(w0) * -2 * scale; + a2 = (1 - tmp) * scale; + + tmp = alpha * a * scale; + + b0 = scale + tmp; + b2 = scale - tmp; + + a0; +); +*/ +// Low-shelf +/* +function rbj_ls(freq, q, gain) + // global(srate) + instance(a1, a2, b0, b1, b2) + local(w0, cos_w0, a, tmp0, tmp1, tmp2, a0, scale) +( + w0 = 2*$pi * min(freq / srate, 0.49); + cos_w0 = cos(w0); + a = sqrt(gain); + + tmp0 = (a - 1) * cos_w0 + a + 1; + tmp1 = sqrt(a) * sin(w0) / q; + tmp2 = (a + 1) * cos_w0; + + scale = 1/(a0 = tmp0 + tmp1); + a1 = (1 - a - tmp2) * 2 * scale; + a2 = (tmp0 - tmp1) * scale; + + tmp0 = (1 - a) * cos_w0 + a + 1; + scale *= a; + + b0 = (tmp0 + tmp1) * scale; + b1 = (a - 1 - tmp2) * 2 * scale; + b2 = (tmp0 - tmp1) * scale; + + a0; +); +*/ +// High-shelf +/* +function rbj_hs(freq, q, gain) + // global(srate) + instance(a1, a2, b0, b1, b2) + local(w0, cos_w0, a, tmp0, tmp1, tmp2, a0, scale) +( + w0 = 2*$pi * min(freq / srate, 0.49); + cos_w0 = cos(w0); + a = sqrt(gain); + + tmp0 = (1 - a) * cos_w0 + a + 1; + tmp1 = sqrt(a) * sin(w0) / q; + tmp2 = (a + 1) * cos_w0; + + scale = 1/(a0 = tmp0 + tmp1); + a1 = (a - 1 - tmp2) * 2 * scale; + a2 = (tmp0 - tmp1) * scale; + + tmp0 = (a - 1) * cos_w0 + a + 1; + scale *= a; + + b0 = (tmp0 + tmp1) * scale; + b1 = (1 - a - tmp2) * 2 * scale; + b2 = (tmp0 - tmp1) * scale; + + a0; +); +*/ +/* +function rbj_gain(gain) + instance(b0, b1, b2) +( + b0 *= gain; + b1 *= gain; + b2 *= gain; +); +*/ +/* +function rbj_dry_wet(dry, wet) + instance(a1, a2, b0, b1, b2) +( + b0 = b0 * wet + dry; + b1 = b1 * wet + a1 * dry; + b2 = b2 * wet + a2 * dry; +); +*/ +// Direct Form 1 + +function rbj_df1(buf, sample) + //instance(a1, a2, b0, b1, b2, x0, x1, y0, y1) + local(x2, y2) +( + x2 = buf[6]; + buf[6] = buf[5]; + buf[5] = sample; + + y2 = buf[8]; + buf[8] = buf[7]; + buf[7] = buf[2]*buf[5] + buf[3]*buf[6] + buf[4]*x2 - buf[0]*buf[8] - buf[1]*y2; +); + +/* +function rbj_reset_df1(input) + instance(a1, a2, b0, b1, b2, x0, x1, y0, y1) +( + x0 = x1 = input; + y0 = y1 = input / (1 + a1 + a2) * (b0 + b1 + b2); +); +*/ + +// Direct Form 2 +function rbj_df2(buf, sample) + // instance(a1, a2, b0, b1, b2, w0, w1) + local(w2) +( + w2 = buf[10]; + buf[10] = buf[9]; + buf[9] = sample - buf[0]*buf[10] - buf[1]*w2; + + buf[2]*buf[9] + buf[3]*buf[10] + buf[4]*w2; +); + +/* +function rbj_reset_df2(input) + instance(a1, a2, w0, w1) +( + w0 = w1 = input / (1 + a1 + a2); +); +*/ +// Transposed Direct Form 2 + +/* +function rbj_tdf2(sample) + instance(a1, a2, b0, b1, b2, s0, s1) + local(y) +( + y = b0 * sample + s0; + + s0 = b1 * sample - a1*y + s1; + s1 = b2 * sample - a2*y; + + y; +); +*/ +/* +function rbj_reset_tdf2(input) + instance(a1, a2, b0, b1, b2, s0, s1) +( + s0 = ((b0 + b1 + b2) / (1 + a1 + a2) - b0) * input; + s1 = b2 * input - a2 * (b0 * input + s0); +); +*/ diff --git a/Synth/tilr_JSAdditiv/add.zdf_filter.jsfx-inc b/Synth/tilr_JSAdditiv/add.zdf_filter.jsfx-inc deleted file mode 100644 index 48c4b58..0000000 --- a/Synth/tilr_JSAdditiv/add.zdf_filter.jsfx-inc +++ /dev/null @@ -1,403 +0,0 @@ -desc:2nd-order zero-delay feedback state variable filter - -// Copyright (C) 2013-2021 Theo Niessink -// This work is free. You can redistribute it and/or modify it under the -// terms of the Do What The Fuck You Want To Public License, Version 2, -// as published by Sam Hocevar. See http://www.wtfpl.net/ for more details. - -// Initially based on the rsStateVariableFilter C++ class by Robin Schmidt, -// as posted (in the public domain) on the KVR forum. -// http://www.kvraudio.com/forum/viewtopic.php?p=5243733#p5243733 - -/* Example - - desc:Low-pass filter - - slider1:1000<20,20000,1>Cutoff (Hz) - slider2:0.5<0.01,4.0,0.01>Q - - import Tale/zdf_filter.jsfx-inc - - @slider - lp.zdf_lp(slider1, slider2); - lp.zdf_gain(0.5); - - @sample - spl0 = spl1 = lp.zdf_svf(spl0 + spl1); - - Setting Functions - - * zdf_lp(freq, q) -- Low-pass - * zdf_hp(freq, q) -- High-pass - * zdf_bp(freq, q) -- Band-pass (constant skirt gain) - * zdf_bp2(freq, q) -- Band-pass (constant peak gain) - * zdf_bs(freq, q) -- Band-stop - * zdf_ap(freq, q) -- All-pass - * zdf_eq(freq, q, gain) -- Peaking EQ - * zdf_ls(freq, q, gain) -- Low-shelf - * zdf_hs(freq, q, gain) -- High-shelf - Example: lp.zdf_lp(1000, 0.7); - Sets up the filter for the specified cutoff frequency (in Hz), and Q - and gain factors, and returns the feedback precomputation factor (h). - - (To convert from dB to gain: gain=10^(db/20).) - - Note: In v20151024 the behavior of zdf_bp2() and zdf_ap() has been - changed in such a way that these functions are not backward - compatible. To convert code relying on the old behavior, replace - zdf_bp2(freq, bw) with zdf_bp(freq, zdf_bwtoq(bw)), and - zdf_ap(freq, bw) with zdf_ap(freq, zdf_bwtoq(bw)). - - * zdf_gain(gain) - Example: lp.zdf_lp(1000, 0.5); lp.zdf_gain(2.0); - Modifies the filter by applying the specified output gain. - - Note: You should always first setup the filter, and then modify it. If - you change the filter frequency/Q afterwards, then this will reset the - gain to 1.0, and so you will have to modify it again. - - * zdf_setf(freq, q) - Example: lp.zdf_setf(1000, 0.7); - Sets up the specialized low-pass, high-pass, or band-pass filter. - - Note: This works only with zdf_svf_lp(), zdf_svf_hp(), or zdf_svf_bp(). - - Filter Functions - - * zdf_svf(sample) - Example: output = lp.zdf_svf(input); - Sends a sample through the filter, and returns its output. - - * zdf_svf_multi(sample) - Example: output = lp.zdf_svf_multi(input); - Sends a sample through the filter, returns its output, and also stores - the individual low-pass, band-pass, and high-pass outputs. - - * zdf_svf_lp(sample) -- Low-pass - * zdf_svf_hp(sample) -- High-pass - * zdf_svf_bp(sample) -- Band-pass - Example: output = lp.zdf_svf_lp(input); - Specialized versions of zdf_svf(), each optimized for a specific - filter type. - - Miscellaneous Functions - - * zdf_reset([input]) - Example: lp.zdf_reset(); - Resets the filter state to the specified input value, or to zero if - the value is omitted. - - * zdf_bwtoq(bw) - * zdf_qtobw(q) - Example: q = zdf_bwtoq(2.0); - Converts bandwidth (in octaves) to Q factor, or vice versa. - - Instance Variables - - * g -- Embedded integrator gain - * r2 -- Damping (1/Q) - * h -- Feedback precomputation factor - Example: lp2.g = lp1.g; lp2.r2 = lp1.r2; lp2.h = lp1.h; - Filter coefficients. - - * cl -- Low-pass mix - * cb -- Band-pass mix - * ch -- High-pass mix - Example: lp2.cl = lp1.cl; lp2.cb = lp1.cb; lp2.ch = lp1.ch; - Filter mode output mix. - - * s1 - * s2 - Example: lp2.s1 = lp1.s1; lp2.s2 = lp1.s2; - Filter state. - - * yl -- Low-pass output - * yb -- Band-pass output - * yh -- High-pass output - Example: hp = lp.yh; - Multi-mode filter outputs. - -*/ - -@init - -function zdf_bwtoq(bw) - local(x) -( - // q = 1/(2 * sinh(log(2) / 2 * bw)) - x = exp(0.5*log(2) * bw); - x/(sqr(x) - 1); -); - -function zdf_qtobw(q) - local(x) -( - // bw = 2 * asinh(1/(2 * q)) / log(2) - x = 0.5 / q; - 2/log(2) * log(x + sqrt(sqr(x) + 1)); -); - -function _zdf_seth() - instance(g, r2, h) -( - h = 1/((r2 + g)*g + 1); -); - -// Low-pass - -function zdf_lp(freq, q) - // global(srate) - instance(g, r2, cl, cb, ch) -( - g = tan($pi * min(freq / srate, 0.49)); - - r2 = 1/q; - cl = 1; - cb = 0; - ch = 0; - - this._zdf_seth(); -); - -// High-pass - -function zdf_hp(freq, q) - // global(srate) - instance(g, r2, cl, cb, ch) -( - g = tan($pi * min(freq / srate, 0.49)); - - r2 = 1/q; - cl = 0; - cb = 0; - ch = 1; - - this._zdf_seth(); -); - -// Band-pass (constant skirt gain, peak gain = Q) - -function zdf_bp(freq, q) - // global(srate) - instance(g, r2, cl, cb, ch) -( - g = tan($pi * min(freq / srate, 0.49)); - - r2 = 1/q; - cl = 0; - cb = 1; - ch = 0; - - this._zdf_seth(); -); - -// Band-pass (constant 0 dB peak gain) - -function zdf_bp2(freq, q) - // global(srate) - instance(g, r2, cl, cb, ch) -( - g = tan($pi * min(freq / srate, 0.49)); - - cl = 0; - cb = r2 = 1/q; - ch = 0; - - this._zdf_seth(); -); - -// Band-stop - -function zdf_bs(freq, q) - // global(srate) - instance(g, r2, cl, cb, ch) -( - g = tan($pi * min(freq / srate, 0.49)); - - r2 = 1/q; - cl = 1; - cb = 0; - ch = 1; - - this._zdf_seth(); -); - -// All-pass - -function zdf_ap(freq, q) - // global(srate) - instance(g, r2, cl, cb, ch) -( - g = tan($pi * min(freq / srate, 0.49)); - - r2 = 1/q; - cl = 1; - cb = -r2; - ch = 1; - - this._zdf_seth(); -); - -// Peaking EQ - -function zdf_eq(freq, q, gain) - // global(srate) - instance(g, r2, cl, cb, ch) -( - g = tan($pi * min(freq / srate, 0.49)); - - r2 = 1/(q * sqrt(gain)); - cl = 1; - cb = r2 * gain; - ch = 1; - - this._zdf_seth(); -); - -// Low-shelf - -function zdf_ls(freq, q, gain) - // global(srate) - instance(g, r2, cl, cb, ch) - local(a) -( - a = sqrt(gain); - g = tan($pi * min(freq / srate, 0.49)) / sqrt(a); - - r2 = 1/q; - cl = gain; - cb = r2 * a; - ch = 1; - - this._zdf_seth(); -); - -// High-shelf - -function zdf_hs(freq, q, gain) - // global(srate) - instance(g, r2, cl, cb, ch) - local(a) -( - a = sqrt(gain); - g = tan($pi * min(freq / srate, 0.49)) * sqrt(a); - - r2 = 1/q; - cl = 1; - cb = r2 * a; - ch = gain; - - this._zdf_seth(); -); - -function zdf_gain(gain) - instance(cl, cb, ch) -( - cl *= gain; - cb *= gain; - ch *= gain; -); - -function zdf_svf(sample) - instance(s1, s2, g, r2, h, cl, cb, ch) - local(yl, yb, yh) -( - // High-pass - yh = (sample - (r2 + g) * s1 - s2) * h; - - // Band-pass - yb = g*yh + s1; - s1 = g*yh + yb; - - // Zero denormals - abs(s1) < 0.00000000000000006 ? s1 = 0; - - // Low-pass - yl = g*yb + s2; - s2 = g*yb + yl; - - abs(s2) < 0.00000000000000006 ? s2 = 0; - - cl*yl + cb*yb + ch*yh; -); - -function zdf_svf_multi(sample) - instance(s1, s2, g, r2, h, cl, cb, ch, yl, yb, yh) -( - yh = (sample - (r2 + g) * s1 - s2) * h; - yb = g*yh + s1; - s1 = g*yh + yb; - abs(s1) < 0.00000000000000006 ? s1 = 0; - yl = g*yb + s2; - s2 = g*yb + yl; - abs(s2) < 0.00000000000000006 ? s2 = 0; - cl*yl + cb*yb + ch*yh; -); - -// Optimized versions of zdf_svf() returning only 1 of the 3 outputs. - -function zdf_setf(freq, q) - // global(srate) - instance(g, r2) -( - g = tan($pi * min(freq / srate, 0.49)); - r2 = 1/q; - this._zdf_seth(); -); - -function zdf_svf_lp(sample) - instance(s1, s2, g, r2, h) - local(yl, yb, yh) -( - yh = (sample - (r2 + g) * s1 - s2) * h; - yb = g*yh + s1; s1 = g*yh + yb; - abs(s1) < 0.00000000000000006 ? s1 = 0; - yl = g*yb + s2; s2 = g*yb + yl; - abs(s2) < 0.00000000000000006 ? s2 = 0; - yl; -); - -function zdf_svf_hp(sample) - instance(s1, s2, g, r2, h) - local(yl, yb, yh) -( - yh = (sample - (r2 + g) * s1 - s2) * h; - yb = g*yh + s1; s1 = g*yh + yb; - abs(s1) < 0.00000000000000006 ? s1 = 0; - yl = g*yb + s2; s2 = g*yb + yl; - abs(s2) < 0.00000000000000006 ? s2 = 0; - yh; -); - -function zdf_svf_bp(sample) - instance(s1, s2, g, r2, h) - local(yl, yb, yh) -( - yh = (sample - (r2 + g) * s1 - s2) * h; - yb = g*yh + s1; s1 = g*yh + yb; - abs(s1) < 0.00000000000000006 ? s1 = 0; - yl = g*yb + s2; s2 = g*yb + yl; - abs(s2) < 0.00000000000000006 ? s2 = 0; - yb; -); - -// Reset SVF state. - -function zdf_reset(input) - instance(s1, s2) -( - s1 = 0; - s2 = input; -); - -// Legacy - -// function zdf_bp2(freq, bw) ( this.zdf_bp(freq, zdf_bwtoq(bw)) ); -// function zdf_ap(freq, bw) ( this.zdf_ap(freq, zdf_bwtoq(bw)) ); -function zdf_notch(freq, bw) ( this.zdf_bs(freq, zdf_bwtoq(bw)) ); -function zdf_peak(freq, gain, bw) local(fc) ( fc = $pi * min(freq / srate, 0.49); this.zdf_eq(freq, zdf_bwtoq(bw) * fc / tan(fc), gain); ); -function zdf_low_shelf(freq, gain, bw) ( this.zdf_ls(freq, zdf_bwtoq(bw), gain) ); -function zdf_high_shelf(freq, gain, bw) ( this.zdf_hs(freq, zdf_bwtoq(bw), gain) ); -function zdf_mute() instance(g, r2, cl, cb, ch) ( g = tan(0.49*$pi); r2 = 1; cl = cb = ch = 0; this._zdf_seth(); ); -function zdf_bypass() instance(g, r2, cl, cb, ch) ( g = tan(0.49*$pi); r2 = cl = cb = ch = 1; this._zdf_seth(); ); -function zdf_bypass(freq, q) ( this.zdf_bypass() );