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.

194 lines
7.5KB

  1. #include "QuantalAudio.hpp"
  2. #include "dsp/resampler.hpp"
  3. #include "dsp/digital.hpp"
  4. namespace rack_plugin_QuantalAudio {
  5. template <int OVERSAMPLE, int QUALITY>
  6. struct VoltageControlledOscillator {
  7. //float lastSyncValue = 0.0f;
  8. float phase = 0.0f;
  9. float freq;
  10. float pw = 0.5f;
  11. float pitch;
  12. Decimator<OVERSAMPLE, QUALITY> sinDecimator;
  13. Decimator<OVERSAMPLE, QUALITY> sawDecimator;
  14. Decimator<OVERSAMPLE, QUALITY> sqrDecimator;
  15. float sinBuffer[OVERSAMPLE] = {};
  16. float sawBuffer[OVERSAMPLE] = {};
  17. float sqrBuffer[OVERSAMPLE] = {};
  18. void setPitch(float octave, float pitchKnob, float pitchCv) {
  19. // Compute frequency
  20. float pitch = 1.0 + roundf(octave);
  21. pitch += pitchKnob;
  22. pitch += pitchCv / 12.0;
  23. // Note C4
  24. freq = 261.626f * powf(2.0f, pitch);
  25. freq = clamp(freq, 0.0f, 20000.0f);
  26. }
  27. void setPulseWidth(float pulseWidth) {
  28. const float pwMin = 0.01f;
  29. pw = clamp(pulseWidth, pwMin, 1.0f - pwMin);
  30. }
  31. void process(float deltaTime) {
  32. // Advance phase
  33. float deltaPhase = clamp(freq * deltaTime, 1e-6, 0.5f);
  34. for (int i = 0; i < OVERSAMPLE; i++) {
  35. sinBuffer[i] = sinf(2.f*M_PI * phase);
  36. if (phase < 0.5f)
  37. sawBuffer[i] = 2.f * phase;
  38. else
  39. sawBuffer[i] = -2.f + 2.f * phase;
  40. sqrBuffer[i] = (phase < pw) ? 1.f : -1.f;
  41. // Advance phase
  42. phase += deltaPhase / OVERSAMPLE;
  43. phase = eucmod(phase, 1.0f);
  44. }
  45. }
  46. float sin() {
  47. return sinDecimator.process(sinBuffer);
  48. }
  49. float saw() {
  50. return sawDecimator.process(sawBuffer);
  51. }
  52. float sqr() {
  53. return sqrDecimator.process(sqrBuffer);
  54. }
  55. float light() {
  56. return sinf(2*M_PI * phase);
  57. }
  58. };
  59. struct Horsehair : Module {
  60. enum ParamIds {
  61. PITCH_PARAM,
  62. ENUMS(OCTAVE_PARAM, 2),
  63. ENUMS(SHAPE_PARAM, 2),
  64. ENUMS(PW_PARAM, 2),
  65. MIX_PARAM,
  66. NUM_PARAMS
  67. };
  68. enum InputIds {
  69. PITCH_INPUT,
  70. ENUMS(SHAPE_CV_INPUT, 2),
  71. ENUMS(PW_CV_INPUT, 2),
  72. MIX_CV_INPUT,
  73. NUM_INPUTS
  74. };
  75. enum OutputIds {
  76. SIN_OUTPUT,
  77. MIX_OUTPUT,
  78. NUM_OUTPUTS
  79. };
  80. enum LightsIds {
  81. OSC_LIGHT,
  82. NUM_LIGHTS
  83. };
  84. VoltageControlledOscillator<16, 16> oscillator;
  85. VoltageControlledOscillator<16, 16> oscillator2;
  86. Horsehair() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {}
  87. void step() override {
  88. float pitchCv = 12.0f * inputs[PITCH_INPUT].value;
  89. oscillator.setPitch(params[OCTAVE_PARAM + 0].value, params[PITCH_PARAM].value, pitchCv);
  90. oscillator.setPulseWidth(params[PW_PARAM + 0].value + inputs[PW_CV_INPUT + 0].value / 10.0);
  91. oscillator.process(engineGetSampleTime());
  92. oscillator2.setPitch(params[OCTAVE_PARAM + 1].value, params[PITCH_PARAM].value, pitchCv);
  93. oscillator2.setPulseWidth(params[PW_PARAM + 1].value + inputs[PW_CV_INPUT + 1].value / 10.0);
  94. oscillator2.process(engineGetSampleTime());
  95. float shape = clamp(params[SHAPE_PARAM + 0].value, 0.0f, 1.0f);
  96. float shape2 = clamp(params[SHAPE_PARAM + 1].value, 0.0f, 1.0f);
  97. if (inputs[SHAPE_CV_INPUT + 0].active) {
  98. shape += inputs[SHAPE_CV_INPUT + 0].value / 10.0;
  99. shape = clamp(shape, 0.0f, 1.0f);
  100. }
  101. if (inputs[SHAPE_CV_INPUT + 1].active) {
  102. shape2 += inputs[SHAPE_CV_INPUT + 1].value / 10.0;
  103. shape2 = clamp(shape2, 0.0f, 1.0f);
  104. }
  105. float out = 0.0f;
  106. float out2 = 0.0f;
  107. if (outputs[MIX_OUTPUT].active) {
  108. out = crossfade(oscillator.sqr(), oscillator.saw(), shape);
  109. out2 = crossfade(oscillator2.sqr(), oscillator2.saw(), shape2);
  110. }
  111. float mix = clamp(params[MIX_PARAM].value + inputs[MIX_CV_INPUT].value / 10.0, 0.0f, 1.0f);
  112. outputs[MIX_OUTPUT].value = 5.0f * crossfade(out, out2, mix);
  113. outputs[SIN_OUTPUT].value = 5.0f * oscillator.sin();
  114. //lights[OSC_LIGHT].setBrightnessSmooth(fmaxf(0.0f, oscillator.light()));
  115. //lights[PHASE_NEG_LIGHT].setBrightnessSmooth(fmaxf(0.0f, -oscillator.light()));
  116. }
  117. };
  118. struct HorsehairWidget : ModuleWidget {
  119. HorsehairWidget(Horsehair *module) : ModuleWidget(module) {
  120. setPanel(SVG::load(assetPlugin(plugin, "res/Horsehair.svg")));
  121. // Screws
  122. addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
  123. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  124. addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  125. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  126. // Pitch & CV
  127. addParam(ParamWidget::create<RoundSmallBlackKnob>(Vec(RACK_GRID_WIDTH * 4 + 3, 50.0), module, Horsehair::PITCH_PARAM, -2.0f, 2.0f, 0.0f));
  128. addInput(Port::create<PJ301MPort>(Vec(RACK_GRID_WIDTH + 3, 50.0), Port::INPUT, module, Horsehair::PITCH_INPUT));
  129. // Octave
  130. addParam(ParamWidget::create<RoundBlackSnapKnob>(Vec(RACK_GRID_WIDTH, 93.0), module, Horsehair::OCTAVE_PARAM + 0, -5.0f, 4.0f, -2.0f));
  131. addParam(ParamWidget::create<RoundBlackSnapKnob>(Vec(RACK_GRID_WIDTH * 4, 93.0), module, Horsehair::OCTAVE_PARAM + 1, -5.0f, 4.0f, -1.0f));
  132. // Shape
  133. addParam(ParamWidget::create<RoundBlackKnob>(Vec(RACK_GRID_WIDTH, 142.0), module, Horsehair::SHAPE_PARAM + 0, 0.0f, 1.0f, 0.0f));
  134. addParam(ParamWidget::create<RoundBlackKnob>(Vec(RACK_GRID_WIDTH * 4, 142.0), module, Horsehair::SHAPE_PARAM + 1, 0.0f, 1.0f, 1.0f));
  135. addInput(Port::create<PJ301MPort>(Vec(RACK_GRID_WIDTH - 11.5, 172.0), Port::INPUT, module, Horsehair::SHAPE_CV_INPUT + 0));
  136. addInput(Port::create<PJ301MPort>(Vec(RACK_GRID_WIDTH * 4 + 16.5, 172.0), Port::INPUT, module, Horsehair::SHAPE_CV_INPUT + 1));
  137. // Pulse width
  138. addParam(ParamWidget::create<RoundBlackKnob>(Vec(RACK_GRID_WIDTH, 215.0), module, Horsehair::PW_PARAM + 0, 0.0f, 1.0f, 0.5f));
  139. addParam(ParamWidget::create<RoundBlackKnob>(Vec(RACK_GRID_WIDTH * 4, 215.0), module, Horsehair::PW_PARAM + 1, 0.0f, 1.0f, 0.5f));
  140. addInput(Port::create<PJ301MPort>(Vec(RACK_GRID_WIDTH - 11.5, 245.0), Port::INPUT, module, Horsehair::PW_CV_INPUT + 0));
  141. addInput(Port::create<PJ301MPort>(Vec(RACK_GRID_WIDTH * 4 + 16.5, 245.0), Port::INPUT, module, Horsehair::PW_CV_INPUT + 1));
  142. // Osc Mix
  143. addParam(ParamWidget::create<RoundLargeBlackKnob>(Vec(RACK_GRID_WIDTH * 3.5 - (38.0/2), 264.0), module, Horsehair::MIX_PARAM, 0.0f, 1.0f, 0.5f));
  144. addInput(Port::create<PJ301MPort>(Vec(RACK_GRID_WIDTH - 8, 277.0), Port::INPUT, module, Horsehair::MIX_CV_INPUT));
  145. // Output
  146. addOutput(Port::create<PJ301MPort>(Vec(RACK_GRID_WIDTH + 3, 320.0), Port::OUTPUT, module, Horsehair::MIX_OUTPUT));
  147. addOutput(Port::create<PJ301MPort>(Vec(RACK_GRID_WIDTH * 4 + 3, 320.0), Port::OUTPUT, module, Horsehair::SIN_OUTPUT));
  148. // Lights
  149. //addChild(ModuleLightWidget::create<SmallLight<GreenRedLight>>(Vec(68, 42.5f), module, Horsehair::OSC_LIGHT));
  150. }
  151. };
  152. } // namespace rack_plugin_QuantalAudio
  153. using namespace rack_plugin_QuantalAudio;
  154. RACK_PLUGIN_MODEL_INIT(QuantalAudio, Horsehair) {
  155. Model *modelHorsehair = Model::create<Horsehair, HorsehairWidget>("QuantalAudio", "Horsehair", "Horsehair VCO | 7HP", OSCILLATOR_TAG);
  156. return modelHorsehair;
  157. }