/* Example custom engine "supersaw-ish": 2 detuned saws + sub sine, drive.
 *   knob 0 (blue)  : detune amount
 *   knob 1 (green) : saw <-> square morph (pulse width via 2nd saw phase offset)
 *   knob 2 (white) : sub-oscillator (sine, -1 oct) level
 *   knob 3 (orange): drive / soft-clip amount
 */
#include "op1_plugin.h"

typedef struct {
    uint32_t vtable;     /* +0x00 owned by firmware — DO NOT TOUCH */
    uint32_t ph1, ph2;   /* +0x04, +0x08 */
    uint32_t phs;        /* +0x0c sub osc */
    int32_t  lp;         /* +0x10 one-pole state for gentle anti-alias */
    uint8_t  gate;       /* +0x14 key held (screen only) */
} voice_t;               /* must stay <= 0x24 bytes */

static void ss_note_on(void *self, const int16_t *params)
{
    voice_t *v = (voice_t *)self;
    (void)params;
    v->ph1 = 0; v->ph2 = 0x40000000u; v->phs = 0; v->lp = 0; v->gate = 1;
}

static void ss_note_off(void *self) { ((voice_t *)self)->gate = 0; }


static void ss_render(void *self, int16_t *out, const int16_t *params)
{
    voice_t *v = (voice_t *)self;
    const int32_t *inc = OP1_PITCH_INC;
    const int16_t *sine = OP1_SINE_TABLE;
    int32_t detune = params[0] >> 7;          /* 0..255 -> up to ~1.6% detune */
    int32_t morph  = params[1];               /* Q15 */
    int32_t sublvl = params[2];               /* Q15 */
    int32_t drive  = 32768 + (params[3] << 2);/* 1x..5x  (Q15) */
    uint32_t p1 = v->ph1, p2 = v->ph2, ps = v->phs;
    int32_t lp = v->lp;
    int32_t vg = op1_voice_vel_gain(self);    /* note velocity */

    for (int n = 0; n < OP1_BLOCK; n++) {
        uint32_t i = (uint32_t)inc[n];
        uint32_t d = (i >> 14) * (uint32_t)detune;    /* i*detune/16384 */
        p1 += i + d;
        p2 += i - d;
        ps += i >> 1;
        int32_t s1 = (int32_t)p1 >> 16;                     /* saw, +-32767 */
        int32_t s2 = (int32_t)p2 >> 16;
        /* morph: saw2 -> inverted saw2 gives saw->pulse (s1 - s2) */
        int32_t s2m = s2 - op1_mul16(2 * s2, morph);
        int32_t x = (s1 + s2m) >> 1;
        x += op1_mul16(sine[((int32_t)ps >> 19) + 4096], sublvl) >> 1;
        x = (x * (drive >> 4)) >> 11;                        /* drive up to 5x: keep the product in 32 bits */
        /* soft clip: x - x^3/3 style on +-32767 */
        if (x > 32767) x = 32767; else if (x < -32767) x = -32767;
        { int32_t x2 = (x * x) >> 15; int32_t x3 = (x2 * x) >> 15; x -= (x3 * 10923) >> 15; }
        lp += (x - lp) >> 1;
        out[n] = op1_sat16(((lp - (lp >> 2)) * vg) >> 15);   /* 0.75x: six voices stay near TE's engines on the synth bus */
    }
    v->ph1 = p1; v->ph2 = p2; v->phs = ps; v->lp = lp;
}

const op1_engine_t ENGINE_supersaw = { ss_note_on, ss_render, ss_note_off };

/* ======================= screen (GUI thread) =======================
 * display/supersaw.svg (generated by display/gen_supersaw_svg.py, geometry in supersaw_geom.h): minimal
 * chainsaw + 4 dials. Drawn here: the chain - top run = oscillator waveform as teeth (saw -> square with
 * green morph) plus a dim blue copy drifting against it with blue detune (beating), bottom run = sub-octave
 * teeth (2x period, height = white sub level). Parts light up 28..100 % with their knob: fuel cap blue
 * (detune), top handle green (morph), smoke puffs from the exhaust white (sub: count + size), trigger red
 * (drive); the saw (group "saw") shakes with drive. The chain runs while keys are held, coasts after. */
#include "supersaw_geom.h"

#define TOOTH_P   12                         /* px per tooth (top run) */
#define TOOTH_H   5                          /* px tooth height */
static void *bars[4], *saw, *sprk;
static int32_t run;                          /* 0..1024 chain speed factor */
static uint32_t ph, ph2, rot, rng = 1;       /* chain phases (Q10 px), sprocket angle */
static uint32_t smoke;                       /* puff phase, 65536 = one puff cycle */
static const uint8_t COL_CHAIN[4] = { 0xDF, 0xD9, 0xFF, 0xFF };
static const uint8_t COL_DET[4]   = { 0x2E, 0x5F, 0x99, 0xFF };   /* blue at 60 % */
static const uint8_t COL_SUB[4]   = { 0xFF, 0xFF, 0xFF, 0xFF };
static const uint8_t COL_TRIG[4]  = { 0xFF, 0x3A, 0x5D, 0xFF };
static const uint8_t COL_CAP[4]   = { 0x4D, 0x9E, 0xFF, 0xFF };   /* blue  = detune */
static const uint8_t COL_HANDLE[4]= { 0x00, 0xED, 0x95, 0xFF };   /* green = morph  */

