1 /*
2     SMF GUI Player test using the MIDI Sequencer C++ library
3     Copyright (C) 2006-2021, Pedro Lopez-Cabanillas <plcl@users.sf.net>
4 
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 3 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 #include <QApplication>
20 #include <QCloseEvent>
21 #include <QDebug>
22 #include <QDragEnterEvent>
23 #include <QDropEvent>
24 #include <QFileDialog>
25 #include <QFileInfo>
26 #include <QInputDialog>
27 #include <QMessageBox>
28 #include <QMimeData>
29 #include <QSettings>
30 #include <QStatusBar>
31 #include <QTextCodec>
32 #include <QToolTip>
33 #include <QUrl>
34 #include <qmath.h>
35 
36 #include "guiplayer.h"
37 #include "iconutils.h"
38 #include "player.h"
39 #include "playerabout.h"
40 #include "song.h"
41 #include "ui_guiplayer.h"
42 #include <drumstick/alsaclient.h>
43 #include <drumstick/alsaevent.h>
44 #include <drumstick/alsaport.h>
45 #include <drumstick/alsaqueue.h>
46 #include <drumstick/qsmf.h>
47 #include <drumstick/qwrk.h>
48 #include <drumstick/rmid.h>
49 #include <drumstick/sequencererror.h>
50 
51 DISABLE_WARNING_PUSH
52 DISABLE_WARNING_DEPRECATED_DECLARATIONS
53 
54 using namespace drumstick;
55 using namespace ALSA;
56 using namespace File;
57 
58 const QString GUIPlayer::QSTR_DOMAIN = QStringLiteral("drumstick.sourceforge.net");
59 const QString GUIPlayer::QSTR_APPNAME = QStringLiteral("GUIPlayer");
60 
GUIPlayer(QWidget * parent,Qt::WindowFlags flags)61 GUIPlayer::GUIPlayer(QWidget *parent, Qt::WindowFlags flags)
62     : QMainWindow(parent, flags),
63     m_portId(-1),
64     m_queueId(-1),
65     m_initialTempo(0),
66     m_currentTrack(0),
67     m_tempoFactor(1.0),
68     m_tick(0),
69     m_state(InvalidState),
70     m_smf(nullptr),
71     m_wrk(nullptr),
72     m_Client(nullptr),
73     m_Port(nullptr),
74     m_Queue(nullptr),
75     m_player(nullptr),
76     m_ui(new Ui::GUIPlayerClass),
77     m_pd(nullptr),
78     m_song(new Song)
79 {
80     m_ui->setupUi(this);
81     setAcceptDrops(true);
82     connect(m_ui->actionAbout, &QAction::triggered, this, &GUIPlayer::about);
83     connect(m_ui->actionAboutQt, &QAction::triggered, qApp, QApplication::aboutQt);
84     connect(m_ui->actionPlay, &QAction::triggered, this, &GUIPlayer::play);
85     connect(m_ui->actionPause, &QAction::triggered, this, &GUIPlayer::pause);
86     connect(m_ui->actionStop, &QAction::triggered, this, &GUIPlayer::stop);
87     connect(m_ui->actionOpen, &QAction::triggered, this, &GUIPlayer::open);
88     connect(m_ui->actionMIDISetup, &QAction::triggered, this, &GUIPlayer::setup);
89     connect(m_ui->actionQuit, &QAction::triggered, this, &GUIPlayer::close);
90     connect(m_ui->btnTempo, &QPushButton::clicked, this, &GUIPlayer::tempoReset);
91     connect(m_ui->btnVolume, &QPushButton::clicked, this, &GUIPlayer::volumeReset);
92     connect(m_ui->sliderTempo, &QSlider::valueChanged, this, &GUIPlayer::tempoSlider);
93     connect(m_ui->volumeSlider, &QSlider::valueChanged, this, &GUIPlayer::volumeSlider);
94     connect(m_ui->spinPitch, QOverload<int>::of(&QSpinBox::valueChanged),
95             this, &GUIPlayer::pitchShift);
96     connect(m_ui->toolBar->toggleViewAction(), &QAction::toggled,
97             m_ui->actionShowToolbar, &QAction::setChecked);
98 
99     m_ui->actionPlay->setIcon(QIcon(IconUtils::GetPixmap(this, ":/resources/play.png")));
100     m_ui->actionPlay->setShortcut( Qt::Key_MediaPlay );
101     m_ui->actionStop->setIcon(QIcon(IconUtils::GetPixmap(this, ":/resources/stop.png")));
102     m_ui->actionStop->setShortcut( Qt::Key_MediaStop );
103     m_ui->actionPause->setIcon(QIcon(IconUtils::GetPixmap(this, ":/resources/pause.png")));
104     m_ui->actionMIDISetup->setIcon(QIcon(IconUtils::GetPixmap(this, ":/resources/setup.png")));
105 
106     m_Client = new MidiClient(this);
107     m_Client->open();
108     m_Client->setPoolOutput(50); // small size, for near real-time pitchShift
109     m_Client->setClientName("MIDI Player");
110     connect( m_Client, &MidiClient::eventReceived, this, &GUIPlayer::sequencerEvent, Qt::QueuedConnection );
111 
112     m_Port = new MidiPort(this);
113     m_Port->attach( m_Client );
114     m_Port->setPortName("MIDI Player Output Port");
115     m_Port->setCapability( SND_SEQ_PORT_CAP_READ |
116                            SND_SEQ_PORT_CAP_SUBS_READ |
117                            SND_SEQ_PORT_CAP_WRITE );
118     m_Port->setPortType( SND_SEQ_PORT_TYPE_APPLICATION |
119                          SND_SEQ_PORT_TYPE_MIDI_GENERIC );
120 
121     m_Queue = m_Client->createQueue(QSTR_APPNAME);
122     m_queueId = m_Queue->getId();
123     m_portId = m_Port->getPortId();
124 
125     m_rmi = new Rmidi(this);
126     connect(m_rmi, &Rmidi::signalRiffData, this, &GUIPlayer::dataHandler);
127 
128     m_smf = new QSmf(this);
129     connect(m_smf, &QSmf::signalSMFHeader, this, &GUIPlayer::smfHeaderEvent);
130     connect(m_smf, &QSmf::signalSMFNoteOn, this, &GUIPlayer::smfNoteOnEvent);
131     connect(m_smf, &QSmf::signalSMFNoteOff, this, &GUIPlayer::smfNoteOffEvent);
132     connect(m_smf, &QSmf::signalSMFKeyPress, this, &GUIPlayer::smfKeyPressEvent);
133     connect(m_smf, &QSmf::signalSMFCtlChange, this, &GUIPlayer::smfCtlChangeEvent);
134     connect(m_smf, &QSmf::signalSMFPitchBend, this, &GUIPlayer::smfPitchBendEvent);
135     connect(m_smf, &QSmf::signalSMFProgram, this, &GUIPlayer::smfProgramEvent);
136     connect(m_smf, &QSmf::signalSMFChanPress, this, &GUIPlayer::smfChanPressEvent);
137     connect(m_smf, &QSmf::signalSMFSysex, this, &GUIPlayer::smfSysexEvent);
138     connect(m_smf, &QSmf::signalSMFText, this, &GUIPlayer::smfUpdateLoadProgress);
139     connect(m_smf, &QSmf::signalSMFTempo, this, &GUIPlayer::smfTempoEvent);
140     connect(m_smf, &QSmf::signalSMFTrackStart, this, &GUIPlayer::smfUpdateLoadProgress);
141     connect(m_smf, &QSmf::signalSMFTrackStart, this, &GUIPlayer::smfTrackStarted);
142     connect(m_smf, &QSmf::signalSMFTrackEnd, this, &GUIPlayer::smfTrackEnded);
143     connect(m_smf, &QSmf::signalSMFendOfTrack, this, &GUIPlayer::smfUpdateLoadProgress);
144     connect(m_smf, &QSmf::signalSMFError, this, &GUIPlayer::smfErrorHandler);
145 
146     m_wrk = new QWrk(this);
147     m_wrk->setTextCodec(QTextCodec::codecForLocale());
148     connect(m_wrk, &QWrk::signalWRKError, this, &GUIPlayer::wrkErrorHandler);
149     connect(m_wrk, &QWrk::signalWRKUnknownChunk, this, &GUIPlayer::wrkUpdateLoadProgress);
150     connect(m_wrk, &QWrk::signalWRKHeader, this, &GUIPlayer::wrkFileHeader);
151     connect(m_wrk, &QWrk::signalWRKEnd, this, &GUIPlayer::wrkEndOfFile);
152     connect(m_wrk, &QWrk::signalWRKStreamEnd, this, &GUIPlayer::wrkStreamEndEvent);
153     connect(m_wrk, &QWrk::signalWRKGlobalVars, this, &GUIPlayer::wrkUpdateLoadProgress);
154     connect(m_wrk, &QWrk::signalWRKTrack, this, &GUIPlayer::wrkTrackHeader);
155     connect(m_wrk, &QWrk::signalWRKTimeBase, this, &GUIPlayer::wrkTimeBase);
156     connect(m_wrk, &QWrk::signalWRKNote, this, &GUIPlayer::wrkNoteEvent);
157     connect(m_wrk, &QWrk::signalWRKKeyPress, this, &GUIPlayer::wrkKeyPressEvent);
158     connect(m_wrk, &QWrk::signalWRKCtlChange, this, &GUIPlayer::wrkCtlChangeEvent);
159     connect(m_wrk, &QWrk::signalWRKPitchBend, this, &GUIPlayer::wrkPitchBendEvent);
160     connect(m_wrk, &QWrk::signalWRKProgram, this, &GUIPlayer::wrkProgramEvent);
161     connect(m_wrk, &QWrk::signalWRKChanPress, this, &GUIPlayer::wrkChanPressEvent);
162     connect(m_wrk, &QWrk::signalWRKSysexEvent, this, &GUIPlayer::wrkSysexEvent);
163     connect(m_wrk, &QWrk::signalWRKSysex, this, &GUIPlayer::wrkSysexEventBank);
164     connect(m_wrk, &QWrk::signalWRKText, this, &GUIPlayer::wrkUpdateLoadProgress);
165     connect(m_wrk, &QWrk::signalWRKTimeSig, this, &GUIPlayer::wrkUpdateLoadProgress);
166     connect(m_wrk, &QWrk::signalWRKKeySig, this, &GUIPlayer::wrkUpdateLoadProgress);
167     connect(m_wrk, &QWrk::signalWRKTempo, this, &GUIPlayer::wrkTempoEvent);
168     connect(m_wrk, &QWrk::signalWRKTrackPatch, this, &GUIPlayer::wrkTrackPatch);
169     connect(m_wrk, &QWrk::signalWRKComments, this, &GUIPlayer::wrkUpdateLoadProgress);
170     connect(m_wrk, &QWrk::signalWRKVariableRecord, this, &GUIPlayer::wrkUpdateLoadProgress);
171     connect(m_wrk, &QWrk::signalWRKNewTrack, this, &GUIPlayer::wrkNewTrackHeader);
172     connect(m_wrk, &QWrk::signalWRKTrackName, this, &GUIPlayer::wrkUpdateLoadProgress);
173     connect(m_wrk, &QWrk::signalWRKTrackVol, this, &GUIPlayer::wrkTrackVol);
174     connect(m_wrk, &QWrk::signalWRKTrackBank, this, &GUIPlayer::wrkTrackBank);
175     connect(m_wrk, &QWrk::signalWRKSegment, this, &GUIPlayer::wrkUpdateLoadProgress);
176     connect(m_wrk, &QWrk::signalWRKChord, this, &GUIPlayer::wrkUpdateLoadProgress);
177     connect(m_wrk, &QWrk::signalWRKExpression, this, &GUIPlayer::wrkUpdateLoadProgress);
178 
179     m_player = new Player(m_Client, m_portId);
180     connect(m_player, &Player::playbackStopped, this, &GUIPlayer::playerStopped, Qt::QueuedConnection);
181 
182     m_Client->setRealTimeInput(false);
183     m_Client->startSequencerInput();
184     tempoReset();
185     volumeReset();
186     updateState(EmptyState);
187     readSettings();
188 }
189 
~GUIPlayer()190 GUIPlayer::~GUIPlayer()
191 {
192     m_Client->stopSequencerInput();
193     m_Port->unsubscribeAll();
194     m_Port->detach();
195     m_Client->close();
196     delete m_player;
197     delete m_ui;
198     delete m_song;
199 }
200 
subscribe(const QString & portName)201 void GUIPlayer::subscribe(const QString& portName)
202 {
203     try {
204         if (!m_subscription.isEmpty()) {
205             m_Port->unsubscribeTo(m_subscription);
206         }
207         m_subscription = portName;
208         m_Port->subscribeTo(m_subscription);
209     } catch (const SequencerError& err) {
210         qWarning() << "SequencerError exception. Error code: " << err.code()
211                    << " (" << err.qstrError() << ")";
212         qWarning() << "Location: " << err.location();
213     }
214 }
215 
updateTimeLabel(int mins,int secs,int cnts)216 void GUIPlayer::updateTimeLabel(int mins, int secs, int cnts)
217 {
218     static QChar fill('0');
219     QString stime = QString("%1:%2.%3").arg(mins,2,10,fill)
220                                        .arg(secs,2,10,fill)
221                                        .arg(cnts,2,10,fill);
222     m_ui->lblTime->setText(stime);
223 }
224 
updateState(PlayerState newState)225 void GUIPlayer::updateState(PlayerState newState)
226 {
227     if (m_state == newState)
228         return;
229     switch (newState) {
230     case EmptyState:
231         m_ui->actionPlay->setEnabled(false);
232         m_ui->actionPause->setEnabled(false);
233         m_ui->actionStop->setEnabled(false);
234         statusBar()->showMessage("Please, load a song");
235         break;
236     case PlayingState:
237         m_ui->actionPlay->setEnabled(false);
238         m_ui->actionPause->setEnabled(true);
239         m_ui->actionStop->setEnabled(true);
240         statusBar()->showMessage("Playing");
241         break;
242     case PausedState:
243         m_ui->actionPlay->setEnabled(false);
244         m_ui->actionStop->setEnabled(true);
245         statusBar()->showMessage("Paused");
246         break;
247     case StoppedState:
248         m_ui->actionPause->setChecked(false);
249         m_ui->actionPause->setEnabled(false);
250         m_ui->actionStop->setEnabled(false);
251         m_ui->actionPlay->setEnabled(true);
252         statusBar()->showMessage("Stopped");
253         break;
254     default:
255         statusBar()->showMessage("Not initialized");
256         break;
257     }
258     m_state = newState;
259 }
260 
play()261 void GUIPlayer::play()
262 {
263     if (!m_song->isEmpty()) {
264         if (m_player->getInitialPosition() == 0) {
265             if (m_initialTempo == 0)
266                 return;
267             QueueTempo firstTempo = m_Queue->getTempo();
268             firstTempo.setPPQ(m_song->getDivision());
269             firstTempo.setTempo(m_initialTempo);
270             firstTempo.setTempoFactor(m_tempoFactor);
271             m_Queue->setTempo(firstTempo);
272             m_Client->drainOutput();
273             m_player->sendVolumeEvents();
274         }
275         m_player->start();
276         updateState(PlayingState);
277     }
278 }
279 
pause()280 void GUIPlayer::pause()
281 {
282     if (m_state == PlayingState || m_player->isRunning()) {
283         m_player->stop();
284         m_player->setPosition(m_Queue->getStatus().getTickTime());
285         updateState(PausedState);
286     } else if (!m_song->isEmpty()) {
287         m_player->start();
288         updateState(PlayingState);
289     }
290 }
291 
stop()292 void GUIPlayer::stop()
293 {
294     if (m_state == PlayingState || m_state == PausedState ||
295         m_player->isRunning()) {
296         m_Queue->stop();
297         m_Queue->clear();
298         m_player->stop();
299     }
300     if (m_initialTempo != 0)
301         songFinished();
302     else
303         updateState(StoppedState);
304 }
305 
progressDialogInit(const QString & type,int max)306 void GUIPlayer::progressDialogInit(const QString& type, int max)
307 {
308     m_pd = new QProgressDialog("", "", 0, max, this);
309     m_pd->setWindowTitle(QString("Loading %1 file...").arg(type));
310     m_pd->setMinimumDuration(1000);
311     m_pd->setValue(0);
312 }
313 
progressDialogUpdate(int pos)314 void GUIPlayer::progressDialogUpdate(int pos)
315 {
316     if (m_pd != nullptr) {
317         m_pd->setValue(pos);
318         qApp->processEvents();
319     }
320 }
321 
progressDialogClose()322 void GUIPlayer::progressDialogClose()
323 {
324     delete m_pd; // set to 0 by QPointer<>
325 }
326 
openFile(const QString & fileName)327 void GUIPlayer::openFile(const QString& fileName)
328 {
329     QFileInfo finfo(fileName);
330     if (finfo.exists()) {
331         m_song->clear();
332         m_loadingMessages.clear();
333         m_tick = 0;
334         m_initialTempo = 0;
335         m_currentTrack = 0;
336         try {
337             QString ext = finfo.suffix().toLower();
338             if (ext == "wrk") {
339                 progressDialogInit("Cakewalk", finfo.size());
340                 m_wrk->readFromFile(fileName);
341             }
342             else if (ext == "mid" || ext == "midi" || ext == "kar") {
343                 progressDialogInit("MIDI", finfo.size());
344                 m_smf->readFromFile(fileName);
345             }
346             else if (ext == "rmi") {
347                 progressDialogInit("RIFF MIDI", finfo.size());
348                 m_rmi->readFromFile(fileName);
349             }
350             progressDialogUpdate(finfo.size());
351             if (m_song->isEmpty()) {
352                 m_ui->lblName->clear();
353             } else {
354                 m_song->sort();
355                 m_player->setSong(m_song);
356                 m_ui->lblName->setText(finfo.fileName());
357                 m_lastDirectory = finfo.absolutePath();
358             }
359         } catch (...) {
360             m_song->clear();
361             m_ui->lblName->clear();
362         }
363         progressDialogClose();
364         if (m_initialTempo == 0) {
365             m_initialTempo = 500000;
366         }
367         updateTimeLabel(0,0,0);
368         updateTempoLabel(6.0e7f / m_initialTempo);
369         m_ui->progressBar->setValue(0);
370         if (!m_loadingMessages.isEmpty()) {
371             m_loadingMessages.insert(0,
372                 "Warning, this file may be non-standard or damaged.<br>");
373             QMessageBox::warning(this, QSTR_APPNAME, m_loadingMessages);
374         }
375         if (m_song->isEmpty())
376             updateState(EmptyState);
377         else
378             updateState(StoppedState);
379     }
380 }
381 
open()382 void GUIPlayer::open()
383 {
384     QString fileName = QFileDialog::getOpenFileName(this,
385           "Open MIDI File", m_lastDirectory,
386           "All files (*.kar *.mid *.midi *rmi *.wrk);;"
387           "Karaoke files (*.kar);;"
388           "MIDI Files (*.mid *.midi);;"
389           "RIFF MIDI Files (*.rmi);;"
390           "Cakewalk files (*.wrk)" );
391     if (! fileName.isEmpty() ) {
392         stop();
393         openFile(fileName);
394     }
395 }
396 
setup()397 void GUIPlayer::setup()
398 {
399     bool ok;
400     int current;
401     QStringList items;
402     QListIterator<PortInfo> it(m_Client->getAvailableOutputs());
403     while(it.hasNext()) {
404         PortInfo p = it.next();
405         items << QString("%1:%2").arg(p.getClientName()).arg(p.getPort());
406     }
407     current = items.indexOf(m_subscription);
408     QString item = QInputDialog::getItem(this, "Player subscription",
409                                          "Output port:", items,
410                                          current, false, &ok);
411     if (ok && !item.isEmpty())
412         subscribe(item);
413 }
414 
songFinished()415 void GUIPlayer::songFinished()
416 {
417     m_player->resetPosition();
418     updateState(StoppedState);
419 }
420 
playerStopped()421 void GUIPlayer::playerStopped()
422 {
423     int portId = m_Port->getPortId();
424     for (int channel = 0; channel < 16; ++channel) {
425         ControllerEvent ev1(channel, MIDI_CTL_ALL_NOTES_OFF, 0);
426         ev1.setSource(portId);
427         ev1.setSubscribers();
428         ev1.setDirect();
429         m_Client->outputDirect(&ev1);
430         ControllerEvent ev2(channel, MIDI_CTL_ALL_SOUNDS_OFF, 0);
431         ev2.setSource(portId);
432         ev2.setSubscribers();
433         ev2.setDirect();
434         m_Client->outputDirect(&ev2);
435     }
436     m_Client->drainOutput();
437 }
438 
updateTempoLabel(float ftempo)439 void GUIPlayer::updateTempoLabel(float ftempo)
440 {
441     QString stempo = QString("%1 bpm").arg(ftempo, 0, 'f', 2);
442     m_ui->lblOther->setText(stempo);
443 }
444 
sequencerEvent(SequencerEvent * ev)445 void GUIPlayer::sequencerEvent(SequencerEvent *ev)
446 {
447     if ((ev->getSequencerType() == SND_SEQ_EVENT_ECHO) && (m_tick != 0)){
448         auto t = ev->getTick();
449         int pos = 100 * t / m_tick;
450         const snd_seq_real_time_t* rt = m_Queue->getStatus().getRealtime();
451         int mins = rt->tv_sec / 60;
452         int secs =  rt->tv_sec % 60;
453         int cnts = qFloor( rt->tv_nsec / 1.0e7 );
454         updateTempoLabel(m_Queue->getTempo().getRealBPM());
455         updateTimeLabel(mins, secs, cnts);
456         m_ui->progressBar->setValue(pos);
457         if (t >= m_tick) {
458             songFinished();
459         }
460     }
461     delete ev;
462 }
463 
dataHandler(const QString & dataType,const QByteArray & data)464 void GUIPlayer::dataHandler(const QString &dataType, const QByteArray &data)
465 {
466     if (dataType == "RMID") {
467         QDataStream ds(data);
468         m_smf->readFromStream(&ds);
469     }
470 }
471 
pitchShift(int value)472 void GUIPlayer::pitchShift(int value)
473 {
474     m_player->setPitchShift(value);
475 }
476 
tempoReset()477 void GUIPlayer::tempoReset()
478 {
479     m_ui->sliderTempo->setValue(100);
480     tempoSlider(100);
481 }
482 
volumeReset()483 void GUIPlayer::volumeReset()
484 {
485     m_ui->volumeSlider->setValue(100);
486     volumeSlider(100);
487 }
488 
tempoSlider(int value)489 void GUIPlayer::tempoSlider(int value)
490 {
491     m_tempoFactor = (value*value + 100.0*value + 20000.0) / 40000.0;
492     QueueTempo qtempo = m_Queue->getTempo();
493     qtempo.setTempoFactor(m_tempoFactor);
494     m_Queue->setTempo(qtempo);
495     m_Client->drainOutput();
496     if (!m_player->isRunning())
497         updateTempoLabel(qtempo.getRealBPM());
498     // Slider tooltip
499     QString tip = QString("%1 %").arg(m_tempoFactor*100.0, 0, 'f', 0);
500     m_ui->sliderTempo->setToolTip(tip);
501     QToolTip::showText(QCursor::pos(), tip, this);
502 }
503 
volumeSlider(int value)504 void GUIPlayer::volumeSlider(int value)
505 {
506     QString tip = QString::number(value)+'%';
507     m_ui->lblVolume->setText(tip);
508     m_ui->volumeSlider->setToolTip(tip);
509     m_player->setVolumeFactor(value);
510     QToolTip::showText(QCursor::pos(), tip, this);
511 }
512 
dragEnterEvent(QDragEnterEvent * event)513 void GUIPlayer::dragEnterEvent( QDragEnterEvent * event )
514 {
515     if (event->mimeData()->hasUrls()) {
516         event->acceptProposedAction();
517     }
518 }
519 
dropEvent(QDropEvent * event)520 void GUIPlayer::dropEvent( QDropEvent * event )
521 {
522     if ( event->mimeData()->hasUrls() ) {
523         QList<QUrl> urls = event->mimeData()->urls();
524         if (!urls.empty()) {
525             QString fileName = urls.first().toLocalFile();
526             if ( fileName.endsWith(".mid", Qt::CaseInsensitive) ||
527                  fileName.endsWith(".midi", Qt::CaseInsensitive) ||
528                  fileName.endsWith(".kar", Qt::CaseInsensitive) ||
529                  fileName.endsWith(".rmi", Qt::CaseInsensitive) ||
530                  fileName.endsWith(".wrk", Qt::CaseInsensitive) ) {
531                 stop();
532                 event->accept();
533                 openFile(fileName);
534             } else {
535                 QMessageBox::warning(this, QSTR_APPNAME,
536                     QString("Dropped file %1 is not supported").arg(fileName));
537             }
538         }
539     }
540 }
541 
readSettings()542 void GUIPlayer::readSettings()
543 {
544     QSettings settings;
545 
546     settings.beginGroup("Window");
547     restoreGeometry(settings.value("Geometry").toByteArray());
548     restoreState(settings.value("State").toByteArray());
549     settings.endGroup();
550 
551     settings.beginGroup("Preferences");
552     m_lastDirectory = settings.value("LastDirectory").toString();
553     QString midiConn = settings.value("MIDIConnection").toString();
554     settings.endGroup();
555 
556     if (midiConn.length() > 0)
557         subscribe(midiConn);
558 }
559 
writeSettings()560 void GUIPlayer::writeSettings()
561 {
562     QSettings settings;
563 
564     settings.beginGroup("Window");
565     settings.setValue("Geometry", saveGeometry());
566     settings.setValue("State", saveState());
567     settings.endGroup();
568 
569     settings.beginGroup("Preferences");
570     settings.setValue("LastDirectory", m_lastDirectory);
571     settings.setValue("MIDIConnection", m_subscription);
572     settings.endGroup();
573 }
574 
closeEvent(QCloseEvent * event)575 void GUIPlayer::closeEvent( QCloseEvent *event )
576 {
577     stop();
578     m_player->wait();
579     writeSettings();
580     event->accept();
581 }
582 
about()583 void GUIPlayer::about()
584 {
585     About aboutDlg(this);
586     aboutDlg.exec();
587 }
588 
589 /* **************************************** *
590  * SMF (Standard MIDI file) format handling
591  * **************************************** */
592 
smfUpdateLoadProgress()593 void GUIPlayer::smfUpdateLoadProgress()
594 {
595     progressDialogUpdate(m_smf->getFilePos());
596 }
597 
appendSMFEvent(SequencerEvent * ev)598 void GUIPlayer::appendSMFEvent(SequencerEvent* ev)
599 {
600     unsigned long tick = m_smf->getCurrentTime();
601     ev->setSource(m_portId);
602     if (ev->getSequencerType() != SND_SEQ_EVENT_TEMPO) {
603         ev->setSubscribers();
604     }
605     ev->scheduleTick(m_queueId, tick, false);
606     m_song->append(ev);
607     if (tick > m_tick)
608         m_tick = tick;
609     smfUpdateLoadProgress();
610 }
611 
smfHeaderEvent(int format,int ntrks,int division)612 void GUIPlayer::smfHeaderEvent(int format, int ntrks, int division)
613 {
614     m_song->setHeader(format, ntrks, division);
615     smfUpdateLoadProgress();
616 }
617 
smfNoteOnEvent(int chan,int pitch,int vol)618 void GUIPlayer::smfNoteOnEvent(int chan, int pitch, int vol)
619 {
620     SequencerEvent* ev = new NoteOnEvent (chan, pitch, vol);
621     appendSMFEvent(ev);
622 }
623 
smfNoteOffEvent(int chan,int pitch,int vol)624 void GUIPlayer::smfNoteOffEvent(int chan, int pitch, int vol)
625 {
626     SequencerEvent* ev = new NoteOffEvent (chan, pitch, vol);
627     appendSMFEvent(ev);
628 }
629 
smfKeyPressEvent(int chan,int pitch,int press)630 void GUIPlayer::smfKeyPressEvent(int chan, int pitch, int press)
631 {
632     SequencerEvent* ev = new KeyPressEvent (chan, pitch, press);
633     appendSMFEvent(ev);
634 }
635 
smfCtlChangeEvent(int chan,int ctl,int value)636 void GUIPlayer::smfCtlChangeEvent(int chan, int ctl, int value)
637 {
638     SequencerEvent* ev = new ControllerEvent (chan, ctl, value);
639     appendSMFEvent(ev);
640 }
641 
smfPitchBendEvent(int chan,int value)642 void GUIPlayer::smfPitchBendEvent(int chan, int value)
643 {
644     SequencerEvent* ev = new PitchBendEvent (chan, value);
645     appendSMFEvent(ev);
646 }
647 
smfProgramEvent(int chan,int patch)648 void GUIPlayer::smfProgramEvent(int chan, int patch)
649 {
650     SequencerEvent* ev = new ProgramChangeEvent (chan, patch);
651     appendSMFEvent(ev);
652 }
653 
smfChanPressEvent(int chan,int press)654 void GUIPlayer::smfChanPressEvent(int chan, int press)
655 {
656     SequencerEvent* ev = new ChanPressEvent (chan, press);
657     appendSMFEvent(ev);
658 }
659 
smfSysexEvent(const QByteArray & data)660 void GUIPlayer::smfSysexEvent(const QByteArray& data)
661 {
662     SequencerEvent* ev = new SysExEvent (data);
663     appendSMFEvent(ev);
664 }
665 
smfTempoEvent(int tempo)666 void GUIPlayer::smfTempoEvent(int tempo)
667 {
668     if ( m_initialTempo == 0 ) {
669         m_initialTempo = tempo;
670     }
671     SequencerEvent* ev = new TempoEvent (m_queueId, tempo);
672     appendSMFEvent(ev);
673 }
674 
smfErrorHandler(const QString & errorStr)675 void GUIPlayer::smfErrorHandler(const QString& errorStr)
676 {
677     if (m_loadingMessages.length() < 1024)
678         m_loadingMessages.append(QString("%1 at file offset %2<br>")
679                                  .arg(errorStr).arg(m_smf->getFilePos()));
680 }
681 
smfTrackStarted()682 void GUIPlayer::smfTrackStarted()
683 {
684     m_currentTrack++;
685 }
686 
smfTrackEnded()687 void GUIPlayer::smfTrackEnded()
688 {
689     if (m_currentTrack == m_smf->getTracks()) {
690         SequencerEvent* ev = new SystemEvent(SND_SEQ_EVENT_ECHO);
691         appendSMFEvent(ev);
692     }
693 }
694 
695 /* ********************************* *
696  * Cakewalk WRK file format handling
697  * ********************************* */
698 
wrkUpdateLoadProgress()699 void GUIPlayer::wrkUpdateLoadProgress()
700 {
701     if (m_pd != nullptr) {
702         progressDialogUpdate(m_wrk->getFilePos());
703     }
704 }
705 
706 void
appendWRKEvent(unsigned long ticks,SequencerEvent * ev)707 GUIPlayer::appendWRKEvent(unsigned long ticks, SequencerEvent* ev)
708 {
709     ev->setSource(m_portId);
710     if (ev->getSequencerType() != SND_SEQ_EVENT_TEMPO) {
711         ev->setSubscribers();
712     }
713     ev->scheduleTick(m_queueId, ticks, false);
714     m_song->append(ev);
715     if (ticks > m_tick)
716         m_tick = ticks;
717     wrkUpdateLoadProgress();
718 }
719 
wrkErrorHandler(const QString & errorStr)720 void GUIPlayer::wrkErrorHandler(const QString& errorStr)
721 {
722     if (m_loadingMessages.length() < 1024) {
723         m_loadingMessages.append(QString("%1 at file offset %2<br>")
724             .arg(errorStr).arg(m_wrk->getFilePos()));
725     }
726 }
727 
wrkFileHeader(int,int)728 void GUIPlayer::wrkFileHeader(int /*verh*/, int /*verl*/)
729 {
730     m_song->setHeader(1, 0, 120);
731     wrkUpdateLoadProgress();
732 //    qDebug() << Q_FUNC_INFO;
733 }
734 
wrkTimeBase(int timebase)735 void GUIPlayer::wrkTimeBase(int timebase)
736 {
737     m_song->setDivision(timebase);
738     wrkUpdateLoadProgress();
739 //    qDebug() << Q_FUNC_INFO << timebase;
740 }
741 
wrkStreamEndEvent(long time)742 void GUIPlayer::wrkStreamEndEvent(long time)
743 {
744     unsigned long ticks = time;
745     if (ticks > m_tick)
746         m_tick = ticks;
747     wrkUpdateLoadProgress();
748 //    qDebug() << Q_FUNC_INFO << time;
749 }
750 
wrkTrackHeader(const QString &,const QString &,int trackno,int channel,int pitch,int velocity,int,bool,bool,bool)751 void GUIPlayer::wrkTrackHeader( const QString& /*name1*/,
752                            const QString& /*name2*/,
753                            int trackno, int channel,
754                            int pitch, int velocity, int /*port*/,
755                            bool /*selected*/, bool /*muted*/, bool /*loop*/ )
756 {
757     TrackMapRec rec;
758     rec.channel = channel;
759     rec.pitch = pitch;
760     rec.velocity = velocity;
761     m_trackMap[trackno] = rec;
762     wrkUpdateLoadProgress();
763 //    qDebug() << Q_FUNC_INFO << trackno << channel << pitch << velocity;
764 }
765 
wrkNoteEvent(int track,long time,int chan,int pitch,int vol,int dur)766 void GUIPlayer::wrkNoteEvent(int track, long time, int chan, int pitch, int vol, int dur)
767 {
768     TrackMapRec rec = m_trackMap[track];
769     int channel = (rec.channel > -1) ? rec.channel : chan;
770     int key = qBound(0, pitch + rec.pitch, 127);
771     int velocity = qBound(0, vol + rec.velocity, 127);
772     SequencerEvent* ev = new NoteEvent(channel, key, velocity, dur);
773     appendWRKEvent(time, ev);
774 //    qDebug() << Q_FUNC_INFO << channel << key << velocity << dur;
775 }
776 
wrkKeyPressEvent(int track,long time,int chan,int pitch,int press)777 void GUIPlayer::wrkKeyPressEvent(int track, long time, int chan, int pitch, int press)
778 {
779     TrackMapRec rec = m_trackMap[track];
780     int key = pitch + rec.pitch;
781     int channel = (rec.channel > -1) ? rec.channel : chan;
782     SequencerEvent* ev = new KeyPressEvent(channel, key, press);
783     appendWRKEvent(time, ev);
784 //    qDebug() << Q_FUNC_INFO;
785 }
786 
wrkCtlChangeEvent(int track,long time,int chan,int ctl,int value)787 void GUIPlayer::wrkCtlChangeEvent(int track, long time, int chan, int ctl, int value)
788 {
789     TrackMapRec rec = m_trackMap[track];
790     int channel = (rec.channel > -1) ? rec.channel : chan;
791     SequencerEvent* ev = new ControllerEvent(channel, ctl, value);
792     appendWRKEvent(time, ev);
793 //    qDebug() << Q_FUNC_INFO;
794 }
795 
wrkPitchBendEvent(int track,long time,int chan,int value)796 void GUIPlayer::wrkPitchBendEvent(int track, long time, int chan, int value)
797 {
798     TrackMapRec rec = m_trackMap[track];
799     int channel = (rec.channel > -1) ? rec.channel : chan;
800     SequencerEvent* ev = new PitchBendEvent(channel, value);
801     appendWRKEvent(time, ev);
802 //    qDebug() << Q_FUNC_INFO;
803 }
804 
wrkProgramEvent(int track,long time,int chan,int patch)805 void GUIPlayer::wrkProgramEvent(int track, long time, int chan, int patch)
806 {
807     TrackMapRec rec = m_trackMap[track];
808     int channel = (rec.channel > -1) ? rec.channel : chan;
809     SequencerEvent* ev = new ProgramChangeEvent(channel, patch);
810     appendWRKEvent(time, ev);
811 //    qDebug() << Q_FUNC_INFO;
812 }
813 
wrkChanPressEvent(int track,long time,int chan,int press)814 void GUIPlayer::wrkChanPressEvent(int track, long time, int chan, int press)
815 {
816     TrackMapRec rec = m_trackMap[track];
817     int channel = (rec.channel > -1) ? rec.channel : chan;
818     SequencerEvent* ev = new ChanPressEvent(channel, press);
819     appendWRKEvent(time, ev);
820 //    qDebug() << Q_FUNC_INFO;
821 }
822 
wrkSysexEvent(int track,long time,int bank)823 void GUIPlayer::wrkSysexEvent(int track, long time, int bank)
824 {
825     Q_UNUSED(track)
826     qDebug() << Q_FUNC_INFO;
827     if (m_savedSysexEvents.contains(bank)) {
828         SysExEvent* ev = m_savedSysexEvents[bank].clone();
829         appendWRKEvent(time, ev);
830         wrkUpdateLoadProgress();
831     }
832 }
833 
wrkSysexEventBank(int bank,const QString &,bool autosend,int,const QByteArray & data)834 void GUIPlayer::wrkSysexEventBank(int bank, const QString& /*name*/,
835         bool autosend, int /*port*/, const QByteArray& data)
836 {
837     //qDebug() << Q_FUNC_INFO;
838     SysExEvent* ev = new SysExEvent(data);
839     if (autosend) {
840         appendWRKEvent(0, ev);
841     } else {
842         m_savedSysexEvents[bank] = *ev;
843         delete ev;
844     }
845     wrkUpdateLoadProgress();
846 }
847 
wrkTempoEvent(long time,int tempo)848 void GUIPlayer::wrkTempoEvent(long time, int tempo)
849 {
850     double bpm = tempo / 100.0;
851     if ( m_initialTempo < 0 )
852         m_initialTempo = qRound( bpm );
853     SequencerEvent* ev = new TempoEvent(m_queueId, qRound ( 6e7 / bpm ) );
854     appendWRKEvent(time, ev);
855 //    qDebug() << Q_FUNC_INFO;
856 }
857 
wrkTrackPatch(int track,int patch)858 void GUIPlayer::wrkTrackPatch(int track, int patch)
859 {
860     TrackMapRec rec = m_trackMap[track];
861     int channel = (rec.channel > -1) ? rec.channel : 0;
862     wrkProgramEvent(track, 0, channel, patch);
863 //    qDebug() << Q_FUNC_INFO;
864 }
865 
wrkNewTrackHeader(const QString &,int trackno,int channel,int pitch,int velocity,int,bool,bool,bool)866 void GUIPlayer::wrkNewTrackHeader( const QString& /*name*/,
867                               int trackno, int channel,
868                               int pitch, int velocity, int /*port*/,
869                               bool /*selected*/, bool /*muted*/, bool /*loop*/ )
870 {
871     TrackMapRec rec;
872     rec.channel = channel;
873     rec.pitch = pitch;
874     rec.velocity = velocity;
875     m_trackMap[trackno] = rec;
876     wrkUpdateLoadProgress();
877 //    qDebug() << Q_FUNC_INFO << trackno << channel << pitch << velocity;
878 }
879 
wrkTrackVol(int track,int vol)880 void GUIPlayer::wrkTrackVol(int track, int vol)
881 {
882     int lsb, msb;
883     TrackMapRec rec = m_trackMap[track];
884     int channel = (rec.channel > -1) ? rec.channel : 0;
885     if (vol < 128)
886         wrkCtlChangeEvent(track, 0, channel, MIDI_CTL_MSB_MAIN_VOLUME, vol);
887     else {
888         lsb = vol % 0x80;
889         msb = vol / 0x80;
890         wrkCtlChangeEvent(track, 0, channel, MIDI_CTL_LSB_MAIN_VOLUME, lsb);
891         wrkCtlChangeEvent(track, 0, channel, MIDI_CTL_MSB_MAIN_VOLUME, msb);
892     }
893 //    qDebug() << Q_FUNC_INFO;
894 }
895 
wrkTrackBank(int track,int bank)896 void GUIPlayer::wrkTrackBank(int track, int bank)
897 {
898     // assume GM/GS bank method
899     int lsb, msb;
900     TrackMapRec rec = m_trackMap[track];
901     int channel = (rec.channel > -1) ? rec.channel : 0;
902     lsb = bank % 0x80;
903     msb = bank / 0x80;
904     wrkCtlChangeEvent(track, 0, channel, MIDI_CTL_MSB_BANK, msb);
905     wrkCtlChangeEvent(track, 0, channel, MIDI_CTL_LSB_BANK, lsb);
906 //    qDebug() << Q_FUNC_INFO;
907 }
908 
wrkEndOfFile()909 void GUIPlayer::wrkEndOfFile()
910 {
911     if (m_initialTempo < 0)
912         m_initialTempo = 120;
913     SequencerEvent* ev = new SystemEvent(SND_SEQ_EVENT_ECHO);
914     appendWRKEvent(m_tick, ev);
915 //    qDebug() << Q_FUNC_INFO;
916 }
917 
918 DISABLE_WARNING_POP
919