desc:Simple drum synth
//tags: generator synthesis
//author: Tale

// Copyright (C) 2014-2022 Theo Niessink
// License: LGPL - http://www.gnu.org/licenses/lgpl.html

// This electronic drum synth supports the following General MIDI (GM)
// subset:

// Note | Sound          | Outputs
// -----+----------------+--------
//  31  | Sticks         |  21/22
//  35  | Bass Drum 2    |   1/2
//  36  | Bass Drum 1    |   1/2
//  37  | Rimshot        |   5/6
//  38  | Snare Drum 1   |   3/4
//  40  | Snare Drum 2   |   3/4
//  41  | [Low Tom]      |   9/10
//  42  | Closed Hi-hat  |   7/8
//  43  | Low Tom        |   9/10
//  44  | Pedal Hi-hat   |   7/8
//  45  | [Mid Tom]      |  11/12
//  46  | Open Hi-hat    |   7/8
//  47  | Mid Tom        |  11/12
//  48  | [High Tom]     |  13/14
//  49  | Crash Cymbal   |  17/18
//  50  | High Tom       |  13/14
//  51  | Ride Cymbal    |  15/16
//  52  | [Crash Cymbal] |  17/18
//  53  | Ride Bell      |  15/16
//  54  | Tambourine     |  19/20
//  55  | [Crash Cymbal] |  17/18
//  56  | Cowbell        |  21/22
//  57  | [Crash Cymbal] |  17/18
//  59  | [Ride Cymbal]  |  15/16
//  82  | Shaker         |  19/20

// Internally the different sounds are generated by 12 sound generators,
// whose outputs can be mixed down to 2 channels in mono/stereo mode, or to
// 22 channels (11 channel pairs) in multichannel mode.

slider1:-5318008<-36.0,12.0,0.01>-[Deprecated] Bass Drum (dB)
slider2:-5318008<-36.0,12.0,0.01>-[Deprecated] Snare Drum (dB)
slider3:-5318008<-36.0,12.0,0.01>-[Deprecated] Side Stick (dB)
slider4:-5318008<-36.0,12.0,0.01>-[Deprecated] Hi-Hat (dB)
slider5:-5318008<-36.0,12.0,0.01>-[Deprecated] Low Tom (dB)
slider6:-5318008<-36.0,12.0,0.01>-[Deprecated] Mid Tom (dB)
slider7:-5318008<-36.0,12.0,0.01>-[Deprecated] High Tom (dB)
slider8:-5318008<-36.0,12.0,0.01>-[Deprecated] Ride Cymbal (dB)
slider9:-5318008<-36.0,12.0,0.01>-[Deprecated] Crash Cymbal (dB)
slider10:0<0,1>-Unused
slider11:-5318008<-100,100,1>-[Deprecated] Bass Drum (%)
slider12:-5318008<-100,100,1>-[Deprecated] Snare Drum (%)
slider13:-5318008<-100,100,1>-[Deprecated] Side Stick (%)
slider14:-5318008<-100,100,1>-[Deprecated] Hi-Hat (%)
slider15:-5318008<-100,100,1>-[Deprecated] Low Tom (%)
slider16:-5318008<-100,100,1>-[Deprecated] Mid Tom (%)
slider17:-5318008<-100,100,1>-[Deprecated] High Tom (%)
slider18:-5318008<-100,100,1>-[Deprecated] Ride Cymbal (%)
slider19:-5318008<-100,100,1>-[Deprecated] Crash Cymbal (%)
slider20:0<0,1>-Unused
slider21:0<0,16,1{Any,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}>-MIDI Ch
slider22:1<0,2,1{Mono,Stereo,Multi}>-Mode
slider23:0<0,100,1>-Drive (%)
slider24:0.0<-36.0,12.0,0.01>-Volume (dB)
slider25:0<0,1>-Unused
slider26:-6.0<-36.0,12.0,0.01>-Bass Drum (dB)
slider27:0<-100,100,1>-Bass Drum (%)
slider28:0.0<-36.0,12.0,0.01>-Snare Drum (dB)
slider29:0<-100,100,1>-Snare Drum (%)
slider30:0.0<-36.0,12.0,0.01>-Rimshot (dB)
slider31:0<-100,100,1>-Rimshot (%)
slider32:-18.0<-36.0,12.0,0.01>-Hi-Hat (dB)
slider33:33<-100,100,1>-Hi-hat (%)
slider34:-6.0<-36.0,12.0,0.01>-Low Tom (dB)
slider35:-50<-100,100,1>-Low Tom (%)
slider36:-6.0<-36.0,12.0,0.01>-Mid Tom (dB)
slider37:-10<-100,100,1>-Mid Tom (%)
slider38:-6.0<-36.0,12.0,0.01>-High Tom (dB)
slider39:25<-100,100,1>-High Tom (%)
slider40:-18.0<-36.0,12.0,0.01>-Ride Cymbal (dB)
slider41:-33<-100,100,1>-Ride Cymbal (%)
slider42:-18.0<-36.0,12.0,0.01>-Crash Cymbal (dB)
slider43:20<-100,100,1>-Crash Cymbal (%)
slider44:-12.0<-36.0,12.0,0.01>-Tambourine (dB)
slider45:0<-100,100,1>-Tambourine (%)
slider46:-6.0<-36.0,12.0,0.01>-Cowbell (dB)
slider47:0<-100,100,1>-Cowbell (%)

