]> ruin.nu Git - moosique.git/blob - Moosique.java
Fixed a play thread that almost works
[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 MidiChannel[] channels;
21         private static MidiChannel activeChannel;
22
23         private static String filename, fileArg;
24         private static long position;
25         private static boolean makeGUI = true, isEdited;
26         private static Thread player;
27
28         /** 
29          * Starts the application.
30          */
31         public static void main (String[] args) {
32                 System.out.println("\nMoosique version 1.0\n");
33
34                 // Parses command-line arguments.
35                 for (int i = 0; i < args.length; i++) {
36                         if (args[i].equals("-n")) {makeGUI = false;}
37                         else if (fileArg == null) {fileArg = args[i];}
38                 }
39
40                 // Acquires MIDI devices and connects them.
41                 System.out.print("Initializing MIDI devices.");
42                 try {
43                         sequencer = MidiSystem.getSequencer();
44                         System.out.print(".");
45                         sequencer.open();
46                         synthesizer = MidiSystem.getSynthesizer();
47                         System.out.print(".");
48                         synthesizer.open();
49                         sequencer.getTransmitter().setReceiver(synthesizer.getReceiver());
50                         channels = synthesizer.getChannels();
51                         setActiveChannel(0);
52                 } catch (MidiUnavailableException e) {
53                         System.out.println("Failed, quitting.");
54 //                      System.exit(1);
55                 }
56                 System.out.println("Done");
57
58                 //If a filename is given as the command-line argument, attempts to load a sequence from the file.
59                 if (fileArg != null) {
60                         System.out.print("Loading MIDI sequence from " + fileArg + "...");
61                         if (!load(fileArg)) {
62                                 System.out.println("Failed");
63                                 clearSequence();
64                         } else {
65                                 System.out.println("Done");
66                         }
67                 } else {
68                         // Otherwise creates a new empty one.
69                         clearSequence();
70                 }
71
72                 // Builds GUI, unless n-flag is set.
73                 if (makeGUI) {
74                         System.out.print("Building GUI...");
75                         try {
76                                 UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
77                         } catch (Exception e) {}
78                         gui = new MooGUI(seq);
79                         System.out.println("Done");
80                 } else {
81                         System.out.print("Playing...");
82                         play();
83                         while (sequencer.isRunning()) {}
84                         System.out.println("Done");
85                         quit();
86                 }
87         }
88
89         /** 
90          * Returns the GUI.
91          * @return the GUI
92          */
93         public static MooGUI getGUI() {
94                 return gui;
95         }
96
97         /** 
98          * Returns the current sequence.
99          * @return the current sequence
100          */
101         public static Sequence getSequence() {
102                 return seq;
103         }
104
105         /** 
106          * Returns the current sequencer.
107          * @return the current sequencer
108          */
109         public static Sequencer getSequencer() {
110                 return sequencer;
111         }
112
113         /** 
114          * Returns the MidiChannels of the selected synthesizer.
115          * @return the available MidiChannels
116          */
117         public static MidiChannel[] getChannels() {
118                 return channels;
119         }
120
121         /** 
122          * Returns the currently active MidiChannel.
123          * @return the active MidiChannel
124          */
125         public static MidiChannel getActiveChannel() {
126                 return activeChannel;
127         }
128
129         /** 
130          * Sets the currently active MidiChannel.
131          * @param channel       the number of the MidiChannel to activate
132          */
133         public static void setActiveChannel(int channel) {
134                 activeChannel = channels[channel];
135         }
136
137         /** 
138          * Replaces the current sequence with a new one, holding three empty tracks.
139          */
140         public static void clearSequence() {
141                 // Creates a new sequence and sends it to the sequencer.
142                 try {
143                         seq = new Sequence(Sequence.PPQ, 96, 3);
144                         sequencer.setSequence(seq);
145                 } catch (InvalidMidiDataException e) {}
146                 // Sends sequence to GUI.
147                 if (gui != null) gui.setSequence(seq);
148         }
149
150         /** 
151          * Starts playback of the current sequence.
152          */
153         public static void play() {
154                 sequencer.setTickPosition(position);
155                 resume();
156                 
157         }
158
159         /** 
160          * Pauses playback of the current sequence.
161          */
162         public static void pause() {
163                 sequencer.stop();
164                 player.destroy();
165         }
166
167         /** 
168          * Resumes playback of the current sequence.
169          */
170         public static void resume() {
171                 sequencer.start();
172
173                 // Disables input to volatile components
174                 // gui.disable();
175
176                 // Creates the visualization thread and starts it.
177                 player = new Thread () {
178                         public void run() {
179                                 gui.update(sequencer.getTickPosition());
180                         }
181                 };
182                 player.start();
183         }
184
185         /** 
186          * Stops playback of the current sequence.
187          */
188         public static void stop() {
189                 sequencer.stop();
190                 sequencer.setTickPosition(position);
191                 player.destroy();
192                 gui.update((long)0);
193         }
194
195         /** 
196          * Returns the current tick position of the sequencer.
197          * @return the tick position
198          */
199         public static long getPosition() {
200                 return position;
201         }
202
203         /** 
204          * Sets the current tick position of the sequencer.
205          * @param ticks         the tick position
206          */
207         public static void setPosition(long ticks) {
208                 position = ticks;
209         }
210
211         /** 
212          * Returns true if the current sequence has been edited.
213          * @return the tick position
214          */
215         public static boolean isEdited() {
216                 return isEdited;
217         }
218
219         /** 
220          * Sets the current sequence as edited, which implies prompts when loading a new sequence.
221          */
222         public static void setEdited() {
223                 isEdited = true;
224         }
225
226         /** 
227          * Rewinds the current sequence the given number of measures.
228          * @param measures      the number of measures to rewind
229          */
230         public static void rewind(long ticks) {
231                 setPosition(position - ticks);
232         }
233
234         /** 
235          * Fast forwards the current sequence the given number of measures.
236          * @param measures      the number of measures to fast forward
237          */
238         public static void forward(long ticks) {
239                 setPosition(position + ticks);
240         }
241
242         /** 
243          * Loads the MooSequence in the given file.
244          * @param filename      the filename to use
245          */
246         public static boolean load(String file) {
247                 // Loads sequence from file
248                 filename = file;
249                 try {
250                         seq = MidiSystem.getSequence(new File(filename));
251                 } catch (InvalidMidiDataException e) {
252                         return false;
253                 } catch (IOException e) {
254                         return false;
255                 }
256                 isEdited = false;
257                 
258                 // Searches the sequence for NoteOn events
259                 Track[] tracks = seq.getTracks();
260                 MidiEvent noteOn, noteOff = null, nextEvent;
261                 MidiMessage nextMsg;
262                 ShortMessage shortMsg;
263                 for (int i = 0; i < tracks.length; i++) {
264         /*
265                         Collections.sort(track[i].events, new Comparator() {
266                                 public int compare(Object o1, Object o2) {
267                                         return ((MidiEvent)o2).getTick() - ((MidiEvent)o1).getTick();
268                                 }
269                         });
270         */
271                         for (int j = 0; j < tracks[i].size(); j++) {
272                                 noteOn = tracks[i].get(j);
273                                 if (noteOn.getMessage() instanceof ShortMessage) {
274                                         if (((ShortMessage)noteOn.getMessage()).getCommand() == ShortMessage.NOTE_ON) {
275                                                 // Finds the corresponding NoteOff event
276                                                 for (int k = j + 1; k < tracks[i].size(); k++) {
277                                                         nextEvent = tracks[i].get(k);
278                                                         nextMsg = nextEvent.getMessage();
279                                                         if (nextMsg instanceof ShortMessage) {
280                                                                 shortMsg = (ShortMessage) nextMsg;
281                                                                 if (shortMsg.getCommand() == ShortMessage.NOTE_OFF && shortMsg.getChannel() == ((ShortMessage)noteOn.getMessage()).getChannel() && shortMsg.getData1() == ((ShortMessage)noteOn.getMessage()).getData1()) {
282                                                                         noteOff = nextEvent;
283                                                                         break;
284                                                                 }
285                                                         }
286                                                 }
287                                                 // Replaces the NoteOn event with a MooNote, if possible with the corresponding NoteOff event
288                                                 tracks[i].remove(noteOn);
289                                                 if (noteOff != null) {
290                                                         tracks[i].add(new MooNote(noteOn, noteOff));
291                                                 } else {
292                                                         tracks[i].add(new MooNote(noteOn));
293                                                 }
294                                         }
295                                 }
296                         }
297                 }
298                 // Sends sequence to GUI and sequencer, then returns
299                 if (gui != null) gui.setSequence(seq);
300                 try {
301                         sequencer.setSequence(seq);
302                 } catch (InvalidMidiDataException e) {}
303                 return true;
304         }
305
306         /** 
307          * Saves the current sequence to the given filename
308          * @param file  the filename to use
309          */
310         public static void saveAs(String file) {
311                 try {
312                         MidiSystem.write(seq, 1, new File(filename));
313                 } catch (IOException e) {}
314                 filename = file;
315         }
316
317         /** 
318          * Saves the current sequence to the previously given filename.
319          */
320         public static void save() {
321                 saveAs(filename);
322         }
323
324         /** 
325          * Releases all reserved devices and exits the program.
326          */
327         public static void quit() {
328                 if (sequencer.isOpen()) sequencer.close();
329                 if (synthesizer.isOpen()) synthesizer.close();
330                 System.exit(0);
331         }
332 }