Programování

Mluvící Java!

Proč byste chtěli, aby vaše aplikace mluvily? Pro začátek je to zábava a vhodná pro zábavné aplikace, jako jsou hry. A existuje vážnější stránka přístupnosti. Nemyslím zde jen na ty, kteří jsou přirozeně znevýhodněni při používání vizuálního rozhraní, ale také na situace, kdy je nemožné - nebo dokonce nelegální - odtrhnout oči od toho, co děláte.

Nedávno jsem pracoval s některými technologiemi na získávání informací HTML a XML z webu [viz „Přístup k největší databázi na světě s připojením Web DataBase“ (JavaWorld, Března 2001)]. Napadlo mě, že mohu tuto práci a tuto myšlenku spojit dohromady a vytvořit mluvící webový prohlížeč. Takový prohlížeč by se osvědčil při poslechu úryvků informací z vašich oblíbených webů - například nadpisů zpráv - stejně jako poslech rádia při chůzi se psem nebo při řízení do práce. Samozřejmě, se současnou technologií byste museli nosit notebook s připojeným mobilním telefonem, ale tento nepraktický scénář by se mohl v blízké budoucnosti změnit s příchodem chytrých telefonů s podporou Java, jako je Nokia 9210 (9290 v NÁS).

Možná z krátkodobého hlediska bude užitečnější čtečka e-mailů, možná také díky rozhraní JavaMail API. Tato aplikace by pravidelně kontrolovala vaši doručenou poštu a vaši pozornost by přitahoval hlas odnikud hlásající „Máte novou poštu, chcete, abych vám ji přečetl?“ V podobném duchu zvažte i mluvící připomenutí - spojené s vaší deníkovou aplikací -, které křičí „Nezapomeňte na schůzku se šéfem za 10 minut!“

Za předpokladu, že jste těmto myšlenkám prodáni nebo máte nějaké dobré nápady, půjdeme dál. Začnu tím, že ukážu, jak uvést můj dodaný soubor zip do práce, abyste se mohli hned rozběhnout a přeskočit podrobnosti implementace, pokud si myslíte, že je to příliš tvrdá práce.

Otestujte motor řeči

Chcete-li použít modul řeči, musíte do souboru CLASSPATH zahrnout soubor jw-0817-javatalk.zip a spustit com.lotontech.speech.Talker třídy z příkazového řádku nebo z programu Java.

Chcete-li jej spustit z příkazového řádku, zadejte:

java com.lotontech.speech.Talker "h | e | l | oo" 

Chcete-li jej spustit z programu Java, jednoduše vložte dva řádky kódu:

com.lotontech.speech.Talker talker = nový com.lotontech.speech.Talker (); talker.sayPhoneWord ("h | e | l | oo"); 

V tomto okamžiku vás pravděpodobně zajímá formát formátu „h | e | l | oo“ řetězec, který zadáte na příkazovém řádku nebo zadáte do sayPhoneWord (...) metoda. Nech mě to vysvětlit.

Řečový modul pracuje zřetězením krátkých zvukových vzorků, které představují nejmenší jednotky lidské - v tomto případě anglické - řeči. Ty zvukové ukázky, volal alofony, jsou označeny jedno-, dvou- nebo třípísmenným identifikátorem. Některé identifikátory jsou zřejmé a jiné ne tak zřejmé, jak vidíte z fonetického vyjádření slova „ahoj“.

  • h - zní, jak byste čekali
  • E - zní, jak byste čekali
  • l - zní, jak byste čekali, ale všimněte si, že jsem zredukoval dvojité „l“ na jedno
  • oo - je zvuk pro „ahoj“, ne pro „robota“ a ne pro „příliš“

Zde je seznam dostupných alofonů:

  • A - jako v kat
  • b - jako v kabině
  • C - jako v kat
  • d - jako v tečce
  • E - jako v sázce
  • F - jako žába
  • G - jako žába
  • h - jako v prase
  • i - jako u prasete
  • j - jako v přípravku
  • k - jako v soudku
  • l - jako v noze
  • m - jako v met
  • n - jako na začátku
  • Ó - jako ne
  • p - jako v hrnci
  • r - jako v hnilobě
  • s - jako v sat
  • t - jako v sat
  • u - jak je uvedeno
  • proti - jako v
  • w - jako na mokru
  • y - jako dosud
  • z - jako v zoo
  • aa - jako falešný
  • ano - jako na seno
  • ee - jako u včely
  • ii - jako vysoko
  • oo - jako v chodu
  • bb - variace b s různým důrazem
  • dd - variace d s různým důrazem
  • ggg - variace g s různým důrazem
  • hh - variace h s různým důrazem
  • ll - variace l s různým důrazem
  • nn - variace n s různým důrazem
  • rr - variace r s různým důrazem
  • tt - variace t s různým důrazem
  • yy - variace y s různým důrazem
  • ar - jako v autě
  • aer - jako v péči
  • ch - jako ve kterém
  • ck - jako v šeku
  • ucho - jako v pivu
  • ehm - jako později
  • chybovat - jako později (delší zvuk)
  • ng - jako při krmení
  • nebo - jako v zákoně
  • ou - jako v zoo
  • ouu - jako v zoo (delší zvuk)
  • ou - jako u krávy
  • oy - jako u chlapce
  • sh - jako v uzavřeném stavu
  • th - jako ve věci
  • dth - jako v tomto
  • uh - variace u
  • wh - jako kde
  • zh - jako v asijských

