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.

291 lines
8.0KB

  1. #include "FrozenWasteland.hpp"
  2. #include "dsp/digital.hpp"
  3. namespace rack_plugin_FrozenWasteland {
  4. struct SeriouslySlowLFO : Module {
  5. enum ParamIds {
  6. TIME_BASE_PARAM,
  7. DURATION_PARAM,
  8. FM_CV_ATTENUVERTER_PARAM,
  9. NUM_PARAMS
  10. };
  11. enum InputIds {
  12. FM_INPUT,
  13. RESET_INPUT,
  14. NUM_INPUTS
  15. };
  16. enum OutputIds {
  17. SIN_OUTPUT,
  18. TRI_OUTPUT,
  19. SAW_OUTPUT,
  20. SQR_OUTPUT,
  21. NUM_OUTPUTS
  22. };
  23. enum LightIds {
  24. MINUTES_LIGHT,
  25. HOURS_LIGHT,
  26. DAYS_LIGHT,
  27. WEEKS_LIGHT,
  28. MONTHS_LIGHT,
  29. NUM_LIGHTS
  30. };
  31. struct LowFrequencyOscillator {
  32. double phase = 0.0;
  33. float pw = 0.5;
  34. float freq = 1.0;
  35. bool offset = false;
  36. bool invert = false;
  37. SchmittTrigger resetTrigger;
  38. LowFrequencyOscillator() {}
  39. void setPitch(float pitch) {
  40. pitch = fminf(pitch, 8.0);
  41. freq = powf(2.0, pitch);
  42. }
  43. void setFrequency(float frequency) {
  44. freq = frequency;
  45. }
  46. void setPulseWidth(float pw_) {
  47. const float pwMin = 0.01;
  48. pw = clamp(pw_, pwMin, 1.0f - pwMin);
  49. }
  50. void setReset(float reset) {
  51. if (resetTrigger.process(reset)) {
  52. phase = 0.0;
  53. }
  54. }
  55. void hardReset()
  56. {
  57. phase = 0.0;
  58. }
  59. void step(float dt) {
  60. //float deltaPhase = fminf(freq * dt, 0.5);
  61. double deltaPhase = freq * dt;
  62. phase += deltaPhase;
  63. if (phase >= 1.0)
  64. phase -= 1.0;
  65. }
  66. float sin() {
  67. if (offset)
  68. return 1.0 - cosf(2*M_PI * phase) * (invert ? -1.0 : 1.0);
  69. else
  70. return sinf(2*M_PI * phase) * (invert ? -1.0 : 1.0);
  71. }
  72. float tri(float x) {
  73. return 4.0 * fabsf(x - roundf(x));
  74. }
  75. float tri() {
  76. if (offset)
  77. return tri(invert ? phase - 0.5 : phase);
  78. else
  79. return -1.0 + tri(invert ? phase - 0.25 : phase - 0.75);
  80. }
  81. float saw(float x) {
  82. return 2.0 * (x - roundf(x));
  83. }
  84. float saw() {
  85. if (offset)
  86. return invert ? 2.0 * (1.0 - phase) : 2.0 * phase;
  87. else
  88. return saw(phase) * (invert ? -1.0 : 1.0);
  89. }
  90. float sqr() {
  91. float sqr = (phase < pw) ^ invert ? 1.0 : -1.0;
  92. return offset ? sqr + 1.0 : sqr;
  93. }
  94. float progress() {
  95. return phase;
  96. }
  97. };
  98. LowFrequencyOscillator oscillator;
  99. SchmittTrigger sumTrigger;
  100. float duration = 0.0;
  101. int timeBase = 0;
  102. SeriouslySlowLFO() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {}
  103. void step() override;
  104. json_t *toJson() override {
  105. json_t *rootJ = json_object();
  106. json_object_set_new(rootJ, "timeBase", json_integer((int) timeBase));
  107. return rootJ;
  108. }
  109. void fromJson(json_t *rootJ) override {
  110. json_t *sumJ = json_object_get(rootJ, "timeBase");
  111. if (sumJ)
  112. timeBase = json_integer_value(sumJ);
  113. }
  114. void reset() override {
  115. timeBase = 0;
  116. }
  117. // For more advanced Module features, read Rack's engine.hpp header file
  118. // - toJson, fromJson: serialization of internal data
  119. // - onSampleRateChange: event triggered by a change of sample rate
  120. // - onReset, onRandomize, onCreate, onDelete: implements special behavior when user clicks these from the context menu
  121. };
  122. void SeriouslySlowLFO::step() {
  123. if (sumTrigger.process(params[TIME_BASE_PARAM].value)) {
  124. timeBase = (timeBase + 1) % 5;
  125. oscillator.hardReset();
  126. }
  127. float numberOfSeconds = 0;
  128. switch(timeBase) {
  129. case 0 :
  130. numberOfSeconds = 60; // Minutes
  131. break;
  132. case 1 :
  133. numberOfSeconds = 3600; // Hours
  134. break;
  135. case 2 :
  136. numberOfSeconds = 86400; // Days
  137. break;
  138. case 3 :
  139. numberOfSeconds = 604800; // Weeks
  140. break;
  141. case 4 :
  142. numberOfSeconds = 259200; // Months
  143. break;
  144. }
  145. duration = params[DURATION_PARAM].value;
  146. if(inputs[FM_INPUT].active) {
  147. duration +=inputs[FM_INPUT].value * params[FM_CV_ATTENUVERTER_PARAM].value;
  148. }
  149. duration = clamp(duration,1.0f,100.0f);
  150. oscillator.setFrequency(1.0 / (duration * numberOfSeconds));
  151. oscillator.step(1.0 / engineGetSampleRate());
  152. if(inputs[RESET_INPUT].active) {
  153. oscillator.setReset(inputs[RESET_INPUT].value);
  154. }
  155. outputs[SIN_OUTPUT].value = 5.0 * oscillator.sin();
  156. outputs[TRI_OUTPUT].value = 5.0 * oscillator.tri();
  157. outputs[SAW_OUTPUT].value = 5.0 * oscillator.saw();
  158. outputs[SQR_OUTPUT].value = 5.0 * oscillator.sqr();
  159. for(int lightIndex = 0;lightIndex<5;lightIndex++)
  160. {
  161. lights[lightIndex].value = lightIndex != timeBase ? 0.0 : 1.0;
  162. }
  163. }
  164. struct SSLFOProgressDisplay : TransparentWidget {
  165. SeriouslySlowLFO *module;
  166. int frame = 0;
  167. std::shared_ptr<Font> font;
  168. SSLFOProgressDisplay() {
  169. font = Font::load(assetPlugin(plugin, "res/fonts/01 Digit.ttf"));
  170. }
  171. void drawProgress(NVGcontext *vg, float phase)
  172. {
  173. //float startArc = (-M_PI) / 2.0; we know this rotates 90 degrees to top
  174. //float endArc = (phase * M_PI) - startArc;
  175. const float rotate90 = (M_PI) / 2.0;
  176. float startArc = 0 - rotate90;
  177. float endArc = (phase * M_PI * 2) - rotate90;
  178. // Draw indicator
  179. nvgFillColor(vg, nvgRGBA(0xff, 0xff, 0x20, 0xff));
  180. {
  181. nvgBeginPath(vg);
  182. nvgArc(vg,109.8,194.5,35,startArc,endArc,NVG_CW);
  183. nvgLineTo(vg,109.8,194.5);
  184. nvgClosePath(vg);
  185. }
  186. nvgFill(vg);
  187. }
  188. void drawDuration(NVGcontext *vg, Vec pos, float duration) {
  189. nvgFontSize(vg, 28);
  190. nvgFontFaceId(vg, font->handle);
  191. nvgTextLetterSpacing(vg, -2);
  192. nvgFillColor(vg, nvgRGBA(0x00, 0xff, 0x00, 0xff));
  193. char text[128];
  194. snprintf(text, sizeof(text), " % #6.1f", duration);
  195. nvgText(vg, pos.x + 22, pos.y, text, NULL);
  196. }
  197. void draw(NVGcontext *vg) override {
  198. drawProgress(vg,module->oscillator.progress());
  199. drawDuration(vg, Vec(0, box.size.y - 140), module->duration);
  200. }
  201. };
  202. struct SeriouslySlowLFOWidget : ModuleWidget {
  203. SeriouslySlowLFOWidget(SeriouslySlowLFO *module);
  204. };
  205. SeriouslySlowLFOWidget::SeriouslySlowLFOWidget(SeriouslySlowLFO *module) : ModuleWidget(module) {
  206. box.size = Vec(15*10, RACK_GRID_HEIGHT);
  207. {
  208. SVGPanel *panel = new SVGPanel();
  209. panel->box.size = box.size;
  210. panel->setBackground(SVG::load(assetPlugin(plugin, "res/SeriouslySlowLFO.svg")));
  211. addChild(panel);
  212. }
  213. addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH - 12, 0)));
  214. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH + 12, 0)));
  215. addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH-12, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  216. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH + 12, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  217. {
  218. SSLFOProgressDisplay *display = new SSLFOProgressDisplay();
  219. display->module = module;
  220. display->box.pos = Vec(0, 0);
  221. display->box.size = Vec(box.size.x, 220);
  222. addChild(display);
  223. }
  224. addParam(ParamWidget::create<CKD6>(Vec(10, 240), module, SeriouslySlowLFO::TIME_BASE_PARAM, 0.0, 1.0, 0.0));
  225. addParam(ParamWidget::create<RoundBlackKnob>(Vec(75, 90), module, SeriouslySlowLFO::DURATION_PARAM, 1.0, 100.0, 1.0));
  226. addParam(ParamWidget::create<RoundSmallBlackKnob>(Vec(41, 121), module, SeriouslySlowLFO::FM_CV_ATTENUVERTER_PARAM, -1.0, 1.0, 0.0));
  227. addInput(Port::create<PJ301MPort>(Vec(40, 93), Port::INPUT, module, SeriouslySlowLFO::FM_INPUT));
  228. addInput(Port::create<PJ301MPort>(Vec(63, 272), Port::INPUT, module, SeriouslySlowLFO::RESET_INPUT));
  229. addOutput(Port::create<PJ301MPort>(Vec(11, 320), Port::OUTPUT, module, SeriouslySlowLFO::SIN_OUTPUT));
  230. addOutput(Port::create<PJ301MPort>(Vec(45, 320), Port::OUTPUT, module, SeriouslySlowLFO::TRI_OUTPUT));
  231. addOutput(Port::create<PJ301MPort>(Vec(80, 320), Port::OUTPUT, module, SeriouslySlowLFO::SAW_OUTPUT));
  232. addOutput(Port::create<PJ301MPort>(Vec(114, 320), Port::OUTPUT, module, SeriouslySlowLFO::SQR_OUTPUT));
  233. addChild(ModuleLightWidget::create<MediumLight<BlueLight>>(Vec(10, 168), module, SeriouslySlowLFO::MINUTES_LIGHT));
  234. addChild(ModuleLightWidget::create<MediumLight<BlueLight>>(Vec(10, 183), module, SeriouslySlowLFO::HOURS_LIGHT));
  235. addChild(ModuleLightWidget::create<MediumLight<BlueLight>>(Vec(10, 198), module, SeriouslySlowLFO::DAYS_LIGHT));
  236. addChild(ModuleLightWidget::create<MediumLight<BlueLight>>(Vec(10, 213), module, SeriouslySlowLFO::WEEKS_LIGHT));
  237. addChild(ModuleLightWidget::create<MediumLight<BlueLight>>(Vec(10, 228), module, SeriouslySlowLFO::MONTHS_LIGHT));
  238. }
  239. } // namespace rack_plugin_FrozenWasteland
  240. using namespace rack_plugin_FrozenWasteland;
  241. RACK_PLUGIN_MODEL_INIT(FrozenWasteland, SeriouslySlowLFO) {
  242. Model *modelSeriouslySlowLFO = Model::create<SeriouslySlowLFO, SeriouslySlowLFOWidget>("Frozen Wasteland", "SeriouslySlowLFO", "Seriously Slow LFO", LFO_TAG);
  243. return modelSeriouslySlowLFO;
  244. }