StoryMatrix connects seven adapter categories—audio, image, language model, music, sound effects, timeline, and text-to-speech—to replaceable ports; montage remains a service layer.

🗣️ TTS adapters

ProviderCategoryAdapter class · filePort implementedAuth env varBase URL / model defaultCostSelected by (config key = value)Mock counterpart
PiperTTSPiperAdapter · infrastructure/adapters/tts/piper.pyTTSServiceSERVICES__PIPER__PIPER_BINARY = piperLocalproviders.strategy.tts_provider = piperMockTTSAdapter
ElevenLabsTTSElevenLabsAdapter · infrastructure/adapters/tts/elevenlabs.pyTTSServicePROVIDERS__ELEVENLABS__API_KEYProvider config selects its model strategyPaid APIproviders.strategy.tts_provider = elevenlabsMockTTSAdapter
PlayAITTSPlayAIAdapter · infrastructure/adapters/tts/playai.pyTTSServicePROVIDERS__PLAYAI__API_KEYhttps://api.play.ht/api/v2Paid APIConstructed in the TTS services dictionary when local-only is false, but omitted from the orchestrator provider list ([piper, elevenlabs, mock])MockTTSAdapter
CoquiTTSCoquiTTSAdapter · infrastructure/adapters/tts/coqui_adapter.pyTTSAdapterNo auth env var; receives services.coquitts_models/multilingual/multi-dataset/xtts_v2 from CoquiTTSConfigLocal modelConstructed in the TTS services dictionary, but omitted from the orchestrator provider list ([piper, elevenlabs, mock])MockTTSAdapter
MockTTSMockTTSAdapter · infrastructure/adapters/tts/mock_tts_adapter.pyTTSServiceSynthetic outputNo external costproviders.strategy.tts_provider = mock

🚀 Open-source TTS ecosystem watch — 2026-08-04

The table above is the implemented StoryMatrix inventory. The projects below are a dated research watchlist, not installed adapters. “Open source” here refers to the code and/or model terms stated by each project; code, weights, voices, and generated-output terms must all be checked before deployment.

