#include "dsp/digital.hpp" #include #include "RJModules.hpp" namespace rack_plugin_RJModules { struct Osc { float phase = 0.0; float pw = 0.5; float freq = 1.0; bool offset = false; bool invert = false; SchmittTrigger resetTrigger; Osc() {} void setPitch(float pitch) { pitch = fminf(pitch, 8.0); freq = powf(2.0, pitch); } void setFreq(float freqi){ freq = freqi; } float getFreq() { return freq; } void setReset(float reset) { if (resetTrigger.process(reset)) { phase = 0.0; } } void step(float dt) { float deltaPhase = fminf(freq * dt, 0.5); phase += deltaPhase; if (phase >= 1.0) phase -= 1.0; } float saw(float x) { return 2.0 * (x - roundf(x)); } float saw() { if (offset) return invert ? 2.0 * (1.0 - phase) : 2.0 * phase; else return saw(phase) * (invert ? -1.0 : 1.0); } float light() { return sinf(2*M_PI * phase); } }; struct ThreeXOSC : Module { enum ParamIds { SHAPE_PARAM_1, ATTACK_PARAM_1, DECAY_PARAM_1, MIX_PARAM_1, SHAPE_PARAM_2, ATTACK_PARAM_2, DECAY_PARAM_2, MIX_PARAM_2, SHAPE_PARAM_3, ATTACK_PARAM_3, DECAY_PARAM_3, MIX_PARAM_3, NUM_PARAMS }; enum InputIds { SHAPE_CV1_1, ATTACK_CV1_1, DECAY_CV1_1, MIX_CV1_1, VOCT_1, SHAPE_CV1_2, ATTACK_CV1_2, DECAY_CV1_2, MIX_CV1_2, VOCT_2, SHAPE_CV1_3, ATTACK_CV1_3, DECAY_CV1_3, MIX_CV1_3, VOCT_3, NUM_INPUTS }; enum OutputIds { OUTPUT_1, OUTPUT_2, OUTPUT_3, OUTPUT_ALL, NUM_OUTPUTS }; enum LightIds { PHASE_POS_LIGHT_1, PHASE_POS_LIGHT_2, PHASE_POS_LIGHT_3, NUM_LIGHTS }; // Pitchies float referenceFrequency = 261.626; // C4; frequency at which Rack 1v/octave CVs are zero. float referenceSemitone = 60.0; // C4; value of C4 in semitones is arbitrary here, so have it match midi note numbers when rounded to integer. float twelfthRootTwo = 1.0594630943592953; float logTwelfthRootTwo = logf(1.0594630943592953); int referencePitch = 0; int referenceOctave = 4; float frequencyToSemitone(float frequency) { return logf(frequency / referenceFrequency) / logTwelfthRootTwo + referenceSemitone; } float semitoneToFrequency(float semitone) { return powf(twelfthRootTwo, semitone - referenceSemitone) * referenceFrequency; } float frequencyToCV(float frequency) { return log2f(frequency / referenceFrequency); } float cvToFrequency(float cv) { return powf(2.0, cv) * referenceFrequency; } float cvToSemitone(float cv) { return frequencyToSemitone(cvToFrequency(cv)); } float semitoneToCV(float semitone) { return frequencyToCV(semitoneToFrequency(semitone)); } Osc osc1; Osc osc2; Osc osc3; float last_1 = 0.0; bool gated1 = false; float DETUNE_STEP = .075; bool decaying1 = false; float env1 = 0.0f; SchmittTrigger trigger1; ThreeXOSC() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {} void step() override; }; void ThreeXOSC::step() { float attack1 = clamp(params[ATTACK_PARAM_1].value + inputs[ATTACK_CV1_1].value / 10.0f, 0.0f, 1.0f); float decay1 = 0.0f; float sustain1 = 1.0f; float release1 = clamp(params[DECAY_PARAM_1].value + inputs[DECAY_CV1_1].value / 10.0f, 0.0f, 1.0f); float osc1_pitch = inputs[VOCT_1].value; osc1.setFreq(cvToFrequency(osc1_pitch)); osc1.step(1.0 / engineGetSampleRate()); // Gate if (last_1 == inputs[VOCT_1].value){ gated1 = false; } else{ gated1 = true; last_1 = inputs[VOCT_1].value; } // ADSR const float base = 20000.0f; const float maxTime = 10.0f; if (gated1) { if (decaying1) { // Decay if (decay1 < 1e-4) { env1 = sustain1; } else { env1 += powf(base, 1 - decay1) / maxTime * (sustain1 - env1) * engineGetSampleTime(); } } else { // Attack // Skip ahead if attack is all the way down (infinitely fast) if (attack1 < 1e-4) { env1 = 1.0f; } else { env1 += powf(base, 1 - attack1) / maxTime * (1.01f - env1) * engineGetSampleTime(); } if (env1 >= 1.0f) { env1 = 1.0f; decaying1 = true; } } } else { // Release if (release1 < 1e-4) { env1 = 0.0f; } else { env1 += powf(base, 1 - release1) / maxTime * (0.0f - env1) * engineGetSampleTime(); } decaying1 = false; } float env_out1 = 10.0f * env1; // Osci float osc_out1 = osc1.saw(); printf("env_out1 %f \n", env_out1); // VCA float cv = 1.f; cv = fmaxf(env_out1 / 10.f, 0.f); cv = powf(cv, 4.f); printf("cv %f \n", cv); printf("output %f\n", osc_out1 * cv); //lastCv = cv; outputs[OUTPUT_1].value = osc_out1 - cv; //outputs[OUTPUT_1].value = osc1.saw() * env_out1; // float root_pitch = params[FREQ_PARAM].value * clamp(inputs[FREQ_CV_INPUT].normalize(10.0f) / 10.0f, 0.0f, 1.0f); // oscillator.setPitch(root_pitch); // oscillator.offset = (params[OFFSET_PARAM].value > 0.0); // oscillator.invert = (params[INVERT_PARAM].value <= 0.0); // oscillator.step(1.0 / engineGetSampleRate()); // oscillator.setReset(inputs[RESET_INPUT].value); // oscillator2.setPitch(root_pitch + (params[DETUNE_PARAM].value * DETUNE_STEP * clamp(inputs[DETUNE_CV_INPUT].normalize(10.0f) / 10.0f, 0.0f, 1.0f))); // oscillator2.offset = (params[OFFSET_PARAM].value > 0.0); // oscillator2.invert = (params[INVERT_PARAM].value <= 0.0); // oscillator2.step(1.0 / engineGetSampleRate()); // oscillator2.setReset(inputs[RESET_INPUT].value); // oscillator3.setPitch(root_pitch - (params[DETUNE_PARAM].value * DETUNE_STEP * clamp(inputs[DETUNE_CV_INPUT].normalize(10.0f) / 10.0f, 0.0f, 1.0f))); // oscillator3.offset = (params[OFFSET_PARAM].value > 0.0); // oscillator3.invert = (params[INVERT_PARAM].value <= 0.0); // oscillator3.step(1.0 / engineGetSampleRate()); // oscillator3.setReset(inputs[RESET_INPUT].value); // float osc3_saw = oscillator3.saw(); // if (params[OFFSET_PARAM].value < 1){ // osc3_saw = 0; // } else{ // osc3_saw = oscillator3.saw(); // } // float mix_percent = params[MIX_PARAM].value * clamp(inputs[MIX_CV_INPUT].normalize(10.0f) / 10.0f, 0.0f, 1.0f); // outputs[SAW_OUTPUT].value = 5.0 * (( oscillator.saw() + (oscillator2.saw() * mix_percent) + (osc3_saw * mix_percent) / 3)); // lights[PHASE_POS_LIGHT].setBrightnessSmooth(fmaxf(0.0, oscillator.light())); // lights[PHASE_NEG_LIGHT].setBrightnessSmooth(fmaxf(0.0, -oscillator.light())); } struct ThreeXOSCWidget: ModuleWidget { ThreeXOSCWidget(ThreeXOSC *module); }; ThreeXOSCWidget::ThreeXOSCWidget(ThreeXOSC *module) : ModuleWidget(module) { box.size = Vec(240, 380); { SVGPanel *panel = new SVGPanel(); panel->box.size = box.size; panel->setBackground(SVG::load(assetPlugin(plugin, "res/ThreeXOSC.svg"))); addChild(panel); } addChild(Widget::create(Vec(15, 0))); addChild(Widget::create(Vec(box.size.x-30, 0))); addChild(Widget::create(Vec(15, 365))); addChild(Widget::create(Vec(box.size.x-30, 365))); int row_base = 30; addParam(ParamWidget::create(Vec(25, row_base), module, ThreeXOSC::SHAPE_PARAM_1, 0.0, 8.0, 5.0)); addParam(ParamWidget::create(Vec(75, row_base), module, ThreeXOSC::ATTACK_PARAM_1, 0.0, 1.0, 0.1)); addParam(ParamWidget::create(Vec(125, row_base), module, ThreeXOSC::DECAY_PARAM_1, 0.0, 1.0, 1.0)); addParam(ParamWidget::create(Vec(175, row_base), module, ThreeXOSC::MIX_PARAM_1, 0.0, 1.0, 1.0)); addInput(Port::create(Vec(30, row_base + 45), Port::INPUT, module, ThreeXOSC::SHAPE_CV1_1)); addInput(Port::create(Vec(80, row_base + 45), Port::INPUT, module, ThreeXOSC::ATTACK_CV1_1)); addInput(Port::create(Vec(130, row_base + 45), Port::INPUT, module, ThreeXOSC::DECAY_CV1_1)); addInput(Port::create(Vec(180, row_base + 45), Port::INPUT, module, ThreeXOSC::MIX_CV1_1)); addInput(Port::create(Vec(30, row_base + 75), Port::INPUT, module, ThreeXOSC::VOCT_1)); addOutput(Port::create(Vec(180, row_base + 75), Port::OUTPUT, module, ThreeXOSC::OUTPUT_1)); addChild(ModuleLightWidget::create>(Vec(210, row_base + 80), module, ThreeXOSC::PHASE_POS_LIGHT_1)); row_base = 140; addParam(ParamWidget::create(Vec(25, row_base), module, ThreeXOSC::SHAPE_PARAM_2, 0.0, 8.0, 5.0)); addParam(ParamWidget::create(Vec(75, row_base), module, ThreeXOSC::ATTACK_PARAM_2, 0.0, 1.0, 0.1)); addParam(ParamWidget::create(Vec(125, row_base), module, ThreeXOSC::DECAY_PARAM_2, 0.0, 1.0, 1.0)); addParam(ParamWidget::create(Vec(175, row_base), module, ThreeXOSC::MIX_PARAM_2, 0.0, 1.0, 1.0)); addInput(Port::create(Vec(30, row_base + 45), Port::INPUT, module, ThreeXOSC::SHAPE_CV1_2)); addInput(Port::create(Vec(80, row_base + 45), Port::INPUT, module, ThreeXOSC::ATTACK_CV1_2)); addInput(Port::create(Vec(130, row_base + 45), Port::INPUT, module, ThreeXOSC::DECAY_CV1_2)); addInput(Port::create(Vec(180, row_base + 45), Port::INPUT, module, ThreeXOSC::MIX_CV1_2)); addInput(Port::create(Vec(30, row_base + 75), Port::INPUT, module, ThreeXOSC::VOCT_2)); addOutput(Port::create(Vec(180, row_base + 75), Port::OUTPUT, module, ThreeXOSC::OUTPUT_2)); addChild(ModuleLightWidget::create>(Vec(210, row_base + 80), module, ThreeXOSC::PHASE_POS_LIGHT_2)); row_base = 250; addParam(ParamWidget::create(Vec(25, row_base), module, ThreeXOSC::SHAPE_PARAM_3, 0.0, 8.0, 5.0)); addParam(ParamWidget::create(Vec(75, row_base), module, ThreeXOSC::ATTACK_PARAM_3, 0.0, 1.0, 0.1)); addParam(ParamWidget::create(Vec(125, row_base), module, ThreeXOSC::DECAY_PARAM_3, 0.0, 1.0, 1.0)); addParam(ParamWidget::create(Vec(175, row_base), module, ThreeXOSC::MIX_PARAM_3, 0.0, 1.0, 1.0)); addInput(Port::create(Vec(30, row_base + 45), Port::INPUT, module, ThreeXOSC::SHAPE_CV1_3)); addInput(Port::create(Vec(80, row_base + 45), Port::INPUT, module, ThreeXOSC::ATTACK_CV1_3)); addInput(Port::create(Vec(130, row_base + 45), Port::INPUT, module, ThreeXOSC::DECAY_CV1_3)); addInput(Port::create(Vec(180, row_base + 45), Port::INPUT, module, ThreeXOSC::MIX_CV1_3)); addInput(Port::create(Vec(30, row_base + 75), Port::INPUT, module, ThreeXOSC::VOCT_3)); addOutput(Port::create(Vec(180, row_base + 75), Port::OUTPUT, module, ThreeXOSC::OUTPUT_3)); // addChild(ModuleLightWidget::create>(Vec(210, row_base + 80), module, ThreeXOSC::PHASE_POS_LIGHT_3)); // addInput(Port::create(Vec(22, 100), Port::INPUT, module, ThreeXOSC::FREQ_CV_INPUT)); // addInput(Port::create(Vec(22, 190), Port::INPUT, module, ThreeXOSC::DETUNE_CV_INPUT)); // addInput(Port::create(Vec(22, 270), Port::INPUT, module, ThreeXOSC::MIX_CV_INPUT)); // addInput(Port::create(Vec(38, 310), Port::INPUT, module, ThreeXOSC::RESET_INPUT)); // addOutput(Port::create(Vec(100, 310), Port::OUTPUT, module, ThreeXOSC::SAW_OUTPUT)); } } // namespace rack_plugin_RJModules using namespace rack_plugin_RJModules; RACK_PLUGIN_MODEL_INIT(RJModules, ThreeXOSC) { Model *modelThreeXOSC = Model::create("RJModules", "ThreeXOSC", "[GEN] 3xOSC", LFO_TAG); return modelThreeXOSC; }