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.

256 lines
8.0KB

  1. #include "plugin.hpp"
  2. using simd::float_4;
  3. struct LFO : Module {
  4. enum ParamIds {
  5. OFFSET_PARAM,
  6. INVERT_PARAM,
  7. FREQ_PARAM,
  8. FM_PARAM,
  9. FM2_PARAM, // removed
  10. PW_PARAM,
  11. PWM_PARAM,
  12. NUM_PARAMS
  13. };
  14. enum InputIds {
  15. FM_INPUT,
  16. FM2_INPUT, // removed
  17. RESET_INPUT,
  18. PW_INPUT,
  19. // added in 2.0
  20. CLOCK_INPUT,
  21. NUM_INPUTS
  22. };
  23. enum OutputIds {
  24. SIN_OUTPUT,
  25. TRI_OUTPUT,
  26. SAW_OUTPUT,
  27. SQR_OUTPUT,
  28. NUM_OUTPUTS
  29. };
  30. enum LightIds {
  31. ENUMS(PHASE_LIGHT, 3),
  32. INVERT_LIGHT,
  33. OFFSET_LIGHT,
  34. NUM_LIGHTS
  35. };
  36. bool offset = false;
  37. bool invert = false;
  38. float_4 phases[4];
  39. dsp::TSchmittTrigger<float_4> clockTriggers[4];
  40. dsp::TSchmittTrigger<float_4> resetTriggers[4];
  41. dsp::SchmittTrigger clockTrigger;
  42. float clockFreq = 1.f;
  43. dsp::Timer clockTimer;
  44. dsp::ClockDivider lightDivider;
  45. LFO() {
  46. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  47. configSwitch(OFFSET_PARAM, 0.f, 1.f, 1.f, "Offset", {"Bipolar", "Unipolar"});
  48. configSwitch(INVERT_PARAM, 0.f, 1.f, 0.f, "Invert");
  49. struct FrequencyQuantity : ParamQuantity {
  50. float getDisplayValue() override {
  51. LFO* module = reinterpret_cast<LFO*>(this->module);
  52. if (module->clockFreq == 2.f) {
  53. unit = " Hz";
  54. displayMultiplier = 1.f;
  55. }
  56. else {
  57. unit = "x";
  58. displayMultiplier = 1 / 2.f;
  59. }
  60. return ParamQuantity::getDisplayValue();
  61. }
  62. };
  63. configParam<FrequencyQuantity>(FREQ_PARAM, -8.f, 10.f, 1.f, "Frequency", " Hz", 2, 1);
  64. configParam(FM_PARAM, -1.f, 1.f, 0.f, "Frequency modulation", "%", 0.f, 100.f);
  65. getParamQuantity(FM_PARAM)->randomizeEnabled = false;
  66. configParam(PW_PARAM, 0.01f, 0.99f, 0.5f, "Pulse width", "%", 0.f, 100.f);
  67. configParam(PWM_PARAM, -1.f, 1.f, 0.f, "Pulse width modulation", "%", 0.f, 100.f);
  68. getParamQuantity(PWM_PARAM)->randomizeEnabled = false;
  69. configInput(FM_INPUT, "Frequency modulation");
  70. configInput(CLOCK_INPUT, "Clock");
  71. configInput(RESET_INPUT, "Reset");
  72. configInput(PW_INPUT, "Pulse width modulation");
  73. configOutput(SIN_OUTPUT, "Sine");
  74. configOutput(TRI_OUTPUT, "Triangle");
  75. configOutput(SAW_OUTPUT, "Sawtooth");
  76. configOutput(SQR_OUTPUT, "Square");
  77. configLight(PHASE_LIGHT, "Phase");
  78. lightDivider.setDivision(16);
  79. onReset();
  80. }
  81. void onReset() override {
  82. for (int c = 0; c < 16; c += 4) {
  83. phases[c / 4] = 0.f;
  84. }
  85. clockFreq = 1.f;
  86. clockTimer.reset();
  87. }
  88. void process(const ProcessArgs& args) override {
  89. float freqParam = params[FREQ_PARAM].getValue();
  90. float fmParam = params[FM_PARAM].getValue();
  91. float pwParam = params[PW_PARAM].getValue();
  92. float pwmParam = params[PWM_PARAM].getValue();
  93. bool offset = params[OFFSET_PARAM].getValue() > 0.f;
  94. bool invert = params[INVERT_PARAM].getValue() > 0.f;
  95. // Clock
  96. if (inputs[CLOCK_INPUT].isConnected()) {
  97. clockTimer.process(args.sampleTime);
  98. if (clockTrigger.process(inputs[CLOCK_INPUT].getVoltage(), 0.1f, 2.f)) {
  99. float clockFreq = 1.f / clockTimer.getTime();
  100. clockTimer.reset();
  101. if (0.001f <= clockFreq && clockFreq <= 1000.f) {
  102. this->clockFreq = clockFreq;
  103. }
  104. }
  105. }
  106. else {
  107. // Default frequency when clock is unpatched
  108. clockFreq = 2.f;
  109. }
  110. int channels = std::max(1, inputs[FM_INPUT].getChannels());
  111. for (int c = 0; c < channels; c += 4) {
  112. // Pitch and frequency
  113. float_4 pitch = freqParam;
  114. pitch += inputs[FM_INPUT].getVoltageSimd<float_4>(c) * fmParam;
  115. float_4 freq = clockFreq / 2.f * dsp::exp2_taylor5(pitch);
  116. // Pulse width
  117. float_4 pw = pwParam;
  118. pw += inputs[PW_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f * pwmParam;
  119. pw = clamp(pw, 0.01f, 0.99f);
  120. // Advance phase
  121. float_4 deltaPhase = simd::fmin(freq * args.sampleTime, 0.5f);
  122. phases[c / 4] += deltaPhase;
  123. phases[c / 4] -= simd::trunc(phases[c / 4]);
  124. // Reset
  125. float_4 reset = inputs[RESET_INPUT].getPolyVoltageSimd<float_4>(c);
  126. float_4 resetTriggered = resetTriggers[c / 4].process(reset, 0.1f, 2.f);
  127. phases[c / 4] = simd::ifelse(resetTriggered, 0.f, phases[c / 4]);
  128. // Sine
  129. if (outputs[SIN_OUTPUT].isConnected()) {
  130. float_4 p = phases[c / 4];
  131. if (offset)
  132. p -= 0.25f;
  133. float_4 v = simd::sin(2 * M_PI * p);
  134. if (invert)
  135. v *= -1.f;
  136. if (offset)
  137. v += 1.f;
  138. outputs[SIN_OUTPUT].setVoltageSimd(5.f * v, c);
  139. }
  140. // Triangle
  141. if (outputs[TRI_OUTPUT].isConnected()) {
  142. float_4 p = phases[c / 4];
  143. if (!offset)
  144. p += 0.25f;
  145. float_4 v = 4.f * simd::fabs(p - simd::round(p)) - 1.f;
  146. if (invert)
  147. v *= -1.f;
  148. if (offset)
  149. v += 1.f;
  150. outputs[TRI_OUTPUT].setVoltageSimd(5.f * v, c);
  151. }
  152. // Sawtooth
  153. if (outputs[SAW_OUTPUT].isConnected()) {
  154. float_4 p = phases[c / 4];
  155. if (offset)
  156. p -= 0.5f;
  157. float_4 v = 2.f * (p - simd::round(p));
  158. if (invert)
  159. v *= -1.f;
  160. if (offset)
  161. v += 1.f;
  162. outputs[SAW_OUTPUT].setVoltageSimd(5.f * v, c);
  163. }
  164. // Square
  165. if (outputs[SQR_OUTPUT].isConnected()) {
  166. float_4 v = simd::ifelse(phases[c / 4] < pw, 1.f, -1.f);
  167. if (invert)
  168. v *= -1.f;
  169. if (offset)
  170. v += 1.f;
  171. outputs[SQR_OUTPUT].setVoltageSimd(5.f * v, c);
  172. }
  173. }
  174. outputs[SIN_OUTPUT].setChannels(channels);
  175. outputs[TRI_OUTPUT].setChannels(channels);
  176. outputs[SAW_OUTPUT].setChannels(channels);
  177. outputs[SQR_OUTPUT].setChannels(channels);
  178. // Light
  179. if (lightDivider.process()) {
  180. if (channels == 1) {
  181. float b = 1.f - phases[0][0];
  182. lights[PHASE_LIGHT + 0].setSmoothBrightness(b, args.sampleTime * lightDivider.getDivision());
  183. lights[PHASE_LIGHT + 1].setSmoothBrightness(b, args.sampleTime * lightDivider.getDivision());
  184. lights[PHASE_LIGHT + 2].setBrightness(0.f);
  185. }
  186. else {
  187. lights[PHASE_LIGHT + 0].setBrightness(0.f);
  188. lights[PHASE_LIGHT + 1].setBrightness(0.f);
  189. lights[PHASE_LIGHT + 2].setBrightness(1.f);
  190. }
  191. lights[OFFSET_LIGHT].setBrightness(offset);
  192. lights[INVERT_LIGHT].setBrightness(invert);
  193. }
  194. }
  195. };
  196. struct LFOWidget : ModuleWidget {
  197. LFOWidget(LFO* module) {
  198. setModule(module);
  199. setPanel(createPanel(asset::plugin(pluginInstance, "res/LFO.svg"), asset::plugin(pluginInstance, "res/LFO-dark.svg")));
  200. addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, 0)));
  201. addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  202. addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  203. addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  204. addParam(createParamCentered<RoundHugeBlackKnob>(mm2px(Vec(22.902, 29.803)), module, LFO::FREQ_PARAM));
  205. addParam(createParamCentered<RoundLargeBlackKnob>(mm2px(Vec(22.861, 56.388)), module, LFO::PW_PARAM));
  206. addParam(createParamCentered<Trimpot>(mm2px(Vec(6.604, 80.603)), module, LFO::FM_PARAM));
  207. addParam(createLightParamCentered<VCVLightLatch<MediumSimpleLight<WhiteLight>>>(mm2px(Vec(17.441, 80.603)), module, LFO::INVERT_PARAM, LFO::INVERT_LIGHT));
  208. addParam(createLightParamCentered<VCVLightLatch<MediumSimpleLight<WhiteLight>>>(mm2px(Vec(28.279, 80.603)), module, LFO::OFFSET_PARAM, LFO::OFFSET_LIGHT));
  209. addParam(createParamCentered<Trimpot>(mm2px(Vec(39.116, 80.603)), module, LFO::PWM_PARAM));
  210. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(6.604, 96.859)), module, LFO::FM_INPUT));
  211. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(17.441, 96.859)), module, LFO::CLOCK_INPUT));
  212. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(28.279, 96.819)), module, LFO::RESET_INPUT));
  213. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(39.116, 96.819)), module, LFO::PW_INPUT));
  214. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(6.604, 113.115)), module, LFO::SIN_OUTPUT));
  215. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(17.441, 113.115)), module, LFO::TRI_OUTPUT));
  216. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(28.279, 113.115)), module, LFO::SAW_OUTPUT));
  217. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(39.116, 113.115)), module, LFO::SQR_OUTPUT));
  218. addChild(createLightCentered<SmallLight<RedGreenBlueLight>>(mm2px(Vec(31.085, 16.428)), module, LFO::PHASE_LIGHT));
  219. }
  220. };
  221. Model* modelLFO = createModel<LFO, LFOWidget>("LFO");