Project and current milestoneLicense postureOfficially documented innovationCPU-only Debian fitStoryMatrix decision
OHF Piper v1.6.0 (2026-07-23)GPL-3.0 engine; verify each voice/modelFast, fully local neural TTS; CLI, Python, and C++ interfaces; current release adds the Nakdimon Hebrew phonemizerProven baseline; x86-64 manylinux wheels and low operational overheadKeep as the offline production default; document the move from the archived MIT-era repository to the maintained GPL fork
Chatterbox family / package v0.1.2 (2025-06-13)MIT code; verify model-card/weight terms; generated audio is watermarkedNano 110M targets on-device use and claims 3× real time on eight CPU cores; Turbo adds one-step decoding and paralinguistic tags; Multilingual V3 covers 23+ languagesHighest-priority bake-off after PiperPrototype a Nano adapter for expressive speech and cloning only after memory, latency, weight-license, and watermark acceptance checks
Kokoro 82MApache-2.0 code and Apache-licensed weightsCompact 82M-parameter, 24 kHz multilingual open-weight modelHigh-priority bake-off; small model, but its reference runtime imports PyTorchBenchmark quality, cold start, peak RAM, and real-time factor against Piper
MeloTTSMITMultilingual synthesis, including Chinese–English mixing; project explicitly documents real-time CPU inferenceStrong CPU fallback candidateBenchmark as a simple multilingual local adapter; it is not the cloning choice
OpenVoice V2 (2024-04)MIT; project states free commercial useZero-shot cross-lingual cloning plus granular voice-style control across six natively supported languagesBenchmark first; moderate local stack with no official CPU throughput claimEvaluate when cloning/style control is required; keep outside the default path until CPU evidence exists
F5-TTS 1.1.22 (2026-07-23)MITFlow-matching Diffusion Transformer, zero-shot reference cloning, chunked inference, multi-style and multi-speaker generationResearch only; official performance evidence is GPU-centric and the PyTorch model is substantially heavier than Piper/KokoroQuality benchmark, not a CPU production default
Fun-CosyVoice 3.0 0.5B (2025-12 model milestone)Apache-2.0Nine languages plus dialects, cross-lingual zero-shot cloning, pronunciation inpainting, instruction control, and bi-streaming text-in/audio-out with claimed latency as low as 150 msResearch only; optimized paths use GPU-oriented vLLM/Triton componentsTrack streaming and directing features; require a native CPU benchmark before adapter work
Qwen3-TTS 0.6B/1.7B (2026-01-22)Apache-2.0Ten languages, three-second cloning, natural-language voice design and prosody control, and hybrid streaming with claimed first-packet latency as low as 97 ms0.6B watch candidate; official setup recommends GPU/FlashAttention 2Benchmark only after a supported CPU path is demonstrated
Spark-TTS 0.5B (2025-03)Apache-2.0Single-stream speech tokens over Qwen2.5, Chinese/English zero-shot cross-lingual cloning, code-switching, and gender/pitch/rate controlResearch only; published throughput is GPU-basedTrack for controllable bilingual narration, not the CPU default
Orpheus 3B multilingual preview (2025-04)Apache-2.0Llama-based zero-shot cloning, emotion tags, and streaming; the project links a no-GPU llama.cpp routeCPU-capable but heavy at 3B parametersExperimental long-form quality bake-off only
VoxCPM2 2B (2026-04)Apache-2.0Tokenizer-free diffusion-autoregressive design, 30 languages, 48 kHz output, voice design, controllable cloning, and streamingNot a strict CPU default; reference requirements specify CUDA, though an on-device llama.cpp-omni path existsWatch the CPU runtime; do not import its CUDA-first stack
StyleTTS2MIT code, but inference uses a GPL dependency; pretrained models impose disclosure/voice-permission conditionsStyle diffusion, speech-language-model adversarial training, and zero-shot speaker adaptationPossible but operationally risky; project notes CPU inference avoids one GPU artifactKeep off the production shortlist until dependency and model-use obligations are accepted
Fish Audio S2 ProNot OSI-open for production: its Research License requires a separate written commercial license and attribution4B Dual-AR model, 80+ languages, natural-language emotion control, multi-speaker/multi-turn synthesis, streaming, and rapid cloningReject for this deployment; CUDA/SGLang/H200-oriented and too large for the strict CPU targetResearch reference only; do not describe it as a deployable FOSS provider

CPU adoption order

  1. Operate now: OHF Piper.
  2. Benchmark next: Chatterbox Nano, Kokoro 82M, MeloTTS, then OpenVoice V2.
  3. Track for future quality/streaming work: F5-TTS, CosyVoice 3.0, Qwen3-TTS 0.6B, Spark-TTS, Orpheus, and VoxCPM2.
  4. Do not adopt without an explicit policy change: StyleTTS2 under its full dependency/model obligations, or Fish Audio under its restrictive commercial terms.

Every bake-off must run on the production-class CPU host and record real-time factor, peak resident memory, cold-start time, language/voice quality, model size, license provenance, and generated-output obligations. GPU latency numbers are not CPU evidence, and a permissive code repository does not automatically make its model weights or voices permissive.

🔎 Audio adapters

ProviderCategoryAdapter class · filePort implementedAuth env varBase URL / model defaultCostSelected by (config key = value)Mock counterpart
ChromaAudioChromaAudioSearchAdapter · infrastructure/adapters/audio/chroma_search.pyAudioSearchServiceChroma DB ./data/chroma_db; all-MiniLM-L6-v2 modelLocalAudio-search wiring selects Chroma when local-only and low-resource flags are falseMockAudioSearchService

The adapter inventory contains 21 concrete classes across these seven directories. Three declared support classes are not adapters, and montage implementations live under infrastructure/services/.

