The Musical Pilot
This article will open the door to string parsing, a powerful way to analyze PILOT strings. Along the way, we'll read and write on the disk/cassette, do some Boolean algebra, change data types and reveal a beautiful PILOT bug. And, oh yes, we'll play four-voice music.
As always, we'll be way "beyond the book." Since it will be getting pretty deep, I'll give page references to Atari's PILOT Primer.
A string is a combination of letters, numbers, symbols, words, etc., "strung together." In PILOT, a "string variable" is made by giving it a name (always beginning with "$") in an A:ccept or C:ompute instruction (pp. 69-76). The book tells how to concatenate ("grow") strings. We'll discuss how to parse ("cut") strings so you can analyze each part of a string. This could be useful for analyzing sentences, riddles, or in this case, for storing data for a program's use (PILOT lacks a "Data" statement). String parsing relies on the Match String command which produces three pre-named variables, $Left, $Match, and $Right (pp. 41-44, 81-82). Parsing programs work as follows (refer to the Pilot Player listing):
1. Place the string into the "accept buffer" (line 1270).
2. Match on the "separator." In this case, I used the blank as a separator. In line 1280, we skip over the initial blank, which the A:ccept instruction inserts in each string, and M:atch on the second blank. (Note the right arrow in the instruction which doesn't print in front of the "_").
3. Check for the end of string (the JN: in line 1290).
4. Store the remainder of the string (found in $Right) in a safe place (line 1300).
5. Use $LEFT as the parsed word, letter, etc. (lines 1310-1370).
6. Jump back to step 1.
Although this may seem complicated, it's conceptually as easy as BASIC.
To play a C,D,E,F chord for a sixteenth, the Pilot Composer produces a string looking like this: " 1 3 5 6 16 ! ". The first four values are the usual notes (pp. 106-107) for each of the ATARI's four voices. The "16" is the inverse duration of the note (1/16 of a note). The "!" is a "terminator" to tell us that we're out of notes. Our problem: parse it and play it. The *Loop2 routine (lines 1250-1390) cuts the string and sets up variables for each voice and for the PAUSE command. After each Match String, the variables look this way (the underlines represent blanks):
$PLAYVALUES $LEFT PASS BEFORE MATCH $LEFT $MATCH $RIGHT USED FOR 0 _1_3_5_6_16_ NULL NULL NULL I _1_3_5_6_16_ _1_ ___ _3_ 5_ 6_16_ #A 2 _3_5_6_16_ _3_ ___ _5_6_16_ #B 3 _5_6_16_ _5_ ___ _6_16 #C 4 _6_16_ _6_ ___ _16_ #D 5 _16_ _16_ ___ ___ #L 6 ___ _16_ ___ ___ NO MATCH
Simply put, each value marches to the left into the $LEFT bucket and then gets used. Notice that the "no match" in pass six did not change any of the special string variables.
The Pilot Composer parses strings in a similar fashion but on each letter. In this case, the match parsing instruc tion (line 1200) skips two spaces (the leading blank and the first letter) and M:atches on the next character to put all remaining characters in $MATCH (the comma does that). Once the string is split, a simple $LEFT inspection finds the character and then restores the balance of the string. The *TRANSLATE module (lines 1400-1690) performs a similar M:atch to find good notes and durations in $GOODNOTES and $GOODDURATION, and then to translate them into note and duration values. The transla tion lookup in $NOTEABLE is "fail safe" Ñ it first Matches on the note followed by "/" and then Matches on the subsequent ".". This forces the value (a 5, say) into $LEFT. This was required, since at M:atch for 1 or 8 without the "." would have found the value of notes C and G. Of course, I could have designed the string in reverse orderÑthat's an improvement for you.
Let's digress to the music before going on With the programrrting. The Pilot Music "System" now has two simple programs. Pilot Composer accepts four-note chords composed of the eight basic notes (no sharps or flats), followed by a duration (a whole note, half note, etc.). It checks these data, catches most errors, and rings a "bell" when it's ready for another chord. It won't find short chords, so make sure you enter four notes and a duration, or change the tTRANSLATE module between lines 1670 and 1680. Chords are written to the disk or cassette every 10 chords. This is required since the maximum length of an accept buffer is 254 characters.
The Pilot Player asks for a tempo (how fast to play) and a file of music. It then opens that file and plays the notes stored there.
Back to the Pilot Composer program. Under PILOT (pp. 73), strings are concatenated by naming two strings in a C:ompute (or A:ccept) instruction (e.g.: G:$0NE = $0NE $TWO). If, however, one of the strings is "undefined" because it has never been used before, it has the value of a text literal rather than the value of a string. In the example, if $TWO had the value JOHN but $0NE was undefined, the new value of $0NE would be $0NEJC)HNÑhardly what we wanted! I avoid this by initializing strings used in this way (see lines 130 & 140).
PILOT input and output (I/O) is handled-with READ:, WRITE: and CLOSE: instructions. Each instruction requires a "device name" (a "C:" for cassette or a "D:" for disk) and, for disk, a file name. These are separated from following data by a comma. The data can be text literals, numeric or string variables. In a single file, READ: must be separated from WRITE: by a CLOSE:. You can try this in immediate mode or in a program:
DISK
WRITE:D:TEST,ABCDCLOSE:D:TEST
READ:D:TEST,$STRING
T:$STRING
CASSETTE
WRITE:C: ,ABCDCLOSE:C:
READ:C:,$STRING
T: $STRING
We'll have more on I/O in a future article to discuss a hidden glitch. For now, just do as line 430 does and put all device specifications in a single string.
Keeping a clean screen in a program often requires erasing a line on the screen. It's not so simple in PELOT since the "blank line?' string automatically defaults to one character. Lines 750 and 1230 show an easy way; just print a series of blanks followed by a non-printing character such as an arrow. Line 750, for instance, prints the #A followed by a blank and a left arrow. When the line is printed, the right-most character is blanked out, and the left arrow holds the space, but doesn't show. You can type an arrow by hitting the ESC key then holding down the CTRL key while;hitting the desired arrow key. Repeat all three strokes for each arrow.
Although the Primer tells us that variables come in two flavorsÑ strings (pp. 69-&i) and numerics (pp. 85-92), we never find out how to change one into the other. It's simple but tricky. String variables can be made from numeric variables by C:omputing or A:ccepting them:
C:$0NE = #A
A:$0NE = #A
A string variable can be turried into a numeric variable ONLY by A:ccepting it:
A:#A - $0NE
After this instruction, #A witl have the numeric value from $0NE; non-numeric data will be disregarded (see the Player program, lines 1310-1350).
Line 1140 in the Player program presents a powerful way to combine "relational operators" to make "conditional statements" (pp. 89-90). Linking conditions with "+" signs creates "logical ors". For instance, line 1140 would be read, "if #T-256 OR if #T-128 OR if #T=64 then J:ump...". In other words, if #T equaled any one of the three numbers, the program would find a "true" and J:ump . Neat! But, you can't do it the other way, with a JN: instruction to execute on a "false," because the "N" looks at the M:atch register, not at the conditionals.
You can get "logical ands" by multiplying the conditionals:
T(#T = 100)*(#U = 200)*(#V = 50):ALL THREE
This statement would be read: "if #T = 100 AND if #U = 200 AND if #V = 50 then T:ype ALL THREE"
At last, the BUG. (A friend says that micros are too small to have bugs. She claims that they have fleas!) Right there on page 31 the Primer tells us that the computer "ignores" remarks. Although that may be accurate in the linguistic sense, it's not so in the operative sense. In line 1150 in the Composer program the remark set off by a "[" MUST be typed without spaces. It seems that the [ turns any intervening spaces into slgnificant space and, therefore, part of the accept buffer. Ditto for other commands. I don't know if it's a bug or a flea r- I know it's a bear to figure out! (Atari's internal manuals even have it wrong!) Be safe, don't use brackets when in doubt.
By Ken Harms
[CODE]