--- title: "Duo-Voice (Beep)" id: 63120 type: "computer_media" slug: "duo-voice-beep" url: "http://localhost/computer_media/duo-voice-beep/" markdown_url: "http://localhost/computer_media/duo-voice-beep.md" published_at: "2026-01-26T12:12:00+00:00" modified_at: "2026-03-30T21:41:46+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2026/01/duo-voice.png" excerpt: "A clever two-voice BEEP music engine that rapidly alternates between voices stored as raw byte data in RAM, simulating polyphony with sentinel values for rests and end-of-tune." category: - name: "Archived Media" slug: "archived-media" taxonomy: "category" url: "http://localhost/category/archived-media/" post_tag: - name: "Byte Power" slug: "byte-power" taxonomy: "post_tag" url: "http://localhost/tag/byte-power/" - name: "Downloadable" slug: "downloadable" taxonomy: "post_tag" url: "http://localhost/tag/downloadable/" - name: "TS 2068" slug: "ts2068" taxonomy: "post_tag" url: "http://localhost/tag/ts2068/" model: - name: "Timex/Sinclair 2068" slug: "ts-2068" taxonomy: "model" url: "http://localhost/model/ts-2068/" indiv: - name: "Kristian Boisvert" slug: "boisvert-kristian" taxonomy: "indiv" url: "http://localhost/indiv/boisvert-kristian/" genre: - name: "Sound" slug: "sound" taxonomy: "genre" url: "http://localhost/type/sound/" media_contents: - id: 63049 title: "Byte Power Spring 1987" type: "computer_media" url: "http://localhost/computer_media/byte-power-spring-1987/" media_type: "Program" programmers: - name: "Kristian Boisvert" slug: "boisvert-kristian" taxonomy: "indiv" url: "http://localhost/indiv/boisvert-kristian/" mediadate: "1987" producer_company: - id: 25181 title: "Byte Power" type: "company" url: "http://localhost/company/byte-power/" images: - url: "http://localhost/wp-content/uploads/2026/01/duo-voice.png" article_media: - id: 63067 title: "Better Programming!" type: "article" url: "http://localhost/article/better-programming-5/" media_type_tags: "Sound" --- # Duo-Voice (Beep) 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: 1. **Lines 0–10:** Variable initialisation. `V1` and `V2` are set to the base addresses of the two note streams (50000 and 50250). `A`, `A1`, `B`, `B1` are zeroed. 2. **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. 3. **Lines 100–120:** Subroutine to fetch the next note for Voice 1 via `PEEK`. 4. **Lines 200–220:** Equivalent subroutine for Voice 2. 5. **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 a `PAUSE 1`. Lines 50 and 55 handle single-voice rests by substituting the other voice’s current pitch, so `BEEP` still 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. ## 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 ```