From e7f97d773b11ec6cd1dd4c667d4bae732d735556 Mon Sep 17 00:00:00 2001 From: TiagoLr Date: Tue, 14 Nov 2023 13:59:32 +0000 Subject: [PATCH] Release JSDrumpad v1.0 (#326) --- Synth/tilr_JSDrumpad.jsfx | 422 +++++++++++++++++++ Synth/tilr_JSDrumpad.jsfx.rpl | 62 +++ Synth/tilr_JSDrumpad/dp.envlib.jsfx-inc | 135 +++++++ Synth/tilr_JSDrumpad/dp.gfxlib.jsfx-inc | 118 ++++++ Synth/tilr_JSDrumpad/dp.mouselib.jsfx-inc | 34 ++ Synth/tilr_JSDrumpad/dp.rbj_filter.jsfx-inc | 424 ++++++++++++++++++++ 6 files changed, 1195 insertions(+) create mode 100644 Synth/tilr_JSDrumpad.jsfx create mode 100644 Synth/tilr_JSDrumpad.jsfx.rpl create mode 100644 Synth/tilr_JSDrumpad/dp.envlib.jsfx-inc create mode 100644 Synth/tilr_JSDrumpad/dp.gfxlib.jsfx-inc create mode 100644 Synth/tilr_JSDrumpad/dp.mouselib.jsfx-inc create mode 100644 Synth/tilr_JSDrumpad/dp.rbj_filter.jsfx-inc diff --git a/Synth/tilr_JSDrumpad.jsfx b/Synth/tilr_JSDrumpad.jsfx new file mode 100644 index 0000000..f2743e4 --- /dev/null +++ b/Synth/tilr_JSDrumpad.jsfx @@ -0,0 +1,422 @@ +desc: JSDrumpad +author: tilr +version: 1.0 +provides: + tilr_JSDrumpad.jsfx.rpl + tilr_JSDrumpad/dp.envlib.jsfx-inc + tilr_JSDrumpad/dp.gfxlib.jsfx-inc + tilr_JSDrumpad/dp.mouselib.jsfx-inc + tilr_JSDrumpad/dp.rbj_filter.jsfx-inc +screenshot: https://raw.githubusercontent.com/tiagolr/JSDrumpad/master/doc/ss.png +about: + # JSDrumpad + + JSFX drumpad synth inspired by vsts like Microtonic and DSK Synthdrums. + + Features: + * 1x OSC (Sine, Triangle, Saw, or Square wave) + * 1x Noise generator (Stereo or mono) + * 3x Envelopes (osc, pitch and noise) + * 2x Filter (Noise color/lp, LowPass, BandPass and HighPass) + * 1x Distortion (Tube) + * 1x Band EQ + +desc:JSDrumpad +tags: drum synth, instrument +author: TiagoLr + +import dp.envlib.jsfx-inc +import dp.mouselib.jsfx-inc +import dp.rbj_filter.jsfx-inc +import dp.gfxlib.jsfx-inc + +slider1:mix=-1<-1, 1, .01>-Mix (Osc - Noise) +slider2:vol=-12<-90, 0, .01>-Volume +slider3:pan=0<-1, 1, .01>-Pan +slider4:distortion=0<0, 48, 0.1>-Drive Db + +slider5:osc_freq=440<20, 10000, 1:log>-Frequency (Hz) +slider6:osc_wave=0<0, 3, 1{Sine,Triangle,Saw,Square}>-Wave +slider7:osc_att=1<1, 5000, 1:log>-Attack +slider8:osc_dec=70<1, 5000, 1:log>-Decay +slider9:osc_shape=0<-1, 1, .01>-Shape + +slider10:pitch_amt=0<-1, 1, 0.01>-Pitch ammount +slider11:pitch_att=1<1, 5000, 1:log>-Pitch attack +slider12:pitch_dec=70<1, 5000, 1:log>-Pitch decay +slider13:pitch_shape=0<-1, 1, 0.01>-Pitch shape +slider14:pitch_track=0<0,1,1{No,Yes}>-Keyboard tracking + +slider15:noise_stereo=0<0, 1, 1{No,Yes}>-Stereo noise +slider16:noise_cutoff=22050<20,22050,1:log>-Noise color +slider17:noise_att=1<1,5000, 1:log>-Noise attack +slider18:noise_dec=70<1,5000, 1:log>-Noise decay +slider19:noise_shape=0<-1, 1, 0.01>-Noise shape + +slider20:filter_shape=0<0,3,1{Low Pass, Band Pass, High Pass}>-Filter shape +slider21:filter_freq=22050<20, 22050, 1:log>-Filter frequency +slider22:filter_q=0.5<0.01, 100, 0.01:log>-Filter Q +slider23:filter_sel=0<1, 0, 1{Noise,Osc,Both}>-Filter target + +slider24:eq_freq=440<20, 22050, 1:log>-EQ frequency +slider25:eq_gain=0<0, -18, 18>-EQ gain +slider26:eq_q=0.5<0.01, 100, 0.01:log>-EQ Q + +slider27:vel_vol=0<0, 1, 0.01>-Velocity volume + +options:gfx_hz=60; + +@init + +osc_cycle = 0; +note_on = 0; +note_vel = 0; +note_counter = 0; +notes = 0; +notes[0]="C";notes[1]="C#";notes[2]="D";notes[3]="D#"; +notes[4]="E";notes[5]="F";notes[6]="F#";notes[7]="G"; +notes[8]="G#";notes[9]="A";notes[10]="A#";notes[11]="B"; + +function wrap(number) (number <= 1 ? number : number - 1;); +function sine_wave(cycle) (sin(cycle * 2 * $pi);); +function tri_wave(cycle) ((cycle > 0.5 ? 4 * cycle - 2 : -4 * cycle + 2) - 1;); +function saw_wave(cycle) (cycle * -2 + 1;); +function square_wave(cycle) (cycle < 0.5 ? -1 : 1;); +function make_noise(cycle) (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(cycle) ( + osc_wave == 0 ? sine_wave(cycle) + : osc_wave == 1 ? tri_wave(cycle) + : osc_wave == 2 ? saw_wave(cycle) + : square_wave(cycle) +); + +function get_note_name() ( + note = freq2note(osc_freq); + sprintf(#, "%s%i", notes[note % 12], floor(note / 12) - 1); +); + +function envelope_refresh() +local (shape, att_shape, dec_shape) +( + shape = osc_shape + 1; + att_shape = shape > 1 ? 3.001 - shape : shape; + dec_shape = shape > 1 ? 2 - shape : 1.001 + shape; + envelope.zen_update( + 0, // delay + osc_att, // attack + osc_dec, // decay + -90, // sustain + 0, // release + att_shape, // attack shape + dec_shape, // decay shape + 1, // release shape + 100, // mix + 0, // min + 100 // max + ); + + shape = pitch_shape + 1; + att_shape = shape > 1 ? 3.001 - shape : shape; + dec_shape = shape > 1 ? 2 - shape : 1.001 + shape; + pitch_envelope.zen_update(0, pitch_att, pitch_dec, -90, 0, att_shape, dec_shape, 1, 100, 0, 100); + + shape = noise_shape + 1; + att_shape = shape > 1 ? 3.001 - shape : shape; + dec_shape = shape > 1 ? 2 - shape: 1.001 + shape; + noise_envelope.zen_update(0, noise_att, noise_dec, -90, 0, att_shape, dec_shape, 1, 100, 0, 100); +); + +function on_slider() ( + tube = db2gain(distortion); + gain = db2gain(vol); + noise_lowpass.rbj_lp(noise_cutoff, 0.5); + filter_shape == 0 ? ( + noise_filter.rbj_lp(filter_freq, filter_q); + ); + filter_shape == 1 ? ( + noise_filter.rbj_bp(filter_freq, filter_q); + ); + filter_shape == 2 ? ( + noise_filter.rbj_hp(filter_freq, filter_q); + ); + eq.rbj_eq(eq_freq, eq_q, db2gain(eq_gain)); + envelope_refresh(); +); + +// Parameter smoothing +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); +); + +tube.rc_set(0.0033); +tube.smooth = db2gain(distortion); +vel_mult.rc_set(0.0066); +vel_mult.smooth = 0; + +// Distortion +function tanh(x) ( + x = exp(2*x); + (x - 1) / (x + 1); +); +dc = 0.05; +tanh_dc = tanh(dc); +function amp(x) ( + tanh(this.smooth * x + dc) - tanh_dc; +); + +@slider + +on_slider(); + +@block + +while (midirecv(offset, msg1, note, vel)) ( + event = msg1 & 0xF0; + channel = msg1 & 0x0F; + + event == 0x90 && vel > 0 ? ( // NOTEON + note_on = 1; + note_vel = vel / 127; + note_counter = 0; + note_freq = note2freq(note); + envelope.zen_trigger(1); + pitch_envelope.zen_trigger(1); + noise_envelope.zen_trigger(1); + ); + + midisend(offset, msg1, note, vel); +); + +@sample + +note_on ? ( + note_counter += 1 / srate * 1000; + note_counter > osc_att + osc_dec && note_counter > noise_att + noise_dec ? ( + note_on = 0; + envelope.zen_release(); + pitch_envelope.zen_release(); + noise_envelope.zen_release(); + ); + + envelope.zen_process(); + pitch_envelope.zen_process(); + noise_envelope.zen_process(); + + freq = pitch_track ? note_freq : osc_freq; + osc_cycle = wrap(osc_cycle + freq / srate * pow(16, pitch_amt * pitch_envelope.env)); + wave_l = make_wave(osc_cycle) * envelope.env; + wave_r = wave_l; + noise_l = make_noise() * noise_envelope.env; + noise_r = noise_stereo ? make_noise() * noise_envelope.env : noise_l; + noise_cutoff < 22000 ? ( + noise_l = noise_lowpass.rbj_df1_0(noise_l); + noise_r = noise_lowpass.rbj_df1_1(noise_r); + ); + + mix < 0 ? ( + noise_l *= 1 + mix; + noise_r *= 1 + mix; + ); + mix > 0 ? ( + wave_l *= 1 - mix; + wave_r *= 1 - mix; + ); + pan < 0 ? ( + noise_r *= 1 + pan; + wave_r *= 1 + pan; + ); + pan > 0 ? ( + noise_l *= 1 - pan; + wave_l *= 1 - pan; + ); + + signal_merged = 0; + (filter_shape == 0 && filter_freq < 22000) || + (filter_shape == 1) || + (filter_shape == 2 && filter_freq > 20) ? + ( + filter_sel == 0 ? ( + noise_l = noise_filter.rbj_df1_0(noise_l); + noise_r = noise_filter.rbj_df1_1(noise_r); + 1; + ); + filter_sel == 1 ? ( + wave_l = noise_filter.rbj_df1_0(wave_l); + wave_r = noise_filter.rbj_df1_1(wave_r); + ); + filter_sel == 2 ? ( + outl = noise_filter.rbj_df1_0(wave_l + noise_l); + outr = noise_filter.rbj_df1_1(wave_r + noise_r); + signal_merged = 1; + ); + ); + + !signal_merged ? ( + outl = noise_l + wave_l; + outr = noise_r + wave_r; + ); + + distortion > 0 ? ( + tube.smooth(); + outl = tube.amp(outl); + outr = tube.amp(outr); + ); + + outl = eq.rbj_df1_0(outl); + outr = eq.rbj_df1_1(outr); + + vel_mult = min(note_vel + (1 - vel_vol), 1); + spl0 += outl * gain * vel_mult.smooth(); + spl1 += outr * gain * vel_mult.smooth(); +); + +@gfx 700 280 + +gfx_clear = 0x141618; +mouse.update_mouse_state(); + +set_color(0x666666); +gfx_x = 20; gfx_y = 20; +gfx_drawstr("MIX"); + +draw_knob(20, 40, 1, "Mix", 0, -1, 1, 0, 1, sprintf(#, "%.2f", slider(1))); +draw_knob(80, 40, 2, "Vol", -12, -90, 0, 0, 0, sprintf(#, "%.1f Db", slider(2))); +draw_knob(20, 110, 3, "Pan", 0, -1, 1, 0, 1, sprintf(#, "%.2f", slider(3))); +draw_knob(80, 110, 4, "Drive", 0, 0, 48, 0, 0, sprintf(#, "%.1f Db", slider(4))); + +set_color(0x666666); +gfx_line(140, 40, 140, 170); +gfx_x = 140; gfx_y = 20; +gfx_drawstr(sprintf(#, "OSC %s", get_note_name())); + +draw_knob(160, 40, 5, "Freq", 440, 20, 10000, 1, 0, sprintf(#, "%i Hz", slider(5))); +draw_knob(220, 40, 7, "Att", 1, 1, 5000, 1, 0, sprintf(#, "%i ms", slider(7))); +draw_knob(160, 110, 8, "Dec", 70, 1, 5000, 1, 0, sprintf(#, "%i ms", slider(8))); +draw_knob(220, 110, 9, "Shape", 0, -1, 1, 0, 1, sprintf(#, "%.2f", slider(9))); + +shape_name = osc_wave == 0 ? "Sin" : osc_wave == 1 ? "Tri" : osc_wave == 2 ? "Saw" : "Sqr"; +draw_button(220, 20, 40, shape_name, 0); +mouse.left_click && mouse_in_rect(220, 20 - 2, 40, 10 + 2) ? ( + gfx_x = 220; gfx_y = 30; + choice = gfx_showmenu("Sine|Triangle|Saw|Square"); + slider(6) = max(choice - 1, 0); +); + +set_color(0x666666); +gfx_line(280, 40, 280, 170); +gfx_x = 280; gfx_y = 20; +gfx_drawstr("PITCH"); + +draw_knob(300, 40, 10, "Amt", 0, -1, 1, 0, 1, sprintf(#, "%.2f", slider(10))); +draw_knob(360, 40, 11, "Att", 1, 1, 5000, 1, 0, sprintf(#, "%i ms", slider(11))); +draw_knob(300, 110, 12, "Dec", 70, 1, 5000, 1, 0, sprintf(#, "%i ms", slider(12))); +draw_knob(360, 110, 13, "Shape", 0, -1, 1, 0, 1, sprintf(#, "%.2f", slider(13))); + +draw_button(340, 20, 60, "KTrack", slider(14)); +mouse.left_click && mouse_in_rect(340, 20 - 2, 60, 10 + 2) ? ( + slider(14) = !slider(14); +); + +set_color(0x666666); +gfx_line(420, 40, 420, 170); +gfx_x = 420; gfx_y = 20; +gfx_drawstr("NOISE"); + +draw_knob(440, 40, 16, "Color", 22050, 20, 22050, 1, 0, sprintf(#, "%i Hz", slider(16))); +draw_knob(500, 40, 17, "Att", 1, 1, 5000, 1, 0, sprintf(#, "%i ms", slider(17))); +draw_knob(440, 110, 18, "Dec", 70, 1, 5000, 1, 0, sprintf(#, "%i ms", slider(18))); +draw_knob(500, 110, 19, "Shape", 0, -1, 1, 0, 1, sprintf(#, "%.2f", slider(19))); + +draw_button(480, 20, 60, slider(15) ? "Stereo" : "Mono", slider(15)); +mouse.left_click && mouse_in_rect(480, 20 - 2, 60, 10 + 2) ? ( + slider(15) = !slider(15); +); + +set_color(0x666666); +gfx_line(560, 40, 560, 170); +gfx_x = 560; gfx_y = 20; +gfx_drawstr("FILTER"); + +draw_knob(580, 40, 21, "Freq", 22050, 20, 22050, 1, 0, sprintf(#, "%i Hz", slider(21))); +draw_knob(640, 40, 22, "Q", 0.5, 0.01, 100, 1, 0, sprintf(#, "%.2f", slider(22))); + +shape_name = filter_shape == 0 ? "LP" : filter_shape == 1 ? "BP" : "HP"; +draw_button(640, 20, 40, shape_name, 0); +mouse.left_click && mouse_in_rect(640, 20 - 2, 40, 10 + 2) ? ( + gfx_x = 640; gfx_y = 30; + choice = gfx_showmenu("LP|BP|HP"); + slider(20) = max(choice - 1, 0); + on_slider(); +); + +filter_name = filter_sel == 0 ? "Noise" : filter_sel == 1 ? "Osc" : "Both"; +draw_button(630, 110, 50, filter_name, 0); +mouse.left_click && mouse_in_rect(630, 110, 50, 10+2) ? ( + gfx_x = 630; gfx_y = 120; + choice = gfx_showmenu("Filter noise|Filter osc|Filter both"); + slider(23) = max(choice - 1, 0); +); + +set_color(0x666666); +gfx_x = 20; gfx_y = 180; +gfx_drawstr("VELOCITY"); + +draw_knob(20, 200, 27, "Vol", 1, 0, 1, 0, 0, sprintf(#, "%.2f", slider(27))); + +set_color(0x666666); +gfx_x = 140; gfx_y = 180; +gfx_drawstr("EQ"); + +draw_knob(160, 200, 24, "Freq", 440, 20, 22050, 1, 0, sprintf(#, "%i Hz", slider(24))); +draw_knob(220, 200, 25, "Gain", 0, -18, 18, 0, 1, sprintf(#, "%.2f Db", slider(25))); +draw_knob(280, 200, 26, "Q", 0.5, 0.01, 100, 1, 0, sprintf(#, "%.2f", slider(26))); + +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_JSDrumpad.jsfx.rpl b/Synth/tilr_JSDrumpad.jsfx.rpl new file mode 100644 index 0000000..61f849e --- /dev/null +++ b/Synth/tilr_JSDrumpad.jsfx.rpl @@ -0,0 +1,62 @@ + + + + + + + + + + + + + +> diff --git a/Synth/tilr_JSDrumpad/dp.envlib.jsfx-inc b/Synth/tilr_JSDrumpad/dp.envlib.jsfx-inc new file mode 100644 index 0000000..19757f1 --- /dev/null +++ b/Synth/tilr_JSDrumpad/dp.envlib.jsfx-inc @@ -0,0 +1,135 @@ +desc: dp.envlib.jsfx-inc +@init + +/* +Envlib 1.0 by tilr +modified version of zenvelib.v1 by dwelx +REAPER v7+ 2023 +----------------------------------- +Library for triggered Delay-Attack-Decay-Sustain-Release +envelope with shape, clamp and intensity control. +*/ + +// main equation for exponential coefficients and base values +function zen_calc(_targetB1,_targetB2,_targetC,_rate,_ratio,_mult1,_mult2) + instance (b,c) ( + _ratio > 1 ? ( // slow-start shape + //_ratio = _mult1 * (_ratio - 1); + _ratio = pow(_ratio - 1, 3); + c = exp(log((_targetC + _ratio) / _ratio) / _rate); + b = (_targetB1 - _mult2 * _ratio) * (1 - c); + ) : ( // fast-start shape (inverse exponential) + //_ratio = _mult1 * _ratio; + _ratio = pow(_ratio, 3); + c = exp(-log((_targetC + _ratio) / _ratio) / _rate); + b = (_targetB2 + _mult2 * _ratio) * (1 - c); + ); +); + +// set or update envelope variables +function zen_update(_delay,_attack,_decay,_sustain,_release,_attackS,_decayS,_releaseS,_intensity,_clampb,_clampt) + instance(delay,attack,decay,sustain,release,intensity,clampb,clampt,clamp,mysrate) ( + // seems that REAPER doesn't care about samplerate when plugin is not record-armed - we care + srate ? mysrate = srate : mysrate = 44100; // assume default if not reported + + // convert ms to samples, dB and % to ratios and normalize values + delay = min(max(_delay, 0) * 0.001, 60) * mysrate; + attack = min(max(_attack, 1) * 0.001, 60) * mysrate; + decay = min(max(_decay, 1) * 0.001, 60) * mysrate; + sustain = 10 ^ (min(max(_sustain, -90), 0) / 20); + release = min(max(_release, 1) * 0.001, 60) * mysrate; + + // for intensity and clamp + intensity = min(max(_intensity, 0) * 0.01, 1); + clampb = min(max(_clampb, 0) * 0.01, 1); + clampt = min(max(_clampt, 0) * 0.01, 1); + clamp = clampt - clampb; + + // for shape ratios + attack.s = min(max(_attackS, 0.001), 10000); + decay.s = min(max(_decayS, 0.001), 10000); + release.s = min(max(_releaseS, 0.001), 10000); + + // calculate envelope shape values + attack.env.zen_calc(0,1,1,attack,attack.s, 1, 1); + decay.env.zen_calc(1,sustain,1 - sustain,decay,decay.s,1, -1); +); + + +// (re)trigger envelope +function zen_trigger(_retrig) + instance (state,delay,trig,d1,d2,env) ( + trig += 1; // increase trigger count + _retrig ? ( // retriggering enabled? + !state ? ( // not triggered already? + d1 = 0; + state = 1; // activate initial delay countdown + ) : ( // else + d2 = delay + 1; // activate retrigger + ); + ) : ( // retriggering disabled? + d1 = 0; + env = 0; // reset envelope + state = 1; + ); +); + + +// release envelope +function zen_release() + instance (state,trig,d2,sustain,release,env) ( + !(trig = max(trig - 1, 0)) ? ( // all keys depressed? (also protect from single note off events) + d2 = 0; // reset retrigger counter + release.env.zen_calc(max(env, sustain),0,max(env, sustain),release,release.s,1,-1); // calculate envelope release shape from this point + state = 5; // activate release + ) : ( // some keys pressed? + trig == 1 ? d2 = 0; // if only one - reset retrigger counter + ); +); + +// emerge envelope +function zen_process() + instance (delay,attack,decay,sustain,release,intensity,clampb,clamp,state,d1,d2,env) ( + d2 ? ( // retrigger? + (d2 -= 1) < 1 ? ( // activate attack after secondary delay (if any) + d2 = 0; // reset retrigger + state = 2; + ); + ); + + state == 1 ? ( // initial delay + (d1 += 1) >= delay ? ( + state = 2; + ); + ); + + state == 2 ? ( // attack + env = attack.env.b + env * attack.env.c; + env >= 1 ? ( + env = 1; + state = 3; + ); + ); + + state == 3 ? ( // decay + env = decay.env.b + env * decay.env.c; + env <= sustain ? ( + env = sustain; + state = 4; + ); + ); + + state == 4 ? ( // sustain + env = sustain; + ); + + state == 5 ? ( // release + env = release.env.b + env * release.env.c; + env <= 0 ? ( + env = 0; + state = 0; + ); + ); + + clampb + env * intensity * clamp; // apply intensity and clamp then return function value (volume ratio) +); diff --git a/Synth/tilr_JSDrumpad/dp.gfxlib.jsfx-inc b/Synth/tilr_JSDrumpad/dp.gfxlib.jsfx-inc new file mode 100644 index 0000000..e28eab1 --- /dev/null +++ b/Synth/tilr_JSDrumpad/dp.gfxlib.jsfx-inc @@ -0,0 +1,118 @@ +desc:dp.gfxlib.jsfx-inc + +@init + +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 log_scale (value, max, min) ( + 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) ( + 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) ( + 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); + 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(0x5DBEE4); + 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(0x5DBEE4); + gfx_rect(x, y - 2, w, 10 + 2); + gfx_x = x; gfx_y = y; + !toggled ? ( + set_color(0x141618); + gfx_rect(x+1, y+1-2, w-2, 10); + ); + set_color(toggled ? 0xFFFFFF : 0x5DBEE4); + gfx_drawstr(label, 1, x+w, y+10); +); diff --git a/Synth/tilr_JSDrumpad/dp.mouselib.jsfx-inc b/Synth/tilr_JSDrumpad/dp.mouselib.jsfx-inc new file mode 100644 index 0000000..e36c0f8 --- /dev/null +++ b/Synth/tilr_JSDrumpad/dp.mouselib.jsfx-inc @@ -0,0 +1,34 @@ +desc:dp.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_JSDrumpad/dp.rbj_filter.jsfx-inc b/Synth/tilr_JSDrumpad/dp.rbj_filter.jsfx-inc new file mode 100644 index 0000000..a4c6941 --- /dev/null +++ b/Synth/tilr_JSDrumpad/dp.rbj_filter.jsfx-inc @@ -0,0 +1,424 @@ +desc:2nd-order RBJ filter +// Copyright (C) 2012-2015 Theo Niessink +// License: GPL - http://www.gnu.org/licenses/gpl.html + +// 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>Corner Frequency (Hz) + slider2:0.5<0.01,4.0,0.01>Q + + import Tale/rbj_filter.jsfx-inc + + @slider + lp.rbj_lp(slider1, slider2); + + @sample + mono = 0.5 * (spl0 + spl1); + spl0 = spl1 = lp.rbj_df1(mono); + + 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 + * rbj_bypass() -- Bypass + * rbj_mute() -- "None shall pass!" + 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).) + + Filter Functions + + * rbj_df1(sample) -- Direct Form 1 + * rbj_df2(sample) -- Direct Form 2 + Example: output = lp.rbj_df2(input); + Sends a sample through the filter, and returns its output. + + Miscellaneous Functions + + * rbj_reset1() -- Direct Form 1 + * rbj_reset2() -- Direct Form 2 + Example: lp.rbj_reset2(); + Resets the filter state. + + * rbj_bwtoq(bw) + * rbj_qtobw(q) + Example: q = rbj_bwtoq(2.0); + Converts bandwidth (in octaves) to Q factor, or vice versa. + + * rbj_scale(a0) + Scales the filter coefficients (i.e. divides by a0), and returns a0. + + 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. + +*/ + +@init + +SAMPLE_RATE = srate; + +function rbj_set_sample_rate(rate) ( + SAMPLE_RATE = rate; +); + +function rbj_bwtoq(bw) + local(x) +( + // q = 1/(2 * sinh(log(2) / 2 * bw)) + x = exp(0.5*log(2) * bw); + 1/(x - 1/x); +); + +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)); +); + + +function rbj_scale(a0) + instance(a1, a2, b0, b1, b2) + local(scale) +( + scale = 1/a0; + + a1 *= scale; + a2 *= scale; + + b0 *= scale; + b1 *= scale; + b2 *= scale; + + a0; +); + + +// Low-pass + +function rbj_lp(freq, q) + instance(a1, a2, b0, b1, b2) + local(w0, cos_w0, alpha, a0) +( + w0 = 2*$pi * min(freq / SAMPLE_RATE, 0.49); + cos_w0 = cos(w0); + alpha = sin(w0) / (2*q); + + b1 = 1 - cos_w0; + b0 = b2 = 0.5 * b1; + a0 = 1 + alpha; + a1 = -2 * cos_w0; + a2 = 1 - alpha; + + this.rbj_scale(a0); +); + + +// High-pass + +function rbj_hp(freq, q) + instance(a1, a2, b0, b1, b2) + local(w0, cos_w0, alpha, a0) +( + w0 = 2*$pi * min(freq / SAMPLE_RATE, 0.49); + cos_w0 = cos(w0); + alpha = sin(w0) / (2*q); + + b1 = -1 - cos_w0; + b0 = b2 = -0.5 * b1; + a0 = 1 + alpha; + a1 = -2 * cos_w0; + a2 = 1 - alpha; + + this.rbj_scale(a0); +); + + +// Band-pass (constant skirt gain, peak gain = Q) + +function rbj_bp(freq, q) + instance(a1, a2, b0, b1, b2) + local(w0, alpha, a0) +( + w0 = 2*$pi * min(freq / SAMPLE_RATE, 0.49); + alpha = sin(w0) / (2*q); + + b0 = q * alpha; + b1 = 0; + b2 = -b0; + a0 = 1 + alpha; + a1 = -2 * cos(w0); + a2 = 1 - alpha; + + this.rbj_scale(a0); +); + + +// Band-pass (constant 0 dB peak gain) + +function rbj_bp2(freq, q) + instance(a1, a2, b0, b1, b2) + local(w0, alpha, a0) +( + w0 = 2*$pi * min(freq / SAMPLE_RATE, 0.49); + alpha = sin(w0) / (2*q); + + b0 = alpha; + b1 = 0; + b2 = -alpha; + a0 = 1 + alpha; + a1 = -2 * cos(w0); + a2 = 1 - alpha; + + this.rbj_scale(a0); +); + + +// Band-stop + +function rbj_bs(freq, q) + instance(a1, a2, b0, b1, b2) + local(w0, alpha, a0) +( + w0 = 2*$pi * min(freq / SAMPLE_RATE, 0.49); + alpha = sin(w0) / (2*q); + + b0 = b2 = 1; + b1 = a1 = -2 * cos(w0); + a0 = 1 + alpha; + a2 = 1 - alpha; + + this.rbj_scale(a0); +); + + +// All-pass + +function rbj_ap(freq, q) + instance(a1, a2, b0, b1, b2) + local(w0, alpha, a0) +( + w0 = 2*$pi * min(freq / SAMPLE_RATE, 0.49); + alpha = sin(w0) / (2*q); + + b0 = a2 = 1 - alpha; + b1 = a1 = -2 * cos(w0); + b2 = a0 = 1 + alpha; + + this.rbj_scale(a0); +); + + +// Peaking EQ + +function rbj_eq(freq, q, gain) + instance(a1, a2, b0, b1, b2) + local(w0, alpha, a, a0) +( + w0 = 2*$pi * min(freq / SAMPLE_RATE, 0.49); + alpha = sin(w0) / (2*q); + a = gain; //sqrt(gain); + + b0 = 1 + alpha * a; + b1 = a1 = -2 * cos(w0); + b2 = 1 - alpha * a; + a0 = 1 + alpha / a; + a2 = 1 - alpha / a; + + this.rbj_scale(a0); +); + + +// Low-shelf + +function rbj_ls(freq, q, gain) + instance(a1, a2, b0, b1, b2) + local(w0, cos_w0, a, tmp0, tmp1, tmp2, a0) +( + w0 = 2*$pi * min(freq / SAMPLE_RATE, 0.49); + cos_w0 = cos(w0); + a = gain; //sqrt(gain); + + tmp0 = 2 * sqrt(a) * sin(w0) / (2 * q); + tmp1 = (a + 1) - (a - 1) * cos_w0; + tmp2 = (a + 1) + (a - 1) * cos_w0; + + b0 = a * (tmp1 + tmp0); + b1 = 2 * a * ((a - 1) - (a + 1) * cos_w0); + b2 = a * (tmp1 - tmp0); + a0 = tmp2 + tmp0; + a1 = -2 * ((a - 1) + (a + 1) * cos_w0); + a2 = tmp2 - tmp0; + + this.rbj_scale(a0); +); + + +// High-shelf + +function rbj_hs(freq, q, gain) + instance(a1, a2, b0, b1, b2) + local(w0, cos_w0, a, tmp0, tmp1, tmp2, a0) +( + w0 = 2*$pi * min(freq / SAMPLE_RATE, 0.49); + cos_w0 = cos(w0); + a = gain; //sqrt(gain); + + tmp0 = 2 * sqrt(a) * sin(w0) / (2 * q); + tmp1 = (a + 1) - (a - 1) * cos_w0; + tmp2 = (a + 1) + (a - 1) * cos_w0; + + b0 = a * (tmp2 + tmp0); + b1 = -2 * a * ((a - 1) + (a + 1) * cos_w0); + b2 = a * (tmp2 - tmp0); + a0 = tmp1 + tmp0; + a1 = 2 * ((a - 1) - (a + 1) * cos_w0); + a2 = tmp1 - tmp0; + + this.rbj_scale(a0); +); + + +// Bypass + +function rbj_bypass() + instance(a1, a2, b0, b1, b2) +( + a1 = a2 = b1 = b2 = 0; + b0 = 1; +); + + +// Mute + +function rbj_mute() + instance(a1, a2, b0, b1, b2) +( + a1 = a2 = b0 = b1 = b2 = 0; +); + + +// Direct Form 1, output 0 + +function rbj_df1_0(sample) + instance(a1, a2, b0, b1, b2, x0_0, x1_0, y0_0, y1_0) + local(x2, y2) +( + x2 = x1_0; + x1_0 = x0_0; + x0_0 = sample; + + y2 = y1_0; + y1_0 = y0_0; + y0_0 = b0*x0_0 + b1*x1_0 + b2*x2 - a1*y1_0 - a2*y2; +); + +function rbj_reset1_0() + instance(x0_0, x1_0, y0_0, y1_0) +( + x0_0 = x1_0 = y0_0 = y1_0 = 0; +); + +// Direct Form 1, output 1 + +function rbj_df1_1(sample) + instance(a1, a2, b0, b1, b2, x0_1, x1_1, y0_1, y1_1) + local(x2, y2) +( + x2 = x1_1; + x1_1 = x0_1; + x0_1 = sample; + + y2 = y1_1; + y1_1 = y0_1; + y0_1 = b0*x0_1 + b1*x1_1 + b2*x2 - a1*y1_1 - a2*y2; +); + +function rbj_reset1() + instance(x0_1, x1_1, y0_1, y1_1) +( + x0_1 = x1_1 = y0_1 = y1_1 = 0; +); + + +// Direct Form 2 + +function rbj_df2(sample) + instance(a1, a2, b0, b1, b2, w0, w1) + local(w2) +( + w2 = w1; + w1 = w0; + w0 = sample - a1*w1 - a2*w2; + + b0*w0 + b1*w1 + b2*w2; +); + +function rbj_reset2() + instance(w0, w1) +( + w0 = w1 = 0; +); + +function magnitude(freq) + instance(a1, a2, b0, b1, b2, w0, w1) + local(w, numerator, denominator) +( + w = 2.0 * $pi * freq / SAMPLE_RATE; + numerator = b0*b0 + b1*b1 + b2*b2 + 2.0 *(b0*b1 + b1*b2)*cos(w) + 2.0*b0*b2*cos(2.0*w); + denominator = 1.0 + a1*a1 + a2*a2 + 2.0 *(a1 + a1*a2)*cos(w) + 2.0*a2*cos(2.0*w); + + // Scalar for the response + mag = sqrt(numerator / denominator); + + // Convert to Db + //20 * log10(mag); +); + +// Legacy + +function rbj_notch(freq, q) ( this.rbj_bs(freq, q) ); +function rbj_peak(freq, q, db_gain) ( this.rbj_eq(freq, q, 10^(0.05 * db_gain)) ); +function rbj_low_shelf(freq, q, db_gain) ( this.rbj_ls(freq, q, 10^(0.05 * db_gain)) ); +function rbj_high_shelf(freq, q, db_gain) ( this.rbj_hs(freq, q, 10^(0.05 * db_gain)) );