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.

608 lines
19KB

  1. //
  2. // CornrowsX Mutable Instruments Braids
  3. // copied from VCV Audible Instruments
  4. //
  5. //
  6. #include <string.h>
  7. #include "Southpole.hpp"
  8. #include "CornrowsSettings.h"
  9. #include "dsp/samplerate.hpp"
  10. #include "dsp/ringbuffer.hpp"
  11. #include "braids/macro_oscillator.h"
  12. #include "braids/vco_jitter_source.h"
  13. #include "braids/signature_waveshaper.h"
  14. #include "braids/envelope.h"
  15. #include "braids/quantizer.h"
  16. #include "braids/quantizer_scales.h"
  17. namespace rack_plugin_Southpole {
  18. struct CornrowsX : Module {
  19. enum ParamIds {
  20. FINE_PARAM,
  21. COARSE_PARAM,
  22. FM_PARAM,
  23. TIMBRE_PARAM,
  24. MODULATION_PARAM,
  25. COLOR_PARAM,
  26. SHAPE_PARAM,
  27. PITCH_RANGE_PARAM,
  28. PITCH_OCTAVE_PARAM,
  29. TRIG_DELAY_PARAM,
  30. ATT_PARAM,
  31. DEC_PARAM,
  32. AD_TIMBRE_PARAM,
  33. AD_MODULATION_PARAM,
  34. AD_COLOR_PARAM,
  35. RATE_PARAM,
  36. BITS_PARAM,
  37. SCALE_PARAM,
  38. ROOT_PARAM,
  39. NUM_PARAMS
  40. };
  41. enum InputIds {
  42. TRIG_INPUT,
  43. PITCH_INPUT,
  44. FM_INPUT,
  45. TIMBRE_INPUT,
  46. COLOR_INPUT,
  47. NUM_INPUTS
  48. };
  49. enum OutputIds {
  50. OUT_OUTPUT,
  51. NUM_OUTPUTS
  52. };
  53. braids::MacroOscillator osc;
  54. braids::SettingsData settings;
  55. braids::VcoJitterSource jitter_source;
  56. braids::SignatureWaveshaper ws;
  57. braids::Envelope envelope;
  58. braids::Quantizer quantizer;
  59. uint8_t current_scale = 0xff;
  60. bool trigger_detected_flag;
  61. bool trigger_flag;
  62. uint16_t trigger_delay;
  63. uint16_t gain_lp;
  64. int16_t previous_pitch = 0;
  65. SampleRateConverter<1> src;
  66. DoubleRingBuffer<Frame<1>, 256> outputBuffer;
  67. bool lastTrig = false;
  68. bool lowCpu = false;
  69. bool paques = false;
  70. const uint16_t bit_reduction_masks[7] = {
  71. 0xc000,
  72. 0xe000,
  73. 0xf000,
  74. 0xf800,
  75. 0xff00,
  76. 0xfff0,
  77. 0xffff };
  78. const uint16_t decimation_factors[7] = { 24, 12, 6, 4, 3, 2, 1 };
  79. // only for display
  80. braids::SettingsData last_settings;
  81. braids::Setting last_setting_changed;
  82. uint32_t disp_timeout = 0;
  83. CornrowsX();
  84. void step() override;
  85. void setShape(int shape);
  86. json_t *toJson() override {
  87. json_t *rootJ = json_object();
  88. json_t *settingsJ = json_array();
  89. uint8_t *settingsArray = &settings.shape;
  90. for (int i = 0; i < 20; i++) {
  91. json_t *settingJ = json_integer(settingsArray[i]);
  92. json_array_insert_new(settingsJ, i, settingJ);
  93. }
  94. json_object_set_new(rootJ, "settings", settingsJ);
  95. json_t *lowCpuJ = json_boolean(lowCpu);
  96. json_object_set_new(rootJ, "lowCpu", lowCpuJ);
  97. return rootJ;
  98. }
  99. void fromJson(json_t *rootJ) override {
  100. json_t *settingsJ = json_object_get(rootJ, "settings");
  101. if (settingsJ) {
  102. uint8_t *settingsArray = &settings.shape;
  103. for (int i = 0; i < 20; i++) {
  104. json_t *settingJ = json_array_get(settingsJ, i);
  105. if (settingJ)
  106. settingsArray[i] = json_integer_value(settingJ);
  107. }
  108. }
  109. json_t *lowCpuJ = json_object_get(rootJ, "lowCpu");
  110. if (lowCpuJ) {
  111. lowCpu = json_boolean_value(lowCpuJ);
  112. }
  113. }
  114. };
  115. CornrowsX::CornrowsX() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {
  116. memset(&osc, 0, sizeof(osc));
  117. osc.Init();
  118. memset(&quantizer, 0, sizeof(quantizer));
  119. quantizer.Init();
  120. memset(&envelope, 0, sizeof(envelope));
  121. envelope.Init();
  122. memset(&jitter_source, 0, sizeof(jitter_source));
  123. jitter_source.Init();
  124. memset(&ws, 0, sizeof(ws));
  125. ws.Init(0x0000);
  126. memset(&settings, 0, sizeof(settings));
  127. }
  128. void CornrowsX::step() {
  129. settings.quantizer_scale = params[SCALE_PARAM].value * 48.; //sizeof(quantization_values);
  130. settings.quantizer_root = params[ROOT_PARAM].value * 11.;
  131. settings.pitch_range = params[PITCH_RANGE_PARAM].value*4.;
  132. settings.pitch_octave = params[PITCH_OCTAVE_PARAM].value*4.;
  133. settings.trig_delay = params[TRIG_DELAY_PARAM].value*6.;
  134. settings.sample_rate = params[RATE_PARAM].value*6.;
  135. settings.resolution = params[BITS_PARAM].value*6.;
  136. settings.ad_attack = params[ATT_PARAM].value*15.;
  137. settings.ad_decay = params[DEC_PARAM].value*15.;
  138. settings.ad_timbre = params[AD_TIMBRE_PARAM].value*15.;
  139. settings.ad_fm = params[AD_MODULATION_PARAM].value*15.;
  140. settings.ad_color = params[AD_COLOR_PARAM].value*15.;
  141. // Display - return to SHAPE after 2s
  142. if (last_setting_changed != braids::SETTING_OSCILLATOR_SHAPE) {
  143. disp_timeout++;
  144. }
  145. if (disp_timeout > 1.0*engineGetSampleRate()) {
  146. last_setting_changed = braids::SETTING_OSCILLATOR_SHAPE;
  147. disp_timeout=0;
  148. }
  149. uint8_t *last_settingsArray = &last_settings.shape;
  150. uint8_t *settingsArray = &settings.shape;
  151. for (int i = 0; i < 20; i++) {
  152. if (settingsArray[i] != last_settingsArray[i]) {
  153. last_settingsArray[i] = settingsArray[i];
  154. last_setting_changed = static_cast<braids::Setting>(i);
  155. disp_timeout=0;
  156. }
  157. }
  158. // Trigger
  159. bool trig = inputs[TRIG_INPUT].value >= 1.0;
  160. if (!lastTrig && trig) {
  161. trigger_detected_flag = trig;
  162. }
  163. lastTrig = trig;
  164. if ( trigger_detected_flag ) {
  165. trigger_delay = settings.trig_delay
  166. ? (1 << settings.trig_delay) : 0;
  167. ++trigger_delay;
  168. trigger_detected_flag = false;
  169. }
  170. if (trigger_delay) {
  171. --trigger_delay;
  172. if (trigger_delay == 0) {
  173. trigger_flag = true;
  174. }
  175. }
  176. // Quantizer
  177. if (current_scale != settings.quantizer_scale) {
  178. current_scale = settings.quantizer_scale;
  179. quantizer.Configure(braids::scales[current_scale]);
  180. }
  181. // Render frames
  182. if (outputBuffer.empty()) {
  183. envelope.Update( settings.ad_attack*8, settings.ad_decay*8 );
  184. uint32_t ad_value = envelope.Render();
  185. float fm = params[FM_PARAM].value * inputs[FM_INPUT].value;
  186. // Set shape
  187. if (paques) {
  188. osc.set_shape(braids::MACRO_OSC_SHAPE_QUESTION_MARK);
  189. } else {
  190. int shape = roundf(params[SHAPE_PARAM].value * braids::MACRO_OSC_SHAPE_LAST_ACCESSIBLE_FROM_META);
  191. if (settings.meta_modulation) {
  192. shape += roundf(fm / 10.0 * braids::MACRO_OSC_SHAPE_LAST_ACCESSIBLE_FROM_META);
  193. }
  194. settings.shape = clamp(shape, 0, braids::MACRO_OSC_SHAPE_LAST_ACCESSIBLE_FROM_META);
  195. // Setup oscillator from settings
  196. osc.set_shape((braids::MacroOscillatorShape) settings.shape);
  197. }
  198. // Set timbre/modulation
  199. float timbre = params[TIMBRE_PARAM].value + params[MODULATION_PARAM].value * inputs[TIMBRE_INPUT].value / 5.0;
  200. float modulation = params[COLOR_PARAM].value + inputs[COLOR_INPUT].value / 5.0;
  201. timbre += ad_value/65535. * settings.ad_timbre / 16.;
  202. modulation += ad_value/65535. * settings.ad_color / 16.;
  203. int16_t param1 = rescale(clamp(timbre, 0.0, 1.0), 0.0, 1.0, 0, INT16_MAX);
  204. int16_t param2 = rescale(clamp(modulation, 0.0, 1.0), 0.0, 1.0, 0, INT16_MAX);
  205. osc.set_parameters(param1, param2);
  206. // Set pitch
  207. float pitchV = inputs[PITCH_INPUT].value + params[COARSE_PARAM].value + params[FINE_PARAM].value / 12.0;
  208. if (!settings.meta_modulation)
  209. pitchV += fm;
  210. if (lowCpu)
  211. pitchV += log2f(96000.0 / engineGetSampleRate());
  212. int32_t pitch = (pitchV * 12.0 + 60) * 128;
  213. // pitch_range
  214. if (settings.pitch_range == braids::PITCH_RANGE_EXTERNAL ||
  215. settings.pitch_range == braids::PITCH_RANGE_LFO) {
  216. // no change - calibration not implemented
  217. } else if (settings.pitch_range == braids::PITCH_RANGE_FREE) {
  218. pitch = pitch - 1638;
  219. } else if (settings.pitch_range == braids::PITCH_RANGE_440) {
  220. pitch = 69 << 7;
  221. } else { // PITCH_RANGE_EXTENDED
  222. pitch -= 60 << 7;
  223. pitch = (pitch - 1638) * 9 >> 1;
  224. pitch += 60 << 7;
  225. }
  226. pitch = quantizer.Process( pitch, (60 + settings.quantizer_root) << 7);
  227. // Check if the pitch has changed to cause an auto-retrigger
  228. int32_t pitch_delta = pitch - previous_pitch;
  229. if (settings.auto_trig &&
  230. (pitch_delta >= 0x40 || -pitch_delta >= 0x40)) {
  231. trigger_detected_flag = true;
  232. }
  233. previous_pitch = pitch;
  234. pitch += jitter_source.Render(settings.vco_drift);
  235. pitch += ad_value * settings.ad_fm >> 7;
  236. pitch = clamp(int(pitch), 0, 16383);
  237. if (settings.vco_flatten) {
  238. pitch = braids::Interpolate88(braids::lut_vco_detune, pitch << 2);
  239. }
  240. // pitch_transposition()
  241. int32_t t = settings.pitch_range == braids::PITCH_RANGE_LFO ? -(36 << 7) : 0;
  242. t += (static_cast<int32_t>(settings.pitch_octave) - 2) * 12 * 128;
  243. osc.set_pitch(pitch + t);
  244. //osc.set_pitch(pitch);
  245. if ( trigger_flag ) {
  246. osc.Strike();
  247. envelope.Trigger(braids::ENV_SEGMENT_ATTACK);
  248. trigger_flag = false;
  249. }
  250. // TODO: add a sync input buffer (must be sample rate converted)
  251. uint8_t sync_buffer[24] = {};
  252. int16_t render_buffer[24];
  253. osc.Render(sync_buffer, render_buffer, 24);
  254. // Signature waveshaping, decimation (not yet supported), and bit reduction (not yet supported)
  255. int16_t sample = 0;
  256. size_t decimation_factor = decimation_factors[settings.sample_rate];
  257. uint16_t bit_mask = bit_reduction_masks[settings.resolution];
  258. int32_t gain = settings.ad_vca>0 ? ad_value : 65535;
  259. uint16_t signature = settings.signature * settings.signature * 4095;
  260. for (size_t i = 0; i < 24; i++) {
  261. //const int16_t bit_mask = 0xffff;
  262. if ((i % decimation_factor) == 0) {
  263. sample = render_buffer[i] & bit_mask;
  264. }
  265. sample = sample * gain_lp >> 16;
  266. gain_lp += (gain - gain_lp) >> 4;
  267. int16_t warped = ws.Transform(sample);
  268. render_buffer[i] = stmlib::Mix(sample, warped, signature);
  269. }
  270. if (lowCpu) {
  271. for (int i = 0; i < 24; i++) {
  272. Frame<1> f;
  273. f.samples[0] = render_buffer[i] / 32768.0;
  274. outputBuffer.push(f);
  275. }
  276. }
  277. else {
  278. // Sample rate convert
  279. Frame<1> in[24];
  280. for (int i = 0; i < 24; i++) {
  281. in[i].samples[0] = render_buffer[i] / 32768.0;
  282. }
  283. src.setRates(96000, engineGetSampleRate());
  284. int inLen = 24;
  285. int outLen = outputBuffer.capacity();
  286. src.process(in, &inLen, outputBuffer.endData(), &outLen);
  287. outputBuffer.endIncr(outLen);
  288. }
  289. }
  290. // Output
  291. if (!outputBuffer.empty()) {
  292. Frame<1> f = outputBuffer.shift();
  293. outputs[OUT_OUTPUT].value = 5.0 * f.samples[0];
  294. }
  295. }
  296. struct CornrowsXDisplay : TransparentWidget {
  297. CornrowsX *module;
  298. std::shared_ptr<Font> font;
  299. CornrowsXDisplay() {
  300. font = Font::load(assetPlugin(plugin, "res/hdad-segment14-1.002/Segment14.ttf"));
  301. }
  302. void draw(NVGcontext *vg) override {
  303. int shape=0;
  304. const char *text="";
  305. // Background
  306. NVGcolor backgroundColor = nvgRGB(0x30, 0x10, 0x10);
  307. NVGcolor borderColor = nvgRGB(0xd0, 0xd0, 0xd0);
  308. nvgBeginPath(vg);
  309. nvgRoundedRect(vg, 0.0, 0.0, box.size.x, box.size.y, 5.0);
  310. nvgFillColor(vg, backgroundColor);
  311. nvgFill(vg);
  312. nvgStrokeWidth(vg, 1.5);
  313. nvgStrokeColor(vg, borderColor);
  314. nvgStroke(vg);
  315. nvgFontSize(vg, 20.);
  316. nvgFontFaceId(vg, font->handle);
  317. nvgTextLetterSpacing(vg, 2.);
  318. Vec textPos = Vec(5, 28);
  319. NVGcolor textColor = nvgRGB(0xff, 0x00, 0x00);
  320. nvgFillColor(vg, nvgTransRGBA(textColor, 16));
  321. nvgText(vg, textPos.x, textPos.y, "~~~~", NULL);
  322. nvgFillColor(vg, textColor);
  323. //blink
  324. if ( module->disp_timeout & 0x1000 ) return;
  325. if (module->last_setting_changed == braids::SETTING_OSCILLATOR_SHAPE) {
  326. shape = module->settings.shape;
  327. if (module->paques) {
  328. text = " 49";
  329. } else {
  330. text = algo_values[shape];
  331. }
  332. }
  333. if (module->last_setting_changed == braids::SETTING_META_MODULATION) {
  334. shape = module->settings.ad_timbre;
  335. text = "META";
  336. }
  337. if (module->last_setting_changed == braids::SETTING_RESOLUTION) {
  338. shape = module->settings.resolution;
  339. text = bits_values[shape];
  340. }
  341. if (module->last_setting_changed == braids::SETTING_SAMPLE_RATE) {
  342. shape = module->settings.sample_rate;
  343. text = rates_values[shape];
  344. }
  345. if (module->last_setting_changed == braids::SETTING_TRIG_SOURCE) {
  346. shape = module->settings.ad_timbre;
  347. text = "AUTO";
  348. }
  349. if (module->last_setting_changed == braids::SETTING_TRIG_DELAY) {
  350. shape = module->settings.trig_delay;
  351. text = trig_delay_values[shape];
  352. }
  353. if (module->last_setting_changed == braids::SETTING_AD_ATTACK) {
  354. shape = module->settings.ad_attack;
  355. text = zero_to_fifteen_values[shape];
  356. }
  357. if (module->last_setting_changed == braids::SETTING_AD_DECAY) {
  358. shape = module->settings.ad_decay;
  359. text = zero_to_fifteen_values[shape];
  360. }
  361. if (module->last_setting_changed == braids::SETTING_AD_FM) {
  362. shape = module->settings.ad_fm;
  363. text = zero_to_fifteen_values[shape];
  364. }
  365. if (module->last_setting_changed == braids::SETTING_AD_TIMBRE) {
  366. shape = module->settings.ad_color;
  367. text = zero_to_fifteen_values[shape];
  368. }
  369. if (module->last_setting_changed == braids::SETTING_AD_COLOR) {
  370. shape = module->settings.ad_color;
  371. text = zero_to_fifteen_values[shape];
  372. }
  373. if (module->last_setting_changed == braids::SETTING_AD_VCA) {
  374. shape = module->settings.ad_color;
  375. text = "\\VCA";
  376. }
  377. if (module->last_setting_changed == braids::SETTING_PITCH_RANGE) {
  378. shape = module->settings.pitch_range;
  379. text = pitch_range_values[shape];
  380. }
  381. if (module->last_setting_changed == braids::SETTING_PITCH_OCTAVE) {
  382. shape = module->settings.pitch_octave;
  383. text = octave_values[shape];
  384. }
  385. if (module->last_setting_changed == braids::SETTING_QUANTIZER_SCALE) {
  386. shape = module->settings.quantizer_scale;
  387. text = quantization_values[shape];
  388. }
  389. if (module->last_setting_changed == braids::SETTING_QUANTIZER_ROOT) {
  390. shape = module->settings.quantizer_root;
  391. text = note_values[shape];
  392. }
  393. if (module->last_setting_changed == braids::SETTING_VCO_FLATTEN) {
  394. shape = module->settings.quantizer_scale;
  395. text = "FLAT";
  396. }
  397. if (module->last_setting_changed == braids::SETTING_VCO_DRIFT) {
  398. shape = module->settings.quantizer_scale;
  399. text = "DRFT";
  400. }
  401. if (module->last_setting_changed == braids::SETTING_SIGNATURE) {
  402. shape = module->settings.quantizer_scale;
  403. text = "SIGN";
  404. }
  405. nvgText(vg, textPos.x, textPos.y, text, NULL);
  406. //nvgText(vg, textPos.x, textPos.y, algo_values[shape], NULL);
  407. }
  408. };
  409. struct CornrowsXWidget : ModuleWidget {
  410. Menu *createContextMenu() override;
  411. CornrowsXWidget(CornrowsX *module) : ModuleWidget(module) {
  412. box.size = Vec( 8 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT );
  413. {
  414. SVGPanel *panel = new SVGPanel();
  415. panel->setBackground(SVG::load(assetPlugin(plugin, "res/Cornrows.svg")));
  416. panel->box.size = box.size;
  417. addChild(panel);
  418. }
  419. {
  420. CornrowsXDisplay *display = new CornrowsXDisplay();
  421. display->box.pos = Vec(8, 32);
  422. display->box.size = Vec(80., 34.);
  423. display->module = module;
  424. addChild(display);
  425. }
  426. const float x1 = 4.;
  427. const float xh = 30.;
  428. const float x2 = x1+xh;
  429. const float x3 = x1+2*xh;
  430. const float x4 = x1+3*xh;
  431. // const float x5 = x1+4*xh;
  432. // const float x6 = x1+5*xh;
  433. const float y1 = 115;
  434. const float yh = 36.;
  435. addParam(ParamWidget::create<sp_Encoder>(Vec(x3+4, 78), module, CornrowsX::SHAPE_PARAM, 0.0, 1.0, 0.0));
  436. addInput(Port::create<sp_Port>(Vec(x1, y1-1*yh), Port::INPUT, module, CornrowsX::TRIG_INPUT));
  437. addParam(ParamWidget::create<sp_Trimpot>(Vec(x2, y1-1*yh), module, CornrowsX::TRIG_DELAY_PARAM, 0.0, 1.0, 0.0));
  438. addParam(ParamWidget::create<sp_SmallBlackKnob>(Vec(x1, y1+0*yh), module, CornrowsX::ATT_PARAM, 0.0, 1.0, 0.0));
  439. addParam(ParamWidget::create<sp_SmallBlackKnob>(Vec(x2, y1+0*yh), module, CornrowsX::DEC_PARAM, 0.0, 1.0, 0.5));
  440. addInput(Port::create<sp_Port>(Vec(x1, y1+yh), Port::INPUT, module, CornrowsX::PITCH_INPUT));
  441. addParam(ParamWidget::create<sp_SmallBlackKnob>(Vec(x2, y1+yh), module, CornrowsX::FINE_PARAM, -1.0, 1.0, 0.0));
  442. addParam(ParamWidget::create<sp_SmallBlackKnob>(Vec(x3, y1+yh), module, CornrowsX::COARSE_PARAM, -2.0, 2.0, 0.0));
  443. addParam(ParamWidget::create<sp_Trimpot>(Vec(x4, y1+yh), module, CornrowsX::PITCH_OCTAVE_PARAM, 0.0, 1.0, 0.5));
  444. addParam(ParamWidget::create<sp_SmallBlackKnob>(Vec(x1, y1+2*yh), module, CornrowsX::ROOT_PARAM, 0.0, 1.0, 0.0));
  445. addParam(ParamWidget::create<sp_SmallBlackKnob>(Vec(x2, y1+2*yh), module, CornrowsX::SCALE_PARAM, 0.0, 1.0, 0.0));
  446. addParam(ParamWidget::create<sp_Trimpot>(Vec(x4, y1+2*yh), module, CornrowsX::PITCH_RANGE_PARAM, 0.0, 1.0, 0.));
  447. addInput(Port::create<sp_Port>(Vec(x1, y1+3*yh), Port::INPUT, module, CornrowsX::FM_INPUT));
  448. addParam(ParamWidget::create<sp_SmallBlackKnob>(Vec(x3, y1+3*yh), module, CornrowsX::FM_PARAM, -1.0, 1.0, 0.0));
  449. addParam(ParamWidget::create<sp_Trimpot>(Vec(x4, y1+3*yh), module, CornrowsX::AD_MODULATION_PARAM, 0.0, 1.0, 0.0));
  450. addInput(Port::create<sp_Port>(Vec(x1, y1+4*yh), Port::INPUT, module, CornrowsX::TIMBRE_INPUT));
  451. addParam(ParamWidget::create<sp_Trimpot>(Vec(x2, y1+4*yh), module, CornrowsX::MODULATION_PARAM, -1.0, 1.0, 0.0));
  452. addParam(ParamWidget::create<sp_SmallBlackKnob>(Vec(x3, y1+4*yh), module, CornrowsX::TIMBRE_PARAM, 0.0, 1.0, 0.5));
  453. addParam(ParamWidget::create<sp_Trimpot>(Vec(x4, y1+4*yh), module, CornrowsX::AD_TIMBRE_PARAM, 0.0, 1.0, 0.0));
  454. addInput(Port::create<sp_Port>(Vec(x1, y1+5*yh), Port::INPUT, module, CornrowsX::COLOR_INPUT));
  455. addParam(ParamWidget::create<sp_SmallBlackKnob>(Vec(x3, y1+5*yh), module, CornrowsX::COLOR_PARAM, 0.0, 1.0, 0.5));
  456. addParam(ParamWidget::create<sp_Trimpot>(Vec(x4, y1+5*yh), module, CornrowsX::AD_COLOR_PARAM, 0.0, 1.0, 0.0));
  457. addParam(ParamWidget::create<sp_SmallBlackKnob>(Vec(x1, y1+5.75*yh), module, CornrowsX::BITS_PARAM, 0.0, 1.0, 1.0));
  458. addParam(ParamWidget::create<sp_SmallBlackKnob>(Vec(x2, y1+5.75*yh), module, CornrowsX::RATE_PARAM, 0.0, 1.0, 1.0));
  459. addOutput(Port::create<sp_Port>(Vec(x4, y1+5.75*yh), Port::OUTPUT, module, CornrowsX::OUT_OUTPUT));
  460. }
  461. };
  462. struct CornrowsXSettingItem : MenuItem {
  463. uint8_t *setting = NULL;
  464. uint8_t offValue = 0;
  465. uint8_t onValue = 1;
  466. void onAction(EventAction &e) override {
  467. // Toggle setting
  468. *setting = (*setting == onValue) ? offValue : onValue;
  469. }
  470. void step() override {
  471. rightText = (*setting == onValue) ? "✔" : "";
  472. MenuItem::step();
  473. }
  474. };
  475. struct CornrowsXLowCpuItem : MenuItem {
  476. CornrowsX *braids;
  477. void onAction(EventAction &e) override {
  478. braids->lowCpu = !braids->lowCpu;
  479. }
  480. void step() override {
  481. rightText = (braids->lowCpu) ? "✔" : "";
  482. MenuItem::step();
  483. }
  484. };
  485. struct CornrowsXPaquesItem : MenuItem {
  486. CornrowsX *braids;
  487. void onAction(EventAction &e) override {
  488. braids->paques = !braids->paques;
  489. }
  490. void step() override {
  491. rightText = (braids->paques) ? "✔" : "";
  492. MenuItem::step();
  493. }
  494. };
  495. Menu *CornrowsXWidget::createContextMenu() {
  496. Menu *menu = ModuleWidget::createContextMenu();
  497. CornrowsX *braids = dynamic_cast<CornrowsX*>(module);
  498. assert(braids);
  499. menu->addChild(construct<MenuLabel>());
  500. menu->addChild(construct<MenuLabel>(&MenuLabel::text, "Options"));
  501. menu->addChild(construct<CornrowsXSettingItem>(&MenuItem::text, "META", &CornrowsXSettingItem::setting, &braids->settings.meta_modulation));
  502. menu->addChild(construct<CornrowsXSettingItem>(&MenuItem::text, "AUTO", &CornrowsXSettingItem::setting, &braids->settings.auto_trig));
  503. menu->addChild(construct<CornrowsXSettingItem>(&MenuItem::text, "|\\VCA", &CornrowsXSettingItem::setting, &braids->settings.ad_vca));
  504. menu->addChild(construct<CornrowsXSettingItem>(&MenuItem::text, "FLAT", &CornrowsXSettingItem::setting, &braids->settings.vco_flatten, &CornrowsXSettingItem::onValue, 4));
  505. menu->addChild(construct<CornrowsXSettingItem>(&MenuItem::text, "DRFT", &CornrowsXSettingItem::setting, &braids->settings.vco_drift, &CornrowsXSettingItem::onValue, 4));
  506. menu->addChild(construct<CornrowsXSettingItem>(&MenuItem::text, "SIGN", &CornrowsXSettingItem::setting, &braids->settings.signature, &CornrowsXSettingItem::onValue, 4));
  507. menu->addChild(construct<CornrowsXLowCpuItem>(&MenuItem::text, "Low CPU", &CornrowsXLowCpuItem::braids, braids));
  508. menu->addChild(construct<CornrowsXPaquesItem>(&MenuItem::text, "Paques", &CornrowsXPaquesItem::braids, braids));
  509. return menu;
  510. }
  511. } // namespace rack_plugin_Southpole
  512. using namespace rack_plugin_Southpole;
  513. RACK_PLUGIN_MODEL_INIT(Southpole, CornrowsX) {
  514. Model *modelCornrowsX = Model::create<CornrowsX,CornrowsXWidget>("Southpole", "CornrowsX", "CornrowsX - macro osc", OSCILLATOR_TAG, WAVESHAPER_TAG);
  515. return modelCornrowsX;
  516. }