diff --git a/Synth/tilr_JS3Osc.jsfx b/Synth/tilr_JS3Osc.jsfx new file mode 100644 index 0000000..c3ee0df --- /dev/null +++ b/Synth/tilr_JS3Osc.jsfx @@ -0,0 +1,592 @@ +desc: JS3Osc +author: tilr +version: 1.0 +provides: + tilr_JS3Osc/3o.adsr.jsfx-inc + tilr_JS3Osc/3o.array.jsfx-inc + tilr_JS3Osc/3o.gfxlib.jsfx-inc + tilr_JS3Osc/3o.mouselib.jsfx-inc + tilr_JS3Osc/3o.zdf_filter.jsfx-inc +screenshot: https://raw.githubusercontent.com/tiagolr/js3osc/master/doc/ss.png +about: + # JS3Osc + + Polyphonic synth based of IL 3xOsc. + + Features: + * Polyphonic voicing + * Filter, pitch and amplitude envelopes per voice + * Three oscillators with 6 wave types + * Oscillator phase stereo detuning and offset + +desc:JS3Osc +tags: synth, instrument + +slider1:maxpoly=16<2, 32, 1>-Polyphony + +slider2:osc1_vol=-18<-60, 0, .01>-Osc1 Volume +slider3:osc1_wave=0<0, 5, 1{Sine,Triangle,Saw,Square,Pulse,Noise}>-Osc1 Wave +slider4:osc1_phase_offset=0<-1,1,0.01>-Osc1 Phase Offset +slider5:osc1_phase_det=0<-100, 100, 1>-Osc1 Phase Detune +slider6:osc1_coarse=0<-24, 24, 1>-Osc1 Coarse +slider7:osc1_fine=0<-99, 99, 1>-Osc1 Fine +slider8:osc1_pan=0<-1, 1, 0.01>-Osc1 Pan +slider9:osc1_inv=0<0, 1, 1{No, Yes}>-Osc1 Invert phase + +slider11:osc2_vol=-18<-60, 0, .01>-Osc2 Volume +slider12:osc2_wave=0<0, 5, 1{Sine,Triangle,Saw,Square,Pulse,Noise}>-Osc2 Wave +slider13:osc2_phase_offset=0<-1,1,0.01>-Osc2 Phase Offset +slider14:osc2_phase_det=0<-100, 100, 1>-Osc2 Phase Detune +slider15:osc2_coarse=-12<-24, 24, 1>-Osc2 Coarse +slider16:osc2_fine=0<-99, 99, 1>-Osc2 Fine +slider17:osc2_pan=0<-1, 1, 0.01>-Osc2 Pan +slider18:osc2_inv=0<0, 1, 1{No, Yes}>-Osc2 Invert phase + +slider20:osc3_vol=-18<-60, 0, .01>-Osc3 Volume +slider21:osc3_wave=0<0, 5, 1{Sine,Triangle,Saw,Square,Pulse,Noise}>-Osc3 Wave +slider22:osc3_phase_offset=0<-1,1,0.01>-Osc3 Phase Offset +slider23:osc3_phase_det=0<-100, 100, 1>-Osc3 Phase Detune +slider24:osc3_coarse=-24<-24, 24, 1>-Osc3 Coarse +slider25:osc3_fine=0<-99, 99, 1>-Osc3 Fine +slider26:osc3_pan=0<-1, 1, 0.01>-Osc3 Pan +slider27:osc3_inv=0<0, 1, 1{No, Yes}>-Osc3 Invert phase + +slider30:env_att=5<1, 10000, 1:log>-Attack +slider31:env_dec=1<1, 10000, 1:log>-Decay +slider32:env_sus=0<-90, 0, .1:log>-Sustain Db +slider33:env_rel=500<1, 10000, 1:log>-Release + +slider35:pitch_amt=0<-24,24,0.1>-Pitch amount +slider36:pitch_att=1<1, 10000, 1:log>-Pitch Attack +slider37:pitch_dec=600<1, 10000, 1:log>-Pitch Decay +slider38:pitch_sus=0<0, 1, 0.01>-Pitch Sustain +slider39:pitch_rel=500<1, 10000, 1:log>-Pitch Release + +slider40:filter_shape=0<0,3,1{Off,Low Pass,Band Pass,High Pass}>-Filter shape +slider41:filter_freq=10000<20, 22000, 1:log>-Filter frequency +slider42:filter_q=0.70<0.01, 40, 0.01:log>-Filter Q +slider43:filter_adsr_amt=0<-1, 1, 0.01>-Filter ASDR amt. +slider44:filter_att=1<1, 10000, 1:log>-Filter Attack +slider45:filter_dec=100<1, 10000, 1:log>-Filter Decay +slider46:filter_sus=0<0, 1, 0.01>-Filter Sustain +slider47:filter_rel=500<1, 10000, 1:log>-Filter Release + +slider50:rand_phase=0<0,1,0.01>-Rand phase + +import 3o.adsr.jsfx-inc +import 3o.array.jsfx-inc +import 3o.zdf_filter.jsfx-inc +import 3o.mouselib.jsfx-inc +import 3o.gfxlib.jsfx-inc + +options:gfx_hz=60 no_meter + +@init + +gfx_sel_envelope = 0; + +poly.array_init(0, 128, 9); // [note, velocity, freq, phase-left, phase-right, ph2l, ph2r, ph3l, ph3r] +envelope = 2000; // 128 * 7 buffer for keys adsr +remove_ptrs.array_init(4000, 128, 1); +pitch_env = 5000; // 128 * 7 buffer for pitch adsr +filter_arr_l = 6000; // 128 * 11 buffer for filters one per key left channel +filter_arr_r = 8000; // 128 * 11 buffer for filters one per key right channel +filter_env = 10000; // 128 * 7 buffer for filter adsr + +function wrap(number) (number <= 1 ? number : number - 1;); +function sine_wave(cycle) (sin(cycle * 2 * $pi);); +function tri_wave(cycle) ( + cycle < 0 ? ( + 4 * cycle + 2 - 1; + ) : ( + cycle > 0.5 + ? 4 * cycle - 2 - 1 + : -4 * cycle + 2 - 1; + ); +); +function saw_wave(cycle) ( + cycle < 0 + ? cycle * -2 - 1 + : cycle * -2 + 1; +); +function square_wave(cycle) ( + cycle < 0 ? ( + cycle < -0.5 ? -1 : 1; + ) : ( + cycle < 0.5 ? -1 : 1; + ); +); +function pulse_wave(cycle) ( + cycle < 0 ? ( + cycle < -0.25 ? -1 : 1; + ) : ( + cycle < 0.25 ? -1 : 1; + ); +); +function make_noise() (rand(2) - 1;); +function db2gain(db) (10^(db / 20);); +function round(in) (floor(in + 0.5 * sign(in));); +function note2freq(n) (440 * pow(2, (n - 69) / 12);); +function freq2note(f) ( round(12*(log(f/440)/log(2))+69); ); + +function make_wave(wave, cycle) ( + wave == 0 ? sine_wave(cycle) + : wave == 1 ? tri_wave(cycle) + : wave == 2 ? saw_wave(cycle) + : wave == 3 ? square_wave(cycle) + : wave == 4 ? pulse_wave(cycle) + : make_noise(); +); + +// 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 on_slider() ( + osc1_gain = db2gain(osc1_vol); + osc1_st_det = pow(2, osc1_phase_det / 1200); // stereo detune + osc1_fine_det = pow(2, osc1_fine / 1200); + osc1_coarse_det = pow(2, osc1_coarse / 12); + + osc2_gain = db2gain(osc2_vol); + osc2_st_det = pow(2, osc2_phase_det / 1200); + osc2_fine_det = pow(2, osc2_fine / 1200); + osc2_coarse_det = pow(2, osc2_coarse / 12); + + osc3_gain = db2gain(osc3_vol); + osc3_st_det = pow(2, osc3_phase_det / 1200); + osc3_fine_det = pow(2, osc3_fine / 1200); + osc3_coarse_det = pow(2, osc3_coarse / 12); +); + +@slider + +on_slider(); + +@block + +while (midirecv(offset, msg1, note, vel)) ( + event = msg1 & 0xF0; + channel = msg1 & 0x0F; + + // Note on + event == 0x90 && vel ? ( + + // remove note if it is already playing + note_repeat = 0; + ptr = poly.array_find(note); + ptr >= 0 ? ( + note_repeat = 1; + last_phase_1l = ptr[3]; + last_phase_1r = ptr[4]; + last_phase_2l = ptr[5]; + last_phase_2r = ptr[6]; + last_phase_3l = ptr[7]; + last_phase_3r = ptr[8]; + poly.array_remove(ptr); + ); + + // if polyphony is full and not mono remove first element + poly.size == maxpoly && maxpoly != 0 ? ( + poly.array_remove(poly.array_first()); + ); + + ptr = poly.array_add(); + ptr[0] = note; + ptr[1] = vel / 127; + ptr[2] = note2freq(note) / srate; + ptr[3] = note_repeat ? last_phase_1l : rand_phase * (rand() - 0.5); // osc start phase left + ptr[4] = note_repeat ? last_phase_1r : rand_phase * (rand() - 0.5); // osc start phase right + ptr[5] = note_repeat ? last_phase_2l : rand_phase * (rand() - 0.5); // osc 2 start phase left + ptr[6] = note_repeat ? last_phase_2r : rand_phase * (rand() - 0.5); // osc 2 + ptr[7] = note_repeat ? last_phase_3l : rand_phase * (rand() - 0.5); // osc 3 + ptr[8] = note_repeat ? last_phase_3r : rand_phase * (rand() - 0.5); // osc 3 + + env = envelope + note * 7; + adsr_seta(env_att * 0.001, env); + adsr_setd(env_dec * 0.001, env); + adsr_sets(exp(log(10)/20 * env_sus), env); + adsr_setr(env_rel * 0.001, env); + adsr_a(vel / 127, env); + + env = pitch_env + note * 7; + adsr_seta(pitch_att * 0.001, env); + adsr_setd(pitch_dec * 0.001, env); + adsr_sets(pitch_sus, env); + adsr_setr(pitch_rel * 0.001, env); + adsr_a(1, env); + + env = filter_env + note * 7; + adsr_seta(filter_att * 0.001, env); + adsr_setd(filter_dec * 0.001, env); + adsr_sets(filter_sus, env); + adsr_setr(filter_rel * 0.001, env); + adsr_a(1, env); + ); + + // Note off + event == 0x80 || (event == 0x90 && !vel) ? ( + ptr = poly.array_find(note); + ptr >= 0 ? ( + adsr_r(envelope + note * 7); + adsr_r(pitch_env + note * 7); + adsr_r(filter_env + note * 7); + ); + ); + + // All notes off + event == 0xB0 && note == 123 ? ( + poly.array_clear(); + ); + + midisend(offset, msg1, note, vel); +); + +@sample + +remove_ptrs.array_clear(); +ptr = poly.array_first(); +while(ptr >= 0) ( // for each note/voice + out_l = 0; + out_r = 0; + envbuf = envelope + ptr[0] * 7; + env_state = adsr_process(envbuf); + pitch_amt ? ( + pitchbuf = pitch_env + ptr[0] * 7; + adsr_process(pitchbuf); + osc_pitch_det = pow(2, pitchbuf[0] * pitch_amt / 12); + ) : ( + osc_pitch_det = 1; + ); + + // OSC1 + osc1_vol > -60 ? ( + osc_det = osc1_coarse_det * osc1_fine_det * osc_pitch_det; + ptr[3] = wrap(ptr[3] + ptr[2] * osc1_st_det * osc_det); // phase left + ptr[4] = wrap(ptr[4] + ptr[2] / osc1_st_det * osc_det); // phase right + wave_l = make_wave(osc1_wave, wrap(ptr[3] + osc1_phase_offset * 0.25)); + wave_r = make_wave(osc1_wave, wrap(ptr[4] - osc1_phase_offset * 0.25)); + out_l += wave_l * (1 - osc1_pan) * envbuf[] * osc1_gain * (osc1_inv ? -1 : 1); + out_r += wave_r * (1 + osc1_pan) * envbuf[] * osc1_gain * (osc1_inv ? -1 : 1); + ); + + // OSC2 + osc2_vol > -60 ? ( + osc_det = osc2_coarse_det * osc2_fine_det * osc_pitch_det; + ptr[5] = wrap(ptr[5] + ptr[2] * osc2_st_det * osc_det); // phase left + ptr[6] = wrap(ptr[6] + ptr[2] / osc2_st_det * osc_det); // phase right + wave_l = make_wave(osc2_wave, wrap(ptr[5] + osc2_phase_offset * 0.25)); + wave_r = make_wave(osc2_wave, wrap(ptr[6] - osc2_phase_offset * 0.25)); + out_l += wave_l * (1 - osc2_pan) * envbuf[] * osc2_gain * (osc2_inv ? -1 : 1); + out_r += wave_r * (1 + osc2_pan) * envbuf[] * osc2_gain * (osc2_inv ? -1 : 1); + ); + + // OSC3 + osc3_vol > -60 ? ( + osc_det = osc3_coarse_det * osc3_fine_det * osc_pitch_det; + ptr[7] = wrap(ptr[7] + ptr[2] * osc3_st_det * osc_det); // phase left + ptr[8] = wrap(ptr[8] + ptr[2] / osc3_st_det * osc_det); // phase right + wave_l = make_wave(osc3_wave, wrap(ptr[7] + osc3_phase_offset * 0.25)); + wave_r = make_wave(osc3_wave, wrap(ptr[8] - osc3_phase_offset * 0.25)); + out_l += wave_l * (1 - osc3_pan) * envbuf[] * osc3_gain * (osc3_inv ? -1 : 1); + out_r += wave_r * (1 + osc3_pan) * envbuf[] * osc3_gain * (osc3_inv ? -1 : 1); + ); + + filter_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/filter_freq, filterbuf[0] * filter_adsr_amt); + + filter_setf(filterbuf_l, filter_freq * multiplier, filter_q); + filter_copy_coefs(filterbuf_l, filterbuf_r); + + filter_shape == 1 ? ( + out_l = filter_lp(filterbuf_l, out_l); + out_r = filter_lp(filterbuf_r, out_r); + ) : filter_shape == 2 ? ( + out_l = filter_bp(filterbuf_l, out_l); + out_r = filter_bp(filterbuf_r, out_r); + ) : ( + out_l = filter_hp(filterbuf_l, out_l); + out_r = filter_hp(filterbuf_r, out_r); + ); + ); + + spl0 += out_l; + spl1 += out_r; + + // mark note for deletion + env_state == 0 ? ( + p = remove_ptrs.array_add(); + p[0] = ptr[0]; + ); + + ptr = poly.array_next(ptr); +); + +// remove notes that finished adsr +ptr = remove_ptrs.array_first(); +while(ptr >= 0) ( + p = poly.array_find(ptr[0]); + p >= 0 ? poly.array_remove(p); + ptr = remove_ptrs.array_next(ptr); +); + +@gfx 440 380 + +gfx_clear = COLOR_BG; +mouse.update_mouse_state(); + +set_color(0x666666); +gfx_x = 20; gfx_y = 20; +gfx_drawstr("OSC1"); + +gfx_setfont(1,"Arial", 14); +set_color(osc1_inv ? COLOR_ACTIVE : 0x666666); +gfx_x = 60; gfx_y = 16; +gfx_drawstr("Ø"); +gfx_setfont(0); +mouse.left_click && mouse_in_rect(60, 10, 10, 20) ? ( + osc1_inv = !osc1_inv; +); + +shape_name = osc1_wave == 0 ? "Sine" + : osc1_wave == 1 ? "Tri" + : osc1_wave == 2 ? "Saw" + : osc1_wave == 3 ? "Sqr" + : osc1_wave == 4 ? "Pls" + : "Nois"; +draw_button(80, 20, 40, shape_name, 0); +mouse.left_click && mouse_in_rect(80, 20 - 2, 40, 10 + 2) ? ( + gfx_x = 80; gfx_y = 30; + choice = gfx_showmenu("Sine|Triangle|Saw|Square|Pulse|Noise"); + choice > 0 ? slider(3) = choice - 1; + on_slider(); +); + +draw_knob(20, 40, 2, "Vol", -12, -60, 12, 0, 0, sprintf(#, "%.1f dB", slider(2))); +draw_knob(80, 40, 4, "Ph Off", 0, -1, 1, 0, 1, sprintf(#, "%.2f", slider(4))); +draw_knob(140, 40, 5, "Ph Det", 0, -100, 100, 0, 1, sprintf(#, "%i", slider(5))); +draw_knob(200, 40, 6, "Coarse", 0, -24, 24, 0, 1, sprintf(#, "%i", slider(6))); +draw_knob(260, 40, 7, "Fine", 0, -99, 99, 0, 1, sprintf(#, "%i", slider(7))); +draw_knob(320, 40, 8, "Pan", 0, -1, 1, 0, 1, sprintf(#, "%.2f", slider(8))); + +set_color(0x666666); +gfx_x = 20; gfx_y = 110; +gfx_drawstr("OSC2"); + +gfx_setfont(1,"Arial", 14); +set_color(osc2_inv ? COLOR_ACTIVE : 0x666666); +gfx_x = 60; gfx_y = 110-4; +gfx_drawstr("Ø"); +gfx_setfont(0); +mouse.left_click && mouse_in_rect(60, 100, 10, 20) ? ( + osc2_inv = !osc2_inv; +); + +shape_name = osc2_wave == 0 ? "Sine" + : osc2_wave == 1 ? "Tri" + : osc2_wave == 2 ? "Saw" + : osc2_wave == 3 ? "Sqr" + : osc2_wave == 4 ? "Pls" + : "Nois"; +draw_button(80, 110, 40, shape_name, 0); +mouse.left_click && mouse_in_rect(80, 110 - 2, 40, 10 + 2) ? ( + gfx_x = 80; gfx_y = 120; + choice = gfx_showmenu("Sine|Triangle|Saw|Square|Pulse|Noise"); + choice > 0 ? slider(12) = choice - 1; + on_slider(); +); + +draw_knob(20, 130, 11, "Vol", -12, -60, 12, 0, 0, sprintf(#, "%.1f dB", slider(11))); +draw_knob(80, 130, 13, "Ph Off", 0, -1, 1, 0, 1, sprintf(#, "%.2f", slider(13))); +draw_knob(140,130, 14, "Ph Det", 0, -100, 100, 0, 1, sprintf(#, "%i", slider(14))); +draw_knob(200,130, 15, "Coarse", 0, -24, 24, 0, 1, sprintf(#, "%i", slider(15))); +draw_knob(260,130, 16, "Fine", 0, -99, 99, 0, 1, sprintf(#, "%i", slider(16))); +draw_knob(320,130, 17, "Pan", 0, -1, 1, 0, 1, sprintf(#, "%.2f", slider(17))); + +set_color(0x666666); +gfx_x = 20; gfx_y = 200; +gfx_drawstr("OSC3"); + +gfx_setfont(1,"Arial", 14); +set_color(osc3_inv ? COLOR_ACTIVE : 0x666666); +gfx_x = 60; gfx_y = 200-4; +gfx_drawstr("Ø"); +gfx_setfont(0); +mouse.left_click && mouse_in_rect(60, 190, 10, 20) ? ( + osc3_inv = !osc3_inv; +); + +shape_name = osc3_wave == 0 ? "Sine" + : osc3_wave == 1 ? "Tri" + : osc3_wave == 2 ? "Saw" + : osc3_wave == 3 ? "Sqr" + : osc3_wave == 4 ? "Pls" + : "Nois"; +draw_button(80, 200, 40, shape_name, 0); +mouse.left_click && mouse_in_rect(80, 200 - 2, 40, 10 + 2) ? ( + gfx_x = 80; gfx_y = 210; + choice = gfx_showmenu("Sine|Triangle|Saw|Square|Pulse|Noise"); + choice > 0 ? slider(21) = choice - 1; + on_slider(); +); + +draw_knob(20, 220, 20, "Vol", -12, -60, 12, 0, 0, sprintf(#, "%.1f dB", slider(20))); +draw_knob(80, 220, 22, "Ph Off", 0, -1, 1, 0, 1, sprintf(#, "%.2f", slider(22))); +draw_knob(140,220, 23, "Ph Det", 0, -100, 100, 0, 1, sprintf(#, "%i", slider(23))); +draw_knob(200,220, 24, "Coarse", 0, -24, 24, 0, 1, sprintf(#, "%i", slider(24))); +draw_knob(260,220, 25, "Fine", 0, -99, 99, 0, 1, sprintf(#, "%i", slider(25))); +draw_knob(320,220, 26, "Pan", 0, -1, 1, 0, 1, sprintf(#, "%.2f", slider(26))); + +set_color(0x666666); +gfx_x = 20; gfx_y = 290; +gfx_drawstr("ENVELOPE"); + +draw_button(100, 290, 60, "Amp", gfx_sel_envelope == 0); +mouse.left_click && mouse_in_rect(100,290-2, 60, 10+2) ? ( + gfx_sel_envelope = 0; +); +draw_button(160, 290, 60, "Pitch", gfx_sel_envelope == 1); +mouse.left_click && mouse_in_rect(160,290-2, 60, 10+2) ? ( + gfx_sel_envelope = 1; +); +draw_button(220, 290, 60, "Filter", gfx_sel_envelope == 2); +mouse.left_click && mouse_in_rect(220,290-2, 60, 10+2) ? ( + gfx_sel_envelope = 2; +); + +gfx_sel_envelope == 0 ? ( // AMP + draw_knob(80, 310, 30, "Att", 1, 1, 10000, 1, 0, sprintf(#, "%i ms", slider(30))); + draw_knob(140,310, 31, "Dec", 1, 1, 10000, 1, 0, sprintf(#, "%i ms", slider(31))); + draw_knob(200,310, 32, "Sus", 0, -90, 0, 0, 0, sprintf(#, "%.f dB", slider(32))); + draw_knob(260,310, 33, "Rel", 1, 1, 10000, 1, 0, sprintf(#, "%i ms", slider(33))); +) +: gfx_sel_envelope == 1 ? ( // PITCH + draw_knob(20, 310, 35, "Amt", 0, -24, 24, 0, 1, sprintf(#, "%.1f", slider(35))); + draw_knob(80, 310, 36, "Att", 1, 1, 10000, 1, 0, sprintf(#, "%i ms", slider(36))); + draw_knob(140,310, 37, "Dec", 1, 1, 10000, 1, 0, sprintf(#, "%i ms", slider(37))); + draw_knob(200,310, 38, "Sus", 1, 0, 1, 0, 0, sprintf(#, "%.2f", slider(38))); + draw_knob(260,310, 39, "Rel", 1, 1, 10000, 1, 0, sprintf(#, "%i ms", slider(39))); +) +: gfx_sel_envelope == 2 ? ( // FILTER + draw_knob(20, 310, 43, "Amt", 0, -1, 1, 0, 1, sprintf(#, "%.2f", slider(43))); + draw_knob(80, 310, 44, "Att", 1, 1, 10000, 1, 0, sprintf(#, "%i ms", slider(44))); + draw_knob(140,310, 45, "Dec", 1, 1, 10000, 1, 0, sprintf(#, "%i ms", slider(45))); + draw_knob(200,310, 46, "Sus", 1, 0, 1, 0, 0, sprintf(#, "%.2f", slider(46))); + draw_knob(260,310, 47, "Rel", 1, 1, 10000, 1, 0, sprintf(#, "%i ms", slider(47))); +); + +set_color(0x666666); +gfx_x = 320; gfx_y = 290; +gfx_drawstr("FILTER"); +gfx_line(310, 290, 310, 350); + +shape_name = filter_shape == 0 ? "Off" + : filter_shape == 1 ? "LP" + : filter_shape == 2 ? "BP" + : "HP"; +draw_button(380, 290, 40, shape_name, 0); +mouse.left_click && mouse_in_rect(380, 290 - 2, 40, 10 + 2) ? ( + gfx_x = 380; gfx_y = 300; + choice = gfx_showmenu("Off|Low Pass|Band Pass|High Pass"); + choice > 0 ? filter_shape = choice - 1; + on_slider(); +); + +draw_knob(320, 310, 41, "Freq", 10000, 20, 22000, 1, 0, sprintf(#, "%i Hz", slider(41))); +draw_knob(380, 310, 42, "Q", 0.70, 0.01, 40, 1, 0, sprintf(#, "%.1f", slider(42))); + +set_color(0x666666); +gfx_line(370, 20, 370, 260); +gfx_x = 380; gfx_y = 20; +gfx_drawstr("PHASE"); + +draw_knob(380, 40, 50, "Rand", 0, 0, 1, 0, 0, sprintf(#, "%.2f", slider(50))); + +function on_knob_move (nslider, slider_min, slider_max, slider_is_log, factor) ( + factor *= mouse.control ? 0.05 : 1; + slider_is_log ? ( + slider(nslider) = slider(nslider) * pow(100, -factor * 0.01); + ) : ( + inc = (slider_max - slider_min) / 100 * -factor; + slider(nslider) += inc; + ); + + slider(nslider) > slider_max ? slider(nslider) = slider_max; + slider(nslider) < slider_min ? slider(nslider) = slider_min; + on_slider(); + sliderchange(slider(nslider)); + slider_automate(slider(nslider)); +); + +wheelknob_nslider ? ( + on_knob_move(wheelknob_nslider, wheelknob_min, wheelknob_max, wheelknob_is_log, mouse.wheel * -7); +); + +selknob_nslider && mouse.left && mouse.dy != 0 ? ( + on_knob_move(selknob_nslider, selknob_min, selknob_max, selknob_is_log, mouse.dy); +); + +doubleclk_nslider ? ( + on_slider(); + sliderchange(slider(doubleclk_nslider)); + slider_automate(slider(doubleclk_nslider)); + doubleclk_nslider = 0; +); + +gfx_setcursor(selknob_nslider ? 32511 : 0); +!mouse.wheel ? wheelknob_nslider = 0; +!mouse.left ? selknob_nslider = 0; diff --git a/Synth/tilr_JS3Osc/3o.adsr.jsfx-inc b/Synth/tilr_JS3Osc/3o.adsr.jsfx-inc new file mode 100644 index 0000000..a8a0225 --- /dev/null +++ b/Synth/tilr_JS3Osc/3o.adsr.jsfx-inc @@ -0,0 +1,128 @@ +desc:ADSR envelope + +// Copyright (C) 2013-2017 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. + +// Modified asrd lib to work with buffers instead + +// buf[0] = env +// buf[1] = a +// buf[2] = d +// buf[3] = s +// buf[4] = r +// buf[5] = state +// buf[6] = scale + +@init + +function _adsr_set(time) +( + 1 / (0.2 * time * srate + 1); +); + +function adsr_seta(time, buf) + //instance(a) +( + buf[1] = _adsr_set(time); +); + +function adsr_setd(time, buf) + //instance(d) +( + buf[2] = _adsr_set(time); +); + +function adsr_sets(gain, buf) + //instance(s) +( + buf[3] = gain; +); + +function adsr_setr(time, buf) + //instance(r) +( + buf[4] = _adsr_set(time); +); + +function adsr_reset(buf) + //instance(state, env) +( + buf[0] = 0; + buf[5] = 0; +); + +function adsr_r(buf) + // instance(state, r) +( + buf[4] < 1 ? ( // r < 1 ? state = 8 : adsr_reset() + buf[5] = 8; + ) : ( + adsr_reset(buf); + ); +); + +function adsr_s(buf) + // instance(state, s, env, scale) +( + buf[0] = buf[6] * buf[3]; // env = scale * s + buf[5] = 4; // state = 4 +); + +function adsr_d(buf) +// instance(state, d) +( + buf[2] < 1 ? ( // d < 1 + buf[5] = 2; // state = 2 + ) : ( + adsr_s(buf); + ); +); + +function adsr_a(scale, buf) + // instance(state, a, env) +( + buf[6] = scale; // this.scale = scale + + buf[1] < 1 ? ( // a < 1 + buf[5] = 1; // state = 1 + ) : ( + buf[0] = scale; // env = scale + adsr_d(buf); + ); +); + +function adsr_process(buf) + //instance(state, a, d, s, r, env, scale) +( + env = buf[0]; + a = buf[1]; + d = buf[2]; + s = buf[3]; + r = buf[4]; + state = buf[5]; + scale = buf[6]; + buf[5] ? ( + + // Decay + buf[5] == 2 ? ( + buf[0] += d * (scale * s - env); + abs(buf[0] - scale * s) <= scale * 0.000001 ? adsr_s(buf); + ) : + + // Release + buf[5] == 8 ? ( + buf[0] += r * (0 - env); + abs(buf[0]) <= scale * 0.000001 ? adsr_reset(buf); + ) : + + // Attack + buf[5] == 1 ? ( + buf[0] += a * (scale - env); + abs(buf[0] - scale) <= scale * exp(-5) ? adsr_d(buf); + ); + ); + + buf[5]; +); diff --git a/Synth/tilr_JS3Osc/3o.array.jsfx-inc b/Synth/tilr_JS3Osc/3o.array.jsfx-inc new file mode 100644 index 0000000..50876db --- /dev/null +++ b/Synth/tilr_JS3Osc/3o.array.jsfx-inc @@ -0,0 +1,269 @@ +desc:Simple two-dimensional array interface + +// Copyright (C) 2015-2019 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. + +/* Example + + desc:Last-note priority mono synth + + import Tale/array.jsfx-inc + import Tale/midi_queue.jsfx-inc + import Tale/poly_blep.jsfx-inc + + @init + + voice.array_init(0, 128, 2); + + @sample + + while(midi.midiq_recv()) ( + midi.msg1 &= 0xF0; + + // Note On + midi.msg1 == 0x90 && midi.msg3 ? ( + + // Remove note if somehow it is already playing. + ptr = voice.array_find(midi.msg2); + ptr >= 0 ? voice.array_remove(ptr); + + // Add note, and set pointer to it. + ptr = voice.array_add(); + ptr[0] = midi.msg2; + + // Set oscillator frequency. + ptr[1] = osc.poly_setf(midi.msg2, 440); + osc.a *= 0.5; + ) : + + // Note Off + midi.msg1 == 0x80 || midi.msg1 == 0x90 ? ( + + // Remove note. + ptr = voice.array_find(midi.msg2); + ptr >= 0 ? ( + voice.array_remove(ptr); + !voice.size ? osc.a = 0 : ( + + // Update pointer to new last note. + ptr = voice.array_get(voice.size - 1); + osc.poly_setdt(ptr[1]); + osc.a *= 0.5; + ); + ); + ) : + + // All Notes Off + midi.msg1 == 0xB0 && midi.msg2 == 123 ? ( + voice.array_clear(); + ); + ); + + spl0 = spl1 = osc.poly_saw(); + + Initialization Functions + + * array_init(index, max_rows[, cols]) + Example: array.array_init(0, 64, 2); + Sets the offset and size of the local memory buffer to store the array + in, and returns the next available memory index (i.e. + index+rows*cols). If cols is omitted, then it defaults to 1. + + * array_alloc(max_rows[, cols]) + * array_free() + Example: array.array_alloc(64, 2); + Allocates/deallocates a block of local memory to store the array in, + and returns its index. + + Note: Requires Tale/malloc.jsfx-inc. + + Array Functions + + * array_get(row) + Example: ptr = array.array_get(0); + Returns a pointer to the local memory index of the specified row. + + * array_add() + Example: ptr = array.array_add(); + Adds a row to the end of the array and returns its local memory index. + Note that the row is added but not initialized (i.e. it does not + contain any data yet, nor is it zeroed.). + + * array_insert(ptr) + Example: array.array_insert(array.array_get(0)); + Inserts a row into the array. Note that the row is inserted but not + initialized. + + * array_remove(ptr) + Example: array.array_remove(array.array_get(0)); + Removes a row from the array. + + * array_clear() + Example: array.array_clear(); + Removes all rows from the array. + + Miscellaneous Functions + + * array_first() + Example: ptr = array.array_first(); + Returns a pointer to the local memory index of the first row, or -1 if + there are no rows. + + * array_next(ptr) + Example: ptr = array.array_next(ptr); + Returns a pointer to the local memory index of the next row, or -1 if + there is no next row. + + * array_last() + Example: ptr = array.array_last(); + Returns a pointer to the local memory index of the last row, or -1 if + there are no rows. + + * array_find(value[, col[, ptr]]) + Example: ptr = array_find(123); + Finds a value in the array at the specified column (0 by default), + starting at the specified row pointer (first row by default), and + returns the local memory index of the entire row, or -1 if the value + was not found. + + Instance Variables + + * buf + Example: ptr = array.buf; + The local memory address of the buffer in which the array is stored. + + * size + Example: num_rows = array.size; + The current size of the array in rows. + + * num + Example: num_cols = array.num; + The number of columns in each row. + +*/ + +@init + +function array_init(index, max_rows, cols) + instance(buf, size, num) +( + buf = index; + size = 0; + num = cols; + + buf + max_rows * num; +); + +function array_init(index, max_rows) +( + this.array_init(index, max_rows, 1); +); + +function array_get(row) + instance(buf, num) +( + buf + row * num; +); + +function array_add() + instance(buf, size, num) +( + buf + ((size += 1) - 1) * num; +); + +function array_insert(ptr) + instance(buf, size, num) + local(end) +( + end = buf + size * num; + size += 1; + ptr < end ? memcpy(ptr + num, ptr, end - ptr); + + // Returning the pointer here might not be very useful, but it is + // consistent with array_add(). + ptr; +); + +function array_remove(ptr) + instance(buf, size, num) + local(end) +( + end = buf + (size -= 1) * num; + ptr < end ? memcpy(ptr, ptr + num, end - ptr); + + // Again, returning the pointer here is not very useful; meh. + ptr; +); + +function array_first() + instance(buf, size) +( + size ? buf : -1; +); + +function array_next(ptr) + instance(buf, size, num) +( + ptr += num; + ptr < buf + size * num ? ptr : -1; +); + +function array_last() + instance(buf, size, num) +( + size ? buf + (size - 1) * num : -1; +); + +function array_find(value, col, ptr) + instance(buf, size, num) + local(ret, end) +( + ret = -1; + end = buf + size * num; + while( + ptr < end ? ( + ptr[col] == value ? ( + ret = ptr; + 0; // break + ) : ( + ptr += num; + 1; // continue + ); + ); + ); + ret; +); + +function array_find(value, col) + instance(buf) +( + this.array_find(value, col, buf); +); + +function array_find(value) + instance(buf, size, num) + local(ret, ptr, end) +( + ret = -1; + end = (ptr = buf) + size * num; + while( + ptr < end ? ( + ptr[] == value ? ( + ret = ptr; + 0; // break + ) : ( + ptr += num; + 1; // continue + ); + ); + ); + ret; +); + +function array_clear() + instance(size) +( + size = 0; +); diff --git a/Synth/tilr_JS3Osc/3o.gfxlib.jsfx-inc b/Synth/tilr_JS3Osc/3o.gfxlib.jsfx-inc new file mode 100644 index 0000000..d9e00f6 --- /dev/null +++ b/Synth/tilr_JS3Osc/3o.gfxlib.jsfx-inc @@ -0,0 +1,148 @@ +desc:3o.gfxlib.jsfx-inc + +@init + +COLOR_ACTIVE = 0xe4cc5d; +COLOR_BG = 0x141618; + +selknob_nslider = 0; +selknob_min = 0; +selknob_max = 0; +selknob_is_log = 0; + +wheelknob_nslider = 0; +wheelknob_min = 0; +wheelknob_max = 0; +wheelknob_is_log = 0; + +doubleclk_nslider = 0; + +function deg2rad (deg) (deg * $pi / 180;); +RAD130 = deg2rad(130); + + +function set_color(color) ( + gfx_r = (color & 0xFF0000) / 0xFF0000; + gfx_g = (color & 0x00FF00) / 0x00FF00; + gfx_b = (color & 0x0000FF) / 0x0000FF; +); + +function draw_wave(x, y, w, h, buf, len) +local(i, _x, _y) +( + set_color(COLOR_ACTIVE); + loop(i = 0; len, + _x = i * w / len + x; + _y = buf[i] * h / 2 + h / 2 + y; + i == 0 ? ( + gfx_x = _x; + gfx_y = _y; + ); + gfx_lineto(_x, _y); + i += 1; + ); +); + +function log_scale (value, max, min) +local (minP, maxP, scale) ( + minP = min; + maxP = max; + + minV = log(min); + maxV = log(max); + + scale = (maxV - minV) / (maxP - minP); + exp(minV + scale * (value - minP)); +); + +function inverse_log_scale (lg, max, min) +local (minP, maxP, scale) ( + minP = min; + maxP = max; + + minV = log(min); + maxV = log(max); + + scale = (maxV - minV) / (maxP - minP); + (log(lg) - minV) / scale + minP; +); + +function mouse_in_rect (x, y, w ,h) ( + mouse.x >= x && mouse.x <= x + w && mouse.y >= y && mouse.y <= y + h; +); + +function draw_knob(x, y, nslider, label, default, _min, _max, is_log, is_sym, val_label) +local (scale) +( + set_color(0x282D32); + gfx_arc(x+20, y+20, 20, -RAD130, RAD130, 1); + gfx_arc(x+20, y+20, 19.5, -RAD130, RAD130, 1); + gfx_arc(x+20, y+20, 19, -RAD130, RAD130, 1); + gfx_arc(x+20, y+20, 18.5, -RAD130, RAD130, 1); + + slider_val = slider(nslider); + nslider == 15 && lslider != slider_val ? ( + lsliderrrr += 1; + lsliderr = slider_val; + lsliderrr = lslider; + lslider = slider_val; + ); + is_log ? ( + slider_val = inverse_log_scale(slider_val, _max, _min); + ); + + // linear map value from min/max to -130deg +130deg + scale = (130 - -130) / (_max-_min); + _offset = (-_min * (130 - -130)) / (_max - _min) + -130; + slider_deg = slider_val * scale + _offset; + slider_rad = deg2rad(slider_deg); + + gfx_circle(x+20, y+20, 15, 1, 1); + set_color(COLOR_ACTIVE); + gfx_circle(x+20-sin(-slider_rad)*10, y+20-cos(-slider_rad)*10, 3, 1); + + gfx_arc(x+20, y+20, 20, is_sym ? 0 : -RAD130, slider_rad, 1); + gfx_arc(x+20, y+20, 19.5, is_sym ? 0 : -RAD130, slider_rad, 1); + gfx_arc(x+20, y+20, 19, is_sym ? 0 : -RAD130, slider_rad, 1); + gfx_arc(x+20, y+20, 18.5, is_sym ? 0 : -RAD130, slider_rad, 1); + + set_color(0xFFFFFF); + gfx_x = x - 20; + gfx_y = y + 20 * 2 + 5; + selknob_nslider == nslider ? ( + gfx_drawstr(val_label, 1, x+20+20*2, y+100); + ) : ( + gfx_drawstr(label, 1, x+20+20*2, y+100); + ); + + mouse_in_rect(x, y, 40, 40) ? ( + mouse.double_click ? ( + slider(nslider) = default; + doubleclk_nslider = nslider; + ); + mouse.left_click ? ( + selknob_nslider = nslider; + selknob_min = _min; + selknob_max = _max; + selknob_is_log = is_log; + ); + mouse.wheel ? ( + wheelknob_nslider = nslider; + wheelknob_min = _min; + wheelknob_max = _max; + wheelknob_is_log = is_log; + ); + ); +); + +function draw_button (x, y, w, label, toggled) ( + set_color(COLOR_ACTIVE); + gfx_rect(x, y - 2, w, 10 + 2); + gfx_x = x; gfx_y = y; + !toggled ? ( + set_color(COLOR_BG); + gfx_rect(x+1, y+1-2, w-2, 10); + ); + set_color(toggled ? 0xFFFFFF : COLOR_ACTIVE); + gfx_drawstr(label, 1, x+w, y+10); +); diff --git a/Synth/tilr_JS3Osc/3o.mouselib.jsfx-inc b/Synth/tilr_JS3Osc/3o.mouselib.jsfx-inc new file mode 100644 index 0000000..751bd5d --- /dev/null +++ b/Synth/tilr_JS3Osc/3o.mouselib.jsfx-inc @@ -0,0 +1,34 @@ +desc:3o.mouselib.jsfx-inc + +@init + +function update_mouse_state() +instance(cap, x, y, lx, ly, dx, dy, right_click, left_click, lleft, lright, left, right, click_time, double_click, control, lwheel, wheel) +global(mouse_cap, mouse_x, mouse_y, mouse_wheel) +( + lleft = left; + lright = right; + lx = x; + ly = y; + cap = mouse_cap; + control = mouse_cap & 4; + x = mouse_x; + y = mouse_y; + + left = cap & 1 > 0; + right = cap & 2 > 0; + left_click = left && lleft == 0; + right_click = right && lright == 0; + dx = x - lx; + dy = y - ly; + + wheel = mouse_wheel > lwheel ? 1 : mouse_wheel < lwheel ? -1 : 0; + lwheel = mouse_wheel; + + left_click ? ( + time_precise() - click_time < .5 ? double_click = 1; + click_time = time_precise(); + ) : ( + double_click = 0; + ); +); diff --git a/Synth/tilr_JS3Osc/3o.zdf_filter.jsfx-inc b/Synth/tilr_JS3Osc/3o.zdf_filter.jsfx-inc new file mode 100644 index 0000000..48c4b58 --- /dev/null +++ b/Synth/tilr_JS3Osc/3o.zdf_filter.jsfx-inc @@ -0,0 +1,403 @@ +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() );