Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: jmqt/QTUtil.java


1   /*
2   
3   <This Java Class is part of the jMusic API version 1.4, February 2003.>
4   
5   Copyright (C) 2000 Andrew Sorensen & Andrew Brown
6   
7   This program is free software; you can redistribute it and/or modify
8   it under the terms of the GNU General Public License as published by
9   the Free Software Foundation; either version 2 of the License, or any
10  later version.
11  
12  This program is distributed in the hope that it will be useful, but
13  WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  GNU General Public License for more details.
16  
17  You should have received a copy of the GNU General Public License
18  along with this program; if not, write to the Free Software
19  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20  
21  */ 
22  
23  package jmqt;
24  
25  import java.awt.*;
26  import java.awt.event.*;
27  
28  import jm.JMC;
29  import jm.music.data.*;
30  
31  import quicktime.*;
32  import quicktime.app.*;
33  import quicktime.app.time.*;
34  import quicktime.io.*;
35  import quicktime.qd.*;
36  import quicktime.sound.*;
37  import quicktime.std.movies.*;
38  import quicktime.std.movies.media.*;
39  import quicktime.std.music.*;
40  import quicktime.std.*;
41  import quicktime.std.qtcomponents.*;
42  //new for OS X
43  //import com.apple.mrj.macos.carbon.CarbonLock;
44  
45  import quicktime.util.*;
46  
47  /**
48   * A class which plays a jMusic score via Apple's QuickTime.
49   * Requires the QTJava classes from Apple Computer inc.
50   * Based on example code and help from Bill Stewart (thanks Bill).
51   * @author Andrew Brown
52   */
53  
54  public class QTUtil implements JMC, StdQTConstants, SoundConstants {  
55    private TunePlayer tunePlayer1;
56    private MusicData tune1;
57    private boolean usingTuneOne = true;
58    private Score score;
59    // set up the number of channels required
60    private NoteChannel[] nc1 =  new NoteChannel[16];
61    private double speed = 1.0;
62    private ToneDescription td;
63          private double tempoMultiplier = 600.0;
64          
65          //-------------
66          //constructor
67          //-------------
68          
69          public QTUtil() { 
70              try {
71                  //CarbonLock.acquire(); // new for OS X
72                  QTSession.open();
73                  tunePlayer1 = new TunePlayer();
74                  // create note channels for all 16 MIDI channels
75                  setupNoteChannels();
76                  tunePlayer1.setNoteChannels(nc1);
77                  // create a tone desc for single note playing
78                  td = new ToneDescription(1);
79              } catch (QTException qte) { 
80                  qte.printStackTrace(); 
81              } finally {
82                  //CarbonLock.release(); // new for OS X
83              }
84          }
85          
86          public QTUtil(Score s) { 
87              try {
88                  QTSession.open();
89                  tunePlayer1 = new TunePlayer();
90                  // create note channels for all 16 MIDI channels
91                  this.setupNoteChannels();
92                  tunePlayer1.setNoteChannels(nc1);
93                  this.setScore(s);
94                  // create a tone desc for single note playing
95                  td = new ToneDescription(1);
96              } catch (QTException qte) { qte.printStackTrace(); }
97          }
98          
99    //-------------------
100   // Auxillary methods
101   //-------------------
102     
103         public void setScore(Score score) {
104                 this.score = score;
105                 try {
106                         usingTuneOne = !usingTuneOne;
107                         setUp();
108                 } catch (QTException qte) { qte.printStackTrace(); }
109                 
110                 }
111                 
112                 public Score getScore() {
113                 return score;
114         }
115         
116         private void setUp() throws QTException {
117                 //erase any empty parts or phrases
118                 if(this.score == null)
119                         System.err.println("jMusic QTtil error: The score is null. Please initialise before using.");
120                 this.score.clean();
121                 // check there is data to convert
122                 if (totalNotesInScore() < 1) {
123                         System.err.println(new Exception("jMusic EXCEPTION: QTUtil Error: The Score is empty!"));
124                                 (new Exception()).printStackTrace();
125                 }
126                 // set speed from tempo
127                 this.speed = score.getTempo() / 60.0;
128                 // setup QT stuff
129                 setGMInstruments();
130                 // Set the number of events (notes and rests) in the score
131                 // Add one for an end-of-sequence marker
132                 int totalNotes = totalNotesInScore();
133                 // get notes from score into a format ready for QT
134                 double[][] noteOrder = new double[totalNotes][4];
135                 noteOrder = sortNotes(score, totalNotes);
136                 // calcuate the size of the sequence
137                 int qt_sequence_length = ((totalNotes * 2 + 1) * 4); // * 4 for 8 bit values
138                 // this is the tune itself
139                 tune1 = convertFromScore(score, noteOrder, qt_sequence_length);
140         }      
141       
142     
143         private void setupNoteChannels() throws QTException {
144                 //System.out.println("QTU set up note channels");
145                 int theKit = 16385; //409;//"electric"
146                 //a channel with 4 voice polyphony
147                 for(int i=0; i<16; i++) {
148                         if (i != 9) {
149                                 nc1[i] = new NoteChannel(1, 6);
150                                 //System.out.println("set up cannel "+ i);
151                         } else {
152                                 nc1[9] = new NoteChannel(theKit, 8);
153                                 //System.out.println("set up cannel "+ i);
154                         }
155                 }
156         }
157         
158         
159         private void setGMInstruments() throws StdQTException {
160                 //System.out.println("Score size is " + score.size());
161                 int scoreSize = score.size();
162                 for(int i=0; i < scoreSize; i++) {
163                         Part currentPart = score.getPart(i);
164                         if ( currentPart.getInstrument() != 250) { // skip pg?
165                                 if (currentPart.getChannel() != 9) { // drum channel?
166                                         nc1[ currentPart.getChannel()].setInstrumentNumber(currentPart.getInstrument()+1);
167                                 } else {
168                                         nc1[9].setInstrumentNumber(currentPart.getInstrument() + 16385);
169                                 }
170                         }
171                 }
172         }
173   
174   
175         private int totalNotesInScore() {
176                 int totalNotes = 0;
177                 final int scoreSize = score.size();
178                 for(int i=0; i< scoreSize; i++) {
179                         Part currentPart = score.getPart(i);
180                         final int partSize = currentPart.size();
181                         for(int j=0; j<partSize ; j++) {
182                                 totalNotes += currentPart.getPhrase(j).size();
183                         }
184                 }
185                 return totalNotes;
186         }
187   
188   private double[][] sortNotes(Score score, int totalNotes) {
189       //System.out.println("QTU sort");
190       // put all notes into an array according to startTimes
191       double[][] noteOrder = new double[totalNotes][4];
192       int index = 0;
193       int scoreSize = score.size();
194       for(int i=0; i < scoreSize; i++) { // for each part
195         Part currentPart = score.getPart(i);
196         int partSize = currentPart.size();
197           for(int j=0; j < partSize; j++) { // for each phrase
198             Phrase currentPhrase = currentPart.getPhrase(j);
199               double st = currentPhrase.getStartTime();
200               for(int k = 0; k < currentPhrase.size(); k++) { // for each note
201                   if (k == 0) { // deal with offset on the first note
202                       st += currentPhrase.getNote(k).getOffset();
203                       if (st < 0.0) { // deal with a negative offset on the first note
204                           st = 0.0;
205                           Note tempNote = (Note)currentPhrase.getNote(k);
206                           tempNote.setRhythmValue( tempNote.getRhythmValue() + tempNote.getOffset());
207                       }
208                   }
209                   // insert the values for each note
210                   noteOrder[index][0] = st; // start time
211                   //System.out.println("Start time is being set to "+noteOrder[index][0]);
212                   noteOrder[index][1] = (double) i; // part in score
213                   noteOrder[index][2] = (double) j; // phrase in part
214                   noteOrder[index][3] = (double) k; // note in phrase
215                   st += currentPhrase.getNote(k).getRhythmValue();
216                   if (k > 0) {st = st - currentPhrase.getNote(k - 1).getOffset()
217                                   + currentPhrase.getNote(k).getOffset();
218                   }
219                   if (st < 0.0) st = 0.0;
220                   index++;
221               }
222           }
223       }
224     
225     quickSort(noteOrder, 0, noteOrder.length - 1);
226     
227     //double time2 = System.currentTimeMillis();
228     //System.out.println("Sort time is "+(time2-time1)/1000.0+" seconds");
229     return noteOrder;
230   }
231   
232         private void quickSort(double[][] noteOrder, int left, int right) {
233                 int i, last;
234                 if(left >= right) return; // already sorted
235                         swap(noteOrder, left, (int)(Math.random() * (right - left)) + left);
236                         // choose new pivot point
237                         last = left;
238                         for (i = left+1; i<=right; i++) {
239                         if(noteOrder[i][0] <= noteOrder[left][0]) swap(noteOrder, ++last, i);
240                 }
241                 swap(noteOrder, left, last); // restore pivot
242                 quickSort(noteOrder, left, last-1);
243                 quickSort(noteOrder, last+1, right);
244         }
245         
246         static void swap(double[][] noteOrder, final int i, final int j) {
247                 double temp;
248                 for(int a=0;a<4;a++){
249                         temp = noteOrder[i][a];
250                         noteOrder[i][a] = noteOrder[j][a];
251                         noteOrder[j][a] = temp;
252                 }
253         }
254           
255           
256   private MusicData convertFromScore(Score score, double[][] noteOrder, int qt_sequence_length) throws QTException {
257     //System.out.println("QTU convert from score");
258     MusicData tune = new MusicData (qt_sequence_length * 3);            
259     double currentTime = 0.0;
260     // duration in 600ths of a second -- @ 300 the tempo will be 120 bpm
261     double deltaTime = 0.0;
262     // event index
263     int eventIndex = 0;
264     // check for not starting at time 0.0
265     if ( noteOrder[0][0] > 0.0) {
266         // add a rest to pad it out until the start time
267         tune.setMusicEvent (eventIndex, MusicData.stuffRestEvent(
268         (int)(noteOrder[0][0] * tempoMultiplier)));
269         eventIndex++;
270         currentTime = noteOrder[0][0];
271     } 
272     // iterate through the notes converting them to QT music events
273       for(int index=0;index<noteOrder.length - 1 ;index++){ 
274          
275         Note n = score.getPart((int)noteOrder[index][1]).getPhrase(
276         (int)noteOrder[index][2]).getNote((int)noteOrder[index][3]);
277        deltaTime = noteOrder[index+1][0]  * tempoMultiplier - currentTime;
278        // store current pan position
279        double lastPan = 0.5;
280        // create events
281        if (n.getPitch() == REST) {
282            
283          tune.setMusicEvent ( eventIndex, MusicData.stuffRestEvent((int)(deltaTime)));
284             eventIndex++;
285       } else {
286             // each note requires a NoteEvent for its duration and 
287             // a RestEvent for the distance before the next note
288             // Add a pan event (controller 10) for each note
289         if (n.getPan() != lastPan) {
290           tune.setMusicEvent (eventIndex, MusicData.stuffControlEvent(
291                   score.getPart((int)noteOrder[index][1]).getChannel() + 1,
292                   10, (int)(n.getPan() * 256) + 256)); // pan
293           eventIndex++;
294           lastPan = n.getPan();
295         }            
296         
297         tune.setXMusicEvent (eventIndex, MusicData.stuffXNoteEvent(score.getPart(
298           (int)noteOrder[index][1]).getChannel()+1,
299           n.getPitch() * 256, n.getDynamic(), (int)(n.getDuration() * tempoMultiplier)));
300         eventIndex+=2;
301         // set timing between events
302             tune.setMusicEvent (eventIndex, MusicData.stuffRestEvent((int)(deltaTime)));
303             eventIndex++;
304        }
305        // the rhythm value time 
306        currentTime += deltaTime;
307      }
308      // deal with the last note
309        Note nLast = score.getPart((int)noteOrder[(noteOrder.length -1)][1]).
310       getPhrase((int)noteOrder[(noteOrder.length -1)][2]).
311       getNote((int)noteOrder[(noteOrder.length -1)][3]);
312        if (nLast.getPitch() == REST) {
313             tune.setMusicEvent ( eventIndex, MusicData.stuffRestEvent(
314         (int)(nLast.getRhythmValue()* tempoMultiplier)));
315             eventIndex++;
316        } else {
317          // Add a pan event (controller 10) for each note
318          tune.setMusicEvent (eventIndex, MusicData.stuffControlEvent(
319                                         score.getPart((int)noteOrder[noteOrder.length - 1][1]).getChannel() + 1,
320                                         10, (int)(nLast.getPan() * 256) + 256)); // pan
321          eventIndex++;
322             // each note requires a NoteEvent for its duration and 
323                         // a RestEvent for the distance before the next note
324          tune.setXMusicEvent (eventIndex, MusicData.stuffXNoteEvent(score.getPart(
325           (int)noteOrder[(noteOrder.length -1)][1]).getChannel()+1,
326           nLast.getPitch() * 256, nLast.getDynamic(), 
327                                         (int)(nLast.getDuration() * tempoMultiplier)));
328          eventIndex+=2;
329                 }
330     return tune;
331   }
332     
333   //--------------------------------------
334   public void playback(Score s){
335     //System.out.println("QTU playback");
336     setScore(s);
337     replay();
338   }
339   
340         /**
341         * Play the current score again.
342         */
343   public void replay() {
344     //System.out.println("QTU replay");
345                 try {
346       if(tunePlayer1 != null) {
347         tunePlayer1.queue (tune1, (float)speed, 0, 0x7FFFFFFF, 0);
348       } else {System.out.println("Woops! No score to play.");}
349                 } catch (QTException qte) { qte.printStackTrace(); }
350         }
351   
352         /**
353         * Halt the current score playabck.
354         */
355   public void stopPlayback() {
356     try {
357       if(tunePlayer1 != null) {
358                                 tunePlayer1.stop();
359       } 
360     } catch (QTException qte) { qte.printStackTrace(); }
361   }
362     
363   /**
364   * return the current speed, which changes with the score tempo
365   */
366   public double getSpeed() {
367     return this.speed;
368   }
369   
370   /**
371   * return the current speed, which changes with the score tempo.
372         * @param speed The tempo to play back with (1.0 = normal).
373   */
374   public void setSpeed(double speed) {
375       this.speed = speed;
376   }
377 
378   // ----------------------------------------
379   /**
380   *  Play a single note via QuickTime
381   * @param note The jMusic note to be played
382   * @param channel The MIDI channel on which to play the note
383   */
384   public void playOneNote(Note note, int channel) {
385   int tempPitch = note.getPitch();
386     try {
387       NoteChannel tempnc = nc1[channel];
388       tempnc.playNote(tempPitch, note.getDynamic()); // note on
389       try { 
390         Thread.sleep ((long)(note.getDuration() * 1000)); // * 1000 = 60 bpm
391       } catch (InterruptedException e) { 
392         System.out.println("jMusic QTUtil ERROR: Problem sleeping single note playback thread");
393         tempnc.playNote (tempPitch, 0); // note off
394       }
395       tempnc.playNote ( tempPitch, 0); // note off
396     } catch (QTException qte) {
397       qte.printStackTrace();
398     }
399   
400   }
401     
402   /**
403   * Send a MIDI control change message immediately
404   * Note: this method is at the mercy of QuickTime and may not work when QT is set to
405   * the internal QT musical instruments sound synthesizer. It does work when sending to
406   * external MIDI devices. QuickTime may also not support external MIDI devices on some 
407   * implementations, for example Mac OS X 10.0. In these cases a standard QT exception 
408   * will be printed to he command line.
409   * @param channel MIDI channel number from 0 - 15
410   * @param controllerNumber MIDI controller number from 1 - 127 .e.g, 7 = volume
411   * @param value data for the specified controller, between 0 - 127
412   */
413   public void sendMIDIControlChange(int channel, int controllerNumber, int value) {
414     int[] data = new int[2];
415     data[0] = controllerNumber;
416     data[1] = value;
417     sendMIDIMessage(176, channel, data);
418   }
419     
420   /**
421   * Send any MIDI channel message immediately
422   * @param staus The MIDI staus value. A number between 127 - 255.
423   * @param channel MIDI channel to send meesage on, 0 - 15.
424   * @param dataByteArray a list of the data values, usually 1 or 2 
425   * values depending on the message status type.
426   */
427   public void sendMIDIMessage(int status, int channel, int[] dataByteArray) {
428     //System.out.println("getting note channel");
429     NoteChannel tempnc = nc1[channel];
430     
431     byte[] midiMessage = new byte[dataByteArray.length + 1];
432     MusicMIDIPacket mmp = new MusicMIDIPacket(midiMessage);
433     mmp.setDataByte(0, status + channel); // 8 bits to a byte.. 
434     // first bit is status or data
435     // next three bits are the message type.  next four bits are channel
436     for(int i = 0; i < dataByteArray.length; i++) {
437       mmp.setDataByte(i + 1, dataByteArray[i]);
438     }
439     try {          
440       tempnc.sendMIDI(mmp);      
441     } catch (QTException qte) {
442       qte.printStackTrace();
443     }
444   }
445 
446     
447   /**
448   * Choose a general MIDI sound for playback of single notes
449   */
450   public void setNoteInstrument() {
451     try {
452       // Have the user choose an instrument and print out the choice
453       td.pickInstrument (NoteAllocator.getDefault(), "Choose an Instrument...", 0);
454       //System.out.println (td);
455     } catch (QTException e) {
456       e.printStackTrace();
457     }
458   }
459   
460   /*
461   * Help Java finish the quickTime session elegantly
462   */
463   public void finalize() {
464     QTSession.close();
465   }
466   
467 }