guiplayer.cpp

SMF playback, graphic user interface program
/*
    SMF GUI Player test using the MIDI Sequencer C++ library
    Copyright (C) 2006-2010, Pedro Lopez-Cabanillas <plcl@users.sf.net>

    This program 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 program 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 INCLUDED_GUIPLAYER_H
#define INCLUDED_GUIPLAYER_H

#include <QtGui/QMainWindow>
#include <QtGui/QProgressDialog>
#include <QtCore/QObject>
#include <QtCore/QString>
#include <QtCore/QList>
#include <QtCore/QHash>
#include <QtCore/QPointer>

namespace drumstick {
    class QSmf;
    class QWrk;
    class MidiClient;
    class MidiPort;
    class MidiQueue;
    class SequencerEvent;
}

namespace Ui {
    class GUIPlayerClass;
}

class Player;
class About;
class Song;

using namespace drumstick;

const QString QSTR_DOMAIN("drumstick.sourceforge.net");
const QString QSTR_APPNAME("GUIPlayer");

enum PlayerState {
    InvalidState,
    EmptyState,
    PlayingState,
    PausedState,
    StoppedState
};

class GUIPlayer : public QMainWindow
{
    Q_OBJECT

public:
    GUIPlayer(QWidget *parent = 0, Qt::WindowFlags flags = 0);
    ~GUIPlayer();

    void appendSMFEvent(SequencerEvent* ev);
    void appendWRKEvent(unsigned long ticks, int track, SequencerEvent* ev);

    void subscribe(const QString& portName);
    void updateTimeLabel(int mins, int secs, int cnts);
    void updateTempoLabel(float ftempo);
    void dragEnterEvent(QDragEnterEvent* event);
    void dropEvent(QDropEvent* event);
    void closeEvent(QCloseEvent* event);
    bool event(QEvent* event);
    void openFile(const QString& fileName);
    void readSettings();
    void writeSettings();
    void updateState(PlayerState newState);

public slots:
    void about();
    void aboutQt();
    void play();
    void pause();
    void stop();
    void open();
    void setup();
    void tempoReset();
    void volumeReset();
    void tempoSlider(int value);
    void quit();
    void volumeSlider(int value);
    void pitchShift(int value);
    void songFinished();
    void playerStopped();
    void sequencerEvent(SequencerEvent* ev);

    /* SMF slots */
    void headerEvent(int format, int ntrks, int division);
    void noteOnEvent(int chan, int pitch, int vol);
    void noteOffEvent(int chan, int pitch, int vol);
    void keyPressEvent(int chan, int pitch, int press);
    void ctlChangeEvent(int chan, int ctl, int value);
    void pitchBendEvent(int chan, int value);
    void programEvent(int chan, int patch);
    void chanPressEvent(int chan, int press);
    void sysexEvent(const QByteArray& data);
    void textEvent(int type, const QString& data);
    void tempoEvent(int tempo);
    void errorHandler(const QString& errorStr);
    void updateSMFLoadProgress();

    /* WRK slots */
    void updateWRKLoadProgress();
    void errorHandlerWRK(const QString& errorStr);
    void unknownChunk(int type, const QByteArray& data);
    void fileHeader(int verh, int verl);
    void endOfWrk();
    void streamEndEvent(long time);
    void trackHeader(const QString& name1, const QString& name2,
                     int trackno, int channel, int pitch,
                     int velocity, int port,
                     bool selected, bool muted, bool loop);
    void timeBase(int timebase);
    void globalVars();
    void noteEvent(int track, long time, int chan, int pitch, int vol, int dur);
    void keyPressEvent(int track, long time, int chan, int pitch, int press);
    void ctlChangeEvent(int track, long time, int chan, int ctl, int value);
    void pitchBendEvent(int track, long time, int chan, int value);
    void programEvent(int track, long time, int chan, int patch);
    void chanPressEvent(int track, long time, int chan, int press);
    void sysexEvent(int track, long time, int bank);
    void sysexEventBank(int bank, const QString& name, bool autosend, int port, const QByteArray& data);
    void textEvent(int track, long time, int typ, const QString& data);
    void timeSigEvent(int bar, int num, int den);
    void keySigEventWRK(int bar, int alt);
    void tempoEvent(long time, int tempo);
    void trackPatch(int track, int patch);
    void comments(const QString& cmt);
    void variableRecord(const QString& name, const QByteArray& data);
    void newTrackHeader(const QString& name,
                        int trackno, int channel, int pitch,
                        int velocity, int port,
                        bool selected, bool muted, bool loop);
    void trackName(int trackno, const QString& name);
    void trackVol(int track, int vol);
    void trackBank(int track, int bank);
    void segment(int track, long time, const QString& name);
    void chord(int track, long time, const QString& name, const QByteArray& data);
    void expression(int track, long time, int code, const QString& text);

private:
    int m_portId;
    int m_queueId;
    int m_initialTempo;
    float m_tempoFactor;
    unsigned long m_tick;
    PlayerState m_state;

    QSmf* m_smf;
    QWrk* m_wrk;
    MidiClient* m_Client;
    MidiPort* m_Port;
    MidiQueue* m_Queue;
    Player* m_player;
    Ui::GUIPlayerClass* m_ui;
    QPointer<QProgressDialog> m_pd;
    QPointer<About> m_aboutDlg;
    Song* m_song;

    QString m_subscription;
    QString m_lastDirectory;
    QString m_loadingMessages;

    struct SysexEventRec {
        int track;
        long time;
        int bank;
    };
    QList<SysexEventRec> m_savedSysexEvents;
    QString m_fileFormat;

    struct TrackMapRec {
        int channel;
        int pitch;
        int velocity;
    };
    QHash<int,TrackMapRec> m_trackMap;

    struct TimeSigRec {
        int bar;
        int num;
        int den;
        long time;
    };
    QList<TimeSigRec> m_bars;

};

