From cd957f166ce1dd1991ed527b4173ac373127aaf0 Mon Sep 17 00:00:00 2001 From: hemmer <915048+hemmer@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:46:13 +0000 Subject: [PATCH] Upsample SYNC and FM inputs to EvenVCO --- .github/workflows/build-plugin.yml | 85 ++++++++++++++++++++++++++++++ CHANGELOG.md | 4 ++ plugin.json | 4 +- src/EvenVCO.cpp | 60 +++++++++++++++------ 4 files changed, 137 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/build-plugin.yml diff --git a/.github/workflows/build-plugin.yml b/.github/workflows/build-plugin.yml new file mode 100644 index 0000000..cedb3a2 --- /dev/null +++ b/.github/workflows/build-plugin.yml @@ -0,0 +1,85 @@ + +name: Build VCV Rack Plugin +on: [push, pull_request] + +env: + rack-sdk-version: 2.5.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 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index a2f6bf9..e6bf036 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## v2.8.2 + * EvenVCO + * Upsample Hard Sync and FM inputs + ## v2.8.1 * Noise Plethora * Fix bug where program choice is wrongly copied between top and bottom sections diff --git a/plugin.json b/plugin.json index 2450087..d2a2274 100644 --- a/plugin.json +++ b/plugin.json @@ -1,6 +1,6 @@ { "slug": "Befaco", - "version": "2.8.1", + "version": "2.8.2", "license": "GPL-3.0-or-later", "name": "Befaco", "brand": "Befaco", @@ -349,6 +349,8 @@ "slug": "Bandit", "name": "Bandit", "description": "Bandit is a spectral processing playground.", + "manualUrl": "https://www.befaco.org/bandit/", + "modularGridUrl": "https://www.modulargrid.net/e/befaco-bandit", "tags": [ "Equalizer", "Filter", diff --git a/src/EvenVCO.cpp b/src/EvenVCO.cpp index ae2bded..3de3aa5 100644 --- a/src/EvenVCO.cpp +++ b/src/EvenVCO.cpp @@ -43,7 +43,7 @@ struct EvenVCO : Module { configInput(PITCH1_INPUT, "Pitch 1"); configInput(PITCH2_INPUT, "Pitch 2"); configInput(FM_INPUT, "FM"); - configInput(SYNC_INPUT, "Sync"); + configInput(SYNC_INPUT, "Hard Sync"); configInput(PWM_INPUT, "Pulse Width Modulation"); configOutput(TRI_OUTPUT, "Triangle"); @@ -52,8 +52,6 @@ struct EvenVCO : Module { configOutput(SAW_OUTPUT, "Sawtooth"); configOutput(SQUARE_OUTPUT, "Square"); - // calculate up/downsampling rates - onSampleRateChange(); } void onSampleRateChange() override { @@ -65,6 +63,13 @@ struct EvenVCO : Module { } } + for (int c = 0; c < 4; c++) { + for (int i = 0; i < NUM_UPSAMPLED_INPUTS; i++) { + oversamplerInputs[i][c].setOversamplingIndex(oversamplingIndex); + oversamplerInputs[i][c].reset(sampleRate); + } + } + const float lowFreqRegime = oversampler[0][0].getOversamplingRatio() * 1e-3 * sampleRate; DEBUG("Low freq regime: %g", lowFreqRegime); } @@ -111,6 +116,12 @@ struct EvenVCO : Module { return (sawOffsetBuff[0] - 2.0 * sawOffsetBuff[1] + sawOffsetBuff[2]); } + enum UpsampledInputs { + FM_INPUT_UP, + SYNC_INPUT_UP, + NUM_UPSAMPLED_INPUTS + }; + chowdsp::VariableOversampling<6, float_4> oversamplerInputs[NUM_UPSAMPLED_INPUTS][4]; // uses a 2*6=12th order Butterworth filter chowdsp::VariableOversampling<6, float_4> oversampler[NUM_OUTPUTS][4]; // uses a 2*6=12th order Butterworth filter int oversamplingIndex = 2; // default is 2^oversamplingIndex == x4 oversampling @@ -131,24 +142,30 @@ struct EvenVCO : Module { 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]); + // input oversampling buffers + float_4* osBufferSync = oversamplerInputs[SYNC_INPUT_UP][c / 4].getOSBuffer(); + float_4* osBufferFM = oversamplerInputs[FM_INPUT_UP][c / 4].getOSBuffer(); + + // upsample hard sync input (if connected) + if (inputs[SYNC_INPUT].isConnected()) { + oversamplerInputs[SYNC_INPUT_UP][c].upsample(inputs[SYNC_INPUT].getPolyVoltageSimd(c)); + } + else { + std::fill(osBufferSync, &osBufferSync[oversamplingRatio], float_4::zero()); + } + // upsample FM input (if connected) + if (inputs[FM_INPUT].isConnected()) { + oversamplerInputs[FM_INPUT_UP][c].upsample(inputs[FM_INPUT].getPolyVoltageSimd(c)); + } + else { + std::fill(osBufferFM, &osBufferFM[oversamplingRatio], float_4::zero()); + } float_4* osBufferTri = oversampler[TRI_OUTPUT][c / 4].getOSBuffer(); float_4* osBufferSaw = oversampler[SAW_OUTPUT][c / 4].getOSBuffer(); @@ -156,11 +173,24 @@ struct EvenVCO : Module { 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) { + // use upsampled FM input + const float_4 fmVoltage = osBufferFM[i] * 0.25f; + 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); phase[c / 4] += deltaBasePhase; // ensure within [0, 1] phase[c / 4] -= simd::floor(phase[c / 4]); + const float_4 syncMask = syncTrigger[c / 4].process(osBufferSync[i]); + phase[c / 4] = simd::ifelse(syncMask, 0.5f, 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);