Skip to content

Commit a9133a3

Browse files
authored
Merge branch 'main' into codex/lyrics-style-format-fallback
2 parents 0ca0692 + 4c162ef commit a9133a3

14 files changed

Lines changed: 1480 additions & 171 deletions

.github/copilot-instructions.md

Lines changed: 404 additions & 0 deletions
Large diffs are not rendered by default.

.github/workflows/build-release.yml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -146,17 +146,10 @@ jobs:
146146
-srcfolder dmg_temp \
147147
-ov -format UDZO \
148148
AceForge-macOS.dmg
149-
150-
- name: Create ZIP archive (alternative distribution)
151-
run: |
152-
cd dist
153-
zip -r ../AceForge-macOS.zip AceForge.app
154-
cd ..
155149
156150
- name: Calculate checksums
157151
run: |
158152
shasum -a 256 AceForge-macOS.dmg > checksums.txt
159-
shasum -a 256 AceForge-macOS.zip >> checksums.txt
160153
cat checksums.txt
161154
162155
- name: Upload DMG artifact
@@ -171,5 +164,4 @@ jobs:
171164
with:
172165
files: |
173166
AceForge-macOS.dmg
174-
AceForge-macOS.zip
175167
checksums.txt

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,24 @@ AceForge is a **local-first AI music workstation for macOS Silicon** powered by
6161

