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.

345 lines
13KB

  1. /*
  2. * DISTRHO Cardinal Plugin
  3. * Copyright (C) 2021-2024 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * This program is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU General Public License as
  7. * published by the Free Software Foundation; either version 3 of
  8. * the License, or any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * For a full copy of the GNU General Public License see the LICENSE file.
  16. */
  17. #include "plugin.hpp"
  18. #include "plugincontext.hpp"
  19. #include "ModuleWidgets.hpp"
  20. #include "engine/TerminalModule.hpp"
  21. // --------------------------------------------------------------------------------------------------------------------
  22. struct HostTime : TerminalModule {
  23. enum ParamIds {
  24. NUM_PARAMS
  25. };
  26. enum InputIds {
  27. NUM_INPUTS
  28. };
  29. enum HostTimeIds {
  30. kHostTimeRolling,
  31. kHostTimeReset,
  32. kHostTimeBar,
  33. kHostTimeBeat,
  34. kHostTimeClock,
  35. kHostTimeBarPhase,
  36. kHostTimeBeatPhase,
  37. kHostTimeCount
  38. };
  39. enum BarDivisions {
  40. Bars1 = 1,
  41. Bars4 = 4,
  42. Bars8 = 8
  43. };
  44. const CardinalPluginContext* const pcontext;
  45. rack::dsp::PulseGenerator pulseReset, pulseBar, pulseBeat, pulseClock;
  46. float sampleTime = 0.0f;
  47. uint32_t lastProcessCounter = 0;
  48. BarDivisions barDivision = Bars1;
  49. // cached time values
  50. struct {
  51. bool reset = true;
  52. int32_t bar = 0;
  53. int32_t beat = 0;
  54. double tick = 0.0;
  55. double tickClock = 0.0;
  56. uint32_t seconds = 0;
  57. } timeInfo;
  58. HostTime()
  59. : pcontext(static_cast<CardinalPluginContext*>(APP))
  60. {
  61. if (pcontext == nullptr)
  62. throw rack::Exception("Plugin context is null.");
  63. config(NUM_PARAMS, NUM_INPUTS, kHostTimeCount, kHostTimeCount);
  64. }
  65. void processTerminalInput(const ProcessArgs& args) override
  66. {
  67. const uint32_t processCounter = pcontext->processCounter;
  68. // local variables for faster access
  69. double tick, tickClock;
  70. // Update time position if running a new audio block
  71. if (lastProcessCounter != processCounter)
  72. {
  73. lastProcessCounter = processCounter;
  74. timeInfo.reset = pcontext->reset;
  75. timeInfo.bar = pcontext->bar;
  76. timeInfo.beat = pcontext->beat;
  77. timeInfo.seconds = pcontext->frame / pcontext->sampleRate;
  78. tick = pcontext->tick;
  79. tickClock = pcontext->tickClock;
  80. }
  81. else
  82. {
  83. tick = timeInfo.tick;
  84. tickClock = timeInfo.tickClock;
  85. }
  86. const bool playing = pcontext->playing;
  87. const bool playingWithBBT = playing && pcontext->bbtValid;
  88. if (playingWithBBT)
  89. {
  90. if (d_isZero(tick))
  91. {
  92. pulseBeat.trigger();
  93. if (timeInfo.beat == 1)
  94. pulseBar.trigger();
  95. }
  96. if (d_isZero(tickClock))
  97. pulseClock.trigger();
  98. if (timeInfo.reset)
  99. {
  100. timeInfo.reset = false;
  101. pulseReset.trigger();
  102. }
  103. tick += pcontext->ticksPerFrame;
  104. // give a little help to keep tick active,
  105. // as otherwise we might miss it if located at the very end of the audio block
  106. if (tick + 0.0001 >= pcontext->ticksPerBeat)
  107. {
  108. tick -= pcontext->ticksPerBeat;
  109. pulseBeat.trigger();
  110. if (++timeInfo.beat > pcontext->beatsPerBar)
  111. {
  112. timeInfo.beat = 1;
  113. ++timeInfo.bar;
  114. if (timeInfo.bar % barDivision == 1)
  115. pulseBar.trigger();
  116. }
  117. }
  118. if ((tickClock += pcontext->ticksPerFrame) >= pcontext->ticksPerClock)
  119. {
  120. tickClock -= pcontext->ticksPerClock;
  121. pulseClock.trigger();
  122. }
  123. }
  124. // store back the local values
  125. timeInfo.tick = tick;
  126. timeInfo.tickClock = tickClock;
  127. if (isBypassed())
  128. return;
  129. const bool hasReset = pulseReset.process(args.sampleTime);
  130. const bool hasBar = pulseBar.process(args.sampleTime);
  131. const bool hasBeat = pulseBeat.process(args.sampleTime);
  132. const bool hasClock = pulseClock.process(args.sampleTime);
  133. const float beatPhase = playingWithBBT && pcontext->ticksPerBeat > 0.0
  134. ? tick / pcontext->ticksPerBeat
  135. : 0.0f;
  136. const float barPhase = playingWithBBT && pcontext->beatsPerBar > 0
  137. ? ((float)((timeInfo.bar - 1) % barDivision) + (timeInfo.beat - 1) + beatPhase)
  138. / (pcontext->beatsPerBar * barDivision)
  139. : 0.0f;
  140. lights[kHostTimeRolling].setBrightness(playing ? 1.0f : 0.0f);
  141. lights[kHostTimeReset].setBrightnessSmooth(hasReset ? 1.0f : 0.0f, args.sampleTime * 0.5f);
  142. lights[kHostTimeBar].setBrightnessSmooth(hasBar ? 1.0f : 0.0f, args.sampleTime * 0.5f);
  143. lights[kHostTimeBeat].setBrightnessSmooth(hasBeat ? 1.0f : 0.0f, args.sampleTime);
  144. lights[kHostTimeClock].setBrightnessSmooth(hasClock ? 1.0f : 0.0f, args.sampleTime * 2.0f);
  145. lights[kHostTimeBarPhase].setBrightness(barPhase);
  146. lights[kHostTimeBeatPhase].setBrightness(beatPhase);
  147. outputs[kHostTimeRolling].setVoltage(playing ? 10.0f : 0.0f);
  148. outputs[kHostTimeReset].setVoltage(hasReset ? 10.0f : 0.0f);
  149. outputs[kHostTimeBar].setVoltage(hasBar ? 10.0f : 0.0f);
  150. outputs[kHostTimeBeat].setVoltage(hasBeat ? 10.0f : 0.0f);
  151. outputs[kHostTimeClock].setVoltage(hasClock ? 10.0f : 0.0f);
  152. outputs[kHostTimeBarPhase].setVoltage(barPhase * 10.0f);
  153. outputs[kHostTimeBeatPhase].setVoltage(beatPhase * 10.0f);
  154. }
  155. void processTerminalOutput(const ProcessArgs&) override
  156. {}
  157. json_t* dataToJson() override {
  158. json_t* rootJ = json_object();
  159. json_object_set_new(rootJ, "barDivision", json_integer(barDivision));
  160. return rootJ;
  161. }
  162. void dataFromJson(json_t* rootJ) override {
  163. if (json_t* bdJ = json_object_get(rootJ, "barDivision")) {
  164. int value = json_integer_value(bdJ);
  165. if (value == Bars1 || value == Bars4 || value == Bars8)
  166. barDivision = static_cast<BarDivisions>(value);
  167. }
  168. }
  169. };
  170. // --------------------------------------------------------------------------------------------------------------------
  171. #ifndef HEADLESS
  172. struct HostTimeWidget : ModuleWidgetWith8HP {
  173. static constexpr const float startX = 10.0f;
  174. static constexpr const float startY_top = 71.0f;
  175. static constexpr const float startY_cv = 115.0f;
  176. static constexpr const float padding = 32.0f;
  177. HostTime* const module;
  178. std::string monoFontPath;
  179. HostTimeWidget(HostTime* const m)
  180. : module(m)
  181. {
  182. setModule(m);
  183. setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/HostTime.svg")));
  184. monoFontPath = asset::system("res/fonts/ShareTechMono-Regular.ttf");
  185. createAndAddScrews();
  186. addOutput(createOutput<PJ301MPort>(Vec(startX, startY_cv + 0 * padding), m, HostTime::kHostTimeRolling));
  187. addOutput(createOutput<PJ301MPort>(Vec(startX, startY_cv + 1 * padding), m, HostTime::kHostTimeReset));
  188. addOutput(createOutput<PJ301MPort>(Vec(startX, startY_cv + 2 * padding), m, HostTime::kHostTimeBar));
  189. addOutput(createOutput<PJ301MPort>(Vec(startX, startY_cv + 3 * padding), m, HostTime::kHostTimeBeat));
  190. addOutput(createOutput<PJ301MPort>(Vec(startX, startY_cv + 4 * padding), m, HostTime::kHostTimeClock));
  191. addOutput(createOutput<PJ301MPort>(Vec(startX, startY_cv + 5 * padding), m, HostTime::kHostTimeBarPhase));
  192. addOutput(createOutput<PJ301MPort>(Vec(startX, startY_cv + 6 * padding), m, HostTime::kHostTimeBeatPhase));
  193. const float x = startX + 28;
  194. addChild(createLightCentered<SmallLight<GreenLight>> (Vec(x, startY_cv + 0 * padding + 12), m, HostTime::kHostTimeRolling));
  195. addChild(createLightCentered<SmallLight<WhiteLight>> (Vec(x, startY_cv + 1 * padding + 12), m, HostTime::kHostTimeReset));
  196. addChild(createLightCentered<SmallLight<RedLight>> (Vec(x, startY_cv + 2 * padding + 12), m, HostTime::kHostTimeBar));
  197. addChild(createLightCentered<SmallLight<YellowLight>>(Vec(x, startY_cv + 3 * padding + 12), m, HostTime::kHostTimeBeat));
  198. addChild(createLightCentered<SmallLight<YellowLight>>(Vec(x, startY_cv + 4 * padding + 12), m, HostTime::kHostTimeClock));
  199. addChild(createLightCentered<SmallLight<YellowLight>>(Vec(x, startY_cv + 5 * padding + 12), m, HostTime::kHostTimeBarPhase));
  200. addChild(createLightCentered<SmallLight<YellowLight>>(Vec(x, startY_cv + 6 * padding + 12), m, HostTime::kHostTimeBeatPhase));
  201. }
  202. void drawOutputLine(NVGcontext* const vg, const uint offset, const char* const text)
  203. {
  204. const float y = startY_cv + offset * padding;
  205. nvgBeginPath(vg);
  206. nvgRoundedRect(vg, startX - 1.0f, y - 2.f, box.size.x - startX * 2 + 2.f, 28.f, 4);
  207. nvgFillColor(vg, rack::settings::preferDarkPanels ? nvgRGB(0xd0, 0xd0, 0xd0) : nvgRGB(0x2f, 0x2f, 0x2f));
  208. nvgFill(vg);
  209. nvgBeginPath(vg);
  210. nvgFillColor(vg, rack::settings::preferDarkPanels ? color::BLACK : color::WHITE);
  211. nvgText(vg, startX + 36, y + 16, text, nullptr);
  212. }
  213. void draw(const DrawArgs& args) override
  214. {
  215. drawBackground(args.vg);
  216. nvgFontFaceId(args.vg, 0);
  217. nvgFontSize(args.vg, 14);
  218. drawOutputLine(args.vg, 0, "Playing");
  219. drawOutputLine(args.vg, 1, "Reset");
  220. drawOutputLine(args.vg, 2, "Bar");
  221. drawOutputLine(args.vg, 3, "Beat");
  222. drawOutputLine(args.vg, 4, "Step");
  223. nvgFontSize(args.vg, 11);
  224. drawOutputLine(args.vg, 5, "Bar Phase");
  225. drawOutputLine(args.vg, 6, "Beat Phase");
  226. nvgBeginPath(args.vg);
  227. nvgRoundedRect(args.vg, startX - 1.0f, startY_top, 98.0f, 38.0f, 4); // 98
  228. nvgFillColor(args.vg, color::BLACK);
  229. nvgFill(args.vg);
  230. ModuleWidget::draw(args);
  231. }
  232. void drawLayer(const DrawArgs& args, int layer) override
  233. {
  234. if (layer == 1)
  235. {
  236. nvgFontSize(args.vg, 17);
  237. nvgFillColor(args.vg, nvgRGBf(0.76f, 0.11f, 0.22f));
  238. char timeString1[24];
  239. char timeString2[24];
  240. std::shared_ptr<Font> monoFont = APP->window->loadFont(monoFontPath);
  241. if (module != nullptr && monoFont != nullptr)
  242. {
  243. nvgFontFaceId(args.vg, monoFont->handle);
  244. const uint32_t seconds = module->timeInfo.seconds;
  245. std::snprintf(timeString1, sizeof(timeString1), " %02d:%02d:%02d",
  246. (seconds / 3600) % 100,
  247. (seconds / 60) % 60,
  248. seconds % 60);
  249. std::snprintf(timeString2, sizeof(timeString2), "%03d:%02d:%04d",
  250. module->timeInfo.bar % 1000,
  251. module->timeInfo.beat % 100,
  252. static_cast<int>(module->timeInfo.tick + 0.5));
  253. }
  254. else
  255. {
  256. std::strcpy(timeString1, " 00:00:00");
  257. std::strcpy(timeString2, "001:01:0000");
  258. }
  259. nvgText(args.vg, startX + 3.5f, startY_top + 15.0f, timeString1, nullptr);
  260. nvgText(args.vg, startX + 3.5f, startY_top + 33.0f, timeString2, nullptr);
  261. }
  262. ModuleWidget::drawLayer(args, layer);
  263. }
  264. void appendContextMenu(Menu* menu) override {
  265. struct BarDivisionItem : MenuItem {
  266. HostTime* module;
  267. HostTime::BarDivisions value;
  268. void onAction(const event::Action& e) override {
  269. module->barDivision = value;
  270. }
  271. };
  272. menu->addChild(new MenuSeparator);
  273. menu->addChild(construct<MenuLabel>(&MenuLabel::text, "Bar Division"));
  274. menu->addChild(construct<BarDivisionItem>(&BarDivisionItem::text, "Bars/1", &BarDivisionItem::module, module, &BarDivisionItem::value, HostTime::Bars1));
  275. menu->addChild(construct<BarDivisionItem>(&BarDivisionItem::text, "Bars/4", &BarDivisionItem::module, module, &BarDivisionItem::value, HostTime::Bars4));
  276. menu->addChild(construct<BarDivisionItem>(&BarDivisionItem::text, "Bars/8", &BarDivisionItem::module, module, &BarDivisionItem::value, HostTime::Bars8));
  277. }
  278. };
  279. #else
  280. struct HostTimeWidget : ModuleWidget {
  281. HostTimeWidget(HostTime* const module) {
  282. setModule(module);
  283. for (uint i=0; i<HostTime::kHostTimeCount; ++i)
  284. addOutput(createOutput<PJ301MPort>({}, module, i));
  285. }
  286. };
  287. #endif
  288. // --------------------------------------------------------------------------------------------------------------------
  289. Model* modelHostTime = createModel<HostTime, HostTimeWidget>("HostTime");
  290. // --------------------------------------------------------------------------------------------------------------------