@@ -14,73 +14,152 @@ using namespace Storage::Tape;
1414 [CoCo-style] CAS files are a raw dump of source bytes as per Microsoft's 6809 BASIC and
1515 the Tandy (or Dragon) encoding.
1616
17- It is therefore very similar to the Thomson K7 file format.
17+ It is therefore very similar to the Thomson K7 file format, but weird:
1818
19+ Contents are not necessarily byte-aligned with original content; within a file block
20+ it's an arbitrarily-aligned bitstream.
21+
22+ ... but, most files abbreviate the sync periods. So you've still to apply
23+ ROM-style formatting.
1924*/
25+ CoCoCAS::CoCoCAS (const std::string &file_name) {
26+ struct Shifter {
27+ Shifter (const std::string &file_name) : file_(file_name, FileMode::Read) {}
28+
29+ uint32_t value () const {
30+ return uint32_t (shifter_);
31+ }
32+
33+ void advance (size_t length) {
34+ while (length--) shift ();
35+ }
36+
37+ void shift () {
38+ if (!depth_ && file_.eof ()) {
39+ return ;
40+ }
41+
42+ shifter_ >>= 1 ;
43+ --depth_;
44+ ++shifted_;
45+ while (depth_ <= 56 && !file_.eof ()) {
46+ shifter_ |= uint64_t (file_.get ()) << depth_;
47+ depth_ += 8 ;
48+ }
49+ }
50+
51+ size_t offset () const {
52+ return shifted_;
53+ }
54+
55+ void set_offset (const size_t offset) {
56+ file_.seek (0 , Whence::SET );
57+ shifted_ = 0 ;
58+ advance (offset);
59+ }
60+
61+ bool eof () const {
62+ return file_.eof () && !depth_;
63+ }
64+
65+ private:
66+ Storage::FileHolder file_;
67+
68+ uint64_t shifter_ = 0 ;
69+ int depth_ = 0 ;
70+ size_t shifted_ = 0 ;
71+ };
72+
73+ Shifter shifter (file_name);
74+ while (!shifter.eof ()) {
75+ // Find next sync byte.
76+ while (!shifter.eof () && (shifter.value () & 0xff ) != 0x3c ) {
77+ shifter.shift ();
78+ }
79+ const auto offset = shifter.offset ();
80+ if (shifter.eof ()) break ;
81+
82+ auto &block = blocks_.emplace_back ();
83+ shifter.advance (8 );
84+ const auto type = uint8_t (shifter.value ());
85+
86+ shifter.advance (8 );
87+ const auto length = uint8_t (shifter.value ());
2088
21- CoCoCAS::CoCoCAS (const std::string &file_name) : file_name_(file_name) {
22- // TODO: reject unless at least one normative CoCo-esque block is within the image.
23- // CAS is not an unambiguous extension.
89+ block.data .reserve (length + 3 );
90+ block.data .push_back (0x3c );
91+ block.data .push_back (type);
92+ block.data .push_back (length);
93+
94+ for (int c = 0 ; c <= length; c++) {
95+ shifter.advance (8 );
96+ block.data .push_back (uint8_t (shifter.value ()));
97+ }
98+
99+ const auto checksum = uint8_t (std::accumulate (block.data .begin () + 1 , block.data .end () - 1 , 0 ));
100+ if (checksum != block.data .back ()) {
101+ blocks_.pop_back ();
102+ shifter.set_offset (offset + 1 );
103+ }
104+ }
105+
106+ if (blocks_.empty ()) {
107+ throw ErrorBadFormat;
108+ }
24109}
25110
26111std::unique_ptr<FormatSerialiser> CoCoCAS::format_serialiser () const {
27- return std::make_unique<Serialiser>(file_name_ );
112+ return std::make_unique<Serialiser>(blocks_ );
28113}
29114
30- CoCoCAS::Serialiser::Serialiser (const std::string &name ) : file_(name, FileMode::Read ) {
115+ CoCoCAS::Serialiser::Serialiser (const std::vector<Block> &blocks ) : blocks_(blocks ) {
31116 reset ();
32117}
33118
119+ void CoCoCAS::Serialiser::reset () {
120+ block_ = blocks_.begin ();
121+ state_ = State::LeadIn;
122+ state_length_ = 0 ;
123+ }
124+
34125void CoCoCAS::Serialiser::push_next_pulses () {
126+ const auto post_bit = [&](const bool bit) {
127+ // Generate a single wave of either 1200Hz (for a 0) or 2400Hz tone (for a 1).
128+ const Time length (
129+ 1 ,
130+ bit ? 4800 : 2400
131+ );
132+ emplace_back (Pulse::Low, length);
133+ emplace_back (Pulse::High, length);
134+ };
135+
35136 const auto serialise = [&](uint8_t next) {
36137 for (int c = 0 ; c < 8 ; c++) {
37- // Generate a single wave of either 1200Hz (for a 0) or 2400Hz tone (for a 1).
38- const Time length (
39- 1 ,
40- next & 0x01 ? 4800 : 2400
41- );
138+ post_bit (next & 1 );
42139 next >>= 1 ;
43-
44- emplace_back (Pulse::Low, length);
45- emplace_back (Pulse::High, length);
46140 }
47141 };
48142
49143 switch (state_) {
50- case State::PreLeadInPause:
51- emplace_back (Pulse::Zero, Time (1 , 2 ));
52- state_ = State::LeadIn;
53- break ;
54-
55- case State::LeadIn: {
56- const uint8_t next = file_.get ();
57- serialise (next);
58-
59- if (next == 0x3c ) {
144+ case State::LeadIn:
145+ serialise (0x55 );
146+ ++state_length_;
147+ if (state_length_ == 150 && block_ != blocks_.end ()) {
60148 state_ = State::Body;
61- state_length_ = - 1 ;
149+ state_length_ = 0 ;
62150 }
63- }
64151 break ;
65152
66- case State::Body: {
67- const uint8_t next = file_.get ();
68- serialise (next);
69-
70- --state_length_;
71- if (state_length_ == -3 ) {
72- state_length_ = next + 1 ;
73- } else if (!state_length_) {
74- state_ = State::PreLeadInPause;
153+ case State::Body:
154+ serialise (block_->data [state_length_]);
155+ ++state_length_;
156+ if (state_length_ == block_->data .size ()) {
157+ state_ = State::LeadIn;
158+ state_length_ = 0 ;
159+ ++block_;
75160 }
76- }
77161 break ;
78162
163+ default : __builtin_unreachable ();
79164 }
80165}
81-
82- void CoCoCAS::Serialiser::reset () {
83- // Add 1s of blank before the tape begins.
84- state_ = State::PreLeadInPause;
85- file_.seek (0 , Whence::SET );
86- }
0 commit comments