mirror of
https://github.com/ivanizag/izapple2.git
synced 2025-02-25 09:29:08 +00:00
Crude sound support
This commit is contained in:
parent
6fca02da6b
commit
e449ccd907
@ -57,10 +57,10 @@ func NewApple2(romFile string, charRomFile string, clockMhz float64, isColor boo
|
||||
return &a
|
||||
}
|
||||
|
||||
// AddDisk2 insterts a DiskII controller on slot 6
|
||||
func (a *Apple2) AddDisk2(diskRomFile string, diskImage string) {
|
||||
// AddDisk2 insterts a DiskII controller
|
||||
func (a *Apple2) AddDisk2(slot int, diskRomFile string, diskImage string) {
|
||||
d := newCardDisk2(diskRomFile)
|
||||
d.cardBase.insert(a, 6)
|
||||
d.cardBase.insert(a, slot)
|
||||
|
||||
if diskImage != "" {
|
||||
diskette := loadDisquette(diskImage)
|
||||
@ -90,6 +90,11 @@ func (a *Apple2) SetKeyboardProvider(kb KeyboardProvider) {
|
||||
a.io.setKeyboardProvider(kb)
|
||||
}
|
||||
|
||||
// SetSpeakerProvider attaches an external keyboard provider
|
||||
func (a *Apple2) SetSpeakerProvider(s SpeakerProvider) {
|
||||
a.io.setSpeakerProvider(s)
|
||||
}
|
||||
|
||||
const (
|
||||
// CommandToggleSpeed toggles cpu speed between full speed and actual Apple II speed
|
||||
CommandToggleSpeed = iota + 1
|
||||
|
@ -9,6 +9,7 @@ type ioC0Page struct {
|
||||
softSwitchesW [256]softSwitchW
|
||||
softSwitchesData [128]uint8
|
||||
keyboard KeyboardProvider
|
||||
speaker SpeakerProvider
|
||||
apple2 *Apple2
|
||||
}
|
||||
|
||||
@ -20,6 +21,12 @@ type KeyboardProvider interface {
|
||||
GetKey(strobe bool) (key uint8, ok bool)
|
||||
}
|
||||
|
||||
// SpeakerProvider declares the speaker implementation requirements
|
||||
type SpeakerProvider interface {
|
||||
// Click receives a speaker click. The argument is the CPU cycle when it is generated
|
||||
Click(cycle uint64)
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
@ -69,6 +76,10 @@ func (p *ioC0Page) setKeyboardProvider(kb KeyboardProvider) {
|
||||
p.keyboard = kb
|
||||
}
|
||||
|
||||
func (p *ioC0Page) setSpeakerProvider(s SpeakerProvider) {
|
||||
p.speaker = s
|
||||
}
|
||||
|
||||
func (p *ioC0Page) Peek(address uint8) uint8 {
|
||||
//fmt.Printf("Peek on $C0%02x ", address)
|
||||
ss := p.softSwitchesR[address]
|
||||
|
@ -27,7 +27,7 @@ func addApple2SoftSwitches(io *ioC0Page) {
|
||||
io.addSoftSwitchRW(0x00, getKeySoftSwitch) // Keyboard
|
||||
io.addSoftSwitchRW(0x10, strobeKeyboardSoftSwitch) // Keyboard Strobe
|
||||
io.addSoftSwitchR(0x20, notImplementedSoftSwitchR) // Cassette Output
|
||||
io.addSoftSwitchR(0x30, notImplementedSoftSwitchR) // Speaker
|
||||
io.addSoftSwitchR(0x30, getSpeakerSoftSwitch) // 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
|
||||
@ -93,6 +93,13 @@ func getSoftSwitch(ioFlag uint8, isSet bool) softSwitchR {
|
||||
}
|
||||
}
|
||||
|
||||
func getSpeakerSoftSwitch(io *ioC0Page) uint8 {
|
||||
if io.speaker != nil {
|
||||
io.speaker.Click(io.apple2.cpu.GetCycles())
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func getKeySoftSwitch(io *ioC0Page) uint8 {
|
||||
strobed := (io.softSwitchesData[ioDataKeyboard] & (1 << 7)) == 0
|
||||
if io.keyboard != nil {
|
||||
|
@ -10,6 +10,9 @@ import (
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
@ -22,7 +25,8 @@ func SDLRun(a *apple2.Apple2) {
|
||||
window.SetTitle("Apple2")
|
||||
|
||||
kp := newSDLKeyBoard(a)
|
||||
a.SetKeyboardProvider(&kp)
|
||||
a.SetKeyboardProvider(kp)
|
||||
a.SetSpeakerProvider(s)
|
||||
go a.Run(false)
|
||||
|
||||
running := true
|
||||
|
@ -12,11 +12,11 @@ type sdlKeyboard struct {
|
||||
a *apple2.Apple2
|
||||
}
|
||||
|
||||
func newSDLKeyBoard(a *apple2.Apple2) sdlKeyboard {
|
||||
func newSDLKeyBoard(a *apple2.Apple2) *sdlKeyboard {
|
||||
var k sdlKeyboard
|
||||
k.keyChannel = make(chan uint8, 100)
|
||||
k.a = a
|
||||
return k
|
||||
return &k
|
||||
}
|
||||
|
||||
func (k *sdlKeyboard) putText(textEvent *sdl.TextInputEvent) {
|
||||
|
161
apple2sdl/sdlSpeaker.go
Normal file
161
apple2sdl/sdlSpeaker.go
Normal file
@ -0,0 +1,161 @@
|
||||
package apple2sdl
|
||||
|
||||
/*
|
||||
typedef unsigned char Uint8;
|
||||
void SpeakerCallback(void *userdata, Uint8 *stream, int len);
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"go6502/apple2"
|
||||
"log"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
const (
|
||||
samplingHz = 48000
|
||||
bufferSize = 10000
|
||||
// bufferSize/samplingHz will be the max delay of the sound
|
||||
sampleDurationCycles = 1000000 * apple2.CpuClockMhz / samplingHz
|
||||
// each sample on the sound stream is 21.31 cpu cycles approx
|
||||
maxOutOfSyncMs = 2000
|
||||
)
|
||||
|
||||
type sdlSpeaker struct {
|
||||
clickChannel chan uint64
|
||||
pendingClicks []uint64
|
||||
lastCycle uint64
|
||||
lastState bool
|
||||
}
|
||||
|
||||
/*
|
||||
I have not found a way to encode the pointer to sdlSpeaker on the userdata of
|
||||
the call to SpeakerCallback(). I use a global as workaround...
|
||||
*/
|
||||
var theSdlSpeaker *sdlSpeaker
|
||||
|
||||
func newSdlSpeaker() *sdlSpeaker {
|
||||
var s sdlSpeaker
|
||||
s.clickChannel = make(chan uint64, bufferSize)
|
||||
s.pendingClicks = make([]uint64, 0, bufferSize)
|
||||
return &s
|
||||
}
|
||||
|
||||
// Click receives a speaker click. The argument is the CPU cycle when it is generated
|
||||
func (s *sdlSpeaker) Click(cycle uint64) {
|
||||
s.clickChannel <- cycle
|
||||
}
|
||||
|
||||
func stateToLevel(state bool) C.Uint8 {
|
||||
if state {
|
||||
return 255
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
//export SpeakerCallback
|
||||
func SpeakerCallback(userdata unsafe.Pointer, stream *C.Uint8, length C.int) {
|
||||
s := theSdlSpeaker
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Adapt C buffer
|
||||
n := int(length)
|
||||
hdr := reflect.SliceHeader{Data: uintptr(unsafe.Pointer(stream)), Len: n, Cap: n}
|
||||
buf := *(*[]C.Uint8)(unsafe.Pointer(&hdr))
|
||||
|
||||
//Read queued clicks
|
||||
done := false
|
||||
for !done {
|
||||
select {
|
||||
case cycle := <-s.clickChannel:
|
||||
s.pendingClicks = append(s.pendingClicks, cycle)
|
||||
default:
|
||||
done = true
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that we are not too long behind
|
||||
var maxOutOfSyncCyclesFloat = 1000 * apple2.CpuClockMhz * maxOutOfSyncMs
|
||||
var maxOutOfSyncCycles = uint64(maxOutOfSyncCyclesFloat)
|
||||
for _, pc := range s.pendingClicks {
|
||||
if pc-s.lastCycle > maxOutOfSyncCycles {
|
||||
// Fast forward
|
||||
s.lastCycle = pc
|
||||
}
|
||||
}
|
||||
|
||||
// Build wave
|
||||
var i, p int
|
||||
level := stateToLevel(s.lastState)
|
||||
for p = 0; p < len(s.pendingClicks); p++ {
|
||||
cycle := s.pendingClicks[p]
|
||||
if cycle < s.lastCycle {
|
||||
// Too old, ignore
|
||||
continue
|
||||
}
|
||||
|
||||
// Fill with samples
|
||||
samplesNeeded := int(float64(cycle-s.lastCycle) / sampleDurationCycles)
|
||||
if samplesNeeded+i > bufferSize {
|
||||
samplesNeeded = bufferSize - i
|
||||
}
|
||||
for j := 0; j < samplesNeeded; j++ {
|
||||
buf[i] = level
|
||||
i++
|
||||
}
|
||||
|
||||
// Update state
|
||||
s.lastCycle = cycle
|
||||
s.lastState = !s.lastState
|
||||
level = stateToLevel(s.lastState)
|
||||
|
||||
if i == bufferSize {
|
||||
// Buffer is complete
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Complete the buffer if needed
|
||||
for b := i; b < bufferSize; b++ {
|
||||
buf[b] = level
|
||||
}
|
||||
|
||||
// Remove processed clicks, store the rest for later
|
||||
remainingClicks := len(s.pendingClicks) - p
|
||||
for r := 0; r < remainingClicks; r++ {
|
||||
s.pendingClicks[r] = s.pendingClicks[p+r]
|
||||
}
|
||||
s.pendingClicks = s.pendingClicks[0:remainingClicks]
|
||||
}
|
||||
|
||||
func (s *sdlSpeaker) start() {
|
||||
err := sdl.Init(sdl.INIT_AUDIO)
|
||||
if err != nil {
|
||||
log.Printf("Error starting SDL audio: %v.\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
spec := &sdl.AudioSpec{
|
||||
Freq: samplingHz,
|
||||
Format: sdl.AUDIO_U8,
|
||||
Channels: 1,
|
||||
Samples: bufferSize,
|
||||
Callback: sdl.AudioCallback(C.SpeakerCallback),
|
||||
}
|
||||
|
||||
if err := sdl.OpenAudio(spec, nil); err != nil {
|
||||
log.Printf("Error opening the SDL audio channel: %v.\n", err)
|
||||
return
|
||||
}
|
||||
sdl.PauseAudio(false)
|
||||
theSdlSpeaker = s
|
||||
}
|
||||
|
||||
func (s *sdlSpeaker) close() {
|
||||
sdl.CloseAudio()
|
||||
sdl.Quit()
|
||||
}
|
@ -11,7 +11,7 @@ import "fmt"
|
||||
type State struct {
|
||||
reg registers
|
||||
mem Memory
|
||||
cycles int64
|
||||
cycles uint64
|
||||
opcodes *[256]opcode
|
||||
}
|
||||
|
||||
@ -60,7 +60,7 @@ func (s *State) ExecuteInstruction(log bool) {
|
||||
fmt.Printf("%#04x %-12s: ", pc, lineString(line, opcode))
|
||||
}
|
||||
opcode.action(s, line, opcode)
|
||||
s.cycles += int64(opcode.cycles)
|
||||
s.cycles += uint64(opcode.cycles)
|
||||
if log {
|
||||
fmt.Printf("%v, [%02x]\n", s.reg, line)
|
||||
}
|
||||
@ -74,7 +74,7 @@ func (s *State) Reset() {
|
||||
}
|
||||
|
||||
// GetCycles returns the count of CPU cycles since last reset.
|
||||
func (s *State) GetCycles() int64 {
|
||||
func (s *State) GetCycles() uint64 {
|
||||
return s.cycles
|
||||
}
|
||||
|
||||
|
8
main.go
8
main.go
@ -15,6 +15,10 @@ func main() {
|
||||
"diskRom",
|
||||
"apple2/romdumps/DISK2.rom",
|
||||
"rom file for the disk drive controller")
|
||||
disk2Slot := flag.Int(
|
||||
"disk2Slot",
|
||||
6,
|
||||
"slot for the disk driver. 0 for none.")
|
||||
diskImage := flag.String(
|
||||
"disk",
|
||||
"../dos33.dsk",
|
||||
@ -59,7 +63,9 @@ func main() {
|
||||
|
||||
log := false
|
||||
a := apple2.NewApple2(*romFile, *charRomFile, *cpuClock, !*mono, *panicSS)
|
||||
a.AddDisk2(*disk2RomFile, *diskImage)
|
||||
if *disk2Slot > 0 {
|
||||
a.AddDisk2(*disk2Slot, *disk2RomFile, *diskImage)
|
||||
}
|
||||
if *useSdl {
|
||||
a.ConfigureStdConsole(false, *stdoutScreen)
|
||||
apple2sdl.SDLRun(a)
|
||||
|
Loading…
x
Reference in New Issue
Block a user