This ZX81 program plays musical melodies using machine code routines that output tones through the ZX81’s EAR/MIC port. The program includes a self-installing machine code loader: lines 9990–9996 POKE a Z80 routine into RAM starting at address 16514, which handles the sound generation, and lines 10–60 POKE melody note data (encoded as hexadecimal pairs in A$) into addresses starting at 16567. Notes are represented as hexadecimal byte values, with “00” producing silence/intervals, and two example melodies are provided — “Happy Birthday” and the 5th Symphony — encoded in lines 1000 and 1010 respectively. A reference table at lines 160–210 maps note names (C through B across two octaves) to their corresponding two-digit hex codes, making it straightforward for users to compose new melodies. The hex strings in A$ are decoded by subtracting 28 from the CODE of each ASCII character to recover nibble values, exploiting the ZX81’s character set offset.
Program Structure
The program is divided into several functional blocks:
- Lines 1–2 (REM blocks): Storage for two Z80 machine code routines embedded as raw bytes — the sound player and melody data table respectively.
- Lines 9990–9998: Bootstrap loader — POKEs the primary sound-driver machine code (from hex string in
A$) into RAM starting at address 16514, then returns to SLOW mode and clears the screen before falling into the main program. - Lines 10–99: Melody POKE and execution — encodes the melody hex string from
A$into RAM at address 16567 (immediately after the machine code), callsUSR 16546to play it, then STOPs. - Lines 100–255: Documentation/UI — prints a title banner, usage instructions, and a note-to-hex reference table across two screens with keypress pauses.
- Lines 1000–1019: Example melodies — “Happy Birthday” (line 1005) and Beethoven’s 5th Symphony (line 1011), each setting
A$and jumping to line 20. - Line 9989: STOP sentinel between example melodies and the bootstrap.
Machine Code Analysis
The bootstrap at line 9990 installs the following Z80 code at address 16514 (0x4082):
| Hex | Mnemonic | Comment |
|---|---|---|
| 7B 3D 20 FD | LD A,E / DEC A / JR NZ,$-1 | Timing inner loop |
| C9 | RET | Return from delay subroutine |
| 06 E0 | LD B,$E0 | Load outer loop counter |
| 5E | LD E,(HL) | Load note frequency byte |
| AF | XOR A | Clear accumulator |
| BB | CP E | Compare with frequency |
| 28 0C | JR Z,+12 | Skip output if note=00 (rest) |
| DB FE | IN A,($FE) | Read port (toggle for audio) |
| CD 82 40 | CALL $4082 | Call delay routine |
| D3 FF | OUT ($FF),A | Output to MIC/EAR port |
| CD 82 40 | CALL $4082 | Call delay routine again |
| 18 05 | JR +5 | Skip rest-handling |
| 0E 05 | LD C,$05 | Rest duration constant |
| 0D 20 FD | DEC C / JR NZ,$-1 | Rest timing loop |
| 10 E8 | DJNZ $-24 | Outer frequency loop |
| C9 | RET | Return from note player |
| 21 B7 40 | LD HL,$40B7 | Point HL to melody data (16567) |
| CD 87 40 | CALL $4087 | Call note player for each byte |
| 23 | INC HL | Advance to next note |
| 7E | LD A,(HL) | Fetch next byte |
| FE FF | CP $FF | Check for end-of-melody sentinel |
| 20 F7 | JR NZ,$-7 | Loop until $FF terminator |
| C9 | RET | Return to BASIC |
The entry point called by USR 16546 (0x40A2) is the sequence beginning LD HL,$40B7. Tone generation works by toggling the ZX81’s port with IN/OUT instructions and using the note’s byte value as a delay parameter in the inner loop, producing a square wave. A value of $FF acts as an end-of-melody sentinel in the note data.
Hex Encoding Scheme
Both the machine code installer and the melody POKE routines use the same decode idiom at lines 40/9993:
POKE O,(CODE A$(I)-28)*16+CODE A$(I+1)-28
The ZX81 character set places digits ‘0’–’9′ at codes 28–37 and letters ‘A’–’F’ at codes 38–43 (after the digit block), so subtracting 28 converts each hex nibble character to its numeric value 0–15. Multiplying the high nibble by 16 and adding the low nibble reconstructs the full byte. This avoids any need for a VAL or string-to-number conversion routine and is compact in BASIC.
Notable Techniques
- Self-modifying bootstrap: The program installs its own machine code at runtime from a hex string in a REM line (lines 1–2 store pre-installed data) and via the 9990-block loader, making the listing self-contained.
- Dual-purpose REM lines: Lines 1 and 2 hold pre-POKEd machine code and melody data respectively, directly in the BASIC program area, used as data storage rather than comments.
- Melody composition table: Lines 160–210 print a two-octave note-to-hex reference table built from parallel strings
B$(1)andB$(2), with note names taken as fixed-length slices ofC$. - Frequency encoding: Each note byte encodes pitch via the delay loop count — higher byte values produce lower frequencies (longer delays), consistent with direct period rather than frequency encoding.
- Port I/O for audio:
IN A,($FE)reads the current port state andOUT ($FF),Awrites it back, toggling the audio bit without explicitly masking — relying on port state persistence for the toggle effect.
Content
Source Code
1 REM 7B3D20FDC9 6E05EAFBB28 CDBFECD8240D3FFCD824018 5 E 5 D20FD10E8C921B740CD8740237EFEFF20F7C92020
2 REM B8B8 0B8 0A5A5 0B8B8 08B8B 094949494 0 0B8B8 0B8 0A5A5 0B8B8 08B8B 094949494 0B8B8 0B8 0606060737373 08B8B8B 09494 0A5A5A5 06B6B 06B7373 09494 08383 0949494FF83 083836B6B9C9C83839494C3C3 07A83 083836B6B9C9C838394947A7A 07A8394A5B8B8C3C3B8B8FF1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B
10 LET A$="... "
20 LET A$=A$+"FFF"
25 LET O=16567
30 FOR I=1 TO LEN A$-1 STEP 2
40 POKE O,(CODE A$(I)-28)*16+CODE A$(I+1)-28
50 LET O=O+1
60 NEXT I
70 FAST
80 LET K=USR 16546
99 STOP
100 SAVE "BEETHOVE%N"
110 PRINT "********************************************BEETHOVEN*******************************************"
120 PRINT ,,"PROGRAMME CREATED BY S. CRAINIC FOR THE SINCLAIR ZX81 +16K"
130 PRINT ,,"EACH SOUND CORRESPONDS TO AN HE-XADECIMAL VALUE. IN ORDER TO OB-TAIN THE DESIRED MELODY,YOU MUSTENTER SUCCESSIVELY IN LINE 10 (LET A$=""... ..."") THE HEXADECI-MAL CODE OF EACH SOUND IN THEIR ORDER IN THE MELODY."
140 PRINT " THE SOUNDS HAVE EQUAL LENGTHS. A LONGER DURATION OF A GIVEN SOUND COULD BE OBTAINED BY ENTE-RING MANY TIMES SUCCESSIVELY THECODE CORRESPONDING TO THE SAME SOUND. INTERVALS ARE OBTAINED BYENTERING THE ""00"" VALUE."
145 PRINT AT 21,0;"HIT A KEY TO CONT."
150 IF INKEY$="" THEN GOTO 150
155 CLS
160 PRINT " NOTE 1ST SCALE 2ND SCALE--------------------------------"
165 DIM B$(2,24)
170 LET B$(1)="F0E5D8CFC3B8ADA59C948BA3"
175 LET B$(2)="7A736B67605A56514B484440"
180 LET C$="C C+;D-D D+;E-E F F+;G-G G+;A-A A+;B-B "
190 FOR I=1 TO 12
200 PRINT C$(I*5-4 TO I*5);TAB 11;B$(1,I*2-1 TO I*2);TAB 24;B$(2,I*2-1 TO I*2)
210 NEXT I
220 PRINT ,,"AS EXAMPLES, SOME MELODIES WERE CODED IN LINES 1000... IF YOU WANT TO ""PLAY"" ONE OF THEM, EN- TER %R%U%N% %1%0%0%0, OR 1010... %>%M%A%K%E% %T%H%E% %T%O%N%E% %O%F% %Y%O%U%R% %T%V% %S%E%T% % % %L%O%U%D%E%R% AND% %N%E%W%L%I%N%E%."
230 PRINT AT 21,0;" HIT A KEY TO CONT "
240 IF INKEY$="" THEN GOTO 240
245 CLS
247 PRINT "EXCUSE ME, I""M WORKING..."
248 FOR I=1 TO 30
249 NEXT I
250 FAST
255 RUN 9990
1000 REM %H%A%P%P%Y% %B%I%R%T%H%D%A%Y
1005 LET A$="B8B800B800A5A500B8B8008B8B00949494940000B8B800B800A5A500B8B8008B8B009494949400B8B800B800606060737373008B8B8B00949400A5A5A5006B6B006B737300949400838300949494"
1009 GOTO 20
1010 REM %4%0%T%H% %S%Y%M%P%H%O%N%Y
1011 LET A$="B8C300C3C300B8C300C3C300B8C300C3C37A7A7A007A8394940094A5B8B800B8C3D8D800D8D8D800C3D800D8D8C3D800D8D800C3D800D8D88383830083949C9C009CB8C3C300C3D8F0F000F0F0007A830083836B6B9C9C83839494C3C3007A830083836B6B9C9C838394947A7A007A8394A5B8B8C3C3B8B8"
1019 GOTO 20
9989 STOP
9990 LET A$="7B3D20FDC906E05EAFBB280CDBFECD8240D3FFCD824018050E050D20FD10E8C921B740CD8740237EFEFF20F7C9"
9991 LET O=16514
9992 FOR I=1 TO LEN A$-1 STEP 2
9993 POKE O,(CODE A$(I)-28)*16+CODE A$(I+1)-28
9994 LET O=O+1
9996 NEXT I
9997 SLOW
9998 CLS
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.

