Just Intonation  Version 1.3.1 (19)
Explore key-independent dynamically adapting tuning in just intonation
midiplayerparser.cpp
Go to the documentation of this file.
1 /*****************************************************************************
2  * Copyright 2016-2017 Karolin Stange, Christoph Wick, and Haye Hinrichsen
3  *
4  * This file is part of JustIntonation.
5  *
6  * JustIntonation is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by the
8  * Free Software Foundation, either version 3 of the License, or (at your
9  * option) any later version.
10  *
11  * JustIntonation is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14  * for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with JustIntonation. If not, see http://www.gnu.org/licenses/.
18  *****************************************************************************/
19 
20 //=============================================================================
21 // Midi file parser
22 //=============================================================================
23 
24 #include "midiplayerparser.h"
25 
26 #include <QDebug>
27 
28 #include "midiplayer.h"
29 #include "midiplayereventlist.h"
30 
31 //-----------------------------------------------------------------------------
32 // Constructor
33 //-----------------------------------------------------------------------------
34 
41 
43  MidiPlayerEventList *eventlist,
44  int verbosity)
45  : pMidiPlayer(player)
46  , pEventList(eventlist)
47  , mCursor(0)
48  , SMPTE(false)
49  , mLastMidiCommand(0)
50  , mVerbosity(verbosity)
51 
52 {}
53 
54 
55 //-----------------------------------------------------------------------------
56 // Parse main function
57 //-----------------------------------------------------------------------------
58 
70 
71 bool MidiPlayerParser::parse(QByteArray &data)
72 {
73  if (not pEventList) return false;
74  if (mVerbosity >= 3) qDebug() << "parsing data file with" << data.length() << "bytes";
75  pEventList->clear();
76 
77  // First look for the header chunk
78  if (findNextChunk(data,"MThd") != 6)
79  { reportError("Could not find MThd header"); return false; }
80  if (mCursor+6 >= data.size())
81  { reportError("Could not read MThd header"); return false; }
82  quint16 format = parseInt16(data);
83  quint16 tracks = parseInt16(data);
84  quint16 division = parseInt16(data);
85 
86  // Filter supported formats
87  if (format > 2) { reportError("Unknown format in MThd header"); return false; }
88  if (format == 2) { reportError("Sequential track format not supported"); return false; }
89  if (tracks == 0) { reportError("No tracks in MIDI file"); return false; }
90  if (tracks >= 256) { reportError("Too many tracks"); return false; }
91  SMPTE = ((division & 0x8000U) != 0);
92 
93  // Report identified track to qDebug
94  if (mVerbosity >= 3)
95  qDebug() << "Detected" << (format==0 ? "single" : "multi") << "channel MIDI data with"
96  << tracks << "tracks in" << (SMPTE ? "absolute SMPTE-based" : "tempo-based") << "division";
97 
98  // If SMPTE (absolute times) calculate ticks per quarter now
99  if (SMPTE)
100  {
101  qint8 negFrames = division>>8;
102  int frames = abs(negFrames);
103  int ticksPerFrame = division & 0xFFU;
104  double msecPerDeltaTick = 1000.0 / frames / ticksPerFrame;
105  if (pMidiPlayer) pMidiPlayer->receiveTempoData(true,msecPerDeltaTick);
106  if (mVerbosity >= 3) qDebug() << "SMPTE: frames =" << frames << "/ Tick per frame"
107  << ticksPerFrame << "/ msec per tick =" << msecPerDeltaTick;
108  }
109  else // if tempo-based
110  {
111  int ticksPerQuarterNote = division;
112  if (mVerbosity >= 3) qDebug() << "Midi file is tempo-based with"
113  << ticksPerQuarterNote << "ticks per quarter note";
114  if (pMidiPlayer) pMidiPlayer->receiveTempoData(false,ticksPerQuarterNote);
115  }
116 
117  // Loop over all tracks
118  for (int track=0; track<tracks; track++)
119  {
120  int length = findNextChunk(data,"MTrk");
121  if (length == 0) { reportError("Could not read MThd header"); return false; }
122  if (not parseTrack(data,length,track))
123  { reportError("Could not read track " + QString::number(track)); }
124  }
125  //pEventList->writeListInReadableForm("/home/hinrichsen/eventlist.txt");
126  return true;
127 }
128 
129 
130 //-----------------------------------------------------------------------------
131 // Find the next chunk tag
132 //-----------------------------------------------------------------------------
133 
145 
146 int MidiPlayerParser::findNextChunk (QByteArray &data, QString tag)
147 {
148  QByteArray pattern(tag.toStdString().c_str());
149  int position = data.indexOf(pattern,mCursor);
150  if (position<0 or position>=data.size()-8) return 0;
151  mCursor = position + 4;
152  int length = parseInt32(data);
153  if (mVerbosity >= 3)
154  qDebug() << "Chunk" << tag << "found at position"
155  << position << "with" << length << "bytes";
156  return length;
157 }
158 
159 
160 //-----------------------------------------------------------------------------
161 // Parse a 32bit integer
162 //-----------------------------------------------------------------------------
163 
169 
170 quint32 MidiPlayerParser::parseInt32 (QByteArray &data)
171 {
172  mCursor += 4;
173  if (mCursor > data.size())
174  { reportError("Reading int32 beyond EOF"); return 0; }
175  quint32 byte0 =(quint8)data[mCursor-1];
176  quint32 byte1 =(quint8)data[mCursor-2];
177  quint32 byte2 =(quint8)data[mCursor-3];
178  quint32 byte3 =(quint8)data[mCursor-4];
179  quint32 result = byte0 + (byte1<<8) + (byte2<<16) + (byte3<<24);
180  return result;
181 }
182 
183 
184 //-----------------------------------------------------------------------------
185 // Parse a 16bit integer
186 //-----------------------------------------------------------------------------
187 
193 
194 quint16 MidiPlayerParser::parseInt16 (QByteArray &data)
195 {
196  mCursor += 2;
197  if (mCursor > data.size())
198  { reportError("Reading int16 beyond EOF"); return 0; }
199  quint16 lsb = (quint8)data[mCursor-1];
200  quint16 msb = (quint8)data[mCursor-2];
201  quint16 result = lsb + (msb<<8);
202  return result;
203 }
204 
205 
206 //-----------------------------------------------------------------------------
207 // Parse a variable-length integer
208 //-----------------------------------------------------------------------------
209 
220 
222 {
223  quint32 number = 0;
224  do
225  {
226  mCursor++;
227  if (mCursor > data.size()) return 0;
228  number <<= 7;
229  number += (data[mCursor-1] & 0x7FU);
230  }
231  while ((data[mCursor-1] & 0x80U) != 0);
232  return number;
233 }
234 
235 
236 //-----------------------------------------------------------------------------
237 // Parse an entire track
238 //-----------------------------------------------------------------------------
239 
251 
252 bool MidiPlayerParser::parseTrack (QByteArray &data, int length, quint8 track)
253 {
254  mLastMidiCommand = 0;
255  int finalPosition = mCursor + length;
256  double cumulativeDelta = 0;
257  do
258  {
259  quint32 delta = parseVariableLengthNumber(data);
260  // The following small offset guarantees the correct order in the list
261  cumulativeDelta += delta + 0.0000001;
262  if (not parseEvent(data, track, delta, cumulativeDelta)) return false;
263  if (mCursor > finalPosition) return false;
264  }
265  while (mCursor < finalPosition);
266  return true;
267 }
268 
269 
270 //-----------------------------------------------------------------------------
271 // Parse a single event
272 //-----------------------------------------------------------------------------
273 
285 
286 bool MidiPlayerParser::parseEvent (QByteArray &data, quint8 track,
287  quint32 delta, double cumulativeDelta)
288 {
289  // Read the command byte
290  if (mCursor >= data.size()) return false;
291  quint8 command = data[mCursor];
292  mCursor++;
293 
294  // Midi commands start with bit7 set. Sometimes this convention is broken in
295  // the following sense: If the parser expects a Midi command but finds
296  // instead a byte with bit7=0, then the last Midi command is used.
297  if ((command & 0x80U) == 0 and (mLastMidiCommand & 0x80U) > 0)
298  {
299  command = mLastMidiCommand;
300  mCursor--;
301  }
302  else mLastMidiCommand = command;
303 
304  // Identify the type of the event
305  enum commandType {undefined,ignore,onebyte,twobytes,sysex,meta};
306  commandType type = undefined;
307  if (command <= 0xBFU) type = twobytes; // 80-BF
308  else if (command <= 0xDFU) type = onebyte; // C0-DF
309  else if (command <= 0xEFU) type = twobytes; // E0-EF
310  else if (command == 0xF0U) type = sysex;
311  else if (command == 0xF2U) type = twobytes;
312  else if (command == 0xF3U) type = onebyte;
313  else if (command == 0xFFU) type = meta;
314  else if (mVerbosity >= 3) qDebug() << "MidiParser: Unrecognized command code =" << command
315  << " ... assuming it to be 1-byte dummy code";
316 
317  // Take action accordingly
318  switch(type)
319  {
320  case undefined:
321  if (mVerbosity >= 2) qWarning() << "WARNING: MidiParser: Unrecognized command code =" << command;
322  for (int i=-10; i<10; i++) qDebug() << (quint8)(data[mCursor+i]);
323  return false;
324  case ignore:
325  return true;
326  case onebyte:
327  {
328  if (mCursor >= data.size()) return false;
329  MidiPlayerEvent event(track,delta,command,data[mCursor],0);
330  if (pEventList) pEventList->insert(cumulativeDelta,event);
331  mCursor++;
332  return true;
333  }
334  case twobytes:
335  {
336  if (mCursor+1 >= data.size()) return false;
337  MidiPlayerEvent event(track,delta,command,data[mCursor],data[mCursor+1]);
338  if (pEventList) pEventList->insert(cumulativeDelta,event);
339  mCursor+=2;
340  return true;
341  }
342  case sysex:
343  qDebug() << "SYSEX NOT YET IMPLEMENTED";
344  return false;
345  case meta:
346  return parseMetaEvent(data,track,cumulativeDelta);
347  }
348  return false;
349 }
350 
351 
352 //-----------------------------------------------------------------------------
353 // Parse a meta event and display info in qDebug
354 //-----------------------------------------------------------------------------
355 
363 
364 bool MidiPlayerParser::parseMetaEvent (QByteArray &data, quint8 track, double cumulativeDelta)
365 {
366  if (mCursor >= data.size()) return false;
367  quint8 command = data[mCursor];
368  mCursor++;
369  quint32 length = parseVariableLengthNumber(data);
370  if (command == 0x2FU and length == 0) // EOT
371  {
372  if (mVerbosity >= 3) qDebug() << "MidiPlayerParser: End of track detected as meta event";
373  }
374  else if (command >= 0x01U and command <= 0x07U) // Text message
375  {
376  QByteArray sub = data.mid(mCursor,length);
377  if (mVerbosity >= 3) qDebug() << "MidiPlayerParser: Track info:\n" << sub.data();
378  }
379  else if (command == 0x51 and length == 3) // Tempo setting
380  {
381  quint32 byte0 = (quint8)data[mCursor+2];
382  quint32 byte1 = (quint8)data[mCursor+1];
383  quint32 byte2 = (quint8)data[mCursor];
384  quint32 result = byte0 + (byte1<<8) + (byte2<<16);
385 
386  int millisecondsPerQuarterNote = result / 1000;
387  if (mVerbosity >= 3) qDebug() << "MidiPlayerParser: Tempo:"
388  << millisecondsPerQuarterNote << "msec per quarter note";
389 
390  // Custom Non-Midi event transmitting tempo changes
391  MidiPlayerEvent event(track,0,0xFF,
392  millisecondsPerQuarterNote >> 8,
393  millisecondsPerQuarterNote & 0xFF);
394  if (pEventList) pEventList->insert(cumulativeDelta,event);
395  }
396  else
397  {
398  if (mVerbosity >= 4) qDebug() << "Skipping meta event";
399  }
400  mCursor += length;
401  if (mCursor > data.size()) return false;
402  return true;
403 }
404 
405 
406 //-----------------------------------------------------------------------------
407 // Report an error back to MidiPlayer
408 //-----------------------------------------------------------------------------
409 
414 
415 void MidiPlayerParser::reportError(QString msg) const
416 {
418 }
419 
420 
quint32 parseInt32(QByteArray &data)
Parse a 32-bit (4-byte) integer and advance the cursor.
bool parseEvent(QByteArray &data, quint8 track, quint32 delta, double cumulativeDelta)
Parse a single event In the Midi specifications three types of events are defined, namely, ordinary Midi events, sysex events, and meta events. This function detects the event type and takes action accordingly.
void reportError(QString msg)
Report an error.
Definition: midiplayer.cpp:321
void receiveTempoData(bool smpte, double parameter)
Receive tempo data from the parser.
Definition: midiplayer.cpp:384
bool parseTrack(QByteArray &data, int length, quint8 track)
Parse an entire track.
Midi Player.
Definition: midiplayer.h:84
Structure used internally in the MidiPlayer to hold a Midi event.
bool SMPTE
Flag indicating the time format.
int mCursor
Cursor from where the events are being played.
int mVerbosity
Level of verbosity of qDebug() messages.
MidiPlayerParser(MidiPlayer *player, MidiPlayerEventList *eventlist, int verbosity=0)
Constructor.
quint8 mLastMidiCommand
Variable storing the last Midi command.
void reportError(QString msg) const
Report an error back to MidiPlayer.
bool parse(QByteArray &data)
Parse a Midi file and store it in the EventList.
Class managing the EventList in the MidiPlayer.
quint32 parseVariableLengthNumber(QByteArray &data)
Parse a variable-length integer.
int findNextChunk(QByteArray &data, QString tag)
Find the next chunk tag.
void insert(double time, const MidiPlayerEvent &event)
Insert a new event in the time-ordered list of events.
MidiPlayer * pMidiPlayer
Pointer back to the MidiPlayer.
MidiPlayerEventList * pEventList
Pointer to event list held by MidiPlayer.
bool parseMetaEvent(QByteArray &data, quint8 track, double cumulativeDelta)
Parse a meta event and display info in qDebug.
quint16 parseInt16(QByteArray &data)
Parse a 16bit (2-byte) integer and advance the cursor.