|
@@ -4,100 +4,24 @@ |
|
|
using simd::float_4; |
|
|
using simd::float_4; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
template <typename T> |
|
|
|
|
|
struct LowFrequencyOscillator { |
|
|
|
|
|
T phase = 0.f; |
|
|
|
|
|
T pw = 0.5f; |
|
|
|
|
|
T freq = 1.f; |
|
|
|
|
|
bool invert = false; |
|
|
|
|
|
bool bipolar = false; |
|
|
|
|
|
T resetState = T::mask(); |
|
|
|
|
|
|
|
|
|
|
|
void setPitch(T pitch) { |
|
|
|
|
|
pitch = simd::fmin(pitch, 10.f); |
|
|
|
|
|
freq = dsp::approxExp2_taylor5(pitch + 30.f) / std::pow(2.f, 30.f); |
|
|
|
|
|
} |
|
|
|
|
|
void setPulseWidth(T pw) { |
|
|
|
|
|
const T pwMin = 0.01f; |
|
|
|
|
|
this->pw = clamp(pw, pwMin, 1.f - pwMin); |
|
|
|
|
|
} |
|
|
|
|
|
void setReset(T reset) { |
|
|
|
|
|
reset = simd::rescale(reset, 0.1f, 2.f, 0.f, 1.f); |
|
|
|
|
|
T on = (reset >= 1.f); |
|
|
|
|
|
T off = (reset <= 0.f); |
|
|
|
|
|
T triggered = ~resetState & on; |
|
|
|
|
|
resetState = simd::ifelse(off, 0.f, resetState); |
|
|
|
|
|
resetState = simd::ifelse(on, T::mask(), resetState); |
|
|
|
|
|
phase = simd::ifelse(triggered, 0.f, phase); |
|
|
|
|
|
} |
|
|
|
|
|
void step(float dt) { |
|
|
|
|
|
T deltaPhase = simd::fmin(freq * dt, 0.5f); |
|
|
|
|
|
phase += deltaPhase; |
|
|
|
|
|
phase -= (phase >= 1.f) & 1.f; |
|
|
|
|
|
} |
|
|
|
|
|
T sin() { |
|
|
|
|
|
T p = phase; |
|
|
|
|
|
if (!bipolar) |
|
|
|
|
|
p -= 0.25f; |
|
|
|
|
|
T v = simd::sin(2 * M_PI * p); |
|
|
|
|
|
if (invert) |
|
|
|
|
|
v *= -1.f; |
|
|
|
|
|
if (!bipolar) |
|
|
|
|
|
v += 1.f; |
|
|
|
|
|
return v; |
|
|
|
|
|
} |
|
|
|
|
|
T tri() { |
|
|
|
|
|
T p = phase; |
|
|
|
|
|
if (bipolar) |
|
|
|
|
|
p += 0.25f; |
|
|
|
|
|
T v = 4.f * simd::fabs(p - simd::round(p)) - 1.f; |
|
|
|
|
|
if (invert) |
|
|
|
|
|
v *= -1.f; |
|
|
|
|
|
if (!bipolar) |
|
|
|
|
|
v += 1.f; |
|
|
|
|
|
return v; |
|
|
|
|
|
} |
|
|
|
|
|
T saw() { |
|
|
|
|
|
T p = phase; |
|
|
|
|
|
if (!bipolar) |
|
|
|
|
|
p -= 0.5f; |
|
|
|
|
|
T v = 2.f * (p - simd::round(p)); |
|
|
|
|
|
if (invert) |
|
|
|
|
|
v *= -1.f; |
|
|
|
|
|
if (!bipolar) |
|
|
|
|
|
v += 1.f; |
|
|
|
|
|
return v; |
|
|
|
|
|
} |
|
|
|
|
|
T sqr() { |
|
|
|
|
|
T v = simd::ifelse(phase < pw, 1.f, -1.f); |
|
|
|
|
|
if (invert) |
|
|
|
|
|
v *= -1.f; |
|
|
|
|
|
if (!bipolar) |
|
|
|
|
|
v += 1.f; |
|
|
|
|
|
return v; |
|
|
|
|
|
} |
|
|
|
|
|
T light() { |
|
|
|
|
|
return simd::sin(2 * T(M_PI) * phase); |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct LFO : Module { |
|
|
struct LFO : Module { |
|
|
enum ParamIds { |
|
|
enum ParamIds { |
|
|
OFFSET_PARAM, |
|
|
OFFSET_PARAM, |
|
|
INVERT_PARAM, |
|
|
INVERT_PARAM, |
|
|
FREQ_PARAM, |
|
|
FREQ_PARAM, |
|
|
FM1_PARAM, |
|
|
|
|
|
|
|
|
FM_PARAM, |
|
|
FM2_PARAM, // removed |
|
|
FM2_PARAM, // removed |
|
|
PW_PARAM, |
|
|
PW_PARAM, |
|
|
PWM_PARAM, |
|
|
PWM_PARAM, |
|
|
NUM_PARAMS |
|
|
NUM_PARAMS |
|
|
}; |
|
|
}; |
|
|
enum InputIds { |
|
|
enum InputIds { |
|
|
FM1_INPUT, |
|
|
|
|
|
|
|
|
FM_INPUT, |
|
|
FM2_INPUT, // removed |
|
|
FM2_INPUT, // removed |
|
|
RESET_INPUT, |
|
|
RESET_INPUT, |
|
|
PW_INPUT, |
|
|
PW_INPUT, |
|
|
|
|
|
// added in 2.0 |
|
|
|
|
|
CLOCK_INPUT, |
|
|
NUM_INPUTS |
|
|
NUM_INPUTS |
|
|
}; |
|
|
}; |
|
|
enum OutputIds { |
|
|
enum OutputIds { |
|
@@ -109,72 +33,161 @@ struct LFO : Module { |
|
|
}; |
|
|
}; |
|
|
enum LightIds { |
|
|
enum LightIds { |
|
|
ENUMS(PHASE_LIGHT, 3), |
|
|
ENUMS(PHASE_LIGHT, 3), |
|
|
|
|
|
INVERT_LIGHT, |
|
|
|
|
|
OFFSET_LIGHT, |
|
|
NUM_LIGHTS |
|
|
NUM_LIGHTS |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
LowFrequencyOscillator<float_4> oscillators[4]; |
|
|
|
|
|
|
|
|
bool offset = false; |
|
|
|
|
|
bool invert = false; |
|
|
|
|
|
|
|
|
|
|
|
float_4 phases[4]; |
|
|
|
|
|
dsp::TSchmittTrigger<float_4> clockTriggers[4]; |
|
|
|
|
|
dsp::TSchmittTrigger<float_4> resetTriggers[4]; |
|
|
|
|
|
dsp::SchmittTrigger clockTrigger; |
|
|
|
|
|
float clockFreq = 1.f; |
|
|
|
|
|
dsp::Timer clockTimer; |
|
|
|
|
|
|
|
|
|
|
|
dsp::BooleanTrigger offsetTrigger; |
|
|
|
|
|
dsp::BooleanTrigger invertTrigger; |
|
|
dsp::ClockDivider lightDivider; |
|
|
dsp::ClockDivider lightDivider; |
|
|
|
|
|
|
|
|
LFO() { |
|
|
LFO() { |
|
|
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); |
|
|
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); |
|
|
configSwitch(OFFSET_PARAM, 0.f, 1.f, 1.f, "Offset", {"Bipolar", "Unipolar"}); |
|
|
|
|
|
configSwitch(INVERT_PARAM, 0.f, 1.f, 1.f, "Orientation", {"Inverted", "Normal"}); |
|
|
|
|
|
|
|
|
configButton(OFFSET_PARAM, "Offset 0-10V"); |
|
|
|
|
|
configButton(INVERT_PARAM, "Invert"); |
|
|
configParam(FREQ_PARAM, -8.f, 10.f, 1.f, "Frequency", " Hz", 2, 1); |
|
|
configParam(FREQ_PARAM, -8.f, 10.f, 1.f, "Frequency", " Hz", 2, 1); |
|
|
configParam(FM1_PARAM, 0.f, 1.f, 0.f, "Frequency modulation 1", "%", 0.f, 100.f); |
|
|
|
|
|
|
|
|
configParam(FM_PARAM, -1.f, 1.f, 0.f, "Frequency modulation", "%", 0.f, 100.f); |
|
|
|
|
|
getParamQuantity(FM_PARAM)->randomizeEnabled = false; |
|
|
configParam(PW_PARAM, 0.01f, 0.99f, 0.5f, "Pulse width", "%", 0.f, 100.f); |
|
|
configParam(PW_PARAM, 0.01f, 0.99f, 0.5f, "Pulse width", "%", 0.f, 100.f); |
|
|
configParam(FM2_PARAM, 0.f, 1.f, 0.f, "Frequency modulation 2", "%", 0.f, 100.f); |
|
|
|
|
|
configParam(PWM_PARAM, 0.f, 1.f, 0.f, "Pulse width modulation", "%", 0.f, 100.f); |
|
|
|
|
|
configInput(FM1_INPUT, "Frequency modulation 1"); |
|
|
|
|
|
configInput(FM2_INPUT, "Frequency modulation 2"); |
|
|
|
|
|
|
|
|
configParam(PWM_PARAM, -1.f, 1.f, 0.f, "Pulse width modulation", "%", 0.f, 100.f); |
|
|
|
|
|
getParamQuantity(PWM_PARAM)->randomizeEnabled = false; |
|
|
|
|
|
|
|
|
|
|
|
configInput(FM_INPUT, "Frequency modulation"); |
|
|
|
|
|
configInput(CLOCK_INPUT, "Clock"); |
|
|
configInput(RESET_INPUT, "Reset"); |
|
|
configInput(RESET_INPUT, "Reset"); |
|
|
configInput(PW_INPUT, "Pulse width modulation"); |
|
|
configInput(PW_INPUT, "Pulse width modulation"); |
|
|
|
|
|
|
|
|
configOutput(SIN_OUTPUT, "Sine"); |
|
|
configOutput(SIN_OUTPUT, "Sine"); |
|
|
configOutput(TRI_OUTPUT, "Triangle"); |
|
|
configOutput(TRI_OUTPUT, "Triangle"); |
|
|
configOutput(SAW_OUTPUT, "Sawtooth"); |
|
|
configOutput(SAW_OUTPUT, "Sawtooth"); |
|
|
configOutput(SQR_OUTPUT, "Square"); |
|
|
configOutput(SQR_OUTPUT, "Square"); |
|
|
|
|
|
|
|
|
configLight(PHASE_LIGHT, "Phase"); |
|
|
configLight(PHASE_LIGHT, "Phase"); |
|
|
lightInfos[PHASE_LIGHT]->description = "Tracks the sine output.\nGreen if positive, red if negative, blue if polyphonic."; |
|
|
|
|
|
|
|
|
|
|
|
lightDivider.setDivision(16); |
|
|
lightDivider.setDivision(16); |
|
|
|
|
|
onReset(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void onReset() override { |
|
|
|
|
|
offset = false; |
|
|
|
|
|
invert = false; |
|
|
|
|
|
for (int c = 0; c < 16; c += 4) { |
|
|
|
|
|
phases[c / 4] = 0.f; |
|
|
|
|
|
} |
|
|
|
|
|
clockFreq = 1.f; |
|
|
|
|
|
clockTimer.reset(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void process(const ProcessArgs& args) override { |
|
|
void process(const ProcessArgs& args) override { |
|
|
float freqParam = params[FREQ_PARAM].getValue(); |
|
|
float freqParam = params[FREQ_PARAM].getValue(); |
|
|
float fm1Param = params[FM1_PARAM].getValue(); |
|
|
|
|
|
float fm2Param = params[FM2_PARAM].getValue(); |
|
|
|
|
|
|
|
|
float fmParam = params[FM_PARAM].getValue(); |
|
|
float pwParam = params[PW_PARAM].getValue(); |
|
|
float pwParam = params[PW_PARAM].getValue(); |
|
|
float pwmParam = params[PWM_PARAM].getValue(); |
|
|
float pwmParam = params[PWM_PARAM].getValue(); |
|
|
|
|
|
|
|
|
int channels = std::max(1, inputs[FM1_INPUT].getChannels()); |
|
|
|
|
|
|
|
|
// Buttons |
|
|
|
|
|
if (offsetTrigger.process(params[OFFSET_PARAM].getValue() > 0.f)) { |
|
|
|
|
|
offset ^= true; |
|
|
|
|
|
} |
|
|
|
|
|
if (invertTrigger.process(params[INVERT_PARAM].getValue() > 0.f)) { |
|
|
|
|
|
invert ^= true; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
for (int c = 0; c < channels; c += 4) { |
|
|
|
|
|
auto* oscillator = &oscillators[c / 4]; |
|
|
|
|
|
oscillator->invert = (params[INVERT_PARAM].getValue() == 0.f); |
|
|
|
|
|
oscillator->bipolar = (params[OFFSET_PARAM].getValue() == 0.f); |
|
|
|
|
|
|
|
|
// Clock |
|
|
|
|
|
if (inputs[CLOCK_INPUT].isConnected()) { |
|
|
|
|
|
clockTimer.process(args.sampleTime); |
|
|
|
|
|
|
|
|
|
|
|
if (clockTrigger.process(inputs[CLOCK_INPUT].getVoltage(), 0.1f, 2.f)) { |
|
|
|
|
|
float clockFreq = 1.f / clockTimer.getTime(); |
|
|
|
|
|
clockTimer.reset(); |
|
|
|
|
|
if (0.001f <= clockFreq && clockFreq <= 1000.f) { |
|
|
|
|
|
this->clockFreq = clockFreq; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
else { |
|
|
|
|
|
// Default frequency when clock is unpatched |
|
|
|
|
|
clockFreq = 2.f; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
int channels = std::max(1, inputs[FM_INPUT].getChannels()); |
|
|
|
|
|
|
|
|
|
|
|
for (int c = 0; c < channels; c += 4) { |
|
|
|
|
|
// Pitch and frequency |
|
|
float_4 pitch = freqParam; |
|
|
float_4 pitch = freqParam; |
|
|
// FM1, polyphonic |
|
|
|
|
|
pitch += inputs[FM1_INPUT].getVoltageSimd<float_4>(c) * fm1Param; |
|
|
|
|
|
// FM2, polyphonic or monophonic |
|
|
|
|
|
pitch += inputs[FM2_INPUT].getPolyVoltageSimd<float_4>(c) * fm2Param; |
|
|
|
|
|
oscillator->setPitch(pitch); |
|
|
|
|
|
|
|
|
pitch += inputs[FM_INPUT].getVoltageSimd<float_4>(c) * fmParam; |
|
|
|
|
|
float_4 freq = clockFreq / 2.f * dsp::approxExp2_taylor5(pitch + 30.f) / std::pow(2.f, 30.f); |
|
|
|
|
|
|
|
|
// Pulse width |
|
|
// Pulse width |
|
|
float_4 pw = pwParam + inputs[PW_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f * pwmParam; |
|
|
|
|
|
oscillator->setPulseWidth(pw); |
|
|
|
|
|
|
|
|
|
|
|
oscillator->step(args.sampleTime); |
|
|
|
|
|
oscillator->setReset(inputs[RESET_INPUT].getPolyVoltageSimd<float_4>(c)); |
|
|
|
|
|
|
|
|
|
|
|
// Outputs |
|
|
|
|
|
if (outputs[SIN_OUTPUT].isConnected()) |
|
|
|
|
|
outputs[SIN_OUTPUT].setVoltageSimd(5.f * oscillator->sin(), c); |
|
|
|
|
|
if (outputs[TRI_OUTPUT].isConnected()) |
|
|
|
|
|
outputs[TRI_OUTPUT].setVoltageSimd(5.f * oscillator->tri(), c); |
|
|
|
|
|
if (outputs[SAW_OUTPUT].isConnected()) |
|
|
|
|
|
outputs[SAW_OUTPUT].setVoltageSimd(5.f * oscillator->saw(), c); |
|
|
|
|
|
if (outputs[SQR_OUTPUT].isConnected()) |
|
|
|
|
|
outputs[SQR_OUTPUT].setVoltageSimd(5.f * oscillator->sqr(), c); |
|
|
|
|
|
|
|
|
float_4 pw = pwParam; |
|
|
|
|
|
pw += inputs[PW_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f * pwmParam; |
|
|
|
|
|
pw = clamp(pw, 0.01f, 0.99f); |
|
|
|
|
|
|
|
|
|
|
|
// Advance phase |
|
|
|
|
|
float_4 deltaPhase = simd::fmin(freq * args.sampleTime, 0.5f); |
|
|
|
|
|
phases[c / 4] += deltaPhase; |
|
|
|
|
|
phases[c / 4] -= simd::trunc(phases[c / 4]); |
|
|
|
|
|
|
|
|
|
|
|
// Reset |
|
|
|
|
|
float_4 reset = inputs[RESET_INPUT].getPolyVoltageSimd<float_4>(c); |
|
|
|
|
|
float_4 resetTriggered = resetTriggers[c / 4].process(reset, 0.1f, 2.f); |
|
|
|
|
|
phases[c / 4] = simd::ifelse(resetTriggered, 0.f, phases[c / 4]); |
|
|
|
|
|
|
|
|
|
|
|
// Sine |
|
|
|
|
|
if (outputs[SIN_OUTPUT].isConnected()) { |
|
|
|
|
|
float_4 p = phases[c / 4]; |
|
|
|
|
|
if (offset) |
|
|
|
|
|
p -= 0.25f; |
|
|
|
|
|
float_4 v = simd::sin(2 * M_PI * p); |
|
|
|
|
|
if (invert) |
|
|
|
|
|
v *= -1.f; |
|
|
|
|
|
if (offset) |
|
|
|
|
|
v += 1.f; |
|
|
|
|
|
outputs[SIN_OUTPUT].setVoltageSimd(5.f * v, c); |
|
|
|
|
|
} |
|
|
|
|
|
// Triangle |
|
|
|
|
|
if (outputs[TRI_OUTPUT].isConnected()) { |
|
|
|
|
|
float_4 p = phases[c / 4]; |
|
|
|
|
|
if (!offset) |
|
|
|
|
|
p += 0.25f; |
|
|
|
|
|
float_4 v = 4.f * simd::fabs(p - simd::round(p)) - 1.f; |
|
|
|
|
|
if (invert) |
|
|
|
|
|
v *= -1.f; |
|
|
|
|
|
if (offset) |
|
|
|
|
|
v += 1.f; |
|
|
|
|
|
outputs[TRI_OUTPUT].setVoltageSimd(5.f * v, c); |
|
|
|
|
|
} |
|
|
|
|
|
// Sawtooth |
|
|
|
|
|
if (outputs[SAW_OUTPUT].isConnected()) { |
|
|
|
|
|
float_4 p = phases[c / 4]; |
|
|
|
|
|
if (offset) |
|
|
|
|
|
p -= 0.5f; |
|
|
|
|
|
float_4 v = 2.f * (p - simd::round(p)); |
|
|
|
|
|
if (invert) |
|
|
|
|
|
v *= -1.f; |
|
|
|
|
|
if (offset) |
|
|
|
|
|
v += 1.f; |
|
|
|
|
|
outputs[SAW_OUTPUT].setVoltageSimd(5.f * v, c); |
|
|
|
|
|
} |
|
|
|
|
|
// Square |
|
|
|
|
|
if (outputs[SQR_OUTPUT].isConnected()) { |
|
|
|
|
|
float_4 v = simd::ifelse(phases[c / 4] < pw, 1.f, -1.f); |
|
|
|
|
|
if (invert) |
|
|
|
|
|
v *= -1.f; |
|
|
|
|
|
if (offset) |
|
|
|
|
|
v += 1.f; |
|
|
|
|
|
outputs[SQR_OUTPUT].setVoltageSimd(5.f * v, c); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
outputs[SIN_OUTPUT].setChannels(channels); |
|
|
outputs[SIN_OUTPUT].setChannels(channels); |
|
@@ -185,9 +198,9 @@ struct LFO : Module { |
|
|
// Light |
|
|
// Light |
|
|
if (lightDivider.process()) { |
|
|
if (lightDivider.process()) { |
|
|
if (channels == 1) { |
|
|
if (channels == 1) { |
|
|
float lightValue = oscillators[0].light().s[0]; |
|
|
|
|
|
lights[PHASE_LIGHT + 0].setSmoothBrightness(-lightValue, args.sampleTime * lightDivider.getDivision()); |
|
|
|
|
|
lights[PHASE_LIGHT + 1].setSmoothBrightness(lightValue, args.sampleTime * lightDivider.getDivision()); |
|
|
|
|
|
|
|
|
float b = 1.f - phases[0][0]; |
|
|
|
|
|
lights[PHASE_LIGHT + 0].setSmoothBrightness(b, args.sampleTime * lightDivider.getDivision()); |
|
|
|
|
|
lights[PHASE_LIGHT + 1].setSmoothBrightness(b, args.sampleTime * lightDivider.getDivision()); |
|
|
lights[PHASE_LIGHT + 2].setBrightness(0.f); |
|
|
lights[PHASE_LIGHT + 2].setBrightness(0.f); |
|
|
} |
|
|
} |
|
|
else { |
|
|
else { |
|
@@ -195,6 +208,41 @@ struct LFO : Module { |
|
|
lights[PHASE_LIGHT + 1].setBrightness(0.f); |
|
|
lights[PHASE_LIGHT + 1].setBrightness(0.f); |
|
|
lights[PHASE_LIGHT + 2].setBrightness(1.f); |
|
|
lights[PHASE_LIGHT + 2].setBrightness(1.f); |
|
|
} |
|
|
} |
|
|
|
|
|
lights[OFFSET_LIGHT].setBrightness(offset); |
|
|
|
|
|
lights[INVERT_LIGHT].setBrightness(invert); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
json_t* dataToJson() override { |
|
|
|
|
|
json_t* rootJ = json_object(); |
|
|
|
|
|
// offset |
|
|
|
|
|
json_object_set_new(rootJ, "offset", json_boolean(offset)); |
|
|
|
|
|
// invert |
|
|
|
|
|
json_object_set_new(rootJ, "invert", json_boolean(invert)); |
|
|
|
|
|
return rootJ; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void dataFromJson(json_t* rootJ) override { |
|
|
|
|
|
// offset |
|
|
|
|
|
json_t* offsetJ = json_object_get(rootJ, "offset"); |
|
|
|
|
|
if (offsetJ) |
|
|
|
|
|
offset = json_boolean_value(offsetJ); |
|
|
|
|
|
// invert |
|
|
|
|
|
json_t* invertJ = json_object_get(rootJ, "invert"); |
|
|
|
|
|
if (invertJ) |
|
|
|
|
|
invert = json_boolean_value(invertJ); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void paramsFromJson(json_t* rootJ) override { |
|
|
|
|
|
Module::paramsFromJson(rootJ); |
|
|
|
|
|
// In <2.0, OFFSET_PARAM and INVERT_PARAM were toggle switches instead of momentary buttons, so if params are on after deserializing, set boolean states instead. |
|
|
|
|
|
if (params[OFFSET_PARAM].getValue() > 0.f) { |
|
|
|
|
|
offset = true; |
|
|
|
|
|
params[OFFSET_PARAM].setValue(0.f); |
|
|
|
|
|
} |
|
|
|
|
|
if (params[INVERT_PARAM].getValue() > 0.f) { |
|
|
|
|
|
invert = true; |
|
|
|
|
|
params[INVERT_PARAM].setValue(0.f); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
@@ -212,13 +260,13 @@ struct LFOWidget : ModuleWidget { |
|
|
|
|
|
|
|
|
addParam(createParamCentered<RoundHugeBlackKnob>(mm2px(Vec(22.902, 29.803)), module, LFO::FREQ_PARAM)); |
|
|
addParam(createParamCentered<RoundHugeBlackKnob>(mm2px(Vec(22.902, 29.803)), module, LFO::FREQ_PARAM)); |
|
|
addParam(createParamCentered<RoundLargeBlackKnob>(mm2px(Vec(22.861, 56.388)), module, LFO::PW_PARAM)); |
|
|
addParam(createParamCentered<RoundLargeBlackKnob>(mm2px(Vec(22.861, 56.388)), module, LFO::PW_PARAM)); |
|
|
addParam(createParamCentered<Trimpot>(mm2px(Vec(6.604, 80.603)), module, LFO::FM1_PARAM)); |
|
|
|
|
|
// addParam(createParamCentered<LEDButton>(mm2px(Vec(17.441, 80.603)), module, LFO::INV_PARAM)); |
|
|
|
|
|
// addParam(createParamCentered<LEDButton>(mm2px(Vec(28.279, 80.603)), module, LFO::OFST_PARAM)); |
|
|
|
|
|
|
|
|
addParam(createParamCentered<Trimpot>(mm2px(Vec(6.604, 80.603)), module, LFO::FM_PARAM)); |
|
|
|
|
|
addParam(createLightParamCentered<LEDLightButton<MediumSimpleLight<YellowLight>>>(mm2px(Vec(17.441, 80.603)), module, LFO::INVERT_PARAM, LFO::INVERT_LIGHT)); |
|
|
|
|
|
addParam(createLightParamCentered<LEDLightButton<MediumSimpleLight<YellowLight>>>(mm2px(Vec(28.279, 80.603)), module, LFO::OFFSET_PARAM, LFO::OFFSET_LIGHT)); |
|
|
addParam(createParamCentered<Trimpot>(mm2px(Vec(39.116, 80.603)), module, LFO::PWM_PARAM)); |
|
|
addParam(createParamCentered<Trimpot>(mm2px(Vec(39.116, 80.603)), module, LFO::PWM_PARAM)); |
|
|
|
|
|
|
|
|
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(6.604, 96.859)), module, LFO::FM1_INPUT)); |
|
|
|
|
|
// addInput(createInputCentered<PJ301MPort>(mm2px(Vec(17.441, 96.859)), module, LFO::CLK_INPUT)); |
|
|
|
|
|
|
|
|
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(6.604, 96.859)), module, LFO::FM_INPUT)); |
|
|
|
|
|
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(17.441, 96.859)), module, LFO::CLOCK_INPUT)); |
|
|
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(28.279, 96.819)), module, LFO::RESET_INPUT)); |
|
|
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(28.279, 96.819)), module, LFO::RESET_INPUT)); |
|
|
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(39.116, 96.819)), module, LFO::PW_INPUT)); |
|
|
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(39.116, 96.819)), module, LFO::PW_INPUT)); |
|
|
|
|
|
|
|
@@ -233,130 +281,3 @@ struct LFOWidget : ModuleWidget { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Model* modelLFO = createModel<LFO, LFOWidget>("LFO"); |
|
|
Model* modelLFO = createModel<LFO, LFOWidget>("LFO"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#if 0 |
|
|
|
|
|
struct LFO2 : Module { |
|
|
|
|
|
enum ParamIds { |
|
|
|
|
|
OFFSET_PARAM, |
|
|
|
|
|
INVERT_PARAM, |
|
|
|
|
|
FREQ_PARAM, |
|
|
|
|
|
WAVE_PARAM, |
|
|
|
|
|
FM_PARAM, |
|
|
|
|
|
NUM_PARAMS |
|
|
|
|
|
}; |
|
|
|
|
|
enum InputIds { |
|
|
|
|
|
FM_INPUT, |
|
|
|
|
|
RESET_INPUT, |
|
|
|
|
|
WAVE_INPUT, |
|
|
|
|
|
NUM_INPUTS |
|
|
|
|
|
}; |
|
|
|
|
|
enum OutputIds { |
|
|
|
|
|
INTERP_OUTPUT, |
|
|
|
|
|
NUM_OUTPUTS |
|
|
|
|
|
}; |
|
|
|
|
|
enum LightIds { |
|
|
|
|
|
ENUMS(PHASE_LIGHT, 3), |
|
|
|
|
|
NUM_LIGHTS |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
LowFrequencyOscillator<float_4> oscillators[4]; |
|
|
|
|
|
dsp::ClockDivider lightDivider; |
|
|
|
|
|
|
|
|
|
|
|
LFO2() { |
|
|
|
|
|
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); |
|
|
|
|
|
configSwitch(OFFSET_PARAM, 0.f, 1.f, 1.f, "Offset", {"Bipolar", "Unipolar"}); |
|
|
|
|
|
configSwitch(INVERT_PARAM, 0.f, 1.f, 1.f, "Orientation", {"Inverted", "Normal"}); |
|
|
|
|
|
configParam(FREQ_PARAM, -8.f, 10.f, 1.f, "Frequency", " Hz", 2, 1); |
|
|
|
|
|
configParam(WAVE_PARAM, 0.f, 3.f, 1.5f, "Wave"); |
|
|
|
|
|
configParam(FM_PARAM, 0.f, 1.f, 1.f, "Frequency modulation", "%", 0.f, 100.f); |
|
|
|
|
|
configInput(FM_INPUT, "Frequency modulation"); |
|
|
|
|
|
configInput(RESET_INPUT, "Reset"); |
|
|
|
|
|
configInput(WAVE_INPUT, "Wave type"); |
|
|
|
|
|
configOutput(INTERP_OUTPUT, "Audio"); |
|
|
|
|
|
configLight(PHASE_LIGHT, "Phase"); |
|
|
|
|
|
lightInfos[PHASE_LIGHT]->description = "Tracks the sine output.\nGreen if positive, red if negative, blue if polyphonic."; |
|
|
|
|
|
|
|
|
|
|
|
lightDivider.setDivision(16); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void process(const ProcessArgs& args) override { |
|
|
|
|
|
float freqParam = params[FREQ_PARAM].getValue(); |
|
|
|
|
|
float fmParam = params[FM_PARAM].getValue(); |
|
|
|
|
|
float waveParam = params[WAVE_PARAM].getValue(); |
|
|
|
|
|
|
|
|
|
|
|
int channels = std::max(1, inputs[FM_INPUT].getChannels()); |
|
|
|
|
|
|
|
|
|
|
|
for (int c = 0; c < channels; c += 4) { |
|
|
|
|
|
auto* oscillator = &oscillators[c / 4]; |
|
|
|
|
|
oscillator->invert = (params[INVERT_PARAM].getValue() == 0.f); |
|
|
|
|
|
oscillator->bipolar = (params[OFFSET_PARAM].getValue() == 0.f); |
|
|
|
|
|
|
|
|
|
|
|
float_4 pitch = freqParam + inputs[FM_INPUT].getVoltageSimd<float_4>(c) * fmParam; |
|
|
|
|
|
oscillator->setPitch(pitch); |
|
|
|
|
|
|
|
|
|
|
|
oscillator->step(args.sampleTime); |
|
|
|
|
|
oscillator->setReset(inputs[RESET_INPUT].getPolyVoltageSimd<float_4>(c)); |
|
|
|
|
|
|
|
|
|
|
|
// Outputs |
|
|
|
|
|
if (outputs[INTERP_OUTPUT].isConnected()) { |
|
|
|
|
|
float_4 wave = simd::clamp(waveParam + inputs[WAVE_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f * 3.f, 0.f, 3.f); |
|
|
|
|
|
float_4 v = 0.f; |
|
|
|
|
|
v += oscillator->sin() * simd::fmax(0.f, 1.f - simd::fabs(wave - 0.f)); |
|
|
|
|
|
v += oscillator->tri() * simd::fmax(0.f, 1.f - simd::fabs(wave - 1.f)); |
|
|
|
|
|
v += oscillator->saw() * simd::fmax(0.f, 1.f - simd::fabs(wave - 2.f)); |
|
|
|
|
|
v += oscillator->sqr() * simd::fmax(0.f, 1.f - simd::fabs(wave - 3.f)); |
|
|
|
|
|
outputs[INTERP_OUTPUT].setVoltageSimd(5.f * v, c); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
outputs[INTERP_OUTPUT].setChannels(channels); |
|
|
|
|
|
|
|
|
|
|
|
// Light |
|
|
|
|
|
if (lightDivider.process()) { |
|
|
|
|
|
if (channels == 1) { |
|
|
|
|
|
float lightValue = oscillators[0].light().s[0]; |
|
|
|
|
|
lights[PHASE_LIGHT + 0].setSmoothBrightness(-lightValue, args.sampleTime * lightDivider.getDivision()); |
|
|
|
|
|
lights[PHASE_LIGHT + 1].setSmoothBrightness(lightValue, args.sampleTime * lightDivider.getDivision()); |
|
|
|
|
|
lights[PHASE_LIGHT + 2].setBrightness(0.f); |
|
|
|
|
|
} |
|
|
|
|
|
else { |
|
|
|
|
|
lights[PHASE_LIGHT + 0].setBrightness(0.f); |
|
|
|
|
|
lights[PHASE_LIGHT + 1].setBrightness(0.f); |
|
|
|
|
|
lights[PHASE_LIGHT + 2].setBrightness(1.f); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct LFO2Widget : ModuleWidget { |
|
|
|
|
|
LFO2Widget(LFO2* module) { |
|
|
|
|
|
setModule(module); |
|
|
|
|
|
setPanel(createPanel(asset::plugin(pluginInstance, "res/WTLFO.svg"))); |
|
|
|
|
|
|
|
|
|
|
|
addChild(createWidget<ScrewSilver>(Vec(15, 0))); |
|
|
|
|
|
addChild(createWidget<ScrewSilver>(Vec(box.size.x - 30, 0))); |
|
|
|
|
|
addChild(createWidget<ScrewSilver>(Vec(15, 365))); |
|
|
|
|
|
addChild(createWidget<ScrewSilver>(Vec(box.size.x - 30, 365))); |
|
|
|
|
|
|
|
|
|
|
|
addParam(createParam<CKSS>(Vec(62, 150), module, LFO2::OFFSET_PARAM)); |
|
|
|
|
|
addParam(createParam<CKSS>(Vec(62, 215), module, LFO2::INVERT_PARAM)); |
|
|
|
|
|
|
|
|
|
|
|
addParam(createParam<RoundHugeBlackKnob>(Vec(18, 60), module, LFO2::FREQ_PARAM)); |
|
|
|
|
|
addParam(createParam<RoundLargeBlackKnob>(Vec(11, 142), module, LFO2::WAVE_PARAM)); |
|
|
|
|
|
addParam(createParam<RoundLargeBlackKnob>(Vec(11, 207), module, LFO2::FM_PARAM)); |
|
|
|
|
|
|
|
|
|
|
|
addInput(createInput<PJ301MPort>(Vec(11, 276), module, LFO2::FM_INPUT)); |
|
|
|
|
|
addInput(createInput<PJ301MPort>(Vec(54, 276), module, LFO2::RESET_INPUT)); |
|
|
|
|
|
addInput(createInput<PJ301MPort>(Vec(11, 319), module, LFO2::WAVE_INPUT)); |
|
|
|
|
|
|
|
|
|
|
|
addOutput(createOutput<PJ301MPort>(Vec(54, 319), module, LFO2::INTERP_OUTPUT)); |
|
|
|
|
|
|
|
|
|
|
|
addChild(createLight<SmallLight<RedGreenBlueLight>>(Vec(68, 42.5f), module, LFO2::PHASE_LIGHT)); |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Model* modelLFO2 = createModel<LFO2, LFO2Widget>("LFO2"); |
|
|
|
|
|
#endif |
|
|
|