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