diff --git a/README.md b/README.md index d07a17a..30e255b 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Portable emulator of an Apple II+. Written in Go. - Fast disk mode to set max speed while using the disks. - Single file executable with embedded ROMs and DOS 3.3 - Optional emulation of the clone Base64A by Copam +- Joystick support. Up to two joysticks or four paddes. ## Running the emulator diff --git a/apple2Setup.go b/apple2Setup.go index c87d192..5b255d2 100644 --- a/apple2Setup.go +++ b/apple2Setup.go @@ -107,3 +107,8 @@ func (a *Apple2) SetKeyboardProvider(kb KeyboardProvider) { func (a *Apple2) SetSpeakerProvider(s SpeakerProvider) { a.io.setSpeakerProvider(s) } + +// SetJoysticksProvider attaches an external joysticks provider +func (a *Apple2) SetJoysticksProvider(j JoysticksProvider) { + a.io.setJoysticksProvider(j) +} diff --git a/apple2sdl/main.go b/apple2sdl/main.go index 5b69091..44d74ac 100644 --- a/apple2sdl/main.go +++ b/apple2sdl/main.go @@ -14,8 +14,6 @@ func main() { // SDLRun starts the Apple2 emulator on SDL func SDLRun(a *apple2.Apple2) { - s := newSDLSpeaker() - s.start() window, renderer, err := sdl.CreateWindowAndRenderer(4*40*7, 4*24*8, sdl.WINDOW_SHOWN) @@ -30,7 +28,14 @@ func SDLRun(a *apple2.Apple2) { kp := newSDLKeyBoard(a) a.SetKeyboardProvider(kp) + + s := newSDLSpeaker() + s.start() a.SetSpeakerProvider(s) + + j := newSDLJoysticks() + a.SetJoysticksProvider(j) + go a.Run(false) running := true @@ -47,6 +52,12 @@ func SDLRun(a *apple2.Apple2) { //fmt.Printf("[%d ms] TextInput\ttype:%d\texts:%s\n", // t.Timestamp, t.Type, t.GetText()) kp.putText(t) + case *sdl.JoyAxisEvent: + //fmt.Printf("AxisEvent: %v, %v, %v\n", t.Axis, t.Value, t.Which) + j.putAxisEvent(t) + case *sdl.JoyButtonEvent: + //fmt.Printf("ButtonEvent: %v, %v, %v\n", t.Button, t.State, t.Which) + j.putButtonEvent(t) } } diff --git a/apple2sdl/sdlJoysticks.go b/apple2sdl/sdlJoysticks.go new file mode 100644 index 0000000..a95521f --- /dev/null +++ b/apple2sdl/sdlJoysticks.go @@ -0,0 +1,80 @@ +package main + +import ( + "github.com/veandco/go-sdl2/sdl" +) + +/* + Apple 2 supports four paddles and 3 pushbuttons. The first two paddles are +the X, Y axis of the first joystick. The second two correspond the the second +joystick. + Button 0 is the primary button of joystick 0. + Button 1 is the secondary button of joystick 0 but also the primary button of +joystick 1. + Button 2 is the secondary button of Joystick 1. +*/ + +type sdlJoysticks struct { + paddle [4]uint8 + button [4]bool +} + +func newSDLJoysticks() *sdlJoysticks { + var j sdlJoysticks + + err := sdl.InitSubSystem(sdl.INIT_JOYSTICK) + if err != nil { + panic(err) + } + + // Init up to two joysticks + sdl.JoystickEventState(sdl.ENABLE) + joyCount := sdl.NumJoysticks() + for iJoy := 0; iJoy < joyCount && iJoy < 2; iJoy++ { + /*joystick := */ sdl.JoystickOpen(iJoy) + } + + // Initialize to maximum resistance if unpugged + j.paddle[0] = 255 + j.paddle[1] = 255 + j.paddle[2] = 255 + j.paddle[3] = 255 + + return &j +} + +func (j *sdlJoysticks) putAxisEvent(e *sdl.JoyAxisEvent) { + if e.Which >= 2 || e.Axis >= 2 { + // Process only the first two axis of the first two joysticks + return + } + + j.paddle[uint8(e.Which)*2+e.Axis] = uint8((e.Value >> 8) + 128) +} + +func (j *sdlJoysticks) putButtonEvent(e *sdl.JoyButtonEvent) { + if e.Which >= 2 || e.Button >= 2 { + // Process only the first two buttons of the first two joysticks + return + } + + j.button[uint8(e.Which)*2+e.Button] = (e.State != 0) +} + +func (j *sdlJoysticks) ReadButton(i int) bool { + switch i { + case 0: + return j.button[0] + case 1: + // It can be secondary of first or primary of second + return j.button[1] || j.button[2] + case 2: + return j.button[3] + default: + return false + } +} + +func (j *sdlJoysticks) ReadPaddle(i int) uint8 { + return j.paddle[i] +} diff --git a/base64a.go b/base64a.go index eab80eb..33400d6 100644 --- a/base64a.go +++ b/base64a.go @@ -98,7 +98,7 @@ func (b *Base64a) loadRom() { // Write on the speaker. That is a double access and should do nothing // but works somehow on the BASE64A b.a.io.addSoftSwitchW(0x30, func(io *ioC0Page, value uint8) { - getSpeakerSoftSwitch(io) + speakerSoftSwitch(io) }) } diff --git a/ioC0Page.go b/ioC0Page.go index 1c5b765..3c097f2 100644 --- a/ioC0Page.go +++ b/ioC0Page.go @@ -7,28 +7,36 @@ import ( ) type ioC0Page struct { - softSwitchesR [256]softSwitchR - softSwitchesW [256]softSwitchW - softSwitchesData [128]uint8 - keyboard KeyboardProvider - speaker SpeakerProvider - apple2 *Apple2 + softSwitchesR [256]softSwitchR + softSwitchesW [256]softSwitchW + softSwitchesData [128]uint8 + keyboard KeyboardProvider + speaker SpeakerProvider + paddlesStrobeCycle uint64 + joysticks JoysticksProvider + apple2 *Apple2 } type softSwitchR func(io *ioC0Page) uint8 type softSwitchW func(io *ioC0Page, value uint8) -// KeyboardProvider declares the keyboard implementation requirements +// KeyboardProvider provides a keyboard implementation type KeyboardProvider interface { GetKey(strobe bool) (key uint8, ok bool) } -// SpeakerProvider declares the speaker implementation requirements +// SpeakerProvider provides a speaker implementation type SpeakerProvider interface { // Click receives a speaker click. The argument is the CPU cycle when it is generated Click(cycle uint64) } +// JoysticksProvider declares de the joysticks +type JoysticksProvider interface { + ReadButton(i int) bool + ReadPaddle(i int) uint8 +} + // See https://www.kreativekorp.com/miscpages/a2info/iomemory.shtml // See https://stason.org/TULARC/pc/apple2/programmer/004-I-d-like-to-do-some-serious-Apple-II-programming-Whe.html @@ -90,6 +98,10 @@ func (p *ioC0Page) setSpeakerProvider(s SpeakerProvider) { p.speaker = s } +func (p *ioC0Page) setJoysticksProvider(j JoysticksProvider) { + p.joysticks = j +} + func (p *ioC0Page) peek(address uint16) uint8 { pageAddress := uint8(address) ss := p.softSwitchesR[pageAddress] diff --git a/softSwitches2.go b/softSwitches2.go index 5f35208..43c02be 100644 --- a/softSwitches2.go +++ b/softSwitches2.go @@ -24,10 +24,10 @@ const ( func addApple2SoftSwitches(io *ioC0Page) { - io.addSoftSwitchRW(0x00, getKeySoftSwitch) // Keyboard + io.addSoftSwitchRW(0x00, keySoftSwitch) // Keyboard io.addSoftSwitchRW(0x10, strobeKeyboardSoftSwitch) // Keyboard Strobe io.addSoftSwitchR(0x20, notImplementedSoftSwitchR) // Cassette Output - io.addSoftSwitchR(0x30, getSpeakerSoftSwitch) // Speaker + io.addSoftSwitchR(0x30, speakerSoftSwitch) // Speaker io.addSoftSwitchR(0x40, notImplementedSoftSwitchR) // Game connector Strobe // Note: Some sources indicate that all these cover 16 positions // for read and write. But the Apple2e take over some of them, with @@ -52,25 +52,25 @@ func addApple2SoftSwitches(io *ioC0Page) { io.addSoftSwitchRW(0x5f, getSoftSwitch(ioFlagAnnunciator3, true)) io.addSoftSwitchR(0x60, notImplementedSoftSwitchR) // Cassette Input - io.addSoftSwitchR(0x61, getStatusSoftSwitch(ioFlagButton0)) - io.addSoftSwitchR(0x62, getStatusSoftSwitch(ioFlagButton1)) - io.addSoftSwitchR(0x63, getStatusSoftSwitch(ioFlagButton2)) - io.addSoftSwitchR(0x64, getStatusSoftSwitch(ioDataPaddle0)) - io.addSoftSwitchR(0x65, getStatusSoftSwitch(ioDataPaddle1)) - io.addSoftSwitchR(0x66, getStatusSoftSwitch(ioDataPaddle2)) - io.addSoftSwitchR(0x67, getStatusSoftSwitch(ioDataPaddle3)) + io.addSoftSwitchR(0x61, getButtonSoftSwitch(0)) + io.addSoftSwitchR(0x62, getButtonSoftSwitch(1)) + io.addSoftSwitchR(0x63, getButtonSoftSwitch(2)) + io.addSoftSwitchR(0x64, getPaddleSoftSwitch(0)) + io.addSoftSwitchR(0x65, getPaddleSoftSwitch(1)) + io.addSoftSwitchR(0x66, getPaddleSoftSwitch(2)) + io.addSoftSwitchR(0x67, getPaddleSoftSwitch(3)) // The previous 8 softswitches are repeated io.addSoftSwitchR(0x68, notImplementedSoftSwitchR) // Cassette Input - io.addSoftSwitchR(0x69, getStatusSoftSwitch(ioFlagButton0)) - io.addSoftSwitchR(0x6A, getStatusSoftSwitch(ioFlagButton1)) - io.addSoftSwitchR(0x6B, getStatusSoftSwitch(ioFlagButton2)) - io.addSoftSwitchR(0x6C, getStatusSoftSwitch(ioDataPaddle0)) - io.addSoftSwitchR(0x6D, getStatusSoftSwitch(ioDataPaddle1)) - io.addSoftSwitchR(0x6E, getStatusSoftSwitch(ioDataPaddle2)) - io.addSoftSwitchR(0x6F, getStatusSoftSwitch(ioDataPaddle3)) + io.addSoftSwitchR(0x69, getButtonSoftSwitch(0)) + io.addSoftSwitchR(0x6A, getButtonSoftSwitch(1)) + io.addSoftSwitchR(0x6B, getButtonSoftSwitch(2)) + io.addSoftSwitchR(0x6C, getPaddleSoftSwitch(0)) + io.addSoftSwitchR(0x6D, getPaddleSoftSwitch(1)) + io.addSoftSwitchR(0x6E, getPaddleSoftSwitch(2)) + io.addSoftSwitchR(0x6F, getPaddleSoftSwitch(3)) - io.addSoftSwitchR(0x70, notImplementedSoftSwitchR) // Game controllers reset + io.addSoftSwitchR(0x70, strobePaddlesSoftSwitch) // Game controllers reset } func notImplementedSoftSwitchR(*ioC0Page) uint8 { @@ -97,14 +97,14 @@ func getSoftSwitch(ioFlag uint8, isSet bool) softSwitchR { } } -func getSpeakerSoftSwitch(io *ioC0Page) uint8 { +func speakerSoftSwitch(io *ioC0Page) uint8 { if io.speaker != nil { io.speaker.Click(io.apple2.cpu.GetCycles()) } return 0 } -func getKeySoftSwitch(io *ioC0Page) uint8 { +func keySoftSwitch(io *ioC0Page) uint8 { strobed := (io.softSwitchesData[ioDataKeyboard] & (1 << 7)) == 0 if io.keyboard != nil { if key, ok := io.keyboard.GetKey(strobed); ok { @@ -123,3 +123,48 @@ func strobeKeyboardSoftSwitch(io *ioC0Page) uint8 { io.softSwitchesData[ioDataKeyboard] &^= 1 << 7 return result } + +func getButtonSoftSwitch(i int) softSwitchR { + return func(io *ioC0Page) uint8 { + if io.joysticks != nil && io.joysticks.ReadButton(i) { + return 128 + } + return 0 + } +} + +/* + Paddle values are calculated by the time taken by a current going + througt 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 + the voltage threshold. + + See: http://www.1000bit.it/support/manuali/apple/technotes/aiie/tn.aiie.06.html +*/ + +const paddleToCyclesFactor = 11 + +func getPaddleSoftSwitch(i int) softSwitchR { + return func(io *ioC0Page) uint8 { + if io.joysticks == nil { + return 127 + } + reading := io.joysticks.ReadPaddle(i) + cyclesNeeded := uint64(reading) * paddleToCyclesFactor + cyclesElapsed := io.apple2.cpu.GetCycles() - io.paddlesStrobeCycle + if cyclesElapsed < cyclesNeeded { + // The capacitor is not charged yet + return 128 + } else { + return 0 + } + + } +} + +func strobePaddlesSoftSwitch(io *ioC0Page) uint8 { + // On the real machine this discharges the capacitors. + io.paddlesStrobeCycle = io.apple2.cpu.GetCycles() + return 0 +}