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.

369 lines
10KB

  1. //#include <string.h>
  2. #include "FrozenWasteland.hpp"
  3. #include "dsp/digital.hpp"
  4. #define DIVISIONS 27
  5. namespace rack_plugin_FrozenWasteland {
  6. struct BPMLFO2 : Module {
  7. enum ParamIds {
  8. DIVISION_PARAM,
  9. DIVISION_CV_ATTENUVERTER_PARAM,
  10. SKEW_PARAM,
  11. SKEW_CV_ATTENUVERTER_PARAM,
  12. OFFSET_PARAM,
  13. WAVESHAPE_PARAM,
  14. HOLD_CLOCK_BEHAVIOR_PARAM,
  15. HOLD_MODE_PARAM,
  16. NUM_PARAMS
  17. };
  18. enum InputIds {
  19. CLOCK_INPUT,
  20. DIVISION_INPUT,
  21. SKEW_INPUT,
  22. RESET_INPUT,
  23. HOLD_INPUT,
  24. NUM_INPUTS
  25. };
  26. enum OutputIds {
  27. LFO_OUTPUT,
  28. NUM_OUTPUTS
  29. };
  30. enum LightIds {
  31. CLOCK_LIGHT,
  32. HOLD_LIGHT,
  33. NUM_LIGHTS
  34. };
  35. enum Waveshapes {
  36. SKEWSAW_WAV,
  37. SQUARE_WAV
  38. };
  39. struct LowFrequencyOscillator {
  40. float phase = 0.0;
  41. float freq = 1.0;
  42. float pw = 0.5;
  43. float skew = 0.5; // Triangle
  44. bool offset = false;
  45. void setPitch(float pitch) {
  46. pitch = fminf(pitch, 8.0);
  47. freq = powf(2.0, pitch);
  48. }
  49. void setFrequency(float frequency) {
  50. freq = frequency;
  51. }
  52. void setPulseWidth(float pw_) {
  53. const float pwMin = 0.01;
  54. pw = clamp(pw_, pwMin, 1.0f - pwMin);
  55. }
  56. void hardReset()
  57. {
  58. phase = 0.0;
  59. }
  60. void step(float dt) {
  61. float deltaPhase = fminf(freq * dt, 0.5);
  62. phase += deltaPhase;
  63. if (phase >= 1.0)
  64. phase -= 1.0;
  65. }
  66. float skewsaw(float x) {
  67. x = eucmod(x,1.0);
  68. float inverseSkew = 1 - skew;
  69. float result;
  70. if (skew == 0 && x == 0) //Avoid /0 error
  71. return 2;
  72. if (x <= skew)
  73. result = 2.0 * (1- (-1 / skew * x + 1));
  74. else
  75. result = 2.0 * (1-(1 / inverseSkew * (x - skew)));
  76. return result;
  77. }
  78. float skewsaw() {
  79. if (offset)
  80. return skewsaw(phase);
  81. else
  82. return skewsaw(phase) - 1; //Going to keep same phase for now
  83. //return skewsaw(phase - .5) - 1;
  84. }
  85. float sqr() {
  86. float sqr = (phase < pw) ? 1.0 : -1.0;
  87. return offset ? sqr + 1.0 : sqr;
  88. }
  89. float progress() {
  90. return phase;
  91. }
  92. };
  93. LowFrequencyOscillator oscillator;
  94. SchmittTrigger clockTrigger,resetTrigger,holdTrigger;
  95. float divisions[DIVISIONS] = {1/64.0f,1/32.0f,1/16.0f,1/13.0f,1/11.0f,1/8.0f,1/7.0f,1/6.0f,1/5.0f,1/4.0f,1/3.0f,1/2.0f,1/1.5f,1,1.5f,2,3,4,5,6,7,8,11,13,16,32,64};
  96. const char* divisionNames[DIVISIONS] = {"/64","/32","/16","/13","/11","/8","/7","/6","/5","/4","/3","/2","/1.5","x 1","x 1.5","x 2","x 3","x 4","x 5","x 6","x 7","x 8","x 11","x 13","x 16","x 32","x 64"};
  97. int division = 0;
  98. float time = 0.0;
  99. float duration = 0;
  100. float waveshape = 0;
  101. float skew = 0.5;
  102. bool holding = false;
  103. bool secondClockReceived = false;
  104. float lfoOutputValue = 0.0;
  105. BPMLFO2() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {}
  106. void step() override;
  107. void reset() override {
  108. division = 0;
  109. }
  110. // For more advanced Module features, read Rack's engine.hpp header file
  111. // - toJson, fromJson: serialization of internal data
  112. // - onSampleRateChange: event triggered by a change of sample rate
  113. // - onReset, onRandomize, onCreate, onDelete: implements special behavior when user clicks these from the context menu
  114. };
  115. void BPMLFO2::step() {
  116. time += 1.0 / engineGetSampleRate();
  117. if(inputs[CLOCK_INPUT].active) {
  118. if(clockTrigger.process(inputs[CLOCK_INPUT].value)) {
  119. if(secondClockReceived) {
  120. duration = time;
  121. }
  122. time = 0;
  123. secondClockReceived = true;
  124. }
  125. lights[CLOCK_LIGHT].value = time > (duration/2.0);
  126. }
  127. float divisionf = params[DIVISION_PARAM].value;
  128. if(inputs[DIVISION_INPUT].active) {
  129. divisionf +=(inputs[DIVISION_INPUT].value * params[DIVISION_CV_ATTENUVERTER_PARAM].value * (DIVISIONS / 10.0));
  130. }
  131. divisionf = clamp(divisionf,0.0f,26.0f);
  132. division = int(divisionf);
  133. waveshape = params[WAVESHAPE_PARAM].value;
  134. skew = params[SKEW_PARAM].value;
  135. if(inputs[SKEW_INPUT].active) {
  136. skew +=inputs[SKEW_INPUT].value / 10 * params[SKEW_CV_ATTENUVERTER_PARAM].value;
  137. }
  138. skew = clamp(skew,0.0f,1.0f);
  139. oscillator.offset = (params[OFFSET_PARAM].value > 0.0);
  140. oscillator.skew = skew;
  141. oscillator.setPulseWidth(skew);
  142. if(duration != 0) {
  143. oscillator.setFrequency(1.0 / (duration / divisions[division]));
  144. }
  145. else {
  146. oscillator.setFrequency(0);
  147. }
  148. if(inputs[RESET_INPUT].active) {
  149. if(resetTrigger.process(inputs[RESET_INPUT].value)) {
  150. oscillator.hardReset();
  151. }
  152. }
  153. if(inputs[HOLD_INPUT].active) {
  154. if(params[HOLD_MODE_PARAM].value == 1.0) { //Latched is default
  155. if(holdTrigger.process(inputs[HOLD_INPUT].value)) {
  156. holding = !holding;
  157. }
  158. } else {
  159. holding = inputs[HOLD_INPUT].value >= 1;
  160. }
  161. lights[HOLD_LIGHT].value = holding;
  162. }
  163. if(!holding || (holding && params[HOLD_CLOCK_BEHAVIOR_PARAM].value == 0.0)) {
  164. oscillator.step(1.0 / engineGetSampleRate());
  165. }
  166. if(!holding) {
  167. if(waveshape == SKEWSAW_WAV)
  168. lfoOutputValue = 5.0 * oscillator.skewsaw();
  169. else
  170. lfoOutputValue = 5.0 * oscillator.sqr();
  171. }
  172. outputs[LFO_OUTPUT].value = lfoOutputValue;
  173. }
  174. struct BPMLFO2ProgressDisplay : TransparentWidget {
  175. BPMLFO2 *module;
  176. int frame = 0;
  177. std::shared_ptr<Font> font;
  178. BPMLFO2ProgressDisplay() {
  179. font = Font::load(assetPlugin(plugin, "res/fonts/01 Digit.ttf"));
  180. }
  181. void drawProgress(NVGcontext *vg, int waveshape, float skew, float phase)
  182. {
  183. float inverseSkew = 1 - skew;
  184. float y;
  185. if (skew == 0 && phase == 0) //Avoid /0 error
  186. y = 72.0;
  187. if (phase <= skew)
  188. y = 72.0 * (1- (-1 / skew * phase + 1));
  189. else
  190. y = 72.0 * (1-(1 / inverseSkew * (phase - skew)));
  191. // Draw indicator
  192. nvgFillColor(vg, nvgRGBA(0xff, 0xff, 0x20, 0xff));
  193. if(waveshape == BPMLFO2::SKEWSAW_WAV)
  194. {
  195. nvgBeginPath(vg);
  196. nvgMoveTo(vg,68,213);
  197. if(phase > skew) {
  198. nvgLineTo(vg,68 + (skew * 68),141);
  199. }
  200. nvgLineTo(vg,68 + (phase * 68),213-y);
  201. nvgLineTo(vg,68 + (phase * 68),213);
  202. nvgClosePath(vg);
  203. nvgFill(vg);
  204. } else
  205. {
  206. float endpoint = min(phase,skew);
  207. nvgBeginPath(vg);
  208. nvgMoveTo(vg,68,177);
  209. nvgRect(vg,68,177,endpoint*68,36.0);
  210. //nvgLineTo(vg,68 + (endpoint * 68),213);
  211. //nvgLineTo(vg,68 + (endpoint * 68),177);
  212. //nvgLineTo(vg,68,177);
  213. nvgClosePath(vg);
  214. nvgFill(vg);
  215. if(phase > skew) {
  216. nvgBeginPath(vg);
  217. nvgMoveTo(vg,68 + (skew * 68),141);
  218. nvgRect(vg,68 + (skew * 68),141,(phase-skew)*68,36.0);
  219. //nvgLineTo(vg,68 + (phase * 68),177);
  220. //nvgLineTo(vg,68 + (phase * 68),141);
  221. //nvgMoveTo(vg,68 + (skew * 68),141);
  222. nvgClosePath(vg);
  223. nvgFill(vg);
  224. }
  225. }
  226. }
  227. void drawWaveShape(NVGcontext *vg, int waveshape, float skew)
  228. {
  229. // Draw wave shape
  230. nvgStrokeColor(vg, nvgRGBA(0xff, 0xff, 0x20, 0xff));
  231. nvgStrokeWidth(vg, 2.0);
  232. if(waveshape == BPMLFO2::SKEWSAW_WAV) {
  233. nvgBeginPath(vg);
  234. nvgMoveTo(vg,68,213);
  235. nvgLineTo(vg,68 + (skew * 68),141);
  236. nvgLineTo(vg,136,213);
  237. nvgClosePath(vg);
  238. } else
  239. {
  240. nvgBeginPath(vg);
  241. nvgMoveTo(vg,68,213);
  242. nvgLineTo(vg,68 + (skew * 68),213);
  243. nvgLineTo(vg,68 + (skew * 68),141);
  244. nvgLineTo(vg,136,141);
  245. //nvgClosePath(vg);
  246. }
  247. nvgStroke(vg);
  248. }
  249. void drawDivision(NVGcontext *vg, Vec pos, int division) {
  250. nvgFontSize(vg, 28);
  251. nvgFontFaceId(vg, font->handle);
  252. nvgTextLetterSpacing(vg, -2);
  253. nvgFillColor(vg, nvgRGBA(0x00, 0xff, 0x00, 0xff));
  254. char text[128];
  255. snprintf(text, sizeof(text), "%s", module->divisionNames[division]);
  256. nvgText(vg, pos.x + 52, pos.y, text, NULL);
  257. }
  258. void draw(NVGcontext *vg) override {
  259. drawWaveShape(vg,module->waveshape, module->skew);
  260. drawProgress(vg,module->waveshape, module->skew, module->oscillator.progress());
  261. drawDivision(vg, Vec(0, box.size.y - 153), module->division);
  262. }
  263. };
  264. struct BPMLFO2Widget : ModuleWidget {
  265. BPMLFO2Widget(BPMLFO2 *module);
  266. };
  267. BPMLFO2Widget::BPMLFO2Widget(BPMLFO2 *module) : ModuleWidget(module) {
  268. box.size = Vec(15*10, RACK_GRID_HEIGHT);
  269. {
  270. SVGPanel *panel = new SVGPanel();
  271. panel->box.size = box.size;
  272. panel->setBackground(SVG::load(assetPlugin(plugin, "res/BPMLFO2.svg")));
  273. addChild(panel);
  274. }
  275. addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH - 12, 0)));
  276. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH + 12, 0)));
  277. addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH - 12, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  278. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH + 12, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  279. {
  280. BPMLFO2ProgressDisplay *display = new BPMLFO2ProgressDisplay();
  281. display->module = module;
  282. display->box.pos = Vec(0, 0);
  283. display->box.size = Vec(box.size.x, 220);
  284. addChild(display);
  285. }
  286. addParam(ParamWidget::create<RoundBlackKnob>(Vec(75, 78), module, BPMLFO2::DIVISION_PARAM, 0.0, 26.5, 13.0));
  287. addParam(ParamWidget::create<RoundSmallBlackKnob>(Vec(40, 109), module, BPMLFO2::DIVISION_CV_ATTENUVERTER_PARAM, -1.0, 1.0, 0.0));
  288. addParam(ParamWidget::create<RoundBlackKnob>(Vec(14, 140), module, BPMLFO2::SKEW_PARAM, 0.0, 1.0, 0.5));
  289. addParam(ParamWidget::create<RoundSmallBlackKnob>(Vec(17, 200), module, BPMLFO2::SKEW_CV_ATTENUVERTER_PARAM, -1.0, 1.0, 0.0));
  290. addParam(ParamWidget::create<CKSS>(Vec(12, 240), module, BPMLFO2::OFFSET_PARAM, 0.0, 1.0, 1.0));
  291. addParam(ParamWidget::create<CKSS>(Vec(42, 240), module, BPMLFO2::WAVESHAPE_PARAM, 0.0, 1.0, 0.0));
  292. addParam(ParamWidget::create<CKSS>(Vec(82, 240), module, BPMLFO2::HOLD_CLOCK_BEHAVIOR_PARAM, 0.0, 1.0, 1.0));
  293. addParam(ParamWidget::create<CKSS>(Vec(125, 240), module, BPMLFO2::HOLD_MODE_PARAM, 0.0, 1.0, 1.0));
  294. addInput(Port::create<PJ301MPort>(Vec(40, 81), Port::INPUT, module, BPMLFO2::DIVISION_INPUT));
  295. addInput(Port::create<PJ301MPort>(Vec(16, 172), Port::INPUT, module, BPMLFO2::SKEW_INPUT));
  296. addInput(Port::create<PJ301MPort>(Vec(31, 290), Port::INPUT, module, BPMLFO2::CLOCK_INPUT));
  297. addInput(Port::create<PJ301MPort>(Vec(62, 290), Port::INPUT, module, BPMLFO2::RESET_INPUT));
  298. addInput(Port::create<PJ301MPort>(Vec(94, 290), Port::INPUT, module, BPMLFO2::HOLD_INPUT));
  299. addOutput(Port::create<PJ301MPort>(Vec(63, 336), Port::OUTPUT, module, BPMLFO2::LFO_OUTPUT));
  300. addChild(ModuleLightWidget::create<LargeLight<BlueLight>>(Vec(12, 294), module, BPMLFO2::CLOCK_LIGHT));
  301. addChild(ModuleLightWidget::create<LargeLight<RedLight>>(Vec(122, 294), module, BPMLFO2::HOLD_LIGHT));
  302. }
  303. } // namespace rack_plugin_FrozenWasteland
  304. using namespace rack_plugin_FrozenWasteland;
  305. RACK_PLUGIN_MODEL_INIT(FrozenWasteland, BPMLFO2) {
  306. Model *modelBPMLFO2 = Model::create<BPMLFO2, BPMLFO2Widget>("Frozen Wasteland", "BPMLFO2", "BPM LFO 2", LFO_TAG);
  307. return modelBPMLFO2;
  308. }