drumstick 2.9.0
C++ MIDI libraries using Qt objects, idioms, and style.
dumpmid.cpp

Print received sequencer events.

Print received sequencer events

/*
MIDI Sequencer C++ library
Copyright (C) 2006-2023, 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef DUMPMIDI_H_
#define DUMPMIDI_H_
/* MidiClient can deliver SequencerEvents with only
* signals or posting QEvents to the QApplication loop */
#undef USE_QEVENTS
//#define USE_QEVENTS
/* To get timestamped events from ALSA, you need a running queue */
//#undef WANT_TIMESTAMPS
#define WANT_TIMESTAMPS
#include <QObject>
#include <QReadWriteLock>
class QDumpMIDI : public QObject, public drumstick::ALSA::SequencerEventHandler
{
Q_OBJECT
public:
QDumpMIDI();
virtual ~QDumpMIDI();
void dumpEvent(drumstick::ALSA::SequencerEvent* ev);
void subscribe(const QString& portName);
void stop();
bool stopped();
void run();
public Q_SLOTS:
void subscription( drumstick::ALSA::MidiPort* port, drumstick::ALSA::Subscription* subs );
#ifdef USE_QEVENTS
protected:
virtual void customEvent( QEvent *ev );
#else
void sequencerEvent( drumstick::ALSA::SequencerEvent* ev );
#endif
private:
#ifdef WANT_TIMESTAMPS
#endif
bool m_Stopped;
QReadWriteLock m_mutex;
};
#endif /*DUMPMIDI_H_*/
Classes managing ALSA Sequencer clients.
Classes managing ALSA Sequencer events.
Classes managing ALSA Sequencer ports.
Classes managing ALSA Sequencer queues.
The QEvent class is the base class of all event classes.
The QObject class is the base class of all Qt objects.
Client management.
Definition: alsaclient.h:219
Port management.
Definition: alsaport.h:125
Queue management.
Definition: alsaqueue.h:201
Sequencer events handler.
Definition: alsaclient.h:196
virtual void handleSequencerEvent(SequencerEvent *ev)=0
Callback function to be implemented by the derived class.
Base class for the event's hierarchy.
Definition: alsaevent.h:68
Subscription management.
Definition: subscription.h:97
Classes managing ALSA sequencer subscriptions.
/*
MIDI Sequencer C++ library
Copyright (C) 2006-2023, 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include "dumpmid.h"
#include <QCommandLineParser>
#include <QCoreApplication>
#include <QObject>
#include <QReadLocker>
#include <QString>
#include <QTextStream>
#include <QWriteLocker>
#include <QIODevice>
#include <QtDebug>
#include <csignal>
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
#define right Qt::right
#define left Qt::left
#define hex Qt::hex
#define dec Qt::dec
#define endl Qt::endl
#endif
QTextStream cout(stdout, QIODevice::WriteOnly);
QTextStream cerr(stderr, QIODevice::WriteOnly);
using namespace drumstick::ALSA;
QDumpMIDI::QDumpMIDI()
: QObject(), m_Stopped(false)
{
m_Client = new MidiClient(this);
m_Client->open();
m_Client->setClientName("DumpMIDI");
#ifndef USE_QEVENTS // using signals instead
connect( m_Client, &MidiClient::eventReceived,
this, &QDumpMIDI::sequencerEvent,
Qt::DirectConnection );
/* note: there is no event loop to handle Qt::QueuedConnection */
#endif
// enable here the callback event delivery
// m_Client->setHandler(this);
m_Port = new MidiPort(this);
m_Port->attach( m_Client );
m_Port->setPortName("DumpMIDI port");
m_Port->setCapability( SND_SEQ_PORT_CAP_WRITE |
SND_SEQ_PORT_CAP_SUBS_WRITE );
m_Port->setPortType( SND_SEQ_PORT_TYPE_APPLICATION |
SND_SEQ_PORT_TYPE_MIDI_GENERIC );
#ifdef WANT_TIMESTAMPS
m_Queue = m_Client->createQueue("DumpMIDI");
m_Port->setTimestamping(true);
//m_Port->setTimestampReal(true);
m_Port->setTimestampQueue(m_Queue->getId());
#endif
connect( m_Port, &MidiPort::subscribed, this, &QDumpMIDI::subscription);
qDebug() << "Trying to subscribe from Announce";
m_Port->subscribeFromAnnounce();
}
QDumpMIDI::~QDumpMIDI()
{
m_Port->detach();
delete m_Port;
m_Client->close();
delete m_Client;
}
bool
QDumpMIDI::stopped()
{
QReadLocker locker(&m_mutex);
return m_Stopped;
}
void
QDumpMIDI::stop()
{
QWriteLocker locker(&m_mutex);
m_Stopped = true;
}
void
QDumpMIDI::subscription(MidiPort*, Subscription* subs)
{
qDebug() << "Subscription made from"
<< subs->getSender()->client << ":"
<< subs->getSender()->port;
}
void QDumpMIDI::subscribe(const QString& portName)
{
try {
qDebug() << "Trying to subscribe" << portName.toLocal8Bit().data();
m_Port->subscribeFrom(portName);
} catch (const SequencerError& err) {
cerr << "SequencerError exception. Error code: " << err.code()
<< " (" << err.qstrError() << ")" << endl;
cerr << "Location: " << err.location() << endl;
throw;
}
}
void QDumpMIDI::run()
{
cout << "Press Ctrl+C to exit" << endl;
#ifdef WANT_TIMESTAMPS
cout << "___Ticks ";
#endif
cout << "Source_ Event_________________ Ch _Data__" << endl;
try {
#ifdef USE_QEVENTS
m_Client->addListener(this);
m_Client->setEventsEnabled(true);
#endif
m_Client->setRealTimeInput(false);
m_Client->startSequencerInput();
#ifdef WANT_TIMESTAMPS
m_Queue->start();
#endif
m_Stopped = false;
while (!stopped()) {
#ifdef USE_QEVENTS
QCoreApplication::sendPostedEvents();
#endif
sleep(1);
}
#ifdef WANT_TIMESTAMPS
m_Queue->stop();
#endif
m_Client->stopSequencerInput();
} catch (const SequencerError& err) {
cerr << "SequencerError exception. Error code: " << err.code()
<< " (" << err.qstrError() << ")" << endl;
cerr << "Location: " << err.location() << endl;
throw;
}
}
void QDumpMIDI::handleSequencerEvent(SequencerEvent *ev)
{
//qDebug() << Q_FUNC_INFO;
dumpEvent(ev);
delete ev;
}
#ifdef USE_QEVENTS
void
QDumpMIDI::customEvent(QEvent *ev)
{
//qDebug() << Q_FUNC_INFO;
if (ev->type() == SequencerEventType) {
SequencerEvent* sev = static_cast<SequencerEvent*>(ev);
if (sev != nullptr) {
dumpEvent(sev);
}
}
}
#else
void
QDumpMIDI::sequencerEvent(SequencerEvent *ev)
{
//qDebug() << Q_FUNC_INFO;
dumpEvent(ev);
delete ev;
}
#endif
void
QDumpMIDI::dumpEvent(SequencerEvent* sev)
{
#ifdef WANT_TIMESTAMPS
cout << qSetFieldWidth(8) << right << sev->getTick();
/* More timestamp options:
cout << sev->getRealTimeSecs();
cout << sev->getRealTimeNanos(); */
/* Getting the time from the queue status object;
QueueStatus sts = m_Queue->getStatus();
cout << qSetFieldWidth(8) << right << sts.getClockTime();
cout << sts.getTickTime(); */
cout << qSetFieldWidth(0) << " ";
#endif
cout << qSetFieldWidth(3) << right << sev->getSourceClient() << qSetFieldWidth(0) << ":";
cout << qSetFieldWidth(3) << left << sev->getSourcePort() << qSetFieldWidth(0) << " ";
switch (sev->getSequencerType()) {
case SND_SEQ_EVENT_NOTEON: {
NoteOnEvent* e = static_cast<NoteOnEvent*>(sev);
if (e != nullptr) {
cout << qSetFieldWidth(23) << left << "Note on";
cout << qSetFieldWidth(2) << right << e->getChannel() << " ";
cout << qSetFieldWidth(3) << e->getKey() << " ";
cout << qSetFieldWidth(3) << e->getVelocity();
}
break;
}
case SND_SEQ_EVENT_NOTEOFF: {
NoteOffEvent* e = static_cast<NoteOffEvent*>(sev);
if (e != nullptr) {
cout << qSetFieldWidth(23) << left << "Note off";
cout << qSetFieldWidth(2) << right << e->getChannel() << " ";
cout << qSetFieldWidth(3) << e->getKey() << " ";
cout << qSetFieldWidth(3) << e->getVelocity();
}
break;
}
case SND_SEQ_EVENT_KEYPRESS: {
KeyPressEvent* e = static_cast<KeyPressEvent*>(sev);
if (e != nullptr) {
cout << qSetFieldWidth(23) << left << "Polyphonic aftertouch";
cout << qSetFieldWidth(2) << right << e->getChannel() << " ";
cout << qSetFieldWidth(3) << e->getKey() << " ";
cout << qSetFieldWidth(3) << e->getVelocity();
}
break;
}
case SND_SEQ_EVENT_CONTROL14:
case SND_SEQ_EVENT_NONREGPARAM:
case SND_SEQ_EVENT_REGPARAM:
case SND_SEQ_EVENT_CONTROLLER: {
ControllerEvent* e = static_cast<ControllerEvent*>(sev);
if (e != nullptr) {
cout << qSetFieldWidth(23) << left << "Control change";
cout << qSetFieldWidth(2) << right << e->getChannel() << " ";
cout << qSetFieldWidth(3) << e->getParam() << " ";
cout << qSetFieldWidth(3) << e->getValue();
}
break;
}
case SND_SEQ_EVENT_PGMCHANGE: {
ProgramChangeEvent* e = static_cast<ProgramChangeEvent*>(sev);
if (e != nullptr) {
cout << qSetFieldWidth(23) << left << "Program change";
cout << qSetFieldWidth(2) << right << e->getChannel() << " ";
cout << qSetFieldWidth(3) << e->getValue();
}
break;
}
case SND_SEQ_EVENT_CHANPRESS: {
ChanPressEvent* e = static_cast<ChanPressEvent*>(sev);
if (e != nullptr) {
cout << qSetFieldWidth(23) << left << "Channel aftertouch";
cout << qSetFieldWidth(2) << right << e->getChannel() << " ";
cout << qSetFieldWidth(3) << e->getValue();
}
break;
}
case SND_SEQ_EVENT_PITCHBEND: {
PitchBendEvent* e = static_cast<PitchBendEvent*>(sev);
if (e != nullptr) {
cout << qSetFieldWidth(23) << left << "Pitch bend";
cout << qSetFieldWidth(2) << right << e->getChannel() << " ";
cout << qSetFieldWidth(5) << e->getValue();
}
break;
}
case SND_SEQ_EVENT_SONGPOS: {
ValueEvent* e = static_cast<ValueEvent*>(sev);
if (e != nullptr) {
cout << qSetFieldWidth(26) << left << "Song position pointer" << qSetFieldWidth(0);
cout << e->getValue();
}
break;
}
case SND_SEQ_EVENT_SONGSEL: {
ValueEvent* e = static_cast<ValueEvent*>(sev);
if (e != nullptr) {
cout << qSetFieldWidth(26) << left << "Song select" << qSetFieldWidth(0);
cout << e->getValue();
}
break;
}
case SND_SEQ_EVENT_QFRAME: {
ValueEvent* e = static_cast<ValueEvent*>(sev);
if (e != nullptr) {
cout << qSetFieldWidth(26) << left << "MTC quarter frame" << qSetFieldWidth(0);
cout << e->getValue();
}
break;
}
case SND_SEQ_EVENT_TIMESIGN: {
ValueEvent* e = static_cast<ValueEvent*>(sev);
if (e != nullptr) {
cout << qSetFieldWidth(26) << left << "SMF time signature" << qSetFieldWidth(0);
cout << hex << e->getValue();
cout << dec;
}
break;
}
case SND_SEQ_EVENT_KEYSIGN: {
ValueEvent* e = static_cast<ValueEvent*>(sev);
if (e != nullptr) {
cout << qSetFieldWidth(26) << left << "SMF key signature" << qSetFieldWidth(0);
cout << hex << e->getValue();
cout << dec;
}
break;
}
case SND_SEQ_EVENT_SETPOS_TICK: {
QueueControlEvent* e = static_cast<QueueControlEvent*>(sev);
if (e != nullptr) {
cout << qSetFieldWidth(26) << left << "Set tick queue pos." << qSetFieldWidth(0);
cout << e->getQueue();
}
break;
}
case SND_SEQ_EVENT_SETPOS_TIME: {
QueueControlEvent* e = static_cast<QueueControlEvent*>(sev);
if (e != nullptr) {
cout << qSetFieldWidth(26) << left << "Set rt queue pos." << qSetFieldWidth(0);
cout << e->getQueue();
}
break;
}
case SND_SEQ_EVENT_TEMPO: {
TempoEvent* e = static_cast<TempoEvent*>(sev);
if (e != nullptr) {
cout << qSetFieldWidth(26) << left << "Set queue tempo";
cout << qSetFieldWidth(3) << right << e->getQueue() << qSetFieldWidth(0) << " ";
cout << e->getValue();
}
break;
}
case SND_SEQ_EVENT_QUEUE_SKEW: {
QueueControlEvent* e = static_cast<QueueControlEvent*>(sev);
if (e != nullptr) {
cout << qSetFieldWidth(26) << left << "Queue timer skew" << qSetFieldWidth(0);
cout << e->getQueue();
}
break;
}
case SND_SEQ_EVENT_START:
cout << left << "Start";
break;
case SND_SEQ_EVENT_STOP:
cout << left << "Stop";
break;
case SND_SEQ_EVENT_CONTINUE:
cout << left << "Continue";
break;
case SND_SEQ_EVENT_CLOCK:
cout << left << "Clock";
break;
case SND_SEQ_EVENT_TICK:
cout << left << "Tick";
break;
case SND_SEQ_EVENT_TUNE_REQUEST:
cout << left << "Tune request";
break;
case SND_SEQ_EVENT_RESET:
cout << left << "Reset";
break;
case SND_SEQ_EVENT_SENSING:
cout << left << "Active Sensing";
break;
case SND_SEQ_EVENT_CLIENT_START: {
ClientEvent* e = static_cast<ClientEvent*>(sev);
if (e != nullptr) {
cout << qSetFieldWidth(26) << left << "Client start"
<< qSetFieldWidth(0) << e->getClient();
}
break;
}
case SND_SEQ_EVENT_CLIENT_EXIT: {
ClientEvent* e = static_cast<ClientEvent*>(sev);
if (e != nullptr) {
cout << qSetFieldWidth(26) << left << "Client exit"
<< qSetFieldWidth(0) << e->getClient();
}
break;
}
case SND_SEQ_EVENT_CLIENT_CHANGE: {
ClientEvent* e = static_cast<ClientEvent*>(sev);
if (e != nullptr) {
cout << qSetFieldWidth(26) << left << "Client changed"
<< qSetFieldWidth(0) << e->getClient();
}
break;
}
case SND_SEQ_EVENT_PORT_START: {
PortEvent* e = static_cast<PortEvent*>(sev);
if (e != nullptr) {
cout << qSetFieldWidth(26) << left << "Port start" << qSetFieldWidth(0);
cout << e->getClient() << ":" << e->getPort();
}
break;
}
case SND_SEQ_EVENT_PORT_EXIT: {
PortEvent* e = static_cast<PortEvent*>(sev);
if (e != nullptr) {
cout << qSetFieldWidth(26) << left << "Port exit" << qSetFieldWidth(0);
cout << e->getClient() << ":" << e->getPort();
}
break;
}
case SND_SEQ_EVENT_PORT_CHANGE: {
PortEvent* e = static_cast<PortEvent*>(sev);
if (e != nullptr) {
cout << qSetFieldWidth(26) << left << "Port changed" << qSetFieldWidth(0);
cout << e->getClient() << ":" << e->getPort();
}
break;
}
case SND_SEQ_EVENT_PORT_SUBSCRIBED: {
SubscriptionEvent* e = static_cast<SubscriptionEvent*>(sev);
if (e != nullptr) {
cout << qSetFieldWidth(26) << left << "Port subscribed" << qSetFieldWidth(0);
cout << e->getSenderClient() << ":" << e->getSenderPort() << " -> ";
cout << e->getDestClient() << ":" << e->getDestPort();
}
break;
}
case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: {
SubscriptionEvent* e = static_cast<SubscriptionEvent*>(sev);
if (e != nullptr) {
cout << qSetFieldWidth(26) << left << "Port unsubscribed" << qSetFieldWidth(0);
cout << e->getSenderClient() << ":" << e->getSenderPort() << " -> ";
cout << e->getDestClient() << ":" << e->getDestPort();
}
break;
}
case SND_SEQ_EVENT_SYSEX: {
SysExEvent* e = static_cast<SysExEvent*>(sev);
if (e != nullptr) {
cout << qSetFieldWidth(26) << left << "System exclusive" << qSetFieldWidth(0);
unsigned int i;
for (i = 0; i < e->getLength(); ++i) {
cout << hex << (unsigned char) e->getData()[i] << " ";
}
cout << dec;
}
break;
}
default:
cout << qSetFieldWidth(26) << "Unknown event type" << qSetFieldWidth(0);
cout << sev->getSequencerType();
};
cout << qSetFieldWidth(0) << endl;
}
QDumpMIDI* test;
void signalHandler(int sig)
{
if (sig == SIGINT)
qDebug() << "Caught a SIGINT. Exiting";
else if (sig == SIGTERM)
qDebug() << "Caught a SIGTERM. Exiting";
test->stop();
}
int main(int argc, char **argv)
{
const QString ERRORSTR = QStringLiteral("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.");
const QString PGM_NAME = QStringLiteral("drumstick-dumpmid");
const QString PGM_DESCRIPTION = QStringLiteral("Drumstick command line utility for decoding MIDI events");
QCoreApplication app(argc, argv);
QCoreApplication::setApplicationName(PGM_NAME);
QCoreApplication::setApplicationVersion(QStringLiteral(QT_STRINGIFY(VERSION)));
QCommandLineParser parser;
parser.setApplicationDescription(PGM_DESCRIPTION);
auto helpOption = parser.addHelpOption();
auto versionOption = parser.addVersionOption();
QCommandLineOption portOption({"p", "port"}, "Source MIDI Port.", "client:port");
parser.addOption(portOption);
parser.process(app);
if (parser.isSet(versionOption) || parser.isSet(helpOption)) {
return 0;
}
try {
test = new QDumpMIDI;
signal(SIGINT, signalHandler);
signal(SIGTERM, signalHandler);
if (parser.isSet(portOption)) {
QString portName = parser.value(portOption);
test->subscribe(portName);
} else {
cerr << "Port argument is mandatory" << endl;
parser.showHelp();
}
test->run();
} catch (const SequencerError& ex) {
cerr << ERRORSTR << " Returned error was: " << ex.qstrError() << endl;
} catch (...) {
cerr << ERRORSTR << endl;
}
delete test;
return 0;
}
Event representing a MIDI channel pressure or after-touch event.
Definition: alsaevent.h:429
int getValue() const
Gets the channel aftertouch value.
Definition: alsaevent.h:446
int getChannel() const
Gets the event's channel.
Definition: alsaevent.h:179
ALSA Event representing a change on some ALSA sequencer client on the system.
Definition: alsaevent.h:709
int getClient() const
Gets the client number.
Definition: alsaevent.h:722
Event representing a MIDI control change event.
Definition: alsaevent.h:325
uint getParam() const
Gets the controller event's parameter.
Definition: alsaevent.h:343
int getValue() const
Gets the controller event's value.
Definition: alsaevent.h:355
int getKey() const
Gets the MIDI note of this event.
Definition: alsaevent.h:202
int getVelocity() const
Gets the note velocity of this event.
Definition: alsaevent.h:214
Event representing a MIDI key pressure, or polyphonic after-touch event.
Definition: alsaevent.h:305
Event representing a note-off MIDI event.
Definition: alsaevent.h:285
Event representing a note-on MIDI event.
Definition: alsaevent.h:265
Event representing a MIDI bender, or pitch wheel event.
Definition: alsaevent.h:399
int getValue() const
Gets the MIDI pitch bend value, zero centered from -8192 to 8191.
Definition: alsaevent.h:416
ALSA Event representing a change on some ALSA sequencer port on the system.
Definition: alsaevent.h:730
int getPort() const
Gets the port number.
Definition: alsaevent.h:743
Event representing a MIDI program change event.
Definition: alsaevent.h:369
int getValue() const
Gets the MIDI program number.
Definition: alsaevent.h:386
ALSA Event representing a queue control command.
Definition: alsaevent.h:542
int getQueue() const
Gets the queue number.
Definition: alsaevent.h:556
int getValue() const
Gets the event's value.
Definition: alsaevent.h:566
Exception class for ALSA Sequencer errors.
unsigned char getSourceClient() const
Gets the source client id.
Definition: alsaevent.h:89
snd_seq_tick_time_t getTick() const
Gets the tick time of the event.
Definition: alsaevent.h:101
unsigned char getSourcePort() const
Gets the source port id.
Definition: alsaevent.h:95
snd_seq_event_type_t getSequencerType() const
Gets the sequencer event type.
Definition: alsaevent.h:81
ALSA Event representing a subscription between two ALSA clients and ports.
Definition: alsaevent.h:663
int getDestClient() const
Gets the destination client number.
Definition: alsaevent.h:696
int getDestPort() const
Gets the destination port number.
Definition: alsaevent.h:701
int getSenderClient() const
Gets the sender client number.
Definition: alsaevent.h:686
int getSenderPort() const
Gets the sender port number.
Definition: alsaevent.h:691
const snd_seq_addr_t * getSender()
Gets the sender address of the subscription (MIDI OUT port)
Event representing a MIDI system exclusive event.
Definition: alsaevent.h:486
ALSA Event representing a tempo change for an ALSA queue.
Definition: alsaevent.h:646
Generic event having a value property.
Definition: alsaevent.h:619
int getValue() const
Gets the event's value.
Definition: alsaevent.h:633
unsigned int getLength() const
Gets the data length.
Definition: alsaevent.h:471
const char * getData() const
Gets the data pointer.
Definition: alsaevent.h:476
QString qstrError() const
Gets the human readable error message from the error code.
int code() const
Gets the numeric error code.
const QString & location() const
Gets the location of the error code as provided in the constructor.
Drumstick ALSA library wrapper.
Definition: alsaclient.cpp:68
SequencerError Exception class.