This page is for users that have absolutely no programming experience. The concepts explained on this page are:
- Addressable Memory (blocks, buffers, bytes, bits)
- Boolean Logic
Large blocks of memory can be divided into buffers, which can be divided into bytes, which can be divided into bits. For example, a keyboard buffer contains enough space to keep track of each key. Every single key has a “bit” which is either on or off – either the key is on or it is off – there is no in between.
A named section of memory is called a buffer. A buffer is a defined size, and contains related data. For example, a buffer may contain keying from a keyboard, a certain number of stops, expression values, etc. Most Opus-Two buffers are 8 bytes / 64 bits. That makes them the ideal size to hold a keyboard worth of keying (61 notes). Buffers can often be addressed by name (sw_k in a coupler statement is swell keying buffer, TI1 is the tab input buffer #1).
A byte is a group of 8 bits. Most bytes that need to be addressed in the config file are already named. For example, the first byte of Input Card #1 is “in1b1.” The first byte of expression value sent to the chamber is exprb1, etc.
A bit is a single piece of information that is either on or off. It can be a note on a keyboard, a stop control, a piston, etc. Anything that is either on or off is a bit. Bits come in groups of various sizes, 8 and 64 are the most common varieties (8 is a byte, 64 is a buffer). Large groups of things wired in logical order do not have to be defined at this specific of a level, but can be. Typically, a keyboard would be defined with one swoop (all 61 notes wired at a certain location in order). Generally, most things are done this way, but it doesn’t have to be so.
Many bits are pre-named for convenience. Every individual bit on input cards is pre-named (in1_12), as is every stop (stop_1), as is every reversable (dwr_1).
Hex And Decimal Byte Values
Each of the 8 bits that makes up a byte is assigned a numeric value for reference purposes. A byte can be written as “0×00″ where the “0x” indicates a Hex value (what we are talking about here) and the “00″ is the Hex value. The hex values of each bit are:
Bit 1 is 01
Bit 2 is 02
Bit 3 is 04
Bit 4 is 08
Bit 5 is 10
Bit 6 is 20
Bit 7 is 40
Bit 8 is 80
These values are additive. So if you are holding down low C and low C# of a keyboard, and no other notes, 0×01 is on, and 0×02 is on. That is displayed as 0×03 (01+02=03). This is 16 bit binary, which means that the acceptable values are: 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F. In other words, when you add together 08 and 04, that equals 0C.
There are not 2 different ways to make any value. Each value represents a unique combination of bits, so that 8 on-off statuses can be carried by only two characters.
So, to know which notes are held down on a keyboard, it is not necessary to send 61 separate messages, but simply 16 characters:
00 00 00 00 00 00 00 00
While each byte contains 8 bits, those do not have to be turned on and off in a particular order. In fact, by treating them as toggle points to turn on and off, there are 256 different combinations of on-off values.
With that in mind, a byte can also be expressed as a decimal value. Keep in mind that these numbers are “zero based,” which means the first value available is 0, not 1. An example of a decimal value would be0F in Hex is 15 in Dec. 2F in Hex is 47 in Decimal. FF is 255.
So if the byte is being used to represent individual bits, you have 8 individual statuses that are either on or off. If it is being used to represent a number, the 8 status are read as a group and interpreted to a numeric value.
The Jal language uses the “0x” to recognize a hex value. Otherwise it assumes a decimal value.
8 bits make a byte (literally low C on a keyboard to the low G are the first byte of that keyboard). A number of bytes make a buffer. And buffers make memory blocks.
Now that we know about different memory types, we need to learn what to do with them. Instructions provided to a computer of any type must be very specific. There should be no room for guessing – the computer will do exactly what it is told. Nothing more, and nothing less. One of the fundamental concepts in digital electronics is Boolean Logic.
There are three fundamental gates: Not, And, Or. A Not is simply an inversion – if the bit is on, it gets turned off. The And gate checks if both conditions are true, and the Or gate check to see if one of the two conditions are true.
Example 1 (AND):
if stop_1 & stop_2 then stop_3 = high else stop_3 = low end if
In this example, stop 3 will only turn on when stop 1 AND stop 2 are both on. As soon as either of them turn off, stop 3 will also turn off.
Example 2 (OR):
if stop_1 | stop_2 then stop_3 = high else stop_3 = low end if
In this example, stop 3 will turn on when either stop 1 or stop 2 are on (or both of them). Stop 3 will not turn off until both stop 1 and stop 2 are both off.
Example 3 (NOT):
if ! stop_1 then stop_3 = high else stop_3 = low end if
In this example, stop 3 will only turn on when stop 1 is off. As soon as stop 1 turns on, stop 3 will turn off. In this example, it is impossible for them both to be on at the same time.
Much like the name implies, comparison operators allow two numeric values to be compared within a logic structure. Comparison operators include the following:
Equal (note two == side by side):
if value1 == value2 then do_something end if
if value1 != value2 then do_something end if
if value1 > value2 then do_something end if
if value1 < value2 then do_soemthing end if
IF (boolean condition) THEN (consequent) ELSE (alternative) END IF
Conditional statements allow a flow of logic to determine which actions to take. Conditional statements can contain the following terms:
IF (gets things started by initiating the first logic check)
THEN (what to do if that logic check works out)
ELSE (in the event none of the requested logic checks were met, this is a “catchall”)
ELSIF (an abbreviated ‘ELSE IF’, which allows multiple checks to occur in one logic tree
END IF (terminates the logic tree)
It is important to know that each IF statement begins a logic tree, where as soon as the search for a true statement is found, the search ends. For example:
1) IF stop_1 THEN 2) stop_2 = on 3) ELSIF stop_2 THEN 4) stop_3 = on 5) END IF
This only works one way – if stop_1 is on, then stop_2 gets turned on. If the first condition of the logic tree is met, the other conditions are not even checked. The system blows right by them. The only way that line 3 is checked is if the logic check of line 1 was false (not true). For this example, conceptually it is often misunderstood that this logic tree will turn on both stop_2 and stop_3, but it will not.
In the event that it isn’t clear, IF statements call for a conditional statement (as explained above). The conditional statement can be quite simple, as in the example above. Line is is checking to see if that bit is enabled. If it is, it is satisfied (true), and follows the THEN statement. If it isn’t, it keeps looking for something that is true, testing line 3 next. If the bit is on, it is true. If you wanted to check for the bit to be OFF instead of on, it is as simple as including an exclamation point (logical NOT):
1) IF ! stop_1 THEN 2) stop_2 = on 3) END IF
A procedure is essentially a form of code-housekeeping. Suppose we have a long and complicated set of instructions we would like to follow several times over. For example, suppose we want to copy all the data from a certain input card to a keying buffer. While we could hand-code all the steps necessary to copy data from one buffer to another every time we want to do it, it is much simpler to create a procedure to do that. The procedure doesn’t necessarily have a mathematical result, but accomplishes a task.
So, the procedure…
card_in ( 1 , 0 )
… tells the system that there is at least one input card connected and that it should reach out to it and copy the data in the memory buffer reserved for Card#1 (the 0 after the comma refers to a second card chain that is not present on the C-I controller card).
Procedures can pass parameters in and out (or just in, or just out). The card_in procedure listed above is passing the value 1 into the procedure. This tells the procedure where to put the data it finds in the first card it encounters. That parameter is being passed in.
In this procedure…
direct_midi_expression ( 1 , exprb1 , midi_1_vol )
… The value of exprb1 (determined earlier in the file) is compared to midi_1_vol. If it is different, it is sent as a MIDI volume message on channel 1. Then midi_1_vol is changed to become the same as exprb1, so next time the message won’t be sent if the value hasn’t changed.
So, the first parameter/byte (static value of 1), is being passed in, and telling it which channel to use. The second parameter/byte (dynamic value of exprb1) is being passed in, so that the procedure is aware of the value of exprb1 on THIS pass. The third parameter/byte is being passed in AND passed back out at the end. This third parameter will always match the second one when the procedure is finished. It’s sole purpose in life is to keep track of what exprb1 was LAST pass, so that a MIDI volume message is only generated when there is a change.