├── Body.stl ├── .gitattributes ├── Big Ass-Wheel.stl ├── Wheel Insert.stl ├── Big Wheel model.f3d ├── Support Waffle.stl ├── Wiring Diagram.pdf ├── Big Wheel Baseplate.ai ├── Waffle Baseplate Combo.stl ├── Test └── Test.ino ├── LICENSE ├── Big_Wheel_Macro_Pad_Firmware ├── pins.h ├── actions.h └── Big_Wheel_Macro_Pad_Firmware.ino ├── README.md ├── Big Wheel Baseplate.dxf └── Wiring Diagram.sch /Body.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackFreedman/Big-Wheel/HEAD/Body.stl -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /Big Ass-Wheel.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackFreedman/Big-Wheel/HEAD/Big Ass-Wheel.stl -------------------------------------------------------------------------------- /Wheel Insert.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackFreedman/Big-Wheel/HEAD/Wheel Insert.stl -------------------------------------------------------------------------------- /Big Wheel model.f3d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackFreedman/Big-Wheel/HEAD/Big Wheel model.f3d -------------------------------------------------------------------------------- /Support Waffle.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackFreedman/Big-Wheel/HEAD/Support Waffle.stl -------------------------------------------------------------------------------- /Wiring Diagram.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackFreedman/Big-Wheel/HEAD/Wiring Diagram.pdf -------------------------------------------------------------------------------- /Big Wheel Baseplate.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackFreedman/Big-Wheel/HEAD/Big Wheel Baseplate.ai -------------------------------------------------------------------------------- /Waffle Baseplate Combo.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackFreedman/Big-Wheel/HEAD/Waffle Baseplate Combo.stl -------------------------------------------------------------------------------- /Test/Test.ino: -------------------------------------------------------------------------------- 1 | void setup() { 2 | Serial.begin(9600); 3 | 4 | // put your setup code here, to run once: 5 | pinMode(2, OUTPUT); 6 | pinMode(6, OUTPUT); 7 | pinMode(7, OUTPUT); 8 | 9 | digitalWrite(2, LOW); 10 | digitalWrite(6, HIGH); 11 | digitalWrite(7, HIGH); 12 | 13 | pinMode(3, INPUT_PULLUP); 14 | pinMode(5, INPUT_PULLUP); 15 | pinMode(4, INPUT_PULLUP); 16 | 17 | delay(5000); 18 | for (int i = 0; i < 100; i++) { 19 | Keyboard.press(KEY_RIGHT_ARROW); 20 | Keyboard.release(KEY_RIGHT_ARROW); 21 | } 22 | } 23 | 24 | void loop() { 25 | // put your main code here, to run repeatedly: 26 | Serial.print("1: "); 27 | Serial.print(digitalRead(3)); 28 | Serial.print(" 2: "); 29 | Serial.print(digitalRead(4)); 30 | Serial.print(" 3: "); 31 | Serial.println(digitalRead(5)); 32 | 33 | delay(20); 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Zack Freedman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Big_Wheel_Macro_Pad_Firmware/pins.h: -------------------------------------------------------------------------------- 1 | #define topKnobA 0 2 | #define topKnobB 1 3 | #define middleKnobA 17 4 | #define middleKnobB 16 5 | #define lowerKnobA 15 6 | #define lowerKnobB 14 7 | #define wheelA 11 8 | #define wheelB 10 9 | 10 | const byte allKnobPins[] = {topKnobA, topKnobB, 11 | middleKnobA, middleKnobB, 12 | lowerKnobA, lowerKnobB, 13 | wheelA, wheelB 14 | }; 15 | 16 | // To simplify wiring, keys are actually in two separate matrices. 17 | // The Teensy has like a million GPIO's, I can sacrifice four. 18 | #define leftButtonsA 23 19 | #define leftButtonsB 22 20 | #define leftButtonsC 18 21 | #define leftButtons1 21 22 | #define leftButtons2 20 23 | #define leftButtons3 19 24 | 25 | #define rightButtonsA 2 26 | #define rightButtonsB 6 27 | #define rightButtonsC 7 28 | #define rightButtons1 3 29 | #define rightButtons2 5 30 | #define rightButtons3 4 31 | 32 | const byte leftButtonRowPins[] = {leftButtonsA, leftButtonsB, leftButtonsC}; 33 | const byte rightButtonRowPins[] = {rightButtonsA, rightButtonsB, rightButtonsC}; 34 | 35 | const byte allButtonRowPins[] = {leftButtonsA, leftButtonsB, leftButtonsC, 36 | rightButtonsA, rightButtonsB, rightButtonsC 37 | }; 38 | 39 | const byte leftButtonColumnPins[] = {leftButtons1, leftButtons2, leftButtons3}; 40 | const byte rightButtonColumnPins[] = {rightButtons1, rightButtons2, rightButtons3}; 41 | 42 | const byte allButtonColumnPins[] = {leftButtons1, leftButtons2, leftButtons3, 43 | rightButtons1, rightButtons2, rightButtons3 44 | }; 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Big Wheel 2 | 3D-printed video control surface 3 | 4 | Slapped together in a weekend by Zack Freedman of Voidstar Lab for [a YouTube video! Check it out!](https://www.youtube.com/watch?v=72a85tWOJVY) 5 | 6 | Materials: 7 | - 1x each of: 8 | - Big Ass-Wheel.stl 9 | - Body.stl 10 | - Wheel Insert.stl 11 | - 1x of either of the following: 12 | - One print of Waffle Baseplate Combo.stl 13 | - One print of Support Waffle.stl and one laser-cut Big Wheel Baseplate.ai 14 | - 7x 10mm M2.5 screws 15 | - 6x M2.5 heat-set inserts, nominal 5mm length 16 | - 1x M2.5 nut 17 | - 14x Cherry MX-compatible keyswitches 18 | - 14x keycaps for keyswitches 19 | - 14x rectifier diodes, such as the 1N4004 20 | - 4x PEC12-compatible rotary encoders with pushbutton, 15mm flatted shaft preferred 21 | - 3x encoder knobs, max. diameter 22mm 22 | - 6x rubber feet, thicker than the head of the M2.5 screws 23 | - 1x Teensy LC, Teensy 3.x, or Teensy 4.0 24 | - Zap-a-Gap or other adhesive that can bond your 3D-printing material 25 | - Lots of wire and heatshrink 26 | 27 | Instructions: 28 | 1. [Watch video](https://www.youtube.com/watch?v=72a85tWOJVY) to see construction overview 29 | 2. Fabricate 3D-printed and laser-cut parts 30 | 3. If using laser-cut Big Wheel Baseplate, glue Support Waffle onto the etched side of the Baseplate. 31 | 4. Snap keyswitches and encoders into Body 32 | 5. Using wiring diagram, build switch matrix. Take care to route wires away from surface that contacts Support Waffle. 33 | 6. Connect switch matrix and encoders to Teensy. Take care to leave extra wire! 34 | 7. Slide Teensy into its rails. It should fit snugly - if it's loose, add a bit of electrical tape to tighten things up. 35 | 8. Connect to computer, load firmware, and test 36 | 9. Using a soldering iron, apply heat-set inserts to the bosses on the Body. 37 | 10. Mount Waffle and Baseplate combo to the Body using the screws. 38 | 11. Apply rubber feet to Baseplate. 39 | 12. Slip nut into slot on Big Ass-Wheel and insert screw through side of wheel. 40 | 13. Mount Big Ass-Wheel on center encoder and tighten screw. It should turn smoothly but not wobble. 41 | 14. Glue Wheel Insert onto Big Ass-Wheel. 42 | 15. Mount remaining encoder knobs and keycaps. 43 | 16. If desired, modify firmware to bind your preferred commands to the controls. 44 | 17. Let Zack know you built the project! -------------------------------------------------------------------------------- /Big_Wheel_Macro_Pad_Firmware/actions.h: -------------------------------------------------------------------------------- 1 | // These define special actions that take more than one keystroke 2 | #define NO_ACTION 0x00 3 | #define ACTION_SOURCE_MONITOR_WINDOW -1 4 | #define ACTION_TIMELINES_WINDOW -2 5 | #define ACTION_AUDIO_CLIP_MIXER_WINDOW -3 6 | #define ACTION_UNDO -4 7 | #define ACTION_JUMP_TO_IN_POINT -5 8 | #define ACTION_CTRL_KEY -8 9 | 10 | // Note that the special shuttling behavior isn't in this file, since it needs to be processed separately 11 | 12 | // This maps matrix key positions onto their logical control ID's 13 | const byte keyMatrix[2][3][3] = { 14 | { 15 | {2, 1, 0}, 16 | {8, 7, 11}, 17 | {17, 12, 15} 18 | }, 19 | { 20 | {5, 3, 4}, 21 | {9, 16, 13}, 22 | {6, 14, 10} 23 | } 24 | }; 25 | 26 | const int switchAssignments[] = 27 | {KEY_F, KEY_Q, KEY_W, KEY_I, KEY_O, 28 | KEY_M, ACTION_AUDIO_CLIP_MIXER_WINDOW, ACTION_SOURCE_MONITOR_WINDOW, KEY_V, KEY_QUOTE, 29 | KEY_BACKSLASH, ACTION_TIMELINES_WINDOW, KEY_A, KEY_COMMA, ACTION_JUMP_TO_IN_POINT, 30 | ACTION_CTRL_KEY, ACTION_UNDO, 31 | NO_ACTION // I wired the big knob click into the circuit, even though pressing it is physically impossible 32 | }; 33 | 34 | // CCW, CW 35 | const int topKnobAssignments[] = {KEY_RIGHT_BRACE, KEY_LEFT_BRACE}; 36 | const int middleKnobAssignments[] = {KEY_EQUAL, KEY_MINUS}; 37 | const int lowerKnobAssignments[] = {KEY_DOWN, KEY_UP}; 38 | const int wheelAssignments[] = {KEY_RIGHT, KEY_LEFT}; 39 | 40 | void performAction(int action) { 41 | switch (action) { 42 | case NO_ACTION: 43 | break; 44 | case ACTION_SOURCE_MONITOR_WINDOW: 45 | Keyboard.press(MODIFIERKEY_SHIFT); 46 | Keyboard.press(KEY_2); 47 | Keyboard.release(KEY_2); 48 | Keyboard.release(MODIFIERKEY_SHIFT); 49 | break; 50 | case ACTION_TIMELINES_WINDOW: 51 | Keyboard.press(MODIFIERKEY_SHIFT); 52 | Keyboard.press(KEY_3); 53 | Keyboard.release(KEY_3); 54 | Keyboard.release(MODIFIERKEY_SHIFT); 55 | break; 56 | case ACTION_AUDIO_CLIP_MIXER_WINDOW: 57 | Keyboard.press(MODIFIERKEY_SHIFT); 58 | Keyboard.press(KEY_9); 59 | Keyboard.release(KEY_9); 60 | Keyboard.release(MODIFIERKEY_SHIFT); 61 | break; 62 | case ACTION_UNDO: 63 | Keyboard.press(MODIFIERKEY_GUI); // I think I remapped this on my PC. Use MODIFIERKEY_CTRL if this doesn't work 64 | Keyboard.press(KEY_Z); 65 | Keyboard.release(KEY_Z); 66 | Keyboard.release(MODIFIERKEY_GUI); 67 | break; 68 | case ACTION_JUMP_TO_IN_POINT: 69 | Keyboard.press(MODIFIERKEY_SHIFT); 70 | Keyboard.press(KEY_I); 71 | Keyboard.release(KEY_I); 72 | Keyboard.release(MODIFIERKEY_SHIFT); 73 | break; 74 | case ACTION_CTRL_KEY: 75 | Keyboard.press(MODIFIERKEY_GUI); 76 | Serial.println("Ctrl down"); 77 | break; 78 | default: 79 | Keyboard.press(action); 80 | Keyboard.release(action); 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /Big Wheel Baseplate.dxf: -------------------------------------------------------------------------------- 1 | 0 2 | SECTION 3 | 2 4 | HEADER 5 | 9 6 | $INSUNITS 7 | 70 8 | 4 9 | 9 10 | $ACADVER 11 | 1 12 | AC1014 13 | 9 14 | $HANDSEED 15 | 5 16 | FFFF 17 | 0 18 | ENDSEC 19 | 0 20 | SECTION 21 | 2 22 | TABLES 23 | 0 24 | TABLE 25 | 2 26 | VPORT 27 | 5 28 | 8 29 | 100 30 | AcDbSymbolTable 31 | 0 32 | ENDTAB 33 | 0 34 | TABLE 35 | 2 36 | LTYPE 37 | 5 38 | 5 39 | 100 40 | AcDbSymbolTable 41 | 0 42 | LTYPE 43 | 5 44 | 14 45 | 100 46 | AcDbSymbolTableRecord 47 | 100 48 | AcDbLinetypeTableRecord 49 | 2 50 | BYBLOCK 51 | 70 52 | 0 53 | 0 54 | LTYPE 55 | 5 56 | 15 57 | 100 58 | AcDbSymbolTableRecord 59 | 100 60 | AcDbLinetypeTableRecord 61 | 2 62 | BYLAYER 63 | 70 64 | 0 65 | 0 66 | ENDTAB 67 | 0 68 | TABLE 69 | 2 70 | LAYER 71 | 5 72 | 2 73 | 100 74 | AcDbSymbolTable 75 | 70 76 | 2 77 | 0 78 | LAYER 79 | 5 80 | 50 81 | 100 82 | AcDbSymbolTableRecord 83 | 100 84 | AcDbLayerTableRecord 85 | 2 86 | 0 87 | 70 88 | 0 89 | 6 90 | CONTINUOUS 91 | 0 92 | ENDTAB 93 | 0 94 | TABLE 95 | 2 96 | STYLE 97 | 5 98 | 3 99 | 100 100 | AcDbSymbolTable 101 | 70 102 | 1 103 | 0 104 | STYLE 105 | 5 106 | 11 107 | 100 108 | AcDbSymbolTableRecord 109 | 100 110 | AcDbTextStyleTableRecord 111 | 2 112 | STANDARD 113 | 70 114 | 0 115 | 0 116 | ENDTAB 117 | 0 118 | TABLE 119 | 2 120 | VIEW 121 | 5 122 | 6 123 | 100 124 | AcDbSymbolTable 125 | 70 126 | 0 127 | 0 128 | ENDTAB 129 | 0 130 | TABLE 131 | 2 132 | UCS 133 | 5 134 | 7 135 | 100 136 | AcDbSymbolTable 137 | 70 138 | 0 139 | 0 140 | ENDTAB 141 | 0 142 | TABLE 143 | 2 144 | APPID 145 | 5 146 | 9 147 | 100 148 | AcDbSymbolTable 149 | 70 150 | 2 151 | 0 152 | APPID 153 | 5 154 | 12 155 | 100 156 | AcDbSymbolTableRecord 157 | 100 158 | AcDbRegAppTableRecord 159 | 2 160 | ACAD 161 | 70 162 | 0 163 | 0 164 | ENDTAB 165 | 0 166 | TABLE 167 | 2 168 | DIMSTYLE 169 | 5 170 | A 171 | 100 172 | AcDbSymbolTable 173 | 70 174 | 1 175 | 0 176 | ENDTAB 177 | 0 178 | TABLE 179 | 2 180 | BLOCK_RECORD 181 | 5 182 | 1 183 | 100 184 | AcDbSymbolTable 185 | 70 186 | 1 187 | 0 188 | BLOCK_RECORD 189 | 5 190 | 1F 191 | 100 192 | AcDbSymbolTableRecord 193 | 100 194 | AcDbBlockTableRecord 195 | 2 196 | *MODEL_SPACE 197 | 0 198 | BLOCK_RECORD 199 | 5 200 | 1B 201 | 100 202 | AcDbSymbolTableRecord 203 | 100 204 | AcDbBlockTableRecord 205 | 2 206 | *PAPER_SPACE 207 | 0 208 | ENDTAB 209 | 0 210 | ENDSEC 211 | 0 212 | SECTION 213 | 2 214 | BLOCKS 215 | 0 216 | BLOCK 217 | 5 218 | 20 219 | 100 220 | AcDbEntity 221 | 100 222 | AcDbBlockBegin 223 | 2 224 | *MODEL_SPACE 225 | 0 226 | ENDBLK 227 | 5 228 | 21 229 | 100 230 | AcDbEntity 231 | 100 232 | AcDbBlockEnd 233 | 0 234 | BLOCK 235 | 5 236 | 1C 237 | 100 238 | AcDbEntity 239 | 100 240 | AcDbBlockBegin 241 | 2 242 | *PAPER_SPACE 243 | 0 244 | ENDBLK 245 | 5 246 | 1D 247 | 100 248 | AcDbEntity 249 | 100 250 | AcDbBlockEnd 251 | 0 252 | ENDSEC 253 | 0 254 | SECTION 255 | 2 256 | ENTITIES 257 | 0 258 | ARC 259 | 5 260 | 100 261 | 100 262 | AcDbEntity 263 | 8 264 | 0 265 | 100 266 | AcDbCircle 267 | 10 268 | -72.999999999999929 269 | 20 270 | 7.4999999999996518 271 | 30 272 | 0 273 | 40 274 | 17.499999999999989 275 | 210 276 | 0 277 | 220 278 | 0 279 | 230 280 | 1 281 | 100 282 | AcDbArc 283 | 50 284 | 90.000000000000142 285 | 51 286 | 180 287 | 0 288 | LINE 289 | 5 290 | 101 291 | 100 292 | AcDbEntity 293 | 8 294 | 0 295 | 100 296 | AcDbLine 297 | 10 298 | 82 299 | 20 300 | 25.000000000000078 301 | 30 302 | 0 303 | 11 304 | -72.999999999999972 305 | 21 306 | 24.999999999999641 307 | 31 308 | 0 309 | 0 310 | LINE 311 | 5 312 | 102 313 | 100 314 | AcDbEntity 315 | 8 316 | 0 317 | 100 318 | AcDbLine 319 | 10 320 | -90.499999999999915 321 | 20 322 | 7.4999999999996572 323 | 30 324 | 0 325 | 11 326 | -90.499999999999986 327 | 21 328 | -43.499999999999801 329 | 31 330 | 0 331 | 0 332 | ARC 333 | 5 334 | 103 335 | 100 336 | AcDbEntity 337 | 8 338 | 0 339 | 100 340 | AcDbCircle 341 | 10 342 | -72.999999999999986 343 | 20 344 | -43.499999999999815 345 | 30 346 | 0 347 | 40 348 | 17.500000000000007 349 | 210 350 | 0 351 | 220 352 | 0 353 | 230 354 | 1 355 | 100 356 | AcDbArc 357 | 50 358 | 180 359 | 51 360 | 269.99999999999983 361 | 0 362 | ARC 363 | 5 364 | 104 365 | 100 366 | AcDbEntity 367 | 8 368 | 0 369 | 100 370 | AcDbCircle 371 | 10 372 | 82 373 | 20 374 | -43.499999999999986 375 | 30 376 | 0 377 | 40 378 | 17.5 379 | 210 380 | 0 381 | 220 382 | 0 383 | 230 384 | 1 385 | 100 386 | AcDbArc 387 | 50 388 | 269.99999999999989 389 | 51 390 | 360 391 | 0 392 | LINE 393 | 5 394 | 105 395 | 100 396 | AcDbEntity 397 | 8 398 | 0 399 | 100 400 | AcDbLine 401 | 10 402 | 99.5 403 | 20 404 | -43.5 405 | 30 406 | 0 407 | 11 408 | 99.500000000000028 409 | 21 410 | 7.5000000000000711 411 | 31 412 | 0 413 | 0 414 | ARC 415 | 5 416 | 106 417 | 100 418 | AcDbEntity 419 | 8 420 | 0 421 | 100 422 | AcDbCircle 423 | 10 424 | 82.000000000000028 425 | 20 426 | 7.5000000000000782 427 | 30 428 | 0 429 | 40 430 | 17.5 431 | 210 432 | -0 433 | 220 434 | 0 435 | 230 436 | 1 437 | 100 438 | AcDbArc 439 | 50 440 | 0 441 | 51 442 | 90.000000000000099 443 | 0 444 | CIRCLE 445 | 5 446 | 107 447 | 100 448 | AcDbEntity 449 | 8 450 | 0 451 | 100 452 | AcDbCircle 453 | 10 454 | -80.424621202458638 455 | 20 456 | 14.924621202458416 457 | 30 458 | 0 459 | 40 460 | 2.0000000000000018 461 | 210 462 | 0 463 | 220 464 | 0 465 | 230 466 | 1 467 | 0 468 | CIRCLE 469 | 5 470 | 108 471 | 100 472 | AcDbEntity 473 | 8 474 | 0 475 | 100 476 | AcDbCircle 477 | 10 478 | -80.424621202458724 479 | 20 480 | -50.924621202458596 481 | 30 482 | 0 483 | 40 484 | 2.0000000000000018 485 | 210 486 | 0 487 | 220 488 | 0 489 | 230 490 | 1 491 | 0 492 | CIRCLE 493 | 5 494 | 109 495 | 100 496 | AcDbEntity 497 | 8 498 | 0 499 | 100 500 | AcDbCircle 501 | 10 502 | 93.821577924916937 503 | 20 504 | -30.500000000000014 505 | 30 506 | 0 507 | 40 508 | 1.9999999999999929 509 | 210 510 | 0 511 | 220 512 | 0 513 | 230 514 | 1 515 | 0 516 | CIRCLE 517 | 5 518 | 110 519 | 100 520 | AcDbEntity 521 | 8 522 | 0 523 | 100 524 | AcDbCircle 525 | 10 526 | 93.821577924916951 527 | 20 528 | -5.5000000000000169 529 | 30 530 | 0 531 | 40 532 | 1.9999999999999929 533 | 210 534 | 0 535 | 220 536 | 0 537 | 230 538 | 1 539 | 0 540 | CIRCLE 541 | 5 542 | 111 543 | 100 544 | AcDbEntity 545 | 8 546 | 0 547 | 100 548 | AcDbCircle 549 | 10 550 | -80.424621202458638 551 | 20 552 | 14.924621202458416 553 | 30 554 | 0 555 | 40 556 | 1.25 557 | 210 558 | 0 559 | 220 560 | 0 561 | 230 562 | 1 563 | 0 564 | CIRCLE 565 | 5 566 | 112 567 | 100 568 | AcDbEntity 569 | 8 570 | 0 571 | 100 572 | AcDbCircle 573 | 10 574 | -80.424621202458724 575 | 20 576 | -50.924621202458596 577 | 30 578 | 0 579 | 40 580 | 1.25 581 | 210 582 | 0 583 | 220 584 | 0 585 | 230 586 | 1 587 | 0 588 | CIRCLE 589 | 5 590 | 113 591 | 100 592 | AcDbEntity 593 | 8 594 | 0 595 | 100 596 | AcDbCircle 597 | 10 598 | 93.821577924916937 599 | 20 600 | -30.500000000000014 601 | 30 602 | 0 603 | 40 604 | 1.25 605 | 210 606 | 0 607 | 220 608 | 0 609 | 230 610 | 1 611 | 0 612 | CIRCLE 613 | 5 614 | 114 615 | 100 616 | AcDbEntity 617 | 8 618 | 0 619 | 100 620 | AcDbCircle 621 | 10 622 | 93.821577924916951 623 | 20 624 | -5.5000000000000169 625 | 30 626 | 0 627 | 40 628 | 1.25 629 | 210 630 | 0 631 | 220 632 | 0 633 | 230 634 | 1 635 | 0 636 | LINE 637 | 5 638 | 115 639 | 100 640 | AcDbEntity 641 | 8 642 | 0 643 | 100 644 | AcDbLine 645 | 10 646 | 5.4999999999999902 647 | 20 648 | -60.999999999999908 649 | 30 650 | 0 651 | 11 652 | 81.999999999999972 653 | 21 654 | -60.999999999999986 655 | 31 656 | 0 657 | 0 658 | CIRCLE 659 | 5 660 | 116 661 | 100 662 | AcDbEntity 663 | 8 664 | 0 665 | 100 666 | AcDbCircle 667 | 10 668 | 10 669 | 20 670 | 17.999999999999872 671 | 30 672 | 0 673 | 40 674 | 1.2500000000000011 675 | 210 676 | 0 677 | 220 678 | -0 679 | 230 680 | 1 681 | 0 682 | CIRCLE 683 | 5 684 | 117 685 | 100 686 | AcDbEntity 687 | 8 688 | 0 689 | 100 690 | AcDbCircle 691 | 10 692 | -12.000000000000002 693 | 20 694 | -12.000000000000002 695 | 30 696 | 0 697 | 40 698 | 1.25 699 | 210 700 | 0 701 | 220 702 | -0 703 | 230 704 | 1 705 | 0 706 | LINE 707 | 5 708 | 118 709 | 100 710 | AcDbEntity 711 | 8 712 | 0 713 | 100 714 | AcDbLine 715 | 10 716 | 5.4999999999999902 717 | 20 718 | -60.999999999999908 719 | 30 720 | 0 721 | 11 722 | -5.5000000000000107 723 | 21 724 | -60.999999999999908 725 | 31 726 | 0 727 | 0 728 | LINE 729 | 5 730 | 119 731 | 100 732 | AcDbEntity 733 | 8 734 | 0 735 | 100 736 | AcDbLine 737 | 10 738 | -66.500000000000028 739 | 20 740 | 1.0000000000000053 741 | 30 742 | 0 743 | 11 744 | -66.5 745 | 21 746 | 20.000000000000007 747 | 31 748 | 0 749 | 0 750 | LINE 751 | 5 752 | 120 753 | 100 754 | AcDbEntity 755 | 8 756 | 0 757 | 100 758 | AcDbLine 759 | 10 760 | -66.5 761 | 20 762 | 20.000000000000007 763 | 30 764 | 0 765 | 11 766 | 82 767 | 21 768 | 20.000000000000078 769 | 31 770 | 0 771 | 0 772 | LINE 773 | 5 774 | 121 775 | 100 776 | AcDbEntity 777 | 8 778 | 0 779 | 100 780 | AcDbLine 781 | 10 782 | -85.5 783 | 20 784 | 1.0000000000000053 785 | 30 786 | 0 787 | 11 788 | -66.500000000000028 789 | 21 790 | 1.0000000000000053 791 | 31 792 | 0 793 | 0 794 | LINE 795 | 5 796 | 122 797 | 100 798 | AcDbEntity 799 | 8 800 | 0 801 | 100 802 | AcDbLine 803 | 10 804 | -85.5 805 | 20 806 | -37 807 | 30 808 | 0 809 | 11 810 | -85.5 811 | 21 812 | 1.0000000000000053 813 | 31 814 | 0 815 | 0 816 | LINE 817 | 5 818 | 123 819 | 100 820 | AcDbEntity 821 | 8 822 | 0 823 | 100 824 | AcDbLine 825 | 10 826 | -66.5 827 | 20 828 | -37 829 | 30 830 | 0 831 | 11 832 | -85.5 833 | 21 834 | -37 835 | 31 836 | 0 837 | 0 838 | LINE 839 | 5 840 | 124 841 | 100 842 | AcDbEntity 843 | 8 844 | 0 845 | 100 846 | AcDbLine 847 | 10 848 | -66.5 849 | 20 850 | -56.000000000000007 851 | 30 852 | 0 853 | 11 854 | -66.5 855 | 21 856 | -37 857 | 31 858 | 0 859 | 0 860 | LINE 861 | 5 862 | 125 863 | 100 864 | AcDbEntity 865 | 8 866 | 0 867 | 100 868 | AcDbLine 869 | 10 870 | -5.5000000000000115 871 | 20 872 | -56.000000000000014 873 | 30 874 | 0 875 | 11 876 | -66.5 877 | 21 878 | -56.000000000000007 879 | 31 880 | 0 881 | 0 882 | LINE 883 | 5 884 | 126 885 | 100 886 | AcDbEntity 887 | 8 888 | 0 889 | 100 890 | AcDbLine 891 | 10 892 | 82 893 | 20 894 | -55.999999999999986 895 | 30 896 | 0 897 | 11 898 | 5.4999999999999893 899 | 21 900 | -56.000000000000014 901 | 31 902 | 0 903 | 0 904 | ARC 905 | 5 906 | 127 907 | 100 908 | AcDbEntity 909 | 8 910 | 0 911 | 100 912 | AcDbCircle 913 | 10 914 | 82 915 | 20 916 | -43.499999999999986 917 | 30 918 | 0 919 | 40 920 | 12.5 921 | 210 922 | 0 923 | 220 924 | 0 925 | 230 926 | 1 927 | 100 928 | AcDbArc 929 | 50 930 | -90 931 | 51 932 | 0 933 | 0 934 | LINE 935 | 5 936 | 128 937 | 100 938 | AcDbEntity 939 | 8 940 | 0 941 | 100 942 | AcDbLine 943 | 10 944 | 94.5 945 | 20 946 | -36.856844150166161 947 | 30 948 | 0 949 | 11 950 | 94.5 951 | 21 952 | -43.499999999999986 953 | 31 954 | 0 955 | 0 956 | ARC 957 | 5 958 | 129 959 | 100 960 | AcDbEntity 961 | 8 962 | 0 963 | 100 964 | AcDbCircle 965 | 10 966 | 94.500000000000014 967 | 20 968 | -30.500000000000014 969 | 30 970 | 0 971 | 40 972 | 6.3568441501661432 973 | 210 974 | 0 975 | 220 976 | -0 977 | 230 978 | 1 979 | 100 980 | AcDbArc 981 | 50 982 | 90 983 | 51 984 | 269.99999999999989 985 | 0 986 | LINE 987 | 5 988 | 130 989 | 100 990 | AcDbEntity 991 | 8 992 | 0 993 | 100 994 | AcDbLine 995 | 10 996 | 94.500000000000014 997 | 20 998 | -11.856844150166179 999 | 30 1000 | 0 1001 | 11 1002 | 94.500000000000014 1003 | 21 1004 | -24.143155849833875 1005 | 31 1006 | 0 1007 | 0 1008 | LINE 1009 | 5 1010 | 131 1011 | 100 1012 | AcDbEntity 1013 | 8 1014 | 0 1015 | 100 1016 | AcDbLine 1017 | 10 1018 | 94.500000000000028 1019 | 20 1020 | 7.5000000000000711 1021 | 30 1022 | 0 1023 | 11 1024 | 94.500000000000028 1025 | 21 1026 | 0.85684415016614501 1027 | 31 1028 | 0 1029 | 0 1030 | ARC 1031 | 5 1032 | 132 1033 | 100 1034 | AcDbEntity 1035 | 8 1036 | 0 1037 | 100 1038 | AcDbCircle 1039 | 10 1040 | 94.500000000000028 1041 | 20 1042 | -5.5000000000000178 1043 | 30 1044 | 0 1045 | 40 1046 | 6.3568441501661628 1047 | 210 1048 | 0 1049 | 220 1050 | -0 1051 | 230 1052 | 1 1053 | 100 1054 | AcDbArc 1055 | 50 1056 | 90 1057 | 51 1058 | 269.99999999999989 1059 | 0 1060 | ARC 1061 | 5 1062 | 133 1063 | 100 1064 | AcDbEntity 1065 | 8 1066 | 0 1067 | 100 1068 | AcDbCircle 1069 | 10 1070 | 82.000000000000043 1071 | 20 1072 | 7.5000000000000959 1073 | 30 1074 | 0 1075 | 40 1076 | 12.499999999999982 1077 | 210 1078 | 0 1079 | 220 1080 | 0 1081 | 230 1082 | 1 1083 | 100 1084 | AcDbArc 1085 | 50 1086 | 0 1087 | 51 1088 | 90.000000000000199 1089 | 0 1090 | LINE 1091 | 5 1092 | 134 1093 | 100 1094 | AcDbEntity 1095 | 8 1096 | 0 1097 | 100 1098 | AcDbLine 1099 | 10 1100 | -5.5000000000000115 1101 | 20 1102 | -56.000000000000014 1103 | 30 1104 | 0 1105 | 11 1106 | 5.4999999999999893 1107 | 21 1108 | -56.000000000000014 1109 | 31 1110 | 0 1111 | 0 1112 | LWPOLYLINE 1113 | 5 1114 | 135 1115 | 100 1116 | AcDbEntity 1117 | 8 1118 | 0 1119 | 100 1120 | AcDbPolyline 1121 | 90 1122 | 16 1123 | 70 1124 | 1 1125 | 43 1126 | 0.0 1127 | 10 1128 | -65.499999985098839 1129 | 20 1130 | -54.999999985098846 1131 | 10 1132 | -5.5000000000000115 1133 | 20 1134 | -54.999999985098853 1135 | 10 1136 | 5.4999999999999893 1137 | 20 1138 | -54.999999985098853 1139 | 10 1140 | 82 1141 | 20 1142 | -54.999999985098825 1143 | 42 1144 | 0.41421356237309503 1145 | 10 1146 | 93.499999985098839 1147 | 20 1148 | -43.499999999999986 1149 | 10 1150 | 93.499999985098839 1151 | 20 1152 | -37.788563372797327 1153 | 42 1154 | -0.87216695870292105 1155 | 10 1156 | 93.499999985098853 1157 | 20 1158 | -23.211436627202705 1159 | 10 1160 | 93.499999985098853 1161 | 20 1162 | -12.788563372797348 1163 | 42 1164 | -0.87216695870292127 1165 | 10 1166 | 93.499999985098867 1167 | 20 1168 | 1.7885633727973138 1169 | 10 1170 | 93.499999985098867 1171 | 20 1172 | 7.5000000000000728 1173 | 42 1174 | 0.41421356237309698 1175 | 10 1176 | 82 1177 | 20 1178 | 18.999999985098917 1179 | 10 1180 | -65.499999985098839 1181 | 20 1182 | 18.999999985098846 1183 | 10 1184 | -65.499999985098867 1185 | 20 1186 | -1.4901155864777138e-08 1187 | 10 1188 | -84.499999985098839 1189 | 20 1190 | -1.4901155864777138e-08 1191 | 10 1192 | -84.499999985098839 1193 | 20 1194 | -35.999999985098839 1195 | 10 1196 | -65.499999985098839 1197 | 20 1198 | -35.999999985098839 1199 | 0 1200 | LINE 1201 | 5 1202 | 136 1203 | 100 1204 | AcDbEntity 1205 | 8 1206 | 0 1207 | 100 1208 | AcDbLine 1209 | 10 1210 | -73 1211 | 20 1212 | -60.999999999999829 1213 | 30 1214 | 0 1215 | 11 1216 | -5.5000000000000071 1217 | 21 1218 | -60.999999999999908 1219 | 31 1220 | 0 1221 | 0 1222 | CIRCLE 1223 | 5 1224 | 137 1225 | 100 1226 | AcDbEntity 1227 | 8 1228 | 0 1229 | 100 1230 | AcDbCircle 1231 | 10 1232 | -4.4408920985006262e-15 1233 | 20 1234 | -24.730000000000004 1235 | 30 1236 | 0 1237 | 40 1238 | 0.75000000000000011 1239 | 210 1240 | 0 1241 | 220 1242 | -0 1243 | 230 1244 | 1 1245 | 0 1246 | ENDSEC 1247 | 0 1248 | SECTION 1249 | 2 1250 | OBJECTS 1251 | 0 1252 | DICTIONARY 1253 | 5 1254 | C 1255 | 100 1256 | AcDbDictionary 1257 | 3 1258 | ACAD_GROUP 1259 | 350 1260 | D 1261 | 3 1262 | ACAD_MLINESTYLE 1263 | 350 1264 | 17 1265 | 0 1266 | DICTIONARY 1267 | 5 1268 | D 1269 | 100 1270 | AcDbDictionary 1271 | 0 1272 | DICTIONARY 1273 | 5 1274 | 1A 1275 | 330 1276 | C 1277 | 100 1278 | AcDbDictionary 1279 | 0 1280 | DICTIONARY 1281 | 5 1282 | 17 1283 | 100 1284 | AcDbDictionary 1285 | 0 1286 | ENDSEC 1287 | 0 1288 | EOF 1289 | -------------------------------------------------------------------------------- /Big_Wheel_Macro_Pad_Firmware/Big_Wheel_Macro_Pad_Firmware.ino: -------------------------------------------------------------------------------- 1 | /* 2 | "Big Wheel" macro pad firmware 3 | Copyright Sept. 2020 Zack Freedman of Voidstar Lab 4 | Licensed Creative Commons 4.0 Noncommercial Share-Alike 5 | 6 | This code is designed to run on this hardware: https://github.com/ZackFreedman/Big-Wheel 7 | This was used in a YouTube video! Check it out! https://www.youtube.com/watch?v=72a85tWOJVY 8 | 9 | Intended for Teensy LC, but should also work on Teensy 3.X and maybe Teensy 4.0 10 | These hotkeys and shortcuts are designed to control Premiere Pro, but you do you fam 11 | 12 | REMEMBER: Tools --> USB Type --> Serial + Keyboard + Mouse + Joystick 13 | */ 14 | 15 | 16 | #include 17 | #include "actions.h" 18 | #include "pins.h" 19 | 20 | //#define DEBUG_DUMP_EVENTS 21 | //#define DEBUG_DUMP_MATRIX 22 | //#define DEBUG_DUMP_WHEEL_SPEED 23 | //#define DEBUG_PLOT_WHEEL_SPEED 24 | //#define DEBUG_SHUTTLE 25 | 26 | Encoder topKnob(topKnobA, topKnobB); 27 | Encoder middleKnob(middleKnobA, middleKnobB); 28 | Encoder lowerKnob(lowerKnobA, lowerKnobB); 29 | Encoder wheel(wheelA, wheelB); 30 | 31 | long lastKnobPositions[4] = {0, 0, 0, 0}; 32 | int lastKnobDeltas[4]; 33 | bool lastSwitchStates[18]; 34 | bool debouncedSwitchStates[18]; 35 | bool lastDebouncedSwitchStates[18]; 36 | elapsedMillis switchDebounceTimestamps[18]; 37 | #define switchDebounceTime 50 38 | 39 | int lastActions[6]; 40 | int allActionsThisFrame[6]; 41 | byte actionIndex = 0; 42 | 43 | // Used to calculate rotational velocity of big wheel 44 | // 1 detent = 4 pulses in the same direction 45 | // 1 delta = Time between two successive detents, in ms 46 | #define wheelDetentDeltaAverageCount 5 // More detents = smoother but laggier shuttling 47 | 48 | // How many ms must elapse without wheel motion before resetting velocity measurements 49 | // wheelTimeout must be > 1000.0 / max(abs(minimumDpsForReverse), minimumDpsForPlay) 50 | #define wheelTimeout 50 51 | 52 | // More detents = more predictable but laggier shuttling. 53 | // minimumWheelDetentsToAverage must be <= wheelDetentDeltaAverageCount! 54 | #define minimumWheelDetentsToAverage 2 55 | 56 | elapsedMillis timeSinceLastWheelDetent; 57 | long wheelDetentDeltas[wheelDetentDeltaAverageCount]; 58 | byte wheelDetentDeltaIndex; // Used to treat wheelDetentDeltas as a circular buffer 59 | float averageWheelDetentDelta; 60 | 61 | // Shuttle states. In normal mode, one big knob detent (4 pulses) moves by 1 frame. 62 | // In other states, a shuttle movement (Hotkeys J or K in Premiere) is in effect. 63 | #define NO_STATE -255 // Shuttle state should never be this 64 | #define STATE_TRIPLE_REVERSE -3 65 | #define STATE_DOUBLE_REVERSE -2 66 | #define STATE_REVERSE -1 67 | #define STATE_NORMAL 0 68 | #define STATE_PLAY 1 69 | #define STATE_DOUBLE_PLAY 2 70 | #define STATE_TRIPLE_PLAY 3 71 | 72 | // Minimum speed the wheel must spin to engage a shuttle mode 73 | // DPS = detents per second 74 | // Positive = clockwise 75 | #define minimumDpsForTripleReverse -40 76 | #define minimumDpsForDoubleReverse -30 77 | #define minimumDpsForReverse -20 78 | #define minimumDpsForPlay 25 79 | #define minimumDpsForDoubleSpeed 40 80 | #define minimumDpsForTripleSpeed 50 81 | 82 | // To engage a shuttle mode, user must keep turning wheel at the threshold speed, 83 | // or a higher threshold speed, for this many ms. 84 | // Increase to make shuttling more predictable but less responsive. 85 | // No effect on dead stops - wheelTimeout always takes priority. 86 | #define timeUntilSpeedTransition 200 87 | 88 | int shuttleState = STATE_NORMAL; 89 | int stateTransitioningInto = NO_STATE; 90 | elapsedMillis speedTransitionTimer; 91 | 92 | // Special behavior - turning wheel while holding ALT key (2, 1) 93 | // locks the shuttle into the Play or Reverse state, making viewing more comfortable. 94 | // Any wheel movement, even one detent, unlocks the shuttle and returns to the Normal state. 95 | bool shuttleLocked = false; 96 | elapsedMillis shuttleLockTimer; 97 | #define shuttleLockout 200 // Wheel movement is ignored for this many ms after locking the shuttle. 98 | 99 | // Lock the system by holding ALT (2, 1) and pressing Undo (2, 6) 100 | bool systemLocked = false; // While the system is locked, no keystrokes are sent over USB. 101 | bool lockKeysLatched = false; // After locking/unlocking, both keys are disabled until both are released 102 | elapsedMillis ctrlGuardTimer; 103 | // When ALT is pressed, user might be locking system. 104 | // Don't send ALT until this many ms have passed. 105 | #define ctrlGracePeriod 50 106 | 107 | // Basic event buffer to collect all applicable shortcut/hotkey actions and send appropriate keystrokes 108 | // Up to 6 actions can be queued, because I don't think the user can realistically hit more. 109 | void queueAction(int action) { 110 | #ifdef DEBUG_DUMP_EVENTS 111 | Serial.print("Action: "); 112 | Serial.println(action); 113 | #endif 114 | 115 | if (action != NO_ACTION && actionIndex < 6) { 116 | allActionsThisFrame[actionIndex] = action; 117 | actionIndex++; 118 | } 119 | } 120 | 121 | void setup() { 122 | Serial.begin(9600); 123 | 124 | // Prepare key matrix pins (including encoder clicks) 125 | // Note that the Encoder library prepares its own pins. 126 | for (int i = 0; i < 6; i++) { 127 | pinMode(allButtonRowPins[i], OUTPUT); 128 | digitalWrite(allButtonRowPins[i], HIGH); 129 | pinMode(allButtonColumnPins[i], INPUT_PULLUP); 130 | } 131 | } 132 | 133 | void loop() { 134 | long knobPositions[] = {topKnob.read(), middleKnob.read(), lowerKnob.read(), wheel.read()}; 135 | int knobDeltas[] = {0, 0, 0, 0}; 136 | bool switchStates[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 137 | for (int i = 0; i < 6; i++) allActionsThisFrame[i] = NO_ACTION; 138 | actionIndex = 0; 139 | 140 | // This is necessary to implement shuttle lock 141 | bool ctrlBeingHeld = debouncedSwitchStates[15]; 142 | 143 | if (timeSinceLastWheelDetent >= wheelTimeout) { 144 | #ifdef DEBUG_SHUTTLE 145 | if (averageWheelDetentDelta != 0) 146 | Serial.println("Resetting speedometer"); 147 | #endif 148 | 149 | for (int i = 0; i < wheelDetentDeltaAverageCount; i++) 150 | wheelDetentDeltas[i] = 0; 151 | wheelDetentDeltaIndex = 0; 152 | 153 | averageWheelDetentDelta = 0; 154 | 155 | if (!shuttleLocked && shuttleState != STATE_NORMAL) { 156 | #ifdef DEBUG_SHUTTLE 157 | Serial.println("Stopping shuttle (timeout)"); 158 | #endif 159 | 160 | Keyboard.release(MODIFIERKEY_GUI); 161 | 162 | Keyboard.press(KEY_K); 163 | Keyboard.release(KEY_K); 164 | 165 | if (ctrlBeingHeld) 166 | Keyboard.press(MODIFIERKEY_GUI); 167 | 168 | shuttleState = STATE_NORMAL; 169 | } 170 | 171 | stateTransitioningInto = NO_STATE; 172 | } 173 | 174 | 175 | for (int i = 0; i < 3; i++) { 176 | digitalWrite(leftButtonRowPins[i], LOW); 177 | for (int j = 0; j < 3; j++) { 178 | if (!digitalRead(leftButtonColumnPins[j])) { 179 | #ifdef DEBUG_DUMP_MATRIX 180 | Serial.print("Left: "); 181 | Serial.print(i); 182 | Serial.print(','); 183 | Serial.print(j); 184 | Serial.print(" (Button "); 185 | Serial.print(keyMatrix[0][i][j]); 186 | Serial.println(')'); 187 | #endif 188 | 189 | switchStates[keyMatrix[0][i][j]] = true; 190 | } 191 | } 192 | digitalWrite(leftButtonRowPins[i], HIGH); 193 | 194 | digitalWrite(rightButtonRowPins[i], LOW); 195 | for (int j = 0; j < 3; j++) { 196 | if (!digitalRead(rightButtonColumnPins[j])) { 197 | #ifdef DEBUG_DUMP_MATRIX 198 | Serial.print("Right: "); 199 | Serial.print(i); 200 | Serial.print(','); 201 | Serial.print(j); 202 | Serial.print(" (Button "); 203 | Serial.print(keyMatrix[1][i][j]); 204 | Serial.println(')'); 205 | #endif 206 | 207 | switchStates[keyMatrix[1][i][j]] = true; 208 | } 209 | } 210 | digitalWrite(rightButtonRowPins[i], HIGH); 211 | } 212 | 213 | for (int i = 0; i < 18; i++) { 214 | if (switchStates[i] != lastSwitchStates[i]) { 215 | switchDebounceTimestamps[i] = 0; 216 | } 217 | 218 | if (switchDebounceTimestamps[i] >= switchDebounceTime) { 219 | lastDebouncedSwitchStates[i] = debouncedSwitchStates[i]; 220 | debouncedSwitchStates[i] = switchStates[i]; 221 | } 222 | 223 | // Perform most actions only on falling edge 224 | if (debouncedSwitchStates[i] && !lastDebouncedSwitchStates[i]) { 225 | // Special buttons with multiple roles 226 | if (i == 15) { 227 | if (!debouncedSwitchStates[16] && !lockKeysLatched) 228 | queueAction(switchAssignments[i]); 229 | } 230 | else if (i == 16) { 231 | if (!debouncedSwitchStates[15] && !lockKeysLatched) 232 | queueAction(switchAssignments[i]); 233 | } 234 | else { 235 | #ifdef DEBUG_DUMP_EVENTS 236 | Serial.print("Queuing action "); 237 | Serial.println(switchAssignments[i]); 238 | #endif 239 | queueAction(switchAssignments[i]); 240 | } 241 | } 242 | } 243 | 244 | if (!debouncedSwitchStates[15] && lastDebouncedSwitchStates[15]) { 245 | Keyboard.release(MODIFIERKEY_GUI); 246 | Serial.println("Ctrl up"); 247 | } 248 | 249 | if (!systemLocked) { 250 | for (int i = 0; i < 4; i++) { 251 | if (abs(knobPositions[i] - lastKnobPositions[i]) >= 4) { 252 | #ifdef DEBUG_DUMP_EVENTS 253 | Serial.print("Knob "); 254 | Serial.print(i); 255 | Serial.print(" "); 256 | Serial.print(lastKnobPositions[i]); 257 | Serial.print(" --> "); 258 | Serial.print(knobPositions[i]); 259 | Serial.print(" "); 260 | #endif 261 | 262 | if (knobPositions[i] > lastKnobPositions[i]) { 263 | #ifdef DEBUG_DUMP_EVENTS 264 | Serial.println("CW"); 265 | #endif 266 | knobDeltas[i] = 1; 267 | } 268 | else { 269 | #ifdef DEBUG_DUMP_EVENTS 270 | Serial.println("CCW"); 271 | #endif 272 | knobDeltas[i] = -1; 273 | } 274 | 275 | lastKnobPositions[i] = knobPositions[i]; 276 | } 277 | } 278 | 279 | if (knobDeltas[3] != 0) { 280 | wheelDetentDeltas[wheelDetentDeltaIndex] = 281 | long(timeSinceLastWheelDetent) * knobDeltas[3]; // Positive: CW, Negative: CCW 282 | 283 | wheelDetentDeltaIndex = (wheelDetentDeltaIndex + 1) % wheelDetentDeltaAverageCount; // Baby's first circular buffer 284 | 285 | float detentsAveraged = 0; // I think this needs to be a float or the division op will be truncated 286 | long runningTotal = 0; 287 | for (int i = 0; i < wheelDetentDeltaAverageCount; i++) { 288 | if (wheelDetentDeltas[i] == 0) break; 289 | 290 | detentsAveraged++; 291 | runningTotal += wheelDetentDeltas[i]; 292 | } 293 | 294 | if (detentsAveraged >= minimumWheelDetentsToAverage) { 295 | averageWheelDetentDelta = float(runningTotal) / detentsAveraged; 296 | 297 | #ifdef DEBUG_DUMP_WHEEL_SPEED 298 | Serial.print(averageWheelDetentDelta); 299 | Serial.print("ms --> "); 300 | Serial.print(1000.0 / averageWheelDetentDelta); 301 | Serial.println("/sec"); 302 | #endif 303 | } 304 | 305 | timeSinceLastWheelDetent = 0; 306 | } 307 | 308 | for (int i = 0; i < 4; i++) { 309 | // CW 310 | if (knobDeltas[i] > 0) { 311 | switch (i) { 312 | case 0: 313 | queueAction(topKnobAssignments[0]); 314 | break; 315 | case 1: 316 | queueAction(middleKnobAssignments[0]); 317 | break; 318 | case 2: 319 | queueAction(lowerKnobAssignments[0]); 320 | break; 321 | case 3: 322 | if (shuttleState == STATE_NORMAL) 323 | queueAction(wheelAssignments[0]); 324 | break; 325 | default: 326 | break; 327 | } 328 | } 329 | // CCW 330 | else if (knobDeltas[i] < 0) { 331 | switch (i) { 332 | case 0: 333 | queueAction(topKnobAssignments[1]); 334 | break; 335 | case 1: 336 | queueAction(middleKnobAssignments[1]); 337 | break; 338 | case 2: 339 | queueAction(lowerKnobAssignments[1]); 340 | break; 341 | case 3: 342 | if (shuttleState == STATE_NORMAL) 343 | queueAction(wheelAssignments[1]); 344 | break; 345 | default: 346 | break; 347 | } 348 | } 349 | } 350 | 351 | if (debouncedSwitchStates[15]) { // Lock shuttle if CTRL is held 352 | if (shuttleLockTimer > shuttleLockout && knobDeltas[3] != 0) { 353 | Keyboard.release(MODIFIERKEY_GUI); 354 | 355 | Serial.print("Knob position: "); 356 | Serial.print(knobPositions[3]); 357 | Serial.print(" Last position: "); 358 | Serial.println(lastKnobPositions[3]); 359 | 360 | if (knobDeltas[3] == 1) { 361 | if (shuttleState != STATE_PLAY) { 362 | #ifdef DEBUG_SHUTTLE 363 | Serial.println("Locking shuttle in Play"); 364 | #endif 365 | 366 | shuttleState = STATE_PLAY; 367 | Keyboard.press(KEY_K); 368 | Keyboard.release(KEY_K); 369 | Keyboard.press(KEY_L); 370 | Keyboard.release(KEY_L); 371 | } 372 | } 373 | else if (knobDeltas[3] == -1) { 374 | if (shuttleState != STATE_REVERSE) { 375 | #ifdef DEBUG_SHUTTLE 376 | Serial.println("Locking shuttle in Reverse"); 377 | #endif 378 | 379 | shuttleState = STATE_REVERSE; 380 | Keyboard.press(KEY_K); 381 | Keyboard.release(KEY_K); 382 | Keyboard.press(KEY_J); 383 | Keyboard.release(KEY_J); 384 | } 385 | } 386 | 387 | stateTransitioningInto = NO_STATE; 388 | shuttleLocked = true; 389 | shuttleLockTimer = 0; 390 | 391 | Keyboard.press(MODIFIERKEY_GUI); 392 | } 393 | } 394 | else { 395 | if (knobDeltas[3] != 0 && shuttleLocked) { 396 | #ifdef DEBUG_SHUTTLE 397 | Serial.print("Shuttle unlocked"); 398 | #endif 399 | 400 | Keyboard.press(KEY_K); 401 | Keyboard.release(KEY_K); 402 | shuttleState = STATE_NORMAL; 403 | shuttleLocked = false; 404 | } 405 | 406 | int stateShuttleShouldBeIn = NO_STATE; 407 | 408 | if (averageWheelDetentDelta == 0) { // If wheel is still, remain locked or return to frame-by-frame scrubbing 409 | if (shuttleLocked) 410 | stateShuttleShouldBeIn = shuttleState; 411 | else 412 | stateShuttleShouldBeIn = STATE_NORMAL; 413 | } 414 | else { // Wheel is moving and ALT is up. Are we moving fast enough to shuttle? 415 | float clicksPerSecond = 1000.0 / averageWheelDetentDelta; 416 | 417 | if (clicksPerSecond >= minimumDpsForTripleSpeed) 418 | stateShuttleShouldBeIn = STATE_TRIPLE_PLAY; 419 | else if (clicksPerSecond >= minimumDpsForDoubleSpeed) 420 | stateShuttleShouldBeIn = STATE_DOUBLE_PLAY; 421 | else if (clicksPerSecond >= minimumDpsForPlay) 422 | stateShuttleShouldBeIn = STATE_PLAY; 423 | else if (clicksPerSecond <= minimumDpsForTripleReverse) 424 | stateShuttleShouldBeIn = STATE_TRIPLE_REVERSE; 425 | else if (clicksPerSecond <= minimumDpsForDoubleReverse) 426 | stateShuttleShouldBeIn = STATE_DOUBLE_REVERSE; 427 | else if (clicksPerSecond <= minimumDpsForReverse) 428 | stateShuttleShouldBeIn = STATE_REVERSE; 429 | else 430 | stateShuttleShouldBeIn = STATE_NORMAL; 431 | } 432 | 433 | if (shuttleState == stateShuttleShouldBeIn) { // No need to change state - we're already there 434 | if (stateTransitioningInto != NO_STATE) { 435 | #ifdef DEBUG_SHUTTLE 436 | Serial.println("Cancelling transition"); 437 | #endif 438 | 439 | speedTransitionTimer = 0; 440 | } 441 | stateTransitioningInto = NO_STATE; 442 | } 443 | 444 | // To switch into a shuttle state, the wheel needs to keep spinning 445 | // at the appropriate speed or faster for a period of time. 446 | else if (stateTransitioningInto == stateShuttleShouldBeIn || 447 | (stateTransitioningInto < 0 && stateShuttleShouldBeIn < stateTransitioningInto) || 448 | (stateTransitioningInto > 0 && stateShuttleShouldBeIn > stateTransitioningInto)) { 449 | if (speedTransitionTimer >= timeUntilSpeedTransition) { 450 | if (stateTransitioningInto == STATE_NORMAL) { 451 | // Any transition to normal mode (frame-by-frame scrubbing) happens immediately. 452 | // The user must be able to precisely control the playhead at a moment's notice. 453 | 454 | #ifdef DEBUG_SHUTTLE 455 | Serial.println("Stopping shuttle (slowdown)"); 456 | #endif 457 | 458 | Keyboard.press(KEY_K); 459 | Keyboard.release(KEY_K); 460 | } 461 | else { 462 | if (stateTransitioningInto == NO_STATE) { 463 | #ifdef DEBUG_SHUTTLE 464 | Serial.println("Tried to transition into NO_STATE"); 465 | #endif 466 | } 467 | else { 468 | // Successive presses of the J and L keys control shuttle speed in Premiere Pro. 469 | // Pressing L starts shuttling at normal speed, L again goes to double speed, 470 | // then J goes back to normal speed, then J twice more reverses 471 | byte shiftsToPerform = abs(shuttleState - stateTransitioningInto); 472 | bool shiftDirection = stateTransitioningInto > shuttleState; 473 | 474 | #ifdef DEBUG_SHUTTLE 475 | Serial.print(shiftDirection ? ">>> " : "<<< "); 476 | Serial.print("Shifting shuttle speed "); 477 | Serial.print(shiftDirection ? "up " : "down "); 478 | Serial.print(shiftsToPerform); 479 | Serial.print(" times, from "); 480 | Serial.print(shuttleState); 481 | Serial.print(" to "); 482 | Serial.println(stateTransitioningInto); 483 | #endif 484 | 485 | if (shuttleLocked) // We can adjust the speed of a locked shuttle... 486 | Keyboard.release(MODIFIERKEY_GUI); // ...but not by pressing CTRL + L 487 | 488 | for (int i = 0; i < shiftsToPerform; i++) { 489 | if (shiftDirection) { 490 | Keyboard.press(KEY_L); 491 | Keyboard.release(KEY_L); 492 | } 493 | else { 494 | Keyboard.press(KEY_J); 495 | Keyboard.release(KEY_J); 496 | } 497 | } 498 | 499 | if (shuttleLocked) 500 | Keyboard.press(MODIFIERKEY_GUI); 501 | } 502 | } 503 | 504 | shuttleState = stateTransitioningInto; 505 | stateTransitioningInto = NO_STATE; 506 | } 507 | } 508 | else { 509 | #ifdef DEBUG_SHUTTLE 510 | Serial.print("Starting transition to "); 511 | Serial.println(stateShuttleShouldBeIn); 512 | #endif 513 | 514 | stateTransitioningInto = stateShuttleShouldBeIn; 515 | speedTransitionTimer = 0; 516 | } 517 | } 518 | 519 | if (timeSinceLastWheelDetent >= wheelTimeout) { 520 | for (int i = 0; i < wheelDetentDeltaAverageCount; i++) 521 | wheelDetentDeltas[i] = 0; 522 | wheelDetentDeltaIndex = 0; 523 | } 524 | 525 | #ifdef DEBUG_PLOT_WHEEL_SPEED 526 | // Good for making your own thresholds for shuttling 527 | if (averageWheelDetentDelta == 0) 528 | Serial.println(0.0); 529 | else 530 | Serial.println(1000.0 / averageWheelDetentDelta); 531 | #endif 532 | } 533 | 534 | 535 | if (!systemLocked) { 536 | for (int i = 0; i < 6; i++) { 537 | bool actionAlreadyDone = false; 538 | 539 | if (allActionsThisFrame[i] == NO_ACTION) continue; 540 | 541 | for (int j = 0; j < 6; j++) { 542 | if (lastActions[j] == allActionsThisFrame[i]) { 543 | actionAlreadyDone = true; 544 | break; 545 | } 546 | } 547 | 548 | if (!actionAlreadyDone) { 549 | performAction(allActionsThisFrame[i]); 550 | } 551 | } 552 | } 553 | 554 | if (!debouncedSwitchStates[15] && !debouncedSwitchStates[16]) 555 | lockKeysLatched = false; 556 | 557 | if (debouncedSwitchStates[15] && debouncedSwitchStates[16] && !lockKeysLatched) { 558 | Keyboard.releaseAll(); 559 | 560 | for (int i = 0; i < 6; i++) { 561 | lastActions[i] = 0; 562 | allActionsThisFrame[i] = 0; 563 | } 564 | 565 | if (systemLocked) { 566 | Serial.println("System unlocked"); 567 | systemLocked = false; 568 | } 569 | else { 570 | Serial.println("System locked"); 571 | systemLocked = true; 572 | } 573 | 574 | lockKeysLatched = true; 575 | } 576 | 577 | for (int i = 0; i < 6; i++) 578 | lastActions[i] = allActionsThisFrame[i]; 579 | 580 | for (int i = 0; i < 6; i++) 581 | lastKnobDeltas[i] = knobDeltas[i]; 582 | 583 | for (int i = 0; i < 18; i++) 584 | lastSwitchStates[i] = switchStates[i]; 585 | 586 | #ifdef DEBUG_PLOT_WHEEL_SPEED 587 | // Don't kick computer in the crotch with firehose of USB traffic 588 | delay(10); 589 | #endif 590 | } 591 | 592 | -------------------------------------------------------------------------------- /Wiring Diagram.sch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | Cherry MX Keyswitch PCB footprints 108 | 109 | 110 | Cherry MX Keyswitch footprint 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | Basic part for Cherry MX series keyswitch 125 | CHERRY-MX 126 | 127 | 128 | >NAME 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | Cherry MX series keyswitch 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | Dangerous Prototypes Standard PCB sizes 157 | http://dangerousprototypes.com 158 | 159 | 160 | Incremental Encoder with switch 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | >NAME 263 | >VALUE 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | >NAME 280 | >VALUE 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | >NAME 298 | >VALUE 299 | 300 | 301 | 302 | 303 | 304 | 305 | Incremental Encoder with push switch 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | <b>Diodes</b><p> 329 | Based on the following sources: 330 | <ul> 331 | <li>Motorola : www.onsemi.com 332 | <li>Fairchild : www.fairchildsemi.com 333 | <li>Philips : www.semiconductors.com 334 | <li>Vishay : www.vishay.de 335 | </ul> 336 | <author>Created by librarian@cadsoft.de</author> 337 | 338 | 339 | <B>DIODE</B><p> 340 | diameter 2.54 mm, horizontal, grid 10.16 mm 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | >NAME 357 | >VALUE 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | >NAME 371 | >VALUE 372 | 373 | 374 | SpiceOrder 1 375 | SpiceOrder 2 376 | 377 | 378 | 379 | 380 | <B>DIODE</B><p> 381 | general purpose rectifier, 1 A 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | <b>Supply Symbols</b><p> 401 | GND, VCC, 0V, +5V, -5V, etc.<p> 402 | Please keep in mind, that these devices are necessary for the 403 | automatic wiring of the supply signals.<p> 404 | The pin name defined in the symbol is identical to the net which is to be wired automatically.<p> 405 | In this library the device names are the same as the pin names of the symbols, therefore the correct signal names appear next to the supply symbols in the schematic.<p> 406 | <author>Created by librarian@cadsoft.de</author> 407 | 408 | 409 | 410 | 411 | 412 | >VALUE 413 | 414 | 415 | 416 | 417 | 418 | <b>SUPPLY SYMBOL</b> 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | Big Wheel Video Controller 485 | Wiring diagram 486 | (c) 2020 Zack Freedman, Voidstar Lab 487 | Licensed Creative Commons 4.0 Attribution Noncommercial Share-Alike 488 | Pin numbering is for Teensy 3.x, LC, and 4.0 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | 850 | 851 | 852 | 853 | 854 | 855 | 856 | 857 | 858 | 859 | 860 | 861 | 862 | 863 | 864 | 865 | 866 | 867 | 868 | 869 | 870 | 871 | 872 | 873 | 874 | 875 | 876 | 877 | 878 | 879 | 880 | 881 | 882 | 883 | 884 | 885 | 886 | 887 | 888 | 889 | 890 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | 898 | 899 | 901 | 902 | 903 | 904 | 905 | 906 | 908 | 909 | 910 | 911 | 912 | 913 | 915 | 916 | 917 | 918 | 919 | 920 | 922 | 923 | 924 | 925 | 926 | 927 | 929 | 930 | 931 | 932 | 933 | 934 | 936 | 937 | 938 | 939 | 940 | 941 | 943 | 944 | 945 | 946 | 947 | 948 | 950 | 951 | 952 | 953 | 954 | 955 | 957 | 958 | 959 | 960 | 961 | 962 | 963 | 964 | 965 | 966 | 967 | 968 | 969 | 970 | 971 | 972 | 973 | 974 | 975 | 976 | 977 | 978 | 979 | 980 | 981 | 982 | 983 | 984 | 985 | 986 | 987 | Since Version 6.2.2 text objects can contain more than one line, 988 | which will not be processed correctly with this version. 989 | 990 | 991 | 992 | --------------------------------------------------------------------------------