9 | 🎤 10 |
11 |12 | 13 | This project is designed to be simple and efficient, using the speech engines created by Google to provide functionality for parts of the API. Essentially, it is an API written in Java, including a recognizer, synthesizer, and a microphone capture utility. The project uses Google services for the synthesizer and recognizer. While this requires an Internet connection, it provides a complete, modern, and fully functional speech API in Java. 14 | 15 |
16 | 17 | --- 18 | 19 | [](https://github.com/goxr3plus/java-google-speech-api/releases) 20 | [![GitHub contributors][contributors-image]][contributors-url] 21 | [](http://hits.dwyl.io/goxr3plus/java-google-speech-api) 22 | [](https://github.com/goxr3plus/java-google-speech-api/releases) 23 | 24 | 25 | [contributors-url]: https://github.com/goxr3plus/java-google-speech-api/graphs/contributors 26 | [contributors-image]: https://img.shields.io/github/contributors/goxr3plus/java-google-speech-api.svg 27 | 28 | 29 | ## Google has released it's official library for [Google Speech Recognition](https://github.com/googleapis/java-speech) . Check this issue for Official Google Speech Library code solution -> [#4](https://github.com/goxr3plus/java-google-speech-api/issues/4) 30 | 31 | 32 | ### Add it to your project using JitPack : 33 | 34 | https://jitpack.io/private#goxr3plus/java-google-speech-api 35 | 36 | >Step 1. Add the JitPack repository to your build file 37 | ``` XML 38 |162 | * Note: This feature is experimental. 163 | *
164 | * 165 | * @param tl 166 | * TL 167 | * @param af 168 | * AF 169 | * @throws LineUnavailableException 170 | * If the Line is unavailable 171 | * @throws InterruptedException 172 | * InterruptedException 173 | */ 174 | public void recognize(TargetDataLine tl , AudioFormat af) throws LineUnavailableException , InterruptedException { 175 | //Generates a unique ID for the response. 176 | final long PAIR = MIN + (long) ( Math.random() * ( ( MAX - MIN ) + 1L ) ); 177 | 178 | //Generates the Downstream URL 179 | final String API_DOWN_URL = GOOGLE_DUPLEX_SPEECH_BASE + "down?maxresults=1&pair=" + PAIR; 180 | 181 | //Generates the Upstream URL 182 | final String API_UP_URL = GOOGLE_DUPLEX_SPEECH_BASE + "up?lang=" + language + "&lm=dictation&client=chromium&pair=" + PAIR + "&key=" + API_KEY 183 | + "&continuous=true&interim=true"; //Tells Google to constantly monitor the stream; 184 | 185 | //Opens downChannel 186 | Thread downChannel = this.downChannel(API_DOWN_URL); 187 | 188 | //Opens upChannel 189 | Thread upChannel = this.upChannel(API_UP_URL, tl, af); 190 | try { 191 | downChannel.join(); 192 | upChannel.interrupt(); 193 | upChannel.join(); 194 | } catch (InterruptedException e) { 195 | downChannel.interrupt(); 196 | downChannel.join(); 197 | upChannel.interrupt(); 198 | upChannel.join(); 199 | } 200 | 201 | } 202 | 203 | /** 204 | * This code opens a new Thread that connects to the downstream URL. Due to threading, the best way to handle this is through the use of 205 | * listeners. 206 | * 207 | * @param The 208 | * URL you want to connect to. 209 | */ 210 | private Thread downChannel(String urlStr) { 211 | final String url = urlStr; 212 | Thread downChannelThread = new Thread("Downstream Thread") { 213 | public void run() { 214 | // handler for DOWN channel http response stream - httpsUrlConn 215 | // response handler should manage the connection.... ?? 216 | // assign a TIMEOUT Value that exceeds by a safe factor 217 | // the amount of time that it will take to write the bytes 218 | // to the UPChannel in a fashion that mimics a liveStream 219 | // of the audio at the applicable Bitrate. BR=sampleRate * bits per sample 220 | // Note that the TLS session uses "* SSLv3, TLS alert, Client hello (1): " 221 | // to wake up the listener when there are additional bytes. 222 | // The mechanics of the TLS session should be transparent. Just use 223 | // httpsUrlConn and allow it enough time to do its work. 224 | Scanner inStream = openHttpsConnection(url); 225 | if (inStream == null) { 226 | //ERROR HAS OCCURED 227 | System.out.println("Error has occured"); 228 | return; 229 | } 230 | String response; 231 | while (inStream.hasNext() && ( response = inStream.nextLine() ) != null) { 232 | if (response.length() > 17) {//Prevents blank responses from Firing 233 | GoogleResponse gr = new GoogleResponse(); 234 | parseResponse(response, gr); 235 | fireResponseEvent(gr); 236 | } 237 | } 238 | inStream.close(); 239 | System.out.println("Finished write on down stream..."); 240 | } 241 | }; 242 | downChannelThread.start(); 243 | return downChannelThread; 244 | } 245 | 246 | /** 247 | * Used to initiate the URL chunking for the upChannel. 248 | * 249 | * @param urlStr 250 | * The URL string you want to upload 2 251 | * @param data 252 | * The data you want to send to the URL 253 | * @param sampleRate 254 | * The specified sample rate of the data. 255 | */ 256 | private void upChannel(String urlStr , byte[][] data , int sampleRate) { 257 | final String murl = urlStr; 258 | final byte[][] mdata = data; 259 | final int mSampleRate = sampleRate; 260 | new Thread("Upstream File Thread") { 261 | public void run() { 262 | openHttpsPostConnection(murl, mdata, mSampleRate); 263 | //Google does not return data via this URL 264 | } 265 | }.start(); 266 | } 267 | 268 | /** 269 | * Streams data from the TargetDataLine to the API. 270 | * 271 | * @param urlStr 272 | * The URL to stream to 273 | * @param tl 274 | * The target data line to stream from. 275 | * @param af 276 | * The AudioFormat to stream with.` 277 | * @throws LineUnavailableException 278 | * If cannot open or stream the TargetDataLine. 279 | */ 280 | private Thread upChannel(String urlStr , TargetDataLine tl , AudioFormat af) throws LineUnavailableException { 281 | final String murl = urlStr; 282 | final TargetDataLine mtl = tl; 283 | final AudioFormat maf = af; 284 | if (!mtl.isOpen()) { 285 | mtl.open(maf); 286 | mtl.start(); 287 | } 288 | Thread upChannelThread = new Thread("Upstream Thread") { 289 | public void run() { 290 | openHttpsPostConnection(murl, mtl, (int) maf.getSampleRate()); 291 | } 292 | }; 293 | upChannelThread.start(); 294 | return upChannelThread; 295 | 296 | } 297 | 298 | /** 299 | * Opens a HTTPS connection to the specified URL string 300 | * 301 | * @param urlStr 302 | * The URL you want to visit 303 | * @return The Scanner to access aforementioned data. 304 | */ 305 | private Scanner openHttpsConnection(String urlStr) { 306 | int resCode = -1; 307 | try { 308 | URL url = new URL(urlStr); 309 | URLConnection urlConn = url.openConnection(); 310 | if (! ( urlConn instanceof HttpsURLConnection )) { 311 | throw new IOException("URL is not an Https URL"); 312 | } 313 | HttpsURLConnection httpConn = (HttpsURLConnection) urlConn; 314 | httpConn.setAllowUserInteraction(false); 315 | // TIMEOUT is required 316 | httpConn.setInstanceFollowRedirects(true); 317 | httpConn.setRequestMethod("GET"); 318 | httpConn.connect(); 319 | resCode = httpConn.getResponseCode(); 320 | if (resCode == HttpsURLConnection.HTTP_OK) { 321 | return new Scanner(httpConn.getInputStream(), "UTF-8"); 322 | } else { 323 | System.out.println("Error: " + resCode); 324 | } 325 | } catch (MalformedURLException e) { 326 | e.printStackTrace(); 327 | } catch (IOException e) { 328 | e.printStackTrace(); 329 | } 330 | return null; 331 | } 332 | 333 | /** 334 | * Opens a HTTPSPostConnection that posts data from a TargetDataLine input 335 | * 336 | * @param murl 337 | * The URL you want to post to. 338 | * @param mtl 339 | * The TargetDataLine you want to post data from. Note should be open 340 | */ 341 | private void openHttpsPostConnection(String murl , TargetDataLine mtl , int sampleRate) { 342 | URL url; 343 | try { 344 | url = new URL(murl); 345 | HttpsURLConnection httpConn = getHttpsURLConnection(sampleRate, url); 346 | // this opens a connection, then sends POST & headers. 347 | final OutputStream out = httpConn.getOutputStream(); 348 | //Note : if the audio is more than 15 seconds 349 | // don't write it to UrlConnInputStream all in one block as this sample does. 350 | // Rather, segment the byteArray and on intermittently, sleeping thread 351 | // supply bytes to the urlConn Stream at a rate that approaches 352 | // the bitrate ( =30K per sec. in this instance ). 353 | System.out.println("Starting to write data to output..."); 354 | ais = new AudioInputStream(mtl); 355 | 356 | AudioSystem.write(ais, FLACFileWriter.FLAC, out); 357 | //Output Stream is automatically closed 358 | // do you need the trailer? 359 | // NOW you can look at the status. 360 | 361 | //Diagonostic Code. 362 | /* 363 | * int resCode = httpConn.getResponseCode(); if (resCode / 100 != 2) { System.out.println("ERROR"); } Scanner scanner = new 364 | * Scanner(httpConn.getInputStream()); while(scanner.hasNextLine()){ System.out.println("UPSTREAM READS:" + scanner.nextLine()); } 365 | * scanner.close(); 366 | */ 367 | System.out.println("Upstream Closed..."); 368 | } catch (IOException ex) { 369 | ex.printStackTrace(); 370 | } 371 | } 372 | 373 | /** 374 | * Force HttpsPostConnection Thread to Stop , Closes this audio input stream and releases any system resources associated with the stream. 375 | */ 376 | public void stopSpeechRecognition() { 377 | //AudioInputStream != null 378 | if (ais != null) 379 | try { 380 | //Close the AudioInput Stream 381 | //so forcing AudioSystem.write(....) to exit , so UpStream Thread will be closed 382 | ais.close(); 383 | } catch (Exception ex) { //catch a general exception here to have our mind peaceful 384 | ex.printStackTrace(); 385 | } 386 | 387 | } 388 | 389 | /** 390 | * Opens a chunked HTTPS post connection and returns a Scanner with incoming data from Google Server Used for to get UPStream Chunked HTTPS 391 | * ensures unlimited file size. 392 | * 393 | * @param urlStr 394 | * The String for the URL 395 | * @param data 396 | * The data you want to send the server 397 | * @param sampleRate 398 | * The sample rate of the flac file. 399 | * @return A Scanner to access the server response. (Probably will never be used) 400 | */ 401 | private Scanner openHttpsPostConnection(String urlStr , byte[][] data , int sampleRate) { 402 | byte[][] mextrad = data; 403 | int resCode = -1; 404 | OutputStream out = null; 405 | // int http_status; 406 | try { 407 | URL url = new URL(urlStr); 408 | HttpsURLConnection httpConn = getHttpsURLConnection(sampleRate, url); 409 | // this opens a connection, then sends POST & headers. 410 | out = httpConn.getOutputStream(); 411 | //Note : if the audio is more than 15 seconds 412 | // dont write it to UrlConnInputStream all in one block as this sample does. 413 | // Rather, segment the byteArray and on intermittently, sleeping thread 414 | // supply bytes to the urlConn Stream at a rate that approaches 415 | // the bitrate ( =30K per sec. in this instance ). 416 | System.out.println("Starting to write"); 417 | for (byte[] dataArray : mextrad) { 418 | out.write(dataArray); // one big block supplied instantly to the underlying chunker wont work for duration > 15 s. 419 | try { 420 | Thread.sleep(1000);//Delays the Audio so Google thinks its a mic. 421 | } catch (InterruptedException e) { 422 | e.printStackTrace(); 423 | } 424 | } 425 | out.write(FINAL_CHUNK); 426 | System.out.println("IO WRITE DONE"); 427 | // do you need the trailer? 428 | // NOW you can look at the status. 429 | resCode = httpConn.getResponseCode(); 430 | if (resCode / 100 != 2) { 431 | System.out.println("ERROR"); 432 | } 433 | if (resCode == HttpsURLConnection.HTTP_OK) { 434 | return new Scanner(httpConn.getInputStream(), "UTF-8"); 435 | } else { 436 | System.out.println("HELP: " + resCode); 437 | } 438 | } catch (MalformedURLException e) { 439 | e.printStackTrace(); 440 | } catch (IOException e) { 441 | e.printStackTrace(); 442 | } 443 | return null; 444 | } 445 | 446 | /** 447 | * @param sampleRate 448 | * The samleRate 449 | * @param url 450 | * The URL 451 | * @return The HTTPSURLConnection 452 | * @throws IOException 453 | * if something goes wrong in reading the file. 454 | */ 455 | private HttpsURLConnection getHttpsURLConnection(int sampleRate , URL url) throws IOException { 456 | URLConnection urlConn = url.openConnection(); 457 | if (! ( urlConn instanceof HttpsURLConnection )) { 458 | throw new IOException("URL is not an Https URL"); 459 | } 460 | HttpsURLConnection httpConn = (HttpsURLConnection) urlConn; 461 | httpConn.setAllowUserInteraction(false); 462 | httpConn.setInstanceFollowRedirects(true); 463 | httpConn.setRequestMethod("POST"); 464 | httpConn.setDoOutput(true); 465 | httpConn.setChunkedStreamingMode(0); 466 | httpConn.setRequestProperty("Transfer-Encoding", "chunked"); 467 | httpConn.setRequestProperty("Content-Type", "audio/x-flac; rate=" + sampleRate); 468 | // also worked with ("Content-Type", "audio/amr; rate=8000"); 469 | httpConn.connect(); 470 | return httpConn; 471 | } 472 | 473 | /** 474 | * Converts the file into a byte[]. Also Android compatible. :) 475 | * 476 | * @param infile 477 | * The File you want to get the byte[] from. 478 | * @return The byte[] 479 | * @throws IOException 480 | * if something goes wrong in reading the file. 481 | */ 482 | private byte[] mapFileIn(File infile) throws IOException { 483 | return Files.readAllBytes(infile.toPath()); 484 | } 485 | 486 | /** 487 | * Parses the String into a GoogleResponse object 488 | * 489 | * @param rawResponse 490 | * The String you want to parse 491 | * @param gr 492 | * the GoogleResponse object to save the data into. 493 | */ 494 | private void parseResponse(String rawResponse , GoogleResponse gr) { 495 | if (rawResponse == null || !rawResponse.contains("\"result\"") || rawResponse.equals("{\"result\":[]}")) { 496 | return; 497 | } 498 | gr.getOtherPossibleResponses().clear(); // Emptys the list 499 | if (rawResponse.contains("\"confidence\":")) { 500 | String confidence = StringUtil.substringBetween(rawResponse, "\"confidence\":", "}"); 501 | gr.setConfidence(confidence); 502 | } else { 503 | gr.setConfidence(String.valueOf(1)); 504 | } 505 | String response = StringUtil.substringBetween(rawResponse, "[{\"transcript\":\"", "\"}],"); 506 | if (response == null) { 507 | response = StringUtil.substringBetween(rawResponse, "[{\"transcript\":\"", "\",\""); 508 | } 509 | gr.setResponse(response); 510 | gr.setFinalResponse(rawResponse.contains("\"final\":true")); 511 | String[] currentHypos = rawResponse.split("\\[\\{\"transcript\":\""); 512 | for (int i = 2; i < currentHypos.length; i++) { 513 | String cleaned = currentHypos[i].substring(0, currentHypos[i].indexOf('\"')); 514 | gr.getOtherPossibleResponses().add(cleaned); 515 | } 516 | } 517 | 518 | /** 519 | * Adds GSpeechResponse Listeners that fire when Google sends a response. 520 | * 521 | * @param rl 522 | * The Listeners you want to add 523 | */ 524 | public synchronized void addResponseListener(GSpeechResponseListener rl) { 525 | responseListeners.add(rl); 526 | } 527 | 528 | /** 529 | * Removes GSpeechResponseListeners that fire when Google sends a response. 530 | * 531 | * @param rl 532 | * The Listeners you want to remove 533 | */ 534 | public synchronized void removeResponseListener(GSpeechResponseListener rl) { 535 | responseListeners.remove(rl); 536 | } 537 | 538 | /** 539 | * Fires responseListeners 540 | * 541 | * @param gr 542 | * The Google Response (in this case the response event). 543 | */ 544 | private synchronized void fireResponseEvent(GoogleResponse gr) { 545 | for (GSpeechResponseListener gl : responseListeners) { 546 | gl.onResponse(gr); 547 | } 548 | } 549 | 550 | /** 551 | * Chunks audio into smaller chunks to stream to the duplex API 552 | * 553 | * @param data 554 | * The data you want to break into smaller pieces 555 | * @return the byte[][] containing on array of chunks. 556 | */ 557 | private byte[][] chunkAudio(byte[] data) { 558 | if (data.length >= MAX_SIZE) {//If larger than 1MB 559 | int frame = MAX_SIZE / 2; 560 | int numOfChunks = (int) ( data.length / ( (double) frame ) ) + 1; 561 | byte[][] data2D = new byte[numOfChunks][]; 562 | for (int i = 0, j = 0; i < data.length && j < data2D.length; i += frame, j++) { 563 | int length = ( data.length - i < frame ) ? data.length - i : frame; 564 | data2D[j] = new byte[length]; 565 | System.arraycopy(data, i, data2D[j], 0, length); 566 | } 567 | return data2D; 568 | } else { 569 | byte[][] tmpData = new byte[1][data.length]; 570 | System.arraycopy(data, 0, tmpData[0], 0, data.length); 571 | return tmpData; 572 | } 573 | } 574 | } 575 | -------------------------------------------------------------------------------- /src/main/java/com/goxr3plus/speech/recognizer/GSpeechResponseListener.java: -------------------------------------------------------------------------------- 1 | package com.goxr3plus.speech.recognizer; 2 | 3 | /** 4 | * Response listeners for URL connections. 5 | * @author Skylion 6 | * 7 | */ 8 | public interface GSpeechResponseListener { 9 | 10 | public void onResponse(GoogleResponse gr); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/goxr3plus/speech/recognizer/GoogleResponse.java: -------------------------------------------------------------------------------- 1 | package com.goxr3plus.speech.recognizer; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /****************************************************************************** 7 | * Class that holds the response and confidence of a Google recognizer request 8 | * 9 | * @author Luke Kuza, Duncan Jauncey, Aaron Gokaslan 10 | ******************************************************************************/ 11 | public class GoogleResponse { 12 | 13 | /** 14 | * Variable that holds the response 15 | */ 16 | private String response; 17 | /** 18 | * Variable that holds the confidence score 19 | */ 20 | private String confidence; 21 | 22 | /** 23 | * List that holds other possible responses for this request. 24 | */ 25 | private List39 | //This class lets a Servlet send its response data as an HTTP/1.1 chunked 40 | //stream. Chunked streams are a way to send arbitrary-length data without 41 | //having to know beforehand how much you're going to send. They are 42 | //introduced by a "Transfer-Encoding: chunked" header, so you have to 43 | //set that header when you make one of these streams. 44 | //
45 | //Sample usage: 46 | //
54 | //47 | //res.setHeader( "Transfer-Encoding", "chunked" ); 48 | //OutputStream out = res.getOutputStream(); 49 | //ChunkedOutputStream chunkOut = new ChunkedOutputStream( out ); 50 | //(write data to chunkOut instead of out) 51 | //(optionally set footers) 52 | //chunkOut.done(); 53 | //
55 | //Every time the stream gets flushed, a chunk is sent. When done() 56 | //is called, an empty chunk is sent, marking the end of the chunked 57 | //stream as per the chunking spec. 58 | //
59 | //Fetch the software.
145 | // The only reason we have to override the BufferedOutputStream version
146 | // of this is that it writes the array directly to the output stream
147 | // if doesn't fit in the buffer. So we make it use our own chunk-write
148 | // routine instead. Otherwise this is identical to the parent-class
149 | // version.
150 | // @param b the data to be written
151 | // @param off the start offset in the data
152 | // @param len the number of bytes that are written
153 | // @exception IOException if an I/O error occurred
154 | public synchronized void write( byte b[], int off, int len ) throws IOException
155 | {
156 | int avail = buf.length - count;
157 |
158 | if ( len <= avail )
159 | {
160 | System.arraycopy( b, off, buf, count, len );
161 | count += len;
162 | return;
163 | }
164 | flush();
165 | writeBuf( b, off, len );
166 | }
167 |
168 | /// The only routine that actually writes to the output stream.
169 | // This is where chunking semantics are implemented.
170 | // @exception IOException if an I/O error occurred
171 | @SuppressWarnings("deprecation")
172 | private void writeBuf( byte b[], int off, int len ) throws IOException
173 | {
174 | // Write the chunk length as a hex number.
175 | String lenStr = Integer.toString( len, 16 );
176 | lenStr.getBytes( 0, lenStr.length(), lenBytes, 0 );
177 | out.write( lenBytes );
178 | // Write a CRLF.
179 | out.write( crlf );
180 | // Write the data.
181 | if ( len != 0 )
182 | out.write( b, off, len );
183 | // Write a CRLF.
184 | out.write( crlf );
185 | // And flush the real stream.
186 | out.flush();
187 | }
188 |
189 | }
190 |
191 |
--------------------------------------------------------------------------------
/src/main/java/com/goxr3plus/speech/util/Complex.java:
--------------------------------------------------------------------------------
1 | package com.goxr3plus.speech.util;
2 |
3 |
4 | /*************************************************************************
5 | * Compilation: javac Complex.java
6 | * Execution: java Complex
7 | *
8 | * Data type for complex numbers.
9 | *
10 | * The data type is "immutable" so once you create and initialize
11 | * a Complex object, you cannot change it. The "final" keyword
12 | * when declaring re and im enforces this rule, making it a
13 | * compile-time error to change the .re or .im fields after
14 | * they've been initialized.
15 | *
16 | * Class based off of Princeton University's Complex.java class
17 | * @author Aaron Gokaslan, Princeton University
18 | *************************************************************************/
19 |
20 | public class Complex {
21 | private final double re; // the real part
22 | private final double im; // the imaginary part
23 |
24 | // create a new object with the given real and imaginary parts
25 | public Complex(double real, double imag) {
26 | re = real;
27 | im = imag;
28 | }
29 |
30 | // return a string representation of the invoking Complex object
31 | public String toString() {
32 | if (im == 0) return re + "";
33 | if (re == 0) return im + "i";
34 | if (im < 0) return re + " - " + (-im) + "i";
35 | return re + " + " + im + "i";
36 | }
37 |
38 | // return abs/modulus/magnitude and angle/phase/argument
39 | public double abs() { return Math.hypot(re, im); } // Math.sqrt(re*re + im*im)
40 | public double phase() { return Math.atan2(im, re); } // between -pi and pi
41 |
42 | // return a new Complex object whose value is (this + b)
43 | public Complex plus(Complex b) {
44 | Complex a = this; // invoking object
45 | double real = a.re + b.re;
46 | double imag = a.im + b.im;
47 | return new Complex(real, imag);
48 | }
49 |
50 | // return a new Complex object whose value is (this - b)
51 | public Complex minus(Complex b) {
52 | Complex a = this;
53 | double real = a.re - b.re;
54 | double imag = a.im - b.im;
55 | return new Complex(real, imag);
56 | }
57 |
58 | // return a new Complex object whose value is (this * b)
59 | public Complex times(Complex b) {
60 | Complex a = this;
61 | double real = a.re * b.re - a.im * b.im;
62 | double imag = a.re * b.im + a.im * b.re;
63 | return new Complex(real, imag);
64 | }
65 |
66 | // scalar multiplication
67 | // return a new object whose value is (this * alpha)
68 | public Complex times(double alpha) {
69 | return new Complex(alpha * re, alpha * im);
70 | }
71 |
72 | // return a new Complex object whose value is the conjugate of this
73 | public Complex conjugate() { return new Complex(re, -im); }
74 |
75 | // return a new Complex object whose value is the reciprocal of this
76 | public Complex reciprocal() {
77 | double scale = re*re + im*im;
78 | return new Complex(re / scale, -im / scale);
79 | }
80 |
81 | // return the real or imaginary part
82 | public double re() { return re; }
83 | public double im() { return im; }
84 |
85 | // return a / b
86 | public Complex divides(Complex b) {
87 | Complex a = this;
88 | return a.times(b.reciprocal());
89 | }
90 |
91 | // return a new Complex object whose value is the complex exponential of this
92 | public Complex exp() {
93 | return new Complex(Math.exp(re) * Math.cos(im), Math.exp(re) * Math.sin(im));
94 | }
95 |
96 | // return a new Complex object whose value is the complex sine of this
97 | public Complex sin() {
98 | return new Complex(Math.sin(re) * Math.cosh(im), Math.cos(re) * Math.sinh(im));
99 | }
100 |
101 | // return a new Complex object whose value is the complex cosine of this
102 | public Complex cos() {
103 | return new Complex(Math.cos(re) * Math.cosh(im), -Math.sin(re) * Math.sinh(im));
104 | }
105 |
106 | // return a new Complex object whose value is the complex tangent of this
107 | public Complex tan() {
108 | return sin().divides(cos());
109 | }
110 |
111 | // returns the magnitude of the imaginary number.
112 | public double getMagnitude(){
113 | return Math.sqrt(re*re+im*im);
114 | }
115 |
116 | public boolean equals(Complex other){
117 | if(other != null) {
118 | return (re == other.re) && (im == other.im);
119 | }
120 | return false;
121 | }
122 |
123 | }
124 |
--------------------------------------------------------------------------------
/src/main/java/com/goxr3plus/speech/util/FFT.java:
--------------------------------------------------------------------------------
1 | package com.goxr3plus.speech.util;
2 |
3 |
4 | /*************************************************************************
5 | * Compilation: javac FFT.java
6 | * Execution: java FFT N
7 | * Dependencies: Complex.java
8 | *
9 | * Compute the FFT and inverse FFT of a length N complex sequence.
10 | * Bare bones implementation that runs in O(N log N) time. Our goal
11 | * is to optimize the clarity of the code, rather than performance.
12 | *
13 | * Limitations
14 | * -----------
15 | * - assumes N is a power of 2
16 | *
17 | * - not the most memory efficient algorithm (because it uses
18 | * an object type for representing complex numbers and because
19 | * it re-allocates memory for the subarray, instead of doing
20 | * in-place or reusing a single temporary array)
21 | *
22 | *************************************************************************/
23 |
24 | /*************************************************************************
25 | * @author Skylion implementation
26 | * @author Princeton University for the actual algorithm.
27 | ************************************************************************/
28 |
29 | public class FFT {
30 |
31 | private FFT() {}
32 |
33 | // compute the FFT of x[], assuming its length is a power of 2
34 | public static Complex[] fft(Complex[] x) {
35 | int N = x.length;
36 |
37 | // base case
38 | if (N == 1) return new Complex[] { x[0] };
39 |
40 | // radix 2 Cooley-Tukey FFT
41 | if (N % 2 != 0) { throw new RuntimeException("N is not a power of 2"); }
42 |
43 | // fft of even terms
44 | Complex[] even = new Complex[N/2];
45 | for (int k = 0; k < N/2; k++) {
46 | even[k] = x[2*k];
47 | }
48 | Complex[] q = fft(even);
49 |
50 | // fft of odd terms
51 | Complex[] odd = even; // reuse the array
52 | for (int k = 0; k < N/2; k++) {
53 | odd[k] = x[2*k + 1];
54 | }
55 | Complex[] r = fft(odd);
56 |
57 | // combine
58 | Complex[] y = new Complex[N];
59 | for (int k = 0; k < N/2; k++) {
60 | double kth = -2 * k * Math.PI / N;
61 | Complex wk = new Complex(Math.cos(kth), Math.sin(kth));
62 | y[k] = q[k].plus(wk.times(r[k]));
63 | y[k + N/2] = q[k].minus(wk.times(r[k]));
64 | }
65 | return y;
66 | }
67 |
68 |
69 | // compute the inverse FFT of x[], assuming its length is a power of 2
70 | public static Complex[] ifft(Complex[] x) {
71 | int N = x.length;
72 | Complex[] y = new Complex[N];
73 |
74 | // take conjugate
75 | for (int i = 0; i < N; i++) {
76 | y[i] = x[i].conjugate();
77 | }
78 |
79 | // compute forward FFT
80 | y = fft(y);
81 |
82 | // take conjugate again
83 | for (int i = 0; i < N; i++) {
84 | y[i] = y[i].conjugate();
85 | }
86 |
87 | // divide by N
88 | for (int i = 0; i < N; i++) {
89 | y[i] = y[i].times(1.0 / N);
90 | }
91 |
92 | return y;
93 |
94 | }
95 |
96 | // compute the circular convolution of x and y
97 | public static Complex[] cconvolve(Complex[] x, Complex[] y) {
98 |
99 | // should probably pad x and y with 0s so that they have same length
100 | // and are powers of 2
101 | if (x.length != y.length) { throw new RuntimeException("Dimensions don't agree"); }
102 |
103 | int N = x.length;
104 |
105 | // compute FFT of each sequence
106 | Complex[] a = fft(x);
107 | Complex[] b = fft(y);
108 |
109 | // point-wise multiply
110 | Complex[] c = new Complex[N];
111 | for (int i = 0; i < N; i++) {
112 | c[i] = a[i].times(b[i]);
113 | }
114 |
115 | // compute inverse FFT
116 | return ifft(c);
117 | }
118 |
119 |
120 | // compute the linear convolution of x and y
121 | public static Complex[] convolve(Complex[] x, Complex[] y) {
122 | Complex ZERO = new Complex(0, 0);
123 |
124 | Complex[] a = new Complex[2*x.length];
125 | for (int i = 0; i < x.length; i++) a[i] = x[i];
126 | for (int i = x.length; i < 2*x.length; i++) a[i] = ZERO;
127 |
128 | Complex[] b = new Complex[2*y.length];
129 | for (int i = 0; i < y.length; i++) b[i] = y[i];
130 | for (int i = y.length; i < 2*y.length; i++) b[i] = ZERO;
131 |
132 | return cconvolve(a, b);
133 | }
134 |
135 | }
136 |
--------------------------------------------------------------------------------
/src/main/java/com/goxr3plus/speech/util/StringUtil.java:
--------------------------------------------------------------------------------
1 | package com.goxr3plus.speech.util;
2 |
3 | /**
4 | * A string utility class for commonly used methods.
5 | * These methods are particularly useful for parsing.
6 | * @author Skylion
7 | */
8 | public class StringUtil {
9 |
10 | private StringUtil() {};//Prevents instantiation
11 |
12 | /**
13 | * Removes quotation marks from beginning and end of string.
14 | * @param s The string you want to remove the quotation marks from.
15 | * @return The modified String.
16 | */
17 | public static String stripQuotes(String s) {
18 | int start = 0;
19 | if( s.startsWith("\"") ) {
20 | start = 1;
21 | }
22 | int end = s.length();
23 | if( s.endsWith("\"") ) {
24 | end = s.length() - 1;
25 | }
26 | return s.substring(start, end);
27 | }
28 |
29 | /**
30 | * Returns the first instance of String found exclusively between part1 and part2.
31 | * @param s The String you want to substring.
32 | * @param part1 The beginning of the String you want to search for.
33 | * @param part2 The end of the String you want to search for.
34 | * @return The String between part1 and part2.
35 | * If the s does not contain part1 or part2, the method returns null.
36 | */
37 | public static String substringBetween(String s, String part1, String part2) {
38 | String sub = null;
39 |
40 | int i = s.indexOf(part1);
41 | int j = s.indexOf(part2, i + part1.length());
42 |
43 | if (i != -1 && j != -1) {
44 | int nStart = i + part1.length();
45 | sub = s.substring(nStart, j);
46 | }
47 |
48 | return sub;
49 | }
50 |
51 | /**
52 | * Gets the string exclusively between the first instance of part1 and the last instance of part2.
53 | * @param s The string you want to trim.
54 | * @param part1 The term to trim after first instance.
55 | * @param part2 The term to before last instance of.
56 | * @return The trimmed String
57 | */
58 | public static String trimString(String s, String part1, String part2){
59 | if(!s.contains(part1) || !s.contains(part2)){
60 | return null;
61 | }
62 | int first = s.indexOf(part1) + part1.length() + 1;
63 | String tmp = s.substring(first);
64 | int last = tmp.lastIndexOf(part2);
65 | tmp = tmp.substring(0, last);
66 | return tmp;
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/test/java/translator/TranslatorTest.java:
--------------------------------------------------------------------------------
1 | package translator;
2 |
3 | import static org.junit.Assert.assertTrue;
4 |
5 | import java.io.IOException;
6 |
7 | import org.junit.Test;
8 |
9 | import com.goxr3plus.speech.translator.GoogleTranslate;
10 |
11 | public class TranslatorTest {
12 |
13 | @Test
14 | public void testString() throws IOException {
15 |
16 | String translatedText = GoogleTranslate.translate("Hola . Buenos días");
17 | String expecetedText = "Hello . Good morning";
18 |
19 | System.out.println(translatedText);
20 | assertTrue(translatedText.equals(expecetedText));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
60 | //Fetch the entire Acme package.
61 |
62 | public class ChunkedOutputStream extends BufferedOutputStream
63 | {
64 |
65 | private static final byte[] crlf = { 13, 10 };
66 | private byte[] lenBytes = new byte[20]; // big enough for any number in hex
67 | private List