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

View file

@ -39,7 +39,7 @@ abstract class SoundChannel
# Called when @period reaches 0
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 write_io(index : Int, value : UInt8) : Nil

View file

@ -57,14 +57,13 @@ class Channel1 < VolumeEnvelopeChannel
end
end
def get_amplitude : Float32
# Outputs a value 0..0xF
def get_amplitude : Int16
if @enabled && @dac_enabled
dac_input = WAVE_DUTY[@duty][@wave_duty_position] * @current_volume
dac_output = (dac_input / 7.5) - 1
dac_output
WAVE_DUTY[@duty][@wave_duty_position].to_i16 * @current_volume
else
0
end.to_f32
0_i16
end
end
# 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
end
def get_amplitude : Float32
# Outputs a value 0..0xF
def get_amplitude : Int16
if @enabled && @dac_enabled
dac_input = WAVE_DUTY[@duty][@wave_duty_position] * @current_volume
dac_output = (dac_input / 7.5) - 1
dac_output
WAVE_DUTY[@duty][@wave_duty_position].to_i16 * @current_volume
else
0
end.to_f32
0_i16
end
end
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_position : UInt8 = 0
@wave_ram_sample_buffer : UInt8 = 0x00
@wave_ram_sample_buffer : Int16 = 0x00
# NR30
@wave_ram_dimension : Bool = false
@ -21,15 +21,13 @@ class Channel3 < SoundChannel
@volume_code : UInt8 = 0x00
@volume_force : Bool = false
@volume_multiplier : Float32 = 0
# NR33 / NR34
@frequency : UInt16 = 0x00
def step_wave_generation : Nil
@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_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
def frequency_timer : UInt32
@ -40,14 +38,14 @@ class Channel3 < SoundChannel
@gba.scheduler.schedule frequency_timer, ->step, Scheduler::EventType::APUChannel3
end
def get_amplitude : Float32
# Outputs a value 0..0xF
def get_amplitude : Int16
if @enabled && @dac_enabled
dac_input = @volume_multiplier * ((@wave_ram_sample_buffer >> (@wave_ram_position & 1 == 0 ? 4 : 0)) & 0x0F)
dac_output = (dac_input / 7.5) - 1
dac_output
volume_shift = (-1 + @volume_code) % 4
(((@wave_ram_sample_buffer >> (@wave_ram_position & 1 == 0 ? 4 : 0)) & 0x0F) >> volume_shift)
else
0
end.to_f32
0_i16
end
end
def read_io(index : Int) : UInt8
@ -82,15 +80,6 @@ class Channel3 < SoundChannel
when 0x73
@volume_code = (value & 0x60) >> 5
@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
@frequency = (@frequency & 0x0700) | value
when 0x75

View file

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

View file

@ -5,7 +5,7 @@ class DMAChannels
@positions = Array(Int32).new 2, 0
@sizes = Array(Int32).new 2, 0
@timers : Array(Proc(UInt16))
@latches = Array(Float32).new 2, 0
@latches = Array(Int16).new 2, 0
def ===(value) : Bool
value.is_a?(Int) && RANGE.includes?(value)
@ -21,7 +21,7 @@ class DMAChannels
def read_io(index : Int) : UInt8
0_u8
end
def write_io(index : Int, value : Byte) : Nil
channel = bit?(index, 2).to_unsafe
if @sizes[channel] < 32
@ -37,7 +37,7 @@ class DMAChannels
if timer == @timers[channel].call
if @sizes[channel] > 0
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
@sizes[channel] -= 1
else
@ -49,7 +49,8 @@ class DMAChannels
end
end
def get_amplitude : Tuple(Float32, Float32)
# Outputs a value -0x100...0x100
def get_amplitude : Tuple(Int16, Int16)
{@latches[0], @latches[1]}
end
end