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 */
|
||||
|
||||
Reference in New Issue
Block a user