// // NIB.cpp // Clock Signal // // Created by Thomas Harte on 21/04/2018. // Copyright 2018 Thomas Harte. All rights reserved. // #include "NIB.hpp" #include "../../Track/PCMTrack.hpp" #include "../../Track/TrackSerialiser.hpp" #include "../../Encodings/AppleGCR/Encoder.hpp" #include "../../Encodings/AppleGCR/Encoder.hpp" #include "../../Encodings/AppleGCR/SegmentParser.hpp" #include using namespace Storage::Disk; namespace { const long track_length = 6656; const std::size_t number_of_tracks = 35; } NIB::NIB(const std::string &file_name) : file_(file_name) { // A NIB should be 35 tracks, each 6656 bytes long. if(file_.stats().st_size != track_length*number_of_tracks) { throw Error::InvalidFormat; } // A real NIB should have every single top bit set. Yes, 1/8th of the // file size is a complete waste. But it provides a hook for validation. while(true) { uint8_t next = file_.get8(); if(file_.eof()) break; if(!(next & 0x80)) throw Error::InvalidFormat; } } HeadPosition NIB::get_maximum_head_position() { return HeadPosition(number_of_tracks); } bool NIB::get_is_read_only() { return file_.get_is_known_read_only(); } long NIB::file_offset(Track::Address address) { return long(address.position.as_int()) * track_length; } std::shared_ptr<::Storage::Disk::Track> NIB::get_track_at_position(::Storage::Disk::Track::Address address) { // NIBs contain data for even-numbered tracks underneath a single head only. if(address.head) return nullptr; long offset = file_offset(address); std::vector track_data; { std::lock_guard lock_guard(file_.get_file_access_mutex()); file_.seek(offset, SEEK_SET); track_data = file_.read(track_length); } // NIB files leave sync bytes implicit and make no guarantees // about overall track positioning. My current best-guess attempt // is to seek sector prologues then work backwards, inserting sync // bits into [at most 5] preceding FFs. This is intended to put the // Disk II into synchronisation just before each sector. std::size_t start_index = 0; std::set sync_starts; // Establish where syncs start by finding instances of 0xd5 0xaa and then regressing // from each along all preceding FFs. for(size_t index = 0; index < track_data.size(); ++index) { // This is a D5 AA... if(track_data[index] == 0xd5 && track_data[(index+1)%track_data.size()] == 0xaa) { // ... count backwards to find out where the preceding FFs started. size_t start = index - 1; size_t length = 0; while(track_data[start] == 0xff && length < 5) { start = (start + track_data.size() - 1) % track_data.size(); ++length; } // Record a sync position only if there were at least five FFs, and // sync only in the final five. One of the many crazy fictions of NIBs // is the fixed track length in bytes, which is quite long. So the aim // is to be as conservative as possible with sync placement. if(length == 5) { sync_starts.insert((start + 1) % track_data.size()); // If the apparent start of this sync area is 'after' the start, then // this sync period overlaps position zero. So this track will start // in a sync block. if(start > index) start_index = start; } } } PCMSegment segment; // If the track started in a sync block, write sync first. if(start_index) { segment += Encodings::AppleGCR::six_and_two_sync(int(start_index)); } std::size_t index = start_index; for(const auto location: sync_starts) { // Write data from index to sync_start. if(location > index) { // This is the usual case; the only occasion on which it won't be true is // when the initial sync was detected to carry over the index hole, // in which case there's nothing to copy. std::vector data_segment( track_data.begin() + ptrdiff_t(index), track_data.begin() + ptrdiff_t(location)); segment += PCMSegment(data_segment); } // Add a sync from sync_start to end of 0xffs, if there are // any before the end of data. index = location; while(index < track_length && track_data[index] == 0xff) ++index; if(index - location) segment += Encodings::AppleGCR::six_and_two_sync(int(index - location)); } // If there's still data remaining on the track, write it out. If a sync ran over // the notional index hole, the loop above will already have completed the track // with sync, so no need to deal with that case here. if(index < track_length) { std::vector data_segment( track_data.begin() + ptrdiff_t(index), track_data.end()); segment += PCMSegment(data_segment); } return std::make_shared(segment); } void NIB::set_tracks(const std::map> &tracks) { std::map> tracks_by_address; // Convert to a map from address to a vector of data that contains the NIB representation // of the track. for(const auto &pair: tracks) { // Grab the track bit stream. auto segment = Storage::Disk::track_serialisation(*pair.second, Storage::Time(1, 50000)); // Process to eliminate all sync bits. std::vector track; track.reserve(track_length); uint8_t shifter = 0; int bit_count = 0; size_t sync_location = 0, location = 0; for(const auto bit: segment.data) { shifter = uint8_t((shifter << 1) | (bit ? 1 : 0)); ++bit_count; ++location; if(shifter & 0x80) { track.push_back(shifter); if(bit_count == 10) { sync_location = location; } shifter = 0; bit_count = 0; } } // Trim or pad out to track_length. if(track.size() > track_length) { track.resize(track_length); } else { while(track.size() < track_length) { std::vector extra_data(size_t(track_length) - track.size(), 0xff); track.insert(track.begin() + ptrdiff_t(sync_location), extra_data.begin(), extra_data.end()); } } tracks_by_address[pair.first] = std::move(track); } // Lock the file and spool out. std::lock_guard lock_guard(file_.get_file_access_mutex()); for(const auto &track: tracks_by_address) { file_.seek(file_offset(track.first), SEEK_SET); file_.write(track.second); } }