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.

255 lines
8.2KB

  1. #include "plugin.hpp"
  2. #include "Wavetable.hpp"
  3. using simd::float_4;
  4. struct WTVCO : Module {
  5. enum ParamIds {
  6. MODE_PARAM, // removed
  7. SOFT_PARAM,
  8. FREQ_PARAM,
  9. POS_PARAM,
  10. FM_PARAM,
  11. // added in 2.0
  12. POS_CV_PARAM,
  13. LINEAR_PARAM,
  14. NUM_PARAMS
  15. };
  16. enum InputIds {
  17. FM_INPUT,
  18. SYNC_INPUT,
  19. POS_INPUT,
  20. // added in 2.0
  21. PITCH_INPUT,
  22. NUM_INPUTS
  23. };
  24. enum OutputIds {
  25. WAVE_OUTPUT,
  26. NUM_OUTPUTS
  27. };
  28. enum LightIds {
  29. ENUMS(PHASE_LIGHT, 3),
  30. SOFT_LIGHT,
  31. LINEAR_LIGHT,
  32. NUM_LIGHTS
  33. };
  34. Wavetable wavetable;
  35. float_4 phases[4] = {};
  36. float lastPos = 0.f;
  37. dsp::ClockDivider lightDivider;
  38. dsp::BooleanTrigger softTrigger;
  39. dsp::BooleanTrigger linearTrigger;
  40. WTVCO() {
  41. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  42. configSwitch(SOFT_PARAM, 0.f, 1.f, 0.f, "Sync", {"Hard", "Soft"});
  43. configSwitch(LINEAR_PARAM, 0.f, 1.f, 0.f, "Linear FM");
  44. configParam(FREQ_PARAM, -75.f, 75.f, 0.f, "Frequency", " Hz", dsp::FREQ_SEMITONE, dsp::FREQ_C4);
  45. configParam(POS_PARAM, 0.f, 1.f, 0.f, "Wavetable position", "%", 0.f, 100.f);
  46. configParam(FM_PARAM, -1.f, 1.f, 0.f, "Frequency modulation", "%", 0.f, 100.f);
  47. getParamQuantity(FM_PARAM)->randomizeEnabled = false;
  48. configParam(POS_CV_PARAM, -1.f, 1.f, 0.f, "Wavetable position CV", "%", 0.f, 100.f);
  49. getParamQuantity(POS_CV_PARAM)->randomizeEnabled = false;
  50. configInput(FM_INPUT, "Frequency modulation");
  51. configInput(SYNC_INPUT, "Sync");
  52. configInput(POS_INPUT, "Wavetable position");
  53. configInput(PITCH_INPUT, "1V/octave pitch");
  54. configOutput(WAVE_OUTPUT, "Wave");
  55. configLight(PHASE_LIGHT, "Phase");
  56. wavetable.setQuality(4);
  57. lightDivider.setDivision(16);
  58. onReset();
  59. }
  60. void onReset() override {
  61. wavetable.reset();
  62. }
  63. void onAdd(const AddEvent& e) override {
  64. std::string path = system::join(getPatchStorageDirectory(), "wavetable.wav");
  65. // Silently fails
  66. wavetable.load(path);
  67. }
  68. void onSave(const SaveEvent& e) override {
  69. if (!wavetable.samples.empty()) {
  70. std::string path = system::join(createPatchStorageDirectory(), "wavetable.wav");
  71. wavetable.save(path);
  72. }
  73. }
  74. void clearOutput() {
  75. outputs[WAVE_OUTPUT].setVoltage(0.f);
  76. outputs[WAVE_OUTPUT].setChannels(1);
  77. lights[PHASE_LIGHT + 0].setBrightness(0.f);
  78. lights[PHASE_LIGHT + 1].setBrightness(0.f);
  79. lights[PHASE_LIGHT + 2].setBrightness(0.f);
  80. }
  81. void process(const ProcessArgs& args) override {
  82. float freqParam = params[FREQ_PARAM].getValue();
  83. float fmParam = params[FM_PARAM].getValue();
  84. float posParam = params[POS_PARAM].getValue();
  85. float posCvParam = params[POS_CV_PARAM].getValue();
  86. bool soft = params[SOFT_PARAM].getValue() > 0.f;
  87. bool linear = params[LINEAR_PARAM].getValue() > 0.f;
  88. int channels = std::max({1, inputs[PITCH_INPUT].getChannels(), inputs[FM_INPUT].getChannels()});
  89. int waveCount = wavetable.getWaveCount();
  90. if (wavetable.waveLen >= 2 && waveCount >= 1) {
  91. // Iterate channels
  92. for (int c = 0; c < channels; c += 4) {
  93. // Calculate frequency in Hz
  94. float_4 pitch = freqParam / 12.f + inputs[PITCH_INPUT].getPolyVoltageSimd<float_4>(c) + inputs[FM_INPUT].getPolyVoltageSimd<float_4>(c) * fmParam;
  95. float_4 freq = dsp::FREQ_C4 * dsp::approxExp2_taylor5(pitch + 30.f) / std::pow(2.f, 30.f);
  96. // Limit to Nyquist frequency
  97. freq = simd::fmin(freq, args.sampleRate / 2);
  98. float_4 nyquistRatio = args.sampleRate / 2 / freq;
  99. // Accumulate phase
  100. float_4 phase = phases[c / 4];
  101. phase += freq * args.sampleTime;
  102. // Wrap phase
  103. phase -= simd::trunc(phase);
  104. phases[c / 4] = phase;
  105. // Scale phase from 0 to waveLen
  106. phase *= (wavetable.waveLen * wavetable.quality);
  107. // Get wavetable position, scaled from 0 to (waveCount - 1)
  108. float_4 pos = posParam + inputs[POS_INPUT].getPolyVoltageSimd<float_4>(c) * posCvParam / 10.f;
  109. pos = simd::clamp(pos);
  110. pos *= (waveCount - 1);
  111. if (c == 0)
  112. lastPos = pos[0];
  113. float_4 out = 0.f;
  114. for (int cc = 0; cc < 4 && c + cc < channels; cc++) {
  115. // Get wave indexes
  116. float phaseF = phase[cc] - std::trunc(phase[cc]);
  117. size_t i0 = std::trunc(phase[cc]);
  118. size_t i1 = (i0 + 1) % (wavetable.waveLen * wavetable.quality);
  119. // Get pos indexes
  120. float posF = pos[cc] - std::trunc(pos[cc]);
  121. size_t pos0 = std::trunc(pos[cc]);
  122. size_t pos1 = pos0 + 1;
  123. // Get waves
  124. // TODO Interpolate octaves
  125. int octave0 = math::log2((int) nyquistRatio[cc]);
  126. octave0 = clamp(octave0, 0, (int) wavetable.octaves - 1);
  127. float out0 = crossfade(wavetable.interpolatedAt(octave0, pos0, i0), wavetable.interpolatedAt(octave0, pos0, i1), phaseF);
  128. if (posF > 0.f) {
  129. float out1 = crossfade(wavetable.interpolatedAt(octave0, pos1, i0), wavetable.interpolatedAt(octave0, pos1, i1), phaseF);
  130. out[cc] = crossfade(out0, out1, posF);
  131. }
  132. else {
  133. out[cc] = out0;
  134. }
  135. }
  136. outputs[WAVE_OUTPUT].setVoltageSimd(out * 5.f, c);
  137. }
  138. }
  139. else {
  140. // Wavetable is invalid, so set 0V
  141. for (int c = 0; c < channels; c += 4) {
  142. outputs[WAVE_OUTPUT].setVoltageSimd(float_4(0.f), c);
  143. }
  144. }
  145. outputs[WAVE_OUTPUT].setChannels(channels);
  146. // Light
  147. if (lightDivider.process()) {
  148. if (channels == 1) {
  149. // float b = 1.f - phases[0][0];
  150. // lights[PHASE_LIGHT + 0].setSmoothBrightness(b, args.sampleTime * lightDivider.getDivision());
  151. // lights[PHASE_LIGHT + 1].setBrightness(0.f);
  152. // lights[PHASE_LIGHT + 2].setBrightness(0.f);
  153. }
  154. else {
  155. lights[PHASE_LIGHT + 0].setBrightness(0.f);
  156. lights[PHASE_LIGHT + 1].setBrightness(0.f);
  157. lights[PHASE_LIGHT + 2].setBrightness(1.f);
  158. }
  159. lights[LINEAR_LIGHT].setBrightness(linear);
  160. lights[SOFT_LIGHT].setBrightness(soft);
  161. }
  162. }
  163. json_t* dataToJson() override {
  164. json_t* rootJ = json_object();
  165. // Merge wavetable
  166. json_t* wavetableJ = wavetable.toJson();
  167. json_object_update(rootJ, wavetableJ);
  168. json_decref(wavetableJ);
  169. return rootJ;
  170. }
  171. void dataFromJson(json_t* rootJ) override {
  172. // wavetable
  173. wavetable.fromJson(rootJ);
  174. }
  175. };
  176. struct WTVCOWidget : ModuleWidget {
  177. WTVCOWidget(WTVCO* module) {
  178. setModule(module);
  179. setPanel(createPanel(asset::plugin(pluginInstance, "res/WTVCO.svg")));
  180. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
  181. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  182. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  183. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  184. addParam(createParamCentered<RoundLargeBlackKnob>(mm2px(Vec(8.915, 56.388)), module, WTVCO::FREQ_PARAM));
  185. addParam(createParamCentered<RoundLargeBlackKnob>(mm2px(Vec(26.645, 56.388)), module, WTVCO::POS_PARAM));
  186. addParam(createParamCentered<Trimpot>(mm2px(Vec(6.897, 80.603)), module, WTVCO::FM_PARAM));
  187. addParam(createLightParamCentered<LEDLightLatch<MediumSimpleLight<WhiteLight>>>(mm2px(Vec(17.734, 80.603)), module, WTVCO::LINEAR_PARAM, WTVCO::LINEAR_LIGHT));
  188. addParam(createParamCentered<Trimpot>(mm2px(Vec(28.571, 80.603)), module, WTVCO::POS_CV_PARAM));
  189. addParam(createLightParamCentered<LEDLightLatch<MediumSimpleLight<WhiteLight>>>(mm2px(Vec(17.734, 96.859)), module, WTVCO::SOFT_PARAM, WTVCO::SOFT_LIGHT));
  190. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(6.897, 96.813)), module, WTVCO::FM_INPUT));
  191. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(28.571, 96.859)), module, WTVCO::POS_INPUT));
  192. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(6.897, 113.115)), module, WTVCO::PITCH_INPUT));
  193. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(17.734, 113.115)), module, WTVCO::SYNC_INPUT));
  194. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(28.571, 113.115)), module, WTVCO::WAVE_OUTPUT));
  195. addChild(createLightCentered<SmallLight<RedGreenBlueLight>>(mm2px(Vec(17.733, 49.409)), module, WTVCO::PHASE_LIGHT));
  196. WTDisplay<WTVCO>* display = createWidget<WTDisplay<WTVCO>>(mm2px(Vec(0.004, 13.04)));
  197. display->box.size = mm2px(Vec(35.56, 29.224));
  198. display->module = module;
  199. addChild(display);
  200. }
  201. void appendContextMenu(Menu* menu) override {
  202. WTVCO* module = dynamic_cast<WTVCO*>(this->module);
  203. assert(module);
  204. menu->addChild(new MenuSeparator);
  205. module->wavetable.appendContextMenu(menu);
  206. }
  207. };
  208. Model* modelVCO2 = createModel<WTVCO, WTVCOWidget>("VCO2");