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.

303 lines
8.4KB

  1. //#include <string.h>
  2. #include "FrozenWasteland.hpp"
  3. #include "dsp/digital.hpp"
  4. #define DIVISIONS 27
  5. namespace rack_plugin_FrozenWasteland {
  6. struct BPMLFO : Module {
  7. enum ParamIds {
  8. DIVISION_PARAM,
  9. OFFSET_PARAM,
  10. HOLD_CLOCK_BEHAVIOR_PARAM,
  11. HOLD_MODE_PARAM,
  12. DIVISION_CV_ATTENUVERTER_PARAM,
  13. NUM_PARAMS
  14. };
  15. enum InputIds {
  16. CLOCK_INPUT,
  17. DIVISION_INPUT,
  18. RESET_INPUT,
  19. HOLD_INPUT,
  20. NUM_INPUTS
  21. };
  22. enum OutputIds {
  23. SIN_OUTPUT,
  24. TRI_OUTPUT,
  25. SAW_OUTPUT,
  26. SQR_OUTPUT,
  27. NUM_OUTPUTS
  28. };
  29. enum LightIds {
  30. CLOCK_LIGHT,
  31. HOLD_LIGHT,
  32. NUM_LIGHTS
  33. };
  34. struct LowFrequencyOscillator {
  35. float phase = 0.0;
  36. float pw = 0.5;
  37. float freq = 1.0;
  38. bool offset = false;
  39. bool invert = false;
  40. void setPitch(float pitch) {
  41. pitch = fminf(pitch, 8.0);
  42. freq = powf(2.0, pitch);
  43. }
  44. void setFrequency(float frequency) {
  45. freq = frequency;
  46. }
  47. void setPulseWidth(float pw_) {
  48. const float pwMin = 0.01;
  49. pw = clamp(pw_, pwMin, 1.0f - pwMin);
  50. }
  51. void hardReset()
  52. {
  53. phase = 0.0;
  54. }
  55. void step(float dt) {
  56. float deltaPhase = fminf(freq * dt, 0.5);
  57. phase += deltaPhase;
  58. if (phase >= 1.0)
  59. phase -= 1.0;
  60. }
  61. float sin() {
  62. if (offset)
  63. return 1.0 - cosf(2*M_PI * phase) * (invert ? -1.0 : 1.0);
  64. else
  65. return sinf(2*M_PI * phase) * (invert ? -1.0 : 1.0);
  66. }
  67. float tri(float x) {
  68. return 4.0 * fabsf(x - roundf(x));
  69. }
  70. float tri() {
  71. if (offset)
  72. return tri(invert ? phase - 0.5 : phase);
  73. else
  74. return -1.0 + tri(invert ? phase - 0.25 : phase - 0.75);
  75. }
  76. float saw(float x) {
  77. return 2.0 * (x - roundf(x));
  78. }
  79. float saw() {
  80. if (offset)
  81. return invert ? 2.0 * (1.0 - phase) : 2.0 * phase;
  82. else
  83. return saw(phase) * (invert ? -1.0 : 1.0);
  84. }
  85. float sqr() {
  86. float sqr = (phase < pw) ^ invert ? 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.5,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;
  98. float time = 0.0;
  99. float duration = 0;
  100. bool holding = false;
  101. bool secondClockReceived = false;
  102. float sinOutputValue = 0.0;
  103. float triOutputValue = 0.0;
  104. float sawOutputValue = 0.0;
  105. float sqrOutputValue = 0.0;
  106. BPMLFO() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {}
  107. void step() override;
  108. void reset() override {
  109. division = 0;
  110. }
  111. // For more advanced Module features, read Rack's engine.hpp header file
  112. // - toJson, fromJson: serialization of internal data
  113. // - onSampleRateChange: event triggered by a change of sample rate
  114. // - onReset, onRandomize, onCreate, onDelete: implements special behavior when user clicks these from the context menu
  115. };
  116. void BPMLFO::step() {
  117. time += 1.0 / engineGetSampleRate();
  118. if(inputs[CLOCK_INPUT].active) {
  119. if(clockTrigger.process(inputs[CLOCK_INPUT].value)) {
  120. if(secondClockReceived) {
  121. duration = time;
  122. }
  123. time = 0;
  124. secondClockReceived = true;
  125. //secondClockReceived = !secondClockReceived;
  126. }
  127. lights[CLOCK_LIGHT].value = time > (duration/2.0);
  128. }
  129. float divisionf = params[DIVISION_PARAM].value;
  130. if(inputs[DIVISION_INPUT].active) {
  131. divisionf +=(inputs[DIVISION_INPUT].value * params[DIVISION_CV_ATTENUVERTER_PARAM].value * (DIVISIONS / 10.0));
  132. }
  133. divisionf = clamp(divisionf,0.0f,26.0f);
  134. division = int(divisionf);
  135. oscillator.offset = (params[OFFSET_PARAM].value > 0.0);
  136. if(duration != 0) {
  137. oscillator.setFrequency(1.0 / (duration / divisions[division]));
  138. }
  139. else {
  140. oscillator.setFrequency(0);
  141. }
  142. if(inputs[RESET_INPUT].active) {
  143. if(resetTrigger.process(inputs[RESET_INPUT].value)) {
  144. oscillator.hardReset();
  145. }
  146. }
  147. if(inputs[HOLD_INPUT].active) {
  148. if(params[HOLD_MODE_PARAM].value == 1.0) { //Latched is default
  149. if(holdTrigger.process(inputs[HOLD_INPUT].value)) {
  150. holding = !holding;
  151. }
  152. } else {
  153. holding = inputs[HOLD_INPUT].value >= 1;
  154. }
  155. lights[HOLD_LIGHT].value = holding;
  156. }
  157. if(!holding || (holding && params[HOLD_CLOCK_BEHAVIOR_PARAM].value == 0.0)) {
  158. oscillator.step(1.0 / engineGetSampleRate());
  159. }
  160. if(!holding) {
  161. sinOutputValue = 5.0 * oscillator.sin();
  162. triOutputValue = 5.0 * oscillator.tri();
  163. sawOutputValue = 5.0 * oscillator.saw();
  164. sqrOutputValue = 5.0 * oscillator.sqr();
  165. }
  166. outputs[SIN_OUTPUT].value = sinOutputValue;
  167. outputs[TRI_OUTPUT].value = triOutputValue;
  168. outputs[SAW_OUTPUT].value = sawOutputValue;
  169. outputs[SQR_OUTPUT].value = sqrOutputValue;
  170. }
  171. struct BPMLFOProgressDisplay : TransparentWidget {
  172. BPMLFO *module;
  173. int frame = 0;
  174. std::shared_ptr<Font> font;
  175. BPMLFOProgressDisplay() {
  176. font = Font::load(assetPlugin(plugin, "res/fonts/01 Digit.ttf"));
  177. }
  178. void drawProgress(NVGcontext *vg, float phase)
  179. {
  180. const float rotate90 = (M_PI) / 2.0;
  181. float startArc = 0 - rotate90;
  182. float endArc = (phase * M_PI * 2) - rotate90;
  183. // Draw indicator
  184. nvgFillColor(vg, nvgRGBA(0xff, 0xff, 0x20, 0xff));
  185. {
  186. nvgBeginPath(vg);
  187. nvgArc(vg,75.8,170,35,startArc,endArc,NVG_CW);
  188. nvgLineTo(vg,75.8,170);
  189. nvgClosePath(vg);
  190. }
  191. nvgFill(vg);
  192. }
  193. void drawDivision(NVGcontext *vg, Vec pos, int division) {
  194. nvgFontSize(vg, 28);
  195. nvgFontFaceId(vg, font->handle);
  196. nvgTextLetterSpacing(vg, -2);
  197. nvgFillColor(vg, nvgRGBA(0x00, 0xff, 0x00, 0xff));
  198. char text[128];
  199. snprintf(text, sizeof(text), "%s", module->divisionNames[division]);
  200. nvgText(vg, pos.x + 52, pos.y, text, NULL);
  201. }
  202. void draw(NVGcontext *vg) override {
  203. drawProgress(vg,module->oscillator.progress());
  204. drawDivision(vg, Vec(0, box.size.y - 153), module->division);
  205. }
  206. };
  207. struct BPMLFOWidget : ModuleWidget {
  208. BPMLFOWidget(BPMLFO *module);
  209. };
  210. BPMLFOWidget::BPMLFOWidget(BPMLFO *module) : ModuleWidget(module) {
  211. box.size = Vec(15*10, RACK_GRID_HEIGHT);
  212. {
  213. SVGPanel *panel = new SVGPanel();
  214. panel->box.size = box.size;
  215. panel->setBackground(SVG::load(assetPlugin(plugin, "res/BPMLFO.svg")));
  216. addChild(panel);
  217. }
  218. addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH - 12, 0)));
  219. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH + 12, 0)));
  220. addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH - 12, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  221. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH + 12, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  222. {
  223. BPMLFOProgressDisplay *display = new BPMLFOProgressDisplay();
  224. display->module = module;
  225. display->box.pos = Vec(0, 0);
  226. display->box.size = Vec(box.size.x, 220);
  227. addChild(display);
  228. }
  229. addParam(ParamWidget::create<RoundBlackKnob>(Vec(75, 78), module, BPMLFO::DIVISION_PARAM, 0.0, 26.5, 13.0));
  230. addParam(ParamWidget::create<RoundSmallBlackKnob>(Vec(40, 109), module, BPMLFO::DIVISION_CV_ATTENUVERTER_PARAM, -1.0, 1.0, 0.0));
  231. addParam(ParamWidget::create<CKSS>(Vec(12, 224), module, BPMLFO::OFFSET_PARAM, 0.0, 1.0, 1.0));
  232. addParam(ParamWidget::create<CKSS>(Vec(70, 224), module, BPMLFO::HOLD_CLOCK_BEHAVIOR_PARAM, 0.0, 1.0, 1.0));
  233. addParam(ParamWidget::create<CKSS>(Vec(125, 224), module, BPMLFO::HOLD_MODE_PARAM, 0.0, 1.0, 1.0));
  234. addInput(Port::create<PJ301MPort>(Vec(40, 81), Port::INPUT, module, BPMLFO::DIVISION_INPUT));
  235. addInput(Port::create<PJ301MPort>(Vec(31, 275), Port::INPUT, module, BPMLFO::CLOCK_INPUT));
  236. addInput(Port::create<PJ301MPort>(Vec(62, 275), Port::INPUT, module, BPMLFO::RESET_INPUT));
  237. addInput(Port::create<PJ301MPort>(Vec(94, 275), Port::INPUT, module, BPMLFO::HOLD_INPUT));
  238. addOutput(Port::create<PJ301MPort>(Vec(11, 323), Port::OUTPUT, module, BPMLFO::SIN_OUTPUT));
  239. addOutput(Port::create<PJ301MPort>(Vec(45, 323), Port::OUTPUT, module, BPMLFO::TRI_OUTPUT));
  240. addOutput(Port::create<PJ301MPort>(Vec(80, 323), Port::OUTPUT, module, BPMLFO::SAW_OUTPUT));
  241. addOutput(Port::create<PJ301MPort>(Vec(114, 323), Port::OUTPUT, module, BPMLFO::SQR_OUTPUT));
  242. addChild(ModuleLightWidget::create<LargeLight<BlueLight>>(Vec(12, 279), module, BPMLFO::CLOCK_LIGHT));
  243. addChild(ModuleLightWidget::create<LargeLight<RedLight>>(Vec(122, 279), module, BPMLFO::HOLD_LIGHT));
  244. }
  245. } // namespace rack_plugin_FrozenWasteland
  246. using namespace rack_plugin_FrozenWasteland;
  247. RACK_PLUGIN_MODEL_INIT(FrozenWasteland, BPMLFO) {
  248. Model *modelBPMLFO = Model::create<BPMLFO, BPMLFOWidget>("Frozen Wasteland", "BPMLFO", "BPM LFO", LFO_TAG);
  249. return modelBPMLFO;
  250. }