Split kit and keys; KEYS GEN with MOTIF, 303, TURING, ARP
Some checks failed
check / check (push) Has been cancelled
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:
@@ -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];
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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++){
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user