From a721c9083f9313743df9a903c80fb32d40c35e75 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Wed, 12 Sep 2018 15:53:36 -0400 Subject: [PATCH] Add Plaits --- CHANGELOG.md | 4 + Makefile | 17 +- eurorack | 2 +- res/Plaits.svg | 2968 ++++++++++++++++++++++++++++++++++++ src/AudibleInstruments.cpp | 1 + src/AudibleInstruments.hpp | 1 + src/Braids.cpp | 2 +- src/Plaits.cpp | 319 ++++ src/Tides.cpp | 2 +- 9 files changed, 3312 insertions(+), 4 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 res/Plaits.svg create mode 100644 src/Plaits.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4b896e5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ + +### 0.6.1 (2018-09-12) + +- Added Macro Oscillator 2 from Audible Instruments Preview diff --git a/Makefile b/Makefile index c998d77..cc7d1a3 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ SLUG = AudibleInstruments -VERSION = 0.6.0 +VERSION = 0.6.1 FLAGS += \ -DTEST \ @@ -7,14 +7,23 @@ FLAGS += \ -Wno-unused-local-typedefs SOURCES += $(wildcard src/*.cpp) + SOURCES += eurorack/stmlib/utils/random.cc SOURCES += eurorack/stmlib/dsp/atan.cc SOURCES += eurorack/stmlib/dsp/units.cc + SOURCES += eurorack/braids/macro_oscillator.cc SOURCES += eurorack/braids/analog_oscillator.cc SOURCES += eurorack/braids/digital_oscillator.cc SOURCES += eurorack/braids/quantizer.cc SOURCES += eurorack/braids/resources.cc + +SOURCES += $(wildcard eurorack/plaits/dsp/*.cc) +SOURCES += $(wildcard eurorack/plaits/dsp/engine/*.cc) +SOURCES += $(wildcard eurorack/plaits/dsp/speech/*.cc) +SOURCES += $(wildcard eurorack/plaits/dsp/physical_modelling/*.cc) +SOURCES += eurorack/plaits/resources.cc + SOURCES += eurorack/clouds/dsp/correlator.cc SOURCES += eurorack/clouds/dsp/granular_processor.cc SOURCES += eurorack/clouds/dsp/mu_law.cc @@ -22,6 +31,7 @@ SOURCES += eurorack/clouds/dsp/pvoc/frame_transformation.cc SOURCES += eurorack/clouds/dsp/pvoc/phase_vocoder.cc SOURCES += eurorack/clouds/dsp/pvoc/stft.cc SOURCES += eurorack/clouds/resources.cc + SOURCES += eurorack/elements/dsp/exciter.cc SOURCES += eurorack/elements/dsp/ominous_voice.cc SOURCES += eurorack/elements/dsp/resonator.cc @@ -31,22 +41,27 @@ SOURCES += eurorack/elements/dsp/part.cc SOURCES += eurorack/elements/dsp/string.cc SOURCES += eurorack/elements/dsp/voice.cc SOURCES += eurorack/elements/resources.cc + SOURCES += eurorack/rings/dsp/fm_voice.cc SOURCES += eurorack/rings/dsp/part.cc SOURCES += eurorack/rings/dsp/string_synth_part.cc SOURCES += eurorack/rings/dsp/string.cc SOURCES += eurorack/rings/dsp/resonator.cc SOURCES += eurorack/rings/resources.cc + SOURCES += eurorack/tides/generator.cc SOURCES += eurorack/tides/resources.cc + SOURCES += eurorack/warps/dsp/modulator.cc SOURCES += eurorack/warps/dsp/oscillator.cc SOURCES += eurorack/warps/dsp/vocoder.cc SOURCES += eurorack/warps/dsp/filter_bank.cc SOURCES += eurorack/warps/resources.cc + SOURCES += eurorack/frames/keyframer.cc SOURCES += eurorack/frames/resources.cc SOURCES += eurorack/frames/poly_lfo.cc + SOURCES += eurorack/peaks/processors.cc SOURCES += eurorack/peaks/resources.cc SOURCES += eurorack/peaks/drums/bass_drum.cc diff --git a/eurorack b/eurorack index 916d962..0bcabd2 160000 --- a/eurorack +++ b/eurorack @@ -1 +1 @@ -Subproject commit 916d9620b538e004c8d1480ce378152805979eba +Subproject commit 0bcabd2baa131576c38780972aee0ef1b30163f8 diff --git a/res/Plaits.svg b/res/Plaits.svg new file mode 100644 index 0000000..dabcb9d --- /dev/null +++ b/res/Plaits.svg @@ -0,0 +1,2968 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/AudibleInstruments.cpp b/src/AudibleInstruments.cpp index 57ba4e7..fe068f4 100644 --- a/src/AudibleInstruments.cpp +++ b/src/AudibleInstruments.cpp @@ -9,6 +9,7 @@ void init(rack::Plugin *p) { p->version = TOSTRING(VERSION); p->addModel(modelBraids); + p->addModel(modelPlaits); p->addModel(modelElements); p->addModel(modelTides); p->addModel(modelClouds); diff --git a/src/AudibleInstruments.hpp b/src/AudibleInstruments.hpp index 2105786..3568ad2 100644 --- a/src/AudibleInstruments.hpp +++ b/src/AudibleInstruments.hpp @@ -7,6 +7,7 @@ using namespace rack; extern Plugin *plugin; extern Model *modelBraids; +extern Model *modelPlaits; extern Model *modelElements; extern Model *modelTides; extern Model *modelClouds; diff --git a/src/Braids.cpp b/src/Braids.cpp index ab79e24..e45bbc6 100644 --- a/src/Braids.cpp +++ b/src/Braids.cpp @@ -129,7 +129,7 @@ void Braids::step() { if (!settings.meta_modulation) pitchV += fm; if (lowCpu) - pitchV += log2f(96000.0 / engineGetSampleRate()); + pitchV += log2f(96000.f * engineGetSampleTime()); int32_t pitch = (pitchV * 12.0 + 60) * 128; pitch += jitter_source.Render(settings.vco_drift); pitch = clamp(pitch, 0, 16383); diff --git a/src/Plaits.cpp b/src/Plaits.cpp new file mode 100644 index 0000000..39c584b --- /dev/null +++ b/src/Plaits.cpp @@ -0,0 +1,319 @@ +#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(); + 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); + diff --git a/src/Tides.cpp b/src/Tides.cpp index 20b9610..d3dab7d 100644 --- a/src/Tides.cpp +++ b/src/Tides.cpp @@ -145,7 +145,7 @@ void Tides::step() { // Sync // Slight deviation from spec here. // Instead of toggling sync by holding the range button, just enable it if the clock port is plugged in. - generator.set_sync(inputs[CLOCK_INPUT].active); + generator.set_sync(inputs[CLOCK_INPUT].active && !sheep); // Generator generator.Process(sheep);