#endif // INCLUDED_GUIPLAYER_H

/*
    SMF GUI Player test using the MIDI Sequencer C++ library
    Copyright (C) 2006-2010, Pedro Lopez-Cabanillas <plcl@users.sf.net>

    This program 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 program 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 "guiplayer.h"
#include "ui_guiplayer.h"
#include "playerabout.h"
#include "player.h"
#include "song.h"

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

#include <QtGui/QApplication>
#include <QtGui/QFileDialog>
#include <QtGui/QInputDialog>
#include <QtGui/QDragEnterEvent>
#include <QtGui/QDropEvent>
#include <QtGui/QCloseEvent>
#include <QtGui/QToolTip>
#include <QtGui/QMessageBox>
#include <QtGui/QStatusBar>
#include <QtCore/QSettings>
#include <QtCore/QUrl>
#include <QtCore/QFileInfo>
#include <QtCore/QTextCodec>
#include <cmath>

GUIPlayer::GUIPlayer(QWidget *parent, Qt::WindowFlags flags)
    : QMainWindow(parent, flags),
    m_portId(-1),
    m_queueId(-1),
    m_initialTempo(0),
    m_tempoFactor(1.0),
    m_tick(0),
    m_state(InvalidState),
    m_smf(0),
    m_wrk(0),
    m_Client(0),
    m_Port(0),
    m_Queue(0),
    m_player(0),
    m_ui(new Ui::GUIPlayerClass),
    m_pd(0),
    m_aboutDlg(0),
    m_song(new Song)
{
    m_ui->setupUi(this);
    connect(m_ui->actionAbout, SIGNAL(triggered()), SLOT(about()));
    connect(m_ui->actionAboutQt, SIGNAL(triggered()), SLOT(aboutQt()));
    connect(m_ui->actionPlay, SIGNAL(triggered()), SLOT(play()));
    connect(m_ui->actionPause, SIGNAL(triggered()), SLOT(pause()));
    connect(m_ui->actionStop, SIGNAL(triggered()), SLOT(stop()));
    connect(m_ui->actionOpen, SIGNAL(triggered()), SLOT(open()));
    connect(m_ui->actionMIDISetup, SIGNAL(triggered()), SLOT(setup()));
    connect(m_ui->actionQuit, SIGNAL(triggered()), SLOT(quit()));
    connect(m_ui->btnTempo, SIGNAL(clicked()), SLOT(tempoReset()));
    connect(m_ui->btnVolume, SIGNAL(clicked()), SLOT(volumeReset()));
    connect(m_ui->sliderTempo, SIGNAL(valueChanged(int)), SLOT(tempoSlider(int)));
    connect(m_ui->volumeSlider, SIGNAL(valueChanged(int)), SLOT(volumeSlider(int)));
    connect(m_ui->spinPitch, SIGNAL(valueChanged(int)), SLOT(pitchShift(int)));
    connect(m_ui->toolBar->toggleViewAction(), SIGNAL(toggled(bool)),
            m_ui->actionShowToolbar, SLOT(setChecked(bool)));

    m_ui->actionPlay->setShortcut( Qt::Key_MediaPlay );
    m_ui->actionStop->setShortcut( Qt::Key_MediaStop );

    m_Client = new MidiClient(this);
    m_Client->open();
    m_Client->setPoolOutput(20); // tiny size, for near real-time pitchShift
    m_Client->setClientName("MIDI Player");
    connect(m_Client, SIGNAL(eventReceived(SequencerEvent*)),
                      SLOT(sequencerEvent(SequencerEvent*)));

    m_Port = new MidiPort(this);
    m_Port->attach( m_Client );
    m_Port->setPortName("MIDI Player Output Port");
    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_APPLICATION |
                         SND_SEQ_PORT_TYPE_MIDI_GENERIC );

    m_Queue = m_Client->createQueue(QSTR_APPNAME);
    m_queueId = m_Queue->getId();
    m_portId = m_Port->getPortId();

    m_smf = new QSmf(this);
    connect(m_smf, SIGNAL(signalSMFHeader(int,int,int)),
                   SLOT(headerEvent(int,int,int)));
    connect(m_smf, SIGNAL(signalSMFNoteOn(int,int,int)),
                   SLOT(noteOnEvent(int,int,int)));
    connect(m_smf, SIGNAL(signalSMFNoteOff(int,int,int)),
                   SLOT(noteOffEvent(int,int,int)));
    connect(m_smf, SIGNAL(signalSMFKeyPress(int,int,int)),
                   SLOT(keyPressEvent(int,int,int)));
    connect(m_smf, SIGNAL(signalSMFCtlChange(int,int,int)),
                   SLOT(ctlChangeEvent(int,int,int)));
    connect(m_smf, SIGNAL(signalSMFPitchBend(int,int)),
                   SLOT(pitchBendEvent(int,int)));
    connect(m_smf, SIGNAL(signalSMFProgram(int,int)),
                   SLOT(programEvent(int,int)));
    connect(m_smf, SIGNAL(signalSMFChanPress(int,int)),
                   SLOT(chanPressEvent(int,int)));
    connect(m_smf, SIGNAL(signalSMFSysex(const QByteArray&)),
                   SLOT(sysexEvent(const QByteArray&)));
    connect(m_smf, SIGNAL(signalSMFText(int,const QString&)),
                   SLOT(textEvent(int,const QString&)));
    connect(m_smf, SIGNAL(signalSMFTempo(int)),
                   SLOT(tempoEvent(int)));
    connect(m_smf, SIGNAL(signalSMFTrackStart()),
                   SLOT(updateSMFLoadProgress()));
    connect(m_smf, SIGNAL(signalSMFTrackEnd()),
                   SLOT(updateSMFLoadProgress()));
    connect(m_smf, SIGNAL(signalSMFendOfTrack()),
                   SLOT(updateSMFLoadProgress()));
    connect(m_smf, SIGNAL(signalSMFError(const QString&)),
                   SLOT(errorHandler(const QString&)));

    m_wrk = new QWrk(this);
    connect(m_wrk, SIGNAL(signalWRKError(const QString&)),
                   SLOT(errorHandlerWRK(const QString&)));
    connect(m_wrk, SIGNAL(signalWRKUnknownChunk(int,const QByteArray&)),
                   SLOT(unknownChunk(int,const QByteArray&)));
    connect(m_wrk, SIGNAL(signalWRKHeader(int,int)),
                   SLOT(fileHeader(int,int)));
    connect(m_wrk, SIGNAL(signalWRKEnd()),
                   SLOT(endOfWrk()));
    connect(m_wrk, SIGNAL(signalWRKStreamEnd(long)),
                   SLOT(streamEndEvent(long)));
    connect(m_wrk, SIGNAL(signalWRKGlobalVars()),
                   SLOT(globalVars()));
    connect(m_wrk, SIGNAL(signalWRKTrack(const QString&, const QString&, int,int,int,int,int,bool,bool,bool)),
                   SLOT(trackHeader(const QString&, const QString&, int,int,int,int,int,bool,bool,bool)));
    connect(m_wrk, SIGNAL(signalWRKTimeBase(int)),
                   SLOT(timeBase(int)));
    connect(m_wrk, SIGNAL(signalWRKNote(int,long,int,int,int,int)),
                   SLOT(noteEvent(int,long,int,int,int,int)));
    connect(m_wrk, SIGNAL(signalWRKKeyPress(int,long,int,int,int)),
                   SLOT(keyPressEvent(int,long,int,int,int)));
    connect(m_wrk, SIGNAL(signalWRKCtlChange(int,long,int,int,int)),
                   SLOT(ctlChangeEvent(int,long,int,int,int)));
    connect(m_wrk, SIGNAL(signalWRKPitchBend(int,long,int,int)),
                   SLOT(pitchBendEvent(int,long,int,int)));
    connect(m_wrk, SIGNAL(signalWRKProgram(int,long,int,int)),
                   SLOT(programEvent(int,long,int,int)));
    connect(m_wrk, SIGNAL(signalWRKChanPress(int,long,int,int)),
                   SLOT(chanPressEvent(int,long,int,int)));
    connect(m_wrk, SIGNAL(signalWRKSysexEvent(int,long,int)),
                   SLOT(sysexEvent(int,long,int)));
    connect(m_wrk, SIGNAL(signalWRKSysex(int,const QString&,bool,int,const QByteArray&)),
                   SLOT(sysexEventBank(int,const QString&,bool,int,const QByteArray&)));
    connect(m_wrk, SIGNAL(signalWRKText(int,long,int,const QString&)),
                   SLOT(textEvent(int,long,int,const QString&)));
    connect(m_wrk, SIGNAL(signalWRKTimeSig(int,int,int)),
                   SLOT(timeSigEvent(int,int,int)));
    connect(m_wrk, SIGNAL(signalWRKKeySig(int,int)),
                   SLOT(keySigEventWRK(int,int)));
    connect(m_wrk, SIGNAL(signalWRKTempo(long,int)),
                   SLOT(tempoEvent(long,int)));
    connect(m_wrk, SIGNAL(signalWRKTrackPatch(int,int)),
                   SLOT(trackPatch(int,int)));
    connect(m_wrk, SIGNAL(signalWRKComments(const QString&)),
                   SLOT(comments(const QString&)));
    connect(m_wrk, SIGNAL(signalWRKVariableRecord(const QString&,const QByteArray&)),
                   SLOT(variableRecord(const QString&,const QByteArray&)));
    connect(m_wrk, SIGNAL(signalWRKNewTrack(const QString&,int,int,int,int,int,bool,bool,bool)),
                   SLOT(newTrackHeader(const QString&,int,int,int,int,int,bool,bool,bool)));
    connect(m_wrk, SIGNAL(signalWRKTrackName(int,const QString&)),
                   SLOT(trackName(int,const QString&)));
    connect(m_wrk, SIGNAL(signalWRKTrackVol(int,int)),
                   SLOT(trackVol(int,int)));
    connect(m_wrk, SIGNAL(signalWRKTrackBank(int,int)),
                   SLOT(trackBank(int,int)));
    connect(m_wrk, SIGNAL(signalWRKSegment(int,long,const QString&)),
                   SLOT(segment(int,long,const QString&)));
    connect(m_wrk, SIGNAL(signalWRKChord(int,long,const QString&,const QByteArray&)),
                   SLOT(chord(int,long,const QString&,const QByteArray&)));
    connect(m_wrk, SIGNAL(signalWRKExpression(int,long,int,const QString&)),
                   SLOT(expression(int,long,int,const QString&)));

    m_player = new Player(m_Client, m_portId);
    connect(m_player, SIGNAL(finished()), SLOT(songFinished()));
    connect(m_player, SIGNAL(stopped()), SLOT(playerStopped()));

    m_Client->startSequencerInput();
    tempoReset();
    volumeReset();
    updateState(EmptyState);
}

GUIPlayer::~GUIPlayer()
{
    m_Client->stopSequencerInput();
    m_Port->detach();
    m_Client->close();
    delete m_player;
}

void GUIPlayer::subscribe(const QString& portName)
{
    try {
        if (!m_subscription.isEmpty()) {
            m_Port->unsubscribeTo(m_subscription);
        }
        m_subscription = portName;
        m_Port->subscribeTo(m_subscription);
    } catch (const SequencerError& err) {
        qWarning() << "SequencerError exception. Error code: " << err.code()
                   << " (" << err.qstrError() << ")";
        qWarning() << "Location: " << err.location();
    }
}

void GUIPlayer::updateTimeLabel(int mins, int secs, int cnts)
{
    static QChar fill('0');
    QString stime = QString("%1:%2.%3").arg(mins,2,10,fill)
                                       .arg(secs,2,10,fill)
                                       .arg(cnts,2,10,fill);
    m_ui->lblTime->setText(stime);
}

void GUIPlayer::updateState(PlayerState newState)
{
    if (m_state == newState)
        return;
    switch (newState) {
    case EmptyState:
        m_ui->actionPlay->setEnabled(false);
        m_ui->actionPause->setEnabled(false);
        m_ui->actionStop->setEnabled(false);
        statusBar()->showMessage("Please, load a song");
        break;
    case PlayingState:
        m_ui->actionPlay->setEnabled(false);
        m_ui->actionPause->setEnabled(true);
        m_ui->actionStop->setEnabled(true);
        statusBar()->showMessage("Playing");
        break;
    case PausedState:
        m_ui->actionPlay->setEnabled(false);
        m_ui->actionStop->setEnabled(true);
        statusBar()->showMessage("Paused");
        break;
    case StoppedState:
        m_ui->actionPause->setChecked(false);
        m_ui->actionPause->setEnabled(false);
        m_ui->actionStop->setEnabled(false);
        m_ui->actionPlay->setEnabled(true);
        statusBar()->showMessage("Stopped");
        break;
    default:
        statusBar()->showMessage("Not initialized");
        break;
    }
    m_state = newState;
}

void GUIPlayer::play()
{
    if (!m_song->isEmpty()) {
        if (m_player->getInitialPosition() == 0) {
            if (m_initialTempo == 0) {
                return;
            }
            QueueTempo firstTempo = m_Queue->getTempo();
            firstTempo.setPPQ(m_song->getDivision());
            firstTempo.setTempo(m_initialTempo);
            firstTempo.setTempoFactor(m_tempoFactor);
            m_Queue->setTempo(firstTempo);
            m_Client->drainOutput();
            m_player->sendVolumeEvents();
        }
        m_player->start();
        updateState(PlayingState);
    }
}

void GUIPlayer::pause()
{
    if (m_player->isRunning()) {
        m_player->stop();
        m_player->setPosition(m_Queue->getStatus().getTickTime());
        updateState(PausedState);
    } else if (!m_song->isEmpty()) {
        m_player->start();
        updateState(PlayingState);
    }
}

void GUIPlayer::stop()
{
    if (m_player->isRunning())
        m_player->stop();
    if (m_initialTempo != 0)
        songFinished();
    else
        updateState(StoppedState);
}

void GUIPlayer::openFile(const QString& fileName)
{
    QFileInfo finfo(fileName);
    if (finfo.exists()) {
        m_song->clear();
        m_loadingMessages.clear();
        m_tick = 0;
        m_initialTempo = 0;
        try {
            m_pd = new QProgressDialog(0, 0, 0, finfo.size(), this);
            m_pd->setWindowTitle("Loading MIDI file...");
            m_pd->setMinimumDuration(1000);
            m_pd->setValue(0);
            QString ext = finfo.suffix().toLower();
            if (ext == "wrk")
                m_wrk->readFromFile(fileName);
            else if (ext == "mid" || ext == "kar")
                m_smf->readFromFile(fileName);
            m_pd->setValue(finfo.size());
            if (m_song->isEmpty()) {
                m_ui->lblName->clear();
                m_ui->lblCopyright->clear();
            } else {
                m_song->sort();
                m_player->setSong(m_song);
                m_ui->lblName->setText(finfo.fileName());
                m_ui->lblCopyright->setText(m_song->getCopyright());
                m_lastDirectory = finfo.absolutePath();
            }
        } catch (...) {
            m_song->clear();
            m_ui->lblName->clear();
            m_ui->lblCopyright->clear();
        }
        delete m_pd;
        if (m_initialTempo == 0) {
            m_initialTempo = 500000;
        }
        updateTimeLabel(0,0,0);
        updateTempoLabel(6.0e7f / m_initialTempo);
        m_ui->progressBar->setValue(0);
        if (!m_loadingMessages.isEmpty()) {
            m_loadingMessages.insert(0, "Warning, this file may be non-standard or damaged<br>");
            QMessageBox::warning(this, QSTR_APPNAME, m_loadingMessages);
        }
        if (m_song->isEmpty())
            updateState(EmptyState);
        else
            updateState(StoppedState);
    }
}

void GUIPlayer::open()
{
    QString fileName = QFileDialog::getOpenFileName(this,
          "Open MIDI File", m_lastDirectory,
          "MIDI Files (*.mid *.midi);;"
          "Karaoke files (*.kar);;"
          "Cakewalk files (*.wrk);;"
          "All files (*.*)");
    if (! fileName.isEmpty() ) {
        stop();
        openFile(fileName);
    }
}

void GUIPlayer::setup()
{
    bool ok;
    int current;
    QStringList items;
    QListIterator<PortInfo> it(m_Client->getAvailableOutputs());
    while(it.hasNext()) {
        PortInfo p = it.next();
        items << QString("%1:%2").arg(p.getClientName()).arg(p.getPort());
    }
    current = items.indexOf(m_subscription);
    QString item = QInputDialog::getItem(this, "Player subscription",
                                         "Output port:", items,
                                         current, false, &ok);
    if (ok && !item.isEmpty()) {
        subscribe(item);
    }
}

void GUIPlayer::songFinished()
{
    m_player->resetPosition();
    updateState(StoppedState);
}

void GUIPlayer::playerStopped()
{
    int portId = m_Port->getPortId();
    for (int channel = 0; channel < 16; ++channel) {
        ControllerEvent ev1(channel, MIDI_CTL_ALL_NOTES_OFF, 0);
        ev1.setSource(portId);
        ev1.setSubscribers();
        ev1.setDirect();
        m_Client->outputDirect(&ev1);
        ControllerEvent ev2(channel, MIDI_CTL_ALL_SOUNDS_OFF, 0);
        ev2.setSource(portId);
        ev2.setSubscribers();
        ev2.setDirect();
        m_Client->outputDirect(&ev2);
    }
    m_Client->drainOutput();
}

void GUIPlayer::updateTempoLabel(float ftempo)
{
    QString stempo = QString("%1 bpm").arg(ftempo, 0, 'f', 2);
    m_ui->lblOther->setText(stempo);
}

void GUIPlayer::sequencerEvent(SequencerEvent *ev)
{
    if ((ev->getSequencerType() == SND_SEQ_EVENT_ECHO) && (m_tick != 0)){
        int pos = 100 * ev->getTick() / m_tick;
        const snd_seq_real_time_t* rt = m_Queue->getStatus().getRealtime();
        int mins = rt->tv_sec / 60;
        int secs =  rt->tv_sec % 60;
        int cnts = floor( rt->tv_nsec / 1.0e7 );
        updateTempoLabel(m_Queue->getTempo().getRealBPM());
        updateTimeLabel(mins, secs, cnts);
        m_ui->progressBar->setValue(pos);
    }
    delete ev;
}

void GUIPlayer::pitchShift(int value)
{
    m_player->setPitchShift(value);
}

void GUIPlayer::headerEvent(int format, int ntrks, int division)
{
    m_song->setHeader(format, ntrks, division);
    updateSMFLoadProgress();
}

void GUIPlayer::noteOnEvent(int chan, int pitch, int vol)
{
    SequencerEvent* ev = new NoteOnEvent (chan, pitch, vol);
    appendSMFEvent(ev);
}

void GUIPlayer::noteOffEvent(int chan, int pitch, int vol)
{
    SequencerEvent* ev = new NoteOffEvent (chan, pitch, vol);
    appendSMFEvent(ev);
}

void GUIPlayer::keyPressEvent(int chan, int pitch, int press)
{
    SequencerEvent* ev = new KeyPressEvent (chan, pitch, press);
    appendSMFEvent(ev);
}

void GUIPlayer::ctlChangeEvent(int chan, int ctl, int value)
{
    SequencerEvent* ev = new ControllerEvent (chan, ctl, value);
    appendSMFEvent(ev);
}

void GUIPlayer::pitchBendEvent(int chan, int value)
{
    SequencerEvent* ev = new PitchBendEvent (chan, value);
    appendSMFEvent(ev);
}

void GUIPlayer::programEvent(int chan, int patch)
{
    SequencerEvent* ev = new ProgramChangeEvent (chan, patch);
    appendSMFEvent(ev);
}

void GUIPlayer::chanPressEvent(int chan, int press)
{
    SequencerEvent* ev = new ChanPressEvent (chan, press);
    appendSMFEvent(ev);
}

void GUIPlayer::sysexEvent(const QByteArray& data)
{
    SequencerEvent* ev = new SysExEvent (data);
    appendSMFEvent(ev);
}

void GUIPlayer::textEvent(int type, const QString& data)
{
    m_song->addText(type, data);
    updateSMFLoadProgress();
}

void GUIPlayer::tempoEvent(int tempo)
{
    if ( m_initialTempo == 0 )
    {
        m_initialTempo = tempo;
    }
    SequencerEvent* ev = new TempoEvent (m_queueId, tempo);
    appendSMFEvent(ev);
}

void GUIPlayer::errorHandler(const QString& errorStr)
{
    if (m_loadingMessages.length() < 1024)
        m_loadingMessages.append(QString("%1 at file offset %2<br>")
            .arg(errorStr).arg(m_smf->getFilePos()));
}

void GUIPlayer::tempoReset()
{
    m_ui->sliderTempo->setValue(100);
    tempoSlider(100);
}

void GUIPlayer::volumeReset()
{
    m_ui->volumeSlider->setValue(100);
    volumeSlider(100);
}

void GUIPlayer::tempoSlider(int value)
{
    m_tempoFactor = (value*value + 100.0*value + 20000.0) / 40000.0;
    QueueTempo qtempo = m_Queue->getTempo();
    qtempo.setTempoFactor(m_tempoFactor);
    m_Queue->setTempo(qtempo);
    m_Client->drainOutput();
    if (!m_player->isRunning()) {
        updateTempoLabel(qtempo.getRealBPM());
    }
    // Slider tooltip
    QString tip = QString("%1\%").arg(m_tempoFactor*100.0, 0, 'f', 0);
    m_ui->sliderTempo->setToolTip(tip);
    QToolTip::showText(QCursor::pos(), tip, this);
}

void GUIPlayer::volumeSlider(int value)
{
    QString tip = QString::number(value)+'%';
    m_ui->lblVolume->setText(tip);
    m_ui->volumeSlider->setToolTip(tip);
    m_player->setVolumeFactor(value);
    QToolTip::showText(QCursor::pos(), tip, this);
}

void GUIPlayer::quit()
{
    stop();
    m_player->wait();
    close();
}

void GUIPlayer::dragEnterEvent( QDragEnterEvent * event )
{
    if (event->mimeData()->hasFormat("text/uri-list")) {
        event->acceptProposedAction();
    }
}

void GUIPlayer::dropEvent( QDropEvent * event )
{
    QString data = event->mimeData()->text();
    QString fileName = QUrl(data).path().trimmed();
    while (fileName.endsWith(QChar::Null)) fileName.chop(1);
    if ( fileName.endsWith(".mid", Qt::CaseInsensitive) ||
         fileName.endsWith(".midi", Qt::CaseInsensitive) ||
         fileName.endsWith(".kar", Qt::CaseInsensitive) ||
         fileName.endsWith(".wrk", Qt::CaseInsensitive) ) {
        stop();
        openFile(fileName);
        event->accept();
    } else {
        qDebug() << "Dropped object is not supported:" << fileName;
    }
}

bool GUIPlayer::event( QEvent * event )
{
    if(event->type() == QEvent::Polish) {
        readSettings();
        /* Process the command line arguments.
           The first argument should be a MIDI file name */
        QStringList args = QCoreApplication::arguments();
        if (args.size() > 1) {
            QString first = args.at(1);
            openFile(first);
        }
        event->accept();
    }
    return QMainWindow::event(event);
}

