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.

456 lines
15KB

  1. //**************************************************************************************
  2. //Delay Plus module for VCV Rack by Alfredo Santamaria - AS - https://github.com/AScustomWorks/AS
  3. //
  4. //Code taken from the Fundamentals plugins by Andrew Belt http://www.vcvrack.com
  5. //**************************************************************************************
  6. #include "AS.hpp"
  7. #include "dsp/samplerate.hpp"
  8. #include "dsp/ringbuffer.hpp"
  9. #include "dsp/filter.hpp"
  10. #include "dsp/digital.hpp"
  11. #include <sstream>
  12. #include <iomanip>
  13. namespace rack_plugin_AS {
  14. #define HISTORY_SIZE (1<<21)
  15. struct DelayPlusStereoFx : Module {
  16. enum ParamIds {
  17. TIME_PARAM_1,
  18. FEEDBACK_PARAM_1,
  19. COLOR_PARAM_1,
  20. TIME_PARAM_2,
  21. FEEDBACK_PARAM_2,
  22. COLOR_PARAM_2,
  23. FBK_LINK_PARAM,
  24. COLOR_LINK_PARAM,
  25. MIX_PARAM,
  26. BYPASS_SWITCH,
  27. NUM_PARAMS
  28. };
  29. enum InputIds {
  30. TIME_CV_INPUT_1,
  31. FEEDBACK__CV_INPUT_1,
  32. COLOR_CV_INPUT_1,
  33. COLOR_RETURN_1,
  34. TIME_CV_INPUT_2,
  35. FEEDBACK__CV_INPUT_2,
  36. COLOR_CV_INPUT_2,
  37. COLOR_RETURN_2,
  38. MIX_CV_INPUT,
  39. SIGNAL_INPUT_1,
  40. SIGNAL_INPUT_2,
  41. BYPASS_CV_INPUT,
  42. NUM_INPUTS
  43. };
  44. enum OutputIds {
  45. COLOR_SEND_1,
  46. COLOR_SEND_2,
  47. SIGNAL_OUTPUT_1,
  48. SIGNAL_OUTPUT_2,
  49. NUM_OUTPUTS
  50. };
  51. enum LightIds {
  52. BYPASS_LED,
  53. NUM_LIGHTS
  54. };
  55. RCFilter lowpassFilter1;
  56. RCFilter highpassFilter1;
  57. RCFilter lowpassFilter2;
  58. RCFilter highpassFilter2;
  59. DoubleRingBuffer<float, HISTORY_SIZE> historyBuffer1;
  60. DoubleRingBuffer<float, 16> outBuffer1;
  61. DoubleRingBuffer<float, HISTORY_SIZE> historyBuffer2;
  62. DoubleRingBuffer<float, 16> outBuffer2;
  63. SampleRateConverter<1> src1;
  64. SampleRateConverter<1> src2;
  65. SchmittTrigger bypass_button_trig;
  66. SchmittTrigger bypass_cv_trig;
  67. int lcd_tempo1 = 0;
  68. int lcd_tempo2 = 0;
  69. bool fx_bypass = false;
  70. float lastWet1 = 0.0f;
  71. float lastWet2 = 0.0f;
  72. float feedback1 = 0.0f;
  73. float feedback2 = 0.0f;
  74. float color1 = 0.0f;
  75. float color2 = 0.0f;
  76. float mix_value = 0.0f;
  77. float fade_in_fx = 0.0f;
  78. float fade_in_dry = 0.0f;
  79. float fade_out_fx = 1.0f;
  80. float fade_out_dry = 1.0f;
  81. const float fade_speed = 0.001f;
  82. float signal_input_1 = 0.0f;
  83. float signal_input_2 = 0.0f;
  84. DelayPlusStereoFx() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  85. }
  86. void step() override;
  87. json_t *toJson()override {
  88. json_t *rootJm = json_object();
  89. json_t *statesJ = json_array();
  90. json_t *bypassJ = json_boolean(fx_bypass);
  91. json_array_append_new(statesJ, bypassJ);
  92. json_object_set_new(rootJm, "as_FxBypass", statesJ);
  93. return rootJm;
  94. }
  95. void fromJson(json_t *rootJm)override {
  96. json_t *statesJ = json_object_get(rootJm, "as_FxBypass");
  97. json_t *bypassJ = json_array_get(statesJ, 0);
  98. fx_bypass = !!json_boolean_value(bypassJ);
  99. }
  100. void resetFades(){
  101. fade_in_fx = 0.0f;
  102. fade_in_dry = 0.0f;
  103. fade_out_fx = 1.0f;
  104. fade_out_dry = 1.0f;
  105. }
  106. };
  107. void DelayPlusStereoFx::step() {
  108. if (bypass_button_trig.process(params[BYPASS_SWITCH].value) || bypass_cv_trig.process(inputs[BYPASS_CV_INPUT].value) ){
  109. fx_bypass = !fx_bypass;
  110. resetFades();
  111. }
  112. lights[BYPASS_LED].value = fx_bypass ? 1.0f : 0.0f;
  113. signal_input_1 = inputs[SIGNAL_INPUT_1].value;
  114. //check for MONO/STEREO CONNECTIONS
  115. if(!inputs[SIGNAL_INPUT_2].active){
  116. signal_input_2 = inputs[SIGNAL_INPUT_1].value;
  117. }else{
  118. signal_input_2 = inputs[SIGNAL_INPUT_2].value;
  119. }
  120. // DELAY L - Feed input to delay block
  121. feedback1 = clamp(params[FEEDBACK_PARAM_1].value + inputs[FEEDBACK__CV_INPUT_1].value / 10.0f, 0.0f, 1.0f);
  122. float dry1 = signal_input_1 + lastWet1 * feedback1;
  123. // Compute delay time in seconds
  124. //float delay = 1e-3 * powf(10.0 / 1e-3, clampf(params[TIME_PARAM_1].value + inputs[TIME_CV_INPUT_1].value / 10.0, 0.0, 1.0));
  125. float delay1 = clamp(params[TIME_PARAM_1].value + inputs[TIME_CV_INPUT_1].value, 0.001f, 10.0f);
  126. //LCD display tempo - show value as ms
  127. lcd_tempo1 = std::round(delay1*1000);
  128. // Number of delay samples
  129. float index1 = delay1 * engineGetSampleRate();
  130. // Push dry sample into history buffer
  131. if (!historyBuffer1.full()) {
  132. historyBuffer1.push(dry1);
  133. }
  134. // How many samples do we need consume to catch up?
  135. float consume1 = index1 - historyBuffer1.size();
  136. if (outBuffer1.empty()) {
  137. double ratio1 = 1.0;
  138. if (consume1 <= -16)
  139. ratio1 = 0.5;
  140. else if (consume1 >= 16)
  141. ratio1 = 2.0;
  142. float inSR1 = engineGetSampleRate();
  143. float outSR1 = ratio1 * inSR1;
  144. int inFrames1 = min(historyBuffer1.size(), 16);
  145. int outFrames1 = outBuffer1.capacity();
  146. src1.setRates(inSR1, outSR1);
  147. src1.process((const Frame<1>*)historyBuffer1.startData(), &inFrames1, (Frame<1>*)outBuffer1.endData(), &outFrames1);
  148. historyBuffer1.startIncr(inFrames1);
  149. outBuffer1.endIncr(outFrames1);
  150. }
  151. float out1;
  152. float wet1 = 0.0f;
  153. if (!outBuffer1.empty()) {
  154. wet1 = outBuffer1.shift();
  155. }
  156. if (!outputs[COLOR_SEND_1].active) {
  157. //internal color
  158. // Apply color to delay wet output
  159. color1 = clamp(params[COLOR_PARAM_1].value + inputs[COLOR_CV_INPUT_1].value / 10.0f, 0.0f, 1.0f);
  160. float lowpassFreq1 = 10000.0f * powf(10.0f, clamp(2.0*color1, 0.0f, 1.0f));
  161. lowpassFilter1.setCutoff(lowpassFreq1 / engineGetSampleRate());
  162. lowpassFilter1.process(wet1);
  163. wet1 = lowpassFilter1.lowpass();
  164. float highpassFreq1 = 10.0f * powf(100.0f, clamp(2.0f*color1 - 1.0f, 0.0f, 1.0f));
  165. highpassFilter1.setCutoff(highpassFreq1 / engineGetSampleRate());
  166. highpassFilter1.process(wet1);
  167. wet1 = highpassFilter1.highpass();
  168. }else {
  169. //external color, to filter the wet delay signal outside of the module, or to feed another module
  170. outputs[COLOR_SEND_1].value = wet1;
  171. wet1 = inputs[COLOR_RETURN_1].value;
  172. }
  173. lastWet1 = wet1;
  174. // end of delay 1 block
  175. // DELAY R - Feed input to delay block
  176. //CHECK IF LINK IS ENABLED FOR FEEDBACK KNOBS
  177. if(params[FBK_LINK_PARAM].value){
  178. feedback2 = feedback1;
  179. }else{
  180. feedback2 = clamp(params[FEEDBACK_PARAM_2].value + inputs[FEEDBACK__CV_INPUT_2].value / 10.0f, 0.0f, 1.0f);
  181. }
  182. float dry2 = signal_input_2 + lastWet2 * feedback2;
  183. // Compute delay time in seconds
  184. //float delay = 1e-3 * powf(10.0 / 1e-3, clampf(params[TIME_PARAM_1].value + inputs[TIME_CV_INPUT_1].value / 10.0, 0.0, 1.0));
  185. float delay2 = clamp(params[TIME_PARAM_2].value + inputs[TIME_CV_INPUT_2].value, 0.001f, 10.0f);
  186. //LCD display tempo - show value as ms
  187. lcd_tempo2 = std::round(delay2*1000);
  188. // Number of delay samples
  189. float index2 = delay2 * engineGetSampleRate();
  190. // Push dry sample into history buffer
  191. if (!historyBuffer2.full()) {
  192. historyBuffer2.push(dry2);
  193. }
  194. // How many samples do we need consume to catch up?
  195. float consume2 = index2 - historyBuffer2.size();
  196. if (outBuffer2.empty()) {
  197. double ratio2 = 1.0;
  198. if (consume2 <= -16)
  199. ratio2 = 0.5;
  200. else if (consume2 >= 16)
  201. ratio2 = 2.0;
  202. float inSR2 = engineGetSampleRate();
  203. float outSR2 = ratio2 * inSR2;
  204. int inFrames2 = min(historyBuffer2.size(), 16);
  205. int outFrames2 = outBuffer2.capacity();
  206. src2.setRates(inSR2, outSR2);
  207. src2.process((const Frame<1>*)historyBuffer2.startData(), &inFrames2, (Frame<1>*)outBuffer2.endData(), &outFrames2);
  208. historyBuffer2.startIncr(inFrames2);
  209. outBuffer2.endIncr(outFrames2);
  210. }
  211. float out2;
  212. float wet2 = 0.0f;
  213. if (!outBuffer2.empty()) {
  214. wet2 = outBuffer2.shift();
  215. }
  216. if (!outputs[COLOR_SEND_2].active) {
  217. //internal color
  218. // Apply color to delay wet output
  219. if ( params[COLOR_LINK_PARAM].value ) {
  220. color2 = color1;
  221. } else {
  222. color2 = clamp(params[COLOR_PARAM_2].value + inputs[COLOR_CV_INPUT_2].value / 10.0f, 0.0f, 1.0f);
  223. }
  224. float lowpassFreq2 = 10000.0f * powf(10.0f, clamp(2.0*color2, 0.0f, 1.0f));
  225. lowpassFilter2.setCutoff(lowpassFreq2 / engineGetSampleRate());
  226. lowpassFilter2.process(wet2);
  227. wet2 = lowpassFilter2.lowpass();
  228. float highpassFreq2 = 10.0f * powf(100.0f, clamp(2.0f*color2 - 1.0f, 0.0f, 1.0f));
  229. highpassFilter2.setCutoff(highpassFreq2 / engineGetSampleRate());
  230. highpassFilter2.process(wet2);
  231. wet2 = highpassFilter2.highpass();
  232. }else {
  233. //external color, to filter the wet delay signal outside of the module, or to feed another module
  234. outputs[COLOR_SEND_2].value = wet2;
  235. wet2 = inputs[COLOR_RETURN_2].value;
  236. }
  237. lastWet2 = wet2;
  238. //mix dry & wet signals
  239. mix_value = clamp(params[MIX_PARAM].value + inputs[MIX_CV_INPUT].value / 10.0f, 0.0f, 1.0f);
  240. out1 = crossfade(signal_input_1, wet1, mix_value);
  241. out2 = crossfade(signal_input_2, wet2, mix_value);
  242. //check bypass switch status
  243. if (fx_bypass){
  244. fade_in_dry += fade_speed;
  245. if ( fade_in_dry > 1.0f ) {
  246. fade_in_dry = 1.0f;
  247. }
  248. fade_out_fx -= fade_speed;
  249. if ( fade_out_fx < 0.0f ) {
  250. fade_out_fx = 0.0f;
  251. }
  252. outputs[SIGNAL_OUTPUT_1].value = ( signal_input_1 * fade_in_dry ) + ( out1 * fade_out_fx );
  253. outputs[SIGNAL_OUTPUT_2].value = ( signal_input_2 * fade_in_dry ) + ( out2 * fade_out_fx );
  254. }else{
  255. fade_in_fx += fade_speed;
  256. if ( fade_in_fx > 1.0f ) {
  257. fade_in_fx = 1.0f;
  258. }
  259. fade_out_dry -= fade_speed;
  260. if ( fade_out_dry < 0.0f ) {
  261. fade_out_dry = 0.0f;
  262. }
  263. outputs[SIGNAL_OUTPUT_1].value = ( signal_input_1 * fade_out_dry ) + ( out1 * fade_in_fx );
  264. outputs[SIGNAL_OUTPUT_2].value = ( signal_input_2 * fade_out_dry ) + ( out2 * fade_in_fx );
  265. }
  266. }
  267. ///////////////////////////////////
  268. struct MsDisplayWidget : TransparentWidget {
  269. int *value;
  270. std::shared_ptr<Font> font;
  271. MsDisplayWidget() {
  272. font = Font::load(assetPlugin(plugin, "res/Segment7Standard.ttf"));
  273. };
  274. void draw(NVGcontext *vg) override
  275. {
  276. // Background
  277. NVGcolor backgroundColor = nvgRGB(0x20, 0x10, 0x10);
  278. NVGcolor borderColor = nvgRGB(0x10, 0x10, 0x10);
  279. nvgBeginPath(vg);
  280. nvgRoundedRect(vg, 0.0, 0.0, box.size.x, box.size.y, 4.0);
  281. nvgFillColor(vg, backgroundColor);
  282. nvgFill(vg);
  283. nvgStrokeWidth(vg, 1.5);
  284. nvgStrokeColor(vg, borderColor);
  285. nvgStroke(vg);
  286. // text
  287. nvgFontSize(vg, 18);
  288. nvgFontFaceId(vg, font->handle);
  289. nvgTextLetterSpacing(vg, 2.5);
  290. std::stringstream to_display;
  291. to_display << std::right << std::setw(5) << *value;
  292. Vec textPos = Vec(4.0f, 17.0f);
  293. NVGcolor textColor = nvgRGB(0xdf, 0xd2, 0x2c);
  294. nvgFillColor(vg, nvgTransRGBA(textColor, 16));
  295. nvgText(vg, textPos.x, textPos.y, "~~~~~", NULL);
  296. textColor = nvgRGB(0xda, 0xe9, 0x29);
  297. nvgFillColor(vg, nvgTransRGBA(textColor, 16));
  298. nvgText(vg, textPos.x, textPos.y, "\\\\\\\\\\", NULL);
  299. textColor = nvgRGB(0xf0, 0x00, 0x00);
  300. nvgFillColor(vg, textColor);
  301. nvgText(vg, textPos.x, textPos.y, to_display.str().c_str(), NULL);
  302. }
  303. };
  304. ////////////////////////////////////
  305. struct DelayPlusStereoFxWidget : ModuleWidget
  306. {
  307. DelayPlusStereoFxWidget(DelayPlusStereoFx *module);
  308. };
  309. DelayPlusStereoFxWidget::DelayPlusStereoFxWidget(DelayPlusStereoFx *module) : ModuleWidget(module) {
  310. setPanel(SVG::load(assetPlugin(plugin, "res/DelayPlusStereo.svg")));
  311. //MS DISPLAY L
  312. MsDisplayWidget *display1 = new MsDisplayWidget();
  313. display1->box.pos = Vec(7,50);
  314. display1->box.size = Vec(70, 20);
  315. display1->value = &module->lcd_tempo1;
  316. addChild(display1);
  317. //MS DISPLAY R
  318. MsDisplayWidget *display2 = new MsDisplayWidget();
  319. display2->box.pos = Vec(103,50);
  320. display2->box.size = Vec(70, 20);
  321. display2->value = &module->lcd_tempo2;
  322. addChild(display2);
  323. //SCREWS
  324. addChild(Widget::create<as_HexScrew>(Vec(RACK_GRID_WIDTH, 0)));
  325. addChild(Widget::create<as_HexScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  326. addChild(Widget::create<as_HexScrew>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  327. addChild(Widget::create<as_HexScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  328. //KNOBS L
  329. addParam(ParamWidget::create<as_FxKnobWhite>(Vec(37, 78), module, DelayPlusStereoFx::TIME_PARAM_1, 0.0f, 10.0f, 0.375f));
  330. addParam(ParamWidget::create<as_FxKnobWhite>(Vec(37, 130), module, DelayPlusStereoFx::FEEDBACK_PARAM_1, 0.0f, 1.0f, 0.5f));
  331. addParam(ParamWidget::create<as_FxKnobWhite>(Vec(37, 180), module, DelayPlusStereoFx::COLOR_PARAM_1, 0.0f, 1.0f, 0.5f));
  332. //KNOBS R
  333. addParam(ParamWidget::create<as_FxKnobWhite>(Vec(106, 78), module, DelayPlusStereoFx::TIME_PARAM_2, 0.0f, 10.0f, 0.750f));
  334. addParam(ParamWidget::create<as_FxKnobWhite>(Vec(106, 130), module, DelayPlusStereoFx::FEEDBACK_PARAM_2, 0.0f, 1.0f, 0.5f));
  335. addParam(ParamWidget::create<as_FxKnobWhite>(Vec(106, 180), module, DelayPlusStereoFx::COLOR_PARAM_2, 0.0f, 1.0f, 0.5f));
  336. //FEEDBACK LINK SWITCH
  337. addParam(ParamWidget::create<as_CKSS>(Vec(82, 145), module, DelayPlusStereoFx::FBK_LINK_PARAM, 0.0f, 1.0f, 0.0f));
  338. //COLOR LINK SWITCH
  339. addParam(ParamWidget::create<as_CKSS>(Vec(82, 195), module, DelayPlusStereoFx::COLOR_LINK_PARAM, 0.0f, 1.0f, 0.0f));
  340. //MIX KNOB
  341. addParam(ParamWidget::create<as_FxKnobWhite>(Vec(71, 251), module, DelayPlusStereoFx::MIX_PARAM, 0.0f, 1.0f, 0.5f));
  342. //BYPASS SWITCH
  343. addParam(ParamWidget::create<LEDBezel>(Vec(79, 292), module, DelayPlusStereoFx::BYPASS_SWITCH , 0.0f, 1.0f, 0.0f));
  344. addChild(ModuleLightWidget::create<LedLight<RedLight>>(Vec(79+2.2, 294), module, DelayPlusStereoFx::BYPASS_LED));
  345. //INPUTS CV L
  346. addInput(Port::create<as_PJ301MPort>(Vec(7, 87), Port::INPUT, module, DelayPlusStereoFx::TIME_CV_INPUT_1));
  347. addInput(Port::create<as_PJ301MPort>(Vec(7, 137), Port::INPUT, module, DelayPlusStereoFx::FEEDBACK__CV_INPUT_1));
  348. addInput(Port::create<as_PJ301MPort>(Vec(7, 187), Port::INPUT, module, DelayPlusStereoFx::COLOR_CV_INPUT_1));
  349. //INPUTS CV R
  350. addInput(Port::create<as_PJ301MPort>(Vec(150, 87), Port::INPUT, module, DelayPlusStereoFx::TIME_CV_INPUT_2));
  351. addInput(Port::create<as_PJ301MPort>(Vec(150, 137), Port::INPUT, module, DelayPlusStereoFx::FEEDBACK__CV_INPUT_2));
  352. addInput(Port::create<as_PJ301MPort>(Vec(150, 187), Port::INPUT, module, DelayPlusStereoFx::COLOR_CV_INPUT_2));
  353. //DELAY SIGNAL SEND L
  354. addOutput(Port::create<as_PJ301MPort>(Vec(15, 224), Port::OUTPUT, module, DelayPlusStereoFx::COLOR_SEND_1));
  355. //DELAY SIGNAL RETURN L
  356. addInput(Port::create<as_PJ301MPort>(Vec(50, 224), Port::INPUT, module, DelayPlusStereoFx::COLOR_RETURN_1));
  357. //DELAY SIGNAL SEND R
  358. addOutput(Port::create<as_PJ301MPort>(Vec(105, 224), Port::OUTPUT, module, DelayPlusStereoFx::COLOR_SEND_2));
  359. //DELAY SIGNAL RETURN R
  360. addInput(Port::create<as_PJ301MPort>(Vec(140, 224), Port::INPUT, module, DelayPlusStereoFx::COLOR_RETURN_2));
  361. //MIX CV INPUT
  362. addInput(Port::create<as_PJ301MPort>(Vec(40, 258), Port::INPUT, module, DelayPlusStereoFx::MIX_CV_INPUT));
  363. //SIGNAL INPUT L
  364. addInput(Port::create<as_PJ301MPort>(Vec(20, 300), Port::INPUT, module, DelayPlusStereoFx::SIGNAL_INPUT_1));
  365. //SIGNAL INPUT R
  366. addInput(Port::create<as_PJ301MPort>(Vec(20, 330), Port::INPUT, module, DelayPlusStereoFx::SIGNAL_INPUT_2));
  367. //SIGNAL OUTPUT L
  368. addOutput(Port::create<as_PJ301MPort>(Vec(135, 300), Port::OUTPUT, module, DelayPlusStereoFx::SIGNAL_OUTPUT_1));
  369. //SIGNAL OUTPUT R
  370. addOutput(Port::create<as_PJ301MPort>(Vec(135, 330), Port::OUTPUT, module, DelayPlusStereoFx::SIGNAL_OUTPUT_2));
  371. //BYPASS CV INPUT
  372. addInput(Port::create<as_PJ301MPort>(Vec(78, 322), Port::INPUT, module, DelayPlusStereoFx::BYPASS_CV_INPUT));
  373. }
  374. } // namespace rack_plugin_AS
  375. using namespace rack_plugin_AS;
  376. RACK_PLUGIN_MODEL_INIT(AS, DelayPlusStereoFx) {
  377. Model *modelDelayPlusStereoFx = Model::create<DelayPlusStereoFx, DelayPlusStereoFxWidget>("AS", "DelayPlusStereoFx", "DelayPlus Stereo Fx", DUAL_TAG, DELAY_TAG, EFFECT_TAG);
  378. return modelDelayPlusStereoFx;
  379. }