6262
> **Note:** The app bundle does NOT include the large model files. On first run, it will download the ACE-Step models (several GB) automatically. You can monitor the download progress in the Terminal window or in the Server Console panel in the web interface.
6363
64+
### Option 2: Run locally for testing (developers)
65+
66+
To run the app from source without building the `.app` bundle:
67+
68+
1. **One-time setup:** Install dependencies (e.g. run the full build once to create the venv):
69+
```bash
70+
./build_local.sh
71+
```
72+
This creates `venv_build/` and installs Python deps. You can cancel after the PyInstaller step if you only want to run locally.
73+
74+
2. **Run the server:**
75+
```bash
76+
./run_local.sh
77+
```
78+
This builds the React UI if needed (requires [Bun](https://bun.sh)), then starts the Flask server at **http://127.0.0.1:5056**. Open that URL in your browser to use AceForge.
79+
80+
If you prefer to use your own venv instead of `venv_build`, install deps with `pip install -r requirements_ace_macos.txt` (and the same extra steps as in `build_local.sh` for TTS/ACE-Step), then run `python music_forge_ui.py`.
81+
6482
## Using AceForge (high-level workflow)
6583

6684
1. Launch AceForge and wait for the UI

api/generate.py

Lines changed: 201 additions & 45 deletions
Large diffs are not rendered by default.

cdmf_pipeline_ace_step.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1355,7 +1355,7 @@ def text2music_diffusion_process(
13551355
use_erg_lyric=False,
13561356
use_erg_diffusion=False,
13571357
retake_random_generators=None,
1358-
retake_variance=0.5,
1358+
retake_variance=0.2,
13591359
add_retake_noise=False,
13601360
guidance_scale_text=0.0,
13611361
guidance_scale_lyric=0.0,
@@ -2052,7 +2052,7 @@ def __call__(
20522052
lora_name_or_path: str = "none",
20532053
lora_weight: float = 1.0,
20542054
retake_seeds: list = None,
2055-
retake_variance: float = 0.5,
2055+
retake_variance: float = 0.2, # ACE-Step-MCP retake/repaint default
20562056
task: str = "text2music",
20572057
repaint_start: int = 0,
20582058
repaint_end: int = 0,
@@ -2195,23 +2195,38 @@ def __call__(
21952195
preprocess_time_cost = end_time - start_time
21962196
start_time = end_time
21972197

2198+
# Lego/extract/complete: generate NEW track from prompt only; use source only for duration (no repaint/retake).
2199+
# Repaint/retake/extend: use src_latents in diffusion.
21982200
add_retake_noise = task in ("retake", "repaint", "extend")
21992201
# retake equal to repaint
22002202
if task == "retake":
22012203
repaint_start = 0
22022204
repaint_end = audio_duration
2205+
if task in ("lego", "extract", "complete"):
2206+
repaint_start = 0
2207+
repaint_end = audio_duration
22032208

22042209
src_latents = None
22052210
if src_audio_path is not None:
22062211
assert src_audio_path is not None and task in (
22072212
"repaint",
22082213
"edit",
22092214
"extend",
2210-
), "src_audio_path is required for retake/repaint/extend task"
2215+
"lego",
2216+
"extract",
2217+
"complete",
2218+
), "src_audio_path is required for repaint/extend/lego/extract/complete task"
22112219
assert os.path.exists(
22122220
src_audio_path
22132221
), f"src_audio_path {src_audio_path} does not exist"
2214-
src_latents = self.infer_latents(src_audio_path)
2222+
src_latents_inferred = self.infer_latents(src_audio_path)
2223+
if task in ("lego", "extract", "complete"):
2224+
# Use source only to set output duration; do not pass latents into diffusion (generate from scratch with prompt).
2225+
num_frames = src_latents_inferred.shape[-1]
2226+
audio_duration = num_frames * 512 * 8 / 44100.0
2227+
src_latents = None # no repaint for lego
2228+
else:
2229+
src_latents = src_latents_inferred
22152230

22162231
ref_latents = None
22172232
if ref_audio_input is not None and audio2audio_enable:

docs/ACE-Step-INFERENCE.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
Source: https://github.com/ace-step/ACE-Step-1.5/blob/main/docs/en/INFERENCE.md
44
Use for: GenerationParams/GenerationConfig, task types (text2music, cover, repaint, etc.),
55
reference_audio vs src_audio, audio_cover_strength, and parameter specs.
6+
7+
AceForge alignment: We align core defaults with the working ACE-Step-MCP reference
8+
(https://huggingface.co/spaces/reach-vb/ACE-Step-MCP/blob/main/ui/components.py):
9+
ref_audio_strength=0.5 (Audio2Audio/cover reference), retake_variance=0.2 (retake/repaint),
10+
and pipeline param names ref_audio_input, src_audio_path, audio2audio_enable.
611
-->
712

813
# ACE-Step Inference API Documentation

docs/ACEFORGE_API.md

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -125,16 +125,21 @@ ACE-Step text-to-music (and related tasks). Jobs are queued and run one at a tim
125125
- `inferenceSteps`: int (e.g. 55).
126126
- `guidanceScale`: float (e.g. 6.0).
127127
- `seed`: int; if `randomSeed` is true, server may override with random.
128-
- `taskType`: `"text2music"` | `"retake"` | `"repaint"` | `"extend"` | `"cover"` | `"audio2audio"`.
129-
- `referenceAudioUrl`, `sourceAudioUrl`: URLs like `/audio/refs/...` or `/audio/<filename>` for reference/cover.
130-
- `audioCoverStrength` / `ref_audio_strength`: 0–1.
131-
- `repaintingStart`, `repaintingEnd`: for repaint task.
128+
- **Task and audio params** (see [ACE-Step Tutorial](https://github.com/ace-step/ACE-Step-1.5/blob/main/docs/en/Tutorial.md#guiding-the-elephant-what-can-you-control)): The API accepts both **ACE-Step official names** (snake_case) and **UI names** (camelCase): `task_type` or `taskType`; `reference_audio` or `referenceAudioUrl` or `reference_audio_path`; `src_audio` or `sourceAudioUrl` or `source_audio_path`; `audio_cover_strength` or `audioCoverStrength` or `ref_audio_strength`.
129+
- `taskType` / `task_type`: `"text2music"` | `"retake"` | `"repaint"` | `"extend"` | `"cover"` | `"audio2audio"` | `"lego"` | `"extract"` | `"complete"`. **Lego**, **extract**, and **complete** require the ACE-Step **Base** DiT model (see Preferences and ACE-Step models).
130+
- `instruction`: optional; for **lego** (and extract/complete), task-specific instruction (e.g. `"Generate the guitar track based on the audio context:"`). If omitted for lego, the server builds one from track name/caption.
131+
- `reference_audio` / `referenceAudioUrl`: path or URL for reference audio (style/timbre). `src_audio` / `sourceAudioUrl`: path or URL for source/backing audio (cover, repaint, lego, etc.). For **lego**, **extract**, and **complete**, source audio is required.
132+
- `audio_cover_strength` / `audioCoverStrength`: 0–1 (reference/source influence strength). Defaults: **cover/retake** 0.8 (strong source); **audio2audio** 0.5 (matches [ACE-Step-MCP](https://huggingface.co/spaces/reach-vb/ACE-Step-MCP)); **lego** uses `legoBackingInfluence` (default 0.25).
133+
- `retake_variance` / `retakeVariance`: 0–1, default 0.2 (retake/repaint; aligned with ACE-Step-MCP).
134+
- `repaintingStart`, `repaintingEnd` / `repaint_start`, `repaint_end`: for repaint task; -1 end = end of audio.
132135
- `title`: base name for output file.
133136
- `outputDir` / `output_dir`: optional; else uses app default.
134137
- `keyScale`, `timeSignature`, `vocalLanguage`, `bpm`: optional.
135138
- `loraNameOrPath`: optional; folder name from LoRA list or path to adapter (see `GET /api/generate/lora_adapters`).
136139
- `loraWeight`: optional; 0–2, default 0.75.
137140

141+
**Base-only tasks (lego, extract, complete):** Require `ace_step_dit_model: "base"` in preferences and the Base model to be installed (Settings or `GET /api/ace-step/models`). For **lego**: send `taskType: "lego"`, `sourceAudioUrl` (backing audio), `instruction` (e.g. `"Generate the <track> track based on the audio context:"`), and `style` as the track description (caption). Supported track names: `vocals`, `backing_vocals`, `drums`, `bass`, `guitar`, `keyboard`, `percussion`, `strings`, `synth`, `fx`, `brass`, `woodwinds`. See `docs/ACE-Step-INFERENCE.md` for extract/complete parameters.
142+
138143
**Response (POST):** `{ "jobId": "<uuid>", "status": "queued", "queuePosition": 1 }`
139144

140145
**Status response:** `{ "jobId", "status": "queued"|"running"|"succeeded"|"failed"|"cancelled", "queuePosition"?, "etaSeconds"?, "result"?, "error"? }`. On success, `result` includes e.g. `audioUrls`, `duration`, `status`. Cancelled jobs have `status: "cancelled"` and `error: "Cancelled by user"`.
@@ -203,11 +208,12 @@ List available DiT/LM models and trigger downloads. **The ACE-Step 1.5 downloade
203208

204209
| Method | Path | Description |
205210
|--------|------|-------------|
206-
| GET | `/api/ace-step/models` | List DiT and LM models with `installed` status, plus `discovered_models`: all model directories found under the checkpoints folder (including custom trained models). Response includes `dit_models`, `lm_models`, `discovered_models` (id, label, path, custom), `acestep_download_available`, `checkpoints_path`. |
207-
| POST | `/api/ace-step/models/download` | Start download. Body: `{ "model": "turbo" | "turbo-shift1" | "sft" | "base" | "0.6B" | "1.7B" | "4B" }`. Uses bundled downloader (or `acestep-download` on PATH if not bundled). Returns `{ "ok", "model", "path" }` or `{ "error", "hint" }`. |
208-
| GET | `/api/ace-step/models/status` | Download progress: `{ "running", "model", "progress", "error" }`. |
211+
| GET | `/api/ace-step/models` | List DiT and LM models with `installed` status, plus `discovered_models`: all model directories found under the checkpoints folder (including custom trained models). Response includes `dit_models`, `lm_models`, `discovered_models` (id, label, path, custom), `acestep_download_available`, `checkpoints_path`. Use this to verify the **Base** model is installed before starting a lego/extract/complete job. |
212+
| POST | `/api/ace-step/models/download` | Start download. Body: `{ "model": "turbo" \| "turbo-shift1" \| "turbo-shift3" \| "turbo-continuous" \| "sft" \| "base" \| "0.6B" \| "1.7B" \| "4B" }`. Uses bundled downloader (or `acestep-download` on PATH if not bundled). Returns `{ "ok", "model", "path" }` or `{ "error", "hint" }`. |
213+
| GET | `/api/ace-step/models/status` | Download progress: `{ "running", "model", "progress", "error", "current_file", "file_index", "total_files", "eta_seconds", "cancelled" }`. |
214+
| POST | `/api/ace-step/models/download/cancel` | Request cancellation of the current download. Returns `{ "cancelled", "message" }`. |
209215

210-
**Task types:** Generation accepts `taskType`: `text2music`, `cover`, `audio2audio`, `repaint`, `extend`, and (ACE-Step 1.5 Base) `lego`, `extract`, `complete`. Lego/extract/complete require the Base model and full 1.5 integration (planned).
216+
**Task → model:** Generation accepts `taskType`: `text2music`, `cover`, `audio2audio`, `repaint`, `extend`, and (Base-only) `lego`, `extract`, `complete`. **Lego**, **extract**, and **complete** require the **Base** DiT model: set `ace_step_dit_model` to `"base"` in preferences and ensure the Base model is installed (download via Settings or `POST /api/ace-step/models/download` with `"model": "base"`). The UI checks `GET /api/ace-step/models` for `dit_models[].installed` before allowing these tasks.
211217

212218
---
213219

generate_ace.py

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -567,9 +567,9 @@ def _prepare_reference_audio(
567567
src_audio_path: str | None,
568568
) -> tuple[str, bool, Optional[str]]:
569569
"""
570-
Normalise the ACE-Step edit / audio2audio mode:
570+
Normalise the ACE-Step edit / audio2audio mode (task_type, reference_audio, src_audio per Tutorial/INFERENCE):
571571
572-
- Task is clamped to one of: text2music / retake / repaint / extend.
572+
- Task (task_type) is clamped to one of: text2music / retake / repaint / extend.
573573
- UI tasks "cover" and "audio2audio" are mapped to "retake" (ACE-Step
574574
then uses ref_audio_input and sets task to "audio2audio" internally).
575575
- If Audio2Audio is enabled while task is still 'text2music', we
@@ -592,15 +592,18 @@ def _prepare_reference_audio(
592592
if audio2audio_enable and task_norm == "text2music":
593593
task_norm = "retake"
594594

595-
# Any of the edit-style tasks imply some form of Audio2Audio.
595+
# Any of the edit-style tasks imply some form of Audio2Audio or source-backed (lego/extract/complete).
596596
audio2audio_flag = bool(
597597
audio2audio_enable or task_norm in ("retake", "repaint", "extend")
598598
)
599+
needs_src_path = audio2audio_flag or task_norm in ("lego", "extract", "complete")
599600

600-
# If we *think* we're in an edit / audio2audio mode but there's no
601-
# reference audio path at all, don't crash — just fall back to
602-
# plain text2music.
603-
if audio2audio_flag and not src_audio_path:
601+
# If we need source/reference audio but none was provided, fall back to text2music (or fail for lego/extract/complete).
602+
if needs_src_path and not src_audio_path:
603+
if task_norm in ("lego", "extract", "complete"):
604+
raise ValueError(
605+
f"Task '{task_norm}' requires backing/source audio. Please provide it in the Lego tab or Custom audio card."
606+
)
604607
print(
605608
"[ACE] Audio2Audio / edit task requested but no reference audio "
606609
"was provided — falling back to plain text2music.",
@@ -612,6 +615,8 @@ def _prepare_reference_audio(
612615

613616
if audio2audio_flag:
614617
ref_path = _ensure_reference_wav(src_audio_path)
618+
elif task_norm in ("lego", "extract", "complete"):
619+
ref_path = _ensure_reference_wav(src_audio_path) # pipeline uses this as src_audio_path
615620
else:
616621
ref_path = None
617622

@@ -825,11 +830,11 @@ def _run_ace_text2music(
825830
task: str = "text2music",
826831
repaint_start: float = 0.0,
827832
repaint_end: float = 0.0,
828-
retake_variance: float = 0.5,
833+
retake_variance: float = 0.2, # MCP retake/repaint use 0.2
829834
src_audio_path: str | None = None,
830-
# Audio2Audio + LoRA
835+
# Audio2Audio + LoRA (ref_audio_strength 0.5 matches ACE-Step-MCP / pipeline default)
831836
audio2audio_enable: bool = False,
832-
ref_audio_strength: float = 0.7,
837+
ref_audio_strength: float = 0.5,
833838
lora_name_or_path: str | None = None,
834839
lora_weight: float = 0.75,
835840
cancel_check: Optional[Callable[[], bool]] = None,
@@ -972,10 +977,10 @@ def _run_ace_text2music(
972977

973978
# Wire up reference vs source audio per ACE-Step pipeline:
974979
#
975-
# - retake / cover / audio2audio: use ref_audio_input (pipeline sets task to
976-
# "audio2audio" and uses ref_latents). Do NOT pass src_audio_path.
977-
# - repaint / extend: use src_audio_path (pipeline uses src_latents for the
978-
# segment to repaint or extend). Do NOT pass ref_audio_input for this path.
980+
# - retake / cover / audio2audio / lego / extract / complete: use ref_audio_input so the pipeline
981+
# gets backing latents. For lego we use LOW ref_audio_strength (API default 0.3) so diffusion
982+
# starts from noisy backing and denoises toward the prompt (new instrument), matching timing.
983+
# - repaint / extend: use src_audio_path (pipeline uses src_latents for repaint/extend segment).
979984
# - text2music: leave both unset (None).
980985
if not src_audio_path:
981986
call_kwargs["ref_audio_input"] = None
@@ -984,7 +989,7 @@ def _run_ace_text2music(
984989
call_kwargs["src_audio_path"] = src_audio_path
985990
call_kwargs["ref_audio_input"] = None
986991
else:
987-
# retake (including cover/audio2audio from UI)
992+
# retake, cover, audio2audio, lego, extract, complete: backing as ref (lego uses low ref_audio_strength)
988993
call_kwargs["ref_audio_input"] = src_audio_path
989994
call_kwargs["src_audio_path"] = None
990995

@@ -1117,9 +1122,9 @@ def generate_track_ace(
11171122
task: str = "text2music",
11181123
repaint_start: float = 0.0,
11191124
repaint_end: float = 0.0,
1120-
retake_variance: float = 0.5,
1125+
retake_variance: float = 0.2, # ACE-Step-MCP retake/repaint default
11211126
audio2audio_enable: bool = False,
1122-
ref_audio_strength: float = 0.7,
1127+
ref_audio_strength: float = 0.5, # ACE-Step-MCP / pipeline default
11231128
src_audio_path: str | None = None,
11241129
lora_name_or_path: str | None = None,
11251130
lora_weight: float = 0.75,
@@ -1219,7 +1224,7 @@ def generate_track_ace(
12191224
if cfg_type not in ("apg", "cfg", "cfg_star"):
12201225
cfg_type = "apg"
12211226

1222-
# Normalise edit / Audio2Audio settings before we talk to ACE-Step.
1227+
# Normalise edit / Audio2Audio (maps to ACE-Step task_type, ref_audio_input, src_audio, audio_cover_strength).
12231228
task, audio2audio_enable, src_audio_path = _prepare_reference_audio(
12241229
task,
12251230
bool(audio2audio_enable),

music_forge_ui.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,9 +395,11 @@ def flush(self):
395395
preferences_bp,
396396
ace_step_models_bp,
397397
)
398+
from api.generate import reset_generation_queue
398399
app.register_blueprint(auth_bp, url_prefix="/api/auth")
399400
app.register_blueprint(songs_bp, url_prefix="/api/songs")
400401
app.register_blueprint(generate_bp, url_prefix="/api/generate")
402+
reset_generation_queue()
401403
app.register_blueprint(playlists_bp, url_prefix="/api/playlists")
402404
app.register_blueprint(users_bp, url_prefix="/api/users")
403405
app.register_blueprint(contact_bp, url_prefix="/api/contact")

run_local.sh

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/bin/bash
2+
# ---------------------------------------------------------------------------
3+
# AceForge - Run locally for testing (no .app bundle)
4+
# Builds the React UI if needed, then starts the Flask server at http://127.0.0.1:5056
5+
#
6+
# Prerequisites:
7+
# - Python 3.11 and dependencies. Easiest: run ./build_local.sh once to create
8+
# venv_build and install deps; then use this script. Or create a venv and:
9+
# pip install -r requirements_ace_macos.txt (plus TTS/ACE-Step as in build_local.sh).
10+
# - Bun (only if ui/dist is missing): https://bun.sh
11+
#
12+
# Optional:
13+
# ACEFORGE_SKIP_UI_BUILD=1 - Skip UI build; use existing ui/dist/
14+
# ---------------------------------------------------------------------------
15+
16+
set -e
17+
APP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
18+
cd "$APP_DIR"
19+
20+
# Build UI if needed
21+
UI_DIR="${APP_DIR}/ui"
22+
if [ -f "$UI_DIR/package.json" ] && [ -z "${ACEFORGE_SKIP_UI_BUILD}" ]; then
23+
if [ ! -f "$UI_DIR/dist/index.html" ]; then
24+
if ! command -v bun &> /dev/null; then
25+
echo "ERROR: ui/dist missing and Bun not found. Install Bun (https://bun.sh) or run: ./scripts/build_ui.sh"
26+
exit 1
27+
fi
28+
echo "[Run] Building UI..."
29+
"${APP_DIR}/scripts/build_ui.sh"
30+
fi
31+
fi
32+
33+
# Prefer venv from full build
34+
VENV_PY="${APP_DIR}/venv_build/bin/python"
35+
if [ -x "$VENV_PY" ]; then
36+
PY="$VENV_PY"
37+
echo "[Run] Using venv_build"
38+
else
39+
PY="python3"
40+
echo "[Run] Using system python3 (install deps in a venv if you see ModuleNotFoundError)"
41+
fi
42+
43+
echo "[Run] Starting AceForge at http://127.0.0.1:5056"
44+
echo ""
45+
exec "$PY" music_forge_ui.py

0 commit comments

Comments
 (0)