void GUIPlayer::readSettings()
{
    QSettings settings;

    settings.beginGroup("Window");
    restoreGeometry(settings.value("Geometry").toByteArray());
    restoreState(settings.value("State").toByteArray());
    settings.endGroup();

    settings.beginGroup("Preferences");
    m_lastDirectory = settings.value("LastDirectory").toString();
    QString midiConn = settings.value("MIDIConnection").toString();
    settings.endGroup();

    if (midiConn.length() > 0) {
        subscribe(midiConn);
    }
}

void GUIPlayer::writeSettings()
{
    QSettings settings;

    settings.beginGroup("Window");
    settings.setValue("Geometry", saveGeometry());
    settings.setValue("State", saveState());
    settings.endGroup();

    settings.beginGroup("Preferences");
    settings.setValue("LastDirectory", m_lastDirectory);
    settings.setValue("MIDIConnection", m_subscription);
    settings.endGroup();
}

void GUIPlayer::closeEvent( QCloseEvent *event )
{
    writeSettings();
    event->accept();
}

void GUIPlayer::about()
{
    if (m_aboutDlg == 0)
        m_aboutDlg = new About(this);
    m_aboutDlg->exec();
}

void GUIPlayer::aboutQt()
{
    qApp->aboutQt();
}

void GUIPlayer::updateSMFLoadProgress()
{
    if (m_pd != 0) {
        m_pd->setValue(m_smf->getFilePos());
    }
}

