/* Standard MIDI File player program Copyright (C) 2006-2010, Pedro Lopez-Cabanillas <plcl@users.sf.net> This library 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 library 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 PLAYSMF_H_ #define PLAYSMF_H_ #include <QObject> #include <QString> #include <QList> #include <QReadWriteLock> #include "qsmf.h" #include "alsaevent.h" #include "alsaclient.h" #include "alsaqueue.h" #include "alsaport.h" using namespace drumstick; class Song : public QList<SequencerEvent*> { public: virtual ~Song(); void sort(); void clear(); }; class PlaySMF : public QObject { Q_OBJECT public: PlaySMF(); virtual ~PlaySMF(); void play(QString fileName); bool stopped(); void stop(); void appendEvent(SequencerEvent* ev); void subscribe(const QString& portName); void dump(const QString& chan, const QString& event, const QString& data); void dumpStr(const QString& event, const QString& data); void shutupSound(); //void usage(); //void info(); public 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 typ, const QString& data); void tempoEvent(int tempo); void timeSigEvent(int b0, int b1, int b2, int b3); void keySigEvent(int b0, int b1); void errorHandler(const QString& errorStr); // void trackStartEvent(); // void trackEndEvent(); // void endOfTrackEvent(); // void variableEvent(const QByteArray& data); // void metaMiscEvent(int typ, const QByteArray& data); // void seqNum(int seq); // void forcedChannel(int channel); // void forcedPort(int port); // void smpteEvent(int b0, int b1, int b2, int b3, int b4); private: int m_division; int m_portId; int m_queueId; int m_initialTempo; bool m_Stopped; QReadWriteLock m_mutex; Song m_song; QSmf* m_engine; MidiClient* m_Client; MidiPort* m_Port; MidiQueue* m_Queue; }; #endif /*PLAYSMF_H_*/
/* Standard MIDI File player program Copyright (C) 2006-2010, Pedro Lopez-Cabanillas <plcl@users.sf.net> This library 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 library 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 "playsmf.h" #include "cmdlineargs.h" #include <signal.h> #include <QApplication> #include <QTextStream> #include <QtAlgorithms> #include <QFileInfo> #include <QReadLocker> #include <QWriteLocker> static QTextStream cout(stdout, QIODevice::WriteOnly); static QTextStream cerr(stderr, QIODevice::WriteOnly); /* ********** * * Song class * ********** */ static inline bool eventLessThan(const SequencerEvent* s1, const SequencerEvent *s2) { return s1->getTick() < s2->getTick(); } void Song::sort() { qStableSort(begin(), end(), eventLessThan); } void Song::clear() { while (!isEmpty()) delete takeFirst(); } Song::~Song() { clear(); } /* ************* * * PlaySMF class * ************* */ PlaySMF::PlaySMF() : m_division(-1), m_portId(-1), m_queueId(-1), m_initialTempo(-1), m_Stopped(true) { m_Client = new MidiClient(this); m_Client->open(); m_Client->setClientName("MIDI Player"); m_Port = new MidiPort(this); m_Port->attach( m_Client ); m_Port->setPortName("MIDI Player port"); m_Port->setCapability(SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ); m_Port->setPortType(SND_SEQ_PORT_TYPE_APPLICATION); m_Queue = m_Client->createQueue(); m_queueId = m_Queue->getId(); m_portId = m_Port->getPortId(); m_engine = new QSmf(this); connect(m_engine, SIGNAL(signalSMFHeader(int,int,int)), SLOT(headerEvent(int,int,int))); connect(m_engine, SIGNAL(signalSMFNoteOn(int,int,int)), SLOT(noteOnEvent(int,int,int))); connect(m_engine, SIGNAL(signalSMFNoteOff(int,int,int)), SLOT(noteOffEvent(int,int,int))); connect(m_engine, SIGNAL(signalSMFKeyPress(int,int,int)), SLOT(keyPressEvent(int,int,int))); connect(m_engine, SIGNAL(signalSMFCtlChange(int,int,int)), SLOT(ctlChangeEvent(int,int,int))); connect(m_engine, SIGNAL(signalSMFPitchBend(int,int)), SLOT(pitchBendEvent(int,int))); connect(m_engine, SIGNAL(signalSMFProgram(int,int)), SLOT(programEvent(int,int))); connect(m_engine, SIGNAL(signalSMFChanPress(int,int)), SLOT(chanPressEvent(int,int))); connect(m_engine, SIGNAL(signalSMFSysex(const QByteArray&)), SLOT(sysexEvent(const QByteArray&))); connect(m_engine, SIGNAL(signalSMFText(int,const QString&)), SLOT(textEvent(int,const QString&))); connect(m_engine, SIGNAL(signalSMFTempo(int)), SLOT(tempoEvent(int))); connect(m_engine, SIGNAL(signalSMFTimeSig(int,int,int,int)), SLOT(timeSigEvent(int,int,int,int))); connect(m_engine, SIGNAL(signalSMFKeySig(int,int)), SLOT(keySigEvent(int,int))); connect(m_engine, SIGNAL(signalSMFError(const QString&)), SLOT(errorHandler(const QString&))); //connect(m_engine, SIGNAL(signalSMFTrackStart()), SLOT(trackStartEvent())); //connect(m_engine, SIGNAL(signalSMFTrackEnd()), SLOT(trackEndEvent())); //connect(m_engine, SIGNAL(signalSMFendOfTrack()), SLOT(endOfTrackEvent())); //connect(m_engine, SIGNAL(signalSMFMetaMisc(int, const QByteArray&)), SLOT(metaMiscEvent(int, const QByteArray&))); //connect(m_engine, SIGNAL(signalSMFVariable(const QByteArray&)), SLOT(variableEvent(const QByteArray&))); //connect(m_engine, SIGNAL(signalSMFSequenceNum(int)), SLOT(seqNum(int))); //connect(m_engine, SIGNAL(signalSMFforcedChannel(int)), SLOT(forcedChannel(int))); //connect(m_engine, SIGNAL(signalSMFforcedPort(int)), SLOT(forcedPort(int))); //connect(m_engine, SIGNAL(signalSMFSmpte(int,int,int,int,int)), SLOT(smpteEvent(int,int,int,int,int))); } PlaySMF::~PlaySMF() { m_Port->detach(); m_Client->close(); } void PlaySMF::subscribe(const QString& portName) { try { qDebug() << "Trying to subscribe to " << portName.toLocal8Bit().data(); m_Port->subscribeTo(portName); } catch (const SequencerError& err) { cerr << "SequencerError exception. Error code: " << err.code() << " (" << err.qstrError() << ")" << endl; cerr << "Location: " << err.location() << endl; throw err; } } bool PlaySMF::stopped() { QReadLocker locker(&m_mutex); return m_Stopped; } void PlaySMF::stop() { QWriteLocker locker(&m_mutex); m_Stopped = true; m_Client->dropOutput(); } void PlaySMF::shutupSound() { int channel; for (channel = 0; channel < 16; ++channel) { ControllerEvent ev(channel, MIDI_CTL_ALL_SOUNDS_OFF, 0); ev.setSource(m_portId); ev.setSubscribers(); ev.setDirect(); m_Client->outputDirect(&ev); } m_Client->drainOutput(); } void PlaySMF::appendEvent(SequencerEvent* ev) { long tick = m_engine->getCurrentTime(); ev->setSource(m_portId); if (ev->getSequencerType() != SND_SEQ_EVENT_TEMPO) { ev->setSubscribers(); } ev->scheduleTick(m_queueId, tick, false); m_song.append(ev); } void PlaySMF::dump(const QString& chan, const QString& event, const QString& data) { cout << right << qSetFieldWidth(7) << m_engine->getCurrentTime(); cout << qSetFieldWidth(3) << chan; cout << qSetFieldWidth(0) << left << " "; cout << qSetFieldWidth(15) << event; cout << qSetFieldWidth(0) << " " << data << endl; } void PlaySMF::dumpStr(const QString& event, const QString& data) { cout << right << qSetFieldWidth(7) << m_engine->getCurrentTime(); cout << qSetFieldWidth(3) << "--"; cout << qSetFieldWidth(0) << left << " "; cout << qSetFieldWidth(15) << event; cout << qSetFieldWidth(0) << " " << data << endl; } void PlaySMF::headerEvent(int format, int ntrks, int division) { m_division = division; dumpStr("SMF Header", QString("Format=%1, Tracks=%2, Division=%3"). arg(format).arg(ntrks).arg(division)); } void PlaySMF::noteOnEvent(int chan, int pitch, int vol) { NoteOnEvent* ev = new NoteOnEvent (chan, pitch, vol); appendEvent(ev); } void PlaySMF::noteOffEvent(int chan, int pitch, int vol) { SequencerEvent* ev = new NoteOffEvent(chan, pitch, vol); appendEvent(ev); } void PlaySMF::keyPressEvent(int chan, int pitch, int press) { SequencerEvent* ev = new KeyPressEvent (chan, pitch, press); appendEvent(ev); } void PlaySMF::ctlChangeEvent(int chan, int ctl, int value) { SequencerEvent* ev = new ControllerEvent (chan, ctl, value); appendEvent(ev); } void PlaySMF::pitchBendEvent(int chan, int value) { SequencerEvent* ev = new PitchBendEvent (chan, value); appendEvent(ev); } void PlaySMF::programEvent(int chan, int patch) { SequencerEvent* ev = new ProgramChangeEvent (chan, patch); appendEvent(ev); } void PlaySMF::chanPressEvent(int chan, int press) { SequencerEvent* ev = new ChanPressEvent (chan, press); appendEvent(ev); } void PlaySMF::sysexEvent(const QByteArray& data) { SysExEvent* ev = new SysExEvent(data); appendEvent(ev); } void PlaySMF::textEvent(int typ, const QString& data) { dumpStr(QString("Text (%1)").arg(typ), data); } void PlaySMF::timeSigEvent(int b0, int b1, int b2, int b3) { dump("--", "Time Signature", QString("%1, %2, %3, %4").arg(b0).arg(b1).arg(b2).arg(b3)); } void PlaySMF::keySigEvent(int b0, int b1) { dump("--", "Key Signature", QString("%1, %2").arg(b0).arg(b1)); } void PlaySMF::tempoEvent(int tempo) { if ( m_initialTempo < 0 ) { m_initialTempo = tempo; } TempoEvent* ev = new TempoEvent(m_queueId, tempo); appendEvent(ev); } void PlaySMF::errorHandler(const QString& errorStr) { cout << "*** Warning! " << errorStr << " at file offset " << m_engine->getFilePos() << endl; } void PlaySMF::play(QString fileName) { cout << "Reading song: " << fileName << endl; cout << "___time ch event__________ data____" << endl; m_engine->readFromFile(fileName); m_song.sort(); m_Client->setPoolOutput(100); QueueTempo firstTempo = m_Queue->getTempo(); firstTempo.setPPQ(m_division); if (m_initialTempo > 0) firstTempo.setTempo(m_initialTempo); m_Queue->setTempo(firstTempo); m_Client->drainOutput(); cout << "Starting playback" << endl; cout << "Press Ctrl+C to exit" << endl; try { QListIterator<SequencerEvent*> i(m_song); m_Stopped = false; m_Queue->start(); while (!stopped() && i.hasNext()) { //m_Client->outputDirect(i.next()); m_Client->output(i.next()); } if (stopped()) { m_Queue->clear(); shutupSound(); } else { m_Client->drainOutput(); m_Client->synchronizeOutput(); } m_Queue->stop(); } catch (const SequencerError& err) { cerr << "SequencerError exception. Error code: " << err.code() << " (" << err.qstrError() << ")" << endl; cerr << "Location: " << err.location() << endl; throw err; } } /* void PlaySMF::usage() { cout << "Error: wrong parameters" << endl; cout << "Usage:" << endl; cout << "\tplaysmf PORT FILE.MID" << endl; } void PlaySMF::info() { SystemInfo info = m_Client->getSystemInfo(); cout << "ALSA Sequencer System Info" << endl; cout << "\tMax Clients: " << info.getMaxClients() << endl; cout << "\tMax Ports: " << info.getMaxPorts() << endl; cout << "\tMax Queues: " << info.getMaxQueues() << endl; cout << "\tMax Channels: " << info.getMaxChannels() << endl; cout << "\tCurrent Queues: " << info.getCurrentQueues() << endl; cout << "\tCurrent Clients: " << info.getCurrentClients() << endl; } */ PlaySMF* player = 0; void signalHandler(int sig) { if (sig == SIGINT) qDebug() << "Caught a SIGINT. Exiting"; else if (sig == SIGTERM) qDebug() << "Caught a SIGTERM. Exiting"; if (player != 0) player->stop(); } int main(int argc, char **argv) { const QString errorstr = "Fatal error from the ALSA sequencer. " "This usually happens when the kernel doesn't have ALSA support, " "or the device node (/dev/snd/seq) doesn't exists, " "or the kernel module (snd_seq) is not loaded. " "Please check your ALSA/MIDI configuration."; CmdLineArgs args; signal(SIGINT, signalHandler); signal(SIGTERM, signalHandler); args.setUsage("[options] port file..."); args.addRequiredArgument("port", "Destination, MIDI port"); args.addMultipleArgument("file", "Input SMF(s)"); args.parse(argc, argv); try { player = new PlaySMF(); QVariant port = args.getArgument("port"); if (!port.isNull()) player->subscribe(port.toString()); QVariantList files = args.getArguments("file"); foreach(const QVariant& f, files) { QFileInfo file(f.toString()); if (file.exists()) player->play(file.canonicalFilePath()); } //player->info(); } catch (const SequencerError& ex) { cerr << errorstr + " Returned error was: " + ex.qstrError() << endl; } catch (...) { cerr << errorstr << endl; } delete player; return 0; }