Refactorings and preperations for loop station

This commit is contained in:
2022-12-18 03:30:46 +01:00
parent 68d420cf41
commit a0efca01c7
24 changed files with 735 additions and 353 deletions

View File

@ -16,6 +16,7 @@ SOURCES += \
audioplayer.cpp \
djwidget.cpp \
drummachinesettings.cpp \
drumpadwidget.cpp \
filesmodel.cpp \
graphrenderer.cpp \
jsonconverters.cpp \
@ -33,6 +34,7 @@ SOURCES += \
scratchwidget.cpp \
sequencerwidget.cpp \
synthisizer.cpp \
synthisizerwidget.cpp \
trackdeck.cpp \
treetotableproxymodel.cpp
@ -42,6 +44,7 @@ HEADERS += \
audioplayer.h \
djwidget.h \
drummachinesettings.h \
drumpadwidget.h \
filesmodel.h \
graphrenderer.h \
jsonconverters.h \
@ -58,16 +61,19 @@ HEADERS += \
scratchwidget.h \
sequencerwidget.h \
synthisizer.h \
synthisizerwidget.h \
trackdeck.h \
treetotableproxymodel.h
FORMS += \
djwidget.ui \
drumpadwidget.ui \
mainwindow.ui \
presetdetailwidget.ui \
sampleswidget.ui \
samplewidget.ui \
sequencerwidget.ui \
synthisizerwidget.ui \
trackdeck.ui
RESOURCES += \

View File

