Skip to content

Commit

Permalink
Merge pull request #14 from sacckey/v1.3.0
Browse files Browse the repository at this point in the history
V1.3.0
  • Loading branch information
sacckey authored Mar 9, 2024
2 parents 31aa875 + abee0b6 commit a2f8e7d
Show file tree
Hide file tree
Showing 24 changed files with 1,835 additions and 969 deletions.
6 changes: 6 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ Metrics/AbcSize:
Metrics/BlockNesting:
Enabled: false

Metrics/BlockLength:
Enabled: false

Metrics/CyclomaticComplexity:
Enabled: false

Expand All @@ -41,6 +44,9 @@ Style/Documentation:
Style/FormatString:
Enabled: false

Style/MultipleComparison:
Enabled: false

Style/NumericLiterals:
Enabled: false

Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
## [Unreleased]

## [1.3.0] - 2024-03-09

- Add ffi to dependencies
- Add SDL2 wrapper
- Add audio
- Optimize cpu
- Cache R/W methods

## [1.2.0] - 2024-01-09

- Add logo
Expand Down
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ gemspec
gem 'rake', '~> 13.0'

group :development, :test do
gem 'heap-profiler', '~> 0.7.0'
gem 'rspec', '~> 3.12'
gem 'rubocop', '~> 1.57'

gem 'stackprof', '~> 0.2.25'
end
13 changes: 4 additions & 9 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
PATH
remote: .
specs:
rubyboy (1.2.0)
raylib-bindings (~> 0.6.0)
rubyboy (1.3.0)
ffi (~> 1.16, >= 1.16.3)

GEM
remote: https://rubygems.org/
Expand All @@ -11,22 +11,16 @@ GEM
base64 (0.1.1)
diff-lcs (1.5.0)
ffi (1.16.3)
heap-profiler (0.7.0)
json (2.6.3)
language_server-protocol (3.17.0.3)
opengl-bindings2 (2.0.1)
parallel (1.23.0)
parser (3.2.2.4)
ast (~> 2.4.1)
racc
racc (1.7.1)
rainbow (3.1.1)
rake (13.0.6)
raylib-bindings (0.6.0-x86_64-darwin)
ffi (~> 1.16)
opengl-bindings2 (~> 2)
raylib-bindings (0.6.0-x86_64-linux)
ffi (~> 1.16)
opengl-bindings2 (~> 2)
regexp_parser (2.8.2)
rexml (3.2.6)
rspec (3.12.0)
Expand Down Expand Up @@ -65,6 +59,7 @@ PLATFORMS
x86_64-linux

DEPENDENCIES
heap-profiler (~> 0.7.0)
rake (~> 13.0)
rspec (~> 3.12)
rubocop (~> 1.57)
Expand Down
61 changes: 19 additions & 42 deletions lib/rubyboy.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require 'raylib'
require 'rubyboy/sdl'
require_relative 'rubyboy/apu'
require_relative 'rubyboy/bus'
require_relative 'rubyboy/cpu'
require_relative 'rubyboy/ppu'
Expand All @@ -14,10 +15,7 @@

module Rubyboy
class Console
include Raylib

def initialize(rom_path)
load_raylib
rom_data = File.open(rom_path, 'r') { _1.read.bytes }
rom = Rom.new(rom_data)
ram = Ram.new
Expand All @@ -26,19 +24,23 @@ def initialize(rom_path)
@ppu = Ppu.new(interrupt)
@timer = Timer.new(interrupt)
@joypad = Joypad.new(interrupt)
@bus = Bus.new(@ppu, rom, ram, mbc, @timer, interrupt, @joypad)
@apu = Apu.new
@bus = Bus.new(@ppu, rom, ram, mbc, @timer, interrupt, @joypad, @apu)
@cpu = Cpu.new(@bus, interrupt)
@lcd = Lcd.new
end

def start
until @lcd.window_should_close?
SDL.InitSubSystem(SDL::INIT_KEYBOARD)
loop do
cycles = @cpu.exec
@timer.step(cycles)
if @ppu.step(cycles)
draw
key_input_check
end
@apu.step(cycles)
next unless @ppu.step(cycles)

@lcd.draw(@ppu.buffer)
key_input_check
break if @lcd.window_should_close?
end
@lcd.close_window
rescue StandardError => e
Expand All @@ -63,40 +65,15 @@ def bench

private

def draw
pixel_data = buffer_to_pixel_data(@ppu.buffer)
@lcd.draw(pixel_data)
end

def buffer_to_pixel_data(buffer)
buffer.flat_map do |row|
[row, row, row]
end.pack('C*')
end

def key_input_check
direction = (IsKeyUp(KEY_D) && 1 || 0) | ((IsKeyUp(KEY_A) && 1 || 0) << 1) | ((IsKeyUp(KEY_W) && 1 || 0) << 2) | ((IsKeyUp(KEY_S) && 1 || 0) << 3)
action = (IsKeyUp(KEY_K) && 1 || 0) | ((IsKeyUp(KEY_J) && 1 || 0) << 1) | ((IsKeyUp(KEY_U) && 1 || 0) << 2) | ((IsKeyUp(KEY_I) && 1 || 0) << 3)
@joypad.direction_button(direction)
@joypad.action_button(action)
end

