First public commit.
This commit is contained in:
parent
2a48f52979
commit
4bac9d83ed
288 changed files with 18417 additions and 1076 deletions
9
lenser/viewer/__init__.py
Normal file
9
lenser/viewer/__init__.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
"""6502 viewer programs, assembled on demand by assemble.py."""
|
||||
|
||||
from .assemble import ( # noqa: F401
|
||||
AssemblerError,
|
||||
SOURCES,
|
||||
assemble_stub,
|
||||
build_viewer_prg,
|
||||
have_xa,
|
||||
)
|
||||
206
lenser/viewer/assemble.py
Normal file
206
lenser/viewer/assemble.py
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
"""Assemble the 6502 viewer stubs with `xa` and build self-contained viewer PRGs.
|
||||
|
||||
Each viewer is a small ML stub originating at $0801 (behind a BASIC SYS 2061
|
||||
autostart). The picture data is appended after the stub, zero-padded so the
|
||||
bitmap lands exactly at $2000, screen RAM at $3F40, etc. The whole thing loads
|
||||
in a single pass -- no second disk access -- so it works identically on real
|
||||
hardware and in any emulator regardless of device configuration.
|
||||
|
||||
`xa -o` emits raw bytes starting at the origin without the 2-byte CBM load
|
||||
address, which we prepend here.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
VIEWER_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
LOAD_ADDR = 0x0801
|
||||
DATA_ADDR = 0x2000 # where appended picture data must land
|
||||
|
||||
# mode/viewer key -> source filename
|
||||
SOURCES = {
|
||||
"hires": "hires.s",
|
||||
"multicolor": "multicolor.s",
|
||||
"fli": "fli.s",
|
||||
"fli_ntsc": "fli_ntsc.s",
|
||||
"interlace": "interlace.s",
|
||||
"slideshow": "slideshow.s",
|
||||
"slideshow_fli": "slideshow_fli.s",
|
||||
"slideshow_interlace": "slideshow_interlace.s",
|
||||
}
|
||||
|
||||
# slideshow advance behaviour -> WAITMODE (see viewer/wait.i)
|
||||
SS_WAITMODE = {"key": 1, "seconds": 2, "both": 3}
|
||||
|
||||
_cache: dict[tuple, bytes] = {}
|
||||
|
||||
# How long the viewer holds the picture (see viewer/wait.i).
|
||||
WAIT_MODES = {"forever": 0, "key": 1, "seconds": 2}
|
||||
|
||||
|
||||
class AssemblerError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def have_xa() -> bool:
|
||||
return shutil.which("xa") is not None
|
||||
|
||||
|
||||
def assemble_stub(viewer_key: str, display: str = "key", seconds: int = 0,
|
||||
video: str = "pal", separate: bool = False) -> bytes:
|
||||
"""Assemble a viewer stub to raw bytes (origin $0801, no load-address prefix).
|
||||
|
||||
``display`` / ``seconds`` choose the wait behaviour (viewer/wait.i); ``video``
|
||||
sets the jiffy rate the seconds timer counts (50 PAL / 60 NTSC). ``separate``
|
||||
makes the viewer KERNAL-load the picture from a "data" file instead of having
|
||||
it appended (viewer/loaddata.i).
|
||||
"""
|
||||
waitmode = WAIT_MODES.get(display, 1)
|
||||
jiffyps = 60 if video == "ntsc" else 50
|
||||
sep = 1 if separate else 0
|
||||
key = (viewer_key, waitmode, int(seconds), jiffyps, sep)
|
||||
if key in _cache:
|
||||
return _cache[key]
|
||||
if not have_xa():
|
||||
raise AssemblerError(
|
||||
"The 'xa' (xa65) assembler was not found on PATH.\n"
|
||||
"Install it with: sudo apt install xa65 (Debian/Ubuntu)\n"
|
||||
"or build from https://www.floodgap.com/retrotech/xa/")
|
||||
|
||||
if not os.path.exists(os.path.join(VIEWER_DIR, SOURCES[viewer_key])):
|
||||
raise AssemblerError(f"viewer source missing: {SOURCES[viewer_key]}")
|
||||
|
||||
# A generated wrapper sets the options then includes the real source.
|
||||
wrapper = (
|
||||
f"#define WAITMODE {waitmode}\n"
|
||||
f"#define WAITSECS {max(0, int(seconds))}\n"
|
||||
f"#define JIFFYPS {jiffyps}\n"
|
||||
f"#define SEPARATE {sep}\n"
|
||||
f'#include "{SOURCES[viewer_key]}"\n')
|
||||
raw = _xa(wrapper, viewer_key)
|
||||
_cache[key] = raw
|
||||
return raw
|
||||
|
||||
|
||||
def _xa(wrapper: str, what: str) -> bytes:
|
||||
"""Assemble a generated wrapper with xa; return raw bytes (no load prefix).
|
||||
|
||||
The wrapper lives in VIEWER_DIR and xa runs there so its #include "...s" and
|
||||
the source's #include "wait.i" both resolve (xa looks relative to cwd)."""
|
||||
if not have_xa():
|
||||
raise AssemblerError(
|
||||
"The 'xa' (xa65) assembler was not found on PATH.\n"
|
||||
"Install it with: sudo apt install xa65 (Debian/Ubuntu)")
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
out = os.path.join(td, "viewer.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 for {what}:\n{proc.stdout}{proc.stderr}")
|
||||
with open(out, "rb") as f:
|
||||
return f.read()
|
||||
finally:
|
||||
os.unlink(wrap)
|
||||
|
||||
|
||||
CART_SIZE = 0x4000 # 16K C64 cartridge ROM at $8000-$BFFF
|
||||
|
||||
|
||||
def build_cart_rom(viewer_key: str, data: bytes, display: str = "forever",
|
||||
seconds: int = 0, video: str = "pal") -> bytes:
|
||||
"""Assemble cart.s for `viewer_key` (hires/multicolor), append the image
|
||||
data, and pad to a 16K cart ROM. Raises if the mode/size can't be a cart."""
|
||||
if viewer_key not in ("hires", "multicolor"):
|
||||
raise AssemblerError(
|
||||
f"the {viewer_key} mode is too large for a 16K cartridge -- "
|
||||
"use a disk image, or pick hires/multicolor/mono")
|
||||
mcmode = 1 if viewer_key == "multicolor" else 0
|
||||
waitmode = WAIT_MODES.get(display, 0)
|
||||
rate = 60 if video == "ntsc" else 50
|
||||
npages = (len(data) + 255) // 256
|
||||
wrapper = (
|
||||
f"#define MCMODE {mcmode}\n"
|
||||
f"#define NPAGES {npages}\n"
|
||||
f"#define WAITMODE {waitmode}\n"
|
||||
f"#define WAITSECS {max(0, int(seconds))}\n"
|
||||
f"#define RATE {rate}\n"
|
||||
'#include "cart.s"\n')
|
||||
rom = _xa(wrapper, "cart") + bytes(data)
|
||||
if len(rom) > CART_SIZE:
|
||||
raise AssemblerError(
|
||||
f"image + cart viewer = {len(rom)} bytes, over the 16K cartridge "
|
||||
"limit -- use a disk image for this mode")
|
||||
return rom + bytes(CART_SIZE - len(rom))
|
||||
|
||||
|
||||
def build_viewer_prg(viewer_key: str, data: bytes, data_addr: int = DATA_ADDR,
|
||||
display: str = "key", seconds: int = 0,
|
||||
video: str = "pal", separate: bool = False) -> bytes:
|
||||
"""Combine the assembled stub + padding + picture ``data`` into one PRG.
|
||||
|
||||
``data`` is the block that must reside from ``data_addr`` upward (bitmap,
|
||||
screen, colour RAM, background, ...). When ``separate`` is set the picture
|
||||
is NOT appended -- the viewer KERNAL-loads it from a "data" file at run time
|
||||
(use ``build_data_prg`` to write that file) -- so this returns just the code.
|
||||
"""
|
||||
stub = assemble_stub(viewer_key, display, seconds, video, separate)
|
||||
if separate:
|
||||
return bytes([LOAD_ADDR & 0xFF, (LOAD_ADDR >> 8) & 0xFF]) + stub
|
||||
pad_len = (data_addr - LOAD_ADDR) - len(stub)
|
||||
if pad_len < 0:
|
||||
raise AssemblerError(
|
||||
f"viewer stub {viewer_key} is {len(stub)} bytes, exceeds the "
|
||||
f"{data_addr - LOAD_ADDR} bytes available before ${data_addr:04x}")
|
||||
payload = stub + bytes(pad_len) + bytes(data)
|
||||
return bytes([LOAD_ADDR & 0xFF, (LOAD_ADDR >> 8) & 0xFF]) + payload
|
||||
|
||||
|
||||
def build_data_prg(data: bytes, data_addr: int = DATA_ADDR) -> bytes:
|
||||
"""The picture as a standalone PRG (2-byte load address + data), for the
|
||||
separate-binary layout's "data" file."""
|
||||
return bytes([data_addr & 0xFF, (data_addr >> 8) & 0xFF]) + bytes(data)
|
||||
|
||||
|
||||
def build_slideshow_prg(mode_bytes, advance: str = "both", seconds: int = 10,
|
||||
loop: bool = True, video: str = "pal",
|
||||
flavor: str = "simple") -> bytes:
|
||||
"""Assemble a slideshow viewer PRG (code only; pictures are separate files).
|
||||
|
||||
``flavor`` selects the engine: "simple" (mixed hires/multicolor/mono, one
|
||||
mode byte per image), "fli" (all FLI), or "interlace" (all IFLI). For the
|
||||
simple flavor ``mode_bytes`` is one byte per image (0 hires/mono, 1
|
||||
multicolor), emitted as the ss_modes table; for fli/interlace only its
|
||||
length (the image count) matters. ``advance`` picks the wait behaviour
|
||||
(key/seconds/both), ``seconds`` the timeout, ``loop`` whether it wraps.
|
||||
"""
|
||||
if not mode_bytes:
|
||||
raise AssemblerError("a slideshow needs at least one image")
|
||||
waitmode = SS_WAITMODE[advance]
|
||||
jiffyps = 60 if video == "ntsc" else 50
|
||||
defines = (
|
||||
f"#define WAITMODE {waitmode}\n"
|
||||
f"#define WAITSECS {max(0, int(seconds))}\n"
|
||||
f"#define JIFFYPS {jiffyps}\n"
|
||||
f"#define NIMAGES {len(mode_bytes)}\n"
|
||||
f"#define LOOPFLAG {1 if loop else 0}\n")
|
||||
if flavor == "simple":
|
||||
table = ",".join(str(int(b) & 1) for b in mode_bytes)
|
||||
wrapper = (defines + '#include "slideshow.s"\n'
|
||||
"ss_modes:\n" f" .byte {table}\n")
|
||||
elif flavor == "fli":
|
||||
wrapper = (defines + f"#define NTSC {1 if video == 'ntsc' else 0}\n"
|
||||
'#include "slideshow_fli.s"\n')
|
||||
elif flavor == "interlace":
|
||||
wrapper = defines + '#include "slideshow_interlace.s"\n'
|
||||
else:
|
||||
raise AssemblerError(f"unknown slideshow flavor: {flavor}")
|
||||
raw = _xa(wrapper, f"slideshow_{flavor}")
|
||||
return bytes([LOAD_ADDR & 0xFF, (LOAD_ADDR >> 8) & 0xFF]) + raw
|
||||
177
lenser/viewer/cart.s
Normal file
177
lenser/viewer/cart.s
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
; lenser -- C64 cartridge viewer (runs from ROM at $8000), fully self-contained.
|
||||
;
|
||||
; Unlike the disk viewer (a PRG at $0801 with the picture appended), this runs
|
||||
; from a 16K cartridge. It copies the image data block -- appended after this
|
||||
; code in ROM -- down to $2000 (same layout the disk viewer wants, bitmap at
|
||||
; $2000, screen $3F40, and for multicolor colram $4328 + bg $4710), programs the
|
||||
; VIC, and holds the picture. It uses NO KERNAL/IRQ services (a 16K cart hides
|
||||
; BASIC and the autostart entry runs before the KERNAL fully initialises), so it
|
||||
; polls the hardware directly -- CIA1 for the keyboard, the raster for timing.
|
||||
;
|
||||
; #defines set by viewer/assemble.py --
|
||||
; MCMODE 0 = hires, 1 = multicolor
|
||||
; NPAGES number of 256-byte pages of image data to copy
|
||||
; WAITMODE 0 forever / 1 until a key / 2 about WAITSECS seconds
|
||||
; WAITSECS, RATE (frames per second, 50 PAL / 60 NTSC)
|
||||
;
|
||||
; assembled by viewer/assemble.py via xa
|
||||
|
||||
* = $8000
|
||||
.word cold ; cold-start vector
|
||||
.word cold ; NMI / warm vector
|
||||
.byte $c3,$c2,$cd,$38,$30 ; CBM80 autostart signature
|
||||
|
||||
SRC = $fb
|
||||
DST = $fd
|
||||
CNT = $02 ; 16-bit frame countdown (seconds mode)
|
||||
|
||||
cold:
|
||||
sei
|
||||
ldx #$ff
|
||||
txs
|
||||
cld
|
||||
|
||||
; map the standard memory layout so BOTH halves of the 16K cart are
|
||||
; visible -- ROML $8000-$9FFF and ROMH $A000-$BFFF (needs HIRAM=1) -- plus
|
||||
; I/O at $D000. Set the data latch ($01) BEFORE the DDR ($00): on a cart
|
||||
; cold-start the latch may be low while the DDR is all-inputs (so the port
|
||||
; reads the correct map via pull-ups). Enabling the DDR first would drive
|
||||
; that low latch onto the port, bank ROML out, and crash the next fetch
|
||||
; (this is exactly what failed in VICE).
|
||||
lda #$37
|
||||
sta $01 ; LORAM+HIRAM+CHAREN = ROML, ROMH, I/O, KERNAL
|
||||
lda #$2f
|
||||
sta $00 ; now enable the port outputs
|
||||
|
||||
; VIC bank 0 ($0000-$3FFF) so the VIC sees our bitmap at $2000
|
||||
lda #$3f
|
||||
sta $dd02 ; CIA2 port A bits 0-5 = output
|
||||
lda #$c7
|
||||
sta $dd00 ; bank 0 (bits 0-1 = 11)
|
||||
|
||||
lda #$0b
|
||||
sta $d011 ; blank during setup
|
||||
|
||||
; ---- copy NPAGES of image data from ROM (datasrc) to $2000 ----
|
||||
lda #<datasrc
|
||||
sta SRC
|
||||
lda #>datasrc
|
||||
sta SRC+1
|
||||
lda #$00
|
||||
sta DST
|
||||
lda #$20
|
||||
sta DST+1
|
||||
ldx #NPAGES
|
||||
ldy #$00
|
||||
dcopy:
|
||||
lda (SRC),y
|
||||
sta (DST),y
|
||||
iny
|
||||
bne dcopy
|
||||
inc SRC+1
|
||||
inc DST+1
|
||||
dex
|
||||
bne dcopy
|
||||
|
||||
; ---- copy screen RAM $3F40 -> $0400 ----
|
||||
lda #$40
|
||||
sta SRC
|
||||
lda #$3f
|
||||
sta SRC+1
|
||||
lda #$00
|
||||
sta DST
|
||||
lda #$04
|
||||
sta DST+1
|
||||
jsr copy1024
|
||||
|
||||
#if MCMODE == 1
|
||||
; ---- copy colour RAM $4328 -> $D800 ----
|
||||
lda #$28
|
||||
sta SRC
|
||||
lda #$43
|
||||
sta SRC+1
|
||||
lda #$00
|
||||
sta DST
|
||||
lda #$d8
|
||||
sta DST+1
|
||||
jsr copy1024
|
||||
#endif
|
||||
|
||||
; ---- program the VIC-II ----
|
||||
lda #$00
|
||||
sta $d020 ; border black
|
||||
#if MCMODE == 1
|
||||
lda $4710
|
||||
sta $d021 ; background colour
|
||||
lda #$d8
|
||||
sta $d016 ; multicolor on
|
||||
#endif
|
||||
#if MCMODE == 0
|
||||
lda #$c8
|
||||
sta $d016 ; hires
|
||||
#endif
|
||||
lda #$18
|
||||
sta $d018 ; screen $0400, bitmap $2000
|
||||
lda #$3b
|
||||
sta $d011 ; bitmap mode, display on
|
||||
|
||||
; ---- hold the picture ----
|
||||
#if WAITMODE == 0
|
||||
forever:
|
||||
jmp forever
|
||||
#endif
|
||||
|
||||
#if WAITMODE == 1
|
||||
lda #$ff
|
||||
sta $dc02 ; CIA1 port A = output (column select)
|
||||
lda #$00
|
||||
sta $dc03 ; CIA1 port B = input (row read)
|
||||
sta $dc00 ; drive all keyboard columns low
|
||||
kwait:
|
||||
lda $dc01 ; rows; $FF = no key, any key pulls a row low
|
||||
cmp #$ff
|
||||
beq kwait
|
||||
jmp $fce2 ; reset (a 16K cart can't return to BASIC)
|
||||
#endif
|
||||
|
||||
#if WAITMODE == 2
|
||||
lda #<(WAITSECS*RATE)
|
||||
sta CNT
|
||||
lda #>(WAITSECS*RATE)
|
||||
sta CNT+1
|
||||
sloop:
|
||||
sw1:
|
||||
lda $d012
|
||||
cmp #$fa
|
||||
bne sw1 ; wait until raster reaches line 250
|
||||
sw2:
|
||||
lda $d012
|
||||
cmp #$fa
|
||||
beq sw2 ; wait until it leaves -> one frame elapsed
|
||||
lda CNT
|
||||
bne sdec
|
||||
dec CNT+1
|
||||
sdec:
|
||||
dec CNT
|
||||
lda CNT
|
||||
ora CNT+1
|
||||
bne sloop
|
||||
jmp $fce2 ; reset
|
||||
#endif
|
||||
|
||||
copy1024:
|
||||
ldx #4
|
||||
ldy #0
|
||||
c1k:
|
||||
lda (SRC),y
|
||||
sta (DST),y
|
||||
iny
|
||||
bne c1k
|
||||
inc SRC+1
|
||||
inc DST+1
|
||||
dex
|
||||
bne c1k
|
||||
rts
|
||||
|
||||
datasrc:
|
||||
; image data block appended here by the packager
|
||||
151
lenser/viewer/fli.s
Normal file
151
lenser/viewer/fli.s
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
; lenser -- FLI multicolor viewer (self-contained)
|
||||
;
|
||||
; Re-points the VIC video matrix ($D018) and forces a badline ($D011 yscroll)
|
||||
; on every visible raster line via a cycle-timed loop, giving per-line colour.
|
||||
;
|
||||
; Memory layout of appended data (loads from $4000):
|
||||
; $4000+L*$400 screen RAM for line L (L=0..7), 1000 bytes each
|
||||
; $6000 bitmap 8000 (offset $2000 in VIC bank 1)
|
||||
; $8000 colour RAM 1000 (copied to $D800)
|
||||
; $83E8 background 1
|
||||
;
|
||||
; assembled by viewer/assemble.py via xa
|
||||
|
||||
; BASIC autostart, SYS 2061
|
||||
* = $0801
|
||||
.word basicend
|
||||
.word 10
|
||||
.byte $9e
|
||||
.byte "2061"
|
||||
.byte 0
|
||||
basicend:
|
||||
.word 0 ; ML begins at $080D
|
||||
|
||||
start:
|
||||
sei
|
||||
|
||||
; copy colour RAM $8000 -> $D800 (1024 bytes)
|
||||
ldx #0
|
||||
ccopy:
|
||||
lda $8000,x
|
||||
sta $d800,x
|
||||
lda $8100,x
|
||||
sta $d900,x
|
||||
lda $8200,x
|
||||
sta $da00,x
|
||||
lda $8300,x
|
||||
sta $db00,x
|
||||
inx
|
||||
bne ccopy
|
||||
|
||||
; build per-line tables. d018tab = ((line AND 7) << 4) OR $08
|
||||
; d011tab = $38 OR (line AND 7) = BMM+DEN+RSEL plus yscroll
|
||||
ldx #0
|
||||
btab:
|
||||
txa
|
||||
and #$07
|
||||
asl
|
||||
asl
|
||||
asl
|
||||
asl
|
||||
ora #$08
|
||||
sta d018tab,x
|
||||
; yscroll must equal (raster AND 7) to force a badline; the first
|
||||
; displayed raster is 51, so line x maps to raster 51+x.
|
||||
txa
|
||||
clc
|
||||
adc #$03
|
||||
and #$07
|
||||
ora #$38
|
||||
sta d011tab,x
|
||||
inx
|
||||
bne btab
|
||||
|
||||
; VIC bank 1 ($4000-$7FFF)
|
||||
lda $dd00
|
||||
and #$fc
|
||||
ora #$02
|
||||
sta $dd00
|
||||
|
||||
lda $83e8
|
||||
sta $d021 ; background
|
||||
lda #$00
|
||||
sta $d020 ; border black
|
||||
lda #$d8
|
||||
sta $d016 ; multicolor on
|
||||
|
||||
; raster IRQ setup
|
||||
lda #<irq1
|
||||
sta $0314
|
||||
lda #>irq1
|
||||
sta $0315
|
||||
lda #$7f
|
||||
sta $dc0d ; disable CIA timer IRQs
|
||||
sta $dd0d
|
||||
lda $dc0d
|
||||
lda $dd0d
|
||||
lda #$01
|
||||
sta $d01a ; enable raster IRQ
|
||||
lda #$30
|
||||
sta $d012 ; line 48
|
||||
lda $d011
|
||||
and #$7f
|
||||
sta $d011
|
||||
asl $d019 ; ack
|
||||
cli
|
||||
hang:
|
||||
jmp hang
|
||||
|
||||
; first IRQ, arrives with jitter on line 48, sets up the stabilised one
|
||||
irq1:
|
||||
lda #<irq2
|
||||
sta $0314
|
||||
lda #>irq2
|
||||
sta $0315
|
||||
inc $d012 ; fire again next line (49)
|
||||
asl $d019 ; ack
|
||||
tsx
|
||||
cli
|
||||
; burn the rest of the line so irq2 fires inside this NOP slide
|
||||
.dsb 40,$ea ; 40 NOPs
|
||||
|
||||
; stabilised IRQ on line 49, runs the FLI loop
|
||||
irq2:
|
||||
txs ; restore sp from irq1
|
||||
; cancel the remaining 0/1-cycle jitter: read the raster twice; the
|
||||
; branch is 2 or 3 cycles depending on alignment, normalising entry.
|
||||
lda $d012
|
||||
cmp $d012
|
||||
beq jl
|
||||
jl:
|
||||
; fixed delay so the first store lands before the c-access window
|
||||
ldx #$0d
|
||||
d0: dex
|
||||
bne d0
|
||||
nop
|
||||
nop
|
||||
|
||||
ldx #$00
|
||||
fliloop:
|
||||
lda d011tab,x ; force badline (yscroll = line&7)
|
||||
sta $d011
|
||||
lda d018tab,x ; point video matrix at screen[line]
|
||||
sta $d018
|
||||
inx
|
||||
cpx #200
|
||||
bne fliloop
|
||||
|
||||
; bottom border: leave bitmap on but stop forcing badlines
|
||||
lda #<irq1
|
||||
sta $0314
|
||||
lda #>irq1
|
||||
sta $0315
|
||||
lda #$30
|
||||
sta $d012
|
||||
asl $d019
|
||||
jmp $ea81 ; restore regs + RTI
|
||||
|
||||
d018tab:
|
||||
.dsb 256,0
|
||||
d011tab:
|
||||
.dsb 256,0
|
||||
155
lenser/viewer/fli_ntsc.s
Normal file
155
lenser/viewer/fli_ntsc.s
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
; lenser -- FLI multicolor viewer, NTSC timing (self-contained)
|
||||
;
|
||||
; Same as fli.s but the inner loop is one NOP (2 cycles) longer so it self-syncs
|
||||
; to the NTSC 65-cycle raster line (25 free CPU cycles per badline) vs PAL's 63.
|
||||
;
|
||||
; Re-points the VIC video matrix ($D018) and forces a badline ($D011 yscroll)
|
||||
; on every visible raster line via a cycle-timed loop, giving per-line colour.
|
||||
;
|
||||
; Memory layout of appended data (loads from $4000):
|
||||
; $4000+L*$400 screen RAM for line L (L=0..7), 1000 bytes each
|
||||
; $6000 bitmap 8000 (offset $2000 in VIC bank 1)
|
||||
; $8000 colour RAM 1000 (copied to $D800)
|
||||
; $83E8 background 1
|
||||
;
|
||||
; assembled by viewer/assemble.py via xa
|
||||
|
||||
; BASIC autostart, SYS 2061
|
||||
* = $0801
|
||||
.word basicend
|
||||
.word 10
|
||||
.byte $9e
|
||||
.byte "2061"
|
||||
.byte 0
|
||||
basicend:
|
||||
.word 0 ; ML begins at $080D
|
||||
|
||||
start:
|
||||
sei
|
||||
|
||||
; copy colour RAM $8000 -> $D800 (1024 bytes)
|
||||
ldx #0
|
||||
ccopy:
|
||||
lda $8000,x
|
||||
sta $d800,x
|
||||
lda $8100,x
|
||||
sta $d900,x
|
||||
lda $8200,x
|
||||
sta $da00,x
|
||||
lda $8300,x
|
||||
sta $db00,x
|
||||
inx
|
||||
bne ccopy
|
||||
|
||||
; build per-line tables. d018tab = ((line AND 7) << 4) OR $08
|
||||
; d011tab = $38 OR (line AND 7) = BMM+DEN+RSEL plus yscroll
|
||||
ldx #0
|
||||
btab:
|
||||
txa
|
||||
and #$07
|
||||
asl
|
||||
asl
|
||||
asl
|
||||
asl
|
||||
ora #$08
|
||||
sta d018tab,x
|
||||
; yscroll must equal (raster AND 7) to force a badline; the first
|
||||
; displayed raster is 51, so line x maps to raster 51+x.
|
||||
txa
|
||||
clc
|
||||
adc #$03
|
||||
and #$07
|
||||
ora #$38
|
||||
sta d011tab,x
|
||||
inx
|
||||
bne btab
|
||||
|
||||
; VIC bank 1 ($4000-$7FFF)
|
||||
lda $dd00
|
||||
and #$fc
|
||||
ora #$02
|
||||
sta $dd00
|
||||
|
||||
lda $83e8
|
||||
sta $d021 ; background
|
||||
lda #$00
|
||||
sta $d020 ; border black
|
||||
lda #$d8
|
||||
sta $d016 ; multicolor on
|
||||
|
||||
; raster IRQ setup
|
||||
lda #<irq1
|
||||
sta $0314
|
||||
lda #>irq1
|
||||
sta $0315
|
||||
lda #$7f
|
||||
sta $dc0d ; disable CIA timer IRQs
|
||||
sta $dd0d
|
||||
lda $dc0d
|
||||
lda $dd0d
|
||||
lda #$01
|
||||
sta $d01a ; enable raster IRQ
|
||||
lda #$30
|
||||
sta $d012 ; line 48
|
||||
lda $d011
|
||||
and #$7f
|
||||
sta $d011
|
||||
asl $d019 ; ack
|
||||
cli
|
||||
hang:
|
||||
jmp hang
|
||||
|
||||
; first IRQ, arrives with jitter on line 48, sets up the stabilised one
|
||||
irq1:
|
||||
lda #<irq2
|
||||
sta $0314
|
||||
lda #>irq2
|
||||
sta $0315
|
||||
inc $d012 ; fire again next line (49)
|
||||
asl $d019 ; ack
|
||||
tsx
|
||||
cli
|
||||
; burn the rest of the line so irq2 fires inside this NOP slide
|
||||
.dsb 40,$ea ; 40 NOPs
|
||||
|
||||
; stabilised IRQ on line 49, runs the FLI loop
|
||||
irq2:
|
||||
txs ; restore sp from irq1
|
||||
; cancel the remaining 0/1-cycle jitter: read the raster twice; the
|
||||
; branch is 2 or 3 cycles depending on alignment, normalising entry.
|
||||
lda $d012
|
||||
cmp $d012
|
||||
beq jl
|
||||
jl:
|
||||
; fixed delay so the first store lands before the c-access window
|
||||
ldx #$0d
|
||||
d0: dex
|
||||
bne d0
|
||||
nop
|
||||
nop
|
||||
|
||||
ldx #$00
|
||||
fliloop:
|
||||
lda d011tab,x ; force badline (yscroll = line&7)
|
||||
sta $d011
|
||||
lda d018tab,x ; point video matrix at screen[line]
|
||||
sta $d018
|
||||
nop ; NTSC extra 2 cycles for 65 cycle line
|
||||
inx
|
||||
cpx #200
|
||||
bne fliloop
|
||||
|
||||
; bottom border: leave bitmap on but stop forcing badlines
|
||||
lda #<irq1
|
||||
sta $0314
|
||||
lda #>irq1
|
||||
sta $0315
|
||||
lda #$30
|
||||
sta $d012
|
||||
asl $d019
|
||||
jmp $ea81 ; restore regs + RTI
|
||||
|
||||
d018tab:
|
||||
.dsb 256,0
|
||||
d011tab:
|
||||
.dsb 256,0
|
||||
83
lenser/viewer/hires.s
Normal file
83
lenser/viewer/hires.s
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
; lenser -- hires bitmap viewer (self-contained)
|
||||
;
|
||||
; The picture data is appended to this program by the exporter and loads in one
|
||||
; pass. Fixed memory layout after load:
|
||||
; $0801 this program (BASIC stub + ML, padded up to $2000)
|
||||
; $2000 bitmap 8000 (VIC reads here directly)
|
||||
; $3F40 screen 1000 (copied to $0400)
|
||||
;
|
||||
; assembled by viewer/assemble.py via xa
|
||||
|
||||
; BASIC autostart, SYS 2061
|
||||
* = $0801
|
||||
.word basicend
|
||||
.word 10
|
||||
.byte $9e
|
||||
.byte "2061"
|
||||
.byte 0
|
||||
basicend:
|
||||
.word 0 ; ML begins at $080D
|
||||
|
||||
SRC = $fb
|
||||
DST = $fd
|
||||
|
||||
start:
|
||||
#include "loaddata.i"
|
||||
lda #$0b
|
||||
sta $d011 ; blank during setup
|
||||
|
||||
; copy screen RAM $3F40 -> $0400
|
||||
lda #$40
|
||||
sta SRC
|
||||
lda #$3f
|
||||
sta SRC+1
|
||||
lda #$00
|
||||
sta DST
|
||||
lda #$04
|
||||
sta DST+1
|
||||
jsr copy1024
|
||||
|
||||
lda $dd00
|
||||
ora #$03
|
||||
sta $dd00 ; VIC bank 0
|
||||
lda #$00
|
||||
sta $d020
|
||||
lda #$18
|
||||
sta $d018 ; screen $0400, bitmap $2000
|
||||
lda #$c8
|
||||
sta $d016 ; hires (multicolor off)
|
||||
lda #$3b
|
||||
sta $d011 ; bitmap mode, display on
|
||||
lda #$ff
|
||||
sta $cc
|
||||
|
||||
#include "wait.i"
|
||||
|
||||
lda #$1b
|
||||
sta $d011
|
||||
lda #$c8
|
||||
sta $d016
|
||||
lda #$15
|
||||
sta $d018
|
||||
lda #$0e
|
||||
sta $d020 ; restore default border (light blue)
|
||||
lda #$06
|
||||
sta $d021 ; and background (blue) for a clean BASIC screen
|
||||
lda #$00
|
||||
sta $cc
|
||||
jsr $e544
|
||||
rts
|
||||
|
||||
copy1024:
|
||||
ldx #4
|
||||
ldy #0
|
||||
cploop:
|
||||
lda (SRC),y
|
||||
sta (DST),y
|
||||
iny
|
||||
bne cploop
|
||||
inc SRC+1
|
||||
inc DST+1
|
||||
dex
|
||||
bne cploop
|
||||
rts
|
||||
139
lenser/viewer/interlace.s
Normal file
139
lenser/viewer/interlace.s
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
; lenser -- multicolor interlace viewer (self-contained)
|
||||
;
|
||||
; Shows two multicolor frames on alternating fields by flipping the VIC bank in a
|
||||
; once-per-frame raster IRQ (no cycle-exact timing needed, so it is robust).
|
||||
; Frame A lives in bank 0 (bitmap $2000, screen $0400); frame B in bank 1 (bitmap
|
||||
; $6000, screen $4400). Both use $D018=$18; only the $DD00 bank bit toggles.
|
||||
;
|
||||
; Memory layout of appended data (loads from $2000):
|
||||
; $2000 bitmap A 8000
|
||||
; $3F40 screen A 1000 (copied to $0400)
|
||||
; $4400 screen B 1000 (in place, bank 1 video matrix)
|
||||
; $6000 bitmap B 8000
|
||||
; $8000 colour RAM 1000 (copied to $D800)
|
||||
; $83E8 background 1
|
||||
;
|
||||
; assembled by viewer/assemble.py via xa
|
||||
|
||||
; BASIC autostart, SYS 2061
|
||||
* = $0801
|
||||
.word basicend
|
||||
.word 10
|
||||
.byte $9e
|
||||
.byte "2061"
|
||||
.byte 0
|
||||
basicend:
|
||||
.word 0
|
||||
|
||||
SRC = $fb
|
||||
DST = $fd
|
||||
|
||||
start:
|
||||
sei
|
||||
|
||||
; screen A $3F40 -> $0400
|
||||
lda #$40
|
||||
sta SRC
|
||||
lda #$3f
|
||||
sta SRC+1
|
||||
lda #$00
|
||||
sta DST
|
||||
lda #$04
|
||||
sta DST+1
|
||||
jsr copy1024
|
||||
|
||||
; colour RAM $8000 -> $D800
|
||||
lda #$00
|
||||
sta SRC
|
||||
lda #$80
|
||||
sta SRC+1
|
||||
lda #$00
|
||||
sta DST
|
||||
lda #$d8
|
||||
sta DST+1
|
||||
jsr copy1024
|
||||
|
||||
lda $83e8
|
||||
sta $d021 ; background
|
||||
lda #$00
|
||||
sta $d020 ; border black
|
||||
lda $dd00
|
||||
and #$fc
|
||||
ora #$03
|
||||
sta $dd00 ; start on VIC bank 0 (frame A)
|
||||
lda #$18
|
||||
sta $d018 ; screen $x400, bitmap $x000+$2000
|
||||
lda #$d8
|
||||
sta $d016 ; multicolor on
|
||||
lda #$3b
|
||||
sta $d011 ; bitmap mode, display on
|
||||
|
||||
; frame-flip raster IRQ near the bottom border
|
||||
lda #<irq
|
||||
sta $0314
|
||||
lda #>irq
|
||||
sta $0315
|
||||
lda #$7f
|
||||
sta $dc0d
|
||||
sta $dd0d
|
||||
lda $dc0d
|
||||
lda $dd0d
|
||||
lda #$01
|
||||
sta $d01a
|
||||
lda #$fa
|
||||
sta $d012 ; line 250
|
||||
lda $d011
|
||||
and #$7f
|
||||
sta $d011
|
||||
asl $d019
|
||||
cli
|
||||
|
||||
; hold the picture per the display option (our IRQ chains to the KERNAL
|
||||
; $ea31 housekeeping, so GETIN and the jiffy clock both work here).
|
||||
#include "wait.i"
|
||||
|
||||
; restore text mode + KERNAL IRQ
|
||||
sei
|
||||
lda #$00
|
||||
sta $d01a ; disable raster IRQ
|
||||
lda #$81
|
||||
sta $dc0d ; re-enable CIA timer IRQ
|
||||
lda #$31
|
||||
sta $0314
|
||||
lda #$ea
|
||||
sta $0315
|
||||
lda #$1b
|
||||
sta $d011
|
||||
lda #$c8
|
||||
sta $d016
|
||||
lda #$15
|
||||
sta $d018
|
||||
lda $dd00
|
||||
ora #$03
|
||||
sta $dd00
|
||||
asl $d019
|
||||
cli
|
||||
jsr $e544 ; clear screen
|
||||
rts
|
||||
|
||||
; once per frame, flip bank 0 <-> bank 1, then let the KERNAL IRQ finish
|
||||
irq:
|
||||
lda $dd00
|
||||
eor #$01
|
||||
sta $dd00
|
||||
asl $d019 ; ack raster IRQ
|
||||
jmp $ea31 ; KERNAL housekeeping (keyboard) + RTI
|
||||
|
||||
copy1024:
|
||||
ldx #4
|
||||
ldy #0
|
||||
cploop:
|
||||
lda (SRC),y
|
||||
sta (DST),y
|
||||
iny
|
||||
bne cploop
|
||||
inc SRC+1
|
||||
inc DST+1
|
||||
dex
|
||||
bne cploop
|
||||
rts
|
||||
20
lenser/viewer/loaddata.i
Normal file
20
lenser/viewer/loaddata.i
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
; Separate-binary prologue. When SEPARATE=1 the picture is NOT appended to this
|
||||
; viewer; it lives in its own disk file "data" (load address $2000). Pull it in
|
||||
; with the KERNAL before displaying. When SEPARATE=0 this is nothing, and the
|
||||
; data was appended after the viewer instead.
|
||||
#if SEPARATE == 1
|
||||
lda #4
|
||||
ldx #<cv_dataname
|
||||
ldy #>cv_dataname
|
||||
jsr $ffbd ; SETNAM
|
||||
lda #1
|
||||
ldx #8
|
||||
ldy #1
|
||||
jsr $ffba ; SETLFS (secondary 1 = load to file's address)
|
||||
lda #0
|
||||
jsr $ffd5 ; LOAD "data" -> $2000
|
||||
jmp cv_loaded
|
||||
cv_dataname:
|
||||
.byte $44,$41,$54,$41 ; "DATA" in PETSCII (the c1541 "data" file)
|
||||
cv_loaded:
|
||||
#endif
|
||||
101
lenser/viewer/multicolor.s
Normal file
101
lenser/viewer/multicolor.s
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
; lenser -- multicolor (Koala) bitmap viewer (self-contained)
|
||||
;
|
||||
; The picture data is appended to this program by the exporter and loads in one
|
||||
; pass, so no second disk access is needed. Fixed memory layout after load:
|
||||
; $0801 this program (BASIC stub + ML, padded up to $2000)
|
||||
; $2000 bitmap 8000 (VIC reads here directly)
|
||||
; $3F40 screen 1000 (copied to $0400)
|
||||
; $4328 colram 1000 (copied to $D800)
|
||||
; $4710 background 1
|
||||
;
|
||||
; assembled by viewer/assemble.py via xa
|
||||
|
||||
; BASIC autostart, SYS 2061
|
||||
* = $0801
|
||||
.word basicend
|
||||
.word 10
|
||||
.byte $9e
|
||||
.byte "2061"
|
||||
.byte 0
|
||||
basicend:
|
||||
.word 0 ; ML begins at $080D
|
||||
|
||||
SRC = $fb
|
||||
DST = $fd
|
||||
|
||||
start:
|
||||
#include "loaddata.i"
|
||||
lda #$0b
|
||||
sta $d011 ; blank screen during setup
|
||||
|
||||
; copy screen RAM $3F40 -> $0400
|
||||
lda #$40
|
||||
sta SRC
|
||||
lda #$3f
|
||||
sta SRC+1
|
||||
lda #$00
|
||||
sta DST
|
||||
lda #$04
|
||||
sta DST+1
|
||||
jsr copy1024
|
||||
|
||||
; copy colour RAM $4328 -> $D800
|
||||
lda #$28
|
||||
sta SRC
|
||||
lda #$43
|
||||
sta SRC+1
|
||||
lda #$00
|
||||
sta DST
|
||||
lda #$d8
|
||||
sta DST+1
|
||||
jsr copy1024
|
||||
|
||||
; program the VIC-II
|
||||
lda $dd00
|
||||
ora #$03
|
||||
sta $dd00 ; VIC bank 0 ($0000-$3FFF)
|
||||
lda $4710
|
||||
sta $d021 ; background colour
|
||||
lda #$00
|
||||
sta $d020 ; border black
|
||||
lda #$18
|
||||
sta $d018 ; screen $0400, bitmap $2000
|
||||
lda #$d8
|
||||
sta $d016 ; multicolor mode on
|
||||
lda #$3b
|
||||
sta $d011 ; bitmap mode, display on
|
||||
lda #$ff
|
||||
sta $cc ; disable cursor blink
|
||||
|
||||
#include "wait.i"
|
||||
|
||||
; restore text mode and return to BASIC
|
||||
lda #$1b
|
||||
sta $d011
|
||||
lda #$c8
|
||||
sta $d016
|
||||
lda #$15
|
||||
sta $d018
|
||||
lda #$0e
|
||||
sta $d020 ; restore default border (light blue)
|
||||
lda #$06
|
||||
sta $d021 ; and background (blue) for a clean BASIC screen
|
||||
lda #$00
|
||||
sta $cc
|
||||
jsr $e544 ; clear screen
|
||||
rts
|
||||
|
||||
; copy 1024 bytes from (SRC) to (DST)
|
||||
copy1024:
|
||||
ldx #4
|
||||
ldy #0
|
||||
cploop:
|
||||
lda (SRC),y
|
||||
sta (DST),y
|
||||
iny
|
||||
bne cploop
|
||||
inc SRC+1
|
||||
inc DST+1
|
||||
dex
|
||||
bne cploop
|
||||
rts
|
||||
238
lenser/viewer/slideshow.s
Normal file
238
lenser/viewer/slideshow.s
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
; lenser -- slideshow viewer (self-contained code; pictures are separate files)
|
||||
;
|
||||
; Boots first on the disk (LOAD"*",8,1 then RUN) and steps through NIMAGES
|
||||
; picture files named "00".."NN", each a PRG that KERNAL-loads to $2000 --
|
||||
; $2000 bitmap 8000 (always)
|
||||
; $3F40 screen 1000 (always -> copied to the buffer's video matrix)
|
||||
; $4328 colram 1000 (multicolor only -> $D800)
|
||||
; $4710 background 1 (multicolor only -> $D021)
|
||||
; The per-image byte in ss_modes selects hires(0) vs multicolor(1) setup. mono
|
||||
; uses the hires path. WAITMODE (viewer/wait.i) selects key / seconds / both.
|
||||
;
|
||||
; DOUBLE BUFFERED so the previous slide stays on screen while the next loads
|
||||
; (no blank between slides). Two VIC banks alternate as front/back buffer:
|
||||
; buffer 0 -- VIC bank 0, bitmap $2000, video matrix $0400 ($DD00 bits %11)
|
||||
; buffer 1 -- VIC bank 1, bitmap $6000, video matrix $4400 ($DD00 bits %10)
|
||||
; Both use $D018=$18 (matrix at bank+$0400, bitmap at bank+$2000); only the
|
||||
; $DD00 bank bits differ, so the swap is a single write. Each slide is KERNAL-
|
||||
; loaded into the *back* buffer (secondary address 0, so the file's $2000 header
|
||||
; is ignored and it lands at the buffer's base) while the front buffer -- the
|
||||
; previous picture -- stays displayed; then a bank flip makes it the front.
|
||||
; ss_hi = ss_buf*$40 is the high-byte offset ($00 buffer 0, $40 buffer 1) added
|
||||
; to every buffer-relative address. Colour RAM ($D800) is shared, so for a
|
||||
; multicolor slide it is copied at the swap (a hires slide needs none, and swaps
|
||||
; perfectly cleanly).
|
||||
;
|
||||
; assembled by viewer/assemble.py via xa; the ss_modes table is appended after
|
||||
; this file by the generated wrapper, and NIMAGES / LOOPFLAG / WAITMODE etc. are
|
||||
; #defined there.
|
||||
|
||||
; BASIC autostart, SYS 2061
|
||||
* = $0801
|
||||
.word basicend
|
||||
.word 10
|
||||
.byte $9e
|
||||
.byte "2061"
|
||||
.byte 0
|
||||
basicend:
|
||||
.word 0 ; ML begins at $080D
|
||||
|
||||
SRC = $fb
|
||||
DST = $fd
|
||||
|
||||
start:
|
||||
lda #$00
|
||||
sta $9d ; suppress KERNAL LOAD messages
|
||||
sta ss_idx ; start at the first image
|
||||
sta ss_buf ; first slide loads into buffer 0
|
||||
sta $d020 ; border black (once)
|
||||
lda #$0b
|
||||
sta $d011 ; display off -- the only blank, before slide 0
|
||||
|
||||
mainloop:
|
||||
jsr name_build ; ss_name = "NN" from ss_idx
|
||||
jsr setup_hi ; ss_hi = ss_buf * $40
|
||||
lda #$ff
|
||||
sta $cc ; cursor off (blink can't corrupt $D800)
|
||||
|
||||
; ---- KERNAL LOAD "NN" into the BACK buffer ($2000 or $6000) ----
|
||||
; secondary address 0 -> the file's $2000 header is ignored and the data
|
||||
; is loaded to the address passed in .X/.Y, so one data file can serve
|
||||
; either buffer. The front buffer stays displayed throughout.
|
||||
lda #2
|
||||
ldx #<ss_name
|
||||
ldy #>ss_name
|
||||
jsr $ffbd ; SETNAM
|
||||
lda #1
|
||||
ldx #8
|
||||
ldy #0 ; SA 0 -> load to .X/.Y address
|
||||
jsr $ffba ; SETLFS
|
||||
ldx #$00 ; load address low = $00
|
||||
lda #$20
|
||||
clc
|
||||
adc ss_hi
|
||||
tay ; load address high = $20 + ss_hi
|
||||
lda #0 ; 0 = load (not verify)
|
||||
jsr $ffd5 ; LOAD
|
||||
|
||||
; ---- copy screen RAM (base+$1F40) -> this buffer's video matrix ----
|
||||
; (into the still-hidden back buffer, so it is invisible)
|
||||
lda #$40
|
||||
sta SRC
|
||||
lda #$3f
|
||||
clc
|
||||
adc ss_hi
|
||||
sta SRC+1 ; SRC = $3F40 / $7F40
|
||||
lda #$00
|
||||
sta DST
|
||||
lda #$04
|
||||
clc
|
||||
adc ss_hi
|
||||
sta DST+1 ; DST = $0400 / $4400
|
||||
jsr copy1024
|
||||
|
||||
; ---- per-image mode -- 0 = hires/mono, 1 = multicolor ----
|
||||
ldx ss_idx
|
||||
lda ss_modes,x
|
||||
beq ss_hires
|
||||
|
||||
; multicolor -- colour RAM (base+$2328) -> $D800, background from base+$2710
|
||||
lda #$28
|
||||
sta SRC
|
||||
lda #$43
|
||||
clc
|
||||
adc ss_hi
|
||||
sta SRC+1 ; SRC = $4328 / $8328
|
||||
lda #$00
|
||||
sta DST
|
||||
lda #$d8
|
||||
sta DST+1
|
||||
jsr copy1024
|
||||
lda #$10
|
||||
sta SRC
|
||||
lda #$47
|
||||
clc
|
||||
adc ss_hi
|
||||
sta SRC+1 ; SRC = $4710 / $8710
|
||||
ldy #$00
|
||||
lda (SRC),y
|
||||
sta $d021 ; background colour
|
||||
jsr flip_bank ; make the back buffer the front
|
||||
lda #$d8
|
||||
sta $d016 ; multicolor on
|
||||
jmp ss_on
|
||||
|
||||
ss_hires:
|
||||
jsr flip_bank ; make the back buffer the front
|
||||
lda #$c8
|
||||
sta $d016 ; hires (multicolor off)
|
||||
|
||||
ss_on:
|
||||
lda #$18
|
||||
sta $d018 ; matrix bank+$0400, bitmap bank+$2000
|
||||
lda #$3b
|
||||
sta $d011 ; bitmap mode, display on
|
||||
|
||||
lda ss_buf
|
||||
eor #$01
|
||||
sta ss_buf ; next slide loads into the other buffer
|
||||
|
||||
#include "wait.i"
|
||||
|
||||
; ---- advance to the next image ----
|
||||
inc ss_idx
|
||||
lda ss_idx
|
||||
cmp #NIMAGES
|
||||
bcc ss_go ; still more images
|
||||
#if LOOPFLAG == 1
|
||||
lda #$00
|
||||
sta ss_idx ; wrap around forever
|
||||
ss_go:
|
||||
jmp mainloop
|
||||
#else
|
||||
jmp ss_end ; done -> restore and return to BASIC
|
||||
ss_go:
|
||||
jmp mainloop
|
||||
ss_end:
|
||||
#endif
|
||||
|
||||
; ---- restore text mode and return to BASIC ----
|
||||
lda $dd00
|
||||
ora #$03
|
||||
sta $dd00 ; VIC bank 0
|
||||
lda #$1b
|
||||
sta $d011
|
||||
lda #$c8
|
||||
sta $d016
|
||||
lda #$15
|
||||
sta $d018
|
||||
lda #$0e
|
||||
sta $d020 ; default border (light blue)
|
||||
lda #$06
|
||||
sta $d021 ; default background (blue)
|
||||
lda #$00
|
||||
sta $cc
|
||||
jsr $e544 ; clear screen
|
||||
rts
|
||||
|
||||
; ss_hi = ss_buf * $40 (high-byte offset for the current back buffer)
|
||||
setup_hi:
|
||||
ldy #$00
|
||||
lda ss_buf
|
||||
beq sh_set
|
||||
ldy #$40
|
||||
sh_set:
|
||||
sty ss_hi
|
||||
rts
|
||||
|
||||
; point VIC at the current buffer's bank (buffer 0 -> bank 0, buffer 1 -> bank 1)
|
||||
flip_bank:
|
||||
lda ss_buf
|
||||
bne fb_one
|
||||
lda $dd00
|
||||
ora #$03
|
||||
sta $dd00 ; bank 0 ($0000-$3FFF)
|
||||
rts
|
||||
fb_one:
|
||||
lda $dd00
|
||||
and #$fc
|
||||
ora #$02
|
||||
sta $dd00 ; bank 1 ($4000-$7FFF)
|
||||
rts
|
||||
|
||||
; build the 2-char filename "NN" from ss_idx (0..99)
|
||||
name_build:
|
||||
lda ss_idx
|
||||
ldx #$2f
|
||||
sec
|
||||
ss_ten:
|
||||
inx
|
||||
sbc #10
|
||||
bcs ss_ten
|
||||
adc #10 ; remainder 0..9 (carry was clear on exit)
|
||||
ora #$30
|
||||
sta ss_name+1 ; ones digit (PETSCII)
|
||||
txa
|
||||
sta ss_name ; tens digit (PETSCII)
|
||||
rts
|
||||
|
||||
; copy 1024 bytes from (SRC) to (DST)
|
||||
copy1024:
|
||||
ldx #4
|
||||
ldy #0
|
||||
cploop:
|
||||
lda (SRC),y
|
||||
sta (DST),y
|
||||
iny
|
||||
bne cploop
|
||||
inc SRC+1
|
||||
inc DST+1
|
||||
dex
|
||||
bne cploop
|
||||
rts
|
||||
|
||||
ss_idx: .byte 0
|
||||
ss_buf: .byte 0 ; 0 or 1 -- which buffer the next slide loads into
|
||||
ss_hi: .byte 0 ; ss_buf * $40 (buffer high-byte offset)
|
||||
ss_name: .byte $30,$30 ; "00", rebuilt each slide from ss_idx
|
||||
; ss_modes table (.byte per image) appended by viewer/assemble.py's wrapper
|
||||
247
lenser/viewer/slideshow_fli.s
Normal file
247
lenser/viewer/slideshow_fli.s
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
; lenser -- FLI slideshow viewer
|
||||
;
|
||||
; Steps through NIMAGES FLI pictures named "00".."NN", each a PRG that KERNAL-
|
||||
; loads to $4000 with the layout from fli.s --
|
||||
; $4000+L*$400 screen RAM for line L (L=0..7), 1000 bytes each
|
||||
; $6000 bitmap 8000 (offset $2000 in VIC bank 1)
|
||||
; $8000 colour RAM 1000 (copied to $D800)
|
||||
; $83E8 background 1
|
||||
; The cycle-timed raster loop re-points $D018/$D011 every line for per-line
|
||||
; colour; at the bottom border it chains to the KERNAL IRQ ($ea31) so GETIN and
|
||||
; the jiffy clock keep working, letting wait.i advance on key / seconds / both.
|
||||
; All slides are FLI (uniform). NTSC=1 adds one NOP for the 65-cycle line.
|
||||
;
|
||||
; assembled by viewer/assemble.py via xa; NIMAGES / LOOPFLAG / WAITMODE / NTSC
|
||||
; etc. are #defined by the generated wrapper.
|
||||
|
||||
; BASIC autostart, SYS 2061
|
||||
* = $0801
|
||||
.word basicend
|
||||
.word 10
|
||||
.byte $9e
|
||||
.byte "2061"
|
||||
.byte 0
|
||||
basicend:
|
||||
.word 0
|
||||
|
||||
start:
|
||||
lda #$00
|
||||
sta $9d ; suppress KERNAL LOAD messages
|
||||
sta ss_idx
|
||||
jsr build_tables ; per-line $D018/$D011 tables (once)
|
||||
|
||||
mainloop:
|
||||
jsr ss_name_build
|
||||
lda #$0b
|
||||
sta $d011 ; blank while loading
|
||||
jsr ss_load ; LOAD "NN",8,1 -> $4000 (KERNAL IRQ active)
|
||||
|
||||
; ---- FLI setup ----
|
||||
sei
|
||||
lda #$ff
|
||||
sta $cc ; cursor off (so blink can't corrupt $D800)
|
||||
|
||||
; copy colour RAM $8000 -> $D800 (1024 bytes)
|
||||
ldx #0
|
||||
ccopy:
|
||||
lda $8000,x
|
||||
sta $d800,x
|
||||
lda $8100,x
|
||||
sta $d900,x
|
||||
lda $8200,x
|
||||
sta $da00,x
|
||||
lda $8300,x
|
||||
sta $db00,x
|
||||
inx
|
||||
bne ccopy
|
||||
|
||||
lda $dd00
|
||||
and #$fc
|
||||
ora #$02
|
||||
sta $dd00 ; VIC bank 1 ($4000-$7FFF)
|
||||
lda $83e8
|
||||
sta $d021 ; background
|
||||
lda #$00
|
||||
sta $d020 ; border black
|
||||
lda #$d8
|
||||
sta $d016 ; multicolor on
|
||||
|
||||
lda #<irq1
|
||||
sta $0314
|
||||
lda #>irq1
|
||||
sta $0315
|
||||
lda #$7f
|
||||
sta $dc0d ; disable CIA timer IRQs
|
||||
sta $dd0d
|
||||
lda $dc0d
|
||||
lda $dd0d
|
||||
lda #$01
|
||||
sta $d01a ; enable raster IRQ
|
||||
lda #$30
|
||||
sta $d012 ; line 48
|
||||
lda $d011
|
||||
and #$7f
|
||||
sta $d011
|
||||
asl $d019
|
||||
cli
|
||||
|
||||
#include "wait.i"
|
||||
|
||||
; ---- stop the engine (KERNAL IRQ back on for the next LOAD) ----
|
||||
sei
|
||||
lda #$00
|
||||
sta $d01a
|
||||
lda #$81
|
||||
sta $dc0d
|
||||
lda #$31
|
||||
sta $0314
|
||||
lda #$ea
|
||||
sta $0315
|
||||
lda $dd00
|
||||
ora #$03
|
||||
sta $dd00 ; bank 0
|
||||
asl $d019
|
||||
cli
|
||||
|
||||
; ---- advance ----
|
||||
inc ss_idx
|
||||
lda ss_idx
|
||||
cmp #NIMAGES
|
||||
bcc ssgo
|
||||
#if LOOPFLAG == 1
|
||||
lda #$00
|
||||
sta ss_idx
|
||||
ssgo:
|
||||
jmp mainloop
|
||||
#else
|
||||
jmp ssend
|
||||
ssgo:
|
||||
jmp mainloop
|
||||
ssend:
|
||||
#endif
|
||||
|
||||
; ---- final restore to BASIC ----
|
||||
lda #$1b
|
||||
sta $d011
|
||||
lda #$c8
|
||||
sta $d016
|
||||
lda #$15
|
||||
sta $d018
|
||||
lda #$0e
|
||||
sta $d020
|
||||
lda #$06
|
||||
sta $d021
|
||||
lda #$00
|
||||
sta $cc
|
||||
jsr $e544
|
||||
rts
|
||||
|
||||
; build d018tab = ((line AND 7) << 4) OR $08, d011tab = $38 OR ((line+3) AND 7)
|
||||
build_tables:
|
||||
ldx #0
|
||||
btab:
|
||||
txa
|
||||
and #$07
|
||||
asl
|
||||
asl
|
||||
asl
|
||||
asl
|
||||
ora #$08
|
||||
sta d018tab,x
|
||||
txa
|
||||
clc
|
||||
adc #$03
|
||||
and #$07
|
||||
ora #$38
|
||||
sta d011tab,x
|
||||
inx
|
||||
bne btab
|
||||
rts
|
||||
|
||||
; first IRQ, arrives with jitter on line 48, sets up the stabilised one
|
||||
irq1:
|
||||
lda #<irq2
|
||||
sta $0314
|
||||
lda #>irq2
|
||||
sta $0315
|
||||
inc $d012
|
||||
asl $d019
|
||||
tsx
|
||||
cli
|
||||
.dsb 40,$ea ; 40 NOPs -- irq2 fires inside this slide
|
||||
|
||||
; stabilised IRQ on line 49, runs the FLI loop then chains to the KERNAL IRQ
|
||||
irq2:
|
||||
txs
|
||||
lda $d012
|
||||
cmp $d012
|
||||
beq jl
|
||||
jl:
|
||||
ldx #$0d
|
||||
d0: dex
|
||||
bne d0
|
||||
nop
|
||||
nop
|
||||
#if NTSC == 1
|
||||
nop ; NTSC extra 2 cycles for the 65-cycle line
|
||||
#endif
|
||||
|
||||
ldx #$00
|
||||
fliloop:
|
||||
lda d011tab,x
|
||||
sta $d011
|
||||
lda d018tab,x
|
||||
sta $d018
|
||||
inx
|
||||
cpx #200
|
||||
bne fliloop
|
||||
|
||||
; bottom border -- re-arm irq1, then KERNAL housekeeping (keys + jiffy)
|
||||
lda #<irq1
|
||||
sta $0314
|
||||
lda #>irq1
|
||||
sta $0315
|
||||
lda #$30
|
||||
sta $d012
|
||||
asl $d019
|
||||
jmp $ea31 ; KERNAL housekeeping (keys + jiffy) then RTI
|
||||
|
||||
ss_load:
|
||||
lda #2
|
||||
ldx #<ss_name
|
||||
ldy #>ss_name
|
||||
jsr $ffbd ; SETNAM
|
||||
lda #1
|
||||
ldx #8
|
||||
ldy #1
|
||||
jsr $ffba ; SETLFS (secondary 1 = file's own address)
|
||||
lda #0
|
||||
jsr $ffd5 ; LOAD
|
||||
rts
|
||||
|
||||
ss_name_build:
|
||||
lda ss_idx
|
||||
ldx #$2f
|
||||
sec
|
||||
ssten:
|
||||
inx
|
||||
sbc #10
|
||||
bcs ssten
|
||||
adc #10
|
||||
ora #$30
|
||||
sta ss_name+1
|
||||
txa
|
||||
sta ss_name
|
||||
rts
|
||||
|
||||
ss_idx: .byte 0
|
||||
ss_name: .byte $30,$30
|
||||
|
||||
; The cycle-exact fliloop reads `lda d018tab,x` / `lda d011tab,x` for x=0..199,
|
||||
; so each table MUST be page-aligned -- otherwise indices past the page boundary
|
||||
; add a page-cross penalty cycle and tear the FLI display.
|
||||
* = (* + $ff) & $ff00
|
||||
d018tab:
|
||||
.dsb 256,0
|
||||
d011tab:
|
||||
.dsb 256,0
|
||||
204
lenser/viewer/slideshow_interlace.s
Normal file
204
lenser/viewer/slideshow_interlace.s
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
; lenser -- interlace (IFLI) slideshow viewer
|
||||
;
|
||||
; Steps through NIMAGES interlace pictures named "00".."NN", each a PRG that
|
||||
; KERNAL-loads to $2000 with the layout from interlace.s --
|
||||
; $2000 bitmap A 8000
|
||||
; $3F40 screen A 1000 (copied to $0400)
|
||||
; $4400 screen B 1000 (in place, bank 1 video matrix)
|
||||
; $6000 bitmap B 8000
|
||||
; $8000 colour RAM 1000 (copied to $D800)
|
||||
; $83E8 background 1
|
||||
; A once-per-frame raster IRQ flips VIC bank 0<->1 to blend the two fields; it
|
||||
; chains to the KERNAL IRQ ($ea31) so GETIN and the jiffy clock work, letting
|
||||
; wait.i advance on key / seconds / both. All slides are interlace (uniform).
|
||||
;
|
||||
; assembled by viewer/assemble.py via xa; NIMAGES / LOOPFLAG / WAITMODE etc. are
|
||||
; #defined by the generated wrapper.
|
||||
|
||||
; BASIC autostart, SYS 2061
|
||||
* = $0801
|
||||
.word basicend
|
||||
.word 10
|
||||
.byte $9e
|
||||
.byte "2061"
|
||||
.byte 0
|
||||
basicend:
|
||||
.word 0
|
||||
|
||||
SRC = $fb
|
||||
DST = $fd
|
||||
|
||||
start:
|
||||
lda #$00
|
||||
sta $9d ; suppress KERNAL LOAD messages
|
||||
sta ss_idx
|
||||
sta $d020 ; border black, once
|
||||
|
||||
mainloop:
|
||||
jsr ss_name_build
|
||||
lda #$0b
|
||||
sta $d011 ; blank while loading the next field pair
|
||||
jsr ss_load ; LOAD "NN",8,1 -> $2000 (KERNAL IRQ active)
|
||||
|
||||
; ---- interlace setup ----
|
||||
sei
|
||||
lda #$ff
|
||||
sta $cc ; cursor off (so blink can't corrupt $D800)
|
||||
|
||||
; screen A $3F40 -> $0400
|
||||
lda #$40
|
||||
sta SRC
|
||||
lda #$3f
|
||||
sta SRC+1
|
||||
lda #$00
|
||||
sta DST
|
||||
lda #$04
|
||||
sta DST+1
|
||||
jsr copy1024
|
||||
|
||||
; colour RAM $8000 -> $D800
|
||||
lda #$00
|
||||
sta SRC
|
||||
lda #$80
|
||||
sta SRC+1
|
||||
lda #$00
|
||||
sta DST
|
||||
lda #$d8
|
||||
sta DST+1
|
||||
jsr copy1024
|
||||
|
||||
lda $83e8
|
||||
sta $d021 ; background
|
||||
lda $dd00
|
||||
and #$fc
|
||||
ora #$03
|
||||
sta $dd00 ; start on VIC bank 0 (frame A)
|
||||
lda #$18
|
||||
sta $d018
|
||||
lda #$d8
|
||||
sta $d016 ; multicolor on
|
||||
lda #$3b
|
||||
sta $d011 ; bitmap mode, display on
|
||||
|
||||
lda #<irq
|
||||
sta $0314
|
||||
lda #>irq
|
||||
sta $0315
|
||||
lda #$7f
|
||||
sta $dc0d
|
||||
sta $dd0d
|
||||
lda $dc0d
|
||||
lda $dd0d
|
||||
lda #$01
|
||||
sta $d01a
|
||||
lda #$fa
|
||||
sta $d012 ; line 250
|
||||
lda $d011
|
||||
and #$7f
|
||||
sta $d011
|
||||
asl $d019
|
||||
cli
|
||||
|
||||
#include "wait.i"
|
||||
|
||||
; ---- stop the engine (KERNAL IRQ back on for the next LOAD) ----
|
||||
sei
|
||||
lda #$00
|
||||
sta $d01a ; disable raster IRQ
|
||||
lda #$81
|
||||
sta $dc0d ; re-enable CIA timer IRQ
|
||||
lda #$31
|
||||
sta $0314
|
||||
lda #$ea
|
||||
sta $0315
|
||||
lda $dd00
|
||||
ora #$03
|
||||
sta $dd00 ; back to bank 0
|
||||
asl $d019
|
||||
cli
|
||||
|
||||
; ---- advance ----
|
||||
inc ss_idx
|
||||
lda ss_idx
|
||||
cmp #NIMAGES
|
||||
bcc ssgo
|
||||
#if LOOPFLAG == 1
|
||||
lda #$00
|
||||
sta ss_idx
|
||||
ssgo:
|
||||
jmp mainloop
|
||||
#else
|
||||
jmp ssend
|
||||
ssgo:
|
||||
jmp mainloop
|
||||
ssend:
|
||||
#endif
|
||||
|
||||
; ---- final restore to BASIC ----
|
||||
lda #$1b
|
||||
sta $d011
|
||||
lda #$c8
|
||||
sta $d016
|
||||
lda #$15
|
||||
sta $d018
|
||||
lda #$0e
|
||||
sta $d020
|
||||
lda #$06
|
||||
sta $d021
|
||||
lda #$00
|
||||
sta $cc
|
||||
jsr $e544
|
||||
rts
|
||||
|
||||
; once per frame, flip bank 0 <-> bank 1, then let the KERNAL IRQ finish
|
||||
irq:
|
||||
lda $dd00
|
||||
eor #$01
|
||||
sta $dd00
|
||||
asl $d019
|
||||
jmp $ea31
|
||||
|
||||
ss_load:
|
||||
lda #2
|
||||
ldx #<ss_name
|
||||
ldy #>ss_name
|
||||
jsr $ffbd ; SETNAM
|
||||
lda #1
|
||||
ldx #8
|
||||
ldy #1
|
||||
jsr $ffba ; SETLFS (secondary 1 = file's own address)
|
||||
lda #0
|
||||
jsr $ffd5 ; LOAD
|
||||
rts
|
||||
|
||||
ss_name_build:
|
||||
lda ss_idx
|
||||
ldx #$2f
|
||||
sec
|
||||
ssten:
|
||||
inx
|
||||
sbc #10
|
||||
bcs ssten
|
||||
adc #10
|
||||
ora #$30
|
||||
sta ss_name+1
|
||||
txa
|
||||
sta ss_name
|
||||
rts
|
||||
|
||||
copy1024:
|
||||
ldx #4
|
||||
ldy #0
|
||||
cploop:
|
||||
lda (SRC),y
|
||||
sta (DST),y
|
||||
iny
|
||||
bne cploop
|
||||
inc SRC+1
|
||||
inc DST+1
|
||||
dex
|
||||
bne cploop
|
||||
rts
|
||||
|
||||
ss_idx: .byte 0
|
||||
ss_name: .byte $30,$30
|
||||
77
lenser/viewer/wait.i
Normal file
77
lenser/viewer/wait.i
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
; Shared "how long to show the picture" epilogue for the simple C64 viewers.
|
||||
; Selected at assembly time by WAITMODE (set by viewer/assemble.py):
|
||||
; 0 forever -- never returns; the picture stays until reset
|
||||
; 1 until a key -- jsr GETIN until a key is pressed, then fall through
|
||||
; 2 WAITSECS secs -- count KERNAL jiffies (JIFFYPS per second), then fall through
|
||||
; 3 key OR secs -- whichever comes first (keys still work, but it auto-
|
||||
; advances after WAITSECS); used by the slideshow viewer
|
||||
; On fall-through the caller restores the text screen and RTSes to BASIC.
|
||||
; The KERNAL IRQ is left running by these viewers, so GETIN and the jiffy clock
|
||||
; ($a0-$a2, big-endian, $a2 = least significant) both work.
|
||||
|
||||
cv_t0 = $fb ; 16-bit jiffy snapshot (free after the copy)
|
||||
cv_el = $fd ; 16-bit elapsed jiffies
|
||||
|
||||
#if WAITMODE == 0
|
||||
cv_wait:
|
||||
jmp cv_wait
|
||||
#endif
|
||||
|
||||
#if WAITMODE == 1
|
||||
lda #$00
|
||||
sta $c6 ; empty the keyboard buffer first, so a key left
|
||||
; over from RUN doesn't dismiss the picture at once
|
||||
cv_wait:
|
||||
jsr $ffe4 ; GETIN
|
||||
beq cv_wait
|
||||
#endif
|
||||
|
||||
#if WAITMODE == 2
|
||||
lda $a2
|
||||
sta cv_t0
|
||||
lda $a1
|
||||
sta cv_t0+1
|
||||
cv_wait:
|
||||
sec
|
||||
lda $a2
|
||||
sbc cv_t0
|
||||
sta cv_el
|
||||
lda $a1
|
||||
sbc cv_t0+1
|
||||
sta cv_el+1
|
||||
lda cv_el+1
|
||||
cmp #>(WAITSECS*JIFFYPS)
|
||||
bcc cv_wait
|
||||
bne cv_done
|
||||
lda cv_el
|
||||
cmp #<(WAITSECS*JIFFYPS)
|
||||
bcc cv_wait
|
||||
cv_done:
|
||||
#endif
|
||||
|
||||
#if WAITMODE == 3
|
||||
lda #$00
|
||||
sta $c6 ; empty the keyboard buffer first
|
||||
lda $a2
|
||||
sta cv_t0
|
||||
lda $a1
|
||||
sta cv_t0+1
|
||||
cv_wait:
|
||||
jsr $ffe4 ; GETIN -- any key ends the slide immediately
|
||||
bne cv_done
|
||||
sec ; else check the elapsed-jiffies timeout
|
||||
lda $a2
|
||||
sbc cv_t0
|
||||
sta cv_el
|
||||
lda $a1
|
||||
sbc cv_t0+1
|
||||
sta cv_el+1
|
||||
lda cv_el+1
|
||||
cmp #>(WAITSECS*JIFFYPS)
|
||||
bcc cv_wait
|
||||
bne cv_done
|
||||
lda cv_el
|
||||
cmp #<(WAITSECS*JIFFYPS)
|
||||
bcc cv_wait
|
||||
cv_done:
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue