orand
MidiFire Beta
Posts: 6
|
Post by orand on Feb 3, 2018 22:54:28 GMT
I have a MIDI controller that can send either relative or absolute CC messages, but it doesn’t update its internal absolute knob positions when it receives sent knob parameters from the synth it’s controlling (the Moog Model 15 app). This results in big jumps in the Model 15 knob positions when I first turn a knob on my controller. And if I later adjust a knob in the app or change programs, the controller’s knob gets out of sync again. Unfortunately, Model 15 doesn’t support relative CC.
I’d like to fix this with MidiFire. MidiFire would maintain an array of CC controller absolute values. Values in this array would be overwritten each time Model 15 sends out parameter changes. Then, my MIDI controller would send relative change messages that can be added/subtracted to the absolute values stored in the array. After the change is made, the updated absolute value will be sent out to Model 15. This should result in the physical knob positions always staying in sync with the virtual knobs.
There are anywhere between 60 and 100 CC parameters I’d like to do this with. Some of the parameters I might want to bypass this processing.
It would be cool but not required to use MIDI Learn to add CC parameters to the list of controllers that participate in this relative-to-absolute processing.
Which parts of this big wish list seem straightforward with Byte Streamer, and which parts are complicated? Anything that might not be possible here? Thanks!
|
|
nic
Soapbox Supremo
Troublemaker
Press any key to continue
Posts: 2,011
|
Post by nic on Feb 5, 2018 9:27:09 GMT
Hi orand , I think this is definitely doable with the Stream Byter although it might take a few iterations. I'll go through my thinking: 1. We maintain the current value of each of the 128 CCs on a specific channel in array, L0-7F (0-127). If you need multi channel support, then we would run up multiple instances of this, one for each channel. IF LOAD ASS K0 = B0 # cc/channel we will monitor ASS L0 = 0 # current CC values in L0-7F END
# preserve absolute CC values # passing through in L0-7F IF M0 == K0 # cc/chan matches ASS LM1 = M2 # retain abs CC value of CC# ENDSo, we now have the current absolute values stored in the L0-7F part of the array which can be used later. What do the relative CC messages look like? We will need to add code to interpret, apply these to our array and send out an absolute value. Now, you only want this to happen on a specific set of CCs - let's maintain a boolean flag of all CCs in L80-FF (128-255) and set to 1 if we wish to operate on or 0 to ignore, so inside another LOAD clause: IF LOAD ASS L80 = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ASS L90 = 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 ASS LA0 = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ASS LB0 = 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 ASS LC0 = 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 ASS LD0 = 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 ASS LE0 = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ASS LF0 = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0ENDSo, that's a static table of 0 or 1 for each of the CCs - where the CC as a '1' indicates we operate on it, '0' would mean ignore. To test whether we want to work with a CC (or not) we do something like: MAT I0 = M1 + 80 # get index into L80-FF from CC# IF LI0 == 1 # CC is flagged, so do our stuff ENDNow, for MIDI Learn, we could use K1 as a boolean variable to flag whether MIDI Learn is active or not (you could add code to toggle this remotely via an incoming MIDI event too, or just change the value and press 'Install Rules' to enable/disable learn mode) and then when we see a CC, set the L80-FF flag accordingly: IF LOAD ASS K1 = 1 # MIDI Learn on=1/off=0 END
IF K1 == 1 # learn is on IF M0 == K0 # cc/channel matches MAT I0 = M1 + 80 # index CC# into L80-FF ASS LI0 = 1 # set operate mode of CC to true END ENDThe problem with this is that if you load up a new scene or restart the app those flags you learnt are lost. The next version of MidiFire (hope to submit this to Apple this week for iOS) will allow you to mark variables as 'preserved' which will save/restore whatever value is in the array across scene changes and app restarts. My suggestion is to start with the basics and do all CCs first, then add the individual CC flagging part. If you let me know what the relative change messages look like, I can guide you on amending the maintained CC value array as these pass through. Regards, Nic.
|
|
orand
MidiFire Beta
Posts: 6
|
Post by orand on Feb 6, 2018 16:04:25 GMT
That’s amazing Nic, thank you! Apologies for the delay, I didn’t realize forum email notifications weren’t working.
You just made another sale on this. :-) I’ll work on getting this put together and report back. Thanks again.
|
|
orand
MidiFire Beta
Posts: 6
|
Post by orand on Feb 12, 2018 6:03:04 GMT
I got it working, hooray! An additional problem I decided to solve was to allow Model 15 and the MiniLab to send on the same channel, yet feed into the same Stream Byter without it getting confused which CC messages were relative and which were absolute. I did this by adding a second Stream Byter module that tunnels the Model 15 CC messages inside a custom sysex message. Then I feed the sysex messages into the Relative-to-Absolute module and filter them out after I’ve processed them.Is there a better solution than sysex tunneling for solving this problem?
Here’s the code for the CC to SysEx module:
# if this is a CC message IF M0 >= B0 IF M0 <= BF # tunnel the message inside a sysex SND F0 00 00 00 12 00 00 M0 M1 M2 F7 END END
# block everything else XX = XX +B
And here’s the code for the Relative-to-Absolute module:
IF LOAD ASS K1 = B0 # rel cc/channel we will monitor ASS L0 = 0 # current CC values in L0-7F
# Table of which CCs to convert from relative to absolute # 0-15 (0x00-0F) ASS L80 = 0 0 0 1 0 1 0 0 0 1 0 1 1 1 1 1 # 16-31 (0x10-1F) ASS L90 = 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 # 32-47 (0x20-2F) ASS LA0 = 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 # 48-63 (0x30-3F) ASS LB0 = 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 # 64-79 (0x40-4F) ASS LC0 = 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 # 80-95 (0x50-5F) ASS LD0 = 1 1 0 0 1 0 1 1 1 1 1 1 1 1 0 1 # 96-111 (0x60-6F) ASS LE0 = 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 # 112-127 (0x70-7F) ASS LF0 = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 END
IF M0 == F0 00 00 00 # our custom sysex # update absolute value array ASS LM8 = M9
# filter out our custom sysex F0 00 00 = XX +B END
IF M0 == K1 # incoming potentially relative value # is this one of the CCs we want to convert? MAT I0 = M1 + 80 # get index into L80-FF from CC# IF LI0 == 1
# Relative mode #1 is centered on 0x40 (64) so subtract 0x40. 63 and below will become negative numbers, resulting in a decrement when added. MAT K2 = M2 - 40
# display our relative offset in the left label SET LB0 K2 +D
# apply our relative increment/decrement to absolute value MAT LM1 = LM1 + K2
# keep the result between 0 and 127 IF LM1 > FF00 ## if less than zero ASS LM1 = 0 END IF LM1 > 7F ASS LM1 = 7F END
# display our absolute output in the right label SET LB1 LM1 +D
# send the updated absolute version out SND M0 M1 LM1
# block the original message XX = XX +B END END
To use, feed the Model 15 input into the CC-to-SysEx model, then feed that into the Relative-to-Absolute module. Feed the MiniLab input into the Relative-to-Absolute module as well. Feed the Relative-to-Absolute output to the Model 15 output.
|
|
orand
MidiFire Beta
Posts: 6
|
Post by orand on Feb 12, 2018 6:08:22 GMT
A likely future enhancement will be to support multiple instances of Model 15 running on different channels, without having to assign to the K1 channel variable.
|
|
nic
Soapbox Supremo
Troublemaker
Press any key to continue
Posts: 2,011
|
Post by nic on Feb 12, 2018 11:06:47 GMT
Hi orand, Oooh, very impressive! Especially the not immediately obvious less than zero conditional! I'm figuring you have some extensive programming experience? Tunnelling data via sysex is perfectly valid. Alternative would be to hijack some unused channel and pipe the data in CCs (blocking them from egressing, just like you've done with the sysex). Thank you for sharing your work. Regards, Nic.
|
|
orand
MidiFire Beta
Posts: 6
|
Post by orand on Feb 13, 2018 7:06:35 GMT
Thanks, yeah I’ve been programming about 25 years now.
One of the things I initially struggled with was conditionally blocking messages. I wanted something like SND, but for blocking, so I could use variables such as M0 and M1 for the blocking. But eventually I realized I could just block everything from inside an IF that matches what I want to conditionally block.
It took a few hours to really get my head around the language (feels a bit like assembly, or NSIS), but I’m now looking forward to the next problems I can solve with it. One of those is to fix a knob turning rate discrepancy on the MiniLab. Knobs 1 and 9 turn at half the rate of the rest of the knobs. I should be able to fix this by scaling their relative-mode outputs to match the others.
|
|