// license:BSD-3-Clause
// copyright-holders:Quench
/**********************************************************************************************
*
* Streaming single channel ADPCM core for the ES8712 chip
* Chip is branded by Excellent Systems, probably OEM'd.
*
* Samples are currently looped, but whether they should and how, is unknown.
* Interface to the chip is also not 100% clear.
* Should there be any status signals signifying busy, end of sample - etc?
*
* Heavily borrowed from the OKI M6295 source
*
**********************************************************************************************/
#include "emu.h"
#include "es8712.h"
#define MAX_SAMPLE_CHUNK 10000
/* 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];
// device type definition
const device_type ES8712 = &device_creator<es8712_device>;
//**************************************************************************
// LIVE DEVICE
//**************************************************************************
//-------------------------------------------------
// es8712_device - constructor
//-------------------------------------------------
es8712_device::es8712_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
: device_t(mconfig, ES8712, "ES8712", tag, owner, clock, "es8712", __FILE__),
device_sound_interface(mconfig, *this),
m_rom(*this, DEVICE_SELF),
m_playing(0),
m_base_offset(0),
m_sample(0),
m_count(0),
m_signal(0),
m_step(0),
m_start(0),
m_end(0),
m_repeat(0),
m_bank_offset(0),
m_stream(nullptr)
{
}
//-------------------------------------------------
// device_start - start emulation of an ES8712 chip
//-------------------------------------------------
void es8712_device::device_start()
{
compute_tables();
m_start = 0;
m_end = 0;
m_repeat = 0;
m_bank_offset = 0;
/* generate the name and create the stream */
m_stream = stream_alloc(0, 1, clock());
/* initialize the rest of the structure */
m_signal = -2;
es8712_state_save_register();
}
//-------------------------------------------------
// device_reset - stop emulation of an ES8712-compatible chip
//-------------------------------------------------
void es8712_device::device_reset()
{
if (m_playing)
{
/* update the stream, then turn it off */
m_stream->update();
m_playing = 0;
m_repeat = 0;
}
}
//-------------------------------------------------
// sound_stream_update - update the sound chip so that it is in sync with CPU execution
//-------------------------------------------------
void es8712_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
stream_sample_t *buffer = outputs[0];
/* generate them into our buffer */
generate_adpcm(buffer, samples);
}
//-------------------------------------------------
// compute_tables -- compute the difference tables
//-------------------------------------------------
void es8712_device::compute_tables()
{
/* 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);
}
}
}
//-------------------------------------------------
// generate_adpcm -- general ADPCM decoding routine
//-------------------------------------------------
void es8712_device::generate_adpcm(stream_sample_t *buffer, int samples)
{
/* if this chip is active */
if (m_playing)
{
UINT8 *base = &m_rom[m_bank_offset + m_base_offset];
int sample = m_sample;
int signal = m_signal;
int count = m_count;
int step = m_step;
int val;
/* loop while we still have samples to generate */
while (samples)
{
/* compute the new amplitude and update the current step */
val = base[sample / 2] >> (((sample & 1) << 2) ^ 4);
signal += diff_lookup[step * 16 + (val & 15)];
/* clamp to the maximum */
if (signal > 2047)
signal = 2047;
else if (signal < -2048)
signal = -2048;
/* adjust the step size and clamp */
step += index_shift[val & 7];
if (step > 48)
step = 48;
else if (step < 0)
step = 0;
/* output to the buffer */
*buffer++ = signal * 16;
samples--;
/* next! */
if (++sample >= count)
{
if (m_repeat)
{
sample = 0;
signal = -2;
step = 0;
}
else
{
m_playing = 0;
break;
}
}
}
/* update the parameters */
m_sample = sample;
m_signal = signal;
m_step = step;
}
/* fill the rest with silence */
while (samples--)
*buffer++ = 0;
}
//-------------------------------------------------
// state save support for MAME
//-------------------------------------------------
void es8712_device::es8712_state_save_register()
{
save_item(NAME(m_bank_offset));
save_item(NAME(m_playing));
save_item(NAME(m_sample));
save_item(NAME(m_count));
save_item(NAME(m_signal));
save_item(NAME(m_step));
save_item(NAME(m_base_offset));
save_item(NAME(m_start));
save_item(NAME(m_end));
save_item(NAME(m_repeat));
}
//-------------------------------------------------
// es8712_set_bank_base -- set the base of the bank on a given chip
//-------------------------------------------------
void es8712_device::set_bank_base(int base)
{
m_stream->update();
m_bank_offset = base;
}
//-------------------------------------------------
// es8712_set_frequency -- dynamically adjusts the frequency of a given ADPCM chip
//-------------------------------------------------
void es8712_device::set_frequency(int frequency)
{
/* update the stream and set the new base */
m_stream->update();
m_stream->set_sample_rate(frequency);
}
//-------------------------------------------------
// play -- Begin playing the addressed sample
//-------------------------------------------------
void es8712_device::play()
{
if (m_start < m_end)
{
if (!m_playing)
{
m_playing = 1;
m_base_offset = m_start;
m_sample = 0;
m_count = 2 * (m_end - m_start + 1);
m_repeat = 0;//1;
/* also reset the ADPCM parameters */
m_signal = -2;
m_step = 0;
}
}
/* invalid samples go here */
else
{
logerror("ES871295:'%s' requested to play invalid sample range %06x-%06x\n", tag(), m_start, m_end);
if (m_playing)
{
/* update the stream */
m_stream->update();
m_playing = 0;
}
}
}
/**********************************************************************************************
es8712_data_0_w -- generic data write functions
es8712_data_1_w
***********************************************************************************************/
/**********************************************************************************************
*
* offset Start End
* 0hmmll - 0HMMLL
* 00 ----ll
* 01 --mm--
* 02 0h----
* 03 ----LL
* 04 --MM--
* 05 0H----
* 06 Go!
*
* Offsets are written in the order -> 00, 02, 01, 03, 05, 04, 06
* Offset 06 is written with the same value as offset 04.
*
***********************************************************************************************/
WRITE8_MEMBER( es8712_device::es8712_w )
{
switch (offset)
{
case 00: m_start &= 0x000fff00;
m_start |= ((data & 0xff) << 0); break;
case 01: m_start &= 0x000f00ff;
m_start |= ((data & 0xff) << 8); break;
case 02: m_start &= 0x0000ffff;
m_start |= ((data & 0x0f) << 16); break;
case 03: m_end &= 0x000fff00;
m_end |= ((data & 0xff) << 0); break;
case 04: m_end &= 0x000f00ff;
m_end |= ((data & 0xff) << 8); break;
case 05: m_end &= 0x0000ffff;
m_end |= ((data & 0x0f) << 16); break;
case 06:
play(); break;
default: break;
}
m_start &= 0xfffff; m_end &= 0xfffff;
}