lpd8/lib/lpd8.js
2016-10-24 06:31:50 +02:00

202 lines
6.7 KiB
JavaScript

'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;