1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-01-12 15:31:09 +00:00

Makes an attempt at implementing all missing TZX 1.20 blocks.

Towards that aim, simplifies CSW handling so that even regular RLE compression uses a static grab of file contents.
This commit is contained in:
Thomas Harte 2018-02-22 21:28:12 -05:00
parent d9d5ffdaa2
commit d83178f29d
6 changed files with 159 additions and 81 deletions

View File

@ -13,21 +13,21 @@
using namespace Storage::Tape;
CSW::CSW(const char *file_name) :
file_(file_name),
source_data_pointer_(0) {
if(file_.stats().st_size < 0x20) throw ErrorNotCSW;
Storage::FileHolder file(file_name);
if(file.stats().st_size < 0x20) throw ErrorNotCSW;
// Check signature.
if(!file_.check_signature("Compressed Square Wave")) {
if(!file.check_signature("Compressed Square Wave")) {
throw ErrorNotCSW;
}
// Check terminating byte.
if(file_.get8() != 0x1a) throw ErrorNotCSW;
if(file.get8() != 0x1a) throw ErrorNotCSW;
// Get version file number.
uint8_t major_version = file_.get8();
uint8_t minor_version = file_.get8();
uint8_t major_version = file.get8();
uint8_t minor_version = file.get8();
// Reject if this is an unknown version.
if(major_version > 2 || !major_version || minor_version > 1) throw ErrorNotCSW;
@ -35,41 +35,42 @@ CSW::CSW(const char *file_name) :
// The header now diverges based on version.
uint32_t number_of_waves = 0;
if(major_version == 1) {
pulse_.length.clock_rate = file_.get16le();
pulse_.length.clock_rate = file.get16le();
if(file_.get8() != 1) throw ErrorNotCSW;
if(file.get8() != 1) throw ErrorNotCSW;
compression_type_ = CompressionType::RLE;
pulse_.type = (file_.get8() & 1) ? Pulse::High : Pulse::Low;
pulse_.type = (file.get8() & 1) ? Pulse::High : Pulse::Low;
file_.seek(0x20, SEEK_SET);
file.seek(0x20, SEEK_SET);
} else {
pulse_.length.clock_rate = file_.get32le();
number_of_waves = file_.get32le();
switch(file_.get8()) {
pulse_.length.clock_rate = file.get32le();
number_of_waves = file.get32le();
switch(file.get8()) {
case 1: compression_type_ = CompressionType::RLE; break;
case 2: compression_type_ = CompressionType::ZRLE; break;
default: throw ErrorNotCSW;
}
pulse_.type = (file_.get8() & 1) ? Pulse::High : Pulse::Low;
uint8_t extension_length = file_.get8();
pulse_.type = (file.get8() & 1) ? Pulse::High : Pulse::Low;
uint8_t extension_length = file.get8();
if(file_.stats().st_size < 0x34 + extension_length) throw ErrorNotCSW;
file_.seek(0x34 + extension_length, SEEK_SET);
if(file.stats().st_size < 0x34 + extension_length) throw ErrorNotCSW;
file.seek(0x34 + extension_length, SEEK_SET);
}
// Grab all data remaining in the file.
std::vector<uint8_t> file_data;
std::size_t remaining_data = static_cast<std::size_t>(file.stats().st_size) - static_cast<std::size_t>(file.tell());
file_data.resize(remaining_data);
file.read(file_data.data(), remaining_data);
if(compression_type_ == CompressionType::ZRLE) {
// The only clue given by CSW as to the output size in bytes is that there will be
// number_of_waves waves. Waves are usually one byte, but may be five. So this code
// is pessimistic.
source_data_.resize(static_cast<std::size_t>(number_of_waves) * 5);
std::vector<uint8_t> file_data;
std::size_t remaining_data = static_cast<std::size_t>(file_.stats().st_size) - static_cast<std::size_t>(file_.tell());
file_data.resize(remaining_data);
file_.read(file_data.data(), remaining_data);
// uncompress will tell how many compressed bytes there actually were, so use its
// modification of output_length to throw away all the memory that isn't actually
// needed.
@ -77,42 +78,34 @@ CSW::CSW(const char *file_name) :
uncompress(source_data_.data(), &output_length, file_data.data(), file_data.size());
source_data_.resize(static_cast<std::size_t>(output_length));
} else {
rle_start_ = file_.tell();
source_data_ = std::move(file_data);
}
invert_pulse();
}
uint8_t CSW::get_next_byte() {
switch(compression_type_) {
case CompressionType::RLE: return file_.get8();
case CompressionType::ZRLE: {
if(source_data_pointer_ == source_data_.size()) return 0xff;
uint8_t result = source_data_[source_data_pointer_];
source_data_pointer_++;
return result;
}
CSW::CSW(const std::vector<uint8_t> &&data, CompressionType compression_type, bool initial_level, uint32_t sampling_rate) {
pulse_.length.clock_rate = sampling_rate;
pulse_.type = initial_level ? Pulse::High : Pulse::Low;
source_data_ = std::move(data);
}
default: assert(false); break;
}
uint8_t CSW::get_next_byte() {
if(source_data_pointer_ == source_data_.size()) return 0xff;
uint8_t result = source_data_[source_data_pointer_];
source_data_pointer_++;
return result;
}
uint32_t CSW::get_next_int32le() {
switch(compression_type_) {
case CompressionType::RLE: return file_.get32le();
case CompressionType::ZRLE: {
if(source_data_pointer_ > source_data_.size() - 4) return 0xffff;
uint32_t result = (uint32_t)(
(source_data_[source_data_pointer_ + 0] << 0) |
(source_data_[source_data_pointer_ + 1] << 8) |
(source_data_[source_data_pointer_ + 2] << 16) |
(source_data_[source_data_pointer_ + 3] << 24));
source_data_pointer_ += 4;
return result;
}
default: assert(false); break;
}
if(source_data_pointer_ > source_data_.size() - 4) return 0xffff;
uint32_t result = (uint32_t)(
(source_data_[source_data_pointer_ + 0] << 0) |
(source_data_[source_data_pointer_ + 1] << 8) |
(source_data_[source_data_pointer_ + 2] << 16) |
(source_data_[source_data_pointer_ + 3] << 24));
source_data_pointer_ += 4;
return result;
}
void CSW::invert_pulse() {
@ -120,21 +113,11 @@ void CSW::invert_pulse() {
}
bool CSW::is_at_end() {
switch(compression_type_) {
case CompressionType::RLE: return file_.eof();
case CompressionType::ZRLE: return source_data_pointer_ == source_data_.size();
default: assert(false); break;
}
return source_data_pointer_ == source_data_.size();
}
void CSW::virtual_reset() {
switch(compression_type_) {
case CompressionType::RLE: file_.seek(rle_start_, SEEK_SET); break;
case CompressionType::ZRLE: source_data_pointer_ = 0; break;
default: assert(false); break;
}
source_data_pointer_ = 0;
}
Tape::Pulse CSW::virtual_get_next_pulse() {

View File

@ -30,6 +30,16 @@ class CSW: public Tape {
*/
CSW(const char *file_name);
enum class CompressionType {
RLE,
ZRLE
};
/*!
Constructs a @c CSW containing content as specified. Does not throw.
*/
CSW(const std::vector<uint8_t> &&data, CompressionType compression_type, bool initial_level, uint32_t sampling_rate);
enum {
ErrorNotCSW
};
@ -38,16 +48,11 @@ class CSW: public Tape {
bool is_at_end();
private:
Storage::FileHolder file_;
void virtual_reset();
Pulse virtual_get_next_pulse();
Pulse pulse_;
enum class CompressionType {
RLE,
ZRLE
} compression_type_;
CompressionType compression_type_;
uint8_t get_next_byte();
uint32_t get_next_int32le();
@ -55,8 +60,6 @@ class CSW: public Tape {
std::vector<uint8_t> source_data_;
std::size_t source_data_pointer_;
long rle_start_;
};
}

View File

@ -8,6 +8,8 @@
#include "TZX.hpp"
#include "CSW.hpp"
using namespace Storage::Tape;
namespace {
@ -59,6 +61,8 @@ void TZX::get_next_pulses() {
case 0x12: get_pure_tone_data_block(); break;
case 0x13: get_pulse_sequence(); break;
case 0x14: get_pure_data_block(); break;
case 0x15: get_direct_recording_block(); break;
case 0x18: get_csw_recording_block(); break;
case 0x19: get_generalised_data_block(); break;
case 0x20: get_pause(); break;
@ -70,13 +74,20 @@ void TZX::get_next_pulses() {
case 0x26: ignore_call_sequence(); break;
case 0x27: ignore_return_from_sequence(); break;
case 0x28: ignore_select_block(); break;
case 0x2a: ignore_stop_tape_if_in_48kb_mode(); break;
case 0x2b: get_set_signal_level(); break;
case 0x30: ignore_text_description(); break;
case 0x31: ignore_message_block(); break;
case 0x32: ignore_archive_info(); break;
case 0x33: get_hardware_type(); break;
case 0x35: ignore_custom_info_block(); break;
case 0x4b: get_kansas_city_block(); break;
case 0x5a: ignore_glue_block(); 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.
@ -87,6 +98,26 @@ void TZX::get_next_pulses() {
}
}
void TZX::get_csw_recording_block() {
const uint32_t block_length = file_.get32le();
const uint16_t pause_after_block = file_.get16le();
const uint32_t sampling_rate = file_.get24le();
const uint8_t compression_type = file_.get8();
const uint32_t number_of_compressed_pulses = file_.get32le();
std::vector<uint8_t> raw_block = file_.read(block_length - 10);
CSW csw(std::move(raw_block), (compression_type == 2) ? CSW::CompressionType::ZRLE : CSW::CompressionType::RLE, current_level_, sampling_rate);
while(!csw.is_at_end()) {
Tape::Pulse next_pulse = csw.get_next_pulse();
current_level_ = (next_pulse.type == Tape::Pulse::High);
emplace_back(std::move(next_pulse));
}
(void)number_of_compressed_pulses;
post_gap(pause_after_block);
}
void TZX::get_generalised_data_block() {
uint32_t block_length = file_.get32le();
long endpoint = file_.tell() + static_cast<long>(block_length);
@ -250,6 +281,36 @@ void TZX::get_pure_data_block() {
get_data(data);
}
void TZX::get_direct_recording_block() {
const Storage::Time length_per_sample(static_cast<unsigned>(file_.get16le()), StandardTZXClock);
const uint16_t pause_after_block = file_.get16le();
uint8_t used_bits_in_final_byte = file_.get8();
const uint32_t length_of_data = file_.get24le();
if(used_bits_in_final_byte < 1) used_bits_in_final_byte = 1;
if(used_bits_in_final_byte > 8) used_bits_in_final_byte = 8;
uint8_t byte = 0;
unsigned int bits_at_level = 0;
uint8_t level = 0;
for(std::size_t bit = 0; bit < (length_of_data - 1) * 8 + used_bits_in_final_byte; ++bit) {
if(!(bit&7)) byte = file_.get8();
if(!bit) level = byte&0x80;
if((byte&0x80) != level) {
emplace_back(level ? Tape::Pulse::High : Tape::Pulse::Low, length_per_sample * bits_at_level);
bits_at_level = 0;
level = byte&0x80;
}
bits_at_level++;
}
current_level_ = !!(level);
emplace_back(level ? Tape::Pulse::High : Tape::Pulse::Low, length_per_sample * bits_at_level);
post_gap(pause_after_block);
}
void TZX::get_pulse_sequence() {
uint8_t number_of_pulses = file_.get8();
while(number_of_pulses--) {
@ -266,6 +327,12 @@ void TZX::get_pause() {
}
}
void TZX::get_set_signal_level() {
file_.seek(4, SEEK_CUR);
const uint8_t level = file_.get8();
current_level_ = !!level;
}
void TZX::get_kansas_city_block() {
uint32_t block_length = file_.get32le();
@ -350,7 +417,6 @@ void TZX::post_pulse(const Storage::Time &time) {
// MARK: - Flow control; currently ignored
void TZX::ignore_group_start() {
printf("Ignoring TZX group\n");
uint8_t length = file_.get8();
file_.seek(length, SEEK_CUR);
}
@ -361,13 +427,11 @@ void TZX::ignore_group_end() {
void TZX::ignore_jump_to_block() {
uint16_t target = file_.get16le();
(void)target;
printf("Ignoring TZX jump\n");
}
void TZX::ignore_loop_start() {
uint16_t number_of_repetitions = file_.get16le();
(void)number_of_repetitions;
printf("Ignoring TZX loop\n");
}
void TZX::ignore_loop_end() {
@ -376,17 +440,25 @@ void TZX::ignore_loop_end() {
void TZX::ignore_call_sequence() {
uint16_t number_of_entries = file_.get16le();
file_.seek(number_of_entries * sizeof(uint16_t), SEEK_CUR);
printf("Ignoring TZX call sequence\n");
}
void TZX::ignore_return_from_sequence() {
printf("Ignoring TZX return from sequence\n");
}
void TZX::ignore_select_block() {
uint16_t length_of_block = file_.get16le();
file_.seek(length_of_block, SEEK_CUR);
printf("Ignoring TZX select block\n");
}
void TZX::ignore_stop_tape_if_in_48kb_mode() {
file_.seek(4, SEEK_CUR);
}
void TZX::ignore_custom_info_block() {
// TODO: enquire about this; the TZX documentation is ambiguous as to whether this is really 10, or 0x10.
file_.seek(10, SEEK_CUR);
uint32_t length = file_.get32le();
file_.seek(length, SEEK_CUR);
}
// MARK: - Messaging
@ -394,7 +466,6 @@ void TZX::ignore_select_block() {
void TZX::ignore_text_description() {
uint8_t length = file_.get8();
file_.seek(length, SEEK_CUR);
printf("Ignoring TZX text description\n");
}
void TZX::ignore_message_block() {
@ -402,12 +473,19 @@ void TZX::ignore_message_block() {
uint8_t length = file_.get8();
file_.seek(length, SEEK_CUR);
(void)time_for_display;
printf("Ignoring TZX message\n");
}
void TZX::ignore_archive_info() {
uint16_t length = file_.get16le();
file_.seek(length, SEEK_CUR);
}
void TZX::get_hardware_type() {
// TODO: pick a way to retain and communicate this.
uint8_t number_of_machines = file_.get8();
file_.seek(number_of_machines * 3, SEEK_CUR);
printf("Ignoring TZX hardware types (%d)\n", number_of_machines);
}
void TZX::ignore_glue_block() {
file_.seek(9, SEEK_CUR);
}

View File

@ -44,11 +44,10 @@ class TZX: public PulseQueuedTape {
void get_pure_tone_data_block();
void get_pulse_sequence();
void get_pure_data_block();
void get_direct_recording_block();
void get_csw_recording_block();
void get_generalised_data_block();
void get_pause();
void get_kansas_city_block();
void get_hardware_type();
void ignore_group_start();
void ignore_group_end();
@ -58,8 +57,18 @@ class TZX: public PulseQueuedTape {
void ignore_call_sequence();
void ignore_return_from_sequence();
void ignore_select_block();
void ignore_stop_tape_if_in_48kb_mode();
void get_set_signal_level();
void ignore_text_description();
void ignore_message_block();
void ignore_archive_info();
void get_hardware_type();
void ignore_custom_info_block();
void get_kansas_city_block();
void ignore_glue_block();
struct Data {
unsigned int length_of_zero_bit_pulse;

View File

@ -33,6 +33,10 @@ void PulseQueuedTape::emplace_back(Tape::Pulse::Type type, Time length) {
queued_pulses_.emplace_back(type, length);
}
void PulseQueuedTape::emplace_back(const Tape::Pulse &&pulse) {
queued_pulses_.emplace_back(pulse);
}
Tape::Pulse PulseQueuedTape::silence() {
Pulse silence;
silence.type = Pulse::Zero;

View File

@ -32,6 +32,7 @@ class PulseQueuedTape: public Tape {
protected:
void emplace_back(Tape::Pulse::Type type, Time length);
void emplace_back(const Tape::Pulse &&pulse);
void clear();
bool empty();