#include "AudibleInstruments.hpp"
#include "dsp/samplerate.hpp"
#include "dsp/ringbuffer.hpp"
#include "dsp/functions.hpp"
#include "dsp/digital.hpp"
#include "plaits/dsp/voice.h"
struct Plaits : Module {
enum ParamIds {
MODEL1_PARAM,
MODEL2_PARAM,
FREQ_PARAM,
HARMONICS_PARAM,
TIMBRE_PARAM,
MORPH_PARAM,
TIMBRE_CV_PARAM,
FREQ_CV_PARAM,
MORPH_CV_PARAM,
NUM_PARAMS
};
enum InputIds {
ENGINE_INPUT,
TIMBRE_INPUT,
FREQ_INPUT,
MORPH_INPUT,
HARMONICS_INPUT,
TRIGGER_INPUT,
LEVEL_INPUT,
NOTE_INPUT,
NUM_INPUTS
};
enum OutputIds {
OUT_OUTPUT,
AUX_OUTPUT,
NUM_OUTPUTS
};
enum LightIds {
ENUMS(MODEL_LIGHT, 8 * 2),
NUM_LIGHTS
};
plaits::Voice voice;
plaits::Patch patch;
plaits::Modulations modulations;
char shared_buffer[16384];
float triPhase = 0.f;
SampleRateConverter<2> outputSrc;
DoubleRingBuffer, 256> outputBuffer;
bool lowCpu = false;
bool lpg = false;
SchmittTrigger model1Trigger;
SchmittTrigger model2Trigger;
Plaits() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
memset(shared_buffer, 0, sizeof(shared_buffer));
stmlib::BufferAllocator allocator(shared_buffer, sizeof(shared_buffer));
voice.Init(&allocator);
memset(&patch, 0, sizeof(patch));
memset(&modulations, 0, sizeof(modulations));
onReset();
}
void onReset() override {
patch.engine = 0;
patch.lpg_colour = 0.5f;
patch.decay = 0.5f;
}
void onRandomize() override {
patch.engine = randomu32() % 16;
}
json_t *toJson() override {
json_t *rootJ = json_object();
json_object_set_new(rootJ, "lowCpu", json_boolean(lowCpu));
json_object_set_new(rootJ, "model", json_integer(patch.engine));
json_object_set_new(rootJ, "lpgColor", json_real(patch.lpg_colour));
json_object_set_new(rootJ, "decay", json_real(patch.decay));
return rootJ;
}
void fromJson(json_t *rootJ) override {
json_t *lowCpuJ = json_object_get(rootJ, "lowCpu");
if (lowCpuJ)
lowCpu = json_boolean_value(lowCpuJ);
json_t *modelJ = json_object_get(rootJ, "model");
if (modelJ)
patch.engine = json_integer_value(modelJ);
json_t *lpgColorJ = json_object_get(rootJ, "lpgColor");
if (lpgColorJ)
patch.lpg_colour = json_number_value(lpgColorJ);
json_t *decayJ = json_object_get(rootJ, "decay");
if (decayJ)
patch.decay = json_number_value(decayJ);
}
void step() override {
if (outputBuffer.empty()) {
const int blockSize = 12;
// Model buttons
if (model1Trigger.process(params[MODEL1_PARAM].value)) {
if (patch.engine >= 8) {
patch.engine -= 8;
}
else {
patch.engine = (patch.engine + 1) % 8;
}
}
if (model2Trigger.process(params[MODEL2_PARAM].value)) {
if (patch.engine < 8) {
patch.engine += 8;
}
else {
patch.engine = (patch.engine + 1) % 8 + 8;
}
}
// Model lights
int activeEngine = voice.active_engine();
triPhase += 2.f * engineGetSampleTime() * blockSize;
if (triPhase >= 1.f)
triPhase -= 1.f;
float tri = (triPhase < 0.5f) ? triPhase * 2.f : (1.f - triPhase) * 2.f;
for (int i = 0; i < 8; i++) {
lights[MODEL_LIGHT + 2*i + 0].setBrightness((activeEngine == i) ? 1.f : (patch.engine == i) ? tri : 0.f);
lights[MODEL_LIGHT + 2*i + 1].setBrightness((activeEngine == i + 8) ? 1.f : (patch.engine == i + 8) ? tri : 0.f);
}
// Calculate pitch for lowCpu mode if needed
float pitch = params[FREQ_PARAM].value;
if (lowCpu)
pitch += log2f(48000.f * engineGetSampleTime());
// Update patch
patch.note = 60.f + pitch * 12.f;
patch.harmonics = params[HARMONICS_PARAM].value;
if (!lpg) {
patch.timbre = params[TIMBRE_PARAM].value;
patch.morph = params[MORPH_PARAM].value;
}
else {
patch.lpg_colour = params[TIMBRE_PARAM].value;
patch.decay = params[MORPH_PARAM].value;
}
patch.frequency_modulation_amount = params[FREQ_CV_PARAM].value;
patch.timbre_modulation_amount = params[TIMBRE_CV_PARAM].value;
patch.morph_modulation_amount = params[MORPH_CV_PARAM].value;
// Update modulations
modulations.engine = inputs[ENGINE_INPUT].value / 5.f;
modulations.note = inputs[NOTE_INPUT].value * 12.f;
modulations.frequency = inputs[FREQ_INPUT].value * 6.f;
modulations.harmonics = inputs[HARMONICS_INPUT].value / 5.f;
modulations.timbre = inputs[TIMBRE_INPUT].value / 8.f;
modulations.morph = inputs[MORPH_INPUT].value / 8.f;
// Triggers at around 0.7 V
modulations.trigger = inputs[TRIGGER_INPUT].value / 3.f;
modulations.level = inputs[LEVEL_INPUT].value / 8.f;
modulations.frequency_patched = inputs[FREQ_INPUT].active;
modulations.timbre_patched = inputs[TIMBRE_INPUT].active;
modulations.morph_patched = inputs[MORPH_INPUT].active;
modulations.trigger_patched = inputs[TRIGGER_INPUT].active;
modulations.level_patched = inputs[LEVEL_INPUT].active;
// Render frames
plaits::Voice::Frame output[blockSize];
voice.Render(patch, modulations, output, blockSize);
// Convert output to frames
Frame<2> outputFrames[blockSize];
for (int i = 0; i < blockSize; i++) {
outputFrames[i].samples[0] = output[i].out / 32768.f;
outputFrames[i].samples[1] = output[i].aux / 32768.f;
}
// Convert output
if (lowCpu) {
int len = min(outputBuffer.capacity(), blockSize);
memcpy(outputBuffer.endData(), outputFrames, len * sizeof(Frame<2>));
outputBuffer.endIncr(len);
}
else {
outputSrc.setRates(48000, engineGetSampleRate());
int inLen = blockSize;
int outLen = outputBuffer.capacity();
outputSrc.process(outputFrames, &inLen, outputBuffer.endData(), &outLen);
outputBuffer.endIncr(outLen);
}
}
// Set output
if (!outputBuffer.empty()) {
Frame<2> outputFrame = outputBuffer.shift();
// Inverting op-amp on outputs
outputs[OUT_OUTPUT].value = -outputFrame.samples[0] * 5.f;
outputs[AUX_OUTPUT].value = -outputFrame.samples[1] * 5.f;
}
}
};
static const std::string modelLabels[16] = {
"Pair of classic waveforms",
"Waveshaping oscillator",
"Two operator FM",
"Granular formant oscillator",
"Harmonic oscillator",
"Wavetable oscillator",
"Chords",
"Vowel and speech synthesis",
"Granular cloud",
"Filtered noise",
"Particle noise",
"Inharmonic string modeling",
"Modal resonator",
"Analog bass drum",
"Analog snare drum",
"Analog hi-hat",
};
struct PlaitsWidget : ModuleWidget {
PlaitsWidget(Plaits *module) : ModuleWidget(module) {
setPanel(SVG::load(assetPlugin(plugin, "res/Plaits.svg")));
addChild(Widget::create(Vec(RACK_GRID_WIDTH, 0)));
addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
addChild(Widget::create(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
addParam(ParamWidget::create(mm2px(Vec(23.32685, 14.6539)), module, Plaits::MODEL1_PARAM, 0.0, 1.0, 0.0));
addParam(ParamWidget::create(mm2px(Vec(32.22764, 14.6539)), module, Plaits::MODEL2_PARAM, 0.0, 1.0, 0.0));
addParam(ParamWidget::create(mm2px(Vec(3.1577, 20.21088)), module, Plaits::FREQ_PARAM, -4.0, 4.0, 0.0));
addParam(ParamWidget::create(mm2px(Vec(39.3327, 20.21088)), module, Plaits::HARMONICS_PARAM, 0.0, 1.0, 0.5));
addParam(ParamWidget::create(mm2px(Vec(4.04171, 49.6562)), module, Plaits::TIMBRE_PARAM, 0.0, 1.0, 0.5));
addParam(ParamWidget::create(mm2px(Vec(42.71716, 49.6562)), module, Plaits::MORPH_PARAM, 0.0, 1.0, 0.5));
addParam(ParamWidget::create(mm2px(Vec(7.88712, 77.60705)), module, Plaits::TIMBRE_CV_PARAM, -1.0, 1.0, 0.0));
addParam(ParamWidget::create(mm2px(Vec(27.2245, 77.60705)), module, Plaits::FREQ_CV_PARAM, -1.0, 1.0, 0.0));
addParam(ParamWidget::create(mm2px(Vec(46.56189, 77.60705)), module, Plaits::MORPH_CV_PARAM, -1.0, 1.0, 0.0));
addInput(Port::create(mm2px(Vec(3.31381, 92.48067)), Port::INPUT, module, Plaits::ENGINE_INPUT));
addInput(Port::create(mm2px(Vec(14.75983, 92.48067)), Port::INPUT, module, Plaits::TIMBRE_INPUT));
addInput(Port::create(mm2px(Vec(26.20655, 92.48067)), Port::INPUT, module, Plaits::FREQ_INPUT));
addInput(Port::create(mm2px(Vec(37.65257, 92.48067)), Port::INPUT, module, Plaits::MORPH_INPUT));
addInput(Port::create(mm2px(Vec(49.0986, 92.48067)), Port::INPUT, module, Plaits::HARMONICS_INPUT));
addInput(Port::create(mm2px(Vec(3.31381, 107.08103)), Port::INPUT, module, Plaits::TRIGGER_INPUT));
addInput(Port::create(mm2px(Vec(14.75983, 107.08103)), Port::INPUT, module, Plaits::LEVEL_INPUT));
addInput(Port::create(mm2px(Vec(26.20655, 107.08103)), Port::INPUT, module, Plaits::NOTE_INPUT));
addOutput(Port::create(mm2px(Vec(37.65257, 107.08103)), Port::OUTPUT, module, Plaits::OUT_OUTPUT));
addOutput(Port::create(mm2px(Vec(49.0986, 107.08103)), Port::OUTPUT, module, Plaits::AUX_OUTPUT));
addChild(ModuleLightWidget::create>(mm2px(Vec(28.79498, 23.31649)), module, Plaits::MODEL_LIGHT + 0 * 2));
addChild(ModuleLightWidget::create>(mm2px(Vec(28.79498, 28.71704)), module, Plaits::MODEL_LIGHT + 1 * 2));
addChild(ModuleLightWidget::create>(mm2px(Vec(28.79498, 34.1162)), module, Plaits::MODEL_LIGHT + 2 * 2));
addChild(ModuleLightWidget::create>(mm2px(Vec(28.79498, 39.51675)), module, Plaits::MODEL_LIGHT + 3 * 2));
addChild(ModuleLightWidget::create>(mm2px(Vec(28.79498, 44.91731)), module, Plaits::MODEL_LIGHT + 4 * 2));
addChild(ModuleLightWidget::create>(mm2px(Vec(28.79498, 50.31785)), module, Plaits::MODEL_LIGHT + 5 * 2));
addChild(ModuleLightWidget::create>(mm2px(Vec(28.79498, 55.71771)), module, Plaits::MODEL_LIGHT + 6 * 2));
addChild(ModuleLightWidget::create>(mm2px(Vec(28.79498, 61.11827)), module, Plaits::MODEL_LIGHT + 7 * 2));
}
void appendContextMenu(Menu *menu) override {
Plaits *module = dynamic_cast(this->module);
struct PlaitsLowCpuItem : MenuItem {
Plaits *module;
void onAction(EventAction &e) override {
module->lowCpu ^= true;
}
};
struct PlaitsLPGItem : MenuItem {
Plaits *module;
void onAction(EventAction &e) override {
module->lpg ^= true;
}
};
struct PlaitsModelItem : MenuItem {
Plaits *module;
int model;
void onAction(EventAction &e) override {
module->patch.engine = model;
}
};
menu->addChild(MenuEntry::create());
PlaitsLowCpuItem *lowCpuItem = MenuItem::create("Low CPU", CHECKMARK(module->lowCpu));
lowCpuItem->module = module;
menu->addChild(lowCpuItem);
PlaitsLPGItem *lpgItem = MenuItem::create("Edit LPG response/decay", CHECKMARK(module->lpg));
lpgItem->module = module;
menu->addChild(lpgItem);
menu->addChild(new MenuEntry());
menu->addChild(MenuLabel::create("Models"));
for (int i = 0; i < 16; i++) {
PlaitsModelItem *modelItem = MenuItem::create(modelLabels[i], CHECKMARK(module->patch.engine == i));
modelItem->module = module;
modelItem->model = i;
menu->addChild(modelItem);
}
}
};
Model *modelPlaits = Model::create("Audible Instruments", "Plaits", "Macro Oscillator 2", OSCILLATOR_TAG, WAVESHAPER_TAG);