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.

327 lines
11KB

  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::MinBlepGenerator<16, 16, float_4> syncMinBleps[4];
  38. float_4 lastSyncValues[4] = {};
  39. float_4 syncDirections[4] = {};
  40. dsp::ClockDivider lightDivider;
  41. dsp::BooleanTrigger softTrigger;
  42. dsp::BooleanTrigger linearTrigger;
  43. WTVCO() {
  44. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  45. configSwitch(SOFT_PARAM, 0.f, 1.f, 0.f, "Sync", {"Hard", "Soft"});
  46. configSwitch(LINEAR_PARAM, 0.f, 1.f, 0.f, "FM mode", {"1V/octave", "Through-zero linear"});
  47. configParam(FREQ_PARAM, -75.f, 75.f, 0.f, "Frequency", " Hz", dsp::FREQ_SEMITONE, dsp::FREQ_C4);
  48. configParam(POS_PARAM, 0.f, 1.f, 0.f, "Wavetable position", "%", 0.f, 100.f);
  49. configParam(FM_PARAM, -1.f, 1.f, 0.f, "Frequency modulation", "%", 0.f, 100.f);
  50. getParamQuantity(FM_PARAM)->randomizeEnabled = false;
  51. configParam(POS_CV_PARAM, -1.f, 1.f, 0.f, "Wavetable position CV", "%", 0.f, 100.f);
  52. getParamQuantity(POS_CV_PARAM)->randomizeEnabled = false;
  53. configInput(FM_INPUT, "Frequency modulation");
  54. configInput(SYNC_INPUT, "Sync");
  55. configInput(POS_INPUT, "Wavetable position");
  56. configInput(PITCH_INPUT, "1V/octave pitch");
  57. configOutput(WAVE_OUTPUT, "Wavetable");
  58. configLight(PHASE_LIGHT, "Phase");
  59. wavetable.setQuality(8);
  60. lightDivider.setDivision(16);
  61. onReset();
  62. }
  63. void onReset() override {
  64. wavetable.reset();
  65. for (int i = 0; i < 4; i++) {
  66. syncDirections[i] = 1.f;
  67. }
  68. }
  69. void onAdd(const AddEvent& e) override {
  70. std::string path = system::join(getPatchStorageDirectory(), "wavetable.wav");
  71. // Silently fails
  72. wavetable.load(path);
  73. }
  74. void onSave(const SaveEvent& e) override {
  75. if (!wavetable.samples.empty()) {
  76. std::string path = system::join(createPatchStorageDirectory(), "wavetable.wav");
  77. wavetable.save(path);
  78. }
  79. }
  80. void clearOutput() {
  81. outputs[WAVE_OUTPUT].setVoltage(0.f);
  82. outputs[WAVE_OUTPUT].setChannels(1);
  83. lights[PHASE_LIGHT + 0].setBrightness(0.f);
  84. lights[PHASE_LIGHT + 1].setBrightness(0.f);
  85. lights[PHASE_LIGHT + 2].setBrightness(0.f);
  86. }
  87. float getWave(float index, float pos, float octave) {
  88. // Get wave indexes
  89. float indexF = index - std::trunc(index);
  90. size_t index0 = std::trunc(index);
  91. size_t index1 = (index0 + 1) % (wavetable.waveLen * wavetable.quality);
  92. // Get position indexes
  93. float posF = pos - std::trunc(pos);
  94. size_t pos0 = std::trunc(pos);
  95. size_t pos1 = pos0 + 1;
  96. // Get octave index
  97. // float octaveF = octave - std::trunc(octave);
  98. size_t octave0 = std::trunc(octave);
  99. octave0 = std::min(octave0, wavetable.octaves - 1);
  100. // size_t octave1 = octave0 + 1;
  101. // Linearly interpolate wave index
  102. float out = crossfade(wavetable.interpolatedAt(octave0, pos0, index0), wavetable.interpolatedAt(octave0, pos0, index1), indexF);
  103. // Interpolate octave
  104. // if (octaveF > 0.f && octave1 < wavetable.octaves) {
  105. // float out1 = crossfade(wavetable.interpolatedAt(octave1, pos0, index0), wavetable.interpolatedAt(octave1, pos0, index1), indexF);
  106. // out = crossfade(out, out1, octaveF);
  107. // }
  108. // Linearly interpolate position if needed
  109. if (posF > 0.f) {
  110. float out1 = crossfade(wavetable.interpolatedAt(octave0, pos1, index0), wavetable.interpolatedAt(octave0, pos1, index1), indexF);
  111. // Interpolate octave
  112. // if (octaveF > 0.f && octave1 < wavetable.octaves) {
  113. // float out2 = crossfade(wavetable.interpolatedAt(octave1, pos1, index0), wavetable.interpolatedAt(octave1, pos1, index1), indexF);
  114. // out1 = crossfade(out1, out2, octaveF);
  115. // }
  116. out = crossfade(out, out1, posF);
  117. }
  118. return out;
  119. }
  120. void process(const ProcessArgs& args) override {
  121. float freqParam = params[FREQ_PARAM].getValue() / 12.f;
  122. float fmParam = params[FM_PARAM].getValue();
  123. float posParam = params[POS_PARAM].getValue();
  124. float posCvParam = params[POS_CV_PARAM].getValue();
  125. bool soft = params[SOFT_PARAM].getValue() > 0.f;
  126. bool linear = params[LINEAR_PARAM].getValue() > 0.f;
  127. bool syncEnabled = inputs[SYNC_INPUT].isConnected();
  128. int channels = std::max({1, inputs[PITCH_INPUT].getChannels(), inputs[FM_INPUT].getChannels()});
  129. int waveCount = wavetable.getWaveCount();
  130. if (wavetable.waveLen >= 2 && waveCount >= 1) {
  131. // Iterate channels
  132. for (int c = 0; c < channels; c += 4) {
  133. // Calculate frequency in Hz
  134. float_4 pitch = freqParam + inputs[PITCH_INPUT].getPolyVoltageSimd<float_4>(c);
  135. float_4 freq;
  136. if (!linear) {
  137. pitch += inputs[FM_INPUT].getPolyVoltageSimd<float_4>(c) * fmParam;
  138. freq = dsp::FREQ_C4 * dsp::approxExp2_taylor5(pitch + 30.f) / std::pow(2.f, 30.f);
  139. }
  140. else {
  141. freq = dsp::FREQ_C4 * dsp::approxExp2_taylor5(pitch + 30.f) / std::pow(2.f, 30.f);
  142. freq += dsp::FREQ_C4 * inputs[FM_INPUT].getPolyVoltageSimd<float_4>(c) * fmParam;
  143. }
  144. // Limit to Nyquist frequency
  145. freq = simd::fmin(freq, args.sampleRate / 2);
  146. float_4 octave = simd::log2(args.sampleRate / 2 / freq);
  147. // Accumulate phase
  148. float_4 phase = phases[c / 4];
  149. if (!soft) {
  150. syncDirections[c / 4] = 1.f;
  151. }
  152. // Delta phase can be negative
  153. float_4 deltaPhase = freq * args.sampleTime * syncDirections[c / 4];
  154. phase += deltaPhase;
  155. // Wrap phase
  156. phase -= simd::floor(phase);
  157. phases[c / 4] = phase;
  158. float_4 index = phase * wavetable.waveLen * wavetable.quality;
  159. // Get wavetable position, scaled from 0 to (waveCount - 1)
  160. float_4 pos = posParam + inputs[POS_INPUT].getPolyVoltageSimd<float_4>(c) * posCvParam / 10.f;
  161. pos = simd::clamp(pos);
  162. pos *= (waveCount - 1);
  163. if (c == 0)
  164. lastPos = pos[0];
  165. // Get wave output serially
  166. int ccs = std::min(4, channels - c);
  167. float_4 out = 0.f;
  168. for (int cc = 0; cc < ccs; cc++) {
  169. out[cc] = getWave(index[cc], pos[cc], octave[cc]);
  170. }
  171. // Sync
  172. if (syncEnabled) {
  173. float_4 syncValue = inputs[SYNC_INPUT].getPolyVoltageSimd<float_4>(c);
  174. float_4 deltaSync = syncValue - lastSyncValues[c / 4];
  175. float_4 syncCrossing = -lastSyncValues[c / 4] / deltaSync;
  176. lastSyncValues[c / 4] = syncValue;
  177. float_4 sync = (0.f < syncCrossing) & (syncCrossing <= 1.f) & (syncValue >= 0.f);
  178. int syncMask = simd::movemask(sync);
  179. if (syncMask) {
  180. if (soft) {
  181. syncDirections[c / 4] = simd::ifelse(sync, -syncDirections[c / 4], syncDirections[c / 4]);
  182. }
  183. else {
  184. phases[c / 4] = simd::ifelse(sync, (1.f - syncCrossing) * deltaPhase, phases[c / 4]);
  185. // Insert minBLEP for sync serially
  186. for (int cc = 0; cc < ccs; cc++) {
  187. if (syncMask & (1 << cc)) {
  188. float_4 mask = simd::movemaskInverse<float_4>(1 << cc);
  189. float p = syncCrossing[cc] - 1.f;
  190. float index = phases[c / 4][cc] * wavetable.waveLen * wavetable.quality;
  191. float out1 = getWave(index, pos[cc], octave[cc]);
  192. float_4 x = mask & (out1 - out[cc]);
  193. syncMinBleps[c / 4].insertDiscontinuity(p, x);
  194. }
  195. }
  196. }
  197. }
  198. }
  199. out += syncMinBleps[c / 4].process();
  200. outputs[WAVE_OUTPUT].setVoltageSimd(out * 5.f, c);
  201. }
  202. }
  203. else {
  204. // Wavetable is invalid, so set 0V
  205. for (int c = 0; c < channels; c += 4) {
  206. outputs[WAVE_OUTPUT].setVoltageSimd(float_4(0.f), c);
  207. }
  208. }
  209. outputs[WAVE_OUTPUT].setChannels(channels);
  210. // Light
  211. if (lightDivider.process()) {
  212. if (channels == 1) {
  213. float b = std::sin(2 * M_PI * phases[0][0]);
  214. lights[PHASE_LIGHT + 0].setSmoothBrightness(-b, args.sampleTime * lightDivider.getDivision());
  215. lights[PHASE_LIGHT + 1].setSmoothBrightness(b, args.sampleTime * lightDivider.getDivision());
  216. lights[PHASE_LIGHT + 2].setBrightness(0.f);
  217. }
  218. else {
  219. lights[PHASE_LIGHT + 0].setBrightness(0.f);
  220. lights[PHASE_LIGHT + 1].setBrightness(0.f);
  221. lights[PHASE_LIGHT + 2].setBrightness(1.f);
  222. }
  223. lights[LINEAR_LIGHT].setBrightness(linear);
  224. lights[SOFT_LIGHT].setBrightness(soft);
  225. }
  226. }
  227. void paramsFromJson(json_t* rootJ) override {
  228. // In <2.0, there were no attenuverters, so set them to 1.0 in case they are not overwritten.
  229. params[POS_CV_PARAM].setValue(1.f);
  230. Module::paramsFromJson(rootJ);
  231. }
  232. json_t* dataToJson() override {
  233. json_t* rootJ = json_object();
  234. // Merge wavetable
  235. json_t* wavetableJ = wavetable.toJson();
  236. json_object_update(rootJ, wavetableJ);
  237. json_decref(wavetableJ);
  238. return rootJ;
  239. }
  240. void dataFromJson(json_t* rootJ) override {
  241. // wavetable
  242. wavetable.fromJson(rootJ);
  243. }
  244. };
  245. struct WTVCOWidget : ModuleWidget {
  246. WTVCOWidget(WTVCO* module) {
  247. setModule(module);
  248. setPanel(createPanel(asset::plugin(pluginInstance, "res/WTVCO.svg")));
  249. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
  250. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  251. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  252. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  253. addParam(createParamCentered<RoundLargeBlackKnob>(mm2px(Vec(8.915, 56.388)), module, WTVCO::FREQ_PARAM));
  254. addParam(createParamCentered<RoundLargeBlackKnob>(mm2px(Vec(26.645, 56.388)), module, WTVCO::POS_PARAM));
  255. addParam(createParamCentered<Trimpot>(mm2px(Vec(6.897, 80.603)), module, WTVCO::FM_PARAM));
  256. addParam(createLightParamCentered<VCVLightLatch<MediumSimpleLight<WhiteLight>>>(mm2px(Vec(17.734, 80.603)), module, WTVCO::LINEAR_PARAM, WTVCO::LINEAR_LIGHT));
  257. addParam(createParamCentered<Trimpot>(mm2px(Vec(28.571, 80.603)), module, WTVCO::POS_CV_PARAM));
  258. addParam(createLightParamCentered<VCVLightLatch<MediumSimpleLight<WhiteLight>>>(mm2px(Vec(17.734, 96.859)), module, WTVCO::SOFT_PARAM, WTVCO::SOFT_LIGHT));
  259. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(6.897, 96.813)), module, WTVCO::FM_INPUT));
  260. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(28.571, 96.859)), module, WTVCO::POS_INPUT));
  261. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(6.897, 113.115)), module, WTVCO::PITCH_INPUT));
  262. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(17.734, 113.115)), module, WTVCO::SYNC_INPUT));
  263. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(28.571, 113.115)), module, WTVCO::WAVE_OUTPUT));
  264. addChild(createLightCentered<SmallLight<RedGreenBlueLight>>(mm2px(Vec(17.733, 49.409)), module, WTVCO::PHASE_LIGHT));
  265. WTDisplay<WTVCO>* display = createWidget<WTDisplay<WTVCO>>(mm2px(Vec(0.004, 13.04)));
  266. display->box.size = mm2px(Vec(35.56, 29.224));
  267. display->module = module;
  268. addChild(display);
  269. }
  270. void appendContextMenu(Menu* menu) override {
  271. WTVCO* module = dynamic_cast<WTVCO*>(this->module);
  272. assert(module);
  273. menu->addChild(new MenuSeparator);
  274. module->wavetable.appendContextMenu(menu);
  275. }
  276. };
  277. Model* modelVCO2 = createModel<WTVCO, WTVCOWidget>("VCO2");