mirror of
https://github.com/dborth/vbagx.git
synced 2024-11-29 14:04:16 +01:00
442 lines
11 KiB
C++
442 lines
11 KiB
C++
#include <string.h>
|
|
|
|
#include "../gba/Sound.h"
|
|
#include "../Util.h"
|
|
#include "gbGlobals.h"
|
|
#include "gbSound.h"
|
|
|
|
#include "../apu/Gb_Apu.h"
|
|
#include "../apu/Effects_Buffer.h"
|
|
|
|
extern int gbHardware;
|
|
extern long soundSampleRate; // current sound quality
|
|
|
|
gb_effects_config_t gb_effects_config = { false, 0.20f, 0.15f, false };
|
|
|
|
static gb_effects_config_t gb_effects_config_current;
|
|
static Simple_Effects_Buffer* stereo_buffer;
|
|
static Gb_Apu* gb_apu;
|
|
|
|
static float soundVolume_ = -1;
|
|
static int prevSoundEnable = -1;
|
|
static bool declicking = false;
|
|
|
|
int const chan_count = 4;
|
|
int const ticks_to_time = 2 * GB_APU_OVERCLOCK;
|
|
|
|
static inline blip_time_t blip_time()
|
|
{
|
|
return (SOUND_CLOCK_TICKS - soundTicks) * ticks_to_time;
|
|
}
|
|
|
|
u8 gbSoundRead( u16 address )
|
|
{
|
|
if ( gb_apu && address >= NR10 && address <= 0xFF3F )
|
|
return gb_apu->read_register( blip_time(), address );
|
|
|
|
return gbMemory[address];
|
|
}
|
|
|
|
void gbSoundEvent(register u16 address, register int data)
|
|
{
|
|
gbMemory[address] = data;
|
|
|
|
if ( gb_apu && address >= NR10 && address <= 0xFF3F )
|
|
gb_apu->write_register( blip_time(), address, data );
|
|
}
|
|
|
|
static void end_frame( blip_time_t time )
|
|
{
|
|
gb_apu ->end_frame( time );
|
|
stereo_buffer->end_frame( time );
|
|
}
|
|
|
|
static void apply_effects()
|
|
{
|
|
prevSoundEnable = soundGetEnable();
|
|
gb_effects_config_current = gb_effects_config;
|
|
|
|
stereo_buffer->config().enabled = gb_effects_config_current.enabled;
|
|
stereo_buffer->config().echo = gb_effects_config_current.echo;
|
|
stereo_buffer->config().stereo = gb_effects_config_current.stereo;
|
|
stereo_buffer->config().surround = gb_effects_config_current.surround;
|
|
stereo_buffer->apply_config();
|
|
|
|
for ( int i = 0; i < chan_count; i++ )
|
|
{
|
|
Multi_Buffer::channel_t ch = { 0, 0, 0 };
|
|
if ( prevSoundEnable >> i & 1 )
|
|
ch = stereo_buffer->channel( i );
|
|
gb_apu->set_output( ch.center, ch.left, ch.right, i );
|
|
}
|
|
}
|
|
|
|
void gbSoundConfigEffects( gb_effects_config_t const& c )
|
|
{
|
|
gb_effects_config = c;
|
|
}
|
|
|
|
static void apply_volume()
|
|
{
|
|
soundVolume_ = soundGetVolume();
|
|
|
|
if ( gb_apu )
|
|
gb_apu->volume( soundVolume_ );
|
|
}
|
|
|
|
void gbSoundTick()
|
|
{
|
|
if ( gb_apu && stereo_buffer )
|
|
{
|
|
// Run sound hardware to present
|
|
end_frame( SOUND_CLOCK_TICKS * ticks_to_time );
|
|
|
|
flush_samples(stereo_buffer);
|
|
|
|
// Update effects config if it was changed
|
|
if ( memcmp( &gb_effects_config_current, &gb_effects_config,
|
|
sizeof gb_effects_config ) || soundGetEnable() != prevSoundEnable )
|
|
apply_effects();
|
|
|
|
if ( soundVolume_ != soundGetVolume() )
|
|
apply_volume();
|
|
}
|
|
}
|
|
|
|
static void reset_apu()
|
|
{
|
|
Gb_Apu::mode_t mode = Gb_Apu::mode_dmg;
|
|
if ( gbHardware & 2 )
|
|
mode = Gb_Apu::mode_cgb;
|
|
if ( gbHardware & 8 || declicking )
|
|
mode = Gb_Apu::mode_agb;
|
|
gb_apu->reset( mode );
|
|
gb_apu->reduce_clicks( declicking );
|
|
|
|
if ( stereo_buffer )
|
|
stereo_buffer->clear();
|
|
|
|
soundTicks = SOUND_CLOCK_TICKS;
|
|
}
|
|
|
|
static void remake_stereo_buffer()
|
|
{
|
|
// Stereo_Buffer
|
|
delete stereo_buffer;
|
|
stereo_buffer = 0;
|
|
|
|
stereo_buffer = new Simple_Effects_Buffer; // TODO: handle out of memory
|
|
if ( stereo_buffer->set_sample_rate( soundSampleRate ) ) { } // TODO: handle out of memory
|
|
stereo_buffer->clock_rate( gb_apu->clock_rate );
|
|
|
|
// APU
|
|
static int const chan_types [chan_count] = {
|
|
Multi_Buffer::wave_type+1, Multi_Buffer::wave_type+2,
|
|
Multi_Buffer::wave_type+3, Multi_Buffer::mixed_type+1
|
|
};
|
|
if ( stereo_buffer->set_channel_count( chan_count, chan_types ) ) { } // TODO: handle errors
|
|
|
|
if ( !gb_apu )
|
|
{
|
|
gb_apu = new Gb_Apu; // TODO: handle errors
|
|
reset_apu();
|
|
}
|
|
|
|
apply_effects();
|
|
apply_volume();
|
|
}
|
|
|
|
void gbSoundSetDeclicking( bool enable )
|
|
{
|
|
if ( declicking != enable )
|
|
{
|
|
declicking = enable;
|
|
if ( gb_apu )
|
|
{
|
|
// Can't change sound hardware mode without resetting APU, so save/load
|
|
// state around mode change
|
|
gb_apu_state_t state;
|
|
gb_apu->save_state( &state );
|
|
reset_apu();
|
|
if ( gb_apu->load_state( state ) ) { } // ignore error
|
|
}
|
|
}
|
|
}
|
|
|
|
bool gbSoundGetDeclicking()
|
|
{
|
|
return declicking;
|
|
}
|
|
|
|
void gbSoundReset()
|
|
{
|
|
SOUND_CLOCK_TICKS = 20000; // 1/100 second
|
|
|
|
remake_stereo_buffer();
|
|
reset_apu();
|
|
|
|
soundPaused = 1;
|
|
|
|
gbSoundEvent(0xff10, 0x80);
|
|
gbSoundEvent(0xff11, 0xbf);
|
|
gbSoundEvent(0xff12, 0xf3);
|
|
gbSoundEvent(0xff14, 0xbf);
|
|
gbSoundEvent(0xff16, 0x3f);
|
|
gbSoundEvent(0xff17, 0x00);
|
|
gbSoundEvent(0xff19, 0xbf);
|
|
|
|
gbSoundEvent(0xff1a, 0x7f);
|
|
gbSoundEvent(0xff1b, 0xff);
|
|
gbSoundEvent(0xff1c, 0xbf);
|
|
gbSoundEvent(0xff1e, 0xbf);
|
|
|
|
gbSoundEvent(0xff20, 0xff);
|
|
gbSoundEvent(0xff21, 0x00);
|
|
gbSoundEvent(0xff22, 0x00);
|
|
gbSoundEvent(0xff23, 0xbf);
|
|
gbSoundEvent(0xff24, 0x77);
|
|
gbSoundEvent(0xff25, 0xf3);
|
|
|
|
if (gbHardware & 0x4)
|
|
gbSoundEvent(0xff26, 0xf0);
|
|
else
|
|
gbSoundEvent(0xff26, 0xf1);
|
|
|
|
/* workaround for game Beetlejuice */
|
|
if (gbHardware & 0x1) {
|
|
gbSoundEvent(0xff24, 0x77);
|
|
gbSoundEvent(0xff25, 0xf3);
|
|
}
|
|
|
|
int addr = 0xff30;
|
|
|
|
while(addr < 0xff40) {
|
|
gbMemory[addr++] = 0x00;
|
|
gbMemory[addr++] = 0xff;
|
|
}
|
|
}
|
|
|
|
void gbSoundSetSampleRate( long sampleRate )
|
|
{
|
|
if ( soundSampleRate != sampleRate )
|
|
{
|
|
if ( systemCanChangeSoundQuality() )
|
|
{
|
|
soundShutdown();
|
|
soundSampleRate = sampleRate;
|
|
soundInit();
|
|
}
|
|
else
|
|
{
|
|
soundSampleRate = sampleRate;
|
|
}
|
|
|
|
remake_stereo_buffer();
|
|
}
|
|
}
|
|
|
|
static struct {
|
|
int version;
|
|
gb_apu_state_t apu;
|
|
} state;
|
|
|
|
static char dummy_state [735 * 2];
|
|
|
|
#define SKIP( type, name ) { dummy_state, sizeof (type) }
|
|
|
|
#define LOAD( type, name ) { &name, sizeof (type) }
|
|
|
|
// Old save state support
|
|
|
|
static variable_desc gbsound_format [] =
|
|
{
|
|
SKIP( int, soundPaused ),
|
|
SKIP( int, soundPlay ),
|
|
SKIP( int, soundTicks ),
|
|
SKIP( int, SOUND_CLOCK_TICKS ),
|
|
SKIP( int, soundLevel1 ),
|
|
SKIP( int, soundLevel2 ),
|
|
SKIP( int, soundBalance ),
|
|
SKIP( int, soundMasterOn ),
|
|
SKIP( int, soundIndex ),
|
|
SKIP( int, soundVIN ),
|
|
SKIP( int, soundOn [0] ),
|
|
SKIP( int, soundATL [0] ),
|
|
SKIP( int, sound1Skip ),
|
|
SKIP( int, soundIndex [0] ),
|
|
SKIP( int, sound1Continue ),
|
|
SKIP( int, soundEnvelopeVolume [0] ),
|
|
SKIP( int, soundEnvelopeATL [0] ),
|
|
SKIP( int, sound1EnvelopeATLReload ),
|
|
SKIP( int, sound1EnvelopeUpDown ),
|
|
SKIP( int, sound1SweepATL ),
|
|
SKIP( int, sound1SweepATLReload ),
|
|
SKIP( int, sound1SweepSteps ),
|
|
SKIP( int, sound1SweepUpDown ),
|
|
SKIP( int, sound1SweepStep ),
|
|
SKIP( int, soundOn [1] ),
|
|
SKIP( int, soundATL [1] ),
|
|
SKIP( int, sound2Skip ),
|
|
SKIP( int, soundIndex [1] ),
|
|
SKIP( int, sound2Continue ),
|
|
SKIP( int, soundEnvelopeVolume [1] ),
|
|
SKIP( int, soundEnvelopeATL [1] ),
|
|
SKIP( int, sound2EnvelopeATLReload ),
|
|
SKIP( int, sound2EnvelopeUpDown ),
|
|
SKIP( int, soundOn [2] ),
|
|
SKIP( int, soundATL [2] ),
|
|
SKIP( int, sound3Skip ),
|
|
SKIP( int, soundIndex [2] ),
|
|
SKIP( int, sound3Continue ),
|
|
SKIP( int, sound3OutputLevel ),
|
|
SKIP( int, soundOn [3] ),
|
|
SKIP( int, soundATL [3] ),
|
|
SKIP( int, sound4Skip ),
|
|
SKIP( int, soundIndex [3] ),
|
|
SKIP( int, sound4Clock ),
|
|
SKIP( int, sound4ShiftRight ),
|
|
SKIP( int, sound4ShiftSkip ),
|
|
SKIP( int, sound4ShiftIndex ),
|
|
SKIP( int, sound4NSteps ),
|
|
SKIP( int, sound4CountDown ),
|
|
SKIP( int, sound4Continue ),
|
|
SKIP( int, soundEnvelopeVolume [2] ),
|
|
SKIP( int, soundEnvelopeATL [2] ),
|
|
SKIP( int, sound4EnvelopeATLReload ),
|
|
SKIP( int, sound4EnvelopeUpDown ),
|
|
SKIP( int, soundEnableFlag ),
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
static variable_desc gbsound_format2 [] =
|
|
{
|
|
SKIP( int, sound1ATLreload ),
|
|
SKIP( int, freq1low ),
|
|
SKIP( int, freq1high ),
|
|
SKIP( int, sound2ATLreload ),
|
|
SKIP( int, freq2low ),
|
|
SKIP( int, freq2high ),
|
|
SKIP( int, sound3ATLreload ),
|
|
SKIP( int, freq3low ),
|
|
SKIP( int, freq3high ),
|
|
SKIP( int, sound4ATLreload ),
|
|
SKIP( int, freq4 ),
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
static variable_desc gbsound_format3 [] =
|
|
{
|
|
SKIP( u8[2*735], soundBuffer ),
|
|
SKIP( u8[2*735], soundBuffer ),
|
|
SKIP( u16[735], soundFinalWave ),
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
enum {
|
|
nr10 = 0,
|
|
nr11, nr12, nr13, nr14,
|
|
nr20, nr21, nr22, nr23, nr24,
|
|
nr30, nr31, nr32, nr33, nr34,
|
|
nr40, nr41, nr42, nr43, nr44,
|
|
nr50, nr51, nr52
|
|
};
|
|
|
|
static void gbSoundReadGameOld(int version,gzFile gzFile)
|
|
{
|
|
if ( version == 11 )
|
|
{
|
|
// Version 11 didn't save any state
|
|
// TODO: same for version 10?
|
|
state.apu.regs [nr50] = 0x77; // volume at max
|
|
state.apu.regs [nr51] = 0xFF; // channels enabled
|
|
state.apu.regs [nr52] = 0x80; // power on
|
|
return;
|
|
}
|
|
|
|
// Load state
|
|
utilReadData( gzFile, gbsound_format );
|
|
|
|
if ( version >= 11 ) // TODO: never executed; remove?
|
|
utilReadData( gzFile, gbsound_format2 );
|
|
|
|
utilReadData( gzFile, gbsound_format3 );
|
|
|
|
int quality = 1;
|
|
if ( version >= 7 )
|
|
quality = utilReadInt( gzFile );
|
|
|
|
gbSoundSetSampleRate( 44100 / quality );
|
|
|
|
// Convert to format Gb_Apu uses
|
|
gb_apu_state_t& s = state.apu;
|
|
|
|
// Only some registers are properly preserved
|
|
static int const regs_to_copy [] = {
|
|
nr10, nr11, nr12, nr21, nr22, nr30, nr32, nr42, nr43, nr50, nr51, nr52, -1
|
|
};
|
|
for ( int i = 0; regs_to_copy [i] >= 0; i++ )
|
|
s.regs [regs_to_copy [i]] = gbMemory [0xFF10 + regs_to_copy [i]];
|
|
|
|
memcpy( &s.regs [0x20], &gbMemory [0xFF30], 0x10 ); // wave
|
|
}
|
|
|
|
// New state format
|
|
|
|
static variable_desc gb_state [] =
|
|
{
|
|
LOAD( int, state.version ), // room_for_expansion will be used by later versions
|
|
|
|
// APU
|
|
LOAD( u8 [0x40], state.apu.regs ), // last values written to registers and wave RAM (both banks)
|
|
LOAD( int, state.apu.frame_time ), // clocks until next frame sequencer action
|
|
LOAD( int, state.apu.frame_phase ), // next step frame sequencer will run
|
|
|
|
LOAD( int, state.apu.sweep_freq ), // sweep's internal frequency register
|
|
LOAD( int, state.apu.sweep_delay ), // clocks until next sweep action
|
|
LOAD( int, state.apu.sweep_enabled ),
|
|
LOAD( int, state.apu.sweep_neg ), // obscure internal flag
|
|
LOAD( int, state.apu.noise_divider ),
|
|
LOAD( int, state.apu.wave_buf ), // last read byte of wave RAM
|
|
|
|
LOAD( int [4], state.apu.delay ), // clocks until next channel action
|
|
LOAD( int [4], state.apu.length_ctr ),
|
|
LOAD( int [4], state.apu.phase ), // square/wave phase, noise LFSR
|
|
LOAD( int [4], state.apu.enabled ), // internal enabled flag
|
|
|
|
LOAD( int [3], state.apu.env_delay ), // clocks until next envelope action
|
|
LOAD( int [3], state.apu.env_volume ),
|
|
LOAD( int [3], state.apu.env_enabled ),
|
|
|
|
SKIP( int [13], room_for_expansion ),
|
|
|
|
// Emulator
|
|
SKIP( int [16], room_for_expansion ),
|
|
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
void gbSoundSaveGame( gzFile out )
|
|
{
|
|
gb_apu->save_state( &state.apu );
|
|
|
|
// Be sure areas for expansion get written as zero
|
|
memset( dummy_state, 0, sizeof dummy_state );
|
|
|
|
state.version = 1;
|
|
utilWriteData( out, gb_state );
|
|
}
|
|
|
|
void gbSoundReadGame( int version, gzFile in )
|
|
{
|
|
// Prepare APU and default state
|
|
reset_apu();
|
|
gb_apu->save_state( &state.apu );
|
|
|
|
if ( version > 11 )
|
|
utilReadData( in, gb_state );
|
|
else
|
|
gbSoundReadGameOld( version, in );
|
|
|
|
gb_apu->load_state( state.apu );
|
|
}
|