|
|
|
@ -0,0 +1,176 @@
|
|
|
|
|
#include "thicc.h"
|
|
|
|
|
using Xybrid::Instruments::Thicc;
|
|
|
|
|
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 <cmath>
|
|
|
|
|
|
|
|
|
|
#include <QDebug>
|
|
|
|
|
#include <QRandomGenerator>
|
|
|
|
|
#include <QCborMap>
|
|
|
|
|
#include <QCborValue>
|
|
|
|
|
#include <QCborArray>
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
bool _ = PluginRegistry::enqueueRegistration([] {
|
|
|
|
|
auto i = std::make_shared<PluginInfo>();
|
|
|
|
|
i->id = "plug:thicc";
|
|
|
|
|
i->displayName = "THiCC";
|
|
|
|
|
i->category = "Instrument";
|
|
|
|
|
i->createInstance = []{ return std::make_shared<Thicc>(); };
|
|
|
|
|
PluginRegistry::registerPlugin(i);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
[[maybe_unused]] inline double wrap(double d) {
|
|
|
|
|
while (true) {
|
|
|
|
|
if (d > 1.0) d = (d - 2.0) * -1; //d-=2.0;
|
|
|
|
|
else if (d < -1.0) d = (d + 2.0) * -1;
|
|
|
|
|
else return d;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
[[maybe_unused]] inline double lerp(double p, double a, double b) {
|
|
|
|
|
return b * p + a * (1.0 - p);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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 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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Thicc::Thicc() { }
|
|
|
|
|
|
|
|
|
|
void Thicc::init() {
|
|
|
|
|
double avg = 0;
|
|
|
|
|
for (int i = 0; i < 4096; i++) avg += std::abs(std::fmod(static_cast<double>(i) / 4096.0, 1.0) * 2.0 - 1.0);
|
|
|
|
|
qDebug() << "average of" << (avg / 4096.0);
|
|
|
|
|
|
|
|
|
|
addPort(Port::Input, Port::Command, 0);
|
|
|
|
|
addPort(Port::Output, Port::Audio, 0);
|
|
|
|
|
|
|
|
|
|
core.onNoteOn = [this](Note& note) {
|
|
|
|
|
//qDebug() << "note on";
|
|
|
|
|
note.adsr = adsr.normalized();
|
|
|
|
|
note.scratch[0] = static_cast<double>(QRandomGenerator::global()->generate() % 573000);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
core.globalParam['Q'] = [](const ParamReader& pr) {
|
|
|
|
|
qDebug() << "global recieved" << pr.param() << pr.val();
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
core.processNote = [this](Note& note, AudioPort* p) {
|
|
|
|
|
double freq;
|
|
|
|
|
|
|
|
|
|
int vc = voices;
|
|
|
|
|
double vf = static_cast<double>(vc);
|
|
|
|
|
double spr = std::pow(SEMI, detune/(vf/2.0));
|
|
|
|
|
|
|
|
|
|
double smpTime = core.sampleTime();
|
|
|
|
|
size_t ts = p->size;
|
|
|
|
|
for (size_t i = 0; i < ts; i++) {
|
|
|
|
|
core.advanceNote(note);
|
|
|
|
|
|
|
|
|
|
double n = note.effectiveNote();
|
|
|
|
|
freq = 440.0 * std::pow(SEMI, n - (45+12*2));
|
|
|
|
|
auto si = note.scratch[0];
|
|
|
|
|
double delta = smpTime * freq;
|
|
|
|
|
note.scratch[0] += delta;
|
|
|
|
|
|
|
|
|
|
// s
|
|
|
|
|
|
|
|
|
|
double o = 0;
|
|
|
|
|
for (int i = 0; i < vc; i++) {
|
|
|
|
|
auto ii = static_cast<double>(i);
|
|
|
|
|
double pc = ii / vf; // phase coefficient
|
|
|
|
|
double dc = ii - ((vf-1.0)/2.0); // detune coefficient
|
|
|
|
|
double dm = std::pow(spr, dc);
|
|
|
|
|
double sg = i % 2 == 0 ? 1.0 : -1.0;
|
|
|
|
|
//o += (std::fmod(pc + si * dm, 1.0) * 2.0 - 1.0) * sg;
|
|
|
|
|
o += oscSaw(pc + si * dm, delta * dm) * sg;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
o /= 1.0 + ((vf - 1.0)/3.0);
|
|
|
|
|
|
|
|
|
|
// e
|
|
|
|
|
|
|
|
|
|
note.scratch[3] += /*(smpTime / (1.0/20000.0))*/0.5 * (o - note.scratch[3]); // simple low pass;
|
|
|
|
|
if (note.scratch[3] != note.scratch[3]) note.scratch[3] = 0; // nan!? WHY
|
|
|
|
|
AudioFrame out = o;//note.scratch[3];
|
|
|
|
|
|
|
|
|
|
(*p)[i] += out.gainBalance(0, note.pan) * note.ampMult();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Thicc::reset() { core.reset(); }
|
|
|
|
|
void Thicc::release() { core.release(); }
|
|
|
|
|
void Thicc::process() { core.process(this); }
|
|
|
|
|
|
|
|
|
|
void Thicc::saveData(QCborMap& m) const {
|
|
|
|
|
m[qs("adsr")] = adsr;
|
|
|
|
|
|
|
|
|
|
m[qs("voices")] = voices;
|
|
|
|
|
m[qs("detune")] = detune;
|
|
|
|
|
//m[qs("shift")] = shift;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Thicc::loadData(const QCborMap& m) {
|
|
|
|
|
adsr = m.value("adsr");
|
|
|
|
|
|
|
|
|
|
voices = static_cast<int>(m.value("voices").toInteger(voices));
|
|
|
|
|
detune = m.value("detune").toDouble(detune);
|
|
|
|
|
//shift = m.value("shift").toDouble(shift);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Thicc::onGadgetCreated() {
|
|
|
|
|
auto l = new LayoutGadget(obj);
|
|
|
|
|
|
|
|
|
|
(new KnobGadget(l))->bind(voices)->setLabel(qs("Voices"))->setRange(1, 16, 1, KnobGadget::BigStep)->setDefault(1);
|
|
|
|
|
(new KnobGadget(l))->bind(detune)->setLabel(qs("Detune"))->setRange(0.0, 1.0, 0.001);
|
|
|
|
|
//(new KnobGadget(l))->bind(shift)->setLabel(qs("Shift"))->setRange(0.0, 25.0, 0.01);
|
|
|
|
|
l->addSpacer();
|
|
|
|
|
KnobGadget::autoCreate(l, adsr);
|
|
|
|
|
}
|