diff options
Diffstat (limited to 'src/devices/machine/mdcr.cpp')
-rw-r--r-- | src/devices/machine/mdcr.cpp | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/src/devices/machine/mdcr.cpp b/src/devices/machine/mdcr.cpp new file mode 100644 index 00000000000..a4b881a9bd4 --- /dev/null +++ b/src/devices/machine/mdcr.cpp @@ -0,0 +1,326 @@ +// license:BSD-3-Clause +// copyright-holders:Erwin Jansen +/********************************************************************** + + Philips Mini Digital Cassette Recorder emulation + +**********************************************************************/ + +#include "emu.h" +#include "mdcr.h" +#include "formats/p2000t_cas.h" + +DEFINE_DEVICE_TYPE(MDCR, mdcr_device, "mdcr", "Philips Mini-DCR") + +int mdcr_device::rdc() +{ + // According to mdcr spec there is cross talk on the wires when writing, + // hence the clock signal is always false when writing. + if (m_recording) + return false; + + return m_fwd ? m_rdc : m_rda; +} + +int mdcr_device::rda() +{ + return m_fwd ? m_rda : m_rdc; +} + +int mdcr_device::bet() +{ + return tape_start_or_end(); +} + +int mdcr_device::cip() +{ + return m_cassette->get_image() != nullptr; +} + +int mdcr_device::wen() +{ + return m_cassette->get_image() != nullptr && m_cassette->is_writeable(); +} + +void mdcr_device::rev(int state) +{ + m_rev = state; + if (m_rev) + { + rewind(); + } + + if (!m_rev && !m_fwd) + { + stop(); + } +} + +void mdcr_device::fwd(int state) +{ + m_fwd = state; + if (m_fwd) + { + forward(); + } + + if (!m_rev && !m_fwd) + { + stop(); + } +} + +void mdcr_device::wda(int state) +{ + m_wda = state; +} + +void mdcr_device::wdc(int state) +{ + if (state) + { + write_bit(m_wda); + }; +} + +void mdcr_device::device_add_mconfig(machine_config &config) +{ + CASSETTE(config, m_cassette); + m_cassette->set_default_state(CASSETTE_STOPPED | CASSETTE_MOTOR_DISABLED | CASSETTE_SPEAKER_MUTED); + m_cassette->set_interface("p2000_cass"); + m_cassette->set_formats(p2000t_cassette_formats); +} + +mdcr_device::mdcr_device(machine_config const &mconfig, char const *tag, device_t *owner, uint32_t clock) + : device_t(mconfig, MDCR, tag, owner, clock) + , m_rdc_cb(*this) + , m_cassette(*this, "cassette") + , m_read_timer(nullptr) +{ +} + +void mdcr_device::device_start() +{ + m_read_timer = timer_alloc(FUNC(mdcr_device::read_timer_tick), this); + m_read_timer->adjust(attotime::from_hz(44100), 0, attotime::from_hz(44100)); + + save_item(NAME(m_fwd)); + save_item(NAME(m_rev)); + save_item(NAME(m_rdc)); + save_item(NAME(m_rda)); + save_item(NAME(m_wda)); + save_item(NAME(m_recording)); + save_item(NAME(m_fwd_pulse_time)); + save_item(NAME(m_last_tape_time)); + save_item(NAME(m_save_tape_time)); + // Phase decoder + save_item(STRUCT_MEMBER(m_phase_decoder, m_last_signal)); + save_item(STRUCT_MEMBER(m_phase_decoder, m_needs_sync)); + save_item(STRUCT_MEMBER(m_phase_decoder, m_bit_queue)); + save_item(STRUCT_MEMBER(m_phase_decoder, m_bit_place)); + save_item(STRUCT_MEMBER(m_phase_decoder, m_current_clock)); + save_item(STRUCT_MEMBER(m_phase_decoder, m_clock_period)); +} + +void mdcr_device::device_pre_save() +{ + m_save_tape_time = m_cassette->get_position(); +} + +void mdcr_device::device_post_load() +{ + m_cassette->seek(m_save_tape_time, SEEK_SET); +} + +TIMER_CALLBACK_MEMBER(mdcr_device::read_timer_tick) +{ + if (!m_recording && m_cassette->motor_on()) + { + // Account for moving backwards. + auto delay = std::abs(m_cassette->get_position() - m_last_tape_time); + + // Decode the signal using the fake phase decode circuit + bool newBit = m_phase_decoder.signal((m_cassette->input() > +0.04), delay); + if (newBit) + { + // Flip rdc + m_rdc = !m_rdc; + m_rda = m_phase_decoder.pull_bit(); + } + } + m_rdc_cb(m_rdc); + m_last_tape_time = m_cassette->get_position(); +} + +void mdcr_device::write_bit(bool bit) +{ + m_recording = true; + m_cassette->change_state(CASSETTE_RECORD, CASSETTE_MASK_UISTATE); + m_cassette->output(bit ? +1.0 : -1.0); + m_phase_decoder.reset(); +} + +void mdcr_device::rewind() +{ + m_fwd = false; + m_recording = false; + m_cassette->set_motor(true); + m_cassette->change_state(CASSETTE_PLAY, CASSETTE_MASK_UISTATE); + m_cassette->go_reverse(); +} + +void mdcr_device::forward() +{ + // A pulse of 1us < T < 20 usec should reset the phase decoder. + // See mdcr spec for details. + constexpr double RESET_PULSE_TIMING = 2.00e-05; + auto now = machine().time().as_double(); + auto pulse_delay = now - m_fwd_pulse_time; + m_fwd_pulse_time = now; + + if (pulse_delay < RESET_PULSE_TIMING) + { + m_phase_decoder.reset(); + } + + m_fwd = true; + m_cassette->set_motor(true); + m_cassette->change_state(m_recording ? CASSETTE_RECORD : CASSETTE_PLAY, CASSETTE_MASK_UISTATE); + m_cassette->go_forward(); +} + +void mdcr_device::stop() +{ + m_cassette->change_state(CASSETTE_PLAY, CASSETTE_MASK_UISTATE); + m_cassette->set_motor(false); +} + +bool mdcr_device::tape_start_or_end() +{ + auto pos = m_cassette->get_position(); + auto bet = m_cassette->motor_on() && (pos <= 0 || pos >= m_cassette->get_length()); + + // Reset phase decoder at tape start/end. + if (bet) + m_phase_decoder.reset(); + + return bet; +} + +void p2000_mdcr_devices(device_slot_interface &device) +{ + device.option_add("mdcr", MDCR); +} + +// +// phase_decoder +// + +mdcr_device::phase_decoder::phase_decoder(double tolerance) +: m_tolerance(tolerance) +{ + reset(); +} + +bool mdcr_device::phase_decoder::pull_bit() +{ + if (m_bit_place == 0) + return false; + auto res = BIT(m_bit_queue, 0); + m_bit_place--; + m_bit_queue >>= 1; + return res; +} + +bool mdcr_device::phase_decoder::signal(bool state, double delay) +{ + m_current_clock += delay; + if (state == m_last_signal) + { + if (m_needs_sync == 0 && m_current_clock > m_clock_period && + !within_tolerance(m_current_clock, m_clock_period)) + { + // We might be at the last bit in a sequence, meaning we + // are only getting the reference signal for a while. + // so we produce one last clock signal. + reset(); + return true; + } + return false; + } + + // A transition happened! + m_last_signal = state; + if (m_needs_sync > 0) + { + // We have not yet determined our clock period. + return sync_signal(state); + } + + // We are within bounds of the current clock + if (within_tolerance(m_current_clock, m_clock_period)) + { + add_bit(state); + return true; + }; + + // We went out of sync, our clock is wayyy out of bounds. + if (m_current_clock > m_clock_period) + reset(); + + // We are likely halfway in our clock signal.. + return false; +}; + +void mdcr_device::phase_decoder::reset() +{ + m_last_signal = false; + m_current_clock = {}; + m_clock_period = {}; + m_needs_sync = SYNCBITS; +} + +void mdcr_device::phase_decoder::add_bit(bool bit) +{ + if (bit) + m_bit_queue |= bit << m_bit_place; + else + m_bit_queue &= ~(bit << m_bit_place); + + if (m_bit_place <= QUEUE_DELAY) + m_bit_place++; + + m_current_clock = {}; +} + +bool mdcr_device::phase_decoder::sync_signal(bool state) +{ + m_needs_sync--; + if (m_needs_sync == SYNCBITS - 1) + { + // We can only synchronize when we go up + // on the first bit. + if (state) + add_bit(true); + return false; + } + if (m_clock_period != 0 && !within_tolerance(m_current_clock, m_clock_period)) + { + // Clock is way off! + reset(); + return false; + } + + // We've derived a clock period, we will use the average. + auto div = SYNCBITS - m_needs_sync - 1; + m_clock_period = ((div - 1) * m_clock_period + m_current_clock) / div; + add_bit(state); + return true; +} + +// y * (1 - tolerance) < x < y * (1 + tolerance) +bool mdcr_device::phase_decoder::within_tolerance(double x, double y) +{ + assert(m_tolerance > 0 && m_tolerance < 1); + return (y * (1 - m_tolerance)) < x && x < (y * (1 + m_tolerance)); +} |