Moved widgets in subfolder
This commit is contained in:
102
widgets/djwidget.cpp
Normal file
102
widgets/djwidget.cpp
Normal file
@@ -0,0 +1,102 @@
|
||||
#include "djwidget.h"
|
||||
#include "ui_djwidget.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QStandardPaths>
|
||||
|
||||
DjWidget::DjWidget(QWidget *parent) :
|
||||
QWidget{parent},
|
||||
m_ui{std::make_unique<Ui::DjWidget>()}
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
const auto createSlot = [this](TrackDeck &trackDeck){
|
||||
return [this,&trackDeck](){
|
||||
const auto index = m_ui->treeViewFiles->currentIndex();
|
||||
if (!index.isValid())
|
||||
return;
|
||||
trackDeck.loadTrack(m_filesModel.filePath(m_filesTableModel.mapToSource(m_sortFilterProxyModel.mapToSource(index))));
|
||||
};
|
||||
};
|
||||
|
||||
connect(m_ui->pushButtonLoadDeckA, &QAbstractButton::pressed, m_ui->trackDeckA, createSlot(*m_ui->trackDeckA));
|
||||
connect(m_ui->pushButtonLoadDeckB, &QAbstractButton::pressed, m_ui->trackDeckB, createSlot(*m_ui->trackDeckB));
|
||||
|
||||
m_directoryModel.setFilter(QDir::Dirs|QDir::Drives|QDir::NoDotAndDotDot);
|
||||
const auto rootIndex = m_directoryModel.setRootPath(QDir::homePath());
|
||||
m_ui->treeViewDirectories->setModel(&m_directoryModel);
|
||||
m_ui->treeViewDirectories->setRootIndex(rootIndex);
|
||||
for (auto i = 0, count = m_directoryModel.columnCount(); i < count; i++)
|
||||
m_ui->treeViewDirectories->setColumnHidden(i, i!=0);
|
||||
|
||||
connect(m_ui->treeViewDirectories->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &DjWidget::directorySelected);
|
||||
|
||||
m_filesModel.setFilter(QDir::Files);
|
||||
m_filesTableModel.setSourceModel(&m_filesModel);
|
||||
m_sortFilterProxyModel.setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
m_sortFilterProxyModel.setSourceModel(&m_filesTableModel);
|
||||
m_ui->treeViewFiles->setModel(&m_sortFilterProxyModel);
|
||||
|
||||
if (const auto locations = QStandardPaths::standardLocations(QStandardPaths::MusicLocation); !locations.isEmpty())
|
||||
{
|
||||
const auto index = m_directoryModel.index(locations.first());
|
||||
m_ui->treeViewDirectories->selectionModel()->select(index, QItemSelectionModel::Clear|QItemSelectionModel::Select|QItemSelectionModel::Current|QItemSelectionModel::Rows);
|
||||
|
||||
const auto rootIndex = m_filesModel.setRootPath(m_directoryModel.filePath(index));
|
||||
m_filesTableModel.setRootIndex(rootIndex);
|
||||
}
|
||||
|
||||
connect(m_ui->lineEditSearch, &QLineEdit::textChanged, &m_sortFilterProxyModel, &QSortFilterProxyModel::setFilterFixedString);
|
||||
}
|
||||
|
||||
DjWidget::~DjWidget() = default;
|
||||
|
||||
void DjWidget::injectDecodingThread(QThread &thread)
|
||||
{
|
||||
m_ui->trackDeckA->injectDecodingThread(thread);
|
||||
m_ui->trackDeckB->injectDecodingThread(thread);
|
||||
}
|
||||
|
||||
void DjWidget::writeSamples(frame_t *begin, frame_t *end)
|
||||
{
|
||||
const auto count = std::distance(begin, end);
|
||||
|
||||
for (TrackDeck *trackDeck : {m_ui->trackDeckA, m_ui->trackDeckB})
|
||||
{
|
||||
frame_t buffer[count];
|
||||
std::fill(buffer, buffer+count, frame_t{0.f,0.f});
|
||||
trackDeck->writeSamples(buffer, buffer+count);
|
||||
std::transform(static_cast<const frame_t *>(begin), static_cast<const frame_t *>(end), buffer, begin, [volume=1.f](const frame_t &frame, const frame_t &frame2){
|
||||
frame_t newFrame;
|
||||
std::transform(std::cbegin(frame), std::cend(frame), std::begin(frame2), std::begin(newFrame),
|
||||
[&volume](const sample_t &left, const sample_t &right) { return left + (right*volume); });
|
||||
return newFrame;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
if (selected.isValid())
|
||||
{
|
||||
const auto rootIndex = m_filesModel.setRootPath(m_directoryModel.filePath(selected));
|
||||
m_filesTableModel.setRootIndex(rootIndex);
|
||||
}
|
||||
}
|
46
widgets/djwidget.h
Normal file
46
widgets/djwidget.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QFileSystemModel>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "audioformat.h"
|
||||
#include "midicontainers.h"
|
||||
#include "treetotableproxymodel.h"
|
||||
|
||||
namespace Ui { class DjWidget; }
|
||||
class DrumMachineSettings;
|
||||
|
||||
class DjWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DjWidget(QWidget *parent = nullptr);
|
||||
~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();
|
||||
|
||||
private:
|
||||
const std::unique_ptr<Ui::DjWidget> m_ui;
|
||||
|
||||
QFileSystemModel m_directoryModel;
|
||||
QFileSystemModel m_filesModel;
|
||||
TreeToTableProxyModel m_filesTableModel;
|
||||
QSortFilterProxyModel m_sortFilterProxyModel;
|
||||
};
|
149
widgets/djwidget.ui
Normal file
149
widgets/djwidget.ui
Normal file
@@ -0,0 +1,149 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>DjWidget</class>
|
||||
<widget class="QWidget" name="DjWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>765</width>
|
||||
<height>519</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,1">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0,1">
|
||||
<item>
|
||||
<widget class="TrackDeck" name="trackDeckA" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="frame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::VLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="TrackDeck" name="trackDeckB" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<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="QSlider" name="horizontalSlider">
|
||||
<property name="minimum">
|
||||
<number>-100</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="invertedAppearance">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="tickPosition">
|
||||
<enum>QSlider::TicksBothSides</enum>
|
||||
</property>
|
||||
<property name="tickInterval">
|
||||
<number>50</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<widget class="QTreeView" name="treeViewDirectories">
|
||||
<property name="headerHidden">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QWidget" name="layoutWidget">
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="lineEditSearch">
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonLoadDeckA">
|
||||
<property name="text">
|
||||
<string>Load in Deck A</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonLoadDeckB">
|
||||
<property name="text">
|
||||
<string>Load in Deck B</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTreeView" name="treeViewFiles">
|
||||
<property name="dragEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>TrackDeck</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>widgets/trackdeck.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
150
widgets/drumpadwidget.cpp
Normal file
150
widgets/drumpadwidget.cpp
Normal file
@@ -0,0 +1,150 @@
|
||||
#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 ¤t)
|
||||
{
|
||||
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()
|
||||
{
|
||||
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
widgets/drumpadwidget.h
Normal file
60
widgets/drumpadwidget.h
Normal 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 ¤t);
|
||||
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
widgets/drumpadwidget.ui
Normal file
108
widgets/drumpadwidget.ui
Normal 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>widgets/sampleswidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>PresetDetailWidget</class>
|
||||
<extends>QScrollArea</extends>
|
||||
<header>widgets/presetdetailwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>SequencerWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>widgets/sequencerwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
372
widgets/mainwindow.cpp
Executable file
372
widgets/mainwindow.cpp
Executable file
@@ -0,0 +1,372 @@
|
||||
#include "mainwindow.h"
|
||||
#include "ui_mainwindow.h"
|
||||
|
||||
#include <QEventLoop>
|
||||
#include <QMetaEnum>
|
||||
#include <QMessageBox>
|
||||
#include <QTimer>
|
||||
#include <QAbstractEventDispatcher>
|
||||
#include <QAudioDeviceInfo>
|
||||
#include <QDebug>
|
||||
|
||||
#include "presets.h"
|
||||
#include "midiinwrapper.h"
|
||||
#include "midicontainers.h"
|
||||
#include "sampleswidget.h"
|
||||
#include "sequencerwidget.h"
|
||||
|
||||
namespace {
|
||||
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);
|
||||
} // namespace
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) :
|
||||
QMainWindow{parent},
|
||||
m_ui{std::make_unique<Ui::MainWindow>()},
|
||||
m_paStream{nullptr, DummyDeleter},
|
||||
m_midiIn{RtMidi::UNSPECIFIED, "DrumMachine"},
|
||||
m_midiOut{RtMidi::UNSPECIFIED, "DrumMachine"}
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
m_cache.setCacheDirectory("cache");
|
||||
m_cache.setMaximumCacheSize(2ull * 1024 * 1024 * 1024);
|
||||
m_networkAccessManager.setCache(&m_cache);
|
||||
|
||||
m_ui->drumPadWidget->injectNetworkAccessManager(m_networkAccessManager);
|
||||
|
||||
connect(&m_midiIn, &MidiInWrapper::messageReceived, this, &MainWindow::messageReceived);
|
||||
|
||||
{
|
||||
QEventLoop eventLoop;
|
||||
connect(&m_decoderThread, &QThread::started, &eventLoop, &QEventLoop::quit);
|
||||
m_decoderThread.start(QThread::HighestPriority);
|
||||
eventLoop.exec();
|
||||
}
|
||||
|
||||
m_ui->drumPadWidget->injectDecodingThread(m_decoderThread);
|
||||
m_ui->djWidget->injectDecodingThread(m_decoderThread);
|
||||
|
||||
updateAudioDevices();
|
||||
connect(m_ui->pushButtonRefreshAudioDevices, &QAbstractButton::pressed, this, &MainWindow::updateAudioDevices);
|
||||
if (const auto &lastAudioDevice = m_settings.lastAudioDevice(); !lastAudioDevice.isEmpty())
|
||||
{
|
||||
if (const auto index = m_ui->comboBoxAudioDevice->findText(lastAudioDevice); index >= 0)
|
||||
m_ui->comboBoxAudioDevice->setCurrentIndex(index);
|
||||
else
|
||||
goto paDefault;
|
||||
}
|
||||
else
|
||||
{
|
||||
paDefault:
|
||||
m_ui->comboBoxAudioDevice->setCurrentIndex(Pa_GetDefaultOutputDevice());
|
||||
}
|
||||
m_ui->spinBoxBufferSize->setValue(m_settings.framesPerBuffer());
|
||||
connect(m_ui->pushButtonAudioDevice, &QAbstractButton::pressed, this, &MainWindow::openAudioDevice);
|
||||
|
||||
updateMidiInDevices();
|
||||
connect(m_ui->pushButtonRefreshMidiIn, &QAbstractButton::pressed, this, &MainWindow::updateMidiInDevices);
|
||||
if (const auto &lastMidiInDevice = m_settings.lastMidiInDevice(); !lastMidiInDevice.isEmpty())
|
||||
{
|
||||
if (const auto index = m_ui->comboBoxMidiIn->findText(lastMidiInDevice); index >= 0)
|
||||
m_ui->comboBoxMidiIn->setCurrentIndex(index);
|
||||
}
|
||||
connect(m_ui->pushButtonMidiIn, &QAbstractButton::pressed, this, &MainWindow::openMidiInDevice);
|
||||
|
||||
updateMidiOutDevices();
|
||||
connect(m_ui->pushButtonRefreshMidiOut, &QAbstractButton::pressed, this, &MainWindow::updateMidiOutDevices);
|
||||
if (const auto &lastMidiOutDevice = m_settings.lastMidiOutDevice(); !lastMidiOutDevice.isEmpty())
|
||||
{
|
||||
if (const auto index = m_ui->comboBoxMidiOut->findText(lastMidiOutDevice); index >= 0)
|
||||
m_ui->comboBoxMidiOut->setCurrentIndex(index);
|
||||
}
|
||||
connect(m_ui->pushButtonMidiOut, &QAbstractButton::pressed, this, &MainWindow::openMidiOutDevice);
|
||||
|
||||
loadSettings();
|
||||
|
||||
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()
|
||||
{
|
||||
m_paStream = nullptr;
|
||||
|
||||
m_decoderThread.exit();
|
||||
m_decoderThread.wait();
|
||||
}
|
||||
|
||||
int MainWindow::writeSamples(frame_t *begin, frame_t *end)
|
||||
{
|
||||
std::fill(begin, end, frame_t{0.,0.});
|
||||
|
||||
m_ui->drumPadWidget->writeSamples(begin, end);
|
||||
m_ui->djWidget->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){
|
||||
return sample*factor;
|
||||
});
|
||||
return frame;
|
||||
});
|
||||
|
||||
return paContinue;
|
||||
}
|
||||
|
||||
void MainWindow::openAudioDevice()
|
||||
{
|
||||
if (m_paStream)
|
||||
{
|
||||
m_paStream = nullptr;
|
||||
m_ui->comboBoxAudioDevice->setEnabled(true);
|
||||
m_ui->spinBoxBufferSize->setEnabled(true);
|
||||
m_ui->pushButtonAudioDevice->setText(tr("Open"));
|
||||
}
|
||||
else
|
||||
{
|
||||
PaDeviceIndex index = m_ui->comboBoxAudioDevice->currentIndex();
|
||||
|
||||
const PaDeviceInfo* pInfo = Pa_GetDeviceInfo(index);
|
||||
|
||||
const PaStreamParameters outputParameters {
|
||||
.device = index,
|
||||
.channelCount = channelCount,
|
||||
.sampleFormat = paFloat32, /* 32 bit floating point output */
|
||||
.suggestedLatency = pInfo->defaultLowOutputLatency,
|
||||
.hostApiSpecificStreamInfo = NULL
|
||||
};
|
||||
|
||||
PaStream *stream{};
|
||||
|
||||
if (PaError err = Pa_OpenStream(&stream, NULL, &outputParameters, frameRate, m_ui->spinBoxBufferSize->value(), paNoFlag, &paCallback, this); err != paNoError)
|
||||
{
|
||||
QMessageBox::warning(this, tr("Error opening stream!"), tr("Error opening stream!") + "\n\n" + Pa_GetErrorText(err));
|
||||
return;
|
||||
}
|
||||
|
||||
auto smartPtr = std::unique_ptr<PaStream, void(*)(PaStream*)>(stream, PaStreamCloser);
|
||||
stream = nullptr;
|
||||
|
||||
if (PaError err = Pa_SetStreamFinishedCallback(smartPtr.get(), &paStreamFinished); err != paNoError)
|
||||
{
|
||||
QMessageBox::warning(this, tr("Error setting finished callback!"), tr("Error setting finished callback!") + "\n\n" + Pa_GetErrorText(err));
|
||||
return;
|
||||
}
|
||||
|
||||
if (PaError err = Pa_StartStream(smartPtr.get()); err != paNoError)
|
||||
{
|
||||
QMessageBox::warning(this, tr("Error starting stream!"), tr("Error starting stream!") + "\n\n" + Pa_GetErrorText(err));
|
||||
return;
|
||||
}
|
||||
|
||||
// stream has been started and from now on we not only need to delete it, but also stop it first
|
||||
smartPtr.get_deleter() = PaStreamStopperAndCloser;
|
||||
|
||||
m_paStream = std::move(smartPtr);
|
||||
m_ui->comboBoxAudioDevice->setEnabled(false);
|
||||
m_ui->spinBoxBufferSize->setEnabled(false);
|
||||
m_ui->pushButtonAudioDevice->setText(tr("Close"));
|
||||
|
||||
m_settings.setLastAudioDevice(m_ui->comboBoxAudioDevice->currentText());
|
||||
m_settings.setFramesPerBuffer(m_ui->spinBoxBufferSize->value());
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::openMidiInDevice()
|
||||
{
|
||||
if (m_midiIn.isPortOpen())
|
||||
m_midiIn.closePort();
|
||||
else
|
||||
{
|
||||
const auto index = m_ui->comboBoxMidiIn->currentIndex();
|
||||
if (index != -1)
|
||||
m_midiIn.openPort(index, "Input");
|
||||
m_settings.setLastMidiInDevice(m_ui->comboBoxMidiIn->currentText());
|
||||
}
|
||||
|
||||
m_ui->comboBoxMidiIn->setDisabled(m_midiIn.isPortOpen());
|
||||
m_ui->pushButtonMidiIn->setText(m_midiIn.isPortOpen() ? tr("Close") : tr("Open"));
|
||||
}
|
||||
|
||||
void MainWindow::openMidiOutDevice()
|
||||
{
|
||||
if (m_midiOut.isPortOpen())
|
||||
{
|
||||
qDebug() << "closing port";
|
||||
unsendColors(m_ui->tabWidget->currentIndex());
|
||||
m_midiOut.closePort();
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto index = m_ui->comboBoxMidiOut->currentIndex();
|
||||
if (index != -1)
|
||||
{
|
||||
m_midiOut.openPort(index, "Output");
|
||||
m_settings.setLastMidiOutDevice(m_ui->comboBoxMidiOut->currentText());
|
||||
sendColors(m_ui->tabWidget->currentIndex());
|
||||
}
|
||||
}
|
||||
|
||||
m_ui->comboBoxMidiOut->setDisabled(m_midiOut.isPortOpen());
|
||||
m_ui->pushButtonMidiOut->setText(m_midiOut.isPortOpen() ? tr("Close") : tr("Open"));
|
||||
}
|
||||
|
||||
void MainWindow::messageReceived(const midi::MidiMessage &message)
|
||||
{
|
||||
m_ui->statusbar->showMessage(tr("Received midi message: flag: %0 cmd: %1 channel: %2 note: %3 velocity: %4")
|
||||
.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->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)
|
||||
{
|
||||
if (m_midiOut.isPortOpen())
|
||||
m_midiOut.sendMessage(midiMsg);
|
||||
}
|
||||
|
||||
void MainWindow::currentChanged(int index)
|
||||
{
|
||||
unsendColors(m_lastIndex);
|
||||
m_lastIndex = index;
|
||||
sendColors(index);
|
||||
}
|
||||
|
||||
void MainWindow::updateAudioDevices()
|
||||
{
|
||||
m_ui->comboBoxAudioDevice->clear();
|
||||
|
||||
const auto count = Pa_GetDeviceCount();
|
||||
|
||||
for (PaDeviceIndex i = 0; i < count; i++)
|
||||
{
|
||||
const auto info = Pa_GetDeviceInfo(i);
|
||||
m_ui->comboBoxAudioDevice->addItem(info->name);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::updateMidiInDevices()
|
||||
{
|
||||
m_ui->comboBoxMidiIn->clear();
|
||||
|
||||
for (const auto &name : m_midiIn.portNames())
|
||||
m_ui->comboBoxMidiIn->addItem(name);
|
||||
|
||||
m_ui->pushButtonMidiIn->setEnabled(m_ui->comboBoxMidiIn->count() > 0);
|
||||
}
|
||||
|
||||
void MainWindow::updateMidiOutDevices()
|
||||
{
|
||||
m_ui->comboBoxMidiOut->clear();
|
||||
|
||||
for (const auto &name : m_midiOut.portNames())
|
||||
m_ui->comboBoxMidiOut->addItem(name);
|
||||
|
||||
m_ui->pushButtonMidiOut->setEnabled(m_ui->comboBoxMidiOut->count() > 0);
|
||||
}
|
||||
|
||||
void MainWindow::loadSettings()
|
||||
{
|
||||
m_ui->drumPadWidget->loadSettings(m_settings);
|
||||
m_ui->djWidget->loadSettings(m_settings);
|
||||
m_ui->synthisizerWidget->loadSettings(m_settings);
|
||||
}
|
||||
|
||||
void MainWindow::unsendColors(int index)
|
||||
{
|
||||
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)
|
||||
{
|
||||
qDebug() << k;
|
||||
for (auto i = 0; i < 8; i++)
|
||||
{
|
||||
midi::MidiMessage midiMsg;
|
||||
midiMsg.channel = 0;
|
||||
midiMsg.cmd = midi::Command::NoteOn;
|
||||
midiMsg.flag = true;
|
||||
midiMsg.note = i + j;
|
||||
midiMsg.velocity = k++;
|
||||
sendMidi(midiMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
62
widgets/mainwindow.h
Executable file
62
widgets/mainwindow.h
Executable file
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QThread>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkDiskCache>
|
||||
|
||||
#include "portaudio.h"
|
||||
|
||||
#include "audioformat.h"
|
||||
#include "midiinwrapper.h"
|
||||
#include "midioutwrapper.h"
|
||||
#include "drummachinesettings.h"
|
||||
|
||||
namespace Ui { class MainWindow; }
|
||||
namespace presets { struct PresetsConfig; }
|
||||
namespace midi { struct MidiMessage; }
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = nullptr);
|
||||
~MainWindow() override;
|
||||
|
||||
int writeSamples(frame_t *begin, frame_t *end);
|
||||
|
||||
private slots:
|
||||
void openAudioDevice();
|
||||
void openMidiInDevice();
|
||||
void openMidiOutDevice();
|
||||
void messageReceived(const midi::MidiMessage &message);
|
||||
void sendMidi(const midi::MidiMessage &midiMsg);
|
||||
void currentChanged(int index);
|
||||
|
||||
private:
|
||||
void updateAudioDevices();
|
||||
void updateMidiInDevices();
|
||||
void updateMidiOutDevices();
|
||||
void loadSettings();
|
||||
void unsendColors(int index);
|
||||
void sendColors(int index);
|
||||
|
||||
const std::unique_ptr<Ui::MainWindow> m_ui;
|
||||
|
||||
DrumMachineSettings m_settings;
|
||||
|
||||
std::unique_ptr<PaStream, void(*)(PaStream*)> m_paStream;
|
||||
|
||||
MidiInWrapper m_midiIn;
|
||||
MidiOutWrapper m_midiOut;
|
||||
|
||||
QNetworkAccessManager m_networkAccessManager;
|
||||
QNetworkDiskCache m_cache;
|
||||
|
||||
QThread m_decoderThread;
|
||||
|
||||
int m_lastIndex;
|
||||
};
|
261
widgets/mainwindow.ui
Executable file
261
widgets/mainwindow.ui
Executable file
@@ -0,0 +1,261 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1520</width>
|
||||
<height>890</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>MainWindow</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="widget">
|
||||
<layout class="QVBoxLayout" stretch="0,0,0">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelDrumMachine">
|
||||
<property name="text">
|
||||
<string><b>DrumMachine</b></string>
|
||||
</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="QLabel" name="labelMaster">
|
||||
<property name="text">
|
||||
<string>Master:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>horizontalSliderMaster</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="horizontalSliderMaster">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</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="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">
|
||||
<string>Midi in:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboBoxMidiIn"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonRefreshMidiIn">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>↻</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonMidiIn">
|
||||
<property name="text">
|
||||
<string>Open</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<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="QLabel" name="labelMidiOut">
|
||||
<property name="text">
|
||||
<string>Midi out:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboBoxMidiOut"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonRefreshMidiOut">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>↻</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonMidiOut">
|
||||
<property name="text">
|
||||
<string>Open</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<widget class="DrumPadWidget" name="drumPadWidget">
|
||||
<attribute name="title">
|
||||
<string>DrumPad</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
<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>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menubar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1520</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusbar"/>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>DjWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>widgets/djwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>DrumPadWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>widgets/drumpadwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>SynthisizerWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>widgets/synthisizerwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
16
widgets/presetdetailwidget.cpp
Executable file
16
widgets/presetdetailwidget.cpp
Executable file
@@ -0,0 +1,16 @@
|
||||
#include "presetdetailwidget.h"
|
||||
#include "ui_presetdetailwidget.h"
|
||||
|
||||
PresetDetailWidget::PresetDetailWidget(QWidget *parent) :
|
||||
QScrollArea{parent},
|
||||
m_ui{std::make_unique<Ui::PresetDetailWidget>()}
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
}
|
||||
|
||||
PresetDetailWidget::~PresetDetailWidget() = default;
|
||||
|
||||
void PresetDetailWidget::setPreset(const presets::Preset &preset)
|
||||
{
|
||||
// TODO
|
||||
}
|
22
widgets/presetdetailwidget.h
Executable file
22
widgets/presetdetailwidget.h
Executable file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QScrollArea>
|
||||
|
||||
namespace Ui { class PresetDetailWidget; }
|
||||
namespace presets { class Preset; }
|
||||
|
||||
class PresetDetailWidget : public QScrollArea
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PresetDetailWidget(QWidget *parent = nullptr);
|
||||
~PresetDetailWidget() override;
|
||||
|
||||
void setPreset(const presets::Preset &preset);
|
||||
|
||||
private:
|
||||
const std::unique_ptr<Ui::PresetDetailWidget> m_ui;
|
||||
};
|
306
widgets/presetdetailwidget.ui
Executable file
306
widgets/presetdetailwidget.ui
Executable file
@@ -0,0 +1,306 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>PresetDetailWidget</class>
|
||||
<widget class="QScrollArea" name="PresetDetailWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>320</width>
|
||||
<height>633</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>318</width>
|
||||
<height>631</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="labelId">
|
||||
<property name="text">
|
||||
<string>id:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>lineEditId</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="labelName">
|
||||
<property name="text">
|
||||
<string>name:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>lineEditName</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="labelAuthor">
|
||||
<property name="text">
|
||||
<string>author:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>lineEditAuthor</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="labelOrderBy">
|
||||
<property name="text">
|
||||
<string>orderBy:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>lineEditOrderBy</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="labelVersion">
|
||||
<property name="text">
|
||||
<string>version:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>lineEditVersion</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="labelTempo">
|
||||
<property name="text">
|
||||
<string>tempo:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>spinBoxTempo</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="labelIcon">
|
||||
<property name="text">
|
||||
<string>icon:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>lineEditIcon</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="labelPrice">
|
||||
<property name="text">
|
||||
<string>price:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>spinBoxPrice</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="labelPriceForSession">
|
||||
<property name="text">
|
||||
<string>priceForSession:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>spinBoxPriceForSession</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="labelHasInfo">
|
||||
<property name="text">
|
||||
<string>hasInfo:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>checkBoxHasInfo</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="labelTags">
|
||||
<property name="text">
|
||||
<string>tags:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="0">
|
||||
<widget class="QLabel" name="labelDeleted">
|
||||
<property name="text">
|
||||
<string>DELETED:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>checkBoxDeleted</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="0">
|
||||
<widget class="QLabel" name="labelDifficulty">
|
||||
<property name="text">
|
||||
<string>difficulty:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>spinBoxDifficulty</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="0">
|
||||
<widget class="QLabel" name="labelSample">
|
||||
<property name="text">
|
||||
<string>sample:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>spinBoxSample</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="14" column="0">
|
||||
<widget class="QLabel" name="labelAudioPreview1Name">
|
||||
<property name="text">
|
||||
<string>audioPreview1Name:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>lineEditAudioPreview1Name</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="15" column="0">
|
||||
<widget class="QLabel" name="labelAudioPreview1URL">
|
||||
<property name="text">
|
||||
<string>audioPreview1URL:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>lineEditAudioPreview1URL</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="16" column="0">
|
||||
<widget class="QLabel" name="labelAudioPreview2Name">
|
||||
<property name="text">
|
||||
<string>audioPreview2Name:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>lineEditAudioPreview2Name</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="17" column="0">
|
||||
<widget class="QLabel" name="labelAudioPreview2URL">
|
||||
<property name="text">
|
||||
<string>audioPreview2URL:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>lineEditAudioPreview2URL</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="18" column="0">
|
||||
<widget class="QLabel" name="labelImagePreview1">
|
||||
<property name="text">
|
||||
<string>imagePreview1:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>lineEditImagePreview1</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="19" column="0">
|
||||
<widget class="QLabel" name="labelVideoPreview">
|
||||
<property name="text">
|
||||
<string>videoPreview:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>lineEditVideoPreview</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="20" column="0">
|
||||
<widget class="QLabel" name="labelVideoTutorial">
|
||||
<property name="text">
|
||||
<string>videoTutorial:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>lineEditVideoTutorial</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="lineEditId"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="lineEditName"/>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="lineEditAuthor"/>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="lineEditOrderBy"/>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="lineEditVersion"/>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QSpinBox" name="spinBoxTempo"/>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QLineEdit" name="lineEditIcon"/>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QSpinBox" name="spinBoxPrice"/>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="QSpinBox" name="spinBoxPriceForSession"/>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<widget class="QCheckBox" name="checkBoxHasInfo">
|
||||
<property name="text">
|
||||
<string>CheckBox</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="1">
|
||||
<widget class="QCheckBox" name="checkBoxDeleted">
|
||||
<property name="text">
|
||||
<string>CheckBox</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="1">
|
||||
<widget class="QSpinBox" name="spinBoxDifficulty"/>
|
||||
</item>
|
||||
<item row="13" column="1">
|
||||
<widget class="QSpinBox" name="spinBoxSample"/>
|
||||
</item>
|
||||
<item row="14" column="1">
|
||||
<widget class="QLineEdit" name="lineEditAudioPreview1Name"/>
|
||||
</item>
|
||||
<item row="15" column="1">
|
||||
<widget class="QLineEdit" name="lineEditAudioPreview1URL"/>
|
||||
</item>
|
||||
<item row="16" column="1">
|
||||
<widget class="QLineEdit" name="lineEditAudioPreview2Name"/>
|
||||
</item>
|
||||
<item row="17" column="1">
|
||||
<widget class="QLineEdit" name="lineEditAudioPreview2URL"/>
|
||||
</item>
|
||||
<item row="18" column="1">
|
||||
<widget class="QLineEdit" name="lineEditImagePreview1"/>
|
||||
</item>
|
||||
<item row="19" column="1">
|
||||
<widget class="QLineEdit" name="lineEditVideoPreview"/>
|
||||
</item>
|
||||
<item row="20" column="1">
|
||||
<widget class="QLineEdit" name="lineEditVideoTutorial"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
90
widgets/previewwidget.cpp
Normal file
90
widgets/previewwidget.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
#include "previewwidget.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QPen>
|
||||
#include <QMouseEvent>
|
||||
|
||||
#include "graphrenderer.h"
|
||||
|
||||
PreviewWidget::PreviewWidget(QWidget *parent) :
|
||||
QWidget(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void PreviewWidget::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
|
||||
if (m_graphCache.isNull() || m_graphCache.size() != size())
|
||||
{
|
||||
m_graphCache = QPixmap{size()};
|
||||
|
||||
QPainter painter;
|
||||
painter.begin(&m_graphCache);
|
||||
|
||||
painter.setBrush(palette().base());
|
||||
painter.drawRect(m_graphCache.rect());
|
||||
|
||||
painter.setPen(QPen{palette().color(QPalette::Text)});
|
||||
painter.setBrush(palette().text());
|
||||
|
||||
if (m_buffer.isValid())
|
||||
GraphRenderer::render(m_graphCache.rect(), m_buffer.constData<frame_t>(), m_buffer.constData<frame_t>() + m_buffer.frameCount(), painter);
|
||||
|
||||
painter.end();
|
||||
}
|
||||
|
||||
QPainter painter;
|
||||
painter.begin(this);
|
||||
|
||||
painter.drawPixmap(0, 0, m_graphCache);
|
||||
|
||||
if (m_buffer.frameCount())
|
||||
{
|
||||
std::size_t currentSample = double(m_position) / m_buffer.frameCount() * width();
|
||||
|
||||
QPen pen(Qt::red);
|
||||
pen.setWidth(3);
|
||||
painter.setPen(pen);
|
||||
painter.drawLine(currentSample, 0, currentSample, height());
|
||||
}
|
||||
|
||||
painter.end();
|
||||
}
|
||||
|
||||
void PreviewWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton)
|
||||
{
|
||||
setMouseTracking(true);
|
||||
clicked(event->x(), event->y());
|
||||
}
|
||||
|
||||
QWidget::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void PreviewWidget::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton)
|
||||
setMouseTracking(false);
|
||||
|
||||
QWidget::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
void PreviewWidget::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->buttons().testFlag(Qt::LeftButton))
|
||||
clicked(event->x(), event->y());
|
||||
|
||||
QWidget::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
void PreviewWidget::clicked(int x, int y)
|
||||
{
|
||||
if (!m_buffer.isValid())
|
||||
return;
|
||||
|
||||
const auto samplesPerPixel = m_buffer.frameCount() / width();
|
||||
|
||||
emit positionSelected(x * samplesPerPixel);
|
||||
}
|
35
widgets/previewwidget.h
Normal file
35
widgets/previewwidget.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QPixmap>
|
||||
#include <QAudioBuffer>
|
||||
|
||||
class PreviewWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PreviewWidget(QWidget *parent = nullptr);
|
||||
|
||||
const QAudioBuffer &buffer() const { return m_buffer; }
|
||||
void setBuffer(const QAudioBuffer &buffer) { m_buffer = buffer; m_graphCache = {}; repaint(); }
|
||||
|
||||
double position() const { return m_position; }
|
||||
void setPosition(double position) { m_position = position; repaint(); }
|
||||
|
||||
signals:
|
||||
void positionSelected(double sample);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
|
||||
private:
|
||||
void clicked(int x, int y);
|
||||
|
||||
QAudioBuffer m_buffer;
|
||||
double m_position{};
|
||||
QPixmap m_graphCache;
|
||||
};
|
175
widgets/sampleswidget.cpp
Executable file
175
widgets/sampleswidget.cpp
Executable file
@@ -0,0 +1,175 @@
|
||||
#include "sampleswidget.h"
|
||||
#include "ui_sampleswidget.h"
|
||||
|
||||
#include <iterator>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "midicontainers.h"
|
||||
|
||||
SamplesWidget::SamplesWidget(QWidget *parent) :
|
||||
QWidget{parent},
|
||||
m_ui{std::make_unique<Ui::SamplesWidget>()}
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
connect(m_ui->checkBox, &QCheckBox::toggled, this, &SamplesWidget::updateWidgets);
|
||||
|
||||
connect(m_ui->pushButtonStopAll, &QAbstractButton::pressed, this, &SamplesWidget::stopAll);
|
||||
|
||||
quint8 padNr{};
|
||||
for (SampleWidget &widget : getWidgets())
|
||||
{
|
||||
widget.setPadNr(padNr++);
|
||||
connect(&widget, &SampleWidget::chokeTriggered, this, &SamplesWidget::chokeTriggered);
|
||||
connect(&widget, &SampleWidget::sendMidi, this, &SamplesWidget::sendMidi);
|
||||
}
|
||||
}
|
||||
|
||||
SamplesWidget::~SamplesWidget() = default;
|
||||
|
||||
void SamplesWidget::loadSettings(DrumMachineSettings &settings)
|
||||
{
|
||||
for (SampleWidget &widget : getWidgets())
|
||||
widget.loadSettings(settings);
|
||||
}
|
||||
|
||||
void SamplesWidget::setPreset(const presets::Preset &preset)
|
||||
{
|
||||
m_preset = preset;
|
||||
|
||||
updateWidgets();
|
||||
}
|
||||
|
||||
void SamplesWidget::messageReceived(const midi::MidiMessage &message)
|
||||
{
|
||||
if (message == midi::MidiMessage{.channel=0,.cmd=midi::Command::ControlChange,.flag=true,.note=64,.velocity=127})
|
||||
{
|
||||
m_ui->checkBox->toggle();
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.cmd != midi::Command::NoteOn && message.cmd != midi::Command::NoteOff)
|
||||
return;
|
||||
|
||||
for (SampleWidget &widget : getWidgets())
|
||||
{
|
||||
if (widget.isLearning())
|
||||
{
|
||||
widget.learn(message.channel, message.note);
|
||||
}
|
||||
else if (widget.channel() == message.channel && widget.note() == message.note)
|
||||
{
|
||||
if (message.cmd == midi::Command::NoteOff || (message.cmd == midi::Command::NoteOn && message.velocity == 0))
|
||||
widget.released();
|
||||
else if (message.cmd == midi::Command::NoteOn)
|
||||
widget.pressed(message.velocity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SamplesWidget::writeSamples(frame_t *begin, frame_t *end)
|
||||
{
|
||||
for (SampleWidget &widget : getWidgets())
|
||||
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())
|
||||
widget.sendColor();
|
||||
}
|
||||
|
||||
void SamplesWidget::sequencerTriggerSample(int index)
|
||||
{
|
||||
const auto widgets = getWidgets();
|
||||
if (index < 0 || index >= std::size(widgets))
|
||||
{
|
||||
qDebug() << "index out of range" << index;
|
||||
return;
|
||||
}
|
||||
widgets[index].get().pressed(127);
|
||||
}
|
||||
|
||||
void SamplesWidget::chokeTriggered(int choke)
|
||||
{
|
||||
for (SampleWidget &widget : getWidgets())
|
||||
{
|
||||
if (&widget == sender())
|
||||
continue;
|
||||
|
||||
if (widget.choke() && *widget.choke() && *widget.choke() == choke)
|
||||
widget.forceStop();
|
||||
}
|
||||
}
|
||||
|
||||
void SamplesWidget::updateWidgets()
|
||||
{
|
||||
const auto widgets = getWidgets();
|
||||
|
||||
auto files = *m_preset.files;
|
||||
|
||||
if (m_ui->checkBox->isChecked())
|
||||
for (int i = 0; i < 12; i++)
|
||||
std::swap(files[i], files[i+12]);
|
||||
|
||||
auto filesIter = std::cbegin(files);
|
||||
auto widgetsIter = std::cbegin(widgets);
|
||||
|
||||
for (; filesIter != std::cend(files) && widgetsIter != std::cend(widgets); std::advance(filesIter, 1), std::advance(widgetsIter, 1))
|
||||
widgetsIter->get().setFile(*m_preset.id, *filesIter);
|
||||
}
|
||||
|
||||
void SamplesWidget::stopAll()
|
||||
{
|
||||
for (SampleWidget &widget : getWidgets())
|
||||
widget.forceStop();
|
||||
}
|
||||
|
||||
std::array<std::reference_wrapper<SampleWidget>, 24> SamplesWidget::getWidgets()
|
||||
{
|
||||
return {
|
||||
std::ref(*m_ui->sampleWidget_1),
|
||||
std::ref(*m_ui->sampleWidget_2),
|
||||
std::ref(*m_ui->sampleWidget_3),
|
||||
std::ref(*m_ui->sampleWidget_4),
|
||||
std::ref(*m_ui->sampleWidget_5),
|
||||
std::ref(*m_ui->sampleWidget_6),
|
||||
std::ref(*m_ui->sampleWidget_7),
|
||||
std::ref(*m_ui->sampleWidget_8),
|
||||
std::ref(*m_ui->sampleWidget_9),
|
||||
std::ref(*m_ui->sampleWidget_10),
|
||||
std::ref(*m_ui->sampleWidget_11),
|
||||
std::ref(*m_ui->sampleWidget_12),
|
||||
std::ref(*m_ui->sampleWidget_13),
|
||||
std::ref(*m_ui->sampleWidget_14),
|
||||
std::ref(*m_ui->sampleWidget_15),
|
||||
std::ref(*m_ui->sampleWidget_16),
|
||||
std::ref(*m_ui->sampleWidget_17),
|
||||
std::ref(*m_ui->sampleWidget_18),
|
||||
std::ref(*m_ui->sampleWidget_19),
|
||||
std::ref(*m_ui->sampleWidget_20),
|
||||
std::ref(*m_ui->sampleWidget_21),
|
||||
std::ref(*m_ui->sampleWidget_22),
|
||||
std::ref(*m_ui->sampleWidget_23),
|
||||
std::ref(*m_ui->sampleWidget_24)
|
||||
};
|
||||
}
|
57
widgets/sampleswidget.h
Executable file
57
widgets/sampleswidget.h
Executable file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <array>
|
||||
#include <functional>
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "audioformat.h"
|
||||
#include "presets.h"
|
||||
|
||||
namespace Ui { class SamplesWidget; }
|
||||
namespace midi { class MidiMessage; }
|
||||
class QNetworkAccessManager;
|
||||
class SampleWidget;
|
||||
class DrumMachineSettings;
|
||||
|
||||
class SamplesWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SamplesWidget(QWidget *parent = nullptr);
|
||||
~SamplesWidget() override;
|
||||
|
||||
void loadSettings(DrumMachineSettings &settings);
|
||||
|
||||
void setPreset(const presets::Preset &preset);
|
||||
|
||||
void messageReceived(const midi::MidiMessage &message);
|
||||
|
||||
void writeSamples(frame_t *begin, frame_t *end);
|
||||
|
||||
void injectNetworkAccessManager(QNetworkAccessManager &networkAccessManager);
|
||||
void injectDecodingThread(QThread &thread);
|
||||
|
||||
void unsendColors();
|
||||
void sendColors();
|
||||
|
||||
signals:
|
||||
void sendMidi(const midi::MidiMessage &midiMsg);
|
||||
|
||||
public slots:
|
||||
void sequencerTriggerSample(int index);
|
||||
|
||||
private slots:
|
||||
void chokeTriggered(int choke);
|
||||
void updateWidgets();
|
||||
void stopAll();
|
||||
|
||||
private:
|
||||
std::array<std::reference_wrapper<SampleWidget>, 24> getWidgets();
|
||||
|
||||
const std::unique_ptr<Ui::SamplesWidget> m_ui;
|
||||
|
||||
presets::Preset m_preset;
|
||||
};
|
184
widgets/sampleswidget.ui
Executable file
184
widgets/sampleswidget.ui
Executable file
@@ -0,0 +1,184 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SamplesWidget</class>
|
||||
<widget class="QWidget" name="SamplesWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>660</width>
|
||||
<height>421</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout" rowstretch="0,0,0,0,0" columnstretch="0,0,0,0,0,0,0,0,0">
|
||||
<item row="3" column="3">
|
||||
<widget class="SampleWidget" name="sampleWidget_9" native="true"/>
|
||||
</item>
|
||||
<item row="4" column="3">
|
||||
<widget class="SampleWidget" name="sampleWidget_12" native="true"/>
|
||||
</item>
|
||||
<item row="1" column="5">
|
||||
<widget class="SampleWidget" name="sampleWidget_13" native="true"/>
|
||||
</item>
|
||||
<item row="1" column="7">
|
||||
<widget class="SampleWidget" name="sampleWidget_15" native="true"/>
|
||||
</item>
|
||||
<item row="2" column="7">
|
||||
<widget class="SampleWidget" name="sampleWidget_18" native="true"/>
|
||||
</item>
|
||||
<item row="4" column="6">
|
||||
<widget class="SampleWidget" name="sampleWidget_23" native="true"/>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="9">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<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="QCheckBox" name="checkBox">
|
||||
<property name="text">
|
||||
<string>Swap left/right</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonStopAll">
|
||||
<property name="text">
|
||||
<string>Stop all</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="SampleWidget" name="sampleWidget_8" native="true"/>
|
||||
</item>
|
||||
<item row="4" column="7">
|
||||
<widget class="SampleWidget" name="sampleWidget_24" native="true"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<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 row="1" column="1">
|
||||
<widget class="SampleWidget" name="sampleWidget_1" native="true"/>
|
||||
</item>
|
||||
<item row="3" column="5">
|
||||
<widget class="SampleWidget" name="sampleWidget_19" native="true"/>
|
||||
</item>
|
||||
<item row="3" column="7">
|
||||
<widget class="SampleWidget" name="sampleWidget_21" native="true"/>
|
||||
</item>
|
||||
<item row="1" column="8">
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<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 row="2" column="2">
|
||||
<widget class="SampleWidget" name="sampleWidget_5" native="true"/>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="SampleWidget" name="sampleWidget_2" native="true"/>
|
||||
</item>
|
||||
<item row="2" column="6">
|
||||
<widget class="SampleWidget" name="sampleWidget_17" native="true"/>
|
||||
</item>
|
||||
<item row="3" column="6">
|
||||
<widget class="SampleWidget" name="sampleWidget_20" native="true"/>
|
||||
</item>
|
||||
<item row="1" column="4">
|
||||
<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 row="2" column="5">
|
||||
<widget class="SampleWidget" name="sampleWidget_16" native="true"/>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="SampleWidget" name="sampleWidget_4" native="true"/>
|
||||
</item>
|
||||
<item row="4" column="5">
|
||||
<widget class="SampleWidget" name="sampleWidget_22" native="true"/>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<widget class="SampleWidget" name="sampleWidget_11" native="true"/>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="SampleWidget" name="sampleWidget_3" native="true"/>
|
||||
</item>
|
||||
<item row="1" column="6">
|
||||
<widget class="SampleWidget" name="sampleWidget_14" native="true"/>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="SampleWidget" name="sampleWidget_10" native="true"/>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<widget class="SampleWidget" name="sampleWidget_6" native="true"/>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="SampleWidget" name="sampleWidget_7" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>SampleWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>widgets/samplewidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
340
widgets/samplewidget.cpp
Executable file
340
widgets/samplewidget.cpp
Executable file
@@ -0,0 +1,340 @@
|
||||
#include "samplewidget.h"
|
||||
#include "ui_samplewidget.h"
|
||||
|
||||
#include <QAbstractEventDispatcher>
|
||||
#include <QSoundEffect>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QMetaEnum>
|
||||
#include <QDebug>
|
||||
|
||||
#include "audiodecoder.h"
|
||||
#include "drummachinesettings.h"
|
||||
#include "midicontainers.h"
|
||||
|
||||
namespace {
|
||||
QString toString(QString value) { return value; }
|
||||
QString toString(int value) { return QString::number(value); }
|
||||
QString toString(bool value) { return value?"true":"false"; }
|
||||
}
|
||||
|
||||
SampleWidget::SampleWidget(QWidget *parent) :
|
||||
QFrame{parent},
|
||||
m_ui{std::make_unique<Ui::SampleWidget>()}
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
connect(m_ui->dialSpeed, &QAbstractSlider::valueChanged, &m_player, [&player=m_player](int value){ player.setSpeed(float(value)/100.f); });
|
||||
connect(m_ui->dialVolume, &QAbstractSlider::valueChanged, &m_player, [&player=m_player](int value){ player.setVolume(float(value)/100.f); });
|
||||
|
||||
connect(&m_player, &AudioPlayer::playingChanged, this, &SampleWidget::updateStatus);
|
||||
|
||||
connect(m_ui->pushButton, &QAbstractButton::pressed, this, [this](){ pressed(127); });
|
||||
connect(m_ui->pushButton, &QAbstractButton::released, this, &SampleWidget::released);
|
||||
connect(m_ui->toolButtonLearn, &QAbstractButton::pressed, this, &SampleWidget::learnPressed);
|
||||
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
SampleWidget::~SampleWidget() = default;
|
||||
|
||||
void SampleWidget::loadSettings(DrumMachineSettings &settings)
|
||||
{
|
||||
m_ui->channelSpinBox->setValue(settings.padChannel(m_padNr));
|
||||
m_ui->noteSpinBox->setValue(settings.padNote(m_padNr));
|
||||
|
||||
m_settings = &settings;
|
||||
}
|
||||
|
||||
void SampleWidget::setFile(const QString &presetId, const presets::File &file)
|
||||
{
|
||||
m_presetId = presetId;
|
||||
m_file = file;
|
||||
|
||||
m_player.setBuffer({});
|
||||
|
||||
startRequest();
|
||||
|
||||
const auto setupLabel = [&](const auto &value, QLabel *label){
|
||||
QString text;
|
||||
QFont font;
|
||||
QPalette pal;
|
||||
|
||||
if (value)
|
||||
text = toString(*value);
|
||||
else
|
||||
{
|
||||
text = tr("(null)");
|
||||
font.setItalic(true);
|
||||
pal.setColor(label->foregroundRole(), Qt::gray);
|
||||
}
|
||||
|
||||
label->setText(text);
|
||||
label->setFont(font);
|
||||
label->setPalette(pal);
|
||||
};
|
||||
|
||||
setupLabel(file.stopOnRelease, m_ui->stopOnReleaseLabel);
|
||||
setupLabel(file.looped, m_ui->loopedLabel);
|
||||
setupLabel(file.choke, m_ui->chokeLabel);
|
||||
}
|
||||
|
||||
quint8 SampleWidget::channel() const
|
||||
{
|
||||
return m_ui->channelSpinBox->value();
|
||||
}
|
||||
|
||||
void SampleWidget::setChannel(quint8 channel)
|
||||
{
|
||||
m_ui->channelSpinBox->setValue(channel);
|
||||
|
||||
if (m_settings)
|
||||
m_settings->setPadChannel(m_padNr, channel);
|
||||
}
|
||||
|
||||
quint8 SampleWidget::note() const
|
||||
{
|
||||
return m_ui->noteSpinBox->value();
|
||||
}
|
||||
|
||||
void SampleWidget::setNote(quint8 note)
|
||||
{
|
||||
m_ui->noteSpinBox->setValue(note);
|
||||
|
||||
if (m_settings)
|
||||
m_settings->setPadNote(m_padNr, note);
|
||||
}
|
||||
|
||||
int SampleWidget::speed() const
|
||||
{
|
||||
return m_ui->dialSpeed->value();
|
||||
}
|
||||
|
||||
void SampleWidget::setSpeed(int speed)
|
||||
{
|
||||
m_ui->dialSpeed->setValue(speed);
|
||||
}
|
||||
|
||||
int SampleWidget::volume() const
|
||||
{
|
||||
return m_ui->dialVolume->value();
|
||||
}
|
||||
|
||||
void SampleWidget::setVolume(int volume)
|
||||
{
|
||||
m_ui->dialVolume->setValue(volume);
|
||||
}
|
||||
|
||||
std::optional<int> SampleWidget::choke() const
|
||||
{
|
||||
if (!m_file)
|
||||
return {};
|
||||
return m_file->choke;
|
||||
}
|
||||
|
||||
void SampleWidget::pressed(quint8 velocity)
|
||||
{
|
||||
Q_UNUSED(velocity)
|
||||
|
||||
m_player.restart();
|
||||
|
||||
if (m_file && m_file->choke && *m_file->choke)
|
||||
emit chokeTriggered(*m_file->choke);
|
||||
}
|
||||
|
||||
void SampleWidget::released()
|
||||
{
|
||||
}
|
||||
|
||||
void SampleWidget::forceStop()
|
||||
{
|
||||
m_player.setPlaying(false);
|
||||
}
|
||||
|
||||
void SampleWidget::injectNetworkAccessManager(QNetworkAccessManager &networkAccessManager)
|
||||
{
|
||||
m_networkAccessManager = &networkAccessManager;
|
||||
if (m_file)
|
||||
startRequest();
|
||||
}
|
||||
|
||||
void SampleWidget::injectDecodingThread(QThread &thread)
|
||||
{
|
||||
QMetaObject::invokeMethod(QAbstractEventDispatcher::instance(&thread), [this](){
|
||||
m_decoder = std::make_unique<AudioDecoder>();
|
||||
connect(this, &SampleWidget::startDecoding, m_decoder.get(), &AudioDecoder::startDecodingDevice);
|
||||
connect(m_decoder.get(), &AudioDecoder::decodingFinished, this, &SampleWidget::decodingFinished);
|
||||
if (m_reply && m_reply->isFinished() && m_reply->error() == QNetworkReply::NoError)
|
||||
m_decoder->startDecodingDevice(m_reply);
|
||||
});
|
||||
}
|
||||
|
||||
void SampleWidget::writeSamples(frame_t *begin, frame_t *end)
|
||||
{
|
||||
m_player.writeSamples(begin, end);
|
||||
}
|
||||
|
||||
void SampleWidget::learn(quint8 channel, quint8 note)
|
||||
{
|
||||
setChannel(channel);
|
||||
setNote(note);
|
||||
if (m_learning)
|
||||
learnPressed();
|
||||
}
|
||||
|
||||
void SampleWidget::unsendColor()
|
||||
{
|
||||
m_sendColors = false;
|
||||
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()
|
||||
{
|
||||
m_sendColors = true;
|
||||
midi::MidiMessage midiMsg;
|
||||
|
||||
midiMsg.channel = m_ui->channelSpinBox->value();
|
||||
midiMsg.cmd = midi::Command::NoteOn;
|
||||
midiMsg.flag = true;
|
||||
midiMsg.note = m_ui->noteSpinBox->value();
|
||||
|
||||
if (m_file && m_file->color && m_player.buffer().isValid())
|
||||
{
|
||||
const auto &color = *m_file->color;
|
||||
if (color == "purple")
|
||||
midiMsg.velocity = m_player.playing() ? 43 : 18;
|
||||
else if (color == "red")
|
||||
midiMsg.velocity = m_player.playing() ? 3 : 1;
|
||||
else if (color == "yellow")
|
||||
midiMsg.velocity = m_player.playing() ? 58 : 33;
|
||||
else if (color == "green")
|
||||
midiMsg.velocity = m_player.playing() ? 56 : 16;
|
||||
else if (color == "blue")
|
||||
midiMsg.velocity = m_player.playing() ? 49 : 51;
|
||||
else
|
||||
goto noColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
noColor:
|
||||
midiMsg.velocity = 0;
|
||||
}
|
||||
|
||||
emit sendMidi(midiMsg);
|
||||
}
|
||||
|
||||
void SampleWidget::updateStatus()
|
||||
{
|
||||
QPalette pal;
|
||||
|
||||
|
||||
if (m_file && m_file->color && m_player.buffer().isValid())
|
||||
{
|
||||
const auto bright = m_player.playing() ? 255 : 155;
|
||||
const auto dark = m_player.playing() ?
|
||||
#if !defined(Q_OS_WIN)
|
||||
80 : 0
|
||||
#else
|
||||
180 : 80
|
||||
#endif
|
||||
;
|
||||
|
||||
const auto &color = *m_file->color;
|
||||
if (color == "purple")
|
||||
pal.setColor(QPalette::Window, QColor{bright, dark, bright});
|
||||
else if (color == "red")
|
||||
pal.setColor(QPalette::Window, QColor{bright, dark, dark});
|
||||
else if (color == "yellow")
|
||||
pal.setColor(QPalette::Window, QColor{bright, bright, dark});
|
||||
else if (color == "green")
|
||||
pal.setColor(QPalette::Window, QColor{dark, bright, dark});
|
||||
else if (color == "blue")
|
||||
pal.setColor(QPalette::Window, QColor{dark, dark, bright});
|
||||
else
|
||||
qWarning() << "unknown color:" << color;
|
||||
}
|
||||
setPalette(pal);
|
||||
|
||||
if (m_sendColors)
|
||||
sendColor();
|
||||
|
||||
if (m_reply)
|
||||
{
|
||||
if (!m_reply->isFinished())
|
||||
m_ui->statusLabel->setText(tr("Downloading..."));
|
||||
else if (m_reply->error() != QNetworkReply::NoError)
|
||||
m_ui->statusLabel->setText(QMetaEnum::fromType<QNetworkReply::NetworkError>().valueToKey(m_reply->error()));
|
||||
else
|
||||
{
|
||||
if (!m_decoder)
|
||||
m_ui->statusLabel->setText(tr("Waiting for decoder thread..."));
|
||||
else
|
||||
m_ui->statusLabel->setText(tr("Decoding"));
|
||||
}
|
||||
}
|
||||
else
|
||||
m_ui->statusLabel->setText(m_player.playing() ? tr("Playing") : tr("Ready"));
|
||||
}
|
||||
|
||||
void SampleWidget::requestFinished()
|
||||
{
|
||||
if (m_reply->error() == QNetworkReply::NoError)
|
||||
{
|
||||
emit startDecoding(m_reply);
|
||||
}
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
void SampleWidget::decodingFinished(const QAudioBuffer &buffer)
|
||||
{
|
||||
m_reply = nullptr;
|
||||
m_player.setBuffer(buffer);
|
||||
setSpeed(100);
|
||||
setVolume(100);
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
void SampleWidget::learnPressed()
|
||||
{
|
||||
auto palette = m_ui->toolButtonLearn->palette();
|
||||
|
||||
if (m_learning)
|
||||
{
|
||||
palette.setColor(m_ui->toolButtonLearn->backgroundRole(), m_oldColor);
|
||||
palette.setBrush(m_ui->toolButtonLearn->backgroundRole(), m_oldBrush);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_oldColor = palette.color(m_ui->toolButtonLearn->backgroundRole());
|
||||
m_oldBrush = palette.brush(m_ui->toolButtonLearn->backgroundRole());
|
||||
palette.setColor(m_ui->toolButtonLearn->backgroundRole(), Qt::red);
|
||||
palette.setBrush(m_ui->toolButtonLearn->backgroundRole(), Qt::red);
|
||||
}
|
||||
m_ui->toolButtonLearn->setPalette(palette);
|
||||
|
||||
m_learning = !m_learning;
|
||||
qDebug() << m_learning;
|
||||
}
|
||||
|
||||
void SampleWidget::startRequest()
|
||||
{
|
||||
if (m_networkAccessManager && m_file->filename)
|
||||
{
|
||||
QNetworkRequest request{QUrl{QString{"https://brunner.ninja/komposthaufen/dpm/presets/extracted/%0/%1"}.arg(m_presetId, *m_file->filename)}};
|
||||
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
|
||||
request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, true);
|
||||
m_reply = std::shared_ptr<QNetworkReply>{m_networkAccessManager->get(request)};
|
||||
connect(m_reply.get(), &QNetworkReply::finished, this, &SampleWidget::requestFinished);
|
||||
}
|
||||
|
||||
updateStatus();
|
||||
}
|
100
widgets/samplewidget.h
Executable file
100
widgets/samplewidget.h
Executable file
@@ -0,0 +1,100 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QFrame>
|
||||
|
||||
#include "audioformat.h"
|
||||
#include "presets.h"
|
||||
#include "audioplayer.h"
|
||||
#include "midicontainers.h"
|
||||
|
||||
namespace Ui { class SampleWidget; }
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
class QAudioBuffer;
|
||||
class AudioDecoder;
|
||||
class DrumMachineSettings;
|
||||
|
||||
class SampleWidget : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SampleWidget(QWidget *parent = nullptr);
|
||||
~SampleWidget() override;
|
||||
|
||||
quint8 padNr() const { return m_padNr; }
|
||||
void setPadNr(quint8 padNr) { m_padNr = padNr; }
|
||||
|
||||
void loadSettings(DrumMachineSettings &settings);
|
||||
|
||||
void setFile(const QString &presetId, const presets::File &file);
|
||||
|
||||
quint8 channel() const;
|
||||
void setChannel(quint8 channel);
|
||||
|
||||
quint8 note() const;
|
||||
void setNote(quint8 note);
|
||||
|
||||
int speed() const;
|
||||
void setSpeed(int speed);
|
||||
|
||||
int volume() const;
|
||||
void setVolume(int volume);
|
||||
|
||||
std::optional<int> choke() const;
|
||||
|
||||
void pressed(quint8 velocity);
|
||||
void released();
|
||||
|
||||
void forceStop();
|
||||
|
||||
void injectNetworkAccessManager(QNetworkAccessManager &networkAccessManager);
|
||||
void injectDecodingThread(QThread &thread);
|
||||
|
||||
void writeSamples(frame_t *begin, frame_t *end);
|
||||
|
||||
bool isLearning() const { return m_learning; }
|
||||
void learn(quint8 channel, quint8 note);
|
||||
|
||||
void unsendColor();
|
||||
void sendColor();
|
||||
|
||||
signals:
|
||||
void chokeTriggered(int choke);
|
||||
void startDecoding(std::shared_ptr<QIODevice> device);
|
||||
void sendMidi(const midi::MidiMessage &midiMsg);
|
||||
|
||||
private slots:
|
||||
void updateStatus();
|
||||
void requestFinished();
|
||||
void decodingFinished(const QAudioBuffer &buffer);
|
||||
void learnPressed();
|
||||
|
||||
private:
|
||||
void startRequest();
|
||||
|
||||
const std::unique_ptr<Ui::SampleWidget> m_ui;
|
||||
|
||||
DrumMachineSettings *m_settings{};
|
||||
|
||||
std::shared_ptr<QNetworkReply> m_reply;
|
||||
|
||||
std::unique_ptr<AudioDecoder> m_decoder;
|
||||
|
||||
AudioPlayer m_player;
|
||||
|
||||
QString m_presetId;
|
||||
std::optional<presets::File> m_file;
|
||||
|
||||
QNetworkAccessManager *m_networkAccessManager{};
|
||||
|
||||
quint8 m_padNr{};
|
||||
|
||||
bool m_learning{};
|
||||
QColor m_oldColor;
|
||||
QBrush m_oldBrush;
|
||||
|
||||
bool m_sendColors{};
|
||||
};
|
156
widgets/samplewidget.ui
Executable file
156
widgets/samplewidget.ui
Executable file
@@ -0,0 +1,156 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SampleWidget</class>
|
||||
<widget class="QFrame" name="SampleWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>140</width>
|
||||
<height>157</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Panel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QLabel" name="statusLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDial" name="dialSpeed">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>80</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>120</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="notchTarget">
|
||||
<double>10.000000000000000</double>
|
||||
</property>
|
||||
<property name="notchesVisible">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<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>▶</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDial" name="dialVolume">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="notchTarget">
|
||||
<double>10.000000000000000</double>
|
||||
</property>
|
||||
<property name="notchesVisible">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="channelSpinBox"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="noteSpinBox"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="toolButtonLearn">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="stopOnReleaseLabel">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Panel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="loopedLabel">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Panel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="chokeLabel">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Panel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
142
widgets/scratchwidget.cpp
Normal file
142
widgets/scratchwidget.cpp
Normal file
@@ -0,0 +1,142 @@
|
||||
#include "scratchwidget.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QRect>
|
||||
#include <QMouseEvent>
|
||||
#include <QDebug>
|
||||
|
||||
#include "graphrenderer.h"
|
||||
|
||||
ScratchWidget::ScratchWidget(QWidget *parent) :
|
||||
QWidget{parent}
|
||||
{
|
||||
connect(&m_timer, &QTimer::timeout, this, &ScratchWidget::timeout);
|
||||
m_timer.setSingleShot(true);
|
||||
m_timer.setInterval(100);
|
||||
}
|
||||
|
||||
void ScratchWidget::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
QPainter painter;
|
||||
painter.begin(this);
|
||||
|
||||
painter.setPen({});
|
||||
painter.setBrush(palette().window());
|
||||
painter.drawRect(rect());
|
||||
|
||||
if (m_buffer.isValid() && m_position < m_buffer.frameCount() - m_framesPerBeat)
|
||||
{
|
||||
{
|
||||
QPen pen{Qt::blue};
|
||||
pen.setWidth(3);
|
||||
painter.setPen(pen);
|
||||
}
|
||||
|
||||
const auto doit = [&](int offset)
|
||||
{
|
||||
int x = ((width()/2)-(float(m_position % m_framesPerBeat) / m_framesPerBeat * m_beatWidth)) + (m_beatWidth*offset);
|
||||
const auto pixmap = getPixmap((m_position/m_framesPerBeat)+offset);
|
||||
if (!pixmap.isNull())
|
||||
painter.drawPixmap(x, 0, pixmap);
|
||||
};
|
||||
|
||||
doit(-2);
|
||||
doit(-1);
|
||||
doit(0);
|
||||
doit(1);
|
||||
doit(2);
|
||||
}
|
||||
|
||||
{
|
||||
QPen pen{Qt::red};
|
||||
pen.setWidth(3);
|
||||
painter.setPen(pen);
|
||||
}
|
||||
|
||||
painter.drawLine(width()/2, 0, width()/2, height());
|
||||
|
||||
painter.end();
|
||||
}
|
||||
|
||||
void ScratchWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton)
|
||||
{
|
||||
m_scratching = true;
|
||||
m_mouseX = event->x();
|
||||
m_timestamp = QDateTime::currentDateTime();
|
||||
setMouseTracking(true);
|
||||
emit scratchBegin();
|
||||
emit scratchSpeed(0.f);
|
||||
}
|
||||
}
|
||||
|
||||
void ScratchWidget::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton)
|
||||
{
|
||||
m_timer.stop();
|
||||
emit scratchSpeed(1.f);
|
||||
m_scratching = false;
|
||||
setMouseTracking(false);
|
||||
emit scratchEnd();
|
||||
}
|
||||
}
|
||||
|
||||
void ScratchWidget::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (m_scratching)
|
||||
{
|
||||
const auto now = QDateTime::currentDateTime();
|
||||
|
||||
int dx = m_mouseX - event->x();
|
||||
int dt = m_timestamp.msecsTo(now);
|
||||
|
||||
emit scratchSpeed(float(dx) / dt * m_framesPerBeat / m_beatWidth / 50);
|
||||
|
||||
m_mouseX = event->x();
|
||||
m_timestamp = now;
|
||||
m_timer.start();
|
||||
}
|
||||
}
|
||||
|
||||
void ScratchWidget::timeout()
|
||||
{
|
||||
emit scratchSpeed(0.f);
|
||||
}
|
||||
|
||||
QPixmap ScratchWidget::getPixmap(int index)
|
||||
{
|
||||
{
|
||||
const auto pixmap = m_graphCache[index];
|
||||
if (pixmap)
|
||||
return *pixmap;
|
||||
}
|
||||
|
||||
if (!m_buffer.isValid() || index < 0 || index >= m_buffer.frameCount()/m_framesPerBeat)
|
||||
{
|
||||
qWarning() << index;
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto *begin = m_buffer.constData<frame_t>() + (index*m_framesPerBeat);
|
||||
|
||||
const auto pixmap = new QPixmap{QSize{m_beatWidth, height()}};
|
||||
|
||||
{
|
||||
QPainter painter;
|
||||
painter.begin(pixmap);
|
||||
|
||||
painter.setPen(Qt::blue);
|
||||
painter.setBrush(palette().base());
|
||||
painter.drawRect(pixmap->rect());
|
||||
|
||||
painter.setPen(QPen{palette().color(QPalette::Text)});
|
||||
painter.setBrush(palette().text());
|
||||
GraphRenderer::render(pixmap->rect(), begin, begin+m_framesPerBeat, painter);
|
||||
}
|
||||
|
||||
m_graphCache.insert(index, pixmap);
|
||||
|
||||
return *pixmap;
|
||||
}
|
59
widgets/scratchwidget.h
Normal file
59
widgets/scratchwidget.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QAudioBuffer>
|
||||
#include <QCache>
|
||||
#include <QDateTime>
|
||||
#include <QTimer>
|
||||
|
||||
#include "audioformat.h"
|
||||
|
||||
class ScratchWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ScratchWidget(QWidget *parent = nullptr);
|
||||
|
||||
const QAudioBuffer &buffer() const { return m_buffer; }
|
||||
void setBuffer(const QAudioBuffer &buffer) { m_buffer = buffer; m_graphCache.clear(); repaint(); }
|
||||
|
||||
std::ptrdiff_t position() const { return m_position; }
|
||||
void setPosition(std::ptrdiff_t position) { m_position = position; repaint(); }
|
||||
|
||||
int beatWidth() const { return m_beatWidth; };
|
||||
void setBeatWidth(int beatWidth) { m_beatWidth = beatWidth; m_graphCache.clear(); repaint(); };
|
||||
|
||||
int framesPerBeat() const { return m_framesPerBeat; }
|
||||
void setFramesPerBeat(int framesPerBeat) { m_framesPerBeat = framesPerBeat; m_graphCache.clear(); repaint(); }
|
||||
|
||||
signals:
|
||||
void scratchBegin();
|
||||
void scratchSpeed(float speed);
|
||||
void scratchEnd();
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void timeout();
|
||||
|
||||
private:
|
||||
QPixmap getPixmap(int index);
|
||||
|
||||
QAudioBuffer m_buffer;
|
||||
std::ptrdiff_t m_position{};
|
||||
QCache<int, QPixmap> m_graphCache;
|
||||
|
||||
int m_beatWidth{100};
|
||||
int m_framesPerBeat{frameRate/4};
|
||||
|
||||
bool m_scratching{};
|
||||
bool m_dragging{};
|
||||
int m_mouseX;
|
||||
QDateTime m_timestamp;
|
||||
QTimer m_timer;
|
||||
};
|
153
widgets/sequencerwidget.cpp
Executable file
153
widgets/sequencerwidget.cpp
Executable file
@@ -0,0 +1,153 @@
|
||||
#include "sequencerwidget.h"
|
||||
#include "ui_sequencerwidget.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "presets.h"
|
||||
|
||||
SequencerWidget::SequencerWidget(QWidget *parent) :
|
||||
QWidget{parent},
|
||||
m_ui{std::make_unique<Ui::SequencerWidget>()}
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
connect(m_ui->spinBoxTempo, qOverload<int>(&QSpinBox::valueChanged), this, &SequencerWidget::tempoChanged);
|
||||
connect(m_ui->comboBoxSequence, qOverload<int>(&QComboBox::currentIndexChanged), this, &SequencerWidget::sequenceSelected);
|
||||
connect(m_ui->horizontalSlider, &QSlider::valueChanged, this, [=](int value){ m_pos = value; updateStatusLabel(); });
|
||||
|
||||
connect(m_ui->pushButtonPlayPause, &QAbstractButton::pressed, this, &SequencerWidget::playPause);
|
||||
connect(m_ui->pushButtonStop, &QAbstractButton::pressed, this, &SequencerWidget::stop);
|
||||
|
||||
connect(&m_timer, &QTimer::timeout, this, &SequencerWidget::timeout);
|
||||
|
||||
updateStatusLabel();
|
||||
}
|
||||
|
||||
SequencerWidget::~SequencerWidget() = default;
|
||||
|
||||
void SequencerWidget::setPreset(const presets::Preset &preset)
|
||||
{
|
||||
if (preset.tempo)
|
||||
m_ui->spinBoxTempo->setValue(*preset.tempo);
|
||||
|
||||
for (int i = 0; i < 24; i++)
|
||||
qobject_cast<QLabel*>(m_ui->gridLayout->itemAtPosition(2+(i*2), 0)->widget())->setText(*preset.files->at(i).filename);
|
||||
|
||||
m_selectedSequence = nullptr;
|
||||
m_ui->horizontalSlider->setMaximum(0);
|
||||
|
||||
m_ui->comboBoxSequence->clear();
|
||||
m_sequences.clear();
|
||||
m_selectedSequence = nullptr;
|
||||
|
||||
const auto doit = [&](const QString &prefix, const std::optional<std::map<QString, std::vector<presets::Sequence>>> &value)
|
||||
{
|
||||
if (!value)
|
||||
return;
|
||||
|
||||
for (const auto &pair : *value)
|
||||
{
|
||||
for (const auto &sequence : pair.second)
|
||||
{
|
||||
m_ui->comboBoxSequence->addItem(QString{"%0/%1/%2"}.arg(prefix, pair.first, sequence.name?*sequence.name:"(null)"));
|
||||
m_sequences.emplace_back(sequence);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
QSignalBlocker blocker{m_ui->comboBoxSequence};
|
||||
doit("beatSchool", preset.beatSchool);
|
||||
doit("easyPlay", preset.easyPlay);
|
||||
}
|
||||
|
||||
sequenceSelected();
|
||||
}
|
||||
|
||||
void SequencerWidget::playPause()
|
||||
{
|
||||
if (m_timer.isActive())
|
||||
{
|
||||
m_timer.stop();
|
||||
m_ui->pushButtonPlayPause->setText(tr("▶"));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_timer.start();
|
||||
m_ui->pushButtonPlayPause->setText(tr("▮▮"));
|
||||
}
|
||||
}
|
||||
|
||||
void SequencerWidget::stop()
|
||||
{
|
||||
m_timer.stop();
|
||||
m_ui->pushButtonPlayPause->setText(tr("▶"));
|
||||
m_pos = 0;
|
||||
m_ui->horizontalSlider->setValue(0);
|
||||
updateStatusLabel();
|
||||
}
|
||||
|
||||
void SequencerWidget::tempoChanged(int tempo)
|
||||
{
|
||||
m_timer.setInterval(1000. * 60. / tempo / 4.);
|
||||
}
|
||||
|
||||
void SequencerWidget::sequenceSelected()
|
||||
{
|
||||
const auto index = m_ui->comboBoxSequence->currentIndex();
|
||||
|
||||
if (index == -1)
|
||||
m_selectedSequence = nullptr;
|
||||
else
|
||||
m_selectedSequence = &m_sequences[index];
|
||||
|
||||
m_ui->horizontalSlider->setMaximum(m_selectedSequence && m_selectedSequence->sequencerSize ? *m_selectedSequence->sequencerSize-2 : 0);
|
||||
|
||||
m_ui->pushButtonPlayPause->setEnabled(m_selectedSequence != nullptr);
|
||||
m_ui->pushButtonStop->setEnabled(m_selectedSequence != nullptr);
|
||||
|
||||
m_pos = 0;
|
||||
m_ui->horizontalSlider->setValue(0);
|
||||
updateStatusLabel();
|
||||
}
|
||||
|
||||
void SequencerWidget::timeout()
|
||||
{
|
||||
if (m_selectedSequence && m_selectedSequence->pads)
|
||||
{
|
||||
for (const auto &pair : *m_selectedSequence->pads)
|
||||
{
|
||||
const auto iter = std::find_if(std::cbegin(pair.second), std::cend(pair.second), [&](const presets::SequencePad &sequencePad){
|
||||
return sequencePad.start && *sequencePad.start == m_pos;
|
||||
});
|
||||
|
||||
if (iter == std::cend(pair.second))
|
||||
continue;
|
||||
|
||||
//TODO: iter->duration;
|
||||
|
||||
bool ok;
|
||||
const auto index = pair.first.toInt(&ok);
|
||||
if (!ok)
|
||||
continue;
|
||||
|
||||
emit triggerSample(index);
|
||||
}
|
||||
}
|
||||
|
||||
m_pos++;
|
||||
|
||||
if (m_pos >= (m_selectedSequence && m_selectedSequence->sequencerSize ? *m_selectedSequence->sequencerSize-1 : -1))
|
||||
m_pos = 0;
|
||||
|
||||
m_ui->horizontalSlider->setValue(m_pos);
|
||||
|
||||
updateStatusLabel();
|
||||
}
|
||||
|
||||
void SequencerWidget::updateStatusLabel()
|
||||
{
|
||||
m_ui->labelStatus->setText(QString{"%0 / %1"}.arg(m_pos+1).arg(m_selectedSequence && m_selectedSequence->sequencerSize ? *m_selectedSequence->sequencerSize-1 : -1));
|
||||
}
|
47
widgets/sequencerwidget.h
Executable file
47
widgets/sequencerwidget.h
Executable file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
#include <QWidget>
|
||||
#include <QTimer>
|
||||
|
||||
namespace Ui { class SequencerWidget; }
|
||||
namespace presets { class Preset; class Sequence; }
|
||||
class QLabel;
|
||||
|
||||
class SequencerWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SequencerWidget(QWidget *parent = nullptr);
|
||||
~SequencerWidget() override;
|
||||
|
||||
void setPreset(const presets::Preset &preset);
|
||||
|
||||
signals:
|
||||
void triggerSample(int index);
|
||||
|
||||
private slots:
|
||||
void playPause();
|
||||
void stop();
|
||||
|
||||
void tempoChanged(int tempo);
|
||||
void sequenceSelected();
|
||||
void timeout();
|
||||
|
||||
void updateStatusLabel();
|
||||
|
||||
private:
|
||||
const std::unique_ptr<Ui::SequencerWidget> m_ui;
|
||||
|
||||
std::vector<presets::Sequence> m_sequences;
|
||||
const presets::Sequence *m_selectedSequence{};
|
||||
|
||||
QTimer m_timer;
|
||||
|
||||
int m_pos{};
|
||||
std::array<QLabel*, 24> m_sampleLabels;
|
||||
};
|
522
widgets/sequencerwidget.ui
Executable file
522
widgets/sequencerwidget.ui
Executable file
@@ -0,0 +1,522 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SequencerWidget</class>
|
||||
<widget class="QWidget" name="SequencerWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>912</width>
|
||||
<height>611</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,1">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0,0,1">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="spinBoxTempo">
|
||||
<property name="suffix">
|
||||
<string>BPM</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>999</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboBoxSequence"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonPlayPause">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>▶</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonStop">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>◼</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="labelStatus">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="horizontalSlider">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QScrollArea" name="scrollArea">
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>878</width>
|
||||
<height>921</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout" columnstretch="0,0,1">
|
||||
<item row="38" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="34" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="19" column="0" colspan="3">
|
||||
<widget class="QFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::HLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="3">
|
||||
<widget class="QFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::HLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="14" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="3">
|
||||
<widget class="QFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::HLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="15" column="0" colspan="3">
|
||||
<widget class="QFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::HLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="41" column="0" colspan="3">
|
||||
<widget class="QFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::HLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="44" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="47" column="0" colspan="3">
|
||||
<widget class="QFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::HLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="29" column="0" colspan="3">
|
||||
<widget class="QFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::HLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="32" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="27" column="0" colspan="3">
|
||||
<widget class="QFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::HLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="48" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="0" colspan="3">
|
||||
<widget class="QFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::HLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="28" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="18" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="16" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="0" colspan="3">
|
||||
<widget class="QFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::HLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="25" column="0" colspan="3">
|
||||
<widget class="QFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::HLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="40" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="30" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="26" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="43" column="0" colspan="3">
|
||||
<widget class="QFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::HLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="42" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="3">
|
||||
<widget class="QFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::HLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="39" column="0" colspan="3">
|
||||
<widget class="QFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::HLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="21" column="0" colspan="3">
|
||||
<widget class="QFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::HLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="31" column="0" colspan="3">
|
||||
<widget class="QFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::HLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="23" column="0" colspan="3">
|
||||
<widget class="QFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::HLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="37" column="0" colspan="3">
|
||||
<widget class="QFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::HLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="35" column="0" colspan="3">
|
||||
<widget class="QFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::HLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0" colspan="3">
|
||||
<widget class="QFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::HLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="20" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="17" column="0" colspan="3">
|
||||
<widget class="QFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::HLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="36" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" rowspan="49" colspan="3">
|
||||
<widget class="QFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::VLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="33" column="0" colspan="3">
|
||||
<widget class="QFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::HLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="22" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="3">
|
||||
<widget class="QFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::HLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="46" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="45" column="0" colspan="3">
|
||||
<widget class="QFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::HLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="24" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
46
widgets/synthisizerwidget.cpp
Normal file
46
widgets/synthisizerwidget.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#include "synthisizerwidget.h"
|
||||
#include "ui_synthisizerwidget.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "midicontainers.h"
|
||||
|
||||
SynthisizerWidget::SynthisizerWidget(QWidget *parent) :
|
||||
QWidget{parent},
|
||||
m_ui{std::make_unique<Ui::SynthisizerWidget>()}
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
connect(m_ui->horizontalSliderVolume, &QSlider::valueChanged,
|
||||
&m_synthisizer, [&synthisizer=m_synthisizer](int value){ synthisizer.setVolume(float(value) / 100.f); });
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (message.cmd == midi::Command::NoteOff || (message.cmd == midi::Command::NoteOn && message.velocity == 0))
|
||||
{
|
||||
if (m_synthisizer.frequency() == int16_t(440.*std::pow(std::pow(2., 1./12.), message.note-48)))
|
||||
m_synthisizer.setFrequency(0);
|
||||
}
|
||||
else if (message.cmd == midi::Command::NoteOn)
|
||||
m_synthisizer.setFrequency(440.*std::pow(std::pow(2., 1./12.), message.note-48));
|
||||
}
|
35
widgets/synthisizerwidget.h
Normal file
35
widgets/synthisizerwidget.h
Normal 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;
|
||||
};
|
67
widgets/synthisizerwidget.ui
Normal file
67
widgets/synthisizerwidget.ui
Normal file
@@ -0,0 +1,67 @@
|
||||
<?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 class="QSlider" name="horizontalSliderVolume">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>110</x>
|
||||
<y>110</y>
|
||||
<width>160</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="labelVolume">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>110</y>
|
||||
<width>55</width>
|
||||
<height>18</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Volume:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>horizontalSliderVolume</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
264
widgets/trackdeck.cpp
Normal file
264
widgets/trackdeck.cpp
Normal file
@@ -0,0 +1,264 @@
|
||||
#include "trackdeck.h"
|
||||
#include "ui_trackdeck.h"
|
||||
|
||||
#include <QAbstractEventDispatcher>
|
||||
#include <QDragEnterEvent>
|
||||
#include <QDragLeaveEvent>
|
||||
#include <QDropEvent>
|
||||
#include <QMimeData>
|
||||
#include <QTextStream>
|
||||
#include <QUrl>
|
||||
#include <QFileInfo>
|
||||
#include <QButtonGroup>
|
||||
#include <QDebug>
|
||||
|
||||
#include "audiodecoder.h"
|
||||
|
||||
TrackDeck::TrackDeck(QWidget *parent) :
|
||||
QWidget{parent},
|
||||
m_ui{std::make_unique<Ui::TrackDeck>()}
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
m_ui->progressBar->hide();
|
||||
|
||||
m_loopGroup.addButton(m_ui->pushButtonLoopOff, -1);
|
||||
m_loopGroup.addButton(m_ui->pushButtonLoop1, 1);
|
||||
m_loopGroup.addButton(m_ui->pushButtonLoop2, 2);
|
||||
m_loopGroup.addButton(m_ui->pushButtonLoop4, 4);
|
||||
m_loopGroup.addButton(m_ui->pushButtonLoop8, 8);
|
||||
connect(&m_loopGroup, &QButtonGroup::idClicked, [&player=m_player,&bpmInput=*m_ui->doubleSpinBoxBpm](int id){
|
||||
const auto position = player.position();
|
||||
|
||||
const auto bpm = bpmInput.value();
|
||||
const auto beatsPerSecond = bpm / 60.;
|
||||
const auto framesPerBeat = frameRate / beatsPerSecond;
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case 1: player.setLoop(std::make_pair(position, position + (framesPerBeat/4))); break;
|
||||
case 2: player.setLoop(std::make_pair(position, position + (framesPerBeat/2))); break;
|
||||
case 4: player.setLoop(std::make_pair(position, position + framesPerBeat)); break;
|
||||
case 8: player.setLoop(std::make_pair(position, position + (framesPerBeat*2))); break;
|
||||
default: player.setLoop({});
|
||||
}
|
||||
});
|
||||
|
||||
connect(m_ui->pushButtonBpm, &QAbstractButton::pressed, this, &TrackDeck::bpmTap);
|
||||
|
||||
connect(m_ui->pushButtonPlay, &QAbstractButton::pressed, &m_player, &AudioPlayer::togglePlaying);
|
||||
connect(m_ui->pushButtonStop, &QAbstractButton::pressed, &m_player, &AudioPlayer::stop);
|
||||
|
||||
connect(m_ui->sliderZoom, &QAbstractSlider::valueChanged, m_ui->scratchWidget, &ScratchWidget::setBeatWidth);
|
||||
connect(m_ui->sliderSpeed, &QAbstractSlider::valueChanged, this, &TrackDeck::speedChanged);
|
||||
connect(m_ui->sliderVolume, &QAbstractSlider::valueChanged, &m_player, [&player=m_player](int value){ player.setVolume(float(value)/100.f); });
|
||||
|
||||
connect(m_ui->previewWidget, &PreviewWidget::positionSelected, &m_player, &AudioPlayer::setPosition);
|
||||
connect(&m_player, &AudioPlayer::positionChanged, m_ui->previewWidget, &PreviewWidget::setPosition);
|
||||
connect(m_ui->scratchWidget, &ScratchWidget::scratchBegin, this, &TrackDeck::scratchBegin);
|
||||
connect(m_ui->scratchWidget, &ScratchWidget::scratchEnd, this, &TrackDeck::scratchEnd);
|
||||
connect(&m_player, &AudioPlayer::positionChanged, m_ui->scratchWidget, &ScratchWidget::setPosition);
|
||||
connect(&m_player, &AudioPlayer::playingChanged, this, &TrackDeck::updatePlayButtonText);
|
||||
|
||||
connect(m_ui->sliderSpeed, &QAbstractSlider::valueChanged, this, &TrackDeck::updatePlaybackBpm);
|
||||
connect(m_ui->doubleSpinBoxBpm, &QDoubleSpinBox::valueChanged, this, &TrackDeck::updatePlaybackBpm);
|
||||
|
||||
connect(&m_timer, &QTimer::timeout, this, &TrackDeck::timeout);
|
||||
m_timer.setSingleShot(true);
|
||||
m_timer.setInterval(1000);
|
||||
|
||||
updatePlaybackBpm();
|
||||
}
|
||||
|
||||
TrackDeck::~TrackDeck() = default;
|
||||
|
||||
void TrackDeck::loadTrack(const QString &filename)
|
||||
{
|
||||
m_filename = filename;
|
||||
emit startDecoding(filename);
|
||||
m_ui->progressBar->show();
|
||||
m_ui->progressBar->setValue(0);
|
||||
}
|
||||
|
||||
void TrackDeck::injectDecodingThread(QThread &thread)
|
||||
{
|
||||
QMetaObject::invokeMethod(QAbstractEventDispatcher::instance(&thread), [this](){
|
||||
m_decoder = std::make_unique<AudioDecoder>();
|
||||
connect(this, &TrackDeck::startDecoding, m_decoder.get(), &AudioDecoder::startDecodingFilename);
|
||||
connect(m_decoder.get(), &AudioDecoder::progress, this, &TrackDeck::decodingProgress);
|
||||
connect(m_decoder.get(), &AudioDecoder::decodingFinished, this, &TrackDeck::decodingFinished);
|
||||
if (!m_filename.isEmpty())
|
||||
m_decoder->startDecodingFilename(m_filename);
|
||||
});
|
||||
}
|
||||
|
||||
void TrackDeck::writeSamples(frame_t *begin, frame_t *end)
|
||||
{
|
||||
m_player.writeSamples(begin, end);
|
||||
}
|
||||
|
||||
void TrackDeck::dragEnterEvent(QDragEnterEvent *event)
|
||||
{
|
||||
if (!event->mimeData()->hasFormat("text/uri-list"))
|
||||
return;
|
||||
|
||||
auto data = event->mimeData()->data("text/uri-list");
|
||||
|
||||
QTextStream textStream(data, QIODevice::ReadOnly | QIODevice::Text);
|
||||
if (textStream.atEnd())
|
||||
return;
|
||||
|
||||
const QUrl url(textStream.readLine());
|
||||
if (!url.isLocalFile())
|
||||
return;
|
||||
|
||||
const QFileInfo fileInfo(url.toLocalFile());
|
||||
if (!fileInfo.exists())
|
||||
return;
|
||||
|
||||
if (!fileInfo.isFile())
|
||||
return;
|
||||
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
|
||||
void TrackDeck::dragLeaveEvent(QDragLeaveEvent *event)
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
}
|
||||
|
||||
void TrackDeck::dropEvent(QDropEvent *event)
|
||||
{
|
||||
if (!event->mimeData()->hasFormat("text/uri-list"))
|
||||
{
|
||||
qWarning() << "wrong type";
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = event->mimeData()->data("text/uri-list");
|
||||
|
||||
QTextStream textStream(data, QIODevice::ReadOnly | QIODevice::Text);
|
||||
if (textStream.atEnd())
|
||||
{
|
||||
qWarning() << "no lines";
|
||||
return;
|
||||
}
|
||||
|
||||
const QUrl url(textStream.readLine());
|
||||
if (!url.isLocalFile())
|
||||
{
|
||||
qWarning() << "isnt local file";
|
||||
return;
|
||||
}
|
||||
|
||||
const QFileInfo fileInfo(url.toLocalFile());
|
||||
if (!fileInfo.exists())
|
||||
{
|
||||
qWarning() << "doesnt exist";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fileInfo.isFile())
|
||||
{
|
||||
qWarning() << "isnt file";
|
||||
return;
|
||||
}
|
||||
|
||||
loadTrack(url.toLocalFile());
|
||||
}
|
||||
|
||||
void TrackDeck::decodingProgress(int progress, int total)
|
||||
{
|
||||
m_ui->progressBar->setMaximum(total);
|
||||
m_ui->progressBar->setValue(progress);
|
||||
}
|
||||
|
||||
void TrackDeck::decodingFinished(const QAudioBuffer &buffer)
|
||||
{
|
||||
m_player.setBuffer(buffer);
|
||||
|
||||
m_ui->labelTitle->setText(QFileInfo{m_filename}.fileName());
|
||||
|
||||
for (auto *btn : m_loopGroup.buttons())
|
||||
btn->setEnabled(true);
|
||||
|
||||
m_ui->previewWidget->setBuffer(buffer);
|
||||
m_ui->scratchWidget->setBuffer(buffer);
|
||||
m_ui->progressBar->hide();
|
||||
}
|
||||
|
||||
void TrackDeck::scratchBegin()
|
||||
{
|
||||
disconnect(m_ui->sliderSpeed, &QAbstractSlider::valueChanged, this, &TrackDeck::speedChanged);
|
||||
m_ui->sliderSpeed->setEnabled(false);
|
||||
connect(m_ui->scratchWidget, &ScratchWidget::scratchSpeed, &m_player, &AudioPlayer::setSpeed);
|
||||
disconnect(&m_player, &AudioPlayer::playingChanged, this, &TrackDeck::updatePlayButtonText);
|
||||
|
||||
m_playingBeforeScratch = m_player.playing();
|
||||
m_player.setPlaying(true);
|
||||
|
||||
m_speedBeforeScratch = m_player.speed();
|
||||
|
||||
m_stopOnEndBeforeScratch = m_player.stopOnEnd();
|
||||
m_player.setStopOnEnd(false);
|
||||
}
|
||||
|
||||
void TrackDeck::scratchEnd()
|
||||
{
|
||||
m_player.setPlaying(m_playingBeforeScratch);
|
||||
m_player.setSpeed(m_speedBeforeScratch);
|
||||
m_player.setStopOnEnd(m_stopOnEndBeforeScratch);
|
||||
|
||||
disconnect(m_ui->scratchWidget, &ScratchWidget::scratchSpeed, &m_player, &AudioPlayer::setSpeed);
|
||||
m_ui->sliderSpeed->setEnabled(true);
|
||||
connect(m_ui->sliderSpeed, &QAbstractSlider::valueChanged, this, &TrackDeck::speedChanged);
|
||||
connect(&m_player, &AudioPlayer::playingChanged, this, &TrackDeck::updatePlayButtonText);
|
||||
}
|
||||
|
||||
void TrackDeck::speedChanged(int value)
|
||||
{
|
||||
m_player.setSpeed(float(value)/100.f);
|
||||
}
|
||||
|
||||
void TrackDeck::updatePlayButtonText(bool playing)
|
||||
{
|
||||
m_ui->pushButtonBpm->setEnabled(playing);
|
||||
m_ui->pushButtonPlay->setText(playing ? tr("▮▮") : tr("▶"));
|
||||
}
|
||||
|
||||
void TrackDeck::bpmTap()
|
||||
{
|
||||
const auto position = m_player.position();
|
||||
if (m_bpmTap)
|
||||
{
|
||||
std::get<1>(*m_bpmTap) = position;
|
||||
std::get<2>(*m_bpmTap)++;
|
||||
const auto framesPerBeat = (std::get<1>(*m_bpmTap)-std::get<0>(*m_bpmTap))/std::get<2>(*m_bpmTap);
|
||||
m_ui->scratchWidget->setFramesPerBeat(framesPerBeat);
|
||||
const auto beatsPerSecond = frameRate/framesPerBeat;
|
||||
const auto bpm = 60.*beatsPerSecond;
|
||||
m_ui->doubleSpinBoxBpm->setValue(bpm);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_bpmTap = std::make_tuple(position, position, 0);
|
||||
}
|
||||
|
||||
m_ui->pushButtonBpm->setText(QString::number(std::get<2>(*m_bpmTap)));
|
||||
m_timer.start();
|
||||
}
|
||||
|
||||
void TrackDeck::timeout()
|
||||
{
|
||||
const auto framesPerBeat = (std::get<1>(*m_bpmTap)-std::get<0>(*m_bpmTap))/std::get<2>(*m_bpmTap);
|
||||
m_ui->scratchWidget->setFramesPerBeat(framesPerBeat);
|
||||
const auto beatsPerSecond = frameRate/framesPerBeat;
|
||||
const auto bpm = 60.*beatsPerSecond;
|
||||
m_ui->pushButtonBpm->setText(tr("BPM tap"));
|
||||
m_ui->doubleSpinBoxBpm->setValue(bpm);
|
||||
m_bpmTap = {};
|
||||
}
|
||||
|
||||
void TrackDeck::updatePlaybackBpm()
|
||||
{
|
||||
m_ui->labelPlaybackBpm->setText(QString::number(m_ui->doubleSpinBoxBpm->value() * m_ui->sliderSpeed->value() / 100., 'f', 2));
|
||||
}
|
69
widgets/trackdeck.h
Normal file
69
widgets/trackdeck.h
Normal file
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
|
||||
#include <QWidget>
|
||||
#include <QButtonGroup>
|
||||
#include <QTimer>
|
||||
|
||||
#include "audioformat.h"
|
||||
#include "audioplayer.h"
|
||||
|
||||
namespace Ui { class TrackDeck; }
|
||||
class AudioDecoder;
|
||||
class QAudioBuffer;
|
||||
|
||||
class TrackDeck : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TrackDeck(QWidget *parent = nullptr);
|
||||
~TrackDeck() override;
|
||||
|
||||
void loadTrack(const QString &filename);
|
||||
|
||||
void injectDecodingThread(QThread &thread);
|
||||
|
||||
void writeSamples(frame_t *begin, frame_t *end);
|
||||
|
||||
signals:
|
||||
void startDecoding(const QString &filename);
|
||||
|
||||
protected:
|
||||
void dragEnterEvent(QDragEnterEvent *event) override;
|
||||
void dragLeaveEvent(QDragLeaveEvent *event) override;
|
||||
void dropEvent(QDropEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void decodingProgress(int progress, int total);
|
||||
void decodingFinished(const QAudioBuffer &buffer);
|
||||
void scratchBegin();
|
||||
void scratchEnd();
|
||||
void speedChanged(int value);
|
||||
void updatePlayButtonText(bool playing);
|
||||
void bpmTap();
|
||||
void timeout();
|
||||
void updatePlaybackBpm();
|
||||
|
||||
private:
|
||||
const std::unique_ptr<Ui::TrackDeck> m_ui;
|
||||
|
||||
QButtonGroup m_loopGroup;
|
||||
|
||||
std::unique_ptr<AudioDecoder> m_decoder;
|
||||
|
||||
AudioPlayer m_player;
|
||||
|
||||
QString m_filename;
|
||||
|
||||
bool m_playingBeforeScratch;
|
||||
float m_speedBeforeScratch;
|
||||
bool m_stopOnEndBeforeScratch;
|
||||
|
||||
QTimer m_timer;
|
||||
|
||||
std::optional<std::tuple<double, double, int>> m_bpmTap;
|
||||
};
|
414
widgets/trackdeck.ui
Normal file
414
widgets/trackdeck.ui
Normal file
@@ -0,0 +1,414 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TrackDeck</class>
|
||||
<widget class="QWidget" name="TrackDeck">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>567</width>
|
||||
<height>283</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="acceptDrops">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,0">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,0,0">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5" stretch="1,0">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelTitle"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progressBar">
|
||||
<property name="value">
|
||||
<number>24</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonBpm">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>BPM tap</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="doubleSpinBoxBpm">
|
||||
<property name="suffix">
|
||||
<string>BPM</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>80.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>250.000000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>130.000000000000000</double>
|
||||
</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="QSlider" name="sliderZoom">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>25</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>200</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="tickPosition">
|
||||
<enum>QSlider::TicksBothSides</enum>
|
||||
</property>
|
||||
<property name="tickInterval">
|
||||
<number>50</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonPlay">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>▶</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonStop">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>◼</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="1,0">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,1,0">
|
||||
<item>
|
||||
<widget class="PreviewWidget" name="previewWidget" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ScratchWidget" name="scratchWidget" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelLoop">
|
||||
<property name="text">
|
||||
<string>Loop:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonLoopOff">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>-</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonLoop1">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>1</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonLoop2">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>2</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonLoop4">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>4</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonLoop8">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>8</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3" stretch="0,1">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelPlaybackBpm">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>8</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>100.00</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4" stretch="0,1,0">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="sliderSpeed">
|
||||
<property name="minimum">
|
||||
<number>80</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>120</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="tickPosition">
|
||||
<enum>QSlider::TicksBothSides</enum>
|
||||
</property>
|
||||
<property name="tickInterval">
|
||||
<number>10</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="sliderVolume">
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="tickPosition">
|
||||
<enum>QSlider::TicksBothSides</enum>
|
||||
</property>
|
||||
<property name="tickInterval">
|
||||
<number>25</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>PreviewWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>widgets/previewwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ScratchWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>widgets/scratchwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
Reference in New Issue
Block a user