├── .gitignore ├── LICENSE.txt ├── Makefile ├── README.md ├── m ├── plugin.json ├── res ├── LabSeven_3340_Classic_Skins │ └── LabSeven_3340_VCO.svg ├── LabSeven_3340_KnobLarge.svg ├── LabSeven_3340_SlidePot.svg ├── LabSeven_3340_SlidePot3Stage.svg ├── LabSeven_3340_SlidePotHandleGreen.svg ├── LabSeven_3340_SlidePotHandleRed.svg ├── LabSeven_3340_SlidePotHandleYellow.svg ├── LabSeven_3340_Standard_Skins_blue │ └── LabSeven_3340_VCO.svg ├── LabSeven_3340_noise.pcm └── LabSeven_Port.svg └── src ├── LS3340VCO.cpp ├── LabSeven.cpp ├── LabSeven.hpp └── LabSeven_3340_VCO.h /.gitignore: -------------------------------------------------------------------------------- 1 | LabSevenModules.* 2 | *.so 3 | /build/* 4 | /dist/* 5 | /devstuff/* 6 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | *** 2 | The LabSeven plugin for VCV Rack is available for free and open source. 3 | If you like it, please consider a donation (https://paypal.me/labseven). 4 | Any amount is greatly appreciated. Thanks! 5 | *** 6 | 7 | The LabSeven plugin is licensed under the 8 | Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) 9 | license, referred to as "the license" see 10 | https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode 11 | and 12 | https://creativecommons.org/licenses/by-nc-sa/4.0/ 13 | 14 | Attribution: 15 | 16 | In case an attribution is required according to the license, please mention the relevant 17 | parts of the LabSeven plugin used by you (what did you take/use?), my name (Gernot Wurst) 18 | and the LabSeven URL http://labseven.prospectum.com. 19 | 20 | Exceptions: 21 | 22 | Exceptions from the restrictions of the license, e.g. commercial use of the plugin or its components, 23 | may be negotiated individually. Please contact me. 24 | 25 | 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 32 | SOFTWARE. 33 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # If RACK_DIR is not defined when calling the Makefile, default to two directories above 2 | RACK_DIR ?= ../.. 3 | 4 | # FLAGS will be passed to both the C and C++ compiler 5 | FLAGS += 6 | CFLAGS += 7 | CXXFLAGS += 8 | 9 | # Careful about linking to shared libraries, since you can't assume much about the user's environment and library search path. 10 | # Static libraries are fine. 11 | LDFLAGS += 12 | 13 | # Add .cpp and .c files to the build 14 | SOURCES += $(wildcard src/*.cpp) 15 | 16 | # Add files to the ZIP package when running `make dist` 17 | # The compiled plugin is automatically added. 18 | DISTRIBUTABLES += $(wildcard LICENSE*) res 19 | 20 | # Include the VCV Rack plugin Makefile framework 21 | include $(RACK_DIR)/plugin.mk 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # LabSeven modules for VCV Rack 3 | 4 | LabSeven modules is a collection of modules for [VCV Rack](https://vcvrack.com/) by Gernot Wurst, [labseven.prospectum.com](http://labseven.prospectum.com/). 5 | 6 | See LICENSE.txt for license information. 7 | 8 | ### Compatibility 9 | 10 | LabSeven modules are compatible with VCV Rack 1.X releases. 11 | 12 | If you like the modules you can support the development by making a donation, it will be appreciated!. Here's the link: [DONATE](https://www.paypal.me/labseven/) 13 | 14 | 15 | 16 | # LabSeven modules 17 | 18 | ### VCO-3340 Oscillator 19 | The first Lab Seven module: An extended version of a classical VCO from a well-known 80s synthesizer. Comes with standard blue and 'classic gray' skins (simply rename svg files to change skins). 20 | 21 | User interface features 22 | 23 | RANGE 24 | RANGE knob: Selects the pitch of the entire VCO in octave steps 25 | RANGE Input: Extends the range of the knob by +- 3 octaves from 128' to an upper limit of 0.5'. 26 | -3V: -3 octaves 27 | -2V: -2 octaves 28 | -1V: -1 octave 29 | 0V: No change 30 | +1V: +1 octave 31 | +2V: +2 octaves 32 | +3V: +3 octaves 33 | Comment: The idea is to equip another module with a (-3, -2, L, M, H, +2, +3) switch or modulate the range input for arpeggiator-like effects. 34 | Comment: Maximum pitch is limited to 45% of the current sample rate (example: 44.1kHz -> 19,8kHz) with a hard threshold of 40kHz. 35 | MOD 36 | MOD fader: Selects the amount of pitch modulation. 37 | MOD Input: +- 5V -> +- 2.25 octaves of pitch modulation (like the original synth) 38 | V/OCT: Determines the pitch of the note to be played. 39 | PWM (pulse width modulation of the square waveform) 40 | PULSE WIDTH fader: Scales the amount of PWM. The effect depends on the selected PWM source. 41 | PULSE WIDTH selection 42 | LFO: Input +- 5V -> 10% to 90% pulse width 43 | MAN: Manually sets pulse width, ranging from 50% to 90% 44 | ENV: Input 0V to +10V -> 50% to 90% pulse width 45 | Source mixer 46 | Waveforms: Square (with PWM), sawtooth, triangle, sub oscillator (3 modes), coloured noise 47 | Individual waveform outputs are unattenuated. Faders only affect the MIX output. 48 | SUB OSC mode selection (SEL) input options 49 | 0V: Manual selection 50 | +1V: 1OCT DOWN 51 | +2V: 2OCT DOWN 52 | +3V: 2OCT DOWN with 25% pulse 53 | 54 | Algorithmic (internal) features 55 | 56 | Internal resolution: 192kHz 57 | VCO algorithm: BLIT (band limited impulse train) + leaky integrator 58 | Impulse: Original 3340 impulse reconstructed by Blackman-Harris kernel + leaky integration 59 | Coloured noise: High quality sample loop @192kHz 60 | Resampling/anti-aliasing: Windowed sinc interpolation 61 | 62 | # Version history 63 | 64 | ### Version 1.1.6.1 (2020-01-16) 65 | V1 Migration 66 | 67 | ### Version 0.6.1 (2019-02-08) 68 | inital release 69 | 70 | ### Enjoy! 71 | -------------------------------------------------------------------------------- /m: -------------------------------------------------------------------------------- 1 | make 2 | cd .. 3 | cd .. 4 | make run 5 | cd plugins 6 | cd LabSeven -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug": "LabSeven", 3 | "name": "LabSeven", 4 | "version": "1.0.0", 5 | "license": "CC-BY-NC-SA-4.0", 6 | "brand": "LabSeven", 7 | "author": "Gernot Wurst", 8 | "authorEmail": "highcarbschwabe@prospectum.com", 9 | "authorUrl": "", 10 | "pluginUrl": "http://labseven.prospectum.com/", 11 | "manualUrl": "http://labseven.prospectum.com/", 12 | "sourceUrl": "https://github.com/LabSevenDevVCVRack/LabSeven_VCVRack_modules.git", 13 | "donateUrl": "https://paypal.me/labseven", 14 | "changelogUrl": "", 15 | "modules": [ 16 | { 17 | "slug": "LS3340VCO", 18 | "name": "LS3340-VCO", 19 | "description": "", 20 | "tags": [ 21 | "Oscillator" 22 | ] 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /res/LabSeven_3340_KnobLarge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 57 | 60 | 72 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /res/LabSeven_3340_SlidePot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 57 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /res/LabSeven_3340_SlidePot3Stage.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 57 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /res/LabSeven_3340_SlidePotHandleGreen.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 57 | 60 | 69 | 78 | 83 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /res/LabSeven_3340_SlidePotHandleRed.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 57 | 60 | 69 | 78 | 83 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /res/LabSeven_3340_SlidePotHandleYellow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 57 | 60 | 69 | 78 | 83 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /res/LabSeven_3340_noise.pcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LabSevenDevVCVRack/LabSeven_VCVRack_modules/b74c73a8d278e6f3da39e9a2ab6176ba5fbb2c6a/res/LabSeven_3340_noise.pcm -------------------------------------------------------------------------------- /res/LabSeven_Port.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 40 | 43 | 44 | 46 | 47 | 49 | image/svg+xml 50 | 52 | 53 | 54 | 55 | 56 | 61 | 77 | 80 | 85 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/LS3340VCO.cpp: -------------------------------------------------------------------------------- 1 | #include "LabSeven.hpp" 2 | 3 | #include "LabSeven_3340_VCO.h" 4 | #include 5 | 6 | #include 7 | using namespace std; 8 | 9 | //TODO: 10 | //fine tune vco parameters to match my own synth 11 | 12 | struct LS3340VCO : Module 13 | { 14 | enum ParamIds 15 | { 16 | PARAM_MOD, 17 | PARAM_RANGE, 18 | PARAM_PULSEWIDTH, 19 | PARAM_PWMSOURCE, 20 | PARAM_VOLSQUARE, 21 | PARAM_VOLSAW, 22 | PARAM_VOLTRIANGLE, 23 | PARAM_VOLSUBOSC, 24 | PARAM_SUBOSCRATIO, 25 | PARAM_VOLNOISE, 26 | NUM_PARAMS 27 | }; 28 | enum InputIds 29 | { 30 | IN_PITCH, 31 | IN_MOD, 32 | IN_RANGE, 33 | IN_LFO, 34 | IN_ENV, 35 | IN_SUBOSCSELECT, 36 | NUM_INPUTS 37 | }; 38 | enum OutputIds 39 | { 40 | OUT_SQUARE, 41 | OUT_SAW, 42 | OUT_SUB, 43 | OUT_TRIANGLE, 44 | OUT_MIX, 45 | OUT_NOISE, 46 | NUM_OUTPUTS 47 | }; 48 | enum LightIds 49 | { 50 | NUM_LIGHTS 51 | }; 52 | 53 | //VCO instance and VCO output frame 54 | LabSeven::LS3340::TLS3340VCO vco; 55 | LabSeven::LS3340::TLS3340VCOFrame nextFrame; 56 | 57 | // Panel Theme 58 | int Theme = 0; 59 | 60 | LS3340VCO() { 61 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 62 | configParam(LS3340VCO::PARAM_MOD, 0.0f, 1.0f, 0.0f, ""); 63 | configParam(LS3340VCO::PARAM_RANGE, 0.0, 3.0, 1.0, ""); 64 | configParam(LS3340VCO::PARAM_PULSEWIDTH, 0.0f, 0.5f, 0.0f, ""); 65 | configParam(LS3340VCO::PARAM_PWMSOURCE, 0.0f, 2.0f, 1.0f, ""); 66 | configParam(LS3340VCO::PARAM_VOLSQUARE, 0.0f, 1.0f, 0.0f, ""); 67 | configParam(LS3340VCO::PARAM_VOLSAW, 0.0f, 1.0f, 0.0f, ""); 68 | configParam(LS3340VCO::PARAM_VOLTRIANGLE, 0.0f, 1.0f, 0.0f, ""); 69 | configParam(LS3340VCO::PARAM_VOLSUBOSC, 0.0f, 1.0f, 0.0f, ""); 70 | configParam(LS3340VCO::PARAM_SUBOSCRATIO, 0.0f, 2.0f, 2.0f, ""); 71 | configParam(LS3340VCO::PARAM_VOLNOISE, 0.0f, 1.0f, 0.0f, ""); 72 | srand(time(0)); 73 | } 74 | void process(const ProcessArgs& args) override; 75 | 76 | float sampleTimeCurrent = 0.0; 77 | float sampleRateCurrent = 0.0; 78 | 79 | double pitch,maxPitch,rangeFactor; 80 | // For more advanced Module features, read Rack's engine.hpp header file 81 | // - dataToJson, dataFromJson: serialization of internal data 82 | // - onSampleRateChange: event triggered by a change of sample rate 83 | // - onReset, onRandomize, onCreate, onDelete: implements special behavior when user clicks these from the context menu 84 | }; 85 | 86 | 87 | void LS3340VCO::process(const ProcessArgs& args) 88 | { 89 | //update external sample rate if neccessary 90 | if (sampleTimeCurrent != args.sampleTime) 91 | { 92 | sampleTimeCurrent = args.sampleTime; 93 | sampleRateCurrent = 1.0/sampleTimeCurrent; 94 | vco.setSamplerateExternal(sampleRateCurrent); 95 | 96 | maxPitch = sampleRateCurrent*0.45; 97 | if (maxPitch > 40000) maxPitch = 40000; //high value so that suboscillator can go up to 10kHz 98 | } 99 | 100 | //get pitch and pitch mod input 101 | pitch = inputs[IN_PITCH].getVoltage(); 102 | pitch += pow(2,2.25*0.2*inputs[IN_MOD].getVoltage() * params[PARAM_MOD].getValue()); 103 | 104 | //set rangeFactor 105 | rangeFactor = params[PARAM_RANGE].getValue(); 106 | switch ((int)rangeFactor) 107 | { 108 | case 0: rangeFactor = 0.5; break; 109 | case 1: rangeFactor = 1.0; break; 110 | case 2: rangeFactor = 2.0; break; 111 | case 3: rangeFactor = 4.0; break; 112 | default: rangeFactor = 1.0; 113 | } 114 | 115 | //range modulation 116 | if (inputs[IN_RANGE].active) 117 | { 118 | int rangeSelect = (int)round(inputs[IN_RANGE].getVoltage()*0.6); 119 | switch (rangeSelect) 120 | { 121 | case -3: rangeFactor /= 8.0; break; 122 | case -2: rangeFactor /= 4.0; break; 123 | case -1: rangeFactor /= 2.0; break; 124 | case 0: break; //no change 125 | case 1: rangeFactor *= 2.0; break; 126 | case 2: rangeFactor *= 4.0; break; 127 | case 3: rangeFactor *= 8.0; break; 128 | } 129 | if (rangeFactor > 16.0) rangeFactor = 16.0; 130 | } 131 | 132 | //set pitch 133 | //TODO: Clean up this paragraph!!! 134 | pitch = 261.626f * pow(2.0, pitch) * rangeFactor; 135 | pitch = clamp(pitch, 0.01f, maxPitch); 136 | //simulate the jitter observed in the hardware synth 137 | //use values > 0.02 for dirtier sound 138 | pitch *= 1.0+0.02*((double) rand() / (RAND_MAX)-0.5); 139 | vco.setFrequency(pitch); 140 | 141 | 142 | //update suboscillator 143 | switch((int)inputs[IN_SUBOSCSELECT].getVoltage()) 144 | { 145 | case 1: vco.setSuboscillatorMode(0); break; 146 | case 2: vco.setSuboscillatorMode(1); break; 147 | case 3: vco.setSuboscillatorMode(2); break; 148 | default: vco.setSuboscillatorMode(2 - (unsigned short)params[PARAM_SUBOSCRATIO].getValue()); 149 | } 150 | 151 | //pulse width modulation 152 | switch ((int)params[PARAM_PWMSOURCE].getValue()) 153 | { 154 | //LFO PWM requires values between -0.4 and 0.4; SH does PWM between 10% and 90% pulse width 155 | case 2: vco.setPwmCoefficient(-2.0*params[PARAM_PULSEWIDTH].getValue()*0.4*0.2*inputs[IN_LFO].getVoltage()); break; //bipolar, -5V - +5V 156 | case 1: vco.setPwmCoefficient(-0.8*params[PARAM_PULSEWIDTH].getValue()); break; 157 | case 0: vco.setPwmCoefficient(-2.0*params[PARAM_PULSEWIDTH].getValue()*0.4*0.1*inputs[IN_ENV].getVoltage()); break; //unipolar, 0V - +10v 158 | default: vco.setPwmCoefficient(-0.8*params[PARAM_PULSEWIDTH].getValue()); 159 | } 160 | 161 | //get next frame and put it out 162 | double scaling = 8.0; 163 | 164 | //TODO: PROPER (FREQUENCY DEPENDENT) AMPLITUDE SCALING FOR SAW AND TRIANGLE 165 | //TODO: PWM FOR TRIANGLE 166 | 167 | //calculate next frame 168 | 169 | if (this->sampleRateCurrent != 192000) 170 | { 171 | //TODO: Add a 'standard/high' quality switch to GUI 172 | //and choose interpolation method accordingly 173 | if (true) 174 | { 175 | vco.getNextFrameAtExternalSampleRateSinc(&nextFrame); 176 | } 177 | else 178 | { 179 | //currently next neighbour interpolation, not used! 180 | //TODO: Add quality switch (low/medium/high) to select 181 | //nn (has its own sound), dsp::cubic or sinc interpolation 182 | vco.getNextFrameAtExternalSampleRateCubic(&nextFrame); 183 | } 184 | } 185 | else //no interpolation required if internal sample rate == external sample rate == 192kHz 186 | { 187 | vco.getNextBlock(&nextFrame,1); 188 | } 189 | 190 | //TODO: Activate/deactivate interpolation if outs are not active 191 | outputs[OUT_SQUARE].setVoltage(scaling * nextFrame.square); 192 | outputs[OUT_SAW].setVoltage(scaling * nextFrame.sawtooth); 193 | outputs[OUT_SUB].setVoltage(scaling * nextFrame.subosc); 194 | outputs[OUT_TRIANGLE].setVoltage(scaling * nextFrame.triangle); 195 | outputs[OUT_NOISE].setVoltage(6.0* nextFrame.noise); 196 | outputs[OUT_MIX].setVoltage(0.4*(outputs[OUT_SQUARE].getVoltage() * params[PARAM_VOLSQUARE].getValue() + 197 | outputs[OUT_SAW].getVoltage() * params[PARAM_VOLSAW].getValue() + 198 | outputs[OUT_SUB].getVoltage() * params[PARAM_VOLSUBOSC].getValue() + 199 | outputs[OUT_TRIANGLE].getVoltage() * params[PARAM_VOLTRIANGLE].getValue() + 200 | outputs[OUT_NOISE].getVoltage() * params[PARAM_VOLNOISE].getValue())); 201 | } 202 | 203 | struct LS3340VCOClassicMenu : MenuItem { 204 | LS3340VCO *ls3340vco; 205 | void onAction(const event::Action &e) override { 206 | ls3340vco->Theme = 0; 207 | } 208 | void step() override { 209 | rightText = (ls3340vco->Theme == 0) ? "✔" : ""; 210 | MenuItem::step(); 211 | } 212 | }; 213 | 214 | struct LS3340VCOBlueMenu : MenuItem { 215 | LS3340VCO *ls3340vco; 216 | void onAction(const event::Action &e) override { 217 | ls3340vco->Theme = 1; 218 | } 219 | void step() override { 220 | rightText = (ls3340vco->Theme == 1) ? "✔" : ""; 221 | MenuItem::step(); 222 | } 223 | }; 224 | 225 | struct LS3340VCOWidget : ModuleWidget { 226 | SvgPanel *panelClassic; 227 | SvgPanel *panelBlue; 228 | 229 | void appendContextMenu(Menu *menu) override { 230 | LS3340VCO *ls3340vco = dynamic_cast(module); 231 | assert(ls3340vco); 232 | menu->addChild(construct()); 233 | menu->addChild(construct(&MenuLabel::text, "Theme")); 234 | menu->addChild(construct(&LS3340VCOClassicMenu::text, "Classic (default)", &LS3340VCOClassicMenu::ls3340vco, ls3340vco)); 235 | menu->addChild(construct(&LS3340VCOBlueMenu::text, "Blue", &LS3340VCOBlueMenu::ls3340vco, ls3340vco)); 236 | } 237 | 238 | void step() override { 239 | if (module) { 240 | LS3340VCO *ls3340vco = dynamic_cast(module); 241 | assert(ls3340vco); 242 | panelClassic->visible = (ls3340vco->Theme == 0); 243 | panelBlue->visible = (ls3340vco->Theme == 1); 244 | } 245 | ModuleWidget::step(); 246 | } 247 | 248 | LS3340VCOWidget(LS3340VCO *module) { 249 | setModule(module); 250 | box.size = Vec(17 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 251 | 252 | //BACKGROUND 253 | // Classic Theme 254 | panelClassic = new SvgPanel(); 255 | panelClassic->box.size = box.size; 256 | panelClassic->setBackground(APP->window->loadSvg(asset::plugin(pluginInstance, "res/LabSeven_3340_Classic_Skins/LabSeven_3340_VCO.svg"))); 257 | panelClassic->visible = true; 258 | addChild(panelClassic); 259 | // Blue Theme 260 | panelBlue = new SvgPanel(); 261 | panelBlue->box.size = box.size; 262 | panelBlue->setBackground(APP->window->loadSvg(asset::plugin(pluginInstance, "res/LabSeven_3340_Standard_Skins_blue/LabSeven_3340_VCO.svg"))); 263 | panelBlue->visible = false; 264 | addChild(panelBlue); 265 | 266 | //SCREWS 267 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 268 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 269 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 270 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 271 | 272 | //INPUTS 273 | addInput(createInput(Vec(114, 380-24-231+2), module, LS3340VCO::IN_PITCH)); 274 | addInput(createInput(Vec( 75, 380-24-231+2), module, LS3340VCO::IN_MOD)); 275 | addInput(createInput(Vec(114, 380-24-276+2), module, LS3340VCO::IN_RANGE)); 276 | addInput(createInput(Vec(219, 380-24-284+2), module, LS3340VCO::IN_LFO)); 277 | addInput(createInput(Vec(219, 380-24-214+2), module, LS3340VCO::IN_ENV)); 278 | addInput(createInput(Vec(153, 380-24- 32+2), module, LS3340VCO::IN_SUBOSCSELECT)); 279 | 280 | //VCO SECTION 281 | addParam(createParam(Vec(28-4, 380-24-272), module, LS3340VCO::PARAM_MOD)); 282 | addParam(createParam(Vec(69, 380-36-266), module, LS3340VCO::PARAM_RANGE)); 283 | addParam(createParam(Vec(164-4, 380-24-272), module, LS3340VCO::PARAM_PULSEWIDTH)); 284 | addParam(createParam(Vec(201-4, 380-40-234), module, LS3340VCO::PARAM_PWMSOURCE)); 285 | 286 | //SOURCE MIXER SECTION 287 | addParam(createParam(Vec( 28-4, 380-20-4-125), module, LS3340VCO::PARAM_VOLSQUARE)); 288 | addParam(createParam(Vec( 59-4, 380-20-4-125), module, LS3340VCO::PARAM_VOLSAW)); 289 | addParam(createParam(Vec( 90-4, 380-20-4-125), module, LS3340VCO::PARAM_VOLTRIANGLE)); 290 | addParam(createParam(Vec(121-4, 380-20-4-125), module, LS3340VCO::PARAM_VOLSUBOSC)); 291 | addParam(createParam(Vec(158-4-1, 380-40-88), module, LS3340VCO::PARAM_SUBOSCRATIO)); 292 | addParam(createParam(Vec(213-4, 380-20-4-125), module, LS3340VCO::PARAM_VOLNOISE)); 293 | 294 | //OUTPUTS 295 | addOutput(createOutput(Vec( 24, 380-30-24), module, LS3340VCO::OUT_SQUARE)); 296 | addOutput(createOutput(Vec( 55, 380-30-24), module, LS3340VCO::OUT_SAW)); 297 | addOutput(createOutput(Vec(117, 380-30-24), module, LS3340VCO::OUT_SUB)); 298 | addOutput(createOutput(Vec( 86, 380-30-24), module, LS3340VCO::OUT_TRIANGLE)); 299 | addOutput(createOutput(Vec(181, 380-30-24), module, LS3340VCO::OUT_MIX)); 300 | addOutput(createOutput(Vec(208, 380-30-24), module, LS3340VCO::OUT_NOISE)); 301 | } 302 | }; 303 | 304 | 305 | // Specify the Module and ModuleWidget subclass, human-readable 306 | // author name for categorization per plugin, module slug (should never 307 | // change), human-readable module name, and any number of tags 308 | // (found in `include/tags.hpp`) separated by commas. 309 | Model *modelLS3340VCO = createModel("LS3340VCO"); 310 | -------------------------------------------------------------------------------- /src/LabSeven.cpp: -------------------------------------------------------------------------------- 1 | #include "LabSeven.hpp" 2 | 3 | Plugin *pluginInstance; 4 | 5 | void init(Plugin *p) 6 | { 7 | 8 | pluginInstance = p; 9 | 10 | // Add all Models defined throughout the pluginInstance 11 | p->addModel(modelLS3340VCO); 12 | 13 | // Any other pluginInstance initialization may go here. 14 | // As an alternative, consider lazy-loading assets and lookup tables when your module is created to reduce startup times of Rack. 15 | } 16 | -------------------------------------------------------------------------------- /src/LabSeven.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | 3 | #pragma once 4 | 5 | using namespace rack; 6 | 7 | // Forward-declare the Plugin, defined in Template.cpp 8 | extern Plugin *pluginInstance; 9 | 10 | // Forward-declare each Model, defined in each module source file 11 | extern Model *modelLS3340VCO; 12 | 13 | //GUI elements 14 | struct LabSeven_Port : SvgPort 15 | { 16 | LabSeven_Port() 17 | { 18 | setSvg(APP->window->loadSvg(asset::plugin(pluginInstance,"res/LabSeven_Port.svg"))); 19 | } 20 | }; 21 | 22 | struct LabSeven_3340_FaderLarge : SvgSlider 23 | { 24 | LabSeven_3340_FaderLarge() 25 | { 26 | Vec margin = Vec(4, 4); 27 | maxHandlePos = Vec(0, -20).plus(margin); 28 | minHandlePos = Vec(0, 40).plus(margin); 29 | background->svg = APP->window->loadSvg(asset::plugin(pluginInstance,"res/LabSeven_3340_SlidePot.svg")); 30 | background->wrap(); 31 | background->box.pos = margin; 32 | box.size = background->box.size.plus(margin.mult(2)); 33 | } 34 | }; 35 | 36 | struct LabSeven_3340_FaderRedLargeRed : LabSeven_3340_FaderLarge 37 | { 38 | LabSeven_3340_FaderRedLargeRed() 39 | { 40 | handle->svg = APP->window->loadSvg(asset::plugin(pluginInstance,"res/LabSeven_3340_SlidePotHandleRed.svg")); 41 | handle->wrap(); 42 | } 43 | }; 44 | 45 | struct LabSeven_3340_FaderRedLargeGreen : LabSeven_3340_FaderLarge 46 | { 47 | LabSeven_3340_FaderRedLargeGreen() 48 | { 49 | handle->svg = APP->window->loadSvg(asset::plugin(pluginInstance,"res/LabSeven_3340_SlidePotHandleGreen.svg")); 50 | handle->wrap(); 51 | } 52 | }; 53 | 54 | struct LabSeven_3340_FaderRedLargeYellow3Stage : SvgSlider 55 | { 56 | LabSeven_3340_FaderRedLargeYellow3Stage() 57 | { 58 | Vec margin = Vec(4, 4); 59 | maxHandlePos = Vec(0, -20).plus(margin); 60 | minHandlePos = Vec(0, -5).plus(margin); 61 | 62 | background->svg = APP->window->loadSvg(asset::plugin(pluginInstance,"res/LabSeven_3340_SlidePot3Stage.svg")); 63 | background->wrap(); 64 | background->box.pos = margin; 65 | 66 | box.size = background->box.size.plus(margin.mult(2)); 67 | handle->svg = APP->window->loadSvg(asset::plugin(pluginInstance,"res/LabSeven_3340_SlidePotHandleYellow.svg")); 68 | handle->wrap(); 69 | snap=true; 70 | } 71 | }; 72 | 73 | 74 | struct LabSeven_3340_KnobLarge : SvgKnob 75 | { 76 | LabSeven_3340_KnobLarge() 77 | { 78 | box.size = Vec(36, 36); 79 | minAngle = -0.3*M_PI; 80 | maxAngle = 0.3*M_PI; 81 | setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/LabSeven_3340_KnobLarge.svg"))); 82 | snap=true; 83 | } 84 | }; 85 | 86 | struct LabSeven_3340_KnobLargeRange : LabSeven_3340_KnobLarge 87 | { 88 | LabSeven_3340_KnobLargeRange() 89 | { 90 | minAngle = -0.25*M_PI; 91 | maxAngle = 0.27*M_PI; 92 | } 93 | }; 94 | 95 | /* 96 | struct LabSeven_KnobLargeBlue : SVGKnob { 97 | LabSeven_KnobLargeBlue() { 98 | box.size = Vec(50, 50); 99 | minAngle = -0.75*M_PI; 100 | maxAngle = 0.75*M_PI; 101 | setSVG(APP->window->loadSvg(asset::plugin(pluginInstance, "res/LabSeven_KnobLargeBlue.svg"))); 102 | } 103 | }; 104 | */ 105 | -------------------------------------------------------------------------------- /src/LabSeven_3340_VCO.h: -------------------------------------------------------------------------------- 1 | #ifndef LABSEVEN_3340_VCO_H 2 | #define LABSEVEN_3340_VCO_H 3 | 4 | #include 5 | #include 6 | 7 | #define _USE_MATH_DEFINES 8 | using namespace std; 9 | 10 | namespace LabSeven 11 | { 12 | namespace LS3340 13 | { 14 | //Blackman Harris function 15 | inline double wfBlackmanHarris64bit(double nbr_points, double t, double *parameters = NULL) 16 | { 17 | double _2_pi_by_N_1_times_t = (2.0 * M_PI / (nbr_points - 1))*t; 18 | return 0.35875 19 | - 0.48829 * cos(1.0 * _2_pi_by_N_1_times_t) 20 | + 0.14128 * cos(2.0 * _2_pi_by_N_1_times_t) 21 | - 0.01168 * cos(3.0 * _2_pi_by_N_1_times_t); 22 | } 23 | 24 | //SINC function 25 | inline double sinc(double t) 26 | { 27 | if(t == 0.0) 28 | { 29 | return 1.0; 30 | } 31 | else 32 | { 33 | double M_PI_t; 34 | M_PI_t = M_PI * t; 35 | return sin(M_PI_t) / M_PI_t; 36 | } 37 | } 38 | 39 | double *makeOversampledUnintegrated3340ImpulseDbl(int widthSamples = 8, int ovrsampling = 10000) 40 | { 41 | //SH pulse specifications: 42 | //@192kHz: width 8 samples, integrator leakage 1.0-0.27 43 | //norm 0.27 (coincidence?) 44 | int nbrSamples = widthSamples*ovrsampling; 45 | double *pulse = new double[nbrSamples]; 46 | double sum; 47 | double norm = 0.27; //magic number! 48 | 49 | sum = 0.0; 50 | for (int i = 0; i < nbrSamples; i++) 51 | { 52 | pulse[i] = wfBlackmanHarris64bit(nbrSamples, i) - wfBlackmanHarris64bit(nbrSamples, 0); 53 | sum += pulse[i]; 54 | } 55 | for (int i = 0; i < nbrSamples; i++) 56 | { 57 | pulse[i] *= norm * (double)ovrsampling / sum; 58 | } 59 | 60 | return pulse; 61 | 62 | //integration to complete 3340 pulse: s[i] += s[i - 1] * (1.0 - 0.27 / oversampling); 63 | } 64 | 65 | struct TLS3340VCOFrame 66 | { 67 | double square; 68 | double sawtooth; 69 | double subosc; 70 | double noise; 71 | double triangle; 72 | }; 73 | 74 | struct TLS3340ImpulseParameters 75 | { 76 | size_t position; 77 | double offset; 78 | double phaseStep; 79 | double scaling; 80 | }; 81 | 82 | struct TLS3340VCOImpulseLUT 83 | { 84 | //use high oversampling factor + next neighbour interpolation for performance 85 | size_t impulseLengthFrames; 86 | double oversamplingFactor; 87 | size_t lutSize; 88 | double *lut; 89 | 90 | //auxiliary 91 | double posOversampled; 92 | 93 | TLS3340VCOImpulseLUT() 94 | { 95 | 96 | impulseLengthFrames = 8; 97 | oversamplingFactor = 10000; 98 | lutSize = impulseLengthFrames * (size_t)oversamplingFactor; 99 | 100 | lut = makeOversampledUnintegrated3340ImpulseDbl(impulseLengthFrames,oversamplingFactor); 101 | } 102 | ~TLS3340VCOImpulseLUT() 103 | { 104 | delete lut; 105 | } 106 | inline double getValAt(double position)//position without oversampling 107 | { 108 | //position with oversampling 109 | posOversampled = position*oversamplingFactor; 110 | 111 | //return 0 if position is out of bounds 112 | if (posOversampled < 0.0 || posOversampled > lutSize-1) // 113 | { 114 | return 0; 115 | } 116 | 117 | //next neigbour interpolation 118 | return lut[(size_t)round(posOversampled)]; 119 | } 120 | }; 121 | 122 | struct TLS3340VCOImpulseTrain 123 | { 124 | const static int maxNbrImpulsesPerTrain = 50; //depends on impulse length! 125 | TLS3340ImpulseParameters train[maxNbrImpulsesPerTrain]; 126 | 127 | //runtime stuff 128 | int trainIndex; 129 | int nbrActiveImpulses; 130 | int currentIndex; 131 | double impulseCounter; 132 | 133 | //needed for the leaky integrator that turns the lut impulse into the 340 impulse 134 | double integrationBuffer; 135 | 136 | TLS3340VCOImpulseTrain() 137 | { 138 | trainIndex = 0; 139 | nbrActiveImpulses = 0; 140 | currentIndex = 0; 141 | integrationBuffer = 0.0; 142 | } 143 | 144 | inline void addImpulse(double offset, double phaseStep, double scaling = 1.0) 145 | { 146 | //increment/wrap trainIndex 147 | trainIndex++; 148 | if (trainIndex >= maxNbrImpulsesPerTrain) trainIndex = 0; 149 | 150 | //initialize new impulse 151 | train[trainIndex].position = 0; 152 | train[trainIndex].offset = offset; 153 | train[trainIndex].phaseStep = phaseStep; 154 | train[trainIndex].scaling = scaling; 155 | 156 | //increment/limit nbrActiveImpulses 157 | nbrActiveImpulses++; 158 | if (nbrActiveImpulses > maxNbrImpulsesPerTrain) 159 | nbrActiveImpulses = maxNbrImpulsesPerTrain; 160 | } 161 | 162 | inline void getNextSumOfImpulsesAndSawtoothSlope(TLS3340VCOImpulseLUT *lut, 163 | double ¤tSumOfImpulses)//,double ¤tSumOfSlopes) 164 | { 165 | impulseCounter = 0.0; 166 | currentSumOfImpulses = 0.0; 167 | 168 | currentIndex = trainIndex; 169 | 170 | for (int i = 0; i < nbrActiveImpulses; i++) 171 | { 172 | //add contribution of current impulse 173 | if (train[currentIndex].phaseStep > 0.0) 174 | { 175 | currentSumOfImpulses += train[currentIndex].scaling * 176 | lut->getValAt(train[currentIndex].position + 177 | train[currentIndex].offset/train[currentIndex].phaseStep); 178 | } 179 | 180 | //deactivate impulse if it has been completely put out 181 | //(can be done this way because this impulse is the oldest active one; 182 | //therefore, this happens only while i == nbrActiveImpulses-1) 183 | train[currentIndex].position++; 184 | if (train[currentIndex].position > lut->impulseLengthFrames-1) 185 | { 186 | nbrActiveImpulses--; 187 | } 188 | 189 | //prepare currentIndex for next impulse 190 | currentIndex--; 191 | if (currentIndex < 0) currentIndex += maxNbrImpulsesPerTrain; 192 | } 193 | 194 | //integration 195 | currentSumOfImpulses += integrationBuffer * (1.0 - 0.27); 196 | integrationBuffer = currentSumOfImpulses; 197 | } 198 | }; 199 | 200 | struct TLS3340NoiseSource 201 | { 202 | private: 203 | static const size_t dataLengthSamples = 960527; 204 | float LS3340Noise[dataLengthSamples]; 205 | unsigned long currentPosition; 206 | public: 207 | TLS3340NoiseSource() 208 | { 209 | //initialize noise array 210 | for (size_t i=0;i= dataLengthSamples) 236 | currentPosition -= dataLengthSamples; 237 | 238 | return LS3340Noise[currentPosition]; 239 | } 240 | } 241 | }; 242 | 243 | struct TLS3340VCOSINCLUT 244 | { 245 | private: 246 | int sincTableSize; 247 | float *sincTable; 248 | 249 | double overSampling; 250 | double zeroCrossings; 251 | double posOversampled; 252 | double middle; 253 | public: 254 | inline float getValAt(double posRelMiddleNotOversampled) 255 | { 256 | posOversampled = posRelMiddleNotOversampled * overSampling; 257 | int pos = (size_t)round(middle+posOversampled); 258 | if (pos >= 0 && pos < sincTableSize) //TODO: This should always be the case but it is not! 259 | { 260 | return sincTable[pos]; 261 | } 262 | else return 0.0f; 263 | } 264 | inline int size(){return sincTableSize;} 265 | TLS3340VCOSINCLUT(int zeroCrossings, int overSampling) 266 | { 267 | this->overSampling = overSampling; 268 | this->zeroCrossings = zeroCrossings; 269 | sincTableSize = (zeroCrossings * 2 * overSampling) + 1; 270 | middle = floor(sincTableSize/2.0); 271 | sincTable = new float[sincTableSize]; 272 | 273 | // BEGIN calculate sinc lut 274 | // Based on code by Daniel Werner https://www.experimentalscene.com/articles/minbleps.php 275 | double a = (double)-zeroCrossings; 276 | double b = (double)zeroCrossings; 277 | double r; 278 | for(int i = 0; i < sincTableSize; i++) 279 | { 280 | r = ((double)i) / ((double)(sincTableSize - 1)); 281 | sincTable[i] = (float)sinc(a + (r * (b - a))); 282 | sincTable[i] *= (float)sinc(-1.0 + (r * (2.0))); //Window function is sinc, too! 283 | } 284 | // END calculate sinc lut 285 | 286 | //normalize sinc lut 287 | double sum = 0.0; 288 | for(int i = 0; i < sincTableSize; i++) 289 | { 290 | sum += sincTable[i]; 291 | } 292 | sum /= overSampling; 293 | sum = 1.0/sum; 294 | for(int i = 0; i < sincTableSize; i++) 295 | { 296 | sincTable[i] *= sum; 297 | } 298 | } 299 | ~TLS3340VCOSINCLUT() 300 | { 301 | delete sincTable; 302 | } 303 | }; 304 | 305 | struct TLS3340VCO 306 | { 307 | private: 308 | 309 | TLS3340VCOFrame lastFrame; 310 | TLS3340VCOImpulseLUT *impulse; 311 | TLS3340VCOImpulseTrain zeroPhaseImpulses;//at the beginnig of a vco cycle 312 | TLS3340VCOImpulseTrain pwmImpulses;//for square pwm 313 | TLS3340VCOImpulseTrain suboscillatorImpulses; 314 | TLS3340VCOImpulseTrain sawImpulses; 315 | 316 | TLS3340NoiseSource noise; 317 | 318 | double vcoFrequencyHz; 319 | double sampleRateHzInternal; 320 | double sampleRateHzExternal; 321 | double sampleTimeS; //time per sample, 1/f 322 | double currentPhaseStep; 323 | double phase; 324 | 325 | double currentSumOfZeroPhaseImpulses, currentSumOfPWMImpulses,currentSumOfSawImpulses,currentSumOfSuboscImpulses; 326 | double sawtoothSlope; 327 | double leakyIntegratorFactor,leakyIntegratorFactorSaw; 328 | 329 | int suboscillatorCounter; 330 | bool squareSwitch,sawSwitch; 331 | double pwmCoefficient; //range: -0.5 to +0.5 332 | int suboscillatorMode; 333 | int noiseIndex; 334 | 335 | double lastPitch = 261.6256; 336 | 337 | //for sinc interpolation (todo: Move iterpolation to struct or class) 338 | const int sincZeroCrossings = 3; //increase for even better anti aliasing 339 | const int sincOversampling = 10000; 340 | LabSeven::LS3340::TLS3340VCOSINCLUT *sincOversampled; 341 | //nbrInternalSamples is much larger than neccessary 342 | //so that sincZeroCrossings can be increased without problems 343 | const int nbrInternalSamples = 1000; 344 | TLS3340VCOFrame internalSamples[1000]; 345 | int nbrInternalSamplesInUse = 0; 346 | int internalSamplesIndex = 0; 347 | 348 | //depend on external samplerate 349 | int radius; 350 | double sampleStep; 351 | double stretch; 352 | int nbrSamplesRequired; 353 | 354 | //runtime stuff 355 | int internalSamplesPointer; 356 | double sincPosition; 357 | double samplePhase = 0.0; 358 | double currentSincCoeff; 359 | double triangleTemp = 0.0; 360 | 361 | 362 | inline void setSamplerateInternal(double sampleRateHz) 363 | { 364 | sampleRateHzInternal = sampleRateHz; 365 | this->sampleTimeS = 1.0/sampleRateHzInternal; 366 | currentPhaseStep = vcoFrequencyHz * sampleTimeS; 367 | sawtoothSlope = currentPhaseStep;//vcoFrequencyHz * sampleTimeS; 368 | } 369 | 370 | public: 371 | inline void setSamplerateExternal(double sampleRateHz) 372 | { 373 | sampleRateHzExternal = sampleRateHz; 374 | 375 | //recalulate frequency dependent sinc interpolation parameters 376 | sampleStep = sampleRateHzInternal/sampleRateHzExternal; 377 | stretch = 0.8*sampleRateHzExternal/sampleRateHzInternal; 378 | 379 | if (sampleRateHz <= 48000) 380 | { 381 | stretch = 0.8*sampleRateHzExternal/sampleRateHzInternal; 382 | } 383 | else if (sampleRateHz <= 96000) 384 | { 385 | stretch = 0.6*sampleRateHzExternal/sampleRateHzInternal; 386 | } 387 | else 388 | { 389 | stretch = 0.5*sampleRateHzExternal/sampleRateHzInternal; 390 | } 391 | 392 | radius = floor((double)sincZeroCrossings/stretch); 393 | nbrSamplesRequired = radius+1; 394 | nbrInternalSamplesInUse = 2*radius+1; 395 | } 396 | 397 | inline void setFrequency(double frequencyHz) 398 | { 399 | this->vcoFrequencyHz = frequencyHz; 400 | currentPhaseStep = vcoFrequencyHz * sampleTimeS; 401 | sawtoothSlope = currentPhaseStep;//vcoFrequencyHz * sampleTimeS; 402 | } 403 | 404 | inline void setPwmCoefficient(double pwmCoefficient) 405 | { 406 | if (pwmCoefficient > 0.4) 407 | this->pwmCoefficient = 0.4; 408 | else if (pwmCoefficient < -0.4) 409 | this->pwmCoefficient = -0.4; 410 | else 411 | this->pwmCoefficient = pwmCoefficient; 412 | } 413 | 414 | inline void setSuboscillatorMode(int mode) 415 | { 416 | if (mode == 0) 417 | this->suboscillatorMode = 0; 418 | else if (mode == 1) 419 | this->suboscillatorMode = 1; 420 | else if (mode == 2) 421 | this->suboscillatorMode = 2; 422 | else 423 | this->suboscillatorMode = 1; 424 | } 425 | 426 | //can output several samples at a time 427 | inline void getNextBlock(TLS3340VCOFrame *block, size_t nbrFramesInBlock) 428 | { 429 | for (size_t i=0; i 1.0) 434 | { 435 | //wrap phase for periodicity 436 | phase -= 1.0; 437 | 438 | //add new positive impulse 439 | zeroPhaseImpulses.addImpulse(phase,currentPhaseStep); 440 | 441 | //inc and wrap suboscillatorCounter 442 | suboscillatorCounter++; 443 | if (suboscillatorCounter > 3) suboscillatorCounter = 0; 444 | 445 | switch (suboscillatorMode) 446 | { 447 | case 0: switch (suboscillatorCounter) 448 | { 449 | case 0: case 2: suboscillatorImpulses.addImpulse(phase,currentPhaseStep, 1.0);break; 450 | case 1: case 3: suboscillatorImpulses.addImpulse(phase,currentPhaseStep,-1.0);break; 451 | default: suboscillatorCounter=suboscillatorCounter; 452 | } 453 | break; 454 | case 1: switch (suboscillatorCounter) 455 | { 456 | case 0: suboscillatorImpulses.addImpulse(phase,currentPhaseStep, 1.0);break; 457 | case 2: suboscillatorImpulses.addImpulse(phase,currentPhaseStep,-1.0);break; 458 | default: suboscillatorCounter=suboscillatorCounter; 459 | } 460 | break; 461 | case 2: switch (suboscillatorCounter) 462 | { 463 | //amplitude set to 1.255 on purpose 464 | case 0: suboscillatorImpulses.addImpulse(phase,currentPhaseStep,1.255); break; 465 | case 1: suboscillatorImpulses.addImpulse(phase,currentPhaseStep,-0.25); break; 466 | case 2: suboscillatorImpulses.addImpulse(phase,currentPhaseStep,-0.005); break; 467 | case 3: suboscillatorImpulses.addImpulse(phase,currentPhaseStep,-1.0);break; 468 | default: suboscillatorCounter=suboscillatorCounter; 469 | } 470 | } 471 | 472 | //tell squareSwitch that a new period has begun 473 | squareSwitch = true; 474 | sawSwitch = true; 475 | } 476 | else 477 | { 478 | //pwmCoefficient is in [-0.4,+0.4] für 10% to 90% pwm range 479 | if (squareSwitch && phase > 0.5+pwmCoefficient) 480 | { 481 | //add new negative impulse 482 | pwmImpulses.addImpulse(phase-(0.5+pwmCoefficient),currentPhaseStep,-1.0); 483 | squareSwitch = false; 484 | } 485 | 486 | if (sawSwitch && phase > 0.5) 487 | { 488 | //add new negative impulse 489 | sawImpulses.addImpulse(phase-0.5,currentPhaseStep,-1.0); 490 | sawSwitch = false; 491 | } 492 | } 493 | 494 | //get current sums of impulses 495 | zeroPhaseImpulses.getNextSumOfImpulsesAndSawtoothSlope(impulse,currentSumOfZeroPhaseImpulses); 496 | pwmImpulses.getNextSumOfImpulsesAndSawtoothSlope(impulse,currentSumOfPWMImpulses); 497 | sawImpulses.getNextSumOfImpulsesAndSawtoothSlope(impulse, currentSumOfSawImpulses); 498 | suboscillatorImpulses.getNextSumOfImpulsesAndSawtoothSlope(impulse,currentSumOfSuboscImpulses); 499 | 500 | //put together the waveforms 501 | leakyIntegratorFactor = 0.9998; 502 | leakyIntegratorFactorSaw = 0.9998 - 0.05*2.0*vcoFrequencyHz*sampleTimeS; 503 | 504 | block[i].sawtooth = currentSumOfSawImpulses + sawtoothSlope + leakyIntegratorFactorSaw * lastFrame.sawtooth; 505 | block[i].square = currentSumOfZeroPhaseImpulses + currentSumOfPWMImpulses + leakyIntegratorFactor * lastFrame.square; 506 | block[i].subosc = currentSumOfSuboscImpulses + leakyIntegratorFactorSaw * lastFrame.subosc; 507 | block[i].noise = noise.getNextNoiseSample(); 508 | 509 | triangleTemp = currentSumOfZeroPhaseImpulses + currentSumOfSawImpulses + leakyIntegratorFactor * triangleTemp; 510 | block[i].triangle = 4.9*sawtoothSlope*triangleTemp + leakyIntegratorFactorSaw * lastFrame.triangle; 511 | 512 | lastFrame.sawtooth = block[i].sawtooth; 513 | lastFrame.square = block[i].square; 514 | lastFrame.triangle = block[i].triangle; 515 | lastFrame.subosc = block[i].subosc; 516 | 517 | lastPitch = vcoFrequencyHz; 518 | } 519 | } 520 | 521 | //output with sinc interpolation 522 | inline void getNextFrameAtExternalSampleRateSinc(TLS3340VCOFrame *f) 523 | { 524 | //get the necessary number of new internal samples 525 | for (int j = 0; j < nbrSamplesRequired; j++) 526 | { 527 | getNextBlock(&(internalSamples[internalSamplesIndex]),1); 528 | 529 | internalSamplesIndex++; 530 | if (internalSamplesIndex >= nbrInternalSamples) 531 | internalSamplesIndex -= nbrInternalSamples; 532 | } 533 | 534 | //calculate interpolation 535 | internalSamplesPointer = internalSamplesIndex; 536 | f->square = 0.0; 537 | f->sawtooth = 0.0; 538 | f->subosc = 0.0; 539 | f->noise = 0.0; 540 | f->triangle = 0.0; 541 | sincPosition = ((double)(nbrInternalSamplesInUse-(radius+1))-samplePhase)*stretch; 542 | 543 | for (int j = 0; j < nbrInternalSamplesInUse; j++) 544 | { 545 | currentSincCoeff = sincOversampled->getValAt(sincPosition); 546 | 547 | f->square += internalSamples[internalSamplesPointer].square * currentSincCoeff; 548 | f->sawtooth += internalSamples[internalSamplesPointer].sawtooth * currentSincCoeff; 549 | f->subosc += internalSamples[internalSamplesPointer].subosc * currentSincCoeff; 550 | f->noise += internalSamples[internalSamplesPointer].noise * currentSincCoeff; 551 | f->triangle += internalSamples[internalSamplesPointer].triangle * currentSincCoeff; 552 | 553 | sincPosition -= stretch; 554 | 555 | internalSamplesPointer--; 556 | if (internalSamplesPointer < 0) 557 | internalSamplesPointer += nbrInternalSamples; 558 | } 559 | 560 | //rescale 561 | f->square *= stretch; 562 | f->sawtooth *= stretch; 563 | f->subosc *= stretch; 564 | f->noise *= stretch; 565 | f->triangle *= stretch; 566 | 567 | //prepare parameters for next interpolation 568 | samplePhase += sampleStep; 569 | nbrSamplesRequired = (int)floor(samplePhase); 570 | samplePhase -= (double)nbrSamplesRequired; 571 | } 572 | 573 | inline void getNextFrameAtExternalSampleRateCubic(TLS3340VCOFrame *f) 574 | { 575 | //get the necessary number of new internal samples 576 | for (int j = 0; j < nbrSamplesRequired; j++) 577 | { 578 | getNextBlock(&(internalSamples[internalSamplesIndex]),1); 579 | 580 | internalSamplesIndex++; 581 | if (internalSamplesIndex >= nbrInternalSamples) 582 | internalSamplesIndex -= nbrInternalSamples; 583 | } 584 | 585 | //calculate interpolation 586 | f->square = 0.0; 587 | f->sawtooth = 0.0; 588 | f->subosc = 0.0; 589 | f->noise = 0.0; 590 | f->triangle = 0.0; 591 | 592 | //Next neighbour interpolation as a stub 593 | //TODO: Replace by cubic interpolation and add a 'quality: std/hgh' switch to GUI 594 | *f = internalSamples[internalSamplesIndex]; 595 | 596 | //prepare parameters for next interpolation 597 | samplePhase += sampleStep; 598 | nbrSamplesRequired = (int)floor(samplePhase); 599 | samplePhase -= (double)nbrSamplesRequired; 600 | } 601 | 602 | TLS3340VCO() 603 | { 604 | //defaults 605 | setSamplerateInternal(192000); //192k covers the complete bandwidth of the original impulse. Do not change! 606 | setSamplerateExternal(44100); //to be changed/updated by host software 607 | setFrequency(261.6256); 608 | 609 | //generate fitted LS3340 impulse and sinc resampler lookup tables 610 | impulse = new TLS3340VCOImpulseLUT(); 611 | sincOversampled = new LabSeven::LS3340::TLS3340VCOSINCLUT(sincZeroCrossings,sincOversampling); 612 | 613 | //some initializations 614 | lastFrame.sawtooth=-0.7; 615 | suboscillatorCounter = 0; 616 | squareSwitch = false; 617 | pwmCoefficient = 0; 618 | suboscillatorMode = 1; 619 | noiseIndex = 0; 620 | 621 | currentSumOfSuboscImpulses = 0.0; 622 | currentSumOfSawImpulses = 0.0; 623 | currentSumOfZeroPhaseImpulses = 0.0; 624 | currentSumOfPWMImpulses = 0.0; 625 | } 626 | 627 | ~TLS3340VCO() 628 | { 629 | delete impulse; 630 | delete sincOversampled; 631 | } 632 | }; 633 | }; 634 | }; 635 | 636 | #endif // LABSEVEN_3340_VCO_H 637 | --------------------------------------------------------------------------------