Nodes/SDXL Auto Prompter/APNext H3 Masked Song Latent (song frozen in the audio latent)
ComfyUI Node

APNext H3 Masked Song Latent (song frozen in the audio latent)

The song, frozen in the latent, so the mouth has to match

By dagthomas·Created 3 years ago·Updated about 23 hours ago· 290
APNext H3 Masked Song Latent (song frozen in the audio latent)
  • latent
  • audio_vae
  • master_audio
  • vae
  • source_frames
  • source_latent
  • voice
  • latent
  • trim_frames
  • clip_audio
clip_start_seconds0.000
context_length39
source_fps24.000
cropdisabled
preroll_seconds1.000
lookahead_seconds0.200
audio_denoise0.00
gap_denoise0.15
gate_hold_seconds0.200

If you want H3 to lip-sync - real, structural lip-sync where the mouth hits the words because the model can't do otherwise - the trick is to write the actual song slice into the audio latent and freeze it there, protected from denoising. That's H3MaskedSongLatent, the node that made the pack's masked-audio music videos possible without a third-party pack.

What it is

It's a drop-in replacement for MiniMaxH3SongMaskedAVContext from ComfyUI-H3-Motion-Context-MultiRef - same inputs, same three outputs, same chain-continuation paths - so the pack's Chain Render and Scene Retake pick it first and fall back to the MultiRef node when it's missing. It writes this clip's exact song slice into the H3 audio latent using the audio VAE, then protects the audio rows with a nested noise mask: video generated, audio preserved. Nothing is patched; it's all ComfyUI core (comfy.model_base.MiniMaxH3 turning the mask into per-row timesteps).

The part people get wrong: the encoder needs context

The reason this node exists, rather than just "write the slice in and go", is that the H3 audio VAE's encoder is a same-padded conv stack under a causal attention block. Cut the slice hard at the clip start and the first tokens have a zero-padded past and nothing to attend to - the pack measured token 0 ~57% off, the error taking half a second to decay. So:

  • preroll_seconds (default 1.0) - encodes that much song before the clip start and drops it, giving the first tokens a real past. 1 s brings the head to ~1% error; 2 s hits the floor. Set it to 0 to reproduce the old hard-cut behaviour for an A/B.
  • lookahead_seconds (default 0.2) - the convs look ahead ~200 ms, so the tail of a hard-cut slice is off too. This encodes real song after the cut (silence once the song has ended) and trims it back.

The node handles the 25 ms tick alignment internally - leave the defaults alone unless you're experimenting.

The inputs that matter

  • latent - the target AV latent from MiniMax H3 Reference to Video / Image to Video.
  • audio_vae - the MiniMax H3 audio VAE, which encodes the song slice.
  • master_audio - the whole song (or the conditioning mix from H3 Voice Over Music). Only the slice this clip covers is written in; the rest is ignored.
  • clip_start_seconds - where this clip starts in the song. For a continuing clip this already includes the pinned prefix (Chain Render handles that).
  • context_length - visual prefix copied from the previous clip: 5 / 22 / 39 / 56… are H3's valid runs; 0 = independent clip.

Optional but genuinely useful: voice - the vocal stem - switches on the voice gate. H3 has one audio latent and it's a mix, so the voice can't go to the lips and the music to the motion separately - but the freeze can follow the voice in time: wherever the stem is sounding, the audio rows hold at audio_denoise (0 = fully frozen, so the lips get the exact vocal); between phrases they relax to gap_denoise (0.15), letting the model re-touch the music bed where no lip-sync is at stake. gate_hold_seconds (0.2) widens the frozen region around each phrase; a small audio_denoise (0.05–0.15) is the A/B knob for letting the model re-touch the audio while the vocal stays.

For chaining, source_latent (previous sampled latent, copied straight - no decode/re-encode) is preferred; source_frames plus vae is the decoded-frames route.

Outputs

