10 '  PC-BASIC - ANSIVIEW.BAS
20 '  Shows text files with ANSI escape codes and plays ANSI music
50 '  Copyright (c) 2015--2023 Rob Hagemans
60 '  This file is released under the Expat MIT licence [https://opensource.org/licenses/MIT]
70 '
100 ' *** input file
110 CLS: KEY OFF: FILES
120 INPUT "Please specify file to open: ", FILE$
130 GOSUB 1000 ' init
140 GOSUB 2000 ' parse file
150 '
200 ' *** final
210 I$ = INKEY$: IF I$="" THEN 210
220 COLOR 7, 0
900 END
999 '
1000 ' *** initialise
1009 ' colour table
1010 DIM CTBL%[8]: FOR I% = 1 TO 8: READ CTBL%[I%]: NEXT
1020 DATA 0, 4, 2, 6, 1, 5, 3, 7
1029 ' global attributes
1030 FG% = 7: BG% = 0: REVERSE% = 0: CONCEAL% = 0
1039 ' global position
1040 ROW% = 1: COL% = 1
1050 CLS: KEY OFF: COLOR FG%, BG%
1060 RETURN
1099 '
2000 ' *** parse file
2005 ON ERROR GOTO 3000: OPEN FILE$ FOR INPUT AS 1: CLOSE 1: ON ERROR GOTO 0
2010 OPEN FILE$ FOR RANDOM ACCESS READ AS 1 LEN=1
2015 FIELD#1, 1 AS C$: GET#1, 1
2020 REC# = 1: STATE% = 0
2025 PAYLOAD$ = "": COMMAND$ = "": MUSIC$ = ""
2030 WHILE REC# < LOF(1)
2040   ON STATE%+1 GOSUB 10000, 11000, 12000, 13000, 14000, 15000, 16000
2050   REC# = REC#+1
2060   GET#1, REC#
2100 WEND
2110 RETURN
2120 '
3000 ' *** error handler
3010 PRINT "File "; FILE$; " could not be opened."
3020 RESUME 900
3030 '
10000 ' *** state 0, printing
10009 ' escape
10010 IF C$ = CHR$(&H1B) THEN STATE% = 2: RETURN
10015 OVERFLOW% = (POS(0) = 80)
10020 IF C$ = CHR$(10) THEN RETURN
10100 PRINT C$;
10110 IF OVERFLOW% THEN COLOR 0, 0: PRINT " " CHR$(&H1D);: GOSUB 25000 ' ensure scrolled line is black
10120 RETURN
10130 '
11000 ' *** state 1, music
11009 ' music note
11010 IF C$ = CHR$(&HE) THEN STATE% = 0: PLAY MUSIC$: MUSIC$ = "": RETURN
11020 MUSIC$ = MUSIC$ + C$
11030 RETURN
11040 '
12000 ' *** state 2, ansi escapes
12010 IF C$ = "[" THEN STATE% = 3: GOSUB 12100: RETURN
12020 IF C$ = "]" THEN STATE% = 4: GOSUB 12100: RETURN
12030 IF C$ = "7" THEN ROW% = CSRLIN: COL% = POS(0): STATE% = 0: RETURN
12040 IF C$ = "8" THEN LOCATE ROW%, COL%: STATE% = 0 :RETURN
12080 STATE% = 0: RETURN 'ignore other escapes - one character gets dropped
12090 RETURN
12099 '
12100 ' *** reset escape buffers
12110 PAYLOAD$ = ""
12120 RETURN
12140 '
13000 ' *** state 3, csi or music
13005 IF C$ = "M" THEN STATE% = 1: MUSIC$ = "M": GOSUB 12100: RETURN
13010 STATE% = 4 ' contine directly
13020 '
14000 ' *** state 4, csi
14010 IF ASC(C$) >= ASC("A") AND ASC(C$) <= ASC("Z") THEN STATE% = 0: GOSUB 20000: RETURN
14020 IF ASC(C$) >= ASC("a") AND ASC(C$) <= ASC("z") THEN STATE% = 0: GOSUB 20000: RETURN
14030 PAYLOAD$ = PAYLOAD$ + C$
14040 RETURN
14050 '
15000 ' *** state 5, osc
15010 IF ASC(C$) = &H7 THEN STATE% = 0: GOSUB 30000: RETURN
15020 IF ASC(C$) = &H1A THEN STATE% = 6: RETURN
15030 PAYLOAD$ = PAYLOAD$ + C$
15040 RETURN
15050 '
16000 ' *** state 6, osc string terminator
16010 IF C$ = "\" THEN STATE% = 0: GOSUB 30000: RETURN
16020 RETURN 'not a proper ST, ignore and do not execute the osc command
16030 '
20000 ' *** execute csi
20010 IF C$ = "s" THEN ROW% = CSRLIN: COL% = POS(0): RETURN
20020 IF C$ = "u" THEN LOCATE ROW%, COL%: RETURN
20030 IF C$ = "J" AND PAYLOAD$ = "2" THEN CLS: RETURN
20040 IF C$ = "A" THEN MOVE$ = CHR$(&H1E): GOSUB 21000: RETURN
20050 IF C$ = "B" THEN MOVE$ = CHR$(&H1F): GOSUB 21000: RETURN
20060 IF C$ = "C" THEN MOVE$ = CHR$(&H1C): GOSUB 21000: RETURN
20070 IF C$ = "D" THEN MOVE$ = CHR$(&H1D): GOSUB 21000: RETURN
20080 IF C$ = "H" THEN GOSUB 22000: RETURN
20090 IF C$ = "m" THEN GOSUB 23000: RETURN
20100 IF C$ = "h" AND PAYLOAD$ = "?25" THEN LOCATE ,,1: RETURN ' show cursor
20110 IF C$ = "l" AND PAYLOAD$ = "?25" THEN LOCATE ,,0: RETURN ' hide cursor
20120 IF C$ = "q" AND PAYLOAD$="0 " OR PAYLOAD$="2 " THEN LOCATE ,,,0,8: RETURN 'block cursor
20130 IF C$ = "q" AND PAYLOAD$="1 " OR PAYLOAD$="3 " OR PAYLOAD$="4 " THEN LOCATE ,,,8,8: RETURN 'line cursor
20990 RETURN
20999 '
21000 ' *** move cursor
21010 NUM% = VAL(PAYLOAD$): IF NUM%=0 THEN NUM%=1
21020 PRINT STRING$(NUM%, MOVE$);
21040 RETURN
21050 '
22000 ' *** locate
22010 SEMI% = INSTR(PAYLOAD$, ";")
22020 IF SEMI% = 0 THEN LOCATE VAL(PAYLOAD$),1: RETURN
22030 R% = VAL(LEFT$(PAYLOAD$, SEMI%-1))
22040 C% = VAL(RIGHT$(PAYLOAD$, LEN(PAYLOAD$)-SEMI%))
22045 IF R% < 1 THEN R% = 1
22048 IF C% < 1 THEN C% = 1
22050 LOCATE R%, C%
22060 RETURN
22070 '
23000 ' *** colours
23010 FCOL% = FG% MOD 8: BOLD% = (FG% \ 8) MOD 2: BLINK% = FG% \ 16
23020 BCOL% = BG% MOD 8
23030 WHILE PAYLOAD$ <> ""
23040   SEMI% = INSTR(PAYLOAD$, ";")
23050   IF SEMI% = 0 THEN NUM% = VAL(PAYLOAD$) ELSE NUM% = VAL(LEFT$(PAYLOAD$, SEMI%-1))
23055   IF SEMI% = 0 THEN PAYLOAD$ = "" ELSE PAYLOAD$ = RIGHT$(PAYLOAD$, LEN(PAYLOAD$)-SEMI%)
23070   IF NUM% >= 30 AND NUM% < 38 THEN FCOL% = CTBL%[NUM%-30+1]
23080   IF NUM% >= 40 AND NUM% < 48 THEN BCOL% = CTBL%[NUM%-40+1]
23090   IF NUM% = 39 THEN FCOL% = 7
23100   IF NUM% = 49 THEN BCOL% = 0
23110   IF NUM% = 1 THEN BOLD% = 1
23120   IF NUM% = 0 THEN FCOL%=7: BCOL%=0: BLINK%=0: BOLD%=0: REVERSE%=0: CONCEAL%=0
23140   IF NUM% = 5 OR NUM% = 6 THEN BLINK% = 1
23150   IF NUM% = 7 THEN REVERSE% = 1
23160   IF NUM% = 2 OR NUM% = 22 THEN BOLD% = 0
23170   IF NUM% = 25 THEN BLINK% = 0
23180   IF NUM% = 27 THEN REVERSE% = 0
23190   IF NUM% = 28 THEN CONCEAL% = 0
23200   IF NUM% >= 90 AND NUM% < 98 THEN FCOL% = CTBL%[NUM%-90+1]: BOLD% = 0 'bright
23210   IF NUM% >= 100 AND NUM% < 108 THEN BCOL% = CTBL%[NUM%-100+1] 'can't do bright bg
23300   FG% = FCOL% + 8 * BOLD% + 16 * BLINK%
23310   BG% = BCOL%
23320   GOSUB 25000
23330 WEND
23340 RETURN
23350 '
25000 ' *** set attribute
25020 IF CONCEAL% THEN COLOR BG%, BG% ELSE IF REVERSE% THEN COLOR BG%, FG% MOD 8 ELSE COLOR FG%, BG%
25030 RETURN
25040 '
30000 ' *** execute osc
30010 RETURN

