Data Flow for Music Generation in ACE-Step UI: Complete Technical Guide

The data flow in ACE-Step UI moves from React frontend parameter collection through an Express backend to a Python ACE-Step engine via Gradio, using SQLite for asynchronous job tracking and status polling.

Understanding the data flow for music generation in ACE-Step UI requires examining how the React frontend, Node.js/Express backend, and Python inference engine communicate. The application bridges these technologies through a REST API layer and Gradio client calls, handling everything from initial parameter validation to final audio file delivery.

Architecture Overview

ACE-Step UI operates as a three-tier system. The React frontend (components/CreatePanel.tsx) collects user parameters and manages UI state. A TypeScript API layer (services/api.ts) handles HTTP communication. The Node/Express backend (server/src/routes/generate.ts) processes requests, manages the SQLite job queue, and interfaces with the ACE-Step Python engine via Gradio endpoints. Optional Gemini integration provides AI-enhanced lyric and metadata generation before the main pipeline executes.

Step-by-Step Data Flow

Frontend Parameter Collection

The process begins in components/CreatePanel.tsx, where user inputs for lyrics, style, duration, and LoRA settings are gathered into local state. When the user clicks Generate, the component calls generateApi.startGeneration at approximately line 800, passing a GenerationParams object and authentication token.

// components/CreatePanel.tsx triggers the pipeline
const job = await generateApi.startGeneration(
  {
    customMode: false,
    songDescription: caption,
    lyrics,
    style,
    duration,
    // additional parameters
  },
  authToken
);

API Request Initialization

The generateApi service in services/api.ts (lines 33-35) wraps a POST request to /api/generate. This thin abstraction layer standardizes request headers, error handling, and JWT token injection across the application.

// services/api.ts - Frontend API wrapper
export const generateApi = {
  startGeneration: (params, token) =>
    api('/api/generate', { method: 'POST', body: params, token }),
  
  getStatus: (jobId, token) =>
    api(`/api/generate/status/${jobId}`, { token })
};

Express Route Handling

The Express route in server/src/routes/generate.ts receives the POST request at its root endpoint. The handler first normalizes the payload using the autoTitle function (lines 25-46) to generate a title if the user left that field empty. After validation, it invokes generateMusicViaAPI from the ACE-Step service layer and returns a jobId to the client immediately.

// server/src/routes/generate.ts - Entry point
router.post('/', authMiddleware, async (req, res) => {
  const params = req.body as GenerationParams;
  const title = autoTitle(params);  // Auto-generate title if missing
  const { jobId } = await generateMusicViaAPI({ ...params, title });
  res.json({ jobId });
});

Gradio Integration and Python Execution

The core bridge to the AI model resides in server/src/services/acestep.ts. The buildGradioArgs function (lines 30-100) constructs a 51-element argument array expected by the Gradio /generation_wrapper endpoint. This includes resolving uploaded audio files via prepareAudioFile, injecting LLM-enhancement flags, and mapping UI parameters to the Python script's positional arguments.

The service then either uses a Gradio client singleton or spawns the Python script simple_generate.py via the PYTHON_SCRIPT environment variable. The Python backend executes the diffusion model with optional LoRA fine-tuning and cover/repaint modes, writing resulting audio files to the public/audio/ directory (as defined by AUDIO_DIR at lines 27-29).

// server/src/services/acestep.ts - Bridge to Python backend
const args = await buildGradioArgs(params);      // 51 positional arguments
const client = await getGradioClient();        // Gradio client singleton
const result = await client.predict(...args);    // Calls /generation_wrapper

Job Tracking and Status Polling

Since generation is asynchronous, generateMusicViaAPI generates a UUID (localJobId), stores the job record in SQLite, and returns immediately. The frontend polls /api/generate/status/:jobId using generateApi.getStatus (lines 37-39 in services/api.ts) to receive progress updates (pending, running, or succeeded).

Result Delivery and UI Updates

