diff --git a/Storage/Tape/Formats/CAS.cpp b/Storage/Tape/Formats/CAS.cpp index 6ea0b037a..6d8948c96 100644 --- a/Storage/Tape/Formats/CAS.cpp +++ b/Storage/Tape/Formats/CAS.cpp @@ -19,67 +19,38 @@ namespace { CAS::CAS(const char *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. - get_next(file, lookahead, 8); + // Entirely fill the lookahead and verify that its start matches the header signature. + get_next(file, lookahead, 10); if(std::memcmp(lookahead, header_signature, sizeof(header_signature))) throw ErrorNotCAS; - File *active_file = nullptr; while(!file.eof()) { // Just found a header, so flush the lookahead. get_next(file, lookahead, 8); - // If no file is active, create one, as this must be an identification block. - if(!active_file) { - // Determine the new file type. - Block type; - switch(lookahead[0]) { - case 0xd3: type = Block::CSAVE; break; - case 0xd0: type = Block::BSAVE; break; - case 0xea: type = Block::ASCII; break; + // Create a new chunk + chunks_.emplace_back(); + Chunk &chunk = chunks_.back(); - // This implies something has gone wrong with parsing. - default: throw ErrorNotCAS; - } + // Decide whether to award a long header and/or a gap. + 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. - files_.emplace_back(); - active_file = &files_.back(); - active_file->type = type; - } - - // Add a new chunk for the new incoming data. - active_file->chunks.emplace_back(); + chunk.long_header = bytes_are_equal && ((lookahead[0] == 0xd3) || (lookahead[0] == 0xd0) || (lookahead[0] == 0xea)); + chunk.has_gap = chunk.long_header && (chunks_.size() > 1); // Keep going until another header arrives or the file ends. 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); } // If the file ended, flush the lookahead. if(file.eof()) { - for(int index = 0; index < 8; index++) - active_file->chunks.back().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 &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; + for(std::size_t index = 0; index < sizeof(lookahead); index++) + chunk.data.push_back(lookahead[index]); } } } @@ -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 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) { - assert(quantity <= 8); +void CAS::get_next(Storage::FileHolder &file, uint8_t (&buffer)[10], std::size_t quantity) { + assert(quantity <= sizeof(buffer)); - if(quantity < 8) - std::memmove(buffer, &buffer[quantity], 8 - quantity); + if(quantity < sizeof(buffer)) + std::memmove(buffer, &buffer[quantity], sizeof(buffer) - 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() { phase_ = Phase::Header; - file_pointer_ = 0; chunk_pointer_ = 0; distance_into_phase_ = 0; distance_into_bit_ = 0; @@ -126,7 +96,6 @@ Tape::Pulse CAS::virtual_get_next_pulse() { if(phase_ == Phase::Gap) { phase_ = Phase::Header; - chunk_pointer_ = 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 // 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; distance_into_phase_ = 0; distance_into_bit_ = 0; @@ -159,7 +128,7 @@ Tape::Pulse CAS::virtual_get_next_pulse() { case Phase::Bytes: { // 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; switch(bit_offset) { case 0: bit = 0; break; @@ -168,28 +137,20 @@ Tape::Pulse CAS::virtual_get_next_pulse() { 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 in the file then, if there are further files switch to a gap. - // Otherwise note end of file. - // - // - otherwise, roll onto the next header. - // + // 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. + // - otherwise, roll onto the next header or gap, depending on whether the next chunk has a gap. distance_into_bit_++; if(distance_into_bit_ == (bit ? 4 : 2)) { distance_into_bit_ = 0; 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; chunk_pointer_++; - if(chunk_pointer_ == files_[file_pointer_].chunks.size()) { - chunk_pointer_ = 0; - file_pointer_++; - phase_ = (file_pointer_ == files_.size()) ? Phase::EndOfFile : Phase::Gap; + if(chunk_pointer_ == chunks_.size()) { + phase_ = Phase::EndOfFile; } else { - phase_ = Phase::Header; + phase_ = chunks_[chunk_pointer_].has_gap ? Phase::Gap : Phase::Header; } } } diff --git a/Storage/Tape/Formats/CAS.hpp b/Storage/Tape/Formats/CAS.hpp index beaca5843..fbd7e3a5f 100644 --- a/Storage/Tape/Formats/CAS.hpp +++ b/Storage/Tape/Formats/CAS.hpp @@ -42,22 +42,19 @@ class CAS: public Tape { Pulse virtual_get_next_pulse(); // 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. - enum class Block { - BSAVE, - CSAVE, - ASCII + // Storage for the array of data blobs to transcribe into audio; + // each chunk is preceded by a header which may be long, and is optionally + // also preceded by a gap. + struct Chunk { + bool has_gap; + bool long_header; + std::vector data; }; - struct File { - Block type; - std::vector> chunks; - }; - std::vector files_; + std::vector chunks_; // Tracker for active state within the file list. - std::size_t file_pointer_ = 0; std::size_t chunk_pointer_ = 0; enum class Phase { Header,