]> 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 MidiChannel[] channels;
21         private static MidiChannel activeChannel;
22         private static MidiEvent[] timeSignatures, tempoChanges;
23         private static ArrayList emptyTracks;
24
25         private static String filename, fileArg;
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                 for (int i = 0; i < args.length; i++) {
39                         if (args[i].equals("-n")) {makeGUI = false;}
40                         else if (fileArg == null) {fileArg = args[i];}
41                 }
42
43                 // Acquires MIDI devices and connects them.
44                 System.out.print("Initializing MIDI devices.");
45                 try {
46                         sequencer = MidiSystem.getSequencer();
47                         System.out.print(".");
48                         sequencer.open();
49                         synthesizer = MidiSystem.getSynthesizer();
50                         System.out.print(".");
51                         synthesizer.open();
52                         sequencer.getTransmitter().setReceiver(synthesizer.getReceiver());
53                         channels = synthesizer.getChannels();
54                         setActiveChannel(0);
55                 } catch (MidiUnavailableException e) {
56                         System.out.println("Failed, quitting.");
57 //                      System.exit(1);
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");
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                         emptyTracks = new ArrayList();
157                 } catch (InvalidMidiDataException e) {}
158                 // Sends sequence to GUI.
159                 if (gui != null) gui.setSequence(seq);
160         }
161
162         /** 
163          * Starts playback of the current sequence.
164          */
165         public static void play() {
166                 sequencer.setTickPosition(editPosition);
167                 resume();
168         }
169
170         /** 
171          * Pauses playback of the current sequence.
172          */
173         public static void pause() {
174                 if (sequencer.isRunning()) {
175                         sequencer.stop();
176                 }
177                 if (player != null) player.interrupt();
178         }
179
180         /** 
181          * Resumes playback of the current sequence.
182          */
183         public static void resume() {
184                 gui.update(0);
185                 sequencer.start();
186
187                 // Disables input to volatile components
188                 // gui.disable();
189
190                 // Creates the visualisation thread and starts it.
191                 player = new Thread () {
192                         public void run() {
193                                 long ticksPerSixteenth = seq.getResolution()/4;
194                                 System.out.println("Ticks/16: " + ticksPerSixteenth);
195                                 long position = sequencer.getTickPosition();
196                                 while(sequencer.isRunning()) {
197                                         long pos = sequencer.getTickPosition();
198                                         long tickDiff = pos - position;
199                                         System.out.print(" ... " + tickDiff);
200                                         position = pos;
201
202                                         // Updates the GUI with the current tick position.
203                                         gui.update(sequencer.getTickPosition());
204
205                                         // Puts the thread to sleep for as long as it takes
206                                         // the sequencer to reach the next sixteenth.
207                                         try {
208                                                 //sleep((long)((15000 / getTempo()) * (tickDiff / ticksPerSixteenth)));
209                                                 sleep (10);
210                                         } catch (InterruptedException e) {
211                                                 Moosique.stop();
212                                         }
213                                 }
214                                 Moosique.stop();
215                         }
216                 };
217                 player.start();
218         }
219
220         /** 
221          * Stops playback of the current sequence.
222          */
223         public static void stop() {
224                 if (sequencer.isRunning()) {
225                         sequencer.stop();
226                 }
227                 sequencer.setTickPosition(editPosition);
228                 if (player != null) player.interrupt();
229                 gui.update((long)0);
230         }
231
232         /** 
233          * Returns the current editing position of the sequencer.
234          * @return the tick position
235          */
236         public static long getEditPosition() {
237                 return editPosition;
238         }
239
240         /** 
241          * Sets the current editing position of the sequencer.
242          * @param ticks         the tick position
243          */
244         public static void setEditPosition(long ticks) {
245                 editPosition = ticks;
246         }
247
248         /** 
249          * Returns the tempo of the current sequence.
250          * @return the tick position
251          */
252         public static int getTempo() {
253                 return 120;
254                 // if (tempoMsg == null) return 0;
255         }
256
257         /** 
258          * Sets the current editing position of the sequencer.
259          * @param ticks         the tick position
260          */
261         public static void setTempo(int bpm) {
262                 // tempoMsg
263         }
264
265         /** 
266          * Returns the tempo of the current sequence.
267          * @return the tick position
268          */
269         public static int[] getTimeSig() {
270                 int[] ts = {4, 4};
271                 return ts;
272                 // if (timeSigMsg == null) return 0;
273         }
274
275         /** 
276          * Sets the current editing position of the sequencer.
277          * @param ticks         the tick position
278          */
279         public static void setTimeSig(int bpm) {
280                 // timeSigMsg
281         }
282
283         /** 
284          * Returns true if the current sequence has been edited.
285          * @return the tick position
286          */
287         public static boolean isEdited() {
288                 return isEdited;
289         }
290
291         /** 
292          * Sets the current sequence as edited, which implies prompts when loading a new sequence.
293          */
294         public static void setEdited() {
295                 isEdited = true;
296         }
297
298         /** 
299          * Rewinds the current sequence the given number of measures.
300          * @param measures      the number of measures to rewind
301          */
302         public static void rewind(long ticks) {
303                 editPosition -= ticks;
304         }
305
306         /** 
307          * Fast forwards the current sequence the given number of measures.
308          * @param measures      the number of measures to fast forward
309          */
310         public static void forward(long ticks) {
311                 editPosition += ticks;
312         }
313
314         /** 
315          * Returns whether the given track should be drawn
316          * @return true if the given track should be drawn
317          */
318         public static boolean shouldBeDrawn(Track track) {
319                 if (drawEmptyTracks) return true;
320                 else return (!emptyTracks.contains(track));
321         }
322
323
324         /** 
325          * Sets whether empty tracks should be drawn
326          * @param state         true if empty tracks should be drawn
327          */
328         public static void setDrawEmptyTracks(boolean state) {
329                 drawEmptyTracks = state;
330         }
331
332         /** 
333          * Loads the MooSequence in the given file.
334          * @param filename      the filename to use
335          */
336         public static boolean load(String file) {
337                 // Loads sequence from file
338                 filename = file;
339                 try {
340                         seq = MidiSystem.getSequence(new File(filename));
341                 } catch (InvalidMidiDataException e) {
342                         return false;
343                 } catch (IOException e) {
344                         return false;
345                 }
346                 isEdited = false;
347
348                 Track[] tracks = seq.getTracks();
349                 emptyTracks = new ArrayList();
350
351                 // Searches track 0 for changes in tempo and time signature.
352                 MidiEvent event;
353                 MetaMessage metaMsg;
354                 ArrayList ts = new ArrayList(), tc = new ArrayList();
355                 for (int i = 0; i < tracks[0].size(); i++) {
356                         event = tracks[0].get(i);
357                         if (event.getMessage().getStatus() == MetaMessage.META) {
358                                 metaMsg = (MetaMessage)event.getMessage();
359                                 switch(metaMsg.getType()) {
360                                         case 81: tc.add(event); break;
361                                         case 88: ts.add(event);
362                                 }
363                         }
364                 }
365 //              timeSignatures = ts.toArray(timeSignatures);
366 //              tempoChanges = tc.toArray(tempoChanges);
367
368                 // Wraps each NoteOn event with its NoteOff event in a MooNote
369                 ArrayList noteOns, noteOffs;
370                 for (int i = 0; i < tracks.length; i++) {
371                         // Searches the sequence for NoteOn and NoteOff events
372                         noteOns = new ArrayList(tracks[i].size() / 2);
373                         noteOffs = new ArrayList(tracks[i].size() / 2);
374                         for (int j = 0; j < tracks[i].size(); j++) {
375                                 event = tracks[i].get(j);
376                                 if (event.getMessage().getStatus() >= 144 &&
377                                     event.getMessage().getStatus() < 160) noteOns.add(event);
378                                 if (event.getMessage().getStatus() >= 128 &&
379                                     event.getMessage().getStatus() < 144) noteOffs.add(event);
380                         }
381                         noteOns.trimToSize();
382                         noteOffs.trimToSize();
383                         if (noteOns.size() == 0) emptyTracks.add(tracks[i]);
384                         
385                         // Sorts the note lists by tick position.
386                         Comparator c = new Comparator() {
387                                 public int compare(Object o1, Object o2) {
388                                         return (int)(((MidiEvent)o1).getTick() - ((MidiEvent)o2).getTick());
389                                 }
390                         };
391                         Collections.sort(noteOns, c);
392                         Collections.sort(noteOffs, c);
393
394                         // For each NoteOn event, finds its NoteOff event and replaces it with a MooNote.
395                         Iterator iOn = noteOns.iterator(), iOff;
396                         MidiEvent on, off = null, nextOff;
397                         ShortMessage onMsg, nextOffMsg;
398                         while(iOn.hasNext()) {
399                                 on = (MidiEvent)iOn.next();
400                                 onMsg = (ShortMessage)on.getMessage();
401                                 iOff = noteOffs.iterator();
402                                 while(iOff.hasNext()) {
403                                         nextOff = (MidiEvent)iOff.next();
404                                         nextOffMsg = (ShortMessage)nextOff.getMessage();
405                                         if(onMsg.getChannel() == nextOffMsg.getChannel() &&
406                                            onMsg.getData1() == nextOffMsg.getData1() &&
407                                            c.compare(nextOff, on) > 0) {
408                                                 off = nextOff;
409                                                 iOff.remove();
410                                                 break;
411                                         }
412                                                 
413                                 }
414                                 tracks[i].remove(on);
415                                 if (off != null) {
416                                         tracks[i].add(new MooNote(on, off));
417                                 } else {
418                                         tracks[i].add(new MooNote(on));
419                                 }
420                                 iOn.remove();
421                         }
422                 }
423
424                 // Sends sequence to GUI and sequencer, then returns
425                 if (gui != null) gui.setSequence(seq);
426                 try {
427                         sequencer.setSequence(seq);
428                 } catch (InvalidMidiDataException e) {}
429                 return true;
430         }
431
432         /** 
433          * Saves the current sequence to the given filename
434          * @param file  the filename to use
435          */
436         public static void saveAs(String file) {
437                 try {
438                         MidiSystem.write(seq, 1, new File(filename));
439                 } catch (IOException e) {}
440                 filename = file;
441                 gui.setStatus("Saved " + file);
442         }
443
444         /** 
445          * Saves the current sequence to the previously given filename.
446          */
447         public static void save() {
448                 saveAs(filename);
449         }
450
451         /** 
452          * Releases all reserved devices and exits the program.
453          */
454         public static void quit() {
455                 if (sequencer.isOpen()) sequencer.close();
456                 if (synthesizer.isOpen()) synthesizer.close();
457                 System.exit(0);
458         }
459 }