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.

298 lines
8.8KB

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