Upon completion, the backend constructs a Song object containing audio URLs, BPM, key, and metadata. The getAudioUrl utility (lines 4-15 in services/api.ts) normalizes absolute file paths into relative /audio/<file> URLs that work through Vite's development proxy. App.tsx (lines 705-800) listens for completion events, updates the Song List and Player components, and optionally triggers the Share modal.

// services/api.ts - URL normalization for frontend
export function getAudioUrl(audioUrl: string, songId?: string) {
  if (!audioUrl) return undefined;
  if (audioUrl.startsWith('/audio/')) return audioUrl;
  return audioUrl; // Preserve absolute URLs
}

Optional AI Enhancement Flow

When users enable AI Enhance, the pipeline includes an additional pre-processing step. Before calling startGeneration, the frontend invokes geminiService.generateSongData (lines 6-24 in services/geminiService.ts) to generate a title, lyrics, and style tags from a user prompt. These values populate the GenerationParams object before the standard HTTP POST to /api/generate.

// services/geminiService.ts - Pre-generation enhancement
const { title, lyrics, tags } = await generateSongData(
  userPrompt,
  selectedStyle
);
// Feed generated values into GenerationParams

Code Implementation Examples

Here is the complete flow from frontend trigger to backend execution:

1. Frontend Request Trigger:

// Initiate generation with full parameter set
const job = await generateApi.startGeneration(
  {
    customMode: true,
    lyrics: "Verse 1: ...",
    style: "Lo-fi hip hop",
    duration: 60,
    loraName: "jazz_fusion_v2"
  },
  authToken
);

2. Backend Argument Construction:

// server/src/services/acestep.ts
async function buildGradioArgs(params: GenerationParams) {
  const audioPath = await prepareAudioFile(params.referenceAudio);
  return [
    params.lyrics,           // arg 0
    params.style,            // arg 1
    params.duration,         // arg 2
    // ... 48 additional arguments
    params.seed || -1        // arg 50
  ];
}

3. Status Polling Pattern:

// Frontend polling implementation
const checkStatus = async () => {
  const status = await generateApi.getStatus(jobId, token);
  if (status.state === 'succeeded') {
    return status.result;
  }
  setTimeout(checkStatus, 2000);
};

Summary

Frequently Asked Questions

How does ACE-Step UI handle long-running generation tasks?

The system uses an asynchronous job queue with SQLite persistence. When generateMusicViaAPI is called, it generates a UUID (localJobId), stores the initial job state in SQLite, and returns immediately to the client. The frontend polls /api/generate/status/:jobId every few seconds using generateApi.getStatus until the Python backend reports completion, preventing HTTP timeout issues during model inference.

What is the significance of the 51-argument array in the Gradio integration?

The buildGradioArgs function in server/src/services/acestep.ts (lines 30-100) constructs a fixed-length positional argument list required by the ACE-Step Gradio endpoint at /generation_wrapper. This array includes lyrics, style tags, duration, seed values, audio reference paths, and boolean flags for features like cover mode and repaint strength. The strict ordering matches the Python function signature in simple_generate.py, ensuring the diffusion model receives parameters in the expected sequence.

How are audio file paths normalized between the backend and frontend?

The getAudioUrl function in services/api.ts (lines 4-15) converts absolute filesystem paths from the Python engine into relative /audio/<filename> URLs. This normalization ensures compatibility with Vite's development proxy and allows the React frontend to stream audio through the Express static file server. The function checks if paths already start with /audio/ to prevent double-prefixing while handling both development and production environments.

Can the generation pipeline function without the Gemini AI Enhancement feature?

Yes, the Gemini integration is entirely optional. The geminiService.generateSongData function (lines 6-24) only executes when users explicitly enable AI Enhance in the UI. The core generation flow—from CreatePanel.tsx through acestep.ts to the Python backend—operates independently using user-provided lyrics and metadata, making the LLM service an optional augmentation rather than a required dependency.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →