Programmering

Snakker Java!

Hvorfor vil du få programmene dine til å snakke? Til å begynne med er det morsomt og egnet for morsomme applikasjoner som spill. Og det er en mer seriøs tilgjengelighetsside. Jeg tenker her ikke bare på de som er naturlig vanskeligstilte når du bruker et visuelt grensesnitt, men også i situasjoner der det er umulig - eller til og med ulovlig - å ta blikket fra det du gjør.

Nylig har jeg jobbet med noen teknologier for å ta HTML- og XML-informasjon fra nettet [se "Få tilgang til verdens største database med Web DataBase-tilkobling" (JavaWorld, Mars 2001)]. Det falt meg inn at jeg kunne koble det arbeidet og denne ideen sammen for å bygge en snakkende nettleser. En slik nettleser vil være nyttig for å lytte til informasjon fra favorittnettstedene dine - for eksempel nyhetsoverskrifter - akkurat som å lytte til radio mens du er ute og går hunden din eller kjører til jobb. Selvfølgelig, med dagens teknologi, må du ha med deg den bærbare datamaskinen med tilkoblet mobiltelefon, men det upraktiske scenariet kan godt endres i nær fremtid med ankomsten av Java-aktiverte smarttelefoner som Nokia 9210 (9290 i OSS).

Kanskje mer nyttig på kort sikt vil være en e-postleser, også mulig takket være JavaMail API. Denne applikasjonen vil sjekke innboksen din med jevne mellomrom, og oppmerksomheten din vil bli tiltrukket av en stemme fra ingensteds som kunngjør "Du har ny post, vil du at jeg skal lese den for deg?" På en lignende måte kan du vurdere en snakkende påminnelse - knyttet til dagbokapplikasjonen din - som roper "Ikke glem møtet med sjefen på 10 minutter!"

Forutsatt at du selges på disse ideene, eller har noen gode ideer, vil vi gå videre. Jeg begynner med å vise hvordan jeg legger den medfølgende zip-filen til å fungere, slik at du kan komme i gang med en gang og hoppe over implementeringsdetaljene hvis du synes det er for mye hardt arbeid.

Testkjør talemotoren

For å bruke talemotoren, må du inkludere jw-0817-javatalk.zip-filen i CLASSPATH og kjøre com.lotontech.speech.Talker klasse fra kommandolinjen eller fra et Java-program.

For å kjøre den fra kommandolinjen, skriv:

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

For å kjøre det fra et Java-program, inkluderer du bare to linjer med kode:

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

På dette punktet lurer du sannsynligvis på formatet til "h | e | l | oo" streng du oppgir på kommandolinjen eller gir til siPhoneWord (...) metode. La meg forklare.

Talemotoren fungerer ved å sammenkorte korte lydprøver som representerer de minste enhetene av menneskelig - i dette tilfellet engelsk - tale. Disse lydprøvene, kalt allofoner, er merket med en, to eller tre bokstaver. Noen identifikatorer er åpenbare og andre ikke så åpenbare, som du kan se fra den fonetiske representasjonen av ordet "hei".

  • h - høres ut som du forventer
  • e - høres ut som du forventer
  • l - høres ut som du forventer, men legg merke til at jeg har redusert en dobbel "l" til en enkelt
  • oo - er lyden for "hei", ikke for "bot", og ikke for "også"

Her er en liste over tilgjengelige allofoner:

  • en - som hos katt
  • b - som i drosjen
  • c - som hos katt
  • d - som i prikk
  • e - som i bet
  • f - som i frosk
  • g - som i frosk
  • h - som i svin
  • Jeg - som hos gris
  • j - som i jig
  • k - som i keg
  • l - som i beinet
  • m - som i met
  • n - som i begynnelsen
  • o - som i ikke
  • s - som i gryte
  • r - som i råte
  • s - som i lørdag
  • t - som i lørdag
  • u - som sagt
  • v - som i har
  • w - som i vått
  • y - som i ennå
  • z - som i dyreparken
  • aa - som i falske
  • ay - som i høy
  • ee - som i bien
  • ii - som i høyden
  • oo - som i gang
  • bb - variasjon av b med ulik vekt
  • dd - variasjon av d med ulik vekt
  • ggg - variasjon av g med ulik vekt
  • hh - variasjon av h med ulik vekt
  • ll - variasjon av l med ulik vekt
  • nn - variasjon av n med ulik vekt
  • rr - variasjon av r med ulik vekt
  • tt - variasjon av t med ulik vekt
  • yy - variasjon av y med ulik vekt
  • ar - som i bil
  • aer - som i pleie
  • ch - som i hvilken
  • ck - som i sjakk
  • øre - som i øl
  • er - som senere
  • feil - som senere (lengre lyd)
  • ng - som i fôring
  • eller - som i lov
  • ou - som i dyreparken
  • ouu - som i dyrehagen (lengre lyd)
  • ow - som hos ku
  • oy - som i gutt
  • sh - som i lukket
  • th - som i ting
  • dth - som i dette
  • uh - variasjon av u
  • wh - som i hvor
  • zh - som på asiatisk

