summaryrefslogtreecommitdiffstatshomepage
path: root/src/lib/formats/cbm_tap.cpp
blob: 23b8a8846ed0605d9431bfb3acbb4e9aa33cc3c0 (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
// license:BSD-3-Clause
// copyright-holders:Fabio Priuli
/*

    Tape support for C16 / C64 / VIC20 TAP format


    Credits to:

        - Peter Schepers for the informations on C64 formats
        - Vice Team for the source of their very complete emulator
        - Attila G. for tap2wav (both the source and the actual tool)


    TODO:
    * to verify and fix (if needed) support for .TAP v2
    * to implement reading (and logging) the remaining part of the header
    * to verify if it is more accurate to use a different HIGH_WAVE value
      when the pulse corresponds to a 0x00 in the .TAP file
    (...)
    * (far away in the future) can this code be merged with TZX code?

*/

/* Info based on http://ist.uwaterloo.ca/~schepers/formats/TAP.TXT      */
/* Please refer to the webpage for the latest version and for a very
   complete listing of various cart types and their bankswitch tricks   */
/*

  Designed by Per Hakan Sundell (author of the CCS64 C64 emulator) in 1997,
this format attempts to duplicate the data stored on a C64  cassette  tape,
bit for bit. Since it is simply a representation of  the  raw  serial  data
from a tape, it should handle *any* custom tape loaders that exist.

  The TAP images are generally very large, being a minimum of eight  times,
and up to sixteen times as large as what a raw PRG file would be.  This  is
due to the way the data is stored, with each bit of the original  file  now
being one byte large in the TAP file. The layout is fairly simple,  with  a
small 14-byte header followed by file data.

    Bytes: $0000-000B: File signature "C64-TAPE-RAW"
                 000C: TAP version (see below for description)
                        $00 - Original layout
                         01 - Updated
            000D-000F: Future expansion
            0010-0013: File  data  size  (not  including  this  header,  in
                       LOW/HIGH format).
            0014-xxxx: File data

  In TAP version $00 files, each data byte in the file data area represents
the length of a pulse, when the C64's hardware  will  trigger  again.  This
pulse length is determined by the following formula:

    pulse length (in seconds) = (8 * data byte) / (clock cycles)

  Therefore, a data value of $2F (47 in decimal) would be:

    (47 * 8) / 985248 = .00038975 seconds.

  A data value of $00 represents an "overflow" condition, any pulse  length
which is more that 255 * 8 in length.

  The value of "clock cylces" from above  (985248)  is  based  on  the  PAL
value.  Since  this  file  format  was  developed  in  Europe,   which   is
predominantly PAL video, this is only logical.  The  NTSC  value  would  be
1022730, which is very close to  the  PAL,  and  therefore  won't  cause  a
compatability problem converting European and NTSC tapes. I would stick  to
using the PAL value just in case.


  In TAP version $01 files, the data value of  $00  has  been  re-coded  to
represent values greater than 255 * 8. When a  $00  is  encountered,  three
bytes will follow which are the actual time (in cycles) of a pulse, and the
above formula does not apply.  The  three  bytes  are  stored  in  LOW/HIGH
format.


  The actual interpretation of the serial data takes a little more work  to
explain.  The  typical  ROM  tape  loader  (and  the  turbo  loaders)  will
initialize a timer with a specified value and start it  counting  down.  If
either the tape data changes or the timer runs out, an IRQ will occur.  The
loader will determine which condition caused the  IRQ.  If  the  tape  data
changed before the timer ran out, we have a short pulse, or a "0"  bit.  If
the timer ran out first, we have a long pulse, or a  "1"  bit.  Doing  this
continuously and we decode the entire file.


[ Additional notes on v.2:

  I found no documents about this around, but it seems an expansion of the
format specifically thought for C16 tapes. In a .TAP version 2, each byte
only stores informations on half of the wave...
Unfortunately, I have no such a .tap file to test, so my implementation
below could be not working.  FP ]
*/

#include <assert.h>

#include "cbm_tap.h"


#define CBM_WAV_FREQUENCY   44100

/* Systems */
#define C64     0
#define VIC20   1
#define C16     2

/* Video standards */
#define PAL     0
#define NTSC    1

/* Frequencies in [Hz] to determine the length of each pulse */
#define C64_PAL     123156      /*  985248 / 8 */
#define C64_NTSC    127841      /* 1022727 / 8 */
#define VIC20_PAL   138551      /* 1108405 / 8 */
#define VIC20_NTSC  127841      /* 1022727 / 8 */
#define C16_PAL     110840      /*  886724 / 8 */
#define C16_NTSC    111860      /*  894886 / 8 */

#define PAUSE (CBM_WAV_FREQUENCY / 50)      /* tap2wav uses this value for 0x00 in .TAP v0, instead of 0x100 */

/* These values do not really matter, as long as the produced pulses
  go above & below 0. However, for documentation purpose it would be
  nice to find out which values were used by Commodore tapes. I was
  not able to find any reference on the subject. */
#define WAVE_HIGH       (0x5a9e >> 1)
#define WAVE_LOW        -(0x5a9e >> 1)
#define WAVE_PAUSE      0x80

#define CBM_HEADER_SIZE 20

static INT16    wave_data = 0;
static int      len;


/* This in fact gives the number of samples for half of the pulse */
INLINE int tap_data_to_samplecount(int data, int frequency)
{
//  return (int) (0.5 * (0.5 + (((double)CBM_WAV_FREQUENCY / frequency) * (double)data)));      // MESS TZX formula
	return (int) (0.5 * (((double)CBM_WAV_FREQUENCY / frequency) * (double)((data) + 0.5)));    // tap2wav formula
}

/* The version with parameters could be handy if we decide to implement a
different values for byte == 0x00 below (the WAVE_PAUSE) as tap2wav does. */
#if 0
static void toggle_wave_data(int low, int high)
{
	wave_data = (wave_data == low) ? high : low;
}
#endif

static void toggle_wave_data(void )
{
	wave_data = (wave_data == WAVE_HIGH) ? WAVE_LOW : WAVE_HIGH;
}

static void cbm_output_wave( INT16 **buffer, int length )
{
	if (buffer == NULL)
		return;

	for( ; length > 0; length-- )
	{
		**buffer = wave_data;
		*buffer = *buffer + 1;
	}
}


static int cbm_tap_do_work( INT16 **buffer, int length, const UINT8 *data )
{
	int i, j = 0;
	int size = 0;

	int version = data[0x0c];
	int system = data[0x0d];
	int video_standard = data[0x0e];
	int tap_frequency = 0;

	int byte_samples = 0;
	UINT8 over_pulse_bytes[3] = {0 , 0, 0 };
	int over_pulse_length = 0;
	/* These waveamp_* values are currently stored but not used.
	  Further investigations are needed to find real pulse amplitude
	  in Commodore tapes. Implementation here would follow */
	/* int waveamp_high, waveamp_low; */

	/* Log .TAP info but only once */
	if (!(buffer == NULL))
	{
		LOG_FORMATS("TAP version    : %d\n", version);
		LOG_FORMATS("Machine type   : %d\n", system);
		LOG_FORMATS("Video standard : %d\n", video_standard);
		LOG_FORMATS("Tape frequency : %d\n", (tap_frequency) << 3);
	}


	/* is this a supported version? */
	if ((version < 0) || (version > 2))
	{
		LOG_FORMATS("Unsupported .tap version: %d \n", version);
		return -1;
	}

	/* is the .tap file corrupted? */
	if ((data == NULL) || (length <= CBM_HEADER_SIZE))
		return -1;


	/* read the frequency from the .tap header */
	switch (system)
	{
		case VIC20:
			tap_frequency = (video_standard == NTSC) ? VIC20_NTSC : VIC20_PAL;
			break;

		case C16:
			tap_frequency = (video_standard == NTSC) ? C16_NTSC : C16_PAL;
			break;

		case C64:
		default:
			tap_frequency = (video_standard == NTSC) ? C64_NTSC : C64_PAL;
			break;
	}


	for (i = CBM_HEADER_SIZE; i < length; i++)
	{
		UINT8 byte = data[i];

		/* .TAP v0 */
		/* Here is simple:
		  if byte is != 0 -> length = byte
		  otherwise -> length =  0x100 (i.e. 0xff + 1) */
		if (!version)
		{
			if (byte != 0x00)
			{
				byte_samples = tap_data_to_samplecount(byte, tap_frequency);
				/* waveamp_high = WAVE_HIGH; */
			}
			else
			{
				byte_samples = tap_data_to_samplecount(PAUSE, tap_frequency);   // tap2wav value
//              byte_samples = tap_data_to_samplecount(0x100, tap_frequency);   // vice value
				/* waveamp_high = WAVE_PAUSE; */
			}
			/* waveamp_low = WAVE_LOW; */
		}

		/* .TAP v1 & v2 */
		/* Here is a bit more complicate:
		  if byte is != 0 -> length = byte
		  otherwise -> the length of the pulse is stored as a 24bit value in the 3 bytes after the 0.
		  See below for comments on the implementation of this mechanism */
		if (version)
		{
			if ((byte != 0x00) && !j)
			{
				byte_samples = tap_data_to_samplecount(byte, tap_frequency);
				/* waveamp_high = WAVE_HIGH; */
			}
			else
			{
				/* If we have a long pulse close to the end of the .TAP, check that bytes still
				  to be read are enough to complete it. */
				if (length - i + j >= 4)
				{
					/* Here we read the 3 following bytes, using an index j
					  j = 0 -> The 0x00 byte: we simply skip everything and go on
					  j = 1,2 -> The 1st and 2nd bytes after 0x00: we store them and go on
					  j = 3 -> The final byte of the pulse length: we store it,
					  and then we pass to finally output the wave */
					if (j > 0)
					{
						over_pulse_bytes[j-1] = byte;
						j += 1;

						if (j >= 4)
						{
							over_pulse_length = ((over_pulse_bytes[2] << 16) | (over_pulse_bytes[1] << 8) | over_pulse_bytes[0]) >> 3;
							byte_samples = tap_data_to_samplecount(over_pulse_length, tap_frequency);
							/* waveamp_high = WAVE_PAUSE; */
							j = 0;
						}
					}
					else
					{
						j += 1;
						LOG_FORMATS("Found a 00 byte close to the end of the .tap file.\n");
						LOG_FORMATS("This is not allowed by the format specs. \n");
						LOG_FORMATS("Check if your .tap file got corrupted when you created it!\n");
					}
				}
				else j = 1;
			}
			/* waveamp_low = WAVE_LOW; */
		}

		if (j == 0)
		{
			cbm_output_wave( buffer, byte_samples );
			size += byte_samples;
//          toggle_wave_data(waveamp_low, waveamp_high);
			toggle_wave_data();
			if (version < 2)
			{
				cbm_output_wave( buffer, byte_samples );
				size += byte_samples;
//              toggle_wave_data(waveamp_low, waveamp_high);
				toggle_wave_data();
			}
		}
	}

	return size;
}


static int cbm_tap_to_wav_size( const UINT8 *tapdata, int taplen )
{
	int size = cbm_tap_do_work(NULL, taplen, tapdata);
	len = taplen;

	return size;
}

static int cbm_tap_fill_wave( INT16 *buffer, int length, UINT8 *bytes )
{
	INT16 *p = buffer;

	return cbm_tap_do_work(&p, len, (const UINT8 *)bytes);
}




static const struct CassetteLegacyWaveFiller cbm_legacy_fill_wave = {
	cbm_tap_fill_wave,      /* fill_wave */
	-1,                     /* chunk_size */
	0,                      /* chunk_samples */
	cbm_tap_to_wav_size,    /* chunk_sample_calc */
	CBM_WAV_FREQUENCY,      /* sample_frequency */
	0,                      /* header_samples */
	0                       /* trailer_samples */
};


static casserr_t cbm_cassette_identify( cassette_image *cassette, struct CassetteOptions *opts )
{
	return cassette_legacy_identify( cassette, opts, &cbm_legacy_fill_wave );
}


static casserr_t cbm_cassette_load( cassette_image *cassette )
{
	return cassette_legacy_construct( cassette, &cbm_legacy_fill_wave );
}


static const struct CassetteFormat cbm_tap_cassette_format = {
	"tap",
	cbm_cassette_identify,
	cbm_cassette_load,
	NULL
};


CASSETTE_FORMATLIST_START(cbm_cassette_formats)
	CASSETTE_FORMAT(cbm_tap_cassette_format)
CASSETTE_FORMATLIST_END