looots of 2x03 stuff
parent
2b42b7066a
commit
307bc54140
9
notes
9
notes
|
@ -53,10 +53,19 @@ TODO {
|
|||
-? return the latency/buffer size to 100ms once multithreading is streamlined
|
||||
# fix how qt5.12 broke header text (removed elide for now)
|
||||
|
||||
pseudoport (L) for legato (note-on for already-playing note)
|
||||
make knob notches more even (currently "previous value" is twice as big as any other step at px>1)
|
||||
add standardized step values for knobs (int enum?)
|
||||
|
||||
bugs to fix {
|
||||
graph connections sometimes spawn in duplicated :|
|
||||
- shifted <>? keys still count as different keys for note previewing
|
||||
|
||||
reset is never called when hooking up a node after starting preview; breaks instrumentcore
|
||||
^ on rebuilding queue, call reset on anything that wasn't there before (unordered_set)
|
||||
|
||||
on starting playback, sometimes a "thunk" sneaks into the waveform?
|
||||
|
||||
-? buffer underruns are being caused by some sync wonkiness between multiple workers
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
#include "basics.h"
|
||||
using namespace Xybrid::NodeLib;
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <QCborMap>
|
||||
#include <QCborValue>
|
||||
#include <QCborArray>
|
||||
|
||||
ADSR ADSR::normalized() {
|
||||
ADSR adsr = *this;
|
||||
adsr.a = std::max(adsr.a, shortStep);
|
||||
adsr.r = std::max(adsr.r, shortStep);
|
||||
if (adsr.s != 1.0) adsr.d = std::max(adsr.d, shortStep);
|
||||
return adsr;
|
||||
}
|
||||
|
||||
ADSR::ADSR(const QCborMap& m) {
|
||||
a = m.value("a").toDouble(a);
|
||||
d = m.value("d").toDouble(d);
|
||||
s = m.value("s").toDouble(s);
|
||||
r = m.value("r").toDouble(r);
|
||||
}
|
||||
ADSR::ADSR(const QCborValue& v) : ADSR(v.toMap()) { }
|
||||
|
||||
ADSR::operator QCborMap() const {
|
||||
QCborMap m;
|
||||
m.insert(QString("a"), a);
|
||||
m.insert(QString("d"), d);
|
||||
m.insert(QString("s"), s);
|
||||
m.insert(QString("r"), r);
|
||||
return m;
|
||||
}
|
||||
ADSR::operator QCborValue() const { return QCborMap(*this).toCborValue(); }
|
|
@ -1,12 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
class QCborMap;
|
||||
class QCborValue;
|
||||
|
||||
namespace Xybrid::NodeLib {
|
||||
// more precision than probably fits in a double, but it certainly shouldn't hurt
|
||||
constexpr double PI = 3.141592653589793238462643383279502884197169399375105820974;
|
||||
const double SEMI = 1.059463094359295264561825294946341700779204317494185628559;
|
||||
|
||||
constexpr double shortAttack = 0.001;
|
||||
constexpr double shortStep = 0.0025;
|
||||
struct ADSR {
|
||||
double a = 0, d = 0, s = 1, r = 0;
|
||||
double a = 0.0, d = 0.0, s = 1.0, r = 0.0;
|
||||
|
||||
ADSR normalized();
|
||||
|
||||
ADSR() = default;
|
||||
ADSR(const QCborMap&);
|
||||
ADSR(const QCborValue&);
|
||||
|
||||
operator QCborMap() const;
|
||||
operator QCborValue() const;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -17,10 +17,9 @@ Note::Note(uint16_t id) {
|
|||
void InstrumentCore::reset() {
|
||||
activeNotes.clear();
|
||||
activeNotes.reserve(16+1);
|
||||
deletedNotes.clear();
|
||||
deletedNotes.reserve(16+1);
|
||||
|
||||
smpTime = 1.0 / audioEngine->curSampleRate();
|
||||
time = 0;
|
||||
}
|
||||
|
||||
void InstrumentCore::process(Node* n) {
|
||||
|
@ -36,11 +35,14 @@ namespace {
|
|||
switch(phase) {
|
||||
case 0: {
|
||||
if (adsr.a == 0) return 1.0;
|
||||
return std::clamp(time / adsr.a, 0.0, 1.0);
|
||||
double a = 1.0 - std::clamp(time / adsr.a, 0.0, 1.0);
|
||||
a *= a;
|
||||
a *= a;
|
||||
return 1.0 - a;
|
||||
}
|
||||
case 1: {
|
||||
if (adsr.d == 0) return adsr.s;
|
||||
double sp = (1.0 - std::clamp(time / adsr.s, 0.0, 1.0));
|
||||
double sp = (1.0 - std::clamp(time / adsr.d, 0.0, 1.0));
|
||||
sp *= sp;
|
||||
return adsr.s + sp * (1.0 - adsr.s);
|
||||
}
|
||||
|
@ -53,10 +55,8 @@ namespace {
|
|||
}
|
||||
}
|
||||
|
||||
ADSR defAdsr{0.01, 0.3, 0.75, 0.2};
|
||||
|
||||
struct NoteIntern {
|
||||
|
||||
bool markedForDeletion = false;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -74,36 +74,44 @@ void InstrumentCore::process(CommandPort* i, AudioPort* o) {
|
|||
if (note.adsrPhase == 2) note = Note(id); // reinstantiate on replace
|
||||
note.note = n;
|
||||
note.time = note.adsrTime = -smpTime; // compensate for first-advance
|
||||
note.adsr = defAdsr;
|
||||
// if (sc.second)
|
||||
if (!sc.second) {
|
||||
// TODO: legato
|
||||
} else if (onNoteOn) onNoteOn(note);
|
||||
} else if (n < -1) { // note off
|
||||
//qDebug() << "note off" << id;
|
||||
//activeNotes.erase(id); // temp; later we'll let it do its own thing
|
||||
if (auto ni = activeNotes.find(id); ni != activeNotes.end()) {
|
||||
auto& note = ni->second;
|
||||
note.adsr.s = adsrVol(note.adsr, note.adsrPhase, note.adsrTime);
|
||||
note.adsrPhase = 2;
|
||||
note.adsrTime = -smpTime;
|
||||
if (n == -3) note.adsr.r = shortAttack;
|
||||
if (n == -3) note.adsr.r = shortStep;
|
||||
if (onNoteOff) onNoteOff(note, n == -3);
|
||||
}
|
||||
}
|
||||
mi += 5 + i->data[mi+4]*2;
|
||||
}
|
||||
|
||||
for (auto id : deletedNotes) activeNotes.erase(id);
|
||||
deletedNotes.clear();
|
||||
|
||||
// then do the thing
|
||||
if (o) o->pull();
|
||||
|
||||
if (processNote) {
|
||||
for (auto& p : activeNotes) {
|
||||
for (auto p = activeNotes.begin(); p != activeNotes.end(); ) {
|
||||
auto& note = p->second;
|
||||
NoteIntern n;
|
||||
p.second.intern = &n;
|
||||
processNote(p.second, o);
|
||||
p.second.intern = nullptr;
|
||||
note.intern = &n;
|
||||
processNote(note, o);
|
||||
if (n.markedForDeletion) {
|
||||
if (onDeleteNote) onDeleteNote(note);
|
||||
auto pr = p;
|
||||
p++;
|
||||
activeNotes.erase(pr);
|
||||
continue;
|
||||
}
|
||||
note.intern = nullptr;
|
||||
p++;
|
||||
}
|
||||
}
|
||||
time += smpTime * audioEngine->curTickSize();
|
||||
}
|
||||
|
||||
void InstrumentCore::advanceNote(Note& n) {
|
||||
|
@ -123,7 +131,15 @@ void InstrumentCore::advanceNote(Note& n) {
|
|||
|
||||
}
|
||||
|
||||
void InstrumentCore::deleteNote(Note& n) { deletedNotes.insert(n.id); }
|
||||
void InstrumentCore::deleteNote(Note& n) {
|
||||
if (n.intern != nullptr) {
|
||||
auto& ni = *reinterpret_cast<NoteIntern*>(n.intern);
|
||||
ni.markedForDeletion = true;
|
||||
} else {
|
||||
if (onDeleteNote) onDeleteNote(n);
|
||||
activeNotes.erase(n.id);
|
||||
}
|
||||
}
|
||||
|
||||
double Note::ampMult() const {
|
||||
double a = adsrVol(adsr, adsrPhase, adsrTime);
|
||||
|
|
|
@ -22,6 +22,7 @@ namespace Xybrid::NodeLib {
|
|||
* Not mandatory by any means, but handles all the "standard" commands for you.
|
||||
*/
|
||||
class InstrumentCore {
|
||||
double time;
|
||||
double smpTime;
|
||||
|
||||
public:
|
||||
|
@ -38,18 +39,23 @@ namespace Xybrid::NodeLib {
|
|||
double adsrTime = 0;
|
||||
uint8_t adsrPhase = 0;
|
||||
|
||||
std::array<double, 5> scratch{0.0};
|
||||
|
||||
Note() = default;
|
||||
Note(uint16_t id);
|
||||
|
||||
double ampMult() const;
|
||||
};
|
||||
std::unordered_map<uint16_t, Note> activeNotes;
|
||||
std::unordered_set<uint16_t> deletedNotes;
|
||||
|
||||
std::function<void(Note&, Data::AudioPort*)> processNote;
|
||||
std::function<void(Note&)> onNoteOn;
|
||||
std::function<void(Note&, bool)> onNoteOff;
|
||||
std::function<void(Note&)> onDeleteNote;
|
||||
|
||||
InstrumentCore() = default;
|
||||
|
||||
inline double globalTime() const { return time; }
|
||||
inline double sampleTime() const { return smpTime; }
|
||||
|
||||
void reset();
|
||||
|
|
|
@ -87,6 +87,7 @@ void Transpose::onGadgetCreated() {
|
|||
k->min = -24;
|
||||
k->max = 24;
|
||||
k->step = 1;
|
||||
k->stepPx = 3;
|
||||
k->bind(amount);
|
||||
|
||||
k->setLabel("Transpose");
|
||||
|
|
|
@ -10,9 +10,16 @@ using namespace Xybrid::Config;
|
|||
#include "audio/audioengine.h"
|
||||
using namespace Xybrid::Audio;
|
||||
|
||||
#include "ui/patchboard/nodeobject.h"
|
||||
#include "ui/gadgets/knobgadget.h"
|
||||
using namespace Xybrid::UI;
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QCborMap>
|
||||
#include <QCborValue>
|
||||
#include <QCborArray>
|
||||
|
||||
namespace {
|
||||
bool _ = PluginRegistry::enqueueRegistration([] {
|
||||
|
@ -26,8 +33,27 @@ namespace {
|
|||
//inf = i;
|
||||
});
|
||||
|
||||
std::unordered_map<int8_t, QString> waveNames = [] {
|
||||
std::unordered_map<int8_t, QString> m;
|
||||
|
||||
m[-1] = "keep";
|
||||
m[0] = "p50";
|
||||
m[1] = "p25";
|
||||
m[2] = "p12.5";
|
||||
m[3] = "tri";
|
||||
m[4] = "saw";
|
||||
m[5] = "pwm.l";
|
||||
m[6] = "pwm.g";
|
||||
|
||||
return m;
|
||||
}();
|
||||
|
||||
// silence qtcreator warnings about gcc optimize attributes
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wattributes"
|
||||
|
||||
// polyBLEP algorithm (pulse antialiasing), slightly modified from https://www.kvraudio.com/forum/viewtopic.php?t=375517
|
||||
double polyblep(double t, double dt) {
|
||||
[[gnu::optimize("O3")]] double polyblep(double t, double dt) {
|
||||
// 0 <= t < 1
|
||||
if (t < dt) {
|
||||
t /= dt;
|
||||
|
@ -42,17 +68,30 @@ namespace {
|
|||
return 0.0;
|
||||
}
|
||||
|
||||
double osc(double time, double freq, double delta) {
|
||||
//constexpr double duty = 0.125;
|
||||
double duty = 0.5 + std::cos(time * 2.5) * (1 - 0.125*2) * 0.5;
|
||||
time *= freq;
|
||||
[[gnu::optimize("O3")]] double oscPulse(double phase, double delta, double duty = 0.5) {
|
||||
//double duty = 0.5 + std::cos(time * 2.5) * (1 - 0.125*2) * 0.5;
|
||||
double d = 1.0;
|
||||
if (std::fmod(time, 1.0) >= duty) d = -1.0;
|
||||
delta *= freq;
|
||||
d += polyblep(std::fmod(time, 1.0), delta);
|
||||
d -= polyblep(std::fmod(time + (1.0 - duty), 1.0), delta);
|
||||
if (std::fmod(phase, 1.0) >= duty) d = -1.0;
|
||||
d += polyblep(std::fmod(phase, 1.0), delta);
|
||||
d -= polyblep(std::fmod(phase + (1.0 - duty), 1.0), delta);
|
||||
return d;
|
||||
}
|
||||
|
||||
[[gnu::optimize("O3")]] double oscTri(double phase) {
|
||||
phase = std::fmod(phase + 0.75, 1.0);
|
||||
phase = phase * 0.2 + (std::floor(phase*32.0) / 32.0) * 0.8;
|
||||
return std::abs(phase*2.0 - 1.0)*2.0 - 1.0;
|
||||
}
|
||||
|
||||
[[gnu::optimize("O3")]] double oscSaw(double phase, double delta) {
|
||||
phase = std::fmod(phase + 0.5, 1.0);
|
||||
double d = phase * 0.2 + (std::floor(phase*7.0) / 7.0 + (0.5/7.0)) * 0.8;
|
||||
d = d * 2.0 - 1.0;
|
||||
d -= polyblep(phase, delta);
|
||||
return d;
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
}
|
||||
|
||||
I2x03::I2x03() {
|
||||
|
@ -63,26 +102,238 @@ void I2x03::init() {
|
|||
addPort(Port::Input, Port::Command, 0);
|
||||
addPort(Port::Output, Port::Audio, 0);
|
||||
|
||||
core.processNote = [this](Note& note, AudioPort* p) {
|
||||
double enote = note.note;
|
||||
double freq = 440.0 * std::pow(SEMI, enote - (45+12*2));
|
||||
core.onNoteOn = [this](Note& note) {
|
||||
//qDebug() << "note on";
|
||||
note.adsr = adsr.normalized();
|
||||
};
|
||||
|
||||
core.processNote = [this](Note& note, AudioPort* p) {
|
||||
//double enote = note.note;
|
||||
double freq;// = 440.0 * std::pow(SEMI, enote - (45+12*2));
|
||||
|
||||
double smpTime = core.sampleTime();
|
||||
size_t ts = p->size;
|
||||
for (size_t i = 0; i < ts; i++) {
|
||||
core.advanceNote(note);
|
||||
//double d = std::clamp((std::sin(note.time * freq * PI*2) + 0.75) * 512.0, -1.0, 1.0) * note.ampMult();
|
||||
double d = osc(note.time, freq, core.sampleTime()) * note.ampMult();
|
||||
p->bufL[i] += static_cast<float>(d);
|
||||
p->bufR[i] += static_cast<float>(d);
|
||||
//note.time += tt;
|
||||
|
||||
double smp = 0.0;
|
||||
double n = note.note;
|
||||
int8_t wave = this->wave;
|
||||
if (note.time < blipTime) {
|
||||
if (blipWave != -1) wave = blipWave;
|
||||
n += blipNote;
|
||||
}
|
||||
freq = 440.0 * std::pow(SEMI, n - (45+12*2));
|
||||
|
||||
switch(wave) {
|
||||
case 0:
|
||||
smp = oscPulse(note.scratch[0], smpTime*freq, 0.5);
|
||||
break;
|
||||
case 1:
|
||||
smp = oscPulse(note.scratch[0], smpTime*freq, 0.25);
|
||||
break;
|
||||
case 2:
|
||||
smp = oscPulse(note.scratch[0], smpTime*freq, 0.125);
|
||||
break;
|
||||
case 3:
|
||||
smp = oscTri(note.scratch[0]);
|
||||
break;
|
||||
case 4:
|
||||
smp = oscSaw(note.scratch[0], smpTime*freq);
|
||||
break;
|
||||
case 5:
|
||||
smp = oscPulse(note.scratch[0], smpTime*freq, 0.5 + std::sin(PI*2 * (note.time / pwmTime + pwmPhase)) * pwmDepth * 0.5);
|
||||
break;
|
||||
case 6:
|
||||
smp = oscPulse(note.scratch[0], smpTime*freq, 0.5 + std::sin(PI*2 * ((core.globalTime() + smpTime * i) / pwmTime + pwmPhase)) * pwmDepth * 0.5);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
smp *= note.ampMult();
|
||||
note.scratch[0] += smpTime * freq;
|
||||
p->bufL[i] += static_cast<float>(smp);
|
||||
p->bufR[i] += static_cast<float>(smp);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void I2x03::reset() {
|
||||
core.reset();
|
||||
void I2x03::reset() { core.reset(); }
|
||||
void I2x03::process() { core.process(this); }
|
||||
|
||||
void I2x03::saveData(QCborMap& m) {
|
||||
m.insert(QString("wave"), wave);
|
||||
m.insert(QString("adsr"), adsr);
|
||||
|
||||
m.insert(QString("blipTime"), blipTime);
|
||||
m.insert(QString("blipWave"), blipWave);
|
||||
m.insert(QString("blipNote"), blipNote);
|
||||
|
||||
m.insert(QString("pwmDepth"), pwmDepth);
|
||||
m.insert(QString("pwmTime"), pwmTime);
|
||||
m.insert(QString("pwmPhase"), pwmPhase);
|
||||
}
|
||||
|
||||
void I2x03::process() {
|
||||
core.process(this);
|
||||
void I2x03::loadData(QCborMap& m) {
|
||||
wave = static_cast<int8_t>(m.value("wave").toInteger(wave));
|
||||
adsr = m.value("adsr");
|
||||
|
||||
blipTime = m.value("blipTime").toDouble(blipTime);
|
||||
blipWave = static_cast<int8_t>(m.value("blipWave").toInteger(blipWave));
|
||||
blipNote = static_cast<int>(m.value("blipNote").toInteger(blipNote));
|
||||
|
||||
pwmDepth = m.value("pwmDepth").toDouble(pwmDepth);
|
||||
pwmTime = m.value("pwmTime").toDouble(pwmTime);
|
||||
pwmPhase = m.value("pwmPhase").toDouble(pwmPhase);
|
||||
}
|
||||
|
||||
void I2x03::onGadgetCreated() {
|
||||
if (!obj) return;
|
||||
|
||||
auto wavetxt = [](double inp) {
|
||||
if (auto f = waveNames.find(static_cast<int8_t>(inp)); f != waveNames.end()) return f->second;
|
||||
return QString("?");
|
||||
};
|
||||
|
||||
constexpr double w = 248;
|
||||
constexpr double r1 = 16;
|
||||
constexpr double r2 = r1+64;
|
||||
constexpr double spc = 38;
|
||||
constexpr double l = spc-32;
|
||||
auto off = QPointF(spc, 0);
|
||||
|
||||
constexpr int stepSmall = 3;
|
||||
constexpr int stepBig = 15;
|
||||
|
||||
obj->setGadgetSize(w, 128);
|
||||
|
||||
{
|
||||
KnobGadget* k;
|
||||
|
||||
k = new KnobGadget(obj->contents);
|
||||
k->setPos(l, r1);
|
||||
k->min = 0;
|
||||
k->max = 6;
|
||||
k->step = 1;
|
||||
k->stepPx = stepBig;
|
||||
k->bind(wave);
|
||||
k->fText = wavetxt;
|
||||
k->setLabel("Wave");
|
||||
}
|
||||
|
||||
{
|
||||
// adsr group
|
||||
auto g = QPointF(w-(spc*4), r1);
|
||||
KnobGadget* k;
|
||||
|
||||
k = new KnobGadget(obj->contents);
|
||||
k->setPos(g + off*0);
|
||||
k->min = 0.0;
|
||||
k->max = 5.0;
|
||||
k->step = .01;
|
||||
k->stepPx = stepSmall;
|
||||
k->bind(adsr.a);
|
||||
k->setLabel("Attack");
|
||||
|
||||
k = new KnobGadget(obj->contents);
|
||||
k->setPos(g + off*1);
|
||||
k->min = 0.0;
|
||||
k->max = 5.0;
|
||||
k->step = .01;
|
||||
k->stepPx = stepSmall;
|
||||
k->bind(adsr.d);
|
||||
k->setLabel("Decay");
|
||||
|
||||
k = new KnobGadget(obj->contents);
|
||||
k->setPos(g + off*2);
|
||||
k->min = 0.0;
|
||||
k->max = 1.0;
|
||||
k->defaultVal = 1.0;
|
||||
k->step = .01;
|
||||
k->stepPx = stepSmall;
|
||||
k->bind(adsr.s);
|
||||
k->setLabel("Sustain");
|
||||
|
||||
k = new KnobGadget(obj->contents);
|
||||
k->setPos(g + off*3);
|
||||
k->min = 0.0;
|
||||
k->max = 5.0;
|
||||
k->step = .01;
|
||||
k->stepPx = stepSmall;
|
||||
k->bind(adsr.r);
|
||||
k->setLabel("Release");
|
||||
}
|
||||
|
||||
{
|
||||
// blip group
|
||||
auto g = QPointF(l, r2);
|
||||
KnobGadget* k;
|
||||
|
||||
k = new KnobGadget(obj->contents);
|
||||
k->setPos(g + off*0);
|
||||
k->min = 0.0;
|
||||
k->max = 0.1;
|
||||
k->step = .01;
|
||||
k->stepPx = stepSmall;
|
||||
k->bind(blipTime);
|
||||
k->setLabel("Blip");
|
||||
|
||||
k = new KnobGadget(obj->contents);
|
||||
k->setPos(g + off*1);
|
||||
k->min = -1;
|
||||
k->max = 4;
|
||||
k->defaultVal = -1;
|
||||
k->step = 1;
|
||||
k->stepPx = stepBig;
|
||||
k->bind(blipWave);
|
||||
k->fText = wavetxt;
|
||||
k->setLabel("Wave");
|
||||
|
||||
k = new KnobGadget(obj->contents);
|
||||
k->setPos(g + off*2);
|
||||
k->min = -12;
|
||||
k->max = 12;
|
||||
k->step = 1;
|
||||
k->stepPx = stepSmall;
|
||||
k->bind(blipNote);
|
||||
k->setLabel("Note");
|
||||
}
|
||||
|
||||
{
|
||||
// pwm group
|
||||
auto g = QPointF(w-(spc*3), r2);
|
||||
KnobGadget* k;
|
||||
|
||||
k = new KnobGadget(obj->contents);
|
||||
k->setPos(g + off*0);
|
||||
k->min = 0.0;
|
||||
k->max = 1.0;
|
||||
k->defaultVal = 0.75;
|
||||
k->step = .01;
|
||||
k->stepPx = stepSmall;
|
||||
k->bind(pwmDepth);
|
||||
k->fText = [](double d) {
|
||||
return QString("%1%").arg(d*100, 0);
|
||||
};
|
||||
k->setLabel("PWM");
|
||||
|
||||
k = new KnobGadget(obj->contents);
|
||||
k->setPos(g + off*1);
|
||||
k->min = 0.01;
|
||||
k->max = 5.0;
|
||||
k->defaultVal = 3.0;
|
||||
k->step = .01;
|
||||
k->stepPx = stepSmall;
|
||||
k->bind(pwmTime);
|
||||
k->setLabel("Time");
|
||||
|
||||
k = new KnobGadget(obj->contents);
|
||||
k->setPos(g + off*2);
|
||||
k->min = 0.0;
|
||||
k->max = 1.0;
|
||||
k->step = .01;
|
||||
k->stepPx = stepSmall;
|
||||
k->bind(pwmPhase);
|
||||
k->setLabel("Phase");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,18 @@
|
|||
|
||||
namespace Xybrid::Instruments {
|
||||
class I2x03: public Data::Node {
|
||||
//
|
||||
NodeLib::InstrumentCore core;
|
||||
|
||||
NodeLib::ADSR adsr;
|
||||
|
||||
int8_t wave = 0;
|
||||
int8_t blipWave = -1;
|
||||
|
||||
int blipNote = 0;
|
||||
double blipTime = 0.0;
|
||||
double pwmTime = 3.0;
|
||||
double pwmDepth = 0.75;
|
||||
double pwmPhase = 0.0;
|
||||
public:
|
||||
I2x03();
|
||||
~I2x03() override = default;
|
||||
|
@ -16,13 +26,13 @@ namespace Xybrid::Instruments {
|
|||
|
||||
//void onRename() override;
|
||||
|
||||
//void saveData(QCborMap&) override;
|
||||
//void loadData(QCborMap&) override;
|
||||
void saveData(QCborMap&) override;
|
||||
void loadData(QCborMap&) override;
|
||||
|
||||
//void onUnparent(std::shared_ptr<Data::Graph>) override;
|
||||
//void onParent(std::shared_ptr<Data::Graph>) override;
|
||||
|
||||
//void onGadgetCreated() override;
|
||||
void onGadgetCreated() override;
|
||||
//void onDoubleClick() override;
|
||||
|
||||
//void drawCustomChrome(QPainter*, const QStyleOptionGraphicsItem*) override;
|
||||
|
|
|
@ -8,6 +8,24 @@ using namespace Xybrid::UI;
|
|||
#include <QRadialGradient>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
|
||||
template void KnobGadget::bind(double&);
|
||||
template void KnobGadget::bind(float&);
|
||||
template void KnobGadget::bind(int8_t&);
|
||||
template void KnobGadget::bind(uint8_t&);
|
||||
template void KnobGadget::bind(int16_t&);
|
||||
template void KnobGadget::bind(uint16_t&);
|
||||
template void KnobGadget::bind(int32_t&);
|
||||
template void KnobGadget::bind(uint32_t&);
|
||||
|
||||
template void KnobGadget::bind(std::atomic<double>&);
|
||||
template void KnobGadget::bind(std::atomic<float>&);
|
||||
template void KnobGadget::bind(std::atomic<int8_t>&);
|
||||
template void KnobGadget::bind(std::atomic<uint8_t>&);
|
||||
template void KnobGadget::bind(std::atomic<int16_t>&);
|
||||
template void KnobGadget::bind(std::atomic<uint16_t>&);
|
||||
template void KnobGadget::bind(std::atomic<int32_t>&);
|
||||
template void KnobGadget::bind(std::atomic<uint32_t>&);
|
||||
|
||||
double KnobGadget::get() {
|
||||
double v = fGet();
|
||||
if (v != lastVal) {
|
||||
|
@ -107,6 +125,7 @@ void KnobGadget::mouseReleaseEvent(QGraphicsSceneMouseEvent* e) {
|
|||
void KnobGadget::mouseMoveEvent(QGraphicsSceneMouseEvent* e) {
|
||||
if (highlighted) {
|
||||
auto tdelta = -(e->screenPos().y() - e->buttonDownScreenPos(Qt::LeftButton).y());
|
||||
tdelta /= stepPx;
|
||||
fSet(std::clamp(startVal + tdelta * step, min, max));
|
||||
}
|
||||
update();
|
||||
|
|
|
@ -27,6 +27,7 @@ namespace Xybrid::UI {
|
|||
double min = 0.0;
|
||||
double max = 1.0;
|
||||
double step = 0.01;
|
||||
int stepPx = 1;
|
||||
double defaultVal = 0.0;
|
||||
|
||||
qreal size = 32;
|
||||
|
|
Loading…
Reference in New Issue