2017-07-17 01:33:11 +00:00
|
|
|
//
|
|
|
|
// TZX.cpp
|
|
|
|
// Clock Signal
|
|
|
|
//
|
|
|
|
// Created by Thomas Harte on 16/07/2017.
|
|
|
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
#include "TZX.hpp"
|
|
|
|
|
|
|
|
using namespace Storage::Tape;
|
|
|
|
|
2017-07-17 02:40:38 +00:00
|
|
|
namespace {
|
|
|
|
const unsigned int StandardTZXClock = 3500000;
|
2017-07-21 22:21:12 +00:00
|
|
|
const unsigned int TZXClockMSMultiplier = 3500;
|
2017-07-17 02:40:38 +00:00
|
|
|
}
|
|
|
|
|
2017-07-17 01:33:11 +00:00
|
|
|
TZX::TZX(const char *file_name) :
|
|
|
|
Storage::FileHolder(file_name),
|
2017-07-21 22:21:12 +00:00
|
|
|
current_level_(false) {
|
2017-07-17 01:33:11 +00:00
|
|
|
|
|
|
|
// Check for signature followed by a 0x1a
|
|
|
|
char identifier[7];
|
|
|
|
char signature[] = "ZXTape!";
|
|
|
|
fread(identifier, 1, strlen(signature), file_);
|
|
|
|
if(memcmp(identifier, signature, strlen(signature))) throw ErrorNotTZX;
|
|
|
|
if(fgetc(file_) != 0x1a) throw ErrorNotTZX;
|
|
|
|
|
|
|
|
// Get version number
|
|
|
|
uint8_t major_version = (uint8_t)fgetc(file_);
|
|
|
|
uint8_t minor_version = (uint8_t)fgetc(file_);
|
|
|
|
|
|
|
|
// Reject if an incompatible version
|
|
|
|
if(major_version != 1 || minor_version > 20) throw ErrorNotTZX;
|
2017-07-20 01:28:33 +00:00
|
|
|
|
|
|
|
virtual_reset();
|
2017-07-17 01:33:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void TZX::virtual_reset() {
|
2017-07-17 02:06:56 +00:00
|
|
|
clear();
|
2017-07-21 22:21:12 +00:00
|
|
|
set_is_at_end(false);
|
|
|
|
fseek(file_, 0x0a, SEEK_SET);
|
2017-07-20 01:28:33 +00:00
|
|
|
|
|
|
|
// This is a workaround for arguably dodgy ZX80/ZX81 TZXs; they launch straight
|
|
|
|
// into data but both machines require a gap before data begins. So impose
|
|
|
|
// an initial gap, in the form of a very long wave.
|
2017-07-21 22:21:12 +00:00
|
|
|
current_level_ = false;
|
|
|
|
post_gap(500);
|
2017-07-17 01:33:11 +00:00
|
|
|
}
|
|
|
|
|
2017-07-17 02:06:56 +00:00
|
|
|
void TZX::get_next_pulses() {
|
2017-07-17 02:40:38 +00:00
|
|
|
while(empty()) {
|
|
|
|
uint8_t chunk_id = (uint8_t)fgetc(file_);
|
|
|
|
if(feof(file_)) {
|
|
|
|
set_is_at_end(true);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch(chunk_id) {
|
2017-07-17 23:38:15 +00:00
|
|
|
case 0x10: get_standard_speed_data_block(); break;
|
|
|
|
case 0x11: get_turbo_speed_data_block(); break;
|
|
|
|
case 0x12: get_pure_tone_data_block(); break;
|
|
|
|
case 0x13: get_pulse_sequence(); break;
|
|
|
|
case 0x19: get_generalised_data_block(); break;
|
2017-07-17 02:40:38 +00:00
|
|
|
|
|
|
|
case 0x30: {
|
|
|
|
// Text description. Ripe for ignoring.
|
|
|
|
int length = fgetc(file_);
|
|
|
|
fseek(file_, length, SEEK_CUR);
|
|
|
|
} break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
// In TZX each chunk has a different way of stating or implying its length,
|
|
|
|
// so there is no route past an unimplemented chunk.
|
|
|
|
printf("!!Unknown %02x!!", chunk_id);
|
|
|
|
set_is_at_end(true);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TZX::get_generalised_data_block() {
|
|
|
|
uint32_t block_length = fgetc32le();
|
2017-07-17 11:35:53 +00:00
|
|
|
long endpoint = ftell(file_) + (long)block_length;
|
2017-07-17 02:40:38 +00:00
|
|
|
uint16_t pause_after_block = fgetc16le();
|
|
|
|
|
|
|
|
uint32_t total_pilot_symbols = fgetc32le();
|
|
|
|
uint8_t maximum_pulses_per_pilot_symbol = (uint8_t)fgetc(file_);
|
|
|
|
uint8_t symbols_in_pilot_table = (uint8_t)fgetc(file_);
|
|
|
|
|
|
|
|
uint32_t total_data_symbols = fgetc32le();
|
|
|
|
uint8_t maximum_pulses_per_data_symbol = (uint8_t)fgetc(file_);
|
|
|
|
uint8_t symbols_in_data_table = (uint8_t)fgetc(file_);
|
|
|
|
|
2017-07-17 11:34:10 +00:00
|
|
|
get_generalised_segment(total_pilot_symbols, maximum_pulses_per_pilot_symbol, symbols_in_pilot_table, false);
|
|
|
|
get_generalised_segment(total_data_symbols, maximum_pulses_per_data_symbol, symbols_in_data_table, true);
|
2017-07-21 22:21:12 +00:00
|
|
|
post_gap(pause_after_block);
|
2017-07-17 11:35:53 +00:00
|
|
|
|
|
|
|
// This should be unnecessary, but intends to preserve sanity.
|
|
|
|
fseek(file_, endpoint, SEEK_SET);
|
2017-07-17 02:40:38 +00:00
|
|
|
}
|
|
|
|
|
2017-07-17 11:34:10 +00:00
|
|
|
void TZX::get_generalised_segment(uint32_t output_symbols, uint8_t max_pulses_per_symbol, uint8_t number_of_symbols, bool is_data) {
|
2017-07-17 02:40:38 +00:00
|
|
|
if(!output_symbols) return;
|
|
|
|
|
|
|
|
// Construct the symbol table.
|
|
|
|
struct Symbol {
|
|
|
|
uint8_t flags;
|
|
|
|
std::vector<uint16_t> pulse_lengths;
|
|
|
|
};
|
|
|
|
std::vector<Symbol> symbol_table;
|
|
|
|
for(int c = 0; c < number_of_symbols; c++) {
|
|
|
|
Symbol symbol;
|
|
|
|
symbol.flags = (uint8_t)fgetc(file_);
|
|
|
|
for(int ic = 0; ic < max_pulses_per_symbol; ic++) {
|
|
|
|
symbol.pulse_lengths.push_back(fgetc16le());
|
|
|
|
}
|
2017-07-17 11:34:10 +00:00
|
|
|
symbol_table.push_back(symbol);
|
2017-07-17 02:40:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Hence produce the output.
|
2017-07-17 11:34:10 +00:00
|
|
|
BitStream stream(file_, false);
|
|
|
|
int base = 2;
|
|
|
|
int bits = 1;
|
|
|
|
while(base < number_of_symbols) {
|
|
|
|
base <<= 1;
|
|
|
|
bits++;
|
|
|
|
}
|
2017-07-22 01:23:34 +00:00
|
|
|
for(size_t c = 0; c < output_symbols; c++) {
|
2017-07-17 11:34:10 +00:00
|
|
|
uint8_t symbol_value;
|
|
|
|
int count;
|
|
|
|
if(is_data) {
|
|
|
|
symbol_value = stream.get_bits(bits);
|
|
|
|
count = 1;
|
|
|
|
} else {
|
|
|
|
symbol_value = (uint8_t)fgetc(file_);
|
|
|
|
count = fgetc16le();
|
2017-07-17 02:40:38 +00:00
|
|
|
}
|
2017-07-17 11:34:10 +00:00
|
|
|
if(symbol_value > number_of_symbols) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
Symbol &symbol = symbol_table[symbol_value];
|
2017-07-17 02:40:38 +00:00
|
|
|
|
2017-07-17 11:34:10 +00:00
|
|
|
while(count--) {
|
|
|
|
// Mutate initial output level.
|
|
|
|
switch(symbol.flags & 3) {
|
|
|
|
case 0: break;
|
2017-07-21 22:21:12 +00:00
|
|
|
case 1: current_level_ ^= true; break;
|
|
|
|
case 2: current_level_ = true; break;
|
|
|
|
case 3: current_level_ = false; break;
|
2017-07-17 11:34:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Output waves.
|
|
|
|
for(auto length : symbol.pulse_lengths) {
|
|
|
|
if(!length) break;
|
2017-07-17 23:38:15 +00:00
|
|
|
post_pulse(length);
|
2017-07-17 11:34:10 +00:00
|
|
|
}
|
2017-07-17 02:40:38 +00:00
|
|
|
}
|
|
|
|
}
|
2017-07-17 01:33:11 +00:00
|
|
|
}
|
2017-07-17 23:38:15 +00:00
|
|
|
|
|
|
|
void TZX::get_standard_speed_data_block() {
|
|
|
|
__unused uint16_t pause_after_block = fgetc16le();
|
|
|
|
uint16_t data_length = fgetc16le();
|
|
|
|
if(!data_length) return;
|
|
|
|
|
|
|
|
uint8_t first_byte = (uint8_t)fgetc(file_);
|
|
|
|
__unused int pilot_tone_pulses = (first_byte < 128) ? 8063 : 3223;
|
|
|
|
ungetc(first_byte, file_);
|
|
|
|
|
|
|
|
// TODO: output pilot_tone_pulses pulses
|
|
|
|
// TODO: output data_length bytes, in the Spectrum encoding
|
|
|
|
fseek(file_, data_length, SEEK_CUR);
|
|
|
|
// TODO: output a pause of length paused_after_block ms
|
|
|
|
}
|
|
|
|
|
|
|
|
void TZX::get_turbo_speed_data_block() {
|
|
|
|
__unused uint16_t length_of_pilot_pulse = fgetc16le();
|
|
|
|
__unused uint16_t length_of_sync_first_pulse = fgetc16le();
|
|
|
|
__unused uint16_t length_of_sync_second_pulse = fgetc16le();
|
|
|
|
__unused uint16_t length_of_zero_bit_pulse = fgetc16le();
|
|
|
|
__unused uint16_t length_of_one_bit_pulse = fgetc16le();
|
|
|
|
__unused uint16_t length_of_pilot_tone = fgetc16le();
|
|
|
|
__unused uint8_t number_of_bits_in_final_byte = (uint8_t)fgetc(file_);
|
|
|
|
__unused uint16_t pause_after_block = fgetc16le();
|
|
|
|
uint16_t data_length = fgetc16le();
|
|
|
|
|
|
|
|
// TODO output as described
|
|
|
|
fseek(file_, data_length, SEEK_CUR);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TZX::get_pure_tone_data_block() {
|
|
|
|
__unused uint16_t length_of_pulse = fgetc16le();
|
|
|
|
__unused uint16_t nunber_of_pulses = fgetc16le();
|
|
|
|
|
|
|
|
while(nunber_of_pulses--) post_pulse(length_of_pulse);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TZX::get_pulse_sequence() {
|
|
|
|
uint8_t number_of_pulses = (uint8_t)fgetc(file_);
|
|
|
|
while(number_of_pulses--) {
|
|
|
|
post_pulse(fgetc16le());
|
|
|
|
}
|
|
|
|
}
|
2017-07-21 22:21:12 +00:00
|
|
|
|
|
|
|
#pragma mark - Output
|
|
|
|
|
|
|
|
void TZX::post_pulse(unsigned int length) {
|
|
|
|
post_pulse(Storage::Time(length, StandardTZXClock));
|
|
|
|
}
|
|
|
|
|
|
|
|
void TZX::post_gap(unsigned int milliseconds) {
|
|
|
|
if(!milliseconds) return;
|
|
|
|
if(milliseconds > 1 && !current_level_) {
|
|
|
|
post_pulse(Storage::Time(TZXClockMSMultiplier, StandardTZXClock));
|
|
|
|
post_pulse(Storage::Time((milliseconds - 1u)*TZXClockMSMultiplier, StandardTZXClock));
|
|
|
|
} else {
|
|
|
|
post_pulse(Storage::Time(milliseconds*TZXClockMSMultiplier, StandardTZXClock));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TZX::post_pulse(const Storage::Time &time) {
|
|
|
|
emplace_back(current_level_ ? Tape::Pulse::High : Tape::Pulse::Low, time);
|
|
|
|
current_level_ ^= true;
|
|
|
|
}
|