├── .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 |
80 |
--------------------------------------------------------------------------------
/res/LabSeven_3340_SlidePot.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
68 |
--------------------------------------------------------------------------------
/res/LabSeven_3340_SlidePot3Stage.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
68 |
--------------------------------------------------------------------------------
/res/LabSeven_3340_SlidePotHandleGreen.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
95 |
--------------------------------------------------------------------------------
/res/LabSeven_3340_SlidePotHandleRed.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
95 |
--------------------------------------------------------------------------------
/res/LabSeven_3340_SlidePotHandleYellow.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
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 |
--------------------------------------------------------------------------------