midi_file.cpp

Go to the documentation of this file.
00001 /*
00002  * midi_file.cpp
00003  *
00004  * Copyright (c) 2003 Thomas A. Vaughan
00005  * All rights reserved.
00006  *
00007  * Implements the midi file object.
00008  */
00009 
00010 // includes --------------------------------------------------------------------
00011 #include "midi_file.h"  // always include our own header first
00012 
00013 #ifdef WIN32
00014 #include <winsock2.h>
00015 #else   // WIN32
00016 #include <netinet/in.h> // host <--> network byte order translations
00017 #endif  // WIN32
00018 
00019 
00020 // namespace
00021 namespace media {
00022 
00023 
00024 
00025 // typedefs and statics --------------------------------------------------------
00026 
00027 static const int s_MaxTracks            = 16;   // maximum # of midi tracks
00028 static const long s_usec_per_minute     = 60 * 1000 * 1000;
00029         
00030         
00031 ////////////////////////////////////////////////////////////////////////////////
00032 //
00033 //      static helper methods
00034 //
00035 ////////////////////////////////////////////////////////////////////////////////
00036 
00037 void
00038 WriteByte
00039 (
00040 IN FILE * file,
00041 IN byte_t byte
00042 )
00043 throw()
00044 {
00045         ASSERT(file, "Invalid file");
00046         ASSERT(1 == fwrite(&byte, sizeof(byte_t), 1, file),
00047             "Failed to write byte to midi file");
00048 }
00049 
00050 
00051 
00052 void
00053 WriteWord
00054 (
00055 IN FILE * file,
00056 IN word_t word
00057 )
00058 throw()
00059 {
00060         word_t big_endian;
00061 
00062         ASSERT(file, "Invalid file");
00063 
00064         // convert to big-endian
00065         big_endian = htons(word);
00066 
00067         // write
00068         ASSERT(1 == fwrite(&big_endian, sizeof(word_t), 1, file),
00069             "Failed to write word to midi file");
00070 }
00071 
00072 
00073 
00074 void
00075 WriteDword
00076 (
00077 IN FILE * file,
00078 IN dword_t dword
00079 )
00080 throw()
00081 {
00082         dword_t big_endian;
00083 
00084         ASSERT(file, "Invalid file");
00085 
00086         // convert to big-endian
00087         big_endian = htonl(dword);
00088 
00089         // write
00090         ASSERT(4 == sizeof(dword_t), "Bad size???");
00091         ASSERT(1 == fwrite(&big_endian, sizeof(dword_t), 1, file),
00092             "Failed to write dword to midi file");
00093 }
00094 
00095 
00096 
00097 int
00098 WriteVariable
00099 (
00100 IN FILE * file,
00101 IN dword_t value
00102 )
00103 throw()
00104 {
00105         byte_t out[4];
00106         int count, retval;
00107 
00108         ASSERT(file, "NULL file");
00109         // ASSERT(written) -- can be null
00110 
00111         // keep going until value is zero
00112         count = 0;
00113         for (;;) {
00114                 out[count] = (value & 0x7f);
00115                 if (count)
00116                         out[count] |= 0x80;
00117                 count++;
00118                 value >>= 7;
00119                 if (!value)
00120                         break;
00121         }
00122 
00123         // tell caller the byte count written if they want to know
00124         retval = count;
00125 
00126         // now keep writing the packed bytes
00127         while (count > 0) {
00128                 count--;
00129                 WriteByte(file, out[count]);
00130                 // DPRINTF("  packed variable byte: %x", out[count]);
00131         }
00132 
00133         return retval;
00134 }
00135 
00136 
00137 
00138 void
00139 WriteChunkHeader
00140 (
00141 IN FILE * file,
00142 IN const char * name,
00143 IN dword_t length
00144 )
00145 throw()
00146 {
00147         ASSERT(file, "invalid file");
00148         ASSERT(name, "Null name");
00149         ASSERT(4 == strlen(name), "Bad name length");
00150 
00151         // write chunk name
00152         ASSERT(4 == fwrite(name, sizeof(char), 4, file),
00153             "Failed to write chunk name to midi file");
00154 
00155         // write chunk length
00156         WriteDword(file, length);
00157 }
00158 
00159 
00160 
00161 /*
00162  * class: MidiFileImpl
00163  *
00164  * Class that implements the MidiFile interface.
00165  */
00166 class MidiFileImpl : public MidiFile {
00167 public:
00168         // constructor, destructor ---------------------------------------------
00169         MidiFileImpl(void) throw();
00170         ~MidiFileImpl(void) throw();
00171 
00172         // public class methods ------------------------------------------------
00173         void initialize() throw();
00174         void readFile(IN const char * filename);
00175 
00176         // media::MidiFile class interface methods -----------------------------
00177         virtual const MidiEventMap& getEvents(void) const throw();
00178         void setPpqn(IN int ppqn) throw();
00179         void setTempo(IN float bpm) throw();
00180         virtual void addEvent(IN unsigned long tick,
00181                                 IN const MidiEvent& event);
00182         virtual void write(IN const char * filename) const;
00183 
00184 private:
00185         // private typedefs ----------------------------------------------------
00186         typedef std::vector<int> VecTracks;     // vector of track numbers
00187 
00188         // private helper methods ----------------------------------------------
00189         void getTracks(OUT VecTracks& tracks) const throw();
00190         void writeTrackEvents(IN FILE * outfile, IN int track_index,
00191                 OUT dword_t& count) const;
00192 
00193         // private data members ------------------------------------------------
00194         int             m_ppqn;         // pulses per quarter note
00195         float           m_bpm;
00196         MidiEventMap    m_events;       // all events in midi file
00197 };
00198 
00199 
00200 
00201 ////////////////////////////////////////////////////////////////////////////////
00202 //
00203 //      MidiFileImpl -- public class methods
00204 //
00205 ////////////////////////////////////////////////////////////////////////////////
00206 
00207 MidiFileImpl::MidiFileImpl(void) throw() :
00208 m_ppqn(96),
00209 m_bpm(120)
00210 {
00211         // DPRINTF("MidiFileImpl constructor...");
00212 }
00213 
00214 
00215 
00216 MidiFileImpl::~MidiFileImpl(void) throw()
00217 {
00218         // DPRINTF("MidiFileImpl destructor...");
00219 }
00220 
00221 
00222 
00223 ////////////////////////////////////////////////////////////////////////////////
00224 //
00225 //      MidiFileImpl -- public class methods
00226 //
00227 ////////////////////////////////////////////////////////////////////////////////
00228 
00229 void
00230 MidiFileImpl::initialize
00231 (
00232 void
00233 )
00234 throw()
00235 {
00236         // nothing to do!
00237 }
00238 
00239 
00240 
00241 void
00242 MidiFileImpl::setPpqn
00243 (
00244 IN int ppqn
00245 )
00246 throw()
00247 {
00248         ASSERT(ppqn > 0, "Bad ppqn");
00249         m_ppqn = ppqn;
00250 }
00251 
00252 
00253 
00254 void
00255 MidiFileImpl::setTempo
00256 (
00257 IN float bpm
00258 )
00259 throw()
00260 {
00261         ASSERT(bpm > 0, "Bad bpm: %f", bpm);
00262 
00263         m_bpm = bpm;
00264 }
00265 
00266 
00267 
00268 void
00269 MidiFileImpl::readFile
00270 (
00271 IN const char * filename
00272 )
00273 {
00274         ASSERT(filename, "NULL filename");
00275 
00276         ASSERT(false, "ReadFile() is not yet implemented");
00277 }
00278 
00279 
00280 
00281 ////////////////////////////////////////////////////////////////////////////////
00282 //
00283 //      MidiFileImpl -- media::MidiFile class interface methods
00284 //
00285 ////////////////////////////////////////////////////////////////////////////////
00286 
00287 const MidiEventMap&
00288 MidiFileImpl::getEvents(void)
00289 const
00290 throw()
00291 {
00292         return m_events;
00293 }
00294 
00295 
00296 
00297 void
00298 MidiFileImpl::addEvent
00299 (
00300 IN unsigned long tick,
00301 IN const MidiEvent& event
00302 )
00303 {
00304         m_events.insert(MidiEventMap::value_type(tick, event));
00305 }
00306 
00307 
00308 
00309 void
00310 MidiFileImpl::write
00311 (
00312 IN const char * filename
00313 )
00314 const
00315 {
00316         VecTracks tracks;
00317         FILE * outfile = NULL;
00318         byte_t byte;
00319 
00320         ASSERT(filename && *filename, "NULL filename");
00321 
00322         // get tracks
00323         this->getTracks(tracks);
00324         ASSERT((int) tracks.size() <= s_MaxTracks, "Bad track count");
00325 
00326         // open file
00327         outfile = fopen(filename, "w");
00328         ASSERT(outfile, "Failed to open file for writing: '%s'", filename);
00329 
00330         // write MThd chunk
00331         WriteChunkHeader(outfile, "MThd", 6);
00332 
00333         // midi file format is 1
00334         WriteWord(outfile, (word_t) 1);
00335 
00336         // # of tracks
00337         WriteWord(outfile, (word_t) (tracks.size() + 1));
00338 
00339         // no midi player understands SMPTE--use ppqn 
00340         byte = (byte_t) (m_ppqn / 256);
00341         WriteByte(outfile, byte);
00342         // DPRINTF("  1st time byte: %x", byte);
00343 
00344         byte = (byte_t) (m_ppqn % 256);
00345         WriteByte(outfile, byte);
00346         // DPRINTF("  2nd time byte: %x", byte);
00347 
00348         // write tracks
00349         for (int i = -1; i < (int) tracks.size(); i++) {
00350                 int track_idx = -1;
00351                 if (i >= 0) {
00352                         track_idx = tracks[i];
00353                 }
00354                 dword_t count = 0;
00355                 long position;
00356 
00357                 // DPRINTF("Writing track %d (idx = %d)", i, track_idx);
00358 
00359                 // remember location
00360                 position = ftell(outfile);
00361                 // DPRINTF("  position = %ld", position);
00362 
00363                 // write chunk header (+ buffer size)
00364                 WriteChunkHeader(outfile, "MTrk", 0);
00365 
00366                 // write all events for this track
00367                 this->writeTrackEvents(outfile, track_idx, count);
00368 
00369                 // back up to chunk header location
00370                 ASSERT(-1 != fseek(outfile, position, SEEK_SET),
00371                     "Failed to set file position");
00372 
00373                 // write chunk again (with correct length now)
00374                 WriteChunkHeader(outfile, "MTrk", count);
00375 
00376                 // move forward to end
00377                 ASSERT(-1 != fseek(outfile, 0, SEEK_END),
00378                     "Failed to move to end of file");
00379                 ASSERT((long) (count + position + 8) == ftell(outfile),
00380                        "Should be '%lu' bytes ahead", count);
00381         }
00382 
00383         // write an empty header
00384         WriteChunkHeader(outfile, "MTrk", 0);
00385 }
00386 
00387 
00388 
00389 ////////////////////////////////////////////////////////////////////////////////
00390 //
00391 //      MidiFileImpl -- private helper methods
00392 //
00393 ////////////////////////////////////////////////////////////////////////////////
00394 
00395 void
00396 MidiFileImpl::getTracks
00397 (
00398 OUT VecTracks& tracks
00399 )
00400 const
00401 throw()
00402 {
00403         MidiEventMap::const_iterator iter;
00404         bool used[s_MaxTracks];
00405 
00406         tracks.clear();
00407         for (int i = 0; i < s_MaxTracks; i++) {
00408                 used[i] = false;
00409         }
00410 
00411         // loop through, accumulate track indices
00412         for (iter = m_events.begin(); iter != m_events.end(); ++iter) {
00413 
00414                 // is this a new index?
00415                 int channel = iter->second.GetChannel();
00416                 ASSERT(channel >=0 && channel < s_MaxTracks,
00417                     "Bad channel/track number for event");
00418 
00419                 if (!used[iter->second.GetChannel()]) {
00420                         used[iter->second.GetChannel()] = true;
00421                         tracks.push_back(iter->second.GetChannel());
00422                 }
00423         }
00424 }
00425 
00426 
00427 
00428 void
00429 MidiFileImpl::writeTrackEvents
00430 (
00431 IN FILE * outfile,
00432 IN int track_index,
00433 OUT dword_t& count
00434 )
00435 const
00436 {
00437         MidiEventMap::const_iterator iter;
00438 
00439         ASSERT(outfile, "NULL output file");
00440         ASSERT(track_index >= -1 && track_index < s_MaxTracks,
00441             "Invalid track index");
00442         count = 0;              // no bytes written
00443 
00444         // index is -1?  Special initial track
00445         if (-1 == track_index) {
00446                 // write tempo
00447                 count += WriteVariable(outfile, 0);     // time = 0
00448                 WriteByte(outfile, 255);                // meta event
00449                 WriteByte(outfile, 81);                 // set tempo
00450                 WriteByte(outfile, 3);                  // writing 3 bytes
00451                 long upqn = (int) (s_usec_per_minute / m_bpm); // microseconds per quarter note
00452                 // DPRINTF("bpm=%3.1lf  -->  upqn=%ld", m_bpm, upqn);
00453                 const byte_t * pb = (const byte_t *) &upqn;
00454 /*              for (int i = 0; i < 4; ++i) {
00455                         DPRINTF("  byte[%d] = %d", i, pb[i]);
00456                 } */
00457                 WriteByte(outfile, pb[2]);
00458                 WriteByte(outfile, pb[1]);
00459                 WriteByte(outfile, pb[0]);
00460                 count += 6;     // meta + event type + length byte + 3 bytes for tempo
00461 
00462                 return;
00463         }
00464 
00465         // loop through all events and write those for this track
00466         long current_tick = 0;  // start at zero tick
00467         for (iter = m_events.begin(); iter != m_events.end(); ++iter) {
00468         
00469                 int track = iter->second.GetChannel();
00470                 ASSERT(track >= 0 && track < s_MaxTracks,
00471                     "Invalid track index");
00472                 // DPRINTF("Track index = %d", track);
00473                 if (track == track_index) {
00474                         // write this event
00475                         byte_t status;
00476 
00477                         // first write the delta time (ticks)
00478                         long delta = iter->first - current_tick;
00479                         int bytes = WriteVariable(outfile, delta);
00480                         ASSERT(bytes > 0, "Invalid byte count");
00481                         current_tick = iter->first;
00482                         count += bytes;
00483 
00484                         // DPRINTF("  Wrote delta: %lu (%d bytes)", delta, bytes);
00485 
00486                         // write the status byte
00487                         status = (iter->second.GetStatus() << 4) |
00488                             iter->second.GetChannel();
00489                         WriteByte(outfile, status);
00490                         count += 1;
00491                         // DPRINTF("  Wrote status byte: %x", status);
00492 
00493                         // write the data bytes
00494                         bytes = MidiEvent::GetDataByteCount(
00495                             iter->second.GetStatus());
00496                         ASSERT(bytes > 0, "Invalid byte count");
00497                         ASSERT(bytes < 3, "Invalid byte count");
00498 
00499                         if (bytes > 0) {
00500                                 WriteByte(outfile, iter->second.GetByte0());
00501                                 //DPRINTF("  byte0 = %x",
00502                                 //    iter->second.GetByte0());
00503                         }
00504                         if (bytes > 1) {
00505                                 WriteByte(outfile, iter->second.GetByte1());
00506                                 //DPRINTF("  byte1 = %x",
00507                                 //    iter->second.GetByte1());
00508                         }
00509 
00510                         count += bytes;
00511                 }
00512         }
00513 }
00514 
00515 
00516 
00517 ////////////////////////////////////////////////////////////////////////////////
00518 //
00519 //      MidiFile -- public static methods (factory methods)
00520 //
00521 ////////////////////////////////////////////////////////////////////////////////
00522 
00523 /*
00524  * Function:  MidiFile::New
00525  */
00526 smart_ptr<MidiFile>
00527 MidiFile::create
00528 (
00529 void
00530 )
00531 {
00532         // create new object
00533         smart_ptr<MidiFileImpl> local = new MidiFileImpl;
00534         ASSERT(local, "out of memory");
00535         local->initialize();
00536 
00537         // return to caller
00538         return local;
00539 }
00540 
00541 
00542 
00543 /*
00544  * Function: MidiFile::Load
00545  */
00546 smart_ptr<MidiFile>
00547 MidiFile::load
00548 (
00549 IN const char * filename
00550 )
00551 {
00552         // new object
00553         smart_ptr<MidiFileImpl> local = new MidiFileImpl;
00554         local->readFile(filename);
00555 
00556         // return
00557         return local;
00558 }
00559 
00560 
00561 
00562 ////////////////////////////////////////////////////////////////////////////////
00563 //
00564 //      Helper Methods for Midi File
00565 //
00566 ////////////////////////////////////////////////////////////////////////////////
00567 
00568 static void
00569 AddNote
00570 (
00571 IN MidiFile * file,
00572 IN dword_t tick,
00573 IN int channel,
00574 IN int note,
00575 IN int velocity,
00576 IN bool note_on
00577 )
00578 throw()
00579 {
00580         MidiEvent event;
00581 
00582         ASSERT(file, "NULL midi file");
00583         ASSERT(channel >= 0, "Bad midi channel");
00584         ASSERT(channel < 16, "Bad midi channel");
00585         ASSERT(note >= 0, "Bad note value");
00586         ASSERT(note < 128, "Bad note value");
00587         ASSERT(velocity >= 0, "Bad velocity");
00588         ASSERT(velocity < 128, "Bad velocity");
00589 
00590         // DPRINTF("Note event on channel %d", channel);
00591 
00592         event.SetStatus(note_on ? MidiEvent::eNoteOn : MidiEvent::eNoteOff);
00593         event.SetChannel(channel);
00594         event.SetByte0(note);
00595         event.SetByte1(velocity);
00596 
00597         file->addEvent(tick, event);
00598 }
00599 
00600 
00601 
00602 void
00603 AddNoteOn
00604 (
00605 IN MidiFile * file,
00606 dword_t tick,
00607 int channel,
00608 int note,
00609 int velocity
00610 )
00611 throw()
00612 {
00613         AddNote(file, tick, channel, note, velocity, true); 
00614 }
00615 
00616 
00617 
00618 void
00619 AddNoteOff
00620 (
00621 IN MidiFile * file,
00622 dword_t tick,
00623 int channel,
00624 int note,
00625 int velocity
00626 )
00627 throw()
00628 {
00629         AddNote(file, tick, channel, note, velocity, false);
00630 }
00631 
00632 
00633 
00634 };      // media namespace
00635