| 
				
				
				
				 | 
			
			 | 
			@@ -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"); |