@ -75,6 +75,22 @@ void DjWidget::writeSamples(frame_t *begin, frame_t *end)
}
}
void DjWidget::loadSettings(DrumMachineSettings &settings)
{
}
void DjWidget::unsendColors()
{
}
void DjWidget::sendColors()
{
}
void DjWidget::messageReceived(const midi::MidiMessage &message)
{
}
void DjWidget::directorySelected()
{
const auto selected = m_ui->treeViewDirectories->currentIndex();

View File

@ -7,9 +7,11 @@
#include <memory>
#include "audioformat.h"
#include "midicontainers.h"
#include "treetotableproxymodel.h"
namespace Ui { class DjWidget; }
class DrumMachineSettings;
class DjWidget : public QWidget
{
@ -20,8 +22,16 @@ public:
~DjWidget() override;
void injectDecodingThread(QThread &thread);
void writeSamples(frame_t *begin, frame_t *end);
void loadSettings(DrumMachineSettings &settings);
void unsendColors();
void sendColors();
signals:
void sendMidi(const midi::MidiMessage &midiMsg);
public slots:
void messageReceived(const midi::MidiMessage &message);
private slots:
void directorySelected();

152
drumpadwidget.cpp Normal file
View File

@ -0,0 +1,152 @@
#include "drumpadwidget.h"
#include "ui_drumpadwidget.h"
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QNetworkAccessManager>
#include <QMessageBox>
#include "jsonconverters.h"
DrumPadWidget::DrumPadWidget(QWidget *parent) :
QSplitter{parent},
m_ui{std::make_unique<Ui::DrumPadWidget>()}
{
m_ui->setupUi(this);
connect(m_ui->samplesWidget, &SamplesWidget::sendMidi, this, &DrumPadWidget::sendMidi);
connect(m_ui->sequencerWidget, &SequencerWidget::triggerSample, m_ui->samplesWidget, &SamplesWidget::sequencerTriggerSample);
m_presetsProxyModel.setFilterCaseSensitivity(Qt::CaseInsensitive);
m_presetsProxyModel.setSortRole(Qt::EditRole);
m_presetsProxyModel.setSourceModel(&m_presetsModel);
m_ui->presetsView->setModel(&m_presetsProxyModel);
m_presetsProxyModel.setFilterKeyColumn(1);
connect(m_ui->lineEdit, &QLineEdit::textChanged, &m_presetsProxyModel, &QSortFilterProxyModel::setFilterFixedString);
m_ui->filesView->setModel(&m_filesModel);
connect(m_ui->presetsView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &DrumPadWidget::currentRowChanged);
connect(m_ui->pushButtonRefresh, &QAbstractButton::pressed, this, &DrumPadWidget::loadPresets);
}
DrumPadWidget::~DrumPadWidget() = default;
void DrumPadWidget::selectFirstPreset()
{
if (!m_presetsProxyModel.rowCount())
return;
const auto index = m_presetsProxyModel.index(0, 0);
if (index.isValid())
{
m_ui->presetsView->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
currentRowChanged(index);
}
}
void DrumPadWidget::writeSamples(frame_t *begin, frame_t *end)
{
m_ui->samplesWidget->writeSamples(begin, end);
}
void DrumPadWidget::injectNetworkAccessManager(QNetworkAccessManager &networkAccessManager)
{
m_networkAccessManager = &networkAccessManager;
loadPresets();
m_ui->samplesWidget->injectNetworkAccessManager(networkAccessManager);
}
void DrumPadWidget::injectDecodingThread(QThread &thread)
{
m_ui->samplesWidget->injectDecodingThread(thread);
}
void DrumPadWidget::loadSettings(DrumMachineSettings &settings)
{
m_ui->samplesWidget->loadSettings(settings);
}
void DrumPadWidget::unsendColors()
{
m_ui->samplesWidget->unsendColors();
}
void DrumPadWidget::sendColors()
{
m_ui->samplesWidget->sendColors();
}
void DrumPadWidget::messageReceived(const midi::MidiMessage &message)
{
m_ui->samplesWidget->messageReceived(message);
}
void DrumPadWidget::currentRowChanged(const QModelIndex &current)
{
if (!current.isValid())
return;
const auto &preset = m_presetsModel.getPreset(m_presetsProxyModel.mapToSource(current));
m_ui->presetDetailWidget->setPreset(preset);
m_filesModel.setPreset(preset);
m_ui->sequencerWidget->setPreset(preset);
m_ui->samplesWidget->setPreset(preset);
}
void DrumPadWidget::loadPresets()
{
if (!m_networkAccessManager)
{
qWarning() << "no network access manager available";
return;
}
m_ui->pushButtonRefresh->setEnabled(false);
QNetworkRequest request{QUrl{"https://brunner.ninja/komposthaufen/dpm/presets_config_v12.json"}};
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, true);
m_reply = std::unique_ptr<QNetworkReply>(m_networkAccessManager->get(request));
connect(m_reply.get(), &QNetworkReply::finished, this, &DrumPadWidget::requestFinished);
}
void DrumPadWidget::requestFinished()
{
qDebug() << "called";
if (!m_reply)
{
qWarning() << "no valid reply";
return;
}
auto reply = std::move(m_reply);
if (reply->error() != QNetworkReply::NoError)
{
QMessageBox::warning(this, tr("Could not load presets!"), tr("Could not load presets!") + "\n\n" + reply->errorString());
return;
}
try
{
auto result = json_converters::parsePresetsConfig(json_converters::loadJson(reply->readAll()));
if (!result.presets)
throw std::runtime_error("presets missing in response");
m_presetsModel.setPresets(std::move(*std::move(result).presets));
selectFirstPreset();
}
catch (const std::exception &e)
{
QMessageBox::warning(this, tr("error"), tr("error") + "\n\n" + QString::fromStdString(e.what()));
}
}

60
drumpadwidget.h Normal file
View File

@ -0,0 +1,60 @@
#pragma once
#include <QSplitter>
#include <QSortFilterProxyModel>
#include <memory>
#include "audioformat.h"
#include "midicontainers.h"
#include "presetsmodel.h"
#include "filesmodel.h"
namespace Ui { class DrumPadWidget; }
class SamplesWidget;
class SequencerWidget;
class QModelIndex;
class QNetworkAccessManager;
class QThread;
class DrumMachineSettings;
class QNetworkReply;
class DrumPadWidget : public QSplitter
{
Q_OBJECT
public:
explicit DrumPadWidget(QWidget *parent = nullptr);
~DrumPadWidget() override;
void selectFirstPreset();
void writeSamples(frame_t *begin, frame_t *end);
void injectNetworkAccessManager(QNetworkAccessManager &networkAccessManager);
void injectDecodingThread(QThread &thread);
void loadSettings(DrumMachineSettings &settings);
void unsendColors();
void sendColors();
signals:
void sendMidi(const midi::MidiMessage &midiMsg);
public slots:
void messageReceived(const midi::MidiMessage &message);
private slots:
void currentRowChanged(const QModelIndex &current);
void loadPresets();
void requestFinished();
private:
const std::unique_ptr<Ui::DrumPadWidget> m_ui;
PresetsModel m_presetsModel;
QSortFilterProxyModel m_presetsProxyModel;
FilesModel m_filesModel;
QNetworkAccessManager *m_networkAccessManager{};
std::unique_ptr<QNetworkReply> m_reply;
};

108
drumpadwidget.ui Normal file
View File

@ -0,0 +1,108 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DrumPadWidget</class>
<widget class="QSplitter">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>805</width>
<height>525</height>
</rect>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout">
<item>
<layout class="QVBoxLayout">
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLineEdit" name="lineEdit">
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonRefresh">
<property name="maximumSize">
<size>
<width>32</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>↻</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTreeView" name="presetsView">
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>2</number>
</property>
<widget class="PresetDetailWidget" name="presetDetailWidget">
<attribute name="title">
<string>Properties</string>
</attribute>
</widget>
<widget class="QTreeView" name="filesView">
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<attribute name="title">
<string>Samples</string>
</attribute>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QSplitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="SequencerWidget" name="sequencerWidget" />
<widget class="SamplesWidget" name="samplesWidget" />
</widget>
</widget>
<customwidgets>
<customwidget>
<class>SamplesWidget</class>
<extends>QWidget</extends>
<header>sampleswidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>PresetDetailWidget</class>
<extends>QScrollArea</extends>
<header>presetdetailwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>SequencerWidget</class>
<extends>QWidget</extends>
<header>sequencerwidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -1,17 +1,10 @@
#include <QApplication>
#include <QSslSocket>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QEventLoop>
#include <QSplashScreen>
#include <QMessageBox>
#include <QNetworkDiskCache>
#include <QDebug>
#include <QMessageBox>
#include "portaudio.h"
#include "jsonconverters.h"
#include "mainwindow.h"
namespace {
@ -58,7 +51,6 @@ int main(int argc, char *argv[])
}
auto helper0 = makeCleanupHelper([](){
qDebug() << "helper0";
if (PaError err = Pa_Terminate(); err != paNoError)
fprintf(stderr, "Could not terminate PortAudio!\n");
});
@ -74,39 +66,6 @@ int main(int argc, char *argv[])
"%{function}(): "
"%{message}");
presets::PresetsConfig presetsConfig;
{
QSplashScreen splashScreen{QPixmap{":/drummachine/splashscreen.png"}};
splashScreen.showMessage(QCoreApplication::translate("main", "Loading list of presets..."));
splashScreen.show();
QEventLoop eventLoop;
QNetworkAccessManager manager;
QNetworkDiskCache cache;
cache.setCacheDirectory("cache");
manager.setCache(&cache);
QNetworkRequest request{QUrl{"https://brunner.ninja/komposthaufen/dpm/presets_config_v12.json"}};
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, true);
const auto reply = std::unique_ptr<QNetworkReply>(manager.get(request));
QObject::connect(reply.get(), &QNetworkReply::finished, &eventLoop, &QEventLoop::quit);
eventLoop.exec();
if (reply->error() != QNetworkReply::NoError)
{
QMessageBox::warning(nullptr, QCoreApplication::translate("main", "Could not load presets!"), QCoreApplication::translate("main", "Could not load presets!") + "\n\n" + reply->errorString());
return 1;
}
presetsConfig = json_converters::parsePresetsConfig(json_converters::loadJson(reply->readAll()));
}
#if !defined(Q_OS_WIN)
QPalette darkPalette;
darkPalette.setColor(QPalette::Window, QColor(53,53,53));
@ -127,9 +86,8 @@ int main(int argc, char *argv[])
app.setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
#endif
MainWindow mainWindow{presetsConfig};
mainWindow.showMaximized();
mainWindow.selectFirstPreset();
MainWindow mainWindow;
mainWindow.show();
return app.exec();
}

View File

@ -12,56 +12,34 @@
#include "presets.h"
#include "midiinwrapper.h"
#include "midicontainers.h"
#include "sampleswidget.h"
#include "sequencerwidget.h"
namespace {
void DummyDeleter(PaStream *stream)
{
Q_UNUSED(stream);
}
void PaStreamCloser(PaStream *stream)
{
if (PaError err = Pa_CloseStream(stream); err != paNoError)
fprintf(stderr, "Could not close stream!\n");
}
void PaStreamStopperAndCloser(PaStream *stream)
{
if (PaError err = Pa_StopStream(stream); err != paNoError)
fprintf(stderr, "Could not stop stream!\n");
PaStreamCloser(stream);
}
void paStreamFinished(void* userData)
{
printf("Stream Completed\n");
}
void DummyDeleter(PaStream *stream);
void PaStreamCloser(PaStream *stream);
void PaStreamStopperAndCloser(PaStream *stream);
void paStreamFinished(void* userData);
int paCallback(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData)
{
Q_UNUSED(inputBuffer)
Q_ASSERT(outputBuffer);
Q_ASSERT(framesPerBuffer);
Q_UNUSED(timeInfo)
Q_UNUSED(statusFlags)
Q_ASSERT(userData);
void *userData);
} // namespace
auto begin = static_cast<frame_t*>(outputBuffer);
static_cast<MainWindow*>(userData)->writeSamples(begin, begin+framesPerBuffer);
}
}
MainWindow::MainWindow(const presets::PresetsConfig &presetsConfig, QWidget *parent) :
MainWindow::MainWindow(QWidget *parent) :
QMainWindow{parent},
m_ui{std::make_unique<Ui::MainWindow>()},
m_paStream{nullptr, DummyDeleter},
m_presetsModel{*presetsConfig.presets}
m_midiIn{RtMidi::UNSPECIFIED, "DrumMachine"},
m_midiOut{RtMidi::UNSPECIFIED, "DrumMachine"}
{
m_ui->setupUi(this);
m_ui->splitter->setSizes({99999, 1});
m_cache.setCacheDirectory("cache");
m_networkAccessManager.setCache(&m_cache);
m_ui->drumPadWidget->injectNetworkAccessManager(m_networkAccessManager);
connect(&m_midiIn, &MidiInWrapper::messageReceived, this, &MainWindow::messageReceived);
@ -72,11 +50,9 @@ MainWindow::MainWindow(const presets::PresetsConfig &presetsConfig, QWidget *par
eventLoop.exec();
}
m_ui->samplesWidget->injectDecodingThread(m_decoderThread);
m_ui->drumPadWidget->injectDecodingThread(m_decoderThread);
m_ui->djWidget->injectDecodingThread(m_decoderThread);
connect(m_ui->sequencerWidget, &SequencerWidget::triggerSample, m_ui->samplesWidget, &SamplesWidget::sequencerTriggerSample);
updateMidiInDevices();
updateMidiOutDevices();
@ -92,7 +68,7 @@ MainWindow::MainWindow(const presets::PresetsConfig &presetsConfig, QWidget *par
{
const auto index = m_ui->comboBoxMidiIn->currentIndex();
if (index != -1)
m_midiIn.openPort(index, "DrumMachine");
m_midiIn.openPort(index, "Input");
}
m_ui->comboBoxMidiIn->setDisabled(m_midiIn.isPortOpen());
@ -103,6 +79,7 @@ MainWindow::MainWindow(const presets::PresetsConfig &presetsConfig, QWidget *par
if (m_midiOut.isPortOpen())
{
qDebug() << "closing port";
unsendColors(m_ui->tabWidget->currentIndex());
m_midiOut.closePort();
}
else
@ -110,8 +87,8 @@ MainWindow::MainWindow(const presets::PresetsConfig &presetsConfig, QWidget *par
const auto index = m_ui->comboBoxMidiOut->currentIndex();
if (index != -1)
{
m_midiOut.openPort(index, "DrumMachine");
sendColors();
m_midiOut.openPort(index, "Output");
sendColors(m_ui->tabWidget->currentIndex());
}
}
@ -127,22 +104,14 @@ MainWindow::MainWindow(const presets::PresetsConfig &presetsConfig, QWidget *par
connect(m_ui->pushButtonAudioDevice, &QAbstractButton::pressed, this, &MainWindow::openAudioDevice);
m_presetsProxyModel.setFilterCaseSensitivity(Qt::CaseInsensitive);
m_presetsProxyModel.setSortRole(Qt::EditRole);
m_presetsProxyModel.setSourceModel(&m_presetsModel);
m_ui->presetsView->setModel(&m_presetsProxyModel);
m_presetsProxyModel.setFilterKeyColumn(1);
connect(m_ui->lineEdit, &QLineEdit::textChanged, &m_presetsProxyModel, &QSortFilterProxyModel::setFilterFixedString);
m_ui->filesView->setModel(&m_filesModel);
connect(m_ui->presetsView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &MainWindow::currentRowChanged);
loadSettings();
connect(m_ui->samplesWidget, &SamplesWidget::sendMidi, this, &MainWindow::sendMidi);
connect(m_ui->drumPadWidget, &DrumPadWidget::sendMidi, this, &MainWindow::sendMidi);
connect(m_ui->djWidget, &DjWidget::sendMidi, this, &MainWindow::sendMidi);
connect(m_ui->synthisizerWidget, &SynthisizerWidget::sendMidi, this, &MainWindow::sendMidi);
m_lastIndex = m_ui->tabWidget->currentIndex();
connect(m_ui->tabWidget, &QTabWidget::currentChanged, this, &MainWindow::currentChanged);
}
MainWindow::~MainWindow()
@ -151,26 +120,13 @@ MainWindow::~MainWindow()
m_decoderThread.wait();
}
void MainWindow::selectFirstPreset()
{
if (m_presetsProxyModel.rowCount())
{
const auto index = m_presetsProxyModel.index(0, 0);
if (index.isValid())
{
m_ui->presetsView->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
currentRowChanged(index);
}
}
}
void MainWindow::writeSamples(frame_t *begin, frame_t *end)
int MainWindow::writeSamples(frame_t *begin, frame_t *end)
{
std::fill(begin, end, frame_t{0.,0.});
m_ui->samplesWidget->writeSamples(begin, end);
m_ui->drumPadWidget->writeSamples(begin, end);
m_ui->djWidget->writeSamples(begin, end);
m_synthisizer.writeSamples(begin, end);
m_ui->synthisizerWidget->writeSamples(begin, end);
std::transform(begin, end, begin, [factor=float(m_ui->horizontalSliderMaster->value())/100.f](frame_t frame){
std::transform(std::cbegin(frame), std::cend(frame), std::begin(frame), [&factor](const sample_t &sample){
@ -178,6 +134,8 @@ void MainWindow::writeSamples(frame_t *begin, frame_t *end)
});
return frame;
});
return paContinue;
}
void MainWindow::openAudioDevice()
@ -242,23 +200,12 @@ void MainWindow::messageReceived(const midi::MidiMessage &message)
.arg(message.flag?"true":"false", QMetaEnum::fromType<midi::Command>().valueToKey(int(message.cmd)))
.arg(message.channel).arg(message.note).arg(message.velocity), 1000);
if (m_ui->comboBoxMidiType->currentIndex() == 0)
m_ui->samplesWidget->messageReceived(message);
else if (m_ui->comboBoxMidiType->currentIndex() == 1)
m_synthisizer.messageReceived(message);
}
void MainWindow::currentRowChanged(const QModelIndex &current)
{
if (!current.isValid())
return;
const auto &preset = m_presetsModel.getPreset(m_presetsProxyModel.mapToSource(current));
m_ui->presetDetailWidget->setPreset(preset);
m_filesModel.setPreset(preset);
m_ui->sequencerWidget->setPreset(preset);
m_ui->samplesWidget->setPreset(preset);
if (m_ui->tabWidget->currentIndex() == 0)
m_ui->drumPadWidget->messageReceived(message);
else if (m_ui->tabWidget->currentIndex() == 1)
m_ui->djWidget->messageReceived(message);
else if (m_ui->tabWidget->currentIndex() == 2)
m_ui->synthisizerWidget->messageReceived(message);
}
void MainWindow::sendMidi(const midi::MidiMessage &midiMsg)
@ -267,6 +214,13 @@ void MainWindow::sendMidi(const midi::MidiMessage &midiMsg)
m_midiOut.sendMessage(midiMsg);
}
void MainWindow::currentChanged(int index)
{
unsendColors(m_lastIndex);
m_lastIndex = index;
sendColors(index);
}
void MainWindow::updateMidiInDevices()
{
m_ui->comboBoxMidiIn->clear();
@ -302,15 +256,34 @@ void MainWindow::updateAudioDevices()
void MainWindow::loadSettings()
{
m_synthisizer.loadSettings(m_settings);
m_ui->samplesWidget->loadSettings(m_settings);
m_ui->drumPadWidget->loadSettings(m_settings);
m_ui->djWidget->loadSettings(m_settings);
m_ui->synthisizerWidget->loadSettings(m_settings);
}
void MainWindow::sendColors()
void MainWindow::unsendColors(int index)
{
m_ui->samplesWidget->sendColors();
if (index == 0)
m_ui->drumPadWidget->unsendColors();
else if (index == 1)
m_ui->djWidget->unsendColors();
else if (index == 2)
m_ui->synthisizerWidget->unsendColors();
}
void MainWindow::sendColors(int index)
{
if (index == 0)
m_ui->drumPadWidget->sendColors();
else if (index == 1)
m_ui->djWidget->sendColors();
else if (index == 2)
m_ui->synthisizerWidget->sendColors();
return;
// this was just for debugging all the available colors on novation launchpad mk1
int k{0};
for (int j = 0; j < 128; j+= 16)
{
@ -327,3 +300,44 @@ void MainWindow::sendColors()
}
}
}
namespace {
void DummyDeleter(PaStream *stream)
{
Q_UNUSED(stream);
}
void PaStreamCloser(PaStream *stream)
{
if (PaError err = Pa_CloseStream(stream); err != paNoError)
fprintf(stderr, "Could not close stream!\n");
}
void PaStreamStopperAndCloser(PaStream *stream)
{
if (PaError err = Pa_StopStream(stream); err != paNoError)
fprintf(stderr, "Could not stop stream!\n");
PaStreamCloser(stream);
}
void paStreamFinished(void* userData)
{
printf("Stream Completed\n");
}
int paCallback(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData)
{
Q_UNUSED(inputBuffer)
Q_ASSERT(outputBuffer);
Q_ASSERT(framesPerBuffer);
Q_UNUSED(timeInfo)
Q_UNUSED(statusFlags)
Q_ASSERT(userData);
auto begin = static_cast<frame_t*>(outputBuffer);
return static_cast<MainWindow*>(userData)->writeSamples(begin, begin+framesPerBuffer);
}
} // namespace

