1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-27 16:31:31 +00:00

Merge pull request #1265 from TomHarte/PCHD

Introduce high-density tracks.
This commit is contained in:
Thomas Harte 2023-12-12 12:14:48 -05:00 committed by GitHub
commit 02b2b9d47e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 340 additions and 237 deletions

View File

@ -19,10 +19,10 @@ using namespace Analyser::Static::Acorn;
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
// c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format
auto catalogue = std::make_unique<Catalogue>();
Storage::Encodings::MFM::Parser parser(false, disk);
Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Single, disk);
const Storage::Encodings::MFM::Sector *const names = parser.get_sector(0, 0, 0);
const Storage::Encodings::MFM::Sector *const details = parser.get_sector(0, 0, 1);
const Storage::Encodings::MFM::Sector *const names = parser.sector(0, 0, 0);
const Storage::Encodings::MFM::Sector *const details = parser.sector(0, 0, 1);
if(!names || !details) return nullptr;
if(names->samples.empty() || details->samples.empty()) return nullptr;
@ -65,7 +65,7 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
uint8_t track = uint8_t(start_sector / 10);
start_sector++;
Storage::Encodings::MFM::Sector *next_sector = parser.get_sector(0, track, sector);
const Storage::Encodings::MFM::Sector *next_sector = parser.sector(0, track, sector);
if(!next_sector) break;
long length_from_sector = std::min(data_length, 256l);
@ -84,15 +84,15 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
*/
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
auto catalogue = std::make_unique<Catalogue>();
Storage::Encodings::MFM::Parser parser(true, disk);
Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
Storage::Encodings::MFM::Sector *free_space_map_second_half = parser.get_sector(0, 0, 1);
const Storage::Encodings::MFM::Sector *free_space_map_second_half = parser.sector(0, 0, 1);
if(!free_space_map_second_half) return nullptr;
std::vector<uint8_t> root_directory;
root_directory.reserve(5 * 256);
for(uint8_t c = 2; c < 7; c++) {
const Storage::Encodings::MFM::Sector *const sector = parser.get_sector(0, 0, c);
const Storage::Encodings::MFM::Sector *const sector = parser.sector(0, 0, c);
if(!sector) return nullptr;
root_directory.insert(root_directory.end(), sector->samples[0].begin(), sector->samples[0].end());
}
@ -166,7 +166,7 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
new_file.data.reserve(size);
while(new_file.data.size() < size) {
const Storage::Encodings::MFM::Sector *const sector = parser.get_sector(start_sector / (80 * 16), (start_sector / 16) % 80, start_sector % 16);
const Storage::Encodings::MFM::Sector *const sector = parser.sector(start_sector / (80 * 16), (start_sector / 16) % 80, start_sector % 16);
if(!sector) break;
const auto length_from_sector = std::min(size - new_file.data.size(), sector->samples[0].size());

View File

@ -159,8 +159,8 @@ void InspectCatalogue(
}
bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
Storage::Encodings::MFM::Parser parser(true, disk);
Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41);
Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
const Storage::Encodings::MFM::Sector *boot_sector = parser.sector(0, 0, 0x41);
if(boot_sector != nullptr && !boot_sector->samples.empty() && boot_sector->samples[0].size() == 512) {
// Check that the first 64 bytes of the sector aren't identical; if they are then probably
// this disk was formatted and the filler byte never replaced.

View File

@ -37,7 +37,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
@returns a sector if one was found; @c nullptr otherwise.
*/
std::shared_ptr<Sector> get_sector(uint8_t track, uint8_t sector) {
std::shared_ptr<Sector> sector(uint8_t track, uint8_t sector) {
int difference = int(track) - int(track_);
track_ = track;
@ -182,7 +182,7 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
uint8_t next_track = 18;
uint8_t next_sector = 1;
while(1) {
sector = parser.get_sector(next_track, next_sector);
sector = parser.sector(next_track, next_sector);
if(!sector) break;
directory.insert(directory.end(), sector->data.begin(), sector->data.end());
next_track = sector->data[0];
@ -221,7 +221,7 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
bool is_first_sector = true;
while(next_track) {
sector = parser.get_sector(next_track, next_sector);
sector = parser.sector(next_track, next_sector);
if(!sector) break;
next_track = sector->data[0];

View File

@ -44,7 +44,7 @@ Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(const Media &me
Storage::Disk::track_serialisation(
*track_zero,
Storage::Encodings::MFM::MFMBitLength
), true);
), Storage::Encodings::MFM::Density::Double);
// If no sectors were found, assume this disk was either single density or high density, which both imply the PC.
if(sector_map.empty() || sector_map.size() > 10) {

View File

@ -85,7 +85,7 @@ bool is_microdisc(Storage::Encodings::MFM::Parser &parser) {
/*
The Microdisc boot sector is sector 2 of track 0 and contains a 23-byte signature.
*/
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 2);
const Storage::Encodings::MFM::Sector *sector = parser.sector(0, 0, 2);
if(!sector) return false;
if(sector->samples.empty()) return false;
@ -108,7 +108,7 @@ bool is_400_loader(Storage::Encodings::MFM::Parser &parser, uint16_t range_start
use disassembly to test for likely matches.
*/
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 1);
const Storage::Encodings::MFM::Sector *sector = parser.sector(0, 0, 1);
if(!sector) return false;
if(sector->samples.empty()) return false;
@ -175,7 +175,7 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med
// 8-DOS is recognised by a dedicated Disk II analyser, so check only for Microdisc,
// Jasmin and BD-DOS formats here.
for(auto &disk: media.disks) {
Storage::Encodings::MFM::Parser parser(true, disk);
Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
if(is_microdisc(parser)) {
target->disk_interface = Target::DiskInterface::Microdisc;

View File

@ -33,14 +33,14 @@ bool IsSpectrumTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
}
bool IsSpectrumDisk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
Storage::Encodings::MFM::Parser parser(true, disk);
Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
// Get logical sector 1; the Spectrum appears to support various physical
// sectors as sector 1.
Storage::Encodings::MFM::Sector *boot_sector = nullptr;
const Storage::Encodings::MFM::Sector *boot_sector = nullptr;
uint8_t sector_mask = 0;
while(!boot_sector) {
boot_sector = parser.get_sector(0, 0, sector_mask + 1);
boot_sector = parser.sector(0, 0, sector_mask + 1);
sector_mask += 0x40;
if(!sector_mask) break;
}

View File

@ -34,8 +34,7 @@
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../Storage/Disk/Track/TrackSerialiser.hpp"
#include "../../Storage/Disk/Encodings/MFM/Constants.hpp"
#include "../../Storage/Disk/Encodings/MFM/SegmentParser.hpp"
#include "../../Storage/Disk/Encodings/MFM/Parser.hpp"
#include "../AudioProducer.hpp"
#include "../KeyboardMachine.hpp"
@ -150,50 +149,32 @@ class FloppyController {
auto target = decoder_.geometry();
bool complete = false;
while(!complete) {
bool found_sector = false;
const auto sector = drives_[decoder_.target().drive].sector(target.head, target.sector);
for(auto &pair: drives_[decoder_.target().drive].sectors(decoder_.target().head)) {
// TODO: I suspect that not all these fields are tested for equality.
if(
(pair.second.address.track == target.cylinder) &&
(pair.second.address.sector == target.sector) &&
(pair.second.address.side == target.head) &&
(pair.second.size == target.size) &&
(pair.second.is_deleted == (decoder_.command() == Command::ReadDeletedData))
) {
found_sector = true;
bool wrote_in_full = true;
if(sector) {
// TODO: I _think_ I'm supposed to validate the rest of the address here?
for(int c = 0; c < 128 << target.size; c++) {
const auto access_result = dma_.write(2, pair.second.samples[0].data()[c]);
switch(access_result) {
default: break;
case AccessResult::NotAccepted:
complete = true;
wrote_in_full = false;
break;
case AccessResult::AcceptedWithEOP:
complete = true;
break;
}
if(access_result != AccessResult::Accepted) {
break;
}
}
for(int c = 0; c < 128 << target.size; c++) {
const auto access_result = dma_.write(2, sector->samples[0].data()[c]);
switch(access_result) {
// Default: keep going.
default: continue;
if(!wrote_in_full) {
status_.set(Intel::i8272::Status1::OverRun);
status_.set(Intel::i8272::Status0::AbnormalTermination);
// Anything else: update flags and exit.
case AccessResult::NotAccepted:
complete = true;
status_.set(Intel::i8272::Status1::OverRun);
status_.set(Intel::i8272::Status0::AbnormalTermination);
break;
case AccessResult::AcceptedWithEOP:
complete = true;
break;
}
++target.sector; // TODO: multitrack?
break;
}
}
if(!found_sector) {
++target.sector; // TODO: multitrack?
} else {
status_.set(Intel::i8272::Status1::EndOfCylinder);
status_.set(Intel::i8272::Status0::AbnormalTermination);
break;
@ -342,58 +323,19 @@ class FloppyController {
bool exists = true;
bool has_disk() const {
return bool(disk);
return static_cast<bool>(parser_);
}
void set_disk(std::shared_ptr<Storage::Disk::Disk> image) {
disk = image;
cached.clear();
parser_ = std::make_unique<Storage::Encodings::MFM::Parser>(image);
}
Storage::Encodings::MFM::SectorMap &sectors(bool side) {
if(cached.track == track && cached.side == side) {
return cached.sectors;
}
cached.track = track;
cached.side = side;
cached.sectors.clear();
if(!disk) {
return cached.sectors;
}
auto raw_track = disk->get_track_at_position(
Storage::Disk::Track::Address(
side,
Storage::Disk::HeadPosition(track)
)
);
if(!raw_track) {
return cached.sectors;
}
const bool is_double_density = true; // TODO: use MFM flag here.
auto serialisation = Storage::Disk::track_serialisation(
*raw_track,
is_double_density ? Storage::Encodings::MFM::MFMBitLength : Storage::Encodings::MFM::FMBitLength
);
cached.sectors = Storage::Encodings::MFM::sectors_from_segment(std::move(serialisation), is_double_density);
return cached.sectors;
const Storage::Encodings::MFM::Sector *sector(int head, uint8_t sector) {
return parser_ ? parser_->sector(head, track, sector) : nullptr;
}
private:
struct {
uint8_t track = 0xff;
bool side;
Storage::Encodings::MFM::SectorMap sectors;
void clear() {
track = 0xff;
sectors.clear();
}
} cached;
std::shared_ptr<Storage::Disk::Disk> disk;
std::unique_ptr<Storage::Encodings::MFM::Parser> parser_;
} drives_[4];

View File

@ -32,7 +32,7 @@ void MFMController::set_is_double_density(bool is_double_density) {
bit_length.clock_rate = is_double_density ? 500000 : 250000;
set_expected_bit_length(bit_length);
shifter_.set_is_double_density(is_double_density);
shifter_.set_is_mfm(is_double_density);
}
bool MFMController::get_is_double_density() {

View File

@ -39,7 +39,7 @@ AcornADF::AcornADF(const std::string &file_name) : MFMSectorDump(file_name) {
head_count_ = 1 + (file_.stats().st_size > sectors_per_track * sizeT(128 << sector_size) * 80);
// Announce disk geometry.
set_geometry(sectors_per_track, sector_size, 0, true);
set_geometry(sectors_per_track, sector_size, 0, Encodings::MFM::Density::Double);
}
HeadPosition AcornADF::get_maximum_head_position() {

View File

@ -214,7 +214,11 @@ std::shared_ptr<Track> CPCDSK::get_track_at_position(::Storage::Disk::Track::Add
}
// TODO: FM encoding, data rate?
return Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors, track->gap3_length, track->filler_byte);
return Storage::Encodings::MFM::TrackWithSectors(
Storage::Encodings::MFM::Density::Double,
sectors,
track->gap3_length,
track->filler_byte);
}
void CPCDSK::set_tracks(const std::map<::Storage::Disk::Track::Address, std::shared_ptr<::Storage::Disk::Track>> &tracks) {
@ -225,7 +229,7 @@ void CPCDSK::set_tracks(const std::map<::Storage::Disk::Track::Address, std::sha
std::map<std::size_t, Storage::Encodings::MFM::Sector> sectors =
Storage::Encodings::MFM::sectors_from_segment(
Storage::Disk::track_serialisation(*pair.second, is_double_density ? Storage::Encodings::MFM::MFMBitLength : Storage::Encodings::MFM::FMBitLength),
is_double_density);
Storage::Encodings::MFM::Density::Double);
// Find slot for track, making it if neccessary.
const std::size_t chronological_track = index_for_track(pair.first);

View File

@ -41,7 +41,12 @@ FAT12::FAT12(const std::string &file_name) :
}
if(log_sector_size >= 5) throw Error::InvalidFormat;
set_geometry(sector_count_, log_sector_size, 1, true);
set_geometry(
sector_count_,
log_sector_size,
1,
sector_count_ > 10 ? Encodings::MFM::Density::High : Encodings::MFM::Density::Double
);
}
HeadPosition FAT12::get_maximum_head_position() {

View File

@ -157,7 +157,13 @@ std::shared_ptr<::Storage::Disk::Track> IMD::get_track_at_position(::Storage::Di
// Mode also indicates data density, but I don't have a good strategy for reconciling that if
// it were to disagree with the density implied by the quantity of sectors. So a broad 'is it MFM' test is
// applied only.
return (mode >= 3) ?
Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors) :
Storage::Encodings::MFM::GetFMTrackWithSectors(sectors);
using namespace Storage::Encodings::MFM;
if(mode < 3) {
return TrackWithSectors(Density::Single, sectors);
}
if(sector_size * sector_count >= 6912) {
return TrackWithSectors(Density::High, sectors);
} else {
return TrackWithSectors(Density::Double, sectors);
}
}

View File

@ -14,10 +14,10 @@ using namespace Storage::Disk;
MFMSectorDump::MFMSectorDump(const std::string &file_name) : file_(file_name) {}
void MFMSectorDump::set_geometry(int sectors_per_track, uint8_t sector_size, uint8_t first_sector, bool is_double_density) {
void MFMSectorDump::set_geometry(int sectors_per_track, uint8_t sector_size, uint8_t first_sector, Encodings::MFM::Density density) {
sectors_per_track_ = sectors_per_track;
sector_size_ = sector_size;
is_double_density_ = is_double_density;
density_ = density;
first_sector_ = first_sector;
}
@ -34,7 +34,14 @@ std::shared_ptr<Track> MFMSectorDump::get_track_at_position(Track::Address addre
file_.read(sectors, sizeof(sectors));
}
return track_for_sectors(sectors, sectors_per_track_, uint8_t(address.position.as_int()), uint8_t(address.head), first_sector_, sector_size_, is_double_density_);
return track_for_sectors(
sectors,
sectors_per_track_,
uint8_t(address.position.as_int()),
uint8_t(address.head),
first_sector_,
sector_size_,
density_);
}
void MFMSectorDump::set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) {
@ -44,7 +51,13 @@ void MFMSectorDump::set_tracks(const std::map<Track::Address, std::shared_ptr<Tr
// in one loop, then write in another.
for(const auto &track : tracks) {
decode_sectors(*track.second, parsed_track, first_sector_, first_sector_ + uint8_t(sectors_per_track_-1), sector_size_, is_double_density_);
decode_sectors(
*track.second,
parsed_track,
first_sector_,
first_sector_ + uint8_t(sectors_per_track_-1),
sector_size_,
density_);
const long file_offset = get_file_offset_for_position(track.first);
std::lock_guard lock_guard(file_.get_file_access_mutex());

View File

@ -11,6 +11,7 @@
#include "../DiskImage.hpp"
#include "../../../FileHolder.hpp"
#include "../../Encodings/MFM/Constants.hpp"
#include <string>
@ -29,14 +30,14 @@ class MFMSectorDump: public DiskImage {
protected:
Storage::FileHolder file_;
void set_geometry(int sectors_per_track, uint8_t sector_size, uint8_t first_sector, bool is_double_density);
void set_geometry(int sectors_per_track, uint8_t sector_size, uint8_t first_sector, Encodings::MFM::Density density);
private:
virtual long get_file_offset_for_position(Track::Address address) = 0;
int sectors_per_track_ = 0;
uint8_t sector_size_ = 0;
bool is_double_density_ = true;
Encodings::MFM::Density density_ = Encodings::MFM::Density::Single;
uint8_t first_sector_ = 0;
};

View File

@ -84,7 +84,7 @@ std::shared_ptr<::Storage::Disk::Track> MSA::get_track_at_position(::Storage::Di
const auto &track = uncompressed_tracks_[size_t(position - starting_track_) * size_t(sides_) + size_t(address.head)];
assert(!track.empty());
return track_for_sectors(track.data(), sectors_per_track_, uint8_t(position), uint8_t(address.head), 1, 2, true);
return track_for_sectors(track.data(), sectors_per_track_, uint8_t(position), uint8_t(address.head), 1, 2, Storage::Encodings::MFM::Density::Double);
}
HeadPosition MSA::get_maximum_head_position() {

View File

@ -119,7 +119,7 @@ void OricMFMDSK::set_tracks(const std::map<Track::Address, std::shared_ptr<Track
for(const auto &track : tracks) {
PCMSegment segment = Storage::Disk::track_serialisation(*track.second, Storage::Encodings::MFM::MFMBitLength);
Storage::Encodings::MFM::Shifter shifter;
shifter.set_is_double_density(true);
shifter.set_is_mfm(true);
shifter.set_should_obey_syncs(true);
std::vector<uint8_t> parsed_track;
int size = 0;

View File

@ -47,7 +47,8 @@ PCBooter::PCBooter(const std::string &file_name) :
break;
}
set_geometry(sector_count_, 2, 1, true);
// TODO: probably single density, actually?
set_geometry(sector_count_, 2, 1, Encodings::MFM::Density::Double);
// TODO: check that an appropriate INT or similar is in the boot sector?
// Should probably factor out the "does this look like a PC boot sector?" test,

View File

@ -29,7 +29,7 @@ SSD::SSD(const std::string &file_name) : MFMSectorDump(file_name) {
if(track_count_ < 40) track_count_ = 40;
else if(track_count_ < 80) track_count_ = 80;
set_geometry(sectors_per_track, sector_size, 0, false);
set_geometry(sectors_per_track, sector_size, 0, Encodings::MFM::Density::Single);
}
HeadPosition SSD::get_maximum_head_position() {

View File

@ -136,7 +136,7 @@ class TrackConstructor {
using Shifter = Storage::Encodings::MFM::Shifter;
Shifter shifter;
shifter.set_should_obey_syncs(true);
shifter.set_is_double_density(true);
shifter.set_is_mfm(true);
result.emplace_back();
@ -469,7 +469,7 @@ std::shared_ptr<::Storage::Disk::Track> STX::get_track_at_position(::Storage::Di
// If this is a trivial .ST-style sector dump, life is easy.
if(!(flags & 1)) {
const auto sector_contents = file_.read(sector_count * 512);
return track_for_sectors(sector_contents.data(), sector_count, uint8_t(address.position.as_int()), uint8_t(address.head), 1, 2, true);
return track_for_sectors(sector_contents.data(), sector_count, uint8_t(address.position.as_int()), uint8_t(address.head), 1, 2, Storage::Encodings::MFM::Density::Double);
}
// Grab sector records, if provided.

View File

@ -18,7 +18,15 @@
using namespace Storage::Disk;
std::shared_ptr<Track> Storage::Disk::track_for_sectors(const uint8_t *const source, int number_of_sectors, uint8_t track, uint8_t side, uint8_t first_sector, uint8_t size, bool is_double_density) {
std::shared_ptr<Track> Storage::Disk::track_for_sectors(
const uint8_t *const source,
int number_of_sectors,
uint8_t track,
uint8_t side,
uint8_t first_sector,
uint8_t size,
Storage::Encodings::MFM::Density density
) {
std::vector<Storage::Encodings::MFM::Sector> sectors;
size_t byte_size = size_t(128 << size);
@ -39,17 +47,27 @@ std::shared_ptr<Track> Storage::Disk::track_for_sectors(const uint8_t *const sou
}
if(!sectors.empty()) {
return is_double_density ? Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors) : Storage::Encodings::MFM::GetFMTrackWithSectors(sectors);
return TrackWithSectors(density, sectors);
}
return nullptr;
}
void Storage::Disk::decode_sectors(Track &track, uint8_t *const destination, uint8_t first_sector, uint8_t last_sector, uint8_t sector_size, bool is_double_density) {
void Storage::Disk::decode_sectors(
const Track &track,
uint8_t *const destination,
uint8_t first_sector,
uint8_t last_sector,
uint8_t sector_size,
Storage::Encodings::MFM::Density density
) {
std::map<std::size_t, Storage::Encodings::MFM::Sector> sectors =
Storage::Encodings::MFM::sectors_from_segment(
Storage::Disk::track_serialisation(track, is_double_density ? Storage::Encodings::MFM::MFMBitLength : Storage::Encodings::MFM::FMBitLength),
is_double_density);
Storage::Disk::track_serialisation(
track,
Storage::Encodings::MFM::bit_length(density)
),
density);
std::size_t byte_size = size_t(128 << sector_size);
for(const auto &pair : sectors) {

View File

@ -13,10 +13,13 @@
#include <memory>
#include <vector>
#include "../../../Encodings/MFM/Encoder.hpp"
namespace Storage::Disk {
std::shared_ptr<Track> track_for_sectors(const uint8_t *source, int number_of_sectors, uint8_t track, uint8_t side, uint8_t first_sector, uint8_t size, bool is_double_density);
void decode_sectors(Track &track, uint8_t *destination, uint8_t first_sector, uint8_t last_sector, uint8_t sector_size, bool is_double_density);
std::shared_ptr<Track> track_for_sectors(const uint8_t *source, int number_of_sectors, uint8_t track, uint8_t side, uint8_t first_sector, uint8_t size, Storage::Encodings::MFM::Density density);
void decode_sectors(const Track &track, uint8_t *destination, uint8_t first_sector, uint8_t last_sector, uint8_t sector_size, Storage::Encodings::MFM::Density density);
}

View File

@ -13,6 +13,14 @@
namespace Storage::Encodings::MFM {
enum class Density {
Single, Double, High
};
constexpr bool is_mfm(Density density) {
return density != Density::Single;
}
const uint8_t IndexAddressByte = 0xfc;
const uint8_t IDAddressByte = 0xfe;
const uint8_t DataAddressByte = 0xfb;
@ -30,9 +38,18 @@ const uint16_t MFMPostSyncCRCValue = 0xcdb4; // the value the CRC generator sho
const uint8_t MFMIndexSyncByteValue = 0xc2;
const uint8_t MFMSyncByteValue = 0xa1;
const Time HDMFMBitLength = Time(1, 200000);
const Time MFMBitLength = Time(1, 100000);
const Time FMBitLength = Time(1, 50000);
constexpr Time bit_length(Density density) {
switch(density) {
case Density::Single: return FMBitLength;
case Density::Double: return MFMBitLength;
case Density::High: return HDMFMBitLength;
}
}
}
#endif /* Constants_h */

View File

@ -18,6 +18,62 @@
using namespace Storage::Encodings::MFM;
namespace {
std::vector<const Sector *> sector_pointers(const std::vector<Sector> &sectors) {
std::vector<const Sector *> pointers;
for(const Sector &sector: sectors) {
pointers.push_back(&sector);
}
return pointers;
}
template <Density density> struct Defaults;
template <> struct Defaults<Density::Single> {
static constexpr size_t expected_track_bytes = 6250;
static constexpr size_t post_index_address_mark_bytes = 26;
static constexpr uint8_t post_index_address_mark_value = 0xff;
static constexpr size_t pre_address_mark_bytes = 6;
static constexpr size_t post_address_address_mark_bytes = 11;
static constexpr uint8_t post_address_address_mark_value = 0xff;
static constexpr size_t pre_data_mark_bytes = 6;
static constexpr size_t post_data_bytes = 27;
static constexpr uint8_t post_data_value = 0xff;
};
template <> struct Defaults<Density::Double> {
static constexpr size_t expected_track_bytes = 12500;
static constexpr size_t post_index_address_mark_bytes = 50;
static constexpr uint8_t post_index_address_mark_value = 0x4e;
static constexpr size_t pre_address_mark_bytes = 12;
static constexpr size_t post_address_address_mark_bytes = 22;
static constexpr uint8_t post_address_address_mark_value = 0x4e;
static constexpr size_t pre_data_mark_bytes = 12;
static constexpr size_t post_data_bytes = 54;
static constexpr uint8_t post_data_value = 0xff;
};
template <> struct Defaults<Density::High> {
static constexpr size_t expected_track_bytes = 25000;
static constexpr size_t post_index_address_mark_bytes = 50;
static constexpr uint8_t post_index_address_mark_value = 0x4e;
static constexpr size_t pre_address_mark_bytes = 12;
static constexpr size_t post_address_address_mark_bytes = 22;
static constexpr uint8_t post_address_address_mark_value = 0x4e;
static constexpr size_t pre_data_mark_bytes = 12;
static constexpr size_t post_data_bytes = 54;
static constexpr uint8_t post_data_value = 0xff;
};
}
enum class SurfaceItem {
Mark,
Data
@ -124,7 +180,7 @@ class FMEncoder: public Encoder {
};
template<class T> std::shared_ptr<Storage::Disk::Track>
GetTrackWithSectors(
GTrackWithSectors(
const std::vector<const Sector *> &sectors,
std::size_t post_index_address_mark_bytes, uint8_t post_index_address_mark_value,
std::size_t pre_address_mark_bytes,
@ -281,58 +337,55 @@ void Encoder::add_crc(bool incorrectly) {
add_byte((crc_value & 0xff) ^ (incorrectly ? 1 : 0));
}
const std::size_t Storage::Encodings::MFM::DefaultSectorGapLength = std::numeric_limits<std::size_t>::max();
namespace {
template <Density density>
std::shared_ptr<Storage::Disk::Track> TTrackWithSectors(
const std::vector<const Sector *> &sectors,
std::optional<std::size_t> sector_gap_length,
std::optional<uint8_t> sector_gap_filler_byte
) {
using EncoderT = std::conditional_t<density == Density::Single, FMEncoder, MFMEncoder>;
return GTrackWithSectors<EncoderT>(
sectors,
Defaults<density>::post_index_address_mark_bytes,
Defaults<density>::post_index_address_mark_value,
Defaults<density>::pre_address_mark_bytes,
Defaults<density>::post_address_address_mark_bytes,
sector_gap_filler_byte ? *sector_gap_filler_byte : Defaults<density>::post_address_address_mark_value,
Defaults<density>::pre_data_mark_bytes,
sector_gap_length ? *sector_gap_length : Defaults<density>::post_data_bytes,
Defaults<density>::post_data_value,
Defaults<density>::expected_track_bytes
);
}
static std::vector<const Sector *> sector_pointers(const std::vector<Sector> &sectors) {
std::vector<const Sector *> pointers;
for(const Sector &sector: sectors) {
pointers.push_back(&sector);
}
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::TrackWithSectors(
Density density,
const std::vector<Sector> &sectors,
std::optional<std::size_t> sector_gap_length,
std::optional<uint8_t> sector_gap_filler_byte
) {
return TrackWithSectors(
density,
sector_pointers(sectors),
sector_gap_length,
sector_gap_filler_byte
);
}
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::TrackWithSectors(
Density density,
const std::vector<const Sector *> &sectors,
std::optional<std::size_t> sector_gap_length,
std::optional<uint8_t> sector_gap_filler_byte
) {
switch(density) {
case Density::Single: return TTrackWithSectors<Density::Single>(sectors, sector_gap_length, sector_gap_filler_byte);
case Density::Double: return TTrackWithSectors<Density::Double>(sectors, sector_gap_length, sector_gap_filler_byte);
case Density::High: return TTrackWithSectors<Density::High>(sectors, sector_gap_length, sector_gap_filler_byte);
}
return pointers;
}
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector<Sector> &sectors, std::size_t sector_gap_length, uint8_t sector_gap_filler_byte) {
return GetTrackWithSectors<FMEncoder>(
sector_pointers(sectors),
26, 0xff,
6,
11, sector_gap_filler_byte,
6,
(sector_gap_length != DefaultSectorGapLength) ? sector_gap_length : 27, 0xff,
6250); // i.e. 250kbps (including clocks) * 60 = 15000kpm, at 300 rpm => 50 kbits/rotation => 6250 bytes/rotation
}
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector<const Sector *> &sectors, std::size_t sector_gap_length, uint8_t sector_gap_filler_byte) {
return GetTrackWithSectors<FMEncoder>(
sectors,
26, 0xff,
6,
11, sector_gap_filler_byte,
6,
(sector_gap_length != DefaultSectorGapLength) ? sector_gap_length : 27, 0xff,
6250); // i.e. 250kbps (including clocks) * 60 = 15000kpm, at 300 rpm => 50 kbits/rotation => 6250 bytes/rotation
}
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetMFMTrackWithSectors(const std::vector<Sector> &sectors, std::size_t sector_gap_length, uint8_t sector_gap_filler_byte) {
return GetTrackWithSectors<MFMEncoder>(
sector_pointers(sectors),
50, 0x4e,
12,
22, sector_gap_filler_byte,
12,
(sector_gap_length != DefaultSectorGapLength) ? sector_gap_length : 54, 0xff,
12500); // unintelligently: double the single-density bytes/rotation (or: 500kbps @ 300 rpm)
}
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetMFMTrackWithSectors(const std::vector<const Sector *> &sectors, std::size_t sector_gap_length, uint8_t sector_gap_filler_byte) {
return GetTrackWithSectors<MFMEncoder>(
sectors,
50, 0x4e,
12,
22, sector_gap_filler_byte,
12,
(sector_gap_length != DefaultSectorGapLength) ? sector_gap_length : 54, 0xff,
12500); // unintelligently: double the single-density bytes/rotation (or: 500kbps @ 300 rpm)
}
std::unique_ptr<Encoder> Storage::Encodings::MFM::GetMFMEncoder(std::vector<bool> &target, std::vector<bool> *fuzzy_target) {

View File

@ -11,34 +11,34 @@
#include <cstdint>
#include <memory>
#include <optional>
#include <vector>
#include "Constants.hpp"
#include "Sector.hpp"
#include "../../Track/Track.hpp"
#include "../../../../Numeric/CRC.hpp"
namespace Storage::Encodings::MFM {
extern const std::size_t DefaultSectorGapLength;
/*!
Converts a vector of sectors into a properly-encoded MFM track.
Converts a vector of sectors into a properly-encoded FM or MFM track.
@param sectors The sectors to write.
@param sector_gap_length If specified, sets the distance in whole bytes between each ID and its data.
@param sector_gap_filler_byte If specified, sets the value (unencoded) that is used to populate the gap between each ID and its data.
*/
std::shared_ptr<Storage::Disk::Track> GetMFMTrackWithSectors(const std::vector<Sector> &sectors, std::size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e);
std::shared_ptr<Storage::Disk::Track> GetMFMTrackWithSectors(const std::vector<const Sector *> &sectors, std::size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e);
std::shared_ptr<Storage::Disk::Track> TrackWithSectors(
Density density,
const std::vector<Sector> &sectors,
std::optional<std::size_t> sector_gap_length = std::nullopt,
std::optional<uint8_t> sector_gap_filler_byte = std::nullopt);
/*!
Converts a vector of sectors into a properly-encoded FM track.
@param sectors The sectors to write.
@param sector_gap_length If specified, sets the distance in whole bytes between each ID and its data.
@param sector_gap_filler_byte If specified, sets the value (unencoded) that is used to populate the gap between each ID and its data.
*/
std::shared_ptr<Storage::Disk::Track> GetFMTrackWithSectors(const std::vector<Sector> &sectors, std::size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0xff);
std::shared_ptr<Storage::Disk::Track> GetFMTrackWithSectors(const std::vector<const Sector *> &sectors, std::size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0xff);
std::shared_ptr<Storage::Disk::Track> TrackWithSectors(
Density density,
const std::vector<const Sector *> &sectors,
std::optional<std::size_t> sector_gap_length = std::nullopt,
std::optional<uint8_t> sector_gap_filler_byte = std::nullopt);
class Encoder {
public:

View File

@ -14,40 +14,58 @@
using namespace Storage::Encodings::MFM;
Parser::Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Disk> &disk) :
disk_(disk), is_mfm_(is_mfm) {}
Parser::Parser(const std::shared_ptr<Storage::Disk::Disk> &disk) :
disk_(disk) {}
void Parser::install_sectors_from_track(const Storage::Disk::Track::Address &address) {
Parser::Parser(Density density, const std::shared_ptr<Storage::Disk::Disk> &disk) :
disk_(disk), density_(density) {}
void Parser::install_track(const Storage::Disk::Track::Address &address) {
if(sectors_by_address_by_track_.find(address) != sectors_by_address_by_track_.end()) {
return;
}
std::shared_ptr<Storage::Disk::Track> track = disk_->get_track_at_position(address);
const auto track = disk_->get_track_at_position(address);
if(!track) {
return;
}
std::map<std::size_t, Sector> sectors = sectors_from_segment(
Storage::Disk::track_serialisation(*track, is_mfm_ ? MFMBitLength : FMBitLength),
is_mfm_);
std::map<int, Storage::Encodings::MFM::Sector> sectors_by_id;
for(const auto &sector : sectors) {
sectors_by_id.insert(std::make_pair(sector.second.address.sector, std::move(sector.second)));
if(density_) {
append(parse_track(*track, *density_), sectors_by_id);
return;
} else {
// Just try all three in succession.
append(parse_track(*track, Density::Single), sectors_by_id);
append(parse_track(*track, Density::Double), sectors_by_id);
append(parse_track(*track, Density::High), sectors_by_id);;
}
sectors_by_address_by_track_.insert(std::make_pair(address, std::move(sectors_by_id)));
sectors_by_address_by_track_.emplace(address, std::move(sectors_by_id));
}
Sector *Parser::get_sector(int head, int track, uint8_t sector) {
Disk::Track::Address address(head, Storage::Disk::HeadPosition(track));
install_sectors_from_track(address);
SectorMap Parser::parse_track(const Storage::Disk::Track &track, Density density) {
return sectors_from_segment(
Storage::Disk::track_serialisation(track, bit_length(density)),
density);
}
auto sectors = sectors_by_address_by_track_.find(address);
void Parser::append(const SectorMap &source, std::map<int, Sector> &destination) {
for(const auto &sector : source) {
destination.emplace(sector.second.address.sector, std::move(sector.second));
}
}
const Sector *Parser::sector(int head, int track, uint8_t sector) {
const Disk::Track::Address address(head, Storage::Disk::HeadPosition(track));
install_track(address);
const auto sectors = sectors_by_address_by_track_.find(address);
if(sectors == sectors_by_address_by_track_.end()) {
return nullptr;
}
auto stored_sector = sectors->second.find(sector);
const auto stored_sector = sectors->second.find(sector);
if(stored_sector == sectors->second.end()) {
return nullptr;
}

View File

@ -9,10 +9,14 @@
#ifndef Parser_hpp
#define Parser_hpp
#include "Constants.hpp"
#include "Sector.hpp"
#include "SegmentParser.hpp"
#include "../../Track/Track.hpp"
#include "../../Drive.hpp"
#include <optional>
namespace Storage::Encodings::MFM {
/*!
@ -20,7 +24,11 @@ namespace Storage::Encodings::MFM {
*/
class Parser {
public:
Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Disk> &disk);
/// Creates a parser that will only attempt to interpret the underlying disk as being of @c density.
Parser(Density density, const std::shared_ptr<Storage::Disk::Disk> &disk);
/// Creates a parser that will automatically try all available FM and MFM densities to try to extract sectors.
Parser(const std::shared_ptr<Storage::Disk::Disk> &disk);
/*!
Seeks to the physical track at @c head and @c track. Searches on it for a sector
@ -28,14 +36,24 @@ class Parser {
@returns a sector if one was found; @c nullptr otherwise.
*/
Storage::Encodings::MFM::Sector *get_sector(int head, int track, uint8_t sector);
const Storage::Encodings::MFM::Sector *sector(int head, int track, uint8_t sector);
// TODO: set_sector.
private:
std::shared_ptr<Storage::Disk::Disk> disk_;
bool is_mfm_ = true;
std::optional<Density> density_;
void install_sectors_from_track(const Storage::Disk::Track::Address &address);
std::map<Storage::Disk::Track::Address, std::map<int, Storage::Encodings::MFM::Sector>> sectors_by_address_by_track_;
void install_track(const Storage::Disk::Track::Address &address);
static SectorMap parse_track(const Storage::Disk::Track &track, Density density);
static void append(const SectorMap &source, std::map<int, Sector> &destination);
// Maps from a track address, i.e. head and position, to a map from
// sector IDs to sectors.
std::map<
Storage::Disk::Track::Address,
std::map<int, Storage::Encodings::MFM::Sector>
> sectors_by_address_by_track_;
};
}

View File

@ -11,10 +11,13 @@
using namespace Storage::Encodings::MFM;
std::map<std::size_t, Storage::Encodings::MFM::Sector> Storage::Encodings::MFM::sectors_from_segment(const Storage::Disk::PCMSegment &&segment, bool is_double_density) {
std::map<std::size_t, Storage::Encodings::MFM::Sector> Storage::Encodings::MFM::sectors_from_segment(
const Storage::Disk::PCMSegment &segment,
Density density
) {
std::map<std::size_t, Sector> result;
Shifter shifter;
shifter.set_is_double_density(is_double_density);
shifter.set_is_mfm(is_mfm(density));
shifter.set_should_obey_syncs(true);
std::unique_ptr<Storage::Encodings::MFM::Sector> new_sector;

View File

@ -9,6 +9,7 @@
#ifndef SegmentParser_hpp
#define SegmentParser_hpp
#include "Constants.hpp"
#include "Sector.hpp"
#include "../../Track/PCMSegment.hpp"
#include <map>
@ -22,7 +23,7 @@ using SectorMap = std::map<std::size_t, Sector>;
the segment (counted in bits from the beginning and pointing to the location the disk
had reached upon detection of the ID mark) to sector.
*/
SectorMap sectors_from_segment(const Disk::PCMSegment &&segment, bool is_double_density);
SectorMap sectors_from_segment(const Disk::PCMSegment &segment, Density density);
}

View File

@ -16,9 +16,9 @@ using namespace Storage::Encodings::MFM;
Shifter::Shifter() : owned_crc_generator_(new CRC::CCITT()), crc_generator_(owned_crc_generator_.get()) {}
Shifter::Shifter(CRC::CCITT *crc_generator) : crc_generator_(crc_generator) {}
void Shifter::set_is_double_density(bool is_double_density) {
is_double_density_ = is_double_density;
if(!is_double_density) is_awaiting_marker_value_ = false;
void Shifter::set_is_mfm(bool is_mfm) {
is_mfm_ = is_mfm;
if(!is_mfm) is_awaiting_marker_value_ = false;
}
void Shifter::set_should_obey_syncs(bool should_obey_syncs) {
@ -31,7 +31,7 @@ void Shifter::add_input_bit(int value) {
token_ = Token::None;
if(should_obey_syncs_) {
if(!is_double_density_) {
if(!is_mfm_) {
switch(shift_register_ & 0xffff) {
case Storage::Encodings::MFM::FMIndexAddressMark:
token_ = Token::Index;
@ -100,7 +100,7 @@ void Shifter::add_input_bit(int value) {
token_ = Token::Byte;
bits_since_token_ = 0;
if(is_awaiting_marker_value_ && is_double_density_) {
if(is_awaiting_marker_value_ && is_mfm_) {
is_awaiting_marker_value_ = false;
switch(get_byte()) {
case Storage::Encodings::MFM::IndexAddressByte:

View File

@ -20,7 +20,7 @@ namespace Storage::Encodings::MFM {
a stream of MFM tokens as output. So e.g. it is suitable for use in parsing
the output of a PLL windowing of disk events.
It supports both FM and MFM parsing; see @c set_is_double_density.
It supports both FM and MFM parsing; see @c set_is_mfm.
It will ordinarily honour sync patterns; that should be turned off when within
a sector because false syncs can occur. See @c set_should_obey_syncs.
@ -48,7 +48,7 @@ class Shifter {
Shifter();
Shifter(CRC::CCITT *crc_generator);
void set_is_double_density(bool is_double_density);
void set_is_mfm(bool is_mfm);
void set_should_obey_syncs(bool should_obey_syncs);
void add_input_bit(int bit);
@ -72,7 +72,7 @@ class Shifter {
Token token_ = None;
// Input configuration.
bool is_double_density_ = false;
bool is_mfm_ = false;
std::unique_ptr<CRC::CCITT> owned_crc_generator_;
CRC::CCITT *crc_generator_;

View File

@ -16,7 +16,7 @@
using namespace Storage::Disk::CPM;
std::unique_ptr<Storage::Disk::CPM::Catalogue> Storage::Disk::CPM::GetCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk, const ParameterBlock &parameters) {
Storage::Encodings::MFM::Parser parser(true, disk);
Storage::Encodings::MFM::Parser parser(Encodings::MFM::Density::Double, disk);
// Assemble the actual bytes of the catalogue.
std::vector<uint8_t> catalogue;
@ -29,7 +29,7 @@ std::unique_ptr<Storage::Disk::CPM::Catalogue> Storage::Disk::CPM::GetCatalogue(
if(catalogue_allocation_bitmap & 0x8000) {
std::size_t size_read = 0;
do {
Storage::Encodings::MFM::Sector *sector_contents = parser.get_sector(0, uint8_t(track), uint8_t(parameters.first_sector + sector));
const Storage::Encodings::MFM::Sector *sector_contents = parser.sector(0, uint8_t(track), uint8_t(parameters.first_sector + sector));
if(!sector_contents || sector_contents->samples.empty()) {
return nullptr;
}
@ -138,7 +138,7 @@ std::unique_ptr<Storage::Disk::CPM::Catalogue> Storage::Disk::CPM::GetCatalogue(
track = first_sector / parameters.sectors_per_track;
for(int s = 0; s < sectors_per_block && record < number_of_records; s++) {
Storage::Encodings::MFM::Sector *sector_contents = parser.get_sector(0, uint8_t(track), uint8_t(parameters.first_sector + sector));
const Storage::Encodings::MFM::Sector *sector_contents = parser.sector(0, uint8_t(track), uint8_t(parameters.first_sector + sector));
if(!sector_contents || sector_contents->samples.empty()) break;
sector++;
if(sector == parameters.sectors_per_track) {

View File

@ -77,10 +77,10 @@ FAT::Directory directory_from(const std::vector<uint8_t> &contents) {
}
std::optional<FAT::Volume> FAT::GetVolume(const std::shared_ptr<Storage::Disk::Disk> &disk) {
Storage::Encodings::MFM::Parser parser(true, disk);
Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
// Grab the boot sector; that'll be enough to establish the volume.
Storage::Encodings::MFM::Sector *const boot_sector = parser.get_sector(0, 0, 1);
const Storage::Encodings::MFM::Sector *const boot_sector = parser.sector(0, 0, 1);
if(!boot_sector || boot_sector->samples.empty() || boot_sector->samples[0].size() < 512) {
return std::nullopt;
}
@ -108,8 +108,8 @@ std::optional<FAT::Volume> FAT::GetVolume(const std::shared_ptr<Storage::Disk::D
const int sector_number = volume.reserved_sectors + c;
const auto address = volume.chs_for_sector(sector_number);
Storage::Encodings::MFM::Sector *const fat_sector =
parser.get_sector(address.head, address.cylinder, uint8_t(address.sector));
const Storage::Encodings::MFM::Sector *const fat_sector =
parser.sector(address.head, address.cylinder, uint8_t(address.sector));
if(!fat_sector || fat_sector->samples.empty() || fat_sector->samples[0].size() != volume.bytes_per_sector) {
return std::nullopt;
}
@ -130,8 +130,8 @@ std::optional<FAT::Volume> FAT::GetVolume(const std::shared_ptr<Storage::Disk::D
const auto sector_number = int(volume.reserved_sectors + c + volume.sectors_per_fat*volume.fat_copies);
const auto address = volume.chs_for_sector(sector_number);
Storage::Encodings::MFM::Sector *const sector =
parser.get_sector(address.head, address.cylinder, uint8_t(address.sector));
const Storage::Encodings::MFM::Sector *const sector =
parser.sector(address.head, address.cylinder, uint8_t(address.sector));
if(!sector || sector->samples.empty() || sector->samples[0].size() != volume.bytes_per_sector) {
return std::nullopt;
}
@ -143,7 +143,7 @@ std::optional<FAT::Volume> FAT::GetVolume(const std::shared_ptr<Storage::Disk::D
}
std::optional<std::vector<uint8_t>> FAT::GetFile(const std::shared_ptr<Storage::Disk::Disk> &disk, const Volume &volume, const File &file) {
Storage::Encodings::MFM::Parser parser(true, disk);
Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
std::vector<uint8_t> contents;
@ -156,8 +156,8 @@ std::optional<std::vector<uint8_t>> FAT::GetFile(const std::shared_ptr<Storage::
for(int c = 0; c < volume.sectors_per_cluster; c++) {
const auto address = volume.chs_for_sector(sector + c);
Storage::Encodings::MFM::Sector *const sector_contents =
parser.get_sector(address.head, address.cylinder, uint8_t(address.sector));
const Storage::Encodings::MFM::Sector *const sector_contents =
parser.sector(address.head, address.cylinder, uint8_t(address.sector));
if(!sector_contents || sector_contents->samples.empty() || sector_contents->samples[0].size() != volume.bytes_per_sector) {
return std::nullopt;
}