diff --git a/README.md b/README.md index c69463a..1e46a88 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,17 @@ Based on [Befaco](http://www.befaco.org/) Eurorack modules. -[VCV Library page](https://library.vcvrack.com/Befaco) \ No newline at end of file +[VCV Library page](https://library.vcvrack.com/Befaco) + + +## Differences with hardware + +We have tried to make the VCV implementations as authentic as possible, however there are some minor changes that have been made (either for usuability or to bring modules in line with VCV rack conventions and standards). + +* Sampling Modulator removes the DC offset of "Clock Out" and "Trigg Out" to allow these to be easily used as oscillators - the legacy behaviour (0 - 10V pulses) can be selected via the context menu. + +* The hardware version of Morphader accepts 0-8V CV for the crossfade control, here we widen this to accept 0-10V. + +* Chopping Kinky hardward is DC coupled, but we add the option (default disabled) to remove this offset. + +* The hardware Muxlicer assigns multiple functions to the "Speed Div/Mult" dial, that cannot be reproduced with a single mouse click. Some of these have been moved to the context menu, specifically: quadratic gates, the "All In" normalled voltage, and the output clock division/mult. \ No newline at end of file diff --git a/src/ABC.cpp b/src/ABC.cpp index 3ef6ecf..cc6c6f1 100644 --- a/src/ABC.cpp +++ b/src/ABC.cpp @@ -11,13 +11,6 @@ static T clip(T x) { / (1.0f + 1.54167f * simd::pow(x, 12) + 0.642361f * simd::pow(x, 24) + 0.0579909f * simd::pow(x, 36)); } - -static float exponentialBipolar80Pade_5_4(float x) { - return (0.109568 * x + 0.281588 * std::pow(x, 3) + 0.133841 * std::pow(x, 5)) - / (1. - 0.630374 * std::pow(x, 2) + 0.166271 * std::pow(x, 4)); -} - - struct ABC : Module { enum ParamIds { B1_LEVEL_PARAM, diff --git a/src/Morphader.cpp b/src/Morphader.cpp index 9ea89f2..dc0d5ee 100644 --- a/src/Morphader.cpp +++ b/src/Morphader.cpp @@ -10,8 +10,8 @@ inline T equalSumCrossfade(T a, T b, const float p) { // equal power crossfade, -1 <= p <= 1 template inline T equalPowerCrossfade(T a, T b, const float p) { - // TODO: investigate more efficient representation (avoid exp) - return std::min(std::exp(4.f * p), 1.f) * b + std::min(std::exp(4.f * -p), 1.f) * a; + //return std::min(std::exp(4.f * p), 1.f) * b + std::min(std::exp(4.f * -p), 1.f) * a; + return std::min(exponentialBipolar80Pade_5_4(p + 1), 1.f) * b + std::min(exponentialBipolar80Pade_5_4(1 - p), 1.f) * a; } // TExponentialSlewLimiter doesn't appear to work correctly (tried for -1 -> +1, and 0 -> 1) @@ -95,13 +95,6 @@ struct Morphader : Module { } }; - struct FaderLagParam : ParamQuantity { - std::string getDisplayValueString() override { - const float slewTime = 2.f / (slewMax * std::pow(slewMin / slewMax, getValue())); - return string::f("%.3gs", slewTime); - } - }; - Morphader() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); @@ -117,7 +110,7 @@ struct Morphader : Module { configParam(MODE + i, AUDIO_MODE, CV_MODE, AUDIO_MODE, "Mode " + std::to_string(i + 1)); } - configParam(FADER_LAG_PARAM, 0.f, 1.f, 0.f, "Fader lag"); + configParam(FADER_LAG_PARAM, 2.0f / slewMax, 2.0f / slewMin, 2.0f / slewMax, "Fader lag", "s"); configParam(FADER_PARAM, -1.f, 1.f, 0.f, "Fader"); } @@ -126,14 +119,14 @@ struct Morphader : Module { simd::float_4 channelCrossfades = {0.f}; - slewLimiter.setSlew(slewMax * std::pow(slewMin / slewMax, params[FADER_LAG_PARAM].getValue())); + slewLimiter.setSlew(2.0f / params[FADER_LAG_PARAM].getValue()); const float masterCrossfadeValue = slewLimiter.process(deltaTime, params[FADER_PARAM].getValue()); for (int i = 0; i < NUM_MIXER_CHANNELS; i++) { if (i == 0) { // CV will be added to master for channel 1, and if not connected, the normalled value of 5.0V will correspond to the midpoint - const float crossfadeCV = clamp(inputs[CV_INPUT + i].getNormalVoltage(5.f), 0.f, 10.f); + const float crossfadeCV = clamp(inputs[CV_INPUT + i].getVoltage(), 0.f, 10.f); channelCrossfades[i] = params[CV_PARAM].getValue() * rescale(crossfadeCV, 0.f, 10.f, 0.f, +2.f) + masterCrossfadeValue; } else { @@ -142,16 +135,18 @@ struct Morphader : Module { const float crossfadeCV = clamp(inputs[CV_INPUT + i].getVoltage(), 0.f, 10.f); channelCrossfades[i] = rescale(crossfadeCV, 0.f, 10.f, -1.f, +1.f); } - // if channel 1 is plugged in, use that + // if channel 1 is plugged in, but this channel isn't, channel 1 is normalled - in + // this scenario, however the CV is summed with the crossfader else if (inputs[CV_INPUT + 0].isConnected()) { - // TODO: is this right, or is is channelCrossfades[i] (i.e. with master fader)? const float crossfadeCV = clamp(inputs[CV_INPUT + 0].getVoltage(), 0.f, 10.f); - channelCrossfades[i] = params[CV_PARAM].getValue() * rescale(crossfadeCV, 0.f, 10.f, -1.f, +1.f); + channelCrossfades[i] = params[CV_PARAM].getValue() * rescale(crossfadeCV, 0.f, 10.f, 0.f, +2.f) + masterCrossfadeValue; } else { channelCrossfades[i] = masterCrossfadeValue; } } + + channelCrossfades[i] = clamp(channelCrossfades[i], -1.f, +1.f); } return channelCrossfades; @@ -183,7 +178,9 @@ struct Morphader : Module { break; } case AUDIO_MODE: { - out[c / 4] = equalPowerCrossfade(inA, inB, channelCrossfades[i]); + // in audio mode, close to the centre point it is possible to get large voltages + // (e.g. if A and B are both 10V const), so clip + out[c / 4] = clamp(equalPowerCrossfade(inA, inB, channelCrossfades[i]), -12.f, +12.f); break; } default: assert(false); diff --git a/src/plugin.hpp b/src/plugin.hpp index 658ca70..3dc27a8 100644 --- a/src/plugin.hpp +++ b/src/plugin.hpp @@ -4,21 +4,21 @@ using namespace rack; -extern Plugin *pluginInstance; - -extern Model *modelEvenVCO; -extern Model *modelRampage; -extern Model *modelABC; -extern Model *modelSpringReverb; -extern Model *modelMixer; -extern Model *modelSlewLimiter; -extern Model *modelDualAtenuverter; -extern Model *modelPercall; -extern Model *modelHexmixVCA; -extern Model *modelChoppingKinky; -extern Model *modelKickall; -extern Model *modelSamplingModulator; -extern Model *modelMorphader; +extern Plugin* pluginInstance; + +extern Model* modelEvenVCO; +extern Model* modelRampage; +extern Model* modelABC; +extern Model* modelSpringReverb; +extern Model* modelMixer; +extern Model* modelSlewLimiter; +extern Model* modelDualAtenuverter; +extern Model* modelPercall; +extern Model* modelHexmixVCA; +extern Model* modelChoppingKinky; +extern Model* modelKickall; +extern Model* modelSamplingModulator; +extern Model* modelMorphader; struct Knurlie : SvgScrew { @@ -121,6 +121,11 @@ T tanh_pade(T x) { return 12.f * x * q / (36.f * x2 + q * q); } +template +T exponentialBipolar80Pade_5_4(T x) { + return (T(0.109568) * x + T(0.281588) * simd::pow(x, 3) + T(0.133841) * simd::pow(x, 5)) + / (T(1.) - T(0.630374) * simd::pow(x, 2) + T(0.166271) * simd::pow(x, 4)); +} struct ADEnvelope { enum Stage {