/***************************************************************************

    okim6295.h

    OKIM 6295 ADCPM sound chip.

****************************************************************************

    Library to transcode from an ADPCM source to raw PCM.
    Written by Buffoni Mirko in 08/06/97
    References: various sources and documents.

    R. Belmont 31/10/2003
    Updated to allow a driver to use both MSM6295s and "raw" ADPCM voices
    (gcpinbal). Also added some error trapping for MAME_DEBUG builds

****************************************************************************

    OKIM 6295 ADPCM chip:

    Command bytes are sent:

        1xxx xxxx = start of 2-byte command sequence, xxxxxxx is the sample
                    number to trigger
        abcd vvvv = second half of command; one of the abcd bits is set to
                    indicate which voice the v bits seem to be volumed

        0abc d000 = stop playing; one or more of the abcd bits is set to
                    indicate which voice(s)

    Status is read:

        ???? abcd = one bit per voice, set to 0 if nothing is playing, or
                    1 if it is active

***************************************************************************/

#include "emu.h"
#include "okim6295.h"


//**************************************************************************
//  GLOBAL VARIABLES
//**************************************************************************

// device type definition
const device_type OKIM6295 = &device_creator<okim6295_device>;

// ADPCM state and tables
bool adpcm_state::s_tables_computed = false;
const INT8 adpcm_state::s_index_shift[8] = { -1, -1, -1, -1, 2, 4, 6, 8 };
int adpcm_state::s_diff_lookup[49*16];

// volume lookup table. The manual lists only 9 steps, ~3dB per step. Given the dB values,
// that seems to map to a 5-bit volume control. Any volume parameter beyond the 9th index
// results in silent playback.
const UINT8 okim6295_device::s_volume_table[16] =
{
	0x20,	//   0 dB
	0x16,	//  -3.2 dB
	0x10,	//  -6.0 dB
	0x0b,	//  -9.2 dB
	0x08,	// -12.0 dB
	0x06,	// -14.5 dB
	0x04,	// -18.0 dB
	0x03,	// -20.5 dB
	0x02,	// -24.0 dB
	0x00,
	0x00,
	0x00,
	0x00,
	0x00,
	0x00,
	0x00,
};

// default address map
static ADDRESS_MAP_START( okim6295, AS_0, 8 )
	AM_RANGE(0x00000, 0x3ffff) AM_ROM
ADDRESS_MAP_END



//**************************************************************************
//  LIVE DEVICE
//**************************************************************************

//-------------------------------------------------
//  okim6295_device - constructor
//-------------------------------------------------

okim6295_device::okim6295_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
	: device_t(mconfig, OKIM6295, "OKI6295", tag, owner, clock),
	  device_sound_interface(mconfig, *this),
	  device_memory_interface(mconfig, *this),
	  m_space_config("samples", ENDIANNESS_LITTLE, 8, 18, 0, NULL, *ADDRESS_MAP_NAME(okim6295)),
	  m_command(-1),
	  m_bank_installed(false),
	  m_bank_offs(0),
	  m_stream(NULL),
	  m_pin7_state(0),
	  m_direct(NULL)
{
}


//-------------------------------------------------
//  static_set_pin7 - configuration helper to set
//  the pin 7 state
//-------------------------------------------------

void okim6295_device::static_set_pin7(device_t &device, int pin7)
{
	okim6295_device &okim6295 = downcast<okim6295_device &>(device);
	okim6295.m_pin7_state = pin7;
}


//-------------------------------------------------
//  device_start - device-specific startup
//-------------------------------------------------

void okim6295_device::device_start()
{
	// find our direct access
	m_direct = &space()->direct();

	// create the stream
	int divisor = m_pin7_state ? 132 : 165;
	m_stream = machine().sound().stream_alloc(*this, 0, 1, clock() / divisor);

	save_item(NAME(m_command));
	save_item(NAME(m_bank_offs));
	for (int voicenum = 0; voicenum < OKIM6295_VOICES; voicenum++)
	{
		save_item(NAME(m_voice[voicenum].m_playing), voicenum);
		save_item(NAME(m_voice[voicenum].m_sample), voicenum);
		save_item(NAME(m_voice[voicenum].m_count), voicenum);
		save_item(NAME(m_voice[voicenum].m_adpcm.m_signal), voicenum);
		save_item(NAME(m_voice[voicenum].m_adpcm.m_step), voicenum);
		save_item(NAME(m_voice[voicenum].m_volume), voicenum);
		save_item(NAME(m_voice[voicenum].m_base_offset), voicenum);
	}
}


