返回技能市场
开发运维 安全

hyperframes-audio

@admin/hyperframes-audio

Use when audio already placed in a HyperFrames composition needs to be mixed: fade-in/fade-out, crossfade, track gain or volume, volume automation, ducking, a music bed that fights a voiceover (voiceover carve), effects on a track (EQ, compressor, limiter, gate, saturation, delay, reverb, chorus, phaser, bitcrush), automation envelopes drawn on a track's volume or any effect parameter, or one submix bus carrying a chain, a fader and an automation clock for several tracks at once (`<hf-audio-group>`). 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`.

admin 热度 484v0.0.1

HyperFrames 音频

混音是一组关系,而不是一堆处理器的堆叠。两条单独听起来都正确的轨道放在一起可能完全不可听,而修复方法几乎从来不是“调低其中一条”——而是找出它们在争抢什么,并把它交给更需要它的那一条。这里的每个工具都是为了表达这些关系中的一种。

效果位于元素上的 data-fx-chain,预览和渲染运行相同的 Web Audio 图——在实时上下文中是 studio,在浏览器内它已经驱动的离线引擎中是 engine。每个效果只有一种实现,因此你在拖动播放头时听到的就是最终写入的内容。你永远不会调两次。

片段计时仍属于 /hyperframes-core:音频/视频裁剪和源范围使用 data-startdata-durationdata-media-start,交叉淡化会在不同轨道上重叠片段。本 skill 负责已放置轨道的淡入/淡出、交叉淡化包络、轨道增益/轨道音量、音量和效果自动化、闪避/旁白雕刻,以及效果链。/media-use 负责获取、生成和预处理。

常量 data-playback-rate0.1..5)在匹配的音频/视频元素使用相同计时、源偏移和速率时,对于画面和保持音高的声音是渲染安全的。不支持源速度斜坡,因为没有速率包络;请预处理一个派生的同步资产。HyperFrames 不提供自动波形同步或漂移校正。 对于可复制的剪切/交叉淡化/重定时配方,使用 /hyperframes-corereferences/creator-editing-recipes.md

三个属性承载一切,位于音频/视频元素本身——或者,对于前两个属性,位于 <hf-audio-group> 总线上(见“一条总线承载多条轨道”):

| 属性 | 承载内容 | | ----------------- | --------------------------------------------------------- | | data-fx-chain | 效果,按信号顺序 | | data-automation | 本轨道音量或其效果参数上的包络 | | data-fx-carve | 雕刻自身的设置,以便可以重新推导 |

已提供的效果族是 gain、EQ(highpass、lowpass、peaking、shelves)、compressor、limiter、gate、saturate、delay、reverb、chorus、phaser 和 bitcrush。

每个效果的确切 JSON,以及一条通道必须满足的规则:references/attributes.md。 每个效果及其参数、范围和单位:references/fx-registry.md。 如何判断你无法听到的文件哪里出了问题: references/diagnosis.md预设、命名作业和单旋钮配置文件,加上症状到修复的表: references/presets.md — 在手工构建链之前先读那个,因为其中一个预设或命名作业通常已经指出了问题的名称。

它如何组合在一起

两个创作表面会写入这些属性;两个运行时通过相同的构建器读取它们。这个共享的中间层就是预览能够预测渲染的原因。

flowchart TB
  voice["voice track<br/>media file"]
  bed["music bed<br/>media file"]

  subgraph AUTHOR["Authoring — the only things that write attributes"]
    panel["Studio<br/>Voiceover carve control"]
    script["scripts/carve.mjs<br/>detects the pair, dynamic by default"]
    analysis["core/audioCarve.ts<br/>carveProfile · analyseCarveBands<br/>analyseCarveDuck · analyseCarveDynamics"]
    panel --> analysis
    script --> analysis
  end

  voice --> analysis
  bed --> analysis

  subgraph ATTRS["Written onto the bed element"]
    carveAttr["data-fx-carve<br/>source · strength · dynamic"]
    chainAttr["data-fx-chain<br/>peaking xN + gain, tagged fromCarve"]
    autoAttr["data-automation<br/>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<br/>live AudioContext<br/>attachElementFxChain"]
  sched --> preview
  build --> render["Render<br/>OfflineAudioContext in the headless browser<br/>applyAudioFxChain"]
  sched --> render

  preview --> heard["what you hear while scrubbing"]
  render --> wav["processed WAV<br/>+ chainTailSeconds so the mix lets the tail through"]
  wav --> mix["engine · audioMixer<br/>volume lane baked into the PCM here, not in the graph"]
  mix --> out["the rendered mix"]

  edit["editing the attribute mid-playback"] -.->|MutationObserver| preview

