Song Generator
This app uses Ace-Step 1.5XL to generate a full song and allows you to choose the genre and write the lyrics, either automatically or manually.
About
Song Generator is a ComfyUI workflow that synthesizes complete songs with the Ace-Step 1.5XL audio model. It supports two lyric paths: automatic, where a short topic is expanded into BPM- and duration-aware lyrics, and manual, where you paste your own text. A ComfySwitchNode routes between these paths before the lyrics are encoded by TextEncodeAceStepAudio1.5 and used to guide the audio model. You can preview the result with PreviewAny and export with SaveAudioAdvanced.
Under the hood, UNETLoader and VAELoader load the Ace-Step 1.5XL checkpoints. EmptyAceStep1.5LatentAudio allocates a latent audio canvas sized to your target duration. ModelSamplingAuraFlow configures the sampler for the model, and KSampler performs the iterative denoising on that latent using the conditioning produced by TextEncodeAceStepAudio1.5. ConditioningZeroOut is included to safely zero optional negative conditioning when not used. After sampling, VAEDecodeAudio converts the latent to a waveform for monitoring and export.
The BPM/duration-aware lyric module uses PrimitiveInt/PrimitiveFloat to capture BPM and target length, StringReplace to inject those values into a songwriter instruction, and TextGenerate to create structured verses/choruses sized for the track. StringCompare helps detect empty input, and the ComfySwitchNode cleanly selects between the auto-lyrics and a PrimitiveStringMultiline node for manual lyrics. DualCLIPLoader/CLIPLoader are available for workflows that optionally mix in CLIP-style text conditioning, though the primary path uses TextEncodeAceStepAudio1.5 for Ace-Step.
What you provide
- Reading the workflow
Runs in ComfyUI. This workflow can't run on this page. Open it in Comfy Cloud with everything loaded, or download it for your own ComfyUI.
Try in CloudInside the workflow
A read-only preview of the node graph. Open it in Comfy Cloud or download it to edit the nodes.
Loading the graph
Open full-size workflow preview- Author
- Rob
Requires custom nodes
- Reading the workflow
Use this workflow from code
Generated from the workflow graph and inputs when possible; stored examples are shown otherwise. Review credentials and endpoints before running code, which may incur charges.
What you need
- Runs on
- Comfy Cloud
- Key
- COMFY_API_KEY
Needs a paid Cloud plan and credits.
# Python 3.10+: pip install comfy-sdk==0.4.0
import os
from comfy_sdk import Comfy
api_key = os.environ.get("COMFY_API_KEY", "").strip()
if not api_key: raise RuntimeError("Set COMFY_API_KEY")
os.environ.setdefault("COMFY_BASE_URL", "https://cloud.comfy.org")
client = Comfy(api_key=api_key)
workflow = client.workflows.from_json({
"3": {
"class_type": "KSampler",
"inputs": {
"cfg": 1,
"denoise": 1,
"latent_image": [
"98",
0
],
"model": [
"78",
0
],
"negative": [
"47",
0
],
"positive": [
"94",
0
],
"sampler_name": "euler",
"scheduler": "simple",
"seed": 0,
"steps": 12
},
"_meta": {
"title": "KSampler"
}
},
"18": {
"class_type": "VAEDecodeAudio",
"inputs": {
"samples": [
"3",
0
],
"vae": [
"106",
0
]
},
"_meta": {
"title": "VAE Decode Audio"
}
},
"47": {
"class_type": "ConditioningZeroOut",
"inputs": {
"conditioning": [
"94",
0
]
},
"_meta": {
"title": "Conditioning Zero Out"
}
},
"78": {
"class_type": "ModelSamplingAuraFlow",
"inputs": {
"model": [
"104",
0
],
"sampling": "flow",
"shift": 3
},
"_meta": {
"title": "ModelSamplingAuraFlow"
}
},
"94": {
"class_type": "TextEncodeAceStepAudio1.5",
"inputs": {
"bpm": [
"129",
0
],
"cfg_scale": 2,
"clip": [
"105",
0
],
"duration": [
"128",
0
],
"generate_audio_codes": True,
"keyscale": "E minor",
"language": "en",
"lyrics": [
"116",
0
],
"min_p": 0,
"seed": 1103026267678031,
"tags": "Lo-fi hip-hop chill track with mellow electric piano, soft vinyl crackle, subtle synth pads, low-pass filtered drums, percussion loops, and soft plucked bass for a relaxed, dreamy vibe.",
"temperature": 0.85,
"timesignature": "4",
"top_k": 0,
"top_p": 0.9
},
"_meta": {
"title": "TextEncodeAceStepAudio1.5"
}
},
"98": {
"class_type": "EmptyAceStep1.5LatentAudio",
"inputs": {
"batch_size": 1,
"seconds": [
"128",
0
]
},
"_meta": {
"title": "Empty Ace Step 1.5 Latent Audio"
}
},
"104": {
"class_type": "UNETLoader",
"inputs": {
"unet_name": "acestep_v1.5_xl_turbo_bf16.safetensors",
"weight_dtype": "default"
},
"_meta": {
"title": "Load Diffusion Model"
}
},
"105": {
"class_type": "DualCLIPLoader",
"inputs": {
"clip_name1": "qwen_0.6b_ace15.safetensors",
"clip_name2": "qwen_4b_ace15.safetensors",
"device": "default",
"type": "ace"
},
"_meta": {
"title": "Load CLIP (Dual)"
}
},
"106": {
"class_type": "VAELoader",
"inputs": {
"vae_name": "ace_1.5_vae.safetensors"
},
"_meta": {
"title": "Load VAE"
}
},
"111": {
"class_type": "SaveAudioAdvanced",
"inputs": {
"audio": [
"18",
0
],
"filename_prefix": "audio/ACE_Step1.5_xl_turbo",
"format": "mp3",
"format.quality": "V0"
},
"_meta": {
"title": "Save Audio (Advanced)"
}
},
"112": {
"class_type": "PrimitiveStringMultiline",
"inputs": {
"value": "An upbeat electronic anthem about building workflows in ComfyUI — connecting nodes late at night and watching the render come through."
},
"_meta": {
"title": "User: song topic (USER_INPUT)"
}
},
"113": {
"class_type": "CLIPLoader",
"inputs": {
"clip_name": "qwen3.5_4b_bf16.safetensors",
"device": "default",
"type": "stable_diffusion"
},
"_meta": {
"title": "CLIP Loader (Qwen LLM for lyrics)"
}
},
"114": {
"class_type": "StringReplace",
"inputs": {
"find": "USER_INPUT",
"replace": [
"112",
0
],
"string": "You are a professional songwriter. Write complete, singable lyrics about the topic below.\n\nThe song is about TARGET_DURATION seconds long at TARGET_BPM BPM. Write enough sections to fill that length naturally:\n- around 120-150s: 2 verses + 2 choruses.\n- around 180-240s: also add a bridge.\n- 210s or more: also add a short intro and outro.\nAt slower tempo (under 90 BPM) use fewer words per line; at faster tempo (over 130 BPM) you can fit more, but keep every line easy to sing in one breath.\n\nOutput ONLY the lyrics — no title, no notes, no explanation.\n\nPut each section tag on its own line, e.g. [Intro], [Verse 1], [Chorus], [Verse 2], [Bridge], [Outro].\n\nRules:\n- Blank line between sections.\n- 6-10 syllables per line; keep lines within a section a similar length.\n- Rhyme the verses; give the chorus one short, repeatable hook.\n- Stay with ONE core image or metaphor.\n- Plain, concrete lines a singer can breathe through — no stacked adjectives or vague filler (neon skies, electric hearts, endless dreams).\n\nTopic: USER_INPUT\nLyrics:"
},
"_meta": {
"title": "Lyrics Prompt (fill USER_INPUT)"
}
},
"115": {
"class_type": "TextGenerate",
"inputs": {
"clip": [
"113",
0
],
"max_length": 512,
"mtp": "auto",
"prompt": [
"132",
0
],
"sampling_mode": "on",
"sampling_mode.min_p": 0.05,
"sampling_mode.presence_penalty": 0,
"sampling_mode.repetition_penalty": 1.05,
"sampling_mode.seed": 35146436134,
"sampling_mode.temperature": 0.7,
"sampling_mode.top_k": 64,
"sampling_mode.top_p": 0.95,
"thinking": False,
"use_default_template": True
},
"_meta": {
"title": "Qwen TextGenerate (lyrics)"
}
},
"116": {
"class_type": "ComfySwitchNode",
"inputs": {
"on_false": [
"117",
0
],
"on_true": [
"115",
0
],
"switch": [
"121",
0
]
},
"_meta": {
"title": "Switch (auto: LLM if manual empty)"
}
},
"117": {
"class_type": "PrimitiveStringMultiline",
"inputs": {
"value": ""
},
"_meta": {
"title": "Manual lyrics (leave EMPTY to auto-generate)"
}
},
"119": {
"class_type": "PreviewAny",
"inputs": {
"source": [
"116",
0
]
},
"_meta": {
"title": "Preview Lyrics"
}
},
"121": {
"class_type": "StringCompare",
"inputs": {
"case_sensitive": True,
"mode": "Equal",
"string_a": [
"117",
0
],
"string_b": ""
},
"_meta": {
"title": "Compare Text (manual lyrics == empty)"
}
},
"127": {
"class_type": "PreviewAny",
"inputs": {
"source": [
"129",
0
]
},
"_meta": {
"title": "bpm to string"
}
},
"128": {
"class_type": "PrimitiveFloat",
"inputs": {
"value": 120
},
"_meta": {
"title": "Float (Duration)"
}
},
"129": {
"class_type": "PrimitiveInt",
"inputs": {
"value": 90
},
"_meta": {
"title": "Int (bpm)"
}
},
"130": {
"class_type": "PreviewAny",
"inputs": {
"source": [
"128",
0
]
},
"_meta": {
"title": "duration to string"
}
},
"131": {
"class_type": "StringReplace",
"inputs": {
"find": "TARGET_DURATION",
"replace": [
"130",
0
],
"string": [
"114",
0
]
},
"_meta": {
"title": "Inject DURATION (TARGET_DURATION)"
}
},
"132": {
"class_type": "StringReplace",
"inputs": {
"find": "TARGET_BPM",
"replace": [
"127",
0
],
"string": [
"131",
0
]
},
"_meta": {
"title": "Inject BPM (TARGET_BPM)"
}
}
})
# run() also passes api_key to the partner nodes in this workflow.
job = client.run(workflow, api_key=api_key)Questions & answers
Yes. Point UNETLoader and VAELoader to the Ace-Step 1.5XL UNet and VAE checkpoints. The workflow relies on TextEncodeAceStepAudio1.5 for conditioning, so ensure the corresponding text encoder resources are available as packaged with Ace-Step.
Duration is enforced by the latent length from EmptyAceStep1.5LatentAudio, so the output length will match closely. BPM guides the lyric density via TextGenerate and prompt injection but does not force strict metronomic alignment; treat it as a strong narrative/timing hint rather than a beat-locked constraint.
Yes. Switch to the Manual path and paste lyrics into PrimitiveStringMultiline. The ComfySwitchNode routes your text directly to TextEncodeAceStepAudio1.5 for conditioning before generation.
Try increasing KSampler steps moderately, lowering CFG if the sound is harsh, and keeping prompts concise and specific. Ensure the correct Ace-Step VAE is loaded, and avoid overly long durations on a single pass if you encounter instability.