out_pin:Bass Drum
out_pin:Bass Drum
out_pin:Snare Drum
out_pin:Snare Drum
out_pin:Rimshot
out_pin:Rimshot
out_pin:Hi-Hat
out_pin:Hi-Hat
out_pin:Low Tom
out_pin:Low Tom
out_pin:Mid Tom
out_pin:Mid Tom
out_pin:High Tom
out_pin:High Tom
out_pin:Ride Cymbal
out_pin:Ride Cymbal
out_pin:Crash Cymbal
out_pin:Crash Cymbal
out_pin:Tambourine
out_pin:Tambourine
out_pin:Cowbell
out_pin:Cowbell

options:no_meter

import midi_queue.jsfx-inc
import adsr.jsfx-inc
import noise.jsfx-inc
import sine.jsfx-inc
import poly_blep.jsfx-inc
import rc_filter.jsfx-inc
import rbj_filter.jsfx-inc

@init

gfx_ext_retina = ext_noinit = 1;
gfx_clear = -1;

// memset(gui_default = 0, 0, 25);
// Volume        Pan
0[ 3] =  -6.0;                // BD
0[ 9] = -18.0;   0[10] =  33; // HH
0[11] =  -6.0;   0[12] = -50; // LT
0[13] =  -6.0;   0[14] = -10; // MT
0[15] =  -6.0;   0[16] =  25; // HT
0[17] = -18.0;   0[18] = -33; // RD
0[19] = -18.0;   0[20] =  20; // CY
0[21] = -12.0;                // TB
0[23] =  -6.0;                // CB

gui_pad = /* gui_default + */ 25;
// Right-Click   Alt-Click      Left-Click     Label
25[ 0] =         25[ 1] = 35;   25[ 2] = 36;   25[ 4] = "BD";
25[ 5] =         25[ 6] =       25[ 7] = 37;   25[ 9] = "RS";
25[10] =         25[11] = 40;   25[12] = 38;   25[14] = "SD";
25[15] = 46;     25[16] = 44;   25[17] = 42;   25[19] = "HH";
25[20] =         25[21] =       25[22] = 41;   25[24] = "LT";
25[25] =         25[26] =       25[27] = 45;   25[29] = "MT";
25[30] =         25[31] =       25[32] = 48;   25[34] = "HT";
25[35] =         25[36] = 53;   25[37] = 51;   25[39] = "RD";
25[40] =         25[41] =       25[42] = 49;   25[44] = "CY";
25[45] =         25[46] = 82;   25[47] = 54;   25[49] = "TB";
25[50] =         25[51] = 31;   25[52] = 56;   25[54] = "CB";

noise_gain = sqrt(srate * 1/44100);

function gain(db, inf)
(
  db > inf ? exp(db * /* log(10)/20 */ 0.11512925464970228); // 0 otherwise
);

function tanh(x)
  local(x2)
(
  x2 = sqr(x);
  min(max(-1, (((x2 + 378)*x2 + 17325)*x2 + 135135)*x / (((28*x2 + 3150)*x2 + 62370)*x2 + 135135)), 1);
);

drive_dc = 0.05;
drive_tanh = /* tanh(drive_dc) */ 0.049958291803755903;

function pan(gain, pos)
  // global(multich, vol)
  instance(ch, gain0, gain1)
(
  multich > 0 ? (
    multich == 1 ? multich = 0;
    ch = multich;
    multich += 2;
  ) : (
    ch = 0;
  );

  gain1 = gain0 = gain(gain, -36.0) * vol;

  multich >= 0 ? (
    pos = min(max(-1, pos * 0.01), 1);
    gain0 *= ((pos * /* (0.25*$pi - 0.5) */  0.28539816339744831 - 0.5) * pos - /* 0.25*$pi */ 0.78539816339744831) * pos + 1;
    gain1 *= ((pos * /* (0.5 - 0.25*$pi) */ -0.28539816339744831 - 0.5) * pos + /* 0.25*$pi */ 0.78539816339744831) * pos + 1;
  );
);

