haha!
This commit is contained in:
commit
0d914c2ddd
44
.gitignore
vendored
Normal file
44
.gitignore
vendored
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules
|
||||||
|
jspm_packages
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
9
README
Normal file
9
README
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
akai lpd8 midi forwarder/router
|
||||||
|
--
|
||||||
|
Usage: lpd8 [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
-h, --help output usage information
|
||||||
|
-V, --version output the version number
|
||||||
|
-c, --conf [value] LPD8 config file
|
||||||
135
bin/lpd8
Executable file
135
bin/lpd8
Executable file
@ -0,0 +1,135 @@
|
|||||||
|
#! /usr/local/bin/node
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// straight forward
|
||||||
|
|
||||||
|
var args = require('commander');
|
||||||
|
|
||||||
|
args
|
||||||
|
.version('0.1')
|
||||||
|
.option('-c, --conf [value]', 'LPD8 config file')
|
||||||
|
.parse(process.argv);
|
||||||
|
|
||||||
|
|
||||||
|
var lpd8 = require('../lib/lpd8.js')
|
||||||
|
var lpd8sb = require('../lib/lpd8-spacebrew.js')
|
||||||
|
|
||||||
|
var midi = require('midi');
|
||||||
|
var input = new midi.input();
|
||||||
|
|
||||||
|
var fs = require('fs');
|
||||||
|
|
||||||
|
/* - - -
|
||||||
|
LIST MIDI PORTS
|
||||||
|
- - - - */
|
||||||
|
|
||||||
|
function list_ports() {
|
||||||
|
var ports = []
|
||||||
|
for (var i = 0; i < input.getPortCount(); i++)
|
||||||
|
ports.push(input.getPortName(i));
|
||||||
|
return ports;
|
||||||
|
}
|
||||||
|
|
||||||
|
var input_ports = list_ports();
|
||||||
|
|
||||||
|
if (input_ports.length < 1){
|
||||||
|
console.log('no midi port detected...');
|
||||||
|
process.exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
var menu = require("terminal-menu")({width:35});
|
||||||
|
menu.reset();
|
||||||
|
menu.write('AKAI LPD8 midi forward\n');
|
||||||
|
menu.write('----------------------\n');
|
||||||
|
|
||||||
|
for(var i = 0; i < input_ports.length; i++) {
|
||||||
|
menu.add(input_ports[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.add('exit');
|
||||||
|
|
||||||
|
menu.on('select', function(item, index) {
|
||||||
|
if (item == 'exit') {
|
||||||
|
menu.reset();
|
||||||
|
menu.close();
|
||||||
|
process.exit();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
menu.reset();
|
||||||
|
menu.close();
|
||||||
|
init_lpd8();
|
||||||
|
open_midi_port(item, index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
menu.on('close', function () {
|
||||||
|
process.stdin.setRawMode(false);
|
||||||
|
process.stdin.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
process.stdin.pipe(menu.createStream()).pipe(process.stdout);
|
||||||
|
process.stdin.setRawMode(true);
|
||||||
|
|
||||||
|
process.stdin.setEncoding( 'utf8' );
|
||||||
|
|
||||||
|
process.stdin.on( 'data', function( key ){
|
||||||
|
if ( key === '\u001B' ) {
|
||||||
|
quit();
|
||||||
|
process.exit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function quit() {
|
||||||
|
menu.reset();
|
||||||
|
menu.close();
|
||||||
|
input.closePort();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* - - -
|
||||||
|
LPD8
|
||||||
|
- - - - */
|
||||||
|
|
||||||
|
var LPD8 = null;
|
||||||
|
var LPD8SB = null;
|
||||||
|
|
||||||
|
function init_lpd8() {
|
||||||
|
|
||||||
|
LPD8 = new lpd8.LPD8(args.conf);
|
||||||
|
LPD8SB = new lpd8sb.LPD8_SB(LPD8);
|
||||||
|
|
||||||
|
LPD8.set_pad_cb(function (...params) {
|
||||||
|
LPD8SB.signal(params);
|
||||||
|
});
|
||||||
|
|
||||||
|
LPD8.set_kontrol_cb(function (...params) {
|
||||||
|
LPD8SB.signal(params);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* - - -
|
||||||
|
MIDI
|
||||||
|
- - - - */
|
||||||
|
|
||||||
|
input.on('message', function(deltaTime, message) {
|
||||||
|
if(LPD8) LPD8.midi_signal(message, deltaTime);
|
||||||
|
});
|
||||||
|
|
||||||
|
function open_midi_port(port_name, index) {
|
||||||
|
console.log('opening port: ' + port_name);
|
||||||
|
input.openPort(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
function close_midi_port() {
|
||||||
|
input.closePort()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
60
lib/lpd8-spacebrew.js
Normal file
60
lib/lpd8-spacebrew.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var lpd8 = require('./lpd8.js');
|
||||||
|
var sb = require('spacebrew');
|
||||||
|
|
||||||
|
// this should config file
|
||||||
|
const server = 'localhost';
|
||||||
|
const name = 'lpd8';
|
||||||
|
const desc = 'lpd8 midi controller';
|
||||||
|
|
||||||
|
class LPD8_SB {
|
||||||
|
|
||||||
|
constructor(lpd8) {
|
||||||
|
|
||||||
|
this.lpd8 = lpd8;
|
||||||
|
this.client = new sb.Client(server, name, desc);
|
||||||
|
|
||||||
|
var i;
|
||||||
|
for(i in lpd8.PADS) {
|
||||||
|
let pad = lpd8.PADS[i];
|
||||||
|
let padname = 'PAD' + pad.nbr;
|
||||||
|
this.client.addPublish(padname, 'boolean', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i in lpd8.KONTROLS) {
|
||||||
|
let k = lpd8.KONTROLS[i];
|
||||||
|
let kname = 'K' + k.nbr;
|
||||||
|
this.client.addPublish(kname, 'range', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.client.connect();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// rest params
|
||||||
|
signal(...params) {
|
||||||
|
|
||||||
|
// weird.. params = [[]]
|
||||||
|
params = params[0];
|
||||||
|
|
||||||
|
switch(params[0])
|
||||||
|
{
|
||||||
|
case 'pad':
|
||||||
|
// 'pad', this.nbr, this.note, this.state, this.vel, this.dt
|
||||||
|
let padname = 'PAD' + params[1];
|
||||||
|
this.client.send(padname, 'boolean', params[3]);
|
||||||
|
break;
|
||||||
|
case 'k':
|
||||||
|
// 'k', this.nbr, this.cc, this.kv, this.dt
|
||||||
|
let kname = 'K' + params[1];
|
||||||
|
this.client.send(kname, 'range', params[3]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.LPD8_SB = LPD8_SB;
|
||||||
201
lib/lpd8.js
Normal file
201
lib/lpd8.js
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var fs = require('fs');
|
||||||
|
|
||||||
|
// midi
|
||||||
|
const NOTEOFF = 0x80;
|
||||||
|
const NOTEON = 0x90;
|
||||||
|
const KCHANGE = 0xB0;
|
||||||
|
|
||||||
|
// akai's third ocatave config
|
||||||
|
var lpd8_midi_notes = { 0x0: "C-2", 0x1: "C#-2", 0x2: "D-2", 0x3: "D#-2", 0x4: "E-2", 0x5: "F-2",
|
||||||
|
0x6: "F#-2", 0x7: "G-2", 0x8: "G#-2", 0x9: "A-2", 0xA: "A#-2", 0xB: "B-2",
|
||||||
|
0xC: "C-1", 0xD: "C#-1", 0xE: "D-1", 0xF: "D#-1", 0x10: "E-1", 0x11: "F-1",
|
||||||
|
0x12: "F#-1", 0x13: "G-1", 0x14: "G#-1", 0x15: "A-1", 0x16: "A#-1", 0x17: "B-1",
|
||||||
|
0x18: "C0", 0x19: "C#0", 0x1A: "D0", 0x1B: "D#0", 0x1C: "E0", 0x1D: "F0",
|
||||||
|
0x1E: "F#0", 0x1F: "G0", 0x20: "G#0", 0x21: "A0", 0x22: "A#0", 0x23: "B0",
|
||||||
|
0x24: "C1", 0x25: "C#1", 0x26: "D1", 0x27: "D#1", 0x28: "E1", 0x29: "F1",
|
||||||
|
0x2A: "F#1", 0x2B: "G1", 0x2C: "G#1", 0x2D: "A1", 0x2E: "A#1", 0x2F: "B1",
|
||||||
|
0x30: "C2", 0x31: "C#2", 0x32: "D2", 0x33: "D#2", 0x34: "E2", 0x35: "F2",
|
||||||
|
0x36: "F#2", 0x37: "G2", 0x38: "G#2", 0x39: "A2", 0x3A: "A#2", 0x3B: "B2",
|
||||||
|
0x3C: "C3", 0x3D: "C#3", 0x3E: "D3", 0x3F: "D#3", 0x40: "E3", 0x41: "F3",
|
||||||
|
0x42: "F#3", 0x43: "G3", 0x44: "G#3", 0x45: "A3", 0x46: "A#3", 0x47: "B3",
|
||||||
|
0x48: "C4", 0x49: "C#4", 0x4A: "D4", 0x4B: "D#4", 0x4C: "E4", 0x4D: "F4",
|
||||||
|
0x4E: "F#4", 0x4F: "G4", 0x50: "G#4", 0x51: "A4", 0x52: "A#4", 0x53: "B4",
|
||||||
|
0x54: "C5", 0x55: "C#5", 0x56: "D5", 0x57: "D#5", 0x58: "E5", 0x59: "F5",
|
||||||
|
0x5A: "F#5", 0x5B: "G5", 0x5C: "G#5", 0x5D: "A5", 0x5E: "A#5", 0x5F: "B5",
|
||||||
|
0x60: "C6", 0x61: "C#6", 0x62: "D6", 0x63: "D#6", 0x64: "E6", 0x65: "F6",
|
||||||
|
0x66: "F#6", 0x67: "G6", 0x68: "G#6", 0x69: "A6", 0x6A: "A#6", 0x6B: "B6",
|
||||||
|
0x6C: "C7", 0x6D: "C#7", 0x6E: "D7", 0x6F: "D#7", 0x70: "E7", 0x71: "F7",
|
||||||
|
0x72: "F#7", 0x73: "G7", 0x74: "G#7", 0x75: "A7", 0x76: "A#7", 0x77: "B7",
|
||||||
|
0x78: "C8", 0x79: "C#8", 0x7A: "D8", 0x7B: "D#8", 0x7C: "E8", 0x7D: "F8",
|
||||||
|
0x7E: "F#8", 0x7F: "G8"
|
||||||
|
};
|
||||||
|
|
||||||
|
// default conf
|
||||||
|
var conf = {
|
||||||
|
'CHANNEL': 0,
|
||||||
|
'PADS': [
|
||||||
|
{'note': 0x24, 'pc': 0, 'cc': 1, 'channel': 0},
|
||||||
|
{'note': 0x25, 'pc': 1, 'cc': 2, 'channel': 0},
|
||||||
|
{'note': 0x26, 'pc': 2, 'cc': 3, 'channel': 0},
|
||||||
|
{'note': 0x27, 'pc': 3, 'cc': 4, 'channel': 0},
|
||||||
|
{'note': 0x28, 'pc': 4, 'cc': 5, 'channel': 0},
|
||||||
|
{'note': 0x29, 'pc': 5, 'cc': 6, 'channel': 0},
|
||||||
|
{'note': 0x2A, 'pc': 6, 'cc': 8, 'channel': 0},
|
||||||
|
{'note': 0x2B, 'pc': 7, 'cc': 9, 'channel': 0}
|
||||||
|
],
|
||||||
|
'KONTROLS': [
|
||||||
|
{'cc': 1, 'low': 0, 'hi': 127},
|
||||||
|
{'cc': 2, 'low': 0, 'hi': 127},
|
||||||
|
{'cc': 3, 'low': 0, 'hi': 127},
|
||||||
|
{'cc': 4, 'low': 0, 'hi': 127},
|
||||||
|
{'cc': 5, 'low': 0, 'hi': 127},
|
||||||
|
{'cc': 6, 'low': 0, 'hi': 127},
|
||||||
|
{'cc': 7, 'low': 0, 'hi': 127},
|
||||||
|
{'cc': 8, 'low': 0, 'hi': 127}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class PAD {
|
||||||
|
|
||||||
|
constructor(pad_nbr, note, pc, cc, channel) {
|
||||||
|
this.nbr = pad_nbr;
|
||||||
|
this.note = note;
|
||||||
|
this.pc = pc;
|
||||||
|
this.cc = cc;
|
||||||
|
this.channel = channel;
|
||||||
|
this.state = false;
|
||||||
|
this.vel = 0;
|
||||||
|
this.dt = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
on(status_byte, velocity, delta_time, signal) {
|
||||||
|
if (this.channel == (status_byte & 0x0f)) {
|
||||||
|
this.state = ((status_byte & 0xf0) == NOTEON);
|
||||||
|
this.dt = delta_time;
|
||||||
|
if (signal)
|
||||||
|
signal('pad', this.nbr, this.note, this.state, this.vel, this.dt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Kontrol {
|
||||||
|
|
||||||
|
constructor(k_nbr, cc, low, hi) {
|
||||||
|
this.nbr = k_nbr;
|
||||||
|
this.cc = cc;
|
||||||
|
this.low = low;
|
||||||
|
this.hi = hi;
|
||||||
|
this.kv = 0;
|
||||||
|
this.dt = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
on(value, delta_time, signal) {
|
||||||
|
this.kv = value;
|
||||||
|
this.dt = delta_time;
|
||||||
|
if(signal)
|
||||||
|
signal('k', this.nbr, this.cc, this.kv, this.dt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LPD8 {
|
||||||
|
|
||||||
|
constructor(config_file_path) {
|
||||||
|
|
||||||
|
this.conf = parse_lpd8_config(config_file_path) || conf;
|
||||||
|
this.CHANNEL = this.conf.CHANNEL;
|
||||||
|
this.PADS = {};
|
||||||
|
this.KONTROLS = {};
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
for(i = 0; i < this.conf.PADS.length; i++) {
|
||||||
|
let pad = this.conf.PADS[i];
|
||||||
|
this.PADS[pad.note] = new PAD(i + 1, pad.note, pad.pc, pad.cc, pad.channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i = 0; i < this.conf.KONTROLS.length; i++) {
|
||||||
|
let k = this.conf.KONTROLS[i];
|
||||||
|
this.KONTROLS[k.cc] = new Kontrol(i + 1, k.cc, k.low, k.hi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set_pad_cb(callback) {
|
||||||
|
this.pad_cb = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_kontrol_cb(callback) {
|
||||||
|
this.k_cb = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
// based on node-midi (not raw midi)
|
||||||
|
midi_signal(message, dt) {
|
||||||
|
|
||||||
|
var cmd = message[0] & 0xf0;
|
||||||
|
var channel = message[0] & 0x0f;
|
||||||
|
|
||||||
|
if(this.CHANNEL !== channel) return;
|
||||||
|
|
||||||
|
if(cmd == NOTEOFF || cmd == NOTEON) {
|
||||||
|
this.PADS[message[1]].on(message[0], message[2], dt, this.pad_cb);
|
||||||
|
} else if(cmd == KCHANGE) {
|
||||||
|
this.KONTROLS[message[1]].on(message[2], dt, this.k_cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function parse_lpd8_config(config_file_path) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.accessSync(config_file_path, fs.R_OK)
|
||||||
|
} catch (e) {
|
||||||
|
console.log('error reading lpd8 config file\n' + e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = fs.readFileSync(config_file_path, 'utf8');
|
||||||
|
var lpd8_conf = {'PADS': [], 'KONTROLS' : []}
|
||||||
|
|
||||||
|
var tok = data.split(' ');
|
||||||
|
var channel = parseInt(tok[8], 10);
|
||||||
|
|
||||||
|
lpd8_conf.CHANNEL = channel;
|
||||||
|
|
||||||
|
var i;
|
||||||
|
for (i = 9; i < (4 * 8) + 9; i += 4) {
|
||||||
|
lpd8_conf.PADS.push({'note': parseInt(tok[i], 10),
|
||||||
|
'pc': parseInt(tok[i + 1], 10),
|
||||||
|
'cc': parseInt(tok[i + 2], 10),
|
||||||
|
'channel': channel});
|
||||||
|
}
|
||||||
|
|
||||||
|
for(; i < (3 * 8) + 41; i += 3) {
|
||||||
|
lpd8_conf.KONTROLS.push({'cc': parseInt(tok[i], 10),
|
||||||
|
'low': parseInt(tok[i + 1], 10),
|
||||||
|
'hi': parseInt(tok[i + 2], 10),
|
||||||
|
'channel': channel});
|
||||||
|
}
|
||||||
|
|
||||||
|
return lpd8_conf;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.PAD = PAD;
|
||||||
|
module.exports.Kontrol = Kontrol;
|
||||||
|
module.exports.LPD8 = LPD8;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
21
package.json
Normal file
21
package.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "lpd8",
|
||||||
|
"version": "0.1",
|
||||||
|
"description": "akai lpd8 midi forwarder/router",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Errorrrr: no no no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"lpd8",
|
||||||
|
"midi"
|
||||||
|
],
|
||||||
|
"author": "gauthiier",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"commander": "^2.9.0",
|
||||||
|
"midi": "^0.9.5",
|
||||||
|
"spacebrew": "0.0.2",
|
||||||
|
"terminal-menu": "^2.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
15
util/third_octave_midi_map.js
Normal file
15
util/third_octave_midi_map.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// for akai lpd8 config [C-2..G8] -- third octave (re: C0 on third octave)
|
||||||
|
// see /lib/lpd8.js
|
||||||
|
var oct = ['-2', '-1', '0', '1', '2', '3', '4', '5', '6', '7', '8'];
|
||||||
|
var notes = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
|
||||||
|
var i = 0, j, k;
|
||||||
|
var midi_map = {};
|
||||||
|
|
||||||
|
for(j = 0; j < oct.length; j++) {
|
||||||
|
for(k = 0; k < notes.length; k++) {
|
||||||
|
process.stdout.write('0x' + i.toString(16).toUpperCase() + ': "' + notes[k] + oct[j] + '", ');
|
||||||
|
if (k == 5) process.stdout.write('\n');
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
process.stdout.write('\n');
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user