🔗 H3 Relay · Audio Seam
Your H3 seams thump because the head is silence — patch it, don't re-encode
- audio
- audio
- report
- joined
You stitched three H3 segments together and every join does the same thing: a tiny dip, then the ambience kicks in late. It's the single most common complaint about multi-segment generation, and it isn't the model's fault. Every decoded segment starts with about 32 ms of near-silence - the decoder's priming - plus a generation transient. Trim AV then cuts the visual overlap onto that exact spot, so the join lands on nothing, then snaps to full level. About 20 ms of −12 to −13 dB hole, in a neighbourhood that was sitting at −72 dB.
H3RelayAudioSeam fixes that in the audio domain. It swaps the head of the current segment for ambient sound taken from an already-rendered segment, then crossfades back into the current segment's own audio. Length-preserving: not a single sample added or removed, so your A/V sync is untouched. That constraint is the whole design. A real crossfade can't do it - it has to eat the tail of the previous segment, and this node only touches its own input. So it patches, and the assemble layer stays a plain butt-join.
There's a second job in here that surprises people: the node also concatenates audio from segment 0 up to the current one and hands it out of the joined output - the same code path as the pack's 🧩 concat button, so no external ffmpeg pass is needed for one continuous track.
How the patch actually works
On each run it resolves the bed source from output/relay_kit/<run_id>/ - the audio sidecar every segment writes - takes a window equal in length to the patch, level-aligns it to the pre-seam audio (capped at ±6 dB), and fades back across fade_seconds. bed_select defaults to tail, so the window sits just before the seam and its level and timbre are naturally continuous with what came before. The old 0.5.0 behaviour (bed_select: quiet, take the globally quietest window) measured −42 dBFS against a −12.8 dBFS pre-seam level: it turned the seam dip into a silence hole and flattened the downbeat. It's kept only as a control.
Then an isolated-transient suppressor runs over the result - declick_ratio (4), declick_quiet_dbfs (−50), declick_max_len_ms (50). It kills one-off "tick" spikes over a dead-quiet background, and keys off how quiet the surroundings are, not how loud the spike is. That's why it doesn't shave the p/t/k plosives off your dialogue: those sit on a −35 dBFS floor, well above the gate.
The inputs worth setting
Required: audio (from Trim AV's audio output; on segment 1 there's no Trim AV, so straight from VAEDecodeAudio), run_id, stage_index.
Then two that matter:
patch_seconds- how much of the head to replace.0is off and bit-exact passthrough. The tooltip suggests 2.0; the pack's own discipline table says keep it ≤ 1.2, and that's the one to follow. The patch replaces content wholesale, so dialogue sitting at the head gets eaten. The safeguard ispatch_guard(on), which finds where your speech starts and shrinks the patch to 0.40 s before it - but it works off the ratio between speech energy and the surrounding median, so on material that's wall-to-wall voice or music the threshold floats above the speech peak and it never fires. Shortening the patch is the fix; the guard is backup.patch_guard- leave it on unless you're deliberately reproducing old behaviour.
fade_seconds (0.25) is the crossfade width at the patch boundary; bed_stage (0) picks which rendered segment supplies the bed - must be lower than stage_index; cut_head_frames should be wired to the bridge's context_frames so the suppressor judges the cropped-out head as a fresh file start. Everything under join_*, plus tile_seconds and note, is advanced and safe to ignore.
Outputs: audio (wire this to CreateVideo.audio, or to your save node), report, and joined - the running concatenated track.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/ZenHG/ComfyUI-H3-Latent-Relay.git
# then restart the ComfyUI backend, which means the Python process, not the browser tab
Or ComfyUI Manager → Custom Nodes Manager → search h3-latent-relay (the registry id drops the "ComfyUI" prefix). No model downloads, no extra pip work for this node - the pack only needs torch/safetensors from your ComfyUI install and, for concatenation, av>=17, which ComfyUI already ships.
Where people get burned
Stage 1 (segment 2) needs segment 1 to have run first - the bed file is written by this node on every run, including the first segment, so run in order and don't skip stages. A "bed source missing" error means exactly that.
join_cross_ms set without join_align_seconds raises on purpose. Crossfading is overlap-add, which shortens the timeline; the node refuses rather than quietly emitting a track that drifts ahead of the picture a few ms per seam.
Last thing: this is a young pack from a solo author (ZenHG) with essentially no Reddit footprint yet, so there won't be a pile of tutorials. Read the tooltips - they're unusually honest, including where the author documents a measurement that killed his own earlier default.
Inputs (22)
| Name | Type | Default | Description |
|---|---|---|---|
| audio | AUDIO | 【接法】从 Trim AV 的 audio 输出接过来。 第 1 段没有 Trim AV 时,直接接音频解码。 | |
| run_id | STRING | relay | 【填什么】片名,与 Latent Save / 桥一字不差。 它靠这个找上一段存下来的音频,当环境声来源。 |
| stage_index | INT | 00–9999 | 【填什么】本段段号(第 1 段 = 0)。 第 1 段无缝可补,会直接直通(音频仍会保存,供第 2 段当环境声来源)。 |
| patch_secondsopt | FLOAT | 1.200–4 | 【头部补丁】把段首这么多秒的内容整段换成上一段的环境声。 默认 1.2:段与段之间的环境声、底噪不会每段重启一次。 · 调大覆盖更多开头(超过 1.2 有吞掉开头台词的风险);调小更保守。 ⚠ 它会替换这 N 秒的全部内容,所以段首本来就不该有台词。0 = 关。 |
| tile_secondsopt | FLOAT | 0.000–4 | 【环境声瓦片秒数】把环境声切成这么长的瓦片,交叠淡化后铺满补丁。 0(默认)= 从整窗直接取最静的一段。上一段干净环境声比补丁短时用它,建议 1.2。 |
| fade_secondsopt | FLOAT | 0.250–0.5 | 【补丁边界】补丁末端与后面的内容之间做多宽的交叉淡变。 0 = 硬切(会有可闻的接点);0.25 是实测可用的值。 |
| bed_stageopt | INT | 00–9999 | 【环境声来源】用第几段的音频(0 = 第 1 段)。 同一场景环境声变化不大,取第 1 段最稳。⚠ 必须小于本段段号。 |
| noteopt | STRING | 【可留空】备注,存进文件的元数据里方便事后分辨版本。 | |
| bed_selectopt | COMBO | tail | 【环境声从哪取】 · tail(默认)= 从环境声来源的尾部取与补丁等长的一段:紧挨接缝,电平和音色自然接上。 · quiet = 取全局最静的一段。实测会把补丁换得更静,接缝从凹陷变成静音洞 ⇒ 仅供对照。 |
| join_curveopt | COMBO | qsin | 【拼接曲线】缝处交叉淡变的曲线。 · qsin(默认,等功率)= 两段内容不相关时不掉电平。 · tri(线性)= 实测中缝掉 4.7 dB,听感是音量先小再恢复 ⇒ 仅供对照。 |
| join_prime_msopt | FLOAT | 330–200 | 【拼接用】每段开头要丢掉的编码器预热(毫秒)。 默认 33 毫秒(AAC 实测值)。它不是内容,丢掉只是对齐;0 = 不丢。 |
| join_cross_msopt | FLOAT | 00–2000 | 【拼接用】缝处交叉淡变长度(毫秒)。默认 0 = 不做交叉。 交叉淡变必然缩短时间轴 ⇒ 后段音频比画面提前,且逐段累积。 所以只有同时填了 join_align_seconds 时才有意义。 ⚠ 只填这一格会直接报错,不会静默拼出一条错位音轨。 |
| join_segment_secondsopt | FLOAT | 0.0000–60 | 【拼接用】每段音频的有效时长(秒)= 该段帧数 ÷ 帧率。 节点 PCM 通常比 mp4 长,不截断拼接就会超长。 0 = 不截。例如 90 帧 / 24 = 3.75。 |
| join_align_secondsopt | FLOAT | 0.0000–10 | 【拼接用】每缝的画面裁量(秒)= 裁掉的帧数 ÷ 帧率。 大于 0 = 时间轴守恒:输出与裁后画面严格等长,缝上没有电平凹陷。 0 = 旧的缩短语义(仅对照)。要求不小于 join_cross_ms。 |
| patch_guardopt | BOOLEAN | true | 【台词守卫】开(默认)= 补丁前先探测本段开头有没有台词。 有 ⇒ 补丁收缩到台词前 0.40 秒;台词太靠前 ⇒ 整个关掉补丁。 关 = 旧行为,补丁可能把段首台词一起换掉。 |
| patch_guard_layersopt | INT | 00–3 | 【台词守卫档位】0(默认)= 只用能量判断。 1 = 加能量阶跃;2 = 加谱质心;3 = 加谱平坦度(后三档都降低灵敏度)。 ⚠ 1~3 档与守卫「宁可误判也不漏」的意图相反,且标定样本少 ⇒ 默认关。 ⚠ 别把台词安全寄托在这一格:主防线是 patch_seconds 不超过 1.2。 |
| declick_ratioopt | FLOAT | 4.00–50 | 【去咔判断】局部峰值 ÷ 背景底噪。0 = 关;建议 4.0。 做什么:掐掉「背景极静处冒出来的一声短脉冲」。 ⚠ 看的是它周围有多静,不是它本身多响 —— 只看响度会连台词辅音一起掐。 ⚠ 只动那一处(两端各 2 毫秒淡变),其余内容逐位不动,长度不变。 |
| declick_quiet_dbfsopt | FLOAT | -50-80–-20 | 【孤立瞬态抑制】背景必须低于这个电平(dBFS)才动手。默认 −50。 实测:孤立瞬态处背景 −56 ⇒ 命中;语音辅音处约 −35 ⇒ 不命中(所以不误伤台词)。 ⚠ 调高更激进、可能碰到轻辅音;调低更保守。 |
| declick_max_len_msopt | FLOAT | 501–200 | 【孤立瞬态抑制】多宽算「一声」(毫秒)。默认 50。 · 10 = 只治 10 毫秒以内的极短脉冲(实测孤立瞬态宽 8 毫秒,仍治得到)。 · 50(默认)= 连 15~30 毫秒的「嗒 / 啪」也治,代价是可能削到真实的短促音效。 · 100 以上 = 激进,不建议。 |
| cut_head_framesopt | INT | 00–9999 | 【孤立瞬态抑制】本段会被裁掉多少帧(= 桥的 context_frames)。 为什么需要:这个节点工作在未裁音频上,而产物是裁后的 ⇒ 缝处那个孤立峰在这里「不孤立」⇒ 不命中,但在成片里它前面什么都没有。 填了它,抑制器就把该处当「新文件头」来判。 ⚠ 0 = 不启用该修正;一般由脚本自动填。 |
| loudness_normalizeopt | BOOLEAN | true | 【响度均衡】开(默认)= 把本段人声响度对齐到统一目标。 为什么默认开:模型跟随声音参考的响度 —— 接参考时参考已归一,不接参考时没人管。 每段电平于是由模型自己决定(实测三段差 7.97 dB)。关 = 恢复旧行为。 |
| loudness_target_dbfsopt | FLOAT | -23.0-60–0 | 【响度均衡】目标电平(有声窗 RMS 中位,dBFS)。默认 −23.0。 ⚠ 不是整段 RMS、也不是峰值 —— 静音与环境声不计入,所以描述的是人声的响度。 |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| audio | AUDIO | — |
| report | STRING | — |
| joined | AUDIO | — |