H3 AudioFirst Lock Audio
Weld the Finished Voice Onto the Video Latent, Then Sample Past It
- video_latent
- audio_latent
- LATENT
This is the node that makes the whole "audio-first" trick real, and it's about forty lines of Python. In a normal MiniMax H3 run the audio and the picture denoise together - the sampler treats them as one nested latent and spends the same four steps on both. That's how you get a lip-synced line of dialogue out of a single model in the first place, and it's also why the voice sounds compressed and ringy at turbo step counts: the audio never gets enough refinement.
H3AudioFirstLockAudio breaks that coupling at the latent level. You hand it an empty video-pass latent and an already finished audio latent, and it returns one latent where the audio is marked as something the sampler must not touch.
How it pulls that off
Two mechanisms, both visible in the source.
First, it builds a new nested latent out of both halves: your video tensor and the finished audio tensor, cloned onto the empty audio tensor's device and dtype. Second, and this is the actual trick, it attaches a noise_mask to the result - torch.ones(...) for the video half, torch.zeros(...) for the audio half. A zero in ComfyUI's denoise mask means "this region is already what it should be," so the sigma schedule has no effect on it. The video pass denoises the picture across four steps and the audio comes out the other side unchanged, ready for H3AudioFirstCheckLock to verify.
That's the clean version of a hack people usually perform with a second sampler and a denoise value, and it's the reason the pack's timing numbers look the way they do: adding the audio pass costs about 11 seconds on a 14-second clip, and the video pass is untouched, so picture quality is identical to the old workflow. The author is explicit that the video pass is the FL2VA configuration from Sayaka Benchmark, used as-is, not a retune.
The two inputs, and the one wire everyone gets wrong
There are exactly two inputs and one output.
video_latent - the empty AV latent from MiniMaxH3ImageToVideo's latent output. Not a sampled latent. It's the container the picture will be generated into.
audio_latent - the output of the audio-pass sampler, i.e. the finished voice. In the examples that's the SamplerCustomAdvanced fed by H3AudioFirstConditioning, the same output that also goes to VAEDecodeAudio.
The output is a single LATENT, and it goes into the video pass's SamplerCustomAdvanced latent slot.
The trap: both inputs are typed LATENT, so ComfyUI will happily let you wire the empty audio latent from H3AudioFirstConditioning into audio_latent, or a sampled latent into video_latent. Nothing complains at validation time. You get a run that finishes and then sounds wrong - an unnoised audio latent makes a beautiful video with static on it - or a shape error. Look at your wires: one side of this node is a loader-side empty latent, the other is always a sampler output.
There is one loud failure it does give you:
Audio shape (...) does not match the video pass (...);
use the same frame count in both passes
That's the length mismatch. Set length identically on H3AudioFirstConditioning and MiniMaxH3ImageToVideo - 17k+5, so 124 / 243 / 345 for 5 / 10 / 14 seconds.
The fast variant locks twice
Worth knowing before you drag open an LBH example and get confused by the graph. In the standard workflow this node appears once. In fl2va_audiofirst_lbh_*, it appears twice, because LBH does 3 steps at 384×672 and then upscales for 1 step at 544×960, and the 3D latent upscaler doesn't know what to do with the audio half. So the example separates the AV latent (LTXVSeparateAVLatent), hands only the video through H3LBHVideoHandoff, re-concatenates with LTXVConcatAVLatent, and locks the audio back on with a second copy of this node before the one-step refine. The one-step refine uses DisableNoise rather than a fresh noise input.
If you're building it by hand and your audio sounds like it got run through the upscaler, you skipped the second lock.
Install, once, for all three nodes
Manager → search H3 AudioFirst X2 Stream; you also want H3 X2 Stream for the save node, and H3 LBH X2 Stream if you're going fast. Or:
cd ComfyUI/custom_nodes
git clone https://github.com/sepiablue-ai/ComfyUI-H3-AudioFirst-X2-Stream
Restart. No pip requirements in this pack - it's pure ComfyUI plumbing plus native MiniMax H3 (MiniMaxH3ImageToVideo, MiniMaxH3SigmaShift, BlockSparseAttention), so you need ComfyUI 0.38.0+. The models are a manual download: INT8 turbo checkpoint, Qwen3-VL encoder, INT8 video VAE, fp32 audio VAE, X2 Detail VAE. Prepare the X2 decoder once with 00_prepare_x2.json. Pick Manager or setup.bat, never both - the directory names differ and you'll register duplicate nodes.
Next stop is H3AudioFirstCheckLock. Don't skip it just because it looks optional; it's the only thing that tells you whether the lock actually held.
Inputs (2)
| Name | Type | Default | Description |
|---|---|---|---|
| video_latent | LATENT | — | |
| audio_latent | LATENT | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| LATENT | LATENT | — |