I menneskelig tale stiger og faller ordhøyde gjennom en hvilken som helst setning. Denne intonasjonen gjør at talen høres mer naturlig ut, mer følelsesladet og gjør det mulig å skille spørsmål fra utsagn. Hvis du noen gang har hørt Stephen Hawkings syntetiske stemme, forstår du hva jeg snakker om. Tenk på disse to setningene:

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

Som du kanskje har gjettet, er måten å heve intonasjonen på å bruke store bokstaver. Du må eksperimentere litt med dette, og hintet mitt er at du bør konsentrere deg om de lange vokallydene.

Det er alt du trenger å vite for å bruke programvaren, men hvis du er interessert i hva som skjer under panseret, kan du lese videre.

Implementere talemotoren

Talemotoren krever bare en klasse å implementere, med fire metoder. Den bruker Java Sound API som følger med J2SE 1.3. Jeg vil ikke gi en omfattende opplæring av Java Sound API, men du lærer med et godt eksempel. Du vil finne at det ikke er mye, og kommentarene forteller deg hva du trenger å vite.

Her er den grunnleggende definisjonen av Talker klasse:

pakke com.lotontech.speech; importere javax.sound.sampled. *; importer java.io. *; importer java.util. *; importer java.net. *; public class Talker {private SourceDataLine line = null; } 

Hvis du løper Talker fra kommandolinjen, hoved(...) metoden nedenfor vil tjene som inngangspunkt. Det tar det første kommandolinjeargumentet, hvis det finnes, og sender det til siPhoneWord (...) metode:

/ * * Denne metoden snakker et fonetisk ord spesifisert på kommandolinjen. * / public static void main (String args []) {Talker player = new Talker (); if (args.length> 0) player.sayPhoneWord (args [0]); System.exit (0); } 

De siPhoneWord (...) metoden kalles av hoved(...) ovenfor, eller det kan kalles direkte fra Java-applikasjonen eller plug-in-støttet applet. Det ser mer komplisert ut enn det er. I hovedsak går det ganske enkelt gjennom ordet allofoner - atskilt med "|"symboler i inngangsteksten - og spiller dem en etter en gjennom en lydutgangskanal. For å få den til å høres mer naturlig sammen, fusjonerer jeg slutten på hver lydprøve med begynnelsen på den neste:

