@@ -131,18 +131,18 @@ struct LFO : Module { | |||||
// FM1, polyphonic | // FM1, polyphonic | ||||
pitch += float_4::load(inputs[FM1_INPUT].getVoltages(c)) * fm1Param; | pitch += float_4::load(inputs[FM1_INPUT].getVoltages(c)) * fm1Param; | ||||
// FM2, polyphonic or monophonic | // FM2, polyphonic or monophonic | ||||
if (inputs[FM2_INPUT].getChannels() == 1) | |||||
pitch += inputs[FM2_INPUT].getVoltage() * fm2Param; | |||||
else | |||||
if (inputs[FM2_INPUT].isPolyphonic()) | |||||
pitch += float_4::load(inputs[FM2_INPUT].getVoltages(c)) * fm2Param; | pitch += float_4::load(inputs[FM2_INPUT].getVoltages(c)) * fm2Param; | ||||
else | |||||
pitch += inputs[FM2_INPUT].getVoltage() * fm2Param; | |||||
oscillator->setPitch(pitch); | oscillator->setPitch(pitch); | ||||
// Pulse width | // Pulse width | ||||
float_4 pw = pwParam; | float_4 pw = pwParam; | ||||
if (inputs[PW_INPUT].getChannels() == 1) | |||||
pw += inputs[PW_INPUT].getVoltage() / 10.f * pwmParam; | |||||
else | |||||
if (inputs[PW_INPUT].isPolyphonic()) | |||||
pw += float_4::load(inputs[PW_INPUT].getVoltages(c)) / 10.f * pwmParam; | pw += float_4::load(inputs[PW_INPUT].getVoltages(c)) / 10.f * pwmParam; | ||||
else | |||||
pw += inputs[PW_INPUT].getVoltage() / 10.f * pwmParam; | |||||
oscillator->setPulseWidth(pw); | oscillator->setPulseWidth(pw); | ||||
// Settings | // Settings | ||||
@@ -284,10 +284,10 @@ struct LFO2 : Module { | |||||
// Wave | // Wave | ||||
float_4 wave = waveParam; | float_4 wave = waveParam; | ||||
inputs[WAVE_INPUT].getVoltage(); | inputs[WAVE_INPUT].getVoltage(); | ||||
if (inputs[WAVE_INPUT].getChannels() == 1) | |||||
wave += inputs[WAVE_INPUT].getVoltage() / 10.f * 3.f; | |||||
else | |||||
if (inputs[WAVE_INPUT].isPolyphonic()) | |||||
wave += float_4::load(inputs[WAVE_INPUT].getVoltages(c)) / 10.f * 3.f; | wave += float_4::load(inputs[WAVE_INPUT].getVoltages(c)) / 10.f * 3.f; | ||||
else | |||||
wave += inputs[WAVE_INPUT].getVoltage() / 10.f * 3.f; | |||||
wave = clamp(wave, 0.f, 3.f); | wave = clamp(wave, 0.f, 3.f); | ||||
// Settings | // Settings | ||||
@@ -42,7 +42,8 @@ struct Merge : Module { | |||||
outputs[POLY_OUTPUT].setVoltage(v, c); | outputs[POLY_OUTPUT].setVoltage(v, c); | ||||
} | } | ||||
outputs[POLY_OUTPUT].setChannels((channels >= 0) ? channels : (lastChannel + 1)); | |||||
// In order to allow 0 channels, modify channels directly instead of using `setChannels()` | |||||
outputs[POLY_OUTPUT].channels = (channels >= 0) ? channels : (lastChannel + 1); | |||||
// Set channel lights infrequently | // Set channel lights infrequently | ||||
if (lightDivider.process()) { | if (lightDivider.process()) { | ||||
@@ -85,13 +85,13 @@ struct Scope : Module { | |||||
int frameCount = (int) std::ceil(deltaTime * args.sampleRate); | int frameCount = (int) std::ceil(deltaTime * args.sampleRate); | ||||
// Set channels | // Set channels | ||||
int channelsX = inputs[X_INPUT].isConnected() ? inputs[X_INPUT].getChannels() : 0; | |||||
int channelsX = inputs[X_INPUT].getChannels(); | |||||
if (channelsX != this->channelsX) { | if (channelsX != this->channelsX) { | ||||
std::memset(bufferX, 0, sizeof(bufferX)); | std::memset(bufferX, 0, sizeof(bufferX)); | ||||
this->channelsX = channelsX; | this->channelsX = channelsX; | ||||
} | } | ||||
int channelsY = inputs[Y_INPUT].isConnected() ? inputs[Y_INPUT].getChannels() : 0; | |||||
int channelsY = inputs[Y_INPUT].getChannels(); | |||||
if (channelsY != this->channelsY) { | if (channelsY != this->channelsY) { | ||||
std::memset(bufferY, 0, sizeof(bufferY)); | std::memset(bufferY, 0, sizeof(bufferY)); | ||||
this->channelsY = channelsY; | this->channelsY = channelsY; | ||||
@@ -28,7 +28,7 @@ struct Split : Module { | |||||
void process(const ProcessArgs &args) override { | void process(const ProcessArgs &args) override { | ||||
for (int c = 0; c < 16; c++) { | for (int c = 0; c < 16; c++) { | ||||
float v = inputs[POLY_INPUT].getVoltage(c); | float v = inputs[POLY_INPUT].getVoltage(c); | ||||
// To allow users to debug buggy modules, don't assume that undefined channels are 0V. | |||||
// To allow users to debug buggy modules, don't assume that undefined channel voltages are 0V. | |||||
outputs[MONO_OUTPUTS + c].setVoltage(v); | outputs[MONO_OUTPUTS + c].setVoltage(v); | ||||
} | } | ||||
@@ -29,9 +29,6 @@ struct VCA : Module { | |||||
} | } | ||||
void processChannel(Input &in, Param &level, Input &lin, Input &exp, Output &out) { | void processChannel(Input &in, Param &level, Input &lin, Input &exp, Output &out) { | ||||
if (!in.isConnected() || !out.isConnected()) | |||||
return; | |||||
// Get input | // Get input | ||||
int channels = in.getChannels(); | int channels = in.getChannels(); | ||||
simd::float_4 v[4]; | simd::float_4 v[4]; | ||||
@@ -47,17 +44,17 @@ struct VCA : Module { | |||||
// Apply linear CV gain | // Apply linear CV gain | ||||
if (lin.isConnected()) { | if (lin.isConnected()) { | ||||
if (lin.getChannels() == 1) { | |||||
float cv = lin.getVoltage() / 10.f; | |||||
cv = clamp(cv, 0.f, 1.f); | |||||
if (lin.isPolyphonic()) { | |||||
for (int c = 0; c < channels; c += 4) { | for (int c = 0; c < channels; c += 4) { | ||||
simd::float_4 cv = simd::float_4::load(lin.getVoltages(c)) / 10.f; | |||||
cv = clamp(cv, 0.f, 1.f); | |||||
v[c / 4] *= cv; | v[c / 4] *= cv; | ||||
} | } | ||||
} | } | ||||
else { | else { | ||||
float cv = lin.getVoltage() / 10.f; | |||||
cv = clamp(cv, 0.f, 1.f); | |||||
for (int c = 0; c < channels; c += 4) { | for (int c = 0; c < channels; c += 4) { | ||||
simd::float_4 cv = simd::float_4::load(lin.getVoltages(c)) / 10.f; | |||||
cv = clamp(cv, 0.f, 1.f); | |||||
v[c / 4] *= cv; | v[c / 4] *= cv; | ||||
} | } | ||||
} | } | ||||
@@ -66,19 +63,19 @@ struct VCA : Module { | |||||
// Apply exponential CV gain | // Apply exponential CV gain | ||||
const float expBase = 50.f; | const float expBase = 50.f; | ||||
if (exp.isConnected()) { | if (exp.isConnected()) { | ||||
if (exp.getChannels() == 1) { | |||||
float cv = exp.getVoltage() / 10.f; | |||||
cv = clamp(cv, 0.f, 1.f); | |||||
cv = rescale(std::pow(expBase, cv), 1.f, expBase, 0.f, 1.f); | |||||
if (exp.isPolyphonic()) { | |||||
for (int c = 0; c < channels; c += 4) { | for (int c = 0; c < channels; c += 4) { | ||||
simd::float_4 cv = simd::float_4::load(exp.getVoltages(c)) / 10.f; | |||||
cv = clamp(cv, 0.f, 1.f); | |||||
cv = rescale(pow(expBase, cv), 1.f, expBase, 0.f, 1.f); | |||||
v[c / 4] *= cv; | v[c / 4] *= cv; | ||||
} | } | ||||
} | } | ||||
else { | else { | ||||
float cv = exp.getVoltage() / 10.f; | |||||
cv = clamp(cv, 0.f, 1.f); | |||||
cv = rescale(std::pow(expBase, cv), 1.f, expBase, 0.f, 1.f); | |||||
for (int c = 0; c < channels; c += 4) { | for (int c = 0; c < channels; c += 4) { | ||||
simd::float_4 cv = simd::float_4::load(exp.getVoltages(c)) / 10.f; | |||||
cv = clamp(cv, 0.f, 1.f); | |||||
cv = rescale(pow(expBase, cv), 1.f, expBase, 0.f, 1.f); | |||||
v[c / 4] *= cv; | v[c / 4] *= cv; | ||||
} | } | ||||
} | } | ||||
@@ -128,12 +128,10 @@ struct VCF : Module { | |||||
// Drive gain | // Drive gain | ||||
float_4 drive = driveParam; | float_4 drive = driveParam; | ||||
if (inputs[DRIVE_INPUT].isConnected()) { | |||||
if (inputs[DRIVE_INPUT].isMonophonic()) | |||||
drive += inputs[DRIVE_INPUT].getVoltage() / 10.f; | |||||
else | |||||
drive += float_4::load(inputs[DRIVE_INPUT].getVoltages(c)) / 10.f; | |||||
} | |||||
if (inputs[DRIVE_INPUT].isPolyphonic()) | |||||
drive += float_4::load(inputs[DRIVE_INPUT].getVoltages(c)) / 10.f; | |||||
else | |||||
drive += inputs[DRIVE_INPUT].getVoltage() / 10.f; | |||||
drive = clamp(drive, 0.f, 1.f); | drive = clamp(drive, 0.f, 1.f); | ||||
float_4 gain = simd::pow(1.f + drive, 5); | float_4 gain = simd::pow(1.f + drive, 5); | ||||
input *= gain; | input *= gain; | ||||
@@ -143,23 +141,19 @@ struct VCF : Module { | |||||
// Set resonance | // Set resonance | ||||
float_4 resonance = resParam; | float_4 resonance = resParam; | ||||
if (inputs[RES_INPUT].isConnected()) { | |||||
if (inputs[RES_INPUT].isMonophonic()) | |||||
resonance += inputs[RES_INPUT].getVoltage() / 10.f; | |||||
else | |||||
resonance += float_4::load(inputs[RES_INPUT].getVoltages(c)) / 10.f; | |||||
} | |||||
if (inputs[RES_INPUT].isPolyphonic()) | |||||
resonance += float_4::load(inputs[RES_INPUT].getVoltages(c)) / 10.f; | |||||
else | |||||
resonance += inputs[RES_INPUT].getVoltage() / 10.f; | |||||
resonance = clamp(resonance, 0.f, 1.f); | resonance = clamp(resonance, 0.f, 1.f); | ||||
filter->resonance = simd::pow(resonance, 2) * 10.f; | filter->resonance = simd::pow(resonance, 2) * 10.f; | ||||
// Get pitch | // Get pitch | ||||
float_4 pitch = 0.f; | float_4 pitch = 0.f; | ||||
if (inputs[FREQ_INPUT].isConnected()) { | |||||
if (inputs[FREQ_INPUT].isMonophonic()) | |||||
pitch += inputs[FREQ_INPUT].getVoltage() * freqCvParam; | |||||
else | |||||
pitch += float_4::load(inputs[FREQ_INPUT].getVoltages(c)) * freqCvParam; | |||||
} | |||||
if (inputs[FREQ_INPUT].isPolyphonic()) | |||||
pitch += float_4::load(inputs[FREQ_INPUT].getVoltages(c)) * freqCvParam; | |||||
else | |||||
pitch += inputs[FREQ_INPUT].getVoltage() * freqCvParam; | |||||
pitch += freqParam; | pitch += freqParam; | ||||
pitch += fineParam; | pitch += fineParam; | ||||
// Set cutoff | // Set cutoff | ||||
@@ -17,7 +17,7 @@ struct Viz : Module { | |||||
NUM_LIGHTS | NUM_LIGHTS | ||||
}; | }; | ||||
int lastChannels = 0; | |||||
int lastChannel = 0; | |||||
dsp::ClockDivider lightDivider; | dsp::ClockDivider lightDivider; | ||||
Viz() { | Viz() { | ||||
@@ -27,7 +27,7 @@ struct Viz : Module { | |||||
void process(const ProcessArgs &args) override { | void process(const ProcessArgs &args) override { | ||||
if (lightDivider.process()) { | if (lightDivider.process()) { | ||||
lastChannels = inputs[POLY_INPUT].getChannels(); | |||||
lastChannel = inputs[POLY_INPUT].getChannels(); | |||||
float deltaTime = args.sampleTime * lightDivider.getDivision(); | float deltaTime = args.sampleTime * lightDivider.getDivision(); | ||||
for (int c = 0; c < 16; c++) { | for (int c = 0; c < 16; c++) { | ||||
@@ -58,7 +58,7 @@ struct VizDisplay : Widget { | |||||
nvgFontSize(args.vg, 11); | nvgFontSize(args.vg, 11); | ||||
nvgTextLetterSpacing(args.vg, 0.0); | nvgTextLetterSpacing(args.vg, 0.0); | ||||
nvgTextAlign(args.vg, NVG_ALIGN_CENTER | NVG_ALIGN_BASELINE); | nvgTextAlign(args.vg, NVG_ALIGN_CENTER | NVG_ALIGN_BASELINE); | ||||
if (module && c < module->lastChannels) | |||||
if (module && c < module->lastChannel) | |||||
nvgFillColor(args.vg, nvgRGB(255, 255, 255)); | nvgFillColor(args.vg, nvgRGB(255, 255, 255)); | ||||
else | else | ||||
nvgFillColor(args.vg, nvgRGB(99, 99, 99)); | nvgFillColor(args.vg, nvgRGB(99, 99, 99)); | ||||