Merge pull request #2 from ace-step/feat/refactor-nodes-sample-mode

feat: refactor nodes into modular workflow with sample_mode and auto audio2code
This commit is contained in:
Gong Junmin
2026-02-28 21:46:21 +08:00
committed by GitHub
5 changed files with 1250 additions and 416 deletions
+139 -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,150 @@ 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 the **Text2music 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
> The key is auto-saved locally (in `.config/` under the node directory) and displayed as `●●●●` for security. It is **not** stored in workflow JSON.
Located in: **api node > audio > AceStep**
### Local Mode
### Inputs
Local mode lets you run the ACE-Step inference server on your own machine (requires a GPU with sufficient VRAM).
| 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) |
**1. Install ACE-Step 1.5**
### Outputs
```bash
git clone https://github.com/ace-step/ACE-Step-1.5.git
cd ACE-Step-1.5
```
| Output | Type | Description |
|--------|------|-------------|
| audio | AUDIO | Generated audio waveform |
| info | STRING | Text response from the API |
Install dependencies (choose one):
```bash
# Using uv (recommended)
uv sync
# Or using pip
pip install -e .
```
**2. Start the local server**
```bash
# Using uv
uv run acestep-openrouter --host 0.0.0.0 --port 8002
# Or if installed via pip
acestep-openrouter --host 0.0.0.0 --port 8002
```
The server will download model weights on first launch and then listen on `http://127.0.0.1:8002`.
**3. Configure ComfyUI**
In the **Text2music Server** node, set **mode** → `local`. The URL auto-fills to `http://127.0.0.1:8002` and the **api_key** field is hidden since no key is needed locally.
## Usage Guide
Drag `workflows/text2music.json` into ComfyUI to load the ready-to-use workflow.
```
┌── refer_audio ──┐
Load Audio (refer) ──────────┘ │
▼
Text2music Gen Params ── gen_params ──► Text2music Server ──► Save Audio
▲ ▲ │
Load Audio (src) ──── src_audio ───────────────┘ Settings ┘ Show Text
Audio Codes ────────── audio_codes ────────────┘
```
### Quick Start: Text-to-Music
1. Open the workflow. **sample_mode** is OFF by default — you are in manual mode.
2. Fill in **caption** (music style description) and **lyrics** (with `[verse]`, `[chorus]` tags). Leave lyrics empty for instrumental.
3. Click **Queue Prompt**. The generated audio appears in **Save Audio** and generation info in **Show Text**.
### Sample Mode (LLM Auto-Generation)
Instead of writing caption and lyrics yourself, let the LLM generate everything from a simple description:
1. Toggle **sample_mode** → ON in the Gen Params node.
2. Fill in **sample_query** (e.g. `"a funk rock song with groovy bass and punchy drums"`).
3. Choose **vocal_language** and set **is_instrumental** if you want instrumental only.
4. Click **Queue Prompt**. The LLM generates caption, lyrics, bpm, key, duration, and time signature automatically, then synthesizes the music.
> In sample mode, only three fields are shown: `sample_query`, `vocal_language`, and `is_instrumental`. All other parameters are decided by the LLM.
### Manual Mode Controls
When **sample_mode** is OFF, you have full manual control:
| Field | Description |
|-------|-------------|
| **caption** | Music style / genre description |
| **lyrics** | Song lyrics (empty = instrumental) |
| **vocal_language** | Language for vocals |
| **auto** | ON (default): let the LM decide bpm, key, duration, time signature. OFF: set them manually below. |
| **bpm** | Beats per minute (shown when auto=OFF) |
| **key** | Musical key, e.g. `C major` (shown when auto=OFF) |
| **duration** | Duration in seconds (shown when auto=OFF) |
| **time_signature** | e.g. `4`, `3`, `6` (shown when auto=OFF) |
| **is_repaint** | Enable repaint mode (see below) |
| **cover_strength** | Noise injection strength for cover (0 = clean cover) |
| **remix_strength** | How much original audio to preserve (1.0 = full) |
### Cover / Remix Mode
1. Load the source song via **Load Audio** and connect it to Gen Params → `src_audio`.
2. Alternatively, connect pre-extracted audio codes via **Audio Codes** → Gen Params → `audio_codes`.
3. The task type automatically switches to `cover` when `src_audio` or `audio_codes` is connected.
4. Adjust **cover_strength** and **remix_strength** to control the output.
> The server automatically converts source audio to audio codes internally when needed. The generated `audio_codes` are also returned as a third output of Text2music Server.
### Repaint Mode
1. Connect source audio to Gen Params → `src_audio`.
2. Toggle **is_repaint** → ON. The **repaint_start** and **repaint_end** fields appear.
3. Set the time range (in seconds) for the region to regenerate.
4. Fill in **caption** and **lyrics** for the regenerated section.
### Reference Audio
Connect a reference audio file to Gen Params → `refer_audio` to guide the style and timbre of the generated music. This works with any mode.
### Settings Node
The **Settings** node controls inference hyperparameters:
| Field | Default | Description |
|-------|---------|-------------|
| **seed** | `-1` | Random seed (`-1` = random, set a number for reproducibility) |
| **thinking** | `true` | Enable 5Hz LM audio code generation (higher quality, slower) |
| **use_cot_caption** | `true` | LM chain-of-thought for caption refinement |
| **use_cot_language** | `true` | LM chain-of-thought for language detection |
| **temperature** | `0.85` | Sampling temperature |
| **lm_cfg_scale** | `1.0` | LM classifier-free guidance scale |
| **dit_guidance_scale** | `3.5` | DiT guidance scale |
| **dit_inference_steps** | `60` | Number of DiT denoising steps |
## Node Reference
| Node | Description | Inputs | Outputs |
|------|-------------|--------|---------|
| **Text2music Gen Params** | Build generation parameters. Supports sample_mode (LLM auto) and manual mode. | `sample_mode`, `vocal_language`, + optional fields | `gen_params` |
| **Settings** | Inference hyperparameters for LM + DiT. | `seed`, `thinking`, `temperature`, etc. | `settings` |
| **Text2music Server** | Calls the ACE-Step API to generate music. | `gen_params`, `settings`, `server_url`, `api_key`, `mode` | `audio`, `info`, `audio_codes` |
| **Audio Codes** | Editable passthrough for audio codes. Paste manually or receive from Text2music Server. | `audio_codes_in` (optional) | `audio_codes` |
| **Show Text** | Displays any STRING input as a read-only text area. | `text` | `text` (passthrough) |
## Tips
- **Cover/Repaint auto-safety:** When the task is `cover` or `repaint`, `thinking`, `use_cot_caption`, and `use_cot_language` are forced to `false` regardless of Settings values — the LM is not used for these tasks.
- **Auto metas:** When `auto` is ON, the LM infers bpm/key/duration/time_signature. When OFF, your manual values are sent directly.
- Use **Show Text** nodes connected to `info` outputs to inspect what the server returned (LLM-generated parameters, generation metadata, etc.).
## 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
}