Skip to main content
Use this reference when Studio controls are not enough: you need to write a mix into HTML, know a parameter’s exact range, automate a knob, or understand why something behaves differently in the render than in preview. For everyday use start with Mix audio and apply effects, and for what to ask an agent for, Audio effects and mixing. A composition carries its whole mix in the HTML. There is no session file and nothing to load beside the markup.

The four attributes

data-audio-group is the odd one: a bare string, and ignored on <video>. The other three are JSON. Of those, only data-fx-chain and data-automation can also sit on an <hf-audio-group>, where they apply to the group’s whole bus; data-fx-carve belongs to a clip. Write the JSON attributes double-quoted, escaping the JSON’s own quotes as &quot; and & as &amp; — as every example on this page does. The browser reads them through getAttribute and does not mind either way, but scripts/carve.mjs finds them with a name="..." regex, so a single-quoted attribute is invisible to it: a carve reports no existing chain and overwrites work it could not see. Nothing static validates a chain. Preview plays an unreadable chain dry so the composition stays workable; the render fails the whole mix rather than shipping a dry track that sounds plausible and is wrong.

data-fx-chain

Unescaped, that chain reads: a high-pass labelled Remove Rumble, a peaking cut labelled Reduce Mud, and a bypassed limiter.
  • Order is signal order. Each node processes what the one before produced.
  • type is an effect id from the registry. params are in the units a person thinks in — dB, ms, Hz. Out-of-range values are clamped on read, so a chain that parses is always safe to realise.
  • id is a stable handle. Automation addresses nodes by id, never by position, so reordering a chain cannot re-point a lane at a different effect. A node with no id loads fine but cannot be automated. Studio hands out the first free n1, n2, …
  • label replaces the effect’s own name in the rack. Write one whenever a node is doing a named job — two peaking nodes otherwise show the same row twice and you cannot tell the mud cut from the clarity lift.
  • enabled: false is bypass: the node stays in the chain, out of the signal path. Absent means enabled.
  • fromCarve: true marks a node the carve analysis generated. fromPreset carries the id of the preset that wrote the node, which is how re-applying a preset replaces its own nodes in place. Do not set fromCarve by hand — the next carve deletes exactly those nodes.

data-automation

That is two lanes: the track’s volume dropping to 0.4 by 2.5 s, and node n2’s gain moving to −6 dB by 1 s along a bent segment.

Two clocks

Getting this backwards is the most expensive mistake on the page: a group lane written in clip-relative time lands wherever the first member happens to start. Preview and render agree on both clocks.

Volume is not capped at 1

v on a volume lane is a linear gain multiplier with a ceiling of +12 dB, which is about 3.981 — the same ceiling data-volume uses. A lane that boosts above unity is valid and will play; 1 is unity, not the maximum. A lane holds its first value backwards to the start of its clip and its last value forward to the end. A bed that begins before the voice therefore needs an explicit “no cut” point at t: 0, or it starts out already ducked. Maximum 512 points per lane. A lane whose node is gone is pruned on read rather than erroring — so a mistyped nodeId costs you the envelope silently. Read ids back out of the chain rather than assuming what was minted. A volume lane and a GSAP tween on volume conflict: the lane wins and the tween is ignored. The linter reports that as audio_volume_double_automation.

data-fx-carve

Unescaped: {"enabled":true,"sources":["voiceover"],"strength":0.25}.
  • sources names what this bed makes room for — element ids, or a group id, which expands to its current members on every analysis.
  • strength is 0–1 and derives the whole mechanism.
  • enabled: false keeps the settings and stops the carve. It exists because a bed with exactly one candidate voice is carved by default; with “off” as an absent attribute, switching it off would read as never-configured and the default would put it back.
