mame/src/emu/sound/i5000.c
2012-04-17 10:44:28 +00:00

269 lines
5.8 KiB
C

/***************************************************************************
i5000.c - Imagetek I5000 sound emulator
Imagetek I5000 is a multi-purpose chip, this covers the sound part.
No official documentation is known to exist. It seems to be a simple
16-channel ADPCM player.
TODO:
- improve volume balance (is it really linear?)
- verify that ADPCM is the same as standard OKI ADPCM
***************************************************************************/
#include "emu.h"
#include "i5000.h"
// device type definition
const device_type I5000_SND = &device_creator<i5000snd_device>;
i5000snd_device::i5000snd_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
: device_t(mconfig, I5000_SND, "I5000", tag, owner, clock),
device_sound_interface(mconfig, *this)
{
}
void i5000snd_device::device_start()
{
// create the stream
m_stream = machine().sound().stream_alloc(*this, 0, 2, clock() / 0x400, this);
m_rom_base = (UINT16 *)device().machine().region(":i5000snd")->base();
m_rom_mask = device().machine().region(":i5000snd")->bytes() / 2 - 1;
}
void i5000snd_device::device_reset()
{
// stop playing
write_reg16(0x43, 0xffff);
// reset channel regs
for (int i = 0; i < 0x40; i++)
write_reg16(i, 0);
}
bool i5000snd_device::read_sample(int ch)
{
m_channels[ch].shift_pos &= 0xf;
m_channels[ch].sample = m_rom_base[m_channels[ch].address];
m_channels[ch].address = (m_channels[ch].address + 1) & m_rom_mask;
// handle command
if (m_channels[ch].sample == 0x7f7f)
{
UINT16 cmd = m_rom_base[m_channels[ch].address];
m_channels[ch].address = (m_channels[ch].address + 1) & m_rom_mask;
// volume envelope? or loop sample?
if ((cmd & 0x00ff) == 0x0007)
{
// TODO
return false;
}
// cmd 0x0000 = end sample
// other values: unused
else return false;
}
return true;
}
void i5000snd_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
for (int i = 0; i < samples; i++)
{
INT32 mix_l = 0;
INT32 mix_r = 0;
// loop over all channels
for (int ch = 0; ch < 16; ch++)
{
if (!m_channels[ch].is_playing)
continue;
m_channels[ch].freq_timer -= m_channels[ch].freq_min;
if (m_channels[ch].freq_timer > 0)
{
mix_r += m_channels[ch].output_r;
mix_l += m_channels[ch].output_l;
continue;
}
m_channels[ch].freq_timer += m_channels[ch].freq_base;
int adpcm_data = m_channels[ch].sample >> m_channels[ch].shift_pos;
m_channels[ch].shift_pos += m_channels[ch].shift_amount;
if (m_channels[ch].shift_pos & 0x10)
{
if (!read_sample(ch))
{
m_channels[ch].is_playing = false;
continue;
}
adpcm_data |= (m_channels[ch].sample << (m_channels[ch].shift_amount - m_channels[ch].shift_pos));
}
adpcm_data = m_channels[ch].m_adpcm.clock(adpcm_data & m_channels[ch].shift_mask);
m_channels[ch].output_r = adpcm_data * m_channels[ch].vol_r / 16;
m_channels[ch].output_l = adpcm_data * m_channels[ch].vol_l / 16;
mix_r += m_channels[ch].output_r;
mix_l += m_channels[ch].output_l;
}
outputs[0][i] = mix_r / 16;
outputs[1][i] = mix_l / 16;
}
}
void i5000snd_device::write_reg16(UINT8 reg, UINT16 data)
{
// channel regs
if (reg < 0x40)
{
int ch = reg >> 2;
switch (reg & 3)
{
// 0, 1: address
// 2: frequency
case 2:
m_channels[ch].freq_base = (0x1ff - (data & 0xff)) << (~data >> 8 & 3);
break;
// 3: left/right volume
case 3:
m_channels[ch].vol_r = ~data & 0xff;
m_channels[ch].vol_l = ~data >> 8 & 0xff;
break;
default:
break;
}
}
// global regs
else
{
switch (reg)
{
// channel key on (0 has no effect)
case 0x42:
for (int ch = 0; ch < 16; ch++)
{
if (data & (1 << ch) && !m_channels[ch].is_playing)
{
UINT32 address = m_regs[ch << 2 | 1] << 16 | m_regs[ch << 2];
UINT16 start = m_rom_base[(address + 0) & m_rom_mask];
UINT16 param = m_rom_base[(address + 1) & m_rom_mask];
// check sample start ID
if (start != 0x7f7f)
{
logerror("i5000snd: channel %d wrong sample start ID %04X!\n", ch, start);
continue;
}
switch (param)
{
// 3-bit ADPCM
case 0x0104:
case 0x0304: // same?
m_channels[ch].freq_min = 0x140;
m_channels[ch].shift_amount = 3;
m_channels[ch].shift_mask = 0xe;
break;
default:
logerror("i5000snd: channel %d unknown sample param %04X!\n", ch, param);
// fall through (take settings from 0x0184)
// 4-bit ADPCM
case 0x0184:
m_channels[ch].freq_min = 0x100;
m_channels[ch].shift_amount = 4;
m_channels[ch].shift_mask = 0xf;
break;
}
m_channels[ch].address = (address + 4) & m_rom_mask;
m_channels[ch].freq_timer = 0;
m_channels[ch].shift_pos = 0;
m_channels[ch].m_adpcm.reset();
m_channels[ch].is_playing = read_sample(ch);
}
}
break;
// channel key off (0 has no effect)
case 0x43:
for (int ch = 0; ch < 16; ch++)
{
if (data & (1 << ch))
{
m_channels[ch].is_playing = false;
}
}
break;
default:
// not accessed often, assume that these are chip init registers
// 0x40: ?
// 0x41: ?
// 0x45: ?
// 0x46: ?
break;
}
}
m_regs[reg] = data;
}
READ16_MEMBER( i5000snd_device::read )
{
UINT16 ret = 0;
switch (offset)
{
// channel active state
case 0x42:
for (int ch = 0; ch < 16; ch++)
{
if (m_channels[ch].is_playing)
ret |= (1 << ch);
}
break;
default:
// 0x41: ?
break;
}
return ret;
}
WRITE16_MEMBER( i5000snd_device::write )
{
if (mem_mask != 0xffff)
{
logerror("i5000snd: wrong mask %04X!\n", mem_mask);
return;
}
m_stream->update();
write_reg16(offset, data);
}