雕刻自身的设置永远不会在播放时被读取——它产生的链和通道才是实际播放的内容。data-fx-carve 的存在是为了让强度可以在现有雕刻上修改,而不是从滤波器中反推。

在被雕刻的音乐垫内部,信号先经过下潜,再经过电平匹配,然后才经过你自己构建的任何东西——这就是为什么你添加的限制器仍然作为最后的天花板起作用:

flowchart LR
  src["decoded bed"] --> p1["peaking<br/>400 Hz"]
  p1 --> p2["peaking<br/>1 kHz"]
  p2 --> p3["peaking<br/>1.6 kHz"]
  p3 --> g["gain<br/>level match"]
  g --> hand["your own effects<br/>e.g. limiter"]
  hand --> dest["track gain, then out"]

  l1["lane fx.n1.gain"] -.->|"envelope of the voice's<br/>level in that band"| p1
  l4["lane fx.n4.gain"] -.->|"how far the bed<br/>ducks overall"| g

静态雕刻是同一个图,使用固定值且完全没有通道。

首先,判断哪里出了问题

下面的表从“它听起来浑浊”开始——这假定已经有人听过并说了出来。面对一个文件和“修好它”,你没有这样的句子,也无法听,因此你必须测量。一条规则支配这一切:

单个未知声音的绝对频谱无法诊断。
共振峰相差可达 ±10 dB,基频运行在 85–255 Hz,句子在结束时会下降 5–6 dB。
这些中的每一个单独看都像缺陷,而它们每一个都是说话者本身。

因此比较,并与同一个文件内部的某些东西比较:如果存在干净原始文件就用它,否则用停顿——间隙中可听到的任何内容都是附加的,而间隙的频谱是通道而不是声音。与公开平均频谱或合成对照声音比较不起作用:两个说话者之间的差异大于大多数缺陷,而本指导背后的评估中两个错误答案都正是来自这一点。

当没有原始文件也没有可用静音时,静态音调缺陷确实是不确定的。说明这一点,并提供符合的读数,而不是选择一个并基于它构建链。

命令、陷阱和已完成的配方:references/diagnosis.md。在诊断一个没人描述过的文件之前先读它。

从症状开始

一旦你知道频带和类型,就说出音频哪里出了问题。糟糕的音频大多是以下一两种,而且每种都有已提供的解答:

| 听起来像 | 使用 | | ---------------------------------- | -------------------------------------------------- | | 底层有哼声或闷响 | rumble-cut,或 80 Hz 处的 highpass | | 浑浊、胸腔感 | Tame Boominess job (200 Hz) | | 发闷,像隔着纸板 | Reduce Mud job (250 Hz) | | 词语难以听清 | Add Clarity job (3 kHz),或雕刻音乐垫 | | 刺耳且令人疲劳 | Soften Harshness job (3.2 kHz) | | 某些词语比其他词语响得多 | 压缩器上的 Evenness,或 Even Out Levels | | 句子之间的房间声 | room-gate | | 人声和音乐争抢 | Voiceover carve — 不是对人声或音乐垫做 EQ | | 干燥,没有录制空间 | room-tightroom-natural | | 只是“业余” | voice-clean,它按顺序包含上面四种 |

完整目录、每个预设包含什么、频带词汇,以及故意未覆盖的内容(齿音消除、噪声移除、音调匹配): references/presets.md

