Support for joysticks

This commit is contained in:
Ivan Izaguirre 2019-08-06 00:37:27 +02:00
parent 67a92895b3
commit 3d75002588
7 changed files with 184 additions and 30 deletions

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}
}

80
apple2sdl/sdlJoysticks.go Normal file
View File

@ -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]
}

View File

@ -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)
})
}

View File

@ -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]

View File

@ -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
}