├── .gitignore ├── sfx ├── a.mp3 ├── b.mp3 └── c.mp3 ├── arduino └── button_basher │ ├── settings.h │ └── button_basher.ino └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_store 2 | -------------------------------------------------------------------------------- /sfx/a.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/button-basher/master/sfx/a.mp3 -------------------------------------------------------------------------------- /sfx/b.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/button-basher/master/sfx/b.mp3 -------------------------------------------------------------------------------- /sfx/c.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/button-basher/master/sfx/c.mp3 -------------------------------------------------------------------------------- /arduino/button_basher/settings.h: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | 3 | /************************** 4 | * KEY CODES AND OPTIONS * 5 | ***************************/ 6 | 7 | int keyCodesAndOptions[NUM_INPUTS][OPTIONS_PER_INPUT] = 8 | { 9 | // Output 1, Output 2, Output 3, Option 4, key, // Position on MM 10 | { KEY_LEFT_CTRL, KEY_LEFT_SHIFT, KEY_LEFT_ALT, 'a' }, // left arrow pad 11 | { KEY_LEFT_CTRL, KEY_LEFT_SHIFT, KEY_LEFT_ALT, 'b' }, // pin D5 12 | { KEY_LEFT_CTRL, KEY_LEFT_SHIFT, KEY_LEFT_ALT, 'c' }, // pin D4 13 | { KEY_LEFT_CTRL, KEY_LEFT_SHIFT, KEY_LEFT_ALT, 'd' }, // pin D3 14 | { KEY_LEFT_CTRL, KEY_LEFT_SHIFT, KEY_LEFT_ALT, 'e' }, // pin D2 15 | { KEY_LEFT_CTRL, KEY_LEFT_SHIFT, KEY_LEFT_ALT, 'f' }, // pin D1 16 | { KEY_LEFT_CTRL, KEY_LEFT_SHIFT, KEY_LEFT_ALT, 'g' }, // pin D0 17 | { KEY_LEFT_CTRL, KEY_LEFT_SHIFT, KEY_LEFT_ALT, 'h' }, // pin A5 18 | { KEY_LEFT_CTRL, KEY_LEFT_SHIFT, KEY_LEFT_ALT, 'i' }, // pin A4 19 | { KEY_LEFT_CTRL, KEY_LEFT_SHIFT, KEY_LEFT_ALT, 'j' }, // pin A3 20 | { KEY_LEFT_CTRL, KEY_LEFT_SHIFT, KEY_LEFT_ALT, 'k' }, // pin A2 21 | { KEY_LEFT_CTRL, KEY_LEFT_SHIFT, KEY_LEFT_ALT, 'l' }, // pin A1 22 | { KEY_LEFT_CTRL, KEY_LEFT_SHIFT, KEY_LEFT_ALT, 'm' }, // pin A0 23 | }; 24 | 25 | /////////////////////////// 26 | // NOISE CANCELLATION ///// 27 | /////////////////////////// 28 | #define SWITCH_THRESHOLD_OFFSET_PERC 5 // number between 1 and 49 29 | // larger value protects better against noise oscillations, but makes it harder to press and release 30 | // recommended values are between 2 and 20 31 | // default value is 5 32 | 33 | #define SWITCH_THRESHOLD_CENTER_BIAS 55 // number between 1 and 99 34 | // larger value makes it easier to "release" keys, but harder to "press" 35 | // smaller value makes it easier to "press" keys, but harder to "release" 36 | // recommended values are between 30 and 70 37 | // 50 is "middle" 2.5 volt center 38 | // default value is 55 39 | // 100 = 5V (never use this high) 40 | // 0 = 0 V (never use this low 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Button Basher 2 | 3 | Using a [Makey Makey](http://www.makeymakey.com/) to control Spotify and play sounds through a mac, via buttons and other physical inputs. 4 | 5 | 6 | ## Setup - Makey Makey 7 | 8 | First, set up the Makey Makey with custom keyboard mappings. It can't use the default key bindings unless you want to make the machine unusable for anything else. Instead, the controls should be mapped to keys that won't otherwise be used. 9 | 10 | Update [Makey Makey](http://www.makeymakey.com/) firmware to map custom keys. I have an older device and so followed [this tutorial](https://learn.sparkfun.com/tutorials/makey-makey-advanced-guide). If you have a newer device you may be able to do it [through a browser](http://makeymakey.com/remap/). 11 | 12 | I wanted to remap multiple keys, which the default Makey Makey firmware doesn't let you do, so I adapted [this firmware](https://github.com/DavidRieman/MaKeyMaKey_Flexible/) instead. 13 | 14 | My firmware is in this repo, in the `/arduino` directory. 15 | 16 | The article mentioned above contains detailed set-up instructions, here in brief is the process I followed: 17 | 18 | - Download [Arduino](http://arduino.cc/) app 19 | - Download either the [custom firmware](https://github.com/DavidRieman/MaKeyMaKey_Flexible/), the [original Makey Makey firmware](https://github.com/sparkfun/MaKeyMaKey) or use my version, and open the main arduino `.ino` file in the Arduino IDE 20 | - Add the _Additional Board Manager URL_ in the Arudino IDE settings: 21 | 22 | ``` 23 | https://raw.githubusercontent.com/sparkfun/Arduino_Boards/master/IDE_Board_Manager/package_sparkfun_index.json 24 | ``` 25 | - From the _Board Manager_ menu select _SparkFun AVR Boards_ 26 | - Select the Makey Makey board 27 | - Set the correct serial port 28 | 29 | Once this is done, you should be able to connect buttons and other inputs to the Makey Makey. Instead of the default keys they should now map to your custom keys. 30 | 31 | My custom firmware uses the 6 analog and 6 digital pins as button inputs. In addition the 'left' arrow holes is used for another button input, and the 'click' holes are used as an LED output. 32 | 33 | An easy way to test this is through the [Keypress](https://dmauro.github.io/Keypress/) website that shows you onscreen the keys that are currently being pressed. 34 | 35 | 36 | ## Setup - mac 37 | 38 | ### Creating custom services with Automator 39 | 40 | Once the Makey Makey is ready, the mac can be set up to detect and act on keyboard input with new services triggered by custom shortcuts. 41 | 42 | I set up new services in Automator following [these instructions](http://apple.stackexchange.com/questions/175215/how-do-i-assign-a-keyboard-shortcut-to-an-applescript-i-wrote): 43 | 44 | - Launch the Automator app 45 | - Create a new service 46 | - Select "no input" and "any application" 47 | - Select "Run AppleScript" or "" 48 | - Enter AppleScript (see below) 49 | - Save the service 50 | 51 | Here are some example AppleScript services: 52 | 53 | `SpotifyToggle` 54 | 55 | ``` 56 | -- Toggle 57 | on run args 58 | try 59 | tell application "Spotify" 60 | playpause 61 | end tell 62 | end try 63 | end run 64 | ``` 65 | 66 | `SpotifyPrevious` 67 | 68 | ``` 69 | -- Previous 70 | on run args 71 | try 72 | tell application "Spotify" 73 | previous track 74 | end tell 75 | end try 76 | end run 77 | ``` 78 | 79 | `SpotifyNext` 80 | 81 | ``` 82 | -- Next 83 | on run args 84 | try 85 | tell application "Spotify" 86 | next track 87 | end tell 88 | end try 89 | end run 90 | ``` 91 | 92 | Alternatively, here are some custom services using Python, which can be run as a shell script: 93 | 94 | `button-sfx` 95 | 96 | ``` 97 | import subprocess 98 | subprocess.call(["afplay", "/path/to/button-basher/sfx/b.mp3"]) 99 | ``` 100 | 101 | `SpotifyPauseplay` 102 | 103 | ``` 104 | import subprocess 105 | 106 | osa = subprocess.Popen(['osascript', '-'], stdin=subprocess.PIPE, stdout=subprocess.PIPE) 107 | state = osa.communicate('tell application "Spotify" to return player state')[0] 108 | 109 | if state.strip() == "playing": 110 | subprocess.call(['osascript', '-e', 'tell application "Spotify" to pause']) 111 | subprocess.call(["afplay", "/path/to/button-basher/sfx/pause.mp3"]) 112 | else: 113 | subprocess.call(["afplay", "/path/to/button-basher/sfx/play.mp3"]) 114 | subprocess.call(['osascript', '-e', 'tell application "Spotify" to play']) 115 | 116 | ``` 117 | 118 | ### Triggering services with a custom keyboard shortcut 119 | 120 | To set up custom keyboard shortcuts: 121 | 122 | - System Preferences > Keyboard > Shortcuts 123 | - Select Services 124 | - Add a shortcut for your service - click the name, then use the Makey Makey to add the input 125 | - System Preferences > Security & Privacy > Privacy 126 | - Select Accessibility 127 | - Click on the + sign, add Automator and Finder (`/System/Library/CoreServices/Finder.app`) 128 | - From any application, go to the menu and run the new shortcut once manually 129 | 130 | That's it - now when you click on a button with the Makey Makey, the custom service should run. 131 | -------------------------------------------------------------------------------- /arduino/button_basher/button_basher.ino: -------------------------------------------------------------------------------- 1 | /* 2 | ************ MAKEY MAKEY CUSTOM FIRMWARE ************ 3 | * 4 | * Adapted from: 5 | * 6 | * - https://github.com/sparkfun/MaKeyMaKey 7 | * - https://github.com/DavidRieman/MaKeyMaKey_Flexible/ 8 | * 9 | */ 10 | 11 | //////////////////////// 12 | // DEFINED CONSTANTS//// 13 | //////////////////////// 14 | 15 | #define BUFFER_LENGTH 3 // 3 bytes gives us 24 samples 16 | #define NUM_INPUTS 13 // 6A + 6D + 1 17 | #define OPTIONS_PER_INPUT 4 // CTRL + ALT + SHIFT + 'letter' 18 | #define KEYS_PER_INPUT 4 // ditto 19 | #define TARGET_LOOP_TIME 744 // (1/56 seconds) / 24 samples = 744 microseconds per sample 20 | 21 | #include 22 | #include "settings.h" 23 | 24 | ///////////////////////// 25 | // STRUCT /////////////// 26 | ///////////////////////// 27 | 28 | typedef struct { 29 | byte pinNumber; 30 | int keyCode[KEYS_PER_INPUT]; 31 | byte measurementBuffer[BUFFER_LENGTH]; 32 | boolean oldestMeasurement; 33 | byte bufferSum; 34 | boolean pressed; 35 | boolean prevPressed; 36 | boolean isKeyboardKey; 37 | boolean isReverseInput; 38 | } 39 | 40 | MakeyMakeyInput; 41 | 42 | MakeyMakeyInput inputs[NUM_INPUTS]; 43 | 44 | /////////////////////////////////// 45 | // VARIABLES ////////////////////// 46 | /////////////////////////////////// 47 | 48 | int bufferIndex = 0; 49 | byte byteCounter = 0; 50 | byte bitCounter = 0; 51 | 52 | int pressThreshold; 53 | int releaseThreshold; 54 | boolean inputChanged; 55 | 56 | // Pin Numbers 57 | int pinNumbers[NUM_INPUTS] = { 58 | 13, // top of makey makey board (left) 59 | 5, 4, 3, 2, 1, 0, // left side of female header, KEYBOARD 60 | 23, 22, 21, 20, 19, 18 // right side of female header, MOUSE 61 | }; 62 | 63 | // input status LED pin numbers 64 | const int inputLED_a = 9; 65 | const int inputLED_b = 10; 66 | const int inputLED_c = 11; 67 | const int outputK = 14; 68 | const int outputM = 16; 69 | byte ledCycleCounter = 0; 70 | 71 | // timing 72 | int loopTime = 0; 73 | int prevTime = 0; 74 | int loopCounter = 0; 75 | 76 | // LED 77 | int buttonLed = 6; 78 | int buttonBrightness = 0; 79 | int buttonFadeAmount = 1; 80 | int buttonDelay = 6; 81 | 82 | /////////////////////////// 83 | // FUNCTIONS ////////////// 84 | /////////////////////////// 85 | void initializeArduino(); 86 | void initializeInputs(); 87 | void updateMeasurementBuffers(); 88 | void updateBufferSums(); 89 | void updateBufferIndex(); 90 | void updateInputStates(); 91 | void addDelay(); 92 | void cycleLEDs(); 93 | void danceLeds(); 94 | void updateOutLEDs(); 95 | 96 | ////////////////////// 97 | // SETUP ///////////// 98 | ////////////////////// 99 | void setup() 100 | { 101 | initializeArduino(); 102 | initializeInputs(); 103 | danceLeds(); 104 | } 105 | 106 | //////////////////// 107 | // MAIN LOOP /////// 108 | //////////////////// 109 | void loop() 110 | { 111 | updateMeasurementBuffers(); 112 | updateBufferSums(); 113 | updateBufferIndex(); 114 | updateInputStates(); 115 | cycleLEDs(); 116 | updateOutLEDs(); 117 | addDelay(); 118 | fadeButton(); 119 | } 120 | 121 | ////////////////////////// 122 | // INITIALIZE ARDUINO 123 | ////////////////////////// 124 | void initializeArduino() { 125 | #ifdef DEBUG 126 | Serial.begin(9600); // Serial for debugging 127 | #endif 128 | 129 | /* Set up input pins 130 | DEactivate the internal pull-ups, since we're using external resistors */ 131 | for (int i=0; i> bitCounter) & 0x01; 202 | 203 | boolean newMeasurement; 204 | 205 | // Make the new measurement, by reading the input directly. 206 | newMeasurement = digitalRead(inputs[i].pinNumber); 207 | 208 | // invert so that true means the switch is closed 209 | newMeasurement = !newMeasurement; 210 | 211 | // store it 212 | if (newMeasurement) { 213 | currentByte |= (1<> bitCounter) & 0x01; 233 | if (currentMeasurement) { 234 | inputs[i].bufferSum++; 235 | } 236 | if (inputs[i].oldestMeasurement) { 237 | inputs[i].bufferSum--; 238 | } 239 | } 240 | } 241 | 242 | /////////////////////////// 243 | // UPDATE BUFFER INDEX 244 | /////////////////////////// 245 | void updateBufferIndex() { 246 | bitCounter++; 247 | if (bitCounter == 8) { 248 | bitCounter = 0; 249 | byteCounter++; 250 | if (byteCounter == BUFFER_LENGTH) { 251 | byteCounter = 0; 252 | } 253 | } 254 | } 255 | 256 | int InputIsKeyboardKey(int inputCode) { 257 | return inputCode > 0; 258 | } 259 | 260 | void PressKeyboard(int inputIndex) { 261 | MakeyMakeyInput input = inputs[inputIndex]; 262 | for (int k=0; k pressThreshold) { // input becomes pressed 309 | if (inputs[i].isKeyboardKey) { 310 | inputs[i].pressed = true; 311 | PressKeyboard(i); 312 | } 313 | } 314 | } 315 | } 316 | } 317 | 318 | /////////////////////////// 319 | // ADD DELAY 320 | /////////////////////////// 321 | void addDelay() { 322 | loopTime = micros() - prevTime; 323 | if (loopTime < TARGET_LOOP_TIME) { 324 | int wait = TARGET_LOOP_TIME - loopTime; 325 | delayMicroseconds(wait); 326 | } 327 | 328 | prevTime = micros(); 329 | } 330 | 331 | /////////////////////////// 332 | // CYCLE LEDS 333 | /////////////////////////// 334 | void cycleLEDs() { 335 | pinMode(inputLED_a, INPUT); 336 | pinMode(inputLED_b, INPUT); 337 | pinMode(inputLED_c, INPUT); 338 | digitalWrite(inputLED_a, LOW); 339 | digitalWrite(inputLED_b, LOW); 340 | digitalWrite(inputLED_c, LOW); 341 | 342 | ledCycleCounter++; 343 | ledCycleCounter %= 6; 344 | 345 | if ((ledCycleCounter == 0) && inputs[0].pressed) { 346 | pinMode(inputLED_a, INPUT); 347 | digitalWrite(inputLED_a, LOW); 348 | pinMode(inputLED_b, OUTPUT); 349 | digitalWrite(inputLED_b, HIGH); 350 | pinMode(inputLED_c, OUTPUT); 351 | digitalWrite(inputLED_c, LOW); 352 | } 353 | if ((ledCycleCounter == 1) && inputs[1].pressed) { 354 | pinMode(inputLED_a, OUTPUT); 355 | digitalWrite(inputLED_a, HIGH); 356 | pinMode(inputLED_b, OUTPUT); 357 | digitalWrite(inputLED_b, LOW); 358 | pinMode(inputLED_c, INPUT); 359 | digitalWrite(inputLED_c, LOW); 360 | } 361 | if ((ledCycleCounter == 2) && inputs[2].pressed) { 362 | pinMode(inputLED_a, OUTPUT); 363 | digitalWrite(inputLED_a, LOW); 364 | pinMode(inputLED_b, OUTPUT); 365 | digitalWrite(inputLED_b, HIGH); 366 | pinMode(inputLED_c, INPUT); 367 | digitalWrite(inputLED_c, LOW); 368 | } 369 | if ((ledCycleCounter == 3) && inputs[3].pressed) { 370 | pinMode(inputLED_a, INPUT); 371 | digitalWrite(inputLED_a, LOW); 372 | pinMode(inputLED_b, OUTPUT); 373 | digitalWrite(inputLED_b, LOW); 374 | pinMode(inputLED_c, OUTPUT); 375 | digitalWrite(inputLED_c, HIGH); 376 | } 377 | if ((ledCycleCounter == 4) && inputs[4].pressed) { 378 | pinMode(inputLED_a, OUTPUT); 379 | digitalWrite(inputLED_a, LOW); 380 | pinMode(inputLED_b, INPUT); 381 | digitalWrite(inputLED_b, LOW); 382 | pinMode(inputLED_c, OUTPUT); 383 | digitalWrite(inputLED_c, HIGH); 384 | } 385 | if ((ledCycleCounter == 5) && inputs[5].pressed) { 386 | pinMode(inputLED_a, OUTPUT); 387 | digitalWrite(inputLED_a, HIGH); 388 | pinMode(inputLED_b, INPUT); 389 | digitalWrite(inputLED_b, LOW); 390 | pinMode(inputLED_c, OUTPUT); 391 | digitalWrite(inputLED_c, LOW); 392 | } 393 | } 394 | 395 | /////////////////////////// 396 | // DANCE LEDS 397 | /////////////////////////// 398 | void danceLeds() 399 | { 400 | int delayTime = 50; 401 | int delayTime2 = 100; 402 | 403 | // CIRCLE 404 | for(int i=0; i<4; i++) 405 | { 406 | // UP 407 | pinMode(inputLED_a, INPUT); 408 | digitalWrite(inputLED_a, LOW); 409 | pinMode(inputLED_b, OUTPUT); 410 | digitalWrite(inputLED_b, HIGH); 411 | pinMode(inputLED_c, OUTPUT); 412 | digitalWrite(inputLED_c, LOW); 413 | delay(delayTime); 414 | 415 | // RIGHT 416 | pinMode(inputLED_a, INPUT); 417 | digitalWrite(inputLED_a, LOW); 418 | pinMode(inputLED_b, OUTPUT); 419 | digitalWrite(inputLED_b, LOW); 420 | pinMode(inputLED_c, OUTPUT); 421 | digitalWrite(inputLED_c, HIGH); 422 | delay(delayTime); 423 | 424 | // DOWN 425 | pinMode(inputLED_a, OUTPUT); 426 | digitalWrite(inputLED_a, HIGH); 427 | pinMode(inputLED_b, OUTPUT); 428 | digitalWrite(inputLED_b, LOW); 429 | pinMode(inputLED_c, INPUT); 430 | digitalWrite(inputLED_c, LOW); 431 | delay(delayTime); 432 | 433 | // LEFT 434 | pinMode(inputLED_a, OUTPUT); 435 | digitalWrite(inputLED_a, LOW); 436 | pinMode(inputLED_b, OUTPUT); 437 | digitalWrite(inputLED_b, HIGH); 438 | pinMode(inputLED_c, INPUT); 439 | digitalWrite(inputLED_c, LOW); 440 | delay(delayTime); 441 | } 442 | 443 | // WIGGLE 444 | for(int i=0; i<4; i++) 445 | { 446 | // SPACE 447 | pinMode(inputLED_a, OUTPUT); 448 | digitalWrite(inputLED_a, HIGH); 449 | pinMode(inputLED_b, INPUT); 450 | digitalWrite(inputLED_b, LOW); 451 | pinMode(inputLED_c, OUTPUT); 452 | digitalWrite(inputLED_c, LOW); 453 | delay(delayTime2); 454 | 455 | // CLICK 456 | pinMode(inputLED_a, OUTPUT); 457 | digitalWrite(inputLED_a, LOW); 458 | pinMode(inputLED_b, INPUT); 459 | digitalWrite(inputLED_b, LOW); 460 | pinMode(inputLED_c, OUTPUT); 461 | digitalWrite(inputLED_c, HIGH); 462 | delay(delayTime2); 463 | } 464 | } 465 | 466 | void updateOutLEDs() 467 | { 468 | boolean keyPressed = 0; 469 | 470 | for (int i=0; i= 255 ) { 508 | buttonFadeAmount = -buttonFadeAmount; 509 | } 510 | } 511 | } 512 | --------------------------------------------------------------------------------