desc:Overstratum EARTH
slider1:0<0,100,0.1>-Earth Gravity (Intensity)
slider2:60<30,120,1>-Focus Freq (Hz)

in_pin:left input
in_pin:right input
out_pin:left output
out_pin:right output

@init
ext_noinit = 1;

// --- 1. SICHERES MEMORY MAPPING ---
in_bufL   = 10000;  
in_bufR   = 20000;
fir_h     = 30000;  
dry_bufL  = 40000;  
dry_bufR  = 50000;  

// Partikel-System (Glühende Asche)
p_x = 60000; p_y = 60100; p_s = 60200; p_a = 60300;
num_particles = 60;
i = 0;
loop(num_particles,
    p_x[i] = rand(450);
    p_y[i] = rand(550);
    p_s[i] = 1.0 + rand(3.0);
    p_a[i] = rand(1.0);
    i += 1;
);

fir_len = 2047; 
fir_delay = (fir_len - 1) / 2; // 1023
half_len = 1023; // Für optimierte symmetrische Faltung

in_idx = 0;
dry_idx = 0;

peak_env = 0;
gr_lp = 1.0; 

gui_meter_env = 0;
anim_time = 0;

need_recalc = 1;
last_srate = 0;
last_freq = 0;

@slider
slider_automate(slider1);
slider_automate(slider2);

@block
inst = slider1 / 100;
wet_mix = inst * 0.25;     

pdc_delay = fir_delay;
pdc_bot_ch = 0; pdc_top_ch = 2;

srate > 0 && srate != last_srate ? (
    need_recalc = 1;
    last_srate = srate;
);

slider2 != last_freq ? (
    need_recalc = 1;
    last_freq = slider2;
);

need_recalc ? (
    need_recalc = 0;
    
    // --- 12dB/Oct Linear Phase Synthese (LOW PASS) ---
    x_f = tan($pi * slider2 / srate);
    a0 = x_f / (1 + x_f); 
    a1 = a0;              
    b1 = (1 - x_f) / (1 + x_f);

    i = 0; loop(fir_len, fir_h[i] = 0; i += 1;);
    fir_h[fir_delay] = 1.0; 
    
    // Vorwärts-Pass (6dB)
    i = 0; y1 = 0; x1 = 0;
    loop(fir_len,
        in = fir_h[i];
        y = a0 * in + a1 * x1 + b1 * y1;
        x1 = in; y1 = y;
        fir_h[i] = y;
        i += 1;
    );
    
    // Rückwärts-Pass (+6dB = 12dB/Okt)
    i = fir_len - 1; y1 = 0; x1 = 0;
    loop(fir_len,
        in = fir_h[i];
        y = a0 * in + a1 * x1 + b1 * y1;
        x1 = in; y1 = y;
        fir_h[i] = y;
        i -= 1;
    );
    
    // Blackman-Nuttall Fenster
    i = 0;
    loop(fir_len,
        w = 0.3635819 - 0.4891775 * cos(2 * $pi * i / (fir_len - 1)) + 0.1365995 * cos(4 * $pi * i / (fir_len - 1)) - 0.0106411 * cos(6 * $pi * i / (fir_len - 1));
        fir_h[i] *= w;
        i += 1;
    );

    atk_coeff = exp(-1.0 / (0.030 * srate)); 
    rel_coeff = exp(-1.0 / (0.150 * srate)); 
    gr_lp_coeff = exp(-1.0 / (0.050 * srate)); 
);

@sample
// 1. Phasenstarres Original-Signal sichern
dry_idx = (dry_idx + 1) & 4095;
dry_bufL[dry_idx] = spl0;
dry_bufR[dry_idx] = spl1;

// 2. Original-Signal in den Filter schicken
in_bufL[in_idx] = spl0;
in_bufL[in_idx + fir_len] = spl0;
in_bufR[in_idx] = spl1;
in_bufR[in_idx + fir_len] = spl1;

// 3. Linear Phase Low Pass Filter (Optimierte Symmetrie-Faltung)
// Da der Filter linearphasig ist, ist fir_h exakt symmetrisch. 
// Wir addieren das verbleibende älteste und das neueste Sample VOR der Multiplikation.
sumL = in_bufL[in_idx + 1024] * fir_h[1023]; // Center Tap
sumR = in_bufR[in_idx + 1024] * fir_h[1023];

rd_new = in_idx + fir_len;
rd_old = in_idx + 1;
j = 0;

loop(half_len,
    coef = fir_h[j];
    sumL += (in_bufL[rd_new] + in_bufL[rd_old]) * coef;
    sumR += (in_bufR[rd_new] + in_bufR[rd_old]) * coef;
    rd_new -= 1;
    rd_old += 1;
    j += 1;
);

