Skip to content

Commit

Permalink
apu serialize
Browse files Browse the repository at this point in the history
  • Loading branch information
akatsuki105 committed Jun 27, 2024
1 parent 604d7d8 commit 3862898
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 71 deletions.
49 changes: 40 additions & 9 deletions core/gb/apu/apu.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package apu

import (
"encoding/binary"
"io"
)

const (
MODEL_GB = iota
MODEL_GBA
Expand All @@ -15,11 +20,14 @@ type APU interface {
Write(addr uint16, val uint8)

Sample() (lsample, rsample uint8)

Serialize(s io.Writer)
Deserialize(s io.Reader)
}

type apu struct {
enabled bool
model int
model uint8

ch1, ch2 *square
ch3 *wave
Expand All @@ -28,15 +36,13 @@ type apu struct {
sequencerCounter int64 // (フレームシーケンサの)512Hzを生み出すためのカウンタ (ref: https://gbdev.io/pandocs/Audio_details.html#div-apu)
sequencerStep int64 // 512Hzから 64, 128, 256Hzなどの生み出すためのカウンタ

sampleTimer int64 // 1サンプルを生み出すために44100Hzを生み出すためのカウンタ

ioreg [0x30]uint8
volume [2]int // NR50(Left, Right)
volume [2]uint8 // NR50(Left, Right)
}

func New(model int) APU {
return &apu{
model: model,
model: uint8(model),
}
}

Expand All @@ -48,8 +54,7 @@ func (a *apu) Reset(hasBIOS bool) {
a.ch4 = newNoiseChannel()
a.sequencerCounter = 0
a.sequencerStep = 0
a.sampleTimer = 0
a.volume = [2]int{7, 7}
a.volume = [2]uint8{7, 7}
a.ioreg = [0x30]uint8{}
if !hasBIOS {
a.skipBIOS()
Expand Down Expand Up @@ -118,7 +123,33 @@ func (a *apu) Step() {

func (a *apu) Sample() (lsample, rsample uint8) {
sample := (a.ch1.getOutput() + a.ch2.getOutput() + a.ch3.getOutput() + a.ch4.getOutput()) // 各チャンネルの出力(音量=波)を足し合わせたものがサンプル
left := uint8((sample * a.volume[0]) / 7)
right := uint8((sample * a.volume[1]) / 7)
left := uint8((sample * int(a.volume[0])) / 7)
right := uint8((sample * int(a.volume[1])) / 7)
return left, right
}

func (a *apu) Serialize(s io.Writer) {
binary.Write(s, binary.LittleEndian, a.enabled)
binary.Write(s, binary.LittleEndian, a.model)
a.ch1.serialize(s)
a.ch2.serialize(s)
a.ch3.serialize(s)
a.ch4.serialize(s)
binary.Write(s, binary.LittleEndian, a.sequencerCounter)
binary.Write(s, binary.LittleEndian, a.sequencerStep)
binary.Write(s, binary.LittleEndian, a.ioreg)
binary.Write(s, binary.LittleEndian, a.volume)
}

func (a *apu) Deserialize(s io.Reader) {
binary.Read(s, binary.LittleEndian, &a.enabled)
binary.Read(s, binary.LittleEndian, &a.model)
a.ch1.deserialize(s)
a.ch2.deserialize(s)
a.ch3.deserialize(s)
a.ch4.deserialize(s)
binary.Read(s, binary.LittleEndian, &a.sequencerCounter)
binary.Read(s, binary.LittleEndian, &a.sequencerStep)
binary.Read(s, binary.LittleEndian, &a.ioreg)
binary.Read(s, binary.LittleEndian, &a.volume)
}
47 changes: 39 additions & 8 deletions core/gb/apu/ch_noise.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
package apu

import (
"encoding/binary"
"io"
)

type noise struct {
enabled bool
ignored bool // Ignore sample output

length int // 音の残り再生時間
length int32 // 音の残り再生時間
stop bool

envelope *envelope

lfsr uint16 // Noiseの疑似乱数(lfsr: Linear Feedback Shift Register = 疑似乱数生成アルゴリズム)

// この2つでノイズの周波数(疑似乱数の生成頻度)を決める
octave int // ノイズ周波数2(オクターブ指定)
divisor int // ノイズ周波数1(カウント指定)
period int
octave int32 // ノイズ周波数2(オクターブ指定)
divisor int32 // ノイズ周波数1(カウント指定)
period int32

width int
width int32

output int
}
Expand Down Expand Up @@ -58,7 +63,7 @@ func (ch *noise) clockTimer() {
}

if (ch.lfsr & 1) == 0 {
result = ch.envelope.volume
result = int(ch.envelope.volume)
}

if !ch.enabled {
Expand All @@ -68,8 +73,8 @@ func (ch *noise) clockTimer() {
ch.output = result
}

func (ch *noise) calcFreqency() int {
freq := 1
func (ch *noise) calcFreqency() int32 {
freq := int32(1)
if ch.divisor != 0 {
freq = 2 * ch.divisor
}
Expand Down Expand Up @@ -98,3 +103,29 @@ func (ch *noise) tryRestart() {
func (ch *noise) dacEnable() bool {
return ((ch.envelope.initialVolume != 0) || ch.envelope.direction)
}

func (ch *noise) serialize(s io.Writer) {
binary.Write(s, binary.LittleEndian, ch.enabled)
binary.Write(s, binary.LittleEndian, ch.ignored)
binary.Write(s, binary.LittleEndian, ch.length)
binary.Write(s, binary.LittleEndian, ch.stop)
ch.envelope.serialize(s)
binary.Write(s, binary.LittleEndian, ch.lfsr)
binary.Write(s, binary.LittleEndian, ch.octave)
binary.Write(s, binary.LittleEndian, ch.divisor)
binary.Write(s, binary.LittleEndian, ch.period)
binary.Write(s, binary.LittleEndian, ch.width)
}

func (ch *noise) deserialize(s io.Reader) {
binary.Read(s, binary.LittleEndian, &ch.enabled)
binary.Read(s, binary.LittleEndian, &ch.ignored)
binary.Read(s, binary.LittleEndian, &ch.length)
binary.Read(s, binary.LittleEndian, &ch.stop)
ch.envelope.deserialize(s)
binary.Read(s, binary.LittleEndian, &ch.lfsr)
binary.Read(s, binary.LittleEndian, &ch.octave)
binary.Read(s, binary.LittleEndian, &ch.divisor)
binary.Read(s, binary.LittleEndian, &ch.period)
binary.Read(s, binary.LittleEndian, &ch.width)
}
51 changes: 43 additions & 8 deletions core/gb/apu/ch_square.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package apu

import (
"encoding/binary"
"io"
)

var squareDutyTable = [4][8]int{
{0, 0, 0, 0, 0, 0, 0, 1}, // 12.5%
{1, 0, 0, 0, 0, 0, 0, 1}, // 25%
Expand All @@ -11,17 +16,17 @@ type square struct {
enabled bool
ignored bool // Ignore sample output

length int // 音の残り再生時間
stop bool // .length が 0 になったときに 音を止めるかどうか(NR14's bit6)
length int32 // 音の残り再生時間
stop bool // .length が 0 になったときに 音を止めるかどうか(NR14's bit6)

envelope *envelope
sweep *sweep

duty int // NR11's bit7-6, (squareDutyTable の index)
dutyCounter int // 0 ~ 7
duty uint8 // NR11's bit7-6, (squareDutyTable の index)
dutyCounter uint8 // 0 ~ 7

period int // GBでは周波数を指定するのではなく、周期の長さを指定する, 実際の周波数は ((4194304/32)/(2048-period)) Hz (64~131072 Hz -> 65536~32 APUサイクル)
freqCounter int
period int32 // GBでは周波数を指定するのではなく、周期の長さを指定する, 実際の周波数は ((4194304/32)/(2048-period)) Hz (64~131072 Hz -> 65536~32 APUサイクル)
freqCounter int32
}

func newSquareChannel(hasSweep bool) *square {
Expand Down Expand Up @@ -73,15 +78,15 @@ func (ch *square) getOutput() int {
if ch.enabled {
dutyTable := (squareDutyTable[ch.duty])[:]
if dutyTable[ch.dutyCounter] != 0 {
return ch.envelope.volume
return int(ch.envelope.volume)
}
}
}
return 0
}

// デューティ比の1ステップの長さをAPUサイクル数で返す
func (ch *square) dutyStepCycle() int {
func (ch *square) dutyStepCycle() int32 {
// hz := (1048576 / (2048 - ch.period)) // freqency
// return 4194304 / hz
return 4 * (2048 - ch.period)
Expand All @@ -102,3 +107,33 @@ func (ch *square) tryRestart() {
ch.length = 64
}
}

func (ch *square) serialize(s io.Writer) {
binary.Write(s, binary.LittleEndian, ch.enabled)
binary.Write(s, binary.LittleEndian, ch.ignored)
binary.Write(s, binary.LittleEndian, ch.length)
binary.Write(s, binary.LittleEndian, ch.stop)
ch.envelope.serialize(s)
if ch.sweep != nil {
ch.sweep.serialize(s)
}
binary.Write(s, binary.LittleEndian, ch.duty)
binary.Write(s, binary.LittleEndian, ch.dutyCounter)
binary.Write(s, binary.LittleEndian, ch.period)
binary.Write(s, binary.LittleEndian, ch.freqCounter)
}

func (ch *square) deserialize(s io.Reader) {
binary.Read(s, binary.LittleEndian, &ch.enabled)
binary.Read(s, binary.LittleEndian, &ch.ignored)
binary.Read(s, binary.LittleEndian, &ch.length)
binary.Read(s, binary.LittleEndian, &ch.stop)
ch.envelope.deserialize(s)
if ch.sweep != nil {
ch.sweep.deserialize(s)
}
binary.Read(s, binary.LittleEndian, &ch.duty)
binary.Read(s, binary.LittleEndian, &ch.dutyCounter)
binary.Read(s, binary.LittleEndian, &ch.period)
binary.Read(s, binary.LittleEndian, &ch.freqCounter)
}
59 changes: 48 additions & 11 deletions core/gb/apu/ch_wave.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
package apu

import (
"encoding/binary"
"io"
)

type wave struct {
enabled bool
ignored bool // Ignore sample output

dacEnable bool // NR30's bit7
stop bool // .length が 0 になったときに 音を止めるかどうか(NR34's bit6)
length int // 音の残り再生時間
volume int // NR32's bit6-5 (0: 0%, 1: 100%, 2: 50%, 3: 25%)
dacEnable bool // NR30's bit7
stop bool // .length が 0 になったときに 音を止めるかどうか(NR34's bit6)
length int32 // 音の残り再生時間
volume uint8 // NR32's bit6-5 (0: 0%, 1: 100%, 2: 50%, 3: 25%)

period int // GBでは周波数を指定するのではなく、周期の長さを指定する
freqCounter int
period int32 // GBでは周波数を指定するのではなく、周期の長さを指定する
freqCounter int32

samples [32]uint8 // 4bit sample
window int // 0 ~ 31
window int8 // 0 ~ 31

// For GBA
bank int // 0 or 1 (NR30's bit6)
usedBank int // 現在演奏中のバンク、modeが1の場合は、 .bank の値と必ずしも一致しないので
mode int //  0: 16バイト(32サンプル)を演奏に使い、裏のバンクでは読み書きを行う、 1: 32バイト(64サンプル)を全部演奏に使う
bank uint8 // 0 or 1 (NR30's bit6)
usedBank uint8 // 現在演奏中のバンク、modeが1の場合は、 .bank の値と必ずしも一致しないので
mode uint8 //  0: 16バイト(32サンプル)を演奏に使い、裏のバンクでは読み書きを行う、 1: 32バイト(64サンプル)を全部演奏に使う
}

func newWaveChannel() *wave {
Expand Down Expand Up @@ -64,6 +69,38 @@ func (ch *wave) getOutput() int {
return 0
}

func (ch *wave) windowStepCycle() int {
func (ch *wave) windowStepCycle() int32 {
return 2 * (2048 - ch.period)
}

func (ch *wave) serialize(s io.Writer) {
binary.Write(s, binary.LittleEndian, ch.enabled)
binary.Write(s, binary.LittleEndian, ch.ignored)
binary.Write(s, binary.LittleEndian, ch.dacEnable)
binary.Write(s, binary.LittleEndian, ch.stop)
binary.Write(s, binary.LittleEndian, ch.length)
binary.Write(s, binary.LittleEndian, ch.volume)
binary.Write(s, binary.LittleEndian, ch.period)
binary.Write(s, binary.LittleEndian, ch.freqCounter)
binary.Write(s, binary.LittleEndian, ch.samples)
binary.Write(s, binary.LittleEndian, ch.window)
binary.Write(s, binary.LittleEndian, ch.bank)
binary.Write(s, binary.LittleEndian, ch.usedBank)
binary.Write(s, binary.LittleEndian, ch.mode)
}

func (ch *wave) deserialize(s io.Reader) {
binary.Read(s, binary.LittleEndian, &ch.enabled)
binary.Read(s, binary.LittleEndian, &ch.ignored)
binary.Read(s, binary.LittleEndian, &ch.dacEnable)
binary.Read(s, binary.LittleEndian, &ch.stop)
binary.Read(s, binary.LittleEndian, &ch.length)
binary.Read(s, binary.LittleEndian, &ch.volume)
binary.Read(s, binary.LittleEndian, &ch.period)
binary.Read(s, binary.LittleEndian, &ch.freqCounter)
binary.Read(s, binary.LittleEndian, &ch.samples)
binary.Read(s, binary.LittleEndian, &ch.window)
binary.Read(s, binary.LittleEndian, &ch.bank)
binary.Read(s, binary.LittleEndian, &ch.usedBank)
binary.Read(s, binary.LittleEndian, &ch.mode)
}
Loading

0 comments on commit 3862898

Please sign in to comment.