1 /*
2     Standard MIDI File player program
3     Copyright (C) 2006-2021, Pedro Lopez-Cabanillas <plcl@users.sf.net>
4 
5     This library 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 library 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 "playsmf.h"
20 #include <QCommandLineParser>
21 #include <QCoreApplication>
22 #include <QDebug>
23 #include <QFileInfo>
24 #include <QReadLocker>
25 #include <QTextStream>
26 #include <QWriteLocker>
27 #include <QtAlgorithms>
28 #include <csignal>
29 #include <drumstick/sequencererror.h>
30 
31 DISABLE_WARNING_PUSH
32 DISABLE_WARNING_DEPRECATED_DECLARATIONS
33 
34 #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
35 #define right Qt::right
36 #define left Qt::left
37 #define endl Qt::endl
38 #endif
39 
40 QTextStream cout(stdout, QIODevice::WriteOnly);
41 QTextStream cerr(stderr, QIODevice::WriteOnly);
42 
43 /* ********** *
44  * Song class
45  * ********** */
46 using namespace drumstick;
47 using namespace ALSA;
48 using namespace File;
49 
eventLessThan(const SequencerEvent * s1,const SequencerEvent * s2)50 static inline bool eventLessThan(const SequencerEvent* s1, const SequencerEvent *s2)
51 {
52     return s1->getTick() < s2->getTick();
53 }
54 
sort()55 void Song::sort()
56 {
57     std::sort(begin(), end(), eventLessThan);
58 }
59 
clear()60 void Song::clear()
61 {
62     while (!isEmpty())
63         delete takeFirst();
64 }
65 
~Song()66 Song::~Song()
67 {
68     clear();
69 }
70 
71 /* ************* *
72  * PlaySMF class
73  * ************* */
74 
PlaySMF()75 PlaySMF::PlaySMF() :
76     m_division(-1),
77     m_portId(-1),
78     m_queueId(-1),
79     m_initialTempo(-1),
80     m_Stopped(true)
81 {
82     m_Client = new MidiClient(this);
83     m_Client->open();
84     m_Client->setClientName("MIDI Player");
85 
86     m_Port = new MidiPort(this);
87     m_Port->attach( m_Client );
88     m_Port->setPortName("MIDI Player port");
89     m_Port->setCapability(SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ);
90     m_Port->setPortType(SND_SEQ_PORT_TYPE_APPLICATION);
91 
92     m_Queue = m_Client->createQueue();
93     m_queueId = m_Queue->getId();
94     m_portId = m_Port->getPortId();
95 
96     m_engine = new QSmf(this);
97     connect(m_engine, &QSmf::signalSMFHeader, this, &PlaySMF::headerEvent);
98     connect(m_engine, &QSmf::signalSMFNoteOn, this, &PlaySMF::noteOnEvent);
99     connect(m_engine, &QSmf::signalSMFNoteOff, this, &PlaySMF::noteOffEvent);
100     connect(m_engine, &QSmf::signalSMFKeyPress, this, &PlaySMF::keyPressEvent);
101     connect(m_engine, &QSmf::signalSMFCtlChange, this, &PlaySMF::ctlChangeEvent);
102     connect(m_engine, &QSmf::signalSMFPitchBend, this, &PlaySMF::pitchBendEvent);
103     connect(m_engine, &QSmf::signalSMFProgram, this, &PlaySMF::programEvent);
104     connect(m_engine, &QSmf::signalSMFChanPress, this, &PlaySMF::chanPressEvent);
105     connect(m_engine, &QSmf::signalSMFSysex, this, &PlaySMF::sysexEvent);
106     connect(m_engine, &QSmf::signalSMFText, this, &PlaySMF::textEvent);
107     connect(m_engine, &QSmf::signalSMFTempo, this, &PlaySMF::tempoEvent);
108     connect(m_engine, &QSmf::signalSMFTimeSig, this, &PlaySMF::timeSigEvent);
109     connect(m_engine, &QSmf::signalSMFKeySig, this, &PlaySMF::keySigEvent);
110     connect(m_engine, &QSmf::signalSMFError, this, &PlaySMF::errorHandler);
111 }
112 
~PlaySMF()113 PlaySMF::~PlaySMF()
114 {
115     m_Port->detach();
116     m_Client->close();
117 }
118 
subscribe(const QString & portName)119 void PlaySMF::subscribe(const QString& portName)
120 {
121     try {
122         qDebug() << "Trying to subscribe to " << portName.toLocal8Bit().data();
123         m_Port->subscribeTo(portName);
124     } catch (const SequencerError& err) {
125         cerr << "SequencerError exception. Error code: " << err.code()
126              << " (" << err.qstrError() << ")" << endl;
127         cerr << "Location: " << err.location() << endl;
128         throw;
129     }
130 }
131 
stopped()132 bool PlaySMF::stopped()
133 {
134 	QReadLocker locker(&m_mutex);
135     return m_Stopped;
136 }
137 
stop()138 void PlaySMF::stop()
139 {
140 	QWriteLocker locker(&m_mutex);
141     m_Stopped = true;
142     m_Client->dropOutput();
143 }
144 
shutupSound()145 void PlaySMF::shutupSound()
146 {
147     int channel;
148     for (channel = 0; channel < 16; ++channel) {
149         ControllerEvent ev(channel, MIDI_CTL_ALL_SOUNDS_OFF, 0);
150         ev.setSource(static_cast<unsigned char>(m_portId));
151         ev.setSubscribers();
152         ev.setDirect();
153         m_Client->outputDirect(&ev);
154     }
155     m_Client->drainOutput();
156 }
157 
appendEvent(SequencerEvent * ev)158 void PlaySMF::appendEvent(SequencerEvent* ev)
159 {
160     long tick = m_engine->getCurrentTime();
161     ev->setSource(static_cast<unsigned char>(m_portId));
162     if (ev->getSequencerType() != SND_SEQ_EVENT_TEMPO) {
163         ev->setSubscribers();
164     }
165     ev->scheduleTick(m_queueId, static_cast<int>(tick), false);
166     m_song.append(ev);
167 }
168 
dump(const QString & chan,const QString & event,const QString & data)169 void PlaySMF::dump(const QString& chan, const QString& event,
170                    const QString& data)
171 {
172     cout << right << qSetFieldWidth(7) << m_engine->getCurrentTime();
173     cout << qSetFieldWidth(3) << chan;
174     cout << qSetFieldWidth(0) << left << " ";
175     cout << qSetFieldWidth(15) << event;
176     cout << qSetFieldWidth(0) << " " << data << endl;
177 }
178 
dumpStr(const QString & event,const QString & data)179 void PlaySMF::dumpStr(const QString& event, const QString& data)
180 {
181     cout << right << qSetFieldWidth(7) << m_engine->getCurrentTime();
182     cout << qSetFieldWidth(3) << "--";
183     cout << qSetFieldWidth(0) << left << " ";
184     cout << qSetFieldWidth(15) << event;
185     cout << qSetFieldWidth(0) << " " << data << endl;
186 }
187 
headerEvent(int format,int ntrks,int division)188 void PlaySMF::headerEvent(int format, int ntrks, int division)
189 {
190     m_division = division;
191     dumpStr("SMF Header", QString("Format=%1, Tracks=%2, Division=%3").
192             arg(format).arg(ntrks).arg(division));
193 }
194 
noteOnEvent(int chan,int pitch,int vol)195 void PlaySMF::noteOnEvent(int chan, int pitch, int vol)
196 {
197     NoteOnEvent* ev = new NoteOnEvent (chan, pitch, vol);
198     appendEvent(ev);
199 }
200 
noteOffEvent(int chan,int pitch,int vol)201 void PlaySMF::noteOffEvent(int chan, int pitch, int vol)
202 {
203     SequencerEvent* ev = new NoteOffEvent(chan, pitch, vol);
204     appendEvent(ev);
205 }
206 
keyPressEvent(int chan,int pitch,int press)207 void PlaySMF::keyPressEvent(int chan, int pitch, int press)
208 {
209     SequencerEvent* ev = new KeyPressEvent (chan, pitch, press);
210     appendEvent(ev);
211 }
212 
ctlChangeEvent(int chan,int ctl,int value)213 void PlaySMF::ctlChangeEvent(int chan, int ctl, int value)
214 {
215     SequencerEvent* ev = new ControllerEvent (chan, ctl, value);
216     appendEvent(ev);
217 }
218 
pitchBendEvent(int chan,int value)219 void PlaySMF::pitchBendEvent(int chan, int value)
220 {
221     SequencerEvent* ev = new PitchBendEvent (chan, value);
222     appendEvent(ev);
223 }
224 
programEvent(int chan,int patch)225 void PlaySMF::programEvent(int chan, int patch)
226 {
227     SequencerEvent* ev = new ProgramChangeEvent (chan, patch);
228     appendEvent(ev);
229 }
230 
chanPressEvent(int chan,int press)231 void PlaySMF::chanPressEvent(int chan, int press)
232 {
233     SequencerEvent* ev = new ChanPressEvent (chan, press);
234     appendEvent(ev);
235 }
236 
sysexEvent(const QByteArray & data)237 void PlaySMF::sysexEvent(const QByteArray& data)
238 {
239     SysExEvent* ev = new SysExEvent(data);
240     appendEvent(ev);
241 }
242 
textEvent(int typ,const QString & data)243 void PlaySMF::textEvent(int typ, const QString& data)
244 {
245     dumpStr(QString("Text (%1)").arg(typ), data);
246 }
247 
timeSigEvent(int b0,int b1,int b2,int b3)248 void PlaySMF::timeSigEvent(int b0, int b1, int b2, int b3)
249 {
250     dump("--", "Time Signature", QString("%1, %2, %3, %4").arg(b0).arg(b1).arg(b2).arg(b3));
251 }
252 
keySigEvent(int b0,int b1)253 void PlaySMF::keySigEvent(int b0, int b1)
254 {
255     dump("--", "Key Signature", QString("%1, %2").arg(b0).arg(b1));
256 }
257 
tempoEvent(int tempo)258 void PlaySMF::tempoEvent(int tempo)
259 {
260     if ( m_initialTempo < 0 )
261     {
262         m_initialTempo = tempo;
263     }
264     TempoEvent* ev = new TempoEvent(m_queueId, tempo);
265     appendEvent(ev);
266 }
267 
errorHandler(const QString & errorStr)268 void PlaySMF::errorHandler(const QString& errorStr)
269 {
270     cout << "*** Warning! " << errorStr
271          << " at file offset " << m_engine->getFilePos()
272          << endl;
273 }
274 
play(QString fileName)275 void PlaySMF::play(QString fileName)
276 {
277     cout << "Reading song: " << fileName << endl;
278     cout << "___time ch event__________ data____" << endl;
279     m_engine->readFromFile(fileName);
280     m_song.sort();
281     m_Client->setPoolOutput(100);
282 
283     QueueTempo firstTempo = m_Queue->getTempo();
284     firstTempo.setPPQ(m_division);
285     if (m_initialTempo > 0)
286         firstTempo.setTempo(static_cast<unsigned int>(m_initialTempo));
287     m_Queue->setTempo(firstTempo);
288     m_Client->drainOutput();
289     cout << "Starting playback" << endl;
290     cout << "Press Ctrl+C to exit" << endl;
291     try {
292         QListIterator<SequencerEvent*> i(m_song);
293         m_Stopped = false;
294         m_Queue->start();
295         while (!stopped() && i.hasNext()) {
296             //m_Client->outputDirect(i.next());
297             m_Client->output(i.next());
298         }
299         if (stopped()) {
300             m_Queue->clear();
301             shutupSound();
302         } else {
303             m_Client->drainOutput();
304             m_Client->synchronizeOutput();
305         }
306         m_Queue->stop();
307     } catch (const SequencerError& err) {
308         cerr << "SequencerError exception. Error code: " << err.code()
309              << " (" << err.qstrError() << ")" << endl;
310         cerr << "Location: " << err.location() << endl;
311         throw;
312     }
313 }
314 
315 static PlaySMF* player = nullptr;
316 
signalHandler(int sig)317 void signalHandler(int sig)
318 {
319     if (sig == SIGINT)
320         qDebug() << "Caught a SIGINT. Exiting";
321     else if (sig == SIGTERM)
322         qDebug() << "Caught a SIGTERM. Exiting";
323     if (player != nullptr)
324         player->stop();
325 }
326 
main(int argc,char ** argv)327 int main(int argc, char **argv)
328 {
329     const QString PGM_NAME = QStringLiteral("drumstick-playsmf");
330     const QString PGM_DESCRIPTION = QStringLiteral("Drumstick command line MIDI file player");
331     const QString ERRORSTR = QStringLiteral("Fatal error from the ALSA sequencer. "
332         "This usually happens when the kernel doesn't have ALSA support, "
333         "or the device node (/dev/snd/seq) doesn't exists, "
334         "or the kernel module (snd_seq) is not loaded. "
335         "Please check your ALSA/MIDI configuration.");
336 
337     signal(SIGINT, signalHandler);
338     signal(SIGTERM, signalHandler);
339 
340     QCoreApplication app(argc, argv);
341     QCoreApplication::setApplicationName(PGM_NAME);
342     QCoreApplication::setApplicationVersion(QStringLiteral(QT_STRINGIFY(VERSION)));
343 
344     QCommandLineParser parser;
345     parser.setApplicationDescription(PGM_DESCRIPTION);
346     auto helpOption = parser.addHelpOption();
347     auto versionOption = parser.addVersionOption();
348     QCommandLineOption portOption({"p","port"}, "Destination, MIDI port.", "client:port");
349     parser.addOption(portOption);
350     parser.addPositionalArgument("file", "Input SMF File(s).", "files...");
351     parser.process(app);
352 
353     if (parser.isSet(versionOption) || parser.isSet(helpOption)) {
354         return 0;
355     }
356 
357     try {
358         player = new PlaySMF();
359         if (parser.isSet(portOption)) {
360             QString port = parser.value(portOption);
361             player->subscribe(port);
362         } else {
363             cerr << "Port argument is missing" << endl;
364             parser.showHelp();
365         }
366         QStringList files = parser.positionalArguments();
367         if (files.isEmpty()) {
368             cerr << "No input files" << endl;
369             parser.showHelp();
370         }
371         foreach(const QString& f, files) {
372             QFileInfo file(f);
373             if (file.exists())
374                 player->play(file.canonicalFilePath());
375         }
376     } catch (const SequencerError& ex) {
377         cerr << ERRORSTR << " Returned error was: " << ex.qstrError() << endl;
378     } catch (...) {
379         cerr << ERRORSTR << endl;
380     }
381     delete player;
382     return 0;
383 }
384 
385 DISABLE_WARNING_POP
386