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.

362 lines
12KB

  1. #include "plugin.hpp"
  2. #include "ChowDSP.hpp"
  3. struct ChoppingKinky : Module {
  4. enum ParamIds {
  5. FOLD_A_PARAM,
  6. FOLD_B_PARAM,
  7. CV_A_PARAM,
  8. CV_B_PARAM,
  9. NUM_PARAMS
  10. };
  11. enum InputIds {
  12. IN_A_INPUT,
  13. IN_B_INPUT,
  14. IN_GATE_INPUT,
  15. CV_A_INPUT,
  16. VCA_CV_A_INPUT,
  17. CV_B_INPUT,
  18. VCA_CV_B_INPUT,
  19. NUM_INPUTS
  20. };
  21. enum OutputIds {
  22. OUT_CHOPP_OUTPUT,
  23. OUT_A_OUTPUT,
  24. OUT_B_OUTPUT,
  25. NUM_OUTPUTS
  26. };
  27. enum LightIds {
  28. LED_A_LIGHT,
  29. LED_B_LIGHT,
  30. NUM_LIGHTS
  31. };
  32. enum {
  33. CHANNEL_A,
  34. CHANNEL_B,
  35. CHANNEL_CHOPP,
  36. NUM_CHANNELS
  37. };
  38. static const int WAVESHAPE_CACHE_SIZE = 256;
  39. float waveshapeA[WAVESHAPE_CACHE_SIZE + 1] = {};
  40. float waveshapeBPositive[WAVESHAPE_CACHE_SIZE + 1] = {};
  41. float waveshapeBNegative[WAVESHAPE_CACHE_SIZE + 1] = {};
  42. dsp::SchmittTrigger trigger;
  43. bool outputAToChopp = false;
  44. float previousA = 0.0;
  45. chowdsp::VariableOversampling<6> oversampler[NUM_CHANNELS]; // uses a 2*6=12th order Butterworth filter
  46. int oversamplingIndex = 2; // default is 2^oversamplingIndex == x4 oversampling
  47. DCBlocker blockDCFilter;
  48. bool blockDC = false;
  49. ChoppingKinky() {
  50. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  51. configParam(FOLD_A_PARAM, 0.f, 2.f, 0.f, "Gain/shape control for channel A");
  52. configParam(FOLD_B_PARAM, 0.f, 2.f, 0.f, "Gain/shape control for channel B");
  53. configParam(CV_A_PARAM, -1.f, 1.f, 0.f, "Channel A CV control attenuverter");
  54. configParam(CV_B_PARAM, -1.f, 1.f, 0.f, "Channel A CV control attenuverter");
  55. configInput(IN_A_INPUT, "A");
  56. configInput(IN_B_INPUT, "B");
  57. configInput(IN_GATE_INPUT, "Chopp");
  58. configInput(CV_A_INPUT, "CV A (with attenuator)");
  59. configInput(VCA_CV_A_INPUT, "CV A");
  60. configInput(CV_B_INPUT, "CV B (with attenuator)");
  61. configInput(VCA_CV_B_INPUT, "CV B");
  62. getInputInfo(CV_B_INPUT)->description = "Normalled to CV A (with attenuator) Input";
  63. configOutput(OUT_CHOPP_OUTPUT, "Chopp");
  64. configOutput(OUT_A_OUTPUT, "A");
  65. configOutput(OUT_B_OUTPUT, "B");
  66. cacheWaveshaperResponses();
  67. // calculate up/downsampling rates
  68. onSampleRateChange();
  69. }
  70. void onSampleRateChange() override {
  71. float sampleRate = APP->engine->getSampleRate();
  72. blockDCFilter.setFrequency(22.05 / sampleRate);
  73. for (int channel_idx = 0; channel_idx < NUM_CHANNELS; channel_idx++) {
  74. oversampler[channel_idx].setOversamplingIndex(oversamplingIndex);
  75. oversampler[channel_idx].reset(sampleRate);
  76. }
  77. }
  78. void process(const ProcessArgs& args) override {
  79. float gainA = params[FOLD_A_PARAM].getValue();
  80. gainA += params[CV_A_PARAM].getValue() * inputs[CV_A_INPUT].getVoltage() / 10.f;
  81. gainA += inputs[VCA_CV_A_INPUT].getVoltage() / 10.f;
  82. gainA = std::max(gainA, 0.f);
  83. // CV_B_INPUT is normalled to CV_A_INPUT (input with attenuverter)
  84. float gainB = params[FOLD_B_PARAM].getValue();
  85. gainB += params[CV_B_PARAM].getValue() * inputs[CV_B_INPUT].getNormalVoltage(inputs[CV_A_INPUT].getVoltage()) / 10.f;
  86. gainB += inputs[VCA_CV_B_INPUT].getVoltage() / 10.f;
  87. gainB = std::max(gainB, 0.f);
  88. const float inA = inputs[IN_A_INPUT].getVoltageSum();
  89. const float inB = inputs[IN_B_INPUT].getNormalVoltage(inputs[IN_A_INPUT].getVoltageSum());
  90. // if the CHOPP gate is wired in, do chop logic
  91. if (inputs[IN_GATE_INPUT].isConnected()) {
  92. // TODO: check rescale?
  93. trigger.process(rescale(inputs[IN_GATE_INPUT].getVoltageSum(), 0.1f, 2.f, 0.f, 1.f));
  94. outputAToChopp = trigger.isHigh();
  95. }
  96. // else zero-crossing detector on input A switches between A and B
  97. else {
  98. if (previousA > 0 && inA < 0) {
  99. outputAToChopp = false;
  100. }
  101. else if (previousA < 0 && inA > 0) {
  102. outputAToChopp = true;
  103. }
  104. }
  105. previousA = inA;
  106. const bool choppIsRequired = outputs[OUT_CHOPP_OUTPUT].isConnected();
  107. const bool aIsRequired = outputs[OUT_A_OUTPUT].isConnected() || choppIsRequired;
  108. const bool bIsRequired = outputs[OUT_B_OUTPUT].isConnected() || choppIsRequired;
  109. if (aIsRequired) {
  110. oversampler[CHANNEL_A].upsample(inA * gainA);
  111. }
  112. if (bIsRequired) {
  113. oversampler[CHANNEL_B].upsample(inB * gainB);
  114. }
  115. if (choppIsRequired) {
  116. oversampler[CHANNEL_CHOPP].upsample(outputAToChopp ? 1.f : 0.f);
  117. }
  118. float* osBufferA = oversampler[CHANNEL_A].getOSBuffer();
  119. float* osBufferB = oversampler[CHANNEL_B].getOSBuffer();
  120. float* osBufferChopp = oversampler[CHANNEL_CHOPP].getOSBuffer();
  121. for (int i = 0; i < oversampler[0].getOversamplingRatio(); i++) {
  122. if (aIsRequired) {
  123. //osBufferA[i] = wavefolderAResponse(osBufferA[i]);
  124. osBufferA[i] = wavefolderAResponseCached(osBufferA[i]);
  125. }
  126. if (bIsRequired) {
  127. //osBufferB[i] = wavefolderBResponse(osBufferB[i]);
  128. osBufferB[i] = wavefolderBResponseCached(osBufferB[i]);
  129. }
  130. if (choppIsRequired) {
  131. osBufferChopp[i] = osBufferChopp[i] * osBufferA[i] + (1.f - osBufferChopp[i]) * osBufferB[i];
  132. }
  133. }
  134. float outA = aIsRequired ? oversampler[CHANNEL_A].downsample() : 0.f;
  135. float outB = bIsRequired ? oversampler[CHANNEL_B].downsample() : 0.f;
  136. float outChopp = choppIsRequired ? oversampler[CHANNEL_CHOPP].downsample() : 0.f;
  137. if (blockDC) {
  138. outChopp = blockDCFilter.process(outChopp);
  139. }
  140. outputs[OUT_A_OUTPUT].setVoltage(outA);
  141. outputs[OUT_B_OUTPUT].setVoltage(outB);
  142. outputs[OUT_CHOPP_OUTPUT].setVoltage(outChopp);
  143. if (inputs[IN_GATE_INPUT].isConnected()) {
  144. lights[LED_A_LIGHT].setSmoothBrightness((float) outputAToChopp, args.sampleTime);
  145. lights[LED_B_LIGHT].setSmoothBrightness((float)(!outputAToChopp), args.sampleTime);
  146. }
  147. else {
  148. lights[LED_A_LIGHT].setBrightness(0.f);
  149. lights[LED_B_LIGHT].setBrightness(0.f);
  150. }
  151. }
  152. float wavefolderAResponseCached(float x) {
  153. if (x >= 0) {
  154. float j = rescale(clamp(x, 0.f, 10.f), 0.f, 10.f, 0, WAVESHAPE_CACHE_SIZE - 1);
  155. return interpolateLinear(waveshapeA, j);
  156. }
  157. else {
  158. return -wavefolderAResponseCached(-x);
  159. }
  160. }
  161. float wavefolderBResponseCached(float x) {
  162. if (x >= 0) {
  163. float j = rescale(clamp(x, 0.f, 10.f), 0.f, 10.f, 0, WAVESHAPE_CACHE_SIZE - 1);
  164. return interpolateLinear(waveshapeBPositive, j);
  165. }
  166. else {
  167. float j = rescale(clamp(-x, 0.f, 10.f), 0.f, 10.f, 0, WAVESHAPE_CACHE_SIZE - 1);
  168. return interpolateLinear(waveshapeBNegative, j);
  169. }
  170. }
  171. static float wavefolderAResponse(float x) {
  172. if (x < 0) {
  173. return -wavefolderAResponse(-x);
  174. }
  175. float xScaleFactor = 1.f / 20.f;
  176. float yScaleFactor = 12.5f;
  177. x = x * xScaleFactor;
  178. float piecewiseX1 = 0.087;
  179. float piecewiseX2 = 0.245;
  180. float piecewiseX3 = 0.3252;
  181. if (x < piecewiseX1) {
  182. float x_ = x / piecewiseX1;
  183. return -0.38 * yScaleFactor * (std::sin(M_PI * std::pow(x_, 0.8)) + 1.0 / (3 * 1.6) * std::sin(3 * M_PI * std::pow(x_, 0.8)));
  184. }
  185. else if (x < piecewiseX2) {
  186. float x_ = x - piecewiseX1;
  187. return -yScaleFactor * (-0.2 * std::sin(0.5 * M_PI * 12.69 * x_) - 0.24 * std::sin(1.5 * M_PI * 12.69 * x_));
  188. }
  189. else if (x < piecewiseX3) {
  190. float x_ = 9.8 * (x - piecewiseX2);
  191. return -0.33 * yScaleFactor * std::sin(x_ / 0.165) * (1 + 0.9 * std::pow(x_, 3) / (1.0 + 2.0 * std::pow(x_, 6)));
  192. }
  193. else {
  194. float x_ = (x - piecewiseX3) / 0.05;
  195. return yScaleFactor * ((0.4274 - 0.031) * std::exp(-std::pow(x_, 2.0)) + 0.031);
  196. }
  197. }
  198. static float wavefolderBResponse(float x) {
  199. float xScaleFactor = 1.f / 20.f;
  200. float yScaleFactor = 12.5f;
  201. x = x * xScaleFactor;
  202. // assymetric response
  203. if (x > 0) {
  204. float piecewiseX1 = 0.117;
  205. float piecewiseX2 = 0.2837;
  206. if (x < piecewiseX1) {
  207. float x_ = x / piecewiseX1;
  208. return -0.3 * yScaleFactor * (std::sin(M_PI * std::pow(x_, 0.67)) + 1.0 / (3 * 0.8) * std::sin(3 * M_PI * std::pow(x_, 0.67)));
  209. }
  210. else if (x < piecewiseX2) {
  211. float x_ = x - piecewiseX1;
  212. return 0.35 * yScaleFactor * std::sin(12. * M_PI * x_);
  213. }
  214. else {
  215. float x_ = (x - piecewiseX2);
  216. return 0.57 * yScaleFactor * std::tanh(x_ / 0.03);
  217. }
  218. }
  219. else {
  220. float piecewiseX1 = -0.105;
  221. float piecewiseX2 = -0.20722;
  222. if (x > piecewiseX1) {
  223. float x_ = x / piecewiseX1;
  224. return 0.37 * yScaleFactor * (std::sin(M_PI * std::pow(x_, 0.65)) + 1.0 / (3 * 1.2) * std::sin(3 * M_PI * std::pow(x_, 0.65)));
  225. }
  226. else if (x > piecewiseX2) {
  227. float x_ = x - piecewiseX1;
  228. return 0.2 * yScaleFactor * std::sin(15 * M_PI * x_) * (1.0 - 10.f * x_);
  229. }
  230. else {
  231. float x_ = (x - piecewiseX2) / 0.07;
  232. return yScaleFactor * ((0.4022 - 0.065) * std::exp(-std::pow(x_, 2)) + 0.065);
  233. }
  234. }
  235. }
  236. // functional form for waveshapers uses a lot of transcendental functions, so we cache
  237. // the response in a LUT
  238. void cacheWaveshaperResponses() {
  239. for (int i = 0; i < WAVESHAPE_CACHE_SIZE; ++i) {
  240. float x = rescale(i, 0, WAVESHAPE_CACHE_SIZE - 1, 0.0, 10.f);
  241. waveshapeA[i] = wavefolderAResponse(x);
  242. waveshapeBPositive[i] = wavefolderBResponse(+x);
  243. waveshapeBNegative[i] = wavefolderBResponse(-x);
  244. }
  245. }
  246. json_t* dataToJson() override {
  247. json_t* rootJ = json_object();
  248. json_object_set_new(rootJ, "filterDC", json_boolean(blockDC));
  249. json_object_set_new(rootJ, "oversamplingIndex", json_integer(oversampler[0].getOversamplingIndex()));
  250. return rootJ;
  251. }
  252. void dataFromJson(json_t* rootJ) override {
  253. json_t* filterDCJ = json_object_get(rootJ, "filterDC");
  254. if (filterDCJ) {
  255. blockDC = json_boolean_value(filterDCJ);
  256. }
  257. json_t* oversamplingIndexJ = json_object_get(rootJ, "oversamplingIndex");
  258. if (oversamplingIndexJ) {
  259. oversamplingIndex = json_integer_value(oversamplingIndexJ);
  260. onSampleRateChange();
  261. }
  262. }
  263. };
  264. struct ChoppingKinkyWidget : ModuleWidget {
  265. ChoppingKinkyWidget(ChoppingKinky* module) {
  266. setModule(module);
  267. setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/panels/ChoppingKinky.svg")));
  268. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, 0)));
  269. addChild(createWidget<Knurlie>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  270. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  271. addChild(createWidget<Knurlie>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  272. addParam(createParamCentered<Davies1900hLargeWhiteKnob>(mm2px(Vec(26.051, 21.999)), module, ChoppingKinky::FOLD_A_PARAM));
  273. addParam(createParamCentered<Davies1900hLargeWhiteKnob>(mm2px(Vec(26.051, 62.768)), module, ChoppingKinky::FOLD_B_PARAM));
  274. addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(10.266, 83.297)), module, ChoppingKinky::CV_A_PARAM));
  275. addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(30.277, 83.297)), module, ChoppingKinky::CV_B_PARAM));
  276. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(6.127, 27.843)), module, ChoppingKinky::IN_A_INPUT));
  277. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(26.057, 42.228)), module, ChoppingKinky::IN_GATE_INPUT));
  278. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(6.104, 56.382)), module, ChoppingKinky::IN_B_INPUT));
  279. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.209, 98.499)), module, ChoppingKinky::CV_A_INPUT));
  280. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.259, 98.499)), module, ChoppingKinky::VCA_CV_A_INPUT));
  281. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(25.308, 98.499)), module, ChoppingKinky::CV_B_INPUT));
  282. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(35.358, 98.499)), module, ChoppingKinky::VCA_CV_B_INPUT));
  283. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(20.23, 109.669)), module, ChoppingKinky::OUT_CHOPP_OUTPUT));
  284. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(31.091, 110.747)), module, ChoppingKinky::OUT_B_OUTPUT));
  285. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(9.589, 110.777)), module, ChoppingKinky::OUT_A_OUTPUT));
  286. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(26.057, 33.307)), module, ChoppingKinky::LED_A_LIGHT));
  287. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(26.057, 51.53)), module, ChoppingKinky::LED_B_LIGHT));
  288. }
  289. void appendContextMenu(Menu* menu) override {
  290. ChoppingKinky* module = dynamic_cast<ChoppingKinky*>(this->module);
  291. assert(module);
  292. menu->addChild(new MenuSeparator());
  293. menu->addChild(createBoolPtrMenuItem("Block DC on Chopp", "", &module->blockDC));
  294. menu->addChild(createMenuLabel("Oversampling mode"));
  295. menu->addChild(createIndexSubmenuItem("Oversampling",
  296. {"Off", "x2", "x4", "x8", "x16"},
  297. [ = ]() {
  298. return module->oversamplingIndex;
  299. },
  300. [ = ](int mode) {
  301. module->oversamplingIndex = mode;
  302. module->onSampleRateChange();
  303. }
  304. ));
  305. }
  306. };
  307. Model* modelChoppingKinky = createModel<ChoppingKinky, ChoppingKinkyWidget>("ChoppingKinky");