Help Request - New to Development for Rack

Greetings,

I am new to development in rack. I have a background in SuperCollider and Swift, but not too much in C++ or DSP for that matter but I’ve been working through some resources on my own and trying to see if I could Frankenstein some code together to make a module.

I cannot figure out why my module is not working and I would really appreciate it if anyone might have some time to shed a little light on it.

I used some of Andrew’s fundamental code and borrowed some from some other libraries, but my module is only outputting a constant 1V right now and I don’t know why.

Any help would be greatly appreciated! I’m trying my best to learn and I hate asking for this kind of thing. The end goal is making a variable feedback sine oscillator but I’m just making one without right now. I can easily write some code for the feedback portion, just sending it to the phase and limiting it to pi/2.

#include “plugin.hpp”

using simd::float_4;

template <int OVERSAMPLE, int QUALITY, typename T> struct VoltageControlledOscillator {

int channels = 0;

T phase = 0.f;
T freq = 0.f;

dsp::MinBlepGenerator<QUALITY, OVERSAMPLE, T> sinMinBlep;

T sinValue = 0.f;

void process(float deltaTime, T) {
    // Advance phase
    T deltaPhase = simd::clamp(freq * deltaTime, 0.f, 0.35f);
    phase += deltaPhase;
    // Wrap phase
    phase -= simd::floor(phase);
    sinValue = sin(phase);
    sinValue += sinMinBlep.process();
}

T sin(T phase) {
    T v;
    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);
    return v;
}

T sin() {
    return sinValue;
}

T light() {
    return simd::sin(2 * T(M_PI) * phase);
}

};

struct Boomerang : Module { enum ParamId { FREQ_PARAM, FEEDBACK_PARAM, FEEDBACK_CV_PARAM, FM_PARAM, PARAMS_LEN }; enum InputId { FM_INPUT, FB_INPUT, VOCT_INPUT, INPUTS_LEN }; enum OutputId { SIN_OUTPUT, OUTPUTS_LEN }; enum LightId { ENUMS(PHASE_LIGHT, 3), LIGHTS_LEN };

VoltageControlledOscillator<16, 16, float_4> oscillators[1];
dsp::ClockDivider lightDivider;

Boomerang() {
    config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN);
    configParam(FREQ_PARAM, -54.f, 54.f, 0.f, "Frequency", " Hz", dsp::FREQ_SEMITONE, dsp::FREQ_C4);
    configParam(FEEDBACK_PARAM, 0.f, 1.f, 0.f, "Feedback", "%", 0.f, 100.f);
    configParam(FEEDBACK_CV_PARAM, 0.f, 1.f, 0.f, "Feedback CV depth", "%", 0.f, 100.f);
    configParam(FM_PARAM, 0.f, 1.f, 0.f, "Frequency modulation", "%", 0.f, 100.f);
    configInput(FM_INPUT, "Frequency modulation");
    configInput(FB_INPUT, "Feedback");
    configInput(VOCT_INPUT, "1V/oct");
    configOutput(SIN_OUTPUT, "Signal");
    
    lightDivider.setDivision(16);
}

void process(const ProcessArgs& args) override {
    float freqParam = params[FREQ_PARAM].getValue() / 12.f;
    float fmParam = params[FM_PARAM].getValue();
    
    int channels = std::max(inputs[VOCT_INPUT].getChannels(), 1);
    
    for (int c = 0; c < channels; c += 1) {
        auto& oscillator = oscillators[c / 1];
        oscillator.channels = std::min(channels - c, 1);
        
        //get frequency
        float_4 pitch = freqParam + inputs[VOCT_INPUT].getPolyVoltageSimd<float_4>(c);
        float_4 freq;
        pitch += inputs[FM_INPUT].getPolyVoltageSimd<float_4>(c) * fmParam;
        freq = dsp::FREQ_C4 * dsp::approxExp2_taylor5(pitch + 30.f) / std::pow(2.f, 30.f);
        freq = clamp(freq, 0.f, args.sampleRate / 2.f);
        oscillator.freq = freq;
        
        if (outputs[SIN_OUTPUT].isConnected())
            outputs[SIN_OUTPUT].setVoltageSimd(5.f * oscillator.sin(), c);
    }
    
    outputs[SIN_OUTPUT].setVoltage(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);
        }
    }
}

};

