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.

426 lines
12KB

  1. //**************************************************************************************
  2. //
  3. //BPM Calculator module for VCV Rack by Alfredo Santamaria - AS - https://github.com/AScustomWorks/AS
  4. //### BPM detect portions of code by Tomasz Sosnowski - KoralFX
  5. //**************************************************************************************
  6. #include "AS.hpp"
  7. #include "dsp/digital.hpp"
  8. #include <sstream>
  9. #include <iomanip>
  10. struct BPMCalc2 : Module {
  11. enum ParamIds {
  12. TEMPO_PARAM,
  13. NUM_PARAMS
  14. };
  15. enum InputIds {
  16. CLOCK_INPUT,
  17. NUM_INPUTS
  18. };
  19. enum OutputIds {
  20. ENUMS(MS_OUTPUT, 16),
  21. NUM_OUTPUTS
  22. };
  23. enum LightIds {
  24. CLOCK_LOCK_LIGHT,
  25. CLOCK_LIGHT,
  26. NUM_LIGHTS
  27. };
  28. //bpm detector variables
  29. bool inMemory = false;
  30. bool beatLock = false;
  31. float beatTime = 0.0f;
  32. int beatCount = 0;
  33. int beatCountMemory = 0;
  34. float beatOld = 0.0f;
  35. std::string tempo = "---";
  36. SchmittTrigger clockTrigger;
  37. PulseGenerator LightPulse;
  38. bool pulse = false;
  39. //calculator variables
  40. float bpm = 120;
  41. float last_bpm = 0;
  42. float millisecs = 60000;
  43. float mult = 1000;
  44. float millisecondsPerBeat;
  45. float millisecondsPerMeasure;
  46. float bar = 1.0f;
  47. float secondsPerBeat = 0.0f;
  48. float secondsPerMeasure = 0.0f;
  49. //ms variables
  50. float half_note_d = 1.0f;
  51. float half_note = 1.0f;
  52. float half_note_t =1.0f;
  53. float qt_note_d = 1.0f;
  54. float qt_note = 1.0f;
  55. float qt_note_t = 1.0f;
  56. float eight_note_d = 1.0f;
  57. float eight_note =1.0f;
  58. float eight_note_t = 1.0f;
  59. float sixth_note_d =1.0f;
  60. float sixth_note = 1.0f;
  61. float sixth_note_t = 1.0f;
  62. float trth_note_d = 1.0f;
  63. float trth_note = 1.0f;
  64. float trth_note_t = 1.0f;
  65. //hz variables
  66. float hz_bar = 1.0f;
  67. float half_hz_d = 1.0f;
  68. float half_hz = 1.0f;
  69. float half_hz_t = 1.0f;
  70. float qt_hz_d = 1.0f;
  71. float qt_hz = 1.0f;
  72. float qt_hz_t = 1.0f;
  73. float eight_hz_d = 1.0f;
  74. float eight_hz = 1.0f;
  75. float eight_hz_t = 1.0f;
  76. float sixth_hz_d = 1.0f;
  77. float sixth_hz = 1.0f;
  78. float sixth_hz_t = 1.0f;
  79. float trth_hz_d = 1.0f;
  80. float trth_hz = 1.0f;
  81. float trth_hz_t = 1.0f;
  82. void calculateValues(float bpm){
  83. millisecondsPerBeat = millisecs/bpm;
  84. millisecondsPerMeasure = millisecondsPerBeat * 4;
  85. secondsPerBeat = 60 / bpm;
  86. secondsPerMeasure = secondsPerBeat * 4;
  87. bar = (millisecondsPerMeasure);
  88. half_note_d = ( millisecondsPerBeat * 3 );
  89. half_note = ( millisecondsPerBeat * 2 );
  90. half_note_t = ( millisecondsPerBeat * 2 * 2 / 3 );
  91. qt_note_d = ( millisecondsPerBeat / 2 ) * 3;
  92. qt_note = millisecondsPerBeat;
  93. qt_note_t = ( millisecondsPerBeat * 2 ) / 3;
  94. eight_note_d = ( millisecondsPerBeat / 4 ) * 3;
  95. eight_note = millisecondsPerBeat / 2;
  96. eight_note_t = millisecondsPerBeat / 3;
  97. sixth_note_d = ( millisecondsPerBeat / 4 ) * 1.5;
  98. sixth_note = millisecondsPerBeat / 4;
  99. sixth_note_t = millisecondsPerBeat / 6;
  100. trth_note_d = ( millisecondsPerBeat / 8 ) * 1.5;
  101. trth_note = millisecondsPerBeat / 8;
  102. trth_note_t = millisecondsPerBeat / 8 * 2 / 3;
  103. //hz measures
  104. hz_bar = (1/secondsPerMeasure);
  105. half_hz_d = mult / half_note_d;
  106. half_hz = mult / half_note;
  107. half_hz_t = mult / half_note_t;
  108. qt_hz_d = mult / qt_note_d;
  109. qt_hz = mult / qt_note;
  110. qt_hz_t = mult / qt_note_t;
  111. eight_hz_d = mult / eight_note_d;
  112. eight_hz = mult / eight_note;
  113. eight_hz_t = mult / eight_note_t;
  114. sixth_hz_d = mult / sixth_note_d;
  115. sixth_hz = mult / sixth_note;
  116. sixth_hz_t = mult / sixth_note_t;
  117. trth_hz_d = mult / trth_note_d;
  118. trth_hz = mult / trth_note;
  119. trth_hz_t = mult / trth_note_t;
  120. last_bpm = bpm;
  121. //seems like round calcs are not really needed:
  122. /*
  123. half_note_d = std::round(millisecondsPerBeat * 3 * mult)/mult;
  124. half_note = std::round(millisecondsPerBeat * 2 * mult)/mult;
  125. half_note_t = std::round(millisecondsPerBeat * 2 * 2 / 3 * mult)/mult;
  126. */
  127. }
  128. void refreshDetector() {
  129. inMemory = false;
  130. beatLock = false;
  131. beatTime = 0.0f;
  132. beatCount = 0;
  133. beatCountMemory = 0;
  134. beatOld = 0.0f;
  135. tempo = "---";
  136. pulse = false;
  137. }
  138. BPMCalc2() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {}
  139. void step() override;
  140. void onReset() override {
  141. refreshDetector();
  142. }
  143. void onInitialize() {
  144. refreshDetector();
  145. }
  146. };
  147. void BPMCalc2::step() {
  148. //BPM detection code
  149. float deltaTime = engineGetSampleTime();
  150. if ( inputs[CLOCK_INPUT].active ) {
  151. float clockInput = inputs[CLOCK_INPUT].value;
  152. //A rising slope
  153. if ( ( clockTrigger.process( inputs[CLOCK_INPUT].value ) ) && !inMemory ) {
  154. beatCount++;
  155. if(!beatLock){
  156. lights[CLOCK_LIGHT].value = 1.0f;
  157. LightPulse.trigger( 0.1f );
  158. }
  159. inMemory = true;
  160. //BPM is locked
  161. if ( beatCount == 2 ) {
  162. lights[CLOCK_LOCK_LIGHT].value = 1.0f;
  163. beatLock = true;
  164. beatOld = beatTime;
  165. }
  166. //BPM is lost
  167. if ( beatCount > 2 ) {
  168. if ( fabs( beatOld - beatTime ) > 0.0005f ) {
  169. beatLock = false;
  170. beatCount = 0;
  171. lights[CLOCK_LOCK_LIGHT].value = 0.0f;
  172. tempo = "---";
  173. }
  174. }
  175. beatTime = 0;
  176. }
  177. //Falling slope
  178. if ( clockInput <= 0 && inMemory ) {
  179. inMemory = false;
  180. }
  181. //When BPM is locked
  182. if ( beatLock ) {
  183. bpm = (int)round( 60 / beatOld );
  184. tempo = std::to_string( (int)round(bpm) );
  185. if(bpm!=last_bpm){
  186. if(bpm<999){
  187. calculateValues(bpm);
  188. }else{
  189. tempo = "OOR";
  190. }
  191. }
  192. } //end of beatLock routine
  193. beatTime += deltaTime;
  194. //when beat is lost
  195. if ( beatTime > 2 ) {
  196. beatLock = false;
  197. beatCount = 0;
  198. lights[CLOCK_LOCK_LIGHT].value = 0.0f;
  199. tempo = "---";
  200. }
  201. beatCountMemory = beatCount;
  202. } else {
  203. beatLock = false;
  204. beatCount = 0;
  205. //tempo = "OFF";
  206. lights[CLOCK_LOCK_LIGHT].value = 0.0f;
  207. //caluculate with knob value instead of bmp detector value
  208. bpm = params[TEMPO_PARAM].value;
  209. if (bpm<30){
  210. bpm = 30;
  211. }
  212. bpm = (int)round(bpm);
  213. tempo = std::to_string( (int)round(bpm) );
  214. if(bpm!=last_bpm){
  215. calculateValues(bpm);
  216. }
  217. }
  218. pulse = LightPulse.process( 1.0 / engineGetSampleRate() );
  219. lights[CLOCK_LIGHT].value = (pulse ? 1.0f : 0.0f);
  220. //OUTPUTS: MS to 10V scaled values
  221. outputs[MS_OUTPUT+0].value = rescale(bar,0.0f,10000.0f,0.0f,10.0f);
  222. outputs[MS_OUTPUT+1].value = rescale(half_note_d,0.0f,10000.0f,0.0f,10.0f);
  223. outputs[MS_OUTPUT+2].value = rescale(half_note,0.0f,10000.0f,0.0f,10.0f);
  224. outputs[MS_OUTPUT+3].value = rescale(half_note_t,0.0f,10000.0f,0.0f,10.0f);
  225. outputs[MS_OUTPUT+4].value = rescale(qt_note_d,0.0f,10000.0f,0.0f,10.0f);
  226. outputs[MS_OUTPUT+5].value = rescale(qt_note,0.0f,10000.0f,0.0f,10.0f);
  227. outputs[MS_OUTPUT+6].value = rescale(qt_note_t,0.0f,10000.0f,0.0f,10.0f);
  228. outputs[MS_OUTPUT+7].value = rescale(eight_note_d,0.0f,10000.0f,0.0f,10.0f);
  229. outputs[MS_OUTPUT+8].value = rescale(eight_note,0.0f,10000.0f,0.0f,10.0f);
  230. outputs[MS_OUTPUT+9].value = rescale(eight_note_t,0.0f,10000.0f,0.0f,10.0f);
  231. outputs[MS_OUTPUT+10].value = rescale(sixth_note_d,0.0f,10000.0f,0.0f,10.0f);
  232. outputs[MS_OUTPUT+11].value = rescale(sixth_note,0.0f,10000.0f,0.0f,10.0f);
  233. outputs[MS_OUTPUT+12].value = rescale(sixth_note_t,0.0f,10000.0f,0.0f,10.0f);
  234. outputs[MS_OUTPUT+13].value = rescale(trth_note_d,0.0f,10000.0f,0.0f,10.0f);
  235. outputs[MS_OUTPUT+14].value = rescale(trth_note,0.0f,10000.0f,0.0f,10.0f);
  236. outputs[MS_OUTPUT+15].value = rescale(trth_note_t,0.0f,10000.0f,0.0f,10.0f);
  237. }
  238. ////////////////////////////////////
  239. struct TempodisplayWidget : TransparentWidget {
  240. std::string *value;
  241. std::shared_ptr<Font> font;
  242. TempodisplayWidget() {
  243. font = Font::load(assetPlugin(plugin, "res/Segment7Standard.ttf"));
  244. };
  245. void draw(NVGcontext *vg) override
  246. {
  247. // Background
  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(14.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 BPMCalc2Widget : ModuleWidget {
  277. BPMCalc2Widget(BPMCalc2 *module);
  278. };
  279. BPMCalc2Widget::BPMCalc2Widget(BPMCalc2 *module) : ModuleWidget(module) {
  280. setPanel(SVG::load(assetPlugin(plugin, "res/BPMCalc2.svg")));
  281. //SCREWS
  282. addChild(Widget::create<as_HexScrew>(Vec(RACK_GRID_WIDTH, 0)));
  283. addChild(Widget::create<as_HexScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  284. addChild(Widget::create<as_HexScrew>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  285. addChild(Widget::create<as_HexScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  286. //BPM DETECTOR PORT
  287. addInput(Port::create<as_PJ301MPort>(Vec(7, 53), Port::INPUT, module, BPMCalc2::CLOCK_INPUT));
  288. //BPM DISPLAY
  289. TempodisplayWidget *display = new TempodisplayWidget();
  290. display->box.pos = Vec(55,54);
  291. display->box.size = Vec(55, 20);
  292. display->value = &module->tempo;
  293. addChild(display);
  294. //DETECTOR LEDS
  295. addChild(ModuleLightWidget::create<DisplayLedLight<RedLight>>(Vec(57, 56), module, BPMCalc2::CLOCK_LOCK_LIGHT));
  296. addChild(ModuleLightWidget::create<DisplayLedLight<RedLight>>(Vec(57, 66), module, BPMCalc2::CLOCK_LIGHT));
  297. //TEMPO KNOB
  298. addParam(ParamWidget::create<as_KnobBlack>(Vec(45, 84), module, BPMCalc2::TEMPO_PARAM, 30.0f, 300.0f, 120.0f));
  299. //MS outputs
  300. int const out_offset = 40;
  301. // 1
  302. addOutput(Port::create<as_PJ301MPort>(Vec(84, 126), Port::OUTPUT, module, BPMCalc2::MS_OUTPUT + 0));
  303. //·1/2 - 1/2 - t1/2
  304. addOutput(Port::create<as_PJ301MPort>(Vec(8, 126+out_offset*1), Port::OUTPUT, module, BPMCalc2::MS_OUTPUT + 1));
  305. addOutput(Port::create<as_PJ301MPort>(Vec(48, 126+out_offset*1), Port::OUTPUT, module, BPMCalc2::MS_OUTPUT + 2));
  306. addOutput(Port::create<as_PJ301MPort>(Vec(84, 126+out_offset*1), Port::OUTPUT, module, BPMCalc2::MS_OUTPUT + 3));
  307. // ·1/4 - 1/4 - t1/4
  308. addOutput(Port::create<as_PJ301MPort>(Vec(8, 126+out_offset*2), Port::OUTPUT, module, BPMCalc2::MS_OUTPUT + 4));
  309. addOutput(Port::create<as_PJ301MPort>(Vec(48, 126+out_offset*2), Port::OUTPUT, module, BPMCalc2::MS_OUTPUT + 5));
  310. addOutput(Port::create<as_PJ301MPort>(Vec(84, 126+out_offset*2), Port::OUTPUT, module, BPMCalc2::MS_OUTPUT + 6));
  311. // ·1/8 - 1/8 - t1/8
  312. addOutput(Port::create<as_PJ301MPort>(Vec(8, 126+out_offset*3), Port::OUTPUT, module, BPMCalc2::MS_OUTPUT + 7));
  313. addOutput(Port::create<as_PJ301MPort>(Vec(48, 126+out_offset*3), Port::OUTPUT, module, BPMCalc2::MS_OUTPUT + 8));
  314. addOutput(Port::create<as_PJ301MPort>(Vec(84, 126+out_offset*3), Port::OUTPUT, module, BPMCalc2::MS_OUTPUT + 9));
  315. // ·1/16 - 1/16
  316. addOutput(Port::create<as_PJ301MPort>(Vec(8, 126+out_offset*4), Port::OUTPUT, module, BPMCalc2::MS_OUTPUT + 10));
  317. addOutput(Port::create<as_PJ301MPort>(Vec(48, 126+out_offset*4), Port::OUTPUT, module, BPMCalc2::MS_OUTPUT + 11));
  318. // t1/16 - ·1/32
  319. addOutput(Port::create<as_PJ301MPort>(Vec(84, 126+out_offset*4), Port::OUTPUT, module, BPMCalc2::MS_OUTPUT + 12));
  320. addOutput(Port::create<as_PJ301MPort>(Vec(8, 126+out_offset*5), Port::OUTPUT, module, BPMCalc2::MS_OUTPUT + 13));
  321. // 1/32 - t1/32
  322. addOutput(Port::create<as_PJ301MPort>(Vec(48, 126+out_offset*5), Port::OUTPUT, module, BPMCalc2::MS_OUTPUT + 14));
  323. addOutput(Port::create<as_PJ301MPort>(Vec(84, 126+out_offset*5), Port::OUTPUT, module, BPMCalc2::MS_OUTPUT + 15));
  324. }
  325. RACK_PLUGIN_MODEL_INIT(AS, BPMCalc2) {
  326. Model *modelBPMCalc2 = Model::create<BPMCalc2, BPMCalc2Widget>("AS", "BPMCalc2", "BPM to Delay MS Calculator Compact", UTILITY_TAG);
  327. return modelBPMCalc2;
  328. }