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

@@ -15,7 +15,7 @@ typedef struct { uint8_t used, ch, note; } pad_t; /* learned pad: ch 0..15 */
/* Everything the sequencer reads. Core 0 writes it under g_live_lock; core 1 copies it under the same lock. */
typedef struct {
sb_state state;
int gen, energy, drift, bpm;
int gen, keys, energy, drift, bpm; /* gen = KIT GEN, keys = KEYS GEN */
int scale, root; /* ROOT / SCALE pages: -1 = AUTO (the seed picks) */
uint8_t on[SB_NT], inv[SB_NT]; /* inv: flipped while the button is held */
pad_t learn[NKIT];

View File

@@ -29,7 +29,7 @@ static const char *const TRACK_NAME[SB_NT] = { "KICK","KICK 2","SNARE","GHOST","
static const char *const NOTE_NAME[12] = { "C","C#","D","D#","E","F","F#","G","G#","A","A#","B" };
static const char *const GEN_NAME[SB_NGEN] = { "EUCLID","SKELETON","SUBDIV","AUTOMATA","AMENIZER" };
static const int8_t PANEL[12] = { 0, 1, 2, -1, 4, 5, 6, 3, 7, 8, 9, 10 }; /* screen map = button grid; -1 = RUN */
enum { PG_SEED, PG_GEN, PG_ROOT, PG_SCALE, PG_BPM, PG_SLOT, PG_ROLL, PG_LEARN, NPAGES };
enum { PG_SEED, PG_GEN, PG_KEYS, PG_ROOT, PG_SCALE, PG_BPM, PG_SLOT, PG_ROLL, PG_LEARN, NPAGES };
static slot_t slots[NSLOTS];
static sb_state history[16];
@@ -57,7 +57,7 @@ static void set_state(sb_state s){
hist_sel = 0; mark_dirty();
}
static void new_seed(void){ sb_state s = { get_rand_32(), {0, 0} }; set_state(s); }
static void reroll(int l){ sb_state s = g_live.state; s.salts[l] = (s.salts[l] + 1) & 0xFFFF; set_state(s); msg(l ? "NEW LINE" : "NEW KIT", 700); }
static void reroll(int l){ sb_state s = g_live.state; s.salts[l] = (s.salts[l] + 1) & 0xFFFF; set_state(s); msg(l ? "NEW KEYS" : "NEW KIT", 700); }
static void apply_on(int tr, int v){ lock(); g_live.on[tr] = (uint8_t)v; unlock(); mark_dirty(); if(!sounds(tr)) send_cmd(CMD_RELEASE_TRACK, tr); }
static void set_inv(int tr, int v){ lock(); g_live.inv[tr] = (uint8_t)v; unlock(); if(!sounds(tr)) send_cmd(CMD_RELEASE_TRACK, tr); }
@@ -68,6 +68,7 @@ static void enc_turn(int d){
switch(page){
case PG_SEED: hist_sel = clampi(hist_sel + d, 0, nhist); break;
case PG_GEN: lock(); g_live.gen = (g_live.gen + d + SB_NGEN) % SB_NGEN; unlock(); mark_dirty(); break;
case PG_KEYS: lock(); g_live.keys = (g_live.keys + d + SB_NKEYS) % SB_NKEYS; unlock(); mark_dirty(); break;
case PG_ROOT: lock(); g_live.root = (g_live.root + 1 + d + 13) % 13 - 1; unlock(); mark_dirty(); break; /* AUTO, C .. B */
case PG_SCALE: lock(); g_live.scale = (g_live.scale + 1 + d + SB_NSCALES + 1) % (SB_NSCALES + 1) - 1; unlock(); mark_dirty(); break; /* AUTO, MAJOR .. LOCRIAN */
case PG_BPM: set_bpm(g_live.bpm + d); break;
@@ -88,13 +89,13 @@ static void enc_push(int hold){
char t[24];
slots[slot_sel].used = 1; slots[slot_sel].seed = g_live.state.seed;
slots[slot_sel].salts[0] = g_live.state.salts[0]; slots[slot_sel].salts[1] = g_live.state.salts[1];
slots[slot_sel].gen = (uint8_t)g_live.gen; mark_dirty();
slots[slot_sel].gen = (uint8_t)g_live.gen; slots[slot_sel].keys = (uint8_t)g_live.keys; mark_dirty();
snprintf(t, sizeof t, "SAVED S%d", slot_sel + 1); msg(t, 900);
return;
}
if(slots[slot_sel].used){
sb_state s = { slots[slot_sel].seed, { slots[slot_sel].salts[0], slots[slot_sel].salts[1] } };
lock(); g_live.gen = slots[slot_sel].gen; unlock();
lock(); g_live.gen = slots[slot_sel].gen; g_live.keys = slots[slot_sel].keys % SB_NKEYS; unlock();
set_state(s);
}
}
@@ -149,15 +150,16 @@ static void screen_text(char *label, char *value){
if(hist_sel > 0) snprintf(n, sizeof n, " -%d", hist_sel);
snprintf(l, sizeof l, "SEED%s%s", n, (s->salts[0] || s->salts[1]) ? " *" : "");
hex8(v, s->seed); break; }
case PG_GEN: strcpy(l, "GENERATOR"); strcpy(v, GEN_NAME[g_live.gen]); break;
case PG_GEN: strcpy(l, "KIT GEN"); strcpy(v, GEN_NAME[g_live.gen]); break;
case PG_KEYS: strcpy(l, "KEYS GEN"); strcpy(v, sb_keys_name(g_live.keys)); break;
case PG_ROOT: case PG_SCALE: { /* on AUTO, show the seed's own pick */
sb_genome g; int sc = page == PG_SCALE, x = sc ? g_live.scale : g_live.root;
if(x < 0){ sb_genome_make(g_live.state.seed, &g); x = sc ? g.scale : g.root; }
if(x < 0){ sb_genome_make(&g_live.state, &g); x = sc ? g.scale : g.root; }
snprintf(l, sizeof l, "%s%s", sc ? "SCALE" : "ROOT", (sc ? g_live.scale : g_live.root) < 0 ? " AUTO" : "");
strcpy(v, sc ? sb_scale_name(x) : NOTE_NAME[x]); break; }
case PG_BPM: strcpy(l, "TEMPO"); snprintf(v, sizeof v, "%d BPM", g_live.bpm); break;
case PG_SLOT: snprintf(l, sizeof l, "SLOT %d", slot_sel + 1); if(slots[slot_sel].used) hex8(v, slots[slot_sel].seed); else strcpy(v, "EMPTY"); break;
case PG_ROLL: strcpy(l, "RE-ROLL"); strcpy(v, roll_sel ? "LINE" : "KIT"); break;
case PG_ROLL: strcpy(l, "RE-ROLL"); strcpy(v, roll_sel ? "KEYS" : "KIT"); break;
case PG_LEARN: {
if(learn_mode == 2){ const char *n = TRACK_NAME[learn_tr]; char *o; strcpy(l, "LEARN "); o = l + 6; for(; *n; n++) if(*n != ' ') *o++ = *n; *o = 0; }
else strcpy(l, "LEARN");

View File

@@ -125,7 +125,7 @@ static void render_ahead(uint64_t now){
sb_params p;
int i;
snapshot(&L);
p.gen = L.gen; p.energy = L.energy; p.drift = L.drift; p.fill = 0; p.scale = L.scale; p.root = L.root; /* no FILL button: bar-4 fills happen on their own */
p.gen = L.gen; p.keys = L.keys; p.energy = L.energy; p.drift = L.drift; p.fill = 0; p.scale = L.scale; p.root = L.root; /* no FILL button: bar-4 fills happen on their own */
sb_render_bar(&L.state, &p, bar, &rbar);
for(i = 0; i < rbar.n; i++){
enqueue(next_bar_tick + rbar.ev[i].t, 1, &rbar.ev[i]);

View File

@@ -5,14 +5,14 @@
#include <string.h>
#define MAGIC 0x53425831u /* "SBX1" */
#define VERSION 2 /* 2: ROOT / SCALE added, BANK gone. Older records are ignored. */
#define VERSION 3 /* 3: KEYS GEN. Older records are ignored. */
#define OFFSET (PICO_FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE)
#define BUFSZ 512
typedef struct {
uint32_t magic, version;
uint32_t seed, salts[2];
int32_t gen, bpm, scale, root, mono_ch, mono_oct;
int32_t gen, keys, bpm, scale, root, mono_ch, mono_oct;
uint8_t on[SB_NT];
pad_t learn[NKIT];
slot_t slots[NSLOTS];
@@ -33,7 +33,7 @@ void storage_defaults(live_t *L, slot_t *slots){
int t;
memset(L, 0, sizeof *L);
L->state.seed = 0x5EEDB0C5u;
L->gen = 0; L->scale = -1; L->root = -1; L->energy = 64; L->drift = 32; L->bpm = 165;
L->gen = 0; L->keys = 0; L->scale = -1; L->root = -1; L->energy = 64; L->drift = 32; L->bpm = 165;
for(t = 0; t < SB_NT; t++) L->on[t] = 1;
L->mono_ch = 1; L->mono_oct = 0; L->clock = 1;
memset(slots, 0, sizeof(slot_t) * NSLOTS);
@@ -45,7 +45,7 @@ int storage_load(live_t *L, slot_t *slots){
storage_defaults(L, slots);
if(r->magic != MAGIC || r->version != VERSION || r->crc != crc(r)) return 0;
L->state.seed = r->seed; L->state.salts[0] = r->salts[0]; L->state.salts[1] = r->salts[1];
L->gen = r->gen; L->bpm = r->bpm; L->scale = r->scale; L->root = r->root; L->mono_ch = r->mono_ch; L->mono_oct = r->mono_oct;
L->gen = r->gen; L->keys = r->keys; L->bpm = r->bpm; L->scale = r->scale; L->root = r->root; L->mono_ch = r->mono_ch; L->mono_oct = r->mono_oct;
for(t = 0; t < SB_NT; t++) L->on[t] = r->on[t] ? 1 : 0;
memcpy(L->learn, r->learn, sizeof L->learn);
memcpy(slots, r->slots, sizeof r->slots);
@@ -64,7 +64,7 @@ int storage_save(const live_t *L, const slot_t *slots){
memset(r, 0, sizeof *r);
r->magic = MAGIC; r->version = VERSION;
r->seed = L->state.seed; r->salts[0] = L->state.salts[0]; r->salts[1] = L->state.salts[1];
r->gen = L->gen; r->bpm = L->bpm; r->scale = L->scale; r->root = L->root; r->mono_ch = L->mono_ch; r->mono_oct = L->mono_oct;
r->gen = L->gen; r->keys = L->keys; r->bpm = L->bpm; r->scale = L->scale; r->root = L->root; r->mono_ch = L->mono_ch; r->mono_oct = L->mono_oct;
memcpy(r->on, L->on, sizeof r->on);
memcpy(r->learn, L->learn, sizeof r->learn);
memcpy(r->slots, slots, sizeof r->slots);

View File

@@ -1,4 +1,4 @@
/* Settings in the last flash sector: slots, learned pads, BPM, generator, root, scale, synth channel and octave,
/* Settings in the last flash sector: slots, learned pads, BPM, kit and keys generators, root, scale, synth channel and octave,
plus the current seed and track on/off (as app.js keeps them). Erasing a sector stalls both cores for tens of
milliseconds, so main.c only saves while stopped. */
#ifndef SEEDBOX_STORAGE_H
@@ -6,7 +6,7 @@
#include "app.h"
typedef struct { uint8_t used, gen; uint32_t seed; uint32_t salts[2]; } slot_t;
typedef struct { uint8_t used, gen, keys; uint32_t seed; uint32_t salts[2]; } slot_t;
void storage_defaults(live_t *L, slot_t *slots);
int storage_load(live_t *L, slot_t *slots); /* 1 if a valid record was found */

View File

@@ -21,9 +21,9 @@ static const char *const SW_NAME[3] = { "SEED", "RUN", "PUSH" };
/* engine self-test: golden cases from test/vectors (8 bars each) */
typedef struct { uint32_t seed; int gen, energy, drift; uint32_t checksum; } golden_t;
static const golden_t GOLDEN[3] = {
{ 0x5EEDB0C5, 0, 64, 32, 0x4F5DE957 }, /* 5EEDB0C5_s0-0_g0_e64_d32 */
{ 0x5EEDB0C5, 3, 127, 0, 0x29C313A5 }, /* 5EEDB0C5_s0-0_g3_e127_d0, dense */
{ 0x5EEDB0C5, 4, 64, 32, 0x8C01C1F9 }, /* 5EEDB0C5_s0-0_g4_e64_d32, AMENIZER */
{ 0x5EEDB0C5, 0, 64, 32, 0xC055F3A7 }, /* 5EEDB0C5_s0-0_g0_k0_e64_d32 */
{ 0x5EEDB0C5, 3, 127,127, 0x1CF02038 }, /* 5EEDB0C5_s0-0_g3_k0_e127_d127, dense */
{ 0x5EEDB0C5, 4, 64, 32, 0x487CBA24 }, /* 5EEDB0C5_s0-0_g4_k0_e64_d32, AMENIZER */
};
static int engine_ok;
static uint32_t engine_worst_us;
@@ -35,7 +35,7 @@ static void engine_selftest(void){
engine_ok = 1; engine_worst_us = 0;
for(c = 0; c < 3; c++){
sb_state st = { GOLDEN[c].seed, {0, 0} };
sb_params p = { GOLDEN[c].gen, GOLDEN[c].energy, GOLDEN[c].drift, 0, -1, -1 };
sb_params p = { GOLDEN[c].gen, SB_KEYS_MOTIF, GOLDEN[c].energy, GOLDEN[c].drift, 0, -1, -1 };
uint32_t h = SB_FNV_INIT;
for(b = 0; b < 8; b++){
uint64_t t0 = time_us_64();

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);

View File

@@ -52,7 +52,11 @@ static void check_primitives(const char *dir){
sb_genome g;
int bad = 0, k;
n++;
sb_genome_make(seed, &g);
{
sb_state gs; jval *sl = json_get(v, "salts");
gs.seed = seed; gs.salts[0] = sl ? (uint32_t)num(sl->items[0]) : 0; gs.salts[1] = sl ? (uint32_t)num(sl->items[1]) : 0;
sb_genome_make(&gs, &g);
}
for(k = 0; k < gj->n; k++){
jval *f = gj->items[k];
const char *key = f->key;
@@ -86,7 +90,7 @@ static void check_case(const jval *c){
n++;
st.seed = hex(json_get(c, "seed"));
st.salts[0] = (uint32_t)num(salts->items[0]); st.salts[1] = (uint32_t)num(salts->items[1]);
p.gen = (int)num(json_get(c, "gen")); p.energy = (int)num(json_get(c, "energy")); p.drift = (int)num(json_get(c, "drift"));
p.gen = (int)num(json_get(c, "gen")); p.keys = (int)num(json_get(c, "keys")); p.energy = (int)num(json_get(c, "energy")); p.drift = (int)num(json_get(c, "drift"));
p.scale = (int)num(json_get(c, "scale")); p.root = (int)num(json_get(c, "root"));
for(b = 0; b < bars->n; b++){
jval *jb = bars->items[b], *ev = json_get(jb, "events");