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:
31
sim/app.js
31
sim/app.js
@@ -18,16 +18,17 @@
|
||||
if(!Array.isArray(cfg.kit.learn) || cfg.kit.learn.length<7) cfg.kit.learn=[null,null,null,null,null,null,null];
|
||||
function saveCfg(){ store('cfg',cfg); }
|
||||
|
||||
var app={ state:load('state',null)||{seed:0x5EEDB0C5,salts:[0,0]}, gen:load('gen',0), scale:load('scale',-1), root:load('root',-1), energy:load('energy',64), drift:load('drift',32), bpm:load('bpm',165),
|
||||
var app={ state:load('state',null)||{seed:0x5EEDB0C5,salts:[0,0]}, gen:load('gen',0), keys:load('keys',0), scale:load('scale',-1), root:load('root',-1), energy:load('energy',64), drift:load('drift',32), bpm:load('bpm',165),
|
||||
on:load('on',null), inv:[], fill:false, history:[], slots:load('slots',[null,null,null,null,null,null,null,null]),
|
||||
page:0, editing:false, histSel:0, slotSel:0, rollSel:0, learn:0, learnTr:-1 };
|
||||
if(!(app.keys>=0 && app.keys<E.KEYS_NAMES.length)) app.keys=0;
|
||||
if(!(app.scale>=-1 && app.scale<E.SCALE_NAMES.length)) app.scale=-1;
|
||||
if(!(app.root>=-1 && app.root<12)) app.root=-1;
|
||||
if(!Array.isArray(app.on) || app.on.length!==NT){ app.on=[]; for(var i9=0;i9<NT;i9++) app.on.push(true); }
|
||||
for(var i0=0;i0<NT;i0++) app.inv.push(false); // inv: flipped while a button is held
|
||||
var PAGES=['SEED','GEN','ROOT','SCALE','BPM','SLOT','ROLL','LEARN'];
|
||||
function params(fillNow){ return {gen:app.gen,energy:app.energy,drift:app.drift,fill:!!fillNow,scale:app.scale,root:app.root}; }
|
||||
function save(){ store('state',app.state); store('gen',app.gen); store('scale',app.scale); store('root',app.root); store('energy',app.energy); store('drift',app.drift); store('bpm',app.bpm); }
|
||||
var PAGES=['SEED','GEN','KEYS','ROOT','SCALE','BPM','SLOT','ROLL','LEARN'];
|
||||
function params(fillNow){ return {gen:app.gen,keys:app.keys,energy:app.energy,drift:app.drift,fill:!!fillNow,scale:app.scale,root:app.root}; }
|
||||
function save(){ store('state',app.state); store('gen',app.gen); store('keys',app.keys); store('scale',app.scale); store('root',app.root); store('energy',app.energy); store('drift',app.drift); store('bpm',app.bpm); }
|
||||
function setState(s){
|
||||
app.history.unshift({seed:app.state.seed,salts:app.state.salts.slice()}); if(app.history.length>16) app.history.pop();
|
||||
app.state={seed:s.seed>>>0,salts:[s.salts[0]|0,s.salts[1]|0]}; app.histSel=0; save(); dirty=true;
|
||||
@@ -150,12 +151,13 @@
|
||||
|
||||
/* ---------- actions ---------- */
|
||||
function newSeed(){ setState({seed:rnd32(),salts:[0,0]}); }
|
||||
function reroll(l){ var s={seed:app.state.seed,salts:app.state.salts.slice()}; s.salts[l]=(s.salts[l]+1)&0xFFFF; setState(s); msg(l?'NEW LINE':'NEW KIT',700); }
|
||||
function reroll(l){ var s={seed:app.state.seed,salts:app.state.salts.slice()}; s.salts[l]=(s.salts[l]+1)&0xFFFF; setState(s); msg(l?'NEW KEYS':'NEW KIT',700); }
|
||||
function encTurn(d){
|
||||
if(!app.editing){ app.page=(app.page+d+PAGES.length)%PAGES.length; app.learn=0; app.learnTr=-1; return; }
|
||||
var pg=PAGES[app.page];
|
||||
if(pg==='SEED') app.histSel=clamp(app.histSel+d,0,app.history.length);
|
||||
else if(pg==='GEN'){ app.gen=(app.gen+d+5)%5; save(); dirty=true; }
|
||||
else if(pg==='KEYS'){ app.keys=mod(app.keys+d,E.KEYS_NAMES.length); save(); dirty=true; }
|
||||
else if(pg==='ROOT'){ app.root=mod(app.root+1+d,13)-1; save(); dirty=true; } // AUTO, C .. B
|
||||
else if(pg==='SCALE'){ var ns=E.SCALE_NAMES.length+1; app.scale=mod(app.scale+1+d,ns)-1; save(); dirty=true; } // AUTO, MAJOR .. LOCRIAN
|
||||
else if(pg==='BPM') setBpm(app.bpm+d);
|
||||
@@ -172,8 +174,8 @@
|
||||
if(!app.editing){ app.editing=true; return; }
|
||||
if(pg==='SEED' && app.histSel>0) setState(app.history[app.histSel-1]);
|
||||
if(pg==='SLOT'){
|
||||
if(hold){ app.slots[app.slotSel]={seed:app.state.seed,salts:app.state.salts.slice(),gen:app.gen}; store('slots',app.slots); msg('SAVED S'+(app.slotSel+1)); return; }
|
||||
var sl=app.slots[app.slotSel]; if(sl){ app.gen=sl.gen|0; setState(sl); }
|
||||
if(hold){ app.slots[app.slotSel]={seed:app.state.seed,salts:app.state.salts.slice(),gen:app.gen,keys:app.keys}; store('slots',app.slots); msg('SAVED S'+(app.slotSel+1)); return; }
|
||||
var sl=app.slots[app.slotSel]; if(sl){ app.gen=sl.gen|0; app.keys=(sl.keys|0)%E.KEYS_NAMES.length; save(); setState(sl); }
|
||||
}
|
||||
if(pg==='ROLL') reroll(app.rollSel); // then back to page select
|
||||
app.editing=false;
|
||||
@@ -299,7 +301,7 @@
|
||||
var ev=r.events.map(function(e){ return [e.t,e.lane,e.v,e.vel,e.len,e.tr]; });
|
||||
bars.push({bar:b,fill:r.fill,events:ev}); rows=rows.concat(ev);
|
||||
}
|
||||
document.getElementById('vecOut').value=JSON.stringify({seed:'0x'+hex(app.state.seed),salts:app.state.salts,params:{gen:app.gen,energy:app.energy,drift:app.drift},
|
||||
document.getElementById('vecOut').value=JSON.stringify({seed:'0x'+hex(app.state.seed),salts:app.state.salts,params:{gen:app.gen,keys:app.keys,energy:app.energy,drift:app.drift},
|
||||
checksum:'0x'+hex(E.checksum(rows)),format:'[tick,lane,voice,vel,len,track]; all tracks are listed, on/off is applied at output',bars:bars});
|
||||
});
|
||||
document.getElementById('vecCopy').addEventListener('click',function(){
|
||||
@@ -339,13 +341,14 @@
|
||||
function screenText(){
|
||||
var pg=PAGES[app.page], label='', value='';
|
||||
if(pg==='SEED'){ var sh=app.histSel>0?app.history[app.histSel-1]:app.state; label='SEED'+(app.histSel>0?' -'+app.histSel:'')+((sh.salts[0]||sh.salts[1])?' *':''); value=hex(sh.seed); }
|
||||
else if(pg==='GEN'){ label='GENERATOR'; value=E.GEN_NAMES[app.gen]; }
|
||||
else if(pg==='GEN'){ label='KIT GEN'; value=E.GEN_NAMES[app.gen]; }
|
||||
else if(pg==='KEYS'){ label='KEYS GEN'; value=E.KEYS_NAMES[app.keys]; }
|
||||
else if(pg==='ROOT' || pg==='SCALE'){ // on AUTO, show the seed's own pick
|
||||
var sc=pg==='SCALE', x=sc?app.scale:app.root, gk=E.genome(app.state.seed);
|
||||
var sc=pg==='SCALE', x=sc?app.scale:app.root, gk=E.genome(app.state.seed,app.state.salts);
|
||||
label=(sc?'SCALE':'ROOT')+(x<0?' AUTO':''); if(x<0) x=sc?gk.scale:gk.root; value=sc?E.SCALE_NAMES[x]:E.NOTE_NAMES[x]; }
|
||||
else if(pg==='BPM'){ label='TEMPO'; value=app.bpm+' BPM'; }
|
||||
else if(pg==='SLOT'){ label='SLOT '+(app.slotSel+1); value=app.slots[app.slotSel]?hex(app.slots[app.slotSel].seed):'EMPTY'; }
|
||||
else if(pg==='ROLL'){ label='RE-ROLL'; value=app.rollSel?'LINE':'KIT'; }
|
||||
else if(pg==='ROLL'){ label='RE-ROLL'; value=app.rollSel?'KEYS':'KIT'; }
|
||||
else if(pg==='LEARN'){ label= app.learn===2 ? 'LEARN '+E.TRACKS[KIT[app.learnTr]].name.replace(' ','') : 'LEARN';
|
||||
value= app.learn===0 ? 'PUSH' : app.learn===1 ? 'PICK TRACK' : (inPort?'HIT PAD':'NO INPUT'); }
|
||||
if(performance.now()<flashMsgUntil) value=flashMsg;
|
||||
@@ -465,11 +468,11 @@
|
||||
if(curBar>=0){ var first=S.log[phrase*4]; if(first) curTick=tk-first.start; }
|
||||
}
|
||||
mask=curMask();
|
||||
var key=phrase+'|'+app.state.seed+'|'+app.state.salts.join('.')+'|'+app.gen+'|'+S.running;
|
||||
var key=phrase+'|'+app.state.seed+'|'+app.state.salts.join('.')+'|'+app.gen+'|'+app.keys+'|'+app.scale+'|'+app.root+'|'+S.running;
|
||||
if(dirty || key!==viewKey || (S.running && view && view.bars.some(function(b,i){ return S.log[phrase*4+i] && S.log[phrase*4+i].r!==b; }))){
|
||||
view={bars:phraseBars(phrase)}; viewKey=key; dirty=false;
|
||||
var g=E.applyKey(E.genome(app.state.seed),params(false)), ds=E.describe(app.state,params(false));
|
||||
factsEl.innerHTML='<span>Seed <b>'+hex(app.state.seed)+'</b></span><span>Generator <b>'+E.GEN_NAMES[app.gen].toLowerCase()+'</b></span><span>Bar <b>'+(app.gen===4?16:g.steps)+' steps</b></span><span>Key <b>'+E.NOTE_NAMES[g.root]+' '+E.SCALE_NAMES[g.scale].toLowerCase()+(app.scale<0||app.root<0?' (auto)':'')+'</b></span>'+(ds?'<span><b>'+ds+'</b></span>':'');
|
||||
var g=E.applyKey(E.genome(app.state.seed,app.state.salts),params(false)), ds=E.describe(app.state,params(false));
|
||||
factsEl.innerHTML='<span>Seed <b>'+hex(app.state.seed)+'</b></span><span>Kit <b>'+E.GEN_NAMES[app.gen].toLowerCase()+'</b></span><span>Keys <b>'+E.KEYS_NAMES[app.keys].toLowerCase()+'</b></span><span>Bar <b>'+(app.gen===4?16:g.steps)+' steps</b></span><span>Key <b>'+E.NOTE_NAMES[g.root]+' '+E.SCALE_NAMES[g.scale].toLowerCase()+(app.scale<0||app.root<0?' (auto)':'')+'</b></span>'+(ds?'<span><b>'+ds+'</b></span>':'');
|
||||
}
|
||||
var on=0; for(var t=0;t<NT;t++) if(sounds(t,mask)) on++;
|
||||
posEl.innerHTML=(S.running && curBar>=0 ? '<span>Phrase <b>'+(phrase+1)+'</b></span><span>Bar <b>'+(curBar%4+1)+'/4</b></span>' : '<span>Stopped</span>')+
|
||||
|
||||
Reference in New Issue
Block a user