00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011 #include "midi_file.h"
00012
00013 #ifdef WIN32
00014 #include <winsock2.h>
00015 #else // WIN32
00016 #include <netinet/in.h>
00017 #endif // WIN32
00018
00019
00020
00021 namespace media {
00022
00023
00024
00025
00026
00027 static const int s_MaxTracks = 16;
00028 static const long s_usec_per_minute = 60 * 1000 * 1000;
00029
00030
00031
00032
00033
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
00065 big_endian = htons(word);
00066
00067
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
00087 big_endian = htonl(dword);
00088
00089
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
00110
00111
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
00124 retval = count;
00125
00126
00127 while (count > 0) {
00128 count--;
00129 WriteByte(file, out[count]);
00130
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
00152 ASSERT(4 == fwrite(name, sizeof(char), 4, file),
00153 "Failed to write chunk name to midi file");
00154
00155
00156 WriteDword(file, length);
00157 }
00158
00159
00160
00161
00162
00163
00164
00165
00166 class MidiFileImpl : public MidiFile {
00167 public:
00168
00169 MidiFileImpl(void) throw();
00170 ~MidiFileImpl(void) throw();
00171
00172
00173 void initialize() throw();
00174 void readFile(IN const char * filename);
00175
00176
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
00186 typedef std::vector<int> VecTracks;
00187
00188
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
00194 int m_ppqn;
00195 float m_bpm;
00196 MidiEventMap m_events;
00197 };
00198
00199
00200
00201
00202
00203
00204
00205
00206
00207 MidiFileImpl::MidiFileImpl(void) throw() :
00208 m_ppqn(96),
00209 m_bpm(120)
00210 {
00211
00212 }
00213
00214
00215
00216 MidiFileImpl::~MidiFileImpl(void) throw()
00217 {
00218
00219 }
00220
00221
00222
00223
00224
00225
00226
00227
00228
00229 void
00230 MidiFileImpl::initialize
00231 (
00232 void
00233 )
00234 throw()
00235 {
00236
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
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
00323 this->getTracks(tracks);
00324 ASSERT((int) tracks.size() <= s_MaxTracks, "Bad track count");
00325
00326
00327 outfile = fopen(filename, "w");
00328 ASSERT(outfile, "Failed to open file for writing: '%s'", filename);
00329
00330
00331 WriteChunkHeader(outfile, "MThd", 6);
00332
00333
00334 WriteWord(outfile, (word_t) 1);
00335
00336
00337 WriteWord(outfile, (word_t) (tracks.size() + 1));
00338
00339
00340 byte = (byte_t) (m_ppqn / 256);
00341 WriteByte(outfile, byte);
00342
00343
00344 byte = (byte_t) (m_ppqn % 256);
00345 WriteByte(outfile, byte);
00346
00347
00348
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
00358
00359
00360 position = ftell(outfile);
00361
00362
00363
00364 WriteChunkHeader(outfile, "MTrk", 0);
00365
00366
00367 this->writeTrackEvents(outfile, track_idx, count);
00368
00369
00370 ASSERT(-1 != fseek(outfile, position, SEEK_SET),
00371 "Failed to set file position");
00372
00373
00374 WriteChunkHeader(outfile, "MTrk", count);
00375
00376
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
00384 WriteChunkHeader(outfile, "MTrk", 0);
00385 }
00386
00387
00388
00389
00390
00391
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
00412 for (iter = m_events.begin(); iter != m_events.end(); ++iter) {
00413
00414
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;
00443
00444
00445 if (-1 == track_index) {
00446
00447 count += WriteVariable(outfile, 0);
00448 WriteByte(outfile, 255);
00449 WriteByte(outfile, 81);
00450 WriteByte(outfile, 3);
00451 long upqn = (int) (s_usec_per_minute / m_bpm);
00452
00453 const byte_t * pb = (const byte_t *) &upqn;
00454
00455
00456
00457 WriteByte(outfile, pb[2]);
00458 WriteByte(outfile, pb[1]);
00459 WriteByte(outfile, pb[0]);
00460 count += 6;
00461
00462 return;
00463 }
00464
00465
00466 long current_tick = 0;
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
00473 if (track == track_index) {
00474
00475 byte_t status;
00476
00477
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
00485
00486
00487 status = (iter->second.GetStatus() << 4) |
00488 iter->second.GetChannel();
00489 WriteByte(outfile, status);
00490 count += 1;
00491
00492
00493
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
00502
00503 }
00504 if (bytes > 1) {
00505 WriteByte(outfile, iter->second.GetByte1());
00506
00507
00508 }
00509
00510 count += bytes;
00511 }
00512 }
00513 }
00514
00515
00516
00517
00518
00519
00520
00521
00522
00523
00524
00525
00526 smart_ptr<MidiFile>
00527 MidiFile::create
00528 (
00529 void
00530 )
00531 {
00532
00533 smart_ptr<MidiFileImpl> local = new MidiFileImpl;
00534 ASSERT(local, "out of memory");
00535 local->initialize();
00536
00537
00538 return local;
00539 }
00540
00541
00542
00543
00544
00545
00546 smart_ptr<MidiFile>
00547 MidiFile::load
00548 (
00549 IN const char * filename
00550 )
00551 {
00552
00553 smart_ptr<MidiFileImpl> local = new MidiFileImpl;
00554 local->readFile(filename);
00555
00556
00557 return local;
00558 }
00559
00560
00561
00562
00563
00564
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
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 };
00635