Beethoven

Developer(s): Stephane Crainic
Date: 1983
Type: Program
Platform(s): TS 1000
Tags: Music

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:

  1. Lines 1–2 (REM blocks): Storage for two Z80 machine code routines embedded as raw bytes — the sound player and melody data table respectively.
  2. 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.
  3. Lines 10–99: Melody POKE and execution — encodes the melody hex string from A$ into RAM at address 16567 (immediately after the machine code), calls USR 16546 to play it, then STOPs.
  4. Lines 100–255: Documentation/UI — prints a title banner, usage instructions, and a note-to-hex reference table across two screens with keypress pauses.
  5. Lines 1000–1019: Example melodies — “Happy Birthday” (line 1005) and Beethoven’s 5th Symphony (line 1011), each setting A$ and jumping to line 20.
  6. 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):

HexMnemonicComment
7B 3D 20 FDLD A,E / DEC A / JR NZ,$-1Timing inner loop
C9RETReturn from delay subroutine
06 E0LD B,$E0Load outer loop counter
5ELD E,(HL)Load note frequency byte
AFXOR AClear accumulator
BBCP ECompare with frequency
28 0CJR Z,+12Skip output if note=00 (rest)
DB FEIN A,($FE)Read port (toggle for audio)
CD 82 40CALL $4082Call delay routine
D3 FFOUT ($FF),AOutput to MIC/EAR port
CD 82 40CALL $4082Call delay routine again
18 05JR +5Skip rest-handling
0E 05LD C,$05Rest duration constant
0D 20 FDDEC C / JR NZ,$-1Rest timing loop
10 E8DJNZ $-24Outer frequency loop
C9RETReturn from note player
21 B7 40LD HL,$40B7Point HL to melody data (16567)
CD 87 40CALL $4087Call note player for each byte
23INC HLAdvance to next note
7ELD A,(HL)Fetch next byte
FE FFCP $FFCheck for end-of-melody sentinel
20 F7JR NZ,$-7Loop until $FF terminator
C9RETReturn 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) and B$(2), with note names taken as fixed-length slices of C$.
  • 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 and OUT ($FF),A writes it back, toggling the audio bit without explicitly masking — relying on port state persistence for the toggle effect.

Content

Appears On

Related Products

Related Articles

Beethoven is a remarkable program which allows you to produce music from your T/S1000/ZX81. Stephane Crainic of Paris has linked...

Related Content

Image Gallery

Beethoven

Source Code

   1 REM 7B3D20FDC96E05EAFBB28CDBFECD8240D3FFCD8240185E5D20FD10E8C921B740CD8740237EFEFF20F7C92020
   2 REM B8B80B80A5A50B8B808B8B09494949400B8B80B80A5A50B8B808B8B0949494940B8B80B8060606073737308B8B8B094940A5A5A506B6B06B737309494083830949494FF83083836B6B9C9C83839494C3C307A83083836B6B9C9C838394947A7A07A8394A5B8B8C3C3B8B8FF1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B
  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.

Scroll to Top