#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 #include #include #include #include namespace { bool _ = PluginRegistry::enqueueRegistration([] { auto i = std::make_shared(); i->id = "plug:2x03"; i->displayName = "2x03"; i->category = "Instrument"; //i->hidden = true; i->createInstance = []{ return std::make_shared(); }; PluginRegistry::registerPlugin(i); //inf = i; }); std::unordered_map waveNames = [] { std::unordered_map 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(smp); //p->bufR[i] += static_cast(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(m.value("wave").toInteger(wave)); adsr = m.value("adsr"); blipTime = m.value("blipTime").toDouble(blipTime); blipWave = static_cast(m.value("blipWave").toInteger(blipWave)); blipNote = static_cast(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(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"); } }