summaryrefslogtreecommitdiffstatshomepage
path: root/trunk/src/emu/sound/spu.c
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/src/emu/sound/spu.c')
-rw-r--r--trunk/src/emu/sound/spu.c3129
1 files changed, 3129 insertions, 0 deletions
diff --git a/trunk/src/emu/sound/spu.c b/trunk/src/emu/sound/spu.c
new file mode 100644
index 00000000000..57a806ce59f
--- /dev/null
+++ b/trunk/src/emu/sound/spu.c
@@ -0,0 +1,3129 @@
+/*
+
+ Sony PlayStation SPU (CXD2922BQ/CXD2925Q) emulator
+ by pSXAuthor
+ MAME adaptation by R. Belmont
+
+*/
+
+#include "emu.h"
+#include "spu.h"
+#include "spureverb.h"
+#include "includes/psx.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_cb(NULL),
+ 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::static_set_irqf(device_t &device, void (*irqf)(device_t *device, UINT32 state))
+{
+ spu_device &spu = downcast<spu_device &>(device);
+ spu.m_irq_cb = irqf;
+}
+
+void spu_device::device_start()
+{
+ 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();
+}
+
+//
+//
+//
+
+unsigned short spu_device::read_word(const unsigned int addr)
+{
+ unsigned short ret=0, *rp=(unsigned short *)(reg+(addr&0x1ff));
+
+ assert((addr&1)==0);
+
+ m_stream->update();
+
+ ret=*rp;
+
+ #ifdef debug_spu_registers
+ printf("spu: read word %08x = %04x [%s]\n",
+ addr,
+ ret,
+ get_register_name(addr));
+ #endif
+
+ return ret;
+}
+
+//
+//
+//
+
+unsigned char spu_device::read_byte(const unsigned int addr)
+{
+ unsigned char ret=0,
+ *rp=reg+(addr&0x1ff);
+
+ ret=*rp;
+
+ #ifdef debug_spu_registers
+ printf("spu: read byte %08x\n",addr);
+ #endif
+
+ return ret;
+}
+
+//
+//
+//
+
+void spu_device::write_word(const unsigned int addr, const unsigned short data)
+{
+ #ifdef debug_spu_registers
+ printf("spu: write %08x = %04x [%s]\n",
+ addr,
+ data,
+ get_register_name(addr));
+ #endif
+
+ assert((addr&1)==0);
+
+ m_stream->update();
+
+ const unsigned int a=addr&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+(addr&0x1ff));
+
+ 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::write_byte(const unsigned int addr, const unsigned char data)
+{
+ #ifdef debug_spu_registers
+ printf("spu: write %08x = %02x\n",addr,data);
+ #endif
+
+ const unsigned int a=addr&0x1ff;
+ reg[a]=data;
+ if ((a>spureg_reverb_config) && (a<=spureg_last))
+ dirty_flags|=dirtyflag_reverb;
+ update_key();
+}
+
+//
+//
+//
+
+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_cb(this, 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)>>15;
+ volr=(volr*xa_volr)>>15;
+
+ // 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 *)&reg[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_cb(this, 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);
+
+ 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;
+ }
+}
+
+// MAME I/O stuff. This can get cleaner when machine/psx.c does.
+
+void spu_device::dma_read( UINT32 n_address, INT32 n_size )
+{
+ UINT8 *psxram = (UINT8 *)memory_get_shared(machine(), "share1");
+
+ start_dma(psxram + n_address, false, n_size*4);
+}
+
+void spu_device::dma_write( UINT32 n_address, INT32 n_size )
+{
+ UINT8 *psxram = (UINT8 *)memory_get_shared(machine(), "share1");
+
+// printf("SPU DMA write from %x, size %x\n", n_address, n_size);
+
+ start_dma(psxram + n_address, true, n_size*4);
+}
+
+READ16_HANDLER( spu_r )
+{
+ spu_device *spu = space->machine().device<spu_device>("spu");
+
+ if (spu == NULL )
+ {
+ return 0;
+ }
+
+ return spu->read_word(offset*2);
+}
+
+WRITE16_HANDLER( spu_w )
+{
+ spu_device *spu = space->machine().device<spu_device>("spu");
+
+ if (spu == NULL)
+ {
+ return;
+ }
+
+ spu->write_word(offset*2, data);
+}