Programmering

Taler Java!

Hvorfor vil du få dine applikationer til at tale? Til at begynde med er det sjovt og velegnet til sjove applikationer som spil. Og der er en mere seriøs tilgængelighedsside. Jeg tænker her ikke kun på de naturligt dårligt stillede, når man bruger en visuel grænseflade, men også i de situationer, hvor det er umuligt - eller endda ulovligt - at fjerne øjnene fra det, du laver.

For nylig har jeg arbejdet med nogle teknologier til at hente HTML- og XML-oplysninger fra Internettet [se "Få adgang til verdens største database med Web DataBase-tilslutning" (JavaWorld, Marts 2001)]. Det faldt mig ind, at jeg kunne tilslutte det arbejde og denne idé sammen for at opbygge en talende webbrowser. En sådan browser ville vise sig nyttig til at lytte til uddrag af information fra dine yndlingssider - for eksempel nyhedsoverskrifter - ligesom at lytte til radioen, mens du går ud på din hund eller kører på arbejde. Selvfølgelig skal du med den nuværende teknologi bære din bærbare computer rundt med din mobiltelefon tilsluttet, men det upraktiske scenario kan meget vel ændre sig i den nærmeste fremtid med ankomsten af ​​Java-aktiverede smartphones som Nokia 9210 (9290 i OS).

Måske mere nyttigt på kort sigt ville være en e-mail-læser, også mulig takket være JavaMail API. Denne applikation vil med jævne mellemrum kontrollere din indbakke, og din opmærksomhed vil blive tiltrukket af en stemme fra ingen steder, der proklamerer "Du har ny mail, vil du have, at jeg læser den til dig?" På samme måde kan du overveje en talende påmindelse - forbundet med din dagbogsprogram - der råber "Glem ikke dit møde med chefen om 10 minutter!"

Forudsat at du sælges på disse ideer eller har nogle gode ideer, fortsætter vi. Jeg begynder med at vise, hvordan jeg sætter min leverede zip-fil i arbejde, så du kan komme i gang med det samme og springe implementeringsoplysningerne over, hvis du synes, det er for meget hårdt arbejde.

Testkørsel talemotoren

For at bruge talemotoren skal du medtage filen jw-0817-javatalk.zip i din CLASSPATH og køre com.lotontech.speech.Talker klasse fra kommandolinjen eller fra et Java-program.

For at køre det fra kommandolinjen skal du skrive:

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

For at køre det fra et Java-program skal du blot inkludere to kodelinjer:

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

På dette tidspunkt undrer du dig sandsynligvis over formatet på "h | e | l | oo" streng, du leverer på kommandolinjen eller giver til sayPhoneWord (...) metode. Lad mig forklare.

Talemotoren fungerer ved sammenkædning af korte lydprøver, der repræsenterer de mindste enheder af menneskelig - i dette tilfælde engelsk - tale. Disse lydeksempler kaldes allofoner, er mærket med en identifikator med et, to eller tre bogstaver. Nogle identifikatorer er indlysende og andre ikke så indlysende, som du kan se fra den fonetiske repræsentation af ordet "hej."

  • h - lyder som du ville forvente
  • e - lyder som du ville forvente
  • l - lyder som du ville forvente, men bemærk at jeg har reduceret en dobbelt "l" til en enkelt
  • oo - er lyden for "hej", ikke for "bot" og ikke for "også"

Her er en liste over tilgængelige allofoner:

  • -en - som hos kat
  • b - som i førerhus
  • c - som hos kat
  • d - som i prik
  • e - som i bet
  • f - som i frøen
  • g - som i frøen
  • h - som i svin
  • jeg - som hos gris
  • j - som i jig
  • k - som i keg
  • l - som i benet
  • m - som i met
  • n - som i starten
  • o - som i ikke
  • s - som i gryde
  • r - som i rådne
  • s - som i lør
  • t - som i lør
  • u - som sagt
  • v - som i have
  • w - som i vådt
  • y - som i endnu
  • z - som i zoologisk have
  • aa - som i falsk
  • ay - som i hø
  • ee - som i bi
  • ii - som i høj
  • oo - som i gang
  • bb - variation af b med forskellig vægt
  • dd - variation af d med forskellig vægt
  • ggg - variation af g med forskellig vægt
  • hh - variation af h med forskellig vægt
  • ll - variation af l med forskellig vægt
  • nn - variation af n med forskellig vægt
  • rr - variation af r med forskellig vægt
  • tt - variation af t med forskellig vægt
  • yy - variation af y med forskellig vægt
  • ar - som i bil
  • aer - som i pleje
  • ch - som i hvilken
  • ck - som i skak
  • øre - som i øl
  • er - som senere
  • fejle - som senere (længere lyd)
  • ng - som ved fodring
  • eller - som i loven
  • ou - som i zoologisk have
  • ouu - som i zoologisk have (længere lyd)
  • ow - som hos ko
  • oy - som hos dreng
  • sh - som lukket
  • th - som i ting
  • dth - som i dette
  • uh - variation af u
  • W h - som i hvor
  • zh - som på asiatisk