This attribute is not read at playback — the chain and lanes it produced are what play. It exists so strength can be changed on an existing carve instead of being guessed back out of the filters. Older projects may carry six mechanism numbers (maxCutDb, bands, q, intelligibilityBias, duckDb, headroomDb) instead of strength. They still load: the depth maps back onto a strength and the rest is re-derived. A stored carve with no enabled reads as on, a single source reads as a one-voice sources list, and a stored dynamic is ignored — every carve follows the speech now.

data-audio-group

Membership is held by the member, not by the group nesting its members, so a deleted clip simply disappears from the group on the next resolve and nothing dangles.
  • <hf-audio-group> is optional metadata: data-label, data-volume, data-hidden, and its own data-fx-chain / data-automation. A group with members but no element still resolves, using its id as the label.
  • Audio only. data-audio-group on a <video> is ignored, and groups do not nest.
  • data-audio-group="" is no group at all.
  • The element must be inert. The runtime injects hf-audio-group{display:none!important} so an unknown custom element cannot take a flex gap or shift :nth-child — but author the rule yourself so a bare preview never lays it out.
  • Group ids and element ids share one namespace.

Effect registry

Sixteen effects. Values outside a range are clamped on read. AUTO marks a parameter an automation lane can drive; anything unmarked cannot move over time. HF_AUDIO_FX in @hyperframes/core/audio-fx is the source of truth.

Filter — which frequencies a track may occupy

q is bandwidth — higher is narrower. poles is the slope: 2 is the usual biquad (12 dB/oct), 1 is gentler (6 dB/oct). Shelving filters have no q; the Web Audio spec leaves it unused for them.

Dynamics — how level behaves over time

Cuts on gain reach −60 dB, boosts stop at +12: it is a level stage for making room, and a chain that could add 40 dB would clip long before that was useful. knee of 1 is a hard corner. mix below 1 blends the dry signal back in (parallel compression). range is how far down the gate pulls when closed.

Nonlinear — changes the waveform’s shape

tanh is the gentlest curve, hard is outright clipping. Higher oversample costs more CPU and keeps aliasing down. samples repeats each sample N times — a crude downsample, which is where the lo-fi character comes from.

Time — where a track sits, and how it moves

pitchshift moves pitch without changing playback speed, which is what separates it from a playback-rate change. Neither of its parameters can be automated — it is a worklet. Reverb convolves a generated impulse and both preview and render generate the same one, so a room is reproducible without shipping an impulse file. Higher damping rolls the top off the tail faster. feedback is bounded below 1 because at 1 it never decays.

Why some parameters cannot be automated

Automation is handed to the audio thread once, as native AudioParam ramps and curves — that is what keeps it sample-accurate and identical between preview and render. A parameter can therefore only be automated if an AudioParam backs it. Three kinds do not: A lane on a non-automatable parameter is silently inert. To make one of those behave differently over time, automate a gain stage around it instead: a lane on a gain before a compressor changes how hard the compressor is driven, which is most of what automating its threshold would have done.

Presets, jobs, and one-knob profiles

Every one of these is a shortcut to a chain you could have built by hand. Open any of them and you find ordinary effects with their parameters showing.

Presets

Twenty-two, in four families. Applying one appends; re-applying one already present replaces its own nodes in place, because position is signal order. chipmunk, giant, and monster are built on pitchshift, so they change the speaker rather than the channel. voice-clean is Remove Rumble → Reduce Mud → Even Out Loudness → Add Clarity → Peak Ceiling. voice-broadcast is denser and more forward; voice-warm adds body rather than cutting it. A preset’s nodes are wrapped in a wet/dry blend, so presetAmount (0–1) fades the whole thing, and fx.preset.<id> is an automation target that ramps it over time. That is the only way to automate a preset as a unit — its nodes share no common parameter.

Jobs

Five named peaking filters with the frequency already chosen. Picking the job is picking the range, which is what makes a single “how much” knob honest. Writing one by hand, carry the name in label — the parameters alone are not the job. Every job also ships inside a preset at identical settings, which is where the five came from. So check what a preset already contains before adding a job on top: voice-clean plus a Reduce Mud job is −6 dB at 250 Hz where −3 was meant.