void GUIPlayer::appendSMFEvent(SequencerEvent* ev)
{
    unsigned long tick = m_smf->getCurrentTime();
    ev->setSource(m_portId);
    if (ev->getSequencerType() != SND_SEQ_EVENT_TEMPO) {
        ev->setSubscribers();
    }
    ev->scheduleTick(m_queueId, tick, false);
    m_song->append(ev);
    if (tick > m_tick)
        m_tick = tick;
    updateSMFLoadProgress();
}

/* ********************************* *
 * Cakewalk WRK file format handling
 * ********************************* */

void GUIPlayer::updateWRKLoadProgress()
{
    if (m_pd != 0) {
        m_pd->setValue(m_wrk->getFilePos());
    }
}

void
GUIPlayer::appendWRKEvent(unsigned long ticks, int /*trck*/, SequencerEvent* ev)
{
    ev->setSource(m_portId);
    if (ev->getSequencerType() != SND_SEQ_EVENT_TEMPO) {
        ev->setSubscribers();
    }
    ev->scheduleTick(m_queueId, ticks, false);
    m_song->append(ev);
    if (ticks > m_tick)
        m_tick = ticks;
    updateWRKLoadProgress();
    qApp->processEvents();
}

void GUIPlayer::errorHandlerWRK(const QString& errorStr)
{
    qWarning() << "*** Warning! " << errorStr
               << " at file offset " << m_wrk->getFilePos();
}

