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.
@@ -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 = Ram.new
11
- @mbc = Cartridge::Factory.create(@rom, @ram)
8
+ @ram = ram
9
+ @mbc = mbc
12
10
  @joypad = joypad
11
+ @apu = apu
13
12
  @interrupt = interrupt
14
13
  @timer = timer
15
14
 
16
- @tmp = {}
15
+ @read_methods = Array.new(0x10000)
16
+ @write_methods = Array.new(0x10000)
17
+
18
+ set_methods
17
19
  end
18
20
 
19
- def read_byte(addr)
20
- case addr
21
- when 0x0000..0x7fff
22
- @mbc.read_byte(addr)
23
- when 0x8000..0x9fff
24
- @ppu.read_byte(addr)
25
- when 0xa000..0xbfff
26
- @mbc.read_byte(addr)
27
- when 0xc000..0xcfff
28
- @ram.wram1[addr - 0xc000]
29
- when 0xd000..0xdfff
30
- @ram.wram2[addr - 0xd000]
31
- when 0xe000..0xfdff
32
- # echo ram
33
- when 0xfe00..0xfe9f
34
- @ppu.read_byte(addr)
35
- when 0xfea0..0xfeff
36
- # unused
37
- 0xff
38
- when 0xff00
39
- @joypad.read_byte(addr)
40
- when 0xff01..0xff02
41
- # serial
42
- @tmp[addr] ||= 0
43
- when 0xff04..0xff07
44
- @timer.read_byte(addr)
45
- when 0xff0f
46
- @interrupt.read_byte(addr)
47
- when 0xff10..0xff26
48
- # sound
49
- @tmp[addr] ||= 0
50
- when 0xff30..0xff3f
51
- # wave pattern ram
52
- @tmp[addr] ||= 0
53
- when 0xff40..0xff4b
54
- @ppu.read_byte(addr)
55
- when 0xff4f
56
- # vbk
57
- @tmp[addr] ||= 0
58
- when 0xff50
59
- # boot rom
60
- @tmp[addr] ||= 0
61
- when 0xff51..0xff55
62
- # hdma
63
- @tmp[addr] ||= 0
64
- when 0xff68..0xff6b
65
- # bgp
66
- @tmp[addr] ||= 0
67
- when 0xff70
68
- # svbk
69
- @tmp[addr] ||= 0
70
- when 0xff80..0xfffe
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
- case addr
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)
@@ -15,7 +15,7 @@ module Rubyboy
15
15
  when 0x08..0x09
16
16
  Nombc.new(rom)
17
17
  else
18
- raise "Unsupported cartridge type: #{cartridge_type}"
18
+ raise "Unsupported cartridge type: #{rom.cartridge_type}"
19
19
  end
20
20
  end
21
21
  end
@@ -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 read_byte(addr)
18
- case addr
19
- when 0x0000..0x3fff
20
- @rom.data[addr]
21
- when 0x4000..0x7fff
22
- @rom.data[addr + (@rom_bank - 1) * 0x4000]
23
- when 0xa000..0xbfff
24
- if @ram_enable
25
- if @ram_banking_mode
26
- @ram.eram[addr - 0xa000 + @ram_bank * 0x800]
27
- else
28
- @ram.eram[addr - 0xa000]
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
- def write_byte(addr, value)
39
- case addr
40
- when 0x0000..0x1fff
41
- @ram_enable = value & 0x0f == 0x0a
42
- when 0x2000..0x3fff
43
- @rom_bank = value & 0x1f
44
- @rom_bank = 1 if @rom_bank == 0
45
- when 0x4000..0x5fff
46
- @ram_bank = value & 0x03
47
- when 0x6000..0x7fff
48
- @ram_banking_mode = value & 0x01 == 0x01
49
- when 0xa000..0xbfff
50
- if @ram_enable
51
- if @ram_banking_mode
52
- @ram.eram[addr - 0xa000 + @ram_bank * 0x800] = value
53
- else
54
- @ram.eram[addr - 0xa000] = value
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