First public commit.
This commit is contained in:
parent
2a48f52979
commit
4bac9d83ed
288 changed files with 18417 additions and 1076 deletions
0
lenser/bbc/viewer/__init__.py
Normal file
0
lenser/bbc/viewer/__init__.py
Normal file
102
lenser/bbc/viewer/assemble.py
Normal file
102
lenser/bbc/viewer/assemble.py
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
"""Assemble the BBC 6502 viewer with `xa` and patch in per-image parameters."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
VIEWER_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
LOAD_ADDR = 0x1900 # DFS PAGE
|
||||
WAIT_MODES = {"forever": 0, "key": 1, "seconds": 2}
|
||||
|
||||
|
||||
class AssemblerError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def have_xa() -> bool:
|
||||
return shutil.which("xa") is not None
|
||||
|
||||
|
||||
def _xa(wrapper: str) -> bytes:
|
||||
if not have_xa():
|
||||
raise AssemblerError("The 'xa' (xa65) assembler was not found on PATH.\n"
|
||||
"Install it with: sudo apt install xa65")
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
out = os.path.join(td, "v.bin")
|
||||
fd, wrap = tempfile.mkstemp(suffix=".s", prefix="_wrap_", dir=VIEWER_DIR)
|
||||
try:
|
||||
with os.fdopen(fd, "w") as f:
|
||||
f.write(wrapper)
|
||||
proc = subprocess.run(["xa", "-o", out, os.path.basename(wrap)],
|
||||
capture_output=True, text=True, cwd=VIEWER_DIR)
|
||||
if proc.returncode != 0:
|
||||
raise AssemblerError(f"xa failed:\n{proc.stdout}{proc.stderr}")
|
||||
with open(out, "rb") as f:
|
||||
return f.read()
|
||||
finally:
|
||||
os.unlink(wrap)
|
||||
|
||||
|
||||
SS_WAITMODE = {"key": 1, "seconds": 2, "both": 3}
|
||||
|
||||
|
||||
def build_slideshow_viewer(bbc_mode: int, ncol: int, base: int, palettes,
|
||||
advance: str = "both", seconds: int = 10,
|
||||
loop: bool = True, video: str = "pal") -> bytes:
|
||||
"""Return the multi-image BBC loader (origin $1900).
|
||||
|
||||
``palettes`` is one physical-colour list per image (each truncated/padded to
|
||||
ncol) -- emitted as the ss_pal table the loader indexes by slide; the screen
|
||||
base hex is patched into the OSCLI *LOAD string.
|
||||
"""
|
||||
if not palettes:
|
||||
raise AssemblerError("a slideshow needs at least one image")
|
||||
rate = 60 if video == "ntsc" else 50
|
||||
flat = []
|
||||
for p in palettes:
|
||||
row = list(p[:ncol])
|
||||
flat += row + [0] * (ncol - len(row))
|
||||
table = ",".join(str(b & 0xFF) for b in flat)
|
||||
wrapper = (f"#define WAITMODE {SS_WAITMODE[advance]}\n"
|
||||
f"#define WAITSECS {max(0, int(seconds))}\n"
|
||||
f"#define RATE {rate}\n"
|
||||
f"#define NIMAGES {len(palettes)}\n"
|
||||
f"#define LOOPFLAG {1 if loop else 0}\n"
|
||||
f"#define MODE {bbc_mode}\n"
|
||||
f"#define NCOL {ncol}\n"
|
||||
'#include "slideshow.s"\n'
|
||||
"ss_pal:\n"
|
||||
f" .byte {table}\n")
|
||||
raw = bytearray(_xa(wrapper))
|
||||
off = raw.find(b"LOAD 00 ")
|
||||
if off < 0:
|
||||
raise AssemblerError("could not locate the OSCLI string to patch")
|
||||
raw[off + 8:off + 12] = f"{base:04X}".encode("ascii") # screen-base hex
|
||||
return bytes(raw)
|
||||
|
||||
|
||||
def build_viewer(bbc_mode: int, ncol: int, physicals, base: int,
|
||||
display: str = "forever", seconds: int = 0,
|
||||
video: str = "pal") -> bytes:
|
||||
"""Return the assembled loader (origin $1900) with the mode/palette/screen-base
|
||||
parameters patched in."""
|
||||
waitmode = WAIT_MODES.get(display, 0)
|
||||
rate = 60 if video == "ntsc" else 50 # bbcb is PAL (50 Hz)
|
||||
wrapper = (f"#define WAITMODE {waitmode}\n"
|
||||
f"#define WAITSECS {max(0, int(seconds))}\n"
|
||||
f"#define RATE {rate}\n"
|
||||
'#include "bbc.s"\n')
|
||||
raw = bytearray(_xa(wrapper))
|
||||
|
||||
off = raw.find(b"LOAD IMG ")
|
||||
if off < 0:
|
||||
raise AssemblerError("could not locate the OSCLI string to patch")
|
||||
raw[off + 9:off + 13] = f"{base:04X}".encode("ascii") # screen-base hex
|
||||
raw[off + 14] = bbc_mode & 0xFF # p_mode
|
||||
raw[off + 15] = ncol & 0xFF # p_ncol
|
||||
for i, p in enumerate(physicals[:8]): # p_pal
|
||||
raw[off + 16 + i] = p & 0xFF
|
||||
return bytes(raw)
|
||||
97
lenser/bbc/viewer/bbc.s
Normal file
97
lenser/bbc/viewer/bbc.s
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
; lenser -- BBC Micro viewer (6502), loaded at PAGE (&1900) and *RUN.
|
||||
;
|
||||
; Sets the screen MODE, programmes the logical->physical palette, then *LOADs the
|
||||
; image file "IMG" straight into screen memory, and holds it per the display
|
||||
; option. Parameters come from a fixed block the packager fills in (so the code
|
||||
; is constant); see build_viewer() in assemble.py.
|
||||
;
|
||||
; #defines from the wrapper -- WAITMODE (0 forever, 1 key, 2 seconds), WAITSECS, RATE.
|
||||
;
|
||||
; assembled by bbc/viewer/assemble.py via xa
|
||||
|
||||
OSWRCH = $FFEE
|
||||
OSRDCH = $FFE0
|
||||
OSBYTE = $FFF4
|
||||
OSCLI = $FFF7
|
||||
|
||||
* = $1900
|
||||
start:
|
||||
; ---- VDU 22, mode ----
|
||||
lda #22
|
||||
jsr OSWRCH
|
||||
lda p_mode
|
||||
jsr OSWRCH
|
||||
|
||||
; ---- palette via VDU 19, logical, physical, 0,0,0 for each logical ----
|
||||
ldx #0
|
||||
palloop:
|
||||
cpx p_ncol
|
||||
beq paldone
|
||||
lda #19
|
||||
jsr OSWRCH
|
||||
txa
|
||||
jsr OSWRCH ; logical colour = X
|
||||
lda p_pal,x
|
||||
jsr OSWRCH ; physical
|
||||
lda #0
|
||||
jsr OSWRCH
|
||||
jsr OSWRCH
|
||||
jsr OSWRCH
|
||||
inx
|
||||
bne palloop
|
||||
paldone:
|
||||
|
||||
; ---- hide the text cursor (VDU 23,1,0;0;0;0;) ----
|
||||
lda #23
|
||||
jsr OSWRCH
|
||||
lda #1
|
||||
jsr OSWRCH
|
||||
ldx #8
|
||||
curz:
|
||||
lda #0
|
||||
jsr OSWRCH
|
||||
dex
|
||||
bne curz
|
||||
|
||||
; ---- *LOAD the image straight into screen memory ----
|
||||
ldx #<cmd
|
||||
ldy #>cmd
|
||||
jsr OSCLI
|
||||
|
||||
; ---- hold the picture ----
|
||||
#if WAITMODE == 0
|
||||
hang:
|
||||
jmp hang
|
||||
#endif
|
||||
#if WAITMODE == 1
|
||||
jsr OSRDCH ; block until a key
|
||||
rts ; back to BASIC
|
||||
#endif
|
||||
#if WAITMODE == 2
|
||||
lda #<(WAITSECS*RATE)
|
||||
sta cnt
|
||||
lda #>(WAITSECS*RATE)
|
||||
sta cnt+1
|
||||
swait:
|
||||
lda #19
|
||||
jsr OSBYTE ; OSBYTE 19 = wait for vertical sync
|
||||
lda cnt
|
||||
bne sdec
|
||||
dec cnt+1
|
||||
sdec:
|
||||
dec cnt
|
||||
lda cnt
|
||||
ora cnt+1
|
||||
bne swait
|
||||
rts ; back to BASIC
|
||||
#endif
|
||||
|
||||
cnt: .byte 0,0
|
||||
|
||||
; OSCLI string -- the packager patches in the right screen-base hex
|
||||
cmd: .byte "LOAD IMG ", "0000", 13
|
||||
|
||||
; parameter block -- the packager fills these in
|
||||
p_mode: .byte 0
|
||||
p_ncol: .byte 0
|
||||
p_pal: .byte 0,0,0,0,0,0,0,0
|
||||
174
lenser/bbc/viewer/slideshow.s
Normal file
174
lenser/bbc/viewer/slideshow.s
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
; lenser -- BBC Micro slideshow loader (6502), loaded at PAGE (&1900) and *RUN.
|
||||
;
|
||||
; Sets the screen MODE once, then steps through NIMAGES pictures stored as DFS
|
||||
; files "00".."NN". For each slide it programmes that image's logical->physical
|
||||
; palette (from the ss_pal table), *LOADs the file straight into screen memory,
|
||||
; and waits (key / seconds / both) before advancing.
|
||||
;
|
||||
; #defines from the build wrapper --
|
||||
; WAITMODE 1 key / 2 seconds / 3 both WAITSECS timeout RATE 50/60 fps
|
||||
; NIMAGES image count LOOPFLAG 1 wrap / 0 stop MODE screen mode NCOL colours
|
||||
; The packager patches the screen-base hex into the OSCLI string and appends the
|
||||
; ss_pal table (NCOL physical-colour bytes per image).
|
||||
|
||||
OSWRCH = $FFEE
|
||||
OSRDCH = $FFE0
|
||||
OSBYTE = $FFF4
|
||||
OSCLI = $FFF7
|
||||
|
||||
palptr = $70
|
||||
cnt = $72
|
||||
|
||||
* = $1900
|
||||
start:
|
||||
; ---- VDU 22, mode (once) ----
|
||||
lda #22
|
||||
jsr OSWRCH
|
||||
lda #MODE
|
||||
jsr OSWRCH
|
||||
|
||||
; ---- hide the text cursor (VDU 23,1,0;0;0;0;) ----
|
||||
lda #23
|
||||
jsr OSWRCH
|
||||
lda #1
|
||||
jsr OSWRCH
|
||||
ldx #8
|
||||
curz:
|
||||
lda #0
|
||||
jsr OSWRCH
|
||||
dex
|
||||
bne curz
|
||||
|
||||
lda #0
|
||||
sta ssidx
|
||||
|
||||
ssmain:
|
||||
; ---- palptr = ss_pal + ssidx*NCOL ----
|
||||
lda #<ss_pal
|
||||
sta palptr
|
||||
lda #>ss_pal
|
||||
sta palptr+1
|
||||
ldx ssidx
|
||||
beq setpal
|
||||
pmul:
|
||||
clc
|
||||
lda palptr
|
||||
adc #NCOL
|
||||
sta palptr
|
||||
bcc pm1
|
||||
inc palptr+1
|
||||
pm1:
|
||||
dex
|
||||
bne pmul
|
||||
setpal:
|
||||
; ---- VDU 19, logical, physical, 0,0,0 for each logical colour ----
|
||||
ldx #0
|
||||
pl:
|
||||
cpx #NCOL
|
||||
beq pldone
|
||||
lda #19
|
||||
jsr OSWRCH
|
||||
txa
|
||||
jsr OSWRCH ; logical colour = X
|
||||
txa
|
||||
tay
|
||||
lda (palptr),y
|
||||
jsr OSWRCH ; physical colour
|
||||
lda #0
|
||||
jsr OSWRCH
|
||||
jsr OSWRCH
|
||||
jsr OSWRCH
|
||||
inx
|
||||
bne pl
|
||||
pldone:
|
||||
|
||||
; ---- build the filename digits "NN" into the OSCLI string ----
|
||||
lda ssidx
|
||||
ldx #$2f
|
||||
sec
|
||||
nten:
|
||||
inx
|
||||
sbc #10
|
||||
bcs nten
|
||||
adc #10
|
||||
ora #$30
|
||||
sta cmd+6 ; ones digit
|
||||
txa
|
||||
sta cmd+5 ; tens digit
|
||||
|
||||
; ---- *LOAD NN <base> straight into screen memory ----
|
||||
ldx #<cmd
|
||||
ldy #>cmd
|
||||
jsr OSCLI
|
||||
|
||||
jsr sswait
|
||||
|
||||
inc ssidx
|
||||
lda ssidx
|
||||
cmp #NIMAGES
|
||||
bcc ssmain
|
||||
#if LOOPFLAG == 1
|
||||
lda #0
|
||||
sta ssidx
|
||||
jmp ssmain
|
||||
#else
|
||||
rts ; back to BASIC
|
||||
#endif
|
||||
|
||||
; ---- wait (returns), flushing the keyboard buffer first ----
|
||||
sswait:
|
||||
lda #15
|
||||
ldx #1
|
||||
jsr OSBYTE ; flush input buffers
|
||||
#if WAITMODE == 1
|
||||
jsr OSRDCH ; block until a key
|
||||
rts
|
||||
#endif
|
||||
#if WAITMODE == 2
|
||||
lda #<(WAITSECS*RATE)
|
||||
sta cnt
|
||||
lda #>(WAITSECS*RATE)
|
||||
sta cnt+1
|
||||
sw2:
|
||||
lda #19
|
||||
jsr OSBYTE ; wait for vertical sync (1 frame)
|
||||
lda cnt
|
||||
bne sd2
|
||||
dec cnt+1
|
||||
sd2:
|
||||
dec cnt
|
||||
lda cnt
|
||||
ora cnt+1
|
||||
bne sw2
|
||||
rts
|
||||
#endif
|
||||
#if WAITMODE == 3
|
||||
lda #<(WAITSECS*RATE)
|
||||
sta cnt
|
||||
lda #>(WAITSECS*RATE)
|
||||
sta cnt+1
|
||||
sw3:
|
||||
lda #129
|
||||
ldx #0
|
||||
ldy #0
|
||||
jsr OSBYTE ; INKEY(0) -- poll keyboard, no wait
|
||||
cpy #$ff
|
||||
bne sw3d ; Y != $FF -> a key was pressed
|
||||
lda #19
|
||||
jsr OSBYTE ; wait one frame
|
||||
lda cnt
|
||||
bne sd3
|
||||
dec cnt+1
|
||||
sd3:
|
||||
dec cnt
|
||||
lda cnt
|
||||
ora cnt+1
|
||||
bne sw3
|
||||
sw3d:
|
||||
rts
|
||||
#endif
|
||||
|
||||
ssidx: .byte 0
|
||||
; OSCLI string -- packager patches the 4-hex screen base; loader patches "NN"
|
||||
cmd: .byte "LOAD 00 0000", 13
|
||||
; ss_pal table (NCOL bytes per image) appended by the build wrapper
|
||||
Loading…
Add table
Add a link
Reference in a new issue