--- title: "Composer" id: 56670 type: "computer_media" slug: "composer" url: "http://localhost/computer_media/composer/" markdown_url: "http://localhost/computer_media/composer.md" published_at: "2024-09-22T23:31:36+00:00" modified_at: "2026-03-30T21:21:58+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/09/Compos1.png" excerpt: "A full-screen music composer that lets you place notes on a scrolling staff, then plays them back using a machine-code sound driver — complete with octaves, semitones, and repeat control." 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: "KOPAK Creations" slug: "kopak-creations-inc" taxonomy: "post_tag" url: "http://localhost/tag/kopak-creations-inc/" - 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/" genre: - name: "Music" slug: "music" taxonomy: "genre" url: "http://localhost/type/music/" media_type: "Cassette" download_url: "https://archive.org/download/timex-sinclair-software-archive/Composer%20%28198x%29%28KOPAK%29%28TS1000%29%28US%29%28Program%29.zip" mediadate: "198x" producer_company: - id: 11026 title: "KOPAK Creations, Inc." type: "company" url: "http://localhost/company/kopak-creations-inc/" images: - url: "http://localhost/wp-content/uploads/2024/09/Compos1.png" - url: "http://localhost/wp-content/uploads/2024/09/INST1-3-scaled.jpg" - url: "http://localhost/wp-content/uploads/2024/09/INST2-3-scaled.jpg" - url: "http://localhost/wp-content/uploads/2024/09/Tape-6-scaled.jpg" related_products: - id: 12191 title: "Composer" type: "product" url: "http://localhost/product/composer/" media_type_tags: "Music" --- This program is a music composition utility that displays a staff-like grid on screen, allowing the user to place notes using cursor keys and save the resulting tune. It precomputes note frequencies for eight octaves across seven notes (A through G, including semitones) using the ZX81’s speaker timer formula F = INT(1/(f × 17×10⁻⁶) + 0.5), storing two-byte timer values in the arrays O$ and R$ for natural and sharp/flat notes respectively. A short machine-code routine embedded in REM line 1 drives the speaker directly, called via USR 16518, with timer and duration values POKEd into addresses 16515–16517. The composer supports variable octave selection, note duration, repeat playback, speed adjustment, and scrolling the staff when the cursor reaches the right edge. *** ## Program Analysis ### Program Structure The program is divided into several functional regions: 1. **Lines 1–131:** Machine code (REM line 1), initialization entry points, note interpolation subroutines, and frequency precomputation. 2. **Lines 133–181:** Playback engine — iterates over the note/octave/duration string arrays and calls the machine code driver. 3. **Lines 183–241:** Main loop — draws the staff, handles keyboard input, dispatches to editing subroutines. 4. **Lines 243–287:** Cursor movement (left/right, up/down across staff lines). 5. **Lines 289–379:** Note entry, array resizing, display update. 6. **Lines 381–427:** Initialization subroutine — prompts for duration and octave, zeros variables. 7. **Lines 429–537:** Auxiliary subroutines: repeat, duration change, octave shift, speed change. 8. **Lines 663–691:** Semitone entry branch (shares tail of note-entry subroutine). 9. **Lines 693–721:** Title/splash screen and SAVE routines. ### Machine Code Routine (REM Line 1) The entire sound driver is stored as raw bytes in the REM statement at line 1. The bytes are: `\FF\10\CC\01\2A\82\40\ED\5B\84\40\01\00\00\E5\D3\FF\CD\A2\40\DB\FE\CD\A2\40\E1\B7\ED\52\30\EF\C9\2A\84\40\37\ED\42\30\FB\C9` This routine is entered via `USR 16518` (line 29 / 155 / 169), which points two bytes past the start of the BASIC line header into the REM body. The three parameters are POKEd into fixed addresses before the call: | Address | Variable | Purpose | | --- | --- | --- | | 16515 | D (duration × 16) | Note duration | | 16516 | Low byte of timer | Speaker half-period low | | 16517 | High byte of timer | Speaker half-period high | The routine uses the Z80 `OUT (0FFH),A` instruction (`D3 FF`) to toggle the ZX81 speaker port and a delay loop keyed to the 16-bit timer value to set pitch. ### Frequency Precomputation Lines 73–119 precompute speaker timer values for seven notes across eight octaves (J=8 down to 1, higher J = lower octave). The formula at line 125 converts a frequency in Hz to a loop-count timer: `F = INT(1 / (F_hz × 17E-6) + 0.5)` The seven base frequencies used (relative multiples) are 26⅔, 30, 32, 36, 40, 42⅔, and 48 — corresponding approximately to D, E, F, G, A, B♭, and C in a just-intonation-adjacent scheme, scaled by powers of two for each octave. Natural notes are stored in `O$(J,N,1..2)`; interpolated semitone (midpoint) values are stored in `R$(J,N,1..2)` via the subroutine at lines 7–27, which averages adjacent timer values. ### Note and Duration Encoding Three parallel string arrays hold the composition: - `N$` — note names (A–G); semitones stored as inverse characters (e.g., `%E`), which have character codes above 128, detected at line 141 (`IF N<=128`). - `X$` — octave per note, encoded as a digit character; decoded with `CODE X$(X)-28`. - `D$` — duration per note, encoded similarly with `CODE D$(X)-28`, then multiplied by 16 at line 140. The arrays are dynamically resized using a `DIM` / copy idiom (lines 307–325): the old contents are saved into a temporary string `L$`, the array is re-dimensioned one element larger, and the contents are restored with a slice assignment. ### Staff Display The five staff lines are represented by rows of `"-"` characters at odd screen rows 1, 3, 5, 7, 9; spaces at even rows and row 11 represent spaces between lines and ledger areas. The array `V$(row, col)` tracks the current character at each cell so notes can be restored when the cursor moves away. The cursor is displayed as a block graphic character (`\##`, a solid 2×2 block). When the cursor reaches column 30, the screen scrolls 19 lines and a new staff section is drawn. ### Key Bindings | Key | Function | | --- | --- | | 5 | Move cursor left | | 8 | Move cursor right / advance | | 6 | Move cursor down (lower pitch) | | 7 | Move cursor up (higher pitch) | | S | Place natural note | | A | Place semitone (sharp/flat) note | | R | Play back the tune (FAST mode) | | Q | Set repeat count | | P | Shift all octaves up or down | | F | Change overall playback speed | | D | Change note duration | | O | Change current octave | | M | Save composition to tape | ### Notable Techniques and Idioms - The `GOSUB 069Z` at line 3 and `GOTO 0037` at line 5 use leading-zero and trailing-letter forms of line numbers — the `069Z` target does not exist as a reachable line in the normal flow and acts as a skip; the program entry for BASIC execution falls through to line 37 where data initialization begins after the precomputation jump at line 121 sends control to line 183. - All numeric literals in `GO TO` and `GO SUB` are written with leading zeros (e.g., `GOTO 0019`), a known memory optimization that stores the number as a string rather than a floating-point value in the token stream. - The semitone detection at line 141 exploits the fact that inverse-video characters in ZX81 BASIC have codes ≥128 (128 + normal code), so `CODE N$(X)-37` yields a value >128 for semitone note names stored as inverse characters. - The silence/rest mechanism (line 147 branch to 175) uses a simple `FOR` delay loop scaled by the duration value when the decoded note index is negative. - Line 179 (`IF X<=LEN N$ THEN GOTO 0157`) re-enters the playback loop mid-subroutine after a rest, rather than continuing the `FOR X` loop normally — an unusual flow-control trick to share the `NEXT X` at line 157. - The splash/title screen code at lines 693–713 is never called from the main program flow visible in this listing; it appears to be a leftover or an entry point used only on initial tape load. ### Bugs and Anomalies - Line 215 is followed immediately by line 217, with line 216 absent — the key test for `"M"` at line 215 jumps to line 715, bypassing line 216 which would have been the next sequential check. No functional bug since the jump is explicit. - The `WAIT` loop at line 207 (`IF INKEY$="" THEN GOTO 0207`) waits for a key to be pressed, but the subsequent key tests (lines 209–239) re-read `INKEY$` without waiting, meaning a fast typist could miss checks if the key is released between lines. This is a standard ZX81 INKEY$ polling hazard. - The `V$(11)` at line 53 is dimensioned to 28 characters with a 30-character initializer — the extra two characters are silently truncated by BASIC. - Line 359 adjusts the octave stored for a note by using `K$(Q+1)` for certain staff positions (semitone rows), implicitly raising the octave index — this relies on `K$="123456788"` having a duplicate “8” at position 9 to avoid an out-of-bounds error at the top octave. ## Source Code ``` 1 REM \FF\10\CC\01\2A\82\40\ED\5B\84\40\01\00\00\E5\D3\FF\CD\A2\40\DB\FE\CD\A2\40\E1\B7\ED\52\30\EF\C9\2A\84\40\37\ED\42\30\FB\C9\20\22\23\25\1C 3 GOSUB 069Z 5 GOTO 0037 7 IF N=1 AND J>1 THEN GOTO 0033 9 IF N>1 THEN GOTO 0015 11 LET N=N+1 13 RETURN 15 LET F=256*CODE O$(J,N-1,1)+CODE O$(J,N-1,2) 17 LET F1=256*CODE O$(J,N,1)+CODE O$(J,N,2) 19 LET F=INT ((F1+F)/2+.5) 21 LET R$(J,N,2)=CHR$ (F-(INT (F/256)*256)) 23 LET R$(J,N,1)=CHR$ (INT (F/256)) 25 LET N=N+1 27 RETURN 29 LET C=USR 16518 31 RETURN 33 LET F1=256*CODE O$(J-1,N,1)+CODE O$(J-1,N,2) 35 GOTO 0019 37 DIM V$(11,28) 39 LET V$(1)="---------------------------" 41 FOR I=3 TO 9 STEP 2 43 LET V$(I)=V$(1) 45 NEXT I 47 FOR I=2 TO 10 STEP 2 49 LET V$(I)=" " 51 NEXT I 53 LET V$(11)=" " 55 DIM R$(8,7,2) 57 LET U=0 59 LET G=1 61 GOSUB 0383 63 DIM O$(8,7,2) 65 DIM X$(3) 67 DIM N$(3) 69 DIM D$(3) 71 FAST 73 FOR J=8 TO 1 STEP -1 75 LET N=1 77 LET F=INT ((26*2**(J-1)+2**(J-1)*(2/3))+.5) 79 GOSUB 0125 81 GOSUB 0007 83 LET F=INT (30*2**(J-1)+.5) 85 GOSUB 0125 87 GOSUB 0007 89 LET F=INT (32*2**(J-1)+.5) 91 GOSUB 0125 93 GOSUB 0007 95 LET F=INT (36*2**(J-1)+.5) 97 GOSUB 0125 99 GOSUB 0007 101 LET F=INT (40*2**(J-1)+.5) 103 GOSUB 0125 105 GOSUB 0007 107 LET F=INT ((42*2**(J-1)+2**(J-1)*(2/3))+.5) 109 GOSUB 0125 111 GOSUB 0007 113 LET F=INT (48*2**(J-1)+.5) 115 GOSUB 0125 117 GOSUB 0007 119 NEXT J 121 GOTO 0183 123 REM %F%O%R% %N%O%T%E%S%. 125 LET F=INT ((1/(F*17E-6))+.5) 127 LET O$(J,N,2)=CHR$ (F-(INT (F/256)*256)) 129 LET O$(J,N,1)=CHR$ (INT (F/256)) 131 RETURN 133 FOR X=1 TO LEN N$ 135 LET N=CODE N$(X)-37 137 LET J=CODE X$(X)-28 139 LET D=(CODE D$(X)-28)*16 141 IF N<=128 THEN GOTO 0147 143 GOTO 0161 145 NEXT X 147 IF N<0 THEN GOTO 0175 149 POKE 16516,CODE O$(J,N,2) 151 POKE 16517,CODE O$(J,N,1) 153 POKE 16515,D 155 LET C=USR 16518 157 NEXT X 159 RETURN 161 LET N=N-128 163 POKE 16516,CODE R$(J,N,2) 165 POKE 16517,CODE R$(J,N,1) 167 POKE 16515,D 169 LET C=USR 16518 171 NEXT X 173 RETURN 175 FOR T=1 TO D*8 177 NEXT T 179 IF X<=LEN N$ THEN GOTO 0157 181 RETURN 183 REM %P%R%I%N%T%S% %B%A%R%S%. 185 FOR I=1 TO 30 187 PRINT AT 1,I;"-";AT 3,I;"-";AT 5,I;"-" 189 PRINT AT 7,I;"-";AT 9,I;"-" 191 NEXT I 193 FOR I=1 TO 9 195 PRINT AT I,1;"\: ";AT I,2;"\ :" 197 PRINT AT 14,1;"%D";AT 16,1;"%C%U%R%R%E%N%T %O%C%T%A%V%E%: " 199 NEXT I 201 SLOW 203 IF TT+T>=30 THEN GOTO 0457 205 PRINT AT A,TT+T;"\##" 207 IF INKEY$="" THEN GOTO 0207 209 IF INKEY$="O" THEN GOTO 0451 213 IF INKEY$="5" THEN GOTO 0243 215 IF INKEY$="M" THEN GOTO 715 217 IF INKEY$<>"A" THEN GOTO 0223 219 LET L=1 221 GOSUB 0307 223 IF INKEY$="Q" THEN GOSUB 0431 225 IF INKEY$="P" THEN GOSUB 0491 227 IF INKEY$="6" THEN GOSUB 0255 229 IF INKEY$="7" THEN GOSUB 0267 231 IF INKEY$="8" THEN GOTO 0279 233 IF INKEY$="D" THEN GOTO 0445 235 IF INKEY$="R" THEN GOSUB 0289 237 IF INKEY$="F" THEN GOSUB 0517 239 IF INKEY$="S" THEN GOSUB 0307 241 GOTO 0203 243 IF TT+T>3 THEN LET T=T-1 245 PRINT AT A,TT+T+1;V$(A,T+1) 247 PRINT AT A,TT+T;"\##" 249 LET PP=1 251 GOTO 0207 253 RETURN 255 PRINT AT A,TT+T;" " 257 IF A=10 OR A=11 OR A=2 OR A=4 OR A=6 OR A=8 THEN GOTO 0261 259 PRINT AT A,TT+T;"-" 261 IF A<11 THEN LET A=A+1 263 LET W=130 265 RETURN 267 PRINT AT A,TT+T;" " 269 IF A=10 OR A=11 OR A=2 OR A=4 OR A=6 OR A=8 THEN GOTO 0273 271 PRINT AT A,TT+T;"-" 273 IF A>1 THEN LET A=A-1 275 LET W=130 277 RETURN 279 PRINT AT A,TT+T;V$(A,T) 281 LET PP=0 283 IF TT+T<30 THEN LET T=T+1 285 PRINT AT A,TT+T;"\##" 287 GOTO 0207 289 FAST 291 LET U=T 293 GOSUB 0133 295 FOR I=1 TO REP-1 297 GOSUB 0133 299 NEXT I 301 SLOW 303 LET T=U 305 GOTO 0205 307 IF B+(T+1)<=LEN N$ THEN GOTO 0327 309 LET L$=N$ 311 DIM N$(B+(T+1)) 313 LET N$(1 TO LEN N$-2)=L$ 315 LET L$=X$ 317 DIM X$(B+(T+1)) 319 LET X$(1 TO LEN X$-2)=L$ 321 LET L$=D$ 323 DIM D$(B+(T+1)) 325 LET D$(1 TO LEN D$-2)=L$ 327 IF L=1 THEN GOTO 0665 329 LET SEMI=6 331 IF A=1 THEN LET N$(B+(T))="F" 333 IF A=2 THEN LET N$(B+(T))="E" 335 IF A=3 THEN LET N$(B+(T))="D" 337 IF A=4 THEN LET N$(B+(T))="C" 339 IF A=5 THEN LET N$(B+T)="B" 341 IF A=6 THEN LET N$(B+T)="A" 343 IF A=7 THEN LET N$(B+T)="G" 345 IF A=8 THEN LET N$(B+T)="F" 347 IF A=9 THEN LET N$(B+T)="E" 349 IF A=10 THEN LET N$(B+T)="D" 351 IF A=11 THEN LET N$(B+(T))="C" 353 LET V$(A,T)="$" 355 LET D$(B+(T))=J$(G) 357 LET X$(B+(T))=K$(Q) 359 IF A=SEMI OR A=5 OR A=2 OR A=1 OR A=3 OR A=4 THEN LET X$(B+(T))=K$(Q+1) 361 IF PP=0 THEN GOTO 0371 363 FOR I=1 TO 11 365 IF I=1 OR I=3 OR I=5 OR I=7 OR I=9 THEN PRINT AT I,TT+T;"-" 367 IF I=2 OR I=4 OR I=6 OR I=8 OR I=10 OR I=11 THEN PRINT AT I,TT+T;" " 369 NEXT I 371 PRINT AT A,TT+T;V$(A,T) 373 LET T=T+1 375 PRINT AT 14,(TT+T)-1;G 377 PRINT AT 16,16;Q 379 RETURN 381 REM %I%N%I%T%I%A%L%I%Z%A%T%I%O%N% %S%U%B 383 PRINT AT 10,2;"INPUT NOTE DURATION(1 TO 8)" 385 LET B=0 387 LET L=0 389 LET Q=0 391 LET W=128 393 LET H=9000 395 LET A=11 397 LET C=1 399 LET T=1 401 LET TT=2 403 LET PP=0 405 LET REP=0 407 IF INKEY$="" THEN GOTO 0407 409 LET G=VAL INKEY$ 411 LET J$="12345678" 413 CLS 415 PRINT AT 10,5;"INPUT OCTAVE(1 TO 6)" 417 IF INKEY$="" THEN GOTO 0417 419 LET Y=(VAL INKEY$)+1 421 LET K$="123456788" 423 LET Q=VAL K$(Y+1) 425 CLS 427 RETURN 429 REM %S%U%B% %T%O% %R%E%P%E%A%T% %T%U%N%E%. 431 PRINT AT 18,1;" YOU WANT THE TUNE TO REPEAT..." 433 PRINT AT 20,5;"HOW MANY TIMES?" 435 INPUT Y 437 LET REP=Y 439 PRINT AT 18,1;" " 441 PRINT AT 20,1;" " 443 RETURN 445 INPUT Y 447 IF Y>0 AND Y<9 THEN LET G=Y 449 GOTO 0205 451 INPUT Y 453 IF Y>0 AND Y<9 THEN LET Q=Y 455 GOTO 0205 457 REM %S%U%B%.% %T%O% %P%R%I%N%T% %2%N%D% %S%E%T% %O%F% %B%A%R%S 459 FOR I=1 TO 19 461 SCROLL 463 NEXT I 465 LET A=11 467 LET B=B+T-1 469 LET T=1 471 LET V$(1)="-----------------------------" 473 LET V$(2)=" " 475 FOR I=1 TO 9 STEP 2 477 LET V$(I)=V$(1) 479 LET V$(I+1)=V$(2) 481 NEXT I 483 LET V$(11)=V$(2) 485 PRINT AT 14,1;"%D";AT 16,1;"%C%U%R%R%E%N%T %O%C%T%A%V%E%: " 487 GOTO 0183 489 REM %S%U%B% %T%O% %C%H%A%N%G%E% %A%L%L% %O%C%T%A%V%E%S\: 491 PRINT AT 20,2;"INCREASE OR DECREASE?" 493 LET P$="12345678" 495 INPUT Y 497 FOR S=1 TO LEN X$-1 499 LET V=VAL X$(S) 501 IF Y=7 AND V<8 THEN LET V=V+1 503 IF Y=6 AND V>1 THEN LET V=V-1 505 LET X$(S)=P$(V) 507 NEXT S 509 PRINT AT 16,16;X$(LEN X$-1) 511 PRINT AT 20,2;" " 513 RETURN 515 REM %S%U%B% %T%O% %C%H%A%N%G%E% %O%V%E%R%A%L%L% % % %S%P%E%E%D% %O%F% %P%L%A%Y%. 517 PRINT AT 20,2;"INCREASE OR DECREASE?" 519 INPUT Y 521 FOR S=1 TO LEN D$-1 523 LET V=VAL D$(S) 525 IF Y=7 AND V<9 THEN LET V=V+1 527 IF Y=6 AND V>1 THEN LET V=V-1 529 LET D$(S)=J$(V) 531 NEXT S 533 PRINT AT 14,3;D$(B+1 TO ) 535 PRINT AT 20,2;" " 537 RETURN 663 REM %S%E%M%I%T%O%N%E% %S%U%B%. 665 IF A=1 THEN LET N$(B+T)="F" 667 IF A=2 THEN LET N$(B+T)="%E" 669 IF A=3 THEN LET N$(B+T)="%D" 671 IF A=4 THEN LET N$(B+T)="%C" 673 IF A=5 THEN LET N$(B+T)="%B" 675 IF A=6 THEN LET N$(B+T)="%A" 677 IF A=7 THEN LET N$(B+T)="%G" 679 IF A=8 THEN LET N$(B+T)="%F" 681 IF A=9 THEN LET N$(B+T)="%E" 683 IF A=10 THEN LET N$(B+T)="%D" 685 IF A=11 THEN LET N$(B+T)="%C" 687 LET L=0 689 LET SEMI=5 691 GOTO 0353 693 PRINT AT 5,8;"% % % % % % % % % % % % " 695 PRINT AT 6,8;"% % % % " 697 PRINT AT 7,8;"% % % % " 699 PRINT AT 8,8;"% % % % % % % % % % % % " 701 PRINT AT 9,8;"% % % " 703 PRINT AT 10,8;"% % % % % % % % % " 705 PRINT AT 12,12;"PRESENT" 707 PRINT AT 15,11;"%C%O%M%P%O%S%E%R% " 709 FOR F=1 TO 15 711 NEXT F 713 CLS 714 RETURN 715 PRINT AT 20,2;"START TAPE AND PRESS ""G""" 716 IF INKEY$<>"G" THEN GOTO 716 717 PRINT AT 20,2;" " 718 SAVE "COMPOSE%R" 719 GOTO 0203 721 SAVE "COMPOSE%R" 723 GOTO 1 ```