mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-27 22:30:49 +00:00
Switches to an attempt to break the .CAS into files ahead of time.
Hopefully the better to insert appropriate lengths of header and gap.
This commit is contained in:
parent
e8ddff0ee0
commit
b4bfcd4279
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include "CAS.hpp"
|
#include "CAS.hpp"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
using namespace Storage::Tape;
|
using namespace Storage::Tape;
|
||||||
@ -16,89 +17,157 @@ namespace {
|
|||||||
const uint8_t header_signature[8] = {0x1f, 0xa6, 0xde, 0xba, 0xcc, 0x13, 0x7d, 0x74};
|
const uint8_t header_signature[8] = {0x1f, 0xa6, 0xde, 0xba, 0xcc, 0x13, 0x7d, 0x74};
|
||||||
}
|
}
|
||||||
|
|
||||||
CAS::CAS(const char *file_name) :
|
CAS::CAS(const char *file_name) {
|
||||||
file_(file_name) {
|
Storage::FileHolder file(file_name);
|
||||||
file_.read(input_, sizeof(input_));
|
uint8_t lookahead[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
||||||
if(std::memcmp(input_, header_signature, sizeof(header_signature))) throw ErrorNotCAS;
|
|
||||||
|
// Get the first header.
|
||||||
|
get_next(file, lookahead, 8);
|
||||||
|
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;
|
||||||
|
|
||||||
|
// This implies something has gone wrong with parsing.
|
||||||
|
default: throw ErrorNotCAS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// Keep going until another header arrives.
|
||||||
|
while(std::memcmp(lookahead, header_signature, sizeof(header_signature)) && !file.eof()) {
|
||||||
|
active_file->chunks.back().push_back(lookahead[0]);
|
||||||
|
get_next(file, lookahead, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CAS::get_next(Storage::FileHolder &file, uint8_t (&buffer)[8], std::size_t quantity) {
|
||||||
|
assert(quantity <= 8);
|
||||||
|
|
||||||
|
if(quantity < 8)
|
||||||
|
std::memmove(buffer, &buffer[quantity], 8 - quantity);
|
||||||
|
|
||||||
|
while(quantity--) {
|
||||||
|
buffer[7 - quantity] = file.get8();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CAS::is_at_end() {
|
bool CAS::is_at_end() {
|
||||||
return file_.eof();
|
return phase_ == Phase::EndOfFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CAS::virtual_reset() {
|
void CAS::virtual_reset() {
|
||||||
file_.seek(0, SEEK_SET);
|
|
||||||
file_.read(input_, sizeof(input_));
|
|
||||||
phase_ = Phase::Header;
|
phase_ = Phase::Header;
|
||||||
|
file_pointer_ = 0;
|
||||||
|
chunk_pointer_ = 0;
|
||||||
distance_into_phase_ = 0;
|
distance_into_phase_ = 0;
|
||||||
|
distance_into_bit_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Tape::Pulse CAS::virtual_get_next_pulse() {
|
Tape::Pulse CAS::virtual_get_next_pulse() {
|
||||||
Pulse pulse;
|
Pulse pulse;
|
||||||
switch(phase_) {
|
pulse.length.clock_rate = 4800;
|
||||||
case Phase::Gap: {
|
|
||||||
// Leave a fixed 0.5 second gap between blocks.
|
|
||||||
pulse.length.length = 2400;
|
|
||||||
pulse.length.clock_rate = 4800;
|
|
||||||
pulse.type = Pulse::Type::Zero;
|
|
||||||
|
|
||||||
if(!file_.eof()) phase_ = Phase::Header;
|
// If this is a gap, then that terminates a file. If this is already the end
|
||||||
} break;
|
// of the file then perpetual gaps await.
|
||||||
|
if(phase_ == Phase::Gap || phase_ == Phase::EndOfFile) {
|
||||||
|
pulse.length.length = pulse.length.clock_rate;
|
||||||
|
pulse.type = Pulse::Type::Zero;
|
||||||
|
|
||||||
|
if(phase_ == Phase::Gap) {
|
||||||
|
phase_ = Phase::Header;
|
||||||
|
file_pointer_ ++;
|
||||||
|
chunk_pointer_ = 0;
|
||||||
|
distance_into_phase_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pulse;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine which bit is now forthcoming.
|
||||||
|
int bit = 1;
|
||||||
|
|
||||||
|
switch(phase_) {
|
||||||
|
default: break;
|
||||||
|
|
||||||
case Phase::Header: {
|
case Phase::Header: {
|
||||||
// 1 bits are two complete cycle at 2400 hz
|
distance_into_bit_++;
|
||||||
pulse.length.length = 1;
|
if(distance_into_bit_ == 2) {
|
||||||
pulse.length.clock_rate = 4800;
|
distance_into_phase_++;
|
||||||
pulse.type = (distance_into_phase_&1) ? Pulse::Type::Low : Pulse::Type::High;
|
if(distance_into_phase_ == (chunk_pointer_ ? 15360 : 3840)) {
|
||||||
|
phase_ = Phase::Bytes;
|
||||||
distance_into_phase_++;
|
distance_into_phase_ = 0;
|
||||||
if(distance_into_phase_ == 7936*4) {
|
distance_into_bit_ = 0;
|
||||||
distance_into_phase_ = 0;
|
|
||||||
phase_ = Phase::Bytes;
|
|
||||||
|
|
||||||
for(int c = 1; c < 8; c++) {
|
|
||||||
input_[c] = file_.get8();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case Phase::Bytes: {
|
case Phase::Bytes: {
|
||||||
unsigned int bit;
|
int byte_value = files_[file_pointer_].chunks[chunk_pointer_][distance_into_phase_ / 11];
|
||||||
const int bit_offset = distance_into_phase_ >> 2;
|
int bit_offset = distance_into_phase_ % 11;
|
||||||
switch(bit_offset) {
|
switch(bit_offset) {
|
||||||
case 0: bit = 0; break;
|
case 0: bit = 0; break;
|
||||||
default: bit = (input_[0] >> (bit_offset - 1)) & 1; break;
|
default: bit = (byte_value >> (bit_offset - 1)) & 1; break;
|
||||||
case 9:
|
case 9:
|
||||||
case 10: bit = 1; break;
|
case 10: bit = 1; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1 bits are two complete cycle at 2400 hz; 0 bits are one complete cycle at 1200 hz
|
distance_into_bit_++;
|
||||||
pulse.length.length = 2 - bit;
|
if(distance_into_bit_ == (bit ? 4 : 2)) {
|
||||||
pulse.length.clock_rate = 4800;
|
distance_into_bit_ = 0;
|
||||||
pulse.type = distance_into_phase_ ? Pulse::Type::High : Pulse::Type::Low;
|
distance_into_phase_++;
|
||||||
|
if(distance_into_phase_ == files_[file_pointer_].chunks[chunk_pointer_].size() * 11) {
|
||||||
int adder = 1;
|
distance_into_phase_ = 0;
|
||||||
if(!bit && ((distance_into_phase_&3) == 1)) adder = 3;
|
chunk_pointer_++;
|
||||||
distance_into_phase_ = (distance_into_phase_ + adder) % (11 * 4);
|
if(chunk_pointer_ == files_[file_pointer_].chunks.size()) {
|
||||||
|
chunk_pointer_ = 0;
|
||||||
|
file_pointer_++;
|
||||||
|
phase_ = (chunk_pointer_ == files_.size()) ? Phase::EndOfFile : Phase::Gap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} break;
|
} break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if(pulse.type == Pulse::Type::Zero) printf("\n---\n");
|
pulse.length.length = static_cast<unsigned int>(2 - bit);
|
||||||
// else {
|
pulse.type = (distance_into_bit_ & 1) ? Pulse::Type::High : Pulse::Type::Low;
|
||||||
// if(pulse.length.length == 1) printf(".");
|
|
||||||
// else if(pulse.length.length == 2) printf("+");
|
|
||||||
// else printf("?");
|
|
||||||
// }
|
|
||||||
|
|
||||||
if(!distance_into_phase_ && phase_ == Phase::Bytes) {
|
|
||||||
std::memmove(input_, &input_[1], 7);
|
|
||||||
input_[7] = file_.get8();
|
|
||||||
if(!is_at_end()) {
|
|
||||||
if(!std::memcmp(input_, header_signature, sizeof(header_signature))) {
|
|
||||||
phase_ = Phase::Header;//Gap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pulse;
|
return pulse;
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,9 @@
|
|||||||
#include "../Tape.hpp"
|
#include "../Tape.hpp"
|
||||||
#include "../../FileHolder.hpp"
|
#include "../../FileHolder.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace Storage {
|
namespace Storage {
|
||||||
namespace Tape {
|
namespace Tape {
|
||||||
|
|
||||||
@ -35,19 +38,35 @@ class CAS: public Tape {
|
|||||||
bool is_at_end();
|
bool is_at_end();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Storage::FileHolder file_;
|
|
||||||
|
|
||||||
void virtual_reset();
|
void virtual_reset();
|
||||||
Pulse virtual_get_next_pulse();
|
Pulse virtual_get_next_pulse();
|
||||||
|
|
||||||
uint8_t input_[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
// Helper for populating the file list, below.
|
||||||
|
void get_next(Storage::FileHolder &file, uint8_t (&buffer)[8], std::size_t quantity);
|
||||||
|
|
||||||
|
// Storage for the array of files to transcribe into audio.
|
||||||
|
enum class Block {
|
||||||
|
BSAVE,
|
||||||
|
CSAVE,
|
||||||
|
ASCII
|
||||||
|
};
|
||||||
|
struct File {
|
||||||
|
Block type;
|
||||||
|
std::vector<std::vector<std::uint8_t>> chunks;
|
||||||
|
};
|
||||||
|
std::vector<File> files_;
|
||||||
|
|
||||||
|
// Tracker for active state within the file list.
|
||||||
|
std::size_t file_pointer_ = 0;
|
||||||
|
std::size_t chunk_pointer_ = 0;
|
||||||
enum class Phase {
|
enum class Phase {
|
||||||
Header,
|
Header,
|
||||||
Bytes,
|
Bytes,
|
||||||
Gap
|
Gap,
|
||||||
|
EndOfFile
|
||||||
} phase_ = Phase::Header;
|
} phase_ = Phase::Header;
|
||||||
int distance_into_phase_ = 0;
|
std::size_t distance_into_phase_ = 0;
|
||||||
|
std::size_t distance_into_bit_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user