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

    okim9810.h

    OKI MSM9810 ADCPM(2) sound chip.

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

#include "emu.h"
#include "okim9810.h"


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

// device type definition
const device_type OKIM9810 = &device_creator<okim9810_device>;

// volume lookup table. The manual lists a full 16 steps, 2dB per step.
// Given the dB values, that seems to map to a 7-bit volume control.
const UINT8 okim9810_device::okim_voice::s_volume_table[16] =
{
	0x80,	//  0 dB
	0x65,	// -2 dB
	0x50,	// -4 dB
	0x40,	// -6 dB
	0x32,	// -8.0 dB
	0x28,	// -10.5 dB
	0x20,	// -12.0 dB
	0x19,	// -14.5 dB
	0x14,	// -16.0 dB
	0x10,	// -18.0 dB
	0x0c,	// -20.0 dB
	0x0a,	// -22.0 dB
	0x08,	// -24.0 dB
	0x06,	// -26.0 dB
	0x05,	// -28.0 dB
	0x04,	// -30.0 dB
};

// sampling frequency lookup table.
const UINT32 okim9810_device::s_sampling_freq_table[16] =
{
    4000,
    8000,
    16000,
    32000,
    0,
    6400,
    12800,
    25600,
    0,
    5300,
    10600,
    21200,
    0,
    0,
    0,
    0
};

// default address map
static ADDRESS_MAP_START( okim9810, AS_0, 8, okim9810_device )
    AM_RANGE(0x000000, 0xffffff) AM_ROM
ADDRESS_MAP_END



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

//-------------------------------------------------
//  okim9810_device - constructor
//-------------------------------------------------

okim9810_device::okim9810_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
	: device_t(mconfig, OKIM9810, "OKI9810", tag, owner, clock),
	  device_sound_interface(mconfig, *this),
	  device_memory_interface(mconfig, *this),
	  m_space_config("samples", ENDIANNESS_BIG, 8, 24, 0, NULL, *ADDRESS_MAP_NAME(okim9810)),
	  m_stream(NULL),
	  m_TMP_register(0x00),
      m_global_volume(0x00),
      m_filter_type(OKIM9810_SECONDARY_FILTER),
      m_output_level(OKIM9810_OUTPUT_TO_DIRECT_DAC)
{
}


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

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

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

    // save state stuff
    // m_TMP_register
    // m_voice
}


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

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


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

void okim9810_device::device_post_load()
{
}


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

void okim9810_device::device_clock_changed()
{
}


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

const address_space_config *okim9810_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 okim9810_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
	// reset the output streams
	memset(outputs[0], 0, samples * sizeof(*outputs[0]));
	memset(outputs[1], 0, samples * sizeof(*outputs[1]));

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


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

UINT8 okim9810_device::read_status()
{
    UINT8 result = 0x00;
    UINT8 channelMask = 0x01;
    for (int i = 0; i < OKIM9810_VOICES; i++, channelMask <<= 1)
    {
        if (!m_voice[i].m_playing)
            result |= channelMask;
    }
	return result;
}


//-------------------------------------------------
//  read - memory interface for reading the active status
//-------------------------------------------------

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


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