latent goes into the sampler, trim_frames is the count to trim, and clip_audio is the song slice for this clip. That's the whole contract - drop-in compatible with the node it replaces.

Installing

Part of the dagthomas pack - ComfyUI Manager, search "comfyui_dagthomas", install, restart:

cd ComfyUI/custom_nodes
git clone https://github.com/dagthomas/comfyui_dagthomas
cd comfyui_dagthomas
pip install -r requirements.txt

The heavy lifting is external: you need the H3 model weights and the H3 audio VAE for audio_vae. There's no extra Python dependency in the pack for this node. Old builds of the pack changed the working directory on load and broke ComfyUI's Restart button; long since fixed, but it's why stale forum posts about it linger.

One honest note: this is the most technical node in the audio suite, and Chain Render drives it automatically - you rarely touch the internals unless you're A/B-ing the preroll or tuning the voice gate. The defaults are good, and the tooltips tell you what each knob measured.

Categorycomfyui_dagthomas

Inputs (16)

NameTypeDefaultDescription
latentLATENTTarget AV latent from MiniMax H3 Reference to Video / Image to Video.
audio_vaeVAEThe MiniMax H3 audio VAE - encodes the song slice into the audio latent.
master_audioAUDIOThe whole song (or the conditioning mix from H3 Voice Over Music). The slice this clip covers is written into the latent and protected from denoising; the rest is ignored.
clip_start_secondsFLOAT0.0000–99999Where this clip starts in the song (the writer's `clip_starts`). For a continuing clip this already includes the pinned prefix (Chain Render does that).
context_lengthINT390–9999Visual prefix copied from the previous clip (source_latent / source_frames): exact H3 runs 5 / 22 / 39 / 56 ...; 0 = no prefix (independent clip).
source_fpsFLOAT24.0001–240FPS of source_frames (the decoded-frames continuation path).
cropCOMBOdisabled2 options: disabled, center
vaeoptVAEThe H3 video VAE - only for the source_frames path.
source_framesoptIMAGEDecoded frames of the previous clip; its tail is encoded as the prefix.
source_latentoptLATENTThe previous clip's sampled AV latent; its tail is copied straight into the new latent (no decode / re-encode). Preferred for chaining.
preroll_secondsoptFLOAT1.0000–4Song audio encoded BEFORE the clip start and then dropped, so the first tokens have a past (the encoder's attention is causal). Measured: 0 s leaves token 0 ~57 % off and the first 0.5 s wrong; 1 s brings the head to ~1 %; 2 s is the floor. 0 = the old hard-cut behaviour, for A/B.
lookahead_secondsoptFLOAT0.2000–1Song audio encoded AFTER the clip end and dropped, so the last tokens see what follows (the encoder's convs look ahead ~200 ms). Silence once the song has ended.
audio_denoiseoptFLOAT0.000–1Noise-mask value of the audio rows. 0 = the song slice is fully frozen (default). A small value (0.05-0.15) lets the model re-touch the audio at that noise level while the vocal stays - an A/B knob; the output still gets the real song. With `voice` connected this is the value while the voice is SINGING; the gaps get `gap_denoise`.
voiceoptAUDIOThe vocal stem (AudioSeparation's vocals) - the VOICE GATE. Wherever the voice is sounding the audio rows are held at `audio_denoise` (0 = frozen, so the lips have the exact vocal); between phrases they are held at `gap_denoise` instead, so the model has a little freedom over the music bed where no lip-sync is at stake. Leave unconnected for one value everywhere.
gap_denoiseoptFLOAT0.150–1Noise-mask value of the audio rows BETWEEN sung phrases (voice connected). 0 = frozen there too; 0.1-0.25 lets the model re-touch the music bed in the gaps.
gate_hold_secondsoptFLOAT0.2000–1How far the frozen region extends before and after every sung stretch (whole 25 ms ticks) - room for the mouth opening ahead of the word and closing after it.

Outputs (3)

NameTypeDescription
latentLATENT
trim_framesINT
clip_audioAUDIO