//-------------------------------------------------
//  device_reset - device-specific reset
//-------------------------------------------------

void okim6295_device::device_reset()
{
	m_stream->update();
	for (int voicenum = 0; voicenum < OKIM6295_VOICES; voicenum++)
		m_voice[voicenum].m_playing = false;
}


//-------------------------------------------------
//  device_post_load - device-specific post-load
//-------------------------------------------------

void okim6295_device::device_post_load()
{
	set_bank_base(m_bank_offs);
}


//-------------------------------------------------
//  device_clock_changed - called if the clock
//  changes
//-------------------------------------------------

void okim6295_device::device_clock_changed()
{
	int divisor = m_pin7_state ? 132 : 165;
	m_stream->set_sample_rate(clock() / divisor);
}


//-------------------------------------------------
//  memory_space_config - return a description of
//  any address spaces owned by this device
//-------------------------------------------------

const address_space_config *okim6295_device::memory_space_config(address_spacenum spacenum) const
{
	return (spacenum == 0) ? &m_space_config : NULL;
}


//-------------------------------------------------
//  stream_generate - handle update requests for
//  our sound stream
//-------------------------------------------------

void okim6295_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
	// reset the output stream
	memset(outputs[0], 0, samples * sizeof(*outputs[0]));

	// iterate over voices and accumulate sample data
	for (int voicenum = 0; voicenum < OKIM6295_VOICES; voicenum++)
		m_voice[voicenum].generate_adpcm(*m_direct, outputs[0], samples);
}


//-------------------------------------------------
//  set_bank_base - old-style bank management;
//  assumes multiple 256k banks
//-------------------------------------------------

void okim6295_device::set_bank_base(offs_t base)
{
	// flush out anything pending
	m_stream->update();

	// if we are setting a non-zero base, and we have no bank, allocate one
	if (!m_bank_installed && base != 0)
	{
		// override our memory map with a bank
		space()->install_read_bank(0x00000, 0x3ffff, tag());
		m_bank_installed = true;
	}

	// if we have a bank number, set the base pointer
	if (m_bank_installed)
	{
		m_bank_offs = base;
		memory_set_bankptr(machine(), tag(), m_region->base() + base);
	}
}


//-------------------------------------------------
//  set_pin7 - change the state of pin 7, which
//  alters the frequency we output
//-------------------------------------------------

void okim6295_device::set_pin7(int pin7)
{
	m_pin7_state = pin7;
	device_clock_changed();
}


//-------------------------------------------------
//  read_status - read the status register
//-------------------------------------------------

UINT8 okim6295_device::read_status()
{
	UINT8 result = 0xf0;	// naname expects bits 4-7 to be 1

	// set the bit to 1 if something is playing on a given channel
	m_stream->update();
	for (int voicenum = 0; voicenum < OKIM6295_VOICES; voicenum++)
		if (m_voice[voicenum].m_playing)
			result |= 1 << voicenum;

	return result;
}


//-------------------------------------------------
//  read - memory interface for read
//-------------------------------------------------

READ8_MEMBER( okim6295_device::read )
{
	return read_status();
}


//-------------------------------------------------
//  write_command - write to the command register
//-------------------------------------------------