// The command is written when the CMD pin is low
void okim9810_device::write_command(UINT8 data)
{
    const UINT8 cmd = (data & 0xf8) >> 3;
    const UINT8 channel = (data & 0x07);

    switch(cmd)
    {
        case 0x00:  // START
        {
            mame_printf_debug("START channel mask %02x\n", m_TMP_register);
            UINT8 channelMask = 0x01;
            for (int i = 0; i < OKIM9810_VOICES; i++, channelMask <<= 1)
            {
                if (channelMask & m_TMP_register)
                {
                    m_voice[i].m_playing = true;
                    mame_printf_debug("\t\tPlaying channel %d: encoder type %d @ %dhz (volume = %d %d).  From %08x for %d samples (looping=%d).\n",
                                        i,
                                        m_voice[i].m_playbackAlgo,
                                        m_voice[i].m_samplingFreq,
                                        m_voice[i].volume_scale(m_global_volume, m_voice[i].m_channel_volume, m_voice[i].m_pan_volume_left),
                                        m_voice[i].volume_scale(m_global_volume, m_voice[i].m_channel_volume, m_voice[i].m_pan_volume_right),
                                        m_voice[i].m_base_offset,
                                        m_voice[i].m_count,
                                        m_voice[i].m_looping);
                }
            }
            break;
        }
        case 0x01:  // STOP
        {
            mame_printf_debug("STOP  channel mask %02x\n", m_TMP_register);
            UINT8 channelMask = 0x01;
            for (int i = 0; i < OKIM9810_VOICES; i++, channelMask <<= 1)
            {
                if (channelMask & m_TMP_register)
                {
                    m_voice[i].m_playing = false;
                    mame_printf_debug("\tChannel %d stopping.\n", i);
                }
            }
            break;
        }
        case 0x02:  // LOOP
        {
            mame_printf_debug("LOOP  channel mask %02x\n", m_TMP_register);
            UINT8 channelMask = 0x01;
            for (int i = 0; i < OKIM9810_VOICES; i++, channelMask <<= 1)
            {
                if (channelMask & m_TMP_register)
                {
                    m_voice[i].m_looping = true;
                    mame_printf_debug("\tChannel %d looping.\n", i);
                }
                else
                {
                    m_voice[i].m_looping = false;
                    mame_printf_debug("\tChannel %d done looping.\n", i);
                }
            }
            break;
        }
        case 0x03:  // OPT (options)
        {
            mame_printf_debug("OPT   complex data %02x\n", m_TMP_register);
            m_global_volume = (m_TMP_register & 0x18) >> 3;
            m_filter_type =   (m_TMP_register & 0x06) >> 1;
            m_output_level =  (m_TMP_register & 0x01);
            mame_printf_debug("\tOPT setting main volume scale to Vdd/%d\n", m_global_volume+1);
            mame_printf_debug("\tOPT setting output filter type to %d\n", m_filter_type);
            mame_printf_debug("\tOPT setting output amp level to %d\n", m_output_level);
            break;
        }
        case 0x04:  // MUON (silence)
        {
            mame_printf_warning("MUON  channel %d length %02x\n", channel, m_TMP_register);
            mame_printf_warning("MSM9810: UNIMPLEMENTED COMMAND!\n");
            break;
        }

        case 0x05:  // FADR (phrase address)
        {
            const offs_t base = m_TMP_register * 8;

            offs_t startAddr;
            UINT8 startFlags = m_direct->read_raw_byte(base + 0);
            startAddr  = m_direct->read_raw_byte(base + 1) << 16;
            startAddr |= m_direct->read_raw_byte(base + 2) << 8;
            startAddr |= m_direct->read_raw_byte(base + 3) << 0;

            offs_t endAddr;
            UINT8 endFlags = m_direct->read_raw_byte(base + 4);
            endAddr  = m_direct->read_raw_byte(base + 5) << 16;
            endAddr |= m_direct->read_raw_byte(base + 6) << 8;
            endAddr |= m_direct->read_raw_byte(base + 7) << 0;

            // Sub-table
            if (startFlags & 0x80)
            {
                offs_t subTable = startAddr;
                // TODO: New startFlags &= 0x80.  Are there further subtables?
                startFlags = m_direct->read_raw_byte(subTable + 0);
                startAddr  = m_direct->read_raw_byte(subTable + 1) << 16;
                startAddr |= m_direct->read_raw_byte(subTable + 2) << 8;
                startAddr |= m_direct->read_raw_byte(subTable + 3) << 0;

                // TODO: What does byte (subTable + 4) refer to?
                endAddr  = m_direct->read_raw_byte(subTable + 5) << 16;
                endAddr |= m_direct->read_raw_byte(subTable + 6) << 8;
                endAddr |= m_direct->read_raw_byte(subTable + 7) << 0;
            }

            m_voice[channel].m_sample = 0;
            m_voice[channel].m_interpSampleNum = 0;
            m_voice[channel].m_startFlags = startFlags;
            m_voice[channel].m_base_offset = startAddr;
            m_voice[channel].m_endFlags = endFlags;
            m_voice[channel].m_count = (endAddr-startAddr) + 1;     // Is there yet another extra byte at the end?

            m_voice[channel].m_playbackAlgo = (startFlags & 0x30) >> 4;
            m_voice[channel].m_samplingFreq = s_sampling_freq_table[startFlags & 0x0f];
            if (m_voice[channel].m_playbackAlgo == OKIM9810_ADPCM_PLAYBACK ||
                m_voice[channel].m_playbackAlgo == OKIM9810_ADPCM2_PLAYBACK)
                m_voice[channel].m_count *= 2;
            else
                mame_printf_warning("MSM9810: UNIMPLEMENTED PLAYBACK METHOD %d\n", m_voice[channel].m_playbackAlgo);

            mame_printf_debug("FADR  channel %d phrase offset %02x => ", channel, m_TMP_register);
            mame_printf_debug("startFlags(%02x) startAddr(%06x) endFlags(%02x) endAddr(%06x) bytes(%d)\n", startFlags, startAddr, endFlags, endAddr, endAddr-startAddr);
            break;
        }

        case 0x06:  // DADR (direct address playback)
        {
            mame_printf_warning("DADR  channel %d complex data %02x\n", channel, m_TMP_register);
            mame_printf_warning("MSM9810: UNIMPLEMENTED COMMAND!\n");
            break;
        }
        case 0x07:  // CVOL (channel volume)
        {
            mame_printf_debug("CVOL  channel %d data %02x\n", channel, m_TMP_register);
            mame_printf_debug("\tChannel %d -> volume index %d.\n", channel, m_TMP_register & 0x0f);

            m_voice[channel].m_channel_volume = m_TMP_register & 0x0f;
            break;
        }
        case 0x08:  // PAN
        {
            const UINT8 leftVolIndex = (m_TMP_register & 0xf0) >> 4;
            const UINT8 rightVolIndex = m_TMP_register & 0x0f;
            mame_printf_debug("PAN   channel %d left index: %02x right index: %02x (%02x)\n", channel, leftVolIndex, rightVolIndex, m_TMP_register);
            mame_printf_debug("\tChannel %d left -> %d right -> %d\n", channel, leftVolIndex, rightVolIndex);
            m_voice[channel].m_pan_volume_left = leftVolIndex;
            m_voice[channel].m_pan_volume_right = rightVolIndex;
            break;
        }
        default:
        {
            mame_printf_warning("MSM9810: UNKNOWN COMMAND!\n");
            break;
        }
    }
}

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


