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