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.

428 lines
17KB

  1. //***********************************************************************************************
  2. //Colliding Sample and Hold module for VCV Rack by Pierre Collard and Marc Boulé
  3. //
  4. //Based on code from the Fundamental plugins by Andrew Belt and graphics
  5. // from the Component Library by Wes Milholen.
  6. //Also based on code from Joel Robichaud's Nohmad Noise module
  7. //See ./LICENSE.txt for all licenses
  8. //
  9. //***********************************************************************************************
  10. #include <dsp/filter.hpp>
  11. #include <random>
  12. #include "Geodesics.hpp"
  13. namespace rack_plugin_Geodesics {
  14. // By Joel Robichaud - Nohmad Noise module
  15. struct NoiseGenerator {
  16. std::mt19937 rng;
  17. std::uniform_real_distribution<float> uniform;
  18. NoiseGenerator() : uniform(-1.0f, 1.0f) {
  19. rng.seed(std::random_device()());
  20. }
  21. float white() {
  22. return uniform(rng);
  23. }
  24. };
  25. //*****************************************************************************
  26. // By Joel Robichaud - Nohmad Noise module
  27. struct PinkFilter {
  28. float b0, b1, b2, b3, b4, b5, b6; // Coefficients
  29. float y; // Out
  30. void process(float x) {
  31. b0 = 0.99886f * b0 + x * 0.0555179f;
  32. b1 = 0.99332f * b1 + x * 0.0750759f;
  33. b2 = 0.96900f * b2 + x * 0.1538520f;
  34. b3 = 0.86650f * b3 + x * 0.3104856f;
  35. b4 = 0.55000f * b4 + x * 0.5329522f;
  36. b5 = -0.7616f * b5 - x * 0.0168980f;
  37. y = b0 + b1 + b2 + b3 + b4 + b5 + b6 + x * 0.5362f;
  38. b6 = x * 0.115926f;
  39. }
  40. float pink() {
  41. return y;
  42. }
  43. };
  44. //*****************************************************************************
  45. struct Branes : Module {
  46. enum ParamIds {
  47. ENUMS(TRIG_BYPASS_PARAMS, 2),
  48. NUM_PARAMS
  49. };
  50. enum InputIds {
  51. ENUMS(IN_INPUTS, 14),
  52. ENUMS(TRIG_INPUTS, 2),
  53. ENUMS(TRIG_BYPASS_INPUTS, 2),
  54. NUM_INPUTS
  55. };
  56. enum OutputIds {
  57. ENUMS(OUT_OUTPUTS, 14),
  58. NUM_OUTPUTS
  59. };
  60. enum LightIds {
  61. ENUMS(BYPASS_CV_LIGHTS, 2 * 2),// room for white-red
  62. ENUMS(BYPASS_TRIG_LIGHTS, 2 * 2),// room for white-red
  63. NUM_LIGHTS
  64. };
  65. // Constants
  66. // S&H are numbered 0 to 6 in BraneA from lower left to lower right
  67. // S&H are numbered 7 to 13 in BraneB from top right to top left
  68. enum NoiseId {NONE, WHITE, PINK, RED, BLUE};//use negative value for inv phase
  69. int noiseSources[14] = {PINK, RED, BLUE, WHITE, -BLUE, -RED, -PINK, -PINK, -RED, -BLUE, WHITE, BLUE, RED, PINK};
  70. static constexpr float nullNoise = 100.0f;// when a noise has not been generated for the current step
  71. // Need to save, with reset
  72. bool trigBypass[2];
  73. // Need to save, no reset
  74. int panelTheme;
  75. // No need to save, with reset
  76. float heldOuts[14];
  77. // No need to save, no reset
  78. SchmittTrigger sampleTriggers[2];
  79. SchmittTrigger trigBypassTriggers[2];
  80. NoiseGenerator whiteNoise;
  81. PinkFilter pinkFilter;
  82. RCFilter redFilter;
  83. RCFilter blueFilter;
  84. float trigLights[2];
  85. Branes() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  86. // Need to save, no reset
  87. panelTheme = 0;
  88. // No need to save, no reset
  89. for (int i = 0; i < 2; i++) {
  90. sampleTriggers[i].reset();
  91. trigBypassTriggers[i].reset();
  92. trigLights[i] = 0.0f;
  93. }
  94. redFilter.setCutoff(441.0f / engineGetSampleRate());
  95. blueFilter.setCutoff(44100.0f / engineGetSampleRate());
  96. onReset();
  97. }
  98. // widgets are not yet created when module is created
  99. // even if widgets not created yet, can use params[] and should handle 0.0f value since step may call
  100. // this before widget creation anyways
  101. // called from the main thread if by constructor, called by engine thread if right-click initialization
  102. // when called by constructor, module is created before the first step() is called
  103. void onReset() override {
  104. // Need to save, with reset
  105. for (int i = 0; i < 2; i++)
  106. trigBypass[i] = false;
  107. // No need to save, with reset
  108. for (int i = 0; i < 14; i++)
  109. heldOuts[i] = 0.0f;
  110. }
  111. // widgets randomized before onRandomize() is called
  112. // called by engine thread if right-click randomize
  113. void onRandomize() override {
  114. // Need to save, with reset
  115. for (int i = 0; i < 2; i++)
  116. trigBypass[i] = (randomu32() % 2) > 0;
  117. // No need to save, with reset
  118. for (int i = 0; i < 14; i++)
  119. heldOuts[i] = 0.0f;
  120. }
  121. // called by main thread
  122. json_t *toJson() override {
  123. json_t *rootJ = json_object();
  124. // Need to save (reset or not)
  125. // trigBypass
  126. json_object_set_new(rootJ, "trigBypass0", json_real(trigBypass[0]));
  127. json_object_set_new(rootJ, "trigBypass1", json_real(trigBypass[1]));
  128. // panelTheme
  129. json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme));
  130. return rootJ;
  131. }
  132. // widgets have their fromJson() called before this fromJson() is called
  133. // called by main thread
  134. void fromJson(json_t *rootJ) override {
  135. // Need to save (reset or not)
  136. // trigBypass
  137. json_t *trigBypass0J = json_object_get(rootJ, "trigBypass0");
  138. if (trigBypass0J)
  139. trigBypass[0] = json_real_value(trigBypass0J);
  140. json_t *trigBypass1J = json_object_get(rootJ, "trigBypass1");
  141. if (trigBypass1J)
  142. trigBypass[1] = json_real_value(trigBypass1J);
  143. // panelTheme
  144. json_t *panelThemeJ = json_object_get(rootJ, "panelTheme");
  145. if (panelThemeJ)
  146. panelTheme = json_integer_value(panelThemeJ);
  147. // No need to save, with reset
  148. for (int i = 0; i < 14; i++)
  149. heldOuts[i] = 0.0f;
  150. }
  151. // Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate()
  152. void step() override {
  153. float stepNoises[6] = {nullNoise, nullNoise, nullNoise, nullNoise, nullNoise, 0.0f};// order is whiteBase (-1 to 1), white, pink, red, blue, pink_processed (1.0f or 0.0f)
  154. // trigBypass buttons and cv inputs
  155. for (int i = 0; i < 2; i++) {
  156. if (trigBypassTriggers[i].process(params[TRIG_BYPASS_PARAMS + i].value + inputs[TRIG_BYPASS_INPUTS + i].value)) {
  157. trigBypass[i] = !trigBypass[i];
  158. }
  159. }
  160. // trig inputs
  161. bool trigs[2];
  162. bool trigInputsActive[2];
  163. for (int i = 0; i < 2; i++) {
  164. trigs[i] = sampleTriggers[i].process(inputs[TRIG_INPUTS + i].value);
  165. if (trigs[i])
  166. trigLights[i] = 1.0f;
  167. trigInputsActive[i] = trigBypass[i] ? false : inputs[TRIG_INPUTS + i].active;
  168. }
  169. // sample and hold outputs
  170. for (int sh = 0; sh < 14; sh++) {
  171. if (trigInputsActive[sh / 7] || (sh == 13 && trigInputsActive[0]) || (sh == 6 && trigInputsActive[1])) {// trig connected (with crosstrigger mechanism)
  172. if (trigs[sh / 7] || (sh == 13 && trigs[0]) || (sh == 6 && trigs[1])) {
  173. if (inputs[IN_INPUTS + sh].active)// if input cable
  174. heldOuts[sh] = inputs[IN_INPUTS + sh].value;// sample and hold input
  175. else {
  176. int noiseIndex = prepareNoise(stepNoises, sh);// sample and hold noise
  177. heldOuts[sh] = stepNoises[noiseIndex] * (noiseSources[sh] > 0 ? 1.0f : -1.0f);
  178. }
  179. }
  180. }
  181. else { // no trig connected
  182. if (inputs[IN_INPUTS + sh].active) {
  183. heldOuts[sh] = inputs[IN_INPUTS + sh].value;// copy of input if no trig and no input
  184. }
  185. else {
  186. heldOuts[sh] = 0.0f;
  187. if (outputs[OUT_OUTPUTS + sh].active) {
  188. int noiseIndex = prepareNoise(stepNoises, sh);
  189. heldOuts[sh] = stepNoises[noiseIndex] * (noiseSources[sh] > 0 ? 1.0f : -1.0f);
  190. }
  191. }
  192. }
  193. outputs[OUT_OUTPUTS + sh].value = heldOuts[sh];
  194. }
  195. // Lights
  196. for (int i = 0; i < 2; i++) {
  197. float red = trigBypass[i] ? 1.0f : 0.0f;
  198. float white = !trigBypass[i] ? trigLights[i] : 0.0f;
  199. lights[BYPASS_CV_LIGHTS + i * 2 + 0].value = white;
  200. lights[BYPASS_CV_LIGHTS + i * 2 + 1].value = red;
  201. lights[BYPASS_TRIG_LIGHTS + i * 2 + 0].value = white;
  202. lights[BYPASS_TRIG_LIGHTS + i * 2 + 1].value = red;
  203. trigLights[i] -= (trigLights[i] / lightLambda) * (float)engineGetSampleTime();
  204. }
  205. }// step()
  206. int prepareNoise(float* stepNoises, int sh) {
  207. int noiseIndex = abs( noiseSources[sh] );
  208. if (stepNoises[noiseIndex] == nullNoise) {
  209. if (stepNoises[0] == nullNoise)
  210. stepNoises[0] = whiteNoise.white();
  211. if ((noiseIndex == PINK || noiseIndex == BLUE) && stepNoises[5] == 0.0f) {
  212. pinkFilter.process(stepNoises[0]);
  213. stepNoises[5] = 1.0f;
  214. }
  215. switch (noiseIndex) {
  216. // most of the code in here is from Joel Robichaud - Nohmad Noise module
  217. case (PINK) :
  218. stepNoises[noiseIndex] = 5.0f * clamp(0.18f * pinkFilter.pink(), -1.0f, 1.0f);
  219. break;
  220. case (RED) :
  221. redFilter.process(stepNoises[0]);
  222. stepNoises[noiseIndex] = 5.0f * clamp(7.8f * redFilter.lowpass(), -1.0f, 1.0f);
  223. break;
  224. case (BLUE) :
  225. blueFilter.process(pinkFilter.pink());
  226. stepNoises[noiseIndex] = 5.0f * clamp(0.64f * blueFilter.highpass(), -1.0f, 1.0f);
  227. break;
  228. default ://(WHITE)
  229. stepNoises[noiseIndex] = 5.0f * stepNoises[0];
  230. break;
  231. }
  232. }
  233. return noiseIndex;
  234. }
  235. };
  236. struct BranesWidget : ModuleWidget {
  237. struct PanelThemeItem : MenuItem {
  238. Branes *module;
  239. int theme;
  240. void onAction(EventAction &e) override {
  241. module->panelTheme = theme;
  242. }
  243. void step() override {
  244. rightText = (module->panelTheme == theme) ? "✔" : "";
  245. }
  246. };
  247. Menu *createContextMenu() override {
  248. Menu *menu = ModuleWidget::createContextMenu();
  249. MenuLabel *spacerLabel = new MenuLabel();
  250. menu->addChild(spacerLabel);
  251. Branes *module = dynamic_cast<Branes*>(this->module);
  252. assert(module);
  253. MenuLabel *themeLabel = new MenuLabel();
  254. themeLabel->text = "Panel Theme";
  255. menu->addChild(themeLabel);
  256. PanelThemeItem *lightItem = new PanelThemeItem();
  257. lightItem->text = lightPanelID;// Geodesics.hpp
  258. lightItem->module = module;
  259. lightItem->theme = 0;
  260. menu->addChild(lightItem);
  261. PanelThemeItem *darkItem = new PanelThemeItem();
  262. darkItem->text = darkPanelID;// Geodesics.hpp
  263. darkItem->module = module;
  264. darkItem->theme = 1;
  265. //menu->addChild(darkItem);
  266. return menu;
  267. }
  268. BranesWidget(Branes *module) : ModuleWidget(module) {
  269. // Main panel from Inkscape
  270. DynamicSVGPanel *panel = new DynamicSVGPanel();
  271. panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/BranesBG-01.svg")));
  272. //panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/BranesBG-02.svg")));// no dark pannel for now
  273. box.size = panel->box.size;
  274. panel->mode = &module->panelTheme;
  275. addChild(panel);
  276. // Screws
  277. // part of svg panel, no code required
  278. float colRulerCenter = box.size.x / 2.0f;
  279. static constexpr float rowRulerHoldA = 119.5;
  280. static constexpr float rowRulerHoldB = 248.5f;
  281. static constexpr float radiusIn = 35.0f;
  282. static constexpr float radiusOut = 64.0f;
  283. static constexpr float offsetIn = 25.0f;
  284. static constexpr float offsetOut = 46.0f;
  285. // BraneA trig intput
  286. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerHoldA), Port::INPUT, module, Branes::TRIG_INPUTS + 0, &module->panelTheme));
  287. // BraneA inputs
  288. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetIn, rowRulerHoldA + offsetIn), Port::INPUT, module, Branes::IN_INPUTS + 0, &module->panelTheme));
  289. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - radiusIn, rowRulerHoldA), Port::INPUT, module, Branes::IN_INPUTS + 1, &module->panelTheme));
  290. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetIn, rowRulerHoldA - offsetIn), Port::INPUT, module, Branes::IN_INPUTS + 2, &module->panelTheme));
  291. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerHoldA - radiusIn), Port::INPUT, module, Branes::IN_INPUTS + 3, &module->panelTheme));
  292. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetIn, rowRulerHoldA - offsetIn), Port::INPUT, module, Branes::IN_INPUTS + 4, &module->panelTheme));
  293. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + radiusIn, rowRulerHoldA), Port::INPUT, module, Branes::IN_INPUTS + 5, &module->panelTheme));
  294. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetIn, rowRulerHoldA + offsetIn), Port::INPUT, module, Branes::IN_INPUTS + 6, &module->panelTheme));
  295. // BraneA outputs
  296. addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetOut, rowRulerHoldA + offsetOut), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 0, &module->panelTheme));
  297. addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter - radiusOut, rowRulerHoldA), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 1, &module->panelTheme));
  298. addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetOut, rowRulerHoldA - offsetOut), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 2, &module->panelTheme));
  299. addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerHoldA - radiusOut), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 3, &module->panelTheme));
  300. addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetOut, rowRulerHoldA - offsetOut), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 4, &module->panelTheme));
  301. addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter + radiusOut, rowRulerHoldA), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 5, &module->panelTheme));
  302. addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetOut, rowRulerHoldA + offsetOut), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 6, &module->panelTheme));
  303. // BraneB trig intput
  304. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerHoldB), Port::INPUT, module, Branes::TRIG_INPUTS + 1, &module->panelTheme));
  305. // BraneB inputs
  306. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetIn, rowRulerHoldB - offsetIn), Port::INPUT, module, Branes::IN_INPUTS + 7, &module->panelTheme));
  307. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + radiusIn, rowRulerHoldB), Port::INPUT, module, Branes::IN_INPUTS + 8, &module->panelTheme));
  308. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetIn, rowRulerHoldB + offsetIn), Port::INPUT, module, Branes::IN_INPUTS + 9, &module->panelTheme));
  309. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerHoldB + radiusIn), Port::INPUT, module, Branes::IN_INPUTS + 10, &module->panelTheme));
  310. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetIn, rowRulerHoldB + offsetIn), Port::INPUT, module, Branes::IN_INPUTS + 11, &module->panelTheme));
  311. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - radiusIn, rowRulerHoldB), Port::INPUT, module, Branes::IN_INPUTS + 12, &module->panelTheme));
  312. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetIn, rowRulerHoldB - offsetIn), Port::INPUT, module, Branes::IN_INPUTS + 13, &module->panelTheme));
  313. // BraneB outputs
  314. addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetOut, rowRulerHoldB - offsetOut), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 7, &module->panelTheme));
  315. addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter + radiusOut, rowRulerHoldB), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 8, &module->panelTheme));
  316. addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetOut, rowRulerHoldB + offsetOut), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 9, &module->panelTheme));
  317. addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerHoldB + radiusOut), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 10, &module->panelTheme));
  318. addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetOut, rowRulerHoldB + offsetOut), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 11, &module->panelTheme));
  319. addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter - radiusOut, rowRulerHoldB), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 12, &module->panelTheme));
  320. addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetOut, rowRulerHoldB - offsetOut), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 13, &module->panelTheme));
  321. static constexpr float rowRulerBypass = 345.5f;
  322. // Trigger bypass
  323. // buttons
  324. addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter - 32.0f, rowRulerBypass), module, Branes::TRIG_BYPASS_PARAMS + 0, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  325. addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter + 32.0f, rowRulerBypass), module, Branes::TRIG_BYPASS_PARAMS + 1, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  326. // cv inputs
  327. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - 65.0f, rowRulerBypass), Port::INPUT, module, Branes::TRIG_BYPASS_INPUTS + 0, &module->panelTheme));
  328. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + 65.0f, rowRulerBypass), Port::INPUT, module, Branes::TRIG_BYPASS_INPUTS + 1, &module->panelTheme));
  329. // LEDs bottom
  330. addChild(createLightCentered<SmallLight<GeoWhiteRedLight>>(Vec(colRulerCenter - 46.5f, rowRulerBypass), module, Branes::BYPASS_CV_LIGHTS + 0 * 2));
  331. addChild(createLightCentered<SmallLight<GeoWhiteRedLight>>(Vec(colRulerCenter + 46.5f, rowRulerBypass), module, Branes::BYPASS_CV_LIGHTS + 1 * 2));
  332. // LEDs top
  333. addChild(createLightCentered<SmallLight<GeoWhiteRedLight>>(Vec(colRulerCenter + 5.5f, rowRulerHoldA + 19.5f), module, Branes::BYPASS_TRIG_LIGHTS + 0 * 2));
  334. addChild(createLightCentered<SmallLight<GeoWhiteRedLight>>(Vec(colRulerCenter - 5.5f, rowRulerHoldB - 19.5f), module, Branes::BYPASS_TRIG_LIGHTS + 1 * 2));
  335. }
  336. };
  337. } // namespace rack_plugin_Geodesics
  338. using namespace rack_plugin_Geodesics;
  339. RACK_PLUGIN_MODEL_INIT(Geodesics, Branes) {
  340. Model *modelBranes = Model::create<Branes, BranesWidget>("Geodesics", "Branes", "Branes", SAMPLE_AND_HOLD_TAG);
  341. return modelBranes;
  342. }
  343. /*CHANGE LOG
  344. 0.6.0:
  345. created
  346. */