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.

396 lines
13KB

  1. #include "plugin.hpp"
  2. using simd::float_4;
  3. struct ADSR : Module {
  4. enum ParamIds {
  5. ATTACK_PARAM,
  6. DECAY_PARAM,
  7. SUSTAIN_PARAM,
  8. RELEASE_PARAM,
  9. // added in 2.0
  10. ATTACK_CV_PARAM,
  11. DECAY_CV_PARAM,
  12. SUSTAIN_CV_PARAM,
  13. RELEASE_CV_PARAM,
  14. PUSH_PARAM,
  15. NUM_PARAMS
  16. };
  17. enum InputIds {
  18. ATTACK_INPUT,
  19. DECAY_INPUT,
  20. SUSTAIN_INPUT,
  21. RELEASE_INPUT,
  22. GATE_INPUT,
  23. RETRIG_INPUT,
  24. NUM_INPUTS
  25. };
  26. enum OutputIds {
  27. ENVELOPE_OUTPUT,
  28. NUM_OUTPUTS
  29. };
  30. enum LightIds {
  31. ATTACK_LIGHT,
  32. DECAY_LIGHT,
  33. SUSTAIN_LIGHT,
  34. RELEASE_LIGHT,
  35. PUSH_LIGHT,
  36. NUM_LIGHTS
  37. };
  38. static constexpr float MIN_TIME = 1e-3f;
  39. static constexpr float MAX_TIME = 10.f;
  40. static constexpr float LAMBDA_BASE = MAX_TIME / MIN_TIME;
  41. static constexpr float ATT_TARGET = 1.2f;
  42. int channels = 1;
  43. float_4 gate[4] = {};
  44. float_4 attacking[4] = {};
  45. float_4 env[4] = {};
  46. dsp::TSchmittTrigger<float_4> trigger[4];
  47. dsp::ClockDivider cvDivider;
  48. float_4 attackLambda[4] = {};
  49. float_4 decayLambda[4] = {};
  50. float_4 releaseLambda[4] = {};
  51. float_4 sustain[4] = {};
  52. dsp::ClockDivider lightDivider;
  53. ADSR() {
  54. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  55. configParam(ATTACK_PARAM, 0.f, 1.f, 0.5f, "Attack", " ms", LAMBDA_BASE, MIN_TIME * 1000);
  56. configParam(DECAY_PARAM, 0.f, 1.f, 0.5f, "Decay", " ms", LAMBDA_BASE, MIN_TIME * 1000);
  57. configParam(SUSTAIN_PARAM, 0.f, 1.f, 0.5f, "Sustain", "%", 0, 100);
  58. configParam(RELEASE_PARAM, 0.f, 1.f, 0.5f, "Release", " ms", LAMBDA_BASE, MIN_TIME * 1000);
  59. configParam(ATTACK_CV_PARAM, -1.f, 1.f, 0.f, "Attack CV", "%", 0, 100);
  60. configParam(DECAY_CV_PARAM, -1.f, 1.f, 0.f, "Decay CV", "%", 0, 100);
  61. configParam(SUSTAIN_CV_PARAM, -1.f, 1.f, 0.f, "Sustain CV", "%", 0, 100);
  62. configParam(RELEASE_CV_PARAM, -1.f, 1.f, 0.f, "Release CV", "%", 0, 100);
  63. configButton(PUSH_PARAM, "Push");
  64. configInput(ATTACK_INPUT, "Attack");
  65. configInput(DECAY_INPUT, "Decay");
  66. configInput(SUSTAIN_INPUT, "Sustain");
  67. configInput(RELEASE_INPUT, "Release");
  68. configInput(GATE_INPUT, "Gate");
  69. configInput(RETRIG_INPUT, "Retrigger");
  70. configOutput(ENVELOPE_OUTPUT, "Envelope");
  71. cvDivider.setDivision(16);
  72. lightDivider.setDivision(128);
  73. }
  74. void process(const ProcessArgs& args) override {
  75. // 0.16-0.19 us serial
  76. // 0.23 us serial with all lambdas computed
  77. // 0.15-0.18 us serial with all lambdas computed with SSE
  78. channels = std::max(1, inputs[GATE_INPUT].getChannels());
  79. // Compute lambdas
  80. if (cvDivider.process()) {
  81. float attackParam = params[ATTACK_PARAM].getValue();
  82. float decayParam = params[DECAY_PARAM].getValue();
  83. float sustainParam = params[SUSTAIN_PARAM].getValue();
  84. float releaseParam = params[RELEASE_PARAM].getValue();
  85. float attackCvParam = params[ATTACK_CV_PARAM].getValue();
  86. float decayCvParam = params[DECAY_CV_PARAM].getValue();
  87. float sustainCvParam = params[SUSTAIN_CV_PARAM].getValue();
  88. float releaseCvParam = params[RELEASE_CV_PARAM].getValue();
  89. for (int c = 0; c < channels; c += 4) {
  90. // CV
  91. float_4 attack = attackParam + inputs[ATTACK_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f * attackCvParam;
  92. float_4 decay = decayParam + inputs[DECAY_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f * decayCvParam;
  93. float_4 sustain = sustainParam + inputs[SUSTAIN_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f * sustainCvParam;
  94. float_4 release = releaseParam + inputs[RELEASE_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f * releaseCvParam;
  95. attack = simd::clamp(attack, 0.f, 1.f);
  96. decay = simd::clamp(decay, 0.f, 1.f);
  97. sustain = simd::clamp(sustain, 0.f, 1.f);
  98. release = simd::clamp(release, 0.f, 1.f);
  99. attackLambda[c / 4] = simd::pow(LAMBDA_BASE, -attack) / MIN_TIME;
  100. decayLambda[c / 4] = simd::pow(LAMBDA_BASE, -decay) / MIN_TIME;
  101. releaseLambda[c / 4] = simd::pow(LAMBDA_BASE, -release) / MIN_TIME;
  102. this->sustain[c / 4] = sustain;
  103. }
  104. }
  105. bool push = (params[PUSH_PARAM].getValue() > 0.f);
  106. for (int c = 0; c < channels; c += 4) {
  107. // Gate
  108. float_4 oldGate = gate[c / 4];
  109. if (push) {
  110. gate[c / 4] = float_4::mask();
  111. }
  112. else {
  113. gate[c / 4] = inputs[GATE_INPUT].getVoltageSimd<float_4>(c) >= 1.f;
  114. }
  115. attacking[c / 4] |= (gate[c / 4] & ~oldGate);
  116. // Retrigger
  117. float_4 triggered = trigger[c / 4].process(inputs[RETRIG_INPUT].getPolyVoltageSimd<float_4>(c));
  118. attacking[c / 4] |= triggered;
  119. // Turn off attacking state if gate is LOW
  120. attacking[c / 4] &= gate[c / 4];
  121. // Get target and lambda for exponential decay
  122. float_4 target = simd::ifelse(attacking[c / 4], ATT_TARGET, simd::ifelse(gate[c / 4], sustain[c / 4], 0.f));
  123. float_4 lambda = simd::ifelse(attacking[c / 4], attackLambda[c / 4], simd::ifelse(gate[c / 4], decayLambda[c / 4], releaseLambda[c / 4]));
  124. // Adjust env
  125. env[c / 4] += (target - env[c / 4]) * lambda * args.sampleTime;
  126. // Turn off attacking state if envelope is HIGH
  127. attacking[c / 4] &= (env[c / 4] < 1.f);
  128. // Set output
  129. outputs[ENVELOPE_OUTPUT].setVoltageSimd(10.f * env[c / 4], c);
  130. }
  131. outputs[ENVELOPE_OUTPUT].setChannels(channels);
  132. // Lights
  133. if (lightDivider.process()) {
  134. lights[ATTACK_LIGHT].setBrightness(0);
  135. lights[DECAY_LIGHT].setBrightness(0);
  136. lights[SUSTAIN_LIGHT].setBrightness(0);
  137. lights[RELEASE_LIGHT].setBrightness(0);
  138. for (int c = 0; c < channels; c += 4) {
  139. const float epsilon = 0.01f;
  140. float_4 sustaining = (sustain[c / 4] <= env[c / 4]) & (env[c / 4] < sustain[c / 4] + epsilon);
  141. float_4 resting = (env[c / 4] < epsilon);
  142. if (simd::movemask(gate[c / 4] & attacking[c / 4]))
  143. lights[ATTACK_LIGHT].setBrightness(1);
  144. if (simd::movemask(gate[c / 4] & ~attacking[c / 4] & ~sustaining))
  145. lights[DECAY_LIGHT].setBrightness(1);
  146. if (simd::movemask(gate[c / 4] & ~attacking[c / 4] & sustaining))
  147. lights[SUSTAIN_LIGHT].setBrightness(1);
  148. if (simd::movemask(~gate[c / 4] & ~resting))
  149. lights[RELEASE_LIGHT].setBrightness(1);
  150. }
  151. // Push button light
  152. bool anyGate = false;
  153. for (int c = 0; c < channels; c += 4)
  154. anyGate = anyGate || simd::movemask(gate[c / 4]);
  155. lights[PUSH_LIGHT].setBrightness(anyGate);
  156. }
  157. }
  158. void paramsFromJson(json_t* rootJ) override {
  159. // These attenuators didn't exist in version <2.0, so set to 1 in case they are not overwritten.
  160. params[ATTACK_CV_PARAM].setValue(1.f);
  161. params[DECAY_CV_PARAM].setValue(1.f);
  162. params[SUSTAIN_CV_PARAM].setValue(1.f);
  163. params[RELEASE_CV_PARAM].setValue(1.f);
  164. Module::paramsFromJson(rootJ);
  165. }
  166. };
  167. struct ADSRDisplay : LedDisplay {
  168. ADSR* module;
  169. void drawLayer(const DrawArgs& args, int layer) override {
  170. if (layer == 1) {
  171. nvgScissor(args.vg, RECT_ARGS(args.clipBox));
  172. Rect gridBox = getBox().zeroPos().shrink(Vec(0, 6.5));
  173. Rect r = gridBox;
  174. r.pos.x += 4.5;
  175. r.size.x -= 4.5;
  176. Vec p;
  177. // Get parameters
  178. float attTime = module ? 1 / module->attackLambda[0][0] : 1.f;
  179. float decTime = module ? 1 / module->decayLambda[0][0] : 1.f;
  180. float relTime = module ? 1 / module->releaseLambda[0][0] : 1.f;
  181. float totalTime = attTime + decTime + relTime;
  182. attTime /= totalTime;
  183. decTime /= totalTime;
  184. relTime /= totalTime;
  185. float sustain = module ? module->sustain[0][0] : 0.5f;
  186. int channels = module ? module->channels : 1;
  187. // Grid
  188. nvgStrokeWidth(args.vg, 1.0);
  189. nvgStrokeColor(args.vg, nvgRGBAf(1, 1, 1, 0.20));
  190. nvgBeginPath(args.vg);
  191. // Left
  192. p = r.getTopLeft();
  193. nvgMoveTo(args.vg, VEC_ARGS(p));
  194. p = r.getBottomLeft();
  195. nvgLineTo(args.vg, VEC_ARGS(p));
  196. // Top
  197. p = gridBox.getTopLeft();
  198. nvgMoveTo(args.vg, VEC_ARGS(p));
  199. p = gridBox.getTopRight();
  200. nvgLineTo(args.vg, VEC_ARGS(p));
  201. // Bottom
  202. p = gridBox.getBottomLeft();
  203. nvgMoveTo(args.vg, VEC_ARGS(p));
  204. p = gridBox.getBottomRight();
  205. nvgLineTo(args.vg, VEC_ARGS(p));
  206. // Attack
  207. p = r.interpolate(Vec(attTime, 0));
  208. nvgMoveTo(args.vg, VEC_ARGS(p));
  209. p = r.interpolate(Vec(attTime, 1));
  210. nvgLineTo(args.vg, VEC_ARGS(p));
  211. // Decay
  212. p = r.interpolate(Vec(attTime + decTime, 1 - sustain));
  213. nvgMoveTo(args.vg, VEC_ARGS(p));
  214. p = r.interpolate(Vec(attTime + decTime, 1));
  215. nvgLineTo(args.vg, VEC_ARGS(p));
  216. // Sustain
  217. p = r.interpolate(Vec(attTime, 1 - sustain));
  218. nvgMoveTo(args.vg, VEC_ARGS(p));
  219. p = r.interpolate(Vec(1, 1 - sustain));
  220. nvgLineTo(args.vg, VEC_ARGS(p));
  221. nvgStroke(args.vg);
  222. // Line
  223. nvgStrokeColor(args.vg, SCHEME_YELLOW);
  224. nvgBeginPath(args.vg);
  225. Vec c1, c2;
  226. // Begin
  227. p = r.getBottomLeft();
  228. nvgMoveTo(args.vg, VEC_ARGS(p));
  229. // Attack
  230. const int I = 10;
  231. for (int i = 1; i <= I; i++) {
  232. float phase = float(i) / I;
  233. // Distribute points more evenly to prevent artifacts
  234. phase = std::pow(phase, 2);
  235. float env = phaseToEnv(phase);
  236. p = r.interpolate(Vec(attTime * phase, 1 - env));
  237. nvgLineTo(args.vg, VEC_ARGS(p));
  238. }
  239. // Decay
  240. for (int i = 1; i <= I; i++) {
  241. float phase = float(i) / I;
  242. phase = std::pow(phase, 2);
  243. float env = 1 - phaseToEnv(phase) * (1 - sustain);
  244. p = r.interpolate(Vec(attTime + decTime * phase, 1 - env));
  245. nvgLineTo(args.vg, VEC_ARGS(p));
  246. }
  247. // Release
  248. for (int i = 1; i <= I; i++) {
  249. float phase = float(i) / I;
  250. phase = std::pow(phase, 2);
  251. float env = (1 - phaseToEnv(phase)) * sustain;
  252. p = r.interpolate(Vec(attTime + decTime + relTime * phase, 1 - env));
  253. nvgLineTo(args.vg, VEC_ARGS(p));
  254. }
  255. nvgStroke(args.vg);
  256. // Sustain circle
  257. {
  258. nvgStrokeColor(args.vg, SCHEME_YELLOW);
  259. nvgBeginPath(args.vg);
  260. p = r.interpolate(Vec(attTime + decTime, 1 - sustain));
  261. nvgCircle(args.vg, VEC_ARGS(p), 2.5);
  262. nvgFillColor(args.vg, nvgRGB(0x22, 0x22, 0x22));
  263. nvgFill(args.vg);
  264. nvgStroke(args.vg);
  265. }
  266. // Position circle
  267. for (int c = 0; c < channels; c++) {
  268. float env = module ? module->env[c / 4][c % 4] : 0.f;
  269. if (env > 0.01f) {
  270. bool attacking = module ? (simd::movemask(module->attacking[c / 4]) & (1 << (c % 4))) : false;
  271. bool gate = module ? module->gate[c / 4][c % 4] : false;
  272. if (attacking) {
  273. float phase = envToPhase(env);
  274. p = r.interpolate(Vec(attTime * phase, 1 - env));
  275. }
  276. else if (gate) {
  277. float phase = envToPhase(1 - (env - sustain) / (1 - sustain));
  278. p = r.interpolate(Vec(attTime + decTime * phase, 1 - env));
  279. }
  280. else {
  281. env = std::min(env, sustain);
  282. float phase = envToPhase(1 - env / sustain);
  283. p = r.interpolate(Vec(attTime + decTime + relTime * phase, 1 - env));
  284. }
  285. nvgBeginPath(args.vg);
  286. nvgCircle(args.vg, VEC_ARGS(p), 2.5);
  287. nvgFillColor(args.vg, nvgRGBAf(1, 1, 1, 0.66));
  288. nvgFill(args.vg);
  289. }
  290. }
  291. nvgResetScissor(args.vg);
  292. }
  293. LedDisplay::drawLayer(args, layer);
  294. }
  295. // Optimized for appearance, not accuracy to ADSR DSP.
  296. static constexpr float TARGET = 1.1f;
  297. static constexpr float LAMBDA = 2.3978952727983702f; //-std::log(1 - 1 / TARGET);
  298. static float phaseToEnv(float phase) {
  299. return (1 - std::exp(-LAMBDA * phase)) * TARGET;
  300. }
  301. static float envToPhase(float env) {
  302. return -std::log(1 - env / TARGET) / LAMBDA;
  303. }
  304. };
  305. struct ADSRWidget : ModuleWidget {
  306. ADSRWidget(ADSR* module) {
  307. setModule(module);
  308. setPanel(createPanel(asset::plugin(pluginInstance, "res/ADSR.svg"), asset::plugin(pluginInstance, "res/ADSR-dark.svg")));
  309. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
  310. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  311. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  312. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  313. addParam(createLightParamCentered<VCVLightSlider<YellowLight>>(mm2px(Vec(6.604, 55.454)), module, ADSR::ATTACK_PARAM, ADSR::ATTACK_LIGHT));
  314. addParam(createLightParamCentered<VCVLightSlider<YellowLight>>(mm2px(Vec(17.441, 55.454)), module, ADSR::DECAY_PARAM, ADSR::DECAY_LIGHT));
  315. addParam(createLightParamCentered<VCVLightSlider<YellowLight>>(mm2px(Vec(28.279, 55.454)), module, ADSR::SUSTAIN_PARAM, ADSR::SUSTAIN_LIGHT));
  316. addParam(createLightParamCentered<VCVLightSlider<YellowLight>>(mm2px(Vec(39.116, 55.454)), module, ADSR::RELEASE_PARAM, ADSR::RELEASE_LIGHT));
  317. addParam(createParamCentered<Trimpot>(mm2px(Vec(6.604, 80.603)), module, ADSR::ATTACK_CV_PARAM));
  318. addParam(createParamCentered<Trimpot>(mm2px(Vec(17.441, 80.63)), module, ADSR::DECAY_CV_PARAM));
  319. addParam(createParamCentered<Trimpot>(mm2px(Vec(28.279, 80.603)), module, ADSR::SUSTAIN_CV_PARAM));
  320. addParam(createParamCentered<Trimpot>(mm2px(Vec(39.119, 80.603)), module, ADSR::RELEASE_CV_PARAM));
  321. addParam(createLightParamCentered<VCVLightBezel<WhiteLight>>(mm2px(Vec(6.604, 113.115)), module, ADSR::PUSH_PARAM, ADSR::PUSH_LIGHT));
  322. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(6.604, 96.882)), module, ADSR::ATTACK_INPUT));
  323. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(17.441, 96.859)), module, ADSR::DECAY_INPUT));
  324. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(28.279, 96.886)), module, ADSR::SUSTAIN_INPUT));
  325. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(39.119, 96.89)), module, ADSR::RELEASE_INPUT));
  326. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(17.441, 113.115)), module, ADSR::GATE_INPUT));
  327. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(28.279, 113.115)), module, ADSR::RETRIG_INPUT));
  328. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(39.119, 113.115)), module, ADSR::ENVELOPE_OUTPUT));
  329. ADSRDisplay* display = createWidget<ADSRDisplay>(mm2px(Vec(0.0, 13.039)));
  330. display->box.size = mm2px(Vec(45.72, 21.219));
  331. display->module = module;
  332. addChild(display);
  333. }
  334. };
  335. Model* modelADSR = createModel<ADSR, ADSRWidget>("ADSR");