#include "ESeries.hpp" #include "dsp/filter.hpp" #include "dsp/minblep.hpp" struct E340 : Module { enum ParamIds { COARSE_PARAM, FINE_PARAM, FM_PARAM, SPREAD_PARAM, CHAOS_PARAM, CHAOS_BW_PARAM, DENSITY_PARAM, NUM_PARAMS }; enum InputIds { PITCH_INPUT, FM_INPUT, SYNC_INPUT, SPREAD_INPUT, CHAOS_INPUT, CHAOS_BW_INPUT, NUM_INPUTS }; enum OutputIds { SAW_OUTPUT, SINE_OUTPUT, NUM_OUTPUTS }; float phases[8] = {}; RCFilter noiseFilters[8]; float sync = 0.0; MinBLEP<16> sineMinBLEP; MinBLEP<16> sawMinBLEP; // For removing DC RCFilter sineFilter; RCFilter sawFilter; E340(); void step() override; }; E340::E340() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) { sineMinBLEP.minblep = minblep_16_32; sineMinBLEP.oversample = 32; sawMinBLEP.minblep = minblep_16_32; sawMinBLEP.oversample = 32; // Randomize initial phases for (int i = 0; i < 8; i++) { phases[i] = randomUniform(); } } void E340::step() { // Base pitch float basePitch = params[COARSE_PARAM].value + 12.0 * inputs[PITCH_INPUT].value; if (inputs[FM_INPUT].active) { basePitch += 12.0 / 4.0 * params[FM_PARAM].value * inputs[FM_INPUT].value; } basePitch += params[FINE_PARAM].value; // Spread float spread = params[SPREAD_PARAM].value + inputs[SPREAD_INPUT].value / 10.0; spread = clamp(spread, 0.0f, 1.0f); const float spreadPower = 50.0; spread = (powf(spreadPower + 1.0, spread) - 1.0) / spreadPower; // Chaos float chaos = params[CHAOS_PARAM].value + inputs[CHAOS_INPUT].value / 10.0; chaos = clamp(chaos, 0.0f, 1.0f); const float chaosPower = 50.0; chaos = 8.0 * (powf(chaosPower + 1.0, chaos) - 1.0) / chaosPower; // Chaos BW float chaosBW = params[CHAOS_BW_PARAM].value + inputs[CHAOS_BW_INPUT].value / 10.0; chaosBW = clamp(chaosBW, 0.0f, 1.0f); chaosBW = 6.0 * powf(100.0, chaosBW); // This shouldn't scale with the global sample rate, because of reasons. float filterCutoff = chaosBW / 44100.0; // Check sync input float newSync = inputs[SYNC_INPUT].value - 0.25; float syncCrossing = INFINITY; if (sync < 0.0 && newSync >= 0.0) { float deltaSync = newSync - sync; syncCrossing = -newSync / deltaSync; } sync = newSync; // Density int density; switch ((int)roundf(params[DENSITY_PARAM].value)) { case 0: density = 2; break; case 1: density = 4; break; default: density = 8; break; } // Detuning amounts, in note value const static float detunings[8] = {-21, 21, -9, 9, -3, 3, -15, 15}; // Perfect fourths // const static float detunings[8] = {-24, 24, -12, 12, -5, 7, -17, 19}; // Fifths // Oscillator block float sines = 0.0; float saws = 0.0; for (int i = 0; i < density; i++) { // Noise float noise = 0.0; if (chaos > 0.0) { noise = randomNormal(); noiseFilters[i].setCutoff(filterCutoff); noiseFilters[i].process(noise); noise = noiseFilters[i].lowpass(); noise *= chaos; } // Frequency float pitch = basePitch + spread * detunings[i] + 12.0 * noise; pitch = clamp(pitch, -72.0f, 72.0f); float freq = 261.626 * powf(2.0, pitch / 12.0); // Advance phase float deltaPhase = freq * engineGetSampleTime(); float phase = phases[i] + deltaPhase; // Reset phase if (phase >= 1.0) { phase -= 1.0; float crossing = -phase / deltaPhase; sawMinBLEP.jump(crossing, -2.0); } // Compute output float sine = -cosf(2*M_PI * phase); float saw = 2.0*phase - 1.0; // Sync if (syncCrossing <= 0.0) { phase = deltaPhase * -syncCrossing; float newSine = -cosf(2*M_PI * phase); float newSaw = 2.0*phase - 1.0; sineMinBLEP.jump(syncCrossing, newSine - sine); sawMinBLEP.jump(syncCrossing, newSaw - saw); sine = newSine; saw = newSaw; } phases[i] = phase; sines += sine; saws += saw; } sines += sineMinBLEP.shift(); saws += sawMinBLEP.shift(); sines /= density; saws /= density; // Apply HP filter at 20Hz float r = 20.0 * engineGetSampleTime(); sineFilter.setCutoff(r); sawFilter.setCutoff(r); sineFilter.process(sines); sawFilter.process(saws); outputs[SINE_OUTPUT].value = 5.0 * sineFilter.highpass(); outputs[SAW_OUTPUT].value = 5.0 * sawFilter.highpass(); } struct E340Widget : ModuleWidget { E340Widget(E340 *module); }; E340Widget::E340Widget(E340 *module) : ModuleWidget(module) { setPanel(SVG::load(assetPlugin(plugin, "res/E340.svg"))); addChild(Widget::create(Vec(15, 0))); addChild(Widget::create(Vec(box.size.x-30, 0))); addChild(Widget::create(Vec(15, 365))); addChild(Widget::create(Vec(box.size.x-30, 365))); addParam(ParamWidget::create(Vec(26, 43), module, E340::COARSE_PARAM, -48.0, 48.0, 0.0)); addParam(ParamWidget::create(Vec(137, 43), module, E340::FINE_PARAM, -1.0, 1.0, 0.0)); addParam(ParamWidget::create(Vec(26, 109), module, E340::FM_PARAM, 0.0, 1.0, 0.0)); addParam(ParamWidget::create(Vec(137, 109), module, E340::SPREAD_PARAM, 0.0, 1.0, 0.0)); addParam(ParamWidget::create(Vec(26, 175), module, E340::CHAOS_PARAM, 0.0, 1.0, 0.0)); addParam(ParamWidget::create(Vec(137, 175), module, E340::CHAOS_BW_PARAM, 0.0, 1.0, 0.5)); addParam(ParamWidget::create(Vec(89, 140), module, E340::DENSITY_PARAM, 0.0, 2.0, 2.0)); addInput(Port::create(Vec(13, 243), Port::INPUT, module, E340::PITCH_INPUT)); addInput(Port::create(Vec(63, 243), Port::INPUT, module, E340::FM_INPUT)); addInput(Port::create(Vec(113, 243), Port::INPUT, module, E340::SYNC_INPUT)); addInput(Port::create(Vec(163, 243), Port::INPUT, module, E340::SPREAD_INPUT)); addInput(Port::create(Vec(13, 301), Port::INPUT, module, E340::CHAOS_INPUT)); addInput(Port::create(Vec(63, 301), Port::INPUT, module, E340::CHAOS_BW_INPUT)); addOutput(Port::create(Vec(113, 301), Port::OUTPUT, module, E340::SAW_OUTPUT)); addOutput(Port::create(Vec(163, 301), Port::OUTPUT, module, E340::SINE_OUTPUT)); } Model *modelE340 = Model::create("E-Series", "E340", "E340 Cloud Generator", OSCILLATOR_TAG);