metronome.cpp

Simple command line MIDI metronome

/*
    Standard MIDI simple metronome
    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 METRONOME_H
#define METRONOME_H

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

#include <QObject>
#include <QString>
#include <QList>
#include <QReadWriteLock>

const int TEMPO_DEFAULT(120);
const int NOTE_DURATION(10);
const int RHYTHM_TS_NUM(4);
const int RHYTHM_TS_DEN(4);
const int METRONOME_CHANNEL(9);
const int METRONOME_STRONG_NOTE(34);
const int METRONOME_WEAK_NOTE(33);
const int METRONOME_VELOCITY(100);
const int METRONOME_PROGRAM(0);
const int METRONOME_RESOLUTION(240);
const int METRONOME_VOLUME(100);
const int METRONOME_PAN(64);

using namespace drumstick;

class Metronome : public QObject, public SequencerEventHandler
{
    Q_OBJECT

public:
    Metronome(QObject *parent = 0);
    virtual ~Metronome();
    void play(QString tempo);
    bool stopped();
    void stop();
    void subscribe(const QString& portName);
    void shutupSound();
    //void usage();
    //void info();

    void sendControlChange( int cc, int value );
    void sendInitialControls();
    void metronome_note(int note, int tick);
    void metronome_echo(int tick);
    void metronome_pattern(int tick);
    void metronome_event_output(SequencerEvent* ev);
    void metronome_schedule_event(SequencerEvent* ev, int tick, bool lb);
    void metronome_set_program();
    void metronome_set_tempo();
    void metronome_set_controls();

    // SequencerEventHandler interface
    void handleSequencerEvent( SequencerEvent* ev );

private:
    int m_weak_note;
    int m_strong_note;
    int m_weak_velocity;
    int m_strong_velocity;
    int m_program;
    int m_channel;
    int m_volume;
    int m_pan;
    int m_resolution;
    int m_bpm;
    int m_ts_num; /* time signature: numerator */
    int m_ts_div; /* time signature: denominator */
    int m_noteDuration;
    int m_patternDuration;

    int m_portId;
    int m_queueId;
    int m_clientId;
    bool m_Stopped;
    QReadWriteLock m_mutex;
    MidiClient* m_Client;
    MidiPort* m_Port;
    MidiQueue* m_Queue;
};

#endif /*METRONOME_H*/
/*
    Standard MIDI simple metronome
    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 "metronome.h"
#include "alsatimer.h"
#include "cmdlineargs.h"

#include <signal.h>
#include <QApplication>
#include <QTextStream>
#include <QtAlgorithms>
#include <QStringList>
#include <QReadLocker>
#include <QWriteLocker>

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

/* *************** *
 * Metronome class *
 * *************** */

Metronome::Metronome(QObject *parent) : QObject(parent),
    m_weak_note(METRONOME_WEAK_NOTE),
    m_strong_note(METRONOME_STRONG_NOTE),
    m_weak_velocity(METRONOME_VELOCITY),
    m_strong_velocity(METRONOME_VELOCITY),
    m_program(METRONOME_PROGRAM),
    m_channel(METRONOME_CHANNEL),
    m_volume(METRONOME_VOLUME),
    m_pan(METRONOME_PAN),
    m_resolution(METRONOME_RESOLUTION),
    m_bpm(TEMPO_DEFAULT),
    m_ts_num(RHYTHM_TS_NUM),
    m_ts_div(RHYTHM_TS_DEN),
    m_noteDuration(NOTE_DURATION),
    m_portId(-1),
    m_queueId(-1),
    m_clientId(-1),
    m_Stopped(true)
{
    QLatin1String name("Metronome");
    m_Client = new MidiClient(this);
    m_Client->open();
    m_Client->setClientName(name);
    m_Client->setHandler(this);
    m_Port = new MidiPort(this);
    m_Port->attach( m_Client );
    m_Port->setPortName(name);
    m_Port->setCapability( SND_SEQ_PORT_CAP_READ |
                           SND_SEQ_PORT_CAP_SUBS_READ |
                           SND_SEQ_PORT_CAP_WRITE );
    m_Port->setPortType( SND_SEQ_PORT_TYPE_MIDI_GENERIC |
                         SND_SEQ_PORT_TYPE_APPLICATION );
    m_Queue = m_Client->createQueue(name);
    m_clientId = m_Client->getClientId();
    m_queueId = m_Queue->getId();
    m_portId = m_Port->getPortId();
    m_Port->setTimestamping(true);
    m_Port->setTimestampQueue(m_queueId);
    // Get and apply the best available timer
    TimerId best = Timer::bestGlobalTimerId();
    QueueTimer qtimer;
    qtimer.setId(best);
    m_Queue->setTimer(qtimer);
    // Start sequencer input
    m_Client->setRealTimeInput(false);
    m_Client->startSequencerInput();
}

Metronome::~Metronome()
{
    m_Port->detach();
    m_Client->close();
}

void Metronome::handleSequencerEvent( SequencerEvent *ev )
{
    if (ev->getSequencerType() == SND_SEQ_EVENT_USR0)
        metronome_pattern(ev->getTick() + m_patternDuration);
    delete ev;
}

void Metronome::metronome_event_output(SequencerEvent* ev)
{
    ev->setSource(m_portId);
    ev->setSubscribers();
    ev->setDirect();
    m_Client->outputDirect(ev);
}

