Post by novelty on May 7, 2022 11:41:47 GMT
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...)
Post by uncledave on May 7, 2022 17:22:42 GMT
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...
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
Ass I10 = searchFlag
If pValue > I11
Mat I10 = I10 | highFlag
Mat pWord = I10 | I11 # store updated flags in the word
Set LB0 pWord
If I10 > 0
# here every time, until input value crosses saved value
Ass I12 = searchFlag
If pValue > I11
Mat I12 = I12 | highFlag
# 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
# 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
HandleCCSearch pWord pValue
# 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
Ass I20 = currBase
Mat I21 = I20 + bankSize
While I20 < I21
Mat LI20 = LI20 | initFlag
Mat I20 = I20 + 1
# 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
# return array index for this CC#
Mat pResult = currBase + M1
End # initialization
If MT == B0
# passes the saved data word for the current CC input
ProcessCC LI00 M2
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.
Post by novelty on May 8, 2022 5:52:40 GMT
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.
Post by uncledave on May 8, 2022 10:38:56 GMT
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.
Post by novelty on May 9, 2022 12:00:30 GMT
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!
Post by uncledave on May 9, 2022 14:22:05 GMT
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.