--- title: "Music Maker" id: 57438 type: "computer_media" slug: "music-maker" url: "http://localhost/computer_media/music-maker/" markdown_url: "http://localhost/computer_media/music-maker.md" published_at: "2024-10-05T13:43:31+00:00" modified_at: "2026-03-30T21:17:53+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/10/239_MM.png" excerpt: "Enter a melody note by note, then replay it with machine-code sound output — this composer program stores pitch and duration arrays and drives audio via direct memory POKEs." 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 1000" slug: "ts1000" taxonomy: "post_tag" url: "http://localhost/tag/ts1000/" model: - name: "Timex/Sinclair 1000" slug: "ts-1000" taxonomy: "model" url: "http://localhost/model/ts-1000/" indiv: - name: "Bill Ferrebee" slug: "bill-ferrebee" taxonomy: "indiv" url: "http://localhost/indiv/bill-ferrebee/" genre: - name: "Music" slug: "music" taxonomy: "genre" url: "http://localhost/type/music/" media_contents: - id: 56736 title: "Timex Sinclair Public Domain Library Tape 1005" type: "computer_media" url: "http://localhost/computer_media/timex-sinclair-public-domain-library-tape-1005/" media_type: "Program" programmers: - name: "Bill Ferrebee" slug: "bill-ferrebee" taxonomy: "indiv" url: "http://localhost/indiv/bill-ferrebee/" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2024/10/239_MM.png" media_type_tags: "Music" --- # Music Maker Music-Maker is a ZX81/TS1000 melody composition and playback program that lets the user define a tune measure by measure, specifying pitch and duration values for each note. Playback is driven by machine code accessed via USR 16514, with timing and pitch values written directly to memory locations 16516, 16518, 16520, and 16528 using POKE statements. The tempo is stored as a single-element array Z(1), calculated as 960 divided by the user-supplied tempo value, and passed to the machine code routine. Notes are stored in parallel arrays N() for pitch and D() for duration, with pitch value 100 used as a special rest code that triggers POKE 16528,255 instead of 254. After entry, the melody can be replayed, have its tempo adjusted, or be saved to tape under the user-supplied melody name. *** ## Program Analysis ### Program Structure The program is divided into several functional sections linked by `GOTO` jumps rather than subroutines: 1. **Lines 20, 180–210:** Title screen and initialisation — displays the “MUSIC-MAKER” and “MOUNTAINEER SOFTWARE 1984” banners, then clears the screen. 2. **Lines 220–350:** Session setup — prompts for melody name (`X$`), number of measures (`M`), and tempo (`T`); computes `Z(1)=960/T`. 3. **Lines 360–670:** Note entry — iterates over each measure (`J`) and each note within it (`K`), collecting pitch into `N(I)` and duration into `D(I)`. 4. **Lines 30–170:** Playback loop — executes the machine code routine for each note, then presents a post-play menu (replay, tempo change, or tape save). 5. **Lines 680–850:** Tape save — counts down and issues `SAVE X$` to record the program under the melody name. 6. **Lines 860–940:** Tempo change — re-prompts for `T` and recomputes `Z(1)` before returning to the menu. 7. **Lines 950–960:** Program self-save at line 950, followed by `RUN`. ### Machine Code Sound Routine All audio output is delegated to a machine code routine whose entry point is address 16514. Four memory locations are configured via `POKE` before each `USR` call: | Address | Variable | Purpose | | --- | --- | --- | | 16516 | `Z(1)` | Tempo/timing constant (960÷T), set once per play | | 16520 | `N(I)` | Pitch value for current note | | 16518 | `D(I)*1000/N(I)` | Duration scaled by pitch | | 16528 | 254 or 255 | Rest flag: 255 = rest (N=100), 254 = normal note | The machine code is not defined within this BASIC listing; it is presumed to reside in memory already (likely POKEd in elsewhere or present in a companion loader). The result of `USR 16514` is assigned to `A` at line 100 but the value is never used — this is simply the idiomatic way to call a machine code routine that returns a value. ### Data Storage Three arrays hold the session data: - `Z(1)` — single-element array holding the tempo constant passed to machine code. - `N(X)` — pitch values; pitch 100 is the special rest code. - `D(X)` — duration values; duration for note `I` is passed as `D(I)*1000/N(I)`, scaling duration relative to pitch. - `A(M)` — note counts per measure; summed during entry to compute total note count `X`. The total note count `X` is accumulated at line 410 inside the measure-count loop, and the `N()` and `D()` arrays are dimensioned only after all measure sizes are known (line 460–470), which is a correct two-pass approach. ### Key BASIC Idioms - **Busy-wait loops:**`FOR I=1 TO 50: NEXT I` (and similar) are used as crude delays throughout, a common ZX81 technique in the absence of a `PAUSE` command in FAST mode. - **FAST/SLOW switching:** Playback runs inside `FAST` (line 30) to maximise the timing accuracy available to the machine code, with `SLOW` (line 120) restored for display output. - **Centred text:**`AT 2,16-(INT (LEN X$/2));X$` at lines 380 and 510 centres the melody name by computing its half-length offset — a standard ZX81 centring idiom. - **INKEY$ polling:** Lines 140 and 700 use the `IF INKEY$="" THEN GOTO` spin-wait pattern to wait for any keypress. ### Notable Techniques The tempo formula `Z(1)=960/T` converts a musical tempo number into a machine-code-friendly period constant. The duration scaling `D(I)*1000/N(I)` means that for a given user duration value, higher-pitched (higher-frequency) notes have proportionally shorter machine code duration counts, which compensates for the relationship between pitch period and perceived note length. The post-play menu (lines 130–170) is implemented without a PAUSE — it waits for a non-empty `INKEY$` at line 140, then tests the same `INKEY$` again at lines 150 and 160. Because `INKEY$` is re-evaluated on each reference, there is a race condition: by the time lines 150 or 160 are reached, the key may have been released, causing the test to fail and falling through to line 170 (replay) even if S or T was pressed. A held key press is needed to reliably reach those branches. ### Bugs and Anomalies - **INKEY$ race (lines 140–160):** As noted, each reference to `INKEY$` is a fresh read; a brief keypress detected at line 140 may not still be held when tested at lines 150 and 160. - **Missing line 630:** The note-entry loop ends with `NEXT J` at line 620, but line numbers jump from 620 to 640 — line 630 is absent. This is harmless as it causes no execution gap. - **Unused variable A:**`LET A=USR 16514` at line 100 stores the machine code return value in scalar `A`, which conflicts with the array `A(M)` dimensioned at line 360. On the ZX81, assigning to scalar `A` after `DIM A(M)` would erase the array. However, by the time playback reaches line 100, note entry is complete and `A()` is no longer needed, so no data is lost in practice. - **Tape save countdown:** The inner loop `FOR J=1 TO 10: NEXT J` in the countdown (lines 730–740, 770–780) provides very short delays between countdown digit updates — the countdown will appear nearly instantaneous rather than one second per digit. ## Source Code ``` 10 REM NEXT A\##I-: INKEY$PEEK COPY ( UNPLOT INKEY$<= RETURN ( UNPLOT H4 LET 94 GOTO TAN 15 REM "MUSIC-MAKER" 1984 BILL FERREBEE 749 HILL STREET NO. 6 PARKERSBURG, WV 26104 TO SAVE GOTO 950 20 GOTO 180 30 FAST 40 POKE 16516,Z(1) 50 FOR I=1 TO X 60 POKE 16520,N(I) 70 POKE 16518,D(I)*1000/N(I) 80 IF N(I)=100 THEN POKE 16528,255 90 IF N(I)<>100 THEN POKE 16528,254 100 LET A=USR 16514 110 NEXT I 120 SLOW 130 PRINT AT 7,4;"PRESS % %S%P%A%C%E% TO STOP";AT 9,3;"PRESS % %T% TO CHANGE TEMPO";AT 11,0;"PRESS % %S% TO SAVE MELODY ON TAPE";AT 13,1;"PRESS ANY OTHER KEY TO REPLAY" 140 IF INKEY$="" THEN GOTO 140 150 IF INKEY$="S" THEN GOTO 680 160 IF INKEY$="T" THEN GOTO 860 170 GOTO 30 180 PRINT AT 10,9;"% %M%U%S%I%C%-%M%A%K%E%R% ";AT 21,2;"% %M%O%U%N%T%A%I%N%E%E%R% %S%O%F%T%W%A%R%E% % %1%9%8%4% " 190 FOR I=1 TO 100 200 NEXT I 210 CLS 220 DIM Z(1) 230 LET X=0 240 PRINT TAB 6;"% %M%U%S%I%C%-%M%A%K%E%R% %L%O%A%D%E%R% ";AT 3,0;"NAME OF MELODY ?: "; 250 INPUT X$ 260 PRINT X$;AT 5,0;"HOW MANY MEASURES ?:"; 270 INPUT M 280 PRINT M 290 PRINT AT 7,0;"TEMPO ?:"; 300 INPUT T 310 PRINT T 320 LET Z(1)=960/T 330 FOR I=1 TO 50 340 NEXT I 350 CLS 360 DIM A(M) 370 FOR I=1 TO M 380 PRINT AT 0,6;"% %M%U%S%I%C%-%M%A%K%E%R% %L%O%A%D%E%R% ";AT 2,16-(INT (LEN X$/2));X$;AT 4,0;"HOW MANY NOTES IN MEASURE ";I;" ?:";AT 4,30;" " 390 INPUT A(I) 400 PRINT AT 4,30;A(I) 410 LET X=X+A(I) 420 FOR J=1 TO 50 430 NEXT J 440 NEXT I 450 CLS 460 DIM N(X) 470 DIM D(X) 480 LET I=1 490 FOR J=1 TO M 500 FOR K=1 TO A(J) 510 PRINT AT 0,16-(INT (LEN X$/2));X$ 520 PRINT AT 2,9;" ";TAB 21;" ";AT 2,0;"MEASURE :";J;TAB 15;"NOTE :";K 530 PRINT AT 4,9;" ";AT 6,12;" " 540 PRINT AT 4,0;"% %P%I%T%C%H% :"; 550 INPUT N(I) 560 PRINT N(I) 570 PRINT AT 6,0;"% %D%U%R%A%T%I%O%N% :"; 580 INPUT D(I) 590 PRINT D(I) 600 LET I=I+1 610 NEXT K 620 NEXT J 640 FOR I=1 TO 50 650 NEXT I 660 CLS 670 GOTO 130 680 CLS 690 PRINT AT 9,0;"PREPARE TAPE RECORDER FOR SAVING";AT 11,3;"(PRESS ANY KEY WHEN READY)" 700 IF INKEY$="" THEN GOTO 700 710 PRINT AT 9,0;" START TAPE RECORDER NOW... ";AT 11,3;" COUNTDOWN : 5 " 720 FOR I=4 TO 0 STEP -1 730 FOR J=1 TO 10 740 NEXT J 750 PRINT AT 11,21;I 760 NEXT I 770 FOR J=1 TO 10 780 NEXT J 790 CLS 800 SAVE X$ 810 PRINT AT 10,5;"NAME OF SAVED PROGRAM:";AT 11,16-(INT (LEN X$/2));X$ 820 FOR I=1 TO 100 830 NEXT I 840 CLS 850 GOTO 130 860 CLS 870 PRINT AT 1,0;"TEMPO ?:"; 880 INPUT T 890 PRINT T 900 LET Z(1)=960/T 910 FOR I=1 TO 50 920 NEXT I 930 CLS 940 GOTO 130 950 SAVE "1023%9" 960 RUN ```