Imported existing sources
This commit is contained in:
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "rtmidi"]
|
||||
path = rtmidi
|
||||
url = git@github.com:thestk/rtmidi.git
|
53
DrumMachine.pro
Executable file
53
DrumMachine.pro
Executable file
@ -0,0 +1,53 @@
|
||||
QT = core multimedia gui widgets network
|
||||
|
||||
CONFIG += c++17
|
||||
|
||||
release: QMAKE_CXXFLAGS_RELEASE -= -O1
|
||||
release: QMAKE_CXXFLAGS_RELEASE -= -O2
|
||||
release: QMAKE_CXXFLAGS_RELEASE += -O3 -ffast-math -march=native -mtune=native
|
||||
|
||||
win32: {
|
||||
DEFINES += __WINDOWS_MM__
|
||||
LIBS += -lwinmm
|
||||
}
|
||||
|
||||
DEFINES += QT_DEPRECATED_WARNINGS QT_DISABLE_DEPRECATED_BEFORE=0x060000
|
||||
|
||||
SOURCES += \
|
||||
filesmodel.cpp \
|
||||
jsonconverters.cpp \
|
||||
main.cpp \
|
||||
mainwindow.cpp \
|
||||
midicontainers.cpp \
|
||||
midiinwrapper.cpp \
|
||||
presetdetailwidget.cpp \
|
||||
presets.cpp \
|
||||
presetsmodel.cpp \
|
||||
rtmidi/RtMidi.cpp \
|
||||
sampleswidget.cpp \
|
||||
samplewidget.cpp \
|
||||
sequencerwidget.cpp
|
||||
|
||||
HEADERS += \
|
||||
filesmodel.h \
|
||||
jsonconverters.h \
|
||||
mainwindow.h \
|
||||
midicontainers.h \
|
||||
midiinwrapper.h \
|
||||
presetdetailwidget.h \
|
||||
presets.h \
|
||||
presetsmodel.h \
|
||||
rtmidi/RtMidi.h \
|
||||
sampleswidget.h \
|
||||
samplewidget.h \
|
||||
sequencerwidget.h
|
||||
|
||||
FORMS += \
|
||||
mainwindow.ui \
|
||||
presetdetailwidget.ui \
|
||||
sampleswidget.ui \
|
||||
samplewidget.ui \
|
||||
sequencerwidget.ui
|
||||
|
||||
RESOURCES += \
|
||||
resources.qrc
|
129
filesmodel.cpp
Executable file
129
filesmodel.cpp
Executable file
@ -0,0 +1,129 @@
|
||||
#include "filesmodel.h"
|
||||
|
||||
#include <iterator>
|
||||
|
||||
#include <QFont>
|
||||
#include <QColor>
|
||||
|
||||
enum {
|
||||
ColumnFilename,
|
||||
ColumnColor,
|
||||
ColumnStopOnRelease,
|
||||
ColumnLooped,
|
||||
ColumnChoke,
|
||||
NumberOfColumns
|
||||
};
|
||||
|
||||
FilesModel::~FilesModel() = default;
|
||||
|
||||
const presets::File &FilesModel::getFile(const QModelIndex &index) const
|
||||
{
|
||||
return getFile(index.row());
|
||||
}
|
||||
|
||||
const presets::File &FilesModel::getFile(int row) const
|
||||
{
|
||||
return m_files->at(row);
|
||||
}
|
||||
|
||||
void FilesModel::setPreset(const presets::Preset &preset)
|
||||
{
|
||||
beginResetModel();
|
||||
m_files = preset.files;
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
int FilesModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
|
||||
if (!m_files)
|
||||
return 0;
|
||||
|
||||
return std::size(*m_files);
|
||||
}
|
||||
|
||||
int FilesModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
|
||||
return NumberOfColumns;
|
||||
}
|
||||
|
||||
QVariant FilesModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::FontRole && role != Qt::ForegroundRole)
|
||||
return {};
|
||||
|
||||
if (!m_files)
|
||||
return {};
|
||||
if (index.column() < 0)
|
||||
return {};
|
||||
if (index.column() >= NumberOfColumns)
|
||||
return {};
|
||||
if (index.row() < 0)
|
||||
return {};
|
||||
if (index.row() >= std::size(*m_files))
|
||||
return {};
|
||||
|
||||
const auto &file = getFile(index);
|
||||
|
||||
const auto handleData = [&](const auto &val) -> QVariant
|
||||
{
|
||||
if (!val)
|
||||
{
|
||||
if (role == Qt::DisplayRole)
|
||||
return this->tr("(null)");
|
||||
else if (role == Qt::FontRole)
|
||||
{
|
||||
QFont font;
|
||||
font.setItalic(true);
|
||||
return font;
|
||||
}
|
||||
else if (role == Qt::ForegroundRole)
|
||||
return QColor{Qt::gray};
|
||||
return {};
|
||||
}
|
||||
|
||||
if (role == Qt::DisplayRole || role == Qt::EditRole)
|
||||
return *val;
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
switch (index.column())
|
||||
{
|
||||
case ColumnFilename: return handleData(file.filename);
|
||||
case ColumnColor: return handleData(file.color);
|
||||
case ColumnStopOnRelease: return handleData(file.stopOnRelease);
|
||||
case ColumnLooped: return handleData(file.looped);
|
||||
case ColumnChoke: return handleData(file.choke);
|
||||
}
|
||||
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
QVariant FilesModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if (role != Qt::DisplayRole && role != Qt::EditRole)
|
||||
return {};
|
||||
|
||||
if (orientation != Qt::Horizontal)
|
||||
return {};
|
||||
|
||||
if (section < 0)
|
||||
return {};
|
||||
if (section >= NumberOfColumns)
|
||||
return {};
|
||||
|
||||
switch (section)
|
||||
{
|
||||
case ColumnFilename: return tr("filename");
|
||||
case ColumnColor: return tr("color");
|
||||
case ColumnStopOnRelease: return tr("stopOnRelease");
|
||||
case ColumnLooped: return tr("looped");
|
||||
case ColumnChoke: return tr("choke");
|
||||
}
|
||||
|
||||
Q_UNREACHABLE();
|
||||
}
|
31
filesmodel.h
Executable file
31
filesmodel.h
Executable file
@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
|
||||
#include "presets.h"
|
||||
|
||||
namespace presets { class Preset; class File; }
|
||||
|
||||
class FilesModel : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using QAbstractTableModel::QAbstractTableModel;
|
||||
~FilesModel() override;
|
||||
|
||||
const presets::File &getFile(const QModelIndex &index) const;
|
||||
const presets::File &getFile(int row) const;
|
||||
|
||||
void setPreset(const presets::Preset &preset);
|
||||
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
int columnCount(const QModelIndex &parent) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||
|
||||
private:
|
||||
std::optional<std::array<presets::File, 24>> m_files;
|
||||
};
|
396
jsonconverters.cpp
Executable file
396
jsonconverters.cpp
Executable file
@ -0,0 +1,396 @@
|
||||
#include "jsonconverters.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <QJsonParseError>
|
||||
|
||||
namespace json_converters
|
||||
{
|
||||
QJsonObject loadJson(const QByteArray &buffer)
|
||||
{
|
||||
QJsonParseError error;
|
||||
QJsonDocument document{QJsonDocument::fromJson(buffer, &error)};
|
||||
if (error.error != QJsonParseError::NoError)
|
||||
throw std::runtime_error{QString{"Could not parse JSON because %0"}.arg(error.errorString()).toStdString()};
|
||||
|
||||
if (!document.isObject())
|
||||
throw std::runtime_error{"JSON is not an object"};
|
||||
|
||||
return document.object();
|
||||
}
|
||||
|
||||
QString parseString(const QJsonValue &jsonValue)
|
||||
{
|
||||
if (!jsonValue.isString())
|
||||
throw std::runtime_error{"json value for string is not a string"};
|
||||
|
||||
return jsonValue.toString();
|
||||
}
|
||||
|
||||
std::vector<QString> parseStringVector(const QJsonValue &jsonValue)
|
||||
{
|
||||
if (!jsonValue.isArray())
|
||||
throw std::runtime_error{"json value for string vector is not an array"};
|
||||
|
||||
std::vector<QString> vector;
|
||||
|
||||
for (const auto &jsonValue : jsonValue.toArray())
|
||||
vector.emplace_back(parseString(jsonValue));
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
||||
int parseInt(const QJsonValue &jsonValue)
|
||||
{
|
||||
if (!jsonValue.isDouble())
|
||||
throw std::runtime_error{"json value for int is not a double"};
|
||||
|
||||
return jsonValue.toDouble();
|
||||
}
|
||||
|
||||
bool parseBool(const QJsonValue &jsonValue)
|
||||
{
|
||||
if (!jsonValue.isBool())
|
||||
throw std::runtime_error{"json value for bool is not a bool"};
|
||||
|
||||
return jsonValue.toBool();
|
||||
}
|
||||
|
||||
std::vector<int> parseIntVector(const QJsonValue &jsonValue)
|
||||
{
|
||||
if (!jsonValue.isArray())
|
||||
throw std::runtime_error{"json value for int vector is not an array"};
|
||||
|
||||
std::vector<int> vector;
|
||||
|
||||
for (const auto &jsonValue : jsonValue.toArray())
|
||||
vector.emplace_back(parseInt(jsonValue));
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
||||
presets::PresetsConfig parsePresetsConfig(const QJsonObject &jsonObj)
|
||||
{
|
||||
presets::PresetsConfig presetConfig;
|
||||
|
||||
for (auto iter = std::cbegin(jsonObj); iter != std::cend(jsonObj); iter++)
|
||||
{
|
||||
if (iter.key() == "categories")
|
||||
presetConfig.categories = parseCategoryVector(iter.value());
|
||||
else if (iter.key() == "presets")
|
||||
presetConfig.presets = parsePresetMap(iter.value());
|
||||
else
|
||||
throw std::runtime_error{QString{"unknown key %0 for PresetConfig"}.arg(iter.key()).toStdString()};
|
||||
}
|
||||
|
||||
return presetConfig;
|
||||
}
|
||||
|
||||
std::vector<presets::Category> parseCategoryVector(const QJsonValue &jsonValue)
|
||||
{
|
||||
if (!jsonValue.isArray())
|
||||
throw std::runtime_error{"json value for vector of Category is not an array"};
|
||||
|
||||
std::vector<presets::Category> vector;
|
||||
|
||||
for (const auto &jsonValue : jsonValue.toArray())
|
||||
vector.emplace_back(parseCategory(jsonValue));
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
||||
std::map<QString, presets::Preset> parsePresetMap(const QJsonValue &jsonValue)
|
||||
{
|
||||
if (!jsonValue.isObject())
|
||||
throw std::runtime_error{"json value for Preset map is not an object"};
|
||||
|
||||
const auto jsonObj = jsonValue.toObject();
|
||||
|
||||
std::map<QString, presets::Preset> map;
|
||||
|
||||
for (auto iter = std::cbegin(jsonObj); iter != std::cend(jsonObj); iter++)
|
||||
map[iter.key()] = parsePreset(iter.value());
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
presets::Category parseCategory(const QJsonValue &jsonValue)
|
||||
{
|
||||
if (!jsonValue.isObject())
|
||||
throw std::runtime_error{"json value for Category is not an object"};
|
||||
|
||||
const auto jsonObj = jsonValue.toObject();
|
||||
|
||||
presets::Category category;
|
||||
|
||||
for (auto iter = std::cbegin(jsonObj); iter != std::cend(jsonObj); iter++)
|
||||
{
|
||||
if (iter.key() == "title")
|
||||
category.title = parseString(iter.value());
|
||||
else if (iter.key() == "filter")
|
||||
category.filter = parseFilter(iter.value());
|
||||
else
|
||||
throw std::runtime_error{QString{"unknown key %0 for Category"}.arg(iter.key()).toStdString()};
|
||||
}
|
||||
|
||||
return category;
|
||||
}
|
||||
|
||||
presets::Filter parseFilter(const QJsonValue &jsonValue)
|
||||
{
|
||||
if (!jsonValue.isObject())
|
||||
throw std::runtime_error{"json value for Filters is not an object"};
|
||||
|
||||
const auto jsonObj = jsonValue.toObject();
|
||||
|
||||
presets::Filter filters;
|
||||
|
||||
for (auto iter = std::cbegin(jsonObj); iter != std::cend(jsonObj); iter++)
|
||||
{
|
||||
if (iter.key() == "tags")
|
||||
filters.tags = parseStringVector(iter.value());
|
||||
else
|
||||
throw std::runtime_error{QString{"unknown key %0 for Filters"}.arg(iter.key()).toStdString()};
|
||||
}
|
||||
|
||||
return filters;
|
||||
}
|
||||
|
||||
presets::Preset parsePreset(const QJsonValue &jsonValue)
|
||||
{
|
||||
if (!jsonValue.isObject())
|
||||
throw std::runtime_error{"json value for Preset is not an object"};
|
||||
|
||||
const auto jsonObj = jsonValue.toObject();
|
||||
|
||||
presets::Preset preset;
|
||||
|
||||
for (auto iter = std::cbegin(jsonObj); iter != std::cend(jsonObj); iter++)
|
||||
{
|
||||
const auto key = iter.key();
|
||||
if (key == "id")
|
||||
preset.id = parseString(iter.value());
|
||||
else if (key == "name")
|
||||
preset.name = parseString(iter.value());
|
||||
else if (key == "author")
|
||||
preset.author = parseString(iter.value());
|
||||
else if (key == "orderBy")
|
||||
preset.orderBy = parseString(iter.value());
|
||||
else if (key == "version")
|
||||
preset.version = parseString(iter.value());
|
||||
else if (key == "tempo")
|
||||
preset.tempo = parseInt(iter.value());
|
||||
else if (key == "icon")
|
||||
preset.icon = parseString(iter.value());
|
||||
else if (key == "price")
|
||||
preset.price = parseInt(iter.value());
|
||||
else if (key == "priceForSession")
|
||||
preset.priceForSession = parseInt(iter.value());
|
||||
else if (key == "hasInfo")
|
||||
preset.hasInfo = parseBool(iter.value());
|
||||
else if (key == "tags")
|
||||
preset.tags = parseStringVector(iter.value());
|
||||
else if (key == "DELETED")
|
||||
preset.DELETED = parseBool(iter.value());
|
||||
else if (key == "difficulty")
|
||||
preset.difficulty = parseInt(iter.value());
|
||||
else if (key == "sample")
|
||||
preset.sample = parseInt(iter.value());
|
||||
else if (key == "audioPreview1Name")
|
||||
preset.audioPreview1Name = parseString(iter.value());
|
||||
else if (key == "audioPreview1URL")
|
||||
preset.audioPreview1URL = parseString(iter.value());
|
||||
else if (key == "audioPreview2Name")
|
||||
preset.audioPreview2Name = parseString(iter.value());
|
||||
else if (key == "audioPreview2URL")
|
||||
preset.audioPreview2URL = parseString(iter.value());
|
||||
else if (key == "imagePreview1")
|
||||
preset.imagePreview1 = parseString(iter.value());
|
||||
else if (key == "videoPreview")
|
||||
preset.videoPreview = parseString(iter.value());
|
||||
else if (key == "videoTutorial")
|
||||
preset.videoTutorial = parseString(iter.value());
|
||||
else if (key == "files")
|
||||
preset.files = parseFileArray(iter.value());
|
||||
else if (key == "beatSchool")
|
||||
preset.beatSchool = parseSequenceVectorMap(iter.value());
|
||||
else if (key == "easyPlay")
|
||||
preset.easyPlay = parseSequenceVectorMap(iter.value());
|
||||
else if (key == "middleDescription")
|
||||
{}
|
||||
else
|
||||
throw std::runtime_error{QString{"unknown key %0 for Preset"}.arg(key).toStdString()};
|
||||
}
|
||||
|
||||
return preset;
|
||||
}
|
||||
|
||||
std::array<presets::File, 24> parseFileArray(const QJsonValue &jsonValue)
|
||||
{
|
||||
if (!jsonValue.isObject())
|
||||
throw std::runtime_error{"json value for File array is not an object"};
|
||||
|
||||
const auto jsonObj = jsonValue.toObject();
|
||||
|
||||
if (jsonObj.size() != 24)
|
||||
throw std::runtime_error{"json value for File array doesn't have exactly 24 entries"};
|
||||
|
||||
std::array<presets::File, 24> array;
|
||||
|
||||
for (auto iter = std::cbegin(jsonObj); iter != std::cend(jsonObj); iter++)
|
||||
{
|
||||
bool ok;
|
||||
const auto index = iter.key().toInt(&ok);
|
||||
|
||||
if (!ok || index < 1 || index >= 25)
|
||||
throw std::runtime_error{QString{"unknown key %0 for File"}.arg(iter.key()).toStdString()};
|
||||
|
||||
array[index - 1] = parseFile(iter.value());
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
presets::File parseFile(const QJsonValue &jsonValue)
|
||||
{
|
||||
if (!jsonValue.isObject())
|
||||
throw std::runtime_error{"json value for File is not an object"};
|
||||
|
||||
const auto jsonObj = jsonValue.toObject();
|
||||
|
||||
presets::File file;
|
||||
|
||||
for (auto iter = std::cbegin(jsonObj); iter != std::cend(jsonObj); iter++)
|
||||
{
|
||||
const auto key = iter.key();
|
||||
if (key == "filename")
|
||||
file.filename = parseString(iter.value());
|
||||
else if (key == "color")
|
||||
file.color = parseString(iter.value());
|
||||
else if (key == "stopOnRelease")
|
||||
file.stopOnRelease = parseString(iter.value());
|
||||
else if (key == "looped")
|
||||
file.looped = parseBool(iter.value());
|
||||
else if (key == "choke")
|
||||
file.choke = parseInt(iter.value());
|
||||
else
|
||||
throw std::runtime_error{QString{"unknown key %0 for File"}.arg(key).toStdString()};
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
std::vector<presets::Sequence> parseSequenceVector(const QJsonValue &jsonValue)
|
||||
{
|
||||
if (!jsonValue.isArray())
|
||||
throw std::runtime_error{"json value for vector of Sequence is not an array"};
|
||||
|
||||
std::vector<presets::Sequence> vector;
|
||||
|
||||
for (const auto &jsonValue : jsonValue.toArray())
|
||||
vector.emplace_back(parseSequence(jsonValue));
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
||||
presets::Sequence parseSequence(const QJsonValue &jsonValue)
|
||||
{
|
||||
if (!jsonValue.isObject())
|
||||
throw std::runtime_error{"json value for File is not an object"};
|
||||
|
||||
const auto jsonObj = jsonValue.toObject();
|
||||
|
||||
presets::Sequence sequence;
|
||||
|
||||
for (auto iter = std::cbegin(jsonObj); iter != std::cend(jsonObj); iter++)
|
||||
{
|
||||
const auto key = iter.key();
|
||||
if (key == "name")
|
||||
sequence.name = parseString(iter.value());
|
||||
else if (key == "id")
|
||||
sequence.id = parseInt(iter.value());
|
||||
else if (key == "version")
|
||||
sequence.version = parseInt(iter.value());
|
||||
else if (key == "orderBy")
|
||||
sequence.orderBy = parseInt(iter.value());
|
||||
else if (key == "sequencerSize")
|
||||
sequence.sequencerSize = parseInt(iter.value());
|
||||
else if (key == "pads")
|
||||
sequence.pads = parseSequencePadVectorMap(iter.value());
|
||||
else if (key == "embientPads")
|
||||
sequence.embientPads = parseIntVector(iter.value());
|
||||
else
|
||||
throw std::runtime_error{QString{"unknown key %0 for Sequence"}.arg(key).toStdString()};
|
||||
}
|
||||
|
||||
return sequence;
|
||||
}
|
||||
|
||||
std::map<QString, std::vector<presets::Sequence>> parseSequenceVectorMap(const QJsonValue &jsonValue)
|
||||
{
|
||||
if (!jsonValue.isObject())
|
||||
throw std::runtime_error{"json value for Sequence vector map is not an object"};
|
||||
|
||||
const auto jsonObj = jsonValue.toObject();
|
||||
|
||||
std::map<QString, std::vector<presets::Sequence>> map;
|
||||
|
||||
for (auto iter = std::cbegin(jsonObj); iter != std::cend(jsonObj); iter++)
|
||||
map[iter.key()] = parseSequenceVector(iter.value());
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
presets::SequencePad parseSequencePad(const QJsonValue &jsonValue)
|
||||
{
|
||||
if (!jsonValue.isObject())
|
||||
throw std::runtime_error{"json value for File is not an object"};
|
||||
|
||||
const auto jsonObj = jsonValue.toObject();
|
||||
|
||||
presets::SequencePad sequencePad;
|
||||
|
||||
for (auto iter = std::cbegin(jsonObj); iter != std::cend(jsonObj); iter++)
|
||||
{
|
||||
if (iter.key() == "start")
|
||||
sequencePad.start = parseInt(iter.value());
|
||||
else if (iter.key() == "duration")
|
||||
sequencePad.duration = parseInt(iter.value());
|
||||
else if (iter.key() == "embient")
|
||||
sequencePad.embient = parseBool(iter.value());
|
||||
else
|
||||
throw std::runtime_error{QString{"unknown key %0 for Sequence"}.arg(iter.key()).toStdString()};
|
||||
}
|
||||
|
||||
return sequencePad;
|
||||
}
|
||||
|
||||
std::vector<presets::SequencePad> parseSequencePadVector(const QJsonValue &jsonValue)
|
||||
{
|
||||
if (!jsonValue.isArray())
|
||||
throw std::runtime_error{"json value for vector of SequencePad is not an array"};
|
||||
|
||||
std::vector<presets::SequencePad> vector;
|
||||
|
||||
for (const auto &jsonValue : jsonValue.toArray())
|
||||
vector.emplace_back(parseSequencePad(jsonValue));
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
||||
std::map<QString, std::vector<presets::SequencePad>> parseSequencePadVectorMap(const QJsonValue &jsonValue)
|
||||
{
|
||||
if (!jsonValue.isObject())
|
||||
throw std::runtime_error{"json value for SequencePad vector map is not an object"};
|
||||
|
||||
const auto jsonObj = jsonValue.toObject();
|
||||
|
||||
std::map<QString, std::vector<presets::SequencePad>> map;
|
||||
|
||||
for (auto iter = std::cbegin(jsonObj); iter != std::cend(jsonObj); iter++)
|
||||
map[iter.key()] = parseSequencePadVector(iter.value());
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
33
jsonconverters.h
Executable file
33
jsonconverters.h
Executable file
@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <QJsonValue>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "presets.h"
|
||||
|
||||
namespace json_converters
|
||||
{
|
||||
QJsonObject loadJson(const QByteArray &buffer);
|
||||
|
||||
QString parseString(const QJsonValue &jsonValue);
|
||||
std::vector<QString> parseStringVector(const QJsonValue &jsonValue);
|
||||
int parseInt(const QJsonValue &jsonValue);
|
||||
bool parseBool(const QJsonValue &jsonValue);
|
||||
std::vector<int> parseIntVector(const QJsonValue &jsonValue);
|
||||
|
||||
presets::PresetsConfig parsePresetsConfig(const QJsonObject &jsonObj);
|
||||
std::vector<presets::Category> parseCategoryVector(const QJsonValue &jsonValue);
|
||||
std::map<QString, presets::Preset> parsePresetMap(const QJsonValue &jsonValue);
|
||||
presets::Category parseCategory(const QJsonValue &jsonValue);
|
||||
presets::Filter parseFilter(const QJsonValue &jsonValue);
|
||||
presets::Preset parsePreset(const QJsonValue &jsonValue);
|
||||
std::array<presets::File, 24> parseFileArray(const QJsonValue &jsonValue);
|
||||
presets::File parseFile(const QJsonValue &jsonValue);
|
||||
std::vector<presets::Sequence> parseSequenceVector(const QJsonValue &jsonValue);
|
||||
presets::Sequence parseSequence(const QJsonValue &jsonValue);
|
||||
std::map<QString, std::vector<presets::Sequence>> parseSequenceVectorMap(const QJsonValue &jsonValue);
|
||||
presets::SequencePad parseSequencePad(const QJsonValue &jsonValue);
|
||||
std::vector<presets::SequencePad> parseSequencePadVector(const QJsonValue &jsonValue);
|
||||
std::map<QString, std::vector<presets::SequencePad>> parseSequencePadVectorMap(const QJsonValue &jsonValue);
|
||||
}
|
85
main.cpp
Executable file
85
main.cpp
Executable file
@ -0,0 +1,85 @@
|
||||
#include <QApplication>
|
||||
#include <QSslSocket>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QEventLoop>
|
||||
#include <QSplashScreen>
|
||||
#include <QMessageBox>
|
||||
#include <QDebug>
|
||||
|
||||
#include "jsonconverters.h"
|
||||
#include "mainwindow.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
QCoreApplication::setOrganizationDomain("brunner.ninja");
|
||||
QCoreApplication::setOrganizationName("brunner.ninja");
|
||||
QCoreApplication::setApplicationName("miditest");
|
||||
QCoreApplication::setApplicationVersion("1.0");
|
||||
|
||||
qDebug() << "supportsSsl" << QSslSocket::supportsSsl();
|
||||
qDebug() << "sslLibraryVersionString" << QSslSocket::sslLibraryVersionString();
|
||||
qDebug() << "sslLibraryBuildVersionString" << QSslSocket::sslLibraryBuildVersionString();
|
||||
|
||||
qSetMessagePattern("%{time dd.MM.yyyy HH:mm:ss.zzz} "
|
||||
"["
|
||||
"%{if-debug}D%{endif}"
|
||||
"%{if-info}I%{endif}"
|
||||
"%{if-warning}W%{endif}"
|
||||
"%{if-critical}C%{endif}"
|
||||
"%{if-fatal}F%{endif}"
|
||||
"] "
|
||||
"%{function}(): "
|
||||
"%{message}");
|
||||
|
||||
presets::PresetsConfig presetsConfig;
|
||||
|
||||
{
|
||||
QSplashScreen splashScreen{QPixmap{":/drummachine/splashscreen.png"}};
|
||||
splashScreen.showMessage(QCoreApplication::translate("main", "Loading list of presets..."));
|
||||
splashScreen.show();
|
||||
|
||||
QEventLoop eventLoop;
|
||||
|
||||
QNetworkAccessManager manager;
|
||||
const auto reply = std::unique_ptr<QNetworkReply>(manager.get(QNetworkRequest{QUrl{"https://brunner.ninja/komposthaufen/dpm/presets_config_v12.json"}}));
|
||||
QObject::connect(reply.get(), &QNetworkReply::finished, &eventLoop, &QEventLoop::quit);
|
||||
eventLoop.exec();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError)
|
||||
{
|
||||
QMessageBox::warning(nullptr, QCoreApplication::translate("main", "Could not load presets!"), QCoreApplication::translate("main", "Could not load presets!") + "\n\n" + reply->errorString());
|
||||
return 1;
|
||||
}
|
||||
|
||||
presetsConfig = json_converters::parsePresetsConfig(json_converters::loadJson(reply->readAll()));
|
||||
}
|
||||
|
||||
#if !defined(Q_OS_WIN)
|
||||
QPalette darkPalette;
|
||||
darkPalette.setColor(QPalette::Window, QColor(53,53,53));
|
||||
darkPalette.setColor(QPalette::WindowText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Base, QColor(25,25,25));
|
||||
darkPalette.setColor(QPalette::AlternateBase, QColor(53,53,53));
|
||||
darkPalette.setColor(QPalette::ToolTipBase, Qt::white);
|
||||
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Text, Qt::white);
|
||||
darkPalette.setColor(QPalette::Button, QColor(53,53,53));
|
||||
darkPalette.setColor(QPalette::ButtonText, Qt::white);
|
||||
darkPalette.setColor(QPalette::BrightText, Qt::red);
|
||||
darkPalette.setColor(QPalette::Link, QColor(42, 130, 218));
|
||||
darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218));
|
||||
darkPalette.setColor(QPalette::HighlightedText, Qt::black);
|
||||
app.setPalette(darkPalette);
|
||||
|
||||
app.setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
|
||||
#endif
|
||||
|
||||
MainWindow mainWindow{presetsConfig};
|
||||
mainWindow.showMaximized();
|
||||
mainWindow.selectFirstPreset();
|
||||
|
||||
return app.exec();
|
||||
}
|
123
mainwindow.cpp
Executable file
123
mainwindow.cpp
Executable file
@ -0,0 +1,123 @@
|
||||
#include "mainwindow.h"
|
||||
#include "ui_mainwindow.h"
|
||||
|
||||
#include <QMetaEnum>
|
||||
#include <QMessageBox>
|
||||
#include <QTimer>
|
||||
#include <QAbstractEventDispatcher>
|
||||
#include <QAudioDeviceInfo>
|
||||
|
||||
#include "presets.h"
|
||||
#include "midiinwrapper.h"
|
||||
#include "midicontainers.h"
|
||||
|
||||
MainWindow::MainWindow(const presets::PresetsConfig &presetsConfig, QWidget *parent) :
|
||||
QMainWindow{parent},
|
||||
m_ui{std::make_unique<Ui::MainWindow>()},
|
||||
m_presetsModel{*presetsConfig.presets}
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
connect(&m_midiIn, &MidiInWrapper::messageReceived, this, &MainWindow::messageReceived);
|
||||
|
||||
updateMidiDevices();
|
||||
|
||||
connect(m_ui->pushButtonMidiController, &QAbstractButton::pressed, this, [this](){
|
||||
if (m_midiIn.isPortOpen())
|
||||
m_midiIn.closePort();
|
||||
else
|
||||
{
|
||||
const auto index = m_ui->comboBoxMidiController->currentIndex();
|
||||
if (index != -1)
|
||||
m_midiIn.openPort(index);
|
||||
}
|
||||
|
||||
m_ui->pushButtonMidiController->setText(m_midiIn.isPortOpen() ? tr("Close") : tr("Open"));
|
||||
});
|
||||
|
||||
updateAudioDevices();
|
||||
|
||||
{
|
||||
const auto index = m_devices.indexOf(QAudioDeviceInfo::defaultOutputDevice());
|
||||
if (index != -1)
|
||||
m_ui->comboBoxAudioDevice->setCurrentIndex(index);
|
||||
}
|
||||
|
||||
{
|
||||
const auto callback = [this](int index){
|
||||
m_ui->samplesWidget->setAudioDevice(m_devices.at(index));
|
||||
};
|
||||
connect(m_ui->comboBoxAudioDevice, qOverload<int>(&QComboBox::currentIndexChanged), m_ui->samplesWidget, callback);
|
||||
callback(m_ui->comboBoxAudioDevice->currentIndex());
|
||||
}
|
||||
|
||||
m_presetsProxyModel.setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
m_presetsProxyModel.setSourceModel(&m_presetsModel);
|
||||
m_ui->presetsView->setModel(&m_presetsProxyModel);
|
||||
|
||||
m_presetsProxyModel.setFilterKeyColumn(1);
|
||||
|
||||
connect(m_ui->lineEdit, &QLineEdit::textChanged, this, [=](){
|
||||
m_presetsProxyModel.setFilterFixedString(m_ui->lineEdit->text());
|
||||
});
|
||||
|
||||
m_ui->filesView->setModel(&m_filesModel);
|
||||
|
||||
connect(m_ui->presetsView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &MainWindow::currentRowChanged);
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow() = default;
|
||||
|
||||
void MainWindow::selectFirstPreset()
|
||||
{
|
||||
if (m_presetsProxyModel.rowCount())
|
||||
{
|
||||
const auto index = m_presetsProxyModel.index(0, 0);
|
||||
if (index.isValid())
|
||||
{
|
||||
m_ui->presetsView->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
|
||||
currentRowChanged(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::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);
|
||||
|
||||
m_ui->samplesWidget->messageReceived(message);
|
||||
}
|
||||
|
||||
void MainWindow::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->samplesWidget->setPreset(preset);
|
||||
}
|
||||
|
||||
void MainWindow::updateMidiDevices()
|
||||
{
|
||||
m_ui->comboBoxMidiController->clear();
|
||||
|
||||
for (const auto &name : m_midiIn.portNames())
|
||||
m_ui->comboBoxMidiController->addItem(name);
|
||||
|
||||
m_ui->pushButtonMidiController->setEnabled(m_ui->comboBoxMidiController->count() > 0);
|
||||
}
|
||||
|
||||
void MainWindow::updateAudioDevices()
|
||||
{
|
||||
m_ui->comboBoxAudioDevice->clear();
|
||||
|
||||
m_devices = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput);
|
||||
|
||||
for (const auto &device : m_devices)
|
||||
m_ui->comboBoxAudioDevice->addItem(device.deviceName());
|
||||
}
|
45
mainwindow.h
Executable file
45
mainwindow.h
Executable file
@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#include "presetsmodel.h"
|
||||
#include "filesmodel.h"
|
||||
#include "midiinwrapper.h"
|
||||
|
||||
namespace Ui { class MainWindow; }
|
||||
namespace presets { struct PresetsConfig; }
|
||||
namespace midi { struct MidiMessage; }
|
||||
class QAudioDeviceInfo;
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MainWindow(const presets::PresetsConfig &presetsConfig, QWidget *parent = nullptr);
|
||||
~MainWindow() override;
|
||||
|
||||
void selectFirstPreset();
|
||||
|
||||
private slots:
|
||||
void messageReceived(const midi::MidiMessage &message);
|
||||
void currentRowChanged(const QModelIndex ¤t);
|
||||
|
||||
private:
|
||||
void updateMidiDevices();
|
||||
void updateAudioDevices();
|
||||
|
||||
const std::unique_ptr<Ui::MainWindow> m_ui;
|
||||
|
||||
MidiInWrapper m_midiIn;
|
||||
|
||||
QList<QAudioDeviceInfo> m_devices;
|
||||
|
||||
PresetsModel m_presetsModel;
|
||||
QSortFilterProxyModel m_presetsProxyModel;
|
||||
|
||||
FilesModel m_filesModel;
|
||||
};
|
228
mainwindow.ui
Executable file
228
mainwindow.ui
Executable file
@ -0,0 +1,228 @@
|
||||
<?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>1017</width>
|
||||
<height>712</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>MainWindow</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="widget">
|
||||
<layout class="QVBoxLayout" stretch="0,1">
|
||||
<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="labelUltraDj">
|
||||
<property name="text">
|
||||
<string><b>UltraDj</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="QLabel" name="labelMidiIn">
|
||||
<property name="text">
|
||||
<string>Midi in:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboBoxMidiController"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonMidiController">
|
||||
<property name="text">
|
||||
<string>Open</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboBoxAudioDevice"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<widget class="QSplitter" name="splitter_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<widget class="QWidget" name="layoutWidget">
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="lineEdit">
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="selectRandomPushButton">
|
||||
<property name="text">
|
||||
<string>Select random</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTreeView" name="presetsView">
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QSplitter" name="splitter_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<widget class="PresetDetailWidget" name="presetDetailWidget"/>
|
||||
<widget class="QTreeView" name="filesView">
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="SamplesWidget" name="samplesWidget" native="true"/>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menubar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1017</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusbar"/>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>SamplesWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>sampleswidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>PresetDetailWidget</class>
|
||||
<extends>QScrollArea</extends>
|
||||
<header>presetdetailwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
23
midicontainers.cpp
Executable file
23
midicontainers.cpp
Executable file
@ -0,0 +1,23 @@
|
||||
#include "midicontainers.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
namespace midi {
|
||||
bool MidiMessage::operator==(const MidiMessage &other) const
|
||||
{
|
||||
return channel == other.channel &&
|
||||
cmd == other.cmd &&
|
||||
flag == other.flag &&
|
||||
note == other.note &&
|
||||
velocity == other.velocity;
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
void registerMidiMessageMetatype()
|
||||
{
|
||||
qRegisterMetaType<midi::MidiMessage>();
|
||||
}
|
||||
|
||||
Q_COREAPP_STARTUP_FUNCTION(registerMidiMessageMetatype)
|
||||
}
|
31
midicontainers.h
Executable file
31
midicontainers.h
Executable file
@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
namespace midi {
|
||||
Q_NAMESPACE
|
||||
|
||||
enum class Command : uint8_t {
|
||||
NoteOff,
|
||||
NoteOn,
|
||||
PolyphonicKeyPressure,
|
||||
ControlChange,
|
||||
ProgramChange,
|
||||
ChannelPressure,
|
||||
PitchBendChange
|
||||
};
|
||||
Q_ENUM_NS(Command)
|
||||
|
||||
struct MidiMessage
|
||||
{
|
||||
uint8_t channel:4;
|
||||
Command cmd:3;
|
||||
bool flag:1;
|
||||
uint8_t note;
|
||||
uint8_t velocity;
|
||||
|
||||
bool operator==(const MidiMessage &other) const;
|
||||
};
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(midi::MidiMessage)
|
73
midiinwrapper.cpp
Executable file
73
midiinwrapper.cpp
Executable file
@ -0,0 +1,73 @@
|
||||
#include "midiinwrapper.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QMetaType>
|
||||
#include <QDebug>
|
||||
|
||||
MidiInWrapper::MidiInWrapper(RtMidi::Api api, const QString& clientName, unsigned int queueSizeLimit, QObject *parent) :
|
||||
QObject{parent},
|
||||
midiIn{api, clientName.toStdString(), queueSizeLimit}
|
||||
{
|
||||
midiIn.ignoreTypes(false, false, false);
|
||||
midiIn.setCallback(&mycallback, this);
|
||||
}
|
||||
|
||||
void MidiInWrapper::openPort(unsigned int portNumber)
|
||||
{
|
||||
midiIn.openPort(portNumber);
|
||||
}
|
||||
|
||||
void MidiInWrapper::openVirtualPort(const QString &portName)
|
||||
{
|
||||
midiIn.openVirtualPort(portName.toStdString());
|
||||
}
|
||||
|
||||
void MidiInWrapper::closePort()
|
||||
{
|
||||
midiIn.closePort();
|
||||
}
|
||||
|
||||
bool MidiInWrapper::isPortOpen() const
|
||||
{
|
||||
return midiIn.isPortOpen();
|
||||
}
|
||||
|
||||
QStringList MidiInWrapper::portNames()
|
||||
{
|
||||
QStringList names;
|
||||
|
||||
const auto count = midiIn.getPortCount();
|
||||
|
||||
for (unsigned int i = 0; i < count; i++)
|
||||
names.append(QString::fromStdString(midiIn.getPortName(i)));
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
void MidiInWrapper::mycallback(double deltatime, std::vector<unsigned char> *message, void *userData)
|
||||
{
|
||||
Q_UNUSED(deltatime)
|
||||
|
||||
if (!userData)
|
||||
{
|
||||
qCritical() << "called without userData pointer to wrapper";
|
||||
return;
|
||||
}
|
||||
|
||||
auto wrapper = static_cast<MidiInWrapper*>(userData);
|
||||
|
||||
if (!message)
|
||||
{
|
||||
qCritical() << "called without message pointer";
|
||||
return;
|
||||
}
|
||||
|
||||
if (message->size() < sizeof(midi::MidiMessage))
|
||||
{
|
||||
qCritical() << "called with message that is shorter than 3 bytes";
|
||||
return;
|
||||
}
|
||||
|
||||
const midi::MidiMessage &midiMessage = reinterpret_cast<const midi::MidiMessage &>(message->at(0));
|
||||
wrapper->messageReceived(midiMessage);
|
||||
}
|
34
midiinwrapper.h
Executable file
34
midiinwrapper.h
Executable file
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include "rtmidi/RtMidi.h"
|
||||
|
||||
#include "midicontainers.h"
|
||||
|
||||
class MidiInWrapper : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MidiInWrapper(RtMidi::Api api = RtMidi::UNSPECIFIED,
|
||||
const QString &clientName = "RtMidi Input Client",
|
||||
unsigned int queueSizeLimit = 100,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
void openPort(unsigned int portNumber);
|
||||
void openVirtualPort(const QString &portName);
|
||||
void closePort();
|
||||
bool isPortOpen() const;
|
||||
|
||||
QStringList portNames();
|
||||
|
||||
signals:
|
||||
void messageReceived(const midi::MidiMessage &message);
|
||||
|
||||
private:
|
||||
static void mycallback(double deltatime, std::vector<unsigned char> *message, void *userData);
|
||||
|
||||
RtMidiIn midiIn;
|
||||
};
|
16
presetdetailwidget.cpp
Executable file
16
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)
|
||||
{
|
||||
|
||||
}
|
22
presetdetailwidget.h
Executable file
22
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
presetdetailwidget.ui
Executable file
306
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>
|
15
presets.cpp
Executable file
15
presets.cpp
Executable file
@ -0,0 +1,15 @@
|
||||
#include "presets.h"
|
||||
|
||||
namespace presets
|
||||
{
|
||||
|
||||
bool File::operator==(const File &other) const
|
||||
{
|
||||
return filename == other.filename &&
|
||||
color == other.color &&
|
||||
stopOnRelease == other.stopOnRelease &&
|
||||
looped == other.looped &&
|
||||
choke == other.choke;
|
||||
}
|
||||
|
||||
}
|
84
presets.h
Executable file
84
presets.h
Executable file
@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace presets
|
||||
{
|
||||
struct Filter
|
||||
{
|
||||
std::optional<std::vector<QString>> tags;
|
||||
};
|
||||
|
||||
struct Category
|
||||
{
|
||||
std::optional<QString> title;
|
||||
std::optional<Filter> filter;
|
||||
};
|
||||
|
||||
struct File
|
||||
{
|
||||
std::optional<QString> filename;
|
||||
std::optional<QString> color; // purple, red, yellow, green, blue
|
||||
std::optional<QString> stopOnRelease;
|
||||
std::optional<bool> looped;
|
||||
std::optional<int> choke;
|
||||
|
||||
bool operator==(const File &other) const;
|
||||
};
|
||||
|
||||
struct SequencePad
|
||||
{
|
||||
std::optional<int> start;
|
||||
std::optional<int> duration;
|
||||
std::optional<bool> embient;
|
||||
};
|
||||
|
||||
struct Sequence
|
||||
{
|
||||
std::optional<QString> name;
|
||||
std::optional<int> id;
|
||||
std::optional<int> version;
|
||||
std::optional<int> orderBy;
|
||||
std::optional<int> sequencerSize;
|
||||
std::optional<std::map<QString, std::vector<SequencePad>>> pads;
|
||||
std::optional<std::vector<int>> embientPads;
|
||||
};
|
||||
|
||||
struct Preset
|
||||
{
|
||||
std::optional<QString> id;
|
||||
std::optional<QString> name;
|
||||
std::optional<QString> author;
|
||||
std::optional<QString> orderBy;
|
||||
std::optional<QString> version;
|
||||
std::optional<int> tempo;
|
||||
std::optional<QString> icon;
|
||||
std::optional<int> price;
|
||||
std::optional<int> priceForSession;
|
||||
std::optional<bool> hasInfo;
|
||||
std::optional<std::vector<QString>> tags;
|
||||
std::optional<bool> DELETED;
|
||||
std::optional<int> difficulty;
|
||||
std::optional<int> sample;
|
||||
std::optional<QString> audioPreview1Name;
|
||||
std::optional<QString> audioPreview1URL;
|
||||
std::optional<QString> audioPreview2Name;
|
||||
std::optional<QString> audioPreview2URL;
|
||||
std::optional<QString> imagePreview1;
|
||||
std::optional<QString> videoPreview;
|
||||
std::optional<QString> videoTutorial;
|
||||
std::optional<std::array<presets::File, 24>> files;
|
||||
std::optional<std::map<QString, std::vector<Sequence>>> beatSchool;
|
||||
std::optional<std::map<QString, std::vector<Sequence>>> easyPlay;
|
||||
};
|
||||
|
||||
struct PresetsConfig
|
||||
{
|
||||
std::optional<std::vector<Category>> categories;
|
||||
std::optional<std::map<QString, Preset>> presets;
|
||||
};
|
||||
}
|
208
presetsmodel.cpp
Executable file
208
presetsmodel.cpp
Executable file
@ -0,0 +1,208 @@
|
||||
#include "presetsmodel.h"
|
||||
|
||||
#include <iterator>
|
||||
|
||||
#include <QFont>
|
||||
#include <QColor>
|
||||
|
||||
#include "presets.h"
|
||||
|
||||
enum {
|
||||
ColumnId,
|
||||
ColumnName,
|
||||
ColumnAuthor,
|
||||
ColumnOrderBy,
|
||||
ColumnVersion,
|
||||
ColumnTempo,
|
||||
ColumnIcon,
|
||||
ColumnPrice,
|
||||
ColumnPriceForSession,
|
||||
ColumnHasInfo,
|
||||
ColumnTags,
|
||||
ColumnDELETED,
|
||||
ColumnDifficulty,
|
||||
ColumnSample,
|
||||
ColumnAudioPreview1Name,
|
||||
ColumnAudioPreview1URL,
|
||||
ColumnAudioPreview2Name,
|
||||
ColumnAudioPreview2URL,
|
||||
ColumnImagePreview1,
|
||||
ColumnVideoPreview,
|
||||
ColumnVideoTutorial,
|
||||
NumberOfColumns
|
||||
};
|
||||
|
||||
PresetsModel::PresetsModel(const std::map<QString, presets::Preset> &presets, QObject *parent) :
|
||||
QAbstractTableModel{parent}
|
||||
{
|
||||
m_presets.reserve(std::size(presets));
|
||||
for (const auto &pair : presets)
|
||||
m_presets.emplace_back(pair.second);
|
||||
}
|
||||
|
||||
PresetsModel::~PresetsModel() = default;
|
||||
|
||||
const presets::Preset &PresetsModel::getPreset(const QModelIndex &index) const
|
||||
{
|
||||
return getPreset(index.row());
|
||||
}
|
||||
|
||||
const presets::Preset &PresetsModel::getPreset(int row) const
|
||||
{
|
||||
Q_ASSERT(row >= 0 && row < std::size(m_presets));
|
||||
return m_presets.at(row);
|
||||
}
|
||||
|
||||
int PresetsModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
|
||||
return std::size(m_presets);
|
||||
}
|
||||
|
||||
int PresetsModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
|
||||
return NumberOfColumns;
|
||||
}
|
||||
|
||||
QVariant PresetsModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::FontRole && role != Qt::ForegroundRole)
|
||||
return {};
|
||||
|
||||
if (index.column() < 0)
|
||||
return {};
|
||||
if (index.column() >= NumberOfColumns)
|
||||
return {};
|
||||
if (index.row() < 0)
|
||||
return {};
|
||||
if (index.row() >= std::size(m_presets))
|
||||
return {};
|
||||
|
||||
const auto &preset = getPreset(index);
|
||||
|
||||
const auto handleData = [&](const auto &val) -> QVariant
|
||||
{
|
||||
if (!val)
|
||||
{
|
||||
if (role == Qt::DisplayRole)
|
||||
return this->tr("(null)");
|
||||
else if (role == Qt::FontRole)
|
||||
{
|
||||
QFont font;
|
||||
font.setItalic(true);
|
||||
return font;
|
||||
}
|
||||
else if (role == Qt::ForegroundRole)
|
||||
return QColor{Qt::gray};
|
||||
return {};
|
||||
}
|
||||
|
||||
if (role == Qt::DisplayRole || role == Qt::EditRole)
|
||||
return *val;
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
const auto handleStringVectorData = [&](const auto &val) -> QVariant
|
||||
{
|
||||
if (!val)
|
||||
{
|
||||
if (role == Qt::DisplayRole)
|
||||
return this->tr("(null)");
|
||||
else if (role == Qt::FontRole)
|
||||
{
|
||||
QFont font;
|
||||
font.setItalic(true);
|
||||
return font;
|
||||
}
|
||||
else if (role == Qt::ForegroundRole)
|
||||
return QColor{Qt::gray};
|
||||
return {};
|
||||
}
|
||||
|
||||
if (role == Qt::DisplayRole || role == Qt::EditRole)
|
||||
{
|
||||
QString text;
|
||||
for (auto iter = std::cbegin(*val); iter != std::cend(*val); iter++)
|
||||
{
|
||||
if (iter != std::cbegin(*val))
|
||||
text += ", ";
|
||||
text += *iter;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
switch (index.column())
|
||||
{
|
||||
case ColumnId: return handleData(preset.id);
|
||||
case ColumnName: return handleData(preset.name);
|
||||
case ColumnAuthor: return handleData(preset.author);
|
||||
case ColumnOrderBy: return handleData(preset.orderBy);
|
||||
case ColumnVersion: return handleData(preset.version);
|
||||
case ColumnTempo: return handleData(preset.tempo);
|
||||
case ColumnIcon: return handleData(preset.icon);
|
||||
case ColumnPrice: return handleData(preset.price);
|
||||
case ColumnPriceForSession: return handleData(preset.priceForSession);
|
||||
case ColumnHasInfo: return handleData(preset.hasInfo);
|
||||
case ColumnTags: return handleStringVectorData(preset.tags);
|
||||
case ColumnDELETED: return handleData(preset.DELETED);
|
||||
case ColumnDifficulty: return handleData(preset.difficulty);
|
||||
case ColumnSample: return handleData(preset.sample);
|
||||
case ColumnAudioPreview1Name: return handleData(preset.audioPreview1Name);
|
||||
case ColumnAudioPreview1URL: return handleData(preset.audioPreview1URL);
|
||||
case ColumnAudioPreview2Name: return handleData(preset.audioPreview2Name);
|
||||
case ColumnAudioPreview2URL: return handleData(preset.audioPreview2URL);
|
||||
case ColumnImagePreview1: return handleData(preset.imagePreview1);
|
||||
case ColumnVideoPreview: return handleData(preset.videoPreview);
|
||||
case ColumnVideoTutorial: return handleData(preset.videoTutorial);
|
||||
}
|
||||
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
QVariant PresetsModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if (role != Qt::DisplayRole && role != Qt::EditRole)
|
||||
return {};
|
||||
|
||||
if (orientation != Qt::Horizontal)
|
||||
return {};
|
||||
|
||||
if (section < 0)
|
||||
return {};
|
||||
if (section >= NumberOfColumns)
|
||||
return {};
|
||||
|
||||
switch (section)
|
||||
{
|
||||
case ColumnId: return tr("id");
|
||||
case ColumnName: return tr("name");
|
||||
case ColumnAuthor: return tr("author");
|
||||
case ColumnOrderBy: return tr("orderBy");
|
||||
case ColumnVersion: return tr("version");
|
||||
case ColumnTempo: return tr("tempo");
|
||||
case ColumnIcon: return tr("icon");
|
||||
case ColumnPrice: return tr("price");
|
||||
case ColumnPriceForSession: return tr("priceForSession");
|
||||
case ColumnHasInfo: return tr("hasInfo");
|
||||
case ColumnTags: return tr("tags");
|
||||
case ColumnDELETED: return tr("DELETED");
|
||||
case ColumnDifficulty: return tr("difficulty");
|
||||
case ColumnSample: return tr("sample");
|
||||
case ColumnAudioPreview1Name: return tr("audioPreview1Name");
|
||||
case ColumnAudioPreview1URL: return tr("audioPreview1URL");
|
||||
case ColumnAudioPreview2Name: return tr("audioPreview2Name");
|
||||
case ColumnAudioPreview2URL: return tr("audioPreview2URL");
|
||||
case ColumnImagePreview1: return tr("imagePreview1");
|
||||
case ColumnVideoPreview: return tr("videoPreview");
|
||||
case ColumnVideoTutorial: return tr("videoTutorial");
|
||||
}
|
||||
|
||||
Q_UNREACHABLE();
|
||||
}
|
27
presetsmodel.h
Executable file
27
presetsmodel.h
Executable file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
|
||||
namespace presets { class Preset; }
|
||||
|
||||
class PresetsModel : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PresetsModel(const std::map<QString, presets::Preset> &presets, QObject *parent = nullptr);
|
||||
~PresetsModel() override;
|
||||
|
||||
const presets::Preset &getPreset(const QModelIndex &index) const;
|
||||
const presets::Preset &getPreset(int row) const;
|
||||
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
int columnCount(const QModelIndex &parent) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||
|
||||
private:
|
||||
std::vector<presets::Preset> m_presets;
|
||||
};
|
5
resources.qrc
Normal file
5
resources.qrc
Normal file
@ -0,0 +1,5 @@
|
||||
<RCC>
|
||||
<qresource prefix="/drummachine">
|
||||
<file>splashscreen.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
1
rtmidi
Submodule
1
rtmidi
Submodule
Submodule rtmidi added at 7ab18ef06b
172
sampleswidget.cpp
Executable file
172
sampleswidget.cpp
Executable file
@ -0,0 +1,172 @@
|
||||
#include "sampleswidget.h"
|
||||
#include "ui_sampleswidget.h"
|
||||
|
||||
#include <iterator>
|
||||
|
||||
#include <QEventLoop>
|
||||
#include <QDebug>
|
||||
|
||||
#include "midicontainers.h"
|
||||
|
||||
SamplesWidget::SamplesWidget(QWidget *parent) :
|
||||
QWidget{parent},
|
||||
m_ui{std::make_unique<Ui::SamplesWidget>()}
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
{
|
||||
QEventLoop eventLoop;
|
||||
connect(&m_audioThread, &QThread::started, &eventLoop, &QEventLoop::quit);
|
||||
m_audioThread.start(QThread::HighestPriority);
|
||||
eventLoop.exec();
|
||||
}
|
||||
|
||||
connect(m_ui->checkBox, &QCheckBox::toggled, this, &SamplesWidget::updateWidgets);
|
||||
|
||||
connect(m_ui->pushButtonStopAll, &QAbstractButton::pressed, this, &SamplesWidget::stopAll);
|
||||
|
||||
for (const auto &ref : getWidgets())
|
||||
connect(&ref.get(), &SampleWidget::chokeTriggered, this, &SamplesWidget::chokeTriggered);
|
||||
|
||||
connect(m_ui->sequencerWidget, &SequencerWidget::triggerSample, this, &SamplesWidget::sequencerTriggerSample);
|
||||
|
||||
m_ui->sampleWidget_1->setNote(48);
|
||||
m_ui->sampleWidget_2->setNote(50);
|
||||
m_ui->sampleWidget_3->setNote(52);
|
||||
m_ui->sampleWidget_4->setNote(53);
|
||||
m_ui->sampleWidget_5->setNote(55);
|
||||
m_ui->sampleWidget_6->setNote(57);
|
||||
m_ui->sampleWidget_7->setNote(59);
|
||||
m_ui->sampleWidget_8->setNote(60);
|
||||
m_ui->sampleWidget_9->setNote(62);
|
||||
m_ui->sampleWidget_10->setNote(64);
|
||||
m_ui->sampleWidget_11->setNote(65);
|
||||
m_ui->sampleWidget_12->setNote(67);
|
||||
|
||||
m_ui->sampleWidget_22->setNote(69);
|
||||
m_ui->sampleWidget_23->setNote(71);
|
||||
m_ui->sampleWidget_24->setNote(72);
|
||||
}
|
||||
|
||||
SamplesWidget::~SamplesWidget()
|
||||
{
|
||||
m_audioThread.exit();
|
||||
m_audioThread.wait();
|
||||
}
|
||||
|
||||
void SamplesWidget::setPreset(const presets::Preset &preset)
|
||||
{
|
||||
m_preset = preset;
|
||||
|
||||
m_ui->sequencerWidget->setPreset(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 (const auto &ref : getWidgets())
|
||||
{
|
||||
if (ref.get().channel() == message.channel && ref.get().note() == message.note)
|
||||
{
|
||||
if (message.cmd == midi::Command::NoteOn)
|
||||
ref.get().pressed(message.velocity);
|
||||
else if (message.cmd == midi::Command::NoteOff)
|
||||
ref.get().released();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SamplesWidget::setAudioDevice(const QAudioDeviceInfo &device)
|
||||
{
|
||||
for (const auto &ref : getWidgets())
|
||||
{
|
||||
connect(&ref.get(), &SampleWidget::chokeTriggered, this, &SamplesWidget::chokeTriggered);
|
||||
ref.get().setupAudioThread(device, m_audioThread);
|
||||
}
|
||||
}
|
||||
|
||||
void SamplesWidget::chokeTriggered(int choke)
|
||||
{
|
||||
for (const auto &ref : getWidgets())
|
||||
{
|
||||
if (&ref.get() == sender())
|
||||
continue;
|
||||
|
||||
if (ref.get().choke() && *ref.get().choke() && *ref.get().choke() == choke)
|
||||
ref.get().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::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::stopAll()
|
||||
{
|
||||
for (const auto &ref : getWidgets())
|
||||
ref.get().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)
|
||||
};
|
||||
}
|
45
sampleswidget.h
Executable file
45
sampleswidget.h
Executable file
@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <array>
|
||||
#include <functional>
|
||||
|
||||
#include <QWidget>
|
||||
#include <QThread>
|
||||
|
||||
#include "presets.h"
|
||||
|
||||
namespace Ui { class SamplesWidget; }
|
||||
namespace midi { class MidiMessage; }
|
||||
class SampleWidget;
|
||||
class QAudioDeviceInfo;
|
||||
|
||||
class SamplesWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SamplesWidget(QWidget *parent = nullptr);
|
||||
~SamplesWidget() override;
|
||||
|
||||
void setPreset(const presets::Preset &preset);
|
||||
|
||||
void messageReceived(const midi::MidiMessage &message);
|
||||
|
||||
void setAudioDevice(const QAudioDeviceInfo &device);
|
||||
|
||||
private slots:
|
||||
void chokeTriggered(int choke);
|
||||
void updateWidgets();
|
||||
void sequencerTriggerSample(int index);
|
||||
void stopAll();
|
||||
|
||||
private:
|
||||
std::array<std::reference_wrapper<SampleWidget>, 24> getWidgets();
|
||||
|
||||
const std::unique_ptr<Ui::SamplesWidget> m_ui;
|
||||
|
||||
presets::Preset m_preset;
|
||||
|
||||
QThread m_audioThread;
|
||||
};
|
253
sampleswidget.ui
Executable file
253
sampleswidget.ui
Executable file
@ -0,0 +1,253 @@
|
||||
<?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="1,0,1,0,0,0,0,1" columnstretch="1,0,0,0,1,0,0,0,1">
|
||||
<item row="3" column="5">
|
||||
<widget class="SampleWidget" name="sampleWidget_13" native="true"/>
|
||||
</item>
|
||||
<item row="6" column="2">
|
||||
<widget class="SampleWidget" name="sampleWidget_11" native="true"/>
|
||||
</item>
|
||||
<item row="2" column="4">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="SampleWidget" name="sampleWidget_4" native="true"/>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="SampleWidget" name="sampleWidget_2" native="true"/>
|
||||
</item>
|
||||
<item row="4" column="7">
|
||||
<widget class="SampleWidget" name="sampleWidget_18" native="true"/>
|
||||
</item>
|
||||
<item row="3" 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="6" column="5">
|
||||
<widget class="SampleWidget" name="sampleWidget_22" native="true"/>
|
||||
</item>
|
||||
<item row="5" column="5">
|
||||
<widget class="SampleWidget" name="sampleWidget_19" native="true"/>
|
||||
</item>
|
||||
<item row="5" column="2">
|
||||
<widget class="SampleWidget" name="sampleWidget_8" native="true"/>
|
||||
</item>
|
||||
<item row="3" column="3">
|
||||
<widget class="SampleWidget" name="sampleWidget_3" native="true"/>
|
||||
</item>
|
||||
<item row="5" column="7">
|
||||
<widget class="SampleWidget" name="sampleWidget_21" native="true"/>
|
||||
</item>
|
||||
<item row="6" column="7">
|
||||
<widget class="SampleWidget" name="sampleWidget_24" native="true"/>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="SampleWidget" name="sampleWidget_7" native="true"/>
|
||||
</item>
|
||||
<item row="6" column="3">
|
||||
<widget class="SampleWidget" name="sampleWidget_12" native="true"/>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<widget class="SampleWidget" name="sampleWidget_5" native="true"/>
|
||||
</item>
|
||||
<item row="4" column="6">
|
||||
<widget class="SampleWidget" name="sampleWidget_17" native="true"/>
|
||||
</item>
|
||||
<item row="3" column="7">
|
||||
<widget class="SampleWidget" name="sampleWidget_15" native="true"/>
|
||||
</item>
|
||||
<item row="3" 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="5" column="6">
|
||||
<widget class="SampleWidget" name="sampleWidget_20" native="true"/>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="SampleWidget" name="sampleWidget_1" native="true"/>
|
||||
</item>
|
||||
<item row="4" column="3">
|
||||
<widget class="SampleWidget" name="sampleWidget_6" native="true"/>
|
||||
</item>
|
||||
<item row="4" column="5">
|
||||
<widget class="SampleWidget" name="sampleWidget_16" native="true"/>
|
||||
</item>
|
||||
<item row="6" column="6">
|
||||
<widget class="SampleWidget" name="sampleWidget_23" native="true"/>
|
||||
</item>
|
||||
<item row="3" column="6">
|
||||
<widget class="SampleWidget" name="sampleWidget_14" native="true"/>
|
||||
</item>
|
||||
<item row="7" column="4">
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="3" 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="6" column="1">
|
||||
<widget class="SampleWidget" name="sampleWidget_10" native="true"/>
|
||||
</item>
|
||||
<item row="5" column="3">
|
||||
<widget class="SampleWidget" name="sampleWidget_9" native="true"/>
|
||||
</item>
|
||||
<item row="1" 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="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Sequencer</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<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>
|
||||
<widget class="SequencerWidget" name="sequencerWidget" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</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="0" column="4">
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>SampleWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>samplewidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>SequencerWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>sequencerwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
192
samplewidget.cpp
Executable file
192
samplewidget.cpp
Executable file
@ -0,0 +1,192 @@
|
||||
#include "samplewidget.h"
|
||||
#include "ui_samplewidget.h"
|
||||
|
||||
#include <QAbstractEventDispatcher>
|
||||
#include <QAudioDeviceInfo>
|
||||
#include <QSoundEffect>
|
||||
#include <QDebug>
|
||||
|
||||
namespace {
|
||||
QString toString(QString value) { return value; }
|
||||
QString toString(int value) { return QString::number(value); }
|
||||
QString toString(bool value) { return value?"true":"false"; }
|
||||
QString toString(QSoundEffect::Status value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case QSoundEffect::Null: return "Null";
|
||||
case QSoundEffect::Loading: return "Loading";
|
||||
case QSoundEffect::Ready: return "Ready";
|
||||
case QSoundEffect::Error: return "Error";
|
||||
}
|
||||
|
||||
return QString{"Unknown (%0)"}.arg(value);
|
||||
}
|
||||
}
|
||||
|
||||
SampleWidget::SampleWidget(QWidget *parent) :
|
||||
QFrame{parent},
|
||||
m_ui{std::make_unique<Ui::SampleWidget>()}
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
connect(m_ui->pushButton, &QAbstractButton::pressed, this, [this](){ pressed(127); });
|
||||
connect(m_ui->pushButton, &QAbstractButton::released, this, &SampleWidget::released);
|
||||
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
SampleWidget::~SampleWidget()
|
||||
{
|
||||
if (m_effect)
|
||||
QMetaObject::invokeMethod(m_effect.get(), [effect=m_effect.release()](){ delete effect; });
|
||||
}
|
||||
|
||||
void SampleWidget::setFile(const QString &presetId, const presets::File &file)
|
||||
{
|
||||
m_presetId = presetId;
|
||||
m_file = file;
|
||||
|
||||
if (m_effect)
|
||||
{
|
||||
auto sampleUrl = this->sampleUrl();
|
||||
if (!sampleUrl.isEmpty())
|
||||
QMetaObject::invokeMethod(m_effect.get(), [&effect=*m_effect,sampleUrl=std::move(sampleUrl)](){
|
||||
effect.setSource(sampleUrl);
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
quint8 SampleWidget::note() const
|
||||
{
|
||||
return m_ui->noteSpinBox->value();
|
||||
}
|
||||
|
||||
void SampleWidget::setNote(quint8 note)
|
||||
{
|
||||
m_ui->noteSpinBox->setValue(note);
|
||||
}
|
||||
|
||||
std::optional<int> SampleWidget::choke() const
|
||||
{
|
||||
if (!m_file)
|
||||
return {};
|
||||
return m_file->choke;
|
||||
}
|
||||
|
||||
void SampleWidget::pressed(quint8 velocity)
|
||||
{
|
||||
Q_UNUSED(velocity)
|
||||
|
||||
if (m_effect)
|
||||
QMetaObject::invokeMethod(m_effect.get(), &QSoundEffect::play);
|
||||
|
||||
if (m_file && m_file->choke && *m_file->choke)
|
||||
emit chokeTriggered(*m_file->choke);
|
||||
}
|
||||
|
||||
void SampleWidget::released()
|
||||
{
|
||||
}
|
||||
|
||||
void SampleWidget::forceStop()
|
||||
{
|
||||
if (m_effect)
|
||||
QMetaObject::invokeMethod(m_effect.get(), &QSoundEffect::stop);
|
||||
}
|
||||
|
||||
void SampleWidget::setupAudioThread(const QAudioDeviceInfo &device, QThread &thread)
|
||||
{
|
||||
const auto setupEffect = [this,device](){
|
||||
m_effect = std::make_unique<QSoundEffect>(device);
|
||||
|
||||
connect(m_effect.get(), &QSoundEffect::playingChanged, this, &SampleWidget::updateStatus);
|
||||
connect(m_effect.get(), &QSoundEffect::statusChanged, this, &SampleWidget::updateStatus);
|
||||
|
||||
const auto sampleUrl = this->sampleUrl();
|
||||
if (!sampleUrl.isEmpty())
|
||||
m_effect->setSource(sampleUrl);
|
||||
|
||||
QMetaObject::invokeMethod(this, &SampleWidget::updateStatus);
|
||||
};
|
||||
|
||||
QMetaObject::invokeMethod(QAbstractEventDispatcher::instance(&thread), setupEffect);
|
||||
//setupEffect();
|
||||
}
|
||||
|
||||
void SampleWidget::updateStatus()
|
||||
{
|
||||
QPalette pal;
|
||||
if (m_effect && m_file && m_file->color)
|
||||
{
|
||||
const auto bright = m_effect->isPlaying() ? 255 : 155;
|
||||
const auto dark = m_effect->isPlaying() ?
|
||||
#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_effect)
|
||||
m_ui->statusLabel->setText(tr("No player"));
|
||||
else
|
||||
m_ui->statusLabel->setText(toString(m_effect->status()));
|
||||
}
|
||||
|
||||
QUrl SampleWidget::sampleUrl() const
|
||||
{
|
||||
if (!m_file || !m_file->filename)
|
||||
return {};
|
||||
|
||||
return QUrl{QString{"https://brunner.ninja/komposthaufen/dpm/presets/extracted/%0/%1"}.arg(m_presetId, *m_file->filename)};
|
||||
}
|
54
samplewidget.h
Executable file
54
samplewidget.h
Executable file
@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QFrame>
|
||||
|
||||
#include "presets.h"
|
||||
|
||||
namespace Ui { class SampleWidget; }
|
||||
class QThread;
|
||||
class QAudioDeviceInfo;
|
||||
class QSoundEffect;
|
||||
|
||||
class SampleWidget : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SampleWidget(QWidget *parent = nullptr);
|
||||
~SampleWidget() override;
|
||||
|
||||
void setFile(const QString &presetId, const presets::File &file);
|
||||
|
||||
quint8 channel() const;
|
||||
void setChannel(quint8 channel);
|
||||
|
||||
quint8 note() const;
|
||||
void setNote(quint8 note);
|
||||
|
||||
std::optional<int> choke() const;
|
||||
|
||||
void pressed(quint8 velocity);
|
||||
void released();
|
||||
|
||||
void forceStop();
|
||||
|
||||
void setupAudioThread(const QAudioDeviceInfo &device, QThread &thread);
|
||||
|
||||
signals:
|
||||
void chokeTriggered(int choke);
|
||||
|
||||
private slots:
|
||||
void updateStatus();
|
||||
|
||||
private:
|
||||
QUrl sampleUrl() const;
|
||||
|
||||
const std::unique_ptr<Ui::SampleWidget> m_ui;
|
||||
|
||||
QString m_presetId;
|
||||
std::optional<presets::File> m_file;
|
||||
|
||||
std::unique_ptr<QSoundEffect> m_effect;
|
||||
};
|
108
samplewidget.ui
Executable file
108
samplewidget.ui
Executable file
@ -0,0 +1,108 @@
|
||||
<?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>400</width>
|
||||
<height>300</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_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="statusLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="toolButton">
|
||||
<property name="text">
|
||||
<string>↻</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton">
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="channelSpinBox"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="noteSpinBox"/>
|
||||
</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>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</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>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</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>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
148
sequencerwidget.cpp
Executable file
148
sequencerwidget.cpp
Executable file
@ -0,0 +1,148 @@
|
||||
#include "sequencerwidget.h"
|
||||
#include "ui_sequencerwidget.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#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);
|
||||
|
||||
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));
|
||||
}
|
44
sequencerwidget.h
Executable file
44
sequencerwidget.h
Executable file
@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <QWidget>
|
||||
#include <QTimer>
|
||||
|
||||
namespace Ui { class SequencerWidget; }
|
||||
namespace presets { class Preset; class Sequence; }
|
||||
|
||||
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;
|
||||
};
|
78
sequencerwidget.ui
Executable file
78
sequencerwidget.ui
Executable file
@ -0,0 +1,78 @@
|
||||
<?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>412</width>
|
||||
<height>65</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<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>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="horizontalSlider">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
BIN
splashscreen.png
Normal file
BIN
splashscreen.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
Reference in New Issue
Block a user