先减后加,滤波后再做电平,电平后再处理关系,最后处理特征和天花板。每一步都会改变下一步听到的内容——在高通之前设置压缩器会把时间花在追赶低频轰鸣上。

根据问题选择族,而不是根据名称

Filtershighpasslowpasspeakinglowshelfhighshelf)决定一条轨道被允许占据哪些频率。这是两个源发生冲突时的第一个工具,因为冲突发生在频带中:音乐垫和人声都想要 1–3 kHz,而从音乐垫那里拿走这些频带,比把整体调低对混音造成的损失小得多。对人声使用高通是处理低频轰鸣的标准修复;低通则故意让声音变暗或变闷。

Dynamicsgaincompressorlimitergate)决定一条轨道的电平如何随时间变化。压缩会缩小响亮与安静之间的距离,从而让安静部分能够抬上来。限制器是一个天花板——它不塑造任何东西,它保证没有任何东西越过。门限会移除低于阈值的内容,这就是你如何在句子之间静音房间声。gain 是一个普通电平阶段,当轨道必须让路时,自动化通道骑的就是它。

Nonlinearsaturatebitcrush)改变波形形状,这会添加原本不存在的谐波。当轨道需要特征或粗糙感而不是校正时,使用它——并记住它是生成性的:它会让薄源变得更稠密,而不是更干净。

Timedelayreverbchorusphaser)把轨道放入空间,或给它宽度。这些是最容易毁掉混音的效果,因为尾音或失谐副本会占据人声需要的同一个空间。把它们用在应该坐在其他东西 _后面_ 的东西上,并保持湿声量低于在孤立中听起来正确的量。

链是串行的:每个效果处理前一个效果产生的内容。因此校正性滤波放在早期,特征放在中间,限制器最后,这样它才能真正作为天花板起作用。

旁白雕刻

它解决的问题。 人声下方的音乐垫会让声音难以跟随。本能反应是把整个音乐垫做音量闪避,这有效,但会夺走音乐垫的全部存在感——音乐在整个旁白期间变得无力。但人声不需要整个频谱。它只需要它实际占据的少数几个频带。雕刻只拿走那些频带,而音乐垫保留它的低频和高频,因此它仍然是音乐,同时人声仍然清晰。

它是一种关系,而不是一个效果。 设置位于 _音乐垫_ 上——也就是被处理的轨道——并且它们指定要监听哪些人声,完全就像 sidechain 压缩器一样:你选择会变安静的轨道,并选择是什么让它变安静。绝不把雕刻放在人声轨道上。 人声对自己做雕刻是一个 bug,而不是一个微妙的混音选择。

每一个声音,而不是其中一个。 sources 是一个列表,因为音乐垫通常运行在整段序列下面——一个旁白、一个采访回答、第二个主持人。在任何测量之前,它们会被求和到音乐垫自己的时钟上(mixCarveSources),因此一次分析覆盖所有声音:频带来自所有语音,包络会在任何语音发生的地方上升。当音乐垫播放时从未播放的人声会被排除;它们无法遮蔽它。

针对多个 clip id 的雕刻是错误的。把片段分组,并针对该组进行雕刻。 这是一条不变量,而不是提示。逐个命名片段必须做到完全正确,而且只能正确到下一次编辑为止——后来添加的第四个旁白片段会在雕刻意识之外播放,音乐垫会在它下面无声地失败。相反,命名组会在分析时解析成员,因此后来加入组的片段会被覆盖,而不需要修改 sources

<!-- group the narration, then carve the bed against the group -->
<audio id="vo-intro" data-audio-group="voiceover" …></audio>
<audio id="vo-middle" data-audio-group="voiceover" …></audio>
<audio id="vo-outro" data-audio-group="voiceover" …></audio>

<audio id="music" data-fx-carve='{"enabled":true,"sources":["voiceover"],"strength":0.8}' …></audio>

