Improve GBC sound by fixing its deferred rendering

This fixes many games that use the PSG, particuarly the noise generator.
It is very noticeable in explosion/collision sounds with Sonic and Kirby
games, where the noise channel is rapidly tweaked.
This commit is contained in:
David Guillen Fandos 2023-06-15 20:12:39 +02:00
parent 2391496432
commit 1d972ec7ff
7 changed files with 60 additions and 59 deletions

View File

@ -23,6 +23,7 @@
/* Sound */
#define gbc_sound_tone_control_low(channel, regn) \
{ \
render_gbc_sound(); \
u32 initial_volume = (value >> 12) & 0x0F; \
u32 envelope_ticks = ((value >> 8) & 0x07) * 4; \
gbc_sound_channel[channel].length_ticks = 64 - (value & 0x3F); \
@ -34,12 +35,12 @@
gbc_sound_channel[channel].envelope_ticks = envelope_ticks; \
gbc_sound_channel[channel].envelope_status = (envelope_ticks != 0); \
gbc_sound_channel[channel].envelope_volume = initial_volume; \
gbc_sound_update = 1; \
write_ioreg(regn, value); \
} \
#define gbc_sound_tone_control_high(channel, regn) \
{ \
render_gbc_sound(); \
u32 rate = value & 0x7FF; \
gbc_sound_channel[channel].rate = rate; \
gbc_sound_channel[channel].frequency_step = \
@ -55,12 +56,12 @@
gbc_sound_channel[channel].envelope_initial_volume; \
} \
\
gbc_sound_update = 1; \
write_ioreg(regn, value & 0x47FF); \
} \
#define gbc_sound_tone_control_sweep() \
{ \
render_gbc_sound(); \
value &= 0x007F; \
u32 sweep_ticks = ((value >> 4) & 0x07) * 2; \
gbc_sound_channel[0].sweep_shift = value & 0x07; \
@ -68,19 +69,18 @@
gbc_sound_channel[0].sweep_status = (value != 8); \
gbc_sound_channel[0].sweep_ticks = sweep_ticks; \
gbc_sound_channel[0].sweep_initial_ticks = sweep_ticks; \
gbc_sound_update = 1; \
write_ioreg(REG_SOUND1CNT_L, value); \
} \
#define gbc_sound_wave_control() \
{ \
render_gbc_sound(); \
gbc_sound_channel[2].wave_type = (value >> 5) & 0x01; \
gbc_sound_channel[2].wave_bank = (value >> 6) & 0x01; \
gbc_sound_channel[2].master_enable = 0; \
if(value & 0x80) \
gbc_sound_channel[2].master_enable = 1; \
\
gbc_sound_update = 1; \
write_ioreg(REG_SOUND3CNT_L, value & 0x00E0); \
} \
@ -88,18 +88,19 @@ static const u32 gbc_sound_wave_volume[4] = { 0, 16384, 8192, 4096 };
#define gbc_sound_tone_control_low_wave() \
{ \
render_gbc_sound(); \
gbc_sound_channel[2].length_ticks = 256 - (value & 0xFF); \
if((value >> 15) & 0x01) \
gbc_sound_channel[2].wave_volume = 12288; \
else \
gbc_sound_channel[2].wave_volume = \
gbc_sound_wave_volume[(value >> 13) & 0x03]; \
gbc_sound_update = 1; \
write_ioreg(REG_SOUND3CNT_H, value); \
} \
#define gbc_sound_tone_control_high_wave() \
{ \
render_gbc_sound(); \
u32 rate = value & 0x7FF; \
gbc_sound_channel[2].rate = rate; \
gbc_sound_channel[2].frequency_step = \
@ -110,7 +111,6 @@ static const u32 gbc_sound_wave_volume[4] = { 0, 16384, 8192, 4096 };
gbc_sound_channel[2].sample_index = 0; \
gbc_sound_channel[2].active_flag = 1; \
} \
gbc_sound_update = 1; \
write_ioreg(REG_SOUND3CNT_X, value); \
} \
@ -118,6 +118,7 @@ static const u32 gbc_sound_wave_volume[4] = { 0, 16384, 8192, 4096 };
{ \
u32 dividing_ratio = value & 0x07; \
u32 frequency_shift = (value >> 4) & 0x0F; \
render_gbc_sound(); \
if(dividing_ratio == 0) \
{ \
gbc_sound_channel[3].frequency_step = \
@ -141,13 +142,13 @@ static const u32 gbc_sound_wave_volume[4] = { 0, 16384, 8192, 4096 };
gbc_sound_channel[3].envelope_volume = \
gbc_sound_channel[3].envelope_initial_volume; \
} \
gbc_sound_update = 1; \
write_ioreg(REG_SOUND4CNT_H, value & 0x40FF); \
} \
static void gbc_trigger_sound(u32 value)
{
u32 channel;
render_gbc_sound();
/* Trigger all 4 GBC sound channels */
for (channel = 0; channel < 4; channel++)
@ -162,6 +163,7 @@ static void gbc_trigger_sound(u32 value)
#define trigger_sound() \
{ \
render_gbc_sound(); \
timer[0].direct_sound_channels = \
((((value >> 10) & 0x01) == 0) | ((((value >> 14) & 0x01) == 0) << 1)); \
timer[1].direct_sound_channels = \
@ -181,6 +183,7 @@ static void gbc_trigger_sound(u32 value)
static void sound_control_x(u32 value)
{
render_gbc_sound();
if (value & 0x80)
{
if (sound_on != 1)
@ -375,9 +378,6 @@ RFILE *gamepak_file_large = NULL;
// Writes to these respective locations should trigger an update
// so the related subsystem may react to it.
// If GBC audio is written to:
u32 gbc_sound_update = 0;
// If the GBC audio waveform is modified:
u32 gbc_sound_wave_update = 0;
@ -885,6 +885,7 @@ cpu_alert_type function_cc write_io_register16(u32 address, u32 value)
// Sound wave RAM, flag wave table update
case REG_SOUNDWAVE_0 ... REG_SOUNDWAVE_7:
render_gbc_sound();
gbc_sound_wave_update = 1;
write_ioreg(ioreg, value);
break;

View File

@ -226,7 +226,6 @@ void memory_term(void);
u8 *load_gamepak_page(u32 physical_index);
extern u32 oam_update;
extern u32 gbc_sound_update;
extern u32 gbc_sound_wave_update;
extern dma_transfer_type dma[DMA_CHAN_CNT];

74
main.c
View File

@ -23,9 +23,8 @@
timer_type timer[4];
u32 cpu_ticks = 0;
u32 execute_cycles = 960;
s32 video_count = 960;
u32 execute_cycles = 0;
s32 video_count = 0;
u32 last_frame = 0;
u32 flush_ram_count = 0;
@ -96,7 +95,6 @@ void init_main(void)
timer[1].direct_sound_channels = TIMER_DS_CHANNEL_NONE;
cpu_ticks = 0;
execute_cycles = 960;
video_count = 960;
@ -126,82 +124,85 @@ u32 function_cc update_gba(int remaining_cycles)
remaining_cycles = 0;
reg[COMPLETED_FRAME] = 0;
if(gbc_sound_update)
{
gbc_update_count++;
update_gbc_sound(cpu_ticks);
gbc_sound_update = 0;
}
// Timers can trigger DMA (usually sound) and consume cycles
dma_cycles = update_timers(&irq_raised, completed_cycles);
// Video count tracks the video cycles remaining until the next event
video_count -= completed_cycles;
// Ran out of cycles, move to the next video area
if(video_count <= 0)
{
u32 vcount = read_ioreg(REG_VCOUNT);
u32 dispstat = read_ioreg(REG_DISPSTAT);
if((dispstat & 0x02) == 0)
// Check if we are in hrefresh (0) or hblank (1)
if ((dispstat & 0x02) == 0)
{
// Transition from hrefresh to hblank
video_count += (272);
dispstat |= 0x02;
video_count += (272); // hblank duration, 272 cycles
if((dispstat & 0x01) == 0)
// Check if we are drawing (0) or we are in vblank (1)
if ((dispstat & 0x01) == 0)
{
u32 i;
// Render the scan line
if(reg[OAM_UPDATED])
oam_update_count++;
update_scanline();
// If in visible area also fire HDMA
for(i = 0; i < 4; i++)
// Trigger the HBlank DMAs if enabled
for (i = 0; i < 4; i++)
{
if(dma[i].start_type == DMA_START_HBLANK)
dma_transfer(i, &dma_cycles);
}
}
if(dispstat & 0x10)
// Trigger the hblank interrupt, if enabled in DISPSTAT
if (dispstat & 0x10)
irq_raised |= IRQ_HBLANK;
}
else
{
// Transition from hblank to next line
// Transition from hblank to the next scan line (vdraw or vblank)
video_count += 960;
dispstat &= ~0x02;
vcount++;
if(vcount == 160)
{
// Transition from vrefresh to vblank
u32 i;
dispstat |= 0x01;
if(dispstat & 0x8)
{
irq_raised |= IRQ_VBLANK;
}
// Reinit affine transformation counters for the next frame
video_reload_counters();
for(i = 0; i < 4; i++)
// Trigger VBlank interrupt if enabled
if (dispstat & 0x8)
irq_raised |= IRQ_VBLANK;
// Trigger the VBlank DMAs if enabled
for (i = 0; i < 4; i++)
{
if(dma[i].start_type == DMA_START_VBLANK)
dma_transfer(i, &dma_cycles);
}
}
else
if(vcount == 228)
else if (vcount == 228)
{
// Transition from vblank to next screen
vcount = 0;
dispstat &= ~0x01;
/* If there's no cheat hook, run on vblank! */
if (cheat_master_hook == ~0U)
process_cheats();
/* printf("frame update (%x), %d instructions total, %d RAM flushes\n",
reg[REG_PC], instruction_count - last_frame, flush_ram_count);
last_frame = instruction_count;
@ -212,26 +213,19 @@ u32 function_cc update_gba(int remaining_cycles)
oam_update_count = 0;
flush_ram_count = 0;
update_gbc_sound(cpu_ticks);
gbc_sound_update = 0;
// Force audio generation. Need to flush samples for this frame.
render_gbc_sound();
/* If there's no cheat hook, run on vblank! */
if (cheat_master_hook == ~0U)
process_cheats();
vcount = 0;
// We completed a frame, tell the dynarec to exit to the main thread
reg[COMPLETED_FRAME] = 1;
}
// Vcount trigger (flag) and IRQ if enabled
if(vcount == (dispstat >> 8))
{
// vcount trigger
dispstat |= 0x04;
if(dispstat & 0x20)
{
irq_raised |= IRQ_VCOUNT;
}
}
else
dispstat &= ~0x04;
@ -241,7 +235,7 @@ u32 function_cc update_gba(int remaining_cycles)
write_ioreg(REG_DISPSTAT, dispstat);
}
// Flag any V/H blank interrupts.
// Flag any V/H blank interrupts, DMA IRQs, Vcount, etc.
if (irq_raised)
flag_interrupt(irq_raised);
@ -249,6 +243,8 @@ u32 function_cc update_gba(int remaining_cycles)
if (check_and_raise_interrupts())
changed_pc = 0x80000000;
// Figure out when we need to stop CPU execution. The next event is
// a video event or a timer event, whatever happens first.
execute_cycles = MAX(video_count, 0);
for (i = 0; i < 4; i++)

2
main.h
View File

@ -66,6 +66,8 @@ typedef enum
boot_bios
} boot_mode;
extern u32 gbc_update_count;
extern u32 cpu_ticks;
extern u32 execute_cycles;
extern u32 skip_next_frame;

View File

@ -109,7 +109,6 @@ bool gba_load_state(const void* src)
instruction_count = 0;
reg[COMPLETED_FRAME] = 0;
reg[OAM_UPDATED] = 1;
gbc_sound_update = 1;
return true;
}

18
sound.c
View File

@ -345,12 +345,12 @@ u32 gbc_sound_master_volume;
#define get_noise_sample_full() \
current_sample = \
((s32)(noise_table15[fp16_16_to_u32(sample_index) >> 5] << \
(fp16_16_to_u32(sample_index) & 0x1F)) >> 31) & 0x0F \
(fp16_16_to_u32(sample_index) & 0x1F)) >> 31) ^ 0x07 \
#define get_noise_sample_half() \
current_sample = \
((s32)(noise_table7[fp16_16_to_u32(sample_index) >> 5] << \
(fp16_16_to_u32(sample_index) & 0x1F)) >> 31) & 0x0F \
(fp16_16_to_u32(sample_index) & 0x1F)) >> 31) ^ 0x07 \
#define gbc_sound_render_noise(type, noise_type, envelope_op, sweep_op) \
for(i = 0; i < buffer_ticks; i++) \
@ -396,10 +396,8 @@ u32 gbc_sound_master_volume;
gs->sample_index = sample_index; \
gs->tick_counter = tick_counter; \
void update_gbc_sound(u32 cpu_ticks)
void render_gbc_sound()
{
fixed16_16 buffer_ticks = float_to_fp16_16((float)(cpu_ticks -
gbc_sound_last_cpu_ticks) * sound_frequency / GBC_BASE_RATE);
u32 i, i2;
gbc_sound_struct *gs = gbc_sound_channel;
fixed16_16 sample_index, frequency_step;
@ -410,9 +408,13 @@ void update_gbc_sound(u32 cpu_ticks)
s32 current_sample;
u16 sound_status = read_ioreg(REG_SOUNDCNT_X) & 0xFFF0;
const s8 *sample_data;
s8 *wave_bank;
u8 *wave_ram = ((u8 *)io_registers) + 0x90;
u32 tick_delta = cpu_ticks - gbc_sound_last_cpu_ticks;
fixed16_16 buffer_ticks = float_to_fp16_16((float)(tick_delta) *
sound_frequency / GBC_BASE_RATE);
if (!tick_delta)
return;
gbc_update_count++;
gbc_sound_partial_ticks += fp16_16_fractional_part(buffer_ticks);
buffer_ticks = fp16_16_to_u32(buffer_ticks);
@ -424,6 +426,7 @@ void update_gbc_sound(u32 cpu_ticks)
if(sound_on == 1)
{
s8 *wave_bank;
gs = gbc_sound_channel + 0;
if(gs->active_flag)
{
@ -446,6 +449,7 @@ void update_gbc_sound(u32 cpu_ticks)
if(gbc_sound_wave_update)
{
unsigned bank = (gs->wave_bank == 1) ? 1 : 0;
u8 *wave_ram = ((u8 *)io_registers) + 0x90;
wave_bank = wave_samples + (bank * 32);
for(i = 0, i2 = 0; i < 16; i++, i2 += 2)

View File

@ -100,7 +100,7 @@ extern u32 sound_on;
void sound_timer_queue32(u32 channel, u32 value);
unsigned sound_timer(fixed8_24 frequency_step, u32 channel);
void sound_reset_fifo(u32 channel);
void update_gbc_sound(u32 cpu_ticks);
void render_gbc_sound();
void init_sound();
unsigned sound_write_savestate(u8 *dst);
bool sound_read_savestate(const u8 *src);