function sample(out)
  // global(drive, drive_dc, drive_tanh, spl0, spl1)
  instance(ch, gain0, gain1)
(
  drive > 1 ? out = tanh(drive * out + drive_dc) - drive_tanh;

  ch ? (
    spl(ch) += gain0 * out;
    spl(ch + 1) += gain1 * out;
  ) : (
    spl0 += gain0 * out;
    spl1 += gain1 * out;
  );
);

// Bass Drum

bd.env.d = 1; // adsr_setd(0)
bd.env.s = 1;

bd.lp.a = bd.lp2.rc_setf(90);

function bd(note, vel)
  instance(env, osc, lp, lp2)
  local(a, r, freq)
(
  note == 36 || note == 35 ? (
    note != this.note ? (
      this.note = note;

      // Bass Drum 1
      note == 36 ? (
        a = 0.012;
        r = 0.300;
        freq = 50;
      ) :

      // Bass Drum 2
      /* note == 35 ? */ (
        a = 0.018;
        r = 0.600;
        freq = 60;
      );

      env.adsr_seta(a);
      env.adsr_setr(r);

      osc.sin_setf(freq);
    );

    note == 35 ? (
      lp.lp = 0;
      lp2.lp = 0;
      vel *= 2/3;
    );
    env.adsr_a(vel * 1/127);
  );
);

function bd()
  // global(noise)
  instance(env, osc, note, lp, lp2)
  local(out)
(
  env.adsr_process() ? (
    env.state == 4 ? env.adsr_r();

    out = osc.sin_sin();
    note == 35 ? out += lp2.rc_lp(lp.rc_lp(noise));
    out * env.env;
  );
);

// Snare Drum

sd.env.adsr_seta(0.005);
sd.env.adsr_setd(0.025);
sd.env.s = 0.5;
sd.env.adsr_setr(0.350);

sd.lp.rc_setf(200);

function sd(note, vel)
  instance(env, lp, dry)
(
  note == 38 || note == 40 ? (
    note == 38 ? dry = 0.045 : // Snare Drum 1
    /* note == 40 ? */ dry = 0.1; // Snare Drum 2

    lp.lp = 0;
    env.adsr_a(vel * 1/127);
  );
);

function sd()
  // global(noise)
  instance(env, lp, dry)
  local(out)
(
  env.adsr_process() ? (
    env.state == 4 ? env.adsr_r();

    out = env.env * noise;
    lp.rc_lp(out) + out * dry;
  );
);

// Side Stick/Rimshot

rs.env.a = 1; // adsr_seta(0)
rs.env.adsr_setd(0.100);
rs.env.s = 0;
rs.env.r = 1; // adsr_setr(0)

rs.hp.rc_setf(1000);
rs.lp.rc_setf(1500);

function rs(note, vel)
  instance(env, hp, lp)
(
  note == 37 ? (
    hp.lp = 0;
    lp.lp = 0;
    env.adsr_a(vel * 1/127);
  );
);

function rs()
  // global(noise)
  instance(env, hp, lp)
(
  env.adsr_process() ? (
    env.state == 4 ? env.adsr_r();
    lp.rc_lp(hp.rc_hp(env.env * noise));
  );
);

// Hi-hat

hh.env.a = 1; // adsr_seta(0)
hh.hp.rc_setf(6000);

function hh(note, vel)
  instance(env)
  local(d, r)
(
  note == 42 || note == 44 || note == 46 ? (
    note != this.note ? (
      this.note = note;

      d = 0.085;
      env.s = 0.15;

      // Closed Hi-hat
      note == 42 ? (
        env.s = 0;
        r = 0;
      ) :
      // Pedal Hi-hat
      note == 44 ? (
        d = 0.008;
        r = 0.050;
      ) :
      // Open Hi-hat
      /* note == 46 ? */ (
        r = 1.5;
      );

      env.adsr_setd(d);
      env.adsr_setr(r);
    );

    env.adsr_a(vel * 1/127);
  );
);

function hh()
  // global(noise)
  instance(env, hp)
(
  env.adsr_process() ? (
    env.state == 4 ? env.adsr_r();
    hp.rc_hp(env.env * noise);
  );
);

// Low Tom

lt.env.adsr_seta(0.015);
lt.env.d = 1; // adsr_setd(0)
lt.env.s = 1;
lt.env.adsr_setr(1.5);

lt.lp.rc_setf(115);
lt.osc.sin_setf(75);

function lt(note, a, b, vel)
  instance(env, lp, lp2)
(
  note == a || note == b ? (
    lp.lp = lp2.lp = 0;
    lp2.a = lp.a;
    env.adsr_a(vel * 1/127);
  );
);

function lt()
  // global(noise)
  instance(env, osc, lp, lp2)
