mac_midi.cc

Go to the documentation of this file.
00001 /*
00002  *  mac_midi.cc - QuickTime based midi player for MacOS.
00003  *
00004  *  Copyright (C) 2001  Max Horn
00005  *
00006  *  This program is free software; you can redistribute it and/or modify
00007  *  it under the terms of the GNU General Public License as published by
00008  *  the Free Software Foundation; either version 2 of the License, or
00009  *  (at your option) any later version.
00010  *
00011  *  This program is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  *  GNU General Public License for more details.
00015  *
00016  *  You should have received a copy of the GNU General Public License
00017  *  along with this program; if not, write to the Free Software
00018  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
00019  */
00020 
00021 #ifdef HAVE_CONFIG_H
00022 #  include <config.h>
00023 #endif
00024 
00025 //MacOS-specific code
00026 #if defined(MACOS) || defined(MACOSX)
00027 
00028 #include <iomanip>
00029 #include <cstdlib>
00030 #include <string>
00031 
00032 #include "mac_midi.h"
00033 
00034 #ifdef MACOSX
00035 #include <CoreServices/CoreServices.h>
00036 #include <QuickTime/QuickTimeMusic.h>
00037 #endif
00038 
00039 using std::cout;
00040 using std::endl;
00041 using std::malloc;
00042 using std::free;
00043 using std::realloc;
00044 using std::memset;
00045 
00046 enum
00047 {
00048   // number of (32-bit) long words in a note request event
00049   kNoteRequestEventLength = ((sizeof(NoteRequest)/sizeof(long)) + 2),
00050 
00051   // number of (32-bit) long words in a marker event
00052   kMarkerEventLength  = 1,
00053 
00054   // number of (32-bit) long words in a general event, minus its data
00055   kGeneralEventLength = 2
00056 };
00057 
00058 #define BUFFER_INCREMENT    5000
00059 
00060 #define REST_IF_NECESSARY() do {\
00061       int timeDiff = eventPos->time - lastEventTime;  \
00062       if(timeDiff)  \
00063       { \
00064         timeDiff = (int)(timeDiff*tick);  \
00065         qtma_StuffRestEvent(*tunePos, timeDiff);  \
00066         tunePos++;  \
00067         lastEventTime = eventPos->time; \
00068       } \
00069     } while(0)
00070 
00071 
00072 static uint32 *BuildTuneSequence(midi_event *evntlist, int ppqn, int part_poly_max[32], int part_to_inst[32], int &numParts);
00073 static uint32 *BuildTuneHeader(int part_poly_max[32], int part_to_inst[32], int numParts);
00074 
00075 
00076 Mac_QT_midi::Mac_QT_midi()
00077   : MidiAbstract(), mTunePlayer(0), mTuneSequence(0), mTuneHeader(0)
00078 {
00079 // FIX ME - check for QuickTime!!!
00080   
00081   mTunePlayer = OpenDefaultComponent(kTunePlayerComponentType, 0);
00082   if( 0 == mTunePlayer )
00083     throw exult_exception("Couldn't get tune place component");
00084 }
00085 
00086 Mac_QT_midi::~Mac_QT_midi(void)
00087 {
00088   if(mTunePlayer)
00089   {
00090     if(mTuneSequence)
00091       TuneStop(mTunePlayer, 0);
00092     CloseComponent(mTunePlayer);
00093     mTunePlayer = 0;
00094   }
00095   if(mTuneSequence)
00096   {
00097     free(mTuneSequence);
00098     mTuneSequence = 0;
00099   }
00100   if(mTuneHeader)
00101   {
00102     DisposePtr((Ptr)mTuneHeader);
00103     mTuneHeader = 0;
00104   }
00105 }
00106 
00107 
00108 void  Mac_QT_midi::start_track(XMIDIEventList *elist, bool repeat)
00109 {
00110   int     part_to_inst[32];
00111   int     part_poly_max[32];
00112   int     numParts = 0;
00113 
00114   // Max, this is just a work around that should allow things to compile
00115   // Note that now the ppqn is always 60, and the tempo is always 500000 mcs
00116   // This means each tick is now always 120th of a second
00117   int ppqn = 60;
00118   midi_event *evntlist = elist->events;
00119 
00120   UInt32    queueFlags = 0;
00121   ComponentResult tpError;
00122 
00123   memset(part_poly_max,0,sizeof(part_poly_max));
00124   memset(part_to_inst,-1,sizeof(part_to_inst));
00125 
00126   // First thing we do - stop any already playing music
00127   stop_track();
00128   
00129   // Build a tune sequence from the event list
00130   mTuneSequence = BuildTuneSequence(evntlist, ppqn, part_poly_max, part_to_inst, numParts);
00131   if(!mTuneSequence)
00132     goto bail;
00133 
00134   // Now build a tune header from the data we collect above, create
00135   // all parts as needed and assign them the correct instrument.
00136   mTuneHeader = BuildTuneHeader(part_poly_max, part_to_inst, numParts);
00137   if(!mTuneHeader)
00138     goto bail;
00139 
00140   // Set up the queue flags
00141   queueFlags = kTuneStartNow;
00142   if(repeat)
00143     queueFlags |= kTuneLoopUntil;
00144 
00145   // Set the time scale (units per second), we want milliseconds
00146   tpError = TuneSetTimeScale(mTunePlayer, 1000);
00147   assert (tpError == noErr);
00148 
00149   // Set the header, to tell what instruments are used
00150   tpError = TuneSetHeader(mTunePlayer, (UInt32 *)mTuneHeader);
00151   assert (tpError == noErr);
00152   
00153   // Have it allocate whatever resources are needed
00154   tpError = TunePreroll(mTunePlayer);
00155   assert (tpError == noErr);
00156 
00157   // We want to play at normal volume
00158   tpError = TuneSetVolume(mTunePlayer, 0x00010000);
00159   assert (tpError == noErr);
00160   
00161   // Finally, start playing the full song
00162   tpError = TuneQueue(mTunePlayer, (UInt32 *)mTuneSequence, 0x00010000, 0, 0xFFFFFFFF, queueFlags, NULL, 0);
00163   assert (tpError == noErr);
00164 
00165   return;
00166   
00167 bail:
00168 
00169   // This disposes of the allocated tune header/sequence
00170   stop_track();
00171 }
00172 
00173 void  Mac_QT_midi::stop_track(void)
00174 {
00175   if(0 == mTuneSequence)
00176         return;
00177 
00178 // For some resons, using a non-zero stopflag doesn't stop the music at all:/
00179 //  TuneStop(mTunePlayer, kTuneStopFade | kTuneStopInstant | kTuneStopReleaseChannels);
00180 
00181   // Stop music
00182   TuneStop(mTunePlayer, 0);
00183   
00184   // Deallocate all instruments
00185   TuneUnroll(mTunePlayer);
00186   
00187   // Finally, free the data storage
00188   free(mTuneSequence);
00189   mTuneSequence = 0;
00190 
00191   if(mTuneHeader)
00192   {
00193     DisposePtr((Ptr)mTuneHeader);
00194     mTuneHeader = 0;
00195   }
00196 }
00197 
00198 bool  Mac_QT_midi::is_playing(void)
00199 {
00200   TuneStatus  ts;
00201 
00202   TuneGetStatus(mTunePlayer,&ts);
00203   return ts.queueTime != 0;
00204 }
00205 
00206 const char *Mac_QT_midi::copyright(void)
00207 {
00208   return "Internal QuickTime MIDI player";
00209 }
00210 
00211 uint32 *BuildTuneSequence(midi_event *evntlist, int ppqn, int part_poly_max[32], int part_to_inst[32], int &numParts)
00212 {
00213   int     part_poly[32];
00214   int     channel_to_part[16];
00215   
00216   int     channel_pan[16];
00217   int     channel_vol[16];
00218   int     channel_pitch_bend[16];
00219   
00220   int     lastEventTime = 0;
00221   int     tempo = 500000;
00222   double    Ippqn = 1.0 / (1000*ppqn);
00223   double    tick = tempo * Ippqn;
00224   midi_event  *eventPos = evntlist;
00225   uint32    *tunePos, *endPos;
00226   uint32    *tuneSequence;
00227   size_t    tuneSize;
00228 #ifdef DEBUG
00229   int     numEventsHandled = 0;
00230 #endif
00231   
00232   // allocate space for the tune header
00233   tuneSize = 5000;
00234   tuneSequence = (uint32 *)malloc(tuneSize * sizeof(uint32));
00235   if (tuneSequence == NULL)
00236     return NULL;
00237   
00238   // Set starting position in our tune memory
00239   tunePos = tuneSequence;
00240   endPos = tuneSequence + tuneSize;
00241 
00242   // Initialise the arrays
00243   memset(part_poly,0,sizeof(part_poly));
00244   
00245   memset(channel_to_part,-1,sizeof(channel_to_part));
00246   memset(channel_pan,-1,sizeof(channel_pan));
00247   memset(channel_vol,-1,sizeof(channel_vol));
00248   memset(channel_pitch_bend,-1,sizeof(channel_pitch_bend));
00249 
00250   /*
00251    * Now the major work - iterate over all GM events,
00252    * and turn them into QuickTime Music format.
00253    * At the same time, calculate the max. polyphony for each part,
00254    * and also the part->instrument mapping.
00255    */
00256   while(eventPos)
00257   {
00258     int status = (eventPos->status&0xF0)>>4;
00259     int channel = eventPos->status&0x0F;
00260     int part = channel_to_part[channel];
00261         int velocity, pitch;
00262         int value, controller;
00263         int bend;
00264         int newInst;
00265     
00266     // Check if we are running low on space...
00267     if((tunePos+16) > endPos)
00268     {
00269       // Resize our data storage.
00270       uint32    *oldTuneSequence = tuneSequence;
00271 
00272       tuneSize += BUFFER_INCREMENT;
00273       tuneSequence = (uint32 *)realloc(tuneSequence, tuneSize * sizeof(uint32));
00274       if(oldTuneSequence != tuneSequence)
00275         tunePos += tuneSequence - oldTuneSequence;
00276       endPos = tuneSequence + tuneSize;
00277     }
00278     
00279 #if defined(DEBUG) && 0
00280     numEventsHandled++;
00281 
00282     cout << std::setw(4) << numEventsHandled << ". ";
00283     cout << "Time: " << std::setw(5) << eventPos->time << " ";
00284     cout << std::hex << std::uppercase;
00285     cout << "Status 0x" << status << " Channel: 0x" << channel << " ";
00286     cout << "Data 0x" << std::setw(4) <<*(unsigned short *)(eventPos->data) << " ";
00287     cout << std::dec;
00288     cout << "Length " << eventPos->len << endl;
00289 #endif
00290 
00291     switch (status)
00292     {
00293     case MIDI_STATUS_NOTE_OFF:
00294       assert(part>=0 && part<=31);
00295 
00296       // Keep track of the polyphony of the current part
00297       part_poly[part]--;
00298       break;
00299     case MIDI_STATUS_NOTE_ON:
00300       if (part < 0)
00301       {
00302         // If no part is specified yet, we default to the first instrument, which
00303         // is piano (or the first drum kit if we are on the drum channel)
00304         int newInst;
00305         
00306         if (channel == 9)
00307           newInst = kFirstDrumkit + 1;    // the first drum kit is the "no drum" kit!
00308         else
00309           newInst = kFirstGMInstrument;
00310         part = channel_to_part[channel] = numParts;
00311         part_to_inst[numParts++] = newInst;
00312       }
00313       // TODO - add support for more than 32 parts using eXtended QTMA events
00314       assert(part<=31);
00315       
00316       // Decode pitch & velocity
00317       pitch = eventPos->data[0];
00318       velocity = eventPos->data[1];
00319       
00320       if (velocity == 0)
00321       {
00322         // was a NOTE OFF in disguise, so we decrement the polyphony
00323         part_poly[part]--;
00324       }
00325       else
00326       {
00327         // Keep track of the polyphony of the current part
00328         int foo = ++part_poly[part];
00329         if (part_poly_max[part] < foo)
00330           part_poly_max[part] = foo;
00331 
00332         // Now scan forward to find the matching NOTE OFF event
00333         midi_event *noteOffPos;
00334         for(noteOffPos = eventPos; noteOffPos; noteOffPos = noteOffPos->next)
00335         {
00336           if ((noteOffPos->status&0xF0)>>4 == MIDI_STATUS_NOTE_OFF
00337             && channel == (eventPos->status&0x0F)
00338             && pitch == noteOffPos->data[0])
00339             break;
00340           // NOTE ON with velocity == 0 is the same as a NOTE OFF
00341           if ((noteOffPos->status&0xF0)>>4 == MIDI_STATUS_NOTE_ON
00342             && channel == (eventPos->status&0x0F)
00343             && pitch == noteOffPos->data[0]
00344             && 0 == noteOffPos->data[1])
00345             break;
00346         }
00347         
00348         // Did we find a note off? Should always be the case, but who knows...
00349         if (noteOffPos)
00350         {
00351           // We found a NOTE OFF, now calculate the note duration
00352           int duration = (int)((noteOffPos->time - eventPos->time)*tick);
00353           
00354           REST_IF_NECESSARY();
00355           // Now we need to check if we get along with a normal Note Even, or if we need an extended one...
00356           if (duration < 2048 && pitch>=32 && pitch<=95 && velocity>=0 && velocity<=127)
00357           {
00358             qtma_StuffNoteEvent(*tunePos, part, pitch, velocity, duration);
00359             tunePos++;
00360           }
00361           else
00362           {
00363             qtma_StuffXNoteEvent(*tunePos, *(tunePos+1), part, pitch, velocity, duration);
00364             tunePos+=2;
00365           }
00366           
00367 #if defined(DEBUG) && 0
00368           cout << std::setw(4) << numEventsHandled << ". ";
00369           cout << "NOTE ON duration " << std::setw(4) << duration << " ";
00370           cout << "Channel " << std::setw(2) << channel << " ";
00371           cout << "Part " << std::setw(2) << part << " ";
00372           cout << "Pitch " << std::setw(2) << pitch << " ";
00373           cout << "Velocity " << std::setw(2) << velocity << endl;
00374 #endif
00375         }
00376       }
00377       break;
00378     case MIDI_STATUS_AFTERTOUCH:
00379       COUT("MIDI_STATUS_AFTERTOUCH");
00380       break;
00381     case MIDI_STATUS_CONTROLLER:
00382       controller = eventPos->data[0];
00383       value = eventPos->data[1];
00384 
00385       switch(controller)
00386       {
00387       case 0: // bank change - igore for now
00388         break;
00389       case kControllerVolume:
00390         if(channel_vol[channel] != value<<8)
00391         {
00392           channel_vol[channel] = value<<8;
00393           if(part>=0 && part<=31)
00394           {
00395             REST_IF_NECESSARY();
00396             qtma_StuffControlEvent(*tunePos, part, kControllerVolume, channel_vol[channel]);
00397             tunePos++;
00398           }
00399         }
00400         break;
00401       case kControllerPan:
00402         if(channel_pan[channel] != ((value << 1) + 256))
00403         {
00404           channel_pan[channel] = ((value << 1) + 256);
00405           if(part>=0 && part<=31)
00406           {
00407             REST_IF_NECESSARY();
00408             qtma_StuffControlEvent(*tunePos, part, kControllerPan, channel_pan[channel]);
00409             tunePos++;
00410           }
00411         }
00412         break;
00413       default:
00414         COUT("CONTROLLER not handled: "<< controller);
00415         break;
00416       }
00417       
00418       break;
00419     case MIDI_STATUS_PROG_CHANGE:
00420       // Instrument changed
00421       newInst = eventPos->data[0];
00422       // Channel 9 (the 10th channel) is different, it indicates a drum kit
00423       if (channel == 9)
00424         newInst += kFirstDrumkit;
00425       else
00426         newInst += kFirstGMInstrument;
00427       // Only if the instrument for this channel *really* changed, add a new part.
00428       if(newInst != part_to_inst[part])
00429       {
00430         // TODO maybe make use of kGeneralEventPartChange here,
00431         // to help QT reuse note channels?
00432         part = channel_to_part[channel] = numParts;
00433         part_to_inst[numParts++] = newInst;
00434 
00435         if(channel_vol[channel] >= 0)
00436         {
00437           REST_IF_NECESSARY();
00438           qtma_StuffControlEvent(*tunePos, part, kControllerVolume, channel_vol[channel]);
00439           tunePos++;
00440         }
00441         if(channel_pan[channel] >= 0)
00442         {
00443           REST_IF_NECESSARY();
00444           qtma_StuffControlEvent(*tunePos, part, kControllerPan, channel_pan[channel]);
00445           tunePos++;
00446         }
00447         if(channel_pitch_bend[channel] >= 0)
00448         {
00449           REST_IF_NECESSARY();
00450           qtma_StuffControlEvent(*tunePos, part, kControllerPitchBend, channel_pitch_bend[channel]);
00451           tunePos++;
00452         }     
00453       }
00454       break;
00455     case MIDI_STATUS_PRESSURE:
00456       COUT("MIDI_STATUS_PRESSURE");
00457       break;
00458     case MIDI_STATUS_PITCH_WHEEL:
00459       // In the midi spec, 0x2000 = center, 0x0000 = - 2 semitones, 0x3FFF = +2 semitones
00460       // but for QTMA, we specify it as a 8.8 fixed point of semitones
00461       // TODO: detect "pitch bend range changes"
00462       bend = eventPos->data[0] & 0x7f | (eventPos->data[1] & 0x7f) << 7;
00463       
00464       // "Center" the bend
00465       bend -= 0x2000;
00466       
00467       // Move it to our format:
00468       bend <<= 4;
00469       
00470       // If it turns out the pitch bend didn't change, stop here
00471       if(channel_pitch_bend[channel] == bend)
00472         break;
00473       
00474       channel_pitch_bend[channel] = bend;
00475       if(part>=0 && part<=31)
00476       {
00477         // Stuff a control event
00478         REST_IF_NECESSARY();
00479         qtma_StuffControlEvent(*tunePos, part, kControllerPitchBend, bend);
00480         tunePos++;
00481       }     
00482       break;
00483     case MIDI_STATUS_SYSEX:
00484       if (eventPos->status == 0xFF && eventPos->data[0] == 0x51) // Tempo change
00485       {
00486         tempo = (eventPos->buffer[0] << 16) +
00487           (eventPos->buffer[1] << 8) +
00488           eventPos->buffer[2];
00489         
00490         tick = tempo * Ippqn;
00491       }
00492       break;
00493     }
00494     
00495     // on to the next event
00496     eventPos = eventPos->next;
00497   } 
00498   
00499   // Finally, place an end marker
00500   *tunePos = kEndMarkerValue;
00501   
00502   return tuneSequence;
00503 }
00504 
00505 
00506 uint32 *BuildTuneHeader(int part_poly_max[32], int part_to_inst[32], int numParts)
00507 {
00508   uint32      *myHeader;
00509   uint32      *myPos1, *myPos2;   // pointers to the head and tail long words of a music event
00510   NoteRequest   *myNoteRequest;
00511   NoteAllocator myNoteAllocator;    // for the NAStuffToneDescription call
00512   ComponentResult myErr = noErr;
00513 
00514   myHeader = NULL;
00515   myNoteAllocator = NULL;
00516 
00517   /*
00518    * Open up the Note Allocator
00519    */
00520   myNoteAllocator = OpenDefaultComponent(kNoteAllocatorComponentType,0);
00521   if (myNoteAllocator == NULL)
00522     goto bail;
00523   
00524   /*
00525    * Allocate space for the tune header
00526    */
00527   myHeader = (uint32 *)
00528       NewPtrClear((numParts * kNoteRequestEventLength + kMarkerEventLength) * sizeof(uint32));
00529   if (myHeader == NULL)
00530     goto bail;
00531   
00532   myPos1 = myHeader;
00533   
00534   /*
00535    * Loop over all parts
00536    */
00537   for(int part = 0; part < numParts; ++part)
00538   {
00539     /*
00540      * Stuff request for the instrument with the given polyphony
00541      */
00542     myPos2 = myPos1 + (kNoteRequestEventLength - 1); // last longword of general event
00543     qtma_StuffGeneralEvent(*myPos1, *myPos2, part, kGeneralEventNoteRequest, kNoteRequestEventLength);
00544     myNoteRequest = (NoteRequest *)(myPos1 + 1);
00545     myNoteRequest->info.flags = 0;
00546 #if defined(MACOS) || defined(MACOSX)
00547     myNoteRequest->info.polyphony.bigEndianValue = EndianS32_LtoB(part_poly_max[part]);
00548     myNoteRequest->info.typicalPolyphony.bigEndianValue = EndianS32_LtoB(0x00010000);
00549 #else
00550     myNoteRequest->info.polyphony = part_poly_max[part];
00551     myNoteRequest->info.typicalPolyphony = 0x00010000;
00552 #endif
00553     myErr = NAStuffToneDescription(myNoteAllocator,part_to_inst[part],&myNoteRequest->tone);
00554     if (myErr != noErr)
00555       goto bail;
00556     
00557     // move pointer to beginning of next event
00558     myPos1 += kNoteRequestEventLength;
00559   }
00560 
00561   *myPos1 = kEndMarkerValue;    /* end of sequence marker */
00562 
00563 
00564 bail:
00565   if(myNoteAllocator)
00566     CloseComponent(myNoteAllocator);
00567 
00568   // if we encountered an error, dispose of the storage we allocated and return NULL
00569   if (myErr != noErr) {
00570     DisposePtr((Ptr)myHeader);
00571     myHeader = NULL;
00572   }
00573 
00574   return myHeader;
00575 }
00576 
00577 #endif

Generated on Mon Jul 9 14:42:41 2007 for ExultEngine by  doxygen 1.5.1