From 47591ea3d1d8533863b02c3850fc60a1fa5ca69f Mon Sep 17 00:00:00 2001 From: zetaPRIME Date: Sun, 23 Jun 2019 03:57:40 -0400 Subject: [PATCH] import/export samples with nodes (both node files and copy/paste) --- xybrid/data/node.cpp | 25 +++++++++++++++++++++++++ xybrid/data/sample.cpp | 27 +++++++++++++++++++++++++++ xybrid/data/sample.h | 4 ++++ xybrid/fileops.cpp | 22 ++++++++++++++++++++++ xybrid/mainwindow.cpp | 2 +- xybrid/mainwindow.h | 2 ++ xybrid/nodes/sampler/beatpad.cpp | 2 +- xybrid/ui/samplelistmodel.cpp | 4 ++++ xybrid/uisocket.h | 2 ++ 9 files changed, 88 insertions(+), 2 deletions(-) diff --git a/xybrid/data/node.cpp b/xybrid/data/node.cpp index b180aea..aca1e97 100644 --- a/xybrid/data/node.cpp +++ b/xybrid/data/node.cpp @@ -1,6 +1,7 @@ #include "node.h" using namespace Xybrid::Data; +#include "data/project.h" #include "data/graph.h" #include "data/porttypes.h" @@ -15,6 +16,8 @@ using namespace Xybrid::Audio; #include "util/strings.h" #include "util/ycombinator.h" +#include "uisocket.h" + #include #include @@ -108,6 +111,8 @@ std::shared_ptr Node::fromCbor(const QCborValue& m, std::shared_ptr QCborMap Node::multiToCbor(std::vector>& v) { QCborMap m; + Sample::startExport(); + std::unordered_map indices; { /* nodes */ } { QCborArray nm; @@ -120,6 +125,13 @@ QCborMap Node::multiToCbor(std::vector>& v) { m[qs("nodes")] = nm; } + // exported samples + if (auto v = Sample::finishExport(); !v.empty()) { + QCborMap smp; + for (auto s : v) smp[QCborValue(s->uuid)] = s->toCbor(); + m[qs("samples")] = smp; + } + { /* connections */ } { QCborArray cm; @@ -176,6 +188,19 @@ std::vector> Node::multiFromCbor(const QCborMap& m, std::s center.setY(static_cast(c.at(1).toInteger())); } + { /* exported samples */ } { + QCborMap smp = m.value("samples").toMap(); + auto project = parent->project; + for (auto it = smp.constBegin(), end = smp.constEnd(); it != end; ++it) { + auto uuid = it.key().toUuid(); + if (project->samples.find(uuid) != project->samples.end()) continue; // we already have this; next + auto s = Sample::fromCbor(it.value(), uuid); + s->project = project; + project->samples.insert(s->uuid, s); + } + emit project->socket->updatePatternLists(); + } + { /* nodes */ } { QCborArray n = m.value("nodes").toArray(); v.reserve(static_cast(n.size())); diff --git a/xybrid/data/sample.cpp b/xybrid/data/sample.cpp index 86a41d7..a19265f 100644 --- a/xybrid/data/sample.cpp +++ b/xybrid/data/sample.cpp @@ -154,3 +154,30 @@ std::shared_ptr Sample::fromFile(QString fileName) { return smp; } + +namespace { + bool exporting = false; + std::unordered_map exportMap; +} + +void Sample::startExport() { + exporting = true; + exportMap.reserve(16); +} + +std::vector> Sample::finishExport() { + std::vector> v; + if (exporting) { + exporting = false; + v.reserve(exportMap.size()); + for (auto it : exportMap) v.push_back(it.first->shared_from_this()); + exportMap.clear(); + } + return v; +} + +void Sample::markForExport() { + if (exporting) { + exportMap[this] = true; + } +} diff --git a/xybrid/data/sample.h b/xybrid/data/sample.h index 07a6ea2..08d290d 100644 --- a/xybrid/data/sample.h +++ b/xybrid/data/sample.h @@ -49,5 +49,9 @@ namespace Xybrid::Data { static std::shared_ptr fromFile(QString); + static void startExport(); + static std::vector> finishExport(); + void markForExport(); + }; } diff --git a/xybrid/fileops.cpp b/xybrid/fileops.cpp index b19f23d..8ea141f 100644 --- a/xybrid/fileops.cpp +++ b/xybrid/fileops.cpp @@ -267,10 +267,19 @@ bool FileOps::saveNode(std::shared_ptr node, QString fileName) { QFile file(fileName); if (!file.open(QFile::WriteOnly)) return false; + Sample::startExport(); + // we handle header and let the node handle the rest of its conversion QCborArray root; root << "xybrid:node" << XYBRID_VERSION << node->toCbor(); + // and write in any exported samples + if (auto v = Sample::finishExport(); !v.empty()) { + QCborMap smp; + for (auto s : v) smp[QCborValue(s->uuid)] = s->toCbor(); + root << smp; + } + // write out QCborStreamWriter w(&file); root.toCborValue().toCbor(w); @@ -296,6 +305,19 @@ std::shared_ptr FileOps::loadNode(QString fileName, std::shared_ptr if (auto v = root.at(1); !v.isInteger() || v.toInteger() > XYBRID_VERSION) return nullptr; // invalid version or too new if (!root.at(2).isMap()) return nullptr; // so close, but... nope + if (root.at(3).isMap()) { // node file has samples, load in any we don't already have + auto smp = root.at(3).toMap(); + auto project = parent->project; + for (auto it = smp.constBegin(), end = smp.constEnd(); it != end; ++it) { + auto uuid = it.key().toUuid(); + if (project->samples.find(uuid) != project->samples.end()) continue; // we already have this; next + auto s = Sample::fromCbor(it.value(), uuid); + s->project = project; + project->samples.insert(s->uuid, s); + } + emit project->socket->updatePatternLists(); + } + return Node::fromCbor(root.at(2), parent); // let Node handle the rest } diff --git a/xybrid/mainwindow.cpp b/xybrid/mainwindow.cpp index ac7ce57..c22101f 100644 --- a/xybrid/mainwindow.cpp +++ b/xybrid/mainwindow.cpp @@ -71,6 +71,7 @@ namespace { MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { + socket = new UISocket(); // create this first ui->setupUi(this); // remove tab containing system widgets @@ -337,7 +338,6 @@ MainWindow::MainWindow(QWidget *parent) : } // Set up signaling from project to UI - socket = new UISocket(); socket->setParent(this); socket->window = this; socket->undoStack = undoStack; diff --git a/xybrid/mainwindow.h b/xybrid/mainwindow.h index 1662bd8..a3ff4d7 100644 --- a/xybrid/mainwindow.h +++ b/xybrid/mainwindow.h @@ -53,6 +53,8 @@ namespace Xybrid { void playbackPosition(int seq, int row); + inline UISocket* uiSocket() { return socket; } + protected: void closeEvent(QCloseEvent*) override; bool eventFilter(QObject *obj, QEvent *event) override; diff --git a/xybrid/nodes/sampler/beatpad.cpp b/xybrid/nodes/sampler/beatpad.cpp index e92ef5e..abfdf15 100644 --- a/xybrid/nodes/sampler/beatpad.cpp +++ b/xybrid/nodes/sampler/beatpad.cpp @@ -130,7 +130,6 @@ void BeatPad::release() { core.release(); } void BeatPad::process() { core.process(this); } void BeatPad::saveData(QCborMap& m) const { - // TODO: mark samples for inclusion in export QCborMap cm; for (auto c : cfg) { if (auto smp = c.second->smp.lock(); smp) { @@ -139,6 +138,7 @@ void BeatPad::saveData(QCborMap& m) const { e[qs("start")] = static_cast(c.second->start); e[qs("end")] = static_cast(c.second->end); cm[c.first] = e; + smp->markForExport(); } } m[qs("notecfg")] = cm; diff --git a/xybrid/ui/samplelistmodel.cpp b/xybrid/ui/samplelistmodel.cpp index 9f941b7..bfaf6ed 100644 --- a/xybrid/ui/samplelistmodel.cpp +++ b/xybrid/ui/samplelistmodel.cpp @@ -6,6 +6,8 @@ using Xybrid::Data::Project; using Xybrid::Data::Pattern; using Xybrid::Data::Sample; +#include "uisocket.h" + #include "editing/projectcommands.h" #include "editing/patterncommands.h" using namespace Xybrid::Editing; @@ -48,6 +50,8 @@ SampleListModel::SampleListModel(QObject* parent, MainWindow* window) : QAbstrac menu->popup(view->mapToGlobal(pt)); }); + + connect(window->uiSocket(), &UISocket::updatePatternLists, this, [this] { refresh(); }); } std::shared_ptr SampleListModel::itemAt(const QModelIndex& ind) { diff --git a/xybrid/uisocket.h b/xybrid/uisocket.h index 51a94e8..db533cb 100644 --- a/xybrid/uisocket.h +++ b/xybrid/uisocket.h @@ -24,6 +24,8 @@ namespace Xybrid { void patternUpdated(Data::Pattern* pattern); void rowUpdated(Data::Pattern* pattern, int channel, int row); + void sampleListUpdated(); + void openGraph(Data::Graph*); void openNodeUI(Data::Node*); };