Mercurial > hg > svcore
comparison data/fileio/MIDIFileReader.cpp @ 148:1a42221a1522
* Reorganising code base. This revision will not compile.
author | Chris Cannam |
---|---|
date | Mon, 31 Jul 2006 11:49:58 +0000 |
parents | |
children | 4b2ea82fd0ed |
comparison
equal
deleted
inserted
replaced
147:3a13b0d4934e | 148:1a42221a1522 |
---|---|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ | |
2 | |
3 /* | |
4 Sonic Visualiser | |
5 An audio file viewer and annotation editor. | |
6 Centre for Digital Music, Queen Mary, University of London. | |
7 | |
8 This program is free software; you can redistribute it and/or | |
9 modify it under the terms of the GNU General Public License as | |
10 published by the Free Software Foundation; either version 2 of the | |
11 License, or (at your option) any later version. See the file | |
12 COPYING included with this distribution for more information. | |
13 */ | |
14 | |
15 | |
16 /* | |
17 This is a modified version of a source file from the | |
18 Rosegarden MIDI and audio sequencer and notation editor. | |
19 This file copyright 2000-2006 Richard Bown and Chris Cannam. | |
20 */ | |
21 | |
22 | |
23 #include <iostream> | |
24 #include <fstream> | |
25 #include <string> | |
26 #include <cstdio> | |
27 #include <algorithm> | |
28 | |
29 #include "MIDIFileReader.h" | |
30 | |
31 #include "base/Model.h" | |
32 #include "base/Pitch.h" | |
33 #include "base/RealTime.h" | |
34 #include "model/NoteModel.h" | |
35 | |
36 #include <QString> | |
37 #include <QMessageBox> | |
38 #include <QInputDialog> | |
39 | |
40 #include <sstream> | |
41 | |
42 using std::string; | |
43 using std::ifstream; | |
44 using std::stringstream; | |
45 using std::cerr; | |
46 using std::endl; | |
47 using std::ends; | |
48 using std::ios; | |
49 using std::vector; | |
50 using std::map; | |
51 using std::set; | |
52 | |
53 //#define MIDI_DEBUG 1 | |
54 | |
55 static const char *const MIDI_FILE_HEADER = "MThd"; | |
56 static const char *const MIDI_TRACK_HEADER = "MTrk"; | |
57 | |
58 static const MIDIFileReader::MIDIByte MIDI_STATUS_BYTE_MASK = 0x80; | |
59 static const MIDIFileReader::MIDIByte MIDI_MESSAGE_TYPE_MASK = 0xF0; | |
60 static const MIDIFileReader::MIDIByte MIDI_CHANNEL_NUM_MASK = 0x0F; | |
61 static const MIDIFileReader::MIDIByte MIDI_NOTE_OFF = 0x80; | |
62 static const MIDIFileReader::MIDIByte MIDI_NOTE_ON = 0x90; | |
63 static const MIDIFileReader::MIDIByte MIDI_POLY_AFTERTOUCH = 0xA0; | |
64 static const MIDIFileReader::MIDIByte MIDI_CTRL_CHANGE = 0xB0; | |
65 static const MIDIFileReader::MIDIByte MIDI_PROG_CHANGE = 0xC0; | |
66 static const MIDIFileReader::MIDIByte MIDI_CHNL_AFTERTOUCH = 0xD0; | |
67 static const MIDIFileReader::MIDIByte MIDI_PITCH_BEND = 0xE0; | |
68 static const MIDIFileReader::MIDIByte MIDI_SELECT_CHNL_MODE = 0xB0; | |
69 static const MIDIFileReader::MIDIByte MIDI_SYSTEM_EXCLUSIVE = 0xF0; | |
70 static const MIDIFileReader::MIDIByte MIDI_TC_QUARTER_FRAME = 0xF1; | |
71 static const MIDIFileReader::MIDIByte MIDI_SONG_POSITION_PTR = 0xF2; | |
72 static const MIDIFileReader::MIDIByte MIDI_SONG_SELECT = 0xF3; | |
73 static const MIDIFileReader::MIDIByte MIDI_TUNE_REQUEST = 0xF6; | |
74 static const MIDIFileReader::MIDIByte MIDI_END_OF_EXCLUSIVE = 0xF7; | |
75 static const MIDIFileReader::MIDIByte MIDI_TIMING_CLOCK = 0xF8; | |
76 static const MIDIFileReader::MIDIByte MIDI_START = 0xFA; | |
77 static const MIDIFileReader::MIDIByte MIDI_CONTINUE = 0xFB; | |
78 static const MIDIFileReader::MIDIByte MIDI_STOP = 0xFC; | |
79 static const MIDIFileReader::MIDIByte MIDI_ACTIVE_SENSING = 0xFE; | |
80 static const MIDIFileReader::MIDIByte MIDI_SYSTEM_RESET = 0xFF; | |
81 static const MIDIFileReader::MIDIByte MIDI_SYSEX_NONCOMMERCIAL = 0x7D; | |
82 static const MIDIFileReader::MIDIByte MIDI_SYSEX_NON_RT = 0x7E; | |
83 static const MIDIFileReader::MIDIByte MIDI_SYSEX_RT = 0x7F; | |
84 static const MIDIFileReader::MIDIByte MIDI_SYSEX_RT_COMMAND = 0x06; | |
85 static const MIDIFileReader::MIDIByte MIDI_SYSEX_RT_RESPONSE = 0x07; | |
86 static const MIDIFileReader::MIDIByte MIDI_MMC_STOP = 0x01; | |
87 static const MIDIFileReader::MIDIByte MIDI_MMC_PLAY = 0x02; | |
88 static const MIDIFileReader::MIDIByte MIDI_MMC_DEFERRED_PLAY = 0x03; | |
89 static const MIDIFileReader::MIDIByte MIDI_MMC_FAST_FORWARD = 0x04; | |
90 static const MIDIFileReader::MIDIByte MIDI_MMC_REWIND = 0x05; | |
91 static const MIDIFileReader::MIDIByte MIDI_MMC_RECORD_STROBE = 0x06; | |
92 static const MIDIFileReader::MIDIByte MIDI_MMC_RECORD_EXIT = 0x07; | |
93 static const MIDIFileReader::MIDIByte MIDI_MMC_RECORD_PAUSE = 0x08; | |
94 static const MIDIFileReader::MIDIByte MIDI_MMC_PAUSE = 0x08; | |
95 static const MIDIFileReader::MIDIByte MIDI_MMC_EJECT = 0x0A; | |
96 static const MIDIFileReader::MIDIByte MIDI_MMC_LOCATE = 0x44; | |
97 static const MIDIFileReader::MIDIByte MIDI_FILE_META_EVENT = 0xFF; | |
98 static const MIDIFileReader::MIDIByte MIDI_SEQUENCE_NUMBER = 0x00; | |
99 static const MIDIFileReader::MIDIByte MIDI_TEXT_EVENT = 0x01; | |
100 static const MIDIFileReader::MIDIByte MIDI_COPYRIGHT_NOTICE = 0x02; | |
101 static const MIDIFileReader::MIDIByte MIDI_TRACK_NAME = 0x03; | |
102 static const MIDIFileReader::MIDIByte MIDI_INSTRUMENT_NAME = 0x04; | |
103 static const MIDIFileReader::MIDIByte MIDI_LYRIC = 0x05; | |
104 static const MIDIFileReader::MIDIByte MIDI_TEXT_MARKER = 0x06; | |
105 static const MIDIFileReader::MIDIByte MIDI_CUE_POINT = 0x07; | |
106 static const MIDIFileReader::MIDIByte MIDI_CHANNEL_PREFIX = 0x20; | |
107 static const MIDIFileReader::MIDIByte MIDI_CHANNEL_PREFIX_OR_PORT = 0x21; | |
108 static const MIDIFileReader::MIDIByte MIDI_END_OF_TRACK = 0x2F; | |
109 static const MIDIFileReader::MIDIByte MIDI_SET_TEMPO = 0x51; | |
110 static const MIDIFileReader::MIDIByte MIDI_SMPTE_OFFSET = 0x54; | |
111 static const MIDIFileReader::MIDIByte MIDI_TIME_SIGNATURE = 0x58; | |
112 static const MIDIFileReader::MIDIByte MIDI_KEY_SIGNATURE = 0x59; | |
113 static const MIDIFileReader::MIDIByte MIDI_SEQUENCER_SPECIFIC = 0x7F; | |
114 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_BANK_MSB = 0x00; | |
115 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_VOLUME = 0x07; | |
116 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_BANK_LSB = 0x20; | |
117 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_MODULATION = 0x01; | |
118 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_PAN = 0x0A; | |
119 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_SUSTAIN = 0x40; | |
120 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RESONANCE = 0x47; | |
121 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RELEASE = 0x48; | |
122 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_ATTACK = 0x49; | |
123 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_FILTER = 0x4A; | |
124 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_REVERB = 0x5B; | |
125 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_CHORUS = 0x5D; | |
126 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_NRPN_1 = 0x62; | |
127 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_NRPN_2 = 0x63; | |
128 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RPN_1 = 0x64; | |
129 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RPN_2 = 0x65; | |
130 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_SOUNDS_OFF = 0x78; | |
131 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RESET = 0x79; | |
132 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_LOCAL = 0x7A; | |
133 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_ALL_NOTES_OFF = 0x7B; | |
134 static const MIDIFileReader::MIDIByte MIDI_PERCUSSION_CHANNEL = 9; | |
135 | |
136 class MIDIEvent | |
137 { | |
138 public: | |
139 typedef MIDIFileReader::MIDIByte MIDIByte; | |
140 | |
141 MIDIEvent(unsigned long deltaTime, | |
142 MIDIByte eventCode, | |
143 MIDIByte data1 = 0, | |
144 MIDIByte data2 = 0) : | |
145 m_deltaTime(deltaTime), | |
146 m_duration(0), | |
147 m_eventCode(eventCode), | |
148 m_data1(data1), | |
149 m_data2(data2), | |
150 m_metaEventCode(0) | |
151 { } | |
152 | |
153 MIDIEvent(unsigned long deltaTime, | |
154 MIDIByte eventCode, | |
155 MIDIByte metaEventCode, | |
156 const string &metaMessage) : | |
157 m_deltaTime(deltaTime), | |
158 m_duration(0), | |
159 m_eventCode(eventCode), | |
160 m_data1(0), | |
161 m_data2(0), | |
162 m_metaEventCode(metaEventCode), | |
163 m_metaMessage(metaMessage) | |
164 { } | |
165 | |
166 MIDIEvent(unsigned long deltaTime, | |
167 MIDIByte eventCode, | |
168 const string &sysEx) : | |
169 m_deltaTime(deltaTime), | |
170 m_duration(0), | |
171 m_eventCode(eventCode), | |
172 m_data1(0), | |
173 m_data2(0), | |
174 m_metaEventCode(0), | |
175 m_metaMessage(sysEx) | |
176 { } | |
177 | |
178 ~MIDIEvent() { } | |
179 | |
180 void setTime(const unsigned long &time) { m_deltaTime = time; } | |
181 void setDuration(const unsigned long& duration) { m_duration = duration;} | |
182 unsigned long addTime(const unsigned long &time) { | |
183 m_deltaTime += time; | |
184 return m_deltaTime; | |
185 } | |
186 | |
187 MIDIByte getMessageType() const | |
188 { return (m_eventCode & MIDI_MESSAGE_TYPE_MASK); } | |
189 | |
190 MIDIByte getChannelNumber() const | |
191 { return (m_eventCode & MIDI_CHANNEL_NUM_MASK); } | |
192 | |
193 unsigned long getTime() const { return m_deltaTime; } | |
194 unsigned long getDuration() const { return m_duration; } | |
195 | |
196 MIDIByte getPitch() const { return m_data1; } | |
197 MIDIByte getVelocity() const { return m_data2; } | |
198 MIDIByte getData1() const { return m_data1; } | |
199 MIDIByte getData2() const { return m_data2; } | |
200 MIDIByte getEventCode() const { return m_eventCode; } | |
201 | |
202 bool isMeta() const { return (m_eventCode == MIDI_FILE_META_EVENT); } | |
203 | |
204 MIDIByte getMetaEventCode() const { return m_metaEventCode; } | |
205 string getMetaMessage() const { return m_metaMessage; } | |
206 void setMetaMessage(const string &meta) { m_metaMessage = meta; } | |
207 | |
208 friend bool operator<(const MIDIEvent &a, const MIDIEvent &b); | |
209 | |
210 private: | |
211 MIDIEvent& operator=(const MIDIEvent); | |
212 | |
213 unsigned long m_deltaTime; | |
214 unsigned long m_duration; | |
215 MIDIByte m_eventCode; | |
216 MIDIByte m_data1; // or Note | |
217 MIDIByte m_data2; // or Velocity | |
218 MIDIByte m_metaEventCode; | |
219 string m_metaMessage; | |
220 }; | |
221 | |
222 // Comparator for sorting | |
223 // | |
224 struct MIDIEventCmp | |
225 { | |
226 bool operator()(const MIDIEvent &mE1, const MIDIEvent &mE2) const | |
227 { return mE1.getTime() < mE2.getTime(); } | |
228 | |
229 bool operator()(const MIDIEvent *mE1, const MIDIEvent *mE2) const | |
230 { return mE1->getTime() < mE2->getTime(); } | |
231 }; | |
232 | |
233 class MIDIException : virtual public std::exception | |
234 { | |
235 public: | |
236 MIDIException(QString message) throw() : m_message(message) { | |
237 cerr << "WARNING: MIDI exception: " | |
238 << message.toLocal8Bit().data() << endl; | |
239 } | |
240 virtual ~MIDIException() throw() { } | |
241 | |
242 virtual const char *what() const throw() { | |
243 return m_message.toLocal8Bit().data(); | |
244 } | |
245 | |
246 protected: | |
247 QString m_message; | |
248 }; | |
249 | |
250 | |
251 MIDIFileReader::MIDIFileReader(QString path, | |
252 size_t mainModelSampleRate) : | |
253 m_timingDivision(0), | |
254 m_format(MIDI_FILE_BAD_FORMAT), | |
255 m_numberOfTracks(0), | |
256 m_trackByteCount(0), | |
257 m_decrementCount(false), | |
258 m_path(path), | |
259 m_midiFile(0), | |
260 m_fileSize(0), | |
261 m_mainModelSampleRate(mainModelSampleRate) | |
262 { | |
263 if (parseFile()) { | |
264 m_error = ""; | |
265 } | |
266 } | |
267 | |
268 MIDIFileReader::~MIDIFileReader() | |
269 { | |
270 for (MIDIComposition::iterator i = m_midiComposition.begin(); | |
271 i != m_midiComposition.end(); ++i) { | |
272 | |
273 for (MIDITrack::iterator j = i->second.begin(); | |
274 j != i->second.end(); ++j) { | |
275 delete *j; | |
276 } | |
277 | |
278 i->second.clear(); | |
279 } | |
280 | |
281 m_midiComposition.clear(); | |
282 } | |
283 | |
284 bool | |
285 MIDIFileReader::isOK() const | |
286 { | |
287 return (m_error == ""); | |
288 } | |
289 | |
290 QString | |
291 MIDIFileReader::getError() const | |
292 { | |
293 return m_error; | |
294 } | |
295 | |
296 long | |
297 MIDIFileReader::midiBytesToLong(const string& bytes) | |
298 { | |
299 if (bytes.length() != 4) { | |
300 throw MIDIException(tr("Wrong length for long data in MIDI stream (%1, should be %2)").arg(bytes.length()).arg(4)); | |
301 } | |
302 | |
303 long longRet = ((long)(((MIDIByte)bytes[0]) << 24)) | | |
304 ((long)(((MIDIByte)bytes[1]) << 16)) | | |
305 ((long)(((MIDIByte)bytes[2]) << 8)) | | |
306 ((long)((MIDIByte)(bytes[3]))); | |
307 | |
308 return longRet; | |
309 } | |
310 | |
311 int | |
312 MIDIFileReader::midiBytesToInt(const string& bytes) | |
313 { | |
314 if (bytes.length() != 2) { | |
315 throw MIDIException(tr("Wrong length for int data in MIDI stream (%1, should be %2)").arg(bytes.length()).arg(2)); | |
316 } | |
317 | |
318 int intRet = ((int)(((MIDIByte)bytes[0]) << 8)) | | |
319 ((int)(((MIDIByte)bytes[1]))); | |
320 return(intRet); | |
321 } | |
322 | |
323 | |
324 // Gets a single byte from the MIDI byte stream. For each track | |
325 // section we can read only a specified number of bytes held in | |
326 // m_trackByteCount. | |
327 // | |
328 MIDIFileReader::MIDIByte | |
329 MIDIFileReader::getMIDIByte() | |
330 { | |
331 if (!m_midiFile) { | |
332 throw MIDIException(tr("getMIDIByte called but no MIDI file open")); | |
333 } | |
334 | |
335 if (m_midiFile->eof()) { | |
336 throw MIDIException(tr("End of MIDI file encountered while reading")); | |
337 } | |
338 | |
339 if (m_decrementCount && m_trackByteCount <= 0) { | |
340 throw MIDIException(tr("Attempt to get more bytes than expected on Track")); | |
341 } | |
342 | |
343 char byte; | |
344 if (m_midiFile->read(&byte, 1)) { | |
345 --m_trackByteCount; | |
346 return (MIDIByte)byte; | |
347 } | |
348 | |
349 throw MIDIException(tr("Attempt to read past MIDI file end")); | |
350 } | |
351 | |
352 | |
353 // Gets a specified number of bytes from the MIDI byte stream. For | |
354 // each track section we can read only a specified number of bytes | |
355 // held in m_trackByteCount. | |
356 // | |
357 string | |
358 MIDIFileReader::getMIDIBytes(unsigned long numberOfBytes) | |
359 { | |
360 if (!m_midiFile) { | |
361 throw MIDIException(tr("getMIDIBytes called but no MIDI file open")); | |
362 } | |
363 | |
364 if (m_midiFile->eof()) { | |
365 throw MIDIException(tr("End of MIDI file encountered while reading")); | |
366 } | |
367 | |
368 if (m_decrementCount && (numberOfBytes > (unsigned long)m_trackByteCount)) { | |
369 throw MIDIException(tr("Attempt to get more bytes than available on Track (%1, only have %2)").arg(numberOfBytes).arg(m_trackByteCount)); | |
370 } | |
371 | |
372 string stringRet; | |
373 char fileMIDIByte; | |
374 | |
375 while (stringRet.length() < numberOfBytes && | |
376 m_midiFile->read(&fileMIDIByte, 1)) { | |
377 stringRet += fileMIDIByte; | |
378 } | |
379 | |
380 // if we've reached the end of file without fulfilling the | |
381 // quota then panic as our parsing has performed incorrectly | |
382 // | |
383 if (stringRet.length() < numberOfBytes) { | |
384 stringRet = ""; | |
385 throw MIDIException(tr("Attempt to read past MIDI file end")); | |
386 } | |
387 | |
388 // decrement the byte count | |
389 if (m_decrementCount) | |
390 m_trackByteCount -= stringRet.length(); | |
391 | |
392 return stringRet; | |
393 } | |
394 | |
395 | |
396 // Get a long number of variable length from the MIDI byte stream. | |
397 // | |
398 long | |
399 MIDIFileReader::getNumberFromMIDIBytes(int firstByte) | |
400 { | |
401 if (!m_midiFile) { | |
402 throw MIDIException(tr("getNumberFromMIDIBytes called but no MIDI file open")); | |
403 } | |
404 | |
405 long longRet = 0; | |
406 MIDIByte midiByte; | |
407 | |
408 if (firstByte >= 0) { | |
409 midiByte = (MIDIByte)firstByte; | |
410 } else if (m_midiFile->eof()) { | |
411 return longRet; | |
412 } else { | |
413 midiByte = getMIDIByte(); | |
414 } | |
415 | |
416 longRet = midiByte; | |
417 if (midiByte & 0x80) { | |
418 longRet &= 0x7F; | |
419 do { | |
420 midiByte = getMIDIByte(); | |
421 longRet = (longRet << 7) + (midiByte & 0x7F); | |
422 } while (!m_midiFile->eof() && (midiByte & 0x80)); | |
423 } | |
424 | |
425 return longRet; | |
426 } | |
427 | |
428 | |
429 // Seek to the next track in the midi file and set the number | |
430 // of bytes to be read in the counter m_trackByteCount. | |
431 // | |
432 bool | |
433 MIDIFileReader::skipToNextTrack() | |
434 { | |
435 if (!m_midiFile) { | |
436 throw MIDIException(tr("skipToNextTrack called but no MIDI file open")); | |
437 } | |
438 | |
439 string buffer, buffer2; | |
440 m_trackByteCount = -1; | |
441 m_decrementCount = false; | |
442 | |
443 while (!m_midiFile->eof() && (m_decrementCount == false)) { | |
444 buffer = getMIDIBytes(4); | |
445 if (buffer.compare(0, 4, MIDI_TRACK_HEADER) == 0) { | |
446 m_trackByteCount = midiBytesToLong(getMIDIBytes(4)); | |
447 m_decrementCount = true; | |
448 } | |
449 } | |
450 | |
451 if (m_trackByteCount == -1) { // we haven't found a track | |
452 return false; | |
453 } else { | |
454 return true; | |
455 } | |
456 } | |
457 | |
458 | |
459 // Read in a MIDI file. The parsing process throws exceptions back up | |
460 // here if we run into trouble which we can then pass back out to | |
461 // whoever called us using a nice bool. | |
462 // | |
463 bool | |
464 MIDIFileReader::parseFile() | |
465 { | |
466 m_error = ""; | |
467 | |
468 #ifdef MIDI_DEBUG | |
469 cerr << "MIDIFileReader::open() : fileName = " << m_fileName.c_str() << endl; | |
470 #endif | |
471 | |
472 // Open the file | |
473 m_midiFile = new ifstream(m_path.toLocal8Bit().data(), | |
474 ios::in | ios::binary); | |
475 | |
476 if (!*m_midiFile) { | |
477 m_error = "File not found or not readable."; | |
478 m_format = MIDI_FILE_BAD_FORMAT; | |
479 delete m_midiFile; | |
480 return false; | |
481 } | |
482 | |
483 bool retval = false; | |
484 | |
485 try { | |
486 | |
487 // Set file size so we can count it off | |
488 // | |
489 m_midiFile->seekg(0, ios::end); | |
490 m_fileSize = m_midiFile->tellg(); | |
491 m_midiFile->seekg(0, ios::beg); | |
492 | |
493 // Parse the MIDI header first. The first 14 bytes of the file. | |
494 if (!parseHeader(getMIDIBytes(14))) { | |
495 m_format = MIDI_FILE_BAD_FORMAT; | |
496 m_error = "Not a MIDI file."; | |
497 goto done; | |
498 } | |
499 | |
500 unsigned int i = 0; | |
501 | |
502 for (unsigned int j = 0; j < m_numberOfTracks; ++j) { | |
503 | |
504 #ifdef MIDI_DEBUG | |
505 cerr << "Parsing Track " << j << endl; | |
506 #endif | |
507 | |
508 if (!skipToNextTrack()) { | |
509 #ifdef MIDI_DEBUG | |
510 cerr << "Couldn't find Track " << j << endl; | |
511 #endif | |
512 m_error = "File corrupted or in non-standard format?"; | |
513 m_format = MIDI_FILE_BAD_FORMAT; | |
514 goto done; | |
515 } | |
516 | |
517 #ifdef MIDI_DEBUG | |
518 cerr << "Track has " << m_trackByteCount << " bytes" << endl; | |
519 #endif | |
520 | |
521 // Run through the events taking them into our internal | |
522 // representation. | |
523 if (!parseTrack(i)) { | |
524 #ifdef MIDI_DEBUG | |
525 cerr << "Track " << j << " parsing failed" << endl; | |
526 #endif | |
527 m_error = "File corrupted or in non-standard format?"; | |
528 m_format = MIDI_FILE_BAD_FORMAT; | |
529 goto done; | |
530 } | |
531 | |
532 ++i; // j is the source track number, i the destination | |
533 } | |
534 | |
535 m_numberOfTracks = i; | |
536 retval = true; | |
537 | |
538 } catch (MIDIException e) { | |
539 | |
540 cerr << "MIDIFileReader::open() - caught exception - " << e.what() << endl; | |
541 m_error = e.what(); | |
542 } | |
543 | |
544 done: | |
545 m_midiFile->close(); | |
546 delete m_midiFile; | |
547 | |
548 for (unsigned int track = 0; track < m_numberOfTracks; ++track) { | |
549 | |
550 // Convert the deltaTime to an absolute time since the track | |
551 // start. The addTime method returns the sum of the current | |
552 // MIDI Event delta time plus the argument. | |
553 | |
554 unsigned long acc = 0; | |
555 | |
556 for (MIDITrack::iterator i = m_midiComposition[track].begin(); | |
557 i != m_midiComposition[track].end(); ++i) { | |
558 acc = (*i)->addTime(acc); | |
559 } | |
560 | |
561 if (consolidateNoteOffEvents(track)) { // returns true if some notes exist | |
562 m_loadableTracks.insert(track); | |
563 } | |
564 } | |
565 | |
566 for (unsigned int track = 0; track < m_numberOfTracks; ++track) { | |
567 updateTempoMap(track); | |
568 } | |
569 | |
570 calculateTempoTimestamps(); | |
571 | |
572 return retval; | |
573 } | |
574 | |
575 // Parse and ensure the MIDI Header is legitimate | |
576 // | |
577 bool | |
578 MIDIFileReader::parseHeader(const string &midiHeader) | |
579 { | |
580 if (midiHeader.size() < 14) { | |
581 #ifdef MIDI_DEBUG | |
582 cerr << "MIDIFileReader::parseHeader() - file header undersized" << endl; | |
583 #endif | |
584 return false; | |
585 } | |
586 | |
587 if (midiHeader.compare(0, 4, MIDI_FILE_HEADER) != 0) { | |
588 #ifdef MIDI_DEBUG | |
589 cerr << "MIDIFileReader::parseHeader()" | |
590 << "- file header not found or malformed" | |
591 << endl; | |
592 #endif | |
593 return false; | |
594 } | |
595 | |
596 if (midiBytesToLong(midiHeader.substr(4,4)) != 6L) { | |
597 #ifdef MIDI_DEBUG | |
598 cerr << "MIDIFileReader::parseHeader()" | |
599 << " - header length incorrect" | |
600 << endl; | |
601 #endif | |
602 return false; | |
603 } | |
604 | |
605 m_format = (MIDIFileFormatType) midiBytesToInt(midiHeader.substr(8,2)); | |
606 m_numberOfTracks = midiBytesToInt(midiHeader.substr(10,2)); | |
607 m_timingDivision = midiBytesToInt(midiHeader.substr(12,2)); | |
608 | |
609 if (m_format == MIDI_SEQUENTIAL_TRACK_FILE) { | |
610 #ifdef MIDI_DEBUG | |
611 cerr << "MIDIFileReader::parseHeader()" | |
612 << "- can't load sequential track file" | |
613 << endl; | |
614 #endif | |
615 return false; | |
616 } | |
617 | |
618 #ifdef MIDI_DEBUG | |
619 if (m_timingDivision < 0) { | |
620 cerr << "MIDIFileReader::parseHeader()" | |
621 << " - file uses SMPTE timing" | |
622 << endl; | |
623 } | |
624 #endif | |
625 | |
626 return true; | |
627 } | |
628 | |
629 // Extract the contents from a MIDI file track and places it into | |
630 // our local map of MIDI events. | |
631 // | |
632 bool | |
633 MIDIFileReader::parseTrack(unsigned int &lastTrackNum) | |
634 { | |
635 MIDIByte midiByte, metaEventCode, data1, data2; | |
636 MIDIByte eventCode = 0x80; | |
637 string metaMessage; | |
638 unsigned int messageLength; | |
639 unsigned long deltaTime; | |
640 unsigned long accumulatedTime = 0; | |
641 | |
642 // The trackNum passed in to this method is the default track for | |
643 // all events provided they're all on the same channel. If we find | |
644 // events on more than one channel, we increment trackNum and record | |
645 // the mapping from channel to trackNum in this channelTrackMap. | |
646 // We then return the new trackNum by reference so the calling | |
647 // method knows we've got more tracks than expected. | |
648 | |
649 // This would be a vector<unsigned int> but we need -1 to indicate | |
650 // "not yet used" | |
651 vector<int> channelTrackMap(16, -1); | |
652 | |
653 // This is used to store the last absolute time found on each track, | |
654 // allowing us to modify delta-times correctly when separating events | |
655 // out from one to multiple tracks | |
656 // | |
657 map<int, unsigned long> trackTimeMap; | |
658 | |
659 // Meta-events don't have a channel, so we place them in a fixed | |
660 // track number instead | |
661 unsigned int metaTrack = lastTrackNum; | |
662 | |
663 // Remember the last non-meta status byte (-1 if we haven't seen one) | |
664 int runningStatus = -1; | |
665 | |
666 bool firstTrack = true; | |
667 | |
668 while (!m_midiFile->eof() && (m_trackByteCount > 0)) { | |
669 | |
670 if (eventCode < 0x80) { | |
671 #ifdef MIDI_DEBUG | |
672 cerr << "WARNING: Invalid event code " << eventCode | |
673 << " in MIDI file" << endl; | |
674 #endif | |
675 throw MIDIException(tr("Invalid event code %1 found").arg(int(eventCode))); | |
676 } | |
677 | |
678 deltaTime = getNumberFromMIDIBytes(); | |
679 | |
680 #ifdef MIDI_DEBUG | |
681 cerr << "read delta time " << deltaTime << endl; | |
682 #endif | |
683 | |
684 // Get a single byte | |
685 midiByte = getMIDIByte(); | |
686 | |
687 if (!(midiByte & MIDI_STATUS_BYTE_MASK)) { | |
688 | |
689 if (runningStatus < 0) { | |
690 throw MIDIException(tr("Running status used for first event in track")); | |
691 } | |
692 | |
693 eventCode = (MIDIByte)runningStatus; | |
694 data1 = midiByte; | |
695 | |
696 #ifdef MIDI_DEBUG | |
697 cerr << "using running status (byte " << int(midiByte) << " found)" << endl; | |
698 #endif | |
699 } else { | |
700 #ifdef MIDI_DEBUG | |
701 cerr << "have new event code " << int(midiByte) << endl; | |
702 #endif | |
703 eventCode = midiByte; | |
704 data1 = getMIDIByte(); | |
705 } | |
706 | |
707 if (eventCode == MIDI_FILE_META_EVENT) { | |
708 | |
709 metaEventCode = data1; | |
710 messageLength = getNumberFromMIDIBytes(); | |
711 | |
712 //#ifdef MIDI_DEBUG | |
713 cerr << "Meta event of type " << int(metaEventCode) << " and " << messageLength << " bytes found, putting on track " << metaTrack << endl; | |
714 //#endif | |
715 metaMessage = getMIDIBytes(messageLength); | |
716 | |
717 long gap = accumulatedTime - trackTimeMap[metaTrack]; | |
718 accumulatedTime += deltaTime; | |
719 deltaTime += gap; | |
720 trackTimeMap[metaTrack] = accumulatedTime; | |
721 | |
722 MIDIEvent *e = new MIDIEvent(deltaTime, | |
723 MIDI_FILE_META_EVENT, | |
724 metaEventCode, | |
725 metaMessage); | |
726 | |
727 m_midiComposition[metaTrack].push_back(e); | |
728 | |
729 if (metaEventCode == MIDI_TRACK_NAME) { | |
730 m_trackNames[metaTrack] = metaMessage.c_str(); | |
731 } | |
732 | |
733 } else { // non-meta events | |
734 | |
735 runningStatus = eventCode; | |
736 | |
737 MIDIEvent *midiEvent; | |
738 | |
739 int channel = (eventCode & MIDI_CHANNEL_NUM_MASK); | |
740 if (channelTrackMap[channel] == -1) { | |
741 if (!firstTrack) ++lastTrackNum; | |
742 else firstTrack = false; | |
743 channelTrackMap[channel] = lastTrackNum; | |
744 } | |
745 | |
746 unsigned int trackNum = channelTrackMap[channel]; | |
747 | |
748 // accumulatedTime is abs time of last event on any track; | |
749 // trackTimeMap[trackNum] is that of last event on this track | |
750 | |
751 long gap = accumulatedTime - trackTimeMap[trackNum]; | |
752 accumulatedTime += deltaTime; | |
753 deltaTime += gap; | |
754 trackTimeMap[trackNum] = accumulatedTime; | |
755 | |
756 switch (eventCode & MIDI_MESSAGE_TYPE_MASK) { | |
757 | |
758 case MIDI_NOTE_ON: | |
759 case MIDI_NOTE_OFF: | |
760 case MIDI_POLY_AFTERTOUCH: | |
761 case MIDI_CTRL_CHANGE: | |
762 data2 = getMIDIByte(); | |
763 | |
764 // create and store our event | |
765 midiEvent = new MIDIEvent(deltaTime, eventCode, data1, data2); | |
766 | |
767 /* | |
768 cerr << "MIDI event for channel " << channel << " (track " | |
769 << trackNum << ")" << endl; | |
770 midiEvent->print(); | |
771 */ | |
772 | |
773 | |
774 m_midiComposition[trackNum].push_back(midiEvent); | |
775 | |
776 if (midiEvent->getChannelNumber() == MIDI_PERCUSSION_CHANNEL) { | |
777 m_percussionTracks.insert(trackNum); | |
778 } | |
779 | |
780 break; | |
781 | |
782 case MIDI_PITCH_BEND: | |
783 data2 = getMIDIByte(); | |
784 | |
785 // create and store our event | |
786 midiEvent = new MIDIEvent(deltaTime, eventCode, data1, data2); | |
787 m_midiComposition[trackNum].push_back(midiEvent); | |
788 break; | |
789 | |
790 case MIDI_PROG_CHANGE: | |
791 case MIDI_CHNL_AFTERTOUCH: | |
792 // create and store our event | |
793 midiEvent = new MIDIEvent(deltaTime, eventCode, data1); | |
794 m_midiComposition[trackNum].push_back(midiEvent); | |
795 break; | |
796 | |
797 case MIDI_SYSTEM_EXCLUSIVE: | |
798 messageLength = getNumberFromMIDIBytes(data1); | |
799 | |
800 #ifdef MIDI_DEBUG | |
801 cerr << "SysEx of " << messageLength << " bytes found" << endl; | |
802 #endif | |
803 | |
804 metaMessage= getMIDIBytes(messageLength); | |
805 | |
806 if (MIDIByte(metaMessage[metaMessage.length() - 1]) != | |
807 MIDI_END_OF_EXCLUSIVE) | |
808 { | |
809 #ifdef MIDI_DEBUG | |
810 cerr << "MIDIFileReader::parseTrack() - " | |
811 << "malformed or unsupported SysEx type" | |
812 << endl; | |
813 #endif | |
814 continue; | |
815 } | |
816 | |
817 // chop off the EOX | |
818 // length fixed by Pedro Lopez-Cabanillas (20030523) | |
819 // | |
820 metaMessage = metaMessage.substr(0, metaMessage.length()-1); | |
821 | |
822 midiEvent = new MIDIEvent(deltaTime, | |
823 MIDI_SYSTEM_EXCLUSIVE, | |
824 metaMessage); | |
825 m_midiComposition[trackNum].push_back(midiEvent); | |
826 break; | |
827 | |
828 case MIDI_END_OF_EXCLUSIVE: | |
829 #ifdef MIDI_DEBUG | |
830 cerr << "MIDIFileReader::parseTrack() - " | |
831 << "Found a stray MIDI_END_OF_EXCLUSIVE" << endl; | |
832 #endif | |
833 break; | |
834 | |
835 default: | |
836 #ifdef MIDI_DEBUG | |
837 cerr << "MIDIFileReader::parseTrack()" | |
838 << " - Unsupported MIDI Event Code: " | |
839 << (int)eventCode << endl; | |
840 #endif | |
841 break; | |
842 } | |
843 } | |
844 } | |
845 | |
846 if (lastTrackNum > metaTrack) { | |
847 for (unsigned int track = metaTrack + 1; track <= lastTrackNum; ++track) { | |
848 m_trackNames[track] = QString("%1 <%2>") | |
849 .arg(m_trackNames[metaTrack]).arg(track - metaTrack + 1); | |
850 } | |
851 } | |
852 | |
853 return true; | |
854 } | |
855 | |
856 // Delete dead NOTE OFF and NOTE ON/Zero Velocity Events after | |
857 // reading them and modifying their relevant NOTE ONs. Return true | |
858 // if there are some notes in this track. | |
859 // | |
860 bool | |
861 MIDIFileReader::consolidateNoteOffEvents(unsigned int track) | |
862 { | |
863 bool notesOnTrack = false; | |
864 bool noteOffFound; | |
865 | |
866 for (MIDITrack::iterator i = m_midiComposition[track].begin(); | |
867 i != m_midiComposition[track].end(); i++) { | |
868 | |
869 if ((*i)->getMessageType() == MIDI_NOTE_ON && (*i)->getVelocity() > 0) { | |
870 | |
871 notesOnTrack = true; | |
872 noteOffFound = false; | |
873 | |
874 for (MIDITrack::iterator j = i; | |
875 j != m_midiComposition[track].end(); j++) { | |
876 | |
877 if (((*j)->getChannelNumber() == (*i)->getChannelNumber()) && | |
878 ((*j)->getPitch() == (*i)->getPitch()) && | |
879 ((*j)->getMessageType() == MIDI_NOTE_OFF || | |
880 ((*j)->getMessageType() == MIDI_NOTE_ON && | |
881 (*j)->getVelocity() == 0x00))) { | |
882 | |
883 (*i)->setDuration((*j)->getTime() - (*i)->getTime()); | |
884 | |
885 delete *j; | |
886 m_midiComposition[track].erase(j); | |
887 | |
888 noteOffFound = true; | |
889 break; | |
890 } | |
891 } | |
892 | |
893 // If no matching NOTE OFF has been found then set | |
894 // Event duration to length of track | |
895 // | |
896 if (!noteOffFound) { | |
897 MIDITrack::iterator j = m_midiComposition[track].end(); | |
898 --j; | |
899 (*i)->setDuration((*j)->getTime() - (*i)->getTime()); | |
900 } | |
901 } | |
902 } | |
903 | |
904 return notesOnTrack; | |
905 } | |
906 | |
907 // Add any tempo events found in the given track to the global tempo map. | |
908 // | |
909 void | |
910 MIDIFileReader::updateTempoMap(unsigned int track) | |
911 { | |
912 std::cerr << "updateTempoMap for track " << track << " (" << m_midiComposition[track].size() << " events)" << std::endl; | |
913 | |
914 for (MIDITrack::iterator i = m_midiComposition[track].begin(); | |
915 i != m_midiComposition[track].end(); ++i) { | |
916 | |
917 if ((*i)->isMeta() && | |
918 (*i)->getMetaEventCode() == MIDI_SET_TEMPO) { | |
919 | |
920 MIDIByte m0 = (*i)->getMetaMessage()[0]; | |
921 MIDIByte m1 = (*i)->getMetaMessage()[1]; | |
922 MIDIByte m2 = (*i)->getMetaMessage()[2]; | |
923 | |
924 long tempo = (((m0 << 8) + m1) << 8) + m2; | |
925 | |
926 std::cerr << "updateTempoMap: have tempo, it's " << tempo << " at " << (*i)->getTime() << std::endl; | |
927 | |
928 if (tempo != 0) { | |
929 double qpm = 60000000.0 / double(tempo); | |
930 m_tempoMap[(*i)->getTime()] = | |
931 TempoChange(RealTime::zeroTime, qpm); | |
932 } | |
933 } | |
934 } | |
935 } | |
936 | |
937 void | |
938 MIDIFileReader::calculateTempoTimestamps() | |
939 { | |
940 unsigned long lastMIDITime = 0; | |
941 RealTime lastRealTime = RealTime::zeroTime; | |
942 double tempo = 120.0; | |
943 int td = m_timingDivision; | |
944 if (td == 0) td = 96; | |
945 | |
946 for (TempoMap::iterator i = m_tempoMap.begin(); i != m_tempoMap.end(); ++i) { | |
947 | |
948 unsigned long mtime = i->first; | |
949 unsigned long melapsed = mtime - lastMIDITime; | |
950 double quarters = double(melapsed) / double(td); | |
951 double seconds = (60.0 * quarters) / tempo; | |
952 | |
953 RealTime t = lastRealTime + RealTime::fromSeconds(seconds); | |
954 | |
955 i->second.first = t; | |
956 | |
957 lastRealTime = t; | |
958 lastMIDITime = mtime; | |
959 tempo = i->second.second; | |
960 } | |
961 } | |
962 | |
963 RealTime | |
964 MIDIFileReader::getTimeForMIDITime(unsigned long midiTime) const | |
965 { | |
966 unsigned long tempoMIDITime = 0; | |
967 RealTime tempoRealTime = RealTime::zeroTime; | |
968 double tempo = 120.0; | |
969 | |
970 TempoMap::const_iterator i = m_tempoMap.lower_bound(midiTime); | |
971 if (i != m_tempoMap.begin()) { | |
972 --i; | |
973 tempoMIDITime = i->first; | |
974 tempoRealTime = i->second.first; | |
975 tempo = i->second.second; | |
976 } | |
977 | |
978 int td = m_timingDivision; | |
979 if (td == 0) td = 96; | |
980 | |
981 unsigned long melapsed = midiTime - tempoMIDITime; | |
982 double quarters = double(melapsed) / double(td); | |
983 double seconds = (60.0 * quarters) / tempo; | |
984 | |
985 /* | |
986 std::cerr << "MIDIFileReader::getTimeForMIDITime(" << midiTime << ")" | |
987 << std::endl; | |
988 std::cerr << "timing division = " << td << std::endl; | |
989 std::cerr << "nearest tempo event (of " << m_tempoMap.size() << ") is at " << tempoMIDITime << " (" | |
990 << tempoRealTime << ")" << std::endl; | |
991 std::cerr << "quarters since then = " << quarters << std::endl; | |
992 std::cerr << "tempo = " << tempo << " quarters per minute" << std::endl; | |
993 std::cerr << "seconds since then = " << seconds << std::endl; | |
994 std::cerr << "resulting time = " << (tempoRealTime + RealTime::fromSeconds(seconds)) << std::endl; | |
995 */ | |
996 | |
997 return tempoRealTime + RealTime::fromSeconds(seconds); | |
998 } | |
999 | |
1000 Model * | |
1001 MIDIFileReader::load() const | |
1002 { | |
1003 if (!isOK()) return 0; | |
1004 | |
1005 if (m_loadableTracks.empty()) { | |
1006 QMessageBox::critical(0, tr("No notes in MIDI file"), | |
1007 tr("MIDI file \"%1\" has no notes in any track") | |
1008 .arg(m_path)); | |
1009 return 0; | |
1010 } | |
1011 | |
1012 std::set<unsigned int> tracksToLoad; | |
1013 | |
1014 if (m_loadableTracks.size() == 1) { | |
1015 | |
1016 tracksToLoad.insert(*m_loadableTracks.begin()); | |
1017 | |
1018 } else { | |
1019 | |
1020 QStringList available; | |
1021 QString allTracks = tr("Merge all tracks"); | |
1022 QString allNonPercussion = tr("Merge all non-percussion tracks"); | |
1023 | |
1024 int nonTrackItems = 1; | |
1025 | |
1026 available << allTracks; | |
1027 | |
1028 if (!m_percussionTracks.empty() && | |
1029 (m_percussionTracks.size() < m_loadableTracks.size())) { | |
1030 available << allNonPercussion; | |
1031 ++nonTrackItems; | |
1032 } | |
1033 | |
1034 for (set<unsigned int>::iterator i = m_loadableTracks.begin(); | |
1035 i != m_loadableTracks.end(); ++i) { | |
1036 | |
1037 unsigned int trackNo = *i; | |
1038 QString label; | |
1039 | |
1040 QString perc; | |
1041 if (m_percussionTracks.find(trackNo) != m_percussionTracks.end()) { | |
1042 perc = tr(" - uses GM percussion channel"); | |
1043 } | |
1044 | |
1045 if (m_trackNames.find(trackNo) != m_trackNames.end()) { | |
1046 label = tr("Track %1 (%2)%3") | |
1047 .arg(trackNo).arg(m_trackNames.find(trackNo)->second) | |
1048 .arg(perc); | |
1049 } else { | |
1050 label = tr("Track %1 (untitled)%3").arg(trackNo).arg(perc); | |
1051 } | |
1052 available << label; | |
1053 } | |
1054 | |
1055 bool ok = false; | |
1056 QString selected = QInputDialog::getItem | |
1057 (0, tr("Select track or tracks to import"), | |
1058 tr("You can only import this file as a single annotation layer,\nbut the file contains more than one track,\nor notes on more than one channel.\n\nPlease select the track or merged tracks you wish to import:"), | |
1059 available, 0, false, &ok); | |
1060 | |
1061 if (!ok || selected.isEmpty()) return 0; | |
1062 | |
1063 if (selected == allTracks || selected == allNonPercussion) { | |
1064 | |
1065 for (set<unsigned int>::iterator i = m_loadableTracks.begin(); | |
1066 i != m_loadableTracks.end(); ++i) { | |
1067 | |
1068 if (selected == allTracks || | |
1069 m_percussionTracks.find(*i) == m_percussionTracks.end()) { | |
1070 | |
1071 tracksToLoad.insert(*i); | |
1072 } | |
1073 } | |
1074 | |
1075 } else { | |
1076 | |
1077 int j = nonTrackItems; | |
1078 | |
1079 for (set<unsigned int>::iterator i = m_loadableTracks.begin(); | |
1080 i != m_loadableTracks.end(); ++i) { | |
1081 | |
1082 if (selected == available[j]) { | |
1083 tracksToLoad.insert(*i); | |
1084 break; | |
1085 } | |
1086 | |
1087 ++j; | |
1088 } | |
1089 } | |
1090 } | |
1091 | |
1092 if (tracksToLoad.empty()) return 0; | |
1093 | |
1094 size_t n = tracksToLoad.size(), count = 0; | |
1095 Model *model = 0; | |
1096 | |
1097 for (std::set<unsigned int>::iterator i = tracksToLoad.begin(); | |
1098 i != tracksToLoad.end(); ++i) { | |
1099 | |
1100 int minProgress = (100 * count) / n; | |
1101 int progressAmount = 100 / n; | |
1102 | |
1103 model = loadTrack(*i, model, minProgress, progressAmount); | |
1104 | |
1105 ++count; | |
1106 } | |
1107 | |
1108 if (dynamic_cast<NoteModel *>(model)) { | |
1109 dynamic_cast<NoteModel *>(model)->setCompletion(100); | |
1110 } | |
1111 | |
1112 return model; | |
1113 } | |
1114 | |
1115 Model * | |
1116 MIDIFileReader::loadTrack(unsigned int trackToLoad, | |
1117 Model *existingModel, | |
1118 int minProgress, | |
1119 int progressAmount) const | |
1120 { | |
1121 if (m_midiComposition.find(trackToLoad) == m_midiComposition.end()) { | |
1122 return 0; | |
1123 } | |
1124 | |
1125 NoteModel *model = 0; | |
1126 | |
1127 if (existingModel) { | |
1128 model = dynamic_cast<NoteModel *>(existingModel); | |
1129 if (!model) { | |
1130 std::cerr << "WARNING: MIDIFileReader::loadTrack: Existing model given, but it isn't a NoteModel -- ignoring it" << std::endl; | |
1131 } | |
1132 } | |
1133 | |
1134 if (!model) { | |
1135 model = new NoteModel(m_mainModelSampleRate, 1, 0.0, 0.0, false); | |
1136 model->setValueQuantization(1.0); | |
1137 } | |
1138 | |
1139 const MIDITrack &track = m_midiComposition.find(trackToLoad)->second; | |
1140 | |
1141 size_t totalEvents = track.size(); | |
1142 size_t count = 0; | |
1143 | |
1144 bool minorKey = false; | |
1145 bool sharpKey = true; | |
1146 | |
1147 for (MIDITrack::const_iterator i = track.begin(); i != track.end(); ++i) { | |
1148 | |
1149 RealTime rt = getTimeForMIDITime((*i)->getTime()); | |
1150 | |
1151 // We ignore most of these event types for now, though in | |
1152 // theory some of the text ones could usefully be incorporated | |
1153 | |
1154 if ((*i)->isMeta()) { | |
1155 | |
1156 switch((*i)->getMetaEventCode()) { | |
1157 | |
1158 case MIDI_KEY_SIGNATURE: | |
1159 minorKey = (int((*i)->getMetaMessage()[1]) != 0); | |
1160 sharpKey = (int((*i)->getMetaMessage()[0]) >= 0); | |
1161 break; | |
1162 | |
1163 case MIDI_TEXT_EVENT: | |
1164 case MIDI_LYRIC: | |
1165 case MIDI_TEXT_MARKER: | |
1166 case MIDI_COPYRIGHT_NOTICE: | |
1167 case MIDI_TRACK_NAME: | |
1168 // The text events that we could potentially use | |
1169 break; | |
1170 | |
1171 case MIDI_SET_TEMPO: | |
1172 // Already dealt with in a separate pass previously | |
1173 break; | |
1174 | |
1175 case MIDI_TIME_SIGNATURE: | |
1176 // Not yet! | |
1177 break; | |
1178 | |
1179 case MIDI_SEQUENCE_NUMBER: | |
1180 case MIDI_CHANNEL_PREFIX_OR_PORT: | |
1181 case MIDI_INSTRUMENT_NAME: | |
1182 case MIDI_CUE_POINT: | |
1183 case MIDI_CHANNEL_PREFIX: | |
1184 case MIDI_SEQUENCER_SPECIFIC: | |
1185 case MIDI_SMPTE_OFFSET: | |
1186 default: | |
1187 break; | |
1188 } | |
1189 | |
1190 } else { | |
1191 | |
1192 switch ((*i)->getMessageType()) { | |
1193 | |
1194 case MIDI_NOTE_ON: | |
1195 | |
1196 if ((*i)->getVelocity() == 0) break; // effective note-off | |
1197 else { | |
1198 RealTime endRT = getTimeForMIDITime((*i)->getTime() + | |
1199 (*i)->getDuration()); | |
1200 | |
1201 long startFrame = RealTime::realTime2Frame | |
1202 (rt, model->getSampleRate()); | |
1203 | |
1204 long endFrame = RealTime::realTime2Frame | |
1205 (endRT, model->getSampleRate()); | |
1206 | |
1207 QString pitchLabel = Pitch::getPitchLabel((*i)->getPitch(), | |
1208 0, | |
1209 !sharpKey); | |
1210 | |
1211 QString noteLabel = tr("%1 - vel %2") | |
1212 .arg(pitchLabel).arg(int((*i)->getVelocity())); | |
1213 | |
1214 Note note(startFrame, (*i)->getPitch(), | |
1215 endFrame - startFrame, noteLabel); | |
1216 | |
1217 // std::cerr << "Adding note " << startFrame << "," << (endFrame-startFrame) << " : " << int((*i)->getPitch()) << std::endl; | |
1218 | |
1219 model->addPoint(note); | |
1220 break; | |
1221 } | |
1222 | |
1223 case MIDI_PITCH_BEND: | |
1224 // I guess we could make some use of this... | |
1225 break; | |
1226 | |
1227 case MIDI_NOTE_OFF: | |
1228 case MIDI_PROG_CHANGE: | |
1229 case MIDI_CTRL_CHANGE: | |
1230 case MIDI_SYSTEM_EXCLUSIVE: | |
1231 case MIDI_POLY_AFTERTOUCH: | |
1232 case MIDI_CHNL_AFTERTOUCH: | |
1233 break; | |
1234 | |
1235 default: | |
1236 break; | |
1237 } | |
1238 } | |
1239 | |
1240 model->setCompletion(minProgress + | |
1241 (count * progressAmount) / totalEvents); | |
1242 ++count; | |
1243 } | |
1244 | |
1245 return model; | |
1246 } | |
1247 | |
1248 |