xybrid/xybrid/nodes/instrument/2x03.cpp

343 lines
9.3 KiB
C++

#include "2x03.h"
using Xybrid::Instruments::I2x03;
using namespace Xybrid::NodeLib;
using Note = InstrumentCore::Note;
using namespace Xybrid::Data;
#include "data/porttypes.h"
#include "config/pluginregistry.h"
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([] {
auto i = std::make_shared<PluginInfo>();
i->id = "plug:2x03";
i->displayName = "2x03";
i->category = "Instrument";
//i->hidden = true;
i->createInstance = []{ return std::make_shared<I2x03>(); };
PluginRegistry::registerPlugin(i);
//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
[[gnu::optimize("O3")]] double polyblep(double t, double dt) {
// 0 <= t < 1
if (t < dt) {
t /= dt;
return t+t - t*t - 1.0;
}
// -1 < t < 0
else if (t > 1.0 - dt) {
t = (t - 1.0) / dt;
return t*t + t+t + 1.0;
}
// 0 otherwise
return 0.0;
}
[[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(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() {
}
void I2x03::init() {
addPort(Port::Input, Port::Command, 0);
addPort(Port::Output, Port::Audio, 0);
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 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;
auto pn = panSignal(smp, note.pan);
p->bufL[i] += pn.first;
p->bufR[i] += pn.second;
//p->bufL[i] += static_cast<float>(smp);
//p->bufR[i] += static_cast<float>(smp);
}
};
}
void I2x03::reset() { core.reset(); }
void I2x03::process() { core.process(this); }
void I2x03::saveData(QCborMap& m) const {
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::loadData(const 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 = .001;
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");
}
}