/*
Sony PlayStation SPU (CXD2922BQ/CXD2925Q) emulator
by pSXAuthor
MAME adaptation by R. Belmont
*/
#include "emu.h"
#include "spu.h"
#include "spureverb.h"
//
//
//
//#define warn_if_sweep_used
#define assert_if_sweep_used
//#define debug_spu_registers
//#define debug_spu
//#define log_loop_cache
//#define show_xa_debug
//#ifndef _FINAL
// #define show_cache_update
//#endif
#ifdef show_xa_debug
#define debug_xa printf
#else
#define debug_xa if (0)
#endif
// device type definition
const device_type SPU = &device_creator<spu_device>;
//
//
//
INLINE unsigned int min(unsigned int a, unsigned int b)
{
return (a > b) ? b : a;
}
INLINE unsigned int max(unsigned int a, unsigned int b)
{
return (a > b) ? a : b;
}
INLINE double mindb(double a, double b)
{
return (a > b) ? b : a;
}
INLINE double maxdb(double a, double b)
{
return (a > b) ? a : b;
}
enum spu_registers
{
spureg_voice=0,
spureg_voice_last=0x17f,
spureg_mvol_l=0x180,
spureg_mvol_r=0x182,
spureg_rvol_l=0x184,
spureg_rvol_r=0x186,
spureg_keyon=0x188,
spureg_keyoff=0x18c,
spureg_fm=0x190,
spureg_noise=0x194,
spureg_reverb=0x198,
spureg_chon=0x19c,
spureg_reverb_addr=0x1a2,
spureg_irq_addr=0x1a4,
spureg_trans_addr=0x1a6,
spureg_data=0x1a8,
spureg_ctrl=0x1aa,
spureg_status=0x1ac,
spureg_cdvol_l=0x1b0,
spureg_cdvol_r=0x1b2,
spureg_exvol_l=0x1b4,
spureg_exvol_r=0x1b6,
spureg_reverb_config=0x1c0,
spureg_last=0x1ff
};
enum spu_ctrl
{
spuctrl_irq_enable=0x40,
spuctrl_noise_shift=8,
spuctrl_noise_mask=0x3f<<spuctrl_noise_shift
};
enum
{
adpcmflag_end=1,
adpcmflag_loop=2,
adpcmflag_loop_start=4
};
struct adpcm_packet
{
unsigned char info,
flags,
data[14];
};
enum adsl_flags
{
adsl_am=0x8000,
adsl_ar_shift=8,
adsl_ar_mask=0x7f<<adsl_ar_shift,
adsl_dr_shift=4,
adsl_dr_mask=0xf<<adsl_dr_shift,
adsl_sl_mask=0xf
};
enum srrr_flags
{
srrr_sm=0x8000,
srrr_sd=0x4000,
srrr_sr_shift=6,
srrr_sr_mask=0x7f<<srrr_sr_shift,
srrr_rm=0x20,
srrr_rr_mask=0x1f
};
static const unsigned int sound_buffer_size=65536*4,
xa_sector_size=(18*28*8)<<1,
xa_buffer_sectors=16,
cdda_sector_size=2352,
cdda_buffer_sectors=16,
num_loop_cache_packets=4,
num_loop_cache_samples=num_loop_cache_packets*28,
spu_ram_size=512*1024,
spu_infinity=0xffffffff,
output_buffer_size=65536/8,
sample_loop_cache_pool_size=64,
sample_loop_cache_extend_size=64,
sample_cache_pool_size=64,
sample_cache_extend_size=64,
stream_marker_pool_size=64,
stream_marker_extend_size=64;
//
//
//
static const int filter_coef[5][2]=
{
{ 0,0 },
{ 60,0 },
{ 115,-52 },
{ 98,-55 },
{ 122,-60 },
};
//
//
//
#ifdef debug_spu_registers
#define _voice_registers(_voice) \
"voice"#_voice".voll", \
"voice"#_voice".volr", \
"voice"#_voice".pitch", \
"voice"#_voice".addr", \
"voice"#_voice".adsl", \
"voice"#_voice".srrr", \
"voice"#_voice".curvol", \
"voice"#_voice".repaddr"
#define _voice_mask_register(_name) \
_name##"0-15", \
_name##"16-23"
static const char *spu_register_names[256]=
{
_voice_registers(0),
_voice_registers(1),
_voice_registers(2),
_voice_registers(3),
_voice_registers(4),
_voice_registers(5),
_voice_registers(6),
_voice_registers(7),
_voice_registers(8),
_voice_registers(9),
_voice_registers(10),
_voice_registers(11),
_voice_registers(12),
_voice_registers(13),
_voice_registers(14),
_voice_registers(15),
_voice_registers(16),
_voice_registers(17),
_voice_registers(18),
_voice_registers(19),
_voice_registers(20),
_voice_registers(21),
_voice_registers(22),
_voice_registers(23),
"mvoll",
"mvolr",
"rvoll",
"rvolr",
"keyon0-15", "keyon16-23",
"keyoff0-15", "keyoff16-23",
"fm0-15", "fm16-23",
"noise0-15", "noise16-23",
"reverb0-15", "reverb16-23",
"chon0-15", "chon16-23",
"unknown",
"reverbaddr",
"irqaddr",
"transaddr",
"data",
"ctrl",
"statusl",
"statush",
"cdvoll",
"cdvolr",
"exvoll",
"exvolr"
};
const char *get_register_name(const unsigned int addr)
{
return spu_register_names[(addr&0x1ff)>>1];
}
#endif
//**************************************************************************
// GLOBAL VARIABLES
//**************************************************************************
reverb_params *spu_reverb_cfg=NULL;
float spu_device::freq_multiplier=1.0f;
//**************************************************************************
// DEVICE CONFIGURATION
//**************************************************************************
class adpcm_decoder
{
int l0,l1;
public:
adpcm_decoder()
{
reset();
}
adpcm_decoder(const adpcm_decoder &other)
{
operator =(other);
}
adpcm_decoder &operator =(const adpcm_decoder &other)
{
l0=other.l0;
l1=other.l1;
return *this;
}
void reset()
{
l0=l1=0;
}
signed short *decode_packet(adpcm_packet *ap, signed short *dp);
};
//
//
//
struct spu_device::sample_cache
{
public:
unsigned int start,
end,
invalid_start,
invalid_end,
loopaddr,
last_update_end;
signed short *data,*loop,*dend;
adpcm_decoder decoder, update_decoder;
mutable int ref_count;
bool valid,
is_loop;
sample_loop_cache *loop_cache;
static unsigned int cache_size;
sample_cache()
: invalid_start(0xffffffff),
invalid_end(0),
last_update_end(0xffffffff),
data(NULL),
ref_count(0),
valid(false),
is_loop(false),
loop_cache(NULL)
{
}
~sample_cache();
void add_ref() const { ref_count++; }
void remove_ref() const
{
ref_count--;
if (ref_count==0)
{
cache_size-=(dend-data)<<1;
global_free(this);
}
}
signed short *get_sample_pointer(const unsigned int addr);
bool get_sample_pointer(const unsigned int addr, cache_pointer *cp);
bool get_loop_pointer(cache_pointer *cp);
unsigned int get_sample_address(const signed short *ptr) const;
sample_loop_cache *find_loop_cache(const unsigned int lpend, const unsigned int lpstart);
void add_loop_cache(sample_loop_cache *lc);
bool is_valid_pointer(signed short *ptr) const;
bool try_update(spu_device *spu);
};
unsigned int spu_device::sample_cache::cache_size;
//
//
//
struct spu_device::sample_loop_cache
{
public:
unsigned int loopend,
loopstart,
len;
signed short data[num_loop_cache_samples];
sample_loop_cache *next;
sample_loop_cache()
: next(NULL)
{
sample_cache::cache_size+=num_loop_cache_samples<<1;
}
~sample_loop_cache()
{
sample_cache::cache_size-=num_loop_cache_samples<<1;
#ifdef log_loop_cache
log(log_spu,"spu: destroy loop cache %08x\n",this);
#endif
}
};
//
//
//
struct spu_device::cache_pointer
{
signed short *ptr;
sample_cache *cache;
cache_pointer()
: ptr(NULL),
cache(NULL)
{
}
cache_pointer(const cache_pointer &other)
: ptr(NULL),
cache(NULL)
{
operator =(other);
}
cache_pointer(signed short *_ptr, sample_cache *_cache)
: ptr(_ptr),
cache(_cache)
{
if (cache) cache->add_ref();
}
~cache_pointer()
{
reset();
}
void reset();
cache_pointer &operator =(const cache_pointer &other);
bool update(spu_device *spu);
unsigned int get_address() const
{
if (cache)
{
return cache->get_sample_address(ptr);
} else
{
return -1;
}
}
operator bool() const { return cache!=NULL; }
bool is_valid() const { return ((cache) && (cache->is_valid_pointer(ptr))); }
};
//
//
//
struct spu_device::voiceinfo
{
cache_pointer play,loop;
sample_loop_cache *loop_cache;
unsigned int dptr,
lcptr;
int env_state;
float env_ar,
env_dr,
env_sr,
env_rr,
env_sl,
env_level,
env_delta,
//>>
sweep_vol[2],
sweep_rate[2];
int vol[2];
//<<
unsigned int pitch,
samplestoend,
samplestoirq,
envsamples;
bool hitirq,
inloopcache,
forceloop,
_pad;
INT64 keyontime;
};
//
//
//
class stream_buffer
{
struct stream_marker
{
public:
unsigned int sector,
offset;
stream_marker *next,
*prev;
};
unsigned char *buffer;
unsigned int head,
tail,
in,
sector_size,
num_sectors,
buffer_size;
stream_marker *marker_head,
*marker_tail;
public:
stream_buffer(const unsigned int _sector_size,
const unsigned int _num_sectors)
: head(0),
tail(0),
in(0),
sector_size(_sector_size),
num_sectors(_num_sectors),
marker_head(NULL),
marker_tail(NULL)
{
buffer_size=sector_size*num_sectors;
buffer=new unsigned char [buffer_size];
memset(buffer,0,buffer_size);
}
~stream_buffer()
{
flush_all();
global_free(buffer);
}
unsigned char *add_sector(const unsigned int sector)
{
stream_marker *xam=new stream_marker;
xam->sector=sector;
xam->offset=head;
xam->next=NULL;
xam->prev=marker_tail;
if (marker_tail)
{
marker_tail->next=xam;
} else
{
marker_head=xam;
}
marker_tail=xam;
unsigned char *ret=buffer+head;
head=(head+sector_size)%buffer_size;
in+=sector_size;
return ret;
}
void flush(const unsigned int sector)
{
// Remove markers from the end of the buffer if they are after
// the specified sector
while ((marker_tail) && (marker_tail->sector>=sector))
{
// debug_xa("flushing: %d\n", marker_tail->sector);
stream_marker *xam=marker_tail;
head=xam->offset;
marker_tail=xam->prev;
if (marker_tail) marker_tail->next=NULL;
global_free(xam);
}
// Set marker head to NULL if the list is now empty
if (! marker_tail) marker_head=NULL;
// Adjust buffer size counter
int sz=(head-tail);
if (sz<0) sz+=buffer_size;
assert(sz<=(int)in);
in=sz;
}
void flush_all()
{
// NOTE: ??what happens to the markers??
while (marker_head)
{
stream_marker *m=marker_head;
marker_head=marker_head->next;
global_free(m);
}
marker_head=marker_tail=NULL;
head=tail=in=0;
}
void delete_markers(const unsigned int oldtail)
{
while (marker_head)
{
int olddist=marker_head->offset-oldtail,
dist=marker_head->offset-tail;
if (olddist<0) olddist+=buffer_size;
if (dist<0) dist+=buffer_size;
bool passed=(((olddist==0) && (dist!=0)) || (dist>olddist));
if (! passed) break;
// debug_xa("passed: %d\n",marker_head->sector);
stream_marker *xam=marker_head;
marker_head=xam->next;
global_free(xam);
if (marker_head) marker_head->prev=NULL;
}
if (! marker_head) marker_head=marker_tail=NULL;
}
unsigned int get_bytes_in() const { return in; }
unsigned int get_bytes_free() const { return buffer_size-in; }
unsigned char *get_tail_ptr() const { return buffer+tail; }
unsigned char *get_tail_ptr(const unsigned int offset) const
{
return buffer+((tail+offset)%buffer_size);
}
unsigned int get_tail_offset() const { return tail; }
void increment_tail(const unsigned int offset)
{
tail=(tail+offset)%buffer_size;
in-=offset;
}
};
//
//
//
static inline int clamp(const int v)
{
if (v<-32768) return -32768;
if (v>32767) return 32767;
return v;
}
//
//
//
spu_device::sample_cache::~sample_cache()
{
global_free(data);
while (loop_cache)
{
sample_loop_cache *lc=loop_cache;
loop_cache=lc->next;
global_free(lc);
}
}
//
//
//
signed short *spu_device::sample_cache::get_sample_pointer(const unsigned int addr)
{
if ((addr>=start) && (addr<end))
{
return data+(((addr-start)>>4)*28);
} else
{
return NULL;
}
}
//
//
//
bool spu_device::sample_cache::get_sample_pointer(const unsigned int addr, cache_pointer *cp)
{
cp->reset();
if ((cp->ptr=get_sample_pointer(addr)))
{
cp->cache=this;
add_ref();
return true;
}
return false;
}
//
//
//
bool spu_device::sample_cache::get_loop_pointer(cache_pointer *cp)
{
cp->reset();
if ((cp->ptr=loop))
{
cp->cache=this;
add_ref();
return true;
}
return false;
}
//
//
//
unsigned int spu_device::sample_cache::get_sample_address(const signed short *ptr) const
{
if ((ptr>=data) && (ptr<=dend))
{
return start+(((ptr-data)/28)<<4);
} else
{
return -1;
}
}
//
//
//
spu_device::sample_loop_cache *spu_device::sample_cache::find_loop_cache(const unsigned int lpend, const unsigned int lpstart)
{
sample_loop_cache *lc;
for (lc=loop_cache; lc; lc=lc->next)
if ((lc->loopend==lpend) && (lc->loopstart==lpstart)) break;
return lc;
}
//
//
//
void spu_device::sample_cache::add_loop_cache(sample_loop_cache *lc)
{
lc->next=loop_cache;
loop_cache=lc;
}
//
//
//
bool spu_device::sample_cache::is_valid_pointer(signed short *ptr) const
{
if ((ptr>=data) && (data<=dend)) return true;
for (sample_loop_cache *slc=loop_cache; slc; slc=slc->next)
if ((ptr>=slc->data) && (ptr<(slc->data+num_loop_cache_samples)))
return true;
return false;
}
//
//
//
bool spu_device::sample_cache::try_update(spu_device *spu)
{
if ((invalid_start>=start) && (invalid_end<=end))
{
adpcm_packet *ap=(adpcm_packet *)(spu->spu_ram+start);
unsigned int a;
unsigned int loop=0;
for (a=start; a<=end; a+=16, ap++)
{
if ((ap->flags&adpcmflag_loop_start) && (ap->flags&adpcmflag_loop)) loop=a;
if (ap->flags&adpcmflag_end) break;
}
if ((a==(end-16)) && (loop==loopaddr))
{
#ifdef show_cache_update
printf("updating %p: ",this);
#endif
if (invalid_start==start)
{
#ifdef show_cache_update
printf("using end values");
#endif
update_decoder=decoder;
} else
if (invalid_start!=last_update_end)
{
#ifdef show_cache_update
printf("resetting decoder (istrt=%08x lupd=%08x)",invalid_start,last_update_end);
#endif
update_decoder.reset();
}
#ifdef show_cache_update
printf("\n");
#endif
signed short *dp=data+(((invalid_start-start)>>4)*28);
ap=(adpcm_packet *)(spu->spu_ram+invalid_start);
for (a=invalid_start; a<invalid_end; a+=16, ap++)
dp=update_decoder.decode_packet(ap,dp);
if (invalid_end==end)
{
#ifdef show_cache_update
printf("updating end values\n");
#endif
decoder=update_decoder;
}
last_update_end=invalid_end;
for (sample_loop_cache *lc=loop_cache; lc; lc=lc->next)
{
if (invalid_start==lc->loopstart)
{
adpcm_decoder tmp=decoder;
dp=lc->data;
signed short *dpend=dp+lc->len;
unsigned int adr=lc->loopstart;
for (unsigned int i=0; ((i<num_loop_cache_packets) && (dp<dpend)); i++, adr+=16)
dp=tmp.decode_packet((adpcm_packet *)(spu->spu_ram+adr),dp);
}
}
invalid_end=0;
invalid_start=0xffffffff;
valid=true;
for (a=start; a<end; a+=16, ap++)
{
spu->cache[a>>4]=this;
}
add_ref();
return true;
}
}
return false;
}
//
//
//
void spu_device::cache_pointer::reset()
{
if (cache)
{
ptr=NULL;
cache->remove_ref();
cache=NULL;
}
}
//
//
//
spu_device::cache_pointer &spu_device::cache_pointer::operator =(const cache_pointer &other)
{
if (cache) cache->remove_ref();
ptr=other.ptr;
cache=other.cache;
if (cache) cache->add_ref();
return *this;
}
//
//
//
bool spu_device::cache_pointer::update(spu_device *spu)
{
if ((cache) && (! cache->valid))
{
/* log(log_spu,"cache_pointer::update: block %08x-%08x invalidated %08x-%08x\n",
cache->start,
cache->end,
cache->invalid_start,
cache->invalid_end);*/
if (! cache->try_update(spu))
{
// Cache is invalid, calculate play address offset from start of
// old cache block
unsigned int off=ptr-cache->data,
addr=cache->start;
// Release cache block and get updated one
spu->translate_sample_addr(addr,this);
// Calculate play address in new cache block
ptr=cache->data+off;
if (ptr>=cache->dend)
{
// Play address is out of bounds in new cache block, release it and get a
// new one starting at the current play address
spu->translate_sample_addr(addr+((off/28)<<4),this);
}
}
}
// Return false if we do not have a cache block or the play address is invalid
if ((cache) && ((ptr>=cache->data) && (ptr<cache->dend)))
{
return true;
} else
{
reset();
return false;
}
}
//
//
//
signed short *adpcm_decoder::decode_packet(adpcm_packet *ap, signed short *dp)
{
int shift=ap->info&0xf,
filter=ap->info>>4,
f0=filter_coef[filter][0],
f1=filter_coef[filter][1];
for (int i=0; i<14; i++)
{
unsigned char b=ap->data[i];
short bl=(b&0xf)<<12,
bh=(b>>4)<<12;
bl=(bl>>shift)+(((l0*f0)+(l1*f1)+32)>>6);
*dp++=bl;
l1=l0;
l0=bl;
bh=(bh>>shift)+(((l0*f0)+(l1*f1)+32)>>6);
*dp++=bh;
l1=l0;
l0=bh;
}
return dp;
}
//
//
//
static int shift_register15(int &shift)
{
int bit0, bit1, bit14;
bit0 = shift & 1;
bit1 = (shift & 2) >> 1;
bit14 = (bit0 ^ bit1) ^ 1;
shift >>= 1;
shift |= (bit14 << 14);
return bit0;
}
//
//
//
//-------------------------------------------------
// spu_device - constructor
//-------------------------------------------------
spu_device::spu_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) :
device_t(mconfig, SPU, "SPU", tag, owner, clock),
device_sound_interface(mconfig, *this),
m_irq_handler(*this),
dirty_flags(-1),
status_enabled(false),
xa_voll(0x8000),
xa_volr(0x8000),
changed_xa_vol(0)
{
}
//-------------------------------------------------
// static_set_irqf - configuration helper to set
// the IRQ callback
//-------------------------------------------------
void spu_device::device_start()
{
m_irq_handler.resolve_safe();
voice=new voiceinfo [24];
spu_ram=new unsigned char [spu_ram_size];
xa_buffer=new stream_buffer(xa_sector_size,xa_buffer_sectors);
cdda_buffer=new stream_buffer(cdda_sector_size,cdda_buffer_sectors);
init_stream();
cache=new sample_cache *[spu_ram_size>>4];
memset(cache,0,(spu_ram_size>>4)*sizeof(sample_cache *));
// register save state stuff
// per-voice variables
for (int v = 0; v < 24; v++)
{
save_item(NAME(spureg.voice[v].vol_l), v);
save_item(NAME(spureg.voice[v].vol_r), v);
save_item(NAME(spureg.voice[v].pitch), v);
save_item(NAME(spureg.voice[v].addr), v);
save_item(NAME(spureg.voice[v].adsl), v);
save_item(NAME(spureg.voice[v].srrr), v);
save_item(NAME(spureg.voice[v].curvol), v);
save_item(NAME(spureg.voice[v].repaddr), v);
}
// SPU globals
save_item(NAME(spureg.mvol_l));
save_item(NAME(spureg.mvol_r));
save_item(NAME(spureg.rvol_l));
save_item(NAME(spureg.rvol_r));
save_item(NAME(spureg.keyon));
save_item(NAME(spureg.keyoff));
save_item(NAME(spureg.fm));
save_item(NAME(spureg.noise));
save_item(NAME(spureg.reverb));
save_item(NAME(spureg.chon));
save_item(NAME(spureg._unknown));
save_item(NAME(spureg.reverb_addr));
save_item(NAME(spureg.irq_addr));
save_item(NAME(spureg.trans_addr));
save_item(NAME(spureg.data));
save_item(NAME(spureg.ctrl));
save_item(NAME(spureg.status));
save_item(NAME(spureg.cdvol_l));
save_item(NAME(spureg.cdvol_r));
save_item(NAME(spureg.exvol_l));
save_item(NAME(spureg.exvol_r));
save_pointer(NAME(spu_ram), spu_ram_size);
}
void spu_device::device_reset()
{
cur_reverb_preset = NULL;
cur_frame_sample = 0;
sample_cache::cache_size = 0;
status_enabled = false;
xa_voll = xa_volr = 0x8000;
dirty_flags = -1;
changed_xa_vol = 0;
xa_cnt=0;
xa_freq=0;
xa_channels=2;
xa_spf=0;
xa_out_ptr=0;
xa_playing=false;
memset(xa_last,0,sizeof(xa_last));
cdda_cnt=0;
cdda_playing=false;
memset(spu_ram,0,spu_ram_size);
memset(reg,0,0x200);
memset(voice,0,sizeof(voiceinfo)*24);
spureg.status|=(1<<7)|(1<<10);
memset(cache,0,(spu_ram_size>>4)*sizeof(sample_cache *));
for (unsigned int i=0; i<4; i++)
output_buf[i]=new unsigned char [output_buffer_size];
output_head=output_tail=output_size=0;
noise_t=0;
noise_seed=12345;
noise_cur=shift_register15(noise_seed)?0x7fff:0x8000;
}
void spu_device::device_post_load()
{
// invalidate the SPURAM cache
invalidate_cache(0, spu_ram_size);
flush_output_buffer();
// mark everything dirty
dirty_flags = -1;
// kill and reallocate reverb to avoid artifacts
global_free(rev);
rev = new reverb(44100);
// and do some update processing
update_reverb();
update_key();
update_voice_state();
update_irq_event();
}
//
//
//
void spu_device::device_stop()
{
for (unsigned int i=0; i<4; i++)
global_free(output_buf[i]);
kill_stream();
global_free(spu_ram);
invalidate_cache(0,spu_ram_size);
global_free(cache);
global_free(xa_buffer);
global_free(cdda_buffer);
global_free(voice);
}
//
//
//
void spu_device::init_stream()
{
const unsigned int hz=44100;
m_stream = machine().sound().stream_alloc(*this, 0, 2, hz);
rev=new reverb(hz);
cdda_freq=(unsigned int)((44100.0f/(float)hz)*4096.0f);
freq_multiplier=(float)spu_base_frequency_hz/(float)hz;
}
//
//
//
void spu_device::kill_stream()
{
global_free(rev);
rev=NULL;
}
//
//
//
void spu_device::reinit_sound()
{
kill_stream();
init_stream();
flush_output_buffer();
dirty_flags|=dirtyflag_voice_mask;
}
//
//
//
void spu_device::kill_sound()
{
kill_stream();
}
//
//
//
READ16_MEMBER( spu_device::read )
{
unsigned short ret=0, *rp=(unsigned short *)(reg+((offset*2)&0x1ff));
m_stream->update();
ret=*rp;
#ifdef debug_spu_registers
printf("spu: read word %08x = %04x [%s]\n",
offset*2,
ret,
get_register_name(offset*2));
#endif
return ret;
}
//
//
//
WRITE16_MEMBER( spu_device::write )
{
#ifdef debug_spu_registers
printf("spu: write %08x = %04x [%s]\n",
offset*2,
data,
get_register_name(offset*2));
#endif
m_stream->update();
const unsigned int a=(offset*2)&0x1ff;
switch (a)
{
case spureg_trans_addr:
spureg.trans_addr=data;
taddr=data<<3;
break;
case spureg_data:
dirty_flags|=dirtyflag_ram;
write_data(data);
break;
default:
{
unsigned short *rp=(unsigned short *)(reg+a);
if ((a==spureg_irq_addr) ||
((a==spureg_ctrl) && ((rp[0]^data)&spuctrl_irq_enable)))
dirty_flags|=dirtyflag_irq;
*rp=data;
break;
}
}
if ((a>spureg_reverb_config) && (a<=spureg_last))
dirty_flags|=dirtyflag_reverb;
if (a<=spureg_voice_last)
{
unsigned int v=(a>>4),r=(a&0xf);
if (r==0xe)
{
voice[v].forceloop=true;
}
dirty_flags|=(1<<v);
}
update_key();
update_vol(a);
update_voice_state();
update_irq_event();
}
//
//
//
void spu_device::update_vol(const unsigned int addr)
{
if (addr<0x180)
{
unsigned int ch=(addr&0xf)>>1;
if (ch<2)
{
unsigned int v=addr>>4;
unsigned short newval=*(unsigned short *)(reg+addr);
if (newval&0x8000)
{
#if 0
printf("cur=%04x on=%d",voice[v].vol[ch],(spureg.chon>>ch)&1);
switch ((newval>>13)&3)
{
case 0: printf("linear inc: phase=%d val=%02x\n",(newval>>12)&1,newval&0x7f); break;
case 1: printf("linear dec: phase=%d val=%02x\n",(newval>>12)&1,newval&0x7f); break;
case 2: printf("exp inc: phase=%d val=%02x\n",(newval>>12)&1,newval&0x7f); break;
case 3: printf("exp dec: phase=%d val=%02x\n",(newval>>12)&1,newval&0x7f); break;
}
#endif
}
else
{
voice[v].vol[ch]=((int)newval<<17)>>17;
}
}
}
}
//
//
//
void spu_device::write_data(const unsigned short data)
{
#ifdef debug_spu_registers
printf("spu: write data %04x @ %04x\n",data,taddr);
#endif
assert(taddr<spu_ram_size);
if (cache[taddr>>4]) flush_cache(cache[taddr>>4],taddr,taddr+2);
*((unsigned short *)(spu_ram+taddr))=data;
taddr+=2;
}
//
//
//
void spu_device::update_key()
{
dirty_flags|=((spureg.keyon|spureg.keyoff)&dirtyflag_voice_mask);
if (spureg.keyoff)
{
unsigned int d=spureg.keyoff;
for (int i=0; i<24; i++, d>>=1)
if (d&1) key_off(i);
}
if (spureg.keyon)
{
unsigned int d=spureg.keyon;
for (int i=0; i<24; i++, d>>=1)
if (d&1) key_on(i);
spureg.chon|=spureg.keyon;
}
spureg.keyon=spureg.keyoff=0;
}
//
//
//
void spu_device::flush_cache(sample_cache *sc,
const unsigned int istart,
const unsigned int iend)
{
for (unsigned int a=sc->start; a<sc->end; a+=16)
cache[a>>4]=NULL;
/* log_static(log_spu,"cache_invalidate: %08x->%08x\n",
sc->start,
sc->end);*/
sc->invalid_start=min(sc->invalid_start,istart);
sc->invalid_end=max(sc->invalid_end,iend);
sc->valid=false;
sc->remove_ref();
}
//
//
//
void spu_device::invalidate_cache(const unsigned int st, const unsigned int en)
{
for (unsigned int a=st; a<en; a+=16)
if (cache[a>>4]) flush_cache(cache[a>>4],st,en);
}
//
//
//
spu_device::sample_cache *spu_device::get_sample_cache(const unsigned int addr)
{
// log_static(log_spu,"get_sample_cache: %08x\n",addr);
assert(addr<spu_ram_size);
sample_cache *sc=cache[addr>>4];
if (sc) return sc;
unsigned int loop=0;
sc=new sample_cache;
sc->valid=true;
sc->start=addr;
sc->loop=NULL;
adpcm_packet *ap=(adpcm_packet *)(spu_ram+sc->start);
unsigned int a;
for (a=addr; a<(512*1024); a+=16, ap++)
{
if (cache[a>>4]) flush_cache(cache[a>>4],a,a+16);
cache[a>>4]=sc;
if ((ap->flags&adpcmflag_loop_start) && (ap->flags&adpcmflag_loop)) loop=a;
if (ap->flags&adpcmflag_end) break;
}
if (ap->flags&adpcmflag_loop) sc->is_loop=true;
sc->end=min(spu_ram_size,a+16);
unsigned int sz=((sc->end-sc->start)>>4)*28;
sc->data=new signed short [sz];
sample_cache::cache_size+=sz<<1;
sc->loopaddr=loop;
if (loop) sc->loop=sc->data+(((loop-sc->start)>>4)*28);
signed short *dp=sc->data;
ap=(adpcm_packet *)(spu_ram+sc->start);
for (a=sc->start; a<sc->end; a+=16, ap++)
dp=sc->decoder.decode_packet(ap,dp);
sc->dend=dp;
sc->add_ref();
/* log_static(log_spu,"cache_add: %08x->%08x\n",
sc->start,
sc->end);*/
return sc;
}
//
//
//
bool spu_device::translate_sample_addr(const unsigned int addr, cache_pointer *cp)
{
assert((addr&0xf)==0);
cp->reset();
if ((cp->cache=get_sample_cache(addr)))
{
cp->ptr=cp->cache->data+(((addr-cp->cache->start)>>4)*28);
cp->cache->add_ref();
return true;
}
return false;
}
//
// Get distance in input samples to next IRQ for voice
//
unsigned int spu_device::get_irq_distance(const voiceinfo *vi)
{
if (spureg.ctrl&spuctrl_irq_enable)
{
unsigned int irq_addr=spureg.irq_addr<<3;
signed short *irq_ptr;
if ((irq_ptr=vi->play.cache->get_sample_pointer(irq_addr)))
{
// IRQ address is inside this voices current cache block. Return distance
// if current play address is lower, or equal (and irq has not already
// triggered)
if ((vi->play.ptr<irq_ptr) ||
((! vi->hitirq) && (vi->play.ptr==irq_ptr)))
{
return irq_ptr-vi->play.ptr;
}
}
if ((vi->loop) &&
(irq_ptr=vi->loop.cache->get_sample_pointer(irq_addr)) &&
(irq_ptr>=vi->loop.ptr))
{
// IRQ address is inside this voices loop cache, return distance
return (vi->play.cache->dend-vi->play.ptr)+
(irq_ptr-vi->loop.ptr);
}
}
// IRQs not enabled, or IRQ address not reachable by voice, distance is spu_infinity
return spu_infinity;
}
//
//
//
void spu_device::update_voice_events(voiceinfo *vi)
{
if (vi->pitch)
{
// Calculate time until end of sample in output samples
vi->samplestoend=(unsigned int)((((INT64)(vi->play.cache->dend-vi->play.ptr)<<12)-vi->dptr)+(vi->pitch-1))/vi->pitch;
if (vi->inloopcache)
{
// Voice is inside loop cache, return time until end of that if lower
assert(vi->lcptr<vi->loop_cache->len);
vi->samplestoend=min(vi->samplestoend,
(unsigned int)((((INT64)(vi->loop_cache->len-vi->lcptr)<<12)-vi->dptr)+(vi->pitch-1))/vi->pitch);
}
// Calculate time until next IRQ in output samples
unsigned int irqdist=get_irq_distance(vi);
if (irqdist!=spu_infinity)
{
// Convert IRQ input sample distance to output samples
vi->samplestoirq=(unsigned int)(((((INT64)irqdist)<<12)-vi->dptr)+(vi->pitch-1))/vi->pitch;
} else
{
vi->samplestoirq=spu_infinity;
}
} else
{
// Voice pitch is 0, distance to sample end and IRQ is spu_infinity
vi->samplestoend=vi->samplestoirq=spu_infinity;
}
}
//
//
//
bool spu_device::update_voice_state(const unsigned int v)
{
voicereg *vr=&spureg.voice[v];
voiceinfo *vi=&voice[v];
// Update sample cache if necessary
if (! vi->play.update(this))
return false;
assert(vi->play.ptr<vi->play.cache->dend);
// Get pitch from voice register and apply frequency multiplier if
// there is one in effect
vi->pitch=vr->pitch;
vi->pitch=(unsigned int)(vi->pitch*freq_multiplier);
// Update event times
update_voice_events(vi);
return true;
}
//
//
//
spu_device::sample_loop_cache *spu_device::get_loop_cache(sample_cache *cache, const unsigned int lpen, sample_cache *lpcache, const unsigned int lpst)
{
// Check for existing loop cache
sample_loop_cache *ret=lpcache->find_loop_cache(lpen,lpst);
if (! ret)
{
// No loop cache exists for this address pair, create a new one
sample_loop_cache *lc=new sample_loop_cache;
lc->loopend=lpen;
lc->loopstart=lpst;
lpcache->add_loop_cache(lc);
ret=lc;
// Decode samples from start address using decoder state at end address
unsigned int adr=lpst;
adpcm_decoder tmp=cache->decoder;
signed short *dp=lc->data;
for (unsigned int i=0; ((i<num_loop_cache_packets) &&
(adr<lpcache->end)); i++, adr+=16)
dp=tmp.decode_packet((adpcm_packet *)(spu_ram+adr),dp);
#ifdef log_loop_cache
log(log_spu,"spu: add loop cache %08x %08x->%08x (end at %08x)\n",lc,lpen,lpst,adr);
#endif
lc->len=dp-lc->data;
}
return ret;
}
//
//
//
void spu_device::update_voice_loop(const unsigned int v)
{
// voicereg *vr=&spureg.voice[v];
voiceinfo *vi=&voice[v];
unsigned int ra = 0;
// Check for looping using the voices repeat address register and get
// a pointer to the loop position if enabled
vi->loop.reset();
// If loop address is not forced get the loop pointer from the cache
// block (if present)
if ((! voice[v].forceloop) &&
(vi->play.cache->get_loop_pointer(&vi->loop)))
{
ra=vi->play.cache->loopaddr;
}
// Otherwise use the address set in repaddr (if set)
if ((! vi->loop) && (vi->play.cache->is_loop))
{
ra=spureg.voice[v].repaddr<<3;
ra=(ra+0xf)&~0xf;
const adpcm_packet *ap=ra?(adpcm_packet *)(spu_ram+ra):NULL;
if (ap)
{
if (ap->flags&adpcmflag_loop)
{
// Repeat address points to a block with loop flag set
if (! vi->play.cache->get_sample_pointer(ra,&vi->loop))
{
// Repeat address is in a different block
translate_sample_addr(ra,&vi->loop);
}
}
}
}
// Update loop cache
if (vi->loop)
{
vi->loop_cache=get_loop_cache(vi->play.cache,vi->play.cache->end,vi->loop.cache,ra);
}
}
//
//
//
void spu_device::update_voice_state()
{
// If RAM or irq state is dirty make all voices dirty
if (dirty_flags&(dirtyflag_ram|dirtyflag_irq))
{
dirty_flags|=dirtyflag_voice_mask;
dirty_flags&=~(dirtyflag_ram|dirtyflag_irq);
}
// Update state for dirty voices
if (dirty_flags&dirtyflag_voice_mask)
{
unsigned int voicemask=1;
for (unsigned int i=0; i<24; i++, voicemask<<=1)
if (dirty_flags&voicemask)
{
update_voice_state(i);
dirty_flags&=~voicemask;
}
}
}
//
// Process voice state and build output segments
//
// Input: const unsigned int v Voice number
// const unsigned int sz Amount of time to process (in output samples)
// unsigned int *tleft Returned number of output samples remaining
//
// Output: bool true if voice is still playing
//
bool spu_device::process_voice(const unsigned int v,
const unsigned int sz,
void *ptr,
void *fmnoise_ptr,
void *outxptr,
unsigned int *tleft)
{
bool ret=true;
unsigned int voice_mask=1<<v,
num=sz,
off=0;
bool noise=((spureg.noise&voice_mask)!=0),
fm=((spureg.fm&voice_mask)!=0);
voiceinfo *vi=&voice[v];
// Early exit if we don't have a sample cache block
if (! vi->play)
{
*tleft=sz;
return false;
}
// Generate samples
while (num)
{
// Play up to end of sample, envelope event, or IRQ, whichever comes first
unsigned int ntoplay=fm?1:num,
nextevent=min(vi->samplestoend,
min(vi->samplestoirq,vi->envsamples));
ntoplay=min(ntoplay,nextevent);
if (ntoplay)
{
signed short *noisep=NULL;
if (fm)
{
int fmv=((signed short *)fmnoise_ptr)[off<<1];
vi->pitch=spureg.voice[v].pitch;
vi->pitch=(unsigned int)(vi->pitch*freq_multiplier);
vi->pitch=(vi->pitch*(fmv+32768))>>15;
} else
if (noise)
{
noisep=(signed short *)fmnoise_ptr;
noisep+=(off<<1);
}
signed short *dp=(signed short *)ptr,
*outxp=(signed short *)outxptr;
dp+=off<<1;
if (outxp) outxp+=off<<1;
generate_voice(v, dp, noisep, outxp, ntoplay);
num-=ntoplay;
off+=ntoplay;
vi->samplestoend-=ntoplay;
if (vi->samplestoirq!=spu_infinity) vi->samplestoirq-=ntoplay;
if (vi->envsamples!=spu_infinity) vi->envsamples-=ntoplay;
vi->hitirq=false;
}
// Determine which event(s) we hit
bool hitend=fm?(vi->play.ptr>=vi->play.cache->dend)
:(vi->samplestoend==0),
hitirq=(vi->samplestoirq==0),
hitenv=(vi->envsamples==0);
// Update loop cache pointer if we are playing a loop cache
if ((vi->inloopcache) && (vi->lcptr>=vi->loop_cache->len))
{
vi->inloopcache=false;
hitend=(vi->play.ptr>=vi->play.cache->dend);
#ifdef log_loop_cache
log(log_spu,"spu: %d leave loop cache %08x, lcptr=%d, hitend=%d\n",
v,
vi->loop_cache,
vi->lcptr,
hitend);
#endif
}
bool stopped=false;
if (hitend)
{
// End of sample reached, calculate how far we overshot
unsigned int poff=vi->play.ptr-vi->play.cache->dend;
// Make sure loop info is up to date and end the current output segment
update_voice_loop(v);
if (vi->loop)
{
// We are looping, set play address to loop address and account for
// overshoot
vi->play=vi->loop;
vi->play.ptr+=poff;
vi->lcptr=poff;
vi->inloopcache=(poff<vi->loop_cache->len);
#ifdef log_loop_cache
if (vi->inloopcache)
log(log_spu,"spu: %d enter loop cache %08x, lcptr=%d\n",
v,
vi->loop_cache,
vi->lcptr);
#endif
// Check for IRQ at/just after repeat address
if (spureg.ctrl&spuctrl_irq_enable)
{
if (spureg.voice[v].repaddr==spureg.irq_addr)
hitirq=true;
signed short *irq_ptr;
unsigned int irq_addr=spureg.irq_addr<<3;
if ((irq_ptr=vi->loop.cache->get_sample_pointer(irq_addr)))
{
if ((irq_ptr>=vi->loop.ptr) &&
(vi->play.ptr>=irq_ptr))
hitirq=true;
}
}
} else
{
// Not looping, stop voice
spureg.reverb&=~(1<<v);
stopped=true;
// Check for IRQ at repeat address
if (spureg.ctrl&spuctrl_irq_enable)
{
if (spureg.voice[v].repaddr==spureg.irq_addr)
hitirq=true;
}
}
assert((stopped) || (vi->play.ptr<vi->play.cache->dend));
} else
{
assert(vi->play.ptr<vi->play.cache->dend);
}
if (hitirq)
{
// Went past IRQ address, trigger IRQ
m_irq_handler(1);
vi->samplestoirq=spu_infinity;
vi->hitirq=true;
}
if (hitenv)
{
// Envelope event, update the envelope (stop if necessary), and start
// a new output segment
stopped=((stopped) || (! update_envelope(v)));
}
if (stopped)
{
// Voice is now stopped
ret=false;
break;
}
// Update voice event times
update_voice_events(vi);
}
// Set current volume register
spureg.voice[v].curvol=(unsigned short)(vi->env_level*32767.0f);
// Return how much time is left and whether or not the voice is still playing
*tleft=num;
return ret;
}
//
// Generate voice output samples
//
// Inputs: const unsigned int v Voice number
// void *ptr Output buffer (if no reverb)
// const unsigned int sz Number of samples to output
//
void spu_device::generate_voice(const unsigned int v,
void *ptr,
void *noiseptr,
void *outxptr,
const unsigned int sz)
{
voiceinfo *vi=&voice[v];
signed short *fp,*sp;
unsigned int n=sz;
// Get input pointer
if (vi->inloopcache)
{
sp=vi->loop_cache->data+vi->lcptr;
} else
{
sp=vi->play.ptr;
}
fp=sp;
unsigned int dptr=vi->dptr;
// Get output pointer (and advance output offset)
signed short *dp=(signed short *)ptr;
signed short *outxp=(signed short *)outxptr;
// Calculate fixed point envelope levels/deltas premultiplied by channel volume
int vol_l=outxptr?0x3fff:vi->vol[0],
vol_r=outxptr?0x3fff:vi->vol[1],
env_l=(int)(vi->env_level*2.0f*vol_l),
env_r=(int)(vi->env_level*2.0f*vol_r),
envdelta_l=(int)(vi->env_delta*2.0f*vol_l),
envdelta_r=(int)(vi->env_delta*2.0f*vol_r);
// Update the segments envelope level
vi->env_level+=(n*vi->env_delta);
if (noiseptr)
{
INT64 dptr=((INT64)n*vi->pitch)+vi->dptr;
unsigned int d=(unsigned int)(dptr>>12);
vi->dptr=(unsigned int)(dptr&0xfff);
vi->play.ptr+=d;
if (vi->inloopcache) vi->lcptr+=d;
sp=(signed short *)noiseptr;
if (outxp)
{
while (n--)
{
int vl=*sp++,
vr=*sp++,
l=(vl*env_l)>>15,
r=(vr*env_r)>>15;
env_l+=envdelta_l;
env_r+=envdelta_r;
outxp[0]=l;
outxp[1]=r;
outxp+=2;
l=(l*vi->vol[0])>>15;
r=(r*vi->vol[1])>>15;
dp[0]=clamp(l+dp[0]);
dp[1]=clamp(r+dp[1]);
dp+=2;
}
} else
{
while (n--)
{
int vl=*sp++,
vr=*sp++,
l=(vl*env_l)>>15,
r=(vr*env_r)>>15;
env_l+=envdelta_l;
env_r+=envdelta_r;
dp[0]=clamp(l+dp[0]);
dp[1]=clamp(r+dp[1]);
dp+=2;
}
}
} else
{
if (1) //settings.sound_interpolate)
{
unsigned int num_stitch=0;
signed short *ep;
// Linear interpolation enabled, calculate how many samples we will
// read from input data
INT64 fracend=(((INT64)(n-1))*vi->pitch)+dptr;
unsigned int end=(unsigned int)(fracend>>12);
// Get pointer to last sample of input data
if (vi->inloopcache)
{
ep=vi->loop_cache->data+vi->loop_cache->len-1;
} else
{
ep=vi->play.cache->dend-1;
}
// If we read the last sample "stitching" will be necessary (inerpolation
// from last sample of segment to first sample of next segment)
if (((sp+end)>=ep) && (vi->pitch))
{
num_stitch=min(n,max(0x1fff/vi->pitch,1));
n-=num_stitch;
}
// Generate samples
if (outxp)
{
while (n--)
{
int v=sp[0];
v+=((sp[1]-v)*(int)dptr)>>12;
int l=(v*env_l)>>15,
r=(v*env_r)>>15;
env_l+=envdelta_l;
env_r+=envdelta_r;
outxp[0]=l;
outxp[1]=r;
outxp+=2;
l=(l*vi->vol[0])>>15;
r=(r*vi->vol[1])>>15;
dp[0]=clamp(l+dp[0]);
dp[1]=clamp(r+dp[1]);
dp+=2;
dptr+=vi->pitch;
sp+=(dptr>>12);
dptr&=0xfff;
}
}
else
{
while (n--)
{
int v=sp[0];
v+=((sp[1]-v)*(int)dptr)>>12;
int l=(v*env_l)>>15,
r=(v*env_r)>>15;
env_l+=envdelta_l;
env_r+=envdelta_r;
dp[0]=clamp(l+dp[0]);
dp[1]=clamp(r+dp[1]);
dp+=2;
dptr+=vi->pitch;
sp+=(dptr>>12);
dptr&=0xfff;
}
}
if (num_stitch)
{
// Stitch samples, get the first sample of the next segment
signed short *nsp=NULL;
if (vi->inloopcache)
{
nsp=vi->play.ptr+(vi->loop_cache->len-vi->lcptr);
if (nsp>=vi->play.cache->dend)
nsp=NULL;
}
if (! nsp)
{
update_voice_loop(v);
if (vi->loop) nsp=vi->loop_cache->data;
}
int ns=nsp?nsp[0]:0;
// Generate stitch samples
if (outxp)
{
while (num_stitch--)
{
int v=sp[0],
nv=(sp>=ep)?ns:sp[1];
v+=((nv-v)*(int)dptr)>>12;
int l=(v*env_l)>>15,
r=(v*env_r)>>15;
env_l+=envdelta_l;
env_r+=envdelta_r;
outxp[0]=l;
outxp[1]=r;
outxp+=2;
l=(l*vi->vol[0])>>15;
r=(r*vi->vol[1])>>15;
dp[0]=clamp(l+dp[0]);
dp[1]=clamp(r+dp[1]);
dp+=2;
dptr+=vi->pitch;
sp+=(dptr>>12);
dptr&=0xfff;
}
} else
{
while (num_stitch--)
{
int v=sp[0],
nv=(sp>=ep)?ns:sp[1];
v+=((nv-v)*(int)dptr)>>12;
int l=(v*env_l)>>15,
r=(v*env_r)>>15;
env_l+=envdelta_l;
env_r+=envdelta_r;
dp[0]=clamp(l+dp[0]);
dp[1]=clamp(r+dp[1]);
dp+=2;
dptr+=vi->pitch;
sp+=(dptr>>12);
dptr&=0xfff;
}
}
}
} else
{
// Generate samples with no interpolation
if (outxp)
{
while (n--)
{
int l=(sp[0]*env_l)>>15,
r=(sp[0]*env_r)>>15;
env_l+=envdelta_l;
env_r+=envdelta_r;
outxp[0]=l;
outxp[1]=r;
outxp+=2;
l=(l*vi->vol[0])>>15;
r=(r*vi->vol[1])>>15;
dp[0]=clamp(l+dp[0]);
dp[1]=clamp(r+dp[1]);
dp+=2;
dptr+=vi->pitch;
sp+=(dptr>>12);
dptr&=0xfff;
}
} else
{
while (n--)
{
int l=(sp[0]*env_l)>>15,
r=(sp[0]*env_r)>>15;
env_l+=envdelta_l;
env_r+=envdelta_r;
dp[0]=clamp(l+dp[0]);
dp[1]=clamp(r+dp[1]);
dp+=2;
dptr+=vi->pitch;
sp+=(dptr>>12);
dptr&=0xfff;
}
}
}
// Update segment pointer
vi->play.ptr+=sp-fp;
vi->dptr=dptr;
if (vi->inloopcache)
vi->lcptr=sp-vi->loop_cache->data;
}
}
//
//
//
bool spu_device::update_envelope(const int v)
{
while (voice[v].envsamples==0)
{
voice[v].env_state++;
switch (voice[v].env_state)
{
case 1: // decay
voice[v].env_level=1.0f;
voice[v].env_delta=voice[v].env_dr;
if (voice[v].env_dr!=0.0f)
{
voice[v].envsamples=(unsigned int)((voice[v].env_sl-1.0f)/voice[v].env_dr);
} else
{
voice[v].envsamples=spu_infinity;
}
break;
case 2: // sustain
voice[v].env_level=voice[v].env_sl;
voice[v].env_delta=voice[v].env_sr;
if (voice[v].env_sr>0.0f)
{
voice[v].envsamples=(unsigned int)((1.0f-voice[v].env_level)/voice[v].env_sr);
} else
if (voice[v].env_sr<0.0f)
{
voice[v].envsamples=(unsigned int)(voice[v].env_level/-voice[v].env_sr);
} else
{
voice[v].envsamples=spu_infinity;
}
break;
case 3: // sustain end
voice[v].envsamples=spu_infinity;
voice[v].env_delta=0.0f;
if (voice[v].env_sr<=0.0f)
{
voice[v].env_level=0.0f;
return false;
} else
{
voice[v].env_level=1.0f;
}
break;
case 4: // release
voice[v].env_level=mindb(1.0f,maxdb(0.0f,voice[v].env_level));
voice[v].env_delta=voice[v].env_rr;
if (voice[v].env_rr == -0.0f) // 0.0 release means infinite time
{
voice[v].envsamples=spu_infinity;
}
else
{
voice[v].envsamples=(unsigned int)(voice[v].env_level/-voice[v].env_rr);
}
break;
case 5: // release end
voice[v].env_level=0.0f;
voice[v].env_delta=0.0f;
voice[v].envsamples=spu_infinity;
return false;
}
}
return true;
}
//
//
//
void spu_device::key_on(const int v)
{
voice[v].loop.reset();
// printf("key_on: %d @ %x (pitch %x)\n", v, spureg.voice[v].addr<<3, spureg.voice[v].pitch);
translate_sample_addr(spureg.voice[v].addr<<3,&voice[v].play);
assert(voice[v].play.ptr<voice[v].play.cache->dend);
voice[v].keyontime=0; //get_system_time();
voice[v].dptr=0;
voice[v].inloopcache=false;
voice[v].lcptr=-1;
voice[v].env_level=0.0f;
voice[v].env_state=0;
voice[v].forceloop=false;
// Note: ChronoCross has note hang problems if this is 0 immediately
// after key on
spureg.voice[v].curvol=1;
for (unsigned int ch=0; ch<2; ch++)
{
{
voice[v].sweep_vol[ch]=1.0f;
}
}
#ifdef warn_if_sweep_used
static bool sweepused;
if ((spureg.voice[v].vol_l|spureg.voice[v].vol_r)&0x8000)
{
if (! sweepused)
{
printf("sweep\n");
sweepused=true;
}
}
#endif
#ifdef assert_if_sweep_used
assert(((spureg.voice[v].vol_l|spureg.voice[v].vol_r)&0x8000)==0);
#endif
if (spureg.voice[v].adsl&adsl_am)
{
voice[v].env_ar=get_pos_exp_rate((spureg.voice[v].adsl&adsl_ar_mask)>>adsl_ar_shift);
} else
{
voice[v].env_ar=get_linear_rate((spureg.voice[v].adsl&adsl_ar_mask)>>adsl_ar_shift);
}
voice[v].env_dr=-get_decay_rate((spureg.voice[v].adsl&adsl_dr_mask)>>adsl_dr_shift);
voice[v].env_sl=get_sustain_level(spureg.voice[v].adsl&adsl_sl_mask);
if (spureg.voice[v].srrr&srrr_sm)
{
if (spureg.voice[v].srrr&srrr_sd)
{
voice[v].env_sr=get_neg_exp_rate((spureg.voice[v].srrr&srrr_sr_mask)>>srrr_sr_shift);
} else
{
voice[v].env_sr=get_pos_exp_rate((spureg.voice[v].srrr&srrr_sr_mask)>>srrr_sr_shift);
}
} else
{
voice[v].env_sr=get_linear_rate((spureg.voice[v].srrr&srrr_sr_mask)>>srrr_sr_shift);
if (spureg.voice[v].srrr&srrr_sd)
voice[v].env_sr=-voice[v].env_sr;
}
if (spureg.voice[v].srrr&srrr_rm)
{
voice[v].env_rr=-get_exp_release_rate(spureg.voice[v].srrr&srrr_rr_mask);
} else
{
voice[v].env_rr=-get_linear_release_rate(spureg.voice[v].srrr&srrr_rr_mask);
}
voice[v].envsamples=(unsigned int)(1.0f/voice[v].env_ar);
voice[v].env_delta=voice[v].env_ar;
}
//
//
//
void spu_device::set_xa_format(const float _freq, const int channels)
{
// Adjust frequency to compensate for slightly slower/faster frame rate
// float freq=44100.0; //(_freq*get_adjusted_frame_rate())/ps1hw.rcnt->get_vertical_refresh();
xa_freq=(unsigned int)((_freq/44100.0)*4096.0f);
xa_channels=channels;
xa_spf=(unsigned int)(_freq/60.0)*channels;
}
//
//
//
void spu_device::generate_xa(void *ptr, const unsigned int sz)
{
if (xa_buffer->get_bytes_in())
{
// Don't start playing until 8 frames worth of data are in
if ((! xa_playing) && (xa_buffer->get_bytes_in()<(xa_spf<<3)))
{
// debug_xa("waiting...\n");
return;
}
xa_playing=true;
// Init buffer pointers/counters
int n=sz>>2;
signed short *sp=(signed short *)xa_buffer->get_tail_ptr(),
*dp=(signed short *)ptr;
unsigned int noff=(1<<xa_channels),
oldtail=xa_buffer->get_tail_offset();
assert((xa_channels==1) || (xa_channels==2));
// Calculate volume
int voll=spureg.cdvol_l,
volr=spureg.cdvol_r;
voll=(voll*xa_voll)>>14;
volr=(volr*xa_volr)>>14;
// Generate requested number of XA samples
while ((xa_buffer->get_bytes_in()) && (n--))
{
// Get left/right input samples
int vl=sp[0],
vr=sp[xa_channels-1];
// Linear interpolation
if (1) //settings.sound_interpolate)
{
signed short *nsp=(signed short *)xa_buffer->get_tail_ptr(noff);
int vdl=nsp[0]-vl,
vdr=nsp[xa_channels-1]-vr;
vl+=(vdl*(int)xa_cnt)>>12;
vr+=(vdr*(int)xa_cnt)>>12;
}
// Multiply by
vl=(vl*voll)>>15;
vr=(vr*volr)>>15;
// Write to SPU XA buffer (for emulation purposes - some games read this
// back to do analysers, etc...)
*(signed short *)(spu_ram+xa_out_ptr)=vl;
*(signed short *)(spu_ram+xa_out_ptr+0x800)=vr;
xa_out_ptr=(xa_out_ptr+2)&0x7ff;
// Mix samples into output buffer
dp[0]=clamp(dp[0]+vl);
dp[1]=clamp(dp[1]+vr);
dp+=2;
// Advance input counter/pointer
xa_cnt+=xa_freq;
int ss=(xa_cnt>>12);
xa_cnt&=0xfff;
if (ss)
{
ss<<=xa_channels;
ss=min(ss,(int)xa_buffer->get_bytes_in());
xa_buffer->increment_tail(ss);
sp=(signed short *)xa_buffer->get_tail_ptr();
}
}
// Delete buffer markers we have passed
xa_buffer->delete_markers(oldtail);
}
// If we run out of input set status to stopped and clear the SPU XA buffer
if (! xa_buffer->get_bytes_in())
{
xa_playing=false;
memset(spu_ram,0,0x1000);
xa_out_ptr=0;
}
}
//
//
//
void spu_device::generate_cdda(void *ptr, const unsigned int sz)
{
if (cdda_buffer->get_bytes_in())
{
unsigned int cdda_spf=(44100*4)/60.0,
freq=(unsigned int)((cdda_freq*60.0)/60.0);
if ((! cdda_playing) && (cdda_buffer->get_bytes_in()<(cdda_spf<<3)))
return;
cdda_playing=true;
int n=sz>>2;
signed short *sp=(signed short *)cdda_buffer->get_tail_ptr(),
*dp=(signed short *)ptr;
unsigned int oldtail=cdda_buffer->get_tail_offset();
int voll=spureg.cdvol_l,
volr=spureg.cdvol_r;
while ((cdda_buffer->get_bytes_in()) && (n--))
{
dp[0]=clamp(dp[0]+((sp[0]*voll)>>15));
dp[1]=clamp(dp[1]+((sp[1]*volr)>>15));
dp+=2;
cdda_cnt+=freq;
int ss=(cdda_cnt>>12);
cdda_cnt&=0xfff;
if (ss)
{
ss<<=2;
cdda_buffer->increment_tail(ss);
sp=(signed short *)cdda_buffer->get_tail_ptr();
}
}
cdda_buffer->delete_markers(oldtail);
if (! cdda_buffer->get_bytes_in())
cdda_playing=false;
// if (n>0) printf("cdda buffer underflow (n=%d cdda_in=%d spf=%d)\n",n,cdda_buffer->get_bytes_in(),cdda_spf);
}
}
//
//
//
void spu_device::key_off(const int v)
{
// printf("key_off: %d\n", v);
if (voice[v].env_state<=3)
{
voice[v].env_state=3;
voice[v].envsamples=0;
}
}
//
//
//
void spu_device::update_reverb()
{
if (dirty_flags&dirtyflag_reverb)
{
cur_reverb_preset=find_reverb_preset((unsigned short *)®[0x1c0]);
if (cur_reverb_preset==NULL)
{
// printf("spu: reverb=unknown (reg 1c0 = %x)\n", reg[0x1c0]);
} else
{
// printf("spu: reverb=%s\n",cur_reverb_preset->name);
spu_reverb_cfg=&cur_reverb_preset->cfg;
if ((mame_stricmp("reverb off",cur_reverb_preset->name)) && (spu_reverb_cfg->band_gain<=0.0f))
{
// printf("spu: no reverb config for %s\n",cur_reverb_preset->name);
}
}
dirty_flags&=~dirtyflag_reverb;
}
}
//
//
//
void spu_device::flush_output_buffer()
{
output_head=output_tail=output_size=0;
}
//
//
//
void spu_device::generate(void *ptr, const unsigned int sz)
{
cur_generate_sample+=sz>>2;
process_until(cur_generate_sample);
update_reverb();
unsigned int left=sz;
unsigned char *dp=(unsigned char *)ptr;
while ((left) && (output_size))
{
unsigned int n=min(min(left,output_buffer_size-output_head),output_size);
memcpy(dp,output_buf[0]+output_head,n);
rev->process((signed short *)dp,
(signed short *)(output_buf[1]+output_head),
spu_reverb_cfg,
(signed short)spureg.rvol_l,
(signed short)spureg.rvol_r,
n);
output_size-=n;
output_head+=n;
output_head&=(output_buffer_size-1);
dp+=n;
left-=n;
}
if (left)
{
memset(dp,0,left);
}
generate_xa(ptr,sz);
generate_cdda(ptr,sz);
}
//
//
//
void spu_device::update_irq_event()
{
if (spureg.ctrl&spuctrl_irq_enable)
{
unsigned int samplestoirq=spu_infinity;
for (int i=0; i<24; i++)
if (voice[i].samplestoirq!=spu_infinity)
{
if (voice[i].samplestoirq==0)
{
m_irq_handler(1);
voice[i].samplestoirq=spu_infinity;
voice[i].hitirq=true;
} else
{
samplestoirq=min(samplestoirq,voice[i].samplestoirq);
}
}
}
}
//
//
//
void spu_device::generate_noise(void *ptr, const unsigned int num)
{
unsigned int np=(unsigned int)(65536.0f/(0x40-((spureg.ctrl&spuctrl_noise_mask)>>spuctrl_noise_shift)));
np=((np<<1)+np)>>1;
signed short *dp=(signed short *)ptr;
for (unsigned int i=0; i<num; i++)
{
signed short v=noise_cur;
*dp++=v;
*dp++=v;
noise_t+=np;
if (noise_t>0xffff)
{
noise_t-=0xffff;
shift_register15(noise_seed);
noise_cur=noise_seed<<1;
}
}
}
//
//
//
void spu_device::process_until(const unsigned int tsample)
{
while (tsample>cur_frame_sample)
{
unsigned int process_samples=(unsigned int)(tsample-cur_frame_sample);
// Drop samples from the head of the queue if its full
process_samples=min(process_samples,output_buffer_size>>2);
unsigned int nsz=output_size+(process_samples<<2);
if (nsz>output_buffer_size)
{
nsz-=output_buffer_size;
output_head+=nsz;
output_size-=nsz;
output_head&=(output_buffer_size-1);
}
// Decide how many samples to process taking into account buffer
// wrap in output queue. Get pointers to the queues.
process_samples=min(process_samples,
(output_buffer_size-output_tail)>>2);
unsigned char *outptr=output_buf[0]+output_tail,
*reverbptr=output_buf[1]+output_tail,
*fmptr=output_buf[2]+output_tail,
*noiseptr=output_buf[3]+output_tail;
output_tail+=process_samples<<2;
output_tail&=(output_buffer_size-1);
output_size+=process_samples<<2;
assert(output_size<=output_buffer_size);
// Intialise the output samples to 0 (process_voice always adds samples)
memset(outptr,0,process_samples<<2);
memset(reverbptr,0,process_samples<<2);
// If noise is enabled for any channels generate noise samples
if (spureg.noise&0xffffff)
generate_noise(noiseptr,process_samples);
unsigned int mask=1;
for (int i=0; i<24; i++, mask<<=1)
{
unsigned int tleft=process_samples;
bool isfmin=((i<23) && (spureg.fm&(1<<(i+1)))),
isfm=(spureg.fm&(1<<i))!=0,
isnoise=(spureg.noise&(1<<i))!=0,
isreverb=(spureg.reverb&(1<<i))!=0;
// This channel is an FM input for the next channel - clear the
// FM input buffer
if (isfmin)
memset(fmptr,0,process_samples<<2);
if (spureg.chon&mask)
{
// Generate samples
if (! process_voice(i,
process_samples,
isreverb?reverbptr:outptr,
isnoise?noiseptr
:(isfm?fmptr:NULL),
isfmin?fmptr:NULL,
&tleft))
{
spureg.chon&=~mask;
//spureg.reverb&=~mask;
voice[i].play.reset();
voice[i].loop.reset();
}
} else
{
spureg.voice[i].curvol=0;
}
}
cur_frame_sample+=process_samples;
}
}
//
//
//
void spu_device::update_timing()
{
samples_per_frame=44100.0/60.0; //get_adjusted_frame_rate();
samples_per_cycle=samples_per_frame/60*(44100*768); //ps1hw.rcnt->get_vertical_cycles();
}
//
//
//
void spu_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
stream_sample_t *outL, *outR;
INT16 temp[44100], *src;
outL = outputs[0];
outR = outputs[1];
generate(temp, samples*4); // second parameter is bytes, * 2 (size of INT16) * 2 (stereo)
src = &temp[0];
for (int i = 0; i < samples; i++)
{
*outL++ = *src++;
*outR++ = *src++;
}
}
//
//
//
void spu_device::start_dma(UINT8 *mainram, bool to_spu, UINT32 size)
{
UINT32 st=spureg.trans_addr<<3, en=st+size;
if (en>(512*1024))
{
en=512*1024;
size=en-st;
}
if (to_spu)
{
invalidate_cache(st,en);
memcpy(spu_ram+(spureg.trans_addr<<3), mainram, size);
dirty_flags|=dirtyflag_ram;
}
else
{
memcpy(mainram, spu_ram+(spureg.trans_addr<<3), size);
}
}
//
//
//
void spu_device::decode_xa_mono(const unsigned char *xa,
unsigned char *ptr)
{
signed short *dp=(signed short *)ptr;
int l0=xa_last[0],
l1=xa_last[1];
for (int b=0; b<18; b++)
{
for (int s=0; s<4; s++)
{
unsigned char flags=xa[4+(s<<1)],
shift=flags&0xf,
filter=flags>>4;
int f0=filter_coef[filter][0],
f1=filter_coef[filter][1];
int i;
for (i=0; i<28; i++)
{
short d=(xa[16+(i<<2)+s]&0xf)<<12;
d=clamp((d>>shift)+(((l0*f0)+(l1*f1)+32)>>6));
*dp++=d;
l1=l0;
l0=d;
}
flags=xa[5+(s<<1)];
shift=flags&0xf;
filter=flags>>4;
f0=filter_coef[filter][0];
f1=filter_coef[filter][1];
for (i=0; i<28; i++)
{
short d=(xa[16+(i<<2)+s]>>4)<<12;
d=clamp((d>>shift)+(((l0*f0)+(l1*f1)+32)>>6));
*dp++=d;
l1=l0;
l0=d;
}
}
xa+=128;
}
xa_last[0]=l0;
xa_last[1]=l1;
}
//
//
//
void spu_device::decode_xa_stereo(const unsigned char *xa,
unsigned char *ptr)
{
signed short *dp=(signed short *)ptr;
int l0=xa_last[0],
l1=xa_last[1],
l2=xa_last[2],
l3=xa_last[3];
for (int b=0; b<18; b++)
{
for (int s=0; s<4; s++)
{
unsigned char flags0=xa[4+(s<<1)],
shift0=flags0&0xf,
filter0=flags0>>4,
flags1=xa[5+(s<<1)],
shift1=flags1&0xf,
filter1=flags1>>4;
int f0=filter_coef[filter0][0],
f1=filter_coef[filter0][1],
f2=filter_coef[filter1][0],
f3=filter_coef[filter1][1];
for (int i=0; i<28; i++)
{
short d=xa[16+(i<<2)+s],
d0=(d&0xf)<<12,
d1=(d>>4)<<12;
d0=clamp((int)(d0>>shift0)+(((l0*f0)+(l1*f1)+32)>>6));
*dp++=d0;
l1=l0;
l0=d0;
d1=clamp((int)(d1>>shift1)+(((l2*f2)+(l3*f3)+32)>>6));
*dp++=d1;
l3=l2;
l2=d1;
}
}
xa+=128;
}
xa_last[0]=l0;
xa_last[1]=l1;
xa_last[2]=l2;
xa_last[3]=l3;
}
//
//
//
/*
enum
{
xaencoding_stereo_mask=3,
xaencoding_freq_shift=2,
xaencoding_freq_mask=3<<xaencoding_freq_shift,
xaencoding_bps_shift=4,
xaencoding_bps_mask=3<<xaencoding_bps_shift,
xaencoding_emphasis=(1<<6)
};
*/
bool spu_device::play_xa(const unsigned int sector, const unsigned char *xa)
{
// Don't process the sector if the buffer is full
if (xa_buffer->get_bytes_free()<xa_sector_size) return false;
// debug_xa("play_xa: %d\n",sector);
// Get XA format from sector header
const unsigned char *hdr=xa+4;
float freq;
int channels;
switch (hdr[3]&0x3f) // ignore emphasis and reserved bits
{
case 0:
channels=1;
freq=37800.0f; //18900.0f;
break;
case 1:
channels=2;
freq=37800.0f;
break;
case 4:
channels=1;
freq=18900.0f; ///2.0f;
break;
case 5:
channels=2;
freq=18900.0f; //37800.0f/2.0f;
break;
default:
printf("play_xa: unhandled xa mode %08x\n",hdr[3]);
return true;
}
set_xa_format(freq,channels);
// Store XA marker
unsigned char *ptr=xa_buffer->add_sector(sector);
// Decode the sector
if (channels==2)
{
decode_xa_stereo(xa+8,ptr);
} else
{
decode_xa_mono(xa+8,ptr);
}
// Return that we processed the sector
return true;
}
//
// Flush everything after a given sector in the XA buffer
//
void spu_device::flush_xa(const unsigned int sector)
{
// debug_xa("flush_xa: %d\n",sector);
if (xa_playing)
{
xa_buffer->flush(sector);
} else
{
// Not playing, flush the entire buffer
xa_buffer->flush_all();
xa_cnt=0;
}
}
//
//
//
bool spu_device::play_cdda(const unsigned int sector, const unsigned char *cdda)
{
if (cdda_buffer->get_bytes_free()<cdda_sector_size) return false;
signed short *dp=(signed short *)cdda_buffer->add_sector(sector);
memcpy(dp,cdda,cdda_sector_size);
// data coming in in MAME is big endian as stored on the CD
unsigned char *flip = (unsigned char *)dp;
for (int i = 0; i < cdda_sector_size; i+= 2)
{
unsigned char temp = flip[i];
flip[i] = flip[i+1];
flip[i+1] = temp;
}
return true;
}
void spu_device::flush_cdda(const unsigned int sector)
{
// debug_xa("flush_cdda: %d\n",sector);
if (cdda_playing)
{
cdda_buffer->flush(sector);
} else
{
cdda_buffer->flush_all();
cdda_cnt=0;
}
}
void spu_device::dma_read( UINT32 *p_n_ram, UINT32 n_address, INT32 n_size )
{
UINT8 *psxram = (UINT8 *)p_n_ram;
start_dma(psxram + n_address, false, n_size*4);
}
void spu_device::dma_write( UINT32 *p_n_ram, UINT32 n_address, INT32 n_size )
{
UINT8 *psxram = (UINT8 *)p_n_ram;
// printf("SPU DMA write from %x, size %x\n", n_address, n_size);
start_dma(psxram + n_address, true, n_size*4);
}