|
- #include "plugin.hpp"
-
-
- using simd::float_4;
-
-
- // Accurate only on [0, 1]
- template <typename T>
- T sin2pi_pade_05_7_6(T x) {
- x -= 0.5f;
- return (T(-6.28319) * x + T(35.353) * simd::pow(x, 3) - T(44.9043) * simd::pow(x, 5) + T(16.0951) * simd::pow(x, 7))
- / (1 + T(0.953136) * simd::pow(x, 2) + T(0.430238) * simd::pow(x, 4) + T(0.0981408) * simd::pow(x, 6));
- }
-
- template <typename T>
- T sin2pi_pade_05_5_4(T x) {
- x -= 0.5f;
- return (T(-6.283185307) * x + T(33.19863968) * simd::pow(x, 3) - T(32.44191367) * simd::pow(x, 5))
- / (1 + T(1.296008659) * simd::pow(x, 2) + T(0.7028072946) * simd::pow(x, 4));
- }
-
- template <typename T>
- T expCurve(T x) {
- return (3 + x * (-13 + 5 * x)) / (3 + 2 * x);
- }
-
-
- template <int OVERSAMPLE, int QUALITY, typename T>
- struct VoltageControlledOscillator {
- bool analog = false;
- bool soft = false;
- bool syncEnabled = false;
- // For optimizing in serial code
- int channels = 0;
-
- T lastSyncValue = 0.f;
- T phase = 0.f;
- T freq = 0.f;
- T pulseWidth = 0.5f;
- T syncDirection = 1.f;
-
- dsp::TRCFilter<T> sqrFilter;
-
- dsp::MinBlepGenerator<QUALITY, OVERSAMPLE, T> sqrMinBlep;
- dsp::MinBlepGenerator<QUALITY, OVERSAMPLE, T> sawMinBlep;
- dsp::MinBlepGenerator<QUALITY, OVERSAMPLE, T> triMinBlep;
- dsp::MinBlepGenerator<QUALITY, OVERSAMPLE, T> sinMinBlep;
-
- T sqrValue = 0.f;
- T sawValue = 0.f;
- T triValue = 0.f;
- T sinValue = 0.f;
-
- void setPulseWidth(T pulseWidth) {
- const float pwMin = 0.01f;
- this->pulseWidth = simd::clamp(pulseWidth, pwMin, 1.f - pwMin);
- }
-
- void process(float deltaTime, T syncValue) {
- // Advance phase
- T deltaPhase = simd::clamp(freq * deltaTime, 0.f, 0.35f);
- if (soft) {
- // Reverse direction
- deltaPhase *= syncDirection;
- }
- else {
- // Reset back to forward
- syncDirection = 1.f;
- }
- phase += deltaPhase;
- // Wrap phase
- phase -= simd::floor(phase);
-
- // Jump sqr when crossing 0, or 1 if backwards
- T wrapPhase = (syncDirection == -1.f) & 1.f;
- T wrapCrossing = (wrapPhase - (phase - deltaPhase)) / deltaPhase;
- int wrapMask = simd::movemask((0 < wrapCrossing) & (wrapCrossing <= 1.f));
- if (wrapMask) {
- for (int i = 0; i < channels; i++) {
- if (wrapMask & (1 << i)) {
- T mask = simd::movemaskInverse<T>(1 << i);
- float p = wrapCrossing[i] - 1.f;
- T x = mask & (2.f * syncDirection);
- sqrMinBlep.insertDiscontinuity(p, x);
- }
- }
- }
-
- // Jump sqr when crossing `pulseWidth`
- T pulseCrossing = (pulseWidth - (phase - deltaPhase)) / deltaPhase;
- int pulseMask = simd::movemask((0 < pulseCrossing) & (pulseCrossing <= 1.f));
- if (pulseMask) {
- for (int i = 0; i < channels; i++) {
- if (pulseMask & (1 << i)) {
- T mask = simd::movemaskInverse<T>(1 << i);
- float p = pulseCrossing[i] - 1.f;
- T x = mask & (-2.f * syncDirection);
- sqrMinBlep.insertDiscontinuity(p, x);
- }
- }
- }
-
- // Jump saw when crossing 0.5
- T halfCrossing = (0.5f - (phase - deltaPhase)) / deltaPhase;
- int halfMask = simd::movemask((0 < halfCrossing) & (halfCrossing <= 1.f));
- if (halfMask) {
- for (int i = 0; i < channels; i++) {
- if (halfMask & (1 << i)) {
- T mask = simd::movemaskInverse<T>(1 << i);
- float p = halfCrossing[i] - 1.f;
- T x = mask & (-2.f * syncDirection);
- sawMinBlep.insertDiscontinuity(p, x);
- }
- }
- }
-
- // Detect sync
- // Might be NAN or outside of [0, 1) range
- if (syncEnabled) {
- T deltaSync = syncValue - lastSyncValue;
- T syncCrossing = -lastSyncValue / deltaSync;
- lastSyncValue = syncValue;
- T sync = (0.f < syncCrossing) & (syncCrossing <= 1.f) & (syncValue >= 0.f);
- int syncMask = simd::movemask(sync);
- if (syncMask) {
- if (soft) {
- syncDirection = simd::ifelse(sync, -syncDirection, syncDirection);
- }
- else {
- T newPhase = simd::ifelse(sync, (1.f - syncCrossing) * deltaPhase, phase);
- // Insert minBLEP for sync
- for (int i = 0; i < channels; i++) {
- if (syncMask & (1 << i)) {
- T mask = simd::movemaskInverse<T>(1 << i);
- float p = syncCrossing[i] - 1.f;
- T x;
- x = mask & (sqr(newPhase) - sqr(phase));
- sqrMinBlep.insertDiscontinuity(p, x);
- x = mask & (saw(newPhase) - saw(phase));
- sawMinBlep.insertDiscontinuity(p, x);
- x = mask & (tri(newPhase) - tri(phase));
- triMinBlep.insertDiscontinuity(p, x);
- x = mask & (sin(newPhase) - sin(phase));
- sinMinBlep.insertDiscontinuity(p, x);
- }
- }
- phase = newPhase;
- }
- }
- }
-
- // Square
- sqrValue = sqr(phase);
- sqrValue += sqrMinBlep.process();
-
- if (analog) {
- sqrFilter.setCutoffFreq(20.f * deltaTime);
- sqrFilter.process(sqrValue);
- sqrValue = sqrFilter.highpass() * 0.95f;
- }
-
- // Saw
- sawValue = saw(phase);
- sawValue += sawMinBlep.process();
-
- // Tri
- triValue = tri(phase);
- triValue += triMinBlep.process();
-
- // Sin
- sinValue = sin(phase);
- sinValue += sinMinBlep.process();
- }
-
- T sin(T phase) {
- T v;
- if (analog) {
- // Quadratic approximation of sine, slightly richer harmonics
- T halfPhase = (phase < 0.5f);
- T x = phase - simd::ifelse(halfPhase, 0.25f, 0.75f);
- v = 1.f - 16.f * simd::pow(x, 2);
- v *= simd::ifelse(halfPhase, 1.f, -1.f);
- }
- else {
- v = sin2pi_pade_05_5_4(phase);
- // v = sin2pi_pade_05_7_6(phase);
- // v = simd::sin(2 * T(M_PI) * phase);
- }
- return v;
- }
- T sin() {
- return sinValue;
- }
-
- T tri(T phase) {
- T v;
- if (analog) {
- T x = phase + 0.25f;
- x -= simd::trunc(x);
- T halfX = (x >= 0.5f);
- x *= 2;
- x -= simd::trunc(x);
- v = expCurve(x) * simd::ifelse(halfX, 1.f, -1.f);
- }
- else {
- v = 1 - 4 * simd::fmin(simd::fabs(phase - 0.25f), simd::fabs(phase - 1.25f));
- }
- return v;
- }
- T tri() {
- return triValue;
- }
-
- T saw(T phase) {
- T v;
- T x = phase + 0.5f;
- x -= simd::trunc(x);
- if (analog) {
- v = -expCurve(x);
- }
- else {
- v = 2 * x - 1;
- }
- return v;
- }
- T saw() {
- return sawValue;
- }
-
- T sqr(T phase) {
- T v = simd::ifelse(phase < pulseWidth, 1.f, -1.f);
- return v;
- }
- T sqr() {
- return sqrValue;
- }
-
- T light() {
- return simd::sin(2 * T(M_PI) * phase);
- }
- };
-
-
- struct VCO : Module {
- enum ParamIds {
- MODE_PARAM, // removed
- SYNC_PARAM,
- FREQ_PARAM,
- FINE_PARAM, // removed
- FM_PARAM,
- PW_PARAM,
- PW_CV_PARAM,
- // new in 2.0
- LINEAR_PARAM,
- NUM_PARAMS
- };
- enum InputIds {
- PITCH_INPUT,
- FM_INPUT,
- SYNC_INPUT,
- PW_INPUT,
- NUM_INPUTS
- };
- enum OutputIds {
- SIN_OUTPUT,
- TRI_OUTPUT,
- SAW_OUTPUT,
- SQR_OUTPUT,
- NUM_OUTPUTS
- };
- enum LightIds {
- ENUMS(PHASE_LIGHT, 3),
- LINEAR_LIGHT,
- SOFT_LIGHT,
- NUM_LIGHTS
- };
-
- VoltageControlledOscillator<16, 16, float_4> oscillators[4];
- dsp::ClockDivider lightDivider;
-
- VCO() {
- config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
- configSwitch(LINEAR_PARAM, 0.f, 1.f, 0.f, "FM mode", {"1V/octave", "Linear"});
- configSwitch(SYNC_PARAM, 0.f, 1.f, 1.f, "Sync mode", {"Soft", "Hard"});
- configParam(FREQ_PARAM, -54.f, 54.f, 0.f, "Frequency", " Hz", dsp::FREQ_SEMITONE, dsp::FREQ_C4);
- configParam(FM_PARAM, -1.f, 1.f, 0.f, "Frequency modulation", "%", 0.f, 100.f);
- getParamQuantity(FM_PARAM)->randomizeEnabled = false;
- configParam(PW_PARAM, 0.01f, 0.99f, 0.5f, "Pulse width", "%", 0.f, 100.f);
- configParam(PW_CV_PARAM, -1.f, 1.f, 0.f, "Pulse width modulation", "%", 0.f, 100.f);
- getParamQuantity(PW_CV_PARAM)->randomizeEnabled = false;
-
- configInput(PITCH_INPUT, "1V/octave pitch");
- configInput(FM_INPUT, "Frequency modulation");
- configInput(SYNC_INPUT, "Sync");
- configInput(PW_INPUT, "Pulse width modulation");
-
- configOutput(SIN_OUTPUT, "Sine");
- configOutput(TRI_OUTPUT, "Triangle");
- configOutput(SAW_OUTPUT, "Sawtooth");
- configOutput(SQR_OUTPUT, "Square");
-
- lightDivider.setDivision(16);
- }
-
- void process(const ProcessArgs& args) override {
- float freqParam = params[FREQ_PARAM].getValue() / 12.f;
- float fmParam = params[FM_PARAM].getValue();
- float pwParam = params[PW_PARAM].getValue();
- float pwCvParam = params[PW_CV_PARAM].getValue();
- bool linear = params[LINEAR_PARAM].getValue() > 0.f;
- bool soft = params[SYNC_PARAM].getValue() <= 0.f;
-
- int channels = std::max(inputs[PITCH_INPUT].getChannels(), 1);
-
- for (int c = 0; c < channels; c += 4) {
- auto& oscillator = oscillators[c / 4];
- oscillator.channels = std::min(channels - c, 4);
- // removed
- oscillator.analog = true;
- oscillator.soft = soft;
-
- // Get frequency
- float_4 pitch = freqParam + inputs[PITCH_INPUT].getPolyVoltageSimd<float_4>(c);
- float_4 freq;
- if (!linear) {
- pitch += inputs[FM_INPUT].getPolyVoltageSimd<float_4>(c) * fmParam;
- freq = dsp::FREQ_C4 * dsp::exp2_taylor5(pitch);
- }
- else {
- freq = dsp::FREQ_C4 * dsp::exp2_taylor5(pitch);
- freq += dsp::FREQ_C4 * inputs[FM_INPUT].getPolyVoltageSimd<float_4>(c) * fmParam;
- }
- freq = clamp(freq, 0.f, args.sampleRate / 2.f);
- oscillator.freq = freq;
-
- // Get pulse width
- float_4 pw = pwParam + inputs[PW_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f * pwCvParam;
- oscillator.setPulseWidth(pw);
-
- oscillator.syncEnabled = inputs[SYNC_INPUT].isConnected();
- float_4 sync = inputs[SYNC_INPUT].getPolyVoltageSimd<float_4>(c);
- oscillator.process(args.sampleTime, sync);
-
- // Set output
- if (outputs[SIN_OUTPUT].isConnected())
- outputs[SIN_OUTPUT].setVoltageSimd(5.f * oscillator.sin(), c);
- if (outputs[TRI_OUTPUT].isConnected())
- outputs[TRI_OUTPUT].setVoltageSimd(5.f * oscillator.tri(), c);
- if (outputs[SAW_OUTPUT].isConnected())
- outputs[SAW_OUTPUT].setVoltageSimd(5.f * oscillator.saw(), c);
- if (outputs[SQR_OUTPUT].isConnected())
- outputs[SQR_OUTPUT].setVoltageSimd(5.f * oscillator.sqr(), c);
- }
-
- outputs[SIN_OUTPUT].setChannels(channels);
- outputs[TRI_OUTPUT].setChannels(channels);
- outputs[SAW_OUTPUT].setChannels(channels);
- outputs[SQR_OUTPUT].setChannels(channels);
-
- // Light
- if (lightDivider.process()) {
- if (channels == 1) {
- float lightValue = oscillators[0].light()[0];
- lights[PHASE_LIGHT + 0].setSmoothBrightness(-lightValue, args.sampleTime * lightDivider.getDivision());
- lights[PHASE_LIGHT + 1].setSmoothBrightness(lightValue, args.sampleTime * lightDivider.getDivision());
- lights[PHASE_LIGHT + 2].setBrightness(0.f);
- }
- else {
- lights[PHASE_LIGHT + 0].setBrightness(0.f);
- lights[PHASE_LIGHT + 1].setBrightness(0.f);
- lights[PHASE_LIGHT + 2].setBrightness(1.f);
- }
- lights[LINEAR_LIGHT].setBrightness(linear);
- lights[SOFT_LIGHT].setBrightness(soft);
- }
- }
- };
-
-
- struct VCOWidget : ModuleWidget {
- VCOWidget(VCO* module) {
- setModule(module);
- setPanel(createPanel(asset::plugin(pluginInstance, "res/VCO.svg"), asset::plugin(pluginInstance, "res/VCO-dark.svg")));
-
- addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, 0)));
- addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
- addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
- addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
-
- addParam(createParamCentered<RoundHugeBlackKnob>(mm2px(Vec(22.905, 29.808)), module, VCO::FREQ_PARAM));
- addParam(createParamCentered<RoundLargeBlackKnob>(mm2px(Vec(22.862, 56.388)), module, VCO::PW_PARAM));
- addParam(createParamCentered<Trimpot>(mm2px(Vec(6.607, 80.603)), module, VCO::FM_PARAM));
- addParam(createLightParamCentered<VCVLightLatch<MediumSimpleLight<WhiteLight>>>(mm2px(Vec(17.444, 80.603)), module, VCO::LINEAR_PARAM, VCO::LINEAR_LIGHT));
- addParam(createLightParamCentered<VCVLightLatch<MediumSimpleLight<WhiteLight>>>(mm2px(Vec(28.282, 80.603)), module, VCO::SYNC_PARAM, VCO::SOFT_LIGHT));
- addParam(createParamCentered<Trimpot>(mm2px(Vec(39.118, 80.603)), module, VCO::PW_CV_PARAM));
-
- addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(6.607, 96.859)), module, VCO::FM_INPUT));
- addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(17.444, 96.859)), module, VCO::PITCH_INPUT));
- addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(28.282, 96.859)), module, VCO::SYNC_INPUT));
- addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(39.15, 96.859)), module, VCO::PW_INPUT));
-
- addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(6.607, 113.115)), module, VCO::SIN_OUTPUT));
- addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(17.444, 113.115)), module, VCO::TRI_OUTPUT));
- addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(28.282, 113.115)), module, VCO::SAW_OUTPUT));
- addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(39.119, 113.115)), module, VCO::SQR_OUTPUT));
-
- addChild(createLightCentered<SmallLight<RedGreenBlueLight>>(mm2px(Vec(31.089, 16.428)), module, VCO::PHASE_LIGHT));
- }
- };
-
-
- Model* modelVCO = createModel<VCO, VCOWidget>("VCO");
|