├── .gitignore ├── browser.js ├── index.js ├── package.json ├── example.js ├── README.md └── node.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /browser.js: -------------------------------------------------------------------------------- 1 | module.exports = require('web-midi') -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | if (global.navigator && global.navigator.requestMIDIAccess) { 2 | module.exports = require('web-midi') 3 | } else { 4 | module.exports = require('./node') 5 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "midi-stream", 3 | "version": "1.3.2", 4 | "description": "Duplex stream wrapper around 'midi' module", 5 | "main": "index.js", 6 | "browser": "browser.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/mmckegg/midi-stream.git" 13 | }, 14 | "keywords": [ 15 | "midi", 16 | "stream", 17 | "duplex", 18 | "launchpad", 19 | "controller" 20 | ], 21 | "author": "Matt McKegg", 22 | "license": "ISC", 23 | "bugs": { 24 | "url": "https://github.com/mmckegg/midi-stream/issues" 25 | }, 26 | "optionalDependencies": { 27 | "midi": "~0.9.3" 28 | }, 29 | "homepage": "https://github.com/mmckegg/midi-stream", 30 | "dependencies": { 31 | "through": "~2.3.4", 32 | "web-midi": "^2.0.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | var MidiStream = require('./index') 2 | 3 | var duplex = MidiStream('Launchpad', 0) 4 | 5 | // clear lights 6 | duplex.write([176, 0, 0]) 7 | 8 | // echo back the message to launchpad 9 | duplex.pipe(duplex) 10 | 11 | // write buttons pressed to console 12 | duplex.on('data', function(data){ 13 | console.log(data) 14 | }) 15 | 16 | // paint a face after 10 seconds :) 17 | var faceCoords = [ 18 | [1,1], [2,1], [5,1], [6,1], 19 | [1,2], [2,2], [5,2], [6,2], 20 | [2,4], [5,4], 21 | [2,5], [3,5], [4,5], [5,5] 22 | ] 23 | 24 | setTimeout(function(){ 25 | faceCoords.forEach(function(xy){ 26 | var id = xy[1] * 16 + xy[0] // convert coords to midi note 27 | duplex.write([144, id, 60]) 28 | }) 29 | }, 10000) 30 | 31 | // wait 20 seconds then close the port 32 | setTimeout(function(){ 33 | duplex.end() 34 | }, 20000) 35 | 36 | 37 | 38 | 39 | ///// bonus round, second launchpad! //// 40 | var bonus = MidiStream('Launchpad', 1) 41 | faceCoords.forEach(function(xy){ 42 | var id = xy[1] * 16 + xy[0] // convert coords to midi note 43 | bonus.write([144, id, 60]) 44 | }) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | midi-stream 2 | === 3 | 4 | Duplex stream wrapper around [midi](https://github.com/justinlatimer/node-midi) module. 5 | 6 | When required from the browser (using browserify) [web-midi](https://github.com/mmckegg/web-midi) is returned instead. 7 | 8 | ## Install 9 | 10 | ```bash 11 | $ npm install midi-stream 12 | ``` 13 | 14 | ## Example 15 | 16 | ```js 17 | var MidiStream = require('midi-stream') 18 | 19 | var duplex = MidiStream('Launchpad', 0) 20 | 21 | // clear lights 22 | duplex.write([176, 0, 0]) 23 | 24 | // echo back the message to launchpad 25 | duplex.pipe(duplex) 26 | 27 | // write buttons pressed to console 28 | duplex.on('data', function(data){ 29 | console.log(data) 30 | }) 31 | 32 | // paint a face after 10 seconds :) 33 | var faceCoords = [ 34 | [1,1], [2,1], [5,1], [6,1], 35 | [1,2], [2,2], [5,2], [6,2], 36 | [2,4], [5,4], 37 | [2,5], [3,5], [4,5], [5,5] 38 | ] 39 | 40 | setTimeout(function(){ 41 | faceCoords.forEach(function(xy){ 42 | var id = xy[1] * 16 + xy[0] // convert coords to midi note 43 | duplex.write([144, id, 60]) 44 | }) 45 | }, 10000) 46 | 47 | // wait 20 seconds then close the port 48 | setTimeout(function(){ 49 | duplex.end() 50 | }, 20000) 51 | 52 | 53 | ///// bonus round, second launchpad! //// 54 | var bonus = MidiStream('Launchpad', 1) 55 | faceCoords.forEach(function(xy){ 56 | var id = xy[1] * 16 + xy[0] // convert coords to midi note 57 | bonus.write([144, id, 60]) 58 | }) 59 | ``` -------------------------------------------------------------------------------- /node.js: -------------------------------------------------------------------------------- 1 | var midi = require("midi") 2 | var Through = require('through') 3 | 4 | module.exports = function(name, opts){ 5 | var stream = Through(write, end) 6 | 7 | stream.close = stream.end 8 | 9 | var input = stream.inputPort = getInput(name, opts) 10 | var output = stream.outputPort = getOutput(name, opts) 11 | 12 | if (!input || !output){ 13 | stream.emit('error', new Error('No midi device found with name ' + name)) 14 | } 15 | 16 | input.on('message', function(deltaTime, data){ 17 | if (opts && opts.normalizeNotes) { 18 | data = normalizeNotes(data) 19 | } 20 | stream.queue(data) 21 | }) 22 | 23 | function write(data){ 24 | output.sendMessage(data) 25 | this.emit('send', data) 26 | } 27 | 28 | function end(){ 29 | this.queue(null) 30 | input.closePort() 31 | output.closePort() 32 | } 33 | 34 | return stream 35 | } 36 | 37 | module.exports.openInput = function(name, opts){ 38 | var stream = Through(function () {}, end) 39 | var input = stream.inputPort = getInput(name, opts) 40 | 41 | if (!input){ 42 | stream.emit('error', new Error('No midi device found with name ' + name)) 43 | } 44 | 45 | input.on('message', function(deltaTime, data){ 46 | if (opts && opts.normalizeNotes) { 47 | data = normalizeNotes(data) 48 | } 49 | stream.queue(data) 50 | }) 51 | 52 | function end(){ 53 | this.queue(null) 54 | input.closePort() 55 | } 56 | 57 | return stream 58 | } 59 | 60 | module.exports.openOutput = function(name, opts){ 61 | var stream = Through(write, end) 62 | var output = stream.outputPort = getOutput(name, opts) 63 | 64 | if (!output){ 65 | stream.emit('error', new Error('No midi device found with name ' + name)) 66 | } 67 | 68 | function write(data){ 69 | output.sendMessage(data) 70 | this.emit('send', data) 71 | } 72 | 73 | function end(){ 74 | this.queue(null) 75 | output.closePort() 76 | } 77 | 78 | return stream 79 | } 80 | 81 | module.exports.getPortNames = function(cb){ 82 | var used = {} 83 | var names = {} 84 | 85 | process.nextTick(function() { 86 | try { 87 | var input = new midi.input() 88 | var count = input.getPortCount() 89 | for (var i=0;i < count;i++){ 90 | var name = input.getPortName(i) 91 | if (used[name]){ 92 | var i = used[name] += 1 93 | names[name + '/' + i] = true 94 | } else { 95 | used[name] = 1 96 | names[name] = true 97 | } 98 | } 99 | 100 | used = {} 101 | var output = new midi.output() 102 | var count = output.getPortCount() 103 | for (var i=0;i < count;i++){ 104 | var name = output.getPortName(i) 105 | if (used[name]){ 106 | var i = used[name] += 1 107 | names[name + '/' + i] = true 108 | } else { 109 | used[name] = 1 110 | names[name] = true 111 | } 112 | } 113 | 114 | cb&&cb(null, Object.keys(names)) 115 | } catch (ex){ 116 | cb&&cb(ex) 117 | } 118 | }) 119 | } 120 | 121 | function getInput(name, opts){ 122 | if (typeof opts === 'number') opts = {index: opts} 123 | var index = opts && opts.index || 0 124 | var ignoreTiming = !opts || !opts.includeTiming 125 | var port = new midi.input() 126 | if (opts && opts.virtual){ 127 | port.openVirtualPort(name) 128 | port.ignoreTypes(false, ignoreTiming, false) 129 | return port 130 | } else { 131 | var count = port.getPortCount() 132 | for (var i=0;i < count;i++){ 133 | if (port.getPortName(i) === name){ 134 | if (index){ 135 | index -= 1 136 | } else { 137 | port.openPort(i); 138 | port.ignoreTypes(false, ignoreTiming, false) 139 | return port; 140 | } 141 | } 142 | } 143 | } 144 | return null; 145 | } 146 | 147 | function getOutput(name, opts){ 148 | if (typeof opts === 'number') opts = {index: opts} 149 | var index = opts && opts.index || 0 150 | var port = new midi.output(); 151 | if (opts && opts.virtual){ 152 | port.openVirtualPort(name) 153 | return port 154 | } else { 155 | var count = port.getPortCount() 156 | for (var i=0;i < count;i++){ 157 | if (port.getPortName(i) === name){ 158 | if (index){ 159 | index -= 1 160 | } else { 161 | port.openPort(i); 162 | return port; 163 | } 164 | } 165 | } 166 | } 167 | 168 | return null; 169 | } 170 | 171 | function normalizeNotes(data) { 172 | if (data[0] >= 128 && data[0] < 128 + 16){ 173 | // convert note off events to 0 velocity note on events 174 | data = [data[0]+16, data[1], 0] 175 | } 176 | return data 177 | } 178 | --------------------------------------------------------------------------------