summaryrefslogtreecommitdiffstatshomepage
path: root/src/mame/machine/decocass_tape.cpp
blob: 48c9fd310ee9ae1c044dc5b5c2a30076eb94c512 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
// license:GPL-2.0+
// copyright-holders:Juergen Buchmueller, David Haywood
/***********************************************************************

    DECO Cassette System machine

 ***********************************************************************/

#include "emu.h"
#include "cpu/m6502/m6502.h"
#include "cpu/mcs48/mcs48.h"
#include "machine/decocass_tape.h"

#define LOG_CASSETTE_STATE      0

/***************************************************************************
    CASSETTE DEVICE INTERFACE
***************************************************************************/

/* number of tape clock pulses per second */
#define TAPE_CLOCKRATE                  4800
#define TAPE_CLOCKS_PER_BIT             2
#define TAPE_CLOCKS_PER_BYTE            (8 * TAPE_CLOCKS_PER_BIT)
#define TAPE_MSEC_TO_CLOCKS(x)          ((x) * TAPE_CLOCKRATE / 1000)


/* Note on a tapes leader-BOT-data-EOT-trailer format:
 * A cassette has a transparent piece of tape on both ends,
 * leader and trailer. And data tapes also have BOT and EOT
 * holes, shortly before the leader and trailer.
 * The holes and clear tape are detected using a photo-resitor.
 * When rewinding, the BOT/EOT signal will show a short
 * pulse and if rewind continues a constant high signal later.
 * The specs say the holes are "> 2ms" in length.
 */

/* duration of the clear LEADER (and trailer) of the tape */
#define REGION_LEADER_START_CLOCK       0
#define REGION_LEADER_LEN_CLOCKS        TAPE_MSEC_TO_CLOCKS(1000)   /* 1s */
#define REGION_LEADER_END_CLOCK         (REGION_LEADER_START_CLOCK+REGION_LEADER_LEN_CLOCKS)

/* duration of the GAP between leader and BOT/EOT */
#define REGION_LEADER_GAP_START_CLOCK   REGION_LEADER_END_CLOCK
#define REGION_LEADER_GAP_LEN_CLOCKS    TAPE_MSEC_TO_CLOCKS(1500)   /* 1.5s */
#define REGION_LEADER_GAP_END_CLOCK     (REGION_LEADER_GAP_START_CLOCK+REGION_LEADER_GAP_LEN_CLOCKS)

/* duration of BOT/EOT holes */
#define REGION_BOT_START_CLOCK          REGION_LEADER_GAP_END_CLOCK
#define REGION_BOT_LEN_CLOCKS           TAPE_MSEC_TO_CLOCKS(2.5)    /* 0.0025s */
#define REGION_BOT_END_CLOCK            (REGION_BOT_START_CLOCK+REGION_BOT_LEN_CLOCKS)

/* gap between BOT/EOT and first/last data block */
#define REGION_BOT_GAP_START_CLOCK      REGION_BOT_END_CLOCK
#define REGION_BOT_GAP_LEN_CLOCKS       TAPE_MSEC_TO_CLOCKS(300)    /* 300ms */
#define REGION_BOT_GAP_END_CLOCK        (REGION_BOT_GAP_START_CLOCK+REGION_BOT_GAP_LEN_CLOCKS)

static UINT16 tape_crc16_byte(UINT16 crc, UINT8 data);

const device_type DECOCASS_TAPE = &device_creator<decocass_tape_device>;

decocass_tape_device::decocass_tape_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
	: device_t(mconfig, DECOCASS_TAPE, "DECO Cassette Tape", tag, owner, clock, "decocass_tape", __FILE__),
	m_tape_timer(nullptr),
	m_speed(0),
	m_region(REGION_LEADER),
	m_bitnum(0),
	m_clockpos(0),
	m_numclocks(0)
{
	for (int i = 0; i < 256; i++)
	m_crc16[i] = 0;
}

//-------------------------------------------------
//  device_config_complete - perform any
//  operations now that the configuration is
//  complete
//-------------------------------------------------

void decocass_tape_device::device_config_complete()
{
}

//-------------------------------------------------
//  device_start - device-specific startup
//-------------------------------------------------

void decocass_tape_device::device_start()
{
	int curblock, offs, numblocks;

	/* fetch the data pointer */
	m_tape_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(decocass_tape_device::tape_clock_callback), this));
	if (region() == nullptr)
		return;
	UINT8 *regionbase = region()->base();

	/* scan for the first non-empty block in the image */
	for (offs = region()->bytes() - 1; offs >= 0; offs--)
		if (regionbase[offs] != 0)
			break;
	numblocks = ((offs | 0xff) + 1) / 256;
	assert(numblocks < ARRAY_LENGTH(m_crc16));

	/* compute the total length */
	m_numclocks = REGION_BOT_GAP_END_CLOCK + numblocks * BYTE_BLOCK_TOTAL * 16 + REGION_BOT_GAP_END_CLOCK;

	/* compute CRCs for each block */
	for (curblock = 0; curblock < numblocks; curblock++)
	{
		UINT16 crc = 0;
		int testval;

		/* first CRC the 256 bytes of data */
		for (offs = 256 * curblock; offs < 256 * curblock + 256; offs++)
			crc = tape_crc16_byte(crc, regionbase[offs]);

		/* then find a pair of bytes that will bring the CRC to 0 (any better way than brute force?) */
		for (testval = 0; testval < 0x10000; testval++)
			if (tape_crc16_byte(tape_crc16_byte(crc, testval >> 8), testval) == 0)
				break;
		m_crc16[curblock] = testval;
	}

	/* register states */
	save_item(NAME(m_speed));
	save_item(NAME(m_bitnum));
	save_item(NAME(m_clockpos));
}

