Files
seedbox/firmware/engine/engine.c
Gee 2196633ed5
Some checks failed
check / check (push) Has been cancelled
Seedbox v0.1 + firmware, generic kit/synth, ROOT/SCALE pages
First commit of the repo. Contents beyond the v0.1 simulator and layout:

- Full Pico 2 W firmware (firmware/app, drivers, engine port, host test).
- BANK page and 404 mode A/B removed; learned pads play exactly what was learned.
- 404/MiniFreak renamed to KIT/SYNTH throughout (jacks, code, sim, docs,
  layout labels; wiring rebuilt).
- ROLL returns to page select after a re-roll; a freshly learned track turns on.
- LCD contrast 66.
- ROOT and SCALE pages: 21 scales, AUTO = the seed picks; global and sticky.

Intended music change: the scale table grew from 8 to 21 and AUTO picks from
all of them, so every seed's scale changed. Vectors regenerated with
npm run vectors, plus new key-override cases. Flash record is now version 2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:39:36 +03:00

759 lines
39 KiB
C

/* Seedbox engine: C port of sim/engine.js (the reference). Read the two side by side:
functions keep their JS names (snake_case) and order. Comments explaining the music live in engine.js.
Port rules:
- Hashes and RNG values are uint32_t. Math.imul(a,b)>>>0 == (uint32_t)a*(uint32_t)b.
- fdiv/mod are floor division and positive modulo; use them wherever the left side can be negative.
- One RNG call per statement. C leaves the evaluation order of operands and arguments unspecified,
so f(r.below(), r.below()) would not match JS's left-to-right order.
- Sorts are stable insertion sorts (JS Array.prototype.sort is stable and the engine relies on it).
- Object key sets keyed by step (fs.K, fs.S) are flag arrays walked in ascending order, which is
how JS enumerates integer keys. */
#include "engine.h"
#include <string.h>
#define TPB SB_TPB
#define TPS SB_TPS
#define PHRASE_BARS SB_PHRASE_BARS
#define MAX_STEPS 20
static int g_overflow;
/* ---------- primitives ---------- */
uint32_t sb_mix(uint32_t a, uint32_t b){
uint32_t h = a ^ (b * 0x9E3779B1u);
h ^= h >> 16; h *= 0x85EBCA6Bu; h ^= h >> 13; h *= 0xC2B2AE35u; h ^= h >> 16;
return h ? h : 0x6D2B79F5u;
}
void sb_rng_init(sb_rng *r, uint32_t s){ r->s = s ? s : 0x6D2B79F5u; }
uint32_t sb_rng_next(sb_rng *r){ uint32_t x = r->s; x ^= x << 13; x ^= x >> 17; x ^= x << 5; r->s = x; return x; }
int sb_rng_below(sb_rng *r, int n){
if(n <= 0) return 0;
return (int)((uint32_t)((uint64_t)(sb_rng_next(r) >> 16) * (uint32_t)n) >> 16);
}
int sb_rng_chance(sb_rng *r, int p){ return (int)(sb_rng_next(r) >> 16) < p; }
static int pick_w(sb_rng *r, const int *w, int n){
int t = 0, i, x;
for(i = 0; i < n; i++) t += w[i];
x = sb_rng_below(r, t);
for(i = 0; i < n; i++){ if(x < w[i]) return i; x -= w[i]; }
return n - 1;
}
#define below sb_rng_below
#define chance sb_rng_chance
#define mix sb_mix
static int clamp(int x, int a, int b){ return x < a ? a : x > b ? b : x; }
static int imin(int a, int b){ return a < b ? a : b; }
static int imax(int a, int b){ return a > b ? a : b; }
static int P(int x){ return clamp(x, 0, 127) * 516; }
static int fdiv(int a, int b){ int q = a / b; if((a % b != 0) && ((a < 0) != (b < 0))) q--; return q; }
static int mod(int a, int b){ return ((a % b) + b) % b; }
static sb_rng rng(uint32_t s){ sb_rng r; sb_rng_init(&r, s); return r; }
/* ---------- tables ---------- */
static const int STEPS[4] = {16,12,14,20}, STEPS_W[4] = {12,3,2,2};
static const int FORMS[6][4] = {{0,0,0,1},{0,0,1,0},{0,1,0,1},{0,0,0,0},{0,1,0,2},{0,1,0,0}}, FORMS_W[6] = {5,5,3,2,3,2};
static const char *const FORM_NAMES[6] = {"AAAB","AABA","ABAB","AAAA","ABAC","ABAA"};
static const int SCALES[SB_NSCALES][8] = {
{0,2,4,5,7,9,11},{0,2,3,5,7,8,10},{0,2,3,5,7,9,10},{0,2,4,5,7,9,10},{0,2,4,6,7,9,11},{0,1,3,5,7,8,10},{0,2,3,5,7,8,11},
{0,3,5,7,10},{0,2,4,7,9},
{0,1,4,5,7,8,10},{0,1,4,5,7,8,11},{0,2,3,6,7,8,11},{0,2,3,6,7,9,10},{0,2,4,6,7,9,10},
{0,2,3,7,8},{0,1,5,7,10},{0,1,5,6,10},{0,2,4,6,9,10},{0,2,4,6,8,10},{0,1,3,4,6,7,9,10},{0,1,3,5,6,8,10}};
static const int SCALE_LEN[SB_NSCALES] = {7,7,7,7,7,7,7, 5,5, 7,7,7,7,7, 5,5,5,6,6,8,7};
static const char *const SCALE_NAMES[SB_NSCALES] = {"MAJOR","MINOR","DORIAN","MIXOLYDIAN","LYDIAN","PHRYGIAN","HARM MINOR","MINOR PENT","MAJOR PENT",
"PHRYG DOM","DBL HARM","HUNG MINOR","UKR DORIAN","LYD DOM","HIRAJOSHI","IN SEN","IWATO","PROMETHEUS","WHOLE TONE","OCTATONIC","LOCRIAN"};
const char *sb_scale_name(int scale){ return scale >= 0 && scale < SB_NSCALES ? SCALE_NAMES[scale] : "?"; }
void sb_apply_key(sb_genome *g, const sb_params *p){
if(p->scale >= 0 && p->scale < SB_NSCALES) g->scale = p->scale;
if(p->root >= 0 && p->root < 12) g->root = p->root;
}
static const int HOCKETS[8] = {0xAA,0x55,0xB4,0x6C,0x96,0xD2,0x5A,0xA5};
static const int PROGS[10][4] = {{0,0,5,4},{0,5,3,4},{0,3,0,4},{0,6,5,4},{0,2,5,4},{0,0,3,3},{0,5,0,5},{0,3,5,6},{0,1,0,6},{0,4,5,3}};
static const int SWINGS[5] = {0,21,35,50,70}, SWING_W[5] = {4,3,3,2,1};
static const int SPLITS[6] = {1,2,3,4,5,7};
typedef struct { int k[4], nk, s[2], ns, g[3], ng; } skel_t;
static const skel_t SKEL[6] = {
{{0,10},2, {4,12},2, {7,15},2}, /* two-step */
{{0,2,10,11},4, {4,12},2, {7,9,15},3}, /* amen-ish */
{{0,11},2, {8},1, {14,3},2}, /* half-time */
{{0,3,10},3, {4,12},2, {7},1}, /* think-ish */
{{0,6,11},3, {4,13},2, {9,15},2}, /* broken */
{{0,7,10},3, {4,12},2, {2,14},2} /* steppy */
};
typedef struct { int k[4], nk, s[2], ns, layer, noOne, cyc, free_; } frame_t;
static const frame_t FRAMES[8] = {
{{0},1, {4,12},2, 0,0,0,0}, /* backbeat */
{{0},1, {8},1, 0,0,0,0}, /* half-time */
{{0,4,8,12},4, {4,12},2, 1,0,0,0}, /* four-floor */
{{0,3,8,11},4, {6,14},2, 0,0,0,0}, /* 3-3-2 */
{{0,10},2, {6,12},2, 0,0,0,0}, /* broken */
{{3,10},2, {8},1, 0,1,0,0}, /* displaced */
{{0},0, {0},0, 0,0,12,0}, /* over the bar */
{{0},1, {0},0, 0,0,0,1} /* free snare */
};
static const int FRAME_W[8] = {3,3,2,2,2,1,1,2};
static const int RULES[14] = {30,45,54,57,60,73,90,105,110,122,126,150,182,225};
static const int POLY_N[6] = {5,7,9,11,12,6};
static const int NUDGE_GRP[SB_NT] = {0,0,1,3,2,2,3,0,4,4,5};
const char *sb_form_name(int form_idx){ return FORM_NAMES[form_idx]; }
/* ---------- event lists ---------- */
typedef struct { int16_t t, v, vel, a, tr; } ev_t;
typedef struct { int n; ev_t e[SB_MAX_EVENTS]; } evlist;
static void push_tr(evlist *l, int t, int v, int vel, int a, int tr){
ev_t *e;
if(l->n >= SB_MAX_EVENTS){ g_overflow = 1; return; }
e = &l->e[l->n++];
e->t = (int16_t)t; e->v = (int16_t)v; e->vel = (int16_t)vel; e->a = (int16_t)a; e->tr = (int16_t)tr;
}
static void push(evlist *l, int t, int v, int vel, int a){ push_tr(l, t, v, vel, a, 0); }
static void push_ev(evlist *l, const ev_t *e){ push_tr(l, e->t, e->v, e->vel, e->a, e->tr); }
/* keep the events where cond holds; inside cond, e points at the current event */
#define FILTER(l, cond) do{ int o_ = 0, i_; for(i_ = 0; i_ < (l)->n; i_++){ const ev_t *e = &(l)->e[i_]; if(cond) (l)->e[o_++] = *e; } (l)->n = o_; }while(0)
static void sort_tv(evlist *l){ /* stable, by (t, v) */
int i, j;
for(i = 1; i < l->n; i++){
ev_t x = l->e[i];
for(j = i - 1; j >= 0 && (l->e[j].t > x.t || (l->e[j].t == x.t && l->e[j].v > x.v)); j--) l->e[j + 1] = l->e[j];
l->e[j + 1] = x;
}
}
/* ---------- genome ---------- */
void sb_genome_make(uint32_t seed, sb_genome *g){
static const int ROT_W[4] = {4,2,2,1}, BASS_DEG[3] = {4,2,3}, ARC_W[3] = {3,2,2};
static const int ACC[5] = {0,3,5,3,7}, ACC_W[5] = {3,3,2,2,1};
sb_rng r = rng(mix(seed, 0x47454E));
int fi;
g->steps = STEPS[pick_w(&r, STEPS_W, 4)];
fi = pick_w(&r, FORMS_W, 6); g->form_idx = fi; memcpy(g->form, FORMS[fi], sizeof g->form);
g->scale = below(&r, SB_NSCALES); g->root = below(&r, 12);
g->dens[0] = 40 + below(&r, 60); g->dens[1] = 30 + below(&r, 70);
g->fillP = 40 + below(&r, 80); g->monoLen = below(&r, 3); g->monoRange = 1 + below(&r, 2);
g->rot = pick_w(&r, ROT_W, 4);
g->hocket = HOCKETS[below(&r, 8)];
g->bassDeg = BASS_DEG[below(&r, 3)];
g->prog_idx = below(&r, 10); memcpy(g->prog, PROGS[g->prog_idx], sizeof g->prog);
g->swing = SWINGS[pick_w(&r, SWING_W, 5)];
g->arc = pick_w(&r, ARC_W, 3);
g->motifLen = 3 + below(&r, 3);
g->accentCycle = ACC[pick_w(&r, ACC_W, 5)];
g->layback = below(&r, 2);
g->frame = pick_w(&r, FRAME_W, 8); g->freeK = 2 + below(&r, 3); g->freeRot = below(&r, 16);
}
static uint32_t role_seed(const sb_state *st, int r){ return mix(mix(st->seed, 0x524F4C45u + (uint32_t)r), st->salts[r]); }
static int eu(int i, int k, int n, int rot){ int j = (i + rot) % n; return ((j * k) % n) < k; }
/* groups(steps): [s, min(4, steps-s)] for s = 0, 4, 8, ... */
static int n_groups(int steps){ return (steps + 3) / 4; }
static int group_len(int steps, int gi){ return imin(4, steps - gi * 4); }
typedef struct { uint8_t K[MAX_STEPS], S[MAX_STEPS]; int noOne; } fsets;
static int sc16(int x, int steps){ return x * steps / 16; }
static void frame_sets(const sb_genome *g, int steps, int pos, fsets *fs){
const frame_t *F = &FRAMES[g->frame];
int i;
memset(fs, 0, sizeof *fs);
if(F->cyc){ for(i = 0; i < steps; i++){ int x = pos * steps + i; if(x % F->cyc == 0) fs->K[i] = 1; else if(x % F->cyc == (F->cyc >> 1)) fs->S[i] = 1; } }
else {
for(i = 0; i < F->nk; i++) fs->K[sc16(F->k[i], steps)] = 1;
if(F->free_){ for(i = 1; i < steps; i++) if(eu(i, g->freeK, steps, g->freeRot) && !fs->K[i]) fs->S[i] = 1; }
else for(i = 0; i < F->ns; i++) fs->S[sc16(F->s[i], steps)] = 1;
}
if(!F->layer) for(i = 0; i < steps; i++) if(fs->S[i]) fs->K[i] = 0;
fs->noOne = F->noOne;
}
/* divide(): velFn is chosen by mode */
enum { VEL_SUB_KICK, VEL_SUB_GHOST, VEL_SUB_BURST, VEL_SUB_HAT, VEL_SUB_PERC };
static int divide_vel(sb_rng *r, int mode, int depth){
switch(mode){
case VEL_SUB_KICK: return 96 + below(r, 16);
case VEL_SUB_GHOST: return 34 + below(r, 22);
case VEL_SUB_BURST: return 50 + below(r, 30);
case VEL_SUB_HAT: return depth == 0 ? 90 : 58 + below(r, 20);
default: return 60 + below(r, 45);
}
}
static void divide(sb_rng *r, int start, int len, int depth, int maxD, const int *W, int prob, evlist *out, int v, int mode){
if(depth < maxD){
int w[6], f, i;
memcpy(w, W, sizeof w); if(depth > 0) w[0] += depth * 3;
f = SPLITS[pick_w(r, w, 6)];
if(f > 1){ for(i = 0; i < f; i++){ int a = start + i * len / f, b = start + (i + 1) * len / f; divide(r, a, b - a, depth + 1, maxD, W, prob, out, v, mode); } return; }
}
if(chance(r, prob)){ int vel = divide_vel(r, mode, depth); push(out, start, v, vel, 0); }
}
/* ---------- kit generators ---------- */
typedef struct { int k, n, rot; } euc_t;
typedef struct { euc_t kick, ghost, hat, perc; int accent; } euclid_params_t;
static void euclid_params(uint32_t coreSeed, int steps, int d, int hd, euclid_params_t *E){
static const int K_OPTS[5] = {3,5,4,2,7}, HN_W[5] = {3,3,1,1,1};
int w[5] = {4,3,2,1,1 + (d >> 5)};
sb_rng c = rng(coreSeed);
int k = K_OPTS[pick_w(&c, w, 5)], rots[MAX_STEPS] = {0}, nr = 0, r0, hn, pn;
k = clamp(k, 1, steps >> 1);
for(r0 = 0; r0 < steps; r0++) if(((r0 * k) % steps) < k) rots[nr++] = r0;
E->kick.k = k; E->kick.n = steps; E->kick.rot = rots[below(&c, nr)];
E->ghost.k = 1 + below(&c, 3); E->ghost.n = steps; E->ghost.rot = below(&c, steps);
hn = pick_w(&c, HN_W, 5) < 2 ? steps : POLY_N[below(&c, 3)];
E->hat.k = clamp((hn * (32 + (hd >> 2))) >> 7, 1, hn); E->hat.n = hn; E->hat.rot = below(&c, hn);
pn = POLY_N[below(&c, 6)];
E->perc.k = 1 + ((pn * d) >> 9); E->perc.k += below(&c, 2); E->perc.k = clamp(E->perc.k, 1, pn - 1);
E->perc.n = pn; E->perc.rot = below(&c, pn);
E->accent = below(&c, E->hat.n);
}
static void kit_euclid(uint32_t coreSeed, uint32_t varSeed, int steps, int d, int hd, int pos, const fsets *fs, evlist *ev){
euclid_params_t E;
sb_rng vr = rng(varSeed), hr = rng(mix(varSeed, 0x484154u + (uint32_t)pos));
int i;
euclid_params(coreSeed, steps, d, hd, &E);
for(i = 0; i < steps; i++){
int t = i * TPS, x = pos * steps + i;
if(fs->S[i]) push(ev, t, 1, 112, 0);
else if(eu(i, E.ghost.k, E.ghost.n, E.ghost.rot) && chance(&vr, P(d >> 1))){ int vel = 38 + below(&vr, 20); push(ev, t, 1, vel, 0); }
if(fs->K[i]) push(ev, t, 0, i == 0 ? 120 : 112, 1);
else if(eu(i, E.kick.k, E.kick.n, E.kick.rot) && !fs->S[i] && !(i == 0 && fs->noOne)) push(ev, t, 0, 104, 0);
if(eu(x, E.hat.k, E.hat.n, E.hat.rot)){
int acc = eu(x, imax(1, E.hat.k >> 1), E.hat.n, E.accent);
push(ev, t, 2, acc ? 92 : 66, 0);
if(chance(&hr, P(hd >> 3))) push(ev, t + (TPS >> 1), 2, 44, 0);
}
if(eu(x, E.perc.k, E.perc.n, E.perc.rot)){ int vel = 70 + below(&vr, 40); push(ev, t, 3, vel, 0); }
}
}
static void kit_subdiv(uint32_t coreSeed, uint32_t varSeed, int steps, int d, int hd, int pos, const fsets *fs, evlist *ev){
static const int W_KICK[6] = {6,5,1,0,0,0}, W_GHOST[6] = {4,4,1,0,0,0}, W_BURST[6] = {1,3,4,2,3,2}, W_HAT[6] = {3,6,0,2,0,0}, W_PERC[6] = {6,2,3,0,1,1};
static evlist tmp;
sb_rng core = rng(coreSeed), vr = rng(varSeed), hr = rng(mix(varSeed, 0x484154u + (uint32_t)pos));
int ng = n_groups(steps), snT[MAX_STEPS], nsn = 0, gi, j, k, burst = -1;
if(chance(&core, P(d >> 1))) burst = below(&core, ng);
for(j = 0; j < steps; j++){
if(fs->S[j]){ int vel = 110 + below(&core, 14); push(ev, j * TPS, 1, vel, 0); snT[nsn++] = j * TPS; }
if(fs->K[j]) push(ev, j * TPS, 0, j == 0 ? 118 : 112, 1);
}
for(gi = 0; gi < ng; gi++){
int s = gi * 4 * TPS, L = group_len(steps, gi) * TPS;
tmp.n = 0;
divide(&core, s, L, 0, 1, W_KICK, P(10 + (d >> 2)), &tmp, 0, VEL_SUB_KICK);
for(j = 0; j < tmp.n; j++){
int inSn = 0;
for(k = 0; k < nsn; k++) if(snT[k] == tmp.e[j].t) inSn = 1;
if(!(tmp.e[j].t == 0 && fs->noOne) && !inSn) push_ev(ev, &tmp.e[j]);
}
divide(&vr, s, L, 0, 1, W_GHOST, P(d >> 3), ev, 1, VEL_SUB_GHOST);
if(gi == burst) divide(&hr, s, L, 0, 2, W_BURST, P(90), ev, 2, VEL_SUB_BURST);
else divide(&hr, s, L, 0, 1, W_HAT, P(36 + (hd >> 2)), ev, 2, VEL_SUB_HAT);
divide(&vr, s, L, 0, 1, W_PERC, P(d >> 2), ev, 3, VEL_SUB_PERC);
}
}
static int contains(const int *a, int n, int x){ int i; for(i = 0; i < n; i++) if(a[i] == x) return 1; return 0; }
static void kit_skeleton(uint32_t coreSeed, uint32_t varSeed, int steps, int d, int hd, int pos, const fsets *fs, int frame, evlist *ev){
static const int DIVS[3] = {2,2,1};
sb_rng core = rng(coreSeed), vr = rng(varSeed), hr = rng(mix(varSeed, 0x484154u + (uint32_t)pos));
const skel_t *sk = &SKEL[below(&core, 6)];
int sn[MAX_STEPS], nsn = 0, i, div, hp, ek, rot;
if(frame == 0) for(i = 0; i < sk->ns; i++) sn[nsn++] = sc16(sk->s[i], steps);
else for(i = 0; i < steps; i++) if(fs->S[i]) sn[nsn++] = i;
for(i = 0; i < steps; i++) if(fs->K[i]) push(ev, i * TPS, 0, 116, 1);
for(i = 0; i < sk->nk; i++){
int s = sc16(sk->k[i], steps);
if(sk->k[i] == 0) continue;
if(chance(&core, P(d >> 2))){ int dir = below(&core, 2) ? 1 : -1; s = clamp(s + dir, 1, steps - 1); }
if(!contains(sn, nsn, s)){ int vel = 104 + below(&core, 12); push(ev, s * TPS, 0, vel, 0); }
}
for(i = 0; i < nsn; i++){ int vel = 110 + below(&core, 14); push(ev, sn[i] * TPS, 1, vel, 0); }
for(i = 0; i < sk->ng; i++) if(chance(&vr, P(d))){ int vel = 36 + below(&vr, 24); push(ev, sc16(sk->g[i], steps) * TPS, 1, vel, 0); }
if(chance(&vr, P(d >> 1))){ int xs = 1 + below(&vr, steps - 1); if(!contains(sn, nsn, xs)){ int vel = 90 + below(&vr, 20); push(ev, xs * TPS, 0, vel, 0); } }
div = DIVS[below(&core, 3)]; hp = P(36 + (hd >> 2));
for(i = 0; i < steps; i += div){
if(i % 4 == 0) push(ev, i * TPS, 2, 92, 0);
else if(chance(&hr, hp)){
int vel = 60 + below(&hr, 22);
if(chance(&hr, P(hd >> 3))){ int L = TPS * div; push(ev, i * TPS, 2, vel, 0); push(ev, i * TPS + (L >> 1), 2, vel - 8, 0); }
else push(ev, i * TPS, 2, vel, 0);
}
}
ek = 1 + ((d * steps) >> 8); ek += below(&core, 3); ek = clamp(ek, 1, steps - 1);
rot = below(&core, steps);
for(i = 0; i < steps; i++) if(eu(i, ek, steps, rot)){ int vel = 64 + below(&vr, 40); push(ev, i * TPS, 3, vel, 0); }
}
static void ca_row(int rule, uint8_t *row, int steps, int gens){ /* in place */
uint8_t nx[MAX_STEPS];
int g, i;
for(g = 0; g < gens; g++){
for(i = 0; i < steps; i++){ int l = row[(i + steps - 1) % steps], c = row[i], r = row[(i + 1) % steps]; nx[i] = (uint8_t)((rule >> ((l << 2) | (c << 1) | r)) & 1); }
memcpy(row, nx, (size_t)steps);
}
}
static void rand_row(sb_rng *r, int steps, int p, uint8_t *a){ int i; for(i = 0; i < steps; i++) a[i] = (uint8_t)(chance(r, p) ? 1 : 0); }
static void kit_ca(uint32_t coreSeed, uint32_t varSeed, int steps, int d, int hd, int pos, int sec, const fsets *fs, evlist *ev){
sb_rng core = rng(coreSeed), vr = rng(varSeed), thin = rng(mix(varSeed, 0x5448u + (uint32_t)pos));
int gen = sec * 5 + pos, i, rk, rs, rh, rp;
uint8_t K[MAX_STEPS], S[MAX_STEPS], H[MAX_STEPS], C[MAX_STEPS];
rk = RULES[below(&core, 14)]; rs = RULES[below(&core, 14)]; rh = RULES[below(&vr, 14)]; rp = RULES[below(&vr, 14)];
rand_row(&core, steps, P(40), K); ca_row(rk, K, steps, gen);
rand_row(&core, steps, P(40), S); ca_row(rs, S, steps, gen);
rand_row(&vr, steps, P(64), H); ca_row(rh, H, steps, gen + pos);
rand_row(&vr, steps, P(40), C); ca_row(rp, C, steps, gen);
for(i = 0; i < steps; i++){
int t = i * TPS, back = fs->S[i];
if(back) push(ev, t, 1, 112, 0);
else if(S[i] && chance(&thin, P(d >> 1))){ int vel = 40 + below(&thin, 20); push(ev, t, 1, vel, 0); }
if(fs->K[i]) push(ev, t, 0, 118, 1);
else if(K[i] && !back && !(i == 0 && fs->noOne) && chance(&thin, P(40 + (d >> 1)))){ int vel = 96 + below(&thin, 20); push(ev, t, 0, vel, 0); }
if(H[i] && chance(&thin, P(56 + (hd >> 2)))){ int nb = H[(i + steps - 1) % steps] + H[(i + 1) % steps]; push(ev, t, 2, 58 + nb * 16, 0); }
if(C[i] && !C[(i + 1) % steps] && chance(&thin, P(d))){ int vel = 70 + below(&thin, 40); push(ev, t, 3, vel, 0); }
}
}
/* ---------- AMENIZER ---------- */
/* [step, voice, velocity, main flag]; the listed hits, then the ride from ride() in engine.js */
typedef struct { uint8_t s, v, vel, a; } amen_hit;
#define RIDE8 {0,2,82,1},{2,2,60,0},{4,2,82,1},{6,2,60,0},{8,2,82,1},{10,2,60,0},{12,2,82,1},{14,2,60,0}
static const amen_hit AMEN_A[] = {{0,0,112,1},{2,0,84,0},{10,0,104,1},{11,0,80,0},{4,1,112,1},{7,1,52,0},{9,1,86,1},{12,1,110,1},{15,1,58,0}, RIDE8};
static const amen_hit AMEN_C[] = {{0,0,112,1},{2,0,84,0},{10,0,104,1},{4,1,112,1},{7,1,52,0},{9,1,86,1},{14,1,110,1}, RIDE8};
static const amen_hit AMEN_D[] = {{2,0,104,1},{3,0,84,0},{10,0,108,1},{1,1,48,0},{4,1,112,1},{7,1,54,0},{9,1,88,1},{14,1,112,1},{10,3,110,0},
{0,2,82,1},{2,2,60,0},{4,2,82,1},{6,2,60,0},{8,2,82,1},{12,2,82,1},{14,2,60,0}}; /* crash at 10 replaces the ride */
static const amen_hit *const AMEN[4] = {AMEN_A, AMEN_A, AMEN_C, AMEN_D};
static const int AMEN_N[4] = {sizeof AMEN_A / sizeof AMEN_A[0], sizeof AMEN_A / sizeof AMEN_A[0], sizeof AMEN_C / sizeof AMEN_C[0], sizeof AMEN_D / sizeof AMEN_D[0]};
#define AMEN_SLICE_MAX 16
static void humanize_amen(evlist *ev, uint32_t seed, int bar){
int last[4] = {-1000000,-1000000,-1000000,-1000000}, lastV[4] = {0,0,0,0}, i, rideBias;
sb_rng br = rng(mix(mix(seed, 0x48554D), (uint32_t)bar));
sort_tv(ev);
rideBias = below(&br, 11) - 5;
for(i = 0; i < ev->n; i++){
ev_t *x = &ev->e[i];
int base = x->vel;
uint32_t h = mix(mix(mix(seed, 0x564C), (uint32_t)bar), (uint32_t)(x->t * 4 + x->v));
int v = base + (int)(h % 13) - 6;
if(x->v == 2) v += rideBias;
if(x->v < 2 && x->t - last[x->v] <= 2 * TPS && lastV[x->v] * 10 >= base * 9) v = fdiv(v * 80, 100);
last[x->v] = x->t; lastV[x->v] = base; x->vel = (int16_t)clamp(v, 1, 127);
}
}
static int amen_feel(uint32_t seed, int bar, int t, int v, int a){
uint32_t h = mix(mix(mix(seed, 0x46454C), (uint32_t)bar), (uint32_t)(t * 4 + v));
#define J(n) ((int)(h % (uint32_t)(2 * (n) + 1)) - (n))
if(v == 0) return J(4);
if(v == 1) return a ? 10 + J(5) : J(10);
if(v == 2) return -4 + J(6);
return J(4);
#undef J
}
static int amen_slice(int bar, int beat, amen_hit *out){
const amen_hit *b = AMEN[bar & 3];
int i, n = 0;
for(i = 0; i < AMEN_N[bar & 3]; i++) if(b[i].s >= beat * 4 && b[i].s < beat * 4 + 4) out[n++] = b[i];
return n;
}
static void kit_amen(uint32_t seed, int bar, int pos, int dr, int fill, evlist *ev){
static const int PB[4] = {2,3,5,6};
amen_hit sl[AMEN_SLICE_MAX], prev[AMEN_SLICE_MAX];
int src = fill ? 3 : pos, nprev = -1, j, k; /* nprev < 0: no previous beat yet (JS prev === null) */
for(j = 0; j < 4; j++){
int nsl = amen_slice(src, j, sl), op = -1;
sb_rng r = rng(mix(mix(seed, 0x414D454E), (uint32_t)(bar * 4 + j)));
int pc = fdiv(dr * 80, 127), pb = fdiv(pc * PB[j], 4), keep = (pos == 0 && j == 0) || (j == 0 && dr < 88), base;
if(!keep && chance(&r, P(pb))){ int w[5] = {4,2,2,2,dr > 80 ? 1 : 0}; op = pick_w(&r, w, 5); }
if(op == 0){ int b = below(&r, 4); nsl = amen_slice(b, j, sl); }
else if(op == 4){ int b = below(&r, 4), bt = below(&r, 4); nsl = amen_slice(b, bt, sl); op = 0; }
else if(op == 1 && nprev >= 0){ memcpy(sl, prev, sizeof sl); nsl = nprev; }
base = j * 4 * TPS;
for(k = 0; k < nsl; k++){
const amen_hit *s = &sl[k];
int rel = s->s % 4, t = base + rel * TPS;
if(op == 2){ if(rel < 2){ push(ev, t, s->v, s->vel, s->a); push(ev, t + 2 * TPS, s->v, imax(1, s->vel - 10), s->a); } continue; }
push(ev, t, s->v, s->vel, s->a);
if(op == 3 && s->v == 1) push(ev, t + (TPS >> 1), 1, imax(1, s->vel - 24), 0);
}
memcpy(prev, sl, sizeof prev); nprev = nsl;
}
}
/* ---------- sections, arc, shaping, fill ---------- */
static void transform_kit(evlist *ev, int steps, int sec, uint32_t seed, const fsets *fs){
sb_rng r;
int ops[2] = {0, 0}, nops = 1, i, half = (steps >> 1) * TPS, barT = steps * TPS;
if(sec == 0) return;
r = rng(mix(seed, 0x5345u + (uint32_t)sec));
ops[0] = below(&r, 8);
if(chance(&r, P(64))) ops[nops++] = below(&r, 8);
#define HAS(o) (ops[0] == (o) || (nops > 1 && ops[1] == (o)))
if(HAS(0)){
int lastK = -1;
for(i = 0; i < ev->n; i++) if(ev->e[i].v == 0 && ev->e[i].t > 0) lastK = i;
if(lastK >= 0 && ev->e[lastK].t + TPS < barT) ev->e[lastK].t += TPS;
push(ev, 3 * TPS, 0, 98, 0);
}
if(HAS(1)){
static int hs[SB_MAX_EVENTS]; /* static: keeps the Pico's 2 KB stacks free */
int nh = 0;
for(i = 0; i < ev->n; i++) if(ev->e[i].v == 2 && ev->e[i].t >= half && ev->e[i].t % TPS == 0) hs[nh++] = ev->e[i].t;
for(i = 0; i < nh; i++) if(hs[i] + TPS < barT) push(ev, hs[i] + TPS, 2, 60, 0);
}
if(HAS(2)) FILTER(ev, !(e->t < half && (e->v == 3 || (e->v == 1 && e->vel < 90))));
if(HAS(3)) for(i = 0; i < ev->n; i++) if(ev->e[i].v == 3) ev->e[i].t = (int16_t)((ev->e[i].t + 2 * TPS) % barT);
if(HAS(4)) for(i = 1; i < steps; i++) if(fs->S[i]) push(ev, (i - 1) * TPS, 1, 44, 0);
if(HAS(5)){
int ls = -1;
for(i = 0; i < ev->n; i++) if(ev->e[i].v == 1 && ev->e[i].vel >= 90) ls = i;
if(ls >= 0 && ev->e[ls].t + TPS < barT) ev->e[ls].t += TPS;
}
if(HAS(6)){
int kn = 0;
for(i = 0; i < ev->n; i++) if(ev->e[i].v == 0) kn++;
if(kn > 1) FILTER(ev, !(e->v == 0 && e->t == 0));
}
if(HAS(7)){
FILTER(ev, !(e->v == 1 && e->vel >= 90));
push(ev, (steps >> 1) * TPS, 1, 116, 0);
}
#undef HAS
}
static void arc_kit(evlist *ev, const sb_genome *g, int pos){
if(g->arc == 1 && pos < 2) FILTER(ev, !((e->v == 3 || (e->v == 1 && e->vel < 90)) && (pos == 0 || e->t % (2 * TPS) != 0)));
else if(g->arc == 2 && pos == 2){
int k0 = 1000000000, i;
for(i = 0; i < ev->n; i++) if(ev->e[i].v == 0 && ev->e[i].t < k0) k0 = ev->e[i].t;
FILTER(ev, (e->v == 0 && e->t == k0) || (e->v == 1 && e->vel >= 90) || (e->v == 2 && e->t % (2 * TPS) == 0));
}
}
static void shape_kit(evlist *ev, const sb_genome *g, int pos, int aoff, int jit, sb_rng *jr){
int i;
for(i = 0; i < ev->n; i++){
ev_t *e = &ev->e[i];
int st, sw, w, x, v;
if(e->v < 2 || e->t % TPS != 0) continue;
st = e->t / TPS; sw = st + aoff; w = (sw % 4 == 0) ? 100 : (sw % 2 == 0) ? 86 : 74; x = pos * g->steps + sw;
v = fdiv(e->vel * w, 100);
if(g->accentCycle && (x % g->accentCycle) == 0) v += 24;
if(jit) v += below(jr, 2 * jit + 1) - jit;
e->vel = (int16_t)clamp(v, 1, 127);
}
}
static void kit_fill(evlist *ev, int steps, int e, sb_rng *fr){
static evlist src;
int ng = n_groups(steps), lastGi = ng - 1, st = lastGi * 4 * TPS, L = group_len(steps, lastGi) * TPS, i, type, ps;
type = below(fr, 4);
src = *ev; ev->n = 0;
for(i = 0; i < src.n; i++) if(src.e[i].t < st) push_ev(ev, &src.e[i]);
if(type == 0){
int m = 4 + fdiv(e, 32);
push(ev, st, 0, 110, 0);
for(i = 0; i < m; i++){ int q = m - i; push(ev, st + L - L * q * q / (m * m), 1, 90 + 30 * i / m, 0); }
}
else if(type == 1){
int k = 0, t;
push(ev, st, 0, 110, 0);
for(t = st; t < st + L; t += (TPS >> 1), k++) push(ev, t, (k % 2) ? 3 : 1, (k % 2) ? 70 + k * 4 : 92 + k * 3, 0);
}
else if(type == 2){ push(ev, st, 0, 116, 0); push(ev, st + L - TPS, 1, 100, 0); push(ev, st + L - (TPS >> 1), 1, 116, 0); }
else {
ps = (ng > 1 ? ng - 2 : lastGi) * 4 * TPS;
for(i = 0; i < src.n; i++){
const ev_t *x = &src.e[i];
if(x->t >= ps && x->t < ps + (L >> 1)){ push(ev, st + (x->t - ps), x->v, x->vel, 0); push(ev, st + (L >> 1) + (x->t - ps), x->v, clamp(x->vel + 10, 1, 127), 0); }
}
}
}
/* ---------- synth tracks ---------- */
static int chord_deg(const sb_genome *g, int pos){ return g->prog[pos % PHRASE_BARS]; }
static int deg_pitch(const sb_genome *g, int base, int idx){ int n = SCALE_LEN[g->scale]; return base + g->root + 12 * fdiv(idx, n) + SCALES[g->scale][mod(idx, n)]; }
static int mel_pitch(const sb_genome *g, int idx){ int p = deg_pitch(g, 60, idx); while(p < 60) p += 12; while(p > 96) p -= 12; return p; }
static int is_chord_tone(const sb_genome *g, int pos, int idx){ int n = SCALE_LEN[g->scale], c = chord_deg(g, pos), x = mod(idx - c, n); return x == 0 || x == 2 || x == 4; }
static int nearest_chord_tone(const sb_genome *g, int pos, int idx){
int d;
for(d = 0; d < 4; d++){ if(is_chord_tone(g, pos, idx - d)) return idx - d; if(is_chord_tone(g, pos, idx + d)) return idx + d; }
return idx;
}
typedef struct { int pos[5], cont[5], n, resp; } motif_t;
static void make_motif(uint32_t rs, const sb_genome *g, motif_t *M){
static const int STP[6] = {-2,-1,1,2,-4,4}, STP_W[6] = {3,4,4,3,1,1};
sb_rng r = rng(mix(rs, 0x4D4F54));
uint8_t used[8] = {1,0,0,0,0,0,0,0};
int np = 1, i, j;
M->n = g->motifLen; M->pos[0] = 0; M->cont[0] = 0;
if(chance(&r, P(40))){ M->pos[0] = below(&r, 2) ? 1 : 2; memset(used, 0, sizeof used); used[M->pos[0]] = 1; }
while(np < M->n){ int p = 1 + below(&r, 7); if(!used[p]){ used[p] = 1; M->pos[np++] = p; } }
for(i = 1; i < M->n; i++){ int x = M->pos[i]; for(j = i - 1; j >= 0 && M->pos[j] > x; j--) M->pos[j + 1] = M->pos[j]; M->pos[j + 1] = x; }
for(i = 1; i < M->n; i++) M->cont[i] = M->cont[i - 1] + STP[pick_w(&r, STP_W, 6)];
M->resp = below(&r, 3);
}
static void sort_dedupe(evlist *l){
int i, o = 0;
sort_tv(l);
for(i = 0; i < l->n; i++){
if(o > 0 && l->e[o - 1].t == l->e[i].t && l->e[o - 1].v == l->e[i].v){ if(l->e[i].vel > l->e[o - 1].vel) l->e[o - 1].vel = l->e[i].vel; }
else l->e[o++] = l->e[i];
}
l->n = o;
for(i = 0; i < l->n; i++) l->e[i].vel = (int16_t)clamp(l->e[i].vel, 1, 127);
}
static void one_at_a_time(evlist *l){
int i, o = 0;
sort_dedupe(l);
for(i = 0; i < l->n; i++){ if(o > 0 && l->e[o - 1].t == l->e[i].t) l->e[o - 1] = l->e[i]; else l->e[o++] = l->e[i]; }
l->n = o;
}
typedef struct { const sb_genome *g; const motif_t *M; sb_rng *vr; evlist *mel; int steps, d, pos, fill, last0; } place_ctx;
static void place(const place_ctx *c, int start, const int *cont, int ncont, int anchor, int velBase){
const sb_genome *g = c->g;
int k;
for(k = 0; k < c->M->n && k < ncont; k++){
int s = start + c->M->pos[k], idx, vel;
if(s >= c->steps) break;
if(c->fill && s >= c->last0) break;
idx = anchor + cont[k];
if(s % 4 == 0) idx = nearest_chord_tone(g, c->pos, idx);
if(chance(c->vr, P(c->d >> 3))) continue;
vel = velBase + (s % 4 == 0 ? 18 : 0) + below(c->vr, 12);
push(c->mel, s * TPS, mel_pitch(g, idx), vel, 0);
if(k + 1 < c->M->n && k + 1 < ncont && chance(c->vr, P(c->d >> 2))){
int s2 = start + c->M->pos[k + 1];
if(s2 - s >= 2 && (s + 1) < c->steps) push(c->mel, (s + 1) * TPS, mel_pitch(g, idx + (cont[k + 1] > cont[k] ? 1 : -1)), velBase - 14, 0);
}
}
}
static int bp(const sb_genome *g, int idx){ int p = deg_pitch(g, 36, idx); while(p > 47) p -= 12; while(p < 36) p += 12; return p; }
static void line_tracks(uint32_t rs, int variant, const sb_genome *g, int steps, int d, int sec, int pos, const evlist *kit, int fill,
evlist *mel, evlist *bass, evlist *pad){
sb_rng core = rng(mix(rs, 0x4D4Fu + (uint32_t)sec)), vr = rng(mix(mix(rs, 0x5641u + (uint32_t)sec), (uint32_t)variant));
int n = SCALE_LEN[g->scale], barT = steps * TPS, i, j;
int ng = n_groups(steps), last0 = (ng - 1) * 4, last1 = group_len(steps, ng - 1);
static int kickList[SB_MAX_EVENTS]; /* static: keeps the Pico's 2 KB stacks free */
int nk = 0, c = chord_deg(g, pos), anchor, half, rot, nextC, tones[3];
uint8_t seen[128];
motif_t M;
place_ctx pc;
mel->n = bass->n = pad->n = 0;
for(i = 0; i < kit->n; i++) if(kit->e[i].v == 0 && kit->e[i].t % TPS == 0) kickList[nk++] = kit->e[i].t;
make_motif(rs, g, &M);
pc.g = g; pc.M = &M; pc.vr = &vr; pc.mel = mel; pc.steps = steps; pc.d = d; pc.pos = pos; pc.fill = fill; pc.last0 = last0;
anchor = c + (chance(&core, P(50)) ? 2 : 0); half = steps >> 1;
place(&pc, 0, M.cont, M.n, anchor, 82);
if(d >= 40 || steps >= 16){
int c2[5], nc2 = M.n, a2 = anchor, st2 = half;
memcpy(c2, M.cont, sizeof c2);
if(M.resp == 0) a2 = anchor + 2;
else if(M.resp == 1) for(j = 0; j < nc2; j++) c2[j] = -c2[j];
else st2 = half + 1;
if(pos == PHRASE_BARS - 1) nc2 = imax(1, nc2 - 1);
place(&pc, st2, c2, nc2, a2, 74);
}
if(pos == PHRASE_BARS - 1 && !fill){
int cs = imax(0, steps - 4);
FILTER(mel, e->t < cs * TPS);
push(mel, cs * TPS, mel_pitch(g, chord_deg(g, 0)), 96, 0);
}
if(fill){
int nxt = chord_deg(g, 0) + n, k2 = 0, tt;
for(tt = last0 * TPS; tt < (last0 + last1) * TPS; tt += TPS, k2++) push(mel, tt, mel_pitch(g, nxt - (last1 - 1 - k2)), 76 + k2 * 8, 0);
}
rot = g->rot * TPS;
for(i = 0; i < mel->n; i++) mel->e[i].t = (int16_t)((mel->e[i].t + rot) % barT);
one_at_a_time(mel);
for(i = 0; i < mel->n; i++) mel->e[i].tr = (int16_t)(((g->hocket >> (i & 7)) & 1) ? SB_T_MELA : SB_T_MELB);
nextC = chord_deg(g, pos + 1);
for(i = 0; i < nk; i++){
int t = kickList[i], idx = c;
if(t > 0 && (i % 2) == 1 && chance(&core, P(70))) idx = c + g->bassDeg;
push_tr(bass, t, bp(g, idx), t == 0 ? 112 : 98, 0, SB_T_BASS);
}
if(steps >= 8 && chance(&core, P(60))){
int at = (steps - 1) * TPS;
if(!contains(kickList, nk, at)){ int dir = below(&core, 2) ? 1 : -1; push_tr(bass, at, bp(g, nextC + dir), 84, 0, SB_T_BASS); }
}
tones[0] = c; tones[1] = c + 2; tones[2] = c + 4;
memset(seen, 0, sizeof seen);
for(i = 0; i < 3; i++){ int p = 48 + mod(deg_pitch(g, 0, tones[i]), 12); if(!seen[p]){ seen[p] = 1; push_tr(pad, 0, p, i == 0 ? 58 : 50, 0, SB_T_PAD); } }
one_at_a_time(bass);
}
static int kit_track(const ev_t *e){
if(e->v == 0) return (e->a || e->t == 0) ? SB_T_KICK : SB_T_KICK2;
if(e->v == 1) return (e->a || e->vel >= 90) ? SB_T_SNARE : SB_T_GHOST;
if(e->v == 2) return (e->a || e->vel >= 85) ? SB_T_HAT : SB_T_HAT16;
return SB_T_PERC;
}
static int groove(int t, const sb_genome *g, int isSnare, int barTicks, int swing){
if(t % TPS == 0 && (t / TPS) % 2 == 1) t += swing;
if(isSnare && g->layback) t += 12;
return imin(t, barTicks - 1);
}
/* ---------- drift ---------- */
static int amt(int d, int a, int b){ return clamp(fdiv((d - a) * 127, b - a), 0, 127); }
static int nudge(uint32_t barSeed, int t, int grp, int micro){
int mx, n;
uint32_t h;
if(!micro || grp == 5) return 0;
mx = fdiv(micro * (grp == 4 ? 30 : 48), 127);
h = mix(mix(barSeed, (uint32_t)t), (uint32_t)grp);
n = (int)(h % (uint32_t)(2 * mx + 1)) - mx;
if(grp == 2) n += fdiv(micro * 10, 127);
if(grp == 1) n -= fdiv(micro * 8, 127);
return n;
}
enum { SG_HAT, SG_PERC, SG_GHOST, SG_KICK2, SG_SNARE };
static int shift_test(const ev_t *x, int which){
switch(which){
case SG_HAT: return x->v == 2;
case SG_PERC: return x->v == 3;
case SG_GHOST: return x->v == 1 && x->vel < 90;
case SG_KICK2: return x->v == 0 && !x->a && x->t > 0;
default: return x->v == 1 && x->vel >= 90;
}
}
static void shift_group(evlist *ev, int which, int by, int barT){
int i;
for(i = 0; i < ev->n; i++) if(shift_test(&ev->e[i], which)) ev->e[i].t = (int16_t)mod(ev->e[i].t + by, barT);
}
static void triplet_warp(evlist *ev, int beat){
int s = beat * TPB, i;
for(i = 0; i < ev->n; i++){
ev_t *e = &ev->e[i];
if(e->t >= s && e->t < s + TPB){ int k = imin(2, fdiv((e->t - s) * 3 + TPB / 2, TPB)); e->t = (int16_t)(s + k * 280); }
}
}
/* ---------- render ---------- */
static void emit(sb_bar *out, int t, int lane, int v, int vel, int len, int tr){
sb_event *e;
if(out->n >= SB_MAX_EVENTS){ g_overflow = 1; return; }
e = &out->ev[out->n++];
e->t = (int16_t)t; e->lane = (uint8_t)lane; e->v = (uint8_t)v; e->vel = (uint8_t)vel; e->len = (int16_t)len; e->tr = (uint8_t)tr;
}
typedef struct { const sb_genome *g; sb_bar *out; uint32_t barSeed; int barTicks, swingBar, micro, melShift; } mono_ctx;
enum { LEN_MEL, LEN_BASS };
static void mono(const mono_ctx *c, const evlist *list, int lenRule){
const sb_genome *g = c->g;
int k;
for(k = 0; k < list->n; k++){
const ev_t *cur = &list->e[k], *nx = k + 1 < list->n ? &list->e[k + 1] : 0;
int gap = (nx ? nx->t : c->barTicks) - cur->t, len, t0;
if(lenRule == LEN_BASS) len = gap - 10;
else if(g->monoLen == 0) len = imin(90, gap - 5);
else if(g->monoLen == 1) len = imax(30, fdiv(gap, 2));
else len = (nx && nx->v != cur->v) ? gap + 20 : gap - 5;
t0 = cur->t;
if(c->melShift && (cur->tr == SB_T_MELA || cur->tr == SB_T_MELB)) t0 = mod(t0 + c->melShift, c->barTicks);
emit(c->out, clamp(groove(t0, g, 0, c->barTicks, c->swingBar) + nudge(c->barSeed, cur->t, NUDGE_GRP[cur->tr], c->micro), 0, c->barTicks - 1),
1, cur->v, cur->vel, imax(10, len - c->swingBar), cur->tr);
}
}
static void sort_out(sb_bar *out){ /* stable, by (t, tr, v) */
int i, j;
for(i = 1; i < out->n; i++){
sb_event x = out->ev[i];
for(j = i - 1; j >= 0; j--){
const sb_event *y = &out->ev[j];
if(y->t > x.t || (y->t == x.t && (y->tr > x.tr || (y->tr == x.tr && y->v > x.v)))) out->ev[j + 1] = out->ev[j];
else break;
}
out->ev[j + 1] = x;
}
}
void sb_render_bar(const sb_state *st, const sb_params *p, int bar, sb_bar *out){
static evlist kit, mel, bass, pad, melA, melB;
sb_genome g;
fsets fs;
sb_rng fr, dRng;
mono_ctx mc;
uint32_t barSeed, krs, cS, vS;
int amen = p->gen == 4, pos, phrase, sec, barTicks, i, j, r, fill;
int dr, dVar, micro, wob, slip, disp, warp, e, arcD, kd, hd, warpBeat, melShift, swingBar, variants[2];
g_overflow = 0;
sb_genome_make(st->seed, &g);
sb_apply_key(&g, p);
if(amen){ g.steps = 16; g.swing = 0; g.layback = 0; }
pos = bar % PHRASE_BARS; phrase = bar / PHRASE_BARS; sec = g.form[pos]; barTicks = g.steps * TPS;
fr = rng(mix(mix(st->seed, 0x46494C), (uint32_t)phrase));
fill = p->fill ? 1 : (!amen && pos == PHRASE_BARS - 1 && chance(&fr, P(g.fillP >> 1)));
dr = clamp(p->drift, 0, 127); dVar = imin(127, dr * 3);
micro = amt(dr, 20, 127); wob = amt(dr, 40, 127); slip = amt(dr, 55, 127); disp = amt(dr, 80, 127); warp = amt(dr, 105, 127);
if(amen){ micro = fdiv(micro, 2); wob = fdiv(wob, 2); slip = fdiv(slip, 2); disp = fdiv(amt(dr, 90, 127), 2); warp = amt(dr, 115, 127); }
dRng = rng(mix(mix(st->seed, 0x445249), (uint32_t)bar)); barSeed = mix(st->seed, 0x4E55u + (uint32_t)bar);
for(r = 0; r < 2; r++){
variants[r] = 0;
if(phrase > 0){ sb_rng vrng = rng(mix(mix(role_seed(st, r), 0xD1F7), (uint32_t)phrase)); if(chance(&vrng, P(dVar))) variants[r] = phrase; }
}
e = clamp(p->energy, 0, 127); krs = role_seed(st, 0); cS = mix(krs, 0x434F); vS = mix(mix(krs, 0x5641u + (uint32_t)sec), (uint32_t)variants[0]);
arcD = (g.arc == 1) ? (pos * 10 - 15) : 0;
kd = clamp(g.dens[0] + e - 64 + arcD, 0, 127); hd = clamp(g.dens[0] + fdiv(e - 64, 4) + arcD, 0, 127);
frame_sets(&g, g.steps, pos, &fs);
kit.n = 0;
if(amen){
kit_amen(krs, bar, pos, dr, fill, &kit);
humanize_amen(&kit, krs, bar);
if(slip){ int jt = fdiv(slip * 24, 127); for(i = 0; i < kit.n; i++){ int dv = below(&dRng, 2 * jt + 1) - jt; kit.e[i].vel = (int16_t)clamp(kit.e[i].vel + dv, 1, 127); } }
} else {
int aoff = 0;
if(p->gen == 0) kit_euclid(cS, vS, g.steps, kd, hd, pos, &fs, &kit);
else if(p->gen == 1) kit_skeleton(cS, vS, g.steps, kd, hd, pos, &fs, g.frame, &kit);
else if(p->gen == 2) kit_subdiv(cS, vS, g.steps, kd, hd, pos, &fs, &kit);
else kit_ca(cS, vS, g.steps, kd, hd, pos, sec, &fs, &kit);
transform_kit(&kit, g.steps, sec, krs, &fs);
arc_kit(&kit, &g, pos);
if(fill) kit_fill(&kit, g.steps, p->energy, &fr);
if(chance(&dRng, P(slip))) aoff = 1 + below(&dRng, 3);
shape_kit(&kit, &g, pos, aoff, fdiv(slip * 24, 127), &dRng);
}
if(disp){
static const int DIRS[2] = {TPS, -TPS};
if(chance(&dRng, P(disp))) shift_group(&kit, SG_HAT, DIRS[below(&dRng, 2)], barTicks);
if(chance(&dRng, P(disp))) shift_group(&kit, SG_PERC, DIRS[below(&dRng, 2)], barTicks);
if(chance(&dRng, P(disp >> 1))) shift_group(&kit, SG_GHOST, DIRS[below(&dRng, 2)], barTicks);
if(chance(&dRng, P(disp >> 1))) shift_group(&kit, SG_KICK2, DIRS[below(&dRng, 2)], barTicks);
if(chance(&dRng, P(disp >> 2))) shift_group(&kit, SG_SNARE, TPS, barTicks);
}
warpBeat = -1;
if(warp && chance(&dRng, P(warp))) warpBeat = below(&dRng, imax(1, fdiv(g.steps, 4)));
if(warpBeat >= 0) triplet_warp(&kit, warpBeat);
sort_dedupe(&kit);
melShift = 0;
if(disp && chance(&dRng, P(disp >> 1))) melShift = below(&dRng, 2) ? TPS : -TPS;
swingBar = g.swing;
if(wob){ int w = fdiv(wob * 40, 127); swingBar = clamp(g.swing + below(&dRng, 2 * w + 1) - w, 0, 90); }
line_tracks(role_seed(st, 1), variants[1], &g, g.steps, clamp(g.dens[1] + e - 64, 0, 127), sec, pos, &kit, fill, &mel, &bass, &pad);
out->n = 0;
for(i = 0; i < kit.n; i++){
const ev_t *k = &kit.e[i];
int tr = kit_track(k), t = k->t, nt = barTicks, len, ft;
if(tr == SB_T_PERC) t = (t + g.rot * TPS) % barTicks;
for(j = i + 1; j < kit.n; j++) if(kit.e[j].v == k->v){ nt = kit.e[j].t; break; }
len = imax(10, imin(105, nt > t ? nt - t : 105));
ft = groove(t, &g, tr == SB_T_SNARE, barTicks, swingBar) + nudge(barSeed, t, NUDGE_GRP[tr], micro) + (amen ? amen_feel(krs, bar, t, k->v, k->a) : 0);
emit(out, clamp(ft, 0, barTicks - 1), 0, k->v, k->vel, len, tr);
}
mc.g = &g; mc.out = out; mc.barSeed = barSeed; mc.barTicks = barTicks; mc.swingBar = swingBar; mc.micro = micro; mc.melShift = melShift;
melA.n = melB.n = 0;
for(i = 0; i < mel.n; i++) push_ev(mel.e[i].tr == SB_T_MELA ? &melA : &melB, &mel.e[i]);
mono(&mc, &melA, LEN_MEL); mono(&mc, &melB, LEN_MEL);
mono(&mc, &bass, LEN_BASS);
for(i = 0; i < pad.n; i++) emit(out, 0, 1, pad.e[i].v, pad.e[i].vel, barTicks - 30, SB_T_PAD);
sort_out(out);
out->bar = bar; out->pos = pos; out->phrase = phrase; out->sec = sec; out->fill = fill;
out->variants[0] = variants[0]; out->variants[1] = variants[1]; out->barTicks = barTicks; out->steps = g.steps;
out->overflow = g_overflow;
}
uint32_t sb_checksum_event(uint32_t h, const sb_event *e){
int f[6], i;
f[0] = e->t; f[1] = e->lane; f[2] = e->v; f[3] = e->vel; f[4] = e->len; f[5] = e->tr;
for(i = 0; i < 6; i++) h = (h ^ ((uint32_t)f[i] & 0xFFFFu)) * 0x01000193u;
return h;
}