/ * * Denne metoden snakker det gitte fonetiske ordet. * / public void sayPhoneWord (String word) {// - Sett opp en dummy byte array for forrige lyd - byte [] previousSound = null; // - Del inngangsstrengen i separate allofoner - StringTokenizer st = new StringTokenizer (word, "|", false); while (st.hasMoreTokens ()) {// - Konstruer et filnavn for allofonen - Streng thisPhoneFile = st.nextToken (); thisPhoneFile = "/ allophones /" + thisPhoneFile + ". au"; // - Få dataene fra filen - byte [] thisSound = getSound (thisPhoneFile); if (previousSound! = null) {// - Slå sammen den forrige allofonen med denne, hvis vi kan - int mergeCount = 0; hvis (forrigeSound.length> = 500 && thisSound.length> = 500) mergeCount = 500; for (int i = 0; i

Ved slutten av siPhoneWord (), vil du se at det ringer spill lyd(...) å sende ut en individuell lydprøve (en allofon), og den ringer avløp(...) for å skylle lydkanalen. Her er koden for spill lyd(...):

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

Og for avløp(...):

/ * * Denne metoden skyller lydkanalen. * / private void drain () {if (line! = null) line.drain (); prøv {Thread.sleep (100);} catch (Unntak e) {}} 

Nå, hvis du ser tilbake på siPhoneWord (...) metode, vil du se at det er en metode jeg ennå ikke har dekket: getSound (...).

getSound (...) leser i en forhåndsinnspilt lydprøve, som byte-data, fra en au-fil. Når jeg sier en fil, mener jeg en ressurs i den medfølgende zip-filen. Jeg trekker skillet fordi måten du får tak i en JAR-ressurs - ved hjelp av getResource (...) metode - går annerledes enn måten du får tak i en fil, et ikke åpenbart faktum.

For en blow-by-blow-konto for å lese dataene, konvertere lydformatet, starte en lydutgangslinje (hvorfor de kaller det en SourceDataLine, Jeg vet ikke), og når jeg monterer byte-dataene, henviser jeg deg til kommentarene i koden som følger:

/ * * Denne metoden leser 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 for avspilling - 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 (), true); stream = AudioSystem.getAudioInputStream (tmpFormat, stream); format = tmpFormat; } DataLine.Info info = ny DataLine.Info (Clip.class, format, ((int) stream.getFrameLength () * format.getFrameSize ())); if (line == null) {// - Utgangslinjen er ikke instantiert ennå - // - Kan vi finne en passende type linje? - DataLine.Info outInfo = ny DataLine.Info (SourceDataLine.class, format); hvis (! AudioSystem.isLineSupported (outInfo)) {System.out.println ("Linjematching" + outInfo + "støttes ikke."); kaste nytt unntak ("Linjematching" + outInfo + "støttes ikke."); } // - Åpne kildedatelinjen (utgangslinjen) - linje = (SourceDataLine) AudioSystem.getLine (outInfo); line.open (format, 50000); line.start (); } // - Noen størrelsesberegninger - int frameSizeInBytes = format.getFrameSize (); int bufferLengthInFrames = line.getBufferSize () / 8; int bufferLengthInBytes = bufferLengthInFrames * frameSizeInBytes; byte [] data = ny byte [bufferLengthInBytes]; // - Les databytes og telle dem - int numBytesRead = 0; hvis ((numBytesRead = stream.read (data))! = -1) {int numBytesRemaining = numBytesRead; } // - Avkutt byteoppsettet til riktig størrelse - byte [] newData = ny byte [numBytesRead]; for (int i = 0; i

Så det er det. En talesyntetiser i omtrent 150 linjer med kode, inkludert kommentarer. Men det er ikke helt over.

Tekst-til-tale-konvertering

Å spesifisere ord fonetisk kan virke litt kjedelig, så hvis du har tenkt å bygge et av eksempelapplikasjonene jeg foreslo i innledningen, vil du gi vanlig tekst som inndata som skal leses.

Etter å ha sett på problemet har jeg gitt en eksperimentell tekst-til-tale-konverteringsklasse i zip-filen. Når du kjører den, vil utdataene gi deg innsikt i hva den gjør.

Du kan kjøre en tekst-til-tale-omformer med en kommando som denne:

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

Det du ser som utdata ser ut som:

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

Eller hva med å kjøre det slik:

java com.lotontech.speech.Converter "Jeg liker å lese JavaWorld" 

å se (og høre) dette:

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

Hvis du lurer på hvordan det fungerer, kan jeg fortelle deg at tilnærmingen min er ganske enkel, og består av et sett med regler for erstatning av tekst som brukes i en bestemt rekkefølge. Her er noen eksempler på regler som du kanskje vil bruke mentalt, for ordene "maur", "ønsker", "ønsket", "uønsket" og "unik":

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

For "uønsket" vil sekvensen være slik:

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 overflødige tegn fjernet) 

Du bør se hvordan ord som inneholder bokstavene vil ikke vil bli snakket på en annen måte til ord som inneholder bokstavene maur. Du bør også se hvordan spesialsaken regelen for hele ordet unik har forrang over de andre reglene slik at dette ordet blir sagt som y | ou ... heller enn u | n ....

$config[zx-auto] not found$config[zx-overlay] not found