fxL = sumL;
fxR = sumR;

// 4. Harmonic Weight Saturator
fxL = fxL + (fxL * fxL) * 0.4;
fxR = fxR + (fxR * fxR) * 0.4;

fxL = fxL / (1.0 + abs(fxL));
fxR = fxR / (1.0 + abs(fxR));

// 5. Thump Dynamics
lim_thresh = 0.05; 
make_up = 18.0;     

peak = max(abs(fxL), abs(fxR));
peak_env = peak > peak_env ? peak + atk_coeff * (peak_env - peak) : peak + rel_coeff * (peak_env - peak);
peak_safe = max(peak_env, 0.0001);

target_gr = peak_safe > lim_thresh ? lim_thresh / peak_safe : 1.0;
gr_lp = target_gr + gr_lp_coeff * (gr_lp - target_gr);

fxL *= (gr_lp * make_up);
fxR *= (gr_lp * make_up);

gui_meter_env = gui_meter_env * 0.995 + (fxL*fxL + fxR*fxR) * 0.005;

// 6. Finaler Mix (Phasenstarr)
rd_idx = (dry_idx - fir_delay) & 4095;
spl0 = dry_bufL[rd_idx] + fxL * wet_mix; 
spl1 = dry_bufR[rd_idx] + fxR * wet_mix;

in_idx += 1;
in_idx >= fir_len ? in_idx = 0;

@gfx 450 550
// Die Wurzel-Berechnung für das Metering geschieht jetzt nur noch in der 30Hz GUI-Schleife.
meter_rms = sqrt(gui_meter_env) * (inst * 1.5); 

gfx_clear = 0x100805; 

cx = gfx_w / 2;
cy = gfx_h / 2 - 40; 
radius = 110;
anim_time += 0.02;

mouse_cap & 1 ? (
    !mouse_down ? (
        mouse_down = 1;
        mouse_y > cy + radius + 40 ? (
            drag_mode = 2; 
            start_x = mouse_x;
            start_v2 = slider2;
        ) : (
            drag_mode = 1; 
            start_y = mouse_y;
            start_v1 = slider1;
            
            (time_precise() - last_click_time < 0.25) && (abs(mouse_x - cx) < radius) && (abs(mouse_y - cy) < radius) ? (
                slider1 = 0;
                slider_automate(slider1);
            );
            last_click_time = time_precise();
        );
    ) : (
        drag_mode == 1 ? (
            dy = start_y - mouse_y;
            slider1 = min(max(start_v1 + dy * 0.4, 0), 100);
            slider_automate(slider1); 
        ) : drag_mode == 2 ? (
            dx = mouse_x - start_x;
            slider2 = min(max(start_v2 + dx * 0.5, 30), 120); 
            slider_automate(slider2);
        );
    );
) : (
    mouse_down = 0;
    drag_mode = 0;
);

// Partikel
i = 0;
gfx_r=0.9; gfx_g=0.3; gfx_b=0.0; 
loop(num_particles,
    p_y[i] += p_s[i] * (0.5 + meter_rms * 2.0); 
    p_y[i] > gfx_h + 10 ? ( p_y[i] = -10; p_x[i] = rand(gfx_w); );
    
    drift_x = p_x[i] + sin(anim_time * p_s[i] * 0.5) * 5;
    gfx_a = p_a[i] * (0.1 + meter_rms * 1.5);
    gfx_circle(drift_x, p_y[i], p_s[i], 1);
    i += 1;
);

// Shadow Glow
gfx_r=0.9; gfx_g=0.2; gfx_b=0.0; gfx_a=0.02 + (meter_rms * 0.15);
i = 0; loop(10, gfx_circle(cx, cy, radius + 5 + (i * 3) + sin(anim_time)*5, 1); i += 1; );

// Rings
gfx_r=0.12; gfx_g=0.08; gfx_b=0.06; gfx_a=1;
gfx_circle(cx, cy, radius, 1);
meter_radius = radius - 18;
gfx_r=0.15; gfx_g=0.1; gfx_b=0.08; gfx_a=1;
gfx_circle(cx, cy, meter_radius, 1); 

// Meter
meter_val = min(meter_rms * 1.5, 1.0);
gfx_r=1.0; gfx_g=0.3; gfx_b=0.0; gfx_a=0.85; 
meter_angle = $pi + (meter_val * $pi); 
breath = sin(anim_time * 3) * 2 * meter_rms;

// Vorberechnete Radius-Offsets (spart Math im Loop)
rad_out = meter_radius + breath;
rad_in = meter_radius - 8 - breath;