V lidské řeči stoupá a klesá stoupání slov v jakékoli mluvené větě. Tato intonace činí řeč přirozenější, emotivnější a umožňuje rozlišení otázek od výroků. Pokud jste někdy slyšeli syntetický hlas Stephena Hawkinga, chápete, o čem mluvím. Zvažte tyto dvě věty:

  • Je to falešné - f | aa | k
  • Je to falešné? - f | AA | k

Jak jste možná uhodli, způsob, jak zvýšit intonaci, je používat velká písmena. S tím musíte trochu experimentovat a můj tip je, že byste se měli soustředit na dlouhé samohlásky.

To je vše, co potřebujete k používání softwaru vědět, ale pokud vás zajímá, co se děje pod kapotou, čtěte dále.

Implementujte modul řeči

Řečový stroj vyžaduje implementaci pouze jedné třídy se čtyřmi metodami. Využívá rozhraní Java Sound API, které je součástí J2SE 1.3. Nebudu poskytovat komplexní výukový program Java Sound API, ale naučíte se příkladem. Zjistíte, že toho není moc, a komentáře vám řeknou, co potřebujete vědět.

Zde je základní definice Mluvka třída:

balíček com.lotontech.speech; importovat javax.sound.sampled. *; importovat java.io. *; import java.util. *; importovat java.net. *; public class Talker {private SourceDataLine line = null; } 

Pokud běžíte Mluvka z příkazového řádku, hlavní(...) níže uvedená metoda bude sloužit jako vstupní bod. Trvá první argument příkazového řádku, pokud existuje, a předá jej sayPhoneWord (...) metoda:

/ * * Tato metoda mluví fonetickým slovem uvedeným na příkazovém řádku. * / public static void main (String args []) {Talker player = new Talker (); if (args.length> 0) player.sayPhoneWord (args [0]); System.exit (0); } 

The sayPhoneWord (...) metodu volá hlavní(...) výše, nebo jej lze volat přímo z vaší aplikace Java nebo appletu podporovaného zásuvným modulem. Vypadá to komplikovaněji, než je. V zásadě jednoduše postupuje slovem allophones - oddělené „|"symboly ve vstupním textu - a přehrává je jeden po druhém prostřednictvím zvukového výstupního kanálu. Aby to znělo přirozeněji, sloučím konec každého zvukového vzorku se začátkem následujícího:

