This commit is contained in:
gauthiier 2016-10-24 06:31:50 +02:00
commit 0d914c2ddd
7 changed files with 485 additions and 0 deletions

44
.gitignore vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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"
}
}

View 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');
}