One-knob profiles

Five effects have no single parameter that can honestly be their face — a compressor’s threshold means nothing without its ratio. They get a derived control instead, 0–1, setting several parameters together. Evenness, Warmth, and Space are level-matched: the make-up gain, output trim, and dry leg move with the drive, so turning the knob up does not also turn the track up. Tightness and Crush are not, because neither has a trim to move. The chain stores the mechanism values, not the knob position — the knob is read back by inverting the curve, so hand-editing a parameter under a profile is allowed and simply moves the knob.

Voiceover carve

A carve is a relationship, not an effect. The settings live on the bed — the track that gets processed — and name the voices to listen to, exactly as a sidechain compressor does. Never put a carve on a voice track. strength derives six numbers that move together in any real mix: how deep to cut, how many bands, how wide, how far to favour intelligibility over raw voice energy, how far the level may drop, and how far under the voice to aim. Every value becomes an envelope of the speech’s own level: silence leaves the bed alone, a loud passage pushes the carve to full depth. There is no static mode. 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. Voices are summed onto the bed’s clock before anything is measured, so one analysis covers all of them. Voices that never play while the bed does are left out; they cannot mask it. Name a group, not a run of clip ids. A list of ids has to be exhaustively right and stays right only until the next edit — a fourth narration clip added later plays outside the carve’s awareness and the bed silently fails to duck under it. A sources list naming two or more plain clip ids is reported as audio_carve_ungrouped_sources. There are two arrangements where naming the group is worse than naming ids, and both come from the group form resolving later and wider than the analysis that wrote it: Neither shows up on the run that writes it — the analysis sums the voices it detected, so the first pass is correct however wrong the stored attribute is. It surfaces on the next re-analysis, against a file the previous run declared good. Keep the bed in its own group. What a carve writes is an ordinary chain of peaking filters plus a gain stage, tagged fromCarve, and one automation lane per carved parameter. That tagging is the whole trick: a re-run replaces the previous carve and leaves every effect and lane you built by hand exactly where it was.

From the command line

That is the whole command — it finds the voice and the bed itself and prints what it decided:
Name tracks with --bed / --voice (repeatable) when the automatic choice is wrong, --strength to push it, --dry-run to see the report and write nothing. It refuses when it cannot tell which track is the bed rather than carving the wrong one. What it records in sources. When every voice it analysed shares exactly one group, and that group is safe to name, it writes the group id. It falls back to the individual clip ids — and says so — when the group contains the bed or a member classified music or SFX, for the reasons in the table above. A group member classified voice or unknown that this run did not analyse is not a reason to fall back: picking up a clip that starts playing later is the whole point of naming the group. Track choice is by name first, using the same classifier Studio’s picker uses, so the two cannot disagree: an id or filename that looks like music (music, bgm, bed, score…) is the bed, and everything else playing over it that is not SFX-shaped is a voice. Needs ffmpeg on PATH and @hyperframes/core installed in the project.

Groups, mute, and solo

A group sums its members through one bus, and the bus carries the group’s own volume, FX chain, and automation. In the render each group is sub-mixed into a single track at full composition length before entering the main mix, so a group-level effect hears the sum rather than each member separately. Mute and solo are not symmetric, on purpose. Solo is a listening tool. An element is audible while any solo is active only if it is itself soloed or its own group is soloed; a group bus is never itself attenuated by solo, so a soloed member’s path out stays open. A group that is not soloed while one of its members is shows as half-lit — the display-only signal that some of what is under it still plays.

Preview and render

Both read the same two attributes through the same builders, which is why preview predicts the render: Effects with a tail — reverb, delay — make the rendered track longer than its source, and the mix is told how much. A bed with reverb therefore no longer ends exactly at its data-duration. That is expected.

What the linter checks

Almost no static gate covers a mix. Three rules exist: Nothing validates the chain or the effect lanes at all. What enforces those is the render. Beyond that, a mix is verified by rendering and listening.