Sequencer
GLM 5.3 Flash · full

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 23,979 bytes · SHA-256 20462da9d665
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DR-909 · Step Sequencer</title>
<style>
/* ============ STYLE ============ */
:root{
--bg:#0c0d10; --panel:#17181d; --panel2:#1e2027; --line:#2a2d36;
--txt:#c8ccd6; --dim:#6b7080;
--amber:#ffb347; --red:#ff5a4e; --green:#4ade80; --cyan:#53d8ff;
--cell:#23252e; --cell-on:#3a3f52;
}
*{box-sizing:border-box; margin:0; padding:0; user-select:none; -webkit-user-select:none;}
body{
background:radial-gradient(1200px 700px at 50% -10%, #1a1c24, var(--bg));
color:var(--txt); font-family:"Segoe UI", system-ui, sans-serif;
min-height:100vh; display:flex; align-items:center; justify-content:center; padding:16px;
}
.machine{
background:linear-gradient(180deg,#1b1d24,#131419);
border:1px solid #000; border-radius:14px; padding:18px 20px 22px;
box-shadow:0 30px 60px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.06);
max-width:1060px; width:100%;
}
.brand{display:flex; align-items:baseline; gap:12px; margin-bottom:14px;}
.brand h1{font-size:20px; letter-spacing:4px; color:#e8ebf2; font-weight:800;}
.brand .led{width:9px;height:9px;border-radius:50%;background:#3a1010;align-self:center;}
.brand .led.on{background:var(--red); box-shadow:0 0 8px var(--red);}
.brand small{color:var(--dim); letter-spacing:2px;}
/* transport */
.transport{display:flex; flex-wrap:wrap; gap:14px; align-items:center;
background:var(--panel); border:1px solid var(--line); border-radius:10px; padding:12px 14px; margin-bottom:14px;}
button{font:inherit; color:var(--txt); background:var(--panel2); border:1px solid var(--line);
border-radius:8px; padding:8px 14px; cursor:pointer; transition:filter .1s, box-shadow .1s;}
button:hover{filter:brightness(1.2);}
button:active{transform:translateY(1px);}
#playBtn{min-width:92px; font-weight:700; letter-spacing:1px;}
#playBtn.playing{background:#3d1418; color:var(--red); border-color:#6b2020; box-shadow:0 0 12px rgba(255,90,78,.35);}
.ctl{display:flex; flex-direction:column; gap:4px; min-width:150px; flex:1;}
.ctl label{font-size:10px; letter-spacing:2px; color:var(--dim); text-transform:uppercase;
display:flex; justify-content:space-between;}
.ctl label b{color:var(--amber); font-variant-numeric:tabular-nums; letter-spacing:1px;}
input[type=range]{-webkit-appearance:none; appearance:none; height:4px; border-radius:2px;
background:#33363f; outline:none; cursor:pointer;}
input[type=range]::-webkit-slider-thumb{-webkit-appearance:none; width:14px; height:20px; border-radius:3px;
background:linear-gradient(180deg,#4a4e5c,#2c2f39); border:1px solid #000; box-shadow:0 0 6px rgba(0,0,0,.8);}
#bpmVal{font-size:22px; font-weight:800; color:var(--amber); text-shadow:0 0 10px rgba(255,179,71,.5);
font-variant-numeric:tabular-nums; min-width:74px; text-align:right;}
/* sequencer */
.grid-wrap{background:var(--panel); border:1px solid var(--line); border-radius:10px; padding:12px 14px;}
.row{display:grid; grid-template-columns:96px repeat(16, 1fr); gap:5px; align-items:center; margin-bottom:6px;}
.row:last-child{margin-bottom:0;}
.row .trackname{display:flex; align-items:center; gap:7px; font-size:12px; font-weight:600; letter-spacing:1px;}
.trackname .dot{width:10px; height:10px; border-radius:3px; flex:none;}
.trackname .tbtns{display:flex; gap:3px; margin-left:auto;}
.tbtns button{padding:2px 6px; font-size:10px; border-radius:4px; letter-spacing:1px;}
.tbtns .mute.active{background:#4a1418; color:var(--red); border-color:#7a2222;}
.tvol{width:52px; height:4px;}
.ruler{display:grid; grid-template-columns:96px repeat(16,1fr); gap:5px; margin-bottom:8px;}
.ruler span{font-size:9px; color:var(--dim); text-align:center; letter-spacing:1px;}
.cell{
aspect-ratio:1/.82; border-radius:5px; background:var(--cell);
border:1px solid rgba(0,0,0,.6); box-shadow:inset 0 1px 0 rgba(255,255,255,.05);
cursor:pointer; position:relative; transition:background .05s;
}
.cell.beat{margin-left:6px;}
.row .cell:first-of-type{margin-left:0;}
.cell.vel1{background:var(--cell-on); box-shadow:inset 0 0 6px rgba(120,140,255,.25);}
.cell.vel2{background:#5b628a; box-shadow:0 0 10px rgba(130,150,255,.55), inset 0 0 8px rgba(180,190,255,.5);}
.cell.ph{outline:2px solid rgba(255,255,255,.55); outline-offset:-2px; filter:brightness(1.5);}
.cell.flash{filter:brightness(2.2);}
.row.muted .cell{opacity:.28;}
.row.muted .trackname{opacity:.45;}
.foot{margin-top:12px; display:flex; gap:10px; justify-content:flex-end;}
.hint{color:var(--dim); font-size:11px; margin-top:10px; letter-spacing:.5px;}
</style>
</head>
<body>
<div class="machine">
<div class="brand"><div class="led" id="powerLed"></div><h1>DR-909</h1><small>STEP SEQUENCER</small>
<span id="bpmVal">120</span></div>
<div class="transport">
<button id="playBtn">▶ PLAY</button>
<div class="ctl"><label>Tempo <b id="bpmLabel">120 BPM</b></label>
<input type="range" id="bpm" min="60" max="200" value="120" step="1"></div>
<div class="ctl"><label>Swing <b id="swingLabel">0%</b></label>
<input type="range" id="swing" min="0" max="60" value="0" step="1"></div>
<div class="ctl"><label>Master <b id="volLabel">80%</b></label>
<input type="range" id="masterVol" min="0" max="100" value="80" step="1"></div>
</div>
<div class="grid-wrap">
<div class="ruler" id="ruler"></div>
<div id="grid"></div>
<div class="hint">Click: toggle · Right-click: accent · Drag: paint · Steps grouped in beats of 4</div>
<div class="foot">
<button id="clearBtn">CLEAR</button>
<button id="randomBtn">RANDOM</button>
</div>
</div>
</div>
<script>
/* ================================================================
TUNABLE CONSTANTS
================================================================ */
const CONST = {
LOOKAHEAD_MS: 25, // scheduler timer interval
SCHEDULE_AHEAD_S: 0.12, // how far ahead we schedule on the audio clock
SCHEDULE_AHEAD_HIDDEN_S: 1.2, // extra lookahead when tab is throttled/backgrounded
BPM_MIN: 60, BPM_MAX: 200, BPM_DEFAULT: 120,
SWING_MAX: 60, // percent of a 16th step
STEPS: 16, TRACKS: 6,
VEL: { OFF:0, NORM:1, ACCENT:2 },
VEL_GAIN: { 1: 0.75, 2: 1.0 },
// instrument patch params
KICK: { f0:150, f1:50, pitchDecay:0.09, ampDecay:0.42, gain:1.0 },
SNARE: { bodyF:185, noiseDecay:0.18, bodyDecay:0.12, hpNorm:1200, hpAccent:2600, gain:0.85 },
HAT: { hp:7500, decayClosed:0.05, decayOpen:0.35, gain:0.5 },
CLAP: { bp:1400, q:1.4, bursts:[0, 0.011, 0.022], tailDecay:0.16, gain:0.8 },
TOM: { f:110, fDrop:0.85, decay:0.32, lpBase:900, lpAccent:3200, gain:0.8 },
TRACK_COLORS: ['#ff5a4e','#ffb347','#53d8ff','#4ade80','#c084fc','#f472b6'],
LS_KEY: 'dr909.pattern.v1'
};
/* ================================================================
STATE
================================================================ */
// grid[track][step] : 0 off, 1 normal, 2 accent
const TRACK_NAMES = ['KICK','SNARE','HAT C','HAT O','CLAP','TOM'];
const state = {
grid: Array.from({length: CONST.TRACKS}, () => new Array(CONST.STEPS).fill(0)),
trackVol: [1,1,1,1,1,1],
mute: [false,false,false,false,false,false],
bpm: CONST.BPM_DEFAULT,
swing: 0,
master: 0.8,
playing: false,
};
/* ================================================================
AUDIO ENGINE
================================================================ */
let ctx = null, masterGain = null, comp = null, noiseBuf = null;
const trackGains = [];
function ensureAudio(){
if (ctx) { if (ctx.state === 'suspended') ctx.resume(); return; }
ctx = new (window.AudioContext || window.webkitAudioContext)();
comp = ctx.createDynamicsCompressor();
comp.threshold.value = -10; comp.ratio.value = 4; comp.attack.value = 0.003;
masterGain = ctx.createGain();
masterGain.gain.value = state.master;
masterGain.connect(comp); comp.connect(ctx.destination);
for (let i = 0; i < CONST.TRACKS; i++){
const g = ctx.createGain();
g.gain.value = state.mute[i] ? 0 : state.trackVol[i];
g.connect(masterGain);
trackGains.push(g);
}
// shared 2s white-noise buffer
const len = ctx.sampleRate * 2;
noiseBuf = ctx.createBuffer(1, len, ctx.sampleRate);
const d = noiseBuf.getChannelData(0);
for (let i = 0; i < len; i++) d[i] = Math.random() * 2 - 1;
}
/* ---------------- Instruments (all scheduled at time t) ---------------- */
function noiseSrc(t, dur){
const s = ctx.createBufferSource();
s.buffer = noiseBuf; s.loop = true;
s.start(t); s.stop(t + dur + 0.05);
return s;
}
function env(t, peak, decay){
const g = ctx.createGain();
g.gain.setValueAtTime(0.0001, t);
g.gain.exponentialRampToValueAtTime(Math.max(peak, 0.0001), t + 0.002);
g.gain.exponentialRampToValueAtTime(0.0001, t + decay);
return g;
}
function playKick(t, vel){
const p = CONST.KICK, out = trackGains[0];
const o = ctx.createOscillator(); o.type = 'sine';
o.frequency.setValueAtTime(p.f0, t);
o.frequency.exponentialRampToValueAtTime(p.f1, t + p.pitchDecay);
const g = env(t, p.gain * CONST.VEL_GAIN[vel] * (vel === 2 ? 1.1 : 1), p.ampDecay * (vel === 2 ? 1.15 : 1));
o.connect(g); g.connect(out);
o.start(t); o.stop(t + p.ampDecay + 0.1);
}
function playSnare(t, vel){
const p = CONST.SNARE, out = trackGains[1], vg = CONST.VEL_GAIN[vel];
// noise snap through highpass (accent = brighter)
const n = noiseSrc(t, p.noiseDecay + 0.1);
const hp = ctx.createBiquadFilter(); hp.type = 'highpass';
hp.frequency.value = vel === 2 ? p.hpAccent : p.hpNorm;
const ng = env(t, 0.9 * vg, p.noiseDecay * (vel === 2 ? 1.2 : 1));
n.connect(hp); hp.connect(ng); ng.connect(out);
// tonal body
const o = ctx.createOscillator(); o.type = 'triangle';
o.frequency.setValueAtTime(p.bodyF, t);
o.frequency.exponentialRampToValueAtTime(p.bodyF * 0.6, t + p.bodyDecay);
const og = env(t, 0.7 * vg, p.bodyDecay);
o.connect(og); og.connect(out);
o.start(t); o.stop(t + p.bodyDecay + 0.1);
}
function playHat(t, vel, open){
const p = CONST.HAT, out = open ? trackGains[3] : trackGains[2];
const dur = open ? p.decayOpen : p.decayClosed;
const n = noiseSrc(t, dur + 0.1);
const hp = ctx.createBiquadFilter(); hp.type = 'highpass'; hp.frequency.value = p.hp;
const bp = ctx.createBiquadFilter(); bp.type = 'bandpass'; bp.frequency.value = 10000; bp.Q.value = 0.8;
const g = env(t, p.gain * CONST.VEL_GAIN[vel], dur);
n.connect(hp); hp.connect(bp); bp.connect(g); g.connect(out);
}
function playClap(t, vel){
const p = CONST.CLAP, out = trackGains[4], vg = CONST.VEL_GAIN[vel];
for (const off of p.bursts){
const n = noiseSrc(t + off, 0.05);
const bp = ctx.createBiquadFilter(); bp.type = 'bandpass';
bp.frequency.value = p.bp; bp.Q.value = p.q;
const g = env(t + off, 0.8 * vg, 0.035);
n.connect(bp); bp.connect(g); g.connect(out);
}
// tail
const n = noiseSrc(t + 0.022, p.tailDecay + 0.1);
const bp = ctx.createBiquadFilter(); bp.type = 'bandpass';
bp.frequency.value = p.bp; bp.Q.value = p.q;
const g = env(t + 0.022, 0.6 * vg, p.tailDecay);
n.connect(bp); bp.connect(g); g.connect(out);
}
function playTom(t, vel){
const p = CONST.TOM, out = trackGains[5], vg = CONST.VEL_GAIN[vel];
const f = p.f * (vel === 2 ? 1.0 : 0.94);
const o = ctx.createOscillator(); o.type = 'sawtooth';
o.frequency.setValueAtTime(f, t);
o.frequency.exponentialRampToValueAtTime(f * p.fDrop, t + p.decay);
const sub = ctx.createOscillator(); sub.type = 'sine';
sub.frequency.setValueAtTime(f / 2, t);
const lp = ctx.createBiquadFilter(); lp.type = 'lowpass';
lp.frequency.setValueAtTime(vel === 2 ? p.lpAccent : p.lpBase, t);
lp.frequency.exponentialRampToValueAtTime(250, t + p.decay);
const g = env(t, p.gain * vg, p.decay);
o.connect(lp); sub.connect(lp); lp.connect(g); g.connect(out);
o.start(t); sub.start(t);
o.stop(t + p.decay + 0.1); sub.stop(t + p.decay + 0.1);
}
const INSTRUMENTS = [playKick, playSnare, (t,v)=>playHat(t,v,false), (t,v)=>playHat(t,v,true), playClap, playTom];
/* ================================================================
SCHEDULER — lookahead pattern on the Web Audio clock
================================================================ */
let currentStep = 0; // absolute next step index (0..15)
let nextNoteTime = 0; // audio-clock time of next step boundary (straight grid)
let timerID = null;
const drawQueue = []; // {step, time, trackFlashes:[...]} for the rAF playhead
// exposed for testing: window.__schedLog = [{step, t, boundaryT}, ...]
window.__schedLog = [];
function stepDuration(){ return (60 / state.bpm) / 4; } // 16th note
function swingOffset(step){
// off-beat 8th notes (steps 2,6,10,14) delayed by swing% of a 16th step
return (step % 4 === 2) ? (state.swing / 100) * stepDuration() : 0;
}
function scheduleStep(step, t){
const flashes = [];
for (let tr = 0; tr < CONST.TRACKS; tr++){
const v = state.grid[tr][step];
if (v !== CONST.VEL.OFF && !state.mute[tr]){
INSTRUMENTS[tr](t, v);
flashes.push(tr);
}
}
drawQueue.push({ step, time: t, flashes });
window.__schedLog.push({ step, t, boundaryT: t - swingOffset(step) });
if (window.__schedLog.length > 400) window.__schedLog.splice(0, 200);
}
function nextNote(){
const sd = stepDuration();
scheduleStep(currentStep, nextNoteTime + swingOffset(currentStep));
nextNoteTime += sd; // straight grid — never accumulates drift
currentStep = (currentStep + 1) % CONST.STEPS;
}
function scheduler(){
if (!state.playing) return;
const ahead = document.hidden ? CONST.SCHEDULE_AHEAD_HIDDEN_S : CONST.SCHEDULE_AHEAD_S;
while (nextNoteTime < ctx.currentTime + ahead) nextNote();
}
function startPlayback(){
ensureAudio();
currentStep = 0;
nextNoteTime = ctx.currentTime + 0.06; // restart cleanly on step 1
state.playing = true;
timerID = setInterval(scheduler, CONST.LOOKAHEAD_MS);
scheduler();
updatePlayUI(true);
}
function stopPlayback(){
state.playing = false;
clearInterval(timerID); timerID = null;
drawQueue.length = 0;
clearPlayhead();
updatePlayUI(false);
}
/* ---------------- Playhead (rAF, driven by audio clock) ---------------- */
const cellEls = []; // cellEls[track][step]
let lastStepEl = null;
function drawLoop(){
if (state.playing && ctx){
const now = ctx.currentTime;
while (drawQueue.length && drawQueue[0].time <= now){
const ev = drawQueue.shift();
setPlayhead(ev.step);
flashCells(ev.flashes, ev.step);
}
}
requestAnimationFrame(drawLoop);
}
function setPlayhead(step){
if (lastStepEl !== null) for (const row of cellEls) row[lastStepEl].classList.remove('ph');
for (const row of cellEls) row[step].classList.add('ph');
lastStepEl = step;
}
function clearPlayhead(){
if (lastStepEl !== null) for (const row of cellEls) row[lastStepEl].classList.remove('ph');
lastStepEl = null;
}
function flashCells(tracks, step){
for (const tr of tracks){
const el = cellEls[tr][step];
el.classList.add('flash');
setTimeout(() => el.classList.remove('flash'), 90);
}
}
requestAnimationFrame(drawLoop);
/* ================================================================
UI — grid, controls, painting
================================================================ */
const gridEl = document.getElementById('grid');
const rulerEl = document.getElementById('ruler');
function buildUI(){
// ruler
for (let s = 0; s < CONST.STEPS; s++)
rulerEl.appendChild(Object.assign(document.createElement('span'),
{ textContent: s % 4 === 0 ? String(s / 4 + 1) : '·' }));
// rows
for (let tr = 0; tr < CONST.TRACKS; tr++){
const row = document.createElement('div'); row.className = 'row';
const name = document.createElement('div'); name.className = 'trackname';
const dot = document.createElement('div'); dot.className = 'dot';
dot.style.background = CONST.TRACK_COLORS[tr];
const label = document.createElement('span'); label.textContent = TRACK_NAMES[tr];
const btns = document.createElement('div'); btns.className = 'tbtns';
const muteBtn = document.createElement('button'); muteBtn.textContent = 'M';
muteBtn.className = 'mute'; muteBtn.title = 'Mute';
muteBtn.onclick = () => {
state.mute[tr] = !state.mute[tr];
muteBtn.classList.toggle('active', state.mute[tr]);
row.classList.toggle('muted', state.mute[tr]);
if (trackGains[tr]) trackGains[tr].gain.setTargetAtTime(state.mute[tr] ? 0 : state.trackVol[tr], ctx.currentTime, 0.01);
save();
};
const vol = document.createElement('input');
vol.type = 'range'; vol.className = 'tvol'; vol.min = 0; vol.max = 100; vol.value = 100;
vol.title = 'Track volume';
vol.oninput = () => {
state.trackVol[tr] = vol.value / 100;
if (trackGains[tr] && !state.mute[tr]) trackGains[tr].gain.setTargetAtTime(state.trackVol[tr], ctx.currentTime, 0.01);
save();
};
btns.append(muteBtn, vol);
name.append(dot, label, btns);
row.appendChild(name);
cellEls[tr] = [];
for (let s = 0; s < CONST.STEPS; s++){
const c = document.createElement('div');
c.className = 'cell' + (s % 4 === 0 && s !== 0 ? ' beat' : '');
c.dataset.tr = tr; c.dataset.s = s;
c.addEventListener('pointerdown', e => onCellDown(e, tr, s));
c.addEventListener('pointerenter', e => { if (painting) onCellDown(e, tr, s, true); });
c.addEventListener('contextmenu', e => e.preventDefault());
row.appendChild(c);
cellEls[tr].push(c);
}
gridEl.appendChild(row);
}
}
// painting: left = toggle off<->normal, right = accent; drag paints the chosen value
let painting = false, paintVal = 0, paintBtn = 0;
function onCellDown(e, tr, s, isEnter){
if (isEnter){ state.grid[tr][s] = paintVal; refreshCell(tr, s); save(); return; }
e.preventDefault();
painting = true; paintBtn = e.button;
const cur = state.grid[tr][s];
if (e.button === 2){
paintVal = (cur === 2) ? CONST.VEL.OFF : CONST.VEL.ACCENT;
} else {
paintVal = (cur === CONST.VEL.OFF) ? CONST.VEL.NORM : CONST.VEL.OFF;
}
state.grid[tr][s] = paintVal;
refreshCell(tr, s); save();
if (ctx) ensureAudio(); // resume on gesture
}
window.addEventListener('pointerup', () => painting = false);
function refreshCell(tr, s){
const c = cellEls[tr][s], v = state.grid[tr][s];
c.classList.toggle('vel1', v === 1);
c.classList.toggle('vel2', v === 2);
}
function refreshAllCells(){
for (let tr = 0; tr < CONST.TRACKS; tr++) for (let s = 0; s < CONST.STEPS; s++) refreshCell(tr, s);
}
/* ---------------- Transport controls ---------------- */
const playBtn = document.getElementById('playBtn');
const powerLed = document.getElementById('powerLed');
function updatePlayUI(on){
playBtn.textContent = on ? '■ STOP' : '▶ PLAY';
playBtn.classList.toggle('playing', on);
powerLed.classList.toggle('on', on);
}
playBtn.onclick = () => state.playing ? stopPlayback() : startPlayback();
const bpmSlider = document.getElementById('bpm');
const bpmLabel = document.getElementById('bpmLabel');
const bpmVal = document.getElementById('bpmVal');
bpmSlider.oninput = () => {
state.bpm = +bpmSlider.value;
bpmLabel.textContent = state.bpm + ' BPM';
bpmVal.textContent = state.bpm;
save();
};
const swingSlider = document.getElementById('swing');
const swingLabel = document.getElementById('swingLabel');
swingSlider.oninput = () => {
state.swing = +swingSlider.value;
swingLabel.textContent = state.swing + '%';
save();
};
const masterSlider = document.getElementById('masterVol');
const volLabel = document.getElementById('volLabel');
masterSlider.oninput = () => {
state.master = masterSlider.value / 100;
volLabel.textContent = masterSlider.value + '%';
if (masterGain) masterGain.gain.setTargetAtTime(state.master, ctx.currentTime, 0.01);
save();
};
document.getElementById('clearBtn').onclick = () => {
state.grid = Array.from({length: CONST.TRACKS}, () => new Array(CONST.STEPS).fill(0));
refreshAllCells(); save();
};
document.getElementById('randomBtn').onclick = () => {
// musical-ish randomization
const prob = [ [0,.9],[1,.25],[2,.7],[3,.12],[4,.3],[5,.2] ];
const g = Array.from({length: CONST.TRACKS}, () => new Array(CONST.STEPS).fill(0));
for (let tr = 0; tr < CONST.TRACKS; tr++)
for (let s = 0; s < CONST.STEPS; s++){
let p = prob[tr][1];
if (tr === 0) p = (s % 4 === 0) ? 0.85 : 0.08; // kick anchors beats
if (tr === 1) p = (s === 4 || s === 12) ? 0.8 : 0.05; // snare backbeat
if (tr === 4) p = (s === 4 || s === 12) ? 0.5 : 0.03;
if (Math.random() < p) g[tr][s] = Math.random() < 0.3 ? 2 : 1;
}
state.grid = g; refreshAllCells(); save();
};
/* ================================================================
PERSISTENCE
================================================================ */
let saveTimer = null;
function save(){
clearTimeout(saveTimer);
saveTimer = setTimeout(() => {
try {
localStorage.setItem(CONST.LS_KEY, JSON.stringify({
grid: state.grid, trackVol: state.trackVol, mute: state.mute,
bpm: state.bpm, swing: state.swing, master: state.master
}));
} catch(e){}
}, 150);
}
function load(){
try {
const raw = localStorage.getItem(CONST.LS_KEY);
if (!raw) return false;
const d = JSON.parse(raw);
if (!d || !Array.isArray(d.grid)) return false;
state.grid = d.grid.map(r => Array.from({length: CONST.STEPS}, (_, i) => r[i] | 0));
while (state.grid.length < CONST.TRACKS) state.grid.push(new Array(CONST.STEPS).fill(0));
state.trackVol = d.trackVol || state.trackVol;
state.mute = d.mute || state.mute;
state.bpm = Math.min(CONST.BPM_MAX, Math.max(CONST.BPM_MIN, d.bpm || CONST.BPM_DEFAULT));
state.swing = Math.min(CONST.SWING_MAX, Math.max(0, d.swing || 0));
state.master = d.master ?? state.master;
return true;
} catch(e){ return false; }
}
/* ================================================================
DEFAULT PATTERN
================================================================ */
function loadDefaultPattern(){
const P = (row, steps, acc = []) => {
steps.forEach(s => state.grid[row][s] = acc.includes(s) ? 2 : 1);
};
P(0, [0,4,8,12], [0]); // kick four-on-floor, accent 1
P(1, [12]); // snare layering with clap at 12
P(2, [2,6,10,14], [2,6,10,14]); // closed hat offbeats, accented
P(3, [14]); // open hat at end of bar 2
P(4, [4,12]); // clap backbeat
P(5, [7,15], [15]); // tom stab pickups
}
/* ================================================================
INIT
================================================================ */
buildUI();
if (!load()) loadDefaultPattern();
refreshAllCells();
bpmSlider.value = state.bpm; bpmLabel.textContent = state.bpm + ' BPM'; bpmVal.textContent = state.bpm;
swingSlider.value = state.swing; swingLabel.textContent = state.swing + '%';
masterSlider.value = Math.round(state.master * 100); volLabel.textContent = Math.round(state.master * 100) + '%';
cellEls.forEach((row, tr) => {
if (state.mute[tr]){
row[0].closest('.row').classList.add('muted');
row[0].closest('.trackname').querySelector('.mute').classList.add('active');
}
});
window.addEventListener('beforeunload', () => { clearTimeout(saveTimer); save(); });
</script>
</body>
</html>
<!-- agent-meta {"model":"z-ai/glm-5.3-flash","provider":"openrouter","persona":"full","sessionId":"7b9b7754-4b54-49c3-b212-3a4447ecadc8","tokensIn":100138,"tokensOut":10413,"tokensTotal":110551,"cost":0.005835839999999999,"turns":7,"toolCalls":6,"failedToolCalls":1,"timestamp":"2026-08-28T04:16:44.777Z"} -->