//-------------------------------------------------
//  device_reset - device-specific reset
//-------------------------------------------------

void decocass_tape_device::device_reset()
{
	/* turn the tape off */
	change_speed(0);
}

/*-------------------------------------------------
    tape_crc16_byte - accumulate 8 bits worth of
    CRC data
-------------------------------------------------*/

static UINT16 tape_crc16_byte(UINT16 crc, UINT8 data)
{
	int bit;

	for (bit = 0; bit < 8; bit++)
	{
		crc = (crc >> 1) | (crc << 15);
		crc ^= (data << 7) & 0x80;
		if (crc & 0x80)
			crc ^= 0x0120;
		data >>= 1;
	}
	return crc;
}

/*-------------------------------------------------
    tape_describe_state - create a string that
    describes the state of the tape
-------------------------------------------------*/

const char *decocass_tape_device::describe_state()
{
	static char buffer[40];
	char temprname[40];
	const char *rname = temprname;

	if (m_region == REGION_LEADER)
		rname = "LEAD";
	else if (m_region == REGION_LEADER_GAP)
		rname = "LGAP";
	else if (m_region == REGION_BOT)
		rname = "BOT ";
	else if (m_region == REGION_BOT_GAP)
		rname = "BGAP";
	else if (m_region == REGION_TRAILER)
		rname = "TRLR";
	else if (m_region == REGION_TRAILER_GAP)
		rname = "TGAP";
	else if (m_region == REGION_EOT)
		rname = "EOT ";
	else if (m_region == REGION_EOT_GAP)
		rname = "EGAP";
	else
	{
		char tempbname[40];
		const char *bname = tempbname;
		int clk;

		if (m_bytenum <= BYTE_PRE_GAP_33)
			sprintf(tempbname, "PR%02d", m_bytenum - BYTE_PRE_GAP_0);
		else if (m_bytenum == BYTE_LEADIN)
			bname = "LDIN";
		else if (m_bytenum == BYTE_HEADER)
			bname = "HEAD";
		else if (m_bytenum <= BYTE_DATA_255)
			sprintf(tempbname, "BY%02X", m_bytenum - BYTE_DATA_0);
		else if (m_bytenum == BYTE_CRC16_MSB)
			bname = "CRCM";
		else if (m_bytenum == BYTE_CRC16_LSB)
			bname = "CRCL";
		else if (m_bytenum == BYTE_TRAILER)
			bname = "TRLR";
		else if (m_bytenum == BYTE_LEADOUT)
			bname = "LOUT";
		else if (m_bytenum == BYTE_LONGCLOCK)
			bname = "LONG";
		else
			sprintf(tempbname, "PO%02d", m_bytenum - BYTE_POSTGAP_0);

		/* in the main data area, the clock alternates at the clock rate */
		if (m_bytenum >= BYTE_LEADIN && m_bytenum <= BYTE_LEADOUT)
			clk = ((UINT32)(m_clockpos - REGION_BOT_GAP_END_CLOCK) & 1) ? 0 : 1;
		else if (m_bytenum == BYTE_LONGCLOCK)
			clk = 1;
		else
			clk = 0;

		sprintf(temprname, "BL%02X.%4s.%d.%d", m_region - REGION_DATA_BLOCK_0, bname, m_bitnum, clk);
	}

	sprintf(buffer, "{%9d=%s}", m_clockpos, rname);
	return buffer;
}


/*-------------------------------------------------
    tape_clock_callback - called once per clock
    to increment/decrement the tape location
-------------------------------------------------*/

