summaryrefslogtreecommitdiffstatshomepage
path: root/trunk/src/emu/sound/c352.c
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/src/emu/sound/c352.c')
-rw-r--r--trunk/src/emu/sound/c352.c530
1 files changed, 530 insertions, 0 deletions
diff --git a/trunk/src/emu/sound/c352.c b/trunk/src/emu/sound/c352.c
new file mode 100644
index 00000000000..fb7f3498a71
--- /dev/null
+++ b/trunk/src/emu/sound/c352.c
@@ -0,0 +1,530 @@
+/*
+ c352.c - Namco C352 custom PCM chip emulation
+ v1.2
+ By R. Belmont
+ Additional code by cync and the hoot development team
+
+ Thanks to Cap of VivaNonno for info and The_Author for preliminary reverse-engineering
+
+ Chip specs:
+ 32 voices
+ Supports 8-bit linear and 8-bit muLaw samples
+ Output: digital, 16 bit, 4 channels
+ Output sample rate is the input clock / (288 * 2).
+ */
+
+#include "emu.h"
+#include "c352.h"
+
+#define VERBOSE (0)
+#define LOG(x) do { if (VERBOSE) logerror x; } while (0)
+
+// device type definition
+const device_type C352 = &device_creator<c352_device>;
+
+// default address map
+static ADDRESS_MAP_START( c352, AS_0, 8, c352_device )
+ AM_RANGE(0x000000, 0xffffff) AM_ROM
+ADDRESS_MAP_END
+
+//**************************************************************************
+// LIVE DEVICE
+//**************************************************************************
+
+//-------------------------------------------------
+// c352_device - constructor
+//-------------------------------------------------
+
+c352_device::c352_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
+ : device_t(mconfig, C352, "C352", tag, owner, clock),
+ device_sound_interface(mconfig, *this),
+ device_memory_interface(mconfig, *this),
+ m_space_config("samples", ENDIANNESS_LITTLE, 8, 24, 0, NULL, *ADDRESS_MAP_NAME(c352))
+{
+}
+
+//-------------------------------------------------
+// memory_space_config - return a description of
+// any address spaces owned by this device
+//-------------------------------------------------
+
+const address_space_config *c352_device::memory_space_config(address_spacenum spacenum) const
+{
+ return (spacenum == 0) ? &m_space_config : NULL;
+}
+
+// noise generator
+int c352_device::get_mseq_bit()
+{
+ unsigned int mask = (1 << (7 - 1));
+ unsigned int reg = m_mseq_reg;
+ unsigned int bit = reg & (1 << (17 - 1));
+
+ if (bit)
+ {
+ reg = ((reg ^ mask) << 1) | 1;
+ }
+ else
+ {
+ reg = reg << 1;
+ }
+
+ m_mseq_reg = reg;
+
+ return (reg & 1);
+}
+
+void c352_device::mix_one_channel(unsigned long ch, long sample_count)
+{
+ int i;
+
+ signed short sample, nextsample;
+ signed short noisebuf;
+ UINT16 noisecnt;
+ INT32 frequency, delta, offset, cnt, flag;
+ UINT32 bank;
+ UINT32 pos;
+
+ frequency = m_c352_ch[ch].pitch;
+ delta=frequency;
+
+ pos = m_c352_ch[ch].current_addr; // sample pointer
+ offset = m_c352_ch[ch].pos; // 16.16 fixed-point offset into the sample
+ flag = m_c352_ch[ch].flag;
+ bank = m_c352_ch[ch].bank << 16;
+
+ noisecnt = m_c352_ch[ch].noisecnt;
+ noisebuf = m_c352_ch[ch].noisebuf;
+
+ for(i = 0 ; (i < sample_count) && (flag & C352_FLG_BUSY) ; i++)
+ {
+ offset += delta;
+ cnt = (offset>>16)&0x7fff;
+ if (cnt) // if there is a whole sample part, chop it off now that it's been applied
+ {
+ offset &= 0xffff;
+ }
+
+ if (pos > 0x1000000)
+ {
+ m_c352_ch[ch].flag &= ~C352_FLG_BUSY;
+ return;
+ }
+
+ sample = (char)m_direct->read_raw_byte(pos);
+ nextsample = (char)m_direct->read_raw_byte(pos+cnt);
+
+ // sample is muLaw, not 8-bit linear (Fighting Layer uses this extensively)
+ if (flag & C352_FLG_MULAW)
+ {
+ sample = m_mulaw_table[(unsigned char)sample];
+ nextsample = m_mulaw_table[(unsigned char)nextsample];
+ }
+ else
+ {
+ sample <<= 8;
+ nextsample <<= 8;
+ }
+
+ // play noise instead of sample data
+ if (flag & C352_FLG_NOISE)
+ {
+ int noise_level = 0x8000;
+ sample = m_c352_ch[ch].noise = (m_c352_ch[ch].noise << 1) | get_mseq_bit();
+ sample = (sample & (noise_level - 1)) - (noise_level >> 1);
+ if (sample > 0x7f)
+ {
+ sample = 0x7f;
+ }
+ else if (sample < 0)
+ {
+ sample = 0xff;
+ }
+ sample = m_mulaw_table[(unsigned char)sample];
+
+ if ( (pos+cnt) == pos )
+ {
+ noisebuf += sample;
+ noisecnt++;
+ sample = noisebuf / noisecnt;
+ }
+ else
+ {
+ if ( noisecnt )
+ {
+ sample = noisebuf / noisecnt;
+ }
+ else
+ {
+ sample = m_mulaw_table[0x7f]; // Nearest sound(s) is here.
+ }
+ noisebuf = 0;
+ noisecnt = ( flag & C352_FLG_FILTER ) ? 0 : 1;
+ }
+ }
+
+ // apply linear interpolation
+ if ( (flag & (C352_FLG_FILTER | C352_FLG_NOISE)) == 0 )
+ {
+ sample = (short)(sample + ((nextsample-sample) * (((double)(0x0000ffff&offset) )/0x10000)));
+ }
+
+ if ( flag & C352_FLG_PHASEFL )
+ {
+ m_channel_l[i] += ((-sample * m_c352_ch[ch].vol_l)>>8);
+ }
+ else
+ {
+ m_channel_l[i] += ((sample * m_c352_ch[ch].vol_l)>>8);
+ }
+
+ if ( flag & C352_FLG_PHASEFR )
+ {
+ m_channel_r[i] += ((-sample * m_c352_ch[ch].vol_r)>>8);
+ }
+ else
+ {
+ m_channel_r[i] += ((sample * m_c352_ch[ch].vol_r)>>8);
+ }
+
+ if ( flag & C352_FLG_PHASERL )
+ {
+ m_channel_l2[i] += ((-sample * m_c352_ch[ch].vol_l2)>>8);
+ }
+ else
+ {
+ m_channel_l2[i] += ((sample * m_c352_ch[ch].vol_l2)>>8);
+ }
+ m_channel_r2[i] += ((sample * m_c352_ch[ch].vol_r2)>>8);
+
+ if ( (flag & C352_FLG_REVERSE) && (flag & C352_FLG_LOOP) )
+ {
+ if ( !(flag & C352_FLG_LDIR) )
+ {
+ pos += cnt;
+ if (
+ (((pos&0xFFFF) > m_c352_ch[ch].end_addr) && ((pos&0xFFFF) < m_c352_ch[ch].start) && (m_c352_ch[ch].start > m_c352_ch[ch].end_addr) ) ||
+ (((pos&0xFFFF) > m_c352_ch[ch].end_addr) && ((pos&0xFFFF) > m_c352_ch[ch].start) && (m_c352_ch[ch].start < m_c352_ch[ch].end_addr) ) ||
+ ((pos > (bank|0xFFFF)) && (m_c352_ch[ch].end_addr == 0xFFFF))
+ )
+ {
+ m_c352_ch[ch].flag |= C352_FLG_LDIR;
+ m_c352_ch[ch].flag |= C352_FLG_LOOPHIST;
+ }
+ }
+ else
+ {
+ pos -= cnt;
+ if (
+ (((pos&0xFFFF) < m_c352_ch[ch].repeat) && ((pos&0xFFFF) < m_c352_ch[ch].end_addr) && (m_c352_ch[ch].end_addr > m_c352_ch[ch].start) ) ||
+ (((pos&0xFFFF) < m_c352_ch[ch].repeat) && ((pos&0xFFFF) > m_c352_ch[ch].end_addr) && (m_c352_ch[ch].end_addr < m_c352_ch[ch].start) ) ||
+ ((pos < bank) && (m_c352_ch[ch].repeat == 0x0000))
+ )
+ {
+ m_c352_ch[ch].flag &= ~C352_FLG_LDIR;
+ m_c352_ch[ch].flag |= C352_FLG_LOOPHIST;
+ }
+ }
+ }
+ else if ( flag & C352_FLG_REVERSE )
+ {
+ pos -= cnt;
+ if (
+ (((pos&0xFFFF) < m_c352_ch[ch].end_addr) && ((pos&0xFFFF) < m_c352_ch[ch].start) && (m_c352_ch[ch].start > m_c352_ch[ch].end_addr) ) ||
+ (((pos&0xFFFF) < m_c352_ch[ch].end_addr) && ((pos&0xFFFF) > m_c352_ch[ch].start) && (m_c352_ch[ch].start < m_c352_ch[ch].end_addr) ) ||
+ ((pos < bank) && (m_c352_ch[ch].end_addr == 0x0000))
+ )
+ {
+ if ( (flag & C352_FLG_LINK) && (flag & C352_FLG_LOOP) )
+ {
+ m_c352_ch[ch].bank = m_c352_ch[ch].start_addr & 0xFF;
+ m_c352_ch[ch].start_addr = m_c352_ch[ch].repeat_addr;
+ m_c352_ch[ch].start = m_c352_ch[ch].start_addr;
+ m_c352_ch[ch].repeat = m_c352_ch[ch].repeat_addr;
+ pos = (m_c352_ch[ch].bank<<16) + m_c352_ch[ch].start_addr;
+ m_c352_ch[ch].flag |= C352_FLG_LOOPHIST;
+ }
+ else if (flag & C352_FLG_LOOP)
+ {
+ pos = (pos & 0xFF0000) + m_c352_ch[ch].repeat;
+ m_c352_ch[ch].flag |= C352_FLG_LOOPHIST;
+ }
+ else
+ {
+ m_c352_ch[ch].flag |= C352_FLG_KEYOFF;
+ m_c352_ch[ch].flag &= ~C352_FLG_BUSY;
+ return;
+ }
+ }
+ } else {
+ pos += cnt;
+ if (
+ (((pos&0xFFFF) > m_c352_ch[ch].end_addr) && ((pos&0xFFFF) < m_c352_ch[ch].start) && (m_c352_ch[ch].start > m_c352_ch[ch].end_addr) ) ||
+ (((pos&0xFFFF) > m_c352_ch[ch].end_addr) && ((pos&0xFFFF) > m_c352_ch[ch].start) && (m_c352_ch[ch].start < m_c352_ch[ch].end_addr) ) ||
+ ((pos > (bank|0xFFFF)) && (m_c352_ch[ch].end_addr == 0xFFFF))
+ )
+ {
+ if ( (flag & C352_FLG_LINK) && (flag & C352_FLG_LOOP) )
+ {
+ m_c352_ch[ch].bank = m_c352_ch[ch].start_addr & 0xFF;
+ m_c352_ch[ch].start_addr = m_c352_ch[ch].repeat_addr;
+ m_c352_ch[ch].start = m_c352_ch[ch].start_addr;
+ m_c352_ch[ch].repeat = m_c352_ch[ch].repeat_addr;
+ pos = (m_c352_ch[ch].bank<<16) + m_c352_ch[ch].start_addr;
+ m_c352_ch[ch].flag |= C352_FLG_LOOPHIST;
+ }
+ else if (flag & C352_FLG_LOOP)
+ {
+ pos = (pos & 0xFF0000) + m_c352_ch[ch].repeat;
+ m_c352_ch[ch].flag |= C352_FLG_LOOPHIST;
+ }
+ else
+ {
+ m_c352_ch[ch].flag |= C352_FLG_KEYOFF;
+ m_c352_ch[ch].flag &= ~C352_FLG_BUSY;
+ return;
+ }
+ }
+ }
+ }
+
+ m_c352_ch[ch].noisecnt = noisecnt;
+ m_c352_ch[ch].noisebuf = noisebuf;
+ m_c352_ch[ch].pos = offset;
+ m_c352_ch[ch].current_addr = pos;
+}
+
+
+void c352_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
+{
+ int i, j;
+ stream_sample_t *bufferl = outputs[0];
+ stream_sample_t *bufferr = outputs[1];
+ stream_sample_t *bufferl2 = outputs[2];
+ stream_sample_t *bufferr2 = outputs[3];
+
+ for(i = 0 ; i < samples ; i++)
+ {
+ m_channel_l[i] = m_channel_r[i] = m_channel_l2[i] = m_channel_r2[i] = 0;
+ }
+
+ for (j = 0 ; j < 32 ; j++)
+ {
+ mix_one_channel(j, samples);
+ }
+
+ for(i = 0 ; i < samples ; i++)
+ {
+ *bufferl++ = (short) (m_channel_l[i] >>3);
+ *bufferr++ = (short) (m_channel_r[i] >>3);
+ *bufferl2++ = (short) (m_channel_l2[i] >>3);
+ *bufferr2++ = (short) (m_channel_r2[i] >>3);
+ }
+}
+
+unsigned short c352_device::read_reg16(unsigned long address)
+{
+ unsigned long chan;
+ unsigned short val;
+
+ m_stream->update();
+
+ chan = (address >> 4) & 0xfff;
+ if (chan > 31)
+ {
+ val = 0;
+ }
+ else
+ {
+ if ((address & 0xf) == 6)
+ {
+ val = m_c352_ch[chan].flag;
+ }
+ else
+ {
+ val = 0;
+ }
+ }
+ return val;
+}
+
+void c352_device::write_reg16(unsigned long address, unsigned short val)
+{
+ unsigned long chan;
+ int i;
+
+ m_stream->update();
+
+ chan = (address >> 4) & 0xfff;
+
+ if ( address >= 0x400 )
+ {
+ switch(address)
+ {
+ case 0x404: // execute key-ons/offs
+ for ( i = 0 ; i <= 31 ; i++ )
+ {
+ if ( m_c352_ch[i].flag & C352_FLG_KEYON )
+ {
+ if (m_c352_ch[i].start_addr != m_c352_ch[i].end_addr)
+ {
+ m_c352_ch[i].current_addr = (m_c352_ch[i].bank << 16) + m_c352_ch[i].start_addr;
+ m_c352_ch[i].start = m_c352_ch[i].start_addr;
+ m_c352_ch[i].repeat = m_c352_ch[i].repeat_addr;
+ m_c352_ch[i].noisebuf = 0;
+ m_c352_ch[i].noisecnt = 0;
+ m_c352_ch[i].flag &= ~(C352_FLG_KEYON | C352_FLG_LOOPHIST);
+ m_c352_ch[i].flag |= C352_FLG_BUSY;
+ }
+ }
+ else if ( m_c352_ch[i].flag & C352_FLG_KEYOFF )
+ {
+ m_c352_ch[i].flag &= ~C352_FLG_BUSY;
+ m_c352_ch[i].flag &= ~(C352_FLG_KEYOFF);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return;
+ }
+
+ if (chan > 31)
+ {
+ LOG(("C352 CTRL %08lx %04x\n", address, val));
+ return;
+ }
+ switch(address & 0xf)
+ {
+ case 0x0:
+ // volumes (output 1)
+ LOG(("CH %02ld LVOL %02x RVOL %02x\n", chan, val & 0xff, val >> 8));
+ m_c352_ch[chan].vol_l = val & 0xff;
+ m_c352_ch[chan].vol_r = val >> 8;
+ break;
+
+ case 0x2:
+ // volumes (output 2)
+ LOG(("CH %02ld RLVOL %02x RRVOL %02x\n", chan, val & 0xff, val >> 8));
+ m_c352_ch[chan].vol_l2 = val & 0xff;
+ m_c352_ch[chan].vol_r2 = val >> 8;
+ break;
+
+ case 0x4:
+ // pitch
+ LOG(("CH %02ld PITCH %04x\n", chan, val));
+ m_c352_ch[chan].pitch = val;
+ break;
+
+ case 0x6:
+ // flags
+ LOG(("CH %02ld FLAG %02x\n", chan, val));
+ m_c352_ch[chan].flag = val;
+ break;
+
+ case 0x8:
+ // bank (bits 16-31 of address);
+ m_c352_ch[chan].bank = val & 0xff;
+ LOG(("CH %02ld BANK %02x", chan, m_c352_ch[chan].bank));
+ break;
+
+ case 0xa:
+ // start address
+ LOG(("CH %02ld SADDR %04x\n", chan, val));
+ m_c352_ch[chan].start_addr = val;
+ break;
+
+ case 0xc:
+ // end address
+ LOG(("CH %02ld EADDR %04x\n", chan, val));
+ m_c352_ch[chan].end_addr = val;
+ break;
+
+ case 0xe:
+ // loop address
+ LOG(("CH %02ld LADDR %04x\n", chan, val));
+ m_c352_ch[chan].repeat_addr = val;
+ break;
+
+ default:
+ LOG(("CH %02ld UNKN %01lx %04x", chan, address & 0xf, val));
+ break;
+ }
+}
+
+void c352_device::device_start()
+{
+ int i;
+ double x_max = 32752.0;
+ double y_max = 127.0;
+ double u = 10.0;
+
+ // find our direct access
+ m_direct = &space()->direct();
+
+ m_sample_rate_base = clock() / 288;
+
+ m_stream = machine().sound().stream_alloc(*this, 0, 4, m_sample_rate_base, this);
+
+ // generate mulaw table for mulaw format samples
+ for (i = 0; i < 256; i++)
+ {
+ double y = (double) (i & 0x7f);
+ double x = (exp (y / y_max * log (1.0 + u)) - 1.0) * x_max / u;
+
+ if (i & 0x80)
+ {
+ x = -x;
+ }
+ m_mulaw_table[i] = (short)x;
+ }
+
+ // register save state info
+ for (i = 0; i < 32; i++)
+ {
+ save_item(NAME(m_c352_ch[i].vol_l), i);
+ save_item(NAME(m_c352_ch[i].vol_r), i);
+ save_item(NAME(m_c352_ch[i].vol_l2), i);
+ save_item(NAME(m_c352_ch[i].vol_r2), i);
+ save_item(NAME(m_c352_ch[i].bank), i);
+ save_item(NAME(m_c352_ch[i].noise), i);
+ save_item(NAME(m_c352_ch[i].noisebuf), i);
+ save_item(NAME(m_c352_ch[i].noisecnt), i);
+ save_item(NAME(m_c352_ch[i].pitch), i);
+ save_item(NAME(m_c352_ch[i].start_addr), i);
+ save_item(NAME(m_c352_ch[i].end_addr), i);
+ save_item(NAME(m_c352_ch[i].repeat_addr), i);
+ save_item(NAME(m_c352_ch[i].flag), i);
+ save_item(NAME(m_c352_ch[i].start), i);
+ save_item(NAME(m_c352_ch[i].repeat), i);
+ save_item(NAME(m_c352_ch[i].current_addr), i);
+ save_item(NAME(m_c352_ch[i].pos), i);
+ }
+}
+
+void c352_device::device_reset()
+{
+ // clear all channels states
+ memset(m_c352_ch, 0, sizeof(c352_ch_t)*32);
+
+ // init noise generator
+ m_mseq_reg = 0x12345678;
+}
+
+READ16_MEMBER( c352_device::read )
+{
+ return(read_reg16(offset*2));
+}
+
+WRITE16_MEMBER( c352_device::write )
+{
+ if (mem_mask == 0xffff)
+ {
+ write_reg16(offset*2, data);
+ }
+ else
+ {
+ logerror("C352: byte-wide write unsupported at this time!\n");
+ }
+}