]> ruin.nu Git - moosique.git/blob - Moosique.java
*** empty log 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
23         private static String filename, fileArg;
24         private static long editPosition;
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(editPosition);
164                 resume();
165         }
166
167         /** 
168          * Pauses playback of the current sequence.
169          */
170         public static void pause() {
171                 if (sequencer.isRunning()) {
172                         sequencer.stop();
173                 }
174                 if (player != null) player.interrupt();
175         }
176
177         /** 
178          * Resumes playback of the current sequence.
179          */
180         public static void resume() {
181                 gui.update(0);
182                 sequencer.start();
183
184                 // Disables input to volatile components
185                 // gui.disable();
186
187                 // Creates the visualization thread and starts it.
188                 player = new Thread () {
189                         public void run() {
190                                 while(sequencer.isRunning()) gui.update(sequencer.getTickPosition());
191                                 Moosique.stop();
192                         }
193                 };
194                 player.start();
195         }
196
197         /** 
198          * Stops playback of the current sequence.
199          */
200         public static void stop() {
201                 if (sequencer.isRunning()) {
202                         sequencer.stop();
203                 }
204                 sequencer.setTickPosition(editPosition);
205                 if (player != null) player.interrupt();
206                 gui.update((long)0);
207         }
208
209         /** 
210          * Returns the current editing position of the sequencer.
211          * @return the tick position
212          */
213         public static long getPosition() {
214                 return editPosition;
215         }
216
217         /** 
218          * Sets the current editing position of the sequencer.
219          * @param ticks         the tick position
220          */
221         public static void setPosition(long ticks) {
222                 editPosition = ticks;
223         }
224
225         /** 
226          * Returns true if the current sequence has been edited.
227          * @return the tick position
228          */
229         public static boolean isEdited() {
230                 return isEdited;
231         }
232
233         /** 
234          * Sets the current sequence as edited, which implies prompts when loading a new sequence.
235          */
236         public static void setEdited() {
237                 isEdited = true;
238         }
239
240         /** 
241          * Rewinds the current sequence the given number of measures.
242          * @param measures      the number of measures to rewind
243          */
244         public static void rewind(long ticks) {
245                 editPosition -= ticks;
246         }
247
248         /** 
249          * Fast forwards the current sequence the given number of measures.
250          * @param measures      the number of measures to fast forward
251          */
252         public static void forward(long ticks) {
253                 editPosition += ticks;
254         }
255
256         /** 
257          * Loads the MooSequence in the given file.
258          * @param filename      the filename to use
259          */
260         public static boolean load(String file) {
261                 // Loads sequence from file
262                 filename = file;
263                 try {
264                         seq = MidiSystem.getSequence(new File(filename));
265                 } catch (InvalidMidiDataException e) {
266                         return false;
267                 } catch (IOException e) {
268                         return false;
269                 }
270                 isEdited = false;
271                 
272                 // Searches the sequence for NoteOn events
273                 Track[] tracks = seq.getTracks();
274                 MidiEvent noteOn, noteOff = null, nextEvent;
275                 MidiMessage nextMsg;
276                 ShortMessage shortMsg;
277                 ArrayList noteOns, noteOffs;
278                 for (int i = 0; i < tracks.length; i++) {
279                          noteOns = new ArrayList(tracks.length);
280         /*
281                         Collections.sort(track[i].events, new Comparator() {
282                                 public int compare(Object o1, Object o2) {
283                                         return ((MidiEvent)o2).getTick() - ((MidiEvent)o1).getTick();
284                                 }
285                         });
286         */
287                         for (int j = 0; j < tracks[i].size(); j++) {
288                                 noteOn = tracks[i].get(j);
289                                 if (noteOn.getMessage().getStatus() == ShortMessage.NOTE_ON) {
290                                         // Finds the corresponding NoteOff event
291                                         for (int k = j + 1; k < tracks[i].size(); k++) {
292                                                 nextEvent = tracks[i].get(k);
293                                                 nextMsg = nextEvent.getMessage();
294                                                 if (nextMsg instanceof ShortMessage) {
295                                                         shortMsg = (ShortMessage) nextMsg;
296                                                         if (shortMsg.getCommand() == ShortMessage.NOTE_OFF && shortMsg.getChannel() == ((ShortMessage)noteOn.getMessage()).getChannel() && shortMsg.getData1() == ((ShortMessage)noteOn.getMessage()).getData1()) {
297                                                                 noteOff = nextEvent;
298                                                                 break;
299                                                         }
300                                                 }
301                                         }
302                                         // Replaces the NoteOn event with a MooNote, if possible with the corresponding NoteOff event
303                                         tracks[i].remove(noteOn);
304                                         if (noteOff != null) {
305                                                 tracks[i].add(new MooNote(noteOn, noteOff));
306                                         } else {
307                                                 tracks[i].add(new MooNote(noteOn));
308                                         }
309                                 }
310                         }
311                 }
312                 // Sends sequence to GUI and sequencer, then returns
313                 if (gui != null) gui.setSequence(seq);
314                 try {
315                         sequencer.setSequence(seq);
316                 } catch (InvalidMidiDataException e) {}
317                 return true;
318         }
319
320         /** 
321          * Saves the current sequence to the given filename
322          * @param file  the filename to use
323          */
324         public static void saveAs(String file) {
325                 try {
326                         MidiSystem.write(seq, 1, new File(filename));
327                 } catch (IOException e) {}
328                 filename = file;
329         }
330
331         /** 
332          * Saves the current sequence to the previously given filename.
333          */
334         public static void save() {
335                 saveAs(filename);
336         }
337
338         /** 
339          * Releases all reserved devices and exits the program.
340          */
341         public static void quit() {
342                 if (sequencer.isOpen()) sequencer.close();
343                 if (synthesizer.isOpen()) synthesizer.close();
344                 System.exit(0);
345         }
346 }