Duet Example 2

This file is part of Byte Power December 1986 - January 1987 . Download the collection to get this file.
Developer(s): Eric Boisvert
Date: 1987
Type: Program
Platform(s): TS 2068

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:

  1. Lines 10–15: Initialize voice pointers v1 and v2, fetch the first note from each voice via subroutines.
  2. Lines 35–70: Main playback loop — check for end sentinel, set volumes, issue SOUND command, pause for tempo, decrement durations, fetch next notes when duration expires, then loop.
  3. 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.
  4. Lines 200–210: Subroutine for voice 2 — same pattern for v2 and channel 9.
  5. 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:

OffsetVariableMeaning
+0a1 / b1AY tone register low byte (pitch LSB)
+1a2 / b2AY tone register high byte (pitch MSB)
+2a3 / b3Duration (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 (binary 00111100) 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 6E46E4 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

Appears On

Related Products

Related Articles

This month I will talk about the ‘REAL ECHO’. This one is a little more complicated than the ‘FALSE ECHO’...

Related Content

Image Gallery

Duet Example 2

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.

Scroll to Top