🧠 LLM adapters

ProviderCategoryAdapter class · filePort implementedAuth env varBase URL / model defaultCostSelected by (config key = value)Mock counterpart
OpenRouterLLMOpenRouterAdapter · infrastructure/adapters/llm/openrouter.pyLLMService, StoryPlannerService, StoryDataService, AudioDirectionServicePROVIDERS__OPENROUTER__API_KEYhttps://openrouter.ai/api/v1; configured modelPaid APIThe PROVIDERS__OPENROUTER__API_KEY environment variable is set and APP__DEV_LOCAL_ONLY is false; the selector then uses providers.strategy.llm_providerMockLLMService
OllamaLLMOllamaService · infrastructure/adapters/llm/ollama.pyLLMServicehttp://localhost:57707; model mistral:latestLocalOllamaService is not constructed by ServiceContainer; no providers.story_data_provider branch routes itMockStoryDataService
Query enhancerLLMOpenRouterQueryEnhancer · infrastructure/adapters/llm/query_enhancer.pyLLMQueryEnhancerOpenRouter credentialOpenRouter configurationPaid APIThe container wires ConcreteLLMQueryEnhancer, not this adapter classMockLLMService through the enhancer
Story dataLLMLLMStoryDataAdapter · infrastructure/adapters/llm/llm_story_data_adapter.pyStoryDataServiceProvider-specificWraps an injected LLMServiceProvider-dependentWrapper class exists, but no providers.story_data_provider selector or DI branch constructs itMockStoryDataService

🔊 SFX adapters

ProviderCategoryAdapter class · filePort implementedAuth env varBase URL / model defaultCostSelected by (config key = value)Mock counterpart
Local librarySFXLocalSFXAdapter · infrastructure/adapters/sfx/local.pySFXServiceConfigured local SFX libraryLocalproviders.strategy.sfx_provider = localMockSfxAdapter
FreesoundSFXFreesoundSFXAdapter · infrastructure/adapters/sfx/freesound.pySFXServicePROVIDERS__FREESOUND__API_KEYhttps://freesound.org/apiv2External APIproviders.strategy.sfx_provider = freesoundMockSfxAdapter
ElevenLabsSFXElevenLabsSFXAdapter · infrastructure/adapters/sfx/elevenlabs.pySFXServicePROVIDERS__ELEVENLABS__API_KEYElevenLabs SFX endpointPaid APIproviders.strategy.sfx_provider = elevenlabsMockSfxAdapter

🎼 Music adapters

ProviderCategoryAdapter class · filePort implementedAuth env varBase URL / model defaultCostSelected by (config key = value)Mock counterpart
Local libraryMusicLocalMusicAdapter · infrastructure/adapters/music/local.pyMusicDiscoveryServiceConfigured local asset libraryLocalproviders.strategy.music_provider = localMockMusicAdapter
MusicGenMusicMusicGenAdapter · infrastructure/adapters/music/musicgen_adapter.pyMusicDiscoveryServiceMUSICGEN_API_KEY when configuredLocal MusicGen modelCompute-onlyproviders.strategy.music_provider = musicgenMockMusicAdapter
MockMusicMockMusicAdapter · infrastructure/adapters/music/mock.pyMusicDiscoveryServiceSynthetic outputNo external costproviders.strategy.music_provider = mock

🖼️ Image adapters

ProviderCategoryAdapter class · filePort implementedAuth env varBase URL / model defaultCostSelected by (config key = value)Mock counterpart
DALL·EImageDalleAdapter · infrastructure/adapters/image/dalle_adapter.pyImageGenerationServiceDALLE_API_KEYhttps://api.openai.com/v1; dall-e-3Paid APIproviders.strategy.image_provider = dallePillowAdapter in local-only mode
MidjourneyImageMidjourneyAdapter · infrastructure/adapters/image/midjourney.pyImageGenerationServicePROVIDERS__MIDJOURNEY__API_KEY<host>:<port> placeholder from MidjourneyConfig.api_urlPaid or hosted APIproviders.strategy.image_provider = midjourneyPillowAdapter in local-only mode
PillowImagePillowAdapter · infrastructure/adapters/image/pillow_adapter.pyImageGenerationServiceLocal image generationLocalproviders.strategy.image_provider = pillow

