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.

380 lines
11KB

  1. //**************************************************************************************
  2. //
  3. //BPM Clock module for VCV Rack by Alfredo Santamaria - AS - https://github.com/AScustomWorks/AS
  4. //
  5. //Based on code taken from Master Clock Module VCV Module Strum 2017 https://github.com/Strum/Strums_Mental_VCV_Modules
  6. //**************************************************************************************
  7. #include "AS.hpp"
  8. #include "dsp/digital.hpp"
  9. #include <sstream>
  10. #include <iomanip>
  11. struct LFOGenerator {
  12. float phase = 0.0f;
  13. float pw = 0.5f;
  14. float freq = 1.0f;
  15. void setFreq(float freq_to_set)
  16. {
  17. freq = freq_to_set;
  18. }
  19. void step(float dt) {
  20. float deltaPhase = fminf(freq * dt, 0.5f);
  21. phase += deltaPhase;
  22. if (phase >= 1.0f)
  23. phase -= 1.0f;
  24. }
  25. float sqr() {
  26. float sqr = phase < pw ? 1.0f : -1.0f;
  27. return sqr;
  28. }
  29. };
  30. struct BPMClock : Module {
  31. enum ParamIds {
  32. TEMPO_PARAM,
  33. TIMESIGTOP_PARAM,
  34. TIMESIGBOTTOM_PARAM,
  35. RESET_SWITCH,
  36. RUN_SWITCH,
  37. NUM_PARAMS
  38. };
  39. enum InputIds {
  40. RESET_INPUT,
  41. NUM_INPUTS
  42. };
  43. enum OutputIds {
  44. BEAT_OUT,
  45. EIGHTHS_OUT,
  46. SIXTEENTHS_OUT,
  47. BAR_OUT,
  48. RESET_OUTPUT,
  49. NUM_OUTPUTS
  50. };
  51. enum LightIds {
  52. RESET_LED,
  53. RUN_LED,
  54. NUM_LIGHTS
  55. };
  56. LFOGenerator clock;
  57. SchmittTrigger eighths_trig;
  58. SchmittTrigger quarters_trig;
  59. SchmittTrigger bars_trig;
  60. SchmittTrigger run_button_trig;
  61. SchmittTrigger reset_btn_trig;
  62. SchmittTrigger reset_ext_trig;
  63. const float lightLambda = 0.075f;
  64. float resetLight = 0.0f;
  65. bool running = true;
  66. int eighths_count = 0;
  67. int quarters_count = 0;
  68. int bars_count = 0;
  69. int tempo, time_sig_top, time_sig_bottom = 0;
  70. float frequency = 2.0f;
  71. int quarters_count_limit = 4;
  72. int eighths_count_limit = 2;
  73. int bars_count_limit = 16;
  74. BPMClock() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  75. /*
  76. params.resize(NUM_PARAMS);
  77. inputs.resize(NUM_INPUTS);
  78. outputs.resize(NUM_OUTPUTS);
  79. lights.resize(NUM_LIGHTS);
  80. eighths_trig.setThresholds(0.0, 1.0);
  81. quarters_trig.setThresholds(0.0, 1.0);
  82. bars_trig.setThresholds(0.0, 1.0);
  83. */
  84. }
  85. void step() override;
  86. json_t *toJson() override
  87. {
  88. json_t *rootJ = json_object();
  89. json_t *button_statesJ = json_array();
  90. json_t *button_stateJ = json_integer((int)running);
  91. json_array_append_new(button_statesJ, button_stateJ);
  92. json_object_set_new(rootJ, "run", button_statesJ);
  93. return rootJ;
  94. }
  95. void fromJson(json_t *rootJ) override
  96. {
  97. json_t *button_statesJ = json_object_get(rootJ, "run");
  98. if (button_statesJ)
  99. {
  100. json_t *button_stateJ = json_array_get(button_statesJ,0);
  101. if (button_stateJ)
  102. running = !!json_integer_value(button_stateJ);
  103. }
  104. }
  105. };
  106. void BPMClock::step()
  107. {
  108. if (run_button_trig.process(params[RUN_SWITCH].value)){
  109. running = !running;
  110. }
  111. lights[RUN_LED].value = running ? 1.0f : 0.0f;
  112. tempo = std::round(params[TEMPO_PARAM].value);
  113. time_sig_top = std::round(params[TIMESIGTOP_PARAM].value);
  114. time_sig_bottom = std::round(params[TIMESIGBOTTOM_PARAM].value);
  115. time_sig_bottom = std::pow(2,time_sig_bottom+1);
  116. frequency = tempo/60.0f;
  117. //RESET TRIGGERS
  118. //EXTERNAL RESET TRIGGER
  119. if (reset_ext_trig.process(inputs[RESET_INPUT].value)) {
  120. eighths_count = 0;
  121. quarters_count = 0;
  122. bars_count = 0;
  123. resetLight = 1.0;
  124. outputs[RESET_OUTPUT].value = 10.0f;
  125. //INTERNAL RESET TRIGGER
  126. }else if (reset_btn_trig.process(params[RESET_SWITCH].value)) {
  127. eighths_count = 0;
  128. quarters_count = 0;
  129. bars_count = 0;
  130. resetLight = 1.0;
  131. outputs[RESET_OUTPUT].value = 10.0f;
  132. }else{
  133. outputs[RESET_OUTPUT].value = 0.0f;
  134. }
  135. resetLight -= resetLight / lightLambda / engineGetSampleRate();
  136. lights[RESET_LED].value = resetLight;
  137. if (!running) {
  138. eighths_count = 0;
  139. quarters_count = 0;
  140. bars_count = 0;
  141. outputs[BAR_OUT].value = 0.0;
  142. outputs[BEAT_OUT].value = 0.0;
  143. outputs[EIGHTHS_OUT].value = 0.0;
  144. outputs[SIXTEENTHS_OUT].value = 0.0;
  145. outputs[SIXTEENTHS_OUT].value = 0.0;
  146. } else{
  147. if (time_sig_top == time_sig_bottom){
  148. clock.setFreq(frequency*4);
  149. quarters_count_limit = 4;
  150. eighths_count_limit = 2;
  151. bars_count_limit = 16;
  152. } else{
  153. if (time_sig_bottom == 4){
  154. quarters_count_limit = 4;
  155. eighths_count_limit = 2;
  156. bars_count_limit = time_sig_top * 4;
  157. clock.setFreq(frequency*4);
  158. }
  159. if (time_sig_bottom == 8){
  160. quarters_count_limit = 4;
  161. eighths_count_limit = 2;
  162. bars_count_limit = time_sig_top * 2;
  163. clock.setFreq(frequency*4);
  164. if ((time_sig_top % 3) == 0){
  165. quarters_count_limit = 6;
  166. eighths_count_limit = 2;
  167. bars_count_limit = (time_sig_top/3) * 6;
  168. clock.setFreq(frequency*6);
  169. }
  170. }
  171. }
  172. clock.step(1.0 / engineGetSampleRate());
  173. outputs[SIXTEENTHS_OUT].value = clamp(10.0f * clock.sqr(), 0.0f, 10.0f);
  174. if (eighths_trig.process(clock.sqr()) && eighths_count <= eighths_count_limit)
  175. eighths_count++;
  176. if (eighths_count >= eighths_count_limit)
  177. {
  178. eighths_count = 0;
  179. }
  180. if (eighths_count == 0) outputs[EIGHTHS_OUT].value = 10.0f;
  181. else outputs[EIGHTHS_OUT].value = 0.0f;
  182. if (quarters_trig.process(clock.sqr()) && quarters_count <= quarters_count_limit)
  183. quarters_count++;
  184. if (quarters_count >= quarters_count_limit)
  185. {
  186. quarters_count = 0;
  187. }
  188. if (quarters_count == 0) outputs[BEAT_OUT].value = 10.0f;
  189. else outputs[BEAT_OUT].value = 0.0f;
  190. if (bars_trig.process(clock.sqr()) && bars_count <= bars_count_limit)
  191. bars_count++;
  192. if (bars_count >= bars_count_limit)
  193. {
  194. bars_count = 0;
  195. }
  196. if (bars_count == 0) outputs[BAR_OUT].value = 10.0f;
  197. else outputs[BAR_OUT].value = 0.0f;
  198. }
  199. }
  200. ////////////////////////////////////
  201. struct BpmDisplayWidget : TransparentWidget {
  202. int *value;
  203. std::shared_ptr<Font> font;
  204. BpmDisplayWidget() {
  205. font = Font::load(assetPlugin(plugin, "res/Segment7Standard.ttf"));
  206. };
  207. void draw(NVGcontext *vg) override
  208. {
  209. // Background
  210. //NVGcolor backgroundColor = nvgRGB(0x20, 0x20, 0x20);
  211. NVGcolor backgroundColor = nvgRGB(0x20, 0x10, 0x10);
  212. NVGcolor borderColor = nvgRGB(0x10, 0x10, 0x10);
  213. nvgBeginPath(vg);
  214. nvgRoundedRect(vg, 0.0, 0.0, box.size.x, box.size.y, 4.0);
  215. nvgFillColor(vg, backgroundColor);
  216. nvgFill(vg);
  217. nvgStrokeWidth(vg, 1.5);
  218. nvgStrokeColor(vg, borderColor);
  219. nvgStroke(vg);
  220. // text
  221. nvgFontSize(vg, 18);
  222. nvgFontFaceId(vg, font->handle);
  223. nvgTextLetterSpacing(vg, 2.5);
  224. std::stringstream to_display;
  225. to_display << std::setw(3) << *value;
  226. Vec textPos = Vec(4.0f, 17.0f);
  227. NVGcolor textColor = nvgRGB(0xdf, 0xd2, 0x2c);
  228. nvgFillColor(vg, nvgTransRGBA(textColor, 16));
  229. nvgText(vg, textPos.x, textPos.y, "~~", NULL);
  230. textColor = nvgRGB(0xda, 0xe9, 0x29);
  231. nvgFillColor(vg, nvgTransRGBA(textColor, 16));
  232. nvgText(vg, textPos.x, textPos.y, "\\\\", NULL);
  233. textColor = nvgRGB(0xf0, 0x00, 0x00);
  234. nvgFillColor(vg, textColor);
  235. nvgText(vg, textPos.x, textPos.y, to_display.str().c_str(), NULL);
  236. }
  237. };
  238. ////////////////////////////////////
  239. struct SigDisplayWidget : TransparentWidget {
  240. int *value;
  241. std::shared_ptr<Font> font;
  242. SigDisplayWidget() {
  243. font = Font::load(assetPlugin(plugin, "res/Segment7Standard.ttf"));
  244. };
  245. void draw(NVGcontext *vg) override
  246. {
  247. // Background
  248. //NVGcolor backgroundColor = nvgRGB(0x20, 0x20, 0x20);
  249. NVGcolor backgroundColor = nvgRGB(0x20, 0x10, 0x10);
  250. NVGcolor borderColor = nvgRGB(0x10, 0x10, 0x10);
  251. nvgBeginPath(vg);
  252. nvgRoundedRect(vg, 0.0, 0.0, box.size.x, box.size.y, 4.0);
  253. nvgFillColor(vg, backgroundColor);
  254. nvgFill(vg);
  255. nvgStrokeWidth(vg, 1.0);
  256. nvgStrokeColor(vg, borderColor);
  257. nvgStroke(vg);
  258. // text
  259. nvgFontSize(vg, 18);
  260. nvgFontFaceId(vg, font->handle);
  261. nvgTextLetterSpacing(vg, 2.5);
  262. std::stringstream to_display;
  263. to_display << std::setw(2) << *value;
  264. Vec textPos = Vec(3, 17);
  265. NVGcolor textColor = nvgRGB(0xdf, 0xd2, 0x2c);
  266. nvgFillColor(vg, nvgTransRGBA(textColor, 16));
  267. nvgText(vg, textPos.x, textPos.y, "~~", NULL);
  268. textColor = nvgRGB(0xda, 0xe9, 0x29);
  269. nvgFillColor(vg, nvgTransRGBA(textColor, 16));
  270. nvgText(vg, textPos.x, textPos.y, "\\\\", NULL);
  271. textColor = nvgRGB(0xf0, 0x00, 0x00);
  272. nvgFillColor(vg, textColor);
  273. nvgText(vg, textPos.x, textPos.y, to_display.str().c_str(), NULL);
  274. }
  275. };
  276. //////////////////////////////////
  277. struct BPMClockWidget : ModuleWidget
  278. {
  279. BPMClockWidget(BPMClock *module);
  280. };
  281. BPMClockWidget::BPMClockWidget(BPMClock *module) : ModuleWidget(module) {
  282. box.size = Vec(6 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
  283. {
  284. SVGPanel *panel = new SVGPanel();
  285. panel->box.size = box.size;
  286. panel->setBackground(SVG::load(assetPlugin(plugin,"res/BPMClock.svg")));
  287. addChild(panel);
  288. }
  289. //SCREWS
  290. addChild(Widget::create<as_HexScrew>(Vec(RACK_GRID_WIDTH, 0)));
  291. addChild(Widget::create<as_HexScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  292. addChild(Widget::create<as_HexScrew>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  293. addChild(Widget::create<as_HexScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  294. //BPM DISPLAY
  295. BpmDisplayWidget *display = new BpmDisplayWidget();
  296. display->box.pos = Vec(23,50);
  297. display->box.size = Vec(45, 20);
  298. display->value = &module->tempo;
  299. addChild(display);
  300. //TEMPO KNOB
  301. addParam(ParamWidget::create<as_KnobBlack>(Vec(26, 74), module, BPMClock::TEMPO_PARAM, 40.0f, 250.0f, 120.0f));
  302. //SIG TOP DISPLAY
  303. SigDisplayWidget *display2 = new SigDisplayWidget();
  304. display2->box.pos = Vec(54,123);
  305. display2->box.size = Vec(30, 20);
  306. display2->value = &module->time_sig_top;
  307. addChild(display2);
  308. //SIG TOP KNOB
  309. addParam(ParamWidget::create<as_KnobBlack>(Vec(8, 110), module, BPMClock::TIMESIGTOP_PARAM,2.0f, 15.0f, 4.0f));
  310. //SIG BOTTOM DISPLAY
  311. SigDisplayWidget *display3 = new SigDisplayWidget();
  312. display3->box.pos = Vec(54,155);
  313. display3->box.size = Vec(30, 20);
  314. display3->value = &module->time_sig_bottom;
  315. addChild(display3);
  316. //SIG BOTTOM KNOB
  317. addParam(ParamWidget::create<as_KnobBlack>(Vec(8, 150), module, BPMClock::TIMESIGBOTTOM_PARAM,0.0f, 3.0f, 1.0f));
  318. //RESET & RUN LEDS
  319. addParam(ParamWidget::create<LEDBezel>(Vec(55, 202), module, BPMClock::RUN_SWITCH , 0.0f, 1.0f, 0.0f));
  320. addChild(ModuleLightWidget::create<LedLight<RedLight>>(Vec(57.2, 204.3), module, BPMClock::RUN_LED));
  321. addParam(ParamWidget::create<LEDBezel>(Vec(10.5, 202), module, BPMClock::RESET_SWITCH , 0.0f, 1.0f, 0.0f));
  322. addChild(ModuleLightWidget::create<LedLight<RedLight>>(Vec(12.7, 204.3), module, BPMClock::RESET_LED));
  323. //RESET INPUT
  324. addInput(Port::create<as_PJ301MPort>(Vec(10, 240), Port::INPUT, module, BPMClock::RESET_INPUT));
  325. //RESET OUTPUT
  326. addOutput(Port::create<as_PJ301MPort>(Vec(55, 240), Port::OUTPUT, module, BPMClock::RESET_OUTPUT));
  327. //TEMPO OUTPUTS
  328. addOutput(Port::create<as_PJ301MPort>(Vec(10, 280), Port::OUTPUT, module, BPMClock::BAR_OUT));
  329. addOutput(Port::create<as_PJ301MPort>(Vec(55, 280), Port::OUTPUT, module, BPMClock::BEAT_OUT));
  330. addOutput(Port::create<as_PJ301MPort>(Vec(10, 320), Port::OUTPUT, module, BPMClock::EIGHTHS_OUT));
  331. addOutput(Port::create<as_PJ301MPort>(Vec(55, 320), Port::OUTPUT, module, BPMClock::SIXTEENTHS_OUT));
  332. }
  333. Model *modelBPMClock = Model::create<BPMClock, BPMClockWidget>("AS", "BPMClock", "BPM Clock", CLOCK_TAG);