View File

@ -3,17 +3,15 @@
#include <memory>
#include <QMainWindow>
#include <QSortFilterProxyModel>
#include <QThread>
#include <QNetworkAccessManager>
#include <QNetworkDiskCache>
#include "portaudio.h"
#include "audioformat.h"
#include "presetsmodel.h"
#include "filesmodel.h"
#include "midiinwrapper.h"
#include "midioutwrapper.h"
#include "synthisizer.h"
#include "drummachinesettings.h"
namespace Ui { class MainWindow; }
@ -25,25 +23,24 @@ class MainWindow : public QMainWindow
Q_OBJECT
public:
explicit MainWindow(const presets::PresetsConfig &presetsConfig, QWidget *parent = nullptr);
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow() override;
void selectFirstPreset();
void writeSamples(frame_t *begin, frame_t *end);
int writeSamples(frame_t *begin, frame_t *end);
private slots:
void openAudioDevice();
void messageReceived(const midi::MidiMessage &message);
void currentRowChanged(const QModelIndex &current);
void sendMidi(const midi::MidiMessage &midiMsg);
void currentChanged(int index);
private:
void updateMidiInDevices();
void updateMidiOutDevices();
void updateAudioDevices();
void loadSettings();
void sendColors();
void unsendColors(int index);
void sendColors(int index);
const std::unique_ptr<Ui::MainWindow> m_ui;
@ -54,12 +51,10 @@ private:
MidiInWrapper m_midiIn;
MidiOutWrapper m_midiOut;
QNetworkAccessManager m_networkAccessManager;
QNetworkDiskCache m_cache;
QThread m_decoderThread;
Synthisizer m_synthisizer;
PresetsModel m_presetsModel;
QSortFilterProxyModel m_presetsProxyModel;
FilesModel m_filesModel;
int m_lastIndex;
};

