misc UI and audio engine things
parent
bb973babc9
commit
fc93dc0519
7
notes
7
notes
|
@ -50,7 +50,10 @@ TODO {
|
|||
- ^ channel-column splitting (partial channel copy/paste)
|
||||
- pattern properties dialog (name, length, time signature)
|
||||
|
||||
probably move the "process all nodes" part of tick processing into its own function?
|
||||
- probably move the "process all nodes" part of tick processing into its own function?
|
||||
|
||||
- set default suffix on save-as dialog
|
||||
- have the graph scene actually keep track of keys-to-notes on preview (like pattern editor)
|
||||
|
||||
multithreaded audio
|
||||
^ audio engine invokes workers, then QThread::wait()s on them
|
||||
|
@ -62,7 +65,7 @@ TODO {
|
|||
|
||||
}
|
||||
|
||||
|
||||
- make short connections look less wonky
|
||||
|
||||
misc features needed before proper release {
|
||||
song metadata (title, artist, comment, default bpm)
|
||||
|
|
|
@ -31,7 +31,7 @@ void AudioEngine::init() {
|
|||
|
||||
// and off to the races
|
||||
thread->start();
|
||||
//thread->setPriority(QThread::TimeCriticalPriority);
|
||||
thread->setPriority(QThread::TimeCriticalPriority);
|
||||
QMetaObject::invokeMethod(audioEngine, &AudioEngine::postInit, Qt::QueuedConnection);
|
||||
}
|
||||
void AudioEngine::postInit() {
|
||||
|
@ -155,7 +155,7 @@ void AudioEngine::preview(std::shared_ptr<Project> p, int16_t port, int16_t note
|
|||
mode = Previewing;
|
||||
emit this->playbackModeChanged();
|
||||
}
|
||||
if (port >= 0 && port <= 255) previewPort = static_cast<uint8_t>(port); // assign port if valid
|
||||
if (port >= 0 && port <= 255 && state) previewPort_ = static_cast<uint8_t>(port); // assign port if valid (and note on)
|
||||
if (note < 0) return; // invalid note (port is set before it so that setting the port can be a separate action)
|
||||
|
||||
// assemble message
|
||||
|
@ -251,8 +251,7 @@ void AudioEngine::nextTick() {
|
|||
tickBufPtr = tickBuf.get();
|
||||
tickId++;
|
||||
|
||||
// (sample rate / seconds per beat) / ticks per beat
|
||||
double tickSize = (1.0 * sampleRate / (static_cast<double>(tempo)/60.0)) / (4*6);
|
||||
double tickSize = 0.005 * sampleRate; // 5ms fixed tick size for preview
|
||||
tickSize += tickAcc; // add sample remainder from last tick
|
||||
double tickSf = std::floor(tickSize);
|
||||
tickAcc = tickSize - tickSf;
|
||||
|
@ -263,12 +262,12 @@ void AudioEngine::nextTick() {
|
|||
if (!queueValid) buildQueue();
|
||||
|
||||
// TODO: send previewing commands
|
||||
if (auto p = std::static_pointer_cast<CommandPort>(project->rootGraph->port(Port::Input, Port::Command, previewPort)); p) {
|
||||
if (auto p = std::static_pointer_cast<CommandPort>(project->rootGraph->port(Port::Input, Port::Command, previewPort_)); p) {
|
||||
p->push(buf);
|
||||
}
|
||||
buf.clear();
|
||||
|
||||
for (auto n : queue) if (!n->try_process()) qWarning() << "Dependency check failed in single threaded mode!";
|
||||
processNodes();
|
||||
if (auto p = std::static_pointer_cast<AudioPort>(project->rootGraph->port(Port::Output, Port::Audio, 0)); p) {
|
||||
p->pull();
|
||||
size_t bufs = ts * sizeof(float);
|
||||
|
@ -447,7 +446,7 @@ void AudioEngine::nextTick() {
|
|||
buffer[0].resize(ts);
|
||||
buffer[1].resize(ts);
|
||||
|
||||
for (auto n : queue) if (!n->try_process()) qWarning() << "Dependency check failed in single threaded mode!";
|
||||
processNodes();
|
||||
if (auto p = std::static_pointer_cast<AudioPort>(project->rootGraph->port(Port::Output, Port::Audio, 0)); p) {
|
||||
p->pull();
|
||||
size_t bufs = ts * sizeof(float);
|
||||
|
@ -473,3 +472,7 @@ void AudioEngine::nextTick() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEngine::processNodes() {
|
||||
for (auto n : queue) if (!n->try_process()) qWarning() << "Dependency check failed in single threaded mode!";
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ namespace Xybrid::Audio {
|
|||
std::unordered_map<std::string*, NoteInfo, PointerCompare<std::string>, PointerCompare<std::string>> nameTrack;
|
||||
std::vector<uint8_t> buf; /// preallocated buffer for building commands
|
||||
|
||||
uint8_t previewPort = 0;
|
||||
uint8_t previewPort_ = 0;
|
||||
|
||||
// playback timing and position
|
||||
float tempo = 140.0;
|
||||
|
@ -78,6 +78,7 @@ namespace Xybrid::Audio {
|
|||
void initAudio(bool startNow = false);
|
||||
void deinitAudio();
|
||||
void nextTick();
|
||||
void processNodes();
|
||||
public:
|
||||
static void init();
|
||||
inline constexpr PlaybackMode playbackMode() const { return mode; }
|
||||
|
@ -85,6 +86,7 @@ namespace Xybrid::Audio {
|
|||
void play(std::shared_ptr<Data::Project>);
|
||||
void stop();
|
||||
void preview(std::shared_ptr<Data::Project>, int16_t port, int16_t note, bool state);
|
||||
inline uint8_t previewPort() const { return previewPort_; }
|
||||
|
||||
inline void invalidateQueue(Data::Project* p) { if (p == project.get()) queueValid = false; }
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ using namespace Xybrid::Config;
|
|||
using namespace Xybrid::Audio;
|
||||
|
||||
namespace {
|
||||
constexpr const auto projectFilter = u8"Xybrid project (*.xyp)\nAll files (*)";
|
||||
constexpr const auto projectFilter = u8"Xybrid project (*.xyp);;All files (*)";
|
||||
}
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) :
|
||||
|
@ -381,8 +381,11 @@ void MainWindow::menuFileSave() {
|
|||
}
|
||||
|
||||
void MainWindow::menuFileSaveAs() {
|
||||
auto fileName = QFileDialog::getSaveFileName(this, "Save project as...", QString(), projectFilter);
|
||||
if (fileName.isEmpty()) return; // canceled
|
||||
QFileDialog dlg(this, "Save project as...", QString(), projectFilter);
|
||||
dlg.setDefaultSuffix("xyp");
|
||||
dlg.setFileMode(QFileDialog::AnyFile);
|
||||
if (!dlg.exec()) return; // canceled
|
||||
auto fileName = dlg.selectedFiles()[0];
|
||||
FileOps::saveProject(project, fileName);
|
||||
undoStack->setClean();
|
||||
updateTitle();
|
||||
|
|
|
@ -359,6 +359,9 @@ void NodeObject::paint(QPainter* painter, const QStyleOptionGraphicsItem* opt, Q
|
|||
painter->drawRoundedRect(r, 8, 8);
|
||||
|
||||
if (showName) {
|
||||
QFont f = painter->font();
|
||||
f.setPointSizeF(9);
|
||||
painter->setFont(f);
|
||||
QRectF tr = r - QMarginsF(edgePad, edgePad - 1, edgePad, 0);
|
||||
if (!node->name.empty()) {
|
||||
painter->setPen(QColor(222, 222, 222));
|
||||
|
@ -374,7 +377,7 @@ void NodeObject::paint(QPainter* painter, const QStyleOptionGraphicsItem* opt, Q
|
|||
|
||||
QRectF NodeObject::boundingRect() const {
|
||||
if (customChrome) return QRectF(QPointF(0, 0), gadgetSize_);
|
||||
if (gadgetSize_.isNull()) return QRectF(0, 0, 192, 36);// + QMarginsF(8, 8, 8, 8);
|
||||
if (gadgetSize_.isNull()) return QRectF(0, 0, 128+32, 36);// + QMarginsF(8, 8, 8, 8);
|
||||
if (showName) return QRectF(QPointF(), gadgetSize_ + QPointF(edgePad * 2, edgePad * 2 + nameSize()));
|
||||
return QRectF(QPointF(), gadgetSize_ + QPointF(edgePad * 2, edgePad * 2));
|
||||
}
|
||||
|
@ -462,6 +465,10 @@ QPainterPath PortConnectionObject::shape(qreal width) const {
|
|||
QPointF out(0, 0);
|
||||
//path.lineTo(start+out);
|
||||
QPointF mod(std::max((end.x() - start.x()) * .64, 96.0), 0);
|
||||
if (auto vdist = std::fabs(end.y() - start.y()), hdist = fabs(end.x() - start.x()) * 0.75; hdist < 96.0 && vdist < 96.0) {
|
||||
double p = std::max(vdist, hdist) / 96.0;
|
||||
mod = QPointF(mod.x() * p, 0);
|
||||
}
|
||||
path.cubicTo(start + out + mod, end - mod, end - out);
|
||||
//path.lineTo(end);
|
||||
|
||||
|
|
|
@ -78,22 +78,30 @@ void PatchboardScene::keyPressEvent(QKeyEvent* e) {
|
|||
if (!e->isAccepted() && !e->isAutoRepeat()) {
|
||||
auto note = Util::keyToNote(e->key());
|
||||
if (note >= 0) {
|
||||
auto p = graph->project->shared_from_this();
|
||||
if (e->modifiers() & Qt::Modifier::SHIFT) note += 24;
|
||||
audioEngine->preview(p, -1, note, true);
|
||||
startPreview(e->key(), note);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PatchboardScene::keyReleaseEvent(QKeyEvent* e) {
|
||||
QGraphicsScene::keyReleaseEvent(e);
|
||||
if (!e->isAccepted() && !e->isAutoRepeat()) {
|
||||
auto note = Util::keyToNote(e->key());
|
||||
if (note >= 0) {
|
||||
auto p = graph->project->shared_from_this();
|
||||
audioEngine->preview(p, -1, note, false);
|
||||
audioEngine->preview(p, -1, note+24, false);
|
||||
}
|
||||
if (!e->isAccepted() && !e->isAutoRepeat()) stopPreview(e->key());
|
||||
}
|
||||
|
||||
void PatchboardScene::startPreview(int key, int16_t note) {
|
||||
stopPreview(key); // end current preview first, if applicable
|
||||
auto p = graph->project->shared_from_this();
|
||||
audioEngine->preview(p, -1, note, true);
|
||||
previewKey[key] = {audioEngine->previewPort(), note};
|
||||
|
||||
}
|
||||
|
||||
void PatchboardScene::stopPreview(int key) {
|
||||
if (auto k = previewKey.find(key); k != previewKey.end()) {
|
||||
auto p = graph->project->shared_from_this();
|
||||
audioEngine->preview(p, k->second[0], k->second[1], false);
|
||||
previewKey.erase(k);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <QGraphicsScene>
|
||||
#include <QGraphicsView>
|
||||
|
@ -12,10 +13,15 @@ namespace Xybrid::UI {
|
|||
std::shared_ptr<Data::Graph> graph;
|
||||
QGraphicsView* view;
|
||||
|
||||
std::unordered_map<int, std::array<int16_t, 2>> previewKey;
|
||||
|
||||
bool resizeQueued = false;
|
||||
void queueResize();
|
||||
void autoSetSize();
|
||||
|
||||
void startPreview(int, int16_t);
|
||||
void stopPreview(int);
|
||||
|
||||
public:
|
||||
PatchboardScene(QGraphicsView* view, const std::shared_ptr<Data::Graph>& graph);
|
||||
~PatchboardScene() override = default;
|
||||
|
|
Loading…
Reference in New Issue