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

734 lines
19 KiB
C
Raw Normal View History

/*
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 "streams.h"
#include <math.h>
#include <stdlib.h>
#include <memory.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)
// 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;
UINT8 Muted;
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;
typedef struct _c352_state c352_state;
struct _c352_state
{
//sound_stream *stream;
c352_ch_t c352_ch[32];
unsigned char *c352_rom_samples;
UINT32 c352_rom_length;
int sample_rate_base;
/*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;
};
/*INLINE c352_state *get_safe_token(running_device *device)
{
assert(device != NULL);
assert(device->type() == C352);
return (c352_state *)downcast<legacy_device_base *>(device)->token();
}*/
// noise generator
static int get_mseq_bit(c352_state *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);
}
/* ctr: this function gets the next sample for the lerp. If the sample position pointer is adjacent to the sample end pointer,
then lerping the sample with the "nextsample" variable causes clicks. To prevent this, simply check if the next sample is
the final sample and if so go to the beginning sample.
If bidi samples causes problems, add a case for that as well. (they might)
*/
char getnextsample(c352_state *chip, c352_ch_t* c352ch, UINT32 pos)
{
INT32 flag = c352ch->flag;
UINT32 bank = c352ch->bank << 16;
if( flag & C352_FLG_REVERSE)
return (char) chip->c352_rom_samples[pos+1]; // todo: Bidi samples
pos++;
if (
(((pos&0xFFFF) > c352ch->end_addr) && ((pos&0xFFFF) < c352ch->start) && (c352ch->start > c352ch->end_addr) ) ||
(((pos&0xFFFF) > c352ch->end_addr) && ((pos&0xFFFF) > c352ch->start) && (c352ch->start < c352ch->end_addr) ) ||
((pos > (bank|0xFFFF)) && (c352ch->end_addr == 0xFFFF))
)
{
if ( (flag & C352_FLG_LINK) && (flag & C352_FLG_LOOP) )
pos = ((c352ch->start_addr & 0xFF)<<16) + c352ch->repeat_addr;
else if (flag & C352_FLG_LOOP)
pos = (pos & 0xFF0000) + c352ch->repeat;
else
{
// key off at this point, just return the previous value
return (char) chip->c352_rom_samples[pos-1];
}
}
return (char) chip->c352_rom_samples[pos];
}
static void c352_mix_one_channel(c352_state *info, unsigned long ch, stream_sample_t **outputs, long sample_count)
{
c352_ch_t* c352ch;
int i;
signed short sample, nextsample;
signed short noisebuf;
UINT16 noisecnt;
INT32 delta, offset, cnt, flag;
UINT32 bank;
UINT32 pos;
c352ch = &info->c352_ch[ch];
delta = c352ch->pitch;
pos = c352ch->current_addr; // sample pointer
offset = c352ch->pos; // 16.16 fixed-point offset into the sample
flag = c352ch->flag;
bank = c352ch->bank << 16;
noisecnt = c352ch->noisecnt;
noisebuf = c352ch->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 >= info->c352_rom_length) // pretty sure this should be >= instead of > -Valley Bell
{
c352ch->flag &= ~C352_FLG_BUSY;
//return;
break; // ensure that it saves the variables
}
sample = (char)info->c352_rom_samples[pos];
//nextsample = (char)info->c352_rom_samples[pos+cnt];
nextsample = getnextsample(info, c352ch, pos);
// 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 = c352ch->noise = (c352ch->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;
if (noisecnt) // avoid Divide By Zero crash -Valley Bell
sample = noisebuf / noisecnt;
else
sample = info->mulaw_table[0x7f];
}
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 * c352ch->vol_l)>>8);
outputs[0][i] += ((-sample * c352ch->vol_l)>>8);
}
else
{
//info->channel_l[i] += ((sample * c352ch->vol_l)>>8);
outputs[0][i] += ((sample * c352ch->vol_l)>>8);
}
if ( flag & C352_FLG_PHASEFR )
{
//info->channel_r[i] += ((-sample * c352ch->vol_r)>>8);
outputs[1][i] += ((-sample * c352ch->vol_r)>>8);
}
else
{
//info->channel_r[i] += ((sample * c352ch->vol_r)>>8);
outputs[1][i] += ((sample * c352ch->vol_r)>>8);
}
if ( flag & C352_FLG_PHASERL )
{
//info->channel_l2[i] += ((-sample * c352ch->vol_l2)>>8);
outputs[0][i] += ((-sample * c352ch->vol_l2)>>8);
}
else
{
//info->channel_l2[i] += ((sample * c352ch->vol_l2)>>8);
outputs[0][i] += ((sample * c352ch->vol_l2)>>8);
}
//info->channel_r2[i] += ((sample * c352ch->vol_r2)>>8);
outputs[1][i] += ((sample * c352ch->vol_r2)>>8);
if ( (flag & C352_FLG_REVERSE) && (flag & C352_FLG_LOOP) )
{
if ( !(flag & C352_FLG_LDIR) )
{
pos += cnt;
if (
(((pos&0xFFFF) > c352ch->end_addr) && ((pos&0xFFFF) < c352ch->start) && (c352ch->start > c352ch->end_addr) ) ||
(((pos&0xFFFF) > c352ch->end_addr) && ((pos&0xFFFF) > c352ch->start) && (c352ch->start < c352ch->end_addr) ) ||
((pos > (bank|0xFFFF)) && (c352ch->end_addr == 0xFFFF))
)
{
c352ch->flag |= C352_FLG_LDIR;
c352ch->flag |= C352_FLG_LOOPHIST;
}
}
else
{
pos -= cnt;
if (
(((pos&0xFFFF) < c352ch->repeat) && ((pos&0xFFFF) < c352ch->end_addr) && (c352ch->end_addr > c352ch->start) ) ||
(((pos&0xFFFF) < c352ch->repeat) && ((pos&0xFFFF) > c352ch->end_addr) && (c352ch->end_addr < c352ch->start) ) ||
((pos < bank) && (c352ch->repeat == 0x0000))
)
{
c352ch->flag &= ~C352_FLG_LDIR;
c352ch->flag |= C352_FLG_LOOPHIST;
}
}
}
else if ( flag & C352_FLG_REVERSE )
{
pos -= cnt;
if (
(((pos&0xFFFF) < c352ch->end_addr) && ((pos&0xFFFF) < c352ch->start) && (c352ch->start > c352ch->end_addr) ) ||
(((pos&0xFFFF) < c352ch->end_addr) && ((pos&0xFFFF) > c352ch->start) && (c352ch->start < c352ch->end_addr) ) ||
((pos < bank) && (c352ch->end_addr == 0x0000))
)
{
if ( (flag & C352_FLG_LINK) && (flag & C352_FLG_LOOP) )
{
c352ch->bank = c352ch->start_addr & 0xFF;
c352ch->start_addr = c352ch->repeat_addr;
c352ch->start = c352ch->start_addr;
c352ch->repeat = c352ch->repeat_addr;
pos = (c352ch->bank<<16) + c352ch->start_addr;
c352ch->flag |= C352_FLG_LOOPHIST;
}
else if (flag & C352_FLG_LOOP)
{
pos = (pos & 0xFF0000) + c352ch->repeat;
c352ch->flag |= C352_FLG_LOOPHIST;
}
else
{
c352ch->flag |= C352_FLG_KEYOFF;
c352ch->flag &= ~C352_FLG_BUSY;
//return;
break;
}
}
} else {
pos += cnt;
if (
(((pos&0xFFFF) > c352ch->end_addr) && ((pos&0xFFFF) < c352ch->start) && (c352ch->start > c352ch->end_addr) ) ||
(((pos&0xFFFF) > c352ch->end_addr) && ((pos&0xFFFF) > c352ch->start) && (c352ch->start < c352ch->end_addr) ) ||
((pos > (bank|0xFFFF)) && (c352ch->end_addr == 0xFFFF))
)
{
if ( (flag & C352_FLG_LINK) && (flag & C352_FLG_LOOP) )
{
c352ch->bank = c352ch->start_addr & 0xFF;
c352ch->start_addr = c352ch->repeat_addr;
c352ch->start = c352ch->start_addr;
c352ch->repeat = c352ch->repeat_addr;
pos = (c352ch->bank<<16) + c352ch->start_addr;
c352ch->flag |= C352_FLG_LOOPHIST;
}
else if (flag & C352_FLG_LOOP)
{
pos = (pos & 0xFF0000) + c352ch->repeat;
c352ch->flag |= C352_FLG_LOOPHIST;
}
else
{
c352ch->flag |= C352_FLG_KEYOFF;
c352ch->flag &= ~C352_FLG_BUSY;
//return;
break;
}
}
}
}
c352ch->noisecnt = noisecnt;
c352ch->noisebuf = noisebuf;
c352ch->pos = offset;
c352ch->current_addr = pos;
}
//static STREAM_UPDATE( c352_update )
void c352_update(void *param, stream_sample_t **outputs, int samples)
{
c352_state *info = (c352_state *)param;
int 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++)
{
info->channel_l[i] = info->channel_r[i] = info->channel_l2[i] = info->channel_r2[i] = 0;
}*/
memset(outputs[0], 0x00, samples * sizeof(stream_sample_t));
memset(outputs[1], 0x00, samples * sizeof(stream_sample_t));
for (j = 0 ; j < 32 ; j++)
{
//c352_mix_one_channel(info, j, samples);
if ((info->c352_ch[j].flag & C352_FLG_BUSY) && ! info->c352_ch[j].Muted)
c352_mix_one_channel(info, j, outputs, samples);
}
/*for(i = 0 ; i < samples ; 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(c352_state *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(c352_state *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 )
{
if (info->c352_ch[i].start_addr != info->c352_ch[i].end_addr)
{
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)
{
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));
info->c352_ch[chan].vol_l = val & 0xff;
info->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));
info->c352_ch[chan].vol_l2 = val & 0xff;
info->c352_ch[chan].vol_r2 = val >> 8;
break;
case 0x4:
// pitch
LOG(("CH %02ld PITCH %04x\n", chan, val));
info->c352_ch[chan].pitch = val;
break;
case 0x6:
// flags
LOG(("CH %02ld FLAG %02x\n", chan, val));
info->c352_ch[chan].flag = val;
break;
case 0x8:
// bank (bits 16-31 of address);
info->c352_ch[chan].bank = val & 0xff;
LOG(("CH %02ld BANK %02x", chan, info->c352_ch[chan].bank));
break;
case 0xa:
// start address
LOG(("CH %02ld SADDR %04x\n", chan, val));
info->c352_ch[chan].start_addr = val;
break;
case 0xc:
// end address
LOG(("CH %02ld EADDR %04x\n", chan, val));
info->c352_ch[chan].end_addr = val;
break;
case 0xe:
// loop address
LOG(("CH %02ld LADDR %04x\n", chan, val));
info->c352_ch[chan].repeat_addr = val;
break;
default:
LOG(("CH %02ld UNKN %01lx %04x", chan, address & 0xf, val));
break;
}
}
//static DEVICE_START( c352 )
int device_start_c352(void **_info, int clock, int clkdiv)
{
//c352_state *info = get_safe_token(device);
c352_state *info;
int i;
double x_max = 32752.0;
double y_max = 127.0;
double u = 10.0;
info = (c352_state *) calloc(1, sizeof(c352_state));
*_info = (void *) info;
//info->c352_rom_samples = *device->region();
//info->c352_rom_length = device->region()->bytes();
info->c352_rom_samples = NULL;
info->c352_rom_length = 0x00;
if (! clkdiv)
clkdiv = 288;
//info->sample_rate_base = device->clock() / 288;
info->sample_rate_base = clock / clkdiv;
//info->stream = stream_create(device, 0, 4, info->sample_rate_base, info, c352_update);
// 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;
}
// register save state info
for (i = 0; i < 32; i++)
{
/*state_save_register_device_item(device, i, info->c352_ch[i].vol_l);
state_save_register_device_item(device, i, info->c352_ch[i].vol_r);
state_save_register_device_item(device, i, info->c352_ch[i].vol_l2);
state_save_register_device_item(device, i, info->c352_ch[i].vol_r2);
state_save_register_device_item(device, i, info->c352_ch[i].bank);
state_save_register_device_item(device, i, info->c352_ch[i].noise);
state_save_register_device_item(device, i, info->c352_ch[i].noisebuf);
state_save_register_device_item(device, i, info->c352_ch[i].noisecnt);
state_save_register_device_item(device, i, info->c352_ch[i].pitch);
state_save_register_device_item(device, i, info->c352_ch[i].start_addr);
state_save_register_device_item(device, i, info->c352_ch[i].end_addr);
state_save_register_device_item(device, i, info->c352_ch[i].repeat_addr);
state_save_register_device_item(device, i, info->c352_ch[i].flag);
state_save_register_device_item(device, i, info->c352_ch[i].start);
state_save_register_device_item(device, i, info->c352_ch[i].repeat);
state_save_register_device_item(device, i, info->c352_ch[i].current_addr);
state_save_register_device_item(device, i, info->c352_ch[i].pos);*/
info->c352_ch[i].Muted = 0x00;
}
return info->sample_rate_base;
}
void device_stop_c352(void *_info)
{
c352_state *info = (c352_state *)_info;
free(info->c352_rom_samples);
info->c352_rom_samples = NULL;
free(info);
return;
}
void device_reset_c352(void *_info)
{
c352_state *info = (c352_state *)_info;
// clear all channels states
memset(info->c352_ch, 0, sizeof(c352_ch_t)*32);
// init noise generator
info->mseq_reg = 0x12345678;
return;
}
//READ16_DEVICE_HANDLER( c352_r )
UINT16 c352_r(void *_info, offs_t offset)
{
c352_state *info = (c352_state *)_info;
return(c352_read_reg16(info, offset*2));
//return(c352_read_reg16(get_safe_token(device), offset*2));
}
//WRITE16_DEVICE_HANDLER( c352_w )
void c352_w(void *_info, offs_t offset, UINT16 data)
{
c352_state *info = (c352_state *)_info;
/*if (mem_mask == 0xffff)
{
c352_write_reg16(get_safe_token(device), offset*2, data);
}
else
{
logerror("C352: byte-wide write unsupported at this time!\n");
}*/
c352_write_reg16(info, offset*2, data);
}
void c352_write_rom(void *_info, offs_t ROMSize, offs_t DataStart, offs_t DataLength,
const UINT8* ROMData)
{
c352_state *info = (c352_state *)_info;
if (info->c352_rom_length != ROMSize)
{
info->c352_rom_samples = (UINT8*)realloc(info->c352_rom_samples, ROMSize);
info->c352_rom_length = ROMSize;
memset(info->c352_rom_samples, 0xFF, ROMSize);
}
if (DataStart > ROMSize)
return;
if (DataStart + DataLength > ROMSize)
DataLength = ROMSize - DataStart;
memcpy(info->c352_rom_samples + DataStart, ROMData, DataLength);
return;
}
void c352_set_mute_mask(void *_info, UINT32 MuteMask)
{
c352_state *info = (c352_state *)_info;
UINT8 CurChn;
for (CurChn = 0; CurChn < 32; CurChn ++)
info->c352_ch[CurChn].Muted = (MuteMask >> CurChn) & 0x01;
return;
}
/**************************************************************************
* Generic get_info
**************************************************************************/
/*DEVICE_GET_INFO( c352 )
{
switch (state)
{
// --- the following bits of info are returned as 64-bit signed integers ---
case DEVINFO_INT_TOKEN_BYTES: info->i = sizeof(c352_state); break;
// --- the following bits of info are returned as pointers to data or functions ---
case DEVINFO_FCT_START: info->start = DEVICE_START_NAME( c352 ); break;
case DEVINFO_FCT_STOP: // nothing // break;
case DEVINFO_FCT_RESET: // nothing // break;
// --- the following bits of info are returned as NULL-terminated strings ---
case DEVINFO_STR_NAME: strcpy(info->s, "C352"); break;
case DEVINFO_STR_FAMILY: strcpy(info->s, "Namco PCM"); break;
case DEVINFO_STR_VERSION: strcpy(info->s, "1.1"); break;
case DEVINFO_STR_SOURCE_FILE: strcpy(info->s, __FILE__); break;
case DEVINFO_STR_CREDITS: strcpy(info->s, "Copyright Nicola Salmoria and the MAME Team"); break;
}
}
DEFINE_LEGACY_SOUND_DEVICE(C352, c352);*/