Commit b4916921 authored by Sandro Andrade's avatar Sandro Andrade

Initial move of current MIDI stuff to drumstick backend plugin

parent 12ec3fc5
......@@ -26,7 +26,8 @@ namespace Minuet
{
ISoundBackend::ISoundBackend(QObject *parent)
: IPlugin(parent)
: IPlugin(parent),
m_state(StoppedState)
{
}
......@@ -34,5 +35,31 @@ ISoundBackend::~ISoundBackend()
{
}
QString ISoundBackend::playbackLabel() const
{
return m_playbackLabel;
}
ISoundBackend::State ISoundBackend::state() const
{
return m_state;
}
void ISoundBackend::setPlaybackLabel(const QString &playbackLabel)
{
if (m_playbackLabel != playbackLabel) {
m_playbackLabel = playbackLabel;
emit playbackLabelChanged(m_playbackLabel);
}
}
void ISoundBackend::setState(State state)
{
if (m_state != state) {
m_state = state;
emit stateChanged(m_state);
}
}
}
......@@ -39,10 +39,23 @@ class MINUETINTERFACES_EXPORT ISoundBackend : public IPlugin
Q_PROPERTY(int pitch MEMBER m_pitch NOTIFY pitchChanged)
Q_PROPERTY(quint8 volume MEMBER m_volume NOTIFY volumeChanged)
Q_PROPERTY(quint8 tempo MEMBER m_tempo NOTIFY tempoChanged)
Q_PROPERTY(QString playbackLabel READ playbackLabel NOTIFY playbackLabelChanged)
Q_ENUMS(State)
Q_PROPERTY(State state READ state NOTIFY stateChanged)
public:
~ISoundBackend() override;
QString playbackLabel() const;
enum State {
StoppedState = 0,
PlayingState,
PausedState
};
ISoundBackend::State state() const;
public Q_SLOTS:
virtual void prepareFromExerciseOptions(QJsonArray selectedOptions, const QString &playMode) = 0;
virtual void prepareFromMidiFile(const QString &fileName) = 0;
......@@ -50,18 +63,24 @@ public Q_SLOTS:
virtual void play() = 0;
virtual void pause() = 0;
virtual void stop() = 0;
Q_SIGNALS:
void pitchChanged(int newPitch);
void volumeChanged(quint8 newVolume);
void tempoChanged(quint8 newTempo);
void playbackLabelChanged(QString newPlaybackLabel);
void stateChanged(State newState);
protected:
explicit ISoundBackend(QObject *parent = 0);
void setPlaybackLabel(const QString &playbackLabel);
void setState(State state);
int m_pitch;
quint8 m_volume;
quint8 m_tempo;
QString m_playbackLabel;
State m_state;
};
}
......
kde_enable_exceptions()
set(drumsticksoundbackend_PLUGIN_SRCS
drumsticksoundbackend.cpp
midisequenceroutputthread.cpp
song.cpp
)
add_library(minuetdrumsticksoundbackend MODULE ${drumsticksoundbackend_PLUGIN_SRCS})
target_link_libraries(minuetdrumsticksoundbackend
Qt5::Core
asound
drumstick-alsa
drumstick-file
Minuet::Interfaces
)
......
......@@ -22,19 +22,136 @@
#include "drumsticksoundbackend.h"
#include <QtMath>
#include <QJsonObject>
#include <drumstick/alsaqueue.h>
#include <drumstick/alsaclient.h>
#include "song.h"
#include "midisequenceroutputthread.h"
DrumstickSoundBackend::DrumstickSoundBackend(QObject *parent)
: Minuet::ISoundBackend(parent)
: Minuet::ISoundBackend(parent),
m_song(0)
{
// MidiClient configuration
m_client = new drumstick::MidiClient(this);
try {
m_client->open();
} catch (const drumstick::SequencerError &err) {
// KMessageBox::error(qobject_cast<QWidget*>(this->parent()), i18n("Fatal error from the ALSA sequencer: \"%1\". "
// "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, "
// "or the user isn't a member of audio group. "
// "Please check your ALSA/MIDI configuration."
// , err.qstrError()),
// i18n("Minuet startup"));
// m_eventSchedulingMode = DAMAGED;
return;
}
m_client->setClientName(QStringLiteral("MinuetSequencer"));
m_client->setPoolOutput(50);
// Connection for events generated when playing MIDI
connect(m_client, &drumstick::MidiClient::eventReceived, this, &DrumstickSoundBackend::eventReceived, Qt::QueuedConnection);
m_client->setRealTimeInput(false);
m_client->startSequencerInput();
// Output port configuration
m_outputPort = new drumstick::MidiPort(this);
m_outputPort->attach(m_client);
m_outputPort->setPortName(QStringLiteral("Minuet Sequencer Output Port"));
m_outputPort->setCapability(SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ);
m_outputPort->setPortType(SND_SEQ_PORT_TYPE_APPLICATION | SND_SEQ_PORT_TYPE_MIDI_GENERIC);
m_outputPortId = m_outputPort->getPortId();
// Input port configuration
m_inputPort = new drumstick::MidiPort(this);
m_inputPort->attach(m_client);
m_inputPort->setPortName(QStringLiteral("Minuet Sequencer Input Port"));
m_inputPort->setCapability(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE);
m_inputPort->setPortType(SND_SEQ_PORT_TYPE_APPLICATION);
m_inputPortId = m_inputPort->getPortId();
// MidiQueue configuration
m_queue = m_client->createQueue();
m_queueId = m_queue->getId();
// OutputThread
m_midiSequencerOutputThread = new MidiSequencerOutputThread(m_client, m_outputPortId);
connect(m_midiSequencerOutputThread, &MidiSequencerOutputThread::stopped, this, &DrumstickSoundBackend::outputThreadStopped);
connect(m_midiSequencerOutputThread, &MidiSequencerOutputThread::finished, this, [=]() {
setPlaybackLabel(QStringLiteral("00:00.00"));
});
}
DrumstickSoundBackend::~DrumstickSoundBackend()
{
m_client->stopSequencerInput();
m_outputPort->detach();
m_inputPort->detach();
m_client->close();
delete m_midiSequencerOutputThread;
}
void DrumstickSoundBackend::prepareFromExerciseOptions(QJsonArray selectedOptions, const QString &playMode)
{
Q_UNUSED(selectedOptions)
Q_UNUSED(playMode)
Song *song = new Song;
song->setHeader(0, 1, 60);
song->setInitialTempo(600000);
m_song.reset(song);
if (m_song->initialTempo() == 0)
m_song->setInitialTempo(600000);
appendEvent(new drumstick::TempoEvent(m_queueId, 600000), 0);
unsigned int barStart = 0;
if (playMode == "rhythm") {
appendEvent(new drumstick::NoteOnEvent(9, 80, 120), 0);
appendEvent(new drumstick::NoteOnEvent(9, 80, 120), 60);
appendEvent(new drumstick::NoteOnEvent(9, 80, 120), 120);
appendEvent(new drumstick::NoteOnEvent(9, 80, 120), 180);
barStart = 240;
}
for (int i = 0; i < selectedOptions.size(); ++i) {
QString sequence = selectedOptions[i].toObject()[QStringLiteral("sequence")].toString();
unsigned int chosenRootNote = selectedOptions[i].toObject()[QStringLiteral("rootNote")].toString().toInt();
if (playMode != "rhythm") {
appendEvent(new drumstick::NoteOnEvent(1, chosenRootNote, 120), barStart);
appendEvent(new drumstick::NoteOnEvent(1, chosenRootNote, 120), barStart + 60);
unsigned int j = 1;
drumstick::SequencerEvent *ev;
foreach(const QString &additionalNote, sequence.split(' ')) {
appendEvent(ev = new drumstick::NoteOnEvent(1, chosenRootNote + additionalNote.toInt(), 120),
(playMode == "scale") ? barStart+60*j:barStart);
ev->setTag(0);
appendEvent(ev = new drumstick::NoteOffEvent(1, chosenRootNote + additionalNote.toInt(), 120),
(playMode == "scale") ? barStart+60*(j+1):barStart+60);
ev->setTag(0);
++j;
}
barStart += 60;
}
else {
appendEvent(new drumstick::NoteOnEvent(9, 80, 120), barStart);
foreach(QString additionalNote, sequence.split(' ')) { // krazy:exclude=foreach
appendEvent(new drumstick::NoteOnEvent(9, 37, 120), barStart);
float dotted = 1;
if (additionalNote.endsWith('.')) {
dotted = 1.5;
additionalNote.chop(1);
}
barStart += dotted*60*(4.0/additionalNote.toInt());
}
}
}
if (playMode == "rhythm")
appendEvent(new drumstick::NoteOnEvent(9, 80, 120), barStart);
}
void DrumstickSoundBackend::prepareFromMidiFile(const QString &fileName)
......@@ -44,14 +161,78 @@ void DrumstickSoundBackend::prepareFromMidiFile(const QString &fileName)
void DrumstickSoundBackend::play()
{
if (m_song && !m_song->isEmpty() && !m_midiSequencerOutputThread->isRunning()) {
if(m_midiSequencerOutputThread->getInitialPosition() == 0 || !m_midiSequencerOutputThread->hasNext())
m_midiSequencerOutputThread->setSong(m_song.data());
m_midiSequencerOutputThread->start();
setState(PlayingState);
}
}
void DrumstickSoundBackend::pause()
{
if (m_midiSequencerOutputThread->isRunning()) {
m_midiSequencerOutputThread->stop();
m_midiSequencerOutputThread->setPosition(m_queue->getStatus().getTickTime());
setState(PausedState);
}
}
void DrumstickSoundBackend::stop()
{
m_midiSequencerOutputThread->stop();
m_midiSequencerOutputThread->resetPosition();
setPlaybackLabel(QStringLiteral("00:00.00"));
setState(StoppedState);
}
void DrumstickSoundBackend::eventReceived(drumstick::SequencerEvent *ev)
{
static QChar fill('0');
drumstick::KeyEvent *kev;
if (!(kev = static_cast<drumstick::KeyEvent*>(ev)))
return;
// if (kev->getSequencerType() == SND_SEQ_EVENT_NOTEON && kev->getTag() == 1)
// emit noteOn(kev->getChannel(), kev->getKey(), kev->getVelocity());
// if (kev->getSequencerType() == SND_SEQ_EVENT_NOTEOFF && kev->getTag() == 1)
// emit noteOff(kev->getChannel(), kev->getKey(), kev->getVelocity());
if (m_tick != 0 && m_midiSequencerOutputThread->isRunning()) {
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 = qFloor( rt->tv_nsec / 1.0e7 );
setPlaybackLabel(QStringLiteral("%1:%2.%3").arg(mins,2,10,fill).arg(secs,2,10,fill).arg(cnts,2,10,fill));
}
}
void DrumstickSoundBackend::outputThreadStopped()
{
for (int channel = 0; channel < 16; ++channel) {
drumstick::ControllerEvent ev1(channel, MIDI_CTL_ALL_NOTES_OFF, 0);
ev1.setSource(m_outputPortId);
ev1.setSubscribers();
ev1.setDirect();
m_client->outputDirect(&ev1);
drumstick::ControllerEvent ev2(channel, MIDI_CTL_ALL_SOUNDS_OFF, 0);
ev2.setSource(m_outputPortId);
ev2.setSubscribers();
ev2.setDirect();
m_client->outputDirect(&ev2);
}
m_client->drainOutput();
}
void DrumstickSoundBackend::appendEvent(drumstick::SequencerEvent *ev, unsigned long tick)
{
ev->setSource(m_outputPortId);
if (ev->getSequencerType() != SND_SEQ_EVENT_TEMPO)
ev->setSubscribers();
ev->scheduleTick(m_queueId, tick, false);
ev->setTag(1);
m_song->append(ev);
if (tick > m_tick)
m_tick = tick;
}
#include "moc_drumsticksoundbackend.cpp"
......@@ -25,6 +25,16 @@
#include <interfaces/isoundbackend.h>
namespace drumstick {
class MidiClient;
class MidiPort;
class SequencerEvent;
class MidiQueue;
}
class MidiSequencerOutputThread;
class Song;
class DrumstickSoundBackend : public Minuet::ISoundBackend
{
Q_OBJECT
......@@ -44,6 +54,24 @@ public Q_SLOTS:
virtual void play() override;
virtual void pause() override;
virtual void stop() override;
private Q_SLOTS:
void eventReceived(drumstick::SequencerEvent *ev);
void outputThreadStopped();
private:
void appendEvent(drumstick::SequencerEvent *ev, unsigned long tick);
drumstick::MidiClient *m_client;
drumstick::MidiPort *m_outputPort;
int m_outputPortId;
drumstick::MidiPort *m_inputPort;
int m_inputPortId;
MidiSequencerOutputThread *m_midiSequencerOutputThread;
unsigned long m_tick;
drumstick::MidiQueue *m_queue;
int m_queueId;
QScopedPointer<Song> m_song;
};
#endif
/****************************************************************************
**
** Copyright (C) 2016 by Sandro S. Andrade <sandroandrade@kde.org>
**
** 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) version 3 or any later version
** accepted by the membership of KDE e.V. (or its successor approved
** by the membership of KDE e.V.), which shall act as a proxy
** defined in Section 14 of version 3 of the license.
**
** 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, see <http://www.gnu.org/licenses/>.
**
****************************************************************************/
#include "midisequenceroutputthread.h"
#include "song.h"
#include <drumstick/alsaqueue.h>
#include <cmath>
MidiSequencerOutputThread::MidiSequencerOutputThread(drumstick::MidiClient *client, int portId) :
drumstick::SequencerOutputThread(client, portId),
m_client(client),
m_song(0),
m_songPosition(0),
m_lastEvent(0),
m_volume(100),
m_pitchShift(0),
m_tempoFactor(1.0),
m_songIterator(0)
{
for (int chan = 0; chan < MIDI_CHANNELS; ++chan)
m_channelVolume[chan] = 100;
}
MidiSequencerOutputThread::~MidiSequencerOutputThread()
{
if (isRunning())
stop();
if (m_songIterator != 0)
delete m_songIterator;
if (m_lastEvent != 0)
delete m_lastEvent;
}
bool MidiSequencerOutputThread::hasNext()
{
return m_songIterator->hasNext();
}
drumstick::SequencerEvent *MidiSequencerOutputThread::nextEvent()
{
if (m_lastEvent != 0)
delete m_lastEvent;
m_lastEvent = m_songIterator->next()->clone();
switch (m_lastEvent->getSequencerType()) {
case SND_SEQ_EVENT_NOTE:
case SND_SEQ_EVENT_NOTEON:
case SND_SEQ_EVENT_NOTEOFF:
case SND_SEQ_EVENT_KEYPRESS: {
drumstick::KeyEvent *kev = static_cast<drumstick::KeyEvent*>(m_lastEvent);
if (kev->getChannel() != MIDI_GM_DRUM_CHANNEL)
kev->setKey(kev->getKey() + m_pitchShift);
}
break;
case SND_SEQ_EVENT_CONTROLLER: {
drumstick::ControllerEvent *cev = static_cast<drumstick::ControllerEvent*>(m_lastEvent);
if (cev->getParam() == MIDI_CTL_MSB_MAIN_VOLUME) {
int chan = cev->getChannel();
int value = cev->getValue();
m_channelVolume[chan] = value;
value = floor(value * m_volume / 100.0);
if (value < 0) value = 0;
if (value > 127) value = 127;
cev->setValue(value);
}
}
break;
}
return m_lastEvent;
}
void MidiSequencerOutputThread::setSong(Song *song)
{
m_song = song;
if (m_songIterator)
delete m_songIterator;
m_songIterator = new QListIterator<drumstick::SequencerEvent *>(*song);
m_songPosition = 0;
drumstick::QueueTempo firstTempo = m_Queue->getTempo();
firstTempo.setPPQ(m_song->division());
firstTempo.setTempo(m_song->initialTempo());
firstTempo.setTempoFactor(m_tempoFactor);
m_Queue->setTempo(firstTempo);
}
void MidiSequencerOutputThread::setVolume(unsigned int volume)
{
m_volume = volume;
for(int chan = 0; chan < MIDI_CHANNELS; ++chan) {
int value = m_channelVolume[chan];
value = floor(value * m_volume / 100.0);
if (value < 0) value = 0;
if (value > 127) value = 127;
sendControllerEvent(chan, MIDI_CTL_MSB_MAIN_VOLUME, value);
}
}
unsigned int MidiSequencerOutputThread::volume() const
{
return m_volume;
}
void MidiSequencerOutputThread::setPitch(int value)
{
bool playing = isRunning();
if (playing) {
stop();
unsigned int pos = m_Queue->getStatus().getTickTime();
m_Queue->clear();
allNotesOff();
setPosition(pos);
}
m_pitchShift = value;
if (playing)
start();
}
int MidiSequencerOutputThread::pitch() const
{
return m_pitchShift;
}
void MidiSequencerOutputThread::setTempoFactor(float value)
{
m_tempoFactor = value;
}
void MidiSequencerOutputThread::setPosition(unsigned int pos)
{
m_songPosition = pos;
m_songIterator->toFront();
while (m_songIterator->hasNext() && (m_songIterator->next()->getTick() < pos)) { };
if (m_songIterator->hasPrevious())
m_songIterator->previous();
}
void MidiSequencerOutputThread::resetPosition()
{
if ((m_song != NULL) && (m_songIterator != NULL)) {
m_songIterator->toFront();
m_songPosition = 0;
}
}
void MidiSequencerOutputThread::allNotesOff()
{
for(int chan = 0; chan < MIDI_CHANNELS; ++chan) {
sendControllerEvent(chan, MIDI_CTL_ALL_NOTES_OFF, 0);
sendControllerEvent(chan, MIDI_CTL_ALL_SOUNDS_OFF, 0);
}
}
void MidiSequencerOutputThread::sendControllerEvent(int chan, int control, int value)
{
drumstick::ControllerEvent ev(chan, control, value);
ev.setSource(m_PortId);
ev.setSubscribers();
ev.setDirect();
sendSongEvent(&ev);
}
/****************************************************************************
**
** Copyright (C) 2016 by Sandro S. Andrade <sandroandrade@kde.org>
**
** 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) version 3 or any later version
** accepted by the membership of KDE e.V. (or its successor approved
** by the membership of KDE e.V.), which shall act as a proxy
** defined in Section 14 of version 3 of the license.
**
** 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, see <http://www.gnu.org/licenses/>.
**
****************************************************************************/
#ifndef MIDISEQUENCEROUTPUTTHREAD_H
#define MIDISEQUENCEROUTPUTTHREAD_H
#include <drumstick/playthread.h>
namespace drumstick {
class MidiClient;
}
class Song;
class MidiSequencerOutputThread : public drumstick::SequencerOutputThread
{
Q_OBJECT
public:
MidiSequencerOutputThread(drumstick::MidiClient *client, int portId);
virtual ~MidiSequencerOutputThread();
// Virtual methods from drumstick::SequencerOutputThread
virtual bool hasNext();
virtual drumstick::SequencerEvent *nextEvent();
virtual unsigned int getInitialPosition() { return m_songPosition; }
void setSong(Song *song);
void setPitch(int value);
int pitch() const;
void setVolume(unsigned int volume);
unsigned int volume() const;
void setPosition(unsigned int pos);
void setTempoFactor(float value);
void resetPosition();
Q_SIGNALS:
void allNotesoff();
private:
void allNotesOff();
void sendControllerEvent(int chan, int control, int value);
private:
drumstick::MidiClient *m_client;