From f423ea293fcb4893cfd3b9b02bded3bbd32abc97 Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Fri, 7 Aug 2026 13:26:25 -0700 Subject: [PATCH] docs(skills): add /hyperframes-audio, with a headless carve MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mixing was the one audio surface no skill covered: the effect chain, automation lanes and the voiceover carve existed with no guidance, so an agent had the attributes and none of the judgement for using them. The skill teaches the carve as what it is — a relationship between two tracks, wired like a sidechain, with the settings on the bed naming the voice — and routes the effect families by the problem each solves rather than listing parameters. It ships scripts/carve.mjs because a skill teaching a feature agents cannot operate is not much of a skill: the analysis needs decoded PCM, and the only other way in is clicking a Studio panel. Same core functions and same decode rate, so headless output and panel output are the same three attributes. It finds the voice and the bed itself — names first, then by ear, measuring how much of each track is quiet, since a voice stops between phrases and a bed does not — and refuses rather than guessing when two tracks are too close to call. References carry the full registry with which parameters can actually be automated (the four worklet effects expose none, so a lane on one is silently inert) and the exact JSON of the three attributes. Counts, catalogues and the core-skill manifest updated in lockstep; the pinning test in skillsManifest is what caught the two surfaces the maintenance checklist in CLAUDE.md does not name. Co-Authored-By: Claude Opus 5 (1M context) --- .claude-plugin/marketplace.json | 1 + CLAUDE.md | 9 +- README.md | 7 +- packages/cli/src/templates/_shared/AGENTS.md | 2 +- packages/cli/src/templates/_shared/CLAUDE.md | 2 +- packages/cli/src/utils/skillsManifest.ts | 1 + skills/hyperframes-audio/SKILL.md | 272 ++++++++++++ .../references/attributes.md | 88 ++++ .../references/fx-registry.md | 84 ++++ skills/hyperframes-audio/scripts/carve.mjs | 418 ++++++++++++++++++ skills/hyperframes/SKILL.md | 1 + 11 files changed, 876 insertions(+), 9 deletions(-) create mode 100644 skills/hyperframes-audio/SKILL.md create mode 100644 skills/hyperframes-audio/references/attributes.md create mode 100644 skills/hyperframes-audio/references/fx-registry.md create mode 100644 skills/hyperframes-audio/scripts/carve.mjs diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 936b1c0945..c472c1282f 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -15,6 +15,7 @@ "skills": [ "./skills/hyperframes", "./skills/hyperframes-animation", + "./skills/hyperframes-audio", "./skills/hyperframes-cli", "./skills/hyperframes-core", "./skills/hyperframes-creative", diff --git a/CLAUDE.md b/CLAUDE.md index 638926508f..31edd2a183 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,12 +4,12 @@ Open-source video rendering framework: write HTML, render video. ## Skills -This repo ships 19 AI agent skills via [vercel-labs/skills](https://github.com/vercel-labs/skills). Install them before writing compositions — they encode framework-specific patterns that generic docs don't cover. **Default to the core set**: the `/hyperframes` router installs each creation workflow on demand; install all 19 only when the user explicitly asks for the full set. +This repo ships 20 AI agent skills via [vercel-labs/skills](https://github.com/vercel-labs/skills). Install them before writing compositions — they encode framework-specific patterns that generic docs don't cover. **Default to the core set**: the `/hyperframes` router installs each creation workflow on demand; install all 20 only when the user explicitly asks for the full set. ```bash npx hyperframes skills update # default: installs/refreshes the core set — workflows install on demand -npx skills add heygen-com/hyperframes --full-depth # interactive picker (terminal only — non-interactive without --skill installs all 19) -npx skills add heygen-com/hyperframes --all --full-depth # all 19 at once — only on explicit request +npx skills add heygen-com/hyperframes --full-depth # interactive picker (terminal only — non-interactive without --skill installs all 20) +npx skills add heygen-com/hyperframes --all --full-depth # all 20 at once — only on explicit request npx skills add heygen-com/hyperframes --skill --full-depth # just one (bare name, no leading slash) ``` @@ -39,6 +39,7 @@ Atomic capabilities the creation workflows compose against — pull one when you - `/hyperframes-keyframes` — seek-safe keyframe authoring across runtimes: GSAP timelines, CSS keyframes, Anime.js, WAAPI, FLIP, paths, masks, SVG morph/draw, text trails, 3D depth; plus `hyperframes keyframes` diagnostics for surfacing and verifying rendered motion. - `/hyperframes-creative` — non-animation creative direction: `frame.md` / `design.md` handling, palettes, typography, narration, beat planning, audio-reactive visuals, composition patterns. - `/media-use` — the media OS: resolve any media need (BGM, SFX, image, icon, logo, voice, color grade, LUT) into a frozen local file or paste-ready block + ledger record; generate via TTS / music / image models when the catalog misses; transcribe, caption, remove backgrounds, and reuse assets across projects. One shared `scripts/audio.mjs` engine + manifest tracking; keeps search noise on disk. +- `/hyperframes-audio` — mix the audio already placed in a composition: voiceover carve (dip a music bed only in the bands the voice occupies, static or dynamic, level match included), the effect chain (EQ, compressor, limiter, gate, saturation, delay, reverb, chorus, phaser, bitcrush), and automation envelopes on volume or any effect parameter. Sourcing the audio is `/media-use`; this is what happens to it afterwards. - `/hyperframes-cli` — CLI dev loop: `init`, `add`, `lint`, `check`, `snapshot`, `preview`, `render`, `publish`, `doctor`, `lambda` (AWS Lambda cloud rendering). - `/hyperframes-registry` — install and wire registry blocks and components into compositions via `hyperframes add`. Covers authoring a new block or component to contribute upstream. - `/figma` — import Figma assets, tokens, components, and storyboard sections → reconstructed motion (frames read as states, not slides) (REST/CLI) plus Motion animations (MCP) and shaders (MCP source / native export) into a composition. @@ -51,7 +52,7 @@ When adding a new skill, or substantially renaming / repurposing an existing one 2. The scaffolded project template `packages/cli/src/templates/_shared/CLAUDE.md` + `AGENTS.md` — written into every `hyperframes init` project, so a stale entry there ships to users. The two template files must stay byte-identical. 3. If the skill changes the routing surface for "make a video" requests, also update the routing table + intent layer in `skills/hyperframes/SKILL.md` AND that workflow's own route file, `skills/hyperframes/references/routes/.md`. One file carries both halves: the input/output/trigger contract the router reads before the workflow is installed, and its interview entry (must-haves, conditionals, deferred asks, run-shape). The older `references/workflow-catalog.md` and `references/route-briefs.md` are now "moved" stubs pointing at `routes/` — don't edit them. 4. Mirror the Router / Creation workflows / Domain skills grouping across all surfaces so a skill always lives in the same column. -5. Skill count appears in the README and CLAUDE.md intro lines ("19 AI agent skills…") — update on add/remove. The `docs/guides/skills.mdx` page and the CLI templates deliberately omit a count to avoid drift; keep them count-free. +5. Skill count appears in the README and CLAUDE.md intro lines ("20 AI agent skills…") — update on add/remove. The `docs/guides/skills.mdx` page and the CLI templates deliberately omit a count to avoid drift; keep them count-free. The skill's own `SKILL.md` frontmatter `description:` is the source of truth for the one-line "use when" blurb; copy from there into the catalog rather than paraphrasing. diff --git a/README.md b/README.md index 2a34005a40..df182d6b42 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Install the HyperFrames skills, then describe the video you want: npx skills add heygen-com/hyperframes --full-depth ``` -> The picker opens with nothing pre-selected — the **Core Skills** group is all you need: the `/hyperframes` router installs each creation workflow on demand. Agents and non-interactive runs should use `npx hyperframes skills update` instead — it installs exactly the core set, whereas a non-interactive `skills add` without `--skill` installs all 19. +> The picker opens with nothing pre-selected — the **Core Skills** group is all you need: the `/hyperframes` router installs each creation workflow on demand. Agents and non-interactive runs should use `npx hyperframes skills update` instead — it installs exactly the core set, whereas a non-interactive `skills add` without `--skill` installs all 20. > > `--full-depth` does a full clone of the repo's current `main`. Without it, `skills add` fetches the skills.sh registry blob, which lags `main` by hours — you'd get an older copy of a skill. (`hyperframes skills update` already installs full-depth.) @@ -53,9 +53,9 @@ The skills teach agents the HyperFrames production loop: plan the video, write v ## Skills -HyperFrames ships 19 skills agents load on demand. Read `/hyperframes` first — it's the router and capability map; it picks a workflow for any "make me a…" request — video, deck, or composition port — and points to the domain skills below. +HyperFrames ships 20 skills agents load on demand. Read `/hyperframes` first — it's the router and capability map; it picks a workflow for any "make me a…" request — video, deck, or composition port — and points to the domain skills below. -Default to the **core set** — the router installs each creation workflow on demand. `npx hyperframes skills update` installs exactly that from anywhere; the interactive picker (`npx skills add heygen-com/hyperframes --full-depth`) lists it as the "Core Skills" group, nothing pre-selected. The picker is interactive-only — a non-interactive or agent run without `--skill` installs all 19. Use `npx skills add heygen-com/hyperframes --all --full-depth` to install all 19 deliberately (skips the picker), or `npx skills add heygen-com/hyperframes --skill --full-depth` for just one (bare name, no leading `/`). Keep `--full-depth` — it installs the current `main`; without it `skills add` fetches the skills.sh blob, which lags by hours. +Default to the **core set** — the router installs each creation workflow on demand. `npx hyperframes skills update` installs exactly that from anywhere; the interactive picker (`npx skills add heygen-com/hyperframes --full-depth`) lists it as the "Core Skills" group, nothing pre-selected. The picker is interactive-only — a non-interactive or agent run without `--skill` installs all 20. Use `npx skills add heygen-com/hyperframes --all --full-depth` to install all 20 deliberately (skips the picker), or `npx skills add heygen-com/hyperframes --skill --full-depth` for just one (bare name, no leading `/`). Keep `--full-depth` — it installs the current `main`; without it `skills add` fetches the skills.sh blob, which lags by hours. Installs stay lean after that: `npx hyperframes init` keeps the **core set** fresh (the router, the `hyperframes-*` domain skills, and `media-use` — plus whatever is already installed; `/figma` stays on demand) and never expands a partial install; the creation workflows install **on demand** — the router runs `npx hyperframes skills update ` before entering one. Nothing re-pulls the full set behind your back. @@ -102,6 +102,7 @@ Atomic capabilities the creation workflows compose against — pull one when you | `/hyperframes-creative` | Non-animation creative direction — `frame.md` / `design.md`, palettes, typography, narration, beat planning, audio-reactive visuals, composition patterns. | | `/media-use` | The media OS — resolve any media need (BGM, SFX, image, icon, logo, voice, color grade, LUT) into a frozen local file or paste-ready block + ledger record, generate via TTS/music/image models when the catalog misses, transcribe, caption, remove backgrounds, and reuse assets across projects. One shared audio engine + manifest tracking. | | `/hyperframes-cli` | CLI dev loop — `init`, `lint`, `check`, `snapshot`, `preview`, `render`, `publish`, `doctor`, plus HeyGen-hosted cloud rendering (`cloud render`) and AWS Lambda rendering (`lambda deploy / render / progress`). | +| `/hyperframes-audio` | Mix the audio already placed in a composition — voiceover carve (dip a music bed only in the bands the voice occupies, static or dynamic, level match included), the effect chain (EQ, compressor, limiter, gate, saturation, delay, reverb, chorus, phaser, bitcrush), and automation envelopes on volume or any effect parameter. Sourcing the audio is `/media-use`. | | `/hyperframes-registry` | Install and wire registry blocks and components into compositions via `hyperframes add`. Authoring a new block or component to contribute upstream. | | `/figma` | Import Figma assets, tokens, components, and storyboard sections → reconstructed motion (frames read as states, not slides) (REST/CLI) plus Motion animations (MCP) and shaders (MCP source / native export) into a composition. | diff --git a/packages/cli/src/templates/_shared/AGENTS.md b/packages/cli/src/templates/_shared/AGENTS.md index 5c8126aeb2..1762ea7a66 100644 --- a/packages/cli/src/templates/_shared/AGENTS.md +++ b/packages/cli/src/templates/_shared/AGENTS.md @@ -18,7 +18,7 @@ **Porting an existing composition?** `/remotion-to-hyperframes` translates a Remotion (React) composition into HyperFrames HTML — a source migration, separate from the creation workflows above. -The domain skills (`/hyperframes-core`, `/hyperframes-animation`, `/hyperframes-keyframes`, `/hyperframes-creative`, `/hyperframes-cli`, `/media-use`, `/hyperframes-registry`, `/figma`) and the full capability map live inside `/hyperframes` — it is the single source of truth for which skill handles which intent. +The domain skills (`/hyperframes-core`, `/hyperframes-animation`, `/hyperframes-keyframes`, `/hyperframes-creative`, `/hyperframes-cli`, `/media-use`, `/hyperframes-audio`, `/hyperframes-registry`, `/figma`) and the full capability map live inside `/hyperframes` — it is the single source of truth for which skill handles which intent. **Changing how real footage or images look or reveal?** Load `/media-use` and read its `references/media-treatments.md` before editing, even when the request only says dark, flat, boring, retro, private, or “make the reveal cooler.” It governs how footage is treated, never whether media may be used. Use canonical media treatments and seek-safe motion; do not improvise equivalent CSS/SVG filters or overlays. diff --git a/packages/cli/src/templates/_shared/CLAUDE.md b/packages/cli/src/templates/_shared/CLAUDE.md index 5c8126aeb2..1762ea7a66 100644 --- a/packages/cli/src/templates/_shared/CLAUDE.md +++ b/packages/cli/src/templates/_shared/CLAUDE.md @@ -18,7 +18,7 @@ **Porting an existing composition?** `/remotion-to-hyperframes` translates a Remotion (React) composition into HyperFrames HTML — a source migration, separate from the creation workflows above. -The domain skills (`/hyperframes-core`, `/hyperframes-animation`, `/hyperframes-keyframes`, `/hyperframes-creative`, `/hyperframes-cli`, `/media-use`, `/hyperframes-registry`, `/figma`) and the full capability map live inside `/hyperframes` — it is the single source of truth for which skill handles which intent. +The domain skills (`/hyperframes-core`, `/hyperframes-animation`, `/hyperframes-keyframes`, `/hyperframes-creative`, `/hyperframes-cli`, `/media-use`, `/hyperframes-audio`, `/hyperframes-registry`, `/figma`) and the full capability map live inside `/hyperframes` — it is the single source of truth for which skill handles which intent. **Changing how real footage or images look or reveal?** Load `/media-use` and read its `references/media-treatments.md` before editing, even when the request only says dark, flat, boring, retro, private, or “make the reveal cooler.” It governs how footage is treated, never whether media may be used. Use canonical media treatments and seek-safe motion; do not improvise equivalent CSS/SVG filters or overlays. diff --git a/packages/cli/src/utils/skillsManifest.ts b/packages/cli/src/utils/skillsManifest.ts index 5145cc0348..ad613270f4 100644 --- a/packages/cli/src/utils/skillsManifest.ts +++ b/packages/cli/src/utils/skillsManifest.ts @@ -150,6 +150,7 @@ export function isCoreSkill(name: string): boolean { export const FALLBACK_CORE_SKILLS: readonly string[] = [ "hyperframes", "hyperframes-animation", + "hyperframes-audio", "hyperframes-cli", "hyperframes-core", "hyperframes-creative", diff --git a/skills/hyperframes-audio/SKILL.md b/skills/hyperframes-audio/SKILL.md new file mode 100644 index 0000000000..9bae288c13 --- /dev/null +++ b/skills/hyperframes-audio/SKILL.md @@ -0,0 +1,272 @@ +--- +name: hyperframes-audio +description: > + Use when audio already placed in a HyperFrames composition needs to be mixed: + a music bed that fights a voiceover (voiceover carve), effects on a track + (EQ, compressor, limiter, gate, saturation, delay, reverb, chorus, phaser, + bitcrush), or automation envelopes drawn on a track's volume or any effect + parameter. + Don't use for sourcing or generating audio — finding BGM, SFX, or making a + voiceover is `/media-use`. Don't use for clip timing or track layout, which is + `/hyperframes-core`. +--- + +# HyperFrames Audio + +A mix is a set of relationships, not a stack of processors. Two tracks that each +sound right alone can be unlistenable together, and the fix is almost never "turn +one down" — it is finding what they are fighting over and giving it to whichever +one needs it. Every tool here exists to express one of those relationships. + +Effects live on the element as `data-fx-chain`, and preview and render run the +same Web Audio graph — the studio in a live context, the engine in an offline one +inside the browser it already drives. There is one implementation of each effect, +so what you hear while scrubbing is what gets written. You never tune twice. + +Three attributes carry everything, all on the audio/video element itself: + +| Attribute | Holds | +| ----------------- | --------------------------------------------------------- | +| `data-fx-chain` | the effects, in signal order | +| `data-automation` | envelopes on this track's volume or its effect parameters | +| `data-fx-carve` | the carve's own settings, so it can be re-derived | + +Exact JSON for each, and the rules a lane must satisfy: `references/attributes.md`. +Every effect with its parameters, ranges and units: `references/fx-registry.md`. + +## How it fits together + +Two authoring surfaces write those attributes; two runtimes read them through the +same builders. That shared middle is why preview predicts the render. + +```mermaid +flowchart TB + voice["voice track
media file"] + bed["music bed
media file"] + + subgraph AUTHOR["Authoring — the only things that write attributes"] + panel["Studio
Voiceover carve control"] + script["scripts/carve.mjs
detects the pair, dynamic by default"] + analysis["core/audioCarve.ts
carveProfile · analyseCarveBands
analyseCarveDuck · analyseCarveDynamics"] + panel --> analysis + script --> analysis + end + + voice --> analysis + bed --> analysis + + subgraph ATTRS["Written onto the bed element"] + carveAttr["data-fx-carve
source · strength · dynamic"] + chainAttr["data-fx-chain
peaking xN + gain, tagged fromCarve"] + autoAttr["data-automation
a lane per carved parameter"] + end + + analysis --> carveAttr + analysis --> chainAttr + analysis --> autoAttr + + subgraph SHARED["One implementation, read by both"] + build["audioFxGraph.ts · buildFxChain"] + sched["audioFxAutomation.ts · scheduleChainAutomation"] + end + + chainAttr --> build + autoAttr --> sched + + build --> preview["Preview
live AudioContext
attachElementFxChain"] + sched --> preview + build --> render["Render
OfflineAudioContext in the headless browser
applyAudioFxChain"] + sched --> render + + preview --> heard["what you hear while scrubbing"] + render --> wav["processed WAV
+ chainTailSeconds so the mix lets the tail through"] + wav --> mix["engine · audioMixer
volume lane baked into the PCM here, not in the graph"] + mix --> out["the rendered mix"] + + edit["editing the attribute mid-playback"] -.->|MutationObserver| preview +``` + +The carve's own settings are never read at playback — the chain and lanes it +produced are what play. `data-fx-carve` exists so strength can be changed on an +existing carve instead of guessed back out of the filters. + +Inside a carved bed the signal runs through the dips first, then the level match, +then anything you built yourself — which is why a limiter you add still acts as +the last ceiling: + +```mermaid +flowchart LR + src["decoded bed"] --> p1["peaking
400 Hz"] + p1 --> p2["peaking
1 kHz"] + p2 --> p3["peaking
1.6 kHz"] + p3 --> g["gain
level match"] + g --> hand["your own effects
e.g. limiter"] + hand --> dest["track gain, then out"] + + l1["lane fx.n1.gain"] -.->|"envelope of the voice's
level in that band"| p1 + l4["lane fx.n4.gain"] -.->|"how far the bed
ducks overall"| g +``` + +A static carve is the same graph with fixed values and no lanes at all. + +## Reach for a family by the problem, not the name + +**Filters** (`highpass`, `lowpass`, `peaking`, `lowshelf`, `highshelf`) decide +which frequencies a track is allowed to occupy. This is the first tool for two +sources colliding, because collisions happen in bands: a bed and a voice both +want 1–3 kHz, and taking that from the bed costs the bed far less than turning +the whole thing down costs the mix. A high-pass on a voice is the standard fix +for rumble; a low-pass darkens or muffles deliberately. + +**Dynamics** (`gain`, `compressor`, `limiter`, `gate`) decide how a track's level +behaves over time. Compression narrows the distance between loud and quiet so the +quiet parts can come up. A limiter is a ceiling — it does not shape anything, it +guarantees nothing gets past. A gate removes what is below a threshold, which is +how you silence room tone between phrases. `gain` is a plain level stage, and it +is what an automation lane rides when a track has to move out of the way. + +**Nonlinear** (`saturate`, `bitcrush`) changes the waveform's shape, which adds +harmonics that were not there. Reach for it when a track needs character or +grit rather than correction — and remember it is generative: it makes a thin +source denser, not cleaner. + +**Time** (`delay`, `reverb`, `chorus`, `phaser`) puts a track in a space or gives +it width. These are the ones that most easily wreck a mix, because a tail or a +detuned copy occupies the same room a voice needs. Use them on the thing that +should sit *behind* something else, and keep the wet amount lower than sounds +right in isolation. + +The chain is serial: each effect processes what the one before it produced. So +corrective filtering goes early, character in the middle, and a limiter last +where it can actually act as a ceiling. + +## Voiceover carve + +**The problem it solves.** A music bed under a voice makes the voice hard to +follow. The reflex is to duck the whole bed, which works and costs the bed all of +its presence — the music goes limp for the entire voiceover. But the voice does +not need the whole spectrum. It needs the few bands it actually occupies. Carve +takes only those, and the bed keeps its low end and its top, so it is still music +while the voice is still intelligible. + +**It is a relationship, not an effect.** The settings live on the *bed* — the +track that gets processed — and they name the voice to listen to, exactly as a +sidechain compressor does: you select the track that gets quieter and pick what +makes it quieter. **Never put a carve on the voice track.** A voice carved +against itself is a bug, not a subtle mix choice. + +**One knob.** `strength` is 0..1 and derives everything: how deep to cut, how +many bands, how wide, how far to favour intelligibility over raw voice energy, +how far the level may drop, how far under the voice to aim. Those six move +together in any real mix — a gentle carve is a shallow cut in few bands with +little ducking, a hard one is deeper in more bands with more — so they are one +relationship written once, in `carveProfile`. Default is `0.25` — a 6 dB dip in +three bands with 6 dB of level room, audible without sounding like a hole. At +`0.5` the dip reaches 10 dB, which is where a carve starts being heard as an +effect rather than as room for the voice; above that is deliberate territory for +a loud bed under a quiet voice. `0` is spectral only — one band, no level match +at all. + +**Carve by default.** A bed playing under narration wants a carve; it is not a +polish step to get to if there is time. Place both tracks, run the command below, +listen. Skip it only when there is no narration for the music to sit under — a +music video, a title card, a montage cut to the track. + +**Static or dynamic — dynamic unless you know otherwise.** A static carve holds +its cuts for the whole clip, including every pause, so the bed is thinned where +there is nothing to make room for. Dynamic turns every value into an envelope of +the voice's own level: silence leaves the bed alone, a loud passage pushes the +carve to full depth. That is what almost every voiceover wants, so it is the +default. Reach for `--static` only for wall-to-wall narration with no real gaps, +where an envelope is hundreds of breakpoints describing a constant. + +**Level matching is part of it.** Spectral carving cannot fix a bed that is +simply louder than the voice. So the carve also measures how far over the voice +the bed sits and writes a `gain` stage: held at one value for a static carve, +driven by an envelope for a dynamic one. That envelope releases slowly on +purpose — music that snaps back to full the instant a word ends sounds like a +machine doing it. + +**Running it.** In Studio: pick the voice in the bed's Voiceover carve control; +turning it on adds the modules and strength adjusts what is there. Headless — +which is the path when you are authoring a composition rather than editing one: + +```bash +node /scripts/carve.mjs --comp index.html +``` + +That is the whole command. It finds the voice and the bed itself, carves +dynamically at the default strength, and prints what it decided: + +``` +bed music-bed (name looks like music) +voice narration (only track left) +carve strength 0.25 dynamic +bands 400Hz -6dB q1.4, 1000Hz -3dB q1.4, 1600Hz -3.17dB q1.4 +level 216-point envelope, floor -6 dB +``` + +Name the pair with `--bed` / `--voice` when the composition has several plausible +tracks, `--strength` to push it, `--static` to hold one depth, `--dry-run` to see +that report and write nothing. + +**How it picks the pair.** Names first, because that is what you already told it +and the answer is explainable: a track whose id or filename looks like music +(`music`, `bgm`, `bed`, `score`…) is the bed, one that looks like a voice +(`voice`, `vo`, `narration`, `speech`…) is the voice, and SFX-shaped names are not +candidates for either. If one role is filled and a single track is left, that +track takes the other role. Only when names decide nothing does it listen: it +measures how much of each track is quiet, and the one that stops between phrases +is the voice. **When two tracks are too close to call it refuses and asks you to +name them** rather than carving the wrong one — a bed carved against a bed is +silent and confusing, and typing two ids is cheap. + +Same analysis functions as the panel, so the result is identical. Needs `ffmpeg` +on PATH and `@hyperframes/core` installed in the project (`npm i -D +@hyperframes/core`) — the CLI inlines core rather than shipping it, so it cannot +be borrowed from there. + +**What it writes** is an ordinary chain of peaking filters plus a gain stage, +tagged `fromCarve`. That tagging is the whole trick: a re-run replaces the +previous carve and leaves every effect you built by hand — and every lane you +drew by hand — exactly where it was. So re-carving at a new strength is safe and +repeatable, and `data-fx-carve` exists so the settings can be read back rather +than guessed from the filters. + +## Automation + +A lane is a set of breakpoints on one parameter: `{t, v}` in clip-local seconds +and the parameter's own units. Targets are `volume` for the track's level, or +`fx..` for an effect's knob. + +**Only some parameters can be automated, and a lane on the others is silently +inert.** A knob is automatable when a Web Audio `AudioParam` backs it. The four +worklet-based effects — `compressor`, `limiter`, `gate`, `bitcrush` — expose +none at all, so no lane on any of their parameters will ever move: to make a +compressor's behaviour change over time, automate a `gain` stage before it +instead. `references/fx-registry.md` marks every parameter. + +## Verify + +Almost no static gate covers the mix. The linter reads `data-automation` for +exactly one conflict — `audio_volume_double_automation`, a volume lane on a track +that also has a GSAP tween on `volume`, where the lane wins and the tween is +ignored — and nothing validates the chain or the effect lanes at all. What +enforces those is the render: a chain it cannot parse fails the whole mix rather +than quietly writing the dry signal, because a mix that sounds plausible and is +wrong is worse than a refusal. Preview is the opposite by design: an unreadable +chain plays dry so the composition stays workable. + +A lane pointing at a node the chain does not have is pruned on read, not an +error — so a typo'd `nodeId` costs you the envelope silently. Read the ids back +out of the chain rather than assuming what was minted. + +Effects with a tail (`reverb`, `delay`) make the rendered track **longer** than +its source, and the mix is told how much by the chain. So a bed with reverb no +longer ends exactly at its `data-duration`; that is expected, not a bug. + +Beyond that, a mix is verified by rendering and listening. For a carve: the voice +should be legible without the bed sounding hollowed, and with `dynamic` the bed +should come back up between phrases rather than staying flat. If the bed sounds +notched rather than simply quieter under the voice, the strength is too high — +that is the one failure mode with an obvious sound. diff --git a/skills/hyperframes-audio/references/attributes.md b/skills/hyperframes-audio/references/attributes.md new file mode 100644 index 0000000000..cb44a1bfd3 --- /dev/null +++ b/skills/hyperframes-audio/references/attributes.md @@ -0,0 +1,88 @@ +# The three audio attributes + +All three go on the `