|  | @@ -0,0 +1,549 @@ | 
														
													
														
															
																|  |  |  |  |  | #include "plugin.hpp" | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | using simd::float_4; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | // from https://github.com/wiqid/repelzen/blob/master/src/aefilter.hpp (29307df4fd3e713d206f2155dcff0337fc067f1f) | 
														
													
														
															
																|  |  |  |  |  | // with permission (GPL-3.0-or-later) | 
														
													
														
															
																|  |  |  |  |  | enum AeFilterType { | 
														
													
														
															
																|  |  |  |  |  | AeLOWPASS, | 
														
													
														
															
																|  |  |  |  |  | AeHIGHPASS | 
														
													
														
															
																|  |  |  |  |  | }; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | enum AeEQType { | 
														
													
														
															
																|  |  |  |  |  | AeLOWSHELVE, | 
														
													
														
															
																|  |  |  |  |  | AeHIGHSHELVE, | 
														
													
														
															
																|  |  |  |  |  | AePEAKINGEQ | 
														
													
														
															
																|  |  |  |  |  | }; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | template <typename T> | 
														
													
														
															
																|  |  |  |  |  | struct AeFilter { | 
														
													
														
															
																|  |  |  |  |  | T x[2] = {}; | 
														
													
														
															
																|  |  |  |  |  | T y[2] = {}; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | float a0, a1, a2, b0, b1, b2; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | inline T process(const T& in) noexcept { | 
														
													
														
															
																|  |  |  |  |  | T out = b0 * in + b1 * x[0] + b2 * x[1] - a1 * y[0] - a2 * y[1]; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | //shift buffers | 
														
													
														
															
																|  |  |  |  |  | x[1] = x[0]; | 
														
													
														
															
																|  |  |  |  |  | x[0] = in; | 
														
													
														
															
																|  |  |  |  |  | y[1] = y[0]; | 
														
													
														
															
																|  |  |  |  |  | y[0] = out; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | return out; | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | void setCutoff(float f, float q, int type) { | 
														
													
														
															
																|  |  |  |  |  | const float w0 = 2 * M_PI * f / APP->engine->getSampleRate(); | 
														
													
														
															
																|  |  |  |  |  | const float alpha = std::sin(w0) / (2.0f * q); | 
														
													
														
															
																|  |  |  |  |  | const float cs0 = std::cos(w0); | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | switch (type) { | 
														
													
														
															
																|  |  |  |  |  | case AeLOWPASS: | 
														
													
														
															
																|  |  |  |  |  | a0 = 1 + alpha; | 
														
													
														
															
																|  |  |  |  |  | b0 = (1 - cs0) / 2 / a0; | 
														
													
														
															
																|  |  |  |  |  | b1 = (1 - cs0) / a0; | 
														
													
														
															
																|  |  |  |  |  | b2 = (1 - cs0) / 2 / a0; | 
														
													
														
															
																|  |  |  |  |  | a1 = (-2 * cs0) / a0; | 
														
													
														
															
																|  |  |  |  |  | a2 = (1 - alpha) / a0; | 
														
													
														
															
																|  |  |  |  |  | break; | 
														
													
														
															
																|  |  |  |  |  | case AeHIGHPASS: | 
														
													
														
															
																|  |  |  |  |  | a0 = 1 + alpha; | 
														
													
														
															
																|  |  |  |  |  | b0 = (1 + cs0) / 2 / a0; | 
														
													
														
															
																|  |  |  |  |  | b1 = -(1 + cs0) / a0; | 
														
													
														
															
																|  |  |  |  |  | b2 = (1 + cs0) / 2 / a0; | 
														
													
														
															
																|  |  |  |  |  | a1 = -2 * cs0 / a0; | 
														
													
														
															
																|  |  |  |  |  | a2 = (1 - alpha) / a0; | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | }; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | template <typename T> | 
														
													
														
															
																|  |  |  |  |  | struct AeFilterStereo : AeFilter<T> { | 
														
													
														
															
																|  |  |  |  |  | T xl[2] = {}; | 
														
													
														
															
																|  |  |  |  |  | T xr[2] = {}; | 
														
													
														
															
																|  |  |  |  |  | T yl[2] = {}; | 
														
													
														
															
																|  |  |  |  |  | T yr[2] = {}; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | void process(T* inL, T* inR) { | 
														
													
														
															
																|  |  |  |  |  | T l = AeFilter<T>::b0 * *inL + AeFilter<T>::b1 * xl[0] + AeFilter<T>::b2 * xl[1] - AeFilter<T>::a1 * yl[0] - AeFilter<T>::a2 * yl[1]; | 
														
													
														
															
																|  |  |  |  |  | T r = AeFilter<T>::b0 * *inR + AeFilter<T>::b1 * xr[0] + AeFilter<T>::b2 * xr[1] - AeFilter<T>::a1 * yr[0] - AeFilter<T>::a2 * yr[1]; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | //shift buffers | 
														
													
														
															
																|  |  |  |  |  | xl[1] = xl[0]; | 
														
													
														
															
																|  |  |  |  |  | xl[0] = *inL; | 
														
													
														
															
																|  |  |  |  |  | xr[1] = xr[0]; | 
														
													
														
															
																|  |  |  |  |  | xr[0] = *inR; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | yl[1] = yl[0]; | 
														
													
														
															
																|  |  |  |  |  | yl[0] = l; | 
														
													
														
															
																|  |  |  |  |  | yr[1] = yr[0]; | 
														
													
														
															
																|  |  |  |  |  | yr[0] = r; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | *inL = l; | 
														
													
														
															
																|  |  |  |  |  | *inR = r; | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | }; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | template <typename T> | 
														
													
														
															
																|  |  |  |  |  | struct AeEqualizer { | 
														
													
														
															
																|  |  |  |  |  | T x[2] = {}; | 
														
													
														
															
																|  |  |  |  |  | T y[2] = {}; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | float a0, a1, a2, b0, b1, b2; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | T process(T in) { | 
														
													
														
															
																|  |  |  |  |  | T out = b0 * in + b1 * x[0] + b2 * x[1] - a1 * y[0] - a2 * y[1]; | 
														
													
														
															
																|  |  |  |  |  | //shift buffers | 
														
													
														
															
																|  |  |  |  |  | x[1] = x[0]; | 
														
													
														
															
																|  |  |  |  |  | x[0] = in; | 
														
													
														
															
																|  |  |  |  |  | y[1] = y[0]; | 
														
													
														
															
																|  |  |  |  |  | y[0] = out; | 
														
													
														
															
																|  |  |  |  |  | return out; | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | void setParams(float f, float q, float gaindb, AeEQType type) { | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | const float w0 = 2 * M_PI * f / APP->engine->getSampleRate(); | 
														
													
														
															
																|  |  |  |  |  | const float alpha = sin(w0) / (2.0f * q); | 
														
													
														
															
																|  |  |  |  |  | const float cs0 = cos(w0); | 
														
													
														
															
																|  |  |  |  |  | const float A = pow(10, gaindb / 40.0f); | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | switch (type) { | 
														
													
														
															
																|  |  |  |  |  | case AeLOWSHELVE: | 
														
													
														
															
																|  |  |  |  |  | a0 = (A + 1.0f) + (A - 1.0f) * cs0 + 2 * sqrt(A) * alpha; | 
														
													
														
															
																|  |  |  |  |  | b0 = A * ((A + 1.0f) - (A - 1.0f) * cs0 + 2 * sqrt(A) * alpha) / a0; | 
														
													
														
															
																|  |  |  |  |  | b1 = 2.0f * A * ((A - 1.0f) - (A + 1.0f) * cs0) / a0; | 
														
													
														
															
																|  |  |  |  |  | b2 = A * ((A + 1.0f) - (A - 1.0f) * cs0 - 2.0f * sqrt(A) * alpha) / a0; | 
														
													
														
															
																|  |  |  |  |  | a1 = -2.0f * ((A - 1.0f) + (A + 1.0f) * cs0) / a0; | 
														
													
														
															
																|  |  |  |  |  | a2 = ((A + 1.0f) + (A - 1.0f) * cs0 - 2.0f * sqrt(A) * alpha) / a0; | 
														
													
														
															
																|  |  |  |  |  | break; | 
														
													
														
															
																|  |  |  |  |  | case AeHIGHSHELVE: | 
														
													
														
															
																|  |  |  |  |  | a0 = (A + 1.0f) - (A - 1.0f) * cs0 + 2 * sqrt(A) * alpha; | 
														
													
														
															
																|  |  |  |  |  | b0 = A * ((A + 1.0f) + (A - 1.0f) * cs0 + 2 * sqrt(A) * alpha) / a0; | 
														
													
														
															
																|  |  |  |  |  | b1 = -2.0f * A * ((A - 1.0f) + (A + 1.0f) * cs0) / a0; | 
														
													
														
															
																|  |  |  |  |  | b2 = A * ((A + 1.0f) + (A - 1.0f) * cs0 - 2.0f * sqrt(A) * alpha) / a0; | 
														
													
														
															
																|  |  |  |  |  | a1 = 2.0f * ((A - 1.0f) - (A + 1.0f) * cs0) / a0; | 
														
													
														
															
																|  |  |  |  |  | a2 = ((A + 1.0f) - (A - 1.0f) * cs0 - 2.0f * sqrt(A) * alpha) / a0; | 
														
													
														
															
																|  |  |  |  |  | break; | 
														
													
														
															
																|  |  |  |  |  | case AePEAKINGEQ: | 
														
													
														
															
																|  |  |  |  |  | a0 = 1.0f + alpha / A; | 
														
													
														
															
																|  |  |  |  |  | b0 = (1.0f + alpha * A) / a0; | 
														
													
														
															
																|  |  |  |  |  | b1 = -2.0f * cs0 / a0; | 
														
													
														
															
																|  |  |  |  |  | b2 = (1.0f - alpha * A) / a0; | 
														
													
														
															
																|  |  |  |  |  | a1 = -2.0f * cs0 / a0; | 
														
													
														
															
																|  |  |  |  |  | a2 = (1.0f - alpha / A) / a0; | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | }; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | template <typename T> | 
														
													
														
															
																|  |  |  |  |  | struct AeEqualizerStereo : AeEqualizer<T> { | 
														
													
														
															
																|  |  |  |  |  | T xl[2] = {}; | 
														
													
														
															
																|  |  |  |  |  | T xr[2] = {}; | 
														
													
														
															
																|  |  |  |  |  | T yl[2] = {}; | 
														
													
														
															
																|  |  |  |  |  | T yr[2] = {}; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | void process(T* inL, T* inR) { | 
														
													
														
															
																|  |  |  |  |  | T l = AeEqualizer<T>::b0 * *inL + AeEqualizer<T>::b1 * xl[0] + AeEqualizer<T>::b2 * xl[1] - AeEqualizer<T>::a1 * yl[0] - AeEqualizer<T>::a2 * yl[1]; | 
														
													
														
															
																|  |  |  |  |  | T r = AeEqualizer<T>::b0 * *inR + AeEqualizer<T>::b1 * xr[0] + AeEqualizer<T>::b2 * xr[1] - AeEqualizer<T>::a1 * yr[0] - AeEqualizer<T>::a2 * yr[1]; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | // shift buffers | 
														
													
														
															
																|  |  |  |  |  | xl[1] = xl[0]; | 
														
													
														
															
																|  |  |  |  |  | xl[0] = *inL; | 
														
													
														
															
																|  |  |  |  |  | xr[1] = xr[0]; | 
														
													
														
															
																|  |  |  |  |  | xr[0] = *inR; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | yl[1] = yl[0]; | 
														
													
														
															
																|  |  |  |  |  | yl[0] = l; | 
														
													
														
															
																|  |  |  |  |  | yr[1] = yr[0]; | 
														
													
														
															
																|  |  |  |  |  | yr[0] = r; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | *inL = l; | 
														
													
														
															
																|  |  |  |  |  | *inR = r; | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | }; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | struct StereoStrip : Module { | 
														
													
														
															
																|  |  |  |  |  | enum ParamId { | 
														
													
														
															
																|  |  |  |  |  | LOW_PARAM, | 
														
													
														
															
																|  |  |  |  |  | MID_PARAM, | 
														
													
														
															
																|  |  |  |  |  | HIGH_PARAM, | 
														
													
														
															
																|  |  |  |  |  | PAN_PARAM, | 
														
													
														
															
																|  |  |  |  |  | MUTE_PARAM, | 
														
													
														
															
																|  |  |  |  |  | PAN_CV_PARAM, | 
														
													
														
															
																|  |  |  |  |  | LEVEL_PARAM, | 
														
													
														
															
																|  |  |  |  |  | IN_BOOST_PARAM, | 
														
													
														
															
																|  |  |  |  |  | OUT_CUT_PARAM, | 
														
													
														
															
																|  |  |  |  |  | PARAMS_LEN | 
														
													
														
															
																|  |  |  |  |  | }; | 
														
													
														
															
																|  |  |  |  |  | enum InputId { | 
														
													
														
															
																|  |  |  |  |  | LEFT_INPUT, | 
														
													
														
															
																|  |  |  |  |  | LEVEL_INPUT, | 
														
													
														
															
																|  |  |  |  |  | RIGHT_INPUT, | 
														
													
														
															
																|  |  |  |  |  | PAN_INPUT, | 
														
													
														
															
																|  |  |  |  |  | INPUTS_LEN | 
														
													
														
															
																|  |  |  |  |  | }; | 
														
													
														
															
																|  |  |  |  |  | enum OutputId { | 
														
													
														
															
																|  |  |  |  |  | LEFT_OUTPUT, | 
														
													
														
															
																|  |  |  |  |  | RIGHT_OUTPUT, | 
														
													
														
															
																|  |  |  |  |  | OUTPUTS_LEN | 
														
													
														
															
																|  |  |  |  |  | }; | 
														
													
														
															
																|  |  |  |  |  | enum LightId { | 
														
													
														
															
																|  |  |  |  |  | ENUMS(LEFT_LIGHT, 3), | 
														
													
														
															
																|  |  |  |  |  | ENUMS(RIGHT_LIGHT, 3), | 
														
													
														
															
																|  |  |  |  |  | LIGHTS_LEN | 
														
													
														
															
																|  |  |  |  |  | }; | 
														
													
														
															
																|  |  |  |  |  | enum MuteStates { | 
														
													
														
															
																|  |  |  |  |  | MUTE_OFF_MOMENTARY = -1, | 
														
													
														
															
																|  |  |  |  |  | MUTE_ON, | 
														
													
														
															
																|  |  |  |  |  | MUTE_OFF | 
														
													
														
															
																|  |  |  |  |  | }; | 
														
													
														
															
																|  |  |  |  |  | enum MixerSides { | 
														
													
														
															
																|  |  |  |  |  | LEFT, | 
														
													
														
															
																|  |  |  |  |  | RIGHT | 
														
													
														
															
																|  |  |  |  |  | }; | 
														
													
														
															
																|  |  |  |  |  | enum PanningLaw { | 
														
													
														
															
																|  |  |  |  |  | LINEAR_6dB, | 
														
													
														
															
																|  |  |  |  |  | EQUAL_POWER, | 
														
													
														
															
																|  |  |  |  |  | LINEAR_CLIPPED | 
														
													
														
															
																|  |  |  |  |  | }; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | PanningLaw panningLaw = LINEAR_6dB; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | AeEqualizer<float_4> eqLow[4][2]; | 
														
													
														
															
																|  |  |  |  |  | AeEqualizer<float_4> eqMid[4][2]; | 
														
													
														
															
																|  |  |  |  |  | AeEqualizer<float_4> eqHigh[4][2]; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | bool applyHighpass = true; | 
														
													
														
															
																|  |  |  |  |  | AeFilter<float_4> highpass[4][2]; | 
														
													
														
															
																|  |  |  |  |  | bool applyHighshelf = true; | 
														
													
														
															
																|  |  |  |  |  | AeEqualizer<float_4> highshelf[4][2]; | 
														
													
														
															
																|  |  |  |  |  | bool applySoftClipping = true; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | float lastLowGain = -INFINITY; | 
														
													
														
															
																|  |  |  |  |  | float lastMidGain = -INFINITY; | 
														
													
														
															
																|  |  |  |  |  | float lastHighGain = -INFINITY; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | // for processing mutes | 
														
													
														
															
																|  |  |  |  |  | dsp::SlewLimiter clickFilter; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | StereoStrip() { | 
														
													
														
															
																|  |  |  |  |  | config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); | 
														
													
														
															
																|  |  |  |  |  | configParam(HIGH_PARAM, -15.0f, 15.0f, 0.0f, "High shelf (2000 Hz) gain", " dB"); | 
														
													
														
															
																|  |  |  |  |  | configParam(MID_PARAM, -12.5f, 12.5f, 0.0f, "Mid band (1200 Hz) gain", " dB"); | 
														
													
														
															
																|  |  |  |  |  | configParam(LOW_PARAM, -20.0f, 20.0f, 0.0f, "Low shelf (125 Hz) gain", " dB"); | 
														
													
														
															
																|  |  |  |  |  | configParam(PAN_PARAM, -1.f, 1.f, 0.0f, "Pan"); | 
														
													
														
															
																|  |  |  |  |  | configSwitch(MUTE_PARAM, MUTE_OFF_MOMENTARY, MUTE_OFF, MUTE_OFF, "Mute", {"Off (momentary)", "On", "Off"}); | 
														
													
														
															
																|  |  |  |  |  | configParam(PAN_CV_PARAM, 0.f, 1.f, 0.f, "Pan CV"); | 
														
													
														
															
																|  |  |  |  |  | configParam(LEVEL_PARAM, -60.0f, 0.0f, -60.0f, "Gain", "dB"); | 
														
													
														
															
																|  |  |  |  |  | configSwitch(IN_BOOST_PARAM, 0, 1, 0, "In boost", {"0dB", "+6dB"}); | 
														
													
														
															
																|  |  |  |  |  | configSwitch(OUT_CUT_PARAM, 0, 1, 0, "Out cut", {"0dB", "-6dB"}); | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | configInput(LEFT_INPUT, "Left"); | 
														
													
														
															
																|  |  |  |  |  | configInput(LEVEL_INPUT, "Level (10 V normalled)"); | 
														
													
														
															
																|  |  |  |  |  | configInput(RIGHT_INPUT, "Right (left normalled)"); | 
														
													
														
															
																|  |  |  |  |  | configInput(PAN_INPUT, "Pan CV (-5 V to +5 V)"); | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | configOutput(LEFT_OUTPUT, "Left"); | 
														
													
														
															
																|  |  |  |  |  | configOutput(RIGHT_OUTPUT, "Right"); | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | configLight(LEFT_LIGHT, "Left"); | 
														
													
														
															
																|  |  |  |  |  | configLight(RIGHT_LIGHT, "Right"); | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | configBypass(LEFT_INPUT, LEFT_OUTPUT); | 
														
													
														
															
																|  |  |  |  |  | configBypass(RIGHT_INPUT, RIGHT_OUTPUT); | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | onSampleRateChange(); | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | clickFilter.rise = 50.f; // Hz | 
														
													
														
															
																|  |  |  |  |  | clickFilter.fall = 50.f; // Hz | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | void onSampleRateChange() override { | 
														
													
														
															
																|  |  |  |  |  | bool forceUpdate = true; | 
														
													
														
															
																|  |  |  |  |  | updateEQsIfChanged(forceUpdate); | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | for (int side = 0; side < 2; ++side) { | 
														
													
														
															
																|  |  |  |  |  | for (int c = 0; c < 16; c += 4) { | 
														
													
														
															
																|  |  |  |  |  | highpass[side][c / 4].setCutoff(25.0f, 0.8f, AeFilterType::AeHIGHPASS); | 
														
													
														
															
																|  |  |  |  |  | highshelf[side][c / 4].setParams(12000.0f, 0.8f, -5.0f, AeEQType::AeHIGHSHELVE); | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | void updateEQsIfChanged(bool forceUpdate = false) { | 
														
													
														
															
																|  |  |  |  |  | float highGain = params[HIGH_PARAM].getValue(); | 
														
													
														
															
																|  |  |  |  |  | float midGain = params[MID_PARAM].getValue(); | 
														
													
														
															
																|  |  |  |  |  | float lowGain = params[LOW_PARAM].getValue(); | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | // only calculate coefficients when neccessary | 
														
													
														
															
																|  |  |  |  |  | if (highGain != lastHighGain || forceUpdate) { | 
														
													
														
															
																|  |  |  |  |  | for (int c = 0; c < 16; c += 4) { | 
														
													
														
															
																|  |  |  |  |  | for (int side = 0; side < 2; ++side) { | 
														
													
														
															
																|  |  |  |  |  | eqHigh[c / 4][side].setParams(2000.0f, 0.4f, highGain, AeEQType::AeHIGHSHELVE); | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | lastHighGain = highGain; | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | if (midGain != lastMidGain || forceUpdate) { | 
														
													
														
															
																|  |  |  |  |  | for (int c = 0; c < 16; c += 4) { | 
														
													
														
															
																|  |  |  |  |  | for (int side = 0; side < 2; ++side) { | 
														
													
														
															
																|  |  |  |  |  | eqMid[c / 4][side].setParams(1200.0f, 0.52f, midGain, AeEQType::AePEAKINGEQ); | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | lastMidGain = midGain; | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | if (lowGain != lastLowGain || forceUpdate) { | 
														
													
														
															
																|  |  |  |  |  | for (int c = 0; c < 16; c += 4) { | 
														
													
														
															
																|  |  |  |  |  | for (int side = 0; side < 2; ++side) { | 
														
													
														
															
																|  |  |  |  |  | eqLow[c / 4][side].setParams(125.0f, 0.45f, lowGain, AeEQType::AeLOWSHELVE); | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | lastLowGain = lowGain; | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | void process(const ProcessArgs& args) override { | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | float_4 out[4][2] = {}, in[4][2] = {}; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | const int numPolyphonyEngines = std::max(inputs[LEFT_INPUT].getChannels(), inputs[RIGHT_INPUT].getChannels()); | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | // slew mute to avoid clicks | 
														
													
														
															
																|  |  |  |  |  | const float muteGain = clickFilter.process(args.sampleTime, params[MUTE_PARAM].getValue() != MUTE_ON); | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | if (inputs[LEFT_INPUT].isConnected() || inputs[RIGHT_INPUT].isConnected()) { | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | const float switchGains = (params[IN_BOOST_PARAM].getValue() ? 2.0f : 1.0f) * (params[OUT_CUT_PARAM].getValue() ? 0.5f : 1.0f); | 
														
													
														
															
																|  |  |  |  |  | const float preVCAGain = switchGains * muteGain * std::pow(10, params[LEVEL_PARAM].getValue() / 20.0f); | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | updateEQsIfChanged(); | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | for (int c = 0; c < numPolyphonyEngines; c += 4) { | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | const float_4 postVCAGain = preVCAGain * clamp(inputs[LEVEL_INPUT].getNormalPolyVoltageSimd<float_4>(10.f, c) / 10.f, 0.f, 1.f); | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | const float_4 panCV = clamp(params[PAN_CV_PARAM].getValue() * inputs[PAN_INPUT].getPolyVoltageSimd<float_4>(c) / 5.f, -1.f, +1.f); | 
														
													
														
															
																|  |  |  |  |  | const float_4 pan = clamp(params[PAN_PARAM].getValue() + panCV, -1.f, +1.f); | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | // https://www.desmos.com/calculator/b0lisclikw | 
														
													
														
															
																|  |  |  |  |  | float_4 gainForSide[2] = {}; | 
														
													
														
															
																|  |  |  |  |  | switch (panningLaw) { | 
														
													
														
															
																|  |  |  |  |  | case LINEAR_6dB: { | 
														
													
														
															
																|  |  |  |  |  | gainForSide[0] = postVCAGain * (1.f - pan); | 
														
													
														
															
																|  |  |  |  |  | gainForSide[1] = postVCAGain * (1.f + pan); | 
														
													
														
															
																|  |  |  |  |  | break; | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | case EQUAL_POWER: { | 
														
													
														
															
																|  |  |  |  |  | gainForSide[0] = postVCAGain * simd::sqrt(1.f - pan); | 
														
													
														
															
																|  |  |  |  |  | gainForSide[1] = postVCAGain * simd::sqrt(1.f + pan); | 
														
													
														
															
																|  |  |  |  |  | break; | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | case LINEAR_CLIPPED: { | 
														
													
														
															
																|  |  |  |  |  | gainForSide[0] = simd::ifelse(pan < 0, postVCAGain, postVCAGain * (1.f - pan)); | 
														
													
														
															
																|  |  |  |  |  | gainForSide[1] = simd::ifelse(pan > 0, postVCAGain, postVCAGain * (1.f + pan)); | 
														
													
														
															
																|  |  |  |  |  | break; | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | in[c / 4][LEFT] = inputs[LEFT_INPUT].getPolyVoltageSimd<float_4>(c); | 
														
													
														
															
																|  |  |  |  |  | in[c / 4][RIGHT] = inputs[RIGHT_INPUT].getNormalPolyVoltageSimd<float_4>(in[c / 4][LEFT], c); | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | for (int side = 0; side < 2; ++side) { | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | float_4 outForSide = in[c / 4][side]; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | outForSide = eqLow[c / 4][side].process(outForSide); | 
														
													
														
															
																|  |  |  |  |  | outForSide = eqMid[c / 4][side].process(outForSide); | 
														
													
														
															
																|  |  |  |  |  | outForSide = eqHigh[c / 4][side].process(outForSide); | 
														
													
														
															
																|  |  |  |  |  | outForSide = applyHighpass ? highpass[c / 4][side].process(outForSide) : outForSide; | 
														
													
														
															
																|  |  |  |  |  | outForSide = applyHighshelf ? highshelf[c / 4][side].process(outForSide) : outForSide; | 
														
													
														
															
																|  |  |  |  |  | outForSide = outForSide * gainForSide[side]; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | // soft clipping: the Saturator used elsewhere expects values in range [-1, +1] roughly, so rescale before | 
														
													
														
															
																|  |  |  |  |  | // and after (assuming input signals are 10Vpp, clipping will kick in above 12Vpp with the present values) | 
														
													
														
															
																|  |  |  |  |  | if (applySoftClipping) { | 
														
													
														
															
																|  |  |  |  |  | outForSide = Saturator<float_4>::process(outForSide / 6.f) * 6.f; | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | out[c / 4][side] = outForSide; | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | if (numPolyphonyEngines <= 1) { | 
														
													
														
															
																|  |  |  |  |  | lights[LEFT_LIGHT + 0].setBrightness(0.f); | 
														
													
														
															
																|  |  |  |  |  | lights[RIGHT_LIGHT + 0].setBrightness(0.f); | 
														
													
														
															
																|  |  |  |  |  | lights[LEFT_LIGHT + 1].setBrightnessSmooth(std::abs(out[0][LEFT][0]), args.sampleTime); | 
														
													
														
															
																|  |  |  |  |  | lights[RIGHT_LIGHT + 1].setBrightnessSmooth(std::abs(out[0][RIGHT][0]), args.sampleTime); | 
														
													
														
															
																|  |  |  |  |  | lights[LEFT_LIGHT + 2].setBrightness(0.f); | 
														
													
														
															
																|  |  |  |  |  | lights[RIGHT_LIGHT + 2].setBrightness(0.f); | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | else { | 
														
													
														
															
																|  |  |  |  |  | lights[LEFT_LIGHT + 0].setBrightness(0.f); | 
														
													
														
															
																|  |  |  |  |  | lights[RIGHT_LIGHT + 0].setBrightness(0.f); | 
														
													
														
															
																|  |  |  |  |  | lights[LEFT_LIGHT + 1].setBrightness(0.f); | 
														
													
														
															
																|  |  |  |  |  | lights[RIGHT_LIGHT + 1].setBrightness(0.f); | 
														
													
														
															
																|  |  |  |  |  | lights[LEFT_LIGHT + 2].setBrightness(1.f); | 
														
													
														
															
																|  |  |  |  |  | lights[RIGHT_LIGHT + 2].setBrightness(1.f); | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | for (int c = 0; c < numPolyphonyEngines; c += 4) { | 
														
													
														
															
																|  |  |  |  |  | outputs[LEFT_OUTPUT].setVoltageSimd(out[c / 4][LEFT], c); | 
														
													
														
															
																|  |  |  |  |  | outputs[RIGHT_OUTPUT].setVoltageSimd(out[c / 4][RIGHT], c); | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | outputs[LEFT_OUTPUT].setChannels(numPolyphonyEngines); | 
														
													
														
															
																|  |  |  |  |  | outputs[RIGHT_OUTPUT].setChannels(numPolyphonyEngines); | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | json_t* dataToJson() override { | 
														
													
														
															
																|  |  |  |  |  | json_t* rootJ = json_object(); | 
														
													
														
															
																|  |  |  |  |  | json_object_set_new(rootJ, "applyHighpass", json_boolean(applyHighpass)); | 
														
													
														
															
																|  |  |  |  |  | json_object_set_new(rootJ, "applyHighshelf", json_boolean(applyHighshelf)); | 
														
													
														
															
																|  |  |  |  |  | json_object_set_new(rootJ, "panningLaw", json_integer(panningLaw)); | 
														
													
														
															
																|  |  |  |  |  | json_object_set_new(rootJ, "applySoftClipping", json_boolean(applySoftClipping)); | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | return rootJ; | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | void dataFromJson(json_t* rootJ) override { | 
														
													
														
															
																|  |  |  |  |  | json_t* applyHighshelfJ = json_object_get(rootJ, "applyHighshelf"); | 
														
													
														
															
																|  |  |  |  |  | if (applyHighshelfJ) { | 
														
													
														
															
																|  |  |  |  |  | applyHighshelf = json_boolean_value(applyHighshelfJ); | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | json_t* applyHighpassJ = json_object_get(rootJ, "applyHighpass"); | 
														
													
														
															
																|  |  |  |  |  | if (applyHighpassJ) { | 
														
													
														
															
																|  |  |  |  |  | applyHighpass = json_boolean_value(applyHighpassJ); | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | json_t* panningLawJ = json_object_get(rootJ, "panningLaw"); | 
														
													
														
															
																|  |  |  |  |  | if (panningLawJ) { | 
														
													
														
															
																|  |  |  |  |  | panningLaw = (PanningLaw) json_integer_value(panningLawJ); | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | json_t* softClippingJ = json_object_get(rootJ, "applySoftClipping"); | 
														
													
														
															
																|  |  |  |  |  | if (softClippingJ) { | 
														
													
														
															
																|  |  |  |  |  | applySoftClipping = json_boolean_value(softClippingJ); | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | }; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | // an implementation of a performable, 3-stage switch, where the bottom state is Momentary | 
														
													
														
															
																|  |  |  |  |  | struct ThreeStateBefacoSwitchMomentary : SvgSwitch { | 
														
													
														
															
																|  |  |  |  |  | ThreeStateBefacoSwitchMomentary() { | 
														
													
														
															
																|  |  |  |  |  | momentary = true; | 
														
													
														
															
																|  |  |  |  |  | addFrame(Svg::load(asset::system("res/ComponentLibrary/BefacoSwitch_0.svg"))); | 
														
													
														
															
																|  |  |  |  |  | addFrame(Svg::load(asset::system("res/ComponentLibrary/BefacoSwitch_1.svg"))); | 
														
													
														
															
																|  |  |  |  |  | addFrame(Svg::load(asset::system("res/ComponentLibrary/BefacoSwitch_2.svg"))); | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | void onDragStart(const event::DragStart& e) override { | 
														
													
														
															
																|  |  |  |  |  | if (e.button == GLFW_MOUSE_BUTTON_LEFT) { | 
														
													
														
															
																|  |  |  |  |  | latched = false; | 
														
													
														
															
																|  |  |  |  |  | pos = Vec(0, 0); | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | ParamWidget::onDragStart(e); | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | void onDragMove(const event::DragMove& e) override { | 
														
													
														
															
																|  |  |  |  |  | if (e.button == GLFW_MOUSE_BUTTON_LEFT) { | 
														
													
														
															
																|  |  |  |  |  | pos += e.mouseDelta; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | // Once the user has dragged the mouse a "threshold" distance, latch | 
														
													
														
															
																|  |  |  |  |  | // to disallow further changes of state until the mouse is released. | 
														
													
														
															
																|  |  |  |  |  | // We don't just setValue(1) (default/rest state) because this creates a | 
														
													
														
															
																|  |  |  |  |  | // jarring UI experience | 
														
													
														
															
																|  |  |  |  |  | if (pos.y < -10 && !latched) { | 
														
													
														
															
																|  |  |  |  |  | getParamQuantity()->setValue(StereoStrip::MUTE_OFF); | 
														
													
														
															
																|  |  |  |  |  | latched = true; | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | if (pos.y > 10 && !latched) { | 
														
													
														
															
																|  |  |  |  |  | getParamQuantity()->setValue(StereoStrip::MUTE_OFF_MOMENTARY); | 
														
													
														
															
																|  |  |  |  |  | latched = true; | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | ParamWidget::onDragMove(e); | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | void onDragEnd(const event::DragEnd& e) override { | 
														
													
														
															
																|  |  |  |  |  | if (e.button == GLFW_MOUSE_BUTTON_LEFT) { | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | // not dragged == clicked | 
														
													
														
															
																|  |  |  |  |  | if (std::sqrt(pos.square()) < 5) { | 
														
													
														
															
																|  |  |  |  |  | // if muted, unmute | 
														
													
														
															
																|  |  |  |  |  | if (getParamQuantity()->getValue() == StereoStrip::MUTE_ON) { | 
														
													
														
															
																|  |  |  |  |  | getParamQuantity()->setValue(StereoStrip::MUTE_OFF); | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | // if ummuted, mute | 
														
													
														
															
																|  |  |  |  |  | else if (getParamQuantity()->getValue() == StereoStrip::MUTE_OFF) { | 
														
													
														
															
																|  |  |  |  |  | getParamQuantity()->setValue(StereoStrip::MUTE_ON); | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | // on release, the switch resets to default/neutral/middle position, if was previously down | 
														
													
														
															
																|  |  |  |  |  | if (getParamQuantity()->getValue() == StereoStrip::MUTE_OFF_MOMENTARY) { | 
														
													
														
															
																|  |  |  |  |  | getParamQuantity()->setValue(StereoStrip::MUTE_ON); | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | latched = false; | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | ParamWidget::onDragEnd(e); | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | Vec pos; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | bool latched = false; | 
														
													
														
															
																|  |  |  |  |  | }; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | struct StereoStripWidget : ModuleWidget { | 
														
													
														
															
																|  |  |  |  |  | StereoStripWidget(StereoStrip* module) { | 
														
													
														
															
																|  |  |  |  |  | setModule(module); | 
														
													
														
															
																|  |  |  |  |  | setPanel(createPanel(asset::plugin(pluginInstance, "res/panels/StereoStrip.svg"))); | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, 0))); | 
														
													
														
															
																|  |  |  |  |  | addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | addParam(createParam<BefacoSlidePotSmall>(mm2px(Vec(2.763, 35.805)), module, StereoStrip::LOW_PARAM)); | 
														
													
														
															
																|  |  |  |  |  | addParam(createParam<BefacoSlidePotSmall>(mm2px(Vec(12.817, 35.805)), module, StereoStrip::MID_PARAM)); | 
														
													
														
															
																|  |  |  |  |  | addParam(createParam<BefacoSlidePotSmall>(mm2px(Vec(22.861, 35.805)), module, StereoStrip::HIGH_PARAM)); | 
														
													
														
															
																|  |  |  |  |  | addParam(createParamCentered<Davies1900hDarkGreyKnob>(mm2px(Vec(15.042, 74.11)), module, StereoStrip::PAN_PARAM)); | 
														
													
														
															
																|  |  |  |  |  | addParam(createParamCentered<ThreeStateBefacoSwitchMomentary>(mm2px(Vec(7.416, 91.244)), module, StereoStrip::MUTE_PARAM)); | 
														
													
														
															
																|  |  |  |  |  | addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(22.842, 91.244)), module, StereoStrip::PAN_CV_PARAM)); | 
														
													
														
															
																|  |  |  |  |  | addParam(createParamCentered<Davies1900hLargeGreyKnob>(mm2px(Vec(15.054, 111.333)), module, StereoStrip::LEVEL_PARAM)); | 
														
													
														
															
																|  |  |  |  |  | addParam(createParam<CKSSNarrow>(mm2px(Vec(2.372, 72.298)), module, StereoStrip::IN_BOOST_PARAM)); | 
														
													
														
															
																|  |  |  |  |  | addParam(createParam<CKSSNarrow>(mm2px(Vec(24.253, 72.298)), module, StereoStrip::OUT_CUT_PARAM)); | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.038, 14.852)), module, StereoStrip::LEFT_INPUT)); | 
														
													
														
															
																|  |  |  |  |  | addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.023, 14.852)), module, StereoStrip::LEVEL_INPUT)); | 
														
													
														
															
																|  |  |  |  |  | addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.038, 26.304)), module, StereoStrip::RIGHT_INPUT)); | 
														
													
														
															
																|  |  |  |  |  | addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.023, 26.304)), module, StereoStrip::PAN_INPUT)); | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(25.069, 14.882)), module, StereoStrip::LEFT_OUTPUT)); | 
														
													
														
															
																|  |  |  |  |  | addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(25.069, 26.317)), module, StereoStrip::RIGHT_OUTPUT)); | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | addChild(createLightCentered<SmallLight<RedGreenBlueLight>>(mm2px(Vec(4.05, 69.906)), module, StereoStrip::LEFT_LIGHT)); | 
														
													
														
															
																|  |  |  |  |  | addChild(createLightCentered<SmallLight<RedGreenBlueLight>>(mm2px(Vec(26.05, 69.906)), module, StereoStrip::RIGHT_LIGHT)); | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | void appendContextMenu(Menu* menu) override { | 
														
													
														
															
																|  |  |  |  |  | StereoStrip* module = dynamic_cast<StereoStrip*>(this->module); | 
														
													
														
															
																|  |  |  |  |  | assert(module); | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | menu->addChild(new MenuSeparator()); | 
														
													
														
															
																|  |  |  |  |  | menu->addChild(createBoolPtrMenuItem("Apply Highpass (25Hz)", "", &module->applyHighpass)); | 
														
													
														
															
																|  |  |  |  |  | menu->addChild(createBoolPtrMenuItem("Apply Highshelf (12kHz)", "", &module->applyHighshelf)); | 
														
													
														
															
																|  |  |  |  |  | menu->addChild(createBoolPtrMenuItem("Apply soft-clipping", "", &module->applySoftClipping)); | 
														
													
														
															
																|  |  |  |  |  | menu->addChild(new MenuSeparator()); | 
														
													
														
															
																|  |  |  |  |  | menu->addChild(createIndexPtrSubmenuItem("Panning law", {"Linear (+6dB)", "Equal power (+3dB)", "Linear clipped"}, &module->panningLaw)); | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | }; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | Model* modelChannelStrip = createModel<StereoStrip, StereoStripWidget>("StereoStrip"); |