Compare commits

...

10 commits

11 changed files with 225 additions and 76 deletions

View file

@ -58,4 +58,18 @@ in this case the pedal's bypass is turned on and has the id "8"; now let's turn
| /midi | | send a midi message via `oscsend` defined by the POST arguments to the osc host | | /midi | | send a midi message via `oscsend` defined by the POST arguments to the osc host |
**note:** **note:**
POST arguments **must** contain the parameters `controller`, `channel` and `value`. POST arguments **must** contain the parameters `controller`, `channel` and `value`.
### save
| url | GET | POST |
| - | - | - |
| /save | | save current pedalboard (optionally with new `title`) |
**note:**
POST arguments **can** contain the parameter `title`.
### cache
| url | GET | POST |
| - | - | - |
| /cache/clear | | clear all cached items |
| /cache/refresh | | clear and refresh all cache items |

View file

@ -1,4 +1,5 @@
{ {
"dev": true,
"server": { "server": {
"listen": "0.0.0.0", "listen": "0.0.0.0",
"port": 3000 "port": 3000
@ -27,19 +28,27 @@
"host": "192.168.1.31", "host": "192.168.1.31",
"port": 3000, "port": 3000,
"colors": { "colors": {
"BigMuffPi": { "pedalboards": {
"enabled": "#008800", "Default": "#0F0F0F",
"bypassed": "002200" "FUZZ": "0F0F00"
}, },
"Overdrive": { "pedals": {
"enabled": "#888800", "BigMuffPi": {
"bypassed": "#222200" "enabled": "#008800",
"bypassed": "002200"
},
"Overdrive": {
"enabled": "#888800",
"bypassed": "#222200"
},
"StompBox_fuzz": {
"enabled": "#7700AA",
"bypassed": "#220022"
}
}, },
"StompBox_fuzz": { "systemd": {
"enabled": "#7700AA", "jack-capture": "#660066"
"bypassed": "#220022"
} }
}, }
"Bypass": "#333333"
} }
} }

View file

@ -5,6 +5,7 @@ const modep = require('./modep.js');
const osc = require('./osc.js'); const osc = require('./osc.js');
const info = require('./info.js'); const info = require('./info.js');
const systemd = require('./systemd.js'); const systemd = require('./systemd.js');
const cache = require('./cache.js');
const endpoints = new Map(); const endpoints = new Map();
@ -20,7 +21,7 @@ function setEndpoint(url, get, post) {
endpoint.POST = post; endpoint.POST = post;
} }
endpoints.set(url, endpoint); endpoints.set(url, endpoint);
logger.debug('set up endpoint \'' + url + '\' > ' + JSON.stringify(endpoint)); logger.debug('set up endpoint \'' + url + '\' > GET: ' + (endpoint.GET !== undefined) + ', POST ' + (endpoint.POST !== undefined));
} }
function getEndpoints() { function getEndpoints() {
@ -71,7 +72,7 @@ async function setupEndpoints() {
constants.API_MIDI, constants.API_MIDI,
undefined, undefined,
{ method: osc.sendByRequest } { method: osc.sendByRequest }
) );
if (global.config.systemd !== undefined) { if (global.config.systemd !== undefined) {
for (let index = 0; index < global.config.systemd.length; index++) { for (let index = 0; index < global.config.systemd.length; index++) {
setEndpoint( setEndpoint(
@ -81,6 +82,21 @@ async function setupEndpoints() {
) )
} }
} }
setEndpoint(
constants.API_SAVE,
undefined,
{ method: modep.saveCurrentPedalboard }
);
setEndpoint(
constants.API_CACHE_CLEAR,
undefined,
{ method: cache.clear }
);
setEndpoint(
constants.API_CACHE_REFRESH,
undefined,
{ method: cache.fill }
);
logger.debug('setting up ' + endpoints.size + ' endpoints took ' + util.timeDiff(timestamp) + 'ms'); logger.debug('setting up ' + endpoints.size + ' endpoints took ' + util.timeDiff(timestamp) + 'ms');
} }

View file