void GUIPlayer::fileHeader(int /*verh*/, int /*verl*/)
{
    //fileFormat = QString("WRK file version %1.%2").arg(verh).arg(verl);
    m_song->setHeader(1, 0, 120);
}

void GUIPlayer::timeBase(int timebase)
{
    m_song->setDivision(timebase);
}

void GUIPlayer::globalVars()
{
    keySigEventWRK(0, m_wrk->getKeySig());
    updateWRKLoadProgress();
}

void GUIPlayer::streamEndEvent(long time)
{
    unsigned long ticks = time;
    if (ticks > m_tick)
        m_tick = ticks;
}

void GUIPlayer::trackHeader( const QString& name1, const QString& name2,
                           int trackno, int channel,
                           int pitch, int velocity, int /*port*/,
                           bool /*selected*/, bool /*muted*/, bool /*loop*/ )
{
    TrackMapRec rec;
    rec.channel = channel;
    rec.pitch = pitch;
    rec.velocity = velocity;
    m_trackMap[trackno] = rec;
    QString trkName = name1 + ' ' + name2;
    trkName = trkName.trimmed();
    if (!trkName.isEmpty()) {
        SequencerEvent* ev = new TextEvent(trkName, Song::TrackName);
        appendWRKEvent(0, trackno, ev);
    }
    updateWRKLoadProgress();
}

