├── .gitignore ├── LICENSE ├── README.md ├── config.js ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Andy Swing (https://github.com/TheOriginalAndrobot) 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # midi2mqtt 2 | 3 | [![License][mit-badge]][mit-url] 4 | [![NPM version](https://badge.fury.io/js/midi2mqtt.svg)](http://badge.fury.io/js/midi2mqtt) 5 | 6 | This is a simple bridge between a MIDI device and MQTT. Right now it only supports `noteon` `noteoff` 7 | `poly aftertouch` and `cc` messages. 8 | 9 | ## Getting started 10 | 11 | ### Prerequisites 12 | * Node.js >= 10.13.0 (including npm). 13 | * MIDI device connected and showing up when running e.g. `amidi -l` 14 | * All pre-requisites from the node-midi package, including the libasound2-dev package if on Linux 15 | 16 | ### Install: 17 | Run `sudo npm install --unsafe-perm -g midi2mqtt`. Note that you may need to first run `sudo apt-get install libasound2-dev` 18 | to satisfy dependancies of the Node.js midi library. 19 | 20 | 21 | ### Start: 22 | First run `midi2mqtt --help` to see how the program is used. 23 | * You will likely need to specify the MIDI port (`-p`) 24 | * You can also specify the MQTT topic prefix with `-t`, including slashes (e.g. `-t "home/midi"`). 25 | 26 | If you do not know the MIDI port names on your system, you can simply run `midi2mqtt` and look for the 27 | messages about avaialble MIDI input and output devices. 28 | 29 | Simply press `ctrl-c` to exit the application on the command line. 30 | 31 | ### Example command line: 32 | `midi2mqtt -t "house/midi" -u "mqtt://mqtt-server" -p "Midi Fighter 3D 20:0"` 33 | 34 | ## Topics and Payloads 35 | 36 | ### Updates from MIDI device to MQTT (read-only) 37 | 38 | Topics take the form `/out/// ` 39 | where \ defaults to 'midi', \ is the MIDI channel (0-15), \ is one 40 | of 'noteon', 'noteoff', etc., \ depends on type, and \ is the message payload 41 | which also depends on type. 42 | 43 | Currently supported topics are as follows: 44 | /out//noteon/ 45 | /out//noteoff/ 46 | /out//poly_aftertouch/ 47 | /out//cc/ 48 | 49 | 50 | ### Commands from MQTT to MIDI device (write-only) 51 | 52 | Topics take the form `/in/// ` 53 | where \ defaults to 'midi', \ is the MIDI channel (0-15), \ is one 54 | of 'noteon', 'noteoff', etc., \ depends on type, and \ is the message payload 55 | which also depends on type. 56 | 57 | Currently supported topics are as follows: 58 | /in//noteon/ 59 | /in//noteoff/ 60 | /in//poly_aftertouch/ 61 | /in//cc/ 62 | 63 | 64 | ### Special-purpose topics 65 | 66 | #### \/connected 67 | Read-only status of the program: 68 | * `1` when program is up 69 | * `0` when the program exits or MQTT disconnects 70 | 71 | 72 | ## Starting at boot 73 | 74 | You can easily start this program on boot using systemd. 75 | 76 | First, as root, create `/usr/lib/systemd/system/midi2mqtt.service` with the following contents: 77 | 78 | [Unit] 79 | Description=Bridge from MIDI to MQTT 80 | Wants=network-online.target 81 | After=network-online.target 82 | 83 | [Service] 84 | Type=simple 85 | ExecStart=/usr/local/bin/midi2mqtt -t "midi" -u "http://mqtt-server" -p "MIDI Device Name" 86 | Restart=always 87 | User= 88 | Group= 89 | 90 | # Give a reasonable amount of time for the server to start up/shut down 91 | TimeoutSec=300 92 | 93 | [Install] 94 | WantedBy=multi-user.target 95 | 96 | **Note:** Be sure to edit the midi2mqtt command line args to match your setup as well as 97 | change the User and Group to what the program should run as. 98 | 99 | 100 | ## License 101 | 102 | MIT © [Andy Swing](https://github.com/TheOriginalAndrobot) 103 | 104 | [mit-badge]: https://img.shields.io/badge/License-MIT-blue.svg?style=flat 105 | [mit-url]: LICENSE 106 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | var pkg = require('./package.json'); 2 | var config = require('yargs') 3 | .usage(pkg.name + ' ' + pkg.version + '\n' + pkg.description + '\n\nUsage: $0 [options]') 4 | .describe('v', 'possible values: "error", "warn", "info", "debug"') 5 | .describe('t', 'prefix for connected topic') 6 | .describe('u', 'mqtt broker url. See https://github.com/mqttjs/MQTT.js#connect-using-a-url') 7 | .describe('p', 'Full MIDI port/device name') 8 | .describe('h', 'show help') 9 | .alias({ 10 | 'h': 'help', 11 | 'v': 'verbosity', 12 | 't': 'topic', 13 | 'u': 'url', 14 | 'p': 'midi-port' 15 | }) 16 | .default({ 17 | 'v': 'info', 18 | 't': 'midi', 19 | 'u': 'mqtt://127.0.0.1', 20 | 'p': 'Midi Fighter 3D 20:0' 21 | }) 22 | .version(pkg.name + ' ' + pkg.version + '\n', 'version') 23 | .help('help') 24 | .argv; 25 | 26 | module.exports = config; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // 4 | // MIDI to MQTT bridge 5 | // 6 | // Author: Andy Swing 7 | // 8 | 9 | 10 | var pkg = require('./package.json'); 11 | var config = require('./config.js'); 12 | var log = require('yalm'); 13 | var Mqtt = require('mqtt'); 14 | var Midi = require('easymidi'); 15 | 16 | log.setLevel(config.v); 17 | log.info(pkg.name + ' ' + pkg.version + ' starting'); 18 | 19 | var mqttConnected; 20 | 21 | 22 | // 23 | // MQTT 24 | // 25 | 26 | log.info('mqtt trying to connect to', config.url); 27 | var mqtt = Mqtt.connect(config.url, {will: {topic: config.topic + '/connected', payload: '0'}}); 28 | 29 | // Shotcut for publishing to MQTT and logging it 30 | function pubMQTT(topic, payload){ 31 | var fullTopic = config.topic + '/' + topic; 32 | log.debug('mqtt >', fullTopic, payload); 33 | mqtt.publish(fullTopic, payload); 34 | } 35 | 36 | mqtt.on('connect', function () { 37 | mqttConnected = true; 38 | log.info('mqtt connected ' + config.url); 39 | mqtt.publish(config.topic + '/connected', '1'); 40 | log.info('mqtt subscribe', config.topic + '/in/+/+/+'); 41 | mqtt.subscribe(config.topic + '/in/+/+/+'); 42 | 43 | }); 44 | 45 | mqtt.on('close', function () { 46 | if (mqttConnected) { 47 | mqttConnected = false; 48 | log.info('mqtt closed ' + config.url); 49 | } 50 | }); 51 | 52 | mqtt.on('error', function (error) { 53 | log.error('mqtt error ' + error); 54 | }); 55 | 56 | mqtt.on('message', function (topic, payload) { 57 | payload = payload.toString(); 58 | log.debug('mqtt <', topic, payload); 59 | 60 | var parts = topic.split('/'); 61 | var channel = parseInt(parts[parts.length-3]); 62 | var type = parts[parts.length-2].toString(); 63 | var param = parseInt(parts[parts.length-1]); 64 | var value = parseInt(payload); 65 | 66 | log.debug('midi > channel:', channel, 'type:', type, 'parameter:', param, 'value:', value); 67 | 68 | switch (type) { 69 | case 'noteon': 70 | case 'noteoff': 71 | var data = Object(); 72 | data['note'] = param; 73 | data['velocity'] = value; 74 | data['channel'] = channel; 75 | midiOut.send(type, data); 76 | break; 77 | case 'cc': 78 | var data = Object(); 79 | data['controller'] = param; 80 | data['value'] = value; 81 | data['channel'] = channel; 82 | midiOut.send(type, data); 83 | break; 84 | case 'poly_aftertouch': 85 | var data = Object(); 86 | data['note'] = param; 87 | data['velocity'] = value; 88 | data['channel'] = channel; 89 | midiOut.send('poly aftertouch', data); 90 | break; 91 | default: 92 | log.error('Unsupported command \'' + type + '\' recieved via MQTT'); 93 | break; 94 | } 95 | 96 | }); 97 | 98 | 99 | // 100 | // Midi connections 101 | // 102 | log.info('Available MIDI inputs: ', Midi.getInputs()); 103 | log.info('Available MIDI outputs: ', Midi.getOutputs()); 104 | 105 | var midiIn = new Midi.Input(config.midiPort); 106 | var midiOut = new Midi.Output(config.midiPort); 107 | 108 | midiIn.on('noteoff', function (msg) { 109 | log.debug('midi < noteoff', msg.note, msg.velocity, msg.channel); 110 | pubMQTT('out/' + msg.channel + '/noteoff/' + msg.note, msg.velocity.toString()); 111 | }); 112 | 113 | midiIn.on('noteon', function (msg) { 114 | log.debug('midi < noteon', msg.note, msg.velocity, msg.channel); 115 | pubMQTT('out/' + msg.channel + '/noteon/' + msg.note, msg.velocity.toString()); 116 | }); 117 | 118 | midiIn.on('poly aftertouch', function (msg) { 119 | log.debug('midi < poly aftertouch', msg.note, msg.pressure, msg.channel); 120 | pubMQTT('out/' + msg.channel + '/poly_aftertouch/' + msg.note, msg.pressure.toString()); 121 | }); 122 | 123 | midiIn.on('cc', function (msg) { 124 | log.debug('midi < cc', msg.controller, msg.value, msg.channel); 125 | pubMQTT('out/' + msg.channel + '/cc/' + msg.controller, msg.value.toString()); 126 | }); 127 | 128 | midiIn.on('program', function (msg) { 129 | log.debug('midi < program', msg.number, msg.channel); 130 | }); 131 | 132 | midiIn.on('channel aftertouch', function (msg) { 133 | log.debug('midi < channel aftertouch', msg.pressure, msg.channel); 134 | }); 135 | 136 | midiIn.on('pitch', function (msg) { 137 | log.debug('midi < pitch', msg.value, msg.channel); 138 | }); 139 | 140 | midiIn.on('position', function (msg) { 141 | log.debug('midi < position', msg.value); 142 | }); 143 | 144 | midiIn.on('select', function (msg) { 145 | log.debug('midi < select', msg.song); 146 | }); 147 | 148 | midiIn.on('clock', function () { 149 | //log.debug('midi < clock'); 150 | }); 151 | 152 | midiIn.on('start', function () { 153 | log.debug('midi < start'); 154 | }); 155 | 156 | midiIn.on('continue', function () { 157 | log.debug('midi < continue'); 158 | }); 159 | 160 | midiIn.on('stop', function () { 161 | log.debug('midi < stop'); 162 | }); 163 | 164 | midiIn.on('reset', function () { 165 | log.debug('midi < reset'); 166 | }); 167 | 168 | 169 | // 170 | // Catch ctrl-c to exit program 171 | // 172 | process.on('SIGINT', function() { 173 | console.log("\nGracefully shutting down from SIGINT (Ctrl+C)"); 174 | midiIn.close(); 175 | midiOut.close(); 176 | mqtt.publish(config.topic + '/connected', '0'); 177 | mqtt.end(function() { 178 | console.log("Exiting..."); 179 | process.exit(); 180 | }); 181 | 182 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "midi2mqtt", 3 | "description": "Bridge a MIDI device to MQTT", 4 | "author": "Andy Swing (https://github.com/TheOriginalAndrobot)", 5 | "license": "MIT", 6 | "version": "1.1.0", 7 | "preferGlobal": true, 8 | "bin": "./index.js", 9 | "dependencies": { 10 | "easymidi": "^1.0.1", 11 | "midi": "^0.9.5", 12 | "mqtt": "^2.18.8", 13 | "yalm": "^4.0.2", 14 | "yargs": "^6.0.3" 15 | }, 16 | "keywords": [ 17 | "mqtt", 18 | "midi", 19 | "bridge", 20 | "smarthome", 21 | "iot" 22 | ], 23 | "repository": "https://github.com/TheOriginalAndrobot/midi2mqtt", 24 | "scripts": { 25 | "test": "echo \"Error: no test specified\" && exit 1" 26 | } 27 | } 28 | --------------------------------------------------------------------------------