Imported from wk-j/ghost-signal (
AGENTS.md). Install upstream withnpx skills add wk-j/ghost-signal. Copyright stays with the author.
AGENTS.md — Ghost Signal
Guidelines for AI coding agents operating in this repository.
Project Overview
Ghost Signal is a collection of browser audio themes — sound palettes for UI
sonification built entirely with the Web Audio API. Each theme is an ES module
(sounds.js) loaded by a shared demo page (demo.html).
There is no build step, no bundler, no package manager, and no dependencies.
Repository Structure
ghost-signal/
demo.html # Shared demo page — loads any theme via ?theme=<name>
TEMPLATE.md # Theme design spec template (fill-in-the-blanks)
TEMPLATE.js # Theme sounds.js template
<theme-name>/ # One directory per theme (kebab-case)
<theme-name>.md # Design spec — mood, colors, 16 sound definitions
sounds.js # ES module — exports { meta, createSounds }
index.html # Thin redirect → ../demo.html?theme=<theme-name>
Build / Lint / Test Commands
There are none. This is a zero-tooling static HTML project.
- No
package.json, no npm scripts, no Makefile - No linter (no ESLint, Prettier, Biome)
- No test framework — verification is manual (open in a browser)
- No CI/CD pipeline (GitHub Actions only generates the landing page)
To verify a theme works, open demo.html?theme=<theme-name> in a browser,
click "Click to initialise AudioContext", and test all 16 sounds interactively.
Sonic Differentiation Rules
Each theme must have a unique Sonic DNA — a set of signature synthesis techniques that fundamentally change how sounds are built. Two themes must never sound like the same engine with different tuning.
What counts as differentiation
- Different primary synthesis technique (FM synthesis vs subtractive vs ring modulation vs waveshaping vs detuned unison)
- Different node topology (different signal routing, not just different parameter values on the same chain)
- Different envelope philosophy (ultra-short percussive vs long reverberant tails vs tape-wobble modulation)
- Different spectral character (harsh harmonics vs pure tones vs filtered noise-dominant vs lo-fi bandwidth-limited)
What does NOT count as differentiation
- Same waveform/filter chain with different frequency values
- Same envelope shape with different timing
- Same node graph with a different oscillator type swapped in
- Adding/removing a single noise layer while keeping the core identical
Existing Sonic DNA (update when adding themes)
| Theme | Primary Waveform | Signature Effect | Transient | Envelope | Spectrum |
|---|---|---|---|---|---|
| Ghost Signal | FM-modulated square/saw | Ring modulation | Hard square impulse <3ms | Sharp attack, resonant ring-out | Mid-highs, harsh resonant peaks |
| Orbit Deck | Pure sine, single oscillator | Feedback delay tails (distance) | Soft fade-in, no click | Long tails dissolving into void | Clean, LP-filtered, sterile |
| Mach Line | FM percussion (high mod index) | Waveshaper distortion | Sub-2ms impulse, zero sustain | Ultra-short, bone-dry | Metallic > 4 kHz shimmer |
| Chill City FM | Detuned triangle/sine pairs | Chorus beating + tape wobble LFO | Soft noise crackle onset | Medium, warm, wobbly | LP-capped at 3-4 kHz |
| Deep Glyph | Additive sine partials (3-6 harmonics) | Comb filter resonance (delay feedback) | Granular noise bursts 5-8ms | Staccato body + spectral tail | Wide partials spread 150 Hz-6 kHz |
| Neon Pulse | Pulse-Width Modulated (PWM) Square | Logic Gate Shimmer (gain LFO) | Frequency-Slide "Blip" 2-4ms | Rhythmic Staccato, zero sustain | Neon-Bright, resonant fundamental |
Before creating a new theme, compare its planned Sonic DNA against this table. At least 3 of 5 columns must be fundamentally different from every existing theme.
Creating a New Theme
- Create a directory:
<theme-name>/(kebab-case) - Copy
TEMPLATE.md→<theme-name>/<theme-name>.md, fill in all{{…}}tokens - Copy
TEMPLATE.js→<theme-name>/sounds.js, then:- Fill in
metaobject: name, subtitle, colors (9 values), placeholder, sounds (16 entries) - Fill in the
SONIC DNAcomment block with 5 signature techniques - Implement all 16 sound functions inside
createSounds(ctx, noiseBuffer) - Every sound must use at least one signature technique from the Sonic DNA
- Fill in
- Create
<theme-name>/index.htmlas a thin redirect:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{{Theme Name}} — Audio Theme</title> <script>location.replace('../demo.html?theme={{theme-name}}');</script> </head> <body></body> </html> - Every theme must define exactly 17 sounds (12 browser + 1 application + 4 keyboard)
- All synthesis uses Web Audio API only — no sample files
sounds.js Module Contract
Each sounds.js exports a default object with two properties:
export default { meta, createSounds };
-
meta— UI metadata object:name— display name (e.g.'Ghost Signal')subtitle— tagline (e.g.'Cyberpunk-noir audio theme')colors— object with 9 keys:accent,accent2,danger,bg,surface,surface2,border,text,textDim(hex strings)placeholder— textarea placeholder textsounds— object keyed by sound ID, each with{ label, meta, desc }
-
createSounds(ctx, noiseBuffer)— factory function receiving anAudioContextand anoiseBuffer(duration)helper. Returns an object of 17 sound functions.
Required Sound IDs
| # | ID | Category |
|---|---|---|
| 1–2 | HOVER, HOVER_UP |
Browser |
| 3–4 | CLICK, IMPORTANT_CLICK |
Browser |
| 5–6 | FEATURE_SWITCH_ON, FEATURE_SWITCH_OFF |
Browser |
| 7–8 | LIMITER_ON, LIMITER_OFF |
Browser |
| 9 | SWITCH_TOGGLE |
Browser |
| 10–12 | TAB_INSERT, TAB_CLOSE, TAB_SLASH |
Browser |
| 13 | APP_START |
Application |
| 14 | TYPING_LETTER (10–20 variants) |
Keyboard |
| 15–17 | TYPING_BACKSPACE, TYPING_ENTER, TYPING_SPACE |
Keyboard |
Code Style
General
- 2-space indentation everywhere (HTML, CSS, JS) — no tabs
- Semicolons always — no ASI reliance
- Single quotes for JS strings:
'sine','HOVER' - Double quotes for HTML attributes:
class="sound-btn" - Backtick template literals only for dynamic strings with
${}
JavaScript
functiondeclarations for top-level named functionsfunction()expressions forsounds.*properties (not arrow functions)- Arrow functions only in callbacks:
.addEventListener,.forEach,setTimeout camelCasefor variables and functions:initAudio,noiseBuffer,bodyFreqUPPER_SNAKE_CASEfor sound IDs:HOVER_UP,FEATURE_SWITCH_ON- Short abbreviations for Web Audio nodes:
osc,bp,lp,hp,nSrc - Gain node naming: first-letter +
G—nG(noise),sG(sub),bG(body) const now = ctx.currentTimeis always the first line in every sound function- Durations as decimal seconds:
0.06not60 / 1000 - No
async/await, no classes — plain functions and object literals - Global
let ctx = nullfor AudioContext, globallet tabCounter = 0for tab state - Chained
.connect()calls:osc.connect(filter).connect(gain).connect(ctx.destination) - Explicit
.stop()calls for node cleanup — no manual disconnect
CSS
kebab-casefor class names:sound-btn,toggle-row,key-ind--kebab-casefor custom properties:--accent,--surface2,--text-dim- Use semantic color variable names (
--accent,--accent2,--danger) not descriptive ones - 9 required CSS variables:
--accent,--accent2,--danger,--bg,--surface,--surface2,--border,--text,--text-dim - Simple rules on single lines:
.init-banner:hover { border-color: var(--accent2); } - Complex rules expanded to multiple lines
- Shorthand properties preferred:
inset: 0,gap: 12px
HTML
- Standard
<!DOCTYPE html>withlang="en" - Inline event handlers:
onclick="play('CLICK')",onmouseenter="play('HOVER')" - No self-closing void elements (use
<meta ...>not<meta ... />) - All CSS inline in
demo.html— sound logic in externalsounds.jsmodules
Comments
JS section dividers (heavy):
// ═══════════════════════════════════════════════════════════════════
// SECTION TITLE
// ═══════════════════════════════════════════════════════════════════
JS sound dividers (light):
// ---------------------------------------------------------------
// N. SOUND_NAME — short description, duration
// ---------------------------------------------------------------
CSS section dividers:
/* ── Section Name ───────────────────────────────────────────────── */
HTML section dividers:
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- SECTION TITLE -->
<!-- ═══════════════════════════════════════════════════════════ -->
Web Audio Patterns
Every sound function follows this structure:
sounds.SOUND_NAME = function() {
const now = ctx.currentTime;
// 1. Create oscillator(s) / buffer source(s)
// 2. Create filter(s) — bandpass, lowpass, highpass
// 3. Create gain node(s) with envelope automation
// 4. Connect chain: source → filter → gain → ctx.destination
// 5. Schedule .start(now + offset) and .stop(now + offset)
};
- Gain envelopes:
setValueAtTime→linearRampToValueAtTimeorexponentialRampToValueAtTime - Noise via
noiseBuffer(duration)helper (creates white noiseBufferSource) - All timing is absolute:
now + 0.03, not relative offsets
Git Conventions
- Imperative mood commit messages: "Add …", "Fix …", "Update …"
- Single-line message with descriptive detail after an em-dash
- No conventional commit prefixes (
feat:,fix:, etc.) - Commit only theme-related files — no generated files or tooling artifacts
