added transpose feature

portability/boost
zetaPRIME 2019-01-08 14:27:25 -05:00
parent f41af3a0ec
commit 875a65d977
5 changed files with 84 additions and 23 deletions

10
notes
View File

@ -45,6 +45,10 @@ project data {
TODO {
immediate frontburner {
- transpose selection (alt+up/down for semitone/0x01, left+right for octave/0x10)
pattern cut+copy+paste
add metadata and pattern properties (artist, song title, project bpm; pattern name, length etc.)
probably move the "process all nodes" part of tick processing into its own function?
multithreaded audio
@ -64,11 +68,7 @@ TODO {
SAMPLES and SAMPLING
- gadget widgets (w/container) - at least a knob with nice range and such
add metadata and pattern properties (artist, song title, project bpm; pattern name, length etc.)
pattern cut+copy+paste
transpose selection
different context menu for multiple selected nodes
pack/unpack selection to/from subgraph
import/export subgraph as file (*.xyg)

View File

@ -15,13 +15,17 @@ using Xybrid::UI::ChannelHeaderView;
using Xybrid::Data::Project;
using Xybrid::Data::Pattern;
#include "editing/compositecommand.h"
#include "editing/patterncommands.h"
using namespace Xybrid::Editing;
#include "audio/audioengine.h"
using namespace Xybrid::Audio;
#include "util/pattern.h"
#include <QKeyEvent>
#include <QShortcut>
#include <QDebug>
#include <QHeaderView>
@ -84,6 +88,44 @@ PatternEditorView::PatternEditorView(QWidget *parent) : QTableView(parent) {
mdl->setPattern(pt);
setModel(&*mdl);
hdr->setModel(&*mdl->hprox);
{ /* set up hotkeys */ } {
auto transpose = [this](int amt, int key = Qt::Key_Alt) {
auto p = mdl->getPattern();
auto sel = this->selectionModel()->selection().first();
if (sel.width() == 1 && sel.height() == 1 && sel.left() % Util::colsPerChannel >= 2) { // single param
amt = std::clamp(amt, -16, 16);
auto c = new PatternDeltaCommand(p, Util::channelForColumn(sel.left()), sel.top());
auto& r = c->row;
auto par = static_cast<size_t>((sel.left() % Util::colsPerChannel) - 2) / 2;
if (r.numParams() > par) {
if (r.param(par)[0] != ' ') r.param(par)[1] = static_cast<uint8_t>(std::clamp(static_cast<int>(r.param(par)[1]) + amt, 0, 255));
c->commit();
} else c->cancel();
} else { // note(s)
amt = std::clamp(amt, -12, 12);
auto cc = new CompositeCommand();
for (auto s : sel.indexes()) {
if (s.column() % Util::colsPerChannel != 1) continue;
int ch = Util::channelForColumn(s.column());
auto c = new PatternDeltaCommand(p, ch, s.row());
if (c->row.note >= 0) {
c->row.note = static_cast<int16_t>(std::max(c->row.note + amt, 0));
cc->compose(c);
} else c->cancel();
}
cc->commit("transpose note(s)");
startPreview(key);
}
};
connect(new QShortcut(QKeySequence("Alt+Up"), this), &QShortcut::activated, this, [transpose] { transpose(1, Qt::Key_Up); });
connect(new QShortcut(QKeySequence("Alt+Down"), this), &QShortcut::activated, this, [transpose] { transpose(-1, Qt::Key_Down); });
connect(new QShortcut(QKeySequence("Alt+Right"), this), &QShortcut::activated, this, [transpose] { transpose(100, Qt::Key_Right); });
connect(new QShortcut(QKeySequence("Alt+Left"), this), &QShortcut::activated, this, [transpose] { transpose(-100, Qt::Key_Left); });
}
}
PatternEditorView::~PatternEditorView() {
@ -111,29 +153,34 @@ void PatternEditorView::keyPressEvent(QKeyEvent* e) {
QAbstractItemView::keyPressEvent(e);
if (!e->isAutoRepeat()) {
if (Util::keyToNote(e->key()) >= 0 || (e->key() >= Qt::Key_0 && e->key() <= Qt::Key_9) || e->key() == Qt::Key_Space) { // note-related key
auto ind = currentIndex();
int cc = ind.column() % PatternEditorModel::colsPerChannel;
int ch = (ind.column() - cc) / PatternEditorModel::colsPerChannel;
if (cc == 1) { // note column
auto& r = mdl->getPattern()->rowAt(ch, ind.row());
auto p = mdl->getPattern()->project->shared_from_this();
previewKey[e->key()] = {r.port, r.note};
audioEngine->preview(p, r.port, r.note, true);
}
startPreview(e->key());
}
}
}
void PatternEditorView::keyReleaseEvent(QKeyEvent* e) {
QAbstractItemView::keyReleaseEvent(e);
if (!e->isAutoRepeat()) {
if (Util::keyToNote(e->key()) >= 0 || (e->key() >= Qt::Key_0 && e->key() <= Qt::Key_9) || e->key() == Qt::Key_Space) { // note-related key
if (auto k = previewKey.find(e->key()); k != previewKey.end()) {
auto p = mdl->getPattern()->project->shared_from_this();
audioEngine->preview(p, k->second[0], k->second[1], false);
previewKey.erase(k);
}
}
if (!e->isAutoRepeat()) stopPreview(e->key());
}
void PatternEditorView::startPreview(int key) {
auto ind = currentIndex();
int cc = ind.column() % PatternEditorModel::colsPerChannel;
int ch = (ind.column() - cc) / PatternEditorModel::colsPerChannel;
if (cc == 1) { // note column
stopPreview(key); // end current preview first, if applicable
auto& r = mdl->getPattern()->rowAt(ch, ind.row());
auto p = mdl->getPattern()->project->shared_from_this();
previewKey[key] = {r.port, r.note};
audioEngine->preview(p, r.port, r.note, true);
}
}
void PatternEditorView::stopPreview(int key) {
if (auto k = previewKey.find(key); k != previewKey.end()) {
auto p = mdl->getPattern()->project->shared_from_this();
audioEngine->preview(p, k->second[0], k->second[1], false);
previewKey.erase(k);
}
}

View File

@ -43,6 +43,9 @@ namespace Xybrid::UI {
void refresh();
private:
void startPreview(int);
void stopPreview(int);
void updateHeaderOffset(int);
void headerMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex);
void headerDoubleClicked(int section);

10
xybrid/util/pattern.h Normal file
View File

@ -0,0 +1,10 @@
#pragma once
namespace Xybrid::Util {
static constexpr int paramSoftCap = 16; // maximum number of parameter columns that can be displayed per channel; the rest are hidden
static constexpr int colsPerChannel = 2 + (2 * paramSoftCap);
inline int channelForColumn(int col) {
return (col - (col % colsPerChannel)) / colsPerChannel;
}
}

View File

@ -89,7 +89,8 @@ HEADERS += \
gadgets/testsynth.h \
util/keys.h \
ui/gadgets/knobgadget.h \
gadgets/gainbalance.h
gadgets/gainbalance.h \
util/pattern.h
FORMS += \
mainwindow.ui