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:
parent
d9d5ffdaa2
commit
d83178f29d
@ -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() {
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user