This program implements a dual-voice music player using the BEEP command, reading note data from two separate blocks of machine-code-stored data in RAM. Voice 1 data is loaded starting at address 50000 (V1) and Voice 2 at 50250 (V2); each note is encoded as two consecutive bytes — pitch value and duration counter respectively. The main loop decrements per-voice counters and fetches the next note via PEEK when the counter reaches zero, then plays both notes in rapid alternation with BEEP .015 to simulate simultaneity. A sentinel value of 255 signals end-of-tune, and a value ≥254 indicates a rest/pause for that voice, which is handled by substituting the other voice’s current note while preserving the original counter. Music data is stored separately as a CODE file named “DUO-MUSIC” loaded to address &H C350 (50000 decimal), spanning 495 bytes.
Program Structure
The program is divided into clearly commented sections, each introduced by a REM block:
- Lines 0–10: Variable initialisation.
V1andV2are set to the base addresses of the two note streams (50000 and 50250).A,A1,B,B1are zeroed. - Lines 20–70: Main playback loop. Primes both voices (line 20), then enters a tight loop (lines 30–70) that decrements counters, fetches new notes as needed, handles special values, plays both notes, and loops.
- Lines 100–120: Subroutine to fetch the next note for Voice 1 via
PEEK. - Lines 200–220: Equivalent subroutine for Voice 2.
- Lines 9000, 9999: Loader and saver lines for both the BASIC program and the music data CODE block.
Data Format
Each note in the data stream is encoded as two consecutive bytes stored in RAM:
| Byte offset | Variable | Meaning |
|---|---|---|
| +0 | A / B | Pitch value (MUSIcomp format; subtract 36 to get semitone offset for BEEP) |
| +1 | A1 / B1 | Duration counter (number of loop iterations to hold this note) |
Voice 1 occupies addresses 50000–50249 (250 bytes = 125 notes maximum). Voice 2 occupies 50250–50494 (245 bytes = ~122 notes). The total CODE block is 495 bytes.
Special Sentinel Values
255— End of music marker. Line 42 halts execution; lines 100 and 200 also guard against overrunning the buffer by returning early once this value is encountered.≥254— Rest/pause for a voice. Line 45 handles both voices resting simultaneously with aPAUSE 1. Lines 50 and 55 handle single-voice rests by substituting the other voice’s current pitch, soBEEPstill sounds but the resting voice’s counter continues to count down independently.
Dual-Voice Simulation Technique
True simultaneous dual-voice output is not possible with a single BEEP channel. The program approximates polyphony by playing two very short BEEP .015 calls (15 ms each) back-to-back at line 60. At 30 ms per combined pair, the rapid alternation creates the auditory illusion of two concurrent voices — a common technique on single-channel hardware.
The pitch calculation A-36 and B-36 offsets the MUSIcomp data format (which apparently stores semitones with a +36 bias) into the semitone range expected by the BEEP command relative to middle C.
Counter-Based Timing
Rather than encoding note duration directly in milliseconds, each note carries a loop-iteration counter in its second byte (A1 / B1). The counter is decremented once per main loop iteration (lines 30 and 35). Since each iteration plays two 15 ms BEEPs (plus BASIC overhead), the actual note duration is approximately A1 × 30 ms plus interpreter overhead. This keeps timing relative and consistent between voices.
Content
Source Code
0
1 REM DUO-VOICE (BEEP)
2 REM RESET 1987 BYTE POWER
3 REM WRITTEN BY K. BOISVERT
4
5 REM INITIALISE VARIABLES
6 LET A=0: LET A1=0: LET B=0: LET B1=0
10 LET V1=50000: LET V2=50250
19 REM GET FIRST NOTES
20 GO SUB 100: GO SUB 200
29 REM COUNTER FOR VOICE 1 IF 0 THEN GET NEXT NOTE
30 LET A1=A1-1: IF A1=0 THEN GO SUB 100
34 REM COUNTER FOR VOICE 2 IF 0 THEN GET NEXT NOTE
35 LET B1=B1-1: IF B1=0 THEN GO SUB 200
41 REM CHECK IF END OF MUSIC YOU MAY NEED TO CHANGE THIS FOR YOUR OWN WAY OF KNOWING WHEN MUSIC IS FINISHED
42 IF A=255 THEN STOP
44 REM CHECK IF BOTH A PAUSE
45 IF A>=254 AND B>=254 THEN PAUSE 1: GO TO 30
49 REM IF ONLY VOICE 1 IS A PAUSE THEN PLAY THE SAME NOTE AS VOICE 2 BUT STILL USING COUNTER FOR VOICE 1
50 IF A>=254 THEN LET A=B
54 REM IF ONLY VOICE 2 IS A PAUSE THEN PLAY THE SAME NOTE AS VOICE 2 BUT STILL USING COUNTER FOR VOICE 2
55 IF B>=254 THEN LET B=A
59 REM PLAY THE NOTES ONE RIGHT AFTER THE OTHER A-36 AND B-36 MEANS MUSIcomp DATA -36 (TO GET THE RIGHT NOTE, SEE SEE MUSIcomp V1.1) IF YOU PUT YOUR OWN DATA YOU WILL NOT NEED TO ALTER THE VALUES OF A OR B. IF YOU WANT AN OCTAVE HIGHER, JUST ADD [-/+] 12 TIMES THE # OF OCTAVES TO BE ADDED OR SUBSTRACTED. IE:IF YOU WANT 3 OCTAVES LOWER, SUBSTRACT 36.
60 BEEP .015,A-36: BEEP .015,B-36
69 REM COMPLETE LOOP
70 GO TO 30
99 REM IF VOICE FINISHED THEN DO NOT GET ANY MORE NOTES
100 IF A=255 THEN RETURN
109 REM GET NEXT NOTE OF VOICE1
110 LET A=PEEK V1: LET A1=PEEK (V1+1): LET V1=V1+2
120 RETURN
199 REM IF VOICE FINISHED THEN DO NOT GET ANY MORE NOTES
200 IF B=255 THEN RETURN
209 REM GET NEXT NOTE OF VOICE2
210 LET B=PEEK V2: LET B1=PEEK (V2+1): LET V2=V2+2
220 RETURN
8999 REM LOADER
9000 LOAD "DUO-MUSIC"CODE 5E4,495: RUN
9998 REM SAVE PROGRAM
9999 SAVE "DUO-VOICE" LINE 9000: SAVE "DUO-MUSIC"CODE 5E4,495
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
