const util = require('./util.js'); const constants = require('./constants.js'); const logger = require('./logger.js'); const cache = require('./cache.js'); const commands = require('./commands.js'); const blinky = require('./blinky.js'); const osc = require('./osc.js'); const path = require('path'); const fs = require('fs'); const ttl2jsonld = require('@frogcat/ttl2jsonld').parse; let pedalboardIdBypassOrigin; async function reset() { await util.httpGET(global.config.modep.host, global.config.modep.port, '/reset'); } async function getBanks() { let banks = cache.getBanks(); if (banks !== undefined) { return banks; } banks = await util.httpGET(global.config.modep.host, global.config.modep.port, '/banks'); for (let index = 0; index < banks.length; index++) { let bank = banks[index]; bank.id = index; } banks = util.sortById(banks); cache.setBanks(banks); return banks; } async function getBankById(bankId) { const banks = await getBanks(); for (let index = 0; index < banks.length; index++) { if (banks[index].id != bankId) { continue; } return banks[index]; } throw new Error('could not find bank by id \'' + bankId + '\''); } async function getPedalboards() { let pedalboards = cache.getPedalboards(); if (pedalboards !== undefined) { return pedalboards; } pedalboards = await util.httpGET(global.config.modep.host, global.config.modep.port, '/pedalboard/list'); let id = 1; for (let index = 0; index < pedalboards.length; index++) { let pedalboard = pedalboards[index]; if (pedalboard.bundle === constants.PEDALBOARD_DEFAULT) { pedalboard.id = 0; defaultPedalboard = pedalboard; continue; } pedalboard.id = id; id++; } pedalboards = util.sortById(pedalboards); cache.setPedalboards(pedalboards); return pedalboards; } async function getDefaultPedalboard() { let defaultPedalboard = cache.getDefaultPedalboard(); if (defaultPedalboard !== undefined) { return defaultPedalboard; } defaultPedalboard = await getPedalboardByBundle(constants.PEDALBOARD_DEFAULT); cache.setDefaultPedalboard(defaultPedalboard); return defaultPedalboard; } async function getCurrentPedalboard() { let currentPedalboard = cache.getCurrentPedalboard(); if (currentPedalboard !== undefined) { return currentPedalboard; } currentPedalboard = await getPedalboardByBundle(await util.httpGET(global.config.modep.host, global.config.modep.port, '/pedalboard/current')); cache.setCurrentPedalboard(currentPedalboard); return currentPedalboard; } async function getPedalboardById(pedalboardId) { const pedalboards = await getPedalboards(); if (pedalboards === undefined) { return; } for (let index = 0; index < pedalboards.length; index++) { if (pedalboards[index].id != pedalboardId) { continue; } return pedalboards[index]; } throw new Error('could not find pedalboard by id \'' + pedalboardId + '\''); } async function getPedalboardByBundle(pedalboardBundle) { if (pedalboardBundle === undefined) { return await getDefaultPedalboard(); } const pedalboards = await getPedalboards(); for (let index = 0; index < pedalboards.length; index++) { if (pedalboards[index].bundle != pedalboardBundle) { continue; } return pedalboards[index]; } throw new Error('could not find pedalboard by bundle \'' + pedalboardBundle + '\''); } async function getPedalControlById(pedalId, controlId) { const pedal = await getCurrentPedalById(pedalId); for (let index = 0; index < pedal.controls.length; index++) { if (pedal.controls[index].id != controlId) { continue; } return pedal.controls[index]; } throw new Error('could not find control for pedal \'' + pedalId + '\' by id \'' + controlId + '\''); } async function getCurrentPedalById(id) { const currentPedals = await getCurrentPedals(); for (let index = 0; index < currentPedals.length; index++) { if (currentPedals[index].id != id) { continue; } return currentPedals[index]; } throw new Error('could not find current pedal by id \'' + id + '\''); } async function getCurrentPedals() { let currentPedals = cache.getCurrentPedals(); if (currentPedals !== undefined) { return currentPedals; } return await parseCurrentPedalboard(await getCurrentPedalboard()); } async function parseCurrentPedalboard(currentPedalboard) { let pedals = []; const defaultPedalboard = await getDefaultPedalboard(); if (defaultPedalboard.id === currentPedalboard.id) { cache.setCurrentPedals(pedals); blinky.setStatus(currentPedalboard, pedals); return pedals; } let startTime = new Date(); logger.debug('parsing current pedalboard \'' + currentPedalboard.title + '\'...'); if (currentPedalboard.uri === undefined) { throw new Error('could not determine current pedalboard config file'); } let file = path.resolve(currentPedalboard.uri.replace('file://', '')); if (global.config?.dev) { file = path.resolve(path.dirname(__dirname) + '/dev/' + currentPedalboard.uri.substring(currentPedalboard.uri.lastIndexOf('/') + 1)); } const data = await new Promise((resolve, reject) => { fs.readFile(file, (err, data) => { if (err) { return reject('could not parse current pedalboard file \'' + file + '\' >>> ' + err); } resolve(data); }); }); let json = ttl2jsonld(data.toString())['@graph']; let id = 0; for (let index = 0; index < json.length; index++) { let tmp = json[index]; if (tmp['lv2:prototype'] === undefined) { continue; } let name = tmp['@id']; pedals.push({ id: id, name: name, controls: [] }); id++; } for (let index = 0; index < json.length; index++) { let tmp = json[index]; let name = tmp['@id']; let value = tmp['ingen:value']; if (value === undefined) { continue; } let pedal = undefined; for (let pedalIndex = 0; pedalIndex < pedals.length; pedalIndex++) { if (!name.startsWith(pedals[pedalIndex].name)) { continue; } pedal = pedals[pedalIndex]; break; } if (pedal === undefined) { continue; } id = pedal.controls.length; name = name.replace(pedal.name + '/', ''); if (name !== constants.CONTROL_BYPASS) { value = value['@value']; } let control = { id, name, value }; pedal.controls.push(control); id++; let midi = tmp['midi:binding']; if (midi === undefined) { continue; } control.midi = { channel: midi['midi:channel'], controller: midi['midi:controllerNumber'] } } logger.debug('parsing current pedalboard file \'' + file + '\' took ' + util.timeDiff(startTime) + 'ms') cache.setCurrentPedals(pedals); blinky.setStatus(currentPedalboard, pedals); return pedals; } async function setControlByRequest(pedalId, requestParams) { if (requestParams === undefined) { throw new Error('could not handle POST missing all parameters', 400); } let controlId = requestParams.get('id'); if (controlId === undefined || controlId === null) { throw new Error('could not handle POST - missing parameter \'id\'', 400); } if (isNaN(controlId)) { throw new Error('parameter \'id\' is not a number', 400); } let value = requestParams.get('value'); if (value === undefined || value === null) { throw new Error('could not handle POST - missing parameter \'value\'', 400); } if (isNaN(value)) { throw new Error('parameter \'value\' is not a number', 400); } return await setControl(await getPedalControlById(pedalId, controlId), parseInt(value)); } async function setControl(control, value) { if (control === undefined || control.midi === undefined) { throw new Error('control \'' + control.name + '\' with id \'' + control.id + '\' has no midi bindings'); } control.midi.value = await osc.send(control.midi.controller, control.midi.channel, value); cache.updateControl(control); logger.info('set value for control \'' + control.name + '\' to \'' + value + '\''); } async function setPedalboardById(pedalboardId) { if (pedalboardId === undefined) { throw new Error('no pedalboard id given'); } if (isNaN(pedalboardId)) { throw new Error('given pedalboard id \'' + pedalboardId + '\' is not a number'); } return await setPedalboard(await getPedalboardById(pedalboardId)); } async function setPedalboard(pedalboard) { if (pedalboard?.bundle === undefined) { throw new Error('no bundle set for pedalboard \'' + pedalboard.title + '\''); } if (pedalboard.id === await getCurrentPedalboard().id) { throw new Error('pedalboard with id \'' + currentPedalboard.id + '\' is already active'); } await reset(); await util.httpPOST(global.config.modep.host, global.config.modep.port, '/pedalboard/load_bundle/?bundlepath=' + pedalboard.bundle); cache.setCurrentPedalboard(pedalboard); logger.info('set current pedalboard to \'' + pedalboard.title + '\''); return await parseCurrentPedalboard(pedalboard); } function hasControlMidiBindings(control) { return control?.midi !== undefined; } function isBypassActive(control) { return (control.midi?.value === undefined && control.value >= 1) || control.midi?.value === 0; } function isPedalBypassed(pedal) { return isBypassActive(getBypassControlFromPedal(pedal)); } function getBypassControlFromPedal(pedal) { if (pedal === undefined) { throw new Error('could not get bypass for an undefined pedal'); } if (pedal.controls === undefined || pedal.controls.length === 0) { throw new Error('could not get bypass for pedal \'' + pedal.name + '\' with id \'' + pedal.id + '\', pedal has no controls'); } let bypass; for (let index = 0; index < pedal.controls.length; index++) { const control = pedal.controls[index]; if (control.name === constants.CONTROL_BYPASS) { bypass = pedal.controls[index]; break; } } if (bypass === undefined) { throw new Error('could not find bypass control for pedal \'' + pedal.name + '\' with id \'' + pedal.id + '\''); } return bypass; } async function toggleBypass(pedalId) { const currentPedalboard = await getCurrentPedalboard(); const defaultPedalboard = await getDefaultPedalboard(); if (currentPedalboard.id === defaultPedalboard.id && pedalboardIdBypassOrigin === undefined) { throw new Error('could not activate bypass, default pedalboard is currently already active'); } if (pedalId === undefined) { try { let pedalboardId; if (pedalboardIdBypassOrigin === undefined) { pedalboardId = defaultPedalboard.id; pedalboardIdBypassOrigin = currentPedalboard.id; } else { pedalboardId = pedalboardIdBypassOrigin; pedalboardIdBypassOrigin = undefined; } await setPedalboardById(pedalboardId); getPedalboardById return { status: 'ok', pedalboard: await getPedalboardById(pedalboardId) }; } catch (err) { throw new Error(err); } } const pedal = await getCurrentPedalById(pedalId); const bypass = getBypassControlFromPedal(pedal); let value = 0; if (isBypassActive(bypass)) { value = 127; } try { await setControl(bypass, value); blinky.setPedalStatus(pedal); } catch (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 = { getBanks, getBankById, getPedalboards, getPedalboardById, getDefaultPedalboard, getCurrentPedalboard, saveCurrentPedalboard, getCurrentPedals, getCurrentPedalById, getPedalControlById, setPedalboard, setPedalboardById, setControlByRequest, toggleBypass, isBypassActive, isPedalBypassed, getBypassControlFromPedal, hasControlMidiBindings }