diff --git a/src/devices/sound/okim9810.cpp b/src/devices/sound/okim9810.cpp index 369d3f6ac5b20..a425304293fd6 100644 --- a/src/devices/sound/okim9810.cpp +++ b/src/devices/sound/okim9810.cpp @@ -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<> (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; - } - - // 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: + // 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 { - m_endSample = (int32_t)m_adpcm.clock(nibble1); - break; + // Otherwise just move the second nibble back to the first spot. + m_startSample = m_endSample; } - case ADPCM2_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_adpcm2.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; } - default: - break; } } diff --git a/src/devices/sound/okim9810.h b/src/devices/sound/okim9810.h index f1a025a8577e3..a2bb7708961e0 100644 --- a/src/devices/sound/okim9810.h +++ b/src/devices/sound/okim9810.h @@ -59,13 +59,26 @@ class okim9810_device : public device_t, 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 @@ class okim9810_device : public device_t, 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 @@ class okim9810_device : public device_t, 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]; };