mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-23 03:32:32 +00:00
Liberalises CAS interpretation.
It seems to be an even weirder file format than I thought; it can contain only ROM-formatted data but seemingly often contains blobs that the ROM cannot write.
This commit is contained in:
parent
5fd0a2b9ea
commit
c481293aca
@ -19,67 +19,38 @@ namespace {
|
|||||||
|
|
||||||
CAS::CAS(const char *file_name) {
|
CAS::CAS(const char *file_name) {
|
||||||
Storage::FileHolder file(file_name);
|
Storage::FileHolder file(file_name);
|
||||||
uint8_t lookahead[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
uint8_t lookahead[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||||
|
|
||||||
// Get the first header.
|
// Entirely fill the lookahead and verify that its start matches the header signature.
|
||||||
get_next(file, lookahead, 8);
|
get_next(file, lookahead, 10);
|
||||||
if(std::memcmp(lookahead, header_signature, sizeof(header_signature))) throw ErrorNotCAS;
|
if(std::memcmp(lookahead, header_signature, sizeof(header_signature))) throw ErrorNotCAS;
|
||||||
|
|
||||||
File *active_file = nullptr;
|
|
||||||
while(!file.eof()) {
|
while(!file.eof()) {
|
||||||
// Just found a header, so flush the lookahead.
|
// Just found a header, so flush the lookahead.
|
||||||
get_next(file, lookahead, 8);
|
get_next(file, lookahead, 8);
|
||||||
|
|
||||||
// If no file is active, create one, as this must be an identification block.
|
// Create a new chunk
|
||||||
if(!active_file) {
|
chunks_.emplace_back();
|
||||||
// Determine the new file type.
|
Chunk &chunk = chunks_.back();
|
||||||
Block type;
|
|
||||||
switch(lookahead[0]) {
|
|
||||||
case 0xd3: type = Block::CSAVE; break;
|
|
||||||
case 0xd0: type = Block::BSAVE; break;
|
|
||||||
case 0xea: type = Block::ASCII; break;
|
|
||||||
|
|
||||||
// This implies something has gone wrong with parsing.
|
// Decide whether to award a long header and/or a gap.
|
||||||
default: throw ErrorNotCAS;
|
bool bytes_are_equal = true;
|
||||||
}
|
for(std::size_t index = 0; index < sizeof(lookahead); index++)
|
||||||
|
bytes_are_equal &= (lookahead[index] == lookahead[0]);
|
||||||
|
|
||||||
// Set the type and feed in the initial data.
|
chunk.long_header = bytes_are_equal && ((lookahead[0] == 0xd3) || (lookahead[0] == 0xd0) || (lookahead[0] == 0xea));
|
||||||
files_.emplace_back();
|
chunk.has_gap = chunk.long_header && (chunks_.size() > 1);
|
||||||
active_file = &files_.back();
|
|
||||||
active_file->type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a new chunk for the new incoming data.
|
|
||||||
active_file->chunks.emplace_back();
|
|
||||||
|
|
||||||
// Keep going until another header arrives or the file ends.
|
// Keep going until another header arrives or the file ends.
|
||||||
while(std::memcmp(lookahead, header_signature, sizeof(header_signature)) && !file.eof()) {
|
while(std::memcmp(lookahead, header_signature, sizeof(header_signature)) && !file.eof()) {
|
||||||
active_file->chunks.back().push_back(lookahead[0]);
|
chunk.data.push_back(lookahead[0]);
|
||||||
get_next(file, lookahead, 1);
|
get_next(file, lookahead, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the file ended, flush the lookahead.
|
// If the file ended, flush the lookahead.
|
||||||
if(file.eof()) {
|
if(file.eof()) {
|
||||||
for(int index = 0; index < 8; index++)
|
for(std::size_t index = 0; index < sizeof(lookahead); index++)
|
||||||
active_file->chunks.back().push_back(lookahead[index]);
|
chunk.data.push_back(lookahead[index]);
|
||||||
}
|
|
||||||
|
|
||||||
switch(active_file->type) {
|
|
||||||
case Block::ASCII:
|
|
||||||
// ASCII files have as many chunks as necessary, the final one being back filled
|
|
||||||
// with 0x1a.
|
|
||||||
if(active_file->chunks.size() >= 2) {
|
|
||||||
std::vector<uint8_t> &last_chunk = active_file->chunks.back();
|
|
||||||
if(last_chunk.back() == 0x1a)
|
|
||||||
active_file = nullptr;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// CSAVE and BSAVE files have exactly two chunks, the second being the data.
|
|
||||||
if(active_file->chunks.size() == 2)
|
|
||||||
active_file = nullptr;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,14 +59,14 @@ CAS::CAS(const char *file_name) {
|
|||||||
Treating @c buffer as a sliding lookahead, shifts it @c quantity elements to the left and
|
Treating @c buffer as a sliding lookahead, shifts it @c quantity elements to the left and
|
||||||
populates the new empty area to the right from @c file.
|
populates the new empty area to the right from @c file.
|
||||||
*/
|
*/
|
||||||
void CAS::get_next(Storage::FileHolder &file, uint8_t (&buffer)[8], std::size_t quantity) {
|
void CAS::get_next(Storage::FileHolder &file, uint8_t (&buffer)[10], std::size_t quantity) {
|
||||||
assert(quantity <= 8);
|
assert(quantity <= sizeof(buffer));
|
||||||
|
|
||||||
if(quantity < 8)
|
if(quantity < sizeof(buffer))
|
||||||
std::memmove(buffer, &buffer[quantity], 8 - quantity);
|
std::memmove(buffer, &buffer[quantity], sizeof(buffer) - quantity);
|
||||||
|
|
||||||
while(quantity--) {
|
while(quantity--) {
|
||||||
buffer[7 - quantity] = file.get8();
|
buffer[sizeof(buffer) - 1 - quantity] = file.get8();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +76,6 @@ bool CAS::is_at_end() {
|
|||||||
|
|
||||||
void CAS::virtual_reset() {
|
void CAS::virtual_reset() {
|
||||||
phase_ = Phase::Header;
|
phase_ = Phase::Header;
|
||||||
file_pointer_ = 0;
|
|
||||||
chunk_pointer_ = 0;
|
chunk_pointer_ = 0;
|
||||||
distance_into_phase_ = 0;
|
distance_into_phase_ = 0;
|
||||||
distance_into_bit_ = 0;
|
distance_into_bit_ = 0;
|
||||||
@ -126,7 +96,6 @@ Tape::Pulse CAS::virtual_get_next_pulse() {
|
|||||||
|
|
||||||
if(phase_ == Phase::Gap) {
|
if(phase_ == Phase::Gap) {
|
||||||
phase_ = Phase::Header;
|
phase_ = Phase::Header;
|
||||||
chunk_pointer_ = 0;
|
|
||||||
distance_into_phase_ = 0;
|
distance_into_phase_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,7 +118,7 @@ Tape::Pulse CAS::virtual_get_next_pulse() {
|
|||||||
|
|
||||||
// This code always produces a 2400 baud signal; so use the appropriate Red Book-supplied
|
// This code always produces a 2400 baud signal; so use the appropriate Red Book-supplied
|
||||||
// constants to check whether the header has come to an end.
|
// constants to check whether the header has come to an end.
|
||||||
if(distance_into_phase_ == (chunk_pointer_ ? 7936 : 31744)) {
|
if(distance_into_phase_ == (chunks_[chunk_pointer_].long_header ? 31744 : 7936)) {
|
||||||
phase_ = Phase::Bytes;
|
phase_ = Phase::Bytes;
|
||||||
distance_into_phase_ = 0;
|
distance_into_phase_ = 0;
|
||||||
distance_into_bit_ = 0;
|
distance_into_bit_ = 0;
|
||||||
@ -159,7 +128,7 @@ Tape::Pulse CAS::virtual_get_next_pulse() {
|
|||||||
|
|
||||||
case Phase::Bytes: {
|
case Phase::Bytes: {
|
||||||
// Provide bits with a single '0' start bit and two '1' stop bits.
|
// Provide bits with a single '0' start bit and two '1' stop bits.
|
||||||
uint8_t byte_value = files_[file_pointer_].chunks[chunk_pointer_][distance_into_phase_ / 11];
|
uint8_t byte_value = chunks_[chunk_pointer_].data[distance_into_phase_ / 11];
|
||||||
int bit_offset = distance_into_phase_ % 11;
|
int bit_offset = distance_into_phase_ % 11;
|
||||||
switch(bit_offset) {
|
switch(bit_offset) {
|
||||||
case 0: bit = 0; break;
|
case 0: bit = 0; break;
|
||||||
@ -168,28 +137,20 @@ Tape::Pulse CAS::virtual_get_next_pulse() {
|
|||||||
case 10: bit = 1; break;
|
case 10: bit = 1; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lots of branches below, to the effect that:
|
// If bit is finished, and if all bytes in chunk have been posted then:
|
||||||
//
|
// - if this is the final chunk then note end of file.
|
||||||
// if bit is finished, and if all bytes in chunk have been posted then:
|
// - otherwise, roll onto the next header or gap, depending on whether the next chunk has a gap.
|
||||||
//
|
|
||||||
// - if this is the final chunk in the file then, if there are further files switch to a gap.
|
|
||||||
// Otherwise note end of file.
|
|
||||||
//
|
|
||||||
// - otherwise, roll onto the next header.
|
|
||||||
//
|
|
||||||
distance_into_bit_++;
|
distance_into_bit_++;
|
||||||
if(distance_into_bit_ == (bit ? 4 : 2)) {
|
if(distance_into_bit_ == (bit ? 4 : 2)) {
|
||||||
distance_into_bit_ = 0;
|
distance_into_bit_ = 0;
|
||||||
distance_into_phase_++;
|
distance_into_phase_++;
|
||||||
if(distance_into_phase_ == files_[file_pointer_].chunks[chunk_pointer_].size() * 11) {
|
if(distance_into_phase_ == chunks_[chunk_pointer_].data.size() * 11) {
|
||||||
distance_into_phase_ = 0;
|
distance_into_phase_ = 0;
|
||||||
chunk_pointer_++;
|
chunk_pointer_++;
|
||||||
if(chunk_pointer_ == files_[file_pointer_].chunks.size()) {
|
if(chunk_pointer_ == chunks_.size()) {
|
||||||
chunk_pointer_ = 0;
|
phase_ = Phase::EndOfFile;
|
||||||
file_pointer_++;
|
|
||||||
phase_ = (file_pointer_ == files_.size()) ? Phase::EndOfFile : Phase::Gap;
|
|
||||||
} else {
|
} else {
|
||||||
phase_ = Phase::Header;
|
phase_ = chunks_[chunk_pointer_].has_gap ? Phase::Gap : Phase::Header;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,22 +42,19 @@ class CAS: public Tape {
|
|||||||
Pulse virtual_get_next_pulse();
|
Pulse virtual_get_next_pulse();
|
||||||
|
|
||||||
// Helper for populating the file list, below.
|
// Helper for populating the file list, below.
|
||||||
void get_next(Storage::FileHolder &file, uint8_t (&buffer)[8], std::size_t quantity);
|
void get_next(Storage::FileHolder &file, uint8_t (&buffer)[10], std::size_t quantity);
|
||||||
|
|
||||||
// Storage for the array of files to transcribe into audio.
|
// Storage for the array of data blobs to transcribe into audio;
|
||||||
enum class Block {
|
// each chunk is preceded by a header which may be long, and is optionally
|
||||||
BSAVE,
|
// also preceded by a gap.
|
||||||
CSAVE,
|
struct Chunk {
|
||||||
ASCII
|
bool has_gap;
|
||||||
|
bool long_header;
|
||||||
|
std::vector<std::uint8_t> data;
|
||||||
};
|
};
|
||||||
struct File {
|
std::vector<Chunk> chunks_;
|
||||||
Block type;
|
|
||||||
std::vector<std::vector<std::uint8_t>> chunks;
|
|
||||||
};
|
|
||||||
std::vector<File> files_;
|
|
||||||
|
|
||||||
// Tracker for active state within the file list.
|
// Tracker for active state within the file list.
|
||||||
std::size_t file_pointer_ = 0;
|
|
||||||
std::size_t chunk_pointer_ = 0;
|
std::size_t chunk_pointer_ = 0;
|
||||||
enum class Phase {
|
enum class Phase {
|
||||||
Header,
|
Header,
|
||||||
|
Loading…
Reference in New Issue
Block a user