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.

425 lines
13KB

  1. #include "global_pre.hpp"
  2. #include "Bidoo.hpp"
  3. #include "BidooComponents.hpp"
  4. #include "dsp/samplerate.hpp"
  5. #include "dsp/decimator.hpp"
  6. #include "dsp/filter.hpp"
  7. #include "global_ui.hpp"
  8. using namespace std;
  9. namespace rack_plugin_Bidoo {
  10. extern float sawTable[2048];
  11. extern float triTable[2048];
  12. struct CLACOS : Module {
  13. enum ParamIds {
  14. PITCH_PARAM,
  15. FINE_PARAM,
  16. DIST_X_PARAM,
  17. DIST_Y_PARAM = DIST_X_PARAM + 4,
  18. WAVEFORM_PARAM = DIST_Y_PARAM + 4,
  19. MODE_PARAM = WAVEFORM_PARAM + 4,
  20. SYNC_PARAM,
  21. FM_PARAM,
  22. NUM_PARAMS
  23. };
  24. enum InputIds {
  25. PITCH_INPUT,
  26. SYNC_INPUT,
  27. DIST_X_INPUT,
  28. DIST_Y_INPUT = DIST_X_INPUT + 4,
  29. WAVEFORM_INPUT = DIST_Y_PARAM + 4,
  30. FM_INPUT = WAVEFORM_INPUT + 4,
  31. NUM_INPUTS
  32. };
  33. enum OutputIds {
  34. MAIN_OUTPUT,
  35. NUM_OUTPUTS
  36. };
  37. enum LightIds {
  38. NUM_LIGHTS
  39. };
  40. bool analog = false;
  41. bool soft = false;
  42. float lastSyncValue = 0.0f;
  43. float phase = 0.0f;
  44. float phaseDist = 0.0f;
  45. float phaseDistX[4] = {0.5f};
  46. float phaseDistY[4] = {0.5f};
  47. int waveFormIndex[4] = {0};
  48. float freq;
  49. float pitch;
  50. bool syncEnabled = false;
  51. bool syncDirection = false;
  52. int index = 0, prevIndex = 3;
  53. Decimator<16, 16> mainDecimator;
  54. RCFilter sqrFilter;
  55. RCFilter mainFilter;
  56. // For analog detuning effect
  57. float pitchSlew = 0.0f;
  58. int pitchSlewIndex = 0;
  59. float sinBuffer[16] = {0.0f};
  60. float triBuffer[16] = {0.0f};
  61. float sawBuffer[16] = {0.0f};
  62. float sqrBuffer[16] = {0.0f};
  63. float mainBuffer[16] = {0.0f};
  64. void setPitch(float pitchKnob, float pitchCv) {
  65. // Compute frequency
  66. pitch = pitchKnob;
  67. if (analog) {
  68. // Apply pitch slew
  69. const float pitchSlewAmount = 3.0f;
  70. pitch += pitchSlew * pitchSlewAmount;
  71. }
  72. else {
  73. // Quantize coarse knob if digital mode
  74. pitch = roundf(pitch);
  75. }
  76. pitch += pitchCv;
  77. // Note C3
  78. freq = 261.626f * powf(2.0f, pitch / 12.0f);
  79. }
  80. void process(float deltaTime, float syncValue) {
  81. if (analog) {
  82. // Adjust pitch slew
  83. if (++pitchSlewIndex > 32) {
  84. const float pitchSlewTau = 100.0f; // Time constant for leaky integrator in seconds
  85. pitchSlew += (randomNormal() - pitchSlew / pitchSlewTau) / engineGetSampleRate();
  86. pitchSlewIndex = 0.0f;
  87. }
  88. }
  89. // Advance phase
  90. float deltaPhase = clamp(freq * deltaTime, 1e-6f, 0.5f);
  91. // Detect sync
  92. int syncIndex = -1; // Index in the oversample loop where sync occurs [0, OVERSAMPLE)
  93. float syncCrossing = 0.0f; // Offset that sync occurs [0.0, 1.0)
  94. if (syncEnabled) {
  95. syncValue -= 0.01f;
  96. if (syncValue > 0.0f && lastSyncValue <= 0.0f) {
  97. float deltaSync = syncValue - lastSyncValue;
  98. syncCrossing = 1.0f - syncValue / deltaSync;
  99. syncCrossing *= 16;
  100. syncIndex = (int)syncCrossing;
  101. syncCrossing -= syncIndex;
  102. }
  103. lastSyncValue = syncValue;
  104. }
  105. if (syncDirection)
  106. deltaPhase *= -1.0f;
  107. sqrFilter.setCutoff(40.0f * deltaTime);
  108. mainFilter.setCutoff(22000.0f * deltaTime);
  109. for (int i = 0; i < 16; i++) {
  110. if (syncIndex == i) {
  111. if (soft) {
  112. syncDirection = !syncDirection;
  113. deltaPhase *= -1.0f;
  114. }
  115. else {
  116. phase = 0.0f;
  117. phaseDist = 0.0f;
  118. }
  119. }
  120. if (phase<0.25f)
  121. index = 0;
  122. else if ((phase>=0.25f) && (phase<0.50f))
  123. index = 1;
  124. else if ((phase>=0.50f) && (phase<0.75f))
  125. index = 2;
  126. else
  127. index = 3;
  128. if (analog) {
  129. // Quadratic approximation of sine, slightly richer harmonics
  130. if (phaseDist < 0.5f)
  131. sinBuffer[i] = 1.0f - 16.0f * powf(phaseDist - 0.25f, 2.0f);
  132. else
  133. sinBuffer[i] = -1.0f + 16.0f * powf(phaseDist - 0.75f, 2.0f);
  134. sinBuffer[i] *= 1.08f;
  135. }
  136. else {
  137. sinBuffer[i] = sinf(2.0f * M_PI * phaseDist);
  138. }
  139. if (analog) {
  140. triBuffer[i] = 1.25f * interpolateLinear(triTable, phaseDist * 2047.f);
  141. }
  142. else {
  143. if (phaseDist < 0.25f)
  144. triBuffer[i] = 4.0f * phaseDist;
  145. else if (phaseDist < 0.75f)
  146. triBuffer[i] = 2.0f - 4.0f * phaseDist;
  147. else
  148. triBuffer[i] = -4.0f + 4.0f * phaseDist;
  149. }
  150. if (analog) {
  151. sawBuffer[i] = 1.66f * interpolateLinear(sawTable, phaseDist * 2047.f);
  152. }
  153. else {
  154. if (phaseDist < 0.5f)
  155. sawBuffer[i] = 2.0f * phaseDist;
  156. else
  157. sawBuffer[i] = -2.0f + 2.0f * phaseDist;
  158. }
  159. sqrBuffer[i] = (phaseDist < 0.5f) ? 1.0f : -1.0f;
  160. if (analog) {
  161. // Simply filter here
  162. sqrFilter.process(sqrBuffer[i]);
  163. sqrBuffer[i] = 0.71f * sqrFilter.highpass();
  164. }
  165. waveFormIndex[index] = inputs[WAVEFORM_INPUT+index].active ? clamp((int)(rescale(inputs[WAVEFORM_INPUT+index].value,0.0f,10.0f,0.0f,3.0f)),0,3) : clamp((int)(params[WAVEFORM_PARAM+index].value),0,3);
  166. if (waveFormIndex[index] == 0)
  167. mainBuffer[i]=sinBuffer[i];
  168. else if (waveFormIndex[index] == 1)
  169. mainBuffer[i]=triBuffer[i];
  170. else if (waveFormIndex[index] == 2)
  171. mainBuffer[i]=sawBuffer[i];
  172. else if (waveFormIndex[index] == 3)
  173. mainBuffer[i]=sqrBuffer[i];
  174. mainFilter.process(mainBuffer[i]);
  175. mainBuffer[i]=mainFilter.lowpass();
  176. // Advance phase
  177. phase += deltaPhase / 16.0f;
  178. phase = eucmod(phase, 1.0f);
  179. if (phase<=0.25f)
  180. index = 0;
  181. else if ((phase>0.25f) && (phase<=0.5f))
  182. index = 1;
  183. else if ((phase>0.5f) && (phase<=0.75f))
  184. index = 2;
  185. else
  186. index = 3;
  187. if (prevIndex!=index){
  188. phaseDist = phase;
  189. prevIndex = index;
  190. }
  191. else {
  192. if (rescale(phase,index*0.25f,(index+1)*0.25f,0.0f,1.0f)<=(phaseDistX[index]))
  193. phaseDist = min(phaseDist + (deltaPhase / 16.0f) * phaseDistY[index]/phaseDistX[index], (index+1)*0.25f);
  194. else
  195. phaseDist = min(phaseDist + (deltaPhase / 16.0f) * (1-phaseDistY[index])/(1-phaseDistX[index]), (index+1)*0.25f);
  196. phaseDist = eucmod(phaseDist, 1.0f);
  197. }
  198. }
  199. }
  200. float main() {
  201. return mainDecimator.process(mainBuffer);
  202. }
  203. float light() {
  204. return sinf(2.0f*M_PI * phase);
  205. }
  206. CLACOS() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  207. }
  208. void step() override;
  209. json_t *toJson() override {
  210. json_t *rootJ = json_object();
  211. for (int i=0; i<4; i++) {
  212. json_object_set_new(rootJ, ("phaseDistX" + to_string(i)).c_str(), json_real(phaseDistX[i]));
  213. json_object_set_new(rootJ, ("phaseDistY" + to_string(i)).c_str(), json_real(phaseDistY[i]));
  214. }
  215. return rootJ;
  216. }
  217. void fromJson(json_t *rootJ) override {
  218. for (int i=0; i<4; i++) {
  219. json_t *phaseDistXJ = json_object_get(rootJ, ("phaseDistX" + to_string(i)).c_str());
  220. if (phaseDistXJ) {
  221. phaseDistX[i] = json_real_value(phaseDistXJ);
  222. }
  223. json_t *phaseDistYJ = json_object_get(rootJ, ("phaseDistY" + to_string(i)).c_str());
  224. if (phaseDistYJ) {
  225. phaseDistY[i] = json_real_value(phaseDistYJ);
  226. }
  227. }
  228. }
  229. void randomize() override {
  230. for (int i=0; i<4; i++) {
  231. if ((!inputs[CLACOS::DIST_X_INPUT+i].active) && (!inputs[CLACOS::DIST_X_INPUT+i].active)) {
  232. phaseDistX[i] = randomUniform();
  233. phaseDistY[i] = randomUniform();
  234. }
  235. }
  236. }
  237. void reset() override {
  238. for (int i=0; i<4; i++) {
  239. if ((!inputs[CLACOS::DIST_X_INPUT+i].active) && (!inputs[CLACOS::DIST_X_INPUT+i].active)) {
  240. phaseDistX[i] = 0.5;
  241. phaseDistY[i] = 0.5;
  242. }
  243. }
  244. }
  245. };
  246. void CLACOS::step() {
  247. analog = params[MODE_PARAM].value > 0.0f;
  248. soft = params[SYNC_PARAM].value <= 0.0f;
  249. for (int i=0; i<4; i++) {
  250. if (inputs[DIST_X_INPUT+i].active)
  251. phaseDistX[i] = rescale(clamp(inputs[DIST_X_INPUT+i].value,0.0f,10.0f),0.0f,10.0f,0.01f,0.99f);
  252. if (inputs[DIST_Y_INPUT+i].active)
  253. phaseDistY[i] = rescale(clamp(inputs[DIST_Y_INPUT+i].value,0.0f,10.0f),0.0f,10.0f,0.01f,0.99f);
  254. }
  255. float pitchFine = 3.0f * quadraticBipolar(params[FINE_PARAM].value);
  256. float pitchCv = 12.0f * inputs[PITCH_INPUT].value;
  257. if (inputs[FM_INPUT].active) {
  258. pitchCv += quadraticBipolar(params[FM_PARAM].value) * 12.0f * inputs[FM_INPUT].value;
  259. }
  260. setPitch(params[PITCH_PARAM].value, pitchFine + pitchCv);
  261. syncEnabled = inputs[SYNC_INPUT].active;
  262. process(1.0f / engineGetSampleRate(), inputs[SYNC_INPUT].value);
  263. // Set output
  264. outputs[MAIN_OUTPUT].value = 5.0f * main();
  265. }
  266. struct CLACOSDisplay : TransparentWidget {
  267. CLACOS *module;
  268. int frame = 0;
  269. string waveForm;
  270. int segmentNumber = 0;
  271. float initX = 0.0f;
  272. float initY = 0.0f;
  273. float dragX = 0.0f;
  274. float dragY = 0.0f;
  275. CLACOSDisplay() {}
  276. void onDragStart(EventDragStart &e) override {
  277. dragX = RACK_PLUGIN_UI_RACKWIDGET->lastMousePos.x;
  278. dragY = RACK_PLUGIN_UI_RACKWIDGET->lastMousePos.y;
  279. }
  280. void onDragMove(EventDragMove &e) override {
  281. if ((!module->inputs[CLACOS::DIST_X_INPUT + segmentNumber].active) && (!module->inputs[CLACOS::DIST_X_INPUT + segmentNumber].active)) {
  282. float newDragX = RACK_PLUGIN_UI_RACKWIDGET->lastMousePos.x;
  283. float newDragY = RACK_PLUGIN_UI_RACKWIDGET->lastMousePos.y;
  284. module->phaseDistX[segmentNumber] = rescale(clamp(initX+(newDragX-dragX),0.0f,70.0f), 0.0f, 70.0f, 0.01f,0.99f);
  285. module->phaseDistY[segmentNumber] = rescale(clamp(initY-(newDragY-dragY),0.0f,70.0f), 0.0f, 70.0f, 0.01f,0.99f);
  286. }
  287. }
  288. void onMouseDown(EventMouseDown &e) override {
  289. if (e.button == 0) {
  290. e.consumed = true;
  291. e.target = this;
  292. initX = e.pos.x;
  293. initY = 70.0f - e.pos.y;
  294. }
  295. }
  296. void draw(NVGcontext *vg) override {
  297. if (++frame >= 4) {
  298. frame = 0;
  299. if (module->waveFormIndex[segmentNumber] == 0)
  300. waveForm="SIN";
  301. else if (module->waveFormIndex[segmentNumber] == 1)
  302. waveForm="TRI";
  303. else if (module->waveFormIndex[segmentNumber] == 2)
  304. waveForm="SAW";
  305. else if (module->waveFormIndex[segmentNumber] == 3)
  306. waveForm="SQR";
  307. }
  308. nvgFontSize(vg, 10.0f);
  309. nvgFillColor(vg, nvgRGBA(42, 87, 117, 255));
  310. nvgText(vg, 12.0f, 79.0f, waveForm.c_str(), NULL);
  311. // Draw ref lines
  312. nvgStrokeColor(vg, nvgRGBA(0xff, 0xff, 0xff, 0x80));
  313. {
  314. nvgBeginPath(vg);
  315. nvgMoveTo(vg, 0.0f, 35.0f);
  316. nvgLineTo(vg, 70.0f, 35.0f);
  317. nvgMoveTo(vg, 35.0f, 0.0f);
  318. nvgLineTo(vg, 35.0f, 70.0f);
  319. nvgClosePath(vg);
  320. }
  321. nvgStroke(vg);
  322. // Draw phase distortion
  323. nvgStrokeColor(vg, nvgRGBA(42, 87, 117, 255));
  324. {
  325. nvgBeginPath(vg);
  326. nvgMoveTo(vg, 0.0f, 70.0f);
  327. nvgLineTo(vg, (int)(rescale(module->phaseDistX[segmentNumber], 0.0f,1.0f,0.0f,70.0f)) , 70.0f - (int)(rescale(module->phaseDistY[segmentNumber], 0.0f,1.0f,0.01f,70.0f)));
  328. nvgMoveTo(vg, (int)(rescale(module->phaseDistX[segmentNumber], 0.0f,1.0f,0.0f,70.0f)) , 70.0f - (int)(rescale(module->phaseDistY[segmentNumber], 0.0f,1.0f,0.01f,70.0f)));
  329. nvgLineTo(vg, 70.0f, 0.0f);
  330. nvgClosePath(vg);
  331. }
  332. nvgStroke(vg);
  333. }
  334. };
  335. struct CLACOSWidget : ModuleWidget {
  336. CLACOSWidget(CLACOS *module) : ModuleWidget(module) {
  337. setPanel(SVG::load(assetPlugin(plugin, "res/CLACOS.svg")));
  338. addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
  339. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  340. addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  341. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  342. for (int i = 0; i < 4; i++)
  343. {
  344. {
  345. CLACOSDisplay *display = new CLACOSDisplay();
  346. display->module = module;
  347. display->segmentNumber = i;
  348. display->box.pos = Vec(3.0f + 74.0f * (i%2), 113.0f + 102.0f * round(i/2));
  349. display->box.size = Vec(70.0f, 70.0f);
  350. addChild(display);
  351. addParam(ParamWidget::create<BidooBlueTrimpot>(Vec(2.0f + 74.0f * (i%2), 194.0f + 102.0f * round(i/2)), module, CLACOS::WAVEFORM_PARAM + i, 0.0f, 3.0f, 0.0f));
  352. addInput(Port::create<TinyPJ301MPort>(Vec(22.0f + 74.0f * (i%2), 196.0f + 102.0f * round(i/2)), Port::INPUT, module, CLACOS::WAVEFORM_INPUT + i));
  353. addInput(Port::create<TinyPJ301MPort>(Vec(40.0f + 74.0f * (i%2), 196.0f + 102.0f * round(i/2)), Port::INPUT, module, CLACOS::DIST_X_INPUT + i));
  354. addInput(Port::create<TinyPJ301MPort>(Vec(57.0f + 74.0f * (i%2), 196.0f + 102.0f * round(i/2)), Port::INPUT, module, CLACOS::DIST_Y_INPUT + i));
  355. }
  356. }
  357. addParam(ParamWidget::create<CKSS>(Vec(15.0f, 80.0f), module, CLACOS::MODE_PARAM, 0.0f, 1.0f, 1.0f));
  358. addParam(ParamWidget::create<CKSS>(Vec(119.0f, 80.0f), module, CLACOS::SYNC_PARAM, 0.0f, 1.0f, 1.0f));
  359. addParam(ParamWidget::create<BidooLargeBlueKnob>(Vec(57.0f, 45.0f), module, CLACOS::PITCH_PARAM, -54.0f, 54.0f, 0.0f));
  360. addParam(ParamWidget::create<BidooBlueTrimpot>(Vec(114.0f,45.0f), module, CLACOS::FINE_PARAM, -1.0f, 1.0f, 0.0f));
  361. addParam(ParamWidget::create<BidooBlueTrimpot>(Vec(18.0f,45.0f), module, CLACOS::FM_PARAM, 0.0f, 1.0f, 0.0f));
  362. addInput(Port::create<TinyPJ301MPort>(Vec(38.0f, 83.0f), Port::INPUT, module, CLACOS::FM_INPUT));
  363. addInput(Port::create<PJ301MPort>(Vec(11.0f, 330.0f), Port::INPUT, module, CLACOS::PITCH_INPUT));
  364. addInput(Port::create<PJ301MPort>(Vec(45.0f, 330.0f), Port::INPUT, module, CLACOS::SYNC_INPUT));
  365. addOutput(Port::create<PJ301MPort>(Vec(114.0f, 330.0f), Port::OUTPUT, module, CLACOS::MAIN_OUTPUT));
  366. }
  367. };
  368. } // namespace rack_plugin_Bidoo
  369. using namespace rack_plugin_Bidoo;
  370. RACK_PLUGIN_MODEL_INIT(Bidoo, CLACOS) {
  371. Model *modelCLACOS = Model::create<CLACOS, CLACOSWidget>("Bidoo", "clACos", "clACos oscillator", OSCILLATOR_TAG);
  372. return modelCLACOS;
  373. }