Add dual ACE-Step music studio interfaces

This commit is contained in:
Mikei386
2026-09-08 18:46:54 +02:00
parent f58d61140e
commit a4e894fe70
12 changed files with 404 additions and 23 deletions
@@ -0,0 +1,38 @@
FROM node:22-bookworm AS build
ARG ACE_STEP_UI_COMMIT
RUN test -n "$ACE_STEP_UI_COMMIT"
RUN apt-get update \
&& apt-get install -y --no-install-recommends git python3 make g++ \
&& rm -rf /var/lib/apt/lists/*
RUN git clone https://github.com/fspecii/ace-step-ui.git /src \
&& cd /src \
&& git checkout --detach "$ACE_STEP_UI_COMMIT"
COPY patch-source.mjs /tmp/patch-source.mjs
RUN node /tmp/patch-source.mjs /src
RUN cd /src \
&& npm ci \
&& npm run build
RUN cd /src/server \
&& npm ci \
&& npm run build \
&& npm prune --omit=dev
FROM node:22-bookworm-slim AS runtime
RUN apt-get update \
&& apt-get install -y --no-install-recommends nginx curl ca-certificates ffmpeg \
&& rm -rf /var/lib/apt/lists/*
COPY --from=build /src/dist /usr/share/nginx/html
COPY --from=build /src/server/dist /app/server/dist
COPY --from=build /src/server/node_modules /app/server/node_modules
COPY --from=build /src/server/package.json /app/server/package.json
COPY --from=build /src/server/public /app/server/public
COPY --from=build /src/server/audio-editor /app/server/audio-editor
COPY nginx.conf /etc/nginx/nginx.conf
COPY entrypoint.sh /usr/local/bin/ace-step-ui-entrypoint
RUN chmod 0755 /usr/local/bin/ace-step-ui-entrypoint \
&& mkdir -p /data/audio /data/datasets/uploads
EXPOSE 3000 3001
ENTRYPOINT ["/usr/local/bin/ace-step-ui-entrypoint"]
@@ -0,0 +1,18 @@
#!/bin/sh
set -eu
node /app/server/dist/index.js &
server_pid=$!
trap 'kill "$server_pid" 2>/dev/null || true' INT TERM EXIT
nginx -g 'daemon off;' &
nginx_pid=$!
while kill -0 "$server_pid" 2>/dev/null && kill -0 "$nginx_pid" 2>/dev/null; do
sleep 1
done
kill "$server_pid" "$nginx_pid" 2>/dev/null || true
wait "$server_pid" 2>/dev/null || true
wait "$nginx_pid" 2>/dev/null || true
exit 1
@@ -0,0 +1,35 @@
worker_processes auto;
pid /tmp/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
client_max_body_size 512m;
server {
listen 3000;
server_name _;
root /usr/share/nginx/html;
index index.html;
location ~ ^/(api|audio|editor|blog|demucs-web)(/|$) {
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_read_timeout 1800s;
proxy_send_timeout 1800s;
}
location / {
try_files $uri $uri/ /index.html;
}
}
}
@@ -0,0 +1,187 @@
import fs from 'node:fs';
import path from 'node:path';
const root = process.argv[2];
if (!root) throw new Error('source root argument is required');
function patch(relativePath, transform) {
const filename = path.join(root, relativePath);
const before = fs.readFileSync(filename, 'utf8');
const after = transform(before);
if (after === before) throw new Error(`patch made no change: ${relativePath}`);
fs.writeFileSync(filename, after);
}
function replaceOnce(text, before, after, label) {
const first = text.indexOf(before);
if (first < 0) throw new Error(`patch anchor missing: ${label}`);
if (text.indexOf(before, first + 1) >= 0) throw new Error(`patch anchor repeated: ${label}`);
return text.slice(0, first) + after + text.slice(first + before.length);
}
patch('server/src/services/acestep.ts', (text) => {
text = replaceOnce(
text,
"const AUDIO_DIR = path.join(__dirname, '../../public/audio');",
'const AUDIO_DIR = config.storage.audioDir;',
'persistent generated audio',
);
const oldArgs = ` params.instruction || 'Fill the audio semantic mask with the style described in the text prompt.', // 17: Instruction
params.audioCoverStrength ?? 1.0, // 18: Audio Cover Strength
0.0, // 19: Cover Noise Strength (ACE-Step v1.5 new param, default 0.0)
(params.taskType === 'audio2audio' ? 'cover' : params.taskType) || 'text2music', // 20: Task Type
params.useAdg ?? false, // 21: Use ADG
params.cfgIntervalStart ?? 0.0, // 22: CFG Interval Start
params.cfgIntervalEnd ?? 1.0, // 23: CFG Interval End
params.shift ?? 3.0, // 24: Shift
params.inferMethod || 'ode', // 25: Inference Method
params.customTimesteps || '', // 26: Custom Timesteps
params.audioFormat || 'mp3', // 27: Audio Format
params.lmTemperature ?? 0.85, // 28: LM Temperature
isThinking, // 29: Think
params.lmCfgScale ?? 2.0, // 30: LM CFG Scale
params.lmTopK ?? 0, // 31: LM Top-K
params.lmTopP ?? 0.9, // 32: LM Top-P
params.lmNegativePrompt || 'NO USER INPUT', // 33: LM Negative Prompt
useCot ? (params.useCotMetas ?? true) : false, // 34: CoT Metas
useCot ? (params.useCotCaption ?? true) : false, // 35: CaptionRewrite
useCot ? (params.useCotLanguage ?? true) : false, // 36: CoT Language
params.isFormatCaption ?? false, // 37: Is Format Caption State
params.constrainedDecodingDebug ?? false, // 38: Constrained Decoding Debug
params.allowLmBatch ?? true, // 39: ParallelThinking
params.getScores ?? false, // 40: Auto Score
params.getLrc ?? false, // 41: Auto LRC (timestamped lyrics)
params.scoreScale ?? 0.5, // 42: Quality Score Sensitivity (0.01-1.0)
params.lmBatchChunkSize ?? 8, // 43: LM Batch Chunk Size
params.trackName || null, // 44: Track Name
params.completeTrackClasses || [], // 45: Track Names
true, // 46: Enable Normalization (ACE-Step v1.5, default true)
-1.0, // 47: Normalization DB (ACE-Step v1.5, default -1.0)
0.0, // 48: Latent Shift (ACE-Step v1.5, default 0.0)
1.0, // 49: Latent Rescale (ACE-Step v1.5, default 1.0)
params.autogen ?? false, // 50: AutoGen`;
const newArgs = ` params.instruction || 'Fill the audio semantic mask based on the given conditions:', // 17: Instruction
params.audioCoverStrength ?? 1.0, // 18: LM Codes Strength
0.0, // 19: Cover Strength
false, // 20: no_fsq
params.useAdg ?? false, // 21: Use ADG
params.cfgIntervalStart ?? 0.0, // 22: CFG Interval Start
params.cfgIntervalEnd ?? 1.0, // 23: CFG Interval End
params.shift ?? 1.0, // 24: Shift
params.inferMethod || 'ode', // 25: Inference Method
'euler', // 26: Sampler Mode
0.0, // 27: Velocity Norm Threshold
0.0, // 28: Velocity EMA Factor
false, // 29: Enable DCW
'double', // 30: DCW Mode
0.02, // 31: DCW Scaler
0.06, // 32: DCW High Scaler
'haar', // 33: DCW Wavelet
params.customTimesteps || '', // 34: Custom Timesteps
params.audioFormat || 'flac', // 35: Audio Format
'320k', // 36: MP3 Bitrate
48000, // 37: MP3 Sample Rate
params.lmTemperature ?? 0.85, // 38: LM Temperature
isThinking, // 39: Think
params.lmCfgScale ?? 2.0, // 40: LM CFG Scale
params.lmTopK ?? 0, // 41: LM Top-K
params.lmTopP ?? 0.9, // 42: LM Top-P
params.lmNegativePrompt || 'NO USER INPUT', // 43: LM Negative Prompt
useCot ? (params.useCotMetas ?? true) : false, // 44: CoT Metas
useCot ? (params.useCotCaption ?? true) : false, // 45: CaptionRewrite
useCot ? (params.useCotLanguage ?? true) : false, // 46: CoT Language
params.constrainedDecodingDebug ?? false, // 47: Constrained Decoding Debug
params.allowLmBatch ?? true, // 48: ParallelThinking
params.getScores ?? false, // 49: Auto Score
params.getLrc ?? false, // 50: Auto LRC
params.scoreScale ?? 0.5, // 51: Quality Score Sensitivity
params.lmBatchChunkSize ?? 8, // 52: LM Batch Chunk Size
params.trackName || null, // 53: Track Name
params.completeTrackClasses || [], // 54: Track Names
true, // 55: Enable Normalization
-1.0, // 56: Target Peak (dB)
0.0, // 57: Fade In
0.0, // 58: Fade Out
0.0, // 59: Latent Shift
1.0, // 60: Latent Rescale
'balanced', // 61: Repaint Mode
0.5, // 62: Repaint Strength
0.0, // 63: variance
'', // 64: repaint seed
false, // 65: Edit
null, // 66: source caption
null, // 67: source lyrics
0.0, // 68: n_min
1.0, // 69: n_max
1, // 70: n_avg
params.autogen ?? false, // 71: AutoGen`;
return replaceOnce(text, oldArgs, newArgs, 'Athena Gradio argument schema');
});
patch('server/src/services/storage/local.ts', (text) => {
text = replaceOnce(
text,
"import type { StorageProvider } from './index.js';",
"import type { StorageProvider } from './index.js';\nimport { config } from '../../config/index.js';",
'storage config import',
);
return replaceOnce(
text,
"const AUDIO_DIR = path.join(__dirname, '../../../public/audio');",
'const AUDIO_DIR = config.storage.audioDir;',
'persistent uploaded audio',
);
});
patch('server/src/index.ts', (text) => replaceOnce(
text,
"app.use('/audio', express.static(path.join(__dirname, '../public/audio')));",
"app.use('/audio', express.static(config.storage.audioDir));",
'persistent audio static route',
));
patch('server/src/routes/referenceTrack.ts', (text) => {
text = replaceOnce(
text,
"import { spawn } from 'child_process';",
"import { spawn } from 'child_process';\nimport { config } from '../config/index.js';",
'reference audio config import',
);
return replaceOnce(
text,
"const AUDIO_DIR = path.join(__dirname, '../../public/audio');",
'const AUDIO_DIR = config.storage.audioDir;',
'persistent reference audio',
);
});
patch('server/src/routes/generate.ts', (text) => {
text = replaceOnce(
text,
" const ALL_DIT_MODELS = [\n 'acestep-v15-turbo',",
" const ALL_DIT_MODELS = [\n 'acestep-v15-xl-sft', // Athena production model\n 'acestep-v15-turbo',",
'XL-SFT model list',
);
const start = text.indexOf("router.get('/limits'");
const end = text.indexOf("router.get('/debug/", start);
if (start < 0 || end < 0) throw new Error('limits route anchors missing');
const limits = `router.get('/limits', async (_req, res: Response) => {
// The UI container intentionally has no CUDA or ACE-Step Python runtime.
// These are the limits reported by Athena's dedicated RTX 5080 worker.
res.json({
tier: process.env.ACESTEP_TIER || 'tier5',
gpu_memory_gb: Number(process.env.ACESTEP_GPU_MEMORY_GB || 15.5),
max_duration_with_lm: Number(process.env.ACESTEP_MAX_DURATION_WITH_LM || 480),
max_duration_without_lm: Number(process.env.ACESTEP_MAX_DURATION_WITHOUT_LM || 600),
max_batch_size_with_lm: Number(process.env.ACESTEP_MAX_BATCH_WITH_LM || 4),
max_batch_size_without_lm: Number(process.env.ACESTEP_MAX_BATCH_WITHOUT_LM || 4),
});
});
`;
return text.slice(0, start) + limits + text.slice(end);
});