Claude Code'un bir /voice modu var. Bir tuşu basılı tutarsınız, konuşursunuz, yazıya döker. Sesli giriş. Sahip olmadığı şey ise sesli çıkış: yanıtlar hâlâ okumanız gereken metin olarak geliyor. claude-can-speak'in arkasındaki bütün fikir bu asimetri. Model size sesli yanıt verebilmeli; duyulmaya değer şeyler için kendi inisiyatifiyle ya da sürekli bir anlatım istediğinizde her yanıtta. Açarsınız ve iki yönlü bir konuşma olur. Kapatırsınız ve yeniden sessizliğe dönersiniz. Geri kalan iş, bu cümleyi hiçbir parça sonradan yamanmış gibi hissettirmeden doğru kılmaktı ve ilk turda yanlış verdiğim kararlardan biri, mevcut /voice anahtarının iki yönü birden taşıyabileceğini varsaymaktı. Taşıyamıyordu ve hikayenin en öğretici kısmı da bu.
İlk gerçek karar ses meselesiydi ve beklediğimden daha az aşikardı. İçgüdüm, yerel olarak çalıştırabileceğim en doğal, en modern sinirsel metinden-sese yöntemine uzanmaktı. Birkaç tane var. Dürüst çerçeveleme "yapay zeka sesi mi robot sesi mi" değil, çünkü artık ciddi yerel seçeneklerin hepsi sinirsel. Asıl eksen, bir CPU üzerinde doğallık ile gecikme arasında ve burada önem taşıyor, çünkü bu şey her yanıttan sonra konuşuyor. Cümle başına dört saniye süren bir model, sürekli devreye girdiğinde kullanılamaz.
Üç tanesine baktım. 82 milyon parametreli bir model olan Kokoro, küçük olanların en doğalı ve bir CPU'da hızlı çalışıyor. Piper bir tık daha az doğal ama otuz küsur dilde çok dilli ve çok hızlı. XTTS sürüm iki hepsinin en doğalı ve ses klonlayabiliyor, ama bir CPU'da yanıt başına birkaç saniye sürüyor ve lisansı ticari olmayan, ki açıkça yayınlamak istediğim bir şey için bu bir sorun. XTTS her iki açıdan da kaybetti.
Geriye Kokoro ile Piper arasında gerçek bir gerilim kaldı ve Kokoro'nun istediğim dilleri kapsayabileceğini varsayma hatasını yaptım. Kapsayamıyor. İngilizce artı Almanca, ideal olarak da Türkçe istemiştim, böylece aynı kurulum gelecek projeler için yeniden kullanılabilirdi. Kokoro'nun kendi ses listesi meseleyi çözüyor: Amerikan ve İngiliz İngilizcesi, Japonca, Mandarin, İspanyolca, Fransızca, Hintçe, İtalyanca, Brezilya Portekizcesi. Almanca yok. Türkçe yok. Bunu inşadan sonra değil önce kontrol etmek, beni yanlış motoru yayınlamaktan kurtardı.
Ben de ikisini de yaptım, biri varsayılan, diğeri bir anahtar uzaklıkta. Piper çok dilli yol: Almanca Thorsten sesiyle, Türkçe dfki sesiyle ve kataloğunun geri kalanı isimle erişilebilir halde. Kokoro varsayılan, çünkü asıl kullanım için, yani İngilizce sesli çıkış için, basitçe daha iyi geliyor kulağa. Bunu teknik föy üzerinden değil, kulakla doğruladım. Aynı cümleyi her aday kadın ses üzerinden, Piper seçenekleri ve Kokoro olanlarıyla sentezleyip art arda dinledim. Kokoro'nun af_heart'ı net biçimde kazandı. Ölçüt "bu bir insan gibi mi duyuluyor" olduğunda, bir dinleme testi bir kıyaslamadan daha değerlidir.
Her iki motor tek bir Docker konteyneri içinde çalışıyor, böylece ana sistemin Python ortamına asla dokunmuyorlar. Konteyner kalıcı: bir kez başlıyor ve sıcak kalıyor, böylece model çalışma zamanını içe aktarmanın maliyeti her yanıtta değil, tek seferde ödeniyor. Ana sistem ona metin veriyor, o bir WAV geri veriyor ve ana sistem onu çalıyor. Sınırı yalnızca ses geçiyor. Sıcakken bir Piper cümlesi yaklaşık bir saniyede, bir Kokoro cümlesi iki saniyenin biraz üzerinde geri geliyor; ikisi de siz çalışmaya devam ederken konuşan bir şey için yeterli.
Onu Claude Code'a bağlamak bir Stop hook'u. Bir yanıt bittiğinde, Claude Code hook'u çalıştırıyor ve tamamlanan mesajı ona veriyor. Hook, yangın hortumunun açık olup olmadığını kontrol ediyor ve açık değilse sessizce çıkıyor ve hiçbir şey konuşmuyor. Açıksa markdown'ı temizliyor ve kod bloklarını atıyor, çünkü bir kod bloğunu sesli okumak dinlenmez bir şey, sonra temizlenmiş metni konteynere gönderiyor ve sonucu çalıyor. Hook hemen dönüyor ve konuşma, ayrılmış bir arka plan sürecinde gerçekleşiyor, böylece bir sonraki sıranızı asla geciktirmiyor. Bu "yangın hortumu açık mı" kontrolünün neyi okuduğu, iki kez vermek zorunda kaldığım tek karar oldu ve ona geri döneceğim.
Burada bir incelik beni yaktı. WAV kodlayıcılarının dosya başlığını yazmak için geriye doğru aramaları gerekiyor ve standart çıkışa giden bir boru aranabilir değil, bu yüzden ilk sürüm kesik, çalınamaz bir dosya üretti. Çözüm, konteyner içinde geçici bir dosyaya sentezleyip sonra onu dışarı akıtmak. Geriye dönüp bakınca aşikar, çarpana kadar görünmez.
Her yanıtı sesli okumak kör bir araç ve yarı yolda bunun tek mod olmaması gerektiğini fark ettim. Bu yüzden ikinci bir mod var: modele kasıtlı bir "şunu sesli söyle" yeteneği veren speak adlı bir Claude Code becerisi. Her şeyi anlatmak yerine, Claude yalnızca duyulmaya değer olanı seslendirmeyi seçebiliyor: uzaklaştığınızda sesli bir "derleme bitti ve testler geçti", bir dağıtımın onay gerektirdiğine dair bir uyarı, istediğiniz kısa bir bildirim. Becerinin tanımı modele onu ne zaman kullanacağını ve daha da önemlisi ne zaman kullanmayacağını söylüyor: asla rutin yanıtlar için, asla kod veya dosya yolları için, yalnızca gerçekten kulağınızı kesmeye değer şeyler için. İki mod birbirinden bağımsız. Yangın hortumunu, kasıtlı beceriyi, ikisini birden ya da hiçbirini çalıştırabilirsiniz.
Her yanıttan sonra konuşmak, aynı zamanda durdurmanın bir yolunu da gerektiriyor. Sonuna kadar dinlemek istemediğiniz uzun bir yanıt, hiç ses olmamasından daha kötüdür. Kesmenin üç yolu var: bir durdurma komutu çalıştırmak, ya da basitçe bir sonraki mesajınızı göndermek, ki ikinci bir hook bunu önceki yanıtı susturma sinyali olarak kullanıyor, ya da yeni bir yanıtın eskisinin yerini almasına izin vermek. Alttaki mekanizma, konuşan süreç grubunu daha sentez bitmeden, başladığı anda kaydediyor, böylece bir kesme, model ister hâlâ sentezliyor olsun ister cümlenin ortasında olsun isabet ediyor. Bu sıralamayı doğru ayarlamak, çalışan bir durdurma düğmesi ile yalnızca bazen çalışan bir düğme arasındaki farktı.
Son karar neyi yayınlayacağımla ilgiliydi ve aslında kılık değiştirmiş bir lisans kararı olduğu ortaya çıktı. Temiz içgüdü şuydu: modelleri hiç paketleme. Piper'ın sesleri ses başına yamalı bir lisans yığını taşıyor, fonemleştiricisi GPL ve Kokoro'nun ses paketi koşulları açıkça belirtilmemiş. Bu karışımı kendi MIT paketim içinde yeniden dağıtmak bir kargaşa olurdu. Onları paketlememek hem daha basit hem daha doğru: paket yalnızca kendi kodumu içeriyor ve her model ilk kullanımda, doğrudan resmi kaynağından, kendi lisansı altında, yerel bir önbelleğe indiriliyor. Hiçbir şeyi yeniden dağıtmıyorum. Kısa bir üçüncü taraf notları dosyası, her motoru ve modeli lisansı ve kökeniyle listeliyor.
Dağıtım da aynı "şeyi doğasına uydur" mantığını izledi. Olağan varsayılanım bir Debian paketidir, ama bu jenerik bir Claude Code uzantısı, bir Linux sistem aracı değil ve bir .deb onu hiçbir iyi gerekçe olmadan Debian ve Ubuntu'ya hapsederdi. Çoğunlukla kullanıcı başına Claude yapılandırması artı küçük bir CLI. npm buna çok daha iyi oturuyor ve macOS, Arch, Fedora ve WSL'e aynı şekilde ulaşıyor. Yani npm install -g claude-can-speak olarak yayınlanıyor; Docker ise bir paketleme bağımlılığı değil, kurulum aracının kontrol edip açıkladığı bir çalışma zamanı gereksinimi.
En öğretici hata sürümden sonra ortaya çıktı ve kendi akıllıca fikrimin geri tepmesiydi. Asıl plan kâğıt üzerinde zarifti: her şeyi seslendiren yangın hortumunu Claude Code'un yerleşik /voice'una bağlamak, böylece tek bir anahtar iki yönü de kontrol etsin, siz ona konuşun ve o size sesli yanıt versin. Onu bu şekilde yayınladım. Sonra /voice'u kapattım ve yanıtlar sesli okunmaya devam etti. Varsayım aynı anda iki yerde yanlıştı. Birincisi, /voice sesli giriş, yani dikte anahtarıdır ve canlı durumu bir hook'un ayar dosyasından güvenilir biçimde okuyabileceği bir şey değildir; okuduğum alan "dikte yapılandırıldı" anlamına geliyordu, ki bu kalıcı olarak doğruydu. İkincisi, biten yanıt hook'u ses durumundan bağımsız olarak her yanıtta tetiklenir, dolayısıyla onu gerçekten sınırlayan hiçbir şey yoktu. Özellik yalnızca, tesadüfen okuduğum alan doğru olduğu için çalışıyormuş gibi görünüyordu.
Çözüm, akıllı olmayı bırakıp araca kendi dürüst anahtarını vermekti. Yangın hortumu artık claude-can-speak on ve claude-can-speak off komutlarının yazdığı tek bir durum dosyasını okuyor ve varsayılanı kapalı. Artık /voice'a hiç bağlı değil, çünkü iki şey aslında hiçbir zaman aynı mesele değildi: biri istemleri nasıl dikte ettiğiniz, diğeri yanıtların size geri okunmasını isteyip istemediğiniz. Bunları birbirine karıştırmak, kapatılamayan bir anahtar üretti. Ders, düzenli olarak yeniden öğrendiğim eski bir ders: başka bir sistemin iç durumunu tersine mühendislikle çözmeye dayanan bir özellik, bozulmayı bekleyen bir özelliktir ve "şu an benim makinemde çalışıyor", "çalışıyor" ile aynı şey değildir. Bunu bir izle, hook'un gerçekten tetiklenip tetiklenmediğini izleyerek doğrulamak, kafa karıştıran bir "neden hâlâ konuşuyor" sorusunu tek satırlık bir kök nedene dönüştüren şey oldu.
Dürüst anahtar davranışı düzeltti, ama yalnızca araç sıfırdan kullanıldığında ortaya çıkan daha sessiz bir sorunu geride bıraktı. Yangın hortumu artık bir terminal komutunun arkasında yaşıyordu: claude-can-speak on. Bu doğru, ama bir Claude Code kullanıcısının baktığı yer değil. İçgüdü, benimki dahil, Claude Code içinde bir eğik çizgi yazıp menüde anahtarı taramak. Orada hiçbir şey yoktu, çünkü araç bilerek /voice'a dokunmuyor ve dolayısıyla doğal tepki "bu gerçekten kurulu mu" oldu. Özellik kusursuz çalışıyor ama bozuk hissettiriyordu, tamamen kontrolün, ona uzanan kişi için yanlış yerde olması yüzünden.
Çözüm, anahtarı elin uzandığı yere koymaktı. Artık araçla birlikte küçük bir /ccs eğik çizgi komutu geliyor ve kurulum sırasında yükleniyor, böylece /ccs on, /ccs off ve /ccs status Claude Code'un içinden, beklediğiniz yerde, /voice'un yanında çalışıyor. Terminal komutu hâlâ var; eğik çizgi komutu yalnızca onun üzerine ince bir sarmalayıcı. Altta bir incelik daha vardı ve açıkça söylemeye değer, çünkü bu, hata gibi okunabilecek türden bir şey. Claude Code, hook listesini bir kez, bir oturum başladığında okuyor. Yangın hortumunu oturumun ortasında açmak, hook'un okuduğu durum dosyasını güncelliyor, ama oturum hook hiç kaydedilmeden önce başladıysa, o oturumda onu okuyacak bir hook yok. Yani ilk seferinde, kurulumdan sonra yeni bir oturum başlatıyorsunuz ve o andan itibaren her yanıt konuşuyor. Hook'un kendi kodu ve aç/kapat durumu canlı olarak güncelleniyor; yalnızca ilk kayıt başlangıçta okunuyor. Bunu belgelerde açıkça adlandırmak asıl çözümün kendisiydi, çünkü yazılım zaten doğru olanı yapıyordu ve eksik olan yalnızca açıklamaydı.
Bu parçaların hiçbiri akıllıca bir algoritma değil. İlginç olan iş, küçük ve dürüst seçimlerin sırasıydı: ham doğallık yerine gecikme, sonra diller doğrulandığında genişlik yerine doğallık, bir yerine iki mod, yalnızca çalmada değil sentez sırasında da devreye giren bir kesme, paketlenmek yerine indirilen modeller, Debian paketi yerine npm, başka bir özellikten ödünç alınmış akıllıca bir anahtar yerine açık bir aç/kapat anahtarı ve o anahtarı elin uzandığı yere koyan bir eğik çizgi komutu. Her biri akıllıca bir hileden değil, gerçek bir kısıtı ciddiye almaktan doğdu. Sonuç, varsayılan olarak sessiz ve siz onu açtığınızda Claude Code sesli yanıt veriyor; ya beceri aracılığıyla kendi inisiyatifiyle ya da yangın hortumu aracılığıyla her yanıtta. Özelliğin tamamı bu ve onu basit hissettirmek bu kararların her birini, iki kez vermek zorunda kaldığım o karar dahil, gerektirdi.
