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.

448 lines
13KB

  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. SchmittTrigger resetTrigger;
  16. void setFreq(float freq_to_set)
  17. {
  18. freq = freq_to_set;
  19. }
  20. void step(float dt) {
  21. float deltaPhase = fminf(freq * dt, 0.5f);
  22. phase += deltaPhase;
  23. if (phase >= 1.0f)
  24. phase -= 1.0f;
  25. }
  26. void setReset(float reset) {
  27. if (resetTrigger.process(reset)) {
  28. phase = 0.0f;
  29. }
  30. }
  31. float sqr() {
  32. float sqr = phase < pw ? 1.0f : -1.0f;
  33. return sqr;
  34. }
  35. };
  36. struct BPMClock : Module {
  37. enum ParamIds {
  38. TEMPO_PARAM,
  39. MODE_PARAM,
  40. TIMESIGTOP_PARAM,
  41. TIMESIGBOTTOM_PARAM,
  42. RESET_SWITCH,
  43. RUN_SWITCH,
  44. NUM_PARAMS
  45. };
  46. enum InputIds {
  47. RUN_CV,
  48. RESET_INPUT,
  49. NUM_INPUTS
  50. };
  51. enum OutputIds {
  52. BEAT_OUT,
  53. EIGHTHS_OUT,
  54. SIXTEENTHS_OUT,
  55. BAR_OUT,
  56. RESET_OUTPUT,
  57. RUN_OUTPUT,
  58. NUM_OUTPUTS
  59. };
  60. enum LightIds {
  61. RESET_LED,
  62. RUN_LED,
  63. NUM_LIGHTS
  64. };
  65. LFOGenerator clock;
  66. SchmittTrigger eighths_trig;
  67. SchmittTrigger quarters_trig;
  68. SchmittTrigger bars_trig;
  69. SchmittTrigger run_button_trig;
  70. SchmittTrigger ext_run_trig;
  71. SchmittTrigger reset_btn_trig;
  72. SchmittTrigger reset_ext_trig;
  73. SchmittTrigger bpm_mode_trig;
  74. PulseGenerator resetPulse;
  75. bool reset_pulse = false;
  76. PulseGenerator runPulse;
  77. bool run_pulse = false;
  78. // PULSES FOR TRIGGER OUTPUTS INSTEAD OF GATES
  79. PulseGenerator clockPulse8s;
  80. bool pulse8s = false;
  81. PulseGenerator clockPulse4s;
  82. bool pulse4s = false;
  83. PulseGenerator clockPulse1s;
  84. bool pulse1s = false;
  85. PulseGenerator clockPulse16s;
  86. bool pulse16s = false;
  87. float trigger_length = 0.0001f;
  88. const float lightLambda = 0.075f;
  89. float resetLight = 0.0f;
  90. bool running = true;
  91. int eighths_count = 0;
  92. int quarters_count = 0;
  93. int bars_count = 0;
  94. float tempo =120.0f;
  95. int time_sig_top, time_sig_bottom = 0;
  96. int time_sig_bottom_old = 0;
  97. float frequency = 2.0f;
  98. int quarters_count_limit = 4;
  99. int eighths_count_limit = 2;
  100. int bars_count_limit = 16;
  101. BPMClock() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {}
  102. void step() override;
  103. json_t *toJson() override{
  104. json_t *rootJ = json_object();
  105. json_t *button_statesJ = json_array();
  106. json_t *button_stateJ = json_integer((int)running);
  107. json_array_append_new(button_statesJ, button_stateJ);
  108. json_object_set_new(rootJ, "run", button_statesJ);
  109. return rootJ;
  110. }
  111. void fromJson(json_t *rootJ) override{
  112. json_t *button_statesJ = json_object_get(rootJ, "run");
  113. if (button_statesJ){
  114. json_t *button_stateJ = json_array_get(button_statesJ,0);
  115. if (button_stateJ)
  116. running = !!json_integer_value(button_stateJ);
  117. }
  118. }
  119. };
  120. void BPMClock::step() {
  121. if (run_button_trig.process(params[RUN_SWITCH].value) || ext_run_trig.process(inputs[RUN_CV].value)){
  122. running = !running;
  123. runPulse.trigger(0.01f);
  124. }
  125. lights[RUN_LED].value = running ? 1.0f : 0.0f;
  126. run_pulse = runPulse.process(1.0 / engineGetSampleRate());
  127. outputs[RUN_OUTPUT].value = (run_pulse ? 10.0f : 0.0f);
  128. if (params[MODE_PARAM].value){
  129. //regular 40 to 250 bpm mode
  130. tempo = std::round(params[TEMPO_PARAM].value);
  131. }else{
  132. //extended 30 to 300 mode
  133. tempo = std::round(rescale(params[TEMPO_PARAM].value,40.0f,250.0f, 30.0f, 300.0f) );
  134. }
  135. //tempo = std::round(params[TEMPO_PARAM].value);
  136. time_sig_top = std::round(params[TIMESIGTOP_PARAM].value);
  137. time_sig_bottom = std::round(params[TIMESIGBOTTOM_PARAM].value);
  138. time_sig_bottom = std::pow(2,time_sig_bottom+1);
  139. frequency = tempo/60.0f;
  140. //RESET TRIGGER
  141. if(reset_ext_trig.process(inputs[RESET_INPUT].value) || reset_btn_trig.process(params[RESET_SWITCH].value)) {
  142. clock.setReset(1.0f);
  143. eighths_count = 0;
  144. quarters_count = 0;
  145. bars_count = 0;
  146. resetLight = 1.0;
  147. resetPulse.trigger(0.01f);
  148. }
  149. resetLight -= resetLight / lightLambda / engineGetSampleRate();
  150. lights[RESET_LED].value = resetLight;
  151. reset_pulse = resetPulse.process(1.0 / engineGetSampleRate());
  152. outputs[RESET_OUTPUT].value = (reset_pulse ? 10.0f : 0.0f);
  153. if(!running){
  154. eighths_count = 0;
  155. quarters_count = 0;
  156. bars_count = 0;
  157. outputs[BAR_OUT].value = 0.0f;
  158. outputs[BEAT_OUT].value = 0.0f;
  159. outputs[EIGHTHS_OUT].value = 0.0f;
  160. outputs[SIXTEENTHS_OUT].value = 0.0f;
  161. }else{
  162. if (time_sig_top == time_sig_bottom){
  163. quarters_count_limit = 4;
  164. eighths_count_limit = 2;
  165. bars_count_limit = 16;
  166. clock.setFreq(frequency*4);
  167. }else{
  168. //clock divisions
  169. if(time_sig_bottom == 4){
  170. //debug("time sig bottom = %i", time_sig_bottom);
  171. quarters_count_limit = 4;
  172. eighths_count_limit = 2;
  173. bars_count_limit = time_sig_top * 4;
  174. clock.setFreq(frequency*4);
  175. }
  176. if(time_sig_bottom == 8){
  177. quarters_count_limit = 4;
  178. eighths_count_limit = 2;
  179. bars_count_limit = time_sig_top * 2;
  180. clock.setFreq(frequency*4);
  181. }
  182. if((time_sig_top % 3) == 0){
  183. quarters_count_limit = 6;
  184. eighths_count_limit = 2;
  185. bars_count_limit = (time_sig_top/3) * 6;
  186. clock.setFreq(frequency*6);
  187. }
  188. }
  189. }
  190. if(running){
  191. clock.step(1.0 / engineGetSampleRate());
  192. //16ths
  193. float clock16s = clamp(10.0f * clock.sqr(), 0.0f, 10.0f);
  194. if(clock16s>0){
  195. clockPulse16s.trigger(trigger_length);
  196. }
  197. //8ths
  198. if (eighths_trig.process(clock.sqr()) && eighths_count <= eighths_count_limit){
  199. eighths_count++;
  200. }
  201. if (eighths_count >= eighths_count_limit){
  202. eighths_count = 0;
  203. }
  204. if(eighths_count == 0){
  205. clockPulse8s.trigger(trigger_length);
  206. }
  207. //4ths
  208. if (quarters_trig.process(clock.sqr()) && quarters_count <= quarters_count_limit){
  209. quarters_count++;
  210. }
  211. if (quarters_count >= quarters_count_limit){
  212. quarters_count = 0;
  213. }
  214. if(quarters_count == 0){
  215. clockPulse4s.trigger(trigger_length);
  216. }
  217. //bars
  218. if (bars_trig.process(clock.sqr()) && bars_count <= bars_count_limit){
  219. bars_count++;
  220. }
  221. if (bars_count >= bars_count_limit){
  222. bars_count = 0;
  223. }
  224. if(bars_count == 0){
  225. clockPulse1s.trigger(trigger_length);
  226. }
  227. }
  228. pulse1s = clockPulse1s.process(1.0 / engineGetSampleRate());
  229. pulse4s = clockPulse4s.process(1.0 / engineGetSampleRate());
  230. pulse8s = clockPulse8s.process(1.0 / engineGetSampleRate());
  231. pulse16s = clockPulse16s.process(1.0 / engineGetSampleRate());
  232. outputs[BAR_OUT].value = (pulse1s ? 10.0f : 0.0f);
  233. outputs[BEAT_OUT].value = (pulse4s ? 10.0f : 0.0f);
  234. outputs[EIGHTHS_OUT].value = (pulse8s ? 10.0f : 0.0f);
  235. outputs[SIXTEENTHS_OUT].value = (pulse16s ? 10.0f : 0.0f);
  236. }
  237. ////////////////////////////////////
  238. struct BpmDisplayWidget : TransparentWidget {
  239. float *value;
  240. std::shared_ptr<Font> font;
  241. BpmDisplayWidget() {
  242. font = Font::load(assetPlugin(plugin, "res/Segment7Standard.ttf"));
  243. };
  244. void draw(NVGcontext *vg) override
  245. {
  246. // Background
  247. //NVGcolor backgroundColor = nvgRGB(0x20, 0x20, 0x20);
  248. NVGcolor backgroundColor = nvgRGB(0x20, 0x10, 0x10);
  249. NVGcolor borderColor = nvgRGB(0x10, 0x10, 0x10);
  250. nvgBeginPath(vg);
  251. nvgRoundedRect(vg, 0.0, 0.0, box.size.x, box.size.y, 4.0);
  252. nvgFillColor(vg, backgroundColor);
  253. nvgFill(vg);
  254. nvgStrokeWidth(vg, 1.5);
  255. nvgStrokeColor(vg, borderColor);
  256. nvgStroke(vg);
  257. // text
  258. nvgFontSize(vg, 18);
  259. nvgFontFaceId(vg, font->handle);
  260. nvgTextLetterSpacing(vg, 2.5);
  261. std::stringstream to_display;
  262. to_display << std::setw(3) << *value;
  263. Vec textPos = Vec(4.0f, 17.0f);
  264. NVGcolor textColor = nvgRGB(0xdf, 0xd2, 0x2c);
  265. nvgFillColor(vg, nvgTransRGBA(textColor, 16));
  266. nvgText(vg, textPos.x, textPos.y, "~~~", NULL);
  267. textColor = nvgRGB(0xda, 0xe9, 0x29);
  268. nvgFillColor(vg, nvgTransRGBA(textColor, 16));
  269. nvgText(vg, textPos.x, textPos.y, "\\\\\\", NULL);
  270. textColor = nvgRGB(0xf0, 0x00, 0x00);
  271. nvgFillColor(vg, textColor);
  272. nvgText(vg, textPos.x, textPos.y, to_display.str().c_str(), NULL);
  273. }
  274. };
  275. ////////////////////////////////////
  276. struct SigDisplayWidget : TransparentWidget {
  277. int *value;
  278. std::shared_ptr<Font> font;
  279. SigDisplayWidget() {
  280. font = Font::load(assetPlugin(plugin, "res/Segment7Standard.ttf"));
  281. };
  282. void draw(NVGcontext *vg) override
  283. {
  284. // Background
  285. //NVGcolor backgroundColor = nvgRGB(0x20, 0x20, 0x20);
  286. NVGcolor backgroundColor = nvgRGB(0x20, 0x10, 0x10);
  287. NVGcolor borderColor = nvgRGB(0x10, 0x10, 0x10);
  288. nvgBeginPath(vg);
  289. nvgRoundedRect(vg, 0.0, 0.0, box.size.x, box.size.y, 4.0);
  290. nvgFillColor(vg, backgroundColor);
  291. nvgFill(vg);
  292. nvgStrokeWidth(vg, 1.0);
  293. nvgStrokeColor(vg, borderColor);
  294. nvgStroke(vg);
  295. // text
  296. nvgFontSize(vg, 18);
  297. nvgFontFaceId(vg, font->handle);
  298. nvgTextLetterSpacing(vg, 2.5);
  299. std::stringstream to_display;
  300. to_display << std::setw(2) << *value;
  301. Vec textPos = Vec(3.0f, 17.0f);
  302. NVGcolor textColor = nvgRGB(0xdf, 0xd2, 0x2c);
  303. nvgFillColor(vg, nvgTransRGBA(textColor, 16));
  304. nvgText(vg, textPos.x, textPos.y, "~~", NULL);
  305. textColor = nvgRGB(0xda, 0xe9, 0x29);
  306. nvgFillColor(vg, nvgTransRGBA(textColor, 16));
  307. nvgText(vg, textPos.x, textPos.y, "\\\\", NULL);
  308. textColor = nvgRGB(0xf0, 0x00, 0x00);
  309. nvgFillColor(vg, textColor);
  310. nvgText(vg, textPos.x, textPos.y, to_display.str().c_str(), NULL);
  311. }
  312. };
  313. //////////////////////////////////
  314. struct BPMClockWidget : ModuleWidget {
  315. BPMClockWidget(BPMClock *module);
  316. };
  317. BPMClockWidget::BPMClockWidget(BPMClock *module) : ModuleWidget(module) {
  318. setPanel(SVG::load(assetPlugin(plugin, "res/BPMClock.svg")));
  319. //SCREWS
  320. addChild(Widget::create<as_HexScrew>(Vec(RACK_GRID_WIDTH, 0)));
  321. addChild(Widget::create<as_HexScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  322. addChild(Widget::create<as_HexScrew>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  323. addChild(Widget::create<as_HexScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  324. //BPM DISPLAY
  325. BpmDisplayWidget *display = new BpmDisplayWidget();
  326. display->box.pos = Vec(23,45);
  327. display->box.size = Vec(45, 20);
  328. display->value = &module->tempo;
  329. addChild(display);
  330. //TEMPO KNOB
  331. addParam(ParamWidget::create<as_KnobBlack>(Vec(8, 69), module, BPMClock::TEMPO_PARAM, 40.0f, 250.0f, 120.0f));
  332. //OLD/NEW SWITCH FROM 40-250 TO 30-300
  333. addParam(ParamWidget::create<as_CKSS>(Vec(67, 77), module, BPMClock::MODE_PARAM, 0.0f, 1.0f, 1.0f));
  334. //SIG TOP DISPLAY
  335. SigDisplayWidget *display2 = new SigDisplayWidget();
  336. display2->box.pos = Vec(54,123);
  337. display2->box.size = Vec(30, 20);
  338. display2->value = &module->time_sig_top;
  339. addChild(display2);
  340. //SIG TOP KNOB
  341. addParam(ParamWidget::create<as_Knob>(Vec(8, 110), module, BPMClock::TIMESIGTOP_PARAM,2.0f, 15.0f, 4.0f));
  342. //SIG BOTTOM DISPLAY
  343. SigDisplayWidget *display3 = new SigDisplayWidget();
  344. display3->box.pos = Vec(54,155);
  345. display3->box.size = Vec(30, 20);
  346. display3->value = &module->time_sig_bottom;
  347. addChild(display3);
  348. //SIG BOTTOM KNOB
  349. addParam(ParamWidget::create<as_Knob>(Vec(8, 150), module, BPMClock::TIMESIGBOTTOM_PARAM,0.0f, 3.0f, 1.0f));
  350. //RESET & RUN LEDS
  351. /*
  352. addParam(ParamWidget::create<LEDBezel>(Vec(60.5, 202), module, BPMClock::RUN_SWITCH , 0.0f, 1.0f, 0.0f));
  353. addChild(ModuleLightWidget::create<LedLight<RedLight>>(Vec(62.7, 204.3), module, BPMClock::RUN_LED));
  354. */
  355. addParam(ParamWidget::create<LEDBezel>(Vec(33.5, 202), module, BPMClock::RUN_SWITCH , 0.0f, 1.0f, 0.0f));
  356. addChild(ModuleLightWidget::create<LedLight<RedLight>>(Vec(35.7, 204.3), module, BPMClock::RUN_LED));
  357. addParam(ParamWidget::create<LEDBezel>(Vec(33.5, 241), module, BPMClock::RESET_SWITCH , 0.0f, 1.0f, 0.0f));
  358. addChild(ModuleLightWidget::create<LedLight<RedLight>>(Vec(35.7, 243.2), module, BPMClock::RESET_LED));
  359. //RESET INPUT
  360. addInput(Port::create<as_PJ301MPort>(Vec(6, 240), Port::INPUT, module, BPMClock::RESET_INPUT));
  361. //RESET OUTPUT
  362. addOutput(Port::create<as_PJ301MPort>(Vec(59, 240), Port::OUTPUT, module, BPMClock::RESET_OUTPUT));
  363. //TEMPO OUTPUTS
  364. addOutput(Port::create<as_PJ301MPort>(Vec(6, 280), Port::OUTPUT, module, BPMClock::BAR_OUT));
  365. addOutput(Port::create<as_PJ301MPort>(Vec(59, 280), Port::OUTPUT, module, BPMClock::BEAT_OUT));
  366. addOutput(Port::create<as_PJ301MPort>(Vec(6, 320), Port::OUTPUT, module, BPMClock::EIGHTHS_OUT));
  367. addOutput(Port::create<as_PJ301MPort>(Vec(59, 320), Port::OUTPUT, module, BPMClock::SIXTEENTHS_OUT));
  368. //RUN CV
  369. addInput(Port::create<as_PJ301MPort>(Vec(6, 200), Port::INPUT, module, BPMClock::RUN_CV));
  370. //RUN TRIGGER OUTPUT
  371. addOutput(Port::create<as_PJ301MPort>(Vec(59, 200), Port::OUTPUT, module, BPMClock::RUN_OUTPUT));
  372. }
  373. RACK_PLUGIN_MODEL_INIT(AS, BPMClock) {
  374. Model *modelBPMClock = Model::create<BPMClock, BPMClockWidget>("AS", "BPMClock", "BPM Clock", CLOCK_TAG);
  375. return modelBPMClock;
  376. }