一个 sources 列表如果命名两个或更多普通 clip id,而不是一个组,会被 audio_carve_ungrouped_sources lint rule 捕获——它仍然能工作,但它是那种在添加片段后会无声腐烂的版本。

保持雕刻组是一个声音组:不要有音乐垫、不要有 SFX、不要有音乐。 sources 中的组 id 会在每一次分析中解析为当前成员,因此你命名的组就是你以后得到的组——不是写入时测量的那些轨道。这种情况有两种咬人方式:

  • 音乐垫在它的源组里。 它把自己作为人声交给自身,并针对自己的内容进行雕刻——“绝不针对自己雕刻轨道”这条规则晚了一次重新分析之后到来。
  • 人声组里有一个 SFX 或音乐片段。 它会在下一次分析进入 sidechain,音乐垫开始在一个 whoosh 下面闪避,尽管写入属性的那次运行从未测量过它。

这两种在雕刻写入的那一刻都是不可见的:分析会求和它检测到的声音,并且不会通过组解析来回往返,因此第一遍确实正确,只有下一遍错误。所以给每个角色自己的组——音乐垫用 music,旁白用 voiceover,打击声用 sfx——并保持 sources 中命名的组只包含声音。

carve.mjs 在看到任一情况时会拒绝写入组形式,记录 clip id,并在 stderr 上说明哪个成员阻止了它。然后 audio_carve_ungrouped_sources 规则会指向该安排,而不是让 CLI 悄悄持久化一个比它测量范围更宽的雕刻。

这次运行遗漏的人声属于这些情况,也不会阻止组形式:carve.mjs 只分析覆盖音乐垫的人声,而无需修改 sources 就拾取稍后播放的片段,正是命名组的全部理由。

一条总线承载多条轨道

仅凭成员身份就足以针对它进行雕刻,如上所述——但添加一个具有该 id 的 <hf-audio-group> 元素后,该组会变成真正的子混音总线:每个成员共用一条链、一个推子、一个自动化时钟。

<hf-audio-group
  id="voiceover"
  data-label="Voiceover"
  data-volume="0.9"
  data-fx-chain='{"version":1,"nodes":[
    {"type":"compressor","id":"g1","params":{"threshold":-18,"ratio":3}},
    {"type":"peaking","id":"g2","params":{"frequency":3000,"gain":2,"q":1}}]}'
></hf-audio-group>

<audio id="vo-intro" data-audio-group="voiceover" …></audio>
<audio id="vo-middle" data-audio-group="voiceover" …></audio>

当同样的处理属于多条轨道时,使用总线。 四个旁白片段各自都想要同一个压缩器,就是四条链要保持同步,而且一旦其中一个被编辑就会漂移;在总线上它是一条链,并且压缩器看到的是完整的人声,而不是孤立地看到每个片段——这才是重点,因为压缩器无法骑行一个只能听到三分之一的序列。逐片段链仍然适用于真正逐片段的东西:一个嘈杂的 take 需要自己的 de-esser。

| 在总线上 | 作用 | | ----------------- | ----------------------------------------- | | data-fx-chain | 一条链覆盖求和后的成员 | | data-automation | 总线上的包络,使用 COMPOSITION time | | data-volume | 每个成员的一个推子(默认 1) | | data-label | 显示名称;回退到 id | | data-hidden | 从混音中移除每个成员 |

组自动化是 composition time,不是 clip time。 总线没有 data-start——成员到达它时已经处于各自的 composition 位置——因此组通道中的 t: 0 是合成的开始,不是任何片段的开始。片段上的通道是 clip-local;同样的数字在两者上表示不同的瞬间,这是把包络从片段移动到其总线时必须做对的一件事。

雕刻保留在片段上。 data-fx-carve 不是组属性。被雕刻的音乐垫是单个轨道,并且是由该轨道携带 data-fx-carve——按照上面的规则指向一个组。组和雕刻在 sources 中相遇,而不是在一个元素上。写在总线上的雕刻是半份效果被应用两次:电平部分测量音乐垫自己的音频,而总线没有这种音频,因此只有滤波器存活——并且总线和其成员是一条信号路径,所以音乐垫随后会经过总线的滤波器以及它自己的滤波器。audio_group_carve_attr lint rule 会捕获它。

