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
13KB

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