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.

438 lines
15KB

  1. #include <string.h>
  2. #include "JWModules.hpp"
  3. #include "dsp/digital.hpp"
  4. namespace rack_plugin_JW_Modules {
  5. enum InputColor {
  6. ORANGE_INPUT_COLOR,
  7. YELLOW_INPUT_COLOR,
  8. PURPLE_INPUT_COLOR,
  9. BLUE_INPUT_COLOR,
  10. WHITE_INPUT_COLOR
  11. };
  12. struct Ball {
  13. Rect box;
  14. Rect previousBox;
  15. Vec vel;
  16. SchmittTrigger resetTrigger, bumpTrigger;
  17. PulseGenerator northPulse, eastPulse, southPulse, westPulse, paddlePulse;
  18. NVGcolor color;
  19. void setPosition(float x, float y){
  20. previousBox.pos.x = box.pos.x;
  21. previousBox.pos.y = box.pos.y;
  22. box.pos.x = x;
  23. box.pos.y = y;
  24. }
  25. };
  26. struct Paddle {
  27. Rect box;
  28. bool locked = true;
  29. bool visible = true;
  30. Paddle(){
  31. box.size.x = 100.0;
  32. box.size.y = 10.0;
  33. }
  34. };
  35. struct BouncyBalls : Module {
  36. enum ParamIds {
  37. RESET_PARAM,
  38. TRIG_BTN_PARAM = RESET_PARAM + 4,
  39. VEL_X_PARAM = TRIG_BTN_PARAM + 4,
  40. VEL_Y_PARAM = VEL_X_PARAM + 4,
  41. SPEED_MULT_PARAM = VEL_Y_PARAM + 4,
  42. SCALE_X_PARAM = SPEED_MULT_PARAM + 4,
  43. SCALE_Y_PARAM,
  44. OFFSET_X_VOLTS_PARAM,
  45. OFFSET_Y_VOLTS_PARAM,
  46. PAD_ON_PARAM,
  47. NUM_PARAMS
  48. };
  49. enum InputIds {
  50. RESET_INPUT,
  51. TRIG_INPUT = RESET_INPUT + 4,
  52. VEL_X_INPUT = TRIG_INPUT + 4,
  53. VEL_Y_INPUT = VEL_X_INPUT + 4,
  54. SPEED_MULT_INPUT = VEL_Y_INPUT + 4,
  55. PAD_POS_X_INPUT = SPEED_MULT_INPUT + 4,
  56. PAD_POS_Y_INPUT,
  57. NUM_INPUTS
  58. };
  59. enum OutputIds {
  60. X_OUTPUT,
  61. Y_OUTPUT = X_OUTPUT + 4,
  62. N_OUTPUT = Y_OUTPUT + 4,
  63. E_OUTPUT = N_OUTPUT + 4,
  64. S_OUTPUT = E_OUTPUT + 4,
  65. W_OUTPUT = S_OUTPUT + 4,
  66. EDGE_HIT_OUTPUT = W_OUTPUT + 4,
  67. PAD_TRIG_OUTPUT = EDGE_HIT_OUTPUT + 4,
  68. NUM_OUTPUTS = PAD_TRIG_OUTPUT + 4
  69. };
  70. enum LightIds {
  71. PAD_ON_LIGHT,
  72. NUM_LIGHTS
  73. };
  74. float displayWidth = 0, displayHeight = 0;
  75. float ballRadius = 10;
  76. float ballStrokeWidth = 2;
  77. float minVolt = -5, maxVolt = 5;
  78. float velScale = 0.01;
  79. float rate = 1.0 / engineGetSampleRate();
  80. Ball *balls = new Ball[4];
  81. Paddle paddle;
  82. BouncyBalls() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  83. balls[0].color = nvgRGB(255, 151, 9);//orange
  84. balls[1].color = nvgRGB(255, 243, 9);//yellow
  85. balls[2].color = nvgRGB(144, 26, 252);//purple
  86. balls[3].color = nvgRGB(25, 150, 252);//blue
  87. for(int i=0; i<4; i++){
  88. balls[i].box.size.x = ballRadius*2 + ballStrokeWidth*2;
  89. balls[i].box.size.y = ballRadius*2 + ballStrokeWidth*2;
  90. balls[i].previousBox.size.x = balls[i].box.size.x;
  91. balls[i].previousBox.size.y = balls[i].box.size.y;
  92. }
  93. lights[PAD_ON_LIGHT].value = 1.0;
  94. }
  95. ~BouncyBalls() {
  96. delete [] balls;
  97. }
  98. void step() override;
  99. void reset() override {
  100. resetBalls();
  101. paddle.locked = true;
  102. paddle.visible = true;
  103. lights[PAD_ON_LIGHT].value = 1.0;
  104. }
  105. void onSampleRateChange() override {
  106. rate = 1.0 / engineGetSampleRate();
  107. }
  108. json_t *toJson() override {
  109. json_t *rootJ = json_object();
  110. json_object_set_new(rootJ, "paddleX", json_real(paddle.box.pos.x));
  111. json_object_set_new(rootJ, "paddleY", json_real(paddle.box.pos.y));
  112. json_object_set_new(rootJ, "paddleVisible", json_boolean(paddle.visible));
  113. return rootJ;
  114. }
  115. void fromJson(json_t *rootJ) override {
  116. json_t *xPosJ = json_object_get(rootJ, "paddleX");
  117. json_t *yPosJ = json_object_get(rootJ, "paddleY");
  118. paddle.box.pos.x = json_real_value(xPosJ);
  119. paddle.box.pos.y = json_real_value(yPosJ);
  120. json_t *paddleVisibleJ = json_object_get(rootJ, "paddleVisible");
  121. if (paddleVisibleJ){
  122. paddle.visible = json_is_true(paddleVisibleJ);
  123. }
  124. lights[PAD_ON_LIGHT].value = paddle.visible ? 1.0 : 0.0;
  125. }
  126. void resetBallAtIdx(int i){
  127. float totalBallStartWidth = ballRadius * 4.0 * 3.0;
  128. balls[i].box.pos.x = (displayWidth*0.5 - totalBallStartWidth*0.5) + ballRadius * 3.0 * i;
  129. balls[i].box.pos.y = displayHeight * 0.45;
  130. balls[i].vel.x = 0;
  131. balls[i].vel.y = 0;
  132. }
  133. void resetBalls(){
  134. for(int i=0; i<4; i++){
  135. resetBallAtIdx(i);
  136. }
  137. paddle.box.pos.x = displayWidth * 0.5 - paddle.box.size.x * 0.5;
  138. paddle.box.pos.y = displayHeight - 30;
  139. }
  140. };
  141. void BouncyBalls::step() {
  142. for(int i=0; i<4; i++){
  143. Ball &b = balls[i];
  144. Vec velocity = Vec(params[VEL_X_PARAM + i].value + inputs[VEL_X_INPUT + i].value,
  145. params[VEL_Y_PARAM + i].value + inputs[VEL_Y_INPUT + i].value);
  146. if (b.resetTrigger.process(inputs[RESET_INPUT + i].value + params[RESET_PARAM + i].value)) {
  147. resetBallAtIdx(i);
  148. b.vel = Vec(velocity.mult(velScale));
  149. }
  150. if (b.bumpTrigger.process(inputs[TRIG_INPUT + i].value + params[TRIG_BTN_PARAM + i].value)) {
  151. b.vel = b.vel.plus(velocity.mult(velScale));
  152. }
  153. if(paddle.visible && b.box.intersects(paddle.box)){
  154. if(b.previousBox.getBottomRight().y < paddle.box.getTopRight().y || //ball was above
  155. b.previousBox.getTopRight().y > paddle.box.getBottomRight().y){ //ball was below
  156. b.vel.y *= -1;
  157. }
  158. if(b.previousBox.getBottomRight().x < paddle.box.getBottomLeft().x || //ball was left
  159. b.previousBox.getBottomLeft().x > paddle.box.getBottomRight().x){ //ball was right
  160. b.vel.x *= -1;
  161. }
  162. b.paddlePulse.trigger(1e-3);
  163. }
  164. bool hitEdge = false;
  165. if(b.box.pos.x + b.box.size.x >= displayWidth){
  166. b.vel.x *= -1;
  167. b.eastPulse.trigger(1e-3);
  168. hitEdge = true;
  169. }
  170. if(b.box.pos.x <= 0){
  171. b.vel.x *= -1;
  172. b.westPulse.trigger(1e-3);
  173. hitEdge = true;
  174. }
  175. if(b.box.pos.y + b.box.size.y >= displayHeight){
  176. b.vel.y *= -1;
  177. b.southPulse.trigger(1e-3);
  178. hitEdge = true;
  179. }
  180. if(b.box.pos.y <= 0){
  181. b.vel.y *= -1;
  182. b.northPulse.trigger(1e-3);
  183. hitEdge = true;
  184. }
  185. if(paddle.visible && inputs[PAD_POS_X_INPUT].active){
  186. paddle.box.pos.x = -50 + clampfjw(rescalefjw(inputs[PAD_POS_X_INPUT].value, -5, 5, 50, displayWidth - 50), 50, displayWidth - 50);
  187. }
  188. if(paddle.visible && inputs[PAD_POS_Y_INPUT].active){
  189. paddle.box.pos.y = clampfjw(rescalefjw(inputs[PAD_POS_Y_INPUT].value, -5, 5, 0, displayHeight - 10), 0, displayHeight - 10);
  190. }
  191. //TODO rotate corners of rectangle
  192. if(outputs[X_OUTPUT + i].active)outputs[X_OUTPUT + i].value = (rescalefjw(b.box.pos.x, 0, displayWidth, minVolt, maxVolt) + params[OFFSET_X_VOLTS_PARAM].value) * params[SCALE_X_PARAM].value;
  193. if(outputs[Y_OUTPUT + i].active)outputs[Y_OUTPUT + i].value = (rescalefjw(b.box.pos.y, 0, displayHeight, maxVolt, minVolt) + params[OFFSET_Y_VOLTS_PARAM].value) * params[SCALE_Y_PARAM].value;//y is inverted because gui coords
  194. if(outputs[N_OUTPUT + i].active)outputs[N_OUTPUT + i].value = b.northPulse.process(rate) ? 10.0 : 0.0;
  195. if(outputs[E_OUTPUT + i].active)outputs[E_OUTPUT + i].value = b.eastPulse.process(rate) ? 10.0 : 0.0;
  196. if(outputs[S_OUTPUT + i].active)outputs[S_OUTPUT + i].value = b.southPulse.process(rate) ? 10.0 : 0.0;
  197. if(outputs[W_OUTPUT + i].active)outputs[W_OUTPUT + i].value = b.westPulse.process(rate) ? 10.0 : 0.0;
  198. if(outputs[EDGE_HIT_OUTPUT + i].active)outputs[EDGE_HIT_OUTPUT + i].value = hitEdge ? 10.0 : 0.0;
  199. if(outputs[PAD_TRIG_OUTPUT + i].active)outputs[PAD_TRIG_OUTPUT + i].value = b.paddlePulse.process(rate) ? 10.0 : 0.0;
  200. Vec newPos = b.box.pos.plus(b.vel.mult(params[SPEED_MULT_PARAM + i].value + inputs[SPEED_MULT_INPUT + i].value));
  201. b.setPosition(
  202. clampfjw(newPos.x, 0, displayWidth),
  203. clampfjw(newPos.y, 0, displayHeight)
  204. );
  205. }
  206. }
  207. struct BouncyBallDisplay : Widget {
  208. BouncyBalls *module;
  209. BouncyBallDisplay(){}
  210. void onMouseMove(EventMouseMove &e) override {
  211. Widget::onMouseMove(e);
  212. BouncyBalls* m = dynamic_cast<BouncyBalls*>(module);
  213. if(!m->paddle.locked && !m->inputs[BouncyBalls::PAD_POS_X_INPUT].active){
  214. m->paddle.box.pos.x = -50 + clampfjw(e.pos.x, 50, box.size.x - 50);
  215. }
  216. if(!m->paddle.locked && !m->inputs[BouncyBalls::PAD_POS_Y_INPUT].active){
  217. m->paddle.box.pos.y = clampfjw(e.pos.y, 0, box.size.y - 10);
  218. }
  219. }
  220. void onMouseDown(EventMouseDown &e) override {
  221. Widget::onMouseDown(e);
  222. BouncyBalls* m = dynamic_cast<BouncyBalls*>(module);
  223. m->paddle.locked = !m->paddle.locked;
  224. }
  225. void draw(NVGcontext *vg) override {
  226. //background
  227. nvgFillColor(vg, nvgRGB(20, 30, 33));
  228. nvgBeginPath(vg);
  229. nvgRect(vg, 0, 0, box.size.x, box.size.y);
  230. nvgFill(vg);
  231. if(module->paddle.visible){
  232. //paddle
  233. nvgFillColor(vg, nvgRGB(255, 255, 255));
  234. nvgBeginPath(vg);
  235. nvgRect(vg, module->paddle.box.pos.x, module->paddle.box.pos.y, 100, 10);
  236. nvgFill(vg);
  237. }
  238. for(int i=0; i<4; i++){
  239. nvgFillColor(vg, module->balls[i].color);
  240. nvgStrokeColor(vg, module->balls[i].color);
  241. nvgStrokeWidth(vg, 2);
  242. nvgBeginPath(vg);
  243. Vec ctr = module->balls[i].box.getCenter();
  244. nvgCircle(vg, ctr.x, ctr.y, module->ballRadius);
  245. nvgFill(vg);
  246. nvgStroke(vg);
  247. }
  248. }
  249. };
  250. struct BouncyBallsWidget : ModuleWidget {
  251. BouncyBallsWidget(BouncyBalls *module);
  252. void addButton(Vec pos, int param);
  253. void addColoredPort(int color, Vec pos, int param, bool input);
  254. };
  255. struct PaddleVisibleButton : TinyButton {
  256. void onMouseDown(EventMouseDown &e) override {
  257. TinyButton::onMouseDown(e);
  258. BouncyBallsWidget *widg = this->getAncestorOfType<BouncyBallsWidget>();
  259. BouncyBalls *bbs = dynamic_cast<BouncyBalls*>(widg->module);
  260. bbs->paddle.visible = !bbs->paddle.visible;
  261. bbs->lights[BouncyBalls::PAD_ON_LIGHT].value = bbs->paddle.visible ? 1.0 : 0.0;
  262. }
  263. };
  264. BouncyBallsWidget::BouncyBallsWidget(BouncyBalls *module) : ModuleWidget(module) {
  265. box.size = Vec(RACK_GRID_WIDTH*48, RACK_GRID_HEIGHT);
  266. SVGPanel *panel = new SVGPanel();
  267. panel->box.size = box.size;
  268. panel->setBackground(SVG::load(assetPlugin(plugin, "res/BouncyBalls.svg")));
  269. addChild(panel);
  270. BouncyBallDisplay *display = new BouncyBallDisplay();
  271. display->module = module;
  272. display->box.pos = Vec(270, 2);
  273. display->box.size = Vec(box.size.x - display->box.pos.x - 2, RACK_GRID_HEIGHT - 4);
  274. addChild(display);
  275. module->displayWidth = display->box.size.x;
  276. module->displayHeight = display->box.size.y;
  277. module->resetBalls();
  278. addChild(Widget::create<Screw_J>(Vec(31, 365)));
  279. addChild(Widget::create<Screw_W>(Vec(46, 365)));
  280. /////////////////////// INPUTS ///////////////////////
  281. float topY = 13.0, leftX = 40.0, xMult = 55.0, yAdder = 34.0, knobDist = 17.0;
  282. for(int x=0; x<4; x++){
  283. addColoredPort(x, Vec(leftX + x * xMult, topY), BouncyBalls::RESET_INPUT + x, true);
  284. addButton(Vec(leftX + knobDist + x * xMult, topY-5), BouncyBalls::RESET_PARAM + x);
  285. }
  286. topY+=yAdder;
  287. for(int x=0; x<4; x++){
  288. addColoredPort(x, Vec(leftX + x * xMult, topY), BouncyBalls::TRIG_INPUT + x, true);
  289. addButton(Vec(leftX + knobDist + x * xMult, topY-5), BouncyBalls::TRIG_BTN_PARAM + x);
  290. }
  291. topY+=yAdder;
  292. for(int x=0; x<4; x++){
  293. addColoredPort(x, Vec(leftX + x * xMult, topY), BouncyBalls::VEL_X_INPUT + x, true);
  294. addParam(ParamWidget::create<SmallWhiteKnob>(Vec(leftX + knobDist + x * xMult, topY-5), module, BouncyBalls::VEL_X_PARAM + x, -3.0, 3.0, 0.25));
  295. }
  296. topY+=yAdder;
  297. for(int x=0; x<4; x++){
  298. addColoredPort(x, Vec(leftX + x * xMult, topY), BouncyBalls::VEL_Y_INPUT + x, true);
  299. addParam(ParamWidget::create<SmallWhiteKnob>(Vec(leftX + knobDist + x * xMult, topY-5), module, BouncyBalls::VEL_Y_PARAM + x, -3.0, 3.0, 0.5));
  300. }
  301. topY+=yAdder;
  302. for(int x=0; x<4; x++){
  303. addColoredPort(x, Vec(leftX + x * xMult, topY), BouncyBalls::SPEED_MULT_INPUT + x, true);
  304. addParam(ParamWidget::create<SmallWhiteKnob>(Vec(leftX + knobDist + x * xMult, topY-5), module, BouncyBalls::SPEED_MULT_PARAM + x, 1.0, 20.0, 1.0));
  305. }
  306. /////////////////////// OUTPUTS ///////////////////////
  307. xMult = 25.0;
  308. yAdder = 25.0;
  309. topY+=yAdder + 5;
  310. leftX = 100;
  311. for(int x=0; x<4; x++){
  312. addColoredPort(x, Vec(leftX + x * xMult, topY), BouncyBalls::X_OUTPUT + x, false);
  313. }
  314. topY+=yAdder;
  315. for(int x=0; x<4; x++){
  316. addColoredPort(x, Vec(leftX + x * xMult, topY), BouncyBalls::Y_OUTPUT + x, false);
  317. }
  318. topY+=yAdder;
  319. for(int x=0; x<4; x++){
  320. addColoredPort(x, Vec(leftX + x * xMult, topY), BouncyBalls::N_OUTPUT + x, false);
  321. }
  322. topY+=yAdder;
  323. for(int x=0; x<4; x++){
  324. addColoredPort(x, Vec(leftX + x * xMult, topY), BouncyBalls::E_OUTPUT + x, false);
  325. }
  326. topY+=yAdder;
  327. for(int x=0; x<4; x++){
  328. addColoredPort(x, Vec(leftX + x * xMult, topY), BouncyBalls::S_OUTPUT + x, false);
  329. }
  330. topY+=yAdder;
  331. for(int x=0; x<4; x++){
  332. addColoredPort(x, Vec(leftX + x * xMult, topY), BouncyBalls::W_OUTPUT + x, false);
  333. }
  334. topY+=yAdder;
  335. for(int x=0; x<4; x++){
  336. addColoredPort(x, Vec(leftX + x * xMult, topY), BouncyBalls::EDGE_HIT_OUTPUT + x, false);
  337. }
  338. topY+=yAdder;
  339. for(int x=0; x<4; x++){
  340. addColoredPort(x, Vec(leftX + x * xMult, topY), BouncyBalls::PAD_TRIG_OUTPUT + x, false);
  341. }
  342. //white pad pos
  343. addColoredPort(WHITE_INPUT_COLOR, Vec(38, 220), BouncyBalls::PAD_POS_X_INPUT, true);
  344. addColoredPort(WHITE_INPUT_COLOR, Vec(38, 245), BouncyBalls::PAD_POS_Y_INPUT, true);
  345. addParam(ParamWidget::create<PaddleVisibleButton>(Vec(38, 270), module, BouncyBalls::PAD_ON_PARAM, 0.0, 1.0, 0.0));
  346. addChild(ModuleLightWidget::create<SmallLight<MyBlueValueLight>>(Vec(38+3.75, 270+3.75), module, BouncyBalls::PAD_ON_LIGHT));
  347. //scale and offset
  348. addParam(ParamWidget::create<SmallWhiteKnob>(Vec(222, 200), module, BouncyBalls::SCALE_X_PARAM, 0.01, 1.0, 0.5));
  349. addParam(ParamWidget::create<SmallWhiteKnob>(Vec(222, 242), module, BouncyBalls::SCALE_Y_PARAM, 0.01, 1.0, 0.5));
  350. addParam(ParamWidget::create<SmallWhiteKnob>(Vec(222, 290), module, BouncyBalls::OFFSET_X_VOLTS_PARAM, -5.0, 5.0, 5.0));
  351. addParam(ParamWidget::create<SmallWhiteKnob>(Vec(222, 338), module, BouncyBalls::OFFSET_Y_VOLTS_PARAM, -5.0, 5.0, 5.0));
  352. }
  353. void BouncyBallsWidget::addButton(Vec pos, int param) {
  354. addParam(ParamWidget::create<SmallButton>(pos, module, param, 0.0, 1.0, 0.0));
  355. }
  356. void BouncyBallsWidget::addColoredPort(int color, Vec pos, int param, bool input) {
  357. switch(color){
  358. case ORANGE_INPUT_COLOR:
  359. if(input) { addInput(Port::create<Orange_TinyPJ301MPort>(pos, Port::INPUT, module, param)); }
  360. else { addOutput(Port::create<Orange_TinyPJ301MPort>(pos, Port::OUTPUT, module, param)); }
  361. break;
  362. case YELLOW_INPUT_COLOR:
  363. if(input) { addInput(Port::create<Yellow_TinyPJ301MPort>(pos, Port::INPUT, module, param)); }
  364. else { addOutput(Port::create<Yellow_TinyPJ301MPort>(pos, Port::OUTPUT, module, param)); }
  365. break;
  366. case PURPLE_INPUT_COLOR:
  367. if(input) { addInput(Port::create<Purple_TinyPJ301MPort>(pos, Port::INPUT, module, param)); }
  368. else { addOutput(Port::create<Purple_TinyPJ301MPort>(pos, Port::OUTPUT, module, param)); }
  369. break;
  370. case BLUE_INPUT_COLOR:
  371. if(input) { addInput(Port::create<Blue_TinyPJ301MPort>(pos, Port::INPUT, module, param)); }
  372. else { addOutput(Port::create<Blue_TinyPJ301MPort>(pos, Port::OUTPUT, module, param)); }
  373. break;
  374. case WHITE_INPUT_COLOR:
  375. if(input) { addInput(Port::create<White_TinyPJ301MPort>(pos, Port::INPUT, module, param)); }
  376. else { addOutput(Port::create<White_TinyPJ301MPort>(pos, Port::OUTPUT, module, param)); }
  377. break;
  378. }
  379. }
  380. } // namespace rack_plugin_JW_Modules
  381. using namespace rack_plugin_JW_Modules;
  382. RACK_PLUGIN_MODEL_INIT(JW_Modules, BouncyBalls) {
  383. Model *modelBouncyBalls = Model::create<BouncyBalls, BouncyBallsWidget>("JW-Modules", "BouncyBalls", "Bouncy Balls", SEQUENCER_TAG, VISUAL_TAG);
  384. return modelBouncyBalls;
  385. }