View File

@ -14,7 +14,7 @@
<string>MainWindow</string>
</property>
<widget class="QWidget" name="widget">
<layout class="QVBoxLayout" stretch="0,1">
<layout class="QVBoxLayout" stretch="0,0,0">
<property name="leftMargin">
<number>0</number>
</property>
@ -91,6 +91,43 @@
</property>
</spacer>
</item>
<item>
<widget class="QComboBox" name="comboBoxAudioDevice"/>
</item>
<item>
<widget class="QPushButton" name="pushButtonRefreshAudioDevices">
<property name="maximumSize">
<size>
<width>32</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>↻</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinBoxBufferSize">
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>32</number>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonAudioDevice">
<property name="text">
<string>Open</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="labelMidiIn">
<property name="text">
@ -121,20 +158,6 @@
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBoxMidiType">
<item>
<property name="text">
<string>Samples</string>
</property>
</item>
<item>
<property name="text">
<string>Synthisizer</string>
</property>
</item>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
@ -178,139 +201,24 @@
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QComboBox" name="comboBoxAudioDevice"/>
</item>
<item>
<widget class="QPushButton" name="pushButtonRefreshAudioDevices">
<property name="maximumSize">
<size>
<width>32</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>↻</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinBoxBufferSize">
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>32</number>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonAudioDevice">
<property name="text">
<string>Open</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QSplitter" name="splitter_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout">
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLineEdit" name="lineEdit">
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="selectRandomPushButton">
<property name="text">
<string>Select random</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QTreeView" name="presetsView">
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
</widget>
<widget class="QTabWidget" name="tabWidget">
<widget class="PresetDetailWidget" name="presetDetailWidget">
<attribute name="title">
<string>Properties</string>
</attribute>
</widget>
<widget class="QTreeView" name="filesView">
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<attribute name="title">
<string>Samples</string>
</attribute>
</widget>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="DjWidget" name="djWidget" native="true"/>
<widget class="QTabWidget" name="tabWidget">
<widget class="DrumPadWidget" name="drumPadWidget">
<attribute name="title">
<string>DrumPad</string>
</attribute>
</widget>
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout" stretch="1,0">
<item>
<widget class="SequencerWidget" name="sequencerWidget" native="true"/>
</item>
<item>
<widget class="SamplesWidget" name="samplesWidget" native="true"/>
</item>
</layout>
<widget class="DjWidget" name="djWidget">
<attribute name="title">
<string>DJ</string>
</attribute>
</widget>
<widget class="SynthisizerWidget" name="synthisizerWidget">
<attribute name="title">
<string>Synthisizer</string>
</attribute>
</widget>
</widget>
</item>
@ -329,30 +237,24 @@
<widget class="QStatusBar" name="statusbar"/>
</widget>
<customwidgets>
<customwidget>
<class>SamplesWidget</class>
<extends>QWidget</extends>
<header>sampleswidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>PresetDetailWidget</class>
<extends>QScrollArea</extends>
<header>presetdetailwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>SequencerWidget</class>
<extends>QWidget</extends>
<header>sequencerwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>DjWidget</class>
<extends>QWidget</extends>
<header>djwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>DrumPadWidget</class>
<extends>QWidget</extends>
<header>drumpadwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>SynthisizerWidget</class>
<extends>QWidget</extends>
<header>synthisizerwidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>