(
  env.adsr_process() ? (
    env.state == 4 ? env.adsr_r();
    (lp2.rc_lp(lp.rc_lp(noise)) + osc.sin_sin()) * env.env;
  );
);

function lt(note, vel) ( this.lt(note, 41, 43, vel) );

// Mid Tom

mt.env.adsr_seta(0.015);
mt.env.d = 1; // adsr_setd(0)
mt.env.s = 1;
mt.env.adsr_setr(1.25);

mt.osc.sin_setf(100);
mt.lp.rc_setf(125);

function mt(note, vel) ( this.lt(note, 45, 47, vel) );
function mt() ( this.lt() );

// High Tom

ht.env.adsr_seta(0.015);
ht.env.d = 1; // adsr_setd(0)
ht.env.s = 1;
ht.env.adsr_setr(1.0);

ht.osc.sin_setf(115);
ht.lp.rc_setf(115);

function ht(note, vel) ( this.lt(note, 48, 50, vel) );
function ht() ( this.lt() );

// Ride Cymbal/Bell

rd.env.a = 1; // adsr_seta(0)
rd.env.adsr_setd(0.015);
rd.env.s = 0.25;
rd.env.adsr_setr(1.5);

function rd(note, vel)
  instance(env, bp)
  local(freq, q)
(
  note == 51 || note == 59 || note == 53 ? (
    note &= 0x37;
    note != this.note ? (
      this.note = note;

      // Ride Cymbal
      note == 51 ? (
        freq = 6000;
        q = 2.0;
      ) :
      // Ride Bell
      /* note == 53 ? */ (
        freq = 5300;
        q = 5.0;
      );

      bp.rbj_bp(freq, q);
    );

    env.adsr_a(vel * 1/127);
  );
);

function rd()
  // global(noise)
  instance(env, bp)
(
  env.adsr_process() ? (
    env.state == 4 ? env.adsr_r();
    bp.rbj_df1(env.env * noise);
  );
);

// Crash Cymbal

cy.env.a = 1; // adsr_seta(0)
cy.env.adsr_setd(0.100);
cy.env.s = 0.9;
cy.env.adsr_setr(2.5);

cy.bp.rbj_bp(4000, 2.5);

function cy(note, vel)
  instance(env)
(
  note == 49 || note == 52 || note == 55 || note == 57 ? env.adsr_a(vel * 1/127);
);

function cy()
(
  this.rd();
);

// Tambourine/Shaker

tb.env.adsr_seta(0.040);
tb.env.adsr_setd(0.060);
tb.env.s = 0.1;
tb.env.adsr_setr(0.120);

tb.lp.rc_setf(1833);

function tb(note, vel)
  instance(env, hp)
  local(freq, q)
(
  note == 54 || note == 82 ? (
    note != this.note ? (
      this.note = note;

      // Tambourine
      note == 54 ? (
        freq = 5500;
        q = 10.0;
      ) :
      // Shaker
      /* note == 82 ? */ (
        freq = 7500;
        q = sqrt(0.5);
      );

      hp.rbj_hp(freq, q);
    );

    env.adsr_a(vel * 1/127);
  );
);

function tb()
  // global(noise)
  instance(env, hp, lp)
(
  env.adsr_process() ? (
    env.state == 4 ? env.adsr_r();
    lp.rc_lp(hp.rbj_df1(env.env * noise));
  );
);

// Cowbell

cb.env.adsr_seta(0.003);
cb.env.adsr_setd(0.350);
// cb.env.s = 0;
cb.env.r = 1; // adsr_setr(0)

cb.osc.poly_setf(465);
cb.bp.rbj_bp(1860, sqrt(0.5));

function cb(note, vel)
  instance(env)
(
  note == 56 ? env.adsr_a(vel * 1/127);
);

function cb()
  // global(noise)
  instance(env, osc, bp)
(
  env.adsr_process() ? (
    env.state == 4 ? env.adsr_r();
    bp.rbj_df2(osc.poly_trap() + noise * 0.1) * sqr(env.env);
  );
);

// Sticks

st.env.a = 1; // adsr_seta(0)
st.env.adsr_setd(0.050);
st.env.s = 0.1;
st.env.adsr_setr(0.050);

st.bp.rbj_bp(2700, 4.0);

function st(note, vel)
  instance(env, rng)
(
  note == 31 ? (
    rng.lcg_init(1234);
    env.adsr_a(vel * 0.5/127);
  );
);

function st()
  // global(noise_gain)
  instance(env, bp, rng)
(
  env.adsr_process() ? (
    env.state == 4 ? env.adsr_r();
    bp.rbj_df2(rng.lcg_white() * noise_gain) * sqr(env.env);
  );
);

function legacy()
  local(i)
