First public commit.
This commit is contained in:
parent
2a48f52979
commit
4bac9d83ed
288 changed files with 18417 additions and 1076 deletions
0
lenser/iigs/__init__.py
Normal file
0
lenser/iigs/__init__.py
Normal file
19
lenser/iigs/convert/__init__.py
Normal file
19
lenser/iigs/convert/__init__.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
"""Apple IIGS conversion dispatch."""
|
||||
from __future__ import annotations
|
||||
|
||||
from ... import imageprep
|
||||
from . import shr, mono
|
||||
|
||||
_MODULES = {"shr": shr, "mono": mono}
|
||||
MODES = list(_MODULES.keys())
|
||||
|
||||
|
||||
def convert_image(path_or_img, mode="shr", palette_name="iigs",
|
||||
dither_mode="floyd", intensive=False, prep_opt=None,
|
||||
base_color=None):
|
||||
prep_opt = prep_opt or imageprep.PrepOptions()
|
||||
module = _MODULES.get(mode, shr)
|
||||
img_rgb = imageprep.prepare(path_or_img, module.WIDTH, module.HEIGHT,
|
||||
module.PIXEL_ASPECT, prep_opt, border_rgb=(0, 0, 0))
|
||||
return module.convert(img_rgb, palette_name, dither_mode, intensive,
|
||||
base_color=base_color)
|
||||
130
lenser/iigs/convert/_common.py
Normal file
130
lenser/iigs/convert/_common.py
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
"""Apple IIGS Super Hi-Res encoder: 320x200, 16 palettes x 16 colours (of 4096),
|
||||
one palette per scanline (the SCB).
|
||||
|
||||
Lines are clustered into 16 palette groups; each group's 16 colours come from a
|
||||
k-means of its pixels (quantised to 12-bit); each line is then assigned its best
|
||||
palette and dithered to that palette's 16 colours. Mono uses a single 16-grey
|
||||
palette for all lines.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ... import dither, palette as c64pal
|
||||
from ...convert.base import perceptual_error
|
||||
from .. import palette as gs
|
||||
|
||||
W, H = 320, 200
|
||||
NPAL = 16
|
||||
PALSIZE = 16
|
||||
# SHR RAM block ($2000-$9FFF) offsets, relative to the 32K block base ($2000):
|
||||
PIX_OFF, SCB_OFF, PAL_OFF = 0x0000, 0x7D00, 0x7E00
|
||||
BLOCK_LEN = 0x8000
|
||||
|
||||
|
||||
def _kmeans(pts, k, iters=12, seed=0):
|
||||
rng = np.random.default_rng(seed)
|
||||
if len(pts) <= k:
|
||||
c = np.zeros((k, pts.shape[1])); c[:len(pts)] = pts; return c
|
||||
cen = pts[rng.choice(len(pts), k, replace=False)].copy()
|
||||
for _ in range(iters):
|
||||
lab = np.argmin(((pts[:, None, :] - cen[None]) ** 2).sum(2), 1)
|
||||
for j in range(k):
|
||||
m = pts[lab == j]
|
||||
if len(m):
|
||||
cen[j] = m.mean(0)
|
||||
return cen
|
||||
|
||||
|
||||
def _build_palettes(img_lab, img_rgb, mono, base_color):
|
||||
"""Return (pal_rgb (NPAL,16,3) float 0-255, pal_q4 (NPAL,16,3) 4-bit,
|
||||
line_pal (H,) palette index per scanline)."""
|
||||
if mono:
|
||||
rgb, q4 = gs.greys12(PALSIZE)
|
||||
if base_color is not None:
|
||||
# tint: black -> base hue -> white ramp
|
||||
base = np.array(base_color, float)
|
||||
t = np.linspace(0, 1, PALSIZE)[:, None]
|
||||
ramp = np.where(t < 0.5, base * (t / 0.5),
|
||||
base + (255 - base) * ((t - 0.5) / 0.5))
|
||||
rgb, q4 = gs.quantize12(ramp)
|
||||
pal_rgb = np.repeat(rgb[None], NPAL, 0)
|
||||
pal_q4 = np.repeat(q4[None], NPAL, 0)
|
||||
return pal_rgb, pal_q4, np.zeros(H, np.int64)
|
||||
|
||||
# cluster the 200 lines into NPAL groups by mean colour
|
||||
line_mean = img_lab.mean(1) # (H,3)
|
||||
gc = _kmeans(line_mean, NPAL)
|
||||
grp = np.argmin(((line_mean[:, None, :] - gc[None]) ** 2).sum(2), 1)
|
||||
|
||||
pal_rgb = np.zeros((NPAL, PALSIZE, 3))
|
||||
pal_q4 = np.zeros((NPAL, PALSIZE, 3), np.int64)
|
||||
rng = np.random.default_rng(0)
|
||||
for g in range(NPAL):
|
||||
lines = np.where(grp == g)[0]
|
||||
if len(lines) == 0:
|
||||
lines = np.array([g % H])
|
||||
pool = img_rgb[lines].reshape(-1, 3)
|
||||
if len(pool) > 4000:
|
||||
pool = pool[rng.choice(len(pool), 4000, replace=False)]
|
||||
cen = _kmeans(c64pal.srgb_to_lab(pool.reshape(1, -1, 3))[0], PALSIZE)
|
||||
# k-means in Lab -> map centroids back to RGB via nearest pool pixel
|
||||
plab_pool = c64pal.srgb_to_lab(pool.reshape(1, -1, 3))[0]
|
||||
rgb16 = np.zeros((PALSIZE, 3))
|
||||
for i in range(PALSIZE):
|
||||
rgb16[i] = pool[np.argmin(((plab_pool - cen[i]) ** 2).sum(1))]
|
||||
rgb_q, q4 = gs.quantize12(rgb16)
|
||||
pal_rgb[g], pal_q4[g] = rgb_q, q4
|
||||
|
||||
# assign each line its best palette (min nearest-colour error)
|
||||
pal_lab = c64pal.srgb_to_lab(pal_rgb.reshape(1, -1, 3))[0].reshape(NPAL, PALSIZE, 3)
|
||||
line_pal = np.zeros(H, np.int64)
|
||||
for y in range(H):
|
||||
row = img_lab[y] # (W,3)
|
||||
best, berr = 0, np.inf
|
||||
for g in range(NPAL):
|
||||
e = np.min(((row[:, None, :] - pal_lab[g][None]) ** 2).sum(2), 1).sum()
|
||||
if e < berr:
|
||||
berr, best = e, g
|
||||
line_pal[y] = best
|
||||
return pal_rgb, pal_q4, line_pal
|
||||
|
||||
|
||||
def encode(img_rgb, dither_mode, mono=False, base_color=None):
|
||||
img_lab = c64pal.srgb_to_lab(img_rgb)
|
||||
pal_rgb, pal_q4, line_pal = _build_palettes(img_lab, img_rgb, mono, base_color)
|
||||
|
||||
# combined colour table (NPAL*16) for the dither; per-pixel allowed = its
|
||||
# line's 16 palette colours
|
||||
rgb_all = pal_rgb.reshape(-1, 3)
|
||||
plab_all = c64pal.srgb_to_lab(rgb_all.reshape(1, -1, 3))[0]
|
||||
allowed = np.zeros((H, W, PALSIZE), np.int64)
|
||||
for y in range(H):
|
||||
base = line_pal[y] * PALSIZE
|
||||
allowed[y, :, :] = np.arange(base, base + PALSIZE)
|
||||
idx = dither.quantize(img_lab, allowed, plab_all, dither_mode).astype(np.int64)
|
||||
nib = (idx - line_pal[:, None] * PALSIZE).astype(np.uint8) # 0-15 within palette
|
||||
|
||||
# ---- emit the 32K SHR block ($2000-$9FFF) ----
|
||||
block = bytearray(BLOCK_LEN)
|
||||
for y in range(H):
|
||||
row = nib[y]
|
||||
off = PIX_OFF + y * 160
|
||||
for bx in range(160):
|
||||
block[off + bx] = (int(row[bx * 2]) << 4) | (int(row[bx * 2 + 1]) & 0x0F)
|
||||
block[SCB_OFF + y] = line_pal[y] & 0x0F # 320 mode, palette n
|
||||
for p in range(NPAL):
|
||||
for c in range(PALSIZE):
|
||||
r4, g4, b4 = pal_q4[p, c]
|
||||
block[PAL_OFF + p * 32 + c * 2:PAL_OFF + p * 32 + c * 2 + 2] = \
|
||||
gs.color_word(int(r4), int(g4), int(b4))
|
||||
|
||||
preview = rgb_all[idx].astype(np.uint8)
|
||||
if mono:
|
||||
# measure against luminance (the greys have no hue), like other mono modes
|
||||
lum = img_lab.copy(); lum[..., 1:] = 0.0
|
||||
plum = plab_all.copy(); plum[:, 1:] = 0.0
|
||||
err = perceptual_error(idx, lum, plum)
|
||||
else:
|
||||
err = perceptual_error(idx, img_lab, plab_all)
|
||||
return bytes(block), preview, err
|
||||
25
lenser/iigs/convert/mono.py
Normal file
25
lenser/iigs/convert/mono.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
"""Apple IIGS SHR monochrome: 320x200 with a single 16-level grey palette (the
|
||||
richest greyscale in lenser). ``--mono-base`` tints the ramp toward a colour."""
|
||||
from __future__ import annotations
|
||||
|
||||
from ...convert.base import Conversion
|
||||
from ...palette import get_palette
|
||||
from . import _common
|
||||
|
||||
WIDTH, HEIGHT = 320, 200
|
||||
PIXEL_ASPECT = 1.0
|
||||
|
||||
|
||||
def convert(img_rgb, palette_name="iigs", dither_mode="floyd",
|
||||
intensive=False, base_color=None):
|
||||
base_rgb = None
|
||||
if isinstance(base_color, int) and 0 <= base_color < 16:
|
||||
base_rgb = get_palette("colodore")[base_color] # tint hue from C64 pal
|
||||
block, preview, err = _common.encode(img_rgb, dither_mode, mono=True,
|
||||
base_color=base_rgb)
|
||||
return Conversion(
|
||||
mode="mono", width=WIDTH, height=HEIGHT, pixel_aspect=PIXEL_ASPECT,
|
||||
index_image=None, data=block, data_addr=0x2000, viewer="iigs",
|
||||
preview_rgb=preview, error=err,
|
||||
meta={"palette": "iigs", "dither": dither_mode},
|
||||
)
|
||||
19
lenser/iigs/convert/shr.py
Normal file
19
lenser/iigs/convert/shr.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
"""Apple IIGS Super Hi-Res: 320x200, 16 colours/line from 16 palettes of 4096."""
|
||||
from __future__ import annotations
|
||||
|
||||
from ...convert.base import Conversion
|
||||
from . import _common
|
||||
|
||||
WIDTH, HEIGHT = 320, 200
|
||||
PIXEL_ASPECT = 1.0
|
||||
|
||||
|
||||
def convert(img_rgb, palette_name="iigs", dither_mode="floyd",
|
||||
intensive=False, base_color=None):
|
||||
block, preview, err = _common.encode(img_rgb, dither_mode, mono=False)
|
||||
return Conversion(
|
||||
mode="shr", width=WIDTH, height=HEIGHT, pixel_aspect=PIXEL_ASPECT,
|
||||
index_image=None, data=block, data_addr=0x2000, viewer="iigs",
|
||||
preview_rgb=preview, error=err,
|
||||
meta={"palette": "iigs", "dither": dither_mode},
|
||||
)
|
||||
13
lenser/iigs/exporter.py
Normal file
13
lenser/iigs/exporter.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
"""Build a bootable Apple IIGS 5.25" .dsk (boots via slot 6 like an Apple II)."""
|
||||
from __future__ import annotations
|
||||
|
||||
from ..apple import dsk
|
||||
from .viewer.assemble import assemble_boot
|
||||
|
||||
|
||||
def export_dsk(conv, output_path, source_path=None, display="forever",
|
||||
seconds=0, video="ntsc"):
|
||||
if not output_path.lower().endswith((".dsk", ".do")):
|
||||
output_path += ".dsk"
|
||||
boot = assemble_boot()
|
||||
return dsk.write_dsk(output_path, dsk.build_boot_dsk(boot, bytes(conv.data)))
|
||||
31
lenser/iigs/palette.py
Normal file
31
lenser/iigs/palette.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
"""Apple IIGS Super Hi-Res colour helpers.
|
||||
|
||||
SHR colours are 12-bit ``$0RGB`` -- 4 bits per channel, each level scaled x17
|
||||
(0->0, 15->255), exactly as MAME's apple2gs palette. There is no fixed master
|
||||
palette: each of the 16 line-palettes holds 16 freely chosen 12-bit colours, so
|
||||
the encoder works in the continuous 4096-colour space and quantises to 12-bit.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
def quantize12(rgb):
|
||||
"""Snap an RGB array (..,3) float 0-255 to the nearest 12-bit SHR colour."""
|
||||
q4 = np.clip(np.rint(np.asarray(rgb) / 17.0), 0, 15).astype(np.int64)
|
||||
return q4 * 17, q4 # (rgb 0..255 in x17 steps, 4-bit)
|
||||
|
||||
|
||||
def color_word(r4: int, g4: int, b4: int) -> bytes:
|
||||
"""16-bit little-endian SHR palette entry: low = (G<<4)|B, high = $0R."""
|
||||
lo = ((g4 & 0x0F) << 4) | (b4 & 0x0F)
|
||||
hi = r4 & 0x0F
|
||||
return bytes([lo, hi])
|
||||
|
||||
|
||||
def greys12(n: int = 16):
|
||||
"""``n`` evenly spaced 12-bit greys, black..white (RGB array, 4-bit array)."""
|
||||
lv4 = np.clip(np.rint(np.linspace(0, 15, n)), 0, 15).astype(np.int64)
|
||||
rgb = np.stack([lv4 * 17] * 3, axis=1).astype(np.float64)
|
||||
q4 = np.stack([lv4] * 3, axis=1)
|
||||
return rgb, q4
|
||||
0
lenser/iigs/viewer/__init__.py
Normal file
0
lenser/iigs/viewer/__init__.py
Normal file
82
lenser/iigs/viewer/assemble.py
Normal file
82
lenser/iigs/viewer/assemble.py
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
"""Assemble the IIGS SHR boot/viewer with `xa` (origin $0800, raw bytes)."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
VIEWER_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
class AssemblerError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def have_xa() -> bool:
|
||||
return shutil.which("xa") is not None
|
||||
|
||||
|
||||
def assemble_boot() -> bytes:
|
||||
"""Return the boot0 sector (<=256 bytes) that loads the SHR block + shows it."""
|
||||
if not have_xa():
|
||||
raise AssemblerError("The 'xa' assembler was not found on PATH.")
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
out = os.path.join(td, "v.bin")
|
||||
proc = subprocess.run(["xa", "-w", "-o", out, "iigs.s"],
|
||||
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:
|
||||
boot = f.read()
|
||||
if len(boot) > 256:
|
||||
raise AssemblerError(f"boot sector is {len(boot)} bytes (>256)")
|
||||
return boot
|
||||
|
||||
|
||||
SS_WAITMODE = {"key": 1, "seconds": 2, "both": 3}
|
||||
|
||||
|
||||
def _xa_wrapper(wrapper: str, what: str) -> bytes:
|
||||
if not have_xa():
|
||||
raise AssemblerError("The 'xa' assembler was not found on PATH.")
|
||||
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", "-w", "-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)
|
||||
|
||||
|
||||
def build_slideshow(n_images: int, advance: str = "both", seconds: int = 10,
|
||||
loop: bool = True, video: str = "ntsc"):
|
||||
"""Assemble the two-stage IIGS SHR slideshow.
|
||||
|
||||
Returns (boot_sector<=256B, stage2_bytes, stage2_pages). The boot reads
|
||||
stage2 (stage2_pages sectors) into $0900, then the images into bank-0 $2000
|
||||
and banks them; stage2 cycles them onto the SHR screen.
|
||||
"""
|
||||
speed = 8 if video != "pal" else 7 # ~1s delay outer count (fast IIGS)
|
||||
stage2 = _xa_wrapper(
|
||||
f"#define WAITMODE {SS_WAITMODE[advance]}\n"
|
||||
f"#define WAITSECS {max(0, min(255, int(seconds)))}\n"
|
||||
f"#define SPEED {speed}\n"
|
||||
f"#define NIMAGES {n_images}\n"
|
||||
f"#define LOOPFLAG {1 if loop else 0}\n"
|
||||
'#include "slideshow_stage2.s"\n', "iigs stage2")
|
||||
s2_pages = max(1, (len(stage2) + 255) // 256)
|
||||
boot = _xa_wrapper(
|
||||
f"#define NIMAGES {n_images}\n"
|
||||
f"#define S2END ${0x09 + s2_pages:02X}\n"
|
||||
'#include "slideshow_boot.s"\n', "iigs boot")
|
||||
if len(boot) > 256:
|
||||
raise AssemblerError(f"IIGS slideshow boot is {len(boot)} bytes (>256)")
|
||||
return boot, stage2, s2_pages
|
||||
116
lenser/iigs/viewer/iigs.s
Normal file
116
lenser/iigs/viewer/iigs.s
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
; lenser -- Apple IIGS Super Hi-Res boot loader + viewer (8-bit, no GS/OS).
|
||||
;
|
||||
; The Disk II boot ROM (slot 6) loads track 0 sector 0 to $0800 and JMPs $0801.
|
||||
; This re-entrant loader reads the 32K SHR block (pixels $2000-$9CFF, SCBs
|
||||
; $9D00, palettes $9E00) into main RAM $2000-$9FFF, then copies it to AUX RAM
|
||||
; with SHR shadowing enabled -- so the writes mirror into bank $E1 (the SHR
|
||||
; screen) -- and turns SHR on (NEWVIDEO bit 7). All 8-bit, no 65C816 needed.
|
||||
|
||||
* = $0800
|
||||
.byte $01 ; ROM scratch (boot sector byte 0)
|
||||
entry: ; $0801, re-entered after every ROM read
|
||||
lda dpage
|
||||
cmp #$a0
|
||||
bcs done ; loaded $2000..$9FFF -> show it
|
||||
lda psec
|
||||
cmp #$10
|
||||
bcc readit ; sectors left on this track
|
||||
jsr seeknext ; finished a track -> step to the next
|
||||
lda #$00
|
||||
sta psec
|
||||
readit:
|
||||
lda psec
|
||||
sta $3d ; desired sector
|
||||
lda curtrk
|
||||
sta $41 ; desired track
|
||||
lda #$00
|
||||
sta $26 ; buffer lo
|
||||
lda dpage
|
||||
sta $27 ; buffer hi
|
||||
inc psec
|
||||
inc dpage
|
||||
ldx $2b ; slot*16 (set by boot ROM)
|
||||
jmp $c65c ; slot 6 ROM read; reads sector then JMP $0801
|
||||
|
||||
done:
|
||||
; Turn on Super Hi-Res (bit 7) AND linearize (bit 6) FIRST. With these
|
||||
; set, CPU writes to bank $E1 $2000-$9FFF are de-interleaved by hardware
|
||||
; into the megaII layout the video reads -- so we can copy our linear SHR
|
||||
; block straight in and it displays correctly.
|
||||
lda $c029
|
||||
ora #$c0
|
||||
sta $c029
|
||||
; copy 32K from bank $00 $2000 -> bank $E1 $2000 (65C816 block move)
|
||||
clc
|
||||
xce ; -> native mode
|
||||
rep #$30 ; 16-bit A/X/Y
|
||||
.al
|
||||
.xl
|
||||
lda #$7fff ; count-1 (32768 bytes)
|
||||
ldx #$2000 ; source offset
|
||||
ldy #$2000 ; dest offset
|
||||
.byte $54, $e1, $00 ; MVN src bank $00 -> dest bank $E1
|
||||
sep #$30 ; 8-bit
|
||||
.as
|
||||
.xs
|
||||
sec
|
||||
xce ; -> emulation mode
|
||||
lda $c034 ; border colour is the low nibble of $C034
|
||||
and #$f0
|
||||
sta $c034 ; black border
|
||||
hang:
|
||||
jmp hang
|
||||
|
||||
; advance the head one track (two half-steps), phase-overlap step.
|
||||
seeknext:
|
||||
inc curtrk
|
||||
lda #$00
|
||||
sta $0a
|
||||
jsr onestep
|
||||
jsr onestep
|
||||
lda halftrk
|
||||
and #$03
|
||||
asl
|
||||
ora $2b
|
||||
tax
|
||||
lda $c080,x
|
||||
rts
|
||||
onestep:
|
||||
lda halftrk
|
||||
clc
|
||||
adc #$01
|
||||
and #$03
|
||||
asl
|
||||
ora $2b
|
||||
tax
|
||||
lda $c081,x
|
||||
ldx $0a
|
||||
lda ontable,x
|
||||
jsr wait
|
||||
lda halftrk
|
||||
and #$03
|
||||
asl
|
||||
ora $2b
|
||||
tax
|
||||
lda $c080,x
|
||||
ldx $0a
|
||||
lda offtable,x
|
||||
jsr wait
|
||||
inc halftrk
|
||||
inc $0a
|
||||
rts
|
||||
ontable: .byte $13,$0a,$08,$06
|
||||
offtable: .byte $46,$1a,$10,$0c
|
||||
wait:
|
||||
tay
|
||||
w1: ldx #$00
|
||||
w2: dex
|
||||
bne w2
|
||||
dey
|
||||
bne w1
|
||||
rts
|
||||
|
||||
psec: .byte $01
|
||||
dpage: .byte $20
|
||||
halftrk: .byte $00
|
||||
curtrk: .byte $00
|
||||
115
lenser/iigs/viewer/slideshow_boot.s
Normal file
115
lenser/iigs/viewer/slideshow_boot.s
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
; lenser -- Apple IIGS SHR slideshow, stage 1 boot sector (<=256 bytes).
|
||||
;
|
||||
; The Disk II boot ROM loads track 0 sector 0 to $0800 and JMPs $0801; every ROM
|
||||
; sector read JMPs back to $0801, so this is the master read driver. It first
|
||||
; reads stage 2 into $0900 (S2END-$09 sectors), then reads each 32K SHR image
|
||||
; into bank-0 $2000-$9FFF and calls stage 2 (stash, $0900) to bank it; after the
|
||||
; last image it JMPs the stage-2 cycle viewer ($0903).
|
||||
;
|
||||
; #defines from the wrapper -- NIMAGES, S2END (one past the last stage-2 page).
|
||||
|
||||
* = $0800
|
||||
.byte $01 ; ROM scratch (boot sector byte 0)
|
||||
entry: ; $0801, re-entered after every ROM read
|
||||
lda dpage
|
||||
cmp endpg
|
||||
bcc rdcont ; not at the current region's end yet
|
||||
lda phase
|
||||
bne imgdone
|
||||
; stage 2 finished loading -> start reading images into $2000
|
||||
inc phase
|
||||
lda #$20
|
||||
sta dpage
|
||||
lda #$a0
|
||||
sta endpg
|
||||
bne rdcont ; always (endpg = $a0)
|
||||
imgdone:
|
||||
jsr $0900 ; stash bank0 $2000 -> next bank
|
||||
inc imgcnt
|
||||
lda imgcnt
|
||||
cmp #NIMAGES
|
||||
bcs allloaded
|
||||
lda #$20
|
||||
sta dpage ; reset for the next image
|
||||
bne rdcont ; always
|
||||
allloaded:
|
||||
jmp $0903 ; stage-2 cycle viewer (never returns)
|
||||
rdcont:
|
||||
lda psec
|
||||
cmp #$10
|
||||
bcc readit
|
||||
jsr seeknext
|
||||
lda #$00
|
||||
sta psec
|
||||
readit:
|
||||
lda psec
|
||||
sta $3d
|
||||
lda curtrk
|
||||
sta $41
|
||||
lda #$00
|
||||
sta $26
|
||||
lda dpage
|
||||
sta $27
|
||||
inc psec
|
||||
inc dpage
|
||||
ldx $2b
|
||||
jmp $c65c ; slot-6 ROM read; reads a sector then JMP $0801
|
||||
|
||||
; advance the head one track (two half-steps), phase-overlap step (from iigs.s)
|
||||
seeknext:
|
||||
inc curtrk
|
||||
lda #$00
|
||||
sta $0a
|
||||
jsr onestep
|
||||
jsr onestep
|
||||
lda halftrk
|
||||
and #$03
|
||||
asl
|
||||
ora $2b
|
||||
tax
|
||||
lda $c080,x
|
||||
rts
|
||||
onestep:
|
||||
lda halftrk
|
||||
clc
|
||||
adc #$01
|
||||
and #$03
|
||||
asl
|
||||
ora $2b
|
||||
tax
|
||||
lda $c081,x
|
||||
ldx $0a
|
||||
lda ontable,x
|
||||
jsr wait
|
||||
lda halftrk
|
||||
and #$03
|
||||
asl
|
||||
ora $2b
|
||||
tax
|
||||
lda $c080,x
|
||||
ldx $0a
|
||||
lda offtable,x
|
||||
jsr wait
|
||||
inc halftrk
|
||||
inc $0a
|
||||
rts
|
||||
ontable: .byte $13,$0a,$08,$06
|
||||
offtable: .byte $46,$1a,$10,$0c
|
||||
wait:
|
||||
tay
|
||||
w1:
|
||||
ldx #$00
|
||||
w2:
|
||||
dex
|
||||
bne w2
|
||||
dey
|
||||
bne w1
|
||||
rts
|
||||
|
||||
phase: .byte 0 ; 0 = loading stage 2, 1 = loading images
|
||||
endpg: .byte S2END ; stage-2 ends one past this page
|
||||
imgcnt: .byte 0
|
||||
psec: .byte $01 ; next physical sector (track 0 starts at 1)
|
||||
dpage: .byte $09 ; stage 2 loads from $0900
|
||||
halftrk: .byte $00
|
||||
curtrk: .byte $00
|
||||
147
lenser/iigs/viewer/slideshow_stage2.s
Normal file
147
lenser/iigs/viewer/slideshow_stage2.s
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
; lenser -- Apple IIGS SHR slideshow, stage 2 (loaded at $0900 by the boot).
|
||||
;
|
||||
; The boot reads each 32K SHR image into bank-0 $2000-$9FFF and calls stash to
|
||||
; block-move it up into a free RAM bank (image i -> bank 1+i, offset $2000).
|
||||
; Once all images are stashed the boot JMPs cycle, which block-moves each banked
|
||||
; image into the SHR screen (bank $E1 $2000) in turn, waiting (key/seconds/both)
|
||||
; between slides. Self-modifying MVN bank bytes select the bank per image.
|
||||
;
|
||||
; #defines from the wrapper -- WAITMODE 1 key/2 seconds/3 both, WAITSECS, SPEED
|
||||
; (delay outer count ~1s at the IIGS clock), NIMAGES, LOOPFLAG.
|
||||
|
||||
* = $0900
|
||||
jmp stash ; $0900 entry (called per loaded image)
|
||||
jmp cycle ; $0903 entry (called once, all images loaded)
|
||||
|
||||
; ---- MVN bank0 $2000 -> curbank $2000 (32K), then advance curbank ----
|
||||
stash:
|
||||
clc
|
||||
xce
|
||||
rep #$30
|
||||
.al
|
||||
.xl
|
||||
lda #$7fff
|
||||
ldx #$2000
|
||||
ldy #$2000
|
||||
.byte $54 ; MVN dest, src
|
||||
mvndst: .byte $01 ; dest bank (patched each call)
|
||||
.byte $00 ; src bank 0
|
||||
.byte $4b ; phk
|
||||
.byte $ab ; plb -- restore DBR = 0 (MVN left it = dest bank)
|
||||
sep #$30
|
||||
.as
|
||||
.xs
|
||||
sec
|
||||
xce
|
||||
inc mvndst ; next image -> next bank
|
||||
rts
|
||||
|
||||
; ---- show each banked image on the SHR screen, forever ----
|
||||
cycle:
|
||||
lda $c029
|
||||
ora #$c0
|
||||
sta $c029 ; SHR on + linearise
|
||||
lda $c034
|
||||
and #$f0
|
||||
sta $c034 ; black border
|
||||
lda #$01
|
||||
sta mvnsrc
|
||||
lda #$00
|
||||
sta cyci
|
||||
cloop:
|
||||
clc
|
||||
xce
|
||||
rep #$30
|
||||
.al
|
||||
.xl
|
||||
lda #$7fff
|
||||
ldx #$2000
|
||||
ldy #$2000
|
||||
.byte $54, $e1 ; MVN dest $E1, src ...
|
||||
mvnsrc: .byte $01 ; src bank (patched per slide)
|
||||
.byte $4b ; phk
|
||||
.byte $ab ; plb -- restore DBR = 0
|
||||
sep #$30
|
||||
.as
|
||||
.xs
|
||||
sec
|
||||
xce
|
||||
jsr sswait
|
||||
inc cyci
|
||||
lda cyci
|
||||
cmp #NIMAGES
|
||||
bcc cnext
|
||||
#if LOOPFLAG == 1
|
||||
lda #$01
|
||||
sta mvnsrc
|
||||
lda #$00
|
||||
sta cyci
|
||||
jmp cloop
|
||||
#else
|
||||
jmp $e000 ; (no loop) cold start to firmware
|
||||
#endif
|
||||
cnext:
|
||||
inc mvnsrc
|
||||
jmp cloop
|
||||
|
||||
; ---- wait (returns); $C000 bit7 = key, delay loop for the timer ----
|
||||
sswait:
|
||||
bit $c010
|
||||
#if WAITMODE == 1
|
||||
swk:
|
||||
lda $c000
|
||||
bpl swk
|
||||
bit $c010
|
||||
rts
|
||||
#endif
|
||||
#if WAITMODE == 2
|
||||
lda #WAITSECS
|
||||
sta wsec
|
||||
wso:
|
||||
lda #SPEED
|
||||
sta wmid
|
||||
wsm:
|
||||
ldx #$00
|
||||
wsx:
|
||||
ldy #$00
|
||||
wsy:
|
||||
dey
|
||||
bne wsy
|
||||
dex
|
||||
bne wsx
|
||||
dec wmid
|
||||
bne wsm
|
||||
dec wsec
|
||||
bne wso
|
||||
rts
|
||||
#endif
|
||||
#if WAITMODE == 3
|
||||
lda #WAITSECS
|
||||
sta wsec
|
||||
wbo:
|
||||
lda #SPEED
|
||||
sta wmid
|
||||
wbm:
|
||||
lda $c000
|
||||
bmi wbk ; a key ends the slide
|
||||
ldx #$00
|
||||
wbx:
|
||||
ldy #$00
|
||||
wby:
|
||||
dey
|
||||
bne wby
|
||||
dex
|
||||
bne wbx
|
||||
dec wmid
|
||||
bne wbm
|
||||
dec wsec
|
||||
bne wbo
|
||||
rts
|
||||
wbk:
|
||||
bit $c010
|
||||
rts
|
||||
#endif
|
||||
|
||||
wsec: .byte 0
|
||||
wmid: .byte 0
|
||||
cyci: .byte 0
|
||||
Loading…
Add table
Add a link
Reference in a new issue