//-----------------------------------------------------------
//  writeTMP - memory interface for writing the TMP register
//-----------------------------------------------------------

// TMP is written when the CMD pin is high
void okim9810_device::write_TMP_register(UINT8 data)
{
    m_TMP_register = data;
}

WRITE8_MEMBER( okim9810_device::write_TMP_register )
{
	write_TMP_register(data);
}


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

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

okim9810_device::okim_voice::okim_voice()
	: m_playbackAlgo(OKIM9810_ADPCM2_PLAYBACK),
	  m_looping(false),
	  m_startFlags(0),
	  m_endFlags(0),
	  m_base_offset(0),
	  m_count(0),
	  m_samplingFreq(s_sampling_freq_table[2]),
	  m_playing(false),
	  m_sample(0),
      m_channel_volume(0x00),
	  m_pan_volume_left(0x00),
      m_pan_volume_right(0x00),
      m_startSample(0),
      m_endSample(0),
      m_interpSampleNum(0)
{
}

//-------------------------------------------------
//  generate_audio - generate audio samples and
//  add them to an output stream
//-------------------------------------------------

void okim9810_device::okim_voice::generate_audio(direct_read_data &direct,
        										 stream_sample_t **buffers,
        										 int samples,
        										 const UINT8 global_volume,
                                                 const UINT32 clock,
                                                 const UINT8 filter_type)
{
	// skip if not active
	if (!m_playing)
		return;

	// separate out left and right channels
	stream_sample_t *outL = buffers[0];
    stream_sample_t *outR = buffers[1];

    // get left and right volumes
	UINT8 volume_scale_left = volume_scale(global_volume, m_channel_volume, m_pan_volume_left);
	UINT8 volume_scale_right = volume_scale(global_volume, m_channel_volume, m_pan_volume_right);

    // total samples per byte
    UINT32 totalInterpSamples = clock / m_samplingFreq;

	// loop while we still have samples to generate
	while (samples-- != 0)
	{
        // If interpSampleNum == 0, we are at the beginning of a new interp chunk, gather data
        if (m_interpSampleNum == 0)
        {
            // If m_sample == 0, we have begun to play a new voice.  Get both the first nibble & the second.
            if (m_sample == 0)
            {
                // fetch the first sample nibble
        		int nibble0 = direct.read_raw_byte(m_base_offset + m_sample / 2) >> (((m_sample & 1) << 2) ^ 4);
                switch (m_playbackAlgo)
                {
                    case OKIM9810_ADPCM_PLAYBACK:
                    {
                        m_adpcm.reset();
                        m_startSample = (INT32)m_adpcm.clock(nibble0);
                        break;
                    }
                    case OKIM9810_ADPCM2_PLAYBACK:
                    {
                        m_adpcm2.reset();
                        m_startSample = (INT32)m_adpcm2.clock(nibble0);
                        break;
                    }
                    default:
                        break;
                }
            }
            else
            {
                // Otherwise just move the second nibble back to the first spot.
                m_startSample = m_endSample;
            }

            // And fetch the second sample nibble
            int nibble1 = direct.read_raw_byte(m_base_offset + (m_sample+1) / 2) >> ((((m_sample+1) & 1) << 2) ^ 4);
            switch (m_playbackAlgo)
            {
                case OKIM9810_ADPCM_PLAYBACK:
                {
                    m_endSample = (INT32)m_adpcm.clock(nibble1);
                    break;
                }
                case OKIM9810_ADPCM2_PLAYBACK:
                {
                    m_endSample = (INT32)m_adpcm2.clock(nibble1);
                    break;
                }
                default:
                    break;
            }
        }

        // TODO: Interpolate using proper numeric types.
        float progress = (float)m_interpSampleNum / (float)totalInterpSamples;
        INT32 interpValue = (INT32)((float)m_startSample + (((float)m_endSample-(float)m_startSample) * progress));

        // if filtering is unwanted
        if (filter_type != OKIM9810_SECONDARY_FILTER && filter_type != OKIM9810_PRIMARY_FILTER)
            interpValue = m_startSample;

		// output to the stereo buffers, scaling by the volume
		// signal in range -2048..2047, volume in range 2..128 => signal * volume / 8 in range -32768..32767
        INT32 interpValueL = (interpValue * (INT32)volume_scale_left) / 8;
        *outL++ += interpValueL;

        INT32 interpValueR = (interpValue * (INT32)volume_scale_right) / 8;
        *outR++ += interpValueR;

		// if the interpsample has reached its end, move on to the next sample
        m_interpSampleNum++;
        if (m_interpSampleNum >= totalInterpSamples)
        {
            m_interpSampleNum = 0;
            m_sample++;
        }

        // the end of the stream has been reached
		if (m_sample >= m_count)
		{
            if (!m_looping)
            {
    			m_playing = false;
                break;
            }
            else
            {
                m_sample = 0;
            }
		}
	}
}


//-------------------------------------------------
//  volume_scale - computes the volume equation as
//                 seen on page 29 of the docs.
//  Returns a value from the volume lookup table.
//-------------------------------------------------

UINT8 okim9810_device::okim_voice::volume_scale(const UINT8 global_volume_index,
				                                const UINT8 channel_volume_index,
                                                const UINT8 pan_volume_index) const
{
    const UINT8& V = channel_volume_index;
    const UINT8& L = pan_volume_index;
    const UINT8& O = global_volume_index;
    UINT32 index = (V+L) + (O*3);

    if (index > 15)
        index = 15;

    return s_volume_table[index];
}
