summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu/sound/multipcm.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/emu/sound/multipcm.c')
-rw-r--r--src/emu/sound/multipcm.c551
1 files changed, 551 insertions, 0 deletions
diff --git a/src/emu/sound/multipcm.c b/src/emu/sound/multipcm.c
new file mode 100644
index 00000000000..414d7681a0f
--- /dev/null
+++ b/src/emu/sound/multipcm.c
@@ -0,0 +1,551 @@
+/*
+ * Sega System 32 Multi/Model 1 custom PCM chip (315-5560) emulation.
+ *
+ * by R. Belmont. Info from AMUSE, Hoot, and the YMF278B (OPL4).
+ * This chip is sort of a dry run for the PCM section of the YMF278B,
+ * and it has many obvious similarities to that chip.
+ *
+ * voice registers:
+ * 0: pan
+ * 1: sample to play (PCM chip uses table to figure out)
+ * 2: LSB of pitch
+ * 3: MSB of pitch
+ * 4: voice control: top bit = 1 for key on, 0 for key off
+ * 5: bit 0: loop, bits 1-7 = volume attenuate (0=max, 7f=min)
+ * 6: LFO (OutRunners engine, singing man in Daytona)
+ * 7: LFO
+ *
+ * The first sample ROM contains a variable length table with 12
+ * bytes per instrument/sample. The end of the table is marked
+ * by 12 bytes of 0xFF. This is very similar to the YMF278B.
+ *
+ * The first 3 bytes are the offset into the file (big endian).
+ * The next 2 are the loop start offset into the file (big endian)
+ * The next 2 are the 2's complement of the total sample size (big endian)
+ * The next byte is unknown.
+ * The next 3 are envelope attack / decay / release parameters (not yet emulated)
+ *
+ */
+
+#include <math.h>
+#include "sndintrf.h"
+#include "streams.h"
+#include "multipcm.h"
+
+#define MULTIPCM_CLOCKDIV (360.0)
+#define MULTIPCM_ONE (18)
+
+static int ctbl[] =
+{
+ 0, 1, 2, 3, 4, 5, 6 , -1, // voice number mapping
+ 7, 8, 9, 10,11,12,13, -1,
+ 14,15,16,17,18,19,20, -1,
+ 21,22,23,24,25,26,27, -1,
+};
+
+static int decaytbl[16] = // decay times
+{
+ 0, 300, 800, 1400,
+ 2100, 3000, 4000, 5200,
+ 6600, 8200, 10000, 12000,
+ 14500, 17500, 21000, 25000
+};
+
+
+// sample info struct
+typedef struct PCM_t
+{
+ INT32 st;
+ INT32 size;
+ INT32 loop;
+ UINT8 env[4];
+} PCMInfoT;
+
+// voice structure
+typedef struct Voice_t
+{
+ INT8 active; // active flag
+ INT8 loop; // loop flag
+ INT32 end; // length of sample
+ INT32 loopst; // loop start offset
+ INT32 pan; // panning
+ INT32 vol; // volume
+ INT8 *pSamp; // pointer to start of sample data
+
+ INT32 ptdelta; // pitch step
+ INT32 ptoffset; // fixed point offset
+ INT32 ptsum; // fixed point sum
+ INT32 relamt; // release amount
+ INT32 relcount; // release counter
+ INT8 relstage; // release stage
+} VoiceT;
+
+// chip structure
+typedef struct MultiPCM_t
+{
+ sound_stream * stream;
+
+ UINT8 registers[28][8]; // 8 registers per voice?
+ UINT32 bankL, bankR;
+
+ VoiceT Voices[28];
+ int curreg, curvoice;
+ INT8 *romptr;
+
+ long dlttbl[0x1001]; // pre-calculated pitch table
+ long voltbl[128]; // pre-calculated volume table
+ long pantbl[16]; // pre-calculated panning table
+
+ PCMInfoT samples[512];
+
+} MultiPCMT;
+
+static void MultiPCM_postload(void *param)
+{
+ MultiPCMT *mpcm = param;
+ int j;
+ for (j = 0; j < 28; j++)
+ {
+ int inum = mpcm->registers[j][1] | ((mpcm->registers[j][2]&0x1)<<8);
+ mpcm->Voices[j].pSamp = &mpcm->romptr[mpcm->samples[inum].st];
+ }
+}
+
+static void MultiPCM_update(void *param, stream_sample_t **inputs, stream_sample_t **buffer, int length )
+{
+ MultiPCMT *mpcm = param;
+ stream_sample_t *datap[2];
+ int i, j;
+ signed long lvol, rvol, mlvol, mrvol;
+ INT8 *pSamp;
+ long cnt, ptsum, ptoffset, ptdelta, end;
+ VoiceT *vptr;
+ float decTemp;
+ char relstage;
+ int relcount, relamt;
+ float invrelamt;
+
+ datap[0] = buffer[0];
+ datap[1] = buffer[1];
+
+ memset(datap[0], 0, sizeof(*datap[0])*length);
+ memset(datap[1], 0, sizeof(*datap[1])*length);
+
+ for (j = 0; j < 28; j++)
+ {
+ vptr = &mpcm->Voices[j];
+
+ // is voice playing?
+ if ((vptr->active) || (vptr->relstage))
+ { // only calculate volume once per voice per update cycle
+ rvol = mpcm->pantbl[vptr->pan];
+ lvol = mpcm->pantbl[15-vptr->pan];
+
+ mrvol = rvol = (rvol * vptr->vol)>>8;
+ mlvol = lvol = (lvol * vptr->vol)>>8;
+
+ decTemp = 1.0f;
+
+ // copy our "working set" into locals
+ ptsum = vptr->ptsum;
+ ptoffset = vptr->ptoffset;
+ ptdelta = vptr->ptdelta;
+ end = vptr->end;
+ pSamp = vptr->pSamp;
+ relstage = vptr->relstage;
+ relcount = vptr->relcount;
+ relamt = vptr->relamt;
+ invrelamt = 1.f / (float)relamt;
+
+ for (i = 0; i < length; i++)
+ {
+ cnt = ptsum >> MULTIPCM_ONE;
+ ptsum &= ((1<<MULTIPCM_ONE)-1);
+ ptoffset += cnt;
+
+ if (ptoffset >= end)
+ {
+ if (vptr->loop)
+ {
+ ptoffset = vptr->loopst;
+ }
+ else
+ {
+ vptr->active = 0;
+ break;
+ }
+ }
+
+ if (relstage)
+ {
+ relcount++;
+ if (relcount > relamt)
+ {
+ relstage = 0;
+ vptr->relstage = 0;
+ break;
+ }
+
+ decTemp = 1.0f - (relcount * invrelamt);
+
+ lvol = mlvol * decTemp;
+ rvol = mrvol * decTemp;
+ }
+
+ ptsum += ptdelta;
+
+ datap[0][i] += ((pSamp[ptoffset] * lvol)>>2);
+ datap[1][i] += ((pSamp[ptoffset] * rvol)>>2);
+ }
+
+ // copy back the working values we need to keep
+ vptr->ptsum = ptsum;
+ vptr->ptoffset = ptoffset;
+ vptr->relcount = relcount;
+ }
+ }
+}
+
+static void *multipcm_start(int sndindex, int clock, const void *config)
+{
+ int i;
+ double unity = (double)(1<<MULTIPCM_ONE);
+ UINT8* phdr;
+ long nowadrs;
+ long idx;
+ const struct MultiPCM_interface *intf = config;
+ MultiPCMT *mpcm;
+
+ // make volume table
+ double max=255.0;
+ double db=(48.0/128);
+
+ mpcm = auto_malloc(sizeof(*mpcm));
+ memset(mpcm, 0, sizeof(*mpcm));
+
+ for (i = 0; i < 128; i++)
+ {
+ mpcm->voltbl[i]=max;
+ max /= pow(10.0,db/20.0);
+ }
+ mpcm->voltbl[127]=0;
+
+ // make pan table
+ for(i=0; i<16; i++)
+ {
+ mpcm->pantbl[i]=(long)( (255/sqrt(15)) * sqrt(i));
+ }
+
+ mpcm->curreg = mpcm->curvoice = 0;
+
+ mpcm->romptr = (INT8 *)memory_region(intf->region);
+
+ for (i = 0; i < 28; i++)
+ {
+ mpcm->Voices[i].active = 0;
+ mpcm->Voices[i].ptsum = 0;
+ mpcm->Voices[i].ptoffset = 0;
+ mpcm->Voices[i].loop = 0;
+ mpcm->Voices[i].loopst = 0;
+ mpcm->Voices[i].end = 0;
+ mpcm->Voices[i].pan = 0;
+ mpcm->Voices[i].vol = 0;
+ mpcm->Voices[i].relamt = 0;
+ mpcm->Voices[i].relcount = 0;
+ mpcm->Voices[i].relstage = 0;
+ }
+
+ mpcm->stream = stream_create(0, 2, clock / MULTIPCM_CLOCKDIV, mpcm, MultiPCM_update);
+
+ // make pitch delta table (1 octave)
+ for(i=0; i<0x1001; i++)
+ {
+ mpcm->dlttbl[i] = (long)(unity * (1.0 + ((double)i / 4096.0)));
+ }
+
+ // precalculate the PCM data for a small speedup
+ phdr = (UINT8 *)mpcm->romptr;
+ for(i = 0; i < 511; i++)
+ {
+ idx = i*12;
+ nowadrs = (phdr[idx + 0]<<16) + (phdr[idx + 1]<<8) + (phdr[idx + 2]);
+
+ if((nowadrs == 0)||(nowadrs==0xffffff))
+ { // invalid entry
+ mpcm->samples[i].st=0;
+ mpcm->samples[i].size=0;
+ }
+ else
+ {
+ mpcm->samples[i].st = nowadrs;
+ mpcm->samples[i].loop = (phdr[idx + 3]<<8) + (phdr[idx + 4]);
+ mpcm->samples[i].size = 0xffff - ((phdr[idx + 5]<<8) + (phdr[idx + 6]));
+ mpcm->samples[i].env[0] = phdr[idx + 8];
+ mpcm->samples[i].env[1] = phdr[idx + 9];
+ mpcm->samples[i].env[2] = phdr[idx + 10];
+ }
+ }
+
+ /* set up the save state info */
+ {
+ int v;
+ char mname[20];
+
+ sprintf(mname, "MultiPCM %d", sndindex);
+
+ state_save_register_item(mname, sndindex, mpcm->bankL);
+ state_save_register_item(mname, sndindex, mpcm->bankR);
+
+ state_save_register_item_2d_array(mname, sndindex, mpcm->registers);
+
+ for (v = 0; v < 28; v++)
+ {
+ char mname2[32];
+
+ sprintf(mname2, "MultiPCM %d v %d", sndindex, v);
+
+ state_save_register_item(mname2, sndindex, mpcm->Voices[v].active);
+ state_save_register_item(mname2, sndindex, mpcm->Voices[v].loop);
+ state_save_register_item(mname2, sndindex, mpcm->Voices[v].end);
+ state_save_register_item(mname2, sndindex, mpcm->Voices[v].loopst);
+ state_save_register_item(mname2, sndindex, mpcm->Voices[v].pan);
+ state_save_register_item(mname2, sndindex, mpcm->Voices[v].vol);
+ state_save_register_item(mname2, sndindex, mpcm->Voices[v].ptdelta);
+ state_save_register_item(mname2, sndindex, mpcm->Voices[v].ptoffset);
+ state_save_register_item(mname2, sndindex, mpcm->Voices[v].ptsum);
+ state_save_register_item(mname2, sndindex, mpcm->Voices[v].relamt);
+ state_save_register_item(mname2, sndindex, mpcm->Voices[v].relstage);
+ }
+
+ state_save_register_item(mname, sndindex, mpcm->curreg);
+ state_save_register_item(mname, sndindex, mpcm->curvoice);
+ }
+
+ state_save_register_func_postload_ptr(MultiPCM_postload, mpcm);
+
+ return mpcm;
+}
+
+/* write register */
+static void MultiPCM_reg_w(int chip, int offset, UINT8 data)
+{
+ int ppp, inum;
+ signed short pitch;
+ long pt_abs, pt_oct, st;
+ int vnum;
+ MultiPCMT *cptr = sndti_token(SOUND_MULTIPCM, chip);
+ VoiceT *vptr;
+
+ stream_update(cptr->stream);
+
+ switch (offset)
+ {
+ case 0: // data / status
+ if ((cptr->curvoice > 27) || (cptr->curvoice < 0))
+ {
+ //logerror("MPCM: unknown write to voice > 28\n");
+ return;
+ }
+
+ vnum = cptr->curvoice;
+ cptr->registers[vnum][cptr->curreg] = data;
+
+ vptr = &cptr->Voices[vnum];
+
+ switch (cptr->curreg)
+ {
+ case 0: // panning
+ ppp = (cptr->registers[vnum][0]>>4)&0xf;
+ if (ppp >= 8)
+ {
+ ppp = -(16-ppp);
+ }
+ vptr->pan = ppp + 8;
+ break;
+
+ case 1: // sample
+ break;
+
+ case 2: // pitch LSB
+ // MUST fall through to update pitch also!
+ case 3: // pitch MSB
+ // compute frequency divisor
+ pitch = (cptr->registers[vnum][3]<<8) + cptr->registers[vnum][2];
+ pt_abs = (double)abs(pitch);
+ pt_oct = pt_abs>>12;
+ if(pitch < 0)
+ {
+ vptr->ptdelta = cptr->dlttbl[0x1000 - (pt_abs&0xfff)];
+ vptr->ptdelta >>= (pt_oct+1);
+ }
+ else
+ {
+ vptr->ptdelta = cptr->dlttbl[pt_abs&0xfff];
+ vptr->ptdelta <<= pt_oct;
+ }
+ break;
+
+ case 4: // key on/off
+ if (data & 0x80)
+ {
+ inum = cptr->registers[vnum][1];
+
+ // calc decay amount
+ vptr->relamt = decaytbl[(0x0f - cptr->samples[inum].env[2])];
+
+ // compute start and end pointers
+ st = cptr->samples[inum].st;
+
+ // perform banking
+ if (st >= 0x100000)
+ {
+/* logerror("MPCM: key on chip %d voice %d\n", chip, vnum);
+ logerror("regs %02x %02x %02x %02x %02x %02x %02x %02x\n", cptr->registers[vnum][0],
+ cptr->registers[vnum][1],cptr->registers[vnum][2],cptr->registers[vnum][3],
+ cptr->registers[vnum][4],cptr->registers[vnum][5],
+ cptr->registers[vnum][6],cptr->registers[vnum][7]);*/
+
+ if (vptr->pan < 8)
+ {
+ st = (st & 0xfffff) + cptr->bankL;
+ }
+ else
+ {
+ st = (st & 0xfffff) + cptr->bankR;
+ }
+ }
+
+ vptr->pSamp = &cptr->romptr[st];
+ vptr->end = cptr->samples[inum].size;
+ vptr->loopst = cptr->samples[inum].loop;
+
+ vptr->ptoffset = 0;
+ vptr->ptsum = 0;
+ vptr->active = 1;
+ vptr->relstage = 0;
+ }
+ else
+ {
+// logerror("MPCM: key off chip %d voice %d\n", chip, vnum);
+ vptr->active = 0;
+ vptr->relcount = 0;
+ if ((vptr->loop) && (vptr->pSamp))
+ {
+ vptr->relstage = 1;
+ }
+ }
+ break;
+
+ case 5: // volume/loop
+ vptr->vol = cptr->voltbl[(cptr->registers[vnum][5]>>1)&0x7f];
+ vptr->loop = (cptr->registers[vnum][5]&0x1) || !vptr->loopst;
+ break;
+
+ case 6: // ??? LFO? reverb?
+ case 7:
+// logerror("write %x to reg %d, voice %d\n", data, cptr->curreg, vnum);
+ break;
+
+ default:
+// logerror("write %x to reg %d, voice %d\n", data, cptr->curreg, vnum);
+ break;
+ }
+ break;
+
+ case 1: // voice select
+ cptr->curvoice = ctbl[data&0x1f];
+ break;
+
+ case 2: // register select
+ cptr->curreg = data;
+ if (cptr->curreg > 7)
+ cptr->curreg = 7;
+ break;
+ }
+}
+
+/* read register */
+
+static UINT8 MultiPCM_reg_r(int chip, int offset)
+{
+ UINT8 retval = 0;
+
+ switch (offset)
+ {
+ case 0:
+ retval = 0; // always return READY
+ break;
+
+ default:
+ //logerror("read from unknown MPCM register %ld\n", offset);
+ break;
+ }
+
+ return retval;
+}
+
+/* MAME/M1 access functions */
+
+READ8_HANDLER( MultiPCM_reg_0_r )
+{
+ return MultiPCM_reg_r(0, offset);
+}
+
+WRITE8_HANDLER( MultiPCM_reg_0_w )
+{
+ MultiPCM_reg_w(0, offset, data);
+}
+
+READ8_HANDLER( MultiPCM_reg_1_r )
+{
+ return MultiPCM_reg_r(1, offset);
+}
+
+WRITE8_HANDLER( MultiPCM_reg_1_w )
+{
+ MultiPCM_reg_w(1, offset, data);
+}
+
+void multipcm_set_bank(int which, UINT32 leftoffs, UINT32 rightoffs)
+{
+ struct MultiPCM_t *mpcm = sndti_token(SOUND_MULTIPCM, which);
+ mpcm->bankL = leftoffs;
+ mpcm->bankR = rightoffs;
+}
+
+
+
+/**************************************************************************
+ * Generic get_info
+ **************************************************************************/
+
+static void multipcm_set_info(void *token, UINT32 state, sndinfo *info)
+{
+ switch (state)
+ {
+ /* no parameters to set */
+ }
+}
+
+
+void multipcm_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 = multipcm_set_info; break;
+ case SNDINFO_PTR_START: info->start = multipcm_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 = "MultiPCM"; break;
+ case SNDINFO_STR_CORE_FAMILY: info->s = "Sega custom"; break;
+ case SNDINFO_STR_CORE_VERSION: info->s = "1.0"; break;
+ case SNDINFO_STR_CORE_FILE: info->s = __FILE__; break;
+ case SNDINFO_STR_CORE_CREDITS: info->s = "Copyright (c) 2004, The MAME Team"; break;
+ }
+}
+