View File

@ -13,9 +13,9 @@ class MidiInWrapper : public QObject
public:
MidiInWrapper(RtMidi::Api api = RtMidi::UNSPECIFIED,
const QString &clientName = "RtMidi Input Client",
unsigned int queueSizeLimit = 100,
QObject *parent = nullptr);
const QString &clientName = "RtMidi Input Client",
unsigned int queueSizeLimit = 100,
QObject *parent = nullptr);
void openPort(unsigned int portNumber, const QString &portName);
void openVirtualPort(const QString &portName);

View File

@ -32,6 +32,11 @@ enum {
NumberOfColumns
};
PresetsModel::PresetsModel(QObject *parent) :
QAbstractTableModel{parent}
{
}
PresetsModel::PresetsModel(const std::map<QString, presets::Preset> &presets, QObject *parent) :
QAbstractTableModel{parent}
{
@ -40,8 +45,44 @@ PresetsModel::PresetsModel(const std::map<QString, presets::Preset> &presets, QO
m_presets.emplace_back(pair.second);
}
PresetsModel::PresetsModel(std::vector<presets::Preset> &&presets, QObject *parent) :
QAbstractTableModel{parent}
{
m_presets = std::move(presets);
}
PresetsModel::PresetsModel(const std::vector<presets::Preset> &presets, QObject *parent) :
QAbstractTableModel{parent}
{
m_presets = presets;
}
PresetsModel::~PresetsModel() = default;
void PresetsModel::setPresets(const std::map<QString, presets::Preset> &presets)
{
beginResetModel();
m_presets.clear();
m_presets.reserve(std::size(presets));
for (const auto &pair : presets)
m_presets.emplace_back(pair.second);
endResetModel();
}
void PresetsModel::setPresets(std::vector<presets::Preset> &&presets)
{
beginResetModel();
m_presets = std::move(presets);
endResetModel();
}
void PresetsModel::setPresets(const std::vector<presets::Preset> &presets)
{
beginResetModel();
m_presets = presets;
endResetModel();
}
const presets::Preset &PresetsModel::getPreset(const QModelIndex &index) const
{
return getPreset(index.row());

View File

@ -11,9 +11,16 @@ class PresetsModel : public QAbstractTableModel
Q_OBJECT
public:
PresetsModel(QObject *parent = nullptr);
PresetsModel(const std::map<QString, presets::Preset> &presets, QObject *parent = nullptr);
PresetsModel(std::vector<presets::Preset> &&presets, QObject *parent = nullptr);
PresetsModel(const std::vector<presets::Preset> &presets, QObject *parent = nullptr);
~PresetsModel() override;
void setPresets(const std::map<QString, presets::Preset> &presets);
void setPresets(std::vector<presets::Preset> &&presets);
void setPresets(const std::vector<presets::Preset> &presets);
const presets::Preset &getPreset(const QModelIndex &index) const;
const presets::Preset &getPreset(int row) const;

View File

@ -13,9 +13,6 @@ SamplesWidget::SamplesWidget(QWidget *parent) :
{
m_ui->setupUi(this);
m_cache.setCacheDirectory("cache");
m_networkAccessManager.setCache(&m_cache);
connect(m_ui->checkBox, &QCheckBox::toggled, this, &SamplesWidget::updateWidgets);
connect(m_ui->pushButtonStopAll, &QAbstractButton::pressed, this, &SamplesWidget::stopAll);
@ -24,7 +21,6 @@ SamplesWidget::SamplesWidget(QWidget *parent) :
for (SampleWidget &widget : getWidgets())
{
widget.setPadNr(padNr++);
widget.injectNetworkAccessManager(m_networkAccessManager);
connect(&widget, &SampleWidget::chokeTriggered, this, &SamplesWidget::chokeTriggered);
connect(&widget, &SampleWidget::sendMidi, this, &SamplesWidget::sendMidi);
}
@ -78,12 +74,24 @@ void SamplesWidget::writeSamples(frame_t *begin, frame_t *end)
widget.writeSamples(begin, end);
}
void SamplesWidget::injectNetworkAccessManager(QNetworkAccessManager &networkAccessManager)
{
for (SampleWidget &widget : getWidgets())
widget.injectNetworkAccessManager(networkAccessManager);
}
void SamplesWidget::injectDecodingThread(QThread &thread)
{
for (SampleWidget &widget : getWidgets())
widget.injectDecodingThread(thread);
}
void SamplesWidget::unsendColors()
{
for (SampleWidget &widget : getWidgets())
widget.unsendColor();
}
void SamplesWidget::sendColors()
{
for (SampleWidget &widget : getWidgets())

View File

@ -5,14 +5,13 @@
#include <functional>
#include <QWidget>
#include <QNetworkAccessManager>
#include <QNetworkDiskCache>
#include "audioformat.h"
#include "presets.h"
namespace Ui { class SamplesWidget; }
namespace midi { class MidiMessage; }
class QNetworkAccessManager;
class SampleWidget;
class DrumMachineSettings;
@ -32,8 +31,10 @@ public:
void writeSamples(frame_t *begin, frame_t *end);
void injectNetworkAccessManager(QNetworkAccessManager &networkAccessManager);
void injectDecodingThread(QThread &thread);
void unsendColors();
void sendColors();
signals:
@ -52,8 +53,5 @@ private:
const std::unique_ptr<Ui::SamplesWidget> m_ui;
QNetworkDiskCache m_cache;
QNetworkAccessManager m_networkAccessManager;
presets::Preset m_preset;
};

View File

@ -183,6 +183,19 @@ void SampleWidget::learn(quint8 channel, quint8 note)
learnPressed();
}
void SampleWidget::unsendColor()
{
midi::MidiMessage midiMsg;
midiMsg.channel = m_ui->channelSpinBox->value();
midiMsg.cmd = midi::Command::NoteOn;
midiMsg.flag = true;
midiMsg.note = m_ui->noteSpinBox->value();
midiMsg.velocity = 0;
emit sendMidi(midiMsg);
}
void SampleWidget::sendColor()
{
midi::MidiMessage midiMsg;

View File

@ -58,14 +58,14 @@ public:
bool isLearning() const { return m_learning; }
void learn(quint8 channel, quint8 note);
void unsendColor();
void sendColor();
signals:
void chokeTriggered(int choke);
void startDecoding(const std::shared_ptr<QIODevice> &device);
void sendMidi(const midi::MidiMessage &midiMsg);
public slots:
void sendColor();
private slots:
void updateStatus();
void requestFinished();

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
<width>140</width>
<height>157</height>
</rect>
</property>
<property name="windowTitle">
@ -63,8 +63,14 @@
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QPushButton" name="pushButton">
<property name="maximumSize">
<size>
<width>32</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
<string></string>
</property>
</widget>
</item>
@ -119,9 +125,6 @@
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
@ -132,9 +135,6 @@
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
@ -145,9 +145,6 @@
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
</layout>

View File

@ -42,6 +42,6 @@ private:
QTimer m_timer;
int m_pos;
int m_pos{};
std::array<QLabel*, 24> m_sampleLabels;
};

View File

@ -6,10 +6,6 @@
constexpr double pi = std::acos(-1);
void Synthisizer::loadSettings(const DrumMachineSettings &settings)
{
}
void Synthisizer::writeSamples(frame_t *begin, frame_t *end)
{
const auto frequency = m_frequency;

View File

@ -9,14 +9,15 @@ class DrumMachineSettings;
class Synthisizer
{
public:
void loadSettings(const DrumMachineSettings &settings);
void setFrequency(int16_t frequency) { m_frequency = frequency; }
void writeSamples(frame_t *begin, frame_t *end);
void messageReceived(const midi::MidiMessage &message);
signals:
void sendMidi(const midi::MidiMessage &midiMsg);
private:
int16_t m_frequency{};
int16_t m_actualFrequency{};

33
synthisizerwidget.cpp Normal file
View File

@ -0,0 +1,33 @@
#include "synthisizerwidget.h"
#include "ui_synthisizerwidget.h"
SynthisizerWidget::SynthisizerWidget(QWidget *parent) :
QWidget{parent},
m_ui{std::make_unique<Ui::SynthisizerWidget>()}
{
m_ui->setupUi(this);
}
SynthisizerWidget::~SynthisizerWidget() = default;
void SynthisizerWidget::writeSamples(frame_t *begin, frame_t *end)
{
m_synthisizer.writeSamples(begin, end);
}
void SynthisizerWidget::loadSettings(DrumMachineSettings &settings)
{
}
void SynthisizerWidget::unsendColors()
{
}
void SynthisizerWidget::sendColors()
{
}
void SynthisizerWidget::messageReceived(const midi::MidiMessage &message)
{
m_synthisizer.messageReceived(message);
}

35
synthisizerwidget.h Normal file
View File

@ -0,0 +1,35 @@
#pragma once
#include <QWidget>
#include <memory>
#include "synthisizer.h"
#include "audioformat.h"
namespace Ui { class SynthisizerWidget; }
class DrumMachineSettings;
class SynthisizerWidget : public QWidget
{
Q_OBJECT
public:
explicit SynthisizerWidget(QWidget *parent = nullptr);
~SynthisizerWidget() override;
void writeSamples(frame_t *begin, frame_t *end);
void loadSettings(DrumMachineSettings &settings);
void unsendColors();
void sendColors();
signals:
void sendMidi(const midi::MidiMessage &midiMsg);
public slots:
void messageReceived(const midi::MidiMessage &message);
private:
const std::unique_ptr<Ui::SynthisizerWidget> m_ui;
Synthisizer m_synthisizer;
};

32
synthisizerwidget.ui Normal file
View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SynthisizerWidget</class>
<widget class="QWidget" name="SynthisizerWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>30</x>
<y>60</y>
<width>221</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>Hier könnte ihr Synthisizer stehen.</string>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>