From a0e9fe00939244a08b3ffda0378c0d505f92fe94 Mon Sep 17 00:00:00 2001 From: zetaPRIME Date: Thu, 18 Jul 2019 06:07:14 -0400 Subject: [PATCH] new synth purpose-built for supersaws --- xybrid/nodes/instrument/thicc.cpp | 176 ++++++++++++++++++++++++++++++ xybrid/nodes/instrument/thicc.h | 31 ++++++ 2 files changed, 207 insertions(+) create mode 100644 xybrid/nodes/instrument/thicc.cpp create mode 100644 xybrid/nodes/instrument/thicc.h diff --git a/xybrid/nodes/instrument/thicc.cpp b/xybrid/nodes/instrument/thicc.cpp new file mode 100644 index 0000000..24704ed --- /dev/null +++ b/xybrid/nodes/instrument/thicc.cpp @@ -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 + +#include +#include +#include +#include +#include + +namespace { + bool _ = PluginRegistry::enqueueRegistration([] { + auto i = std::make_shared(); + i->id = "plug:thicc"; + i->displayName = "THiCC"; + i->category = "Instrument"; + i->createInstance = []{ return std::make_shared(); }; + 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(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(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(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(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(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); +} diff --git a/xybrid/nodes/instrument/thicc.h b/xybrid/nodes/instrument/thicc.h new file mode 100644 index 0000000..a528911 --- /dev/null +++ b/xybrid/nodes/instrument/thicc.h @@ -0,0 +1,31 @@ +#pragma once + +#include "nodelib/instrumentcore.h" + +namespace Xybrid::Instruments { + class Thicc : public Data::Node { + NodeLib::InstrumentCore core; + + NodeLib::ADSR adsr; + + int voices = 1; + + double detune = 0.0; + //double shift = 0.0; + + public: + Thicc(); + ~Thicc() override = default; + + void init() override; + void reset() override; + void release() override; + void process() override; + + void saveData(QCborMap&) const override; + void loadData(const QCborMap&) override; + + void onGadgetCreated() override; + }; +} +