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.

443 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. if(time_sig_bottom == 4){
  169. quarters_count_limit = 4;
  170. eighths_count_limit = 2;
  171. bars_count_limit = time_sig_top * 4;
  172. clock.setFreq(frequency*4);
  173. }
  174. if(time_sig_bottom == 8){
  175. quarters_count_limit = 4;
  176. eighths_count_limit = 2;
  177. bars_count_limit = time_sig_top * 2;
  178. clock.setFreq(frequency*4);
  179. }
  180. if((time_sig_top % 3) == 0){
  181. quarters_count_limit = 6;
  182. eighths_count_limit = 2;
  183. bars_count_limit = (time_sig_top/3) * 6;
  184. clock.setFreq(frequency*6);
  185. }
  186. }
  187. }
  188. if(running){
  189. clock.step(1.0 / engineGetSampleRate());
  190. //16ths
  191. float clock16s = clamp(10.0f * clock.sqr(), 0.0f, 10.0f);
  192. if(clock16s>0){
  193. clockPulse16s.trigger(trigger_length);
  194. }
  195. //8ths
  196. if (eighths_trig.process(clock.sqr()) && eighths_count <= eighths_count_limit){
  197. eighths_count++;
  198. }
  199. if (eighths_count >= eighths_count_limit){
  200. eighths_count = 0;
  201. }
  202. if(eighths_count == 0){
  203. clockPulse8s.trigger(trigger_length);
  204. }
  205. //4ths
  206. if (quarters_trig.process(clock.sqr()) && quarters_count <= quarters_count_limit){
  207. quarters_count++;
  208. }
  209. if (quarters_count >= quarters_count_limit){
  210. quarters_count = 0;
  211. }
  212. if(quarters_count == 0){
  213. clockPulse4s.trigger(trigger_length);
  214. }
  215. //bars
  216. if (bars_trig.process(clock.sqr()) && bars_count <= bars_count_limit){
  217. bars_count++;
  218. }
  219. if (bars_count >= bars_count_limit){
  220. bars_count = 0;
  221. }
  222. if(bars_count == 0){
  223. clockPulse1s.trigger(trigger_length);
  224. }
  225. }
  226. pulse1s = clockPulse1s.process(1.0 / engineGetSampleRate());
  227. pulse4s = clockPulse4s.process(1.0 / engineGetSampleRate());
  228. pulse8s = clockPulse8s.process(1.0 / engineGetSampleRate());
  229. pulse16s = clockPulse16s.process(1.0 / engineGetSampleRate());
  230. outputs[BAR_OUT].value = (pulse1s ? 10.0f : 0.0f);
  231. outputs[BEAT_OUT].value = (pulse4s ? 10.0f : 0.0f);
  232. outputs[EIGHTHS_OUT].value = (pulse8s ? 10.0f : 0.0f);
  233. outputs[SIXTEENTHS_OUT].value = (pulse16s ? 10.0f : 0.0f);
  234. }
  235. ////////////////////////////////////
  236. struct BpmDisplayWidget : TransparentWidget {
  237. float *value;
  238. std::shared_ptr<Font> font;
  239. BpmDisplayWidget() {
  240. font = Font::load(assetPlugin(plugin, "res/Segment7Standard.ttf"));
  241. };
  242. void draw(NVGcontext *vg) override
  243. {
  244. // Background
  245. //NVGcolor backgroundColor = nvgRGB(0x20, 0x20, 0x20);
  246. NVGcolor backgroundColor = nvgRGB(0x20, 0x10, 0x10);
  247. NVGcolor borderColor = nvgRGB(0x10, 0x10, 0x10);
  248. nvgBeginPath(vg);
  249. nvgRoundedRect(vg, 0.0, 0.0, box.size.x, box.size.y, 4.0);
  250. nvgFillColor(vg, backgroundColor);
  251. nvgFill(vg);
  252. nvgStrokeWidth(vg, 1.5);
  253. nvgStrokeColor(vg, borderColor);
  254. nvgStroke(vg);
  255. // text
  256. nvgFontSize(vg, 18);
  257. nvgFontFaceId(vg, font->handle);
  258. nvgTextLetterSpacing(vg, 2.5);
  259. std::stringstream to_display;
  260. to_display << std::setw(3) << *value;
  261. Vec textPos = Vec(4.0f, 17.0f);
  262. NVGcolor textColor = nvgRGB(0xdf, 0xd2, 0x2c);
  263. nvgFillColor(vg, nvgTransRGBA(textColor, 16));
  264. nvgText(vg, textPos.x, textPos.y, "~~~", NULL);
  265. textColor = nvgRGB(0xda, 0xe9, 0x29);
  266. nvgFillColor(vg, nvgTransRGBA(textColor, 16));
  267. nvgText(vg, textPos.x, textPos.y, "\\\\\\", NULL);
  268. textColor = nvgRGB(0xf0, 0x00, 0x00);
  269. nvgFillColor(vg, textColor);
  270. nvgText(vg, textPos.x, textPos.y, to_display.str().c_str(), NULL);
  271. }
  272. };
  273. ////////////////////////////////////
  274. struct SigDisplayWidget : TransparentWidget {
  275. int *value;
  276. std::shared_ptr<Font> font;
  277. SigDisplayWidget() {
  278. font = Font::load(assetPlugin(plugin, "res/Segment7Standard.ttf"));
  279. };
  280. void draw(NVGcontext *vg) override
  281. {
  282. // Background
  283. //NVGcolor backgroundColor = nvgRGB(0x20, 0x20, 0x20);
  284. NVGcolor backgroundColor = nvgRGB(0x20, 0x10, 0x10);
  285. NVGcolor borderColor = nvgRGB(0x10, 0x10, 0x10);
  286. nvgBeginPath(vg);
  287. nvgRoundedRect(vg, 0.0, 0.0, box.size.x, box.size.y, 4.0);
  288. nvgFillColor(vg, backgroundColor);
  289. nvgFill(vg);
  290. nvgStrokeWidth(vg, 1.0);
  291. nvgStrokeColor(vg, borderColor);
  292. nvgStroke(vg);
  293. // text
  294. nvgFontSize(vg, 18);
  295. nvgFontFaceId(vg, font->handle);
  296. nvgTextLetterSpacing(vg, 2.5);
  297. std::stringstream to_display;
  298. to_display << std::setw(2) << *value;
  299. Vec textPos = Vec(3.0f, 17.0f);
  300. NVGcolor textColor = nvgRGB(0xdf, 0xd2, 0x2c);
  301. nvgFillColor(vg, nvgTransRGBA(textColor, 16));
  302. nvgText(vg, textPos.x, textPos.y, "~~", NULL);
  303. textColor = nvgRGB(0xda, 0xe9, 0x29);
  304. nvgFillColor(vg, nvgTransRGBA(textColor, 16));
  305. nvgText(vg, textPos.x, textPos.y, "\\\\", NULL);
  306. textColor = nvgRGB(0xf0, 0x00, 0x00);
  307. nvgFillColor(vg, textColor);
  308. nvgText(vg, textPos.x, textPos.y, to_display.str().c_str(), NULL);
  309. }
  310. };
  311. //////////////////////////////////
  312. struct BPMClockWidget : ModuleWidget {
  313. BPMClockWidget(BPMClock *module);
  314. };
  315. BPMClockWidget::BPMClockWidget(BPMClock *module) : ModuleWidget(module) {
  316. setPanel(SVG::load(assetPlugin(plugin, "res/BPMClock.svg")));
  317. //SCREWS
  318. addChild(Widget::create<as_HexScrew>(Vec(RACK_GRID_WIDTH, 0)));
  319. addChild(Widget::create<as_HexScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  320. addChild(Widget::create<as_HexScrew>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  321. addChild(Widget::create<as_HexScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  322. //BPM DISPLAY
  323. BpmDisplayWidget *display = new BpmDisplayWidget();
  324. display->box.pos = Vec(23,45);
  325. display->box.size = Vec(45, 20);
  326. display->value = &module->tempo;
  327. addChild(display);
  328. //TEMPO KNOB
  329. addParam(ParamWidget::create<as_KnobBlack>(Vec(8, 69), module, BPMClock::TEMPO_PARAM, 40.0f, 250.0f, 120.0f));
  330. //OLD/NEW SWITCH FROM 40-250 TO 30-300
  331. addParam(ParamWidget::create<as_CKSS>(Vec(67, 77), module, BPMClock::MODE_PARAM, 0.0f, 1.0f, 1.0f));
  332. //SIG TOP DISPLAY
  333. SigDisplayWidget *display2 = new SigDisplayWidget();
  334. display2->box.pos = Vec(54,123);
  335. display2->box.size = Vec(30, 20);
  336. display2->value = &module->time_sig_top;
  337. addChild(display2);
  338. //SIG TOP KNOB
  339. addParam(ParamWidget::create<as_Knob>(Vec(8, 110), module, BPMClock::TIMESIGTOP_PARAM,2.0f, 15.0f, 4.0f));
  340. //SIG BOTTOM DISPLAY
  341. SigDisplayWidget *display3 = new SigDisplayWidget();
  342. display3->box.pos = Vec(54,155);
  343. display3->box.size = Vec(30, 20);
  344. display3->value = &module->time_sig_bottom;
  345. addChild(display3);
  346. //SIG BOTTOM KNOB
  347. addParam(ParamWidget::create<as_Knob>(Vec(8, 150), module, BPMClock::TIMESIGBOTTOM_PARAM,0.0f, 3.0f, 1.0f));
  348. //RESET & RUN LEDS
  349. /*
  350. addParam(ParamWidget::create<LEDBezel>(Vec(60.5, 202), module, BPMClock::RUN_SWITCH , 0.0f, 1.0f, 0.0f));
  351. addChild(ModuleLightWidget::create<LedLight<RedLight>>(Vec(62.7, 204.3), module, BPMClock::RUN_LED));
  352. */
  353. addParam(ParamWidget::create<LEDBezel>(Vec(33.5, 202), module, BPMClock::RUN_SWITCH , 0.0f, 1.0f, 0.0f));
  354. addChild(ModuleLightWidget::create<LedLight<RedLight>>(Vec(35.7, 204.3), module, BPMClock::RUN_LED));
  355. addParam(ParamWidget::create<LEDBezel>(Vec(33.5, 241), module, BPMClock::RESET_SWITCH , 0.0f, 1.0f, 0.0f));
  356. addChild(ModuleLightWidget::create<LedLight<RedLight>>(Vec(35.7, 243.2), module, BPMClock::RESET_LED));
  357. //RESET INPUT
  358. addInput(Port::create<as_PJ301MPort>(Vec(6, 240), Port::INPUT, module, BPMClock::RESET_INPUT));
  359. //RESET OUTPUT
  360. addOutput(Port::create<as_PJ301MPort>(Vec(59, 240), Port::OUTPUT, module, BPMClock::RESET_OUTPUT));
  361. //TEMPO OUTPUTS
  362. addOutput(Port::create<as_PJ301MPort>(Vec(6, 280), Port::OUTPUT, module, BPMClock::BAR_OUT));
  363. addOutput(Port::create<as_PJ301MPort>(Vec(59, 280), Port::OUTPUT, module, BPMClock::BEAT_OUT));
  364. addOutput(Port::create<as_PJ301MPort>(Vec(6, 320), Port::OUTPUT, module, BPMClock::EIGHTHS_OUT));
  365. addOutput(Port::create<as_PJ301MPort>(Vec(59, 320), Port::OUTPUT, module, BPMClock::SIXTEENTHS_OUT));
  366. //RUN CV
  367. addInput(Port::create<as_PJ301MPort>(Vec(6, 200), Port::INPUT, module, BPMClock::RUN_CV));
  368. //RUN TRIGGER OUTPUT
  369. addOutput(Port::create<as_PJ301MPort>(Vec(59, 200), Port::OUTPUT, module, BPMClock::RUN_OUTPUT));
  370. }
  371. RACK_PLUGIN_MODEL_INIT(AS, BPMClock) {
  372. Model *modelBPMClock = Model::create<BPMClock, BPMClockWidget>("AS", "BPMClock", "BPM Clock", CLOCK_TAG);
  373. return modelBPMClock;
  374. }