1 /*
2     Standard MIDI simple metronome
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 "metronome.h"
20 #include <QCommandLineParser>
21 #include <QCoreApplication>
22 #include <QDebug>
23 #include <QReadLocker>
24 #include <QStringList>
25 #include <QTextStream>
26 #include <QIODevice>
27 #include <QWriteLocker>
28 #include <QtAlgorithms>
29 #include <csignal>
30 #include <drumstick/alsatimer.h>
31 #include <drumstick/sequencererror.h>
32 
33 #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
34 #define right Qt::right
35 #define left Qt::left
36 #define endl Qt::endl
37 #endif
38 
39 QTextStream cout(stdout, QIODevice::WriteOnly);
40 QTextStream cerr(stderr, QIODevice::WriteOnly);
41 
42 using namespace drumstick::ALSA;
43 
44 /* *************** *
45  * Metronome class *
46  * *************** */
47 
Metronome(QObject * parent)48 Metronome::Metronome(QObject *parent) : QObject(parent),
49     m_weak_note(METRONOME_WEAK_NOTE),
50     m_strong_note(METRONOME_STRONG_NOTE),
51     m_weak_velocity(METRONOME_VELOCITY),
52     m_strong_velocity(METRONOME_VELOCITY),
53     m_program(METRONOME_PROGRAM),
54     m_channel(METRONOME_CHANNEL),
55     m_volume(METRONOME_VOLUME),
56     m_pan(METRONOME_PAN),
57     m_resolution(METRONOME_RESOLUTION),
58     m_bpm(TEMPO_DEFAULT),
59     m_ts_num(RHYTHM_TS_NUM),
60     m_ts_div(RHYTHM_TS_DEN),
61     m_noteDuration(NOTE_DURATION),
62     m_portId(-1),
63     m_queueId(-1),
64     m_clientId(-1),
65     m_Stopped(true)
66 {
67     QString name{QStringLiteral("Metronome")};
68     m_Client = new MidiClient(this);
69     m_Client->open();
70     m_Client->setClientName(name);
71     m_Client->setHandler(this);
72     m_Port = new MidiPort(this);
73     m_Port->attach( m_Client );
74     m_Port->setPortName(name);
75     m_Port->setCapability( SND_SEQ_PORT_CAP_READ |
76                            SND_SEQ_PORT_CAP_SUBS_READ |
77                            SND_SEQ_PORT_CAP_WRITE );
78     m_Port->setPortType( SND_SEQ_PORT_TYPE_MIDI_GENERIC |
79                          SND_SEQ_PORT_TYPE_APPLICATION );
80     m_Queue = m_Client->createQueue(name);
81     m_clientId = m_Client->getClientId();
82     m_queueId = m_Queue->getId();
83     m_portId = m_Port->getPortId();
84     m_Port->setTimestamping(true);
85     m_Port->setTimestampQueue(m_queueId);
86     // Get and apply the best available timer
87     TimerId best = Timer::bestGlobalTimerId();
88     QueueTimer qtimer;
89     qtimer.setId(best);
90     m_Queue->setTimer(qtimer);
91     // Start sequencer input
92     m_Client->setRealTimeInput(false);
93     m_Client->startSequencerInput();
94 }
95 
~Metronome()96 Metronome::~Metronome()
97 {
98     m_Port->detach();
99     m_Client->close();
100 }
101 
handleSequencerEvent(SequencerEvent * ev)102 void Metronome::handleSequencerEvent( SequencerEvent *ev )
103 {
104     if (ev->getSequencerType() == SND_SEQ_EVENT_USR0)
105         metronome_pattern(static_cast<int>(ev->getTick()) + m_patternDuration);
106     delete ev;
107 }
108 
metronome_event_output(SequencerEvent * ev)109 void Metronome::metronome_event_output(SequencerEvent* ev)
110 {
111     ev->setSource(static_cast<unsigned char>(m_portId));
112     ev->setSubscribers();
113     ev->setDirect();
114     m_Client->outputDirect(ev);
115 }
116 
sendControlChange(int cc,int value)117 void Metronome::sendControlChange(int cc, int value)
118 {
119     ControllerEvent ev(m_channel, cc, value);
120     metronome_event_output(&ev);
121 }
122 
sendInitialControls()123 void Metronome::sendInitialControls()
124 {
125     metronome_set_program();
126     metronome_set_controls();
127     metronome_set_tempo();
128 }
129 
metronome_set_program()130 void Metronome::metronome_set_program()
131 {
132     ProgramChangeEvent ev(m_channel, m_program);
133     metronome_event_output(&ev);
134 }
135 
metronome_schedule_event(SequencerEvent * ev,int tick,bool lb)136 void Metronome::metronome_schedule_event(SequencerEvent* ev, int tick, bool lb)
137 {
138     ev->setSource(static_cast<unsigned char>(m_portId));
139     if (lb) // loop back
140         ev->setDestination(static_cast<unsigned char>(m_clientId), static_cast<unsigned char>(m_portId));
141     else
142         ev->setSubscribers();
143     ev->scheduleTick(m_queueId, tick, false);
144     m_Client->outputDirect(ev);
145 }
146 
metronome_note(int note,int tick)147 void Metronome::metronome_note(int note, int tick)
148 {
149     NoteEvent ev(m_channel, note, METRONOME_VELOCITY, m_noteDuration);
150     metronome_schedule_event(&ev, tick, false);
151 }
152 
metronome_echo(int tick)153 void Metronome::metronome_echo(int tick)
154 {
155     SystemEvent ev(SND_SEQ_EVENT_USR0);
156     metronome_schedule_event(&ev, tick, true);
157 }
158 
metronome_pattern(int tick)159 void Metronome::metronome_pattern(int tick)
160 {
161     int j, t, duration;
162     t = tick;
163     duration = m_resolution * 4 / m_ts_div;
164     for (j = 0; j < m_ts_num; j++) {
165         metronome_note(j ? m_weak_note : m_strong_note, t);
166         t += duration;
167     }
168     metronome_echo(t);
169 }
170 
metronome_set_tempo()171 void Metronome::metronome_set_tempo()
172 {
173     QueueTempo t = m_Queue->getTempo();
174     t.setPPQ(m_resolution);
175     t.setNominalBPM(m_bpm);
176     m_Queue->setTempo(t);
177     m_Client->drainOutput();
178 }
179 
metronome_set_controls()180 void Metronome::metronome_set_controls()
181 {
182     sendControlChange(MIDI_CTL_MSB_MAIN_VOLUME, m_volume);
183     sendControlChange(MIDI_CTL_MSB_PAN, m_pan);
184 }
185 
subscribe(const QString & portName)186 void Metronome::subscribe(const QString& portName)
187 {
188     m_Port->subscribeTo(portName);
189 }
190 
stopped()191 bool Metronome::stopped()
192 {
193 	QReadLocker locker(&m_mutex);
194     return m_Stopped;
195 }
196 
stop()197 void Metronome::stop()
198 {
199 	QWriteLocker locker(&m_mutex);
200     m_Stopped = true;
201     m_Client->dropOutput();
202 }
203 
shutupSound()204 void Metronome::shutupSound()
205 {
206     sendControlChange( MIDI_CTL_ALL_NOTES_OFF, 0 );
207     sendControlChange( MIDI_CTL_ALL_SOUNDS_OFF, 0 );
208 }
209 
play(QString tempo)210 void Metronome::play(QString tempo)
211 {
212     bool ok;
213     m_Stopped = false;
214     m_patternDuration = m_resolution * 4 / m_ts_div * m_ts_num;
215     m_bpm = tempo.toInt(&ok);
216     if (!ok) m_bpm = TEMPO_DEFAULT;
217     cout << "Metronome playing. " << m_bpm << " bpm" << endl;
218     cout << "Press Ctrl+C to exit" << endl;
219     try {
220         sendInitialControls();
221         m_Queue->start();
222         metronome_pattern(0);
223         metronome_pattern(m_patternDuration);
224         while (!stopped())
225             sleep(1);
226     } catch (const SequencerError& err) {
227         cerr << "SequencerError exception. Error code: " << err.code()
228              << " (" << err.qstrError() << ")" << endl;
229         cerr << "Location: " << err.location() << endl;
230     }
231 }
232 
233 static Metronome* metronome = nullptr;
234 
signalHandler(int sig)235 void signalHandler(int sig)
236 {
237     if (sig == SIGINT)
238         qDebug() << "Caught a SIGINT. Exiting";
239     else if (sig == SIGTERM)
240         qDebug() << "Caught a SIGTERM. Exiting";
241     if (metronome != nullptr) {
242         metronome->stop();
243         metronome->shutupSound();
244     }
245 }
246 
main(int argc,char ** argv)247 int main(int argc, char **argv)
248 {
249     const QString PGM_NAME = QStringLiteral("drumstick-metronome");
250     const QString PGM_DESCRIPTION = QStringLiteral("ALSA based command line metronome");
251     const QString ERRORSTR = QStringLiteral("Fatal error from the ALSA sequencer. "
252         "This usually happens when the kernel doesn't have ALSA support, "
253         "or the device node (/dev/snd/seq) doesn't exists, "
254         "or the kernel module (snd_seq) is not loaded. "
255         "Please check your ALSA/MIDI configuration.");
256 
257     signal(SIGINT, signalHandler);
258     signal(SIGTERM, signalHandler);
259 
260     QCoreApplication app(argc, argv);
261     QCoreApplication::setApplicationName(PGM_NAME);
262     QCoreApplication::setApplicationVersion(QStringLiteral(QT_STRINGIFY(VERSION)));
263 
264     QCommandLineParser parser;
265     parser.setApplicationDescription(PGM_DESCRIPTION);
266     auto helpOption = parser.addHelpOption();
267     auto versionOption = parser.addVersionOption();
268     QCommandLineOption portOption({"p","port"}, "Destination, MIDI port identifier.", "client:port");
269     parser.addOption(portOption);
270     QCommandLineOption bpmOption({"b","bpm"}, "Tempo, in beats per minute (default=120).", "BPM", "120");
271     parser.addOption(bpmOption);
272     parser.process(app);
273 
274     if (parser.isSet(versionOption) || parser.isSet(helpOption)) {
275         return 0;
276     }
277 
278     try {
279         metronome = new Metronome();
280         if (parser.isSet(portOption)) {
281             QString port = parser.value(portOption);
282             metronome->subscribe(port);
283         } else {
284             cerr << "Destination Port is mandatory" << endl;
285             parser.showHelp();
286         }
287 
288         QString bpm("120");
289         if (parser.isSet(bpmOption)) {
290             bpm = parser.value(bpmOption);
291         }
292         metronome->play(bpm);
293 
294     } catch (const SequencerError& ex) {
295         cerr << ERRORSTR << " Returned error was: " << ex.qstrError() << endl;
296     } catch (...) {
297         cerr << ERRORSTR << endl;
298     }
299     delete metronome;
300     return 0;
301 }
302