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.

390 lines
14KB

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