bunch of audio engine and graph skeleton work
parent
12a563afc4
commit
c1e73b922a
|
@ -1,3 +1,4 @@
|
|||
# attached braces
|
||||
style=attach
|
||||
indent-namespaces
|
||||
keep-one-line-blocks
|
||||
|
|
10
notes
10
notes
|
@ -50,9 +50,9 @@ TODO {
|
|||
- strut command in pattern editor (mostly selection agnostic)
|
||||
}
|
||||
group 2 {
|
||||
skeleton graph and node - data namespace I guess
|
||||
skeleton plugin registry - stuff into config namespace?
|
||||
skeleton audio engine
|
||||
skeleton plugin registry
|
||||
skeleton graph and node
|
||||
}
|
||||
|
||||
# fix how qt5.12 broke header text (removed elide for now)
|
||||
|
@ -71,9 +71,15 @@ TODO {
|
|||
make everything relevant check if editing is locked
|
||||
|
||||
make the save routine displace the old file and write a new one
|
||||
|
||||
multi-document, single-instance (QLocalServer etc.)
|
||||
}
|
||||
}
|
||||
|
||||
dumb per-cycle atomic memory allocator from fixed pool for port buffer allocations
|
||||
can also set up a tlsf pool per worker; prefix allocations with single byte identifier indicating which one they came from,
|
||||
and defer freeing operations via message queues
|
||||
|
||||
resampler object {
|
||||
one used internally for each note
|
||||
reference to sample
|
||||
|
|
|
@ -19,6 +19,7 @@ project: [
|
|||
"meta": { "artist": ... "title": ... "comment": ... etc. }
|
||||
"patterns": [ array of pattern structs ]
|
||||
"sequence": [ array of pattern numbers, int, separator is anything negative ]
|
||||
"graph": { root graph (contents) }
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
#include "audioengine.h"
|
||||
#include "data/project.h"
|
||||
using namespace Xybrid::Audio;
|
||||
using namespace Xybrid::Data;
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QThread>
|
||||
|
||||
// zero-initialize
|
||||
AudioEngine* Xybrid::Audio::audioEngine = nullptr;
|
||||
|
||||
void AudioEngine::init() {
|
||||
if (audioEngine) return; // already set up
|
||||
|
||||
// instantiate singleton
|
||||
QThread* thread = new QThread;
|
||||
audioEngine = new AudioEngine(nullptr);
|
||||
audioEngine->moveToThread(thread);
|
||||
audioEngine->thread = thread;
|
||||
|
||||
// hook up signals
|
||||
// ...
|
||||
|
||||
// and off to the races
|
||||
thread->start();
|
||||
QMetaObject::invokeMethod(audioEngine, &AudioEngine::postInit);
|
||||
}
|
||||
void AudioEngine::postInit() {
|
||||
open(QIODevice::ReadOnly);
|
||||
// set up QAudioOutput and buffer here
|
||||
|
||||
}
|
||||
|
||||
AudioEngine::AudioEngine(QObject *parent) : QIODevice(parent) { }
|
||||
|
||||
void AudioEngine::play(std::shared_ptr<Project> p) {
|
||||
QMetaObject::invokeMethod(this, [this, p]() {
|
||||
if (!p) return; // nope
|
||||
project = p;
|
||||
// stop and reset, then init playback
|
||||
|
||||
const QAudioDeviceInfo& deviceInfo = QAudioDeviceInfo::defaultOutputDevice();
|
||||
|
||||
QAudioFormat format;
|
||||
format.setSampleRate(48000);
|
||||
format.setChannelCount(2);
|
||||
format.setSampleSize(16);
|
||||
format.setCodec("audio/pcm");
|
||||
format.setByteOrder(QAudioFormat::LittleEndian);
|
||||
format.setSampleType(QAudioFormat::SignedInt);
|
||||
|
||||
if (!deviceInfo.isFormatSupported(format)) {
|
||||
qWarning() << "Default format not supported - trying to use nearest";
|
||||
format = deviceInfo.nearestFormat(format);
|
||||
}
|
||||
sampleRate = format.sampleRate();
|
||||
|
||||
output.reset(new QAudioOutput(deviceInfo, format));
|
||||
output->setObjectName("Xybrid"); // if Qt ever implements naming the stream this way, WE'LL BE READY
|
||||
output->setCategory("something?");
|
||||
output->setBufferSize(sampleRate*4*(10/1000)); // 10ms
|
||||
output->start(this);
|
||||
|
||||
mode = Playing;
|
||||
}, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void AudioEngine::stop() {
|
||||
QMetaObject::invokeMethod(this, [this]() {
|
||||
project = nullptr;
|
||||
// stop and reset
|
||||
mode = Stopped;
|
||||
}, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
qint64 AudioEngine::readData(char *data, qint64 maxlen) {
|
||||
static double time = 0;
|
||||
qint64 sr = maxlen;
|
||||
const double PI = std::atan(1)*4;
|
||||
const double SEMI = std::pow(2.0, 1.0/12.0);
|
||||
|
||||
double vol = std::pow(.5, 4);
|
||||
|
||||
while (sr >= 4) {
|
||||
sr -= 4;
|
||||
|
||||
int16_t* l = reinterpret_cast<int16_t*>(data);
|
||||
int16_t* r = reinterpret_cast<int16_t*>(data+2);
|
||||
|
||||
vol = std::pow(.5 + std::sin(time * PI*2) * .15, 4);
|
||||
|
||||
/**l = static_cast<int16_t>(std::sin(time * PI*2 * 440 * std::pow(SEMI, 4)) * 32767 * vol);
|
||||
*r = static_cast<int16_t>(std::sin(time * PI*2 * 440) * 32767 * vol);
|
||||
*l += static_cast<int16_t>(std::sin(time * PI*2 * 440 * std::pow(SEMI, 11.9)) * 32767 * vol);
|
||||
*r += static_cast<int16_t>(std::sin(time * PI*2 * 440 * std::pow(SEMI, 7)) * 32767 * vol);*/
|
||||
*l = 0;
|
||||
//*l += static_cast<int16_t>(std::sin(time * PI*2 * 440 * std::pow(SEMI, 1)) * 32767 * vol);
|
||||
//*l += static_cast<int16_t>(std::sin(time * PI*2 * 440 * std::pow(SEMI, 1.1)) * 32767 * vol);
|
||||
*l += static_cast<int16_t>(std::clamp(std::sin(time * PI*2 * 440 * std::pow(SEMI, time - 48)) * 3, -1.0, 1.0) * 32767 * vol);
|
||||
*r = *l;
|
||||
|
||||
time += 1.0/sampleRate;
|
||||
data += 4;
|
||||
}
|
||||
while (sr > 0) {
|
||||
sr--;
|
||||
*data = 0;
|
||||
++data;
|
||||
}
|
||||
//qDebug() << "audio engine requested:" << maxlen;
|
||||
return maxlen;
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
#include <QIODevice>
|
||||
#include <QAudioOutput>
|
||||
|
||||
class QThread;
|
||||
namespace Xybrid::Data {
|
||||
class Project;
|
||||
}
|
||||
namespace Xybrid::Audio {
|
||||
class AudioEngine : public QIODevice {
|
||||
Q_OBJECT
|
||||
explicit AudioEngine(QObject *parent = nullptr);
|
||||
public:
|
||||
enum PlaybackMode {
|
||||
Stopped, // stopped
|
||||
Playing, // playing track
|
||||
Previewing, // instrument live preview
|
||||
Rendering, // rendering to file
|
||||
};
|
||||
private:
|
||||
QThread* thread;
|
||||
std::unique_ptr<QAudioOutput> output;
|
||||
int sampleRate = 48000;
|
||||
|
||||
PlaybackMode mode = Stopped;
|
||||
std::shared_ptr<Data::Project> project;
|
||||
void postInit();
|
||||
public:
|
||||
static void init();
|
||||
inline constexpr PlaybackMode playbackMode() const { return mode; }
|
||||
inline constexpr const std::shared_ptr<Data::Project>& playingProject() const { return project; }
|
||||
void play(std::shared_ptr<Data::Project>);
|
||||
void stop();
|
||||
|
||||
// QIODevice functions
|
||||
qint64 readData(char* data, qint64 maxlen) override;// {return 0;}
|
||||
qint64 writeData(const char*, qint64) override { return 0; }
|
||||
qint64 bytesAvailable() const override { return 1166; }
|
||||
|
||||
signals:
|
||||
void playbackModeChanged(PlaybackMode);
|
||||
|
||||
public slots:
|
||||
};
|
||||
|
||||
extern AudioEngine* audioEngine;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include "data/node.h"
|
||||
|
||||
namespace Xybrid::Data {
|
||||
class Graph : public Node {
|
||||
public:
|
||||
std::vector<std::shared_ptr<Node>> children;
|
||||
};
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
#include "node.h"
|
||||
using Xybrid::Data::Node;
|
||||
using Xybrid::Data::Port;
|
||||
|
||||
#include "data/graph.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
bool Port::canConnectTo(DataType d) {
|
||||
return d == dataType();
|
||||
}
|
||||
|
||||
bool Port::connect(std::shared_ptr<Port> p) {
|
||||
if (!p) return false; // no blank pointers pls
|
||||
// actual processing is always done on the input port, since that's where any limits are
|
||||
if (type == Port::Output) return p->type == Port::Input && p->connect(shared_from_this());
|
||||
if (!canConnectTo(p->dataType())) return false; // can't hook up to an incompatible data type
|
||||
for (auto c : connections) if (c.lock() == p) return true; // I guess report success if already connected?
|
||||
if (singleInput() && connections.size() > 0) return false; // reject multiple connections on single-input ports
|
||||
// actually hook up
|
||||
connections.emplace_back(p);
|
||||
p->connections.emplace_back(shared_from_this());
|
||||
return true;
|
||||
}
|
||||
|
||||
void Port::disconnect(std::shared_ptr<Port> p) {
|
||||
if (!p) return;
|
||||
auto t = shared_from_this();
|
||||
connections.erase(std::remove_if(connections.begin(), connections.end(), [p](auto w) { return w.lock() == p; }), connections.end());
|
||||
p->connections.erase(std::remove_if(p->connections.begin(), p->connections.end(), [t](auto w) { return w.lock() == t; }), p->connections.end());
|
||||
}
|
||||
|
||||
void Node::parentTo(std::shared_ptr<Graph> graph) {
|
||||
auto t = shared_from_this(); // keep alive during reparenting
|
||||
if (auto p = parent.lock(); p) {
|
||||
p->children.erase(std::remove(p->children.begin(), p->children.end(), t), p->children.end());
|
||||
}
|
||||
parent = graph;
|
||||
if (graph) {
|
||||
graph->children.push_back(t);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace Xybrid::Data {
|
||||
class Graph;
|
||||
class Node;
|
||||
|
||||
class Port : public std::enable_shared_from_this<Port> {
|
||||
public:
|
||||
enum Type : char {
|
||||
Input, Output
|
||||
};
|
||||
enum DataType : char {
|
||||
Command, MIDI, Audio, Parameter
|
||||
};
|
||||
std::weak_ptr<Node> owner;
|
||||
std::vector<std::weak_ptr<Port>> connections;
|
||||
Type type; // TODO: figure out passthrough?
|
||||
|
||||
virtual ~Port() = default;
|
||||
|
||||
virtual DataType dataType();
|
||||
virtual bool singleInput() { return false; }
|
||||
virtual bool canConnectTo(DataType);
|
||||
/*virtual*/ bool connect(std::shared_ptr<Port>);
|
||||
/*virtual*/ void disconnect(std::shared_ptr<Port>);
|
||||
};
|
||||
|
||||
class Node : public std::enable_shared_from_this<Node> {
|
||||
public:
|
||||
std::weak_ptr<Graph> parent;
|
||||
int x{}, y{};
|
||||
std::string name;
|
||||
|
||||
std::vector<std::shared_ptr<Port>> inputs, outputs;
|
||||
|
||||
virtual ~Node() = default;
|
||||
|
||||
void parentTo(std::shared_ptr<Graph>);
|
||||
};
|
||||
}
|
|
@ -17,9 +17,7 @@ namespace Xybrid::Data {
|
|||
|
||||
TimeSignature() = default;
|
||||
TimeSignature(int b, int r, int t) : beatsPerMeasure(b), rowsPerBeat(r), ticksPerRow(t) {}
|
||||
constexpr int rowsPerMeasure() const {
|
||||
return beatsPerMeasure * rowsPerBeat;
|
||||
}
|
||||
constexpr int rowsPerMeasure() const { return beatsPerMeasure * rowsPerBeat; }
|
||||
};
|
||||
class Pattern {
|
||||
public:
|
||||
|
@ -41,9 +39,7 @@ namespace Xybrid::Data {
|
|||
|
||||
Row& operator=(const Row&) noexcept;
|
||||
|
||||
bool isEmpty() const {
|
||||
return numParams() == 0 && port == -1 && note == -1;
|
||||
}
|
||||
bool isEmpty() const { return numParams() == 0 && port == -1 && note == -1; }
|
||||
|
||||
size_t numParams() const {
|
||||
if (!this->params) return 0;
|
||||
|
@ -117,9 +113,7 @@ namespace Xybrid::Data {
|
|||
void addChannel(int at = -1);
|
||||
void deleteChannel(int at);
|
||||
|
||||
size_t numChannels() const {
|
||||
return channels.size();
|
||||
}
|
||||
size_t numChannels() const { return channels.size(); }
|
||||
|
||||
bool valid() const;
|
||||
bool validFor(const Project*) const;
|
||||
|
|
|
@ -2,6 +2,13 @@
|
|||
using Xybrid::Data::Project;
|
||||
using Xybrid::Data::Pattern;
|
||||
|
||||
#include "data/graph.h"
|
||||
using Xybrid::Data::Graph;
|
||||
|
||||
Project::Project() {
|
||||
rootGraph = std::make_shared<Graph>();
|
||||
}
|
||||
|
||||
Project::~Project() {
|
||||
// orphan patterns so they're not pointing at a non-project
|
||||
for (auto& pat : patterns) pat->project = nullptr;
|
||||
|
|
|
@ -16,6 +16,7 @@ namespace Xybrid {
|
|||
}
|
||||
|
||||
namespace Xybrid::Data {
|
||||
class Graph;
|
||||
class Project {
|
||||
public:
|
||||
bool editingLocked = false;
|
||||
|
@ -27,7 +28,6 @@ namespace Xybrid::Data {
|
|||
|
||||
QString fileName;
|
||||
|
||||
size_t sampleRate = 48000; // global sr for rendering
|
||||
float tempo = 140.0;
|
||||
TimeSignature time;
|
||||
// default time signature
|
||||
|
@ -36,10 +36,10 @@ namespace Xybrid::Data {
|
|||
std::vector<std::shared_ptr<Pattern>> patterns;
|
||||
std::vector<Pattern*> sequence; // nullptr as separator
|
||||
|
||||
//std::shared_ptr<Graph> mainGraph;
|
||||
// list of input nodes is just part of mainGraph
|
||||
std::shared_ptr<Graph> rootGraph;
|
||||
// list of input nodes is just part of rootGraph
|
||||
|
||||
Project() = default;
|
||||
Project();
|
||||
~Project();
|
||||
|
||||
void updatePatternIndices();
|
||||
|
|
|
@ -27,9 +27,7 @@ namespace Xybrid::Editing {
|
|||
PatternDeltaCommand(const std::shared_ptr<Data::Pattern>& pattern, int channel, int row);
|
||||
~PatternDeltaCommand() override = default;
|
||||
|
||||
int id() const override {
|
||||
return 2000;
|
||||
}
|
||||
int id() const override { return 2000; }
|
||||
|
||||
bool mergeWith(const QUndoCommand*) override;
|
||||
void redo() override;
|
||||
|
@ -43,9 +41,7 @@ namespace Xybrid::Editing {
|
|||
PatternRenameCommand(const std::shared_ptr<Data::Pattern>& pattern, const std::string& to);
|
||||
~PatternRenameCommand() override = default;
|
||||
|
||||
int id() const override {
|
||||
return 2070;
|
||||
}
|
||||
int id() const override { return 2070; }
|
||||
|
||||
bool mergeWith(const QUndoCommand*) override;
|
||||
void redo() override;
|
||||
|
@ -59,9 +55,7 @@ namespace Xybrid::Editing {
|
|||
PatternChannelMoveCommand(const std::shared_ptr<Data::Pattern>& pattern, int from, int to);
|
||||
~PatternChannelMoveCommand() override = default;
|
||||
|
||||
int id() const override {
|
||||
return 2100;
|
||||
}
|
||||
int id() const override { return 2100; }
|
||||
|
||||
bool mergeWith(const QUndoCommand*) override;
|
||||
void redo() override;
|
||||
|
@ -76,9 +70,7 @@ namespace Xybrid::Editing {
|
|||
PatternChannelRenameCommand(const std::shared_ptr<Data::Pattern>& pattern, int channel, const std::string& to);
|
||||
~PatternChannelRenameCommand() override = default;
|
||||
|
||||
int id() const override {
|
||||
return 2101;
|
||||
}
|
||||
int id() const override { return 2101; }
|
||||
|
||||
bool mergeWith(const QUndoCommand*) override;
|
||||
void redo() override;
|
||||
|
|
|
@ -28,9 +28,7 @@ namespace Xybrid::Editing {
|
|||
ProjectSequencerDeltaCommand(const std::shared_ptr<Data::Project>& project);
|
||||
~ProjectSequencerDeltaCommand() override = default;
|
||||
|
||||
int id() const override {
|
||||
return 1000;
|
||||
}
|
||||
int id() const override { return 1000; }
|
||||
|
||||
bool mergeWith(const QUndoCommand*) override;
|
||||
void redo() override;
|
||||
|
@ -43,9 +41,7 @@ namespace Xybrid::Editing {
|
|||
ProjectPatternMoveCommand(const std::shared_ptr<Data::Project>& project, int from, int to);
|
||||
~ProjectPatternMoveCommand() override = default;
|
||||
|
||||
int id() const override {
|
||||
return 1001;
|
||||
}
|
||||
int id() const override { return 1001; }
|
||||
|
||||
bool mergeWith(const QUndoCommand*) override;
|
||||
void redo() override;
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
#include "mainwindow.h"
|
||||
#include <QApplication>
|
||||
#include "audio/audioengine.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QFontDatabase>
|
||||
#include <QApplication>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
QApplication a(argc, argv);
|
||||
|
@ -12,8 +13,10 @@ int main(int argc, char *argv[]) {
|
|||
// make sure bundled fonts are loaded
|
||||
QFontDatabase::addApplicationFont(":/fonts/iosevka-term-light.ttf");
|
||||
|
||||
Xybrid::MainWindow w;
|
||||
w.show();
|
||||
Xybrid::Audio::AudioEngine::init();
|
||||
|
||||
auto* w = new Xybrid::MainWindow();
|
||||
w->show();
|
||||
|
||||
return a.exec();
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ using Xybrid::MainWindow;
|
|||
#include <QFileDialog>
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QWindow>
|
||||
#include <QUndoStack>
|
||||
|
||||
#include "util/strings.h"
|
||||
|
@ -20,6 +21,8 @@ using Xybrid::MainWindow;
|
|||
|
||||
#include "editing/projectcommands.h"
|
||||
|
||||
#include "audio/audioengine.h"
|
||||
|
||||
using Xybrid::Data::Project;
|
||||
using Xybrid::Data::Pattern;
|
||||
|
||||
|
@ -39,6 +42,11 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
ui(new Ui::MainWindow) {
|
||||
ui->setupUi(this);
|
||||
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
// remove tab containing system widgets
|
||||
ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->extra_));
|
||||
|
||||
undoStack = new QUndoStack(this);
|
||||
//undoStack->setUndoLimit(256);
|
||||
connect(undoStack, &QUndoStack::cleanChanged, [this](bool) {
|
||||
|
@ -53,11 +61,10 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
redoAction->setShortcuts(QKeySequence::Redo);
|
||||
ui->menuEdit->addAction(redoAction);
|
||||
|
||||
auto t = ui->tabWidget;
|
||||
auto* t = ui->tabWidget;
|
||||
t->setCornerWidget(ui->menuBar);
|
||||
t->setCornerWidget(ui->label, Qt::TopLeftCorner);
|
||||
auto mb = ui->menuBar;
|
||||
mb->setStyleSheet("QMenuBar { background: transparent; vertical-align: center; } QMenuBar::item { } QMenuBar::item:!pressed { background: transparent; }");
|
||||
//ui->menuBar->setStyleSheet("QMenuBar { background: transparent; vertical-align: center; } QMenuBar::item { } QMenuBar::item:!pressed { background: transparent; }");
|
||||
|
||||
// prevent right pane of pattern view from being collapsed
|
||||
ui->patternViewSplitter->setCollapsible(1, false);
|
||||
|
@ -104,7 +111,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
});
|
||||
}
|
||||
menu->popup(ui->patternList->mapToGlobal(pt));
|
||||
});
|
||||
});//*/
|
||||
}
|
||||
|
||||
{ /* Set up sequencer */ } {
|
||||
|
@ -163,7 +170,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
}
|
||||
|
||||
menu->popup(ui->patternSequencer->mapToGlobal(pt));
|
||||
});
|
||||
});//*/
|
||||
}
|
||||
|
||||
{ /* Set up keyboard shortcuts for pattern view */ } {
|
||||
|
@ -186,18 +193,31 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
auto count = ui->patternSequencer->horizontalHeader()->count();
|
||||
ui->patternSequencer->setCurrentIndex(i.siblingAtColumn((count + i.column() + 1) % count));
|
||||
});
|
||||
|
||||
/* tmp test
|
||||
connect(new QShortcut(QKeySequence("Ctrl+F1"), ui->patchboard), &QShortcut::activated, [this]() {
|
||||
auto inp = QInputDialog::getText(this, "yes", "yes");
|
||||
WId id = inp.toULongLong();
|
||||
auto* w = QWindow::fromWinId(id);
|
||||
auto* w2 = new QWindow();
|
||||
auto* wc = QWidget::createWindowContainer(w2);
|
||||
w->setParent(w2);
|
||||
ui->patchboard->layout()->addWidget(wc);
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
// Set up signaling from project to UI
|
||||
socket.reset(new UISocket());
|
||||
socket = new UISocket();
|
||||
socket->setParent(this);
|
||||
socket->window = this;
|
||||
socket->undoStack = undoStack;
|
||||
connect(socket.get(), &UISocket::updatePatternLists, this, &MainWindow::updatePatternLists);
|
||||
connect(socket.get(), &UISocket::patternUpdated, [this](Pattern* p) {
|
||||
connect(socket, &UISocket::updatePatternLists, this, &MainWindow::updatePatternLists);
|
||||
connect(socket, &UISocket::patternUpdated, [this](Pattern* p) {
|
||||
if (editingPattern.get() != p) return;
|
||||
ui->patternEditor->refresh();
|
||||
});
|
||||
connect(socket.get(), &UISocket::rowUpdated, [this](Pattern* p, int ch, int r) {
|
||||
connect(socket, &UISocket::rowUpdated, [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);
|
||||
|
@ -212,6 +232,8 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
/*project->sequence.push_back(nullptr);
|
||||
project->patterns[0]->name = "waffle iron";
|
||||
project->sequence.push_back(project->newPattern().get());*/
|
||||
|
||||
Audio::audioEngine->play(project);
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow() {
|
||||
|
@ -269,7 +291,7 @@ void MainWindow::menuFileSaveAs() {
|
|||
void MainWindow::onNewProjectLoaded() {
|
||||
undoStack->clear();
|
||||
|
||||
project->socket = socket.get();
|
||||
project->socket = socket;
|
||||
updatePatternLists();
|
||||
patternSelection(0);
|
||||
sequenceSelection(-1);
|
||||
|
|
|
@ -22,9 +22,9 @@ namespace Xybrid {
|
|||
|
||||
private:
|
||||
Ui::MainWindow* ui;
|
||||
std::unique_ptr<UISocket> socket;
|
||||
UISocket* socket;
|
||||
std::shared_ptr<Data::Project> project;
|
||||
std::shared_ptr<Data::Pattern> editingPattern; // temporary pattern for testing the editor
|
||||
std::shared_ptr<Data::Pattern> editingPattern;
|
||||
|
||||
QUndoStack* undoStack;
|
||||
|
||||
|
@ -35,9 +35,7 @@ namespace Xybrid {
|
|||
void updateTitle();
|
||||
|
||||
public:
|
||||
const std::shared_ptr<Data::Project>& getProject() const {
|
||||
return project;
|
||||
}
|
||||
const std::shared_ptr<Data::Project>& getProject() const { return project; }
|
||||
|
||||
int patternSelection(int = -100);
|
||||
int sequenceSelection(int = -100);
|
||||
|
|
|
@ -79,6 +79,9 @@
|
|||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::ClickFocus</enum>
|
||||
</property>
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
</property>
|
||||
|
@ -229,12 +232,37 @@
|
|||
<attribute name="title">
|
||||
<string>Patchboard</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<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="QGraphicsView" name="patchboardView"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="extra_">
|
||||
<attribute name="title">
|
||||
<string>nonexistent</string>
|
||||
</attribute>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>310</x>
|
||||
<y>80</y>
|
||||
<width>61</width>
|
||||
<x>10</x>
|
||||
<y>10</y>
|
||||
<width>63</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
|
|
|
@ -61,7 +61,7 @@ QVariant PatternEditorHeaderProxyModel::headerData(int section, Qt::Orientation
|
|||
|
||||
PatternEditorModel::PatternEditorModel(QObject *parent)
|
||||
:QAbstractTableModel(parent) {
|
||||
hprox.reset(new PatternEditorHeaderProxyModel(parent, this));
|
||||
hprox = new PatternEditorHeaderProxyModel(parent, this);
|
||||
}
|
||||
|
||||
int PatternEditorModel::rowCount(const QModelIndex & /*parent*/) const {
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace Xybrid::UI {
|
|||
static constexpr int colsPerChannel = 2 + (2 * paramSoftCap);
|
||||
static constexpr int cellPadding = 2;
|
||||
|
||||
std::unique_ptr<PatternEditorHeaderProxyModel> hprox;
|
||||
PatternEditorHeaderProxyModel* hprox;
|
||||
|
||||
bool fitHeaderToName = false;
|
||||
|
||||
|
|
|
@ -84,7 +84,12 @@ PatternEditorView::PatternEditorView(QWidget *parent) : QTableView(parent) {
|
|||
|
||||
PatternEditorView::~PatternEditorView() {
|
||||
//
|
||||
horizontalHeader()->deleteLater();
|
||||
/*mdl.release();
|
||||
del.release();
|
||||
hdr.release();
|
||||
cornerBoxBox.release();
|
||||
cornerBox.release();*/
|
||||
//horizontalHeader()->deleteLater();
|
||||
}
|
||||
|
||||
void PatternEditorView::keyPressEvent(QKeyEvent *event) {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#
|
||||
#-------------------------------------------------
|
||||
|
||||
QT += core gui
|
||||
QT += core gui multimedia
|
||||
|
||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||
|
||||
|
@ -41,7 +41,9 @@ SOURCES += \
|
|||
fileops.cpp \
|
||||
editing/patterncommands.cpp \
|
||||
editing/projectcommands.cpp \
|
||||
editing/compositecommand.cpp
|
||||
editing/compositecommand.cpp \
|
||||
data/node.cpp \
|
||||
audio/audioengine.cpp
|
||||
|
||||
HEADERS += \
|
||||
mainwindow.h \
|
||||
|
@ -59,7 +61,10 @@ HEADERS += \
|
|||
fileops.h \
|
||||
editing/patterncommands.h \
|
||||
editing/projectcommands.h \
|
||||
editing/compositecommand.h
|
||||
editing/compositecommand.h \
|
||||
data/node.h \
|
||||
data/graph.h \
|
||||
audio/audioengine.h
|
||||
|
||||
FORMS += \
|
||||
mainwindow.ui
|
||||
|
|
Loading…
Reference in New Issue