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.

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