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