// license:BSD-3-Clause // copyright-holders:Quench /********************************************************************************************** * * Streaming single channel ADPCM core for the ES8712 chip * Chip is branded by Excellent Systems, probably OEM'd. * * Samples are currently looped, but whether they should and how, is unknown. * Interface to the chip is also not 100% clear. * * This implementation, heavily borrowed from the OKI M6295 source, is based on * almost certainly incorrect assumptions about the chip's nature. It seems * that the ES8712 is actually a programmable counter which can stream ADPCM * samples when hooked up to a ROM and a M5205 or M6585 (whose VCK signal can * drive the counter). Neither of these seem to be used in conjunction with the * ES8712 on Dream 9 and Dream 9 Final, which suggests it may have been used * for an entirely different purpose there (perhaps to do with video timing). * **********************************************************************************************/ #include "emu.h" #include "es8712.h" #define MAX_SAMPLE_CHUNK 10000 /* step size index shift table */ static constexpr int index_shift[8] = { -1, -1, -1, -1, 2, 4, 6, 8 }; /* lookup table for the precomputed difference */ static int diff_lookup[49*16]; // device type definition DEFINE_DEVICE_TYPE(ES8712, es8712_device, "es8712", "Excellent Systems ES8712 ADPCM") //************************************************************************** // LIVE DEVICE //************************************************************************** //------------------------------------------------- // es8712_device - constructor //------------------------------------------------- es8712_device::es8712_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, ES8712, tag, owner, clock), device_sound_interface(mconfig, *this), m_rom(*this, DEVICE_SELF), m_reset_handler(*this), m_playing(0), m_base_offset(0), m_sample(0), m_count(0), m_signal(0), m_step(0), m_start(0), m_end(0), m_bank_offset(0), m_stream(nullptr) { } //------------------------------------------------- // device_start - start emulation of an ES8712 chip //------------------------------------------------- void es8712_device::device_start() { compute_tables(); m_reset_handler.resolve_safe(); m_start = 0; m_end = 0; m_bank_offset = 0; /* generate the name and create the stream */ m_stream = stream_alloc(0, 1, clock()); /* initialize the rest of the structure */ m_signal = -2; es8712_state_save_register(); } //------------------------------------------------- // device_reset - stop emulation of an ES8712-compatible chip //------------------------------------------------- void es8712_device::device_reset() { if (m_playing) { /* update the stream, then turn it off */ m_stream->update(); m_playing = 0; } m_reset_handler(1); } //------------------------------------------------- // sound_stream_update - update the sound chip so that it is in sync with CPU execution //------------------------------------------------- void es8712_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) { stream_sample_t *buffer = outputs[0]; /* generate them into our buffer */ generate_adpcm(buffer, samples); } //------------------------------------------------- // compute_tables -- compute the difference tables //------------------------------------------------- void es8712_device::compute_tables() { /* nibble to bit map */ static const int nbl2bit[16][4] = { { 1, 0, 0, 0}, { 1, 0, 0, 1}, { 1, 0, 1, 0}, { 1, 0, 1, 1}, { 1, 1, 0, 0}, { 1, 1, 0, 1}, { 1, 1, 1, 0}, { 1, 1, 1, 1}, {-1, 0, 0, 0}, {-1, 0, 0, 1}, {-1, 0, 1, 0}, {-1, 0, 1, 1}, {-1, 1, 0, 0}, {-1, 1, 0, 1}, {-1, 1, 1, 0}, {-1, 1, 1, 1} }; int step, nib; /* loop over all possible steps */ for (step = 0; step <= 48; step++) { /* compute the step value */ int stepval = floor(16.0 * pow(11.0 / 10.0, (double)step)); /* loop over all nibbles and compute the difference */ for (nib = 0; nib < 16; nib++) { diff_lookup[step*16 + nib] = nbl2bit[nib][0] * (stepval * nbl2bit[nib][1] + stepval/2 * nbl2bit[nib][2] + stepval/4 * nbl2bit[nib][3] + stepval/8); } } } //------------------------------------------------- // generate_adpcm -- general ADPCM decoding routine //------------------------------------------------- void es8712_device::generate_adpcm(stream_sample_t *buffer, int samples) { /* if this chip is active */ if (m_playing) { uint8_t *base = &m_rom[m_bank_offset + m_base_offset]; int sample = m_sample; int signal = m_signal; int count = m_count; int step = m_step; int val; /* loop while we still have samples to generate */ while (samples) { /* compute the new amplitude and update the current step */ val = base[sample / 2] >> (((sample & 1) << 2) ^ 4); signal += diff_lookup[step * 16 + (val & 15)]; /* clamp to the maximum */ if (signal > 2047) signal = 2047; else if (signal < -2048) signal = -2048; /* adjust the step size and clamp */ step += index_shift[val & 7]; if (step > 48) step = 48; else if (step < 0) step = 0; /* output to the buffer */ *buffer++ = signal * 16; samples--; /* next! */ if (++sample >= count) { m_playing = 0; m_reset_handler(1); break; } } /* update the parameters */ m_sample = sample; m_signal = signal; m_step = step; } /* fill the rest with silence */ while (samples--) *buffer++ = 0; } //------------------------------------------------- // state save support for MAME //------------------------------------------------- void es8712_device::es8712_state_save_register() { save_item(NAME(m_bank_offset)); save_item(NAME(m_playing)); save_item(NAME(m_sample)); save_item(NAME(m_count)); save_item(NAME(m_signal)); save_item(NAME(m_step)); save_item(NAME(m_base_offset)); save_item(NAME(m_start)); save_item(NAME(m_end)); } //------------------------------------------------- // es8712_set_bank_base -- set the base of the bank on a given chip //------------------------------------------------- void es8712_device::set_bank_base(int base) { m_stream->update(); m_bank_offset = base; } //------------------------------------------------- // es8712_set_frequency -- dynamically adjusts the frequency of a given ADPCM chip //------------------------------------------------- void es8712_device::set_frequency(int frequency) { /* update the stream and set the new base */ m_stream->update(); m_stream->set_sample_rate(frequency); } //------------------------------------------------- // play -- Begin playing the addressed sample //------------------------------------------------- void es8712_device::play() { if (m_start < m_end) { if (!m_playing) { logerror("Playing sample range %06x-%06x\n", m_start + m_bank_offset, m_end + m_bank_offset); m_playing = 1; m_reset_handler(0); m_base_offset = m_start; m_sample = 0; m_count = 2 * (m_end - m_start + 1); /* also reset the ADPCM parameters */ m_signal = -2; m_step = 0; } } /* invalid samples go here */ else { logerror("Request to play invalid sample range %06x-%06x\n", m_start + m_bank_offset, m_end + m_bank_offset); if (m_playing) { /* update the stream */ m_stream->update(); m_playing = 0; m_reset_handler(1); } } } /********************************************************************************************** es8712_data_0_w -- generic data write functions es8712_data_1_w ***********************************************************************************************/ /********************************************************************************************** * * offset Start End * 0hmmll - 0HMMLL * 00 ----ll * 01 --mm-- * 02 0h---- * 03 ----LL * 04 --MM-- * 05 0H---- * 06 Go! * * Offsets are written in the order -> 00, 02, 01, 03, 05, 04, 06 * Offset 06 is written with the same value as offset 04. * ***********************************************************************************************/ WRITE8_MEMBER(es8712_device::write) { switch (offset) { case 00: m_start &= 0x000fff00; m_start |= ((data & 0xff) << 0); break; case 01: m_start &= 0x000f00ff; m_start |= ((data & 0xff) << 8); break; case 02: m_start &= 0x0000ffff; m_start |= ((data & 0x0f) << 16); break; case 03: m_end &= 0x000fff00; m_end |= ((data & 0xff) << 0); break; case 04: m_end &= 0x000f00ff; m_end |= ((data & 0xff) << 8); break; case 05: m_end &= 0x0000ffff; m_end |= ((data & 0x0f) << 16); break; case 06: play(); break; default: break; } m_start &= 0xfffff; m_end &= 0xfffff; } READ8_MEMBER(es8712_device::read) { // busy state (other bits unknown) if (offset == 0) return m_playing ? 1 : 0; return 0; }