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