static inline int32_t tsin(int32_t a) { return OP1_SINE_TABLE[a & 8191]; }   /* table[k] = sin(2 pi k/8192) */
static inline int32_t tcos(int32_t a) { return tsin(a + 2048); }

static void ss_screen_init(void *scr)
{
    static const char *ids[4] = { "bar0", "bar1", "bar2", "bar3" };
    for (int i = 0; i < 4; i++) bars[i] = op1_find_node(scr, ids[i]);
    saw = op1_find_node(scr, "saw");
    sprk = op1_find_node(scr, "sprk");
}

static void seg(void *r, int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t lo, int32_t hi, const uint8_t *c)
{                                            /* line clipped to lo <= x <= hi (Q10), x0 <= x1 */
    if (x1 < lo || x0 > hi) return;
    if (x0 < lo) { if (x1 != x0) y0 += (int32_t)((int64_t)(y1 - y0) * (lo - x0) / (x1 - x0)); x0 = lo; }
    if (x1 > hi) { if (x1 != x0) y1 -= (int32_t)((int64_t)(y1 - y0) * (x1 - hi) / (x1 - x0)); x1 = hi; }
    op1_pt a = { x0, y0 }, b = { x1, y1 };
    op1_raster_line(r, &a, &b, 0x600, c);
}

/* one chain run along y = base (teeth towards dir = -1 up / +1 down), period P px, height h (Q10),
 * phase (Q10 px), shape = saw (morph 0) .. square (morph 32767), offset ox/oy (Q10) */
static void chain(void *r, int32_t base, int dir, int P, int32_t h, uint32_t phase, int32_t morph,
                  int32_t ox, int32_t oy, const uint8_t *c)
{
    int32_t lo = OP1_Q10(SS_CHAIN_X0) + ox, hi = OP1_Q10(SS_CHAIN_X1) + ox, Pq = OP1_Q10(P);
    /* tooth: ramp 0 -> h over rise, hold to fall, drop, flat to the end. saw: rise = fall = P;
     * square: rise = 0, fall = P/2; morph interpolates both (full height at every setting) */
    int32_t rise = (int32_t)((int64_t)Pq * (32767 - morph) >> 15);
    int32_t fall = Pq - (int32_t)((int64_t)(Pq / 2) * morph >> 15);
    int32_t y0 = OP1_Q10(base) + oy, y1 = y0 + dir * h;
    for (int32_t xs = lo - (int32_t)(phase % (uint32_t)Pq) - Pq; xs < hi; xs += Pq) {
        seg(r, xs, y0, xs + rise, y1, lo, hi, c);                        /* ramp */
        if (fall > rise) seg(r, xs + rise, y1, xs + fall, y1, lo, hi, c); /* hold */
        seg(r, xs + fall, y1, xs + fall, y0, lo, hi, c);                 /* drop */
        if (fall < Pq) seg(r, xs + fall, y0, xs + Pq, y0, lo, hi, c);    /* low */
    }
}

static void glow(uint8_t *o, const uint8_t *c, int32_t knob)        /* 28 % .. 100 % */
{
    int32_t l = 72 + (knob * 184 >> 15);
    for (int i = 0; i < 3; i++) o[i] = (uint8_t)((c[i] * l) >> 8);
    o[3] = 0xFF;
}

static void circle(void *r, int32_t cx, int32_t cy, int32_t rad, const uint8_t *c)   /* Q10 */
{
    op1_pt p = { cx + rad, cy };
    for (int a = 512; a <= 8192; a += 512) {                         /* 16 segments */
        op1_pt q = { cx + (rad * tcos(a) >> 15), cy + (rad * tsin(a) >> 15) };
        op1_raster_line(r, &p, &q, 0x600, c);
        p = q;
    }
}