(
  i = 0;
  loop(9,
    slider(i + 1) != -5318008 ? slider(2*i + 26) = slider(i + 1);
    slider(i + 11) != -5318008 ? slider(2*i + 27) = slider(i + 11);
    i += 1;
  );
);

@slider

legacy();

gui_slider_update = 1;
slider_update = 1;

@block

slider_update ? (
  slider_update = 0;

  midi_ch = max((slider21 + 0.0001)|0, 0) - 1;
  midi_ch > 15 ? midi_ch = -1;

  multich = ((slider22 + 0.0001)|0) - 1;
  multich < -1 || multich > 1 ? multich = 0;

  vol = gain(slider24, -36.0);
  drive = /* 0.012*log(10) */ 0.027631021115928548 * max(slider23, 0);
  drive > 0 ? vol *= exp(drive * -0.5);
  drive = exp(drive);

  bd.pan(slider26, slider27);
  sd.pan(slider28, slider29);
  rs.pan(slider30, slider31);
  hh.pan(slider32, slider33);
  lt.pan(slider34, slider35);
  mt.pan(slider36, slider37);
  ht.pan(slider38, slider39);
  rd.pan(slider40, slider41);
  cy.pan(slider42, slider43);
  tb.pan(slider44, slider45);
  cb.pan(slider46, slider47);

  st.ch = cb.ch; st.gain0 = cb.gain0; st.gain1 = cb.gain1;
);

gui_trigger ? (
  note = gui_note;
  vel = gui_vel;
  gui_trigger = 0;
);

@sample

while(
  note ? (
    gui_pad_update = 1;

    bd.bd(note, vel) ||
    sd.sd(note, vel) ||
    rs.rs(note, vel) ||
    hh.hh(note, vel) ||
    lt.lt(note, vel) ||
    mt.mt(note, vel) ||
    ht.ht(note, vel) ||
    rd.rd(note, vel) ||
    cy.cy(note, vel) ||
    tb.tb(note, vel) ||
    cb.cb(note, vel) ||
    st.st(note, vel);

    note = 0;
  );

  midi.midiq_recv() ? (
    (midi_ch < 0 || (midi.msg1 & 0x0F) == midi_ch)
    && (midi.msg1 & 0xF0) == 0x90 && midi.msg3 ? (
      note = midi.msg2;
      vel = midi.msg3;
    );
    1; // while
  );
);

noise = rng.lcg_white() * noise_gain;

bd.sample(bd.bd());
sd.sample(sd.sd());
rs.sample(rs.rs());
hh.sample(hh.hh());
lt.sample(lt.lt());
mt.sample(mt.mt());
ht.sample(ht.ht());
rd.sample(rd.rd());
cy.sample(cy.cy());
tb.sample(tb.tb());
cb.sample(cb.cb());
st.sample(st.st());

@gfx 600 400

function gui_mouse_cap()
  // global(gui_slider_update, gui_pad_update, gui_mouse_cap, gui_mouse_drag, gui_mouse_y, gui_mouse_dbl,
  // gui_slider_x1, gui_slider_y1, gui_slider_x2, gui_slider_y2, gui_slider_x3, gui_slider_y3,
  // gui_pad_x1, gui_pad_y1, gui_pad_x2, gui_pad_y2,
  // mouse_cap, mouse_x, mouse_y)

  static(timer, x, y)
(
  timer ? timer -= 1;

  gui_mouse_drag ? (
    mouse_cap & 1 ? (
      mouse_y != gui_mouse_y ? gui_slider_update = 1;
    ) : (
      gui_mouse_drag = 0;
    );
  );

  (mouse_cap & 3) != (gui_mouse_cap & 3) ? (
    mouse_cap & 1 ? (
      !(gui_mouse_cap & 1) ? (
        timer && mouse_x == x && mouse_y == y ? (
          timer = 0;
          gui_mouse_dbl = 1;
        ) : (
          timer = 12;
          x = mouse_x;
          y = mouse_y;
          gui_mouse_dbl = 0;
        );
      );
    ) :
    gui_mouse_cap & 1 ? (
      gui_mouse_dbl = 0;
    );

    mouse_x >= gui_slider_x1 && mouse_x < gui_slider_x2 && mouse_y >= gui_slider_y1 && mouse_y < gui_slider_y3
    && (mouse_x < gui_slider_x3 || mouse_y < gui_slider_y2) ? gui_slider_update = 1;

    mouse_x >= gui_pad_x1 && mouse_x < gui_pad_x2 && mouse_y >= gui_pad_y1 && mouse_y < gui_pad_y2 ? gui_pad_update = 1;
  );

  mouse_wheel ? gui_slider_update = 1;
);

function slider_update(idx, lo, hi, delta)
  // global(slider_update)
  local(x, y)