一个片段不是总线。 组的存在是为了让多条轨道共用一条链、一个推子和一个时钟。把单个片段包在总线中不会买到该片段自己的 data-fx-chain 尚未做到的任何东西,并且它会让后续编辑需要落地的位置翻倍。唯一值得这样做的理由:总线的自动化时钟是 composition time,因此单成员总线是让该片段上的通道获得 composition-time 计时的方式。

一个旋钮。 strength 是 0..1,并派生一切:切多深、多少频带、多宽、在清晰度与原始人声能量之间偏向多少、电平可以下降多少、在人声下方瞄准多少。这六个在任何真实混音中一起移动——一个轻柔雕刻是少数频带中的浅切,闪避很少,一个硬雕刻是在更多频带中更深、更多——因此它们是同一个关系,只写一次,在 carveProfile 中。carve.mjs 默认 0.8——从 250 Hz 到 2.5 kHz 的六个频带各切约 7 dB,在 1.6 kHz 处切 15 dB,并有 19 dB 的电平空间——因为旁白下面的音乐垫必须先让路,其次才是音乐;0.25(三个频带中的 6 dB 下潜,6 dB 空间)让音乐垫保持存在,但仍然让它与人声争抢,并在实践中被判断为太弱。在 0.5 时下潜达到 10 dB,这是雕刻开始被听成效果而不是听成人声空间的地方。当音乐垫是重点且人声稀疏时降低强度。0 只是频谱——一个频带,完全没有电平匹配。

默认雕刻——只要音乐在人声下播放就必须。 任何声音轨道(旁白、avatar 语音、采访、voiceover)下方的音乐垫作为完成混音的一部分会获得雕刻,而不是作为有时间才做的润色步骤。放置两条轨道,运行下面的命令(默认强度 0.8;当检测选错时添加 --bed / --voice),用 npx hyperframes check 确认写入的 data-fx-carvedata-fx-chaindata-automation,然后才渲染。单独的音量闪避不是完成的混音:它让人声和音乐垫在 1–3 kHz 频带中争抢,并夺走音乐垫在整个旁白期间的全部存在感。只有当没有声音供音乐垫在其下时才跳过雕刻——音乐视频、标题卡、按轨道剪辑的蒙太奇。

它始终跟随人声。 没有静态模式:固定深度会在每个停顿中削弱音乐垫,而一旦你听过两者,就没有理由想要它。每个值都会变成语音自身电平的包络——静音会让音乐垫保持不动,响亮段落会把雕刻推到完整深度——写为普通自动化,这就是为什么通道会出现在时间线上并且之后可以编辑。

电平匹配是其中的一部分。 频谱雕刻无法修复一个只是比人声更响的音乐垫。因此雕刻还会测量音乐垫高于人声多少,并写一个 gain 阶段:对于静态雕刻保持一个值,对于动态雕刻由包络驱动。这个包络故意缓慢释放——当一个词结束时音乐垫立刻弹回满音量,听起来像机器在做。

运行它。 在 Studio 中,雕刻是轨道效果架顶部的一个模块——一个卡片中有人声、强度、动态,以及它产生的分析。只要另一个轨道可能是人声,它就会出现,并且上方恰好有一个候选项的音乐垫会默认、动态地以默认强度雕刻:这就是旁白下面音乐垫想要的,而模块是你更改或关闭它的地方。多个候选项会让选择器等待,而不是猜测。Headless—— 也就是当你是在创作合成而不是编辑一个合成时的路径:

node <SKILL_DIR>/scripts/carve.mjs --comp index.html

这就是整条命令。它会自己找到人声和音乐垫,以默认强度动态雕刻,并打印它的决定:

