summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu/sound/c352.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/emu/sound/c352.c')
-rw-r--r--src/emu/sound/c352.c643
1 files changed, 643 insertions, 0 deletions
diff --git a/src/emu/sound/c352.c b/src/emu/sound/c352.c
new file mode 100644
index 00000000000..572fd4db3ff
--- /dev/null
+++ b/src/emu/sound/c352.c
@@ -0,0 +1,643 @@
+/*
+ c352.c - Namco C352 custom PCM chip emulation
+ v1.1
+ 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 / 384.
+ */
+
+#include <math.h>
+#include "sndintrf.h"
+#include "streams.h"
+#include "c352.h"
+
+#define VERBOSE (0)
+
+// flags
+
+enum {
+ C352_FLG_BUSY = 0x8000, // channel is busy
+ C352_FLG_KEYON = 0x4000, // Keyon
+ C352_FLG_KEYOFF = 0x2000, // Keyoff
+ C352_FLG_LOOPTRG = 0x1000, // Loop Trigger
+ C352_FLG_LOOPHIST = 0x0800, // Loop History
+ C352_FLG_FM = 0x0400, // Frequency Modulation
+ C352_FLG_PHASERL = 0x0200, // Rear Left invert phase 180 degrees
+ C352_FLG_PHASEFL = 0x0100, // Front Left invert phase 180 degrees
+ C352_FLG_PHASEFR = 0x0080, // invert phase 180 degrees (e.g. flip sign of sample)
+ C352_FLG_LDIR = 0x0040, // loop direction
+ C352_FLG_LINK = 0x0020, // "long-format" sample (can't loop, not sure what else it means)
+ C352_FLG_NOISE = 0x0010, // play noise instead of sample
+ C352_FLG_MULAW = 0x0008, // sample is mulaw instead of linear 8-bit PCM
+ C352_FLG_FILTER = 0x0004, // don't apply filter
+ C352_FLG_REVLOOP = 0x0003, // loop backwards
+ C352_FLG_LOOP = 0x0002, // loop forward
+ C352_FLG_REVERSE = 0x0001, // play sample backwards
+};
+
+typedef struct
+{
+ UINT8 vol_l;
+ UINT8 vol_r;
+ UINT8 vol_l2;
+ UINT8 vol_r2;
+ UINT8 bank;
+ INT16 noise;
+ INT16 noisebuf;
+ UINT16 noisecnt;
+ UINT16 pitch;
+ UINT16 start_addr;
+ UINT16 end_addr;
+ UINT16 repeat_addr;
+ UINT32 flag;
+
+ UINT16 start;
+ UINT16 repeat;
+ UINT32 current_addr;
+ UINT32 pos;
+} c352_ch_t;
+
+struct c352_info
+{
+ sound_stream *stream;
+ c352_ch_t c352_ch[32];
+ unsigned char *c352_rom_samples;
+ int c352_region;
+ int sample_rate_base;
+
+ INT16 level_table[256];
+
+ long channel_l[2048*2];
+ long channel_r[2048*2];
+ long channel_l2[2048*2];
+ long channel_r2[2048*2];
+
+ short mulaw_table[256];
+ unsigned int mseq_reg;
+};
+
+// noise generator
+static int get_mseq_bit(struct c352_info *info)
+{
+ unsigned int mask = (1 << (7 - 1));
+ unsigned int reg = info->mseq_reg;
+ unsigned int bit = reg & (1 << (17 - 1));
+
+ if (bit)
+ {
+ reg = ((reg ^ mask) << 1) | 1;
+ }
+ else
+ {
+ reg = reg << 1;
+ }
+
+ info->mseq_reg = reg;
+
+ return (reg & 1);
+}
+
+static void c352_mix_one_channel(struct c352_info *info, 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 = info->c352_ch[ch].pitch;
+ delta=frequency;
+
+ pos = info->c352_ch[ch].current_addr; // sample pointer
+ offset = info->c352_ch[ch].pos; // 16.16 fixed-point offset into the sample
+ flag = info->c352_ch[ch].flag;
+ bank = info->c352_ch[ch].bank << 16;
+
+ noisecnt = info->c352_ch[ch].noisecnt;
+ noisebuf = info->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 > (UINT32)memory_region_length(info->c352_region))
+ {
+ info->c352_ch[ch].flag &= ~C352_FLG_BUSY;
+ return;
+ }
+
+ sample = (char)info->c352_rom_samples[pos];
+ nextsample = (char)info->c352_rom_samples[pos+cnt];
+
+ // sample is muLaw, not 8-bit linear (Fighting Layer uses this extensively)
+ if (flag & C352_FLG_MULAW)
+ {
+ sample = info->mulaw_table[(unsigned char)sample];
+ nextsample = info->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 = info->c352_ch[ch].noise = (info->c352_ch[ch].noise << 1) | get_mseq_bit(info);
+ sample = (sample & (noise_level - 1)) - (noise_level >> 1);
+ if (sample > 0x7f)
+ {
+ sample = 0x7f;
+ }
+ else if (sample < 0)
+ {
+ sample = 0xff;
+ }
+ sample = info->mulaw_table[(unsigned char)sample];
+
+ if ( (pos+cnt) == pos )
+ {
+ noisebuf += sample;
+ noisecnt++;
+ sample = noisebuf / noisecnt;
+ }
+ else
+ {
+ if ( noisecnt )
+ {
+ sample = noisebuf / noisecnt;
+ }
+ else
+ {
+ sample = info->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 )
+ {
+ info->channel_l[i] += ((-sample * info->c352_ch[ch].vol_l)>>8);
+ }
+ else
+ {
+ info->channel_l[i] += ((sample * info->c352_ch[ch].vol_l)>>8);
+ }
+
+ if ( flag & C352_FLG_PHASEFR )
+ {
+ info->channel_r[i] += ((-sample * info->c352_ch[ch].vol_r)>>8);
+ }
+ else
+ {
+ info->channel_r[i] += ((sample * info->c352_ch[ch].vol_r)>>8);
+ }
+
+ if ( flag & C352_FLG_PHASERL )
+ {
+ info->channel_l2[i] += ((-sample * info->c352_ch[ch].vol_l2)>>8);
+ }
+ else
+ {
+ info->channel_l2[i] += ((sample * info->c352_ch[ch].vol_l2)>>8);
+ }
+ info->channel_r2[i] += ((sample * info->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) > info->c352_ch[ch].end_addr) && ((pos&0xFFFF) < info->c352_ch[ch].start) && (info->c352_ch[ch].start > info->c352_ch[ch].end_addr) ) ||
+ (((pos&0xFFFF) > info->c352_ch[ch].end_addr) && ((pos&0xFFFF) > info->c352_ch[ch].start) && (info->c352_ch[ch].start < info->c352_ch[ch].end_addr) ) ||
+ ((pos > (bank|0xFFFF)) && (info->c352_ch[ch].end_addr == 0xFFFF))
+ )
+ {
+ info->c352_ch[ch].flag |= C352_FLG_LDIR;
+ info->c352_ch[ch].flag |= C352_FLG_LOOPHIST;
+ }
+ }
+ else
+ {
+ pos -= cnt;
+ if (
+ (((pos&0xFFFF) < info->c352_ch[ch].repeat) && ((pos&0xFFFF) < info->c352_ch[ch].end_addr) && (info->c352_ch[ch].end_addr > info->c352_ch[ch].start) ) ||
+ (((pos&0xFFFF) < info->c352_ch[ch].repeat) && ((pos&0xFFFF) > info->c352_ch[ch].end_addr) && (info->c352_ch[ch].end_addr < info->c352_ch[ch].start) ) ||
+ ((pos < bank) && (info->c352_ch[ch].repeat == 0x0000))
+ )
+ {
+ info->c352_ch[ch].flag &= ~C352_FLG_LDIR;
+ info->c352_ch[ch].flag |= C352_FLG_LOOPHIST;
+ }
+ }
+ }
+ else if ( flag & C352_FLG_REVERSE )
+ {
+ pos -= cnt;
+ if (
+ (((pos&0xFFFF) < info->c352_ch[ch].end_addr) && ((pos&0xFFFF) < info->c352_ch[ch].start) && (info->c352_ch[ch].start > info->c352_ch[ch].end_addr) ) ||
+ (((pos&0xFFFF) < info->c352_ch[ch].end_addr) && ((pos&0xFFFF) > info->c352_ch[ch].start) && (info->c352_ch[ch].start < info->c352_ch[ch].end_addr) ) ||
+ ((pos < bank) && (info->c352_ch[ch].end_addr == 0x0000))
+ )
+ {
+ if ( (flag & C352_FLG_LINK) && (flag & C352_FLG_LOOP) )
+ {
+ info->c352_ch[ch].bank = info->c352_ch[ch].start_addr & 0xFF;
+ info->c352_ch[ch].start_addr = info->c352_ch[ch].repeat_addr;
+ info->c352_ch[ch].start = info->c352_ch[ch].start_addr;
+ info->c352_ch[ch].repeat = info->c352_ch[ch].repeat_addr;
+ pos = (info->c352_ch[ch].bank<<16) + info->c352_ch[ch].start_addr;
+ info->c352_ch[ch].flag |= C352_FLG_LOOPHIST;
+ }
+ else if (flag & C352_FLG_LOOP)
+ {
+ pos = (pos & 0xFF0000) + info->c352_ch[ch].repeat;
+ info->c352_ch[ch].flag |= C352_FLG_LOOPHIST;
+ }
+ else
+ {
+ info->c352_ch[ch].flag |= C352_FLG_KEYOFF;
+ info->c352_ch[ch].flag &= ~C352_FLG_BUSY;
+ return;
+ }
+ }
+ } else {
+ pos += cnt;
+ if (
+ (((pos&0xFFFF) > info->c352_ch[ch].end_addr) && ((pos&0xFFFF) < info->c352_ch[ch].start) && (info->c352_ch[ch].start > info->c352_ch[ch].end_addr) ) ||
+ (((pos&0xFFFF) > info->c352_ch[ch].end_addr) && ((pos&0xFFFF) > info->c352_ch[ch].start) && (info->c352_ch[ch].start < info->c352_ch[ch].end_addr) ) ||
+ ((pos > (bank|0xFFFF)) && (info->c352_ch[ch].end_addr == 0xFFFF))
+ )
+ {
+ if ( (flag & C352_FLG_LINK) && (flag & C352_FLG_LOOP) )
+ {
+ info->c352_ch[ch].bank = info->c352_ch[ch].start_addr & 0xFF;
+ info->c352_ch[ch].start_addr = info->c352_ch[ch].repeat_addr;
+ info->c352_ch[ch].start = info->c352_ch[ch].start_addr;
+ info->c352_ch[ch].repeat = info->c352_ch[ch].repeat_addr;
+ pos = (info->c352_ch[ch].bank<<16) + info->c352_ch[ch].start_addr;
+ info->c352_ch[ch].flag |= C352_FLG_LOOPHIST;
+ }
+ else if (flag & C352_FLG_LOOP)
+ {
+ pos = (pos & 0xFF0000) + info->c352_ch[ch].repeat;
+ info->c352_ch[ch].flag |= C352_FLG_LOOPHIST;
+ }
+ else
+ {
+ info->c352_ch[ch].flag |= C352_FLG_KEYOFF;
+ info->c352_ch[ch].flag &= ~C352_FLG_BUSY;
+ return;
+ }
+ }
+ }
+ }
+
+ info->c352_ch[ch].noisecnt = noisecnt;
+ info->c352_ch[ch].noisebuf = noisebuf;
+ info->c352_ch[ch].pos = offset;
+ info->c352_ch[ch].current_addr = pos;
+}
+
+
+static void c352_update(void *param, stream_sample_t **inputs, stream_sample_t **buf, int sample_count)
+{
+ struct c352_info *info = param;
+ int i, j;
+ stream_sample_t *bufferl = buf[0];
+ stream_sample_t *bufferr = buf[1];
+ stream_sample_t *bufferl2 = buf[2];
+ stream_sample_t *bufferr2 = buf[3];
+
+ for(i = 0 ; i < sample_count ; i++)
+ {
+ info->channel_l[i] = info->channel_r[i] = info->channel_l2[i] = info->channel_r2[i] = 0;
+ }
+
+ for (j = 0 ; j < 32 ; j++)
+ {
+ c352_mix_one_channel(info, j, sample_count);
+ }
+
+ for(i = 0 ; i < sample_count ; i++)
+ {
+ *bufferl++ = (short) (info->channel_l[i] >>3);
+ *bufferr++ = (short) (info->channel_r[i] >>3);
+ *bufferl2++ = (short) (info->channel_l2[i] >>3);
+ *bufferr2++ = (short) (info->channel_r2[i] >>3);
+ }
+}
+
+static unsigned short c352_read_reg16(struct c352_info *info, unsigned long address)
+{
+ unsigned long chan;
+ unsigned short val;
+
+ stream_update(info->stream);
+
+ chan = (address >> 4) & 0xfff;
+ if (chan > 31)
+ {
+ val = 0;
+ }
+ else
+ {
+ if ((address & 0xf) == 6)
+ {
+ val = info->c352_ch[chan].flag;
+ }
+ else
+ {
+ val = 0;
+ }
+ }
+ return val;
+}
+
+static void c352_write_reg16(struct c352_info *info, unsigned long address, unsigned short val)
+{
+ unsigned long chan;
+ int i;
+
+ stream_update(info->stream);
+
+ chan = (address >> 4) & 0xfff;
+
+ if ( address >= 0x400 )
+ {
+ switch(address)
+ {
+ case 0x404: // execute key-ons/offs
+ for ( i = 0 ; i <= 31 ; i++ )
+ {
+ if ( info->c352_ch[i].flag & C352_FLG_KEYON )
+ {
+ info->c352_ch[i].current_addr = (info->c352_ch[i].bank << 16) + info->c352_ch[i].start_addr;
+ info->c352_ch[i].start = info->c352_ch[i].start_addr;
+ info->c352_ch[i].repeat = info->c352_ch[i].repeat_addr;
+ info->c352_ch[i].noisebuf = 0;
+ info->c352_ch[i].noisecnt = 0;
+ info->c352_ch[i].flag &= ~(C352_FLG_KEYON | C352_FLG_LOOPHIST);
+ info->c352_ch[i].flag |= C352_FLG_BUSY;
+ }
+ else if ( info->c352_ch[i].flag & C352_FLG_KEYOFF )
+ {
+ info->c352_ch[i].flag &= ~C352_FLG_BUSY;
+ info->c352_ch[i].flag &= ~(C352_FLG_KEYOFF);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return;
+ }
+
+ if (chan > 31)
+ {
+ #if VERBOSE
+ logerror("C352 CTRL %08x %04x\n", address, val);
+ #endif
+ return;
+ }
+ switch(address & 0xf)
+ {
+ case 0x0:
+ // volumes (output 1)
+ #if VERBOSE
+ logerror("CH %02d LVOL %02x RVOL %02x\n", chan, val & 0xff, val >> 8);
+ #endif
+ info->c352_ch[chan].vol_l = val & 0xff;
+ info->c352_ch[chan].vol_r = val >> 8;
+ break;
+
+ case 0x2:
+ // volumes (output 2)
+ #if VERBOSE
+ logerror("CH %02d RLVOL %02x RRVOL %02x\n", chan, val & 0xff, val >> 8);
+ #endif
+ info->c352_ch[chan].vol_l2 = val & 0xff;
+ info->c352_ch[chan].vol_r2 = val >> 8;
+ break;
+
+ case 0x4:
+ // pitch
+ #if VERBOSE
+ logerror("CH %02d PITCH %04x\n", chan, val);
+ #endif
+ info->c352_ch[chan].pitch = val;
+ break;
+
+ case 0x6:
+ // flags
+ #if VERBOSE
+ logerror("CH %02d FLAG %02x\n", chan, val);
+ #endif
+ info->c352_ch[chan].flag = val;
+ break;
+
+ case 0x8:
+ // bank (bits 16-31 of address);
+ info->c352_ch[chan].bank = val & 0xff;
+ #if VERBOSE
+ logerror("CH %02d BANK %02x", chan, info->c352_ch[chan].bank);
+ #endif
+ break;
+
+ case 0xa:
+ // start address
+ #if VERBOSE
+ logerror("CH %02d SADDR %04x\n", chan, val);
+ #endif
+ info->c352_ch[chan].start_addr = val;
+ break;
+
+ case 0xc:
+ // end address
+ #if VERBOSE
+ logerror("CH %02d EADDR %04x\n", chan, val);
+ #endif
+ info->c352_ch[chan].end_addr = val;
+ break;
+
+ case 0xe:
+ // loop address
+ #if VERBOSE
+ logerror("CH %02d LADDR %04x\n", chan, val);
+ #endif
+ info->c352_ch[chan].repeat_addr = val;
+ break;
+
+ default:
+ #if VERBOSE
+ logerror("CH %02d UNKN %01x %04x", chan, address & 0xf, val);
+ #endif
+ break;
+ }
+}
+
+static void c352_init(struct c352_info *info, int sndindex)
+{
+ int i;
+ double x_max = 32752.0;
+ double y_max = 127.0;
+ double u = 10.0;
+
+ // clear all channels states
+ memset(info->c352_ch, 0, sizeof(c352_ch_t)*32);
+
+ // 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;
+ }
+ info->mulaw_table[i] = (short)x;
+ }
+
+ // init noise generator
+ info->mseq_reg = 0x12345678;
+
+ // register save state info
+ for (i = 0; i < 32; i++)
+ {
+ char cname[32];
+
+ sprintf(cname, "C352 v %02d", i);
+
+ state_save_register_item(cname, sndindex, info->c352_ch[i].vol_l);
+ state_save_register_item(cname, sndindex, info->c352_ch[i].vol_r);
+ state_save_register_item(cname, sndindex, info->c352_ch[i].vol_l2);
+ state_save_register_item(cname, sndindex, info->c352_ch[i].vol_r2);
+ state_save_register_item(cname, sndindex, info->c352_ch[i].bank);
+ state_save_register_item(cname, sndindex, info->c352_ch[i].noise);
+ state_save_register_item(cname, sndindex, info->c352_ch[i].noisebuf);
+ state_save_register_item(cname, sndindex, info->c352_ch[i].noisecnt);
+ state_save_register_item(cname, sndindex, info->c352_ch[i].pitch);
+ state_save_register_item(cname, sndindex, info->c352_ch[i].start_addr);
+ state_save_register_item(cname, sndindex, info->c352_ch[i].end_addr);
+ state_save_register_item(cname, sndindex, info->c352_ch[i].repeat_addr);
+ state_save_register_item(cname, sndindex, info->c352_ch[i].flag);
+ state_save_register_item(cname, sndindex, info->c352_ch[i].start);
+ state_save_register_item(cname, sndindex, info->c352_ch[i].repeat);
+ state_save_register_item(cname, sndindex, info->c352_ch[i].current_addr);
+ state_save_register_item(cname, sndindex, info->c352_ch[i].pos);
+ }
+
+ for (i = 0; i < 256; i++)
+ {
+ double max_level = 255.0;
+
+ info->level_table[255-i] = (int) (pow (10.0, (double) i / 256.0 * -20.0 / 20.0) * max_level);
+ }
+}
+
+static void *c352_start(int sndindex, int clock, const void *config)
+{
+ const struct C352interface *intf;
+ struct c352_info *info;
+
+ info = auto_malloc(sizeof(*info));
+ memset(info, 0, sizeof(*info));
+
+ intf = config;
+
+ info->c352_rom_samples = memory_region(intf->region);
+ info->c352_region = intf->region;
+
+ info->sample_rate_base = clock / 192;
+
+ info->stream = stream_create(0, 4, info->sample_rate_base, info, c352_update);
+
+ c352_init(info, sndindex);
+
+ return info;
+}
+
+
+READ16_HANDLER( c352_0_r )
+{
+ return(c352_read_reg16(sndti_token(SOUND_C352, 0), offset*2));
+}
+
+WRITE16_HANDLER( c352_0_w )
+{
+ if (mem_mask == 0)
+ {
+ c352_write_reg16(sndti_token(SOUND_C352, 0), offset*2, data);
+ }
+ else
+ {
+ logerror("C352: byte-wide write unsupported at this time!\n");
+ }
+}
+
+
+
+
+
+
+
+/**************************************************************************
+ * Generic get_info
+ **************************************************************************/
+
+static void c352_set_info(void *token, UINT32 state, sndinfo *info)
+{
+ switch (state)
+ {
+ /* no parameters to set */
+ }
+}
+
+
+void c352_get_info(void *token, UINT32 state, sndinfo *info)
+{
+ switch (state)
+ {
+ /* --- the following bits of info are returned as 64-bit signed integers --- */
+
+ /* --- the following bits of info are returned as pointers to data or functions --- */
+ case SNDINFO_PTR_SET_INFO: info->set_info = c352_set_info; break;
+ case SNDINFO_PTR_START: info->start = c352_start; break;
+ case SNDINFO_PTR_STOP: /* nothing */ break;
+ case SNDINFO_PTR_RESET: /* nothing */ break;
+
+ /* --- the following bits of info are returned as NULL-terminated strings --- */
+ case SNDINFO_STR_NAME: info->s = "C352"; break;
+ case SNDINFO_STR_CORE_FAMILY: info->s = "Namco PCM"; break;
+ case SNDINFO_STR_CORE_VERSION: info->s = "1.1"; break;
+ case SNDINFO_STR_CORE_FILE: info->s = __FILE__; break;
+ case SNDINFO_STR_CORE_CREDITS: info->s = "Copyright (c) 2004-2007, The MAME Team"; break;
+ }
+}
+