bunch of UI work; now has a working pattern viewer!

portability/boost
zetaPRIME 2018-11-22 06:36:27 -05:00
parent d3005dc72a
commit 29b71cfaf8
13 changed files with 453 additions and 31 deletions

3
.astylerc Normal file
View File

@ -0,0 +1,3 @@
# attached braces
style=attach
indent-namespaces

26
notes
View File

@ -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
}
}
}
}

27
xybrid/data/pattern.cpp Normal file
View File

@ -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));
}
}

39
xybrid/data/pattern.h Normal file
View File

@ -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);
};
}

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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

View File

@ -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>&amp;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/>

View File

@ -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);
}

View File

@ -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;
};
}

View File

@ -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;
}

View File

@ -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);
};
}

View File

@ -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