i = $pi;
while(i <= meter_angle) (
    si = sin(i); ci = cos(i); // Nur 1x sin/cos pro Durchlauf
    x1 = cx + si * rad_out;
    y1 = cy - ci * rad_out;
    x2 = cx + si * rad_in;
    y2 = cy - ci * rad_in;
    gfx_line(x1, y1, x2, y2);
    
    x1_sym = cx - si * rad_out;
    x2_sym = cx - si * rad_in;
    gfx_line(x1_sym, y1, x2_sym, y2);
    
    // Schrittweite von 0.02 auf 0.05 erhöht. 
    // Optisch kein Unterschied, aber spart ~60% der Trigonometrie-Last im GUI.
    i += 0.05; 
);

// Knob Core
knob_radius = radius - 28;
gfx_r=0.18; gfx_g=0.12; gfx_b=0.1; gfx_a=1;
gfx_circle(cx, cy, knob_radius, 1);
gfx_r=0.09; gfx_g=0.06; gfx_b=0.05; gfx_a=1;
gfx_circle(cx, cy, knob_radius - 4, 1); 

// Value Arc (Glow)
gfx_r=1.0; gfx_g=0.5; gfx_b=0.0; gfx_a=0.9;
angle_min = 0.75 * $pi;
angle_max = 2.25 * $pi;
val_angle = angle_min + (slider1 / 100) * (angle_max - angle_min);

rad_in_v = radius + 2;
rad_out_v = radius + 10;

i = angle_min;
while(i <= val_angle) (
    si = sin(i); ci = cos(i);
    x1 = cx + si * rad_in_v; y1 = cy - ci * rad_in_v;
    x2 = cx + si * rad_out_v; y2 = cy - ci * rad_out_v;
    gfx_line(x1, y1, x2, y2); 
    i += 0.05;
);

px1 = cx + sin(val_angle) * (knob_radius - 20);
py1 = cy - cos(val_angle) * (knob_radius - 20);
px2 = cx + sin(val_angle) * knob_radius;
py2 = cy - cos(val_angle) * knob_radius;
gfx_r=1; gfx_g=1; gfx_b=1; gfx_a=1;
gfx_line(px1, py1, px2, py2);

// Title Label
gfx_r=1; gfx_g=0.9; gfx_b=0.8; gfx_a=0.95;
gfx_setfont(1, "Arial", 32, 'b');
str = "EARTH";
gfx_measurestr(str, str_w, str_h);
gfx_x = cx - str_w / 2;
gfx_y = cy - radius - 80; 
gfx_drawstr(str);

gfx_setfont(2, "Arial", 22, 'b');
sprintf(val_str, "%.1f", slider1);
gfx_measurestr(val_str, vw, vh);
gfx_x = cx - vw / 2;
gfx_y = cy - vh / 2;
gfx_drawstr(val_str);

// Sub-Label "GRAVITY"
gfx_setfont(3, "Arial", 11, 'b');
gfx_r=1.0; gfx_g=0.3; gfx_b=0.0; gfx_a=0.8;
g_str = "GRAVITY";
gfx_measurestr(g_str, gw, gh);
gfx_x = cx - gw / 2;
gfx_y = cy + vh / 2 + 10;
gfx_drawstr(g_str);

// Freq Slider View
slider_y = cy + radius + 60;
slider_w = 200;
slider_x = cx - slider_w / 2;
gfx_r=0.25; gfx_g=0.15; gfx_b=0.1; gfx_a=1;
gfx_line(slider_x, slider_y, slider_x + slider_w, slider_y);
freq_norm = (slider2 - 30) / (120 - 30);
knob_x = slider_x + freq_norm * slider_w;
gfx_r=1.0; gfx_g=0.4; gfx_b=0.0; gfx_a=1;
gfx_circle(knob_x, slider_y + 0.5, 6, 1);
gfx_a=0.3; gfx_circle(knob_x, slider_y + 0.5, 12 + sin(anim_time*5)*2, 1);

gfx_setfont(3, "Arial", 12, 'b');
gfx_r=0.7; gfx_g=0.6; gfx_b=0.55; gfx_a=1;
m_str = "FOCUS FREQ";
gfx_measurestr(m_str, mw, mh);
gfx_x = slider_x - mw - 15; gfx_y = slider_y - mh / 2 + 1;
gfx_drawstr(m_str);
sprintf(f_str, "%.0f Hz", slider2);
gfx_measurestr(f_str, fw, fh);
gfx_x = slider_x + slider_w + 15; gfx_y = slider_y - fh / 2 + 1;
gfx_r=1.0; gfx_g=0.4; gfx_b=0.0; gfx_a=1; 
gfx_drawstr(f_str);