Drum
📦 Prompts
✨ The Prompt Phrase
Design a dark-themed drum machine UI for a music production web app. Include an 8 by 4 pad grid where each pad triggers a different drum sound, a BPM slider ranging from 60 to 200, a 16-step pattern sequencer showing active steps highlighted in amber, and a sidebar listing drum kit presets. Use dark charcoal and amber accents with glowing active pads and smooth hover transitions.
💻 Code Preview
📦 All-in-One Code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>BeatForge — Drum Machine</title>
<style>
/* ═══════════════════════════════════════════════════════════
DESIGN TOKENS
═══════════════════════════════════════════════════════════ */
:root {
--bg-900: #0e0e0e;
--bg-800: #141414;
--bg-700: #1a1a1a;
--bg-600: #212121;
--bg-500: #2a2a2a;
--bg-400: #333333;
--amber-600: #d97706;
--amber-500: #f59e0b;
--amber-400: #fbbf24;
--amber-300: #fcd34d;
--amber-glow: rgba(251,191,36,.35);
--amber-glow-sm: rgba(251,191,36,.18);
--green-500: #22c55e;
--green-glow: rgba(34,197,94,.3);
--red-500: #ef4444;
--blue-500: #3b82f6;
--purple-500: #a855f7;
--text-primary: #f0f0f0;
--text-secondary: #909090;
--text-muted: #555555;
--border: rgba(255,255,255,.07);
--border-mid: rgba(255,255,255,.12);
--r-sm: 6px;
--r-md: 10px;
--r-lg: 14px;
--r-xl: 20px;
--t: .15s cubic-bezier(.4,0,.2,1);
--t-slow: .3s cubic-bezier(.4,0,.2,1);
--sidebar-w: 210px;
}
/* ═══════════════════════════════════════════════════════════
RESET
═══════════════════════════════════════════════════════════ */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { height: 100%; }
body {
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
background: var(--bg-900);
color: var(--text-primary);
min-height: 100vh;
display: flex;
flex-direction: column;
-webkit-font-smoothing: antialiased;
overflow-x: hidden;
}
button { font-family: inherit; cursor: pointer; border: none; }
input { font-family: inherit; }
::-webkit-scrollbar { width: 5px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--bg-500); border-radius: 99px; }
/* ═══════════════════════════════════════════════════════════
TOP BAR
═══════════════════════════════════════════════════════════ */
.topbar {
display: flex;
align-items: center;
gap: 1rem;
padding: .75rem 1.25rem;
background: var(--bg-800);
border-bottom: 1px solid var(--border);
flex-shrink: 0;
z-index: 50;
}
.logo {
display: flex; align-items: center; gap: .55rem;
font-size: 1rem; font-weight: 900;
letter-spacing: -.03em; color: var(--text-primary);
white-space: nowrap;
}
.logo-icon {
width: 28px; height: 28px;
background: linear-gradient(135deg, var(--amber-600), var(--amber-400));
border-radius: var(--r-sm);
display: flex; align-items: center; justify-content: center;
font-size: .85rem;
box-shadow: 0 0 12px var(--amber-glow);
}
.logo span { color: var(--amber-400); }
.topbar-sep {
width: 1px; height: 22px;
background: var(--border-mid); flex-shrink: 0;
}
.topbar-spacer { flex: 1; }
/* Transport controls */
.transport {
display: flex; align-items: center; gap: .5rem;
}
.t-btn {
display: flex; align-items: center; justify-content: center;
width: 36px; height: 36px;
border-radius: var(--r-md);
background: var(--bg-600);
border: 1px solid var(--border-mid);
color: var(--text-secondary);
font-size: .9rem;
transition: background var(--t), color var(--t), box-shadow var(--t);
}
.t-btn:hover {
background: var(--bg-500);
color: var(--text-primary);
}
.t-btn.play {
width: 42px; height: 42px;
background: linear-gradient(135deg, var(--amber-600), var(--amber-500));
color: #000;
font-size: 1rem;
border: none;
box-shadow: 0 0 16px var(--amber-glow);
}
.t-btn.play:hover {
background: linear-gradient(135deg, var(--amber-500), var(--amber-400));
box-shadow: 0 0 24px var(--amber-glow);
transform: scale(1.05);
}
.t-btn.play.active {
background: linear-gradient(135deg, var(--green-500), #16a34a);
box-shadow: 0 0 20px var(--green-glow);
color: #fff;
}
.t-btn.stop:hover { color: var(--red-500); }
.t-btn.rec:hover { color: var(--red-500); }
/* BPM control */
.bpm-wrap {
display: flex; align-items: center; gap: .6rem;
background: var(--bg-700);
border: 1px solid var(--border-mid);
border-radius: var(--r-md);
padding: .35rem .75rem;
}
.bpm-label {
font-size: .65rem; font-weight: 800;
text-transform: uppercase; letter-spacing: .1em;
color: var(--text-muted);
}
.bpm-val {
font-size: 1.05rem; font-weight: 900;
color: var(--amber-400);
min-width: 38px; text-align: center;
font-variant-numeric: tabular-nums;
}
.bpm-slider {
-webkit-appearance: none;
width: 100px; height: 4px;
background: var(--bg-500);
border-radius: 99px; outline: none;
cursor: pointer;
}
.bpm-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 14px; height: 14px;
border-radius: 50%;
background: var(--amber-400);
box-shadow: 0 0 8px var(--amber-glow);
cursor: pointer;
transition: transform var(--t), box-shadow var(--t);
}
.bpm-slider::-webkit-slider-thumb:hover {
transform: scale(1.3);
box-shadow: 0 0 14px var(--amber-glow);
}
.bpm-slider::-moz-range-thumb {
width: 14px; height: 14px;
border-radius: 50%; border: none;
background: var(--amber-400);
box-shadow: 0 0 8px var(--amber-glow);
cursor: pointer;
}
/* Step counter badge */
.step-badge {
display: flex; align-items: center; gap: .4rem;
font-size: .72rem; color: var(--text-muted);
white-space: nowrap;
}
.step-dot {
width: 7px; height: 7px; border-radius: 50%;
background: var(--green-500);
box-shadow: 0 0 6px var(--green-glow);
animation: pulse 1s ease-in-out infinite;
}
.step-dot.stopped { background: var(--bg-400); box-shadow: none; animation: none; }
@keyframes pulse {
0%,100% { opacity: 1; }
50% { opacity: .4; }
}
/* ═══════════════════════════════════════════════════════════
MAIN LAYOUT
═══════════════════════════════════════════════════════════ */
.main {
display: flex;
flex: 1;
overflow: hidden;
min-height: 0;
}
/* ═══════════════════════════════════════════════════════════
SIDEBAR — PRESETS
═══════════════════════════════════════════════════════════ */
.sidebar {
width: var(--sidebar-w);
flex-shrink: 0;
background: var(--bg-800);
border-right: 1px solid var(--border);
display: flex; flex-direction: column;
overflow-y: auto;
}
.sidebar-head {
padding: 1rem 1rem .5rem;
flex-shrink: 0;
}
.sidebar-head h2 {
font-size: .65rem; font-weight: 800;
letter-spacing: .12em; text-transform: uppercase;
color: var(--text-muted);
margin-bottom: .75rem;
}
.search-box {
display: flex; align-items: center; gap: .4rem;
background: var(--bg-700);
border: 1px solid var(--border-mid);
border-radius: var(--r-sm);
padding: .4rem .6rem;
}
.search-box input {
background: none; border: none; outline: none;
color: var(--text-primary); font-size: .78rem;
width: 100%;
}
.search-box input::placeholder { color: var(--text-muted); }
.search-icon { color: var(--text-muted); font-size: .8rem; flex-shrink: 0; }
.preset-section { padding: .5rem .75rem; }
.preset-section-label {
font-size: .6rem; font-weight: 800;
letter-spacing: .1em; text-transform: uppercase;
color: var(--text-muted);
padding: .5rem .25rem .3rem;
}
.preset-item {
display: flex; align-items: center; gap: .6rem;
padding: .55rem .6rem;
border-radius: var(--r-sm);
cursor: pointer;
transition: background var(--t), color var(--t);
border: 1px solid transparent;
}
.preset-item:hover {
background: var(--bg-600);
color: var(--text-primary);
}
.preset-item.active {
background: rgba(251,191,36,.1);
border-color: rgba(251,191,36,.2);
color: var(--amber-400);
}
.preset-dot {
width: 8px; height: 8px; border-radius: 50%;
flex-shrink: 0;
}
.preset-name {
font-size: .8rem; font-weight: 500;
flex: 1; color: inherit;
}
.preset-item.active .preset-name { font-weight: 700; }
.preset-tag {
font-size: .6rem; font-weight: 700;
padding: .1em .45em; border-radius: 4px;
background: rgba(255,255,255,.07);
color: var(--text-muted);
text-transform: uppercase; letter-spacing: .05em;
}
/* ═══════════════════════════════════════════════════════════
CONTENT AREA
═══════════════════════════════════════════════════════════ */
.content {
flex: 1; min-width: 0;
display: flex; flex-direction: column;
overflow-y: auto;
padding: 1.25rem;
gap: 1.25rem;
}
/* ── Section header ── */
.section-header {
display: flex; align-items: center;
justify-content: space-between;
margin-bottom: .75rem;
}
.section-title {
font-size: .65rem; font-weight: 800;
letter-spacing: .12em; text-transform: uppercase;
color: var(--text-muted);
display: flex; align-items: center; gap: .5rem;
}
.section-title::before {
content: '';
display: block; width: 3px; height: 12px;
background: linear-gradient(180deg, var(--amber-500), var(--amber-600));
border-radius: 99px;
box-shadow: 0 0 6px var(--amber-glow);
}
.chip-btn {
display: flex; align-items: center; gap: .3rem;
background: var(--bg-600);
border: 1px solid var(--border-mid);
border-radius: var(--r-sm);
padding: .3rem .65rem;
font-size: .72rem; font-weight: 600;
color: var(--text-secondary);
transition: all var(--t);
}
.chip-btn:hover { background: var(--bg-500); color: var(--text-primary); }
.chip-btn.active {
background: rgba(251,191,36,.12);
border-color: rgba(251,191,36,.3);
color: var(--amber-400);
}
/* ═══════════════════════════════════════════════════════════
PAD GRID 8 × 4
═══════════════════════════════════════════════════════════ */
.pad-panel {
background: var(--bg-800);
border: 1px solid var(--border);
border-radius: var(--r-xl);
padding: 1.25rem;
}
.pad-grid {
display: grid;
grid-template-columns: repeat(8, 1fr);
gap: .55rem;
}
.pad {
aspect-ratio: 1;
border-radius: var(--r-md);
display: flex; flex-direction: column;
align-items: center; justify-content: center;
gap: .25rem;
cursor: pointer;
position: relative;
overflow: hidden;
border: 1px solid rgba(255,255,255,.07);
transition:
transform var(--t),
box-shadow var(--t),
border-color var(--t),
filter var(--t);
user-select: none;
-webkit-tap-highlight-color: transparent;
}
/* Pad shimmer overlay */
.pad::before {
content: '';
position: absolute; inset: 0;
background: linear-gradient(135deg,
rgba(255,255,255,.08) 0%,
transparent 60%);
pointer-events: none;
}
/* Pad label */
.pad-label {
font-size: .58rem; font-weight: 800;
letter-spacing: .06em; text-transform: uppercase;
opacity: .6; z-index: 1; text-align: center;
line-height: 1.2;
}
.pad-icon {
font-size: 1.1rem; z-index: 1;
transition: transform var(--t);
}
.pad:hover {
transform: scale(1.04) translateY(-1px);
border-color: rgba(255,255,255,.18);
filter: brightness(1.15);
}
.pad:active, .pad.hit {
transform: scale(.94);
filter: brightness(1.4);
}
/* Colour themes per row */
.pad.row-0 { background: linear-gradient(145deg, #1f1a10, #2a2210); color: #fcd34d; }
.pad.row-0:hover { box-shadow: 0 0 18px rgba(251,191,36,.3); }
.pad.row-0.active-pad {
background: linear-gradient(145deg, var(--amber-600), var(--amber-500));
color: #000;
border-color: var(--amber-400);
box-shadow: 0 0 24px var(--amber-glow), inset 0 1px 0 rgba(255,255,255,.2);
}
.pad.row-1 { background: linear-gradient(145deg, #101a1f, #102230); color: #93c5fd; }
.pad.row-1:hover { box-shadow: 0 0 18px rgba(59,130,246,.25); }
.pad.row-1.active-pad {
background: linear-gradient(145deg, #1d4ed8, #3b82f6);
color: #fff;
border-color: #60a5fa;
box-shadow: 0 0 24px rgba(59,130,246,.45), inset 0 1px 0 rgba(255,255,255,.2);
}
.pad.row-2 { background: linear-gradient(145deg, #1a101a, #22102a); color: #d8b4fe; }
.pad.row-2:hover { box-shadow: 0 0 18px rgba(168,85,247,.25); }
.pad.row-2.active-pad {
background: linear-gradient(145deg, #7e22ce, #a855f7);
color: #fff;
border-color: #c084fc;
box-shadow: 0 0 24px rgba(168,85,247,.45), inset 0 1px 0 rgba(255,255,255,.2);
}
.pad.row-3 { background: linear-gradient(145deg, #101a10, #102210); color: #86efac; }
.pad.row-3:hover { box-shadow: 0 0 18px rgba(34,197,94,.25); }
.pad.row-3.active-pad {
background: linear-gradient(145deg, #15803d, #22c55e);
color: #fff;
border-color: #4ade80;
box-shadow: 0 0 24px rgba(34,197,94,.45), inset 0 1px 0 rgba(255,255,255,.2);
}
/* ═══════════════════════════════════════════════════════════
SEQUENCER
═══════════════════════════════════════════════════════════ */
.seq-panel {
background: var(--bg-800);
border: 1px solid var(--border);
border-radius: var(--r-xl);
padding: 1.25rem;
}
.seq-rows { display: flex; flex-direction: column; gap: .5rem; }
.seq-row {
display: grid;
grid-template-columns: 72px 1fr;
align-items: center;
gap: .75rem;
}
.seq-row-label {
font-size: .7rem; font-weight: 700;
color: var(--text-secondary);
text-align: right;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.seq-steps {
display: grid;
grid-template-columns: repeat(16, 1fr);
gap: .3rem;
}
.seq-step {
aspect-ratio: 1;
border-radius: var(--r-sm);
background: var(--bg-600);
border: 1px solid var(--border);
cursor: pointer;
position: relative;
transition: background var(--t), box-shadow var(--t), border-color var(--t), transform var(--t);
}
/* Beat grouping — subtle separator every 4 steps */
.seq-step:nth-child(4n+1) { margin-left: .15rem; }
.seq-step:hover {
background: var(--bg-500);
border-color: rgba(255,255,255,.18);
transform: scale(1.08);
}
/* Active (toggled on) step */
.seq-step.on {
background: linear-gradient(135deg, var(--amber-600), var(--amber-400));
border-color: var(--amber-400);
box-shadow: 0 0 10px var(--amber-glow), 0 0 3px var(--amber-glow-sm);
}
.seq-step.on:hover {
background: linear-gradient(135deg, var(--amber-500), var(--amber-300));
box-shadow: 0 0 16px var(--amber-glow);
}
/* Playhead highlight */
.seq-step.playing {
outline: 2px solid rgba(255,255,255,.5);
outline-offset: 1px;
}
.seq-step.on.playing {
background: linear-gradient(135deg, #fff, var(--amber-300));
box-shadow: 0 0 20px rgba(255,255,255,.4), 0 0 10px var(--amber-glow);
}
/* Beat number ticks */
.seq-ticks {
display: grid;
grid-template-columns: repeat(16, 1fr);
gap: .3rem;
margin-bottom: .3rem;
padding-left: calc(72px + .75rem);
}
.seq-tick {
font-size: .55rem; font-weight: 800;
color: var(--text-muted);
text-align: center;
letter-spacing: 0;
}
.seq-tick.beat { color: var(--amber-600); }
/* ═══════════════════════════════════════════════════════════
MIXER STRIP
═══════════════════════════════════════════════════════════ */
.mixer-panel {
background: var(--bg-800);
border: 1px solid var(--border);
border-radius: var(--r-xl);
padding: 1.25rem;
}
.mixer-strips {
display: grid;
grid-template-columns: repeat(8, 1fr);
gap: .6rem;
}
.mixer-strip {
display: flex; flex-direction: column;
align-items: center; gap: .5rem;
}
.mix-label {
font-size: .58rem; font-weight: 700;
color: var(--text-muted); text-align: center;
text-transform: uppercase; letter-spacing: .05em;
line-height: 1.2;
}
.mix-fader-wrap {
display: flex; flex-direction: column;
align-items: center; gap: .3rem;
width: 100%;
}
.mix-fader {
-webkit-appearance: none;
writing-mode: vertical-lr;
direction: rtl;
width: 4px; height: 60px;
background: var(--bg-500);
border-radius: 99px; outline: none;
cursor: pointer;
}
.mix-fader::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px; height: 10px;
border-radius: 4px;
background: linear-gradient(135deg, var(--bg-400), var(--bg-300, #444));
border: 1px solid rgba(255,255,255,.15);
cursor: pointer;
box-shadow: 0 2px 4px rgba(0,0,0,.4);
transition: background var(--t);
}
.mix-fader::-webkit-slider-thumb:hover {
background: linear-gradient(135deg, var(--amber-600), var(--amber-500));
box-shadow: 0 0 8px var(--amber-glow);
}
.mix-val {
font-size: .6rem; font-weight: 700;
color: var(--text-muted);
font-variant-numeric: tabular-nums;
}
.mix-mute {
width: 28px; height: 18px;
border-radius: 4px;
background: var(--bg-600);
border: 1px solid var(--border-mid);
font-size: .55rem; font-weight: 800;
color: var(--text-muted);
transition: all var(--t);
text-transform: uppercase;
letter-spacing: .05em;
}
.mix-mute:hover { background: var(--bg-500); color: var(--text-primary); }
.mix-mute.muted {
background: rgba(239,68,68,.15);
border-color: rgba(239,68,68,.3);
color: var(--red-500);
}
/* ═══════════════════════════════════════════════════════════
VU METER
═══════════════════════════════════════════════════════════ */
.vu-meter {
display: flex; gap: 2px; align-items: flex-end;
height: 32px;
}
.vu-bar {
width: 4px; border-radius: 2px;
background: var(--bg-500);
transition: height .08s ease, background .08s ease;
}
/* ═══════════════════════════════════════════════════════════
STATUS BAR
═══════════════════════════════════════════════════════════ */
.statusbar {
display: flex; align-items: center; gap: 1rem;
padding: .45rem 1.25rem;
background: var(--bg-800);
border-top: 1px solid var(--border);
font-size: .68rem; color: var(--text-muted);
flex-shrink: 0;
}
.status-item { display: flex; align-items: center; gap: .35rem; }
.status-dot {
width: 6px; height: 6px; border-radius: 50%;
background: var(--green-500);
box-shadow: 0 0 5px var(--green-glow);
}
.status-dot.amber {
background: var(--amber-500);
box-shadow: 0 0 5px var(--amber-glow);
}
.status-spacer { flex: 1; }
.status-val { color: var(--text-secondary); font-weight: 600; }
/* ═══════════════════════════════════════════════════════════
RESPONSIVE
═══════════════════════════════════════════════════════════ */
@media (max-width: 900px) {
.sidebar { display: none; }
.pad-grid { grid-template-columns: repeat(4, 1fr); }
.mixer-strips { grid-template-columns: repeat(4, 1fr); }
.bpm-slider { width: 70px; }
}
@media (max-width: 600px) {
.seq-row { grid-template-columns: 52px 1fr; }
.seq-row-label { font-size: .6rem; }
.topbar { flex-wrap: wrap; gap: .5rem; }
}
</style>
</head>
<body>
<!-- ══════════════════════════════════════════════════════════════
TOP BAR
══════════════════════════════════════════════════════════════ -->
<header class="topbar">
<div class="logo">
<div class="logo-icon">🥁</div>
Beat<span>Forge</span>
</div>
<div class="topbar-sep"></div>
<!-- Transport -->
<div class="transport">
<button class="t-btn rec" id="btn-rec" title="Record">⏺</button>
<button class="t-btn stop" id="btn-stop" title="Stop">⏹</button>
<button class="t-btn play" id="btn-play" title="Play / Pause">▶</button>
</div>
<div class="topbar-sep"></div>
<!-- BPM -->
<div class="bpm-wrap">
<span class="bpm-label">BPM</span>
<input type="range" class="bpm-slider" id="bpm-slider"
min="60" max="200" value="120"/>
<span class="bpm-val" id="bpm-val">120</span>
</div>
<div class="topbar-sep"></div>
<!-- Step indicator -->
<div class="step-badge">
<div class="step-dot stopped" id="play-dot"></div>
<span id="step-label">Stopped</span>
</div>
<div class="topbar-spacer"></div>
<!-- Swing / Quantise chips -->
<button class="chip-btn" id="btn-swing">⟳ Swing 0%</button>
<button class="chip-btn active" id="btn-quant">⊞ Quantise</button>
<button class="chip-btn" id="btn-clear">✕ Clear</button>
</header>
<!-- ══════════════════════════════════════════════════════════════
MAIN
══════════════════════════════════════════════════════════════ -->
<div class="main">
<!-- SIDEBAR -->
<aside class="sidebar">
<div class="sidebar-head">
<h2>🎛 Kit Presets</h2>
<div class="search-box">
<span class="search-icon">🔍</span>
<input type="text" placeholder="Search kits…" id="preset-search"/>
</div>
</div>
<div class="preset-section">
<div class="preset-section-label">Factory</div>
<div class="preset-item active" data-kit="0">
<div class="preset-dot" style="background:#fbbf24"></div>
<span class="preset-name">Classic 808</span>
<span class="preset-tag">Hip-Hop</span>
</div>
<div class="preset-item" data-kit="1">
<div class="preset-dot" style="background:#3b82f6"></div>
<span class="preset-name">TR-909 House</span>
<span class="preset-tag">House</span>
</div>
<div class="preset-item" data-kit="2">
<div class="preset-dot" style="background:#a855f7"></div>
<span class="preset-name">Jungle Breaks</span>
<span class="preset-tag">DnB</span>
</div>
<div class="preset-item" data-kit="3">
<div class="preset-dot" style="background:#22c55e"></div>
<span class="preset-name">Lo-Fi Boom Bap</span>
<span class="preset-tag">Lo-Fi</span>
</div>
<div class="preset-item" data-kit="4">
<div class="preset-dot" style="background:#ef4444"></div>
<span class="preset-name">Trap Kit</span>
<span class="preset-tag">Trap</span>
</div>
<div class="preset-item" data-kit="5">
<div class="preset-dot" style="background:#f97316"></div>
<span class="preset-name">Afrobeats</span>
<span class="preset-tag">Afro</span>
</div>
<div class="preset-section-label" style="margin-top:.5rem">User</div>
<div class="preset-item" data-kit="6">
<div class="preset-dot" style="background:#06b6d4"></div>
<span class="preset-name">My Session 1</span>
<span class="preset-tag">Custom</span>
</div>
<div class="preset-item" data-kit="7">
<div class="preset-dot" style="background:#84cc16"></div>
<span class="preset-name">Late Night WIP</span>
<span class="preset-tag">Custom</span>
</div>
</div>
</aside>
<!-- CONTENT -->
<div class="content">
<!-- ── PAD GRID ──────────────────────────────────────────── -->
<div class="pad-panel">
<div class="section-header">
<div class="section-title">Drum Pads — 8 × 4</div>
<div style="display:flex;gap:.4rem">
<button class="chip-btn" id="btn-vel">Velocity</button>
<button class="chip-btn active">Chromatic</button>
</div>
</div>
<div class="pad-grid" id="pad-grid"></div>
</div>
<!-- ── SEQUENCER ─────────────────────────────────────────── -->
<div class="seq-panel">
<div class="section-header">
<div class="section-title">16-Step Pattern Sequencer</div>
<div style="display:flex;gap:.4rem">
<button class="chip-btn" id="btn-steps-8">8</button>
<button class="chip-btn active" id="btn-steps-16">16</button>
<button class="chip-btn" id="btn-steps-32">32</button>
</div>
</div>
<!-- Beat ticks -->
<div class="seq-ticks" id="seq-ticks"></div>
<!-- Rows -->
<div class="seq-rows" id="seq-rows"></div>
</div>
<!-- ── MIXER ─────────────────────────────────────────────── -->
<div class="mixer-panel">
<div class="section-header">
<div class="section-title">Channel Mixer</div>
</div>
<div class="mixer-strips" id="mixer-strips"></div>
</div>
</div><!-- /.content -->
</div><!-- /.main -->
<!-- STATUS BAR -->
<footer class="statusbar">
<div class="status-item">
<div class="status-dot" id="audio-dot"></div>
<span>Audio Engine Ready</span>
</div>
<div class="status-item">
<div class="status-dot amber"></div>
<span>Kit: <span class="status-val" id="status-kit">Classic 808</span></span>
</div>
<div class="status-item">
Step: <span class="status-val" id="status-step">—</span>
</div>
<div class="status-item">
Pattern: <span class="status-val">A1</span>
</div>
<div class="status-spacer"></div>
<div class="status-item">
CPU: <span class="status-val" id="cpu-val">2%</span>
</div>
<div class="status-item">
Latency: <span class="status-val">8ms</span>
</div>
<div class="status-item">
44.1 kHz · 24-bit
</div>
</footer>
<!-- ══════════════════════════════════════════════════════════════
JAVASCRIPT
══════════════════════════════════════════════════════════════ -->
<script>
(() => {
'use strict';
/* ── PAD DEFINITIONS ─────────────────────────────────────── */
const PADS = [
// Row 0 — Amber (kick / snare family)
{ label: 'Kick', icon: '🥁', row: 0, freq: 60, type: 'kick' },
{ label: 'Snare', icon: '🪘', row: 0, freq: 200, type: 'snare' },
{ label: 'Rim', icon: '🔔', row: 0, freq: 400, type: 'rim' },
{ label: 'Clap', icon: '👏', row: 0, freq: 800, type: 'clap' },
{ label: 'Snap', icon: '✨', row: 0, freq: 1200,type: 'snap' },
{ label: 'Tom Hi', icon: '🥁', row: 0, freq: 180, type: 'tom' },
{ label: 'Tom Mid', icon: '🥁', row: 0, freq: 120, type: 'tom' },
{ label: 'Tom Lo', icon: '🥁', row: 0, freq: 80, type: 'tom' },
// Row 1 — Blue (hi-hats / cymbals)
{ label: 'HH Cls', icon: '🎵', row: 1, freq: 8000, type: 'hh' },
{ label: 'HH Opn', icon: '🎶', row: 1, freq: 6000, type: 'hh' },
{ label: 'HH Pdl', icon: '🎵', row: 1, freq: 7000, type: 'hh' },
{ label: 'Crash', icon: '💥', row: 1, freq: 5000, type: 'cym' },
{ label: 'Ride', icon: '🔔', row: 1, freq: 4500, type: 'cym' },
{ label: 'Bell', icon: '🛎', row: 1, freq: 3500, type: 'bell' },
{ label: 'Shaker', icon: '🎼', row: 1, freq: 9000, type: 'perc' },
{ label: 'Tamb', icon: '🎵', row: 1, freq: 7500, type: 'perc' },
// Row 2 — Purple (synth / FX)
{ label: '808 Sub', icon: '〰', row: 2, freq: 40, type: '808' },
{ label: 'Zap', icon: '⚡', row: 2, freq: 500, type: 'synth' },
{ label: 'Blip', icon: '🔵', row: 2, freq: 900, type: 'synth' },
{ label: 'Sweep', icon: '🌊', row: 2, freq: 300, type: 'synth' },
{ label: 'Noise', icon: '📡', row: 2, freq: 0, type: 'noise' },
{ label: 'Rev Cym', icon: '🔄', row: 2, freq: 4000,type: 'fx' },
{ label: 'Vinyl', icon: '💿', row: 2, freq: 100, type: 'fx' },
{ label: 'FX Hit', icon: '🎯', row: 2, freq: 600, type: 'fx' },
// Row 3 — Green (percussion)
{ label: 'Conga Hi',icon: '🪘', row: 3, freq: 350, type: 'conga' },
{ label: 'Conga Lo',icon: '🪘', row: 3, freq: 220, type: 'conga' },
{ label: 'Bongo', icon: '🥁', row: 3, freq: 450, type: 'bongo' },
{ label: 'Cowbell', icon: '🔔', row: 3, freq: 550, type: 'cowbell'},
{ label: 'Woodblk', icon: '🪵', row: 3, freq: 700, type: 'wood' },
{ label: 'Maracas', icon: '🎶', row: 3, freq: 8500,type: 'perc' },
{ label: 'Cabasa', icon: '🎵', row: 3, freq: 7200,type: 'perc' },
{ label: 'Claves', icon: '🥢', row: 3, freq: 1800,type: 'wood' },
];
/* Sequencer row labels (one per row, 8 rows for 8 pads per row) */
const SEQ_ROWS = [
{ label: 'Kick', color: '#fbbf24' },
{ label: 'Snare', color: '#fbbf24' },
{ label: 'HH Cls', color: '#60a5fa' },
{ label: 'HH Opn', color: '#60a5fa' },
{ label: '808 Sub', color: '#c084fc' },
{ label: 'Zap', color: '#c084fc' },
{ label: 'Conga Hi', color: '#4ade80' },
{ label: 'Cowbell', color: '#4ade80' },
];
/* Default pattern (8 rows × 16 steps) */
const DEFAULT_PATTERN = [
[1,0,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0], // Kick
[0,0,0,0, 1,0,0,0, 0,0,0,0, 1,0,0,0], // Snare
[1,0,1,0, 1,0,1,0, 1,0,1,0, 1,0,1,0], // HH Cls
[0,0,0,0, 0,0,0,1, 0,0,0,0, 0,0,1,0], // HH Opn
[1,0,0,0, 0,0,0,0, 1,0,0,0, 0,0,0,0], // 808 Sub
[0,0,0,0, 0,0,1,0, 0,0,0,0, 0,1,0,0], // Zap
[0,0,1,0, 0,0,0,0, 0,1,0,0, 0,0,0,0], // Conga Hi
[0,0,0,0, 0,0,0,0, 0,0,0,0, 1,0,0,0], // Cowbell
];
/* ── STATE ───────────────────────────────────────────────── */
let bpm = 120;
let playing = false;
let currentStep = -1;
let stepTimer = null;
let pattern = DEFAULT_PATTERN.map(r => [...r]);
let audioCtx = null;
let swingPct = 0;
let volumes = new Array(8).fill(80);
let muted = new Array(8).fill(false);
/* ── AUDIO ───────────────────────────────────────────────── */
function getAudio() {
if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
return audioCtx;
}
function playSound(pad) {
try {
const ctx = getAudio();
const gain = ctx.createGain();
gain.connect(ctx.destination);
if (pad.type === 'noise') {
const buf = ctx.createBuffer(1, ctx.sampleRate * 0.15, ctx.sampleRate);
const data = buf.getChannelData(0);
for (let i = 0; i < data.length; i++) data[i] = Math.random() * 2 - 1;
const src = ctx.createBufferSource();
src.buffer = buf;
const filt = ctx.createBiquadFilter();
filt.type = 'bandpass'; filt.frequency.value = 2000;
src.connect(filt); filt.connect(gain);
gain.gain.setValueAtTime(.4, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(.001, ctx.currentTime + .15);
src.start(); src.stop(ctx.currentTime + .15);
return;
}
const osc = ctx.createOscillator();
const now = ctx.currentTime;
if (pad.type === 'kick') {
osc.frequency.setValueAtTime(160, now);
osc.frequency.exponentialRampToValueAtTime(pad.freq, now + .12);
gain.gain.setValueAtTime(.9, now);
gain.gain.exponentialRampToValueAtTime(.001, now + .35);
osc.type = 'sine';
} else if (pad.type === 'snare' || pad.type === 'clap') {
osc.type = 'triangle';
osc.frequency.setValueAtTime(pad.freq, now);
osc.frequency.exponentialRampToValueAtTime(pad.freq * .5, now + .08);
gain.gain.setValueAtTime(.6, now);
gain.gain.exponentialRampToValueAtTime(.001, now + .18);
// Add noise layer
const nbuf = ctx.createBuffer(1, ctx.sampleRate * .18, ctx.sampleRate);
const nd = nbuf.getChannelData(0);
for (let i = 0; i < nd.length; i++) nd[i] = Math.random() * 2 - 1;
const nsrc = ctx.createBufferSource();
nsrc.buffer = nbuf;
const ng = ctx.createGain();
ng.gain.setValueAtTime(.35, now);
ng.gain.exponentialRampToValueAtTime(.001, now + .18);
nsrc.connect(ng); ng.connect(ctx.destination);
nsrc.start();
} else if (pad.type === 'hh') {
osc.type = 'square';
osc.frequency.value = pad.freq;
gain.gain.setValueAtTime(.25, now);
const dur = pad.label.includes('Opn') ? .25 : .04;
gain.gain.exponentialRampToValueAtTime(.001, now + dur);
} else if (pad.type === '808') {
osc.type = 'sine';
osc.frequency.setValueAtTime(pad.freq * 3, now);
osc.frequency.exponentialRampToValueAtTime(pad.freq, now + .08);
gain.gain.setValueAtTime(.8, now);
gain.gain.exponentialRampToValueAtTime(.001, now + .6);
} else {
osc.type = ['sine','triangle','sawtooth','square'][pad.row] || 'sine';
osc.frequency.value = pad.freq;
gain.gain.setValueAtTime(.4, now);
gain.gain.exponentialRampToValueAtTime(.001, now + .12);
}
osc.connect(gain);
osc.start(now);
osc.stop(now + .8);
} catch(e) { /* silent */ }
}
/* ── BUILD PAD GRID ──────────────────────────────────────── */
const padGrid = document.getElementById('pad-grid');
PADS.forEach((pad, i) => {
const el = document.createElement('div');
el.className = `pad row-${pad.row}`;
el.dataset.idx = i;
el.innerHTML = `<span class="pad-icon">${pad.icon}</span><span class="pad-label">${pad.label}</span>`;
el.addEventListener('pointerdown', () => {
playSound(pad);
el.classList.add('hit');
setTimeout(() => el.classList.remove('hit'), 120);
});
padGrid.appendChild(el);
});
/* ── BUILD SEQUENCER ─────────────────────────────────────── */
const seqTicks = document.getElementById('seq-ticks');
const seqRows = document.getElementById('seq-rows');
// Ticks
for (let i = 0; i < 16; i++) {
const t = document.createElement('div');
t.className = 'seq-tick' + (i % 4 === 0 ? ' beat' : '');
t.textContent = i % 4 === 0 ? (i/4 + 1) : '·';
seqTicks.appendChild(t);
}
// Rows
SEQ_ROWS.forEach((row, ri) => {
const rowEl = document.createElement('div');
rowEl.className = 'seq-row';
const lbl = document.createElement('div');
lbl.className = 'seq-row-label';
lbl.textContent = row.label;
lbl.style.color = row.color;
rowEl.appendChild(lbl);
const steps = document.createElement('div');
steps.className = 'seq-steps';
steps.dataset.row = ri;
for (let si = 0; si < 16; si++) {
const step = document.createElement('div');
step.className = 'seq-step' + (pattern[ri][si] ? ' on' : '');
step.dataset.row = ri;
step.dataset.step = si;
step.addEventListener('click', () => {
pattern[ri][si] = pattern[ri][si] ? 0 : 1;
step.classList.toggle('on', !!pattern[ri][si]);
});
steps.appendChild(step);
}
rowEl.appendChild(steps);
seqRows.appendChild(rowEl);
});
/* ── BUILD MIXER ─────────────────────────────────────────── */
const mixerStrips = document.getElementById('mixer-strips');
SEQ_ROWS.forEach((row, ri) => {
const strip = document.createElement('div');
strip.className = 'mixer-strip';
strip.innerHTML = `
<div class="mix-label">${row.label}</div>
<div class="vu-meter" id="vu-${ri}">
${[...Array(6)].map((_,i) => `<div class="vu-bar" style="height:${4+i*4}px"></div>`).join('')}
</div>
<div class="mix-fader-wrap">
<input type="range" class="mix-fader" min="0" max="100"
value="${volumes[ri]}" data-row="${ri}"
orient="vertical"/>
<span class="mix-val" id="vol-val-${ri}">${volumes[ri]}</span>
</div>
<button class="mix-mute" data-row="${ri}">M</button>
`;
mixerStrips.appendChild(strip);
});
// Fader events
document.querySelectorAll('.mix-fader').forEach(f => {
f.addEventListener('input', () => {
const ri = parseInt(f.dataset.row);
volumes[ri] = parseInt(f.value);
document.getElementById(`vol-val-${ri}`).textContent = f.value;
});
});
// Mute events
document.querySelectorAll('.mix-mute').forEach(btn => {
btn.addEventListener('click', () => {
const ri = parseInt(btn.dataset.row);
muted[ri] = !muted[ri];
btn.classList.toggle('muted', muted[ri]);
});
});
/* ── SEQUENCER ENGINE ────────────────────────────────────── */
function getStepMs() {
const beatMs = (60 / bpm) * 1000;
const stepMs = beatMs / 4; // 16th notes
return stepMs;
}
function advanceStep() {
currentStep = (currentStep + 1) % 16;
// Clear previous playhead
document.querySelectorAll('.seq-step.playing')
.forEach(s => s.classList.remove('playing'));
// Highlight current step column
document.querySelectorAll(`.seq-step[data-step="${currentStep}"]`)
.forEach(s => s.classList.add('playing'));
// Fire sounds
SEQ_ROWS.forEach((_, ri) => {
if (pattern[ri][currentStep] && !muted[ri]) {
const padIdx = ri * 8; // first pad of each row
playSound(PADS[padIdx]);
animateVU(ri);
}
});
// Update status
document.getElementById('status-step').textContent =
`${Math.floor(currentStep / 4) + 1}.${(currentStep % 4) + 1}`;
// Swing: odd steps get a slight delay
const swing = currentStep % 2 === 1 ? (swingPct / 100) * getStepMs() * .5 : 0;
stepTimer = setTimeout(advanceStep, getStepMs() + swing);
}
function startSeq() {
if (playing) return;
playing = true;
currentStep = -1;
document.getElementById('btn-play').classList.add('active');
document.getElementById('btn-play').textContent = '⏸';
document.getElementById('play-dot').classList.remove('stopped');
document.getElementById('step-label').textContent = 'Playing';
advanceStep();
}
function stopSeq() {
playing = false;
clearTimeout(stepTimer);
currentStep = -1;
document.querySelectorAll('.seq-step.playing')
.forEach(s => s.classList.remove('playing'));
document.getElementById('btn-play').classList.remove('active');
document.getElementById('btn-play').textContent = '▶';
document.getElementById('play-dot').classList.add('stopped');
document.getElementById('step-label').textContent = 'Stopped';
document.getElementById('status-step').textContent = '—';
}
/* ── VU METER ANIMATION ──────────────────────────────────── */
function animateVU(ri) {
const vu = document.getElementById(`vu-${ri}`);
if (!vu) return;
const bars = vu.querySelectorAll('.vu-bar');
const colors = ['#22c55e','#22c55e','#22c55e','#fbbf24','#f97316','#ef4444'];
bars.forEach((b, i) => {
const h = 4 + Math.random() * 24;
b.style.height = h + 'px';
b.style.background = h > 20 ? colors[5] : h > 14 ? colors[3] : colors[0];
});
setTimeout(() => {
bars.forEach((b, i) => {
b.style.height = (4 + i * 4) + 'px';
b.style.background = 'var(--bg-500)';
});
}, 140);
}
/* ── CONTROLS ────────────────────────────────────────────── */
document.getElementById('btn-play').addEventListener('click', () => {
if (playing) stopSeq(); else startSeq();
});
document.getElementById('btn-stop').addEventListener('click', stopSeq);
document.getElementById('bpm-slider').addEventListener('input', e => {
bpm = parseInt(e.target.value);
document.getElementById('bpm-val').textContent = bpm;
});
document.getElementById('btn-clear').addEventListener('click', () => {
pattern = SEQ_ROWS.map(() => new Array(16).fill(0));
document.querySelectorAll('.seq-step').forEach(s => s.classList.remove('on'));
});
// Swing
let swingIdx = 0;
const swingVals = [0, 15, 30, 50, 65];
document.getElementById('btn-swing').addEventListener('click', () => {
swingIdx = (swingIdx + 1) % swingVals.length;
swingPct = swingVals[swingIdx];
document.getElementById('btn-swing').textContent = `⟳ Swing ${swingPct}%`;
});
// Presets
const kitNames = [
'Classic 808','TR-909 House','Jungle Breaks',
'Lo-Fi Boom Bap','Trap Kit','Afrobeats',
'My Session 1','Late Night WIP'
];
document.querySelectorAll('.preset-item').forEach(item => {
item.addEventListener('click', () => {
document.querySelectorAll('.preset-item').forEach(i => i.classList.remove('active'));
item.classList.add('active');
const name = item.querySelector('.preset-name').textContent;
document.getElementById('status-kit').textContent = name;
// Randomise pattern for demo
pattern = SEQ_ROWS.map(() =>
Array.from({length:16}, () => Math.random() > .72 ? 1 : 0)
);
document.querySelectorAll('.seq-step').forEach(s => {
const ri = parseInt(s.dataset.row);
const si = parseInt(s.dataset.step);
s.classList.toggle('on', !!pattern[ri][si]);
});
});
});
// CPU fake counter
setInterval(() => {
if (playing) {
document.getElementById('cpu-val').textContent =
(2 + Math.random() * 4).toFixed(0) + '%';
}
}, 1500);
// Keyboard shortcuts
document.addEventListener('keydown', e => {
if (e.target.tagName === 'INPUT') return;
if (e.code === 'Space') { e.preventDefault(); playing ? stopSeq() : startSeq(); }
});
})();
</script>
</body>
</html>
Live Preview