Seedbox v0.1 + firmware, generic kit/synth, ROOT/SCALE pages
Some checks failed
check / check (push) Has been cancelled

First commit of the repo. Contents beyond the v0.1 simulator and layout:

- Full Pico 2 W firmware (firmware/app, drivers, engine port, host test).
- BANK page and 404 mode A/B removed; learned pads play exactly what was learned.
- 404/MiniFreak renamed to KIT/SYNTH throughout (jacks, code, sim, docs,
  layout labels; wiring rebuilt).
- ROLL returns to page select after a re-roll; a freshly learned track turns on.
- LCD contrast 66.
- ROOT and SCALE pages: 21 scales, AUTO = the seed picks; global and sticky.

Intended music change: the scale table grew from 8 to 21 and AUTO picks from
all of them, so every seed's scale changed. Vectors regenerated with
npm run vectors, plus new key-override cases. Flash record is now version 2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-11 20:39:36 +03:00
commit 2196633ed5
77 changed files with 52207 additions and 0 deletions

34
test/gen-vectors.js Normal file
View File

@@ -0,0 +1,34 @@
/* Regenerates test/vectors/*.json from sim/engine.js.
Only run this when you MEAN to change the music. Otherwise run check-vectors.js. */
var fs=require('fs'), path=require('path');
var E=require('../sim/engine.js'), C=require('./cases.js'), R=require('./render.js');
var dir=path.join(__dirname,'vectors');
var FORMAT='events: [tick, lane, voice, vel, len, track]. lane 0 = kit (voice 0 kick, 1 snare, 2 hat, 3 perc), lane 1 = synth (voice = MIDI pitch). scale/root: -1 = AUTO (the seed picks). Every track is listed; on/off is applied at output. Ticks: 840 per quarter, 210 per 16th.';
// 1. primitives: hash, RNG, genome. Port these first.
var mixV=[], pairs=[[0,0],[1,0],[0,1],[0x5EEDB0C5,0x46494C],[0xFFFFFFFF,0xFFFFFFFF],[0xDEADBEEF,-1],[12345,0x4E55+7]];
pairs.forEach(function(p){ mixV.push({a:R.hex(p[0]>>>0),b:p[1]|0,out:R.hex(E.mix(p[0]>>>0,p[1]))}); });
var rngV=[]; [0,1,0x5EEDB0C5,0xDEADBEEF].forEach(function(s){ var r=new E.Rng(s), seq=[], below=[], ch=[];
for(var i=0;i<16;i++) seq.push(R.hex(r.next()));
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');
// 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){
return {bar:b.bar,sec:b.sec,fill:b.fill,barTicks:b.barTicks,events:b.events}; })});
index.push({id:id,checksum:out.checksum});
});
Object.keys(byGen).forEach(function(g){
var s=JSON.stringify({generator:E.GEN_NAMES[g],format:FORMAT,cases:byGen[g]});
s=s.replace(/\],\[/g,'],\n['); // one event per line keeps git diffs readable
fs.writeFileSync(path.join(dir,'gen'+g+'-'+E.GEN_NAMES[g].toLowerCase()+'.json'),s+'\n');
});
fs.writeFileSync(path.join(dir,'index.json'),JSON.stringify({bars:C.BARS,cases:index},null,1)+'\n');
console.log('wrote',index.length,'cases');