mirror of
https://github.com/ivanizag/izapple2.git
synced 2024-12-21 18:29:45 +00:00
Support RGB mode 14. Mix if mono with no NTSC artifacts and color
This commit is contained in:
parent
dad2a67321
commit
6bfa1d2986
27
README.md
27
README.md
@ -22,7 +22,7 @@ Portable emulator of an Apple II+ or //e. Written in Go.
|
||||
- 1Mb Memory Expansion Card
|
||||
- ThunderClock Plus real time clock
|
||||
- Bootable hard disk card
|
||||
- Apple //e 80 columns with 64Kb extra RAM
|
||||
- Apple //e 80 columns with 64Kb extra RAM and optional RGB modes
|
||||
- VidHd, limited to the ROM signature and SHR as used by Total Replay, only for //e models with 128Kb
|
||||
- FASTChip, limited to what Total Replay needs to set and clear fast mode
|
||||
- Graphic modes:
|
||||
@ -34,16 +34,19 @@ Portable emulator of an Apple II+ or //e. Written in Go.
|
||||
- Double-Width High-Resolution graphics (Apple //e only)
|
||||
- Super High Resolution (VidHD only)
|
||||
- Mixed mode
|
||||
- RGB card mode 11, mono 560x192
|
||||
- RGB card mode 13, ntsc 140*192 (regular DHGR)
|
||||
- RGB card mode 14, mix of modes 11 and 13 on the fly
|
||||
- Displays:
|
||||
- Green monochrome monitor with half width pixel support
|
||||
- NTSC Color TV (extracting the phase from the mono signal)
|
||||
- RGB for Super High Resolution
|
||||
- RGB for Super High Resolution and RGB card
|
||||
- ANSI Console, avoiding the SDL2 dependency
|
||||
- Other features:
|
||||
- Sound
|
||||
- Joystick support. Up to two joysticks or four paddles.
|
||||
- Adjustable speed.
|
||||
- Fast disk mode to set max speed while using the disks.
|
||||
- Joystick support. Up to two joysticks or four paddles
|
||||
- Adjustable speed
|
||||
- Fast disk mode to set max speed while using the disks
|
||||
- Single file executable with embedded ROMs and DOS 3.3
|
||||
- Pause (thanks a2geek)
|
||||
- ProDOS MLI calls tracing
|
||||
@ -142,11 +145,11 @@ Only valid on SDL mode
|
||||
-diskRom string
|
||||
rom file for the disk drive controller (default "<internal>/DISK2.rom")
|
||||
-diskb string
|
||||
file to load on the second disk drive
|
||||
file to load on the second disk drive
|
||||
-dumpChars
|
||||
shows the character map
|
||||
-fastChipSlot int
|
||||
slot for the FASTChip accelerator card, -1 for none (default 3)
|
||||
slot for the FASTChip accelerator card, -1 for none (default 3)
|
||||
-fastDisk
|
||||
set fast mode when the disks are spinning (default true)
|
||||
-hd string
|
||||
@ -156,7 +159,7 @@ Only valid on SDL mode
|
||||
-languageCardSlot int
|
||||
slot for the 16kb language card. -1 for none
|
||||
-memoryExpSlot int
|
||||
slot for the Memory Expansion card with 1GB. -1 for none (default 4)
|
||||
slot for the Memory Expansion card with 1GB. -1 for none (default 4)
|
||||
-mhz float
|
||||
cpu speed in Mhz, use 0 for full speed. Use F5 to toggle. (default 1.0227142857142857)
|
||||
-model string
|
||||
@ -167,6 +170,8 @@ Only valid on SDL mode
|
||||
panic if a not implemented softswitch is used
|
||||
-profile
|
||||
generate profile trace to analyse with pprof
|
||||
-rgb
|
||||
emulate the RGB modes of the 80col RGB card for DHGR (default true)
|
||||
-rom string
|
||||
main rom file (default "<default>")
|
||||
-saturnCardSlot int
|
||||
@ -178,13 +183,13 @@ Only valid on SDL mode
|
||||
-traceHD
|
||||
dump to the console the hd commands
|
||||
-traceMLI
|
||||
dump to the console the calls to ProDOS machine langunage interface calls to $BF00
|
||||
dump to the console the calls to ProDOS machine language interface calls to $BF00
|
||||
-traceSS
|
||||
dump to the console the sofswitches calls
|
||||
-vidHDSlot int
|
||||
slot for the VidHD card, only for //e models. -1 for none (default 2)
|
||||
slot for the VidHD card, only for //e models. -1 for none (default 2)
|
||||
-woz string
|
||||
show WOZ file information
|
||||
show WOZ file information
|
||||
|
||||
|
||||
```
|
||||
|
@ -189,6 +189,11 @@ func (a *Apple2) AddThunderClockPlusCard(slot int, romFile string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddRGBCard inserts an RBG option to the Apple IIe 80 col 64KB card
|
||||
func (a *Apple2) AddRGBCard() {
|
||||
setupRGBCard(a)
|
||||
}
|
||||
|
||||
// AddCardLogger inserts a fake card that logs accesses
|
||||
func (a *Apple2) AddCardLogger(slot int) {
|
||||
a.insertCard(&cardLogger{}, slot)
|
||||
|
@ -77,6 +77,10 @@ func MainApple() *Apple2 {
|
||||
"mono",
|
||||
false,
|
||||
"emulate a green phosphor monitor instead of a NTSC color TV. Use F6 to toggle.")
|
||||
rgbCard := flag.Bool(
|
||||
"rgb",
|
||||
true,
|
||||
"emulate the RGB modes of the 80col RGB card for DHGR")
|
||||
fastDisk := flag.Bool(
|
||||
"fastDisk",
|
||||
true,
|
||||
@ -112,7 +116,7 @@ func MainApple() *Apple2 {
|
||||
traceMLI := flag.Bool(
|
||||
"traceMLI",
|
||||
false,
|
||||
"dump to the console the calls to ProDOS machine langunage interface calls to $BF00")
|
||||
"dump to the console the calls to ProDOS machine language interface calls to $BF00")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
@ -249,6 +253,10 @@ func MainApple() *Apple2 {
|
||||
}
|
||||
}
|
||||
|
||||
if *rgbCard {
|
||||
a.AddRGBCard()
|
||||
}
|
||||
|
||||
//a.AddCardInOut(2)
|
||||
//a.AddCardLogger(4)
|
||||
|
||||
|
72
cardRGB.go
Normal file
72
cardRGB.go
Normal file
@ -0,0 +1,72 @@
|
||||
package apple2
|
||||
|
||||
/*
|
||||
Extended 80-Column Text AppleColor Card or Video7 RGB-SL7 card
|
||||
See:
|
||||
https://mirrors.apple2.org.za/Apple%20II%20Documentation%20Project/Interface%20Cards/Apple%20IIe/Apple%20IIe%20Extended%2080%20Column%20RGB%20Card/Manuals/Apple%20Ext80ColumnAppleColorCardHR%20Manual.pdf
|
||||
https://apple2online.com/web_documents/Video-7%20Manual%20KB.pdf
|
||||
https://mirrors.apple2.org.za/ftp.apple.asimov.net/documentation/hardware/video/DIGICARD%2064K%20Extended%2080%20Column%20RGB%20Card%20for%20Apple%20IIe%20Instruction%20Manual.pdf
|
||||
|
||||
It goes to the 80 column slot.
|
||||
|
||||
To set the state it AN3 in graphics mode has to go off-on-off-on. Each pair off-on record the state of 80col:
|
||||
on step 0, an ANN3OFF moves to step 1
|
||||
on step 1, an ANN3ON moves to step 2, and the value of 80COL is copied to RGB flag 1
|
||||
on step 2, an ANN3OFF moves to step 3
|
||||
on step 3, an ANN3ON moves to step 4, and the value of 80COL is copied to RGB flag 2
|
||||
|
||||
Modes by RGB flags 1 and 2:
|
||||
0-0: 560*192 mono
|
||||
1-1: 140*192 ntsc
|
||||
0-1: Mixed mode
|
||||
1-0: 160*192 ntsc (not supported)
|
||||
|
||||
*/
|
||||
|
||||
type cardRGB struct {
|
||||
// cardBase, not a regular card
|
||||
step uint8
|
||||
}
|
||||
|
||||
func setupRGBCard(a *Apple2) *cardRGB {
|
||||
var c cardRGB
|
||||
c.step = 0
|
||||
|
||||
// Does not have ROM or private softswitches. It spies on the softswitches
|
||||
a.io.addSoftSwitchRW(0x50, func(io *ioC0Page) uint8 {
|
||||
io.softSwitchesData[ioFlagText] = ssOff
|
||||
// Reset RGB modes when entering graphics mode
|
||||
c.step = 0
|
||||
io.softSwitchesData[ioFlag1RGBCard] = ssOn
|
||||
io.softSwitchesData[ioFlag2RGBCard] = ssOn
|
||||
return 0
|
||||
}, "TEXTOFF")
|
||||
|
||||
a.io.addSoftSwitchRW(0x5e, func(io *ioC0Page) uint8 {
|
||||
io.softSwitchesData[ioFlagAnnunciator3] = ssOff
|
||||
switch c.step {
|
||||
case 0:
|
||||
c.step++
|
||||
case 2:
|
||||
c.step++
|
||||
}
|
||||
|
||||
return 0
|
||||
}, "ANN3OFF-RGB")
|
||||
|
||||
a.io.addSoftSwitchRW(0x5f, func(io *ioC0Page) uint8 {
|
||||
io.softSwitchesData[ioFlagAnnunciator3] = ssOn
|
||||
switch c.step {
|
||||
case 1:
|
||||
io.softSwitchesData[ioFlag1RGBCard] = io.softSwitchesData[ioFlag80Col]
|
||||
c.step++
|
||||
case 3:
|
||||
io.softSwitchesData[ioFlag2RGBCard] = io.softSwitchesData[ioFlag80Col]
|
||||
c.step++
|
||||
}
|
||||
|
||||
return 0
|
||||
}, "ANN3ON-RGB")
|
||||
|
||||
return &c
|
||||
}
|
20
screen.go
20
screen.go
@ -30,6 +30,11 @@ func activeSnapshot(a *Apple2, raw bool) *image.RGBA {
|
||||
isSecondPage := a.io.isSoftSwitchActive(ioFlagSecondPage) && !a.mmu.store80Active
|
||||
isSuperHighResMode := a.io.isSoftSwitchActive(ioDataNewVideo)
|
||||
|
||||
rgbFlag1 := a.io.isSoftSwitchActive(ioFlag1RGBCard)
|
||||
rgbFlag2 := a.io.isSoftSwitchActive(ioFlag2RGBCard)
|
||||
isMono560 := isDoubleResMode && !rgbFlag1 && !rgbFlag2
|
||||
isRGBMixMode := isDoubleResMode && !rgbFlag1 && rgbFlag2
|
||||
|
||||
var lightColor color.Color
|
||||
if isColor {
|
||||
lightColor = color.White
|
||||
@ -41,6 +46,7 @@ func activeSnapshot(a *Apple2, raw bool) *image.RGBA {
|
||||
}
|
||||
|
||||
var snap *image.RGBA
|
||||
var ntscMask *image.Alpha
|
||||
if isSuperHighResMode { // Has to be first and disables the rest
|
||||
snap = snapshotSuperHiResMode(a)
|
||||
} else if isTextMode {
|
||||
@ -48,7 +54,7 @@ func activeSnapshot(a *Apple2, raw bool) *image.RGBA {
|
||||
} else {
|
||||
if isHiResMode {
|
||||
if isDoubleResMode {
|
||||
snap = snapshotDoubleHiResModeMono(a, isSecondPage, isMixMode, lightColor)
|
||||
snap, ntscMask = snapshotDoubleHiResModeMono(a, isSecondPage, isMixMode, isRGBMixMode, lightColor)
|
||||
} else {
|
||||
snap = snapshotHiResModeMono(a, isSecondPage, isMixMode, lightColor)
|
||||
}
|
||||
@ -60,8 +66,8 @@ func activeSnapshot(a *Apple2, raw bool) *image.RGBA {
|
||||
snapText := snapshotTextMode(a, is80Columns, false /*isSecondPage*/, true /*isMixMode*/, lightColor)
|
||||
snap = mixSnapshots(snap, snapText)
|
||||
}
|
||||
if isColor && !raw {
|
||||
snap = filterNTSCColor(false /*blacker*/, snap)
|
||||
if isColor && !(raw || isMono560) {
|
||||
snap = filterNTSCColor(snap, ntscMask)
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,15 +77,15 @@ func activeSnapshot(a *Apple2, raw bool) *image.RGBA {
|
||||
return snap
|
||||
}
|
||||
|
||||
// SnapshotAllModes to get all modes mixed
|
||||
// SnapshotHGRModes to get all modes mixed
|
||||
func SnapshotHGRModes(a *Apple2) *image.RGBA {
|
||||
bwSnap := activeSnapshot(a, true)
|
||||
if bwSnap.Bounds().Dx() == hiResWidth {
|
||||
bwSnap = doubleWidthFilter(bwSnap)
|
||||
}
|
||||
colorSnap := filterNTSCColor(false, bwSnap)
|
||||
page1Snap := filterNTSCColor(false /*blacker*/, snapshotHiResModeMono(a, false /*2nd page*/, false /*mix*/, color.White)) // HGR 1
|
||||
page2Snap := filterNTSCColor(false /*blacker*/, snapshotHiResModeMono(a, true /*2nd page*/, false /*mix*/, color.White)) // HGR 2
|
||||
colorSnap := filterNTSCColor(bwSnap, nil)
|
||||
page1Snap := filterNTSCColor(snapshotHiResModeMono(a, false /*2nd page*/, false /*mix*/, color.White), nil) // HGR 1
|
||||
page2Snap := filterNTSCColor(snapshotHiResModeMono(a, true /*2nd page*/, false /*mix*/, color.White), nil) // HGR 2
|
||||
|
||||
size := image.Rect(0, 0, hiResWidth*4, hiResHeight*2)
|
||||
out := image.NewRGBA(size)
|
||||
|
@ -9,15 +9,21 @@ const (
|
||||
doubleHiResWidth = 2 * hiResWidth
|
||||
)
|
||||
|
||||
func snapshotDoubleHiResModeMono(a *Apple2, isSecondPage bool, mixedMode bool, light color.Color) *image.RGBA {
|
||||
func snapshotDoubleHiResModeMono(a *Apple2, isSecondPage bool, mixedMode bool, getNTSCMask bool, light color.Color) (*image.RGBA, *image.Alpha) {
|
||||
// As described in "Inside the Apple IIe"
|
||||
|
||||
height := hiResHeight
|
||||
if mixedMode {
|
||||
height = hiResHeightMixed
|
||||
}
|
||||
|
||||
size := image.Rect(0, 0, doubleHiResWidth, height)
|
||||
|
||||
// To support RGB-mode14 we will have a mask to mark where we should not have the NTSC filter applied
|
||||
// See: https://apple2online.com/web_documents/Video-7%20Manual%20KB.pdf
|
||||
var ntscMask *image.Alpha
|
||||
if getNTSCMask {
|
||||
ntscMask = image.NewAlpha(size)
|
||||
}
|
||||
|
||||
img := image.NewRGBA(size)
|
||||
for y := 0; y < height; y++ {
|
||||
lineParts := [][]uint8{
|
||||
@ -27,21 +33,34 @@ func snapshotDoubleHiResModeMono(a *Apple2, isSecondPage bool, mixedMode bool, l
|
||||
x := 0
|
||||
// For the NTSC filter to work we have to insert an initial black pixel and skip the last one
|
||||
img.Set(x, y, color.Black)
|
||||
if getNTSCMask {
|
||||
ntscMask.Set(x, y, color.Opaque)
|
||||
}
|
||||
x++
|
||||
for iByte := 0; iByte < hiResLineBytes; iByte++ {
|
||||
for iPart := 0; iPart < 2; iPart++ {
|
||||
b := lineParts[iPart][iByte]
|
||||
mask := color.Transparent // Apply the NTSC filter
|
||||
if getNTSCMask && b&0x80 == 0 {
|
||||
mask = color.Opaque // Do not apply the NTSC filter
|
||||
}
|
||||
for j := uint(0); j < 7; j++ {
|
||||
// Set color
|
||||
bit := (b >> j) & 1
|
||||
colour := light
|
||||
if bit == 0 {
|
||||
colour = color.Black
|
||||
}
|
||||
img.Set(x, y, colour)
|
||||
|
||||
// Set mask if requested
|
||||
if getNTSCMask {
|
||||
ntscMask.Set(x, y, mask)
|
||||
}
|
||||
x++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return img
|
||||
return img, ntscMask
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ func getNTSCColorMap() []color.Color {
|
||||
return colorMap
|
||||
}
|
||||
|
||||
func filterNTSCColor(blacker bool, in *image.RGBA) *image.RGBA {
|
||||
func filterNTSCColor(in *image.RGBA, mask *image.Alpha) *image.RGBA {
|
||||
colorMap := getNTSCColorMap()
|
||||
|
||||
b := in.Bounds()
|
||||
@ -69,19 +69,21 @@ func filterNTSCColor(blacker bool, in *image.RGBA) *image.RGBA {
|
||||
r, _, _, _ := cIn.RGBA()
|
||||
|
||||
pos := 1 << (3 - uint(x%4))
|
||||
var cOut color.Color
|
||||
if r != 0 {
|
||||
v |= pos
|
||||
cOut = colorMap[v]
|
||||
} else {
|
||||
v &^= pos
|
||||
if blacker {
|
||||
// If there is no luminance, let's have black anyway
|
||||
cOut = colorMap[0]
|
||||
} else {
|
||||
cOut = colorMap[v]
|
||||
}
|
||||
|
||||
cOut := colorMap[v]
|
||||
if mask != nil {
|
||||
// RGM mode7
|
||||
_, _, _, a := mask.At(x, y).RGBA()
|
||||
if a > 0 {
|
||||
cOut = cIn
|
||||
}
|
||||
}
|
||||
|
||||
out.Set(x, y, cOut)
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ const (
|
||||
ioFlagMixed uint8 = 0x52
|
||||
ioFlagSecondPage uint8 = 0x54
|
||||
ioFlagHiRes uint8 = 0x56
|
||||
ioFlagAnnunciator0 uint8 = 0x58 // On Copam Electronics Base-64A this is used to bank swith the ROM
|
||||
ioFlagAnnunciator0 uint8 = 0x58
|
||||
ioFlagAnnunciator1 uint8 = 0x5a
|
||||
ioFlagAnnunciator2 uint8 = 0x5c
|
||||
ioFlagAnnunciator3 uint8 = 0x5e
|
||||
@ -20,6 +20,9 @@ const (
|
||||
ioDataPaddle1 uint8 = 0x65
|
||||
ioDataPaddle2 uint8 = 0x66
|
||||
ioDataPaddle3 uint8 = 0x67
|
||||
|
||||
ioFlag1RGBCard uint8 = 0x7e // Not real softSwitches. Using the numbers to store the flags somewhere.
|
||||
ioFlag2RGBCard uint8 = 0x7f
|
||||
)
|
||||
|
||||
func addApple2SoftSwitches(io *ioC0Page) {
|
||||
@ -71,6 +74,10 @@ func addApple2SoftSwitches(io *ioC0Page) {
|
||||
io.addSoftSwitchR(0x6F, getPaddleSoftSwitch(3), "PDL3")
|
||||
|
||||
io.addSoftSwitchR(0x70, strobePaddlesSoftSwitch, "RESETPDL") // Game controllers reset
|
||||
|
||||
// For RGB screen modes. Default to NTSC artifacts
|
||||
io.softSwitchesData[ioFlag1RGBCard] = ssOn
|
||||
io.softSwitchesData[ioFlag2RGBCard] = ssOn
|
||||
}
|
||||
|
||||
func notImplementedSoftSwitchR(*ioC0Page) uint8 {
|
||||
@ -141,9 +148,9 @@ func getButtonSoftSwitch(i int) softSwitchR {
|
||||
|
||||
/*
|
||||
Paddle values are calculated by the time taken by a current going
|
||||
througt the paddle variable resistor to charge a capacitor.
|
||||
through the paddle variable resistor to charge a capacitor.
|
||||
The capacitor is discharged via the strobe softswitch. The result is
|
||||
hoy many times a 11 cycles loop runs before the capacitor reaches
|
||||
how many times a 11 cycles loop runs before the capacitor reaches
|
||||
the voltage threshold.
|
||||
|
||||
See: http://www.1000bit.it/support/manuali/apple/technotes/aiie/tn.aiie.06.html
|
||||
|
Loading…
Reference in New Issue
Block a user