1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-04-03 06:29:47 +00:00

Merge pull request #250 from TomHarte/TrackToBits

Refactors MFM support, breaking it into components
This commit is contained in:
Thomas Harte 2017-09-30 20:31:43 -04:00 committed by GitHub
commit cd1e5dea4d
37 changed files with 1392 additions and 1038 deletions

View File

@ -7,7 +7,7 @@
//
#include "1770.hpp"
#include "../../Storage/Disk/Encodings/MFM.hpp"
#include "../../Storage/Disk/Encodings/MFM/Constants.hpp"
using namespace WD;

View File

@ -7,7 +7,7 @@
//
#include "i8272.hpp"
#include "../../Storage/Disk/Encodings/MFM.hpp"
//#include "../../Storage/Disk/Encodings/MFM/Encoder.hpp"
#include <cstdio>

View File

@ -84,6 +84,7 @@
4B50730A1DDFCFDF00C48FBD /* ArrayBuilderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */; };
4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */; };
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */; };
4B58601E1F806AB200AEE2E3 /* MFMSectorDump.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B58601C1F806AB200AEE2E3 /* MFMSectorDump.cpp */; };
4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */; };
4B5A12571DD55862007A2231 /* Disassembler6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5A12551DD55862007A2231 /* Disassembler6502.cpp */; };
4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5FADB81DE3151600AEC565 /* FileHolder.cpp */; };
@ -94,6 +95,10 @@
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; };
4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; };
4B6A4C991F58F09E00E3F787 /* 6502Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */; };
4B7136861F78724F008B8ED9 /* Encoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136841F78724F008B8ED9 /* Encoder.cpp */; };
4B7136891F78725F008B8ED9 /* Shifter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136871F78725F008B8ED9 /* Shifter.cpp */; };
4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368C1F788112008B8ED9 /* Parser.cpp */; };
4B7136911F789C93008B8ED9 /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368F1F789C93008B8ED9 /* SegmentParser.cpp */; };
4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7913CA1DFCD80E00175A82 /* Video.cpp */; };
4B79E4441E3AF38600141F11 /* cassette.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4411E3AF38600141F11 /* cassette.png */; };
4B79E4451E3AF38600141F11 /* floppy35.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4421E3AF38600141F11 /* floppy35.png */; };
@ -416,6 +421,7 @@
4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */; };
4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */; };
4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */; };
4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */; };
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B74D1CD194CC00F86E85 /* Shader.cpp */; };
4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B7501CD1956900F86E85 /* OutputShader.cpp */; };
4BC5E4921D7ED365008CF980 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC5E4901D7ED365008CF980 /* StaticAnalyser.cpp */; };
@ -448,12 +454,12 @@
4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */; };
4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; };
4BF1354C1D6D2C300054B2EA /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF1354A1D6D2C300054B2EA /* StaticAnalyser.cpp */; };
4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8295B1D8F048B001BAE39 /* MFM.cpp */; };
4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829641D8F732B001BAE39 /* Disk.cpp */; };
4BFCA1241ECBDCB400AC40C1 /* AllRAMProcessor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFCA1211ECBDCAF00AC40C1 /* AllRAMProcessor.cpp */; };
4BFCA1271ECBE33200AC40C1 /* TestMachineZ80.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BFCA1261ECBE33200AC40C1 /* TestMachineZ80.mm */; };
4BFCA1291ECBE7A700AC40C1 /* zexall.com in Resources */ = {isa = PBXBuildFile; fileRef = 4BFCA1281ECBE7A700AC40C1 /* zexall.com */; };
4BFCA12B1ECBE7C400AC40C1 /* ZexallTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFCA12A1ECBE7C400AC40C1 /* ZexallTests.swift */; };
4BFDD78C1F7F2DB4008579B9 /* ImplicitSectors.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFDD78B1F7F2DB4008579B9 /* ImplicitSectors.cpp */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -626,6 +632,8 @@
4B55CE5B1C3B7D6F0093A61B /* CSOpenGLView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSOpenGLView.h; sourceTree = "<group>"; };
4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSOpenGLView.m; sourceTree = "<group>"; };
4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineDocument.swift; sourceTree = "<group>"; };
4B58601C1F806AB200AEE2E3 /* MFMSectorDump.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MFMSectorDump.cpp; sourceTree = "<group>"; };
4B58601D1F806AB200AEE2E3 /* MFMSectorDump.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MFMSectorDump.hpp; sourceTree = "<group>"; };
4B59199A1DAC6C46005BB85C /* OricTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OricTAP.cpp; sourceTree = "<group>"; };
4B59199B1DAC6C46005BB85C /* OricTAP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OricTAP.hpp; sourceTree = "<group>"; };
4B5A12551DD55862007A2231 /* Disassembler6502.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Disassembler6502.cpp; path = ../../StaticAnalyser/Disassembler/Disassembler6502.cpp; sourceTree = "<group>"; };
@ -647,6 +655,16 @@
4B6A4C911F58F09E00E3F787 /* 6502AllRAM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6502AllRAM.cpp; sourceTree = "<group>"; };
4B6A4C921F58F09E00E3F787 /* 6502AllRAM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6502AllRAM.hpp; sourceTree = "<group>"; };
4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6502Base.cpp; sourceTree = "<group>"; };
4B7136841F78724F008B8ED9 /* Encoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Encoder.cpp; sourceTree = "<group>"; };
4B7136851F78724F008B8ED9 /* Encoder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Encoder.hpp; sourceTree = "<group>"; };
4B7136871F78725F008B8ED9 /* Shifter.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Shifter.cpp; sourceTree = "<group>"; };
4B7136881F78725F008B8ED9 /* Shifter.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Shifter.hpp; sourceTree = "<group>"; };
4B71368A1F787349008B8ED9 /* Constants.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Constants.hpp; sourceTree = "<group>"; };
4B71368B1F7880D1008B8ED9 /* Sector.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sector.hpp; sourceTree = "<group>"; };
4B71368C1F788112008B8ED9 /* Parser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Parser.cpp; sourceTree = "<group>"; };
4B71368D1F788112008B8ED9 /* Parser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Parser.hpp; sourceTree = "<group>"; };
4B71368F1F789C93008B8ED9 /* SegmentParser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SegmentParser.cpp; sourceTree = "<group>"; };
4B7136901F789C93008B8ED9 /* SegmentParser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SegmentParser.hpp; sourceTree = "<group>"; };
4B77069C1EC904570053B588 /* Z80.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Z80.hpp; path = Z80/Z80.hpp; sourceTree = "<group>"; };
4B7913CA1DFCD80E00175A82 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Electron/Video.cpp; sourceTree = "<group>"; };
4B7913CB1DFCD80E00175A82 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = Electron/Video.hpp; sourceTree = "<group>"; };
@ -680,6 +698,7 @@
4B8805FA1DCFF807003085B1 /* Oric.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Oric.hpp; path = Parsers/Oric.hpp; sourceTree = "<group>"; };
4B8805FC1DD02552003085B1 /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = ../../StaticAnalyser/Oric/Tape.cpp; sourceTree = "<group>"; };
4B8805FD1DD02552003085B1 /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = ../../StaticAnalyser/Oric/Tape.hpp; sourceTree = "<group>"; };
4B8D287E1F77207100645199 /* TrackSerialiser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TrackSerialiser.hpp; sourceTree = "<group>"; };
4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyboardMachine.hpp; sourceTree = "<group>"; };
4B8FE2141DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/Atari2600Options.xib"; sourceTree = SOURCE_ROOT; };
4B8FE2161DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/MachineDocument.xib"; sourceTree = SOURCE_ROOT; };
@ -1015,6 +1034,7 @@
4BBF99191C8FC2750075DAFB /* CRTTypes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTTypes.hpp; sourceTree = "<group>"; };
4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ZX8081.cpp; path = Parsers/ZX8081.cpp; sourceTree = "<group>"; };
4BBFBB6B1EE8401E00C01E7A /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ZX8081.hpp; path = Parsers/ZX8081.hpp; sourceTree = "<group>"; };
4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = TrackSerialiser.cpp; sourceTree = "<group>"; };
4BC3B74D1CD194CC00F86E85 /* Shader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Shader.cpp; sourceTree = "<group>"; };
4BC3B74E1CD194CC00F86E85 /* Shader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Shader.hpp; sourceTree = "<group>"; };
4BC3B7501CD1956900F86E85 /* OutputShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OutputShader.cpp; sourceTree = "<group>"; };
@ -1093,8 +1113,6 @@
4BF4A2D91F534DB300B171F4 /* TargetPlatforms.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TargetPlatforms.hpp; sourceTree = "<group>"; };
4BF4A2DA1F5365C600B171F4 /* CSZX8081+Instantiation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CSZX8081+Instantiation.h"; sourceTree = "<group>"; };
4BF6606A1F281573002CB053 /* ClockReceiver.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ClockReceiver.hpp; sourceTree = "<group>"; };
4BF8295B1D8F048B001BAE39 /* MFM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MFM.cpp; path = Encodings/MFM.cpp; sourceTree = "<group>"; };
4BF8295C1D8F048B001BAE39 /* MFM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = MFM.hpp; path = Encodings/MFM.hpp; sourceTree = "<group>"; };
4BF8295F1D8F3C87001BAE39 /* CRC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRC.hpp; path = ../../NumberTheory/CRC.hpp; sourceTree = "<group>"; };
4BF829641D8F732B001BAE39 /* Disk.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Disk.cpp; path = ../../StaticAnalyser/Acorn/Disk.cpp; sourceTree = "<group>"; };
4BF829651D8F732B001BAE39 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Disk.hpp; path = ../../StaticAnalyser/Acorn/Disk.hpp; sourceTree = "<group>"; };
@ -1105,6 +1123,8 @@
4BFCA1261ECBE33200AC40C1 /* TestMachineZ80.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TestMachineZ80.mm; sourceTree = "<group>"; };
4BFCA1281ECBE7A700AC40C1 /* zexall.com */ = {isa = PBXFileReference; lastKnownFileType = file; name = zexall.com; path = Zexall/zexall.com; sourceTree = "<group>"; };
4BFCA12A1ECBE7C400AC40C1 /* ZexallTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZexallTests.swift; sourceTree = "<group>"; };
4BFDD78A1F7F2DB4008579B9 /* ImplicitSectors.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ImplicitSectors.hpp; sourceTree = "<group>"; };
4BFDD78B1F7F2DB4008579B9 /* ImplicitSectors.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ImplicitSectors.cpp; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -1407,14 +1427,16 @@
isa = PBXGroup;
children = (
4B4518711F75E91800926311 /* PCMPatchedTrack.cpp */,
4B4518721F75E91800926311 /* PCMPatchedTrack.hpp */,
4B4518731F75E91800926311 /* PCMSegment.cpp */,
4B4518741F75E91800926311 /* PCMSegment.hpp */,
4B4518751F75E91800926311 /* PCMTrack.cpp */,
4B4518761F75E91800926311 /* PCMTrack.hpp */,
4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */,
4B4518771F75E91800926311 /* UnformattedTrack.cpp */,
4B4518781F75E91800926311 /* UnformattedTrack.hpp */,
4B4518721F75E91800926311 /* PCMPatchedTrack.hpp */,
4B4518741F75E91800926311 /* PCMSegment.hpp */,
4B4518761F75E91800926311 /* PCMTrack.hpp */,
4B4518881F75ECB100926311 /* Track.hpp */,
4B8D287E1F77207100645199 /* TrackSerialiser.hpp */,
4B4518781F75E91800926311 /* UnformattedTrack.hpp */,
);
path = Track;
sourceTree = "<group>";
@ -1463,10 +1485,13 @@
4B4518941F75FD1B00926311 /* G64.hpp */,
4B4518951F75FD1B00926311 /* HFE.cpp */,
4B4518961F75FD1B00926311 /* HFE.hpp */,
4B58601C1F806AB200AEE2E3 /* MFMSectorDump.cpp */,
4B58601D1F806AB200AEE2E3 /* MFMSectorDump.hpp */,
4B4518971F75FD1B00926311 /* OricMFMDSK.cpp */,
4B4518981F75FD1B00926311 /* OricMFMDSK.hpp */,
4B4518991F75FD1B00926311 /* SSD.cpp */,
4B45189A1F75FD1B00926311 /* SSD.hpp */,
4BFDD7891F7F2DB4008579B9 /* Utility */,
);
path = Formats;
sourceTree = "<group>";
@ -1649,6 +1674,24 @@
path = Implementation;
sourceTree = "<group>";
};
4B7136831F78724F008B8ED9 /* MFM */ = {
isa = PBXGroup;
children = (
4B7136841F78724F008B8ED9 /* Encoder.cpp */,
4B71368C1F788112008B8ED9 /* Parser.cpp */,
4B71368F1F789C93008B8ED9 /* SegmentParser.cpp */,
4B7136871F78725F008B8ED9 /* Shifter.cpp */,
4B71368A1F787349008B8ED9 /* Constants.hpp */,
4B7136851F78724F008B8ED9 /* Encoder.hpp */,
4B71368D1F788112008B8ED9 /* Parser.hpp */,
4B71368B1F7880D1008B8ED9 /* Sector.hpp */,
4B7136901F789C93008B8ED9 /* SegmentParser.hpp */,
4B7136881F78725F008B8ED9 /* Shifter.hpp */,
);
name = MFM;
path = Encodings/MFM;
sourceTree = "<group>";
};
4B77069E1EC9045B0053B588 /* Z80 */ = {
isa = PBXGroup;
children = (
@ -2016,10 +2059,9 @@
4BB697CF1D4BA44900248BDF /* Encodings */ = {
isa = PBXGroup;
children = (
4B7136831F78724F008B8ED9 /* MFM */,
4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */,
4BB697CD1D4BA44400248BDF /* CommodoreGCR.hpp */,
4BF8295B1D8F048B001BAE39 /* MFM.cpp */,
4BF8295C1D8F048B001BAE39 /* MFM.hpp */,
);
name = Encodings;
sourceTree = "<group>";
@ -2423,6 +2465,15 @@
path = ../../ClockReceiver;
sourceTree = "<group>";
};
4BFDD7891F7F2DB4008579B9 /* Utility */ = {
isa = PBXGroup;
children = (
4BFDD78B1F7F2DB4008579B9 /* ImplicitSectors.cpp */,
4BFDD78A1F7F2DB4008579B9 /* ImplicitSectors.hpp */,
);
path = Utility;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -2851,6 +2902,8 @@
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */,
4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */,
4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */,
4B7136861F78724F008B8ED9 /* Encoder.cpp in Sources */,
4B58601E1F806AB200AEE2E3 /* MFMSectorDump.cpp in Sources */,
4B448E841F1C4C480009ABD6 /* PulseQueuedTape.cpp in Sources */,
4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */,
4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */,
@ -2874,6 +2927,7 @@
4B3FE75E1F3CF68B00448EE4 /* CPM.cpp in Sources */,
4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */,
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */,
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */,
4B38F3481F2EC11D00D9235D /* AmstradCPC.cpp in Sources */,
4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */,
@ -2895,6 +2949,7 @@
4B4518831F75E91A00926311 /* PCMTrack.cpp in Sources */,
4B45189F1F75FD1C00926311 /* AcornADF.cpp in Sources */,
4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */,
4B7136911F789C93008B8ED9 /* SegmentParser.cpp in Sources */,
4B4518A21F75FD1C00926311 /* G64.cpp in Sources */,
4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */,
4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */,
@ -2906,7 +2961,7 @@
4B8334841F5DA0360097E338 /* Z80Storage.cpp in Sources */,
4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */,
4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */,
4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */,
4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */,
4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */,
4B14978B1EE4AC5E00CE2596 /* StaticAnalyser.cpp in Sources */,
4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */,
@ -2953,6 +3008,8 @@
4BEA52661DF3472B007E74F2 /* Speaker.cpp in Sources */,
4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */,
4B83348A1F5DB94B0097E338 /* IRQDelegatePortHandler.cpp in Sources */,
4B7136891F78725F008B8ED9 /* Shifter.cpp in Sources */,
4BFDD78C1F7F2DB4008579B9 /* ImplicitSectors.cpp in Sources */,
4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */,
4B5073071DDD3B9400C48FBD /* ArrayBuilder.cpp in Sources */,
4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */,

View File

@ -95,9 +95,16 @@ class MachineDocument:
}
override func close() {
bestEffortUpdater.flush()
optionsPanel?.setIsVisible(false)
optionsPanel = nil
openGLView.delegate = nil
bestEffortUpdater.delegate = nil
bestEffortUpdater = nil
actionLock.lock()
drawLock.lock()
machine = nil
openGLView.invalidate()
openGLView.openGLContext!.makeCurrentContext()
actionLock.unlock()

View File

@ -22,7 +22,7 @@
@property (nonatomic, assign) double clockRate;
@property (nonatomic, assign) BOOL runAsUnlimited;
@property (nonatomic, weak) id<CSBestEffortUpdaterDelegate> delegate;
@property (atomic, weak) id<CSBestEffortUpdaterDelegate> delegate;
- (void)update;
- (void)flush;

View File

@ -20,6 +20,7 @@
NSTimeInterval _previousTimeInterval;
NSTimeInterval _cyclesError;
BOOL _hasSkipped;
id<CSBestEffortUpdaterDelegate> _delegate;
}
- (instancetype)init
@ -35,15 +36,12 @@
return self;
}
- (void)update
{
- (void)update {
// Always post an -openGLView:didUpdateToTime: if a previous one isn't still ongoing. This is the hook upon which the substantial processing occurs.
if(!atomic_flag_test_and_set(&_updateIsOngoing))
{
if(!atomic_flag_test_and_set(&_updateIsOngoing)) {
dispatch_async(_serialDispatchQueue, ^{
NSTimeInterval timeInterval = [NSDate timeIntervalSinceReferenceDate];
if(_previousTimeInterval > DBL_EPSILON && timeInterval > _previousTimeInterval)
{
if(_previousTimeInterval > DBL_EPSILON && timeInterval > _previousTimeInterval) {
NSTimeInterval timeToRunFor = timeInterval - _previousTimeInterval;
double cyclesToRunFor = timeToRunFor * self.clockRate + _cyclesError;
@ -52,24 +50,35 @@
// treat 'unlimited' as running at a factor of 10
if(self.runAsUnlimited) integerCyclesToRunFor *= 10;
[self.delegate bestEffortUpdater:self runForCycles:integerCyclesToRunFor didSkipPreviousUpdate:_hasSkipped];
[_delegate bestEffortUpdater:self runForCycles:integerCyclesToRunFor didSkipPreviousUpdate:_hasSkipped];
}
_previousTimeInterval = timeInterval;
_hasSkipped = NO;
atomic_flag_clear(&_updateIsOngoing);
});
}
else
{
} else {
dispatch_async(_serialDispatchQueue, ^{
_hasSkipped = YES;
});
}
}
- (void)flush
{
- (void)flush {
dispatch_sync(_serialDispatchQueue, ^{});
}
- (void)setDelegate:(id<CSBestEffortUpdaterDelegate>)delegate {
dispatch_sync(_serialDispatchQueue, ^{
_delegate = delegate;
});
}
- (id<CSBestEffortUpdaterDelegate>)delegate {
__block id<CSBestEffortUpdaterDelegate> delegate;
dispatch_sync(_serialDispatchQueue, ^{
delegate = _delegate;
});
return delegate;
}
@end

View File

@ -8,7 +8,7 @@
#include "Disk.hpp"
#include "../../Storage/Disk/Controller/DiskController.hpp"
#include "../../Storage/Disk/Encodings/MFM.hpp"
#include "../../Storage/Disk/Encodings/MFM/Parser.hpp"
#include "../../NumberTheory/CRC.hpp"
#include <algorithm>
@ -19,8 +19,8 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
std::unique_ptr<Catalogue> catalogue(new Catalogue);
Storage::Encodings::MFM::Parser parser(false, disk);
std::shared_ptr<Storage::Encodings::MFM::Sector> names = parser.get_sector(0, 0, 0);
std::shared_ptr<Storage::Encodings::MFM::Sector> details = parser.get_sector(0, 0, 1);
Storage::Encodings::MFM::Sector *names = parser.get_sector(0, 0, 0);
Storage::Encodings::MFM::Sector *details = parser.get_sector(0, 0, 1);
if(!names || !details) return nullptr;
if(names->data.size() != 256 || details->data.size() != 256) return nullptr;
@ -61,7 +61,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
uint8_t track = (uint8_t)(start_sector / 10);
start_sector++;
std::shared_ptr<Storage::Encodings::MFM::Sector> next_sector = parser.get_sector(0, track, sector);
Storage::Encodings::MFM::Sector *next_sector = parser.get_sector(0, track, sector);
if(!next_sector) break;
long length_from_sector = std::min(data_length, 256l);
@ -77,13 +77,13 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::sh
std::unique_ptr<Catalogue> catalogue(new Catalogue);
Storage::Encodings::MFM::Parser parser(true, disk);
std::shared_ptr<Storage::Encodings::MFM::Sector> free_space_map_second_half = parser.get_sector(0, 0, 1);
Storage::Encodings::MFM::Sector *free_space_map_second_half = parser.get_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++) {
std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector(0, 0, c);
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, c);
if(!sector) return nullptr;
root_directory.insert(root_directory.end(), sector->data.begin(), sector->data.end());
}

View File

@ -9,7 +9,7 @@
#include "StaticAnalyser.hpp"
#include "../../Storage/Disk/Parsers/CPM.hpp"
#include "../../Storage/Disk/Encodings/MFM.hpp"
#include "../../Storage/Disk/Encodings/MFM/Parser.hpp"
static bool strcmp_insensitive(const char *a, const char *b) {
if(strlen(a) != strlen(b)) return false;
@ -152,7 +152,7 @@ static void InspectCatalogue(
static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, StaticAnalyser::Target &target) {
Storage::Encodings::MFM::Parser parser(true, disk);
std::shared_ptr<Storage::Encodings::MFM::Sector> boot_sector = parser.get_sector(0, 0, 0x41);
Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41);
if(boot_sector != nullptr) {
// 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

@ -8,7 +8,7 @@
#include "MFMDiskController.hpp"
#include "../Encodings/MFM.hpp"
#include "../Encodings/MFM/Constants.hpp"
using namespace Storage::Disk;
@ -16,7 +16,7 @@ MFMController::MFMController(Cycles clock_rate) :
Storage::Disk::Controller(clock_rate),
crc_generator_(0x1021, 0xffff),
data_mode_(DataMode::Scanning),
is_awaiting_marker_value_(false) {
shifter_(&crc_generator_) {
}
void MFMController::process_index_hole() {
@ -34,7 +34,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);
if(!is_double_density) is_awaiting_marker_value_ = false;
shifter_.set_is_double_density(is_double_density);
}
bool MFMController::get_is_double_density() {
@ -43,6 +43,7 @@ bool MFMController::get_is_double_density() {
void MFMController::set_data_mode(DataMode mode) {
data_mode_ = mode;
shifter_.set_should_obey_syncs(mode == DataMode::Scanning);
}
MFMController::Token MFMController::get_latest_token() {
@ -56,102 +57,32 @@ NumberTheory::CRC16 &MFMController::get_crc_generator() {
void MFMController::process_input_bit(int value) {
if(data_mode_ == DataMode::Writing) return;
shift_register_ = (shift_register_ << 1) | value;
bits_since_token_++;
if(data_mode_ == DataMode::Scanning) {
Token::Type token_type = Token::Byte;
if(!is_double_density_) {
switch(shift_register_ & 0xffff) {
case Storage::Encodings::MFM::FMIndexAddressMark:
token_type = Token::Index;
crc_generator_.reset();
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::IndexAddressByte);
break;
case Storage::Encodings::MFM::FMIDAddressMark:
token_type = Token::ID;
crc_generator_.reset();
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::IDAddressByte);
break;
case Storage::Encodings::MFM::FMDataAddressMark:
token_type = Token::Data;
crc_generator_.reset();
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::DataAddressByte);
break;
case Storage::Encodings::MFM::FMDeletedDataAddressMark:
token_type = Token::DeletedData;
crc_generator_.reset();
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::DeletedDataAddressByte);
break;
default:
break;
}
} else {
switch(shift_register_ & 0xffff) {
case Storage::Encodings::MFM::MFMIndexSync:
bits_since_token_ = 0;
is_awaiting_marker_value_ = true;
token_type = Token::Sync;
latest_token_.byte_value = Storage::Encodings::MFM::MFMIndexSyncByteValue;
break;
case Storage::Encodings::MFM::MFMSync:
bits_since_token_ = 0;
is_awaiting_marker_value_ = true;
crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
token_type = Token::Sync;
latest_token_.byte_value = Storage::Encodings::MFM::MFMSyncByteValue;
break;
default:
break;
}
}
if(token_type != Token::Byte) {
latest_token_.type = token_type;
bits_since_token_ = 0;
posit_event((int)Event::Token);
return;
}
}
if(bits_since_token_ == 16) {
latest_token_.type = Token::Byte;
latest_token_.byte_value = (uint8_t)(
((shift_register_ & 0x0001) >> 0) |
((shift_register_ & 0x0004) >> 1) |
((shift_register_ & 0x0010) >> 2) |
((shift_register_ & 0x0040) >> 3) |
((shift_register_ & 0x0100) >> 4) |
((shift_register_ & 0x0400) >> 5) |
((shift_register_ & 0x1000) >> 6) |
((shift_register_ & 0x4000) >> 7));
bits_since_token_ = 0;
if(is_awaiting_marker_value_ && is_double_density_) {
is_awaiting_marker_value_ = false;
switch(latest_token_.byte_value) {
case Storage::Encodings::MFM::IndexAddressByte:
latest_token_.type = Token::Index;
break;
case Storage::Encodings::MFM::IDAddressByte:
latest_token_.type = Token::ID;
break;
case Storage::Encodings::MFM::DataAddressByte:
latest_token_.type = Token::Data;
break;
case Storage::Encodings::MFM::DeletedDataAddressByte:
latest_token_.type = Token::DeletedData;
break;
default: break;
}
}
crc_generator_.add(latest_token_.byte_value);
posit_event((int)Event::Token);
shifter_.add_input_bit(value);
switch(shifter_.get_token()) {
case Encodings::MFM::Shifter::Token::None:
return;
case Encodings::MFM::Shifter::Token::Index:
latest_token_.type = Token::Index;
break;
case Encodings::MFM::Shifter::Token::ID:
latest_token_.type = Token::ID;
break;
case Encodings::MFM::Shifter::Token::Data:
latest_token_.type = Token::Data;
break;
case Encodings::MFM::Shifter::Token::DeletedData:
latest_token_.type = Token::DeletedData;
break;
case Encodings::MFM::Shifter::Token::Sync:
latest_token_.type = Token::Sync;
break;
case Encodings::MFM::Shifter::Token::Byte:
latest_token_.type = Token::Byte;
break;
}
latest_token_.byte_value = shifter_.get_byte();
posit_event((int)Event::Token);
}
void MFMController::write_bit(int bit) {

View File

@ -12,6 +12,7 @@
#include "DiskController.hpp"
#include "../../../NumberTheory/CRC.hpp"
#include "../../../ClockReceiver/ClockReceiver.hpp"
#include "../Encodings/MFM/Shifter.hpp"
namespace Storage {
namespace Disk {
@ -150,18 +151,14 @@ class MFMController: public Controller {
virtual void process_index_hole();
virtual void process_write_completed();
// PLL input state
int bits_since_token_;
int shift_register_;
bool is_awaiting_marker_value_;
// Reading state.
Token latest_token_;
Encodings::MFM::Shifter shifter_;
// input configuration
bool is_double_density_;
DataMode data_mode_;
// output
Token latest_token_;
// writing
int last_bit_;

View File

@ -8,23 +8,20 @@
#include "AcornADF.hpp"
#include <sys/stat.h>
#include "../../Encodings/MFM.hpp"
#include "Utility/ImplicitSectors.hpp"
namespace {
static const unsigned int sectors_per_track = 16;
static const unsigned int bytes_per_sector = 256;
static const unsigned int sector_size = 1;
}
using namespace Storage::Disk;
AcornADF::AcornADF(const char *file_name) :
Storage::FileHolder(file_name) {
AcornADF::AcornADF(const char *file_name) : MFMSectorDump(file_name) {
// very loose validation: the file needs to be a multiple of 256 bytes
// and not ungainly large
if(file_stats_.st_size % bytes_per_sector) throw ErrorNotAcornADF;
if(file_stats_.st_size < 7 * bytes_per_sector) throw ErrorNotAcornADF;
if(file_stats_.st_size % (off_t)(128 << sector_size)) throw ErrorNotAcornADF;
if(file_stats_.st_size < 7 * (off_t)(128 << sector_size)) throw ErrorNotAcornADF;
// check that the initial directory's 'Hugo's are present
fseek(file_, 513, SEEK_SET);
@ -35,6 +32,8 @@ AcornADF::AcornADF(const char *file_name) :
fseek(file_, 0x6fb, SEEK_SET);
fread(bytes, 1, 4, file_);
if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw ErrorNotAcornADF;
set_geometry(sectors_per_track, sector_size, true);
}
unsigned int AcornADF::get_head_position_count() {
@ -45,64 +44,6 @@ unsigned int AcornADF::get_head_count() {
return 1;
}
bool AcornADF::get_is_read_only() {
return is_read_only_;
}
long AcornADF::get_file_offset_for_position(unsigned int head, unsigned int position) {
return (position * 1 + head) * bytes_per_sector * sectors_per_track;
}
std::shared_ptr<Track> AcornADF::get_track_at_position(unsigned int head, unsigned int position) {
std::shared_ptr<Track> track;
if(head >= 2) return track;
long file_offset = get_file_offset_for_position(head, position);
std::vector<Storage::Encodings::MFM::Sector> sectors;
{
std::lock_guard<std::mutex> lock_guard(file_access_mutex_);
fseek(file_, file_offset, SEEK_SET);
for(unsigned int sector = 0; sector < sectors_per_track; sector++) {
Storage::Encodings::MFM::Sector new_sector;
new_sector.track = (uint8_t)position;
new_sector.side = (uint8_t)head;
new_sector.sector = (uint8_t)sector;
new_sector.size = sector_size;
new_sector.data.resize(bytes_per_sector);
fread(&new_sector.data[0], 1, bytes_per_sector, file_);
if(feof(file_))
break;
sectors.push_back(std::move(new_sector));
}
}
if(sectors.size()) return Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors);
return track;
}
void AcornADF::set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track) {
std::vector<uint8_t> parsed_track;
Storage::Encodings::MFM::Parser parser(true, track);
for(unsigned int c = 0; c < sectors_per_track; c++) {
std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector(0, (uint8_t)position, (uint8_t)c);
if(sector) {
parsed_track.insert(parsed_track.end(), sector->data.begin(), sector->data.end());
} else {
// TODO: what's correct here? Warn the user that whatever has been written to the disk,
// it can no longer be stored as an SSD? If so, warn them by what route?
parsed_track.resize(parsed_track.size() + bytes_per_sector);
}
}
long file_offset = get_file_offset_for_position(head, position);
std::lock_guard<std::mutex> lock_guard(file_access_mutex_);
ensure_file_is_at_least_length(file_offset);
fseek(file_, file_offset, SEEK_SET);
fwrite(parsed_track.data(), 1, parsed_track.size(), file_);
return (position * 1 + head) * (128 << sector_size) * sectors_per_track;
}

View File

@ -9,8 +9,7 @@
#ifndef AcornADF_hpp
#define AcornADF_hpp
#include "../DiskImage.hpp"
#include "../../../FileHolder.hpp"
#include "MFMSectorDump.hpp"
namespace Storage {
namespace Disk {
@ -18,7 +17,7 @@ namespace Disk {
/*!
Provies a @c Disk containing an ADF disk image a decoded sector dump of an Acorn ADFS disk.
*/
class AcornADF: public DiskImage, public Storage::FileHolder {
class AcornADF: public MFMSectorDump {
public:
/*!
Construct an @c AcornADF containing content from the file with name @c file_name.
@ -34,12 +33,8 @@ class AcornADF: public DiskImage, public Storage::FileHolder {
unsigned int get_head_position_count();
unsigned int get_head_count();
bool get_is_read_only();
void set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track);
std::shared_ptr<Track> get_track_at_position(unsigned int head, unsigned int position);
private:
std::mutex file_access_mutex_;
long get_file_offset_for_position(unsigned int head, unsigned int position);
};

View File

@ -8,7 +8,7 @@
#include "CPCDSK.hpp"
#include "../../Encodings/MFM.hpp"
#include "../../Encodings/MFM/Encoder.hpp"
using namespace Storage::Disk;
@ -114,9 +114,9 @@ std::shared_ptr<Track> CPCDSK::get_track_at_position(unsigned int head, unsigned
std::vector<Storage::Encodings::MFM::Sector> sectors;
for(auto &sector_info : sector_infos) {
Storage::Encodings::MFM::Sector new_sector;
new_sector.track = sector_info.track;
new_sector.side = sector_info.side;
new_sector.sector = sector_info.sector;
new_sector.address.track = sector_info.track;
new_sector.address.side = sector_info.side;
new_sector.address.sector = sector_info.sector;
new_sector.size = sector_info.length;
size_t data_size;

View File

@ -0,0 +1,54 @@
//
// MFMSectorDump.cpp
// Clock Signal
//
// Created by Thomas Harte on 30/09/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "MFMSectorDump.hpp"
#include "Utility/ImplicitSectors.hpp"
using namespace Storage::Disk;
MFMSectorDump::MFMSectorDump(const char *file_name) : Storage::FileHolder(file_name) {}
void MFMSectorDump::set_geometry(int sectors_per_track, uint8_t sector_size, bool is_double_density) {
sectors_per_track_ = sectors_per_track;
sector_size_ = sector_size;
is_double_density_ = is_double_density;
}
bool MFMSectorDump::get_is_read_only() {
return is_read_only_;
}
std::shared_ptr<Track> MFMSectorDump::get_track_at_position(unsigned int head, unsigned int position) {
uint8_t sectors[(128 << sector_size_)*sectors_per_track_];
if(head > 1) return nullptr;
long file_offset = get_file_offset_for_position(head, position);
{
std::lock_guard<std::mutex> lock_guard(file_access_mutex_);
fseek(file_, file_offset, SEEK_SET);
fread(sectors, 1, sizeof(sectors), file_);
}
return track_for_sectors(sectors, (uint8_t)position, (uint8_t)head, 0, sector_size_, is_double_density_);
}
void MFMSectorDump::set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track) {
uint8_t parsed_track[(128 << sector_size_)*sectors_per_track_];
// Assumption here: sector IDs will run from 0.
decode_sectors(*track, parsed_track, 0, (uint8_t)(sectors_per_track_-1), sector_size_, is_double_density_);
long file_offset = get_file_offset_for_position(head, position);
std::lock_guard<std::mutex> lock_guard(file_access_mutex_);
ensure_file_is_at_least_length(file_offset);
fseek(file_, file_offset, SEEK_SET);
fwrite(parsed_track, 1, sizeof(parsed_track), file_);
fflush(file_);
}

View File

@ -0,0 +1,42 @@
//
// MFMSectorDump.hpp
// Clock Signal
//
// Created by Thomas Harte on 30/09/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef SectorDump_hpp
#define SectorDump_hpp
#include "../DiskImage.hpp"
#include "../../../FileHolder.hpp"
namespace Storage {
namespace Disk {
/*!
Provies the base for writeable [M]FM disk images that just contain contiguous sector content dumps.
*/
class MFMSectorDump: public DiskImage, public Storage::FileHolder {
public:
MFMSectorDump(const char *file_name);
void set_geometry(int sectors_per_track, uint8_t sector_size, bool is_double_density);
bool get_is_read_only();
void set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track);
std::shared_ptr<Track> get_track_at_position(unsigned int head, unsigned int position);
private:
std::mutex file_access_mutex_;
virtual long get_file_offset_for_position(unsigned int head, unsigned int position) = 0;
int sectors_per_track_ = 0;
uint8_t sector_size_ = 0;
bool is_double_density_ = true;
};
}
}
#endif /* SectorDump_hpp */

View File

@ -9,7 +9,10 @@
#include "OricMFMDSK.hpp"
#include "../../Track/PCMTrack.hpp"
#include "../../Encodings/MFM.hpp"
#include "../../Encodings/MFM/Constants.hpp"
#include "../../Encodings/MFM/Shifter.hpp"
#include "../../Encodings/MFM/Encoder.hpp"
#include "../../Track/TrackSerialiser.hpp"
using namespace Storage::Disk;
@ -116,12 +119,48 @@ std::shared_ptr<Track> OricMFMDSK::get_track_at_position(unsigned int head, unsi
}
void OricMFMDSK::set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track) {
Storage::Encodings::MFM::Parser parser(true, track);
std::vector<uint8_t> parsed_track = parser.get_track(0);
PCMSegment segment = Storage::Disk::track_serialisation(*track, Storage::Encodings::MFM::MFMBitLength);
Storage::Encodings::MFM::Shifter shifter;
shifter.set_is_double_density(true);
shifter.set_should_obey_syncs(true);
std::vector<uint8_t> parsed_track;
int size = 0;
int offset = 0;
bool capture_size = false;
for(unsigned int bit = 0; bit < segment.number_of_bits; ++bit) {
shifter.add_input_bit(segment.bit(bit));
if(shifter.get_token() == Storage::Encodings::MFM::Shifter::Token::None) continue;
parsed_track.push_back(shifter.get_byte());
if(offset) {
offset--;
if(!offset) {
shifter.set_should_obey_syncs(true);
}
if(capture_size && offset == 2) {
size = parsed_track.back();
capture_size = false;
}
}
if( shifter.get_token() == Storage::Encodings::MFM::Shifter::Token::Data ||
shifter.get_token() == Storage::Encodings::MFM::Shifter::Token::DeletedData) {
offset = 128 << size;
shifter.set_should_obey_syncs(false);
}
if(shifter.get_token() == Storage::Encodings::MFM::Shifter::Token::ID) {
offset = 6;
shifter.set_should_obey_syncs(false);
capture_size = true;
}
}
long file_offset = get_file_offset_for_position(head, position);
std::lock_guard<std::mutex> lock_guard(file_access_mutex_);
fseek(file_, file_offset, SEEK_SET);
std::lock_guard<std::mutex> lock_guard(file_access_mutex_);
fseek(file_, file_offset, SEEK_SET);
size_t track_size = std::min((size_t)6400, parsed_track.size());
fwrite(parsed_track.data(), 1, track_size, file_);
}

View File

@ -8,12 +8,16 @@
#include "SSD.hpp"
#include "../../Encodings/MFM.hpp"
#include "Utility/ImplicitSectors.hpp"
namespace {
static const unsigned int sectors_per_track = 10;
static const unsigned int sector_size = 1;
}
using namespace Storage::Disk;
SSD::SSD(const char *file_name) :
Storage::FileHolder(file_name) {
SSD::SSD(const char *file_name) : MFMSectorDump(file_name) {
// very loose validation: the file needs to be a multiple of 256 bytes
// and not ungainly large
@ -26,6 +30,8 @@ SSD::SSD(const char *file_name) :
track_count_ = (unsigned int)(file_stats_.st_size / (256 * 10));
if(track_count_ < 40) track_count_ = 40;
else if(track_count_ < 80) track_count_ = 80;
set_geometry(sectors_per_track, sector_size, false);
}
unsigned int SSD::get_head_position_count() {
@ -36,66 +42,6 @@ unsigned int SSD::get_head_count() {
return head_count_;
}
bool SSD::get_is_read_only() {
return is_read_only_;
}
long SSD::get_file_offset_for_position(unsigned int head, unsigned int position) {
return (position * head_count_ + head) * 256 * 10;
}
std::shared_ptr<Track> SSD::get_track_at_position(unsigned int head, unsigned int position) {
std::shared_ptr<Track> track;
if(head >= head_count_) return track;
std::vector<Storage::Encodings::MFM::Sector> sectors;
{
std::lock_guard<std::mutex> lock_guard(file_access_mutex_);
fseek(file_, get_file_offset_for_position(head, position), SEEK_SET);
for(int sector = 0; sector < 10; sector++) {
Storage::Encodings::MFM::Sector new_sector;
new_sector.track = (uint8_t)position;
new_sector.side = 0;
new_sector.sector = (uint8_t)sector;
new_sector.size = 1;
new_sector.data.resize(256);
fread(new_sector.data.data(), 1, 256, file_);
// zero out if this wasn't present in the disk image; it's still appropriate to put a sector
// on disk because one will have been placed during formatting, but there's no reason to leak
// information from outside the emulated machine's world
if(feof(file_)) memset(new_sector.data.data(), 0, 256);
sectors.push_back(std::move(new_sector));
}
}
if(sectors.size()) return Storage::Encodings::MFM::GetFMTrackWithSectors(sectors);
return track;
}
void SSD::set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track) {
std::vector<uint8_t> data;
Storage::Encodings::MFM::Parser parser(false, track);
for(unsigned int c = 0; c < 10; c++) {
std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector(0, (uint8_t)position, (uint8_t)c);
if(sector) {
data.insert(data.end(), sector->data.begin(), sector->data.end());
} else {
// TODO: what's correct here? Warn the user that whatever has been written to the disk,
// it can no longer be stored as an SSD? If so, warn them by what route?
data.resize(data.size() + 256);
}
}
long file_offset = get_file_offset_for_position(head, position);
std::lock_guard<std::mutex> lock_guard(file_access_mutex_);
ensure_file_is_at_least_length(file_offset);
fseek(file_, file_offset, SEEK_SET);
fwrite(data.data(), 1, data.size(), file_);
}

View File

@ -9,8 +9,7 @@
#ifndef SSD_hpp
#define SSD_hpp
#include "../DiskImage.hpp"
#include "../../../FileHolder.hpp"
#include "MFMSectorDump.hpp"
namespace Storage {
namespace Disk {
@ -18,7 +17,7 @@ namespace Disk {
/*!
Provies a @c Disk containing a DSD or SSD disk image a decoded sector dump of an Acorn DFS disk.
*/
class SSD: public DiskImage, public Storage::FileHolder {
class SSD: public MFMSectorDump {
public:
/*!
Construct an @c SSD containing content from the file with name @c file_name.
@ -32,15 +31,10 @@ class SSD: public DiskImage, public Storage::FileHolder {
ErrorNotSSD,
};
// implemented to satisfy @c Disk
unsigned int get_head_position_count();
unsigned int get_head_count();
bool get_is_read_only();
void set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track);
std::shared_ptr<Track> get_track_at_position(unsigned int head, unsigned int position);
private:
std::mutex file_access_mutex_;
long get_file_offset_for_position(unsigned int head, unsigned int position);
unsigned int head_count_;

View File

@ -0,0 +1,58 @@
//
// ImplicitSectors.cpp
// Clock Signal
//
// Created by Thomas Harte on 29/09/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "ImplicitSectors.hpp"
#include "../../../Encodings/MFM/Sector.hpp"
#include "../../../Encodings/MFM/Encoder.hpp"
#include "../../../Encodings/MFM/Constants.hpp"
#include "../../../Track/TrackSerialiser.hpp"
#include "../../../Encodings/MFM/SegmentParser.hpp"
using namespace Storage::Disk;
std::shared_ptr<Track> Storage::Disk::track_for_sectors(uint8_t *const source, uint8_t track, uint8_t side, uint8_t first_sector, uint8_t size, bool is_double_density) {
std::vector<Storage::Encodings::MFM::Sector> sectors;
off_t byte_size = (off_t)(128 << size);
off_t source_pointer = 0;
for(int sector = 0; sector < 10; sector++) {
sectors.emplace_back();
Storage::Encodings::MFM::Sector &new_sector = sectors.back();
new_sector.address.track = track;
new_sector.address.side = size;
new_sector.address.sector = first_sector;
first_sector++;
new_sector.size = size;
new_sector.data.insert(new_sector.data.begin(), source + source_pointer, source + source_pointer + byte_size);
source_pointer += byte_size;
}
if(sectors.size()) {
return is_double_density ? Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors) : Storage::Encodings::MFM::GetFMTrackWithSectors(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) {
std::map<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);
size_t byte_size = (size_t)(128 << sector_size);
for(auto &pair : sectors) {
if(pair.second.address.sector > last_sector) continue;
if(pair.second.address.sector < first_sector) continue;
if(pair.second.size != sector_size) continue;
memcpy(&destination[pair.second.address.sector * byte_size], pair.second.data.data(), std::min(pair.second.data.size(), byte_size));
}
}

View File

@ -0,0 +1,25 @@
//
// ImplicitSectors.hpp
// Clock Signal
//
// Created by Thomas Harte on 29/09/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef ImplicitSectors_hpp
#define ImplicitSectors_hpp
#include "../../../Track/Track.hpp"
#include <memory>
#include <vector>
namespace Storage {
namespace Disk {
std::shared_ptr<Track> track_for_sectors(uint8_t *const source, uint8_t track, uint8_t side, uint8_t first_sector, uint8_t size, bool is_double_density);
void decode_sectors(Track &track, uint8_t *const destination, uint8_t first_sector, uint8_t last_sector, uint8_t sector_size, bool is_double_density);
}
}
#endif /* ImplicitSectors_hpp */

View File

@ -1,534 +0,0 @@
//
// MFM.cpp
// Clock Signal
//
// Created by Thomas Harte on 18/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "MFM.hpp"
#include "../Track/PCMTrack.hpp"
#include "../SingleTrackDisk/SingleTrackDisk.hpp"
#include "../../../NumberTheory/CRC.hpp"
#include <set>
using namespace Storage::Encodings::MFM;
class MFMEncoder: public Encoder {
public:
MFMEncoder(std::vector<uint8_t> &target) : Encoder(target) {}
void add_byte(uint8_t input) {
crc_generator_.add(input);
uint16_t spread_value =
(uint16_t)(
((input & 0x01) << 0) |
((input & 0x02) << 1) |
((input & 0x04) << 2) |
((input & 0x08) << 3) |
((input & 0x10) << 4) |
((input & 0x20) << 5) |
((input & 0x40) << 6) |
((input & 0x80) << 7)
);
uint16_t or_bits = (uint16_t)((spread_value << 1) | (spread_value >> 1) | (last_output_ << 15));
uint16_t output = spread_value | ((~or_bits) & 0xaaaa);
output_short(output);
}
void add_index_address_mark() {
for(int c = 0; c < 3; c++) output_short(MFMIndexSync);
add_byte(IndexAddressByte);
}
void add_ID_address_mark() {
output_sync();
add_byte(IDAddressByte);
}
void add_data_address_mark() {
output_sync();
add_byte(DataAddressByte);
}
void add_deleted_data_address_mark() {
output_sync();
add_byte(DeletedDataAddressByte);
}
private:
uint16_t last_output_;
void output_short(uint16_t value) {
last_output_ = value;
Encoder::output_short(value);
}
void output_sync() {
for(int c = 0; c < 3; c++) output_short(MFMSync);
crc_generator_.set_value(MFMPostSyncCRCValue);
}
};
class FMEncoder: public Encoder {
// encodes each 16-bit part as clock, data, clock, data [...]
public:
FMEncoder(std::vector<uint8_t> &target) : Encoder(target) {}
void add_byte(uint8_t input) {
crc_generator_.add(input);
output_short(
(uint16_t)(
((input & 0x01) << 0) |
((input & 0x02) << 1) |
((input & 0x04) << 2) |
((input & 0x08) << 3) |
((input & 0x10) << 4) |
((input & 0x20) << 5) |
((input & 0x40) << 6) |
((input & 0x80) << 7) |
0xaaaa
));
}
void add_index_address_mark() {
crc_generator_.reset();
crc_generator_.add(IndexAddressByte);
output_short(FMIndexAddressMark);
}
void add_ID_address_mark() {
crc_generator_.reset();
crc_generator_.add(IDAddressByte);
output_short(FMIDAddressMark);
}
void add_data_address_mark() {
crc_generator_.reset();
crc_generator_.add(DataAddressByte);
output_short(FMDataAddressMark);
}
void add_deleted_data_address_mark() {
crc_generator_.reset();
crc_generator_.add(DeletedDataAddressByte);
output_short(FMDeletedDataAddressMark);
}
};
template<class T> std::shared_ptr<Storage::Disk::Track>
GetTrackWithSectors(
const std::vector<Sector> &sectors,
size_t post_index_address_mark_bytes, uint8_t post_index_address_mark_value,
size_t pre_address_mark_bytes,
size_t post_address_mark_bytes, uint8_t post_address_mark_value,
size_t pre_data_mark_bytes,
size_t post_data_bytes, uint8_t post_data_value,
size_t expected_track_bytes) {
Storage::Disk::PCMSegment segment;
segment.data.reserve(expected_track_bytes);
T shifter(segment.data);
// output the index mark
shifter.add_index_address_mark();
// add the post-index mark
for(size_t c = 0; c < post_index_address_mark_bytes; c++) shifter.add_byte(post_index_address_mark_value);
// add sectors
for(const Sector &sector : sectors) {
// gap
for(size_t c = 0; c < pre_address_mark_bytes; c++) shifter.add_byte(0x00);
// sector header
shifter.add_ID_address_mark();
shifter.add_byte(sector.track);
shifter.add_byte(sector.side);
shifter.add_byte(sector.sector);
shifter.add_byte(sector.size);
shifter.add_crc(sector.has_header_crc_error);
// gap
for(size_t c = 0; c < post_address_mark_bytes; c++) shifter.add_byte(post_address_mark_value);
for(size_t c = 0; c < pre_data_mark_bytes; c++) shifter.add_byte(0x00);
// data, if attached
if(!sector.data.empty()) {
if(sector.is_deleted)
shifter.add_deleted_data_address_mark();
else
shifter.add_data_address_mark();
size_t c = 0;
size_t declared_length = (size_t)(128 << sector.size);
for(c = 0; c < sector.data.size() && c < declared_length; c++) {
shifter.add_byte(sector.data[c]);
}
for(; c < declared_length; c++) {
shifter.add_byte(0x00);
}
shifter.add_crc(sector.has_data_crc_error);
}
// gap
for(size_t c = 0; c < post_data_bytes; c++) shifter.add_byte(post_data_value);
}
while(segment.data.size() < expected_track_bytes) shifter.add_byte(0x00);
// Allow the amount of data written to be up to 10% more than the expected size. Which is generous.
size_t max_size = expected_track_bytes + (expected_track_bytes / 10);
if(segment.data.size() > max_size) segment.data.resize(max_size);
segment.number_of_bits = (unsigned int)(segment.data.size() * 8);
return std::shared_ptr<Storage::Disk::Track>(new Storage::Disk::PCMTrack(std::move(segment)));
}
Encoder::Encoder(std::vector<uint8_t> &target) :
crc_generator_(0x1021, 0xffff),
target_(target) {}
void Encoder::output_short(uint16_t value) {
target_.push_back(value >> 8);
target_.push_back(value & 0xff);
}
void Encoder::add_crc(bool incorrectly) {
uint16_t crc_value = crc_generator_.get_value();
add_byte(crc_value >> 8);
add_byte((crc_value & 0xff) ^ (incorrectly ? 1 : 0));
}
const size_t Storage::Encodings::MFM::DefaultSectorGapLength = (size_t)~0;
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector<Sector> &sectors, size_t sector_gap_length, uint8_t sector_gap_filler_byte) {
return GetTrackWithSectors<FMEncoder>(
sectors,
26, 0xff,
6,
11, 0xff,
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, size_t sector_gap_length, uint8_t sector_gap_filler_byte) {
return GetTrackWithSectors<MFMEncoder>(
sectors,
50, 0x4e,
12,
22, 0x4e,
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<uint8_t> &target) {
return std::unique_ptr<Encoder>(new MFMEncoder(target));
}
std::unique_ptr<Encoder> Storage::Encodings::MFM::GetFMEncoder(std::vector<uint8_t> &target) {
return std::unique_ptr<Encoder>(new FMEncoder(target));
}
#pragma mark - Parser
Parser::Parser(bool is_mfm) :
Storage::Disk::Controller(4000000),
crc_generator_(0x1021, 0xffff),
shift_register_(0), is_mfm_(is_mfm),
track_(0), head_(0) {
Storage::Time bit_length;
bit_length.length = 1;
bit_length.clock_rate = is_mfm ? 500000 : 250000; // i.e. 250 kbps (including clocks)
set_expected_bit_length(bit_length);
drive_.reset(new Storage::Disk::Drive(4000000, 300, 2));
set_drive(drive_);
drive_->set_motor_on(true);
}
Parser::Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Disk> &disk) :
Parser(is_mfm) {
drive_->set_disk(disk);
}
Parser::Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Track> &track) :
Parser(is_mfm) {
drive_->set_disk(std::make_shared<Disk::DiskImageHolder<Disk::SingleTrackDiskImage>>(track));
}
void Parser::seek_to_track(uint8_t track) {
int difference = (int)track - (int)track_;
track_ = track;
if(difference) {
int direction = difference < 0 ? -1 : 1;
difference *= direction;
for(int c = 0; c < difference; c++) drive_->step(direction);
}
}
std::shared_ptr<Sector> Parser::get_sector(uint8_t head, uint8_t track, uint8_t sector) {
// Switch head and track if necessary.
if(head_ != head) {
drive_->set_head(head);
}
seek_to_track(track);
int track_index = get_index(head, track, 0);
// Populate the sector cache if it's not already populated by asking for sectors unless and until
// one is returned that has already been seen.
if(decoded_tracks_.find(track_index) == decoded_tracks_.end()) {
std::shared_ptr<Sector> first_sector = get_next_sector();
std::set<uint8_t> visited_sectors;
if(first_sector) {
while(1) {
std::shared_ptr<Sector> next_sector = get_next_sector();
if(next_sector) {
if(visited_sectors.find(next_sector->sector) != visited_sectors.end()) {
break;
}
visited_sectors.insert(next_sector->sector);
}
}
}
decoded_tracks_.insert(track_index);
}
// Check cache for sector.
int index = get_index(head, track, sector);
auto cached_sector = sectors_by_index_.find(index);
if(cached_sector != sectors_by_index_.end()) {
return cached_sector->second;
}
// If it wasn't found, it doesn't exist.
return nullptr;
}
std::vector<uint8_t> Parser::get_track(uint8_t track) {
seek_to_track(track);
return get_track();
}
void Parser::process_input_bit(int value) {
shift_register_ = ((shift_register_ << 1) | (unsigned int)value) & 0xffff;
bit_count_++;
}
void Parser::process_index_hole() {
index_count_++;
}
uint8_t Parser::get_byte_for_shift_value(uint16_t value) {
return (uint8_t)(
((value&0x0001) >> 0) |
((value&0x0004) >> 1) |
((value&0x0010) >> 2) |
((value&0x0040) >> 3) |
((value&0x0100) >> 4) |
((value&0x0400) >> 5) |
((value&0x1000) >> 6) |
((value&0x4000) >> 7));
}
uint8_t Parser::get_next_byte() {
bit_count_ = 0;
// Archetypal MFM is 500,000 bps given that the drive has an RPM of 300. Clock rate was
// specified at 4,000,000. So that's an idealised 8 cycles per bit, Jump ahead 14
// times that...
run_for(Cycles(14 * 8));
// ... and proceed at half-idealised-bit intervals to get the next bit. Then proceed very gingerly indeed.
while(bit_count_ < 15) run_for(Cycles(4));
while(bit_count_ < 16) run_for(Cycles(2));
uint8_t byte = get_byte_for_shift_value((uint16_t)shift_register_);
crc_generator_.add(byte);
return byte;
}
std::vector<uint8_t> Parser::get_track() {
std::vector<uint8_t> result;
int distance_until_permissible_sync = 0;
uint8_t last_id[6] = {0, 0, 0, 0, 0, 0};
int last_id_pointer = 0;
bool next_is_type = false;
// align to the next index hole
index_count_ = 0;
while(!index_count_) run_for(Cycles(1));
// capture every other bit until the next index hole
index_count_ = 0;
while(1) {
// wait until either another bit or the index hole arrives
bit_count_ = 0;
bool found_sync = false;
while(!index_count_ && !found_sync && bit_count_ < 16) {
int previous_bit_count = bit_count_;
run_for(Cycles(1));
if(!distance_until_permissible_sync && bit_count_ != previous_bit_count) {
uint16_t low_shift_register = (shift_register_&0xffff);
if(is_mfm_) {
found_sync = (low_shift_register == MFMIndexSync) || (low_shift_register == MFMSync);
} else {
found_sync =
(low_shift_register == FMIndexAddressMark) ||
(low_shift_register == FMIDAddressMark) ||
(low_shift_register == FMDataAddressMark) ||
(low_shift_register == FMDeletedDataAddressMark);
}
}
}
// if that was the index hole then finish
if(index_count_) {
if(bit_count_) result.push_back(get_byte_for_shift_value((uint16_t)(shift_register_ << (16 - bit_count_))));
break;
}
// store whatever the current byte is
uint8_t byte_value = get_byte_for_shift_value((uint16_t)shift_register_);
result.push_back(byte_value);
if(last_id_pointer < 6) last_id[last_id_pointer++] = byte_value;
// if no syncs are permissible here, decrement the waiting period and perform no further contemplation
bool found_id = false, found_data = false;
if(distance_until_permissible_sync) {
distance_until_permissible_sync--;
} else {
if(found_sync) {
if(is_mfm_) {
next_is_type = true;
} else {
switch(shift_register_&0xffff) {
case FMIDAddressMark: found_id = true; break;
case FMDataAddressMark:
case FMDeletedDataAddressMark: found_data = true; break;
}
}
} else if(next_is_type) {
switch(byte_value) {
case IDAddressByte: found_id = true; break;
case DataAddressByte:
case DeletedDataAddressByte: found_data = true; break;
}
}
}
if(found_id) {
distance_until_permissible_sync = 6;
last_id_pointer = 0;
}
if(found_data) {
distance_until_permissible_sync = 128 << last_id[3];
}
}
return result;
}
std::shared_ptr<Sector> Parser::get_next_sector() {
std::shared_ptr<Sector> sector(new Sector);
index_count_ = 0;
while(index_count_ < 2) {
// look for an ID address mark
bool id_found = false;
while(!id_found) {
run_for(Cycles(1));
if(is_mfm_) {
while(shift_register_ == MFMSync) {
uint8_t mark = get_next_byte();
if(mark == IDAddressByte) {
crc_generator_.set_value(MFMPostSyncCRCValue);
id_found = true;
break;
}
}
} else {
if(shift_register_ == FMIDAddressMark) {
crc_generator_.reset();
id_found = true;
}
}
if(index_count_ >= 2) return nullptr;
}
crc_generator_.add(IDAddressByte);
sector->track = get_next_byte();
sector->side = get_next_byte();
sector->sector = get_next_byte();
sector->size = get_next_byte();
uint16_t header_crc = crc_generator_.get_value();
if((header_crc >> 8) != get_next_byte()) sector->has_header_crc_error = true;
if((header_crc & 0xff) != get_next_byte()) sector->has_header_crc_error = true;
// look for data mark
bool data_found = false;
while(!data_found) {
run_for(Cycles(1));
if(is_mfm_) {
while(shift_register_ == MFMSync) {
uint8_t mark = get_next_byte();
if(mark == DataAddressByte) {
crc_generator_.set_value(MFMPostSyncCRCValue);
data_found = true;
break;
}
if(mark == IDAddressByte) return nullptr;
}
} else {
if(shift_register_ == FMDataAddressMark) {
crc_generator_.reset();
data_found = true;
}
if(shift_register_ == FMIDAddressMark) return nullptr;
}
if(index_count_ >= 2) return nullptr;
}
crc_generator_.add(DataAddressByte);
size_t data_size = (size_t)(128 << sector->size);
sector->data.reserve(data_size);
for(size_t c = 0; c < data_size; c++) {
sector->data.push_back(get_next_byte());
}
uint16_t data_crc = crc_generator_.get_value();
if((data_crc >> 8) != get_next_byte()) sector->has_data_crc_error = true;
if((data_crc & 0xff) != get_next_byte()) sector->has_data_crc_error = true;
// Put this sector into the cache.
int index = get_index(head_, track_, sector->sector);
sectors_by_index_[index] = sector;
return sector;
}
return nullptr;
}
std::shared_ptr<Sector> Parser::get_sector(uint8_t sector) {
std::shared_ptr<Sector> first_sector;
index_count_ = 0;
while(!first_sector && index_count_ < 2) first_sector = get_next_sector();
if(!first_sector) return nullptr;
if(first_sector->sector == sector) return first_sector;
while(1) {
std::shared_ptr<Sector> next_sector = get_next_sector();
if(!next_sector) continue;
if(next_sector->sector == first_sector->sector) return nullptr;
if(next_sector->sector == sector) return next_sector;
}
}
int Parser::get_index(uint8_t head, uint8_t track, uint8_t sector) {
return head | (track << 8) | (sector << 16);
}

View File

@ -1,152 +0,0 @@
//
// MFM.hpp
// Clock Signal
//
// Created by Thomas Harte on 18/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef Storage_Disk_Encodings_MFM_hpp
#define Storage_Disk_Encodings_MFM_hpp
#include <cstdint>
#include <vector>
#include "../Disk.hpp"
#include "../Controller/DiskController.hpp"
#include "../../../NumberTheory/CRC.hpp"
namespace Storage {
namespace Encodings {
namespace MFM {
const uint8_t IndexAddressByte = 0xfc;
const uint8_t IDAddressByte = 0xfe;
const uint8_t DataAddressByte = 0xfb;
const uint8_t DeletedDataAddressByte = 0xf8;
const uint16_t FMIndexAddressMark = 0xf77a; // data 0xfc, with clock 0xd7 => 1111 1100 with clock 1101 0111 => 1111 0111 0111 1010
const uint16_t FMIDAddressMark = 0xf57e; // data 0xfe, with clock 0xc7 => 1111 1110 with clock 1100 0111 => 1111 0101 0111 1110
const uint16_t FMDataAddressMark = 0xf56f; // data 0xfb, with clock 0xc7 => 1111 1011 with clock 1100 0111 => 1111 0101 0110 1111
const uint16_t FMDeletedDataAddressMark = 0xf56a; // data 0xf8, with clock 0xc7 => 1111 1000 with clock 1100 0111 => 1111 0101 0110 1010
const uint16_t MFMIndexSync = 0x5224; // data 0xc2, with a missing clock at 0x0080 => 0101 0010 1010 0100 without 1000 0000
const uint16_t MFMSync = 0x4489; // data 0xa1, with a missing clock at 0x0020 => 0100 0100 1010 1001 without 0010 0000
const uint16_t MFMPostSyncCRCValue = 0xcdb4; // the value the CRC generator should have after encountering three 0xa1s
const uint8_t MFMIndexSyncByteValue = 0xc2;
const uint8_t MFMSyncByteValue = 0xa1;
/*!
Represents a single [M]FM sector, identified by its track, side and sector records, a blob of data
and a few extra flags of metadata.
*/
struct Sector {
uint8_t track, side, sector, size;
std::vector<uint8_t> data;
bool has_data_crc_error;
bool has_header_crc_error;
bool is_deleted;
Sector() : track(0), side(0), sector(0), size(0), has_data_crc_error(false), has_header_crc_error(false), is_deleted(false) {}
};
extern const size_t DefaultSectorGapLength;
/*!
Converts a vector of sectors into a properly-encoded 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, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e);
/*!
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, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e);
class Encoder {
public:
Encoder(std::vector<uint8_t> &target);
virtual void add_byte(uint8_t input) = 0;
virtual void add_index_address_mark() = 0;
virtual void add_ID_address_mark() = 0;
virtual void add_data_address_mark() = 0;
virtual void add_deleted_data_address_mark() = 0;
virtual void output_short(uint16_t value);
/// Outputs the CRC for all data since the last address mask; if @c incorrectly is @c true then outputs an incorrect CRC.
void add_crc(bool incorrectly);
protected:
NumberTheory::CRC16 crc_generator_;
private:
std::vector<uint8_t> &target_;
};
std::unique_ptr<Encoder> GetMFMEncoder(std::vector<uint8_t> &target);
std::unique_ptr<Encoder> GetFMEncoder(std::vector<uint8_t> &target);
class Parser: public Storage::Disk::Controller {
public:
Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Disk> &disk);
Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Track> &track);
/*!
Attempts to read the sector located at @c track and @c sector.
@returns a sector if one was found; @c nullptr otherwise.
*/
std::shared_ptr<Storage::Encodings::MFM::Sector> get_sector(uint8_t head, uint8_t track, uint8_t sector);
/*!
Attempts to read the track at @c track, starting from the index hole.
Decodes data bits only; clocks are omitted. Synchronisation values begin a new
byte. If a synchronisation value begins partway through a byte then
synchronisation-contributing bits will appear both in the preceding byte and
in the next.
@returns a vector of data found.
*/
std::vector<uint8_t> get_track(uint8_t track);
private:
Parser(bool is_mfm);
std::shared_ptr<Storage::Disk::Drive> drive_;
unsigned int shift_register_;
int index_count_;
uint8_t track_, head_;
int bit_count_;
NumberTheory::CRC16 crc_generator_;
bool is_mfm_;
void seek_to_track(uint8_t track);
void process_input_bit(int value);
void process_index_hole();
uint8_t get_next_byte();
uint8_t get_byte_for_shift_value(uint16_t value);
std::shared_ptr<Storage::Encodings::MFM::Sector> get_next_sector();
std::shared_ptr<Storage::Encodings::MFM::Sector> get_sector(uint8_t sector);
std::vector<uint8_t> get_track();
std::map<int, std::shared_ptr<Storage::Encodings::MFM::Sector>> sectors_by_index_;
std::set<int> decoded_tracks_;
int get_index(uint8_t head, uint8_t track, uint8_t sector);
};
}
}
}
#endif /* MFM_hpp */

View File

@ -0,0 +1,42 @@
//
// Constants.hpp
// Clock Signal
//
// Created by Thomas Harte on 24/09/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Constants_h
#define Constants_h
#include "../../../Storage.hpp"
namespace Storage {
namespace Encodings {
namespace MFM {
const uint8_t IndexAddressByte = 0xfc;
const uint8_t IDAddressByte = 0xfe;
const uint8_t DataAddressByte = 0xfb;
const uint8_t DeletedDataAddressByte = 0xf8;
const uint16_t FMIndexAddressMark = 0xf77a; // data 0xfc, with clock 0xd7 => 1111 1100 with clock 1101 0111 => 1111 0111 0111 1010
const uint16_t FMIDAddressMark = 0xf57e; // data 0xfe, with clock 0xc7 => 1111 1110 with clock 1100 0111 => 1111 0101 0111 1110
const uint16_t FMDataAddressMark = 0xf56f; // data 0xfb, with clock 0xc7 => 1111 1011 with clock 1100 0111 => 1111 0101 0110 1111
const uint16_t FMDeletedDataAddressMark = 0xf56a; // data 0xf8, with clock 0xc7 => 1111 1000 with clock 1100 0111 => 1111 0101 0110 1010
const uint16_t MFMIndexSync = 0x5224; // data 0xc2, with a missing clock at 0x0080 => 0101 0010 1010 0100 without 1000 0000
const uint16_t MFMSync = 0x4489; // data 0xa1, with a missing clock at 0x0020 => 0100 0100 1010 1001 without 0010 0000
const uint16_t MFMPostSyncCRCValue = 0xcdb4; // the value the CRC generator should have after encountering three 0xa1s
const uint8_t MFMIndexSyncByteValue = 0xc2;
const uint8_t MFMSyncByteValue = 0xa1;
const Time MFMBitLength = Time(1, 100000);
const Time FMBitLength = Time(1, 50000);
}
}
}
#endif /* Constants_h */

View File

@ -0,0 +1,233 @@
//
// MFM.cpp
// Clock Signal
//
// Created by Thomas Harte on 18/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "Encoder.hpp"
#include "Constants.hpp"
#include "../../Track/PCMTrack.hpp"
#include "../../../../NumberTheory/CRC.hpp"
#include <set>
using namespace Storage::Encodings::MFM;
class MFMEncoder: public Encoder {
public:
MFMEncoder(std::vector<uint8_t> &target) : Encoder(target) {}
void add_byte(uint8_t input) {
crc_generator_.add(input);
uint16_t spread_value =
(uint16_t)(
((input & 0x01) << 0) |
((input & 0x02) << 1) |
((input & 0x04) << 2) |
((input & 0x08) << 3) |
((input & 0x10) << 4) |
((input & 0x20) << 5) |
((input & 0x40) << 6) |
((input & 0x80) << 7)
);
uint16_t or_bits = (uint16_t)((spread_value << 1) | (spread_value >> 1) | (last_output_ << 15));
uint16_t output = spread_value | ((~or_bits) & 0xaaaa);
output_short(output);
}
void add_index_address_mark() {
for(int c = 0; c < 3; c++) output_short(MFMIndexSync);
add_byte(IndexAddressByte);
}
void add_ID_address_mark() {
output_sync();
add_byte(IDAddressByte);
}
void add_data_address_mark() {
output_sync();
add_byte(DataAddressByte);
}
void add_deleted_data_address_mark() {
output_sync();
add_byte(DeletedDataAddressByte);
}
private:
uint16_t last_output_;
void output_short(uint16_t value) {
last_output_ = value;
Encoder::output_short(value);
}
void output_sync() {
for(int c = 0; c < 3; c++) output_short(MFMSync);
crc_generator_.set_value(MFMPostSyncCRCValue);
}
};
class FMEncoder: public Encoder {
// encodes each 16-bit part as clock, data, clock, data [...]
public:
FMEncoder(std::vector<uint8_t> &target) : Encoder(target) {}
void add_byte(uint8_t input) {
crc_generator_.add(input);
output_short(
(uint16_t)(
((input & 0x01) << 0) |
((input & 0x02) << 1) |
((input & 0x04) << 2) |
((input & 0x08) << 3) |
((input & 0x10) << 4) |
((input & 0x20) << 5) |
((input & 0x40) << 6) |
((input & 0x80) << 7) |
0xaaaa
));
}
void add_index_address_mark() {
crc_generator_.reset();
crc_generator_.add(IndexAddressByte);
output_short(FMIndexAddressMark);
}
void add_ID_address_mark() {
crc_generator_.reset();
crc_generator_.add(IDAddressByte);
output_short(FMIDAddressMark);
}
void add_data_address_mark() {
crc_generator_.reset();
crc_generator_.add(DataAddressByte);
output_short(FMDataAddressMark);
}
void add_deleted_data_address_mark() {
crc_generator_.reset();
crc_generator_.add(DeletedDataAddressByte);
output_short(FMDeletedDataAddressMark);
}
};
template<class T> std::shared_ptr<Storage::Disk::Track>
GetTrackWithSectors(
const std::vector<Sector> &sectors,
size_t post_index_address_mark_bytes, uint8_t post_index_address_mark_value,
size_t pre_address_mark_bytes,
size_t post_address_mark_bytes, uint8_t post_address_mark_value,
size_t pre_data_mark_bytes,
size_t post_data_bytes, uint8_t post_data_value,
size_t expected_track_bytes) {
Storage::Disk::PCMSegment segment;
segment.data.reserve(expected_track_bytes);
T shifter(segment.data);
// output the index mark
shifter.add_index_address_mark();
// add the post-index mark
for(size_t c = 0; c < post_index_address_mark_bytes; c++) shifter.add_byte(post_index_address_mark_value);
// add sectors
for(const Sector &sector : sectors) {
// gap
for(size_t c = 0; c < pre_address_mark_bytes; c++) shifter.add_byte(0x00);
// sector header
shifter.add_ID_address_mark();
shifter.add_byte(sector.address.track);
shifter.add_byte(sector.address.side);
shifter.add_byte(sector.address.sector);
shifter.add_byte(sector.size);
shifter.add_crc(sector.has_header_crc_error);
// gap
for(size_t c = 0; c < post_address_mark_bytes; c++) shifter.add_byte(post_address_mark_value);
for(size_t c = 0; c < pre_data_mark_bytes; c++) shifter.add_byte(0x00);
// data, if attached
if(!sector.data.empty()) {
if(sector.is_deleted)
shifter.add_deleted_data_address_mark();
else
shifter.add_data_address_mark();
size_t c = 0;
size_t declared_length = (size_t)(128 << sector.size);
for(c = 0; c < sector.data.size() && c < declared_length; c++) {
shifter.add_byte(sector.data[c]);
}
for(; c < declared_length; c++) {
shifter.add_byte(0x00);
}
shifter.add_crc(sector.has_data_crc_error);
}
// gap
for(size_t c = 0; c < post_data_bytes; c++) shifter.add_byte(post_data_value);
}
while(segment.data.size() < expected_track_bytes) shifter.add_byte(0x00);
// Allow the amount of data written to be up to 10% more than the expected size. Which is generous.
size_t max_size = expected_track_bytes + (expected_track_bytes / 10);
if(segment.data.size() > max_size) segment.data.resize(max_size);
segment.number_of_bits = (unsigned int)(segment.data.size() * 8);
return std::shared_ptr<Storage::Disk::Track>(new Storage::Disk::PCMTrack(std::move(segment)));
}
Encoder::Encoder(std::vector<uint8_t> &target) :
crc_generator_(0x1021, 0xffff),
target_(target) {}
void Encoder::output_short(uint16_t value) {
target_.push_back(value >> 8);
target_.push_back(value & 0xff);
}
void Encoder::add_crc(bool incorrectly) {
uint16_t crc_value = crc_generator_.get_value();
add_byte(crc_value >> 8);
add_byte((crc_value & 0xff) ^ (incorrectly ? 1 : 0));
}
const size_t Storage::Encodings::MFM::DefaultSectorGapLength = (size_t)~0;
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector<Sector> &sectors, size_t sector_gap_length, uint8_t sector_gap_filler_byte) {
return GetTrackWithSectors<FMEncoder>(
sectors,
26, 0xff,
6,
11, 0xff,
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, size_t sector_gap_length, uint8_t sector_gap_filler_byte) {
return GetTrackWithSectors<MFMEncoder>(
sectors,
50, 0x4e,
12,
22, 0x4e,
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<uint8_t> &target) {
return std::unique_ptr<Encoder>(new MFMEncoder(target));
}
std::unique_ptr<Encoder> Storage::Encodings::MFM::GetFMEncoder(std::vector<uint8_t> &target) {
return std::unique_ptr<Encoder>(new FMEncoder(target));
}

View File

@ -0,0 +1,68 @@
//
// MFM.hpp
// Clock Signal
//
// Created by Thomas Harte on 18/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef Storage_Disk_Encodings_MFM_hpp
#define Storage_Disk_Encodings_MFM_hpp
#include <cstdint>
#include <vector>
#include "Sector.hpp"
#include "../../Track/Track.hpp"
#include "../../../../NumberTheory/CRC.hpp"
namespace Storage {
namespace Encodings {
namespace MFM {
extern const size_t DefaultSectorGapLength;
/*!
Converts a vector of sectors into a properly-encoded 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, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e);
/*!
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, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e);
class Encoder {
public:
Encoder(std::vector<uint8_t> &target);
virtual void add_byte(uint8_t input) = 0;
virtual void add_index_address_mark() = 0;
virtual void add_ID_address_mark() = 0;
virtual void add_data_address_mark() = 0;
virtual void add_deleted_data_address_mark() = 0;
virtual void output_short(uint16_t value);
/// Outputs the CRC for all data since the last address mask; if @c incorrectly is @c true then outputs an incorrect CRC.
void add_crc(bool incorrectly);
protected:
NumberTheory::CRC16 crc_generator_;
private:
std::vector<uint8_t> &target_;
};
std::unique_ptr<Encoder> GetMFMEncoder(std::vector<uint8_t> &target);
std::unique_ptr<Encoder> GetFMEncoder(std::vector<uint8_t> &target);
}
}
}
#endif /* MFM_hpp */

View File

@ -0,0 +1,58 @@
//
// Parser.cpp
// Clock Signal
//
// Created by Thomas Harte on 24/09/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "Parser.hpp"
#include "Constants.hpp"
#include "../../Track/TrackSerialiser.hpp"
#include "SegmentParser.hpp"
using namespace Storage::Encodings::MFM;
Parser::Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Disk> &disk) :
is_mfm_(is_mfm), disk_(disk) {}
void Parser::install_sectors_from_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((unsigned int)address.head, (unsigned int)address.position);
if(!track) {
return;
}
std::map<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(auto &sector : sectors) {
sectors_by_id.insert(std::make_pair(sector.second.address.sector, std::move(sector.second)));
}
sectors_by_address_by_track_.insert(std::make_pair(address, std::move(sectors_by_id)));
}
Sector *Parser::get_sector(int head, int track, uint8_t sector) {
Disk::Track::Address address;
address.position = track;
address.head = head;
install_sectors_from_track(address);
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);
if(stored_sector == sectors->second.end()) {
return nullptr;
}
return &stored_sector->second;
}

View File

@ -0,0 +1,47 @@
//
// Parser.hpp
// Clock Signal
//
// Created by Thomas Harte on 24/09/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Parser_hpp
#define Parser_hpp
#include "Sector.hpp"
#include "../../Track/Track.hpp"
#include "../../Drive.hpp"
namespace Storage {
namespace Encodings {
namespace MFM {
/*!
Provides a mechanism for collecting sectors from a disk.
*/
class Parser {
public:
Parser(bool is_mfm, 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
with logical address @c sector.
@returns a sector if one was found; @c nullptr otherwise.
*/
Storage::Encodings::MFM::Sector *get_sector(int head, int track, uint8_t sector);
private:
std::shared_ptr<Storage::Disk::Disk> disk_;
bool is_mfm_ = true;
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_;
};
}
}
}
#endif /* Parser_hpp */

View File

@ -0,0 +1,58 @@
//
// Sector.hpp
// Clock Signal
//
// Created by Thomas Harte on 24/09/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Sector_h
#define Sector_h
#include <cstdint>
#include <vector>
namespace Storage {
namespace Encodings {
namespace MFM {
/*!
Represents a single [M]FM sector, identified by its track, side and sector records, a blob of data
and a few extra flags of metadata.
*/
struct Sector {
/*!
Describes the location of a sector, implementing < to allow for use as a set key.
*/
struct Address {
uint8_t track = 0, side = 0, sector = 0;
bool operator < (const Address &rhs) const {
return ((track << 24) | (side << 8) | sector) < ((rhs.track << 24) | (rhs.side << 8) | rhs.sector);
}
};
Address address;
uint8_t size = 0;
std::vector<uint8_t> data;
bool has_data_crc_error = false;
bool has_header_crc_error = false;
bool is_deleted = false;
Sector() noexcept {}
Sector(const Sector &&rhs) noexcept :
address(rhs.address),
size(rhs.size),
data(std::move(rhs.data)),
has_data_crc_error(rhs.has_data_crc_error),
has_header_crc_error(rhs.has_header_crc_error),
is_deleted(rhs.is_deleted ){}
};
}
}
}
#endif /* Sector_h */

View File

@ -0,0 +1,81 @@
//
// SegmentParser.cpp
// Clock Signal
//
// Created by Thomas Harte on 24/09/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "SegmentParser.hpp"
#include "Shifter.hpp"
using namespace Storage::Encodings::MFM;
std::map<size_t, Storage::Encodings::MFM::Sector> Storage::Encodings::MFM::sectors_from_segment(const Storage::Disk::PCMSegment &&segment, bool is_double_density) {
std::map<size_t, Sector> result;
Shifter shifter;
shifter.set_is_double_density(is_double_density);
shifter.set_should_obey_syncs(true);
std::unique_ptr<Storage::Encodings::MFM::Sector> new_sector;
bool is_reading = false;
size_t position = 0;
size_t size = 0;
size_t start_location = 0;
for(unsigned int bit = 0; bit < segment.number_of_bits; ++bit) {
shifter.add_input_bit(segment.bit(bit));
switch(shifter.get_token()) {
case Shifter::Token::None:
case Shifter::Token::Sync:
case Shifter::Token::Index:
break;
case Shifter::Token::ID:
new_sector.reset(new Storage::Encodings::MFM::Sector);
is_reading = true;
start_location = bit;
position = 0;
shifter.set_should_obey_syncs(false);
break;
case Shifter::Token::Data:
case Shifter::Token::DeletedData:
if(new_sector) {
is_reading = true;
shifter.set_should_obey_syncs(false);
new_sector->is_deleted = (shifter.get_token() == Shifter::Token::DeletedData);
}
break;
case Shifter::Token::Byte:
if(is_reading) {
switch(position) {
case 0: new_sector->address.track = shifter.get_byte(); ++position; break;
case 1: new_sector->address.side = shifter.get_byte(); ++position; break;
case 2: new_sector->address.sector = shifter.get_byte(); ++position; break;
case 3:
new_sector->size = shifter.get_byte();
size = (size_t)(128 << new_sector->size);
++position;
is_reading = false;
shifter.set_should_obey_syncs(true);
break;
default:
new_sector->data.push_back(shifter.get_byte());
++position;
if(position == size + 4) {
result.insert(std::make_pair(start_location, std::move(*new_sector)));
is_reading = false;
shifter.set_should_obey_syncs(true);
new_sector.reset();
}
break;
}
}
break;
}
}
return result;
}

View File

@ -0,0 +1,30 @@
//
// SegmentParser.hpp
// Clock Signal
//
// Created by Thomas Harte on 24/09/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef SegmentParser_hpp
#define SegmentParser_hpp
#include "Sector.hpp"
#include "../../Track/PCMSegment.hpp"
#include <map>
namespace Storage {
namespace Encodings {
namespace MFM {
/*!
Scans @c segment for all included sectors, returning a set that maps from location within
the segment (counted in bits from the beginning) to sector.
*/
std::map<size_t, Sector> sectors_from_segment(const Disk::PCMSegment &&segment, bool is_double_density);
}
}
}
#endif /* SegmentParser_hpp */

View File

@ -0,0 +1,120 @@
//
// Shifter.cpp
// Clock Signal
//
// Created by Thomas Harte on 24/09/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "Shifter.hpp"
#include "Constants.hpp"
using namespace Storage::Encodings::MFM;
Shifter::Shifter() : owned_crc_generator_(new NumberTheory::CRC16(0x1021, 0xffff)), crc_generator_(owned_crc_generator_.get()) {}
Shifter::Shifter(NumberTheory::CRC16 *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_should_obey_syncs(bool should_obey_syncs) {
should_obey_syncs_ = should_obey_syncs;
}
void Shifter::add_input_bit(int value) {
shift_register_ = (shift_register_ << 1) | value;
bits_since_token_++;
token_ = Token::None;
if(should_obey_syncs_) {
if(!is_double_density_) {
switch(shift_register_ & 0xffff) {
case Storage::Encodings::MFM::FMIndexAddressMark:
token_ = Token::Index;
crc_generator_->reset();
crc_generator_->add(Storage::Encodings::MFM::IndexAddressByte);
break;
case Storage::Encodings::MFM::FMIDAddressMark:
token_ = Token::ID;
crc_generator_->reset();
crc_generator_->add(Storage::Encodings::MFM::IDAddressByte);
break;
case Storage::Encodings::MFM::FMDataAddressMark:
token_ = Token::Data;
crc_generator_->reset();
crc_generator_->add(Storage::Encodings::MFM::DataAddressByte);
break;
case Storage::Encodings::MFM::FMDeletedDataAddressMark:
token_ = Token::DeletedData;
crc_generator_->reset();
crc_generator_->add(Storage::Encodings::MFM::DeletedDataAddressByte);
break;
default:
break;
}
} else {
switch(shift_register_ & 0xffff) {
case Storage::Encodings::MFM::MFMIndexSync:
bits_since_token_ = 0;
is_awaiting_marker_value_ = true;
token_ = Token::Sync;
break;
case Storage::Encodings::MFM::MFMSync:
bits_since_token_ = 0;
is_awaiting_marker_value_ = true;
crc_generator_->set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
token_ = Token::Sync;
break;
default:
break;
}
}
if(token_ != Token::None) {
bits_since_token_ = 0;
return;
}
}
if(bits_since_token_ == 16) {
token_ = Token::Byte;
bits_since_token_ = 0;
if(is_awaiting_marker_value_ && is_double_density_) {
is_awaiting_marker_value_ = false;
switch(get_byte()) {
case Storage::Encodings::MFM::IndexAddressByte:
token_ = Token::Index;
break;
case Storage::Encodings::MFM::IDAddressByte:
token_ = Token::ID;
break;
case Storage::Encodings::MFM::DataAddressByte:
token_ = Token::Data;
break;
case Storage::Encodings::MFM::DeletedDataAddressByte:
token_ = Token::DeletedData;
break;
default: break;
}
}
crc_generator_->add(get_byte());
}
}
uint8_t Shifter::get_byte() const {
return (uint8_t)(
((shift_register_ & 0x0001) >> 0) |
((shift_register_ & 0x0004) >> 1) |
((shift_register_ & 0x0010) >> 2) |
((shift_register_ & 0x0040) >> 3) |
((shift_register_ & 0x0100) >> 4) |
((shift_register_ & 0x0400) >> 5) |
((shift_register_ & 0x1000) >> 6) |
((shift_register_ & 0x4000) >> 7));
}

View File

@ -0,0 +1,59 @@
//
// Shifter.hpp
// Clock Signal
//
// Created by Thomas Harte on 24/09/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Shifter_hpp
#define Shifter_hpp
#include <cstdint>
#include <memory>
#include "../../../../NumberTheory/CRC.hpp"
namespace Storage {
namespace Encodings {
namespace MFM {
class Shifter {
public:
Shifter();
Shifter(NumberTheory::CRC16 *crc_generator);
void set_is_double_density(bool is_double_density);
void set_should_obey_syncs(bool should_obey_syncs);
void add_input_bit(int bit);
enum Token {
Index, ID, Data, DeletedData, Sync, Byte, None
};
uint8_t get_byte() const;
Token get_token() const {
return token_;
}
NumberTheory::CRC16 &get_crc_generator() {
return *crc_generator_;
}
private:
// Bit stream input state
int bits_since_token_ = 0;
int shift_register_ = 0;
bool is_awaiting_marker_value_ = false;
bool should_obey_syncs_;
Token token_;
// input configuration
bool is_double_density_ = false;
std::unique_ptr<NumberTheory::CRC16> owned_crc_generator_;
NumberTheory::CRC16 *crc_generator_;
};
}
}
}
#endif /* Shifter_hpp */

View File

@ -8,7 +8,7 @@
#include "CPM.hpp"
#include "../Encodings/MFM.hpp"
#include "../Encodings/MFM/Parser.hpp"
using namespace Storage::Disk::CPM;
@ -26,7 +26,7 @@ std::unique_ptr<Storage::Disk::CPM::Catalogue> Storage::Disk::CPM::GetCatalogue(
if(catalogue_allocation_bitmap & 0x8000) {
size_t size_read = 0;
do {
std::shared_ptr<Storage::Encodings::MFM::Sector> sector_contents = parser.get_sector(0, (uint8_t)track, (uint8_t)(parameters.first_sector + sector));
Storage::Encodings::MFM::Sector *sector_contents = parser.get_sector(0, (uint8_t)track, (uint8_t)(parameters.first_sector + sector));
if(!sector_contents) {
return nullptr;
}
@ -46,77 +46,109 @@ std::unique_ptr<Storage::Disk::CPM::Catalogue> Storage::Disk::CPM::GetCatalogue(
catalogue_allocation_bitmap <<= 1;
}
std::unique_ptr<Catalogue> result(new Catalogue);
bool has_long_allocation_units = (parameters.tracks * parameters.sectors_per_track * (int)sector_size / parameters.block_size) >= 256;
size_t bytes_per_catalogue_entry = (has_long_allocation_units ? 16 : 8) * (size_t)parameters.block_size;
struct CatalogueEntry {
uint8_t user_number;
std::string name;
std::string type;
bool read_only;
bool system;
size_t extent;
uint8_t number_of_records;
size_t catalogue_index;
// From the catalogue, create files.
std::map<std::vector<uint8_t>, size_t> indices_by_name;
File empty_file;
bool operator < (const CatalogueEntry &rhs) const {
return std::tie(user_number, name, type, extent) < std::tie(rhs.user_number, rhs.name, rhs.type, rhs.extent);
}
bool is_same_file(const CatalogueEntry &rhs) const {
return std::tie(user_number, name, type) == std::tie(rhs.user_number, rhs.name, rhs.type);
}
};
// From the catalogue, get catalogue entries.
std::vector<CatalogueEntry> catalogue_entries;
for(size_t c = 0; c < catalogue.size(); c += 32) {
// Skip this file if it's deleted; this is marked by it having 0xe5 as its user number
if(catalogue[c] == 0xe5) continue;
// Check whether this file has yet been seen; if not then add it to the list
std::vector<uint8_t> descriptor;
size_t index;
descriptor.insert(descriptor.begin(), &catalogue[c], &catalogue[c + 12]);
auto iterator = indices_by_name.find(descriptor);
if(iterator != indices_by_name.end()) {
index = iterator->second;
} else {
File new_file;
new_file.user_number = catalogue[c];
for(size_t s = 0; s < 8; s++) new_file.name.push_back((char)catalogue[c + s + 1]);
for(size_t s = 0; s < 3; s++) new_file.type.push_back((char)catalogue[c + s + 9] & 0x7f);
new_file.read_only = catalogue[c + 9] & 0x80;
new_file.system = catalogue[c + 10] & 0x80;
index = result->files.size();
result->files.push_back(new_file);
indices_by_name[descriptor] = index;
catalogue_entries.emplace_back();
CatalogueEntry &entry = catalogue_entries.back();
entry.user_number = catalogue[c];
entry.name.insert(entry.name.begin(), &catalogue[c+1], &catalogue[c+9]);
for(size_t s = 0; s < 3; s++) entry.type.push_back((char)catalogue[c + s + 9] & 0x7f);
entry.read_only = catalogue[c + 9] & 0x80;
entry.system = catalogue[c + 10] & 0x80;
entry.extent = (size_t)(catalogue[c + 12] + (catalogue[c + 14] << 5));
entry.number_of_records = catalogue[c + 15];
entry.catalogue_index = c;
}
// Sort the catalogue entries and then map to files.
std::sort(catalogue_entries.begin(), catalogue_entries.end());
std::unique_ptr<Catalogue> result(new Catalogue);
bool has_long_allocation_units = (parameters.tracks * parameters.sectors_per_track * (int)sector_size / parameters.block_size) >= 256;
size_t bytes_per_catalogue_entry = (has_long_allocation_units ? 16 : 8) * (size_t)parameters.block_size;
int sectors_per_block = parameters.block_size / (int)sector_size;
int records_per_sector = (int)sector_size / 128;
auto entry = catalogue_entries.begin();
while(entry != catalogue_entries.end()) {
// Find final catalogue entry that relates to the same file.
auto final_entry = entry + 1;
while(final_entry != catalogue_entries.end() && final_entry->is_same_file(*entry)) {
final_entry++;
}
final_entry--;
// figure out where this data needs to be pasted in
size_t extent = (size_t)(catalogue[c + 12] + (catalogue[c + 14] << 5));
int number_of_records = catalogue[c + 15];
// Create file.
result->files.emplace_back();
File &new_file = result->files.back();
new_file.user_number = entry->user_number;
new_file.name = std::move(entry->name);
new_file.type = std::move(entry->type);
new_file.read_only = entry->read_only;
new_file.system = entry->system;
size_t required_size = extent * bytes_per_catalogue_entry + (size_t)number_of_records * 128;
if(result->files[index].data.size() < required_size) {
result->files[index].data.resize(required_size);
}
// Create storage for data.
size_t required_size = final_entry->extent * bytes_per_catalogue_entry + (size_t)final_entry->number_of_records * 128;
new_file.data.resize(required_size);
int sectors_per_block = parameters.block_size / (int)sector_size;
int records_per_sector = (int)sector_size / 128;
int record = 0;
for(size_t block = 0; block < (has_long_allocation_units ? 8 : 16) && record < number_of_records; block++) {
int block_number;
if(has_long_allocation_units) {
block_number = catalogue[c + 16 + (block << 1)] + (catalogue[c + 16 + (block << 1) + 1] << 8);
} else {
block_number = catalogue[c + 16 + block];
}
if(!block_number) {
record += parameters.block_size / 128;
continue;
}
int first_sector = block_number * sectors_per_block;
sector = first_sector % parameters.sectors_per_track;
track = first_sector / parameters.sectors_per_track;
for(int s = 0; s < sectors_per_block && record < number_of_records; s++) {
std::shared_ptr<Storage::Encodings::MFM::Sector> sector_contents = parser.get_sector(0, (uint8_t)track, (uint8_t)(parameters.first_sector + sector));
if(!sector_contents) break;
sector++;
if(sector == parameters.sectors_per_track) {
sector = 0;
track++;
// Accumulate all data.
while(entry <= final_entry) {
int record = 0;
for(size_t block = 0; block < (has_long_allocation_units ? 8 : 16) && record < entry->number_of_records; block++) {
int block_number;
if(has_long_allocation_units) {
block_number = catalogue[entry->catalogue_index + 16 + (block << 1)] + (catalogue[entry->catalogue_index + 16 + (block << 1) + 1] << 8);
} else {
block_number = catalogue[entry->catalogue_index + 16 + block];
}
if(!block_number) {
record += parameters.block_size / 128;
continue;
}
int first_sector = block_number * sectors_per_block;
int records_to_copy = std::min(number_of_records - record, records_per_sector);
memcpy(&result->files[index].data[extent * bytes_per_catalogue_entry + (size_t)record * 128], sector_contents->data.data(), (size_t)records_to_copy * 128);
record += records_to_copy;
sector = first_sector % parameters.sectors_per_track;
track = first_sector / parameters.sectors_per_track;
for(int s = 0; s < sectors_per_block && record < entry->number_of_records; s++) {
Storage::Encodings::MFM::Sector *sector_contents = parser.get_sector(0, (uint8_t)track, (uint8_t)(parameters.first_sector + sector));
if(!sector_contents) break;
sector++;
if(sector == parameters.sectors_per_track) {
sector = 0;
track++;
}
int records_to_copy = std::min(entry->number_of_records - record, records_per_sector);
memcpy(&new_file.data[entry->extent * bytes_per_catalogue_entry + (size_t)record * 128], sector_contents->data.data(), (size_t)records_to_copy * 128);
record += records_to_copy;
}
}
entry++;
}
}

View File

@ -31,6 +31,10 @@ struct PCMSegment {
PCMSegment(Time length_of_a_bit, unsigned int number_of_bits, std::vector<uint8_t> data)
: length_of_a_bit(length_of_a_bit), number_of_bits(number_of_bits), data(data) {}
PCMSegment() {}
int bit(size_t index) const {
return (data[index >> 3] >> (7 ^ (index & 7)))&1;
}
};
/*!

View File

@ -22,6 +22,23 @@ namespace Disk {
*/
class Track {
public:
/*!
Describes the location of a track, implementing < to allow for use as a set key.
*/
struct Address {
int head, position;
bool operator < (const Address &rhs) const {
return (head < rhs.head) || (position < rhs.position);
}
// Address(const Address &rhs) noexcept : head(rhs.head), position(rhs.position) {}
// const Address &operator =(const Address &rhs) {
// head = rhs.head;
// position = rhs.position;
// return *this;
// }
};
/*!
Describes a detectable track event either a flux transition or the passing of the index hole,
along with the length of time between the previous event and its occurance.

View File

@ -0,0 +1,58 @@
//
// TrackSerialiser.cpp
// Clock Signal
//
// Created by Thomas Harte on 26/09/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "TrackSerialiser.hpp"
// TODO: if this is a PCMTrack with only one segment and that segment's bit rate is within tolerance,
// just return a copy of that segment.
Storage::Disk::PCMSegment Storage::Disk::track_serialisation(Track &track, Time length_of_a_bit) {
unsigned int history_size = 16;
DigitalPhaseLockedLoop pll(100, history_size);
struct ResultAccumulator: public DigitalPhaseLockedLoop::Delegate {
PCMSegment result;
void digital_phase_locked_loop_output_bit(int value) {
result.data.resize(1 + (result.number_of_bits >> 3));
if(value) result.data[result.number_of_bits >> 3] |= 0x80 >> (result.number_of_bits & 7);
result.number_of_bits++;
}
} result_accumulator;
result_accumulator.result.number_of_bits = 0;
result_accumulator.result.length_of_a_bit = length_of_a_bit;
Time length_multiplier = Time(100*length_of_a_bit.clock_rate, length_of_a_bit.length);
length_multiplier.simplify();
// start at the index hole
track.seek_to(Time(0));
// grab events until the next index hole
Time time_error = Time(0);
while(true) {
Track::Event next_event = track.get_next_event();
if(next_event.type == Track::Event::IndexHole) break;
Time extended_length = next_event.length * length_multiplier + time_error;
time_error.clock_rate = extended_length.clock_rate;
time_error.length = extended_length.length % extended_length.clock_rate;
pll.run_for(Cycles((int)extended_length.get_unsigned_int()));
pll.add_pulse();
// If the PLL is now sufficiently primed, restart, and start recording bits this time.
if(history_size) {
history_size--;
if(!history_size) {
track.seek_to(Time(0));
time_error.set_zero();
pll.set_delegate(&result_accumulator);
}
}
}
return result_accumulator.result;
}

View File

@ -0,0 +1,38 @@
//
// TrackSerialiser.hpp
// Clock Signal
//
// Created by Thomas Harte on 23/09/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef TrackSerialiser_h
#define TrackSerialiser_h
#include "../DPLL/DigitalPhaseLockedLoop.hpp"
#include "PCMSegment.hpp"
#include "Track.hpp"
namespace Storage {
namespace Disk {
/*!
Instantiates a PLL with a target bit length of @c length_of_a_bit and provides a complete
serialisation of @c track, starting from the index hole.
This feature is offered for the benefit of various parts of the code that need to make
sense of a track **other than emulation**, as it renders a one-off image of the track,
which can be inaccurate. However there are many occasions where a single rendering is
desireable e.g. file formats that apply that constraint, or static analysis prior to
emulation launch, which works with broad strokes.
@param track The track to serialise.
@param length_of_a_bit The expected length of a single bit, as a proportion of the
track length.
*/
PCMSegment track_serialisation(Track &track, Time length_of_a_bit);
}
}
#endif /* TrackSerialiser_h */