The Basics of Beeper Sound

Many simple Z80 systems (like the ZX Spectrum) lack a dedicated sound chip and rely on a simple speaker connected to an I/O port. This speaker is known as the beeper.

How Tones are Made: A tone is generated by rapidly toggling a single bit in the output port (switching the speaker ON and OFF) at a specific frequency.

Frequency & Pitch: The speed of the toggling loop determines the tone’s frequency (pitch).

  • A faster loop = a higher pitch.
  • A slower loop = a lower pitch.

The I/O Port and Toggling

We use the OUT instruction to write to the beeper port. We only care about the single bit that controls the speaker.

Generic Beeper Port Example: Assume the beeper is controlled by Bit 4 of I/O port FEH.

BEEPER_PORT EQU 0FEH
SPEAKER_ON  EQU 10H         ; 00010000b (Bit 4 set)
SPEAKER_OFF EQU 00H         ; 00000000b (Bit 4 clear)

TONE_LOOP:
    ; 1. Turn Speaker ON
    LD   A, SPEAKER_ON
    OUT  (BEEPER_PORT), A

    ; 2. Delay for half a cycle (sets the frequency)
    CALL DELAY_ROUTINE

    ; 3. Turn Speaker OFF
    LD   A, SPEAKER_OFF
    OUT  (BEEPER_PORT), A

    ; 4. Delay for the other half cycle
    CALL DELAY_ROUTINE
    
    ; 5. Loop this entire block repeatedly
    JP   TONE_LOOP

Creating the DELAY_ROUTINE

The DELAY_ROUTINE is critical. Its total T-state count determines the pitch of the tone. You must calculate the T-states of the instructions in the routine to achieve a precise musical frequency (e.g., 440 Hz for A4).

Delay Routine Structure:

DELAY_ROUTINE:
    LD   BC, 500         ; Load a counter value (must be calculated for T-states)
DELAY_LOOP:
    DEC  BC              ; Decrements the 16-bit BC pair (6 cycles)
    LD   A, B            ; Check if BC has reached zero
    OR   C               ; (A OR C) sets Z if both B and C are zero
    JP   NZ, DELAY_LOOP  ; Loop if NOT Zero (BC is not zero)
    RET

Creating Melody: To play a melody, you simply change the value loaded into `BC′ between notes and use an outer loop to control the note’s duration.