def load_raylib
shared_lib_path = "#{Gem::Specification.find_by_name('raylib-bindings').full_gem_path}/lib/"
case RUBY_PLATFORM
when /mswin|msys|mingw/ # Windows
Raylib.load_lib("#{shared_lib_path}libraylib.dll")
when /darwin/ # macOS
arch = RUBY_PLATFORM.split('-')[0]
Raylib.load_lib(shared_lib_path + "libraylib.#{arch}.dylib")
when /linux/ # Ubuntu Linux (x86_64 or aarch64)
arch = RUBY_PLATFORM.split('-')[0]
Raylib.load_lib(shared_lib_path + "libraylib.#{arch}.so")
else
raise "Unknown system: #{RUBY_PLATFORM}"
end
SDL.PumpEvents
keyboard = SDL.GetKeyboardState(nil)
keyboard_state = keyboard.read_array_of_uint8(229)

SetTraceLogLevel(LOG_ERROR)
direction = (keyboard_state[SDL::SDL_SCANCODE_D]) | (keyboard_state[SDL::SDL_SCANCODE_A] << 1) | (keyboard_state[SDL::SDL_SCANCODE_W] << 2) | (keyboard_state[SDL::SDL_SCANCODE_S] << 3)
action = (keyboard_state[SDL::SDL_SCANCODE_K]) | (keyboard_state[SDL::SDL_SCANCODE_J] << 1) | (keyboard_state[SDL::SDL_SCANCODE_U] << 2) | (keyboard_state[SDL::SDL_SCANCODE_I] << 3)
@joypad.direction_button(15 - direction)
@joypad.action_button(15 - action)
end
end
end
118 changes: 118 additions & 0 deletions lib/rubyboy/apu.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# frozen_string_literal: true

require_relative 'audio'
require_relative 'apu_channels/channel1'
require_relative 'apu_channels/channel2'
require_relative 'apu_channels/channel3'
require_relative 'apu_channels/channel4'

module Rubyboy
class Apu
def initialize
@audio = Audio.new
@nr50 = 0
@nr51 = 0
@cycles = 0
@sampling_cycles = 0
@fs = 0
@samples = Array.new(1024, 0.0)
@sample_idx = 0
@channel1 = ApuChannels::Channel1.new
@channel2 = ApuChannels::Channel2.new
@channel3 = ApuChannels::Channel3.new
@channel4 = ApuChannels::Channel4.new
end

def step(cycles)
@cycles += cycles
@sampling_cycles += cycles

@channel1.step(cycles)
@channel2.step(cycles)
@channel3.step(cycles)
@channel4.step(cycles)

if @cycles >= 0x1fff
@cycles -= 0x1fff

@channel1.step_fs(@fs)
@channel2.step_fs(@fs)
@channel3.step_fs(@fs)
@channel4.step_fs(@fs)

@fs = (@fs + 1) % 8
end

if @sampling_cycles >= 87
@sampling_cycles -= 87

left_sample = (
@nr51[7] * @channel4.dac_output +
@nr51[6] * @channel3.dac_output +
@nr51[5] * @channel2.dac_output +
@nr51[4] * @channel1.dac_output
) / 4.0

right_sample = (
@nr51[3] * @channel4.dac_output +
@nr51[2] * @channel3.dac_output +
@nr51[1] * @channel2.dac_output +
@nr51[0] * @channel1.dac_output
) / 4.0

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

@samples[@sample_idx * 2] = (@nr50[4..6] / 7.0) * left_sample
@samples[@sample_idx * 2 + 1] = (@nr50[0..2] / 7.0) * right_sample
@sample_idx += 1
end

return if @sample_idx < 512

@sample_idx = 0
@audio.queue(@samples)
end

def read_byte(addr)
case addr
when 0xff10..0xff14 then @channel1.read_nr1x(addr - 0xff10)
when 0xff15..0xff19 then @channel2.read_nr2x(addr - 0xff15)
when 0xff1a..0xff1e then @channel3.read_nr3x(addr - 0xff1a)
when 0xff1f..0xff23 then @channel4.read_nr4x(addr - 0xff1f)
when 0xff24 then @nr50
when 0xff25 then @nr51
when 0xff26 then (@channel1.enabled ? 0x01 : 0x00) | (@channel2.enabled ? 0x02 : 0x00) | (@channel3.enabled ? 0x04 : 0x00) | (@channel4.enabled ? 0x08 : 0x00) | 0x70 | (@enabled ? 0x80 : 0x00)
when 0xff30..0xff3f then @channel3.wave_ram[(addr - 0xff30)]
else raise "Invalid APU read at #{addr.to_s(16)}"
end
end

def write_byte(addr, val)
return if !@enabled && ![0xff11, 0xff16, 0xff1b, 0xff20, 0xff26].include?(addr) && !(0xff30..0xff3f).include?(addr)

val &= 0x3f if !@enabled && [0xff11, 0xff16, 0xff1b, 0xff20].include?(addr)

case addr
when 0xff10..0xff14 then @channel1.write_nr1x(addr - 0xff10, val)
when 0xff15..0xff19 then @channel2.write_nr2x(addr - 0xff15, val)
when 0xff1a..0xff1e then @channel3.write_nr3x(addr - 0xff1a, val)
when 0xff1f..0xff23 then @channel4.write_nr4x(addr - 0xff1f, val)
when 0xff24 then @nr50 = val
when 0xff25 then @nr51 = val
when 0xff26
flg = val & 0x80 > 0
if !flg && @enabled
(0xff10..0xff25).each { |a| write_byte(a, 0) }
elsif flg && !@enabled
@fs = 0
@channel1.wave_duty_position = 0
@channel2.wave_duty_position = 0
@channel3.wave_duty_position = 0
end
@enabled = flg
when 0xff30..0xff3f then @channel3.wave_ram[(addr - 0xff30)] = val
else raise "Invalid APU write at #{addr.to_s(16)}"
end
end
end
end
Loading

0 comments on commit a2f8e7d

Please sign in to comment.