From 50d958f418cd09b70bad43bdf43fd1a1aac10df2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 16:30:20 +0000 Subject: [PATCH 1/2] Initial plan From 1cde892e2118f2c5ed39e45948b9bf4e2a27370c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 16:43:18 +0000 Subject: [PATCH 2/2] Fix model paths and cover mode for acestep-cpp spawn mode Co-authored-by: lmangani <1423657+lmangani@users.noreply.github.com> --- server/src/config/index.ts | 29 ++++++++++++++++++++--------- server/src/services/acestep.ts | 30 ++++++++++++++++++++++++------ 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/server/src/config/index.ts b/server/src/config/index.ts index 37b727b..ffbb3e3 100644 --- a/server/src/config/index.ts +++ b/server/src/config/index.ts @@ -13,11 +13,22 @@ const __dirname = path.dirname(__filename); // release: /server/dist/config/ → ../../.. → / const APP_ROOT = path.resolve(__dirname, '../../..'); +// ── Path helpers ───────────────────────────────────────────────────────────── + +/** + * Resolves a path relative to APP_ROOT when it is not already absolute. + * This prevents relative paths from .env (e.g. `./models`) being interpreted + * relative to the server/ working directory instead of the project root. + */ +function resolveFromRoot(p: string): string { + return path.isAbsolute(p) ? p : path.resolve(APP_ROOT, p); +} + // ── Binary resolution ─────────────────────────────────────────────────────── /** Resolves the ace-qwen3 LLM binary path (step 1 of the pipeline). */ function resolveLmBin(): string { - if (process.env.ACE_QWEN3_BIN) return process.env.ACE_QWEN3_BIN; + if (process.env.ACE_QWEN3_BIN) return resolveFromRoot(process.env.ACE_QWEN3_BIN); for (const name of ['ace-qwen3', 'ace-qwen3.exe']) { const p = path.join(APP_ROOT, 'bin', name); if (existsSync(p)) return p; @@ -27,7 +38,7 @@ function resolveLmBin(): string { /** Resolves the dit-vae binary path (step 2 of the pipeline). */ function resolveDitVaeBin(): string { - if (process.env.DIT_VAE_BIN) return process.env.DIT_VAE_BIN; + if (process.env.DIT_VAE_BIN) return resolveFromRoot(process.env.DIT_VAE_BIN); for (const name of ['dit-vae', 'dit-vae.exe']) { const p = path.join(APP_ROOT, 'bin', name); if (existsSync(p)) return p; @@ -39,13 +50,13 @@ function resolveDitVaeBin(): string { /** Resolves the models directory. */ function resolveModelsDir(): string { - if (process.env.MODELS_DIR) return process.env.MODELS_DIR; + if (process.env.MODELS_DIR) return resolveFromRoot(process.env.MODELS_DIR); return path.join(APP_ROOT, 'models'); } /** Resolves the DiT model (acestep-v15-turbo-*.gguf). */ function resolveDitModel(modelsDir: string): string { - if (process.env.ACESTEP_MODEL) return process.env.ACESTEP_MODEL; + if (process.env.ACESTEP_MODEL) return resolveFromRoot(process.env.ACESTEP_MODEL); if (!existsSync(modelsDir)) return ''; const preference = [ @@ -73,7 +84,7 @@ function resolveDitModel(modelsDir: string): string { /** Resolves the causal LM model (acestep-5Hz-lm-*.gguf). */ function resolveLmModel(modelsDir: string): string { - if (process.env.LM_MODEL) return process.env.LM_MODEL; + if (process.env.LM_MODEL) return resolveFromRoot(process.env.LM_MODEL); if (!existsSync(modelsDir)) return ''; // Prefer 4B Q8_0, then smaller quantisations, then smaller LM sizes @@ -103,7 +114,7 @@ function resolveLmModel(modelsDir: string): string { /** Resolves the text-encoder model (Qwen3-Embedding-*.gguf). */ function resolveTextEncoderModel(modelsDir: string): string { - if (process.env.TEXT_ENCODER_MODEL) return process.env.TEXT_ENCODER_MODEL; + if (process.env.TEXT_ENCODER_MODEL) return resolveFromRoot(process.env.TEXT_ENCODER_MODEL); if (!existsSync(modelsDir)) return ''; for (const name of [ @@ -125,7 +136,7 @@ function resolveTextEncoderModel(modelsDir: string): string { /** Resolves the VAE model (vae-BF16.gguf). */ function resolveVaeModel(modelsDir: string): string { - if (process.env.VAE_MODEL) return process.env.VAE_MODEL; + if (process.env.VAE_MODEL) return resolveFromRoot(process.env.VAE_MODEL); if (!existsSync(modelsDir)) return ''; for (const name of ['vae-BF16.gguf', 'vae-Q8_0.gguf']) { @@ -174,7 +185,7 @@ export const config = { // SQLite database database: { - path: process.env.DATABASE_PATH || path.join(APP_ROOT, 'data', 'acestep.db'), + path: resolveFromRoot(process.env.DATABASE_PATH || path.join(APP_ROOT, 'data', 'acestep.db')), }, // acestep-cpp — spawn mode uses ace-qwen3 + dit-vae directly. @@ -204,7 +215,7 @@ export const config = { storage: { provider: 'local' as const, - audioDir: process.env.AUDIO_DIR || path.join(APP_ROOT, 'public', 'audio'), + audioDir: resolveFromRoot(process.env.AUDIO_DIR || path.join(APP_ROOT, 'public', 'audio')), }, jwt: { diff --git a/server/src/services/acestep.ts b/server/src/services/acestep.ts index 1c5522e..6259582 100644 --- a/server/src/services/acestep.ts +++ b/server/src/services/acestep.ts @@ -130,9 +130,20 @@ let isProcessingQueue = false; // Mode detection // --------------------------------------------------------------------------- -function useSpawnMode(): boolean { - // Spawn mode requires both ace-qwen3 (LLM) and dit-vae (synthesis) binaries - return Boolean(config.acestep.lmBin && config.acestep.ditVaeBin); +/** + * Returns true when the spawn-mode binaries satisfy the requirements for + * the given generation parameters. + * + * - Cover/passthrough (sourceAudioUrl or audioCodes): only dit-vae is needed; + * ace-qwen3 is skipped because the audio codes come from the source audio. + * - Text-to-music: both ace-qwen3 (LLM) and dit-vae (synthesis) are required. + */ +function useSpawnMode(params?: Pick): boolean { + if (!config.acestep.ditVaeBin) return false; + // Cover / passthrough: only dit-vae is needed — no LLM step + if (params?.sourceAudioUrl || params?.audioCodes) return true; + // Text-to-music: need ace-qwen3 too + return Boolean(config.acestep.lmBin); } // --------------------------------------------------------------------------- @@ -259,14 +270,20 @@ async function runViaSpawn( if (params.timeSignature) requestJson.timesignature = params.timeSignature; // Passthrough: skip the LLM when audio codes are already provided if (params.audioCodes) requestJson.audio_codes = params.audioCodes; + // Cover/audio-to-audio: strength of the source audio influence on the output + if (params.audioCoverStrength !== undefined) requestJson.audio_cover_strength = params.audioCoverStrength; const requestPath = path.join(tmpDir, 'request.json'); await writeFile(requestPath, JSON.stringify(requestJson, null, 2)); // ── Step 1: ace-qwen3 — LLM (lyrics + audio codes) ──────────────────── + // Skipped when: + // • audio_codes are provided (passthrough) — codes are already known + // • sourceAudioUrl is provided (cover/audio-to-audio) — dit-vae derives + // codes directly from the source audio; running ace-qwen3 is not needed let enrichedPaths: string[] = []; - if (!params.audioCodes) { + if (!params.audioCodes && !params.sourceAudioUrl) { job.stage = 'LLM: generating lyrics and audio codes…'; const lmBin = config.acestep.lmBin!; @@ -294,7 +311,8 @@ async function runViaSpawn( throw new Error('ace-qwen3 produced no enriched request files'); } } else { - // Passthrough: use the original request.json directly (audio_codes present) + // Passthrough: use the original request.json directly + // (audio codes provided, or source audio supplied for cover/audio-to-audio mode) enrichedPaths = [requestPath]; } @@ -619,7 +637,7 @@ async function processGeneration( try { job.stage = 'Generating music...'; - if (useSpawnMode()) { + if (useSpawnMode(params)) { await runViaSpawn(jobId, params, job); } else { await runViaHttp(jobId, params, job);