--- title: "Composition Editor" id: 54976 type: "computer_media" slug: "janice" url: "http://localhost/computer_media/janice/" markdown_url: "http://localhost/computer_media/janice.md" published_at: "2024-06-10T01:44:45+00:00" modified_at: "2026-03-30T21:41:32+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2019/02/20230809-041332.jpg" excerpt: "A three-voice composition editor lets you enter, edit, and play back music note-by-note, storing pitch data as raw sound-chip register values in string arrays." 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: "Bryan Lewis" slug: "bryan-lewis" taxonomy: "indiv" url: "http://localhost/indiv/bryan-lewis/" genre: - name: "Music" slug: "music" taxonomy: "genre" url: "http://localhost/type/music/" media_contents: - id: 54964 title: "ISTUG Public Domain Library 6" type: "computer_media" url: "http://localhost/computer_media/istug-public-domain-library-6/" media_type: "Program" programmers: - name: "Bryan Lewis" slug: "bryan-lewis" taxonomy: "indiv" url: "http://localhost/indiv/bryan-lewis/" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2024/06/SCR-20260329-njck.png" media_type_tags: "Music" --- This program is a three-voice composition editor that allows users to enter, edit, and play back musical notes using the TS2068’s hardware sound chip (AY-3-8910). Notes are encoded as two-byte values stored in fixed-length string arrays, with each pair of characters holding the register values needed to set pitch on one of the chip’s three tone channels. Note names (c, d, e, f, g, a, b and their sharps) are pre-mapped to numeric indices via single-letter variable assignments at line 26, and a 3D array `n` translates note/octave pairs into the corresponding AY register byte values. Playback iterates through up to 400 note slots per voice, writing directly to AY registers via consecutive `SOUND` statements with a variable speed pause between steps. An edit mode allows real-time single-step navigation through a voice using key presses, with inline note replacement via subroutines at lines 1000 and 1050. *** ## Program Analysis ### Program Structure The program is organized around a main menu at line 50 that dispatches to five sections via a computed `GO TO` at line 55: `GO TO 80+opt*20`. This maps menu options 1–5 to lines 100, 120, 140, 160, and 180 respectively. The five sections cover entering notes for Voice A, Voice B, Voice C, editing, and playback. | Option | Target Line | Function | | --- | --- | --- | | 1 | 100 | Enter Voice A notes | | 2 | 120 | Enter Voice B notes | | 3 | 140 | Enter Voice C notes | | 4 | 160/200 | Edit a voice | | 5 | 180 | Play back composition | ### Note Encoding and the `n` Array A 3D numeric array `n` (implicitly dimensioned elsewhere or pre-populated) maps a note index and octave number to two AY-3-8910 register bytes. Line 16 seeds one entry: `n(6,1,1)=228` and `n(6,1,2)=4`, which represents the coarse and fine pitch bytes for one note. At line 26, single-letter variables are assigned integer indices: `c=1, cc=2, d=3, dd=4, e=5, f=6, ff=7, g=8, gg=9, a=10, aa=11, b=12`. This allows the user to type note names like `f1` or `g2`, from which `n1` (note index, all characters except the last) and `n2` (octave, last character) are extracted via `VAL`. ### String Array Storage Notes are stored in fixed-length two-character string arrays `a$`, `b$`, and `c$`, each dimensioned `(1000,2)` as noted in the REM at line 20. Each element pair holds the two AY register bytes as characters via `CHR$`, and they are retrieved during playback with `CODE a$(j,1)` and `CODE a$(j,2)`. A rest is represented by storing `CHR$ 0` in both elements. ### Playback Mechanism Playback at lines 190–194 uses consecutive `SOUND` statements to load AY registers directly. Registers 0–1 carry Voice A pitch, 2–3 carry Voice B pitch, and 4–5 carry Voice C pitch (with register 4 decremented by 1 at line 192, possibly to compensate for an off-by-one in the note table). Register 7 sets the mixer, and registers 8–10 set channel volumes. The loop runs to note 400 and restarts from the same start position, creating a loop. Pressing `x` during playback silences the chip and returns to the menu. ### Edit Mode Edit mode (lines 200–229) lets the user navigate through notes of a single voice in real time. A computed `GO TO voice*10+200` at line 205 dispatches to the appropriate voice editor (210 for A, 220 for B). Within each editor, the current note plays continuously via `SOUND`, and `INKEY$` polling at lines 216/226 moves forward (`"7"`) or backward (`"6"`) through note slots. Pressing `"i"` triggers a `GO SUB` to a note-input routine at line 1000 or 1050. There is no edit handler for Voice C — line 230 does not exist, so selecting voice 3 in edit mode would cause an error. ### Key BASIC Idioms - Computed `GO TO` for menu dispatch: `GO TO 80+opt*20` and `GO TO voice*10+200`. - Boolean arithmetic for navigation: `LET j=j+(INKEY$="7")-(INKEY$="6")` increments or decrements `j` based on key state. - `INPUT ... LINE k$` used to accept unquoted string input including note names. - The last entered note is remembered in `z$` and reused when the user presses ENTER without typing a new note (lines 107, 127, 147). - `VAL n$` used to parse a numeric start position from string input. ### Notable Techniques The note string is parsed by splitting on length: `k$(1 TO (LEN k$-1))` gives the note name/index portion, and `k$(LEN k$)` gives the octave digit. Since note indices are assigned to variables (`f=6`, etc.), the user can type something like `f1` or `g2`, and `VAL "f1"(1 TO 1)` would return the value of variable `f` — a clever use of BASIC’s `VAL` evaluating variable names within strings. The dummy `FOR/NEXT` loop at lines 22–24 (iterating 1 to 1000 with no body) is used as a progress indicator during a “Configuring memory now” message, serving as a visible delay while appearing to perform initialization work. ### Bugs and Anomalies - Line 20 is a `REM` containing the `DIM` statements for `a$`, `b$`, and `c$`. These arrays are never actually dimensioned, so accessing them at runtime would produce an error unless the arrays were pre-existing in memory from a prior session. - Voice C has no edit subroutine — `GO TO voice*10+200` with `voice=3` targets line 230, which does not exist. - The typo “Compostion” (missing ‘i’) appears in the title at line 10, and “promt” (missing ‘p’) appears in the entry prompts at lines 100, 120, and 140. - Line 16 pre-populates one cell of array `n` before the array is dimensioned, which would cause an error on a fresh run; the array must be pre-loaded separately. - At line 192, `CODE c$(j,1)-1` subtracts 1 from the Voice C coarse pitch register value, which may be intentional tuning compensation but is inconsistent with voices A and B. - Variable name case inconsistency: `JJG` and `jjg` are used interchangeably (lines 101–103, 203), which in Sinclair BASIC are treated as the same variable since variable names are case-insensitive for numeric variables. ## Source Code ``` 10 BEEP .05,30: CLS : PRINT "Compostion Editor"'"V1.1 27 November 84"'"Bryan Lewis" 16 LET n(6,1,1)=228: LET n(6,1,2)=4 20 REM DIM a$(1000,2): DIM b$(1000,2): DIM c$(1000,2) 21 CLS : PRINT "OK"''"Configuring memory now" 22 FOR f=1 TO 1000 24 NEXT f 25 PRINT '''"OK" 26 LET c=1: LET cc=2: LET d=3: LET dd=4: LET e=5: LET f=6: LET ff=7: LET g=8: LET gg=9: LET a=10: LET aa=11: LET b=12 30 LET z$="stop" 50 CLS : PRINT ;TAB 7;"Options";AT 5,0;TAB 7;"1. Enter Voice A"'TAB 7;"2. Enter Voice B"';TAB 7;"3. Enter Voice C"';TAB 7;"4. Edit";'TAB 7;"5. Play" 51 SOUND 7,63 52 INPUT opt 54 CLS : IF opt>5 OR opt<1 THEN GO TO 50 55 GO TO 80+opt*20 100 PRINT "Enter note on promt. If no "'"change, press 'ENTER'" 101 PRINT "Enter 'stop' to EXIT": PAUSE 600: INPUT "START NOTE#? ";n$: IF n$="" THEN LET JJG=1: GO TO 103 102 LET jjg=VAL n$ 103 FOR j=JJG TO 1000 104 PRINT AT 10,0;"NOTE#: ";j;" "'''''"MEASURE: ";(j-1)/16+1;" in 4/4 (16th's)";AT 11,0;"NOTE: "; 106 INPUT "NOTE? "; LINE k$: IF k$="r" THEN LET a$(j,1)=CHR$ 0: LET a$(j,2)=CHR$ 0: NEXT j 107 IF k$="" THEN LET k$=z$ 108 IF k$="stop" THEN GO TO 50 109 LET n1=VAL k$(1 TO (LEN k$-1)) 110 LET n2=VAL k$(LEN k$) 111 LET z$=k$ 112 LET a$(j,1)=CHR$ n(n1,n2,1) 113 LET a$(j,2)=CHR$ n(n1,n2,2) 114 PRINT k$;" " 115 NEXT j 116 GO TO 50 120 PRINT "Enter note on promt. If no "'"change, press 'ENTER'" 121 PRINT "Enter 'stop' to EXIT": INPUT "START NOTE#? ";n$: IF n$="" THEN LET jjg=1: GO TO 123 122 LET jjg=VAL n$ 123 FOR j=jjg TO 1000 124 PRINT AT 10,0;"NOTE#: ";j;" " 125 PRINT ''''"MEASURE: ";(j-1)/16+1;" in 4/4 (16th's)";AT 11,0;"NOTE: "; 126 INPUT "NOTE? "; LINE k$: IF k$="r" THEN LET b$(j,1)=CHR$ 0: LET b$(j,2)=CHR$ 0: NEXT j 127 IF k$="" THEN LET k$=z$ 128 IF k$="stop" THEN GO TO 50 129 LET n1=VAL k$(1 TO (LEN k$-1)) 130 LET n2=VAL k$(LEN k$) 131 LET z$=k$ 132 LET b$(j,1)=CHR$ n(n1,n2,1) 133 LET b$(j,2)=CHR$ n(n1,n2,2) 134 PRINT k$;" " 135 NEXT j 136 GO TO 50 140 PRINT "Enter note on promt. If no "'"change, press 'ENTER'" 141 PRINT "Enter 'stop' to EXIT": INPUT "St NOTE#? ";n$: IF n$="" THEN LET jjg=1: GO TO 143 142 LET jjg=VAL n$ 143 FOR j=jjg TO 1000 144 PRINT AT 10,0;"NOTE#: ";j;" " 145 PRINT ''''"MEASURE: ";(j-1)/16+1;" in 4/4 (16th's)";AT 11,0;"NOTE: "; 146 INPUT "NOTE? "; LINE m$: IF m$="r" THEN LET c$(j,1)=CHR$ 0: LET c$(j,2)=CHR$ 0: NEXT j 147 IF m$="" THEN LET m$=z$ 148 IF m$="stop" THEN GO TO 50 149 LET o1=VAL m$(1 TO (LEN m$-1)) 150 LET o2=VAL m$(LEN m$) 151 LET z$=m$ 152 LET c$(j,1)=CHR$ n(o1,o2,1) 153 LET c$(j,2)=CHR$ n(o1,o2,2) 154 PRINT m$;" " 155 NEXT j 156 GO TO 50 160 REM EDIT: GO TO 200 161 GO TO 200 180 CLS : PRINT "PLAY" 181 INPUT "A Volume",avol: INPUT "B volume",bvol: INPUT "C Volume",cvol 182 INPUT "Speed",sp,"START#? ";jjg 185 SOUND 7,56;8,avol;9,bvol;10,cvol 190 FOR j=jjg TO 400 191 SOUND 0,CODE a$(j,1);1,CODE a$(j,2);2,CODE b$(j,1);3,CODE b$(j,2) 192 SOUND 4,CODE c$(j,1)-1;5,CODE c$(j,2): PAUSE sp 193 IF INKEY$="x" THEN SOUND 8,0;9,0;10,0: GO TO 50 194 NEXT j: GO TO 190 195 GO TO 50 200 CLS : INPUT "What voice? (1=a, 2=b, 3=c) ";voice 201 PRINT "Use ^ to go fwd"'"Use down arrow to go bckwd": PAUSE 480 202 CLS 203 INPUT "START WHERE? (#) ";JJG 205 GO TO voice*10+200 210 LET j=JJG 211 SOUND 0,CODE a$(j,1);1,CODE a$(j,2);7,62;8,15 212 PRINT AT 10,0;"NOTE#: ";j;" " 214 IF INKEY$="x" THEN GO TO 50 215 IF INKEY$="i" THEN GO SUB 1000 216 LET j=j+(INKEY$="7")-(INKEY$="6"): IF j<1 THEN LET j=1 219 GO TO 211 220 LET j=JJG 221 SOUND 0,CODE b$(j,1);1,CODE b$(j,2);7,62;8,15 222 PRINT AT 10,0;"NOTE#: ";j;" " 224 IF INKEY$="x" THEN GO TO 50 225 IF INKEY$="i" THEN GO SUB 1050 226 LET j=j+(INKEY$="7")-(INKEY$="6"): IF j<1 THEN LET j=1 229 GO TO 221 1000 INPUT "NOTE? "; LINE m$: IF m$="r" THEN LET a$(j,1)=CHR$ 0: LET a$(j,2)=CHR$ 0: RETURN 1002 LET n1=VAL m$( TO (LEN m$-1)): LET n2=VAL m$(LEN m$): LET a$(j,1)=CHR$ n(n1,n2,1): LET a$(j,2)=CHR$ n(n1,n2,2) 1004 RETURN 1050 INPUT "NOTE? "; LINE m$: IF m$="r" THEN LET b$(j,1)=CHR$ 0: LET b$(j,2)=CHR$ 0: RETURN 1052 LET n1=VAL m$( TO (LEN m$-1)): LET n2=VAL m$(LEN m$): LET b$(j,1)=CHR$ n(n1,n2,1): LET b$(j,2)=CHR$ n(n1,n2,2) 1054 RETURN 9998 SAVE "janice" LINE 50 ```