🎞️ Timeline adapters

ProviderCategoryAdapter class · filePort implementedAuth env varBase URL / model defaultCostSelected by (config key = value)Mock counterpart
Agentic LLMTimelineLLMAgenticTimelineAdapter · infrastructure/adapters/timeline/llm_agentic_timeline_adapter.pyAgenticTimelineService, AudioDirectionServiceProvider-specificConfigured agentic providerProvider-dependentproviders.strategy.agentic_timeline_provider = openrouter or mock is wired; openai and anthropic are accepted configuration values but unwired selector keys and therefore raise a selector-key errorMockAgenticTimelineAdapter
Mock agenticTimelineMockAgenticTimelineAdapter · infrastructure/adapters/timeline/mock_agentic_timeline_adapter.pyAgenticTimelineServiceSynthetic timelineNo external costproviders.strategy.agentic_timeline_provider = mock

🎬 Montage services

Montage is not an adapter category. FFmpegMontageService and MontageServiceImpl implement MontageService under infrastructure/services/ffmpeg_montage.py and infrastructure/services/montage.py; the container factories ffmpeg_montage_service_factory and pydub_montage_service_factory select those service implementations.

🔌 Dependency-injection wiring

ServiceContainer in infrastructure/container.py exposes factories and selectors rather than importing every implementation eagerly. openrouter_adapter_factory creates OpenRouterAdapter; _create_dalle_adapter, _create_elevenlabs_adapter, _create_piper_adapter, _create_coqui_adapter, _create_elevenlabs_sfx_adapter, and _create_mock_sfx_adapter create their named providers. ffmpeg_montage_service_factory and pydub_montage_service_factory create the montage implementations; _create_chroma_audio_adapter creates semantic audio search; _get_sfx_providers and _get_music_providers build ordered provider lists.

ServiceBranch condition in ServiceContainer
Image generationAPP__DEV_LOCAL_ONLY or APP__LOW_RESOURCE_MODE truthy forces pillow; otherwise PROVIDERS__STRATEGY__IMAGE_PROVIDER or providers.strategy.image_provider, default pillow.
TTS listLocal-only or low-resource returns [piper, mock]; otherwise [piper, elevenlabs, mock]. PlayAI and Coqui are constructed in the services dictionary but excluded from this orchestrator list.
SFXLocal-only or low-resource returns [local, mock]; otherwise the strategy env var or configured providers.strategy.sfx_provider is used. ProviderStrategy.sfx_provider defaults to local; the freesound fallback applies only when the strategy is absent entirely.
LLMLocal-only or missing PROVIDERS__OPENROUTER__API_KEY returns MockLLMService; otherwise the configured OpenRouter selector.
MusicLocal-only or low-resource selects local; otherwise the strategy env var or configured strategy, default local.
Audio searchLocal-only or low-resource creates MockAudioSearchService; otherwise Chroma is created lazily.

🧪 What APP__DEV_LOCAL_ONLY=true swaps

Real adapter or serviceLocal-only substitute
OpenRouterAdapterMockLLMService
ElevenLabsAdapter, PlayAIAdapter, and cloud TTS selectionPiperAdapter plus MockTTSAdapter
FreesoundSFXAdapter and ElevenLabsSFXAdapterMockSfxAdapter
MusicGenAdapterLocalMusicAdapter
DalleAdapter and MidjourneyAdapterPillowAdapter
Chroma audio searchMockAudioSearchService
Agentic external timeline providerThe timeline adapter does not switch because of local-only mode; LLMAgenticTimelineAdapter remains selected for openrouter, while MockAgenticTimelineAdapter is selected only when providers.strategy.agentic_timeline_provider = mock. Local-only changes the injected LLM service to MockLLMService.