I menneskelig tale stiger og falder ordet gennem enhver talt sætning. Denne intonation får talen til at lyde mere naturlig, mere følelsesladet og gør det muligt at skelne mellem spørgsmål og udsagn. Hvis du nogensinde har hørt Stephen Hawkings syntetiske stemme, forstår du, hvad jeg taler om. Overvej disse to sætninger:

  • Det er falsk - f | aa | k
  • Er det falsk? - f | AA | k

Som du måske har gættet, er måden at hæve intonationen på at bruge store bogstaver. Du skal eksperimentere lidt med dette, og mit tip er, at du skal koncentrere dig om de lange vokallyde.

Det er alt hvad du behøver at vide for at bruge softwaren, men hvis du er interesseret i, hvad der foregår under emhætten, skal du læse videre.

Implementere talemotoren

Talemotoren kræver kun en klasse at implementere med fire metoder. Det anvender Java Sound API, der følger med J2SE 1.3. Jeg giver ikke en omfattende tutorial af Java Sound API, men du lærer med et godt eksempel. Du finder ud af, at der ikke er meget ved det, og kommentarerne fortæller dig, hvad du har brug for at vide.

Her er den grundlæggende definition af Talker klasse:

pakke com.lotontech.speech; import javax.sound.sampled. *; import java.io. *; importer java.util. *; import java.net. *; offentlig klasse Talker {privat SourceDataLine linje = null; } 

Hvis du løber Talker fra kommandolinjen, vigtigste (...) nedenstående metode fungerer som indgangspunkt. Det tager det første kommandolinjeargument, hvis der findes en, og sender det til sayPhoneWord (...) metode:

/ * * Denne metode taler et fonetisk ord, der er angivet på kommandolinjen. * / public static void main (String args []) {Talker player = new Talker (); hvis (args.længde> 0) spiller.sayPhoneWord (args [0]); System.exit (0); } 

Det sayPhoneWord (...) metode kaldes af vigtigste (...) ovenfor, eller det kan kaldes direkte fra din Java-applikation eller plug-in-understøttede applet. Det ser mere kompliceret ud end det er. I det væsentlige træder det simpelthen skridt gennem ordet allophones - adskilt af "|"symboler i inputteksten - og afspiller dem en efter en gennem en lydudgangskanal. For at få det til at lyde mere naturligt fletter jeg slutningen af ​​hver lydprøve med begyndelsen på den næste:

/ * * Denne metode taler det givne fonetiske ord. * / public void sayPhoneWord (String word) {// - Opret et dummy byte array til den forrige lyd - byte [] previousSound = null; // - Opdel inputstrengen i separate allofoner - StringTokenizer st = new StringTokenizer (word, "|", false); while (st.hasMoreTokens ()) {// - Konstruer et filnavn til allofonen - String thisPhoneFile = st.nextToken (); thisPhoneFile = "/ allophones /" + thisPhoneFile + ". au"; // - Hent dataene fra filen - byte [] thisSound = getSound (thisPhoneFile); if (previousSound! = null) {// - Flet den forrige allofon med denne, hvis vi kan - int mergeCount = 0; hvis (previousSound.length> = 500 && thisSound.length> = 500) mergeCount = 500; for (int i = 0; i

Ved udgangen af sayPhoneWord (), vil du se det kalder Afspil lyd(...) at udsende en individuel lydprøve (en allofon), og den kalder dræne(...) for at skylle lydkanalen. Her er koden til Afspil lyd(...):

/ * * Denne metode afspiller en lydeksempel. * / private void playSound (byte [] data) {if (data.length> 0) line.write (data, 0, data.length); } 

Og for dræne(...):

/ * * Denne metode skyller lydkanalen. * / privat hulrumsafløb () {if (line! = null) line.drain (); prøv {Thread.sleep (100);} fang (Undtagelse e) {}} 

Nu, hvis du ser tilbage på sayPhoneWord (...) metode, vil du se, at der er en metode, som jeg endnu ikke har dækket: getSound (...).

getSound (...) læser i en forudindspillet lydprøve som byte-data fra en au-fil. Når jeg siger en fil, mener jeg en ressource indeholdt i den medfølgende zip-fil. Jeg trækker forskellen, fordi den måde, du får fat i en JAR-ressource - ved hjælp af getResource (...) metode - forløber forskelligt fra den måde, du får fat i en fil, en ikke indlysende kendsgerning.

For en blow-by-blow-konto for læsning af data, konvertering af lydformat, instantering af en lydoutputlinje (hvorfor de kalder det en SourceDataLine, Jeg ved det ikke), og ved at samle byte-data henviser jeg til kommentarerne i koden, der følger:

/ * * Denne metode læser filen for en enkelt allofon og * konstruerer en bytevektor. * / privat byte [] getSound (strengfilnavn) {prøv {URL url = Talker.class.getResource (filnavn); AudioInputStream stream = AudioSystem.getAudioInputStream (url); AudioFormat format = stream.getFormat (); // - Konverter en ALAW / ULAW-lyd til PCM til afspilning - hvis ((format.getEncoding () == AudioFormat.Encoding.ULAW) || (format.getEncoding () == AudioFormat.Encoding.ALAW)) { AudioFormat tmpFormat = ny AudioFormat (AudioFormat.Encoding.PCM_SIGNED, format.getSampleRate (), format.getSampleSizeInBits () * 2, format.getChannels (), format.getFrameSize () * 2, format.getFrameRate (), sand); stream = AudioSystem.getAudioInputStream (tmpFormat, stream); format = tmpFormat; } DataLine.Info info = ny DataLine.Info (Clip.class, format, ((int) stream.getFrameLength () * format.getFrameSize ())); if (line == null) {// - Outputlinjen er endnu ikke instantieret - // - Kan vi finde en passende type linje? - DataLine.Info outInfo = ny DataLine.Info (SourceDataLine.class, format); hvis (! AudioSystem.isLineSupported (outInfo)) {System.out.println ("Line matching" + outInfo + "understøttes ikke."); smid ny undtagelse ("Line matching" + outInfo + "understøttes ikke."); } // - Åbn kildedatelinjen (outputlinjen) - linje = (SourceDataLine) AudioSystem.getLine (outInfo); line.open (format, 50000); line.start (); } // - Nogle størrelsesberegninger - int frameSizeInBytes = format.getFrameSize (); int bufferLengthInFrames = line.getBufferSize () / 8; int bufferLengthInBytes = bufferLengthInFrames * frameSizeInBytes; byte [] data = ny byte [bufferLengthInBytes]; // - Læs databytes og tæl dem - int numBytesRead = 0; hvis ((numBytesRead = stream.read (data))! = -1) {int numBytesRemaining = numBytesRead; } // - Afkort byte-arrayet til den rigtige størrelse - byte [] newData = ny byte [numBytesRead]; for (int i = 0; i

Så det er det. En talesynthesizer i omkring 150 linjer kode, inklusive kommentarer. Men det er ikke helt forbi.

Tekst til tale konvertering

At specificere ord fonetisk kan virke lidt kedeligt, så hvis du har til hensigt at opbygge en af ​​de eksempler på applikationer, som jeg foreslog i indledningen, vil du give almindelig tekst som input, der skal læses.

Efter at have undersøgt problemet har jeg leveret en eksperimentel klasse-til-tale-konverteringsklasse i zip-filen. Når du kører det, vil output give dig indsigt i, hvad det gør.

Du kan køre en tekst-til-tale-konverter med en kommando som denne:

java com.lotontech.speech.Converter "hej der" 

Det, du ser som output, ser ud som:

hej -> h | e | l | oo der -> dth | aer 

Eller hvad med at køre det som:

java com.lotontech.speech.Converter "Jeg kan godt lide at læse JavaWorld" 

at se (og høre) dette:

i -> ii kan lide -> l | ii | k til -> t | ouu læse -> r | ee | a | d java -> j | a | v | en verden -> w | fejl | l | d 

Hvis du spekulerer på, hvordan det fungerer, kan jeg fortælle dig, at min tilgang er ret enkel og består af et sæt regler til udskiftning af tekst, der anvendes i en bestemt rækkefølge. Her er nogle eksempler på regler, som du måske gerne vil anvende mentalt, i rækkefølge for ordene "myre", "ønsker", "ønsket", "uønsket" og "unik":

  1. Udskift "* unik *" med "| y | ou | n | ee | k |"
  2. Udskift "* want *" med "| w | o | n | t |"
  3. Erstat "* a *" med "| a |"
  4. Erstat "* e *" med "| e |"
  5. Erstat "* d *" med "| d |"
  6. Erstat "* n *" med "| n |"
  7. Erstat "* u *" med "| u |"
  8. Erstat "* t *" med "| t |"

For "uønsket" ville sekvensen være således:

uønsketun [| w | o | n | t |] ed (regel 2) [| u |] [| n |] [| w | o | n | t |] [| e |] [| d |] (regler 4, 5, 6, 7) u | n | w | o | n | t | e | d (med overskydende tegn fjernet) 

Du bør se, hvordan ord, der indeholder bogstaverne plejer vil blive talt på en anden måde end ord, der indeholder bogstaverne myre. Du skal også se, hvordan specialsagen styrer det komplette ord enestående har forrang over de andre regler, så dette ord tales som y | ou ... hellere end u | n ....