mirror of
https://github.com/ivanizag/izapple2.git
synced 2025-01-02 20:29:44 +00:00
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.
|
||||
- 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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
80
apple2sdl/sdlJoysticks.go
Normal 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]
|
||||
}
|
@ -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)
|
||||
})
|
||||
|
||||
}
|
||||
|
16
ioC0Page.go
16
ioC0Page.go
@ -12,23 +12,31 @@ type ioC0Page struct {
|
||||
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]
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user