Split kit and keys; KEYS GEN with MOTIF, 303, TURING, ARP
Some checks failed
check / check (push) Has been cancelled

- genome(seed, salts) now has three parts with their own RNG streams:
  shared (steps, form, swing, arc, layback, fill odds, phase offset),
  kit (density, frame, accent cycle, free snare) and keys (scale/root,
  progression, motif, hocket, bass tone, note lengths, range). ROLL KEYS
  now renews the harmony too; ROLL KIT leaves the keys alone.
- Keys generators share one signature and can fix note lengths:
  MOTIF (the old line), 303 (acid bass, answers in the rests, drone or
  silent pad), TURING (8/16-bit shift register, DRIFT flips bits, 16-bar
  window), ARP (triad/7th arpeggio, roots on kicks).
- Pages: KIT GEN, KEYS GEN; ROLL shows KIT / KEYS; slots keep both gens.
- Flash record v3 (old settings ignored). Bring-up golden checksums updated.
- C port matches; host test and ASan/UBSan pass.

Intended music change: every seed sounds different (genome split, new
generators). Vectors regenerated with npm run vectors; the case grid now
crosses all kit and keys generators, with TURING runs past its window.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-11 21:50:41 +03:00
parent c2c8a55642
commit 346c820b10
26 changed files with 64270 additions and 42622 deletions

View File

