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:
- Initialization (lines 20–70): plays a startup beep fanfare, sets
CLEAR 63999to reserve high RAM, configures display attributes, and pokes printer speed into address 23609. - 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 to64000–64519. - 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 at64520. - 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 andCLEAR(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
ais incremented after the last POKE). This is consistent with theSAVEinstruction on line 730 specifying length 1536 — twice 768 — implying UDG data from a second source would also be saved. - The destination pointer
afor 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
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