void GUIPlayer::noteEvent(int track, long time, int chan, int pitch, int vol, int dur)
{
    int channel = chan;
    TrackMapRec rec = m_trackMap[track];
    int key = pitch + rec.pitch;
    int velocity = vol + rec.velocity;
    if (rec.channel > -1)
        channel = rec.channel;
    SequencerEvent* ev = new NoteEvent(channel, key, velocity, dur);
    appendWRKEvent(time, track, ev);
}

void GUIPlayer::keyPressEvent(int track, long time, int chan, int pitch, int press)
{
    int channel = chan;
    TrackMapRec rec = m_trackMap[track];
    int key = pitch + rec.pitch;
    if (rec.channel > -1)
        channel = rec.channel;
    SequencerEvent* ev = new KeyPressEvent(channel, key, press);
    appendWRKEvent(time, track, ev);
}

void GUIPlayer::ctlChangeEvent(int track, long time, int chan, int ctl, int value)
{
    int channel = chan;
    TrackMapRec rec = m_trackMap[track];
    if (rec.channel > -1)
        channel = rec.channel;
    SequencerEvent* ev = new ControllerEvent(channel, ctl, value);
    appendWRKEvent(time, track, ev);
}

void GUIPlayer::pitchBendEvent(int track, long time, int chan, int value)
{
    int channel = chan;
    TrackMapRec rec = m_trackMap[track];
    if (rec.channel > -1)
        channel = rec.channel;
    SequencerEvent* ev = new PitchBendEvent(channel, value);
    appendWRKEvent(time, track, ev);
}