@ -1,9 +1,17 @@
const logger = require('./logger.js'); const logger = require('./logger.js');
const { CONTROL_BYPASS } = require('./constants.js'); const { CONTROL_BYPASS, SYSTEMD_STATE_ACTIVE } = require('./constants.js');
const { httpPOST } = require('./util.js'); const { httpPOST, httpGET } = require('./util.js');
const BLINKSTICK_SQUARE = 'square';
const BLINKSTICK_STRIP = 'strip';
const COLOR_RANDOM = 'random';
const COLOR_DEFAULT = '#333333';
let enabled; let enabled;
let isBypassActive; let fnIsBypassActive;
let animationsInProgress = {};
async function initialize() { async function initialize() {
enabled = global.config.blinky !== undefined && enabled = global.config.blinky !== undefined &&
@ -12,36 +20,60 @@ async function initialize() {
global.config.blinky.port !== undefined; global.config.blinky.port !== undefined;
} }
async function setActive(active) { async function getColor() {
if (!enabled) { if (!enabled) {
return; return;
} }
logger.info('handling blinky event \'setActive\' (argument: \'' + active + '\')...');
try { try {
if (active) { return await httpGET(
global.config.blinky.host,
global.config.blinky.port,
'color'
);
} catch (err) {
logger.error('[blinky] ' + err);
}
}
async function poweroff(...blinksticks) {
if (!enabled) {
return;
}
if (blinksticks === undefined || blinksticks.length === 0) {
blinksticks = [BLINKSTICK_SQUARE, BLINKSTICK_STRIP];
}
for (let index = 0; index < blinksticks.length; index++) {
try {
await httpPOST( await httpPOST(
global.config.blinky.host, global.config.blinky.host,
global.config.blinky.port, global.config.blinky.port,
'/set', 'poweroff',
{ blinkstick: 'square', color: '#0f0f0f' } { blinkstick: blinksticks[index] }
); );
logger.debug('[blinky] powered off blinkstick \'' + blinksticks[index]);
} catch (err) {
logger.error('[blinky] ' + err);
}
}
}
async function setPedalboardStatus(pedalboard) {
try {
if (!enabled || pedalboard === undefined) {
return; return;
} }
let mode = '/poweroff';
await httpPOST( await httpPOST(
global.config.blinky.host, global.config.blinky.host,
global.config.blinky.port, global.config.blinky.port,
mode, 'set',
{ blinkstick: 'square' } {
); blinkstick: BLINKSTICK_SQUARE,
await httpPOST( color: getPedalboardColor(pedalboard.title)
global.config.blinky.host, }
global.config.blinky.port,
mode,
{ blinkstick: 'strip' }
); );
logger.debug('[blinky] set status for pedalboard \'' + pedalboard.title + '\'');
} catch (err) { } catch (err) {
logger.error('[BLINKY] ' + err); logger.error('[blinky] ' + err);
} }
} }
@ -50,8 +82,8 @@ async function setPedalStatus(pedal) {
if (!enabled || pedal === undefined || pedal.controls === undefined) { if (!enabled || pedal === undefined || pedal.controls === undefined) {
return; return;
} }
if (isBypassActive === undefined) { if (fnIsBypassActive === undefined) {
isBypassActive = require('./modep.js').isBypassActive; fnIsBypassActive = require('./modep.js').isBypassActive;
} }
for (let index = 0; index < pedal.controls.length; index++) { for (let index = 0; index < pedal.controls.length; index++) {
const bypass = pedal.controls[index]; const bypass = pedal.controls[index];
@ -63,23 +95,25 @@ async function setPedalStatus(pedal) {
global.config.blinky.port, global.config.blinky.port,
'set', 'set',
{ {
blinkstick: 'strip', blinkstick: BLINKSTICK_STRIP,
index: pedal.id, index: pedal.id,
color: getPedalColor(pedal.name, isBypassActive(bypass)) color: getPedalColor(pedal.name, fnIsBypassActive(bypass))
} }
); );
logger.debug('[blinky] set status for pedal \'' + pedal.name + '\'');
} }
} catch (err) { } catch (err) {
logger.error('[BLINKY] ' + err); logger.error('[blinky] ' + err);
} }
} }
async function setStatus(pedals) { async function setStatus(pedalboard, pedals) {
if (!enabled) { if (!enabled) {
return; return;
} }
try { try {
await httpPOST(global.config.blinky.host, global.config.blinky.port, '/poweroff', { blinkstick: 'strip' }); setPedalboardStatus(pedalboard);
await poweroff([BLINKSTICK_STRIP]);
if (pedals === undefined || pedals.length === 0) { if (pedals === undefined || pedals.length === 0) {
return; return;
} }
@ -98,38 +132,58 @@ async function setStatus(pedals) {
} }
} }
} catch (err) { } catch (err) {
logger.error('[BLINKY] ' + err); logger.error('[blinky] ' + err);
} }
} }
async function setBypass(bypassActive) { async function setSystemdStatus(name, state) {
if (!enabled) { if (name === undefined || state === undefined) {
return; return;
} }
try { let mode = 'set';
if (!bypassActive) { let options = {
return await setActive(true); blinkstick: BLINKSTICK_SQUARE,
color: animationsInProgress[name] || COLOR_DEFAULT
};
if (state === SYSTEMD_STATE_ACTIVE) {
const result = await getColor([BLINKSTICK_SQUARE]);
for (let index = 0; index < result.length; index++) {
if (result[index].blinkstick !== options.blinkstick) {
continue;
}
animationsInProgress[name] = result[index].leds[0]?.color || COLOR_RANDOM;
mode = 'pulse';
options.color = getSystemdColor(name);
break;
} }
await httpPOST(global.config.blinky.host, global.config.blinky.port, 'pulse', { blinkstick: 'square', color: '#660000' });
} catch (err) {
logger.error('[BLINKY] ' + err);
} }
return await httpPOST(
global.config.blinky.host,
global.config.blinky.port,
mode,
options
);
}
function getPedalboardColor(name) {
return global.config?.blinky?.colors?.pedalboards[name] || COLOR_RANDOM;
} }
function getPedalColor(name, bypassed) { function getPedalColor(name, bypassed) {
let color;
if (bypassed) { if (bypassed) {
color = global.config.blinky?.colors[name]?.bypassed || global.config.blinky?.colors['Bypass'] || '#333333'; return global.config?.blinky?.colors?.pedals[name]?.bypassed || COLOR_DEFAULT;
} else {
color = global.config.blinky?.colors[name]?.enabled || 'random';
} }
return color; return global.config?.blinky?.colors?.pedals[name]?.enabled || COLOR_RANDOM;
}
function getSystemdColor(name) {
return global.config?.blinky?.colors?.systemd[name] || COLOR_RANDOM;
} }
module.exports = { module.exports = {
initialize, initialize,
setActive, poweroff,
setBypass,
setStatus, setStatus,
setPedalStatus setPedalStatus,
setSystemdStatus
} }

