diff --git a/README.md b/README.md index 3757eb2..5720b2f 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,8 @@ $ ./build/darwin-amd64/mettaur XXXX.gba ## ToDo -- [ ] Sound +- [ ] Sound DMA +- [ ] Clear sound - [ ] Window - [ ] Mosaic - [ ] Blend diff --git a/cmd/main.go b/cmd/main.go index 8ae3867..0d01745 100755 --- a/cmd/main.go +++ b/cmd/main.go @@ -123,13 +123,7 @@ type Emulator struct { } func (e *Emulator) Update() error { - defer func() { - if err := recover(); err != nil { - fmt.Fprintf(os.Stderr, "crash in emulation: %s in 0x%08x\n", err, e.gba.PC()) - e.gba.Exit("") - panic("") - } - }() + defer e.gba.PanicHandler(true) e.gba.Update() if e.gba.DoSav && e.gba.Frame%60 == 0 { e.writeSav() diff --git a/go.mod b/go.mod index 1377b0e..d7b6a2c 100755 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module mettaur go 1.16 require ( - github.com/anthonynsimon/bild v0.13.0 // indirect - github.com/hajimehoshi/ebiten/v2 v2.0.6 + github.com/anthonynsimon/bild v0.13.0 + github.com/hajimehoshi/ebiten/v2 v2.0.8 + github.com/hajimehoshi/oto v0.7.1 ) diff --git a/go.sum b/go.sum index de1acfd..724432b 100644 --- a/go.sum +++ b/go.sum @@ -14,12 +14,14 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200707082815-5321531c36a2/go.mod h1:tQ2 github.com/gofrs/flock v0.8.0 h1:MSdYClljsF3PbENUUEx85nkWfJSGfzYI9yEBZOJz6CY= github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/hajimehoshi/bitmapfont/v2 v2.1.0/go.mod h1:2BnYrkTQGThpr/CY6LorYtt/zEPNzvE/ND69CRTaHMs= -github.com/hajimehoshi/ebiten/v2 v2.0.6 h1:sHNymgI+q80xasP69oFyrpup6r2qCNsKxqwsGEh6PWE= -github.com/hajimehoshi/ebiten/v2 v2.0.6/go.mod h1:uS3OjMW3f2DRDMtWoIF7yMMmrMkv+fZ6pXcwR1pfA0Y= +github.com/hajimehoshi/ebiten/v2 v2.0.8 h1:+LPEcUhsLS3swxf2LtBb2V1TO72YoGPArWPqxzXEDYI= +github.com/hajimehoshi/ebiten/v2 v2.0.8/go.mod h1:uS3OjMW3f2DRDMtWoIF7yMMmrMkv+fZ6pXcwR1pfA0Y= github.com/hajimehoshi/file2byteslice v0.0.0-20200812174855-0e5e8a80490e/go.mod h1:CqqAHp7Dk/AqQiwuhV1yT2334qbA/tFWQW0MD2dGqUE= github.com/hajimehoshi/go-mp3 v0.3.1/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= github.com/hajimehoshi/oto v0.6.8/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= +github.com/hajimehoshi/oto v0.7.1 h1:I7maFPz5MBCwiutOrz++DLdbr4rTzBsbBuV2VpgU9kk= +github.com/hajimehoshi/oto v0.7.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jakecoffman/cp v1.0.0/go.mod h1:JjY/Fp6d8E1CHnu74gWNnU0+b9VzEdUVPoJxg2PsTQg= diff --git a/pkg/gba/apu.go b/pkg/gba/apu.go new file mode 100644 index 0000000..a480cb6 --- /dev/null +++ b/pkg/gba/apu.go @@ -0,0 +1,592 @@ +package gba + +import ( + "math" + "mettaur/pkg/ram" + "mettaur/pkg/util" + "time" + + "github.com/hajimehoshi/oto" +) + +const ( + CPU_FREQ_HZ = 16777216 + SND_FREQUENCY = 32768 + SND_SAMPLES = 512 + SAMP_CYCLES = (CPU_FREQ_HZ / SND_FREQUENCY) + BUFF_SAMPLES = ((SND_SAMPLES) * 16 * 2) + BUFF_SAMPLES_MSK = ((BUFF_SAMPLES) - 1) + SAMPLE_TIME float64 = 1.0 / SND_FREQUENCY + SAMPLE_RATE = SND_FREQUENCY + STREAM_LEN = 2940 // 2 * 2 * sampleRate * (1/60) +) + +const ( + PSG_MAX = 0x7f + PSG_MIN = -0x80 +) + +const ( + SAMP_MAX = 0x1ff + SAMP_MIN = -0x200 +) + +var waveSamples byte +var wavePosition byte + +var waveRAM [0x20]byte + +type APU struct { + context *oto.Context + player *oto.Player + chans [4]*SoundChan +} + +type SoundChan struct { + phase bool + lfsr uint16 + samples float64 + lengthTime float64 + sweepTime float64 + envTime float64 +} + +func isSoundIO(addr uint32) bool { + return addr >= 0x04000060 && addr <= 0x04000081 +} + +func isWaveRAM(addr uint32) bool { + return addr >= 0x04000090 && addr <= 0x0400009f +} + +func isResetSoundChan(addr uint32) bool { + return addr == 0x04000065 || addr == 0x0400006d || addr == 0x04000075 || addr == 0x0400007d +} +func (g *GBA) resetSoundChan(addr uint32, b byte) { + m := map[uint32]int{0x04000065: 0, 0x0400006d: 1, 0x04000075: 2, 0x0400007d: 3} + g._resetSoundChan(m[addr], util.Bit(b, 7)) +} + +func newAPU() *APU { + stream = make([]byte, STREAM_LEN) + + context, err := oto.NewContext(SAMPLE_RATE, 2, 2, STREAM_LEN) + if err != nil { + panic(err) + } + + player := context.NewPlayer() + return &APU{ + context: context, + player: player, + chans: [4]*SoundChan{&SoundChan{}, &SoundChan{}, &SoundChan{}, &SoundChan{}}, + } +} + +func (g *GBA) exitAPU() { + g.apu.context.Close() +} + +func (g *GBA) playSound() { + go func() { + for range time.Tick(time.Second / 60) { + g.soundMix() + g.apu.player.Write(stream) + } + }() +} + +var dutyLookUp = [4]float64{0.125, 0.25, 0.5, 0.75} +var dutyLookUpi = [4]float64{0.875, 0.75, 0.5, 0.25} + +func (g *GBA) squareSample(ch int) int8 { + if !g.isSoundChanEnable(ch) { + return 0 + } + + toneAddr := uint32(ram.SOUND1CNT_H) + if ch == 1 { + toneAddr = ram.SOUND2CNT_L + } + + freqHz := g._getRAM(ram.SOUND1CNT_X) & 0b0111_1111_1111 + if ch == 1 { + freqHz = g._getRAM(ram.SOUND2CNT_H) & 0b0111_1111_1111 + } + frequency := 131072 / float64(2048-freqHz) + + // Full length of the generated wave (if enabled) in seconds + soundLen := g._getRAM(toneAddr) & 0b0011_1111 + length := float64(64-soundLen) / 256 + + // Envelope volume change interval in seconds + envStep := g._getRAM(toneAddr) >> 8 & 0b111 + envelopeInterval := float64(envStep) / 64 + + cycleSamples := SND_FREQUENCY / frequency // Numbers of samples that a single cycle (wave phase change 1 -> 0) takes at output sample rate + + // Length reached check (if so, just disable the channel and return silence) + if (ch == 0 && util.Bit(g._getRAM(ram.SOUND1CNT_X), 14)) || (ch == 1 && util.Bit(g._getRAM(ram.SOUND2CNT_H), 14)) { + g.apu.chans[ch].lengthTime += SAMPLE_TIME + if g.apu.chans[ch].lengthTime >= length { + g.enableSoundChan(ch, false) + return 0 + } + } + + // Frequency sweep (Square 1 channel only) + if ch == 0 { + sweepTime := (g._getRAM(ram.SOUND1CNT_L) >> 4) & 0b111 + sweepInterval := 0.0078 * float64(sweepTime+1) // Frquency sweep change interval in seconds + + g.apu.chans[0].sweepTime += SAMPLE_TIME + if g.apu.chans[0].sweepTime >= sweepInterval { + g.apu.chans[0].sweepTime -= sweepInterval + + // A Sweep Shift of 0 means that Sweep is disabled + sweepShift := byte(g._getRAM(ram.SOUND1CNT_L) & 7) + + if sweepShift != 0 { + disp := freqHz >> sweepShift + + if util.Bit(g._getRAM(ram.SOUND1CNT_L), 3) { + freqHz -= disp + } else { + freqHz += disp + } + + if freqHz < 0x7ff { + // update frequency + ctrl := uint16(g._getRAM(ram.SOUND1CNT_X)) + ctrl = (ctrl & ^uint16(0x7ff)) | uint16(freqHz) + g._setRAM16(ram.SOUND1CNT_X, ctrl) + } else { + g.enableSoundChan(0, false) + } + } + } + } + + // Envelope volume + envelope := uint16((g._getRAM(toneAddr) >> 12) & 0xf) + if envStep > 0 { + g.apu.chans[ch].envTime += SAMPLE_TIME + + if g.apu.chans[ch].envTime >= envelopeInterval { + g.apu.chans[ch].envTime -= envelopeInterval + + tone := uint16(g._getRAM(toneAddr)) + if util.Bit(tone, 11) { + if envelope < 0xf { + envelope++ + } + } else { + if envelope > 0 { + envelope-- + } + } + + tone = (tone & ^uint16(0xf000)) | (envelope << 12) + g._setRAM16(toneAddr, tone) + } + } + + // Phase change (when the wave goes from Low to High or High to Low, the Square Wave pattern) + duty := (g._getRAM(toneAddr) >> 6) & 0b11 + g.apu.chans[ch].samples++ + if g.apu.chans[ch].phase { + // 1 -> 0 + phaseChange := cycleSamples * dutyLookUp[duty] + if g.apu.chans[ch].samples > phaseChange { + g.apu.chans[ch].samples -= phaseChange + g.apu.chans[ch].phase = false + } + } else { + // 0 -> 1 + phaseChange := cycleSamples * dutyLookUpi[duty] + if g.apu.chans[ch].samples > phaseChange { + g.apu.chans[ch].samples -= phaseChange + g.apu.chans[ch].phase = true + } + } + + if g.apu.chans[ch].phase { + return int8(float64(envelope) * PSG_MAX / 15) + } + return int8(float64(envelope) * PSG_MIN / 15) +} + +func (g *GBA) enableSoundChan(ch int, enable bool) { + cntx := byte(g._getRAM(ram.SOUNDCNT_X)) + if enable { + cntx = cntx | (1 << ch) + } else { + cntx = cntx & ^(1 << ch) + } + g.RAM.IO[ram.IOOffset(ram.SOUNDCNT_X)] = cntx +} + +func (g *GBA) isSoundMasterEnable() bool { + cntx := byte(g._getRAM(ram.SOUNDCNT_X)) + return util.Bit(cntx, 7) +} + +func (g *GBA) isSoundChanEnable(ch int) bool { + cntx := byte(g._getRAM(ram.SOUNDCNT_X)) + return util.Bit(cntx, ch) +} + +func (g *GBA) waveSample() int8 { + wave := uint16(g._getRAM(ram.SOUND3CNT_L)) + if !(g.isSoundChanEnable(2) && util.Bit(wave, 7)) { + return 0 + } + + // Actual frequency in Hertz + freqHz := g._getRAM(ram.SOUND3CNT_X) & 2047 + frequency := 2097152 / (2048 - float64(freqHz)) + + cnth := uint16(g._getRAM(ram.SOUND3CNT_H)) // volume + + // Full length of the generated wave (if enabled) in seconds + soundLen := cnth & 0xff + length := (256 - float64(soundLen)) / 256. + + // Numbers of samples that a single "cycle" (all entries on Wave RAM) takes at output sample rate + cycleSamples := SND_FREQUENCY / frequency + + // Length reached check (if so, just disable the channel and return silence) + if util.Bit(uint16(g._getRAM(ram.SOUND3CNT_X)), 14) { + g.apu.chans[2].lengthTime += SAMPLE_TIME + if g.apu.chans[2].lengthTime >= length { + g.enableSoundChan(2, false) + return 0 + } + } + + g.apu.chans[2].samples++ + if g.apu.chans[2].samples >= cycleSamples { + g.apu.chans[2].samples -= cycleSamples + + waveSamples-- + if waveSamples != 0 { + wavePosition = (wavePosition + 1) & 0b0011_1111 + } else { + g.waveReset() + } + } + + waveIdx := (uint32(wavePosition) >> 1) & 0x1f + samp := int8(waveRAM[waveIdx]&0xf) - 8 + if wavePosition&1 == 0 { + samp = int8((waveRAM[waveIdx]>>4)&0xf) - 8 + } + + volume := (cnth >> 13) & 0x7 + switch volume { + case 0: + samp = 0 + case 1: + samp >>= 0 + case 2: + samp >>= 1 + case 3: + samp >>= 2 + default: + samp = (samp >> 2) * 3 + } + + if samp >= 0 { + return int8(float64(samp) / 7 * PSG_MAX) + } + return int8(float64(samp) / (-8) * PSG_MIN) +} + +func (g *GBA) noiseSample() int8 { + if !g.isSoundChanEnable(3) { + return 0 + } + + cnth := g._getRAM(ram.SOUND4CNT_H) // ctrl + + // Actual frequency in Hertz (524288 / r / 2^(s+1)) + r, s := float64(cnth&0x7), float64((cnth>>4)&0xf) + if r == 0 { + r = 0.5 + } + frequency := (524288 / r) / math.Pow(2, s+1) + + cntl := g._getRAM(ram.SOUND4CNT_L) // env + // Full length of the generated wave (if enabled) in seconds + soundLen := cntl & 0x3f + length := (64 - float64(soundLen)) / 256 + + // Length reached check (if so, just disable the channel and return silence) + if util.Bit(cnth, 14) { + g.apu.chans[3].lengthTime += SAMPLE_TIME + if g.apu.chans[3].lengthTime >= length { + g.enableSoundChan(3, false) + return 0 + } + } + + // Envelope volume change interval in seconds + envStep := (cntl >> 8) & 0x7 + envelopeInterval := float64(envStep) / 64 + + // Envelope volume + envelope := (cntl >> 12) & 0xf + if envStep != 0 { + g.apu.chans[3].envTime += SAMPLE_TIME + if g.apu.chans[3].envTime >= envelopeInterval { + g.apu.chans[3].envTime -= envelopeInterval + + if util.Bit(cntl, 11) { + if envelope < 0xf { + envelope++ + } + } else { + if envelope > 0x0 { + envelope-- + } + } + + newCntl := (cntl & ^uint32(0xf000)) | (envelope << 12) + g._setRAM32(ram.SOUND4CNT_L, newCntl) + } + } + + // Numbers of samples that a single cycle (pseudo-random noise value) takes at output sample rate + cycleSamples := SND_FREQUENCY / frequency + + carry := byte(g.apu.chans[3].lfsr & 0b1) + g.apu.chans[3].samples++ + if g.apu.chans[3].samples >= cycleSamples { + g.apu.chans[3].samples -= cycleSamples + g.apu.chans[3].lfsr >>= 1 + + high := uint16(byte(g.apu.chans[3].lfsr&1) ^ carry) + if util.Bit(cnth, 3) { + g.apu.chans[3].lfsr |= (high << 6) + } else { + g.apu.chans[3].lfsr |= (high << 14) + } + } + + if carry != 0 { + return int8((float64(envelope) / 15) * PSG_MAX) + } + return int8((float64(envelope) / 15) * PSG_MIN) +} + +func (g *GBA) waveReset() { + wave := uint16(g._getRAM(ram.SOUND3CNT_L)) + if util.Bit(wave, 5) { + // 64 samples (at 4 bits each, uses both banks so initial position is always 0) + wavePosition, waveSamples = 0, 64 + return + } + // 32 samples (at 4 bits each, bank selectable through Wave Control register) + wavePosition, waveSamples = byte((wave>>1)&0x20), 32 +} + +var ( + sndCurPlay uint32 = 0 + sndCurWrite uint32 = 0x200 +) + +// This prevents the cursor from overflowing. Call after some time (like per frame, or per second...) +func (g *GBA) soundBufferWrap() { + left, right := sndCurPlay/BUFF_SAMPLES, sndCurWrite/BUFF_SAMPLES + if left == right { + sndCurPlay &= BUFF_SAMPLES_MSK + sndCurWrite &= BUFF_SAMPLES_MSK + } +} + +var sndBuffer [BUFF_SAMPLES]int16 +var stream []byte + +func (g *GBA) soundMix() { + for i := 0; i < STREAM_LEN; i += 4 { + snd := sndBuffer[sndCurPlay&BUFF_SAMPLES_MSK] << 6 + stream[i+0], stream[i+1] = byte(snd), byte(snd>>8) + sndCurPlay++ + snd = sndBuffer[sndCurPlay&BUFF_SAMPLES_MSK] << 6 + stream[i+2], stream[i+3] = byte(snd), byte(snd>>8) + sndCurPlay++ + } + + // Avoid desync between the Play cursor and the Write cursor + delta := (int32(sndCurWrite-sndCurPlay) >> 8) - (int32(sndCurWrite-sndCurPlay)>>8)%2 + if delta >= 0 { + sndCurPlay += uint32(delta) + } else { + sndCurPlay -= uint32(-delta) + } +} + +var ( + fifoALen, fifoBLen byte + fifoA, fifoB [0x20]int8 +) + +func (g *GBA) fifoACopy() { + // FIFO A full + if fifoALen+4 > 0x20 { + return + } + + for i := uint32(0); i < 4; i++ { + fifoA[fifoALen] = int8(g.RAM.IO[ram.IOOffset(0x040000a0+i)]) + fifoALen++ + } +} +func (g *GBA) fifoBCopy() { + // FIFO B full + if fifoBLen+4 > 0x20 { + return + } + + for i := uint32(0); i < 4; i++ { + fifoB[fifoBLen] = int8(g.RAM.IO[ram.IOOffset(0x040000a4+i)]) + fifoBLen++ + } +} + +var ( + fifoASamp, fifoBSamp int8 +) + +func (g *GBA) fifoALoad() { + if fifoALen == 0 { + return + } + + fifoASamp = fifoA[0] + fifoALen-- + + for i := byte(0); i < fifoALen; i++ { + fifoA[i] = fifoA[i+1] + } + for i := fifoALen; i < 0x20; i++ { + fifoA[i] = 0 + } +} + +func (g *GBA) fifoBLoad() { + if fifoBLen == 0 { + return + } + + fifoBSamp = fifoB[0] + fifoBLen-- + + for i := byte(0); i < fifoBLen; i++ { + fifoB[i] = fifoB[i+1] + } + for i := fifoBLen; i < 0x20; i++ { + fifoB[i] = 0 + } +} + +var ( + sndCycles = uint32(0) + psgVolLut = [8]int32{0x000, 0x024, 0x049, 0x06d, 0x092, 0x0b6, 0x0db, 0x100} + psgRshLut = [4]int32{0xa, 0x9, 0x8, 0x7} +) + +func (g *GBA) clip(val int32) int16 { + if val > SAMP_MAX { + val = SAMP_MAX + } + if val < SAMP_MIN { + val = SAMP_MIN + } + + return int16(val) +} + +func (g *GBA) soundClock(cycles uint32) { + defer g.PanicHandler(true) + sndCycles += cycles + + sampPcmL, sampPcmR := int16(0), int16(0) + + cnth := uint16(g._getRAM(ram.SOUNDCNT_H)) // snd_pcm_vol + volADiv, volBDiv := int16((cnth>>2)&0b1), int16((cnth>>3)&0b1) + sampCh4, sampCh5 := fifoASamp>>volADiv, fifoBSamp>>volBDiv + sampCh4, sampCh5 = 0, 0 + + // Left + if util.Bit(cnth, 9) { + sampPcmL = g.clip(int32(sampPcmL) + int32(sampCh4)) + } + if util.Bit(cnth, 13) { + sampPcmL = g.clip(int32(sampPcmL) + int32(sampCh5)) + } + + // Right + if util.Bit(cnth, 8) { + sampPcmR = g.clip(int32(sampPcmR) + int32(sampCh4)) + } + if util.Bit(cnth, 12) { + sampPcmR = g.clip(int32(sampPcmR) + int32(sampCh5)) + } + + for sndCycles >= SAMP_CYCLES { + sampCh := [4]int16{int16(g.squareSample(0)), int16(g.squareSample(1)), int16(g.waveSample()), int16(g.noiseSample())} + sampPsgL, sampPsgR := int32(0), int32(0) + + cntl := uint16(g._getRAM(ram.SOUNDCNT_L)) // snd_psg_vol + for i := 0; i < 4; i++ { + if util.Bit(cntl, 12+i) { + sampPsgL = int32(g.clip(sampPsgL + int32(sampCh[i]))) + } + } + for i := 0; i < 4; i++ { + if util.Bit(cntl, 8+i) { + sampPsgR = int32(g.clip(sampPsgR + int32(sampCh[i]))) + } + } + + sampPsgL *= psgVolLut[(cntl>>4)&7] + sampPsgR *= psgVolLut[(cntl>>0)&7] + + sampPsgL >>= psgRshLut[(cnth>>0)&3] + sampPsgR >>= psgRshLut[(cnth>>0)&3] + + sndBuffer[sndCurWrite&BUFF_SAMPLES_MSK] = g.clip(sampPsgL + int32(sampPcmL)) + sndCurWrite++ + sndBuffer[sndCurWrite&BUFF_SAMPLES_MSK] = g.clip(sampPsgR + int32(sampPcmR)) + sndCurWrite++ + + sndCycles -= SAMP_CYCLES + } +} + +func (g *GBA) _resetSoundChan(ch int, enable bool) { + if enable { + g.apu.chans[ch] = &SoundChan{ + phase: false, + samples: 0, + lengthTime: 0, + sweepTime: 0, + envTime: 0, + } + + if ch == 2 { + g.waveReset() + } + + if ch == 3 { + if util.Bit(g._getRAM(ram.SOUND4CNT_H), 3) { + g.apu.chans[3].lfsr = 0x007f + } else { + g.apu.chans[3].lfsr = 0x7fff + } + } + + g.enableSoundChan(ch, true) + } +} diff --git a/pkg/gba/debug.go b/pkg/gba/debug.go index 5d632cd..c3aa09b 100644 --- a/pkg/gba/debug.go +++ b/pkg/gba/debug.go @@ -4,6 +4,8 @@ import ( "fmt" "mettaur/pkg/ram" "mettaur/pkg/util" + "os" + "runtime" ) const ( @@ -331,3 +333,18 @@ func (ih IRQHistory) String() string { } return fmt.Sprintf("IRQ(%s): 0x%08x -> 0x%08x on %s", ih.irq, ih.start, ih.returnTo, mode) } + +func (g *GBA) PanicHandler(stack bool) { + if err := recover(); err != nil { + fmt.Fprintf(os.Stderr, "crash in emulation: %s in 0x%08x\n", err, g.PC()) + for depth := 0; ; depth++ { + _, file, line, ok := runtime.Caller(depth) + if !ok { + break + } + fmt.Printf("======> %d: %v:%d\n", depth, file, line) + } + g.Exit("") + panic("") + } +} diff --git a/pkg/gba/dma.go b/pkg/gba/dma.go index 9176342..ba08512 100644 --- a/pkg/gba/dma.go +++ b/pkg/gba/dma.go @@ -21,6 +21,12 @@ func NewDMA() [4]*DMA { return [4]*DMA{&DMA{}, &DMA{}, &DMA{}, &DMA{}} } func (ch *DMA) src() uint32 { return util.LE32(ch.io[:]) } func (ch *DMA) dst() uint32 { return util.LE32(ch.io[4:]) } func (ch *DMA) cnt() uint32 { return util.LE32(ch.io[8:]) } +func (ch *DMA) setSrc(v uint32) { + ch.io[0] = byte(v) + ch.io[1] = byte(v >> 8) + ch.io[2] = byte(v >> 16) + ch.io[3] = byte(v >> 24) +} func (ch *DMA) setCnt(v uint32) { ch.io[8] = byte(v) ch.io[9] = byte(v >> 8) @@ -131,3 +137,33 @@ func (g *GBA) dmaTransfer(t dmaTiming) { } } } + +func (g *GBA) dmaTransferFifo(ch int) { + if !g.isSoundMasterEnable() || !g.dma[ch].enabled() || g.dma[ch].timing() != dmaSpecial { + return + } + + // 32bit × 4 = 4 words + dst := g.dma[ch].dst() + for i := 0; i < 4; i++ { + src := g.dma[ch].src() + g.setRAM32(dst, g.getRAM32(src, true), true) + + if ch == 1 { + g.fifoACopy() + } else { + g.fifoBCopy() + } + + switch (g.dma[ch].cnt() >> (16 + 7)) & 0b11 { + case 0: + g.dma[ch].setSrc(src + 4) + case 1: + g.dma[ch].setSrc(src - 4) + } + } + + if g.dma[ch].irq() { + g.triggerIRQ(IRQID(irqDMA0 + ch)) + } +} diff --git a/pkg/gba/gba.go b/pkg/gba/gba.go index 8262dc5..1ea0443 100644 --- a/pkg/gba/gba.go +++ b/pkg/gba/gba.go @@ -7,7 +7,6 @@ import ( "mettaur/pkg/gpu" "mettaur/pkg/joypad" "mettaur/pkg/ram" - "mettaur/pkg/timer" "mettaur/pkg/util" ) @@ -53,10 +52,11 @@ type GBA struct { line int halt bool pipe Pipe - timers timer.Timers + timers Timers dma [4]*DMA joypad joypad.Joypad DoSav bool + apu *APU } type Pipe struct { @@ -77,8 +77,11 @@ func New(src []byte) *GBA { CartHeader: cart.New(src), RAM: *ram.New(src), dma: NewDMA(), + apu: newAPU(), + timers: newTimers(), } g._setRAM16(ram.KEYINPUT, 0x3ff) + g.playSound() return g } @@ -87,6 +90,7 @@ func (g *GBA) Exit(s string) { if debug { g.PrintHistory() } + g.exitAPU() panic("") } @@ -185,6 +189,7 @@ func (g *GBA) Update() { g.joypad.Read() } + g.soundBufferWrap() g.Frame++ } @@ -202,6 +207,7 @@ func (g *GBA) scanline() { g.GPU.SetHBlank(true) g.dmaTransfer(dmaHBlank) g.exec(1232 - 1006) + g.soundClock(1232) g.GPU.SetHBlank(false) vCount := g.GPU.IncrementVCount() diff --git a/pkg/gba/io.go b/pkg/gba/io.go index 8920b63..5fa0f40 100644 --- a/pkg/gba/io.go +++ b/pkg/gba/io.go @@ -4,7 +4,6 @@ import ( "fmt" "mettaur/pkg/gpu" "mettaur/pkg/ram" - "mettaur/pkg/timer" "mettaur/pkg/util" "strings" ) @@ -13,6 +12,10 @@ func (g *GBA) _getRAM(addr uint32) uint32 { switch { case gpu.IsIO(addr): return util.LE32(g.GPU.IO[(addr - 0x0400_0000):]) + case isWaveRAM(addr): + bank := (g._getRAM(ram.SOUND3CNT_L) >> 2) & 0x10 + idx := (bank ^ 0x10) | (addr & 0xf) + return util.LE32(waveRAM[idx:]) case isDMA0IO(addr): return g.dma[0].get(addr - 0x0400_00b0) case isDMA1IO(addr): @@ -21,7 +24,7 @@ func (g *GBA) _getRAM(addr uint32) uint32 { return g.dma[2].get(addr - 0x0400_00c8) case isDMA3IO(addr): return g.dma[3].get(addr - 0x0400_00d4) - case timer.IsIO(addr): + case IsTimerIO(addr): return g.timers.GetIO(addr - 0x0400_0100) case addr == ram.KEYINPUT || addr == ram.KEYINPUT+1: return util.LE32(g.joypad.Input[addr-ram.KEYINPUT:]) @@ -55,6 +58,10 @@ func (g *GBA) getRAM8(addr uint32, s bool) byte { func (g *GBA) setRAM32(addr, value uint32, s bool) { g.timer(g.waitBus(addr, 32, s)) + g._setRAM32(addr, value) +} + +func (g *GBA) _setRAM32(addr, value uint32) { b0, b1, b2, b3 := value&0xff, (value>>8)&0xff, (value>>16)&0xff, (value>>24)&0xff g._setRAM8(addr, byte(b0)) g._setRAM8(addr+1, byte(b1)) @@ -93,6 +100,38 @@ func (g *GBA) _setRAM8(addr uint32, b byte) { switch { case gpu.IsIO(addr): g.GPU.IO[addr-0x0400_0000] = b + case isSoundIO(addr): + if util.Bit(byte(g._getRAM(ram.SOUNDCNT_X)), 7) { + g.RAM.Set8(addr, b) + if isResetSoundChan(addr) { + g.resetSoundChan(addr, b) + } + } + case addr == ram.SOUNDCNT_H+1: + if util.Bit(b, 3) { + fifoA = [32]int8{} + fifoALen = 0 + } + g.RAM.Set8(addr, b) + case addr == ram.SOUNDCNT_H+3: + if util.Bit(b, 7) { + fifoB = [32]int8{} + fifoBLen = 0 + } + g.RAM.Set8(addr, b) + case addr == ram.SOUNDCNT_X: + old := byte(g._getRAM(addr)) + old = (old & 0xf) | (b & 0xf0) + g.RAM.Set8(addr, old) + if !util.Bit(b, 7) { + for i := uint32(0x4000060); i <= 0x4000081; i++ { + g.RAM.IO[ram.IOOffset(i)] = 0 + } + } + case isWaveRAM(addr): + bank := (g._getRAM(ram.SOUND3CNT_L) >> 2) & 0x10 + idx := (bank ^ 0x10) | (addr & 0xf) + waveRAM[idx] = b case isDMA0IO(addr): if g.dma[0].set(addr-0x0400_00b0, b) { g.dmaTransfer(dmaImmediate) @@ -109,7 +148,7 @@ func (g *GBA) _setRAM8(addr uint32, b byte) { if g.dma[3].set(addr-0x0400_00d4, b) { g.dmaTransfer(dmaImmediate) } - case timer.IsIO(addr): + case IsTimerIO(addr): g.timers.SetIO(addr-0x0400_0100, b) case addr == ram.KEYINPUT || addr == ram.KEYINPUT+1 || addr == ram.KEYCNT || addr == ram.KEYCNT+1: g.joypad.Input[addr-ram.KEYINPUT] = b diff --git a/pkg/gba/timer.go b/pkg/gba/timer.go index c925437..e564d8e 100644 --- a/pkg/gba/timer.go +++ b/pkg/gba/timer.go @@ -2,6 +2,12 @@ package gba import ( "mettaur/pkg/ram" + "mettaur/pkg/util" +) + +const ( + SoundATimer = 10 + SoundBTimer = 14 ) var ( @@ -76,7 +82,7 @@ func (g *GBA) waitBus(addr uint32, size int, s bool) int { func (g *GBA) timer(cycle int) { for cycle > 0 { g.cycle++ - irqs := g.timers.Tick() + irqs := g.Tick() for i, irq := range irqs { if irq { g.triggerIRQ(IRQID(i + 3)) @@ -85,3 +91,157 @@ func (g *GBA) timer(cycle int) { cycle-- } } + +type Timers [4]*Timer + +func newTimers() Timers { + return Timers{&Timer{}, &Timer{}, &Timer{}, &Timer{}} +} + +type Timer struct { + Count uint16 + Next int // if this value is 0, count up timer.Count + Reload uint16 + Control byte +} + +func (t *Timer) period() int { + switch t.Control & 0b11 { + case 0: + return 1 + case 1: + return 64 + case 2: + return 256 + default: + return 1024 + } +} +func (t *Timer) cascade() bool { return util.Bit(t.Control, 2) } +func (t *Timer) irq() bool { return util.Bit(t.Control, 6) } +func (t *Timer) enable() bool { return util.Bit(t.Control, 7) } +func (t *Timer) increment() bool { + t.Next = t.period() + previous := t.Count + t.Count++ + current := t.Count + return current < previous // if overflow occurs +} +func (t *Timer) overflow() bool { + t.Count = t.Reload + return t.irq() +} + +// IsIO returns true if addr is for Timer IO register. +func IsTimerIO(addr uint32) bool { + return (addr >= 0x0400_0100) && (addr < 0x0400_0110) +} +func (ts *Timers) GetIO(offset uint32) uint32 { + idx := offset / 4 + ofs := offset % 4 + switch ofs { + case 0: + return uint32(ts[idx].Control)<<16 | uint32(ts[idx].Count) + case 1: + return uint32(ts[idx].Count >> 8) + case 2: + return uint32(ts[idx].Control) + case 3: + return 0 + } + return 0 +} + +func (ts *Timers) SetIO(offset uint32, b byte) { + idx := offset / 4 + ofs := offset % 4 + switch ofs { + case 0: + ts[idx].Reload = (ts[idx].Reload & 0xff00) | uint16(b) + case 1: + ts[idx].Reload = (ts[idx].Reload & 0xff) | (uint16(b) << 8) + case 2: + previous := util.Bit(ts[idx].Control, 7) + ts[idx].Control = b + // The reload value is copied into the counter when the timer start bit becomes changed from 0 to 1. + if !previous && util.Bit(ts[idx].Control, 7) { + ts[idx].Count = ts[idx].Reload + } + } +} + +func (g *GBA) Tick() [4]bool { + overflow, irq := false, [4]bool{} + ts := &g.timers + if ts[0].enable() { + if ts[0].Next > 0 { + ts[0].Next-- + } + if ts[0].Next == 0 { + overflow = ts[0].increment() + if overflow { + cnth := uint16(g._getRAM(ram.SOUNDCNT_H)) + if (cnth>>SoundATimer)&1 == 0 { + g.fifoALoad() + if fifoALen <= 0x10 { + g.dmaTransferFifo(1) + } + } + if (cnth>>SoundBTimer)&1 == 0 { + g.fifoBLoad() + if fifoBLen <= 0x10 { + g.dmaTransferFifo(2) + } + } + if ts[0].overflow() { + irq[0] = true + } + } + } + } + + for i := 1; i < 4; i++ { + if !ts[i].enable() { + overflow = false + continue + } + + countUp := false + if ts[i].cascade() { + countUp = overflow + } else { + if ts[i].Next > 0 { + ts[i].Next-- + } + countUp = ts[i].Next == 0 + } + overflow = false + + if countUp { + overflow = ts[i].increment() + if overflow { + if i == 1 { + cnth := uint16(g._getRAM(ram.SOUNDCNT_H)) + if (cnth>>SoundATimer)&1 == 1 { + g.fifoALoad() + if fifoALen <= 0x10 { + g.dmaTransferFifo(1) + } + } + if (cnth>>SoundBTimer)&1 == 1 { + g.fifoBLoad() + if fifoBLen <= 0x10 { + g.dmaTransferFifo(2) + } + } + } + + if ts[i].overflow() { + irq[i] = true + } + } + } + } + + return irq +} diff --git a/pkg/ram/io.go b/pkg/ram/io.go index aba6360..67c55b8 100644 --- a/pkg/ram/io.go +++ b/pkg/ram/io.go @@ -43,6 +43,27 @@ const ( BLDY = base + 0x54 ) +// Sound IO +const ( + SOUND1CNT_L = base + 0x60 + SOUND1CNT_H = base + 0x62 + SOUND1CNT_X = base + 0x64 + SOUND2CNT_L = base + 0x68 + SOUND2CNT_H = base + 0x6c + SOUND3CNT_L = base + 0x70 + SOUND3CNT_H = base + 0x72 + SOUND3CNT_X = base + 0x74 + SOUND4CNT_L = base + 0x78 + SOUND4CNT_H = base + 0x7c + SOUNDCNT_L = base + 0x80 + SOUNDCNT_H = base + 0x82 + SOUNDCNT_X = base + 0x84 + SOUNDBIAS = base + 0x88 + WAVE_RAM = base + 0x90 + FIFO_A = base + 0xa0 + FIFO_B = base + 0xa4 +) + // DMA Transfer const ( DMA0SAD = base + 0xb0 diff --git a/pkg/timer/tick.go b/pkg/timer/tick.go deleted file mode 100644 index e367a08..0000000 --- a/pkg/timer/tick.go +++ /dev/null @@ -1,38 +0,0 @@ -package timer - -func (ts *Timers) Tick() [4]bool { - overflow, irq := false, [4]bool{} - if ts[0].enable() { - ts[0].Next-- - if ts[0].Next == 0 { - overflow = ts[0].increment() - if overflow && ts[0].overflow() { - irq[0] = true - } - } - } - - for i := 1; i < 4; i++ { - if !ts[i].enable() { - continue - } - - countUp := false - if ts[i].cascade() { - countUp = overflow - overflow = false - } else { - ts[i].Next-- - countUp = ts[1].Next == 0 - } - - if countUp { - overflow = ts[i].increment() - if overflow && ts[i].overflow() { - irq[i] = true - } - } - } - - return irq -} diff --git a/pkg/timer/timer.go b/pkg/timer/timer.go deleted file mode 100644 index a52fc06..0000000 --- a/pkg/timer/timer.go +++ /dev/null @@ -1,89 +0,0 @@ -package timer - -import "mettaur/pkg/util" - -// IO offset -const ( - TM0CNT_L = 0x0 - TM0CNT_H = 0x2 - TM1CNT_L = 0x4 - TM1CNT_H = 0x6 - TM2CNT_L = 0x8 - TM2CNT_H = 0xa - TM3CNT_L = 0xc - TM3CNT_H = 0xe -) - -type Timers [4]Timer - -type Timer struct { - Count uint16 - Next int // if this value is 0, count up timer.Count - Reload uint16 - Control byte -} - -func (t *Timer) period() int { - switch t.Control & 0b11 { - case 0: - return 1 - case 1: - return 64 - case 2: - return 256 - default: - return 1024 - } -} -func (t *Timer) cascade() bool { return util.Bit(t.Control, 2) } -func (t *Timer) irq() bool { return util.Bit(t.Control, 6) } -func (t *Timer) enable() bool { return util.Bit(t.Control, 7) } -func (t *Timer) increment() bool { - t.Next = t.period() - previous := t.Count - t.Count++ - current := t.Count - return current < previous // if overflow occurs -} -func (t *Timer) overflow() bool { - t.Count = t.Reload - return t.irq() -} - -// IsIO returns true if addr is for Timer IO register. -func IsIO(addr uint32) bool { - return (addr >= 0x0400_0100) && (addr < 0x0400_0110) -} -func (ts *Timers) GetIO(offset uint32) uint32 { - idx := offset / 4 - ofs := offset % 4 - switch ofs { - case 0: - return uint32(ts[idx].Count) - case 1: - return uint32(ts[idx].Count >> 8) - case 2: - return uint32(ts[idx].Control) - case 3: - return 0 - } - return 0 -} - -func (ts *Timers) SetIO(offset uint32, b byte) { - idx := offset / 4 - ofs := offset % 4 - switch ofs { - case 0: - ts[idx].Reload = (ts[idx].Reload & 0xff00) | uint16(b) - case 1: - ts[idx].Reload = (ts[idx].Reload & 0x00ff) | (uint16(b) << 8) - case 2: - previous := util.Bit(ts[idx].Control, 7) - ts[idx].Control = b - // The reload value is copied into the counter when the timer start bit becomes changed from 0 to 1. - if !previous && util.Bit(ts[idx].Control, 7) { - ts[idx].Count = ts[idx].Reload - } - } -}