initial commit
This commit is contained in:
commit
3705d8cca7
14 changed files with 1202 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
node_modules/
|
||||
yarn.lock
|
14
.vscode/launch.json
vendored
Normal file
14
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "pwa-node",
|
||||
"request": "launch",
|
||||
"name": "api",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"program": "${workspaceFolder}/api.js"
|
||||
}
|
||||
]
|
||||
}
|
7
README.md
Normal file
7
README.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# pbc
|
||||
|
||||
pedal board control
|
||||
|
||||
## description
|
||||
|
||||
control your MODEP pedalboard(s) via http requests
|
19
config.json
Normal file
19
config.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"server": {
|
||||
"listen": "0.0.0.0",
|
||||
"port": 3000,
|
||||
"timestamp": "DD.MM.YYYY HH:mm:ss:SS"
|
||||
},
|
||||
"log": {
|
||||
"level": "debug"
|
||||
},
|
||||
"osc": {
|
||||
"host": "192.168.1.24",
|
||||
"port": "12101",
|
||||
"address": "/midi"
|
||||
},
|
||||
"modep": {
|
||||
"host": "192.168.1.24",
|
||||
"port": 80
|
||||
}
|
||||
}
|
354
dev/FUZZ.ttl
Normal file
354
dev/FUZZ.ttl
Normal file
|
@ -0,0 +1,354 @@
|
|||
@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
|
||||
@prefix doap: <http://usefulinc.com/ns/doap#> .
|
||||
@prefix ingen: <http://drobilla.net/ns/ingen#> .
|
||||
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
|
||||
@prefix midi: <http://lv2plug.in/ns/ext/midi#> .
|
||||
@prefix mod: <http://moddevices.com/ns/mod#> .
|
||||
@prefix pedal: <http://moddevices.com/ns/modpedal#> .
|
||||
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
||||
|
||||
_:b1
|
||||
ingen:tail <capture_1> ;
|
||||
ingen:head <StompBox_fuzz/INPUT_L> .
|
||||
|
||||
_:b2
|
||||
ingen:tail <BigMuffPi/Out1> ;
|
||||
ingen:head <Overdrive/right_in> .
|
||||
|
||||
_:b3
|
||||
ingen:tail <capture_2> ;
|
||||
ingen:head <BigMuffPi/In> .
|
||||
|
||||
_:b4
|
||||
ingen:tail <Overdrive/left_out> ;
|
||||
ingen:head <playback_1> .
|
||||
|
||||
_:b5
|
||||
ingen:tail <capture_1> ;
|
||||
ingen:head <BigMuffPi/In> .
|
||||
|
||||
_:b6
|
||||
ingen:tail <StompBox_fuzz/OUTPUT_L> ;
|
||||
ingen:head <playback_1> .
|
||||
|
||||
_:b7
|
||||
ingen:tail <capture_2> ;
|
||||
ingen:head <StompBox_fuzz/INPUT_R> .
|
||||
|
||||
_:b8
|
||||
ingen:tail <StompBox_fuzz/OUTPUT_R> ;
|
||||
ingen:head <playback_2> .
|
||||
|
||||
_:b9
|
||||
ingen:tail <BigMuffPi/Out1> ;
|
||||
ingen:head <Overdrive/left_in> .
|
||||
|
||||
_:b10
|
||||
ingen:tail <Overdrive/right_out> ;
|
||||
ingen:head <playback_2> .
|
||||
|
||||
<BigMuffPi>
|
||||
ingen:canvasX 561.0 ;
|
||||
ingen:canvasY 210.0 ;
|
||||
ingen:enabled false ;
|
||||
ingen:polyphonic false ;
|
||||
lv2:microVersion 0 ;
|
||||
lv2:minorVersion 0 ;
|
||||
mod:builderVersion 0 ;
|
||||
mod:releaseNumber 0 ;
|
||||
lv2:port <BigMuffPi/In> ,
|
||||
<BigMuffPi/Out1> ,
|
||||
<BigMuffPi/Tone> ,
|
||||
<BigMuffPi/Level> ,
|
||||
<BigMuffPi/Sustain> ,
|
||||
<BigMuffPi/:bypass> ;
|
||||
lv2:prototype <http://moddevices.com/plugins/mod-devel/BigMuffPi> ;
|
||||
pedal:preset <> ;
|
||||
a ingen:Block .
|
||||
|
||||
<BigMuffPi/In>
|
||||
a lv2:AudioPort ,
|
||||
lv2:InputPort .
|
||||
|
||||
<BigMuffPi/In>
|
||||
a lv2:AudioPort ,
|
||||
lv2:OutputPort .
|
||||
|
||||
<BigMuffPi/Tone>
|
||||
ingen:value 0.250000 ;
|
||||
midi:binding [
|
||||
midi:channel 0 ;
|
||||
midi:controllerNumber 1 ;
|
||||
lv2:minimum 0.000000 ;
|
||||
lv2:maximum 1.000000 ;
|
||||
a midi:Controller ;
|
||||
] ;
|
||||
a lv2:ControlPort ,
|
||||
lv2:InputPort .
|
||||
|
||||
<BigMuffPi/Level>
|
||||
ingen:value 0.500000 ;
|
||||
a lv2:ControlPort ,
|
||||
lv2:InputPort .
|
||||
|
||||
<BigMuffPi/Sustain>
|
||||
ingen:value 0.500000 ;
|
||||
a lv2:ControlPort ,
|
||||
lv2:InputPort .
|
||||
|
||||
<BigMuffPi/:bypass>
|
||||
ingen:value 1 ;
|
||||
midi:binding [
|
||||
midi:channel 0 ;
|
||||
midi:controllerNumber 0 ;
|
||||
a midi:Controller ;
|
||||
] ;
|
||||
a lv2:ControlPort ,
|
||||
lv2:InputPort .
|
||||
|
||||
<Overdrive>
|
||||
ingen:canvasX 1061.0 ;
|
||||
ingen:canvasY 180.0 ;
|
||||
ingen:enabled true ;
|
||||
ingen:polyphonic false ;
|
||||
lv2:microVersion 4 ;
|
||||
lv2:minorVersion 0 ;
|
||||
mod:builderVersion 0 ;
|
||||
mod:releaseNumber 0 ;
|
||||
lv2:port <Overdrive/left_in> ,
|
||||
<Overdrive/right_in> ,
|
||||
<Overdrive/left_out> ,
|
||||
<Overdrive/right_out> ,
|
||||
<Overdrive/drive> ,
|
||||
<Overdrive/muffle> ,
|
||||
<Overdrive/output> ,
|
||||
<Overdrive/:bypass> ;
|
||||
lv2:prototype <http://moddevices.com/plugins/mda/Overdrive> ;
|
||||
pedal:preset <> ;
|
||||
a ingen:Block .
|
||||
|
||||
<Overdrive/left_in>
|
||||
a lv2:AudioPort ,
|
||||
lv2:InputPort .
|
||||
|
||||
<Overdrive/right_in>
|
||||
a lv2:AudioPort ,
|
||||
lv2:InputPort .
|
||||
|
||||
<Overdrive/left_in>
|
||||
a lv2:AudioPort ,
|
||||
lv2:OutputPort .
|
||||
|
||||
<Overdrive/right_in>
|
||||
a lv2:AudioPort ,
|
||||
lv2:OutputPort .
|
||||
|
||||
<Overdrive/drive>
|
||||
ingen:value 50.000000 ;
|
||||
a lv2:ControlPort ,
|
||||
lv2:InputPort .
|
||||
|
||||
<Overdrive/muffle>
|
||||
ingen:value 25.000000 ;
|
||||
a lv2:ControlPort ,
|
||||
lv2:InputPort .
|
||||
|
||||
<Overdrive/output>
|
||||
ingen:value 0.000000 ;
|
||||
a lv2:ControlPort ,
|
||||
lv2:InputPort .
|
||||
|
||||
<Overdrive/:bypass>
|
||||
ingen:value 0 ;
|
||||
a lv2:ControlPort ,
|
||||
lv2:InputPort .
|
||||
|
||||
<StompBox_fuzz>
|
||||
ingen:canvasX 547.0 ;
|
||||
ingen:canvasY 740.0 ;
|
||||
ingen:enabled true ;
|
||||
ingen:polyphonic false ;
|
||||
lv2:microVersion 0 ;
|
||||
lv2:minorVersion 0 ;
|
||||
mod:builderVersion 0 ;
|
||||
mod:releaseNumber 0 ;
|
||||
lv2:port <StompBox_fuzz/INPUT_L> ,
|
||||
<StompBox_fuzz/INPUT_R> ,
|
||||
<StompBox_fuzz/OUTPUT_L> ,
|
||||
<StompBox_fuzz/OUTPUT_R> ,
|
||||
<StompBox_fuzz/BYPASS> ,
|
||||
<StompBox_fuzz/LEVEL> ,
|
||||
<StompBox_fuzz/HI> ,
|
||||
<StompBox_fuzz/MID> ,
|
||||
<StompBox_fuzz/LO> ,
|
||||
<StompBox_fuzz/GAIN> ,
|
||||
<StompBox_fuzz/:bypass> ;
|
||||
lv2:prototype <http://rakarrack.sourceforge.net/effects.html#StompBox_fuzz> ;
|
||||
pedal:preset <> ;
|
||||
a ingen:Block .
|
||||
|
||||
<StompBox_fuzz/INPUT_L>
|
||||
a lv2:AudioPort ,
|
||||
lv2:InputPort .
|
||||
|
||||
<StompBox_fuzz/INPUT_R>
|
||||
a lv2:AudioPort ,
|
||||
lv2:InputPort .
|
||||
|
||||
<StompBox_fuzz/INPUT_L>
|
||||
a lv2:AudioPort ,
|
||||
lv2:OutputPort .
|
||||
|
||||
<StompBox_fuzz/INPUT_R>
|
||||
a lv2:AudioPort ,
|
||||
lv2:OutputPort .
|
||||
|
||||
<StompBox_fuzz/BYPASS>
|
||||
ingen:value 0.000000 ;
|
||||
a lv2:ControlPort ,
|
||||
lv2:InputPort .
|
||||
|
||||
<StompBox_fuzz/LEVEL>
|
||||
ingen:value 64.000000 ;
|
||||
a lv2:ControlPort ,
|
||||
lv2:InputPort .
|
||||
|
||||
<StompBox_fuzz/HI>
|
||||
ingen:value 20.000000 ;
|
||||
a lv2:ControlPort ,
|
||||
lv2:InputPort .
|
||||
|
||||
<StompBox_fuzz/MID>
|
||||
ingen:value 29.000000 ;
|
||||
a lv2:ControlPort ,
|
||||
lv2:InputPort .
|
||||
|
||||
<StompBox_fuzz/LO>
|
||||
ingen:value 20.000000 ;
|
||||
a lv2:ControlPort ,
|
||||
lv2:InputPort .
|
||||
|
||||
<StompBox_fuzz/GAIN>
|
||||
ingen:value 64.000000 ;
|
||||
a lv2:ControlPort ,
|
||||
lv2:InputPort .
|
||||
|
||||
<StompBox_fuzz/:bypass>
|
||||
ingen:value 0 ;
|
||||
a lv2:ControlPort ,
|
||||
lv2:InputPort .
|
||||
|
||||
<:bpb>
|
||||
ingen:value 4.000000 ;
|
||||
lv2:index 0 ;
|
||||
a lv2:ControlPort ,
|
||||
lv2:InputPort .
|
||||
|
||||
<:bpm>
|
||||
ingen:value 120.000000 ;
|
||||
lv2:index 1 ;
|
||||
a lv2:ControlPort ,
|
||||
lv2:InputPort .
|
||||
|
||||
<:rolling>
|
||||
ingen:value 0 ;
|
||||
lv2:index 2 ;
|
||||
a lv2:ControlPort ,
|
||||
lv2:InputPort .
|
||||
|
||||
<control_in>
|
||||
atom:bufferType atom:Sequence ;
|
||||
lv2:index 3 ;
|
||||
lv2:name "Control In" ;
|
||||
lv2:portProperty lv2:connectionOptional ;
|
||||
lv2:symbol "control_in" ;
|
||||
<http://lv2plug.in/ns/ext/resize-port#minimumSize> 4096 ;
|
||||
a atom:AtomPort ,
|
||||
lv2:InputPort .
|
||||
|
||||
<control_out>
|
||||
atom:bufferType atom:Sequence ;
|
||||
lv2:index 4 ;
|
||||
lv2:name "Control Out" ;
|
||||
lv2:portProperty lv2:connectionOptional ;
|
||||
lv2:symbol "control_out" ;
|
||||
<http://lv2plug.in/ns/ext/resize-port#minimumSize> 4096 ;
|
||||
a atom:AtomPort ,
|
||||
lv2:OutputPort .
|
||||
|
||||
<capture_1>
|
||||
lv2:index 5 ;
|
||||
lv2:name "Capture 1" ;
|
||||
lv2:portProperty lv2:connectionOptional ;
|
||||
lv2:symbol "capture_1" ;
|
||||
a lv2:AudioPort ,
|
||||
lv2:InputPort .
|
||||
|
||||
<capture_2>
|
||||
lv2:index 6 ;
|
||||
lv2:name "Capture 2" ;
|
||||
lv2:portProperty lv2:connectionOptional ;
|
||||
lv2:symbol "capture_2" ;
|
||||
a lv2:AudioPort ,
|
||||
lv2:InputPort .
|
||||
|
||||
<playback_1>
|
||||
lv2:index 7 ;
|
||||
lv2:name "Playback 1" ;
|
||||
lv2:portProperty lv2:connectionOptional ;
|
||||
lv2:symbol "playback_1" ;
|
||||
a lv2:AudioPort ,
|
||||
lv2:OutputPort .
|
||||
|
||||
<playback_2>
|
||||
lv2:index 8 ;
|
||||
lv2:name "Playback 2" ;
|
||||
lv2:portProperty lv2:connectionOptional ;
|
||||
lv2:symbol "playback_2" ;
|
||||
a lv2:AudioPort ,
|
||||
lv2:OutputPort .
|
||||
|
||||
<midi_separated_mode>
|
||||
ingen:value 0 ;
|
||||
lv2:index 9 ;
|
||||
a atom:AtomPort ,
|
||||
lv2:InputPort .
|
||||
|
||||
<>
|
||||
doap:name "FUZZ" ;
|
||||
pedal:unitName "Unknown" ;
|
||||
pedal:unitModel "Unknown" ;
|
||||
pedal:width 3788 ;
|
||||
pedal:height 1546 ;
|
||||
pedal:addressings <addressings.json> ;
|
||||
pedal:screenshot <screenshot.png> ;
|
||||
pedal:thumbnail <thumbnail.png> ;
|
||||
pedal:version 7 ;
|
||||
ingen:polyphony 1 ;
|
||||
ingen:arc _:b1 ,
|
||||
_:b2 ,
|
||||
_:b3 ,
|
||||
_:b4 ,
|
||||
_:b5 ,
|
||||
_:b6 ,
|
||||
_:b7 ,
|
||||
_:b8 ,
|
||||
_:b9 ,
|
||||
_:b10 ;
|
||||
ingen:block <BigMuffPi> ,
|
||||
<Overdrive> ,
|
||||
<StompBox_fuzz> ;
|
||||
lv2:port <:bpb> ,
|
||||
<:bpm> ,
|
||||
<:rolling> ,
|
||||
<midi_separated_mode> ,
|
||||
<control_in> ,
|
||||
<control_out> ,
|
||||
<capture_1> ,
|
||||
<capture_2> ,
|
||||
<playback_1> ,
|
||||
<playback_2> ;
|
||||
lv2:extensionData <http://lv2plug.in/ns/ext/state#interface> ;
|
||||
a lv2:Plugin ,
|
||||
ingen:Graph ,
|
||||
pedal:Pedalboard .
|
64
libs/api.js
Normal file
64
libs/api.js
Normal file
|
@ -0,0 +1,64 @@
|
|||
const logger = require('./logger.js');
|
||||
const util = require('./util.js');
|
||||
const constants = require('./constants.js');
|
||||
const modep = require('./modep.js');
|
||||
|
||||
const endpoints = new Map();
|
||||
|
||||
function setEndpoints(url, data) {
|
||||
var startTime = new Date();
|
||||
var index = 0;
|
||||
var elements = [];
|
||||
for (var index = 0; index < data.length; index++) {
|
||||
var element = data[index];
|
||||
element.id = index;
|
||||
setEndpoint(url + '/' + element.id, [constants.HTTP_GET, constants.HTTP_POST], element);
|
||||
elements.push(element);
|
||||
}
|
||||
elements = elements.sort(function (a, b) {
|
||||
return a.id - b.id;
|
||||
});
|
||||
setEndpoint(url, constants.HTTP_GET, elements);
|
||||
logger.debug('setting up ' + elements.length + ' endpoint(s) for path \'' + url + '\' took ' + util.timeDiff(startTime) + 'ms');
|
||||
}
|
||||
|
||||
function setEndpoint(url, types, data) {
|
||||
logger.debug('setting up \'' + types + '\' endpoint \'' + url + '\'...')
|
||||
endpoints.set(url, { types: types, data: data });
|
||||
}
|
||||
|
||||
function getEndpoints() {
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
function setupEndpoints() {
|
||||
return new Promise(function (resolve, reject) {
|
||||
modep.getBanks()
|
||||
.then(function (data) {
|
||||
setEndpoints(constants.API_BANKS, data);
|
||||
})
|
||||
.then(modep.getPedalboards)
|
||||
.then(function (data) {
|
||||
setEndpoints(constants.API_PEDALBOARDS, data);
|
||||
})
|
||||
.then(modep.getDefaultPedalboard)
|
||||
.then(function (data) {
|
||||
setEndpoint(constants.API_PEDALBOARDS_DEFAULT, constants.HTTP_GET, data);
|
||||
})
|
||||
.then(modep.getCurrentPedalboard)
|
||||
.then(function (data) {
|
||||
setEndpoint(constants.API_PEDALBOARDS_CURRENT, constants.HTTP_GET, data);
|
||||
})
|
||||
.then(modep.getCurrentPedals)
|
||||
.then(function (data) {
|
||||
setEndpoints(constants.API_PEDALS, data);
|
||||
})
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setupEndpoints,
|
||||
getEndpoints
|
||||
}
|
27
libs/commands.js
Normal file
27
libs/commands.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
const logger = require('../libs/logger.js');
|
||||
const { spawn } = require('child_process')
|
||||
|
||||
function execute(endpoint) {
|
||||
if (!endpoint || !endpoint.command) {
|
||||
logger.warn('no command defined');
|
||||
return;
|
||||
}
|
||||
logger.debug('executing command \'' + endpoint.command + '\' with args \'' + endpoint.args + '\'...');
|
||||
var cmd = spawn(endpoint.command, endpoint.args);
|
||||
cmd.stdout.on('data', function(data) {
|
||||
logger.debug(data);
|
||||
});
|
||||
cmd.stderr.on('data', function(data) {
|
||||
logger.error(data);
|
||||
});
|
||||
cmd.on('close', function(code) {
|
||||
logger.debug('command \'' + endpoint.command + '\' with args \'' + endpoint.args + '\' finished with exit code ' + code);
|
||||
});
|
||||
cmd.on('error', function(err) {
|
||||
logger.error('command \'' + endpoint.command + '\' with args \'' + endpoint.args + '\' encountered an error >>> ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
execute
|
||||
}
|
10
libs/constants.js
Normal file
10
libs/constants.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
exports.HTTP_GET = 'GET';
|
||||
exports.HTTP_POST = 'POST';
|
||||
|
||||
exports.API_BANKS = '/banks';
|
||||
exports.API_PEDALBOARDS = '/pedalboards';
|
||||
exports.API_PEDALBOARDS_DEFAULT = '/pedalboards/default';
|
||||
exports.API_PEDALBOARDS_CURRENT = '/pedalboards/current';
|
||||
exports.API_PEDALS = '/pedals';
|
||||
|
||||
exports.PEDALBOARD_DEFAULT = '/var/modep/pedalboards/default.pedalboard';
|
110
libs/logger.js
Normal file
110
libs/logger.js
Normal file
|
@ -0,0 +1,110 @@
|
|||
const config = require("../config.json");
|
||||
const moment = require("moment");
|
||||
|
||||
// constants
|
||||
const LOG_PREFIX_DEBUG = "debug";
|
||||
const LOG_PREFIX_INFO = "info";
|
||||
const LOG_PREFIX_WARNING = "warning";
|
||||
const LOG_PREFIX_ERROR = "error";
|
||||
const LOGLEVEL_DEBUG = 0;
|
||||
const LOGLEVEL_INFO = 1;
|
||||
const LOGLEVEL_WARNING = 2;
|
||||
const LOGLEVEL_ERROR = 3;
|
||||
|
||||
// set loglevel on "require"
|
||||
const loglevel = function() {
|
||||
switch (config.log.level) {
|
||||
case LOG_PREFIX_DEBUG:
|
||||
case LOGLEVEL_DEBUG:
|
||||
return LOGLEVEL_DEBUG;
|
||||
case LOG_PREFIX_INFO:
|
||||
case LOGLEVEL_INFO:
|
||||
return LOGLEVEL_INFO;
|
||||
case LOG_PREFIX_WARNING:
|
||||
case LOGLEVEL_WARNING:
|
||||
return LOGLEVEL_WARNING;
|
||||
case LOG_PREFIX_ERROR:
|
||||
case LOGLEVEL_ERROR:
|
||||
return LOGLEVEL_ERROR;
|
||||
default:
|
||||
return LOGLEVEL_INFO;
|
||||
}
|
||||
}();
|
||||
|
||||
// log a http request
|
||||
function request(request) {
|
||||
let message = "[" + request.method + "] url: \"" + request.url + "\"";
|
||||
let counter = 1;
|
||||
for (let param in request.body) {
|
||||
message += ", parameter " + counter + ": \"" + param + "=" + request.body[param] + "\"";
|
||||
counter++;
|
||||
}
|
||||
debug(message.trim());
|
||||
}
|
||||
|
||||
// prefix log with "info"
|
||||
function info(message) {
|
||||
if (loglevel > LOGLEVEL_INFO) {
|
||||
return;
|
||||
}
|
||||
trace(message);
|
||||
}
|
||||
|
||||
// prefix log with "info"
|
||||
function warn(message) {
|
||||
if (loglevel > LOGLEVEL_WARNING) {
|
||||
return;
|
||||
}
|
||||
trace(message, "warning");
|
||||
}
|
||||
|
||||
// prefix log with "debug"
|
||||
function debug(message) {
|
||||
if (loglevel > LOGLEVEL_DEBUG) {
|
||||
return;
|
||||
}
|
||||
trace(message, "debug");
|
||||
}
|
||||
|
||||
// prefix log with "error"
|
||||
function error(message) {
|
||||
if (loglevel > LOGLEVEL_ERROR) {
|
||||
return;
|
||||
}
|
||||
trace(message, "error");
|
||||
}
|
||||
|
||||
// default logging function
|
||||
function trace(message, prefix) {
|
||||
if (message === undefined || message === null || message.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (prefix === undefined || prefix === null || prefix.length === 0) {
|
||||
prefix = "info";
|
||||
}
|
||||
let print;
|
||||
switch (prefix) {
|
||||
case "error":
|
||||
print = console.error;
|
||||
break;
|
||||
case "debug":
|
||||
print = console.debug;
|
||||
break;
|
||||
case "warning":
|
||||
print = console.warn;
|
||||
break;
|
||||
default:
|
||||
print = console.log;
|
||||
}
|
||||
message = moment().format(config.server.timestamp) + " | " + prefix + " > " + message;
|
||||
print(message);
|
||||
}
|
||||
|
||||
// exports
|
||||
module.exports = {
|
||||
info,
|
||||
warn,
|
||||
debug,
|
||||
error,
|
||||
request
|
||||
};
|
329
libs/modep.js
Normal file
329
libs/modep.js
Normal file
|
@ -0,0 +1,329 @@
|
|||
const config = require('../config.json');
|
||||
const util = require('./util.js');
|
||||
const constants = require('./constants.js');
|
||||
const logger = require('./logger.js');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { spawn } = require('child_process')
|
||||
const ttl2jsonld = require('@frogcat/ttl2jsonld').parse;
|
||||
|
||||
var banks = undefined;
|
||||
var pedalboards = undefined;
|
||||
var defaultPedalboard = undefined;
|
||||
var currentPedalboard = undefined;
|
||||
var currentPedals = undefined;
|
||||
|
||||
function reset() {
|
||||
return new Promise(function (resolve, reject) {
|
||||
util.httpGET(config.modep.host, config.modep.port, '/reset')
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
function getBanks() {
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (banks) {
|
||||
return resolve(banks);
|
||||
}
|
||||
// FAKE DATA
|
||||
var fake = [{ "title": "The Button", "pedalboards": [{ "valid": true, "broken": false, "uri": "file:///var/modep/pedalboards/default.pedalboard/default.ttl", "bundle": "/var/modep/pedalboards/default.pedalboard", "title": "Default", "version": 0 }, { "valid": true, "broken": false, "uri": "file:///var/modep/pedalboards/FUZZ.pedalboard/FUZZ.ttl", "bundle": "/var/modep/pedalboards/FUZZ.pedalboard", "title": "FUZZ", "version": 1 }] }];
|
||||
for (var index = 0; index < fake.length; index++) {
|
||||
fake.id = index;
|
||||
}
|
||||
banks = util.sortById(fake);
|
||||
return resolve(fake);
|
||||
|
||||
util.httpGET(config.modep.host, config.modep.port, '/banks')
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
function getPedalboards() {
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (pedalboards) {
|
||||
return resolve(pedalboards);
|
||||
}
|
||||
// FAKE DATA
|
||||
var fake = [{ "valid": true, "broken": false, "uri": "file:///var/modep/pedalboards/FUZZ.pedalboard/FUZZ.ttl", "bundle": "/var/modep/pedalboards/FUZZ.pedalboard", "title": "FUZZ", "version": 1 }, { "valid": true, "broken": false, "uri": "file:///var/modep/pedalboards/default.pedalboard/default.ttl", "bundle": "/var/modep/pedalboards/default.pedalboard", "title": "Default", "version": 0 }];
|
||||
var id = 1;
|
||||
for (var index = 0; index < fake.length; index++) {
|
||||
var pedalboard = fake[index];
|
||||
if (pedalboard.bundle == constants.PEDALBOARD_DEFAULT) {
|
||||
pedalboard.id = 0;
|
||||
defaultPedalboard = pedalboard;
|
||||
continue;
|
||||
}
|
||||
pedalboard.id = id;
|
||||
id++;
|
||||
}
|
||||
pedalboards = util.sortById(fake);
|
||||
return resolve(fake);
|
||||
|
||||
util.httpGET(config.modep.host, config.modep.port, '/pedalboard/list')
|
||||
.then(function (pedalboards) {
|
||||
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
function getDefaultPedalboard() {
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (defaultPedalboard) {
|
||||
return resolve(defaultPedalboard);
|
||||
}
|
||||
getPedalboardByBundle(constants.PEDALBOARD_DEFAULT)
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
function getCurrentPedalboard() {
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (currentPedalboard && currentPedalboard.id) {
|
||||
return resolve(currentPedalboard);
|
||||
}
|
||||
// FAKE DATA
|
||||
var fake = '/var/modep/pedalboards/FUZZ.pedalboard';
|
||||
getPedalboardByBundle(fake)
|
||||
.then(function (pedalboard) {
|
||||
currentPedalboard = pedalboard;
|
||||
return resolve(pedalboard);
|
||||
})
|
||||
.catch(reject);
|
||||
|
||||
// PRODUCTION
|
||||
// util.httpGET(config.modep.host, config.modep.port, '/pedalboard/current')
|
||||
// .then(getPedalboardByBundle)
|
||||
// .then(resolve)
|
||||
// .catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
function getPedalboardById(pedalboardId) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
getPedalboards()
|
||||
.then(function (pedalboards) {
|
||||
for (var index = 0; index < pedalboards.length; index++) {
|
||||
if (pedalboards[index].id != pedalboardId) {
|
||||
continue;
|
||||
}
|
||||
return resolve(pedalboards[index]);
|
||||
}
|
||||
return reject('error: could not find pedalboard by id \'' + controlId + '\'');
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
function getPedalboardByBundle(pedalboardBundle) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
getPedalboards()
|
||||
.then(function (pedalboards) {
|
||||
for (var index = 0; index < pedalboards.length; index++) {
|
||||
if (pedalboards[index].bundle != pedalboardBundle) {
|
||||
continue;
|
||||
}
|
||||
return resolve(pedalboards[index]);
|
||||
}
|
||||
return reject('error: could not find pedalboard by bundle \'' + pedalboardBundle + '\'');
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
function getPedalControlById(pedalId, controlId) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
getCurrentPedalById(pedalId)
|
||||
.then(function (pedal) {
|
||||
for (var index = 0; index < pedal.controls.length; index++) {
|
||||
if (pedal.controls[index].id != controlId) {
|
||||
continue;
|
||||
}
|
||||
return resolve(pedal.controls[index]);
|
||||
}
|
||||
return reject('error: could not find control for pedal \'' + pedalId + '\' by id \'' + controlId + '\'');
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
function getCurrentPedalById(id) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
getCurrentPedals()
|
||||
.then(function () {
|
||||
for (var index = 0; index < currentPedals.length; index++) {
|
||||
if (currentPedals[index].id != id) {
|
||||
continue;
|
||||
}
|
||||
return resolve(currentPedals[index]);
|
||||
}
|
||||
return reject('error: could not find current pedal by id \'' + id + '\'');
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
function getCurrentPedals() {
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (currentPedals) {
|
||||
return resolve(currentPedals);
|
||||
}
|
||||
getCurrentPedalboard()
|
||||
.then(parseCurrentPedalboard)
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
function parseCurrentPedalboard(pedalboardBundle) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var startTime = new Date();
|
||||
logger.debug('parsing current pedalboard...');
|
||||
if (!currentPedalboard || !currentPedalboard.uri) {
|
||||
for (var index = 0; index < pedalboards.length; index++) {
|
||||
var pedalboard = pedalboards[index];
|
||||
if (!pedalboard.bundle || pedalboardBundle != pedalboard.bundle) {
|
||||
continue;
|
||||
}
|
||||
currentPedalboard = pedalboard;
|
||||
break;
|
||||
};
|
||||
}
|
||||
if (!currentPedalboard.uri) {
|
||||
reject('error: could not determine current pedalboard config file');
|
||||
}
|
||||
// FAKE DATA
|
||||
var file = path.resolve('./dev/FUZZ.ttl');
|
||||
// var file = path.resolve(currentPedalboard.uri.replace('file://', ''));
|
||||
fs.readFile(file, function (err, data) {
|
||||
if (err) {
|
||||
return reject('error: could not parse current pedalboard file \'' + file + '\' >>> ' + err);
|
||||
}
|
||||
currentPedals = [];
|
||||
var json = ttl2jsonld(data.toString())['@graph'];
|
||||
var id = 0;
|
||||
for (var index = 0; index < json.length; index++) {
|
||||
var tmp = json[index];
|
||||
if (!tmp['lv2:prototype']) {
|
||||
continue;
|
||||
}
|
||||
var name = tmp['@id'];
|
||||
currentPedals.push({ id: id, name: name, controls: [] });
|
||||
id++;
|
||||
}
|
||||
for (var index = 0; index < json.length; index++) {
|
||||
var tmp = json[index];
|
||||
var name = tmp['@id'];
|
||||
var value = tmp['ingen:value'];
|
||||
if (value == undefined) {
|
||||
continue;
|
||||
}
|
||||
var pedal = undefined;
|
||||
for (var pedalIndex = 0; pedalIndex < currentPedals.length; pedalIndex++) {
|
||||
if (!name.startsWith(currentPedals[pedalIndex].name)) {
|
||||
continue;
|
||||
}
|
||||
pedal = currentPedals[pedalIndex];
|
||||
break;
|
||||
}
|
||||
if (!pedal) {
|
||||
continue;
|
||||
}
|
||||
id = pedal.controls.length;
|
||||
name = name.replace(pedal.name + '/', '');
|
||||
if (name == ':bypass') {
|
||||
value = value;
|
||||
} else {
|
||||
value = value['@value'];
|
||||
}
|
||||
var control = { id: id, name: name, value: value };
|
||||
pedal.controls.push(control);
|
||||
id++;
|
||||
var midi = tmp['midi:binding'];
|
||||
if (!midi) {
|
||||
continue;
|
||||
}
|
||||
control.midi = { channel: midi['midi:channel'], controller: midi['midi:controllerNumber'] }
|
||||
}
|
||||
logger.debug('parsing current pedalboard file \'' + file + '\' took ' + util.timeDiff(startTime) + 'ms')
|
||||
resolve(currentPedals);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function sendValueToControl(value, control) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (!control || !control.midi) {
|
||||
return reject('error: control \'' + control.name + '\' with id \'' + control.id + '\' has no midi bindings');
|
||||
}
|
||||
if (value > 127) {
|
||||
value = 127;
|
||||
} else if (value < 0) {
|
||||
value = 0;
|
||||
}
|
||||
value = '00' + util.toHex(value) + '0' + util.toHex(control.midi.controller) + 'b' + util.toHex(control.midi.channel);
|
||||
var cmd = 'oscsend';
|
||||
var args = [config.osc.host, config.osc.port, config.osc.address, 'm', value];
|
||||
logger.debug('executing command \'' + cmd + '\' with args \'' + args + '\'...');
|
||||
var spawned = spawn(cmd, args);
|
||||
spawned.stdout.on('data', function (data) {
|
||||
logger.debug(data);
|
||||
});
|
||||
spawned.stderr.on('data', function (data) {
|
||||
logger.error(data);
|
||||
});
|
||||
spawned.on('close', function (code) {
|
||||
logger.debug('command \'' + cmd + '\' with args \'' + args + '\' finished with exit code ' + code);
|
||||
resolve();
|
||||
});
|
||||
spawned.on('error', function (err) {
|
||||
logger.error('command \'' + cmd + '\' with args \'' + args + '\' encountered an error >>> ' + err);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setPedalboardById(pedalboardId) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (!pedalboardId) {
|
||||
return reject('error: no pedalboard id given');
|
||||
}
|
||||
getPedalboardById(pedalboardId)
|
||||
.then(setPedalboard)
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
function setPedalboard(pedalboard) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (!pedalboard || !pedalboard.bundle) {
|
||||
return reject('error: no bundle set for pedalboard');
|
||||
}
|
||||
if (pedalboard.id == currentPedalboard.id) {
|
||||
return resolve('pedalboard \'' + pedalboard.id + '\' is already active');
|
||||
}
|
||||
reset()
|
||||
.then(function () {
|
||||
util.httpPOST(config.modep.host, config.modep.port, '/pedalboard/load_bundle/?bundlepath=' + pedalboard.bundle)
|
||||
})
|
||||
.then(getCurrentPedalboard)
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getBanks,
|
||||
getPedalboards,
|
||||
getDefaultPedalboard,
|
||||
getCurrentPedalboard,
|
||||
getCurrentPedals,
|
||||
getCurrentPedalById,
|
||||
getPedalControlById,
|
||||
sendValueToControl,
|
||||
setPedalboard,
|
||||
setPedalboardById,
|
||||
}
|
151
libs/server.js
Normal file
151
libs/server.js
Normal file
|
@ -0,0 +1,151 @@
|
|||
const config = require('../config.json');
|
||||
const logger = require('./logger.js');
|
||||
const api = require('./api.js')
|
||||
const constants = require('./constants.js');
|
||||
const modep = require('./modep.js');
|
||||
const http = require('http');
|
||||
|
||||
var server;
|
||||
|
||||
function start() {
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (!server) {
|
||||
server = http.createServer();
|
||||
}
|
||||
server.listen(config.server.port, config.server.listen).on('listening', function () {
|
||||
logger.debug('server listening on ' + config.server.listen + ':' + config.server.port + '...');
|
||||
handleRequests();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function handleRequests() {
|
||||
server.on('request', function (request, response) {
|
||||
logger.request(request);
|
||||
var endpoint = api.getEndpoints().get(request.url);
|
||||
if (!endpoint) {
|
||||
var msg = 'endpoint \'' + request.url + '\' not defined';
|
||||
response.writeHead(501);
|
||||
response.end(msg);
|
||||
logger.debug(msg);
|
||||
return;
|
||||
}
|
||||
if (!endpoint.types.includes(request.method)) {
|
||||
var msg = 'endpoint \'' + request.url + '\' does not support ' + request.method + ' requests';
|
||||
response.writeHead(405);
|
||||
response.end(msg);
|
||||
logger.debug(msg);
|
||||
return;
|
||||
}
|
||||
if (request.method == constants.HTTP_GET) {
|
||||
handleGET(response, endpoint);
|
||||
return;
|
||||
}
|
||||
if (request.method == constants.HTTP_POST) {
|
||||
handlePOST(request, response, endpoint);
|
||||
return;
|
||||
}
|
||||
var msg = 'endpoint \'' + request.url + '\' does not have any handlers for ' + request.method + ' requests';
|
||||
response.writeHead(405);
|
||||
response.end(msg);
|
||||
logger.debug(msg);
|
||||
});
|
||||
}
|
||||
|
||||
function handleGET(response, endpoint) {
|
||||
var data = endpoint.data;
|
||||
if (!data) {
|
||||
response.writeHead(500);
|
||||
response.end('error: could not get data for endpoint')
|
||||
return;
|
||||
}
|
||||
response.writeHead(200);
|
||||
response.end(JSON.stringify(data));
|
||||
}
|
||||
|
||||
function handlePOST(request, response, endpoint) {
|
||||
if (request.url.startsWith(constants.API_PEDALS)) {
|
||||
handlePOSTPedals(request)
|
||||
.then(function (msg) {
|
||||
response.writeHead(200);
|
||||
response.end(msg);
|
||||
})
|
||||
.catch(function (err, code) {
|
||||
if (!code) {
|
||||
code = 500;
|
||||
}
|
||||
response.writeHead(code);
|
||||
response.end(err);
|
||||
logger.error(err);
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (request.url.startsWith(constants.API_PEDALBOARDS)) {
|
||||
handlePOSTPedalboards(request)
|
||||
.then(function (msg) {
|
||||
response.writeHead(200);
|
||||
response.end(msg);
|
||||
})
|
||||
.catch(function (err, code) {
|
||||
if (!code) {
|
||||
code = 500;
|
||||
}
|
||||
response.writeHead(code);
|
||||
response.end(err);
|
||||
logger.error(err);
|
||||
});
|
||||
return;
|
||||
}
|
||||
var msg = 'endpoint \'' + request.url + '\' is not yet implemented for ' + request.method + ' requests';
|
||||
response.writeHead(405);
|
||||
response.end(msg);
|
||||
|
||||
}
|
||||
|
||||
function getPOSTParams(request) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var params = "";
|
||||
request.on("data", function (data) {
|
||||
params += data;
|
||||
});
|
||||
request.on("end", function () {
|
||||
return resolve(new URLSearchParams(params));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function handlePOSTPedals(request) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
getPOSTParams(request)
|
||||
.then(function (params) {
|
||||
var pedalId = request.url.substring(request.url.lastIndexOf('/') + 1);
|
||||
var controlId = params.get('id');
|
||||
if (controlId == undefined) {
|
||||
reject('error: could not handle POST - missing parameter \'id\'', 400)
|
||||
}
|
||||
var value = params.get('value');
|
||||
if (value == undefined) {
|
||||
reject('error: could not handle POST - missing parameter \'value\'', 400)
|
||||
}
|
||||
modep.getPedalControlById(pedalId, controlId)
|
||||
.then(function (control) {
|
||||
modep.sendValueToControl(value, control);
|
||||
})
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
function handlePOSTPedalboards(request) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
modep.setPedalboardById(request.url.substring(request.url.lastIndexOf('/') + 1))
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
start
|
||||
}
|
78
libs/util.js
Normal file
78
libs/util.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
const logger = require('./logger.js');
|
||||
const http = require('http');
|
||||
const { HTTP_GET } = require('./constants.js');
|
||||
|
||||
function timeDiff(startTime) {
|
||||
if (startTime instanceof Date) {
|
||||
return (new Date().getTime() - startTime.getTime());
|
||||
}
|
||||
return new Date().getTime - startTime;
|
||||
}
|
||||
|
||||
function clone(object) {
|
||||
var clone = {};
|
||||
for (key in object) {
|
||||
clone[key] = object[key];
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
|
||||
function httpGET(host, port, path, args) {
|
||||
return httpRequest(host, port, path, HTTP_GET, args);
|
||||
}
|
||||
|
||||
function httpPOST(host, port, path, args) {
|
||||
return httpRequest(host, port, path, HTTP_POST, args);
|
||||
}
|
||||
|
||||
function httpRequest(host, port, path, method, args) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (!path.startsWith("/")) {
|
||||
path = "/" + path;
|
||||
}
|
||||
logger.debug('sending http \'' + method + '\' request to \'' + host + ':' + port + path + '\'...');
|
||||
const request = http.request({
|
||||
hostname: host,
|
||||
port: port,
|
||||
path: path,
|
||||
method: method
|
||||
}, function (response) {
|
||||
if (!response) {
|
||||
return reject('error: no response from host for http \'' + method + '\' request \'' + host + ':' + port + path + '\'');
|
||||
}
|
||||
logger.debug('http \'' + method + '\' request \'' + host + ':' + port + path + '\' returned status code ' + response.statusCode);
|
||||
var responseData = "";
|
||||
response.on('data', function (data) {
|
||||
responseData += data;
|
||||
});
|
||||
response.on('end', function () {
|
||||
logger.debug('http \'' + method + '\' request \'' + host + ':' + port + path + '\' returned data \'' + responseData + '\'');
|
||||
return resolve(responseData);
|
||||
});
|
||||
});
|
||||
request.on('error', function (err) {
|
||||
return reject('http \'' + method + '\' request \'' + host + ':' + port + path + '\' returned an error >>> ' + err);
|
||||
});
|
||||
request.end();
|
||||
});
|
||||
}
|
||||
|
||||
function toHex(value) {
|
||||
var hex = Number(value).toString(16);
|
||||
return hex;
|
||||
}
|
||||
|
||||
function sortById(array) {
|
||||
return array.sort(function (a, b) {
|
||||
return a.id - b.id;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
timeDiff,
|
||||
clone,
|
||||
httpGET,
|
||||
httpPOST,
|
||||
toHex,
|
||||
sortById
|
||||
}
|
25
package.json
Normal file
25
package.json
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name": "pbc",
|
||||
"version": "0.0.1",
|
||||
"description": "pedal board control",
|
||||
"main": "pbc.js",
|
||||
"scripts": {
|
||||
"start": "node pbc.js"
|
||||
},
|
||||
"keywords": [
|
||||
"scripts",
|
||||
"commands",
|
||||
"remote",
|
||||
"api"
|
||||
],
|
||||
"author": "Daniel Sommer <daniel.sommer@velvettear.de>",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.velvettear.de/velvettear/pbc.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@frogcat/ttl2jsonld": "^0.0.6",
|
||||
"moment": "^2.29.1"
|
||||
}
|
||||
}
|
12
pbc.js
Normal file
12
pbc.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
const logger = require('./libs/logger.js');
|
||||
const api = require('./libs/api.js');
|
||||
const server = require('./libs/server.js')
|
||||
const packageJSON = require('./package.json');
|
||||
|
||||
logger.info("launching " + packageJSON.name + " " + packageJSON.version);
|
||||
api.setupEndpoints()
|
||||
.then(server.start)
|
||||
.catch(function (err) {
|
||||
logger.error(err);
|
||||
process.exit(1);
|
||||
});
|
Loading…
Reference in a new issue