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.

392 lines
13KB

  1. #include "plugin.hpp"
  2. #pragma GCC diagnostic push
  3. #ifndef __clang__
  4. #pragma GCC diagnostic ignored "-Wsuggest-override"
  5. #endif
  6. #include "plaits/dsp/voice.h"
  7. #pragma GCC diagnostic pop
  8. struct Plaits : Module {
  9. enum ParamIds {
  10. MODEL1_PARAM,
  11. MODEL2_PARAM,
  12. FREQ_PARAM,
  13. HARMONICS_PARAM,
  14. TIMBRE_PARAM,
  15. MORPH_PARAM,
  16. TIMBRE_CV_PARAM,
  17. FREQ_CV_PARAM,
  18. MORPH_CV_PARAM,
  19. LPG_COLOR_PARAM,
  20. LPG_DECAY_PARAM,
  21. NUM_PARAMS
  22. };
  23. enum InputIds {
  24. ENGINE_INPUT,
  25. TIMBRE_INPUT,
  26. FREQ_INPUT,
  27. MORPH_INPUT,
  28. HARMONICS_INPUT,
  29. TRIGGER_INPUT,
  30. LEVEL_INPUT,
  31. NOTE_INPUT,
  32. NUM_INPUTS
  33. };
  34. enum OutputIds {
  35. OUT_OUTPUT,
  36. AUX_OUTPUT,
  37. NUM_OUTPUTS
  38. };
  39. enum LightIds {
  40. ENUMS(MODEL_LIGHT, 8 * 2),
  41. NUM_LIGHTS
  42. };
  43. plaits::Voice voice[16];
  44. plaits::Patch patch = {};
  45. char shared_buffer[16][16384] = {};
  46. float triPhase = 0.f;
  47. dsp::SampleRateConverter<16 * 2> outputSrc;
  48. dsp::DoubleRingBuffer<dsp::Frame<16 * 2>, 256> outputBuffer;
  49. bool lowCpu = false;
  50. dsp::BooleanTrigger model1Trigger;
  51. dsp::BooleanTrigger model2Trigger;
  52. Plaits() {
  53. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  54. configButton(MODEL1_PARAM, "Pitched models");
  55. configButton(MODEL2_PARAM, "Noise/percussive models");
  56. configParam(FREQ_PARAM, -4.0, 4.0, 0.0, "Frequency", " semitones", 0.f, 12.f);
  57. configParam(HARMONICS_PARAM, 0.0, 1.0, 0.5, "Harmonics", "%", 0.f, 100.f);
  58. configParam(TIMBRE_PARAM, 0.0, 1.0, 0.5, "Timbre", "%", 0.f, 100.f);
  59. configParam(LPG_COLOR_PARAM, 0.0, 1.0, 0.5, "Lowpass gate response", "%", 0.f, 100.f);
  60. configParam(MORPH_PARAM, 0.0, 1.0, 0.5, "Morph", "%", 0.f, 100.f);
  61. configParam(LPG_DECAY_PARAM, 0.0, 1.0, 0.5, "Lowpass gate decay", "%", 0.f, 100.f);
  62. configParam(TIMBRE_CV_PARAM, -1.0, 1.0, 0.0, "Timbre CV");
  63. configParam(FREQ_CV_PARAM, -1.0, 1.0, 0.0, "Frequency CV");
  64. configParam(MORPH_CV_PARAM, -1.0, 1.0, 0.0, "Morph CV");
  65. configInput(ENGINE_INPUT, "Model");
  66. configInput(TIMBRE_INPUT, "Timbre");
  67. configInput(FREQ_INPUT, "FM");
  68. configInput(MORPH_INPUT, "Morph");
  69. configInput(HARMONICS_INPUT, "Harmonics");
  70. configInput(TRIGGER_INPUT, "Trigger");
  71. configInput(LEVEL_INPUT, "Level");
  72. configInput(NOTE_INPUT, "Pitch (1V/oct)");
  73. configOutput(OUT_OUTPUT, "Main");
  74. configOutput(AUX_OUTPUT, "Auxiliary");
  75. for (int i = 0; i < 16; i++) {
  76. stmlib::BufferAllocator allocator(shared_buffer[i], sizeof(shared_buffer[i]));
  77. voice[i].Init(&allocator);
  78. }
  79. onReset();
  80. }
  81. void onReset() override {
  82. patch.engine = 0;
  83. patch.lpg_colour = 0.5f;
  84. patch.decay = 0.5f;
  85. }
  86. void onRandomize() override {
  87. patch.engine = random::u32() % 16;
  88. }
  89. json_t* dataToJson() override {
  90. json_t* rootJ = json_object();
  91. json_object_set_new(rootJ, "lowCpu", json_boolean(lowCpu));
  92. json_object_set_new(rootJ, "model", json_integer(patch.engine));
  93. return rootJ;
  94. }
  95. void dataFromJson(json_t* rootJ) override {
  96. json_t* lowCpuJ = json_object_get(rootJ, "lowCpu");
  97. if (lowCpuJ)
  98. lowCpu = json_boolean_value(lowCpuJ);
  99. json_t* modelJ = json_object_get(rootJ, "model");
  100. if (modelJ)
  101. patch.engine = json_integer_value(modelJ);
  102. // Legacy <=1.0.2
  103. json_t* lpgColorJ = json_object_get(rootJ, "lpgColor");
  104. if (lpgColorJ)
  105. params[LPG_COLOR_PARAM].setValue(json_number_value(lpgColorJ));
  106. // Legacy <=1.0.2
  107. json_t* decayJ = json_object_get(rootJ, "decay");
  108. if (decayJ)
  109. params[LPG_DECAY_PARAM].setValue(json_number_value(decayJ));
  110. }
  111. void process(const ProcessArgs& args) override {
  112. int channels = std::max(inputs[NOTE_INPUT].getChannels(), 1);
  113. if (outputBuffer.empty()) {
  114. const int blockSize = 12;
  115. // Model buttons
  116. if (model1Trigger.process(params[MODEL1_PARAM].getValue())) {
  117. if (patch.engine >= 8) {
  118. patch.engine -= 8;
  119. }
  120. else {
  121. patch.engine = (patch.engine + 1) % 8;
  122. }
  123. }
  124. if (model2Trigger.process(params[MODEL2_PARAM].getValue())) {
  125. if (patch.engine < 8) {
  126. patch.engine += 8;
  127. }
  128. else {
  129. patch.engine = (patch.engine + 1) % 8 + 8;
  130. }
  131. }
  132. // Model lights
  133. // Pulse light at 2 Hz
  134. triPhase += 2.f * args.sampleTime * blockSize;
  135. if (triPhase >= 1.f)
  136. triPhase -= 1.f;
  137. float tri = (triPhase < 0.5f) ? triPhase * 2.f : (1.f - triPhase) * 2.f;
  138. // Get active engines of all voice channels
  139. bool activeEngines[16] = {};
  140. bool pulse = false;
  141. for (int c = 0; c < channels; c++) {
  142. int activeEngine = voice[c].active_engine();
  143. activeEngines[activeEngine] = true;
  144. // Pulse the light if at least one voice is using a different engine.
  145. if (activeEngine != patch.engine)
  146. pulse = true;
  147. }
  148. // Set model lights
  149. for (int i = 0; i < 16; i++) {
  150. // Transpose the [light][color] table
  151. int lightId = (i % 8) * 2 + (i / 8);
  152. float brightness = activeEngines[i];
  153. if (patch.engine == i && pulse)
  154. brightness = tri;
  155. lights[MODEL_LIGHT + lightId].setBrightness(brightness);
  156. }
  157. // Calculate pitch for lowCpu mode if needed
  158. float pitch = params[FREQ_PARAM].getValue();
  159. if (lowCpu)
  160. pitch += std::log2(48000.f * args.sampleTime);
  161. // Update patch
  162. patch.note = 60.f + pitch * 12.f;
  163. patch.harmonics = params[HARMONICS_PARAM].getValue();
  164. patch.timbre = params[TIMBRE_PARAM].getValue();
  165. patch.morph = params[MORPH_PARAM].getValue();
  166. patch.lpg_colour = params[LPG_COLOR_PARAM].getValue();
  167. patch.decay = params[LPG_DECAY_PARAM].getValue();
  168. patch.frequency_modulation_amount = params[FREQ_CV_PARAM].getValue();
  169. patch.timbre_modulation_amount = params[TIMBRE_CV_PARAM].getValue();
  170. patch.morph_modulation_amount = params[MORPH_CV_PARAM].getValue();
  171. // Render output buffer for each voice
  172. dsp::Frame<16 * 2> outputFrames[blockSize];
  173. for (int c = 0; c < channels; c++) {
  174. // Construct modulations
  175. plaits::Modulations modulations;
  176. modulations.engine = inputs[ENGINE_INPUT].getPolyVoltage(c) / 5.f;
  177. modulations.note = inputs[NOTE_INPUT].getVoltage(c) * 12.f;
  178. modulations.frequency = inputs[FREQ_INPUT].getPolyVoltage(c) * 6.f;
  179. modulations.harmonics = inputs[HARMONICS_INPUT].getPolyVoltage(c) / 5.f;
  180. modulations.timbre = inputs[TIMBRE_INPUT].getPolyVoltage(c) / 8.f;
  181. modulations.morph = inputs[MORPH_INPUT].getPolyVoltage(c) / 8.f;
  182. // Triggers at around 0.7 V
  183. modulations.trigger = inputs[TRIGGER_INPUT].getPolyVoltage(c) / 3.f;
  184. modulations.level = inputs[LEVEL_INPUT].getPolyVoltage(c) / 8.f;
  185. modulations.frequency_patched = inputs[FREQ_INPUT].isConnected();
  186. modulations.timbre_patched = inputs[TIMBRE_INPUT].isConnected();
  187. modulations.morph_patched = inputs[MORPH_INPUT].isConnected();
  188. modulations.trigger_patched = inputs[TRIGGER_INPUT].isConnected();
  189. modulations.level_patched = inputs[LEVEL_INPUT].isConnected();
  190. // Render frames
  191. plaits::Voice::Frame output[blockSize];
  192. voice[c].Render(patch, modulations, output, blockSize);
  193. // Convert output to frames
  194. for (int i = 0; i < blockSize; i++) {
  195. outputFrames[i].samples[c * 2 + 0] = output[i].out / 32768.f;
  196. outputFrames[i].samples[c * 2 + 1] = output[i].aux / 32768.f;
  197. }
  198. }
  199. // Convert output
  200. if (lowCpu) {
  201. int len = std::min((int) outputBuffer.capacity(), blockSize);
  202. std::memcpy(outputBuffer.endData(), outputFrames, len * sizeof(outputFrames[0]));
  203. outputBuffer.endIncr(len);
  204. }
  205. else {
  206. outputSrc.setRates(48000, (int) args.sampleRate);
  207. int inLen = blockSize;
  208. int outLen = outputBuffer.capacity();
  209. outputSrc.setChannels(channels * 2);
  210. outputSrc.process(outputFrames, &inLen, outputBuffer.endData(), &outLen);
  211. outputBuffer.endIncr(outLen);
  212. }
  213. }
  214. // Set output
  215. if (!outputBuffer.empty()) {
  216. dsp::Frame<16 * 2> outputFrame = outputBuffer.shift();
  217. for (int c = 0; c < channels; c++) {
  218. // Inverting op-amp on outputs
  219. outputs[OUT_OUTPUT].setVoltage(-outputFrame.samples[c * 2 + 0] * 5.f, c);
  220. outputs[AUX_OUTPUT].setVoltage(-outputFrame.samples[c * 2 + 1] * 5.f, c);
  221. }
  222. }
  223. outputs[OUT_OUTPUT].setChannels(channels);
  224. outputs[AUX_OUTPUT].setChannels(channels);
  225. }
  226. };
  227. static const std::string modelLabels[16] = {
  228. "Pair of classic waveforms",
  229. "Waveshaping oscillator",
  230. "Two operator FM",
  231. "Granular formant oscillator",
  232. "Harmonic oscillator",
  233. "Wavetable oscillator",
  234. "Chords",
  235. "Vowel and speech synthesis",
  236. "Granular cloud",
  237. "Filtered noise",
  238. "Particle noise",
  239. "Inharmonic string modeling",
  240. "Modal resonator",
  241. "Analog bass drum",
  242. "Analog snare drum",
  243. "Analog hi-hat",
  244. };
  245. struct PlaitsWidget : ModuleWidget {
  246. bool lpgMode = false;
  247. PlaitsWidget(Plaits* module) {
  248. setModule(module);
  249. setPanel(Svg::load(asset::plugin(pluginInstance, "res/Plaits.svg")));
  250. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
  251. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  252. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  253. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  254. addParam(createParam<TL1105>(mm2px(Vec(23.32685, 14.6539)), module, Plaits::MODEL1_PARAM));
  255. addParam(createParam<TL1105>(mm2px(Vec(32.22764, 14.6539)), module, Plaits::MODEL2_PARAM));
  256. addParam(createParam<Rogan3PSWhite>(mm2px(Vec(3.1577, 20.21088)), module, Plaits::FREQ_PARAM));
  257. addParam(createParam<Rogan3PSWhite>(mm2px(Vec(39.3327, 20.21088)), module, Plaits::HARMONICS_PARAM));
  258. addParam(createParam<Rogan1PSWhite>(mm2px(Vec(4.04171, 49.6562)), module, Plaits::TIMBRE_PARAM));
  259. addParam(createParam<Rogan1PSWhite>(mm2px(Vec(42.71716, 49.6562)), module, Plaits::MORPH_PARAM));
  260. addParam(createParam<Trimpot>(mm2px(Vec(7.88712, 77.60705)), module, Plaits::TIMBRE_CV_PARAM));
  261. addParam(createParam<Trimpot>(mm2px(Vec(27.2245, 77.60705)), module, Plaits::FREQ_CV_PARAM));
  262. addParam(createParam<Trimpot>(mm2px(Vec(46.56189, 77.60705)), module, Plaits::MORPH_CV_PARAM));
  263. ParamWidget* lpgColorParam = createParam<Rogan1PSBlue>(mm2px(Vec(4.04171, 49.6562)), module, Plaits::LPG_COLOR_PARAM);
  264. lpgColorParam->hide();
  265. addParam(lpgColorParam);
  266. ParamWidget* decayParam = createParam<Rogan1PSBlue>(mm2px(Vec(42.71716, 49.6562)), module, Plaits::LPG_DECAY_PARAM);
  267. decayParam->hide();
  268. addParam(decayParam);
  269. addInput(createInput<PJ301MPort>(mm2px(Vec(3.31381, 92.48067)), module, Plaits::ENGINE_INPUT));
  270. addInput(createInput<PJ301MPort>(mm2px(Vec(14.75983, 92.48067)), module, Plaits::TIMBRE_INPUT));
  271. addInput(createInput<PJ301MPort>(mm2px(Vec(26.20655, 92.48067)), module, Plaits::FREQ_INPUT));
  272. addInput(createInput<PJ301MPort>(mm2px(Vec(37.65257, 92.48067)), module, Plaits::MORPH_INPUT));
  273. addInput(createInput<PJ301MPort>(mm2px(Vec(49.0986, 92.48067)), module, Plaits::HARMONICS_INPUT));
  274. addInput(createInput<PJ301MPort>(mm2px(Vec(3.31381, 107.08103)), module, Plaits::TRIGGER_INPUT));
  275. addInput(createInput<PJ301MPort>(mm2px(Vec(14.75983, 107.08103)), module, Plaits::LEVEL_INPUT));
  276. addInput(createInput<PJ301MPort>(mm2px(Vec(26.20655, 107.08103)), module, Plaits::NOTE_INPUT));
  277. addOutput(createOutput<PJ301MPort>(mm2px(Vec(37.65257, 107.08103)), module, Plaits::OUT_OUTPUT));
  278. addOutput(createOutput<PJ301MPort>(mm2px(Vec(49.0986, 107.08103)), module, Plaits::AUX_OUTPUT));
  279. addChild(createLight<MediumLight<GreenRedLight>>(mm2px(Vec(28.79498, 23.31649)), module, Plaits::MODEL_LIGHT + 0 * 2));
  280. addChild(createLight<MediumLight<GreenRedLight>>(mm2px(Vec(28.79498, 28.71704)), module, Plaits::MODEL_LIGHT + 1 * 2));
  281. addChild(createLight<MediumLight<GreenRedLight>>(mm2px(Vec(28.79498, 34.1162)), module, Plaits::MODEL_LIGHT + 2 * 2));
  282. addChild(createLight<MediumLight<GreenRedLight>>(mm2px(Vec(28.79498, 39.51675)), module, Plaits::MODEL_LIGHT + 3 * 2));
  283. addChild(createLight<MediumLight<GreenRedLight>>(mm2px(Vec(28.79498, 44.91731)), module, Plaits::MODEL_LIGHT + 4 * 2));
  284. addChild(createLight<MediumLight<GreenRedLight>>(mm2px(Vec(28.79498, 50.31785)), module, Plaits::MODEL_LIGHT + 5 * 2));
  285. addChild(createLight<MediumLight<GreenRedLight>>(mm2px(Vec(28.79498, 55.71771)), module, Plaits::MODEL_LIGHT + 6 * 2));
  286. addChild(createLight<MediumLight<GreenRedLight>>(mm2px(Vec(28.79498, 61.11827)), module, Plaits::MODEL_LIGHT + 7 * 2));
  287. }
  288. void appendContextMenu(Menu* menu) override {
  289. Plaits* module = dynamic_cast<Plaits*>(this->module);
  290. menu->addChild(new MenuSeparator);
  291. menu->addChild(createBoolPtrMenuItem("Low CPU (disable resampling)", &module->lowCpu));
  292. menu->addChild(createBoolMenuItem("Edit LPG response/decay",
  293. [=]() {return this->getLpgMode();},
  294. [=](bool val) {this->setLpgMode(val);}
  295. ));
  296. menu->addChild(new MenuSeparator);
  297. menu->addChild(createMenuLabel("Pitched models"));
  298. for (int i = 0; i < 8; i++) {
  299. menu->addChild(createCheckMenuItem(modelLabels[i],
  300. [=]() {return module->patch.engine == i;},
  301. [=]() {module->patch.engine = i;}
  302. ));
  303. }
  304. menu->addChild(new MenuSeparator);
  305. menu->addChild(createMenuLabel("Noise/percussive models"));
  306. for (int i = 8; i < 16; i++) {
  307. menu->addChild(createCheckMenuItem(modelLabels[i],
  308. [=]() {return module->patch.engine == i;},
  309. [=]() {module->patch.engine = i;}
  310. ));
  311. }
  312. }
  313. void setLpgMode(bool lpgMode) {
  314. // ModuleWidget::getParam() doesn't work if the ModuleWidget doesn't have a module.
  315. if (!module)
  316. return;
  317. if (lpgMode) {
  318. getParam(Plaits::MORPH_PARAM)->hide();
  319. getParam(Plaits::TIMBRE_PARAM)->hide();
  320. getParam(Plaits::LPG_DECAY_PARAM)->show();
  321. getParam(Plaits::LPG_COLOR_PARAM)->show();
  322. }
  323. else {
  324. getParam(Plaits::MORPH_PARAM)->show();
  325. getParam(Plaits::TIMBRE_PARAM)->show();
  326. getParam(Plaits::LPG_DECAY_PARAM)->hide();
  327. getParam(Plaits::LPG_COLOR_PARAM)->hide();
  328. }
  329. this->lpgMode = lpgMode;
  330. }
  331. bool getLpgMode() {
  332. return this->lpgMode;
  333. }
  334. };
  335. Model* modelPlaits = createModel<Plaits, PlaitsWidget>("Plaits");