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.

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