move apu to use Int16 rather than Float32 samples

This commit is contained in:
Matthew Berry 2021-01-27 01:19:22 -08:00
parent 0de7d0c0c5
commit 278a7ff2cb
7 changed files with 51 additions and 71 deletions

View file

@ -22,7 +22,7 @@ class APU
@sound_enabled : Bool = false @sound_enabled : Bool = false
@soundbias = Reg::SOUNDBIAS.new 0 @soundbias = Reg::SOUNDBIAS.new 0
@buffer = Slice(Float32).new BUFFER_SIZE @buffer = Slice(Int16).new BUFFER_SIZE
@buffer_pos = 0 @buffer_pos = 0
@frame_sequencer_stage = 0 @frame_sequencer_stage = 0
getter first_half_of_length_period = false getter first_half_of_length_period = false
@ -35,7 +35,7 @@ class APU
def initialize(@gba : GBA) def initialize(@gba : GBA)
@audiospec = LibSDL::AudioSpec.new @audiospec = LibSDL::AudioSpec.new
@audiospec.freq = SAMPLE_RATE @audiospec.freq = SAMPLE_RATE
@audiospec.format = LibSDL::AUDIO_F32SYS @audiospec.format = LibSDL::AUDIO_S16
@audiospec.channels = CHANNELS @audiospec.channels = CHANNELS
@audiospec.samples = BUFFER_SIZE @audiospec.samples = BUFFER_SIZE
@audiospec.callback = nil @audiospec.callback = nil
@ -101,48 +101,41 @@ class APU
def get_sample : Nil def get_sample : Nil
abort "Prohibited sound 1-4 volume #{@soundcnt_h.sound_volume}" if @soundcnt_h.sound_volume >= 3 abort "Prohibited sound 1-4 volume #{@soundcnt_h.sound_volume}" if @soundcnt_h.sound_volume >= 3
psg_master_volume = ((100 >> (2 - @soundcnt_h.sound_volume)) / 100).to_f32 # Puts PSGs on scale of 0...0x40
psg_sound = ((@channel1.get_amplitude * @soundcnt_l.channel_1_left) + psg_sound = ((@channel1.get_amplitude * @soundcnt_l.channel_1_left) +
(@channel2.get_amplitude * @soundcnt_l.channel_2_left) + (@channel2.get_amplitude * @soundcnt_l.channel_2_left) +
(@channel3.get_amplitude * @soundcnt_l.channel_3_left) + (@channel3.get_amplitude * @soundcnt_l.channel_3_left) +
(@channel4.get_amplitude * @soundcnt_l.channel_4_left)) / 4 (@channel4.get_amplitude * @soundcnt_l.channel_4_left))# * 2 - 0x80
psg_left = psg_master_volume * # Puts PSGs on scale of 0...0x200
(@soundcnt_l.left_volume / 7) * psg_left = (psg_sound * @soundcnt_l.left_volume) >> (2 - @soundcnt_h.sound_volume)
psg_sound psg_right = (psg_sound * @soundcnt_l.right_volume) >> (2 - @soundcnt_h.sound_volume)
psg_right = psg_master_volume *
(@soundcnt_l.right_volume / 7) *
psg_sound
# Gets DMAs on scale of -0x100...0x100
dma_a, dma_b = @dma_channels.get_amplitude dma_a, dma_b = @dma_channels.get_amplitude
vol_a = ((100 >> (1 - @soundcnt_h.dma_sound_a_volume)) / 100).to_f32 dma_a <<= 1
vol_b = ((100 >> (1 - @soundcnt_h.dma_sound_b_volume)) / 100).to_f32 dma_b <<= 1
dma_left = ((dma_a * @soundcnt_h.dma_sound_a_left * vol_a) + # Puts DMAs on scale of -0x200...0x200
(dma_b * @soundcnt_h.dma_sound_b_left * vol_b)) / 2 dma_a <<= @soundcnt_h.dma_sound_a_volume
dma_right = ((dma_a * @soundcnt_h.dma_sound_a_right * vol_a) + dma_b <<= @soundcnt_h.dma_sound_b_volume
(dma_b * @soundcnt_h.dma_sound_b_right * vol_b)) / 2 dma_left = dma_a * @soundcnt_h.dma_sound_a_left + dma_b * @soundcnt_h.dma_sound_b_left
dma_right = dma_a * @soundcnt_h.dma_sound_a_right + dma_b * @soundcnt_h.dma_sound_b_right
@buffer[@buffer_pos] = (psg_left + 2*dma_left) / 3 bias = 0x200
@buffer[@buffer_pos + 1] = (psg_right + 2*dma_right) / 3
if @buffer[@buffer_pos].abs > 1 || @buffer[@buffer_pos + 1].abs > 1 total_left = (psg_left + dma_left + bias).clamp(0_i16..0x3FF_i16)
STDERR.puts "Left: #{@buffer[@buffer_pos]}" total_right = (psg_right + dma_right + bias).clamp(0_i16..0x3FF_i16)
STDERR.puts " PSG: #{psg_left}"
STDERR.puts " DMA: #{dma_right}"
STDERR.puts "Right: #{@buffer[@buffer_pos + 1]}"
STDERR.puts " PSG: #{psg_right}"
STDERR.puts " DMA: #{dma_right}"
exit 1
end
@buffer[@buffer_pos] = total_left * 32
@buffer[@buffer_pos + 1] = total_right * 32
@buffer_pos += 2 @buffer_pos += 2
# push to SDL if buffer is full # push to SDL if buffer is full
if @buffer_pos >= BUFFER_SIZE if @buffer_pos >= BUFFER_SIZE
LibSDL.clear_queued_audio 1 unless @sync LibSDL.clear_queued_audio 1 unless @sync
while LibSDL.get_queued_audio_size(1) > BUFFER_SIZE * sizeof(Float32) * 2 while LibSDL.get_queued_audio_size(1) > BUFFER_SIZE * sizeof(Int16) * 2
LibSDL.delay(1) LibSDL.delay(1)
end end
LibSDL.queue_audio 1, @buffer, BUFFER_SIZE * sizeof(Float32) LibSDL.queue_audio 1, @buffer, BUFFER_SIZE * sizeof(Int16)
@buffer_pos = 0 @buffer_pos = 0
end end

