TutSynth
GPT 5.6 terra · typescript

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 21,921 bytes · SHA-256 022263758f61
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Synth Playground — Learn by Listening</title>
<style>
/* ── css: base ── */
@import url('https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Syne:wght@500;600;700;800&display=swap');
:root{--ink:#1c2038;--muted:#777c93;--paper:#f5f3ee;--card:#fffefa;--line:#d9d6cf;--violet:#6d55f6;--lime:#c9ff4b;--coral:#ff7065;--yellow:#ffcc4d;--shadow:0 16px 40px rgba(29,31,61,.09)}*{box-sizing:border-box}body{margin:0;background:var(--paper);color:var(--ink);font-family:'DM Mono',monospace}button{font:inherit;cursor:pointer}#synth-app{max-width:1280px;margin:auto;min-height:100vh;padding:29px 42px 22px}.topbar{display:flex;justify-content:space-between;align-items:center}.eyebrow,.chapter{letter-spacing:.12em;font-size:10px;font-weight:500;color:var(--violet);margin:0 0 7px}.topbar h1{font-family:Syne,sans-serif;font-size:28px;line-height:1;margin:0;letter-spacing:-1.4px}.top-actions{display:flex;gap:10px}.power,.ghost{height:39px;border-radius:4px;padding:0 14px;border:1px solid var(--line);background:transparent;color:var(--ink);font-size:11px}.power{background:var(--ink);color:#fff;border-color:var(--ink)}.power-dot{display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--coral);margin-right:7px}.power.on .power-dot{background:var(--lime);box-shadow:0 0 0 3px rgba(201,255,75,.16)}.hero{margin:39px 0 26px;display:flex;justify-content:space-between;align-items:end}.hero h2{font:700 34px/1.05 Syne,sans-serif;letter-spacing:-1.8px;margin:0 0 9px}.hero p:not(.chapter){margin:0;max-width:575px;color:#676c80;font-size:12px;line-height:1.7}.progress-wrap{width:285px}.progress-label{display:flex;justify-content:space-between;font-size:9px;color:var(--muted);letter-spacing:.07em;margin-bottom:9px}.progress{height:5px;background:#e1ded6;border-radius:9px;overflow:hidden}.progress i{display:block;height:100%;width:20%;background:var(--violet);transition:width .35s}.stage-dots{display:flex;gap:5px;margin-top:10px}.stage-dots i{width:7px;height:7px;border-radius:50%;background:#dcd9d2}.stage-dots i.active{background:var(--violet)}.workspace{display:grid;grid-template-columns:245px 1fr;border:1px solid var(--line);background:var(--card);box-shadow:var(--shadow)}.lesson-panel{border-right:1px solid var(--line);padding:27px 24px;min-height:413px;display:flex;flex-direction:column}.lesson-mark{font:700 48px/1 Syne,sans-serif;color:#e5e2db;letter-spacing:-3px}.lesson-panel h3{font:700 18px Syne,sans-serif;margin:19px 0 9px}.lesson-panel>p{font-size:11px;line-height:1.65;color:#666c80;margin:0}.tip{display:flex;gap:9px;border-top:1px solid var(--line);margin-top:21px;padding-top:17px;color:#5f558e}.tip span{color:var(--violet)}.tip p{font-size:10px;line-height:1.55;margin:0}.next{margin-top:auto;width:100%;background:var(--lime);border:0;padding:12px 10px;text-align:left;font-size:10px;font-weight:500}.next span{float:right;font-size:15px;line-height:10px}.synth-panel{padding:23px 28px 18px;min-width:0}.signal-line{display:flex;align-items:center;height:25px;gap:8px;font-size:9px;letter-spacing:.06em;color:#83889a}.signal-line i{height:1px;background:#ddd9d0;flex:1}.signal-line .node,.source,.output{border:1px solid #ddd9d0;padding:5px 8px;border-radius:12px;background:#fff}.signal-line .active{background:var(--violet);color:#fff;border-color:var(--violet)}.source{color:#5e6378}.output{border-color:#bfdc7b;background:#f7ffe9}.controls{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin:20px 0}.module{border:1px solid var(--line);min-height:171px;padding:13px;position:relative;background:#fff}.module.locked{opacity:.36;pointer-events:none;background:#f5f3ee}.module-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}.module-title{font:700 12px Syne,sans-serif}.badge{font-size:8px;color:var(--muted);letter-spacing:.07em}.badge.new{color:var(--violet)}.waves{display:grid;grid-template-columns:repeat(4,1fr);gap:5px}.wave{height:34px;border:1px solid #e1ded7;background:#fcfbf8;color:#8b8e9c;padding:0;border-radius:2px;font-size:16px}.wave.selected{border-color:var(--violet);background:#f0edff;color:var(--violet)}.knob-row{display:flex;justify-content:space-around;gap:7px}.knob-wrap{text-align:center;min-width:52px}.knob{width:49px;height:49px;border-radius:50%;margin:0 auto 7px;background:conic-gradient(var(--violet) var(--deg),#e4e2dc 0);position:relative}.knob:after{content:'';position:absolute;inset:5px;background:#fff;border-radius:50%}.knob b{position:absolute;width:2px;height:14px;background:var(--ink);left:23px;top:6px;transform-origin:1px 18px;transform:rotate(var(--rot));z-index:1;border-radius:2px}.knob-wrap label{font-size:8px;color:#767b8f;display:block}.knob-wrap output{font-size:9px;display:block;margin-top:3px;color:var(--ink)}input[type=range]{width:100%;accent-color:var(--violet);margin:8px 0 2px}.range-label{font-size:8px;color:#777c90;display:flex;justify-content:space-between}.filter-visual{height:34px;border-bottom:1px solid #bbb7ae;margin-bottom:12px;position:relative;overflow:hidden}.filter-visual:after{content:'';position:absolute;bottom:0;left:2px;width:93%;height:25px;border-top:2px solid var(--coral);border-radius:0 20px 0 0;transform:skewY(-11deg)}.visualizer{border:1px solid var(--line);height:106px;padding:9px 11px;background:#171b31}.scope-head{color:#8d93b1;display:flex;justify-content:space-between;font-size:8px;letter-spacing:.08em}.scope-head span:last-child{color:#d9dcf0}canvas{width:100%;height:69px;display:block}.keyboard-area{padding:21px 0 11px}.keyboard-copy{display:flex;align-items:center;gap:8px;font-size:9px;color:#7a7e91;margin-bottom:12px}.keyboard-copy b{color:var(--ink);font-size:10px}.live-dot{width:7px;height:7px;background:var(--coral);border-radius:50%}.keyboard{height:119px;display:flex;position:relative;border:1px solid #bbb8b0;background:#fff;box-shadow:0 8px 15px rgba(29,31,61,.05)}.key{height:100%;width:8.333%;background:#fff;border-right:1px solid #d5d2cc;position:relative;display:flex;align-items:flex-end;justify-content:center;padding-bottom:10px;color:#9498a7;font-size:9px;user-select:none}.key.active{background:#eeeaff;color:var(--violet)}.key.black{position:absolute;z-index:2;height:69px;width:5.1%;background:var(--ink);border:3px solid var(--ink);border-radius:0 0 4px 4px;color:#d7dbec;padding-bottom:7px}.key.black.active{background:var(--violet);border-color:var(--violet)}.keyboard-help{display:flex;align-items:center;gap:8px;margin-top:12px;color:#85899b;font-size:9px}.keyboard-help kbd{background:#e3e0d9;color:#555b6e;padding:3px 5px;border-radius:2px;font:9px 'DM Mono'}.keyboard-help span{margin-left:8px}footer{border-top:1px solid var(--line);padding-top:15px;display:flex;justify-content:space-between;color:#85899b;font-size:9px}@media(max-width:760px){#synth-app{padding:22px 16px}.topbar{align-items:flex-start}.hero{display:block;margin-top:33px}.progress-wrap{width:100%;margin-top:25px}.workspace{grid-template-columns:1fr}.lesson-panel{min-height:210px;border-right:0;border-bottom:1px solid var(--line)}.next{margin-top:18px}.synth-panel{padding:17px}.controls{grid-template-columns:repeat(2,1fr)}.signal-line{overflow:hidden}.signal-line i{min-width:9px}.keyboard-help{overflow:hidden;white-space:nowrap}.top-actions .ghost{display:none}.hero h2{font-size:29px}}
</style>
</head>
<body>
<main id="synth-app"><header class="topbar"><div><p class="eyebrow">INTERACTIVE AUDIO LAB</p><h1>Synth Playground</h1></div><div class="top-actions"><button id="audio-toggle" class="power" aria-label="Enable audio"><span class="power-dot"></span> Enable audio</button><button id="reset" class="ghost">Reset patch</button></div></header><section class="hero"><div><p class="chapter">CHAPTER <span id="chapter-num">01</span> / 05</p><h2 id="lesson-title">Start with a tone</h2><p id="lesson-copy">An oscillator creates a repeating electrical shape. Hold a key below to hear its raw voice.</p></div><div class="progress-wrap"><div class="progress-label"><span>YOUR SIGNAL PATH</span><span id="progress-text">1 of 5</span></div><div class="progress"><i id="progress-fill"></i></div><div class="stage-dots" id="stage-dots"></div></div></section><section class="workspace"><aside class="lesson-panel"><div class="lesson-mark">01</div><h3>What to listen for</h3><p id="listen-for">Compare the different wave shapes. Their harmonics give each one a different character.</p><div class="tip"><span>✦</span><p id="tip-text">Try holding <b>A</b>, then switch from Sine to Saw.</p></div><button id="next-lesson" class="next">Next: Shape the volume <span>→</span></button></aside><section class="synth-panel"><div class="signal-line"><span class="source">MIDI</span><i></i><span class="node active" id="node-osc">OSC</span><i></i><span class="node" id="node-filter">FILTER</span><i></i><span class="node" id="node-amp">AMP</span><i></i><span class="node" id="node-fx">FX</span><i></i><span class="output">OUT</span></div><div id="controls" class="controls"></div><div class="visualizer"><div class="scope-head"><span>OSCILLOSCOPE</span><span id="wave-readout">SINE · 220 Hz</span></div><canvas id="scope" width="900" height="160"></canvas></div></section></section><section class="keyboard-area"><div class="keyboard-copy"><span class="live-dot"></span><b>PLAY IT</b><span>Computer keyboard enabled</span></div><div id="keyboard" class="keyboard" aria-label="Playable piano keyboard"></div><div class="keyboard-help"><kbd>A</kbd><kbd>W</kbd><kbd>S</kbd><kbd>E</kbd><kbd>D</kbd><kbd>F</kbd><kbd>T</kbd><kbd>G</kbd><kbd>Y</kbd><kbd>H</kbd><kbd>U</kbd><kbd>J</kbd><span>← use your keyboard or click keys →</span></div></section><footer><span>Built to explore — change one control, then listen.</span><span id="status">Audio is standing by</span></footer></main>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {};
function __require(id) {
if (__cache[id]) return __cache[id].exports;
var module = __cache[id] = { exports: {} };
var factory = __mods[id];
if (!factory) throw new Error("Module not found: " + id);
factory(module.exports, function (spec) {
var target = (__map[id] && __map[id][spec]) || spec;
return __require(target);
}, module);
return module.exports;
}
// ── module: src/main.ts ──
__mods["src/main.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const lessons = [
{ title: 'Start with a tone', copy: 'An oscillator creates a repeating electrical shape. Hold a key below to hear its raw voice.', listen: 'Compare the different wave shapes. Their harmonics give each one a different character.', tip: 'Try holding <b>A</b>, then switch from Sine to Saw.', next: 'Next: Shape the volume' },
{ title: 'Shape the volume', copy: 'An envelope turns a steady tone into a musical note: it rises, settles, and fades away.', listen: 'Listen for the soft entrance and release. Attack is the time it takes to arrive.', tip: 'Set <b>Attack</b> high, tap A, and hear the note bloom.', next: 'Next: Carve the tone' },
{ title: 'Carve the tone', copy: 'A low-pass filter removes high frequencies. Its cutoff is your brightness control.', listen: 'Hold a note and sweep Cutoff. Low values make a muffled, rounded sound.', tip: 'Move <b>Cutoff</b> slowly while holding A.', next: 'Next: Add movement' },
{ title: 'Add movement', copy: 'An LFO is a slow oscillator. Here it gently moves the filter for a repeating wah effect.', listen: 'Turn up LFO depth, then lower its rate for a wide, slow sweep.', tip: 'Try <b>Depth</b> at 70% and Rate at 2 Hz.', next: 'Next: Space & echo' },
{ title: 'Space & echo', copy: 'Delay repeats your sound. A short delay adds depth; a longer one creates rhythmic echoes.', listen: 'Play a few notes and use Mix to blend dry sound with the echoes.', tip: 'Set <b>Mix</b> to 35%, then play a melody.', next: 'Start again' }
];
const keyData = [
['A', 0, false], ['W', 1, true], ['S', 2, false], ['E', 3, true], ['D', 4, false], ['F', 5, false], ['T', 6, true], ['G', 7, false], ['Y', 8, true], ['H', 9, false], ['U', 10, true], ['J', 11, false]
];
let chapter = 0, wave = 'sine', enabled = false;
let attack = .03, release = .35, cutoff = 5200, resonance = 1.2, lfoRate = 2, lfoDepth = 0, delayMix = .12;
let ctx = null, filter = null, master = null, delay = null, wet = null, lfo = null, lfoGain = null;
const active = new Map();
const $ = (s) => document.querySelector(s);
function enableAudio() {
if (!ctx) {
ctx = new AudioContext();
filter = ctx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.value = cutoff;
filter.Q.value = resonance;
master = ctx.createGain();
master.gain.value = .32;
delay = ctx.createDelay(1);
delay.delayTime.value = .29;
wet = ctx.createGain();
wet.gain.value = delayMix;
const feedback = ctx.createGain();
feedback.gain.value = .33;
filter.connect(master);
master.connect(ctx.destination);
master.connect(delay);
delay.connect(wet);
wet.connect(ctx.destination);
delay.connect(feedback);
feedback.connect(delay);
lfo = ctx.createOscillator();
lfo.type = 'sine';
lfo.frequency.value = lfoRate;
lfoGain = ctx.createGain();
lfoGain.gain.value = lfoDepth * 2200;
lfo.connect(lfoGain);
lfoGain.connect(filter.frequency);
lfo.start();
}
ctx.resume();
enabled = true;
$('#audio-toggle').classList.add('on');
$('#audio-toggle').innerHTML = '<span class="power-dot"></span> Audio enabled';
$('#status').textContent = 'Audio enabled · ready to play';
}
function knob(label, value, min, max, unit, id) { const p = (value - min) / (max - min); const deg = Math.round(p * 270); const rot = -135 + p * 270; return `<div class="knob-wrap"><div class="knob" style="--deg:${deg}deg;--rot:${rot}deg"><b></b></div><label>${label}</label><output id="out-${id}">${value}${unit}</output><input aria-label="${label}" id="${id}" type="range" min="${min}" max="${max}" step="${(max - min) / 100}" value="${value}"></div>`; }
function renderControls() {
const locked = (n) => chapter < n ? ' locked' : '';
$('#controls').innerHTML = `
<article class="module"><div class="module-header"><span class="module-title">01 · OSCILLATOR</span><span class="badge">VOICE</span></div><div class="waves">${['sine', 'square', 'sawtooth', 'triangle'].map((w, i) => `<button class="wave ${wave === w ? 'selected' : ''}" data-wave="${w}" title="${w}">${['∿', '⊓', '⌁', '△'][i]}</button>`).join('')}</div><div class="range-label" style="margin-top:15px"><span>WAVEFORM</span><span>${wave.toUpperCase().replace('SAWTOOTH', 'SAW')}</span></div></article>
<article class="module${locked(1)}"><div class="module-header"><span class="module-title">02 · ENVELOPE</span><span class="badge ${chapter === 1 ? 'new' : ''}">${chapter < 1 ? 'LOCKED' : 'VOLUME'}</span></div><div class="knob-row">${knob('ATTACK', attack.toFixed(2), .01, 1.2, 's', 'attack')}${knob('RELEASE', release.toFixed(2), .05, 1.5, 's', 'release')}</div></article>
<article class="module${locked(2)}"><div class="module-header"><span class="module-title">03 · FILTER</span><span class="badge ${chapter === 2 ? 'new' : ''}">${chapter < 2 ? 'LOCKED' : 'TONE'}</span></div><div class="filter-visual"></div><div class="knob-row">${knob('CUTOFF', Math.round(cutoff), 200, 9000, ' Hz', 'cutoff')}${knob('RESONANCE', resonance.toFixed(1), .2, 14, '', 'resonance')}</div></article>
<article class="module${locked(3)}"><div class="module-header"><span class="module-title">04 · MODULATION</span><span class="badge ${chapter === 3 ? 'new' : ''}">${chapter < 3 ? 'LOCKED' : 'MOTION'}</span></div><div class="knob-row">${knob('RATE', lfoRate.toFixed(1), .1, 12, ' Hz', 'lfoRate')}${knob('DEPTH', Math.round(lfoDepth * 100), 0, 100, '%', 'lfoDepth')}</div><div class="range-label" style="margin-top:12px"><span>FILTER LFO</span><span>SINE</span></div></article>
<article class="module${locked(4)}"><div class="module-header"><span class="module-title">05 · DELAY</span><span class="badge ${chapter === 4 ? 'new' : ''}">${chapter < 4 ? 'LOCKED' : 'SPACE'}</span></div><div class="knob-row">${knob('MIX', Math.round(delayMix * 100), 0, 70, '%', 'delayMix')}</div><div class="range-label" style="margin-top:18px"><span>TIME</span><span>290 ms</span></div></article>`;
document.querySelectorAll('[data-wave]').forEach(b => b.onclick = () => { wave = b.dataset.wave; renderControls(); });
const bind = (id, fn) => { const e = document.querySelector('#' + id); if (e)
e.oninput = () => { fn(+e.value); renderControls(); }; };
bind('attack', v => attack = v);
bind('release', v => release = v);
bind('cutoff', v => { cutoff = v; if (filter)
filter.frequency.value = v; });
bind('resonance', v => { resonance = v; if (filter)
filter.Q.value = v; });
bind('lfoRate', v => { lfoRate = v; if (lfo)
lfo.frequency.value = v; });
bind('lfoDepth', v => { lfoDepth = v; if (lfoGain)
lfoGain.gain.value = v * 2200; });
bind('delayMix', v => { delayMix = v / 100; if (wet)
wet.gain.value = delayMix; });
}
function setChapter(n) { chapter = n; const l = lessons[n]; $('#chapter-num').textContent = `0${n + 1}`; $('#lesson-title').textContent = l.title; $('#lesson-copy').textContent = l.copy; $('#listen-for').textContent = l.listen; $('#tip-text').innerHTML = l.tip; $('#next-lesson').innerHTML = `${l.next} <span>→</span>`; $('#progress-text').textContent = `${n + 1} of 5`; $('#progress-fill').style.width = `${(n + 1) * 20}%`; $('#stage-dots').innerHTML = lessons.map((_, i) => `<i class="${i <= n ? 'active' : ''}"></i>`).join(''); ['osc', 'amp', 'filter', 'fx'].forEach((id, i) => { const node = $('#node-' + id); node.classList.toggle('active', i <= n); }); renderControls(); }
function noteOn(code, index) { if (active.has(code))
return; enableAudio(); if (!ctx || !filter)
return; const osc = ctx.createOscillator(), gain = ctx.createGain(); const freq = 220 * Math.pow(2, index / 12); osc.type = wave; osc.frequency.value = freq; gain.gain.setValueAtTime(.0001, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(.24, ctx.currentTime + attack); osc.connect(gain); gain.connect(filter); osc.start(); active.set(code, { osc, gain }); document.querySelector(`[data-key="${code}"]`)?.classList.add('active'); $('#wave-readout').textContent = `${wave.toUpperCase().replace('SAWTOOTH', 'SAW')} · ${Math.round(freq)} Hz`; }
function noteOff(code) { const n = active.get(code); if (!n || !ctx)
return; n.gain.gain.cancelScheduledValues(ctx.currentTime); n.gain.gain.setValueAtTime(Math.max(.0001, n.gain.gain.value), ctx.currentTime); n.gain.gain.exponentialRampToValueAtTime(.0001, ctx.currentTime + release); n.osc.stop(ctx.currentTime + release + .04); active.delete(code); document.querySelector(`[data-key="${code}"]`)?.classList.remove('active'); }
function makeKeyboard() { const keyboard = $('#keyboard'); const whites = keyData.filter(x => !x[2]); whites.forEach(([k]) => { const el = document.createElement('button'); el.className = 'key'; el.dataset.key = k; el.textContent = k; keyboard.append(el); }); const blackPositions = [8.33, 16.66, 41.66, 50, 58.33]; keyData.filter(x => x[2]).forEach(([k], i) => { const el = document.createElement('button'); el.className = 'key black'; el.dataset.key = k; el.textContent = k; el.style.left = `calc(${blackPositions[i]}% - 3.2%)`; keyboard.append(el); }); keyData.forEach(([code, index]) => { const el = document.querySelector(`[data-key="${code}"]`); el.onpointerdown = e => { e.preventDefault(); noteOn(code, index); }; el.onpointerup = () => noteOff(code); el.onpointerleave = () => noteOff(code); }); }
function draw() { const c = $('#scope'), x = c.getContext('2d'); const w = c.width, h = c.height; x.clearRect(0, 0, w, h); x.strokeStyle = '#303754'; x.lineWidth = 1; for (let i = 0; i < 5; i++) {
x.beginPath();
x.moveTo(0, 12 + i * 28);
x.lineTo(w, 12 + i * 28);
x.stroke();
} x.strokeStyle = '#c9ff4b'; x.lineWidth = 2; x.beginPath(); const t = performance.now() / 300; for (let i = 0; i < w; i++) {
const q = (i / w) * Math.PI * 10 + t;
let y = Math.sin(q);
if (wave === 'square')
y = Math.sign(y);
if (wave === 'sawtooth')
y = 2 * (q / (Math.PI * 2) - Math.floor(.5 + q / (Math.PI * 2)));
if (wave === 'triangle')
y = 2 / Math.PI * Math.asin(Math.sin(q));
const amp = active.size ? 23 : 7;
x.lineTo(i, h / 2 + y * amp);
} x.stroke(); requestAnimationFrame(draw); }
$('#audio-toggle').onclick = enableAudio;
$('#reset').onclick = () => { wave = 'sine'; attack = .03; release = .35; cutoff = 5200; resonance = 1.2; lfoRate = 2; lfoDepth = 0; delayMix = .12; setChapter(0); };
$('#next-lesson').onclick = () => setChapter((chapter + 1) % 5);
window.addEventListener('keydown', e => { const entry = keyData.find(x => x[0] === e.key.toUpperCase()); if (entry && !e.repeat) {
e.preventDefault();
noteOn(entry[0], entry[1]);
} });
window.addEventListener('keyup', e => { const entry = keyData.find(x => x[0] === e.key.toUpperCase()); if (entry)
noteOff(entry[0]); });
makeKeyboard();
setChapter(0);
draw();
};
// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"openai/gpt-5.6-terra","provider":"openrouter","persona":"typescript","sessionId":"79985aa6-aeff-47ac-b180-b4214822f6b8","tokensIn":98446,"tokensOut":7801,"tokensTotal":106247,"turns":11,"toolCalls":16,"failedToolCalls":2,"timestamp":"2026-07-31T18:08:58.531Z"} -->