This TS2068 program is a three-part music suite offering a tune composer, a live keyboard piano, and a random “Parakeet” sound generator. The tune composer (mode 1) parses a string of alternating letter/number pairs, mapping letters to BEEP pitches via ASCII code arithmetic and encoding accidentals with a K offset variable; it supports two octaves, lower-case for the upper octave and upper-case for the lower. The piano keyboard mode (mode 2) maps the top two rows of keys (Q–P plus number row) to 17 chromatic pitches using a 256-element lookup array indexed by character code, and displays a graphical keyboard built from block graphic characters. The Parakeet mode (mode 3) uses the TS2068-specific SOUND command with randomised AY register values to produce procedurally generated bird-like calls, looping via RUN rather than GO TO.
Program Analysis
Program Structure
The program opens with GO TO 1000 at line 5, jumping to a menu at lines 1000–1040. Three modes branch from there: the tune composer (lines 10–400), the live piano keyboard (lines 500–700), and the Parakeet synthesiser (lines 1500–1640). A SAVE statement at line 2000 archives the program. Line 580 (the target of GO SUB 580 at line 500) does not exist; execution falls through to line 590, which is the actual keyboard-drawing routine — a deliberate use of the non-existent-line technique to skip line 500’s own setup code on the return path.
Tune Composer (Lines 10–400)
The user enters a string such as C4E4G4 where every odd character is a note letter and every even character is a duration digit. Two parallel arrays, X(50) for durations and Y(50) for pitches, are filled by separate FOR loops stepping by 2 through the input string. Pitch is computed from the ASCII code of the letter with an offset and an accidental correction variable K.
| Octave | Case | Pitch formula |
|---|---|---|
| Upper | Lower-case (a–g, code ≥97) | (CODE N$(A)-87) + 2*K |
| Lower | Upper-case (A–G, code <97) | (CODE N$(A)-67-K)*2 |
The variable K acts as a semitone adjustment and is reset to 0 after each note. Lines 130–170 and 290–320 set K for notes that need a half-step shift (e.g. A, B in the lower octave; a, d, e, f, g in the upper), approximating a chromatic scale. Note that lines 150 and 160 both test for "e" and "f" respectively and both assign K=1, effectively treating them identically — the intent appears to be distinguishing E and F but the logic is consistent since natural E and F are a semitone apart and both get the same adjustment here.
Live Piano Keyboard (Lines 500–700)
Mode 2 constructs a 256-element array a() as a sparse lookup table mapping key character codes to scale degrees. The string n$="q2w3er5t6y7ui9o0p" encodes the standard piano keyboard layout across two rows, with number-row characters representing black keys interspersed between letter-row white keys. The mapping loop at line 530 stores the position index into a(CODE n$(j)+1), so a(i) is non-zero only for the 17 valid keys.
The polling loop at lines 550–570 reads INKEY$ continuously without any PAUSE, creating a tight busy-wait. When a valid key is detected, BEEP .5,a(i)-1 plays for half a second. A graphical two-octave keyboard is drawn using dense block-graphic strings (█ characters from zmakebas \:: escapes) with note names printed inside the keys.
Parakeet Synthesiser (Lines 1500–1640)
This mode exploits the TS2068’s AY-3-8912 sound chip via the SOUND keyword (rendered as } in the source encoding). Register 7 controls the noise/tone mixer; registers 8–10 set channel volumes; registers 11–13 set the envelope period and shape. The outer loop at line 1510 iterates over a random pitch range with a random step (which can be negative, producing a descending chirp), writing to registers 0, 11, 12, and 13 on each iteration to vary frequency and envelope.
After the main call, the program randomly pauses (line 1550) and then either loops back via RUN 1500 or enters a secondary noise-burst section (lines 1570–1640) that uses register 6 (noise frequency) and short empty FOR loops as software timing delays before resetting to silence and restarting. Using RUN 1500 rather than GO TO 1500 reinitialises variables on each cycle, ensuring fresh random seeds.
Notable Techniques
- Parallel array parsing: Two separate FOR/STEP 2 loops over the input string fill pitch and duration arrays independently, keeping the parsing logic simple.
- Sparse lookup array: The 256-element
a()array indexed directly by character code avoids any string searching in the real-time key polling loop. - PAUSE NOT PI: At line 400,
PAUSE NOT PIevaluates toPAUSE 0(sinceNOT PIis 0 in Sinclair BASIC), implementing an efficient wait-for-keypress idiom. - Software delay loops: Empty
FOR K=0 TO INT(RND*N): NEXT Kloops in the Parakeet section provide variable timing between SOUND commands without any PAUSE statement. - Menu via GO TO non-existent target:
GO SUB 580targets a non-existent line, causing execution to continue at line 590 — the first line of the keyboard-drawing subroutine.
Bugs and Anomalies
- Line 90 states the upper octave runs “from a to c” but lines 130–180 handle notes a through g, giving a full seven-note upper octave — the on-screen description is inaccurate.
- The arrays
X(50)andY(50)limit tunes to 50 notes; longer inputs will cause an index-out-of-range error with no bounds check. - Duration parsing at line 240 uses
VAL N$(T)which reads only the single digit at position T; multi-digit durations are not supported. - The variable
xfrom the initial speed prompt at line 10 is stored but never used in the tune playback logic —X(Z)/2at line 380 uses the arrayX, not the scalar speed variable. - Line 580 is referenced by
GO SUB 580but does not exist; line 590 is the first line of the subroutine. This is intentional but the missingRETURNat line 700 is replaced byRETURNwhich does exist, so the subroutine call works correctly.
Content
Source Code
5 GO TO 1000
10 INPUT "What speed? 1 is slow, 5 is fast";x
20 REM MUSIC PLAYER
30 DIM X(50): DIM Y(50)
40 LET K=0: LET L=1
50 BORDER 2: PAPER 4: INK 9: CLS
60 PRINT AT 0,10; INVERSE 1;"MUSIC PLAYER"''
70 PRINT "TYPE IN YOUR MUSIC AS A LINE OF LETTERS AND NUMBERS."
80 PRINT ''"PUT NOTES AS LETTERS FOLLOWED BY THEIR LENGTH IN BEATS"
90 PRINT ''"YOU HAVE TWO OCTAVES.THE LOWER ONE IS FROM A TO G AND THE UPPER FROM a TO c."
100 INPUT N$
110 FOR A=1 TO LEN N$ STEP 2
120 IF CODE N$(A)<97 THEN GO TO 290
130 IF N$(A)="a" THEN LET K=-0.5
140 IF N$(A)="d" THEN LET K=0.5
150 IF N$(A)="e" THEN LET K=1
160 IF N$(A)="f" THEN LET K=1
170 IF N$(A)="g" THEN LET K=1.5
180 LET Y(L)=(CODE N$(A)-87)+(2*K)
190 LET L=L+1
200 LET K=0
210 NEXT A
220 LET L=1
230 FOR T=2 TO LEN N$ STEP 2
240 LET X(L)=VAL N$(T)/2
250 LET L=L+1
260 NEXT T
270 GO TO 370
280 STOP
290 IF N$(A)="A" THEN LET K=-0.5
300 IF N$(A)="B" THEN LET K=-0.5
310 IF N$(A)="F" THEN LET K=0.5
320 IF N$(A)="G" THEN LET K=0.5
330 LET Y(L)=(CODE N$(A)-67-K)*2
340 LET K=0
350 LET L=L+1
360 GO TO 210
370 FOR Z=1 TO LEN N$/2
380 BEEP X(Z)/2,Y(Z)
390 NEXT Z
400 PRINT #1,"Press any key": PAUSE NOT PI: RUN
500 CLS : GO SUB 580
510 LET n$="q2w3er5t6y7ui9o0p"
520 DIM a(256)
530 FOR j=1 TO 17: LET a(CODE n$(j)+1)=j
540 NEXT j
550 LET i=CODE INKEY$+1
560 IF a(i) THEN BEEP .5,a(i)-1
570 GO TO 550
590 PRINT '" 2 3 5 6 7 9 0": PRINT
610 PRINT "\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::D#\::E#\::\::\::\::G#\::A#\::B#\::\::\::\::D#\::E#\::\::"
620 PRINT "\::\::\::\::__\::__\::\::\::\::__\::__\::__\::\::\::\::__\::__\::\::"
650 PRINT "\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::C \::D \::E \::F \::G \::A \::B \::C \::D \::E \::"
660 PRINT "\::\::__\::__\::__\::__\::__\::__\::__\::__\::__\::__\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::"
680 PRINT : PRINT " Q W E R T Y U I O P"
690 PRINT AT 15,2;"USE THE TOP TWO ROWS OF KEYS"
700 RETURN
1000 BORDER 7: PAPER 7: INK 9: CLS
1010 PRINT 'TAB 9;"Piano Simulations"''''" 1 Write a tune"''" 2 Play the Keys"''" 3 Parakeet"
1020 INPUT "Select 1, 2, 3, ";x: IF x=1 THEN GO TO 10
1030 IF x=2 THEN GO TO 500
1035 IF x=3 THEN GO TO 1500
1040 GO TO 1020
1500 SOUND 7,62;8,16;9,0;10,0
1510 FOR I=INT (RND*75)+25 TO INT (RND*75)+20 STEP INT (RND*4)*2-5
1520 SOUND 0,I;11,32;12,100;13,2
1530 NEXT I
1540 SOUND 12,1;13,0
1550 IF INT (RND*2)=1 THEN PAUSE INT ((RND*60)+1)
1560 IF INT (RND*4)<>1 THEN RUN 1500
1570 FOR J=RND*1 TO RND*10
1580 FOR K=0 TO INT (RND*4)
1590 NEXT K
1600 SOUND 6,16;7,7;8,16;9,16;10,16;11,300+INT (RND*10);12,INT (((RND*2)+.5)/2);13,INT (RND*2)*8
1610 FOR K=0 TO INT (RND*10)
1620 NEXT K
1630 SOUND 7,63;8,0;9,0;10,0
1640 NEXT j: RUN 1500
2000 SAVE "3 Musics" LINE 1
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
