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.

343 lines
11KB

  1. #include "plugin.hpp"
  2. struct BefacoADSREnvelope {
  3. enum Stage {
  4. STAGE_OFF,
  5. STAGE_ATTACK,
  6. STAGE_DECAY,
  7. STAGE_SUSTAIN,
  8. STAGE_RELEASE
  9. };
  10. Stage stage = STAGE_OFF;
  11. float env = 0.f;
  12. float releaseValue;
  13. float timeInCurrentStage = 0.f;
  14. float attackTime = 0.1, decayTime = 0.1, releaseTime = 0.1;
  15. float attackShape = 1.0, decayShape = 1.0, releaseShape = 1.0;
  16. float sustainLevel;
  17. BefacoADSREnvelope() { };
  18. void retrigger() {
  19. stage = STAGE_ATTACK;
  20. // get the linear value of the envelope
  21. timeInCurrentStage = attackTime * std::pow(env, 1.0f / attackShape);
  22. }
  23. void processTransitionsGateMode(const bool& gateHeld) {
  24. if (gateHeld) {
  25. // calculate stage transitions
  26. switch (stage) {
  27. case STAGE_OFF: {
  28. env = 0.0f;
  29. timeInCurrentStage = 0.f;
  30. stage = STAGE_ATTACK;
  31. break;
  32. }
  33. case STAGE_ATTACK: {
  34. if (env >= 1.f) {
  35. timeInCurrentStage = 0.f;
  36. stage = STAGE_DECAY;
  37. }
  38. break;
  39. }
  40. case STAGE_DECAY: {
  41. if (timeInCurrentStage >= decayTime) {
  42. timeInCurrentStage = 0.f;
  43. stage = STAGE_SUSTAIN;
  44. }
  45. break;
  46. }
  47. case STAGE_SUSTAIN: {
  48. break;
  49. }
  50. case STAGE_RELEASE: {
  51. stage = STAGE_ATTACK;
  52. timeInCurrentStage = attackTime * env;
  53. break;
  54. }
  55. }
  56. }
  57. else {
  58. if (stage == STAGE_ATTACK || stage == STAGE_DECAY || stage == STAGE_SUSTAIN) {
  59. timeInCurrentStage = 0.f;
  60. stage = STAGE_RELEASE;
  61. releaseValue = env;
  62. }
  63. else if (stage == STAGE_RELEASE) {
  64. if (timeInCurrentStage >= releaseTime) {
  65. stage = STAGE_OFF;
  66. timeInCurrentStage = 0.f;
  67. }
  68. }
  69. }
  70. }
  71. void processTransitionsTriggerMode(const bool& gateHeld) {
  72. // calculate stage transitions
  73. switch (stage) {
  74. case STAGE_ATTACK: {
  75. if (env >= 1.f) {
  76. timeInCurrentStage = 0.f;
  77. if (gateHeld) {
  78. stage = STAGE_DECAY;
  79. }
  80. else {
  81. stage = STAGE_RELEASE;
  82. releaseValue = 1.f;
  83. }
  84. }
  85. break;
  86. }
  87. case STAGE_DECAY: {
  88. if (timeInCurrentStage >= decayTime) {
  89. timeInCurrentStage = 0.f;
  90. if (gateHeld) {
  91. stage = STAGE_SUSTAIN;
  92. }
  93. else {
  94. stage = STAGE_RELEASE;
  95. releaseValue = env;
  96. }
  97. }
  98. break;
  99. }
  100. case STAGE_OFF:
  101. case STAGE_RELEASE:
  102. case STAGE_SUSTAIN: {
  103. break;
  104. }
  105. }
  106. if (!gateHeld) {
  107. if (stage == STAGE_DECAY || stage == STAGE_SUSTAIN) {
  108. timeInCurrentStage = 0.f;
  109. stage = STAGE_RELEASE;
  110. releaseValue = env;
  111. }
  112. else if (stage == STAGE_RELEASE) {
  113. if (timeInCurrentStage >= releaseTime) {
  114. stage = STAGE_OFF;
  115. timeInCurrentStage = 0.f;
  116. }
  117. }
  118. }
  119. }
  120. void evolveEnvelope(const float& sampleTime) {
  121. switch (stage) {
  122. case STAGE_OFF: {
  123. env = 0.0f;
  124. break;
  125. }
  126. case STAGE_ATTACK: {
  127. timeInCurrentStage += sampleTime;
  128. env = std::min(timeInCurrentStage / attackTime, 1.f);
  129. env = std::pow(env, attackShape);
  130. break;
  131. }
  132. case STAGE_DECAY: {
  133. timeInCurrentStage += sampleTime;
  134. env = std::pow(1.f - std::min(1.f, timeInCurrentStage / decayTime), decayShape);
  135. env = sustainLevel + (1.f - sustainLevel) * env;
  136. break;
  137. }
  138. case STAGE_SUSTAIN: {
  139. env = sustainLevel;
  140. break;
  141. }
  142. case STAGE_RELEASE: {
  143. timeInCurrentStage += sampleTime;
  144. env = std::min(1.0f, timeInCurrentStage / releaseTime);
  145. env = releaseValue * std::pow(1.0f - env, releaseShape);
  146. break;
  147. }
  148. }
  149. }
  150. void process(const float& sampleTime, const bool& gateHeld, const bool& triggerMode) {
  151. if (triggerMode) {
  152. processTransitionsTriggerMode(gateHeld);
  153. }
  154. else {
  155. processTransitionsGateMode(gateHeld);
  156. }
  157. evolveEnvelope(sampleTime);
  158. }
  159. };
  160. struct ADSR : Module {
  161. enum ParamIds {
  162. TRIGG_GATE_TOGGLE_PARAM,
  163. MANUAL_TRIGGER_PARAM,
  164. SHAPE_PARAM,
  165. ATTACK_PARAM,
  166. DECAY_PARAM,
  167. SUSTAIN_PARAM,
  168. RELEASE_PARAM,
  169. NUM_PARAMS
  170. };
  171. enum InputIds {
  172. TRIGGER_INPUT,
  173. CV_ATTACK_INPUT,
  174. CV_DECAY_INPUT,
  175. CV_SUSTAIN_INPUT,
  176. CV_RELEASE_INPUT,
  177. NUM_INPUTS
  178. };
  179. enum OutputIds {
  180. OUT_OUTPUT,
  181. STAGE_ATTACK_OUTPUT,
  182. STAGE_DECAY_OUTPUT,
  183. STAGE_SUSTAIN_OUTPUT,
  184. STAGE_RELEASE_OUTPUT,
  185. NUM_OUTPUTS
  186. };
  187. enum LightIds {
  188. LED_LIGHT,
  189. LED_ATTACK_LIGHT,
  190. LED_DECAY_LIGHT,
  191. LED_SUSTAIN_LIGHT,
  192. LED_RELEASE_LIGHT,
  193. NUM_LIGHTS
  194. };
  195. enum EnvelopeMode {
  196. GATE_MODE,
  197. TRIGGER_MODE
  198. };
  199. BefacoADSREnvelope envelope;
  200. dsp::SchmittTrigger gateTrigger;
  201. dsp::ClockDivider cvDivider;
  202. float shape;
  203. static constexpr float minStageTime = 0.003f; // in seconds
  204. static constexpr float maxStageTime = 10.f; // in seconds
  205. // given a value from the slider and/or cv (rescaled to range 0 to 1), transform into the appropriate time in seconds
  206. static float convertCVToTimeInSeconds(float cv) {
  207. return minStageTime * std::pow(maxStageTime / minStageTime, cv);
  208. }
  209. ADSR() {
  210. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  211. configSwitch(TRIGG_GATE_TOGGLE_PARAM, GATE_MODE, TRIGGER_MODE, GATE_MODE, "Mode", {"Gate", "Trigger"});
  212. configButton(MANUAL_TRIGGER_PARAM, "Trigger envelope");
  213. configParam(SHAPE_PARAM, 0.f, 1.f, 0.f, "Envelope shape");
  214. configParam(ATTACK_PARAM, 0.f, 1.f, 0.4f, "Attack time", "s", maxStageTime / minStageTime, minStageTime);
  215. configParam(DECAY_PARAM, 0.f, 1.f, 0.4f, "Decay time", "s", maxStageTime / minStageTime, minStageTime);
  216. configParam(SUSTAIN_PARAM, 0.f, 1.f, 0.5f, "Sustain level", "%", 0.f, 100.f);
  217. configParam(RELEASE_PARAM, 0.f, 1.f, 0.4f, "Release time", "s", maxStageTime / minStageTime, minStageTime);
  218. configInput(TRIGGER_INPUT, "Trigger");
  219. configInput(CV_ATTACK_INPUT, "Attack CV");
  220. configInput(CV_DECAY_INPUT, "Decay CV");
  221. configInput(CV_SUSTAIN_INPUT, "Sustain CV");
  222. configInput(CV_RELEASE_INPUT, "Release CV");
  223. configOutput(OUT_OUTPUT, "Envelope");
  224. configOutput(STAGE_ATTACK_OUTPUT, "Attack stage");
  225. configOutput(STAGE_DECAY_OUTPUT, "Decay stage");
  226. configOutput(STAGE_SUSTAIN_OUTPUT, "Sustain stage");
  227. configOutput(STAGE_RELEASE_OUTPUT, "Release stage");
  228. cvDivider.setDivision(16);
  229. }
  230. void process(const ProcessArgs& args) override {
  231. if (cvDivider.process()) {
  232. shape = params[SHAPE_PARAM].getValue();
  233. envelope.decayShape = 1.f + shape;
  234. envelope.attackShape = 1.f - shape / 2.f;
  235. envelope.releaseShape = 1.f + shape;
  236. const float attackCV = clamp(params[ATTACK_PARAM].getValue() + inputs[CV_ATTACK_INPUT].getVoltage() / 10.f, 0.f, 1.f);
  237. envelope.attackTime = convertCVToTimeInSeconds(attackCV);
  238. const float decayCV = clamp(params[DECAY_PARAM].getValue() + inputs[CV_DECAY_INPUT].getVoltage() / 10.f, 0.f, 1.f);
  239. envelope.decayTime = convertCVToTimeInSeconds(decayCV);
  240. const float sustainCV = clamp(params[SUSTAIN_PARAM].getValue() + inputs[CV_SUSTAIN_INPUT].getVoltage() / 10.f, 0.f, 1.f);
  241. envelope.sustainLevel = sustainCV;
  242. const float releaseCV = clamp(params[RELEASE_PARAM].getValue() + inputs[CV_RELEASE_INPUT].getVoltage() / 10.f, 0.f, 1.f);
  243. envelope.releaseTime = convertCVToTimeInSeconds(releaseCV);
  244. }
  245. const bool triggered = gateTrigger.process(rescale(params[MANUAL_TRIGGER_PARAM].getValue() * 10.f + inputs[TRIGGER_INPUT].getVoltage(), 0.1f, 2.f, 0.f, 1.f));
  246. const bool gateOn = gateTrigger.isHigh() || params[MANUAL_TRIGGER_PARAM].getValue();
  247. const bool triggerMode = params[TRIGG_GATE_TOGGLE_PARAM].getValue() == 1;
  248. if (triggerMode) {
  249. if (triggered) {
  250. envelope.retrigger();
  251. }
  252. }
  253. envelope.process(args.sampleTime, gateOn, triggerMode);
  254. outputs[OUT_OUTPUT].setVoltage(envelope.env * 10.f);
  255. outputs[STAGE_ATTACK_OUTPUT].setVoltage(10.f * (envelope.stage == BefacoADSREnvelope::STAGE_ATTACK));
  256. outputs[STAGE_DECAY_OUTPUT].setVoltage(10.f * (envelope.stage == BefacoADSREnvelope::STAGE_DECAY));
  257. outputs[STAGE_SUSTAIN_OUTPUT].setVoltage(10.f * (envelope.stage == BefacoADSREnvelope::STAGE_SUSTAIN));
  258. outputs[STAGE_RELEASE_OUTPUT].setVoltage(10.f * (envelope.stage == BefacoADSREnvelope::STAGE_RELEASE));
  259. lights[LED_ATTACK_LIGHT].setBrightness((envelope.stage == BefacoADSREnvelope::STAGE_ATTACK));
  260. lights[LED_DECAY_LIGHT].setBrightness((envelope.stage == BefacoADSREnvelope::STAGE_DECAY));
  261. lights[LED_SUSTAIN_LIGHT].setBrightness((envelope.stage == BefacoADSREnvelope::STAGE_SUSTAIN));
  262. lights[LED_RELEASE_LIGHT].setBrightness((envelope.stage == BefacoADSREnvelope::STAGE_RELEASE));
  263. lights[LED_LIGHT].setBrightness((float) gateOn);
  264. }
  265. };
  266. struct ADSRWidget : ModuleWidget {
  267. ADSRWidget(ADSR* module) {
  268. setModule(module);
  269. setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/panels/ADSR.svg")));
  270. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, 0)));
  271. addChild(createWidget<Knurlie>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  272. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  273. addChild(createWidget<Knurlie>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  274. addParam(createParamCentered<BefacoSwitch>(mm2px(Vec(20.263, 17.128)), module, ADSR::TRIGG_GATE_TOGGLE_PARAM));
  275. addParam(createParamCentered<BefacoPush>(mm2px(Vec(11.581, 32.473)), module, ADSR::MANUAL_TRIGGER_PARAM));
  276. addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(29.063, 32.573)), module, ADSR::SHAPE_PARAM));
  277. addParam(createParam<BefacoSlidePot>(mm2px(Vec(2.294, 45.632)), module, ADSR::ATTACK_PARAM));
  278. addParam(createParam<BefacoSlidePot>(mm2px(Vec(12.422, 45.632)), module, ADSR::DECAY_PARAM));
  279. addParam(createParam<BefacoSlidePot>(mm2px(Vec(22.551, 45.632)), module, ADSR::SUSTAIN_PARAM));
  280. addParam(createParam<BefacoSlidePot>(mm2px(Vec(32.68, 45.632)), module, ADSR::RELEASE_PARAM));
  281. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(6.841, 15.5)), module, ADSR::TRIGGER_INPUT));
  282. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.022, 113.506)), module, ADSR::CV_ATTACK_INPUT));
  283. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.195, 113.506)), module, ADSR::CV_DECAY_INPUT));
  284. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(25.368, 113.506)), module, ADSR::CV_SUSTAIN_INPUT));
  285. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(35.541, 113.506)), module, ADSR::CV_RELEASE_INPUT));
  286. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(33.721, 15.479)), module, ADSR::OUT_OUTPUT));
  287. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(5.022, 100.858)), module, ADSR::STAGE_ATTACK_OUTPUT));
  288. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(15.195, 100.858)), module, ADSR::STAGE_DECAY_OUTPUT));
  289. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(25.368, 100.858)), module, ADSR::STAGE_SUSTAIN_OUTPUT));
  290. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(35.541, 100.858)), module, ADSR::STAGE_RELEASE_OUTPUT));
  291. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(20.254, 40.864)), module, ADSR::LED_LIGHT));
  292. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(5.001, 92.893)), module, ADSR::LED_ATTACK_LIGHT));
  293. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(15.174, 92.893)), module, ADSR::LED_DECAY_LIGHT));
  294. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(25.347, 92.893)), module, ADSR::LED_SUSTAIN_LIGHT));
  295. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(35.52, 92.893)), module, ADSR::LED_RELEASE_LIGHT));
  296. }
  297. };
  298. Model* modelADSR = createModel<ADSR, ADSRWidget>("ADSR");