--- title: "CHRs Gen" id: 71461 type: "computer_media" slug: "chrs-gen" url: "http://localhost/computer_media/chrs-gen/" markdown_url: "http://localhost/computer_media/chrs-gen.md" published_at: "2026-09-09T09:04:46+00:00" modified_at: "2026-09-09T09:04:46+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2026/09/chrs-gen.png" excerpt: "A font-remapping utility that rewrites every character definition in RAM, then auto-prints instructions before wiping itself — leaving behind a boldened printer font." 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/" basic: - name: "User Defined Graphics (UDG)" slug: "user-defined-graphics-udg" taxonomy: "basic" url: "http://localhost/basic/user-defined-graphics-udg/" model: - name: "Timex/Sinclair 2068" slug: "ts-2068" taxonomy: "model" url: "http://localhost/model/ts-2068/" genre: - name: "User Defined Graphics" slug: "user-defined-graphics" taxonomy: "genre" url: "http://localhost/type/user-defined-graphics/" media_type: "Program" download_url: "https://archive.org/download/timex-sinclair-software-archive/CHRs%20Gen%20(198x)(Biddell%2C%20M.%20P..)(TS2068)(US)(Program).zip" tsrun_member: "CHRs Gen (198x)(Biddell, M. P..)(TS2068)(US)(Program).tap" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2026/09/chrs-gen.png" media_type_tags: "User Defined Graphics" --- # CHRs Gen CHRs Gen is a character font generator that creates a modified TS 2068 character set in RAM starting at address 64000, intended for use with a printer (specifically a 2040-series printer). The program reads the ROM character definitions from addresses 15616–16383 using PEEK, applies a series of byte-value substitutions to thicken or restyle each character’s pixel rows, and POKEs the results into a 1536-byte block. System variable 23607 (CHARS) is manipulated to switch between the new font (249/0 combination) and the default ROM font (60), while 23609 controls the printer’s speed. After building the font, the program prints instructions, triggers two COPY operations to produce hardcopy output, then self-destructs with NEW. *** ### Program Structure The program divides into four logical phases: 1. **Initialization** (lines 20–70): plays a startup beep fanfare, sets `CLEAR 63999` to reserve high RAM, configures display attributes, and pokes printer speed into address 23609. 2. **First font block** (lines 80–270): iterates over ROM addresses `15616`–`16135` (the first 65 characters’ pixel rows), applies byte substitution rules, and POKEs results to `64000`–`64519`. 3. **Second font block** (lines 280–510): iterates over ROM addresses `16136`–`16383` (remaining characters and UDGs), applies a separate set of substitution rules, and POKEs results starting at `64520`. 4. **Output and self-destruction** (lines 520–750): switches CHARS pointer to the new font, prints and copies instructions twice (once with new font, once with standard font for comparison), then calls `NEW`. ### Font Relocation Mechanism The Spectrum’s system variable at address 23607 (`CHARS`, the low byte of the character set pointer) is the key to font switching. By default the ROM font lives at 15360; the system variable pair 23606/23607 holds a pointer offset by −256. Setting 23607 to 249 and 23606 to 0 (lines 520–530) points the system at address 64000−256 = 63744, making the new font active. Line 600 restores 23607 to 60, switching back to the ROM font for the comparison printout. ### Byte Substitution Logic Rather than storing a fully hand-crafted font, the program derives the new character bitmaps by transforming the existing ROM pixel rows. Each 8-bit value represents one horizontal pixel row of a character. The substitution tables in lines 130–240 and 310–490 map specific byte values to new ones, generally widening strokes or adding pixels to produce a bolder appearance suited to the 2040 printer’s output characteristics. Notable substitution patterns: - Line 130: `128 → 192` (adds a set bit, extends leftmost pixel) - Line 190: `64 → 96`, line 200: `32 → 48`, line 210: `16 → 24` — a cascade that progressively doubles the rightmost set bit for thinner rows - Lines 310–490 handle the upper character range with a denser substitution table covering values like `120→124`, `56→60`, `60→126`, etc. Line 250 includes a bounds guard: if `b=0` or `b>255` the POKE is skipped, avoiding writing a zero (blank row) that might have been an unmodified pass-through, and catching any arithmetic overflow — though overflow cannot actually occur here since no rule produces a value above 255. ### Key BASIC Idioms - `VAL "number"` is used throughout for numeric literals in loop bounds and `CLEAR` (lines 40, 80, 110, 290), a well-known technique to reduce the memory consumed by stored line data since short string tokens take less space than full numeric mantissas. - The subroutine at line 740 prints a blank line and plays a descending beep sweep (`FOR j=20 TO 30: BEEP .1,-j: NEXT j`), acting as both an audio and visual separator between instruction sections. - Line 640 ends with `NEW`, causing the program to erase itself after the second printout — the “self-destruct” described in the on-screen instructions. ### Memory Map Summary | Address range | Purpose | | --- | --- | | 15616–16135 | ROM font, chars 32–96 (source, first block) | | 16136–16383 | ROM font, chars 97–127 + UDGs (source, second block) | | 64000–64519 | Modified font, first block (destination) | | 64520–64767 | Modified font, second block (destination) | | 23606–23607 | CHARS system variable (font pointer −256) | | 23609 | Printer speed control | ### Anomalies and Observations - The two font loops together cover 521 bytes (lines 80–270) and 248 bytes (lines 280–510), totaling 769 source bytes — but the destination block runs from 64000 to 64767, also 768 bytes (plus one, since `a` is incremented after the last POKE). This is consistent with the `SAVE` instruction on line 730 specifying length 1536 — twice 768 — implying UDG data from a second source would also be saved. - The destination pointer `a` for the second block is initialized to 64520 (line 280) rather than 64521, which means the second loop’s output slightly overlaps with where the first loop’s pointer ended up, leaving one address (64520) written by both loops. This is likely a minor off-by-one in the design. - The credit string on line 550 embeds an author name reference which is present in the program text but is noted here only as a structural observation about line content. ## Source Code ``` 10 REM "CHR$ gen" 20 POKE 23609,125 30 BEEP .2,0:BEEP .2,12:BEEP .2,24:BEEP .2,36:BEEP .2,48 40 CLEAR VAL "63999" 50 PAPER 0:BORDER 0:CLS 60 PRINT AT 10,0; INK 7; FLASH 1; PAPER 1;"PLEASE WAIT-CREATING NEW SCRIPT" 70 POKE 23607,150 80 FOR j= VAL "64000" TO VAL "65000" 90 POKE j,0:NEXT j 100 LET a=64000 110 FOR j= VAL "15616" TO VAL "16135" 120 LET b= PEEK j 130 IF b=128 THEN LET b=b+64 140 IF b=98 THEN LET b=102 150 IF b=82 THEN LET b=114 160 IF b=70 THEN LET b=102 170 IF b=68 THEN LET b=b+40 180 IF b=66 THEN LET b=102 190 IF b=64 THEN LET b=b+32 200 IF b=32 THEN LET b=b+16 210 IF b=16 THEN LET b=b+8 220 IF b=8 THEN LET b=b+4 230 IF b=4 THEN LET b=b+2 240 IF b=2 THEN LET b=b+4 250 IF b=0 OR b>255 THEN GO TO 270 260 POKE a,b 270 LET a=a+1:NEXT j 280 LET a=64520 290 FOR j= VAL "16136" TO VAL "16383" 300 LET b= PEEK j 310 IF b=120 THEN LET b=124 320 IF b=146 THEN LET b=214 330 IF b=84 THEN LET b=214 340 IF b=104 THEN LET b=238 350 IF b=96 THEN LET b=112 360 IF b=68 THEN LET b=102 370 IF b=64 THEN LET b=96 380 IF b=60 THEN LET b=126 390 IF b=56 THEN LET b=60 400 IF b=48 THEN LET b=56 410 IF b=40 THEN LET b=44 420 IF b=36 THEN LET b=38 430 IF b=34 THEN LET b=102 440 IF b=32 THEN LET b=96 450 IF b=28 THEN LET b=60 460 IF b=24 THEN LET b=30 470 IF b=16 THEN LET b=24 480 IF b=12 THEN LET b=30 490 IF b=4 THEN LET b=6 500 POKE a,b 510 LET a=a+1:NEXT j 520 POKE 23607,249 530 POKE 23606,0 540 BORDER 0:PAPER 0:CLS 550 PRINT AT 10,0; INK 5; PAPER 0;"CHARACTERS loaded-@ m.p.biddell" 560 PRINT 570 PAUSE 200:CLS 580 GO SUB 650 590 PAUSE 100:COPY 600 POKE 23607,60 610 CLS 620 GO SUB 650 630 COPY 640 PAUSE 100:NEW 650 PRINT INK 6;"INSTRUCTIONS-NEW CHARACTER FONT" 660 GO SUB 740 670 PRINT INK 5;"1. After this program has self destructed, poke 23607,249 to turn on the new character set." 680 GO SUB 740 690 PRINT INK 5;"2. Then load your own program inthe normal way, Poke 23607,249 and it is instantly converted to the new text. Now use LLIST to see the enhanced output","from the 2040-printer." 700 GO SUB 740 710 PRINT INK 5;"3. Now Poke 23607,60 and LLIST. Compare this normal listing withthe enhanced one." 720 GO SUB 740 730 PRINT INK 5;"4. SAVE 'text' CODE ,64000,1536 (this also saves U.D.G's.)" 740 PRINT :FOR j=20 TO 30 750 BEEP .1,-j:NEXT j:RETURN ```