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 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -   - - - - - - -   - - - - - - -   -   - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - - - - - 0-10v - -10-0v - 0-10v - -10-0v - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - MIDI THING V2 - - - - - - - - - - MIDI THINGV2 -   -   -   - - - - - - - - - - - - - -   -   - - - - - 1 - 2 - 3 - MIDI THING V2 - MIDI THINGV2 -   -   -   - - - - - - - - - - - - - -   -   - - - - - MIDI THINGV2 -   -   -   - - - - - - - - - - - - - -   -   - - - - - MIDI THINGV2 -   -   -   - - - - - - - - - - - - - -   -   - - - - - 0-10v - -5/5v - -10-0v - 0-10v - -5/5v - -10-0v - 0-10v - -10-0v - SYNC - - - - - - - - - - - - - - - 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 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - VCA - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -   - - - - - - - - - - - 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() {