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.

350 lines
14KB

  1. #include "plugin.hpp"
  2. #define MAX_REPETITIONS 32 /// max number of repetitions
  3. #define TRIGGER_TIME 0.001
  4. // a tempo/clock calculator that responds to pings - this sets the base tempo, multiplication/division of
  5. // this tempo occurs in the BurstEngine
  6. struct PingableClock {
  7. dsp::Timer timer; // time the gap between pings
  8. dsp::PulseGenerator clockTimer; // counts down from tempo length to zero
  9. dsp::BooleanTrigger clockExpiry; // checks for when the clock timer runs out
  10. float pingDuration = 0.5f; // used for calculating and updating tempo (default 2Hz / 120 bpm)
  11. float tempo = 0.5f; // actual current tempo of clock
  12. PingableClock() {
  13. clockTimer.trigger(tempo);
  14. }
  15. void process(bool pingRecieved, float sampleTime) {
  16. timer.process(sampleTime);
  17. bool clockRestarted = false;
  18. if (pingRecieved) {
  19. bool tempoShouldBeUpdated = true;
  20. float duration = timer.getTime();
  21. // if the ping was unusually different to last time
  22. bool outlier = duration > (pingDuration * 2) || duration < (pingDuration / 2);
  23. // if there is a previous estimate of tempo, but it's an outlier
  24. if ((pingDuration && outlier)) {
  25. // don't calculate tempo from this; prime so future pings will update
  26. tempoShouldBeUpdated = false;
  27. pingDuration = 0;
  28. }
  29. else {
  30. pingDuration = duration;
  31. }
  32. timer.reset();
  33. if (tempoShouldBeUpdated) {
  34. // if the tempo should be updated, do so
  35. tempo = pingDuration;
  36. clockRestarted = true;
  37. }
  38. }
  39. // we restart the clock if a) a new valid ping arrived OR b) the current clock expired
  40. clockRestarted = clockExpiry.process(!clockTimer.process(sampleTime)) || clockRestarted;
  41. if (clockRestarted) {
  42. clockTimer.reset();
  43. clockTimer.trigger(tempo);
  44. }
  45. }
  46. bool isTempoOutHigh() {
  47. // give a 1ms pulse as tempo out
  48. return clockTimer.remaining > tempo - TRIGGER_TIME;
  49. }
  50. };
  51. // engine that generates a burst when triggered
  52. struct BurstEngine {
  53. dsp::PulseGenerator eocOutput; // for generating EOC trigger
  54. dsp::PulseGenerator burstOutput; // for generating triggers for each occurance of the burst
  55. dsp::Timer burstTimer; // for timing how far through the current burst we are
  56. float timings[MAX_REPETITIONS + 1] = {}; // store timings (calculated once on burst trigger)
  57. int triggersOccurred = 0; // how many triggers have been
  58. int triggersRequested = 0; // how many bursts have been requested (fixed over course of burst)
  59. bool active = true; // is there a burst active
  60. bool wasInhibited = false; // was this burst inhibited (i.e. just the first trigger sent)
  61. std::tuple<float, float, bool> process(float sampleTime) {
  62. if (active) {
  63. burstTimer.process(sampleTime);
  64. }
  65. bool eocTriggered = false;
  66. if (burstTimer.time > timings[triggersOccurred]) {
  67. if (triggersOccurred < triggersRequested) {
  68. burstOutput.reset();
  69. burstOutput.trigger(TRIGGER_TIME);
  70. }
  71. else if (triggersOccurred == triggersRequested) {
  72. eocOutput.reset();
  73. eocOutput.trigger(TRIGGER_TIME);
  74. active = false;
  75. eocTriggered = true;
  76. }
  77. triggersOccurred++;
  78. }
  79. const float burstOut = burstOutput.process(sampleTime);
  80. // NOTE: we don't get EOC if the burst was inhibited
  81. const float eocOut = eocOutput.process(sampleTime) * !wasInhibited;
  82. return std::make_tuple(burstOut, eocOut, eocTriggered);
  83. }
  84. void trigger(int numBursts, int multDiv, float baseTimeWindow, float distribution, bool inhibitBurst, bool includeOriginalTrigger) {
  85. active = true;
  86. wasInhibited = inhibitBurst;
  87. // the window in which the burst fits is a multiple (or division) of the base tempo
  88. int divisions = multDiv + (multDiv > 0 ? 1 : multDiv < 0 ? -1 : 0); // skip 2/-2
  89. float actualTimeWindow = baseTimeWindow;
  90. if (divisions > 0) {
  91. actualTimeWindow = baseTimeWindow * divisions;
  92. }
  93. else if (divisions < 0) {
  94. actualTimeWindow = baseTimeWindow / (-divisions);
  95. }
  96. // calculate the times at which triggers should fire, will be skewed by distribution
  97. const float power = 1 + std::abs(distribution) * 2;
  98. for (int i = 0; i <= numBursts; ++i) {
  99. if (distribution >= 0) {
  100. timings[i] = actualTimeWindow * std::pow((float)i / numBursts, power);
  101. }
  102. else {
  103. timings[i] = actualTimeWindow * std::pow((float)i / numBursts, 1 / power);
  104. }
  105. }
  106. triggersOccurred = includeOriginalTrigger ? 0 : 1;
  107. triggersRequested = inhibitBurst ? 1 : numBursts;
  108. burstTimer.reset();
  109. }
  110. };
  111. struct Burst : Module {
  112. enum ParamIds {
  113. CYCLE_PARAM,
  114. QUANTITY_PARAM,
  115. TRIGGER_PARAM,
  116. QUANTITY_CV_PARAM,
  117. DISTRIBUTION_PARAM,
  118. TIME_PARAM,
  119. PROBABILITY_PARAM,
  120. NUM_PARAMS
  121. };
  122. enum InputIds {
  123. QUANTITY_INPUT,
  124. DISTRIBUTION_INPUT,
  125. PING_INPUT,
  126. TIME_INPUT,
  127. PROBABILITY_INPUT,
  128. TRIGGER_INPUT,
  129. NUM_INPUTS
  130. };
  131. enum OutputIds {
  132. TEMPO_OUTPUT,
  133. EOC_OUTPUT,
  134. OUT_OUTPUT,
  135. NUM_OUTPUTS
  136. };
  137. enum LightIds {
  138. ENUMS(QUANTITY_LIGHTS, 16),
  139. TEMPO_LIGHT,
  140. EOC_LIGHT,
  141. OUT_LIGHT,
  142. NUM_LIGHTS
  143. };
  144. dsp::SchmittTrigger pingTrigger; // for detecting Ping in
  145. dsp::SchmittTrigger triggTrigger; // for detecting Trigg in
  146. dsp::BooleanTrigger buttonTrigger; // for detecting when the trigger button is pressed
  147. dsp::ClockDivider ledUpdate; // for only updating LEDs every N samples
  148. const int ledUpdateRate = 16; // LEDs updated every N = 16 samples
  149. PingableClock pingableClock;
  150. BurstEngine burstEngine;
  151. bool includeOriginalTrigger = true;
  152. Burst() {
  153. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  154. configSwitch(Burst::CYCLE_PARAM, 0.0, 1.0, 0.0, "Mode", {"One-shot", "Cycle"});
  155. auto quantityParam = configParam(Burst::QUANTITY_PARAM, 1, MAX_REPETITIONS, 4, "Number of bursts");
  156. quantityParam->snapEnabled = true;
  157. configButton(Burst::TRIGGER_PARAM, "Manual Trigger");
  158. configParam(Burst::QUANTITY_CV_PARAM, 0.0, 1.0, 1.0, "Quantity CV");
  159. configParam(Burst::DISTRIBUTION_PARAM, -1.0, 1.0, 0.0, "Distribution");
  160. auto timeParam = configParam(Burst::TIME_PARAM, -4.0, 4.0, 0.0, "Time Division/Multiplication");
  161. timeParam->snapEnabled = true;
  162. configParam(Burst::PROBABILITY_PARAM, 0.0, 1.0, 0.0, "Probability", "%", 0.f, -100, 100.);
  163. configInput(QUANTITY_INPUT, "Quantity CV");
  164. configInput(DISTRIBUTION_INPUT, "Distribution");
  165. configInput(PING_INPUT, "Ping");
  166. configInput(TIME_INPUT, "Time Division/Multiplication");
  167. configInput(PROBABILITY_INPUT, "Probability");
  168. configInput(TRIGGER_INPUT, "Trigger");
  169. ledUpdate.setDivision(ledUpdateRate);
  170. }
  171. void process(const ProcessArgs& args) override {
  172. const bool pingReceived = pingTrigger.process(inputs[PING_INPUT].getVoltage());
  173. pingableClock.process(pingReceived, args.sampleTime);
  174. if (ledUpdate.process()) {
  175. updateLEDRing(args);
  176. }
  177. const float quantityCV = params[QUANTITY_CV_PARAM].getValue() * clamp(inputs[QUANTITY_INPUT].getVoltage(), -5.0, +10.f) / 5.f;
  178. const int quantity = clamp((int)(params[QUANTITY_PARAM].getValue() + std::round(16 * quantityCV)), 1, MAX_REPETITIONS);
  179. const bool loop = params[CYCLE_PARAM].getValue();
  180. const float divMultCV = 4.0 * inputs[TIME_INPUT].getVoltage() / 10.f;
  181. const int divMult = -clamp((int)(divMultCV + params[TIME_PARAM].getValue()), -4, +4);
  182. const float distributionCV = inputs[DISTRIBUTION_INPUT].getVoltage() / 10.f;
  183. const float distribution = clamp(distributionCV + params[DISTRIBUTION_PARAM].getValue(), -1.f, +1.f);
  184. const bool triggerInputTriggered = triggTrigger.process(inputs[TRIGGER_INPUT].getVoltage());
  185. const bool triggerButtonTriggered = buttonTrigger.process(params[TRIGGER_PARAM].getValue());
  186. const bool startBurst = triggerInputTriggered || triggerButtonTriggered;
  187. if (startBurst) {
  188. const float prob = clamp(params[PROBABILITY_PARAM].getValue() + inputs[PROBABILITY_INPUT].getVoltage() / 10.f, 0.f, 1.f);
  189. const bool inhibitBurst = rack::random::uniform() < prob;
  190. // remember to do at current tempo
  191. burstEngine.trigger(quantity, divMult, pingableClock.tempo, distribution, inhibitBurst, includeOriginalTrigger);
  192. }
  193. float burstOut, eocOut;
  194. bool eoc;
  195. std::tie(burstOut, eocOut, eoc) = burstEngine.process(args.sampleTime);
  196. // if the burst has finished, we can also re-trigger
  197. if (eoc && loop) {
  198. const float prob = clamp(params[PROBABILITY_PARAM].getValue() + inputs[PROBABILITY_INPUT].getVoltage() / 10.f, 0.f, 1.f);
  199. const bool inhibitBurst = rack::random::uniform() < prob;
  200. // remember to do at current tempo
  201. burstEngine.trigger(quantity, divMult, pingableClock.tempo, distribution, inhibitBurst, includeOriginalTrigger);
  202. }
  203. const bool tempoOutHigh = pingableClock.isTempoOutHigh();
  204. outputs[TEMPO_OUTPUT].setVoltage(10.f * tempoOutHigh);
  205. lights[TEMPO_LIGHT].setBrightnessSmooth(tempoOutHigh, args.sampleTime);
  206. outputs[OUT_OUTPUT].setVoltage(10.f * burstOut);
  207. lights[OUT_LIGHT].setBrightnessSmooth(burstOut, args.sampleTime);
  208. outputs[EOC_OUTPUT].setVoltage(10.f * eocOut);
  209. lights[EOC_LIGHT].setBrightnessSmooth(eocOut, args.sampleTime);
  210. }
  211. void updateLEDRing(const ProcessArgs& args) {
  212. int activeLed;
  213. if (burstEngine.active) {
  214. activeLed = (burstEngine.triggersOccurred - 1) % 16;
  215. }
  216. else {
  217. activeLed = (((int) params[QUANTITY_PARAM].getValue() - 1) % 16);
  218. }
  219. for (int i = 0; i < 16; ++i) {
  220. lights[QUANTITY_LIGHTS + i].setBrightnessSmooth(i == activeLed, args.sampleTime * ledUpdateRate);
  221. }
  222. }
  223. json_t* dataToJson() override {
  224. json_t* rootJ = json_object();
  225. json_object_set_new(rootJ, "includeOriginalTrigger", json_boolean(includeOriginalTrigger));
  226. return rootJ;
  227. }
  228. void dataFromJson(json_t* rootJ) override {
  229. json_t* includeOriginalTriggerJ = json_object_get(rootJ, "includeOriginalTrigger");
  230. if (includeOriginalTriggerJ) {
  231. includeOriginalTrigger = json_boolean_value(includeOriginalTriggerJ);
  232. }
  233. }
  234. };
  235. struct BurstWidget : ModuleWidget {
  236. BurstWidget(Burst* module) {
  237. setModule(module);
  238. setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/panels/Burst.svg")));
  239. addChild(createWidget<Knurlie>(Vec(15, 0)));
  240. addChild(createWidget<Knurlie>(Vec(15, 365)));
  241. addParam(createParam<BefacoSwitch>(mm2px(Vec(28.44228, 10.13642)), module, Burst::CYCLE_PARAM));
  242. addParam(createParam<Davies1900hWhiteKnobEndless>(mm2px(Vec(9.0322, 16.21467)), module, Burst::QUANTITY_PARAM));
  243. addParam(createParam<BefacoPush>(mm2px(Vec(28.43253, 29.6592)), module, Burst::TRIGGER_PARAM));
  244. addParam(createParam<BefacoTinyKnobLightGrey>(mm2px(Vec(17.26197, 41.95461)), module, Burst::QUANTITY_CV_PARAM));
  245. addParam(createParam<BefacoTinyKnobDarkGrey>(mm2px(Vec(22.85243, 58.45676)), module, Burst::DISTRIBUTION_PARAM));
  246. addParam(createParam<BefacoTinyKnobBlack>(mm2px(Vec(28.47229, 74.91607)), module, Burst::TIME_PARAM));
  247. addParam(createParam<BefacoTinyKnobDarkGrey>(mm2px(Vec(22.75115, 91.35201)), module, Burst::PROBABILITY_PARAM));
  248. addInput(createInput<BananutBlack>(mm2px(Vec(2.02153, 42.27628)), module, Burst::QUANTITY_INPUT));
  249. addInput(createInput<BananutBlack>(mm2px(Vec(7.90118, 58.74959)), module, Burst::DISTRIBUTION_INPUT));
  250. addInput(createInput<BananutBlack>(mm2px(Vec(2.05023, 75.25163)), module, Burst::PING_INPUT));
  251. addInput(createInput<BananutBlack>(mm2px(Vec(13.7751, 75.23049)), module, Burst::TIME_INPUT));
  252. addInput(createInput<BananutBlack>(mm2px(Vec(7.89545, 91.66642)), module, Burst::PROBABILITY_INPUT));
  253. addInput(createInput<BananutBlack>(mm2px(Vec(1.11155, 109.30346)), module, Burst::TRIGGER_INPUT));
  254. addOutput(createOutput<BananutRed>(mm2px(Vec(11.07808, 109.30346)), module, Burst::TEMPO_OUTPUT));
  255. addOutput(createOutput<BananutRed>(mm2px(Vec(21.08452, 109.32528)), module, Burst::EOC_OUTPUT));
  256. addOutput(createOutput<BananutRed>(mm2px(Vec(31.01113, 109.30346)), module, Burst::OUT_OUTPUT));
  257. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(14.03676, 9.98712)), module, Burst::QUANTITY_LIGHTS + 0));
  258. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(18.35846, 10.85879)), module, Burst::QUANTITY_LIGHTS + 1));
  259. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(22.05722, 13.31827)), module, Burst::QUANTITY_LIGHTS + 2));
  260. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(24.48707, 16.96393)), module, Burst::QUANTITY_LIGHTS + 3));
  261. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(25.38476, 21.2523)), module, Burst::QUANTITY_LIGHTS + 4));
  262. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(24.48707, 25.5354)), module, Burst::QUANTITY_LIGHTS + 5));
  263. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(22.05722, 29.16905)), module, Burst::QUANTITY_LIGHTS + 6));
  264. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(18.35846, 31.62236)), module, Burst::QUANTITY_LIGHTS + 7));
  265. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(14.03676, 32.48786)), module, Burst::QUANTITY_LIGHTS + 8));
  266. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(9.74323, 31.62236)), module, Burst::QUANTITY_LIGHTS + 9));
  267. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(6.10149, 29.16905)), module, Burst::QUANTITY_LIGHTS + 10));
  268. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(3.68523, 25.5354)), module, Burst::QUANTITY_LIGHTS + 11));
  269. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(2.85312, 21.2523)), module, Burst::QUANTITY_LIGHTS + 12));
  270. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(3.68523, 16.96393)), module, Burst::QUANTITY_LIGHTS + 13));
  271. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(6.10149, 13.31827)), module, Burst::QUANTITY_LIGHTS + 14));
  272. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(9.74323, 10.85879)), module, Burst::QUANTITY_LIGHTS + 15));
  273. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(14.18119, 104.2831)), module, Burst::TEMPO_LIGHT));
  274. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(24.14772, 104.2831)), module, Burst::EOC_LIGHT));
  275. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(34.11425, 104.2831)), module, Burst::OUT_LIGHT));
  276. }
  277. void appendContextMenu(Menu* menu) override {
  278. Burst* module = dynamic_cast<Burst*>(this->module);
  279. assert(module);
  280. menu->addChild(new MenuSeparator());
  281. menu->addChild(createBoolPtrMenuItem("Include original trigger in output", "", &module->includeOriginalTrigger));
  282. }
  283. };
  284. Model* modelBurst = createModel<Burst, BurstWidget>("Burst");