001/** 002 * Portions Copyright 2003 Sun Microsystems, Inc. 003 * Portions Copyright 1999-2001 Language Technologies Institute, 004 * Carnegie Mellon University. 005 * All Rights Reserved. Use is subject to license terms. 006 * 007 * See the file "license.terms" for information on usage and 008 * redistribution of this file, and for a DISCLAIMER OF ALL 009 * WARRANTIES. 010 */ 011package com.sun.speech.freetts.diphone; 012import java.io.BufferedInputStream; 013import java.io.BufferedReader; 014import java.io.DataInputStream; 015import java.io.DataOutputStream; 016import java.io.FileInputStream; 017import java.io.FileNotFoundException; 018import java.io.FileOutputStream; 019import java.io.IOException; 020import java.io.InputStream; 021import java.io.InputStreamReader; 022import java.lang.ref.Reference; 023import java.lang.ref.WeakReference; 024import java.net.URL; 025import java.nio.ByteBuffer; 026import java.nio.MappedByteBuffer; 027import java.nio.channels.FileChannel; 028import java.util.HashMap; 029import java.util.Iterator; 030import java.util.LinkedHashMap; 031import java.util.Map; 032import java.util.NoSuchElementException; 033import java.util.StringTokenizer; 034import java.util.logging.Level; 035import java.util.logging.Logger; 036 037import com.sun.speech.freetts.relp.Sample; 038import com.sun.speech.freetts.relp.SampleInfo; 039import com.sun.speech.freetts.util.BulkTimer; 040import com.sun.speech.freetts.util.Utilities; 041 042/** 043 * Represents and manages the unit data for all diphones. The diphone 044 * data set is stored in a set of data files. These data are loaded by this 045 * class into internal data structures before diphone synthesis can 046 * occur. 047 * <p> 048 *The diphone data set is one of the largest sets of data that 049 * needs to be loaded by the synthesizer and therefore can add to the 050 * overall startup time for any system using this database. For 051 * certain applications, the startup time is a critical spec that 052 * needs to be optimized, while for other applications, startup time 053 * is inconsequential. This class provides settings (via system 054 * properties) that control how the database is to be loaded so that 055 * applications can tune for quick startup or optimal run time. 056 * <p> 057 * This class serves also as a testbed for comparing performance of 058 * the traditional java binary I/O and the new io ( <code>java.nio </code>) 059 * package. 060 * <p> 061 * <p> A diphone database can be loaded from a text data file, or a 062 * binary datafile. The binary version loads significantly faster 063 * than the text version. Additionally, a binary index can be 064 * generated and used to reduce overall memory footprint. 065 * <p> 066 * <p> 067 * A DiphoneUnitDatabase contains an array of frames, and an aray of 068 * residuals. The frames are the samples of the wave, and the 069 * residuals are for linear predictive coding use. This is called 070 * "cst_sts" (a struct) in flite. 071 * <p> 072 * Note that if 'com.sun.speech.freetts.useNewIO' is set to true and 073 * the input type is binary, than the JDK1.4+ new IO api is used to 074 * load the database. 075 * <p> 076 * The system property 077 * <pre> 078 * com.sun.speech.freetts.diphone.UnitDatabase.cacheType 079 * </pre> 080 * 081 * can be set to one of: 082 * 083 * <ul> 084 * <li> preload: database is loaded at startup 085 * <li> demand: database is loaded on demand 086 * <li> hard: database is loaded on demand but cached 087 * <li> soft: database is loaded on demand but cached with soft references 088 * </ul> 089 * 090 * This <code> cacheType </code> setting controls how the database is 091 * loaded. The default is to 'preload' the database. This setting 092 * gives best runtime performance but with longer initial startup 093 * cost. 094 */ 095public class DiphoneUnitDatabase { 096 /** Logger instance. */ 097 private static final Logger LOGGER = 098 Logger.getLogger(DiphoneUnitDatabase.class.getName()); 099 100 private String name; 101 private int sampleRate; 102 private int numChannels; 103 private int residualFold = 1; 104 private float lpcMin; 105 private float lpcRange; 106 private int lineCount = 0; 107 private Diphone defaultDiphone; 108 private Map diphoneMap = null; 109 private Map diphoneIndex; 110 private SampleInfo sampleInfo; 111 112 private boolean useNewIO = 113 Utilities.getProperty("com.sun.speech.freetts.useNewIO", 114 "true").equals("true"); 115 // cache can be 'preload' 'none', 'soft' or 'hard' 116 private String cacheType = 117 Utilities.getProperty( 118 "com.sun.speech.freetts.diphone.UnitDatabase.cacheType", 119 "preload"); 120 private boolean useIndexing = !cacheType.equals("preload"); 121 private boolean useCache = !cacheType.equals("demand"); 122 private boolean useSoftCache = cacheType.equals("soft"); 123 124 private final static int MAGIC = 0xFEEDFACE; 125 private final static int INDEX_MAGIC = 0xFACADE; 126 private final static int VERSION = 1; 127 private final static int MAX_DB_SIZE = 4 * 1024 * 1024; 128 129 private String indexName = null; 130 private MappedByteBuffer mbb = null; 131 private int defaultIndex = -1; 132 133 /** 134 * Creates the DiphoneUnitDatabase from the given input stream. 135 * 136 * @param url the location of the database 137 * @param isBinary if <code>true</code> the database is in 138 * binary format; otherwise it is in text format 139 * 140 * @throws IOException if there is trouble opening the DB 141 */ 142 public DiphoneUnitDatabase(URL url, boolean isBinary) throws IOException { 143 // MS, 22.04.2005: Commented out the "if" clause: 144 // indexing is applied only when useNewIO is turned on and 145 // data is read from a FileInputStream. This is not true when useing 146 // the default settings but setting 147 // com.sun.speech.freetts.diphone.UnitDatabase.cacheType=demand 148 //if (!useIndexing || useCache) { 149 diphoneMap = new LinkedHashMap(); 150 //} 151 InputStream is = Utilities.getInputStream(url); 152 153 indexName = getIndexName(url.toString()); 154 155 if (isBinary) { 156 loadBinary(is); 157 } else { 158 loadText(is); 159 } 160 is.close(); 161 sampleInfo = new SampleInfo(sampleRate, numChannels, 162 residualFold, lpcMin, lpcRange, 0.0f); 163 } 164 165 /** 166 * Return the information about the sample data 167 * for this database. 168 * 169 * @return the sample info 170 */ 171 172 SampleInfo getSampleInfo() { 173 return sampleInfo; 174 } 175 176 177 /** 178 * Returns the index name from the databaseName. 179 * 180 * @param databaseName the database name 181 * 182 * @return the index name or null if the database is not 183 * a binary database. 184 * 185 * [[[ TODO the index should probably be incorporated into the 186 * binary database ]]] 187 */ 188 private String getIndexName(String databaseName) { 189 String indexName = null; 190 if (databaseName.lastIndexOf(".") != -1) { 191 indexName = databaseName.substring(0, 192 databaseName.lastIndexOf(".")) + ".idx"; 193 } 194 return indexName; 195 } 196 197 /** 198 * Loads the database from the given input stream. 199 * 200 * @param is the input stream 201 */ 202 private void loadText(InputStream is) { 203 BufferedReader reader; 204 String line; 205 206 if (is == null) { 207 throw new Error("Can't load diphone db file."); 208 } 209 210 reader = new BufferedReader(new InputStreamReader(is)); 211 try { 212 line = reader.readLine(); 213 lineCount++; 214 while (line != null) { 215 if (!line.startsWith("***")) { 216 parseAndAdd(line, reader); 217 } 218 line = reader.readLine(); 219 } 220 reader.close(); 221 } catch (IOException e) { 222 throw new Error(e.getMessage() + " at line " + lineCount); 223 } finally { 224 } 225 } 226 227 /** 228 * Parses and process the given line. Used to process the text 229 * form of the database. 230 * 231 * @param line the line to process 232 * @param reader the source for the lines 233 */ 234 private void parseAndAdd(String line, BufferedReader reader) { 235 try { 236 StringTokenizer tokenizer = new StringTokenizer(line," "); 237 String tag = tokenizer.nextToken(); 238 if (tag.equals("NAME")) { 239 name = tokenizer.nextToken(); 240 } else if (tag.equals("SAMPLE_RATE")) { 241 sampleRate = Integer.parseInt(tokenizer.nextToken()); 242 } else if (tag.equals("NUM_CHANNELS")) { 243 numChannels = Integer.parseInt(tokenizer.nextToken()); 244 } else if (tag.equals("LPC_MIN")) { 245 lpcMin = Float.parseFloat(tokenizer.nextToken()); 246 } else if (tag.equals("COEFF_MIN")) { 247 lpcMin = Float.parseFloat(tokenizer.nextToken()); 248 } else if (tag.equals("COEFF_RANGE")) { 249 lpcRange = Float.parseFloat(tokenizer.nextToken()); 250 } else if (tag.equals("LPC_RANGE")) { 251 lpcRange = Float.parseFloat(tokenizer.nextToken()); 252 } else if (tag.equals("ALIAS")) { 253 String name = tokenizer.nextToken(); 254 String origName = tokenizer.nextToken(); 255 AliasDiphone diphone = new AliasDiphone(name, origName); 256 add(diphone); 257 } else if (tag.equals("DIPHONE")) { 258 String name = tokenizer.nextToken(); 259 int start = Integer.parseInt(tokenizer.nextToken()); 260 int mid = Integer.parseInt(tokenizer.nextToken()); 261 int end = Integer.parseInt(tokenizer.nextToken()); 262 int numSamples = (end - start); 263 int midPoint = mid - start; 264 265 if (numChannels <= 0) { 266 throw new Error("For diphone '"+name+"': Bad number of channels " + numChannels); 267 } 268 269 if (numSamples <= 0) { 270 throw new Error("For diphone '"+name+"': Bad number of samples " + numSamples); 271 } 272 273 Sample[] samples = new Sample[numSamples]; 274 275 for (int i = 0; i < samples.length; i++) { 276 samples[i] = new Sample(reader, numChannels); 277 } 278 Diphone diphone = new Diphone(name, samples, midPoint); 279 add(diphone); 280 } else { 281 throw new Error("Unsupported tag " + tag); 282 } 283 } catch (NoSuchElementException nse) { 284 throw new Error("Error parsing db " + nse.getMessage()); 285 } catch (NumberFormatException nfe) { 286 throw new Error("Error parsing numbers in db " + nfe.getMessage()); 287 } 288 } 289 290 291 /** 292 * Adds the given diphone to the DB. Diphones are kept in a map so 293 * they can be accessed by name. 294 * 295 * @param diphone the diphone to add. 296 */ 297 private void add(Diphone diphone) { 298 if (diphone instanceof AliasDiphone) { 299 AliasDiphone adiph = (AliasDiphone) diphone; 300 Diphone original = (Diphone) 301 diphoneMap.get(adiph.getOriginalName()); 302 if (original != null) { 303 adiph.setOriginalDiphone(original); 304 } else { 305 // No original was found for this alias 306 // -- complain, and ignore 307 if (LOGGER.isLoggable(Level.FINER)) { 308 LOGGER.finer("For diphone alias " 309 +adiph.getName()+", could not find original " 310 +adiph.getOriginalName()); 311 } 312 return; 313 } 314 } 315 diphoneMap.put(diphone.getName(), diphone); 316 if (defaultDiphone == null) { 317 defaultDiphone = diphone; 318 } 319 } 320 321 /** 322 * Looks up the diphone with the given name. 323 * 324 * @param unitName the name of the diphone to look for 325 * 326 * @return the diphone or the defaultDiphone if not found. 327 */ 328 public Diphone getUnit(String unitName) { 329 Diphone diphone = null; 330 331 if (useIndexing) { 332 diphone = getFromCache(unitName); 333 if (diphone == null) { 334 int index = getIndex(unitName); 335 if (index != -1) { 336 mbb.position(index); 337 try { 338 diphone = Diphone.loadBinary(mbb); 339 if (diphone != null) { 340 // If diphone is an alias, must also get the original 341 if (diphone instanceof AliasDiphone) { 342 AliasDiphone adiph = (AliasDiphone) diphone; 343 Diphone original = getUnit(adiph.getOriginalName()); 344 if (original != null) { 345 adiph.setOriginalDiphone(original); 346 putIntoCache(unitName, adiph); 347 } else { 348 // No original was found for this alias 349 // -- complain, and ignore 350 if (LOGGER.isLoggable(Level.FINER)) { 351 LOGGER.finer("For diphone alias " 352 +adiph.getName()+", could not find original " 353 +adiph.getOriginalName()); 354 } 355 diphone = null; 356 } 357 } else { // a normal diphone 358 putIntoCache(unitName, diphone); 359 } 360 } 361 } catch (IOException ioe) { 362 System.err.println("Can't load diphone " + 363 unitName); 364 diphone = null; 365 } 366 } 367 } 368 } else { 369 diphone = (Diphone) diphoneMap.get(unitName); 370 } 371 372 if (diphone == null) { 373 System.err.println("Can't find diphone " + unitName); 374 diphone = defaultDiphone; 375 } 376 377 return diphone; 378 } 379 380 /** 381 * Gets the named diphone from the cache. If we are using soft 382 * caching, the reference may be a soft/weak reference so check to 383 * see if the reference is still valid, if so return it; otherwise 384 * invalidate it. Note that we have not had good success with weak 385 * caches so far. The goal is to reduce the minimum required 386 * memory footprint as far as possible while not compromising 387 * performance. In small memory systems, the weak cache would 388 * likely be reclaimed, giving us lower performance but with the 389 * ability to still be able to run. In reality, the soft caches 390 * did not help much. They just did not work correctly. 391 * [[[ TODO: test weak/soft cache behavior with new versions of 392 * the runtime to see if their behavior has improved ]]] 393 * 394 * @param name the name of the diphone 395 * 396 * @return the diphone or <code> null </code> if not in the cache 397 */ 398 private Diphone getFromCache(String name) { 399 if (diphoneMap == null) { 400 return null; 401 } 402 Diphone diphone = null; 403 404 if (useSoftCache) { 405 Reference ref = (Reference) diphoneMap.get(name); 406 if (ref != null) { 407 diphone = (Diphone) ref.get(); 408 if (diphone == null) { 409 diphoneMap.remove(name); 410 } else { 411 } 412 } 413 } else { 414 diphone = (Diphone) diphoneMap.get(name); 415 } 416 return diphone; 417 } 418 419 /** 420 * Puts the diphone in the cache. 421 * 422 * @param diphoneName the name of the diphone 423 * @param diphone the diphone to put in the cache 424 */ 425 private void putIntoCache(String diphoneName, Diphone diphone) { 426 if (diphoneMap == null) { 427 return ; 428 } 429 if (useSoftCache) { 430 diphoneMap.put(diphoneName, new WeakReference(diphone)); 431 } else { 432 diphoneMap.put(diphoneName, diphone); 433 } 434 } 435 436 /** 437 * Dumps the soft ref cache. 438 */ 439 private void dumpCacheSize() { 440 int empty = 0; 441 int full = 0; 442 System.out.println("Entries: " + diphoneMap.size()); 443 for (Iterator i = diphoneMap.values().iterator(); i.hasNext(); ) { 444 Reference ref = (Reference) i.next(); 445 if (ref.get() == null) { 446 empty++; 447 } else { 448 full++; 449 } 450 } 451 System.out.println(" empty: " + empty); 452 System.out.println(" full: " + full); 453 } 454 455 456 /** 457 * Returns the name of this DiphoneUnitDatabase. 458 */ 459 public String getName() { 460 return name; 461 } 462 463 /** 464 * Dumps the diphone database. 465 */ 466 public void dump() { 467 System.out.println("Name " + name); 468 System.out.println("SampleRate " + sampleRate); 469 System.out.println("NumChannels " + numChannels); 470 System.out.println("lpcMin " + lpcMin); 471 System.out.println("lpcRange " + lpcRange); 472 473 for (Iterator i = diphoneMap.values().iterator(); i.hasNext(); ) { 474 Diphone diphone = (Diphone) i.next(); 475 diphone.dump(); 476 } 477 } 478 479 /** 480 * Dumps a binary form of the database. 481 * 482 * @param path the path to dump the file to 483 */ 484 public void dumpBinary(String path) { 485 try { 486 FileOutputStream fos = new FileOutputStream(path); 487 DataOutputStream os = new DataOutputStream(fos); 488 int written; 489 490 os.writeInt(MAGIC); 491 os.writeInt(VERSION); 492 os.writeInt(sampleRate); 493 os.writeInt(numChannels); 494 os.writeFloat(lpcMin); 495 os.writeFloat(lpcRange); 496 os.writeInt(diphoneMap.size()); 497 498 for (Iterator i = diphoneMap.values().iterator(); i.hasNext();) { 499 Diphone diphone = (Diphone) i.next(); 500 diphone.dumpBinary(os); 501 } 502 os.flush(); 503 fos.close(); 504 505 } catch (FileNotFoundException fe) { 506 throw new Error("Can't dump binary database " + 507 fe.getMessage()); 508 } catch (IOException ioe) { 509 throw new Error("Can't write binary database " + 510 ioe.getMessage()); 511 } 512 } 513 514 /** 515 * Dumps a binary index. The database index is used if our 516 * cacheType is not set to 'preload' and we are loading a binary 517 * database. The index is a simple mapping of diphone names (the 518 * key) to the file position in the database. In situations where 519 * the entire database is not preloaded, this index can be loaded 520 * and used to provide quicker startup (since only the index need 521 * be loaded at startup) and quick access to the diphone data. 522 * 523 * @param path the path to dump the file to 524 */ 525 void dumpBinaryIndex(String path) { 526 try { 527 FileOutputStream fos = new FileOutputStream(path); 528 DataOutputStream dos = new DataOutputStream(fos); 529 530 dos.writeInt(INDEX_MAGIC); 531 dos.writeInt(diphoneIndex.keySet().size()); 532 533 for (Iterator i = diphoneIndex.keySet().iterator(); i.hasNext();) { 534 String key = (String) i.next(); 535 int pos = ((Integer) diphoneIndex.get(key)).intValue(); 536 dos.writeUTF(key); 537 dos.writeInt(pos); 538 } 539 dos.close(); 540 541 } catch (FileNotFoundException fe) { 542 throw new Error("Can't dump binary index " + 543 fe.getMessage()); 544 } catch (IOException ioe) { 545 throw new Error("Can't write binary index " + 546 ioe.getMessage()); 547 } 548 } 549 550 /** 551 * Loads a binary index. 552 * 553 * @param url the location of the binary index file 554 */ 555 private void loadBinaryIndex(URL url) { 556 557 diphoneIndex = new HashMap(); 558 559 try { 560 InputStream is = Utilities.getInputStream(url); 561 DataInputStream dis = new DataInputStream(is); 562 563 if (dis.readInt() != INDEX_MAGIC) { 564 throw new Error("Bad index file format"); 565 } 566 567 int size = dis.readInt(); 568 569 for (int i = 0; i < size; i++) { 570 String diphoneName = dis.readUTF(); 571 int pos = dis.readInt(); 572 diphoneIndex.put(diphoneName, new Integer(pos)); 573 } 574 dis.close(); 575 576 } catch (FileNotFoundException fe) { 577 throw new Error("Can't load binary index " + 578 fe.getMessage()); 579 } catch (IOException ioe) { 580 throw new Error("Can't read binary index " + 581 ioe.getMessage()); 582 } 583 } 584 585 /** 586 * Gets the index for the given diphone. 587 * 588 * @param diphone the name of the diphone 589 * 590 * @return the index into the database for the diphone 591 */ 592 private int getIndex(String diphone) { 593 Integer index = (Integer) diphoneIndex.get(diphone); 594 if (index != null) { 595 int idx = index.intValue(); 596 if (defaultIndex == -1) { 597 defaultIndex = idx; 598 } 599 return idx; 600 } else { 601 System.out.println("Can't find index entry for " + diphone); 602 return defaultIndex; 603 } 604 } 605 606 607 608 /** 609 * Loads a binary file from the input stream. 610 * <p> 611 * Note that we currently have four! methods of loading up the 612 * database. We were interested in the performance characteristics 613 * of the various methods of loading the database so we coded it 614 * all up. 615 * 616 * @param is the input stream to read the database 617 * from 618 * 619 * @throws IOException if there is trouble opening the DB 620 * 621 */ 622 private void loadBinary(InputStream is) throws IOException { 623 // we get better performance if we can map the file in 624 // 1.0 seconds vs. 1.75 seconds, but we can't 625 // always guarantee that we can do that. 626 if (useNewIO && is instanceof FileInputStream) { 627 FileInputStream fis = (FileInputStream) is; 628 if (useIndexing) { 629 loadBinaryIndex(new URL(indexName)); 630 mapDatabase(fis); 631 } else { 632 loadMappedBinary(fis); 633 } 634 } else { 635 useIndexing = false; // just to make this clear 636 DataInputStream dis = new DataInputStream( 637 new BufferedInputStream(is)); 638 loadBinary(dis); 639 } 640 } 641 642 643 /** 644 * Loads the binary data from the given input stream. 645 * 646 * @param dis the data input stream. 647 */ 648 private void loadBinary(DataInputStream dis) throws IOException { 649 int size; 650 if (dis.readInt() != MAGIC) { 651 throw new Error("Bad magic in db"); 652 } 653 if (dis.readInt() != VERSION) { 654 throw new Error("Bad VERSION in db"); 655 } 656 657 sampleRate = dis.readInt(); 658 numChannels = dis.readInt(); 659 lpcMin = dis.readFloat(); 660 lpcRange = dis.readFloat(); 661 size = dis.readInt(); 662 663 for (int i = 0; i < size; i++) { 664 Diphone diphone = Diphone.loadBinary(dis); 665 add(diphone); 666 } 667 } 668 669 670 /** 671 * Loads the database from the given FileInputStream. 672 * 673 * @param is the InputStream to load the database from 674 * 675 * @throws IOException if there is trouble opening the DB 676 */ 677 private void loadMappedBinary(FileInputStream is) throws IOException { 678 FileChannel fc = is.getChannel(); 679 680 MappedByteBuffer bb = 681 fc.map(FileChannel.MapMode.READ_ONLY, 0, (int) fc.size()); 682 bb.load(); 683 loadDatabase(bb); 684 is.close(); 685 } 686 687 /** 688 * Maps the database from the given FileInputStream. 689 * 690 * @param is the InputStream to load the database from 691 * 692 * @throws IOException if there is trouble opening the DB 693 */ 694 private void mapDatabase(FileInputStream is) throws IOException { 695 FileChannel fc = is.getChannel(); 696 mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, (int) fc.size()); 697 mbb.load(); 698 loadDatabaseHeader(mbb); 699 } 700 701 /** 702 * Loads the database header from the given byte buffer. 703 * 704 * @param bb the byte buffer to load the db from 705 * 706 * @throws IOException if there is trouble opening the DB 707 */ 708 private void loadDatabaseHeader(ByteBuffer bb) throws IOException { 709 if (bb.getInt() != MAGIC) { 710 throw new Error("Bad magic in db"); 711 } 712 if (bb.getInt() != VERSION) { 713 throw new Error("Bad VERSION in db"); 714 } 715 716 sampleRate = bb.getInt(); 717 numChannels = bb.getInt(); 718 lpcMin = bb.getFloat(); 719 lpcRange = bb.getFloat(); 720 } 721 722 /** 723 * Loads the database from the given byte buffer. 724 * 725 * @param bb the byte buffer to load the db from 726 * 727 * @throws IOException if there is trouble opening the DB 728 */ 729 private void loadDatabase(ByteBuffer bb) throws IOException { 730 int size; 731 loadDatabaseHeader(bb); 732 size = bb.getInt(); 733 734 diphoneIndex = new HashMap(); 735 for (int i = 0; i < size; i++) { 736 int pos = bb.position(); 737 Diphone diphone = Diphone.loadBinary(bb); 738 add(diphone); 739 diphoneIndex.put(diphone.getName(), new Integer(pos)); 740 } 741 } 742 743 /** 744 * Compares this database to another. This is used for testing. 745 * With this method we can load up two databases (one perhaps from 746 * a text source and one from a binary source) and compare to 747 * verify that the dbs are identical 748 * 749 * @param other the other database 750 * 751 * @return <code>true</code> if the DBs are identical; 752 * otherwise <code>false</code> 753 */ 754 public boolean compare(DiphoneUnitDatabase other) { 755 if (sampleRate != other.sampleRate) { 756 return false; 757 } 758 759 if (numChannels != other.numChannels) { 760 return false; 761 } 762 763 if (lpcMin != other.lpcMin) { 764 return false; 765 } 766 767 if (lpcRange != other.lpcRange) { 768 return false; 769 } 770 771 for (Iterator i = diphoneMap.values().iterator(); i.hasNext(); ) { 772 Diphone diphone = (Diphone) i.next(); 773 Diphone otherDiphone = (Diphone) other.getUnit(diphone.getName()); 774 if (!diphone.compare(otherDiphone)) { 775 System.out.println("Diphones differ:"); 776 System.out.println("THis:"); 777 diphone.dump(); 778 System.out.println("Other:"); 779 otherDiphone.dump(); 780 return false; 781 } 782 } 783 return true; 784 } 785 786 787 /** 788 * Manipulates a DiphoneUnitDatabase. This program is typically 789 * used to generate the binary form (with index) of the 790 * DiphoneUnitDatabase from the text form. Additionally, this program 791 * can be used to compare two databases to see if they are 792 * identical (used for testing). 793 * 794 * <p> 795 * <b> Usage </b> 796 * <p> 797 * <code> java com.sun.speech.freetts.diphone.DiphoneUnitDatabase 798 * [options]</code> 799 * <p> 800 * <b> Options </b> 801 * <p> 802 * <ul> 803 * <li> <code> -src path </code> provides a directory 804 * path to the source text for the database 805 * <li> <code> -dest path </code> provides a directory 806 * for where to place the resulting binaries 807 * <li> <code> -generate_binary [filename] </code> 808 * reads in the text 809 * version of the database and generates the binary 810 * version of the database. 811 * <li> <code> -compare </code> Loads the text and 812 * binary versions of the database and compares them to 813 * see if they are equivalent. 814 * <li> <code> -showTimes </code> shows timings for any 815 * loading, comparing or dumping operation 816 * </ul> 817 * 818 */ 819 public static void main(String[] args) { 820 boolean showTimes = false; 821 String srcPath = "."; 822 String destPath = "."; 823 824 try { 825 if (args.length > 0) { 826 BulkTimer timer = BulkTimer.LOAD; 827 timer.start(); 828 for (int i = 0 ; i < args.length; i++) { 829 if (args[i].equals("-src")) { 830 srcPath = args[++i]; 831 } else if (args[i].equals("-dest")) { 832 destPath = args[++i]; 833 } else if (args[i].equals("-generate_binary")) { 834 String name = "diphone_units.txt"; 835 if (i + 1 < args.length) { 836 String nameArg = args[++i]; 837 if (!nameArg.startsWith("-")) { 838 name = nameArg; 839 } 840 } 841 842 int suffixPos = name.lastIndexOf(".txt"); 843 844 String binaryName = "diphone_units.bin"; 845 if (suffixPos != -1) { 846 binaryName = name.substring(0, suffixPos) + ".bin"; 847 } 848 849 String indexName = "diphone_units.idx"; 850 851 if (suffixPos != -1) { 852 indexName = name.substring(0, suffixPos) + ".idx"; 853 } 854 855 System.out.println("Loading " + name); 856 timer.start("load_text"); 857 DiphoneUnitDatabase udb = new DiphoneUnitDatabase( 858 new URL("file:" 859 + srcPath + "/" + name), false); 860 timer.stop("load_text"); 861 862 System.out.println("Dumping " + binaryName); 863 timer.start("dump_binary"); 864 udb.dumpBinary(destPath + "/" + binaryName); 865 timer.stop("dump_binary"); 866 867 timer.start("load_binary"); 868 DiphoneUnitDatabase budb = 869 new DiphoneUnitDatabase( 870 new URL("file:" 871 + destPath + "/" + binaryName), 872 true); 873 timer.stop("load_binary"); 874 875 System.out.println("Dumping " + indexName); 876 timer.start("dump index"); 877 budb.dumpBinaryIndex(destPath + "/" + indexName); 878 timer.stop("dump index"); 879 } else if (args[i].equals("-compare")) { 880 881 timer.start("load_text"); 882 DiphoneUnitDatabase udb = new DiphoneUnitDatabase( 883 new URL("file:./diphone_units.txt"), false); 884 timer.stop("load_text"); 885 886 timer.start("load_binary"); 887 DiphoneUnitDatabase budb = 888 new DiphoneUnitDatabase( 889 new URL("file:./diphone_units.bin"), true); 890 timer.stop("load_binary"); 891 892 timer.start("compare"); 893 if (udb.compare(budb)) { 894 System.out.println("other compare ok"); 895 } else { 896 System.out.println("other compare different"); 897 } 898 timer.stop("compare"); 899 } else if (args[i].equals("-showtimes")) { 900 showTimes = true; 901 } else { 902 System.out.println("Unknown option " + args[i]); 903 } 904 } 905 timer.stop(); 906 if (showTimes) { 907 timer.show("DiphoneUnitDatabase"); 908 } 909 } else { 910 System.out.println("Options: "); 911 System.out.println(" -src path"); 912 System.out.println(" -dest path"); 913 System.out.println(" -compare"); 914 System.out.println(" -generate_binary"); 915 System.out.println(" -showTimes"); 916 } 917 } catch (IOException ioe) { 918 System.err.println(ioe); 919 } 920 } 921}