From 6c606b5506a36127271f1da6b1e85413a126c77f Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Sat, 25 Dec 2021 17:06:12 -0500 Subject: [PATCH 01/25] Fix through route to `TargetPlatform::TypeDistinguisher`. --- Storage/Disk/DiskImage/DiskImage.hpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Storage/Disk/DiskImage/DiskImage.hpp b/Storage/Disk/DiskImage/DiskImage.hpp index ed051756f..d12d4709a 100644 --- a/Storage/Disk/DiskImage/DiskImage.hpp +++ b/Storage/Disk/DiskImage/DiskImage.hpp @@ -14,6 +14,7 @@ #include "../Disk.hpp" #include "../Track/Track.hpp" +#include "../../TargetPlatforms.hpp" namespace Storage { namespace Disk { @@ -86,8 +87,11 @@ class DiskImageHolderBase: public Disk { Provides a wrapper that wraps a DiskImage to make it into a Disk, providing caching and, thereby, an intermediate store for modified tracks so that mutable disk images can either update on the fly or perform a block update on closure, as appropriate. + + Implements TargetPlatform::TypeDistinguisher to return either no information whatsoever, if + the underlying image doesn't implement TypeDistinguisher, or else to pass the call along. */ -template <typename T> class DiskImageHolder: public DiskImageHolderBase { +template <typename T> class DiskImageHolder: public DiskImageHolderBase, public TargetPlatform::TypeDistinguisher { public: template <typename... Ts> DiskImageHolder(Ts&&... args) : disk_image_(args...) {} @@ -103,6 +107,14 @@ template <typename T> class DiskImageHolder: public DiskImageHolderBase { private: T disk_image_; + + TargetPlatform::Type target_platform_type() final { + if constexpr (std::is_base_of<TargetPlatform::TypeDistinguisher, T>::value) { + return static_cast<TargetPlatform::TypeDistinguisher *>(&disk_image_)->target_platform_type(); + } else { + return TargetPlatform::Type(~0); + } + } }; #include "DiskImageImplementation.hpp" From dba3a3d9426cf45cc92ee2939fda43b28ec1a0f8 Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Sat, 25 Dec 2021 17:06:47 -0500 Subject: [PATCH 02/25] Add through route to an IPF container. --- Analyser/Static/StaticAnalyser.cpp | 13 +++-- .../Clock Signal.xcodeproj/project.pbxproj | 8 +++ OSBindings/Mac/Clock Signal/Info.plist | 20 +++++++ Storage/Disk/DiskImage/Formats/IPF.cpp | 27 +++++++++ Storage/Disk/DiskImage/Formats/IPF.hpp | 56 +++++++++++++++++++ 5 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 Storage/Disk/DiskImage/Formats/IPF.cpp create mode 100644 Storage/Disk/DiskImage/Formats/IPF.hpp diff --git a/Analyser/Static/StaticAnalyser.cpp b/Analyser/Static/StaticAnalyser.cpp index fb0c1b7a5..99668d6e1 100644 --- a/Analyser/Static/StaticAnalyser.cpp +++ b/Analyser/Static/StaticAnalyser.cpp @@ -43,11 +43,12 @@ #include "../../Storage/Disk/DiskImage/Formats/AppleDSK.hpp" #include "../../Storage/Disk/DiskImage/Formats/CPCDSK.hpp" #include "../../Storage/Disk/DiskImage/Formats/D64.hpp" -#include "../../Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp" #include "../../Storage/Disk/DiskImage/Formats/G64.hpp" #include "../../Storage/Disk/DiskImage/Formats/DMK.hpp" #include "../../Storage/Disk/DiskImage/Formats/FAT12.hpp" #include "../../Storage/Disk/DiskImage/Formats/HFE.hpp" +#include "../../Storage/Disk/DiskImage/Formats/IPF.hpp" +#include "../../Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp" #include "../../Storage/Disk/DiskImage/Formats/MSA.hpp" #include "../../Storage/Disk/DiskImage/Formats/NIB.hpp" #include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp" @@ -103,8 +104,8 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform:: #define InsertInstance(list, instance, platforms) \ list.emplace_back(instance);\ potential_platforms |= platforms;\ - TargetPlatform::TypeDistinguisher *distinguisher = dynamic_cast<TargetPlatform::TypeDistinguisher *>(list.back().get());\ - if(distinguisher) potential_platforms &= distinguisher->target_platform_type(); \ + TargetPlatform::TypeDistinguisher *const distinguisher = dynamic_cast<TargetPlatform::TypeDistinguisher *>(list.back().get());\ + if(distinguisher) potential_platforms &= distinguisher->target_platform_type(); #define Insert(list, class, platforms, ...) \ InsertInstance(list, new Storage::class(__VA_ARGS__), platforms); @@ -161,7 +162,11 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform:: // HFE (TODO: switch to AllDisk once the MSX stops being so greedy) Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2) Format("image", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2) - Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::Enterprise) // IMG (Enterprise/MS-DOS style) + Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::Enterprise) // IMG (Enterprise/MS-DOS style) + Format( "ipf", + result.disks, + Disk::DiskImageHolder<Storage::Disk::IPF>, + TargetPlatform::Amiga | TargetPlatform::AtariST | TargetPlatform::AmstradCPC | TargetPlatform::ZXSpectrum) // IPF Format("msa", result.disks, Disk::DiskImageHolder<Storage::Disk::MSA>, TargetPlatform::AtariST) // MSA Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII) // NIB Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 7962a7be7..734e8c3cb 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -273,6 +273,8 @@ 4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */; }; 4B595FAD2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B595FAC2086DFBA0083CAA8 /* AudioToggle.cpp */; }; 4B595FAE2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B595FAC2086DFBA0083CAA8 /* AudioToggle.cpp */; }; + 4B5B37312777C7FC0047F238 /* IPF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5B372F2777C7FC0047F238 /* IPF.cpp */; }; + 4B5B37322777C7FC0047F238 /* IPF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5B372F2777C7FC0047F238 /* IPF.cpp */; }; 4B5D5C9725F56FC7001B4623 /* Spectrum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5D5C9525F56FC7001B4623 /* Spectrum.cpp */; }; 4B5D5C9825F56FC7001B4623 /* Spectrum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5D5C9525F56FC7001B4623 /* Spectrum.cpp */; }; 4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5FADB81DE3151600AEC565 /* FileHolder.cpp */; }; @@ -1350,6 +1352,8 @@ 4B59199B1DAC6C46005BB85C /* OricTAP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OricTAP.hpp; sourceTree = "<group>"; }; 4B595FAB2086DFBA0083CAA8 /* AudioToggle.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AudioToggle.hpp; sourceTree = "<group>"; }; 4B595FAC2086DFBA0083CAA8 /* AudioToggle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AudioToggle.cpp; sourceTree = "<group>"; }; + 4B5B372F2777C7FC0047F238 /* IPF.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = IPF.cpp; sourceTree = "<group>"; }; + 4B5B37302777C7FC0047F238 /* IPF.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = IPF.hpp; sourceTree = "<group>"; }; 4B5D5C9525F56FC7001B4623 /* Spectrum.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Spectrum.cpp; path = Parsers/Spectrum.cpp; sourceTree = "<group>"; }; 4B5D5C9625F56FC7001B4623 /* Spectrum.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Spectrum.hpp; path = Parsers/Spectrum.hpp; sourceTree = "<group>"; }; 4B5FADB81DE3151600AEC565 /* FileHolder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FileHolder.cpp; sourceTree = "<group>"; }; @@ -2788,6 +2792,7 @@ 4BEBFB4B2002C4BF000708CC /* FAT12.cpp */, 4B4518931F75FD1B00926311 /* G64.cpp */, 4B4518951F75FD1B00926311 /* HFE.cpp */, + 4B5B372F2777C7FC0047F238 /* IPF.cpp */, 4BB4BFAE22A42F290069048D /* MacintoshIMG.cpp */, 4B58601C1F806AB200AEE2E3 /* MFMSectorDump.cpp */, 4BC131782346DF2B00E4FF3D /* MSA.cpp */, @@ -2807,6 +2812,7 @@ 4BEBFB4C2002C4BF000708CC /* FAT12.hpp */, 4B4518941F75FD1B00926311 /* G64.hpp */, 4B4518961F75FD1B00926311 /* HFE.hpp */, + 4B5B37302777C7FC0047F238 /* IPF.hpp */, 4BB4BFAF22A42F290069048D /* MacintoshIMG.hpp */, 4B58601D1F806AB200AEE2E3 /* MFMSectorDump.hpp */, 4BC131792346DF2B00E4FF3D /* MSA.hpp */, @@ -5421,6 +5427,7 @@ 4BC131772346DE9100E4FF3D /* StaticAnalyser.cpp in Sources */, 4B055ACF1FAE9B030060FFFF /* SoundGenerator.cpp in Sources */, 4B4DEC08252BFA56004583AC /* 65816Base.cpp in Sources */, + 4B5B37322777C7FC0047F238 /* IPF.cpp in Sources */, 4B894519201967B4007DE474 /* ConfidenceCounter.cpp in Sources */, 4B055AEE1FAE9BBF0060FFFF /* Keyboard.cpp in Sources */, 4B055AED1FAE9BA20060FFFF /* Z80Storage.cpp in Sources */, @@ -5574,6 +5581,7 @@ 4B89451E201967B4007DE474 /* Tape.cpp in Sources */, 4BAF2B4E2004580C00480230 /* DMK.cpp in Sources */, 4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */, + 4B5B37312777C7FC0047F238 /* IPF.cpp in Sources */, 4B0ACC3023775819008902D0 /* TIASound.cpp in Sources */, 4B7136861F78724F008B8ED9 /* Encoder.cpp in Sources */, 4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index c0ae89362..635d364c9 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -652,6 +652,26 @@ <key>NSDocumentClass</key> <string>$(PRODUCT_MODULE_NAME).MachineDocument</string> </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>ipf</string> + </array> + <key>CFBundleTypeName</key> + <string>Software Preservation Society Disk Image</string> + <key>CFBundleTypeOSTypes</key> + <array> + <string>????</string> + </array> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + <key>LSHandlerRank</key> + <string>Owner</string> + <key>LSTypeIsPackage</key> + <false/> + <key>NSDocumentClass</key> + <string>$(PRODUCT_MODULE_NAME).MachineDocument</string> + </dict> </array> <key>CFBundleExecutable</key> <string>$(EXECUTABLE_NAME)</string> diff --git a/Storage/Disk/DiskImage/Formats/IPF.cpp b/Storage/Disk/DiskImage/Formats/IPF.cpp new file mode 100644 index 000000000..44999490e --- /dev/null +++ b/Storage/Disk/DiskImage/Formats/IPF.cpp @@ -0,0 +1,27 @@ +// +// IPF.cpp +// Clock Signal +// +// Created by Thomas Harte on 25/12/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#include "IPF.hpp" + +using namespace Storage::Disk; + + +IPF::IPF(const std::string &file_name) : file_(file_name) { +} + +HeadPosition IPF::get_maximum_head_position() { + return HeadPosition(80); // TODO; +} + +int IPF::get_head_count() { + return 2; // TODO; +} + +std::shared_ptr<Track> IPF::get_track_at_position([[maybe_unused]] Track::Address address) { + return nullptr; +} diff --git a/Storage/Disk/DiskImage/Formats/IPF.hpp b/Storage/Disk/DiskImage/Formats/IPF.hpp new file mode 100644 index 000000000..b7f08a52b --- /dev/null +++ b/Storage/Disk/DiskImage/Formats/IPF.hpp @@ -0,0 +1,56 @@ +// +// IPF.hpp +// Clock Signal +// +// Created by Thomas Harte on 25/12/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#ifndef IPF_hpp +#define IPF_hpp + +#include "../DiskImage.hpp" +#include "../../../FileHolder.hpp" +#include "../../../TargetPlatforms.hpp" + +#include <string> + +namespace Storage { +namespace Disk { + +/*! + Provides a @c DiskImage containing an IPF. +*/ +class IPF: public DiskImage, public TargetPlatform::TypeDistinguisher { + public: + /*! + Construct an @c IPF containing content from the file with name @c file_name. + + @throws Storage::FileHolder::Error::CantOpen if this file can't be opened. + @throws Error::InvalidFormat if the file doesn't appear to contain an .HFE format image. + @throws Error::UnknownVersion if the file looks correct but is an unsupported version. + */ + IPF(const std::string &file_name); + + // implemented to satisfy @c Disk + HeadPosition get_maximum_head_position() final; + int get_head_count() final; + std::shared_ptr<Track> get_track_at_position(Track::Address address) final; + + private: + Storage::FileHolder file_; + uint16_t seek_track(Track::Address address); + + int head_count_; + int track_count_; + + TargetPlatform::Type target_platform_type() final { + return platform_type_; + } + TargetPlatform::Type platform_type_ = TargetPlatform::Amiga; +}; + +} +} + +#endif /* IPF_hpp */ From c118dd8afe15bb9dd6005186b0064c82c139a829 Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Sat, 25 Dec 2021 17:27:50 -0500 Subject: [PATCH 03/25] Adds just enough to list all the blocks in an IPF. --- Storage/Disk/DiskImage/Formats/IPF.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Storage/Disk/DiskImage/Formats/IPF.cpp b/Storage/Disk/DiskImage/Formats/IPF.cpp index 44999490e..f5056073f 100644 --- a/Storage/Disk/DiskImage/Formats/IPF.cpp +++ b/Storage/Disk/DiskImage/Formats/IPF.cpp @@ -12,6 +12,28 @@ using namespace Storage::Disk; IPF::IPF(const std::string &file_name) : file_(file_name) { + while(true) { + const auto start_of_block = file_.tell(); + const uint32_t type = file_.get32be(); + uint32_t length = file_.get32be(); // Can't be const because of the dumb encoding of DATA blocks. + [[maybe_unused]] const uint32_t crc = file_.get32be(); + if(file_.eof()) break; + +#define BLOCK(a, b, c, d) (a << 24) | (b << 16) | (c << 8) | d + switch(type) { + default: + printf("Ignoring %c%c%c%c, starting at %ld of length %d\n", (type >> 24), (type >> 16) & 0xff, (type >> 8) & 0xff, type & 0xff, start_of_block, length); + break; + + case BLOCK('D', 'A', 'T', 'A'): { + length += file_.get32be(); + printf("Handling DATA block at %ld of length %d\n", start_of_block, length); + } break; + } +#undef BLOCK + + file_.seek(start_of_block + length, SEEK_SET); + } } HeadPosition IPF::get_maximum_head_position() { From e457ce66ea01d5968806f5370729cabca8ad9d35 Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Sat, 25 Dec 2021 17:32:29 -0500 Subject: [PATCH 04/25] Adds sanity checks around CAPS block. --- Storage/Disk/DiskImage/Formats/IPF.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Storage/Disk/DiskImage/Formats/IPF.cpp b/Storage/Disk/DiskImage/Formats/IPF.cpp index f5056073f..45220eb8e 100644 --- a/Storage/Disk/DiskImage/Formats/IPF.cpp +++ b/Storage/Disk/DiskImage/Formats/IPF.cpp @@ -19,12 +19,26 @@ IPF::IPF(const std::string &file_name) : file_(file_name) { [[maybe_unused]] const uint32_t crc = file_.get32be(); if(file_.eof()) break; -#define BLOCK(a, b, c, d) (a << 24) | (b << 16) | (c << 8) | d +#define BLOCK(a, b, c, d) ((a << 24) | (b << 16) | (c << 8) | d) + + // Sanity check: the first thing in a file should be the CAPS record. + if(!start_of_block && type != BLOCK('C', 'A', 'P', 'S')) { + throw Error::InvalidFormat; + } + switch(type) { default: printf("Ignoring %c%c%c%c, starting at %ld of length %d\n", (type >> 24), (type >> 16) & 0xff, (type >> 8) & 0xff, type & 0xff, start_of_block, length); break; + case BLOCK('C', 'A', 'P', 'S'): + // Analogously to the sanity check above, if a CAPS block is anywhere other + // than first then something is amiss. + if(start_of_block) { + throw Error::InvalidFormat; + } + break; + case BLOCK('D', 'A', 'T', 'A'): { length += file_.get32be(); printf("Handling DATA block at %ld of length %d\n", start_of_block, length); From a6b326da4831ef6fac5e279e0cee497a3022ebfc Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Sat, 25 Dec 2021 18:17:13 -0500 Subject: [PATCH 05/25] Parse the INFO record. --- Storage/Disk/DiskImage/Formats/IPF.cpp | 50 ++++++++++++++++++++++++-- Storage/Disk/DiskImage/Formats/IPF.hpp | 4 +-- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/Storage/Disk/DiskImage/Formats/IPF.cpp b/Storage/Disk/DiskImage/Formats/IPF.cpp index 45220eb8e..5720b44ab 100644 --- a/Storage/Disk/DiskImage/Formats/IPF.cpp +++ b/Storage/Disk/DiskImage/Formats/IPF.cpp @@ -39,6 +39,52 @@ IPF::IPF(const std::string &file_name) : file_(file_name) { } break; + case BLOCK('I', 'N', 'F', 'O'): { + // There are a lot of useful archival fields in the info chunk, which for emulation + // aren't that interesting. + + // Make sure this is a floppy disk. + const uint32_t media_type = file_.get32be(); + if(media_type != 1) { + throw Error::InvalidFormat; + } + + // Skip: encoder type, revision, file key and revision, CRC of the original .ctr, and minimum track. + file_.seek(24, SEEK_CUR); + track_count_ = int(1 + file_.get32be()); + + // Skip: min side. + file_.seek(4, SEEK_CUR); + head_count_ = int(1 + file_.get32be()); + + // Skip: creation date, time. + file_.seek(8, SEEK_CUR); + + platform_type_ = 0; + for(int c = 0; c < 4; c++) { + const uint8_t platform = file_.get8(); + switch(platform) { + default: break; + case 1: platform_type_ |= TargetPlatform::Amiga; break; + case 2: platform_type_ |= TargetPlatform::AtariST; break; + /* Omitted: 3 -> IBM PC */ + case 4: platform_type_ |= TargetPlatform::AmstradCPC; break; + case 5: platform_type_ |= TargetPlatform::ZXSpectrum; break; + /* Omitted: 6 -> Sam Coupé */ + /* Omitted: 7 -> Archimedes */ + /* Omitted: 8 -> C64 */ + /* Omitted: 9 -> Atari 8-bit */ + } + } + + // If the file didn't declare anything, default to supporting everything. + if(!platform_type_) { + platform_type_ = ~0; + } + + // Ignore: disk number, creator ID, reserved area. + } break; + case BLOCK('D', 'A', 'T', 'A'): { length += file_.get32be(); printf("Handling DATA block at %ld of length %d\n", start_of_block, length); @@ -51,11 +97,11 @@ IPF::IPF(const std::string &file_name) : file_(file_name) { } HeadPosition IPF::get_maximum_head_position() { - return HeadPosition(80); // TODO; + return HeadPosition(track_count_); } int IPF::get_head_count() { - return 2; // TODO; + return head_count_; } std::shared_ptr<Track> IPF::get_track_at_position([[maybe_unused]] Track::Address address) { diff --git a/Storage/Disk/DiskImage/Formats/IPF.hpp b/Storage/Disk/DiskImage/Formats/IPF.hpp index b7f08a52b..ad016f29a 100644 --- a/Storage/Disk/DiskImage/Formats/IPF.hpp +++ b/Storage/Disk/DiskImage/Formats/IPF.hpp @@ -45,9 +45,9 @@ class IPF: public DiskImage, public TargetPlatform::TypeDistinguisher { int track_count_; TargetPlatform::Type target_platform_type() final { - return platform_type_; + return TargetPlatform::Type(platform_type_); } - TargetPlatform::Type platform_type_ = TargetPlatform::Amiga; + TargetPlatform::IntType platform_type_ = TargetPlatform::Amiga; }; } From 0433db037049e24f0e2add5f8fc07c04353ad278 Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Sat, 25 Dec 2021 19:36:54 -0500 Subject: [PATCH 06/25] Eliminate macro. --- Storage/Disk/DiskImage/Formats/IPF.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Storage/Disk/DiskImage/Formats/IPF.cpp b/Storage/Disk/DiskImage/Formats/IPF.cpp index 5720b44ab..c97656be2 100644 --- a/Storage/Disk/DiskImage/Formats/IPF.cpp +++ b/Storage/Disk/DiskImage/Formats/IPF.cpp @@ -10,6 +10,15 @@ using namespace Storage::Disk; +namespace { + +constexpr uint32_t block(const char *src) { + static_assert(sizeof(int) >= sizeof(uint32_t)); + return uint32_t((src[0] << 24) | (src[1] << 16) | (src[2] << 8) | src[3]); +} + +} + IPF::IPF(const std::string &file_name) : file_(file_name) { while(true) { @@ -19,10 +28,8 @@ IPF::IPF(const std::string &file_name) : file_(file_name) { [[maybe_unused]] const uint32_t crc = file_.get32be(); if(file_.eof()) break; -#define BLOCK(a, b, c, d) ((a << 24) | (b << 16) | (c << 8) | d) - // Sanity check: the first thing in a file should be the CAPS record. - if(!start_of_block && type != BLOCK('C', 'A', 'P', 'S')) { + if(!start_of_block && type != block("CAPS")) { throw Error::InvalidFormat; } @@ -31,7 +38,7 @@ IPF::IPF(const std::string &file_name) : file_(file_name) { printf("Ignoring %c%c%c%c, starting at %ld of length %d\n", (type >> 24), (type >> 16) & 0xff, (type >> 8) & 0xff, type & 0xff, start_of_block, length); break; - case BLOCK('C', 'A', 'P', 'S'): + case block("CAPS"): // Analogously to the sanity check above, if a CAPS block is anywhere other // than first then something is amiss. if(start_of_block) { @@ -39,7 +46,7 @@ IPF::IPF(const std::string &file_name) : file_(file_name) { } break; - case BLOCK('I', 'N', 'F', 'O'): { + case block("INFO"): { // There are a lot of useful archival fields in the info chunk, which for emulation // aren't that interesting. @@ -85,12 +92,11 @@ IPF::IPF(const std::string &file_name) : file_(file_name) { // Ignore: disk number, creator ID, reserved area. } break; - case BLOCK('D', 'A', 'T', 'A'): { + case block("DATA"): { length += file_.get32be(); printf("Handling DATA block at %ld of length %d\n", start_of_block, length); } break; } -#undef BLOCK file_.seek(start_of_block + length, SEEK_SET); } From 28572d4392abe36082c243c6afcb8cefa76ddb17 Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Sun, 26 Dec 2021 09:12:44 -0500 Subject: [PATCH 07/25] Enforce string-length requirement. --- Storage/Disk/DiskImage/Formats/IPF.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Storage/Disk/DiskImage/Formats/IPF.cpp b/Storage/Disk/DiskImage/Formats/IPF.cpp index c97656be2..ca55cd1ae 100644 --- a/Storage/Disk/DiskImage/Formats/IPF.cpp +++ b/Storage/Disk/DiskImage/Formats/IPF.cpp @@ -12,9 +12,13 @@ using namespace Storage::Disk; namespace { -constexpr uint32_t block(const char *src) { - static_assert(sizeof(int) >= sizeof(uint32_t)); - return uint32_t((src[0] << 24) | (src[1] << 16) | (src[2] << 8) | src[3]); +constexpr uint32_t block(const char (& src)[5]) { + return uint32_t( + (uint32_t(src[0]) << 24) | + (uint32_t(src[1]) << 16) | + (uint32_t(src[2]) << 8) | + uint32_t(src[3]) + ); } } @@ -92,6 +96,9 @@ IPF::IPF(const std::string &file_name) : file_(file_name) { // Ignore: disk number, creator ID, reserved area. } break; + case block("IMGE"): + break; + case block("DATA"): { length += file_.get32be(); printf("Handling DATA block at %ld of length %d\n", start_of_block, length); From 9d3cf9c73cae11ec161b9f9bf76d9aaba9cf1804 Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Sun, 26 Dec 2021 14:49:51 -0500 Subject: [PATCH 08/25] Collate descriptions of all tracks. --- Storage/Disk/DiskImage/Formats/IPF.cpp | 61 ++++++++++++++++++++++++-- Storage/Disk/DiskImage/Formats/IPF.hpp | 25 +++++++++++ 2 files changed, 83 insertions(+), 3 deletions(-) diff --git a/Storage/Disk/DiskImage/Formats/IPF.cpp b/Storage/Disk/DiskImage/Formats/IPF.cpp index ca55cd1ae..978f24b12 100644 --- a/Storage/Disk/DiskImage/Formats/IPF.cpp +++ b/Storage/Disk/DiskImage/Formats/IPF.cpp @@ -25,6 +25,10 @@ constexpr uint32_t block(const char (& src)[5]) { IPF::IPF(const std::string &file_name) : file_(file_name) { + std::map<uint32_t, Track::Address> tracks_by_data_key; + + // For now, just build up a list of tracks that exist, noting the file position at which their data begins + // plus the other fields that'll be necessary to convert them into flux on demand later. while(true) { const auto start_of_block = file_.tell(); const uint32_t type = file_.get32be(); @@ -96,12 +100,63 @@ IPF::IPF(const std::string &file_name) : file_(file_name) { // Ignore: disk number, creator ID, reserved area. } break; - case block("IMGE"): - break; + case block("IMGE"): { + // Get track location. + const uint32_t track = file_.get32be(); + const uint32_t side = file_.get32be(); + const Track::Address address{int(side), HeadPosition(int(track))}; + + // Hence generate a TrackDescription. + auto pair = tracks_.emplace(address, TrackDescription()); + TrackDescription &description = pair.first->second; + + // Read those fields of interest... + + // Bit density. I've no idea why the density can't just be given as a measurement. + description.density = TrackDescription::Density(file_.get32be()); + if(description.density > TrackDescription::Density::Max) { + description.density = TrackDescription::Density::Unknown; + } + + + file_.seek(12, SEEK_CUR); // Skipped: signal type, track bytes, start byte position. + description.start_bit_pos = file_.get32be(); + description.data_bits = file_.get32be(); + description.gap_bits = file_.get32be(); + + file_.seek(4, SEEK_CUR); // Skipped: track bits, which is entirely redundant. + description.block_count = file_.get32be(); + + file_.seek(4, SEEK_CUR); // Skipped: encoder process. + description.has_fuzzy_bits = file_.get32be() & 1; + + // For some reason the authors decided to introduce another primary key, + // in addition to that which naturally exists of (track, side). So set up + // a mapping from the one to the other. + const uint32_t data_key = file_.get32be(); + tracks_by_data_key.emplace(data_key, address); + } break; case block("DATA"): { length += file_.get32be(); - printf("Handling DATA block at %ld of length %d\n", start_of_block, length); + + file_.seek(8, SEEK_CUR); // Skipped: bit size, CRC. + + // Grab the data key and use that to establish the file starting + // position for this track. + // + // Assumed here: DATA records will come after corresponding IMGE records. + const uint32_t data_key = file_.get32be(); + const auto pair = tracks_by_data_key.find(data_key); + if(pair == tracks_by_data_key.end()) { + break; + } + + auto description = tracks_.find(pair->second); + if(description == tracks_.end()) { + break; + } + description->second.file_offset = file_.tell(); } break; } diff --git a/Storage/Disk/DiskImage/Formats/IPF.hpp b/Storage/Disk/DiskImage/Formats/IPF.hpp index ad016f29a..993704546 100644 --- a/Storage/Disk/DiskImage/Formats/IPF.hpp +++ b/Storage/Disk/DiskImage/Formats/IPF.hpp @@ -14,6 +14,7 @@ #include "../../../TargetPlatforms.hpp" #include <string> +#include <map> namespace Storage { namespace Disk { @@ -41,8 +42,32 @@ class IPF: public DiskImage, public TargetPlatform::TypeDistinguisher { Storage::FileHolder file_; uint16_t seek_track(Track::Address address); + struct TrackDescription { + long file_offset = 0; + enum class Density { + Unknown, + Noise, + Auto, + CopylockAmiga, + CopylockAmigaNew, + CopylockST, + SpeedlockAmiga, + OldSpeedlockAmiga, + AdamBrierleyAmiga, + AdamBrierleyDensityKeyAmiga, + + Max = AdamBrierleyDensityKeyAmiga + } density = Density::Unknown; + uint32_t start_bit_pos = 0; + uint32_t data_bits = 0; + uint32_t gap_bits = 0; + uint32_t block_count; + bool has_fuzzy_bits = false; + }; + int head_count_; int track_count_; + std::map<Track::Address, TrackDescription> tracks_; TargetPlatform::Type target_platform_type() final { return TargetPlatform::Type(platform_type_); From 9b6ccbcc957b6721899f4520cd14ac92fde59ff2 Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Mon, 27 Dec 2021 18:12:44 -0500 Subject: [PATCH 09/25] Parses data and gap stream elements. --- Storage/Disk/DiskImage/Formats/IPF.cpp | 122 ++++++++++++++++++++++++- Storage/Disk/DiskImage/Formats/IPF.hpp | 1 + 2 files changed, 121 insertions(+), 2 deletions(-) diff --git a/Storage/Disk/DiskImage/Formats/IPF.cpp b/Storage/Disk/DiskImage/Formats/IPF.cpp index 978f24b12..e7a039700 100644 --- a/Storage/Disk/DiskImage/Formats/IPF.cpp +++ b/Storage/Disk/DiskImage/Formats/IPF.cpp @@ -21,6 +21,15 @@ constexpr uint32_t block(const char (& src)[5]) { ); } +constexpr size_t block_size(Storage::FileHolder &file, uint8_t header) { + uint8_t size_width = header >> 5; + size_t length = 0; + while(size_width--) { + length = (length << 8) | file.get8(); + } + return length; +} + } @@ -64,8 +73,11 @@ IPF::IPF(const std::string &file_name) : file_(file_name) { throw Error::InvalidFormat; } - // Skip: encoder type, revision, file key and revision, CRC of the original .ctr, and minimum track. - file_.seek(24, SEEK_CUR); + // Determine whether this is a newer SPS-style file. + is_sps_format_ = file_.get32be() > 1; + + // Skip: revision, file key and revision, CRC of the original .ctr, and minimum track. + file_.seek(20, SEEK_CUR); track_count_ = int(1 + file_.get32be()); // Skip: min side. @@ -173,5 +185,111 @@ int IPF::get_head_count() { } std::shared_ptr<Track> IPF::get_track_at_position([[maybe_unused]] Track::Address address) { + // Get the track description, if it exists, and check either that the file has contents for the track. + auto pair = tracks_.find(address); + if(pair == tracks_.end()) { + return nullptr; + } + const TrackDescription &description = pair->second; + if(!description.file_offset) { + return nullptr; + } + + // Seek to track content. + file_.seek(description.file_offset, SEEK_SET); + + // Read the block descriptions up front. + // + // This is less efficient than just seeking for each block in turn, + // but is a useful crutch to comprehension of the file format on a + // first run through. + struct BlockDescriptor { + uint32_t data_bits = 0; + uint32_t gap_bits = 0; + uint32_t gap_offset = 0; + bool is_mfm = false; + bool has_forward_gap = false; + bool has_backwards_gap = false; + bool data_unit_is_bits = false; + uint32_t default_gap_value = 0; + uint32_t data_offset = 0; + }; + std::vector<BlockDescriptor> blocks; + blocks.reserve(description.block_count); + for(uint32_t c = 0; c < description.block_count; c++) { + auto &block = blocks.emplace_back(); + block.data_bits = file_.get32be(); + block.gap_bits = file_.get32be(); + if(is_sps_format_) { + block.gap_offset = file_.get32be(); + file_.seek(4, SEEK_CUR); // Skip 'cell type' which appears to provide no content. + } else { + // Skip potlower-resolution copies of data_bits and gap_bits. + file_.seek(8, SEEK_CUR); + } + block.is_mfm = file_.get32be() == 1; + + const uint32_t flags = file_.get32be(); + block.has_forward_gap = flags & 1; + block.has_backwards_gap = flags & 2; + block.data_unit_is_bits = flags & 4; + + block.default_gap_value = file_.get32be(); + block.data_offset = file_.get32be(); + } + + // TODO: Append as necessary for each gap and data stream as per above. + for(auto &block: blocks) { + if(block.gap_offset) { + file_.seek(description.file_offset + block.gap_offset, SEEK_SET); + while(true) { + const uint8_t gap_header = file_.get8(); + if(!gap_header) break; + + // Decompose the header and read the length. + enum class Type { + None, GapLength, SampleLength + } type = Type(gap_header & 0x1f); + const size_t length = block_size(file_, gap_header); + + // TODO: write the gap. + switch(type) { + case Type::GapLength: + printf("Unhandled gap length %zu\n", length); + break; + + default: + case Type::SampleLength: + printf("Unhandled sampled gap length %zu\n", length); + file_.seek(long(length >> 3), SEEK_CUR); + break; + } + } + } + + if(block.data_offset) { + file_.seek(description.file_offset + block.data_offset, SEEK_SET); + while(true) { + const uint8_t data_header = file_.get8(); + if(!data_header) break; + + // Decompose the header and read the length. + enum class Type { + None, Sync, Data, Gap, Raw, Fuzzy + } type = Type(data_header & 0x1f); + const size_t length = block_size(file_, data_header) * (block.data_unit_is_bits ? 1 : 8); + + // TODO: write the data. + switch(type) { + default: + printf("Unhandled data type %d, length %zu\n", int(type), length); + file_.seek(long(length >> 3), SEEK_CUR); + break; + } + } + } + printf("\n"); + } + return nullptr; } diff --git a/Storage/Disk/DiskImage/Formats/IPF.hpp b/Storage/Disk/DiskImage/Formats/IPF.hpp index 993704546..306aa693d 100644 --- a/Storage/Disk/DiskImage/Formats/IPF.hpp +++ b/Storage/Disk/DiskImage/Formats/IPF.hpp @@ -68,6 +68,7 @@ class IPF: public DiskImage, public TargetPlatform::TypeDistinguisher { int head_count_; int track_count_; std::map<Track::Address, TrackDescription> tracks_; + bool is_sps_format_ = false; TargetPlatform::Type target_platform_type() final { return TargetPlatform::Type(platform_type_); From dc994f001dcaa9a7a3a687c1f8c05201253690ac Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Mon, 27 Dec 2021 18:55:11 -0500 Subject: [PATCH 10/25] Mention units. --- Storage/Disk/DiskImage/Formats/IPF.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Storage/Disk/DiskImage/Formats/IPF.cpp b/Storage/Disk/DiskImage/Formats/IPF.cpp index e7a039700..47cb56246 100644 --- a/Storage/Disk/DiskImage/Formats/IPF.cpp +++ b/Storage/Disk/DiskImage/Formats/IPF.cpp @@ -255,12 +255,12 @@ std::shared_ptr<Track> IPF::get_track_at_position([[maybe_unused]] Track::Addres // TODO: write the gap. switch(type) { case Type::GapLength: - printf("Unhandled gap length %zu\n", length); + printf("Unhandled gap length %zu bytes\n", length); break; default: case Type::SampleLength: - printf("Unhandled sampled gap length %zu\n", length); + printf("Unhandled sampled gap length %zu bytes\n", length); file_.seek(long(length >> 3), SEEK_CUR); break; } @@ -282,7 +282,7 @@ std::shared_ptr<Track> IPF::get_track_at_position([[maybe_unused]] Track::Addres // TODO: write the data. switch(type) { default: - printf("Unhandled data type %d, length %zu\n", int(type), length); + printf("Unhandled data type %d, length %zu bits\n", int(type), length); file_.seek(long(length >> 3), SEEK_CUR); break; } From 4f3c75477118f095fc691b9b2fea331cf7f0891d Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Mon, 27 Dec 2021 19:15:46 -0500 Subject: [PATCH 11/25] Adds exposition. --- Storage/Disk/DiskImage/Formats/IPF.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Storage/Disk/DiskImage/Formats/IPF.hpp b/Storage/Disk/DiskImage/Formats/IPF.hpp index 306aa693d..20352c99c 100644 --- a/Storage/Disk/DiskImage/Formats/IPF.hpp +++ b/Storage/Disk/DiskImage/Formats/IPF.hpp @@ -20,7 +20,10 @@ namespace Storage { namespace Disk { /*! - Provides a @c DiskImage containing an IPF. + Provides a @c DiskImage containing an IPF, which is a mixed stream of raw flux windows and + unencoded MFM sections along with gap records that can be used to record write splices, all + of which is variably clocked (albeit not at flux transition resolution; as a result IPF files tend to be + close in size to more primitive formats). */ class IPF: public DiskImage, public TargetPlatform::TypeDistinguisher { public: From 350c98ab4d6fb6d6fec2065cdc45b25e20d71bb8 Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Wed, 29 Dec 2021 18:15:37 -0500 Subject: [PATCH 12/25] Add those densities I've yet discovered the rules for. --- Storage/Disk/DiskImage/Formats/IPF.cpp | 61 +++++++++++++++++++++++++- Storage/Disk/DiskImage/Formats/IPF.hpp | 2 + 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/Storage/Disk/DiskImage/Formats/IPF.cpp b/Storage/Disk/DiskImage/Formats/IPF.cpp index 47cb56246..6f21e53c6 100644 --- a/Storage/Disk/DiskImage/Formats/IPF.cpp +++ b/Storage/Disk/DiskImage/Formats/IPF.cpp @@ -32,7 +32,6 @@ constexpr size_t block_size(Storage::FileHolder &file, uint8_t header) { } - IPF::IPF(const std::string &file_name) : file_(file_name) { std::map<uint32_t, Track::Address> tracks_by_data_key; @@ -130,7 +129,6 @@ IPF::IPF(const std::string &file_name) : file_(file_name) { description.density = TrackDescription::Density::Unknown; } - file_.seek(12, SEEK_CUR); // Skipped: signal type, track bytes, start byte position. description.start_bit_pos = file_.get32be(); description.data_bits = file_.get32be(); @@ -293,3 +291,62 @@ std::shared_ptr<Track> IPF::get_track_at_position([[maybe_unused]] Track::Addres return nullptr; } + +/// @returns A vector of the length of a bit in each block for a count of @c blocks in an area of data density @c density. +/// +/// @discussion At least to me, this is the least well-designed part] of the IPF specification; rather than just dictating cell +/// densities (or, equivalently, lengths) in the file, densities are named according to their protection scheme and the decoder +/// is required to know all named protection schemes. Which makes IPF unable to handle arbitrary disks (or, indeed, disks +/// with multiple protection schemes on a single track). +std::vector<Storage::Time> IPF::bit_lengths(TrackDescription::Density density, size_t blocks) { + std::vector<Storage::Time> result; + result.reserve(size_t(blocks)); + + // Establish the default density of 2 µs. + for(size_t c = 0; c < blocks; c++) { + result.push_back(Storage::Time(1, 500'000)); // i.e. default to 2µs. + } + + switch(density) { + default: + break; + + case TrackDescription::Density::CopylockAmiga: + if(blocks > 4) result[4] = Storage::Time(189, 100'000'000); // 1.89µs + if(blocks > 5) result[5] = Storage::Time(199, 100'000'000); // 1.99µs + if(blocks > 6) result[6] = Storage::Time(209, 100'000'000); // 2.09µs + break; + + case TrackDescription::Density::CopylockAmigaNew: + if(blocks > 0) result[0] = Storage::Time(189, 100'000'000); // 1.89µs + if(blocks > 1) result[1] = Storage::Time(199, 100'000'000); // 1.99µs + if(blocks > 2) result[2] = Storage::Time(209, 100'000'000); // 2.09µs + break; + + case TrackDescription::Density::CopylockST: + if(blocks > 5) result[5] = Storage::Time(21, 10'000'000); // 2.1µs + break; + + case TrackDescription::Density::SpeedlockAmiga: + if(blocks > 1) result[1] = Storage::Time(11, 5'000'000); // 2.2µs + if(blocks > 2) result[2] = Storage::Time(9, 5'000'000); // 1.8µs + break; + + case TrackDescription::Density::OldSpeedlockAmiga: + if(blocks > 1) result[1] = Storage::Time(21, 10'000'000); // 2.1µs + break; + + case TrackDescription::Density::AdamBrierleyAmiga: + if(blocks > 1) result[1] = Storage::Time(11, 5'000'000); // 2.2µs + if(blocks > 2) result[2] = Storage::Time(21, 10'000'000); // 2.1µs + + if(blocks > 4) result[3] = Storage::Time(19, 10'000'000); // 1.9µs + if(blocks > 5) result[5] = Storage::Time(9, 5'000'000); // 1.8µs + if(blocks > 6) result[6] = Storage::Time(17, 10'000'000); // 1.7µs + break; + + // TODO: AdamBrierleyDensityKeyAmiga. + } + + return result; +} diff --git a/Storage/Disk/DiskImage/Formats/IPF.hpp b/Storage/Disk/DiskImage/Formats/IPF.hpp index 20352c99c..d8bd5d4d6 100644 --- a/Storage/Disk/DiskImage/Formats/IPF.hpp +++ b/Storage/Disk/DiskImage/Formats/IPF.hpp @@ -68,6 +68,8 @@ class IPF: public DiskImage, public TargetPlatform::TypeDistinguisher { bool has_fuzzy_bits = false; }; + std::vector<Time> bit_lengths(TrackDescription::Density, size_t blocks); + int head_count_; int track_count_; std::map<Track::Address, TrackDescription> tracks_; From d3189acaa63caf048025794aa3b0fb8f87faf6f6 Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Sat, 1 Jan 2022 17:14:52 -0500 Subject: [PATCH 13/25] Add a constexpr route that explicitly calculates the simplest possible form. --- Storage/Storage.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Storage/Storage.hpp b/Storage/Storage.hpp index f096e63bb..90db2b5cd 100644 --- a/Storage/Storage.hpp +++ b/Storage/Storage.hpp @@ -33,6 +33,10 @@ struct Time { Time(float value) { install_float(value); } + static constexpr Time simplified(unsigned int _length, unsigned int _clock_rate) { + const auto gcd = std::gcd(_length, _clock_rate); + return Time(_length / gcd, _clock_rate / gcd); + } /*! Reduces this @c Time to its simplest form; eliminates all common factors from @c length From 38dd3c5c609ac3b3dbe78b7d0c58f1a40fab8c81 Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Sat, 1 Jan 2022 17:15:12 -0500 Subject: [PATCH 14/25] On second thoughts, no need to use a vector here. --- Storage/Disk/DiskImage/Formats/IPF.cpp | 55 ++++++++++++++------------ Storage/Disk/DiskImage/Formats/IPF.hpp | 2 +- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/Storage/Disk/DiskImage/Formats/IPF.cpp b/Storage/Disk/DiskImage/Formats/IPF.cpp index 6f21e53c6..d608cb167 100644 --- a/Storage/Disk/DiskImage/Formats/IPF.cpp +++ b/Storage/Disk/DiskImage/Formats/IPF.cpp @@ -292,61 +292,64 @@ std::shared_ptr<Track> IPF::get_track_at_position([[maybe_unused]] Track::Addres return nullptr; } -/// @returns A vector of the length of a bit in each block for a count of @c blocks in an area of data density @c density. +/// @returns The correct bit length for @c block on a track of @c density. /// /// @discussion At least to me, this is the least well-designed part] of the IPF specification; rather than just dictating cell /// densities (or, equivalently, lengths) in the file, densities are named according to their protection scheme and the decoder /// is required to know all named protection schemes. Which makes IPF unable to handle arbitrary disks (or, indeed, disks /// with multiple protection schemes on a single track). -std::vector<Storage::Time> IPF::bit_lengths(TrackDescription::Density density, size_t blocks) { - std::vector<Storage::Time> result; - result.reserve(size_t(blocks)); - - // Establish the default density of 2 µs. - for(size_t c = 0; c < blocks; c++) { - result.push_back(Storage::Time(1, 500'000)); // i.e. default to 2µs. - } +Storage::Time IPF::bit_length(TrackDescription::Density density, int block) { + constexpr unsigned int us = 100'000'000; + static constexpr auto us170 = Storage::Time::simplified(170, us); + static constexpr auto us180 = Storage::Time::simplified(180, us); + static constexpr auto us189 = Storage::Time::simplified(189, us); + static constexpr auto us190 = Storage::Time::simplified(190, us); + static constexpr auto us199 = Storage::Time::simplified(199, us); + static constexpr auto us200 = Storage::Time::simplified(200, us); + static constexpr auto us209 = Storage::Time::simplified(209, us); + static constexpr auto us210 = Storage::Time::simplified(210, us); + static constexpr auto us220 = Storage::Time::simplified(220, us); switch(density) { default: break; case TrackDescription::Density::CopylockAmiga: - if(blocks > 4) result[4] = Storage::Time(189, 100'000'000); // 1.89µs - if(blocks > 5) result[5] = Storage::Time(199, 100'000'000); // 1.99µs - if(blocks > 6) result[6] = Storage::Time(209, 100'000'000); // 2.09µs + if(block == 4) return us189; + if(block == 5) return us199; + if(block == 6) return us209; break; case TrackDescription::Density::CopylockAmigaNew: - if(blocks > 0) result[0] = Storage::Time(189, 100'000'000); // 1.89µs - if(blocks > 1) result[1] = Storage::Time(199, 100'000'000); // 1.99µs - if(blocks > 2) result[2] = Storage::Time(209, 100'000'000); // 2.09µs + if(block == 0) return us189; + if(block == 1) return us199; + if(block == 2) return us209; break; case TrackDescription::Density::CopylockST: - if(blocks > 5) result[5] = Storage::Time(21, 10'000'000); // 2.1µs + if(block == 5) return us210; break; case TrackDescription::Density::SpeedlockAmiga: - if(blocks > 1) result[1] = Storage::Time(11, 5'000'000); // 2.2µs - if(blocks > 2) result[2] = Storage::Time(9, 5'000'000); // 1.8µs + if(block == 1) return us220; + if(block == 2) return us180; break; case TrackDescription::Density::OldSpeedlockAmiga: - if(blocks > 1) result[1] = Storage::Time(21, 10'000'000); // 2.1µs + if(block == 1) return us210; break; case TrackDescription::Density::AdamBrierleyAmiga: - if(blocks > 1) result[1] = Storage::Time(11, 5'000'000); // 2.2µs - if(blocks > 2) result[2] = Storage::Time(21, 10'000'000); // 2.1µs - - if(blocks > 4) result[3] = Storage::Time(19, 10'000'000); // 1.9µs - if(blocks > 5) result[5] = Storage::Time(9, 5'000'000); // 1.8µs - if(blocks > 6) result[6] = Storage::Time(17, 10'000'000); // 1.7µs + if(block == 1) return us220; + if(block == 2) return us210; + if(block == 3) return us200; + if(block == 4) return us190; + if(block == 5) return us180; + if(block == 6) return us170; break; // TODO: AdamBrierleyDensityKeyAmiga. } - return result; + return us200; // i.e. default to 2µs. } diff --git a/Storage/Disk/DiskImage/Formats/IPF.hpp b/Storage/Disk/DiskImage/Formats/IPF.hpp index d8bd5d4d6..1ce1e74c7 100644 --- a/Storage/Disk/DiskImage/Formats/IPF.hpp +++ b/Storage/Disk/DiskImage/Formats/IPF.hpp @@ -68,7 +68,7 @@ class IPF: public DiskImage, public TargetPlatform::TypeDistinguisher { bool has_fuzzy_bits = false; }; - std::vector<Time> bit_lengths(TrackDescription::Density, size_t blocks); + Time bit_length(TrackDescription::Density, int block); int head_count_; int track_count_; From ed1b0b90f7c18b21bb1b12e791937c679450b967 Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Sat, 1 Jan 2022 18:36:44 -0500 Subject: [PATCH 15/25] Makes a first attempt at encoding data. --- Storage/Disk/DiskImage/Formats/IPF.cpp | 59 ++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/Storage/Disk/DiskImage/Formats/IPF.cpp b/Storage/Disk/DiskImage/Formats/IPF.cpp index d608cb167..1d2e82997 100644 --- a/Storage/Disk/DiskImage/Formats/IPF.cpp +++ b/Storage/Disk/DiskImage/Formats/IPF.cpp @@ -8,6 +8,9 @@ #include "IPF.hpp" +#include "../../Track/PCMTrack.hpp" +#include "../../Encodings/MFM/Encoder.hpp" + using namespace Storage::Disk; namespace { @@ -236,8 +239,11 @@ std::shared_ptr<Track> IPF::get_track_at_position([[maybe_unused]] Track::Addres block.data_offset = file_.get32be(); } - // TODO: Append as necessary for each gap and data stream as per above. + std::vector<Storage::Disk::PCMSegment> segments; + int block_count = 0; for(auto &block: blocks) { + const auto length_of_a_bit = bit_length(description.density, block_count); + if(block.gap_offset) { file_.seek(description.file_offset + block.gap_offset, SEEK_SET); while(true) { @@ -279,6 +285,52 @@ std::shared_ptr<Track> IPF::get_track_at_position([[maybe_unused]] Track::Addres // TODO: write the data. switch(type) { + case Type::Gap: { + auto &segment = segments.emplace_back(); + segment.length_of_a_bit = length_of_a_bit; + segment.data.reserve(size_t(length + 31) & size_t(~31)); + auto encoder = Storage::Encodings::MFM::GetMFMEncoder(segment.data); + while(segment.data.size() < length) { + encoder->add_byte(uint8_t(block.default_gap_value >> 24)); + encoder->add_byte(uint8_t(block.default_gap_value >> 16)); + encoder->add_byte(uint8_t(block.default_gap_value >> 8)); + encoder->add_byte(uint8_t(block.default_gap_value >> 0)); + } + segment.data.resize(length); + } break; + + case Type::Data: { + auto &segment = segments.emplace_back(); + segment.length_of_a_bit = length_of_a_bit; + segment.data.reserve(size_t(length + 7) & size_t(~7)); + auto encoder = Storage::Encodings::MFM::GetMFMEncoder(segment.data); + while(segment.data.size() < length) { + encoder->add_byte(file_.get8()); + } + segment.data.resize(length); + } break; + + case Type::Sync: + case Type::Raw: { + auto &segment = segments.emplace_back(); + segment.length_of_a_bit = length_of_a_bit; + segment.data.reserve(length); + + for(size_t bit = 0; bit < length; bit += 8) { + const uint8_t next = file_.get8(); + segment.data.push_back(next & 0x80); + segment.data.push_back(next & 0x40); + segment.data.push_back(next & 0x20); + segment.data.push_back(next & 0x10); + segment.data.push_back(next & 0x08); + segment.data.push_back(next & 0x04); + segment.data.push_back(next & 0x02); + segment.data.push_back(next & 0x01); + } + + segment.data.resize(length); + } break; + default: printf("Unhandled data type %d, length %zu bits\n", int(type), length); file_.seek(long(length >> 3), SEEK_CUR); @@ -286,10 +338,11 @@ std::shared_ptr<Track> IPF::get_track_at_position([[maybe_unused]] Track::Addres } } } - printf("\n"); + + ++block_count; } - return nullptr; + return std::make_shared<Storage::Disk::PCMTrack>(segments); } /// @returns The correct bit length for @c block on a track of @c density. From d031381e70138dd73cc1e6f07b717ea0591c1583 Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Sat, 1 Jan 2022 18:47:07 -0500 Subject: [PATCH 16/25] Gaps provide content, and data chunk lengths seem to be in terms of unencoded bytes. --- Storage/Disk/DiskImage/Formats/IPF.cpp | 42 +++++++++++++++++--------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/Storage/Disk/DiskImage/Formats/IPF.cpp b/Storage/Disk/DiskImage/Formats/IPF.cpp index 1d2e82997..270bb234e 100644 --- a/Storage/Disk/DiskImage/Formats/IPF.cpp +++ b/Storage/Disk/DiskImage/Formats/IPF.cpp @@ -282,10 +282,13 @@ std::shared_ptr<Track> IPF::get_track_at_position([[maybe_unused]] Track::Addres None, Sync, Data, Gap, Raw, Fuzzy } type = Type(data_header & 0x1f); const size_t length = block_size(file_, data_header) * (block.data_unit_is_bits ? 1 : 8); +#ifndef NDEBUG + const auto next_chunk = file_.tell() + long(length >> 3); +#endif // TODO: write the data. switch(type) { - case Type::Gap: { + /*case Type::Gap: { auto &segment = segments.emplace_back(); segment.length_of_a_bit = length_of_a_bit; segment.data.reserve(size_t(length + 31) & size_t(~31)); @@ -297,24 +300,32 @@ std::shared_ptr<Track> IPF::get_track_at_position([[maybe_unused]] Track::Addres encoder->add_byte(uint8_t(block.default_gap_value >> 0)); } segment.data.resize(length); - } break; + } break;*/ case Type::Data: { + printf("Handling data type %d, length %zu bits\n", int(type), length); + auto &segment = segments.emplace_back(); + segment.length_of_a_bit = length_of_a_bit; + + // Length appears to be in pre-encoded bits; double that to get encoded bits. + const auto byte_length = (length + 7) >> 3; + segment.data.reserve(byte_length * 2); + + auto encoder = Storage::Encodings::MFM::GetMFMEncoder(segment.data); + for(size_t c = 0; c < (length >> 3); c++) { + encoder->add_byte(file_.get8()); + } + + segment.data.resize(length * 2); + } break; + + case Type::Gap: + case Type::Sync: + case Type::Raw: { + printf("Handling data type %d, length %zu bits\n", int(type), length); auto &segment = segments.emplace_back(); segment.length_of_a_bit = length_of_a_bit; segment.data.reserve(size_t(length + 7) & size_t(~7)); - auto encoder = Storage::Encodings::MFM::GetMFMEncoder(segment.data); - while(segment.data.size() < length) { - encoder->add_byte(file_.get8()); - } - segment.data.resize(length); - } break; - - case Type::Sync: - case Type::Raw: { - auto &segment = segments.emplace_back(); - segment.length_of_a_bit = length_of_a_bit; - segment.data.reserve(length); for(size_t bit = 0; bit < length; bit += 8) { const uint8_t next = file_.get8(); @@ -333,9 +344,10 @@ std::shared_ptr<Track> IPF::get_track_at_position([[maybe_unused]] Track::Addres default: printf("Unhandled data type %d, length %zu bits\n", int(type), length); - file_.seek(long(length >> 3), SEEK_CUR); break; } + + assert(file_.tell() == next_chunk); } } From dc920a04f65b1c43ad676378fcd864dc03922f1d Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Sat, 1 Jan 2022 19:03:07 -0500 Subject: [PATCH 17/25] Add missing #include. --- Storage/Disk/DiskImage/Formats/IPF.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Storage/Disk/DiskImage/Formats/IPF.cpp b/Storage/Disk/DiskImage/Formats/IPF.cpp index 270bb234e..d14d43306 100644 --- a/Storage/Disk/DiskImage/Formats/IPF.cpp +++ b/Storage/Disk/DiskImage/Formats/IPF.cpp @@ -11,6 +11,8 @@ #include "../../Track/PCMTrack.hpp" #include "../../Encodings/MFM/Encoder.hpp" +#include <cassert> + using namespace Storage::Disk; namespace { From 58d10943ed5c00198476ba807a5d8bdbde9ba400 Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Sat, 1 Jan 2022 19:08:44 -0500 Subject: [PATCH 18/25] Add asserts to validate my reserve sizes. --- Storage/Disk/DiskImage/Formats/IPF.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Storage/Disk/DiskImage/Formats/IPF.cpp b/Storage/Disk/DiskImage/Formats/IPF.cpp index d14d43306..3de324e29 100644 --- a/Storage/Disk/DiskImage/Formats/IPF.cpp +++ b/Storage/Disk/DiskImage/Formats/IPF.cpp @@ -311,13 +311,14 @@ std::shared_ptr<Track> IPF::get_track_at_position([[maybe_unused]] Track::Addres // Length appears to be in pre-encoded bits; double that to get encoded bits. const auto byte_length = (length + 7) >> 3; - segment.data.reserve(byte_length * 2); + segment.data.reserve(byte_length * 16); auto encoder = Storage::Encodings::MFM::GetMFMEncoder(segment.data); for(size_t c = 0; c < (length >> 3); c++) { encoder->add_byte(file_.get8()); } + assert(segment.data.size() <= (byte_length * 16)); segment.data.resize(length * 2); } break; @@ -327,7 +328,9 @@ std::shared_ptr<Track> IPF::get_track_at_position([[maybe_unused]] Track::Addres printf("Handling data type %d, length %zu bits\n", int(type), length); auto &segment = segments.emplace_back(); segment.length_of_a_bit = length_of_a_bit; - segment.data.reserve(size_t(length + 7) & size_t(~7)); + + const auto bit_length = size_t(length + 7) & size_t(~7); + segment.data.reserve(bit_length); for(size_t bit = 0; bit < length; bit += 8) { const uint8_t next = file_.get8(); @@ -341,6 +344,7 @@ std::shared_ptr<Track> IPF::get_track_at_position([[maybe_unused]] Track::Addres segment.data.push_back(next & 0x01); } + assert(segment.data.size() <= bit_length); segment.data.resize(length); } break; From 3e0b7d71d473f04451bf9d4936c08ea1738b373d Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Sat, 1 Jan 2022 19:09:19 -0500 Subject: [PATCH 19/25] Properly handle partial bytes. --- Storage/Disk/DiskImage/Formats/IPF.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Storage/Disk/DiskImage/Formats/IPF.cpp b/Storage/Disk/DiskImage/Formats/IPF.cpp index 3de324e29..063089cc8 100644 --- a/Storage/Disk/DiskImage/Formats/IPF.cpp +++ b/Storage/Disk/DiskImage/Formats/IPF.cpp @@ -314,7 +314,7 @@ std::shared_ptr<Track> IPF::get_track_at_position([[maybe_unused]] Track::Addres segment.data.reserve(byte_length * 16); auto encoder = Storage::Encodings::MFM::GetMFMEncoder(segment.data); - for(size_t c = 0; c < (length >> 3); c++) { + for(size_t c = 0; c < length; c += 8) { encoder->add_byte(file_.get8()); } From f37179d9f2c91b9e71895cbff8d711bea84d2631 Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Sun, 2 Jan 2022 15:39:26 -0500 Subject: [PATCH 20/25] Gaps appear to contain pre-MFM data (?) --- Storage/Disk/DiskImage/Formats/IPF.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Storage/Disk/DiskImage/Formats/IPF.cpp b/Storage/Disk/DiskImage/Formats/IPF.cpp index 063089cc8..ad7024207 100644 --- a/Storage/Disk/DiskImage/Formats/IPF.cpp +++ b/Storage/Disk/DiskImage/Formats/IPF.cpp @@ -304,6 +304,7 @@ std::shared_ptr<Track> IPF::get_track_at_position([[maybe_unused]] Track::Addres segment.data.resize(length); } break;*/ + case Type::Gap: case Type::Data: { printf("Handling data type %d, length %zu bits\n", int(type), length); auto &segment = segments.emplace_back(); @@ -322,7 +323,6 @@ std::shared_ptr<Track> IPF::get_track_at_position([[maybe_unused]] Track::Addres segment.data.resize(length * 2); } break; - case Type::Gap: case Type::Sync: case Type::Raw: { printf("Handling data type %d, length %zu bits\n", int(type), length); From 18b6f17e86da1527c19de0b88988b0b2cffdf7dd Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Thu, 6 Jan 2022 17:24:31 -0500 Subject: [PATCH 21/25] With some refactoring makes some minor steps towards supporting gaps. --- Storage/Disk/DiskImage/Formats/IPF.cpp | 131 ++++++++++++++----------- Storage/Disk/DiskImage/Formats/IPF.hpp | 8 +- 2 files changed, 77 insertions(+), 62 deletions(-) diff --git a/Storage/Disk/DiskImage/Formats/IPF.cpp b/Storage/Disk/DiskImage/Formats/IPF.cpp index ad7024207..a610564d0 100644 --- a/Storage/Disk/DiskImage/Formats/IPF.cpp +++ b/Storage/Disk/DiskImage/Formats/IPF.cpp @@ -8,7 +8,6 @@ #include "IPF.hpp" -#include "../../Track/PCMTrack.hpp" #include "../../Encodings/MFM/Encoder.hpp" #include <cassert> @@ -258,19 +257,22 @@ std::shared_ptr<Track> IPF::get_track_at_position([[maybe_unused]] Track::Addres } type = Type(gap_header & 0x1f); const size_t length = block_size(file_, gap_header); - // TODO: write the gap. switch(type) { case Type::GapLength: - printf("Unhandled gap length %zu bytes\n", length); + printf("Adding gap length %zu bits\n", length); + add_gap(segments, length_of_a_bit, length, block.default_gap_value); break; default: case Type::SampleLength: - printf("Unhandled sampled gap length %zu bytes\n", length); - file_.seek(long(length >> 3), SEEK_CUR); + printf("Adding sampled gap length %zu bits\n", length); + add_raw_data(segments, length_of_a_bit, length); +// file_.seek(long(length >> 3), SEEK_CUR); break; } } + } else if(block.gap_bits) { + add_gap(segments, length_of_a_bit, block.gap_bits, block.default_gap_value); } if(block.data_offset) { @@ -288,65 +290,16 @@ std::shared_ptr<Track> IPF::get_track_at_position([[maybe_unused]] Track::Addres const auto next_chunk = file_.tell() + long(length >> 3); #endif - // TODO: write the data. switch(type) { - /*case Type::Gap: { - auto &segment = segments.emplace_back(); - segment.length_of_a_bit = length_of_a_bit; - segment.data.reserve(size_t(length + 31) & size_t(~31)); - auto encoder = Storage::Encodings::MFM::GetMFMEncoder(segment.data); - while(segment.data.size() < length) { - encoder->add_byte(uint8_t(block.default_gap_value >> 24)); - encoder->add_byte(uint8_t(block.default_gap_value >> 16)); - encoder->add_byte(uint8_t(block.default_gap_value >> 8)); - encoder->add_byte(uint8_t(block.default_gap_value >> 0)); - } - segment.data.resize(length); - } break;*/ - case Type::Gap: - case Type::Data: { - printf("Handling data type %d, length %zu bits\n", int(type), length); - auto &segment = segments.emplace_back(); - segment.length_of_a_bit = length_of_a_bit; - - // Length appears to be in pre-encoded bits; double that to get encoded bits. - const auto byte_length = (length + 7) >> 3; - segment.data.reserve(byte_length * 16); - - auto encoder = Storage::Encodings::MFM::GetMFMEncoder(segment.data); - for(size_t c = 0; c < length; c += 8) { - encoder->add_byte(file_.get8()); - } - - assert(segment.data.size() <= (byte_length * 16)); - segment.data.resize(length * 2); - } break; + case Type::Data: + add_unencoded_data(segments, length_of_a_bit, length); + break; case Type::Sync: - case Type::Raw: { - printf("Handling data type %d, length %zu bits\n", int(type), length); - auto &segment = segments.emplace_back(); - segment.length_of_a_bit = length_of_a_bit; - - const auto bit_length = size_t(length + 7) & size_t(~7); - segment.data.reserve(bit_length); - - for(size_t bit = 0; bit < length; bit += 8) { - const uint8_t next = file_.get8(); - segment.data.push_back(next & 0x80); - segment.data.push_back(next & 0x40); - segment.data.push_back(next & 0x20); - segment.data.push_back(next & 0x10); - segment.data.push_back(next & 0x08); - segment.data.push_back(next & 0x04); - segment.data.push_back(next & 0x02); - segment.data.push_back(next & 0x01); - } - - assert(segment.data.size() <= bit_length); - segment.data.resize(length); - } break; + case Type::Raw: + add_raw_data(segments, length_of_a_bit, length); + break; default: printf("Unhandled data type %d, length %zu bits\n", int(type), length); @@ -424,3 +377,61 @@ Storage::Time IPF::bit_length(TrackDescription::Density density, int block) { return us200; // i.e. default to 2µs. } + +void IPF::add_gap(std::vector<Storage::Disk::PCMSegment> &track, Time bit_length, size_t num_bits, uint32_t value) { + auto &segment = track.emplace_back(); + segment.length_of_a_bit = bit_length; + + // Empirically, I think gaps require MFM encoding. + const auto byte_length = (num_bits + 7) >> 3; + segment.data.reserve(byte_length * 16); + + auto encoder = Storage::Encodings::MFM::GetMFMEncoder(segment.data); + while(segment.data.size() < num_bits) { + encoder->add_byte(uint8_t(value >> 24)); + value = (value << 8) | (value >> 24); + } + + assert(segment.data.size() <= (byte_length * 16)); + segment.data.resize(num_bits); +} + +void IPF::add_unencoded_data(std::vector<Storage::Disk::PCMSegment> &track, Time bit_length, size_t num_bits) { + auto &segment = track.emplace_back(); + segment.length_of_a_bit = bit_length; + + // Length appears to be in pre-encoded bits; double that to get encoded bits. + const auto byte_length = (num_bits + 7) >> 3; + segment.data.reserve(num_bits * 16); + + auto encoder = Storage::Encodings::MFM::GetMFMEncoder(segment.data); + for(size_t c = 0; c < num_bits; c += 8) { + encoder->add_byte(file_.get8()); + } + + assert(segment.data.size() <= (byte_length * 16)); + segment.data.resize(num_bits * 2); +} + +void IPF::add_raw_data(std::vector<Storage::Disk::PCMSegment> &track, Time bit_length, size_t num_bits) { + auto &segment = track.emplace_back(); + segment.length_of_a_bit = bit_length; + + const auto num_bits_ceiling = size_t(num_bits + 7) & size_t(~7); + segment.data.reserve(num_bits_ceiling); + + for(size_t bit = 0; bit < num_bits; bit += 8) { + const uint8_t next = file_.get8(); + segment.data.push_back(next & 0x80); + segment.data.push_back(next & 0x40); + segment.data.push_back(next & 0x20); + segment.data.push_back(next & 0x10); + segment.data.push_back(next & 0x08); + segment.data.push_back(next & 0x04); + segment.data.push_back(next & 0x02); + segment.data.push_back(next & 0x01); + } + + assert(segment.data.size() <= num_bits_ceiling); + segment.data.resize(num_bits); +} diff --git a/Storage/Disk/DiskImage/Formats/IPF.hpp b/Storage/Disk/DiskImage/Formats/IPF.hpp index 1ce1e74c7..fa29aafa8 100644 --- a/Storage/Disk/DiskImage/Formats/IPF.hpp +++ b/Storage/Disk/DiskImage/Formats/IPF.hpp @@ -10,6 +10,7 @@ #define IPF_hpp #include "../DiskImage.hpp" +#include "../../Track/PCMTrack.hpp" #include "../../../FileHolder.hpp" #include "../../../TargetPlatforms.hpp" @@ -68,8 +69,6 @@ class IPF: public DiskImage, public TargetPlatform::TypeDistinguisher { bool has_fuzzy_bits = false; }; - Time bit_length(TrackDescription::Density, int block); - int head_count_; int track_count_; std::map<Track::Address, TrackDescription> tracks_; @@ -79,6 +78,11 @@ class IPF: public DiskImage, public TargetPlatform::TypeDistinguisher { return TargetPlatform::Type(platform_type_); } TargetPlatform::IntType platform_type_ = TargetPlatform::Amiga; + + Time bit_length(TrackDescription::Density, int block); + void add_gap(std::vector<Storage::Disk::PCMSegment> &, Time bit_length, size_t num_bits, uint32_t value); + void add_unencoded_data(std::vector<Storage::Disk::PCMSegment> &, Time bit_length, size_t num_bits); + void add_raw_data(std::vector<Storage::Disk::PCMSegment> &, Time bit_length, size_t num_bits); }; } From e994910ff67cfa1965ae2274694b918be5213650 Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Thu, 2 Jun 2022 16:46:41 -0400 Subject: [PATCH 22/25] Switch to `unique_ptr`. --- Concurrency/AsyncTaskQueue.cpp | 7 ++++--- Concurrency/AsyncTaskQueue.hpp | 4 +--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Concurrency/AsyncTaskQueue.cpp b/Concurrency/AsyncTaskQueue.cpp index 798683c39..66b0fdaa3 100644 --- a/Concurrency/AsyncTaskQueue.cpp +++ b/Concurrency/AsyncTaskQueue.cpp @@ -88,16 +88,17 @@ DeferringAsyncTaskQueue::~DeferringAsyncTaskQueue() { void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) { if(!deferred_tasks_) { - deferred_tasks_ = std::make_shared<std::list<std::function<void(void)>>>(); + deferred_tasks_ = std::make_unique<std::list<std::function<void(void)>>>(); } deferred_tasks_->push_back(function); } void DeferringAsyncTaskQueue::perform() { if(!deferred_tasks_) return; - std::shared_ptr<std::list<std::function<void(void)>>> deferred_tasks = deferred_tasks_; + auto deferred_tasks_raw = deferred_tasks_.release(); deferred_tasks_.reset(); - enqueue([deferred_tasks] { + enqueue([deferred_tasks_raw] { + std::unique_ptr<std::list<std::function<void(void)>>> deferred_tasks(deferred_tasks_raw); for(const auto &function : *deferred_tasks) { function(); } diff --git a/Concurrency/AsyncTaskQueue.hpp b/Concurrency/AsyncTaskQueue.hpp index da5d052c8..2962e329c 100644 --- a/Concurrency/AsyncTaskQueue.hpp +++ b/Concurrency/AsyncTaskQueue.hpp @@ -93,9 +93,7 @@ class DeferringAsyncTaskQueue: public AsyncTaskQueue { void flush(); private: - // TODO: this is a shared_ptr because of the issues capturing moveables in C++11; - // switch to a unique_ptr if/when adapting to C++14 - std::shared_ptr<std::list<std::function<void(void)>>> deferred_tasks_; + std::unique_ptr<std::list<std::function<void(void)>>> deferred_tasks_; }; } From 9d278d80f196b13cc39ead85a700b9b4d32c970f Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Thu, 2 Jun 2022 16:50:59 -0400 Subject: [PATCH 23/25] Remove redundant `reset`. --- Concurrency/AsyncTaskQueue.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Concurrency/AsyncTaskQueue.cpp b/Concurrency/AsyncTaskQueue.cpp index 66b0fdaa3..be63ca571 100644 --- a/Concurrency/AsyncTaskQueue.cpp +++ b/Concurrency/AsyncTaskQueue.cpp @@ -96,7 +96,6 @@ void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) { void DeferringAsyncTaskQueue::perform() { if(!deferred_tasks_) return; auto deferred_tasks_raw = deferred_tasks_.release(); - deferred_tasks_.reset(); enqueue([deferred_tasks_raw] { std::unique_ptr<std::list<std::function<void(void)>>> deferred_tasks(deferred_tasks_raw); for(const auto &function : *deferred_tasks) { From e389dcb9120de28b7a60d1d461bd0322eea54326 Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Thu, 2 Jun 2022 16:52:03 -0400 Subject: [PATCH 24/25] Further simplify syntax. --- Concurrency/AsyncTaskQueue.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Concurrency/AsyncTaskQueue.cpp b/Concurrency/AsyncTaskQueue.cpp index be63ca571..38350d8df 100644 --- a/Concurrency/AsyncTaskQueue.cpp +++ b/Concurrency/AsyncTaskQueue.cpp @@ -95,8 +95,7 @@ void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) { void DeferringAsyncTaskQueue::perform() { if(!deferred_tasks_) return; - auto deferred_tasks_raw = deferred_tasks_.release(); - enqueue([deferred_tasks_raw] { + enqueue([deferred_tasks_raw = deferred_tasks_.release()] { std::unique_ptr<std::list<std::function<void(void)>>> deferred_tasks(deferred_tasks_raw); for(const auto &function : *deferred_tasks) { function(); From 7f33a5ca0c37e1b658372b702dafc345f7f55935 Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Thu, 2 Jun 2022 17:02:36 -0400 Subject: [PATCH 25/25] Simplify: (i) repetitive type for `TaskList`; (ii) unnecessary `unique_ptr`. --- Concurrency/AsyncTaskQueue.cpp | 66 +++++++++++++++++----------------- Concurrency/AsyncTaskQueue.hpp | 14 ++++---- 2 files changed, 41 insertions(+), 39 deletions(-) diff --git a/Concurrency/AsyncTaskQueue.cpp b/Concurrency/AsyncTaskQueue.cpp index 38350d8df..12615430e 100644 --- a/Concurrency/AsyncTaskQueue.cpp +++ b/Concurrency/AsyncTaskQueue.cpp @@ -12,47 +12,47 @@ using namespace Concurrency; AsyncTaskQueue::AsyncTaskQueue() #ifndef USE_GCD - : should_destruct_(false) -#endif -{ -#ifdef USE_GCD - serial_dispatch_queue_ = dispatch_queue_create("com.thomasharte.clocksignal.asyntaskqueue", DISPATCH_QUEUE_SERIAL); + : + should_destruct_(false), + thread_([this] () { + while(!should_destruct_) { + std::function<void(void)> next_function; + + // Take lock, check for a new task. + std::unique_lock lock(queue_mutex_); + if(!pending_tasks_.empty()) { + next_function = pending_tasks_.front(); + pending_tasks_.pop_front(); + } + + if(next_function) { + // If there is a task, release lock and perform it. + lock.unlock(); + next_function(); + } else { + // If there isn't a task, atomically block on the processing condition and release the lock + // until there's something pending (and then release it again via scope). + processing_condition_.wait(lock); + } + } + }) #else - thread_ = std::make_unique<std::thread>([this]() { - while(!should_destruct_) { - std::function<void(void)> next_function; - - // Take lock, check for a new task - std::unique_lock lock(queue_mutex_); - if(!pending_tasks_.empty()) { - next_function = pending_tasks_.front(); - pending_tasks_.pop_front(); - } - - if(next_function) { - // If there is a task, release lock and perform it - lock.unlock(); - next_function(); - } else { - // If there isn't a task, atomically block on the processing condition and release the lock - // until there's something pending (and then release it again via scope) - processing_condition_.wait(lock); - } - } - }); + : serial_dispatch_queue_(dispatch_queue_create("com.thomasharte.clocksignal.asyntaskqueue", DISPATCH_QUEUE_SERIAL)) #endif -} +{} AsyncTaskQueue::~AsyncTaskQueue() { #ifdef USE_GCD flush(); dispatch_release(serial_dispatch_queue_); - serial_dispatch_queue_ = nullptr; #else + // Set should destruct, and then give the thread a bit of a nudge + // via an empty enqueue. should_destruct_ = true; enqueue([](){}); - thread_->join(); - thread_.reset(); + + // Wait for the thread safely to terminate. + thread_.join(); #endif } @@ -88,7 +88,7 @@ DeferringAsyncTaskQueue::~DeferringAsyncTaskQueue() { void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) { if(!deferred_tasks_) { - deferred_tasks_ = std::make_unique<std::list<std::function<void(void)>>>(); + deferred_tasks_ = std::make_unique<TaskList>(); } deferred_tasks_->push_back(function); } @@ -96,7 +96,7 @@ void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) { void DeferringAsyncTaskQueue::perform() { if(!deferred_tasks_) return; enqueue([deferred_tasks_raw = deferred_tasks_.release()] { - std::unique_ptr<std::list<std::function<void(void)>>> deferred_tasks(deferred_tasks_raw); + std::unique_ptr<TaskList> deferred_tasks(deferred_tasks_raw); for(const auto &function : *deferred_tasks) { function(); } diff --git a/Concurrency/AsyncTaskQueue.hpp b/Concurrency/AsyncTaskQueue.hpp index 2962e329c..225f49580 100644 --- a/Concurrency/AsyncTaskQueue.hpp +++ b/Concurrency/AsyncTaskQueue.hpp @@ -23,6 +23,8 @@ namespace Concurrency { +using TaskList = std::list<std::function<void(void)>>; + /*! An async task queue allows a caller to enqueue void(void) functions. Those functions are guaranteed to be performed serially and asynchronously from the caller. A caller may also request to flush, @@ -51,12 +53,12 @@ class AsyncTaskQueue { #ifdef USE_GCD dispatch_queue_t serial_dispatch_queue_; #else - std::unique_ptr<std::thread> thread_; - - std::mutex queue_mutex_; - std::list<std::function<void(void)>> pending_tasks_; - std::condition_variable processing_condition_; std::atomic_bool should_destruct_; + std::condition_variable processing_condition_; + std::mutex queue_mutex_; + TaskList pending_tasks_; + + std::thread thread_; #endif }; @@ -93,7 +95,7 @@ class DeferringAsyncTaskQueue: public AsyncTaskQueue { void flush(); private: - std::unique_ptr<std::list<std::function<void(void)>>> deferred_tasks_; + std::unique_ptr<TaskList> deferred_tasks_; }; }