1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-19 08:31:11 +00:00

Merge pull request #1259 from TomHarte/NIBSlipBits

NIB: switch to a strategy supporting non-standard formats.
This commit is contained in:
Thomas Harte 2023-12-11 10:33:22 -05:00 committed by GitHub
commit 50b4132db7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 75 additions and 69 deletions

View File

@ -23,8 +23,14 @@ DiskII::DiskII(int clock_rate) :
clock_rate_(clock_rate), clock_rate_(clock_rate),
inputs_(input_command), inputs_(input_command),
drives_{ drives_{
Storage::Disk::Drive{clock_rate, 300, 1}, // Bit of a hack here: drives are marginally slowed down compared to real drives
Storage::Disk::Drive{clock_rate, 300, 1} // in order to accomodate NIB files, which usually carry more data than will
// physically fit on a track once slip bits are reinserted.
//
// I don't like the coupling here.
// TODO: resolve better, somehow.
Storage::Disk::Drive{clock_rate, 295, 1},
Storage::Disk::Drive{clock_rate, 295, 1}
} }
{ {
drives_[0].set_clocking_hint_observer(this); drives_[0].set_clocking_hint_observer(this);

View File

@ -55,95 +55,85 @@ long NIB::file_offset(Track::Address address) {
} }
std::shared_ptr<::Storage::Disk::Track> NIB::get_track_at_position(::Storage::Disk::Track::Address address) { 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. // NIBs contain data for a fixed quantity of integer-position tracks underneath a single head only.
//
// Therefore:
// * reject any attempt to read from the second head;
// * treat 3/4 of any physical track as formatted, the remaining quarter as unformatted; and
// * reject any attempt to read beyond the defined number of tracks.
if(address.head) return nullptr; if(address.head) return nullptr;
if((address.position.as_quarter() & 3) == 3) return nullptr;
if(size_t(address.position.as_int()) >= number_of_tracks) return nullptr;
long offset = file_offset(address); const long offset = file_offset(address);
std::vector<uint8_t> track_data; std::vector<uint8_t> track_data;
{ {
std::lock_guard lock_guard(file_.get_file_access_mutex()); std::lock_guard lock_guard(file_.get_file_access_mutex());
if(cached_offset_ == offset && cached_track_) {
return cached_track_;
}
file_.seek(offset, SEEK_SET); file_.seek(offset, SEEK_SET);
track_data = file_.read(track_length); track_data = file_.read(track_length);
} }
// NIB files leave sync bytes implicit and make no guarantees // NIB files leave sync bytes implicit and make no guarantees
// about overall track positioning. My current best-guess attempt // about overall track positioning. The attempt works by locating
// is to seek sector prologues then work backwards, inserting sync // any single run of FF that is sufficiently long and marking the last
// bits into [at most 5] preceding FFs. This is intended to put the // five as including slip bits.
// Disk II into synchronisation just before each sector. std::set<size_t> sync_locations;
std::size_t start_index = 0;
std::set<size_t> 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) { for(size_t index = 0; index < track_data.size(); ++index) {
// This is a D5 AA... // Count the number of FFs starting from here.
if(track_data[index] == 0xd5 && track_data[(index+1)%track_data.size()] == 0xaa) { size_t length = 0;
// ... count backwards to find out where the preceding FFs started. size_t end = index;
size_t start = index - 1; while(track_data[end] == 0xff) {
size_t length = 0; end = (end + 1) % track_data.size();
while(track_data[start] == 0xff && length < 5) { ++length;
start = (start + track_data.size() - 1) % track_data.size(); }
++length;
// If that's at least five, regress and mark all as syncs.
if(length >= 5) {
for(int c = 0; c < 5; c++) {
end = (end + track_data.size() - 1) % track_data.size();
sync_locations.insert(end);
} }
// Record a sync position only if there were at least five FFs, and // Experimental!! Permit only one run of sync locations.
// sync only in the final five. One of the many crazy fictions of NIBs // That should synchronise the Disk II to the nibble stream
// is the fixed track length in bytes, which is quite long. So the aim // such that it remains synchronised from then on. At least,
// is to be as conservative as possible with sync placement. // while this remains a read-only format.
if(length == 5) { break;
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; PCMSegment segment;
std::size_t index = 0;
// If the track started in a sync block, write sync first. while(index < track_data.size()) {
if(start_index) { // Deal with a run of sync values, if present.
segment += Encodings::AppleGCR::six_and_two_sync(int(start_index)); const auto sync_start = index;
} while(sync_locations.find(index) != sync_locations.end() && index < track_data.size()) {
++index;
std::size_t index = start_index; }
for(const auto location: sync_starts) { if(index != sync_start) {
// Write data from index to sync_start. segment += Encodings::AppleGCR::six_and_two_sync(int(index - 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<uint8_t> 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 // Deal with regular data.
// any before the end of data. const auto data_start = index;
index = location; while(sync_locations.find(index) == sync_locations.end() && index < track_data.size()) {
while(index < track_length && track_data[index] == 0xff)
++index; ++index;
}
if(index - location) if(index != data_start) {
segment += Encodings::AppleGCR::six_and_two_sync(int(index - location)); std::vector<uint8_t> data_segment(
track_data.begin() + ptrdiff_t(data_start),
track_data.begin() + ptrdiff_t(index));
segment += PCMSegment(data_segment);
}
} }
// If there's still data remaining on the track, write it out. If a sync ran over std::lock_guard lock_guard(file_.get_file_access_mutex());
// the notional index hole, the loop above will already have completed the track cached_offset_ = offset;
// with sync, so no need to deal with that case here. cached_track_ = std::make_shared<PCMTrack>(segment);
if(index < track_length) { return cached_track_;
std::vector<uint8_t> data_segment(
track_data.begin() + ptrdiff_t(index),
track_data.end());
segment += PCMSegment(data_segment);
}
return std::make_shared<PCMTrack>(segment);
} }
void NIB::set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) { void NIB::set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) {
@ -194,4 +184,5 @@ void NIB::set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tra
file_.seek(file_offset(track.first), SEEK_SET); file_.seek(file_offset(track.first), SEEK_SET);
file_.write(track.second); file_.write(track.second);
} }
cached_track_ = nullptr; // Conservative, but safe.
} }

View File

@ -11,6 +11,9 @@
#include "../DiskImage.hpp" #include "../DiskImage.hpp"
#include "../../../FileHolder.hpp" #include "../../../FileHolder.hpp"
#include "../../Track/PCMTrack.hpp"
#include <memory>
namespace Storage::Disk { namespace Storage::Disk {
@ -33,6 +36,12 @@ class NIB: public DiskImage {
FileHolder file_; FileHolder file_;
long get_file_offset_for_position(Track::Address address); long get_file_offset_for_position(Track::Address address);
long file_offset(Track::Address address); long file_offset(Track::Address address);
// Cache for the last-generated track, given that head steps on an Apple II
// occur in quarter-track increments, so there'll routinely be four gets in
// a row for the same data.
long cached_offset_ = 0;
std::shared_ptr<Storage::Disk::PCMTrack> cached_track_;
}; };
} }