371 lines
9.5 KiB
C
371 lines
9.5 KiB
C
/*
|
|
c352.c - Namco C352 custom PCM chip emulation
|
|
v2.0
|
|
|
|
Rewritten by superctr
|
|
|
|
Original code 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 "streams.h"
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stddef.h> // for NULL
|
|
#include "mamedef.h"
|
|
#include "c352.h"
|
|
|
|
#define VERBOSE (0)
|
|
#define LOG(x) do { if (VERBOSE) logerror x; } while (0)
|
|
|
|
#define C352_VOICES 32
|
|
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 {
|
|
|
|
UINT32 pos;
|
|
UINT32 counter;
|
|
|
|
INT16 sample;
|
|
INT16 last_sample;
|
|
|
|
UINT16 vol_f;
|
|
UINT16 vol_r;
|
|
UINT16 freq;
|
|
UINT16 flags;
|
|
|
|
UINT16 wave_bank;
|
|
UINT16 wave_start;
|
|
UINT16 wave_end;
|
|
UINT16 wave_loop;
|
|
|
|
int mute;
|
|
|
|
} C352_Voice;
|
|
|
|
typedef struct {
|
|
|
|
UINT32 rate;
|
|
UINT8 muteRear;
|
|
|
|
C352_Voice v[C352_VOICES];
|
|
|
|
UINT16 control1; // unknown purpose for both
|
|
UINT16 control2;
|
|
|
|
UINT8* wave;
|
|
UINT32 wavesize;
|
|
UINT32 wave_mask;
|
|
|
|
UINT16 random;
|
|
|
|
INT16 mulaw[256];
|
|
|
|
} C352;
|
|
|
|
void C352_generate_mulaw(C352 *c)
|
|
{
|
|
int i;
|
|
double x_max = 32752.0;
|
|
double y_max = 127.0;
|
|
double u = 10.0;
|
|
|
|
// 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;
|
|
c->mulaw[i] = (short)x;
|
|
}
|
|
}
|
|
|
|
void C352_fetch_sample(C352 *c, int i)
|
|
{
|
|
C352_Voice *v = &c->v[i];
|
|
v->last_sample = v->sample;
|
|
|
|
if(v->flags & C352_FLG_NOISE)
|
|
{
|
|
c->random = (c->random>>1) ^ (-(c->random&1)) & 0xfff6;
|
|
v->sample = (c->random&4) ? 0xc000 : 0x3fff;
|
|
|
|
v->last_sample = v->sample; // No interpolation for noise samples
|
|
}
|
|
else
|
|
{
|
|
INT8 s;
|
|
UINT16 pos;
|
|
|
|
s = (INT8)c->wave[v->pos&0xffffff];
|
|
|
|
if(v->flags & C352_FLG_MULAW)
|
|
v->sample = c->mulaw[(uint8_t)s];
|
|
else
|
|
v->sample = s<<8;
|
|
|
|
pos = v->pos&0xffff;
|
|
|
|
if((v->flags & C352_FLG_LOOP) && v->flags & C352_FLG_REVERSE)
|
|
{
|
|
// backwards>forwards
|
|
if((v->flags & C352_FLG_LDIR) && pos == v->wave_loop)
|
|
v->flags &= ~C352_FLG_LDIR;
|
|
// forwards>backwards
|
|
else if(!(v->flags & C352_FLG_LDIR) && pos == v->wave_end)
|
|
v->flags |= C352_FLG_LDIR;
|
|
|
|
v->pos += (v->flags&C352_FLG_LDIR) ? -1 : 1;
|
|
}
|
|
else if(pos == v->wave_end)
|
|
{
|
|
if((v->flags & C352_FLG_LINK) && (v->flags & C352_FLG_LOOP))
|
|
{
|
|
v->pos = (v->wave_start<<16) | v->wave_loop;
|
|
v->flags |= C352_FLG_LOOPHIST;
|
|
}
|
|
else if(v->flags & C352_FLG_LOOP)
|
|
{
|
|
v->pos = (v->pos&0xff0000) | v->wave_loop;
|
|
v->flags |= C352_FLG_LOOPHIST;
|
|
}
|
|
else
|
|
{
|
|
v->flags |= C352_FLG_KEYOFF;
|
|
v->flags &= ~C352_FLG_BUSY;
|
|
v->sample=0;
|
|
v->last_sample=0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
v->pos += (v->flags&C352_FLG_REVERSE) ? -1 : 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
UINT16 C352_update_voice(C352 *c, int i)
|
|
{
|
|
C352_Voice *v = &c->v[i];
|
|
INT32 temp;
|
|
|
|
if((v->flags & C352_FLG_BUSY) == 0)
|
|
return 0;
|
|
|
|
v->counter += v->freq;
|
|
|
|
if(v->counter > 0x10000)
|
|
{
|
|
v->counter &= 0xffff;
|
|
C352_fetch_sample(c,i);
|
|
}
|
|
|
|
temp = v->sample;
|
|
|
|
if((v->flags & C352_FLG_FILTER) == 0)
|
|
temp = v->last_sample + (v->counter*(v->sample-v->last_sample)>>16);
|
|
|
|
return temp;
|
|
}
|
|
|
|
void c352_update(void *_info, stream_sample_t **outputs, int samples)
|
|
{
|
|
C352 *c = (C352 *) _info;
|
|
int i, j;
|
|
INT16 s;
|
|
memset(outputs[0], 0x00, samples * sizeof(stream_sample_t));
|
|
memset(outputs[1], 0x00, samples * sizeof(stream_sample_t));
|
|
|
|
for(i=0;i<samples;i++)
|
|
{
|
|
for(j=0;j<32;j++)
|
|
{
|
|
s = C352_update_voice(c,j);
|
|
if(!c->v[j].mute)
|
|
{
|
|
// Left
|
|
outputs[0][i] += (c->v[j].flags & C352_FLG_PHASEFL) ? (-s * (c->v[j].vol_f>>8) )>>8
|
|
: ( s * (c->v[j].vol_f>>8) )>>8;
|
|
if (!c->muteRear)
|
|
outputs[0][i] += (c->v[j].flags & C352_FLG_PHASERL) ? (-s * (c->v[j].vol_r>>8) )>>8
|
|
: ( s * (c->v[j].vol_r>>8) )>>8;
|
|
|
|
// Right
|
|
outputs[1][i] += (c->v[j].flags & C352_FLG_PHASEFR) ? (-s * (c->v[j].vol_f&0xff))>>8
|
|
: ( s * (c->v[j].vol_f&0xff))>>8;
|
|
if (!c->muteRear)
|
|
outputs[1][i] += ( s * (c->v[j].vol_r&0xff))>>8;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
int device_start_c352(void **_info, int clock, int clkdiv)
|
|
{
|
|
C352 *c = calloc(1, sizeof(C352));
|
|
*_info = (void *) c;
|
|
|
|
c->wave = NULL;
|
|
c->wavesize = 0x00;
|
|
|
|
if(!clkdiv)
|
|
clkdiv = 288;
|
|
|
|
c->rate = (clock&0x7FFFFFFF)/clkdiv;
|
|
c->muteRear = (clock&0x80000000)>>31;
|
|
|
|
memset(c->v,0,sizeof(C352_Voice)*C352_VOICES);
|
|
|
|
c->control1 = 0;
|
|
c->control2 = 0;
|
|
c->random = 0x1234;
|
|
|
|
C352_generate_mulaw(c);
|
|
|
|
return c->rate;
|
|
}
|
|
|
|
void device_stop_c352(void *_info)
|
|
{
|
|
C352 *c = (C352 *)_info;
|
|
|
|
free(c->wave);
|
|
c->wave = NULL;
|
|
|
|
free(c);
|
|
|
|
return;
|
|
}
|
|
|
|
void device_reset_c352(void *_info)
|
|
{
|
|
C352 *c = (C352 *) _info;
|
|
|
|
memset(c->v,0,sizeof(C352_Voice)*C352_VOICES);
|
|
|
|
return;
|
|
}
|
|
|
|
static const UINT16 C352RegMap[8] = {
|
|
offsetof(C352_Voice,vol_f) / sizeof(UINT16),
|
|
offsetof(C352_Voice,vol_r) / sizeof(UINT16),
|
|
offsetof(C352_Voice,freq) / sizeof(UINT16),
|
|
offsetof(C352_Voice,flags) / sizeof(UINT16),
|
|
offsetof(C352_Voice,wave_bank) / sizeof(UINT16),
|
|
offsetof(C352_Voice,wave_start) / sizeof(UINT16),
|
|
offsetof(C352_Voice,wave_end) / sizeof(UINT16),
|
|
offsetof(C352_Voice,wave_loop) / sizeof(UINT16),
|
|
};
|
|
|
|
UINT16 c352_r(void *_info, offs_t offset)
|
|
{
|
|
C352 *c = (C352 *) _info;
|
|
|
|
if(offset < 0x100)
|
|
return *((UINT16*)&c->v[offset/8]+C352RegMap[offset%8]);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
void c352_w(void *_info, offs_t offset, UINT16 data)
|
|
{
|
|
C352 *c = (C352 *) _info;
|
|
|
|
int i;
|
|
|
|
if(offset < 0x100) // Channel registers, see map above.
|
|
*((UINT16*)&c->v[offset/8]+C352RegMap[offset%8]) = data;
|
|
else if(offset == 0x200) // Unknown purpose.
|
|
c->control1 = data;
|
|
else if(offset == 0x201)
|
|
c->control2 = data;
|
|
else if(offset == 0x202) // execute keyons/keyoffs
|
|
{
|
|
for(i=0;i<C352_VOICES;i++)
|
|
{
|
|
if((c->v[i].flags & C352_FLG_KEYON))
|
|
{
|
|
c->v[i].pos = (c->v[i].wave_bank<<16) | c->v[i].wave_start;
|
|
|
|
c->v[i].sample = 0;
|
|
c->v[i].last_sample = 0;
|
|
c->v[i].counter = 0x10000; // Immediate update
|
|
|
|
c->v[i].flags |= C352_FLG_BUSY;
|
|
c->v[i].flags &= ~(C352_FLG_KEYON|C352_FLG_LOOPHIST);
|
|
}
|
|
else if(c->v[i].flags & C352_FLG_KEYOFF)
|
|
{
|
|
c->v[i].sample=0;
|
|
c->v[i].last_sample=0;
|
|
c->v[i].flags &= ~(C352_FLG_BUSY|C352_FLG_KEYOFF);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void c352_write_rom(void *_info, offs_t ROMSize, offs_t DataStart, offs_t DataLength,
|
|
const UINT8* ROMData)
|
|
{
|
|
C352 *c = (C352 *) _info;
|
|
|
|
if (c->wavesize != ROMSize)
|
|
{
|
|
c->wave = (UINT8*)realloc(c->wave, ROMSize);
|
|
c->wavesize = ROMSize;
|
|
memset(c->wave, 0xFF, ROMSize);
|
|
}
|
|
if (DataStart > ROMSize)
|
|
return;
|
|
if (DataStart + DataLength > ROMSize)
|
|
DataLength = ROMSize - DataStart;
|
|
|
|
memcpy(c->wave + DataStart, ROMData, DataLength);
|
|
|
|
return;
|
|
}
|
|
|
|
void c352_set_mute_mask(void *_info, UINT32 MuteMask)
|
|
{
|
|
C352 *c = (C352 *) _info;
|
|
UINT8 CurChn;
|
|
|
|
for (CurChn = 0; CurChn < 32; CurChn ++)
|
|
c->v[CurChn].mute = (MuteMask >> CurChn) & 0x01;
|
|
|
|
return;
|
|
}
|
|
|