#include "LabSeven.hpp" #include "LabSeven_3340_VCO.h" #include #include using namespace std; //TODO: //fine tune vco parameters to match my own synth namespace rack_plugin_LabSeven { struct LS3340VCO : Module { enum ParamIds { PARAM_MOD, PARAM_RANGE, PARAM_PULSEWIDTH, PARAM_PWMSOURCE, PARAM_VOLSQUARE, PARAM_VOLSAW, PARAM_VOLTRIANGLE, PARAM_VOLSUBOSC, PARAM_SUBOSCRATIO, PARAM_VOLNOISE, NUM_PARAMS }; enum InputIds { IN_PITCH, IN_MOD, IN_RANGE, IN_LFO, IN_ENV, IN_SUBOSCSELECT, NUM_INPUTS }; enum OutputIds { OUT_SQUARE, OUT_SAW, OUT_SUB, OUT_TRIANGLE, OUT_MIX, OUT_NOISE, NUM_OUTPUTS }; enum LightIds { NUM_LIGHTS }; //VCO instance and VCO output frame LabSeven::LS3340::TLS3340VCO vco; LabSeven::LS3340::TLS3340VCOFrame nextFrame; LS3340VCO() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {} void step() override; float sampleTimeCurrent = 0.0; float sampleRateCurrent = 0.0; double pitch,maxPitch,rangeFactor; // For more advanced Module features, read Rack's engine.hpp header file // - toJson, fromJson: serialization of internal data // - onSampleRateChange: event triggered by a change of sample rate // - onReset, onRandomize, onCreate, onDelete: implements special behavior when user clicks these from the context menu }; void LS3340VCO::step() { //update external sample rate if neccessary if (sampleTimeCurrent != engineGetSampleTime()) { sampleTimeCurrent = engineGetSampleTime(); sampleRateCurrent = 1.0/sampleTimeCurrent; vco.setSamplerateExternal(sampleRateCurrent); maxPitch = sampleRateCurrent*0.45; if (maxPitch > 40000) maxPitch = 40000; //high value so that suboscillator can go up to 10kHz } //get pitch and pitch mod input pitch = inputs[IN_PITCH].value; pitch += pow(2,2.25*0.2*inputs[IN_MOD].value * params[PARAM_MOD].value); //set rangeFactor rangeFactor = params[PARAM_RANGE].value; switch ((int)rangeFactor) { case 0: rangeFactor = 0.5; break; case 1: rangeFactor = 1.0; break; case 2: rangeFactor = 2.0; break; case 3: rangeFactor = 4.0; break; default: rangeFactor = 1.0; } //range modulation if (inputs[IN_RANGE].active) { int rangeSelect = (int)round(inputs[IN_RANGE].value*0.6); switch (rangeSelect) { case -3: rangeFactor /= 8.0; break; case -2: rangeFactor /= 4.0; break; case -1: rangeFactor /= 2.0; break; case 0: break; //no change case 1: rangeFactor *= 2.0; break; case 2: rangeFactor *= 4.0; break; case 3: rangeFactor *= 8.0; break; } if (rangeFactor > 16.0) rangeFactor = 16.0; } //set pitch //TODO: Clean up this paragraph!!! pitch = 261.626f * pow(2.0, pitch) * rangeFactor; pitch = clamp(pitch, 0.01f, maxPitch); //simulate the jitter observed in the hardware synth //use values > 0.02 for dirtier sound pitch *= 1.0+0.02*((double) rand() / (RAND_MAX)-0.5); vco.setFrequency(pitch); //update suboscillator switch((int)inputs[IN_SUBOSCSELECT].value) { case 1: vco.setSuboscillatorMode(0); break; case 2: vco.setSuboscillatorMode(1); break; case 3: vco.setSuboscillatorMode(2); break; default: vco.setSuboscillatorMode((unsigned short)params[PARAM_SUBOSCRATIO].value); } //pulse width modulation switch ((int)params[PARAM_PWMSOURCE].value) { //LFO PWM requires values between -0.4 and 0.4; SH does PWM between 10% and 90% pulse width case 2: vco.setPwmCoefficient(-2.0*params[PARAM_PULSEWIDTH].value*0.4*0.2*inputs[IN_LFO].value); break; //bipolar, -5V - +5V case 1: vco.setPwmCoefficient(-0.8*params[PARAM_PULSEWIDTH].value); break; case 0: vco.setPwmCoefficient(-2.0*params[PARAM_PULSEWIDTH].value*0.4*0.1*inputs[IN_ENV].value); break; //unipolar, 0V - +10v default: vco.setPwmCoefficient(-0.8*params[PARAM_PULSEWIDTH].value); } //get next frame and put it out double scaling = 8.0; //TODO: PROPER (FREQUENCY DEPENDENT) AMPLITUDE SCALING FOR SAW AND TRIANGLE //TODO: PWM FOR TRIANGLE //calculate next frame if (this->sampleRateCurrent != 192000) { //TODO: Add a 'standard/high' quality switch to GUI //and choose interpolation method accordingly if (true) { vco.getNextFrameAtExternalSampleRateSinc(&nextFrame); } else { //currently next neighbour interpolation, not used! //TODO: Add quality switch (low/medium/high) to select //nn (has its own sound), cubic or sinc interpolation vco.getNextFrameAtExternalSampleRateCubic(&nextFrame); } } else //no interpolation required if internal sample rate == external sample rate == 192kHz { vco.getNextBlock(&nextFrame,1); } //TODO: Activate/deactivate interpolation if outs are not active outputs[OUT_SQUARE].value = scaling * nextFrame.square; outputs[OUT_SAW].value = scaling * nextFrame.sawtooth; outputs[OUT_SUB].value = scaling * nextFrame.subosc; outputs[OUT_TRIANGLE].value = scaling * nextFrame.triangle; outputs[OUT_NOISE].value = 6.0* nextFrame.noise; outputs[OUT_MIX].value = 0.4*(outputs[OUT_SQUARE].value * params[PARAM_VOLSQUARE].value + outputs[OUT_SAW].value * params[PARAM_VOLSAW].value + outputs[OUT_SUB].value * params[PARAM_VOLSUBOSC].value + outputs[OUT_TRIANGLE].value * params[PARAM_VOLTRIANGLE].value + outputs[OUT_NOISE].value * params[PARAM_VOLNOISE].value); } struct LS3340VCOWidget : ModuleWidget { LS3340VCOWidget(LS3340VCO *module) : ModuleWidget(module) { srand(time(0)); //BACKGROUND setPanel(SVG::load(assetPlugin(plugin, "res/LabSeven_3340_VCO.svg"))); //SCREWS addChild(Widget::create(Vec(RACK_GRID_WIDTH, 0))); addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); addChild(Widget::create(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); //INPUTS addInput(Port::create(Vec(114, 380-24-231+2), Port::INPUT, module, LS3340VCO::IN_PITCH)); addInput(Port::create(Vec( 75, 380-24-231+2), Port::INPUT, module, LS3340VCO::IN_MOD)); addInput(Port::create(Vec(114, 380-24-276+2), Port::INPUT, module, LS3340VCO::IN_RANGE)); addInput(Port::create(Vec(219, 380-24-284+2), Port::INPUT, module, LS3340VCO::IN_LFO)); addInput(Port::create(Vec(219, 380-24-214+2), Port::INPUT, module, LS3340VCO::IN_ENV)); addInput(Port::create(Vec(153, 380-24- 32+2), Port::INPUT, module, LS3340VCO::IN_SUBOSCSELECT)); //VCO SECTION addParam(ParamWidget::create(Vec(28-4, 380-24-272), module, LS3340VCO::PARAM_MOD, 0.0f, 1.0f, 0.0f)); addParam(ParamWidget::create(Vec(69, 380-36-266), module, LS3340VCO::PARAM_RANGE, 0.0, 3.0, 1.0)); addParam(ParamWidget::create(Vec(164-4, 380-24-272), module, LS3340VCO::PARAM_PULSEWIDTH, 0.0f, 0.5f, 0.0f)); addParam(ParamWidget::create(Vec(201-4, 380-40-234), module, LS3340VCO::PARAM_PWMSOURCE, 0.0f, 2.0f, 1.0f)); //SOURCE MIXER SECTION addParam(ParamWidget::create(Vec( 28-4, 380-20-4-125), module, LS3340VCO::PARAM_VOLSQUARE, 0.0f, 1.0f, 0.0f)); addParam(ParamWidget::create(Vec( 59-4, 380-20-4-125), module, LS3340VCO::PARAM_VOLSAW, 0.0f, 1.0f, 0.0f)); addParam(ParamWidget::create(Vec( 90-4, 380-20-4-125), module, LS3340VCO::PARAM_VOLTRIANGLE, 0.0f, 1.0f, 0.0f)); addParam(ParamWidget::create(Vec(121-4, 380-20-4-125), module, LS3340VCO::PARAM_VOLSUBOSC, 0.0f, 1.0f, 0.0f)); addParam(ParamWidget::create(Vec(158-4-1, 380-40-88), module, LS3340VCO::PARAM_SUBOSCRATIO, 2.0f, 0.0f, 2.0f)); addParam(ParamWidget::create(Vec(213-4, 380-20-4-125), module, LS3340VCO::PARAM_VOLNOISE, 0.0f, 1.0f, 0.0f)); //OUTPUTS addOutput(Port::create(Vec( 24, 380-30-24), Port::OUTPUT, module, LS3340VCO::OUT_SQUARE)); addOutput(Port::create(Vec( 55, 380-30-24), Port::OUTPUT, module, LS3340VCO::OUT_SAW)); addOutput(Port::create(Vec(117, 380-30-24), Port::OUTPUT, module, LS3340VCO::OUT_SUB)); addOutput(Port::create(Vec( 86, 380-30-24), Port::OUTPUT, module, LS3340VCO::OUT_TRIANGLE)); addOutput(Port::create(Vec(181, 380-30-24), Port::OUTPUT, module, LS3340VCO::OUT_MIX)); addOutput(Port::create(Vec(208, 380-30-24), Port::OUTPUT, module, LS3340VCO::OUT_NOISE)); } }; } // namespace rack_plugin_LabSeven using namespace rack_plugin_LabSeven; RACK_PLUGIN_MODEL_INIT(LabSeven, LS3340VCO) { Model *modelLS3340VCO = Model::create("LabSeven", "LS3340VCO", "LS3340-VCO", OSCILLATOR_TAG); return modelLS3340VCO; }