(
  x = slider(idx);
  y = min(max(lo, x + delta), hi);

  slider(idx) = y;
  y < x || y > x ? (
    slider_automate(2 ^ (idx - 1));
    slider_update = 1;
  );
);

function slider_cycle(idx, lo, hi, delta)
  // global(slider_update)
  local(y)
(
  y = slider(idx) + delta;
  y > hi ? y = lo : y < lo ? y = hi;

  slider(idx) = y;
  slider_automate(2 ^ (idx - 1));
  slider_update = 1;
);

function slider_reset(idx, value)
  local(old)
(
  old = slider(idx);

  value < old || value > old ? (
    slider(idx) = value;
    slider_automate(2 ^ (idx - 1));
    slider_update = 1;
  );
);

function gui_setfg(shade)
  // global(gfx_r, gfx_g, gfx_b)
(
  gfx_r = shade <= 1 ? 0 : shade - 1;
  gfx_g = shade;
  gfx_b = shade * 2/3;
);

function gui_setbg()
  // global(gfx_r, gfx_g, gfx_b)
(
  gfx_r = 0;
  gfx_g = 1/15;
  gfx_b = 2/45;
);

function gui_drawstr(x, y, w, str)
  // global(gfx_w, gfx_x, gfx_y)
  local(w2, h)
(
  gfx_measurestr(str, w2, h);

  gfx_x = x + ((w - w2)|0) * 0.5;
  gfx_y = y;
  gfx_drawstr(str);
);

function gui_drawbg()
  // global(gui_slider_update, gui_pad_update, gfx_w, gfx_h, gfx_a, gfx_texth)
  static(i, old_t, old_w, old_h)
  local(t, str, x, y)
(
  t = time();
  t - old_t >= 2 ? (
    old_t ? i = rand(8) & 7;
    i >= 5 ? i = 0;
    old_w = 0;
  );
  old_t = t;

  gfx_w != old_w || gfx_h != old_h ? (
    old_w = gfx_w;
    old_h = gfx_h;

    gfx_a = 1;
    gui_setbg();
    gfx_rect(0, 0, gfx_w, gfx_h);

    str = #;
    strcpy(str, "Arial");
    gfx_setfont(1, str, gfx_w * 0.021);
    gfx_setfont(2, str, gfx_w * 0.026);

    strcpy(str,
    i == 0 ? "Simple Drum Synth" :
    i == 1 ? "Swedish Druum Suunth" :
    i == 2 ? "Imperial Drum Sith":
    i == 3 ? "Silly Drum Kit":
    i == 4 ? "More Cowbell, Baby!");

    gui_setfg(0.75);
    x = y = gfx_w * 0.02;
    gfx_rect(x, y, gfx_w * 0.96, gfx_w * 0.01 + gfx_texth);
    gui_setbg();
    gui_drawstr(0, y + gfx_texth * 1/6, gfx_w, str);

    gui_slider_update = 1;
    gui_pad_update = 1;
  );
);

function gui_drawslider(x, y, w, h, shade1, shade2, pos)
  local(h2)
(
  x += 0.5;
  y += 0.5;

  h2 = ((1 - max(0, min(1, pos))) * h + 0.5)|0;
  h = (h + 0.5)|0;

  h2 > 0 ? (
    gui_setfg(shade2);
    gfx_rect(x, y, w, h2);
  );

  h2 < h ? (
    gui_setfg(shade1);
    gfx_rect(x, y + h2, w, h - h2);
  );
);

function gui_drawknob(x, y, d, shade, pos)
  local(r, ang, ang_x, ang_y, x2, y2)
(
  x -= 0.5;
  y += 0.5;

  d = (d + 0.5)|1;
  r = 0.5*d;

  gui_setbg();
  gfx_rect(x - 1, y - 1, d + 2, d + 2);

  gui_setfg(shade);
  x += r|0;
  y += r|0;
  gfx_circle(x|0, y|0, r|0, 1);

  ang = 1.25*$pi - max(0, min(1, pos)) * 1.5*$pi;
  ang_x = cos(ang) * r;
  ang_y = sin(ang) * r;

  gui_setbg();
  x2 = (x -= 0.5) + ang_x;
  y2 = (y -= 0.5) - ang_y;
  x += ang_x * 0.6;
  y -= ang_y * 0.6;
  gfx_line(x, y, x2, y2);

  abs(x2 - x) > abs(y2 - y) ? (
    y += 1;
    y2 += 1;
  ) : (
    x += 1;
    x2 += 1;
  );
  gfx_line(x, y, x2, y2);
);

function gui_slider_mouse_cap(i, j, x, y3, w, h)
  // global(gui_mouse_drag, gui_mouse_y, gui_mouse_dbl, gui_default,
  // gui_slider_y1, gui_slider_y2,
  // mouse_cap, mouse_x, mouse_y, mouse_wheel)

  local(idx, lo, hi, delta)
