diff options
Diffstat (limited to 'src/devices/sound/hc55516.cpp')
-rw-r--r-- | src/devices/sound/hc55516.cpp | 584 |
1 files changed, 418 insertions, 166 deletions
diff --git a/src/devices/sound/hc55516.cpp b/src/devices/sound/hc55516.cpp index 6397d2c927c..2d96ad2cb3b 100644 --- a/src/devices/sound/hc55516.cpp +++ b/src/devices/sound/hc55516.cpp @@ -1,8 +1,18 @@ // 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: research HC-55536 and HC-55564 differences vs HC-55516 (better auto-zeroing, and removal of the encoder offset compensation DAC?) + + Driver TODOs: + /src/mame/audio/exidy440.cpp has its own internal implementation of the MC3417 and MC3418, it should be using this file instead + *****************************************************************************/ @@ -10,7 +20,7 @@ #include "hc55516.h" -/* 4x oversampling */ +/* fixed samplerate of 192khz */ #define SAMPLE_RATE (48000 * 4) #define INTEGRATOR_LEAK_TC 0.001 @@ -18,36 +28,26 @@ #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) + , 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_curr_sample(0) + , m_next_sample(0) + , m_samples_generated(0) { } @@ -55,240 +55,490 @@ 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_curr_sample)); + save_item(NAME(m_next_sample)); + 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) +READ_LINE_MEMBER( 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 -//------------------------------------------------- +WRITE_LINE_MEMBER( cvsd_device_base::mclock_w ) +{ + clock_w(state); +} -void mc3417_device::device_start() +WRITE_LINE_MEMBER( cvsd_device_base::digin_w ) { - 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") +WRITE_LINE_MEMBER( cvsd_device_base::dec_encq_w ) +{ + 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) +READ_LINE_MEMBER( 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; } +inline bool cvsd_device_base::is_clock_changed(bool clock_state) +{ + return ((!m_last_clock_state && clock_state) || + (m_last_clock_state && !clock_state)); +} -void hc55516_device::start_common(uint8_t _shiftreg_mask, int _active_clock_hi) +inline bool cvsd_device_base::is_active_clock_transition(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 ((clock_state != m_last_clock_state) && + (clock_state == m_active_clock_edge)); +} - m_shiftreg_mask = _shiftreg_mask; - m_active_clock_hi = _active_clock_hi; - m_last_clock_state = 0; +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 (((((uint64_t)m_samples_generated<<32) * clock() * 2 / SAMPLE_RATE)>>32) & 0x1)?true:false; + return (((uint64_t)m_samples_generated * clock() * 2 / SAMPLE_RATE) & 0x01)?true:false; +} - /* create the stream */ - m_channel = stream_alloc_legacy(0, 1, SAMPLE_RATE); +void cvsd_device_base::digit_w(int digit) +{ + m_stream->update(); + m_buffered_bit = digit ? true : false; +} - 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::clock_w(int state) +{ + /* update the output buffer first */ + m_stream->update(); + bool clock_state = state ? true : false; + + /* 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; } -inline int hc55516_device::is_external_oscillator() +int cvsd_device_base::clock_state_r() { - return clock() != 0; + /* only makes sense for setups with an external oscillator */ + assert(is_external_oscillator()); + + m_stream->update(); + + return current_clock_state(); } +void cvsd_device_base::process_bit(bool bit, bool clock_state) +{ + // stub +} -inline int hc55516_device::is_active_clock_transition(int clock_state) +//------------------------------------------------- +// sound_stream_update - handle a stream update +//------------------------------------------------- + +void cvsd_device_base::sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs) +{ + // Stub, just return silence + auto &buffer = outputs[0]; + + m_samples_generated += buffer.samples(); + if (m_samples_generated >= SAMPLE_RATE) + m_samples_generated -= SAMPLE_RATE; + buffer.fill(0); +} + + +//######################################### +// 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) +{ +} + +// 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) + , m_sylmask(sylmask) + , m_sylshift(sylshift) + , m_syladd(syladd) + , m_intshift(intshift) + , m_sylfilter(0) + , m_intfilter(0) + , m_agc(true) + , m_buffered_fzq(true) { - return (( m_active_clock_hi && !m_last_clock_state && clock_state) || - (!m_active_clock_hi && m_last_clock_state && !clock_state)); } +//------------------------------------------------- +// device_start - device-specific start +//------------------------------------------------- -inline int hc55516_device::current_clock_state() +void hc55516_device::device_start() { - return ((uint64_t)m_update_count * clock() * 2 / SAMPLE_RATE) & 0x01; + cvsd_device_base::device_start(); + save_item(NAME(m_sylfilter)); + save_item(NAME(m_intfilter)); + save_item(NAME(m_agc)); + save_item(NAME(m_buffered_fzq)); + + /* resolve lines */ + m_agc_push_cb.resolve(); + m_fzq_pull_cb.resolve(); } +//------------------------------------------------- +// device_reset - device-specific reset +//------------------------------------------------- -void hc55516_device::process_digit() +void hc55516_device::device_reset() { - double integrator = m_integrator, temp; + cvsd_device_base::device_reset(); + // 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 +} - /* shift the bit into the shift register */ - m_shiftreg = (m_shiftreg << 1) | m_digit; +// device specific functions - /* move the estimator up or down a step based on the bit */ - if (m_digit) - integrator += m_filter; - else - integrator -= m_filter; +WRITE_LINE_MEMBER( hc55516_device::fzq_w ) +{ + m_buffered_fzq = state; +} - /* simulate leakage */ - integrator *= m_leak; +READ_LINE_MEMBER( hc55516_device::agc_r ) +{ + // prevent debugger from changing the internal state + if (!machine().side_effects_disabled()) + m_stream->update(); /* bring up to date first */ + return m_agc; +} + +void hc55516_device::process_bit(bool bit, bool clock_state) +{ + bool frozen = ( ( (m_intfilter >= 0x180) && (!bit) ) || + ( (m_intfilter <= -0x180) && (bit) ) ); - /* 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)) + int32_t sum; + if (is_active_clock_transition(clock_state)) { - m_filter = FILTER_MAX - ((FILTER_MAX - m_filter) * m_charge); + // 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.isnull()) + fzq_state = m_fzq_pull_cb(); + else + fzq_state = m_buffered_fzq; + + if (!fzq_state) // /FZ is active low, if it is active, the input bit is ignored and the inverse of the previous bit in the shifter is used instead + 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; - if (m_filter > FILTER_MAX) - m_filter = FILTER_MAX; + sum = ( ((~m_intfilter) >> m_intshift) + 1 ) & 0x3ff; } - - /* simulate decay */ - else + else // inactive clock transition { - m_filter *= m_decay; + if (m_shiftreg&1) + { + sum = ( ( ~std::max(2, m_sylfilter >> 6) ) + 1 ) & 0x3ff; + } + else + { + sum = std::max(2, m_sylfilter >> 6) & 0x3ff; + } + } - if (m_filter < FILTER_MIN) - m_filter = FILTER_MIN; + if (sum & 0x200) + sum |= ~0x3ff; // sign extend + + if (!frozen) + { + m_intfilter += sum; + m_intfilter &= 0x3ff; + if (m_intfilter & 0x200) m_intfilter |= ~0x3ff; // sign extend } - /* compute the sample as a 32-bit word */ - temp = integrator * SAMPLE_GAIN; - m_integrator = integrator; + /* 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 ) ); - /* 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)); + // update agc state + if ( (m_intfilter >= 0x100) || (m_intfilter <= -0x100) ) + m_agc = false; else - m_next_sample = (int)(temp / (temp * (1.0 / 32768.0) + 1.0)); + m_agc = true; + + // push agc state if a callback is present + if (!m_agc_push_cb.isnull()) + m_agc_push_cb(m_agc); } -void hc55516_device::clock_w(int state) +//------------------------------------------------- +// sound_stream_update_legacy - handle a stream update +//------------------------------------------------- + +void hc55516_device::sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs) { - uint8_t clock_state = state ? true : false; + auto &buffer = outputs[0]; - /* only makes sense for setups with a software driven clock */ - assert(!is_external_oscillator()); +/* + if (!is_external_oscillator()) + { + // 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 += buffer.samples(); + if (m_samples_generated > SAMPLE_RATE / 32) + { + m_samples_generated = SAMPLE_RATE; + m_next_sample = 0; + } + } +*/ - /* speech clock changing? */ - if (is_active_clock_transition(clock_state)) + if (is_external_oscillator()) { - /* update the output buffer before changing the registers */ - m_channel->update(); + /* external oscillator */ + for (int i = 0; i < buffer.samples(); i++) + { + buffer.put_int(i, m_next_sample, 32768); - /* clear the update count */ - m_update_count = 0; + m_samples_generated++; - process_digit(); + 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; + } } - /* update the clock */ - m_last_clock_state = clock_state; + /* software driven clock */ + else + for (int i = 0; i < buffer.samples(); i++) + buffer.put_int(i, m_next_sample, 32768); } -void hc55516_device::digit_w(int digit) + +//######################################### +// 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) { - if (is_external_oscillator()) - { - m_channel->update(); - m_new_digit = digit & 1; - } - else - m_digit = digit & 1; } +//------------------------------------------------- +// device_reset - device-specific reset +//------------------------------------------------- -int hc55516_device::clock_state_r() +void hc55532_device::device_reset() { - /* only makes sense for setups with an external oscillator */ - assert(is_external_oscillator()); + 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 +} - m_channel->update(); - return current_clock_state(); + +//########################################## +// 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_d(0.0) + , m_intfilter_d(0.0) +{ +} //------------------------------------------------- -// sound_stream_update_legacy - handle a stream update +// device_start - device-specific startup //------------------------------------------------- -void hc55516_device::sound_stream_update_legacy(sound_stream &stream, stream_sample_t const * const *inputs, stream_sample_t * const *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_d)); + save_item(NAME(m_intfilter_d)); +} - /* zero-length? bail */ - if (samples == 0) - return; +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); + + /* move the estimator up or down a step based on the bit */ + if (!bit) + m_intfilter_d += m_sylfilter_d; + else + m_intfilter_d -= m_sylfilter_d; + + /* simulate leakage */ + m_intfilter_d *= 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_d = FILTER_MAX - ((FILTER_MAX - m_sylfilter_d) * m_charge); + + if (m_sylfilter_d > FILTER_MAX) + m_sylfilter_d = FILTER_MAX; + } + else + { + m_sylfilter_d *= m_decay; + + if (m_sylfilter_d < FILTER_MIN) + m_sylfilter_d = FILTER_MIN; + } + + /* compute the sample as a 32-bit word */ + m_next_sample = m_intfilter_d * SAMPLE_GAIN; + } +} + +void mc3417_device::sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs) +{ + auto &buffer = outputs[0]; 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 += buffer.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; + stream_buffer::sample_t sample = m_curr_sample; + stream_buffer::sample_t slope = (m_next_sample - sample) / buffer.samples(); m_curr_sample = m_next_sample; if (is_external_oscillator()) { /* external oscillator */ - for (i = 0; i < samples; i++, sample += slope) + for (int i = 0; i < buffer.samples(); i++, sample += slope) { - uint8_t clock_state; - - *buffer++ = sample; + buffer.put(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)) + 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; @@ -297,16 +547,18 @@ void hc55516_device::sound_stream_update_legacy(sound_stream &stream, stream_sam /* software driven clock */ else - for (i = 0; i < samples; i++, sample += slope) - *buffer++ = sample; + for (int i = 0; i < buffer.samples(); i++, sample += slope) + buffer.put(i, sample); } -void mc3417_device::sound_stream_update_legacy(sound_stream &stream, stream_sample_t const * const *inputs, stream_sample_t * const *outputs, int samples) -{ - hc55516_device::sound_stream_update_legacy(stream, inputs, outputs, samples); -} -void mc3418_device::sound_stream_update_legacy(sound_stream &stream, stream_sample_t const * const *inputs, stream_sample_t * const *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_legacy(stream, inputs, outputs, samples); } |