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.

331 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. if (!wavetable.mutex.try_lock())
  130. return;
  131. DEFER({wavetable.mutex.unlock();});
  132. int waveCount = wavetable.getWaveCount();
  133. if (wavetable.waveLen >= 2 && waveCount >= 1) {
  134. // Iterate channels
  135. for (int c = 0; c < channels; c += 4) {
  136. // Calculate frequency in Hz
  137. float_4 pitch = freqParam + inputs[PITCH_INPUT].getPolyVoltageSimd<float_4>(c);
  138. float_4 freq;
  139. if (!linear) {
  140. pitch += inputs[FM_INPUT].getPolyVoltageSimd<float_4>(c) * fmParam;
  141. freq = dsp::FREQ_C4 * dsp::approxExp2_taylor5(pitch + 30.f) / std::pow(2.f, 30.f);
  142. }
  143. else {
  144. freq = dsp::FREQ_C4 * dsp::approxExp2_taylor5(pitch + 30.f) / std::pow(2.f, 30.f);
  145. freq += dsp::FREQ_C4 * inputs[FM_INPUT].getPolyVoltageSimd<float_4>(c) * fmParam;
  146. }
  147. // Limit to Nyquist frequency
  148. freq = simd::fmin(freq, args.sampleRate / 2);
  149. float_4 octave = simd::log2(args.sampleRate / 2 / freq);
  150. // Accumulate phase
  151. float_4 phase = phases[c / 4];
  152. if (!soft) {
  153. syncDirections[c / 4] = 1.f;
  154. }
  155. // Delta phase can be negative
  156. float_4 deltaPhase = freq * args.sampleTime * syncDirections[c / 4];
  157. phase += deltaPhase;
  158. // Wrap phase
  159. phase -= simd::floor(phase);
  160. phases[c / 4] = phase;
  161. float_4 index = phase * wavetable.waveLen * wavetable.quality;
  162. // Get wavetable position, scaled from 0 to (waveCount - 1)
  163. float_4 pos = posParam + inputs[POS_INPUT].getPolyVoltageSimd<float_4>(c) * posCvParam / 10.f;
  164. pos = simd::clamp(pos);
  165. pos *= (waveCount - 1);
  166. if (c == 0)
  167. lastPos = pos[0];
  168. // Get wave output serially
  169. int ccs = std::min(4, channels - c);
  170. float_4 out = 0.f;
  171. for (int cc = 0; cc < ccs; cc++) {
  172. out[cc] = getWave(index[cc], pos[cc], octave[cc]);
  173. }
  174. // Sync
  175. if (syncEnabled) {
  176. float_4 syncValue = inputs[SYNC_INPUT].getPolyVoltageSimd<float_4>(c);
  177. float_4 deltaSync = syncValue - lastSyncValues[c / 4];
  178. float_4 syncCrossing = -lastSyncValues[c / 4] / deltaSync;
  179. lastSyncValues[c / 4] = syncValue;
  180. float_4 sync = (0.f < syncCrossing) & (syncCrossing <= 1.f) & (syncValue >= 0.f);
  181. int syncMask = simd::movemask(sync);
  182. if (syncMask) {
  183. if (soft) {
  184. syncDirections[c / 4] = simd::ifelse(sync, -syncDirections[c / 4], syncDirections[c / 4]);
  185. }
  186. else {
  187. phases[c / 4] = simd::ifelse(sync, (1.f - syncCrossing) * deltaPhase, phases[c / 4]);
  188. // Insert minBLEP for sync serially
  189. for (int cc = 0; cc < ccs; cc++) {
  190. if (syncMask & (1 << cc)) {
  191. float_4 mask = simd::movemaskInverse<float_4>(1 << cc);
  192. float p = syncCrossing[cc] - 1.f;
  193. float index = phases[c / 4][cc] * wavetable.waveLen * wavetable.quality;
  194. float out1 = getWave(index, pos[cc], octave[cc]);
  195. float_4 x = mask & (out1 - out[cc]);
  196. syncMinBleps[c / 4].insertDiscontinuity(p, x);
  197. }
  198. }
  199. }
  200. }
  201. }
  202. out += syncMinBleps[c / 4].process();
  203. outputs[WAVE_OUTPUT].setVoltageSimd(out * 5.f, c);
  204. }
  205. }
  206. else {
  207. // Wavetable is invalid, so set 0V
  208. for (int c = 0; c < channels; c += 4) {
  209. outputs[WAVE_OUTPUT].setVoltageSimd(float_4(0.f), c);
  210. }
  211. }
  212. outputs[WAVE_OUTPUT].setChannels(channels);
  213. // Light
  214. if (lightDivider.process()) {
  215. if (channels == 1) {
  216. float b = std::sin(2 * M_PI * phases[0][0]);
  217. lights[PHASE_LIGHT + 0].setSmoothBrightness(-b, args.sampleTime * lightDivider.getDivision());
  218. lights[PHASE_LIGHT + 1].setSmoothBrightness(b, args.sampleTime * lightDivider.getDivision());
  219. lights[PHASE_LIGHT + 2].setBrightness(0.f);
  220. }
  221. else {
  222. lights[PHASE_LIGHT + 0].setBrightness(0.f);
  223. lights[PHASE_LIGHT + 1].setBrightness(0.f);
  224. lights[PHASE_LIGHT + 2].setBrightness(1.f);
  225. }
  226. lights[LINEAR_LIGHT].setBrightness(linear);
  227. lights[SOFT_LIGHT].setBrightness(soft);
  228. }
  229. }
  230. void paramsFromJson(json_t* rootJ) override {
  231. // In <2.0, there were no attenuverters, so set them to 1.0 in case they are not overwritten.
  232. params[POS_CV_PARAM].setValue(1.f);
  233. Module::paramsFromJson(rootJ);
  234. }
  235. json_t* dataToJson() override {
  236. json_t* rootJ = json_object();
  237. // Merge wavetable
  238. json_t* wavetableJ = wavetable.toJson();
  239. json_object_update(rootJ, wavetableJ);
  240. json_decref(wavetableJ);
  241. return rootJ;
  242. }
  243. void dataFromJson(json_t* rootJ) override {
  244. // wavetable
  245. wavetable.fromJson(rootJ);
  246. }
  247. };
  248. struct WTVCOWidget : ModuleWidget {
  249. WTVCOWidget(WTVCO* module) {
  250. setModule(module);
  251. setPanel(createPanel(asset::plugin(pluginInstance, "res/WTVCO.svg")));
  252. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
  253. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  254. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  255. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  256. addParam(createParamCentered<RoundLargeBlackKnob>(mm2px(Vec(8.915, 56.388)), module, WTVCO::FREQ_PARAM));
  257. addParam(createParamCentered<RoundLargeBlackKnob>(mm2px(Vec(26.645, 56.388)), module, WTVCO::POS_PARAM));
  258. addParam(createParamCentered<Trimpot>(mm2px(Vec(6.897, 80.603)), module, WTVCO::FM_PARAM));
  259. addParam(createLightParamCentered<VCVLightLatch<MediumSimpleLight<WhiteLight>>>(mm2px(Vec(17.734, 80.603)), module, WTVCO::LINEAR_PARAM, WTVCO::LINEAR_LIGHT));
  260. addParam(createParamCentered<Trimpot>(mm2px(Vec(28.571, 80.603)), module, WTVCO::POS_CV_PARAM));
  261. addParam(createLightParamCentered<VCVLightLatch<MediumSimpleLight<WhiteLight>>>(mm2px(Vec(17.734, 96.859)), module, WTVCO::SOFT_PARAM, WTVCO::SOFT_LIGHT));
  262. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(6.897, 96.813)), module, WTVCO::FM_INPUT));
  263. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(28.571, 96.859)), module, WTVCO::POS_INPUT));
  264. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(6.897, 113.115)), module, WTVCO::PITCH_INPUT));
  265. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(17.734, 113.115)), module, WTVCO::SYNC_INPUT));
  266. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(28.571, 113.115)), module, WTVCO::WAVE_OUTPUT));
  267. addChild(createLightCentered<SmallLight<RedGreenBlueLight>>(mm2px(Vec(17.733, 49.409)), module, WTVCO::PHASE_LIGHT));
  268. WTDisplay<WTVCO>* display = createWidget<WTDisplay<WTVCO>>(mm2px(Vec(0.004, 13.04)));
  269. display->box.size = mm2px(Vec(35.56, 29.224));
  270. display->module = module;
  271. addChild(display);
  272. }
  273. void appendContextMenu(Menu* menu) override {
  274. WTVCO* module = dynamic_cast<WTVCO*>(this->module);
  275. assert(module);
  276. menu->addChild(new MenuSeparator);
  277. module->wavetable.appendContextMenu(menu);
  278. }
  279. };
  280. Model* modelVCO2 = createModel<WTVCO, WTVCOWidget>("VCO2");