(
  idx = 0;

  gui_mouse_drag ? (
    gui_mouse_drag >= 23 ? (
      delta = gui_mouse_y - mouse_y;
      mouse_cap & 4 ? delta *= 0.1;

      gui_mouse_drag == i ? ( idx = i; delta /= h; ) :
      gui_mouse_drag == j ? ( idx = j; delta *= 0.25 / w; );
    );
  ) :

  mouse_wheel || gui_mouse_dbl || (mouse_cap & 1) ? (
    mouse_x >= x && mouse_x < x + w ? (
      mouse_y >= gui_slider_y1 && mouse_y < gui_slider_y1 + h ? idx = i :
      mouse_y >= y3 && mouse_y < gui_slider_y2 ? idx = j;

      idx > 0 ? (
        mouse_wheel ? (
          gfx_getchar(); // Force mouse_cap update, seems to work only if JSFX window has focus.
          delta = mouse_wheel * (idx == i ? 0.5/(48.0 * 120) : 1/(200 * 120));
          idx == 23 ? delta *= 2;
          mouse_cap & 4 ? delta *= 0.1;
        ) : (
          gui_mouse_dbl ? slider_reset(idx, mouse_cap & 16 ? 0 : gui_default[idx - 23]) : gui_mouse_drag = idx;
          idx = -1;
        );
      );
    );
  );

  idx > 0 ? (
    idx == i ? ( lo = -36.0; hi = 12.0; ) :
    idx == j ? ( j == 23 ? lo = 0 : lo = -100; hi = 100; );

    slider_update(idx, lo, hi, delta * (hi - lo));
    idx = -1;
  );

  idx == 0;
);

function gui_slider_update()
  // global(slider_update, gui_slider_update, gui_mouse_cap, gui_mouse_drag, gui_mouse_y, gui_mouse_dbl,
  // gui_slider_x1, gui_slider_y1, gui_slider_x2, gui_slider_y2, gui_slider_x3, gui_slider_y3,
  // gfx_w, gfx_h, gfx_texth, mouse_cap, mouse_x, mouse_y, mouse_wheel, slider21, slider22)

  local(x, w, y2, h, y3, y4, dx, shade, i, j, mouse, str, db, pct, abs_pct, ch, mode)
(
  gui_slider_update = 0;
  mode = (slider22 + 0.0001)|0;

  x = gui_slider_x1 = gfx_w * 0.02;
  w = gfx_w * 0.85/12;

  gfx_setfont(2);
  gui_slider_y1 = x * 2 + gfx_texth;
  y2 = 0.25 * gfx_texth * 0.25 + gui_slider_y1;
  h = gfx_h - (gfx_w * 2.66/12 + gfx_texth);

  gfx_setfont(1);
  y3 = gfx_w * 0.01 + gui_slider_y1 + h;
  gui_slider_y2 = y3 + w;
  y4 = (w - gfx_texth) * 0.5 + y3;

  dx = gfx_w * 0.97/12;
  shade = 1;

  mouse = gui_mouse_drag || mouse_wheel || gui_mouse_dbl || (mouse_cap & 1);
  str = #;

  i = 24;
  loop(12,
    j = i + (i > 24 ? 1 : -1);

    mouse ? mouse = gui_slider_mouse_cap(i, j, x, y3, w, h);

    db = slider(i);
    gui_drawslider(x, gui_slider_y1, w, h, shade, shade * 2/3, (db + 36.0) * 1/48.0);

    pct = slider(j);
    abs_pct = abs(pct);
    gui_drawknob(x, y3, w, ((j == 23 || mode ? min(max(0, abs_pct * 0.01), 1) * 1/3) + 2/3) * shade, j > 23 ? pct * 0.005 + 0.5 : pct * 0.01);

    gui_setbg();

    db == 0.0 ? strcpy(str, "0.0") : db <= -36.0 ? strcpy(str, "-inf") : sprintf(str, "%+.1f", db);
    gfx_setfont(2);
    gui_drawstr(x, y2, w, str);

    j == 23 ? sprintf(str, "%.0f", pct) : pct == 0 ? strcpy(str, "C") : sprintf(str, "%.0f%c", abs_pct, pct < 0 ? 'L' : 'R');
    gfx_setfont(1);
    gui_drawstr(x, y4, w - 1, str);

    shade = 0.75;
    x += dx;
    i += i >= 32 || i == 24 ? 2 : i < 30 ? 4 : -2;
  );
  gui_slider_x2 = x - gfx_w * 0.01;

  x = gfx_w * 0.02;
  h = gfx_w * 0.005;
  y3 = gfx_h - gfx_w * 1.09/12 + 1;
  y4 = (w + h) * 0.5 + y3;

  gui_setfg(0.5);
  gfx_rect(x, y3, w, w);

  gui_setbg();
  gfx_rect(x, y4 - h, w, h);

  gui_setbg();
  gfx_setfont(1);

  gui_slider_x3 = x + w;
  gui_slider_y3 = gfx_h - x;

  gui_mouse_drag ? (
    gui_mouse_drag == 21 ? slider_update(21, 0, 16, (gui_mouse_y - mouse_y) * 2 / (y4 - h - y3));
  ) :

  (mouse_wheel || gui_mouse_dbl || (mouse_cap & 1))
  && mouse_x >= x && mouse_x < gui_slider_x3 && mouse_y >= y3 && mouse_y < y4 - h ? (
    mouse_wheel ? slider_update(21, 0, 16, mouse_wheel * 1/120) :
    gui_mouse_dbl ? slider_reset(21, 0) : gui_mouse_drag = 21;
  );

  ch = (slider21 + 0.0001)|0;
  ch >= 1 && ch <= 16 ? sprintf(str, "Ch %d", ch) : strcpy(str, "Ch -");
  gui_drawstr(x, gfx_texth * 1/3 + y3, w, str);

  (mouse_cap & 3) && !(gui_mouse_cap & 3)
  && mouse_x >= x && mouse_x < gui_slider_x3 && mouse_y >= y4 && mouse_y < gui_slider_y3 ? (
    slider_cycle(22, 0, 2, (mouse_cap & 2) || (mouse_cap & 16) ? -1 : 1);
  );

  mode = (slider22 + 0.0001)|0;
  strcpy(str, mode == 0 ? "Mono" : mode != 2 ? "Stereo" : "Multi");
  gui_drawstr(x, gfx_texth * 1/3 + y4, w, str);
);