/ * * Tato metoda mluví daným fonetickým slovem. * / public void sayPhoneWord (řetězcové slovo) {// - Nastavit fiktivní bajtové pole pro předchozí zvuk - byte [] previousSound = null; // - Rozdělte vstupní řetězec na samostatná alofony - StringTokenizer st = nový StringTokenizer (slovo, "|", false); while (st.hasMoreTokens ()) {// - Vytvořte název souboru pro allophone - String thisPhoneFile = st.nextToken (); thisPhoneFile = "/ allophones /" + thisPhoneFile + ". au"; // - Načíst data ze souboru - byte [] thisSound = getSound (thisPhoneFile); if (previousSound! = null) {// - Sloučit předchozí alofon s tímto, pokud můžeme - int mergeCount = 0; if (previousSound.length> = 500 && thisSound.length> = 500) mergeCount = 500; pro (int i = 0; i

Na konci sayPhoneWord (), uvidíte, že volá přehrát zvuk(...) vydat jednotlivý zvukový vzorek (alofon) a zavolá vypustit (...) propláchnutí zvukového kanálu. Tady je kód pro přehrát zvuk(...):

/ * * Tato metoda přehrává zvukový vzorek. * / private void playSound (byte [] data) {if (data.length> 0) line.write (data, 0, data.length); } 

A pro vypustit (...):

/ * * Tato metoda vyprázdní zvukový kanál. * / private void drain () {if (line! = null) line.drain (); zkuste {Thread.sleep (100);} chytit (výjimka e) {}} 

Pokud se nyní podíváte zpět na sayPhoneWord (...) metoda, uvidíte, že existuje jedna metoda, kterou jsem dosud nepokryl: getSound (...).

getSound (...) čte předem nahraný zvukový vzorek ze souboru au jako bajtová data. Když řeknu soubor, mám na mysli zdroj uchovávaný v dodaném souboru zip. Dělám rozdíl, protože způsob, jakým se zmocníte zdroje JAR - pomocí getResource (...) metoda - postupuje odlišně od způsobu, jakým se chopíte souboru, což není zřejmý fakt.

Pro podrobný popis čtení dat, převodu zvukového formátu a vytvoření instance zvukového výstupního řádku (proč se tomu říká SourceDataLine`` Nevím) a při sestavování dat bajtů vás odkazuji na komentáře v následujícím kódu:

/ * * Tato metoda načte soubor pro jeden alofon a * zkonstruuje bajtový vektor. * / private byte [] getSound (String fileName) {try {URL url = Talker.class.getResource (fileName); Stream AudioInputStream = AudioSystem.getAudioInputStream (url); Formát AudioFormat = stream.getFormat (); // - Převést zvuk ALAW / ULAW na PCM pro přehrávání - if ((format.getEncoding () == AudioFormat.Encoding.ULAW) || (format.getEncoding () == AudioFormat.Encoding.ALAW)) { AudioFormat tmpFormat = nový AudioFormat (AudioFormat.Encoding.PCM_SIGNED, format.getSampleRate (), format.getSampleSizeInBits () * 2, format.getChannels (), format.getFrameSize () * 2, format.getFrameRate (), true); stream = AudioSystem.getAudioInputStream (tmpFormat, stream); format = tmpFormat; } DataLine.Info info = nový DataLine.Info (Clip.class, format, ((int) stream.getFrameLength () * format.getFrameSize ())); if (line == null) {// - Výstupní řádek dosud není vytvořen - // - Můžeme najít vhodný druh řádku? - DataLine.Info outInfo = nový DataLine.Info (SourceDataLine.class, formát); if (! AudioSystem.isLineSupported (outInfo)) {System.out.println ("Line matching" + outInfo + "not supported."); vyvolá novou výjimku ("Line matching" + outInfo + "není podporováno."); } // - Otevřete zdrojový datový řádek (výstupní řádek) - line = (SourceDataLine) AudioSystem.getLine (outInfo); line.open (formát, 50000); line.start (); } // - Některé výpočty velikosti - int frameSizeInBytes = format.getFrameSize (); int bufferLengthInFrames = line.getBufferSize () / 8; int bufferLengthInBytes = bufferLengthInFrames * frameSizeInBytes; byte [] data = nový bajt [bufferLengthInBytes]; // - Přečíst datové bajty a spočítat je - int numBytesRead = 0; if ((numBytesRead = stream.read (data))! = -1) {int numBytesRemaining = numBytesRead; } // - Zkrátit bajtové pole na správnou velikost - byte [] newData = nový bajt [numBytesRead]; pro (int i = 0; i

Takže to je ono. Syntetizátor řeči v asi 150 řádcích kódu, včetně komentářů. Ještě to ale neskončilo.

Převod textu na řeč

Fonetické upřesňování slov se může zdát trochu zdlouhavé, takže pokud máte v úmyslu vytvořit jednu z příkladů aplikací, které jsem navrhl v úvodu, chcete jako vstup poskytnout obyčejný text.

Po prozkoumání problému jsem v souboru zip poskytl experimentální třídu převodu textu na řeč. Když jej spustíte, výstup vám poskytne vhled do toho, co dělá.

Převaděč textu na řeč můžete spustit příkazem, jako je tento:

java com.lotontech.speech.Converter "ahoj" 

To, co uvidíte jako výstup, vypadá asi takto:

ahoj -> h | e | l | oo tam -> dth | aer 

Nebo co takhle běžet jako:

java com.lotontech.speech.Converter "Rád čtu JavaWorld" 

vidět (a slyšet) toto:

i -> ii like -> l | ii | k to -> t | ouu read -> r | ee | a | d java -> j | a | v | a world -> w | err | l | d 

Pokud vás zajímá, jak to funguje, mohu vám říci, že můj přístup je celkem jednoduchý a sestává ze sady pravidel nahrazování textu použitých v určitém pořadí. Zde je několik příkladů pravidel, která byste chtěli mentálně uplatnit, aby pro slova „mravenec“, „chtěli“, „chtěli“, „nechtěli“ a „jedinečné“:

  1. Nahraďte „* unique *“ za „| y | ou | n | ee | k |“
  2. Nahraďte „* want *“ za „| w | o | n | t |“
  3. Nahraďte „* a *“ za „| a |“
  4. Nahraďte „* e *“ za „| e |“
  5. Nahraďte „* d *“ za „| d |“
  6. Nahraďte „* n *“ za „| n |“
  7. Nahraďte „* u *“ za „| u |“
  8. Nahraďte „* t *“ za „| t |“

Pro „nežádoucí“ by sekvence byla takto:

nechtěnýun [| w | o | n | t |] vyd (pravidlo 2) [| u |] [| n |] [| w | o | n | t |] [| e |] [| d |] (pravidla 4, 5, 6, 7) u | n | w | o | n | t | e | d (s odstraněnými přebytečnými znaky) 

Měli byste vidět, jak slova obsahující písmena zvyklý bude mluveno jiným způsobem než slova obsahující písmena mravenec. Měli byste také vidět, jak pravidlo zvláštních případů pro celé slovo unikátní má přednost před ostatními pravidly, aby se toto slovo vyslovovalo jako y | ou ... spíše než u | n ....