sequence rewrite, part 2 (the big one!)

portability/boost
zetaPRIME 2019-07-08 04:50:48 -04:00
parent a7921f8173
commit 467c1009ea
10 changed files with 75 additions and 59 deletions

View File

@ -132,7 +132,7 @@ void AudioEngine::play(std::shared_ptr<Project> p, int fromPos) {
}
seqPos = fromPos;
curRow = 0;
curRow = -1;
curTick = -2;
tempo = project->tempo;
tickAcc = 0;
@ -352,6 +352,25 @@ qint64 AudioEngine::readData(char *data, qint64 maxlen) {
return maxlen - sr;
}
Pattern* AudioEngine::findPattern(int adv) {
seqPos += adv;
for (int tr = 0; tr < 25; tr++) {
if (seqPos < 0) seqPos = 0;
SequenceEntry* s = nullptr;
if (auto sp = static_cast<size_t>(seqPos); sp < project->sequence.size()) s = &project->sequence[sp];
if (mode == Rendering && !s) return nullptr; // stop
else if (mode == Rendering && s->type == SequenceEntry::LoopTrigger) { seqPos++; continue; }
else if (!s || s->type == SequenceEntry::LoopTrigger) { // off end or explicit loop, find loop point
for (seqPos = std::min(seqPos, static_cast<int>(project->sequence.size()) - 1); seqPos >= 0; --seqPos)
if (project->sequence[static_cast<size_t>(seqPos)].type == SequenceEntry::LoopStart) break;
continue;
} else if (s->type == SequenceEntry::Pattern) return s->pattern().get();
else { seqPos++; continue; }
}
return nullptr; // out of tries
}
void AudioEngine::nextTick() {
bufPos = 0;
@ -404,31 +423,21 @@ void AudioEngine::nextTick() {
Pattern* p = nullptr;
Pattern* pOld = nullptr;
auto setP = [&] {
if (seqPos >= 0 && seqPos < static_cast<int>(project->sequence.size())) p = project->sequence[static_cast<size_t>(seqPos)];
else p = nullptr;
};
setP();
p = findPattern();
bool newRow = false;
bool newPattern = false;
auto advanceSeq = [&] {
pOld = p;
p = nullptr;
int tries = 0;
while (!p) {
seqPos++;
if (mode == Playing) seqPos %= static_cast<int>(project->sequence.size());
else if (seqPos > static_cast<int>(project->sequence.size())) { stop(); return; } // stop at end if rendering
setP();
if (++tries > 25) return; // either you have 25 separators in a row, or you have no patterns
}
p = findPattern(1);
if (!p) { stop(); return; }
curRow = 0;
// set pattern things
if (p->tempo > 0) tempo = p->tempo;
newPattern = true;
newRow = true;
};
auto advanceRow = [&] {
curTick = 0;
@ -553,9 +562,9 @@ void AudioEngine::nextTick() {
}
};
curTick++;
if (!p || curTick >= p->time.ticksPerRow) advanceRow();
if (!p) return; // no patterns to be found, abort
curTick++;
if (curTick >= p->time.ticksPerRow) advanceRow();
// (sample rate / seconds per beat) / ticks per beat
double tickSize = (1.0 * sampleRate / (static_cast<double>(tempo)/60.0)) / (p->time.rowsPerBeat * p->time.ticksPerRow);

View File

@ -15,6 +15,7 @@
class QThread;
namespace Xybrid::Data {
class Project;
class Pattern;
class Node;
}
namespace Xybrid::Audio {
@ -94,6 +95,7 @@ namespace Xybrid::Audio {
void postInit();
void initAudio(bool startNow = false);
void deinitAudio();
Data::Pattern* findPattern(int = 0);
void nextTick();
void processNodes();
public:

View File

@ -24,7 +24,7 @@ namespace Xybrid::Data {
constexpr bool operator==(const TimeSignature& o) const { return beatsPerMeasure == o.beatsPerMeasure && rowsPerBeat == o.rowsPerBeat && ticksPerRow == o.ticksPerRow; }
constexpr bool operator!=(const TimeSignature& o) const { return !(*this == o); }
};
class Pattern {
class Pattern : public std::enable_shared_from_this<Pattern> {
public:
class Row { // with std::unique_ptr<std::vector>, each Row is 12 bytes inline on 64-bit (8 bytes on 32)
public:

View File

@ -12,6 +12,7 @@
#include "data/pattern.h"
#include "data/sample.h"
#include "data/sequenceentry.h"
namespace Xybrid {
class UISocket;
@ -37,7 +38,7 @@ namespace Xybrid::Data {
// shared to ease reordering and prevent crashes due to invalidating things that UI stuff is using
std::vector<std::shared_ptr<Pattern>> patterns;
std::vector<Pattern*> sequence; // nullptr as separator
std::vector<SequenceEntry> sequence;
std::shared_ptr<Graph> rootGraph;
// list of input nodes is just part of rootGraph

View File

@ -19,9 +19,17 @@ namespace Xybrid::Data {
std::weak_ptr<Data::Pattern> p;
SequenceEntry() = default;
inline SequenceEntry() = default;
inline SequenceEntry(std::shared_ptr<Data::Pattern> p) : type(Pattern), p(p) { }
inline SequenceEntry(Type t) : type(t) { }
inline SequenceEntry(const SequenceEntry&) = default;
inline SequenceEntry(SequenceEntry&&) = default;
SequenceEntry& operator=(const SequenceEntry&) = default;
// required for matching when deleting a pattern
inline bool operator==(std::shared_ptr<Data::Pattern> o) const { return type == Pattern && p.lock() == o; }
inline bool operator==(Data::Pattern* o) const { return type == Pattern && p.lock().get() == o; }
SequenceEntry(Project*, const QCborValue&);
operator QCborValue() const;

View File

@ -100,7 +100,7 @@ void ProjectPatternAddCommand::redo() {
if (copyOf) *np = *copyOf;
project->updatePatternIndices();
if (atSeq > -1) {
project->sequence.insert(project->sequence.begin() + atSeq, np.get());
project->sequence.insert(project->sequence.begin() + atSeq, np);
emit project->socket->updatePatternLists();
project->socket->window->patternSelection(at);
project->socket->window->sequenceSelection(atSeq);
@ -117,9 +117,9 @@ void ProjectPatternAddCommand::undo() {
else if (copyOf) project->socket->window->patternSelection(static_cast<int>(copyOf->index));
}
ProjectPatternDeleteCommand::ProjectPatternDeleteCommand(const std::shared_ptr<Project>& project, const std::shared_ptr<Pattern>& pattern) {
this->project = project;
this->pattern = pattern;
ProjectPatternDeleteCommand::ProjectPatternDeleteCommand(const std::shared_ptr<Project>& project_, const std::shared_ptr<Pattern>& pattern_) {
project = project_;
pattern = pattern_;
setText("delete pattern");
oldSeq = project->sequence;
@ -128,8 +128,8 @@ ProjectPatternDeleteCommand::ProjectPatternDeleteCommand(const std::shared_ptr<P
void ProjectPatternDeleteCommand::redo() {
int seqSel = oldSeqSel;
for (int i = 0; i <= oldSeqSel; i++) if (seqSel > 0 && project->sequence[static_cast<size_t>(i)] == pattern.get()) seqSel--;
project->sequence.erase(std::remove(project->sequence.begin(), project->sequence.end(), pattern.get()), project->sequence.end());
for (int i = 0; i <= oldSeqSel; i++) if (seqSel > 0 && project->sequence[static_cast<size_t>(i)].pattern() == pattern) seqSel--;
project->sequence.erase(std::remove(project->sequence.begin(), project->sequence.end(), pattern), project->sequence.end());
project->patterns.erase(project->patterns.begin() + static_cast<ptrdiff_t>(pattern->index));
project->updatePatternIndices();
emit project->socket->updatePatternLists();

View File

@ -19,10 +19,10 @@ namespace Xybrid::Editing {
};
class ProjectSequencerDeltaCommand : public ProjectCommand {
std::vector<Data::Pattern*> oldSeq;
std::vector<Data::SequenceEntry> oldSeq;
int oldSeqSel;
public:
std::vector<Data::Pattern*> seq;
std::vector<Data::SequenceEntry> seq;
int seqSel;
ProjectSequencerDeltaCommand(const std::shared_ptr<Data::Project>& project);
@ -61,7 +61,7 @@ namespace Xybrid::Editing {
class ProjectPatternDeleteCommand : public ProjectCommand {
std::shared_ptr<Data::Pattern> pattern;
std::vector<Data::Pattern*> oldSeq;
std::vector<Data::SequenceEntry> oldSeq;
int oldSeqSel;
public:
ProjectPatternDeleteCommand(const std::shared_ptr<Data::Project>& project, const std::shared_ptr<Data::Pattern>& pattern);

View File

@ -81,10 +81,7 @@ bool FileOps::saveProject(std::shared_ptr<Project> project, QString fileName) {
{ /* Sequence */ } {
QCborArray seq;
for (auto s : project->sequence) {
if (!s) seq << -1;
else seq << static_cast<int>(s->index);
}
for (auto& s : project->sequence) seq << s;
main[qs("sequence")] = seq;
}
@ -238,11 +235,7 @@ std::shared_ptr<Project> FileOps::loadProject(QString fileName) {
{ /* Sequence */ } {
QCborArray seq = main.value("sequence").toArray();
project->sequence.reserve(static_cast<size_t>(seq.size()));
for (auto s : seq) {
size_t ss = static_cast<size_t>(s.toInteger());
if (ss >= project->patterns.size()) project->sequence.push_back(nullptr);
else project->sequence.push_back(project->patterns[ss].get());
}
for (auto s : seq) project->sequence.emplace_back(project.get(), QCborValue(s));
}
{ /* Samples */ } {

View File

@ -46,6 +46,7 @@ using Xybrid::MainWindow;
using Xybrid::Data::Project;
using Xybrid::Data::Pattern;
using Xybrid::Data::SequenceEntry;
using Xybrid::Data::Graph;
using Xybrid::Data::Node;
using Xybrid::Data::Port;
@ -203,7 +204,7 @@ MainWindow::MainWindow(QWidget *parent) :
connect(ui->patternSequencer->selectionModel(), &QItemSelectionModel::currentChanged, this, [this](const QModelIndex& index, const QModelIndex&) {
size_t idx = static_cast<size_t>(index.column());
if (idx >= project->sequence.size()) return;
this->selectPatternForEditing(project->sequence[idx]);
this->selectPatternForEditing(project->sequence[idx].pattern().get());
});
// rightclick menu
@ -215,14 +216,14 @@ MainWindow::MainWindow(QWidget *parent) :
if (!editingPattern->validFor(project)) return; // nope
int si = static_cast<int>(std::min(idx, project->sequence.size()));
auto* c = new ProjectSequencerDeltaCommand(project);
c->seq.insert(c->seq.begin() + si, editingPattern.get());
c->seq.insert(c->seq.begin() + si, editingPattern);
c->seqSel = si+1;
c->commit();
});
menu->addAction("Insert Separator", this, [this, idx] {
int si = static_cast<int>(std::min(idx, project->sequence.size()));
auto* c = new ProjectSequencerDeltaCommand(project);
c->seq.insert(c->seq.begin() + si, nullptr);
c->seq.insert(c->seq.begin() + si, { });
c->seqSel = si+1;
c->commit();
});
@ -237,14 +238,14 @@ MainWindow::MainWindow(QWidget *parent) :
int si = static_cast<int>(std::min(idx, project->sequence.size()));
(new ProjectPatternAddCommand(project, -1, si))->commit();
});
if (idx < project->sequence.size() && project->sequence[idx]) {
menu->addAction("Duplicate Pattern", this, [this, idx, p = project->patterns[project->sequence[idx]->index]] {
int si = static_cast<int>(std::min(idx + 1, project->sequence.size()));
(new ProjectPatternAddCommand(project, static_cast<int>(p->index) + 1, si, p))->commit();
});
menu->addSeparator();
menu->addAction("Properties...", this, [this, p = project->patterns[project->sequence[idx]->index]] { openPatternProperties(p); });
}
if (idx < project->sequence.size()) if (auto p = project->sequence[idx].pattern(); p) {
menu->addAction("Duplicate Pattern", this, [this, idx, p] {
int si = static_cast<int>(std::min(idx + 1, project->sequence.size()));
(new ProjectPatternAddCommand(project, static_cast<int>(p->index) + 1, si, p))->commit();
});
menu->addSeparator();
menu->addAction("Properties...", this, [this, p] { openPatternProperties(p); });
}
menu->setAttribute(Qt::WA_DeleteOnClose);
menu->popup(ui->patternSequencer->mapToGlobal(pt));
@ -426,7 +427,7 @@ void MainWindow::menuFileNew() {
auto hold = project; // keep alive until done
if (audioEngine->playingProject() == project) audioEngine->stop();
project = std::make_shared<Project>();
project->sequence.push_back(project->newPattern().get());
project->sequence.emplace_back(project->newPattern());
onNewProjectLoaded();
}
@ -472,8 +473,8 @@ void MainWindow::onNewProjectLoaded() {
updatePatternLists();
patternSelection(0);
sequenceSelection(-1);
for (size_t i = 0; i < project->sequence.size(); i++) { // find first non-spacer in sequence, else fall back to first pattern numerically
if (project->sequence[i] == nullptr) continue;
for (size_t i = 0; i < project->sequence.size(); i++) { // find first actual pattern in sequence, else fall back to first pattern numerically
if (!project->sequence[i].pattern()) continue;
sequenceSelection(static_cast<int>(i));
break;
}

View File

@ -5,6 +5,7 @@ using Xybrid::UI::PatternSequencerModel;
using Xybrid::Data::Pattern;
using Xybrid::Data::Project;
using Xybrid::Data::SequenceEntry;
#include "mainwindow.h"
@ -30,13 +31,14 @@ QVariant PatternSequencerModel::data(const QModelIndex &index, int role) const {
if (!project) return QVariant();
if (role == Qt::DisplayRole || role == Qt::ToolTipRole) {
bool toolTip = (role == Qt::ToolTipRole);
if (static_cast<size_t>(index.column()) >= project->sequence.size())
return !toolTip ? QString("+") : QString("Add new");
auto* pattern = project->sequence[static_cast<size_t>(index.column())];
if (static_cast<size_t>(index.column()) >= project->sequence.size()) return !toolTip ? QString("+") : QString("Add new");
auto& e = project->sequence[static_cast<size_t>(index.column())];
return toolTip ? e.toolTip() : e.symbol();
/*auto& pattern = project->sequence[static_cast<size_t>(index.column())];
if (!pattern) return !toolTip ? QString("-") : QString("(separator)");
if (!toolTip) return QString("%1").arg(pattern->index, 1, 10, QChar('0'));
if (pattern->name.isEmpty()) return QVariant(); // no tool tip without name
return QString("(%1) %2").arg(pattern->index, 1, 10, QChar('0')).arg(pattern->name);
return QString("(%1) %2").arg(pattern->index, 1, 10, QChar('0')).arg(pattern->name);*/
}
if (role == Qt::TextAlignmentRole ) return Qt::AlignHCenter + Qt::AlignVCenter;
return QVariant();
@ -91,7 +93,7 @@ bool PatternSequencerModel::dropMimeData(const QMimeData *data, Qt::DropAction a
if (column < 0 || column > static_cast<int>(p->project->sequence.size())) column = static_cast<int>(p->project->sequence.size()); // if dropped on empty space, snap to end
auto* c = new ProjectSequencerDeltaCommand(window->getProject());
c->seq.insert(c->seq.begin() + column, p);
c->seq.insert(c->seq.begin() + column, p->shared_from_this());
c->seqSel = column;
return c->commit();
@ -120,9 +122,9 @@ bool PatternSequencerModel::dropMimeData(const QMimeData *data, Qt::DropAction a
if (!copy && column > static_cast<int>(idx)) column -= 1; // compensate ahead of time for snap-out
auto* c = new ProjectSequencerDeltaCommand(window->getProject());
Pattern* p = c->seq[idx];
SequenceEntry s = c->seq[idx];
if (!copy) c->seq.erase(c->seq.begin() + static_cast<int>(idx));
c->seq.insert(c->seq.begin() + column, p);
c->seq.insert(c->seq.begin() + column, s);
c->seqSel = column;
return c->commit();