feat: refactor nodes into modular workflow with sample_mode and auto audio2code

- Replace monolithic AceStepMusicGen node with modular architecture:
  AceStepText2MusicGenParams, AceStepSettings, AceStepText2MusicServer,
  AceStepAudioCodes, AceStepShowText
- Merge sample_mode into GenParams node with JS-driven widget visibility
  (sample_mode ON: sample_query/is_instrumental/language only;
   sample_mode OFF: full manual text2music controls)
- Move conditionally-hidden fields to optional inputs with defaults to
  prevent ComfyUI validation errors when JS hides widgets
- Remove standalone audio2code and example_generate server nodes;
  audio_codes now returned as a third output of Text2MusicServer
- Add frontend JS for dynamic widget hide/show, API key masking,
  server mode toggling (cloud/local), and show-text display
- Include pre-built text2music.json workflow
- Rewrite README with full node reference and usage guide

Made-with: Cursor
This commit is contained in:
Gong Junmin
2026-02-28 21:35:25 +08:00
parent 2a46ef8f4f
commit 324b15952b
5 changed files with 1229 additions and 416 deletions
+118 -42
View File
@@ -1,19 +1,12 @@
# ACE-Step-ComfyUI
Official ComfyUI node for [ACE-Step](https://acemusic.ai) AI music generation via ACE-Step API.
## Features
- **Text to Music** — Generate music from text descriptions and lyrics
- **Cover** — Create cover versions from source audio with style control
- **Repaint** — Regenerate specific sections of an audio track
ComfyUI nodes for [ACE-Step](https://acemusic.ai) AI music generation — text-to-music, cover/remix, repaint, and LLM-powered sample generation.
## Installation
### Via ComfyUI Manager
Search for `ACE-Step-ComfyUI` in ComfyUI Manager and install.
Search for **ACE-Step-ComfyUI** in ComfyUI Manager and install.
### Manual Installation
@@ -28,46 +21,129 @@ Restart ComfyUI after installation.
## Setup
1. Get your API key from [acemusic.ai](https://acemusic.ai/api-key)
2. Set the key using **one** of these methods:
- Enter it in the `api_key` widget on the node (auto-saved locally after first input, displayed as `●●●●` for security). The key is **not** stored in workflow JSON, so sharing workflows will not leak your API key
- Set the environment variable `ACESTEP_API_KEY`
### Cloud Mode (default)
> **Note:** The API key is saved in the `.config/` folder under the node directory. Do not share this folder with others.
1. Get your API key from [acemusic.ai/api-key](https://acemusic.ai/api-key)
2. In any Server node, set **mode** → `cloud` and paste your key into **api_key**
3. Or set the environment variable `ACESTEP_API_KEY`
## Node: ACE-Step Music Generate
### Local Mode
Located in: **api node > audio > AceStep**
1. Start the ACE-Step OpenRouter server locally:
```bash
cd ACE-Step-1.5
uv run acestep-openrouter --host 0.0.0.0 --port 8002
```
2. In any Server node, set **mode** → `local`
3. The URL auto-fills to `http://127.0.0.1:8002`; **api_key** and **Get API Key** button are hidden in local mode
### Inputs
## Workflows
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| server_url | STRING | `https://api.acemusic.ai` | ACE-Step API server URL |
| prompt | STRING | | Music description / caption |
| lyrics | STRING | | Song lyrics (leave empty for instrumental) |
| task_type | ENUM | `text2music` | `text2music`, `cover`, or `repaint` |
| instrumental | BOOLEAN | `false` | Generate instrumental only |
| duration | FLOAT | `30.0` | Audio duration in seconds (1–300) |
| bpm | INT | `0` | Beats per minute (0 = auto) |
| key_scale | STRING | | Musical key and scale (e.g. `C major`) |
| time_signature | STRING | | Time signature (e.g. `4/4`, `3/4`) |
| vocal_language | ENUM | `en` | Vocal language |
| sample_mode | BOOLEAN | `false` | Use LLM to generate prompt/lyrics from query |
| use_format | BOOLEAN | `false` | Use LLM to enhance caption and lyrics |
| seed | STRING | `-1` | Random seed (-1 = random, comma-separated for batch) |
| temperature | FLOAT | `0.85` | Sampling temperature |
| guidance_scale | FLOAT | `7.0` | Guidance scale |
| src_audio | AUDIO | | Source audio for cover/repaint tasks |
| ref_audio | AUDIO | | Reference audio for style/timbre guidance |
| api_key | STRING | | API key (auto-saved locally) |
Two ready-to-use workflow files are provided. Drag any `.json` file into ComfyUI to load it.
### Outputs
| File | Use Case |
|------|----------|
| `workflows/text2music.json` | Text-to-music, cover, remix, and repaint |
| `workflows/example_generate.json` | LLM auto-generates caption/lyrics/metadata, then synthesizes |
| Output | Type | Description |
|--------|------|-------------|
| audio | AUDIO | Generated audio waveform |
| info | STRING | Text response from the API |
### Workflow 1: Text2music / Cover / Remix / Repaint
**`workflows/text2music.json`**
```
Load Audio (refer) ─── refer_audio ──┐
Load Audio (src) ──┬── src_audio ────┤
│ ▼
audio2code Server Text2music Gen Params ── gen_params ──► Text2music Server ──► Save Audio
│ ▲ ▲ │
Audio Codes ── audio_codes┘ Settings ─┘ Show Text
```
**How to use:**
- **Text-to-music (basic):** Fill in **caption** and **lyrics** in Gen Params, leave everything else default. Click Queue Prompt.
- **With reference audio:** Load an audio file in "Load Audio (refer audio)", connect it to Gen Params → `refer_audio`. This guides the style/timbre.
- **Cover mode:** Load the source song in "Load Audio (src audio)", connect it to either:
- Gen Params → `src_audio` (sends raw audio directly), OR
- audio2code Server → Audio Codes → Gen Params → `audio_codes` (converts to codes first)
- Task type automatically switches to `cover` when `src_audio` or `audio_codes` is connected
- **cover_strength** — noise injection strength (0 = clean cover, higher = more variation)
- **remix_strength** — how much of the original to preserve (1.0 = full, 0 = none)
- **Repaint mode:** Connect source audio to Gen Params → `src_audio`, then check **is_repaint** in Gen Params. Set **repaint_start** and **repaint_end** (in seconds) to define the region to regenerate. Fill in **caption** and **lyrics** for the regenerated region.
- Cover/repaint mode automatically disables thinking, CoT caption, and CoT language — no LM is used.
**Settings tips:**
- **auto** toggle (ON by default): lets the LM decide bpm, key, duration, time signature. Turn OFF to set them manually.
- **thinking** = true: enables 5Hz LM audio code generation (higher quality, slower)
- **seed**: set a number for reproducible results, `-1` for random
### Workflow 2: Example Generate (LLM Auto-Generation)
**`workflows/example_generate.json`**
```
Example Generate Server ── gen_params ──► Text2music Server ──► Save Audio
│ ▲ │
└── info ──► Show Text (LLM params) Settings ─┘ Show Text (server info)
```
**How to use:**
1. Type a natural language description in **sample_query** (e.g. "upbeat pop song about summer")
2. Set **language** and **is_instrumental**
3. Click Queue Prompt — the LLM generates caption, lyrics, bpm, key, duration, etc., then synthesizes music
4. Check "LLM Generated Params" to see what the LLM produced
This is the equivalent of Gradio's "Simple mode" — fully automatic.
## Node Reference
### Server Nodes
These nodes call the ACE-Step API. Each has **mode** (cloud/local), **server_url**, and **api_key** fields.
| Node | Description | Outputs |
|------|-------------|---------|
| **audio2code Server** | Converts source audio → audio code tokens | `audio_codes`, `info` |
| **Example Generate Server** | LLM generates caption/lyrics/metadata from a query | `gen_params`, `caption`, `lyrics`, `info` |
| **Text2music Server** | Generates music (text2music/cover/remix/repaint) from gen_params + settings | `audio`, `info` |
### Parameter Nodes
| Node | Description | Key Fields |
|------|-------------|------------|
| **Text2music Gen Params** | Parameters for text2music / cover / remix / repaint | `caption`, `lyrics`, `vocal_language`, `bpm`, `key`, `duration`, `time_signature`, `auto`, `cover_strength`, `remix_strength`, `is_repaint`, `repaint_start`, `repaint_end` + optional `refer_audio`, `src_audio`, `audio_codes` |
| **Settings** | LM + DiT inference hyperparameters | `seed`, `thinking`, `use_cot_caption`, `use_cot_language`, `temperature`, `lm_cfg_scale`, `lm_top_p`, `lm_top_k`, `dit_guidance_scale`, `dit_inference_steps`, `dit_infer_method` |
### Utility Nodes
| Node | Description |
|------|-------------|
| **Audio Codes** | Editable passthrough for audio codes — paste manually or receive from audio2code Server. Displays received codes in the text area after execution. |
| **Show Text** | Displays any STRING input as multiline text with auto-sizing. |
### Built-in ComfyUI Nodes Used
- **LoadAudio** — load audio files from disk
- **SaveAudio** — save generated audio to disk
## Task Mode Summary
| Mode | Trigger | LM Used? | Key Parameters |
|------|---------|----------|----------------|
| **text2music** | No src_audio, no audio_codes, is_repaint OFF | Yes (if thinking=true) | caption, lyrics, bpm, key, duration |
| **cover** | src_audio connected OR audio_codes non-empty | No (auto-disabled) | cover_strength, remix_strength |
| **repaint** | src_audio connected AND is_repaint checked | No (auto-disabled) | repaint_start, repaint_end |
| **example generate** | Use Example Generate workflow | Yes (LLM generates params) | sample_query, language, is_instrumental |
## Tips
- **Cover/Repaint auto-safety:** When task is `cover` or `repaint`, `thinking`, `use_cot_caption`, `use_cot_language`, and `use_cot_metas` are all forced to `false` regardless of Settings values. The LM is not used for these tasks.
- **Auto metas:** When `auto` is ON in Gen Params, the `use_cot_metas` flag is sent as `true`, letting the LM infer bpm/key/duration/time_signature. When `auto` is OFF, your manual values are sent directly.
- **is_repaint toggle:** When OFF (default), `repaint_start` and `repaint_end` are hidden. When ON, they appear. Repaint requires `src_audio` to be connected.
- Workflows can be combined — users can copy nodes between workflows to create custom pipelines.
- Use **Show Text** nodes connected to `info` outputs to inspect generation parameters and server responses.
## License
+17 -3
View File
@@ -1,11 +1,25 @@
from .nodes import AceStepMusicGen
from .nodes import (
AceStepAudioCodes,
AceStepText2MusicGenParams,
AceStepSettings,
AceStepText2MusicServer,
AceStepShowText,
)
NODE_CLASS_MAPPINGS = {
"AceStepMusicGen": AceStepMusicGen,
"AceStepAudioCodes": AceStepAudioCodes,
"AceStepText2MusicGenParams": AceStepText2MusicGenParams,
"AceStepSettings": AceStepSettings,
"AceStepText2MusicServer": AceStepText2MusicServer,
"AceStepShowText": AceStepShowText,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"AceStepMusicGen": "ACE-Step Music Generate",
"AceStepAudioCodes": "ACE-Step Audio Codes",
"AceStepText2MusicGenParams": "ACE-Step Text2music Gen Params",
"AceStepSettings": "ACE-Step Settings",
"AceStepText2MusicServer": "ACE-Step Text2music Server",
"AceStepShowText": "ACE-Step Show Text",
}
WEB_DIRECTORY = "./js"
+210 -59
View File
@@ -1,5 +1,9 @@
import { app } from "../../scripts/app.js";
const SERVER_NODES = [
"AceStepText2MusicServer",
];
function hideWidget(node, widget) {
if (widget._hidden) return;
widget._hidden = true;
@@ -7,6 +11,11 @@ function hideWidget(node, widget) {
widget._origComputeSize = widget.computeSize;
widget.type = "hidden";
widget.computeSize = () => [0, -4];
const idx = node.widgets.indexOf(widget);
if (idx >= 0) {
widget._origIndex = idx;
node.widgets.splice(idx, 1);
}
}
function showWidget(node, widget) {
@@ -16,71 +25,213 @@ function showWidget(node, widget) {
widget.computeSize = widget._origComputeSize;
delete widget._origType;
delete widget._origComputeSize;
if (!node.widgets.includes(widget)) {
const idx = Math.min(widget._origIndex ?? node.widgets.length, node.widgets.length);
node.widgets.splice(idx, 0, widget);
}
delete widget._origIndex;
}
function recalcSize(node, extraH = 0) {
const sz = node.computeSize();
node.setSize([Math.max(sz[0], node.size[0]), sz[1] + extraH]);
}
function setupApiKeyMasking(node) {
const w = node.widgets.find(w => w.name === "api_key");
if (!w) return;
let realKey = "";
w.serializeValue = async () => realKey;
w.callback = function (value) {
if (!value) { realKey = ""; return; }
if (/^●+$/.test(value)) return;
realKey = value;
w.value = "●".repeat(Math.min(value.length, 16));
};
}
function setupServerNode(node) {
setupApiKeyMasking(node);
const modeW = node.widgets.find(w => w.name === "mode");
const urlW = node.widgets.find(w => w.name === "server_url");
const keyW = node.widgets.find(w => w.name === "api_key");
if (!modeW || !urlW) return;
const btnW = node.addWidget("button", "🔑 Get API Key", null, () => {
window.open("https://acemusic.ai/api-key", "_blank");
});
btnW.serialize = false;
const CLOUD = "https://api.acemusic.ai";
const LOCAL = "http://127.0.0.1:8002";
function update() {
const isLocal = modeW.value === "local";
if (urlW.value === CLOUD || urlW.value === LOCAL || !urlW.value.trim()) {
urlW.value = isLocal ? LOCAL : CLOUD;
}
if (keyW) { isLocal ? hideWidget(node, keyW) : showWidget(node, keyW); }
isLocal ? hideWidget(node, btnW) : showWidget(node, btnW);
recalcSize(node);
}
const orig = modeW.callback;
modeW.callback = function (...args) {
if (orig) orig.apply(this, args);
update();
};
update();
}
function setupGenParamsToggles(node) {
// Master list: canonical widget order, captured once.
const masterWidgets = node.widgets.slice();
const origTypes = new Map();
const origCompute = new Map();
for (const w of masterWidgets) {
origTypes.set(w, w.type);
origCompute.set(w, w.computeSize);
}
const byName = (n) => masterWidgets.find(w => w.name === n);
const sampleModeW = byName("sample_mode");
const autoW = byName("auto");
const repaintW = byName("is_repaint");
const TEXT_ROWS = { caption: 3, lyrics: 7, sample_query: 4 };
let extraHeight = 0;
for (const w of masterWidgets) {
if (TEXT_ROWS[w.name] && w.inputEl) {
const defaultRows = w.inputEl.rows || 3;
w.inputEl.rows = TEXT_ROWS[w.name];
w.inputEl.style.minHeight = (TEXT_ROWS[w.name] * 1.4) + "em";
extraHeight += (TEXT_ROWS[w.name] - defaultRows) * 18;
}
}
function computeVisible() {
const vis = new Set();
const isSample = sampleModeW && sampleModeW.value;
vis.add("sample_mode");
if (isSample) {
vis.add("sample_query");
vis.add("is_instrumental");
vis.add("vocal_language");
} else {
vis.add("caption");
vis.add("lyrics");
vis.add("vocal_language");
vis.add("auto");
vis.add("is_repaint");
const isAuto = autoW && autoW.value;
if (!isAuto) {
vis.add("bpm");
vis.add("key");
vis.add("duration");
vis.add("time_signature");
}
const isRepaint = repaintW && repaintW.value;
if (isRepaint) {
vis.add("repaint_start");
vis.add("repaint_end");
} else {
vis.add("cover_strength");
vis.add("remix_strength");
}
}
return vis;
}
function update() {
const vis = computeVisible();
// Rebuild node.widgets in master order, only including visible ones.
node.widgets.length = 0;
for (const w of masterWidgets) {
if (vis.has(w.name)) {
w.type = origTypes.get(w);
w.computeSize = origCompute.get(w);
w._hidden = false;
node.widgets.push(w);
} else {
w.type = "hidden";
w.computeSize = () => [0, -4];
w._hidden = true;
}
}
recalcSize(node, extraHeight);
}
for (const toggleW of [sampleModeW, autoW, repaintW]) {
if (!toggleW) continue;
const orig = toggleW.callback;
toggleW.callback = function (...args) {
if (orig) orig.apply(this, args);
update();
};
}
update();
}
function setupShowText(node) {
const el = document.createElement("textarea");
el.readOnly = true;
el.style.cssText =
"width:100%;background:transparent;color:inherit;border:none;" +
"resize:none;font:inherit;padding:4px;opacity:0.85;outline:none;" +
"white-space:pre-wrap;word-wrap:break-word;overflow-y:auto;";
el.rows = 4;
const textWidget = node.addDOMWidget("ace_text_output", "customtext", el, {
serialize: false,
getValue() { return el.value; },
setValue(v) { el.value = v || ""; },
});
node.onExecuted = function (data) {
if (data?.text?.[0] != null) {
el.value = data.text[0];
const lines = (data.text[0].match(/\n/g) || []).length + 1;
const rows = Math.max(4, Math.min(lines, 20));
el.rows = rows;
const h = Math.max(120, Math.min(rows * 18 + 80, 500));
node.setSize([Math.max(node.size[0], 300), h]);
}
};
}
function setupAudioCodes(node) {
node.onExecuted = function (data) {
const codes = data?.audio_codes?.[0];
if (codes == null) return;
const w = node.widgets.find(w => w.name === "audio_codes");
if (w) {
w.value = codes;
recalcSize(node);
}
};
}
app.registerExtension({
name: "AceStep.NodeExtensions",
nodeCreated(node) {
if (node.comfyClass !== "AceStepMusicGen") return;
const REPAINT_WIDGETS = ["repainting_start", "repainting_end"];
const COVER_WIDGETS = ["audio_cover_strength"];
function updateVisibility() {
const taskWidget = node.widgets.find(w => w.name === "task_type");
if (!taskWidget) return;
const task = taskWidget.value;
for (const w of node.widgets) {
if (REPAINT_WIDGETS.includes(w.name)) {
task === "repaint" ? showWidget(node, w) : hideWidget(node, w);
} else if (COVER_WIDGETS.includes(w.name)) {
task === "cover" ? showWidget(node, w) : hideWidget(node, w);
}
}
const sz = node.computeSize();
node.setSize([Math.max(sz[0], node.size[0]), sz[1]]);
if (SERVER_NODES.includes(node.comfyClass)) {
requestAnimationFrame(() => setupServerNode(node));
}
requestAnimationFrame(() => {
const taskWidget = node.widgets.find(w => w.name === "task_type");
if (taskWidget) {
const origCallback = taskWidget.callback;
taskWidget.callback = function (...args) {
if (origCallback) origCallback.apply(this, args);
updateVisibility();
};
}
updateVisibility();
});
// --- API Key masking ---
const apiKeyWidget = node.widgets.find(w => w.name === "api_key");
if (apiKeyWidget) {
let realKey = "";
// Return real key for execution, not the masked display value
apiKeyWidget.serializeValue = async () => realKey;
apiKeyWidget.callback = function (value) {
if (!value) {
realKey = "";
return;
}
// Ignore if user submitted the mask unchanged
if (/^●+$/.test(value)) return;
realKey = value;
apiKeyWidget.value = "●".repeat(Math.min(value.length, 16));
};
if (node.comfyClass === "AceStepText2MusicGenParams") {
requestAnimationFrame(() => setupGenParamsToggles(node));
}
if (node.comfyClass === "AceStepShowText") {
requestAnimationFrame(() => setupShowText(node));
}
if (node.comfyClass === "AceStepAudioCodes") {
requestAnimationFrame(() => setupAudioCodes(node));
}
// --- Get API Key button ---
const btn = node.addWidget("button", "🔑 Get API Key", null, () => {
window.open("https://acemusic.ai/api-key", "_blank");
});
btn.serialize = false;
},
});
+659 -312
View File
File diff suppressed because it is too large Load Diff
+225
View File
@@ -0,0 +1,225 @@
{
"id": "ffe0e07f-9772-45cb-845d-bf5666d27930",
"revision": 1,
"last_node_id": 12,
"last_link_id": 10,
"nodes": [
{
"id": 5,
"type": "AceStepText2MusicGenParams",
"pos": [40, 40],
"size": [420, 500],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{ "name": "refer_audio", "shape": 7, "type": "AUDIO", "link": null },
{ "name": "src_audio", "shape": 7, "type": "AUDIO", "link": null },
{ "name": "audio_codes", "shape": 7, "type": "STRING", "link": null }
],
"outputs": [
{
"name": "gen_params",
"type": "ACESTEP_GEN_PARAMS",
"slot_index": 0,
"links": [1]
}
],
"title": "Text2music Gen Params",
"properties": { "Node name for S&R": "AceStepText2MusicGenParams" },
"widgets_values": [
false,
"en"
]
},
{
"id": 6,
"type": "AceStepSettings",
"pos": [40, 590],
"size": [420, 298],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "settings",
"type": "ACESTEP_SETTINGS",
"slot_index": 0,
"links": [2]
}
],
"title": "Settings",
"properties": { "Node name for S&R": "AceStepSettings" },
"widgets_values": [
"-1", true, true, true, 0.85, 2, 0.9, 0, 7, 8, "ode"
]
},
{
"id": 11,
"type": "LoadAudio",
"pos": [500, 40],
"size": [270, 136],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [],
"outputs": [
{ "name": "AUDIO", "type": "AUDIO", "slot_index": 0, "links": null }
],
"title": "Load Audio (refer audio)",
"properties": { "Node name for S&R": "LoadAudio" },
"widgets_values": ["", null, null]
},
{
"id": 2,
"type": "LoadAudio",
"pos": [500, 220],
"size": [270, 136],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [],
"outputs": [
{ "name": "AUDIO", "type": "AUDIO", "slot_index": 0, "links": null }
],
"title": "Load Audio (src audio)",
"properties": { "Node name for S&R": "LoadAudio" },
"widgets_values": ["", null, null]
},
{
"id": 7,
"type": "AceStepText2MusicServer",
"pos": [500, 420],
"size": [340, 130],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{ "name": "gen_params", "type": "ACESTEP_GEN_PARAMS", "link": 1 },
{ "name": "settings", "type": "ACESTEP_SETTINGS", "link": 2 }
],
"outputs": [
{ "name": "audio", "type": "AUDIO", "slot_index": 0, "links": [3] },
{ "name": "info", "type": "STRING", "slot_index": 1, "links": [4] },
{ "name": "audio_codes", "type": "STRING", "slot_index": 2, "links": [5] }
],
"title": "Text2music Server",
"properties": { "Node name for S&R": "AceStepText2MusicServer" },
"widgets_values": ["cloud", "https://api.acemusic.ai"]
},
{
"id": 8,
"type": "SaveAudio",
"pos": [500, 600],
"size": [340, 112],
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{ "name": "audio", "type": "AUDIO", "link": 3 }
],
"outputs": [],
"title": "Save Audio",
"properties": { "Node name for S&R": "SaveAudio" },
"widgets_values": ["ace-step/text2music"]
},
{
"id": 9,
"type": "AceStepShowText",
"pos": [500, 760],
"size": [340, 160],
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{ "name": "text", "type": "STRING", "link": 4 }
],
"outputs": [
{ "name": "text", "type": "STRING", "links": null }
],
"title": "Generation Info",
"properties": { "Node name for S&R": "AceStepShowText" },
"widgets_values": [""]
},
{
"id": 4,
"type": "AceStepAudioCodes",
"pos": [880, 40],
"size": [300, 200],
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{ "name": "audio_codes_in", "shape": 7, "type": "STRING", "link": 5 }
],
"outputs": [
{ "name": "audio_codes", "type": "STRING", "slot_index": 0, "links": null }
],
"title": "Audio Codes",
"properties": { "Node name for S&R": "AceStepAudioCodes" },
"widgets_values": [""]
},
{
"id": 12,
"type": "Note",
"pos": [880, 280],
"size": [300, 340],
"flags": {},
"order": 8,
"mode": 0,
"inputs": [],
"outputs": [],
"title": "Usage Guide",
"properties": {},
"widgets_values": [
"=== Text2music ===\nFill caption + lyrics, click Queue Prompt.\nOr toggle sample_mode ON and describe\nthe music you want in sample_query.\n\n=== Cover / Remix ===\nConnect src_audio to Gen Params.\nServer auto-converts audio to codes.\nAdjust cover/remix_strength.\n\n=== Repaint ===\nConnect src_audio, check is_repaint,\nset repaint_start / repaint_end.\n\n=== Reference Audio ===\nConnect refer_audio for style guidance.\n\n=== Auto Toggle ===\nON = LM decides bpm/key/duration.\nOFF = use your manual values.\n\n=== Audio Codes ===\nCodes from generation are output to\nthe Audio Codes node for reuse."
],
"color": "#232",
"bgcolor": "#353"
}
],
"links": [
[1, 5, 0, 7, 0, "ACESTEP_GEN_PARAMS"],
[2, 6, 0, 7, 1, "ACESTEP_SETTINGS"],
[3, 7, 0, 8, 0, "AUDIO"],
[4, 7, 1, 9, 0, "STRING"],
[5, 7, 2, 4, 0, "STRING"]
],
"groups": [
{
"id": 1,
"title": "Parameters",
"bounding": [20, -10, 460, 920],
"color": "#3f789e",
"font_size": 18,
"flags": {}
},
{
"id": 2,
"title": "Audio Input & Generation",
"bounding": [480, -10, 380, 960],
"color": "#3f789e",
"font_size": 18,
"flags": {}
},
{
"id": 3,
"title": "Output & Codes",
"bounding": [860, -10, 340, 660],
"color": "#3f789e",
"font_size": 18,
"flags": {}
}
],
"config": {},
"extra": {
"ds": {
"scale": 0.8,
"offset": [30, 30]
},
"workflowRendererVersion": "LG",
"frontendVersion": "1.39.19"
},
"version": 0.4
}