511 lines
11 KiB
C++
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);
|
|
}
|
|
}
|