summaryrefslogtreecommitdiffstatshomepage
path: root/trunk/src/lib/formats/cbm_tap.c
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/src/lib/formats/cbm_tap.c')
-rw-r--r--trunk/src/lib/formats/cbm_tap.c369
1 files changed, 369 insertions, 0 deletions
diff --git a/trunk/src/lib/formats/cbm_tap.c b/trunk/src/lib/formats/cbm_tap.c
new file mode 100644
index 00000000000..847934de6c8
--- /dev/null
+++ b/trunk/src/lib/formats/cbm_tap.c
@@ -0,0 +1,369 @@
+/*
+
+ 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 "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