Muse Voice Swap V1
Re-voice a generated video without murdering the ambience
- source_audio
- reference_audio
- reference_audio_2
- reference_audio_3
- final_audio
- new_vocals_only
- bg_audio_only
- speaker_2_vocals_only
- speaker_3_vocals_only
- timeline_data
Video models like LTX don't make sound, and the audio you bolt on afterward usually has one weak link: the voice. The ambience is fine, but the dialogue is whatever generic synthetic voice the audio model hallucinated - and it's the first thing a viewer notices.
Muse Voice Swap V1 exists to fix exactly that. Feed it your mixed audio and a 5–30 second reference of the voice you actually want, and it re-clones every spoken line in that voice, then lays the new vocals back over the untouched ambience. Same clip, same room tone, different person talking. Fully local, no API key, no quota.
How it works
Under the hood it's an eight-step pipeline, and your settings map onto the steps:
- Separate - Demucs splits vocals from the bass/drums/other bed.
- Detect - a VAD finds speech segments in the vocal track.
- Transcribe - Whisper writes each segment's text.
- Tag (optional) - a local Gemma model listens to each segment's actual audio and inserts Fish S2-style emotion tags like
[excited]or[whisper]. - Assign (optional) - ECAPA-TDNN compares each segment against your reference voices to guess who's speaking.
- Clone - Fish Audio S2 re-speaks each line in the target voice.
- Time-match - the clone gets placed back at the right moment.
- Remix - the new vocals go over the untouched background, plus isolated stems.
The notable thing is step 0: this node doesn't reimplement any of it - it bridges into Christian Byrne's audio-separation-nodes-comfyui and Saganaki22's ComfyUI-FishAudioS2 at runtime and calls their classes. That means those two packs must be installed first - otherwise you get a "is ... installed and loaded?" error instead of a swap.
The timeline widget is the real differentiator
Most voice-swap tools are fire-and-forget. This one embeds a Muse-Director-style timeline widget right in the node: drag boundaries, fix transcript text, assign a speaker, tweak or clear the emotion tag, lock a segment, even drop a per-segment voice override (a filename in ComfyUI/input). Then flip use_timeline_override on and re-run - it skips re-detection and re-transcription and only re-clones the parts you changed. That edit-then-regen loop is what makes it usable on real clips instead of demos.
Inputs that matter
source_audioandreference_audioare the only two you must wire. The reference should be clean, just the person talking.multi_speaker_mode(withreference_audio_2/_3) turns it into a 3-speaker dubbing rig and adds thespeaker_2_vocals_only/speaker_3_vocals_onlyoutputs. Addauto_assign_speakersand it guesses who says what - but treat the guess as a draft; short segments score low confidence regardless.use_llm_emotion_tagsis the toggle behind the emotion-tag step - the fix for flat, monotone clones - and needs a Gemma checkpoint inmodels/text_encoders/(the README points atgemma4_e4b_it_fp8_scaled.safetensors).fish_model_path- set it tos2-pro-fp8 (auto download)and the model fetches itself on first run.time_match_mode(ripple/stretch_to_fit/off) andbg_extend_mode(hold_last/loop/trim) decide how the clone lands back in time and what happens when the background runs short. Defaults are fine to start.vad_methoddefaults to Silero, the good one;energy_thresholdis the older librosa path, kept as a fallback. The README's "energy-based VAD" line is stale - the shipped default is Silero.
Outputs
final_audio is your mix. new_vocals_only and bg_audio_only are the stems - handy if you want to rebalance or re-process them. And timeline_data should be wired back into the node's own timeline_data input so your manual edits persist across runs.
Install
Via ComfyUI Manager, search "Muse Collective Voice Swap" (or install by Git URL), or manually:
cd ComfyUI/custom_nodes
git clone https://github.com/muse-collective-26/MuseVoiceSwap.git
Then restart. The pack's own requirements (librosa, openai-whisper, soundfile, speechbrain, hyperpyyaml) install through Manager. Before you can run anything you also need, from Manager:
ComfyUI-FishAudioS2- the voice cloneraudio-separation-nodes-comfyui- Demucs separation and the mixingComfyUI-VideoHelperSuite- to load the audio/video in the first place- A Gemma checkpoint in
models/text_encoders/if you want emotion tags
First run is slow and that's not a bug: Demucs (hdemucs_high_trained.pt), the Whisper model, the ECAPA-TDNN speaker encoder, and the Fish S2 pro model all auto-download on first use.
Gotchas
- Missing pack = immediate error. The bridge resolves classes at runtime, so install order matters. "AudioSeparation not found" or "FishS2VoiceCloneTTS not found" is the dependency, not the node.
- Duration drift. Fish S2 has no native duration control, so in
ripplemode (the default) the final length can drift from the source over many segments. It's logged to the console, not hidden.stretch_to_fittrades that for possible time-stretch artifacts. - Expect to hand-edit segments. No VAD nails boundaries on every clip - that's what the timeline is for. Plan a first pass that's just detection, correct, then re-run with the override on.
- Demucs on foley. It's trained on music stems; its separation of non-musical ambience is "unproven beyond ear-testing" per the author. On weird sources, check
bg_audio_onlybefore you trust the mix.
It's a lot of moving parts for one node and the prerequisite stack is real. But the payoff - a flat generated voice turned into a re-voiced mix with the room intact - is something nothing else in the ComfyUI audio corner does in a single box.
Inputs (45)
| Name | Type | Default | Description |
|---|---|---|---|
| source_audio | AUDIO | The LTX-generated mixed audio (speech + ambient). | |
| reference_audio | AUDIO | Voice-clone target reference, 5-30s. | |
| timeline_data | STRING | {} | — |
| use_timeline_override | BOOLEAN | false | ON = keep existing segment boundaries/text from timeline_data; skip auto-detect and auto-transcribe, only re-clone + remix. |
| multi_speaker_mode | BOOLEAN | false | ON = assign each segment to speaker 1/2/3 (set per-segment in the timeline panel) and clone it using that speaker's reference voice (reference_audio / reference_audio_2 / reference_audio_3). Also produces isolated audio tracks for speakers 2 and 3. |
| auto_assign_speakers | BOOLEAN | false | ON (with multi_speaker_mode) = auto-guess each segment's speaker by comparing its voice against reference_audio/_2/_3 using a speaker-embedding similarity match. This is a best-guess classifier, not a guarantee — review/correct assignments in the timeline afterward, same as you would Whisper's transcript. Only runs on a fresh (non-override) pass — once use_timeline_override is on, your manual corrections are trusted as-is and this is skipped entirely, so you don't need to lock a segment just to protect a manual speaker fix. |
| sep_chunk_length | FLOAT | 10.02–60 | — |
| sep_chunk_overlap | FLOAT | 0.100–2 | — |
| sep_chunk_fade_shape | COMBO | linear | 4 options: linear, half_sine, logarithmic, exponential |
| vad_top_db | FLOAT | 355–60 | — |
| min_segment_seconds | FLOAT | 0.300.05–5 | — |
| min_gap_seconds | FLOAT | 0.200.02–5 | — |
| whisper_model_size | COMBO | small | 5 options: tiny, base, small, medium, large-v3 |
| whisper_language | COMBO | auto | 12 options: auto, en, zh, ja, ko, es, +6 |
| use_llm_emotion_tags | BOOLEAN | false | ON = feed each segment's actual audio to a local Gemma model and ask it to insert Fish S2-style emotion/prosody tags ([excited], [whisper], [pause], etc.) based on the real delivery, before cloning. Fixes flat/ monotone clones. Fully local — no API key, no quota/cost. |
| gemma_clip_name | COMBO | (no text_encoders found) | Gemma checkpoint from ComfyUI/models/text_encoders/, loaded via the same CLIPLoader + TextGenerate nodes ComfyUI uses natively. e.g. gemma4_e4b_it_fp8_scaled.safetensors |
| emotion_tag_temperature | FLOAT | 0.700–2 | — |
| fish_model_path | COMBO | 1 options: (FishAudioS2 not loaded) | |
| fish_language | COMBO | auto | 44 options: auto, en, zh, ja, ko, es, +38 |
| fish_device | COMBO | auto | 4 options: auto, cuda, cpu, mps |
| fish_precision | COMBO | auto | 4 options: auto, bfloat16, float16, float32 |
| fish_attention | COMBO | auto | 4 options: auto, sdpa, sage_attention, flash_attention |
| fish_max_new_tokens | INT | 00–4096 | — |
| fish_chunk_length | INT | 200100–400 | — |
| fish_temperature | FLOAT | 0.800.1–1 | — |
| fish_top_p | FLOAT | 0.800.1–1 | — |
| fish_repetition_penalty | FLOAT | 1.100.9–2 | — |
| fish_seed | INT | 00–2147483647 | — |
| fish_keep_model_loaded | BOOLEAN | true | — |
| time_match_mode | COMBO | ripple | 3 options: ripple, stretch_to_fit, off |
| stretch_fft_size | INT | 2048512–8192 | — |
| bg_extend_mode | COMBO | hold_last | 3 options: hold_last, loop, trim |
| bg_volume | FLOAT | 1.000–2 | — |
| vad_method | COMBO | silero | silero (recommended) = neural voice-activity model, correctly tells quiet-but-real speech apart from actual silence. energy_threshold = the original librosa.effects.split method (relative dB threshold) — kept as an instant revert path if silero doesn't suit some source material. min_segment_seconds/min_gap_seconds above apply to either; vad_top_db only applies to energy_threshold. |
| silero_threshold | FLOAT | 0.500.1–0.9 | Only applies when vad_method is silero. Speech-probability cutoff Silero requires before calling a chunk of audio speech at all -- LOWER this if it's missing quiet/soft-spoken talking (more sensitive, but risks catching more non-speech noise too). Separate from min_segment_seconds/min_gap_seconds, which only filter/merge what Silero already decided was speech. |
| silero_speech_pad_ms | INT | 300–500 | Only applies when vad_method is silero. Padding (ms) added to both ends of each detected speech chunk -- raise this if word onsets/tails right at a segment boundary are getting clipped/missed. |
| timeline_ui | STRING | — | |
| min_confidence_margin | FLOAT | 0.100–0.9 | Only applies with multi_speaker_mode + auto_assign_speakers. How much the top speaker match must beat the runner-up (cosine similarity) before a segment's speaker gets auto-assigned. LOWER = more segments get auto-assigned (more risk of a wrong guess); HIGHER = fewer, more confident assignments (more segments left unchanged for manual review). 0.1 was previously hardcoded; short (<1s) segments tend to score low confidence regardless of this value — that's an ECAPA-TDNN limitation on short clips, not something this setting fixes. |
| speaker_aware_merge | BOOLEAN | false | ON = before merging two speech intervals separated by a gap shorter than min_gap_seconds, check whether they sound like the same voice first. Only merges when they do — a brief interjection from someone else (an 'ouch!', 'help!', a quick reaction) wedged into a small gap stays as its own segment instead of being silently absorbed into the surrounding speaker's segment, which is what happens with this OFF (the original behavior). Compares the two intervals directly against each other, not against any reference voice — works independently of multi_speaker_mode/auto_assign_speakers, and is useful even with a single known speaker (keeps an unrelated voice from merging into their segment). Falls back to always-merge whenever the check itself can't be computed. |
| merge_similarity_threshold | FLOAT | 0.300–0.9 | Only applies with speaker_aware_merge ON. How similar two adjacent intervals' voices must be (cosine similarity) to still merge across a short gap. LOWER = merges more readily (risks absorbing a real interjection); HIGHER = splits more readily (risks splitting one person's own natural pause into two segments). |
| reference_audio_2opt | AUDIO | Reference voice for segments assigned speaker 2 (multi_speaker_mode). | |
| reference_audio_3opt | AUDIO | Reference voice for segments assigned speaker 3 (multi_speaker_mode). | |
| reference_textopt | STRING | Transcript of reference_audio. Leave disconnected to auto-transcribe it internally via Whisper — no need to wire anything in for the normal case. | |
| reference_text_2opt | STRING | — | |
| reference_text_3opt | STRING | — |
Outputs (6)
| Name | Type | Description |
|---|---|---|
| final_audio | AUDIO | — |
| new_vocals_only | AUDIO | — |
| bg_audio_only | AUDIO | — |
| speaker_2_vocals_only | AUDIO | — |
| speaker_3_vocals_only | AUDIO | — |
| timeline_data | STRING | — |