bed    music-bed (name looks like music)
voice  narration (only track left)
carve  strength 0.8 dynamic
bands  250Hz -7.4dB q2.06, 400Hz -7.4dB q2.06, 630Hz -7.4dB q2.06, 1000Hz -7.4dB q2.06, 1600Hz -14.8dB q2.06, 2500Hz -7.4dB q2.06
level  273-point envelope, floor -19.2 dB

当自动选择错误时,用 --bed / --voice(可重复)命名轨道;用 --strength 推它;用 --dry-run 查看报告而不写入任何东西。

它如何选择轨道。 名称优先,因为那是你已经告诉它的东西,而且答案可解释——core 中的 classifyAudioName,与 Studio 自己的选择器使用的相同分类器,因此两者不会不一致。id 或文件名看起来像音乐(musicbgmbedscore…)的轨道是音乐垫;其他在它上面播放并且不是 SFX 形状的东西是人声。优先选择 audio 元素:video 只有在没有剩下 audio 轨道可以作为人声时,或者合成中的每个 B-roll 片段都会被理解为某人在说话时才计入。当它无法判断哪条轨道是音乐垫时,它会拒绝,而不是雕刻错误的那条——输入一个 id 很便宜。

与面板相同的分析函数,因此结果相同。需要 ffmpeg 在 PATH 上,并在项目中安装 @hyperframes/corenpm i -D @hyperframes/core)——CLI 会内联 core 而不是发布它,因此不能从那里借用。

它写入的内容 是一条普通的 peaking 滤波器链,加上一个 gain 阶段,标记为 fromCarve。这个标记就是整个技巧:重新运行会替换之前的雕刻,并让你手工构建的每个效果——以及你手工绘制的每个通道——留在原来的位置。因此以新强度重新雕刻是安全且可重复的,而 data-fx-carve 的存在是为了可以读回设置,而不是从滤波器中猜测。

自动化

通道是一组断点,位于一个参数上:{t, v} 使用 clip-local 秒数和参数自己的单位。目标对于轨道电平是 volume,或者对于效果旋钮是 fx.<nodeId>.<param>

只有某些参数可以自动化,其他参数上的通道会静默无效。 当有 Web Audio AudioParam 支持时,旋钮才是可自动化的。四个基于 worklet 的效果——compressorlimitergatebitcrush——完全不暴露 AudioParam,因此它们任何参数上的通道永远不会移动:要让压缩器的行为随时间变化,改为在它之前自动化一个 gain 阶段。references/fx-registry.md 标记每个参数。

验证

几乎没有静态门覆盖混音。linter 只读取 data-automation 以处理恰好一个冲突——audio_volume_double_automation,在同时具有 volume GSAP tween 的轨道上有音量通道,其中通道获胜而 tween 被忽略——以及 audio_volume_tween_overrides_gain,在其 volume 被 tween 的轨道上有已编写的 data-volume,其中 tween 的值是绝对的,并替换该 gain 而不是缩放它。没有任何东西验证链或效果通道。强制这些的是渲染:它无法解析的链会失败整个混音,而不是安静地写入干信号,因为一个听起来合理但错误的混音比拒绝更糟。预览在设计上相反:不可读的链会播放干信号,以便合成保持可工作。

指向链没有的节点的通道会在读取时被剪枝,而不是错误——因此拼写错误的 nodeId 会静默地让你失去包络。从链中读回 id,而不是假设铸造了什么。

具有尾音的效果(reverbdelay)会让渲染的轨道比其源更长,并且链会告诉混音多了多少。因此带有混响的音乐垫不再恰好在其 data-duration 结束;这是预期,不是 bug。

除此之外,混音通过渲染和聆听来验证。对于雕刻:人声应该清晰,而音乐垫不应该听起来被掏空,并且在使用 dynamic 时,音乐垫应该在句子之间抬回来,而不是保持平坦。如果音乐垫听起来被陷波而不是仅仅在人声下更安静,强度就太高——这是唯一一个具有明显声音的失败模式。

qianwen skills install @admin/hyperframes-audio