/**********************************************************************************************
*
* OKI MSM6376 ADPCM
* by Mirko Buffoni
*
* TODO:
* add BEEP tone generator
* add 2ch handling (for echoing and continuous speech)
* add echo
* Something is definitely not right with the rates and divisions here - the maximum
* oscillation frequency should only be 256KHz, yet this only sounds decent at 3MHz
* add proper NAR handling - this varies with clock rate
**********************************************************************************************/
#include "emu.h"
#include "okim6376.h"
#define MAX_SAMPLE_CHUNK 10000
#define MAX_WORDS 111
/* struct describing a single playing ADPCM voice */
struct ADPCMVoice
{
UINT8 playing; /* 1 if we are actively playing */
UINT32 base_offset; /* pointer to the base memory location */
UINT32 sample; /* current sample number */
UINT32 count; /* total samples to play */
UINT32 volume; /* output volume */
INT32 signal;
INT32 step;
};
typedef struct _okim6376_state okim6376_state;
struct _okim6376_state
{
#define OKIM6376_VOICES 2
struct ADPCMVoice voice[OKIM6376_VOICES];
INT32 command[OKIM6376_VOICES];
INT32 latch; /* Command data is held before transferring to either channel */
UINT8 *region_base; /* pointer to the base of the region */
sound_stream *stream; /* which stream are we playing on? */
UINT32 master_clock; /* master clock frequency */
UINT8 channel;
UINT8 nar; /* Next Address Ready */
UINT8 busy;
UINT8 ch2; /* 2CH pin - enables Channel 2 operation */
UINT8 st; /* STart */
UINT8 st_pulses; /* Keep track of attenuation */
UINT8 ch2_update; /* Pulse shape */
UINT8 st_update;
};
/* step size index shift table */
static const int index_shift[8] = { -1, -1, -1, -1, 2, 4, 6, 8 };
/* lookup table for the precomputed difference */
static int diff_lookup[49*16];
/* volume lookup table. Upon configuration, the number of ST pulses determine how much
attenuation to apply to the sound signal. However, this only applies to the second
channel*/
static const int volume_table[3] =
{
0x20, // 0 dB
0x10, // -6.0 dB
0x08, // -12.0 dB
};
/* tables computed? */
static int tables_computed = 0;
INLINE okim6376_state *get_safe_token(device_t *device)
{
assert(device != NULL);
assert(device->type() == OKIM6376);
return (okim6376_state *)downcast<legacy_device_base *>(device)->token();
}
/**********************************************************************************************
compute_tables -- compute the difference tables
***********************************************************************************************/
static void compute_tables(void)
{
/* nibble to bit map */
static const int nbl2bit[16][4] =
{
{ 1, 0, 0, 0}, { 1, 0, 0, 1}, { 1, 0, 1, 0}, { 1, 0, 1, 1},
{ 1, 1, 0, 0}, { 1, 1, 0, 1}, { 1, 1, 1, 0}, { 1, 1, 1, 1},
{-1, 0, 0, 0}, {-1, 0, 0, 1}, {-1, 0, 1, 0}, {-1, 0, 1, 1},
{-1, 1, 0, 0}, {-1, 1, 0, 1}, {-1, 1, 1, 0}, {-1, 1, 1, 1}
};
int step, nib;
/* loop over all possible steps */
for (step = 0; step <= 48; step++)
{
/* compute the step value */
int stepval = floor(16.0 * pow(11.0 / 10.0, (double)step));
/* loop over all nibbles and compute the difference */
for (nib = 0; nib < 16; nib++)
{
diff_lookup[step*16 + nib] = nbl2bit[nib][0] *
(stepval * nbl2bit[nib][1] +
stepval/2 * nbl2bit[nib][2] +
stepval/4 * nbl2bit[nib][3] +
stepval/8);
}
}
tables_computed = 1;
}
/**********************************************************************************************
reset_adpcm -- reset the ADPCM stream
***********************************************************************************************/
static void reset_adpcm(struct ADPCMVoice *voice)
{
/* make sure we have our tables */
if (!tables_computed)
compute_tables();
/* reset the signal/step */
voice->signal = -2;
voice->step = 0;
}
/**********************************************************************************************
clock_adpcm -- clock the next ADPCM byte
***********************************************************************************************/
static INT16 clock_adpcm(struct ADPCMVoice *voice, UINT8 nibble)
{
voice->signal += diff_lookup[voice->step * 16 + (nibble & 15)];
/* clamp to the maximum 12bit */
if (voice->signal > 2047)
voice->signal = 2047;
else if (voice->signal < -2048)
voice->signal = -2048;
/* adjust the step size and clamp */
voice->step += index_shift[nibble & 7];
if (voice->step > 48)
voice->step = 48;
else if (voice->step < 0)
voice->step = 0;
/* return the signal */
return voice->signal;
}
/**********************************************************************************************
generate_adpcm -- general ADPCM decoding routine
***********************************************************************************************/
static void generate_adpcm(okim6376_state *chip, struct ADPCMVoice *voice, INT16 *buffer, int samples)
{
/* if this voice is active */
if (voice->playing)
{
UINT8 *base = chip->region_base + voice->base_offset;
int sample = voice->sample;
int count = voice->count;
/* loop while we still have samples to generate */
while (samples)
{
int nibble;
if (count == 0)
{
/* get the number of samples to play */
count = (base[sample / 2] & 0x7f) << 1;
/* end of voice marker */
if (count == 0)
{
voice->playing = 0;
break;
}
else
{
/* step past the count byte */
sample += 2;
}
}
/* compute the new amplitude and update the current step */
nibble = base[sample / 2] >> (((sample & 1) << 2) ^ 4);
/* output to the buffer, scaling by the volume */
/* signal in range -4096..4095, volume in range 2..16 => signal * volume / 2 in range -32768..32767 */
*buffer++ = clock_adpcm(voice, nibble) * voice->volume / 2;
++sample;
--count;
--samples;
}
/* update the parameters */
voice->sample = sample;
voice->count = count;
}
/* fill the rest with silence */
while (samples--)
*buffer++ = 0;
}
/**********************************************************************************************
okim6376_update -- update the sound chip so that it is in sync with CPU execution
***********************************************************************************************/
static STREAM_UPDATE( okim6376_update )
{
okim6376_state *chip = (okim6376_state *)param;
int i;
memset(outputs[0], 0, samples * sizeof(*outputs[0]));
for (i = 0; i < OKIM6376_VOICES; i++)
{
struct ADPCMVoice *voice = &chip->voice[i];
stream_sample_t *buffer = outputs[0];
INT16 sample_data[MAX_SAMPLE_CHUNK];
int remaining = samples;
/* loop while we have samples remaining */
while (remaining)
{
int samples = (remaining > MAX_SAMPLE_CHUNK) ? MAX_SAMPLE_CHUNK : remaining;
int samp;
generate_adpcm(chip, voice, sample_data, samples);
for (samp = 0; samp < samples; samp++)
*buffer++ += sample_data[samp];
remaining -= samples;
}
}
}
/**********************************************************************************************
state save support for MAME
***********************************************************************************************/
static void adpcm_state_save_register(struct ADPCMVoice *voice, device_t *device, int index)
{
device->save_item(NAME(voice->playing), index);
device->save_item(NAME(voice->sample), index);
device->save_item(NAME(voice->count), index);
device->save_item(NAME(voice->signal), index);
device->save_item(NAME(voice->step), index);
device->save_item(NAME(voice->volume), index);
device->save_item(NAME(voice->base_offset), index);
}
static void okim6376_state_save_register(okim6376_state *info, device_t *device)
{
int j;
for (j = 0; j < OKIM6376_VOICES; j++)
{
adpcm_state_save_register(&info->voice[j], device, j);
}
device->save_item(NAME(info->command[0]));
device->save_item(NAME(info->command[1]));
}
static void oki_process(device_t *device, int channel, int command)
{
okim6376_state *info = get_safe_token(device);
/* if a command is pending, process the second half */
if ((command != -1) && (command != 0)) //process silence separately
{
int start;
unsigned char *base/*, *base_end*/;
info->nar = 0;//processing
/* update the stream */
info->stream->update();
/* determine which voice(s) (voice is set by the state of 2CH) */
{
struct ADPCMVoice *voice = &info->voice[channel];
/* determine the start position, max address space is 16Mbit */
base = &info->region_base[info->command[channel] * 4];
//base_end = &info->region_base[(MAX_WORDS+1) * 4];
start = ((base[0] << 16) + (base[1] << 8) + base[2]) & 0x1fffff;
if (start == 0)
{
voice->playing = 0;
}
else
{
/* set up the voice to play this sample */
if (!voice->playing)
{
voice->playing = 1;
voice->base_offset = start;
voice->sample = 0;
voice->count = 0;
/* also reset the ADPCM parameters */
reset_adpcm(voice);
/* FIX: no attenuation for now, handle for channel 2 separately */
voice->volume = volume_table[0];
}
else
{
logerror("OKIM6376:'%s' requested to play sample %02x on non-stopped voice\n",device->tag(),info->command[channel]);
}
}
info->nar = 1;
}
}
/* otherwise, see if this is a silence command */
else
{
info->nar = 0;
/* update the stream, then turn it off */
info->stream->update();
if (command ==0)
{
int i;
for (i = 0; i < OKIM6376_VOICES; i++)
{
struct ADPCMVoice *voice = &info->voice[i];
voice->playing = 0;
}
info->nar = 1;
}
}
}
/**********************************************************************************************
DEVICE_START( okim6376 ) -- start emulation of an OKIM6376-compatible chip
***********************************************************************************************/
static DEVICE_START( okim6376 )
{
okim6376_state *info = get_safe_token(device);
int voice;
int divisor = 165;
compute_tables();
info->command[0] = -1;
info->command[1] = -1;
info->latch = 0;
info->region_base = *device->region();
info->master_clock = device->clock();
info->nar = 1;
info->busy = 1;
info->st = 1;
info->st_update = 0;
info->ch2_update = 0;
info->st_pulses = 0;
/* generate the name and create the stream */
info->stream = device->machine().sound().stream_alloc(*device, 0, 1, device->clock()/divisor, info, okim6376_update);
/* initialize the voices */
for (voice = 0; voice < OKIM6376_VOICES; voice++)
{
/* initialize the rest of the structure */
info->voice[voice].volume = 0;
reset_adpcm(&info->voice[voice]);
}
okim6376_state_save_register(info, device);
}
void okim6376_set_frequency(device_t *device, int frequency)
{
okim6376_state *info = get_safe_token(device);
info->master_clock = frequency;
info->stream->set_sample_rate(info->master_clock / 165);
}
/**********************************************************************************************
DEVICE_RESET( okim6376 ) -- stop emulation of an OKIM6376-compatible chip
***********************************************************************************************/
static DEVICE_RESET( okim6376 )
{
okim6376_state *info = get_safe_token(device);
int i;
info->stream->update();
for (i = 0; i < OKIM6376_VOICES; i++)
info->voice[i].playing = 0;
}
/**********************************************************************************************
okim6376_status_r -- read the status port of an OKIM6376-compatible chip
***********************************************************************************************/
READ8_DEVICE_HANDLER( okim6376_r )
{
okim6376_state *info = get_safe_token(device);
int i, result;
result = 0xff;
/* set the bit to 1 if something is playing on a given channel */
info->stream->update();
for (i = 0; i < OKIM6376_VOICES; i++)
{
struct ADPCMVoice *voice = &info->voice[i];
/* set the bit if it's playing */
if (!voice->playing)
result ^= 1 << i;
}
return result;
}
READ_LINE_DEVICE_HANDLER( okim6376_busy_r )
{
okim6376_state *info = get_safe_token(device);
int i,result=1;
for (i = 0; i < OKIM6376_VOICES; i++)
{
struct ADPCMVoice *voice = &info->voice[i];
/* set the bit low if it's playing */
if (voice->playing)
result = 0;
}
return result;
}
READ_LINE_DEVICE_HANDLER( okim6376_nar_r )
{
okim6376_state *info = get_safe_token(device);
return info->nar;
}
WRITE_LINE_DEVICE_HANDLER( okim6376_ch2_w )
{
okim6376_state *info = get_safe_token(device);
if (info->ch2 != state)
{
info->ch2 = state;
info->ch2_update = 1;
}
if((!info->ch2)&&(info->ch2_update))
{
info->channel = 1;
if (info->command[1] != info->latch)
{
info->command[1] = info->latch;
}
}
if((info->ch2)&&(info->ch2_update))
{
info->channel = 0;
oki_process(device, 0, info->command[1]);
}
}
WRITE_LINE_DEVICE_HANDLER( okim6376_st_w )
{
//As in STart, presumably, this triggers everything
okim6376_state *info = get_safe_token(device);
if (info->st != state)
{
info->st = state;
info->st_update = 1;
if ((info->channel == 1) & !info->st)//ST acts as attenuation for Channel 2 when low, and stays at that level until the channel is reset
{
struct ADPCMVoice *voice = &info->voice[info->channel];
{
info->st_pulses ++;
if (info->st_pulses > 3)
{
info->st_pulses = 3; //undocumented behaviour beyond 3 pulses
}
voice->volume = volume_table[info->st_pulses];
}
}
if (info->st && info->st_update)
{
info->command[info->channel] = info->latch;
if (info->channel ==0)
{
oki_process(device, 0, info->command[0]);
}
}
}
}
/**********************************************************************************************
okim6376_data_w -- write to the data port of an OKIM6376-compatible chip
***********************************************************************************************/
WRITE8_DEVICE_HANDLER( okim6376_w )
{
// The data port is purely used to set the latch, everything else is started by an ST pulse
okim6376_state *info = get_safe_token(device);
info->latch = data & 0x7f;
// FIX: maximum adpcm words are 111, there are other 8 commands to generate BEEP tone (0x70 to 0x77),
// and others for internal testing, that manual explicitly says not to use (0x78 to 0x7f)
}
/**************************************************************************
* Generic get_info
**************************************************************************/
DEVICE_GET_INFO( okim6376 )
{
switch (state)
{
/* --- the following bits of info are returned as 64-bit signed integers --- */
case DEVINFO_INT_TOKEN_BYTES: info->i = sizeof(okim6376_state); break;
/* --- the following bits of info are returned as pointers to data or functions --- */
case DEVINFO_FCT_START: info->start = DEVICE_START_NAME( okim6376 ); break;
case DEVINFO_FCT_STOP: /* nothing */ break;
case DEVINFO_FCT_RESET: info->reset = DEVICE_RESET_NAME( okim6376 ); break;
/* --- the following bits of info are returned as NULL-terminated strings --- */
case DEVINFO_STR_NAME: strcpy(info->s, "OKI6376"); break;
case DEVINFO_STR_FAMILY: strcpy(info->s, "OKI ADPCM"); break;
case DEVINFO_STR_VERSION: strcpy(info->s, "1.0"); 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(OKIM6376, okim6376);