Guide: lexigram-multimedia-music
Learn how to use the music generation subsystem effectively.
Overview
Section titled “Overview”lexigram-multimedia-music turns a text prompt into audio — full songs, instrumental tracks, FX, and ambient sound. It is a backend-agnostic layer: your code talks to the MusicProvider contract, and which backend answers is decided by MusicConfig.backend.
It is one of the lexigram-multimedia-* subsystems. The umbrella package lexigram-multimedia auto-discovers it through the lexigram.multimedia.subsystems entry-point group; you can also use it standalone via AudioMusicModule.
When to use it
Section titled “When to use it”- You need generated background music, jingles, or SFX in an application.
- You want one code path that can switch between a self-hosted model and a hosted API later.
- You want generation to degrade gracefully (
Result[MediaAsset, MusicGenerationError], never a thrown exception for domain failures).
When not to use it
Section titled “When not to use it”- Speech synthesis →
lexigram-multimedia-tts. - Beat/tempo analysis of existing audio →
lexigram-multimedia-beat. - Image/video generation → the sibling
lexigram-multimedia-image/lexigram-multimedia-videopackages.
Core Concepts
Section titled “Core Concepts”MusicProvider— the structural protocol (fromlexigram-contracts). Implementations exposeasync generate(request: MusicRequest) -> Result[MediaAsset, MultimediaError]. Every backend in this package satisfies it.MusicRequest— the frozen request value:prompt,duration_seconds(default30.0),format(default"mp3"), andextra— the escape hatch for backend-specific knobs.MediaAsset— the frozen result value:mime_type,provider,bytes_dataand/oruri. Always checkhas_bytes/has_uribefore consuming.MusicGenerationError— the package’s error family (leaf ofMultimediaError, codeLEX_ERR_MM_003). Failures are returned insideErr(...), not raised.AudioMusicProvider— the DI provider (name"music") that readsMusicConfig, builds the right backend, and registers it in the container.- Reference servers — small
aiohttpservers that load a model once at startup and serve/generate+/health. They are the deployment target for the local backends.
Typical Usage
Section titled “Typical Usage”Connectionless zero-config flow
Section titled “Connectionless zero-config flow”import asyncio
from lexigram import Applicationfrom lexigram.contracts.multimedia import MusicProvider, MusicRequestfrom lexigram.di.module import Module, modulefrom lexigram.multimedia.music import AudioMusicModule
@module(imports=[AudioMusicModule.configure()])class AppModule(Module): pass
async def demo() -> None: async with Application.boot(modules=[AppModule]) as app: music: MusicProvider = await app.container.resolve(MusicProvider) result = await music.generate( MusicRequest(prompt="dark ambient drone, 60 bpm", duration_seconds=45.0) ) match result: case _ if result.is_ok(): asset = result.unwrap() print(asset.provider, asset.mime_type) case _: print(f"generation failed: {result.unwrap_err()}")
asyncio.run(demo())What is happening:
AudioMusicModule.configure()with no argument usesMusicConfig()→backend="local-http"→LocalHttpMusicProviderpointed athttp://localhost:5003.- One
POST {base_url}/generateships{prompt, duration_seconds, format}and the response body becomesMediaAsset.bytes_data. - Because the API returns
Result, the caller decides how to handle failure — the backend never raises for a non-200 or a timeout.
Selecting a different backend
Section titled “Selecting a different backend”from lexigram.multimedia.music import AudioMusicModulefrom lexigram.multimedia.music.config import MusicConfig
module = AudioMusicModule.configure( config=MusicConfig(backend="ace-step", timeout=120.0))Build the config from YAML instead by adding a multimedia: music: (or multimedia_music:) section to application.yaml — the provider’s config_key is "multimedia_music".
Common Patterns
Section titled “Common Patterns”Pattern: One codebase, many engines
Section titled “Pattern: One codebase, many engines”The same MusicProvider resolution works for every backend, so deployment decides the engine, not your code:
multimedia_music: backend: "stable-audio-open" # or local-http | ace-step | stability-audio stable_audio_open_base_url: "http://192.168.1.20:5301"When you later need a hosted API, swap backend — no call-site changes.
Pattern: Structured generation with ACE-Step
Section titled “Pattern: Structured generation with ACE-Step”AceStepMusicProvider reads the ACE-Step-specific vocabulary from extra: tags (style keywords) and lyrics. Empty/absent lyrics means instrumental-only; non-empty lyrics produces vocals.
request = MusicRequest( prompt="an uplifting synthwave track", duration_seconds=90.0, extra={"tags": "synthwave, driving, uplifting", "lyrics": ""},)Pattern: Async job execution
Section titled “Pattern: Async job execution”The container also exposes MusicGenerationTask — a lexigram-tasks-compatible handler whose run(params) returns a JSON-serializable dict (never raw bytes), keeping the job result store happy:
task: MusicGenerationTask = await app.container.resolve(MusicGenerationTask)job_result = await task.run( {"prompt": "jingle for the launch video", "duration_seconds": 15.0})# -> {"provider": ..., "mime_type": ..., "bytes_data": ..., "uri": ..., "metadata": ...}Pattern: Resilience without code changes
Section titled “Pattern: Resilience without code changes”If the container has RetryPolicyProtocol and CircuitBreakerProtocol registered (e.g. from lexigram-resilience), the provider resolves them during register() and every backend automatically executes its HTTP call through retry.execute(circuit_breaker.call, ...). No backend code changes; the injected retry/circuit-breaker are just wired in.
Integration
Section titled “Integration”lexigramcore —Application.boot(), the provider lifecycle (register→boot), container singleton bindings forMusicConfig,MusicProvider, andMusicGenerationTask.lexigram-contracts—MusicProviderprotocol,MusicRequest/MediaAssetvalue types, and the error family (MultimediaError→MusicGenerationError,ProviderNotInstalledError). Defined inlexigram.contracts.multimedia.lexigram-resilience— optional; injectsRetryPolicyProtocol+CircuitBreakerProtocolinto every backend automatically.- Secrets / storage — the provider currently needs no secrets; when a hosted backend (e.g. Stability Audio) lands, expect its key resolved via
AsyncSecretStoreProtocol— the option is already wired inAudioMusicProvider(viaresolve_optional). lexigram-multimediaumbrella — auto-discovery: the package registerslexigram.multimedia.subsystems: musicandlexigram.multimedia.modules: musicentry points, so installing it next to the umbrella lights up music capabilities with zero extra wiring.
Best Practices
Section titled “Best Practices”- ✅ Use
backend="local-http"for development; it needs no model download and no API keys. - ✅ Resolve
MusicProviderfrom the container — never instantiate a backend class manually. - ✅ Check
result.is_ok()/result.unwrap_err()and handle both cases explicitly. - ✅ Run reference servers in a dedicated venv (
pip install "lexigram-multimedia-music[ace-step-server]") so torch weights stay out of your app process. - ✅ Give prompts concrete style targets (
"upbeat synthwave, 120 bpm") for more consistent output. - ✅ Keep generated output out of
bytes_datawhen you persist it — useMusicGenerationTaskand let storage handle the bytes. - ❌ Don’t call
result.unwrap()blindly — it raises onErr. - ❌ Don’t use
backend="stability-audio"— it raisesProviderNotInstalledErrorat registration (“not yet implemented”); contribute an implementation instead. - ❌ Don’t set a per-request timeout smaller than
MusicConfig.timeout— long generations are the norm, not the exception. - ❌ Don’t run heavy model servers inside the app container; keep them out-of-process and check
/healthbefore relying on them.
Next Steps
Section titled “Next Steps”- How-Tos — task-oriented recipes
- Configuration — every config key
- Architecture — internal design and extension points
- Troubleshooting — common failures and fixes