├── .gitignore ├── README.md ├── launchpadder.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | *.swp 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Launchpadder 2 | A node library for interacting with the [Novation Launchpad](http://global.novationmusic.com/midi-controllers-digital-dj/launchpad). 3 | 4 | # Installing 5 | You can install Launchpadder via npm: 6 | 7 | ```shell 8 | $ npm install launchpadder 9 | ``` 10 | 11 | # Usage 12 | I tried to make usage as straightforward as possible: 13 | 14 | ```javascript 15 | var launchpadder = require("./launchpadder").Launchpad; 16 | var launchpadColor = require("./launchpadder").Color; 17 | 18 | // The 0 represents the MIDI port to connect with 19 | // The 1 represents the MIDI output-port to connect with 20 | // Both arguments are optional and default to 0 21 | var pad = new launchpadder(0, 1); 22 | 23 | pad.on("press", function(button) { 24 | button.light(launchpadColor.RED); 25 | console.log(button + " was pressed"); 26 | }); 27 | 28 | pad.on("release", function(button) { 29 | button.dark(); 30 | console.log(button + " was released"); 31 | }); 32 | ``` 33 | 34 | # Documentation 35 | Launchpadder was made because I felt that such a simple piece of hardware should also have a simple API behind it. If you feel that any part of the software doesn't follow this philosphy, feel free to open an issue and I'll look into it. So without further adieu, the Launchpadder API: 36 | 37 | ## `Launchpad` class 38 | Represents the launchpad as a whole. 39 | 40 | ### `Launchpad([int midi_in], [int midi_out])` 41 | Constructor. To create a new instance of the Launchpad class you may include a midi port to connect to (probably 0, unless you have a much cooler MIDI setup than I do). Both of these values are optional, however. 42 | 43 | ### `getButton(int x, [int y])` 44 | Gets the button at coordinate x, y. If y is undefined, the method assumes you are providing it with a Launchpad-specific MIDI note and will attempt to convert it into x, y coordinates. 45 | 46 | ### `allDark()` 47 | Sets all of the LEDs to dark. This method works by sending the reset command to the Launchpad (not sending Color.OFF to each button). 48 | 49 | ### `getX(note)` 50 | Returns the X-coordinate for a Launchpad-specific MIDI note. 51 | 52 | ### `getY(note)` 53 | Returns the Y-coordinate for a Launchpad-specific MIDI note. 54 | 55 | ### Events 56 | This class also inherits methods frome Node's [event.EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter). Possible events to listen for are: 57 | 58 | * `press` 59 | * `release` 60 | 61 | Both events emit the button object as an argument. Eg: 62 | 63 | ```javascript 64 | launchpad.on("press", function(button) { 65 | // Do something awesome... 66 | }) 67 | ``` 68 | 69 | ## `Button` class 70 | Represents each individual button on the launchpad. Only obtainable through the `Launchpad` class. 71 | 72 | ### `getX()` 73 | Returns the X-coordinate of the button. 74 | 75 | ### `getY()` 76 | Returns the Y-coordinate of the button. 77 | 78 | ### `light([int color])` 79 | Turns on the button's LED with the specified color (see `Color` object for color constants). If no color is provided, it defaults to amber. 80 | 81 | ### `dark()` 82 | Turns off the button's LED. 83 | 84 | ### `isLit([int color])` 85 | Returns true when the Button is lit up. Optionally: specify "color" to validate against a certain color. 86 | 87 | ### `getState()` 88 | Gets the button's current state, returns a MIDI representation of the color. See `Color` class below. 89 | 90 | ### `toggle([int color], [int color])` 91 | Toggles the button's state between two colors. The first color defaults to amber if unspecified, the second to off. If the current color is neither of the two values, the second value is used. 92 | 93 | ### `startBlinking([int color])` 94 | Starts blinking the button in the specified color (defaults to amber). The timeout is set at 500ms. 95 | 96 | ### `stopBlinking()` 97 | Stops blinking the button. 98 | 99 | ### `isBlinking()` 100 | Returns true or false depending if the button is blinking or not. 101 | 102 | ### `toString()` 103 | Returns a string representation of the button's coordinates in `(x, y)` format. 104 | 105 | ### `toNote()` 106 | Converts the x, y coordinates of the button into the Launchpad-specific MIDI representation. See the `Launchpad` class to get the x and y values back. 107 | 108 | ### Events 109 | This class also inherits methods frome Node's [event.EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter). Possible events to listen for are: 110 | 111 | * `press` 112 | * `release` 113 | 114 | Both events emit the button object as an argument (much like the Launchpad class). 115 | 116 | ## `Color` class 117 | Represents a Launchpad-specific MIDI color. 118 | 119 | ### `getColor([int red], [int green])` 120 | Returns the MIDI color representing your red and green light values (values can be 0 up to a maximum of 3). Both values default to 0, and therefore `getColor()` will return value 12 (which is off; see static colors below). 121 | 122 | ### `getGreen(int note)` 123 | Returns the green value (0 to 3) of the specified MIDI note. 124 | 125 | ### `getRed(int note)` 126 | Returns the red value (0 to 3) of the specified MIDI note. 127 | 128 | Launchpadder comes with several static values stored in the `Color` class which can be used to light up buttons. 129 | 130 | * `#OFF`: 12 131 | * `#LOW_RED`: 13 132 | * `#RED`: 15 133 | * `#LOW_AMBER`: 29 134 | * `#AMBER`: 63 135 | * `#LOW_GREEN`: 28 136 | * `#GREEN`: 60 137 | * `#YELLOW`: 62 138 | -------------------------------------------------------------------------------- /launchpadder.js: -------------------------------------------------------------------------------- 1 | var Midi = require("midi"); 2 | var util = require("util"); 3 | var events = require("events"); 4 | 5 | /* 6 | * Button 7 | * Represents a single button on the Launchpad 8 | */ 9 | var Button = function(grid, note, y) { 10 | this._grid = grid; 11 | this._state = Color.OFF; 12 | 13 | // Are we being assigned via a note or x, y? 14 | if (!y) { 15 | this.x = this._grid.getX(note); 16 | this.y = this._grid.getY(note); 17 | } else { 18 | this.x = note; 19 | this.y = y; 20 | } 21 | 22 | // Setup the event emitter 23 | events.EventEmitter.call(this); 24 | 25 | this.light = function(color) { 26 | color = color || Color.AMBER; 27 | 28 | // Send the instruction to the launchpad 29 | if (this.y == 8) { 30 | grid._output.sendMessage([176, this.toNote(), color]); 31 | } else { 32 | grid._output.sendMessage([144, this.toNote(), color]); 33 | } 34 | // Save the state 35 | this._state = color; 36 | }; 37 | 38 | this.dark = function() { 39 | if (this.y == 8) { 40 | grid._output.sendMessage([176, this.toNote(), Color.OFF]); 41 | } else { 42 | grid._output.sendMessage([144, this.toNote(), Color.OFF]); 43 | } 44 | this._state = Color.OFF; 45 | }; 46 | 47 | this.getState = function() { 48 | return this._state; 49 | }; 50 | 51 | this.getX = function() { return this.x; } 52 | this.getY = function() { return this.y; } 53 | 54 | this.toggle = function(color, color2) { 55 | color = color || Color.AMBER; 56 | color2 = color2 || Color.OFF; 57 | 58 | if (this._state == color2) { 59 | var sColor = color; 60 | } else { 61 | var sColor = color2; 62 | } 63 | this._state = sColor; 64 | 65 | if (this.y == 8) { 66 | grid._output.sendMessage([176, this.toNote(), sColor]); 67 | } else { 68 | grid._output.sendMessage([144, this.toNote(), sColor]); 69 | } 70 | 71 | }; 72 | 73 | this.startBlinking = function(color) { 74 | this._blink_color = color || Color.AMBER; 75 | this._grid._blinking.push(this); 76 | 77 | // If we're adding the first blinking LED, start the interval 78 | if (this._grid._blinking.length == 1) { 79 | this._grid._blink_interval = setInterval(this._grid._tick, 500); 80 | } 81 | }; 82 | 83 | this.stopBlinking = function() { 84 | var index = this._grid._blinking.indexOf(this) 85 | if (index == -1) { 86 | return; 87 | } 88 | delete this._blink_color; 89 | this._grid._blinking.splice(index, 1); 90 | this.dark(); 91 | }; 92 | 93 | this.isBlinking = function() { 94 | if (this._grid._blinking.indexOf(this) == -1) { 95 | return false; 96 | } else { 97 | return true; 98 | } 99 | }; 100 | 101 | this.isLit = function(color) { 102 | if (!color) { 103 | if (this._state == Color.OFF) { 104 | return false; 105 | } 106 | } else { 107 | if (this._state != color) { 108 | return false 109 | } 110 | } 111 | return true; 112 | }; 113 | 114 | // Converts x,y -> MIDI note 115 | this.toNote = function() { 116 | if (this.y == 8) { 117 | return 104 + this.x; 118 | } else { 119 | return (this.y * 16) + this.x; 120 | } 121 | }; 122 | 123 | this.toString = function() { 124 | return "(" + this.x + ", " + this.y + ")"; 125 | }; 126 | }; 127 | 128 | util.inherits(Button, events.EventEmitter); 129 | 130 | /* 131 | * Launchpad 132 | * Represents the launchpad as a whole 133 | */ 134 | var Launchpad = function(midi_input, midi_output) { 135 | midi_input = midi_input || 0; 136 | midi_output = midi_output || midi_input; 137 | var that = this; 138 | this._grid = []; 139 | this._blinking = []; 140 | 141 | // Connect to the MIDI port 142 | this._input = new Midi.input(); 143 | this._output = new Midi.output(); 144 | 145 | this._input.openPort(midi_input); 146 | this._output.openPort(midi_output); 147 | 148 | // Setup the event emitter 149 | events.EventEmitter.call(this); 150 | 151 | // Some functions to resolve X and Y from a note 152 | this.getX = function(note) { 153 | // For right buttons 154 | if (note % 8 == 0 && ((note / 8) % 2 == 1)) { 155 | return 8; 156 | } 157 | return note % 8; 158 | }; 159 | this.getY = function(note) { 160 | // For right buttons 161 | if (note % 8 == 0 && ((note / 8) % 2 == 1)) { 162 | return Math.floor(note / 8 / 2); 163 | } 164 | return Math.floor(note / 8) / 2; 165 | }; 166 | 167 | // Initialize all of the buttons 168 | for (var x = 0; x < 9; x++) { 169 | this._grid.push([]); 170 | for (var y = 0; y < 9; y++) { 171 | this._grid[x][y] = new Button(this, x, y); 172 | } 173 | } 174 | 175 | // Gets a button object from this._grid 176 | this.getButton = function(x, y) { 177 | if (y) { 178 | if (x > 8 || y > 8) { 179 | return null; 180 | } 181 | return this._grid[x][y]; 182 | } 183 | // Hax 8D 184 | return this._grid[that.getX(x)][that.getY(x)]; 185 | }; 186 | 187 | // Turns all LEDs off 188 | this.allDark = function() { 189 | this._output.sendMessage([176, 0, 0]); 190 | 191 | // Reset the state on all buttons 192 | for (var x = 0; x < 9; x++) { 193 | for (var y = 0; y < 9; y++) { 194 | this._grid[x][y]._state = false; 195 | } 196 | } 197 | }; 198 | 199 | this._tick = function() { 200 | if(that._blinking.length == 0) { 201 | clearInterval(that._blink_interval); 202 | // MOD start 203 | return; 204 | // MOD end 205 | } 206 | for (var i in that._blinking) { 207 | if(that._blinking[i].getState() == Color.OFF) { 208 | that._blinking[i].light(that._blinking[i]._blink_color); 209 | } else { 210 | that._blinking[i].dark(); 211 | } 212 | } 213 | }; 214 | 215 | // Event handler for Button press 216 | this._input.on("message", function(deltaTime, msg) { 217 | // Parse the MIDI message 218 | msg = msg.toString().split(","); 219 | 220 | // We have to do something special for the top buttons 221 | if (msg[0] == "176") { 222 | var button = that.getButton(parseInt(msg[1]) % 8, 8); 223 | } else { 224 | var button = that.getButton(msg[1]); 225 | } 226 | 227 | // On or off? 228 | var state = (parseInt(msg[2]) == 127) ? true : false; 229 | 230 | // Emit an event 231 | if (state) { 232 | that.emit("press", button); 233 | button.emit("press", button); 234 | } else { 235 | that.emit("release", button); 236 | button.emit("release", button); 237 | } 238 | }); 239 | }; 240 | util.inherits(Launchpad, events.EventEmitter); 241 | 242 | var Color = { 243 | OFF: 12, 244 | LOW_RED: 13, 245 | RED: 15, 246 | LOW_AMBER: 29, 247 | AMBER: 63, 248 | LOW_GREEN: 28, 249 | GREEN: 60, 250 | YELLOW: 62, 251 | getColor: function(green, red) { 252 | green = green || 0; 253 | red = red || 0; 254 | return 16 * green + red + 12; 255 | }, 256 | getGreen: function(color) { 257 | if (!color) { 258 | return false; 259 | } 260 | return Math.floor(color / 16); 261 | }, 262 | getRed: function(color) { 263 | if (!color) { 264 | return false; 265 | } 266 | return (color - 12) % 16; 267 | } 268 | }; 269 | 270 | exports.Launchpad = Launchpad; 271 | exports.Color = Color; 272 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "launchpadder", 3 | "version": "v0.0.4", 4 | "engines": { 5 | "node": ">=0.8.0" 6 | }, 7 | "description": "A library for making the Novation Launchpad incredibly easy to play with.", 8 | "author": { 9 | "name": "Steve Gattuso", 10 | "email": "steve@stevegattuso.me", 11 | "url": "http://stevegattuso.me" 12 | }, 13 | "main": "launchpadder.js", 14 | "dependencies": { 15 | "midi": "git://github.com/drewish/node-midi.git#master" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/stevenleeg/launchpadder.git" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var launchpadder = require("./launchpadder").Launchpad; 2 | var Color = require("./launchpadder").Color; 3 | 4 | // The 0 represents the MIDI port to connect with 5 | // The 1 represents the MIDI output-port to connect with 6 | var pad = new launchpadder(0, 0); 7 | 8 | pad.on("press", function (button) { 9 | // Ignore scene buttons 10 | if (button.getX() == 8) { 11 | console.log("Scene button " + button.getY() + " was pressed"); 12 | return; 13 | } 14 | button.light(); 15 | console.log(button + " was pressed"); 16 | }); 17 | 18 | pad.on("release", function (button) { 19 | // Ignore scene buttons 20 | if (button.getX() == 8) { 21 | console.log("Scene button " + button.getY() + " was released"); 22 | return; 23 | } 24 | button.dark(); 25 | console.log(button + " was released"); 26 | }); 27 | 28 | 29 | // Start blinking the first scene-button 30 | var state = Color.GREEN; 31 | var scene = pad.getButton(8, 0); 32 | scene.startBlinking(state); 33 | 34 | // Toggle blink Color on press 35 | scene.on("press", function (button) { 36 | if (state == Color.GREEN) { 37 | button.startBlinking(Color.YELLOW); 38 | } else { 39 | button.startBlinking(Color.GREEEN); 40 | } 41 | }); 42 | 43 | // Toggle on/off state for second scene button 44 | var scene2 = pad.getButton(8, 1); 45 | scene2.light(Color.AMBER); 46 | scene2.on("press", function (button) { 47 | button.toggle(Color.AMBER, color.RED); 48 | }); 49 | 50 | --------------------------------------------------------------------------------