TIMER_CALLBACK_MEMBER( decocass_tape_device::tape_clock_callback )
{
	/* advance by one clock in the desired direction */
	if (m_speed < 0 && m_clockpos > 0)
		m_clockpos--;
	else if (m_speed > 0 && m_clockpos < m_numclocks)
		m_clockpos++;

	/* look for states before the start of data */
	if (m_clockpos < REGION_LEADER_END_CLOCK)
		m_region = REGION_LEADER;
	else if (m_clockpos < REGION_LEADER_GAP_END_CLOCK)
		m_region = REGION_LEADER_GAP;
	else if (m_clockpos < REGION_BOT_END_CLOCK)
		m_region = REGION_BOT;
	else if (m_clockpos < REGION_BOT_GAP_END_CLOCK)
		m_region = REGION_BOT_GAP;

	/* look for states after the end of data */
	else if (m_clockpos >= m_numclocks - REGION_LEADER_END_CLOCK)
		m_region = REGION_TRAILER;
	else if (m_clockpos >= m_numclocks - REGION_LEADER_GAP_END_CLOCK)
		m_region = REGION_TRAILER_GAP;
	else if (m_clockpos >= m_numclocks - REGION_BOT_END_CLOCK)
		m_region = REGION_EOT;
	else if (m_clockpos >= m_numclocks - REGION_BOT_GAP_END_CLOCK)
		m_region = REGION_EOT_GAP;

	/* everything else is data */
	else
	{
		UINT32 dataclock = m_clockpos - REGION_BOT_GAP_END_CLOCK;

		/* compute the block number */
		m_region = (tape_region)(REGION_DATA_BLOCK_0 + dataclock / (TAPE_CLOCKS_PER_BYTE * BYTE_BLOCK_TOTAL));
		dataclock -= (m_region - REGION_DATA_BLOCK_0) * TAPE_CLOCKS_PER_BYTE * BYTE_BLOCK_TOTAL;

		/* compute the byte within the block */
		m_bytenum = (tape_byte)(dataclock / TAPE_CLOCKS_PER_BYTE);
		dataclock -= m_bytenum * TAPE_CLOCKS_PER_BYTE;

		/* compute the bit within the byte */
		m_bitnum = dataclock / TAPE_CLOCKS_PER_BIT;
	}

	/* log */
	if (LOG_CASSETTE_STATE)
		describe_state();
}


/*-------------------------------------------------
    tape_get_status_bits - return the 3 status
    bits from the tape
-------------------------------------------------*/

UINT8 decocass_tape_device::get_status_bits()
{
	UINT8 tape_bits = 0;
	UINT8 *tape_data = region()->base();

	/* bit 0x20 is the BOT/EOT signal, which is also set in the leader/trailer area */
	if (m_region == REGION_LEADER || m_region == REGION_BOT || m_region == REGION_EOT || m_region == REGION_TRAILER)
		tape_bits |= 0x20;

	/* bit 0x40 is the clock, which is only valid in some areas of the data block */
	/* bit 0x80 is the data, which is only valid in some areas of the data block */
	if (m_region >= REGION_DATA_BLOCK_0 && m_region <= REGION_DATA_BLOCK_255)
	{
		int blocknum = m_region - REGION_DATA_BLOCK_0;
		UINT8 byteval = 0x00;

		/* in the main data area, the clock alternates at the clock rate */
		if (m_bytenum >= BYTE_LEADIN && m_bytenum <= BYTE_LEADOUT)
			tape_bits |= ((UINT32)(m_clockpos - REGION_BOT_GAP_END_CLOCK) & 1) ? 0x00 : 0x40;

		/* in the longclock area, the clock holds high */
		else if (m_bytenum == BYTE_LONGCLOCK)
			tape_bits |= 0x40;

		/* everywhere else, the clock holds to 0 */
		else
		{
				/* nothing */
		}

		/* lead-in and lead-out bytes are 0xAA */
		if (m_bytenum == BYTE_HEADER || m_bytenum == BYTE_TRAILER)
			byteval = 0xaa;

		/* data block bytes are data */
		else if (m_bytenum >= BYTE_DATA_0 && m_bytenum <= BYTE_DATA_255)
			byteval = tape_data[blocknum * 256 + (m_bytenum - BYTE_DATA_0)];

		/* CRC MSB */
		else if (m_bytenum == BYTE_CRC16_MSB)
			byteval = m_crc16[blocknum] >> 8;

		/* CRC LSB */
		else if (m_bytenum == BYTE_CRC16_LSB)
			byteval = m_crc16[blocknum];

		/* select the appropriate bit from the byte and move to the upper bit */
		if ((byteval >> m_bitnum) & 1)
			tape_bits |= 0x80;
	}
	return tape_bits;
}


/*-------------------------------------------------
    tape_is_present - return TRUE if the tape is
    present
-------------------------------------------------*/

UINT8 decocass_tape_device::is_present()
{
	return region() != nullptr;
}


/*-------------------------------------------------
    tape_change_speed - alter the speed of tape
    playback
-------------------------------------------------*/

void decocass_tape_device::change_speed(INT8 newspeed)
{
	attotime newperiod;
	INT8 absnewspeed;

	/* do nothing if speed has not changed */
	if (m_speed == newspeed)
		return;

	/* compute how fast to run the tape timer */
	absnewspeed = (newspeed < 0) ? -newspeed : newspeed;
	if (newspeed == 0)
		newperiod = attotime::never;
	else
		newperiod = attotime::from_hz(TAPE_CLOCKRATE * absnewspeed);

	/* set the new speed */
	m_tape_timer->adjust(newperiod, 0, newperiod);
	m_speed = newspeed;
}