speech_mlx

Provider-neutral MLX Audio batch recognition and incremental text-to-speech for speech_core. MLX stays a batch inference engine: compose it with VAD and utterance buffering instead of treating it as a streaming recognizer.

final provider = MlxSpeechProvider(
  worker: MlxIsolateBatchWorker(
    modelPath: 'mlx-community/parakeet-tdt-0.6b-v2',
  ),
  modelId: 'parakeet-tdt-0.6b-v2',
  ttsWorker: MlxIsolateTtsWorker(
    modelPath: 'mlx-community/pocket-tts',
  ),
  ttsModelId: 'pocket-tts',
  voiceIds: const <String>['alba'],
);

try {
  final result = await provider.transcribe(
    BatchRecognitionRequest(audio: utteranceSource),
  );

  final source = provider.synthesize(
    SpeechSynthesisRequest(
      text: result.text,
      voiceId: 'alba',
      providerOptions: const MlxSynthesisOptions(seed: 7),
    ),
  );
  final session = await source.prepare();
  final frames = session.frames.listen(routeFrame);
  await session.start();
  await frames.cancel();
  await session.close();
} finally {
  await provider.close();
}

One MlxSpeechProvider advertises every configured capability under the stable mlx provider ID, so it can be registered once without colliding STT and TTS adapters. Either worker may be omitted when an application needs only one capability.

utteranceSource must be finite, continuous, and within maximumInputSamples. Capture, VAD, utterance buffering, and routing remain outside the provider. Source-native interleaved PCM crosses the worker boundary; the long-lived model isolate performs the full concatenation, downmix, and resampling work away from Flutter's UI isolate.

MlxIsolateBatchWorker and MlxIsolateTtsWorker load one model per long-lived isolate and serialize inference. Their request queues are bounded. TTS delivers real TtsModel.onAudioChunk PCM before generation completes and enforces a finite maximumOutputSamples limit before sending chunks, so a stalled consumer cannot create unbounded cross-isolate storage. The returned audio source is intentionally non-pausable; attach its single frame stream to an AudioRouter before calling start.

Cancelling active synchronous MLX inference terminates the worker isolate promptly. A later request transparently starts a fresh worker because native MLX kernels cannot process a Dart port message while a kernel is running.

Use SerializedMlxBatchWorker and SerializedMlxTtsWorker for deterministic tests or embedding-specific workers. They serialize calls but do not move work off the calling isolate. Their close timeout makes cleanup deterministic even when an injected callback violates the cooperative-cancellation contract.

Libraries

speech_mlx
MLX Audio adapters for provider-neutral speech contracts.