Hi all. I'm sure this has been done. Does anyone have a snippet/block of code that will block an incoming CC until it "catches" a stored CC, and then allow all further changes after that? Usage: I want to store sets of CC values on a per channel basis in an array and then recall them when I switch channels; the recall will set the "block until catching the stored CC value" flag; flag is cleared once caught, allowing further changes until the flag is set once more. Sorry for not explaining this with correct terminology! (I could figure this out, but...)
Actually, this is not as simple as it sounds. Each control has 4 states: initial, looking for high, looking for low, and normal. You can detect the pickup of the saved value when the input value crosses the saved value from low to high, or high to low. It may never equal the saved value, depending on how fast the controller scans the knobs, so you must not check for equality.
The following works, for a simple setup of 4 banks of 8 CCs. There is one 16-bit word for each CC in each bank, containing the last value and the control flags. The flags are all zero in normal mode, so the word is just the data value. The flags enable the special processing when the word value is >= 100. You should be able to modify the bank structure to meet your needs.
Notice that we create a flags word that should match the current one. Then exclusive or (the "^" operator), logical disjunction, detects whether the CC value has crossed the saved value. I use I10.. and I20.. in subs to avoid conflict with normal use of I00... #NewBlock Algorithm
Set name BLKCC
# The data word contains the last value for this CC# on this channel, # and these control flags. Alias 100 initFlag # set when channel changed Alias 200 searchFlag # set when waiting for input match Alias 400 highFlag # set when initial input was high Alias FF00 flagMask # flags are in high byte of word Alias 7F dataMask # saved value in low byte
# processes one CC change. pWord contains saved value and flags. # pValue is CC value from message (M2) Sub HandleCCSearch pWord pValue # split word into flags and value Mat I10 = pWord & flagMask Mat I11 = pWord & dataMask If I10 == initFlag # here on first change for this CC# since channel change If pValue == I11 # special case for initial match Ass I10 = 0 Ass pWord = pValue Else Ass I10 = searchFlag If pValue > I11 Mat I10 = I10 | highFlag End Mat pWord = I10 | I11 # store updated flags in the word End Set LB0 pWord End If I10 > 0 # here every time, until input value crosses saved value Ass I12 = searchFlag If pValue > I11 Mat I12 = I12 | highFlag End # I12 will match I10 until input value crosses saved value Mat I12 = I12 ^ I10 If I12 > 0 # difference means input has crossed value Ass pWord = pValue Else Block End End End
# this is simple when tracking this CC#, more complicated if # knob not yet aligned to saved value Sub ProcessCC pWord pValue If pWord < initFlag Ass pWord = pValue # remember updated value Else HandleCCSearch pWord pValue End End
# create 8 banks of 8 CCs, using CC# 0 to 7 Alias L0 currBase Alias L1 currChan Alias 2 bankBase Alias 8 bankSize Alias 4 numBanks Ass L00 = bankBase 10 # do not save these in preset Ass L02 = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +P Alias L12 = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +P
# sets the initFlag for all values in current bank # call this whenever the channel is changed # treats the saved data as a section of an array Sub PresetBank Ass I20 = currBase Mat I21 = I20 + bankSize While I20 < I21 Mat LI20 = LI20 | initFlag Mat I20 = I20 + 1 End End
# returns the saved array index for the current message # knows how to index the saved data Sub GetIndex pResult If currChan != MC # handle new channel If MC < numBanks Ass currChan = MC Mat I10 = currChan * bankSize Mat currBase = I10 + bankBase PresetBank End End # return array index for this CC# Mat pResult = currBase + M1 End
End # initialization
If MT == B0 GetIndex I00 # passes the saved data word for the current CC input ProcessCC LI00 M2 End Edit: I've realized this doesn't reset properly on reload. I will fix that. Edit: Fixed by initializing with a nonexistent channel. Means any CC will be on a "new" channel. Edit: I've marked the three locations that "know" the layout of the saved data values, and their relation to the input CC#s. My sample scheme is trivially simple, relating CC#s 0..7 to banks of 8 values, selected by channels 0..3.
Thanks, uncledave! I thought I had replied to this earlier but forgot to send it. I'm time poor at the moment but will take a deep dive into this soon. Much appreciated. And, yes, I hadn't realised that I woudln't be able to test for equality.
Please be sure to use the latest edited version. It initializes the current channel to 16, so that any CC input will be seen as a "new" channel. This will be significant when restoring a saved session.
Thanks. It will most likely be the weekend before I can work with it, but I'm looking forward to it. I'm fairly new to MIDI coding but I do have some coding background, so hopefully I'll be able to pick it up quickly and tweak it for my needs!
The StreamByter documentation is comprehensive and definitely worth a read. But I've collected the following notes on some of the peculiarities of StreamByter as a programming language.
1. All variables are global arrays, so care is required to prevent conflict between "local" variables in Subs. You can Alias widely-used variables to make them seem more conventional. You might create an Alias name that confuses SB; choose another name if that happens. You can initialize arrays in If Load; use +P to cause the current values to be saved in any host session/preset.
2. You can access an array element using an indirect reference, for example LI00. Here L is the array name, and the value of I00 is the array index. Array indices are zero-based, like C.
3. Most variables are "unsigned", or non-negative. If you set a variable to a negative value, it will be treated as a very large positive value. For example, Ass I00 = -1 will set I00 to FFFF ($65535). These will actually work correctly in addition and subtraction, thanks to 2's complement arithmetic.
4. Literal values are treated as hexadeximal by default; use "$" as prefix for decimal. For example, "100" is actually "0x100", or $256. This will trip you up once or twice.
5. I use Aliases for constants, similar to #define in C. This makes the program easy to change by simply revising the aliased value.
6. Every SB statement begins with a keyword, like classic Basic. You must use Assign (Ass) to set a variable to a value, and Math (Mat) or Calc (Cal) to perform a calculation. SB will always require you to correct this if you get it wrong. Note that each calculation uses only one operator and two operands. You need to parse more complex expressions, and may need to introduce temporary variables.
7. Be sure to Install Rules before Saving a script. Only the compiled copy is Saved.
8. The "If Load" section(s) of the program runs only once when the program is first loaded. The rest of the program (main body) runs once for each received MIDI message. The bytes of the message are in the M array, typically M0, M1, and M2. You can learn about MIDI messages from the MIDI website, www.midi.org. The message is passed through unless explicitly Blocked.
9. Sub parameters are given dummy names on the Sub statement, similar to other languages. But all parameters are passed "by reference" or "in/out", similar to traditional Fortran. So you must not modify them in the Sub, unless you actually intend to return a value. I use pResult for what would be the returned value of a function.