View file

@ -8,8 +8,8 @@ const cache = new Map();
async function fill() { async function fill() {
const timestamp = new Date(); const timestamp = new Date();
logger.debug('filling cache...');
clear(); clear();
logger.debug('filling cache...');
try { try {
if (modep === undefined) { if (modep === undefined) {
modep = require('./modep.js'); modep = require('./modep.js');
@ -27,6 +27,7 @@ async function fill() {
} }
function clear() { function clear() {
logger.debug('clearing cache...');
cache.clear(); cache.clear();
} }

View file

@ -9,6 +9,8 @@ const PEDALS = '/pedals';
const BYPASS = '/bypass'; const BYPASS = '/bypass';
const MIDI = '/midi'; const MIDI = '/midi';
const SYSTEMD = '/systemd'; const SYSTEMD = '/systemd';
const SAVE = '/save';
const CACHE = '/cache';
exports.SYSTEMD_STATE_ACTIVE = 'active'; exports.SYSTEMD_STATE_ACTIVE = 'active';
exports.SYSTEMD_STATE_INACTIVE = 'inactive'; exports.SYSTEMD_STATE_INACTIVE = 'inactive';
@ -33,6 +35,9 @@ exports.API_BYPASS = BYPASS;
exports.API_BYPASS_BY_ID = BYPASS + '/' + VARIABLE_ID; exports.API_BYPASS_BY_ID = BYPASS + '/' + VARIABLE_ID;
exports.API_MIDI = MIDI; exports.API_MIDI = MIDI;
exports.API_SYSTEMD = SYSTEMD; exports.API_SYSTEMD = SYSTEMD;
exports.API_SAVE = SAVE;
exports.API_CACHE_CLEAR = CACHE + '/clear';
exports.API_CACHE_REFRESH = CACHE + '/refresh';
exports.CACHE_BANKS = 'banks'; exports.CACHE_BANKS = 'banks';
exports.CACHE_PEDALBOARDS = 'pedalboards'; exports.CACHE_PEDALBOARDS = 'pedalboards';

View file

@ -146,7 +146,7 @@ async function parseCurrentPedalboard(currentPedalboard) {
const defaultPedalboard = await getDefaultPedalboard(); const defaultPedalboard = await getDefaultPedalboard();
if (defaultPedalboard.id === currentPedalboard.id) { if (defaultPedalboard.id === currentPedalboard.id) {
cache.setCurrentPedals(pedals); cache.setCurrentPedals(pedals);
blinky.setStatus(pedals); blinky.setStatus(currentPedalboard, pedals);
return pedals; return pedals;
} }
let startTime = new Date(); let startTime = new Date();
@ -154,9 +154,10 @@ async function parseCurrentPedalboard(currentPedalboard) {
if (currentPedalboard.uri === undefined) { if (currentPedalboard.uri === undefined) {
throw new Error('could not determine current pedalboard config file'); throw new Error('could not determine current pedalboard config file');
} }
// FAKE DATA let file = path.resolve(currentPedalboard.uri.replace('file://', ''));
let file = path.resolve(path.dirname(__dirname) + '/dev/' + currentPedalboard.uri.substring(currentPedalboard.uri.lastIndexOf('/') + 1)); if (global.config?.dev) {
// let file = path.resolve(currentPedalboard.uri.replace('file://', '')); file = path.resolve(path.dirname(__dirname) + '/dev/' + currentPedalboard.uri.substring(currentPedalboard.uri.lastIndexOf('/') + 1));
}
const data = await new Promise((resolve, reject) => { const data = await new Promise((resolve, reject) => {
fs.readFile(file, (err, data) => { fs.readFile(file, (err, data) => {
if (err) { if (err) {
@ -210,7 +211,7 @@ async function parseCurrentPedalboard(currentPedalboard) {
} }
logger.debug('parsing current pedalboard file \'' + file + '\' took ' + util.timeDiff(startTime) + 'ms') logger.debug('parsing current pedalboard file \'' + file + '\' took ' + util.timeDiff(startTime) + 'ms')
cache.setCurrentPedals(pedals); cache.setCurrentPedals(pedals);
blinky.setStatus(pedals); blinky.setStatus(currentPedalboard, pedals);
return pedals; return pedals;
} }
@ -241,6 +242,7 @@ async function setControl(control, value) {
} }
control.midi.value = await osc.send(control.midi.controller, control.midi.channel, value); control.midi.value = await osc.send(control.midi.controller, control.midi.channel, value);
cache.updateControl(control); cache.updateControl(control);
logger.info('set value for control \'' + control.name + '\' to \'' + value + '\'');
} }
async function setPedalboardById(pedalboardId) { async function setPedalboardById(pedalboardId) {
@ -263,6 +265,7 @@ async function setPedalboard(pedalboard) {
await reset(); await reset();
await util.httpPOST(global.config.modep.host, global.config.modep.port, '/pedalboard/load_bundle/?bundlepath=' + pedalboard.bundle); await util.httpPOST(global.config.modep.host, global.config.modep.port, '/pedalboard/load_bundle/?bundlepath=' + pedalboard.bundle);
cache.setCurrentPedalboard(pedalboard); cache.setCurrentPedalboard(pedalboard);
logger.info('set current pedalboard to \'' + pedalboard.title + '\'');
return await parseCurrentPedalboard(pedalboard); return await parseCurrentPedalboard(pedalboard);
} }
@ -300,8 +303,8 @@ function getBypassControlFromPedal(pedal) {
} }
async function toggleBypass(pedalId) { async function toggleBypass(pedalId) {
let currentPedalboard = await getCurrentPedalboard(); const currentPedalboard = await getCurrentPedalboard();
let defaultPedalboard = await getDefaultPedalboard(); const defaultPedalboard = await getDefaultPedalboard();
if (currentPedalboard.id === defaultPedalboard.id && pedalboardIdBypassOrigin === undefined) { if (currentPedalboard.id === defaultPedalboard.id && pedalboardIdBypassOrigin === undefined) {
throw new Error('could not activate bypass, default pedalboard is currently already active'); throw new Error('could not activate bypass, default pedalboard is currently already active');
} }
@ -316,11 +319,11 @@ async function toggleBypass(pedalId) {
pedalboardIdBypassOrigin = undefined; pedalboardIdBypassOrigin = undefined;
} }
await setPedalboardById(pedalboardId); await setPedalboardById(pedalboardId);
blinky.setBypass(pedalboardId === defaultPedalboard.id); getPedalboardById
return { status: 'ok', pedalboard: await getPedalboardById(pedalboardId) };
} catch (err) { } catch (err) {
throw new Error(err); throw new Error(err);
} }
return;
} }
const pedal = await getCurrentPedalById(pedalId); const pedal = await getCurrentPedalById(pedalId);
const bypass = getBypassControlFromPedal(pedal); const bypass = getBypassControlFromPedal(pedal);
@ -334,6 +337,46 @@ async function toggleBypass(pedalId) {
} catch (err) { } catch (err) {
throw new Error('could not toggle bypass for pedal ' + pedal.name + ' with id \'' + pedal.id + '\' > ' + err); throw new Error('could not toggle bypass for pedal ' + pedal.name + ' with id \'' + pedal.id + '\' > ' + err);
} }
logger.info('toggled bypass for pedal \'' + pedal.name + '\'');
return { status: 'ok', bypass: isBypassActive(bypass), control: bypass };
}
async function saveCurrentPedalboard(id, args) {
const currentPedalboard = await getCurrentPedalboard();
const defaultPedalboard = await getDefaultPedalboard();
if (currentPedalboard.id === defaultPedalboard.id) {
throw new Error('not saving default pedalboard \'' + defaultPedalboard.title + '\'');
}
let title = args?.get('title') || currentPedalboard.title;
let newBoard = 1;
if (title === currentPedalboard.title) {
newBoard = 0;
} else {
const pedalboards = await getPedalboards();
for (let index = 0; index < pedalboards.length; index++) {
if (pedalboards[index].title === title) {
throw new Error('not saving current state of pedalboard \'' + currentPedalboard.title + '\' as new pedalboard \'' + title + '\', pedalboard already exists')
}
}
}
await util.httpPOST(global.config.modep.host, global.config.modep.port, '/pedalboard/save', { title: title, asNew: newBoard });
result = {
status: 'ok',
title: title,
pedalboard: currentPedalboard
};
if (newBoard === 0) {
logger.info('saved current state of pedalboard \'' + currentPedalboard.title + '\'');
} else {
logger.info('saved current state of pedalboard \'' + currentPedalboard.title + '\' as new pedalboard \'' + title + '\'');
cache.clearPedalboards();
await getPedalboards();
}
return {
status: 'ok',
title: title,
pedalboard: currentPedalboard
};
} }
module.exports = { module.exports = {
@ -343,6 +386,7 @@ module.exports = {
getPedalboardById, getPedalboardById,
getDefaultPedalboard, getDefaultPedalboard,
getCurrentPedalboard, getCurrentPedalboard,
saveCurrentPedalboard,
getCurrentPedals, getCurrentPedals,
getCurrentPedalById, getCurrentPedalById,
getPedalControlById, getPedalControlById,

View file

@ -1,6 +1,5 @@
const logger = require('./logger.js'); const logger = require('./logger.js');
const api = require('./api.js'); const api = require('./api.js');
const blinky = require('./blinky.js');
const http = require('http'); const http = require('http');
const ID = require('./constants.js').VARIABLE_ID; const ID = require('./constants.js').VARIABLE_ID;
@ -13,7 +12,6 @@ async function start() {
} }
server.listen(global.config.server.port, global.config.server.listen).on('listening', () => { server.listen(global.config.server.port, global.config.server.listen).on('listening', () => {
logger.debug('server listening on ' + global.config.server.listen + ':' + global.config.server.port + '...'); logger.debug('server listening on ' + global.config.server.listen + ':' + global.config.server.port + '...');
blinky.setActive(true);
handleRequests(); handleRequests();
}); });
} }

View file

@ -1,6 +1,7 @@
const logger = require('./logger.js'); const logger = require('./logger.js');
const commands = require('./commands.js'); const commands = require('./commands.js');
const constants = require('./constants.js'); const constants = require('./constants.js');
const blinky = require('./blinky.js');
async function getServiceState(name) { async function getServiceState(name) {
if (name === undefined) { if (name === undefined) {
@ -13,12 +14,14 @@ async function getServiceState(name) {
} }
async function setServiceState(name, args) { async function setServiceState(name, args) {
if (name === undefined || args === undefined) { if (name === undefined) {
return; return;
} }
let state = args.get('state'); let state = args?.get('state');
if (state === undefined) { if (state === undefined) {
return; const msg = 'missing argument \'state\', pass either \'active\', \'inactive\' or \'toggle\'';
logger.debug(msg);
return { err: msg };
} }
if (state !== constants.SYSTEMD_STATE_ACTIVE && state !== constants.SYSTEMD_STATE_INACTIVE && state !== constants.SYSTEMD_STATE_TOGGLE) { if (state !== constants.SYSTEMD_STATE_ACTIVE && state !== constants.SYSTEMD_STATE_INACTIVE && state !== constants.SYSTEMD_STATE_TOGGLE) {
const msg = 'can not set state of service \'' + name + '\' to unknown state \'' + state + '\''; const msg = 'can not set state of service \'' + name + '\' to unknown state \'' + state + '\'';
@ -40,7 +43,9 @@ async function setServiceState(name, args) {
state = constants.SYSTEMD_STATE_ACTIVE; state = constants.SYSTEMD_STATE_ACTIVE;
} }
logger.debug('setting state of service \'' + name + '\' to \'' + state + '\'...'); logger.debug('setting state of service \'' + name + '\' to \'' + state + '\'...');
return { service: name, state: state, result: await commands.execute('systemctl', [command, name], true)}; const result = await commands.execute('systemctl', [command, name], true);
blinky.setSystemdStatus(name, state);
return { service: name, state: state, result: result};
} }
module.exports = { module.exports = {

2
pbc.js
View file

@ -40,7 +40,7 @@ function handleExit() {
} }
async function exit(err, code) { async function exit(err, code) {
await blinky.setActive(false); await blinky.poweroff();
if (code === undefined) { if (code === undefined) {
code = 0; code = 0;
if (err !== undefined) { if (err !== undefined) {

View file

@ -1,12 +1,15 @@
[Unit] [Unit]
Description=pbc (pedal board control) Description=pbc (pedal board control)
After=modep-mod-host.service modep-mod-ui.service modep-touchosc2midi.service
Wants=modep-mod-host.service modep-mod-ui.service modep-touchosc2midi.service
BindsTo=modep-mod-ui.service
[Service] [Service]
Type=simple Type=simple
User=node User=root
Group=node Group=root
WorkingDirectory=/opt/pbc WorkingDirectory=/opt/pbc
ExecStart=/opt/nvm/nvm-exec node pbc.js ExecStart=/opt/nvm/nvm-exec node pbc.js
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target