debug optimization, connection guards, playback fixes and UI hookups
parent
6a091c5ea6
commit
76568244ff
|
@ -3,6 +3,9 @@
|
|||
using namespace Xybrid::Audio;
|
||||
using namespace Xybrid::Data;
|
||||
|
||||
#include "mainwindow.h"
|
||||
#include "uisocket.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
|
@ -88,7 +91,10 @@ void AudioEngine::play(std::shared_ptr<Project> p) {
|
|||
// stop and reset, then init playback
|
||||
|
||||
initAudio();
|
||||
for (auto& b : buffer) b.reserve(static_cast<size_t>(sampleRate/4));
|
||||
for (auto& b : buffer) {
|
||||
b.clear();
|
||||
b.reserve(static_cast<size_t>(sampleRate/4));
|
||||
}
|
||||
|
||||
seqPos = -1;
|
||||
tempo = project->tempo;
|
||||
|
@ -99,6 +105,7 @@ void AudioEngine::play(std::shared_ptr<Project> p) {
|
|||
//tickId = 0; // actually, no reason to reset this
|
||||
|
||||
mode = Playing;
|
||||
emit this->playbackModeChanged(mode);
|
||||
}, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
|
@ -107,6 +114,7 @@ void AudioEngine::stop() {
|
|||
project = nullptr;
|
||||
deinitAudio();
|
||||
mode = Stopped;
|
||||
emit this->playbackModeChanged(mode);
|
||||
}, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
|
@ -182,6 +190,8 @@ void AudioEngine::nextTick() {
|
|||
curTick = 0;
|
||||
curRow++;
|
||||
if (!p || curRow >= p->rows) advanceSeq();
|
||||
MainWindow* w = project->socket->window;
|
||||
QMetaObject::invokeMethod(w, [this, w]{ w->playbackPosition(seqPos, curRow); }, Qt::QueuedConnection);
|
||||
};
|
||||
|
||||
curTick++;
|
||||
|
@ -196,15 +206,13 @@ void AudioEngine::nextTick() {
|
|||
size_t ts = static_cast<size_t>(tickSf);
|
||||
buffer[0].resize(ts);
|
||||
buffer[1].resize(ts);
|
||||
qDebug() << "tick" << tickId << "contains"<<ts<<"samples";
|
||||
//qDebug() << "tick" << tickId << "contains"<<ts<<"samples";
|
||||
|
||||
// test
|
||||
const double PI = std::atan(1)*4;
|
||||
const double SEMI = std::pow(2.0, 1.0/12.0);
|
||||
double time = 0;
|
||||
//int note = static_cast<int>(random() % 5);
|
||||
static int note = -1;
|
||||
note = (note+1) % 6;
|
||||
int note = curRow % 4;
|
||||
for (size_t i = 0; i < ts; i++) {
|
||||
|
||||
buffer[0][i] = static_cast<float>(std::sin(time * PI*2 * 440 * std::pow(SEMI, -6 + note * 5)) * .25);
|
||||
|
|
|
@ -5,6 +5,9 @@ using Xybrid::Data::Pattern;
|
|||
#include "data/graph.h"
|
||||
using Xybrid::Data::Graph;
|
||||
|
||||
#include "audio/audioengine.h"
|
||||
using namespace Xybrid::Audio;
|
||||
|
||||
Project::Project() {
|
||||
rootGraph = std::make_shared<Graph>();
|
||||
}
|
||||
|
@ -14,6 +17,10 @@ Project::~Project() {
|
|||
for (auto& pat : patterns) pat->project = nullptr;
|
||||
}
|
||||
|
||||
bool Project::editingLocked() {
|
||||
return (audioEngine->playingProject().get() == this && (audioEngine->playbackMode() == AudioEngine::Playing || audioEngine->playbackMode() == AudioEngine::Paused));
|
||||
}
|
||||
|
||||
void Project::updatePatternIndices() {
|
||||
for (size_t i = 0; i < patterns.size(); i++) patterns[i]->index = i;
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace Xybrid::Data {
|
|||
class Graph;
|
||||
class Project {
|
||||
public:
|
||||
bool editingLocked = false;
|
||||
bool editingLocked();
|
||||
|
||||
UISocket* socket;
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
|
||||
undoStack = new QUndoStack(this);
|
||||
//undoStack->setUndoLimit(256);
|
||||
connect(undoStack, &QUndoStack::cleanChanged, [this](bool) {
|
||||
connect(undoStack, &QUndoStack::cleanChanged, this, [this](bool) {
|
||||
updateTitle();
|
||||
});
|
||||
|
||||
|
@ -69,7 +69,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
|
||||
// prevent right pane of pattern view from being collapsed
|
||||
ui->patternViewSplitter->setCollapsible(1, false);
|
||||
connect(ui->patternViewSplitter, &QSplitter::splitterMoved, [this](int, int) {
|
||||
connect(ui->patternViewSplitter, &QSplitter::splitterMoved, this, [this](int, int) {
|
||||
// and when the list is collapsed, make sure header size is updated
|
||||
ui->patternEditor->updateHeader();
|
||||
});
|
||||
|
@ -80,33 +80,33 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
|
||||
// events
|
||||
// on selection change
|
||||
connect(ui->patternList->selectionModel(), &QItemSelectionModel::currentChanged, [this](const QModelIndex& index, const QModelIndex& old) {
|
||||
connect(ui->patternList->selectionModel(), &QItemSelectionModel::currentChanged, this, [this](const QModelIndex& index, const QModelIndex& old) {
|
||||
if (index == old) return; // no actual change
|
||||
size_t idx = static_cast<size_t>(index.row());
|
||||
if (idx >= project->patterns.size()) return;
|
||||
this->selectPatternForEditing(project->patterns[idx].get());
|
||||
});
|
||||
// on click
|
||||
connect(ui->patternList, &QListView::clicked, [this]() { // deselect on sequencer when list clicked
|
||||
connect(ui->patternList, &QListView::clicked, this, [this]() { // deselect on sequencer when list clicked
|
||||
ui->patternSequencer->setCurrentIndex(ui->patternSequencer->model()->index(0, -1));
|
||||
});
|
||||
|
||||
// rightclick menu
|
||||
connect(ui->patternList, &QListView::customContextMenuRequested, [this](const QPoint& pt) {
|
||||
connect(ui->patternList, &QListView::customContextMenuRequested, this, [this](const QPoint& pt) {
|
||||
size_t idx = static_cast<size_t>(ui->patternList->indexAt(pt).row());
|
||||
std::shared_ptr<Pattern> p = nullptr;
|
||||
if (idx < project->patterns.size()) p = project->patterns[idx];
|
||||
|
||||
QMenu* menu = new QMenu(this);
|
||||
menu->addAction("New Pattern", [this, idx]() {
|
||||
menu->addAction("New Pattern", this, [this, idx]() {
|
||||
(new ProjectPatternAddCommand(project, static_cast<int>(idx)))->commit();
|
||||
});
|
||||
if (p) {
|
||||
menu->addAction("Duplicate Pattern", [this, p, idx]() {
|
||||
menu->addAction("Duplicate Pattern", this, [this, p, idx]() {
|
||||
(new ProjectPatternAddCommand(project, static_cast<int>(idx) + 1, -1, p))->commit();
|
||||
});
|
||||
menu->addSeparator();
|
||||
menu->addAction("Delete Pattern", [this, p]() {
|
||||
menu->addAction("Delete Pattern", this, [this, p]() {
|
||||
if (QMessageBox::warning(this, "Are you sure?", QString("Remove pattern %1?").arg(Util::numAndName(p->index, p->name)), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) return;
|
||||
(new ProjectPatternDeleteCommand(project, p))->commit();
|
||||
});
|
||||
|
@ -126,18 +126,18 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
|
||||
// events
|
||||
// on selection change
|
||||
connect(ui->patternSequencer->selectionModel(), &QItemSelectionModel::currentChanged, [this](const QModelIndex& index, const QModelIndex&) {
|
||||
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]);
|
||||
});
|
||||
|
||||
// rightclick menu
|
||||
connect(ui->patternSequencer, &QTableView::customContextMenuRequested, [this](const QPoint& pt) {
|
||||
connect(ui->patternSequencer, &QTableView::customContextMenuRequested, this, [this](const QPoint& pt) {
|
||||
size_t idx = static_cast<size_t>(ui->patternSequencer->indexAt(pt).column());
|
||||
|
||||
QMenu* menu = new QMenu(this);
|
||||
menu->addAction("Insert Pattern", [this, idx]() {
|
||||
menu->addAction("Insert Pattern", this, [this, idx]() {
|
||||
if (!editingPattern->validFor(project)) return; // nope
|
||||
int si = static_cast<int>(std::min(idx, project->sequence.size()));
|
||||
auto* c = new ProjectSequencerDeltaCommand(project);
|
||||
|
@ -145,26 +145,26 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
c->seqSel = si+1;
|
||||
c->commit();
|
||||
});
|
||||
menu->addAction("Insert Separator", [this, idx]() {
|
||||
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->seqSel = si+1;
|
||||
c->commit();
|
||||
});
|
||||
if (idx < project->sequence.size()) menu->addAction("Remove", [this, idx]() {
|
||||
if (idx < project->sequence.size()) menu->addAction("Remove", this, [this, idx]() {
|
||||
auto* c = new ProjectSequencerDeltaCommand(project);
|
||||
c->seq.erase(c->seq.begin() + static_cast<ptrdiff_t>(idx));
|
||||
c->seqSel = static_cast<int>(idx)-1;
|
||||
c->commit();
|
||||
});
|
||||
menu->addSeparator();
|
||||
menu->addAction("Create New Pattern", [this, idx]() {
|
||||
menu->addAction("Create New Pattern", this, [this, idx]() {
|
||||
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, idx, p = project->patterns[project->sequence[idx]->index]]() {
|
||||
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();
|
||||
});
|
||||
|
@ -176,7 +176,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
|
||||
{ /* Set up keyboard shortcuts for pattern view */ } {
|
||||
// Ctrl+PgUp/Down - previous or next pattern in sequencer
|
||||
connect(new QShortcut(QKeySequence("Ctrl+PgUp"), ui->pattern), &QShortcut::activated, [this]() {
|
||||
connect(new QShortcut(QKeySequence("Ctrl+PgUp"), ui->pattern), &QShortcut::activated, this, [this]() {
|
||||
auto i = ui->patternSequencer->currentIndex();
|
||||
if (!i.isValid()) {
|
||||
ui->patternSequencer->setCurrentIndex(ui->patternSequencer->model()->index(ui->patternSequencer->horizontalHeader()->count() - 1, 0));
|
||||
|
@ -185,7 +185,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
auto count = ui->patternSequencer->horizontalHeader()->count();
|
||||
ui->patternSequencer->setCurrentIndex(i.siblingAtColumn((count + i.column() - 1) % count));
|
||||
});
|
||||
connect(new QShortcut(QKeySequence("Ctrl+PgDown"), ui->pattern), &QShortcut::activated, [this]() {
|
||||
connect(new QShortcut(QKeySequence("Ctrl+PgDown"), ui->pattern), &QShortcut::activated, this, [this]() {
|
||||
auto i = ui->patternSequencer->currentIndex();
|
||||
if (!i.isValid()) {
|
||||
ui->patternSequencer->setCurrentIndex(ui->patternSequencer->model()->index(0, 0));
|
||||
|
@ -196,13 +196,13 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
});
|
||||
|
||||
// TEMP - play/stop
|
||||
connect(new QShortcut(QKeySequence("Ctrl+P"), ui->pattern), &QShortcut::activated, [this]() {
|
||||
connect(new QShortcut(QKeySequence("Ctrl+P"), ui->pattern), &QShortcut::activated, this, [this]() {
|
||||
if (audioEngine->playbackMode() == AudioEngine::Playing) audioEngine->stop();
|
||||
else audioEngine->play(project);
|
||||
});
|
||||
|
||||
/* tmp test
|
||||
connect(new QShortcut(QKeySequence("Ctrl+F1"), ui->patchboard), &QShortcut::activated, [this]() {
|
||||
connect(new QShortcut(QKeySequence("Ctrl+F1"), ui->patchboard), &QShortcut::activated, this, [this]() {
|
||||
auto inp = QInputDialog::getText(this, "yes", "yes");
|
||||
WId id = inp.toULongLong();
|
||||
auto* w = QWindow::fromWinId(id);
|
||||
|
@ -220,11 +220,11 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
socket->window = this;
|
||||
socket->undoStack = undoStack;
|
||||
connect(socket, &UISocket::updatePatternLists, this, &MainWindow::updatePatternLists);
|
||||
connect(socket, &UISocket::patternUpdated, [this](Pattern* p) {
|
||||
connect(socket, &UISocket::patternUpdated, this, [this](Pattern* p) {
|
||||
if (editingPattern.get() != p) return;
|
||||
ui->patternEditor->refresh();
|
||||
});
|
||||
connect(socket, &UISocket::rowUpdated, [this](Pattern* p, int ch, int r) {
|
||||
connect(socket, &UISocket::rowUpdated, this, [this](Pattern* p, int ch, int r) {
|
||||
if (editingPattern.get() != p) return;
|
||||
const auto cpc = PatternEditorModel::colsPerChannel;
|
||||
auto ind = ui->patternEditor->model()->index(r, ch * cpc);
|
||||
|
@ -232,6 +232,13 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
static_cast<PatternEditorModel*>(ui->patternEditor->model())->updateColumnDisplay();
|
||||
});
|
||||
|
||||
// and from audio engine
|
||||
connect(audioEngine, &AudioEngine::playbackModeChanged, this, [this, undoAction, redoAction](AudioEngine::PlaybackMode) {
|
||||
bool locked = project->editingLocked();
|
||||
undoAction->setEnabled(!locked);
|
||||
redoAction->setEnabled(!locked);
|
||||
});
|
||||
|
||||
// and start with a new project
|
||||
menuFileNew();
|
||||
}
|
||||
|
@ -257,6 +264,7 @@ bool MainWindow::eventFilter(QObject *obj [[maybe_unused]], QEvent *event) {
|
|||
|
||||
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());
|
||||
|
||||
|
@ -271,6 +279,7 @@ void MainWindow::menuFileOpen() {
|
|||
QMessageBox::critical(this, "Error", "Error loading project");
|
||||
return;
|
||||
}
|
||||
if (audioEngine->playingProject() == project) audioEngine->stop();
|
||||
project = np;
|
||||
onNewProjectLoaded();
|
||||
}
|
||||
|
@ -316,6 +325,16 @@ int MainWindow::sequenceSelection(int n) {
|
|||
return i.isValid() ? i.column() : -1;
|
||||
}
|
||||
|
||||
void MainWindow::playbackPosition(int seq, int row) {
|
||||
sequenceSelection(seq);
|
||||
auto mi = ui->patternEditor->currentIndex().siblingAtRow(row);
|
||||
if (!mi.isValid()) mi = ui->patternEditor->model()->index(row, 0);
|
||||
ui->patternEditor->setCurrentIndex(mi);
|
||||
ui->patternEditor->selectionModel()->select(QItemSelection(mi.siblingAtColumn(0), mi.siblingAtColumn(ui->patternEditor->horizontalHeader()->count()-1)), QItemSelectionModel::SelectionFlag::ClearAndSelect);
|
||||
ui->patternEditor->scrollTo(mi, QAbstractItemView::PositionAtCenter);
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::updatePatternLists() {
|
||||
emit ui->patternList->model()->layoutChanged();
|
||||
emit ui->patternSequencer->model()->layoutChanged();
|
||||
|
|
|
@ -40,6 +40,8 @@ namespace Xybrid {
|
|||
int patternSelection(int = -100);
|
||||
int sequenceSelection(int = -100);
|
||||
|
||||
void playbackPosition(int seq, int row);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
|
||||
|
|
|
@ -11,8 +11,8 @@ ChannelHeaderView::ChannelHeaderView(QWidget *parent) : QHeaderView(Qt::Horizont
|
|||
//
|
||||
}
|
||||
|
||||
QWidget* ChannelHeaderItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option [[maybe_unused]], const QModelIndex &index) const {
|
||||
/*QWidget* ChannelHeaderItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option [[maybe_unused]], const QModelIndex &index) const {
|
||||
qDebug() << QString("trying edit");
|
||||
auto e = new QTextEdit(parent);
|
||||
return e;
|
||||
}
|
||||
}*/
|
||||
|
|
|
@ -17,7 +17,7 @@ namespace Xybrid::UI {
|
|||
//bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override;
|
||||
|
||||
protected:
|
||||
QWidget *createEditor(QWidget *parent, 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;
|
||||
};
|
||||
|
|
|
@ -69,7 +69,7 @@ PatternEditorView::PatternEditorView(QWidget *parent) : QTableView(parent) {
|
|||
layoutCheckBox->setContentsMargins(0,0,0,0);
|
||||
cornerBox->setToolTip("Expand to fit names");
|
||||
cornerBox->setFocusPolicy(Qt::NoFocus);
|
||||
connect(cornerBox.get(), &QCheckBox::toggled, [this](bool state) {
|
||||
connect(cornerBox.get(), &QCheckBox::toggled, this, [this](bool state) {
|
||||
mdl->fitHeaderToName = state;
|
||||
mdl->updateColumnDisplay();
|
||||
});
|
||||
|
@ -164,15 +164,15 @@ void PatternEditorView::headerContextMenu(QPoint pt) {
|
|||
std::shared_ptr<Pattern> p = mdl->getPattern();
|
||||
|
||||
QMenu* menu = new QMenu(this);
|
||||
menu->addAction("Add Channel", [/*this,*/ idx, p]() {
|
||||
menu->addAction("Add Channel", this, [/*this,*/ idx, p]() {
|
||||
(new PatternChannelAddCommand(p, idx))->commit();
|
||||
});
|
||||
if (idx < hdr->count() - 1) {
|
||||
menu->addAction("Delete Channel", [this, idx, p]() {
|
||||
menu->addAction("Delete Channel", this, [this, idx, p]() {
|
||||
if (QMessageBox::warning(this, "Are you sure?", QString("Remove channel %1 from pattern %2?").arg(Util::numAndName(idx, p->channel(idx).name)).arg(Util::numAndName(p->index, p->name)), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) return;
|
||||
(new PatternChannelDeleteCommand(p, idx))->commit();
|
||||
});
|
||||
menu->addAction("Rename Channel", [this, idx, p]() {
|
||||
menu->addAction("Rename Channel", this, [this, idx, p]() {
|
||||
if (p != mdl->getPattern()) return; // swapped already
|
||||
startRenameChannel(idx);
|
||||
});
|
||||
|
|
|
@ -26,6 +26,9 @@ DISTFILES += ../.astylerc
|
|||
|
||||
CONFIG += c++17
|
||||
|
||||
# use all optimizations that won't generally interfere with debugging
|
||||
QMAKE_CXXFLAGS_DEBUG += -Og
|
||||
|
||||
SOURCES += \
|
||||
main.cpp \
|
||||
mainwindow.cpp \
|
||||
|
|
Loading…
Reference in New Issue