# Architecture How SongForge-MCP turns a style description and lyrics into a finished track. ## Overview ``` ┌──────────────┐ stdio ┌───────────────────┐ browser automation ┌──────────────┐ │ MCP Client │◄────────────►│ SongForge-MCP │◄───────────────────────►│ ACE-Step │ │(AI assistant)│ (JSON-RPC) │ FastMCP │ (Playwright) │ 1.5 server │ └──────────────┘ └───────────────────┘ └──────────────┘ │ │ subprocess ▼ ┌───────────────────┐ │ audio-separator │ │ (stem separation) │ └───────────────────┘ ``` ## Why browser automation, not a direct API call ACE-Step 1.5 exposes a Gradio web interface backed by a generation endpoint that takes roughly seventy positional parameters, most of them unlabeled in its own API schema. Reconstructing correct values for all of them from the underlying source code was attempted directly three separate times and produced unusable (static/noise) output every time, even after independently fixing two real environment issues along the way — the failure was in the reconstructed defaults themselves, not the environment. Driving ACE-Step's actual web interface programmatically instead — the same interface a person would use, via [Playwright](https://playwright.dev/) — sidesteps the problem entirely: every field this server doesn't explicitly set keeps the interface's own real, working default. This is the reason `generate_vocal_track`'s `advanced_settings` parameter takes exact UI field labels rather than a fixed parameter list — it's the same principle applied to intentional overrides. `songforge_mcp/acestep_client.py` owns this integration, including two reliability behaviors that were not obvious from a single successful run: - A short delay is enforced after clicking "Generate" before anything else happens. Navigating away immediately can abort the request on the server side before it is fully registered. - The completion timeout is generous rather than tuned to the common case — supplying reference audio measurably shifts ACE-Step onto a slower internal code path, and a timeout sized for the fast path fails the slow one. ## What reference audio actually conditions on ACE-Step's own UI text describes reference audio as controlling "vocal timbre, mixing style, and overall atmosphere" — implying a narrow, voice-only signal. Tracing the actual mechanism in ACE-Step's source tells a different story: `infer_refer_latent()` in `acestep/core/generation/handler/conditioning_embed.py` runs the reference clip through `tiled_encode` — the same VAE audio encoder used for real audio elsewhere in the pipeline — producing `refer_audio_acoustic_hidden_states` that the diffusion model is conditioned on directly. That's a real acoustic latent of the reference clip's actual sound, not a narrow speaker/timbre embedding. Practically, this means a reference clip's instrumentation and production texture (not just its vocal character) plausibly influences the generation too — this project's own docs previously claimed otherwise, copying ACE-Step's UI description without verifying it against the code. What's still unconfirmed: exactly how strongly this acoustic conditioning competes against the `caption` text's own description when they pull in different directions (e.g. a caption naming one instrument against a reference clip dominated by another). That would need an actual side-by-side listening comparison, which hasn't been run in this project. Until it has, `generate_vocal_track`'s own docs treat `caption` as the reliable lever for a specific instrument being prominent, and reference audio as secondary reinforcement rather than a guarantee. ## Why output_format needs its own Settings-panel dance `output_format` (default `"wav"`, overriding ACE-Step's own `"mp3"` UI default) can't be set the same way as a normal `advanced_settings` field. Reading ACE-Step's own source (`acestep/ui/gradio/interfaces/user_preferences.py` and `generation_advanced_output_controls.py`) showed the Audio Format control is a global, `localStorage`-persisted user preference living in the Settings panel (behind a collapsed "Audio Output & Post-processing" accordion), not a field on the per-generation form — and it isn't a native `