// license:BSD-3-Clause // copyright-holders:Sandro Ronco /******************************************************************** Support for Videoton TVC .cas files http://tvc.homeserver.hu/html/konvertformatum.html ********************************************************************/ #include "tvc_cas.h" #include "multibyte.h" #define TVC64_BIT0_FREQ 1812 #define TVC64_BIT1_FREQ 2577 #define TVC64_PRE_FREQ 2128 #define TVC64_SYNC_FREQ 1359 #define WAVE_AMPLITUDE 0x3fffffff #define TVC64_HEADER_BYTES 0x90 #define TVC64_HEADER_BYTES 0x90 static void tvc64_emit_level(cassette_image *cass, double &time, int freq, int level) { double period = 1.0 / freq; cass->put_sample(0, time, period, level * WAVE_AMPLITUDE); time += period; } static cassette_image::error tvc64_output_bytes(cassette_image *cass, double &time, const uint8_t *buffer, int size) { while (size-- > 0) { uint8_t byte = *buffer++; for (int i=0; i<8; i++) { if ((byte>>i) & 0x01) { tvc64_emit_level(cass, time, TVC64_BIT1_FREQ*2, +1); tvc64_emit_level(cass, time, TVC64_BIT1_FREQ*2, -1); } else { tvc64_emit_level(cass, time, TVC64_BIT0_FREQ*2, +1); tvc64_emit_level(cass, time, TVC64_BIT0_FREQ*2, -1); } } } return cassette_image::error::SUCCESS; } static int tvc64_output_predata(cassette_image *cass, double &time, int number) { for (int i=0; i>8) & 0xff); if (al & 0x80) crc ^= 0x0810; crc <<= 1; if (al & 0x80) crc += 1; } } return crc; } static cassette_image::error tvc64_cassette_load(cassette_image *cassette) { uint8_t tmp_buff[512]; int buff_idx = 0; double time = 0.0; uint8_t header[TVC64_HEADER_BYTES]; cassette->image_read(header, 0, TVC64_HEADER_BYTES); uint16_t cas_size = get_u16le(&header[0x82]); // tape header tmp_buff[buff_idx++] = 0x00; tmp_buff[buff_idx++] = 0x6a; tmp_buff[buff_idx++] = 0xff; // head sector tmp_buff[buff_idx++] = 0x11; // not puffered tmp_buff[buff_idx++] = 0x00; // not write protected tmp_buff[buff_idx++] = 0x01; // 1 sector tmp_buff[buff_idx++] = 0x00; // sector number tmp_buff[buff_idx++] = 0x12; // head size tmp_buff[buff_idx++] = 0x01; // name size tmp_buff[buff_idx++] = ' '; // name tmp_buff[buff_idx++] = 0x00; tmp_buff[buff_idx++] = header[0x81]; // type tmp_buff[buff_idx++] = header[0x82]; // size LSB tmp_buff[buff_idx++] = header[0x83]; // size MSB tmp_buff[buff_idx++] = header[0x84]; // autostart // sector fill for (int i=0; i<10 ; i++) tmp_buff[buff_idx++] = 0x00; tmp_buff[buff_idx++] = 0x00; // file version tmp_buff[buff_idx++] = 0x00; // no last sector // updates the header CRC uint16_t crc = tvc64_calc_crc(tmp_buff, buff_idx); tmp_buff[buff_idx++] = crc & 0xff; tmp_buff[buff_idx++] = (crc>>8) & 0xff; // 2 sec silence tvc64_emit_level(cassette, time, 2, 0); // 10240 pre data cycles tvc64_output_predata(cassette, time, 10240); // 1 synchro cycle tvc64_emit_level(cassette, time, TVC64_SYNC_FREQ*2, +1); tvc64_emit_level(cassette, time, TVC64_SYNC_FREQ*2, -1); // header data tvc64_output_bytes(cassette, time, tmp_buff, buff_idx); // 5 post data cycles tvc64_output_predata(cassette, time, 5); // 1 sec silence tvc64_emit_level(cassette, time, 1, 0); // 5120 pre data cycles tvc64_output_predata(cassette, time, 5120); // 1 synchro cycle tvc64_emit_level(cassette, time, TVC64_SYNC_FREQ*2, +1); tvc64_emit_level(cassette, time, TVC64_SYNC_FREQ*2, -1); // first data sector contain the data header buff_idx = 0; tmp_buff[buff_idx++] = 0x00; tmp_buff[buff_idx++] = 0x6a; tmp_buff[buff_idx++] = 0x00; // data sector tmp_buff[buff_idx++] = 0x11; // not puffered tmp_buff[buff_idx++] = 0x00; // not write protected tmp_buff[buff_idx++] = (uint8_t)((cas_size / 256) + ((cas_size % 256) > 0 ? 1 : 0)); // number of sectors uint8_t sect_num = 1; int sector_num = cas_size / 256; for (int i=0; i<=sector_num; i++) { tmp_buff[buff_idx++] = sect_num++; // sector number // sector size if (i == sector_num) tmp_buff[buff_idx++] = cas_size % 256; else tmp_buff[buff_idx++] = 0x00; // sector data int sector_size = (i == sector_num) ? (cas_size % 256) : 256; cassette->image_read(&tmp_buff[buff_idx], TVC64_HEADER_BYTES + i*256, sector_size); buff_idx += sector_size; if (i == sector_num || ((i+1) == sector_num && (cas_size % 256 ) == 0)) tmp_buff[buff_idx++] = 0xff; // last sector else tmp_buff[buff_idx++] = 0x00; // no last sector // sector crc crc = tvc64_calc_crc(tmp_buff, buff_idx); tmp_buff[buff_idx++] = crc & 0xff; tmp_buff[buff_idx++] = (crc>>8) & 0xff; // output the sector tvc64_output_bytes(cassette, time, tmp_buff, buff_idx); buff_idx = 0; } // 5 post data cycles tvc64_output_predata(cassette, time, 5); // 1 sec silence tvc64_emit_level(cassette, time, 1, 0); return cassette_image::error::SUCCESS; } static cassette_image::error tvc64_cassette_identify(cassette_image *cassette, cassette_image::Options *opts) { if (cassette->image_read_byte(0) == 0x11) { opts->bits_per_sample = 16; opts->channels = 1; opts->sample_frequency = 44100; return cassette_image::error::SUCCESS; } return cassette_image::error::INVALID_IMAGE; } static const cassette_image::Format tvc64_cassette_image_format = { "cas", tvc64_cassette_identify, tvc64_cassette_load, nullptr }; CASSETTE_FORMATLIST_START(tvc64_cassette_formats) CASSETTE_FORMAT(tvc64_cassette_image_format) CASSETTE_FORMATLIST_END