initial pattern data copy+paste support
parent
875a65d977
commit
7001adb132
3
notes
3
notes
|
@ -46,7 +46,8 @@ project data {
|
|||
TODO {
|
||||
immediate frontburner {
|
||||
- transpose selection (alt+up/down for semitone/0x01, left+right for octave/0x10)
|
||||
pattern cut+copy+paste
|
||||
- pattern cut+copy+paste
|
||||
^ channel-column splitting
|
||||
add metadata and pattern properties (artist, song title, project bpm; pattern name, length etc.)
|
||||
|
||||
probably move the "process all nodes" part of tick processing into its own function?
|
||||
|
|
|
@ -57,7 +57,7 @@ namespace Xybrid::Data {
|
|||
else params->push_back(p);
|
||||
}
|
||||
|
||||
void insertParam(size_t index, char c = '.', unsigned char v = 0) {
|
||||
void insertParam(size_t index, char c = ' ', unsigned char v = 0) {
|
||||
std::array<unsigned char, 2> p {static_cast<unsigned char>(c), v};
|
||||
if (!this->params) this->params.reset(new std::vector<std::array<unsigned char, 2>>({p}));
|
||||
else {
|
||||
|
@ -66,6 +66,13 @@ namespace Xybrid::Data {
|
|||
}
|
||||
}
|
||||
|
||||
/// Sets parameter at given index, adding struts before if necessary
|
||||
void setParam(size_t index, char c = ' ', unsigned char v = 0) {
|
||||
if (!this->params) this->params.reset(new std::vector<std::array<unsigned char, 2>>());
|
||||
if (params->size() <= index) params->resize(index+1, {' ', 0});
|
||||
(*params)[index] = {static_cast<unsigned char>(c), v};
|
||||
}
|
||||
|
||||
void removeParam(size_t index) {
|
||||
if (!this->params || this->params->size() <= index) return; // invalid index
|
||||
if (this->params->size() == 1) { // deallocate vector entirely if empty
|
||||
|
|
|
@ -80,9 +80,7 @@ bool Xybrid::FileOps::saveProject(std::shared_ptr<Project> project, QString file
|
|||
}
|
||||
QCborArray row;
|
||||
row << r.port << r.note;
|
||||
if (r.params) for (auto p : *r.params) {
|
||||
row << p[0] << p[1];
|
||||
}
|
||||
if (r.params) for (auto p : *r.params) row << p[0] << p[1];
|
||||
rows << row;
|
||||
}
|
||||
if (!ch.rows.back().isEmpty()) needsCount = false; // omit extra count if any channel has data on the last row
|
||||
|
|
|
@ -26,6 +26,11 @@ using namespace Xybrid::Audio;
|
|||
|
||||
#include <QKeyEvent>
|
||||
#include <QShortcut>
|
||||
#include <QGuiApplication>
|
||||
#include <QClipboard>
|
||||
#include <QMimeData>
|
||||
#include <QCborArray>
|
||||
#include <QCborMap>
|
||||
#include <QDebug>
|
||||
|
||||
#include <QHeaderView>
|
||||
|
@ -90,6 +95,7 @@ PatternEditorView::PatternEditorView(QWidget *parent) : QTableView(parent) {
|
|||
hdr->setModel(&*mdl->hprox);
|
||||
|
||||
{ /* set up hotkeys */ } {
|
||||
// transpose notes
|
||||
auto transpose = [this](int amt, int key = Qt::Key_Alt) {
|
||||
auto p = mdl->getPattern();
|
||||
auto sel = this->selectionModel()->selection().first();
|
||||
|
@ -125,6 +131,83 @@ PatternEditorView::PatternEditorView(QWidget *parent) : QTableView(parent) {
|
|||
connect(new QShortcut(QKeySequence("Alt+Down"), this), &QShortcut::activated, this, [transpose] { transpose(-1, Qt::Key_Down); });
|
||||
connect(new QShortcut(QKeySequence("Alt+Right"), this), &QShortcut::activated, this, [transpose] { transpose(100, Qt::Key_Right); });
|
||||
connect(new QShortcut(QKeySequence("Alt+Left"), this), &QShortcut::activated, this, [transpose] { transpose(-100, Qt::Key_Left); });
|
||||
|
||||
// cut/copy/paste
|
||||
auto copy = [this] {
|
||||
auto* clip = QGuiApplication::clipboard();
|
||||
auto* data = new QMimeData();
|
||||
|
||||
auto p = mdl->getPattern();
|
||||
auto sel = selectionModel()->selection()[0];
|
||||
auto chMin = Util::channelForColumn(sel.left());
|
||||
auto chMax = Util::channelForColumn(sel.right());
|
||||
|
||||
// cbor data format: (may change between versions, assume a given system only has one Xybrid version installed)
|
||||
// [ integer first, last, n*[ integer port, note, (param, val)... ]... ]...
|
||||
QCborArray root;
|
||||
|
||||
for (int ch = chMin; ch <= chMax; ch++) {
|
||||
QCborArray chm;
|
||||
int first = 0; // TODO ... later
|
||||
int last = 255;
|
||||
chm << first << last;
|
||||
for (int r = sel.top(); r <= sel.bottom(); r++) {
|
||||
QCborArray rm;
|
||||
auto& row = p->rowAt(ch, r);
|
||||
rm << row.port;
|
||||
rm << row.note;
|
||||
if (row.params) for (auto p : *row.params) rm << p[0] << p[1];
|
||||
chm << rm;
|
||||
}
|
||||
root << chm;
|
||||
}
|
||||
|
||||
data->setData("xybrid-internal/x-pattern-copy", root.toCborValue().toCbor());
|
||||
clip->setMimeData(data);
|
||||
};
|
||||
|
||||
connect(new QShortcut(QKeySequence(QKeySequence::StandardKey::Cut), this), &QShortcut::activated, this, [this, copy] {
|
||||
copy();
|
||||
this->edit(currentIndex(), EditTrigger::EditKeyPressed, new QKeyEvent(QKeyEvent::Type::KeyPress, Qt::Key_Delete, Qt::KeyboardModifier::NoModifier));
|
||||
});
|
||||
connect(new QShortcut(QKeySequence(QKeySequence::StandardKey::Copy), this), &QShortcut::activated, this, [copy] { copy(); });
|
||||
connect(new QShortcut(QKeySequence(QKeySequence::StandardKey::Paste), this), &QShortcut::activated, this, [this] {
|
||||
const auto* clip = QGuiApplication::clipboard();
|
||||
const auto* data = clip->mimeData();
|
||||
|
||||
if (!data->hasFormat("xybrid-internal/x-pattern-copy")) return; // no pattern data
|
||||
|
||||
auto p = mdl->getPattern();
|
||||
auto idx = currentIndex();
|
||||
auto chMin = Util::channelForColumn(idx.column());
|
||||
auto rMin = idx.row();
|
||||
|
||||
auto root = QCborValue::fromCbor(data->data("xybrid-internal/x-pattern-copy")).toArray();
|
||||
|
||||
auto cc = new CompositeCommand();
|
||||
|
||||
for (int ch = 0; ch < static_cast<int>(p->numChannels()) - chMin && ch < root.size(); ch++) {
|
||||
auto chm = root[ch].toArray();
|
||||
|
||||
//int first = static_cast<int>(chm[0].toInteger());
|
||||
//int last = static_cast<int>(chm[1].toInteger());
|
||||
|
||||
for (int r = 0; r < p->rows - rMin && r < chm.size() - 2; r++) {
|
||||
auto rm = chm[r+2].toArray();
|
||||
auto c = new PatternDeltaCommand(p, ch+chMin, r+rMin);
|
||||
auto& row = c->row;
|
||||
|
||||
row.port = static_cast<int16_t>(rm[0].toInteger());
|
||||
row.note = static_cast<int16_t>(rm[1].toInteger());
|
||||
for (int p = 0; p < (rm.size() - 2) / 2; p++)
|
||||
row.setParam(static_cast<size_t>(p), static_cast<char>(rm[p*2+2].toInteger()), static_cast<unsigned char>(rm[p*2+3].toInteger()));
|
||||
|
||||
cc->compose(c);
|
||||
}
|
||||
}
|
||||
|
||||
cc->commit("paste pattern data");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue