]> ruin.nu Git - moosique.git/blob - Moosique.java
89669d37e972d197ffdba584062477e2c6ae5c84
[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         private static MidiEvent[] timeSignatures, tempoChanges;
23
24         private static String filename, fileArg;
25         private static long editPosition;
26         private static boolean makeGUI = true, isEdited;
27         private static Thread player;
28         public static final int DEFAULT_RESOLUTION = 96, DEFAULT_TRACKS = 4;
29
30         /** 
31          * Starts the application.
32          */
33         public static void main (String[] args) {
34                 System.out.println("\nMoosique version 1.0\n");
35
36                 // Parses command-line arguments.
37                 for (int i = 0; i < args.length; i++) {
38                         if (args[i].equals("-n")) {makeGUI = false;}
39                         else if (fileArg == null) {fileArg = args[i];}
40                 }
41
42                 // Acquires MIDI devices and connects them.
43                 System.out.print("Initializing MIDI devices.");
44                 try {
45                         sequencer = MidiSystem.getSequencer();
46                         System.out.print(".");
47                         sequencer.open();
48                         synthesizer = MidiSystem.getSynthesizer();
49                         System.out.print(".");
50                         synthesizer.open();
51                         sequencer.getTransmitter().setReceiver(synthesizer.getReceiver());
52                         channels = synthesizer.getChannels();
53                         setActiveChannel(0);
54                 } catch (MidiUnavailableException e) {
55                         System.out.println("Failed, quitting.");
56 //                      System.exit(1);
57                 }
58                 System.out.println("Done");
59
60                 //If a filename is given as the command-line argument, attempts to load a sequence from the file.
61                 if (fileArg != null) {
62                         System.out.print("Loading MIDI sequence from " + fileArg + "...");
63                         if (!load(fileArg)) {
64                                 System.out.println("Failed");
65                                 clearSequence();
66                         } else {
67                                 System.out.println("Done");
68                         }
69                 } else {
70                         // Otherwise creates a new empty one.
71                         clearSequence();
72                 }
73
74                 // Builds GUI, unless n-flag is set.
75                 if (makeGUI) {
76                         System.out.print("Building GUI...");
77                         try {
78                                 UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
79                         } catch (Exception e) {}
80                         gui = new MooGUI(seq);
81                         System.out.println("Done");
82                 } else {
83                         System.out.print("Playing...");
84                         play();
85                         while (sequencer.isRunning()) {}
86                         System.out.println("Done");
87                         quit();
88                 }
89         }
90
91         /** 
92          * Returns the GUI.
93          * @return the GUI
94          */
95         public static MooGUI getGUI() {
96                 return gui;
97         }
98
99         /** 
100          * Returns the current sequence.
101          * @return the current sequence
102          */
103         public static Sequence getSequence() {
104                 return seq;
105         }
106
107         /** 
108          * Returns the current sequencer.
109          * @return the current sequencer
110          */
111         public static Sequencer getSequencer() {
112                 return sequencer;
113         }
114
115         /** 
116          * Returns the MidiChannels of the selected synthesizer.
117          * @return the available MidiChannels
118          */
119         public static MidiChannel[] getChannels() {
120                 return channels;
121         }
122
123         /** 
124          * Returns the MidiChannels of the selected synthesizer.
125          * @return the available MidiChannels
126          */
127         public static MidiChannel getChannel(int i) {
128                 return channels[i];
129         }
130
131         /** 
132          * Returns the currently active MidiChannel.
133          * @return the active MidiChannel
134          */
135         public static MidiChannel getActiveChannel() {
136                 return activeChannel;
137         }
138
139         /** 
140          * Sets the currently active MidiChannel.
141          * @param channel       the number of the MidiChannel to activate
142          */
143         public static void setActiveChannel(int channel) {
144                 activeChannel = channels[channel];
145         }
146
147         /** 
148          * Replaces the current sequence with a new one, holding three empty tracks.
149          */
150         public static void clearSequence() {
151                 // Creates a new sequence and sends it to the sequencer.
152                 try {
153                         seq = new Sequence(Sequence.PPQ, DEFAULT_RESOLUTION, DEFAULT_TRACKS);
154                         sequencer.setSequence(seq);
155                 } catch (InvalidMidiDataException e) {}
156                 // Sends sequence to GUI.
157                 if (gui != null) gui.setSequence(seq);
158         }
159
160         /** 
161          * Starts playback of the current sequence.
162          */
163         public static void play() {
164                 sequencer.setTickPosition(editPosition);
165                 resume();
166         }
167
168         /** 
169          * Pauses playback of the current sequence.
170          */
171         public static void pause() {
172                 if (sequencer.isRunning()) {
173                         sequencer.stop();
174                 }
175                 if (player != null) player.interrupt();
176         }
177
178         /** 
179          * Resumes playback of the current sequence.
180          */
181         public static void resume() {
182                 gui.update(0);
183                 sequencer.start();
184
185                 // Disables input to volatile components
186                 // gui.disable();
187
188                 System.out.println("Ticks/16: " + seq.getResolution()/4);
189
190                 // Creates the visualisation thread and starts it.
191                 player = new Thread () {
192                         public void run() {
193                                 long position = sequencer.getTickPosition();
194                                 while(sequencer.isRunning()) {
195                                         long pos = sequencer.getTickPosition();
196                                         System.out.print(" ... " + (pos - position));
197                                         position = pos;
198
199                                         // Updates the GUI with the current tick position.
200                                         gui.update(sequencer.getTickPosition());
201
202                                         // Puts the thread to sleep for as long as it takes
203                                         // the sequencer to reach the next sixteenth.
204                                         try {
205                                                 sleep((long)(15000 / getTempo()));
206                                         } catch (InterruptedException e) {
207                                                 Moosique.stop();
208                                         }
209                                 }
210                                 Moosique.stop();
211                         }
212                 };
213                 player.start();
214         }
215
216         /** 
217          * Stops playback of the current sequence.
218          */
219         public static void stop() {
220                 if (sequencer.isRunning()) {
221                         sequencer.stop();
222                 }
223                 sequencer.setTickPosition(editPosition);
224                 if (player != null) player.interrupt();
225                 gui.update((long)0);
226         }
227
228         /** 
229          * Returns the current editing position of the sequencer.
230          * @return the tick position
231          */
232         public static long getEditPosition() {
233                 return editPosition;
234         }
235
236         /** 
237          * Sets the current editing position of the sequencer.
238          * @param ticks         the tick position
239          */
240         public static void setEditPosition(long ticks) {
241                 editPosition = ticks;
242         }
243
244         /** 
245          * Returns the tempo of the current sequence.
246          * @return the tick position
247          */
248         public static int getTempo() {
249                 return 120;
250                 // if (tempoMsg == null) return 0;
251         }
252
253         /** 
254          * Sets the current editing position of the sequencer.
255          * @param ticks         the tick position
256          */
257         public static void setTempo(int bpm) {
258                 // tempoMsg
259         }
260
261         /** 
262          * Returns the tempo of the current sequence.
263          * @return the tick position
264          */
265         public static int[] getTimeSig() {
266                 int[] ts = {4, 4};
267                 return ts;
268                 // if (timeSigMsg == null) return 0;
269         }
270
271         /** 
272          * Sets the current editing position of the sequencer.
273          * @param ticks         the tick position
274          */
275         public static void setTimeSig(int bpm) {
276                 // timeSigMsg
277         }
278
279         /** 
280          * Returns true if the current sequence has been edited.
281          * @return the tick position
282          */
283         public static boolean isEdited() {
284                 return isEdited;
285         }
286
287         /** 
288          * Sets the current sequence as edited, which implies prompts when loading a new sequence.
289          */
290         public static void setEdited() {
291                 isEdited = true;
292         }
293
294         /** 
295          * Rewinds the current sequence the given number of measures.
296          * @param measures      the number of measures to rewind
297          */
298         public static void rewind(long ticks) {
299                 editPosition -= ticks;
300         }
301
302         /** 
303          * Fast forwards the current sequence the given number of measures.
304          * @param measures      the number of measures to fast forward
305          */
306         public static void forward(long ticks) {
307                 editPosition += ticks;
308         }
309
310         /** 
311          * Loads the MooSequence in the given file.
312          * @param filename      the filename to use
313          */
314         public static boolean load(String file) {
315                 // Loads sequence from file
316                 filename = file;
317                 try {
318                         seq = MidiSystem.getSequence(new File(filename));
319                 } catch (InvalidMidiDataException e) {
320                         return false;
321                 } catch (IOException e) {
322                         return false;
323                 }
324                 isEdited = false;
325
326                 Track[] tracks = seq.getTracks();
327
328                 // Searches track 0 for changes in tempo and time signature.
329                 MidiEvent event;
330                 MetaMessage metaMsg;
331                 ArrayList ts = new ArrayList(), tc = new ArrayList();
332                 for (int i = 0; i < tracks[0].size(); i++) {
333                         event = tracks[0].get(i);
334                         if (event.getMessage().getStatus() == MetaMessage.META) {
335                                 metaMsg = (MetaMessage)event.getMessage();
336                                 switch(metaMsg.getType()) {
337                                         case 81: tc.add(event); break;
338                                         case 88: ts.add(event);
339                                 }
340                         }
341                 }
342 //              timeSignatures = ts.toArray(timeSignatures);
343 //              tempoChanges = tc.toArray(tempoChanges);
344
345                 // Wraps each NoteOn event with its NoteOff event in a MooNote
346                 ArrayList noteOns, noteOffs;
347                 for (int i = 0; i < tracks.length; i++) {
348                         // Searches the sequence for NoteOn and NoteOff events
349                         noteOns = new ArrayList(tracks[i].size() / 2);
350                         noteOffs = new ArrayList(tracks[i].size() / 2);
351                         for (int j = 0; j < tracks[i].size(); j++) {
352                                 event = tracks[i].get(j);
353                                 if (event.getMessage().getStatus() >= 144 &&
354                                     event.getMessage().getStatus() < 160) noteOns.add(event);
355                                 if (event.getMessage().getStatus() >= 128 &&
356                                     event.getMessage().getStatus() < 144) noteOffs.add(event);
357                         }
358                         noteOns.trimToSize();
359                         noteOffs.trimToSize();
360                         
361                         // Sorts the note lists by tick position.
362                         Comparator c = new Comparator() {
363                                 public int compare(Object o1, Object o2) {
364                                         return (int)(((MidiEvent)o1).getTick() - ((MidiEvent)o2).getTick());
365                                 }
366                         };
367                         Collections.sort(noteOns, c);
368                         Collections.sort(noteOffs, c);
369
370                         // For each NoteOn event, finds its NoteOff event and replaces it with a MooNote.
371                         Iterator iOn = noteOns.iterator(), iOff;
372                         MidiEvent on, off = null, nextOff;
373                         ShortMessage onMsg, nextOffMsg;
374                         while(iOn.hasNext()) {
375                                 on = (MidiEvent)iOn.next();
376                                 onMsg = (ShortMessage)on.getMessage();
377                                 iOff = noteOffs.iterator();
378                                 while(iOff.hasNext()) {
379                                         nextOff = (MidiEvent)iOff.next();
380                                         nextOffMsg = (ShortMessage)nextOff.getMessage();
381                                         if(onMsg.getChannel() == nextOffMsg.getChannel() &&
382                                            onMsg.getData1() == nextOffMsg.getData1() &&
383                                            c.compare(nextOff, on) > 0) {
384                                                 off = nextOff;
385                                                 iOff.remove();
386                                                 break;
387                                         }
388                                                 
389                                 }
390                                 tracks[i].remove(on);
391                                 if (off != null) {
392                                         tracks[i].add(new MooNote(on, off));
393                                 } else {
394                                         tracks[i].add(new MooNote(on));
395                                 }
396                                 iOn.remove();
397                         }
398                 }
399
400         /*
401                 Collections.sort(track[i].events, new Comparator() {
402                         public int compare(Object o1, Object o2) {
403                                 return ((MidiEvent)o2).getTick() - ((MidiEvent)o1).getTick();
404                         }
405                 });
406
407                 // Searches the sequence for NoteOn events
408                 MidiEvent noteOn, noteOff = null, nextEvent;
409                 MidiMessage nextMsg;
410                 ShortMessage shortMsg;
411
412                 for (int i = 0; i < tracks.length; i++) {
413                         for (int j = 0; j < tracks[i].size(); j++) {
414                                 noteOn = tracks[i].get(j);
415                                 if (noteOn.getMessage().getStatus() == ShortMessage.NOTE_ON) {
416                                         // Finds the corresponding NoteOff event
417                                         for (int k = j + 1; k < tracks[i].size(); k++) {
418                                                 nextEvent = tracks[i].get(k);
419                                                 nextMsg = nextEvent.getMessage();
420                                                 if (nextMsg instanceof ShortMessage) {
421                                                         shortMsg = (ShortMessage) nextMsg;
422                                                         if (shortMsg.getCommand() == ShortMessage.NOTE_OFF && shortMsg.getChannel() == ((ShortMessage)noteOn.getMessage()).getChannel() && shortMsg.getData1() == ((ShortMessage)noteOn.getMessage()).getData1()) {
423                                                                 noteOff = nextEvent;
424                                                                 break;
425                                                         }
426                                                 }
427                                         }
428                                         // Replaces the NoteOn event with a MooNote, if possible with the corresponding NoteOff event
429                                         tracks[i].remove(noteOn);
430                                         if (noteOff != null) {
431                                                 tracks[i].add(new MooNote(noteOn, noteOff));
432                                         } else {
433                                                 tracks[i].add(new MooNote(noteOn));
434                                         }
435                                 }
436                         }
437                 }
438 */
439                 // Sends sequence to GUI and sequencer, then returns
440                 if (gui != null) gui.setSequence(seq);
441                 try {
442                         sequencer.setSequence(seq);
443                 } catch (InvalidMidiDataException e) {}
444                 return true;
445         }
446
447         /** 
448          * Saves the current sequence to the given filename
449          * @param file  the filename to use
450          */
451         public static void saveAs(String file) {
452                 try {
453                         MidiSystem.write(seq, 1, new File(filename));
454                 } catch (IOException e) {}
455                 filename = file;
456                 gui.setStatus("Saved " + file);
457         }
458
459         /** 
460          * Saves the current sequence to the previously given filename.
461          */
462         public static void save() {
463                 saveAs(filename);
464         }
465
466         /** 
467          * Releases all reserved devices and exits the program.
468          */
469         public static void quit() {
470                 if (sequencer.isOpen()) sequencer.close();
471                 if (synthesizer.isOpen()) synthesizer.close();
472                 System.exit(0);
473         }
474 }