🔁 Retry, backoff, and caching

Adapters pass retry and timeout settings to their clients where the provider exposes them. OpenRouterAdapter uses configured timeout_seconds and max_retries; MidjourneyAdapter reads both from its provider config; montage and local adapters rely on local process or library errors rather than HTTP retry. SFX and TTS adapters surface provider failures through their async service methods, while the SFX resolver controls local-first ordering before external lookup. Backoff is provider-client behavior; no shared adapter-level exponential-backoff policy appears in the adapter modules.

infrastructure/services/api_cache.py defines a @cached(ttl, namespace) decorator, including an example voice lookup, but no adapter imports or invokes the decorator in this checkout. The cache decorator is therefore currently unused by integration wiring.

AdapterRetry / backoff / cache behavior
PiperAdapterSynthesis passes configured timeout_seconds to the async subprocess executor; the config default and SERVICES__PIPER__TIMEOUT_SECONDS determine that value. The 10s timeout is only for the Piper installation probe; voice-index data is cached in memory; no HTTP retry.
ElevenLabsAdapterClient timeout is 60s; streaming collects provider output; no shared backoff or adapter cache.
PlayAIAdapterHTTP client timeout comes from provider settings; provider errors surface; no shared cache.
CoquiTTSAdapterLocal model/API errors surface; no shared retry or cache.
MockTTSAdapterLocal deterministic response; no retry, backoff, or cache.
ChromaAudioSearchAdapterLocal vector search; no network retry or shared cache.
OpenRouterAdapterConfigured max_retries and timeout are passed to the client; structured calls use client retry behavior; no shared cache.
OllamaServiceConfigured timeout bounds the local HTTP call; no adapter retry or shared cache.
LLMStoryDataAdapterDelegates to the selected LLMService; retry belongs to that service.
OpenRouterQueryEnhancerDelegates to the selected LLM; retry belongs to that service.
LocalSFXAdapterLocal search and file operations have no HTTP retry or shared cache.
FreesoundSFXAdapterTimeout is 30s; no retry is implemented; no shared cache.
ElevenLabsSFXAdapterTimeout is 60s with bounded async waiting; no shared cache.
LocalMusicAdapterSemantic search falls back to keyword search; no network retry or shared cache.
MusicGenAdapterLocal model generation has a 120s timeout; no shared retry or cache.
MockMusicAdapterLocal silent-WAV generation; no retry, backoff, or cache.
DalleAdapterEach request permits max_retries + 1 attempts with linear sleep between attempts; no shared cache.
MidjourneyAdapterConfigured submission retries and polling bounds apply; no shared cache.
PillowAdapterLocal PIL operations surface errors; no retry, backoff, or cache.
LLMAgenticTimelineAdapterRetry behavior belongs to its injected LLM service; no shared cache.
MockAgenticTimelineAdapterDeterministic local timeline; no retry, backoff, or cache.

🧰 External binaries

PiperAdapter resolves its executable through SERVICES__PIPER__PIPER_BINARY, whose default is piper. Montage resolves ffmpeg and ffprobe through PathsSettings.ffmpeg and PathsSettings.ffprobe, whose defaults are /usr/bin/ffmpeg and /usr/bin/ffprobe. Keep these endpoint and binary values in backticks when copying them into commands or configuration.

MidjourneyConfig.api_url defaults to a host-specific value and is published as the <host>:<port> placeholder: override it in deployment configuration rather than depending on a machine-specific endpoint. OllamaProviderConfig.base_url is the real code default http://localhost:57707; OllamaService is not wired by ServiceContainer.

⚙️ Configuration explains the full settings tree, and 🔑 Environment Variables lists the complete environment catalogue.