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.

386 lines
12KB

  1. #include <string.h>
  2. #include "FrozenWasteland.hpp"
  3. #include "dsp/digital.hpp"
  4. #define BUFFER_SIZE 512
  5. namespace rack_plugin_FrozenWasteland {
  6. struct LissajousLFO : Module {
  7. enum ParamIds {
  8. AMPLITUDE1_PARAM,
  9. AMPLITUDE2_PARAM,
  10. FREQX1_PARAM,
  11. FREQY1_PARAM,
  12. FREQX2_PARAM,
  13. FREQY2_PARAM,
  14. NUM_PARAMS
  15. };
  16. enum InputIds {
  17. AMPLITUDE1_INPUT,
  18. AMPLITUDE2_INPUT,
  19. FREQX1_INPUT,
  20. FREQY1_INPUT,
  21. FREQX2_INPUT,
  22. FREQY2_INPUT,
  23. NUM_INPUTS
  24. };
  25. enum OutputIds {
  26. OUTPUT_1,
  27. OUTPUT_2,
  28. OUTPUT_3,
  29. OUTPUT_4,
  30. OUTPUT_5,
  31. NUM_OUTPUTS
  32. };
  33. enum LightIds {
  34. BLINK_LIGHT_1,
  35. BLINK_LIGHT_2,
  36. BLINK_LIGHT_3,
  37. BLINK_LIGHT_4,
  38. NUM_LIGHTS
  39. };
  40. struct LowFrequencyOscillator {
  41. float phase = 0.0;
  42. float freq = 1.0;
  43. bool offset = false;
  44. bool invert = false;
  45. //SchmittTrigger resetTrigger;
  46. //LowFrequencyOscillator() {
  47. //}
  48. void setPitch(float pitch) {
  49. pitch = fminf(pitch, 8.0);
  50. freq = powf(2.0, pitch);
  51. }
  52. //void setReset(float reset) {
  53. // if (resetTrigger.process(reset)) {
  54. // phase = 0.0;
  55. // }
  56. //}
  57. void step(float dt) {
  58. float deltaPhase = fminf(freq * dt, 0.5);
  59. phase += deltaPhase;
  60. if (phase >= 1.0)
  61. phase -= 1.0;
  62. }
  63. float sin() {
  64. if (offset)
  65. return 1.0 - cosf(2*M_PI * phase) * (invert ? -1.0 : 1.0);
  66. else
  67. return sinf(2*M_PI * phase) * (invert ? -1.0 : 1.0);
  68. }
  69. float light() {
  70. return sinf(2*M_PI * phase);
  71. }
  72. };
  73. float phase = 0.0;
  74. LowFrequencyOscillator oscillatorX1,oscillatorY1,oscillatorX2,oscillatorY2;
  75. float bufferX1[BUFFER_SIZE] = {};
  76. float bufferY1[BUFFER_SIZE] = {};
  77. float bufferX2[BUFFER_SIZE] = {};
  78. float bufferY2[BUFFER_SIZE] = {};
  79. int bufferIndex = 0;
  80. float frameIndex = 0;
  81. float deltaTime = powf(2.0, -8);
  82. //SchmittTrigger resetTrigger;
  83. float x1 = 0.0;
  84. float y1 = 0.0;
  85. float x2 = 0.0;
  86. float y2 = 0.0;
  87. LissajousLFO() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {}
  88. void step() override;
  89. // For more advanced Module features, read Rack's engine.hpp header file
  90. // - toJson, fromJson: serialization of internal data
  91. // - onSampleRateChange: event triggered by a change of sample rate
  92. // - onReset, onRandomize, onCreate, onDelete: implements special behavior when user clicks these from the context menu
  93. };
  94. void LissajousLFO::step() {
  95. float amplitude1 = clamp(params[AMPLITUDE1_PARAM].value + (inputs[AMPLITUDE1_INPUT].value / 2.0f),0.0f,5.0f);
  96. float amplitude2 = clamp(params[AMPLITUDE2_PARAM].value + (inputs[AMPLITUDE2_INPUT].value / 2.0f),0.0f,5.0f);
  97. // Implement 4 simple sine oscillators
  98. oscillatorX1.setPitch(params[FREQX1_PARAM].value + inputs[FREQX1_INPUT].value);
  99. oscillatorY1.setPitch(params[FREQY1_PARAM].value + inputs[FREQY1_INPUT].value);
  100. oscillatorX2.setPitch(params[FREQX2_PARAM].value + inputs[FREQX2_INPUT].value);
  101. oscillatorY2.setPitch(params[FREQY2_PARAM].value + inputs[FREQY2_INPUT].value);
  102. oscillatorX1.step(1.0 / engineGetSampleRate());
  103. oscillatorY1.step(1.0 / engineGetSampleRate());
  104. oscillatorX2.step(1.0 / engineGetSampleRate());
  105. oscillatorY2.step(1.0 / engineGetSampleRate());
  106. //Amplitude isn't doing anything at the moment
  107. float x1 = amplitude1 * oscillatorX1.sin();
  108. float y1 = amplitude1 * oscillatorY1.sin();
  109. float x2 = amplitude2 * oscillatorX2.sin();
  110. float y2 = amplitude2 * oscillatorY2.sin();
  111. outputs[OUTPUT_1].value = (x1 + x2) / 2;
  112. outputs[OUTPUT_2].value = (y1 + y2) / 2;
  113. outputs[OUTPUT_3].value = (x1 + x2 + y1 + y2) / 4;
  114. float out4 = (x1/x2);
  115. outputs[OUTPUT_4].value = std::isfinite(out4) ? clamp(out4,-5.0f,5.0f) : 0.f;
  116. float out5 = (y1/y2);
  117. outputs[OUTPUT_5].value = std::isfinite(out5) ? clamp(out5,-5.0f,5.0f) : 0.f;
  118. //Update scope.
  119. int frameCount = (int)ceilf(deltaTime * engineGetSampleRate());
  120. // Add frame to buffers
  121. if (bufferIndex < BUFFER_SIZE) {
  122. if (++frameIndex > frameCount) {
  123. frameIndex = 0;
  124. bufferX1[bufferIndex] = x1;
  125. bufferY1[bufferIndex] = y1;
  126. bufferX2[bufferIndex] = x2;
  127. bufferY2[bufferIndex] = y2;
  128. bufferIndex++;
  129. }
  130. }
  131. // Are we waiting on the next trigger?
  132. if (bufferIndex >= BUFFER_SIZE) {
  133. bufferIndex = 0;
  134. frameIndex = 0;
  135. }
  136. }
  137. struct ScopeDisplay : TransparentWidget {
  138. LissajousLFO *module;
  139. int frame = 0;
  140. std::shared_ptr<Font> font;
  141. struct Stats {
  142. float vrms, vpp, vmin, vmax;
  143. void calculate(float *values) {
  144. vrms = 0.0;
  145. vmax = -INFINITY;
  146. vmin = INFINITY;
  147. for (int i = 0; i < BUFFER_SIZE; i++) {
  148. float v = values[i];
  149. vrms += v*v;
  150. vmax = fmaxf(vmax, v);
  151. vmin = fminf(vmin, v);
  152. }
  153. vrms = sqrtf(vrms / BUFFER_SIZE);
  154. vpp = vmax - vmin;
  155. }
  156. };
  157. Stats statsX, statsY;
  158. ScopeDisplay() {
  159. font = Font::load(assetPlugin(plugin, "res/fonts/Sudo.ttf"));
  160. }
  161. void drawWaveform(NVGcontext *vg, float *valuesX, float *valuesY) {
  162. if (!valuesX)
  163. return;
  164. nvgSave(vg);
  165. Rect b = Rect(Vec(0, 15), box.size.minus(Vec(0, 15*2)));
  166. nvgScissor(vg, b.pos.x, b.pos.y, b.size.x, b.size.y);
  167. nvgBeginPath(vg);
  168. // Draw maximum display left to right
  169. for (int i = 0; i < BUFFER_SIZE; i++) {
  170. float x, y;
  171. if (valuesY) {
  172. x = valuesX[i] / 2.0 + 0.5;
  173. y = valuesY[i] / 2.0 + 0.5;
  174. }
  175. else {
  176. x = (float)i / (BUFFER_SIZE - 1);
  177. y = valuesX[i] / 2.0 + 0.5;
  178. }
  179. Vec p;
  180. p.x = b.pos.x + b.size.x * x;
  181. p.y = b.pos.y + b.size.y * (1.0 - y);
  182. if (i == 0)
  183. nvgMoveTo(vg, p.x, p.y);
  184. else
  185. nvgLineTo(vg, p.x, p.y);
  186. }
  187. nvgLineCap(vg, NVG_ROUND);
  188. nvgMiterLimit(vg, 2.0);
  189. nvgStrokeWidth(vg, 1.5);
  190. nvgGlobalCompositeOperation(vg, NVG_LIGHTER);
  191. nvgStroke(vg);
  192. nvgResetScissor(vg);
  193. nvgRestore(vg);
  194. }
  195. void drawTrig(NVGcontext *vg, float value) {
  196. Rect b = Rect(Vec(0, 15), box.size.minus(Vec(0, 15*2)));
  197. nvgScissor(vg, b.pos.x, b.pos.y, b.size.x, b.size.y);
  198. value = value / 2.0 + 0.5;
  199. Vec p = Vec(box.size.x, b.pos.y + b.size.y * (1.0 - value));
  200. // Draw line
  201. nvgStrokeColor(vg, nvgRGBA(0xff, 0xff, 0xff, 0x10));
  202. {
  203. nvgBeginPath(vg);
  204. nvgMoveTo(vg, p.x - 13, p.y);
  205. nvgLineTo(vg, 0, p.y);
  206. nvgClosePath(vg);
  207. }
  208. nvgStroke(vg);
  209. // Draw indicator
  210. nvgFillColor(vg, nvgRGBA(0xff, 0xff, 0xff, 0x60));
  211. {
  212. nvgBeginPath(vg);
  213. nvgMoveTo(vg, p.x - 2, p.y - 4);
  214. nvgLineTo(vg, p.x - 9, p.y - 4);
  215. nvgLineTo(vg, p.x - 13, p.y);
  216. nvgLineTo(vg, p.x - 9, p.y + 4);
  217. nvgLineTo(vg, p.x - 2, p.y + 4);
  218. nvgClosePath(vg);
  219. }
  220. nvgFill(vg);
  221. nvgFontSize(vg, 9);
  222. nvgFontFaceId(vg, font->handle);
  223. nvgFillColor(vg, nvgRGBA(0x1e, 0x28, 0x2b, 0xff));
  224. nvgText(vg, p.x - 8, p.y + 3, "T", NULL);
  225. nvgResetScissor(vg);
  226. }
  227. void drawStats(NVGcontext *vg, Vec pos, const char *title, Stats *stats) {
  228. nvgFontSize(vg, 13);
  229. nvgFontFaceId(vg, font->handle);
  230. nvgTextLetterSpacing(vg, -2);
  231. nvgFillColor(vg, nvgRGBA(0xff, 0xff, 0xff, 0x40));
  232. nvgText(vg, pos.x + 6, pos.y + 11, title, NULL);
  233. nvgFillColor(vg, nvgRGBA(0xff, 0xff, 0xff, 0x80));
  234. char text[128];
  235. snprintf(text, sizeof(text), "pp % 06.2f max % 06.2f min % 06.2f", stats->vpp, stats->vmax, stats->vmin);
  236. nvgText(vg, pos.x + 22, pos.y + 11, text, NULL);
  237. }
  238. void draw(NVGcontext *vg) override {
  239. float gainX = powf(2.0, 1);
  240. float gainY = powf(2.0, 1);
  241. //float offsetX = module->x1;
  242. //float offsetY = module->y1;
  243. float valuesX[BUFFER_SIZE];
  244. float valuesY[BUFFER_SIZE];
  245. for (int i = 0; i < BUFFER_SIZE; i++) {
  246. int j = i;
  247. // Lock display to buffer if buffer update deltaTime <= 2^-11
  248. j = (i + module->bufferIndex) % BUFFER_SIZE;
  249. valuesX[i] = (module->bufferX1[j]) * gainX / 10.0;
  250. valuesY[i] = (module->bufferY1[j]) * gainY / 10.0;
  251. }
  252. // Draw waveforms for LFO 1
  253. // X x Y
  254. nvgStrokeColor(vg, nvgRGBA(0x9f, 0xe4, 0x36, 0xc0));
  255. drawWaveform(vg, valuesX, valuesY);
  256. for (int i = 0; i < BUFFER_SIZE; i++) {
  257. int j = i;
  258. // Lock display to buffer if buffer update deltaTime <= 2^-11
  259. j = (i + module->bufferIndex) % BUFFER_SIZE;
  260. valuesX[i] = (module->bufferX2[j]) * gainX / 10.0;
  261. valuesY[i] = (module->bufferY2[j]) * gainY / 10.0;
  262. }
  263. // Draw waveforms for LFO 2
  264. // X x Y
  265. nvgStrokeColor(vg, nvgRGBA(0x3f, 0xe4, 0x96, 0xc0));
  266. drawWaveform(vg, valuesX, valuesY);
  267. // Calculate and draw stats
  268. //if (++frame >= 4) {
  269. // frame = 0;
  270. // statsX.calculate(module->bufferX);
  271. // statsY.calculate(module->bufferY);
  272. //}
  273. //drawStats(vg, Vec(0, 0), "X", &statsX);
  274. //drawStats(vg, Vec(0, box.size.y - 15), "Y", &statsY);
  275. }
  276. };
  277. struct LissajousLFOWidget : ModuleWidget {
  278. LissajousLFOWidget(LissajousLFO *module);
  279. };
  280. LissajousLFOWidget::LissajousLFOWidget(LissajousLFO *module) : ModuleWidget(module) {
  281. box.size = Vec(15*13, RACK_GRID_HEIGHT);
  282. {
  283. SVGPanel *panel = new SVGPanel();
  284. panel->box.size = box.size;
  285. panel->setBackground(SVG::load(assetPlugin(plugin, "res/LissajousLFO.svg")));
  286. addChild(panel);
  287. }
  288. addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH - 12, 0)));
  289. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH + 12, 0)));
  290. addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH-12, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  291. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH + 12, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  292. {
  293. ScopeDisplay *display = new ScopeDisplay();
  294. display->module = module;
  295. display->box.pos = Vec(0, 35);
  296. display->box.size = Vec(box.size.x, 140);
  297. addChild(display);
  298. }
  299. addParam(ParamWidget::create<RoundBlackKnob>(Vec(37, 186), module, LissajousLFO::AMPLITUDE1_PARAM, 0.0, 5.0, 2.5));
  300. addParam(ParamWidget::create<RoundBlackKnob>(Vec(87, 186), module, LissajousLFO::FREQX1_PARAM, -8.0, 3.0, 0.0));
  301. addParam(ParamWidget::create<RoundBlackKnob>(Vec(137, 186), module, LissajousLFO::FREQY1_PARAM, -8.0, 3.0, 2.0));
  302. addParam(ParamWidget::create<RoundBlackKnob>(Vec(37, 265), module, LissajousLFO::AMPLITUDE2_PARAM, 0.0, 5.0, 2.5));
  303. addParam(ParamWidget::create<RoundBlackKnob>(Vec(87, 265), module, LissajousLFO::FREQX2_PARAM, -8.0, 3.0, 0.0));
  304. addParam(ParamWidget::create<RoundBlackKnob>(Vec(137, 265), module, LissajousLFO::FREQY2_PARAM, -8.0, 3.0, 1.0));
  305. addInput(Port::create<PJ301MPort>(Vec(41, 219), Port::INPUT, module, LissajousLFO::AMPLITUDE1_INPUT));
  306. addInput(Port::create<PJ301MPort>(Vec(91, 219), Port::INPUT, module, LissajousLFO::FREQX1_INPUT));
  307. addInput(Port::create<PJ301MPort>(Vec(141, 219), Port::INPUT, module, LissajousLFO::FREQY1_INPUT));
  308. addInput(Port::create<PJ301MPort>(Vec(41, 298), Port::INPUT, module, LissajousLFO::AMPLITUDE2_INPUT));
  309. addInput(Port::create<PJ301MPort>(Vec(91, 298), Port::INPUT, module, LissajousLFO::FREQX2_INPUT));
  310. addInput(Port::create<PJ301MPort>(Vec(141, 298), Port::INPUT, module, LissajousLFO::FREQY2_INPUT));
  311. addOutput(Port::create<PJ301MPort>(Vec(22, 338), Port::OUTPUT, module, LissajousLFO::OUTPUT_1));
  312. addOutput(Port::create<PJ301MPort>(Vec(53, 338), Port::OUTPUT, module, LissajousLFO::OUTPUT_2));
  313. addOutput(Port::create<PJ301MPort>(Vec(86, 338), Port::OUTPUT, module, LissajousLFO::OUTPUT_3));
  314. addOutput(Port::create<PJ301MPort>(Vec(126, 338), Port::OUTPUT, module, LissajousLFO::OUTPUT_4));
  315. addOutput(Port::create<PJ301MPort>(Vec(158, 338), Port::OUTPUT, module, LissajousLFO::OUTPUT_5));
  316. //addChild(ModuleLightWidget::create<MediumLight<BlueLight>>(Vec(21, 59), module, LissajousLFO::BLINK_LIGHT_1));
  317. //addChild(ModuleLightWidget::create<MediumLight<BlueLight>>(Vec(41, 59), module, LissajousLFO::BLINK_LIGHT_2));
  318. //addChild(ModuleLightWidget::create<MediumLight<BlueLight>>(Vec(61, 59), module, LissajousLFO::BLINK_LIGHT_3));
  319. //addChild(ModuleLightWidget::create<MediumLight<BlueLight>>(Vec(81, 59), module, LissajousLFO::BLINK_LIGHT_4));
  320. }
  321. } // namespace rack_plugin_FrozenWasteland
  322. using namespace rack_plugin_FrozenWasteland;
  323. RACK_PLUGIN_MODEL_INIT(FrozenWasteland, LissajousLFO) {
  324. Model *modelLissajousLFO = Model::create<LissajousLFO, LissajousLFOWidget>("Frozen Wasteland", "LissajousLFO", "Lissajous LFO", LFO_TAG);
  325. return modelLissajousLFO;
  326. }