Support for joysticks
This commit is contained in:
parent
67a92895b3
commit
3d75002588
|
@ -23,6 +23,7 @@ Portable emulator of an Apple II+. Written in Go.
|
||||||
- Fast disk mode to set max speed while using the disks.
|
- Fast disk mode to set max speed while using the disks.
|
||||||
- Single file executable with embedded ROMs and DOS 3.3
|
- Single file executable with embedded ROMs and DOS 3.3
|
||||||
- Optional emulation of the clone Base64A by Copam
|
- Optional emulation of the clone Base64A by Copam
|
||||||
|
- Joystick support. Up to two joysticks or four paddes.
|
||||||
|
|
||||||
|
|
||||||
## Running the emulator
|
## Running the emulator
|
||||||
|
|
|
@ -107,3 +107,8 @@ func (a *Apple2) SetKeyboardProvider(kb KeyboardProvider) {
|
||||||
func (a *Apple2) SetSpeakerProvider(s SpeakerProvider) {
|
func (a *Apple2) SetSpeakerProvider(s SpeakerProvider) {
|
||||||
a.io.setSpeakerProvider(s)
|
a.io.setSpeakerProvider(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetJoysticksProvider attaches an external joysticks provider
|
||||||
|
func (a *Apple2) SetJoysticksProvider(j JoysticksProvider) {
|
||||||
|
a.io.setJoysticksProvider(j)
|
||||||
|
}
|
||||||
|
|
|
@ -14,8 +14,6 @@ func main() {
|
||||||
|
|
||||||
// SDLRun starts the Apple2 emulator on SDL
|
// SDLRun starts the Apple2 emulator on SDL
|
||||||
func SDLRun(a *apple2.Apple2) {
|
func SDLRun(a *apple2.Apple2) {
|
||||||
s := newSDLSpeaker()
|
|
||||||
s.start()
|
|
||||||
|
|
||||||
window, renderer, err := sdl.CreateWindowAndRenderer(4*40*7, 4*24*8,
|
window, renderer, err := sdl.CreateWindowAndRenderer(4*40*7, 4*24*8,
|
||||||
sdl.WINDOW_SHOWN)
|
sdl.WINDOW_SHOWN)
|
||||||
|
@ -30,7 +28,14 @@ func SDLRun(a *apple2.Apple2) {
|
||||||
|
|
||||||
kp := newSDLKeyBoard(a)
|
kp := newSDLKeyBoard(a)
|
||||||
a.SetKeyboardProvider(kp)
|
a.SetKeyboardProvider(kp)
|
||||||
|
|
||||||
|
s := newSDLSpeaker()
|
||||||
|
s.start()
|
||||||
a.SetSpeakerProvider(s)
|
a.SetSpeakerProvider(s)
|
||||||
|
|
||||||
|
j := newSDLJoysticks()
|
||||||
|
a.SetJoysticksProvider(j)
|
||||||
|
|
||||||
go a.Run(false)
|
go a.Run(false)
|
||||||
|
|
||||||
running := true
|
running := true
|
||||||
|
@ -47,6 +52,12 @@ func SDLRun(a *apple2.Apple2) {
|
||||||
//fmt.Printf("[%d ms] TextInput\ttype:%d\texts:%s\n",
|
//fmt.Printf("[%d ms] TextInput\ttype:%d\texts:%s\n",
|
||||||
// t.Timestamp, t.Type, t.GetText())
|
// t.Timestamp, t.Type, t.GetText())
|
||||||
kp.putText(t)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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]
|
||||||
|
}
|
|
@ -98,7 +98,7 @@ func (b *Base64a) loadRom() {
|
||||||
// Write on the speaker. That is a double access and should do nothing
|
// Write on the speaker. That is a double access and should do nothing
|
||||||
// but works somehow on the BASE64A
|
// but works somehow on the BASE64A
|
||||||
b.a.io.addSoftSwitchW(0x30, func(io *ioC0Page, value uint8) {
|
b.a.io.addSoftSwitchW(0x30, func(io *ioC0Page, value uint8) {
|
||||||
getSpeakerSoftSwitch(io)
|
speakerSoftSwitch(io)
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
28
ioC0Page.go
28
ioC0Page.go
|
@ -7,28 +7,36 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type ioC0Page struct {
|
type ioC0Page struct {
|
||||||
softSwitchesR [256]softSwitchR
|
softSwitchesR [256]softSwitchR
|
||||||
softSwitchesW [256]softSwitchW
|
softSwitchesW [256]softSwitchW
|
||||||
softSwitchesData [128]uint8
|
softSwitchesData [128]uint8
|
||||||
keyboard KeyboardProvider
|
keyboard KeyboardProvider
|
||||||
speaker SpeakerProvider
|
speaker SpeakerProvider
|
||||||
apple2 *Apple2
|
paddlesStrobeCycle uint64
|
||||||
|
joysticks JoysticksProvider
|
||||||
|
apple2 *Apple2
|
||||||
}
|
}
|
||||||
|
|
||||||
type softSwitchR func(io *ioC0Page) uint8
|
type softSwitchR func(io *ioC0Page) uint8
|
||||||
type softSwitchW func(io *ioC0Page, value uint8)
|
type softSwitchW func(io *ioC0Page, value uint8)
|
||||||
|
|
||||||
// KeyboardProvider declares the keyboard implementation requirements
|
// KeyboardProvider provides a keyboard implementation
|
||||||
type KeyboardProvider interface {
|
type KeyboardProvider interface {
|
||||||
GetKey(strobe bool) (key uint8, ok bool)
|
GetKey(strobe bool) (key uint8, ok bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpeakerProvider declares the speaker implementation requirements
|
// SpeakerProvider provides a speaker implementation
|
||||||
type SpeakerProvider interface {
|
type SpeakerProvider interface {
|
||||||
// Click receives a speaker click. The argument is the CPU cycle when it is generated
|
// Click receives a speaker click. The argument is the CPU cycle when it is generated
|
||||||
Click(cycle uint64)
|
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://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
|
// 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
|
p.speaker = s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *ioC0Page) setJoysticksProvider(j JoysticksProvider) {
|
||||||
|
p.joysticks = j
|
||||||
|
}
|
||||||
|
|
||||||
func (p *ioC0Page) peek(address uint16) uint8 {
|
func (p *ioC0Page) peek(address uint16) uint8 {
|
||||||
pageAddress := uint8(address)
|
pageAddress := uint8(address)
|
||||||
ss := p.softSwitchesR[pageAddress]
|
ss := p.softSwitchesR[pageAddress]
|
||||||
|
|
|
@ -24,10 +24,10 @@ const (
|
||||||
|
|
||||||
func addApple2SoftSwitches(io *ioC0Page) {
|
func addApple2SoftSwitches(io *ioC0Page) {
|
||||||
|
|
||||||
io.addSoftSwitchRW(0x00, getKeySoftSwitch) // Keyboard
|
io.addSoftSwitchRW(0x00, keySoftSwitch) // Keyboard
|
||||||
io.addSoftSwitchRW(0x10, strobeKeyboardSoftSwitch) // Keyboard Strobe
|
io.addSoftSwitchRW(0x10, strobeKeyboardSoftSwitch) // Keyboard Strobe
|
||||||
io.addSoftSwitchR(0x20, notImplementedSoftSwitchR) // Cassette Output
|
io.addSoftSwitchR(0x20, notImplementedSoftSwitchR) // Cassette Output
|
||||||
io.addSoftSwitchR(0x30, getSpeakerSoftSwitch) // Speaker
|
io.addSoftSwitchR(0x30, speakerSoftSwitch) // Speaker
|
||||||
io.addSoftSwitchR(0x40, notImplementedSoftSwitchR) // Game connector Strobe
|
io.addSoftSwitchR(0x40, notImplementedSoftSwitchR) // Game connector Strobe
|
||||||
// Note: Some sources indicate that all these cover 16 positions
|
// Note: Some sources indicate that all these cover 16 positions
|
||||||
// for read and write. But the Apple2e take over some of them, with
|
// 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.addSoftSwitchRW(0x5f, getSoftSwitch(ioFlagAnnunciator3, true))
|
||||||
|
|
||||||
io.addSoftSwitchR(0x60, notImplementedSoftSwitchR) // Cassette Input
|
io.addSoftSwitchR(0x60, notImplementedSoftSwitchR) // Cassette Input
|
||||||
io.addSoftSwitchR(0x61, getStatusSoftSwitch(ioFlagButton0))
|
io.addSoftSwitchR(0x61, getButtonSoftSwitch(0))
|
||||||
io.addSoftSwitchR(0x62, getStatusSoftSwitch(ioFlagButton1))
|
io.addSoftSwitchR(0x62, getButtonSoftSwitch(1))
|
||||||
io.addSoftSwitchR(0x63, getStatusSoftSwitch(ioFlagButton2))
|
io.addSoftSwitchR(0x63, getButtonSoftSwitch(2))
|
||||||
io.addSoftSwitchR(0x64, getStatusSoftSwitch(ioDataPaddle0))
|
io.addSoftSwitchR(0x64, getPaddleSoftSwitch(0))
|
||||||
io.addSoftSwitchR(0x65, getStatusSoftSwitch(ioDataPaddle1))
|
io.addSoftSwitchR(0x65, getPaddleSoftSwitch(1))
|
||||||
io.addSoftSwitchR(0x66, getStatusSoftSwitch(ioDataPaddle2))
|
io.addSoftSwitchR(0x66, getPaddleSoftSwitch(2))
|
||||||
io.addSoftSwitchR(0x67, getStatusSoftSwitch(ioDataPaddle3))
|
io.addSoftSwitchR(0x67, getPaddleSoftSwitch(3))
|
||||||
|
|
||||||
// The previous 8 softswitches are repeated
|
// The previous 8 softswitches are repeated
|
||||||
io.addSoftSwitchR(0x68, notImplementedSoftSwitchR) // Cassette Input
|
io.addSoftSwitchR(0x68, notImplementedSoftSwitchR) // Cassette Input
|
||||||
io.addSoftSwitchR(0x69, getStatusSoftSwitch(ioFlagButton0))
|
io.addSoftSwitchR(0x69, getButtonSoftSwitch(0))
|
||||||
io.addSoftSwitchR(0x6A, getStatusSoftSwitch(ioFlagButton1))
|
io.addSoftSwitchR(0x6A, getButtonSoftSwitch(1))
|
||||||
io.addSoftSwitchR(0x6B, getStatusSoftSwitch(ioFlagButton2))
|
io.addSoftSwitchR(0x6B, getButtonSoftSwitch(2))
|
||||||
io.addSoftSwitchR(0x6C, getStatusSoftSwitch(ioDataPaddle0))
|
io.addSoftSwitchR(0x6C, getPaddleSoftSwitch(0))
|
||||||
io.addSoftSwitchR(0x6D, getStatusSoftSwitch(ioDataPaddle1))
|
io.addSoftSwitchR(0x6D, getPaddleSoftSwitch(1))
|
||||||
io.addSoftSwitchR(0x6E, getStatusSoftSwitch(ioDataPaddle2))
|
io.addSoftSwitchR(0x6E, getPaddleSoftSwitch(2))
|
||||||
io.addSoftSwitchR(0x6F, getStatusSoftSwitch(ioDataPaddle3))
|
io.addSoftSwitchR(0x6F, getPaddleSoftSwitch(3))
|
||||||
|
|
||||||
io.addSoftSwitchR(0x70, notImplementedSoftSwitchR) // Game controllers reset
|
io.addSoftSwitchR(0x70, strobePaddlesSoftSwitch) // Game controllers reset
|
||||||
}
|
}
|
||||||
|
|
||||||
func notImplementedSoftSwitchR(*ioC0Page) uint8 {
|
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 {
|
if io.speaker != nil {
|
||||||
io.speaker.Click(io.apple2.cpu.GetCycles())
|
io.speaker.Click(io.apple2.cpu.GetCycles())
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func getKeySoftSwitch(io *ioC0Page) uint8 {
|
func keySoftSwitch(io *ioC0Page) uint8 {
|
||||||
strobed := (io.softSwitchesData[ioDataKeyboard] & (1 << 7)) == 0
|
strobed := (io.softSwitchesData[ioDataKeyboard] & (1 << 7)) == 0
|
||||||
if io.keyboard != nil {
|
if io.keyboard != nil {
|
||||||
if key, ok := io.keyboard.GetKey(strobed); ok {
|
if key, ok := io.keyboard.GetKey(strobed); ok {
|
||||||
|
@ -123,3 +123,48 @@ func strobeKeyboardSoftSwitch(io *ioC0Page) uint8 {
|
||||||
io.softSwitchesData[ioDataKeyboard] &^= 1 << 7
|
io.softSwitchesData[ioDataKeyboard] &^= 1 << 7
|
||||||
return result
|
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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue