// license:BSD-3-Clause
// copyright-holders:Nicola Salmoria,Hiromitsu Shioya
/*********************************************************/
/* Konami PCM controller */
/*********************************************************/
/*
Changelog, Hiromitsu Shioya 02/05/2002
fixed start address decode timing. (sample loop bug.)
Changelog, Mish, August 1999:
Removed interface support for different memory regions per channel.
Removed interface support for differing channel volume.
Added bankswitching.
Added support for multiple chips.
(NB: Should different memory regions per channel be needed, the bankswitching function can set this up).
Chanelog, Nicola, August 1999:
Added Support for the k007232_VOL() macro.
Added external port callback, and functions to set the volume of the channels
*/
#include "emu.h"
#include "k007232.h"
#include "wavwrite.h"
#define K007232_LOG_PCM (0)
#define BASE_SHIFT (12)
DEFINE_DEVICE_TYPE(K007232, k007232_device, "k007232", "K007232 PCM Controller")
k007232_device::k007232_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, K007232, tag, owner, clock)
, device_sound_interface(mconfig, *this)
, m_rom(*this, DEVICE_SELF)
, m_port_write_handler(*this)
{
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void k007232_device::device_start()
{
/* Set up the chips */
m_pcmlimit = m_rom.bytes();
m_port_write_handler.resolve_safe();
for (int i = 0; i < KDAC_A_PCM_MAX; i++)
{
m_addr[i] = 0;
m_start[i] = 0;
m_step[i] = 0;
m_play[i] = 0;
m_bank[i] = 0;
}
m_vol[0][0] = 255; /* channel A output to output A */
m_vol[0][1] = 0;
m_vol[1][0] = 0;
m_vol[1][1] = 255; /* channel B output to output B */
for (auto & elem : m_wreg)
elem = 0;
m_stream = machine().sound().stream_alloc(*this, 0 , 2, clock()/128);
make_fncodes();
save_item(NAME(m_vol));
save_item(NAME(m_addr));
save_item(NAME(m_start));
save_item(NAME(m_step));
save_item(NAME(m_bank));
save_item(NAME(m_play));
save_item(NAME(m_wreg));
}
void k007232_device::make_fncodes()
{
for (int i = 0; i < 0x200; i++)
{
m_fncode[i] = (32 << BASE_SHIFT) / (0x200 - i);
}
}
uint32_t k007232_device::get_start_address(int channel)
{
const uint32_t reg_index = (channel ? 6 : 0);
return (BIT(m_wreg[reg_index + 4], 0) << 16) | (m_wreg[reg_index + 3] << 8) | m_wreg[reg_index + 2] | m_bank[channel];
}
/************************************************/
/* Konami PCM write register */
/************************************************/
WRITE8_MEMBER(k007232_device::write)
{
m_stream->update();
m_wreg[offset] = data; // standard data write
if (offset == 12)
{
// external port, usually volume control
m_port_write_handler(0, data);
}
else if (offset == 13)
{
// loop flag, handled by standard data write
}
else
{
int channel = (offset >= 6 ? 1 : 0);
int reg_index = (offset >= 6 ? 6 : 0);
if (offset >= 6)
offset -= 6;
switch (offset)
{
case 0: // address step, LSB
case 1: // address step, MSB
m_step[channel] = m_fncode[(BIT(m_wreg[reg_index + 1], 0) << 8) | m_wreg[reg_index]];
if (machine().input().code_pressed(KEYCODE_K)) printf("Step: %02x, %02x = %04x (%08x)\n", m_wreg[reg_index + 1], m_wreg[reg_index], (BIT(m_wreg[reg_index + 1], 0) << 8) | m_wreg[reg_index], m_step[channel]);
break;
case 2:
case 3:
case 4:
// working data for start address
break;
case 5: // start address
m_start[channel] = get_start_address(channel);
if (machine().input().code_pressed(KEYCODE_K)) printf("Start: %08x\n", m_start[channel]);
if (m_start[channel] < m_pcmlimit)
{
m_play[channel] = 1;
m_addr[channel] = 0;
}
break;
}
}
}
/************************************************/
/* Konami PCM read register */
/************************************************/
READ8_MEMBER(k007232_device::read)
{
if (offset == 5 || offset == 11)
{
int channel = (offset == 11) ? 1 : 0;
m_start[channel] = get_start_address(channel);
if (m_start[channel] < m_pcmlimit)
{
m_play[channel] = 1;
m_addr[channel] = 0;
}
}
return 0;
}
/*****************************************************************************/
void k007232_device::set_volume(int channel, int vol_a, int vol_b)
{
m_vol[channel][0] = vol_a;
m_vol[channel][1] = vol_b;
}
void k007232_device::set_bank(int chan_a_bank, int chan_b_bank)
{
m_bank[0] = chan_a_bank << 17;
m_bank[1] = chan_b_bank << 17;
}
/*****************************************************************************/
//-------------------------------------------------
// sound_stream_update - handle a stream update
//-------------------------------------------------
void k007232_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
memset(outputs[0], 0, samples * sizeof(stream_sample_t));
memset(outputs[1], 0, samples * sizeof(stream_sample_t));
if (K007232_LOG_PCM)
{
for (int i = 0; i < KDAC_A_PCM_MAX; i++)
{
if (m_play[i])
{
char filebuf[256];
snprintf(filebuf, 256, "pcm%08x.wav", m_start[i]);
wav_file *file = wav_open(filebuf, stream.sample_rate(), 1);
if (file != nullptr)
{
uint32_t addr = m_start[i];
while (!BIT(m_rom[addr], 7) && addr < m_pcmlimit)
{
int16_t out = ((m_rom[addr] & 0x7f) - 0x40) << 7;
wav_add_data_16(file, &out, 1);
addr++;
}
wav_close(file);
}
}
}
}
for (int i = 0; i < KDAC_A_PCM_MAX; i++)
{
if (m_play[i])
{
/**** PCM setup ****/
uint32_t addr_lsb = (m_addr[i] >> BASE_SHIFT) & 0x000fffff;
uint32_t addr = m_start[i] + addr_lsb;
int vol_a = m_vol[i][0] * 2;
int vol_b = m_vol[i][1] * 2;
for (int j = 0; j < samples; j++)
{
uint32_t old_addr = addr;
addr_lsb = (m_addr[i] >> BASE_SHIFT) & 0x000fffff;
addr = m_start[i] + addr_lsb;
while (old_addr <= addr)
{
if (BIT(m_rom[old_addr], 7) || old_addr >= m_pcmlimit)
{
// end of sample
if (BIT(m_wreg[13], i))
{
/* loop to the beginning */
m_start[i] = get_start_address(i);
addr = m_start[i];
m_addr[i] = 0;
old_addr = addr; /* skip loop */
}
else
{
/* stop sample */
m_play[i] = 0;
}
break;
}
old_addr++;
}
if (m_play[i] == 0)
break;
m_addr[i] += m_step[i];
int out = (m_rom[addr] & 0x7f) - 0x40;
outputs[0][j] += out * vol_a;
outputs[1][j] += out * vol_b;
}
}
}
}