void Metronome::sendControlChange(int cc, int value)
{
    ControllerEvent ev(m_channel, cc, value);
    metronome_event_output(&ev);
}

void Metronome::sendInitialControls()
{
    metronome_set_program();
    metronome_set_controls();
    metronome_set_tempo();
}

void Metronome::metronome_set_program()
{
    ProgramChangeEvent ev(m_channel, m_program);
    metronome_event_output(&ev);
}

void Metronome::metronome_schedule_event(SequencerEvent* ev, int tick, bool lb)
{
    ev->setSource(m_portId);
    if (lb) // loop back
        ev->setDestination(m_clientId, m_portId);
    else
        ev->setSubscribers();
    ev->scheduleTick(m_queueId, tick, false);
    m_Client->outputDirect(ev);
}

void Metronome::metronome_note(int note, int tick)
{
    NoteEvent ev(m_channel, note, METRONOME_VELOCITY, m_noteDuration);
    metronome_schedule_event(&ev, tick, false);
}

void Metronome::metronome_echo(int tick)
{
    SystemEvent ev(SND_SEQ_EVENT_USR0);
    metronome_schedule_event(&ev, tick, true);
}

void Metronome::metronome_pattern(int tick)
{
    int j, t, duration;
    t = tick;
    duration = m_resolution * 4 / m_ts_div;
    for (j = 0; j < m_ts_num; j++) {
        metronome_note(j ? m_weak_note : m_strong_note, t);
        t += duration;
    }
    metronome_echo(t);
}

void Metronome::metronome_set_tempo()
{
    QueueTempo t = m_Queue->getTempo();
    t.setPPQ(m_resolution);
    t.setNominalBPM(m_bpm);
    m_Queue->setTempo(t);
    m_Client->drainOutput();
}

void Metronome::metronome_set_controls()
{
    sendControlChange(MIDI_CTL_MSB_MAIN_VOLUME, m_volume);
    sendControlChange(MIDI_CTL_MSB_PAN, m_pan);
}

void Metronome::subscribe(const QString& portName)
{
    m_Port->subscribeTo(portName);
}

bool Metronome::stopped()
{
    QReadLocker locker(&m_mutex);
    return m_Stopped;
}

void Metronome::stop()
{
    QWriteLocker locker(&m_mutex);
    m_Stopped = true;
    m_Client->dropOutput();
}

void Metronome::shutupSound()
{
    sendControlChange( MIDI_CTL_ALL_NOTES_OFF, 0 );
    sendControlChange( MIDI_CTL_ALL_SOUNDS_OFF, 0 );
}

void Metronome::play(QString tempo)
{
    bool ok;
    m_Stopped = false;
    m_patternDuration = m_resolution * 4 / m_ts_div * m_ts_num;
    m_bpm = tempo.toInt(&ok);
    if (!ok) m_bpm = TEMPO_DEFAULT;
    cout << "Metronome playing. " << m_bpm << " bpm" << endl;
    cout << "Press Ctrl+C to exit" << endl;
    try {
        sendInitialControls();
        m_Queue->start();
        metronome_pattern(0);
        metronome_pattern(m_patternDuration);
        while (!stopped())
            sleep(1);
    } catch (const SequencerError& err) {
        cerr << "SequencerError exception. Error code: " << err.code()
             << " (" << err.qstrError() << ")" << endl;
        cerr << "Location: " << err.location() << endl;
    }
}

/*
void Metronome::usage()
{
    cout << "Error: wrong parameters" << endl;
    cout << "Usage:" << endl;
    cout << "   metronome PORT BPM" << endl;
}

void Metronome::info()
{
    SystemInfo info = m_Client->getSystemInfo();
    cout << "ALSA Sequencer System Info" << endl;
    cout << "Max Clients: " << info.getMaxClients() << endl;
    cout << "Max Ports: " << info.getMaxPorts() << endl;
    cout << "Max Queues: " << info.getMaxQueues() << endl;
    cout << "Max Channels: " << info.getMaxChannels() << endl;
    cout << "Current Queues: " << info.getCurrentQueues() << endl;
    cout << "Current Clients: " << info.getCurrentClients() << endl;
}
*/

Metronome* metronome = 0;

void signalHandler(int sig)
{
    if (sig == SIGINT)
        qDebug() << "Caught a SIGINT. Exiting";
    else if (sig == SIGTERM)
        qDebug() << "Caught a SIGTERM. Exiting";
    if (metronome != 0) {
        metronome->stop();
        metronome->shutupSound();
    }
}

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;
    signal(SIGINT, signalHandler);
    signal(SIGTERM, signalHandler);

    args.setUsage("[options] port [bpm]");
    args.addRequiredArgument("port", "Destination, MIDI port identifier");
    args.addOptionalArgument("bpm", "Tempo, in beats per minute (default=120)");
    args.parse(argc, argv);

    try {
        metronome = new Metronome();
        QVariant port = args.getArgument("port");
        if (!port.isNull())
            metronome->subscribe(port.toString());
        QVariant bpm = args.getArgument("bpm");
        metronome->play(bpm.toString());
    } catch (const SequencerError& ex) {
        cerr << errorstr + " Returned error was: " + ex.qstrError() << endl;
    } catch (...) {
        cerr << errorstr << endl;
    }
    delete metronome;
    return 0;
}
Generated on Thu Sep 9 11:07:21 2010 for drumstick by  doxygen 1.6.3