-
Notifications
You must be signed in to change notification settings - Fork 35
docs(llms): Web Audio API research llms-txt + catalog entry (no refresh_url) #265
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
docs(llms): Web Audio API research llms-txt + catalog entry (no refresh_url) #265
Conversation
✅ Deploy Preview for fireproof-ai-builder ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall wiring looks correct: the new Web Audio doc is statically imported and mapped, and the catalog entry matches the key. The llmsTxtUrl field in the JSON appears unused by the current loader and risks going stale. Some doc snippets would benefit from best-practice adjustments: user-gesture–gated AudioContext.resume(), cleanup of metronome nodes, and basic error handling when loading IRs. These are documentation-level improvements and don’t affect runtime behavior.
Additional notes (1)
- Maintainability |
app/llms/web-audio.txt:100-103
The IR loader fetches and decodes without checkingresponse.okor guardingdecodeAudioData, which can yield cryptic errors (especially with CORS). A minimal check and try/catch improves robustness and makes failures clearer to users following the snippet.
Summary of changes
- Added new LLM catalog entry
app/llms/web-audio.jsonwith fields:name,label,llmsTxtUrl(placeholder),module,description,importModule, andimportName. - Introduced comprehensive Web Audio research doc
app/llms/web-audio.txtcovering fundamentals, an echo/delay with FX-in-feedback loop, mic monitoring + metronome, and audio-clock scheduling/timing architecture. - Wired the new doc into the prompts system by statically importing
./llms/web-audio.txt?rawand registering it inllmsTextContentwithinapp/prompts.ts.
See commit: docs(llms): add Web Audio research llms-txt and catalog entry (no refresh_url); wire into prompts (#228, #229).
app/llms/web-audio.json
Outdated
| { | ||
| "name": "web-audio", | ||
| "label": "Web Audio API", | ||
| "llmsTxtUrl": "https://vibes.diy/local/web-audio-llms.txt", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
llmsTxtUrl appears unused by the prompt loader (which pulls .txt docs via static imports). Keeping an inert URL risks drift/staleness and can confuse future catalog/UI consumers. If it’s intentionally a placeholder, prefer omitting it until a real URL exists, or ensure it matches a served asset path.
Suggestion
You can drop the llmsTxtUrl property for now to avoid confusion:
{
"name": "web-audio",
"label": "Web Audio API",
"module": "web-audio",
"description": "Web Audio fundamentals; echo/delay with effects in the feedback path; mic monitoring with a metronome; audio‑clock scheduling; timing design for multi‑channel drum machines and MIDI synths with accurate voice overlap.",
"importModule": "web-audio",
"importName": "WebAudioAPI"
}Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this change.
| const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); | ||
| await audioCtx.resume(); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This example calls audioCtx.resume() immediately after context creation, which will be blocked on many browsers without a user gesture. It also contradicts the preceding note about requiring a gesture. Show a gesture-initiated initialization to avoid confusion and make the snippet copy-paste safe.
Suggestion
Wrap context start/resume in a user-gesture handler, e.g.:
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
document.querySelector('#start-audio').addEventListener('click', async () => {
if (audioCtx.state !== 'running') await audioCtx.resume();
// now safe to create/start nodes
});Reply with "@CharlieHelps yes please" if you'd like me to update the snippet accordingly in the doc.
| function scheduleClick(atTime, downbeat = false) { | ||
| const osc = audioCtx.createOscillator(); | ||
| const env = audioCtx.createGain(); | ||
| osc.type = 'square'; | ||
| osc.frequency.setValueAtTime(downbeat ? 2000 : 1600, atTime); | ||
| env.gain.setValueAtTime(0.0001, atTime); | ||
| env.gain.exponentialRampToValueAtTime(1.0, atTime + 0.001); | ||
| env.gain.exponentialRampToValueAtTime(0.0001, atTime + 0.03); | ||
| osc.connect(env).connect(metronomeGain); | ||
| osc.start(atTime); | ||
| osc.stop(atTime + 0.05); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
scheduleClick allocates an OscillatorNode and GainNode per tick but never disconnects them. Over long sessions (e.g., continuous metronome), this can accumulate nodes and retain memory unnecessarily, even though stop() is called. Add an onended cleanup to disconnect nodes and allow GC promptly.
Suggestion
Add cleanup after osc.stop via the ended event:
function scheduleClick(atTime, downbeat = false) {
const osc = audioCtx.createOscillator();
const env = audioCtx.createGain();
osc.type = 'square';
osc.frequency.setValueAtTime(downbeat ? 2000 : 1600, atTime);
env.gain.setValueAtTime(0.0001, atTime);
env.gain.exponentialRampToValueAtTime(1.0, atTime + 0.001);
env.gain.exponentialRampToValueAtTime(0.0001, atTime + 0.03);
osc.connect(env).connect(metronomeGain);
osc.start(atTime);
osc.stop(atTime + 0.05);
osc.onended = () => {
try { osc.disconnect(); } catch {}
try { env.disconnect(); } catch {}
};
}Reply with "@CharlieHelps yes please" if you'd like me to apply this improvement in the doc.
|
@CharlieHelps yes please to all |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The catalog entry includes an unused llmsTxtUrl field that can drift and confuse consumers; removing it improves maintainability. Several doc snippets would benefit from production-safe patterns: user‑gesture–gated AudioContext.resume(), robust fetch/decode handling (check response.ok, guard decodeAudioData), and cleanup of per‑tick metronome nodes. Mic initialization lacks error handling and a cleanup mechanism to stop the stream. These changes enhance correctness and reduce copy‑paste pitfalls without altering runtime behavior.
Summary of changes
- Added new LLM catalog entry:
app/llms/web-audio.jsonwithname,label,module,description,importModule,importName, and a placeholderllmsTxtUrl. - Introduced comprehensive research doc:
app/llms/web-audio.txtcovering fundamentals, echo/delay with FX in feedback, mic monitoring + metronome, scheduling, and timing design. - Wired the new doc into the prompts system: static import in
app/prompts.tsand mapping'web-audio'→ raw text inllmsTextContent.
Links: Issue #228, [#229].
app/llms/web-audio.json
Outdated
| { | ||
| "name": "web-audio", | ||
| "label": "Web Audio API", | ||
| "llmsTxtUrl": "https://vibes.diy/local/web-audio-llms.txt", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
llmsTxtUrl appears unused by the current loader (docs are included via static imports), so this inert field risks going stale and confusing future consumers. Given the PR goal explicitly avoids external refresh/URLs, dropping this field improves maintainability and avoids drift.
Suggestion
Remove the unused llmsTxtUrl field from the catalog entry:
{
"name": "web-audio",
"label": "Web Audio API",
"module": "web-audio",
"description": "Web Audio fundamentals; echo/delay with effects in the feedback path; mic monitoring with a metronome; audio‑clock scheduling; timing design for multi‑channel drum machines and MIDI synths with accurate voice overlap.",
"importModule": "web-audio",
"importName": "WebAudioAPI"
}Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion.
| // 1) Context (user gesture required in many browsers) | ||
| const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); | ||
| await audioCtx.resume(); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This snippet resumes the AudioContext immediately after creation, which will be blocked on many browsers without a user gesture and contradicts the preceding note. Demonstrating a gesture-gated start prevents copy/paste users from hitting silent failures.
Suggestion
Wrap audioCtx.resume() inside a user gesture handler:
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
// e.g., on a Start button
document.querySelector('#start-audio')?.addEventListener('click', async () => {
if (audioCtx.state !== 'running') await audioCtx.resume();
// now safe to create/start nodes
});Reply with "@CharlieHelps yes please" if you'd like me to update the doc snippet accordingly.
| // 3) Load/decode and play a file | ||
| const buf = await fetch('/path/audio.mp3').then(r => r.arrayBuffer()).then(b => audioCtx.decodeAudioData(b)); | ||
| const src = audioCtx.createBufferSource(); | ||
| src.buffer = buf; | ||
| src.connect(audioCtx.destination); | ||
| src.start(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The file load/decode example fetches and decodes without checking response.ok and without guarding decodeAudioData. When requests fail (404/CORS) or decoding fails, users get cryptic errors. Adding minimal checks makes the example robust.
Suggestion
Use explicit ok checks and try/catch around decoding:
async function loadDecodedBuffer(url) {
const res = await fetch(url);
if (!res.ok) throw new Error(`Failed to fetch audio (${res.status} ${res.statusText})`);
const ab = await res.arrayBuffer();
try {
return await audioCtx.decodeAudioData(ab);
} catch (e) {
console.error('decodeAudioData failed', e);
throw e;
}
}
const buf = await loadDecodedBuffer('/path/audio.mp3');
const src = audioCtx.createBufferSource();
src.buffer = buf;Reply with "@CharlieHelps yes please" if you'd like me to incorporate this into the doc.
| async function loadImpulseResponse(url) { | ||
| const ab = await fetch(url, { mode: 'cors' }).then(r => r.arrayBuffer()); | ||
| return audioCtx.decodeAudioData(ab); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
loadImpulseResponse mirrors the same robustness issue: it neither checks response.ok nor guards decodeAudioData. For IRs, CORS/mime issues are common; clearer errors help users.
Suggestion
Harden the helper with response checks and guarded decoding:
async function loadImpulseResponse(url) {
const res = await fetch(url, { mode: 'cors' });
if (!res.ok) throw new Error(`IR fetch failed (${res.status} ${res.statusText})`);
const ab = await res.arrayBuffer();
try {
return await audioCtx.decodeAudioData(ab);
} catch (e) {
console.error('IR decode failed', e);
throw e;
}
}Reply with "@CharlieHelps yes please" if you'd like me to update the doc with this version.
| function scheduleClick(atTime, downbeat = false) { | ||
| const osc = audioCtx.createOscillator(); | ||
| const env = audioCtx.createGain(); | ||
| osc.type = 'square'; | ||
| osc.frequency.setValueAtTime(downbeat ? 2000 : 1600, atTime); | ||
| env.gain.setValueAtTime(0.0001, atTime); | ||
| env.gain.exponentialRampToValueAtTime(1.0, atTime + 0.001); | ||
| env.gain.exponentialRampToValueAtTime(0.0001, atTime + 0.03); | ||
| osc.connect(env).connect(metronomeGain); | ||
| osc.start(atTime); | ||
| osc.stop(atTime + 0.05); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
scheduleClick creates oscillator and envelope nodes per tick but never disconnects them. While they stop shortly after starting, explicitly disconnecting on ended helps avoid accumulation and ensures prompt GC during long sessions.
Suggestion
Add onended cleanup to disconnect nodes after they stop:
function scheduleClick(atTime, downbeat = false) {
const osc = audioCtx.createOscillator();
const env = audioCtx.createGain();
osc.type = 'square';
osc.frequency.setValueAtTime(downbeat ? 2000 : 1600, atTime);
env.gain.setValueAtTime(0.0001, atTime);
env.gain.exponentialRampToValueAtTime(1.0, atTime + 0.001);
env.gain.exponentialRampToValueAtTime(0.0001, atTime + 0.03);
osc.connect(env).connect(metronomeGain);
osc.start(atTime);
osc.stop(atTime + 0.05);
osc.onended = () => {
try { osc.disconnect(); } catch {}
try { env.disconnect(); } catch {}
};
}Reply with "@CharlieHelps yes please" if you'd like me to apply this improvement in the doc.
| async function initMic() { | ||
| const stream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: false } }); | ||
| const micSrc = audioCtx.createMediaStreamSource(stream); | ||
| micSrc.connect(micGain); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
initMic doesn’t handle permission denial or other getUserMedia failures, and it doesn’t provide a way to stop the stream. Adding try/catch and a returned cleanup function makes the example safer and more complete.
Suggestion
Wrap mic initialization with error handling and return a cleanup:
async function initMic() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: false }
});
const micSrc = audioCtx.createMediaStreamSource(stream);
micSrc.connect(micGain);
return () => {
try { micSrc.disconnect(); } catch {}
stream.getTracks().forEach(t => t.stop());
};
} catch (err) {
console.error('Mic init failed', err);
// surface to UI as needed
return () => {};
}
}Reply with "@CharlieHelps yes please" if you'd like me to update the doc accordingly.
Expand this to see my work.
|
…eb Audio examples (gesture-gated resume, robust IR loader, metronome cleanup)
|
Applied the approved suggestions (“yes please to all”). Changes are in 2ebf602.
Updated snippets (for quick review)AudioContext (gesture‑gated): // 1) Context (user gesture required in many browsers)
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
// Start/resume only in direct response to a user gesture (e.g., a Play button)
document.querySelector('#start-audio')?.addEventListener('click', async () => {
if (audioCtx.state !== 'running') await audioCtx.resume();
// now safe to create/start nodes
});Impulse response loader (validation + error handling): async function loadImpulseResponse(url) {
const res = await fetch(url, { mode: 'cors' });
if (!res.ok) throw new Error(`Failed to fetch IR ${url}: ${res.status} ${res.statusText}`);
const ab = await res.arrayBuffer();
try {
return await audioCtx.decodeAudioData(ab);
} catch (err) {
console.error('decodeAudioData failed for IR', url, err);
throw err; // Surface decoding/CORS-related failures clearly
}
}Metronome click (explicit cleanup): function scheduleClick(atTime, downbeat = false) {
const osc = audioCtx.createOscillator();
const env = audioCtx.createGain();
osc.type = 'square';
osc.frequency.setValueAtTime(downbeat ? 2000 : 1600, atTime);
env.gain.setValueAtTime(0.0001, atTime);
env.gain.exponentialRampToValueAtTime(1.0, atTime + 0.001);
env.gain.exponentialRampToValueAtTime(0.0001, atTime + 0.03);
osc.connect(env).connect(metronomeGain);
osc.start(atTime);
osc.stop(atTime + 0.05);
// Cleanup to avoid accumulating nodes during long sessions
osc.onended = () => {
try { osc.disconnect(); } catch {}
try { env.disconnect(); } catch {}
};
}Doc note added: clean up per‑tick nodes after ended to prevent buildup in long‑running metronomes. Verification
Commit: 2ebf602 |
Package Web Audio research from Issue #228 into a new llms‑txt and index it in the LLM catalog from #229. No runtime audio features added.
Context
app/llms/*.jsonand loaded viaapp/llms/catalog.ts. The prompt builder imports raw.txtdocs statically inapp/prompts.ts.refresh_urlfield anywhere. Omitting it is compatible with the current loader/schema.Changes
app/llms/web-audio.txtapp/llms/web-audio.jsonname,label,description(serves as the requested summary),module,importModule,importName, and a placeholderllmsTxtUrlstring.refresh_urlfield.app/prompts.ts./llms/web-audio.txt?rawand register it inllmsTextContentso the docs are included when this catalog entry is selected.Impact
DEFAULT_DEPENDENCIESunchanged).Verification
pnpm typecheck— no TypeScript errorspnpm test— 68 files, 386 tests passed (4 skipped)Closes #228