rubyboy 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +9 -0
- data/CHANGELOG.md +15 -0
- data/README.md +5 -1
- data/exe/rubyboy +2 -1
- data/lib/rubyboy/apu.rb +118 -0
- data/lib/rubyboy/apu_channels/channel1.rb +154 -0
- data/lib/rubyboy/apu_channels/channel2.rb +116 -0
- data/lib/rubyboy/apu_channels/channel3.rb +117 -0
- data/lib/rubyboy/apu_channels/channel4.rb +148 -0
- data/lib/rubyboy/audio.rb +33 -0
- data/lib/rubyboy/bus.rb +65 -121
- data/lib/rubyboy/cartridge/factory.rb +1 -1
- data/lib/rubyboy/cartridge/mbc1.rb +52 -38
- data/lib/rubyboy/cpu.rb +701 -677
- data/lib/rubyboy/lcd.rb +30 -19
- data/lib/rubyboy/ppu.rb +16 -4
- data/lib/rubyboy/raylib/audio.rb +32 -0
- data/lib/rubyboy/raylib/lcd.rb +40 -0
- data/lib/rubyboy/raylib/raylib_loader.rb +36 -0
- data/lib/rubyboy/registers.rb +67 -61
- data/lib/rubyboy/sdl.rb +71 -0
- data/lib/rubyboy/version.rb +1 -1
- data/lib/rubyboy.rb +23 -41
- data/resource/logo/logo.png +0 -0
- data/resource/logo/logo.svg +1 -0
- metadata +23 -16
- data/lib/rubyboy/operand/direct16.rb +0 -13
- data/lib/rubyboy/operand/direct8.rb +0 -13
- data/lib/rubyboy/operand/hl_dec.rb +0 -11
- data/lib/rubyboy/operand/hl_inc.rb +0 -11
- data/lib/rubyboy/operand/immediate16.rb +0 -13
- data/lib/rubyboy/operand/immediate8.rb +0 -13
- data/lib/rubyboy/operand/indirect.rb +0 -13
- data/lib/rubyboy/operand/register16.rb +0 -13
- data/lib/rubyboy/operand/register8.rb +0 -13
- data/lib/rubyboy/operand.rb +0 -12
- data/lib/rubyboy/register.rb +0 -24
@@ -0,0 +1,148 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rubyboy
|
4
|
+
module ApuChannels
|
5
|
+
class Channel4
|
6
|
+
attr_accessor :enabled, :wave_duty_position
|
7
|
+
|
8
|
+
WAVE_DUTY = [
|
9
|
+
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], # 12.5%
|
10
|
+
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0], # 25%
|
11
|
+
[0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0], # 50%
|
12
|
+
[0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] # 75%
|
13
|
+
].freeze
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@cycles = 0
|
17
|
+
@frequency = 0
|
18
|
+
@frequency_timer = 0
|
19
|
+
@wave_duty_position = 0
|
20
|
+
|
21
|
+
@enabled = false
|
22
|
+
@dac_enabled = false
|
23
|
+
@length_enabled = false
|
24
|
+
@is_upwards = false
|
25
|
+
@is_decrementing = false
|
26
|
+
@sweep_enabled = false
|
27
|
+
@sweep_period = 0
|
28
|
+
@sweep_shift = 0
|
29
|
+
@period = 0
|
30
|
+
@period_timer = 0
|
31
|
+
@current_volume = 0
|
32
|
+
@initial_volume = 0
|
33
|
+
@shadow_frequency = 0
|
34
|
+
@sweep_timer = 0
|
35
|
+
@length_timer = 0
|
36
|
+
@wave_duty_pattern = 0
|
37
|
+
|
38
|
+
@lfsr = 0x7fff
|
39
|
+
@width_mode = false
|
40
|
+
@shift_amount = 0
|
41
|
+
@divisor_code = 0
|
42
|
+
end
|
43
|
+
|
44
|
+
def step(cycles)
|
45
|
+
@cycles += cycles
|
46
|
+
|
47
|
+
return if @cycles < @frequency_timer
|
48
|
+
|
49
|
+
@cycles -= @frequency_timer
|
50
|
+
@frequency_timer = [8, @divisor_code << 4].max << @shift_amount
|
51
|
+
|
52
|
+
xor = (@lfsr & 0x01) ^ ((@lfsr & 0b10) >> 1)
|
53
|
+
@lfsr = (@lfsr >> 1) | (xor << 14)
|
54
|
+
return unless @width_mode
|
55
|
+
|
56
|
+
@lfsr &= ~(1 << 6)
|
57
|
+
@lfsr |= xor << 6
|
58
|
+
end
|
59
|
+
|
60
|
+
def step_fs(fs)
|
61
|
+
length if fs & 0x01 == 0
|
62
|
+
envelope if fs == 7
|
63
|
+
end
|
64
|
+
|
65
|
+
def length
|
66
|
+
return unless @length_enabled && @length_timer > 0
|
67
|
+
|
68
|
+
@length_timer -= 1
|
69
|
+
@enabled &= @length_timer > 0
|
70
|
+
end
|
71
|
+
|
72
|
+
def envelope
|
73
|
+
return if @period == 0
|
74
|
+
|
75
|
+
@period_timer -= 1 if @period_timer > 0
|
76
|
+
|
77
|
+
return if @period_timer != 0
|
78
|
+
|
79
|
+
@period_timer = @period
|
80
|
+
|
81
|
+
if @current_volume < 15 && @is_upwards
|
82
|
+
@current_volume += 1
|
83
|
+
elsif @current_volume > 0 && !@is_upwards
|
84
|
+
@current_volume -= 1
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def calculate_frequency
|
89
|
+
if @is_decrementing
|
90
|
+
if @shadow_frequency >= (@shadow_frequency >> @sweep_shift)
|
91
|
+
@shadow_frequency - (@shadow_frequency >> @sweep_shift)
|
92
|
+
else
|
93
|
+
0
|
94
|
+
end
|
95
|
+
else
|
96
|
+
[0x3ff, @shadow_frequency + (@shadow_frequency >> @sweep_shift)].min
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def dac_output
|
101
|
+
return 0.0 unless @dac_enabled && @enabled
|
102
|
+
|
103
|
+
ret = (@lfsr & 0x01) * @current_volume
|
104
|
+
(ret / 7.5) - 1.0
|
105
|
+
end
|
106
|
+
|
107
|
+
def read_nr4x(x)
|
108
|
+
case x
|
109
|
+
when 0 then 0xff
|
110
|
+
when 1 then 0xff
|
111
|
+
when 2 then (@initial_volume << 4) | (@is_upwards ? 0x08 : 0x00) | @period
|
112
|
+
when 3 then (@shift_amount << 4) | (@width_mode ? 0x08 : 0x00) | @divisor_code
|
113
|
+
when 4 then (@length_enabled ? 0x40 : 0x00) | 0xbf
|
114
|
+
else 0xff
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def write_nr4x(x, val)
|
119
|
+
case x
|
120
|
+
when 0
|
121
|
+
# nop
|
122
|
+
when 1
|
123
|
+
@length_timer = 64 - (val & 0x3f)
|
124
|
+
when 2
|
125
|
+
@is_upwards = (val & 0x08) > 0
|
126
|
+
@initial_volume = val >> 4
|
127
|
+
@period = val & 0x07
|
128
|
+
@dac_enabled = val & 0xf8 > 0
|
129
|
+
@enabled &= @dac_enabled
|
130
|
+
when 3
|
131
|
+
@shift_amount = (val >> 4) & 0x0f
|
132
|
+
@width_mode = (val & 0x08) > 0
|
133
|
+
@divisor_code = val & 0x07
|
134
|
+
when 4
|
135
|
+
@length_enabled = (val & 0x40) > 0
|
136
|
+
@length_timer = 64 if @length_timer == 0
|
137
|
+
return unless (val & 0x80) > 0
|
138
|
+
|
139
|
+
@enabled = true if @dac_enabled
|
140
|
+
|
141
|
+
@lfsr = 0x7fff
|
142
|
+
@period_timer = @period
|
143
|
+
@current_volume = @initial_volume
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rubyboy/sdl'
|
4
|
+
|
5
|
+
module Rubyboy
|
6
|
+
class Audio
|
7
|
+
SAMPLE_RATE = 48000
|
8
|
+
SAMPLES = 512
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
SDL.InitSubSystem(SDL::INIT_AUDIO)
|
12
|
+
|
13
|
+
desired = SDL::AudioSpec.new
|
14
|
+
desired[:freq] = SAMPLE_RATE
|
15
|
+
desired[:format] = SDL::AUDIO_F32SYS
|
16
|
+
desired[:channels] = 2
|
17
|
+
desired[:samples] = SAMPLES * 2
|
18
|
+
|
19
|
+
@device = SDL.OpenAudioDevice(nil, 0, desired, nil, 0)
|
20
|
+
|
21
|
+
SDL.PauseAudioDevice(@device, 0)
|
22
|
+
end
|
23
|
+
|
24
|
+
def queue(buffer)
|
25
|
+
sleep(0.001) while SDL.GetQueuedAudioSize(@device) > 8192
|
26
|
+
|
27
|
+
buf_ptr = FFI::MemoryPointer.new(:float, buffer.size)
|
28
|
+
buf_ptr.put_array_of_float(0, buffer)
|
29
|
+
|
30
|
+
SDL.QueueAudio(@device, buf_ptr, buffer.size * buf_ptr.type_size)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/rubyboy/bus.rb
CHANGED
@@ -1,140 +1,84 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'cartridge/factory'
|
4
|
-
|
5
3
|
module Rubyboy
|
6
4
|
class Bus
|
7
|
-
def initialize(ppu, rom, timer, interrupt, joypad)
|
5
|
+
def initialize(ppu, rom, ram, mbc, timer, interrupt, joypad, apu)
|
8
6
|
@ppu = ppu
|
9
7
|
@rom = rom
|
10
|
-
@ram =
|
11
|
-
@mbc =
|
8
|
+
@ram = ram
|
9
|
+
@mbc = mbc
|
12
10
|
@joypad = joypad
|
11
|
+
@apu = apu
|
13
12
|
@interrupt = interrupt
|
14
13
|
@timer = timer
|
15
14
|
|
16
|
-
@
|
15
|
+
@read_methods = Array.new(0x10000)
|
16
|
+
@write_methods = Array.new(0x10000)
|
17
|
+
|
18
|
+
set_methods
|
17
19
|
end
|
18
20
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
@ram.hram[addr - 0xff80]
|
72
|
-
when 0xffff
|
73
|
-
@interrupt.read_byte(addr)
|
74
|
-
else
|
75
|
-
0xff
|
21
|
+
def set_methods
|
22
|
+
0x10000.times do |addr|
|
23
|
+
case addr
|
24
|
+
when 0x0000..0x7fff
|
25
|
+
@read_methods[addr] = -> { @mbc.read_byte(addr) }
|
26
|
+
@write_methods[addr] = ->(value) { @mbc.write_byte(addr, value) }
|
27
|
+
when 0x8000..0x9fff
|
28
|
+
@read_methods[addr] = -> { @ppu.read_byte(addr) }
|
29
|
+
@write_methods[addr] = ->(value) { @ppu.write_byte(addr, value) }
|
30
|
+
when 0xa000..0xbfff
|
31
|
+
@read_methods[addr] = -> { @mbc.read_byte(addr) }
|
32
|
+
@write_methods[addr] = ->(value) { @mbc.write_byte(addr, value) }
|
33
|
+
when 0xc000..0xcfff
|
34
|
+
@read_methods[addr] = -> { @ram.wram1[addr - 0xc000] }
|
35
|
+
@write_methods[addr] = ->(value) { @ram.wram1[addr - 0xc000] = value }
|
36
|
+
when 0xd000..0xdfff
|
37
|
+
@read_methods[addr] = -> { @ram.wram2[addr - 0xd000] }
|
38
|
+
@write_methods[addr] = ->(value) { @ram.wram2[addr - 0xd000] = value }
|
39
|
+
when 0xfe00..0xfe9f
|
40
|
+
@read_methods[addr] = -> { @ppu.read_byte(addr) }
|
41
|
+
@write_methods[addr] = ->(value) { @ppu.write_byte(addr, value) }
|
42
|
+
when 0xff00
|
43
|
+
@read_methods[addr] = -> { @joypad.read_byte(addr) }
|
44
|
+
@write_methods[addr] = ->(value) { @joypad.write_byte(addr, value) }
|
45
|
+
when 0xff04..0xff07
|
46
|
+
@read_methods[addr] = -> { @timer.read_byte(addr) }
|
47
|
+
@write_methods[addr] = ->(value) { @timer.write_byte(addr, value) }
|
48
|
+
when 0xff0f
|
49
|
+
@read_methods[addr] = -> { @interrupt.read_byte(addr) }
|
50
|
+
@write_methods[addr] = ->(value) { @interrupt.write_byte(addr, value) }
|
51
|
+
when 0xff10..0xff26
|
52
|
+
@read_methods[addr] = -> { @apu.read_byte(addr) }
|
53
|
+
@write_methods[addr] = ->(value) { @apu.write_byte(addr, value) }
|
54
|
+
when 0xff30..0xff3f
|
55
|
+
@read_methods[addr] = -> { @apu.read_byte(addr) }
|
56
|
+
@write_methods[addr] = ->(value) { @apu.write_byte(addr, value) }
|
57
|
+
when 0xff46
|
58
|
+
@read_methods[addr] = -> { @ppu.read_byte(addr) }
|
59
|
+
@write_methods[addr] = ->(value) { 0xa0.times { |i| write_byte(0xfe00 + i, read_byte((value << 8) + i)) } }
|
60
|
+
when 0xff40..0xff4b
|
61
|
+
@read_methods[addr] = -> { @ppu.read_byte(addr) }
|
62
|
+
@write_methods[addr] = ->(value) { @ppu.write_byte(addr, value) }
|
63
|
+
when 0xff80..0xfffe
|
64
|
+
@read_methods[addr] = -> { @ram.hram[addr - 0xff80] }
|
65
|
+
@write_methods[addr] = ->(value) { @ram.hram[addr - 0xff80] = value }
|
66
|
+
when 0xffff
|
67
|
+
@read_methods[addr] = -> { @interrupt.read_byte(addr) }
|
68
|
+
@write_methods[addr] = ->(value) { @interrupt.write_byte(addr, value) }
|
69
|
+
else
|
70
|
+
@read_methods[addr] = -> { 0xff }
|
71
|
+
@write_methods[addr] = ->(_value) {}
|
72
|
+
end
|
76
73
|
end
|
77
74
|
end
|
78
75
|
|
76
|
+
def read_byte(addr)
|
77
|
+
@read_methods[addr].call
|
78
|
+
end
|
79
|
+
|
79
80
|
def write_byte(addr, value)
|
80
|
-
|
81
|
-
when 0x0000..0x7fff
|
82
|
-
@mbc.write_byte(addr, value)
|
83
|
-
when 0x8000..0x9fff
|
84
|
-
@ppu.write_byte(addr, value)
|
85
|
-
when 0xa000..0xbfff
|
86
|
-
@mbc.write_byte(addr, value)
|
87
|
-
when 0xc000..0xcfff
|
88
|
-
@ram.wram1[addr - 0xc000] = value
|
89
|
-
when 0xd000..0xdfff
|
90
|
-
@ram.wram2[addr - 0xd000] = value
|
91
|
-
when 0xe000..0xfdff
|
92
|
-
# echo ram
|
93
|
-
when 0xfe00..0xfe9f
|
94
|
-
@ppu.write_byte(addr, value)
|
95
|
-
when 0xfea0..0xfeff
|
96
|
-
# unused
|
97
|
-
when 0xff00
|
98
|
-
@joypad.write_byte(addr, value)
|
99
|
-
when 0xff01..0xff02
|
100
|
-
# serial
|
101
|
-
@tmp[addr] = value
|
102
|
-
when 0xff04..0xff07
|
103
|
-
@timer.write_byte(addr, value)
|
104
|
-
when 0xff0f
|
105
|
-
@interrupt.write_byte(addr, value)
|
106
|
-
when 0xff10..0xff26
|
107
|
-
# sound
|
108
|
-
@tmp[addr] = value
|
109
|
-
when 0xff30..0xff3f
|
110
|
-
# wave pattern ram
|
111
|
-
@tmp[addr] = value
|
112
|
-
when 0xff46
|
113
|
-
0xa0.times do |i|
|
114
|
-
write_byte(0xfe00 + i, read_byte((value << 8) + i))
|
115
|
-
end
|
116
|
-
when 0xff40..0xff4b
|
117
|
-
@ppu.write_byte(addr, value)
|
118
|
-
when 0xff4f
|
119
|
-
# vbk
|
120
|
-
@tmp[addr] = value
|
121
|
-
when 0xff50
|
122
|
-
# boot rom
|
123
|
-
@tmp[addr] = value
|
124
|
-
when 0xff51..0xff55
|
125
|
-
# hdma
|
126
|
-
@tmp[addr] = value
|
127
|
-
when 0xff68..0xff6b
|
128
|
-
# bgp
|
129
|
-
@tmp[addr] = value
|
130
|
-
when 0xff70
|
131
|
-
# svbk
|
132
|
-
@tmp[addr] = value
|
133
|
-
when 0xff80..0xfffe
|
134
|
-
@ram.hram[addr - 0xff80] = value
|
135
|
-
when 0xffff
|
136
|
-
@interrupt.write_byte(addr, value)
|
137
|
-
end
|
81
|
+
@write_methods[addr].call(value)
|
138
82
|
end
|
139
83
|
|
140
84
|
def read_word(addr)
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '../ram'
|
4
|
-
|
5
3
|
module Rubyboy
|
6
4
|
module Cartridge
|
7
5
|
class Mbc1
|
@@ -12,50 +10,66 @@ module Rubyboy
|
|
12
10
|
@ram_bank = 0
|
13
11
|
@ram_enable = false
|
14
12
|
@ram_banking_mode = false
|
13
|
+
|
14
|
+
@read_methods = Array.new(0xc000)
|
15
|
+
@write_methods = Array.new(0xc000)
|
16
|
+
|
17
|
+
set_methods
|
15
18
|
end
|
16
19
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
20
|
+
def set_methods
|
21
|
+
0xc000.times do |addr|
|
22
|
+
@read_methods[addr] =
|
23
|
+
case addr
|
24
|
+
when 0x0000..0x3fff then -> { @rom.data[addr] }
|
25
|
+
when 0x4000..0x7fff then -> { @rom.data[addr + (@rom_bank - 1) * 0x4000] }
|
26
|
+
when 0xa000..0xbfff
|
27
|
+
lambda do
|
28
|
+
if @ram_enable
|
29
|
+
if @ram_banking_mode
|
30
|
+
@ram.eram[addr - 0xa000 + @ram_bank * 0x800]
|
31
|
+
else
|
32
|
+
@ram.eram[addr - 0xa000]
|
33
|
+
end
|
34
|
+
else
|
35
|
+
0xff
|
36
|
+
end
|
37
|
+
end
|
29
38
|
end
|
30
|
-
else
|
31
|
-
0xff
|
32
|
-
end
|
33
|
-
else
|
34
|
-
raise "not implemented: read_byte #{addr}"
|
35
39
|
end
|
36
|
-
end
|
37
40
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
41
|
+
0xc000.times do |addr|
|
42
|
+
@write_methods[addr] =
|
43
|
+
case addr
|
44
|
+
when 0x0000..0x1fff then ->(value) { @ram_enable = value & 0x0f == 0x0a }
|
45
|
+
when 0x2000..0x3fff
|
46
|
+
lambda do |value|
|
47
|
+
@rom_bank = value & 0x1f
|
48
|
+
@rom_bank = 1 if @rom_bank == 0
|
49
|
+
end
|
50
|
+
when 0x4000..0x5fff then ->(value) { @ram_bank = value & 0x03 }
|
51
|
+
when 0x6000..0x7fff then ->(value) { @ram_banking_mode = value & 0x01 == 0x01 }
|
52
|
+
when 0xa000..0xbfff
|
53
|
+
lambda do |value|
|
54
|
+
if @ram_enable
|
55
|
+
if @ram_banking_mode
|
56
|
+
@ram.eram[addr - 0xa000 + @ram_bank * 0x800] = value
|
57
|
+
else
|
58
|
+
@ram.eram[addr - 0xa000] = value
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
55
62
|
end
|
56
|
-
end
|
57
63
|
end
|
58
64
|
end
|
65
|
+
|
66
|
+
def read_byte(addr)
|
67
|
+
@read_methods[addr].call
|
68
|
+
end
|
69
|
+
|
70
|
+
def write_byte(addr, value)
|
71
|
+
@write_methods[addr].call(value)
|
72
|
+
end
|
59
73
|
end
|
60
74
|
end
|
61
75
|
end
|