Crude sound support

This commit is contained in:
Ivan Izaguirre 2019-05-10 00:09:15 +02:00
parent 6fca02da6b
commit e449ccd907
8 changed files with 205 additions and 11 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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