void okim6295_device::write_command(UINT8 command)
{
	// if a command is pending, process the second half
	if (m_command != -1)
	{
		// the manual explicitly says that it's not possible to start multiple voices at the same time
		int voicemask = command >> 4;
		//if (voicemask != 0 && voicemask != 1 && voicemask != 2 && voicemask != 4 && voicemask != 8)
		//  popmessage("OKI6295 start %x contact MAMEDEV", voicemask);

		// update the stream
		m_stream->update();

		// determine which voice(s) (voice is set by a 1 bit in the upper 4 bits of the second byte)
		for (int voicenum = 0; voicenum < OKIM6295_VOICES; voicenum++, voicemask >>= 1)
			if (voicemask & 1)
			{
				okim_voice &voice = m_voice[voicenum];

				// determine the start/stop positions
				offs_t base = m_command * 8;

				offs_t start = m_direct->read_raw_byte(base + 0) << 16;
				start |= m_direct->read_raw_byte(base + 1) << 8;
				start |= m_direct->read_raw_byte(base + 2) << 0;
				start &= 0x3ffff;

				offs_t stop = m_direct->read_raw_byte(base + 3) << 16;
				stop |= m_direct->read_raw_byte(base + 4) << 8;
				stop |= m_direct->read_raw_byte(base + 5) << 0;
				stop &= 0x3ffff;

				// set up the voice to play this sample
				if (start < stop)
				{
					if (!voice.m_playing) // fixes Got-cha and Steel Force
					{
						voice.m_playing = true;
						voice.m_base_offset = start;
						voice.m_sample = 0;
						voice.m_count = 2 * (stop - start + 1);

						// also reset the ADPCM parameters
						voice.m_adpcm.reset();
						voice.m_volume = s_volume_table[command & 0x0f];
					}
					else
						logerror("OKIM6295:'%s' requested to play sample %02x on non-stopped voice\n",tag(),m_command);
				}

				// invalid samples go here
				else
				{
					logerror("OKIM6295:'%s' requested to play invalid sample %02x\n",tag(),m_command);
					voice.m_playing = false;
				}
			}

		// reset the command
		m_command = -1;
	}

	// if this is the start of a command, remember the sample number for next time
	else if (command & 0x80)
		m_command = command & 0x7f;

	// otherwise, see if this is a silence command
	else
	{
		// update the stream, then turn it off
		m_stream->update();

		// determine which voice(s) (voice is set by a 1 bit in bits 3-6 of the command
		int voicemask = command >> 3;
		for (int voicenum = 0; voicenum < OKIM6295_VOICES; voicenum++, voicemask >>= 1)
			if (voicemask & 1)
				m_voice[voicenum].m_playing = false;
	}
}


//-------------------------------------------------
//  write - memory interface for write
//-------------------------------------------------

WRITE8_MEMBER( okim6295_device::write )
{
	write_command(data);
}



//**************************************************************************
//  OKIM VOICE
//**************************************************************************

//-------------------------------------------------
//  okim_voice - constructor
//-------------------------------------------------

okim6295_device::okim_voice::okim_voice()
	: m_playing(false),
	  m_base_offset(0),
	  m_sample(0),
	  m_count(0),
	  m_volume(0)
{
}


//-------------------------------------------------
//  generate_adpcm - generate ADPCM samples and
//  add them to an output stream
//-------------------------------------------------

void okim6295_device::okim_voice::generate_adpcm(direct_read_data &direct, stream_sample_t *buffer, int samples)
{
	// skip if not active
	if (!m_playing)
		return;

	// loop while we still have samples to generate
	while (samples-- != 0)
	{
		// fetch the next sample byte
		int nibble = direct.read_raw_byte(m_base_offset + m_sample / 2) >> (((m_sample & 1) << 2) ^ 4);

		// output to the buffer, scaling by the volume
		// signal in range -2048..2047, volume in range 2..32 => signal * volume / 2 in range -32768..32767
		*buffer++ += m_adpcm.clock(nibble) * m_volume / 2;

		// next!
		if (++m_sample >= m_count)
		{
			m_playing = false;
			break;
		}
	}
}



//**************************************************************************
//  ADPCM STATE HELPER
//**************************************************************************

//-------------------------------------------------
//  reset - reset the ADPCM state
//-------------------------------------------------

void adpcm_state::reset()
{
	// reset the signal/step
	m_signal = -2;
	m_step = 0;
}


//-------------------------------------------------
//  device_clock_changed - called if the clock
//  changes
//-------------------------------------------------

INT16 adpcm_state::clock(UINT8 nibble)
{
	// update the signal
	m_signal += s_diff_lookup[m_step * 16 + (nibble & 15)];

	// clamp to the maximum
	if (m_signal > 2047)
		m_signal = 2047;
	else if (m_signal < -2048)
		m_signal = -2048;

	// adjust the step size and clamp
	m_step += s_index_shift[nibble & 7];
	if (m_step > 48)
		m_step = 48;
	else if (m_step < 0)
		m_step = 0;

	// return the signal
	return m_signal;
}


//-------------------------------------------------
//  compute_tables - precompute tables for faster
//  sound generation
//-------------------------------------------------

void adpcm_state::compute_tables()
{
	// skip if we already did it
	if (s_tables_computed)
		return;
	s_tables_computed = true;

	// nibble to bit map
	static const INT8 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}
	};

	// loop over all possible steps
	for (int 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 (int nib = 0; nib < 16; nib++)
		{
			s_diff_lookup[step*16 + nib] = nbl2bit[nib][0] *
				(stepval   * nbl2bit[nib][1] +
				 stepval/2 * nbl2bit[nib][2] +
				 stepval/4 * nbl2bit[nib][3] +
				 stepval/8);
		}
	}
}
