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.

249 lines
9.2KB

  1. #include "plugin.hpp"
  2. #include <samplerate.h>
  3. struct Delay : Module {
  4. enum ParamId {
  5. TIME_PARAM,
  6. FEEDBACK_PARAM,
  7. TONE_PARAM,
  8. MIX_PARAM,
  9. // new in 2.0
  10. TIME_CV_PARAM,
  11. FEEDBACK_CV_PARAM,
  12. TONE_CV_PARAM,
  13. MIX_CV_PARAM,
  14. NUM_PARAMS
  15. };
  16. enum InputId {
  17. TIME_INPUT,
  18. FEEDBACK_INPUT,
  19. TONE_INPUT,
  20. MIX_INPUT,
  21. IN_INPUT,
  22. // new in 2.0
  23. CLOCK_INPUT,
  24. NUM_INPUTS
  25. };
  26. enum OutputId {
  27. MIX_OUTPUT,
  28. // new in 2.0
  29. WET_OUTPUT,
  30. NUM_OUTPUTS
  31. };
  32. enum LightId {
  33. CLOCK_LIGHT,
  34. NUM_LIGHTS
  35. };
  36. constexpr static size_t HISTORY_SIZE = 1 << 21;
  37. dsp::DoubleRingBuffer<float, HISTORY_SIZE> historyBuffer;
  38. dsp::DoubleRingBuffer<float, 16> outBuffer;
  39. SRC_STATE* src;
  40. float lastWet = 0.f;
  41. dsp::RCFilter lowpassFilter;
  42. dsp::RCFilter highpassFilter;
  43. float clockFreq = 1.f;
  44. dsp::Timer clockTimer;
  45. dsp::SchmittTrigger clockTrigger;
  46. float clockPhase = 0.f;
  47. Delay() {
  48. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  49. // VCV Delay was written before the pitch voltage standard existed, so it uses TIME_PARAM = 0 as 0.001s and TIME_PARAM = 1 as 10s with a formula of:
  50. // time = 0.001 * 10000^TIME_PARAM
  51. // or
  52. // TIME_PARAM = log10(time * 1000) / 4
  53. const float timeMin = log10(0.001f * 1000) / 4;
  54. const float timeMax = log10(10.f * 1000) / 4;
  55. const float timeDefault = log10(0.5f * 1000) / 4;
  56. configParam(TIME_PARAM, timeMin, timeMax, timeDefault, "Time", " s", 10.f / 1e-3, 1e-3);
  57. configParam(FEEDBACK_PARAM, 0.f, 1.f, 0.5f, "Feedback", "%", 0, 100);
  58. configParam(TONE_PARAM, 0.f, 1.f, 0.5f, "Tone", "%", 0, 200, -100);
  59. configParam(MIX_PARAM, 0.f, 1.f, 0.5f, "Mix", "%", 0, 100);
  60. configParam(TIME_CV_PARAM, -1.f, 1.f, 0.f, "Time CV", "%", 0, 100);
  61. getParamQuantity(TIME_CV_PARAM)->randomizeEnabled = false;
  62. configParam(FEEDBACK_CV_PARAM, -1.f, 1.f, 0.f, "Feedback CV", "%", 0, 100);
  63. getParamQuantity(FEEDBACK_CV_PARAM)->randomizeEnabled = false;
  64. configParam(TONE_CV_PARAM, -1.f, 1.f, 0.f, "Tone CV", "%", 0, 100);
  65. getParamQuantity(TONE_CV_PARAM)->randomizeEnabled = false;
  66. configParam(MIX_CV_PARAM, -1.f, 1.f, 0.f, "Mix CV", "%", 0, 100);
  67. getParamQuantity(MIX_CV_PARAM)->randomizeEnabled = false;
  68. configInput(TIME_INPUT, "Time");
  69. getInputInfo(TIME_INPUT)->description = "1V/octave when Time CV is 100%";
  70. configInput(FEEDBACK_INPUT, "Feedback");
  71. configInput(TONE_INPUT, "Tone");
  72. configInput(MIX_INPUT, "Mix");
  73. configInput(IN_INPUT, "Audio");
  74. configInput(CLOCK_INPUT, "Clock");
  75. configOutput(MIX_OUTPUT, "Mix");
  76. configOutput(WET_OUTPUT, "Wet");
  77. configBypass(IN_INPUT, WET_OUTPUT);
  78. configBypass(IN_INPUT, MIX_OUTPUT);
  79. src = src_new(SRC_SINC_FASTEST, 1, NULL);
  80. }
  81. ~Delay() {
  82. if (src)
  83. src_delete(src);
  84. }
  85. void process(const ProcessArgs& args) override {
  86. // Clock
  87. if (inputs[CLOCK_INPUT].isConnected()) {
  88. clockTimer.process(args.sampleTime);
  89. if (clockTrigger.process(inputs[CLOCK_INPUT].getVoltage(), 0.1f, 2.f)) {
  90. float clockFreq = 1.f / clockTimer.getTime();
  91. clockTimer.reset();
  92. if (0.001f <= clockFreq && clockFreq <= 1000.f) {
  93. this->clockFreq = clockFreq;
  94. }
  95. }
  96. }
  97. else {
  98. // Default frequency when clock is unpatched
  99. clockFreq = 2.f;
  100. }
  101. // Get input to delay block
  102. float in = inputs[IN_INPUT].getVoltageSum();
  103. float feedback = params[FEEDBACK_PARAM].getValue() + inputs[FEEDBACK_INPUT].getVoltage() / 10.f * params[FEEDBACK_CV_PARAM].getValue();
  104. feedback = clamp(feedback, 0.f, 1.f);
  105. float dry = in + lastWet * feedback;
  106. // Compute freq
  107. // Scale time knob to 1V/oct pitch based on formula explained in constructor, for backwards compatibility
  108. float pitch = std::log2(1000.f) - std::log2(10000.f) * params[TIME_PARAM].getValue();
  109. pitch += inputs[TIME_INPUT].getVoltage() * params[TIME_CV_PARAM].getValue();
  110. float freq = clockFreq / 2.f * dsp::exp2_taylor5(pitch);
  111. // Number of desired delay samples
  112. float index = args.sampleRate / freq;
  113. // In order to delay accurate samples, subtract by the historyBuffer size, and an experimentally tweaked amount.
  114. index -= 16 + 4.f;
  115. index = clamp(index, 2.f, float(HISTORY_SIZE - 1));
  116. // DEBUG("freq %f index %f", freq, index);
  117. // Push dry sample into history buffer
  118. if (!historyBuffer.full()) {
  119. historyBuffer.push(dry);
  120. }
  121. if (outBuffer.empty()) {
  122. // How many samples do we need consume to catch up?
  123. float consume = index - historyBuffer.size();
  124. double ratio = std::pow(4.f, clamp(consume / 10000.f, -1.f, 1.f));
  125. // DEBUG("index %f historyBuffer %lu consume %f ratio %lf", index, historyBuffer.size(), consume, ratio);
  126. // Convert samples from the historyBuffer to catch up or slow down so `index` and `historyBuffer.size()` eventually match approximately
  127. SRC_DATA srcData = {};
  128. srcData.data_in = (const float*) historyBuffer.startData();
  129. srcData.data_out = (float*) outBuffer.endData();
  130. srcData.input_frames = std::min((int) historyBuffer.size(), 16);
  131. srcData.output_frames = outBuffer.capacity();
  132. srcData.end_of_input = false;
  133. srcData.src_ratio = ratio;
  134. if (src)
  135. src_process(src, &srcData);
  136. historyBuffer.startIncr(srcData.input_frames_used);
  137. outBuffer.endIncr(srcData.output_frames_gen);
  138. // DEBUG("used %ld gen %ld", srcData.input_frames_used, srcData.output_frames_gen);
  139. }
  140. float wet = 0.f;
  141. if (!outBuffer.empty()) {
  142. wet = outBuffer.shift();
  143. }
  144. wet = clamp(wet, -100.f, 100.f);
  145. // Apply color to delay wet output
  146. float color = params[TONE_PARAM].getValue() + inputs[TONE_INPUT].getVoltage() / 10.f * params[TONE_CV_PARAM].getValue();
  147. color = clamp(color, 0.f, 1.f);
  148. float colorFreq = std::pow(100.f, 2.f * color - 1.f);
  149. float lowpassFreq = clamp(20000.f * colorFreq, 20.f, 20000.f);
  150. lowpassFilter.setCutoffFreq(lowpassFreq / args.sampleRate);
  151. lowpassFilter.process(wet);
  152. wet = lowpassFilter.lowpass();
  153. float highpassFreq = clamp(20.f * colorFreq, 20.f, 20000.f);
  154. highpassFilter.setCutoff(highpassFreq / args.sampleRate);
  155. highpassFilter.process(wet);
  156. wet = highpassFilter.highpass();
  157. // Set wet output
  158. outputs[WET_OUTPUT].setVoltage(wet);
  159. lastWet = wet;
  160. // Set mix output
  161. float mix = params[MIX_PARAM].getValue() + inputs[MIX_INPUT].getVoltage() / 10.f * params[MIX_CV_PARAM].getValue();
  162. mix = clamp(mix, 0.f, 1.f);
  163. float out = crossfade(in, wet, mix);
  164. outputs[MIX_OUTPUT].setVoltage(out);
  165. // Clock light
  166. clockPhase += freq * args.sampleTime;
  167. if (clockPhase >= 1.f) {
  168. clockPhase -= 1.f;
  169. lights[CLOCK_LIGHT].setBrightness(1.f);
  170. }
  171. else {
  172. lights[CLOCK_LIGHT].setBrightnessSmooth(0.f, args.sampleTime);
  173. }
  174. }
  175. void paramsFromJson(json_t* rootJ) override {
  176. // These attenuators didn't exist in version <2.0, so set to 1 in case they are not overwritten.
  177. params[FEEDBACK_CV_PARAM].setValue(1.f);
  178. params[TONE_CV_PARAM].setValue(1.f);
  179. params[MIX_CV_PARAM].setValue(1.f);
  180. // The time input scaling has changed, so don't set to 1.
  181. // params[TIME_CV_PARAM].setValue(1.f);
  182. Module::paramsFromJson(rootJ);
  183. }
  184. };
  185. struct DelayWidget : ModuleWidget {
  186. DelayWidget(Delay* module) {
  187. setModule(module);
  188. setPanel(createPanel(asset::plugin(pluginInstance, "res/Delay.svg"), asset::plugin(pluginInstance, "res/Delay-dark.svg")));
  189. addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, 0)));
  190. addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  191. addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  192. addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  193. addParam(createParamCentered<RoundLargeBlackKnob>(mm2px(Vec(12.579, 26.747)), module, Delay::TIME_PARAM));
  194. addParam(createParamCentered<RoundLargeBlackKnob>(mm2px(Vec(32.899, 26.747)), module, Delay::FEEDBACK_PARAM));
  195. addParam(createParamCentered<RoundLargeBlackKnob>(mm2px(Vec(12.579, 56.388)), module, Delay::TONE_PARAM));
  196. addParam(createParamCentered<RoundLargeBlackKnob>(mm2px(Vec(32.899, 56.388)), module, Delay::MIX_PARAM));
  197. addParam(createParamCentered<Trimpot>(mm2px(Vec(6.605, 80.561)), module, Delay::TIME_CV_PARAM));
  198. addParam(createParamCentered<Trimpot>(mm2px(Vec(17.442, 80.561)), module, Delay::FEEDBACK_CV_PARAM));
  199. addParam(createParamCentered<Trimpot>(mm2px(Vec(28.278, 80.561)), module, Delay::TONE_CV_PARAM));
  200. addParam(createParamCentered<Trimpot>(mm2px(Vec(39.115, 80.561)), module, Delay::MIX_CV_PARAM));
  201. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(6.605, 96.859)), module, Delay::TIME_INPUT));
  202. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(17.442, 96.859)), module, Delay::FEEDBACK_INPUT));
  203. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(28.278, 96.819)), module, Delay::TONE_INPUT));
  204. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(39.115, 96.819)), module, Delay::MIX_INPUT));
  205. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(6.605, 113.115)), module, Delay::IN_INPUT));
  206. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(17.442, 113.115)), module, Delay::CLOCK_INPUT));
  207. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(28.278, 113.115)), module, Delay::WET_OUTPUT));
  208. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(39.115, 113.115)), module, Delay::MIX_OUTPUT));
  209. addChild(createLightCentered<SmallLight<YellowLight>>(mm2px(Vec(22.738, 16.428)), module, Delay::CLOCK_LIGHT));
  210. }
  211. };
  212. Model* modelDelay = createModel<Delay, DelayWidget>("Delay");