function gui_drawpad(x, y, w, env)
  // global(gui_pad_update)
(
  gui_setfg(env * 2/3 + 0.5);
  gfx_rect(x, y, w, w);

  env > 0 ? gui_pad_update = 1;
);

function gui_pad_update()
  // global(gui_pad_update, gui_pad, gui_mouse_cap, gui_trigger, gui_note, gui_vel,
  // gui_pad_x1, gui_pad_y1, gui_pad_x2, gui_pad_y2,
  // bd.env.env, sd.env.env, rs.env.env, hh.env.env, lt.env.env, mt.env.env, ht.env.env, rd.env.env, cy.env.env, tb.env.env, cb.env.env, st.env.env,
  // gfx_w, gfx_h, gfx_texth, mouse_cap, mouse_x, mouse_y)

  local(x, w, y2, dx, trig, ptr, str)
(
  gui_pad_update = 0;

  ptr = gui_pad;
  ptr[3] = bd.env.env;
  ptr[8] = rs.env.env;
  ptr[13] = sd.env.env;
  ptr[18] = hh.env.env;
  ptr[23] = lt.env.env;
  ptr[28] = mt.env.env;
  ptr[33] = ht.env.env;
  ptr[38] = rd.env.env;
  ptr[43] = cy.env.env;
  ptr[48] = tb.env.env;
  ptr[53] = max(st.env.env * 4, cb.env.env);

  gfx_setfont(2);
  x = gui_pad_x1 = gfx_w * 1.21/12;
  w = gfx_w * 0.85/12;
  gui_pad_y1 = gfx_h - gfx_w * 1.09/12 + 1;
  gui_pad_y2 = gui_pad_y1 + w;
  y2 = (w - gfx_texth) * 0.5 + gui_pad_y1;
  dx = gfx_w * 0.97/12;

  trig = (mouse_cap & 3) && !(gui_mouse_cap & 3) && mouse_y >= gui_pad_y1 && mouse_y < gui_pad_y2 && !gui_trigger;

  loop(11,
    trig && mouse_x >= x && mouse_x < x + w ? (
      gui_note = ptr[mouse_cap & 2 ? 0 : mouse_cap & 16 ? 1 : 2];
      gui_vel = min(max(1, ((mouse_y - gui_pad_y1) * 160 / w)|0), 127);
      gui_trigger = 1;
      trig = 0;
    );

    gui_drawpad(x, gui_pad_y1, w, ptr[3]);

    str = ptr[4];
    gui_setbg();
    gui_drawstr(x, y2, w, str);

    x += dx;
    ptr += 5;
  );

  gui_pad_x2 = x - gfx_w * 0.01;
);

gui_mouse_cap();
gui_drawbg();

gui_slider_update ? gui_slider_update();
gui_pad_update ? gui_pad_update();

gui_mouse_cap = mouse_cap;
gui_mouse_y = mouse_y;
mouse_wheel = 0;
