upstream version 1.2.2
[debian/freetts] / com / sun / speech / freetts / clunits / ClusterUnitDatabase.java
1 /**
2  * Portions Copyright 2003 Sun Microsystems, Inc.
3  * Portions Copyright 1999-2001 Language Technologies Institute, 
4  * Carnegie Mellon University.
5  * All Rights Reserved.  Use is subject to license terms.
6  * 
7  * See the file "license.terms" for information on usage and
8  * redistribution of this file, and for a DISCLAIMER OF ALL 
9  * WARRANTIES.
10  */
11 package com.sun.speech.freetts.clunits;
12
13 import java.io.BufferedOutputStream;
14 import java.io.BufferedReader;
15 import java.io.DataInputStream;
16 import java.io.DataOutputStream;
17 import java.io.FileInputStream;
18 import java.io.FileNotFoundException;
19 import java.io.FileOutputStream;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.io.InputStreamReader;
23 import java.net.URL;
24 import java.nio.ByteBuffer;
25 import java.nio.MappedByteBuffer;
26 import java.nio.channels.FileChannel;
27 import java.util.ArrayList;
28 import java.util.HashMap;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.NoSuchElementException;
33 import java.util.StringTokenizer;
34
35 import com.sun.speech.freetts.cart.CART;
36 import com.sun.speech.freetts.cart.CARTImpl;
37 import com.sun.speech.freetts.relp.SampleInfo;
38 import com.sun.speech.freetts.relp.SampleSet;
39 import com.sun.speech.freetts.util.BulkTimer;
40 import com.sun.speech.freetts.util.Utilities;
41
42
43 /**
44  * Provides support for the cluster unit database. The use of the
45  * cluster unit database is confined to this clunits package. This
46  * class provides a main program that can be used to convert from a
47  * text version of the database to a binary version of the database.
48  *
49  * The ClusterUnitDataBase can be loaded from a text or a binary
50  * source. The binary form of the database loads much faster and
51  * therefore is generally used in a deployed system.
52  *
53  */
54 public class ClusterUnitDatabase {
55
56     final  static int CLUNIT_NONE = 65535;
57
58     private DatabaseClusterUnit[] units;
59     private UnitType[] unitTypes;
60     private SampleSet sts;
61     private SampleSet mcep;
62     
63     private UnitOriginInfo[] unitOrigins; // for debugging
64
65     private int continuityWeight;
66     private int optimalCoupling;
67     private int extendSelections;
68     private int joinMethod;
69     private int[] joinWeights;
70     private int joinWeightShift;
71
72     private Map cartMap = new HashMap();
73     private CART defaultCart = null;
74
75     private transient List unitList;
76     private transient int lineCount;
77     private transient List unitTypesList;
78
79     private final static int MAGIC = 0xf0cacc1a;
80     private final static int VERSION = 0x1000;
81
82
83     /**
84      * Creates the UnitDatabase from the given input stream.
85      *
86      * @param is the input stream to read the database from
87      * @param isBinary the input stream is a binary stream
88      *
89      * @throws IOException if there is trouble opening the DB
90      */
91     ClusterUnitDatabase(URL url, boolean isBinary) throws IOException {
92         BulkTimer.LOAD.start("ClusterUnitDatabase");
93         InputStream is = Utilities.getInputStream(url);
94         if (isBinary) {
95             loadBinary(is);
96         } else {
97             loadText(is);
98         }
99         is.close();
100     // Attempt to load debug info from a .debug resource.
101     // This will silently fail if no debug info is available.
102     String urlString = url.toExternalForm();
103     URL debugURL = new URL(urlString.substring(0, urlString.lastIndexOf(".")) + ".debug");
104     try {
105         InputStream debugInfoStream = Utilities.getInputStream(debugURL);
106         loadUnitOrigins(debugInfoStream);
107     } catch (IOException ioe) {
108         // Silently ignore if you cannot load the debug info
109     }
110         BulkTimer.LOAD.stop("ClusterUnitDatabase");
111     }
112
113
114     /**
115      * Retrieves the begininning sample index for the
116      * given entry.
117      *
118      * @param unitEntry the entry of interest
119      *
120      * @return the begininning sample index
121      */
122     int getStart(int unitEntry) {
123         return units[unitEntry].start;
124     }
125
126     /**
127      * Retrieves the ending sample index for the
128      * given entry.
129      *
130      * @param unitEntry the entry of interest
131      *
132      * @return the ending sample index
133      */
134     int getEnd(int unitEntry) {
135         return units[unitEntry].end;
136     }
137
138     /**
139      * Retrieves the phone for the given entry
140      *
141      * @param unitEntry the entry of interest
142      *
143      * @return the phone for the entry
144      */
145     int getPhone(int unitEntry) {
146         return units[unitEntry].phone;
147     }
148
149     /**
150      * Returns the cart of the given unit type.
151      *
152      * @param unitType the type of cart
153      *
154      * @return the cart 
155      */
156     CART getTree(String unitType) {
157         CART cart =  (CART) cartMap.get(unitType);
158
159         if (cart == null) {
160             System.err.println("ClusterUnitDatabase: can't find tree for " 
161                     + unitType);
162             return defaultCart;         // "graceful" failrue
163         }
164         return cart;
165     }
166
167     /**
168      * Retrieves the type index for the name given a name. 
169      *
170      * @param name the name
171      *
172      * @return the index for the name
173      */
174 // [[[TODO: perhaps replace this with  java.util.Arrays.binarySearch]]]
175     int getUnitTypeIndex(String name) {
176         int start, end, mid, c;
177
178         start = 0;
179         end = unitTypes.length;
180
181         while (start < end) {
182             mid = (start + end) / 2;
183             c = unitTypes[mid].getName().compareTo(name);
184             if (c == 0) {
185                 return mid;
186             } else if (c > 0) {
187                 end = mid;
188             } else {
189                 start = mid + 1;
190             }
191         }
192         return -1;
193     }
194
195     /**
196      * Retrieves the unit index given a unit type and val.
197      *
198      * @param unitType the type of the unit
199      * @param instance the value associated with the unit
200      *
201      * @return the index.
202      */
203     int getUnitIndex(String unitType, int instance) {
204         int i = getUnitTypeIndex(unitType);
205         if (i == -1) {
206             error("getUnitIndex: can't find unit type " + unitType);
207             i = 0;
208         }
209         if (instance >= unitTypes[i].getCount()) {
210             error("getUnitIndex: can't find instance " 
211                     + instance + " of " + unitType);
212             instance = 0;
213         }
214         return unitTypes[i].getStart() + instance;
215     }
216
217
218     /**
219      * Retrieves the index for the name given a name. 
220      *
221      * @param name the name
222      *
223      * @return the index for the name
224      */
225     int getUnitIndexName(String name) {
226         int lastIndex = name.lastIndexOf('_');
227         if (lastIndex == -1) {
228             error("getUnitIndexName: bad unit name " + name);
229             return -1;
230         }
231         int index = Integer.parseInt(name.substring(lastIndex + 1));
232         String type = name.substring(0, lastIndex);
233         return getUnitIndex(type, index);
234     }
235
236     /**
237      * Retrieves the extend selections setting.
238      *
239      * @return the extend selections setting
240      */
241     int getExtendSelections() {
242         return extendSelections;
243     }
244
245     /**
246      * Gets the next unit.
247      *
248      * @return the next unit
249      */
250     int getNextUnit(int which) {
251         return units[which].next;
252     }
253
254     /**
255      * Gets the previous units.
256      *
257      * @param which which unit is of interest
258      *
259      * @return the previous unit
260      */
261     int getPrevUnit(int which) {
262         return units[which].prev;
263     }
264
265
266     /**
267      * Determines if the unit types are equal.
268      *
269      * @param unitA the index of unit a
270      * @param unitB the index of unit B
271      *
272      * @return <code>true</code> if the types of units a and b are
273      *     equal; otherwise return <code>false</code> 
274      */
275     boolean isUnitTypeEqual(int unitA, int unitB)  {
276         return units[unitA].type == units[unitB].type;
277         // String nameA = units[unitA].getName();
278         // String nameB = units[unitB].getName();
279         // int lastUnderscore = nameA.lastIndexOf('_');
280         // return nameA.regionMatches(0, nameB, 0, lastUnderscore + 1);
281     }
282
283     /**
284      * Retrieves the optimal coupling setting.
285      *
286      * @return the optimal coupling setting
287      */
288     int getOptimalCoupling() {
289         return optimalCoupling;
290     }
291
292     /**
293      * Retrieves the continuity weight setting.
294      *
295      * 
296      * @return  the continuity weight setting
297      */
298     int getContinuityWeight()  {
299         return continuityWeight;
300     }
301
302     /**
303      * Retrieves the join weights.
304      *
305      * @return  the join weights
306      */
307     int[] getJoinWeights() {
308         return joinWeights;
309     }
310
311
312     /**
313      * Looks up the unit with the given name.
314      *
315      * @param unitName the name of the unit to look for
316      *
317      * @return the unit or the defaultUnit if not found.
318      */
319     DatabaseClusterUnit getUnit(String unitName) {
320         return null;
321     }
322     
323     /**
324      * Looks up the unit with the given index.
325      *
326      * @param index the index of the unit to look for
327      *
328      * @return the unit 
329      */
330     DatabaseClusterUnit getUnit(int which) {
331         return units[which];
332     }
333
334     /**
335      * Looks up the origin info for the unit with the given index.
336      *
337      * @param index the index of the unit to look for
338      *
339      * @return the origin info for the unit, or null if none is available 
340      */
341     UnitOriginInfo getUnitOriginInfo(int which) {
342         if (unitOrigins != null)
343             return unitOrigins[which];
344         else
345             return null;
346     }
347
348     
349     /**
350      * Returns the name of this UnitDatabase.
351      *
352      * @return the name of the database
353      */
354     String getName() {
355         return "ClusterUnitDatabase";
356     }
357     
358     /**
359      * Returns the sample info for this set of data.
360      *
361      * @return the sample info
362      */
363     SampleInfo getSampleInfo() {
364         return sts.getSampleInfo();
365     }
366
367
368     /**
369      * Gets the sample list.
370      *
371      * @return the sample list
372      */
373     SampleSet getSts() {
374         return sts;
375     }
376
377     /**
378      * Gets the Mel Ceptra list.
379      *
380      * @return the Mel Ceptra list
381      */
382     SampleSet getMcep() {
383         return mcep;
384     }
385
386     /**
387      * Determines if the application of the given join weights could
388      * be applied  as a simple right-shift. If so return the shift
389      * otherwise return 0.
390      *
391      * @return the amount to right shift (or zero if not possible)
392      */
393     int getJoinWeightShift() {
394         return joinWeightShift;
395     }
396
397
398     /**
399      * Calculates the join weight shift.
400      *
401      * @param joinWeights the weights to check
402      *
403      * @return the amount to right shift (or zero if not possible)
404      */
405     private int calcJoinWeightShift(int[] joinWeights) {
406         int first = joinWeights[0];
407         for (int i = 1; i < joinWeights.length; i++) {
408             if (joinWeights[i] != first) {
409                 return 0;
410             }
411         }
412
413         int divisor = 65536 / first;
414         if (divisor == 2) {
415             return 1;
416         } else if (divisor == 4) {
417             return 2;
418         }
419         return 0;
420     }
421
422     /**
423      * Loads the database from the given input stream.
424      *
425      * @param is the input stream
426      */
427     private void loadText(InputStream is) {
428         BufferedReader reader;
429         String line;
430
431
432         unitList = new ArrayList();
433         unitTypesList = new ArrayList();
434
435         if (is == null) {
436             throw new Error("Can't load cluster db file.");
437         }
438
439         reader = new BufferedReader(new InputStreamReader(is));
440         try {
441             line = reader.readLine();
442             lineCount++;
443             while (line != null) {
444                 if (!line.startsWith("***")) {
445                     parseAndAdd(line, reader);
446                 }
447                 line = reader.readLine();
448             }
449             reader.close();
450
451             units = new DatabaseClusterUnit[unitList.size()];
452             units = (DatabaseClusterUnit[]) unitList.toArray(units);
453             unitList = null;
454
455             unitTypes = new UnitType[unitTypesList.size()];
456             unitTypes = (UnitType[]) unitTypesList.toArray(unitTypes);
457             unitTypesList = null;
458
459         } catch (IOException e) {
460             throw new Error(e.getMessage() + " at line " + lineCount);
461         } finally {
462         }
463     }
464
465     /**
466      * Parses and process the given line.
467      *
468      * @param line the line to process
469      * @param reader the source for the lines
470      *
471      * @throws IOException if an error occurs while reading
472      */
473     private void parseAndAdd(String line, BufferedReader reader)
474         throws IOException {
475         try {
476             StringTokenizer tokenizer = new StringTokenizer(line," ");
477             String tag = tokenizer.nextToken();
478             if (tag.equals("CONTINUITY_WEIGHT")) {
479                 continuityWeight = Integer.parseInt(tokenizer.nextToken());
480             } else if (tag.equals("OPTIMAL_COUPLING")) {
481                 optimalCoupling = Integer.parseInt(tokenizer.nextToken());
482             }  else if (tag.equals("EXTEND_SELECTIONS")) {
483                 extendSelections = Integer.parseInt(tokenizer.nextToken());
484             }  else if (tag.equals("JOIN_METHOD")) {
485                 joinMethod = Integer.parseInt(tokenizer.nextToken());
486             }  else if (tag.equals("JOIN_WEIGHTS")) {
487                 int numWeights = Integer.parseInt(tokenizer.nextToken());
488                 joinWeights = new int[numWeights];
489                 for (int i = 0; i < numWeights; i++) {
490                     joinWeights[i]  = Integer.parseInt(tokenizer.nextToken());
491                 }
492
493                 joinWeightShift = calcJoinWeightShift(joinWeights);
494
495             } else if (tag.equals("STS")) {
496                 String name = tokenizer.nextToken();
497                 if (name.equals("STS")) {
498                     sts = new SampleSet(tokenizer, reader);
499                 } else {
500                     mcep = new SampleSet(tokenizer, reader);
501                 }
502             } else if (tag.equals("UNITS")) {
503                 int type = Integer.parseInt(tokenizer.nextToken());
504                 int phone = Integer.parseInt(tokenizer.nextToken());
505                 int start = Integer.parseInt(tokenizer.nextToken());
506                 int end = Integer.parseInt(tokenizer.nextToken());
507                 int prev = Integer.parseInt(tokenizer.nextToken());
508                 int next = Integer.parseInt(tokenizer.nextToken());
509                 DatabaseClusterUnit unit 
510                       = new DatabaseClusterUnit(type, phone, start, 
511                               end, prev, next);
512                 unitList.add(unit);
513             } else if (tag.equals("CART")) {
514                 String name = tokenizer.nextToken();
515                 int nodes = Integer.parseInt(tokenizer.nextToken());
516                 CART cart = new CARTImpl(reader, nodes);
517                 cartMap.put(name, cart);
518
519                 if (defaultCart == null) {
520                     defaultCart = cart;
521                 }
522             } else if (tag.equals("UNIT_TYPE")) {
523                 String name = tokenizer.nextToken();
524                 int start = Integer.parseInt(tokenizer.nextToken());
525                 int count = Integer.parseInt(tokenizer.nextToken());
526                 UnitType unitType = new UnitType(name, start, count);
527                 unitTypesList.add(unitType);
528             } else {
529                 throw new Error("Unsupported tag " + tag + " in db line `" + line + "'");
530             }
531         } catch (NoSuchElementException nse) {
532             throw new Error("Error parsing db " + nse.getMessage());
533         } catch (NumberFormatException nfe) {
534             throw new Error("Error parsing numbers in db line `" + line + "':" + nfe.getMessage());
535         }
536     }
537
538     /**
539      * Loads a binary file from the input stream. 
540      *
541      * @param is the input stream to read the database from
542      *
543      * @throws IOException if there is trouble opening the DB
544      *
545      */
546     private void loadBinary(InputStream is) throws IOException {
547         // we get better performance if we can map the file in
548         // 1.0 seconds vs. 1.75 seconds, but we can't
549         // always guarantee that we can do that.
550         if (is instanceof FileInputStream) {
551             FileInputStream fis = (FileInputStream) is;
552             FileChannel fc = fis.getChannel();
553
554             MappedByteBuffer bb = 
555                 fc.map(FileChannel.MapMode.READ_ONLY, 0, (int) fc.size());
556             bb.load();
557             loadBinary(bb);
558             is.close();
559         } else {
560             loadBinary(new DataInputStream(is));
561         }
562     }
563
564     /**
565      * Loads the database from the given byte buffer.
566      *
567      * @param bb the byte buffer to load the db from
568      *
569      * @throws IOException if there is trouble opening the DB
570      */
571     private void loadBinary(ByteBuffer bb) throws IOException {
572
573         if (bb.getInt() != MAGIC)  {
574             throw new Error("Bad magic in db");
575         }
576         if (bb.getInt() != VERSION)  {
577             throw new Error("Bad VERSION in db");
578         }
579
580         continuityWeight = bb.getInt();
581         optimalCoupling = bb.getInt();
582         extendSelections = bb.getInt();
583         joinMethod = bb.getInt();
584         joinWeightShift = bb.getInt();
585
586         int weightLength = bb.getInt();
587         joinWeights = new int[weightLength];
588         for (int i = 0; i < joinWeights.length; i++) {
589             joinWeights[i] = bb.getInt();
590         }
591
592         int unitsLength = bb.getInt();
593         units = new DatabaseClusterUnit[unitsLength];
594         for (int i = 0; i < units.length; i++) {
595             units[i] = new DatabaseClusterUnit(bb);
596         }
597
598         int unitTypesLength = bb.getInt();
599         unitTypes = new UnitType[unitTypesLength];
600         for (int i = 0; i < unitTypes.length; i++) {
601             unitTypes[i] = new UnitType(bb);
602         }
603         sts = new SampleSet(bb);
604         mcep = new SampleSet(bb);
605
606         int numCarts = bb.getInt();
607         cartMap = new HashMap();
608         for (int i = 0; i < numCarts; i++) {
609             String name = Utilities.getString(bb);
610             CART cart = CARTImpl.loadBinary(bb);
611             cartMap.put(name, cart);
612
613             if (defaultCart == null) {
614                 defaultCart = cart;
615             }
616         }
617     }
618
619     /**
620      * Loads the database from the given input stream.
621      *
622      * @param is the input stream to load the db from
623      *
624      * @throws IOException if there is trouble opening the DB
625      */
626     private void loadBinary(DataInputStream is) throws IOException {
627
628         if (is.readInt() != MAGIC)  {
629             throw new Error("Bad magic in db");
630         }
631         if (is.readInt() != VERSION)  {
632             throw new Error("Bad VERSION in db");
633         }
634
635         continuityWeight = is.readInt();
636         optimalCoupling = is.readInt();
637         extendSelections = is.readInt();
638         joinMethod = is.readInt();
639         joinWeightShift = is.readInt();
640
641         int weightLength = is.readInt();
642         joinWeights = new int[weightLength];
643         for (int i = 0; i < joinWeights.length; i++) {
644             joinWeights[i] = is.readInt();
645         }
646
647         int unitsLength = is.readInt();
648         units = new DatabaseClusterUnit[unitsLength];
649         for (int i = 0; i < units.length; i++) {
650             units[i] = new DatabaseClusterUnit(is);
651         }
652
653         int unitTypesLength = is.readInt();
654         unitTypes = new UnitType[unitTypesLength];
655         for (int i = 0; i < unitTypes.length; i++) {
656             unitTypes[i] = new UnitType(is);
657         }
658         sts = new SampleSet(is);
659         mcep = new SampleSet(is);
660
661         int numCarts = is.readInt();
662         cartMap = new HashMap();
663         for (int i = 0; i < numCarts; i++) {
664             String name = Utilities.getString(is);
665             CART cart = CARTImpl.loadBinary(is);
666             cartMap.put(name, cart);
667
668             if (defaultCart == null) {
669                 defaultCart = cart;
670             }
671         }
672     }
673     
674     /**
675      * Load debug info about the origin of units from the given input stream.
676      * The file format is identical to that of the Festvox .catalogue files.
677      * This is useful when creating and debugging new voices: For a selected
678      * unit, you can find out which unit from which original sound file
679      * was used.
680      * @param is the input stream from which to read the debug info.
681      * @throws IOException if a read problem occurs.
682      */
683     private void loadUnitOrigins(InputStream is)  throws IOException
684     {
685         unitOrigins = new UnitOriginInfo[units.length];
686         BufferedReader in = new BufferedReader(new InputStreamReader(is));
687         
688         String currentLine = null;
689         // Skip EST header:
690         while ((currentLine = in.readLine()) != null) {
691             if (currentLine.startsWith("EST_Header_End")) break;
692         }
693         while ((currentLine = in.readLine()) != null) {
694             String[] tokens = currentLine.split(" ");
695             String name = tokens[0];
696             int index = getUnitIndexName(name);
697             try {
698                 unitOrigins[index] = new UnitOriginInfo();
699                 unitOrigins[index].originFile = tokens[1];
700                 unitOrigins[index].originStart = Float.valueOf(tokens[2]).floatValue();
701                 unitOrigins[index].originEnd = Float.valueOf(tokens[4]).floatValue();
702             } catch (NumberFormatException nfe) {}
703         }
704         in.close();
705     }
706
707
708     /**
709      * Dumps a binary form of the database.
710      *
711      * @param path the path to dump the file to
712      */
713     void dumpBinary(String path) {
714         try {
715             FileOutputStream fos = new FileOutputStream(path);
716             DataOutputStream os = new DataOutputStream(new
717                     BufferedOutputStream(fos));
718
719             os.writeInt(MAGIC);
720             os.writeInt(VERSION);
721             os.writeInt(continuityWeight);
722             os.writeInt(optimalCoupling);
723             os.writeInt(extendSelections);
724             os.writeInt(joinMethod);
725             os.writeInt(joinWeightShift);
726             os.writeInt(joinWeights.length);
727             for (int i = 0; i < joinWeights.length; i++) {
728                 os.writeInt(joinWeights[i]);
729             }
730
731             os.writeInt(units.length);
732             for (int i = 0; i < units.length; i++) {
733                 units[i].dumpBinary(os);
734             }
735
736             os.writeInt(unitTypes.length);
737             for (int i = 0; i < unitTypes.length; i++) {
738                 unitTypes[i].dumpBinary(os);
739             }
740             sts.dumpBinary(os);
741             mcep.dumpBinary(os);
742
743             os.writeInt(cartMap.size());
744             for (Iterator i = cartMap.keySet().iterator(); i.hasNext();) {
745                 String name = (String) i.next();
746                 CART cart =  (CART) cartMap.get(name);
747
748                 Utilities.outString(os, name);
749                 cart.dumpBinary(os);
750             }
751             os.close();
752
753             // note that we are not currently saving the state
754             // of the default cart
755
756         } catch (FileNotFoundException fe) {
757             throw new Error("Can't dump binary database " +
758                     fe.getMessage());
759         } catch (IOException ioe) {
760             throw new Error("Can't write binary database " +
761                     ioe.getMessage());
762         }
763     }
764
765
766     /**
767      * Determines if two databases are identical.
768      *
769      * @param other the database to compare this one to
770      *
771      * @return true if the databases are identical
772      */
773     public boolean compare(ClusterUnitDatabase other) {
774         System.out.println("Warning: Compare not implemented yet");
775         return false;
776     }
777
778     /**
779      *  Manipulates a ClusterUnitDatabase.  
780      *
781      * <p>
782      * <b> Usage </b>
783      * <p>
784      *  <code> java com.sun.speech.freetts.clunits.ClusterUnitDatabase
785      *  [options]</code> 
786      * <p>
787      * <b> Options </b>
788      * <p>
789      *    <ul>
790      *          <li> <code> -src path </code> provides a directory
791      *          path to the source text for the database
792      *          <li> <code> -dest path </code> provides a directory
793      *          for where to place the resulting binaries
794      *          <li> <code> -generate_binary [filename]</code> reads
795      *          in the text version of the database and generates
796      *          the binary version of the database.
797      *          <li> <code> -compare </code>  Loads the text and
798      *          binary versions of the database and compares them to
799      *          see if they are equivalent.
800      *          <li> <code> -showTimes </code> shows timings for any
801      *          loading, comparing or dumping operation
802      *    </ul>
803      * 
804      */
805     public static void main(String[] args) {
806         boolean showTimes = false;
807         String srcPath = ".";
808         String destPath = ".";
809
810         try {
811             if (args.length > 0) {
812                 BulkTimer timer = new BulkTimer();
813                 timer.start();
814                 for (int i = 0 ; i < args.length; i++) {
815                     if (args[i].equals("-src")) {
816                         srcPath = args[++i];
817                     } else if (args[i].equals("-dest")) {
818                         destPath = args[++i];
819                     } else if (args[i].equals("-generate_binary")) {
820                          String name = "clunits.txt";
821                          if (i + 1 < args.length) {
822                              String nameArg = args[++i];
823                              if (!nameArg.startsWith("-")) {
824                                  name = nameArg;
825                              }
826                          }
827
828                          int suffixPos = name.lastIndexOf(".txt");
829
830                          String binaryName = "clunits.bin";
831                          if (suffixPos != -1) {
832                              binaryName = name.substring(0, suffixPos) + ".bin";
833                          }
834
835                          System.out.println("Loading " + name);
836                          timer.start("load_text");
837                          ClusterUnitDatabase udb = new
838                              ClusterUnitDatabase(
839                                 new URL("file:" + srcPath + "/" + name),
840                                 false);
841                          timer.stop("load_text");
842
843                          System.out.println("Dumping " + binaryName);
844                          timer.start("dump_binary");
845                          udb.dumpBinary(destPath + "/" + binaryName);
846                          timer.stop("dump_binary");
847
848                     } else if (args[i].equals("-compare")) {
849
850                         timer.start("load_text");
851                          ClusterUnitDatabase udb = new
852                              ClusterUnitDatabase(
853                                 new URL("file:./cmu_time_awb.txt"), false);
854                         timer.stop("load_text");
855
856                         timer.start("load_binary");
857                         ClusterUnitDatabase budb = 
858                             new ClusterUnitDatabase(
859                                     new URL("file:./cmu_time_awb.bin"), true);
860                         timer.stop("load_binary");
861
862                         timer.start("compare");
863                         if (udb.compare(budb)) {
864                             System.out.println("other compare ok");
865                         } else {
866                             System.out.println("other compare different");
867                         }
868                         timer.stop("compare");
869                     } else if (args[i].equals("-showtimes")) {
870                         showTimes = true;
871                     } else {
872                         System.out.println("Unknown option " + args[i]);
873                     }
874                 }
875                 timer.stop();
876                 if (showTimes) {
877                     timer.show("ClusterUnitDatabase");
878                 }
879             } else {
880                 System.out.println("Options: ");
881                 System.out.println("    -src path");
882                 System.out.println("    -dest path");
883                 System.out.println("    -compare");
884                 System.out.println("    -generate_binary");
885                 System.out.println("    -showTimes");
886             }
887         } catch (IOException ioe) {
888             System.err.println(ioe);
889         }
890     }
891
892
893     /**
894      * Represents a unit  for the cluster database.
895      */
896     class DatabaseClusterUnit {
897
898         int type;
899         int phone;
900         int start;
901         int end;
902         int prev;
903         int next;
904
905         /**
906          * Constructs a unit.
907          *
908          * @param type the name of the unit
909          * @param phone the name of the unit
910          * @param start the starting frame
911          * @param end the ending frame
912          * @param prev the previous index
913          * @param next the next index
914          */
915         DatabaseClusterUnit(int type, int phone, int start, 
916                 int end, int prev, int next) {
917             this.type = type;
918             this.phone = phone;
919             this.start = start;
920             this.end = end;
921             this.prev = prev;
922             this.next = next;
923         }
924
925         /**
926          * Creates a unit by reading it from the given byte buffer.
927          *
928          * @param bb source of the DatabaseClusterUnit data
929          *
930          * @throws IOException if an IO error occurs
931          */
932         DatabaseClusterUnit(ByteBuffer bb) throws IOException {
933             this.type = bb.getInt();
934             this.phone = bb.getInt();
935             this.start = bb.getInt();
936             this.end = bb.getInt();
937             this.prev = bb.getInt();
938             this.next = bb.getInt();
939         }
940
941         /**
942          * Creates a unit by reading it from the given input stream.
943          *
944          * @param is source of the DatabaseClusterUnit data
945          *
946          * @throws IOException if an IO error occurs
947          */
948         DatabaseClusterUnit(DataInputStream is) throws IOException {
949             this.type = is.readInt();
950             this.phone = is.readInt();
951             this.start = is.readInt();
952             this.end = is.readInt();
953             this.prev = is.readInt();
954             this.next = is.readInt();
955         }
956
957         /**
958          * Returns the name of the unit.
959          *
960          * @return the name
961          */
962         String getName() {
963             return unitTypes[type].getName();
964         }
965
966         /**
967          * Dumps this unit to the given output stream.
968          *
969          * @param os the output stream
970          *
971          * @throws IOException if an error occurs.
972          */
973         void dumpBinary(DataOutputStream os) throws IOException {
974             os.writeInt(type);
975             os.writeInt(phone);
976             os.writeInt(start);
977             os.writeInt(end);
978             os.writeInt(prev);
979             os.writeInt(next);
980         }
981     }
982     
983     /**
984      * Represents debug information about the origin of a unit.
985      */
986     class UnitOriginInfo {
987         String originFile;
988         float originStart;
989         float originEnd;
990     }
991
992     /**
993      * Displays an error message
994      *
995      * @param s the error message
996      */
997     private void error(String s) {
998         System.out.println("ClusterUnitDatabase Error: " + s);
999     }
1000 }
1001
1002 /**
1003  * Represents a unit type in the system
1004  */
1005 class UnitType {
1006     private String name;
1007     private int start;
1008     private int count;
1009
1010     /**
1011      * Constructs a UnitType from the given parameters
1012      *
1013      * @param name the name of the type
1014      * @param start the starting index for this type
1015      * @param count the number of elements for this type
1016      */
1017     UnitType(String name, int start, int count) {
1018         this.name = name;
1019         this.start = start;
1020         this.count = count;
1021     }
1022
1023     /**
1024      * Creates a unit type by reading it from the given input stream.
1025      *
1026      * @param is source of the UnitType data
1027      *
1028      * @throws IOException if an IO error occurs
1029      */
1030     UnitType(DataInputStream is) throws IOException {
1031         this.name = Utilities.getString(is);
1032         this.start = is.readInt();
1033         this.count = is.readInt();
1034     }
1035
1036     /**
1037      * Creates a unit type by reading it from the given byte buffer.
1038      *
1039      * @param bb source of the UnitType  data
1040      *
1041      * @throws IOException if an IO error occurs
1042      */
1043     UnitType(ByteBuffer bb) throws IOException {
1044         this.name = Utilities.getString(bb);
1045         this.start = bb.getInt();
1046         this.count = bb.getInt();
1047     }
1048
1049     /**
1050      * Gets the name for this unit type
1051      * 
1052      * @return the name for the type
1053      */
1054     String getName() {
1055         return name;
1056     }
1057
1058     /**
1059      * Gets the start index for this type
1060      *
1061      * @return the start index
1062      */
1063     int getStart() {
1064         return start;
1065     }
1066
1067     /**
1068      * Gets the count for this type
1069      *
1070      * @return the  count for this type
1071      */
1072     int getCount() {
1073         return count;
1074     }
1075
1076     /**
1077      * Dumps this unit to the given output stream.
1078      *
1079      * @param os the output stream
1080      *
1081      * @throws IOException if an error occurs.
1082      */
1083     void dumpBinary(DataOutputStream os) throws IOException {
1084         Utilities.outString(os, name);
1085         os.writeInt(start);
1086         os.writeInt(count);
1087     }
1088 }