mirror of
https://github.com/holub/mame
synced 2025-04-23 00:39:36 +03:00
okim9810 : Sampling frequency is actually divider, Change name/Accurate this (#3222)
Add device_clock_changed, Implemented DADR, Serial interface, Add notes
This commit is contained in:
parent
93c238d5ec
commit
5d110e6a52
@ -6,6 +6,11 @@
|
||||
|
||||
OKI MSM9810 ADPCM(2) sound chip.
|
||||
|
||||
TODO:
|
||||
Serial input/output are not verified
|
||||
8-bit Non-linear PCM Algorithm aren't implemented
|
||||
DADR Command is correct?
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
#include "emu.h"
|
||||
@ -41,25 +46,25 @@ const uint8_t okim9810_device::okim_voice::s_volume_table[16] =
|
||||
0x04, // -30.0 dB
|
||||
};
|
||||
|
||||
// sampling frequency lookup table.
|
||||
const uint32_t okim9810_device::s_sampling_freq_table[16] =
|
||||
// sampling frequency divider lookup table.
|
||||
const uint32_t okim9810_device::s_sampling_freq_div_table[16] =
|
||||
{
|
||||
4000,
|
||||
8000,
|
||||
16000,
|
||||
32000,
|
||||
0,
|
||||
6400,
|
||||
12800,
|
||||
25600,
|
||||
0,
|
||||
5300,
|
||||
10600,
|
||||
21200,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
1024, // 4.0KHz
|
||||
512, // 8.0KHz
|
||||
256, // 16.0KHz
|
||||
128, // 32.0KHz
|
||||
1,
|
||||
640, // 6.4KHz
|
||||
320, // 12.8KHz
|
||||
160, // 25.6KHz
|
||||
1,
|
||||
768, // 5.3KHz
|
||||
384, // 10.6KHz
|
||||
192, // 21.3KHz
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1
|
||||
};
|
||||
|
||||
|
||||
@ -80,7 +85,19 @@ okim9810_device::okim9810_device(const machine_config &mconfig, const char *tag,
|
||||
m_TMP_register(0x00),
|
||||
m_global_volume(0x00),
|
||||
m_filter_type(SECONDARY_FILTER),
|
||||
m_output_level(OUTPUT_TO_DIRECT_DAC)
|
||||
m_output_level(OUTPUT_TO_DIRECT_DAC),
|
||||
m_dadr(0),
|
||||
m_dadr_start_offset(0),
|
||||
m_dadr_end_offset(0),
|
||||
m_dadr_flags(0),
|
||||
m_serial(0),
|
||||
m_serial_read_latch(0),
|
||||
m_serial_write_latch(0),
|
||||
m_serial_bits(0),
|
||||
m_ud(0),
|
||||
m_si(0),
|
||||
m_sd(0)
|
||||
|
||||
{
|
||||
}
|
||||
|
||||
@ -92,7 +109,6 @@ okim9810_device::okim9810_device(const machine_config &mconfig, const char *tag,
|
||||
void okim9810_device::device_start()
|
||||
{
|
||||
// create the stream
|
||||
//int divisor = m_pin7 ? 132 : 165;
|
||||
m_stream = machine().sound().stream_alloc(*this, 0, 2, clock());
|
||||
|
||||
// save state stuff
|
||||
@ -100,6 +116,19 @@ void okim9810_device::device_start()
|
||||
save_item(NAME(m_global_volume));
|
||||
save_item(NAME(m_filter_type));
|
||||
save_item(NAME(m_output_level));
|
||||
|
||||
save_item(NAME(m_dadr));
|
||||
save_item(NAME(m_dadr_start_offset));
|
||||
save_item(NAME(m_dadr_end_offset));
|
||||
save_item(NAME(m_dadr_flags));
|
||||
|
||||
save_item(NAME(m_serial));
|
||||
save_item(NAME(m_serial_read_latch));
|
||||
save_item(NAME(m_serial_write_latch));
|
||||
save_item(NAME(m_serial_bits));
|
||||
save_item(NAME(m_ud));
|
||||
save_item(NAME(m_si));
|
||||
save_item(NAME(m_sd));
|
||||
|
||||
for (int i = 0; i < OKIM9810_VOICES; i++)
|
||||
{
|
||||
@ -137,6 +166,8 @@ void okim9810_device::device_reset()
|
||||
m_stream->update();
|
||||
for (auto & elem : m_voice)
|
||||
elem.m_playing = false;
|
||||
|
||||
m_serial_bits = 0;
|
||||
}
|
||||
|
||||
|
||||
@ -146,6 +177,7 @@ void okim9810_device::device_reset()
|
||||
|
||||
void okim9810_device::device_post_load()
|
||||
{
|
||||
device_clock_changed();
|
||||
}
|
||||
|
||||
|
||||
@ -156,6 +188,7 @@ void okim9810_device::device_post_load()
|
||||
|
||||
void okim9810_device::device_clock_changed()
|
||||
{
|
||||
m_stream->set_sample_rate(clock());
|
||||
}
|
||||
|
||||
|
||||
@ -183,7 +216,7 @@ void okim9810_device::sound_stream_update(sound_stream &stream, stream_sample_t
|
||||
|
||||
// iterate over voices and accumulate sample data
|
||||
for (auto & elem : m_voice)
|
||||
elem.generate_audio(*this, outputs, samples, m_global_volume, clock(), m_filter_type);
|
||||
elem.generate_audio(*this, outputs, samples, m_global_volume, m_filter_type);
|
||||
}
|
||||
|
||||
|
||||
@ -210,6 +243,7 @@ uint8_t okim9810_device::read_status()
|
||||
|
||||
READ8_MEMBER( okim9810_device::read )
|
||||
{
|
||||
assert(!m_serial);
|
||||
return read_status();
|
||||
}
|
||||
|
||||
@ -338,12 +372,12 @@ void okim9810_device::write_command(uint8_t data)
|
||||
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];
|
||||
m_voice[channel].m_playbackAlgo = (startFlags & 0x30) >> 4; // Not verified
|
||||
m_voice[channel].m_samplingFreq = startFlags & 0x0f;
|
||||
if (m_voice[channel].m_playbackAlgo == ADPCM_PLAYBACK ||
|
||||
m_voice[channel].m_playbackAlgo == ADPCM2_PLAYBACK)
|
||||
m_voice[channel].m_count *= 2;
|
||||
else
|
||||
else if (m_voice[channel].m_playbackAlgo == NONLINEAR8_PLAYBACK)
|
||||
osd_printf_warning("MSM9810: UNIMPLEMENTED PLAYBACK METHOD %d\n", m_voice[channel].m_playbackAlgo);
|
||||
|
||||
osd_printf_debug("FADR channel %d phrase offset %02x => ", channel, m_TMP_register);
|
||||
@ -353,8 +387,33 @@ void okim9810_device::write_command(uint8_t data)
|
||||
|
||||
case 0x06: // DADR (direct address playback)
|
||||
{
|
||||
osd_printf_warning("DADR channel %d complex data %02x\n", channel, m_TMP_register);
|
||||
osd_printf_warning("MSM9810: UNIMPLEMENTED COMMAND!\n");
|
||||
if ((channel & 4) == 0) // DADR is available only channel 1~4
|
||||
{
|
||||
offs_t startAddr = m_dadr_start_offset;
|
||||
offs_t endAddr = m_dadr_end_offset;
|
||||
uint8_t startFlags = m_dadr_flags;
|
||||
|
||||
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 = 0;
|
||||
m_voice[channel].m_count = (endAddr-startAddr) + 1; // Is there yet another extra byte at the end?
|
||||
|
||||
m_voice[channel].m_playbackAlgo = (startFlags & 0x0c) >> 2;
|
||||
m_voice[channel].m_samplingFreq = (startFlags & 0xf0) >> 4;
|
||||
if (m_voice[channel].m_playbackAlgo == ADPCM_PLAYBACK ||
|
||||
m_voice[channel].m_playbackAlgo == ADPCM2_PLAYBACK)
|
||||
m_voice[channel].m_count *= 2;
|
||||
else if (m_voice[channel].m_playbackAlgo == NONLINEAR8_PLAYBACK)
|
||||
osd_printf_warning("MSM9810: UNIMPLEMENTED PLAYBACK METHOD %d\n", m_voice[channel].m_playbackAlgo);
|
||||
|
||||
osd_printf_debug("startFlags(%02x) startAddr(%06x) endAddr(%06x) bytes(%d)\n", startFlags, startAddr, endAddr, endAddr-startAddr);
|
||||
}
|
||||
else
|
||||
{
|
||||
osd_printf_warning("MSM9810: UNKNOWN COMMAND!\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x07: // CVOL (channel volume)
|
||||
@ -381,10 +440,12 @@ void okim9810_device::write_command(uint8_t data)
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_dadr = 0;
|
||||
}
|
||||
|
||||
WRITE8_MEMBER( okim9810_device::write )
|
||||
{
|
||||
assert(!m_serial);
|
||||
write_command(data);
|
||||
}
|
||||
|
||||
@ -397,14 +458,122 @@ WRITE8_MEMBER( okim9810_device::write )
|
||||
void okim9810_device::write_tmp_register(uint8_t data)
|
||||
{
|
||||
m_TMP_register = data;
|
||||
|
||||
if (m_dadr < 7)
|
||||
{
|
||||
switch (m_dadr)
|
||||
{
|
||||
case 0:
|
||||
m_dadr_start_offset = (m_TMP_register << 16);
|
||||
break;
|
||||
case 1:
|
||||
m_dadr_start_offset |= (m_TMP_register << 8);
|
||||
break;
|
||||
case 2:
|
||||
m_dadr_start_offset |= (m_TMP_register << 0);
|
||||
break;
|
||||
case 3:
|
||||
m_dadr_end_offset = (m_TMP_register << 16);
|
||||
break;
|
||||
case 4:
|
||||
m_dadr_end_offset |= (m_TMP_register << 8);
|
||||
break;
|
||||
case 5:
|
||||
m_dadr_end_offset |= (m_TMP_register << 0);
|
||||
break;
|
||||
case 6:
|
||||
m_dadr_flags = m_TMP_register;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
osd_printf_debug("DADR direct offset %02 = %02x => ", m_dadr, m_TMP_register);
|
||||
m_dadr++;
|
||||
}
|
||||
}
|
||||
|
||||
WRITE8_MEMBER( okim9810_device::write_tmp_register )
|
||||
{
|
||||
assert(!m_serial);
|
||||
write_tmp_register(data);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// Serial interface, NOT verified
|
||||
//-----------------------------------------------------------
|
||||
|
||||
WRITE_LINE_MEMBER( okim9810_device::serial_w )
|
||||
{
|
||||
m_serial = state;
|
||||
}
|
||||
|
||||
WRITE_LINE_MEMBER( okim9810_device::si_w )
|
||||
{
|
||||
if (m_si != state)
|
||||
{
|
||||
m_si = state;
|
||||
if (m_si)
|
||||
{
|
||||
m_serial_write_latch = (m_serial_write_latch << 1) | (m_sd);
|
||||
m_serial_bits++;
|
||||
if (m_serial_bits >= 8)
|
||||
{
|
||||
if (m_cmd = 0)
|
||||
{
|
||||
write_command(m_serial_write_latch);
|
||||
}
|
||||
else
|
||||
{
|
||||
write_tmp_register(m_serial_write_latch);
|
||||
}
|
||||
m_serial_bits = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WRITE_LINE_MEMBER( okim9810_device::sd_w )
|
||||
{
|
||||
m_sd = state;
|
||||
}
|
||||
|
||||
WRITE_LINE_MEMBER( okim9810_device::ud_w )
|
||||
{
|
||||
m_ud = state;
|
||||
}
|
||||
|
||||
WRITE_LINE_MEMBER( okim9810_device::cmd_w )
|
||||
{
|
||||
m_cmd = state;
|
||||
}
|
||||
|
||||
READ_LINE_MEMBER( okim9810_device::so_r )
|
||||
{
|
||||
m_serial_read_latch = (m_serial_read_latch & ~(1<<m_serial_bits)) | (read_status() & (1<<m_serial_bits));
|
||||
return (read_status() >> (7-m_serial_bits)) & 1;
|
||||
}
|
||||
|
||||
READ_LINE_MEMBER( okim9810_device::sr0_r )
|
||||
{
|
||||
return (m_serial_read_latch >> ((m_ud) ? 4 : 0)) & 1;
|
||||
}
|
||||
|
||||
READ_LINE_MEMBER( okim9810_device::sr1_r )
|
||||
{
|
||||
return (m_serial_read_latch >> ((m_ud) ? 5 : 1)) & 1;
|
||||
}
|
||||
|
||||
READ_LINE_MEMBER( okim9810_device::sr2_r )
|
||||
{
|
||||
return (m_serial_read_latch >> ((m_ud) ? 6 : 2)) & 1;
|
||||
}
|
||||
|
||||
READ_LINE_MEMBER( okim9810_device::sr3_r )
|
||||
{
|
||||
return (m_serial_read_latch >> ((m_ud) ? 7 : 3)) & 1;
|
||||
}
|
||||
|
||||
//**************************************************************************
|
||||
// OKIM VOICE
|
||||
//**************************************************************************
|
||||
@ -420,7 +589,7 @@ okim9810_device::okim_voice::okim_voice()
|
||||
m_endFlags(0),
|
||||
m_base_offset(0),
|
||||
m_count(0),
|
||||
m_samplingFreq(s_sampling_freq_table[2]),
|
||||
m_samplingFreq(2),
|
||||
m_playing(false),
|
||||
m_sample(0),
|
||||
m_channel_volume(0x00),
|
||||
@ -441,7 +610,6 @@ void okim9810_device::okim_voice::generate_audio(device_rom_interface &rom,
|
||||
stream_sample_t **buffers,
|
||||
int samples,
|
||||
const uint8_t global_volume,
|
||||
const uint32_t clock,
|
||||
const uint8_t filter_type)
|
||||
{
|
||||
// skip if not active
|
||||
@ -457,7 +625,9 @@ void okim9810_device::okim_voice::generate_audio(device_rom_interface &rom,
|
||||
uint8_t volume_scale_right = volume_scale(global_volume, m_channel_volume, m_pan_volume_right);
|
||||
|
||||
// total samples per byte
|
||||
uint32_t totalInterpSamples = clock / m_samplingFreq;
|
||||
uint32_t totalInterpSamples = s_sampling_freq_div_table[m_samplingFreq];
|
||||
if (totalInterpSamples == 1)
|
||||
return;
|
||||
|
||||
// loop while we still have samples to generate
|
||||
while (samples-- != 0)
|
||||
@ -465,23 +635,45 @@ void okim9810_device::okim_voice::generate_audio(device_rom_interface &rom,
|
||||
// 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)
|
||||
if (m_playbackAlgo & 8BIT_PLAYBACK) // 8-bit case
|
||||
{
|
||||
// fetch the first sample nibble
|
||||
int nibble0 = rom.read_byte(m_base_offset + m_sample / 2) >> (((m_sample & 1) << 2) ^ 4);
|
||||
// If m_sample == 0, we have begun to play a new voice. Get both the first byte & the second.
|
||||
if (m_sample == 0)
|
||||
{
|
||||
// fetch the first sample byte
|
||||
switch (m_playbackAlgo)
|
||||
{
|
||||
case STRAIGHT8_PLAYBACK:
|
||||
{
|
||||
m_startSample = ((int8_t)rom.read_byte(m_base_offset + m_sample)) << 4; // shift to 12bit
|
||||
break;
|
||||
}
|
||||
case NONLINEAR8_PLAYBACK: // TODO : Algorithm Unimplemented
|
||||
{
|
||||
m_startSample = ((int8_t)rom.read_byte(m_base_offset + m_sample)) << 4; // shift to 12bit
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise just move the second byte back to the first spot.
|
||||
m_startSample = m_endSample;
|
||||
}
|
||||
|
||||
// And fetch the second sample byte
|
||||
switch (m_playbackAlgo)
|
||||
{
|
||||
case ADPCM_PLAYBACK:
|
||||
case STRAIGHT8_PLAYBACK:
|
||||
{
|
||||
m_adpcm.reset();
|
||||
m_startSample = (int32_t)m_adpcm.clock(nibble0);
|
||||
m_endSample = ((int8_t)rom.read_byte(m_base_offset + m_sample + 1)) << 4; // shift to 12bit
|
||||
break;
|
||||
}
|
||||
case ADPCM2_PLAYBACK:
|
||||
case NONLINEAR8_PLAYBACK: // TODO : Algorithm Unimplemented
|
||||
{
|
||||
m_adpcm2.reset();
|
||||
m_startSample = (int32_t)m_adpcm2.clock(nibble0);
|
||||
m_endSample = ((int8_t)rom.read_byte(m_base_offset + m_sample + 1)) << 4; // shift to 12bit
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -490,26 +682,52 @@ void okim9810_device::okim_voice::generate_audio(device_rom_interface &rom,
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise just move the second nibble back to the first spot.
|
||||
m_startSample = m_endSample;
|
||||
}
|
||||
// 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 = rom.read_byte(m_base_offset + m_sample / 2) >> (((m_sample & 1) << 2) ^ 4);
|
||||
switch (m_playbackAlgo)
|
||||
{
|
||||
case ADPCM_PLAYBACK:
|
||||
{
|
||||
m_adpcm.reset();
|
||||
m_startSample = (int32_t)m_adpcm.clock(nibble0);
|
||||
break;
|
||||
}
|
||||
case ADPCM2_PLAYBACK:
|
||||
{
|
||||
m_adpcm2.reset();
|
||||
m_startSample = (int32_t)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 = rom.read_byte(m_base_offset + (m_sample+1) / 2) >> ((((m_sample+1) & 1) << 2) ^ 4);
|
||||
switch (m_playbackAlgo)
|
||||
{
|
||||
case ADPCM_PLAYBACK:
|
||||
// And fetch the second sample nibble
|
||||
int nibble1 = rom.read_byte(m_base_offset + (m_sample+1) / 2) >> ((((m_sample+1) & 1) << 2) ^ 4);
|
||||
switch (m_playbackAlgo)
|
||||
{
|
||||
m_endSample = (int32_t)m_adpcm.clock(nibble1);
|
||||
break;
|
||||
case ADPCM_PLAYBACK:
|
||||
{
|
||||
m_endSample = (int32_t)m_adpcm.clock(nibble1);
|
||||
break;
|
||||
}
|
||||
case ADPCM2_PLAYBACK:
|
||||
{
|
||||
m_endSample = (int32_t)m_adpcm2.clock(nibble1);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
case ADPCM2_PLAYBACK:
|
||||
{
|
||||
m_endSample = (int32_t)m_adpcm2.clock(nibble1);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,13 +59,26 @@ public:
|
||||
DECLARE_WRITE8_MEMBER( write );
|
||||
DECLARE_WRITE8_MEMBER( write_tmp_register );
|
||||
|
||||
// serial read/write handlers
|
||||
DECLARE_WRITE_LINE_MEMBER( serial_w );
|
||||
DECLARE_WRITE_LINE_MEMBER( si_w );
|
||||
DECLARE_WRITE_LINE_MEMBER( sd_w );
|
||||
DECLARE_WRITE_LINE_MEMBER( ud_w );
|
||||
DECLARE_WRITE_LINE_MEMBER( cmd_w );
|
||||
DECLARE_READ_LINE_MEMBER( so_r );
|
||||
DECLARE_READ_LINE_MEMBER( sr0_r );
|
||||
DECLARE_READ_LINE_MEMBER( sr1_r );
|
||||
DECLARE_READ_LINE_MEMBER( sr2_r );
|
||||
DECLARE_READ_LINE_MEMBER( sr3_r );
|
||||
|
||||
protected:
|
||||
enum
|
||||
{
|
||||
ADPCM_PLAYBACK = 0,
|
||||
ADPCM2_PLAYBACK = 1,
|
||||
STRAIGHT8_PLAYBACK = 2,
|
||||
NONLINEAR8_PLAYBACK = 3
|
||||
NONLINEAR8_PLAYBACK = 2,
|
||||
STRAIGHT8_PLAYBACK = 3,
|
||||
8BIT_PLAYBACK = 2
|
||||
};
|
||||
|
||||
enum
|
||||
@ -103,7 +116,6 @@ protected:
|
||||
stream_sample_t **buffers,
|
||||
int samples,
|
||||
const uint8_t global_volume,
|
||||
const uint32_t clock,
|
||||
const uint8_t filter_type);
|
||||
|
||||
// computes volume scale from 3 volume numbers
|
||||
@ -144,11 +156,24 @@ protected:
|
||||
uint8_t m_global_volume; // volume index set with the OPT command
|
||||
uint8_t m_filter_type; // interpolation filter type set with the OPT command
|
||||
uint8_t m_output_level; // flag stating if a voltage follower is connected
|
||||
|
||||
int m_dadr;
|
||||
offs_t m_dadr_start_offset;
|
||||
offs_t m_dadr_end_offset;
|
||||
uint8_t m_dadr_flags;
|
||||
|
||||
int m_serial;
|
||||
int m_serial_read_latch;
|
||||
int m_serial_write_latch;
|
||||
int m_serial_bits;
|
||||
int m_ud;
|
||||
int m_si;
|
||||
int m_sd;
|
||||
|
||||
static constexpr int OKIM9810_VOICES = 8;
|
||||
okim_voice m_voice[OKIM9810_VOICES];
|
||||
|
||||
static const uint32_t s_sampling_freq_table[16];
|
||||
static const uint32_t s_sampling_freq_div_table[16];
|
||||
};
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user