diff options
Diffstat (limited to 'src/devices/sound/okim9810.cpp')
-rw-r--r-- | src/devices/sound/okim9810.cpp | 270 |
1 files changed, 166 insertions, 104 deletions
diff --git a/src/devices/sound/okim9810.cpp b/src/devices/sound/okim9810.cpp index 2641e1527e2..f9d0d6e26c6 100644 --- a/src/devices/sound/okim9810.cpp +++ b/src/devices/sound/okim9810.cpp @@ -4,7 +4,7 @@ okim9810.h - OKI MSM9810 ADPCM(2) sound chip. + OKI MSM9810 / MSM9811 ADPCM(2) sound chips. TODO: Serial input/output are not verified @@ -16,6 +16,9 @@ #include "emu.h" #include "okim9810.h" +#define VERBOSE 0 +#include "logmacro.h" + //************************************************************************** // GLOBAL VARIABLES @@ -80,7 +83,7 @@ const uint32_t okim9810_device::s_sampling_freq_div_table[16] = okim9810_device::okim9810_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, OKIM9810, tag, owner, clock), device_sound_interface(mconfig, *this), - device_rom_interface(mconfig, *this, 24), + device_rom_interface(mconfig, *this), m_stream(nullptr), m_TMP_register(0x00), m_global_volume(0x00), @@ -109,7 +112,7 @@ okim9810_device::okim9810_device(const machine_config &mconfig, const char *tag, void okim9810_device::device_start() { // create the stream - m_stream = machine().sound().stream_alloc(*this, 0, 2, clock()); + m_stream = stream_alloc(0, 2, clock()); // save state stuff save_item(NAME(m_TMP_register)); @@ -135,26 +138,32 @@ void okim9810_device::device_start() { okim_voice *voice = &m_voice[i]; + // can't use STRUCT_MEMBER on nested structs save_item(NAME(voice->m_adpcm.m_signal), i); save_item(NAME(voice->m_adpcm.m_step), i); save_item(NAME(voice->m_adpcm2.m_signal), i); save_item(NAME(voice->m_adpcm2.m_step), i); - save_item(NAME(voice->m_playbackAlgo), i); - save_item(NAME(voice->m_looping), i); - save_item(NAME(voice->m_startFlags), i); - save_item(NAME(voice->m_endFlags), i); - save_item(NAME(voice->m_base_offset), i); - save_item(NAME(voice->m_count), i); - save_item(NAME(voice->m_samplingFreq), i); - save_item(NAME(voice->m_playing), i); - save_item(NAME(voice->m_sample), i); - save_item(NAME(voice->m_channel_volume), i); - save_item(NAME(voice->m_pan_volume_left), i); - save_item(NAME(voice->m_pan_volume_right), i); - save_item(NAME(voice->m_startSample), i); - save_item(NAME(voice->m_endSample), i); - save_item(NAME(voice->m_interpSampleNum), i); } + + save_item(STRUCT_MEMBER(m_voice, m_playbackAlgo)); + save_item(STRUCT_MEMBER(m_voice, m_looping)); + save_item(STRUCT_MEMBER(m_voice, m_startFlags)); + save_item(STRUCT_MEMBER(m_voice, m_endFlags)); + save_item(STRUCT_MEMBER(m_voice, m_base_offset)); + save_item(STRUCT_MEMBER(m_voice, m_count)); + save_item(STRUCT_MEMBER(m_voice, m_samplingFreq)); + save_item(STRUCT_MEMBER(m_voice, m_playing)); + save_item(STRUCT_MEMBER(m_voice, m_sample)); + save_item(STRUCT_MEMBER(m_voice, m_phrase_offset)); + save_item(STRUCT_MEMBER(m_voice, m_phrase_count)); + save_item(STRUCT_MEMBER(m_voice, m_phrase_wait_cnt)); + save_item(STRUCT_MEMBER(m_voice, m_phrase_state)); + save_item(STRUCT_MEMBER(m_voice, m_channel_volume)); + save_item(STRUCT_MEMBER(m_voice, m_pan_volume_left)); + save_item(STRUCT_MEMBER(m_voice, m_pan_volume_right)); + save_item(STRUCT_MEMBER(m_voice, m_startSample)); + save_item(STRUCT_MEMBER(m_voice, m_endSample)); + save_item(STRUCT_MEMBER(m_voice, m_interpSampleNum)); } @@ -195,10 +204,11 @@ void okim9810_device::device_clock_changed() //------------------------------------------------- -// rom_bank_updated - the rom bank has changed +// rom_bank_pre_change - refresh the stream if the +// ROM banking changes //------------------------------------------------- -void okim9810_device::rom_bank_updated() +void okim9810_device::rom_bank_pre_change() { m_stream->update(); } @@ -209,15 +219,11 @@ void okim9810_device::rom_bank_updated() // our sound stream //------------------------------------------------- -void okim9810_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) +void okim9810_device::sound_stream_update(sound_stream &stream) { - // 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 (auto & elem : m_voice) - elem.generate_audio(*this, outputs, samples, m_global_volume, m_filter_type); + elem.generate_audio(*this, stream, m_global_volume, m_filter_type); } @@ -242,7 +248,7 @@ uint8_t okim9810_device::read_status() // read - memory interface for reading the active status //------------------------------------------------- -READ8_MEMBER( okim9810_device::read ) +uint8_t okim9810_device::read() { assert(!m_serial); return read_status(); @@ -263,14 +269,14 @@ void okim9810_device::write_command(uint8_t data) { case 0x00: // START { - osd_printf_debug("START channel mask %02x\n", m_TMP_register); + LOG("START channel mask %02x\n", m_TMP_register); uint8_t channelMask = 0x01; for (int i = 0; i < OKIM9810_VOICES; i++, channelMask <<= 1) { if (channelMask & m_TMP_register) { m_voice[i].m_playing = true; - osd_printf_debug("\t\tPlaying channel %d: encoder type %d @ %dhz (volume = %d %d). From %08x for %d samples (looping=%d).\n", + LOG("\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, @@ -285,52 +291,52 @@ void okim9810_device::write_command(uint8_t data) } case 0x01: // STOP { - osd_printf_debug("STOP channel mask %02x\n", m_TMP_register); + LOG("STOP channel mask %02x\n", m_TMP_register); uint8_t channelMask = 0x01; for (int i = 0; i < OKIM9810_VOICES; i++, channelMask <<= 1) { if (channelMask & m_TMP_register) { m_voice[i].m_playing = false; - osd_printf_debug("\tChannel %d stopping.\n", i); + LOG("\tChannel %d stopping.\n", i); } } break; } case 0x02: // LOOP { - osd_printf_debug("LOOP channel mask %02x\n", m_TMP_register); + LOG("LOOP channel mask %02x\n", m_TMP_register); uint8_t channelMask = 0x01; for (int i = 0; i < OKIM9810_VOICES; i++, channelMask <<= 1) { if (channelMask & m_TMP_register) { m_voice[i].m_looping = true; - osd_printf_debug("\tChannel %d looping.\n", i); + LOG("\tChannel %d looping.\n", i); } else { m_voice[i].m_looping = false; - osd_printf_debug("\tChannel %d done looping.\n", i); + LOG("\tChannel %d done looping.\n", i); } } break; } case 0x03: // OPT (options) { - osd_printf_debug("OPT complex data %02x\n", m_TMP_register); + LOG("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); - osd_printf_debug("\tOPT setting main volume scale to Vdd/%d\n", m_global_volume+1); - osd_printf_debug("\tOPT setting output filter type to %d\n", m_filter_type); - osd_printf_debug("\tOPT setting output amp level to %d\n", m_output_level); + LOG("\tOPT setting main volume scale to Vdd/%d\n", m_global_volume+1); + LOG("\tOPT setting output filter type to %d\n", m_filter_type); + LOG("\tOPT setting output amp level to %d\n", m_output_level); break; } case 0x04: // MUON (silence) { - osd_printf_warning("MUON channel %d length %02x\n", channel, m_TMP_register); - osd_printf_warning("MSM9810: UNIMPLEMENTED COMMAND!\n"); + logerror("MUON channel %d length %02x\n", channel, m_TMP_register); + logerror("MSM9810: UNIMPLEMENTED COMMAND!\n"); break; } @@ -350,39 +356,37 @@ void okim9810_device::write_command(uint8_t data) endAddr |= read_byte(base + 6) << 8; endAddr |= read_byte(base + 7) << 0; - // Sub-table - if (startFlags & 0x80) + if (BIT(startFlags, 7)) { - offs_t subTable = startAddr; - // TODO: New startFlags &= 0x80. Are there further subtables? - startFlags = read_byte(subTable + 0); - startAddr = read_byte(subTable + 1) << 16; - startAddr |= read_byte(subTable + 2) << 8; - startAddr |= read_byte(subTable + 3) << 0; - - // TODO: What does byte (subTable + 4) refer to? - endAddr = read_byte(subTable + 5) << 16; - endAddr |= read_byte(subTable + 6) << 8; - endAddr |= read_byte(subTable + 7) << 0; + // use "Phrase Control Table" mode + m_voice[channel].m_phrase_offset = startAddr; + m_voice[channel].m_phrase_count = endAddr - startAddr + 1; + if (m_voice[channel].m_phrase_count == 0) + LOG("MSM9810: Empty phrase subtable\n"); + m_voice[channel].m_phrase_state = SEQ_ACTIVE; } + else + { + // single sample mode + m_voice[channel].m_phrase_state = SEQ_STOP; + 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; - 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; // 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 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); - osd_printf_debug("startFlags(%02x) startAddr(%06x) endFlags(%02x) endAddr(%06x) bytes(%d)\n", startFlags, startAddr, endFlags, endAddr, endAddr-startAddr); + 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 if (m_voice[channel].m_playbackAlgo == NONLINEAR8_PLAYBACK) + logerror("MSM9810: UNIMPLEMENTED PLAYBACK METHOD %d\n", m_voice[channel].m_playbackAlgo); + + LOG("FADR channel %d phrase offset %02x => ", channel, m_TMP_register); + LOG("startFlags(%02x) startAddr(%06x) endFlags(%02x) endAddr(%06x) bytes(%d)\n", startFlags, startAddr, endFlags, endAddr, endAddr - startAddr); + } break; } @@ -394,6 +398,7 @@ void okim9810_device::write_command(uint8_t data) offs_t endAddr = m_dadr_end_offset; uint8_t startFlags = m_dadr_flags; + m_voice[channel].m_phrase_state = SEQ_STOP; m_voice[channel].m_sample = 0; m_voice[channel].m_interpSampleNum = 0; m_voice[channel].m_startFlags = startFlags; @@ -407,20 +412,20 @@ void okim9810_device::write_command(uint8_t data) 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); + logerror("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); + LOG("startFlags(%02x) startAddr(%06x) endAddr(%06x) bytes(%d)\n", startFlags, startAddr, endAddr, endAddr-startAddr); } else { - osd_printf_warning("MSM9810: UNKNOWN COMMAND!\n"); + logerror("MSM9810: UNKNOWN COMMAND!\n"); } break; } case 0x07: // CVOL (channel volume) { - osd_printf_debug("CVOL channel %d data %02x\n", channel, m_TMP_register); - osd_printf_debug("\tChannel %d -> volume index %d.\n", channel, m_TMP_register & 0x0f); + LOG("CVOL channel %d data %02x\n", channel, m_TMP_register); + LOG("\tChannel %d -> volume index %d.\n", channel, m_TMP_register & 0x0f); m_voice[channel].m_channel_volume = m_TMP_register & 0x0f; break; @@ -429,22 +434,22 @@ void okim9810_device::write_command(uint8_t data) { const uint8_t leftVolIndex = (m_TMP_register & 0xf0) >> 4; const uint8_t rightVolIndex = m_TMP_register & 0x0f; - osd_printf_debug("PAN channel %d left index: %02x right index: %02x (%02x)\n", channel, leftVolIndex, rightVolIndex, m_TMP_register); - osd_printf_debug("\tChannel %d left -> %d right -> %d\n", channel, leftVolIndex, rightVolIndex); + LOG("PAN channel %d left index: %02x right index: %02x (%02x)\n", channel, leftVolIndex, rightVolIndex, m_TMP_register); + LOG("\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: { - osd_printf_warning("MSM9810: UNKNOWN COMMAND!\n"); + logerror("MSM9810: UNKNOWN COMMAND!\n"); break; } } m_dadr = 0; } -WRITE8_MEMBER( okim9810_device::write ) +void okim9810_device::write(uint8_t data) { assert(!m_serial); write_command(data); @@ -488,12 +493,12 @@ void okim9810_device::write_tmp_register(uint8_t data) default: break; } - osd_printf_debug("DADR direct offset %02x = %02x => ", m_dadr, m_TMP_register); + LOG("DADR direct offset %02x = %02x => ", m_dadr, m_TMP_register); m_dadr++; } } -WRITE8_MEMBER( okim9810_device::tmp_register_w ) +void okim9810_device::tmp_register_w(uint8_t data) { assert(!m_serial); write_tmp_register(data); @@ -504,12 +509,12 @@ WRITE8_MEMBER( okim9810_device::tmp_register_w ) // Serial interface, NOT verified //----------------------------------------------------------- -WRITE_LINE_MEMBER( okim9810_device::serial_w ) +void okim9810_device::serial_w(int state) { m_serial = state; } -WRITE_LINE_MEMBER( okim9810_device::si_w ) +void okim9810_device::si_w(int state) { if (m_si != state) { @@ -534,43 +539,43 @@ WRITE_LINE_MEMBER( okim9810_device::si_w ) } } -WRITE_LINE_MEMBER( okim9810_device::sd_w ) +void okim9810_device::sd_w(int state) { m_sd = state; } -WRITE_LINE_MEMBER( okim9810_device::ud_w ) +void okim9810_device::ud_w(int state) { m_ud = state; } -WRITE_LINE_MEMBER( okim9810_device::cmd_w ) +void okim9810_device::cmd_w(int state) { m_cmd = state; } -READ_LINE_MEMBER( okim9810_device::so_r ) +int 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 ) +int okim9810_device::sr0_r() { return (m_serial_read_latch >> ((m_ud) ? 4 : 0)) & 1; } -READ_LINE_MEMBER( okim9810_device::sr1_r ) +int okim9810_device::sr1_r() { return (m_serial_read_latch >> ((m_ud) ? 5 : 1)) & 1; } -READ_LINE_MEMBER( okim9810_device::sr2_r ) +int okim9810_device::sr2_r() { return (m_serial_read_latch >> ((m_ud) ? 6 : 2)) & 1; } -READ_LINE_MEMBER( okim9810_device::sr3_r ) +int okim9810_device::sr3_r() { return (m_serial_read_latch >> ((m_ud) ? 7 : 3)) & 1; } @@ -593,6 +598,10 @@ okim9810_device::okim_voice::okim_voice() m_samplingFreq(2), m_playing(false), m_sample(0), + m_phrase_offset(0), + m_phrase_count(0), + m_phrase_wait_cnt(0), + m_phrase_state(SEQ_STOP), m_channel_volume(0x00), m_pan_volume_left(0x00), m_pan_volume_right(0x00), @@ -608,8 +617,7 @@ okim9810_device::okim_voice::okim_voice() //------------------------------------------------- void okim9810_device::okim_voice::generate_audio(device_rom_interface &rom, - stream_sample_t **buffers, - int samples, + sound_stream &stream, const uint8_t global_volume, const uint8_t filter_type) { @@ -617,10 +625,6 @@ void okim9810_device::okim_voice::generate_audio(device_rom_interface &rom, 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_t volume_scale_left = volume_scale(global_volume, m_channel_volume, m_pan_volume_left); uint8_t volume_scale_right = volume_scale(global_volume, m_channel_volume, m_pan_volume_right); @@ -631,8 +635,61 @@ void okim9810_device::okim_voice::generate_audio(device_rom_interface &rom, return; // loop while we still have samples to generate - while (samples-- != 0) + for (int sampindex = 0; sampindex < stream.samples(); sampindex++) { + if (m_phrase_state == SEQ_PAUSE) + { + m_phrase_wait_cnt--; + if (m_phrase_wait_cnt > 0) + continue; + else + m_phrase_state = SEQ_ACTIVE; + } + if (m_phrase_state == SEQ_ACTIVE) + { + if (m_phrase_count > 0) + { + offs_t startAddr, endAddr; + uint8_t startFlags = rom.read_byte(m_phrase_offset++); + m_phrase_count--; + if (!BIT(startFlags, 7)) + { + m_phrase_state = SEQ_PAUSE; + m_phrase_wait_cnt = (startFlags & 0x1f) * 16384; // not verified, doc says range from 4ms to 124ms + continue; + } + + startAddr = rom.read_byte(m_phrase_offset++) << 16; + startAddr |= rom.read_byte(m_phrase_offset++) << 8; + startAddr |= rom.read_byte(m_phrase_offset++) << 0; + m_channel_volume = rom.read_byte(m_phrase_offset++) & 0xf; // not verified + endAddr = rom.read_byte(m_phrase_offset++) << 16; + endAddr |= rom.read_byte(m_phrase_offset++) << 8; + endAddr |= rom.read_byte(m_phrase_offset++) << 0; + m_phrase_count -= 7; + + m_sample = 0; + m_interpSampleNum = 0; + m_startFlags = startFlags; + m_base_offset = startAddr; + m_count = endAddr - startAddr + 1; + m_playbackAlgo = (startFlags & 0x30) >> 4; // Not verified + m_samplingFreq = startFlags & 0x0f; + if (m_playbackAlgo == ADPCM_PLAYBACK || m_playbackAlgo == ADPCM2_PLAYBACK) + m_count *= 2; + + m_phrase_state = SEQ_PLAY; + totalInterpSamples = s_sampling_freq_div_table[m_samplingFreq]; + if (totalInterpSamples == 1) + return; + } + else + { + m_phrase_state = SEQ_STOP; + m_playing = false; + break; + } + } // If interpSampleNum == 0, we are at the beginning of a new interp chunk, gather data if (m_interpSampleNum == 0) { @@ -742,11 +799,11 @@ void okim9810_device::okim_voice::generate_audio(device_rom_interface &rom, // 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_t interpValueL = (interpValue * (int32_t)volume_scale_left) / 8; - *outL++ += interpValueL; + int32_t interpValueL = (interpValue * (int32_t)volume_scale_left); + stream.add_int(0, sampindex, interpValueL, 32768 * 8); - int32_t interpValueR = (interpValue * (int32_t)volume_scale_right) / 8; - *outR++ += interpValueR; + int32_t interpValueR = (interpValue * (int32_t)volume_scale_right); + stream.add_int(1, sampindex, interpValueR, 32768 * 8); // if the interpsample has reached its end, move on to the next sample m_interpSampleNum++; @@ -761,8 +818,13 @@ void okim9810_device::okim_voice::generate_audio(device_rom_interface &rom, { if (!m_looping) { - m_playing = false; - break; + if (m_phrase_state == SEQ_STOP) + { + m_playing = false; + break; + } + else + m_phrase_state = SEQ_ACTIVE; } else { |