cog/Frameworks/GME/vgmplay/chips/c352.c

371 lines
9.5 KiB
C
Raw Normal View History

/*
c352.c - Namco C352 custom PCM chip emulation
2016-07-02 09:57:36 +00:00
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>
2016-07-02 09:57:36 +00:00
#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)
2016-07-02 09:57:36 +00:00
#define C352_VOICES 32
enum {
2016-07-02 09:57:36 +00:00
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
};
2016-07-02 09:57:36 +00:00
typedef struct {
2016-07-02 09:57:36 +00:00
UINT32 pos;
UINT32 counter;
INT16 sample;
INT16 last_sample;
2016-07-02 09:57:36 +00:00
UINT16 vol_f;
UINT16 vol_r;
UINT16 freq;
UINT16 flags;
UINT16 wave_bank;
UINT16 wave_start;
UINT16 wave_end;
UINT16 wave_loop;
int mute;
2016-07-02 09:57:36 +00:00
} C352_Voice;
2016-07-02 09:57:36 +00:00
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)
{
2016-07-02 09:57:36 +00:00
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;
}
}
2016-07-02 09:57:36 +00:00
void C352_fetch_sample(C352 *c, int i)
{
2016-07-02 09:57:36 +00:00
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;
}
}
}
2016-07-02 09:57:36 +00:00
UINT16 C352_update_voice(C352 *c, int i)
{
2016-07-02 09:57:36 +00:00
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;
}
2016-07-02 09:57:36 +00:00
void c352_update(void *_info, stream_sample_t **outputs, int samples)
{
2016-07-02 09:57:36 +00:00
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)
{
2016-07-02 09:57:36 +00:00
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)
{
2016-07-02 09:57:36 +00:00
C352 *c = (C352 *)_info;
free(c->wave);
c->wave = NULL;
free(c);
return;
}
void device_reset_c352(void *_info)
{
2016-07-02 09:57:36 +00:00
C352 *c = (C352 *) _info;
memset(c->v,0,sizeof(C352_Voice)*C352_VOICES);
return;
}
2016-07-02 09:57:36 +00:00
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)
{
2016-07-02 09:57:36 +00:00
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)
{
2016-07-02 09:57:36 +00:00
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,
2016-07-02 09:57:36 +00:00
const UINT8* ROMData)
{
2016-07-02 09:57:36 +00:00
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)
{
2016-07-02 09:57:36 +00:00
C352 *c = (C352 *) _info;
UINT8 CurChn;
for (CurChn = 0; CurChn < 32; CurChn ++)
c->v[CurChn].mute = (MuteMask >> CurChn) & 0x01;
return;
}