View file

@ -39,7 +39,7 @@ abstract class SoundChannel
# Called when @period reaches 0 # Called when @period reaches 0
abstract def step_wave_generation : Nil abstract def step_wave_generation : Nil
abstract def get_amplitude : Float32 abstract def get_amplitude : Int16
abstract def read_io(index : Int) : UInt8 abstract def read_io(index : Int) : UInt8
abstract def write_io(index : Int, value : UInt8) : Nil abstract def write_io(index : Int, value : UInt8) : Nil

View file

@ -57,14 +57,13 @@ class Channel1 < VolumeEnvelopeChannel
end end
end end
def get_amplitude : Float32 # Outputs a value 0..0xF
def get_amplitude : Int16
if @enabled && @dac_enabled if @enabled && @dac_enabled
dac_input = WAVE_DUTY[@duty][@wave_duty_position] * @current_volume WAVE_DUTY[@duty][@wave_duty_position].to_i16 * @current_volume
dac_output = (dac_input / 7.5) - 1
dac_output
else else
0 0_i16
end.to_f32 end
end end
# Calculate the new shadow frequency, disable channel if overflow 11 bits # Calculate the new shadow frequency, disable channel if overflow 11 bits

View file

@ -33,14 +33,13 @@ class Channel2 < VolumeEnvelopeChannel
@gba.scheduler.schedule frequency_timer, ->step, Scheduler::EventType::APUChannel2 @gba.scheduler.schedule frequency_timer, ->step, Scheduler::EventType::APUChannel2
end end
def get_amplitude : Float32 # Outputs a value 0..0xF
def get_amplitude : Int16
if @enabled && @dac_enabled if @enabled && @dac_enabled
dac_input = WAVE_DUTY[@duty][@wave_duty_position] * @current_volume WAVE_DUTY[@duty][@wave_duty_position].to_i16 * @current_volume
dac_output = (dac_input / 7.5) - 1
dac_output
else else
0 0_i16
end.to_f32 end
end end
def read_io(index : Int) : UInt8 def read_io(index : Int) : UInt8

View file

