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:
@@ -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
@@ -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
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user