MINUETZ is a music composition and dice-game program that generates or records musical scores based on dice rolls, implementing an algorithmic composition system reminiscent of Mozart’s Musikalisches Würfelspiel. The program uses extensive machine code routines called via RANDOMIZE USR at addresses such as 61518, 61998, 62276, and 62350, handling music playback, sorting, and display tasks. Numeric constants are stored as single-letter and two-letter variables (B=1, C=2, D=3, E=4, F=6, etc.) initialised at line 3 to save memory and speed up execution. The score display routine at lines 8530–8775 renders musical notation graphically using PLOT, DRAW, and CIRCLE commands, including note stems, flags, ledger lines, and accidentals. A manuscript save/print facility allows compositions to be saved as SCREEN$ files or dumped to a printer via LPRINT and COPY.
Program Analysis
Program Structure
The program is organised into several functional blocks accessed via GO TO and GO SUB:
- Initialisation (lines 1–4): Jumps to line 3, sets up all numeric constants and hardware addresses.
- Main menu and dice loop (lines 36–44): Offers three modes (gamble, predict, auto-compose), collects 16 dice rolls into array
M. - Animation and display (lines 60, 200–205): Shows a brief dice-roll animation via a DATA-driven subroutine at 9000 (not listed but called), then displays the dice record.
- Composition and playback (lines 207–254): Calls score-display and playback routines, handles volume/tempo input, adjusts playback speed by POKEing tempo and re-scaling note data.
- Score rendering (lines 8530–8775): Draws musical staves and individual notes graphically.
- Note-classification subroutine (lines 8660–8732): Classifies notes into rhythmic values using array
S. - Notehead stem/flag drawing (lines 8771–8775): Small subroutines for different note types.
- Support routines (lines 9800–9930): Playback repeat, OUT-based sound initialisation, dice-record display, staff-line drawing, treble-clef drawing, random data seeding.
- Save/load block (lines 9998–9999): Saves the BASIC program and a separate CODE block, then reloads on startup.
Constant-Variable Encoding
Line 3 encodes small integers into single- and two-letter variables to reduce token storage and speed arithmetic throughout the program. The mapping is:
| Variable | Value | Use |
|---|---|---|
B | 1 | Unity, loop steps, POKE offsets |
A | 0 | Zero, BORDER/PAPER black, AT row/col |
C | 2 | Small counts, array indices |
D | 3 | Coordinates, note calculations |
E | 4 | INK colour (cyan), coordinates |
F | 6 | Dice face count, coordinates |
VO | 2 (default) | Volume |
KY | 5/6 | Playback key transpose ratio |
O | 256 (set line 36) | Page size, GO TO targets |
Derived targets such as O-F (250), O-E (252), O-D (253) are used in GO TO statements, exploiting the constant O=256 to avoid embedding literal line numbers.
Machine Code Subroutines
Several pre-loaded machine code routines are invoked via RANDOMIZE USR and RANDOMIZE USR PL. Their entry points are stored as named variables:
| Variable | Address | Purpose |
|---|---|---|
DI | 61998 | Dice animation/display render |
SORT | 62276 | Sort note data |
RNDT | 62209 | Base of random dice-total table (16 bytes) |
DT | 61518 | Dice translation table base |
VT | 62161 | Voice/note data table base |
BV | 62291 | Playback voice bitmask POKE target |
PL | 62350 | Music playback engine |
RN | 62281 | Random/part seed POKE target |
LD | 62159 | Left-die value POKE address |
RD | 62160 (LD+1) | Right-die value POKE address |
The code block is stored separately, saved as "minuetz C" CODE at address 58930, length 3642 bytes, and reloaded at line 9999 on each run.
Algorithmic Composition Mode
When mode 3 (auto-compose) is selected, line 36 calls GO SUB 9930, which seeds the 16-entry dice-total table (RNDT) with random values using POKE RNDT+N, RND*10. In modes 1 and 2 the dice totals are entered manually or generated by RND and then POKEd into the same table, providing a uniform interface to the machine code playback engine regardless of input method.
Score Rendering (Lines 8530–8775)
The graphical score renderer iterates over three voices (V=1 TO 3), two groups (G=1 TO 2), and five columns (L=0 TO 3). For each note position it reads pitch data from VT, calls the note-classifier at 8660 to populate array S with rhythmic values (whole, half, quarter, eighth etc.), then uses CIRCLE, PLOT, and DRAW to render noteheads, stems, flags, ledger lines, and accidentals. The variable Z tracks whether a ledger line is needed above or below the staff.
Note Classification (Lines 8660–8732)
The subroutine at 8660 classifies a 7-element note array N into rhythmic values stored in S. A rest is detected when N(2)=0 and assigned S(n)=5. Repeated adjacent pitches trigger the tie/beam logic in subroutine 8730, which walks backwards through preceding notes incrementing their S values. A special case at 8666 detects a three-note repeated chord and assigns value 2 to positions 2, 4, and 6.
Sound Output
Playback uses direct hardware port writes via OUT R,… and OUT RO,… (ports 245 and 246) to address the sound chip, consistent with the AY-3-8912 interface on the TS2068. Volume is applied by adding VO to the channel level bytes. Tempo is controlled by POKEing address 62385 with 115+L*F (where L is the tempo 1–10 and F=6), giving values 121–175. The playback speed scaling at line 254 multiplies each 16-bit note-duration word in the range 61416–61517 by KY (the key ratio), storing results back as a little-endian word pair.
Keyboard Detection and Joystick Check
Line 4 checks PEEK 61416 <> 148 to detect whether the machine code data has been loaded correctly; if not, it halves KY as a fallback. The PAUSE VAL "4E4" at line 40 waits up to approximately 167 seconds for a keypress to register a dice throw.
Notable Techniques
VAL "number"in GO TO/GO SUB targets reduces token storage for long line numbers.- The treble-clef drawing at lines 9924–9925 uses a sequence of arc-DRAW commands (with non-zero third parameters for circular arcs) to approximate the clef shape.
- Staff lines are drawn in a loop at 9923 using horizontal DRAW from x=0 to x=255 (
O-B), iterating with a STEP ofF(6 pixels) for line spacing. DIM M(16)is used to store the sum of each pair of dice rolls; after the loop it is re-dimensioned toDIM M(1)at line 207 to free memory before composition display.- The manuscript print path at line 226 uses
LPRINT,COPY, and a secondLPRINTto title and hard-copy the screen, while the save path at line 224 jumps toGO TO C(line 2), which executesSAVE A$ SCREEN$. - Line 205 scrolls 22 lines using
POKE 23692,-B(setting the scroll counter to 255) to suppress the “scroll?” prompt during screen clearing.
Bugs and Anomalies
- Line 36 references variable
e(lowercase) inAT F+E+E,e, but onlyE(uppercase) is defined. In Sinclair BASIC variable names are case-insensitive for numeric variables, so this resolves correctly to 4. - Line 9930 contains
RANDOMIZE L*0, which always randomizes with seed 0 regardless ofL. This appears to be a typo forRANDOMIZE L*O(capital letter O = 256), mirroring the idiom used at line 36. - Line 8570 uses lowercase
v,y1in the condition, mixed with uppercase elsewhere; these resolve to the same variables in Sinclair BASIC. - Subroutine 9005 (the dice-face drawing routine called from line 60) references variable
swhich is not initialised in that scope — it is the loop variable from the enclosing FOR at line 60, relying on the caller having set it correctly.
Content
Source Code
1 GO TO VAL "3"
2 SAVE A$ SCREEN$ : GO TO 230
3 LET B=VAL "1": LET A=B-B: LET C=B+B: LET D=C+B:: LET E=D+B: LET F=E+C: LET VO=C: LET KY=(E+B)/F: LET R=VAL "245": LET RO=VAL "246": LET LD=VAL "62159": LET RD=LD+B: LET DT=VAL "61518": LET DI=VAL "61998": LET VT=VAL "62161": LET BV=VAL "62291": LET RN=VAL "62281": LET SORT=VAL "62276": LET RNDT=VAL "62209"
4 LET PL=VAL "62350": IF PEEK 61416<>148 THEN LET KY=B/KY
36 LET O=VAL "256": LET L=RND*O: RANDOMIZE L*O: LET KY=B/KY: BORDER A: PAPER A: INK E+D: CLS : LET AU=A: LET CO=A: POKE VAL "23658",F+F: PLOT E*F,40: DRAW A,120: DRAW 220,A: DRAW A,-120: DRAW -220,A: OVER B: PRINT AT D,E;"DO YOU WISH TO :";AT F,E;" 1. GAMBLE WITH THE DICE ? or,";AT F+E,E;" 2. PREDICT THE FALL OF THE DICE ? or";AT F+E+E,e;" 3. COMPOSE AUTOMATICALLY?": INPUT " ENTER 1,2 OR 3 ";A$: IF A$="3" THEN LET AU=B: GO SUB 9930: GO TO 207
38 OVER A: IF A$="2" THEN LET CO=B
40 INVERSE A: DIM M(16): FOR Q=A TO 15: PRINT AT 21,A;"THROW [ ENTER ]": PAUSE VAL "4E4"
41 LET M=INT (RND*F)+B: RANDOMIZE RND*5E4: LET L=INT (RND*F)+B
42 IF co THEN INPUT "No. ON LEFT DICE ?";M: IF M<B OR M>F THEN GO TO 42
43 IF CO THEN INPUT "No. ON RIGHT DICE ?";L: IF L<B OR L>F THEN GO TO 43
44 POKE LD,M: POKE RD,L: POKE RNDT+Q,L+M-C: LET M(Q+B)=L+M
50 DATA 235,160,180,165,170,125,217,110
60 CLS : RESTORE 50: FOR s=10 TO -17 STEP -27: FOR n=B TO C: READ x,y: GO SUB 9000: NEXT n: CLS : NEXT s
200 PRINT AT A,A;: RANDOMIZE USR DI: PLOT E,171: DRAW 22,A: DRAW A,-14: DRAW -22,A: DRAW A,14: PRINT AT B,B; INVERSE B;Q+B: INVERSE A: NEXT Q: INPUT "'ENTER' TO CONTINUE ";A$
205 FOR n=B TO 22: RANDOMIZE USR 2361: POKE 23692,-B: PRINT " ": NEXT n
207 GO SUB VAL "9890": DIM M(1): IF AU THEN GO TO O-F
210 LET P1=b: INPUT "Manuscript ?(Y/N)";a$: IF A$<>"Y" THEN GO TO O-F
220 POKE RN,209: GO SUB VAL "9916": PRINT AT A,A;"PART ";P1: POKE RN,209+(P1-B)*8: GO SUB 8500: INPUT " SAVE manuscript ?(Y/N)";A$: IF A$<>"Y" THEN GO TO 230
222 INPUT "TITLE ? ";A$: IF LEN A$>F+E THEN LET A$=A$( TO F+E)
223 INPUT "Save SCREEN$ ? or Copy to PRINTER ? (S/P) ";F$: IF F$="P" THEN GO TO 225
224 IF F$="S" THEN GO TO C: GO TO 223
226 LPRINT A$: LPRINT : COPY : LPRINT
230 LET P1=P1+B: IF P1=C THEN GO TO 220
252 INPUT "ENTER VOLUME(1-10) ";VO: IF VO>F+4 THEN GO TO O-E
253 INPUT "TEMPO (1-10) ";L: IF L<B OR L>F+4 THEN GO TO O-D
254 POKE VAL "62385",115+L*F: INPUT "'ENTER' TO CONTINUE ";A$: FOR N=VAL "61416" TO VAL "61517" STEP C: LET L=(PEEK N+O*PEEK (N+B))*KY: POKE N,L-(INT (L/O)*O): POKE N+B,INT (L/O): NEXT N: GO SUB VAL "9820": CLS : GO TO F
8530 LET Z=A: POKE BV,240: DIM N(7): FOR V=B TO D: RANDOMIZE USR SORT: LET TS=VT: LET YS=CODE "P": FOR G=B TO C: FOR L=A TO D
8540 DIM A(7): FOR T=C TO F+B: LET N(T)=PEEK TS: LET TS=TS+B: IF N(T)>127 THEN LET A(T)=B: LET N(T)=N(T)-128
8548 NEXT T: GO SUB 8660
8550 LET P=18+L*60: FOR T=C TO F+B
8560 LET U=B: IF NOT S(T) THEN GO TO 8630
8570 LET X=P+(T-C)*10: LET Y1=(N(T)-B)*D: IF (v=D AND y1>14) OR (V=B AND Y1>68) OR (V=C AND Y1>50) THEN LET U=-B
8575 IF NOT Y1 OR Y1=F OR Y1>72 AND V=B AND INT (Y1/F)=Y1/F THEN LET Z=B
8585 LET Y=YS+Y1: IF S(T)<>5 THEN GO TO 8610
8590 IF V=C THEN GO TO 8630
8600 LET Y=YS+32: IF V=B THEN LET Y=Y+32
8605 CIRCLE X,Y,B: GO SUB 8775: GO TO 8630
8620 CIRCLE X,Y,B: CIRCLE X,Y,C: PLOT X+C*U,Y: DRAW A,11*U: GO SUB 8770+S(T)
8625 IF Z THEN PLOT X-E,Y: DRAW 8,A: IF V=Z THEN PLOT X-E,Y-F: DRAW 8,A
8626 IF z AND v=D THEN PLOT x-E,y+F: DRAW F,A
8627 IF A(T) THEN PLOT X-8,Y: DRAW F,A: PLOT X-8,Y+C: DRAW F,A: PLOT X-7,Y-C: DRAW A,F: PLOT X-E,Y-C: DRAW A,F
8630 LET Z=A: NEXT T: NEXT L: LET YS=C: NEXT G: POKE BV,240+V: NEXT V: RETURN
8660 DIM S(7): IF NOT N(C) THEN GO TO 8670
8665 LET Q=A: FOR N=C TO F: IF N(N)<>N(N+B) THEN LET Q=B
8666 NEXT N: IF NOT Q THEN LET S(C)=C: LET S(E)=C: LET S(F)=C: RETURN
8670 FOR N=C TO 7: IF NOT n(n) THEN LET s(n)=5: LET n=n+B: GO TO 8710
8690 IF n(n)=n(n-B) THEN GO SUB 8730: GO TO 8710
8700 LET s(n)=B
8710 NEXT n: RETURN
8730 FOR Q=B TO D: IF N(N-Q-B)<>N(N-Q) THEN LET S(N-Q)=S(N-Q)+B: RETURN
8732 NEXT Q: LET S(N-E)=S(N-E)+B: RETURN
8771 DRAW D,-E*U: DRAW -D,E*U: DRAW A,-E: DRAW D,-E*U: RETURN
8772 DRAW D,-E*U: RETURN
8773 DRAW D,-E*U: CIRCLE X+E,Y,B: RETURN
8774 RETURN
8775 DRAW 5,C,PI: DRAW -D,-8: RETURN
9005 PLOT X,Y: DRAW s,s*D/E: DRAW s,-s/C: DRAW -s,-s: DRAW -s,s*D/E: DRAW A,-s: DRAW s,-s*D/E: DRAW OVER B;A,s: DRAW A,-s: DRAW s,s: DRAW A,s: RETURN
9800 POKE VAL "62291",240: INPUT "Repeat ?",a$: IF A$<>"Y" THEN RETURN
9810 INPUT "ENTER VOLUME(1-10) ";VO: IF VO>F+4 THEN GO TO 9810
9815 INPUT "TEMPO(1-10) ";L: IF L<B OR L>F+4 THEN GO TO 9815
9816 POKE VAL "62385",115+L*F
9830 POKE VAL "62291",240: FOR N=A TO E+B: OUT R,N: OUT RO,A: NEXT N: OUT R,E+E: OUT RO,E+VO: OUT R,F+D: OUT RO,E+VO: OUT R,F+E: OUT RO,E+VO: OUT R,F+B: OUT RO,248
9835 RANDOMIZE USR PL: OUT R,F+B: OUT RO,-B: GO TO VAL "9800"
9890 LET p=DT-11: FOR n=A TO 15: LET p=p+11: POKE RNDT+N,PEEK (P+PEEK (RNDT+N)): NEXT N: IF AU THEN RETURN
9900 CLS : PRINT AT C,F+E;"Dice Record": PLOT A,145: DRAW O-B,A: DRAW A,-70: DRAW -O+B,A: DRAW A,70: PLOT 60,75: DRAW A,70: OVER B: FOR K=A TO B: PRINT AT F+K*E,B;"PART ";K+B;: FOR N=B TO 8: PRINT TAB 5+N*D;M(K*8+N);: NEXT n: NEXT K: OVER A: RETURN
9916 BORDER D+E: PAPER D+E: INK F+D: CLS : LET Q=9923: LET L=14: GO SUB Q: LET L=N+F: GO SUB Q: LET L=N+C*F: GO SUB Q: LET L=N+F: GO SUB Q: LET L=44: GO SUB Q+B: LET L=122: GO SUB Q+B: LET M=F*F: PLOT C,M:: GO SUB Q+C: LET M=114: PLOT C,M: GO SUB Q+C
9917 FOR N=O-B TO 45 STEP -61: PLOT N,14: DRAW A,60: PLOT N,92: DRAW A,60: NEXT N: PLOT O-E,14: DRAW A,60: LET L=O-F: CIRCLE L,23,B: CIRCLE L,29,B: CIRCLE L,59,B: CIRCLE L,65,B: RETURN
9923 FOR N=L TO L+E*F STEP F: PLOT A,N: DRAW O-B,A: NEXT N: RETURN
9924 PLOT E,L: DRAW E,A,C: DRAW -D,32,-.1: DRAW E,A,-C: DRAW -9,-F*D,-.6: DRAW F+F,A,3.8: DRAW -F-C,-E,D: CIRCLE E,L,B: RETURN
9925 DRAW F,A,-C: DRAW -F,-C*F,-1.2: CIRCLE C,M-B,B: CIRCLE 11,M,B: CIRCLE 11,M-F,B: RETURN
9930 LET L=RND*O: RANDOMIZE L*0: FOR N=A TO 15: POKE RNDT+N,RND*10: NEXT N: RETURN
9998 SAVE "MINUETZ" LINE 9999: BEEP .4,15: SAVE "minuetz C"CODE 58930,3642: STOP
9999 PRINT AT 10,1;"Leave recorder running"," MINUETZ data now loading": LOAD "minuetz C"CODE : GO TO 1
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
