--- title: "Duet Example 2" id: 63228 type: "computer_media" slug: "duet-example-2" url: "http://localhost/computer_media/duet-example-2/" markdown_url: "http://localhost/computer_media/duet-example-2.md" published_at: "2026-01-31T13:57:53+00:00" modified_at: "2026-04-03T07:57:55+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2026/01/duet-example.png" excerpt: "A two-voice Schumann piece uses SOUND with machine-code note data, using a clever volume-dip trick to articulate notes without silencing the channels." category: - name: "Archived Media" slug: "archived-media" taxonomy: "category" url: "http://localhost/category/archived-media/" post_tag: - 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: "Eric Boisvert" slug: "boisvert-eric" taxonomy: "indiv" url: "http://localhost/indiv/boisvert-eric/" genre: - name: "Sound" slug: "sound" taxonomy: "genre" url: "http://localhost/type/sound/" media_contents: - id: 63047 title: "Byte Power December 1986 – January 1987" type: "computer_media" url: "http://localhost/computer_media/byte-power-december-1986-january-1987/" media_type: "Program" programmers: - name: "Eric Boisvert" slug: "boisvert-eric" taxonomy: "indiv" url: "http://localhost/indiv/boisvert-eric/" 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/duet-example.png" article_media: - id: 63091 title: "Better Programming!" type: "article" url: "http://localhost/article/better-programming-7/" media_type_tags: "Sound" --- # Duet Example 2 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: | 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` (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 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. ## 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 ```