From f95bb66c6c017a74c827083d229545a3d1298cb1 Mon Sep 17 00:00:00 2001
From: hemmer <915048+hemmer@users.noreply.github.com>
Date: Thu, 4 Apr 2024 20:42:58 +0100
Subject: [PATCH] Prepare v2.6.0 for library
---
.github/workflows/build-plugin.yml | 84 -
CHANGELOG.md | 6 +-
plugin.json | 36 -
res/panels/EvenVCObeta.svg | 1850 --------
res/panels/MidiThing.svg | 6828 ----------------------------
res/panels/PonyVCF.svg | 1147 -----
src/EvenVCO2.cpp | 331 --
src/MidiThing.cpp | 806 ----
src/PonyVCF.cpp | 224 -
src/plugin.cpp | 3 -
src/plugin.hpp | 3 -
11 files changed, 3 insertions(+), 11315 deletions(-)
delete mode 100644 .github/workflows/build-plugin.yml
delete mode 100644 res/panels/EvenVCObeta.svg
delete mode 100644 res/panels/MidiThing.svg
delete mode 100644 res/panels/PonyVCF.svg
delete mode 100644 src/EvenVCO2.cpp
delete mode 100644 src/MidiThing.cpp
delete mode 100644 src/PonyVCF.cpp
diff --git a/.github/workflows/build-plugin.yml b/.github/workflows/build-plugin.yml
deleted file mode 100644
index ec63601..0000000
--- a/.github/workflows/build-plugin.yml
+++ /dev/null
@@ -1,84 +0,0 @@
-name: Build VCV Rack Plugin
-on: [push, pull_request]
-
-env:
- rack-sdk-version: 2.4.1
- rack-plugin-toolchain-dir: /home/build/rack-plugin-toolchain
-
-defaults:
- run:
- shell: bash
-
-jobs:
- build:
- name: ${{ matrix.platform }}
- runs-on: ubuntu-latest
- container:
- image: ghcr.io/qno/rack-plugin-toolchain-win-linux
- options: --user root
- strategy:
- fail-fast: false
- matrix:
- platform: [win-x64, lin-x64]
- steps:
- - uses: actions/checkout@v3
- with:
- submodules: recursive
- - name: Build plugin
- run: |
- export PLUGIN_DIR=$GITHUB_WORKSPACE
- pushd ${{ env.rack-plugin-toolchain-dir }}
- make plugin-build-${{ matrix.platform }}
- - name: Upload artifact
- uses: actions/upload-artifact@v3
- with:
- path: ${{ env.rack-plugin-toolchain-dir }}/plugin-build
- name: ${{ matrix.platform }}
-
- build-mac:
- name: mac
- runs-on: macos-12
- strategy:
- fail-fast: false
- matrix:
- platform: [x64, arm64]
- steps:
- - uses: actions/checkout@v3
- with:
- submodules: recursive
- - name: Get Rack-SDK
- run: |
- pushd $HOME
- curl -o Rack-SDK.zip https://vcvrack.com/downloads/Rack-SDK-${{ env.rack-sdk-version }}-mac-${{ matrix.platform }}.zip
- unzip Rack-SDK.zip
- - name: Build plugin
- run: |
- CROSS_COMPILE_TARGET_x64=x86_64-apple-darwin
- CROSS_COMPILE_TARGET_arm64=arm64-apple-darwin
- export RACK_DIR=$HOME/Rack-SDK
- export CROSS_COMPILE=$CROSS_COMPILE_TARGET_${{ matrix.platform }}
- make dep
- make dist
- echo "Plugin architecture '$(lipo -archs plugin.dylib)'"
- - name: Upload artifact
- uses: actions/upload-artifact@v3
- with:
- path: dist/*.vcvplugin
- name: mac-${{ matrix.platform }}
-
- publish:
- name: Publish plugin
- runs-on: ubuntu-latest
- needs: [build, build-mac]
- steps:
- - uses: actions/download-artifact@v3
- with:
- path: _artifacts
- - uses: "marvinpinto/action-automatic-releases@latest"
- with:
- repo_token: "${{ secrets.GITHUB_TOKEN }}"
- automatic_release_tag: "latest"
- prerelease: true
- title: "Development Build"
- files: |
- _artifacts/**/*.vcvplugin
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1eaeea3..58da59d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,11 +1,11 @@
# Change Log
-## v2.6.0 (in progress)
- * Midi Thing 2
- * Initial release
+## v2.6.0
* Octaves
* Initial release
+ * Misc
+ * Better default values for ADSR and Burst
## v2.5.0
diff --git a/plugin.json b/plugin.json
index 6ecf88f..3befb93 100644
--- a/plugin.json
+++ b/plugin.json
@@ -23,18 +23,6 @@
"Polyphonic"
]
},
- {
- "slug": "EvenVCO2",
- "name": "Even VCO (beta)",
- "description": "Oscillator including even-harmonic waveform",
- "manualUrl": "https://www.befaco.org/even-vco/",
- "modularGridUrl": "https://www.modulargrid.net/e/befaco-even-vco-",
- "tags": [
- "VCO",
- "Hardware clone",
- "Polyphonic"
- ]
- },
{
"slug": "Rampage",
"name": "Rampage",
@@ -308,18 +296,6 @@
"Hardware clone"
]
},
- {
- "slug": "MidiThingV2",
- "name": "MIDI Thing V2",
- "description": "Hardware MIDI Thing v2 is a flexible MIDI to CV converter, this module acts as a bridge from VCV",
- "manualUrl": "https://github.com/VCVRack/Befaco/blob/v2/docs/MIDIThingV2.md",
- "modularGridUrl": "https://www.modulargrid.net/e/befaco-midi-thing-v2",
- "tags": [
- "External",
- "MIDI",
- "Hardware clone"
- ]
- },
{
"slug": "Voltio",
"name": "Voltio",
@@ -342,18 +318,6 @@
"Hardware clone",
"VCO"
]
- },
- {
- "slug": "PonyVCF",
- "name": "PonyVCF",
- "description": "Space-conscious lowpass filter and volume processor.",
- "manualUrl": "https://www.befaco.org/pony-vcf/",
- "modularGridUrl": "https://www.modulargrid.net/e/befaco-pony-vcf-",
- "tags": [
- "Hardware clone",
- "Mixer",
- "Filter"
- ]
}
]
}
\ No newline at end of file
diff --git a/res/panels/EvenVCObeta.svg b/res/panels/EvenVCObeta.svg
deleted file mode 100644
index dd1e1e5..0000000
--- a/res/panels/EvenVCObeta.svg
+++ /dev/null
@@ -1,1850 +0,0 @@
-
-
-
-
diff --git a/res/panels/MidiThing.svg b/res/panels/MidiThing.svg
deleted file mode 100644
index c6d658d..0000000
--- a/res/panels/MidiThing.svg
+++ /dev/null
@@ -1,6828 +0,0 @@
-
-
diff --git a/res/panels/PonyVCF.svg b/res/panels/PonyVCF.svg
deleted file mode 100644
index 00edd01..0000000
--- a/res/panels/PonyVCF.svg
+++ /dev/null
@@ -1,1147 +0,0 @@
-
-
-
-
diff --git a/src/EvenVCO2.cpp b/src/EvenVCO2.cpp
deleted file mode 100644
index 7886ff6..0000000
--- a/src/EvenVCO2.cpp
+++ /dev/null
@@ -1,331 +0,0 @@
-#include "plugin.hpp"
-#include "ChowDSP.hpp"
-
-using simd::float_4;
-
-struct EvenVCO2 : Module {
- enum ParamIds {
- OCTAVE_PARAM,
- TUNE_PARAM,
- PWM_PARAM,
- NUM_PARAMS
- };
- enum InputIds {
- PITCH1_INPUT,
- PITCH2_INPUT,
- FM_INPUT,
- SYNC_INPUT,
- PWM_INPUT,
- NUM_INPUTS
- };
- enum OutputIds {
- TRI_OUTPUT,
- SINE_OUTPUT,
- EVEN_OUTPUT,
- SAW_OUTPUT,
- SQUARE_OUTPUT,
- NUM_OUTPUTS
- };
-
-
- float_4 phase[4] = {};
- dsp::TSchmittTrigger syncTrigger[4];
- bool removePulseDC = true;
- bool limitPW = true;
-
- EvenVCO2() {
- config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS);
- configParam(OCTAVE_PARAM, -5.0, 4.0, 0.0, "Octave", "'", 0.5);
- getParamQuantity(OCTAVE_PARAM)->snapEnabled = true;
- configParam(TUNE_PARAM, -7.0, 7.0, 0.0, "Tune", " semitones");
- configParam(PWM_PARAM, -1.0, 1.0, 0.0, "Pulse width");
-
- configInput(PITCH1_INPUT, "Pitch 1");
- configInput(PITCH2_INPUT, "Pitch 2");
- configInput(FM_INPUT, "FM");
- configInput(SYNC_INPUT, "Sync");
- configInput(PWM_INPUT, "Pulse Width Modulation");
-
- configOutput(TRI_OUTPUT, "Triangle");
- configOutput(SINE_OUTPUT, "Sine");
- configOutput(EVEN_OUTPUT, "Even");
- configOutput(SAW_OUTPUT, "Sawtooth");
- configOutput(SQUARE_OUTPUT, "Square");
-
- // calculate up/downsampling rates
- onSampleRateChange();
- }
-
- void onSampleRateChange() override {
- float sampleRate = APP->engine->getSampleRate();
- for (int i = 0; i < NUM_OUTPUTS; ++i) {
- for (int c = 0; c < 4; c++) {
- oversampler[i][c].setOversamplingIndex(oversamplingIndex);
- oversampler[i][c].reset(sampleRate);
- }
- }
-
- const float lowFreqRegime = oversampler[0][0].getOversamplingRatio() * 1e-3 * sampleRate;
- DEBUG("Low freq regime: %g", lowFreqRegime);
- }
-
- float_4 aliasSuppressedTri(float_4* phases) {
- float_4 triBuffer[3];
- for (int i = 0; i < 3; ++i) {
- float_4 p = 2 * phases[i] - 1.0; // range -1.0 to +1.0
- float_4 s = 0.5 - simd::abs(p); // eq 30
- triBuffer[i] = (s * s * s - 0.75 * s) / 3.0; // eq 29
- }
- return (triBuffer[0] - 2.0 * triBuffer[1] + triBuffer[2]);
- }
-
- float_4 aliasSuppressedSaw(float_4* phases) {
- float_4 sawBuffer[3];
- for (int i = 0; i < 3; ++i) {
- float_4 p = 2 * phases[i] - 1.0; // range -1 to +1
- sawBuffer[i] = (p * p * p - p) / 6.0; // eq 11
- }
-
- return (sawBuffer[0] - 2.0 * sawBuffer[1] + sawBuffer[2]);
- }
-
- float_4 aliasSuppressedDoubleSaw(float_4* phases) {
- float_4 sawBuffer[3];
- for (int i = 0; i < 3; ++i) {
- float_4 p = 4.0 * simd::ifelse(phases[i] < 0.5, phases[i], phases[i] - 0.5) - 1.0;
- sawBuffer[i] = (p * p * p - p) / 24.0; // eq 11 (modified for doubled freq)
- }
-
- return (sawBuffer[0] - 2.0 * sawBuffer[1] + sawBuffer[2]);
- }
-
- float_4 aliasSuppressedOffsetSaw(float_4* phases, float_4 pw) {
- float_4 sawOffsetBuff[3];
-
- for (int i = 0; i < 3; ++i) {
- float_4 p = 2 * phases[i] - 1.0; // range -1 to +1
- float_4 pwp = p + 2 * pw; // phase after pw (pw in [0, 1])
- pwp += simd::ifelse(pwp > 1, -2, 0); // modulo on [-1, +1]
- sawOffsetBuff[i] = (pwp * pwp * pwp - pwp) / 6.0; // eq 11
- }
- return (sawOffsetBuff[0] - 2.0 * sawOffsetBuff[1] + sawOffsetBuff[2]);
- }
-
- chowdsp::VariableOversampling<6, float_4> oversampler[NUM_OUTPUTS][4]; // uses a 2*6=12th order Butterworth filter
- int oversamplingIndex = 1; // default is 2^oversamplingIndex == x2 oversampling
-
- void process(const ProcessArgs& args) override {
-
- // pitch inputs determine number of polyphony engines
- const int channels = std::max({1, inputs[PITCH1_INPUT].getChannels(), inputs[PITCH2_INPUT].getChannels()});
-
- const float pitchKnobs = 1.f + std::round(params[OCTAVE_PARAM].getValue()) + params[TUNE_PARAM].getValue() / 12.f;
- const int oversamplingRatio = oversampler[0][0].getOversamplingRatio();
-
- for (int c = 0; c < channels; c += 4) {
- float_4 pw = simd::clamp(params[PWM_PARAM].getValue() + inputs[PWM_INPUT].getPolyVoltageSimd(c) / 5.f, -1.f, 1.f);
- if (limitPW) {
- pw = simd::rescale(pw, -1, +1, 0.05f, 0.95f);
- }
- else {
- pw = simd::rescale(pw, -1.f, +1.f, 0.f, 1.f);
- }
-
- const float_4 fmVoltage = inputs[FM_INPUT].getPolyVoltageSimd(c) * 0.25f;
- const float_4 pitch = inputs[PITCH1_INPUT].getPolyVoltageSimd(c) + inputs[PITCH2_INPUT].getPolyVoltageSimd(c);
- const float_4 freq = dsp::FREQ_C4 * simd::pow(2.f, pitchKnobs + pitch + fmVoltage);
- const float_4 deltaBasePhase = simd::clamp(freq * args.sampleTime / oversamplingRatio, 1e-6, 0.5f);
- // floating point arithmetic doesn't work well at low frequencies, specifically because the finite difference denominator
- // becomes tiny - we check for that scenario and use naive / 1st order waveforms in that frequency regime (as aliasing isn't
- // a problem there). With no oversampling, at 44100Hz, the threshold frequency is 44.1Hz.
- const float_4 lowFreqRegime = simd::abs(deltaBasePhase) < 1e-3;
- // 1 / denominator for the second-order FD
- const float_4 denominatorInv = 0.25 / (deltaBasePhase * deltaBasePhase);
-
- // pulsewave waveform doesn't have DC even for non 50% duty cycles, but Befaco team would like the option
- // for it to be added back in for hardware compatibility reasons
- const float_4 pulseDCOffset = (!removePulseDC) * 2.f * (0.5f - pw);
-
- // hard sync
- const float_4 syncMask = syncTrigger[c / 4].process(inputs[SYNC_INPUT].getPolyVoltageSimd(c));
- phase[c / 4] = simd::ifelse(syncMask, 0.5f, phase[c / 4]);
-
- float_4* osBufferTri = oversampler[TRI_OUTPUT][c / 4].getOSBuffer();
- float_4* osBufferSaw = oversampler[SAW_OUTPUT][c / 4].getOSBuffer();
- float_4* osBufferSin = oversampler[SINE_OUTPUT][c / 4].getOSBuffer();
- float_4* osBufferSquare = oversampler[SQUARE_OUTPUT][c / 4].getOSBuffer();
- float_4* osBufferEven = oversampler[EVEN_OUTPUT][c / 4].getOSBuffer();
- for (int i = 0; i < oversamplingRatio; ++i) {
-
- phase[c / 4] += deltaBasePhase;
- // ensure within [0, 1]
- phase[c / 4] -= simd::floor(phase[c / 4]);
-
- float_4 phases[3]; // phase as extrapolated to the current and two previous samples
-
- phases[0] = phase[c / 4] - 2 * deltaBasePhase + simd::ifelse(phase[c / 4] < 2 * deltaBasePhase, 1.f, 0.f);
- phases[1] = phase[c / 4] - deltaBasePhase + simd::ifelse(phase[c / 4] < deltaBasePhase, 1.f, 0.f);
- phases[2] = phase[c / 4];
-
- if (outputs[SINE_OUTPUT].isConnected() || outputs[EVEN_OUTPUT].isConnected()) {
- // sin doesn't need PDW
- osBufferSin[i] = -simd::cos(2.0 * M_PI * phase[c / 4]);
- }
-
- if (outputs[TRI_OUTPUT].isConnected()) {
- const float_4 dpwOrder1 = 1.0 - 2.0 * simd::abs(2 * phase[c / 4] - 1.0);
- const float_4 dpwOrder3 = aliasSuppressedTri(phases) * denominatorInv;
-
- osBufferTri[i] = simd::ifelse(lowFreqRegime, dpwOrder1, dpwOrder3);
- }
-
- if (outputs[SAW_OUTPUT].isConnected()) {
- const float_4 dpwOrder1 = 2 * phase[c / 4] - 1.0;
- const float_4 dpwOrder3 = aliasSuppressedSaw(phases) * denominatorInv;
-
- osBufferSaw[i] = simd::ifelse(lowFreqRegime, dpwOrder1, dpwOrder3);
- }
-
- if (outputs[SQUARE_OUTPUT].isConnected()) {
-
- float_4 dpwOrder1 = simd::ifelse(phase[c / 4] < pw, -1.0, +1.0);
- dpwOrder1 -= removePulseDC ? 2.f * (0.5f - pw) : 0.f;
-
- float_4 saw = aliasSuppressedSaw(phases);
- float_4 sawOffset = aliasSuppressedOffsetSaw(phases, pw);
- float_4 dpwOrder3 = (saw - sawOffset) * denominatorInv + pulseDCOffset;
-
- osBufferSquare[i] = simd::ifelse(lowFreqRegime, dpwOrder1, dpwOrder3);
- }
-
- if (outputs[EVEN_OUTPUT].isConnected()) {
-
- float_4 dpwOrder1 = 4.0 * simd::ifelse(phase[c / 4] < 0.5, phase[c / 4], phase[c / 4] - 0.5) - 1.0;
- float_4 dpwOrder3 = aliasSuppressedDoubleSaw(phases) * denominatorInv;
- float_4 doubleSaw = simd::ifelse(lowFreqRegime, dpwOrder1, dpwOrder3);
- osBufferEven[i] = 0.55 * (doubleSaw + 1.27 * osBufferSin[i]);
- }
-
-
- } // end of oversampling loop
-
- // downsample (if required)
- if (outputs[SINE_OUTPUT].isConnected()) {
- const float_4 outSin = (oversamplingRatio > 1) ? oversampler[SINE_OUTPUT][c / 4].downsample() : osBufferSin[0];
- outputs[SINE_OUTPUT].setVoltageSimd(5.f * outSin, c);
- }
-
- if (outputs[TRI_OUTPUT].isConnected()) {
- const float_4 outTri = (oversamplingRatio > 1) ? oversampler[TRI_OUTPUT][c / 4].downsample() : osBufferTri[0];
- outputs[TRI_OUTPUT].setVoltageSimd(5.f * outTri, c);
- }
-
- if (outputs[SAW_OUTPUT].isConnected()) {
- const float_4 outSaw = (oversamplingRatio > 1) ? oversampler[SAW_OUTPUT][c / 4].downsample() : osBufferSaw[0];
- outputs[SAW_OUTPUT].setVoltageSimd(5.f * outSaw, c);
- }
-
- if (outputs[SQUARE_OUTPUT].isConnected()) {
- const float_4 outSquare = (oversamplingRatio > 1) ? oversampler[SQUARE_OUTPUT][c / 4].downsample() : osBufferSquare[0];
- outputs[SQUARE_OUTPUT].setVoltageSimd(5.f * outSquare, c);
- }
-
- if (outputs[EVEN_OUTPUT].isConnected()) {
- const float_4 outEven = (oversamplingRatio > 1) ? oversampler[EVEN_OUTPUT][c / 4].downsample() : osBufferEven[0];
- outputs[EVEN_OUTPUT].setVoltageSimd(5.f * outEven, c);
- }
-
- } // end of channels loop
-
- // Outputs
- outputs[TRI_OUTPUT].setChannels(channels);
- outputs[SINE_OUTPUT].setChannels(channels);
- outputs[EVEN_OUTPUT].setChannels(channels);
- outputs[SAW_OUTPUT].setChannels(channels);
- outputs[SQUARE_OUTPUT].setChannels(channels);
- }
-
-
- json_t* dataToJson() override {
- json_t* rootJ = json_object();
- json_object_set_new(rootJ, "removePulseDC", json_boolean(removePulseDC));
- json_object_set_new(rootJ, "limitPW", json_boolean(limitPW));
- json_object_set_new(rootJ, "oversamplingIndex", json_integer(oversampler[0][0].getOversamplingIndex()));
- return rootJ;
- }
-
- void dataFromJson(json_t* rootJ) override {
- json_t* pulseDCJ = json_object_get(rootJ, "removePulseDC");
- if (pulseDCJ) {
- removePulseDC = json_boolean_value(pulseDCJ);
- }
-
- json_t* limitPWJ = json_object_get(rootJ, "limitPW");
- if (limitPWJ) {
- limitPW = json_boolean_value(limitPWJ);
- }
-
- json_t* oversamplingIndexJ = json_object_get(rootJ, "oversamplingIndex");
- if (oversamplingIndexJ) {
- oversamplingIndex = json_integer_value(oversamplingIndexJ);
- onSampleRateChange();
- }
- }
-};
-
-
-struct EvenVCO2Widget : ModuleWidget {
- EvenVCO2Widget(EvenVCO2* module) {
- setModule(module);
- setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/panels/EvenVCObeta.svg")));
-
- addChild(createWidget(Vec(15, 0)));
- addChild(createWidget(Vec(15, 365)));
- addChild(createWidget(Vec(15 * 6, 0)));
- addChild(createWidget(Vec(15 * 6, 365)));
-
- addParam(createParam(Vec(22, 32), module, EvenVCO2::OCTAVE_PARAM));
- addParam(createParam(Vec(73, 131), module, EvenVCO2::TUNE_PARAM));
- addParam(createParam(Vec(16, 230), module, EvenVCO2::PWM_PARAM));
-
- addInput(createInput(Vec(8, 120), module, EvenVCO2::PITCH1_INPUT));
- addInput(createInput(Vec(19, 157), module, EvenVCO2::PITCH2_INPUT));
- addInput(createInput(Vec(48, 183), module, EvenVCO2::FM_INPUT));
- addInput(createInput(Vec(86, 189), module, EvenVCO2::SYNC_INPUT));
-
- addInput(createInput(Vec(72, 236), module, EvenVCO2::PWM_INPUT));
-
- addOutput(createOutput(Vec(10, 283), module, EvenVCO2::TRI_OUTPUT));
- addOutput(createOutput(Vec(87, 283), module, EvenVCO2::SINE_OUTPUT));
- addOutput(createOutput(Vec(48, 306), module, EvenVCO2::EVEN_OUTPUT));
- addOutput(createOutput(Vec(10, 327), module, EvenVCO2::SAW_OUTPUT));
- addOutput(createOutput(Vec(87, 327), module, EvenVCO2::SQUARE_OUTPUT));
- }
-
- void appendContextMenu(Menu* menu) override {
- EvenVCO2* module = dynamic_cast(this->module);
- assert(module);
-
- menu->addChild(new MenuSeparator());
- menu->addChild(createSubmenuItem("Hardware compatibility", "",
- [ = ](Menu * menu) {
- menu->addChild(createBoolPtrMenuItem("Remove DC from pulse", "", &module->removePulseDC));
- menu->addChild(createBoolPtrMenuItem("Limit pulsewidth (5\%-95\%)", "", &module->limitPW));
- }
- ));
-
- menu->addChild(createIndexSubmenuItem("Oversampling",
- {"Off", "x2", "x4", "x8"},
- [ = ]() {
- return module->oversamplingIndex;
- },
- [ = ](int mode) {
- module->oversamplingIndex = mode;
- module->onSampleRateChange();
- }
- ));
- }
-};
-
-
-Model* modelEvenVCO2 = createModel("EvenVCO2");
diff --git a/src/MidiThing.cpp b/src/MidiThing.cpp
deleted file mode 100644
index 23717ee..0000000
--- a/src/MidiThing.cpp
+++ /dev/null
@@ -1,806 +0,0 @@
-#include "plugin.hpp"
-
-
-/*! \brief Decode System Exclusive messages.
- SysEx messages are encoded to guarantee transmission of data bytes higher than
- 127 without breaking the MIDI protocol. Use this static method to reassemble
- your received message.
- \param inSysEx The SysEx data received from MIDI in.
- \param outData The output buffer where to store the decrypted message.
- \param inLength The length of the input buffer.
- \param inFlipHeaderBits True for Korg and other who store MSB in reverse order
- \return The length of the output buffer.
- @see encodeSysEx @see getSysExArrayLength
- Code inspired from Ruin & Wesen's SysEx encoder/decoder - http://ruinwesen.com
- */
-unsigned decodeSysEx(const uint8_t* inSysEx,
- uint8_t* outData,
- unsigned inLength,
- bool inFlipHeaderBits) {
- unsigned count = 0;
- uint8_t msbStorage = 0;
- uint8_t byteIndex = 0;
-
- for (unsigned i = 0; i < inLength; ++i) {
- if ((i % 8) == 0) {
- msbStorage = inSysEx[i];
- byteIndex = 6;
- }
- else {
- const uint8_t body = inSysEx[i];
- const uint8_t shift = inFlipHeaderBits ? 6 - byteIndex : byteIndex;
- const uint8_t msb = uint8_t(((msbStorage >> shift) & 1) << 7);
- byteIndex--;
- outData[count++] = msb | body;
- }
- }
- return count;
-}
-
-struct RoundRobinProcessor {
- // if a channel (0 - 11) should be updated, return it's index, otherwise return -1
- int process(float sampleTime, float period, int numActiveChannels) {
-
- if (numActiveChannels == 0 || period <= 0) {
- return -1;
- }
-
- time += sampleTime;
-
- if (time > period) {
- time -= period;
-
- // special case: when there's only one channel, the below logic (which looks for when active channel changes)
- // wont fire. as we've completed a period, return an "update channel 0" value
- if (numActiveChannels == 1) {
- return 0;
- }
- }
-
- int currentActiveChannel = numActiveChannels * time / period;
-
- if (currentActiveChannel != previousActiveChannel) {
- previousActiveChannel = currentActiveChannel;
- return currentActiveChannel;
- }
-
- // if we've got this far, no updates needed (-1)
- return -1;
- }
-private:
- float time = 0.f;
- int previousActiveChannel = -1;
-};
-
-
-struct MidiThing : Module {
- enum ParamId {
- REFRESH_PARAM,
- PARAMS_LEN
- };
- enum InputId {
- A1_INPUT,
- B1_INPUT,
- C1_INPUT,
- A2_INPUT,
- B2_INPUT,
- C2_INPUT,
- A3_INPUT,
- B3_INPUT,
- C3_INPUT,
- A4_INPUT,
- B4_INPUT,
- C4_INPUT,
- INPUTS_LEN
- };
- enum OutputId {
- OUTPUTS_LEN
- };
- enum LightId {
- LIGHTS_LEN
- };
- /// Port mode
- enum PORTMODE_t {
- NOPORTMODE = 0,
- MODE10V,
- MODEPN5V,
- MODENEG10V,
- MODE8V,
- MODE5V,
-
- LASTPORTMODE
- };
-
- const char* cfgPortModeNames[7] = {
- "No Mode",
- "0/10v",
- "-5/5v",
- "-10/0v",
- "0/8v",
- "0/5v",
- ""
- };
-
- const std::vector updateRates = {250., 500., 1000., 2000., 4000., 8000.};
- const std::vector updateRateNames = {"250 Hz (fewest active channels, slowest, lowest-cpu)", "500 Hz", "1 kHz", "2 kHz", "4 kHz",
- "8 kHz (most active channels, fast, highest-cpu)"
- };
- int updateRateIdx = 2;
-
- // use Pre-def 4 for bridge mode
- const static int VCV_BRIDGE_PREDEF = 4;
-
- midi::Output midiOut;
- RoundRobinProcessor roundRobinProcessor;
-
- MidiThing() {
- config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN);
- configButton(REFRESH_PARAM, "");
-
- for (int i = 0; i < NUM_INPUTS; ++i) {
- portModes[i] = MODE10V;
- configInput(A1_INPUT + i, string::f("Port %d", i + 1));
- }
- }
-
- void onReset() override {
- midiOut.reset();
-
- }
-
- void requestAllChannelsParamsOverSysex() {
- for (int row = 0; row < 4; ++row) {
- for (int col = 0; col < 3; ++col) {
- const int PORT_CONFIG = 2;
- requestParamOverSysex(row, col, PORT_CONFIG);
- }
- }
- }
-
- // request that MidiThing loads a pre-defined template, 1-4
- void setPredef(uint8_t predef) {
- predef = clamp(predef, 1, 4);
- midi::Message msg;
- msg.bytes.resize(8);
- // Midi spec is zeroo indexed
- uint8_t predefToSend = predef - 1;
- msg.bytes = {0xF0, 0x7D, 0x17, 0x00, 0x00, 0x02, 0x00, predefToSend, 0xF7};
- midiOut.setChannel(0);
- midiOut.sendMessage(msg);
- // DEBUG("Predef %d msg request sent: %s", predef, msg.toString().c_str());
- }
-
- void setMidiMergeViaSysEx(bool mergeOn) {
- midi::Message msg;
- msg.bytes.resize(8);
-
- msg.bytes = {0xF0, 0x7D, 0x19, 0x00, 0x05, 0x02, 0x00, (uint8_t) mergeOn, 0xF7};
- midiOut.setChannel(0);
- midiOut.sendMessage(msg);
- // DEBUG("Predef %d msg request sent: %s", mergeOn, msg.toString().c_str());
- }
-
-
- void setVoltageModeOnHardware(uint8_t row, uint8_t col, PORTMODE_t outputMode_) {
- uint8_t port = 3 * row + col;
- portModes[port] = outputMode_;
-
- midi::Message msg;
- msg.bytes.resize(8);
- // F0 7D 17 2n 02 02 00 0m F7
- // Where n = 0 based port number
- // and m is the volt output mode to select from:
- msg.bytes = {0xF0, 0x7D, 0x17, static_cast(32 + port), 0x02, 0x02, 0x00, (uint8_t) portModes[port], 0xF7};
- midiOut.sendMessage(msg);
- // DEBUG("Voltage mode msg sent: port %d (%d), mode %d", port, static_cast(32 + port), portModes[port]);
- }
-
- void setVoltageModeOnHardware(uint8_t row, uint8_t col) {
- setVoltageModeOnHardware(row, col, portModes[3 * row + col]);
- }
-
- void syncVcvStateToHardware() {
- for (int row = 0; row < 4; ++row) {
- for (int col = 0; col < 3; ++col) {
- setVoltageModeOnHardware(row, col);
- }
- }
- }
-
-
- midi::InputQueue inputQueue;
- void requestParamOverSysex(uint8_t row, uint8_t col, uint8_t mode) {
-
- midi::Message msg;
- msg.bytes.resize(8);
- // F0 7D 17 00 01 03 00 nm pp F7
- uint8_t port = 3 * row + col;
- //Where n is:
- // 0 = Full configuration request. The module will send only pre def, port functions and modified parameters
- // 2 = Send Port configuration
- // 4 = Send MIDI Channel configuration
- // 6 = Send Voice Configuration
-
- uint8_t n = mode * 16;
- uint8_t m = port; // element number: 0-11 port number, 1-16 channel or voice number
- uint8_t pp = 2;
- msg.bytes = {0xF0, 0x7D, 0x17, 0x00, 0x01, 0x03, 0x00, static_cast(n + m), pp, 0xF7};
- midiOut.sendMessage(msg);
- // DEBUG("API request mode msg sent: port %d, pp %s", port, msg.toString().c_str());
- }
-
- int getVoltageMode(uint8_t row, uint8_t col) {
- // -1 because menu is zero indexed but enum is not
- int channel = clamp(3 * row + col, 0, NUM_INPUTS - 1);
- return portModes[channel] - 1;
- }
-
- const static int NUM_INPUTS = 12;
- bool isClipping[NUM_INPUTS] = {};
-
- bool checkIsVoltageWithinRange(uint8_t channel, float voltage) {
- const float tol = 0.001;
- switch (portModes[channel]) {
- case MODE10V: return 0 - tol < voltage && voltage < 10 + tol;
- case MODEPN5V: return -5 - tol < voltage && voltage < 5 + tol;
- case MODENEG10V: return -10 - tol < voltage && voltage < 0 + tol;
- case MODE8V: return 0 - tol < voltage && voltage < 8 + tol;
- case MODE5V: return 0 - tol < voltage && voltage < 5 + tol;
- default: return false;
- }
- }
-
- uint16_t rescaleVoltageForChannel(uint8_t channel, float voltage) {
- switch (portModes[channel]) {
- case MODE10V: return rescale(clamp(voltage, 0.f, 10.f), 0.f, +10.f, 0, 16383);
- case MODEPN5V: return rescale(clamp(voltage, -5.f, 5.f), -5.f, +5.f, 0, 16383);
- case MODENEG10V: return rescale(clamp(voltage, -10.f, 0.f), -10.f, +0.f, 0, 16383);
- case MODE8V: return rescale(clamp(voltage, 0.f, 8.f), 0.f, +8.f, 0, 16383);
- case MODE5V: return rescale(clamp(voltage, 0.f, 5.f), 0.f, +5.f, 0, 16383);
- default: return 0;
- }
- }
-
- // one way sync (VCV -> hardware) for now
- void doSync() {
- // switch to VCV template (predef 4)
- setPredef(4);
-
- // disable MIDI merge (otherwise large sample rates will not work)
- setMidiMergeViaSysEx(false);
-
- // send full VCV config
- syncVcvStateToHardware();
-
- // disabled for now, but this would request what state the hardware is in
- if (parseSysExMessagesFromHardware) {
- requestAllChannelsParamsOverSysex();
- }
- }
-
- // debug only
- bool parseSysExMessagesFromHardware = false;
- int numActiveChannels = 0;
- dsp::BooleanTrigger buttonTrigger;
- dsp::Timer rateLimiterTimer;
- PORTMODE_t portModes[NUM_INPUTS] = {};
- void process(const ProcessArgs& args) override {
-
- if (buttonTrigger.process(params[REFRESH_PARAM].getValue())) {
- doSync();
- }
-
- // disabled for now, but this is how VCV would read SysEx coming from the hardware (if requested above)
- if (parseSysExMessagesFromHardware) {
- midi::Message msg;
- uint8_t outData[32] = {};
- while (inputQueue.tryPop(&msg, args.frame)) {
- // DEBUG("msg (size: %d): %s", msg.getSize(), msg.toString().c_str());
-
- uint8_t outLen = decodeSysEx(&msg.bytes[0], outData, msg.bytes.size(), false);
- if (outLen > 3) {
-
- int channel = (outData[2] & 0x0f) >> 0;
-
- if (channel >= 0 && channel < NUM_INPUTS) {
- if (outData[outLen - 1] < LASTPORTMODE) {
- portModes[channel] = (PORTMODE_t) outData[outLen - 1];
- // DEBUG("Channel %d, %d: mode %d (%s)", outData[2], channel, portModes[channel], cfgPortModeNames[portModes[channel]]);
- }
- }
- }
- }
- }
-
- std::vector activeChannels;
- for (int c = 0; c < NUM_INPUTS; ++c) {
- if (inputs[A1_INPUT + c].isConnected()) {
- activeChannels.push_back(c);
- }
- }
- numActiveChannels = activeChannels.size();
- // we're done if no channels are active
- if (numActiveChannels == 0) {
- return;
- }
-
- //DEBUG("updateRateIdx: %d", updateRateIdx);
- const float updateRateHz = updateRates[updateRateIdx];
- //DEBUG("updateRateHz: %f", updateRateHz);
- const int maxCCMessagesPerSecondPerChannel = updateRateHz / numActiveChannels;
-
- // MIDI baud rate is 31250 b/s, or 3125 B/s.
- // CC messages are 3 bytes, so we can send a maximum of 1041 CC messages per second.
- // The refresh rate period (i.e. how often we can send X channels of data is:
- const float rateLimiterPeriod = 1.f / maxCCMessagesPerSecondPerChannel;
-
- // this returns -1 if no channel should be updated, or the index of the channel that should be updated
- // it distributes update times in a round robin fashion
- int channelIdxToUpdate = roundRobinProcessor.process(args.sampleTime, rateLimiterPeriod, numActiveChannels);
-
- if (channelIdxToUpdate >= 0 && channelIdxToUpdate < numActiveChannels) {
- int c = activeChannels[channelIdxToUpdate];
-
- const float channelVoltage = inputs[A1_INPUT + c].getVoltage();
- uint16_t pw = rescaleVoltageForChannel(c, channelVoltage);
- isClipping[c] = !checkIsVoltageWithinRange(c, channelVoltage);
- midi::Message m;
- m.setStatus(0xe);
- m.setNote(pw & 0x7f);
- m.setValue((pw >> 7) & 0x7f);
- m.setFrame(args.frame);
-
- midiOut.setChannel(c);
- midiOut.sendMessage(m);
- }
- }
-
-
- json_t* dataToJson() override {
- json_t* rootJ = json_object();
- json_object_set_new(rootJ, "midiOutput", midiOut.toJson());
- json_object_set_new(rootJ, "inputQueue", inputQueue.toJson());
- json_object_set_new(rootJ, "updateRateIdx", json_integer(updateRateIdx));
-
- for (int c = 0; c < NUM_INPUTS; ++c) {
- json_object_set_new(rootJ, string::f("portMode%d", c).c_str(), json_integer(portModes[c]));
- }
-
- return rootJ;
- }
-
- void dataFromJson(json_t* rootJ) override {
- json_t* midiOutputJ = json_object_get(rootJ, "midiOutput");
- if (midiOutputJ) {
- midiOut.fromJson(midiOutputJ);
- }
-
- json_t* midiInputQueueJ = json_object_get(rootJ, "inputQueue");
- if (midiInputQueueJ) {
- inputQueue.fromJson(midiInputQueueJ);
- }
-
- json_t* updateRateIdxJ = json_object_get(rootJ, "updateRateIdx");
- if (updateRateIdxJ) {
- updateRateIdx = json_integer_value(updateRateIdxJ);
- }
-
- for (int c = 0; c < NUM_INPUTS; ++c) {
- json_t* portModeJ = json_object_get(rootJ, string::f("portMode%d", c).c_str());
- if (portModeJ) {
- portModes[c] = (PORTMODE_t)json_integer_value(portModeJ);
- }
- }
-
- // requestAllChannelsParamsOverSysex();
- syncVcvStateToHardware();
- }
-};
-
-struct MidiThingPort : PJ301MPort {
- int row = 0, col = 0;
- MidiThing* module;
-
- void appendContextMenu(Menu* menu) override {
-
- menu->addChild(new MenuSeparator());
- std::string label = string::f("Voltage Mode Port %d", 3 * row + col + 1);
-
- menu->addChild(createIndexSubmenuItem(label,
- {"0 to 10v", "-5 to 5v", "-10 to 0v", "0 to 8v", "0 to 5v"},
- [ = ]() {
- return module->getVoltageMode(row, col);
- },
- [ = ](int modeIdx) {
- MidiThing::PORTMODE_t mode = (MidiThing::PORTMODE_t)(modeIdx + 1);
- module->setVoltageModeOnHardware(row, col, mode);
- }
- ));
-
- /*
- menu->addChild(createIndexSubmenuItem("Get Port Info",
- {"Full", "Port", "MIDI", "Voice"},
- [ = ]() {
- return -1;
- },
- [ = ](int mode) {
- module->requestParamOverSysex(row, col, 2 * mode);
- }
- ));
- */
- }
-};
-
-// dervied from https://github.com/countmodula/VCVRackPlugins/blob/v2.0.0/src/components/CountModulaLEDDisplay.hpp
-struct LEDDisplay : LightWidget {
- float fontSize = 9;
- Vec textPos = Vec(1, 13);
- int numChars = 7;
- int row = 0, col = 0;
- MidiThing* module;
-
- LEDDisplay() {
- box.size = mm2px(Vec(9.298, 5.116));
- }
-
- void setCentredPos(Vec pos) {
- box.pos.x = pos.x - box.size.x / 2;
- box.pos.y = pos.y - box.size.y / 2;
- }
-
- void drawBackground(const DrawArgs& args) override {
- // Background
- NVGcolor backgroundColor = nvgRGB(0x20, 0x20, 0x20);
- NVGcolor borderColor = nvgRGB(0x10, 0x10, 0x10);
- nvgBeginPath(args.vg);
- nvgRoundedRect(args.vg, 0.0, 0.0, box.size.x, box.size.y, 2.0);
- nvgFillColor(args.vg, backgroundColor);
- nvgFill(args.vg);
- nvgStrokeWidth(args.vg, 1.0);
- nvgStrokeColor(args.vg, borderColor);
- nvgStroke(args.vg);
- }
-
- void drawLight(const DrawArgs& args) override {
- // Background
- NVGcolor backgroundColor = nvgRGB(0x20, 0x20, 0x20);
- NVGcolor borderColor = nvgRGB(0x10, 0x10, 0x10);
- NVGcolor textColor = nvgRGB(0xff, 0x10, 0x10);
-
- nvgBeginPath(args.vg);
- nvgRoundedRect(args.vg, 0.0, 0.0, box.size.x, box.size.y, 2.0);
- nvgFillColor(args.vg, backgroundColor);
- nvgFill(args.vg);
- nvgStrokeWidth(args.vg, 1.0);
-
- if (module) {
- const bool isClipping = module->isClipping[col + row * 3];
- if (isClipping) {
- borderColor = nvgRGB(0xff, 0x20, 0x20);
- }
- }
-
- nvgStrokeColor(args.vg, borderColor);
- nvgStroke(args.vg);
-
- std::shared_ptr font = APP->window->loadFont(asset::plugin(pluginInstance, "res/fonts/miso.otf"));
-
- if (font && font->handle >= 0) {
-
- std::string text = "?-?v"; // fallback if module not yet defined
- if (module) {
- text = module->cfgPortModeNames[module->getVoltageMode(row, col) + 1];
- }
- char buffer[numChars + 1];
- int l = text.size();
- if (l > numChars)
- l = numChars;
-
- nvgGlobalTint(args.vg, color::WHITE);
-
- text.copy(buffer, l);
- buffer[l] = '\0';
-
- nvgFontSize(args.vg, fontSize);
- nvgFontFaceId(args.vg, font->handle);
- nvgFillColor(args.vg, textColor);
- nvgTextAlign(args.vg, NVG_ALIGN_CENTER | NVG_ALIGN_BOTTOM);
- NVGtextRow textRow;
- nvgTextBreakLines(args.vg, text.c_str(), NULL, box.size.x, &textRow, 1);
- nvgTextBox(args.vg, textPos.x, textPos.y, box.size.x, textRow.start, textRow.end);
- }
- }
-
- void onButton(const ButtonEvent& e) override {
- if (e.button == GLFW_MOUSE_BUTTON_RIGHT && e.action == GLFW_PRESS) {
- ui::Menu* menu = createMenu();
-
- menu->addChild(createMenuLabel(string::f("Voltage mode port %d:", col + 3 * row + 1)));
-
- const std::string labels[5] = {"0 to 10v", "-5 to 5v", "-10 to 0v", "0 to 8v", "0 to 5v"};
-
- for (int i = 0; i < 5; ++i) {
- menu->addChild(createCheckMenuItem(labels[i], "",
- [ = ]() {
- return module->getVoltageMode(row, col) == i;
- },
- [ = ]() {
- MidiThing::PORTMODE_t mode = (MidiThing::PORTMODE_t)(i + 1);
- module->setVoltageModeOnHardware(row, col, mode);
- }
- ));
- }
-
- e.consume(this);
- return;
- }
-
- LightWidget::onButton(e);
- }
-
-};
-
-
-struct MidiThingWidget : ModuleWidget {
-
- struct LedDisplayCenterChoiceEx : LedDisplayChoice {
- LedDisplayCenterChoiceEx() {
- box.size = mm2px(math::Vec(0, 8.0));
- color = nvgRGB(0xf0, 0xf0, 0xf0);
- bgColor = nvgRGBAf(0, 0, 0, 0);
- textOffset = math::Vec(0, 16);
- }
-
- void drawLayer(const DrawArgs& args, int layer) override {
- nvgScissor(args.vg, RECT_ARGS(args.clipBox));
- if (layer == 1) {
- if (bgColor.a > 0.0) {
- nvgBeginPath(args.vg);
- nvgRect(args.vg, 0, 0, box.size.x, box.size.y);
- nvgFillColor(args.vg, bgColor);
- nvgFill(args.vg);
- }
-
- std::shared_ptr font = APP->window->loadFont(asset::plugin(pluginInstance, "res/fonts/miso.otf"));
-
- if (font && font->handle >= 0 && !text.empty()) {
- nvgFillColor(args.vg, color);
- nvgFontFaceId(args.vg, font->handle);
- nvgTextLetterSpacing(args.vg, -0.6f);
- nvgFontSize(args.vg, 10);
- nvgTextAlign(args.vg, NVG_ALIGN_CENTER | NVG_ALIGN_BOTTOM);
- NVGtextRow textRow;
- nvgTextBreakLines(args.vg, text.c_str(), NULL, box.size.x, &textRow, 1);
- nvgTextBox(args.vg, textOffset.x, textOffset.y, box.size.x, textRow.start, textRow.end);
- }
- }
- nvgResetScissor(args.vg);
- }
- };
-
-
- struct MidiDriverItem : ui::MenuItem {
- midi::Port* port;
- int driverId;
- void onAction(const event::Action& e) override {
- port->setDriverId(driverId);
- }
- };
-
- struct MidiDriverChoice : LedDisplayCenterChoiceEx {
- midi::Port* port;
- void onAction(const event::Action& e) override {
- if (!port)
- return;
- createContextMenu();
- }
-
- virtual ui::Menu* createContextMenu() {
- ui::Menu* menu = createMenu();
- menu->addChild(createMenuLabel("MIDI driver"));
- for (int driverId : midi::getDriverIds()) {
- MidiDriverItem* item = new MidiDriverItem;
- item->port = port;
- item->driverId = driverId;
- item->text = midi::getDriver(driverId)->getName();
- item->rightText = CHECKMARK(item->driverId == port->driverId);
- menu->addChild(item);
- }
- return menu;
- }
-
- void step() override {
- text = port ? port->getDriver()->getName() : "";
- if (text.empty()) {
- text = "(No driver)";
- color.a = 0.5f;
- }
- else {
- color.a = 1.f;
- }
- }
- };
-
- struct MidiDeviceItem : ui::MenuItem {
- midi::Port* outPort, *inPort;
- int deviceId;
- void onAction(const event::Action& e) override {
- outPort->setDeviceId(deviceId);
- inPort->setDeviceId(deviceId);
- }
- };
-
- struct MidiDeviceChoice : LedDisplayCenterChoiceEx {
- midi::Port* outPort, *inPort;
- void onAction(const event::Action& e) override {
- if (!outPort || !inPort)
- return;
- createContextMenu();
- }
-
- virtual ui::Menu* createContextMenu() {
- ui::Menu* menu = createMenu();
- menu->addChild(createMenuLabel("MIDI device"));
- {
- MidiDeviceItem* item = new MidiDeviceItem;
- item->outPort = outPort;
- item->inPort = inPort;
- item->deviceId = -1;
- item->text = "(No device)";
- item->rightText = CHECKMARK(item->deviceId == outPort->deviceId);
- menu->addChild(item);
- }
- for (int deviceId : outPort->getDeviceIds()) {
- MidiDeviceItem* item = new MidiDeviceItem;
- item->outPort = outPort;
- item->inPort = inPort;
- item->deviceId = deviceId;
- item->text = outPort->getDeviceName(deviceId);
- item->rightText = CHECKMARK(item->deviceId == outPort->deviceId);
- menu->addChild(item);
- }
- return menu;
- }
-
- void step() override {
- text = outPort ? outPort->getDeviceName(outPort->deviceId) : "";
- if (text.empty()) {
- text = "(No device)";
- color.a = 0.5f;
- }
- else {
- color.a = 1.f;
- }
- }
- };
-
- struct MidiWidget : LedDisplay {
- MidiDriverChoice* driverChoice;
- LedDisplaySeparator* driverSeparator;
- MidiDeviceChoice* deviceChoice;
- LedDisplaySeparator* deviceSeparator;
-
- void setMidiPorts(midi::Port* outPort, midi::Port* inPort) {
-
- clearChildren();
- math::Vec pos;
-
- MidiDriverChoice* driverChoice = createWidget(pos);
- driverChoice->box.size = Vec(box.size.x, 20.f);
- //driverChoice->textOffset = Vec(6.f, 14.7f);
- driverChoice->color = nvgRGB(0xf0, 0xf0, 0xf0);
- driverChoice->port = outPort;
-
- addChild(driverChoice);
- pos = driverChoice->box.getBottomLeft();
- this->driverChoice = driverChoice;
-
- this->driverSeparator = createWidget(pos);
- this->driverSeparator->box.size.x = box.size.x;
- addChild(this->driverSeparator);
-
- MidiDeviceChoice* deviceChoice = createWidget(pos);
- deviceChoice->box.size = Vec(box.size.x, 21.f);
- //deviceChoice->textOffset = Vec(6.f, 14.7f);
- deviceChoice->color = nvgRGB(0xf0, 0xf0, 0xf0);
- deviceChoice->outPort = outPort;
- deviceChoice->inPort = inPort;
- addChild(deviceChoice);
- pos = deviceChoice->box.getBottomLeft();
- this->deviceChoice = deviceChoice;
- }
- };
-
-
- MidiThingWidget(MidiThing* module) {
- setModule(module);
- setPanel(createPanel(asset::plugin(pluginInstance, "res/panels/MidiThing.svg")));
-
- addChild(createWidget(Vec(RACK_GRID_WIDTH, 0)));
- addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
-
- MidiWidget* midiInputWidget = createWidget(Vec(1.5f, 36.4f)); //mm2px(Vec(0.5f, 10.f)));
- midiInputWidget->box.size = mm2px(Vec(5.08 * 6 - 1, 13.5f));
- if (module) {
- midiInputWidget->setMidiPorts(&module->midiOut, &module->inputQueue);
- }
- else {
- midiInputWidget->setMidiPorts(nullptr, nullptr);
- }
- addChild(midiInputWidget);
-
- addParam(createParamCentered(mm2px(Vec(21.12, 57.32)), module, MidiThing::REFRESH_PARAM));
-
- const float xStartLed = 0.2 + 0.628;
- const float yStartLed = 28.019;
-
- for (int row = 0; row < 4; row++) {
- for (int col = 0; col < 3; col++) {
-
- LEDDisplay* display = createWidget(mm2px(Vec(xStartLed + 9.751 * col, yStartLed + 5.796 * row)));
- display->module = module;
- display->row = row;
- display->col = col;
- addChild(display);
-
- auto input = createInputCentered(mm2px(Vec(5.08 + 10 * col, 69.77 + 14.225 * row)), module, MidiThing::A1_INPUT + 3 * row + col);
- input->row = row;
- input->col = col;
- input->module = module;
- addInput(input);
-
-
- }
- }
- }
-
- void appendContextMenu(Menu* menu) override {
- MidiThing* module = dynamic_cast(this->module);
- assert(module);
-
- menu->addChild(new MenuSeparator());
-
- menu->addChild(createSubmenuItem("Select MIDI Device", "",
- [ = ](Menu * menu) {
-
- for (auto driverId : rack::midi::getDriverIds()) {
- midi::Driver* driver = midi::getDriver(driverId);
- const bool activeDriver = module->midiOut.getDriverId() == driverId;
-
- menu->addChild(createSubmenuItem(driver->getName(), CHECKMARK(activeDriver),
- [ = ](Menu * menu) {
-
- for (auto deviceId : driver->getOutputDeviceIds()) {
- const bool activeDevice = activeDriver && module->midiOut.getDeviceId() == deviceId;
-
- menu->addChild(createMenuItem(driver->getOutputDeviceName(deviceId),
- CHECKMARK(activeDevice),
- [ = ]() {
- module->midiOut.setDriverId(driverId);
- module->midiOut.setDeviceId(deviceId);
-
- module->inputQueue.setDriverId(driverId);
- module->inputQueue.setDeviceId(deviceId);
- module->inputQueue.setChannel(0); // TODO update
-
- module->doSync();
-
- // DEBUG("Updating Output MIDI settings - driver: %s, device: %s",
- // driver->getName().c_str(), driver->getOutputDeviceName(deviceId).c_str());
- }));
- }
- }));
- }
- }));
-
- menu->addChild(createIndexPtrSubmenuItem("All channels MIDI update rate",
- module->updateRateNames,
- &module->updateRateIdx));
-
- float updateRate = module->updateRates[module->updateRateIdx] / module->numActiveChannels;
- menu->addChild(createMenuLabel(string::f("Per-channel MIDI update rate: %.3g Hz", updateRate)));
- }
-};
-
-
-Model* modelMidiThing = createModel("MidiThingV2");
\ No newline at end of file
diff --git a/src/PonyVCF.cpp b/src/PonyVCF.cpp
deleted file mode 100644
index 1ed763a..0000000
--- a/src/PonyVCF.cpp
+++ /dev/null
@@ -1,224 +0,0 @@
-#include "plugin.hpp"
-
-using simd::float_4;
-
-// filter engine is just Fundemental VCF from https://github.com/VCVRack/Fundamental/blob/v2/src/VCF.cpp for now
-// GPL-v3
-
-
-template
-static T clip(T x) {
- // return std::tanh(x);
- // Pade approximant of tanh
- x = simd::clamp(x, -3.f, 3.f);
- return x * (27 + x * x) / (27 + 9 * x * x);
-}
-
-
-template
-struct LadderFilter {
- T omega0;
- T resonance = 1;
- T state[4];
- T input;
-
- LadderFilter() {
- reset();
- setCutoff(0);
- }
-
- void reset() {
- for (int i = 0; i < 4; i++) {
- state[i] = 0;
- }
- }
-
- void setCutoff(T cutoff) {
- omega0 = 2 * T(M_PI) * cutoff;
- }
-
- void process(T input, T dt) {
- dsp::stepRK4(T(0), dt, state, 4, [&](T t, const T x[], T dxdt[]) {
- T inputt = crossfade(this->input, input, t / dt);
- T inputc = clip(inputt - resonance * x[3]);
- T yc0 = clip(x[0]);
- T yc1 = clip(x[1]);
- T yc2 = clip(x[2]);
- T yc3 = clip(x[3]);
-
- dxdt[0] = omega0 * (inputc - yc0);
- dxdt[1] = omega0 * (yc0 - yc1);
- dxdt[2] = omega0 * (yc1 - yc2);
- dxdt[3] = omega0 * (yc2 - yc3);
- });
-
- this->input = input;
- }
-
- T lowpass() {
- return state[3];
- }
- T highpass() {
- return clip((input - resonance * state[3]) - 4 * state[0] + 6 * state[1] - 4 * state[2] + state[3]);
- }
-};
-
-
-struct PonyVCF : Module {
- enum ParamId {
- CV1_PARAM,
- RES_PARAM,
- FREQ_PARAM,
- GAIN1_PARAM,
- GAIN2_PARAM,
- GAIN3_PARAM,
- ROUTING_PARAM,
- PARAMS_LEN
- };
- enum InputId {
- IN1_INPUT,
- RES_INPUT,
- VCA_INPUT,
- IN2_INPUT,
- CV1_INPUT,
- IN3_INPUT,
- CV2_INPUT,
- INPUTS_LEN
- };
- enum OutputId {
- OUTPUT,
- OUTPUTS_LEN
- };
- enum LightId {
- IN2_LIGHT,
- IN1_LIGHT,
- LIGHTS_LEN
- };
-
- LadderFilter filters[4];
-
- PonyVCF() {
- config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN);
- configParam(CV1_PARAM, 0.f, 1.f, 0.f, "CV1 Attenuator");
- configParam(RES_PARAM, 0.f, 1.f, 0.f, "Resonance");
- configParam(FREQ_PARAM, 0.f, 1.f, 0.f, "Frequency");
- configParam(GAIN1_PARAM, 0.f, 1.2f, 1.f, "Gain Channel 1");
- configParam(GAIN2_PARAM, 0.f, 1.2f, 1.f, "Gain Channel 2");
- configParam(GAIN3_PARAM, 0.f, 1.2f, 1.f, "Gain Channel 3");
- configParam(ROUTING_PARAM, 0.f, 1.f, 0.f, "VCA routing");
-
- configInput(IN1_INPUT, "Channel 1");
- configInput(RES_INPUT, "Resonance CV");
- configInput(VCA_INPUT, "VCA");
- configInput(IN2_INPUT, "Channel 2");
- configInput(CV1_INPUT, "Frequency (CV1)");
- configInput(IN3_INPUT, "Channel 3");
- configInput(CV2_INPUT, "Frequency (CV2)");
-
- configOutput(OUTPUT, "Main");
-
- onReset();
- }
-
-
- void onReset() override {
- for (int i = 0; i < 4; i++)
- filters[i].reset();
- }
-
- float_4 prevOut[4] = {};
-
- void process(const ProcessArgs& args) override {
- if (!outputs[OUTPUT].isConnected()) {
- return;
- }
-
- float resParam = params[RES_PARAM].getValue();
- float freqParam = params[FREQ_PARAM].getValue();
- float freqCvParam = params[CV1_PARAM].getValue();
-
-
- int channels = std::max({1, inputs[IN1_INPUT].getChannels(), inputs[IN2_INPUT].getChannels(), inputs[IN3_INPUT].getChannels()});
-
- for (int c = 0; c < channels; c += 4) {
- auto& filter = filters[c / 4];
-
- float_4 input = inputs[IN1_INPUT].getVoltageSimd(c) * params[GAIN1_PARAM].getValue();
- input += inputs[IN2_INPUT].getVoltageSimd(c) * params[GAIN2_PARAM].getValue();
- input += inputs[IN3_INPUT].getNormalVoltageSimd(prevOut[c / 4], c) * params[GAIN3_PARAM].getValue();
-
-
- // input = Saturator::process(input / 5.0f) * 1.1f;
- input = clip(input / 5.0f) * 1.1f;
-
- // Add -120dB noise to bootstrap self-oscillation
- input += 1e-6f * (2.f * random::uniform() - 1.f);
-
- // Set resonance
- float_4 resonance = resParam + inputs[RES_INPUT].getPolyVoltageSimd(c) / 10.f;
- resonance = clamp(resonance, 0.f, 1.f);
- filter.resonance = simd::pow(resonance, 2) * 10.f;
-
- // Get pitch
- float_4 pitch = 5 * freqParam + inputs[CV1_INPUT].getPolyVoltageSimd(c) * freqCvParam + inputs[CV2_INPUT].getPolyVoltageSimd(c);
- // Set cutoff
- float_4 cutoff = dsp::FREQ_C4 * dsp::exp2_taylor5(pitch);
- // Without oversampling, we must limit to 8000 Hz or so @ 44100 Hz
- cutoff = clamp(cutoff, 1.f, args.sampleRate / 2.f);
-
- // Without oversampling, we must limit to 8000 Hz or so @ 44100 Hz
- cutoff = clamp(cutoff, 1.f, args.sampleRate * 0.18f);
- filter.setCutoff(cutoff);
-
- // Set outputs
- filter.process(input, args.sampleTime);
- if (outputs[OUTPUT].isConnected()) {
- float_4 resGain = 1.0f / (0.05 + 0.9 * dsp::exp2_taylor5(-8.f * simd::pow(resonance, 2.0)));
- // float_4 resGain = (1.0f + 8.f * resonance); // 1st order empirical fit
- float_4 out = 5.f * filter.lowpass() * resGain;
- outputs[OUTPUT].setVoltageSimd(out, c);
-
- prevOut[c / 4] = out;
- }
-
- // DEBUG("channel %d %g", channels, input[0]);
- }
-
- outputs[OUTPUT].setChannels(channels);
- }
-};
-
-
-struct PonyVCFWidget : ModuleWidget {
- PonyVCFWidget(PonyVCF* module) {
- setModule(module);
- setPanel(createPanel(asset::plugin(pluginInstance, "res/panels/PonyVCF.svg")));
-
- addChild(createWidget(Vec(RACK_GRID_WIDTH, 0)));
- addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
-
- addParam(createParamCentered(mm2px(Vec(7.62, 14.5)), module, PonyVCF::CV1_PARAM));
- addParam(createParamCentered(mm2px(Vec(22.38, 14.5)), module, PonyVCF::RES_PARAM));
- addParam(createParamCentered(mm2px(Vec(15.0, 35.001)), module, PonyVCF::FREQ_PARAM));
- addParam(createParam(mm2px(Vec(3.217, 48.584)), module, PonyVCF::GAIN1_PARAM));
- addParam(createParam(mm2px(Vec(13.271, 48.584)), module, PonyVCF::GAIN2_PARAM));
- addParam(createParam(mm2px(Vec(23.316, 48.584)), module, PonyVCF::GAIN3_PARAM));
- addParam(createParam(mm2px(Vec(23.498, 96.784)), module, PonyVCF::ROUTING_PARAM));
-
- addInput(createInputCentered(mm2px(Vec(5.0, 86.5)), module, PonyVCF::IN1_INPUT));
- addInput(createInputCentered(mm2px(Vec(15.0, 86.5)), module, PonyVCF::RES_INPUT));
- addInput(createInputCentered(mm2px(Vec(25.0, 86.5)), module, PonyVCF::VCA_INPUT));
- addInput(createInputCentered(mm2px(Vec(5.0, 100.0)), module, PonyVCF::IN2_INPUT));
- addInput(createInputCentered(mm2px(Vec(15.0, 100.0)), module, PonyVCF::CV1_INPUT));
- addInput(createInputCentered(mm2px(Vec(5.0, 113.5)), module, PonyVCF::IN3_INPUT));
- addInput(createInputCentered(mm2px(Vec(15.0, 113.5)), module, PonyVCF::CV2_INPUT));
-
- addOutput(createOutputCentered(mm2px(Vec(25.0, 113.5)), module, PonyVCF::OUTPUT));
-
- addChild(createLightCentered>(mm2px(Vec(2.578, 23.492)), module, PonyVCF::IN2_LIGHT));
- addChild(createLightCentered>(mm2px(Vec(2.578, 27.159)), module, PonyVCF::IN1_LIGHT));
- }
-};
-
-
-Model* modelPonyVCF = createModel("PonyVCF");
\ No newline at end of file
diff --git a/src/plugin.cpp b/src/plugin.cpp
index fa8f4bc..704debd 100644
--- a/src/plugin.cpp
+++ b/src/plugin.cpp
@@ -7,7 +7,6 @@ void init(rack::Plugin *p) {
pluginInstance = p;
p->addModel(modelEvenVCO);
- p->addModel(modelEvenVCO2);
p->addModel(modelRampage);
p->addModel(modelABC);
p->addModel(modelSpringReverb);
@@ -29,8 +28,6 @@ void init(rack::Plugin *p) {
p->addModel(modelPonyVCO);
p->addModel(modelMotionMTR);
p->addModel(modelBurst);
- p->addModel(modelMidiThing);
p->addModel(modelVoltio);
p->addModel(modelOctaves);
- p->addModel(modelPonyVCF);
}
diff --git a/src/plugin.hpp b/src/plugin.hpp
index 57eb131..17addd5 100644
--- a/src/plugin.hpp
+++ b/src/plugin.hpp
@@ -8,7 +8,6 @@ using namespace rack;
extern Plugin* pluginInstance;
extern Model* modelEvenVCO;
-extern Model* modelEvenVCO2;
extern Model* modelRampage;
extern Model* modelABC;
extern Model* modelSpringReverb;
@@ -30,10 +29,8 @@ extern Model* modelChannelStrip;
extern Model* modelPonyVCO;
extern Model* modelMotionMTR;
extern Model* modelBurst;
-extern Model* modelMidiThing;
extern Model* modelVoltio;
extern Model* modelOctaves;
-extern Model* modelPonyVCF;
struct Knurlie : SvgScrew {
Knurlie() {