/* ================= APP (browser only: UI, Web MIDI, scheduler) ================= */ (function(){ var E=Engine, TPB=E.TPB, NT=E.NT, HOLD_MS=250; function store(k,v){ try{ localStorage.setItem('seedbox3.'+k,JSON.stringify(v)); }catch(e){} } function load(k,d){ try{ var v=localStorage.getItem('seedbox3.'+k); return v?JSON.parse(v):d; }catch(e){ return d; } } function rnd32(){ try{ return crypto.getRandomValues(new Uint32Array(1))[0]>>>0; }catch(e){ return (Date.now()*2654435761)>>>0; } } function hex(n){ return ('00000000'+(n>>>0).toString(16).toUpperCase()).slice(-8); } function clamp(x,a,b){ return xb?b:x; } function mod(a,n){ return ((a%n)+n)%n; } function laneTracks(l){ var a=[]; for(var t=0;t=0 && app.keys=-1 && app.scale=-1 && app.root<12)) app.root=-1; if(!Array.isArray(app.on) || app.on.length!==NT){ app.on=[]; for(var i9=0;i916) app.history.pop(); app.state={seed:s.seed>>>0,salts:[s.salts[0]|0,s.salts[1]|0]}; app.histSel=0; save(); dirty=true; } function baseOn(tr){ return !!app.on[tr]; } function sounds(tr,mask){ var b=baseOn(tr,mask); return app.inv[tr]?!b:b; } var flashMsg='', flashMsgUntil=0; function msg(t,ms){ flashMsg=t; flashMsgUntil=performance.now()+(ms||900); } /* ---------- MIDI ---------- */ var midi=null, outs={}, ins={}, inPort=null, statusEl=document.getElementById('midiStatus'); function setStatus(t,cls){ statusEl.textContent=t; statusEl.className='pill'+(cls?' '+cls:''); } function fillSelect(id,list,re){ var sel=document.getElementById(id), want=cfg[id], map={}; sel.innerHTML=''; var none=document.createElement('option'); none.value=''; none.textContent='(none)'; sel.appendChild(none); list.forEach(function(o){ map[o.id]=o; var op=document.createElement('option'); op.value=o.id; op.textContent=o.name; sel.appendChild(op); }); if(!want || !map[want]){ var guess=list.filter(function(o){ return re.test(o.name); })[0]; if(guess){ want=guess.id; cfg[id]=want; } } sel.value=map[want]?want:''; } function fillPorts(){ outs={}; ins={}; var lo=[], li=[]; if(midi){ midi.outputs.forEach(function(o){ outs[o.id]=o; lo.push(o); }); midi.inputs.forEach(function(o){ ins[o.id]=o; li.push(o); }); } fillSelect('portDrums',lo,/404/i); fillSelect('portMono',lo,/minifreak|freak/i); fillSelect('portIn',li,/404/i); hookInput(); if(midi) setStatus('MIDI: '+lo.length+' out, '+li.length+' in', lo.length?'ok':''); } function hookInput(){ if(inPort) inPort.onmidimessage=null; inPort=ins[cfg.portIn]||null; if(inPort) inPort.onmidimessage=function(e){ onMidiIn(e.data); }; } if(navigator.requestMIDIAccess){ navigator.requestMIDIAccess({sysex:false}).then(function(m){ midi=m; m.onstatechange=fillPorts; fillPorts(); }, function(){ setStatus('MIDI blocked in this view. Open the standalone file in Chrome','bad'); fillPorts(); }); } else { setStatus('No Web MIDI here. Use Chrome or Edge on desktop','bad'); fillPorts(); } /* Pad learn: LEARN page, push, press a kit track, then hit the pad on the sampler. Stores the exact channel + note received. */ function onMidiIn(d){ if(!d || d.length<3) return; if((d[0]&0xF0)!==0x90 || d[2]===0) return; if(app.learn===2 && app.learnTr>=0){ cfg.kit.learn[app.learnTr]={ch:d[0]&0x0F,note:d[1]}; saveCfg(); refreshPadFields(); applyOn(KIT[app.learnTr],true); dirty=true; // a freshly learned track starts on msg('N'+d[1]+' CH'+((d[0]&15)+1)+' OK',1400); app.learn=0; app.learnTr=-1; app.editing=false; } } function portFor(lane){ return outs[lane===1?cfg.portMono:cfg.portDrums]||null; } /* A learned pad plays on exactly the channel and note it was learned with. */ function learned(tr){ var L=cfg.kit.learn[tr]; return L?{ch:L.ch&15,note:L.note&127}:null; } /* Kit tracks play only once learned; an unlearned track stays silent. */ function chFor(ev){ if(ev.lane===1) return clamp(cfg.mono.ch|0,1,16)-1; var L=learned(ev.tr); return L?L.ch:0; } function noteFor(ev){ if(ev.lane===1) return clamp(ev.v+12*(cfg.mono.oct|0),0,127); var L=learned(ev.tr); return L?L.note:null; } function kitChannels(){ var c={}; KIT.forEach(function(t){ var L=learned(t); if(L) c[L.ch]=1; }); return Object.keys(c).map(Number); } /* ---------- scheduler (look-ahead, timestamped sends) ---------- */ var LOOK=110, S={running:false,anchorTime:0,anchorTick:0,nextBarTick:0,bar:0,nextClock:0,queue:[],sounding:{},timer:null,log:{},mask:0}, flashes=[]; function msPerTick(){ return 60000/(app.bpm*TPB); } function timeOf(t){ return S.anchorTime+(t-S.anchorTick)*msPerTick(); } function tickAt(ms){ return S.anchorTick+(ms-S.anchorTime)/msPerTick(); } function clockPorts(){ var p=[], a=outs[cfg.portDrums], b=outs[cfg.portMono]; if(a) p.push(a); if(b && b!==a) p.push(b); return p; } function send(port,bytes,ts){ if(!port) return; try{ port.send(bytes,ts); }catch(e){} } function start(){ if(S.running) return; S.running=true; S.anchorTime=performance.now()+60; S.anchorTick=0; S.nextBarTick=0; S.bar=0; S.nextClock=0; S.queue=[]; S.sounding={}; S.log={}; S.mask=0; if(cfg.clock) clockPorts().forEach(function(p){ send(p,[0xFA],S.anchorTime); }); S.timer=setInterval(pump,15); pump(); runBtn.setAttribute('aria-pressed','true'); } function stop(){ if(!S.running) return; clearInterval(S.timer); S.running=false; Object.keys(outs).forEach(function(id){ try{ outs[id].clear(); }catch(e){} }); Object.keys(S.sounding).forEach(function(k){ var s=S.sounding[k]; send(s.port,[0x80|s.ch,s.note,0]); }); var pd=portFor(0), pm=portFor(1); if(pd) kitChannels().forEach(function(c){ send(pd,[0xB0|c,120,0]); }); // samplers that ignore All Notes Off still obey All Sound Off (120) if(pm){ var mc=clamp(cfg.mono.ch|0,1,16)-1; send(pm,[0xB0|mc,120,0]); send(pm,[0xB0|mc,123,0]); } if(cfg.clock) clockPorts().forEach(function(p){ send(p,[0xFC]); }); S.queue=[]; S.sounding={}; for(var t=0;tnow && L.start0) 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,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; } /* ---------- device controls ---------- */ var runBtn=(function(){ var b=document.createElement('button'); b.className='toggle'; b.id='run'; b.type='button'; b.setAttribute('aria-pressed','false'); b.setAttribute('aria-label','Run / stop'); b.innerHTML='RunStop'; return b; })(), big=document.getElementById('big'); runBtn.addEventListener('click',function(){ S.running?stop():start(); }); var seedHeld=false, comboUsed=false; big.addEventListener('pointerdown',function(e){ if(big.setPointerCapture) big.setPointerCapture(e.pointerId); seedHeld=true; comboUsed=false; big.classList.add('down'); }); big.addEventListener('pointerup',function(){ big.classList.remove('down'); if(seedHeld && !comboUsed) newSeed(); seedHeld=false; }); big.addEventListener('pointercancel',function(){ big.classList.remove('down'); seedHeld=false; }); big.addEventListener('keydown',function(e){ if(e.key==='Enter'){ e.preventDefault(); e.stopPropagation(); newSeed(); } }); /* Track buttons. Tap: on <-> off. Hold past 250 ms: flip while held. In LEARN a kit button selects the track to learn instead. */ /* Panel grid, same order on the screen map: row 1 KICK KICK2 SNARE [RUN toggle], row 2 HAT HAT16 PERC GHOST, row 3 BASS MEL A MEL B PAD. -1 = the RUN toggle. In the wiring matrix that slot is empty; GHOST is wired at row 2 / column 4. */ var PANEL=[0,1,2,-1, 4,5,6,3, 7,8,9,10]; var trk=document.getElementById('trk'), tleds=[], tbtns=[], held={}; function addCell(name,onDown,onUp){ var c=document.createElement('div'); c.className='tcell'; var led=document.createElement('span'); led.className='tled'; var b=document.createElement('button'); b.className='tbtn'; b.type='button'; b.setAttribute('aria-label',name); var n=document.createElement('span'); n.className='tname'; n.textContent=name; c.appendChild(led); c.appendChild(b); c.appendChild(n); trk.appendChild(c); b.addEventListener('pointerdown',function(e){ if(b.setPointerCapture) b.setPointerCapture(e.pointerId); b.classList.add('down'); onDown(e); }); b.addEventListener('pointerup',function(){ b.classList.remove('down'); onUp(); }); b.addEventListener('pointercancel',function(){ b.classList.remove('down'); onUp(); }); b.addEventListener('keydown',function(e){ if((e.key==='Enter'||e.key===' ') && !e.repeat){ e.preventDefault(); e.stopPropagation(); b.classList.add('down'); onDown(e); } }); b.addEventListener('keyup',function(e){ if(e.key==='Enter'||e.key===' '){ e.preventDefault(); b.classList.remove('down'); onUp(); } }); return {led:led,btn:b}; } function trackDown(t,e){ var pg=PAGES[app.page]; if(app.learn===1 || app.learn===2){ if(E.TRACKS[t].lane!==0){ msg('KIT ONLY',700); return; } app.learnTr=KIT.indexOf(t); app.learn=2; return; } held[t]={t0:performance.now(),timer:setTimeout(function(){ if(held[t]){ held[t].long=true; setInv(t,true); } },HOLD_MS)}; } function trackUp(t){ var h=held[t]; if(!h) return; clearTimeout(h.timer); delete held[t]; if(h.long) setInv(t,false); else setOn(t,!targetOn(t)); } PANEL.forEach(function(t){ if(t<0){ var rc=document.createElement('div'); rc.className='tcell runcell'; rc.appendChild(runBtn); trk.appendChild(rc); return; } // the RUN toggle sits in the grid, outside the matrix var c=addCell(E.TRACKS[t].name,function(e){ trackDown(t,e); },function(){ trackUp(t); }); tleds[t]=c.led; tbtns[t]=c.btn; }); function knob(el,valEl,get,set){ function draw(){ var v=get(); el.style.setProperty('--rot',(-135+270*v/127)+'deg'); el.setAttribute('aria-valuenow',v); valEl.textContent=v; } var y0=0,v0=0; el.addEventListener('pointerdown',function(e){ el.setPointerCapture(e.pointerId); y0=e.clientY; v0=get(); }); el.addEventListener('pointermove',function(e){ if(!el.hasPointerCapture(e.pointerId)) return; set(clamp(Math.round(v0+(y0-e.clientY)/1.5),0,127)); draw(); }); el.addEventListener('wheel',function(e){ e.preventDefault(); set(clamp(get()+(e.deltaY<0?2:-2),0,127)); draw(); },{passive:false}); el.addEventListener('keydown',function(e){ var d=(e.key==='ArrowUp'||e.key==='ArrowRight')?1:(e.key==='ArrowDown'||e.key==='ArrowLeft')?-1:0; if(d){ e.preventDefault(); e.stopPropagation(); set(clamp(get()+d*(e.shiftKey?8:2),0,127)); draw(); } }); draw(); } knob(document.getElementById('kEnergy'),document.getElementById('vEnergy'),function(){return app.energy;},function(v){ app.energy=v; save(); dirty=true; }); knob(document.getElementById('kDrift'),document.getElementById('vDrift'),function(){return app.drift;},function(v){ app.drift=v; save(); dirty=true; }); var enc=document.getElementById('enc'), encDot=enc.querySelector('i'), encRot=0; function encStep(d){ encRot+=d*18; encDot.style.setProperty('--rot',encRot+'deg'); encTurn(d); } (function(){ var x0=0, acc=0, moved=false, t0=0; enc.addEventListener('pointerdown',function(e){ enc.setPointerCapture(e.pointerId); x0=e.clientX; acc=0; moved=false; t0=performance.now(); }); enc.addEventListener('pointermove',function(e){ if(!enc.hasPointerCapture(e.pointerId)) return; acc+=e.clientX-x0; x0=e.clientX; while(Math.abs(acc)>=14){ moved=true; var d=acc>0?1:-1; acc-=d*14; encStep(d); } }); enc.addEventListener('pointerup',function(){ if(!moved) encPush(performance.now()-t0>600); }); enc.addEventListener('wheel',function(e){ e.preventDefault(); encStep(e.deltaY>0?1:-1); },{passive:false}); })(); var KEYMAP=['Digit1','Digit2','Digit3','Digit4','Digit5','Digit6','Digit7','Digit8','Digit9','Digit0','Minus']; document.addEventListener('keydown',function(e){ var tg=e.target.tagName; if(tg==='INPUT'||tg==='SELECT'||tg==='TEXTAREA'||tg==='BUTTON' && (e.key==='Enter'||e.key===' ')) return; if(e.repeat && e.key!=='ArrowLeft' && e.key!=='ArrowRight') return; var t=KEYMAP.indexOf(e.code); if(t>=0){ tbtns[t].classList.add('down'); trackDown(t,e); } else if(e.key===' '){ e.preventDefault(); big.classList.add('down'); newSeed(); setTimeout(function(){ big.classList.remove('down'); },90); } else if(e.key==='ArrowLeft'){ e.preventDefault(); encStep(-1); } else if(e.key==='ArrowRight'){ e.preventDefault(); encStep(1); } else if(e.key==='Enter'){ e.preventDefault(); encPush(e.shiftKey); } else if(e.key==='r'||e.key==='R'){ S.running?stop():start(); } }); document.addEventListener('keyup',function(e){ var t=KEYMAP.indexOf(e.code); if(t>=0){ tbtns[t].classList.remove('down'); trackUp(t); } }); /* ---------- bench settings ---------- */ function bind(id,get,set){ var el=document.getElementById(id); el.value=get(); el.addEventListener('change',function(){ set(el.value); saveCfg(); dirty=true; }); return el; } bind('monoCh',function(){return cfg.mono.ch;},function(v){cfg.mono.ch=clamp(v|0,1,16);}); bind('monoOct',function(){return cfg.mono.oct;},function(v){cfg.mono.oct=clamp(v|0,-3,3);}); ['portDrums','portMono'].forEach(function(id){ document.getElementById(id).addEventListener('change',function(e){ cfg[id]=e.target.value; saveCfg(); }); }); document.getElementById('portIn').addEventListener('change',function(e){ cfg.portIn=e.target.value; saveCfg(); hookInput(); }); function applyHw(){ trk.classList.toggle('noled',!cfg.hw.leds); var D=DISPLAYS[cfg.hw.display]||DISPLAYS['5110'], w=document.getElementById('hwNote'); w.textContent = D.h===32 ? (cfg.hw.leds ? 'This screen only fits the menu (two big lines). Track status is on the LEDs.' : 'This screen only fits the menu. Without LEDs there is no way to see which tracks are on.') : 'Menu on top, a 4 × 3 track map underneath laid out like the buttons, so the LEDs are optional.'; } bind('hwDisplay',function(){ return cfg.hw.display; },function(v){ cfg.hw.display=DISPLAYS[v]?v:'5110'; applyHw(); }); (function(){ var el=document.getElementById('hwLeds'); el.checked=!!cfg.hw.leds; el.addEventListener('change',function(){ cfg.hw.leds=el.checked; saveCfg(); applyHw(); }); })(); var clk=document.getElementById('sendClock'); clk.checked=!!cfg.clock; clk.addEventListener('change',function(){ cfg.clock=clk.checked; saveCfg(); }); var padNotes=[]; (function(){ var box=document.getElementById('padFields'); KIT.forEach(function(t,k){ var f=document.createElement('div'); f.className='field'; var lab=document.createElement('span'); lab.className='lbl'; lab.textContent=E.TRACKS[t].name; var nt=document.createElement('span'); nt.className='val'; f.appendChild(lab); f.appendChild(nt); box.appendChild(f); padNotes[k]=nt; }); })(); function refreshPadFields(){ KIT.forEach(function(t,k){ var L=learned(t); padNotes[k].textContent = L ? 'ch '+(L.ch+1)+' · note '+L.note : 'not learned'; padNotes[k].classList.toggle('miss',!L); }); } document.getElementById('clearLearn').addEventListener('click',function(){ cfg.kit.learn=[null,null,null,null,null,null,null]; saveCfg(); refreshPadFields(); }); refreshPadFields(); document.getElementById('vecGo').addEventListener('click',function(){ var n=clamp(document.getElementById('vecBars').value|0,1,64), rows=[], bars=[], p=params(false), mask=0; for(var b=0;b':[0,65,34,20,8],'?':[2,1,81,9,6],'!':[0,0,95,0,0], '0':[62,81,73,69,62],'1':[0,66,127,64,0],'2':[66,97,81,73,70],'3':[33,65,69,75,49],'4':[24,20,18,127,16],'5':[39,69,69,69,57],'6':[60,74,73,73,48],'7':[1,113,9,5,3],'8':[54,73,73,73,54],'9':[6,73,73,41,30], 'A':[126,17,17,17,126],'B':[127,73,73,73,54],'C':[62,65,65,65,34],'D':[127,65,65,34,28],'E':[127,73,73,73,65],'F':[127,9,9,9,1],'G':[62,65,73,73,122],'H':[127,8,8,8,127],'I':[0,65,127,65,0],'J':[32,64,65,63,1], 'K':[127,8,20,34,65],'L':[127,64,64,64,64],'M':[127,2,12,2,127],'N':[127,4,8,16,127],'O':[62,65,65,65,62],'P':[127,9,9,9,6],'Q':[62,65,81,33,94],'R':[127,9,25,41,70],'S':[70,73,73,73,49],'T':[1,1,127,1,1], 'U':[63,64,64,64,63],'V':[31,32,64,32,31],'W':[63,64,56,64,63],'X':[99,20,8,20,99],'Y':[7,8,112,8,7],'Z':[97,81,73,69,67]}; var oled=document.getElementById('oled'), octx=oled.getContext('2d'), zoom=document.getElementById('oledZoom'), zctx=zoom.getContext('2d'); var FB_W=128, fb=new Uint8Array(128*64), fbH=32; function fitText(s,x,y,maxW,v){ if(textW(s,2)<=maxW) text(s,x,y,2,v); else text(s,x,y+4,1,v); } function px(x,y,v){ if(x>=0&&x=0&&y>row)&1) fillR(x+col*sc,y+row*sc,sc,sc,v); x+=(sc===1?6:12); } } function textW(s,sc){ return String(s).length*(sc===1?6:12)-(sc===1?1:2); } 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='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,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?'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()>1)-1,y+(h>>1)-1,2,2,1); } function cellState(t,now,mask,blink){ if(app.learn>0) return E.TRACKS[t].lane!==0 ? 0 : (app.learn===2 && KIT[app.learnTr]===t) ? (blink?4:3) : 3; if(E.TRACKS[t].lane===0 && !cfg.kit.learn[t]) return 3; // a dot: this kit track still needs learning var on=sounds(t,mask); if(pending[t] && blink) on=!on; return on ? (now=0) for(i=0;i<4;i++){ var nx=64+i*5; if(i===curBar%4) fillR(nx,1,4,5,1); else boxR(nx,1,4,5); } if(app.editing){ fillR(0,8,84,10,1); text(s.value,1,9,1,0); } else text(s.value,1,9,1,1); drawMap(1,20,19,8,2,2); } else if(D.h===32){ text(s.label,2,0,2,1); if(app.editing){ fillR(0,16,128,16,1); text(s.value,2,17,2,0); } else text(s.value,2,17,2,1); } else { text(s.label,1,0,1,1); if(S.running && curBar>=0) for(i=0;i<4;i++){ var bx=104+i*6; if(i===curBar%4) fillR(bx,1,5,5,1); else boxR(bx,1,5,5); } if(app.editing){ fillR(0,9,128,18,1); text(s.value,2,11,2,0); } else text(s.value,2,11,2,1); drawMap(1,29,30,10,2,2); } var img=octx.createImageData(FB_W,fbH), d=img.data, ON=D.lcd?[28,40,38]:[214,236,255], OFF=D.lcd?[150,176,160]:[3,6,10]; for(i=0;i0){ if(E.TRACKS[t].lane!==0) col='var(--bezel)'; else if(app.learn===2 && KIT[app.learnTr]===t){ col=blink?'#FFB547':'var(--bezel)'; glow=blink; } else { col=slow?'#FFB547':'var(--bezel)'; } } else if(E.TRACKS[t].lane===0 && !cfg.kit.learn[t]){ col=rgba(lane,0.15); } else { if(on){ col=now=7?1.4:1)}); var unit=(H-24)/rows.reduce(function(a,r){ return a+r.h; },0); rows.forEach(function(r){ r.y=y; r.hh=r.h*unit; y+=r.hh; }); var gb=y; gctx.font='500 10px '+T.font; gctx.textBaseline='middle'; for(i=0;i=0){ var px=Math.round(X(curTick))+.5; gctx.strokeStyle=T.accent; gctx.lineWidth=2; gctx.beginPath(); gctx.moveTo(px,2); gctx.lineTo(px,gb); gctx.stroke(); } } var factsEl=document.getElementById('facts'), posEl=document.getElementById('pos'); function frame(){ var now=performance.now(), curBar=-1, curTick=-1, phrase=0, mask, T=tokens(); if(S.running){ var tk=tickAt(now); for(var b in S.log){ var L=S.log[b]; if(tk>=L.start && tk=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+'|'+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,app.state.salts),params(false)), ds=E.describe(app.state,params(false)); factsEl.innerHTML='Seed '+hex(app.state.seed)+'Kit '+E.GEN_NAMES[app.gen].toLowerCase()+'Keys '+E.KEYS_NAMES[app.keys].toLowerCase()+'Bar '+(app.gen===4?16:g.steps)+' stepsKey '+E.NOTE_NAMES[g.root]+' '+E.SCALE_NAMES[g.scale].toLowerCase()+(app.scale<0||app.root<0?' (auto)':'')+''+(ds?''+ds+'':''); } var on=0; for(var t=0;t=0 ? 'Phrase '+(phrase+1)+'Bar '+(curBar%4+1)+'/4' : 'Stopped')+ 'Tracks on '+on+'/'+NT+''+app.bpm+' BPM'; while(flashes.length && flashes[0].time<=now){ hitUntil[flashes[0].tr]=now+80; flashes.shift(); } drawLeds(now,mask,T); drawGrid(curTick,mask,T); drawOled(curBar,mask); requestAnimationFrame(frame); } applyHw(); requestAnimationFrame(frame); window.addEventListener('beforeunload',function(){ stop(); }); })();