This program plays a two-voice arrangement of Robert Schumann’s “Evening Song” using SOUND. Musical data for both voices is stored as machine code blocks in memory starting at addresses 60000 (V1) and 60112 (V2); each note is encoded as three consecutive bytes: pitch register low byte, pitch register high byte, and duration. The main loop uses the SOUND statement to drive two independent tone channels (channels 0/1 for voice 1 and channels 2/3 for voice 2) with separate volume channels (8 and 9), implementing polyphony. A brief volume dip (to level 13) between notes, rather than silencing completely, is used to articulate note boundaries while keeping transitions smooth. The program is described as MUSIcomp-compatible and is part of a series by E & K Boisvert, published by Byte Power in 1986.
Program Structure
The program is organized into a tight main loop with two subroutines and a save/load block at the end. The flow is:
- Lines 10–15: Initialize voice pointers
v1andv2, fetch the first note from each voice via subroutines. - Lines 35–70: Main playback loop — check for end sentinel, set volumes, issue
SOUNDcommand, pause for tempo, decrement durations, fetch next notes when duration expires, then loop. - Lines 100–110: Subroutine for voice 1 — reads three bytes from
v1, advances the pointer by 3, then briefly lowers channel 8 volume to 13. - Lines 200–210: Subroutine for voice 2 — same pattern for
v2and channel 9. - Lines 9000/9999: Loader and saver lines — load the music data block then
RUN, or save both the BASIC and code files.
Music Data Format
Each note is stored as three consecutive bytes in a raw machine code block:
| Offset | Variable | Meaning |
|---|---|---|
| +0 | a1 / b1 | AY tone register low byte (pitch LSB) |
| +1 | a2 / b2 | AY tone register high byte (pitch MSB) |
| +2 | a3 / b3 | Duration (countdown in main-loop ticks) |
The sentinel value 255 in the first byte of a note signals end-of-music, while 254 signals a rest (volume is set to 0 for that tick). This encoding is noted as MUSIcomp-compatible, meaning data blocks can be shared with that suite.
SOUND Chip Usage
The single SOUND statement at line 50 programs multiple AY-3-8912 registers in one call:
- Register 8 — amplitude (volume) for channel A (
a4, voice 1) - Register 9 — amplitude for channel B (
b4, voice 2) - Register 7 — mixer control, value
60(binary00111100) enables tone on channels A and B, disables noise - Registers 0/1 — tone period low/high for channel A (
a1,a2) - Registers 2/3 — tone period low/high for channel B (
b1,b2)
This batches seven register writes into a single BASIC statement, minimizing overhead in the timing loop.
Notable Techniques
Dual independent voice pointers: v1 and v2 are simple integer addresses advanced by 3 each time a note is consumed. Because the two voices have independent durations (a3 and b3), their pointers advance asynchronously, enabling true two-voice polyphony without a pre-interleaved data structure.
Soft note articulation: Rather than muting a channel to zero when fetching the next note (which would cause an audible gap), the subroutines reduce volume to 13 (out of 15). This produces a subtle envelope drop that separates adjacent notes of the same pitch while maintaining a smooth sound — a deliberate musical design choice documented in the inline REMs.
Tempo control: PAUSE 5 at line 55 provides a fixed inter-note tick of approximately 100 ms (5 × 20 ms at 50 Hz). Duration values in the data are multiples of this tick, giving rhythmic flexibility through the data rather than code changes.
Memory layout: Voice 2 starts at 60112, which is exactly 112 bytes (≈ 37 three-byte notes) above voice 1 at 60000. The total music block loaded at line 9000 is declared as 224 bytes (224 in the SAVE … CODE statement), consistent with two 112-byte voice buffers packed contiguously.
Save/Load Block
Line 9000 uses LOAD ""CODE 6E4 — 6E4 is hexadecimal for 1764, but in TS2068 BASIC the VAL "number" memory-optimization idiom is not used here; the literal 6E4 is interpreted by BASIC as the floating-point number 6×10⁴ = 60000, which is the base address of the music data block. This is an elegant way to express the load address using scientific notation within a BASIC literal. Line 9999 saves both files and verifies them; the CODE 6E4,224 argument confirms the data block is 224 bytes long.
Content
Source Code
1 REM EXAMPLE 2 WRITTEN BY E & K BOISVERT ©1986 BYTE POWER MUSIC EVENING SONG BY R SCHUMANN
9 REM V1=ADDRESS OF VOICE 1 V2=ADDRESS OF VOICE 2
10 LET v1=60000: LET v2=60112
14 REM FIND FIRST NOTES OF VOICE 1 & 2
15 GO SUB 100: GO SUB 200
30 REM 255=END OF MUSIC 254=REST (PAUSE) (MUSIcomp COMPATIBLE DATA)
35 IF a1=255 THEN PAUSE 5: STOP
39 REM A4 & B4 ARE THE VOLUME OF VOICE 1 & 2
40 LET a4=15: IF a1=254 THEN LET a4=0
45 LET b4=15: IF b1=254 THEN LET b4=0
49 REM PLAY NOTES
50 SOUND 8,a4;9,b4;7,60;0,a1;1,a2;2,b1;3,b2
54 REM TEMPO (PAUSE 5)
55 PAUSE 5
59 REM A3 & B3 DURATION OF NOTE IF EQUAL 0 THEN FIND NEXT NOTE
60 LET a3=a3-1: IF a3=0 THEN GO SUB 100
65 LET b3=b3-1: IF b3=0 THEN GO SUB 200
69 REM COMPLETE LOOP
70 GO TO 35
100 REM FIND NOTE OF VOICE 1 AND ADD 3 TO V1 (NEXT NOTE OF VOICE 1)
105 LET a1=PEEK v1: LET a2=PEEK (v1+1): LET a3=PEEK (v1+2): LET v1=v1+3
109 REM LOWER VOLUME TO SEPARATE NOTES (NOT 0 FOR SMOOTHER DELIVERY)
110 SOUND 8,13: RETURN
200 REM FIND NOTE OF VOICE 2 AND ADD 3 TO V2 (NEXT NOTE OF VOICE 2)
205 LET b1=PEEK v2: LET b2=PEEK (v2+1): LET b3=PEEK (v2+2): LET v2=v2+3
209 REM LOWER VOLUME TO SEPARATE NOTES (NOT 0 FOR SMOOTHER DELIVERY)
210 SOUND 9,13: RETURN
8999 REM LOAD CODES FOR MUSIC 2
9000 LOAD ""CODE 6E4: RUN
9999 SAVE "EXAMPLE 2" LINE 9000: SAVE "MUSIC 2"CODE 6E4,224: VERIFY "EXAMPLE 2": VERIFY "MUSIC 2"CODE
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
