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