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

@@ -1,21 +1,28 @@
/* The golden-vector case list. Shared by gen-vectors.js and check-vectors.js. */
var SEEDS=[0x5EEDB0C5,0x00000001,0xDEADBEEF,0x12345678,0xC0FFEE00,0x0BADF00D];
var SETS=[[64,32],[0,0],[127,0],[127,127],[100,90],[40,64]]; // [energy, drift]
/* The golden-vector case list. Shared by gen-vectors.js and check-vectors.js.
keys = KEYS GEN (0 MOTIF, 1 303, 2 TURING, 3 ARP); scale/root -1 = AUTO; bars (optional) overrides BARS. */
var SEEDS=[0x5EEDB0C5,0x00000001,0xDEADBEEF,0x00009AAB]; // bars of 14, 16, 12 and 20 steps
var SETS=[[64,32],[0,0],[127,127]]; // [energy, drift]
var BARS=8;
function base(seed,gen,keys,e,d){ return {seed:seed,salts:[0,0],gen:gen,keys:keys,energy:e,drift:d,fillBars:[],scale:-1,root:-1}; }
function cases(){
var out=[];
SEEDS.forEach(function(seed){ for(var gen=0;gen<5;gen++) SETS.forEach(function(ed){
out.push({seed:seed,salts:[0,0],gen:gen,energy:ed[0],drift:ed[1],fillBars:[],scale:-1,root:-1});
// every kit generator against every keys generator
SEEDS.forEach(function(seed){ for(var gen=0;gen<5;gen++) for(var keys=0;keys<4;keys++) SETS.forEach(function(ed){
out.push(base(seed,gen,keys,ed[0],ed[1]));
}); });
// salts (re-rolling one half of the seed) and FILL
[0,2,4].forEach(function(gen){
out.push({seed:0x5EEDB0C5,salts:[3,0],gen:gen,energy:64,drift:32,fillBars:[],scale:-1,root:-1});
out.push({seed:0x5EEDB0C5,salts:[0,5],gen:gen,energy:64,drift:32,fillBars:[],scale:-1,root:-1});
out.push({seed:0x5EEDB0C5,salts:[0,0],gen:gen,energy:80,drift:50,fillBars:[3,7],scale:-1,root:-1});
});
// ROOT / SCALE pages: [seed, gen, scale, root]; -1 = AUTO. Covers 5-, 6-, 7- and 8-note scales and each override alone.
[[0x5EEDB0C5,0,14,2],[0x5EEDB0C5,2,19,9],[0x5EEDB0C5,4,0,7],[0xDEADBEEF,1,18,-1],[0x12345678,3,-1,5],[0xC0FFEE00,0,7,11],[0x00000001,1,20,0]].forEach(function(k){
out.push({seed:k[0],salts:[0,0],gen:k[1],energy:64,drift:32,fillBars:[3],scale:k[2],root:k[3]});
// ROLL (salts) and FILL, per keys generator
for(var keys=0;keys<4;keys++){
var a=base(0x5EEDB0C5,0,keys,64,32); a.salts=[3,0]; out.push(a);
var b=base(0x5EEDB0C5,2,keys,64,32); b.salts=[0,5]; out.push(b);
var c=base(0x5EEDB0C5,keys===3?4:1,keys,80,50); c.fillBars=[3,7]; out.push(c);
}
// TURING over more than its 16-bar window: locked at drift 0, flipping at drift 64 and 127
[0,64,127].forEach(function(d){ var t=base(0x12345678,1,2,64,d); t.bars=24; out.push(t); });
// a long 303 and ARP run for the phrase variants
[1,3].forEach(function(keys){ var t=base(0xC0FFEE00,0,keys,90,100); t.bars=16; out.push(t); });
// ROOT / SCALE pages: [seed, gen, keys, scale, root]. Covers 5-, 6-, 7- and 8-note scales and each override alone.
[[0x5EEDB0C5,0,0,14,2],[0x5EEDB0C5,2,3,19,9],[0x5EEDB0C5,4,1,0,7],[0xDEADBEEF,1,2,18,-1],[0x12345678,3,0,-1,5],[0xC0FFEE00,0,1,7,11],[0x00000001,1,3,20,0]].forEach(function(k){
var c=base(k[0],k[1],k[2],64,32); c.fillBars=[3]; c.scale=k[3]; c.root=k[4]; out.push(c);
});
return out;
}

View File

@@ -6,11 +6,11 @@ var dir=path.join(__dirname,'vectors'), fail=0, n=0;
var prim=JSON.parse(fs.readFileSync(path.join(dir,'primitives.json'),'utf8'));
prim.mix.forEach(function(v){ n++; if(R.hex(E.mix(parseInt(v.a,16)>>>0,v.b))!==v.out){ fail++; console.log('mix',v.a,v.b,'differs'); } });
prim.rng.forEach(function(v){ n++; var r=new E.Rng(parseInt(v.seed,16)>>>0); for(var i=0;i<16;i++) if(R.hex(r.next())!==v.next[i]){ fail++; console.log('rng',v.seed,'step',i,'differs'); break; } });
prim.genomes.forEach(function(v){ n++; if(JSON.stringify(E.genome(parseInt(v.seed,16)>>>0))!==JSON.stringify(v.genome)){ fail++; console.log('genome',v.seed,'differs'); } });
prim.genomes.forEach(function(v){ n++; if(JSON.stringify(E.genome(parseInt(v.seed,16)>>>0,v.salts))!==JSON.stringify(v.genome)){ fail++; console.log('genome',v.seed,'differs'); } });
fs.readdirSync(dir).filter(function(f){ return /^gen\d/.test(f); }).sort().forEach(function(f){
JSON.parse(fs.readFileSync(path.join(dir,f),'utf8')).cases.forEach(function(c){
n++;
var got=R.renderCase({seed:parseInt(c.seed,16)>>>0,salts:c.salts,gen:c.gen,energy:c.energy,drift:c.drift,fillBars:c.fillBars,scale:c.scale,root:c.root},c.bars.length);
var got=R.renderCase({seed:parseInt(c.seed,16)>>>0,salts:c.salts,gen:c.gen,keys:c.keys,energy:c.energy,drift:c.drift,fillBars:c.fillBars,scale:c.scale,root:c.root},c.bars.length);
if(got.checksum===c.checksum) return;
fail++;
for(var b=0;b<c.bars.length;b++){ var a=JSON.stringify(c.bars[b].events), g=JSON.stringify(got.bars[b].events);

View File

@@ -13,15 +13,15 @@ var rngV=[]; [0,1,0x5EEDB0C5,0xDEADBEEF].forEach(function(s){ var r=new E.Rng(s)
r=new E.Rng(s); for(i=0;i<16;i++) below.push(r.below(i+1));
r=new E.Rng(s); for(i=0;i<16;i++) ch.push(r.chance(i*4096)?1:0);
rngV.push({seed:R.hex(s>>>0),next:seq,below_1_to_16:below,chance_i_times_4096:ch}); });
var genV=C.cases().filter(function(c,i,a){ return a.findIndex(function(d){ return d.seed===c.seed; })===i; })
.map(function(c){ return {seed:R.hex(c.seed),genome:E.genome(c.seed>>>0)}; });
fs.writeFileSync(path.join(dir,'primitives.json'),JSON.stringify({note:'mix(a,b) hash, xorshift32 Rng (next, below(n), chance(p)) and genome(seed). genome keys are the JS field names.',mix:mixV,rng:rngV,genomes:genV},null,1)+'\n');
var genV=C.cases().filter(function(c,i,a){ return a.findIndex(function(d){ return d.seed===c.seed && d.salts.join()===c.salts.join(); })===i; })
.map(function(c){ return {seed:R.hex(c.seed),salts:c.salts,genome:E.genome(c.seed>>>0,c.salts)}; });
fs.writeFileSync(path.join(dir,'primitives.json'),JSON.stringify({note:'mix(a,b) hash, xorshift32 Rng (next, below(n), chance(p)) and genome(seed, salts). genome keys are the JS field names.',mix:mixV,rng:rngV,genomes:genV},null,1)+'\n');
// 2. full renders, one file per generator
var byGen={}, index=[];
C.cases().forEach(function(c){
var out=R.renderCase(c,C.BARS), id=R.caseId(c);
(byGen[c.gen]=byGen[c.gen]||[]).push({id:id,seed:R.hex(c.seed),salts:c.salts,gen:c.gen,energy:c.energy,drift:c.drift,fillBars:c.fillBars,scale:c.scale,root:c.root,checksum:out.checksum,bars:out.bars.map(function(b){
(byGen[c.gen]=byGen[c.gen]||[]).push({id:id,seed:R.hex(c.seed),salts:c.salts,gen:c.gen,keys:c.keys,energy:c.energy,drift:c.drift,fillBars:c.fillBars,scale:c.scale,root:c.root,checksum:out.checksum,bars:out.bars.map(function(b){
return {bar:b.bar,sec:b.sec,fill:b.fill,barTicks:b.barTicks,events:b.events}; })});
index.push({id:id,checksum:out.checksum});
});

View File

@@ -3,12 +3,13 @@ var E=require('../sim/engine.js');
function hex(n){ return '0x'+('00000000'+(n>>>0).toString(16).toUpperCase()).slice(-8); }
function renderCase(c,bars){
var st={seed:c.seed>>>0,salts:c.salts.slice()}, all=[], out=[];
bars=c.bars||bars;
for(var b=0;b<bars;b++){
var r=E.renderBar(st,{gen:c.gen,energy:c.energy,drift:c.drift,fill:c.fillBars.indexOf(b)>=0,scale:c.scale,root:c.root},b);
var r=E.renderBar(st,{gen:c.gen,keys:c.keys,energy:c.energy,drift:c.drift,fill:c.fillBars.indexOf(b)>=0,scale:c.scale,root:c.root},b);
var ev=r.events.map(function(e){ return [e.t,e.lane,e.v,e.vel,e.len,e.tr]; });
out.push({bar:b,sec:r.sec,fill:!!r.fill,barTicks:r.barTicks,events:ev}); all=all.concat(ev);
}
return {bars:out,checksum:hex(E.checksum(all))};
}
function caseId(c){ return hex(c.seed).slice(2)+'_s'+c.salts.join('-')+'_g'+c.gen+'_e'+c.energy+'_d'+c.drift+(c.fillBars.length?'_f'+c.fillBars.join('-'):'')+(c.scale>=0?'_sc'+c.scale:'')+(c.root>=0?'_r'+c.root:''); }
function caseId(c){ return hex(c.seed).slice(2)+'_s'+c.salts.join('-')+'_g'+c.gen+'_k'+c.keys+'_e'+c.energy+'_d'+c.drift+(c.fillBars.length?'_f'+c.fillBars.join('-'):'')+(c.scale>=0?'_sc'+c.scale:'')+(c.root>=0?'_r'+c.root:'')+(c.bars?'_b'+c.bars:''); }
module.exports={renderCase:renderCase,caseId:caseId,hex:hex};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
{
"note": "mix(a,b) hash, xorshift32 Rng (next, below(n), chance(p)) and genome(seed). genome keys are the JS field names.",
"note": "mix(a,b) hash, xorshift32 Rng (next, below(n), chance(p)) and genome(seed, salts). genome keys are the JS field names.",
"mix": [
{
"a": "0x00000000",
@@ -270,6 +270,10 @@
"genomes": [
{
"seed": "0x5EEDB0C5",
"salts": [
0,
0
],
"genome": {
"steps": 14,
"form": [
@@ -279,36 +283,40 @@
1
],
"formName": "ABAB",
"scale": 18,
"root": 6,
"fillP": 110,
"rot": 1,
"swing": 35,
"arc": 0,
"layback": 0,
"dens": [
81,
37
59,
77
],
"fillP": 47,
"monoLen": 1,
"monoRange": 2,
"rot": 0,
"hocket": 85,
"accentCycle": 3,
"frame": 7,
"freeK": 4,
"freeRot": 3,
"scale": 0,
"root": 5,
"monoLen": 0,
"monoRange": 1,
"hocket": 180,
"bassDeg": 2,
"prog": [
0,
3,
0,
6,
5,
4
],
"swing": 35,
"arc": 1,
"motifLen": 5,
"accentCycle": 3,
"layback": 0,
"frame": 2,
"freeK": 3,
"freeRot": 5
"motifLen": 5
}
},
{
"seed": "0x00000001",
"salts": [
0,
0
],
"genome": {
"steps": 16,
"form": [
@@ -318,36 +326,40 @@
0
],
"formName": "AABA",
"scale": 16,
"root": 2,
"dens": [
80,
95
],
"fillP": 110,
"monoLen": 1,
"monoRange": 2,
"fillP": 102,
"rot": 0,
"swing": 35,
"arc": 2,
"layback": 1,
"dens": [
53,
68
],
"accentCycle": 7,
"frame": 3,
"freeK": 2,
"freeRot": 15,
"scale": 19,
"root": 0,
"monoLen": 2,
"monoRange": 1,
"hocket": 90,
"bassDeg": 3,
"bassDeg": 2,
"prog": [
0,
2,
5,
0,
5
4
],
"swing": 21,
"arc": 1,
"motifLen": 3,
"accentCycle": 3,
"layback": 0,
"frame": 0,
"freeK": 2,
"freeRot": 1
"motifLen": 3
}
},
{
"seed": "0xDEADBEEF",
"salts": [
0,
0
],
"genome": {
"steps": 12,
"form": [
@@ -357,36 +369,169 @@
0
],
"formName": "AAAA",
"scale": 10,
"root": 0,
"fillP": 80,
"rot": 0,
"swing": 50,
"arc": 1,
"layback": 0,
"dens": [
91,
66
78,
78
],
"fillP": 40,
"accentCycle": 5,
"frame": 0,
"freeK": 3,
"freeRot": 14,
"scale": 5,
"root": 1,
"monoLen": 0,
"monoRange": 2,
"rot": 1,
"hocket": 170,
"bassDeg": 4,
"prog": [
0,
1,
0,
3,
5,
6
],
"swing": 50,
"arc": 2,
"motifLen": 4,
"accentCycle": 5,
"motifLen": 5
}
},
{
"seed": "0x00009AAB",
"salts": [
0,
0
],
"genome": {
"steps": 20,
"form": [
0,
1,
0,
1
],
"formName": "ABAB",
"fillP": 60,
"rot": 1,
"swing": 0,
"arc": 0,
"layback": 1,
"dens": [
66,
42
],
"accentCycle": 7,
"frame": 6,
"freeK": 2,
"freeRot": 12,
"scale": 5,
"root": 3,
"monoLen": 0,
"monoRange": 2,
"hocket": 165,
"bassDeg": 2,
"prog": [
0,
6,
5,
4
],
"motifLen": 3
}
},
{
"seed": "0x5EEDB0C5",
"salts": [
3,
0
],
"genome": {
"steps": 14,
"form": [
0,
1,
0,
1
],
"formName": "ABAB",
"fillP": 110,
"rot": 1,
"swing": 35,
"arc": 0,
"layback": 0,
"dens": [
89,
77
],
"accentCycle": 0,
"frame": 1,
"freeK": 3,
"freeRot": 12,
"scale": 0,
"root": 5,
"monoLen": 0,
"monoRange": 1,
"hocket": 180,
"bassDeg": 2,
"prog": [
0,
6,
5,
4
],
"motifLen": 5
}
},
{
"seed": "0x5EEDB0C5",
"salts": [
0,
5
],
"genome": {
"steps": 14,
"form": [
0,
1,
0,
1
],
"formName": "ABAB",
"fillP": 110,
"rot": 1,
"swing": 35,
"arc": 0,
"layback": 0,
"dens": [
59,
33
],
"accentCycle": 3,
"frame": 7,
"freeK": 4,
"freeRot": 3
"freeRot": 3,
"scale": 12,
"root": 11,
"monoLen": 2,
"monoRange": 1,
"hocket": 170,
"bassDeg": 3,
"prog": [
0,
4,
5,
3
],
"motifLen": 5
}
},
{
"seed": "0x12345678",
"salts": [
0,
0
],
"genome": {
"steps": 16,
"form": [
@@ -396,36 +541,40 @@
1
],
"formName": "ABAB",
"scale": 0,
"root": 5,
"fillP": 43,
"rot": 1,
"swing": 0,
"arc": 2,
"layback": 1,
"dens": [
50,
83
49,
72
],
"fillP": 111,
"accentCycle": 5,
"frame": 4,
"freeK": 2,
"freeRot": 5,
"scale": 10,
"root": 2,
"monoLen": 1,
"monoRange": 1,
"rot": 2,
"hocket": 170,
"bassDeg": 3,
"monoRange": 2,
"hocket": 165,
"bassDeg": 2,
"prog": [
0,
0,
6,
5,
4
],
"swing": 0,
"arc": 1,
"motifLen": 5,
"accentCycle": 3,
"layback": 0,
"frame": 1,
"freeK": 3,
"freeRot": 9
"motifLen": 5
}
},
{
"seed": "0xC0FFEE00",
"salts": [
0,
0
],
"genome": {
"steps": 16,
"form": [
@@ -435,71 +584,32 @@
0
],
"formName": "AABA",
"scale": 19,
"root": 0,
"dens": [
42,
74
],
"fillP": 78,
"monoLen": 1,
"monoRange": 2,
"rot": 2,
"hocket": 210,
"bassDeg": 2,
"prog": [
0,
0,
3,
3
],
"swing": 21,
"fillP": 115,
"rot": 0,
"swing": 0,
"arc": 1,
"motifLen": 3,
"accentCycle": 5,
"layback": 0,
"frame": 3,
"freeK": 3,
"freeRot": 12
}
},
{
"seed": "0x0BADF00D",
"genome": {
"steps": 16,
"form": [
0,
0,
1,
0
],
"formName": "AABA",
"scale": 9,
"root": 6,
"dens": [
46,
85
49,
47
],
"fillP": 92,
"monoLen": 1,
"accentCycle": 3,
"frame": 3,
"freeK": 4,
"freeRot": 9,
"scale": 12,
"root": 3,
"monoLen": 0,
"monoRange": 1,
"rot": 1,
"hocket": 90,
"bassDeg": 2,
"hocket": 180,
"bassDeg": 4,
"prog": [
0,
3,
0,
6,
5,
4
],
"swing": 35,
"arc": 2,
"motifLen": 3,
"accentCycle": 3,
"layback": 0,
"frame": 3,
"freeK": 2,
"freeRot": 9
"motifLen": 5
}
}
]