bunch of UI work; now has a working pattern viewer!
parent
d3005dc72a
commit
29b71cfaf8
26
notes
26
notes
|
@ -17,15 +17,16 @@ project data {
|
|||
length in rows (duh)
|
||||
time signature, in beats per measure and rows per beat (can default to project global)
|
||||
|
||||
per-pattern channels? since everything is determined by instruments and plugins
|
||||
- this would orphan all currently playing notes when crossing a pattern boundary (maybe treat same-named channels as the same for this purpose?)
|
||||
per-pattern channels; note continuity is defined by name
|
||||
- send note-off on entering a pattern without a channel of that name? maybe project setting
|
||||
|
||||
command format
|
||||
01 C-5 v7F ... ... ...
|
||||
instrument (port) number first; note-sharp-octave notation same as most trackers, but arbitrary number of a single type of parameter
|
||||
[plugins receive commands more or less exactly as written; meaning is by convention more than anything, but there is a "standard" way of handling notes, handled by a library on the lua side]
|
||||
- leave pitch bends to automation? or build them as per-tick messages from host? also, stepped by tick or smoothed per sample?
|
||||
- note-on events send the actual note as a float value
|
||||
x note-on events send the actual note as a float value
|
||||
- nope, separate event for cents (bcd? that would futz with interpolation though... signed byte, -100..100)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,3 +38,22 @@ resampler object {
|
|||
}
|
||||
|
||||
lv2 support: lilv (duh) for actual plugin loading, suil for UI embedding
|
||||
|
||||
keybinds {
|
||||
pattern editor {
|
||||
note column {
|
||||
( a-z, []\ ;' ,./ ) - "flat piano" in three octaves a la openMPT (maybe minus /)
|
||||
shift - hold to transpose up one or two octaves while playing
|
||||
1234567890 - assign octave
|
||||
note off, hard cut?
|
||||
}
|
||||
param column {
|
||||
symbol {
|
||||
anything printable - set symbol and move to value
|
||||
}
|
||||
value {
|
||||
minus - negate current value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
#include "pattern.h"
|
||||
using Xybrid::Data::Pattern;
|
||||
using Row = Pattern::Row;
|
||||
using Channel = Pattern::Channel;
|
||||
|
||||
Channel::Channel(int numRows, std::string name) : Channel() {
|
||||
this->name = name;
|
||||
this->rows.resize(static_cast<unsigned long>(numRows));
|
||||
}
|
||||
|
||||
Pattern::Pattern() {
|
||||
|
||||
}
|
||||
|
||||
Pattern::Pattern(int rows, int channels) : Pattern() {
|
||||
this->rows = rows;
|
||||
for (int i = 0; i < channels; i++) this->channels.emplace_back();
|
||||
this->setLength(rows);
|
||||
}
|
||||
|
||||
void Pattern::setLength(int r) {
|
||||
if (r < 1) r = 1;
|
||||
rows = r;
|
||||
for (auto & c : channels) {
|
||||
c.rows.resize(static_cast<unsigned long>(rows));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace Xybrid::Data {
|
||||
class Pattern {
|
||||
public:
|
||||
class Row { // with std::unique_ptr<std::vector>, each Row is 12 bytes inline on 64-bit (8 bytes on 32)
|
||||
public:
|
||||
int16_t port = -1;
|
||||
int16_t note = -1;
|
||||
std::unique_ptr<std::vector<unsigned char[2]>> params = nullptr; // empty by default
|
||||
};
|
||||
|
||||
class Channel {
|
||||
public:
|
||||
std::string name;
|
||||
std::vector<Row> rows;
|
||||
|
||||
Channel() = default;
|
||||
Channel(int numRows, std::string name = "");
|
||||
};
|
||||
|
||||
int rows = 64;
|
||||
|
||||
std::vector<Channel> channels;
|
||||
|
||||
Pattern();
|
||||
Pattern(int rows, int channels);
|
||||
|
||||
void setLength(int rows);
|
||||
};
|
||||
}
|
||||
|
|
@ -1,11 +1,15 @@
|
|||
#include "mainwindow.h"
|
||||
#include <QApplication>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
#include <QDebug>
|
||||
#include <vector>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
QApplication a(argc, argv);
|
||||
MainWindow w;
|
||||
w.show();
|
||||
|
||||
qDebug() << QString::number(sizeof(std::vector<unsigned short>));
|
||||
|
||||
return a.exec();
|
||||
}
|
||||
|
|
|
@ -1,14 +1,55 @@
|
|||
#include "mainwindow.h"
|
||||
#include "ui_mainwindow.h"
|
||||
|
||||
#include <QToolButton>
|
||||
#include <QPushButton>
|
||||
|
||||
#include "ui/patterneditoritemdelegate.h"
|
||||
|
||||
using Xybrid::UI::PatternEditorModel;
|
||||
using Xybrid::UI::PatternEditorItemDelegate;
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) :
|
||||
QMainWindow(parent),
|
||||
ui(new Ui::MainWindow)
|
||||
{
|
||||
ui(new Ui::MainWindow) {
|
||||
ui->setupUi(this);
|
||||
|
||||
QTabWidget* t = this->ui->tabWidget;
|
||||
//QToolButton* mb = t->findChild<QToolButton*>();
|
||||
|
||||
//QToolButton* menuBtn = new QToolButton(this);
|
||||
//menuBtn->setArrowType(Qt::ArrowType::DownArrow);
|
||||
t->setCornerWidget(this->ui->menuBar);
|
||||
auto mb = this->ui->menuBar;
|
||||
mb->setStyleSheet("QMenuBar { background: transparent; } QMenuBar::item { } QMenuBar::item:!pressed { background: transparent; }");
|
||||
//mb->resize(mb->size().width(), t->tabBar()->size().height());
|
||||
|
||||
|
||||
pattern.reset(new Xybrid::Data::Pattern(64, 8));
|
||||
pattern->channels[0].rows[0].port = 15;
|
||||
pattern->channels[0].rows[0].note = 64;
|
||||
pattern->channels[0].rows[0].params.reset(new std::vector<unsigned char[2]>(1));
|
||||
pattern->channels[0].rows[0].params->at(0)[0] = 'v';
|
||||
pattern->channels[0].rows[0].params->at(0)[1] = 255;
|
||||
pattern->channels[1].rows[0].port = 1;
|
||||
pattern->channels[1].rows[0].note = 0;
|
||||
|
||||
//t->tabBar()->setTabText(0, QString::number(pattern->rows));
|
||||
|
||||
auto pe = t->findChild<QTableView*>("patternEditor");
|
||||
pe->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
pe->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
pe->setItemDelegate(new PatternEditorItemDelegate());
|
||||
//pe->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed);
|
||||
//pe->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
|
||||
pe->setGridStyle(Qt::PenStyle::NoPen);
|
||||
pe->setStyleSheet("QTableView::item { border: 0px; margin: 0px; padding: 0px; }");
|
||||
//pe->setStyleSheet("QTableView::item { text-overflow: clip; overflow: hidden; white-space: nowrap; }");
|
||||
pmodel.reset(new PatternEditorModel(nullptr));
|
||||
pmodel->setPattern(&*pattern);
|
||||
pe->setModel(&*pmodel);
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
MainWindow::~MainWindow() {
|
||||
delete ui;
|
||||
}
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
#pragma once
|
||||
|
||||
#include <QMainWindow>
|
||||
|
||||
#include "data/pattern.h"
|
||||
#include "ui/patterneditormodel.h"
|
||||
|
||||
namespace Ui {
|
||||
class MainWindow;
|
||||
class MainWindow;
|
||||
}
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
class MainWindow : public QMainWindow {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
@ -16,7 +17,7 @@ public:
|
|||
~MainWindow();
|
||||
|
||||
private:
|
||||
Ui::MainWindow *ui;
|
||||
Ui::MainWindow* ui;
|
||||
std::unique_ptr<Xybrid::Data::Pattern> pattern; // temporary pattern for testing the editor
|
||||
std::unique_ptr<Xybrid::UI::PatternEditorModel> pmodel;
|
||||
};
|
||||
|
||||
#endif // MAINWINDOW_H
|
||||
|
|
|
@ -6,28 +6,143 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
<width>920</width>
|
||||
<height>611</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Xybrid</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralWidget">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>80</x>
|
||||
<y>150</y>
|
||||
<width>191</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>oh! this is just like visual basic</string>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="pattern">
|
||||
<attribute name="title">
|
||||
<string>Pattern</string>
|
||||
</attribute>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<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="QTableView" name="patternEditor">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Iosevka Term Light</family>
|
||||
<pointsize>9</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::AnyKeyPressed|QAbstractItemView::EditKeyPressed</set>
|
||||
</property>
|
||||
<property name="dragDropOverwriteMode">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="textElideMode">
|
||||
<enum>Qt::ElideNone</enum>
|
||||
</property>
|
||||
<property name="showGrid">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="gridStyle">
|
||||
<enum>Qt::NoPen</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderDefaultSectionSize">
|
||||
<number>24</number>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderMinimumSectionSize">
|
||||
<number>5</number>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderDefaultSectionSize">
|
||||
<number>24</number>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderMinimumSectionSize">
|
||||
<number>2</number>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="patchboard">
|
||||
<attribute name="title">
|
||||
<string>Patchboard</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menuBar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>920</width>
|
||||
<height>21</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
|
||||
<horstretch>255</horstretch>
|
||||
<verstretch>255</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<widget class="QMenu" name="menua_menu_item">
|
||||
<property name="title">
|
||||
<string>&File</string>
|
||||
</property>
|
||||
<addaction name="actionpickle"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuEdit">
|
||||
<property name="title">
|
||||
<string>Edit</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuHelp">
|
||||
<property name="title">
|
||||
<string>Help</string>
|
||||
</property>
|
||||
</widget>
|
||||
<addaction name="menua_menu_item"/>
|
||||
<addaction name="menuEdit"/>
|
||||
<addaction name="menuHelp"/>
|
||||
</widget>
|
||||
<action name="actionpickle">
|
||||
<property name="text">
|
||||
<string>Open</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<resources/>
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
#include "patterneditoritemdelegate.h"
|
||||
using Xybrid::UI::PatternEditorItemDelegate;
|
||||
|
||||
#include "ui/patterneditormodel.h"
|
||||
using Xybrid::UI::PatternEditorModel;
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
namespace {
|
||||
constexpr int pad = 2;
|
||||
}
|
||||
|
||||
void PatternEditorItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
|
||||
QString s = index.data().toString();
|
||||
auto fm = QFontMetrics(QFont("Iosevka Term Light", 9));
|
||||
int cc = index.column() % PatternEditorModel::colsPerChannel;
|
||||
int align = Qt::AlignCenter;
|
||||
if (cc > 1) { // param field
|
||||
if (cc % 2 == 0) align = Qt::AlignVCenter | Qt::AlignRight;
|
||||
else align = Qt::AlignVCenter | Qt::AlignLeft;
|
||||
}
|
||||
painter->drawText(option.rect, align, s);
|
||||
}//*/
|
||||
|
||||
QSize PatternEditorItemDelegate::sizeHint(const QStyleOptionViewItem &option [[maybe_unused]], const QModelIndex &index) const {
|
||||
auto fm = QFontMetrics(QFont("Iosevka Term Light", 9));
|
||||
int cc = index.column() % PatternEditorModel::colsPerChannel;
|
||||
std::string s = "FF";
|
||||
if (cc == 1) s = "C#2";
|
||||
else if (cc > 1 && cc % 2 == 0) s = "v";
|
||||
if (cc > 1) fm.boundingRect(QString::fromStdString(s)).size() + QSize(pad,0); // only one padding on params
|
||||
return fm.boundingRect(QString::fromStdString(s)).size() + QSize(pad*2,0);
|
||||
//return QSize(24, 16);
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
#include <QStyledItemDelegate>
|
||||
|
||||
namespace Xybrid::UI {
|
||||
class PatternEditorItemDelegate : public QStyledItemDelegate {
|
||||
Q_OBJECT
|
||||
public:
|
||||
PatternEditorItemDelegate(QWidget *parent = nullptr) : QStyledItemDelegate(parent) {}
|
||||
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||
//QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||
//void setEditorData(QWidget *editor, const QModelIndex &index) const override;
|
||||
//void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
|
||||
};
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
#include "patterneditormodel.h"
|
||||
using Xybrid::UI::PatternEditorModel;
|
||||
using Xybrid::Data::Pattern;
|
||||
|
||||
#include <QFontMetrics>
|
||||
|
||||
namespace { // helper functions
|
||||
constexpr char hexmap[] = {'0', '1', '2', '3', '4', '5', '6', '7',
|
||||
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
|
||||
};
|
||||
|
||||
constexpr char notemap[] = "A-A#B-C-C#D-D#E-F-F#G-G#";
|
||||
|
||||
std::string hexStr(unsigned char *data, uint len) {
|
||||
std::string s(len * 2, ' ');
|
||||
for (uint i = 0; i < len; ++i) {
|
||||
s[2 * i] = hexmap[(data[i] & 0xF0) >> 4];
|
||||
s[2 * i + 1] = hexmap[data[i] & 0x0F];
|
||||
}
|
||||
return s;
|
||||
}
|
||||
std::string byteStr(int t) {
|
||||
unsigned char c = static_cast<unsigned char>(t & 255);
|
||||
return hexStr(&c, 1);
|
||||
}
|
||||
|
||||
std::string noteStr(int n) {
|
||||
std::string s(3, ' ');
|
||||
int nn = n % 12;
|
||||
int oc = (n - nn) / 12;
|
||||
s[2] = '0' + static_cast<char>(oc);
|
||||
s[0] = notemap[nn*2];
|
||||
s[1] = notemap[nn*2+1];
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
PatternEditorModel::PatternEditorModel(QObject *parent)
|
||||
:QAbstractTableModel(parent) {
|
||||
}
|
||||
|
||||
int PatternEditorModel::rowCount(const QModelIndex & /*parent*/) const {
|
||||
return pattern->rows;
|
||||
}
|
||||
|
||||
int PatternEditorModel::columnCount(const QModelIndex & /*parent*/) const {
|
||||
return colsPerChannel * static_cast<int>(pattern->channels.size());
|
||||
}
|
||||
|
||||
QVariant PatternEditorModel::data(const QModelIndex &index, int role) const {
|
||||
if (role == Qt::DisplayRole) {
|
||||
int cc = index.column() % colsPerChannel;
|
||||
int ch = (index.column() - cc) / colsPerChannel;
|
||||
auto& row = pattern->channels[static_cast<unsigned long>(ch)].rows[static_cast<unsigned long>(index.row())];
|
||||
if (cc == 0) { // port
|
||||
if (row.port >= 0 && row.port < 256) return QString::fromStdString(byteStr(row.port));
|
||||
return QString("-");
|
||||
} else if (cc == 1) { // note
|
||||
if (row.note >= 0) return QString::fromStdString(noteStr(row.note));
|
||||
return QString("-");
|
||||
} else {
|
||||
int cp = ((cc - 2) - (cc % 2)) / 2;
|
||||
//return QString::number((cp));
|
||||
if (cc % 2 == 0) {
|
||||
if (row.params && row.params->size() > cp) return QString::fromStdString(std::string(1,static_cast<char>(row.params->at(cp)[0])));
|
||||
return QString("-");
|
||||
}
|
||||
if (row.params && row.params->size() > cp) return QString::fromStdString(byteStr(row.params->at(cp)[1]));
|
||||
return QString("--");
|
||||
}
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant PatternEditorModel::headerData(int section, Qt::Orientation orientation, int role) const {
|
||||
if (role == Qt::DisplayRole) {
|
||||
//if (orientation == Qt::Orientation::Horizontal) return QString(""); // avoid stretch
|
||||
return QString::number(section);
|
||||
} else if (role == Qt::SizeHintRole) {
|
||||
auto fm = QFontMetrics(QFont("Iosevka Term Light", 9));
|
||||
if (orientation == Qt::Orientation::Vertical) return fm.boundingRect("127").size() + QSize(0, 4);
|
||||
return QSize(0, fm.height() + 4);
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void PatternEditorModel::setPattern(Pattern* pattern) {
|
||||
this->pattern = pattern;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
|
||||
#include "data/pattern.h"
|
||||
|
||||
namespace Xybrid::UI {
|
||||
class PatternEditorModel : public QAbstractTableModel {
|
||||
Q_OBJECT
|
||||
|
||||
Xybrid::Data::Pattern* pattern;
|
||||
|
||||
public:
|
||||
static constexpr int paramSoftCap = 3;//16; // maximum number of parameter columns that can be displayed per channel; the rest are hidden
|
||||
static constexpr int colsPerChannel = 2 + (2 * paramSoftCap);
|
||||
|
||||
PatternEditorModel(QObject *parent);
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override ;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
|
||||
void setPattern(Xybrid::Data::Pattern* pattern);
|
||||
};
|
||||
}
|
|
@ -22,14 +22,22 @@ DEFINES += QT_DEPRECATED_WARNINGS
|
|||
# You can also select to disable deprecated APIs only up to a certain version of Qt.
|
||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||
|
||||
DISTFILES += ../.astylerc
|
||||
|
||||
CONFIG += c++17
|
||||
|
||||
SOURCES += \
|
||||
main.cpp \
|
||||
mainwindow.cpp
|
||||
mainwindow.cpp \
|
||||
data/pattern.cpp \
|
||||
ui/patterneditormodel.cpp \
|
||||
ui/patterneditoritemdelegate.cpp
|
||||
|
||||
HEADERS += \
|
||||
mainwindow.h
|
||||
mainwindow.h \
|
||||
data/pattern.h \
|
||||
ui/patterneditormodel.h \
|
||||
ui/patterneditoritemdelegate.h
|
||||
|
||||
FORMS += \
|
||||
mainwindow.ui
|
||||
|
|
Loading…
Reference in New Issue