From 7c19aac60e12d6f5ea301bdb34d7826a01e0b06f Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Sun, 8 Nov 2015 12:56:12 +0100 Subject: Rename *.c -> *.cpp in our source (nw) --- src/lib/formats/cbm_tap.cpp | 373 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 373 insertions(+) create mode 100644 src/lib/formats/cbm_tap.cpp (limited to 'src/lib/formats/cbm_tap.cpp') diff --git a/src/lib/formats/cbm_tap.cpp b/src/lib/formats/cbm_tap.cpp new file mode 100644 index 00000000000..23b8a8846ed --- /dev/null +++ b/src/lib/formats/cbm_tap.cpp @@ -0,0 +1,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 + +#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 -- cgit v1.2.3-70-g09d2