mirror of
https://github.com/freewilll/apple2-go.git
synced 2024-06-26 08:29:27 +00:00
Every frame sends a bunch of audio samples in a channel. This channel is consumed by the audio Read() function which is called asynchronously. There's all kinds of timing issues where the audio/video are not aligned. Issues: - There's a large delay between the audio being produced and it being played - Something with the timing is wrong. The first not of lemonade stand and the system beep are both incorrect. Changing the CPU frequency fixes it for one but not for the other. This means something must be wrong in the cycle counting. Also added FPS display that can be toggled with ctrl-alt-F.
304 lines
6.9 KiB
Go
304 lines
6.9 KiB
Go
package mmu
|
|
|
|
import (
|
|
"fmt"
|
|
"mos6502go/audio"
|
|
"mos6502go/keyboard"
|
|
)
|
|
|
|
// Adapted from
|
|
// https://mirrors.apple2.org.za/apple.cabi.net/Languages.Programming/MemoryMap.IIe.64K.128K.txt
|
|
// https://github.com/cmosher01/Apple-II-Platform/blob/master/asminclude/iorom.asm
|
|
|
|
const (
|
|
KEYBOARD = 0xC000 // keyboard data (latched) (RD-only)
|
|
CLR80COL = 0xC000 // use 80-column memory mapping (WR-only)
|
|
SET80COL = 0xC001
|
|
CLRAUXRD = 0xC002 // read from auxilliary 48K
|
|
SETAUXRD = 0xC003
|
|
CLRAUXWR = 0xC004 // write to auxilliary 48K
|
|
SETAUXWR = 0xC005
|
|
CLRCXROM = 0xC006 // use external slot ROM
|
|
SETCXROM = 0xC007
|
|
CLRAUXZP = 0xC008 // use auxilliary ZP, stack, & LC
|
|
SETAUXZP = 0xC009
|
|
CLRC3ROM = 0xC00A // use external slot C3 ROM
|
|
SETC3ROM = 0xC00B
|
|
CLR80VID = 0xC00C // use 80-column display mode
|
|
SET80VID = 0xC00D
|
|
CLRALTCH = 0xC00E // use alternate character set ROM
|
|
SETALTCH = 0xC00F
|
|
STROBE = 0xC010 // strobe (unlatch) keyboard data
|
|
|
|
RDLCBNK2 = 0xC011 // reading from LC bank $Dx 2
|
|
RDLCRAM = 0xC012 // reading from LC RAM
|
|
RDRAMRD = 0xC013 // reading from auxilliary 48K
|
|
RDRAMWR = 0xC014 // writing to auxilliary 48K
|
|
RDCXROM = 0xC015 // using external slot ROM
|
|
RDAUXZP = 0xC016 // using auxilliary ZP, stack, & LC
|
|
RDC3ROM = 0xC017 // using external slot C3 ROM
|
|
RD80COL = 0xC018 // using 80-column memory mapping
|
|
RDVBLBAR = 0xC019 // not VBL (VBL signal low)
|
|
RDTEXT = 0xC01A // using text mode
|
|
RDMIXED = 0xC01B // using mixed mode
|
|
RDPAGE2 = 0xC01C // using text/graphics page2
|
|
RDHIRES = 0xC01D // using Hi-res graphics mode
|
|
RDALTCH = 0xC01E // using alternate character set ROM
|
|
RD80VID = 0xC01F // using 80-column display mode
|
|
SPEAKER = 0xC030 // toggle speaker diaphragm
|
|
|
|
CLRTEXT = 0xC050 // enable text-only mode
|
|
SETTEXT = 0xC051
|
|
CLRMIXED = 0xC052 // enable graphics/text mixed mode
|
|
SETMIXED = 0xC053
|
|
TXTPAGE1 = 0xC054 // select page1/2 (or page1/1x)
|
|
TXTPAGE2 = 0xC055
|
|
CLRHIRES = 0xC056 // enable Hi-res graphics
|
|
SETHIRES = 0xC057
|
|
|
|
SETAN0 = 0xC058 // 4-bit annunciator inputs
|
|
CLRAN0 = 0xC059
|
|
SETAN1 = 0xC05A
|
|
CLRAN1 = 0xC05B
|
|
SETAN2 = 0xC05C
|
|
CLRAN2 = 0xC05D
|
|
SETAN3 = 0xC05E
|
|
CLRAN3 = 0xC05F
|
|
|
|
OPNAPPLE = 0xC061 // open apple (command) key data
|
|
CLSAPPLE = 0xC062 // closed apple (option) key data
|
|
|
|
PDLTRIG = 0xC070 // trigger paddles
|
|
|
|
// Slot 6 Drive IO
|
|
S6CLRDRVP0 = 0xC0E0 // stepper phase 0 (Q0)
|
|
S6SETDRVP0 = 0xC0E1 //
|
|
S6CLRDRVP1 = 0xC0E2 // stepper phase 1 (Q1)
|
|
S6SETDRVP1 = 0xC0E3 //
|
|
S6CLRDRVP2 = 0xC0E4 // stepper phase 2 (Q2)
|
|
S6SETDRVP2 = 0xC0E5 //
|
|
S6CLRDRVP3 = 0xC0E6 // stepper phase 3 (Q3)
|
|
S6SETDRVP3 = 0xC0E7 //
|
|
S6MOTOROFF = 0xC0E8 // drive motor (Q4)
|
|
S6MOTORON = 0xC0E9 //
|
|
S6SELDRV1 = 0xC0EA // drive select (Q5)
|
|
S6SELDRV2 = 0xC0EB //
|
|
S6Q6L = 0xC0EC // read (Q6)
|
|
S6Q6H = 0xC0ED // WP sense
|
|
S6Q7L = 0xC0EE // WP sense/read (Q7)
|
|
S6Q7H = 0xC0EF // write
|
|
)
|
|
|
|
var DriveState struct {
|
|
Drive uint8
|
|
Spinning bool
|
|
Phase uint8
|
|
ArmPosition uint8
|
|
BytePosition int
|
|
Q6 bool
|
|
Q7 bool
|
|
}
|
|
|
|
var VideoState struct {
|
|
TextMode bool
|
|
Mixed bool
|
|
}
|
|
|
|
func InitIO() {
|
|
// Empty slots that aren't yet implemented
|
|
emptySlot(3)
|
|
emptySlot(4)
|
|
emptySlot(7)
|
|
|
|
// Initialize slot 6 drive
|
|
DriveState.Drive = 1
|
|
DriveState.Spinning = false
|
|
DriveState.Phase = 0
|
|
DriveState.ArmPosition = 0
|
|
DriveState.BytePosition = 0
|
|
DriveState.Q6 = false
|
|
DriveState.Q7 = false
|
|
|
|
VideoState.TextMode = true
|
|
VideoState.Mixed = false
|
|
|
|
InitDiskImage()
|
|
}
|
|
|
|
func driveIsreadSequencing() bool {
|
|
return (!DriveState.Q6) && (!DriveState.Q7)
|
|
}
|
|
|
|
// Handle soft switch addresses where both a read and a write has a side
|
|
// effect and the return value is meaningless
|
|
func readWrite(address uint16, isRead bool) bool {
|
|
switch address {
|
|
case CLR80VID:
|
|
// 80 column card hasn't been implemented yet
|
|
return true
|
|
case CLRTEXT:
|
|
VideoState.TextMode = false
|
|
return true
|
|
case SETTEXT:
|
|
VideoState.TextMode = true
|
|
return true
|
|
case CLRMIXED:
|
|
VideoState.Mixed = false
|
|
return true
|
|
case SETMIXED:
|
|
VideoState.Mixed = true
|
|
return true
|
|
case TXTPAGE1:
|
|
return true
|
|
case TXTPAGE2:
|
|
return true
|
|
fmt.Println("TXTPAGE2 not implemented")
|
|
return true
|
|
case CLRHIRES:
|
|
return true
|
|
case SETHIRES:
|
|
panic("SETIRES not implemented")
|
|
|
|
// Drive stepper motor phase change
|
|
case S6CLRDRVP0, S6SETDRVP0, S6CLRDRVP1, S6SETDRVP1, S6CLRDRVP2, S6SETDRVP2, S6CLRDRVP3, S6SETDRVP3:
|
|
if ((address - S6CLRDRVP0) % 2) == 1 {
|
|
// When the magnet coil is energized, move the arm by half a track
|
|
phase := int8(address-S6CLRDRVP0) / 2
|
|
change := int8(DriveState.Phase) - phase
|
|
if change < 0 {
|
|
change += 4
|
|
}
|
|
|
|
if change == 1 { // Inward
|
|
if DriveState.ArmPosition > 0 {
|
|
DriveState.ArmPosition--
|
|
}
|
|
} else if change == 3 { // Outward
|
|
if DriveState.ArmPosition < 79 {
|
|
DriveState.ArmPosition++
|
|
}
|
|
}
|
|
|
|
DriveState.Phase = uint8(phase)
|
|
MakeTrackData(DriveState.ArmPosition)
|
|
}
|
|
|
|
return true
|
|
|
|
case S6MOTOROFF:
|
|
DriveState.Spinning = false
|
|
return true
|
|
case S6MOTORON:
|
|
DriveState.Spinning = true
|
|
return true
|
|
case S6SELDRV1:
|
|
DriveState.Drive = 1
|
|
return true
|
|
case S6SELDRV2:
|
|
DriveState.Drive = 2
|
|
return true
|
|
case S6Q6L:
|
|
if !isRead {
|
|
DriveState.Q6 = false
|
|
return true
|
|
}
|
|
return false
|
|
case S6Q6H:
|
|
DriveState.Q6 = true
|
|
return true
|
|
case S6Q7L:
|
|
DriveState.Q7 = false
|
|
return true
|
|
case S6Q7H:
|
|
DriveState.Q7 = true
|
|
return true
|
|
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func ReadIO(address uint16) uint8 {
|
|
if readWrite(address, true) {
|
|
return 0
|
|
}
|
|
|
|
switch address {
|
|
case KEYBOARD, STROBE:
|
|
keyBoardData, strobe := keyboard.Read()
|
|
if address == KEYBOARD {
|
|
return keyBoardData
|
|
} else {
|
|
keyboard.ResetStrobe()
|
|
return strobe
|
|
}
|
|
case RDCXROM:
|
|
if UsingExternalSlotRom {
|
|
return 0x8d
|
|
} else {
|
|
return 0x0d
|
|
}
|
|
case RD80VID:
|
|
// using 80-column display mode not implemented
|
|
return 0x0d
|
|
|
|
// 4-bit annunciator inputs
|
|
case SETAN0, CLRAN0, SETAN1, CLRAN1, SETAN2, CLRAN2, SETAN3, CLRAN3:
|
|
// Annunciators not implemented
|
|
case OPNAPPLE:
|
|
// Open apple key not implemented
|
|
return 0
|
|
case CLSAPPLE:
|
|
// Closed apple key not implemented
|
|
case RD80COL:
|
|
// RD80COL not implemented
|
|
return 0x0d
|
|
case RDPAGE2:
|
|
// RDPAGE2 not implemented
|
|
return 0x0d
|
|
case RDALTCH:
|
|
// RDALTCH not implemented
|
|
return 0x0d
|
|
case SPEAKER:
|
|
audio.Click()
|
|
case S6Q6L:
|
|
return ReadTrackData()
|
|
default:
|
|
panic(fmt.Sprintf("TODO read %04x\n", address))
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
func WriteIO(address uint16, value uint8) {
|
|
if readWrite(address, false) {
|
|
return
|
|
}
|
|
|
|
switch address {
|
|
case STROBE:
|
|
keyboard.ResetStrobe()
|
|
case CLRCXROM:
|
|
MapFirstHalfOfIO()
|
|
case SETCXROM:
|
|
MapSecondHalfOfIO()
|
|
case CLRALTCH:
|
|
return
|
|
case SETALTCH:
|
|
panic("SETALTCH not implemented")
|
|
case CLR80COL:
|
|
// CLR80COL not implemented
|
|
return
|
|
case SET80COL:
|
|
// SET80COL not implemented
|
|
case CLRC3ROM:
|
|
// CLRC3ROM not implemented
|
|
case SETC3ROM:
|
|
// SETC3ROM not implemented
|
|
default:
|
|
panic(fmt.Sprintf("TODO write %04x\n", address))
|
|
}
|
|
|
|
return
|
|
}
|