--- title: "Music Beep 2" id: 55136 type: "computer_media" slug: "music-beep-2" url: "http://localhost/computer_media/music-beep-2/" markdown_url: "http://localhost/computer_media/music-beep-2.md" published_at: "2024-06-17T00:17:43+00:00" modified_at: "2026-03-30T21:44:14+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/06/SCR-20240616-rpck.png" excerpt: "A multi-song BASIC music player that precomputes an equal-temperament frequency table and uses polyphonic sound hardware to play melodies with synchronized on-screen graphics." 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/" genre: - name: "Music" slug: "music" taxonomy: "genre" url: "http://localhost/type/music/" media_contents: - id: 54965 title: "ISTUG Public Domain Library 5" type: "computer_media" url: "http://localhost/computer_media/istug-public-domain-library-5/" media_type: "Program" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2024/06/SCR-20240616-rpck.png" media_type_tags: "Music" --- # Music Beep 2 This program is a music player that encodes and plays multiple songs using the TS2068’s SOUND command for polyphonic audio output. At startup, a subroutine at line 820 precomputes a two-dimensional frequency table stored in array f(80,2), calculating the high and low bytes of the period value for each MIDI-like note number using the formula based on 36.8 Hz and equal-temperament tuning (2^(1/12) per semitone). Song data is encoded in DATA statements using named constants (tw=2, fr=4, r9=49, f1=51, f4=54) alongside BASIC expressions like NOT PI (=0) and SGN PI (=1) as compact numeric literals. Each note is represented as a pitch/duration pair, with j=0 triggering a rest via SOUND with silence, j=99 marking the end of a song, and the graphics loop at line 80 drawing a rectangle on screen synchronized to each note. The program cycles through songs automatically, resetting when the screen coordinate counter exceeds 250. *** ## Program Analysis ### Program Structure The program is organized into three logical sections: 1. **Initialization (lines 10–20):** Sets display attributes, calls the setup subroutine, and initializes playback state variables. 2. **Playback loop (lines 30–110):** READs note pairs from DATA, handles rests, end-of-song markers, screen wrapping, graphics, and sound output. 3. **Setup subroutine (lines 820–880):** Precomputes a frequency lookup table and defines symbolic constants. 4. **Song data (lines 120–810):** Encoded note/duration pairs for multiple songs, terminated by `99,0` sentinels. ### Frequency Table Precomputation The subroutine at line 820 defines playback constants and builds a 2D array `f(80,2)` covering note numbers 30–80. For each note, the period in microseconds is computed as: `freq = 36.8 * (2^(1/12))^(f-6)` `x = 1,750,000 / (16 * freq)` This period is then split into high byte (`f(f,1)`) and low byte (`f(f,2)`) for use with the two-register `SOUND` command. The base of 36.8 Hz and the offset of 6 semitones from note index are chosen to map the integer note numbers to standard equal-temperament pitches. ### Symbolic Constants as DATA Tokens Because BASIC DATA values must be numeric expressions, the program exploits several compact idioms to avoid spelling out repeated numbers: | Symbol | Value | Meaning | | --- | --- | --- | | `tw` | 2 | Sixteenth note duration unit | | `fr` | 4 | Eighth note duration unit | | `r9` | 49 | Note D (MIDI-like index) | | `f1` | 51 | Note E | | `f4` | 54 | Note F#/Gb | | `NOT PI` | 0 | Rest pitch / zero duration | | `SGN PI` | 1 | Shortest duration unit | | `PI` | 3.14159… | Used as ~3 duration value | The use of `NOT PI` (which evaluates to 0) and `SGN PI` (which evaluates to 1) in DATA statements is a clever space-saving technique, as these are shorter token sequences than writing the digits directly in many cases, and they tokenize efficiently in Sinclair BASIC. ### Playback Loop Logic Each iteration of the loop at line 30 reads a pair `j,k` where `j` is pitch and `k` is duration: - `j=99`: End of song — pause briefly, clear screen, restart DATA from the top via `GO TO 20`. Because `GO TO 20` does not issue a `RESTORE`, successive songs are simply delimited by `99,0` and the DATA pointer advances naturally. - `j=0`: Rest — plays silence with `SOUND 8,0` and advances the time counter `i`. - Otherwise: Draws a rectangle at the current horizontal position using `PLOT`/`DRAW`, plays the note via `SOUND` with the precomputed frequency bytes from `f(j,…)`, then pauses for `k*2` frames. ### Screen Coordinate Management Variable `i` tracks horizontal position on screen; `u` tracks vertical position. Lines 60–70 implement a two-phase wrap using `flag`: when `i` reaches 250, if `flag=0` the vertical position drops to 0 and `flag` is set; if `flag=1` the screen is cleared and position resets to the top. This gives two rows of graphics before clearing, matching the two screen halves. ### SOUND Command Usage Line 90 uses a multi-channel `SOUND` invocation with channels 7, 8, 12, 11, and 13, configuring envelope and mixer settings for the AY-3-8912 sound chip. Line 100 then sets channels 0 and 1 to the note frequency bytes from the lookup table. This separates tone setup (line 90) from pitch assignment (line 100), enabling consistent envelope behavior across all notes. ### Notable Techniques - Precomputed floating-point frequency table avoids repeated transcendental calculations during playback. - Integer split into 256-byte high/low registers mirrors the AY chip’s 12-bit tone period registers. - Using `PI` directly as a duration value (≈3.14) relies on BASIC’s `PAUSE` and arithmetic truncating or using the float value — effectively treating PI as approximately 3. - The `RESTORE` is implicitly performed by jumping to line 20, which re-executes `READ` from the beginning of DATA — a standard Sinclair BASIC idiom for song looping. - Multiple songs are concatenated in DATA, each terminated by the `99,0` sentinel, so `GO TO 20` at the end of one song simply starts reading the next song’s data without an explicit `RESTORE`. ### Potential Anomalies - At line 60, `LET u=i` is executed after `LET i=0`, so `u` is always set to 0 rather than the intended prior value of `i`. This may be intentional (dropping to the bottom of the screen) or a subtle ordering bug. - `PI` used as a duration value produces a non-integer PAUSE argument; BASIC will truncate or round this, effectively treating it as 3, which may cause slight timing drift compared to integer durations. - The `DIM f(80,2)` at line 830 uses variable `f` as both the array name and the loop variable in the immediately following `FOR f=30 TO 80`. This works in Sinclair BASIC because arrays and simple variables occupy separate namespaces. ## Source Code ``` 10 INK 0: PAPER 7: BORDER 7: CLS : GO SUB 820 20 LET u=100: LET i=0: LET flag=i 30 READ j,k 40 IF j=99 THEN PAUSE 100: CLS : GO TO 20 50 IF j=0 THEN SOUND 8,0: PAUSE 4*k: LET i=i+k: GO TO 30 60 IF i>=250 AND flag=0 THEN LET i=0: LET u=i: LET flag=1 70 IF i>=250 AND flag=1 THEN CLS : LET i=0: LET u=100: LET flag=i 80 PLOT i,u: DRAW 0,j: DRAW k,0: DRAW 0,-j: DRAW -k,0: LET i=i+k 90 SOUND 7,56;8,16;12,30;11,30;13,9 100 SOUND 0,f(j,2);1,f(j,1) 110 PAUSE k*2: GO TO 30 120 DATA f4,6,f4,6,52,PI,f1,PI,f1,6,50,PI,f1,PI,f1,16,NOT PI,tw,50,3 130 DATA f1,PI,f1,6,50,PI,f1,PI,f4,6,f1,PI,f4,PI,52,12,r9,6,NOT PI,PI,r9,PI,r9,6,48,PI,r9,3 140 DATA 49,6,48,PI,r9,3 150 DATA 52,16,NOT PI,tw,f1,PI,r9,PI,f1,PI,f4,9,56,9,56,PI,r9,16,NOT PI,tw,f4,6 160 DATA f4,6,52,PI,f1,PI,f1,6,50,PI,f1,PI,f1,16,NOT PI,tw,50,PI,f1,PI,f1,6,50,PI,f1,3 170 DATA 52,PI,f1,PI,r9,5,46,SGN PI,r9,12,47,6,NOT PI,PI,47,PI,47,6,46,PI,47,PI,50,6,r9,PI,47,3 180 DATA 59,15,NOT PI,PI,47,PI,r9,PI,f1,PI,f4,SGN PI,NOT PI,tw,47,PI,r9,PI,f1,PI,f4,SGN PI,NOT PI,tw,42,PI,44,5 190 DATA f1,SGN PI,r9,12,47,SGN PI,99,0 200 DATA 52,tw,57,tw,NOT PI,tw,52,tw,57,tw,NOT PI,tw,52,tw,57,6,NOT PI,fr,52,tw,57,tw,59,tw,57,tw,56,4 210 DATA 57,tw,59,tw,NOT PI,8,52,tw,56,tw,NOT PI,tw,52,tw,56,tw,NOT PI,tw,52,tw,56,6,NOT PI,fr,52,2 220 DATA 56,tw,57,tw,56,tw,f4,fr,56,tw,57,tw,NOT PI,6,64,tw,63,tw,64,tw,61,tw,60,tw,61,2 230 DATA 57,tw,56,tw,57,tw,52,tw,NOT PI,fr,r9,tw,50,tw,52,tw,f4,tw,56,tw,57,tw,59,tw,61,2 240 DATA 62,tw,59,tw,NOT PI,fr,62,tw,61,tw,62,tw,59,tw,58,tw,59,tw,56,tw,55,tw,56,tw,52,2 250 DATA 0,fr,64,tw,63,tw,64,tw,66,tw,64,tw,62,tw,61,tw,59,tw,57,tw,99,0 260 DATA 49,8,r9,8,f1,12,NOT PI,fr,r9,8,r9,8,f1,12,NOT PI,fr,r9,8,f1,8,52,8,f1,8 270 DATA 49,8,f1,fr,r9,fr,f4,16,44,8,40,8,44,8,45,8 280 DATA 44,8,44,fr,40,fr,39,16,r9,8,r9,8,f1,12,NOT PI,fr,r9,8,r9,8,f1,12,NOT PI,4 290 DATA 40,8,44,8,45,8,r9,8,f1,fr,r9,fr,45,8,44,16,99,0 300 DATA 37,SGN PI,NOT PI,tw,30,SGN PI,NOT PI,5,42,PI,42,PI,41,PI,39,PI,41,PI,NOT PI,PI,42,PI,44,PI,NOT PI,PI,37,SGN PI,NOT PI,2 310 DATA 30,SGN PI,NOT PI,5,42,PI,42,PI,41,PI,39,PI,41,PI,NOT PI,PI,42,PI,44,PI,NOT PI,PI,37,PI,42,PI,NOT PI,PI,45,3 320 DATA 49,6,47,PI,45,PI,NOT PI,PI,r9,PI,52,6,50,PI,r9,PI,NOT PI,PI,53,PI,56,6,f4,PI,53,PI,50,3 330 DATA 49,PI,47,PI,45,PI,44,PI,30,SGN PI,NOT PI,5,42,PI,42,PI,41,PI,39,PI,41,PI,NOT PI,PI,42,PI,44,3 340 DATA 0,PI,37,SGN PI,NOT PI,tw,30,SGN PI,NOT PI,5,42,PI,42,PI,41,PI,39,PI,41,PI,NOT PI,PI,42,PI,44,PI,0 350 DATA PI,37,PI,NOT PI,PI,r9,PI,52,6,50,PI,r9,PI,47,PI,45,PI,43,PI,47,PI,50,PI,42,3 360 DATA 41,PI,42,PI,44,PI,NOT PI,PI,45,SGN PI,NOT PI,tw,44,9,42,SGN PI,99,0 370 DATA 47,tw,NOT PI,tw,47,tw,r9,tw,NOT PI,tw,r9,tw,f1,tw,f4,tw,f1,tw,47,tw,NOT PI,tw,42,2 380 DATA 47,tw,NOT PI,tw,47,tw,r9,tw,NOT PI,tw,r9,tw,f1,6,47,tw,NOT PI,tw,42,tw,47,tw,NOT PI,tw,47,tw,r9,2 390 DATA 0,tw,r9,tw,f1,tw,f4,tw,f1,tw,47,tw,NOT PI,fr,56,tw,NOT PI,fr,r9,tw,NOT PI,tw,52,tw,f1,6,47,2 400 DATA 0,fr,59,tw,NOT PI,tw,59,tw,56,tw,NOT PI,tw,59,tw,58,tw,61,tw,58,tw,f4,tw,NOT PI,fr,59,tw,NOT PI,2 410 DATA 59,tw,56,tw,NOT PI,tw,59,tw,58,6,f4,tw,NOT PI,tw,f1,tw,52,tw,NOT PI,tw,f1,tw,52,tw,NOT PI,tw,f4,2 420 DATA 56,tw,NOT PI,tw,58,tw,59,tw,NOT PI,fr,56,tw,NOT PI,fr,r9,tw,NOT PI,tw,52,tw,f1,6,47,tw,99,0 430 DATA 47,PI,NOT PI,tw,r9,SGN PI,47,PI,NOT PI,tw,r9,SGN PI,f1,PI,NOT PI,tw,f4,SGN PI,56,PI,NOT PI,tw,f4,1 440 DATA 59,PI,NOT PI,tw,58,SGN PI,61,PI,NOT PI,tw,59,SGN PI,58,PI,NOT PI,tw,61,SGN PI,59,PI,NOT PI,tw,56,1 450 DATA f4,PI,NOT PI,tw,f4,SGN PI,56,PI,NOT PI,tw,f4,SGN PI,59,PI,NOT PI,tw,56,SGN PI,f4,PI,NOT PI,tw,f1,1 460 DATA 49,24,47,PI,NOT PI,tw,r9,SGN PI,47,PI,NOT PI,tw,r9,SGN PI,f1,PI,NOT PI,tw,f4,SGN PI,56,PI,NOT PI,tw,f4,1 470 DATA 56,PI,NOT PI,tw,58,SGN PI,61,PI,NOT PI,tw,59,SGN PI,58,PI,NOT PI,tw,61,SGN PI,59,PI,NOT PI,tw,56,1 480 DATA f4,PI,NOT PI,tw,f4,SGN PI,59,PI,NOT PI,tw,47,SGN PI,r9,6,f4,6,47,18,99,0 490 DATA 55,tw,f4,tw,f4,fr,55,tw,f4,tw,f4,fr,55,tw,f4,tw,f4,fr,62,fr,NOT PI,4 500 DATA 62,tw,61,tw,59,fr,59,tw,57,tw,55,fr,55,tw,f4,2 510 DATA 52,fr,52,fr,NOT PI,fr,f4,tw,52,tw,52,fr,f4,tw,52,tw,52,fr,f4,tw,52,2 520 DATA 52,fr,61,fr,NOT PI,fr,61,tw,59,tw,58,fr,58,tw,55,tw,f4,fr,f4,tw,52,2 530 DATA 50,fr,50,fr,NOT PI,fr,62,tw,61,tw,61,fr,64,fr,58,fr,61,4 540 DATA 59,fr,f4,fr,NOT PI,fr,62,tw,61,tw,61,fr,64,fr,58,fr,61,4 550 DATA 59,fr,62,fr,61,tw,59,tw,57,tw,55,tw,f4,fr,46,fr,47,fr,r9,4 560 DATA 50,fr,52,tw,50,tw,r9,fr,47,fr,f4,fr,NOT PI,fr,65,8 570 DATA 66,tw,NOT PI,6,65,8,66,tw,NOT PI,6,65,8 580 DATA 66,fr,65,fr,66,fr,65,fr,66,fr,99,0 590 DATA 50,PI,50,PI,52,PI,f4,PI,50,PI,f4,PI,52,PI,45,PI,50,PI,50,PI,52,PI,f4,PI,50,6 600 DATA 49,PI,NOT PI,PI,50,PI,50,PI,52,PI,f4,PI,55,PI,f4,PI,52,PI,50,PI,r9,PI,45,PI,47,PI,r9,PI,50,6 610 DATA 50,PI,NOT PI,PI,47,5,r9,SGN PI,47,PI,45,PI,47,PI,r9,PI,50,PI,NOT PI,PI,45,5,47,SGN PI,45,PI,43,PI,42,6 620 DATA 45,PI,NOT PI,PI,47,5,r9,SGN PI,47,PI,45,PI,47,PI,r9,PI,50,PI,47,fr,45,PI,50,PI,r9,PI,52,PI,50,6,50,6 630 DATA 99,0 640 DATA 42,SGN PI,NOT PI,SGN PI,42,SGN PI,NOT PI,SGN PI,42,SGN PI,NOT PI,SGN PI,47,SGN PI,NOT PI,5,51,SGN PI,NOT PI,PI,42,SGN PI,NOT PI,SGN PI,42,SGN PI,NOT PI,1 650 DATA 42,SGN PI,NOT PI,SGN PI,47,SGN PI,NOT PI,5,51,SGN PI,NOT PI,5,30,SGN PI,NOT PI,SGN PI,30,SGN PI,NOT PI,SGN PI,35,SGN PI,NOT PI,PI,47,SGN PI,NOT PI,1 660 DATA 47,SGN PI,NOT PI,SGN PI,46,SGN PI,NOT PI,SGN PI,46,SGN PI,NOT PI,SGN PI,44,SGN PI,NOT PI,SGN PI,44,SGN PI,NOT PI,SGN PI,42,8,NOT PI,tw,42,SGN PI,NOT PI,1 670 DATA 42,SGN PI,NOT PI,SGN PI,42,SGN PI,NOT PI,SGN PI,46,SGN PI,NOT PI,5,r9,SGN PI,NOT PI,PI,42,SGN PI,NOT PI,SGN PI,42,SGN PI,NOT PI,SGN PI,42,SGN PI,NOT PI,1 680 DATA 46,SGN PI,NOT PI,5,r9,SGN PI,NOT PI,5,37,SGN PI,NOT PI,SGN PI,37,SGN PI,NOT PI,SGN PI,30,SGN PI,NOT PI,PI,f4,tw,56,tw,f4,tw,52,2 690 DATA f1,tw,r9,tw,47,8,99,0 700 DATA 42,fr,42,fr,46,fr,r9,fr,r9,fr,NOT PI,fr,61,tw,NOT PI,tw,61,tw,NOT PI,6,58,tw,NOT PI,tw,58,tw,NOT PI,6,42,fr,42,4 710 DATA 46,fr,r9,fr,r9,fr,NOT PI,fr,61,tw,NOT PI,tw,61,tw,NOT PI,6,58,tw,NOT PI,tw,58,tw,NOT PI,tw,NOT PI,6,42,fr,42,4 720 DATA 46,fr,r9,fr,r9,fr,NOT PI,fr,61,tw,NOT PI,tw,61,tw,NOT PI,6,59,tw,NOT PI,tw,59,tw,NOT PI,6,41,fr,41,fr,44,fr,f1,fr,f1,4 730 DATA 0,fr,63,tw,NOT PI,tw,63,tw,NOT PI,6,59,tw,NOT PI,2 740 DATA 59,tw,NOT PI,6,41,fr,41,fr,44,fr,f1,fr,f1,fr,NOT PI,fr,63,tw,NOT PI,tw,63,tw,NOT PI,6,58,tw,NOT PI,tw,58,2 750 DATA 0,6,42,fr,42,fr,46,fr,r9,fr,f4,fr,NOT PI,fr,66,tw,NOT PI,tw,66,tw,NOT PI,6,61,tw,NOT PI,tw,61,tw,NOT PI,6,42,4 760 DATA 42,fr,46,fr,r9,fr,f4,fr,NOT PI,fr,66,tw,NOT PI,2 770 DATA 66,tw,NOT PI,6,63,tw,NOT PI,tw,63,tw,NOT PI,6,44,fr,44,fr,47,fr,f1,tw,NOT PI,tw,f1,14,NOT PI,tw,48,fr,r9,fr,58,16 780 DATA f4,fr,46,fr,46,8,44,fr,f1,8,r9,fr,42,fr,NOT PI,tw,42,tw,42,fr,NOT PI,8,r9,tw,NOT PI,tw,47,2 790 DATA NOT PI,6,r9,tw,NOT PI,tw,47,tw,NOT PI,6,r9,fr,58,16,56,fr,r9,tw,NOT PI,tw,46,tw,NOT PI,6,r9,tw,NOT PI,tw,46,tw,NOT PI,6,r9,4 800 DATA 56,16,f4,fr,r9,tw,NOT PI,tw,47,tw,NOT PI,6,r9,tw,NOT PI,tw,47,tw,NOT PI,6,r9,fr,58,16 810 DATA 56,fr,r9,fr,f4,fr,58,fr,61,8,59,fr,58,tw,58,tw,58,fr,56,tw,NOT PI,tw,f4,fr,NOT PI,8,99,0 820 LET tw=2: LET fr=4: LET r9=49: LET f1=51: LET f4=54 830 DIM f(80,2): FOR f=30 TO 80 840 PRINT AT PI,PI;"Setting f(";f;")" 850 LET freq=36.8*(2^(1/12))^(f-6) 860 LET x=1.75/(16*freq): LET x=x*1000000 870 LET f(f,1)=INT (x/256): LET f(f,2)=INT x-INT (x/256)*256 880 NEXT f: RETURN 890 SAVE "MusicBeep2" LINE 10 ```