void GUIPlayer::programEvent(int track, long time, int chan, int patch)
{
    int channel = chan;
    TrackMapRec rec = m_trackMap[track];
    if (rec.channel > -1)
        channel = rec.channel;
    SequencerEvent* ev = new ProgramChangeEvent(channel, patch);
    appendWRKEvent(time, track, ev);
}

void GUIPlayer::chanPressEvent(int track, long time, int chan, int press)
{
    int channel = chan;
    TrackMapRec rec = m_trackMap[track];
    if (rec.channel > -1)
        channel = rec.channel;
    SequencerEvent* ev = new ChanPressEvent(channel, press);
    appendWRKEvent(time, track, ev);
}

void GUIPlayer::sysexEvent(int track, long time, int bank)
{
    SysexEventRec rec;
    rec.track = track;
    rec.time = time;
    rec.bank = bank;
    m_savedSysexEvents.append(rec);
}

void GUIPlayer::sysexEventBank(int bank, const QString& /*name*/, bool autosend, int /*port*/, const QByteArray& data)
{
    SysExEvent* ev = new SysExEvent(data);

    if (autosend)
        appendWRKEvent(0, 0, ev->clone());

    foreach(const SysexEventRec& rec, m_savedSysexEvents) {
        if (rec.bank == bank) {
            appendWRKEvent(rec.time, rec.track, ev->clone());
        }
    }

    delete ev;
}

void GUIPlayer::textEvent(int track, long time, int type, const QString& data)
{
    SequencerEvent* ev = new TextEvent(data, type);
    appendWRKEvent(time, track, ev);
}

void GUIPlayer::timeSigEvent(int bar, int num, int den)
{
    SequencerEvent* ev = new SequencerEvent();
    ev->setSequencerType(SND_SEQ_EVENT_TIMESIGN);
    int div, d = den;
    for ( div = 0; d > 1; d /= 2 )
        ++div;
    ev->setRaw8(0, num);
    ev->setRaw8(1, div);
    ev->setRaw8(2, 24 * 4 / den);
    ev->setRaw8(3, 8);

    TimeSigRec newts;
    newts.bar = bar;
    newts.num = num;
    newts.den = den;
    newts.time = 0;
    if (m_bars.isEmpty()) {
        m_bars.append(newts);
    } else {
        bool found = false;
        foreach(const TimeSigRec& ts, m_bars) {
            if (ts.bar == bar) {
                newts.time = ts.time;
                found = true;
                break;
            }
        }
        if (!found) {
            TimeSigRec& lasts = m_bars.last();
            newts.time = lasts.time + ( lasts.num * 4 / lasts.den
                * m_song->getDivision() * (bar - lasts.bar) );
            m_bars.append(newts);
        }
    }
    appendWRKEvent(newts.time, 0, ev);
}