static void ss_screen_update(void *scr, int dt)
{
    int slot = OP1_SCREEN_SLOT & 7;
    int16_t k[4];
    op1_synth_knobs_live(slot, k);

    int held = 0;
    for (int i = 0; i < OP1_VOICES; i++) if (((const voice_t *)OP1_VOICE(i))->gate == 1) held = 1;
    run = held ? run + ((1024 - run) >> 2) : run - (run >> 4);          /* spin up fast, coast down */
    if (!held && run < 8) run = 0;

    uint32_t v = (uint32_t)(5 * run);                                    /* Q10 px per frame */
    ph += v;
    ph2 += v + (uint32_t)((int64_t)v * k[0] >> 17);                      /* detuned: up to +25 % */
    rot += v * 8192u / (2 * 314 * 4 / 100) >> 10;                       /* sprocket r = 4 px */

    /* shake with drive while running */
    int32_t ox = 0, oy = 0;
    int32_t amp = (int32_t)((int64_t)k[3] * run >> 15) * 3 >> 1;        /* Q10 px, up to 1.5 px */
    if (amp > 0) {
        rng = rng * 1664525u + 1013904223u; ox = (int32_t)((rng >> 16) % (uint32_t)(2 * amp + 1)) - amp;
        rng = rng * 1664525u + 1013904223u; oy = (int32_t)((rng >> 16) % (uint32_t)(2 * amp + 1)) - amp;
    }
    if (saw) { int32_t m[9] = { 1024, 0, 0, 0, 1024, 0, ox, oy, 1024 }; op1_node_set_transform(saw, m); }
    if (sprk) {
        int32_t a = -(int32_t)rot, cx = OP1_Q10(SS_SPRK_X), cy = OP1_Q10(SS_SPRK_Y);
        int32_t c = tcos(a) >> 5, s_ = tsin(a) >> 5;
        int32_t m[9] = { c, s_, 0, -s_, c, 0, cx - ((cx * c - cy * s_) >> 10) + ox,
                         cy - ((cx * s_ + cy * c) >> 10) + oy, 1024 };
        op1_node_set_transform(sprk, m);
    }
    for (int i = 0; i < 4; i++) if (bars[i]) {                         /* dials: -135..+135 degrees */
        int32_t a = ((int32_t)k[i] * 6144 >> 15) - 3072;
        op1_node_rotate(bars[i], OP1_Q10(ss_dial_x[i]), OP1_Q10(SS_DIAL_Y), tsin(a), tcos(a));
    }
    op1_screen_draw(scr, dt);

    void *r = op1_raster();
    int32_t H = OP1_Q10(TOOTH_H);
    /* top run (moves right): detuned copy first, then the main chain */
    if (k[0] > 256) chain(r, SS_BAR_TOP, -1, TOOTH_P, H, ph2, k[1], ox, oy, COL_DET);
    chain(r, SS_BAR_TOP, -1, TOOTH_P, H, ph, k[1], ox, oy, COL_CHAIN);
    /* bottom run (moves left): sub octave, height = sub level */
    int32_t hs = (int32_t)((int64_t)H * k[2] >> 15);
    if (hs > 256) chain(r, SS_BAR_BOT, 1, 2 * TOOTH_P, hs, (uint32_t)(-(int32_t)(ph >> 1)), k[1], ox, oy, COL_SUB);
    uint8_t c[4];
    /* top handle: green with morph */
    glow(c, COL_HANDLE, k[1]);
    for (int i = 1; i < SS_HANDLE_N; i++) {
        op1_pt a = { ss_handle[i - 1][0] + ox, ss_handle[i - 1][1] + oy }, b = { ss_handle[i][0] + ox, ss_handle[i][1] + oy };
        op1_raster_line(r, &a, &b, 0x600, c);
    }
    /* fuel cap: blue with detune */
    glow(c, COL_CAP, k[0]);
    circle(r, OP1_Q10(SS_CAP_X) + ox, OP1_Q10(SS_CAP_Y) + oy, OP1_Q10(SS_CAP_R), c);
    /* trigger: red with drive */
    glow(c, COL_TRIG, k[3]);
    op1_pt a = { OP1_Q10(SS_TRIG_X0) + ox, OP1_Q10(SS_TRIG_Y0) + oy }, b = { OP1_Q10(SS_TRIG_X1) + ox, OP1_Q10(SS_TRIG_Y1) + oy };
    op1_raster_line(r, &a, &b, 0x800, c);
    /* smoke: up to 3 puffs rising up-left from the exhaust, growing and fading; count + size = sub */
    smoke += 300 + (uint32_t)(run * 3);                                  /* drifts, faster when running */
    int npuff = 1 + ((int32_t)k[2] * 3 >> 15); if (npuff > 3) npuff = 3;
    if (k[2] > 512)
        for (int i = 0; i < npuff; i++) {
            int32_t p = (int32_t)((smoke + (uint32_t)i * 65536u / 3u) & 0xFFFF);   /* 0..65535 along the path */
            int32_t grow = OP1_Q10(1) + (int32_t)((int64_t)OP1_Q10(5) * k[2] >> 15);
            int32_t rad = OP1_Q10(1.5) + (int32_t)((int64_t)p * grow >> 16);
            int32_t cx = OP1_Q10(SS_EXH_X) - (int32_t)((int64_t)p * OP1_Q10(22) >> 16) + ox;
            int32_t cy = OP1_Q10(SS_EXH_Y) - (int32_t)((int64_t)p * OP1_Q10(34) >> 16) + oy;
            int32_t l = (int32_t)((int64_t)(65536 - p) * (72 + (k[2] * 184 >> 15)) >> 16);   /* fade out */
            for (int j = 0; j < 3; j++) c[j] = (uint8_t)((COL_SUB[j] * l) >> 8);
            circle(r, cx, cy, rad, c);
        }
}

const op1_screen_t SCREEN_supersaw = { ss_screen_init, ss_screen_update };
