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