diff options
Diffstat (limited to 'src/devices/sound/hc55516.cpp')
-rw-r--r-- | src/devices/sound/hc55516.cpp | 578 |
1 files changed, 407 insertions, 171 deletions
diff --git a/src/devices/sound/hc55516.cpp b/src/devices/sound/hc55516.cpp index 99cc782dc20..0bd36ae381f 100644 --- a/src/devices/sound/hc55516.cpp +++ b/src/devices/sound/hc55516.cpp @@ -1,16 +1,33 @@ // license:BSD-3-Clause -// copyright-holders:Aaron Giles +// copyright-holders:Aaron Giles, Jonathan Gevaryahu +// thanks-to:Zonn Moore /***************************************************************************** - Harris HC-55516 (and related) emulator + Continuously Variable Slope Demodulator standalone chip emulator: + Harris HC-55516 (sometimes labeled HCI-55516 or HC1-55516) + Harris HC-55532 (sometimes labeled HCI-55532 or HC1-55532) [preliminary] + Motorola MC-3417/MC-34115 + Motorola MC-3418 + + TODO: + - see .h file + - research HC-55536 and HC-55564 differences vs HC-55516 (better auto-zeroing, + and removal of the encoder offset compensation DAC?) + - /src/mame/exidy/exidy440_a.cpp has its own internal implementation of the + MC3417 and MC3418, it should be using this file instead + - MC3417 interpolation slope being determined by the number of samples in + the current stream slice doesn't make sense + + BTANB: + - outputs a low-volume but very high-pitched background tone when /FZ is + active and the hardware doesn't have a low-pass filter *****************************************************************************/ #include "emu.h" #include "hc55516.h" - -/* 4x oversampling */ +// fixed samplerate of 192khz #define SAMPLE_RATE (48000 * 4) #define INTEGRATOR_LEAK_TC 0.001 @@ -18,36 +35,24 @@ #define FILTER_CHARGE_TC 0.004 #define FILTER_MIN 0.0416 #define FILTER_MAX 1.0954 -#define SAMPLE_GAIN 10000.0 - - - - -DEFINE_DEVICE_TYPE(HC55516, hc55516_device, "hc55516", "HC-55516") - -hc55516_device::hc55516_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : hc55516_device(mconfig, HC55516, tag, owner, clock) -{ -} - -hc55516_device::hc55516_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) - : device_t(mconfig, type, tag, owner, clock), - device_sound_interface(mconfig, *this), - m_channel(nullptr), - m_active_clock_hi(0), - m_shiftreg_mask(0), - m_last_clock_state(0), - m_digit(0), - m_new_digit(0), - m_shiftreg(0), - m_curr_sample(0), - m_next_sample(0), - m_update_count(0), - m_filter(0), - m_integrator(0), - m_charge(0), - m_decay(0), - m_leak(0) +#define SAMPLE_GAIN (10000.0 / 32768.0) + + +//##################################### +// COMMON +//##################################### +cvsd_device_base::cvsd_device_base(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, bool active_clock_edge, uint8_t shiftreg_mask) + : device_t(mconfig, type, tag, owner, clock) + , device_sound_interface(mconfig, *this) + , m_clock_state_push_cb(*this) + , m_digin_pull_cb(*this, 1) + , m_digout_push_cb(*this) + , m_active_clock_edge(active_clock_edge) + , m_shiftreg_mask(shiftreg_mask) + , m_last_clock_state(false) + , m_buffered_bit(false) + , m_shiftreg(0) + , m_samples_generated(0) { } @@ -55,258 +60,489 @@ hc55516_device::hc55516_device(const machine_config &mconfig, device_type type, // device_start - device-specific startup //------------------------------------------------- -void hc55516_device::device_start() +void cvsd_device_base::device_start() { - start_common(0x07, true); + // create the stream + m_stream = stream_alloc(0, 1, SAMPLE_RATE); + + save_item(NAME(m_last_clock_state)); + save_item(NAME(m_buffered_bit)); + save_item(NAME(m_shiftreg)); + save_item(NAME(m_samples_generated)); } //------------------------------------------------- // device_reset - device-specific reset //------------------------------------------------- -void hc55516_device::device_reset() +void cvsd_device_base::device_reset() { m_last_clock_state = 0; } -DEFINE_DEVICE_TYPE(MC3417, mc3417_device, "mc3417", "MC3417") +//------------------------------------------------- +// device_clock_changed - device-specific samplerate change +//------------------------------------------------- +/*void cvsd_device_base::device_clock_changed() +{ + // do nothing. + //m_stream->set_sample_rate(clock()); +}*/ -mc3417_device::mc3417_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : hc55516_device(mconfig, MC3417, tag, owner, clock) +int cvsd_device_base::clock_r() { + // prevent debugger from changing the internal state + if (!machine().side_effects_disabled()) + m_stream->update(); // bring up to date first + return clock_state_r(); } -//------------------------------------------------- -// device_start - device-specific startup -//------------------------------------------------- +void cvsd_device_base::mclock_w(int state) +{ + clock_w(state); +} -void mc3417_device::device_start() +void cvsd_device_base::digin_w(int state) { - start_common(0x07, false); + digit_w(state); } +// the following encode related functions don't do anything yet, don't call them. +/*void cvsd_device_base::audio_in_w(int16_t data) +{ + assert(0); +}*/ -DEFINE_DEVICE_TYPE(MC3418, mc3418_device, "mc3418", "MC3418") +void cvsd_device_base::dec_encq_w(int state) +{ + assert(0); +} -mc3418_device::mc3418_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : hc55516_device(mconfig, MC3418, tag, owner, clock) +int cvsd_device_base::digout_r() { + return 0; } -//------------------------------------------------- -// device_start - device-specific startup -//------------------------------------------------- +// default and stub implementations -void mc3418_device::device_start() +inline bool cvsd_device_base::is_external_oscillator() { - start_common(0x0f, false); + return clock() != 0; } - -void hc55516_device::start_common(uint8_t _shiftreg_mask, int _active_clock_hi) +inline bool cvsd_device_base::is_clock_changed(bool clock_state) { - /* compute the fixed charge, decay, and leak time constants */ - m_charge = pow(exp(-1.0), 1.0 / (FILTER_CHARGE_TC * 16000.0)); - m_decay = pow(exp(-1.0), 1.0 / (FILTER_DECAY_TC * 16000.0)); - m_leak = pow(exp(-1.0), 1.0 / (INTEGRATOR_LEAK_TC * 16000.0)); + return ((!m_last_clock_state && clock_state) || (m_last_clock_state && !clock_state)); +} - m_shiftreg_mask = _shiftreg_mask; - m_active_clock_hi = _active_clock_hi; - m_last_clock_state = 0; +inline bool cvsd_device_base::is_active_clock_transition(bool clock_state) +{ + return ((clock_state != m_last_clock_state) && (clock_state == m_active_clock_edge)); +} - /* create the stream */ - m_channel = machine().sound().stream_alloc(*this, 0, 1, SAMPLE_RATE); +inline bool cvsd_device_base::current_clock_state() +{ + // keep track of the clock state given its previous state and the number of samples produced + // i.e. if we generated m_samples_generated samples, at a sample rate of SAMPLE_RATE, then are we on a + // positive or negative level of a squarewave at clock() hz? SAMPLE_RATE may not be an integer multiple of clock() + //uint64_t fractions_of_second = (((uint64_t)m_samples_generated) << 32) / SAMPLE_RATE; // 32.32 bits of seconds passed so far + //uint32_t clock_edges_passed = (fractions_of_second * clock() * 2) >> 32 + //return bool(((((uint64_t)m_samples_generated << 32) * clock() * 2 / SAMPLE_RATE) >> 32) & 0x1); + return bool(((uint64_t)m_samples_generated * clock() * 2 / SAMPLE_RATE) & 0x01); +} - save_item(NAME(m_last_clock_state)); - save_item(NAME(m_digit)); - save_item(NAME(m_new_digit)); - save_item(NAME(m_shiftreg)); - save_item(NAME(m_curr_sample)); - save_item(NAME(m_next_sample)); - save_item(NAME(m_update_count)); - save_item(NAME(m_filter)); - save_item(NAME(m_integrator)); +void cvsd_device_base::digit_w(int digit) +{ + m_stream->update(); + m_buffered_bit = bool(digit); } -inline int hc55516_device::is_external_oscillator() +void cvsd_device_base::clock_w(int state) { - return clock() != 0; + // update the output buffer first + m_stream->update(); + bool clock_state = bool(state); + + // only makes sense for setups with a software driven clock + assert(!is_external_oscillator()); + + // speech clock changing? + if (is_clock_changed(clock_state)) + { + // clear the update count + m_samples_generated = 0; + process_bit(m_buffered_bit, clock_state); + } + + // update the clock + m_last_clock_state = clock_state; } +int cvsd_device_base::clock_state_r() +{ + // only makes sense for setups with an external oscillator + assert(is_external_oscillator()); + + m_stream->update(); + + return current_clock_state(); +} -inline int hc55516_device::is_active_clock_transition(int clock_state) +void cvsd_device_base::process_bit(bool bit, bool clock_state) { - return (( m_active_clock_hi && !m_last_clock_state && clock_state) || - (!m_active_clock_hi && m_last_clock_state && !clock_state)); + // stub } +//------------------------------------------------- +// sound_stream_update - handle a stream update +//------------------------------------------------- -inline int hc55516_device::current_clock_state() +void cvsd_device_base::sound_stream_update(sound_stream &stream) { - return ((uint64_t)m_update_count * clock() * 2 / SAMPLE_RATE) & 0x01; + // Stub, just return silence + m_samples_generated += stream.samples(); + if (m_samples_generated >= SAMPLE_RATE) + m_samples_generated -= SAMPLE_RATE; } -void hc55516_device::process_digit() +//######################################### +// HC55516 +//######################################### +DEFINE_DEVICE_TYPE(HC55516, hc55516_device, "hc55516", "HC-55516") + +hc55516_device::hc55516_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : hc55516_device(mconfig, HC55516, tag, owner, clock, 0xfc0, 6, 0xfc1, 4) { - double integrator = m_integrator, temp; +} - /* shift the bit into the shift register */ - m_shiftreg = (m_shiftreg << 1) | m_digit; +// overridable type for hc55532 etc +hc55516_device::hc55516_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, uint32_t sylmask, int32_t sylshift, int32_t syladd, int32_t intshift) + : cvsd_device_base(mconfig, type, tag, owner, clock, RISING, 0x7) + , m_agc_push_cb(*this) + , m_fzq_pull_cb(*this, 1) + , m_sylmask(sylmask) + , m_sylshift(sylshift) + , m_syladd(syladd) + , m_intshift(intshift) + , m_sylfilter(0) + , m_intfilter(0) + , m_next_sample(0) + , m_agc(true) + , m_buffered_fzq(true) +{ +} - /* move the estimator up or down a step based on the bit */ - if (m_digit) - integrator += m_filter; - else - integrator -= m_filter; +//------------------------------------------------- +// device_start - device-specific start +//------------------------------------------------- - /* simulate leakage */ - integrator *= m_leak; +void hc55516_device::device_start() +{ + cvsd_device_base::device_start(); - /* if we got all 0's or all 1's in the last n bits, bump the step up */ - if (((m_shiftreg & m_shiftreg_mask) == 0) || - ((m_shiftreg & m_shiftreg_mask) == m_shiftreg_mask)) - { - m_filter = FILTER_MAX - ((FILTER_MAX - m_filter) * m_charge); + save_item(NAME(m_sylfilter)); + save_item(NAME(m_intfilter)); + save_item(NAME(m_next_sample)); + save_item(NAME(m_agc)); + save_item(NAME(m_buffered_fzq)); +} - if (m_filter > FILTER_MAX) - m_filter = FILTER_MAX; - } +//------------------------------------------------- +// device_reset - device-specific reset +//------------------------------------------------- - /* simulate decay */ - else - { - m_filter *= m_decay; +void hc55516_device::device_reset() +{ + cvsd_device_base::device_reset(); - if (m_filter < FILTER_MIN) - m_filter = FILTER_MIN; - } + // simulate /FZ having been held for a while + m_sylfilter = 0x3f; + m_intfilter = 0; + m_agc = true; + m_buffered_fzq = true; // assuming /FZ was just released and is now high/inactive +} - /* compute the sample as a 32-bit word */ - temp = integrator * SAMPLE_GAIN; - m_integrator = integrator; +// device specific functions - /* compress the sample range to fit better in a 16-bit word */ - if (temp < 0) - m_next_sample = (int)(temp / (-temp * (1.0 / 32768.0) + 1.0)); - else - m_next_sample = (int)(temp / (temp * (1.0 / 32768.0) + 1.0)); +void hc55516_device::fzq_w(int state) +{ + m_buffered_fzq = state; } -void hc55516_device::clock_w(int state) +int hc55516_device::agc_r() { - uint8_t clock_state = state ? true : false; + // prevent debugger from changing the internal state + if (!machine().side_effects_disabled()) + m_stream->update(); // bring up to date first + return m_agc; +} - /* only makes sense for setups with a software driven clock */ - assert(!is_external_oscillator()); +void hc55516_device::process_bit(bool bit, bool clock_state) +{ + const bool frozen = (((m_intfilter >= 0x180) && !bit) || ((m_intfilter <= -0x180) && bit)); + int32_t sum; - /* speech clock changing? */ if (is_active_clock_transition(clock_state)) { - /* update the output buffer before changing the registers */ - m_channel->update(); + // grab the /FZ state; if the callback is present, use that, otherwise use the buffered state + bool fzq_state = false; + if (!m_fzq_pull_cb.isunset()) + fzq_state = m_fzq_pull_cb(); + else + fzq_state = m_buffered_fzq; + + // if /FZ is active, the input bit is ignored and the inverse of the previous bit in the shifter is used instead + if (!fzq_state) + bit = !(m_shiftreg & 1); + + // shift the new bit into the shift register + m_shiftreg = (m_shiftreg << 1) | (bit ? 1 : 0); + + // if we got all 0's or all 1's in the last n bits... + if (((m_shiftreg & m_shiftreg_mask) == 0) || ((m_shiftreg & m_shiftreg_mask) == m_shiftreg_mask)) + { + // coincidence is true + if (!frozen) m_sylfilter += (((~m_sylfilter) & m_sylmask) >> m_sylshift); + } + else + { + // coincidence is false + if (!frozen) m_sylfilter += (((~m_sylfilter) & m_sylmask) >> m_sylshift) + m_syladd; + } + m_sylfilter &= 0xfff; - /* clear the update count */ - m_update_count = 0; + sum = util::sext(((~m_intfilter) >> m_intshift) + 1, 10); + } + else + { + // inactive clock transition + if (m_shiftreg & 1) + { + sum = util::sext((~std::max(2, m_sylfilter >> 6)) + 1, 10); + } + else + { + sum = util::sext(std::max(2, m_sylfilter >> 6), 10); + } + } - process_digit(); + if (!frozen) + { + m_intfilter = util::sext(m_intfilter + sum, 10); } - /* update the clock */ - m_last_clock_state = clock_state; + /* scale the result (-512 to 511) to -32768 thru 32767 */ + /* + F E D C B A 9 8 7 6 5 4 3 2 1 0 + 9 8 7 6 5 4 3 2 1 0/9 8 7 6 5 4 + */ + m_next_sample = ((m_intfilter << 6) | (((m_intfilter & 0x3ff) ^ 0x200) >> 4)); + + // update agc state + if ((m_intfilter >= 0x100) || (m_intfilter <= -0x100)) + m_agc = false; + else + m_agc = true; + + // push agc state if a callback is present + m_agc_push_cb(m_agc); } +//------------------------------------------------- +// sound_stream_update_legacy - handle a stream update +//------------------------------------------------- -void hc55516_device::digit_w(int digit) +void hc55516_device::sound_stream_update(sound_stream &stream) { if (is_external_oscillator()) { - m_channel->update(); - m_new_digit = digit & 1; + // external oscillator + for (int i = 0; i < stream.samples(); i++) + { + stream.put_int(0, i, m_next_sample, 32768); + + m_samples_generated++; + + uint8_t clock_state = current_clock_state(); + + // pull in next digit on the appropriate edge of the clock + if (is_clock_changed(clock_state)) + { + process_bit(m_buffered_bit, clock_state); + } + + m_last_clock_state = clock_state; + } } + + // software driven clock else - m_digit = digit & 1; + { + for (int i = 0; i < stream.samples(); i++) + stream.put_int(0, i, m_next_sample, 32768); + } } -int hc55516_device::clock_state_r() + +//######################################### +// HC55532 +//######################################### +DEFINE_DEVICE_TYPE(HC55532, hc55532_device, "hc55532", "HC-55532") + +hc55532_device::hc55532_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : hc55516_device(mconfig, HC55532, tag, owner, clock, 0xf80, 7, 0xfe1, 5) { - /* only makes sense for setups with an external oscillator */ - assert(is_external_oscillator()); +} - m_channel->update(); +//------------------------------------------------- +// device_reset - device-specific reset +//------------------------------------------------- - return current_clock_state(); +void hc55532_device::device_reset() +{ + cvsd_device_base::device_reset(); + + // simulate /FZ having been held for a while + m_sylfilter = 0x7f; + m_intfilter = 0; + m_agc = true; + m_buffered_fzq = true; // assuming /FZ was just released and is now high/inactive } + +//########################################## +// MC3417 +//########################################## +DEFINE_DEVICE_TYPE(MC3417, mc3417_device, "mc3417", "MC3417") + +mc3417_device::mc3417_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : mc3417_device(mconfig, MC3417, tag, owner, clock, 0x7) +{ +} + +// overridable type for mc3418 etc +mc3417_device::mc3417_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, uint8_t shiftreg_mask) + : cvsd_device_base(mconfig, type, tag, owner, clock, FALLING, shiftreg_mask) + , m_charge(pow(exp(-1.0), 1.0 / (FILTER_CHARGE_TC * 16000.0))) + , m_decay(pow(exp(-1.0), 1.0 / (FILTER_DECAY_TC * 16000.0))) + , m_leak(pow(exp(-1.0), 1.0 / (INTEGRATOR_LEAK_TC * 16000.0))) + , m_sylfilter(0.0) + , m_intfilter(0.0) + , m_curr_sample(0.0) + , m_next_sample(0.0) +{ +} + //------------------------------------------------- -// sound_stream_update - handle a stream update +// device_start - device-specific startup //------------------------------------------------- -void hc55516_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) +void mc3417_device::device_start() { - stream_sample_t *buffer = outputs[0]; - int i; - int32_t sample, slope; + cvsd_device_base::device_start(); + + save_item(NAME(m_sylfilter)); + save_item(NAME(m_intfilter)); + save_item(NAME(m_curr_sample)); + save_item(NAME(m_next_sample)); +} + +void mc3417_device::process_bit(bool bit, bool clock_state) +{ + if (is_active_clock_transition(clock_state)) + { + // shift the new bit into the shift register + m_shiftreg = (m_shiftreg << 1) | (bit ? 1 : 0); - /* zero-length? bail */ - if (samples == 0) - return; + // move the estimator up or down a step based on the bit + if (!bit) + m_intfilter += m_sylfilter; + else + m_intfilter -= m_sylfilter; + // simulate leakage + m_intfilter *= m_leak; + + // if we got all 0's or all 1's in the last n bits, bump the step up + if (((m_shiftreg & m_shiftreg_mask) == 0) || ((m_shiftreg & m_shiftreg_mask) == m_shiftreg_mask)) + { + // coincidence is true + m_sylfilter = FILTER_MAX - ((FILTER_MAX - m_sylfilter) * m_charge); + + if (m_sylfilter > FILTER_MAX) + m_sylfilter = FILTER_MAX; + } + else + { + m_sylfilter *= m_decay; + + if (m_sylfilter < FILTER_MIN) + m_sylfilter = FILTER_MIN; + } + + // compute the sample as a 32-bit word + m_next_sample = m_intfilter * SAMPLE_GAIN; + } +} + +void mc3417_device::sound_stream_update(sound_stream &stream) +{ if (!is_external_oscillator()) { - /* track how many samples we've updated without a clock */ - m_update_count += samples; - if (m_update_count > SAMPLE_RATE / 32) + // track how many samples we've updated without a clock; if it's been more than 1/32 of a second, output silence + m_samples_generated += stream.samples(); + if (m_samples_generated > SAMPLE_RATE / 32) { - m_update_count = SAMPLE_RATE; + m_samples_generated = SAMPLE_RATE; m_next_sample = 0; } } - /* compute the interpolation slope */ - sample = m_curr_sample; - slope = ((int32_t)m_next_sample - sample) / samples; + // compute the interpolation slope + sound_stream::sample_t sample = m_curr_sample; + sound_stream::sample_t slope = (m_next_sample - sample) / stream.samples(); m_curr_sample = m_next_sample; if (is_external_oscillator()) { - /* external oscillator */ - for (i = 0; i < samples; i++, sample += slope) + // external oscillator + for (int i = 0; i < stream.samples(); i++, sample += slope) { - uint8_t clock_state; - - *buffer++ = sample; + stream.put(0, i, sample); - m_update_count++; + m_samples_generated++; - clock_state = current_clock_state(); + uint8_t clock_state = current_clock_state(); - /* pull in next digit on the appropriate edge of the clock */ - if (is_active_clock_transition(clock_state)) + // pull in next digit on the appropriate edge of the clock + if (is_clock_changed(clock_state)) { - m_digit = m_new_digit; - - process_digit(); + process_bit(m_buffered_bit, clock_state); } m_last_clock_state = clock_state; } } - /* software driven clock */ + // software driven clock else - for (i = 0; i < samples; i++, sample += slope) - *buffer++ = sample; + { + for (int i = 0; i < stream.samples(); i++, sample += slope) + stream.put(0, i, sample); + } } -void mc3417_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) -{ - hc55516_device::sound_stream_update(stream, inputs, outputs, samples); -} -void mc3418_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) + +//########################################## +// MC3418 +//########################################## +DEFINE_DEVICE_TYPE(MC3418, mc3418_device, "mc3418", "MC3418") + +mc3418_device::mc3418_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : mc3417_device(mconfig, MC3418, tag, owner, clock, 0xf) { - hc55516_device::sound_stream_update(stream, inputs, outputs, samples); } |