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 +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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f641e78dedf235a5ac8bab34c18fbd624150e5e6c9df92a710d2bc458fab3d62
|
4
|
+
data.tar.gz: 7ab941409fcbec7b0eff98241231e002c249f84be16a19c28216e33f91ac14d2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
data/exe/rubyboy
CHANGED
data/lib/rubyboy/apu.rb
ADDED
@@ -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
|