An AI composer that creates original music in any style, to help you find inspiration.
---
format: typebulb/v1
name: Bach
---
**code.tsx**
```tsx
import React, { useState, useEffect, useRef, useCallback, useMemo } from "react";
import { createRoot } from "react-dom/client";
// ============ Types ============
type WaveformType = "sine" | "square" | "sawtooth" | "triangle";
type FilterType = "lowpass" | "highpass" | "bandpass" | "notch";
interface OscSettings { waveform: WaveformType; detune: number; gain: number }
interface FilterSettings { type: FilterType; frequency: number; Q: number }
interface EnvSettings { attack: number; decay: number; sustain: number; release: number }
interface FxSettings { delayTime: number; delayFeedback: number; reverbMix: number }
interface SequenceNote { freq: number; time: number; duration: number; voice?: number }
interface Composition { patch: SynthState; score: string | string[]; name?: string }
interface SynthState {
osc1: OscSettings;
osc2: OscSettings;
filter: FilterSettings;
env: EnvSettings;
fx: FxSettings;
master: number;
}
// ============ Constants ============
const NOTE_NAMES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
const KEY_BINDINGS: Record<string, string> = {
'C4': 'a', 'C#4': 'w', 'D4': 's', 'D#4': 'e', 'E4': 'd', 'F4': 'f',
'F#4': 't', 'G4': 'g', 'G#4': 'y', 'A4': 'h', 'A#4': 'u', 'B4': 'j',
'C5': 'k', 'C#5': 'o', 'D5': 'l', 'D#5': 'p', 'E5': ';',
};
function noteToFreq(note: string): number | null {
const m = note.match(/^([A-G])(#|b)?(\d)$/);
if (!m) return null;
const semi: Record<string, number> = { C: 0, D: 2, E: 4, F: 5, G: 7, A: 9, B: 11 };
let s = semi[m[1]];
if (m[2] === '#') s++; else if (m[2] === 'b') s--;
return 440 * Math.pow(2, (s - 9) / 12 + (parseInt(m[3]) - 4));
}
function parseChord(tok: string): number[] {
if (tok.startsWith('[') && tok.endsWith(']'))
return tok.slice(1, -1).split(',').map(n => noteToFreq(n.trim())).filter((f): f is number => f !== null);
const f = noteToFreq(tok);
return f ? [f] : [];
}
const KEYBOARD: [string, number, string][] = [];
for (let oct = 2; oct <= 6; oct++) {
for (let i = 0; i < NOTE_NAMES.length; i++) {
if (oct === 6 && i > 0) break;
const note = NOTE_NAMES[i];
const name = `${note}${oct}`;
const freq = noteToFreq(name)!;
KEYBOARD.push([KEY_BINDINGS[name] || `_${name.toLowerCase().replace('#', 's')}`, freq, note]);
}
}
const DEFAULT_STATE: SynthState = {
osc1: { waveform: "sawtooth", detune: 0, gain: 0.3 },
osc2: { waveform: "square", detune: -5, gain: 0.2 },
filter: { type: "lowpass", frequency: 2000, Q: 1 },
env: { attack: 0.01, decay: 0.1, sustain: 0.7, release: 0.3 },
fx: { delayTime: 0, delayFeedback: 0, reverbMix: 0.2 },
master: 0.35,
};
// ============ Formatters ============
const fmt = {
pct: (v: number) => (v * 100).toFixed(0),
signed: (v: number) => (v > 0 ? '+' : '') + v.toFixed(0),
freq: (v: number) => v < 1000 ? v.toFixed(0) : (v / 1000).toFixed(1) + 'k',
time: (v: number) => v < 1 ? (v * 1000).toFixed(0) : v.toFixed(1) + 's',
ms: (v: number) => (v * 1000).toFixed(0),
dec: (v: number) => v.toFixed(1),
};
function parseScore(input: any): SequenceNote[] {
if (Array.isArray(input)) {
if (input.length && typeof input[0] === 'string') return parseScore(input.join('\n'));
return input;
}
if (typeof input !== 'string') return [];
if (input.includes('@bpm') || input.includes('@step')) return parseTracker(input);
const tokens = input.trim().split(/\s+/);
const notes: SequenceNote[] = [];
let t = 0;
for (let i = 0; i < tokens.length; i += 2) {
const pitch = tokens[i];
const dur = parseFloat(tokens[i + 1]);
if (isNaN(dur)) continue;
if (pitch !== 'R') {
for (const freq of parseChord(pitch))
notes.push({ freq, time: t, duration: dur });
}
t += dur;
}
return notes;
}
function parseTracker(input: string): SequenceNote[] {
const lines = input.split('\n');
let bpm = 120;
let stepNum = 1, stepDen = 8;
let swing = 0.5;
const rows: string[][] = [];
for (const line of lines) {
const t = line.trim();
if (!t || t.startsWith('#') || /^[-=]+$/.test(t)) continue;
if (t.startsWith('@bpm')) { bpm = parseFloat(t.split(/\s+/)[1]); continue; }
if (t.startsWith('@step')) {
const p = t.split(/\s+/)[1].split('/');
stepNum = parseInt(p[0]); stepDen = parseInt(p[1]); continue;
}
if (t.startsWith('@swing')) { swing = parseFloat(t.split(/\s+/)[1]); continue; }
if (t.startsWith('@')) continue;
rows.push(t.split(/\s+/));
}
const stepDur = (60 / bpm) * 4 * (stepNum / stepDen);
const pairDur = 2 * stepDur;
const st = (i: number) => {
const p = Math.floor(i / 2) * pairDur;
return i % 2 === 0 ? p : p + swing * pairDur;
};
const voiceCount = rows.length > 0 ? Math.max(...rows.map(r => r.length)) : 0;
const active: ({ freqs: number[]; start: number; dur: number } | null)[] = Array(voiceCount).fill(null);
const notes: SequenceNote[] = [];
const flush = (v: number) => {
if (!active[v]) return;
for (const freq of active[v]!.freqs)
notes.push({ freq, time: active[v]!.start, duration: active[v]!.dur, voice: v });
active[v] = null;
};
for (let i = 0; i < rows.length; i++) {
const time = st(i);
const dur = st(i + 1) - time;
for (let v = 0; v < voiceCount; v++) {
const tok = (rows[i][v] || '.').trim();
if (tok === '|') {
if (active[v]) active[v]!.dur += dur;
} else if (tok === '.') {
flush(v);
} else {
flush(v);
const freqs = parseChord(tok);
if (freqs.length) active[v] = { freqs, start: time, dur };
}
}
}
for (let v = 0; v < voiceCount; v++) flush(v);
return notes;
}
// ============ Audio Engine ============
class SynthEngine {
public ctx: AudioContext | null = null;
private nodes: { master: GainNode; filter: BiquadFilterNode; delay: DelayNode; delayGain: GainNode; delayWet: GainNode; reverbGain: GainNode; dryGain: GainNode } | null = null;
private activeNotes = new Map<string, { oscs: OscillatorNode[]; gains: GainNode[] }>();
private sequenceTimeouts: number[] = [];
init(state: SynthState): boolean {
if (this.ctx) return false;
this.ctx = new AudioContext();
const { ctx } = this;
const master = ctx.createGain();
master.gain.value = state.master;
const filter = ctx.createBiquadFilter();
Object.assign(filter, { type: state.filter.type });
filter.frequency.value = state.filter.frequency;
filter.Q.value = state.filter.Q;
const delay = ctx.createDelay(2);
delay.delayTime.value = state.fx.delayTime;
const delayGain = ctx.createGain();
delayGain.gain.value = state.fx.delayFeedback;
const delayWet = ctx.createGain();
delayWet.gain.value = state.fx.delayFeedback;
const reverb = ctx.createConvolver();
reverb.buffer = this.createImpulse(ctx);
const reverbGain = ctx.createGain();
reverbGain.gain.value = state.fx.reverbMix;
const dryGain = ctx.createGain();
dryGain.gain.value = 1 - state.fx.reverbMix;
// Routing
filter.connect(dryGain);
filter.connect(delay);
delay.connect(delayGain).connect(delay);
delay.connect(delayWet);
filter.connect(reverb).connect(reverbGain);
dryGain.connect(master);
delayWet.connect(master);
reverbGain.connect(master);
master.connect(ctx.destination);
this.nodes = { master, filter, delay, delayGain, delayWet, reverbGain, dryGain };
return true;
}
private createImpulse(ctx: AudioContext) {
const len = ctx.sampleRate * 2;
const buf = ctx.createBuffer(2, len, ctx.sampleRate);
for (let c = 0; c < 2; c++) {
const data = buf.getChannelData(c);
for (let i = 0; i < len; i++) data[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / len, 2);
}
return buf;
}
updateParams(state: SynthState) {
if (!this.nodes) return;
const { master, filter, delay, delayGain, delayWet, reverbGain, dryGain } = this.nodes;
master.gain.value = state.master;
filter.type = state.filter.type;
filter.frequency.value = state.filter.frequency;
filter.Q.value = state.filter.Q;
delay.delayTime.value = state.fx.delayTime;
delayGain.gain.value = state.fx.delayFeedback;
delayWet.gain.value = state.fx.delayFeedback;
reverbGain.gain.value = state.fx.reverbMix;
dryGain.gain.value = 1 - state.fx.reverbMix;
}
noteOn(key: string, freq: number, state: SynthState) {
if (!this.ctx || !this.nodes || this.activeNotes.has(key)) return;
const { ctx, nodes } = this;
const now = ctx.currentTime;
const oscs: OscillatorNode[] = [];
const gains: GainNode[] = [];
[state.osc1, state.osc2].forEach(osc => {
if (osc.gain === 0) return;
const o = ctx.createOscillator();
o.type = osc.waveform;
o.frequency.value = freq;
o.detune.value = osc.detune;
const g = ctx.createGain();
g.gain.setValueAtTime(0, now);
g.gain.linearRampToValueAtTime(osc.gain, now + state.env.attack);
g.gain.linearRampToValueAtTime(osc.gain * state.env.sustain, now + state.env.attack + state.env.decay);
o.connect(g).connect(nodes.filter);
o.start(now);
oscs.push(o);
gains.push(g);
});
this.activeNotes.set(key, { oscs, gains });
}
noteOff(key: string, release: number) {
const note = this.activeNotes.get(key);
if (!this.ctx || !note) return;
const now = this.ctx.currentTime;
note.gains.forEach((g, i) => {
g.gain.cancelScheduledValues(now);
g.gain.setValueAtTime(g.gain.value, now);
g.gain.linearRampToValueAtTime(0, now + release);
note.oscs[i].stop(now + release + 0.1);
});
this.activeNotes.delete(key);
}
playComposition(comp: Composition, onNote: (freq: number, on: boolean, voice: number) => void, startOffset = 0) {
if (!this.ctx || !this.nodes) return;
this.stopSequence();
const notes = parseScore(comp.score);
notes.forEach((n, i) => {
const noteEnd = n.time + n.duration;
if (noteEnd <= startOffset) return;
const key = `auto-${i}`;
const v = n.voice ?? 0;
const onDelay = Math.max(0, n.time - startOffset);
const offDelay = noteEnd - startOffset;
const startId = window.setTimeout(() => {
this.noteOn(key, n.freq, comp.patch);
onNote(n.freq, true, v);
}, onDelay * 1000);
const endId = window.setTimeout(() => {
this.noteOff(key, comp.patch.env.release);
onNote(n.freq, false, v);
}, offDelay * 1000);
this.sequenceTimeouts.push(startId, endId);
});
}
stopSequence() {
this.sequenceTimeouts.forEach(clearTimeout);
this.sequenceTimeouts = [];
this.activeNotes.forEach((_, key) => {
if (key.startsWith('auto-')) this.noteOff(key, 0.1);
});
}
get isReady() { return !!this.ctx; }
}
// ============ UI Components ============
function Knob({ value, min, max, onChange, label, format = (v: number) => v.toFixed(0) }: {
value: number; min: number; max: number; onChange: (v: number) => void; label: string; format?: (v: number) => string;
}) {
const drag = useRef({ active: false, startY: 0, startVal: 0 });
const onMouseDown = (e: React.MouseEvent) => {
drag.current = { active: true, startY: e.clientY, startVal: value };
const onMove = (e: MouseEvent) => {
if (!drag.current.active) return;
const delta = (drag.current.startY - e.clientY) * (max - min) / 100;
onChange(Math.max(min, Math.min(max, drag.current.startVal + delta)));
};
const onUp = () => {
drag.current.active = false;
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
};
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
};
const rotation = ((value - min) / (max - min)) * 270 - 135;
return (
<div className="knob-wrap">
<div className="knob" onMouseDown={onMouseDown}>
<div className="knob-inner" style={{ transform: `rotate(${rotation}deg)` }}>
<div className="knob-tick" />
</div>
</div>
</div>
);
}
// ============ Visualizer ============
const VOICE_COLORS = ['#e94560', '#50fa7b', '#bd93f9', '#f1fa8c', '#ff79c6', '#8be9fd'];
function freqToSemi(freq: number): number {
return Math.round(12 * Math.log2(freq / 440));
}
function semiToName(semi: number): string {
const names = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#'];
const idx = ((semi % 12) + 12) % 12;
const octave = Math.floor((semi + 9) / 12) + 4;
return names[idx] + octave;
}
function Visualizer({ notes, currentTime, isPlaying, voiceCount, keyboardW }: {
notes: SequenceNote[]; currentTime: number; isPlaying: boolean; voiceCount: number; keyboardW: number;
}) {
const bodyRef = useRef<HTMLDivElement>(null);
const PPS = 100;
const totalDur = notes.reduce((m, n) => Math.max(m, n.time + n.duration), 0);
const trackH = totalDur * PPS + 40;
const viewH = bodyRef.current?.clientHeight ?? 200;
const offset = currentTime > 0 ? currentTime * PPS - viewH * 0.35 : 0;
const sw = keyboardW / 49;
const semiX = (semi: number) => (semi + 33.5) * sw;
const cLines = [2, 3, 4, 5, 6].map(oct => semiX(freqToSemi(noteToFreq(`C${oct}`)!)));
return (
<div className="visualizer">
<div className="viz-body" ref={bodyRef}>
<div style={{ width: keyboardW, margin: '0 auto', position: 'relative' as const, height: '100%' }}>
<div className="viz-grid">
{cLines.map((x, i) => (
<div key={i} className="viz-grid-line" style={{ left: x }} />
))}
</div>
<div className="viz-track" style={{ height: trackH, transform: `translateY(${-offset}px)` }}>
{notes.map((n, i) => {
const v = n.voice ?? 0;
const cx = semiX(freqToSemi(n.freq));
const active = isPlaying && currentTime >= n.time && currentTime < n.time + n.duration;
return (
<div key={i} className={`viz-note${active ? ' active' : ''}`}
style={{
left: cx - sw / 2,
width: sw,
top: n.time * PPS,
height: Math.max(3, n.duration * PPS - 2),
background: VOICE_COLORS[v % VOICE_COLORS.length],
}}
/>
);
})}
</div>
{isPlaying && <div className="viz-playhead" style={{ top: '35%' }} />}
</div>
</div>
</div>
);
}
// ============ Main App ============
function App() {
const engine = useRef(new SynthEngine());
const synthRef = useRef<HTMLDivElement>(null);
const [state, setState] = useState<SynthState>(DEFAULT_STATE);
const [keyW, setKeyW] = useState(18);
const keyboardW = keyW * 29;
const blackW = Math.round(keyW * 2 / 3);
const [lastComp, setLastComp] = useState<Composition | null>(() => {
try { return tb.insight<Composition>(); } catch (e) { return null; }
});
const [activeKeys, setActiveKeys] = useState<Map<string, number>>(new Map());
const [isGenerating, setIsGenerating] = useState(false);
const [ready, setReady] = useState(false);
const [playTime, setPlayTime] = useState(0);
const [playing, setPlaying] = useState(false);
const rafId = useRef(0);
const playStart = useRef(0);
const vizData = useMemo(() => {
if (!lastComp) return { notes: [] as SequenceNote[], voiceCount: 1, totalDur: 0 };
const notes = parseScore(lastComp.score);
const vc = notes.reduce((m, n) => Math.max(m, (n.voice ?? 0) + 1), 1);
const dur = notes.reduce((m, n) => Math.max(m, n.time + n.duration), 0);
return { notes, voiceCount: vc, totalDur: dur };
}, [lastComp]);
const init = useCallback(async () => {
if (engine.current.init(state)) {
setReady(true);
} else {
const ctx = engine.current.ctx;
if (ctx && ctx.state === 'suspended') await ctx.resume();
}
}, [state]);
useEffect(() => {
if (ready) engine.current.updateParams(state);
}, [state, ready]);
const playComp = useCallback((comp: Composition, startOffset = 0) => {
cancelAnimationFrame(rafId.current);
engine.current.playComposition(comp, (freq, on, voice) => {
const keyEntry = KEYBOARD.find(k => Math.abs(k[1] - freq) < 1);
if (keyEntry) {
setActiveKeys(prev => {
const next = new Map(prev);
on ? next.set(keyEntry[0], voice) : next.delete(keyEntry[0]);
return next;
});
}
}, startOffset);
playStart.current = performance.now() - startOffset * 1000;
setPlaying(true);
const tick = () => {
const t = (performance.now() - playStart.current) / 1000;
if (t > vizData.totalDur + 0.5) { setPlaying(false); setPlayTime(0); setActiveKeys(new Map()); return; }
setPlayTime(t);
rafId.current = requestAnimationFrame(tick);
};
rafId.current = requestAnimationFrame(tick);
}, [vizData.totalDur]);
const pausePlaying = useCallback(() => {
engine.current.stopSequence();
cancelAnimationFrame(rafId.current);
setPlaying(false);
setActiveKeys(new Map());
}, []);
const rewind = useCallback(() => {
engine.current.stopSequence();
cancelAnimationFrame(rafId.current);
setPlaying(false);
setPlayTime(0);
setActiveKeys(new Map());
}, []);
const noteOn = useCallback((key: string, freq: number) => {
init();
engine.current.noteOn(key, freq, state);
setActiveKeys(s => new Map(s).set(key, 0));
}, [init, state]);
const noteOff = useCallback((key: string) => {
engine.current.noteOff(key, state.env.release);
setActiveKeys(s => { const n = new Map(s); n.delete(key); return n; });
}, [state.env.release]);
const handleCompose = async () => {
await init();
setIsGenerating(true);
try {
const result = await tb.infer<Composition>().catch(() => undefined);
if (result) {
setState(result.patch);
setLastComp(result);
engine.current.updateParams(result.patch);
playComp(result);
}
} finally {
setIsGenerating(false);
}
};
useEffect(() => {
const keyMap = new Map(KEYBOARD.map(([k, f]) => [k, f]));
const onDown = (e: KeyboardEvent) => {
const k = e.key.toLowerCase();
if (!e.repeat && keyMap.has(k)) noteOn(k, keyMap.get(k)!);
};
const onUp = (e: KeyboardEvent) => {
const k = e.key.toLowerCase();
if (keyMap.has(k)) noteOff(k);
};
window.addEventListener('keydown', onDown);
window.addEventListener('keyup', onUp);
return () => { window.removeEventListener('keydown', onDown); window.removeEventListener('keyup', onUp); };
}, [noteOn, noteOff]);
useEffect(() => {
const el = synthRef.current;
if (!el) return;
const ro = new ResizeObserver(entries => {
const w = entries[0].contentBoxSize[0].inlineSize;
setKeyW(Math.max(19, Math.floor((w - 10) / 29)));
});
ro.observe(el);
return () => ro.disconnect();
}, []);
return (
<div className="synth" ref={synthRef}>
<div className="topbar">
<button className={`ai-btn ${isGenerating ? 'loading' : ''}`} onClick={handleCompose}>
{isGenerating ? 'Generating...' : '✨ AI Compose'}
</button>
<button
className={`play-btn ${playing ? 'playing' : ''}`}
onClick={async () => {
if (playing) {
pausePlaying();
} else {
await init();
if (lastComp) playComp(lastComp, playTime >= vizData.totalDur ? 0 : playTime);
}
}}
disabled={!lastComp}
>
{playing ? '⏸' : '▶'}
</button>
<button className="ctrl-btn" onClick={rewind} disabled={!lastComp}>⏮</button>
<Knob value={state.master} min={0} max={1} onChange={v => setState(s => ({ ...s, master: v }))} label="VOL" format={fmt.pct} />
</div>
<div className="scroll-area">
<div className="scroll-inner" style={{ minWidth: keyboardW + 10 }}>
{vizData.notes.length > 0 && (
<Visualizer notes={vizData.notes} currentTime={playTime} isPlaying={playing} voiceCount={vizData.voiceCount} keyboardW={keyboardW} />
)}
<div className="keyboard">
{KEYBOARD.map(([key, freq, note]) => {
const voice = activeKeys.get(key);
const isBlack = note.includes('#');
const keyStyle: React.CSSProperties = isBlack
? { width: blackW, margin: `0 -${blackW / 2}px` }
: { width: keyW };
if (voice !== undefined) keyStyle.background = VOICE_COLORS[voice % VOICE_COLORS.length];
return (
<div key={key} className={`key ${isBlack ? 'b' : 'w'} ${voice !== undefined ? 'on' : ''}`}
style={keyStyle}
onMouseDown={() => noteOn(key, freq)} onMouseUp={() => noteOff(key)} onMouseLeave={() => noteOff(key)}
onTouchStart={e => { e.preventDefault(); noteOn(key, freq); }} onTouchEnd={() => noteOff(key)}>
<span className="n">{note}</span>
<span className="h">{key.length === 1 ? key.toUpperCase() : ''}</span>
</div>
);
})}
</div>
</div>
</div>
</div>
);
}
createRoot(document.getElementById("root")!).render(<App />);
```
**styles.css**
```css
:root { --bg: #151515; --panel: #1c1c1c; --border: #2a2a2a; --text: #ccc; --dim: #666; --accent: #e94560; }
html[data-theme="light"] { --bg: #e4e4e4; --panel: #f0f0f0; --border: #ccc; --text: #222; --dim: #888; --accent: #d63850; }
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body { font: 10px/1.2 'SF Mono', Consolas, monospace; background: var(--bg); color: var(--text); padding: 8px; }
.synth { display: flex; flex-direction: column; height: calc(100vh - 16px); max-width: 960px; margin: 0 auto; }
/* Topbar */
.topbar { display: flex; align-items: center; justify-content: center; gap: 12px; padding: 10px; background: var(--panel); border: 1px solid var(--border); border-radius: 4px; margin-bottom: 6px; }
.ai-btn { background: #333; border: 1px solid var(--accent); color: var(--accent); padding: 8px 16px; border-radius: 4px; font: inherit; font-size: 12px; cursor: pointer; transition: all 0.2s; }
.ai-btn:hover { background: var(--accent); color: #fff; }
.ai-btn.loading { opacity: 0.5; cursor: wait; }
.ctrl-btn { background: #444; border: 1px solid #666; color: #fff; padding: 8px 12px; border-radius: 4px; font: inherit; font-size: 14px; cursor: pointer; }
.ctrl-btn:hover { background: #555; }
.ctrl-btn:disabled { opacity: 0.3; cursor: default; }
.play-btn { background: #444; border: 1px solid #666; color: #fff; padding: 8px 12px; border-radius: 4px; font: inherit; font-size: 14px; cursor: pointer; }
.play-btn:hover { background: #555; }
.play-btn:disabled { opacity: 0.3; cursor: default; }
.play-btn.playing { background: var(--accent); border-color: var(--accent); }
.knob-wrap { display: flex; flex-direction: column; align-items: center; gap: 2px; }
.knob { width: 36px; height: 36px; background: #222; border: 2px solid #333; border-radius: 50%; cursor: pointer; }
html[data-theme="light"] .knob { background: #ddd; border-color: #bbb; }
.knob:hover { border-color: var(--accent); }
.knob-inner { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; }
.knob-tick { width: 2px; height: 10px; background: var(--accent); border-radius: 1px; transform: translateY(-5px); }
.knob-info { text-align: center; line-height: 1; }
.knob-val { font-size: 10px; font-weight: 600; display: block; }
.knob-lbl { font-size: 8px; color: var(--dim); }
/* Scroll area */
.scroll-area { flex: 1; min-height: 0; overflow-x: auto; }
.scroll-inner { display: flex; flex-direction: column; height: 100%; }
/* Visualizer */
.visualizer { flex: 1; min-height: 120px; background: var(--panel); border: 1px solid var(--border); border-radius: 4px; margin-bottom: 6px; position: relative; overflow: hidden; }
.viz-body { position: relative; overflow: hidden; height: 100%; }
.viz-grid { position: absolute; inset: 0; pointer-events: none; z-index: 0; }
.viz-grid-line { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--border); opacity: 0.5; }
.viz-track { position: relative; z-index: 1; }
.viz-note { position: absolute; border-radius: 2px; opacity: 0.5; transition: opacity 0.15s; }
.viz-note.active { opacity: 1; box-shadow: 0 0 10px 2px currentColor; filter: brightness(1.3); }
.viz-playhead { position: absolute; left: 0; right: 0; height: 2px; background: var(--dim); box-shadow: 0 0 6px var(--dim); z-index: 5; pointer-events: none; }
/* Keyboard */
.keyboard { display: flex; justify-content: center; background: var(--panel); border: 1px solid var(--border); border-radius: 4px; padding: 8px 4px; }
.key { display: flex; flex-direction: column; align-items: center; justify-content: flex-end; padding-bottom: 4px; border-radius: 0 0 3px 3px; cursor: pointer; user-select: none; flex-shrink: 0; }
.key.w { height: 64px; background: #f5f5f5; border: 1px solid #aaa; z-index: 1; color: #333; }
.key.b { height: 40px; background: #1a1a1a; border: 1px solid #000; z-index: 2; color: #777; }
.key.w:hover { background: #e8e8e8; }
.key.b:hover { background: #2a2a2a; }
.key.on { color: #fff !important; transform: translateY(1px); }
.key .n { font-size: 8px; font-weight: 600; }
.key .h { font-size: 7px; opacity: .5; }
```
**index.html**
```html
<div id="root"></div>
```
**data.txt**
```txt
Modal Jazz
```
**infer.md**
```md
Analyze the musical guidance in the input data (style, mood, or specific instructions).
Create a musical composition and matching synthesizer patch.
1. **Patch Design**: Adjust Osc1, Osc2, Filter, Envelope, and FX to match the requested style.
2. **Score**: Compose a polyphonic piece using the vertical tracker format below.
### Register Guidelines
The keyboard spans C2–C6. Keep most melodic material in the **C3–C5** range where this synth sounds best. Bass voices can dip to C2 but should sit mostly around **C2–C3**. Avoid writing melodies or lead voices below C3 — low registers sound muddy on a synth and lack definition.
### Vertical Tracker Format
A grid where **each row is one time step** and **each column is a voice**. Time flows top-to-bottom.
#### Header
- `@bpm 120` — tempo in beats per minute
- `@step 1/8` — time resolution (fraction of a whole note: 1/4, 1/8, 1/16)
- `@swing 0.67` — swing ratio (0.5 = straight, 0.67 = triplet swing). Use for jazz, blues, shuffle feels.
- `@voices melody bass` — voice names (informational, one per column)
#### Grid Symbols
- **Note name** (`C4`, `F#3`, `Bb5`) — attack: start a new note
- `|` — sustain: hold the previous note for another step
- `.` — rest: silence (ends any held note)
- `[C4,E4,G4]` — chord: multiple simultaneous notes in one voice
#### Example
```
**insight.json**
```json
{
"patch": {
"osc1": { "waveform": "triangle", "detune": 0, "gain": 0.5 },
"osc2": { "waveform": "sine", "detune": 12, "gain": 0.2 },
"filter": { "type": "lowpass", "frequency": 1200, "Q": 1.5 },
"env": { "attack": 0.05, "decay": 0.6, "sustain": 0.4, "release": 0.5 },
"fx": { "delayTime": 0.3, "delayFeedback": 0.2, "reverbMix": 0.4 },
"master": 0.5
},
"score": [
"@bpm 112",
"@step 1/8",
"@swing 0.6",
"@voices sax piano bass",
"",
"A4 [F3,B3,E4] D2",
"| | |",
". . D3",
"C5 . |",
"| [F3,B3,E4] F2",
"B4 | |",
"| . G2",
"G4 . |",
"--------",
"A4 [F3,B3,E4] A2",
"| | |",
". . C2",
"F4 . |",
"G4 [F3,B3,E4] D2",
"| | |",
"A4 . E2",
"| . |",
"--------",
"D5 [F3,B3,E4] F2",
"| | |",
". . G2",
"C5 . |",
"B4 [F3,B3,E4] A2",
"| | |",
"A4 . G2",
"G4 . |",
"--------",
"A4 [F3,B3,E4] D2",
"| | |",
". . D3",
". . |",
". [F3,B3,E4] C3",
". | |",
". . B2",
". . |",
"--------",
"Bb4 [Gb3,C4,F4] Eb2",
"| | |",
". . Eb3",
"Db5 . |",
"| [Gb3,C4,F4] Gb2",
"C5 | |",
"| . Ab2",
"Ab4 . |",
"--------",
"Bb4 [Gb3,C4,F4] Bb2",
"| | |",
". . Db2",
"Gb4 . |",
"Ab4 [Gb3,C4,F4] Eb2",
"| | |",
"Bb4 . F2",
"| . |",
"--------",
"Eb5 [Gb3,C4,F4] Gb2",
"| | |",
". . Ab2",
"Db5 . |",
"C5 [Gb3,C4,F4] Bb2",
"| | |",
"Bb4 . Ab2",
"Ab4 . |",
"--------",
"A4 [F3,B3,E4] D2",
"| | |",
"| | |",
". . |",
"[A4,C5,E5] [F3,B3,E4] D2",
"| | |",
"| | |",
". . ."
]
}
```
**config.json**
```json
{
"dependencies": {
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
"description": "An AI composer that creates original music in any style, to help you find inspiration.",
"inference": {
"title": "AI Composer",
"dataTitle": "Musical Style/Instructions",
"submitTitle": "Compose Music"
}
}
```