struct BoomerangWidget : ModuleWidget { BoomerangWidget(Boomerang* module) { setModule(module); setPanel(createPanel(asset::plugin(pluginInstance, “res/Boomerang.svg”)));

    addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
    addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
    addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
    addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
    
    //defaults not working, check coordinates in illustrator and use mm
    
    addParam(createParamCentered<RoundHugeBlackKnob>(mm2px(Vec(15.24, 35.122)), module, Boomerang::FREQ_PARAM));
    addParam(createParamCentered<RoundLargeBlackKnob>(mm2px(Vec(15.24, 61.912)), module, Boomerang::FEEDBACK_PARAM));
    addParam(createParamCentered<Trimpot>(mm2px(Vec(21.943, 81.86)), module, Boomerang::FEEDBACK_CV_PARAM));
    addParam(createParamCentered<Trimpot>(mm2px(Vec(8.537, 81.86)), module, Boomerang::FM_PARAM));
    
    addInput(createInputCentered<PJ301MPort>(mm2px(Vec(8.537, 94.918)), module, Boomerang::FM_INPUT));
    addInput(createInputCentered<PJ301MPort>(mm2px(Vec(21.943, 94.918)), module, Boomerang::FB_INPUT));
    addInput(createInputCentered<PJ301MPort>(mm2px(Vec(8.537, 108.265)), module, Boomerang::VOCT_INPUT));
    
    addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(21.943, 108.265)), module, Boomerang::SIN_OUTPUT));
    
    addChild(createLightCentered<MediumLight<WhiteLight>>(mm2px(Vec(15.24, 18.754)), module, Boomerang::PHASE_LIGHT));
}

};

Model* modelBoomerang = createModel<Boomerang, BoomerangWidget>(“Boomerang”);

You are setting the output voltage to 1 via the channels variable.

2 Likes

Got it! Thanks for pointing me in the right direction!

1 Like

Ok once again apologies in advance for my ignorance. I changed this to

outputs[SIN_OUTPUT].setChannel(channels);

and I get an output of 0V. I’m guessing something I’m doing with managing polyphony is wrong and whatever I should have the output set to is either wrong or missing.

Andrew had

for (int c = 0; c < channels; c += 4) { auto& oscillator = oscillators[c / 4]; oscillator.channels = std::min(channels - c, 4);

which I assumed was due to the fact that there are four separate outputs on the fundamental VCO so I would need to change 4 to 1.

My understanding is that my signal output should be here:

    if (outputs[SIN_OUTPUT].isConnected())
        outputs[SIN_OUTPUT].setVoltageSimd(5.f * oscillator.sin(), c);

Could anyone kindly shed some light on where I’m going wrong here? I’m trying my best to put this together based on the knowledge of other languages I have but I am not sure where I’ve gone wrong. I’ve put the fundamental VCO and mine side by side many times and compared the bracket levels but I can’t seem to find what the inconsistency is.

Thanks in advance!

The loop in the fundamental code is not for separate outputs, it’s for separate channels in one polyphonic output.

1 Like

Andrew has 4 there, not because there are 4 outputs, but because the oscillator can process 4 polyphonic channels at a time.

To do the work for all 16 polyphonic channels that might be engaged, he has an array of 4 oscillators.

setVoltageSimd sets the the channels c, c+1, c+2 and c+3 to the 4 values returned from the oscillator.sin function (multiplied by 5.f).

Gotcha! Thank you! I expect this should help me get everything up and running. I appreciate it very much

I really appreciate you guys! I’ve been referencing your code as well as David’s to make some other things or just try to figure out how everything is put together.

1 Like