diff --git a/drummachinesettings.cpp b/drummachinesettings.cpp index 4c67b20..a57c9f0 100644 --- a/drummachinesettings.cpp +++ b/drummachinesettings.cpp @@ -196,6 +196,16 @@ void DrumMachineSettings::setLoopstationNextPreset(const MidiLearnSetting &value setLearnSetting("loopstation/nextPreset", value); } +MidiLearnSetting DrumMachineSettings::loopstationSample(quint8 pad) const +{ + return learnSetting(QString{"loopstation/pad%0"}.arg(pad)); +} + +void DrumMachineSettings::setLoopstationSample(quint8 pad, const MidiLearnSetting &value) +{ + setLearnSetting(QString{"loopstation/pad%0"}.arg(pad), value); +} + MidiLearnSetting DrumMachineSettings::learnSetting(const QString &key) const { return MidiLearnSetting{ diff --git a/drummachinesettings.h b/drummachinesettings.h index a524c45..ef640bc 100644 --- a/drummachinesettings.h +++ b/drummachinesettings.h @@ -69,6 +69,9 @@ public: MidiLearnSetting loopstationNextPreset() const; void setLoopstationNextPreset(const MidiLearnSetting &value); + MidiLearnSetting loopstationSample(quint8 pad) const; + void setLoopstationSample(quint8 pad, const MidiLearnSetting &value); + private: MidiLearnSetting learnSetting(const QString &key) const; void setLearnSetting(const QString &key, const MidiLearnSetting &value); diff --git a/widgets/drumpadsampleswidget.cpp b/widgets/drumpadsampleswidget.cpp index 8165961..7b0091e 100755 --- a/widgets/drumpadsampleswidget.cpp +++ b/widgets/drumpadsampleswidget.cpp @@ -52,9 +52,7 @@ void DrumPadSamplesWidget::midiReceived(const midi::MidiMessage &message) } for (DrumPadSampleWidget &widget : getWidgets()) - { widget.midiReceived(message); - } } void DrumPadSamplesWidget::writeSamples(frame_t *begin, frame_t *end) diff --git a/widgets/drumpadsamplewidget.cpp b/widgets/drumpadsamplewidget.cpp index b96171c..bf056d9 100755 --- a/widgets/drumpadsamplewidget.cpp +++ b/widgets/drumpadsamplewidget.cpp @@ -2,7 +2,6 @@ #include "ui_drumpadsamplewidget.h" #include -#include #include #include #include @@ -17,7 +16,7 @@ namespace { QString toString(QString value) { return value; } QString toString(int value) { return QString::number(value); } QString toString(bool value) { return value?"true":"false"; } -} +} // namespace DrumPadSampleWidget::DrumPadSampleWidget(QWidget *parent) : QFrame{parent}, @@ -212,7 +211,6 @@ void DrumPadSampleWidget::updateStatus() { QPalette pal; - if (m_file && m_file->color && m_player.buffer().isValid()) { const auto bright = m_player.playing() ? 255 : 155; @@ -264,9 +262,7 @@ void DrumPadSampleWidget::updateStatus() void DrumPadSampleWidget::requestFinished() { if (m_reply->error() == QNetworkReply::NoError) - { emit startDecoding(m_reply); - } updateStatus(); } diff --git a/widgets/loopstationsampleswidget.cpp b/widgets/loopstationsampleswidget.cpp index b186df0..c6333a3 100644 --- a/widgets/loopstationsampleswidget.cpp +++ b/widgets/loopstationsampleswidget.cpp @@ -1,54 +1,82 @@ #include "loopstationsampleswidget.h" #include "ui_loopstationsampleswidget.h" +#include "loopstationpresets.h" + LoopStationSamplesWidget::LoopStationSamplesWidget(QWidget *parent) : QWidget{parent}, m_ui{std::make_unique()} { m_ui->setupUi(this); + + quint8 padNr{}; + for (LoopStationSampleWidget &widget : getWidgets()) + { + widget.setPadNr(padNr++); + connect(&widget, &LoopStationSampleWidget::sendMidi, this, &LoopStationSamplesWidget::sendMidi); + } } LoopStationSamplesWidget::~LoopStationSamplesWidget() = default; void LoopStationSamplesWidget::loadSettings(DrumMachineSettings &settings) { - Q_UNUSED(settings) + for (LoopStationSampleWidget &widget : getWidgets()) + widget.loadSettings(settings); } void LoopStationSamplesWidget::setPreset(const loopstation_presets::Preset &preset) { - Q_UNUSED(preset) + assert(preset.id); + assert(preset.pads); + + const auto &presetId = *preset.id; + const auto &widgets = getWidgets(); + const auto &pads = *preset.pads; + + auto iter = std::begin(widgets); + auto iter2 = std::begin(pads); + int i{}; + for (; iter != std::end(widgets) && iter2 != std::end(pads); iter++, iter2++) + { + ((*iter)).get().setSample(presetId, QString{"%0_%1.wav"}.arg(presetId).arg(++i, 2, 10, QLatin1Char('0')), *iter2); + } } void LoopStationSamplesWidget::midiReceived(const midi::MidiMessage &message) { - Q_UNUSED(message) + for (LoopStationSampleWidget &widget : getWidgets()) + widget.midiReceived(message); } void LoopStationSamplesWidget::writeSamples(frame_t *begin, frame_t *end) { - Q_UNUSED(begin) - Q_UNUSED(end) + for (LoopStationSampleWidget &widget : getWidgets()) + widget.writeSamples(begin, end); } void LoopStationSamplesWidget::injectNetworkAccessManager(QNetworkAccessManager &networkAccessManager) { - Q_UNUSED(networkAccessManager) + for (LoopStationSampleWidget &widget : getWidgets()) + widget.injectNetworkAccessManager(networkAccessManager); } void LoopStationSamplesWidget::injectDecodingThread(QThread &thread) { - Q_UNUSED(thread) + for (LoopStationSampleWidget &widget : getWidgets()) + widget.injectDecodingThread(thread); } void LoopStationSamplesWidget::unsendColors() { - + for (LoopStationSampleWidget &widget : getWidgets()) + widget.unsendColor(); } void LoopStationSamplesWidget::sendColors() { - + for (LoopStationSampleWidget &widget : getWidgets()) + widget.sendColor(); } std::array, 48> LoopStationSamplesWidget::getWidgets() diff --git a/widgets/loopstationsamplewidget.cpp b/widgets/loopstationsamplewidget.cpp index ae28cb2..c927492 100644 --- a/widgets/loopstationsamplewidget.cpp +++ b/widgets/loopstationsamplewidget.cpp @@ -1,11 +1,166 @@ #include "loopstationsamplewidget.h" #include "ui_loopstationsamplewidget.h" +#include +#include +#include +#include +#include + +#include "audioformat.h" +#include "audiodecoder.h" +#include "drummachinesettings.h" +#include "midicontainers.h" + LoopStationSampleWidget::LoopStationSampleWidget(QWidget *parent) : QFrame{parent}, m_ui{std::make_unique()} { m_ui->setupUi(this); + + connect(&m_player, &AudioPlayer::playingChanged, this, &LoopStationSampleWidget::updateStatus); + + connect(m_ui->pushButtonPlay, &QAbstractButton::pressed, this, &LoopStationSampleWidget::pressed); + + updateStatus(); } LoopStationSampleWidget::~LoopStationSampleWidget() = default; + +void LoopStationSampleWidget::loadSettings(DrumMachineSettings &settings) +{ + m_settings = &settings; + + m_ui->pushButtonPlay->setLearnSetting(m_settings->loopstationSample(m_padNr)); + + connect(m_ui->pushButtonPlay, &MidiButton::learnSettingChanged, this, [this](const MidiLearnSetting &learnSetting){ + Q_ASSERT(m_settings); + if (m_settings) + m_settings->setLoopstationSample(m_padNr, learnSetting); + else + qWarning() << "no settings available"; + }); +} + +void LoopStationSampleWidget::setSample(const QString &presetId, const QString &filename, const QString &type) +{ + m_presetId = presetId; + m_filename = filename; + + m_player.setBuffer({}); + + startRequest(); + + m_ui->labelFilename->setText(filename); + m_ui->labelType->setText(type); +} + +void LoopStationSampleWidget::pressed() +{ + m_player.restart(); +} + +void LoopStationSampleWidget::injectNetworkAccessManager(QNetworkAccessManager &networkAccessManager) +{ + m_networkAccessManager = &networkAccessManager; + if (!m_presetId.isEmpty() && !m_filename.isEmpty()) + startRequest(); +} + +void LoopStationSampleWidget::injectDecodingThread(QThread &thread) +{ + QMetaObject::invokeMethod(QAbstractEventDispatcher::instance(&thread), [this](){ + m_decoder = std::make_unique(); + connect(this, &LoopStationSampleWidget::startDecoding, m_decoder.get(), &AudioDecoder::startDecodingDevice); + connect(m_decoder.get(), &AudioDecoder::decodingFinished, this, &LoopStationSampleWidget::decodingFinished); + if (m_reply && m_reply->isFinished() && m_reply->error() == QNetworkReply::NoError) + m_decoder->startDecodingDevice(m_reply); + }); +} + +void LoopStationSampleWidget::writeSamples(frame_t *begin, frame_t *end) +{ + m_player.writeSamples(begin, end); +} + +void LoopStationSampleWidget::midiReceived(const midi::MidiMessage &message) +{ + m_ui->pushButtonPlay->midiReceived(message); +} + +void LoopStationSampleWidget::unsendColor() +{ + m_sendColors = false; + + emit sendMidi(midi::MidiMessage { + .channel = m_ui->pushButtonPlay->learnSetting().channel, + .cmd = m_ui->pushButtonPlay->learnSetting().cmd, + .flag = true, + .note = m_ui->pushButtonPlay->learnSetting().note, + .velocity = 0 + }); +} + +void LoopStationSampleWidget::sendColor() +{ + m_sendColors = true; + + emit sendMidi(midi::MidiMessage { + .channel = m_ui->pushButtonPlay->learnSetting().channel, + .cmd = m_ui->pushButtonPlay->learnSetting().cmd, + .flag = true, + .note = m_ui->pushButtonPlay->learnSetting().note, + .velocity = uint8_t(m_padNr+1) + }); +} + +void LoopStationSampleWidget::updateStatus() +{ + if (m_sendColors) + sendColor(); + + if (m_reply) + { + if (!m_reply->isFinished()) + m_ui->labelStatus->setText(tr("Downloading...")); + else if (m_reply->error() != QNetworkReply::NoError) + m_ui->labelStatus->setText(QMetaEnum::fromType().valueToKey(m_reply->error())); + else + { + if (!m_decoder) + m_ui->labelStatus->setText(tr("Waiting for decoder thread...")); + else + m_ui->labelStatus->setText(tr("Decoding")); + } + } + else + m_ui->labelStatus->setText(m_player.playing() ? tr("Playing") : tr("Ready")); +} + +void LoopStationSampleWidget::requestFinished() +{ + if (m_reply->error() == QNetworkReply::NoError) + emit startDecoding(m_reply); + updateStatus(); +} + +void LoopStationSampleWidget::decodingFinished(const QAudioBuffer &buffer) +{ + m_reply = nullptr; + m_player.setBuffer(buffer); + updateStatus(); +} + +void LoopStationSampleWidget::startRequest() +{ + if (m_networkAccessManager && !m_presetId.isEmpty() && !m_filename.isEmpty()) + { + QNetworkRequest request{QUrl{QString{"https://brunner.ninja/komposthaufen/groovepad/presets/extracted/%0/%1"}.arg(m_presetId, m_filename)}}; + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); + request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, true); + m_reply = std::shared_ptr{m_networkAccessManager->get(request)}; + connect(m_reply.get(), &QNetworkReply::finished, this, &LoopStationSampleWidget::requestFinished); + } + + updateStatus(); +} diff --git a/widgets/loopstationsamplewidget.h b/widgets/loopstationsamplewidget.h index b2403d6..a46cbeb 100644 --- a/widgets/loopstationsamplewidget.h +++ b/widgets/loopstationsamplewidget.h @@ -4,7 +4,16 @@ #include +#include "audioplayer.h" + namespace Ui { class LoopStationSampleWidget; } +class QNetworkAccessManager; +class QNetworkReply; +class QAudioBuffer; +class AudioDecoder; +class DrumMachineSettings; +namespace midi { struct MidiMessage; } +struct frame_t; class LoopStationSampleWidget : public QFrame { @@ -12,8 +21,55 @@ class LoopStationSampleWidget : public QFrame public: explicit LoopStationSampleWidget(QWidget *parent = nullptr); - ~LoopStationSampleWidget(); + ~LoopStationSampleWidget() override; + + quint8 padNr() const { return m_padNr; } + void setPadNr(quint8 padNr) { m_padNr = padNr; } + + void loadSettings(DrumMachineSettings &settings); + + void setSample(const QString &presetId, const QString &filename, const QString &type); + + void pressed(); + + void injectNetworkAccessManager(QNetworkAccessManager &networkAccessManager); + void injectDecodingThread(QThread &thread); + + void writeSamples(frame_t *begin, frame_t *end); + + void midiReceived(const midi::MidiMessage &message); + + void unsendColor(); + void sendColor(); + +signals: + void startDecoding(std::shared_ptr device); + void sendMidi(const midi::MidiMessage &midiMsg); + +private slots: + void updateStatus(); + void requestFinished(); + void decodingFinished(const QAudioBuffer &buffer); private: + void startRequest(); + const std::unique_ptr m_ui; + + DrumMachineSettings *m_settings{}; + + std::shared_ptr m_reply; + + std::unique_ptr m_decoder; + + AudioPlayer m_player; + + QString m_presetId; + QString m_filename; + + QNetworkAccessManager *m_networkAccessManager{}; + + quint8 m_padNr{}; + + bool m_sendColors{}; }; diff --git a/widgets/loopstationsamplewidget.ui b/widgets/loopstationsamplewidget.ui index 40eeaa4..f7d43b7 100644 --- a/widgets/loopstationsamplewidget.ui +++ b/widgets/loopstationsamplewidget.ui @@ -51,6 +51,13 @@ + + + + TextLabel + + + diff --git a/widgets/loopstationwidget.cpp b/widgets/loopstationwidget.cpp index 5aac81b..182bcaf 100644 --- a/widgets/loopstationwidget.cpp +++ b/widgets/loopstationwidget.cpp @@ -22,6 +22,8 @@ LoopStationWidget::LoopStationWidget(QWidget *parent) : connect(m_ui->pushButtonDown, &QAbstractButton::pressed, this, &LoopStationWidget::selectNextPreset); connect(m_ui->pushButtonRefresh, &QAbstractButton::pressed, this, &LoopStationWidget::loadPresets); + connect(m_ui->samplesWidget, &LoopStationSamplesWidget::sendMidi, this, &LoopStationWidget::sendMidi); + m_presetsProxyModel.setFilterCaseSensitivity(Qt::CaseInsensitive); m_presetsProxyModel.setSortRole(Qt::EditRole); m_presetsProxyModel.setSourceModel(&m_presetsModel);