rubyboy 1.1.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f71c49ce8d91b143c346ea5ac3534d531faa1d7ed7fc886243ed77e9e76e31b7
4
- data.tar.gz: c9211303e89f8d775fb8c083a0eb3ab97e2299e41722b6c461bfac0cc9ea94ac
3
+ metadata.gz: f641e78dedf235a5ac8bab34c18fbd624150e5e6c9df92a710d2bc458fab3d62
4
+ data.tar.gz: 7ab941409fcbec7b0eff98241231e002c249f84be16a19c28216e33f91ac14d2
5
5
  SHA512:
6
- metadata.gz: 76374e3846dc6a917a0dda34b71f6c667155cd5458bbb70ab0fba1ae16a8e5482153808e4de64aa8670d958a69ec52625e5f41673a04780d2b063b35c1564606
7
- data.tar.gz: a3354719d960846738a7cb9252afa1a86fa0c3a2a0d3529013808386ab8a5aaa4a08ebc132cefed02353e20c4193f96804620031a79b6ca324c1e01ed5b8f12f
6
+ metadata.gz: f220a9aeff3fa642f334c0248c4f155cf648c53a4bb132801e86a8008039f50d3cb9d3519af07b47e44f5aa2032419ba370e8ddcbb41ffebcb7937e8ed397b92
7
+ data.tar.gz: 63065e706e48de9fc26dcd9f7bb75332876374769db0914c54843c3f60aa1dd53a950fe0f33755539331e8f02b5b71cc0d4cf0bdca31421911d23911454a8108
data/.rubocop.yml CHANGED
@@ -17,9 +17,15 @@ Metrics/AbcSize:
17
17
  Metrics/BlockNesting:
18
18
  Enabled: false
19
19
 
20
+ Metrics/BlockLength:
21
+ Enabled: false
22
+
20
23
  Metrics/CyclomaticComplexity:
21
24
  Enabled: false
22
25
 
26
+ Metrics/ParameterLists:
27
+ Enabled: false
28
+
23
29
  Metrics/PerceivedComplexity:
24
30
  Enabled: false
25
31
 
@@ -38,6 +44,9 @@ Style/Documentation:
38
44
  Style/FormatString:
39
45
  Enabled: false
40
46
 
47
+ Style/MultipleComparison:
48
+ Enabled: false
49
+
41
50
  Style/NumericLiterals:
42
51
  Enabled: false
43
52
 
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.3.0] - 2024-03-09
4
+
5
+ - Add ffi to dependencies
6
+ - Add SDL2 wrapper
7
+ - Add audio
8
+ - Optimize cpu
9
+ - Cache R/W methods
10
+
11
+ ## [1.2.0] - 2024-01-09
12
+
13
+ - Add logo
14
+ - Bump raylib-bindings version to 0.6.0
15
+ - Optimizing
16
+ - Remove unnecessary classes
17
+
3
18
  ## [1.1.0] - 2023-12-28
4
19
 
5
20
  - Add bench option
data/README.md CHANGED
@@ -1,4 +1,8 @@
1
- # Rubyboy
1
+ <br>
2
+ <p align="center">
3
+ <img src="/resource/logo/logo.svg" width="480px">
4
+ </p>
5
+ <br>
2
6
 
3
7
  A Game Boy emulator written in Ruby
4
8
 
data/exe/rubyboy CHANGED
@@ -6,7 +6,8 @@ require 'bench'
6
6
 
7
7
  arg = ARGV[0]
8
8
 
9
- puts "yjit: #{RubyVM::YJIT.enabled?}"
9
+ puts "Ruby: #{RUBY_VERSION}"
10
+ puts "YJIT: #{RubyVM::YJIT.enabled?}"
10
11
 
11
12
  if arg == 'bench'
