For synths that use CC-over-NRPN (like the Circuit Tracks), assembling NRPN messages is pretty simple in a script:
function sendNRPN(ch, MSB, LSB, val)
-- nrpn address msb
sendMIDI({ 176 + ch - 1, 99, MSB })
-- nrpn address lsb
sendMIDI({ 176 + ch - 1, 98, LSB })
-- coarse entry (CC-over-NRPN)
sendMIDI({ 176 + ch - 1, 6, val })
-- null nrpn address msb
sendMIDI({ 176 + ch - 1, 99, 127 })
-- null nrpn address lsb
sendMIDI({ 176 + ch - 1, 98, 127 })
end
You need to copy this function into each component's script and then call it from there, e.g. from an onValueChanged
handler. For example, if we want to change the source for compressor 1, we look into the reference manual for the Circuit:
And then send the appropriate message, for example (for a radio control):
sendNRPN(16, 2, 55, self.values.x)
Here's a small version that is easier to copy around:
function sendNRPN(ch,MSB,LSB,val)
local f=sendMIDI;ad=176+ch-1
f({ad,99,MSB})
f({ad,98,LSB})
f({ad,6,val})
f({ad,99,127})
f({ad,98,127})
end
To prevent us from having to copy the function into each component, we can create a global dispatcher in the root element. Click on the backround to access the root control and add this script:
function onReceiveNotify(cmd, vt)
if cmd == 'nrpn' then
sendNRPN(unpack(vt))
end
end
function sendNRPN(ch,MSB,LSB,val)
local f=sendMIDI;ad=176+ch-1
f({ad,99,MSB})
f({ad,98,LSB})
f({ad,6,val})
f({ad,99,127})
f({ad,98,127})
end
Now in any control, we can directly send NRPN messages like this:
root:notify('nrpn', {15, 2, 55, self.values.x})
We can build a reverse dispatch to route incoming NRPN back to the controls. First, we need code to parse incoming NRPN and find applicable controls to send the NRPN to. We introduce a tag nrpn_enabled
, so that any control with this tag will receive any incoming NRPN. In the root control code:
local buf_nrpn = nil
function onReceiveMIDI(message, connections)
if message[1] == 248 then
return
end
if message[2] == 99 and message[3] ~= 127 then
buf_nrpn = { message[1] - 176 + 1, message[3] }
elseif message[2] == 98 and message[3] ~= 127 then
if not buf_nrpn or #buf_nrpn ~= 2 then
print('error: LSB did not follow MSB')
buf_nrpn = nil
end
table.insert(buf_nrpn, message[3])
elseif buf_nrpn and message[2] == 6 then
table.insert(buf_nrpn, message[3])
print('IN/NRPN', unpack(buf_nrpn))
distributeNRPN(buf_nrpn)
buf_nrpn = nil
elseif buf_nrpn then
print('error: unknown NRPN structure (abandon)')
buf_nrpn = nil
else
print('IN/MIDI', unpack(message))
end
end
function distributeNRPN(vt)
-- send to all tag:nrpn_enabled
local recvs = root:findAllByProperty('tag', 'nrpn_enabled', true)
print('dispatching NRPN to ' .. tostring(#recvs) .. ' controls')
for _, ctrl in ipairs(recvs) do
ctrl:notify('nrpn', vt)
end
end
and inside of a control, we can respond like this:
function onReceiveNotify(cmd, vt)
if cmd == 'nrpn' then
if vt[1] == 16 and vt[2] == 2 and vt[3] == 55 then
self.values.x = vt[4]
end
end
end