← Sequencer results

Sequencer

GPT 6 Luna · full

Initial view of Sequencer

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

Imported historical artifact. Its exact original instructions and lineage are not known.

Generation prompt · exact text unavailable

See the current prompt for reference. It may differ from the instructions used to generate this artifact.

Inspect original source 25,338 bytes · SHA-256 f604abcfa2a3
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Pulse / 16-Step Drum Sequencer</title>
<style>
:root{color-scheme:dark;--bg:#101214;--panel:#191d20;--panel2:#202529;--line:#30383d;--muted:#778187;--text:#eef2ef;--lime:#c6fb52;--cyan:#54dfd2;--orange:#ff9c58;--red:#ff5d58;--cell:#292f33;--shadow:0 18px 55px #0007}
*{box-sizing:border-box}body{margin:0;background:radial-gradient(ellipse at 50% -20%,#273034 0,#15191b 45%,#101214 100%);color:var(--text);font:14px/1.4 ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif;min-height:100vh;padding:34px 24px 50px}.shell{max-width:1140px;margin:auto}.topline{display:flex;align-items:center;justify-content:space-between;margin-bottom:24px}.brand{display:flex;gap:13px;align-items:center}.brandmark{width:42px;height:42px;border-radius:12px;background:var(--lime);color:#10140b;display:grid;place-items:center;font-size:21px;font-weight:900;box-shadow:0 0 30px #c6fb5230}.eyebrow{font:700 10px/1.2 ui-monospace,SFMono-Regular,monospace;letter-spacing:.19em;color:var(--lime);text-transform:uppercase}.title{margin:3px 0 0;font-size:20px;letter-spacing:-.04em;font-weight:750}.status{display:flex;align-items:center;gap:8px;color:#929da1;font:11px ui-monospace,monospace;letter-spacing:.08em;text-transform:uppercase}.status-dot{height:7px;width:7px;background:#596268;border-radius:50%}.status.playing{color:var(--lime)}.status.playing .status-dot{background:var(--lime);box-shadow:0 0 12px var(--lime)}
.deck{background:linear-gradient(155deg,#1d2225,#171b1e);border:1px solid #343b3f;border-radius:18px;box-shadow:var(--shadow);overflow:hidden}.transport{padding:23px 25px 21px;display:grid;grid-template-columns:1.2fr 1fr 1.15fr auto;gap:26px;align-items:center;border-bottom:1px solid var(--line)}.transport-block{min-width:0}.label{font:700 10px ui-monospace,monospace;text-transform:uppercase;letter-spacing:.15em;color:#899499;display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}.bpm-read{color:var(--lime);font-size:12px;letter-spacing:.04em}.range-row{display:flex;align-items:center;gap:12px}.range-row input[type=range]{flex:1}.range-edge{font:10px ui-monospace,monospace;color:#687277}input[type=range]{accent-color:var(--lime);height:4px;cursor:pointer}.transport-buttons{display:flex;align-items:center;gap:9px}.btn{border:1px solid #414a4f;background:#252b2f;color:#d7dddc;border-radius:9px;min-height:39px;padding:0 14px;font:700 11px ui-monospace,monospace;letter-spacing:.07em;text-transform:uppercase;cursor:pointer;transition:.15s}.btn:hover{border-color:#78848a;background:#30373b}.btn:active{transform:translateY(1px)}.btn.play{background:var(--lime);border-color:var(--lime);color:#17200c;min-width:90px;box-shadow:0 4px 19px #c6fb5220}.btn.play:hover{background:#d4ff78}.btn.icon{padding:0;width:40px;font-size:15px}.control-select{border:1px solid #414a4f;background:#252b2f;color:#e7edeb;border-radius:7px;padding:7px 8px;font:12px ui-monospace,monospace}.master{max-width:145px}.swing-value{color:var(--cyan);font:11px ui-monospace,monospace}
.grid-wrap{padding:17px 24px 21px}.grid-head{display:grid;grid-template-columns:190px repeat(16,minmax(28px,1fr));gap:6px;align-items:center;margin-bottom:8px}.grid-caption{font:700 10px ui-monospace,monospace;color:#737e83;letter-spacing:.14em;text-transform:uppercase}.step-head{height:20px;text-align:center;color:#718087;font:10px ui-monospace,monospace;position:relative}.step-head.group-start{color:#bec7c6}.step-head.playhead::after{content:"";position:absolute;left:50%;bottom:-6px;transform:translateX(-50%);width:5px;height:5px;border-radius:50%;background:var(--lime);box-shadow:0 0 8px var(--lime)}.track-row{display:grid;grid-template-columns:190px repeat(16,minmax(28px,1fr));gap:6px;align-items:center;min-height:57px;border-top:1px solid #2b3236}.track-info{display:flex;align-items:center;gap:10px;min-width:0;padding-right:8px}.track-icon{width:32px;height:32px;flex:none;display:grid;place-items:center;border-radius:9px;background:#292f33;color:#c8d2d0;font-size:15px}.track-copy{min-width:0;flex:1}.track-name{font-weight:750;font-size:11px;letter-spacing:.07em;white-space:nowrap}.track-sub{font:9px ui-monospace,monospace;color:#737f84;letter-spacing:.06em;margin-top:2px}.track-volume{width:48px!important;flex:none}.mute{width:25px;height:24px;border:1px solid #424a4e;border-radius:6px;background:#252b2f;color:#899398;font:800 9px ui-monospace,monospace;padding:0;cursor:pointer}.mute.on{background:#422b2b;border-color:#85504c;color:#ff8579}.cell{position:relative;height:31px;border:1px solid #343c40;border-radius:6px;background:linear-gradient(180deg,#2b3135,#252a2e);cursor:pointer;transition:background .08s,border .08s,transform .08s,box-shadow .08s;padding:0;touch-action:none}.cell.group-start{margin-left:3px}.cell:hover{border-color:#78858a;transform:translateY(-1px)}.cell.active{background:#62812e;border-color:#9ccc46;box-shadow:inset 0 1px #d9ff8c40,0 0 12px #c6fb5213}.cell.accent{background:#b0e648;border-color:#d9ff91;box-shadow:0 0 16px #c6fb5230}.cell.current{outline:2px solid var(--cyan);outline-offset:2px;z-index:2}.track-row.muted .track-info{opacity:.42}.track-row.muted .cell{opacity:.42}.track-row.solo .track-name{color:var(--lime)}.cell .vel{position:absolute;inset:auto 0 3px;text-align:center;font:7px ui-monospace,monospace;color:#f3ffd8;opacity:.7;pointer-events:none}.cell:not(.active) .vel{display:none}.track-row:nth-of-type(3) .cell.active{background:#27776f;border-color:#51c9bc}.track-row:nth-of-type(3) .cell.accent{background:#48d4c5;border-color:#9bfff1}.track-row:nth-of-type(4) .cell.active{background:#276a65;border-color:#48aaa2}.track-row:nth-of-type(5) .cell.active{background:#75472d;border-color:#c1814f}.track-row:nth-of-type(5) .cell.accent{background:#e49253;border-color:#ffc78d}.track-row:nth-of-type(6) .cell.active{background:#5b426e;border-color:#a486bc}
.footer{padding:14px 25px;border-top:1px solid var(--line);display:flex;justify-content:space-between;align-items:center;color:#758086;font:10px ui-monospace,monospace;letter-spacing:.05em}.footer strong{color:#bac4c2;font-weight:600}.footer-actions{display:flex;align-items:center;gap:8px}.footer .btn{min-height:30px;padding:0 10px;font-size:9px}.hint{color:#616c71}.divider{height:1px;background:var(--line);margin:20px 0}.toast{position:fixed;bottom:22px;left:50%;transform:translate(-50%,15px);background:#252c2f;border:1px solid #485256;border-radius:9px;padding:9px 14px;color:#e4ece8;font:11px ui-monospace,monospace;opacity:0;transition:.2s;pointer-events:none}.toast.show{opacity:1;transform:translate(-50%,0)}
@media(max-width:900px){body{padding:20px 12px}.transport{grid-template-columns:1fr 1fr;gap:20px}.grid-wrap{padding:14px 14px 18px;overflow-x:auto}.grid-head,.track-row{min-width:850px;grid-template-columns:155px repeat(16,minmax(28px,1fr))}.track-volume{width:38px!important}.track-name{font-size:10px}.footer{padding:12px 14px}.master{max-width:none}}
@media(max-width:520px){.transport{padding:17px 15px;gap:17px}.topline{margin-bottom:16px}.status{font-size:9px}.footer{align-items:flex-start;gap:10px;flex-direction:column}.transport-buttons{justify-content:flex-start}.btn.play{min-width:82px}}
</style>
</head>
<body>
<div class="shell">
  <header class="topline"><div class="brand"><div class="brandmark">▦</div><div><div class="eyebrow">Rhythm instrument · 01</div><h1 class="title">PULSE / STEP SEQUENCER</h1></div></div><div id="status" class="status"><i class="status-dot"></i><span id="statusText">Engine idle</span></div></header>
  <main class="deck">
    <section class="transport" aria-label="Transport controls">
      <div class="transport-block"><div class="label"><span>Tempo</span><span class="bpm-read"><span id="bpmValue">112</span> BPM</span></div><div class="range-row"><span class="range-edge">60</span><input id="bpm" type="range" min="60" max="200" value="112" aria-label="Tempo"><span class="range-edge">200</span></div></div>
      <div class="transport-block"><div class="label"><span>Shuffle</span><span id="swingValue" class="swing-value">18%</span></div><div class="range-row"><span class="range-edge">0</span><input id="swing" type="range" min="0" max="60" value="18" aria-label="Swing"><span class="range-edge">60</span></div></div>
      <div class="transport-block"><div class="label"><span>Master</span><span id="masterValue" class="bpm-read">82%</span></div><div class="range-row"><span class="range-edge">0</span><input id="master" type="range" min="0" max="100" value="82" aria-label="Master volume"><span class="range-edge">100</span></div></div>
      <div class="transport-buttons"><button id="play" class="btn play" aria-label="Play">▶ &nbsp; PLAY</button><button id="stop" class="btn icon" aria-label="Stop" title="Stop">■</button></div>
    </section>
    <section class="grid-wrap" aria-label="16 step pattern">
      <div id="gridHead" class="grid-head"><div class="grid-caption">Track / level</div></div>
      <div id="grid"></div>
    </section>
    <footer class="footer"><div><strong>16 STEPS</strong> &nbsp;·&nbsp; 4/4 &nbsp;·&nbsp; <span id="saveState">AUTO-SAVED</span></div><div class="footer-actions"><span class="hint">CLICK · toggle &nbsp; RIGHT-CLICK · accent &nbsp; DRAG · paint</span><button id="random" class="btn">↻ Random</button><button id="clear" class="btn">Clear</button></div></footer>
  </main>
</div>
<div id="toast" class="toast" role="status"></div>
<script>
'use strict';
/* ======================== Tunable engine & patch constants ======================== */
const CFG={MIN_BPM:60,MAX_BPM:200,LOOKAHEAD_MS:25,SCHEDULE_AHEAD:0.12,LATE_TOLERANCE:0.035,START_LEAD:0.045,TRACKS:[
  {id:'kick',name:'KICK',sub:'LOW / PUNCH',icon:'◉',volume:.90},
  {id:'snare',name:'SNARE',sub:'NOISE / BODY',icon:'▰',volume:.76},
  {id:'closed',name:'CLOSED HAT',sub:'TIGHT / METAL',icon:'✣',volume:.52},
  {id:'open',name:'OPEN HAT',sub:'AIR / DECAY',icon:'◌',volume:.48},
  {id:'clap',name:'CLAP',sub:'BURST / SNAP',icon:'≋',volume:.62},
  {id:'tom',name:'TOM',sub:'PITCH / TONE',icon:'◒',volume:.68}
],PATCH:{kick:{startHz:150,endHz:48,decay:.34},snare:{bodyHz:185,decay:.22,noiseHz:1500},hat:{closedDecay:.075,openDecay:.48,cutoff:7200},clap:{decay:.19,cutoff:1450},tom:{startHz:122,endHz:72,decay:.31}}};
const STORE_KEY='pulse-sequencer-v1';
const DEFAULT_PATTERN=[
 [1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0],
 [0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0],
 [1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0],
 [0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0],
 [0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0],
 [0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0]
];
let state={pattern:DEFAULT_PATTERN.map(r=>r.slice()),bpm:112,swing:.18,master:.82,volumes:CFG.TRACKS.map(t=>t.volume),muted:CFG.TRACKS.map(()=>false)};
let audio=null,masterBus=null,trackBuses=[],noiseBuffer=null,activeVoices=new Set(),isPlaying=false,schedulerTimer=0,rafId=0,nextStep=0,segments=[],scheduledStepTimes=[],lastVisualStep=-1,paintMode=null,painted=new Set(),saveTimer=0;
const $=s=>document.querySelector(s),grid=$('#grid'),gridHead=$('#gridHead');
/* ======================== Persistence ======================== */
function loadState(){try{const raw=localStorage.getItem(STORE_KEY);if(!raw)return;const d=JSON.parse(raw);if(Array.isArray(d.pattern)&&d.pattern.length===6&&d.pattern.every(r=>Array.isArray(r)&&r.length===16))state.pattern=d.pattern.map(r=>r.map(v=>[0,1,2].includes(v)?v:0));if(Number.isFinite(d.bpm))state.bpm=clamp(d.bpm,60,200);if(Number.isFinite(d.swing))state.swing=clamp(d.swing,0,.6);if(Number.isFinite(d.master))state.master=clamp(d.master,0,1);if(Array.isArray(d.volumes))state.volumes=CFG.TRACKS.map((_,i)=>clamp(Number(d.volumes[i])||0,0,1));if(Array.isArray(d.muted))state.muted=CFG.TRACKS.map((_,i)=>!!d.muted[i]);}catch(e){console.warn('Could not restore sequencer state',e)}}
function persist(){clearTimeout(saveTimer);$('#saveState').textContent='SAVING…';saveTimer=setTimeout(()=>{try{localStorage.setItem(STORE_KEY,JSON.stringify(state));$('#saveState').textContent='AUTO-SAVED'}catch(e){$('#saveState').textContent='LOCAL STORAGE UNAVAILABLE'}},120)}
function clamp(v,a,b){return Math.max(a,Math.min(b,v))}
/* ======================== Grid construction & interaction ======================== */
function buildGrid(){gridHead.innerHTML='<div class="grid-caption">Track / level</div>';for(let s=0;s<16;s++){const d=document.createElement('div');d.className='step-head'+(s%4===0?' group-start':'');d.textContent=String(s+1).padStart(2,'0');d.dataset.step=s;gridHead.appendChild(d)}grid.innerHTML='';CFG.TRACKS.forEach((track,ti)=>{const row=document.createElement('div');row.className='track-row';row.dataset.track=ti;const info=document.createElement('div');info.className='track-info';info.innerHTML=`<div class="track-icon">${track.icon}</div><div class="track-copy"><div class="track-name">${track.name}</div><div class="track-sub">${track.sub}</div></div>`;const vol=document.createElement('input');vol.type='range';vol.min='0';vol.max='100';vol.value=Math.round(state.volumes[ti]*100);vol.className='track-volume';vol.title=track.name+' volume';vol.setAttribute('aria-label',track.name+' volume');vol.addEventListener('input',()=>{state.volumes[ti]=Number(vol.value)/100;if(trackBuses[ti])setBusGain(trackBuses[ti],state.muted[ti]?0:state.volumes[ti]);persist()});const mute=document.createElement('button');mute.className='mute'+(state.muted[ti]?' on':'');mute.textContent='M';mute.title='Mute '+track.name;mute.setAttribute('aria-label','Mute '+track.name);mute.setAttribute('aria-pressed',state.muted[ti]);mute.addEventListener('click',()=>{state.muted[ti]=!state.muted[ti];mute.classList.toggle('on',state.muted[ti]);mute.setAttribute('aria-pressed',state.muted[ti]);row.classList.toggle('muted',state.muted[ti]);if(trackBuses[ti])setBusGain(trackBuses[ti],state.muted[ti]?0:state.volumes[ti]);persist()});info.append(vol,mute);row.appendChild(info);for(let si=0;si<16;si++){const cell=document.createElement('button');cell.type='button';cell.className='cell'+(si%4===0?' group-start':'');cell.dataset.track=ti;cell.dataset.step=si;cell.setAttribute('aria-label',`${track.name}, step ${si+1}`);cell.addEventListener('pointerdown',e=>{if(e.button===2)return;e.preventDefault();paintMode=state.pattern[ti][si]===0?1:0;painted.clear();paintCell(cell,paintMode)});cell.addEventListener('pointerenter',()=>{if(paintMode!==null)paintCell(cell,paintMode)});cell.addEventListener('contextmenu',e=>{e.preventDefault();state.pattern[ti][si]=state.pattern[ti][si]===0?2:2;refreshCell(cell);persist()});row.appendChild(cell)}if(state.muted[ti])row.classList.add('muted');grid.appendChild(row)});document.addEventListener('pointerup',()=>{paintMode=null;painted.clear()});refreshAll()}
function paintCell(cell,value){const key=cell.dataset.track+':'+cell.dataset.step;if(painted.has(key))return;painted.add(key);state.pattern[Number(cell.dataset.track)][Number(cell.dataset.step)]=value;refreshCell(cell);persist()}
function refreshCell(cell){const v=state.pattern[Number(cell.dataset.track)][Number(cell.dataset.step)];cell.classList.toggle('active',v>0);cell.classList.toggle('accent',v===2);cell.setAttribute('aria-pressed',v>0?'true':'false');cell.innerHTML=v===2?'<span class="vel">ACC</span>':v===1?'<span class="vel">●</span>':''}
function refreshAll(){document.querySelectorAll('.cell').forEach(refreshCell)}
/* ======================== Audio graph & synthesized instruments ======================== */
async function initAudio(){if(!audio){const AC=window.AudioContext||window.webkitAudioContext;if(!AC){showToast('Web Audio is not supported in this browser');return false}audio=new AC({latencyHint:'interactive'});masterBus=audio.createGain();masterBus.gain.value=state.master;masterBus.connect(audio.destination);trackBuses=CFG.TRACKS.map((_,i)=>{const g=audio.createGain();g.gain.value=state.muted[i]?0:state.volumes[i];g.connect(masterBus);return g});noiseBuffer=audio.createBuffer(1,audio.sampleRate*2,audio.sampleRate);const data=noiseBuffer.getChannelData(0);let seed=0x5eed;for(let i=0;i<data.length;i++){seed=(seed*1664525+1013904223)>>>0;data[i]=((seed/4294967296)*2-1)*.8}}
try{if(audio.state!=='running')await audio.resume();return audio.state==='running'}catch(e){console.warn(e);return false}}
function setBusGain(g,value){if(!audio)return;const t=audio.currentTime;g.gain.cancelScheduledValues(t);g.gain.setTargetAtTime(value,t,.012)}
function envelope(node,t,peak,endTime,attack=.004){node.gain.setValueAtTime(0,t);node.gain.linearRampToValueAtTime(Math.max(.0001,peak),t+attack);node.gain.exponentialRampToValueAtTime(.0001,Math.max(t+attack+.003,endTime));}
function ownVoice(src,g){const voice={src,g};activeVoices.add(voice);src.onended=()=>activeVoices.delete(voice);return voice}
function noiseVoice(t,track,duration,{highpass=0,lowpass=18000,peak=.5,velocity=1,bright=1}={}){const src=audio.createBufferSource();src.buffer=noiseBuffer;src.loop=true;let node=src; if(highpass){const hp=audio.createBiquadFilter();hp.type='highpass';hp.frequency.setValueAtTime(highpass*bright,t);hp.Q.value=.7;node.connect(hp);node=hp}if(lowpass<18000){const lp=audio.createBiquadFilter();lp.type='lowpass';lp.frequency.setValueAtTime(Math.min(18000,lowpass*bright),t);node.connect(lp);node=lp}const g=audio.createGain();envelope(g,t,peak*velocity,t+duration,.0025);node.connect(g);g.connect(trackBuses[track]);ownVoice(src,g);src.start(t);src.stop(t+duration+.015)}
function kick(t,v){const p=CFG.PATCH.kick,o=audio.createOscillator(),g=audio.createGain();o.type='sine';o.frequency.setValueAtTime(p.startHz,t);o.frequency.exponentialRampToValueAtTime(p.endHz,t+.105);envelope(g,t,.92*v,t+p.decay,.003);o.connect(g);g.connect(trackBuses[0]);ownVoice(o,g);o.start(t);o.stop(t+p.decay+.015)}
function snare(t,v){const p=CFG.PATCH.snare;noiseVoice(t,1,p.decay,{highpass:p.noiseHz,lowpass:11500,peak:.66,velocity:v,bright:1+.23*(v-0.68)});const o=audio.createOscillator(),g=audio.createGain();o.type='triangle';o.frequency.setValueAtTime(p.bodyHz,t);o.frequency.exponentialRampToValueAtTime(115,t+.09);envelope(g,t,.35*v,t+.16,.002);o.connect(g);g.connect(trackBuses[1]);ownVoice(o,g);o.start(t);o.stop(t+.18)}
function hat(t,v,open=false){const p=CFG.PATCH.hat;noiseVoice(t,open?3:2,open?p.openDecay:p.closedDecay,{highpass:open?5100:6800,lowpass:p.cutoff,peak:open?.36:.43,velocity:v,bright:1+.3*(v-.68)})}
function clap(t,v){const p=CFG.PATCH.clap,src=audio.createBufferSource(),bp=audio.createBiquadFilter(),g=audio.createGain();src.buffer=noiseBuffer;src.loop=true;bp.type='bandpass';bp.frequency.setValueAtTime(p.cutoff*(1+.25*(v-.68)),t);bp.Q.value=.8;g.gain.setValueAtTime(0,t);const offsets=[0,.014,.031,.051],amps=[.9,.68,.48,.3];offsets.forEach((off,i)=>{const a=t+off;g.gain.setValueAtTime(.0001,a);g.gain.linearRampToValueAtTime(.42*v*amps[i],a+.002);g.gain.exponentialRampToValueAtTime(.0001,a+.012)});g.gain.setValueAtTime(.0001,t+p.decay);src.connect(bp);bp.connect(g);g.connect(trackBuses[4]);ownVoice(src,g);src.start(t);src.stop(t+p.decay+.02)}
function tom(t,v){const p=CFG.PATCH.tom,o=audio.createOscillator(),g=audio.createGain();o.type='sine';o.frequency.setValueAtTime(p.startHz,t);o.frequency.exponentialRampToValueAtTime(p.endHz,t+.13);envelope(g,t,.62*v,t+p.decay,.003);o.connect(g);g.connect(trackBuses[5]);ownVoice(o,g);o.start(t);o.stop(t+p.decay+.015)}
const TRIGGER=[kick,snare,(t,v)=>hat(t,v,false),(t,v)=>hat(t,v,true),clap,tom];
function triggerStep(step,time){for(let ti=0;ti<6;ti++){const vel=state.pattern[ti][step];if(vel&&!state.muted[ti])TRIGGER[ti](time,vel===2?1:.68)}}
/* ======================== Absolute-time lookahead scheduler ======================== */
function stepDuration(bpm=state.bpm){return 60/bpm/4}
function timeForStep(n){let seg=segments[0];for(let i=1;i<segments.length;i++){if(segments[i].startIndex<=n)seg=segments[i];else break}const d=60/seg.bpm/4;const odd=k=>(k&1)?1:0;return seg.time+(n-seg.startIndex)*d+(odd(n)-odd(seg.startIndex))*seg.swing*d}
function addTimingSegment(index,time,bpm,swing){segments=segments.filter(s=>s.startIndex<index);segments.push({startIndex:index,time,bpm,swing})}
function schedule(){if(!isPlaying||!audio)return;const now=audio.currentTime,horizon=now+CFG.SCHEDULE_AHEAD;let guard=0;while(timeForStep(nextStep)<horizon&&guard++<64){let t=timeForStep(nextStep);if(t<now-CFG.LATE_TOLERANCE){nextStep++;continue}if(t<now+.002)t=now+.002;triggerStep(nextStep%16,t);scheduledStepTimes.push({step:nextStep%16,index:nextStep,time:t,bpm:state.bpm});if(scheduledStepTimes.length>2048)scheduledStepTimes.splice(0,512);nextStep++}}
async function play(){if(isPlaying){stop();return}if(!await initAudio())return;isPlaying=true;nextStep=0;scheduledStepTimes=[];segments=[{startIndex:0,time:audio.currentTime+CFG.START_LEAD,bpm:state.bpm,swing:state.swing}];$('#play').innerHTML='Ⅱ &nbsp; PAUSE';$('#status').classList.add('playing');$('#statusText').textContent='Audio running';schedule();schedulerTimer=setInterval(schedule,CFG.LOOKAHEAD_MS);drawPlayhead()}
function stop(){if(!isPlaying)return;isPlaying=false;clearInterval(schedulerTimer);schedulerTimer=0;if(rafId)cancelAnimationFrame(rafId);rafId=0;const now=audio?.currentTime||0;for(const voice of activeVoices){try{voice.g.gain.cancelScheduledValues(now);voice.g.gain.setTargetAtTime(0,now,.0015);voice.src.stop(now+.012)}catch(e){}}activeVoices.clear();lastVisualStep=-1;document.querySelectorAll('.cell.current,.step-head.playhead').forEach(e=>e.classList.remove('current','playhead'));$('#play').innerHTML='▶ &nbsp; PLAY';$('#status').classList.remove('playing');$('#statusText').textContent='Engine idle'}
function changeTiming(){if(isPlaying&&audio){const idx=nextStep;const boundary=timeForStep(idx);addTimingSegment(idx,boundary,state.bpm,state.swing)}persist()}
function visualStepAt(now){if(!segments.length)return -1;let seg=segments[0];for(let i=1;i<segments.length;i++){if(segments[i].time<=now)seg=segments[i];else break}const d=60/seg.bpm/4;const rough=seg.startIndex+Math.floor((now-seg.time)/d);let best=seg.startIndex-1;for(let n=Math.max(seg.startIndex,rough-3);n<=rough+3;n++){if(timeForStep(n)<=now+1e-6&&n>best)best=n}return best}
function drawPlayhead(){if(!isPlaying||!audio)return;const n=visualStepAt(audio.currentTime),step=n>=0?n%16:-1;if(step!==lastVisualStep){document.querySelectorAll('.cell.current,.step-head.playhead').forEach(e=>e.classList.remove('current','playhead'));if(step>=0){document.querySelectorAll(`.cell[data-step="${step}"]`).forEach(e=>e.classList.add('current'));const h=gridHead.querySelector(`[data-step="${step}"]`);if(h)h.classList.add('playhead')}lastVisualStep=step}rafId=requestAnimationFrame(drawPlayhead)}
/* ======================== Controls ======================== */
$('#bpm').value=state.bpm;$('#swing').value=Math.round(state.swing*100);$('#master').value=Math.round(state.master*100);$('#bpmValue').textContent=state.bpm;$('#swingValue').textContent=Math.round(state.swing*100)+'%';$('#masterValue').textContent=Math.round(state.master*100)+'%';
$('#bpm').addEventListener('input',e=>{state.bpm=Number(e.target.value);$('#bpmValue').textContent=state.bpm;changeTiming()});$('#swing').addEventListener('input',e=>{state.swing=Number(e.target.value)/100;$('#swingValue').textContent=e.target.value+'%';changeTiming()});$('#master').addEventListener('input',e=>{state.master=Number(e.target.value)/100;$('#masterValue').textContent=e.target.value+'%';if(masterBus)setBusGain(masterBus,state.master);persist()});$('#play').addEventListener('click',play);$('#stop').addEventListener('click',stop);
$('#clear').addEventListener('click',()=>{state.pattern=state.pattern.map(r=>r.map(()=>0));refreshAll();persist();showToast('Pattern cleared')});$('#random').addEventListener('click',()=>{state.pattern=CFG.TRACKS.map((_,ti)=>Array.from({length:16},(_,s)=>{const p=ti===0?(s%4===0?.9:.07):ti===1?(s===4||s===12?.8:.035):ti===2?.64:ti===3?.09:ti===4?.17:.12;return Math.random()<p?(Math.random()<.17?2:1):0}));refreshAll();persist();showToast('Fresh pattern generated')});
function showToast(msg){const t=$('#toast');t.textContent=msg;t.classList.add('show');clearTimeout(showToast.timer);showToast.timer=setTimeout(()=>t.classList.remove('show'),1500)}
/* ======================== Diagnostics exposed for timing verification ======================== */
window.__sequencer={get scheduledStepTimes(){return scheduledStepTimes.slice()},get state(){return{...state,pattern:state.pattern.map(r=>r.slice()),isPlaying,nextStep,segments:segments.map(s=>({...s})),audioState:audio?.state||'not-created'}},timeForStep:(n)=>timeForStep(n),play,stop};
loadState();$('#bpm').value=state.bpm;$('#swing').value=Math.round(state.swing*100);$('#master').value=Math.round(state.master*100);$('#bpmValue').textContent=state.bpm;$('#swingValue').textContent=Math.round(state.swing*100)+'%';$('#masterValue').textContent=Math.round(state.master*100)+'%';buildGrid();persist();
</script>
</body>
</html>
<!-- agent-meta {"model":"openai/gpt-6-luna","provider":"openrouter","persona":"full","sessionId":"288607c3-5119-4d89-9e0f-aa08faecb503","tokensIn":359969,"tokensOut":20241,"tokensTotal":380210,"cost":0.016701519999999997,"turns":19,"toolCalls":18,"failedToolCalls":2,"timestamp":"2026-09-23T02:48:47.530Z"} -->