/* SMF GUI Player test using the MIDI Sequencer C++ library Copyright (C) 2006-2010, Pedro Lopez-Cabanillas <plcl@users.sf.net> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef INCLUDED_GUIPLAYER_H #define INCLUDED_GUIPLAYER_H #include <QtGui/QMainWindow> #include <QtGui/QProgressDialog> #include <QtCore/QObject> #include <QtCore/QString> #include <QtCore/QList> #include <QtCore/QHash> #include <QtCore/QPointer> namespace drumstick { class QSmf; class QWrk; class MidiClient; class MidiPort; class MidiQueue; class SequencerEvent; } namespace Ui { class GUIPlayerClass; } class Player; class About; class Song; using namespace drumstick; const QString QSTR_DOMAIN("drumstick.sourceforge.net"); const QString QSTR_APPNAME("GUIPlayer"); enum PlayerState { InvalidState, EmptyState, PlayingState, PausedState, StoppedState }; class GUIPlayer : public QMainWindow { Q_OBJECT public: GUIPlayer(QWidget *parent = 0, Qt::WindowFlags flags = 0); ~GUIPlayer(); void appendSMFEvent(SequencerEvent* ev); void appendWRKEvent(unsigned long ticks, int track, SequencerEvent* ev); void subscribe(const QString& portName); void updateTimeLabel(int mins, int secs, int cnts); void updateTempoLabel(float ftempo); void dragEnterEvent(QDragEnterEvent* event); void dropEvent(QDropEvent* event); void closeEvent(QCloseEvent* event); bool event(QEvent* event); void openFile(const QString& fileName); void readSettings(); void writeSettings(); void updateState(PlayerState newState); public slots: void about(); void aboutQt(); void play(); void pause(); void stop(); void open(); void setup(); void tempoReset(); void volumeReset(); void tempoSlider(int value); void quit(); void volumeSlider(int value); void pitchShift(int value); void songFinished(); void playerStopped(); void sequencerEvent(SequencerEvent* ev); /* SMF slots */ void headerEvent(int format, int ntrks, int division); void noteOnEvent(int chan, int pitch, int vol); void noteOffEvent(int chan, int pitch, int vol); void keyPressEvent(int chan, int pitch, int press); void ctlChangeEvent(int chan, int ctl, int value); void pitchBendEvent(int chan, int value); void programEvent(int chan, int patch); void chanPressEvent(int chan, int press); void sysexEvent(const QByteArray& data); void textEvent(int type, const QString& data); void tempoEvent(int tempo); void errorHandler(const QString& errorStr); void updateSMFLoadProgress(); /* WRK slots */ void updateWRKLoadProgress(); void errorHandlerWRK(const QString& errorStr); void unknownChunk(int type, const QByteArray& data); void fileHeader(int verh, int verl); void endOfWrk(); void streamEndEvent(long time); void trackHeader(const QString& name1, const QString& name2, int trackno, int channel, int pitch, int velocity, int port, bool selected, bool muted, bool loop); void timeBase(int timebase); void globalVars(); void noteEvent(int track, long time, int chan, int pitch, int vol, int dur); void keyPressEvent(int track, long time, int chan, int pitch, int press); void ctlChangeEvent(int track, long time, int chan, int ctl, int value); void pitchBendEvent(int track, long time, int chan, int value); void programEvent(int track, long time, int chan, int patch); void chanPressEvent(int track, long time, int chan, int press); void sysexEvent(int track, long time, int bank); void sysexEventBank(int bank, const QString& name, bool autosend, int port, const QByteArray& data); void textEvent(int track, long time, int typ, const QString& data); void timeSigEvent(int bar, int num, int den); void keySigEventWRK(int bar, int alt); void tempoEvent(long time, int tempo); void trackPatch(int track, int patch); void comments(const QString& cmt); void variableRecord(const QString& name, const QByteArray& data); void newTrackHeader(const QString& name, int trackno, int channel, int pitch, int velocity, int port, bool selected, bool muted, bool loop); void trackName(int trackno, const QString& name); void trackVol(int track, int vol); void trackBank(int track, int bank); void segment(int track, long time, const QString& name); void chord(int track, long time, const QString& name, const QByteArray& data); void expression(int track, long time, int code, const QString& text); private: int m_portId; int m_queueId; int m_initialTempo; float m_tempoFactor; unsigned long m_tick; PlayerState m_state; QSmf* m_smf; QWrk* m_wrk; MidiClient* m_Client; MidiPort* m_Port; MidiQueue* m_Queue; Player* m_player; Ui::GUIPlayerClass* m_ui; QPointer<QProgressDialog> m_pd; QPointer<About> m_aboutDlg; Song* m_song; QString m_subscription; QString m_lastDirectory; QString m_loadingMessages; struct SysexEventRec { int track; long time; int bank; }; QList<SysexEventRec> m_savedSysexEvents; QString m_fileFormat; struct TrackMapRec { int channel; int pitch; int velocity; }; QHash<int,TrackMapRec> m_trackMap; struct TimeSigRec { int bar; int num; int den; long time; }; QList<TimeSigRec> m_bars; }; #endif // INCLUDED_GUIPLAYER_H
/* SMF GUI Player test using the MIDI Sequencer C++ library Copyright (C) 2006-2010, Pedro Lopez-Cabanillas <plcl@users.sf.net> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "guiplayer.h" #include "ui_guiplayer.h" #include "playerabout.h" #include "player.h" #include "song.h" #include "qsmf.h" #include "qwrk.h" #include "alsaevent.h" #include "alsaclient.h" #include "alsaqueue.h" #include "alsaport.h" #include <QtGui/QApplication> #include <QtGui/QFileDialog> #include <QtGui/QInputDialog> #include <QtGui/QDragEnterEvent> #include <QtGui/QDropEvent> #include <QtGui/QCloseEvent> #include <QtGui/QToolTip> #include <QtGui/QMessageBox> #include <QtGui/QStatusBar> #include <QtCore/QSettings> #include <QtCore/QUrl> #include <QtCore/QFileInfo> #include <QtCore/QTextCodec> #include <cmath> GUIPlayer::GUIPlayer(QWidget *parent, Qt::WindowFlags flags) : QMainWindow(parent, flags), m_portId(-1), m_queueId(-1), m_initialTempo(0), m_tempoFactor(1.0), m_tick(0), m_state(InvalidState), m_smf(0), m_wrk(0), m_Client(0), m_Port(0), m_Queue(0), m_player(0), m_ui(new Ui::GUIPlayerClass), m_pd(0), m_aboutDlg(0), m_song(new Song) { m_ui->setupUi(this); connect(m_ui->actionAbout, SIGNAL(triggered()), SLOT(about())); connect(m_ui->actionAboutQt, SIGNAL(triggered()), SLOT(aboutQt())); connect(m_ui->actionPlay, SIGNAL(triggered()), SLOT(play())); connect(m_ui->actionPause, SIGNAL(triggered()), SLOT(pause())); connect(m_ui->actionStop, SIGNAL(triggered()), SLOT(stop())); connect(m_ui->actionOpen, SIGNAL(triggered()), SLOT(open())); connect(m_ui->actionMIDISetup, SIGNAL(triggered()), SLOT(setup())); connect(m_ui->actionQuit, SIGNAL(triggered()), SLOT(quit())); connect(m_ui->btnTempo, SIGNAL(clicked()), SLOT(tempoReset())); connect(m_ui->btnVolume, SIGNAL(clicked()), SLOT(volumeReset())); connect(m_ui->sliderTempo, SIGNAL(valueChanged(int)), SLOT(tempoSlider(int))); connect(m_ui->volumeSlider, SIGNAL(valueChanged(int)), SLOT(volumeSlider(int))); connect(m_ui->spinPitch, SIGNAL(valueChanged(int)), SLOT(pitchShift(int))); connect(m_ui->toolBar->toggleViewAction(), SIGNAL(toggled(bool)), m_ui->actionShowToolbar, SLOT(setChecked(bool))); m_ui->actionPlay->setShortcut( Qt::Key_MediaPlay ); m_ui->actionStop->setShortcut( Qt::Key_MediaStop ); m_Client = new MidiClient(this); m_Client->open(); m_Client->setPoolOutput(20); // tiny size, for near real-time pitchShift m_Client->setClientName("MIDI Player"); connect(m_Client, SIGNAL(eventReceived(SequencerEvent*)), SLOT(sequencerEvent(SequencerEvent*))); m_Port = new MidiPort(this); m_Port->attach( m_Client ); m_Port->setPortName("MIDI Player Output Port"); m_Port->setCapability( SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_WRITE ); m_Port->setPortType( SND_SEQ_PORT_TYPE_APPLICATION | SND_SEQ_PORT_TYPE_MIDI_GENERIC ); m_Queue = m_Client->createQueue(QSTR_APPNAME); m_queueId = m_Queue->getId(); m_portId = m_Port->getPortId(); m_smf = new QSmf(this); connect(m_smf, SIGNAL(signalSMFHeader(int,int,int)), SLOT(headerEvent(int,int,int))); connect(m_smf, SIGNAL(signalSMFNoteOn(int,int,int)), SLOT(noteOnEvent(int,int,int))); connect(m_smf, SIGNAL(signalSMFNoteOff(int,int,int)), SLOT(noteOffEvent(int,int,int))); connect(m_smf, SIGNAL(signalSMFKeyPress(int,int,int)), SLOT(keyPressEvent(int,int,int))); connect(m_smf, SIGNAL(signalSMFCtlChange(int,int,int)), SLOT(ctlChangeEvent(int,int,int))); connect(m_smf, SIGNAL(signalSMFPitchBend(int,int)), SLOT(pitchBendEvent(int,int))); connect(m_smf, SIGNAL(signalSMFProgram(int,int)), SLOT(programEvent(int,int))); connect(m_smf, SIGNAL(signalSMFChanPress(int,int)), SLOT(chanPressEvent(int,int))); connect(m_smf, SIGNAL(signalSMFSysex(const QByteArray&)), SLOT(sysexEvent(const QByteArray&))); connect(m_smf, SIGNAL(signalSMFText(int,const QString&)), SLOT(textEvent(int,const QString&))); connect(m_smf, SIGNAL(signalSMFTempo(int)), SLOT(tempoEvent(int))); connect(m_smf, SIGNAL(signalSMFTrackStart()), SLOT(updateSMFLoadProgress())); connect(m_smf, SIGNAL(signalSMFTrackEnd()), SLOT(updateSMFLoadProgress())); connect(m_smf, SIGNAL(signalSMFendOfTrack()), SLOT(updateSMFLoadProgress())); connect(m_smf, SIGNAL(signalSMFError(const QString&)), SLOT(errorHandler(const QString&))); m_wrk = new QWrk(this); connect(m_wrk, SIGNAL(signalWRKError(const QString&)), SLOT(errorHandlerWRK(const QString&))); connect(m_wrk, SIGNAL(signalWRKUnknownChunk(int,const QByteArray&)), SLOT(unknownChunk(int,const QByteArray&))); connect(m_wrk, SIGNAL(signalWRKHeader(int,int)), SLOT(fileHeader(int,int))); connect(m_wrk, SIGNAL(signalWRKEnd()), SLOT(endOfWrk())); connect(m_wrk, SIGNAL(signalWRKStreamEnd(long)), SLOT(streamEndEvent(long))); connect(m_wrk, SIGNAL(signalWRKGlobalVars()), SLOT(globalVars())); connect(m_wrk, SIGNAL(signalWRKTrack(const QString&, const QString&, int,int,int,int,int,bool,bool,bool)), SLOT(trackHeader(const QString&, const QString&, int,int,int,int,int,bool,bool,bool))); connect(m_wrk, SIGNAL(signalWRKTimeBase(int)), SLOT(timeBase(int))); connect(m_wrk, SIGNAL(signalWRKNote(int,long,int,int,int,int)), SLOT(noteEvent(int,long,int,int,int,int))); connect(m_wrk, SIGNAL(signalWRKKeyPress(int,long,int,int,int)), SLOT(keyPressEvent(int,long,int,int,int))); connect(m_wrk, SIGNAL(signalWRKCtlChange(int,long,int,int,int)), SLOT(ctlChangeEvent(int,long,int,int,int))); connect(m_wrk, SIGNAL(signalWRKPitchBend(int,long,int,int)), SLOT(pitchBendEvent(int,long,int,int))); connect(m_wrk, SIGNAL(signalWRKProgram(int,long,int,int)), SLOT(programEvent(int,long,int,int))); connect(m_wrk, SIGNAL(signalWRKChanPress(int,long,int,int)), SLOT(chanPressEvent(int,long,int,int))); connect(m_wrk, SIGNAL(signalWRKSysexEvent(int,long,int)), SLOT(sysexEvent(int,long,int))); connect(m_wrk, SIGNAL(signalWRKSysex(int,const QString&,bool,int,const QByteArray&)), SLOT(sysexEventBank(int,const QString&,bool,int,const QByteArray&))); connect(m_wrk, SIGNAL(signalWRKText(int,long,int,const QString&)), SLOT(textEvent(int,long,int,const QString&))); connect(m_wrk, SIGNAL(signalWRKTimeSig(int,int,int)), SLOT(timeSigEvent(int,int,int))); connect(m_wrk, SIGNAL(signalWRKKeySig(int,int)), SLOT(keySigEventWRK(int,int))); connect(m_wrk, SIGNAL(signalWRKTempo(long,int)), SLOT(tempoEvent(long,int))); connect(m_wrk, SIGNAL(signalWRKTrackPatch(int,int)), SLOT(trackPatch(int,int))); connect(m_wrk, SIGNAL(signalWRKComments(const QString&)), SLOT(comments(const QString&))); connect(m_wrk, SIGNAL(signalWRKVariableRecord(const QString&,const QByteArray&)), SLOT(variableRecord(const QString&,const QByteArray&))); connect(m_wrk, SIGNAL(signalWRKNewTrack(const QString&,int,int,int,int,int,bool,bool,bool)), SLOT(newTrackHeader(const QString&,int,int,int,int,int,bool,bool,bool))); connect(m_wrk, SIGNAL(signalWRKTrackName(int,const QString&)), SLOT(trackName(int,const QString&))); connect(m_wrk, SIGNAL(signalWRKTrackVol(int,int)), SLOT(trackVol(int,int))); connect(m_wrk, SIGNAL(signalWRKTrackBank(int,int)), SLOT(trackBank(int,int))); connect(m_wrk, SIGNAL(signalWRKSegment(int,long,const QString&)), SLOT(segment(int,long,const QString&))); connect(m_wrk, SIGNAL(signalWRKChord(int,long,const QString&,const QByteArray&)), SLOT(chord(int,long,const QString&,const QByteArray&))); connect(m_wrk, SIGNAL(signalWRKExpression(int,long,int,const QString&)), SLOT(expression(int,long,int,const QString&))); m_player = new Player(m_Client, m_portId); connect(m_player, SIGNAL(finished()), SLOT(songFinished())); connect(m_player, SIGNAL(stopped()), SLOT(playerStopped())); m_Client->startSequencerInput(); tempoReset(); volumeReset(); updateState(EmptyState); } GUIPlayer::~GUIPlayer() { m_Client->stopSequencerInput(); m_Port->detach(); m_Client->close(); delete m_player; } void GUIPlayer::subscribe(const QString& portName) { try { if (!m_subscription.isEmpty()) { m_Port->unsubscribeTo(m_subscription); } m_subscription = portName; m_Port->subscribeTo(m_subscription); } catch (const SequencerError& err) { qWarning() << "SequencerError exception. Error code: " << err.code() << " (" << err.qstrError() << ")"; qWarning() << "Location: " << err.location(); } } void GUIPlayer::updateTimeLabel(int mins, int secs, int cnts) { static QChar fill('0'); QString stime = QString("%1:%2.%3").arg(mins,2,10,fill) .arg(secs,2,10,fill) .arg(cnts,2,10,fill); m_ui->lblTime->setText(stime); } void GUIPlayer::updateState(PlayerState newState) { if (m_state == newState) return; switch (newState) { case EmptyState: m_ui->actionPlay->setEnabled(false); m_ui->actionPause->setEnabled(false); m_ui->actionStop->setEnabled(false); statusBar()->showMessage("Please, load a song"); break; case PlayingState: m_ui->actionPlay->setEnabled(false); m_ui->actionPause->setEnabled(true); m_ui->actionStop->setEnabled(true); statusBar()->showMessage("Playing"); break; case PausedState: m_ui->actionPlay->setEnabled(false); m_ui->actionStop->setEnabled(true); statusBar()->showMessage("Paused"); break; case StoppedState: m_ui->actionPause->setChecked(false); m_ui->actionPause->setEnabled(false); m_ui->actionStop->setEnabled(false); m_ui->actionPlay->setEnabled(true); statusBar()->showMessage("Stopped"); break; default: statusBar()->showMessage("Not initialized"); break; } m_state = newState; } void GUIPlayer::play() { if (!m_song->isEmpty()) { if (m_player->getInitialPosition() == 0) { if (m_initialTempo == 0) { return; } QueueTempo firstTempo = m_Queue->getTempo(); firstTempo.setPPQ(m_song->getDivision()); firstTempo.setTempo(m_initialTempo); firstTempo.setTempoFactor(m_tempoFactor); m_Queue->setTempo(firstTempo); m_Client->drainOutput(); m_player->sendVolumeEvents(); } m_player->start(); updateState(PlayingState); } } void GUIPlayer::pause() { if (m_player->isRunning()) { m_player->stop(); m_player->setPosition(m_Queue->getStatus().getTickTime()); updateState(PausedState); } else if (!m_song->isEmpty()) { m_player->start(); updateState(PlayingState); } } void GUIPlayer::stop() { if (m_player->isRunning()) m_player->stop(); if (m_initialTempo != 0) songFinished(); else updateState(StoppedState); } void GUIPlayer::openFile(const QString& fileName) { QFileInfo finfo(fileName); if (finfo.exists()) { m_song->clear(); m_loadingMessages.clear(); m_tick = 0; m_initialTempo = 0; try { m_pd = new QProgressDialog(0, 0, 0, finfo.size(), this); m_pd->setWindowTitle("Loading MIDI file..."); m_pd->setMinimumDuration(1000); m_pd->setValue(0); QString ext = finfo.suffix().toLower(); if (ext == "wrk") m_wrk->readFromFile(fileName); else if (ext == "mid" || ext == "kar") m_smf->readFromFile(fileName); m_pd->setValue(finfo.size()); if (m_song->isEmpty()) { m_ui->lblName->clear(); m_ui->lblCopyright->clear(); } else { m_song->sort(); m_player->setSong(m_song); m_ui->lblName->setText(finfo.fileName()); m_ui->lblCopyright->setText(m_song->getCopyright()); m_lastDirectory = finfo.absolutePath(); } } catch (...) { m_song->clear(); m_ui->lblName->clear(); m_ui->lblCopyright->clear(); } delete m_pd; if (m_initialTempo == 0) { m_initialTempo = 500000; } updateTimeLabel(0,0,0); updateTempoLabel(6.0e7f / m_initialTempo); m_ui->progressBar->setValue(0); if (!m_loadingMessages.isEmpty()) { m_loadingMessages.insert(0, "Warning, this file may be non-standard or damaged<br>"); QMessageBox::warning(this, QSTR_APPNAME, m_loadingMessages); } if (m_song->isEmpty()) updateState(EmptyState); else updateState(StoppedState); } } void GUIPlayer::open() { QString fileName = QFileDialog::getOpenFileName(this, "Open MIDI File", m_lastDirectory, "MIDI Files (*.mid *.midi);;" "Karaoke files (*.kar);;" "Cakewalk files (*.wrk);;" "All files (*.*)"); if (! fileName.isEmpty() ) { stop(); openFile(fileName); } } void GUIPlayer::setup() { bool ok; int current; QStringList items; QListIterator<PortInfo> it(m_Client->getAvailableOutputs()); while(it.hasNext()) { PortInfo p = it.next(); items << QString("%1:%2").arg(p.getClientName()).arg(p.getPort()); } current = items.indexOf(m_subscription); QString item = QInputDialog::getItem(this, "Player subscription", "Output port:", items, current, false, &ok); if (ok && !item.isEmpty()) { subscribe(item); } } void GUIPlayer::songFinished() { m_player->resetPosition(); updateState(StoppedState); } void GUIPlayer::playerStopped() { int portId = m_Port->getPortId(); for (int channel = 0; channel < 16; ++channel) { ControllerEvent ev1(channel, MIDI_CTL_ALL_NOTES_OFF, 0); ev1.setSource(portId); ev1.setSubscribers(); ev1.setDirect(); m_Client->outputDirect(&ev1); ControllerEvent ev2(channel, MIDI_CTL_ALL_SOUNDS_OFF, 0); ev2.setSource(portId); ev2.setSubscribers(); ev2.setDirect(); m_Client->outputDirect(&ev2); } m_Client->drainOutput(); } void GUIPlayer::updateTempoLabel(float ftempo) { QString stempo = QString("%1 bpm").arg(ftempo, 0, 'f', 2); m_ui->lblOther->setText(stempo); } void GUIPlayer::sequencerEvent(SequencerEvent *ev) { if ((ev->getSequencerType() == SND_SEQ_EVENT_ECHO) && (m_tick != 0)){ int pos = 100 * ev->getTick() / m_tick; const snd_seq_real_time_t* rt = m_Queue->getStatus().getRealtime(); int mins = rt->tv_sec / 60; int secs = rt->tv_sec % 60; int cnts = floor( rt->tv_nsec / 1.0e7 ); updateTempoLabel(m_Queue->getTempo().getRealBPM()); updateTimeLabel(mins, secs, cnts); m_ui->progressBar->setValue(pos); } delete ev; } void GUIPlayer::pitchShift(int value) { m_player->setPitchShift(value); } void GUIPlayer::headerEvent(int format, int ntrks, int division) { m_song->setHeader(format, ntrks, division); updateSMFLoadProgress(); } void GUIPlayer::noteOnEvent(int chan, int pitch, int vol) { SequencerEvent* ev = new NoteOnEvent (chan, pitch, vol); appendSMFEvent(ev); } void GUIPlayer::noteOffEvent(int chan, int pitch, int vol) { SequencerEvent* ev = new NoteOffEvent (chan, pitch, vol); appendSMFEvent(ev); } void GUIPlayer::keyPressEvent(int chan, int pitch, int press) { SequencerEvent* ev = new KeyPressEvent (chan, pitch, press); appendSMFEvent(ev); } void GUIPlayer::ctlChangeEvent(int chan, int ctl, int value) { SequencerEvent* ev = new ControllerEvent (chan, ctl, value); appendSMFEvent(ev); } void GUIPlayer::pitchBendEvent(int chan, int value) { SequencerEvent* ev = new PitchBendEvent (chan, value); appendSMFEvent(ev); } void GUIPlayer::programEvent(int chan, int patch) { SequencerEvent* ev = new ProgramChangeEvent (chan, patch); appendSMFEvent(ev); } void GUIPlayer::chanPressEvent(int chan, int press) { SequencerEvent* ev = new ChanPressEvent (chan, press); appendSMFEvent(ev); } void GUIPlayer::sysexEvent(const QByteArray& data) { SequencerEvent* ev = new SysExEvent (data); appendSMFEvent(ev); } void GUIPlayer::textEvent(int type, const QString& data) { m_song->addText(type, data); updateSMFLoadProgress(); } void GUIPlayer::tempoEvent(int tempo) { if ( m_initialTempo == 0 ) { m_initialTempo = tempo; } SequencerEvent* ev = new TempoEvent (m_queueId, tempo); appendSMFEvent(ev); } void GUIPlayer::errorHandler(const QString& errorStr) { if (m_loadingMessages.length() < 1024) m_loadingMessages.append(QString("%1 at file offset %2<br>") .arg(errorStr).arg(m_smf->getFilePos())); } void GUIPlayer::tempoReset() { m_ui->sliderTempo->setValue(100); tempoSlider(100); } void GUIPlayer::volumeReset() { m_ui->volumeSlider->setValue(100); volumeSlider(100); } void GUIPlayer::tempoSlider(int value) { m_tempoFactor = (value*value + 100.0*value + 20000.0) / 40000.0; QueueTempo qtempo = m_Queue->getTempo(); qtempo.setTempoFactor(m_tempoFactor); m_Queue->setTempo(qtempo); m_Client->drainOutput(); if (!m_player->isRunning()) { updateTempoLabel(qtempo.getRealBPM()); } // Slider tooltip QString tip = QString("%1\%").arg(m_tempoFactor*100.0, 0, 'f', 0); m_ui->sliderTempo->setToolTip(tip); QToolTip::showText(QCursor::pos(), tip, this); } void GUIPlayer::volumeSlider(int value) { QString tip = QString::number(value)+'%'; m_ui->lblVolume->setText(tip); m_ui->volumeSlider->setToolTip(tip); m_player->setVolumeFactor(value); QToolTip::showText(QCursor::pos(), tip, this); } void GUIPlayer::quit() { stop(); m_player->wait(); close(); } void GUIPlayer::dragEnterEvent( QDragEnterEvent * event ) { if (event->mimeData()->hasFormat("text/uri-list")) { event->acceptProposedAction(); } } void GUIPlayer::dropEvent( QDropEvent * event ) { QString data = event->mimeData()->text(); QString fileName = QUrl(data).path().trimmed(); while (fileName.endsWith(QChar::Null)) fileName.chop(1); if ( fileName.endsWith(".mid", Qt::CaseInsensitive) || fileName.endsWith(".midi", Qt::CaseInsensitive) || fileName.endsWith(".kar", Qt::CaseInsensitive) || fileName.endsWith(".wrk", Qt::CaseInsensitive) ) { stop(); openFile(fileName); event->accept(); } else { qDebug() << "Dropped object is not supported:" << fileName; } } bool GUIPlayer::event( QEvent * event ) { if(event->type() == QEvent::Polish) { readSettings(); /* Process the command line arguments. The first argument should be a MIDI file name */ QStringList args = QCoreApplication::arguments(); if (args.size() > 1) { QString first = args.at(1); openFile(first); } event->accept(); } return QMainWindow::event(event); } void GUIPlayer::readSettings() { QSettings settings; settings.beginGroup("Window"); restoreGeometry(settings.value("Geometry").toByteArray()); restoreState(settings.value("State").toByteArray()); settings.endGroup(); settings.beginGroup("Preferences"); m_lastDirectory = settings.value("LastDirectory").toString(); QString midiConn = settings.value("MIDIConnection").toString(); settings.endGroup(); if (midiConn.length() > 0) { subscribe(midiConn); } } void GUIPlayer::writeSettings() { QSettings settings; settings.beginGroup("Window"); settings.setValue("Geometry", saveGeometry()); settings.setValue("State", saveState()); settings.endGroup(); settings.beginGroup("Preferences"); settings.setValue("LastDirectory", m_lastDirectory); settings.setValue("MIDIConnection", m_subscription); settings.endGroup(); } void GUIPlayer::closeEvent( QCloseEvent *event ) { writeSettings(); event->accept(); } void GUIPlayer::about() { if (m_aboutDlg == 0) m_aboutDlg = new About(this); m_aboutDlg->exec(); } void GUIPlayer::aboutQt() { qApp->aboutQt(); } void GUIPlayer::updateSMFLoadProgress() { if (m_pd != 0) { m_pd->setValue(m_smf->getFilePos()); } } void GUIPlayer::appendSMFEvent(SequencerEvent* ev) { unsigned long tick = m_smf->getCurrentTime(); ev->setSource(m_portId); if (ev->getSequencerType() != SND_SEQ_EVENT_TEMPO) { ev->setSubscribers(); } ev->scheduleTick(m_queueId, tick, false); m_song->append(ev); if (tick > m_tick) m_tick = tick; updateSMFLoadProgress(); } /* ********************************* * * Cakewalk WRK file format handling * ********************************* */ void GUIPlayer::updateWRKLoadProgress() { if (m_pd != 0) { m_pd->setValue(m_wrk->getFilePos()); } } void GUIPlayer::appendWRKEvent(unsigned long ticks, int /*trck*/, SequencerEvent* ev) { ev->setSource(m_portId); if (ev->getSequencerType() != SND_SEQ_EVENT_TEMPO) { ev->setSubscribers(); } ev->scheduleTick(m_queueId, ticks, false); m_song->append(ev); if (ticks > m_tick) m_tick = ticks; updateWRKLoadProgress(); qApp->processEvents(); } void GUIPlayer::errorHandlerWRK(const QString& errorStr) { qWarning() << "*** Warning! " << errorStr << " at file offset " << m_wrk->getFilePos(); } void GUIPlayer::fileHeader(int /*verh*/, int /*verl*/) { //fileFormat = QString("WRK file version %1.%2").arg(verh).arg(verl); m_song->setHeader(1, 0, 120); } void GUIPlayer::timeBase(int timebase) { m_song->setDivision(timebase); } void GUIPlayer::globalVars() { keySigEventWRK(0, m_wrk->getKeySig()); updateWRKLoadProgress(); } void GUIPlayer::streamEndEvent(long time) { unsigned long ticks = time; if (ticks > m_tick) m_tick = ticks; } void GUIPlayer::trackHeader( const QString& name1, const QString& name2, int trackno, int channel, int pitch, int velocity, int /*port*/, bool /*selected*/, bool /*muted*/, bool /*loop*/ ) { TrackMapRec rec; rec.channel = channel; rec.pitch = pitch; rec.velocity = velocity; m_trackMap[trackno] = rec; QString trkName = name1 + ' ' + name2; trkName = trkName.trimmed(); if (!trkName.isEmpty()) { SequencerEvent* ev = new TextEvent(trkName, Song::TrackName); appendWRKEvent(0, trackno, ev); } updateWRKLoadProgress(); } void GUIPlayer::noteEvent(int track, long time, int chan, int pitch, int vol, int dur) { int channel = chan; TrackMapRec rec = m_trackMap[track]; int key = pitch + rec.pitch; int velocity = vol + rec.velocity; if (rec.channel > -1) channel = rec.channel; SequencerEvent* ev = new NoteEvent(channel, key, velocity, dur); appendWRKEvent(time, track, ev); } void GUIPlayer::keyPressEvent(int track, long time, int chan, int pitch, int press) { int channel = chan; TrackMapRec rec = m_trackMap[track]; int key = pitch + rec.pitch; if (rec.channel > -1) channel = rec.channel; SequencerEvent* ev = new KeyPressEvent(channel, key, press); appendWRKEvent(time, track, ev); } void GUIPlayer::ctlChangeEvent(int track, long time, int chan, int ctl, int value) { int channel = chan; TrackMapRec rec = m_trackMap[track]; if (rec.channel > -1) channel = rec.channel; SequencerEvent* ev = new ControllerEvent(channel, ctl, value); appendWRKEvent(time, track, ev); } void GUIPlayer::pitchBendEvent(int track, long time, int chan, int value) { int channel = chan; TrackMapRec rec = m_trackMap[track]; if (rec.channel > -1) channel = rec.channel; SequencerEvent* ev = new PitchBendEvent(channel, value); appendWRKEvent(time, track, ev); } void GUIPlayer::programEvent(int track, long time, int chan, int patch) { int channel = chan; TrackMapRec rec = m_trackMap[track]; if (rec.channel > -1) channel = rec.channel; SequencerEvent* ev = new ProgramChangeEvent(channel, patch); appendWRKEvent(time, track, ev); } void GUIPlayer::chanPressEvent(int track, long time, int chan, int press) { int channel = chan; TrackMapRec rec = m_trackMap[track]; if (rec.channel > -1) channel = rec.channel; SequencerEvent* ev = new ChanPressEvent(channel, press); appendWRKEvent(time, track, ev); } void GUIPlayer::sysexEvent(int track, long time, int bank) { SysexEventRec rec; rec.track = track; rec.time = time; rec.bank = bank; m_savedSysexEvents.append(rec); } void GUIPlayer::sysexEventBank(int bank, const QString& /*name*/, bool autosend, int /*port*/, const QByteArray& data) { SysExEvent* ev = new SysExEvent(data); if (autosend) appendWRKEvent(0, 0, ev->clone()); foreach(const SysexEventRec& rec, m_savedSysexEvents) { if (rec.bank == bank) { appendWRKEvent(rec.time, rec.track, ev->clone()); } } delete ev; } void GUIPlayer::textEvent(int track, long time, int type, const QString& data) { SequencerEvent* ev = new TextEvent(data, type); appendWRKEvent(time, track, ev); } void GUIPlayer::timeSigEvent(int bar, int num, int den) { SequencerEvent* ev = new SequencerEvent(); ev->setSequencerType(SND_SEQ_EVENT_TIMESIGN); int div, d = den; for ( div = 0; d > 1; d /= 2 ) ++div; ev->setRaw8(0, num); ev->setRaw8(1, div); ev->setRaw8(2, 24 * 4 / den); ev->setRaw8(3, 8); TimeSigRec newts; newts.bar = bar; newts.num = num; newts.den = den; newts.time = 0; if (m_bars.isEmpty()) { m_bars.append(newts); } else { bool found = false; foreach(const TimeSigRec& ts, m_bars) { if (ts.bar == bar) { newts.time = ts.time; found = true; break; } } if (!found) { TimeSigRec& lasts = m_bars.last(); newts.time = lasts.time + ( lasts.num * 4 / lasts.den * m_song->getDivision() * (bar - lasts.bar) ); m_bars.append(newts); } } appendWRKEvent(newts.time, 0, ev); } void GUIPlayer::keySigEventWRK(int bar, int alt) { SequencerEvent* ev = new SequencerEvent(); ev->setSequencerType(SND_SEQ_EVENT_KEYSIGN); ev->setRaw8(0, alt); long time = 0; foreach(const TimeSigRec& ts, m_bars) { if (ts.bar == bar) { time = ts.time; break; } } appendWRKEvent(time, 0, ev); } void GUIPlayer::tempoEvent(long time, int tempo) { double bpm = tempo / 100.0; if ( m_initialTempo < 0 ) { m_initialTempo = round( bpm ); } SequencerEvent* ev = new TempoEvent(m_queueId, round ( 6e7 / bpm ) ); appendWRKEvent(time, 0, ev); } void GUIPlayer::trackPatch(int track, int patch) { int channel = 0; TrackMapRec rec = m_trackMap[track]; if (rec.channel > -1) channel = rec.channel; programEvent(track, 0, channel, patch); } void GUIPlayer::comments(const QString& cmt) { SequencerEvent* ev = new TextEvent("Comment: " + cmt, Song::Text); appendWRKEvent(0, 0, ev); } void GUIPlayer::variableRecord(const QString& name, const QByteArray& data) { SequencerEvent* ev = NULL; QString s; bool isReadable = ( name == "Title" || name == "Author" || name == "Copyright" || name == "Subtitle" || name == "Instructions" || name == "Keywords" ); if (isReadable) { QByteArray b2 = data.left(qstrlen(data)); if (m_wrk->getTextCodec() == 0) s = QString(b2); else s = m_wrk->getTextCodec()->toUnicode(b2); if ( name == "Title" ) ev = new TextEvent(s, Song::TrackName); else if ( name == "Copyright" ) ev = new TextEvent(s, Song::Copyright); else ev = new TextEvent(name + ": " + s, Song::Text); appendWRKEvent(0, 0, ev); } } void GUIPlayer::newTrackHeader( const QString& name, int trackno, int channel, int pitch, int velocity, int /*port*/, bool /*selected*/, bool /*muted*/, bool /*loop*/ ) { TrackMapRec rec; rec.channel = channel; rec.pitch = pitch; rec.velocity = velocity; m_trackMap[trackno] = rec; if (!name.isEmpty()) textEvent(trackno, 0, Song::TrackName, name); updateWRKLoadProgress(); } void GUIPlayer::trackName(int trackno, const QString& name) { SequencerEvent* ev = new TextEvent(name, Song::TrackName); appendWRKEvent(0, trackno, ev); } void GUIPlayer::trackVol(int track, int vol) { int channel = 0; int lsb, msb; TrackMapRec rec = m_trackMap[track]; if (rec.channel > -1) channel = rec.channel; if (vol < 128) ctlChangeEvent(track, 0, channel, MIDI_CTL_MSB_MAIN_VOLUME, vol); else { lsb = vol % 0x80; msb = vol / 0x80; ctlChangeEvent(track, 0, channel, MIDI_CTL_LSB_MAIN_VOLUME, lsb); ctlChangeEvent(track, 0, channel, MIDI_CTL_MSB_MAIN_VOLUME, msb); } } void GUIPlayer::trackBank(int track, int bank) { // assume GM/GS bank method int channel = 0; int lsb, msb; TrackMapRec rec = m_trackMap[track]; if (rec.channel > -1) channel = rec.channel; lsb = bank % 0x80; msb = bank / 0x80; ctlChangeEvent(track, 0, channel, MIDI_CTL_MSB_BANK, msb); ctlChangeEvent(track, 0, channel, MIDI_CTL_LSB_BANK, lsb); } void GUIPlayer::segment(int track, long time, const QString& name) { if (!name.isEmpty()) { SequencerEvent *ev = new TextEvent("Segment: " + name, Song::Marker); appendWRKEvent(time, track, ev); } } void GUIPlayer::chord(int track, long time, const QString& name, const QByteArray& /*data*/ ) { if (!name.isEmpty()) { SequencerEvent *ev = new TextEvent("Chord: " + name, Song::Text); appendWRKEvent(time, track, ev); } } void GUIPlayer::expression(int track, long time, int /*code*/, const QString& text) { if (!text.isEmpty()) { SequencerEvent *ev = new TextEvent(text, Song::Text); appendWRKEvent(time, track, ev); } } void GUIPlayer::endOfWrk() { if (m_initialTempo < 0) m_initialTempo = 120; SequencerEvent* ev = new SystemEvent(SND_SEQ_EVENT_ECHO); appendWRKEvent(m_tick, 0, ev); } void GUIPlayer::unknownChunk(int /*type*/, const QByteArray& /*data*/) { /*qDebug() << "dec:" << type << "hex:" << hex << type << dec << "size:" << data.length();*/ }