void GUIPlayer::keySigEventWRK(int bar, int alt)
{
    SequencerEvent* ev = new SequencerEvent();
    ev->setSequencerType(SND_SEQ_EVENT_KEYSIGN);
    ev->setRaw8(0, alt);
    long time = 0;
    foreach(const TimeSigRec& ts, m_bars) {
        if (ts.bar == bar) {
            time = ts.time;
            break;
        }
    }
    appendWRKEvent(time, 0, ev);
}

void GUIPlayer::tempoEvent(long time, int tempo)
{
    double bpm = tempo / 100.0;
    if ( m_initialTempo < 0 )
    {
        m_initialTempo = round( bpm );
    }
    SequencerEvent* ev = new TempoEvent(m_queueId, round ( 6e7 / bpm ) );
    appendWRKEvent(time, 0, ev);
}

void GUIPlayer::trackPatch(int track, int patch)
{
    int channel = 0;
    TrackMapRec rec = m_trackMap[track];
    if (rec.channel > -1)
        channel = rec.channel;
    programEvent(track, 0, channel, patch);
}

void GUIPlayer::comments(const QString& cmt)
{
    SequencerEvent* ev = new TextEvent("Comment: " + cmt, Song::Text);
    appendWRKEvent(0, 0, ev);
}

void GUIPlayer::variableRecord(const QString& name, const QByteArray& data)
{
    SequencerEvent* ev = NULL;
    QString s;
    bool isReadable = ( name == "Title" || name == "Author" ||
                       name == "Copyright" || name == "Subtitle" ||
                       name == "Instructions" || name == "Keywords" );
    if (isReadable) {
        QByteArray b2 = data.left(qstrlen(data));
        if (m_wrk->getTextCodec() == 0)
            s = QString(b2);
        else
            s = m_wrk->getTextCodec()->toUnicode(b2);
        if ( name == "Title" )
            ev = new TextEvent(s, Song::TrackName);
        else if ( name == "Copyright" )
            ev = new TextEvent(s, Song::Copyright);
        else
            ev = new TextEvent(name + ": " + s, Song::Text);
        appendWRKEvent(0, 0, ev);
    }
}

void GUIPlayer::newTrackHeader( const QString& name,
                              int trackno, int channel,
                              int pitch, int velocity, int /*port*/,
                              bool /*selected*/, bool /*muted*/, bool /*loop*/ )
{
    TrackMapRec rec;
    rec.channel = channel;
    rec.pitch = pitch;
    rec.velocity = velocity;
    m_trackMap[trackno] = rec;
    if (!name.isEmpty())
        textEvent(trackno, 0, Song::TrackName, name);
    updateWRKLoadProgress();
}

void GUIPlayer::trackName(int trackno, const QString& name)
{
    SequencerEvent* ev = new TextEvent(name, Song::TrackName);
    appendWRKEvent(0, trackno, ev);
}

void GUIPlayer::trackVol(int track, int vol)
{
    int channel = 0;
    int lsb, msb;
    TrackMapRec rec = m_trackMap[track];
    if (rec.channel > -1)
        channel = rec.channel;
    if (vol < 128)
        ctlChangeEvent(track, 0, channel, MIDI_CTL_MSB_MAIN_VOLUME, vol);
    else {
        lsb = vol % 0x80;
        msb = vol / 0x80;
        ctlChangeEvent(track, 0, channel, MIDI_CTL_LSB_MAIN_VOLUME, lsb);
        ctlChangeEvent(track, 0, channel, MIDI_CTL_MSB_MAIN_VOLUME, msb);
    }
}

void GUIPlayer::trackBank(int track, int bank)
{
    // assume GM/GS bank method
    int channel = 0;
    int lsb, msb;
    TrackMapRec rec = m_trackMap[track];
    if (rec.channel > -1)
        channel = rec.channel;
    lsb = bank % 0x80;
    msb = bank / 0x80;
    ctlChangeEvent(track, 0, channel, MIDI_CTL_MSB_BANK, msb);
    ctlChangeEvent(track, 0, channel, MIDI_CTL_LSB_BANK, lsb);
}

void GUIPlayer::segment(int track, long time, const QString& name)
{
    if (!name.isEmpty()) {
        SequencerEvent *ev = new TextEvent("Segment: " + name, Song::Marker);
        appendWRKEvent(time, track, ev);
    }
}

void GUIPlayer::chord(int track, long time, const QString& name, const QByteArray& /*data*/ )
{
    if (!name.isEmpty()) {
        SequencerEvent *ev = new TextEvent("Chord: " + name, Song::Text);
        appendWRKEvent(time, track, ev);
    }
}

void GUIPlayer::expression(int track, long time, int /*code*/, const QString& text)
{
    if (!text.isEmpty()) {
        SequencerEvent *ev = new TextEvent(text, Song::Text);
        appendWRKEvent(time, track, ev);
    }
}

void GUIPlayer::endOfWrk()
{
    if (m_initialTempo < 0)
        m_initialTempo = 120;
    SequencerEvent* ev = new SystemEvent(SND_SEQ_EVENT_ECHO);
    appendWRKEvent(m_tick, 0, ev);
}

void GUIPlayer::unknownChunk(int /*type*/, const QByteArray& /*data*/)
{
    /*qDebug() << "dec:" << type
             << "hex:" << hex << type << dec
             << "size:" << data.length();*/
}

Generated on Wed Sep 8 10:12:09 2010 for drumstick by  doxygen 1.5.9