Commit a6ca2b16 authored by Sandro Andrade's avatar Sandro Andrade

Move drumstick code to soundbackend plugin

parent 28e917e3
......@@ -14,6 +14,7 @@ generate_export_header(minuetinterfaces EXPORT_FILE_NAME minuetinterfacesexport.
target_link_libraries(minuetinterfaces
Qt5::Core
Qt5::Quick
)
install(TARGETS
......
......@@ -40,7 +40,7 @@ class MINUETINTERFACES_EXPORT ICore : public QObject
Q_OBJECT
Q_PROPERTY(IPluginController * pluginController READ pluginController);
Q_PROPERTY(ISoundBackend * soundBackend READ soundBackend);
Q_PROPERTY(ISoundBackend * soundBackend READ soundBackend NOTIFY soundBackendChanged);
Q_PROPERTY(IExerciseController * exerciseController READ exerciseController);
Q_PROPERTY(IUiController * uiController READ uiController);
......@@ -54,6 +54,9 @@ public:
virtual Minuet::IExerciseController *exerciseController() = 0;
virtual Minuet::IUiController *uiController() = 0;
Q_SIGNALS:
void soundBackendChanged(ISoundBackend *newSoundBackend);
protected:
explicit ICore(QObject *parent = 0);
static ICore *m_self;
......
......@@ -22,6 +22,8 @@
#include "iexercisecontroller.h"
#include <QtQml>
namespace Minuet
{
......@@ -31,6 +33,7 @@ IExerciseController::IExerciseController(QObject *parent)
m_maxRootNote(0),
m_answerLength(1)
{
qmlRegisterInterface<IExerciseController>("IExerciseController");
}
IExerciseController::~IExerciseController()
......
......@@ -36,6 +36,7 @@ class MINUETINTERFACES_EXPORT IExerciseController : public QObject
Q_OBJECT
Q_PROPERTY(quint8 minRootNote MEMBER m_minRootNote)
Q_PROPERTY(quint8 maxRootNote MEMBER m_maxRootNote)
Q_PROPERTY(QJsonArray exercises READ exercises)
Q_PROPERTY(QJsonArray currentExercise MEMBER m_currentExercise)
Q_PROPERTY(quint8 answerLength MEMBER m_answerLength)
Q_PROPERTY(QJsonArray selectedOptions MEMBER m_selectedOptions)
......@@ -43,7 +44,7 @@ class MINUETINTERFACES_EXPORT IExerciseController : public QObject
public:
virtual ~IExerciseController() override;
virtual QJsonObject exercises() const = 0;
virtual QJsonArray exercises() const = 0;
public Q_SLOTS:
virtual void randomlySelectOptions() = 0;
......
......@@ -22,6 +22,8 @@
#include "isoundbackend.h"
#include <QtQml>
namespace Minuet
{
......@@ -29,6 +31,8 @@ ISoundBackend::ISoundBackend(QObject *parent)
: IPlugin(parent),
m_state(StoppedState)
{
qmlRegisterInterface<ISoundBackend>("ISoundBackend");
qmlRegisterUncreatableType<ISoundBackend>("org.kde.minuet", 1, 0, "ISoundBackend", "ISoundBackend cannot be instantiated");
}
ISoundBackend::~ISoundBackend()
......
......@@ -14,6 +14,7 @@ target_link_libraries(minuetdrumsticksoundbackend
drumstick-alsa
drumstick-file
Minuet::Interfaces
KF5::CoreAddons
)
install(TARGETS minuetdrumsticksoundbackend DESTINATION ${PLUGIN_INSTALL_DIR}/minuet/)
......@@ -23,6 +23,7 @@
#include "drumsticksoundbackend.h"
#include <QtMath>
#include <QTime>
#include <QJsonObject>
#include <drumstick/alsaqueue.h>
......@@ -84,7 +85,11 @@ DrumstickSoundBackend::DrumstickSoundBackend(QObject *parent)
connect(m_midiSequencerOutputThread, &MidiSequencerOutputThread::stopped, this, &DrumstickSoundBackend::outputThreadStopped);
connect(m_midiSequencerOutputThread, &MidiSequencerOutputThread::finished, this, [=]() {
setPlaybackLabel(QStringLiteral("00:00.00"));
setState(StoppedState);
});
startTimidity();
m_outputPort->subscribeTo("TiMidity:0");
}
DrumstickSoundBackend::~DrumstickSoundBackend()
......@@ -235,4 +240,55 @@ void DrumstickSoundBackend::appendEvent(drumstick::SequencerEvent *ev, unsigned
m_tick = tick;
}
void DrumstickSoundBackend::startTimidity()
{
QString error;
if (!availableOutputPorts().contains(QStringLiteral("TiMidity:0"))) {
// qCDebug(MINUET) << "Starting TiMidity++ at" << MinuetSettings::timidityLocation().remove(QStringLiteral("file://"));
// m_timidityProcess.setProgram(MinuetSettings::timidityLocation().remove(QStringLiteral("file://")), QStringList() << MinuetSettings::timidityParameters());
m_timidityProcess.setProgram("/usr/bin/timidity", QStringList() << "-iA");
m_timidityProcess.start();
if (!m_timidityProcess.waitForStarted(-1)) {
error = m_timidityProcess.errorString();
}
else {
if (!waitForTimidityOutputPorts(3000))
// error = i18n("error when waiting for TiMidity++ output ports!");
error = "error when waiting for TiMidity++ output ports!";
else
qCDebug(MINUET) << "TiMidity++ started!";
}
}
else {
qCDebug(MINUET) << "TiMidity++ already running!";
}
// if (!error.isEmpty())
// KMessageBox::error(this,
// i18n("There was an error when starting TiMidity++: \"%1\". "
// "Is another application using the audio system? "
// "Also, please check Minuet settings!", error),
// i18n("Minuet startup"));
}
bool DrumstickSoundBackend::waitForTimidityOutputPorts(int msecs)
{
QTime time;
time.start();
while (!availableOutputPorts().contains(QStringLiteral("TiMidity:0")))
if (msecs != -1 && time.elapsed() > msecs)
return false;
return true;
}
QStringList DrumstickSoundBackend::availableOutputPorts() const
{
QStringList availableOutputPorts;
QListIterator<drumstick::PortInfo> it(m_client->getAvailableOutputs());
while(it.hasNext()) {
drumstick::PortInfo p = it.next();
availableOutputPorts << QStringLiteral("%1:%2").arg(p.getClientName()).arg(p.getPort());
}
return availableOutputPorts;
}
#include "moc_drumsticksoundbackend.cpp"
......@@ -25,6 +25,13 @@
#include <interfaces/isoundbackend.h>
#include <QLoggingCategory>
#include <KProcess>
Q_DECLARE_LOGGING_CATEGORY(MINUET)
Q_LOGGING_CATEGORY(MINUET, "minuet")
namespace drumstick {
class MidiClient;
class MidiPort;
......@@ -61,6 +68,9 @@ private Q_SLOTS:
private:
void appendEvent(drumstick::SequencerEvent *ev, unsigned long tick);
void startTimidity();
bool waitForTimidityOutputPorts(int msecs);
QStringList availableOutputPorts() const;
drumstick::MidiClient *m_client;
drumstick::MidiPort *m_outputPort;
......@@ -72,6 +82,8 @@ private:
drumstick::MidiQueue *m_queue;
int m_queueId;
QScopedPointer<Song> m_song;
KProcess m_timidityProcess;
};
#endif
......@@ -53,9 +53,6 @@ public:
void setTempoFactor(float value);
void resetPosition();
Q_SIGNALS:
void allNotesoff();
private:
void allNotesOff();
void sendControllerEvent(int chan, int control, int value);
......
......@@ -47,7 +47,7 @@ bool Core::initialize()
IPluginController *Core::pluginController()
{
return m_pluginController.data();
return m_pluginController;
}
ISoundBackend *Core::soundBackend()
......@@ -57,29 +57,34 @@ ISoundBackend *Core::soundBackend()
IExerciseController *Core::exerciseController()
{
return m_exerciseController.data();
return m_exerciseController;
}
IUiController *Core::uiController()
{
return m_uiController.data();
return m_uiController;
}
void Core::setSoundBackend(ISoundBackend *soundBackend)
{
m_soundBackend = soundBackend;
if (m_soundBackend != soundBackend) {
m_soundBackend = soundBackend;
emit soundBackendChanged(m_soundBackend);
}
}
Core::Core(QObject *parent)
: ICore(parent),
m_pluginController(new PluginController),
m_soundBackend(0),
m_exerciseController(new ExerciseController),
m_uiController(new UiController)
m_soundBackend(0)
{
((PluginController *)m_pluginController.data())->initialize(this);
((UiController *)m_uiController.data())->initialize();
m_pluginController = new PluginController(this);
((PluginController *)m_pluginController)->initialize(this);
m_exerciseController = new ExerciseController(this);
((ExerciseController *)m_exerciseController)->initialize(this);
m_uiController = new UiController(this);
((UiController *)m_uiController)->initialize(this);
}
}
......
......@@ -49,10 +49,10 @@ public:
private:
Core(QObject *parent = 0);
QScopedPointer<IPluginController> m_pluginController;
IPluginController *m_pluginController;
ISoundBackend *m_soundBackend;
QScopedPointer<IExerciseController> m_exerciseController;
QScopedPointer<IUiController> m_uiController;
IExerciseController *m_exerciseController;
IUiController *m_uiController;
};
}
......
......@@ -38,8 +38,8 @@
namespace Minuet
{
ExerciseController::ExerciseController(MidiSequencer *midiSequencer) :
m_midiSequencer(midiSequencer),
ExerciseController::ExerciseController(QObject *parent) :
IExerciseController(parent),
m_chosenRootNote(0)
{
m_exercises["exercises"] = QJsonArray();
......@@ -51,7 +51,7 @@ ExerciseController::~ExerciseController()
{
}
bool ExerciseController::initialize()
bool ExerciseController::initialize(Core *core)
{
bool definitionsMerge = mergeJsonFiles("definitions", m_definitions);
bool exercisesMerge = mergeJsonFiles("exercises", m_exercises, true, "name", "children");
......@@ -98,19 +98,14 @@ unsigned int ExerciseController::chosenRootNote()
return m_chosenRootNote;
}
void ExerciseController::playChoosenExercise()
{
m_midiSequencer->play();
}
QString ExerciseController::errorString() const
{
return m_errorString;
}
QJsonObject ExerciseController::exercises() const
QJsonArray ExerciseController::exercises() const
{
return m_exercises;
return m_exercises[QStringLiteral("exercises")].toArray();
}
bool ExerciseController::mergeJsonFiles(const QString directoryName, QJsonObject &targetObject, bool applyDefinitionsFlag, QString commonKey, QString mergeKey)
......
......@@ -34,22 +34,23 @@ class MidiSequencer;
namespace Minuet
{
class Core;
class MINUETSHELL_EXPORT ExerciseController : public IExerciseController
{
Q_OBJECT
public:
explicit ExerciseController(MidiSequencer *midiSequencer = 0);
explicit ExerciseController(QObject *parent= 0);
virtual ~ExerciseController();
bool initialize();
bool initialize(Core *core);
Q_INVOKABLE unsigned int chosenRootNote();
Q_INVOKABLE void playChoosenExercise();
QString errorString() const;
virtual QJsonObject exercises() const override;
virtual QJsonArray exercises() const override;
public Q_SLOTS:
virtual void randomlySelectOptions();
......@@ -64,7 +65,6 @@ private:
void filterDefinitions(QJsonArray &definitions, QJsonObject &exerciseObject, const QString &filterTagsKey, DefinitionFilteringMode definitionFilteringMode);
QJsonArray mergeJsonArrays(QJsonArray oldFile, QJsonArray newFile, QString commonKey = "", QString mergeKey = "");
MidiSequencer *m_midiSequencer;
QJsonObject m_exercises;
QJsonObject m_definitions;
unsigned int m_chosenRootNote;
......
......@@ -47,7 +47,7 @@ MidiSequencer::MidiSequencer(QObject *parent) :
m_state(StoppedState),
m_playMode(ScalePlayMode)
{
qmlRegisterType<MidiSequencer>("org.kde.minuet", 1, 0, "MidiSequencer");
qmlRegisterUncreatableType<MidiSequencer>("org.kde.minuet", 1, 0, "MidiSequencer", "MidiSequencer can't be instantiated");
// MidiClient configuration
m_client = new drumstick::MidiClient(this);
try {
......
......@@ -53,9 +53,6 @@ public:
void setTempoFactor(float value);
void resetPosition();
Q_SIGNALS:
void allNotesoff();
private:
void allNotesOff();
void sendControllerEvent(int chan, int control, int value);
......
......@@ -23,7 +23,6 @@
#include "minuetmainwindow.h"
#include "wizard.h"
#include "midisequencer.h"
#include "exercisecontroller.h"
#include <KActionCollection>
......@@ -45,27 +44,16 @@ Q_LOGGING_CATEGORY(MINUET, "minuet")
#include <QToolBar>
MinuetMainWindow::MinuetMainWindow() :
KXmlGuiWindow(),
m_midiSequencer(new MidiSequencer(this)),
m_exerciseController(new Minuet::ExerciseController(m_midiSequencer)),
#include "core.h"
MinuetMainWindow::MinuetMainWindow(Minuet::Core *core, QWidget *parent, Qt::WindowFlags f) :
KXmlGuiWindow(parent, f),
m_quickView(new QQuickView),
m_initialGroup(KSharedConfig::openConfig(), "version")
{
if (m_midiSequencer->schedulingMode() == MidiSequencer::DAMAGED) {
QTimer::singleShot(0, qApp, SLOT(quit()));
return;
}
if (!m_exerciseController->initialize())
KMessageBox::error(this,
i18n("There was an error when parsing exercises JSON files: \"%1\".", m_exerciseController->errorString()),
i18n("Minuet startup"));
QQmlContext *rootContext = m_quickView->engine()->rootContext();
rootContext->setContextProperty(QStringLiteral("exerciseCategories"), m_exerciseController->exercises()[QStringLiteral("exercises")].toArray());
rootContext->setContextProperty(QStringLiteral("sequencer"), m_midiSequencer);
rootContext->setContextProperty(QStringLiteral("exerciseController"), m_exerciseController);
rootContext->setContextProperty(QStringLiteral("core"), core);
m_quickView->setSource(QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::DataLocation, QStringLiteral("qml/Main.qml"))));
m_quickView->setResizeMode(QQuickView::SizeRootObjectToView);
setCentralWidget(QWidget::createWindowContainer(m_quickView, this));
......@@ -85,66 +73,11 @@ MinuetMainWindow::MinuetMainWindow() :
if (!m_initialGroup.exists())
runWizard();
startTimidity();
subscribeToMidiOutputPort();
}
void MinuetMainWindow::startTimidity()
{
QString error;
if (!m_midiSequencer->availableOutputPorts().contains(QStringLiteral("TiMidity:0"))) {
qCDebug(MINUET) << "Starting TiMidity++ at" << MinuetSettings::timidityLocation().remove(QStringLiteral("file://"));
m_timidityProcess.setProgram(MinuetSettings::timidityLocation().remove(QStringLiteral("file://")), QStringList() << MinuetSettings::timidityParameters());
m_timidityProcess.start();
if (!m_timidityProcess.waitForStarted(-1)) {
error = m_timidityProcess.errorString();
}
else {
if (!waitForTimidityOutputPorts(3000))
error = i18n("error when waiting for TiMidity++ output ports!");
else
qCDebug(MINUET) << "TiMidity++ started!";
}
}
else {
qCDebug(MINUET) << "TiMidity++ already running!";
}
if (!error.isEmpty())
KMessageBox::error(this,
i18n("There was an error when starting TiMidity++: \"%1\". "
"Is another application using the audio system? "
"Also, please check Minuet settings!", error),
i18n("Minuet startup"));
}
bool MinuetMainWindow::waitForTimidityOutputPorts(int msecs)
{
QTime time;
time.start();
while (!m_midiSequencer->availableOutputPorts().contains(QStringLiteral("TiMidity:0")))
if (msecs != -1 && time.elapsed() > msecs)
return false;
return true;
}
void MinuetMainWindow::subscribeToMidiOutputPort()
{
QString midiOutputPort = MinuetSettings::midiOutputPort();
if (!midiOutputPort.isEmpty() && m_midiSequencer->availableOutputPorts().contains(midiOutputPort))
m_midiSequencer->subscribeTo(midiOutputPort);
}
MinuetMainWindow::~MinuetMainWindow()
{
delete m_quickView;
delete m_exerciseController;
m_timidityProcess.kill();
qCDebug(MINUET) << "Stoping TiMidity++!";
if (!m_timidityProcess.waitForFinished(-1))
qCDebug(MINUET) << "Error when stoping TiMidity++:" << m_timidityProcess.errorString();
else
qCDebug(MINUET) << "TiMidity++ stoped!";
}
bool MinuetMainWindow::queryClose()
......@@ -153,15 +86,6 @@ bool MinuetMainWindow::queryClose()
return true;
}
/*
void MinuetMainWindow::fileOpen()
{
QString fileName = QFileDialog::getOpenFileName(this, i18n("Open File")); // krazy:exclude=qclasses
if (!fileName.isEmpty())
m_midiSequencer->openFile(fileName);
}
*/
void MinuetMainWindow::runWizard()
{
QScopedPointer<Wizard> w (new Wizard(this));
......@@ -180,11 +104,8 @@ void MinuetMainWindow::settingsConfigure()
QWidget *midiSettingsDialog = new QWidget;
m_settingsMidi.setupUi(midiSettingsDialog);
m_settingsMidi.kcfg_midiOutputPort->setVisible(false);
m_settingsMidi.cboMidiOutputPort->insertItems(0, m_midiSequencer->availableOutputPorts());
m_settingsMidi.cboMidiOutputPort->setCurrentIndex(m_settingsMidi.cboMidiOutputPort->findText(MinuetSettings::midiOutputPort()));
dialog->addPage(midiSettingsDialog, i18n("MIDI"), QStringLiteral("media-playback-start"));
dialog->setAttribute(Qt::WA_DeleteOnClose);
if (dialog->exec() == QDialog::Accepted)
subscribeToMidiOutputPort();
delete dialog;
}
......@@ -25,18 +25,16 @@
#include "ui_settingsmidi.h"
#include "minuetsettings.h"
#include "midisequencer.h"
#include <KProcess>
#include <KXmlGuiWindow>
#include <QLoggingCategory>
class QQuickView;
class MidiSequencer;
namespace Minuet {
class ExerciseController;
class Core;
}
Q_DECLARE_LOGGING_CATEGORY(MINUET)
......@@ -57,7 +55,7 @@ public:
/**
* Default Constructor
*/
MinuetMainWindow();
MinuetMainWindow(Minuet::Core *core, QWidget *parent=0, Qt::WindowFlags f=KDE_DEFAULT_WINDOWFLAGS);
/**
* Default Destructor
......@@ -67,11 +65,6 @@ public:
protected:
virtual bool queryClose();
private:
void startTimidity();
bool waitForTimidityOutputPorts(int msecs);
void subscribeToMidiOutputPort();
private Q_SLOTS:
/**
* Create a new window
......@@ -86,11 +79,8 @@ private Q_SLOTS:
private:
Ui::SettingsMidi m_settingsMidi;
MidiSequencer *m_midiSequencer;
Minuet::ExerciseController *m_exerciseController;
QQuickView *m_quickView;
KConfigGroup m_initialGroup;
KProcess m_timidityProcess;
};
#endif // MINUETMAINWINDOW_H
......
......@@ -51,12 +51,12 @@ Item {
answerRectangle = answerGrid.children[i]
}
answerRectangle.model.sequence.split(' ').forEach(function(note) {
answerHoverEnter(0, exerciseController.chosenRootNote() + parseInt(note), 0, answerRectangle.color)
answerHoverEnter(0, core.exerciseController.chosenRootNote() + parseInt(note), 0, answerRectangle.color)
})
animation.start()
}
function itemChanged(model) {
sequencer.allNotesOff()
// sequencer.allNotesOff()
clearExerciseGrid()
var length = model.length
answerGrid.columns = Math.min(6, length)
......@@ -109,9 +109,9 @@ Item {
text: i18n("new question")
onClicked: {
exerciseView.state = "waitingForAnswer"
exerciseController.randomlySelectOptions()
var selectedOptions = exerciseController.selectedOptions
sequencer.generateSong(selectedOptions)
core.exerciseController.randomlySelectOptions()
var selectedOptions = core.exerciseController.selectedOptions
core.soundBackend.prepareFromExerciseOptions(selectedOptions, "scale")
var newChosenExercises = [];
for (var i = 0; i < selectedOptions.length; ++i)
newChosenExercises.push(selectedOptions[i].name);
......@@ -126,8 +126,8 @@ Item {
return i18n("Hear %1 and then choose an answer from options below!<br/>Click 'play question' if you want to hear again!", i18nc("technical term, do you have a musician friend?", userMessage))
})
if (userMessage != "the rhythm")
answerHoverEnter(0, exerciseController.chosenRootNote(), 0, "white")
exerciseController.playChoosenExercise()
answerHoverEnter(0, core.exerciseController.chosenRootNote(), 0, "white")
core.soundBackend.play()
}
style: MinuetButtonStyle{ labelHorizontalAlignment: Qt.AlignHCenter }
}
......@@ -136,7 +136,7 @@ Item {
width: 124; height: 44
text: i18n("play question")
onClicked: exerciseController.playChoosenExercise()
onClicked: core.soundBackend.play()
style: MinuetButtonStyle{ labelHorizontalAlignment: Qt.AlignHCenter }
}
Button {
......@@ -210,7 +210,7 @@ Item {
messageText.text = i18n("Congratulations!<br/>You answered correctly!")
else
messageText.text = i18n("Oops, not this time!<br/>Try again!")
answerHoverExit(0, exerciseController.chosenRootNote() + parseInt(model.sequence), 0)
answerHoverExit(0, core.exerciseController.chosenRootNote() + parseInt(model.sequence), 0)
highlightRightAnswer()
}
else {
......@@ -222,7 +222,7 @@ Item {
answerRectangle.color = Qt.darker(answerRectangle.color, 1.1)
if (userMessage != "the rhythm") {
model.sequence.split(' ').forEach(function(note) {
answerHoverEnter(0, exerciseController.chosenRootNote() + parseInt(note), 0, colors[answerRectangle.index])
answerHoverEnter(0, core.exerciseController.chosenRootNote() + parseInt(note), 0, colors[answerRectangle.index])
})
}
}
......@@ -231,7 +231,7 @@ Item {
if (userMessage != "the rhythm") {
if (!animation.running)
model.sequence.split(' ').forEach(function(note) {
answerHoverExit(0, exerciseController.chosenRootNote() + parseInt(note), 0)
answerHoverExit(0, core.exerciseController.chosenRootNote() + parseInt(note), 0)
})
}
}
......@@ -258,7 +258,7 @@ Item {
name: "waitingForAnswer"
StateChangeScript {