@@ -99,17 +99,18 @@ 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 { int16_t t, v, vel, a, tr, len; } ev_t; /* len: keys generators' fixed length, 0 = the default rule */
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;
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; e->len = 0;
}
static void push_len(evlist *l, int t, int v, int vel, int tr, int len){ int n0 = l->n; push_tr(l, t, v, vel, 0, tr); if(l->n > n0) l->e[l->n - 1].len = (int16_t)len; }
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); }
static void push_ev(evlist *l, const ev_t *e){ if(l->n >= SB_MAX_EVENTS){ g_overflow = 1; return; } l->e[l->n++] = *e; }
/* 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)
@@ -124,28 +125,35 @@ static void sort_tv(evlist *l){ /* stable, by (t, v) */
}
/* ---------- genome ---------- */
void sb_genome_make(uint32_t seed, sb_genome *g){
static uint32_t role_seed(const sb_state *st, int r){ return mix(mix(st->seed, 0x524F4C45u + (uint32_t)r), st->salts[r]); }
void sb_genome_make(const sb_state *st, 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));
sb_rng r = rng(mix(st->seed, 0x47454E)), k, l;
int fi;
/* shared */
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->fillP = 40 + below(&r, 80);
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);
/* kit */
k = rng(mix(role_seed(st, 0), 0x4B4954));
g->dens[0] = 40 + below(&k, 60); g->dens[1] = 0;
g->accentCycle = ACC[pick_w(&k, ACC_W, 5)];
g->frame = pick_w(&k, FRAME_W, 8); g->freeK = 2 + below(&k, 3); g->freeRot = below(&k, 16);
/* keys */
l = rng(mix(role_seed(st, 1), 0x4B4559));
g->scale = below(&l, SB_NSCALES); g->root = below(&l, 12);
g->dens[1] = 30 + below(&l, 70);
g->monoLen = below(&l, 3); g->monoRange = 1 + below(&l, 2);
g->hocket = HOCKETS[below(&l, 8)];
g->bassDeg = BASS_DEG[below(&l, 3)];
g->prog_idx = below(&l, 10); memcpy(g->prog, PROGS[g->prog_idx], sizeof g->prog);
g->motifLen = 3 + below(&l, 3);
}
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; }
@@ -578,6 +586,136 @@ static void line_tracks(uint32_t rs, int variant, const sb_genome *g, int steps,
one_at_a_time(bass);
}
/* ---------- keys generators ---------- */
typedef struct { uint32_t rs; int variant; const sb_genome *g; int steps, d, sec, pos; const evlist *kit; int fill, bar, drift; } keys_in;
static void keys_motif(const keys_in *in, evlist *mel, evlist *bass, evlist *pad){
line_tracks(in->rs, in->variant, in->g, in->steps, in->d, in->sec, in->pos, in->kit, in->fill, mel, bass, pad);
}
static void pad_chord(const sb_genome *g, const int *tones, int nt, evlist *pad){
uint8_t seen[128];
int i;
memset(seen, 0, sizeof seen);
for(i = 0; i < nt; 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); } }
}
typedef struct { int rank, off, up, acc, sl; } step303;
static void keys_303(const keys_in *in, evlist *mel, evlist *bass, evlist *pad){
static const int OFF[4] = {0,2,4,6}, OFF_W[4] = {10,2,3,1}, TONE[3] = {0,2,4};
const sb_genome *g = in->g;
sb_rng r = rng(mix(in->rs, 0x333033)), ar;
step303 pat[16];
uint8_t on[MAX_STEPS];
int c = chord_deg(g, in->pos), steps = in->steps, i, s, drone, thr;
mel->n = bass->n = pad->n = 0;
for(i = 0; i < 16; i++){
pat[i].rank = 0;
if(i > 0) pat[i].rank = below(&r, 128);
pat[i].off = OFF[pick_w(&r, OFF_W, 4)];
pat[i].up = chance(&r, P(24));
pat[i].acc = chance(&r, P(40));
pat[i].sl = chance(&r, P(28));
}
drone = chance(&r, P(64));
if(in->variant){
sb_rng vr = rng(mix(mix(in->rs, 0x5641u + (uint32_t)in->sec), (uint32_t)in->variant));
for(i = 0; i < 16; i++){
if(chance(&vr, P(20))) pat[i].acc = !pat[i].acc;
if(chance(&vr, P(16))) pat[i].sl = !pat[i].sl;
if(chance(&vr, P(16))) pat[i].up = !pat[i].up;
if(i > 0 && chance(&vr, P(16))) pat[i].rank = 127 - pat[i].rank;
}
}
thr = clamp(in->d + 16, 0, 128);
for(s = 0; s < steps; s++) on[s] = (uint8_t)(pat[s % 16].rank < thr);
for(s = 0; s < steps; s++) if(on[s]){
const step303 *p = &pat[s % 16];
int nx = s + 1;
while(nx < steps && !on[nx]) nx++;
push_len(bass, s * TPS, bp(g, c + p->off) + (p->up ? 12 : 0), p->acc ? 120 : 80, SB_T_BASS, p->sl ? (nx - s) * TPS + 20 : TPS >> 1);
}
ar = rng(mix(in->rs, 0x414E53u + (uint32_t)in->sec));
for(s = 0; s < steps;){
int e2 = s, tr;
if(on[s]){ s++; continue; }
while(e2 < steps && !on[e2]) e2++;
if(e2 - s >= 2 && chance(&ar, P(in->d + 32))){
tr = ((g->hocket >> (s & 7)) & 1) ? SB_T_MELA : SB_T_MELB;
push_len(mel, s * TPS, mel_pitch(g, c + TONE[below(&ar, 3)]), 84, tr, TPS - 40);
if(e2 - s >= 3 && chance(&ar, P(64))) push_len(mel, (s + 1) * TPS, mel_pitch(g, c + TONE[below(&ar, 3)]), 72, tr, TPS - 40);
}
s = e2;
}
if(drone) push_tr(pad, 0, 48 + g->root, 52, 0, SB_T_PAD);
}
typedef struct { int L, flipP; uint32_t mask, fs; } turing_t;
static uint32_t rotl_bits(uint32_t v, int s, int L){ return s ? (((v << s) | (v >> (L - s))) & ((1u << L) - 1u)) : v; }
static uint32_t turing_step(const turing_t *T, uint32_t v, int k){
uint32_t top = (v >> (T->L - 1)) & 1u;
if(T->flipP && (int)(mix(T->fs, (uint32_t)k) >> 16) < T->flipP) top ^= 1u;
return ((v << 1) | top) & T->mask;
}
static void keys_turing(const keys_in *in, evlist *mel, evlist *bass, evlist *pad){
const sb_genome *g = in->g;
sb_rng r = rng(mix(in->rs, 0x545552));
turing_t T;
uint32_t reg;
int n = SCALE_LEN[g->scale], c = chord_deg(g, in->pos), from, to, k, s, j = 0, span, tones[3];
mel->n = bass->n = pad->n = 0;
T.L = chance(&r, P(64)) ? 8 : 16; T.mask = (1u << T.L) - 1u;
reg = sb_rng_next(&r) & T.mask;
if(!reg) reg = 1;
T.flipP = P(in->drift >> 2); T.fs = mix(in->rs, 0x464C50);
from = imax(0, in->bar - 16) * in->steps; to = in->bar * in->steps;
reg = rotl_bits(reg, from % T.L, T.L);
for(k = from; k < to; k++) reg = turing_step(&T, reg, k);
span = n * g->monoRange;
for(s = 0; s < in->steps; s++){
if((int)((reg >> 2) & 7u) * 18 < in->d){
int v = mel_pitch(g, c + (int)(((reg & 0xFFu) * (uint32_t)(span + 1)) >> 8));
push_tr(mel, s * TPS, v, 70 + (int)((reg >> 5) & 3u) * 10 + (s % 4 == 0 ? 12 : 0), 0, ((g->hocket >> (j & 7)) & 1) ? SB_T_MELA : SB_T_MELB);
j++;
}
if(reg & 1u) push_tr(bass, s * TPS, bp(g, c) + (((reg >> (T.L >> 1)) & 1u) ? 12 : 0), s == 0 ? 110 : 94, 0, SB_T_BASS);
reg = turing_step(&T, reg, to + s);
}
tones[0] = c; tones[1] = c + 2; tones[2] = c + 4;
pad_chord(g, tones, 3, pad);
}
static void keys_arp(const keys_in *in, evlist *mel, evlist *bass, evlist *pad){
const sb_genome *g = in->g;
sb_rng r = rng(mix(in->rs, 0x415250));
int n = SCALE_LEN[g->scale], c = chord_deg(g, in->pos), i, o, seventh, dir, nch, chord[4], up[8], seq[16], nup = 0, nseq = 0;
int off = 0, rate, last0, s = 0, k = 0, nk = 0;
mel->n = bass->n = pad->n = 0;
seventh = chance(&r, P(64));
dir = below(&r, 4);
nch = seventh ? 4 : 3; chord[0] = c; chord[1] = c + 2; chord[2] = c + 4; chord[3] = c + 6;
for(o = 0; o < g->monoRange; o++) for(i = 0; i < nch; i++) up[nup++] = chord[i] + o * n;
if(dir == 1) for(i = nup - 1; i >= 0; i--) seq[nseq++] = up[i];
else {
for(i = 0; i < nup; i++) seq[nseq++] = up[i];
if(dir == 2) for(i = nup - 2; i > 0; i--) seq[nseq++] = up[i];
else if(dir == 3) for(i = nseq - 1; i > 0; i--){ int j = below(&r, i + 1), tmp = seq[i]; seq[i] = seq[j]; seq[j] = tmp; }
}
if(in->variant){ sb_rng vr = rng(mix(mix(in->rs, 0x5641u + (uint32_t)in->sec), (uint32_t)in->variant)); off = below(&vr, nseq); }
rate = in->d >= 64 ? 1 : 2; last0 = (n_groups(in->steps) - 1) * 4;
while(s < in->steps){
int stp = (in->fill && s >= last0) ? 1 : rate;
push_len(mel, s * TPS, mel_pitch(g, seq[(k + off) % nseq]), s % 4 == 0 ? 96 : 78, (k & 1) ? SB_T_MELB : SB_T_MELA, stp * TPS - 20);
k++; s += stp;
}
for(i = 0; i < in->kit->n; i++){
const ev_t *e = &in->kit->e[i];
if(e->v == 0 && e->t % TPS == 0){ push_tr(bass, e->t, bp(g, c), e->t == 0 ? 112 : 98, 0, SB_T_BASS); nk++; }
}
if(!nk) push_tr(bass, 0, bp(g, c), 104, 0, SB_T_BASS);
pad_chord(g, chord, nch, pad);
}
static const char *const KEYS_NAMES[SB_NKEYS] = {"MOTIF","303","TURING","ARP"};
const char *sb_keys_name(int keys){ return keys >= 0 && keys < SB_NKEYS ? KEYS_NAMES[keys] : "?"; }
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;
@@ -640,14 +778,15 @@ static void mono(const mono_ctx *c, const evlist *list, int lenRule){
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;
if(cur->len > 0) len = cur->len; /* fixed by the keys generator */
else 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);
1, cur->v, cur->vel, imax(10, cur->len > 0 ? len : len - c->swingBar), cur->tr);
}
}
static void sort_out(sb_bar *out){ /* stable, by (t, tr, v) */
@@ -674,7 +813,7 @@ void sb_render_bar(const sb_state *st, const sb_params *p, int bar, sb_bar *out)
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_genome_make(st, &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;
@@ -725,7 +864,17 @@ void sb_render_bar(const sb_state *st, const sb_params *p, int bar, sb_bar *out)
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);
{
keys_in ki;
ki.rs = role_seed(st, 1); ki.variant = variants[1]; ki.g = &g; ki.steps = g.steps; ki.d = clamp(g.dens[1] + e - 64, 0, 127);
ki.sec = sec; ki.pos = pos; ki.kit = &kit; ki.fill = fill; ki.bar = bar; ki.drift = dr;
switch(clamp(p->keys, 0, SB_NKEYS - 1)){
case SB_KEYS_303: keys_303(&ki, &mel, &bass, &pad); break;
case SB_KEYS_TURING: keys_turing(&ki, &mel, &bass, &pad); break;
case SB_KEYS_ARP: keys_arp(&ki, &mel, &bass, &pad); break;
default: keys_motif(&ki, &mel, &bass, &pad); break;
}
}
out->n = 0;
for(i = 0; i < kit.n; i++){

View File

@@ -17,6 +17,7 @@
enum { SB_T_KICK, SB_T_KICK2, SB_T_SNARE, SB_T_GHOST, SB_T_HAT, SB_T_HAT16, SB_T_PERC,
SB_T_BASS, SB_T_MELA, SB_T_MELB, SB_T_PAD };
enum { SB_GEN_EUCLID, SB_GEN_SKELETON, SB_GEN_SUBDIV, SB_GEN_AUTOMATA, SB_GEN_AMENIZER, SB_NGEN };
enum { SB_KEYS_MOTIF, SB_KEYS_303, SB_KEYS_TURING, SB_KEYS_ARP, SB_NKEYS };
/* hash and RNG */
uint32_t sb_mix(uint32_t a, uint32_t b);
@@ -38,11 +39,12 @@ typedef struct {
int prog[4];
int swing, arc, motifLen, accentCycle, layback, frame, freeK, freeRot;
} sb_genome;
void sb_genome_make(uint32_t seed, sb_genome *g);
typedef struct { uint32_t seed; uint32_t salts[2]; } sb_state;
/* shared part from the seed; kit and keys parts also take their salt (ROLL KIT / ROLL KEYS) */
void sb_genome_make(const sb_state *st, sb_genome *g);
const char *sb_form_name(int form_idx);
typedef struct { uint32_t seed; uint32_t salts[2]; } sb_state;
typedef struct { int gen, energy, drift, fill, scale, root; } sb_params; /* scale, root: -1 = AUTO (the seed picks) */
typedef struct { int gen, keys, energy, drift, fill, scale, root; } sb_params; /* scale, root: -1 = AUTO (the seed picks) */
/* one output row: [t, lane, v, vel, len, tr] */
typedef struct { int16_t t, len; uint8_t lane, v, vel, tr; } sb_event;
@@ -56,7 +58,8 @@ typedef struct {
/* ROOT / SCALE pages: overrides the genome's scale and root when they're >= 0 (app.js applyKey). */
void sb_apply_key(sb_genome *g, const sb_params *p);
const char *sb_scale_name(int scale); /* screen name, at most 10 characters */
const char *sb_scale_name(int scale);
const char *sb_keys_name(int keys); /* MOTIF, 303, TURING, ARP */ /* screen name, at most 10 characters */
void sb_render_bar(const sb_state *st, const sb_params *p, int bar, sb_bar *out);