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.

344 lines
13KB

  1. #include "plugin.hpp"
  2. float aliasSuppressedSaw(const float* phases, float pw) {
  3. float sawBuffer[3];
  4. for (int i = 0; i < 3; ++i) {
  5. float p = 2 * phases[i] - 1.0; // range -1 to +1
  6. float pwp = p + 2 * pw; // phase after pw (pw in [0, 1])
  7. pwp += simd::ifelse(pwp > 1, -2, simd::ifelse(pwp < -1, +2, 0)); // modulo on [-1, +1]
  8. sawBuffer[i] = (pwp * pwp * pwp - pwp) / 6.0; // eq 11
  9. }
  10. return (sawBuffer[0] - 2.0 * sawBuffer[1] + sawBuffer[2]);
  11. }
  12. float aliasSuppressedOffsetSaw(const float* phases, float pw) {
  13. float sawOffsetBuff[3];
  14. for (int i = 0; i < 3; ++i) {
  15. float pwp = 2 * phases[i] - 2 * pw; // range -1 to +1
  16. pwp += simd::ifelse(pwp > 1, -2, 0); // modulo on [-1, +1]
  17. sawOffsetBuff[i] = (pwp * pwp * pwp - pwp) / 6.0; // eq 11
  18. }
  19. return (sawOffsetBuff[0] - 2.0 * sawOffsetBuff[1] + sawOffsetBuff[2]);
  20. }
  21. struct Octaves : Module {
  22. enum ParamId {
  23. PWM_CV_PARAM,
  24. OCTAVE_PARAM,
  25. TUNE_PARAM,
  26. PWM_PARAM,
  27. RANGE_PARAM,
  28. GAIN_01F_PARAM,
  29. GAIN_02F_PARAM,
  30. GAIN_04F_PARAM,
  31. GAIN_08F_PARAM,
  32. GAIN_16F_PARAM,
  33. GAIN_32F_PARAM,
  34. PARAMS_LEN
  35. };
  36. enum InputId {
  37. VOCT1_INPUT,
  38. VOCT2_INPUT,
  39. SYNC_INPUT,
  40. PWM_INPUT,
  41. GAIN_01F_INPUT,
  42. GAIN_02F_INPUT,
  43. GAIN_04F_INPUT,
  44. GAIN_08F_INPUT,
  45. GAIN_16F_INPUT,
  46. GAIN_32F_INPUT,
  47. INPUTS_LEN
  48. };
  49. enum OutputId {
  50. OUT_01F_OUTPUT,
  51. OUT_02F_OUTPUT,
  52. OUT_04F_OUTPUT,
  53. OUT_08F_OUTPUT,
  54. OUT_16F_OUTPUT,
  55. OUT_32F_OUTPUT,
  56. OUT_OUTPUT,
  57. OUT2_OUTPUT,
  58. OUT_01F_OUTPUT_ALT,
  59. OUT_02F_OUTPUT_ALT,
  60. OUT_04F_OUTPUT_ALT,
  61. OUT_08F_OUTPUT_ALT,
  62. OUT_16F_OUTPUT_ALT,
  63. OUT_32F_OUTPUT_ALT,
  64. OUTPUTS_LEN
  65. };
  66. enum LightId {
  67. LIGHTS_LEN
  68. };
  69. bool limitPW = true;
  70. bool removePulseDC = false;
  71. int oversamplingIndex = 0;
  72. Octaves() {
  73. config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN);
  74. configParam(PWM_CV_PARAM, 0.f, 1.f, 1.f, "PWM CV attenuater");
  75. auto octParam = configSwitch(OCTAVE_PARAM, 0.f, 6.f, 4.f, "Octave", {"C1", "C2", "C3", "C4", "C5", "C6", "C7"});
  76. octParam->snapEnabled = true;
  77. configParam(TUNE_PARAM, -1.f, 1.f, 0.f, "Tune");
  78. configParam(PWM_PARAM, 0.5f, 0.f, 0.5f, "PWM");
  79. auto rangeParam = configSwitch(RANGE_PARAM, 0.f, 2.f, 0.f, "Range", {"VCO: Full", "VCO: Octave", "VCO: Semitone"});
  80. rangeParam->snapEnabled = true;
  81. configParam(GAIN_01F_PARAM, 0.f, 1.f, 0.f, "Gain Fundamental");
  82. configParam(GAIN_02F_PARAM, 0.f, 1.f, 0.f, "Gain x2 Fundamental");
  83. configParam(GAIN_04F_PARAM, 0.f, 1.f, 0.f, "Gain x4 Fundamental");
  84. configParam(GAIN_08F_PARAM, 0.f, 1.f, 0.f, "Gain x8 Fundamental");
  85. configParam(GAIN_16F_PARAM, 0.f, 1.f, 0.f, "Gain x16 Fundamental");
  86. configParam(GAIN_32F_PARAM, 0.f, 1.f, 0.f, "Gain x32 Fundamental");
  87. configInput(VOCT1_INPUT, "V/Octave 1");
  88. configInput(VOCT2_INPUT, "V/Octave 2");
  89. configInput(SYNC_INPUT, "Sync");
  90. configInput(PWM_INPUT, "PWM");
  91. configInput(GAIN_01F_INPUT, "Gain x1F CV");
  92. configInput(GAIN_02F_INPUT, "Gain x1F CV");
  93. configInput(GAIN_04F_INPUT, "Gain x1F CV");
  94. configInput(GAIN_08F_INPUT, "Gain x1F CV");
  95. configInput(GAIN_16F_INPUT, "Gain x1F CV");
  96. configInput(GAIN_32F_INPUT, "Gain x1F CV");
  97. configOutput(OUT_01F_OUTPUT, "x1F");
  98. configOutput(OUT_02F_OUTPUT, "x2F");
  99. configOutput(OUT_04F_OUTPUT, "x4F");
  100. configOutput(OUT_08F_OUTPUT, "x8F");
  101. configOutput(OUT_16F_OUTPUT, "x16F");
  102. configOutput(OUT_32F_OUTPUT, "x32F");
  103. configOutput(OUT_OUTPUT, "debug");
  104. }
  105. float phase = 0.f;
  106. float phases[3];
  107. bool forceNaive = false;
  108. void process(const ProcessArgs& args) override {
  109. float pitch = params[TUNE_PARAM].getValue() + inputs[VOCT1_INPUT].getVoltage() + inputs[VOCT2_INPUT].getVoltage();
  110. pitch += params[OCTAVE_PARAM].getValue() - 3;
  111. float freq = dsp::FREQ_C4 * dsp::exp2_taylor5(pitch);
  112. // -1 to +1
  113. float pwmCV = params[PWM_CV_PARAM].getValue() * clamp(inputs[PWM_INPUT].getVoltage() / 10.f, -1.f, 1.f);
  114. const float pulseWidthLimit = limitPW ? 0.05f : 0.0f;
  115. // pwm in [-0.25 : +0.25]
  116. float pwm = clamp(0.5 - params[PWM_PARAM].getValue() + 0.5 * pwmCV, -0.5f + pulseWidthLimit, 0.5f - pulseWidthLimit);
  117. pwm /= 2.0;
  118. float deltaPhase = freq * args.sampleTime;
  119. phase += deltaPhase;
  120. phase -= std::floor(phase);
  121. float sum = 0.f;
  122. float sumNaive = 0.f;
  123. for (int c = 0; c < 6; c++) {
  124. // derive phases for higher octaves from base phase (this keeps things in sync!)
  125. const float n = (float)(1 << c);
  126. // this is on [0, 1]
  127. const float effectivePhaseRaw = n * std::fmod(phase, 1 / n);
  128. // this is on [0, 1], and offset in time by 0.25
  129. const float effectivePhase = std::fmod(effectivePhaseRaw + 0.25, 1);
  130. const float effectiveDeltaPhase = deltaPhase * n;
  131. const float gainCV = clamp(inputs[GAIN_01F_INPUT + c].getNormalVoltage(10.f) / 10.f, 0.f, 1.0f);
  132. const float gain = params[GAIN_01F_PARAM + c].getValue() * gainCV;
  133. // floating point arithmetic doesn't work well at low frequencies, specifically because the finite difference denominator
  134. // becomes tiny - we check for that scenario and use naive / 1st order waveforms in that frequency regime (as aliasing isn't
  135. // a problem there). With no oversampling, at 44100Hz, the threshold frequency is 44.1Hz.
  136. const bool lowFreqRegime = forceNaive; //effectiveDeltaPhase < 1e-3 || forceNaive;
  137. //float waveTri = 1.0 - 2.0 * std::abs(2.f * effectivePhase - 1.0);
  138. // float dpwOrder1 = (waveTri > 2 * pwm - 1) ? 1.0 : -1.0;
  139. float dpwOrder1 = gain * (effectivePhaseRaw > pwm + 0.25 && effectivePhaseRaw < 0.75 - pwm ? -1.0 : +1.0);
  140. dpwOrder1 -= removePulseDC ? 2.f * (0.5f - pwm) : 0.f;
  141. // dpwOrder1 = waveTri * gain;
  142. sumNaive += dpwOrder1;
  143. outputs[OUT_01F_OUTPUT_ALT + c].setVoltage(dpwOrder1);
  144. float outForOctave = dpwOrder1;
  145. if (!lowFreqRegime) {
  146. phases[0] = effectivePhase - 2 * effectiveDeltaPhase + (effectivePhase < 2 * effectiveDeltaPhase ? 1.f : 0.f);
  147. phases[1] = effectivePhase - 1 * effectiveDeltaPhase + (effectivePhase < 1 * effectiveDeltaPhase ? 1.f : 0.f);
  148. phases[2] = effectivePhase;
  149. float saw = aliasSuppressedSaw(phases, pwm);
  150. float sawOffset = aliasSuppressedOffsetSaw(phases, pwm);
  151. float denominatorInv = 0.25 / (effectiveDeltaPhase * effectiveDeltaPhase);
  152. float dpwOrder3 = gain * (sawOffset - saw) * denominatorInv;
  153. const float pulseDCOffset = (!removePulseDC) * 4.f * pwm * gain;
  154. dpwOrder3 += pulseDCOffset;
  155. outForOctave = dpwOrder3;
  156. }
  157. sum += outForOctave;
  158. sum = clamp(sum, -1.f, 1.f);
  159. if (outputs[OUT_01F_OUTPUT + c].isConnected()) {
  160. outputs[OUT_01F_OUTPUT + c].setVoltage(5 * sum);
  161. sum = 0.f;
  162. }
  163. if (c == 0) {
  164. outputs[OUT_OUTPUT].setVoltage(effectivePhase);
  165. float saw = aliasSuppressedSaw(phases, 2*pwm);
  166. float sawOffset = aliasSuppressedOffsetSaw(phases, 2*pwm);
  167. float denominatorInv = 0.25 / (effectiveDeltaPhase * effectiveDeltaPhase);
  168. float dpwOrder3_ = gain * (-saw) * denominatorInv;
  169. outputs[OUT2_OUTPUT].setVoltage(dpwOrder3_);
  170. }
  171. }
  172. //outputs[OUT_OUTPUT].setVoltage(sum);
  173. //outputs[OUT2_OUTPUT].setVoltage(phase > 0.5 ? +5 : -5);
  174. }
  175. json_t* dataToJson() override {
  176. json_t* rootJ = json_object();
  177. json_object_set_new(rootJ, "removePulseDC", json_boolean(removePulseDC));
  178. json_object_set_new(rootJ, "limitPW", json_boolean(limitPW));
  179. json_object_set_new(rootJ, "forceNaive", json_boolean(forceNaive));
  180. // TODO:
  181. // json_object_set_new(rootJ, "oversamplingIndex", json_integer(oversampler[0].getOversamplingIndex()));
  182. return rootJ;
  183. }
  184. void dataFromJson(json_t* rootJ) override {
  185. json_t* removePulseDCJ = json_object_get(rootJ, "removePulseDC");
  186. if (removePulseDCJ) {
  187. removePulseDC = json_boolean_value(removePulseDCJ);
  188. }
  189. json_t* limitPWJ = json_object_get(rootJ, "limitPW");
  190. if (limitPWJ) {
  191. limitPW = json_boolean_value(limitPWJ);
  192. }
  193. json_t* forceNaiveJ = json_object_get(rootJ, "forceNaive");
  194. if (forceNaiveJ) {
  195. forceNaive = json_boolean_value(forceNaiveJ);
  196. }
  197. json_t* oversamplingIndexJ = json_object_get(rootJ, "oversamplingIndex");
  198. if (oversamplingIndexJ) {
  199. oversamplingIndex = json_integer_value(oversamplingIndexJ);
  200. onSampleRateChange();
  201. }
  202. }
  203. };
  204. struct OctavesWidget : ModuleWidget {
  205. OctavesWidget(Octaves* module) {
  206. setModule(module);
  207. setPanel(createPanel(asset::plugin(pluginInstance, "res/panels/Octaves.svg")));
  208. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, 0)));
  209. addChild(createWidget<Knurlie>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  210. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  211. addChild(createWidget<Knurlie>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  212. addParam(createParamCentered<BefacoTinyKnobLightGrey>(mm2px(Vec(52.138, 15.037)), module, Octaves::PWM_CV_PARAM));
  213. addParam(createParam<CKSSVert7>(mm2px(Vec(22.171, 30.214)), module, Octaves::OCTAVE_PARAM));
  214. addParam(createParamCentered<BefacoTinyKnobLightGrey>(mm2px(Vec(10.264, 33.007)), module, Octaves::TUNE_PARAM));
  215. addParam(createParamCentered<Davies1900hLargeRedKnob>(mm2px(Vec(45.384, 40.528)), module, Octaves::PWM_PARAM));
  216. addParam(createParam<CKSSThreeHorizontal>(mm2px(Vec(6.023, 48.937)), module, Octaves::RANGE_PARAM));
  217. addParam(createParam<BefacoSlidePotSmall>(mm2px(Vec(2.9830, 60.342)), module, Octaves::GAIN_01F_PARAM));
  218. addParam(createParam<BefacoSlidePotSmall>(mm2px(Vec(12.967, 60.342)), module, Octaves::GAIN_02F_PARAM));
  219. addParam(createParam<BefacoSlidePotSmall>(mm2px(Vec(22.951, 60.342)), module, Octaves::GAIN_04F_PARAM));
  220. addParam(createParam<BefacoSlidePotSmall>(mm2px(Vec(32.936, 60.342)), module, Octaves::GAIN_08F_PARAM));
  221. addParam(createParam<BefacoSlidePotSmall>(mm2px(Vec(42.920, 60.342)), module, Octaves::GAIN_16F_PARAM));
  222. addParam(createParam<BefacoSlidePotSmall>(mm2px(Vec(52.905, 60.342)), module, Octaves::GAIN_32F_PARAM));
  223. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.247, 15.181)), module, Octaves::VOCT1_INPUT));
  224. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.282, 15.181)), module, Octaves::VOCT2_INPUT));
  225. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(25.316, 15.181)), module, Octaves::SYNC_INPUT));
  226. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(37.092, 15.135)), module, Octaves::PWM_INPUT));
  227. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.247, 100.492)), module, Octaves::GAIN_01F_INPUT));
  228. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.282, 100.492)), module, Octaves::GAIN_02F_INPUT));
  229. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(25.316, 100.492)), module, Octaves::GAIN_04F_INPUT));
  230. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(35.35, 100.492)), module, Octaves::GAIN_08F_INPUT));
  231. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(45.384, 100.492)), module, Octaves::GAIN_16F_INPUT));
  232. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(55.418, 100.492)), module, Octaves::GAIN_32F_INPUT));
  233. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(5.247, 113.508)), module, Octaves::OUT_01F_OUTPUT));
  234. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(15.282, 113.508)), module, Octaves::OUT_02F_OUTPUT));
  235. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(25.316, 113.508)), module, Octaves::OUT_04F_OUTPUT));
  236. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(35.35, 113.508)), module, Octaves::OUT_08F_OUTPUT));
  237. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(45.384, 113.508)), module, Octaves::OUT_16F_OUTPUT));
  238. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(55.418, 113.508)), module, Octaves::OUT_32F_OUTPUT));
  239. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(25.316, 106.508)), module, Octaves::OUT_OUTPUT));
  240. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(35.316, 106.508)), module, Octaves::OUT2_OUTPUT));
  241. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(5.247, 120.508)), module, Octaves::OUT_01F_OUTPUT_ALT));
  242. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(15.282, 120.508)), module, Octaves::OUT_02F_OUTPUT_ALT));
  243. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(25.316, 120.508)), module, Octaves::OUT_04F_OUTPUT_ALT));
  244. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(35.35, 120.508)), module, Octaves::OUT_08F_OUTPUT_ALT));
  245. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(45.384, 120.508)), module, Octaves::OUT_16F_OUTPUT_ALT));
  246. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(55.418, 120.508)), module, Octaves::OUT_32F_OUTPUT_ALT));
  247. }
  248. void appendContextMenu(Menu* menu) override {
  249. Octaves* module = dynamic_cast<Octaves*>(this->module);
  250. assert(module);
  251. menu->addChild(new MenuSeparator());
  252. menu->addChild(createSubmenuItem("Hardware compatibility", "",
  253. [ = ](Menu * menu) {
  254. menu->addChild(createBoolPtrMenuItem("Limit pulsewidth (5\%-95\%)", "", &module->limitPW));
  255. menu->addChild(createBoolPtrMenuItem("Remove pulse DC", "", &module->removePulseDC));
  256. }
  257. ));
  258. menu->addChild(createIndexSubmenuItem("Oversampling",
  259. {"Off", "x2", "x4", "x8"},
  260. [ = ]() {
  261. return module->oversamplingIndex;
  262. },
  263. [ = ](int mode) {
  264. module->oversamplingIndex = mode;
  265. module->onSampleRateChange();
  266. }
  267. ));
  268. menu->addChild(createBoolPtrMenuItem("Force naive waveforms", "", &module->forceNaive));
  269. }
  270. };
  271. Model* modelOctaves = createModel<Octaves, OctavesWidget>("Octaves");