12
13
  Rubyboy::Bench.new.bench
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'audio'
4
+ require_relative 'apu_channels/channel1'
5
+ require_relative 'apu_channels/channel2'
6
+ require_relative 'apu_channels/channel3'
7
+ require_relative 'apu_channels/channel4'
8
+
9
+ module Rubyboy
10
+ class Apu
11
+ def initialize
12
+ @audio = Audio.new
13
+ @nr50 = 0
14
+ @nr51 = 0
15
+ @cycles = 0
16
+ @sampling_cycles = 0
17
+ @fs = 0
18
+ @samples = Array.new(1024, 0.0)
19
+ @sample_idx = 0
20
+ @channel1 = ApuChannels::Channel1.new
21
+ @channel2 = ApuChannels::Channel2.new
22
+ @channel3 = ApuChannels::Channel3.new
23
+ @channel4 = ApuChannels::Channel4.new
24
+ end
25
+
26
+ def step(cycles)
27
+ @cycles += cycles
28
+ @sampling_cycles += cycles
29
+
30
+ @channel1.step(cycles)
31
+ @channel2.step(cycles)
32
+ @channel3.step(cycles)
33
+ @channel4.step(cycles)
34
+
35
+ if @cycles >= 0x1fff
36
+ @cycles -= 0x1fff
37
+
38
+ @channel1.step_fs(@fs)
39
+ @channel2.step_fs(@fs)
40
+ @channel3.step_fs(@fs)
41
+ @channel4.step_fs(@fs)
42
+
43
+ @fs = (@fs + 1) % 8
44
+ end
45
+
46
+ if @sampling_cycles >= 87
47
+ @sampling_cycles -= 87
48
+
49
+ left_sample = (
50
+ @nr51[7] * @channel4.dac_output +
51
+ @nr51[6] * @channel3.dac_output +
52
+ @nr51[5] * @channel2.dac_output +
53
+ @nr51[4] * @channel1.dac_output
54
+ ) / 4.0
55
+
56
+ right_sample = (
57
+ @nr51[3] * @channel4.dac_output +
58
+ @nr51[2] * @channel3.dac_output +
59
+ @nr51[1] * @channel2.dac_output +
60
+ @nr51[0] * @channel1.dac_output
61
+ ) / 4.0
62
+
63
+ raise "#{@nr51} #{@channel4.dac_output}, #{@channel3.dac_output}, #{@channel2.dac_output},#{@channel1.dac_output}" if left_sample.abs > 1.0 || right_sample.abs > 1.0
64
+
65
+ @samples[@sample_idx * 2] = (@nr50[4..6] / 7.0) * left_sample
66
+ @samples[@sample_idx * 2 + 1] = (@nr50[0..2] / 7.0) * right_sample
67
+ @sample_idx += 1
68
+ end
69
+
70
+ return if @sample_idx < 512
71
+
72
+ @sample_idx = 0
73
+ @audio.queue(@samples)
74
+ end
75
+
76
+ def read_byte(addr)
77
+ case addr
78
+ when 0xff10..0xff14 then @channel1.read_nr1x(addr - 0xff10)
79
+ when 0xff15..0xff19 then @channel2.read_nr2x(addr - 0xff15)
80
+ when 0xff1a..0xff1e then @channel3.read_nr3x(addr - 0xff1a)
81
+ when 0xff1f..0xff23 then @channel4.read_nr4x(addr - 0xff1f)
82
+ when 0xff24 then @nr50
83
+ when 0xff25 then @nr51
84
+ when 0xff26 then (@channel1.enabled ? 0x01 : 0x00) | (@channel2.enabled ? 0x02 : 0x00) | (@channel3.enabled ? 0x04 : 0x00) | (@channel4.enabled ? 0x08 : 0x00) | 0x70 | (@enabled ? 0x80 : 0x00)
85
+ when 0xff30..0xff3f then @channel3.wave_ram[(addr - 0xff30)]
86
+ else raise "Invalid APU read at #{addr.to_s(16)}"
87
+ end
88
+ end
89
+
90
+ def write_byte(addr, val)
91
+ return if !@enabled && ![0xff11, 0xff16, 0xff1b, 0xff20, 0xff26].include?(addr) && !(0xff30..0xff3f).include?(addr)
92
+
93
+ val &= 0x3f if !@enabled && [0xff11, 0xff16, 0xff1b, 0xff20].include?(addr)
94
+
95
+ case addr
96
+ when 0xff10..0xff14 then @channel1.write_nr1x(addr - 0xff10, val)
97
+ when 0xff15..0xff19 then @channel2.write_nr2x(addr - 0xff15, val)
98
+ when 0xff1a..0xff1e then @channel3.write_nr3x(addr - 0xff1a, val)
99
+ when 0xff1f..0xff23 then @channel4.write_nr4x(addr - 0xff1f, val)
100
+ when 0xff24 then @nr50 = val
101
+ when 0xff25 then @nr51 = val
102
+ when 0xff26
103
+ flg = val & 0x80 > 0
104
+ if !flg && @enabled
105
+ (0xff10..0xff25).each { |a| write_byte(a, 0) }
106
+ elsif flg && !@enabled
107
+ @fs = 0
108
+ @channel1.wave_duty_position = 0
109
+ @channel2.wave_duty_position = 0
110
+ @channel3.wave_duty_position = 0
111
+ end
112
+ @enabled = flg
113
+ when 0xff30..0xff3f then @channel3.wave_ram[(addr - 0xff30)] = val
114
+ else raise "Invalid APU write at #{addr.to_s(16)}"
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubyboy
4
+ module ApuChannels
5
+ class Channel1
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
+ end
38
+
39
+ def step(cycles)
40
+ @cycles += cycles
41
+
42
+ return if @cycles < @frequency_timer
43
+
44
+ @cycles -= @frequency_timer
45
+ @frequency_timer = (2048 - @frequency) * 4
46
+ @wave_duty_position = (@wave_duty_position + 1) % 8
47
+ end
48
+
49
+ def step_fs(fs)
50
+ length if fs & 0x01 == 0
51
+ envelope if fs == 7
52
+ sweep if fs == 2 || fs == 6
53
+ end
54
+
55
+ def length
56
+ return unless @length_enabled && @length_timer > 0
57
+
58
+ @length_timer -= 1
59
+ @enabled &= @length_timer > 0
60
+ end
61
+
62
+ def envelope
63
+ return if @period == 0
64
+
65
+ @period_timer -= 1 if @period_timer > 0
66
+
67
+ return if @period_timer != 0
68
+
69
+ @period_timer = @period
70
+
71
+ if @current_volume < 15 && @is_upwards
72
+ @current_volume += 1
73
+ elsif @current_volume > 0 && !@is_upwards
74
+ @current_volume -= 1
75
+ end
76
+ end
77
+
78
+ def sweep
79
+ @sweep_timer -= 1 if @sweep_timer > 0
80
+
81
+ return if @sweep_timer != 0
82
+
83
+ @sweep_timer = @sweep_period
84
+
85
+ return unless @sweep_enabled
86
+
87
+ @frequency = calculate_frequency
88
+ @shadow_frequency = @frequency
89
+ end
90
+
91
+ def calculate_frequency
92
+ if @is_decrementing
93
+ if @shadow_frequency >= (@shadow_frequency >> @sweep_shift)
94
+ @shadow_frequency - (@shadow_frequency >> @sweep_shift)
95
+ else
96
+ 0
97
+ end
98
+ else
99
+ [0x3ff, @shadow_frequency + (@shadow_frequency >> @sweep_shift)].min
100
+ end
101
+ end
102
+
103
+ def dac_output
104
+ return 0.0 unless @dac_enabled && @enabled
105
+
106
+ ret = WAVE_DUTY[@wave_duty_pattern][@wave_duty_position] * @current_volume
107
+ (ret / 7.5) - 1.0
108
+ end
109
+
110
+ def read_nr1x(x)
111
+ case x
112
+ when 0 then (@sweep_period << 4) | @sweep_shift | (@is_decrementing ? 0x08 : 0x00) | 0x80
113
+ when 1 then (@wave_duty_pattern << 6) | 0x3f
114
+ when 2 then (@initial_volume << 4) | (@is_upwards ? 0x08 : 0x00) | @period
115
+ when 3 then 0xff
116
+ when 4 then (@length_enabled ? 0x40 : 0x00) | 0xbf
117
+ else 0xff
118
+ end
119
+ end
120
+
121
+ def write_nr1x(x, val)
122
+ case x
123
+ when 0
124
+ @sweep_period = (val >> 4) & 0x07
125
+ @is_decrementing = (val & 0x08) > 0
126
+ @sweep_shift = val & 0x07
127
+ when 1
128
+ @wave_duty_pattern = (val >> 6) & 0x03
129
+ @length_timer = 64 - (val & 0x3f)
130
+ when 2
131
+ @is_upwards = (val & 0x08) > 0
132
+ @initial_volume = (val >> 4)
133
+ @period = val & 0x07
134
+ @dac_enabled = val & 0xf8 > 0
135
+ @enabled &= @dac_enabled
136
+ when 3
137
+ @frequency = (@frequency & 0x700) | val
138
+ when 4
139
+ @frequency = (@frequency & 0xff) | ((val & 0x07) << 8)
140
+ @length_enabled = (val & 0x40) > 0
141
+ @length_timer = 64 if @length_timer == 0
142
+ return unless (val & 0x80) > 0 && @dac_enabled
143
+
144
+ @enabled = true
145
+ @period_timer = @period
146
+ @current_volume = @initial_volume
147
+ @shadow_frequency = @frequency
148
+ @sweep_timer = @sweep_period
149
+ @sweep_enabled = @sweep_period > 0 || @sweep_shift > 0
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubyboy
4
+ module ApuChannels
5
+ class Channel2
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
+ @period = 0
27
+ @period_timer = 0
28
+ @current_volume = 0
29
+ @initial_volume = 0
30
+ @shadow_frequency = 0
31
+ @length_timer = 0
32
+ @wave_duty_pattern = 0
33
+ end
34
+
35
+ def step(cycles)
36
+ @cycles += cycles
37
+
38
+ return if @cycles < @frequency_timer
39
+
40
+ @cycles -= @frequency_timer
41
+ @frequency_timer = (2048 - @frequency) * 4
42
+ @wave_duty_position = (@wave_duty_position + 1) % 8
43
+ end
44
+
45
+ def step_fs(fs)
46
+ length if fs & 0x01 == 0
47
+ envelope if fs == 7
48
+ end
49
+
50
+ def length
51
+ return unless @length_enabled && @length_timer > 0
52
+
53
+ @length_timer -= 1
54
+ @enabled &= @length_timer > 0
55
+ end
56
+
57
+ def envelope
58
+ return if @period == 0
59
+
60
+ @period_timer -= 1 if @period_timer > 0
61
+
62
+ return if @period_timer != 0
63
+
64
+ @period_timer = @period
65
+
66
+ if @current_volume < 15 && @is_upwards
67
+ @current_volume += 1
68
+ elsif @current_volume > 0 && !@is_upwards
69
+ @current_volume -= 1
70
+ end
71
+ end
72
+
73
+ def dac_output
74
+ return 0.0 unless @dac_enabled && @enabled
75
+
76
+ ret = WAVE_DUTY[@wave_duty_pattern][@wave_duty_position] * @current_volume
77
+ (ret / 7.5) - 1.0
78
+ end
79
+
80
+ def read_nr2x(x)
81
+ case x
82
+ when 1 then (@wave_duty_pattern << 6) | 0x3f
83
+ when 2 then (@initial_volume << 4) | (@is_upwards ? 0x08 : 0x00) | @period
84
+ when 3 then 0xff
85
+ when 4 then (@length_enabled ? 0x40 : 0x00) | 0xbf
86
+ else 0xff
87
+ end
88
+ end
89
+
90
+ def write_nr2x(x, val)
91
+ case x
92
+ when 1
93
+ @wave_duty_pattern = (val >> 6) & 0x03
94
+ @length_timer = 64 - (val & 0x3f)
95
+ when 2
96
+ @is_upwards = (val & 0x08) > 0
97
+ @initial_volume = (val >> 4)
98
+ @period = val & 0x07
99
+ @dac_enabled = val & 0xf8 > 0
100
+ @enabled &= @dac_enabled
101
+ when 3
102
+ @frequency = (@frequency & 0x700) | val
103
+ when 4
104
+ @frequency = (@frequency & 0xff) | ((val & 0x07) << 8)
105
+ @length_enabled = (val & 0x40) > 0
106
+ @length_timer = 64 if @length_timer == 0
107
+ return unless (val & 0x80) > 0 && @dac_enabled
108
+
109
+ @enabled = true
110
+ @period_timer = @period
111
+ @current_volume = @initial_volume
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubyboy
4
+ module ApuChannels
5
+ class Channel3
6
+ attr_accessor :enabled, :wave_duty_position, :wave_ram
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
+ @output_level = 0
39
+ @volume_shift = 0
40
+ @wave_ram = Array.new(16, 0)
41
+ end
42
+
43
+ def step(cycles)
44
+ @cycles += cycles
45
+
46
+ return if @cycles < @frequency_timer
47
+
48
+ @cycles -= @frequency_timer
49
+ @frequency_timer = (2048 - @frequency) * 2
50
+ @wave_duty_position = (@wave_duty_position + 1) % 32
51
+ end
52
+
53
+ def step_fs(fs)
54
+ length if fs & 0x01 == 0
55
+ end
56
+
57
+ def length
58
+ return unless @length_enabled && @length_timer > 0
59
+
60
+ @length_timer -= 1
61
+ @enabled &= @length_timer > 0
62
+ end
63
+
64
+ def calculate_frequency
65
+ if @is_decrementing
66
+ if @shadow_frequency >= (@shadow_frequency >> @sweep_shift)
67
+ @shadow_frequency - (@shadow_frequency >> @sweep_shift)
68
+ else
69
+ 0
70
+ end
71
+ else
72
+ [0x3ff, @shadow_frequency + (@shadow_frequency >> @sweep_shift)].min
73
+ end
74
+ end
75
+
76
+ def dac_output
77
+ return 0.0 unless @dac_enabled && @enabled
78
+
79
+ ret = ((0xf & (
80
+ @wave_ram[@wave_duty_position >> 1] >> ((@wave_duty_position & 0x01) << 2)
81
+ ))) >> @volume_shift
82
+
83
+ (ret / 7.5) - 1.0
84
+ end
85
+
86
+ def read_nr3x(x)
87
+ case x
88
+ when 0 then ((@dac_enabled ? 0x80 : 0x00) | 0x7f)
89
+ when 1 then 0xff
90
+ when 2 then (@output_level << 5) | 0x9f
91
+ when 3 then 0xff
92
+ when 4 then (@length_enabled ? 0x40 : 0x00) | 0xbf
93
+ else 0xff
94
+ end
95
+ end
96
+
97
+ def write_nr3x(x, val)
98
+ case x
99
+ when 0
100
+ @dac_enabled = val & 0x80 > 0
101
+ @enabled &= @dac_enabled
102
+ when 1
103
+ @length_timer = 256 - val
104
+ when 2
105
+ @output_level = (val >> 5) & 0x03
106
+ when 3
107
+ @frequency = (@frequency & 0x700) | val
108
+ when 4
109
+ @frequency = (@frequency & 0xff) | ((val & 0x07) << 8)
110
+ @length_enabled = val & 0x40 > 0
111
+ @length_timer = 256 if @length_timer == 0
112
+ @enabled = true if (val & 0x80) > 0 && @dac_enabled
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end