]> ruin.nu Git - moosique.git/blob - Moosique.java
no message
[moosique.git] / Moosique.java
1 import javax.sound.midi.*;
2 import java.io.*;
3 import javax.swing.*;
4 import java.util.*;
5
6 /**
7  * Moosique - The MIDI Tracker
8  * 
9  * Main class that handles initiation, IO and sound.
10  * 
11  * @author  Einar Pehrson
12  */
13  
14 public class Moosique {
15
16         private static MooGUI gui;
17         private static Sequence seq;
18         private static Sequencer sequencer;
19         private static Synthesizer synthesizer;
20         private static Receiver receiver;
21         private static MidiChannel[] channels;
22         private static MidiChannel activeChannel;
23
24         private static ArrayList copyBuffer, emptyTracks, timeSignatures, tempoChanges;
25         private static TreeSet selection;
26         private static Map trackMute = new HashMap();
27         private static Map trackSolo = new HashMap();
28         private static Thread player;
29
30         private static File file = null;
31         private static long editPosition;
32         private static boolean makeGUI = true, initSound = true, edited = false, drawEmptyTracks = false;
33         public static final int DEFAULT_RESOLUTION = 96, DEFAULT_TRACKS = 4;
34         public static final int WHOLE_NOTE = 0, HALF_NOTE = 1, QUARTER_NOTE = 2, EIGHTH_NOTE = 3, SIXTEENTH_NOTE = 4;
35
36         /** 
37          * Starts the application.
38          * 
39          * Parses command-line arguments, acquires MIDI devices and connects them,
40          * loads a sequence and creates the GUI.
41          */
42         public static void main (String[] args) {
43                 out("\nMoosique version 1.0\n", true);
44
45                 // Parses command-line arguments.
46                 String fileArg = null;
47                 for (int i = 0; i < args.length; i++) {
48                         if (args[i].equals("-n")) makeGUI = false;
49                         else if (fileArg == null) fileArg = args[i];
50                 }
51
52                 // Acquires MIDI devices and connects them.
53                 out("Initializing MIDI devices.", false);
54                 try {
55                         // Configures sequencer
56                         sequencer = MidiSystem.getSequencer();
57                         advanceStatus();
58                         sequencer.open();
59                         sequencer.addMetaEventListener(new SongEndListener());
60
61                         // Configures synthesizer
62                         synthesizer = MidiSystem.getSynthesizer();
63                         advanceStatus();
64                         synthesizer.open();
65
66                         // Configures receiver, transmitter and channels.
67                         receiver = synthesizer.getReceiver();
68                         sequencer.getTransmitter().setReceiver(receiver);
69                         channels = synthesizer.getChannels();
70                         setActiveChannel(0);
71                 } catch (MidiUnavailableException e) {
72                         out("Failed, quitting.", true);
73                         System.exit(1);
74                 }
75                 out("Done", true);
76
77                 // Loads user preferences (work directory, last opened files etc).
78                 loadPreferences();
79
80                 //If a filename is given as the command-line argument, attempts to load a sequence from the file.
81                 if (fileArg != null) {
82                         out("Loading MIDI sequence from " + fileArg + "...", false);
83                         if (!load(new File(fileArg))) {
84                                 out("Failed, creating new sequence", true);
85                                 clearSequence();
86                         } else {
87                                 out("Done", true);
88                         }
89                 } else {
90                         // Otherwise creates a new empty one.
91                         clearSequence();
92                 }
93
94                 // Builds GUI, unless n-flag is set.
95                 if (makeGUI) {
96                         out("Building GUI.", false);
97                         try {
98                                 UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
99                         } catch (Exception e) {}
100                         gui = new MooGUI(seq, file);
101                         out("Done", true);
102                 } else {
103                         out("Playing...", false);
104                         play();
105                         while (sequencer.isRunning()) {}
106                         out("Done", true);
107                         quit();
108                 }
109         }
110
111
112
113
114
115
116
117
118         /*                              ***
119          **       ACCESSOR METHODS       **
120          ***                              */
121
122
123
124
125
126
127
128
129         /** 
130          * Returns the currently active MidiChannel.
131          * @return the active MidiChannel
132          */
133         public static MidiChannel getActiveChannel() {
134                 return activeChannel;
135         }
136
137         /** 
138          * Returns the MidiChannels of the selected synthesizer.
139          * @return the available MidiChannels
140          */
141         public static MidiChannel getChannel(int i) {
142                 return channels[i];
143         }
144
145         /** 
146          * Returns the current copy buffer.
147          * @return the current copy buffer
148          */
149         public static ArrayList getCopyBuffer() {
150                 return copyBuffer;
151         }
152
153         /** 
154          * Returns the current editing position of the sequencer.
155          * @return the tick position
156          */
157         public static long getEditPosition() {
158                 return editPosition;
159         }
160
161         /** 
162          * Returns the GUI.
163          * @return the GUI
164          */
165         public static MooGUI getGUI() {
166                 return gui;
167         }
168
169         /** 
170          * Calculates the position (measures, beats, ticks) in the current sequence for the given tick position.
171          * @param tickPosition the tick position for which to calculate the position
172          * @return an array of integers where index 0 is measures, 1 is beats and 2 is ticks.
173          */
174         public static int[] getPositionForTicks(long tickPosition) {
175                 int ticksPerBeat = seq.getResolution(), sigs = timeSignatures.size(), beatsPerMeasure = 4;
176                 long measures = 0, beats = 0, ticks = 0;
177                 if (sigs > 1) {
178                         /*
179                         Iterator it = timeSignatures.iterator();
180                         MidiEvent lastTSEvent = (MidiEvent)it.next();
181                         if (lastTSEvent.getTick() != 0) tickPos += (int)lastTSEvent.getTick();
182                         while(it.hasNext()) {
183                                 MidiEvent nextTSEvent = (MidiEvent)it.next();
184                                 long tickDiff = nextTSEvent.getTick() - lastTSEvent.getTick();
185                                 ts = decodeTimeSig(((MetaMessage)lastTSEvent.getMessage()).getData());
186                                 beatsPerMeasure = ts[0] * (4 / ts[1]);
187                                 tickPos += ((beatsPerMeasure * measures + beats) * res + ticks);
188                         }
189                         */
190                 } else {
191                         if (sigs == 1) {
192                                 MidiEvent TSEvent = (MidiEvent)timeSignatures.get(0);
193                                 int[] ts = decodeTimeSig(((MetaMessage)TSEvent.getMessage()).getData());
194                                 beatsPerMeasure = ts[0] * (4 / ts[1]);
195                         }
196                         measures = tickPosition / (beatsPerMeasure * ticksPerBeat);
197                         beats = (tickPosition - measures * beatsPerMeasure * ticksPerBeat) / ticksPerBeat;
198                         ticks = tickPosition - measures * beatsPerMeasure * ticksPerBeat - beats * ticksPerBeat;
199                 }
200                 int[] pos = {(int)measures + 1, (int)beats + 1, (int)ticks + 1};
201                 return pos;
202         }
203
204         /** 
205          * Returns the receiver of the current sequencer.
206          * @return the receiver
207          */
208         public static Receiver getReceiver() {
209                 return receiver;
210         }
211
212         /** 
213          * Returns the current sequence.
214          * @return the current sequence
215          */
216         public static Sequence getSequence() {
217                 return seq;
218         }
219
220         /** 
221          * Returns the current sequencer.
222          * @return the current sequencer
223          */
224         public static Sequencer getSequencer() {
225                 return sequencer;
226         }
227
228         /** 
229          * Returns the tempo in the given tick position.
230          * @param tick the tick position for which to return the tempo
231          * @return the tempo at the specified tick position
232          */
233         public static int getTempo(long tick) {
234                 if (tempoChanges.size() == 0) return 120;
235                 MidiEvent tempoEvent = (MidiEvent)tempoChanges.get(0);
236                 if (tempoChanges.size() > 1) {
237                         for (int i = 1; i < tempoChanges.size(); i++) {
238                                 MidiEvent nextTempoEvent = (MidiEvent)tempoChanges.get(i);
239                                 if (nextTempoEvent.getTick() < tick && nextTempoEvent.getTick() > tempoEvent.getTick())
240                                         tempoEvent = nextTempoEvent;
241                         }
242                 }
243                 return decodeTempo(((MetaMessage)tempoEvent.getMessage()).getData());
244         }
245
246         /** 
247          * Calculates the tick position in the current sequence for the given position (measures, beats, ticks).
248          * @return the tick position.
249          */
250         public static long getTicksForPosition(int measures, int beats, int ticks) {
251                 int res = seq.getResolution();
252                 long tickPos = 0;
253                 switch (timeSignatures.size()) {
254                         case 0:
255                                 tickPos = (4 * measures + beats) * res + ticks;
256                         case 1:
257                                 MidiEvent TSEvent = (MidiEvent)timeSignatures.get(0);
258                                 int[] ts = decodeTimeSig(((MetaMessage)TSEvent.getMessage()).getData());
259                                 int beatsPerMeasure = ts[0] * (4 / ts[1]);
260                                 tickPos = (beatsPerMeasure * measures + beats) * res + ticks;
261                         default:
262                                 Iterator it = timeSignatures.iterator();
263                                 MidiEvent lastTSEvent = (MidiEvent)it.next();
264                                 if (lastTSEvent.getTick() != 0) tickPos += (int)lastTSEvent.getTick();
265                                 while(it.hasNext()) {
266                                         MidiEvent nextTSEvent = (MidiEvent)it.next();
267                                         long tickDiff = nextTSEvent.getTick() - lastTSEvent.getTick();
268                                         ts = decodeTimeSig(((MetaMessage)lastTSEvent.getMessage()).getData());
269                                         beatsPerMeasure = ts[0] * (4 / ts[1]);
270                                         tickPos += ((beatsPerMeasure * measures + beats) * res + ticks);
271                                 }
272                 }
273                 return tickPos;
274         }
275
276         /** 
277          * Returns the time signature in the given tick position.
278          * @param tick the tick position for which to return the time signature
279          * @return an array of two integers where [0] is the numerator and [1] the denominator
280          */
281         public static int[] getTimeSig(long tick) {
282                 int[] ts = {4, 4};
283                 if (timeSignatures.size() == 0) return ts;
284                 MidiEvent timeSigEvent = (MidiEvent)timeSignatures.get(0);
285                 if (timeSignatures.size() > 1) {
286                         for (int i = 1; i < timeSignatures.size(); i++) {
287                                 MidiEvent nextTimeSigEvent = (MidiEvent)timeSignatures.get(i);
288                                 if (nextTimeSigEvent.getTick() < tick && nextTimeSigEvent.getTick() > timeSigEvent.getTick())
289                                         timeSigEvent = nextTimeSigEvent;
290                         }
291                 }
292                 return decodeTimeSig(((MetaMessage)timeSigEvent.getMessage()).getData());
293         }
294
295         /** 
296          * Returns true if the current sequence has been edited.
297          * @return the tick position
298          */
299         public static boolean isEdited() {
300                 return edited;
301         }
302
303         /** 
304          * Returns whether the given track should be drawn
305          * @return true if the given track should be drawn
306          */
307         public static boolean shouldBeDrawn(Track track) {
308                 if (drawEmptyTracks) return true;
309                 else return (!emptyTracks.contains(track));
310         }
311
312
313
314
315
316
317
318
319         /*                              ***
320          **       MUTATOR METHODS        **
321          ***                              */
322
323
324
325
326
327
328
329
330         /** 
331          * Fast forwards the current sequence the given number of measures.
332          * @param measures      the number of measures to fast forward
333          */
334         public static void forward(long ticks) {
335                 editPosition += ticks;
336         }
337
338         /** 
339          * Rewinds the current sequence the given number of measures.
340          * @param measures      the number of measures to rewind
341          */
342         public static void rewind(long ticks) {
343                 editPosition -= ticks;
344         }
345
346         /** 
347          * Sets the currently active MidiChannel.
348          * @param channel       the number of the MidiChannel to activate
349          */
350         public static void setActiveChannel(int channel) {
351                 activeChannel = channels[channel];
352         }
353
354         /** 
355          * Sets the current copy buffer.
356          * @param the copy buffer
357          */
358         public static void setCopyBuffer(ArrayList buffer) {
359                 copyBuffer = buffer;
360         }
361
362         /** 
363          * Sets whether empty tracks should be drawn
364          * @param state         true if empty tracks should be drawn
365          */
366         public static void setDrawEmptyTracks(boolean state) {
367                 drawEmptyTracks = state;
368         }
369
370         /** 
371          * Sets the current sequence as edited, which implies prompts when loading a new sequence.
372          */
373         public static void setEdited() {
374                 edited = true;
375         }
376
377         /** 
378          * Sets the current editing position of the sequencer.
379          * @param ticks         the tick position
380          */
381         public static void setEditPosition(long ticks) {
382                 editPosition = ticks;
383         }
384
385         /** 
386          * Sets the current editing position of the sequencer.
387          * @param ticks         the tick position
388          */
389         public static void setTempo(int bpm) {
390                 // tempoMsg
391         }
392
393         /** 
394          * Sets the current editing position of the sequencer.
395          * @param ticks         the tick position
396          */
397         public static void setTimeSig(int bpm) {
398                 // timeSigMsg
399         }
400         
401         /** 
402          * Sets the solo setting of the given track.
403          * @param on    true for solo, false for not
404          */
405         public static void setTrackSolo(Track track, boolean on){
406                 trackSolo.put(track, new Boolean(on));  
407         }
408
409         /** 
410          * Sets the mute setting of the given track.
411          * @param on    true for mute, false for not
412          */
413         public static void setTrackMute(Track track, boolean on){
414                 trackMute.put(track, new Boolean(on));  
415         }
416
417         /** 
418          * Sets the current playback volume.
419          * @param volume        the volume, between 0 and 1
420          */
421         public void setVolume(long volume) {
422                 for (int i = 0; i < channels.length; i++) {
423                         channels[i].controlChange(7, (int)(volume * 127.0));
424                 }
425         }
426
427
428
429
430
431
432
433
434         /*                              ***
435          **  ENCODING / DECODING METHODS **
436          ***                              */
437
438
439
440
441
442
443
444
445         /** 
446          * Returns the byte array for the given tempo.
447          * @param tempo the tempo in beats per minute
448          * @return an array of bytes representing the given tempo
449          */
450         public static byte[] encodeTempo(int tempo) {
451                 int microSecsPerQuarter = 60000000 / tempo;
452                 byte[] b = new byte[3];
453                 b[0] = (byte)(microSecsPerQuarter / 65536);
454                 b[1] = (byte)((microSecsPerQuarter - (b[0] * 65536)) / 256);
455                 b[2] = (byte)(microSecsPerQuarter - (b[0] * 65536) - (b[1] * 256));
456                 return b;
457         }
458
459         /** 
460          * Returns the tempo for the given byte array.
461          * @param an array of three bytes representing the tempo
462          * @return the tempo in beats per minute
463          */
464         public static int decodeTempo(byte[] bytes) {
465                 return 60000000 / (bytes[0] * 65536 + bytes[1] * 256 + bytes[2]);
466         }
467
468         /** 
469          * Returns the byte array for the given time signature.
470          * @param numerator     the numerator of the time signature
471          * @param denominator   the denominator of the time signature
472          * @return an array of bytes representing the given time signature
473          */
474         public static byte[] encodeTimeSig(int numerator, int denominator) {
475                 byte[] b = {
476                         (byte)numerator,
477                         (byte)(Math.log(denominator) / Math.log(2)), // logarithm of denominator in base 2
478                         (byte)96,
479                         (byte)8
480                 };
481                 return b;
482         }
483         /** 
484          * Returns the time signature for the given byte array.
485          * @param an array of four bytes representing the time signature
486          * @return an array of two integers where [0] is the numerator and [1] the denominator
487          */
488         public static int[] decodeTimeSig(byte[] bytes) {
489                 int[] t = {
490                         (int)bytes[0],
491                         (int)(1 << bytes[1])
492                 };
493                 return t;
494         }
495
496
497
498
499
500
501
502
503         /*                              ***
504          **       SELECTION METHODS      **
505          ***                              */
506
507
508
509
510
511
512
513
514         /** 
515          * Returns the current selection.
516          * @return the current selection
517          */
518         public static TreeSet getSelection() {
519                 return selection;
520         }
521
522         /**
523          * Selects the given note
524          * @param the note to select
525          */
526         public static void selectNote(MooNoteElement elem) {
527                 selection.add(elem);
528         }
529
530         /**
531          * Deselects the given note
532          * @param the note to deselect
533          */
534         public static void deselectNote(MooNoteElement elem) {
535                 selection.remove(elem);
536         }
537
538         /**
539          * Deselects all notes.
540          */
541         public static void deselectAllNotes() {
542                 Iterator it = selection.iterator();
543                 while(it.hasNext()) {
544                         ((MooNoteElement)it.next()).deselect();
545                 }
546                 selection.clear();
547         }
548
549         /**
550          * Determines if the given MooNoteElement is the only one in the track view that is selected.
551          * @return if the given element is the only selected one
552          */
553         public static boolean isTheOnlySelected(MooNoteElement elem) {
554                 Iterator it = selection.iterator();
555                 while(it.hasNext()) {
556                         if (!it.next().equals(elem)) return false;
557                 }
558                 return true;
559         }
560
561
562
563
564
565
566
567
568         /*                              ***
569          **       PLAYBACK METHODS       **
570          ***                              */
571
572
573
574
575
576
577
578
579         /** 
580          * Starts playback of the current sequence.
581          */
582         public static void play() {
583                 sequencer.setTickPosition(editPosition);
584                 resume();
585         }
586
587         /** 
588          * Resumes playback of the current sequence.
589          */
590         public static void resume() {
591                 gui.update(0);
592                 try {
593                         sequencer.setSequence(seq);
594                 } catch (InvalidMidiDataException e) {}
595                 Track[] tracks = seq.getTracks();
596
597                 sequencer.start();
598
599                 for (int i = 0; i < tracks.length; i++) {
600
601                         Object ob = trackSolo.get(tracks[i]);
602                         if(ob instanceof Boolean){
603                                 sequencer.setTrackSolo(i,((Boolean)ob).booleanValue());
604                         }
605
606                         ob = trackMute.get(tracks[i]);
607                         if(ob instanceof Boolean){
608                                 sequencer.setTrackMute(i,((Boolean)ob).booleanValue());
609                         }
610                 }
611
612                 // Disables input to volatile components
613                 // gui.disable();
614
615                 // Creates the visualisation thread and starts it.
616                 player = new PlayThread();
617                 player.start();
618         }
619
620         /** 
621          * Pauses playback of the current sequence.
622          */
623         public static void pause() {
624                 if (sequencer.isRunning()) {
625                         sequencer.stop();
626                 }
627                 player = null;
628         }
629
630         /** 
631          * Stops playback of the current sequence.
632          */
633         public static void stop() {
634                 if (sequencer.isRunning()) {
635                         sequencer.stop();
636                 }
637                 sequencer.setTickPosition(editPosition);
638                 player = null;
639                 gui.update((long)0);
640         }
641
642
643
644
645
646
647
648
649         /*                              ***
650          **       SYSTEM & IO METHODS    **
651          ***                              */
652
653
654
655
656
657
658
659
660         /** 
661          * Replaces the current sequence with a new one, holding three empty tracks.
662          */
663         public static void clearSequence() {
664                 try {
665                         // Creates a new sequence.
666                         seq = new Sequence(Sequence.PPQ, DEFAULT_RESOLUTION, DEFAULT_TRACKS);
667                         Track[] tracks = seq.getTracks();
668
669                         // Creates messages for default tempo (120) and time signature (4/4), and adds them to track 0.
670                         MetaMessage timeSigMsg = new MetaMessage();
671                         MetaMessage tempoMsg = new MetaMessage();
672                         try {
673                                 timeSigMsg.setMessage(88, encodeTimeSig(4, 4), 4);
674                                 tempoMsg.setMessage(81, encodeTempo(120), 3);
675                         } catch (InvalidMidiDataException e) {}
676                         tracks[0].add(new MidiEvent(timeSigMsg, (long)0));
677                         tracks[0].add(new MidiEvent(tempoMsg, (long)0));
678
679                         // Sets program and title for the tracks.
680                         initializeTrack(tracks[1], 0, 24, "Guitar");
681                         initializeTrack(tracks[2], 1, 33, "Bass");
682                         initializeTrack(tracks[3], 9, 0, "Drums");
683                 } catch (InvalidMidiDataException e) {}
684
685                 // Reinitializes sequence variables
686                 file = null;
687                 reinitializeVariables();
688
689                 // Sends the sequence to the GUI.
690                 if (gui != null) gui.setSequence(seq, null);
691         }
692
693         /** 
694          * Creates event in the given track for program change and title.
695          */
696         private static void initializeTrack(Track track, int channel, int program, String title) {
697                         // Creates program change and title message.
698                         ShortMessage programMsg = new ShortMessage();
699                         MetaMessage titleMsg = new MetaMessage();
700
701                         // Sets the data of the messages.
702                         try {
703                                 programMsg.setMessage(ShortMessage.PROGRAM_CHANGE, channel, program, 0);
704                                 titleMsg.setMessage(3, title.getBytes(), title.length());
705                         } catch (InvalidMidiDataException e) {}
706
707                         // Sends the program change to the channel
708                         getChannel(channel).programChange(program);
709
710                         // Adds them to the track.
711                         track.add(new MidiEvent(programMsg, (long)0));
712                         track.add(new MidiEvent(titleMsg, (long)0));
713         }
714         
715         /** 
716          * Wraps each NoteOn event in the track with its NoteOff event in a MooNote.
717          * @param track         the track to convert
718          * @param quantize      whether to round locations and durations in the track to nearest 16th
719          * @return a list of the created MooNotes
720          */
721         public static List convertTrack(Track track, boolean quantize) {
722                 // Searches the track for NoteOn and NoteOff events
723                 ArrayList noteOns = new ArrayList(track.size() / 2);
724                 ArrayList noteOffs = new ArrayList(track.size() / 2);
725                 ArrayList newMooNotes = new ArrayList();
726                 MidiEvent event;
727                 for (int j = 0; j < track.size(); j++) {
728                         event = track.get(j);
729                         if (event.getMessage().getStatus() >= 144 &&
730                             event.getMessage().getStatus() < 160) noteOns.add(event);
731                         if (event.getMessage().getStatus() >= 128 &&
732                             event.getMessage().getStatus() < 144) noteOffs.add(event);
733                 }
734                 noteOns.trimToSize();
735                 noteOffs.trimToSize();
736                 if (noteOns.size() == 0) emptyTracks.add(track);
737                 
738                 // Sorts the note lists by tick position.
739                 Comparator c = new MidiEventComparator();
740                 Collections.sort(noteOns, c);
741                 Collections.sort(noteOffs, c);
742
743                 // Replaces each NoteOn event it with a MooNote containing a reference to the NoteOff event.
744                 Iterator iOn = noteOns.iterator(), iOff;
745                 MidiEvent on, off = null, nextOff;
746                 ShortMessage onMsg, nextOffMsg;
747                 while(iOn.hasNext()) {
748                         on = (MidiEvent)iOn.next();
749                         if (!(on instanceof MooNote)) {
750                                 onMsg = (ShortMessage)on.getMessage();
751                                 iOff = noteOffs.iterator();
752                                 while(iOff.hasNext()) {
753                                         nextOff = (MidiEvent)iOff.next();
754                                         nextOffMsg = (ShortMessage)nextOff.getMessage();
755                                         if(onMsg.getChannel() == nextOffMsg.getChannel() &&
756                                            onMsg.getData1() == nextOffMsg.getData1() &&
757                                            c.compare(nextOff, on) > 0) {
758                                                 off = nextOff;
759                                                 iOff.remove();
760                                                 break;
761                                         }
762                                                 
763                                 }
764                                 track.remove(on);
765                                 MooNote mn;
766                                 if (off != null) {
767                                         mn = new MooNote(on, off);
768                                 } else {
769                                         mn = new MooNote(on, new MidiEvent((ShortMessage)on.getMessage().clone(), on.getTick() + 48));
770                                 }
771                                 track.add(mn);
772                                 newMooNotes.add(mn);
773                                 iOn.remove();
774                         }
775                 }
776                 if (quantize) quantize(newMooNotes, SIXTEENTH_NOTE, true, true);
777                 return newMooNotes;
778         }
779
780         /** 
781          * Loads a MIDI sequence from the given file.
782          * @param filename      the filename to use
783          */
784         public static boolean load(File loadFile) {
785                 // Loads sequence from file
786                 try {seq = MidiSystem.getSequence(loadFile);}
787                 catch (Exception e) {return false;}
788                 file = loadFile;
789                 edited = false;
790
791                 Track[] tracks = seq.getTracks();
792                 reinitializeVariables();
793
794                 // Searches track 0 for changes in tempo and time signature.
795                 MidiEvent event;
796                 MetaMessage metaMsg;
797                 for (int i = 0; i < tracks[0].size(); i++) {
798                         event = tracks[0].get(i);
799                         if (event.getMessage().getStatus() == MetaMessage.META) {
800                                 metaMsg = (MetaMessage)event.getMessage();
801                                 switch(metaMsg.getType()) {
802                                         case 81: tempoChanges.add(event); break;
803                                         case 88: timeSignatures.add(event);
804                                 }
805                         }
806                 }
807                 Comparator c = new MidiEventComparator();
808                 Collections.sort(tempoChanges, c);
809                 Collections.sort(timeSignatures, c);
810
811                 // Converts tracks.
812                 for (int i = 0; i < tracks.length; i++) {
813                         convertTrack(tracks[i], false);
814                 }
815
816                 // Sends sequence to GUI and sequencer, then returns
817                 if (gui != null) gui.setSequence(seq, file);
818                 try {
819                         sequencer.setSequence(seq);
820                 } catch (InvalidMidiDataException e) {}
821                 return true;
822         }
823
824         /** 
825          * Quantizes the given list of MIDI events
826          * @param notes         a list of the notes to quantize
827          * @param resolution    the note size to round each note to
828          * @param location      whether the quantize should affect the location of the note
829          * @param duration      whether the quantize should affect the duration of the note
830          */
831         public static void quantize(List notes, int resolution, boolean location, boolean duration) {
832                 // Math.round(mn.getTick() / ticksPerSixteenth);
833         }
834
835         /** 
836          * Reinitializes sequence-specific variables.
837          */
838         private static void reinitializeVariables() {
839                 emptyTracks = new ArrayList();
840                 timeSignatures = new ArrayList();
841                 tempoChanges = new ArrayList();
842                 trackSolo = new HashMap();
843                 trackMute = new HashMap();
844                 copyBuffer = new ArrayList();
845                 selection = new TreeSet();
846         }
847
848         /** 
849          * Loads the user preferences.
850          */
851         public static void loadPreferences() {
852                 
853         }
854
855         /** 
856          * Saves the user preferences.
857          */
858         public static void savePreferences() {
859                 
860         }
861
862         /** 
863          * Prompts the user to save any unsaved changes.
864          */
865         public static boolean promptOnUnsavedChanges() {
866                 if (!edited) return false;
867                 int exitOption = JOptionPane.showConfirmDialog(gui,
868                         "The current sequence has been edited, but not saved.\nDo you wish to continue anyway?",
869                         "File not saved - continue?", 
870                         JOptionPane.OK_CANCEL_OPTION, 
871                         JOptionPane.WARNING_MESSAGE);
872                 if (exitOption == JOptionPane.CANCEL_OPTION || exitOption == JOptionPane.CLOSED_OPTION) return true;
873                 return false;
874         }
875
876         /** 
877          * Saves the current sequence to the previously given filename.
878          */
879         public static boolean save() {
880                 if (file == null) return false;
881                 else {
882                         saveAs(file);
883                         return true;
884                 }
885         }
886
887         /** 
888          * Saves the current sequence to the given filename
889          * @param file  the filename to use
890          */
891         public static boolean saveAs(File saveFile) {
892                 try {
893                         MidiSystem.write(seq, 1, saveFile);
894                         file = saveFile;
895                         edited = false;
896                         gui.setStatus("Saved " + file.getAbsolutePath());
897                         return true;
898                 } catch (IOException e) {
899                         gui.setStatus("Failed in saving " + file.getAbsolutePath());
900                         return false;
901                 }
902         }
903
904         /** 
905          * Prints the given string to the System output.
906          */
907         private static void out(String text, boolean newLine) {
908                 if (newLine) System.out.println(text);
909                 else System.out.print(text);
910         }
911
912         /** 
913          * Advances the current progress counter by printing a "." to the System output.
914          */
915         private static void advanceStatus() {
916                 out(".", false);
917         }
918
919         /** 
920          * Releases all reserved devices and exits the program.
921          */
922         public static void quit() {
923                 if (gui != null) {
924                         if (promptOnUnsavedChanges()) return;
925                 }
926                 savePreferences();
927                 if (sequencer.isOpen()) sequencer.close();
928                 if (synthesizer.isOpen()) synthesizer.close();
929                 System.exit(0);
930         }
931         
932         /** 
933          * A Ccmparator for sorting lists of MidiEvents.
934          */
935         public static class MidiEventComparator implements Comparator {
936                 public int compare(Object o1, Object o2) {
937                         int diff = (int)(((MidiEvent)o1).getTick() - ((MidiEvent)o2).getTick());
938                         if (diff != 0 || !(((MidiEvent)o1).getMessage() instanceof ShortMessage) || !(((MidiEvent)o2).getMessage() instanceof ShortMessage)) return diff;
939                         return (((ShortMessage)((MidiEvent)o1).getMessage()).getData1() - ((ShortMessage)((MidiEvent)o2).getMessage()).getData1());
940                 }
941         }
942
943         /** 
944          * The thread that updates the GUI during playback.
945          */
946         public static class PlayThread extends Thread {
947                 public void run() {
948                         Thread currentThread = Thread.currentThread();
949                         while(currentThread == player) {
950                                 gui.update(sequencer.getTickPosition());
951                                 try {
952                                         sleep(10);
953                                 } catch (InterruptedException e) {
954                                         Moosique.stop();
955                                 }
956                         }
957                 }
958         }
959
960         /** 
961          * A listener for detecting the Meta event signifying the end of the sequence.
962          */
963         public static class SongEndListener implements MetaEventListener {
964                 public void meta(MetaMessage event) {
965                         if (event.getType() == 47)
966                                 stop();
967                 }
968         }
969 }