229 lines
7.0 KiB
C++
229 lines
7.0 KiB
C++
#include "2x03.h"
|
|
using Xybrid::Instruments::I2x03;
|
|
using namespace Xybrid::NodeLib;
|
|
using Note = InstrumentCore::Note;
|
|
using namespace Xybrid::Data;
|
|
|
|
#include "nodelib/commandreader.h"
|
|
|
|
#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/layoutgadget.h"
|
|
#include "ui/gadgets/knobgadget.h"
|
|
using namespace Xybrid::UI;
|
|
|
|
#include "util/strings.h"
|
|
#include "util/ext.h"
|
|
|
|
#include <cmath>
|
|
|
|
#include <QDebug>
|
|
#include <QCborMap>
|
|
#include <QCborValue>
|
|
#include <QCborArray>
|
|
|
|
// clazy:excludeall=non-pod-global-static
|
|
RegisterPlugin(I2x03, {
|
|
i->id = "plug:2x03";
|
|
i->displayName = "2x03";
|
|
i->category = "Instrument";
|
|
})
|
|
|
|
namespace {
|
|
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;
|
|
}();
|
|
|
|
// polyBLEP algorithm (pulse antialiasing), slightly modified from https://www.kvraudio.com/forum/viewtopic.php?t=375517
|
|
force_opt 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;
|
|
}
|
|
|
|
force_opt 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 - (duty-0.5)*2; // DC offset compensation
|
|
}
|
|
|
|
force_opt 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;
|
|
}
|
|
|
|
force_opt 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;
|
|
}
|
|
|
|
}
|
|
|
|
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.globalParam['Q'] = [](const ParamReader& pr) {
|
|
qDebug() << "global recieved" << pr.param() << pr.val();
|
|
return true;
|
|
};
|
|
|
|
core.processNote = [this](Note& note, AudioPort* p) {
|
|
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.effectiveNote() + detune;
|
|
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::release() { core.release(); }
|
|
void I2x03::process() { core.process(this); }
|
|
|
|
void I2x03::saveData(QCborMap& m) const {
|
|
m[qs("wave")] = wave;
|
|
m[qs("detune")] = detune;
|
|
m[qs("adsr")] = adsr;
|
|
|
|
m[qs("blipTime")] = blipTime;
|
|
m[qs("blipWave")] = blipWave;
|
|
m[qs("blipNote")] = blipNote;
|
|
|
|
m[qs("pwmDepth")] = pwmDepth;
|
|
m[qs("pwmTime")] = pwmTime;
|
|
m[qs("pwmPhase")] = pwmPhase;
|
|
}
|
|
|
|
void I2x03::loadData(const QCborMap& m) {
|
|
wave = static_cast<int8_t>(m.value("wave").toInteger(wave));
|
|
detune = m.value("detune").toDouble(detune);
|
|
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 qs("?");
|
|
};
|
|
|
|
auto ol = new LayoutGadget(obj, true);
|
|
auto l1 = new LayoutGadget(ol);
|
|
auto l2 = new LayoutGadget(ol);
|
|
|
|
// first line
|
|
(new KnobGadget(l1))->bind(wave)->setLabel(qs("Wave"))->setTextFunc(wavetxt)->setRange(0, 6, 1, KnobGadget::BigStep);
|
|
(new KnobGadget(l1))->bind(detune)->setLabel(qs("Detune"))->setRange(-1.0, 1.0, 0.01);
|
|
l1->addSpacer();
|
|
KnobGadget::autoCreate(l1, adsr);
|
|
|
|
// blip group
|
|
(new KnobGadget(l2))->bind(blipTime)->setLabel(qs("Blip"))->setRange(0.0, 0.1, 0.001);
|
|
(new KnobGadget(l2))->bind(blipWave)->setLabel(qs("Wave"))->setTextFunc(wavetxt)->setRange(-1, 4, 1, KnobGadget::BigStep)->setDefault(-1);
|
|
(new KnobGadget(l2))->bind(blipNote)->setLabel(qs("Note"))->setTextFunc(KnobGadget::textOffset)->setRange(-12, 12, 1);
|
|
|
|
l2->addSpacer();
|
|
|
|
// pwm group
|
|
KnobGadget::autoPercent(l2, pwmDepth)->setLabel(qs("PWM"))->setDefault(0.75);
|
|
(new KnobGadget(l2))->bind(pwmTime)->setLabel(qs("Time"))->setRange(0.01, 5.0, 0.01)->setDefault(3.0);
|
|
KnobGadget::autoPercent(l2, pwmPhase)->setLabel(qs("Phase"));
|
|
|
|
//
|
|
}
|