drumstick  0.5.0
dumpmid.cpp

Print received sequencer events

/*
    MIDI Sequencer C++ library
    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 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

/* Tp get timestamped events from ALSA, you need a running queue */
//#undef WANT_TIMESTAMPS
#define WANT_TIMESTAMPS

#include <QObject>
#include <QReadWriteLock>

#include "alsaevent.h"
#include "alsaclient.h"
#include "alsaport.h"
#include "alsaqueue.h"
#include "subscription.h"

using namespace drumstick;

class QDumpMIDI : public QObject
{
    Q_OBJECT

public:
    QDumpMIDI();
    virtual ~QDumpMIDI();

    void dumpEvent(SequencerEvent* ev);
    void subscribe(const QString& portName);
    void stop();
    bool stopped();
    void run();

public slots:
    void subscription(MidiPort* port, Subscription* subs);

#ifdef USE_QEVENTS
protected:
    virtual void customEvent( QEvent *ev );
#else
    void sequencerEvent( SequencerEvent* ev );
#endif

private:
    MidiClient* m_Client;
    MidiPort* m_Port;
#ifdef WANT_TIMESTAMPS
    MidiQueue* m_Queue;
#endif
    bool m_Stopped;
    QReadWriteLock m_mutex;
};

#endif /*DUMPMIDI_H_*/
/*
    MIDI Sequencer C++ library
    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 "dumpmid.h"
#include "cmdlineargs.h"

#include <signal.h>
#include <QObject>
#include <QString>
#include <QApplication>
#include <QTextStream>
#include <QtDebug>
#include <QReadLocker>
#include <QWriteLocker>

static QTextStream cout(stdout, QIODevice::WriteOnly);
static QTextStream cerr(stderr, QIODevice::WriteOnly);

QDumpMIDI::QDumpMIDI()
    : QObject()
{
    m_Client = new MidiClient(this);
    m_Client->open();
    m_Client->setClientName("DumpMIDI");
#ifndef USE_QEVENTS // using signals instead
    connect( m_Client, SIGNAL(eventReceived(SequencerEvent*)),
                       SLOT(sequencerEvent(SequencerEvent*)),
                       Qt::DirectConnection );
#endif
    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, SIGNAL(subscribed(MidiPort*,Subscription*)),
                     SLOT(subscription(MidiPort*,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 err;
    }
}

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
            QApplication::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 err;
    }
}

#ifdef USE_QEVENTS
void
QDumpMIDI::customEvent(QEvent *ev)
{
    if (ev->type() == SequencerEventType) {
        SequencerEvent* sev = static_cast<SequencerEvent*>(ev);
        if (sev != NULL) {
            dumpEvent(sev);
        }
    }
}
#else
void
QDumpMIDI::sequencerEvent(SequencerEvent *ev)
{
    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 != NULL) {
            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 != NULL) {
            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 != NULL) {
            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 != NULL) {
            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 != NULL) {
            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 != NULL) {
            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 != NULL) {
            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 != NULL) {
            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 != NULL) {
            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 != NULL) {
            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 != NULL) {
            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 != NULL) {
            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 != NULL) {
            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 != NULL) {
            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 != NULL) {
            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 != NULL) {
            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 != NULL) {
            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 != NULL) {
            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 != NULL) {
            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 != NULL) {
            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 != NULL) {
            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 != NULL) {
            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 != NULL) {
            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 != NULL) {
            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 != NULL) {
            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 = "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;
    args.setUsage("[port]");
    args.addOptionalArgument("port", "Source MIDI port");
    args.parse(argc, argv);
    try {
        test = new QDumpMIDI();
        signal(SIGINT, signalHandler);
        signal(SIGTERM, signalHandler);
        QVariant portName = args.getArgument("port");
        if (!portName.isNull())
            test->subscribe(portName.toString());
        test->run();
    } catch (const SequencerError& ex) {
        cerr << errorstr + " Returned error was: " + ex.qstrError() << endl;
    } catch (...) {
        cerr << errorstr << endl;
    }
    delete test;
    return 0;
}