1 // license:BSD-3-Clause
2 // copyright-holders:Erwin Jansen
3 /**********************************************************************
4 
5     Philips P2000T Mini Digital Cassette Recorder  emulation
6 
7 **********************************************************************/
8 
9 #include "emu.h"
10 #include "p2000t_mdcr.h"
11 #include "formats/p2000t_cas.h"
12 
13 DEFINE_DEVICE_TYPE(MDCR, mdcr_device, "mdcr", "Philips Mini DCR")
14 
READ_LINE_MEMBER(mdcr_device::rdc)15 READ_LINE_MEMBER(mdcr_device::rdc)
16 {
17 	// According to mdcr spec there is cross talk on the wires when writing,
18 	// hence the clock signal is always false when writing.
19 	if (m_recording)
20 		return false;
21 
22 	return m_fwd ? m_rdc : m_rda;
23 }
24 
READ_LINE_MEMBER(mdcr_device::rda)25 READ_LINE_MEMBER(mdcr_device::rda)
26 {
27 	return m_fwd ? m_rda : m_rdc;
28 }
29 
READ_LINE_MEMBER(mdcr_device::bet)30 READ_LINE_MEMBER(mdcr_device::bet)
31 {
32 	return tape_start_or_end();
33 }
34 
READ_LINE_MEMBER(mdcr_device::cip)35 READ_LINE_MEMBER(mdcr_device::cip)
36 {
37 	return m_cassette->get_image() != nullptr;
38 }
39 
READ_LINE_MEMBER(mdcr_device::wen)40 READ_LINE_MEMBER(mdcr_device::wen)
41 {
42 	return m_cassette->get_image() != nullptr && m_cassette->is_writeable();
43 }
44 
WRITE_LINE_MEMBER(mdcr_device::rev)45 WRITE_LINE_MEMBER(mdcr_device::rev)
46 {
47 	m_rev = state;
48 	if (m_rev)
49 	{
50 		rewind();
51 	}
52 
53 	if (!m_rev && !m_fwd)
54 	{
55 		stop();
56 	}
57 }
58 
WRITE_LINE_MEMBER(mdcr_device::fwd)59 WRITE_LINE_MEMBER(mdcr_device::fwd)
60 {
61 	m_fwd = state;
62 	if (m_fwd)
63 	{
64 		forward();
65 	}
66 
67 	if (!m_rev && !m_fwd)
68 	{
69 		stop();
70 	}
71 }
72 
WRITE_LINE_MEMBER(mdcr_device::wda)73 WRITE_LINE_MEMBER(mdcr_device::wda)
74 {
75 	m_wda = state;
76 }
77 
WRITE_LINE_MEMBER(mdcr_device::wdc)78 WRITE_LINE_MEMBER(mdcr_device::wdc)
79 {
80 	if (state)
81 	{
82 		write_bit(m_wda);
83 	};
84 }
85 
device_add_mconfig(machine_config & config)86 void mdcr_device::device_add_mconfig(machine_config &config)
87 {
88 	CASSETTE(config, m_cassette);
89 	m_cassette->set_default_state(CASSETTE_STOPPED | CASSETTE_MOTOR_DISABLED |
90 								  CASSETTE_SPEAKER_MUTED);
91 	m_cassette->set_interface("p2000_cass");
92 	m_cassette->set_formats(p2000t_cassette_formats);
93 }
94 
mdcr_device(machine_config const & mconfig,char const * tag,device_t * owner,uint32_t clock)95 mdcr_device::mdcr_device(machine_config const &mconfig, char const *tag, device_t *owner, uint32_t clock)
96 : device_t(mconfig, MDCR, tag, owner, clock)
97 , m_cassette(*this, "cassette")
98 , m_read_timer(nullptr)
99 {
100 }
101 
device_start()102 void mdcr_device::device_start()
103 {
104 	m_read_timer = timer_alloc();
105 	m_read_timer->adjust(attotime::from_hz(44100), 0, attotime::from_hz(44100));
106 
107 	save_item(NAME(m_fwd));
108 	save_item(NAME(m_rev));
109 	save_item(NAME(m_rdc));
110 	save_item(NAME(m_rda));
111 	save_item(NAME(m_wda));
112 	save_item(NAME(m_recording));
113 	save_item(NAME(m_fwd_pulse_time));
114 	save_item(NAME(m_last_tape_time));
115 	save_item(NAME(m_save_tape_time));
116 	// Phase decoder
117 	save_item(STRUCT_MEMBER(m_phase_decoder, m_last_signal));
118 	save_item(STRUCT_MEMBER(m_phase_decoder, m_needs_sync));
119 	save_item(STRUCT_MEMBER(m_phase_decoder, m_bit_queue));
120 	save_item(STRUCT_MEMBER(m_phase_decoder, m_bit_place));
121 	save_item(STRUCT_MEMBER(m_phase_decoder, m_current_clock));
122 	save_item(STRUCT_MEMBER(m_phase_decoder, m_clock_period));
123 }
124 
device_pre_save()125 void mdcr_device::device_pre_save()
126 {
127 	m_save_tape_time = m_cassette->get_position();
128 }
129 
device_post_load()130 void mdcr_device::device_post_load()
131 {
132 	m_cassette->seek(m_save_tape_time, SEEK_SET);
133 }
134 
device_timer(emu_timer & timer,device_timer_id id,int param,void * ptr)135 void mdcr_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
136 {
137 	if (!m_recording && m_cassette->motor_on())
138 	{
139 		// Account for moving backwards.
140 		auto delay = std::abs(m_cassette->get_position() - m_last_tape_time);
141 
142 		// Decode the signal using the fake phase decode circuit
143 		bool newBit = m_phase_decoder.signal((m_cassette->input() > +0.04), delay);
144 		if (newBit)
145 		{
146 			// Flip rdc
147 			m_rdc = !m_rdc;
148 			m_rda = m_phase_decoder.pull_bit();
149 		}
150 	}
151 	m_last_tape_time = m_cassette->get_position();
152 }
153 
write_bit(bool bit)154 void mdcr_device::write_bit(bool bit)
155 {
156 	m_recording = true;
157 	m_cassette->change_state(CASSETTE_RECORD, CASSETTE_MASK_UISTATE);
158 	m_cassette->output(bit ? +1.0 : -1.0);
159 	m_phase_decoder.reset();
160 }
161 
rewind()162 void mdcr_device::rewind()
163 {
164 	m_fwd       = false;
165 	m_recording = false;
166 	m_cassette->set_motor(true);
167 	m_cassette->change_state(CASSETTE_PLAY, CASSETTE_MASK_UISTATE);
168 	m_cassette->go_reverse();
169 }
170 
forward()171 void mdcr_device::forward()
172 {
173 	// A pulse of 1us < T < 20 usec should reset the phase decoder.
174 	// See mdcr spec for details.
175 	constexpr double RESET_PULSE_TIMING = 2.00e-05;
176 	auto now                            = machine().time().as_double();
177 	auto pulse_delay                    = now - m_fwd_pulse_time;
178 	m_fwd_pulse_time                    = now;
179 
180 	if (pulse_delay < RESET_PULSE_TIMING)
181 	{
182 		m_phase_decoder.reset();
183 	}
184 
185 	m_fwd = true;
186 	m_cassette->set_motor(true);
187 	m_cassette->change_state(m_recording ? CASSETTE_RECORD : CASSETTE_PLAY,
188 	CASSETTE_MASK_UISTATE);
189 	m_cassette->go_forward();
190 }
191 
stop()192 void mdcr_device::stop()
193 {
194 	m_cassette->change_state(CASSETTE_PLAY, CASSETTE_MASK_UISTATE);
195 	m_cassette->set_motor(false);
196 }
197 
tape_start_or_end()198 bool mdcr_device::tape_start_or_end()
199 {
200 	auto pos = m_cassette->get_position();
201 	auto bet = m_cassette->motor_on() &&
202 		   (pos <= 0 || pos >= m_cassette->get_length());
203 
204 	// Reset phase decoder at tape start/end.
205 	if (bet)
206 		m_phase_decoder.reset();
207 
208 	return bet;
209 }
210 
p2000_mdcr_devices(device_slot_interface & device)211 void p2000_mdcr_devices(device_slot_interface &device)
212 {
213 	device.option_add("mdcr", MDCR);
214 }
215 
216 //
217 // phase_decoder
218 //
219 
phase_decoder(double tolerance)220 mdcr_device::phase_decoder::phase_decoder(double tolerance)
221 : m_tolerance(tolerance)
222 {
223 	reset();
224 }
225 
pull_bit()226 bool mdcr_device::phase_decoder::pull_bit()
227 {
228 	if (m_bit_place == 0)
229 		return false;
230 	auto res = BIT(m_bit_queue, 0);
231 	m_bit_place--;
232 	m_bit_queue >>= 1;
233 	return res;
234 }
235 
signal(bool state,double delay)236 bool mdcr_device::phase_decoder::signal(bool state, double delay)
237 {
238 	m_current_clock += delay;
239 	if (state == m_last_signal)
240 	{
241 		if (m_needs_sync == 0 && m_current_clock > m_clock_period &&
242 			!within_tolerance(m_current_clock, m_clock_period))
243 		{
244 			// We might be at the last bit in a sequence, meaning we
245 			// are only getting the reference signal for a while.
246 			// so we produce one last clock signal.
247 			reset();
248 			return true;
249 		}
250 		return false;
251 	}
252 
253 	// A transition happened!
254 	m_last_signal = state;
255 	if (m_needs_sync > 0)
256 	{
257 		// We have not yet determined our clock period.
258 		return sync_signal(state);
259 	}
260 
261 	// We are within bounds of the current clock
262 	if (within_tolerance(m_current_clock, m_clock_period))
263 	{
264 		add_bit(state);
265 		return true;
266 	};
267 
268 	// We went out of sync, our clock is wayyy out of bounds.
269 	if (m_current_clock > m_clock_period)
270 		reset();
271 
272 	// We are likely halfway in our clock signal..
273 	return false;
274 };
275 
reset()276 void mdcr_device::phase_decoder::reset()
277 {
278 	m_last_signal   = false;
279 	m_current_clock = {};
280 	m_clock_period  = {};
281 	m_needs_sync    = SYNCBITS;
282 }
283 
add_bit(bool bit)284 void mdcr_device::phase_decoder::add_bit(bool bit)
285 {
286 	if (bit)
287 		m_bit_queue |= bit << m_bit_place;
288 	else
289 		m_bit_queue &= ~(bit << m_bit_place);
290 
291 	if (m_bit_place <= QUEUE_DELAY)
292 		m_bit_place++;
293 
294 	m_current_clock = {};
295 }
296 
sync_signal(bool state)297 bool mdcr_device::phase_decoder::sync_signal(bool state)
298 {
299 	m_needs_sync--;
300 	if (m_needs_sync == SYNCBITS - 1)
301 	{
302 		// We can only synchronize when we go up
303 		// on the first bit.
304 		if (state)
305 			add_bit(true);
306 		return false;
307 	}
308 	if (m_clock_period != 0 && !within_tolerance(m_current_clock, m_clock_period))
309 	{
310 		// Clock is way off!
311 		reset();
312 		return false;
313 	}
314 
315 	// We've derived a clock period, we will use the average.
316 	auto div       = SYNCBITS - m_needs_sync - 1;
317 	m_clock_period = ((div - 1) * m_clock_period + m_current_clock) / div;
318 	add_bit(state);
319 	return true;
320 }
321 
322 // y * (1 - tolerance) < x < y * (1 + tolerance)
within_tolerance(double x,double y)323 bool mdcr_device::phase_decoder::within_tolerance(double x, double y)
324 {
325 	assert(m_tolerance > 0 && m_tolerance < 1);
326 	return (y * (1 - m_tolerance)) < x && x < (y * (1 + m_tolerance));
327 }
328