Browse Source

Upsample SYNC and FM inputs to EvenVCO

pull/57/head
hemmer 5 months ago
parent
commit
cd957f166c
4 changed files with 137 additions and 16 deletions
  1. +85
    -0
      .github/workflows/build-plugin.yml
  2. +4
    -0
      CHANGELOG.md
  3. +3
    -1
      plugin.json
  4. +45
    -15
      src/EvenVCO.cpp

+ 85
- 0
.github/workflows/build-plugin.yml View File

@@ -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

+ 4
- 0
CHANGELOG.md View File

@@ -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


+ 3
- 1
plugin.json View File

@@ -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",


+ 45
- 15
src/EvenVCO.cpp View File

@@ -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<float_4>(c) * 0.25f;
const float_4 pitch = inputs[PITCH1_INPUT].getPolyVoltageSimd<float_4>(c) + inputs[PITCH2_INPUT].getPolyVoltageSimd<float_4>(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<float_4>(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<float_4>(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<float_4>(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);


Loading…
Cancel
Save