|
"use strict"; |
|
|
|
const dgram = require("dgram"); |
|
const oscmsg = require("osc-msg"); |
|
const colors = require("colors"); |
|
const sdefDecoder = require("synthdef-json-decoder"); |
|
|
|
const LANG_PORT = 57120; |
|
const MY_PORT = 57110; |
|
const SYNTH_PORT = 57115; |
|
|
|
const langSocket = dgram.createSocket("udp4"); |
|
const synthSocket = dgram.createSocket("udp4"); |
|
|
|
const DONE_QUIT = oscmsg.encode({address: "/done", args:[ "/quit" ]}); |
|
|
|
const SCCommandIndex = ` |
|
none notify status quit cmd d_recv d_load d_loadDir d_freeAll s_new n_trace |
|
n_free n_run n_cmd n_map n_set n_setn n_fill n_before n_after u_cmd g_new g_head |
|
g_tail g_freeAll c_set c_setn c_fill b_alloc b_allocRead b_read b_write b_free |
|
b_close b_zero b_set b_setn b_fill b_gen dumpOSC c_get c_getn b_get b_getn s_get |
|
s_getn n_query b_query n_mapn s_noid g_deepFree clearSched sync d_free |
|
b_allocReadChannel b_readChannel g_dumpTree g_queryTree error s_newargs n_mapa |
|
n_mapan n_order |
|
`.trim().split(/\s+/).map(x => "/" + x); |
|
|
|
const UnaryOpUGenIndex = ` |
|
neg not isNil notNil bitNot abs asFloat asInt ceil floor frac sign squared cubed sqrt exp reciprocal |
|
midicps cpsmidi midiratio ratiomidi dbamp ampdb octcps cpsoct log log2 log10 sin cos tan asin acos |
|
atan sinh cosh tanh rand rand2 linrand bilinrand sum3rand distort softclip coin digitvalue silence |
|
thru rectWindow hanWindow welWindow triWindow ramp scurve |
|
`.trim().split(/\s+/); |
|
|
|
const BinaryOpUGenIndex = ` |
|
+ - * / // % == != < > <= >= min max bitAnd bitOr bitXor lcm gcd round roundUp trunc atan2 hypot |
|
hypotApx pow << >> >>> fill ring1 ring2 ring3 ring4 difsqr sumsqr |
|
sqrsum sqrdif absdif thresh amclip scaleneg clip2 excess fold2 wrap2 firstarg rrand exprand |
|
`.trim().split(/\s+/); |
|
|
|
let sdef = null |
|
|
|
langSocket.bind(MY_PORT); |
|
|
|
|
|
langSocket.on("message", (buffer) => { |
|
sdef = null |
|
|
|
const msg = fmtOSC(oscmsg.decode(buffer)); |
|
|
|
if (msg.error) { |
|
console.log(colors.red(msg.error)); |
|
console.log(colors.gray(JSON.stringify(Array.from(buffer)))); |
|
} |
|
|
|
if (msg.address === "/quit") { |
|
return langSocket.send(DONE_QUIT, LANG_PORT, "127.0.0.1"); |
|
} |
|
|
|
if (msg.address !== "/status") { |
|
console.log(colors.cyan(`>> ${ JSON.stringify(msg) }`)); |
|
if (sdef !== null) { |
|
console.log(sdef); |
|
} |
|
} |
|
|
|
synthSocket.send(buffer, SYNTH_PORT, "127.0.0.1"); |
|
}); |
|
|
|
|
|
synthSocket.on("message", (buffer) => { |
|
const msg = fmtOSC(oscmsg.decode(buffer)); |
|
|
|
if (msg.error) { |
|
console.log(colors.red(msg.error)); |
|
console.log(colors.gray(JSON.stringify(Array.from(buffer)))); |
|
} |
|
|
|
if (msg.address !== "/status.reply") { |
|
console.log(colors.yellow(`<< ${ JSON.stringify(msg) }`)); |
|
} |
|
|
|
langSocket.send(buffer, LANG_PORT, "127.0.0.1"); |
|
}); |
|
|
|
|
|
function fmtOSC(msg) { |
|
if (msg.oscType === "bundle") { |
|
msg.elements = msg.elements.map(fmtOSC); |
|
} else { |
|
msg.address = SCCommandIndex[msg.address] || msg.address; |
|
msg.args = msg.args.map(value => value.value); |
|
if (msg.address === "/d_recv") { |
|
sdef = fmtSDef(sdefDecoder.decode(msg.args[0])); |
|
msg.args[0] = `<SDEF ${ msg.args[0].length }bytes>`; |
|
} |
|
if (msg.args[msg.args.length - 1] instanceof Buffer) { |
|
msg.args[msg.args.length - 1] = fmtOSC(oscmsg.decode(msg.args[msg.args.length - 1])); |
|
} |
|
} |
|
delete msg.oscType; |
|
return msg; |
|
} |
|
|
|
|
|
function fmtSDef(sdefs) { |
|
const lines = []; |
|
|
|
sdefs.forEach((sdef) => { |
|
const args = new Array(sdef.paramIndices.length); |
|
const refs = new Array(sdef.paramValues.length); |
|
const unitNames = []; |
|
|
|
sdef.paramIndices.forEach(({ name, index, length }, i) => { |
|
if (length === 1) { |
|
args[i] = `${ name }=${ sdef.paramValues[index] }`; |
|
refs[index] = name; |
|
} else { |
|
const vals = sdef.paramValues.slice(index, index + length); |
|
|
|
args[i] = `${ name }=#${ JSON.stringify(vals) }`; |
|
for (let i = 0; i < length; i++) { |
|
refs[index + i] = `${ name }[${ i }]`; |
|
} |
|
} |
|
}); |
|
|
|
lines.push(sdef.name); |
|
lines.push(`arg ${ args.join(", ")};`) |
|
|
|
const align = (i) => { |
|
const len = Math.floor(Math.log10(sdef.units.length) + 1); |
|
|
|
return (" ".repeat(len) + i).slice(-len); |
|
}; |
|
|
|
sdef.units.forEach((unit, i) => { |
|
if (/\b(|Audio|Trig)Control\b/.test(unit[0])) { |
|
return; |
|
} |
|
|
|
const unitInputs = unit[3].map(([ nodeid, index ]) => { |
|
if (nodeid === -1) { |
|
return sdef.consts[index]; |
|
} |
|
if (/\b(|Audio|Trig)Control\b/.test(sdef.units[nodeid][0])) { |
|
return refs[sdef.units[nodeid][2] + index]; |
|
} |
|
if (sdef.units[nodeid][4].length >= 2) { |
|
return `(${ nodeid }: ${ unitNames[nodeid] }[${ index }])`; |
|
} |
|
return `(${ nodeid }: ${ unitNames[nodeid] })`; |
|
}); |
|
|
|
if (unit[0] === "UnaryOpUGen") { |
|
unitNames[i] = UnaryOpUGenIndex[unit[2]]; |
|
lines.push(` ${ align(i) }: ${ unitInputs[0] }.${ UnaryOpUGenIndex[unit[2]] }`); |
|
} else if (unit[0] === "BinaryOpUGen") { |
|
unitNames[i] = BinaryOpUGenIndex[unit[2]]; |
|
lines.push(` ${ align(i) }: (${ unitInputs[0] } ${ BinaryOpUGenIndex[unit[2]] } ${ unitInputs[1] })`); |
|
} else { |
|
unitNames[i] = `${ unit[0] }.${ toRate(unit[1]) }`; |
|
lines.push(` ${ align(i) }: ${ unit[0] }.${ toRate(unit[1]) }(${ unitInputs.join(", ") })`); |
|
} |
|
}); |
|
|
|
lines.push(" "); |
|
}); |
|
|
|
return lines.join("\n").trim(); |
|
} |
|
|
|
|
|
function toRate(rate) { |
|
return [ "ir", "kr", "ar", "dr" ][rate]; |
|
} |