Aaron Helwig

Turning ideas into digital magic

mlx-speechd

I built a small local speech daemon for my Mac, with a command-line interface called msd. It keeps a Qwen3 speech model warm through MLX-Audio and plays audio as chunks arrive, so another tool can ask it to speak without loading the model for every sentence.

msd say "The render is ready." --voice Aiden --instruct "quiet, warm, slow"

From model output to something you hear

Generating audio is only one part of the job. I connected streamed model output to a playback queue, normalized chunks into mono float samples and handled differences between the model and output-device sample rates. The same engine can also render a complete audio file.

A new request interrupts the previous one by default. That means cancelling queued work as well as stopping playback: an older sentence should not suddenly start speaking after the newer request has arrived.

Finishing the sentence

Some of the most useful work was in the lifecycle details. Producing the last chunk does not mean the speaker has finished playing it. An idle timeout must not unload a model while it is warming. An audio device that fails after a long idle needs recovery, and an output failure needs to reach the caller instead of looking like success.

I separated synthesis, playback and daemon state so those cases could be tested independently. It is a focused tool, but building it gave me a much closer understanding of how local model inference, streaming audio and the operating system meet.