Merge pull request #1 from KrisKennaway/dhgr2

Dhgr2
This commit is contained in:
KrisKennaway 2019-07-11 23:56:06 +01:00 committed by GitHub
commit 2dcd3dadab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 2502 additions and 705 deletions

View File

@ -397,7 +397,7 @@ _op_header_hgr:
; Y register has the high byte of the W5100 address pointer in the RX socket code, so we
; can't trash this until we are ready to point back there.
checkrecv:
BIT TICK ; 4
STA TICK ; 4
LDA #<S0RXRSR ; 2 Socket 0 Received Size register
STA WADRL ; 4
@ -440,7 +440,7 @@ recv: ; 15 cycles so far
; X will usually already be 0 from op_ack except during first frame when reading
; header but reset it unconditionally
LDX #$00 ; 2
BIT TICK ; 4 ; 36
STA TICK ; 4 ; 36
NOP ; 2
STA dummy ; 4
@ -464,7 +464,7 @@ op_nop:
; - read 2 bytes from the stream as address of next opcode
;
; Each opcode has 6 cycles of padding, which is necessary to support reordering things to
; get the second "BIT TICK" at the right cycle offset.
; get the second "STA TICK" at the right cycle offset.
;
; Where possible we share code by JMPing to a common tail instruction sequence in one of the
; earlier opcodes. This is critical for reducing code size enough to fit.
@ -480,8 +480,8 @@ op_nop:
.macro op_tick_4 page
;4+(4)+2+4+4+4+5+4+5+4+5+4+5+4+4+4+4+3=73
.ident (.concat ("op_tick_4_page_", .string(page))):
BIT TICK ; 4
BIT TICK ; 4
STA TICK ; 4
STA TICK ; 4
STA zpdummy ; 3
STA zpdummy ; 3
@ -524,9 +524,9 @@ tickident page, 7
.macro op_tick_6 page
;4+(2+4)+3+4+4+5+4+5+4+5+4+5+4+4+4+5+3
.ident (.concat ("op_tick_6_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
NOP ; 2
BIT TICK ; 4
STA TICK ; 4
STA zpdummy ; 3
@ -569,9 +569,9 @@ tickident page, 8
.macro op_tick_8 page
;4+(4+4)+3+3+55
.ident (.concat ("op_tick_8_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
BIT TICK ; 4
STA TICK ; 4
STA zpdummy ; 3
JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_55")) ; 3 + 55
@ -580,10 +580,10 @@ tickident page, 8
.macro op_tick_10 page
;4+(4+2+4)+3+56
.ident (.concat ("op_tick_10_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
NOP ; 2
BIT TICK ; 4
STA TICK ; 4
JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_56")) ; 3 + 56
.endmacro
@ -591,10 +591,10 @@ tickident page, 8
.macro op_tick_12 page
;4+(4+4+4)+3+3+51
.ident (.concat ("op_tick_12_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
LDY WDATA ; 4
BIT TICK ; 4
STA TICK ; 4
STA zpdummy ; 3
JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_51")) ; 3 + 51
@ -603,11 +603,11 @@ tickident page, 8
.macro op_tick_14 page
;4+(4+4+2+4)+3+52
.ident (.concat ("op_tick_14_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
LDY WDATA ; 4
NOP ; 2
BIT TICK ; 4
STA TICK ; 4
JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_52")) ; 3+52
.endmacro
@ -615,14 +615,14 @@ tickident page, 8
.macro op_tick_16 page
; 4+(4+4+4+4)+5+2+3+43
.ident (.concat ("op_tick_16_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
; This temporarily violates X=0 invariant required by tick_6, but lets us share a
; common opcode tail; otherwise we need a dummy 4-cycle opcode between the ticks, which
; doesn't leave enough to JMP with.
LDX WDATA ; 4
LDY WDATA ; 4
BIT TICK ; 4
STA TICK ; 4
STA page << 8,x ; 5
LDX #$00 ; 2 restore X=0 invariant
@ -633,14 +633,14 @@ tickident page, 8
.macro op_tick_18 page
; 4 + (4+4+4+2+4)+5+5+2+2+4+5+4+5+4+4+4+4+3
.ident (.concat ("op_tick_18_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
LDY WDATA ; 4
; lets us reorder the 5-cycle STA page << 8,y outside of tick loop.
; This temporarily violates X=0 invariant required by tick_6
LDX WDATA ; 4
NOP ; 2
BIT TICK ; 4
STA TICK ; 4
STA page << 8,Y ; 5
STA page << 8,X ; 5
@ -667,12 +667,12 @@ tickident page, 8
.macro op_tick_20 page
;4+(4+4+5+3+4)+3+46=73
.ident (.concat ("op_tick_20_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
LDY WDATA ; 4
STA page << 8,Y ; 5
STA zpdummy ; 3
BIT TICK ; 4
STA TICK ; 4
JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_46"))
.endmacro
@ -681,12 +681,12 @@ tickident page, 8
.macro op_tick_22 page
; 4+(4+4+5+4+4)+3+3+42
.ident (.concat ("op_tick_22_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
LDY WDATA ; 4
STA page << 8,Y ; 5
LDY WDATA ; 4
BIT TICK ; 4
STA TICK ; 4
STA zpdummy ; 3
JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_42")) ; 3 + 42
@ -695,13 +695,13 @@ tickident page, 8
.macro op_tick_24 page
;4+(4+4+5+4+3+4)+3+42
.ident (.concat ("op_tick_24_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
LDY WDATA ; 4
STA page << 8,Y ; 5
LDY WDATA ; 4
STA zpdummy ; 3
BIT TICK ; 4
STA TICK ; 4
JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_42"))
.endmacro
@ -709,13 +709,13 @@ tickident page, 8
.macro op_tick_26 page ; pattern repeats from op_tick_8
; 4+(4+4+5+4+5+4)+3+37
.ident (.concat ("op_tick_26_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
LDY WDATA ; 4
STA page << 8,Y ; 5
LDY WDATA ; 4
STA page << 8,Y ; 5
BIT TICK; 4
STA TICK; 4
STA zpdummy ; 3
JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_37")) ; 3 + 37
@ -724,14 +724,14 @@ tickident page, 8
.macro op_tick_28 page ; pattern repeats from op_tick_10
; 4+(4+2+4+5+4+5+4)+3+38
.ident (.concat ("op_tick_28_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
LDY WDATA ; 4
STA page << 8,Y ; 5
LDY WDATA ; 4
STA page << 8,Y ; 5
NOP ; 2
BIT TICK ; 4
STA TICK ; 4
JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_38"))
.endmacro
@ -739,14 +739,14 @@ tickident page, 8
.macro op_tick_30 page ; pattern repeats from op_tick_12
;4+(4+4+5+4+5+4+4)+3+3+33
.ident (.concat ("op_tick_30_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
LDY WDATA ; 4
STA page << 8,Y ; 5
LDY WDATA ; 4
STA page << 8,Y ; 5
LDY WDATA ; 4
BIT TICK ; 4
STA TICK ; 4
STA zpdummy ; 3
JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_33")) ; 3 + 33
@ -755,7 +755,7 @@ tickident page, 8
.macro op_tick_32 page ; pattern repeats from op_tick_14
;4+(4+4+5+4+5+4+2+4)+3+34
.ident (.concat ("op_tick_32_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
LDY WDATA ; 4
STA page << 8,Y ; 5
@ -763,7 +763,7 @@ tickident page, 8
STA page << 8,Y ; 5
LDY WDATA ; 4
NOP ; 2
BIT TICK ; 4
STA TICK ; 4
JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_34"))
.endmacro
@ -771,7 +771,7 @@ tickident page, 8
.macro op_tick_34 page ; pattern repeats from op_tick_16
; 4+(4+4+5+4+5+4+4+4)+2+5+5+3+20
.ident (.concat ("op_tick_34_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
LDY WDATA ; 4
STA page << 8,Y ; 5
@ -779,7 +779,7 @@ tickident page, 8
STA page << 8,Y ; 5
LDY WDATA ; 4
LDX WDATA ; 4 ; allows reordering STA ...,X outside ticks
BIT TICK ; 4
STA TICK ; 4
STA page << 8,Y ; 5
STA page << 8,X ; 5
@ -792,7 +792,7 @@ tickident page, 8
.macro op_tick_36 page ; pattern repeats from op_tick_18
;4+(4+4+5+4+5+4+4+2+4)+5+5+2+2+4+4+4+4+3
.ident (.concat ("op_tick_36_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
LDY WDATA ; 4
STA page << 8,Y ; 5
@ -801,7 +801,7 @@ tickident page, 8
LDY WDATA ; 4
LDX WDATA ; 4
NOP ; 2
BIT TICK ; 4
STA TICK ; 4
STA page << 8,Y ; 5
STA page << 8,X ; 5
@ -820,7 +820,7 @@ tickident page, 8
.macro op_tick_38 page ; pattern repeats from op_tick_20
; 4 + (4+4+5+4+5+4+5+3+4)+3+28
.ident (.concat ("op_tick_38_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
LDY WDATA ; 4
STA page << 8,Y ; 5
@ -829,7 +829,7 @@ tickident page, 8
LDY WDATA ; 4
STA page << 8,Y ; 5
STA zpdummy ; 3
BIT TICK ; 4
STA TICK ; 4
JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_28")) ; 3 + 28
.endmacro
@ -838,7 +838,7 @@ tickident page, 8
.macro op_tick_40 page ; pattern repeats from op_tick_22
;4+(4+4+5+4+5+4+5+4+4)+3+3+24
.ident (.concat ("op_tick_40_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
LDY WDATA ; 4
STA page << 8,Y ; 5
@ -847,7 +847,7 @@ tickident page, 8
LDY WDATA ; 4
STA page << 8,Y ; 5
LDY WDATA ; 4
BIT TICK ; 4
STA TICK ; 4
STA zpdummy
JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_24"))
@ -856,7 +856,7 @@ tickident page, 8
.macro op_tick_42 page ; pattern repeats from op_tick_24
;4+(4+4+5+4+5+4+5+4+3+4)+3+24
.ident (.concat ("op_tick_42_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
LDY WDATA ; 4
STA page << 8,Y ; 5
@ -866,7 +866,7 @@ tickident page, 8
STA page << 8,Y ; 5
LDY WDATA ; 4
STA zpdummy ; 3
BIT TICK ; 4
STA TICK ; 4
JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_24")) ; 3 + 24
.endmacro
@ -874,7 +874,7 @@ tickident page, 8
.macro op_tick_44 page ; pattern repeats from op_tick_26
; 4 + (4+4+5+4+5+4+5+4+5+4)+3+3+19
.ident (.concat ("op_tick_44_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
LDY WDATA ; 4
STA page << 8,Y ; 5
@ -884,7 +884,7 @@ tickident page, 8
STA page << 8,Y ; 5
LDY WDATA ; 4
STA page << 8,Y ; 5
BIT TICK; 4
STA TICK; 4
STA zpdummy ; 3
JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_19")) ; 3 + 19
@ -893,7 +893,7 @@ tickident page, 8
.macro op_tick_46 page ; pattern repeats from op_tick_28
;4+(4+2+4+5+4+5+4+5+4+5+4)+3+20
.ident (.concat ("op_tick_46_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
LDY WDATA ; 4
STA page << 8,Y ; 5
@ -904,7 +904,7 @@ tickident page, 8
LDY WDATA ; 4
STA page << 8,Y ; 5
NOP ; 2
BIT TICK ; 4
STA TICK ; 4
JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_20"))
.endmacro
@ -912,7 +912,7 @@ tickident page, 8
.macro op_tick_48 page ; pattern repeats from op_tick_30
;4+(4+4+5+4+5+4+5+4+5+4+4)+3+3+15
.ident (.concat ("op_tick_48_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
LDY WDATA ; 4
STA page << 8,Y ; 5
@ -924,7 +924,7 @@ tickident page, 8
STA page << 8,Y ; 5
LDA WDATA ; 4
BIT TICK ; 4
STA TICK ; 4
STA zpdummy ; 3
JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_15")) ; 3 + 15
@ -933,7 +933,7 @@ tickident page, 8
.macro op_tick_50 page ; pattern repeats from op_tick_32
;4+(4+4+5+4+5+4+5+4+5+4+2+4)+3+16
.ident (.concat ("op_tick_50_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
LDY WDATA ; 4
STA page << 8,Y ; 5
@ -946,7 +946,7 @@ tickident page, 8
LDA WDATA ; 4
NOP ; 2
BIT TICK ; 4
STA TICK ; 4
JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_16"))
.endmacro
@ -954,7 +954,7 @@ tickident page, 8
.macro op_tick_52 page ; pattern repeats from op_tick_34
;4+(4+4+5+4+5+4+5+4+5+4+4+4)+2+3+12
.ident (.concat ("op_tick_52_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
LDY WDATA ; 4
STA page << 8,Y ; 5
@ -967,7 +967,7 @@ tickident page, 8
LDA WDATA ; 4
STA .ident (.concat ("_op_tick_6_page_", .string(page), "_jmp"))+2 ; 4
BIT TICK ; 4
STA TICK ; 4
NOP ; 2
JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_12"))
@ -976,7 +976,7 @@ tickident page, 8
.macro op_tick_54 page ; pattern repeats from op_tick_36
; 4 + (4+4+5+4+5+4+5+3+3+4+5+4+4)+4+4+4+3
.ident (.concat ("op_tick_54_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
LDY WDATA ; 4
STA page << 8,Y ; 5
@ -992,7 +992,7 @@ tickident page, 8
STA zpdummy ; 3
STA zpdummy ; 3
BIT TICK ; 4
STA TICK ; 4
; used >3 pad cycles between tick pair; can't branch to tail
STA @D+2 ; 4
@ -1005,7 +1005,7 @@ tickident page, 8
.macro op_tick_56 page
; 4+(4+4+5+4+5+4+5+4+5+4+4+4+4)+2+4+4+3
.ident (.concat ("op_tick_56_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
LDY WDATA ; 4
STA page << 8,Y ; 5
@ -1020,7 +1020,7 @@ tickident page, 8
STA @D+2 ; 4
STA dummy ; 4
BIT TICK ; 4
STA TICK ; 4
; used >3 pad cycles between tick pair; can't branch to tail
NOP ; 2
@ -1034,7 +1034,7 @@ tickident page, 8
.macro op_tick_58 page ; pattern repeats from op_tick_40
;4+(4+4+5+4+5+4+5+4+5+4+4+3+3+4)+4+4+3
.ident (.concat ("op_tick_58_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
LDY WDATA ; 4
STA page << 8,Y ; 5
@ -1050,7 +1050,7 @@ tickident page, 8
STA zpdummy ; 3
STA zpdummy ; 3
BIT TICK ; 4
STA TICK ; 4
; used >3 pad cycles between tick pair; can't branch to tail
LDA WDATA ; 4
@ -1062,7 +1062,7 @@ tickident page, 8
.macro op_tick_60 page
; 4+(4+4+5+4+5+4+5+4+5+4+4+4+4+4)+2+4+3
.ident (.concat ("op_tick_60_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
LDY WDATA ; 4
STA page << 8,Y ; 5
@ -1079,7 +1079,7 @@ tickident page, 8
LDA WDATA ; 4
STA dummy ; 4
BIT TICK ; 4
STA TICK ; 4
; used >3 pad cycles between tick pair; can't branch to tail
NOP ; 2
@ -1091,7 +1091,7 @@ tickident page, 8
.macro op_tick_62 page
;4+(4+4+5+4+5+4+5+4+5+4+4+4+3+3+4)+4+3
.ident (.concat ("op_tick_62_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
LDY WDATA ; 4
STA page << 8,Y ; 5
@ -1108,7 +1108,7 @@ tickident page, 8
STA zpdummy ; 3
STA zpdummy ; 3
BIT TICK ; 4
STA TICK ; 4
; used >3 pad cycles between tick pair; can't branch to tail
STA @D+1 ; 4
@ -1119,7 +1119,7 @@ tickident page, 8
.macro op_tick_64 page
;4+(4+4+5+4+5+4+5+4+5+4+4+4+4+4+4)+2+3
.ident (.concat ("op_tick_64_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
LDY WDATA ; 4
STA page << 8,Y ; 5
@ -1137,7 +1137,7 @@ tickident page, 8
STA @D+1 ; 4
STA dummy ; 4
BIT TICK ; 4
STA TICK ; 4
NOP ; 2
@D:
@ -1147,7 +1147,7 @@ tickident page, 8
.macro op_tick_66 page ; pattern repeats from op_tick_8
; 4+(4+4+5+4+5+4+5+4+5+4+4+4+3+4+3+4)+3
.ident (.concat ("op_tick_66_page_", .string(page))):
BIT TICK ; 4
STA TICK ; 4
LDA WDATA ; 4
LDY WDATA ; 4
STA page << 8,Y ; 5
@ -1166,7 +1166,7 @@ tickident page, 8
STA zpdummy ; 3
STA zpdummy ; 3
BIT TICK ; 4
STA TICK ; 4
@D:
JMP op_nop ; 3
@ -1293,7 +1293,7 @@ op_terminate:
; the last 4 bytes in a 2K "TCP frame". i.e. we can assume that we need to consume
; exactly 2K from the W5100 socket buffer.
op_ack:
BIT TICK ; 4
STA TICK ; 4
; allow flip-flopping the PAGE1/PAGE2 soft switches to steer writes to MAIN/AUX screens
; actually this allows touching any $C0XX soft-switch, in case that is useful somehow
@ -1314,7 +1314,7 @@ op_ack:
LDX #<S0RXRD ; 2
STX WADRL ; 4
BIT TICK ; 4 (36)
STA TICK ; 4 (36)
LDA WDATA ; 4 Read high byte
; No need to read low byte since it's guaranteed to be 0 since we're at the end of a 2K frame.
@ -1339,7 +1339,7 @@ op_ack:
; - used as the low byte for resetting the W5100 address pointer when we're ready to start processing more data
LDX #$00 ; 2 restore invariant for dispatch loop
JMP checkrecv ; 3 (37 with following BIT TICK)
JMP checkrecv ; 3 (37 with following STA TICK)
; Quit to ProDOS
exit:

View File

@ -1,25 +1,149 @@
"""Apple II logical display colours."""
"""Apple II nominal display colours, represented by 4-bit dot sequences.
These are the "asymptotic" colours as displayed in e.g. continuous runs of
pixels. The effective colours that are actually displayed are not discrete,
due to NTSC artifacting being a continuous process.
"""
from typing import Tuple, Type
import enum
import functools
class DHGRColours(enum.Enum):
# Value is memory bit order, which is opposite to screen order (bits
# ordered Left to Right on screen)
class NominalColours(enum.Enum):
pass
class HGRColours(NominalColours):
"""Map from 4-bit dot representation to DHGR pixel colours.
Dots are in memory bit order (MSB -> LSB), which is opposite to screen
order (LSB -> MSB is ordered left-to-right on the screen)
Note that these are right-rotated from the HGR mapping, because of a
1-tick phase difference in the colour reference signal for DHGR vs HGR
"""
BLACK = 0b0000
MAGENTA = 0b0001
BROWN = 0b1000
ORANGE = 0b1001 # HGR colour
DARK_GREEN = 0b0100
GREY1 = 0b0101
GREEN = 0b1100 # HGR colour
YELLOW = 0b1101
DARK_BLUE = 0b0010
VIOLET = 0b0011 # HGR colour
GREY2 = 0b1010
PINK = 0b1011
MED_BLUE = 0b0110 # HGR colour
LIGHT_BLUE = 0b0111
AQUA = 0b1110
WHITE = 0b1111
class DHGRColours(NominalColours):
"""Map from 4-bit dot representation to DHGR pixel colours.
Dots are in memory bit order (MSB -> LSB), which is opposite to screen
order (LSB -> MSB is ordered left-to-right on the screen)
Note that these are right-rotated from the HGR mapping, because of a
1-tick phase difference in the colour reference signal for DHGR vs HGR
"""
# representation.
BLACK = 0b0000
MAGENTA = 0b1000
BROWN = 0b0100
ORANGE = 0b1100
ORANGE = 0b1100 # HGR colour
DARK_GREEN = 0b0010
GREY1 = 0b1010
GREEN = 0b0110
GREEN = 0b0110 # HGR colour
YELLOW = 0b1110
DARK_BLUE = 0b0001
VIOLET = 0b1001
VIOLET = 0b1001 # HGR colour
GREY2 = 0b0101
PINK = 0b1101
MED_BLUE = 0b0011
MED_BLUE = 0b0011 # HGR colour
LIGHT_BLUE = 0b1011
AQUA = 0b0111
WHITE = 0b1111
def ror(int4: int, howmany: int) -> int:
"""Rotate-right an int4 some number of times."""
res = int4
for _ in range(howmany):
res = _ror(res)
return res
def _ror(int4: int) -> int:
return ((int4 & 0b1110) >> 1) ^ ((int4 & 0b0001) << 3)
def rol(int4: int, howmany: int) -> int:
"""Rotate-left an int4 some number of times."""
res = int4
for _ in range(howmany):
res = _rol(res)
return res
def _rol(int4: int) -> int:
return ((int4 & 0b0111) << 1) ^ ((int4 & 0b1000) >> 3)
@functools.lru_cache(None)
def dots_to_nominal_colour_pixels(
num_bits: int,
dots: int,
colours: Type[NominalColours],
init_phase: int = 1 # Such that phase = 0 at start of body
) -> Tuple[NominalColours]:
"""Sequence of num_bits nominal colour pixels via sliding 4-bit window.
Includes the 3-bit header that represents the trailing 3 bits of the
previous tuple body. e.g. for DHGR, storing a byte in aux even columns
will also influence the colours of the previous main odd column.
This naively models (approximates) the NTSC colour artifacting.
TODO: Use a more careful analogue colour composition model to produce
effective pixel colours.
TODO: DHGR vs HGR colour differences can be modeled by changing init_phase
"""
res = []
shifted = dots
phase = init_phase
for i in range(num_bits):
colour = rol(shifted & 0b1111, phase)
res.append(colours(colour))
shifted >>= 1
phase += 1
if phase == 4:
phase = 0
return tuple(res)
@functools.lru_cache(None)
def dots_to_nominal_colour_pixel_values(
num_bits: int,
dots: int,
colours: Type[NominalColours],
init_phase: int = 1 # Such that phase = 0 at start of body
) -> Tuple[int]:
""""Sequence of num_bits nominal colour values via sliding 4-bit window."""
return tuple(p.value for p in dots_to_nominal_colour_pixels(
num_bits, dots, colours, init_phase
))

113
transcoder/colours_test.py Normal file
View File

@ -0,0 +1,113 @@
import unittest
import colours
HGRColours = colours.HGRColours
class TestColours(unittest.TestCase):
def test_dots_to_pixels(self):
self.assertEqual(
(
HGRColours.BLACK,
HGRColours.BLACK,
HGRColours.BLACK,
HGRColours.BLACK,
HGRColours.BLACK,
HGRColours.BLACK,
HGRColours.DARK_BLUE,
HGRColours.MED_BLUE,
HGRColours.AQUA,
HGRColours.AQUA,
HGRColours.GREEN,
HGRColours.BROWN,
HGRColours.BLACK,
HGRColours.BLACK,
HGRColours.BLACK,
HGRColours.BLACK,
HGRColours.BLACK,
HGRColours.BLACK,
HGRColours.BLACK,
HGRColours.BLACK,
HGRColours.BLACK,
HGRColours.BLACK,
HGRColours.BLACK,
HGRColours.BLACK,
HGRColours.BLACK,
HGRColours.BLACK,
HGRColours.BLACK,
HGRColours.BLACK,
HGRColours.BLACK,
HGRColours.BLACK,
HGRColours.BLACK
),
colours.dots_to_nominal_colour_pixels(
31, 0b00000000000000000000111000000000, HGRColours, init_phase=0
)
)
self.assertEqual(
(
HGRColours.BLACK,
HGRColours.MAGENTA,
HGRColours.VIOLET,
HGRColours.LIGHT_BLUE,
HGRColours.WHITE,
HGRColours.AQUA,
HGRColours.GREEN,
HGRColours.BROWN,
HGRColours.BLACK,
HGRColours.MAGENTA,
HGRColours.VIOLET,
HGRColours.LIGHT_BLUE,
HGRColours.WHITE,
HGRColours.AQUA,
HGRColours.GREEN,
HGRColours.BROWN,
HGRColours.BLACK,
HGRColours.MAGENTA,
HGRColours.VIOLET,
HGRColours.LIGHT_BLUE,
HGRColours.WHITE,
HGRColours.AQUA,
HGRColours.GREEN,
HGRColours.BROWN,
HGRColours.BLACK,
HGRColours.BLACK,
HGRColours.BLACK,
HGRColours.BLACK,
HGRColours.BLACK,
HGRColours.BLACK,
HGRColours.BLACK
),
colours.dots_to_nominal_colour_pixels(
31, 0b0000111100001111000011110000, HGRColours, init_phase=0
)
)
class TestRolRoR(unittest.TestCase):
def testRolOne(self):
self.assertEqual(0b1111, colours.rol(0b1111, 1))
self.assertEqual(0b0001, colours.rol(0b1000, 1))
self.assertEqual(0b1010, colours.rol(0b0101, 1))
def testRolMany(self):
self.assertEqual(0b1111, colours.rol(0b1111, 3))
self.assertEqual(0b0010, colours.rol(0b1000, 2))
self.assertEqual(0b0101, colours.rol(0b0101, 2))
def testRorOne(self):
self.assertEqual(0b1111, colours.ror(0b1111, 1))
self.assertEqual(0b1000, colours.ror(0b0001, 1))
self.assertEqual(0b0101, colours.ror(0b1010, 1))
def testRoRMany(self):
self.assertEqual(0b1111, colours.ror(0b1111, 3))
self.assertEqual(0b1000, colours.ror(0b0010, 2))
self.assertEqual(0b0101, colours.ror(0b0101, 2))
if __name__ == "__main__":
unittest.main()

1
transcoder/data/.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
*.bz2 filter=lfs diff=lfs merge=lfs -text

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b47eadfdf8c8e16c6539f9a16ed0b5a393b17e0cbd03831aacda7f659e9522d6
size 120830327

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8c245981f91ffa89b47abdd1c9d646c2e79499a0c82c38c91234be0a59e52f1f
size 118832545

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3fd52feb08eb6f99b267a1050c68905f25d0d106ad7c2c63473cc0a0f6aa1b25
size 224334626

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:dbf83e3d0b6c7867ccf7ae1d55a6ed4e906409b08043dec514e1104cec95f0fc
size 220565577

View File

@ -54,7 +54,7 @@ class FileFrameGrabber(FrameGrabber):
return "P%d" % self.palette.value
def frames(self) -> Iterator[screen.MemoryMap]:
"""Encode frame to HGR using bmp2dhr.
"""Encode frame to (D)HGR using bmp2dhr.
We do the encoding in a background thread to parallelize.
"""

View File

@ -3,12 +3,11 @@
from typing import Iterator
# TODO: screen memory changes should happen via Machine while emitting opcodes?
class Machine:
"""Represents Apple II and player virtual machine state."""
def __init__(self):
self.page = 0x20 # type: int
def emit(self, opcode: "Opcode") -> Iterator[int]:
"""

View File

@ -1,4 +1,4 @@
"""Transcodes an input video file to ][Vision format."""
"""Transcodes an input video file to ][-Vision format."""
import argparse
@ -7,7 +7,7 @@ import palette
import video_mode
parser = argparse.ArgumentParser(
description='Transcode videos to ][Vision format.')
description='Transcode videos to ][-Vision format.')
parser.add_argument(
'input', help='Path to input video file.')
parser.add_argument(

View File

@ -1,6 +1,7 @@
import bz2
import functools
import pickle
import sys
from typing import Iterable, Type
import colormath.color_conversions
@ -8,66 +9,12 @@ import colormath.color_diff
import colormath.color_objects
import numpy as np
import weighted_levenshtein
from etaprogress.progress import ProgressBar
import colours
import palette
import screen
# The DHGR display encodes 7 pixels across interleaved 4-byte sequences
# of AUX and MAIN memory, as follows:
#
# PBBBAAAA PDDCCCCB PFEEEEDD PGGGGFFF
# Aux N Main N Aux N+1 Main N+1 (N even)
#
# Where A..G are the pixels, and P represents the (unused) palette bit.
#
# This layout makes more sense when written as a (little-endian) 32-bit integer:
#
# 33222222222211111111110000000000 <- bit pos in uint32
# 10987654321098765432109876543210
# PGGGGFFFPFEEEEDDPDDCCCCBPBBBAAAA
#
# i.e. apart from the palette bits this is a linear ordering of pixels,
# when read from LSB to MSB (i.e. right-to-left). i.e. the screen layout order
# of bits is opposite to the usual binary representation ordering.
#
# If we now look at the effect of storing a byte in each of the 4
# byte-offset positions within this uint32,
#
# PGGGGFFFPFEEEEDDPDDCCCCBPBBBAAAA
# 33333333222222221111111100000000
#
# We see that these byte offsets cause changes to the following pixels:
#
# 0: A B
# 1: B C D
# 2: D E F
# 3: F G
#
# i.e. DHGR byte stores to offsets 0 and 3 result in changing one 8-bit value
# (2 DHGR pixels) into another; offsets 1 and 3 result in changing one 12-bit
# value (3 DHGR pixels).
#
# We can simplify things by stripping out the palette bit and packing
# down to a 28-bit integer representation:
#
# 33222222222211111111110000000000 <- bit pos in uint32
# 10987654321098765432109876543210
#
# 0000GGGGFFFFEEEEDDDDCCCCBBBBAAAA <- pixel A..G
# 3210321032103210321032103210 <- bit pos in A..G pixel
#
# 3333333222222211111110000000 <- byte offset 0.3
#
# With this representation, we can precompute an edit distance for the
# pixel changes resulting from all possible DHGR byte stores.
#
# We further encode these (source, target) -> distance mappings by
# concatenating source and target into 16- or 24-bit values. This is
# efficient to work with in the video transcoder.
#
# Since we are enumerating all such 16- or 24-bit values, these can be packed
# contiguously into an array whose index is the (source, target) pair and
# the value is the edit distance.
PIXEL_CHARS = "0123456789ABCDEF"
@ -77,57 +24,13 @@ def pixel_char(i: int) -> str:
@functools.lru_cache(None)
def pixel_string(pixels: Iterable[colours.DHGRColours]) -> str:
return "".join(pixel_char(p.value) for p in pixels)
@functools.lru_cache(None)
def pixels_influenced_by_byte_index(
pixels: str,
idx: int) -> str:
"""Return subset of pixels that are influenced by given byte index (0..4)"""
start, end = {
0: (0, 1),
1: (1, 3),
2: (3, 5),
3: (5, 6)
}[idx]
return pixels[start:end + 1]
@functools.lru_cache(None)
def int28_to_pixels(int28):
return tuple(
palette.DHGRColours(
(int28 & (0b1111 << (4 * i))) >> (4 * i)) for i in range(7)
)
# TODO: these duplicates byte_mask32/byte_shift from DHGRBitmap
# Map n-bit int into 32-bit masked value
def map_int8_to_mask32_0(int8):
assert 0 <= int8 < 2 ** 8, int8
return int8
def map_int12_to_mask32_1(int12):
assert 0 <= int12 < 2 ** 12, int12
return int12 << 4
def map_int12_to_mask32_2(int12):
assert 0 <= int12 < 2 ** 12, int12
return int12 << 12
def map_int8_to_mask32_3(int8):
assert 0 <= int8 < 2 ** 8, int8
return int8 << 20
def pixel_string(pixels: Iterable[int]) -> str:
return "".join(pixel_char(p) for p in pixels)
class EditDistanceParams:
"""Data class for parameters to Damerau-Levenshtein edit distance."""
# Don't even consider insertions and deletions into the string, they don't
# make sense for comparing pixel strings
insert_costs = np.ones(128, dtype=np.float64) * 100000
@ -135,20 +38,26 @@ class EditDistanceParams:
# Smallest substitution value is ~20 from palette.diff_matrices, i.e.
# we always prefer to transpose 2 pixels rather than substituting colours.
transpose_costs = np.ones((128, 128), dtype=np.float64) * 10
# TODO: is quality really better allowing transposes?
transpose_costs = np.ones((128, 128), dtype=np.float64) * 100000 # 10
# These will be filled in later
substitute_costs = np.zeros((128, 128), dtype=np.float64)
# Substitution costs to use when evaluating other potential offsets at which
# to store a content byte. We penalize more harshly for introducing
# errors that alter pixel colours, since these tend to be very
# noticeable as visual noise.
#
# TODO: currently unused
error_substitute_costs = np.zeros((128, 128), dtype=np.float64)
def compute_diff_matrix(pal: Type[palette.BasePalette]):
# Compute matrix of CIE2000 delta values for this pal, representing
# perceptual distance between colours.
"""Compute matrix of perceptual distance between colour pairs.
Specifically CIE2000 delta values for this palette.
"""
dm = np.ndarray(shape=(16, 16), dtype=np.int)
for colour1, a in pal.RGB.items():
@ -162,7 +71,9 @@ def compute_diff_matrix(pal: Type[palette.BasePalette]):
return dm
def make_substitute_costs(pal: Type[palette.BasePalette]):
def compute_substitute_costs(pal: Type[palette.BasePalette]):
"""Compute costs for substituting one colour pixel for another."""
edp = EditDistanceParams()
diff_matrix = compute_diff_matrix(pal)
@ -171,20 +82,20 @@ def make_substitute_costs(pal: Type[palette.BasePalette]):
for i, c in enumerate(PIXEL_CHARS):
for j, d in enumerate(PIXEL_CHARS):
cost = diff_matrix[i, j]
edp.substitute_costs[(ord(c), ord(d))] = cost # / 20
edp.substitute_costs[(ord(d), ord(c))] = cost # / 20
edp.error_substitute_costs[(ord(c), ord(d))] = 5 * cost # / 4
edp.error_substitute_costs[(ord(d), ord(c))] = 5 * cost # / 4
edp.substitute_costs[(ord(c), ord(d))] = cost
edp.substitute_costs[(ord(d), ord(c))] = cost
edp.error_substitute_costs[(ord(c), ord(d))] = 5 * cost
edp.error_substitute_costs[(ord(d), ord(c))] = 5 * cost
return edp
@functools.lru_cache(None)
def edit_distance(
edp: EditDistanceParams,
a: str,
b: str,
error: bool) -> np.float64:
"""Damerau-Levenshtein edit distance between two pixel strings."""
res = weighted_levenshtein.dam_lev(
a, b,
insert_costs=edp.insert_costs,
@ -193,86 +104,100 @@ def edit_distance(
edp.error_substitute_costs if error else edp.substitute_costs),
)
assert res == 0 or (1 <= res < 2 ** 16), res
# Make sure result can fit in a uint16
assert (0 <= res < 2 ** 16), res
return res
def make_edit_distance(edp: EditDistanceParams):
edit = [
np.zeros(shape=(2 ** 16), dtype=np.int16),
np.zeros(shape=(2 ** 24), dtype=np.int16),
np.zeros(shape=(2 ** 24), dtype=np.int16),
np.zeros(shape=(2 ** 16), dtype=np.int16),
]
def compute_edit_distance(
edp: EditDistanceParams,
bitmap_cls: Type[screen.Bitmap],
nominal_colours: Type[colours.NominalColours]
):
"""Computes edit distance matrix between all pairs of pixel strings.
for i in range(2 ** 8):
print(i)
for j in range(2 ** 8):
pair = (i << 8) + j
Enumerates all possible values of the masked bit representation from
bitmap_cls (assuming it is contiguous, i.e. we enumerate all
2**bitmap_cls.MASKED_BITS values). These are mapped to the dot
representation, turned into coloured pixel strings, and we compute the
edit distance.
first = map_int8_to_mask32_0(i)
second = map_int8_to_mask32_0(j)
The effect of this is that we precompute the effect of storing all possible
byte values against all possible screen backgrounds (e.g. as
influencing/influenced by neighbouring bytes).
"""
first_pixels = pixels_influenced_by_byte_index(
pixel_string(int28_to_pixels(first)), 0)
second_pixels = pixels_influenced_by_byte_index(
pixel_string(int28_to_pixels(second)), 0)
bits = bitmap_cls.MASKED_BITS
edit[0][pair] = edit_distance(
edp, first_pixels, second_pixels, error=False)
bitrange = np.uint64(2 ** bits)
first = map_int8_to_mask32_3(i)
second = map_int8_to_mask32_3(j)
edit = []
for _ in range(len(bitmap_cls.BYTE_MASKS)):
edit.append(
np.zeros(shape=np.uint64(bitrange * bitrange), dtype=np.uint16))
first_pixels = pixels_influenced_by_byte_index(
pixel_string(int28_to_pixels(first)), 3)
second_pixels = pixels_influenced_by_byte_index(
pixel_string(int28_to_pixels(second)), 3)
# Matrix is symmetrical with zero diagonal so only need to compute upper
# triangle
bar = ProgressBar((bitrange * (bitrange - 1)) / 2, max_width=80)
edit[3][pair] = edit_distance(
edp, first_pixels, second_pixels, error=False)
num_dots = bitmap_cls.MASKED_DOTS
for i in range(2 ** 12):
print(i)
for j in range(2 ** 12):
pair = (i << 12) + j
cnt = 0
for i in range(np.uint64(bitrange)):
for j in range(i):
cnt += 1
first = map_int12_to_mask32_1(i)
second = map_int12_to_mask32_1(j)
if cnt % 10000 == 0:
bar.numerator = cnt
print(bar, end='\r')
sys.stdout.flush()
first_pixels = pixels_influenced_by_byte_index(
pixel_string(int28_to_pixels(first)), 1)
second_pixels = pixels_influenced_by_byte_index(
pixel_string(int28_to_pixels(second)), 1)
pair = (np.uint64(i) << bits) + np.uint64(j)
edit[1][pair] = edit_distance(
edp, first_pixels, second_pixels, error=False)
for o, ph in enumerate(bitmap_cls.PHASES):
first_dots = bitmap_cls.to_dots(i, byte_offset=o)
second_dots = bitmap_cls.to_dots(j, byte_offset=o)
first = map_int12_to_mask32_2(i)
second = map_int12_to_mask32_2(j)
first_pixels = pixels_influenced_by_byte_index(
pixel_string(int28_to_pixels(first)), 2)
second_pixels = pixels_influenced_by_byte_index(
pixel_string(int28_to_pixels(second)), 2)
edit[2][pair] = edit_distance(
edp, first_pixels, second_pixels, error=False)
first_pixels = pixel_string(
colours.dots_to_nominal_colour_pixel_values(
num_dots, first_dots, nominal_colours,
init_phase=ph)
)
second_pixels = pixel_string(
colours.dots_to_nominal_colour_pixel_values(
num_dots, second_dots, nominal_colours,
init_phase=ph)
)
edit[o][pair] = edit_distance(
edp, first_pixels, second_pixels, error=False)
return edit
def make_edit_distance(
pal: Type[palette.BasePalette],
edp: EditDistanceParams,
bitmap_cls: Type[screen.Bitmap],
nominal_colours: Type[colours.NominalColours]
):
"""Write file containing (D)HGR edit distance matrix for a palette."""
dist = compute_edit_distance(edp, bitmap_cls, nominal_colours)
data = "transcoder/data/%s_palette_%d_edit_distance.pickle.bz2" % (
bitmap_cls.NAME, pal.ID.value)
with bz2.open(data, "wb", compresslevel=9) as out:
pickle.dump(dist, out, protocol=pickle.HIGHEST_PROTOCOL)
def main():
for p in palette.PALETTES.values():
print("Processing palette %s" % p)
edp = make_substitute_costs(p)
edit = make_edit_distance(edp)
edp = compute_substitute_costs(p)