You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

444 lines
17KB

  1. #include "plugin.hpp"
  2. #include "simd_mask.hpp"
  3. #include "PulseGenerator_4.hpp"
  4. #define MAX(a,b) a>b?a:b
  5. static simd::float_4 shapeDelta(simd::float_4 delta, simd::float_4 tau, float shape) {
  6. simd::float_4 lin = simd::sgn(delta) * 10.f / tau;
  7. if (shape < 0.f) {
  8. simd::float_4 log = simd::sgn(delta) * simd::float_4(40.f) / tau / (simd::fabs(delta) + simd::float_4(1.f));
  9. return crossfade_4(lin, log, -shape * 0.95f);
  10. }
  11. else {
  12. simd::float_4 exp = M_E * delta / tau;
  13. return crossfade_4(lin, exp, shape * 0.90f);
  14. }
  15. }
  16. struct Rampage : Module {
  17. enum ParamIds {
  18. RANGE_A_PARAM,
  19. RANGE_B_PARAM,
  20. SHAPE_A_PARAM,
  21. SHAPE_B_PARAM,
  22. TRIGG_A_PARAM,
  23. TRIGG_B_PARAM,
  24. RISE_A_PARAM,
  25. RISE_B_PARAM,
  26. FALL_A_PARAM,
  27. FALL_B_PARAM,
  28. CYCLE_A_PARAM,
  29. CYCLE_B_PARAM,
  30. BALANCE_PARAM,
  31. NUM_PARAMS
  32. };
  33. enum InputIds {
  34. IN_A_INPUT,
  35. IN_B_INPUT,
  36. TRIGG_A_INPUT,
  37. TRIGG_B_INPUT,
  38. RISE_CV_A_INPUT,
  39. RISE_CV_B_INPUT,
  40. FALL_CV_A_INPUT,
  41. FALL_CV_B_INPUT,
  42. EXP_CV_A_INPUT,
  43. EXP_CV_B_INPUT,
  44. CYCLE_A_INPUT,
  45. CYCLE_B_INPUT,
  46. NUM_INPUTS
  47. };
  48. enum OutputIds {
  49. RISING_A_OUTPUT,
  50. RISING_B_OUTPUT,
  51. FALLING_A_OUTPUT,
  52. FALLING_B_OUTPUT,
  53. EOC_A_OUTPUT,
  54. EOC_B_OUTPUT,
  55. OUT_A_OUTPUT,
  56. OUT_B_OUTPUT,
  57. COMPARATOR_OUTPUT,
  58. MIN_OUTPUT,
  59. MAX_OUTPUT,
  60. NUM_OUTPUTS
  61. };
  62. enum LightIds {
  63. ENUMS(COMPARATOR_LIGHT, 3),
  64. ENUMS(MIN_LIGHT, 3),
  65. ENUMS(MAX_LIGHT, 3),
  66. ENUMS(OUT_A_LIGHT, 3),
  67. ENUMS(OUT_B_LIGHT, 3),
  68. ENUMS(RISING_A_LIGHT, 3),
  69. ENUMS(RISING_B_LIGHT, 3),
  70. ENUMS(FALLING_A_LIGHT, 3),
  71. ENUMS(FALLING_B_LIGHT, 3),
  72. NUM_LIGHTS
  73. };
  74. /*
  75. float out[2] = {};
  76. bool gate[2] = {};
  77. */
  78. simd::float_4 out[2][4];
  79. simd::float_4 gate[2][4]; // use simd __m128 logic instead of bool
  80. dsp::TSchmittTrigger<simd::float_4> trigger_4[2][4];
  81. PulseGenerator_4 endOfCyclePulse[2][4];
  82. ChannelMask channelMask;
  83. Rampage() {
  84. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  85. configParam(RANGE_A_PARAM, 0.0, 2.0, 0.0, "Ch 1 range");
  86. configParam(SHAPE_A_PARAM, -1.0, 1.0, 0.0, "Ch 1 shape");
  87. configParam(TRIGG_A_PARAM, 0.0, 1.0, 0.0, "Ch 1 trigger");
  88. configParam(RISE_A_PARAM, 0.0, 1.0, 0.0, "Ch 1 rise time");
  89. configParam(FALL_A_PARAM, 0.0, 1.0, 0.0, "Ch 1 fall time");
  90. configParam(CYCLE_A_PARAM, 0.0, 1.0, 0.0, "Ch 1 cycle");
  91. configParam(RANGE_B_PARAM, 0.0, 2.0, 0.0, "Ch 2 range");
  92. configParam(SHAPE_B_PARAM, -1.0, 1.0, 0.0, "Ch 2 shape");
  93. configParam(TRIGG_B_PARAM, 0.0, 1.0, 0.0, "Ch 2 trigger");
  94. configParam(RISE_B_PARAM, 0.0, 1.0, 0.0, "Ch 2 rise time");
  95. configParam(FALL_B_PARAM, 0.0, 1.0, 0.0, "Ch 2 fall time");
  96. configParam(CYCLE_B_PARAM, 0.0, 1.0, 0.0, "Ch 2 cycle");
  97. configParam(BALANCE_PARAM, 0.0, 1.0, 0.5, "Balance");
  98. memset(out, 0, sizeof(out));
  99. memset(gate, 0, sizeof(gate));
  100. }
  101. void process(const ProcessArgs &args) override {
  102. int channels_in[2];
  103. int channels_trig[2];
  104. int channels[2];
  105. // determine number of channels:
  106. for (int part=0; part<2; part++) {
  107. channels_in[part] = inputs[IN_A_INPUT+part].getChannels();
  108. channels_trig[part] = inputs[TRIGG_A_INPUT+part].getChannels();
  109. channels[part] = MAX(channels_in[part], channels_trig[part]);
  110. channels[part] = MAX(1, channels[part]);
  111. outputs[OUT_A_OUTPUT+part].setChannels(channels[part]);
  112. outputs[RISING_A_OUTPUT+part].setChannels(channels[part]);
  113. outputs[FALLING_A_OUTPUT+part].setChannels(channels[part]);
  114. outputs[EOC_A_OUTPUT+part].setChannels(channels[part]);
  115. }
  116. int channels_max = MAX(channels[0], channels[1]);
  117. outputs[COMPARATOR_OUTPUT].setChannels(channels_max);
  118. outputs[MIN_OUTPUT].setChannels(channels_max);
  119. outputs[MAX_OUTPUT].setChannels(channels_max);
  120. // loop over two parts of Rampage:
  121. for (int part = 0; part < 2; part++) {
  122. simd::float_4 in[4];
  123. simd::float_4 in_trig[4];
  124. simd::float_4 expCV[4];
  125. simd::float_4 riseCV[4];
  126. simd::float_4 fallCV[4];
  127. simd::float_4 cycle[4];
  128. // get parameters:
  129. float shape = params[SHAPE_A_PARAM + part].getValue();
  130. float minTime;
  131. switch ((int) params[RANGE_A_PARAM + part].getValue()) {
  132. case 0: minTime = 1e-2; break;
  133. case 1: minTime = 1e-3; break;
  134. default: minTime = 1e-1; break;
  135. }
  136. simd::float_4 param_rise = simd::float_4(params[RISE_A_PARAM + part].getValue() * 10.0f);
  137. simd::float_4 param_fall = simd::float_4(params[FALL_A_PARAM + part].getValue() * 10.0f);
  138. simd::float_4 param_trig = simd::float_4(params[TRIGG_A_PARAM + part].getValue() * 20.0f);
  139. simd::float_4 param_cycle = simd::float_4(params[CYCLE_A_PARAM + part].getValue() * 10.0f);
  140. for(int c=0; c<channels[part]; c+=4) {
  141. riseCV[c/4] = param_rise;
  142. fallCV[c/4] = param_fall;
  143. cycle[c/4] = param_cycle;
  144. in_trig[c/4] = param_trig;
  145. }
  146. // read inputs:
  147. if(inputs[IN_A_INPUT + part].isConnected()) {
  148. load_input(inputs[IN_A_INPUT + part], in, channels_in[part]);
  149. channelMask.apply_all(in, channels_in[part]);
  150. } else {
  151. memset(in, 0, sizeof(in));
  152. }
  153. if(inputs[TRIGG_A_INPUT + part].isConnected()) {
  154. add_input(inputs[TRIGG_A_INPUT + part], in_trig, channels_trig[part]);
  155. channelMask.apply_all(in_trig, channels_trig[part]);
  156. }
  157. if(inputs[EXP_CV_A_INPUT + part].isConnected()) {
  158. load_input(inputs[EXP_CV_A_INPUT + part], expCV, channels[part]);
  159. for(int c=0; c<channels[part]; c+=4) {
  160. riseCV[c/4] -= expCV[c/4];
  161. fallCV[c/4] -= expCV[c/4];
  162. }
  163. }
  164. add_input(inputs[RISE_CV_A_INPUT + part], riseCV, channels[part]);
  165. add_input(inputs[FALL_CV_A_INPUT + part], fallCV, channels[part]);
  166. add_input(inputs[CYCLE_A_INPUT+part], cycle, channels[part]);
  167. channelMask.apply(cycle, channels[part]); // check whether this is necessary
  168. // start processing:
  169. for(int c=0; c<channels[part]; c+=4) {
  170. // process SchmittTriggers
  171. simd::float_4 trig_mask = trigger_4[part][c/4].process(in_trig[c/4]/2.0);
  172. gate[part][c/4] = ifelse(trig_mask, simd::float_4::mask(), gate[part][c/4]);
  173. in[c/4] = ifelse(gate[part][c/4], simd::float_4(10.0f), in[c/4]);
  174. simd::float_4 delta = in[c/4] - out[part][c/4];
  175. // rise / fall branching
  176. simd::float_4 delta_gt_0 = delta > simd::float_4::zero();
  177. simd::float_4 delta_lt_0 = delta < simd::float_4::zero();
  178. simd::float_4 delta_eq_0 = ~(delta_lt_0|delta_gt_0);
  179. simd::float_4 rateCV = ifelse(delta_gt_0, riseCV[c/4], simd::float_4::zero());
  180. rateCV = ifelse(delta_lt_0, fallCV[c/4], rateCV);
  181. rateCV = clamp(rateCV, simd::float_4::zero(), simd::float_4(10.0f));
  182. simd::float_4 rate = minTime * simd::pow(2.0f, rateCV);
  183. out[part][c/4] += shapeDelta(delta, rate, shape) * args.sampleTime;
  184. simd::float_4 rising = (in[c/4] - out[part][c/4]) > simd::float_4( 1e-3);
  185. simd::float_4 falling = (in[c/4] - out[part][c/4]) < simd::float_4(-1e-3);
  186. simd::float_4 end_of_cycle = simd::andnot(falling,delta_lt_0);
  187. endOfCyclePulse[part][c/4].trigger(end_of_cycle, 1e-3);
  188. gate[part][c/4] = ifelse( simd::andnot(rising, delta_gt_0), simd::float_4::zero(), gate[part][c/4]);
  189. gate[part][c/4] = ifelse( end_of_cycle & (cycle[c/4]>=simd::float_4(4.0f)), simd::float_4::mask(), gate[part][c/4] );
  190. gate[part][c/4] = ifelse( delta_eq_0, simd::float_4::zero(), gate[part][c/4] );
  191. out[part][c/4] = ifelse( rising|falling, out[part][c/4], in[c/4] );
  192. simd::float_4 out_rising = ifelse(rising, simd::float_4(10.0f), simd::float_4::zero() );
  193. simd::float_4 out_falling = ifelse(falling, simd::float_4(10.0f), simd::float_4::zero() );
  194. simd::float_4 pulse = endOfCyclePulse[part][c/4].process(args.sampleTime);
  195. simd::float_4 out_EOC = ifelse(pulse, simd::float_4(10.f), simd::float_4::zero() );
  196. out[part][c/4].store(outputs[OUT_A_OUTPUT+part].getVoltages(c));
  197. out_rising.store( outputs[RISING_A_OUTPUT+part].getVoltages(c));
  198. out_falling.store(outputs[FALLING_A_OUTPUT+part].getVoltages(c));
  199. out_EOC.store(outputs[EOC_A_OUTPUT+part].getVoltages(c));
  200. } // for(int c, ...)
  201. if(channels[part] == 1) {
  202. lights[RISING_A_LIGHT + 3*part ].setSmoothBrightness(outputs[RISING_A_OUTPUT+part].getVoltage()/10.f, args.sampleTime);
  203. lights[RISING_A_LIGHT + 3*part+1].setBrightness(0.0f);
  204. lights[RISING_A_LIGHT + 3*part+2].setBrightness(0.0f);
  205. lights[FALLING_A_LIGHT + 3*part ].setSmoothBrightness(outputs[FALLING_A_OUTPUT+part].getVoltage()/10.f, args.sampleTime);
  206. lights[FALLING_A_LIGHT + 3*part+1].setBrightness(0.0f);
  207. lights[FALLING_A_LIGHT + 3*part+2].setBrightness(0.0f);
  208. lights[OUT_A_LIGHT + 3*part ].setSmoothBrightness(out[part][0].s[0] / 10.0, args.sampleTime);
  209. lights[OUT_A_LIGHT + 3*part+1].setBrightness(0.0f);
  210. lights[OUT_A_LIGHT + 3*part+2].setBrightness(0.0f);
  211. } else {
  212. lights[RISING_A_LIGHT + 3*part ].setBrightness(0.0f);
  213. lights[RISING_A_LIGHT + 3*part+1].setBrightness(0.0f);
  214. lights[RISING_A_LIGHT + 3*part+2].setBrightness(10.0f);
  215. lights[FALLING_A_LIGHT + 3*part ].setBrightness(0.0f);
  216. lights[FALLING_A_LIGHT + 3*part+1].setBrightness(0.0f);
  217. lights[FALLING_A_LIGHT + 3*part+2].setBrightness(10.0f);
  218. lights[OUT_A_LIGHT + 3*part ].setBrightness(0.0f);
  219. lights[OUT_A_LIGHT + 3*part+1].setBrightness(0.0f);
  220. lights[OUT_A_LIGHT + 3*part+2].setBrightness(10.0f);
  221. }
  222. // Integrator
  223. // bool rising = false;
  224. // bool falling = false;
  225. /*
  226. if (delta > 0) {
  227. // Rise
  228. float riseCv = params[RISE_A_PARAM + c].getValue() - inputs[EXP_CV_A_INPUT + c].getVoltage() / 10.0 + inputs[RISE_CV_A_INPUT + c].getVoltage() / 10.0;
  229. riseCv = clamp(riseCv, 0.0f, 1.0f);
  230. float rise = minTime * std::pow(2.0, riseCv * 10.0);
  231. out[c] += shapeDelta(delta, rise, shape) * args.sampleTime;
  232. rising = (in - out[c] > 1e-3);
  233. if (!rising) {
  234. gate[c] = false;
  235. }
  236. }
  237. else if (delta < 0) {
  238. // Fall
  239. float fallCv = params[FALL_A_PARAM + c].getValue() - inputs[EXP_CV_A_INPUT + c].getVoltage() / 10.0 + inputs[FALL_CV_A_INPUT + c].getVoltage() / 10.0;
  240. fallCv = clamp(fallCv, 0.0f, 1.0f);
  241. float fall = minTime * std::pow(2.0, fallCv * 10.0);
  242. out[c] += shapeDelta(delta, fall, shape) * args.sampleTime;
  243. falling = (in - out[c] < -1e-3);
  244. if (!falling) {
  245. // End of cycle, check if we should turn the gate back on (cycle mode)
  246. endOfCyclePulse[c].trigger(1e-3);
  247. if (params[CYCLE_A_PARAM + c].getValue() * 10.0 + inputs[CYCLE_A_INPUT + c].getVoltage() >= 4.0) {
  248. gate[c] = true;
  249. }
  250. }
  251. }
  252. else {
  253. gate[c] = false;
  254. }
  255. if (!rising && !falling) {
  256. out[c] = in;
  257. }
  258. */
  259. // outputs[RISING_A_OUTPUT + part].setVoltage((rising ? 10.0 : 0.0));
  260. // outputs[FALLING_A_OUTPUT + part].setVoltage((falling ? 10.0 : 0.0));
  261. // lights[RISING_A_LIGHT + part].setSmoothBrightness(rising ? 1.0 : 0.0, args.sampleTime);
  262. // lights[FALLING_A_LIGHT + part].setSmoothBrightness(falling ? 1.0 : 0.0, args.sampleTime);
  263. // outputs[EOC_A_OUTPUT + part].setVoltage((endOfCyclePulse[c].process(args.sampleTime) ? 10.0 : 0.0));
  264. // outputs[OUT_A_OUTPUT + part].setVoltage(out[c]);
  265. // lights[OUT_A_LIGHT + part].setSmoothBrightness(out[c] / 10.0, args.sampleTime);
  266. } // for (int part, ... )
  267. // Logic
  268. float balance = params[BALANCE_PARAM].getValue();
  269. for(int c=0; c<channels_max; c+=4) {
  270. simd::float_4 a = out[0][c/4];
  271. simd::float_4 b = out[1][c/4];
  272. if (balance < 0.5)
  273. b *= 2.0f * balance;
  274. else if (balance > 0.5)
  275. a *= 2.0f * (1.0 - balance);
  276. simd::float_4 comp = ifelse( b>a, simd::float_4(10.0f), simd::float_4::zero() );
  277. simd::float_4 out_min = simd::fmin(a,b);
  278. simd::float_4 out_max = simd::fmax(a,b);
  279. comp.store(outputs[COMPARATOR_OUTPUT].getVoltages(c));
  280. out_min.store(outputs[MIN_OUTPUT].getVoltages(c));
  281. out_max.store(outputs[MAX_OUTPUT].getVoltages(c));
  282. }
  283. // Lights
  284. if(channels_max==1) {
  285. lights[COMPARATOR_LIGHT ].setSmoothBrightness(outputs[COMPARATOR_OUTPUT].getVoltage(), args.sampleTime);
  286. lights[COMPARATOR_LIGHT+1].setBrightness(0.0f);
  287. lights[COMPARATOR_LIGHT+2].setBrightness(0.0f);
  288. lights[MIN_LIGHT ].setSmoothBrightness(outputs[MIN_OUTPUT].getVoltage(), args.sampleTime);
  289. lights[MIN_LIGHT+1].setBrightness(0.0f);
  290. lights[MIN_LIGHT+2].setBrightness(0.0f);
  291. lights[MAX_LIGHT ].setSmoothBrightness(outputs[MAX_OUTPUT].getVoltage(), args.sampleTime);
  292. lights[MAX_LIGHT+1].setBrightness(0.0f);
  293. lights[MAX_LIGHT+2].setBrightness(0.0f);
  294. } else {
  295. lights[COMPARATOR_LIGHT ].setBrightness(0.0f);
  296. lights[COMPARATOR_LIGHT+1].setBrightness(0.0f);
  297. lights[COMPARATOR_LIGHT+2].setBrightness(10.0f);
  298. lights[MIN_LIGHT ].setBrightness(0.0f);
  299. lights[MIN_LIGHT+1].setBrightness(0.0f);
  300. lights[MIN_LIGHT+2].setBrightness(10.0f);
  301. lights[MAX_LIGHT ].setBrightness(0.0f);
  302. lights[MAX_LIGHT+1].setBrightness(0.0f);
  303. lights[MAX_LIGHT+2].setBrightness(10.0f);
  304. }
  305. } // end process()
  306. };
  307. struct RampageWidget : ModuleWidget {
  308. RampageWidget(Rampage *module) {
  309. setModule(module);
  310. setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Rampage.svg")));
  311. addChild(createWidget<Knurlie>(Vec(15, 0)));
  312. addChild(createWidget<Knurlie>(Vec(box.size.x-30, 0)));
  313. addChild(createWidget<Knurlie>(Vec(15, 365)));
  314. addChild(createWidget<Knurlie>(Vec(box.size.x-30, 365)));
  315. addParam(createParam<BefacoSwitch>(Vec(94, 32), module, Rampage::RANGE_A_PARAM));
  316. addParam(createParam<BefacoTinyKnob>(Vec(27, 90), module, Rampage::SHAPE_A_PARAM));
  317. addParam(createParam<BefacoPush>(Vec(72, 82), module, Rampage::TRIGG_A_PARAM));
  318. addParam(createParam<BefacoSlidePot>(Vec(16, 135), module, Rampage::RISE_A_PARAM));
  319. addParam(createParam<BefacoSlidePot>(Vec(57, 135), module, Rampage::FALL_A_PARAM));
  320. addParam(createParam<BefacoSwitch>(Vec(101, 238), module, Rampage::CYCLE_A_PARAM));
  321. addParam(createParam<BefacoSwitch>(Vec(147, 32), module, Rampage::RANGE_B_PARAM));
  322. addParam(createParam<BefacoTinyKnob>(Vec(217, 90), module, Rampage::SHAPE_B_PARAM));
  323. addParam(createParam<BefacoPush>(Vec(170, 82), module, Rampage::TRIGG_B_PARAM));
  324. addParam(createParam<BefacoSlidePot>(Vec(197, 135), module, Rampage::RISE_B_PARAM));
  325. addParam(createParam<BefacoSlidePot>(Vec(238, 135), module, Rampage::FALL_B_PARAM));
  326. addParam(createParam<BefacoSwitch>(Vec(141, 238), module, Rampage::CYCLE_B_PARAM));
  327. addParam(createParam<Davies1900hWhiteKnob>(Vec(117, 76), module, Rampage::BALANCE_PARAM));
  328. addInput(createInput<PJ301MPort>(Vec(14, 30), module, Rampage::IN_A_INPUT));
  329. addInput(createInput<PJ301MPort>(Vec(52, 37), module, Rampage::TRIGG_A_INPUT));
  330. addInput(createInput<PJ301MPort>(Vec(8, 268), module, Rampage::RISE_CV_A_INPUT));
  331. addInput(createInput<PJ301MPort>(Vec(67, 268), module, Rampage::FALL_CV_A_INPUT));
  332. addInput(createInput<PJ301MPort>(Vec(38, 297), module, Rampage::EXP_CV_A_INPUT));
  333. addInput(createInput<PJ301MPort>(Vec(102, 290), module, Rampage::CYCLE_A_INPUT));
  334. addInput(createInput<PJ301MPort>(Vec(229, 30), module, Rampage::IN_B_INPUT));
  335. addInput(createInput<PJ301MPort>(Vec(192, 37), module, Rampage::TRIGG_B_INPUT));
  336. addInput(createInput<PJ301MPort>(Vec(176, 268), module, Rampage::RISE_CV_B_INPUT));
  337. addInput(createInput<PJ301MPort>(Vec(237, 268), module, Rampage::FALL_CV_B_INPUT));
  338. addInput(createInput<PJ301MPort>(Vec(207, 297), module, Rampage::EXP_CV_B_INPUT));
  339. addInput(createInput<PJ301MPort>(Vec(143, 290), module, Rampage::CYCLE_B_INPUT));
  340. addOutput(createOutput<PJ301MPort>(Vec(8, 326), module, Rampage::RISING_A_OUTPUT));
  341. addOutput(createOutput<PJ301MPort>(Vec(68, 326), module, Rampage::FALLING_A_OUTPUT));
  342. addOutput(createOutput<PJ301MPort>(Vec(104, 326), module, Rampage::EOC_A_OUTPUT));
  343. addOutput(createOutput<PJ301MPort>(Vec(102, 195), module, Rampage::OUT_A_OUTPUT));
  344. addOutput(createOutput<PJ301MPort>(Vec(177, 326), module, Rampage::RISING_B_OUTPUT));
  345. addOutput(createOutput<PJ301MPort>(Vec(237, 326), module, Rampage::FALLING_B_OUTPUT));
  346. addOutput(createOutput<PJ301MPort>(Vec(140, 326), module, Rampage::EOC_B_OUTPUT));
  347. addOutput(createOutput<PJ301MPort>(Vec(142, 195), module, Rampage::OUT_B_OUTPUT));
  348. addOutput(createOutput<PJ301MPort>(Vec(122, 133), module, Rampage::COMPARATOR_OUTPUT));
  349. addOutput(createOutput<PJ301MPort>(Vec(89, 157), module, Rampage::MIN_OUTPUT));
  350. addOutput(createOutput<PJ301MPort>(Vec(155, 157), module, Rampage::MAX_OUTPUT));
  351. addChild(createLight<SmallLight<RedGreenBlueLight>>(Vec(132, 167), module, Rampage::COMPARATOR_LIGHT));
  352. addChild(createLight<SmallLight<RedGreenBlueLight>>(Vec(123, 174), module, Rampage::MIN_LIGHT));
  353. addChild(createLight<SmallLight<RedGreenBlueLight>>(Vec(141, 174), module, Rampage::MAX_LIGHT));
  354. addChild(createLight<SmallLight<RedGreenBlueLight>>(Vec(126, 185), module, Rampage::OUT_A_LIGHT));
  355. addChild(createLight<SmallLight<RedGreenBlueLight>>(Vec(138, 185), module, Rampage::OUT_B_LIGHT));
  356. addChild(createLight<SmallLight<RedGreenBlueLight>>(Vec(18, 312), module, Rampage::RISING_A_LIGHT));
  357. addChild(createLight<SmallLight<RedGreenBlueLight>>(Vec(78, 312), module, Rampage::FALLING_A_LIGHT));
  358. addChild(createLight<SmallLight<RedGreenBlueLight>>(Vec(187, 312), module, Rampage::RISING_B_LIGHT));
  359. addChild(createLight<SmallLight<RedGreenBlueLight>>(Vec(247, 312), module, Rampage::FALLING_B_LIGHT));
  360. }
  361. };
  362. Model *modelRampage = createModel<Rampage, RampageWidget>("Rampage");