added transpose feature
parent
f41af3a0ec
commit
875a65d977
10
notes
10
notes
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue