--- title: "Capital Area Timex Sinclair Users Group Membership Files" id: 56096 type: "computer_media" slug: "capital-area-timex-sinclair-users-group-membership-files" url: "http://localhost/computer_media/capital-area-timex-sinclair-users-group-membership-files/" markdown_url: "http://localhost/computer_media/capital-area-timex-sinclair-users-group-membership-files.md" published_at: "2024-07-19T00:47:18+00:00" modified_at: "2026-03-30T21:29:22+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/07/SCR-20240718-salm.png" excerpt: "A full-featured membership database with Shell sort, payment-expiry tracking, and formatted roster printing — all built in BASIC for a real computer club." 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: "Mark Fisher" slug: "mark-fisher" taxonomy: "indiv" url: "http://localhost/indiv/mark-fisher/" genre: - name: "Database" slug: "database" taxonomy: "genre" url: "http://localhost/type/database/" media_type: "Program" programmers: - name: "Mark Fisher" slug: "mark-fisher" taxonomy: "indiv" url: "http://localhost/indiv/mark-fisher/" download_url: "https://archive.org/download/timex-sinclair-software-archive/CATS%20Member%20List%20%281987%29%28Capital%20Area%20Timex%20Sinclair%20Users%20Group%29%28TS2068%29%28US%29%28Program%29.zip" mediadate: "1987" images: - url: "http://localhost/wp-content/uploads/2024/07/SCR-20240718-salm.png" related_content: - id: 24821 title: "Capital Area Timex Sinclair Users Group" type: "usergroup" url: "http://localhost/usergroup/capital-area-timex-sinclair-users-group/" media_type_tags: "Database" --- # Capital Area Timex Sinclair Users Group Membership Files This program is a membership database manager for [CATS](http://localhost/usergroup/capital-area-timex-sinclair-users-group/), written in 1983 by [Ned Beeler](http://localhost/indiv/edward-ned-beeler/) and revised in 1985 by [Mark Fisher](http://localhost/indiv/mark-fisher/). It stores up to 214 member records across parallel string arrays covering name, street, city, state, ZIP, home phone, office phone, job, equipment, and a payment-status field. The main menu offers seven functions: adding records, searching and editing records with a cursor-driven update screen, listing to screen, printing mailing labels, printing a formatted roster, printing a ZIP-code-sorted list, and running a Shell sort on any chosen array variable. Payment currency is determined by comparing a stored date field against today’s date using a month-offset formula (`VAL I$(TO 2)+12*VAL I$(4 TO 5)-12`), and lapsed members receive an LPRINT-formatted “LAST ISSUE” renewal notice with a $18.00 dues amount. Printer control uses Epson-style escape sequences embedded in LPRINT statements, and two POKEs at addresses 64256 and 64260 configure printer hardware settings. *** ## Program Analysis ### Program Structure The program is organized as a dispatch table centered on a main menu loop at line `1000`–`1499`. Menu choices 1–7 are routed via the expression `GO SUB VAL J$*500+G` (line `1200`), where `G` is a constant (likely `1000`), mapping digit “1” to subroutine 1500, “2” to 2000 (search/update), “3” to 2500 (list), “4” to 3000 (label print), “5” to 3500 (roster print), “6” to 4000 (ZIP list), and “7” to 4500 (sort). Lines 0–14 and 50–96 form a field-entry subroutine bank, where `GO SUB I` inside a `FOR I=O TO 13` loop dispatches to the matching line number for each field. ### Key Constants and Variables Several single-letter variables serve as constants throughout, established before the listing begins: - `L` = 1 (used as numeric constant one and loop start) - `O` = 0 (used as zero in AT coordinates and loop bounds) - `D` = 10 (column offset and field index) - `M` = 1 (used in PRINT AT for membership number row) - `G` = 1000 (base for menu dispatch GO SUB) - `W$` = a separator/ruler string printed between sections - `X$` holds the current sort key description displayed on the menu - `N` = current number of records; `K` = last membership number assigned ### Data Storage Records are held in parallel string arrays, each dimensioned to at least 214 elements: | Array | Field | | --- | --- | | `N$(J)` | Name | | `R$(J)` | Street address | | `C$(J)` | City | | `S$(J)` | State | | `Z$(J)` | ZIP code | | `H$(J)` | Home phone | | `P$(J)` | Office phone | | `O$(J)` | Job/occupation | | `E$(J)` | Equipment owned | | `D$(J)` | Payment record (date in columns 1–5, status in column 10) | The `D$(J)` array is multipurpose: columns 1–5 store the payment date (MO-YR format), column 6 onward stores the membership number as a string, and column 10 stores a single-character payment status code. ### Payment Expiry Logic At lines `1000` and `3010`, the current date is converted to a month-offset integer using `VAL I$(TO L+L)+12*VAL I$(4 TO 5)-12`. In the label-print routine (line `3220`–`3225`), each member’s stored payment date undergoes the same conversion and is compared to `Q`. Members with status `"0"` are silently skipped; those with status above `"4"` are treated as currently paid; those whose date offset does not exceed `Q` (i.e., paid within 12 months) are also skipped. All others receive an LPRINT-formatted renewal notice including Epson escape sequences for emphasis and a $18.00 dues reminder, and their status is set to `"0"`. ### Cursor-Driven Update Screen The update/delete routine (lines `2100`–`2499`) implements a simple full-screen record editor. A highlighted bar (`FLASH`, `OVER`) is drawn at row `Q` using `W$`. The user moves the bar with keys `"6"` (down) and `"7"` (up), checked via `INKEY$` in a tight loop at line `2220`. Row position `Q` maps to specific actions: - Rows 0–13: dispatch `GO SUB Q` to the matching field-entry subroutine - Row 15: forward to next record (`GO SUB O`, i.e., GO SUB 0) - Rows 16–17: navigate forward/backward through records - Row 19: delete with confirmation - Row >19: return to menu Bounds wrapping at line `2230` uses the expression `Q+(Q<0)-(Q>20)`, adding or subtracting 1 when the cursor goes out of range — a common Sinclair BASIC boolean arithmetic idiom. ### Shell Sort Implementation Lines `4500`–`4999` implement a Shell (diminishing-increment) sort. The gap sequence is computed by halving `Q` repeatedly (`Q=INT(Q/2)`), with odd-gap enforcement at line `4530`. The sort variable is user-selectable: choices “1” and “2” map to `N$(J)` and `Z$(J)` respectively, or the user may type any array expression directly. The comparison at line `4545` uses a remarkable runtime string construction technique: `IF VAL$ J$<=VAL$ (J$(TO 4)+"+Q"+J$(5 TO))` This builds a string like `"N$(J+Q)"` by splicing `"+Q"` into the sort-key expression string at position 5, then evaluates it with `VAL$` — effectively an eval-based comparison that avoids hardcoding the sort field. When a swap is needed, all ten parallel arrays are swapped together using temporary variables (`A$` through `Y$`). ### Printer Control All print routines use Epson-style escape codes embedded as string concatenation: `CHR$ 27+"N"` (normal pitch), `CHR$ 27+"!"` (condensed), `CHR$ 27+"X..."+CHR$ 27+"Y..."+CHR$ 27+"Q..."` for formatted renewal notices. The two POKEs at addresses `64256` and `64260` configure the TS2068’s printer interface port settings, switching between different printer modes across routines. ### Initialization and Auto-Start Line `5060` is the program’s auto-start entry point (used in the `SAVE I$ LINE 5060` at line `5010`). It sets system variables via POKEs (scroll count at 23561/23562, caps lock at 23658, and flags at 23609), configures the display with `BORDER 0: PAPER 0: INK 7`, and prompts for the date. The date-parsing loop at lines `5080`–`5110` finds the first non-digit character (the separator) and normalizes the date to a two-digit month, hyphen, two-digit year format stored in `I$`. ### Notable Techniques and Anomalies - The field-dispatch loop `FOR I=O TO 13: GO SUB I: NEXT I` targets line numbers 0–13 directly, keeping field routines at the very top of the program for fast lookup. - Lines `2`, `8`, and `11` are bare `RETURN` statements acting as stubs for unimplemented fields (family, and two phone-adjacent fields). - Lines `90`–`94` are commented out with `REM`, preserving an earlier screen-clearing loop that was apparently superseded. - Line `9000`–`9050` is a maintenance utility (with the print statement itself also REMmed out at line `9010`) that bulk-deletes records by calling `GO SUB 2400` in reverse order — reverse traversal is necessary to avoid index shifting during deletion. - Line `9500` is a one-off data-migration snippet that replaces status code `"Q"` with `"4"` across all records. - At line `3570`, the roster print routine uses `IF D$(J,D)<"4" THEN NEXT J` — placing `NEXT J` inside an `IF` statement, which is valid in Sinclair BASIC and skips non-current members without a `GO TO`. - Line `2275` reads `IF Q=15 THEN GO SUB O`, calling `GO SUB 0` (the payment status subroutine at line 0) when the cursor is on the “UPDATE” row — the row numbering on screen and the `Q` values do not align intuitively with the displayed menu rows. ## Source Code ``` 0 LET D$(J,D)="5": PRINT AT L,D-L;D$(J,D): LET D$(J, TO 5)=I$: PRINT AT L,16;D$(J, TO 5): RETURN 1 INPUT "PAYMENT STATUS ";D$(J,D): PRINT AT L,D-L;D$(J,D): RETURN 2 RETURN 3 INPUT "NAME ";N$(J): PRINT AT 3,D;N$(J): RETURN 4 INPUT "STREET ";R$(J): PRINT AT 4,D;R$(J): RETURN 5 INPUT "CITY ";C$(J): PRINT AT 5,D;C$(J): RETURN 6 INPUT "STATE ";S$(J): PRINT AT 6,D;S$(J): RETURN 7 INPUT "ZIP CODE ";Z$(J): PRINT AT 7,D;Z$(J): RETURN 8 RETURN 9 INPUT "HOME PHONE ";H$(J): PRINT AT D-L,D;H$(J): RETURN 10 INPUT "OFFICE PHONE ";P$(J): PRINT AT D,D;P$(J): RETURN 11 RETURN 12 INPUT "JOB ";O$(J): PRINT AT 12,D;O$(J): RETURN 13 GO SUB 50: INPUT "EQUIPMENT";E$(J): PRINT AT 13,D;E$(J) 14 RETURN 50 PRINT AT 17,O;"A.TS1000 F.E-PROM K.FLOPPY " 52 PRINT "B.TS1500 G.16KRAM L.STRNGY " 54 PRINT "C.TS2068 H.32KRAM M. " 56 PRINT "D.PRINTER I.64KRAM N. " 58 PRINT "E.MODEM J.DISK O. " 62 RETURN 90 REM FOR I=16 TO D+D+L 92 REM PRINT AT I,O;" " 94 REM NEXT I 96 RETURN 100 PRINT "NAME :"';"STREET :"';"CITY :"';"STATE :"';"ZIP CODE :" 120 PRINT W$ 130 PRINT "H-PHONE :"'"O-PHONE :"'"FAMILY :"'"JOB :"'"EQUIP. :" 140 PRINT W$ 150 RETURN 999 STOP 1000 LET Q=VAL I$( TO L+L)+12*VAL I$(4 TO 5)-12 1006 POKE 64256,L: POKE 64260,O 1010 CLS 1020 PRINT ''"******* MEMBERSHIP FILES ******* by Ned Beeler 1983 revised by M. Fisher '85 " 1025 PRINT " TODAY'S DATE """;I$;"""" 1030 PRINT '"1. ADD NEW NAME"''"2. OR """"; UPDATE/DELETE RECORD"''"3. LIST RECORDS"'' 1050 PRINT "4. LPRINT LABELS"''"5. LPRINT ROSTER"''"6. LPRINT ZIP LIST "'' 1075 PRINT "7. SORT: curr. by ";X$''"8. SAVE program" 1090 INPUT "SELECT YOUR CHOICE";J$ 1095 CLS 1151 IF LEN J$58 THEN GO TO G 1200 GO SUB VAL J$*500+G 1499 GO TO G 1500 IF N>=214 THEN PRINT "FILE FULL": PAUSE D*D: RETURN 1505 LET N=N+L: LET K=K+L: LET J=N 1510 CLS 1520 PRINT "FILE NO. ";N,"MEMBSHP NO. ";K 1525 PRINT "----PAID ------------DATE -----" 1530 PRINT W$ 1535 GO SUB D*D 1546 LET D$(N,6 TO D-L)=STR$ K: PRINT AT M,28;D$(N,6 TO D-L) 1550 FOR I=O TO 13: GO SUB I: NEXT I 1655 GO SUB D*D-D 1680 INPUT "IS ALL CORRECT (Y/N) ";J$ 1700 IF J$="N" THEN GO SUB 2100 1720 CLS 1730 INPUT "ANY MORE NAMES? (Y/N) ";J$ 1750 IF J$="Y" THEN GO TO 1500 1999 RETURN 2010 CLS 2020 INPUT "SEARCH NAME ? ";J$ 2030 FOR J=L TO N 2040 IF J$=N$(J, TO LEN J$) THEN GO TO 2100 2050 NEXT J 2055 CLS 2060 INPUT " NO NAME-PRESS ENTER TO RETURN ";J$ 2070 RETURN 2100 LET Q=15 2105 CLS 2110 PRINT "FILE NO. ";J;" MEMBERSHIP NO. ";D$(J,6 TO D-L) 2120 PRINT "PAID---- ";D$(J,D);" DATE ";D$(J, TO 5) 2125 PRINT W$ 2130 GO SUB D*D 2150 PRINT AT 3,D;N$(J);AT 4,D;R$(J);AT 5,D;C$(J) 2160 PRINT AT 6,D;S$(J);AT 7,D;Z$(J) 2170 PRINT AT D-L,D;H$(J);AT D,D;P$(J) 2180 PRINT AT 12,D;O$(J);AT 13,D;E$(J) 2195 PRINT W$ 2200 PRINT "UPDATE"'"FOREWARD"'"BACK"'" "'" DELETE "'" "'">--- RETURN TO MENU ---> OR (M)"; 2210 PRINT #1;AT 0,0;"PUT CURSOR ON LINE TO BE CHANGEDPRESS WHEN READY" 2215 PRINT OVER L; FLASH L;AT Q,O;W$; 2216 PAUSE O 2217 PRINT OVER L; FLASH O;AT Q,O;W$ 2220 LET Q=Q+(INKEY$="6")-(INKEY$="7") 2230 LET Q=Q+(Q<0)-(Q>20) 2240 IF INKEY$=CHR$ 13 THEN GO TO 2270 2245 IF INKEY$="M" THEN RETURN 2250 GO TO 2215 2275 IF Q=15 THEN GO SUB O 2280 IF Q=16 OR Q=17 THEN LET J=J+(Q=16 AND J<215)-(Q=17 AND J>1): GO TO 2105 2310 IF Q>19 THEN RETURN 2320 IF Q<14 THEN GO SUB Q 2380 IF Q=19 THEN INPUT "DELETE: ARE YOU SURE? (Y/N) ";J$: IF J$="Y" THEN GO TO 2400 2398 GO TO 2210 2400 FOR Z=J TO N 2402 LET N$(Z)=N$(Z+L) 2404 LET R$(Z)=R$(Z+L) 2406 LET C$(Z)=C$(Z+L) 2408 LET S$(Z)=S$(Z+L) 2410 LET H$(Z)=H$(Z+L) 2412 LET P$(Z)=P$(Z+L) 2414 LET Z$(Z)=Z$(Z+L) 2416 LET O$(Z)=O$(Z+L) 2420 LET E$(Z)=E$(Z+L) 2422 LET D$(Z)=D$(Z+L) 2430 NEXT Z 2440 LET N=N-L 2499 RETURN 2504 LET Z=0 2505 FOR J=L TO N 2510 PRINT J;" ";N$(J, TO 18);D$(J): LET Z=Z+1 2550 NEXT J 2555 PRINT Z 2560 PAUSE 0 2990 RETURN 3000 CLS 3010 LET Q=VAL I$( TO 2)+12*VAL I$(4 TO 5)-12 3100 POKE 64256,O: POKE 64260,D 3200 LPRINT CHR$ 27+"N TEST XXXXX"'''''' 3205 PAUSE 0 3210 FOR J=L TO N 3220 IF D$(J,D)>"4" THEN LPRINT : GO TO 3230: REM current year's member 3221 IF D$(J,D)="0" THEN GO TO 3330: REM previously crossed off list 3222 IF VAL D$(J, TO L+L)+12*VAL D$(J,4 TO 5)>Q THEN LPRINT : GO TO 3230: REM less than 12 months since paid - still current 3225 LPRINT CHR$ 27+"XLAST ISSUE"+CHR$ 27+"Y-"+CHR$ 27+"Q$18.00; we need your support."+CHR$ 27+"N": LET D$(J,D)="0" 3230 LPRINT N$(J);" pd ";D$(J, TO 5);" ";D$(J,D) 3250 LPRINT R$(J) 3270 LPRINT C$(J);" "; 3280 LPRINT S$(J);" ";Z$(J) 3310 LPRINT '' 3330 NEXT J 3340 RETURN 3500 REM LPRINT "..." 3506 POKE 64256,O: POKE 64260,D 3507 LPRINT CHR$ 27+"N"+CHR$ 27+"!" 3510 LPRINT "CATS MEMBERSHIP, ";I$ 3515 LPRINT "Name # Address H# O#"' 3520 LET LINE=3 3560 FOR J=L TO N 3566 IF LINE=57 THEN LET LINE=2: PAUSE 0: LPRINT CHR$ 27+"NName # Address H# O#"' 3569 IF N$(J,1)<>N$(J-1,1) THEN LET LINE=LINE+1: LPRINT 3570 IF D$(J,D)<"4" THEN NEXT J 3575 LET LINE=LINE+1 3580 LPRINT CHR$ 27;"N"; 3590 LPRINT N$(J);CHR$ 27;"Q ";D$(J,6 TO );" "; 3600 LPRINT R$(J);" "; 3610 LPRINT C$(J);" "; 3620 LPRINT S$(J);" ";Z$(J);" ";H$(J);" ";P$(J) 3630 NEXT J 3640 RETURN 4000 LPRINT CHR$ 27+"N" 4002 POKE 64256,O: POKE 64260,D 4003 LPRINT CHR$ 27+"N"+CHR$ 27+"!" 4004 LET J$=Z$(L) 4010 LPRINT "CATS members, sorted by ZIP, ";I$ 4020 FOR J=L TO N 4025 IF D$(J,10)<"3" THEN GO TO 4050 4028 IF J$( TO 3)<>Z$(J, TO 3) THEN LPRINT : LET LINE=LINE+1 4030 LPRINT N$(J);CHR$ 27+"Q"+C$(J)+CHR$ 27+"N"+S$(J);" ";Z$(J) 4040 LET J$=Z$(J) 4050 NEXT J 4100 RETURN 4500 CLS : PRINT "SORT BY:","(1) N$(J) NAME"'',"(2) Z$(J) ZIP" 4510 INPUT "SELECT SORT,OR ANY VAR";J$: PRINT ''"SORTED BY: ";J$ 4512 IF J$="1" THEN LET J$="N$(J)" 4513 IF J$="2" THEN LET J$="Z$(J)" 4515 LET T=N 4520 LET Q=T 4525 LET Q=INT (Q/2): IF Q0 THEN GO TO 4544 4750 NEXT I 4999 GO TO 4525 5000 CLS : PRINT "WILL BE SAVED AS; """;I$;"""" 5010 SAVE I$ LINE 5060: PRINT ''"Rewind tape and VERIFY" 5020 VERIFY I$ 5030 RETURN 5060 POKE 23561,30: POKE 23562,2: POKE 23658,8: POKE 23609,3: BORDER 0: PAPER 0: INK 7 5065 POKE 64260,0: FOR J=64263 TO 64265: POKE J,0: NEXT J 5070 INPUT "TODAY'S DATE? (MO-YR) ";I$ 5080 FOR I=L TO 5: IF I$(I)<"0" THEN GO TO 5100 5090 NEXT I 5100 LET I$( TO L+L)=I$( TO I-L): LET I$(3 TO )="-"+I$(I+L TO ) 5110 GO TO G 8996 STOP 9000 FOR J=N TO 196 STEP -1 9010 REM IF D$(J,10)<"3" THEN PRINT "OUT NO.";J,N$(J): GO SUB 2400 9020 GO SUB 2400 9050 NEXT J: STOP 9500 FOR J=L TO N: IF D$(J,10)="Q" THEN LET D$(J,10)="4": 9510 NEXT J ```