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.

309 lines
9.5KB

  1. //**************************************************************************************
  2. //Delay Plus module for VCV Rack by Alfredo Santamaria - AS - https://github.com/AScustomWorks/AS
  3. //
  4. //Code based on Fundamental 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 DelayPlusFx : Module {
  16. enum ParamIds {
  17. TIME_PARAM,
  18. FEEDBACK_PARAM,
  19. COLOR_PARAM,
  20. MIX_PARAM,
  21. BYPASS_SWITCH,
  22. NUM_PARAMS
  23. };
  24. enum InputIds {
  25. TIME_INPUT,
  26. FEEDBACK_INPUT,
  27. COLOR_INPUT,
  28. COLOR_RETURN,
  29. MIX_INPUT,
  30. IN_INPUT,
  31. BYPASS_CV_INPUT,
  32. NUM_INPUTS
  33. };
  34. enum OutputIds {
  35. COLOR_SEND,
  36. OUT_OUTPUT,
  37. NUM_OUTPUTS
  38. };
  39. enum LightIds {
  40. BYPASS_LED,
  41. NUM_LIGHTS
  42. };
  43. RCFilter lowpassFilter;
  44. RCFilter highpassFilter;
  45. DoubleRingBuffer<float, HISTORY_SIZE> historyBuffer;
  46. DoubleRingBuffer<float, 16> outBuffer;
  47. SampleRateConverter<1> src;
  48. SchmittTrigger bypass_button_trig;
  49. SchmittTrigger bypass_cv_trig;
  50. int lcd_tempo = 0;
  51. bool fx_bypass = false;
  52. float lastWet = 0.0f;
  53. float fade_in_fx = 0.0f;
  54. float fade_in_dry = 0.0f;
  55. float fade_out_fx = 1.0f;
  56. float fade_out_dry = 1.0f;
  57. const float fade_speed = 0.001f;
  58. DelayPlusFx() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  59. }
  60. void step() override;
  61. json_t *toJson()override {
  62. json_t *rootJm = json_object();
  63. json_t *statesJ = json_array();
  64. json_t *bypassJ = json_boolean(fx_bypass);
  65. json_array_append_new(statesJ, bypassJ);
  66. json_object_set_new(rootJm, "as_FxBypass", statesJ);
  67. return rootJm;
  68. }
  69. void fromJson(json_t *rootJm)override {
  70. json_t *statesJ = json_object_get(rootJm, "as_FxBypass");
  71. json_t *bypassJ = json_array_get(statesJ, 0);
  72. fx_bypass = !!json_boolean_value(bypassJ);
  73. }
  74. void resetFades(){
  75. fade_in_fx = 0.0f;
  76. fade_in_dry = 0.0f;
  77. fade_out_fx = 1.0f;
  78. fade_out_dry = 1.0f;
  79. }
  80. };
  81. void DelayPlusFx::step() {
  82. if (bypass_button_trig.process(params[BYPASS_SWITCH].value) || bypass_cv_trig.process(inputs[BYPASS_CV_INPUT].value) ){
  83. fx_bypass = !fx_bypass;
  84. resetFades();
  85. }
  86. lights[BYPASS_LED].value = fx_bypass ? 1.0f : 0.0f;
  87. // Get input to delay block
  88. float signal_input = inputs[IN_INPUT].value;
  89. float feedback = clamp(params[FEEDBACK_PARAM].value + inputs[FEEDBACK_INPUT].value / 10.0f, 0.0f, 1.0f);
  90. float dry = signal_input + lastWet * feedback;
  91. // Compute delay time in seconds
  92. //float delay = 1e-3 * powf(10.0 / 1e-3, clampf(params[TIME_PARAM].value + inputs[TIME_INPUT].value / 10.0, 0.0, 1.0));
  93. float delay = clamp(params[TIME_PARAM].value + inputs[TIME_INPUT].value, 0.001f, 10.0f);
  94. //LCD display tempo - show value as ms
  95. lcd_tempo = std::round(delay*1000);
  96. // Number of delay samples
  97. float index = delay * engineGetSampleRate();
  98. // Push dry sample into history buffer
  99. if (!historyBuffer.full()) {
  100. historyBuffer.push(dry);
  101. }
  102. // How many samples do we need consume to catch up?
  103. float consume = index - historyBuffer.size();
  104. if (outBuffer.empty()) {
  105. double ratio = 1.0;
  106. if (consume <= -16)
  107. ratio = 0.5;
  108. else if (consume >= 16)
  109. ratio = 2.0;
  110. float inSR = engineGetSampleRate();
  111. float outSR = ratio * inSR;
  112. int inFrames = min(historyBuffer.size(), 16);
  113. int outFrames = outBuffer.capacity();
  114. src.setRates(inSR, outSR);
  115. src.process((const Frame<1>*)historyBuffer.startData(), &inFrames, (Frame<1>*)outBuffer.endData(), &outFrames);
  116. historyBuffer.startIncr(inFrames);
  117. outBuffer.endIncr(outFrames);
  118. }
  119. float out;
  120. float mix;
  121. float wet = 0.0f;
  122. if (!outBuffer.empty()) {
  123. wet = outBuffer.shift();
  124. }
  125. if (outputs[COLOR_SEND].active == false) {
  126. //internal color
  127. // Apply color to delay wet output
  128. float color = clamp(params[COLOR_PARAM].value + inputs[COLOR_INPUT].value / 10.0f, 0.0f, 1.0f);
  129. float lowpassFreq = 10000.0f * powf(10.0f, clamp(2.0*color, 0.0f, 1.0f));
  130. lowpassFilter.setCutoff(lowpassFreq / engineGetSampleRate());
  131. lowpassFilter.process(wet);
  132. wet = lowpassFilter.lowpass();
  133. float highpassFreq = 10.0f * powf(100.0f, clamp(2.0f*color - 1.0f, 0.0f, 1.0f));
  134. highpassFilter.setCutoff(highpassFreq / engineGetSampleRate());
  135. highpassFilter.process(wet);
  136. wet = highpassFilter.highpass();
  137. //lastWet = wet;
  138. }else {
  139. //external color, to filter the wet delay signal outside of the module, or to feed another module
  140. outputs[COLOR_SEND].value = wet;
  141. wet = inputs[COLOR_RETURN].value;
  142. }
  143. lastWet = wet;
  144. mix = clamp(params[MIX_PARAM].value + inputs[MIX_INPUT].value / 10.0f, 0.0f, 1.0f);
  145. out = crossfade(signal_input, wet, mix);
  146. //check bypass switch status
  147. if (fx_bypass){
  148. fade_in_dry += fade_speed;
  149. if ( fade_in_dry > 1.0f ) {
  150. fade_in_dry = 1.0f;
  151. }
  152. fade_out_fx -= fade_speed;
  153. if ( fade_out_fx < 0.0f ) {
  154. fade_out_fx = 0.0f;
  155. }
  156. outputs[OUT_OUTPUT].value = ( signal_input * fade_in_dry ) + ( out * fade_out_fx );
  157. }else{
  158. fade_in_fx += fade_speed;
  159. if ( fade_in_fx > 1.0f ) {
  160. fade_in_fx = 1.0f;
  161. }
  162. fade_out_dry -= fade_speed;
  163. if ( fade_out_dry < 0.0f ) {
  164. fade_out_dry = 0.0f;
  165. }
  166. outputs[OUT_OUTPUT].value = ( signal_input * fade_out_dry ) + ( out * fade_in_fx );
  167. }
  168. }
  169. ///////////////////////////////////
  170. struct MsDisplayWidget : TransparentWidget {
  171. int *value;
  172. std::shared_ptr<Font> font;
  173. MsDisplayWidget() {
  174. font = Font::load(assetPlugin(plugin, "res/Segment7Standard.ttf"));
  175. };
  176. void draw(NVGcontext *vg) override
  177. {
  178. // Background
  179. NVGcolor backgroundColor = nvgRGB(0x20, 0x10, 0x10);
  180. NVGcolor borderColor = nvgRGB(0x10, 0x10, 0x10);
  181. nvgBeginPath(vg);
  182. nvgRoundedRect(vg, 0.0, 0.0, box.size.x, box.size.y, 4.0);
  183. nvgFillColor(vg, backgroundColor);
  184. nvgFill(vg);
  185. nvgStrokeWidth(vg, 1.5);
  186. nvgStrokeColor(vg, borderColor);
  187. nvgStroke(vg);
  188. // text
  189. nvgFontSize(vg, 18);
  190. nvgFontFaceId(vg, font->handle);
  191. nvgTextLetterSpacing(vg, 2.5);
  192. std::stringstream to_display;
  193. to_display << std::right << std::setw(5) << *value;
  194. Vec textPos = Vec(4.0f, 17.0f);
  195. NVGcolor textColor = nvgRGB(0xdf, 0xd2, 0x2c);
  196. nvgFillColor(vg, nvgTransRGBA(textColor, 16));
  197. nvgText(vg, textPos.x, textPos.y, "~~~~~", NULL);
  198. textColor = nvgRGB(0xda, 0xe9, 0x29);
  199. nvgFillColor(vg, nvgTransRGBA(textColor, 16));
  200. nvgText(vg, textPos.x, textPos.y, "\\\\\\\\\\", NULL);
  201. textColor = nvgRGB(0xf0, 0x00, 0x00);
  202. nvgFillColor(vg, textColor);
  203. nvgText(vg, textPos.x, textPos.y, to_display.str().c_str(), NULL);
  204. }
  205. };
  206. ////////////////////////////////////
  207. struct DelayPlusFxWidget : ModuleWidget
  208. {
  209. DelayPlusFxWidget(DelayPlusFx *module);
  210. };
  211. DelayPlusFxWidget::DelayPlusFxWidget(DelayPlusFx *module) : ModuleWidget(module) {
  212. setPanel(SVG::load(assetPlugin(plugin, "res/DelayPlus.svg")));
  213. //MS DISPLAY
  214. MsDisplayWidget *display = new MsDisplayWidget();
  215. display->box.pos = Vec(14,50);
  216. display->box.size = Vec(70, 20);
  217. display->value = &module->lcd_tempo;
  218. addChild(display);
  219. int y_offset=40;
  220. //SCREWS
  221. addChild(Widget::create<as_HexScrew>(Vec(RACK_GRID_WIDTH, 0)));
  222. addChild(Widget::create<as_HexScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  223. addChild(Widget::create<as_HexScrew>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  224. addChild(Widget::create<as_HexScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  225. //KNOBS
  226. addParam(ParamWidget::create<as_FxKnobWhite>(Vec(74, 38+y_offset), module, DelayPlusFx::TIME_PARAM, 0.0f, 10.0f, 0.350f));
  227. addParam(ParamWidget::create<as_FxKnobWhite>(Vec(74, 90+y_offset), module, DelayPlusFx::FEEDBACK_PARAM, 0.0f, 1.0f, 0.5f));
  228. addParam(ParamWidget::create<as_FxKnobWhite>(Vec(74, 140+y_offset), module, DelayPlusFx::COLOR_PARAM, 0.0f, 1.0f, 0.5f));
  229. addParam(ParamWidget::create<as_FxKnobWhite>(Vec(74, 213+y_offset), module, DelayPlusFx::MIX_PARAM, 0.0f, 1.0f, 0.5f));
  230. //BYPASS SWITCH
  231. addParam(ParamWidget::create<LEDBezel>(Vec(49.5, 250+y_offset), module, DelayPlusFx::BYPASS_SWITCH , 0.0f, 1.0f, 0.0f));//Y=272
  232. addChild(ModuleLightWidget::create<LedLight<RedLight>>(Vec(51.7, 252+y_offset), module, DelayPlusFx::BYPASS_LED));//Y=274
  233. //INPUTS
  234. addInput(Port::create<as_PJ301MPort>(Vec(10, 45+y_offset), Port::INPUT, module, DelayPlusFx::TIME_INPUT));
  235. addInput(Port::create<as_PJ301MPort>(Vec(10, 95+y_offset), Port::INPUT, module, DelayPlusFx::FEEDBACK_INPUT));
  236. addInput(Port::create<as_PJ301MPort>(Vec(10, 145+y_offset), Port::INPUT, module, DelayPlusFx::COLOR_INPUT));
  237. //DELAY SIGNAL SEND
  238. addOutput(Port::create<as_PJ301MPort>(Vec(20, 184+y_offset), Port::OUTPUT, module, DelayPlusFx::COLOR_SEND));
  239. //DELAY SIGNAL RETURN
  240. addInput(Port::create<as_PJ301MPort>(Vec(75, 184+y_offset), Port::INPUT, module, DelayPlusFx::COLOR_RETURN));
  241. //INPUTS
  242. addInput(Port::create<as_PJ301MPort>(Vec(10, 220+y_offset), Port::INPUT, module, DelayPlusFx::MIX_INPUT));
  243. addInput(Port::create<as_PJ301MPort>(Vec(10, 310), Port::INPUT, module, DelayPlusFx::IN_INPUT));
  244. //OUTPUT
  245. addOutput(Port::create<as_PJ301MPort>(Vec(85, 310), Port::OUTPUT, module, DelayPlusFx::OUT_OUTPUT));
  246. //BYPASS CV INPUT
  247. addInput(Port::create<as_PJ301MPort>(Vec(49, 320), Port::INPUT, module, DelayPlusFx::BYPASS_CV_INPUT));
  248. }
  249. } // namespace rack_plugin_AS
  250. using namespace rack_plugin_AS;
  251. RACK_PLUGIN_MODEL_INIT(AS, DelayPlusFx) {
  252. Model *modelDelayPlusFx = Model::create<DelayPlusFx, DelayPlusFxWidget>("AS", "DelayPlusFx", "DelayPlus Fx", DELAY_TAG, EFFECT_TAG);
  253. return modelDelayPlusFx;
  254. }