diff --git a/README.md b/README.md index 9a9a210..79e8562 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ Portable emulator of an Apple II+ or //e. Written in Go. - Apple //e 80 columns card with 64Kb extra RAM and optional RGB modes - No Slot Clock based on the DS1216 - Videx Videoterm 80 column card with the Videx Soft Video Switch (Apple ][+ only) + - Videx Ultraterm 80 to 160 column card wuth integrated Video Switch - SwyftCard (Apple //e only) - Brain Board - Brain Board II @@ -52,7 +53,9 @@ Portable emulator of an Apple II+ or //e. Written in Go. - Graphic modes: - Text 40 columns - - Text 80 columns (Apple //e and Videx VideoTerm) + - Text 80 columns Apple //e + - Text 80 columns Videx VideoTerm + - Text up to 160 columns and 48 lines Videx UltraTerm - Low-Resolution graphics - Double-Width Low-Resolution graphics (Apple //e only) - High-Resolution graphics @@ -237,6 +240,7 @@ The available pre-configured models are: desktop: Apple II DeskTop dos32: Apple ][ with 13 sectors disk adapter and DOS 3.2x swyft: swyft + ultraterm: Apple ][+ with Videx Ultraterm demo The available cards are: brainboard: Firmware card. It has two ROM banks @@ -259,7 +263,8 @@ The available cards are: softswitchlogger: Card to log softswitch accesses swyftcard: Card with the ROM needed to run the Swyftcard word processing system thunderclock: Clock card - videx: Videx compatible 80 columns card + videx: Videx Videoterm compatible 80 columns card + videxultraterm: Videx Utraterm compatible 80 columns card vidhd: Firmware signature of the VidHD card to trick Total Replay to use the SHR mode z80softcard: Microsoft Z80 SoftCard to run CP/M diff --git a/apple2.go b/apple2.go index 28c0f9f..9ab08f6 100644 --- a/apple2.go +++ b/apple2.go @@ -18,7 +18,7 @@ type Apple2 struct { cards [8]Card tracers []executionTracer - softVideoSwitch *SoftVideoSwitch + softVideoSwitch softVideoSwitch board string isApple2e bool hasLowerCase bool diff --git a/apple2Tester.go b/apple2Tester.go index eb1f631..3ae8202 100644 --- a/apple2Tester.go +++ b/apple2Tester.go @@ -69,9 +69,12 @@ func (at *apple2Tester) getText(textMode testTextModeFunc) string { func (at *apple2Tester) getTextBest() string { videxMaybe := at.a.cards[3] if videxMaybe != nil { - if videx, ok := videxMaybe.(*CardVidex); ok { + if videx, ok := videxMaybe.(*CardVidexVideoterm); ok { return videx.getText() } + if videxUltraterm, ok := videxMaybe.(*CardVidexUltraterm); ok { + return videxUltraterm.getText() + } } videoMode := at.a.video.GetCurrentVideoMode() diff --git a/cardBase.go b/cardBase.go index ff1ce0f..baab0ab 100644 --- a/cardBase.go +++ b/cardBase.go @@ -101,7 +101,7 @@ func (c *cardBase) loadRom(data []uint8, layout cardRomLayout) error { if len(data) == 0x400 { // The file has C800 to CBFF for ROM // The 256 bytes in Cx00 are copied from the last page in C800-CBFF - // Used on the Videx 80 columns card + // Used on the Videx Videoterm 80 columns card c.romCsxx = newMemoryRangeROM(0, data[0x300:], "Slot ROM") c.romC8xx = newMemoryRangeROM(0xc800, data, "Slot C8 ROM") } else { @@ -141,7 +141,7 @@ func (c *cardBase) assign(a *Apple2, slot int) { } if c.romCxxx != nil { rom := traceMemory(c.romCxxx, c.name, c.traceMemory) - a.mmu.setCardROM(slot, c.romCxxx) + a.mmu.setCardROM(slot, rom) a.mmu.setCardROMExtra(slot, rom) } } diff --git a/cardBuilder.go b/cardBuilder.go index 9584133..98b2f47 100644 --- a/cardBuilder.go +++ b/cardBuilder.go @@ -60,7 +60,8 @@ func getCardFactory() map[string]*cardBuilder { cardFactory["smartport"] = newCardSmartPortStorageBuilder() cardFactory["swyftcard"] = newCardSwyftBuilder() cardFactory["thunderclock"] = newCardThunderClockPlusBuilder() - cardFactory["videx"] = newCardVidexBuilder() + cardFactory["videx"] = newCardVidexVideotermBuilder() + cardFactory["videxultraterm"] = newCardVidexUltratermBuilder() cardFactory["vidhd"] = newCardVidHDBuilder() cardFactory["z80softcard"] = newCardZ80SoftCardBuilder() return cardFactory diff --git a/cardCat_test.go b/cardCat_test.go index 99e5858..94a68db 100644 --- a/cardCat_test.go +++ b/cardCat_test.go @@ -60,10 +60,14 @@ func TestCardsDetected(t *testing.T) { testCardDetectedInternal(t, "2plus", "saturn", "s0", 50_000_000, "SATURN 128K CARD IN SLOT 0") }) - t.Run("test Videx card", func(t *testing.T) { + t.Run("test Videx Videoterm card", func(t *testing.T) { testCardDetectedInternal(t, "2plus", "videx", "s3", 50_000_000, "3 38-18-01-82 Videx 80 Column Text Display Card") }) + t.Run("test Videx Ultraterm card", func(t *testing.T) { + testCardDetectedInternal(t, "2plus", "videxultraterm", "s3", 50_000_000, "3 38-18-01-87 ? Unknown 80-Column Display Card") + }) + t.Run("test Dan 2 SD card", func(t *testing.T) { testCardDetectedInternal(t, "2enh", "dan2sd", "s2", 50_000_000, "2 03-3C-01-9D DAN II Card") }) diff --git a/cardVidexUltraterm.go b/cardVidexUltraterm.go new file mode 100644 index 0000000..07f25ac --- /dev/null +++ b/cardVidexUltraterm.go @@ -0,0 +1,383 @@ +package izapple2 + +import ( + "errors" + "fmt" + "image" + "image/color" + "strings" + "time" + + "github.com/ivanizag/izapple2/component" +) + +/* + Videx Ultraterm 80 columns card for the Apple II+ + +See: + https://mirrors.apple2.org.za/Apple%20II%20Documentation%20Project/Interface%20Cards/80%20Column%20Cards/Videx%20UltraTerm/ + http://www.bitsavers.org/components/motorola/_dataSheets/6845.pdf + + +Firmware: + The firmware to operate your UltraTerm is contained in a 4K-byte 2732A +EPROM, U6. The lower half of this IC contains seven versions of the code +which appears at $CN00 to $CNFF, one for each slot. +There are 2K bytes of address space available for use in the co-resident +memory space at $C800. However, the upper 1 K bytes of this space is +used by the video refresh memory. For this reason the firmware is split into +two banks. These banks are selected with bit seven of the MCP When the +second bank of firmware is selected it overlays the Video Refresh Memory +(VRM) at addresses from $CC00 to $CFE0. The first bank of the firmware +always occupies the region from $C800 to $CBFF. + +Formats (from the firmware listing, appendix F of the manual): + 0: 80 x 24 non-interlaced (low density chars) (sram512, Apple Video source ??) + 1: 96 x 24 non-interlaced (low density chars) (sram256, 17 Mhz) + 2: 160 x 24 non-interlaced (low density chars) (sram256, 28 mhz) + 3: 80 x 24 interlaced (high density chars) (sram256, 17 Mhz) + 4: 80 x 32 interlaced (high density chars) (sram256, 17 Mhz) + 5: 80 x 48 interlaced (low density chars) (sram256, 17 Mhz) + 6: 160 x 24 interlaced (used for 132 x 24 screen) (high density chars) (sram256, 28 mhz) + 7: 128 X 32 interlaced (high density chars) (sram256, 28 mhz) +*/ + +// CardVidex represents a Videx Ultraterm compatible 80 column card +type CardVidexUltraterm struct { + cardBase + mc6845 component.MC6845 + modeControl uint8 + videoAttribute uint8 + cxrom memoryHandler + alwaysShow bool + sramPage512 uint8 // sram page on 512 kb mode (videoterm emultation) + + sram [0x1000]uint8 + charGen []uint8 +} + +func newCardVidexUltratermBuilder() *cardBuilder { + return &cardBuilder{ + name: "Videx Ultraterm 80 columns Card", + description: "Videx Utraterm compatible 80 columns card", + defaultParams: &[]paramSpec{ + {"rom", "ROM file to load", "/videx_ultraterm_frm_b537.bin"}, + {"charmap", "Character map file to load", "/videx_ultraterm_chs_7859.bin"}, + {"always", "Always show the 80 columns output", "false"}, + }, + buildFunc: func(params map[string]string) (Card, error) { + var c CardVidexUltraterm + + // The C800 area has ROM and RAM + err := c.loadRomFromResource(paramsGetPath(params, "rom"), cardRomFull) + if err != nil { + return nil, err + } + c.cxrom = c.romCxxx + c.romCxxx = &c + + err = c.loadCharacterMap(paramsGetPath(params, "charmap")) + if err != nil { + return nil, err + } + + c.alwaysShow = paramsGetBool(params, "always") + c.videoAttribute = videxUltratermDefaultVideoAttribute + return &c, nil + }, + } +} + +func (c *CardVidexUltraterm) loadCharacterMap(filename string) error { + bytes, _, err := LoadResource(filename) + if err != nil { + return err + } + size := len(bytes) + if size < 0x1000 { + return errors.New("character ROM size not supported for Videx") + } + c.charGen = bytes + return nil +} + +func (c *CardVidexUltraterm) assign(a *Apple2, slot int) { + + for page := uint8(0); page < 4; page++ { + bitsA3A2 := page << 2 + ssName := fmt.Sprintf("ULTRATERMPAGE%v", page) + + pageCopy := page + c.addCardSoftSwitchR(0+bitsA3A2, func() uint8 { + c.sramPage512 = pageCopy + return c.mc6845.Read(false) + }, ssName+"REGR") + c.addCardSoftSwitchW(0+bitsA3A2, func(value uint8) { + c.mc6845.Write(false, value) + }, ssName+"REGW") + + c.addCardSoftSwitchR(1+bitsA3A2, func() uint8 { + c.sramPage512 = pageCopy + return c.mc6845.Read(true) + }, ssName+"VALR") + c.addCardSoftSwitchW(1+bitsA3A2, func(value uint8) { + c.mc6845.Write(true, value) + }, ssName+"VALW") + + c.addCardSoftSwitchR(2+bitsA3A2, func() uint8 { + c.sramPage512 = pageCopy + return c.modeControl + }, ssName+"MODER") + c.addCardSoftSwitchW(2+bitsA3A2, func(value uint8) { + c.modeControl = value + }, ssName+"MODEW") + + c.addCardSoftSwitchR(3+bitsA3A2, func() uint8 { + c.sramPage512 = pageCopy + return c.videoAttribute + }, ssName+"ATTRE") + c.addCardSoftSwitchW(3+bitsA3A2, func(value uint8) { + c.videoAttribute = value + }, ssName+"ATTRW") + } + + c.cardBase.assign(a, slot) + a.setSoftVideoSwitch(c) +} + +/* +Bit: + + 7 Firmware Page Select + 6 Video Signal Select + 1 = UltraTerm + 5 Clock Frequency + 1 = 28.7595,0 = 17.430 MHz + 4 Character Address Format + 1 = 256-Byte Pages, 0 = 512-Byte Blocks + 3 Character RAM Address bit 11 (256-byte mode) + 2 Character RAM Address bit 10 (256-byte mode) + 1 Character RAM Address bit 9 (256-byte mode) + 0 Character RAM Address bit 8 (256-by1e mode) +*/ +const ( + videxUltratermMCPFirmwarePageSelect = uint8(0x80) + videxUltratermMCPVideoSignalSelect = uint8(0x40) + videxUltratermMCPClockFrequency = uint8(0x20) + videxUltratermMCPSRamAdressFormat = uint8(0x10) + videxUltratermMCPSramPageMask = uint8(0x0f) +) + +const ( + videxUltratermAttributesHighlight = uint8(1) // Lowlight or Highlight + videxUltratermAttributesInverse = uint8(2) // Normal or Inverse + videxUltratermAttributesAlternateChar = uint8(4) // Normal or Alternate (low quality) character sets +) + +const videxUltratermDefaultVideoAttribute = uint8(0x00 | videxUltratermAttributesInverse<<4) // LowlightNormal and LowlightInverse + +const videxUltratermSramStart = uint16(0xcc00) +const videxUltratermSramLegacyMask = uint16(0x01ff) +const videxUltratermSramMask = uint16(0x0ff) +const videxUltratermSram512Mask = uint16(0x7ff) +const videxUltratermSram256Mask = uint16(0xfff) + +func (c *CardVidexUltraterm) sramAddress(address uint16) uint16 { + is512mode := c.modeControl&videxUltratermMCPSRamAdressFormat == 0 + if is512mode { + // Legacy or 512 mode + return address&videxUltratermSramLegacyMask + uint16(c.sramPage512)*512 + } + + sramPage256 := c.modeControl & videxUltratermMCPSramPageMask + return address&videxUltratermSramMask + uint16(sramPage256)*256 +} + +func (c *CardVidexUltraterm) peek(address uint16) uint8 { + isFirmwarePageSelected := c.modeControl&videxUltratermMCPFirmwarePageSelect != 0 + if address < videxUltratermSramStart || isFirmwarePageSelected { + return c.cxrom.peek(address) + } + return c.sram[c.sramAddress(address)] +} + +func (c *CardVidexUltraterm) poke(address uint16, value uint8) { + if address >= videxUltratermSramStart { + c.sram[c.sramAddress(address)] = value + } +} + +func (c *CardVidexUltraterm) isSoftSwitchActive() bool { + if c.alwaysShow { + return true + } + return c.modeControl&videxUltratermMCPVideoSignalSelect != 0 +} + +const ( + videxUltratermCharWidth = uint8(9) +) + +func (c *CardVidexUltraterm) colorsPerAttributes(topBit bool, lightColor color.Color) (color.Color, color.Color) { + attributes := c.videoAttribute + if topBit { + attributes >>= 4 + } + + inverse := attributes&videxUltratermAttributesInverse != 0 + highlight := attributes&videxUltratermAttributesHighlight != 0 + + var clearColor color.Color = color.Black + setColor := lightColor + + if !highlight { + r, g, b, a := setColor.RGBA() + setColor = color.NRGBA64{ + uint16(r>>1 + r>>2), + uint16(g>>1 + g>>2), + uint16(b>>1 + b>>2), + uint16(a)} + } + + if inverse { + temp := setColor + setColor = clearColor + clearColor = temp + } + + return clearColor, setColor + +} + +func (c *CardVidexUltraterm) buildImage(light color.Color) *image.RGBA { + params := c.mc6845.ImageData() + width, height := params.DisplayedWidthHeight(videxUltratermCharWidth) + if (width == 0) || (height == 0) { + // No image available + size := image.Rect(0, 0, 3, 3) + img := image.NewRGBA(size) + img.Set(1, 1, color.White) + return img + } + ms := time.Now().Nanosecond() / (1000 * 1000) // Host time, used for the cursor blink + + size := image.Rect(0, 0, width, height) + img := image.NewRGBA(size) + + upperClearColor, upperSetColor := c.colorsPerAttributes(true, light) + lowerClearColor, lowerSetColor := c.colorsPerAttributes(false, light) + altChar := c.videoAttribute&videxUltratermAttributesAlternateChar != 0 + + sramMask := videxUltratermSram256Mask + is512mode := c.modeControl&videxUltratermMCPSRamAdressFormat == 0 + if is512mode { + sramMask = videxUltratermSram512Mask + } + + params.IterateScreen(func(address uint16, charLine uint8, + cursorMode uint8, displayEnable bool, + column uint8, y int) { + + if !displayEnable { + return + } + + bits := uint8(0) + colorOn := lowerSetColor + colorOff := lowerClearColor + char := c.sram[address&sramMask] + if char&0x80 != 0 { + colorOn = upperSetColor + colorOff = upperClearColor + } + + romIndex := (uint16(char&0x7f) << 4) + uint16(charLine) + if !altChar { + romIndex += 2048 + } + bits = c.charGen[romIndex] + + // Cursor + isCursor := false + switch cursorMode { + case component.MC6845CursorFixed: + isCursor = true + case component.MC6845CursorSlow: + // It should be 533ms (32/60, 32 screen refreshes) + // Let's make a 2 blinks per second + isCursor = ms/2 > 1000/4 + case component.MC6845CursorFast: + // It should be 266ms (32/60, 16 screen refreshes) + // Let's make a 4 blinks per second + isCursor = ms/4 > 1000/8 + } + if isCursor { + bits = ^bits + } + + x := int(column) * int(videxUltratermCharWidth) + color := colorOff + for i := 0; i < int(videxUltratermCharWidth-1); i++ { + pixel := (bits & 0x80) != 0 + if pixel { + color = colorOn + } else { + color = colorOff + } + img.Set(x, y, color) + bits <<= 1 + x++ + } + + // The ninth bit: blank or repetition of the last bit + if char&0x7f < 0x20 { + img.Set(x, y, color) + } else { + img.Set(x, y, colorOff) + } + }) + + return img +} + +func (c *CardVidexUltraterm) getText() string { + text := "" + params := c.mc6845.ImageData() + + sramMask := videxUltratermSram256Mask + is512mode := c.modeControl&videxUltratermMCPSRamAdressFormat == 0 + if is512mode { + sramMask = videxUltratermSram512Mask + } + + address := params.FirstChar + for line := uint8(0); line < params.Lines; line++ { + for column := uint8(0); column < params.Columns; column++ { + char := c.sram[address&sramMask] + text += string(char) + address++ + } + text = strings.TrimRight(text, " ") + text += "\n" + } + return text +} + +//lint:ignore U1000 Ignore function used for debugging +func (c *CardVidexUltraterm) dumpState() { + data := c.mc6845.ImageData() + width, height := data.DisplayedWidthHeight(videxUltratermCharWidth) + is512mode := c.modeControl&videxUltratermMCPSRamAdressFormat == 0 + sramPage256 := c.modeControl & videxUltratermMCPSramPageMask + mhz := "17.430" + if c.modeControl&videxUltratermMCPClockFrequency != 0 { + mhz = "28.7595" + } + + flags := c.a.mmu.Peek(0x7f8 + uint16(c.slot)) + + fmt.Printf("%vx%v %vx%v %vx%v 512:%v,%v 256page:%v %v MHz +-%v Flags: %v\n", + videxUltratermCharWidth, data.CharLines, + data.Columns, data.Lines, width, height, is512mode, c.sramPage512, sramPage256, mhz, + data.AdjustLines, flags) +} diff --git a/cardVidex.go b/cardVidexVideotern.go similarity index 80% rename from cardVidex.go rename to cardVidexVideotern.go index d328fc7..659c8ba 100644 --- a/cardVidex.go +++ b/cardVidexVideotern.go @@ -12,17 +12,17 @@ import ( ) /* - Videx 80 columns card for the Apple II+ + Videx Videoterm 80 columns card for the Apple II+ See: https://mirrors.apple2.org.za/Apple%20II%20Documentation%20Project/Interface%20Cards/80%20Column%20Cards/Videx%20Videoterm/Manuals/Videx%20Videoterm%20-%20Installation%20and%20Operation%20Manual.pdf - http://bitsavers.trailing-edge.com/components/motorola/_dataSheets/6845.pdf + http://www.bitsavers.org/components/motorola/_dataSheets/6845.pdf https://glasstty.com/?p=660 */ -// CardVidex represents a Videx compatible 80 column card -type CardVidex struct { +// CardVidexVideoterm represents a Videx compatible 80 column card +type CardVidexVideoterm struct { cardBase mc6845 component.MC6845 sramPage uint8 @@ -32,20 +32,20 @@ type CardVidex struct { alwaysShow bool } -func newCardVidexBuilder() *cardBuilder { +func newCardVidexVideotermBuilder() *cardBuilder { return &cardBuilder{ name: "Videx 80 columns Card", - description: "Videx compatible 80 columns card", + description: "Videx Videoterm compatible 80 columns card", defaultParams: &[]paramSpec{ {"rom", "ROM file to load", "/Videx Videoterm ROM 2.4.bin"}, {"charmap", "Character map file to load", "/80ColumnP110.BIN"}, {"always", "Always show the 80 columns output", "false"}, }, buildFunc: func(params map[string]string) (Card, error) { - var c CardVidex + var c CardVidexVideoterm // The C800 area has ROM and RAM - err := c.loadRomFromResource("/Videx Videoterm ROM 2.4.bin", cardRomUpperHalfEnd) + err := c.loadRomFromResource(paramsGetPath(params, "rom"), cardRomUpperHalfEnd) if err != nil { return nil, err } @@ -63,7 +63,7 @@ func newCardVidexBuilder() *cardBuilder { } } -func (c *CardVidex) loadCharacterMap(filename string) error { +func (c *CardVidexVideoterm) loadCharacterMap(filename string) error { bytes, _, err := LoadResource(filename) if err != nil { return err @@ -76,7 +76,7 @@ func (c *CardVidex) loadCharacterMap(filename string) error { return nil } -func (c *CardVidex) assign(a *Apple2, slot int) { +func (c *CardVidexVideoterm) assign(a *Apple2, slot int) { // TODO: use addCardSoftSwitches() for i := uint8(0x0); i <= 0xf; i++ { @@ -106,14 +106,14 @@ func (c *CardVidex) assign(a *Apple2, slot int) { } c.cardBase.assign(a, slot) - a.softVideoSwitch = NewSoftVideoSwitch(c, c.alwaysShow) + a.setSoftVideoSwitch(c) } const videxRomLimit = uint16(0xcc00) const videxSramLimit = uint16(0xce00) const videxSramMask = uint16(0x01ff) -func (c *CardVidex) peek(address uint16) uint8 { +func (c *CardVidexVideoterm) peek(address uint16) uint8 { if address < videxRomLimit { return c.upperROM.peek(address) } else if address < videxSramLimit { @@ -122,17 +122,27 @@ func (c *CardVidex) peek(address uint16) uint8 { return 0 } -func (c *CardVidex) poke(address uint16, value uint8) { +func (c *CardVidexVideoterm) poke(address uint16, value uint8) { if address >= videxRomLimit && address < videxSramLimit { c.sram[address&videxSramMask+uint16(c.sramPage)*0x200] = value } } +func (c *CardVidexVideoterm) isSoftSwitchActive() bool { + if c.alwaysShow { + return true + } + + isTextMode := c.a.io.isSoftSwitchActive(ioFlagText) + ann0 := c.a.io.isSoftSwitchActive(ioFlagAnnunciator0) + return isTextMode && ann0 +} + const ( videxCharWidth = uint8(8) ) -func (c *CardVidex) buildImage(light color.Color) *image.RGBA { +func (c *CardVidexVideoterm) buildImage(light color.Color) *image.RGBA { params := c.mc6845.ImageData() width, height := params.DisplayedWidthHeight(videxCharWidth) if (width == 0) || (height == 0) { @@ -194,7 +204,7 @@ func (c *CardVidex) buildImage(light color.Color) *image.RGBA { return img } -func (c *CardVidex) getText() string { +func (c *CardVidexVideoterm) getText() string { text := "" params := c.mc6845.ImageData() address := params.FirstChar diff --git a/component/mc6845.go b/component/mc6845.go index fdb9fa7..d04f27a 100644 --- a/component/mc6845.go +++ b/component/mc6845.go @@ -34,8 +34,13 @@ func (m *MC6845) Write(rs bool, value uint8) { m.sel = value & 0x1f } else if m.sel <= 15 { // R0 to R15 are writable - m.reg[m.sel] = value - // fmt.Printf("Set %v to %v\n", m.sel, value) + + if m.sel == 1 && value == 144 { + // Horrible hack for the mode 6. + m.reg[m.sel] = 160 + } else { + m.reg[m.sel] = value + } } } @@ -43,15 +48,17 @@ func (m *MC6845) ImageData() MC6845ImageData { var data MC6845ImageData data.FirstChar = uint16(m.reg[12]&0x3f)<<8 + uint16(m.reg[13]) - data.charLines = (m.reg[9] + 1) & 0x1f + data.CharLines = (m.reg[9] + 1) & 0x1f data.Columns = m.reg[1] data.Lines = m.reg[6] & 0x7f - data.adjustLines = m.reg[5] & 0x1f + data.AdjustLines = m.reg[5] & 0x1f data.cursorPos = uint16(m.reg[14]&0x3f)<<8 + uint16(m.reg[15]) data.cursorStart = m.reg[10] & 0x1f data.cursorEnd = m.reg[11] & 0x1f - data.cursorMode = (m.reg[10] >> 5) & 0x03 // Bit 6 and 5 + data.cursorMode = (m.reg[10] >> 5) & 0x03 // Bits 6 and 5 + + data.InterlaceMode = m.reg[8] & 0x03 // Bits 1 and 0 return data } @@ -64,21 +71,23 @@ const ( type MC6845ImageData struct { FirstChar uint16 // 14 bits, address of the firt char on the first line - charLines uint8 // 5 bits, lines par character + CharLines uint8 // 5 bits, lines par character Columns uint8 // 8 bits, chars per line Lines uint8 // 7 bits, char lines per screen - adjustLines uint8 // 5 bits, extra blank lines + AdjustLines uint8 // 5 bits, extra blank lines cursorPos uint16 // 14 bits, address? of the cursor position cursorStart uint8 // 5 bits, cursor starting char row cursorEnd uint8 // 5 bits, cursor ending char row cursorMode uint8 // 2 bits, cursor mode + InterlaceMode uint8 // 2 bit, interlace mode + } func (data *MC6845ImageData) DisplayedWidthHeight(charWidth uint8) (int, int) { return int(data.Columns) * int(charWidth), - int(data.Lines)*int(data.charLines) + int(data.adjustLines) + int(data.Lines)*int(data.CharLines) + int(data.AdjustLines) } type MC6845RasterCallBack func(address uint16, charLine uint8, // Lookup in char ROM @@ -90,7 +99,7 @@ func (data *MC6845ImageData) IterateScreen(callBack MC6845RasterCallBack) { y := 0 var address uint16 for line := uint8(0); line < data.Lines; line++ { - for charLine := uint8(0); charLine < data.charLines; charLine++ { + for charLine := uint8(0); charLine < data.CharLines; charLine++ { address = lineAddress // Back to the first char of the line for column := uint8(0); column < data.Columns; column++ { cursorMode := MC6845CursorNone @@ -108,7 +117,7 @@ func (data *MC6845ImageData) IterateScreen(callBack MC6845RasterCallBack) { } lineAddress = address } - for adjust := uint8(0); adjust <= data.adjustLines; adjust++ { + for adjust := uint8(0); adjust <= data.AdjustLines; adjust++ { for column := uint8(0); column < data.Columns; column++ { callBack(0, 0, MC6845CursorNone, false, column, y) // lines with display not enabled } diff --git a/configs/ultraterm.cfg b/configs/ultraterm.cfg new file mode 100644 index 0000000..07612da --- /dev/null +++ b/configs/ultraterm.cfg @@ -0,0 +1,9 @@ +name: Apple ][+ with Videx Ultraterm demo +parent: _base +board: 2plus +rom: /Apple2_Plus.rom +charrom: /Apple2rev7CharGen.rom +forceCaps: true +s0: language +s3: videxultraterm +s6: diskii,disk1=/Videx Ultraterm Utilities.dsk \ No newline at end of file diff --git a/doc/usage.txt b/doc/usage.txt index e480aaa..ad865a0 100644 --- a/doc/usage.txt +++ b/doc/usage.txt @@ -57,6 +57,7 @@ The available pre-configured models are: desktop: Apple II DeskTop dos32: Apple ][ with 13 sectors disk adapter and DOS 3.2x swyft: swyft + ultraterm: Apple ][+ with Videx Ultraterm demo The available cards are: brainboard: Firmware card. It has two ROM banks @@ -79,7 +80,8 @@ The available cards are: softswitchlogger: Card to log softswitch accesses swyftcard: Card with the ROM needed to run the Swyftcard word processing system thunderclock: Clock card - videx: Videx compatible 80 columns card + videx: Videx Videoterm compatible 80 columns card + videxultraterm: Videx Utraterm compatible 80 columns card vidhd: Firmware signature of the VidHD card to trick Total Replay to use the SHR mode z80softcard: Microsoft Z80 SoftCard to run CP/M diff --git a/resources/Videx Ultraterm Utilities.dsk b/resources/Videx Ultraterm Utilities.dsk new file mode 100644 index 0000000..152a205 Binary files /dev/null and b/resources/Videx Ultraterm Utilities.dsk differ diff --git a/resources/videx_ultraterm_chs_7859.bin b/resources/videx_ultraterm_chs_7859.bin new file mode 100644 index 0000000..915f18e Binary files /dev/null and b/resources/videx_ultraterm_chs_7859.bin differ diff --git a/resources/videx_ultraterm_frm_b537.bin b/resources/videx_ultraterm_frm_b537.bin new file mode 100644 index 0000000..0c60468 Binary files /dev/null and b/resources/videx_ultraterm_frm_b537.bin differ diff --git a/softVideoSwitch.go b/softVideoSwitch.go index 0a5204d..7997843 100644 --- a/softVideoSwitch.go +++ b/softVideoSwitch.go @@ -6,45 +6,25 @@ import ( ) /* - Videx Soft Video Switch + Videx Soft Video Switch external on the Videx Videoterm and integrated on the Videx Ultraterm See: https://archive.org/details/videx-soft-video-switch - */ -// SoftVideoSwitch represents a Videx soft video switch -type SoftVideoSwitch struct { - card *CardVidex - forced bool +type softVideoSwitch interface { + buildImage(light color.Color) *image.RGBA + isSoftSwitchActive() bool } -// NewSoftVideoSwitch creates a new SoftVideoSwitch -func NewSoftVideoSwitch(card *CardVidex, force bool) *SoftVideoSwitch { - var vs SoftVideoSwitch - vs.card = card - vs.forced = force - return &vs +func (a *Apple2) setSoftVideoSwitch(card softVideoSwitch) { + a.softVideoSwitch = card } -func (vs *SoftVideoSwitch) isActive() bool { - if vs == nil { +func (a *Apple2) isSoftVideoSwitchActive() bool { + if a.softVideoSwitch == nil { return false } - if vs.forced { - return true - } - - isTextMode := vs.card.a.io.isSoftSwitchActive(ioFlagText) - ann0 := vs.card.a.io.isSoftSwitchActive(ioFlagAnnunciator0) - return isTextMode && ann0 -} - -func (vs *SoftVideoSwitch) BuildAlternateImage(light color.Color) *image.RGBA { - return vs.card.buildImage(light) -} - -func (a *Apple2) SoftVideoSwitch() *SoftVideoSwitch { - return a.softVideoSwitch + return a.softVideoSwitch.isSoftSwitchActive() } diff --git a/video.go b/video.go index 392bcc9..a228ff0 100644 --- a/video.go +++ b/video.go @@ -36,7 +36,7 @@ func (v *video) GetCurrentVideoMode() uint32 { isStore80Active := v.a.mmu.store80Active isDoubleResMode := !isTextMode && is80Columns && !v.a.io.isSoftSwitchActive(ioFlagAnnunciator3) isSuperHighResMode := v.a.io.isSoftSwitchActive(ioDataNewVideo) - isVidex := v.a.softVideoSwitch.isActive() + isVidex := v.a.isSoftVideoSwitchActive() isRGBCard := v.a.io.isSoftSwitchActive(ioFlagRGBCardActive) rgbFlag1 := v.a.io.isSoftSwitchActive(ioFlag1RGBCard) @@ -162,7 +162,7 @@ func (v *video) GetCharacterPixel(char uint8, rowInChar int, colInChar int, isAl // GetCardImage returns an image provided by a card, like the videx card func (v *video) GetCardImage(light color.Color) *image.RGBA { - return v.a.softVideoSwitch.BuildAlternateImage(light) + return v.a.softVideoSwitch.buildImage(light) } // SupportsLowercase returns true if the video source supports lowercase