@ -8,7 +8,7 @@ class Channel3 < SoundChannel
@wave_ram = Array(Bytes).new 2, Bytes.new(WAVE_RAM_RANGE.size) { |idx| idx & 1 == 0 ? 0x00_u8 : 0xFF_u8 } @wave_ram = Array(Bytes).new 2, Bytes.new(WAVE_RAM_RANGE.size) { |idx| idx & 1 == 0 ? 0x00_u8 : 0xFF_u8 }
@wave_ram_position : UInt8 = 0 @wave_ram_position : UInt8 = 0
@wave_ram_sample_buffer : UInt8 = 0x00 @wave_ram_sample_buffer : Int16 = 0x00
# NR30 # NR30
@wave_ram_dimension : Bool = false @wave_ram_dimension : Bool = false
@ -21,15 +21,13 @@ class Channel3 < SoundChannel
@volume_code : UInt8 = 0x00 @volume_code : UInt8 = 0x00
@volume_force : Bool = false @volume_force : Bool = false
@volume_multiplier : Float32 = 0
# NR33 / NR34 # NR33 / NR34
@frequency : UInt16 = 0x00 @frequency : UInt16 = 0x00
def step_wave_generation : Nil def step_wave_generation : Nil
@wave_ram_position = (@wave_ram_position + 1) % (WAVE_RAM_RANGE.size * 2) @wave_ram_position = (@wave_ram_position + 1) % (WAVE_RAM_RANGE.size * 2)
@wave_ram_bank ^= 1 if @wave_ram_position == 0 && @wave_ram_dimension @wave_ram_bank ^= 1 if @wave_ram_position == 0 && @wave_ram_dimension
@wave_ram_sample_buffer = @wave_ram[@wave_ram_bank][@wave_ram_position // 2] @wave_ram_sample_buffer = @wave_ram[@wave_ram_bank][@wave_ram_position // 2].to_i16
end end
def frequency_timer : UInt32 def frequency_timer : UInt32
@ -40,14 +38,14 @@ class Channel3 < SoundChannel
@gba.scheduler.schedule frequency_timer, ->step, Scheduler::EventType::APUChannel3 @gba.scheduler.schedule frequency_timer, ->step, Scheduler::EventType::APUChannel3
end end
def get_amplitude : Float32 # Outputs a value 0..0xF
def get_amplitude : Int16
if @enabled && @dac_enabled if @enabled && @dac_enabled
dac_input = @volume_multiplier * ((@wave_ram_sample_buffer >> (@wave_ram_position & 1 == 0 ? 4 : 0)) & 0x0F) volume_shift = (-1 + @volume_code) % 4
dac_output = (dac_input / 7.5) - 1 (((@wave_ram_sample_buffer >> (@wave_ram_position & 1 == 0 ? 4 : 0)) & 0x0F) >> volume_shift)
dac_output
else else
0 0_i16
end.to_f32 end
end end
def read_io(index : Int) : UInt8 def read_io(index : Int) : UInt8
@ -82,15 +80,6 @@ class Channel3 < SoundChannel
when 0x73 when 0x73
@volume_code = (value & 0x60) >> 5 @volume_code = (value & 0x60) >> 5
@volume_force = bit?(value, 7) @volume_force = bit?(value, 7)
# Internal values
@volume_multiplier = case {@volume_force, @volume_code}
when {true, _} then 0.75_f32
when {_, 0b00} then 0_f32
when {_, 0b01} then 1_f32
when {_, 0b10} then 0.5_f32
when {_, 0b11} then 0.25_f32
else raise "Impossible volume code #{@volume_code}"
end
when 0x74 when 0x74
@frequency = (@frequency & 0x0700) | value @frequency = (@frequency & 0x0700) | value
when 0x75 when 0x75

View file

@ -33,14 +33,13 @@ class Channel4 < VolumeEnvelopeChannel
@gba.scheduler.schedule frequency_timer, ->step, Scheduler::EventType::APUChannel4 @gba.scheduler.schedule frequency_timer, ->step, Scheduler::EventType::APUChannel4
end end
def get_amplitude : Float32 # Outputs a value 0..0xF
def get_amplitude : Int16
if @enabled && @dac_enabled if @enabled && @dac_enabled
dac_input = (~@lfsr & 1) * @current_volume ((~@lfsr & 1) * @current_volume).to_i16
dac_output = (dac_input / 7.5) - 1
dac_output
else else
0 0_i16
end.to_f32 end
end end
def read_io(index : Int) : UInt8 def read_io(index : Int) : UInt8

View file

@ -5,7 +5,7 @@ class DMAChannels
@positions = Array(Int32).new 2, 0 @positions = Array(Int32).new 2, 0
@sizes = Array(Int32).new 2, 0 @sizes = Array(Int32).new 2, 0
@timers : Array(Proc(UInt16)) @timers : Array(Proc(UInt16))
@latches = Array(Float32).new 2, 0 @latches = Array(Int16).new 2, 0
def ===(value) : Bool def ===(value) : Bool
value.is_a?(Int) && RANGE.includes?(value) value.is_a?(Int) && RANGE.includes?(value)
@ -37,7 +37,7 @@ class DMAChannels
if timer == @timers[channel].call if timer == @timers[channel].call
if @sizes[channel] > 0 if @sizes[channel] > 0
log "Timer overflow good; channel:#{channel}, timer:#{timer}".colorize.fore(:yellow) log "Timer overflow good; channel:#{channel}, timer:#{timer}".colorize.fore(:yellow)
@latches[channel] = (@fifos[channel][@positions[channel]] / 128).to_f32 @latches[channel] = @fifos[channel][@positions[channel]].to_i16
@positions[channel] = (@positions[channel] + 1) % 32 @positions[channel] = (@positions[channel] + 1) % 32
@sizes[channel] -= 1 @sizes[channel] -= 1
else else
@ -49,7 +49,8 @@ class DMAChannels
end end
end end
def get_amplitude : Tuple(Float32, Float32) # Outputs a value -0x100...0x100
def get_amplitude : Tuple(Int16, Int16)
{@latches[0], @latches[1]} {@latches[0], @latches[1]}
end end
end end