desc: Retro-Grit Master (Sinus RM & Fixed Scope)
// Gebaseerd op JSFX Programming Reference [cite: 1]

// Sliders met gecorrigeerde volgorde en standaardwaarden
slider1:0<-12,36,0.1>Input Gain (dB)
slider2:0<0,1,0.01>Waveshape Drive
slider3:0<0,1,0.01>Ringmod Mix
slider4:440<20,2000,1>RM Freq (Hz)
slider5:0<0,1,1{Sinus,Sawtooth}>RM Shape
slider6:16<1,16,1>Bit Depth
slider7:1<0.01,1,0.01>Sample Rate Reducer
slider8:0<-24,0,0.1>Clip Threshold (dB)
slider9:0<-24,24,0.1>Output Gain (dB)
slider10:400<100,4000,10>Scope Zoom

@init
// Gebruik db2amp helper [cite: 203]
function db2amp(db) ( 10^(db/20); ); 
// Lokale geheugen adressen [cite: 185, 186]
buf_L = 0;      
buf_R = 10000;  
buf_size_max = 8000; // Maximale bufferlengte
hold0 = 0; hold1 = 0; count = 0;
rm_phase = 0;
peak_val = 0;

@slider
// Lees slider waarden [cite: 125, 359]
ingain = db2amp(slider1);
drive = slider2 * 10;
rm_mix = slider3;
rm_f = (2 * $pi * slider4) / srate; // [cite: 156, 392]
rm_type = slider5; // 0=Sinus, 1=Saw
bits = 2^(slider6); 
srate_mod = slider7;
thresh = db2amp(slider8);
outgain = db2amp(slider9);
zoom = slider10;

@sample
// Hoofd audio verwerking [cite: 131, 132]

// 1. Gain & Distortion
s0 = spl0 * ingain; 
s1 = spl1 * ingain;
drive > 0 ? (
  s0 = atan(s0 * drive) / atan(drive); // [cite: 283]
  s1 = atan(s1 * drive) / atan(drive);
);

// 2. Ring Modulator
rm_type == 0 ? (
  rm_osc = sin(rm_phase); // [cite: 278]
) : (
  rm_osc = (rm_phase / $pi) - 1; // Zaagtand [cite: 156]
);
rm_phase += rm_f;
rm_phase > $pi ? rm_phase -= 2 * $pi;

// Pas mix toe (0 mix = bypass)
s0 = (s0 * (1-rm_mix)) + ((s0 * rm_osc) * rm_mix);
s1 = (s1 * (1-rm_mix)) + ((s1 * rm_osc) * rm_mix);

// 3. Lofi (Bits & Sample Rate)
s0 = floor(s0 * bits + 0.5) / bits; // [cite: 300]
s1 = floor(s1 * bits + 0.5) / bits;
count += srate_mod;
count >= 1 ? ( hold0 = s0; hold1 = s1; count -= 1; );

// 4. Hard Clipper (0dB Limit) [cite: 294, 295, 346]
spl0 = max(-thresh, min(thresh, hold0));
spl1 = max(-thresh, min(thresh, hold1));

// 5. Output
spl0 *= outgain; 
spl1 *= outgain;

// Peak detectie [cite: 293]
abs(spl0) > peak_val ? peak_val = abs(spl0);

// Sla samples op in lokale geheugen slots [cite: 184, 185]
buf_L[buf_pos] = spl0;
buf_R[buf_pos] = spl1;

// Trigger op zero-crossing voor de scope
(last_s < 0 && spl0 >= 0) ? ( trig_pos = buf_pos; );
last_s = spl0;

buf_pos += 1; 
buf_pos >= buf_size_max ? buf_pos = 0;

@gfx 500 350
// Teken-engine [cite: 140, 1140]
gfx_clear = 0;

// Teken Rode Clipping Zones [cite: 990, 1137]
gfx_r = 1; gfx_g = 0; gfx_b = 0; gfx_a = 0.15; 
ty = (1 - thresh) * gfx_h * 0.5;
by = (1 + thresh) * gfx_h * 0.5;
gfx_rect(0, 0, gfx_w, ty); 
gfx_rect(0, by, gfx_w, gfx_h - by); 

// Teken dikke Threshold lijnen [cite: 986]
gfx_a = 0.8;
gfx_line(0, ty, gfx_w, ty); 
gfx_line(0, by, gfx_w, by);

// Teken de Waveforms (L=Cyaan, R=Magenta) [cite: 983, 1137]
x_scale = gfx_w / zoom;
i = 0;
loop(zoom,
  p = (trig_pos + i) % buf_size_max;
  
  // Linker kanaal (spl0)
  y_l = (1 - buf_L[p]) * gfx_h * 0.5;
  gfx_r=0; gfx_g=1; gfx_b=1; gfx_a=0.8;
  i == 0 ? (gfx_x=0; gfx_y=y_l;) : gfx_lineto(i * x_scale, y_l, 1);
  
  // Rechter kanaal (spl1)
  y_r = (1 - buf_R[p]) * gfx_h * 0.5;
  gfx_r=1; gfx_g=0; gfx_b=1; gfx_a=0.5;
  tx = i * x_scale; 
  i == 0 ? (rx=0; ry=y_r;) : (gfx_line(rx, ry, tx, y_r); rx=tx; ry=y_r;);
  
  i += 1;
);

// Peak info onderaan [cite: 292, 1024]
gfx_r=1; gfx_g=1; gfx_b=1; gfx_a=0.6;
gfx_x=10; gfx_y=gfx_h-20;
gfx_drawstr("PEAK: "); gfx_drawnumber(20*log10(peak_val), 2); gfx_drawstr(" dB (CLICK TO RESET)");
mouse_cap & 1 ? peak_val = 0; // [cite: 1173]
