Just Intonation  Version 1.3.0 (18)
Explore scale-independent dynamically adapting tuning in just intonation
midiplayer.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 Player
22 //=============================================================================
23 
24 #include "midiplayer.h"
25 #include <QUrl>
26 #include <QFile>
27 
28 //-----------------------------------------------------------------------------
29 // Constructor
30 //-----------------------------------------------------------------------------
34 
36  : mEventList()
37  , mCurrentlyPlaying(false) // Flag whether player is actually playing
38  , mRepeatMode(false) // Flag indicating that playing should be repeated
39  , mPlayAgainAfterSuspend(false) // Flag for playing after resuming from suspend
40  , mTimerActive(false) // Flag indicating that player is waiting for timeout
41  , mOverallTempoFactor(1) // Time scaling factor (1 = 100% = recorded tempo)
42  , mDeltaTicks(0) // Elapsed time units (ticks) since the last event
43  , mMillisecondsPerTick(10) // Calculated number of milliseconds per tick
44  , mTicksPerQuarterNote(100) // Calculated number of ticks per quarter note
45  , mAccumulatedTime(0) // Accumulated time since last progress signal
46 
47 {
48  setThreadName("MidiPlayer");
49  setModuleName("Midiplayer");
50 
51  // The following helperSingal for reading Midi files is used to decouple the threads
53 }
54 
55 
56 //-----------------------------------------------------------------------------
57 // Stop player
58 //-----------------------------------------------------------------------------
59 
67 
69 {
71  mEventList.clear();
72  return ThreadBase::stop();
73 }
74 
75 
76 //-----------------------------------------------------------------------------
77 // Load Midi file
78 //-----------------------------------------------------------------------------
87 
88 void MidiPlayer::loadFile (QString filename, bool autostart)
89 {
90  LOGMESSAGE << "Request for loading file" << filename;
91  if (filename.size() == 0) return;
92  allNotesOff();
93  rewind();
94  emit helperSignalToReadMidiFile(filename,autostart);
95 }
96 
97 //-----------------------------------------------------------------------------
98 // Load Midi file
99 //-----------------------------------------------------------------------------
107 
108 void MidiPlayer::loadUrl(QString url)
109 {
110  QUrl URL(url);
111  QString filename = URL.toLocalFile();
112  if (filename.endsWith(".mid") or
113  filename.endsWith(".MID") or
114  filename.endsWith(".midi"))
115  loadFile (filename,true);
116  else LOGMESSAGE << "No Midi file:" << filename;
117 }
118 
119 
120 //-----------------------------------------------------------------------------
121 // Set tempo scale factor
122 //-----------------------------------------------------------------------------
127 
128 void MidiPlayer::setTempo(double factor)
129 {
130  LOGMESSAGE << "MidiPlayer: Set tempo factor to value" << factor;
131  setTempoFactor(factor);
132 }
133 
134 
135 //-----------------------------------------------------------------------------
136 // Set progress manually
137 //-----------------------------------------------------------------------------
142 
143 void MidiPlayer::setMidiProgress(double percent)
144 {
145  LOGMESSAGE << "Setting progress in percent manually:" << percent;
146  allNotesOff();
147  mEventList.setProgress(percent);
148 }
149 
150 
151 //-----------------------------------------------------------------------------
152 // Rewind
153 //-----------------------------------------------------------------------------
157 
159 {
160  allNotesOff();
161  mEventList.rewind();
162  emit signalProgressInPercent(0);
163 }
164 
165 
166 //-----------------------------------------------------------------------------
167 // Toggle between play and pause
168 //-----------------------------------------------------------------------------
172 
174 {
175  if (isPlaying()) pause();
176  else play();
177 }
178 
179 
180 //-----------------------------------------------------------------------------
181 // Switch repeat mode on and off
182 //-----------------------------------------------------------------------------
186 
188 {
189  LOGMESSAGE << "Repeat mode on =" << on;
190  mRepeatMode = on;
191 }
192 
193 
194 //-----------------------------------------------------------------------------
195 // Start playing
196 //-----------------------------------------------------------------------------
202 
204 {
205  if (mEventList.atEnd()) rewind();
206  mCurrentlyPlaying = true;
207  // Start playing after 100 msec
208  if (not mTimerActive)
209  {
210  QTimer::singleShot(100,this,&MidiPlayer::midiTimeout);
211  mTimerActive = true;
212  }
214 }
215 
216 
217 
218 //-----------------------------------------------------------------------------
219 // Pause
220 //-----------------------------------------------------------------------------
227 
229 {
230  LOGMESSAGE << "Pause";
231  mCurrentlyPlaying = false;
232  allNotesOff();
234 }
235 
236 
237 //-----------------------------------------------------------------------------
238 // All notes off
239 //-----------------------------------------------------------------------------
243 
245 {
246  // Send all notes off on all Midi channels
247  for (quint8 command = 0xB0U; command < 0xC0U; command++)
248  {
249  LOGSTATUS << "signal Midi Event:" << "\t\t\t"
250  << QString::number(command,16) << QString::number(123,16) << "0";
251  signalMidiEvent (QMidiMessage(command, 123, 0, 0.0));
252  }
253 }
254 
255 //-----------------------------------------------------------------------------
256 // Find out whether the player is playing
257 //-----------------------------------------------------------------------------
262 
264 {
265  return mCurrentlyPlaying;
266 }
267 
268 //-----------------------------------------------------------------------------
269 // Suspend playing
270 //-----------------------------------------------------------------------------
274 
276 {
277  LOGMESSAGE << "Entering suspend mode";
279  pause();
280 }
281 
282 
283 //-----------------------------------------------------------------------------
284 // Resume from suspend mode
285 //-----------------------------------------------------------------------------
289 
291 {
292  LOGMESSAGE << "Resuming from suspend mode";
294  mPlayAgainAfterSuspend = false;
295 }
296 
297 
298 //-----------------------------------------------------------------------------
299 // Modify tempo factor
300 //-----------------------------------------------------------------------------
305 
306 void MidiPlayer::setTempoFactor(double factor)
307 { if (factor>0) mOverallTempoFactor = factor; }
308 
309 
310 //-----------------------------------------------------------------------------
311 // Report an error
312 //-----------------------------------------------------------------------------
320 
321 void MidiPlayer::reportError(QString msg)
322 {
323  LOGERROR<< msg;
324  emit signalError(msg);
325 }
326 
327 
328 //-----------------------------------------------------------------------------
329 // Read and parse a Midi file
330 //-----------------------------------------------------------------------------
338 
339 void MidiPlayer::readAndParseMidiFile (QString filename, bool autostart)
340 {
341  LOGMESSAGE << "MidiPlayer load file:" << filename;
342 
343  // Open file and read its complete content into data
344  QFile file(filename);
345  if (not file.open(QIODevice::ReadOnly))
346  { reportError("Cannot open Midi file "+filename); return; }
347  mEventList.clear();
348  QByteArray data = file.readAll();
349 
350  // Create the parser instance
351  MidiPlayerParser parser (this,&mEventList,getVerbosity());
352 
353  // Parse file content
354  LOGMESSAGE << "MidiPlayer start parsing...";
355  parser.parse(data);
356  LOGMESSAGE << "MidiPlayer parsing complete";
357 
358  // Disconnect signals, exit
359  mEventList.rewind();
360  if (autostart) play();
361  const QStringList parts = filename.split("/");
362  const QString barefilename = parts.at(parts.size()-1);
363  emit setDisplayedMidiFilename(barefilename);
364  mDeltaTicks = 0;
365 }
366 
367 
368 //-----------------------------------------------------------------------------
369 // Receive tempo data from the parser
370 //-----------------------------------------------------------------------------
383 
384 void MidiPlayer::receiveTempoData(bool smpte, double parameter)
385 {
386  if (smpte) mMillisecondsPerTick = parameter;
387  else mTicksPerQuarterNote = parameter;
388 }
389 
390 
391 //-----------------------------------------------------------------------------
392 // midiTimeout
393 //-----------------------------------------------------------------------------
403 
405 {
406  mTimerActive = false;
407  if (not mCurrentlyPlaying) return;
408  if (mEventList.atEnd()) { pause(); return; }
409 
410  quint32 currentTicks = mEventList.getCumulativeTime();
412 
413  // consider special meta-event for tempo change:
414  if (event.command == 0xFFU)
415  {
416  double millisecondsPerQuarterNote = 256.0*event.byte1 + event.byte2;
417  LOGMESSAGE << "MidiPlayer tempo change:"
418  << millisecondsPerQuarterNote << "msec per quarter note";
419  if (mTicksPerQuarterNote>0)
420  {
421  mMillisecondsPerTick = millisecondsPerQuarterNote / mTicksPerQuarterNote;
422  LOGMESSAGE << "MidiPlayer: Now "
423  << mMillisecondsPerTick << "msec per delta tick";
424  }
425  }
426  else // emit all other MIDI events to the player
427  if (event.command < 0xE0 or event.command >= 0xF0) // except for pitch bends
428  // because pitch bends are sometimes used to implement Midi in historical temperaments.
429  {
430  //double milliseconds = mOverallTempoFactor * mDeltaTicks * mMillisecondsPerTick;
431  LOGSTATUS << "signal Midi Event:" << "\t\t\t"
432  << QString::number (event.command,16)
433  << QString::number (event.byte1,16)
434  << QString::number (event.byte2,16);
435  signalMidiEvent (QMidiMessage(event.command, event.byte1, event.byte2));
436  }
437 
439  if (mEventList.atEnd())
440  {
441  // if in repeat mode play the file again, else stop.
442  if (mRepeatMode) play();
443  else { pause(); return; }
444  }
445 
446  quint32 nextTicks = mEventList.getCumulativeTime();
447  mDeltaTicks = nextTicks - currentTicks;
448  if (mDeltaTicks>0)
449  {
450  double milliseconds = mOverallTempoFactor * mDeltaTicks * mMillisecondsPerTick;
451  LOGSTATUS << "Let the timer wait for" << milliseconds << "msec";
452  QTimer::singleShot(milliseconds,this,&MidiPlayer::midiTimeout);
453  mTimerActive = true;
454  if (mAccumulatedTime > 500)
455  {
457  mAccumulatedTime = 0;
458  }
459  mAccumulatedTime += milliseconds;
460  }
461  else midiTimeout();
462 }
463 
bool mPlayAgainAfterSuspend
Flag for playing after resume.
Definition: midiplayer.h:128
void resume()
Resume from suspend mode.
Definition: midiplayer.cpp:290
quint32 getCumulativeTime() const
Return time of actual event (zero if iterator is at the end of the list).
void play()
Start or resume playing.
Definition: midiplayer.cpp:203
void setModuleName(const QString &name)
Specify the name of the class-specific module.
Definition: log.cpp:82
void reportError(QString msg)
Report an error.
Definition: midiplayer.cpp:321
void rewind()
Rewind: Move the iterator of the event list to the beginning.
Definition: midiplayer.cpp:158
void receiveTempoData(bool smpte, double parameter)
Receive tempo data from the parser.
Definition: midiplayer.cpp:384
void setDisplayedMidiFilename(QVariant filename)
bool mTimerActive
Flag indicating that player is waiting for timeout.
Definition: midiplayer.h:129
double mOverallTempoFactor
Time scaling factor (1 = 100% = recorded tempo)
Definition: midiplayer.h:130
int getVerbosity()
Get verbosity level.
Definition: threadbase.h:80
MidiPlayer()
Constructor.
Definition: midiplayer.cpp:35
void signalError(QVariant msg)
void setTempo(double factor)
Set tempo scale factor.
Definition: midiplayer.cpp:128
#define LOGMESSAGE
Definition: log.h:43
quint8 byte1
Midi first argument byte.
void advance()
Advance iterator in the list of events.
bool isPlaying() const
Find out whether the player is playing.
Definition: midiplayer.cpp:263
MidiPlayerEventList mEventList
Definition: midiplayer.h:125
void signalPlayingStatusChanged(QVariant playing)
void clear()
Clear the event list (clear all Midi data).
bool atEnd() const
Predicate, telling us whether we are already at the end of the list.
Structure used internally in the MidiPlayer to hold a Midi event.
quint64 mDeltaTicks
Elapsed time units (ticks) since the last event.
Definition: midiplayer.h:131
void loadFile(QString filename, bool autostart=false)
Load a midi file (*.mid) from disk.
Definition: midiplayer.cpp:88
bool mRepeatMode
Mode indicating that file shall be repeated.
Definition: midiplayer.h:127
const MidiPlayerEvent getEvent() const
Return a copy of the actual event (zero event if iterator is at the end of the list).
void loadUrl(QString url)
Load a midi file (*.mid) from disk (URL-Version)
Definition: midiplayer.cpp:108
bool mCurrentlyPlaying
Flag whether player is actually playing.
Definition: midiplayer.h:126
void suspend()
Suspend (stop playing, thread idle)
Definition: midiplayer.cpp:275
void setRepeatMode(bool on)
Switch repeat mode on and off.
Definition: midiplayer.cpp:187
void helperSignalToReadMidiFile(QString filename, bool autostart)
#define LOGERROR
Definition: log.h:45
void pause()
Pause: Interrupt playing.
Definition: midiplayer.cpp:228
void rewind()
Rewind: Move the iterator back to the beginning of the Midi song.
void setTempoFactor(double tempo)
Modify the tempo scale factor.
Definition: midiplayer.cpp:306
void signalProgressInPercent(QVariant percent)
void midiTimeout()
Timer midiTimeout.
Definition: midiplayer.cpp:404
double getProgressInPercent() const
Get the playing progress in percent.
Midi file parser.
double mMillisecondsPerTick
Calculated number of milliseconds per tick.
Definition: midiplayer.h:132
#define LOGSTATUS
Definition: log.h:42
void allNotesOff()
Turn all notes off.
Definition: midiplayer.cpp:244
double mAccumulatedTime
Accumulated time since last progress signal.
Definition: midiplayer.h:134
void togglePlayPause()
Toggle between play and pause.
Definition: midiplayer.cpp:173
double mTicksPerQuarterNote
Calculated number of ticks per quarter note.
Definition: midiplayer.h:133
void setProgress(double percent)
Set the EventList iterator according to a given progress in percent.
void setMidiProgress(double percent)
Set progress in pecent manually.
Definition: midiplayer.cpp:143
void setThreadName(const QString name)
Set thread name (Linux only)
Definition: threadbase.cpp:121
bool stop()
Stop the player.
Definition: midiplayer.cpp:68
bool parse(QByteArray &data)
Parse a Midi file and store it in the EventList.
void signalMidiEvent(QMidiMessage event)
virtual bool stop()
Stop the thread.
Definition: threadbase.cpp:152
void readAndParseMidiFile(QString filename, bool autostart)
Read and parse a Midi file.
Definition: midiplayer.cpp:339