]> ruin.nu Git - moosique.git/blob - Moosique.java
font settings..
[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
25         private static String filename;
26         private static long editPosition;
27         private static boolean makeGUI = true, isEdited = false, drawEmptyTracks = false;
28         private static Thread player;
29         public static final int DEFAULT_RESOLUTION = 96, DEFAULT_TRACKS = 4;
30
31         /** 
32          * Starts the application.
33          */
34         public static void main (String[] args) {
35                 System.out.println("\nMoosique version 1.0\n");
36
37                 // Parses command-line arguments.
38                 String fileArg = null;
39                 for (int i = 0; i < args.length; i++) {
40                         if (args[i].equals("-n")) {makeGUI = false;}
41                         else if (fileArg == null) {fileArg = args[i];}
42                 }
43
44                 // Acquires MIDI devices and connects them.
45                 System.out.print("Initializing MIDI devices.");
46                 try {
47                         sequencer = MidiSystem.getSequencer();
48                         System.out.print(".");
49                         sequencer.open();
50                         synthesizer = MidiSystem.getSynthesizer();
51                         System.out.print(".");
52                         synthesizer.open();
53                         sequencer.getTransmitter().setReceiver(synthesizer.getReceiver());
54                         channels = synthesizer.getChannels();
55                         setActiveChannel(0);
56                 } catch (MidiUnavailableException e) {
57                         System.out.println("Failed, quitting.");
58                 }
59                 System.out.println("Done");
60
61                 //If a filename is given as the command-line argument, attempts to load a sequence from the file.
62                 if (fileArg != null) {
63                         System.out.print("Loading MIDI sequence from " + fileArg + "...");
64                         if (!load(fileArg)) {
65                                 System.out.println("Failed, creating new sequence");
66                                 clearSequence();
67                         } else {
68                                 System.out.println("Done");
69                         }
70                 } else {
71                         // Otherwise creates a new empty one.
72                         clearSequence();
73                 }
74
75                 // Builds GUI, unless n-flag is set.
76                 if (makeGUI) {
77                         System.out.print("Building GUI.");
78                         try {
79                                 UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
80                         } catch (Exception e) {}
81                         gui = new MooGUI(seq);
82                         System.out.println("Done");
83                 } else {
84                         System.out.print("Playing...");
85                         play();
86                         while (sequencer.isRunning()) {}
87                         System.out.println("Done");
88                         quit();
89                 }
90         }
91
92         /** 
93          * Returns the GUI.
94          * @return the GUI
95          */
96         public static MooGUI getGUI() {
97                 return gui;
98         }
99
100         /** 
101          * Returns the current sequence.
102          * @return the current sequence
103          */
104         public static Sequence getSequence() {
105                 return seq;
106         }
107
108         /** 
109          * Returns the current sequencer.
110          * @return the current sequencer
111          */
112         public static Sequencer getSequencer() {
113                 return sequencer;
114         }
115
116         /** 
117          * Returns the MidiChannels of the selected synthesizer.
118          * @return the available MidiChannels
119          */
120         public static MidiChannel[] getChannels() {
121                 return channels;
122         }
123
124         /** 
125          * Returns the MidiChannels of the selected synthesizer.
126          * @return the available MidiChannels
127          */
128         public static MidiChannel getChannel(int i) {
129                 return channels[i];
130         }
131
132         /** 
133          * Returns the currently active MidiChannel.
134          * @return the active MidiChannel
135          */
136         public static MidiChannel getActiveChannel() {
137                 return activeChannel;
138         }
139
140         /** 
141          * Sets the currently active MidiChannel.
142          * @param channel       the number of the MidiChannel to activate
143          */
144         public static void setActiveChannel(int channel) {
145                 activeChannel = channels[channel];
146         }
147
148         /** 
149          * Replaces the current sequence with a new one, holding three empty tracks.
150          */
151         public static void clearSequence() {
152                 // Creates a new sequence and sends it to the sequencer.
153                 try {
154                         seq = new Sequence(Sequence.PPQ, DEFAULT_RESOLUTION, DEFAULT_TRACKS);
155                         sequencer.setSequence(seq);
156                         filename = null;
157                         emptyTracks = new ArrayList();
158                 } catch (InvalidMidiDataException e) {}
159                 // Sends sequence to GUI.
160                 if (gui != null) gui.setSequence(seq);
161         }
162
163         /** 
164          * Starts playback of the current sequence.
165          */
166         public static void play() {
167                 sequencer.setTickPosition(editPosition);
168                 resume();
169         }
170
171         /** 
172          * Pauses playback of the current sequence.
173          */
174         public static void pause() {
175                 if (sequencer.isRunning()) {
176                         sequencer.stop();
177                 }
178                 if (player != null) player.interrupt();
179         }
180
181         /** 
182          * Resumes playback of the current sequence.
183          */
184         public static void resume() {
185                 gui.update(0);
186                 try {
187                         sequencer.setSequence(seq);
188                 } catch (InvalidMidiDataException e) {}
189                 sequencer.start();
190
191                 // Disables input to volatile components
192                 // gui.disable();
193
194                 // Creates the visualisation thread and starts it.
195                 player = new Thread () {
196                         public void run() {
197                                 while(sequencer.isRunning()) {
198                                         // Updates the GUI with the current tick position.
199                                         gui.update(sequencer.getTickPosition());
200
201                                         // Puts the thread to sleep for as long as it takes
202                                         // the sequencer to reach the next sixteenth.
203                                         try {
204                                                 //sleep((long)((15000 / getTempo()) * (tickDiff / ticksPerSixteenth)));
205                                                 sleep (10);
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          * Returns whether the given track should be drawn
312          * @return true if the given track should be drawn
313          */
314         public static boolean shouldBeDrawn(Track track) {
315                 if (drawEmptyTracks) return true;
316                 else return (!emptyTracks.contains(track));
317         }
318
319
320         /** 
321          * Sets whether empty tracks should be drawn
322          * @param state         true if empty tracks should be drawn
323          */
324         public static void setDrawEmptyTracks(boolean state) {
325                 drawEmptyTracks = state;
326         }
327
328         /** 
329          * Loads the MooSequence in the given file.
330          * @param filename      the filename to use
331          */
332         public static boolean load(String file) {
333                 // Loads sequence from file
334                 filename = file;
335                 try {
336                         seq = MidiSystem.getSequence(new File(filename));
337                 } catch (InvalidMidiDataException e) {
338                         return false;
339                 } catch (IOException e) {
340                         return false;
341                 }
342                 isEdited = false;
343
344                 Track[] tracks = seq.getTracks();
345                 emptyTracks = new ArrayList();
346
347                 // Searches track 0 for changes in tempo and time signature.
348                 MidiEvent event;
349                 MetaMessage metaMsg;
350                 ArrayList ts = new ArrayList(), tc = new ArrayList();
351                 for (int i = 0; i < tracks[0].size(); i++) {
352                         event = tracks[0].get(i);
353                         if (event.getMessage().getStatus() == MetaMessage.META) {
354                                 metaMsg = (MetaMessage)event.getMessage();
355                                 switch(metaMsg.getType()) {
356                                         case 81: tc.add(event); break;
357                                         case 88: ts.add(event);
358                                 }
359                         }
360                 }
361 //              timeSignatures = ts.toArray(timeSignatures);
362 //              tempoChanges = tc.toArray(tempoChanges);
363
364                 // Wraps each NoteOn event with its NoteOff event in a MooNote
365                 ArrayList noteOns, noteOffs;
366                 for (int i = 0; i < tracks.length; i++) {
367                         // Searches the sequence for NoteOn and NoteOff events
368                         noteOns = new ArrayList(tracks[i].size() / 2);
369                         noteOffs = new ArrayList(tracks[i].size() / 2);
370                         for (int j = 0; j < tracks[i].size(); j++) {
371                                 event = tracks[i].get(j);
372                                 if (event.getMessage().getStatus() >= 144 &&
373                                     event.getMessage().getStatus() < 160) noteOns.add(event);
374                                 if (event.getMessage().getStatus() >= 128 &&
375                                     event.getMessage().getStatus() < 144) noteOffs.add(event);
376                         }
377                         noteOns.trimToSize();
378                         noteOffs.trimToSize();
379                         if (noteOns.size() == 0) emptyTracks.add(tracks[i]);
380                         
381                         // Sorts the note lists by tick position.
382                         Comparator c = new Comparator() {
383                                 public int compare(Object o1, Object o2) {
384                                         return (int)(((MidiEvent)o1).getTick() - ((MidiEvent)o2).getTick());
385                                 }
386                         };
387                         Collections.sort(noteOns, c);
388                         Collections.sort(noteOffs, c);
389
390                         // Replaces each NoteOn event it with a MooNote containing a reference to the NoteOff event.
391                         Iterator iOn = noteOns.iterator(), iOff;
392                         MidiEvent on, off = null, nextOff;
393                         ShortMessage onMsg, nextOffMsg;
394                         while(iOn.hasNext()) {
395                                 on = (MidiEvent)iOn.next();
396                                 onMsg = (ShortMessage)on.getMessage();
397                                 iOff = noteOffs.iterator();
398                                 while(iOff.hasNext()) {
399                                         nextOff = (MidiEvent)iOff.next();
400                                         nextOffMsg = (ShortMessage)nextOff.getMessage();
401                                         if(onMsg.getChannel() == nextOffMsg.getChannel() &&
402                                            onMsg.getData1() == nextOffMsg.getData1() &&
403                                            c.compare(nextOff, on) > 0) {
404                                                 off = nextOff;
405                                                 iOff.remove();
406                                                 break;
407                                         }
408                                                 
409                                 }
410                                 tracks[i].remove(on);
411                                 if (off != null) {
412                                         tracks[i].add(new MooNote(on, off));
413                                 } else {
414                                         tracks[i].add(new MooNote(on));
415                                 }
416                                 iOn.remove();
417                         }
418                 }
419                 // Sends sequence to GUI and sequencer, then returns
420                 if (gui != null) gui.setSequence(seq);
421                 try {
422                         sequencer.setSequence(seq);
423                 } catch (InvalidMidiDataException e) {}
424                 return true;
425         }
426
427         /** 
428          * Saves the current sequence to the given filename
429          * @param file  the filename to use
430          */
431         public static boolean saveAs(String file) {
432                 try {
433                         MidiSystem.write(seq, 1, new File(file));
434                         filename = file;
435                         gui.setStatus("Saved " + file);
436                         return true;
437                 } catch (IOException e) {
438                         gui.setStatus("Failed in saving " + file);
439                         return false;
440                 }
441         }
442
443         /** 
444          * Saves the current sequence to the previously given filename.
445          */
446         public static boolean save() {
447                 if (filename == null) return false;
448                 else {
449                         saveAs(filename);
450                         return true;
451                 }
452         }
453
454         /** 
455          * Releases all reserved devices and exits the program.
456          */
457         public static void quit() {
458                 if (sequencer.isOpen()) sequencer.close();
459                 if (synthesizer.isOpen()) synthesizer.close();
460                 System.exit(0);
461         }
462 }