cog/Frameworks/HighlyAdvanced/HighlyAdvanced/vbam/gba/Sound.cpp

511 lines
11 KiB
C++

#include <string.h>
#include "Sound.h"
#include "GBA.h"
#include "Globals.h"
#include "../common/Port.h"
#include "../apu/Gb_Apu.h"
#include "../apu/Multi_Buffer.h"
#define NR10 0x60
#define NR11 0x62
#define NR12 0x63
#define NR13 0x64
#define NR14 0x65
#define NR21 0x68
#define NR22 0x69
#define NR23 0x6c
#define NR24 0x6d
#define NR30 0x70
#define NR31 0x72
#define NR32 0x73
#define NR33 0x74
#define NR34 0x75
#define NR41 0x78
#define NR42 0x79
#define NR43 0x7c
#define NR44 0x7d
#define NR50 0x80
#define NR51 0x81
#define NR52 0x84
static inline GBA::blip_time_t blip_time(GBASystem *gba)
{
return gba->SOUND_CLOCK_TICKS - gba->soundTicks;
}
void Gba_Pcm::init(GBASystem *gba)
{
this->gba = gba;
output = 0;
last_time = 0;
last_amp = 0;
shift = 0;
}
void Gba_Pcm::apply_control( int idx )
{
shift = ~gba->ioMem [SGCNT0_H] >> (2 + idx) & 1;
int ch = 0;
if ( (gba->soundEnableFlag >> idx & 0x100) && (gba->ioMem [NR52] & 0x80) )
ch = gba->ioMem [SGCNT0_H+1] >> (idx * 4) & 3;
GBA::Blip_Buffer* out = 0;
switch ( ch )
{
case 1: out = gba->stereo_buffer->right(); break;
case 2: out = gba->stereo_buffer->left(); break;
case 3: out = gba->stereo_buffer->center(); break;
}
if ( output != out )
{
if ( output )
{
output->set_modified();
gba->pcm_synth [0].offset( blip_time(gba), -last_amp, output );
}
last_amp = 0;
output = out;
}
}
void Gba_Pcm::end_frame( GBA::blip_time_t time )
{
last_time -= time;
if ( last_time < -2048 )
last_time = -2048;
if ( output )
output->set_modified();
}
void Gba_Pcm::update( int dac )
{
if ( output )
{
GBA::blip_time_t time = blip_time(gba);
dac = (s8) dac >> shift;
int delta = dac - last_amp;
if ( delta )
{
last_amp = dac;
int filter = 0;
if ( gba->soundInterpolation )
{
// base filtering on how long since last sample was output
blip_long period = time - last_time;
int idx = (unsigned) period / 512;
if ( idx >= 3 )
idx = 3;
static int const filters [4] = { 0, 0, 1, 2 };
filter = filters [idx];
}
gba->pcm_synth [filter].offset( time, delta, output );
}
last_time = time;
}
}
void Gba_Pcm_Fifo::init(GBASystem *gba)
{
this->gba = gba;
pcm.init(gba);
}
void Gba_Pcm_Fifo::timer_overflowed( int which_timer )
{
if ( which_timer == timer && enabled )
{
/* Mother 3 fix, refined to not break Metroid Fusion */
if ( count == 16 || count == 0 )
{
// Need to fill FIFO
int saved_count = count;
CPUCheckDMA( gba, 3, which ? 4 : 2 );
if ( saved_count == 0 && count == 16 )
CPUCheckDMA( gba, 3, which ? 4 : 2 );
if ( count == 0 )
{
// Not filled by DMA, so fill with 16 bytes of silence
int reg = which ? FIFOB_L : FIFOA_L;
for ( int n = 8; n--; )
{
soundEvent(gba, reg , (u16)0);
soundEvent(gba, reg+2, (u16)0);
}
}
}
// Read next sample from FIFO
count--;
dac = fifo [readIndex];
readIndex = (readIndex + 1) & 31;
pcm.update( dac );
}
}
void Gba_Pcm_Fifo::write_control( int data )
{
enabled = (data & 0x0300) ? true : false;
timer = (data & 0x0400) ? 1 : 0;
if ( data & 0x0800 )
{
// Reset
writeIndex = 0;
readIndex = 0;
count = 0;
dac = 0;
memset( fifo, 0, sizeof fifo );
}
pcm.apply_control( which );
pcm.update( dac );
}
void Gba_Pcm_Fifo::write_fifo( int data )
{
fifo [writeIndex ] = data & 0xFF;
fifo [writeIndex+1] = data >> 8;
count += 2;
writeIndex = (writeIndex + 2) & 31;
}
static void apply_control(GBASystem *gba)
{
gba->pcm [0].pcm.apply_control( 0 );
gba->pcm [1].pcm.apply_control( 1 );
}
static int gba_to_gb_sound( int addr )
{
static const int table [0x40] =
{
0xFF10, 0,0xFF11,0xFF12,0xFF13,0xFF14, 0, 0,
0xFF16,0xFF17, 0, 0,0xFF18,0xFF19, 0, 0,
0xFF1A, 0,0xFF1B,0xFF1C,0xFF1D,0xFF1E, 0, 0,
0xFF20,0xFF21, 0, 0,0xFF22,0xFF23, 0, 0,
0xFF24,0xFF25, 0, 0,0xFF26, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0xFF30,0xFF31,0xFF32,0xFF33,0xFF34,0xFF35,0xFF36,0xFF37,
0xFF38,0xFF39,0xFF3A,0xFF3B,0xFF3C,0xFF3D,0xFF3E,0xFF3F,
};
if ( addr >= 0x60 && addr < 0xA0 )
return table [addr - 0x60];
return 0;
}
void soundEvent(GBASystem *gba, u32 address, u8 data)
{
int gb_addr = gba_to_gb_sound( address );
if ( gb_addr )
{
gba->ioMem[address] = data;
gba->gb_apu->write_register( blip_time(gba), gb_addr, data );
if ( address == NR52 )
apply_control(gba);
}
gba->ioMem[NR52] = (gba->ioMem[NR52] & 0x80) | (gba->gb_apu->read_status() & 0x7f);
// TODO: what about byte writes to SGCNT0_H etc.?
}
static void apply_volume( GBASystem *gba, bool apu_only = false )
{
if ( !apu_only )
gba->soundVolume_ = gba->soundVolume;
if ( gba->gb_apu )
{
static float const apu_vols [4] = { 0.25, 0.5, 1, 0.25 };
gba->gb_apu->volume( gba->soundVolume_ * apu_vols [gba->ioMem [SGCNT0_H] & 3] );
}
if ( !apu_only )
{
for ( int i = 0; i < 3; i++ )
gba->pcm_synth [i].volume( 0.66 / 256 * gba->soundVolume_ );
}
}
static void write_SGCNT0_H( GBASystem *gba, int data )
{
WRITE16LE( &gba->ioMem [SGCNT0_H], data & 0x770F );
gba->pcm [0].write_control( data );
gba->pcm [1].write_control( data >> 4 );
apply_volume( gba, true );
}
void soundEvent(GBASystem *gba, u32 address, u16 data)
{
switch ( address )
{
case SGCNT0_H:
write_SGCNT0_H( gba, data );
break;
case FIFOA_L:
case FIFOA_H:
gba->pcm [0].write_fifo( data );
WRITE16LE( &gba->ioMem[address], data );
break;
case FIFOB_L:
case FIFOB_H:
gba->pcm [1].write_fifo( data );
WRITE16LE( &gba->ioMem[address], data );
break;
case 0x88:
data &= 0xC3FF;
WRITE16LE( &gba->ioMem[address], data );
break;
default:
soundEvent( gba, address & ~1, (u8) (data ) ); // even
soundEvent( gba, address | 1, (u8) (data >> 8) ); // odd
break;
}
}
void soundTimerOverflow(GBASystem *gba, int timer)
{
gba->pcm [0].timer_overflowed( timer );
gba->pcm [1].timer_overflowed( timer );
}
static void end_frame( GBASystem *gba, GBA::blip_time_t time )
{
gba->pcm [0].pcm.end_frame( time );
gba->pcm [1].pcm.end_frame( time );
gba->gb_apu ->end_frame( time );
gba->stereo_buffer->end_frame( time );
}
void flush_samples(GBASystem *gba, GBA::Multi_Buffer * buffer)
{
// We want to write the data frame by frame to support legacy audio drivers
// that don't use the length parameter of the write method.
// TODO: Update the Win32 audio drivers (DS, OAL, XA2), and flush all the
// samples at once to help reducing the audio delay on all platforms.
blip_long soundBufferLen = ( gba->soundSampleRate / 60 ) * 4;
// soundBufferLen should have a whole number of sample pairs
assert( soundBufferLen % (2 * sizeof *gba->soundFinalWave) == 0 );
// number of samples in output buffer
blip_long const out_buf_size = soundBufferLen / sizeof *gba->soundFinalWave;
while ( buffer->samples_avail() )
{
long samples_read = buffer->read_samples( (GBA::blip_sample_t*) gba->soundFinalWave, out_buf_size );
if(gba->soundPaused)
soundResume(gba);
gba->output->write(gba->soundFinalWave, samples_read * sizeof *gba->soundFinalWave);
}
}
static void apply_filtering(GBASystem *gba)
{
gba->soundFiltering_ = gba->soundFiltering;
int const base_freq = (int) (32768 - gba->soundFiltering_ * 16384);
blip_long const nyquist = gba->stereo_buffer->sample_rate() / 2;
for ( int i = 0; i < 3; i++ )
{
blip_long cutoff = base_freq >> i;
if ( cutoff > nyquist )
cutoff = nyquist;
gba->pcm_synth [i].treble_eq( GBA::blip_eq_t( 0, 0, gba->stereo_buffer->sample_rate(), cutoff ) );
}
}
void psoundTickfn(GBASystem *gba)
{
if ( gba->gb_apu && gba->stereo_buffer )
{
// Run sound hardware to present
end_frame( gba, gba->SOUND_CLOCK_TICKS );
flush_samples( gba, gba->stereo_buffer );
if ( gba->soundFiltering_ != gba->soundFiltering )
apply_filtering(gba);
if ( gba->soundVolume_ != gba->soundVolume )
apply_volume(gba);
}
}
static void apply_muting(GBASystem *gba)
{
if ( !gba->stereo_buffer || !gba->ioMem )
return;
// PCM
apply_control(gba);
if ( gba->gb_apu )
{
// APU
for ( int i = 0; i < 4; i++ )
{
if ( gba->soundEnableFlag >> i & 1 )
gba->gb_apu->set_output( gba->stereo_buffer->center(),
gba->stereo_buffer->left(), gba->stereo_buffer->right(), i );
else
gba->gb_apu->set_output( 0, 0, 0, i );
}
}
}
static void reset_apu(GBASystem *gba)
{
if (gba->gb_apu)
{
gba->gb_apu->reduce_clicks(gba->soundDeclicking);
gba->gb_apu->reset( gba->gb_apu->mode_agb, true );
}
if ( gba->stereo_buffer )
gba->stereo_buffer->clear();
gba->soundTicks = gba->SOUND_CLOCK_TICKS;
}
static void remake_stereo_buffer(GBASystem *gba)
{
if ( !gba->ioMem )
return;
// Clears pointers kept to old stereo_buffer
gba->pcm [0].init(gba);
gba->pcm [1].init(gba);
// Stereo_Buffer
if (gba->stereo_buffer)
{
delete gba->stereo_buffer;
gba->stereo_buffer = 0;
}
gba->stereo_buffer = new GBA::Stereo_Buffer; // TODO: handle out of memory
gba->stereo_buffer->set_sample_rate( gba->soundSampleRate ); // TODO: handle out of memory
gba->stereo_buffer->clock_rate( gba->gb_apu->clock_rate );
// PCM
gba->pcm [0].which = 0;
gba->pcm [1].which = 1;
apply_filtering(gba);
// APU
if ( !gba->gb_apu )
{
gba->gb_apu = new GBA::Gb_Apu; // TODO: handle out of memory
reset_apu(gba);
gba->gb_apu->treble_eq( GBA::blip_eq_t( 0, 0, gba->soundSampleRate, gba->soundSampleRate / 2 ) );
}
apply_muting(gba);
apply_volume(gba);
}
void soundShutdown(GBASystem *gba)
{
// APU
if ( !gba->gb_apu )
{
delete gba->gb_apu;
gba->gb_apu = 0;
}
// Stereo_Buffer
if (gba->stereo_buffer)
{
delete gba->stereo_buffer;
gba->stereo_buffer = 0;
}
}
void soundPause(GBASystem *gba)
{
gba->soundPaused = true;
}
void soundResume(GBASystem *gba)
{
gba->soundPaused = false;
}
void soundSetVolume( GBASystem *gba, float volume )
{
gba->soundVolume = volume;
}
float soundGetVolume(GBASystem *gba)
{
return gba->soundVolume;
}
void soundSetEnable(GBASystem *gba, int channels)
{
gba->soundEnableFlag = channels;
apply_muting(gba);
}
int soundGetEnable(GBASystem *gba)
{
return (gba->soundEnableFlag & 0x30f);
}
void soundReset(GBASystem *gba)
{
remake_stereo_buffer(gba);
reset_apu(gba);
gba->soundPaused = true;
gba->SOUND_CLOCK_TICKS = GBASystem::SOUND_CLOCK_TICKS_;
gba->soundTicks = GBASystem::SOUND_CLOCK_TICKS_;
soundEvent( gba, NR52, (u8) 0x80 );
}
bool soundInit(GBASystem *gba, GBASoundOut *out)
{
gba->soundPaused = true;
gba->output = out;
return true;
}
long soundGetSampleRate(GBASystem *gba)
{
return gba->soundSampleRate;
}
void soundSetSampleRate(GBASystem *gba, long sampleRate)
{
if ( gba->soundSampleRate != sampleRate )
{
{
gba->soundSampleRate = sampleRate;
}
remake_stereo_buffer(gba);
}
}