oto-Sink bekommt eine Format-Weiche: RIFF/WAVE-PCM (osbuiltin) und MP3
(ElevenLabs/OpenAI/Edge ab Task 03). Dekoder ist github.com/hajimehoshi/
go-mp3 v0.3.4 — reines Go ohne CGO, verifiziert per GOOS/GOARCH-Matrix mit
CGO_ENABLED=0 (alle 6 Targets bauen). Der Dekoder liefert immer 16-Bit-LE
stereo (Mono-Quellen wie Edge 24 kHz werden gespiegelt); die Sample-Rate
kommt aus dem ersten Frame. Fixtures sind echte, einmalig erzeugte MP3s
(mono 24 kHz 48 kbit/s = Edge-Format, stereo 44,1 kHz = Cloud-typisch),
als Base64 eingebettet — Tests bleiben werkzeugfrei.
NewSink bekommt einen optionalen Log-Writer (die CLI übergibt stderr);
zuvor verwarf der null-Sink bei erfolgreicher Synthese stumm — die
sichtbare Zeile kam nur aus dem ErrNoByteOutput-Zweig. Sink-Interface
unverändert; Aufrufstellen ab Task 03 müssen den Writer übergeben.
TestSayArgumentMitNullSink beweist die Zeile jetzt extern auf dem
CLI-Pfad, TestNewSink deckt das Factory-Wiring ab; Live-Beweis mit
espeak-ng dokumentiert ("audio: N Bytes verworfen (null sink)", Exit 0).
Dazu (Minor): "was" aus englishWords entfernt — widersprach dem
Mehrdeutigkeits-Kommentar und dem Report; neuer TestFall
"Was macht er?" → de.
Text aus Argument oder stdin → langdetect → Provider (Default osbuiltin;
--provider existiert, Kette folgt Task 03) → Synthesize → Sink.Play,
blockierend. Ohne byte-fähiges OS-TTS (Linux ohne espeak): Direktansage
über DirectSpeaker (spd-say); mit audio.sink=null wird die Ansage
stattdessen verworfen — CI/Server laufen tonlos mit Exit 0 durch.
run() bekommt stdin als Reader-Seam; an einem Terminal ohne Text gibt
es einen Benutzungsfehler statt blockierendem Warten. say-Hilfe zeigt
nicht mehr den Stub-Hinweis; alle anderen Befehle bleiben Stubs.
Sink-Vertrag Play/Name; oto-Sink (github.com/ebitengine/oto/v3, auf
v3.5.0 gepinnt — aktuelle stabile Major-Version; purego statt CGO, damit
cross-compilierbar) spielt RIFF/WAVE-PCM blockierend, ein Context pro
PCM-Format gecacht, da oto nur einen Kontext pro Prozess erlaubt.
null-Sink verwirft (CI/Server/Tests) und loggt optional. WAV-Parser
(RIFF-Chunks, PCM 8/16 Bit, Float32) mit klarer Fehler für alles andere.
Echte Wiedergabe nur hinter TALK_TEST_REAL_AUDIO=1.
Interface Name/Available/Synthesize (Contract für die Kette aus Task 03)
plus dokumentierte optionale Erweiterung DirectSpeaker für Plattformen,
deren OS-TTS keine Audio-Bytes liefert: spd-say (speech-dispatcher) kann
nach --help-Verifikation nur selbst abspielen — dort spricht
SpeakDirect blockierend (spd-say -w -l <lang>). Synthesize liefert
RIFF/WAVE: Linux espeak/espeak-ng (--stdout), macOS say (temporäre
WAV-Datei, LEI16@22050), Windows SAPI (PowerShell, -EncodedCommand).
Tests mit Fake-Prozessen über PATH-Manipulation; echte spd-say-Ausgabe
nur hinter TALK_TEST_REAL_SPD=1.
Defaults → optionale config.toml → Env-Overrides (TALK_AUDIO_SINK);
Validierungsfehler sind klar, nie still. Plattform-Pfadlogik
(linux/darwin/windows, XDG_CONFIG_HOME, HOME, APPDATA) mit goos als
Parameter — alle Plattformen tischgesteuert vom Dev-Host testbar.
Erste externe Dependency: github.com/BurntSushi/toml v1.6.0 (MIT, pure Go).
Detect(text) → "de" | "en" (Default "de"): Common-Word-Listen plus
Umlaute/ß als Deutsch-Signale; Englisch gewinnt nur bei strikter Mehrheit.
Tischgesteuerte Tests für de/en/gemischt/leer.