mirror of
https://github.com/TomHarte/CLK.git
synced 2025-04-09 15:39:08 +00:00
Merge pull request #86 from TomHarte/DiskWrites
Implements backing work for in-memory disk writes
This commit is contained in:
commit
e56beb3e9c
@ -10,11 +10,14 @@
|
||||
4B049CDD1DA3C82F00322067 /* BCDTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B049CDC1DA3C82F00322067 /* BCDTest.swift */; };
|
||||
4B0BE4281D3481E700D5256B /* DigitalPhaseLockedLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0BE4261D3481E700D5256B /* DigitalPhaseLockedLoop.cpp */; };
|
||||
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */; };
|
||||
4B121F951E05E66800BFDA12 /* PCMPatchedTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */; };
|
||||
4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */; };
|
||||
4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; };
|
||||
4B14145D1B5887A600E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; };
|
||||
4B14145E1B5887AA00E04248 /* CPU6502AllRAM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414591B58879D00E04248 /* CPU6502AllRAM.cpp */; };
|
||||
4B1414601B58885000E04248 /* WolfgangLorenzTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */; };
|
||||
4B1414621B58888700E04248 /* KlausDormannTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414611B58888700E04248 /* KlausDormannTests.swift */; };
|
||||
4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B1D08051E0F7A1100763741 /* TimeTests.mm */; };
|
||||
4B1E85751D170228001EF87D /* Typer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85731D170228001EF87D /* Typer.cpp */; };
|
||||
4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85801D176468001EF87D /* 6532Tests.swift */; };
|
||||
4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2409531C45AB05004DA684 /* Speaker.cpp */; };
|
||||
@ -39,6 +42,7 @@
|
||||
4B3BA0CF1D318B44005DD7A7 /* MOS6522Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C91D318B44005DD7A7 /* MOS6522Bridge.mm */; };
|
||||
4B3BA0D01D318B44005DD7A7 /* MOS6532Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0CB1D318B44005DD7A7 /* MOS6532Bridge.mm */; };
|
||||
4B3BA0D11D318B44005DD7A7 /* TestMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0CD1D318B44005DD7A7 /* TestMachine.mm */; };
|
||||
4B3F1B461E0388D200DB26EE /* PCMPatchedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */; };
|
||||
4B44EBF51DC987AF00A7820C /* AllSuiteA.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */; };
|
||||
4B44EBF71DC9883B00A7820C /* 6502_functional_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */; };
|
||||
4B44EBF91DC9898E00A7820C /* BCDTEST_beeb in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */; };
|
||||
@ -381,6 +385,8 @@
|
||||
4BCF1FAB1DADD41B0039D2E7 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1FA91DADD41B0039D2E7 /* StaticAnalyser.cpp */; };
|
||||
4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */; };
|
||||
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD468F51D8DF41D0084958B /* 1770.cpp */; };
|
||||
4BD4A8CD1E077E8A0020D856 /* PCMSegment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F961E060CF000BFDA12 /* PCMSegment.cpp */; };
|
||||
4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */; };
|
||||
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; };
|
||||
4BD69F941D98760000243FE1 /* AcornADF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD69F921D98760000243FE1 /* AcornADF.cpp */; };
|
||||
4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE77A2C1D84ADFB00BC3827 /* File.cpp */; };
|
||||
@ -424,6 +430,10 @@
|
||||
4B0BE4271D3481E700D5256B /* DigitalPhaseLockedLoop.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DigitalPhaseLockedLoop.hpp; sourceTree = "<group>"; };
|
||||
4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = "<group>"; };
|
||||
4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = "<group>"; };
|
||||
4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMPatchedTrackTests.mm; sourceTree = "<group>"; };
|
||||
4B121F961E060CF000BFDA12 /* PCMSegment.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMSegment.cpp; sourceTree = "<group>"; };
|
||||
4B121F971E060CF000BFDA12 /* PCMSegment.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMSegment.hpp; sourceTree = "<group>"; };
|
||||
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMSegmentEventSourceTests.mm; sourceTree = "<group>"; };
|
||||
4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ClockSignal-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
4B1414571B58879D00E04248 /* CPU6502.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CPU6502.cpp; sourceTree = "<group>"; };
|
||||
4B1414581B58879D00E04248 /* CPU6502.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CPU6502.hpp; sourceTree = "<group>"; };
|
||||
@ -431,6 +441,7 @@
|
||||
4B14145A1B58879D00E04248 /* CPU6502AllRAM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CPU6502AllRAM.hpp; sourceTree = "<group>"; };
|
||||
4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WolfgangLorenzTests.swift; sourceTree = "<group>"; };
|
||||
4B1414611B58888700E04248 /* KlausDormannTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KlausDormannTests.swift; sourceTree = "<group>"; };
|
||||
4B1D08051E0F7A1100763741 /* TimeTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TimeTests.mm; sourceTree = "<group>"; };
|
||||
4B1E85731D170228001EF87D /* Typer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Typer.cpp; sourceTree = "<group>"; };
|
||||
4B1E85741D170228001EF87D /* Typer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Typer.hpp; sourceTree = "<group>"; };
|
||||
4B1E857B1D174DEC001EF87D /* 6532.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6532.hpp; sourceTree = "<group>"; };
|
||||
@ -482,6 +493,8 @@
|
||||
4B3BA0CB1D318B44005DD7A7 /* MOS6532Bridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MOS6532Bridge.mm; sourceTree = "<group>"; };
|
||||
4B3BA0CC1D318B44005DD7A7 /* TestMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestMachine.h; sourceTree = "<group>"; };
|
||||
4B3BA0CD1D318B44005DD7A7 /* TestMachine.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TestMachine.mm; sourceTree = "<group>"; };
|
||||
4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMPatchedTrack.cpp; sourceTree = "<group>"; };
|
||||
4B3F1B451E0388D200DB26EE /* PCMPatchedTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMPatchedTrack.hpp; sourceTree = "<group>"; };
|
||||
4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = AllSuiteA.bin; path = AllSuiteA/AllSuiteA.bin; sourceTree = "<group>"; };
|
||||
4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 6502_functional_test.bin; path = "Klaus Dormann/6502_functional_test.bin"; sourceTree = "<group>"; };
|
||||
4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */ = {isa = PBXFileReference; lastKnownFileType = file; name = BCDTEST_beeb; path = BCDTest/BCDTEST_beeb; sourceTree = "<group>"; };
|
||||
@ -497,7 +510,6 @@
|
||||
4B4DC82A1D2C27A4003C5BF8 /* SerialBus.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SerialBus.hpp; sourceTree = "<group>"; };
|
||||
4B5073051DDD3B9400C48FBD /* ArrayBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ArrayBuilder.cpp; sourceTree = "<group>"; };
|
||||
4B5073061DDD3B9400C48FBD /* ArrayBuilder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ArrayBuilder.hpp; sourceTree = "<group>"; };
|
||||
4B5073081DDFCFDF00C48FBD /* ArrayBuilderTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArrayBuilderTests.h; sourceTree = "<group>"; };
|
||||
4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ArrayBuilderTests.mm; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
@ -892,6 +904,7 @@
|
||||
4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
4BD468F51D8DF41D0084958B /* 1770.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = 1770.cpp; path = 1770/1770.cpp; sourceTree = "<group>"; };
|
||||
4BD468F61D8DF41D0084958B /* 1770.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 1770.hpp; path = 1770/1770.hpp; sourceTree = "<group>"; };
|
||||
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMTrackTests.mm; sourceTree = "<group>"; };
|
||||
4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = "<group>"; };
|
||||
4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CSBestEffortUpdater.m; path = Updater/CSBestEffortUpdater.m; sourceTree = "<group>"; };
|
||||
4BD69F921D98760000243FE1 /* AcornADF.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AcornADF.cpp; sourceTree = "<group>"; };
|
||||
@ -1295,11 +1308,15 @@
|
||||
4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */,
|
||||
4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */,
|
||||
4B30512B1D989E2200B4FED8 /* Drive.cpp */,
|
||||
4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */,
|
||||
4B121F961E060CF000BFDA12 /* PCMSegment.cpp */,
|
||||
4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */,
|
||||
4B0BE4271D3481E700D5256B /* DigitalPhaseLockedLoop.hpp */,
|
||||
4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */,
|
||||
4B6C73BC1D387AE500AFCFCA /* DiskController.hpp */,
|
||||
4B30512C1D989E2200B4FED8 /* Drive.hpp */,
|
||||
4B3F1B451E0388D200DB26EE /* PCMPatchedTrack.hpp */,
|
||||
4B121F971E060CF000BFDA12 /* PCMSegment.hpp */,
|
||||
4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */,
|
||||
4BB697CF1D4BA44900248BDF /* Encodings */,
|
||||
4BAB62B21D327F7E00DF5BA0 /* Formats */,
|
||||
@ -1673,8 +1690,11 @@
|
||||
4BB73EB51B587A5100552FC2 /* Clock SignalTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B5073081DDFCFDF00C48FBD /* ArrayBuilderTests.h */,
|
||||
4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */,
|
||||
4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */,
|
||||
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */,
|
||||
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */,
|
||||
4B1D08051E0F7A1100763741 /* TimeTests.mm */,
|
||||
4BB73EB81B587A5100552FC2 /* Info.plist */,
|
||||
4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */,
|
||||
4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */,
|
||||
@ -2334,6 +2354,7 @@
|
||||
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */,
|
||||
4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */,
|
||||
4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */,
|
||||
4BD4A8CD1E077E8A0020D856 /* PCMSegment.cpp in Sources */,
|
||||
4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */,
|
||||
4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */,
|
||||
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */,
|
||||
@ -2367,6 +2388,7 @@
|
||||
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */,
|
||||
4BF829691D8F7361001BAE39 /* File.cpp in Sources */,
|
||||
4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */,
|
||||
4B3F1B461E0388D200DB26EE /* PCMPatchedTrack.cpp in Sources */,
|
||||
4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */,
|
||||
4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */,
|
||||
4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */,
|
||||
@ -2439,6 +2461,7 @@
|
||||
4B3BA0D11D318B44005DD7A7 /* TestMachine.mm in Sources */,
|
||||
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */,
|
||||
4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */,
|
||||
4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */,
|
||||
4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */,
|
||||
4B3BA0CF1D318B44005DD7A7 /* MOS6522Bridge.mm in Sources */,
|
||||
4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */,
|
||||
@ -2446,7 +2469,10 @@
|
||||
4B3BA0C31D318AEC005DD7A7 /* C1540Tests.swift in Sources */,
|
||||
4B1414621B58888700E04248 /* KlausDormannTests.swift in Sources */,
|
||||
4B1414601B58885000E04248 /* WolfgangLorenzTests.swift in Sources */,
|
||||
4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */,
|
||||
4B049CDD1DA3C82F00322067 /* BCDTest.swift in Sources */,
|
||||
4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */,
|
||||
4B121F951E05E66800BFDA12 /* PCMPatchedTrackTests.mm in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -1,13 +0,0 @@
|
||||
//
|
||||
// ArrayBuilderTests.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 19/11/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
@interface ArrayBuilderTests : XCTestCase
|
||||
|
||||
@end
|
@ -6,7 +6,8 @@
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ArrayBuilderTests.h"
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "ArrayBuilder.hpp"
|
||||
|
||||
static NSData *inputData, *outputData;
|
||||
@ -17,6 +18,9 @@ static void setData(bool is_input, uint8_t *data, size_t size)
|
||||
if(is_input) inputData = dataObject; else outputData = dataObject;
|
||||
}
|
||||
|
||||
@interface ArrayBuilderTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation ArrayBuilderTests
|
||||
|
||||
+ (void)setUp
|
||||
@ -43,6 +47,11 @@ static void setData(bool is_input, uint8_t *data, size_t size)
|
||||
}
|
||||
}
|
||||
|
||||
- (std::function<void(uint8_t *input, size_t input_size, uint8_t *output, size_t output_size)>)emptyFlushFunction
|
||||
{
|
||||
return [=] (uint8_t *input, size_t input_size, uint8_t *output, size_t output_size) {};
|
||||
}
|
||||
|
||||
- (void)testSingleWriteSingleFlush
|
||||
{
|
||||
Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData);
|
||||
@ -53,7 +62,7 @@ static void setData(bool is_input, uint8_t *data, size_t size)
|
||||
for(int c = 0; c < 5; c++) input[c] = c;
|
||||
for(int c = 0; c < 3; c++) output[c] = c + 0x80;
|
||||
|
||||
arrayBuilder.flush();
|
||||
arrayBuilder.flush(self.emptyFlushFunction);
|
||||
arrayBuilder.submit();
|
||||
|
||||
[self assertMonotonicForInputSize:5 outputSize:3];
|
||||
@ -77,7 +86,7 @@ static void setData(bool is_input, uint8_t *data, size_t size)
|
||||
for(int c = 0; c < 2; c++) input[c] = c+2;
|
||||
for(int c = 0; c < 2; c++) output[c] = c+2 + 0x80;
|
||||
|
||||
arrayBuilder.flush();
|
||||
arrayBuilder.flush(self.emptyFlushFunction);
|
||||
arrayBuilder.submit();
|
||||
|
||||
[self assertMonotonicForInputSize:4 outputSize:4];
|
||||
@ -98,7 +107,7 @@ static void setData(bool is_input, uint8_t *data, size_t size)
|
||||
XCTAssert(inputData.length == 0, @"No input data should have been received; %lu bytes were received", (unsigned long)inputData.length);
|
||||
XCTAssert(outputData.length == 0, @"No output data should have been received; %lu bytes were received", (unsigned long)outputData.length);
|
||||
|
||||
arrayBuilder.flush();
|
||||
arrayBuilder.flush(self.emptyFlushFunction);
|
||||
arrayBuilder.submit();
|
||||
|
||||
XCTAssert(inputData.length == 25, @"All input data should have been received; %lu bytes were received", (unsigned long)inputData.length);
|
||||
@ -112,7 +121,7 @@ static void setData(bool is_input, uint8_t *data, size_t size)
|
||||
arrayBuilder.get_input_storage(5);
|
||||
arrayBuilder.get_output_storage(5);
|
||||
|
||||
arrayBuilder.flush();
|
||||
arrayBuilder.flush(self.emptyFlushFunction);
|
||||
|
||||
uint8_t *input = arrayBuilder.get_input_storage(5);
|
||||
uint8_t *output = arrayBuilder.get_output_storage(5);
|
||||
@ -122,7 +131,7 @@ static void setData(bool is_input, uint8_t *data, size_t size)
|
||||
for(int c = 0; c < 5; c++) input[c] = c;
|
||||
for(int c = 0; c < 5; c++) output[c] = c + 0x80;
|
||||
|
||||
arrayBuilder.flush();
|
||||
arrayBuilder.flush(self.emptyFlushFunction);
|
||||
arrayBuilder.submit();
|
||||
|
||||
[self assertMonotonicForInputSize:5 outputSize:5];
|
||||
|
220
OSBindings/Mac/Clock SignalTests/PCMPatchedTrackTests.mm
Normal file
220
OSBindings/Mac/Clock SignalTests/PCMPatchedTrackTests.mm
Normal file
@ -0,0 +1,220 @@
|
||||
//
|
||||
// PCMPatchedTrackTests.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 17/12/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "PCMTrack.hpp"
|
||||
#include "PCMPatchedTrack.hpp"
|
||||
|
||||
@interface PCMPatchedTrackTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation PCMPatchedTrackTests
|
||||
|
||||
#pragma mark - Prebuilt tracks
|
||||
|
||||
- (std::shared_ptr<Storage::Disk::Track>)togglingTrack
|
||||
{
|
||||
Storage::Disk::PCMSegment segment;
|
||||
segment.data = { 0xff, 0xff, 0xff, 0xff };
|
||||
segment.number_of_bits = 32;
|
||||
return std::shared_ptr<Storage::Disk::Track>(new Storage::Disk::PCMTrack(segment));
|
||||
}
|
||||
|
||||
- (std::shared_ptr<Storage::Disk::Track>)patchableTogglingTrack
|
||||
{
|
||||
std::shared_ptr<Storage::Disk::Track> track = self.togglingTrack;
|
||||
return std::shared_ptr<Storage::Disk::Track>(new Storage::Disk::PCMPatchedTrack(track));
|
||||
}
|
||||
|
||||
- (std::shared_ptr<Storage::Disk::Track>)fourSegmentPatchedTrack
|
||||
{
|
||||
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.patchableTogglingTrack;
|
||||
Storage::Disk::PCMPatchedTrack *patchable = static_cast<Storage::Disk::PCMPatchedTrack *>(patchableTrack.get());
|
||||
|
||||
for(int c = 0; c < 4; c++)
|
||||
{
|
||||
Storage::Disk::PCMSegment segment;
|
||||
segment.data = {0xff};
|
||||
segment.number_of_bits = 8;
|
||||
segment.length_of_a_bit.length = 1;
|
||||
segment.length_of_a_bit.clock_rate = 32;
|
||||
patchable->add_segment(Storage::Time(c, 4), segment);
|
||||
}
|
||||
|
||||
return patchableTrack;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (std::vector<Storage::Disk::Track::Event>)eventsFromTrack:(std::shared_ptr<Storage::Disk::Track>)track
|
||||
{
|
||||
std::vector<Storage::Disk::Track::Event> events;
|
||||
while(1)
|
||||
{
|
||||
events.push_back(track->get_next_event());
|
||||
if(events.back().type == Storage::Disk::Track::Event::IndexHole) break;
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
- (Storage::Time)timeForEvents:(const std::vector<Storage::Disk::Track::Event> &)events
|
||||
{
|
||||
Storage::Time result(0);
|
||||
for(auto event : events)
|
||||
{
|
||||
result += event.length;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)patchTrack:(std::shared_ptr<Storage::Disk::Track>)track withSegment:(Storage::Disk::PCMSegment)segment atTime:(Storage::Time)time
|
||||
{
|
||||
Storage::Disk::PCMPatchedTrack *patchable = static_cast<Storage::Disk::PCMPatchedTrack *>(track.get());
|
||||
patchable->add_segment(time, segment);
|
||||
}
|
||||
|
||||
#pragma mark - Repeating Asserts
|
||||
|
||||
- (void)assertOneThirtyTwosForTrack:(std::shared_ptr<Storage::Disk::Track>)track
|
||||
{
|
||||
// Confirm that there are now flux transitions (just the first five will do)
|
||||
// located 1/32nd of a rotation apart.
|
||||
for(int c = 0; c < 5; c++)
|
||||
{
|
||||
Storage::Disk::Track::Event event = track->get_next_event();
|
||||
XCTAssert(
|
||||
event.length == (c ? Storage::Time(1, 32) : Storage::Time(1, 64)),
|
||||
@"flux transitions should be 1/32nd of a track apart");
|
||||
}
|
||||
}
|
||||
|
||||
- (void)assertEvents:(const std::vector<Storage::Disk::Track::Event> &)events hasEntries:(size_t)numberOfEntries withEntry:(size_t)entry ofLength:(Storage::Time)time
|
||||
{
|
||||
XCTAssert(events.size() == numberOfEntries, @"Should be %zu total events", numberOfEntries);
|
||||
|
||||
XCTAssert(events[entry].length == time, @"Event %zu should have been %d/%d long, was %d/%d", entry, time.length, time.clock_rate, events[entry].length.length, events[entry].length.clock_rate);
|
||||
|
||||
Storage::Time eventTime = [self timeForEvents:events];
|
||||
XCTAssert(eventTime == Storage::Time(1), @"Total track length should be 1; was %d/%d", eventTime.length, eventTime.clock_rate);
|
||||
}
|
||||
|
||||
#pragma mark - Unpatched tracks
|
||||
|
||||
- (void)testUnpatchedRawTrack
|
||||
{
|
||||
[self assertOneThirtyTwosForTrack:self.togglingTrack];
|
||||
}
|
||||
|
||||
- (void)testUnpatchedTrack
|
||||
{
|
||||
[self assertOneThirtyTwosForTrack:self.patchableTogglingTrack];
|
||||
}
|
||||
|
||||
#pragma mark - Insertions affecting one existing segment
|
||||
|
||||
- (void)testSingleSplice
|
||||
{
|
||||
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.patchableTogglingTrack;
|
||||
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(1, 32), 1, {0xff}) atTime:Storage::Time(3, 128)];
|
||||
|
||||
std::vector<Storage::Disk::Track::Event> events = [self eventsFromTrack:patchableTrack];
|
||||
Storage::Time total_length = [self timeForEvents:events];
|
||||
|
||||
XCTAssert(events.size() == 33, @"Should still be 33 total events");
|
||||
XCTAssert(events[0].length == Storage::Time(1, 64), @"First event should be after 1/64 as usual");
|
||||
XCTAssert(events[1].length == Storage::Time(3, 128), @"Second event should be 3/128 later"); // ... as it was inserted at 3/128 and runs at the same rate as the main data, so first inserted event is at 3/128+1/64-1/64
|
||||
XCTAssert(events[2].length == Storage::Time(5, 128), @"Should still be 33 total events"); // 1/64 = 2/128 to exit the patch, plus 3/128 to get to the next event, having spliced in 1/128 ahead of the normal clock
|
||||
XCTAssert(total_length == Storage::Time(1), @"Total track length should still be 1");
|
||||
}
|
||||
|
||||
- (void)testLeftReplace
|
||||
{
|
||||
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.patchableTogglingTrack;
|
||||
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(1, 16), 8, {0x00}) atTime:Storage::Time(0)];
|
||||
|
||||
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:17 withEntry:0 ofLength:Storage::Time(33, 64)];
|
||||
}
|
||||
|
||||
- (void)testRightReplace
|
||||
{
|
||||
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.patchableTogglingTrack;
|
||||
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(1, 16), 8, {0x00}) atTime:Storage::Time(1, 2)];
|
||||
|
||||
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:17 withEntry:16 ofLength:Storage::Time(33, 64)];
|
||||
}
|
||||
|
||||
#pragma mark - Insertions affecting three existing segments
|
||||
|
||||
- (void)testMultiSegmentTrack
|
||||
{
|
||||
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.fourSegmentPatchedTrack;
|
||||
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:33 withEntry:4 ofLength:Storage::Time(1, 32)];
|
||||
}
|
||||
|
||||
- (void)testMultiTrimBothSideReplace
|
||||
{
|
||||
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.fourSegmentPatchedTrack;
|
||||
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(1, 16), 8, {0x00}) atTime:Storage::Time(1, 8)];
|
||||
|
||||
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:17 withEntry:4 ofLength:Storage::Time(17, 32)];
|
||||
}
|
||||
|
||||
- (void)testMultiTrimRightReplace
|
||||
{
|
||||
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.fourSegmentPatchedTrack;
|
||||
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(3, 8), 1, {0x00}) atTime:Storage::Time(1, 8)];
|
||||
|
||||
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:21 withEntry:4 ofLength:Storage::Time(13, 32)];
|
||||
}
|
||||
|
||||
- (void)testMultiTrimLeftReplace
|
||||
{
|
||||
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.fourSegmentPatchedTrack;
|
||||
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(3, 8), 1, {0x00}) atTime:Storage::Time(1, 4)];
|
||||
|
||||
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:21 withEntry:8 ofLength:Storage::Time(13, 32)];
|
||||
}
|
||||
|
||||
#pragma mark - Insertions affecting two existing segments
|
||||
|
||||
- (void)testTwoSegmentOverlap
|
||||
{
|
||||
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.fourSegmentPatchedTrack;
|
||||
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(1, 32), 8, {0x00}) atTime:Storage::Time(1, 8)];
|
||||
|
||||
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:25 withEntry:4 ofLength:Storage::Time(9, 32)];
|
||||
}
|
||||
|
||||
- (void)testTwoSegmentRightReplace
|
||||
{
|
||||
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.fourSegmentPatchedTrack;
|
||||
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(3, 8), 1, {0x00}) atTime:Storage::Time(1, 8)];
|
||||
|
||||
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:21 withEntry:4 ofLength:Storage::Time(13, 32)];
|
||||
}
|
||||
|
||||
- (void)testTwoSegmentLeftReplace
|
||||
{
|
||||
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.fourSegmentPatchedTrack;
|
||||
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(3, 8), 1, {0x00}) atTime:Storage::Time(0)];
|
||||
|
||||
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:21 withEntry:0 ofLength:Storage::Time(25, 64)];
|
||||
}
|
||||
|
||||
#pragma mark - Wrapping segment
|
||||
|
||||
- (void)testWrappingSegment
|
||||
{
|
||||
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.patchableTogglingTrack;
|
||||
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(5, 2), 1, {0x00}) atTime:Storage::Time(0)];
|
||||
|
||||
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:1 withEntry:0 ofLength:Storage::Time(1, 1)];
|
||||
}
|
||||
|
||||
@end
|
113
OSBindings/Mac/Clock SignalTests/PCMSegmentEventSourceTests.mm
Normal file
113
OSBindings/Mac/Clock SignalTests/PCMSegmentEventSourceTests.mm
Normal file
@ -0,0 +1,113 @@
|
||||
//
|
||||
// PCMSegmentEventSourceTests.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 17/12/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "PCMSegment.hpp"
|
||||
|
||||
@interface PCMSegmentEventSourceTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation PCMSegmentEventSourceTests
|
||||
|
||||
- (Storage::Disk::PCMSegmentEventSource)segmentSource
|
||||
{
|
||||
Storage::Disk::PCMSegment alternatingFFs;
|
||||
alternatingFFs.data = {0xff, 0x00, 0xff, 0x00};
|
||||
alternatingFFs.length_of_a_bit.length = 1;
|
||||
alternatingFFs.length_of_a_bit.clock_rate = 10;
|
||||
alternatingFFs.number_of_bits = 32;
|
||||
return Storage::Disk::PCMSegmentEventSource(alternatingFFs);
|
||||
}
|
||||
|
||||
- (void)testCentring
|
||||
{
|
||||
Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource;
|
||||
[self assertFirstTwoEventLengthsForSource:segmentSource];
|
||||
}
|
||||
|
||||
- (void)assertFirstTwoEventLengthsForSource:(Storage::Disk::PCMSegmentEventSource &)segmentSource
|
||||
{
|
||||
Storage::Disk::Track::Event first_event = segmentSource.get_next_event();
|
||||
Storage::Disk::Track::Event second_event = segmentSource.get_next_event();
|
||||
|
||||
first_event.length.simplify();
|
||||
second_event.length.simplify();
|
||||
XCTAssertTrue(first_event.length.length == 1 && first_event.length.clock_rate == 20, @"First event should occur half a bit's length in");
|
||||
XCTAssertTrue(second_event.length.length == 1 && second_event.length.clock_rate == 10, @"Second event should occur a whole bit's length after the first");
|
||||
}
|
||||
|
||||
- (void)testLongerGap
|
||||
{
|
||||
Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource;
|
||||
|
||||
// skip first eight flux transitions
|
||||
for(int c = 0; c < 8; c++) segmentSource.get_next_event();
|
||||
|
||||
Storage::Disk::Track::Event next_event = segmentSource.get_next_event();
|
||||
next_event.length.simplify();
|
||||
|
||||
XCTAssertTrue(next_event.length.length == 9 && next_event.length.clock_rate == 10, @"Zero byte should give a nine bit length event gap");
|
||||
}
|
||||
|
||||
- (void)testTermination
|
||||
{
|
||||
Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource;
|
||||
Storage::Time total_time;
|
||||
for(int c = 0; c < 16; c++) total_time += segmentSource.get_next_event().length;
|
||||
|
||||
Storage::Disk::Track::Event final_event = segmentSource.get_next_event();
|
||||
total_time += final_event.length;
|
||||
total_time.simplify();
|
||||
|
||||
XCTAssertTrue(final_event.type == Storage::Disk::Track::Event::IndexHole, @"Segment should end with an index hole");
|
||||
XCTAssertTrue(total_time.length == 16 && total_time.clock_rate == 5, @"Should have taken 32 bit lengths to finish the segment");
|
||||
}
|
||||
|
||||
- (void)testReset
|
||||
{
|
||||
Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource;
|
||||
for(int c = 0; c < 8; c++) segmentSource.get_next_event();
|
||||
segmentSource.reset();
|
||||
[self assertFirstTwoEventLengthsForSource:segmentSource];
|
||||
}
|
||||
|
||||
- (void)testSeekToSecondBit
|
||||
{
|
||||
Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource;
|
||||
Storage::Time target_time(1, 10);
|
||||
|
||||
Storage::Time found_time = segmentSource.seek_to(target_time);
|
||||
found_time.simplify();
|
||||
|
||||
XCTAssertTrue(found_time.length == 1 && found_time.clock_rate == 20, @"A request to seek to 1/10th should have seeked to 1/20th");
|
||||
|
||||
Storage::Disk::Track::Event next_event = segmentSource.get_next_event();
|
||||
next_event.length.simplify();
|
||||
|
||||
XCTAssertTrue(next_event.length.length == 1 && next_event.length.clock_rate == 10, @"Next event should be 1/10th later");
|
||||
}
|
||||
|
||||
- (void)testSeekBeyondFinalBit
|
||||
{
|
||||
Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource;
|
||||
Storage::Time target_time(24, 10);
|
||||
|
||||
Storage::Time found_time = segmentSource.seek_to(target_time);
|
||||
found_time.simplify();
|
||||
|
||||
XCTAssertTrue(found_time.length == 47 && found_time.clock_rate == 20, @"A request to seek to 24/10ths should have seeked to 47/20ths");
|
||||
|
||||
Storage::Disk::Track::Event next_event = segmentSource.get_next_event();
|
||||
next_event.length.simplify();
|
||||
|
||||
XCTAssertTrue(next_event.length.length == 17 && next_event.length.clock_rate == 20, @"Next event should be 17/20ths later");
|
||||
XCTAssertTrue(next_event.type == Storage::Disk::Track::Event::IndexHole, @"End should have been reached");
|
||||
}
|
||||
|
||||
@end
|
54
OSBindings/Mac/Clock SignalTests/PCMTrackTests.mm
Normal file
54
OSBindings/Mac/Clock SignalTests/PCMTrackTests.mm
Normal file
@ -0,0 +1,54 @@
|
||||
//
|
||||
// PCMTrackTests.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/12/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "PCMTrack.hpp"
|
||||
|
||||
@interface PCMTrackTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation PCMTrackTests
|
||||
|
||||
- (Storage::Disk::PCMTrack)multiSpeedTrack
|
||||
{
|
||||
Storage::Disk::PCMSegment quickSegment, slowSegment;
|
||||
|
||||
quickSegment.data = {0xff};
|
||||
quickSegment.number_of_bits = 8;
|
||||
quickSegment.length_of_a_bit.length = 1;
|
||||
quickSegment.length_of_a_bit.clock_rate = 100;
|
||||
|
||||
slowSegment.data = {0xff};
|
||||
slowSegment.number_of_bits = 8;
|
||||
slowSegment.length_of_a_bit.length = 1;
|
||||
slowSegment.length_of_a_bit.clock_rate = 3;
|
||||
|
||||
return Storage::Disk::PCMTrack({quickSegment, slowSegment});
|
||||
}
|
||||
|
||||
- (void)testMultispeedTrack
|
||||
{
|
||||
Storage::Disk::PCMTrack track = self.multiSpeedTrack;
|
||||
std::vector<Storage::Disk::Track::Event> events;
|
||||
Storage::Time total_length;
|
||||
do {
|
||||
events.push_back(track.get_next_event());
|
||||
total_length += events.back().length;
|
||||
} while(events.back().type != Storage::Disk::Track::Event::IndexHole);
|
||||
|
||||
XCTAssert(events.size() == 17, "Should have received 17 events; got %lu", events.size());
|
||||
|
||||
total_length.simplify();
|
||||
XCTAssert(total_length.length == 1 && total_length.clock_rate == 1, "Events should have summed to a total time of 1; instead got %u/%u", total_length.length, total_length.clock_rate);
|
||||
|
||||
Storage::Time transition_length = events[0].length + events.back().length;
|
||||
XCTAssert(events[8].length == transition_length, "Time taken in transition between speed zones should be half of a bit length in the first part plus half of a bit length in the second");
|
||||
}
|
||||
|
||||
@end
|
44
OSBindings/Mac/Clock SignalTests/TimeTests.mm
Normal file
44
OSBindings/Mac/Clock SignalTests/TimeTests.mm
Normal file
@ -0,0 +1,44 @@
|
||||
//
|
||||
// TimeTests.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/12/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "Storage.hpp"
|
||||
|
||||
@interface TimeTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation TimeTests
|
||||
|
||||
- (void)testHalf
|
||||
{
|
||||
Storage::Time half(0.5f);
|
||||
XCTAssert(half == Storage::Time(1, 2), @"0.5 should be converted to 1/2");
|
||||
}
|
||||
|
||||
- (void)testTwenty
|
||||
{
|
||||
Storage::Time twenty(20.0f);
|
||||
XCTAssert(twenty == Storage::Time(20, 1), @"20.0 should be converted to 20/1");
|
||||
}
|
||||
|
||||
- (void)testTooSmallFloat
|
||||
{
|
||||
float original = 1.0f / powf(2.0f, 25.0f);
|
||||
Storage::Time time(original);
|
||||
XCTAssert(time == Storage::Time(0), @"Numbers too small to be represented should be 0");
|
||||
}
|
||||
|
||||
- (void)testTooBigFloat
|
||||
{
|
||||
float original = powf(2.0f, 48.0f);
|
||||
Storage::Time time(original);
|
||||
XCTAssert(time == Storage::Time::max(), @"Numbers too big to be represented should saturate");
|
||||
}
|
||||
|
||||
@end
|
@ -10,9 +10,23 @@
|
||||
|
||||
using namespace Storage::Disk;
|
||||
|
||||
int Disk::get_id_for_track_at_position(unsigned int head, unsigned int position)
|
||||
{
|
||||
return (int)(position * get_head_count() + head);
|
||||
}
|
||||
|
||||
void Disk::set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track)
|
||||
{
|
||||
if(!get_is_read_only()) return;
|
||||
|
||||
int address = get_id_for_track_at_position(head, position);
|
||||
cached_tracks_[address] = track;
|
||||
modified_tracks_.insert(address);
|
||||
}
|
||||
|
||||
std::shared_ptr<Track> Disk::get_track_at_position(unsigned int head, unsigned int position)
|
||||
{
|
||||
int address = (int)(position * get_head_count() + head);
|
||||
int address = get_id_for_track_at_position(head, position);
|
||||
std::map<int, std::shared_ptr<Track>>::iterator cached_track = cached_tracks_.find(address);
|
||||
if(cached_track != cached_tracks_.end()) return cached_track->second;
|
||||
|
||||
@ -20,3 +34,17 @@ std::shared_ptr<Track> Disk::get_track_at_position(unsigned int head, unsigned i
|
||||
cached_tracks_[address] = track;
|
||||
return track;
|
||||
}
|
||||
|
||||
std::shared_ptr<Track> Disk::get_modified_track_at_position(unsigned int head, unsigned int position)
|
||||
{
|
||||
int address = get_id_for_track_at_position(head, position);
|
||||
if(modified_tracks_.find(address) == modified_tracks_.end()) return nullptr;
|
||||
std::map<int, std::shared_ptr<Track>>::iterator cached_track = cached_tracks_.find(address);
|
||||
if(cached_track == cached_tracks_.end()) return nullptr;
|
||||
return cached_track->second;
|
||||
}
|
||||
|
||||
bool Disk::get_is_modified()
|
||||
{
|
||||
return !modified_tracks_.empty();
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include "../Storage.hpp"
|
||||
|
||||
namespace Storage {
|
||||
@ -48,7 +49,7 @@ class Track {
|
||||
|
||||
@returns the time jumped to.
|
||||
*/
|
||||
virtual Time seek_to(Time time_since_index_hole) = 0;
|
||||
virtual Time seek_to(const Time &time_since_index_hole) = 0;
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -85,6 +86,18 @@ class Disk {
|
||||
*/
|
||||
std::shared_ptr<Track> get_track_at_position(unsigned int head, unsigned int position);
|
||||
|
||||
/*!
|
||||
Replaces the Track at position @c position underneath @c head with @c track. Ignored if this disk is read-only.
|
||||
Subclasses that are not read-only should use the protected methods @c get_is_modified and, optionally,
|
||||
@c get_modified_track_at_position to query for changes when closing.
|
||||
*/
|
||||
void set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track);
|
||||
|
||||
/*!
|
||||
@returns whether the disk image is read only. Defaults to @c true if not overridden.
|
||||
*/
|
||||
virtual bool get_is_read_only() { return true; }
|
||||
|
||||
protected:
|
||||
/*!
|
||||
Subclasses should implement this to return the @c Track at @c position underneath @c head. Returned tracks
|
||||
@ -93,8 +106,20 @@ class Disk {
|
||||
*/
|
||||
virtual std::shared_ptr<Track> get_uncached_track_at_position(unsigned int head, unsigned int position) = 0;
|
||||
|
||||
/*!
|
||||
@returns @c true if any calls to set_track_at_position occurred; @c false otherwise.
|
||||
*/
|
||||
bool get_is_modified();
|
||||
|
||||
/*!
|
||||
@returns the @c Track at @c position underneath @c head if a modification was written there.
|
||||
*/
|
||||
std::shared_ptr<Track> get_modified_track_at_position(unsigned int head, unsigned int position);
|
||||
|
||||
private:
|
||||
std::map<int, std::shared_ptr<Track>> cached_tracks_;
|
||||
std::set<int> modified_tracks_;
|
||||
int get_id_for_track_at_position(unsigned int head, unsigned int position);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -13,37 +13,44 @@ using namespace Storage::Disk;
|
||||
Controller::Controller(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsigned int revolutions_per_minute) :
|
||||
clock_rate_(clock_rate * clock_rate_multiplier),
|
||||
clock_rate_multiplier_(clock_rate_multiplier),
|
||||
rotational_multiplier_(60u, revolutions_per_minute),
|
||||
|
||||
cycles_since_index_hole_(0),
|
||||
motor_is_on_(false),
|
||||
|
||||
is_reading_(true),
|
||||
track_is_dirty_(false),
|
||||
|
||||
TimedEventLoop(clock_rate * clock_rate_multiplier)
|
||||
{
|
||||
rotational_multiplier_.length = 60;
|
||||
rotational_multiplier_.clock_rate = revolutions_per_minute;
|
||||
rotational_multiplier_.simplify();
|
||||
|
||||
// seed this class with a PLL, any PLL, so that it's safe to assume non-nullptr later
|
||||
Time one;
|
||||
Time one(1);
|
||||
set_expected_bit_length(one);
|
||||
}
|
||||
|
||||
void Controller::setup_track()
|
||||
{
|
||||
if(patched_track_)
|
||||
{
|
||||
drive_->set_track(patched_track_);
|
||||
}
|
||||
|
||||
track_ = drive_->get_track();
|
||||
track_is_dirty_ = false;
|
||||
|
||||
Time offset;
|
||||
if(track_ && time_into_track_.length > 0)
|
||||
Time track_time_now = get_time_into_track();
|
||||
if(track_ && track_time_now > Time(0))
|
||||
{
|
||||
Time time_found = track_->seek_to(time_into_track_).simplify();
|
||||
offset = (time_into_track_ - time_found).simplify();
|
||||
time_into_track_ = time_found;
|
||||
Time time_found = track_->seek_to(track_time_now);
|
||||
offset = track_time_now - time_found;
|
||||
}
|
||||
else
|
||||
{
|
||||
offset = time_into_track_;
|
||||
time_into_track_.set_zero();
|
||||
offset = track_time_now;
|
||||
}
|
||||
|
||||
reset_timer_to_offset(offset * rotational_multiplier_);
|
||||
get_next_event();
|
||||
get_next_event(offset);
|
||||
}
|
||||
|
||||
void Controller::run_for_cycles(int number_of_cycles)
|
||||
@ -51,14 +58,17 @@ void Controller::run_for_cycles(int number_of_cycles)
|
||||
if(drive_ && drive_->has_disk() && motor_is_on_)
|
||||
{
|
||||
if(!track_) setup_track();
|
||||
|
||||
number_of_cycles *= clock_rate_multiplier_;
|
||||
while(number_of_cycles)
|
||||
{
|
||||
int cycles_until_next_event = (int)get_cycles_until_next_event();
|
||||
int cycles_to_run_for = std::min(cycles_until_next_event, number_of_cycles);
|
||||
|
||||
cycles_since_index_hole_ += (unsigned int)cycles_to_run_for;
|
||||
|
||||
number_of_cycles -= cycles_to_run_for;
|
||||
pll_->run_for_cycles(cycles_to_run_for);
|
||||
if(is_reading_) pll_->run_for_cycles(cycles_to_run_for);
|
||||
TimedEventLoop::run_for_cycles(cycles_to_run_for);
|
||||
}
|
||||
}
|
||||
@ -66,7 +76,7 @@ void Controller::run_for_cycles(int number_of_cycles)
|
||||
|
||||
#pragma mark - Track timed event loop
|
||||
|
||||
void Controller::get_next_event()
|
||||
void Controller::get_next_event(const Time &duration_already_passed)
|
||||
{
|
||||
if(track_)
|
||||
current_event_ = track_->get_next_event();
|
||||
@ -77,9 +87,9 @@ void Controller::get_next_event()
|
||||
current_event_.type = Track::Event::IndexHole;
|
||||
}
|
||||
|
||||
// divide interval, which is in terms of a rotation of the disk, by rotation speed, and
|
||||
// convert it into revolutions per second
|
||||
set_next_event_time_interval(current_event_.length * rotational_multiplier_);
|
||||
// divide interval, which is in terms of a single rotation of the disk, by rotation speed to
|
||||
// convert it into revolutions per second; this is achieved by multiplying by rotational_multiplier_
|
||||
set_next_event_time_interval((current_event_.length - duration_already_passed) * rotational_multiplier_);
|
||||
}
|
||||
|
||||
void Controller::process_next_event()
|
||||
@ -87,16 +97,56 @@ void Controller::process_next_event()
|
||||
switch(current_event_.type)
|
||||
{
|
||||
case Track::Event::FluxTransition:
|
||||
pll_->add_pulse();
|
||||
time_into_track_ += current_event_.length;
|
||||
if(is_reading_) pll_->add_pulse();
|
||||
break;
|
||||
case Track::Event::IndexHole:
|
||||
printf("%p %d [/%d = %d]\n", this, cycles_since_index_hole_, clock_rate_multiplier_, cycles_since_index_hole_ / clock_rate_multiplier_);
|
||||
cycles_since_index_hole_ = 0;
|
||||
time_into_track_.set_zero();
|
||||
process_index_hole();
|
||||
break;
|
||||
}
|
||||
get_next_event();
|
||||
get_next_event(Time(0));
|
||||
}
|
||||
|
||||
Storage::Time Controller::get_time_into_track()
|
||||
{
|
||||
// this is proportion of a second
|
||||
Time result(cycles_since_index_hole_, 8000000 * clock_rate_multiplier_);
|
||||
result /= rotational_multiplier_;
|
||||
result.simplify();
|
||||
return result;
|
||||
}
|
||||
|
||||
#pragma mark - Writing
|
||||
|
||||
void Controller::begin_writing()
|
||||
{
|
||||
is_reading_ = false;
|
||||
|
||||
write_segment_.length_of_a_bit = bit_length_ * rotational_multiplier_;
|
||||
write_segment_.data.clear();
|
||||
write_segment_.number_of_bits = 0;
|
||||
|
||||
write_start_time_ = get_time_into_track();
|
||||
}
|
||||
|
||||
void Controller::write_bit(bool value)
|
||||
{
|
||||
bool needs_new_byte = !(write_segment_.number_of_bits&7);
|
||||
if(needs_new_byte) write_segment_.data.push_back(0);
|
||||
if(value) write_segment_.data[write_segment_.number_of_bits >> 3] |= 0x80 >> (write_segment_.number_of_bits & 7);
|
||||
write_segment_.number_of_bits++;
|
||||
}
|
||||
|
||||
void Controller::end_writing()
|
||||
{
|
||||
is_reading_ = true;
|
||||
|
||||
if(!patched_track_)
|
||||
{
|
||||
patched_track_.reset(new PCMPatchedTrack(track_));
|
||||
}
|
||||
patched_track_->add_segment(write_start_time_, write_segment_);
|
||||
}
|
||||
|
||||
#pragma mark - PLL control and delegate
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
#include "Drive.hpp"
|
||||
#include "DigitalPhaseLockedLoop.hpp"
|
||||
#include "PCMSegment.hpp"
|
||||
#include "PCMPatchedTrack.hpp"
|
||||
#include "../TimedEventLoop.hpp"
|
||||
|
||||
namespace Storage {
|
||||
@ -59,6 +61,24 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop
|
||||
*/
|
||||
bool get_motor_on();
|
||||
|
||||
/*!
|
||||
Begins write mode, initiating a PCM sampled region of data. Bits should be written via
|
||||
@c write_bit. They will be written with the length set via @c set_expected_bit_length.
|
||||
It is acceptable to supply a backlog of bits. Flux transition events will not be reported
|
||||
while writing.
|
||||
*/
|
||||
void begin_writing();
|
||||
|
||||
/*!
|
||||
Writes the bit @c value as the next in the PCM stream initiated by @c begin_writing.
|
||||
*/
|
||||
void write_bit(bool value);
|
||||
|
||||
/*!
|
||||
Ends write mode, switching back to read mode. The drive will stop overwriting events.
|
||||
*/
|
||||
void end_writing();
|
||||
|
||||
/*!
|
||||
Should be implemented by subclasses; communicates each bit that the PLL recognises, also specifying
|
||||
the amount of time since the index hole was last seen.
|
||||
@ -91,12 +111,18 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop
|
||||
std::shared_ptr<Track> track_;
|
||||
unsigned int cycles_since_index_hole_;
|
||||
|
||||
inline void get_next_event();
|
||||
inline void get_next_event(const Time &duration_already_passed);
|
||||
Track::Event current_event_;
|
||||
Time time_into_track_;
|
||||
bool motor_is_on_;
|
||||
|
||||
bool is_reading_;
|
||||
bool track_is_dirty_;
|
||||
std::shared_ptr<PCMPatchedTrack> patched_track_;
|
||||
PCMSegment write_segment_;
|
||||
Time write_start_time_;
|
||||
|
||||
void setup_track();
|
||||
Time get_time_into_track();
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -39,8 +39,19 @@ void Drive::set_head(unsigned int head)
|
||||
head_ = head;
|
||||
}
|
||||
|
||||
bool Drive::get_is_read_only()
|
||||
{
|
||||
if(disk_) return disk_->get_is_read_only();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<Track> Drive::get_track()
|
||||
{
|
||||
if(disk_) return disk_->get_track_at_position(head_, (unsigned int)head_position_);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Drive::set_track(const std::shared_ptr<Track> &track)
|
||||
{
|
||||
if(disk_) disk_->set_track_at_position(head_, (unsigned int)head_position_, track);
|
||||
}
|
||||
|
@ -35,17 +35,31 @@ class Drive {
|
||||
bool get_is_track_zero();
|
||||
|
||||
/*!
|
||||
Steps the disk head the specified number of tracks. Positive numbers step inwards, negative numbers
|
||||
step outwards.
|
||||
Steps the disk head the specified number of tracks. Positive numbers step inwards (i.e. away from track 0),
|
||||
negative numbers step outwards (i.e. towards track 0).
|
||||
*/
|
||||
void step(int direction);
|
||||
|
||||
/*!
|
||||
Sets the current read head.
|
||||
*/
|
||||
void set_head(unsigned int head);
|
||||
|
||||
/*!
|
||||
@returns @c true if the inserted disk is read-only; @c false otherwise.
|
||||
*/
|
||||
bool get_is_read_only();
|
||||
|
||||
/*!
|
||||
@returns the track underneath the current head at the location now stepped to.
|
||||
*/
|
||||
std::shared_ptr<Track> get_track();
|
||||
|
||||
/*!
|
||||
Attempts to set @c track as the track underneath the current head at the location now stepped to.
|
||||
*/
|
||||
void set_track(const std::shared_ptr<Track> &track);
|
||||
|
||||
private:
|
||||
std::shared_ptr<Disk> disk_;
|
||||
int head_position_;
|
||||
|
219
Storage/Disk/PCMPatchedTrack.cpp
Normal file
219
Storage/Disk/PCMPatchedTrack.cpp
Normal file
@ -0,0 +1,219 @@
|
||||
//
|
||||
// PCMPatchedTrack.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 15/12/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "PCMPatchedTrack.hpp"
|
||||
|
||||
using namespace Storage::Disk;
|
||||
|
||||
PCMPatchedTrack::PCMPatchedTrack(std::shared_ptr<Track> underlying_track) :
|
||||
underlying_track_(underlying_track)
|
||||
{
|
||||
const Time zero(0);
|
||||
const Time one(1);
|
||||
periods_.emplace_back(zero, one, zero, nullptr);
|
||||
active_period_ = periods_.begin();
|
||||
underlying_track_->seek_to(zero);
|
||||
}
|
||||
|
||||
void PCMPatchedTrack::add_segment(const Time &start_time, const PCMSegment &segment)
|
||||
{
|
||||
std::shared_ptr<PCMSegmentEventSource> event_source(new PCMSegmentEventSource(segment));
|
||||
|
||||
Time zero(0);
|
||||
Time end_time = start_time + event_source->get_length();
|
||||
Period insertion_period(start_time, end_time, zero, event_source);
|
||||
|
||||
// the new segment may wrap around, so divide it up into track-length parts if required
|
||||
Time one = Time(1);
|
||||
while(insertion_period.end_time > one)
|
||||
{
|
||||
Time next_end_time = insertion_period.end_time - one;
|
||||
insertion_period.end_time = one;
|
||||
insert_period(insertion_period);
|
||||
|
||||
insertion_period.start_time = zero;
|
||||
insertion_period.end_time = next_end_time;
|
||||
}
|
||||
insert_period(insertion_period);
|
||||
|
||||
// the vector may have been resized, potentially invalidating active_period_ even if
|
||||
// the thing it pointed to is still the same thing. So work it out afresh.
|
||||
active_period_ = periods_.begin();
|
||||
while(active_period_->start_time > current_time_) active_period_++;
|
||||
}
|
||||
|
||||
void PCMPatchedTrack::insert_period(const Period &period)
|
||||
{
|
||||
// find the existing period that the new period starts in
|
||||
std::vector<Period>::iterator start_period = periods_.begin();
|
||||
while(start_period->end_time <= period.start_time) start_period++;
|
||||
|
||||
// find the existing period that the new period end in
|
||||
std::vector<Period>::iterator end_period = start_period;
|
||||
while(end_period->end_time < period.end_time) end_period++;
|
||||
|
||||
// perform a division if called for
|
||||
if(start_period == end_period)
|
||||
{
|
||||
if(start_period->start_time == period.start_time)
|
||||
{
|
||||
if(start_period->end_time == period.end_time)
|
||||
{
|
||||
// period has the same start and end time as start_period. So just replace it.
|
||||
*start_period = period;
|
||||
}
|
||||
else
|
||||
{
|
||||
// period has the same start time as start_period but a different end time.
|
||||
// So trim the left-hand side of start_period and insert the new period in front.
|
||||
start_period->push_start_to_time(period.end_time);
|
||||
periods_.insert(start_period, period);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(start_period->end_time == period.end_time)
|
||||
{
|
||||
// period has the same end time as start_period but a different start time.
|
||||
// So trim the right-hand side of start_period and insert the new period afterwards
|
||||
start_period->trim_end_to_time(period.start_time);
|
||||
periods_.insert(start_period + 1, period);
|
||||
}
|
||||
else
|
||||
{
|
||||
// start_period has an earlier start and a later end than period. So copy it,
|
||||
// trim the right off the original and the left off the copy, then insert the
|
||||
// new period and the copy after start_period
|
||||
Period right_period = *start_period;
|
||||
|
||||
right_period.push_start_to_time(period.end_time);
|
||||
start_period->trim_end_to_time(period.start_time);
|
||||
|
||||
// the iterator isn't guaranteed to survive the insert, e.g. if it causes a resize
|
||||
std::vector<Period>::difference_type offset = start_period - periods_.begin();
|
||||
periods_.insert(start_period + 1, period);
|
||||
periods_.insert(periods_.begin() + offset + 2, right_period);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bool should_insert = false;
|
||||
std::vector<Period>::difference_type insertion_offset = 0;
|
||||
|
||||
if(start_period->start_time == period.start_time)
|
||||
{
|
||||
// start_period starts at the same place as period. Period then
|
||||
// ends after start_period. So replace.
|
||||
*start_period = period;
|
||||
should_insert = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// start_period starts before period. So trim and plan to insert afterwards.
|
||||
start_period->trim_end_to_time(period.start_time);
|
||||
should_insert = true;
|
||||
insertion_offset = start_period + 1 - periods_.begin();
|
||||
}
|
||||
|
||||
if(end_period->end_time == period.end_time)
|
||||
{
|
||||
// end_period ends exactly when period does. So include it from the list to delete
|
||||
end_period++;
|
||||
}
|
||||
else
|
||||
{
|
||||
end_period->push_start_to_time(period.end_time);
|
||||
}
|
||||
|
||||
// remove everything that is exiting in between
|
||||
periods_.erase(start_period + 1, end_period);
|
||||
|
||||
// insert the new period if required
|
||||
if(should_insert)
|
||||
periods_.insert(periods_.begin()+insertion_offset, period);
|
||||
}
|
||||
}
|
||||
|
||||
Track::Event PCMPatchedTrack::get_next_event()
|
||||
{
|
||||
const Time one(1);
|
||||
const Time zero(0);
|
||||
Time extra_time(0);
|
||||
Time period_error(0);
|
||||
|
||||
while(1)
|
||||
{
|
||||
// get the next event from the current active period
|
||||
Track::Event event;
|
||||
if(active_period_->event_source) event = active_period_->event_source->get_next_event();
|
||||
else event = underlying_track_->get_next_event();
|
||||
|
||||
// see what time that gets us to. If it's still within the current period, return the found event
|
||||
Time event_time = current_time_ + event.length - period_error;
|
||||
if(event_time < active_period_->end_time)
|
||||
{
|
||||
current_time_ = event_time;
|
||||
event.length += extra_time - period_error;
|
||||
return event;
|
||||
}
|
||||
|
||||
// otherwise move time back to the end of the outgoing period, accumulating the error into
|
||||
// extra_time, and advance the extra period
|
||||
extra_time += (active_period_->end_time - current_time_);
|
||||
current_time_ = active_period_->end_time;
|
||||
active_period_++;
|
||||
|
||||
// test for having reached the end of the track
|
||||
if(active_period_ == periods_.end())
|
||||
{
|
||||
// if this is the end of the track then jump the active pointer back to the beginning
|
||||
// of the list of periods and reset current_time_ to zero
|
||||
active_period_ = periods_.begin();
|
||||
if(active_period_->event_source) active_period_->event_source->reset();
|
||||
else underlying_track_->seek_to(zero);
|
||||
current_time_ = zero;
|
||||
|
||||
// then return an index hole that is the aggregation of accumulated extra_time away
|
||||
event.type = Storage::Disk::Track::Event::IndexHole;
|
||||
event.length = extra_time;
|
||||
return event;
|
||||
}
|
||||
else
|
||||
{
|
||||
// if this is not the end of the track then move to the next period and note how much will need
|
||||
// to be subtracted if an event is found here
|
||||
if(active_period_->event_source) period_error = active_period_->segment_start_time - active_period_->event_source->seek_to(active_period_->segment_start_time);
|
||||
else period_error = current_time_ - underlying_track_->seek_to(current_time_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Storage::Time PCMPatchedTrack::seek_to(const Time &time_since_index_hole)
|
||||
{
|
||||
// start at the beginning and continue while segments start after the time sought
|
||||
active_period_ = periods_.begin();
|
||||
while(active_period_->start_time > time_since_index_hole) active_period_++;
|
||||
|
||||
// allow whatever storage represents the period found to perform its seek
|
||||
if(active_period_->event_source)
|
||||
return active_period_->event_source->seek_to(time_since_index_hole - active_period_->start_time) + active_period_->start_time;
|
||||
else
|
||||
return underlying_track_->seek_to(time_since_index_hole);
|
||||
}
|
||||
|
||||
void PCMPatchedTrack::Period::push_start_to_time(const Storage::Time &new_start_time)
|
||||
{
|
||||
segment_start_time += new_start_time - start_time;
|
||||
start_time = new_start_time;
|
||||
}
|
||||
|
||||
void PCMPatchedTrack::Period::trim_end_to_time(const Storage::Time &new_end_time)
|
||||
{
|
||||
end_time = new_end_time;
|
||||
}
|
63
Storage/Disk/PCMPatchedTrack.hpp
Normal file
63
Storage/Disk/PCMPatchedTrack.hpp
Normal file
@ -0,0 +1,63 @@
|
||||
//
|
||||
// PCMPatchedTrack.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 15/12/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef PCMPatchedTrack_hpp
|
||||
#define PCMPatchedTrack_hpp
|
||||
|
||||
#include "PCMTrack.hpp"
|
||||
#include "PCMSegment.hpp"
|
||||
|
||||
namespace Storage {
|
||||
namespace Disk {
|
||||
|
||||
/*!
|
||||
A subclass of @c Track that patches an existing track with PCM segments.
|
||||
*/
|
||||
class PCMPatchedTrack: public Track {
|
||||
public:
|
||||
/*!
|
||||
Constructs a @c PCMPatchedTrack that will return events from @c underlying_track in
|
||||
regions where it has not had alternative PCM data installed.
|
||||
*/
|
||||
PCMPatchedTrack(std::shared_ptr<Track> underlying_track);
|
||||
|
||||
/*!
|
||||
Replaces whatever is currently on the track from @c start_position to @c start_position + segment length
|
||||
with the contents of @c segment.
|
||||
*/
|
||||
void add_segment(const Time &start_time, const PCMSegment &segment);
|
||||
|
||||
// To satisfy Storage::Disk::Track
|
||||
Event get_next_event();
|
||||
Time seek_to(const Time &time_since_index_hole);
|
||||
|
||||
private:
|
||||
std::shared_ptr<Track> underlying_track_;
|
||||
|
||||
struct Period {
|
||||
Time start_time, end_time;
|
||||
Time segment_start_time;
|
||||
std::shared_ptr<PCMSegmentEventSource> event_source; // nullptr => use the underlying track
|
||||
|
||||
void push_start_to_time(const Storage::Time &new_start_time);
|
||||
void trim_end_to_time(const Storage::Time &new_end_time);
|
||||
|
||||
Period(const Time &start_time, const Time &end_time, const Time &segment_start_time, std::shared_ptr<PCMSegmentEventSource> event_source) :
|
||||
start_time(start_time), end_time(end_time), segment_start_time(segment_start_time), event_source(event_source) {}
|
||||
};
|
||||
std::vector<Period> periods_;
|
||||
std::vector<Period>::iterator active_period_;
|
||||
Time current_time_;
|
||||
|
||||
void insert_period(const Period &period);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* PCMPatchedTrack_hpp */
|
114
Storage/Disk/PCMSegment.cpp
Normal file
114
Storage/Disk/PCMSegment.cpp
Normal file
@ -0,0 +1,114 @@
|
||||
//
|
||||
// PCMSegment.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 17/12/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "PCMSegment.hpp"
|
||||
|
||||
using namespace Storage::Disk;
|
||||
|
||||
PCMSegmentEventSource::PCMSegmentEventSource(const PCMSegment &segment) :
|
||||
segment_(segment)
|
||||
{
|
||||
// add an extra bit of storage at the bottom if one is going to be needed;
|
||||
// events returned are going to be in integral multiples of the length of a bit
|
||||
// other than the very first and very last which will include a half bit length
|
||||
if(segment_.length_of_a_bit.length&1)
|
||||
{
|
||||
segment_.length_of_a_bit.length <<= 1;
|
||||
segment_.length_of_a_bit.clock_rate <<= 1;
|
||||
}
|
||||
|
||||
// load up the clock rate once only
|
||||
next_event_.length.clock_rate = segment_.length_of_a_bit.clock_rate;
|
||||
|
||||
// set initial conditions
|
||||
reset();
|
||||
}
|
||||
|
||||
void PCMSegmentEventSource::reset()
|
||||
{
|
||||
// start with the first bit to be considered the zeroth, and assume that it'll be
|
||||
// flux transitions for the foreseeable
|
||||
bit_pointer_ = 0;
|
||||
next_event_.type = Track::Event::FluxTransition;
|
||||
}
|
||||
|
||||
Storage::Disk::Track::Event PCMSegmentEventSource::get_next_event()
|
||||
{
|
||||
// track the initial bit pointer for potentially considering whether this was an
|
||||
// initial index hole or a subsequent one later on
|
||||
size_t initial_bit_pointer = bit_pointer_;
|
||||
|
||||
// if starting from the beginning, pull half a bit backward, as if the initial bit
|
||||
// is set, it should be in the centre of its window
|
||||
next_event_.length.length = bit_pointer_ ? 0 : -(segment_.length_of_a_bit.length >> 1);
|
||||
|
||||
// search for the next bit that is set, if any
|
||||
const uint8_t *segment_data = segment_.data.data();
|
||||
while(bit_pointer_ < segment_.number_of_bits)
|
||||
{
|
||||
int bit = segment_data[bit_pointer_ >> 3] & (0x80 >> (bit_pointer_&7));
|
||||
bit_pointer_++; // so this always points one beyond the most recent bit returned
|
||||
next_event_.length.length += segment_.length_of_a_bit.length;
|
||||
|
||||
// if this bit is set, return the event
|
||||
if(bit) return next_event_;
|
||||
}
|
||||
|
||||
// if the end is reached without a bit being set, it'll be index holes from now on
|
||||
next_event_.type = Track::Event::IndexHole;
|
||||
|
||||
// test whether this is the very first time that bits have been exhausted. If so then
|
||||
// allow an extra half bit's length to run from the position of the potential final transition
|
||||
// event to the end of the segment. Otherwise don't allow any extra time, as it's already
|
||||
// been consumed
|
||||
if(initial_bit_pointer <= segment_.number_of_bits)
|
||||
{
|
||||
next_event_.length.length += (segment_.length_of_a_bit.length >> 1);
|
||||
bit_pointer_++;
|
||||
}
|
||||
return next_event_;
|
||||
}
|
||||
|
||||
Storage::Time PCMSegmentEventSource::get_length()
|
||||
{
|
||||
return segment_.length_of_a_bit * segment_.number_of_bits;
|
||||
}
|
||||
|
||||
Storage::Time PCMSegmentEventSource::seek_to(const Time &time_from_start)
|
||||
{
|
||||
// test for requested time being beyond the end
|
||||
Time length = get_length();
|
||||
if(time_from_start >= length)
|
||||
{
|
||||
next_event_.type = Track::Event::IndexHole;
|
||||
bit_pointer_ = segment_.number_of_bits+1;
|
||||
return length;
|
||||
}
|
||||
|
||||
// if not beyond the end then make an initial assumption that the next thing encountered will be a flux transition
|
||||
next_event_.type = Track::Event::FluxTransition;
|
||||
|
||||
// test for requested time being before the first bit
|
||||
Time half_bit_length = segment_.length_of_a_bit;
|
||||
half_bit_length.length >>= 1;
|
||||
if(time_from_start < half_bit_length)
|
||||
{
|
||||
bit_pointer_ = 0;
|
||||
Storage::Time zero;
|
||||
return zero;
|
||||
}
|
||||
|
||||
// adjust for time to get to bit zero and determine number of bits in;
|
||||
// bit_pointer_ always records _the next bit_ that might trigger an event,
|
||||
// so should be one beyond the one reached by a seek.
|
||||
Time relative_time = time_from_start - half_bit_length;
|
||||
bit_pointer_ = 1 + (relative_time / segment_.length_of_a_bit).get_unsigned_int();
|
||||
|
||||
// map up to the correct amount of time
|
||||
return half_bit_length + segment_.length_of_a_bit * (unsigned int)(bit_pointer_ - 1);
|
||||
}
|
80
Storage/Disk/PCMSegment.hpp
Normal file
80
Storage/Disk/PCMSegment.hpp
Normal file
@ -0,0 +1,80 @@
|
||||
//
|
||||
// PCMSegment.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 17/12/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef PCMSegment_hpp
|
||||
#define PCMSegment_hpp
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "../Storage.hpp"
|
||||
#include "Disk.hpp"
|
||||
|
||||
namespace Storage {
|
||||
namespace Disk {
|
||||
|
||||
/*!
|
||||
A segment of PCM-sampled data.
|
||||
|
||||
Bits from each byte are taken MSB to LSB.
|
||||
*/
|
||||
struct PCMSegment {
|
||||
Time length_of_a_bit;
|
||||
unsigned int number_of_bits;
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
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() {}
|
||||
};
|
||||
|
||||
/*!
|
||||
Provides a stream of events by inspecting a PCMSegment.
|
||||
*/
|
||||
class PCMSegmentEventSource {
|
||||
public:
|
||||
/*!
|
||||
Constructs a @c PCMSegmentEventSource that will derive events from @c segment.
|
||||
The event source is initially @c reset.
|
||||
*/
|
||||
PCMSegmentEventSource(const PCMSegment &segment);
|
||||
|
||||
/*!
|
||||
@returns the next event that will occur in this event stream.
|
||||
*/
|
||||
Track::Event get_next_event();
|
||||
|
||||
/*!
|
||||
Resets the event source to the beginning of its event stream, exactly as if
|
||||
it has just been constructed.
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/*!
|
||||
Seeks as close to @c time_from_start as the event source can manage while not
|
||||
exceeding it.
|
||||
|
||||
@returns the time the source is now at.
|
||||
*/
|
||||
Time seek_to(const Time &time_from_start);
|
||||
|
||||
/*!
|
||||
@returns the total length of the stream of data that the source will provide.
|
||||
*/
|
||||
Time get_length();
|
||||
|
||||
private:
|
||||
PCMSegment segment_;
|
||||
size_t bit_pointer_;
|
||||
Track::Event next_event_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* PCMSegment_hpp */
|
@ -11,111 +11,105 @@
|
||||
|
||||
using namespace Storage::Disk;
|
||||
|
||||
PCMTrack::PCMTrack(std::vector<PCMSegment> segments)
|
||||
{
|
||||
segments_ = std::move(segments);
|
||||
fix_length();
|
||||
}
|
||||
PCMTrack::PCMTrack() : segment_pointer_(0)
|
||||
{}
|
||||
|
||||
PCMTrack::PCMTrack(PCMSegment segment)
|
||||
PCMTrack::PCMTrack(const std::vector<PCMSegment> &segments) : PCMTrack()
|
||||
{
|
||||
segment.length_of_a_bit.length = 1;
|
||||
segment.length_of_a_bit.clock_rate = 1;
|
||||
segments_.push_back(std::move(segment));
|
||||
fix_length();
|
||||
}
|
||||
|
||||
PCMTrack::Event PCMTrack::get_next_event()
|
||||
{
|
||||
// find the next 1 in the input stream, keeping count of length as we go, and assuming it's going
|
||||
// to be a flux transition
|
||||
next_event_.type = Track::Event::FluxTransition;
|
||||
next_event_.length.length = 0;
|
||||
while(segment_pointer_ < segments_.size())
|
||||
// sum total length of all segments
|
||||
Time total_length;
|
||||
for(auto segment : segments)
|
||||
{
|
||||
unsigned int clock_multiplier = track_clock_rate_ / segments_[segment_pointer_].length_of_a_bit.clock_rate;
|
||||
unsigned int bit_length = clock_multiplier * segments_[segment_pointer_].length_of_a_bit.length;
|
||||
total_length += segment.length_of_a_bit * segment.number_of_bits;
|
||||
}
|
||||
total_length.simplify();
|
||||
|
||||
const uint8_t *segment_data = &segments_[segment_pointer_].data[0];
|
||||
while(bit_pointer_ < segments_[segment_pointer_].number_of_bits)
|
||||
// each segment is then some proportion of the total; for them all to sum to 1 they'll
|
||||
// need to be adjusted to be
|
||||
for(auto segment : segments)
|
||||
{
|
||||
Time original_length_of_segment = segment.length_of_a_bit * segment.number_of_bits;
|
||||
Time proportion_of_whole = original_length_of_segment / total_length;
|
||||
proportion_of_whole.simplify();
|
||||
PCMSegment length_adjusted_segment = segment;
|
||||
length_adjusted_segment.length_of_a_bit = proportion_of_whole / segment.number_of_bits;
|
||||
length_adjusted_segment.length_of_a_bit.simplify();
|
||||
segment_event_sources_.emplace_back(length_adjusted_segment);
|
||||
}
|
||||
}
|
||||
|
||||
PCMTrack::PCMTrack(const PCMSegment &segment) : PCMTrack()
|
||||
{
|
||||
// a single segment necessarily fills the track
|
||||
PCMSegment length_adjusted_segment = segment;
|
||||
length_adjusted_segment.length_of_a_bit.length = 1;
|
||||
length_adjusted_segment.length_of_a_bit.clock_rate = segment.number_of_bits;
|
||||
segment_event_sources_.emplace_back(length_adjusted_segment);
|
||||
}
|
||||
|
||||
Track::Event PCMTrack::get_next_event()
|
||||
{
|
||||
// ask the current segment for a new event
|
||||
Track::Event event = segment_event_sources_[segment_pointer_].get_next_event();
|
||||
|
||||
// if it was a flux transition, that's code for end-of-segment, so dig deeper
|
||||
if(event.type == Track::Event::IndexHole)
|
||||
{
|
||||
// multiple segments may be crossed, so start summing lengths in case the net
|
||||
// effect is an index hole
|
||||
Time total_length = event.length;
|
||||
|
||||
// continue until somewhere no returning an index hole
|
||||
while(event.type == Track::Event::IndexHole)
|
||||
{
|
||||
// for timing simplicity, bits are modelled as happening at the end of their window
|
||||
// TODO: should I account for the converse bit ordering? Or can I assume MSB first?
|
||||
int bit = segment_data[bit_pointer_ >> 3] & (0x80 >> (bit_pointer_&7));
|
||||
bit_pointer_++;
|
||||
next_event_.length.length += bit_length;
|
||||
// advance to the [start of] the next segment
|
||||
segment_pointer_ = (segment_pointer_ + 1) % segment_event_sources_.size();
|
||||
segment_event_sources_[segment_pointer_].reset();
|
||||
|
||||
if(bit) return next_event_;
|
||||
// if this is all the way back to the start, that's a genuine index hole,
|
||||
// so set the summed length and return
|
||||
if(!segment_pointer_)
|
||||
{
|
||||
return event;
|
||||
}
|
||||
|
||||
// otherwise get the next event (if it's not another index hole, the loop will end momentarily),
|
||||
// summing in any prior accumulated time
|
||||
event = segment_event_sources_[segment_pointer_].get_next_event();
|
||||
total_length += event.length;
|
||||
event.length = total_length;
|
||||
}
|
||||
bit_pointer_ = 0;
|
||||
segment_pointer_++;
|
||||
}
|
||||
|
||||
// check whether we actually reached the index hole
|
||||
if(segment_pointer_ == segments_.size())
|
||||
{
|
||||
segment_pointer_ = 0;
|
||||
next_event_.type = Track::Event::IndexHole;
|
||||
}
|
||||
|
||||
return next_event_;
|
||||
return event;
|
||||
}
|
||||
|
||||
Storage::Time PCMTrack::seek_to(Time time_since_index_hole)
|
||||
Storage::Time PCMTrack::seek_to(const Time &time_since_index_hole)
|
||||
{
|
||||
// initial condition: no time yet accumulated, the whole thing requested yet to navigate
|
||||
Storage::Time accumulated_time;
|
||||
Storage::Time time_left_to_seek = time_since_index_hole;
|
||||
|
||||
// search from the first segment
|
||||
segment_pointer_ = 0;
|
||||
|
||||
// pick a common clock rate for counting time on this track and multiply up the time being sought appropriately
|
||||
Time time_so_far;
|
||||
time_so_far.clock_rate = NumberTheory::least_common_multiple(next_event_.length.clock_rate, time_since_index_hole.clock_rate);
|
||||
time_since_index_hole.length *= time_so_far.clock_rate / time_since_index_hole.clock_rate;
|
||||
time_since_index_hole.clock_rate = time_so_far.clock_rate;
|
||||
|
||||
while(segment_pointer_ < segments_.size())
|
||||
do
|
||||
{
|
||||
// determine how long this segment is in terms of the master clock
|
||||
unsigned int clock_multiplier = time_so_far.clock_rate / next_event_.length.clock_rate;
|
||||
unsigned int bit_length = ((clock_multiplier / track_clock_rate_) / segments_[segment_pointer_].length_of_a_bit.clock_rate) * segments_[segment_pointer_].length_of_a_bit.length;
|
||||
unsigned int time_in_this_segment = bit_length * segments_[segment_pointer_].number_of_bits;
|
||||
|
||||
// if this segment goes on longer than the time being sought, end here
|
||||
unsigned int time_remaining = time_since_index_hole.length - time_so_far.length;
|
||||
if(time_in_this_segment >= time_remaining)
|
||||
// if this segment extends beyond the amount of time left to seek, trust it to complete
|
||||
// the seek
|
||||
Storage::Time segment_time = segment_event_sources_[segment_pointer_].get_length();
|
||||
if(segment_time > time_left_to_seek)
|
||||
{
|
||||
// get the amount of time actually to move into this segment
|
||||
unsigned int time_found = time_remaining - (time_remaining % bit_length);
|
||||
|
||||
// resolve that into the stateful bit count
|
||||
bit_pointer_ = 1 + (time_remaining / bit_length);
|
||||
|
||||
// update and return the time sought to
|
||||
time_so_far.length += time_found;
|
||||
return time_so_far;
|
||||
return accumulated_time + segment_event_sources_[segment_pointer_].seek_to(time_left_to_seek);
|
||||
}
|
||||
|
||||
// otherwise, accumulate time and keep moving
|
||||
time_so_far.length += time_in_this_segment;
|
||||
segment_pointer_++;
|
||||
// otherwise swallow this segment, updating the time left to seek and time so far accumulated
|
||||
time_left_to_seek -= segment_time;
|
||||
accumulated_time += segment_time;
|
||||
segment_pointer_ = (segment_pointer_ + 1) % segment_event_sources_.size();
|
||||
}
|
||||
return time_since_index_hole;
|
||||
}
|
||||
|
||||
void PCMTrack::fix_length()
|
||||
{
|
||||
// find the least common multiple of all segment clock rates
|
||||
track_clock_rate_ = segments_[0].length_of_a_bit.clock_rate;
|
||||
for(size_t c = 1; c < segments_.size(); c++)
|
||||
{
|
||||
track_clock_rate_ = NumberTheory::least_common_multiple(track_clock_rate_, segments_[c].length_of_a_bit.clock_rate);
|
||||
}
|
||||
|
||||
// thereby determine the total length, storing it to next_event as the track-total divisor
|
||||
next_event_.length.clock_rate = 0;
|
||||
for(size_t c = 0; c < segments_.size(); c++)
|
||||
{
|
||||
unsigned int multiplier = track_clock_rate_ / segments_[c].length_of_a_bit.clock_rate;
|
||||
next_event_.length.clock_rate += segments_[c].length_of_a_bit.length * segments_[c].number_of_bits * multiplier;
|
||||
}
|
||||
|
||||
segment_pointer_ = bit_pointer_ = 0;
|
||||
while(segment_pointer_);
|
||||
|
||||
// if all segments have now been swallowed, the closest we can get is the very end of
|
||||
// the list of segments
|
||||
return accumulated_time;
|
||||
}
|
||||
|
@ -10,22 +10,12 @@
|
||||
#define PCMTrack_hpp
|
||||
|
||||
#include "Disk.hpp"
|
||||
#include "PCMSegment.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace Storage {
|
||||
namespace Disk {
|
||||
|
||||
/*!
|
||||
A segment of PCM-sampled data.
|
||||
|
||||
Bits from each byte are taken MSB to LSB.
|
||||
*/
|
||||
struct PCMSegment {
|
||||
Time length_of_a_bit;
|
||||
unsigned int number_of_bits;
|
||||
std::vector<uint8_t> data;
|
||||
};
|
||||
|
||||
/*!
|
||||
A subclass of @c Track that provides its @c Events by querying a pulse-code modulated record of original
|
||||
flux detections, with an implied index hole at the very start of the data.
|
||||
@ -38,35 +28,26 @@ class PCMTrack: public Track {
|
||||
/*!
|
||||
Creates a @c PCMTrack consisting of multiple segments of data, permitting multiple clock rates.
|
||||
*/
|
||||
PCMTrack(std::vector<PCMSegment> segments);
|
||||
PCMTrack(const std::vector<PCMSegment> &segments);
|
||||
|
||||
/*!
|
||||
Creates a @c PCMTrack consisting of a single continuous run of data, implying a constant clock rate.
|
||||
The segment's @c length_of_a_bit will be ignored and therefore need not be filled in.
|
||||
*/
|
||||
PCMTrack(PCMSegment segment);
|
||||
PCMTrack(const PCMSegment &segment);
|
||||
|
||||
// as per @c Track
|
||||
Event get_next_event();
|
||||
Time seek_to(Time time_since_index_hole);
|
||||
Time seek_to(const Time &time_since_index_hole);
|
||||
|
||||
private:
|
||||
// storage for the segments that describe this track
|
||||
std::vector<PCMSegment> segments_;
|
||||
|
||||
// a helper to determine the overall track clock rate and it's length
|
||||
void fix_length();
|
||||
|
||||
// the event perpetually returned; impliedly contains the length of the entire track
|
||||
// as its clock rate, per the need for everything on a Track to sum to a length of 1
|
||||
PCMTrack::Event next_event_;
|
||||
|
||||
// contains the master clock rate
|
||||
unsigned int track_clock_rate_;
|
||||
std::vector<PCMSegmentEventSource> segment_event_sources_;
|
||||
|
||||
// a pointer to the first bit to consider as the next event
|
||||
size_t segment_pointer_;
|
||||
size_t bit_pointer_;
|
||||
|
||||
PCMTrack();
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -10,6 +10,9 @@
|
||||
#define Storage_hpp
|
||||
|
||||
#include "../NumberTheory/Factors.hpp"
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
namespace Storage {
|
||||
|
||||
@ -20,91 +23,157 @@ namespace Storage {
|
||||
struct Time {
|
||||
unsigned int length, clock_rate;
|
||||
Time() : length(0), clock_rate(1) {}
|
||||
Time(unsigned int unsigned_int_value) : length(unsigned_int_value), clock_rate(1) {}
|
||||
Time(int int_value) : Time((unsigned int)int_value) {}
|
||||
Time(unsigned int length, unsigned int clock_rate) : length(length), clock_rate(clock_rate) { simplify(); }
|
||||
Time(int length, int clock_rate) : Time((unsigned int)length, (unsigned int)clock_rate) {}
|
||||
Time(uint64_t length, uint64_t clock_rate)
|
||||
{
|
||||
install_result(length, clock_rate);
|
||||
simplify();
|
||||
}
|
||||
Time(float value)
|
||||
{
|
||||
install_float(value);
|
||||
simplify();
|
||||
}
|
||||
|
||||
/*!
|
||||
Reduces this @c Time to its simplest form — eliminates all common factors from @c length
|
||||
and @c clock_rate.
|
||||
*/
|
||||
inline Time &simplify()
|
||||
void simplify()
|
||||
{
|
||||
unsigned int common_divisor = NumberTheory::greatest_common_divisor(length, clock_rate);
|
||||
length /= common_divisor;
|
||||
clock_rate /= common_divisor;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns the floating point conversion of this @c Time. This will often be less precise.
|
||||
*/
|
||||
inline float get_float()
|
||||
inline float get_float() const
|
||||
{
|
||||
return (float)length / (float)clock_rate;
|
||||
}
|
||||
|
||||
inline bool operator < (Time other)
|
||||
inline unsigned int get_unsigned_int() const
|
||||
{
|
||||
return other.clock_rate * length < clock_rate * other.length;
|
||||
return length / clock_rate;
|
||||
}
|
||||
|
||||
inline Time operator + (Time other)
|
||||
inline bool operator < (const Time &other) const
|
||||
{
|
||||
Time result;
|
||||
result.clock_rate = NumberTheory::least_common_multiple(clock_rate, other.clock_rate);
|
||||
result.length = length * (result.clock_rate / clock_rate) + other.length * (result.clock_rate / other.clock_rate);
|
||||
return result;
|
||||
return (uint64_t)other.clock_rate * (uint64_t)length < (uint64_t)clock_rate * (uint64_t)other.length;
|
||||
}
|
||||
|
||||
inline Time &operator += (Time other)
|
||||
inline bool operator <= (const Time &other) const
|
||||
{
|
||||
unsigned int combined_clock_rate = NumberTheory::least_common_multiple(clock_rate, other.clock_rate);
|
||||
length = length * (combined_clock_rate / clock_rate) + other.length * (combined_clock_rate / other.clock_rate);
|
||||
clock_rate = combined_clock_rate;
|
||||
return (uint64_t)other.clock_rate * (uint64_t)length <= (uint64_t)clock_rate * (uint64_t)other.length;
|
||||
}
|
||||
|
||||
inline bool operator > (const Time &other) const
|
||||
{
|
||||
return (uint64_t)other.clock_rate * (uint64_t)length > (uint64_t)clock_rate * (uint64_t)other.length;
|
||||
}
|
||||
|
||||
inline bool operator >= (const Time &other) const
|
||||
{
|
||||
return (uint64_t)other.clock_rate * (uint64_t)length >= (uint64_t)clock_rate * (uint64_t)other.length;
|
||||
}
|
||||
|
||||
inline bool operator == (const Time &other) const
|
||||
{
|
||||
return (uint64_t)other.clock_rate * (uint64_t)length == (uint64_t)clock_rate * (uint64_t)other.length;
|
||||
}
|
||||
|
||||
inline Time operator + (const Time &other) const
|
||||
{
|
||||
uint64_t result_length = (uint64_t)length * (uint64_t)other.clock_rate + (uint64_t)other.length * (uint64_t)clock_rate;
|
||||
uint64_t result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate;
|
||||
return Time(result_length, result_clock_rate);
|
||||
}
|
||||
|
||||
inline Time &operator += (const Time &other)
|
||||
{
|
||||
uint64_t result_length = (uint64_t)length * (uint64_t)other.clock_rate + (uint64_t)other.length * (uint64_t)clock_rate;
|
||||
uint64_t result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate;
|
||||
install_result(result_length, result_clock_rate);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Time operator - (Time other)
|
||||
inline Time operator - (const Time &other) const
|
||||
{
|
||||
Time result;
|
||||
result.clock_rate = NumberTheory::least_common_multiple(clock_rate, other.clock_rate);
|
||||
result.length = length * (result.clock_rate / clock_rate) - other.length * (result.clock_rate / other.clock_rate);
|
||||
return result;
|
||||
uint64_t result_length = (uint64_t)length * (uint64_t)other.clock_rate - (uint64_t)other.length * (uint64_t)clock_rate;
|
||||
uint64_t result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate;
|
||||
return Time(result_length, result_clock_rate);
|
||||
}
|
||||
|
||||
inline Time operator -= (Time other)
|
||||
inline Time operator -= (const Time &other)
|
||||
{
|
||||
unsigned int combined_clock_rate = NumberTheory::least_common_multiple(clock_rate, other.clock_rate);
|
||||
length = length * (combined_clock_rate / clock_rate) - other.length * (combined_clock_rate / other.clock_rate);
|
||||
clock_rate = combined_clock_rate;
|
||||
uint64_t result_length = (uint64_t)length * (uint64_t)other.clock_rate - (uint64_t)other.length * (uint64_t)clock_rate;
|
||||
uint64_t result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate;
|
||||
install_result(result_length, result_clock_rate);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Time operator * (Time other)
|
||||
inline Time operator * (const Time &other) const
|
||||
{
|
||||
Time result;
|
||||
result.clock_rate = clock_rate * other.clock_rate;
|
||||
result.length = length * other.length;
|
||||
return result;
|
||||
uint64_t result_length = (uint64_t)length * (uint64_t)other.length;
|
||||
uint64_t result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate;
|
||||
return Time(result_length, result_clock_rate);
|
||||
}
|
||||
|
||||
inline Time &operator *= (Time other)
|
||||
inline Time &operator *= (const Time &other)
|
||||
{
|
||||
length *= other.length;
|
||||
clock_rate *= other.clock_rate;
|
||||
uint64_t result_length = (uint64_t)length * (uint64_t)other.length;
|
||||
uint64_t result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate;
|
||||
install_result(result_length, result_clock_rate);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Time operator / (Time other)
|
||||
inline Time operator * (unsigned int multiplier) const
|
||||
{
|
||||
Time result;
|
||||
result.clock_rate = clock_rate * other.length;
|
||||
result.length = length * other.clock_rate;
|
||||
return result;
|
||||
uint64_t result_length = (uint64_t)length * (uint64_t)multiplier;
|
||||
uint64_t result_clock_rate = (uint64_t)clock_rate;
|
||||
return Time(result_length, result_clock_rate);
|
||||
}
|
||||
|
||||
inline Time &operator /= (Time other)
|
||||
inline Time &operator *= (unsigned int multiplier)
|
||||
{
|
||||
length *= other.clock_rate;
|
||||
clock_rate *= other.length;
|
||||
uint64_t result_length = (uint64_t)length * (uint64_t)multiplier;
|
||||
uint64_t result_clock_rate = (uint64_t)clock_rate;
|
||||
install_result(result_length, result_clock_rate);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Time operator / (const Time &other) const
|
||||
{
|
||||
uint64_t result_length = (uint64_t)length * (uint64_t)other.clock_rate;
|
||||
uint64_t result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.length;
|
||||
return Time(result_length, result_clock_rate);
|
||||
}
|
||||
|
||||
inline Time &operator /= (const Time &other)
|
||||
{
|
||||
uint64_t result_length = (uint64_t)length * (uint64_t)other.clock_rate;
|
||||
uint64_t result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.length;
|
||||
install_result(result_length, result_clock_rate);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Time operator / (unsigned int divisor) const
|
||||
{
|
||||
uint64_t result_length = (uint64_t)length;
|
||||
uint64_t result_clock_rate = (uint64_t)clock_rate * (uint64_t)divisor;
|
||||
return Time(result_length, result_clock_rate);
|
||||
}
|
||||
|
||||
inline Time &operator /= (unsigned int divisor)
|
||||
{
|
||||
uint64_t result_length = (uint64_t)length;
|
||||
uint64_t result_clock_rate = (uint64_t)clock_rate * (uint64_t)divisor;
|
||||
install_result(result_length, result_clock_rate);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -119,6 +188,80 @@ struct Time {
|
||||
length = 1;
|
||||
clock_rate = 1;
|
||||
}
|
||||
|
||||
static Time max()
|
||||
{
|
||||
return Time(std::numeric_limits<unsigned int>::max());
|
||||
}
|
||||
|
||||
private:
|
||||
inline void install_result(uint64_t long_length, uint64_t long_clock_rate)
|
||||
{
|
||||
// TODO: switch to appropriate values if the result is too large or small to fit, even with trimmed accuracy.
|
||||
|
||||
while(!(long_length&1) && !(long_clock_rate&1))
|
||||
{
|
||||
long_length >>= 1;
|
||||
long_clock_rate >>= 1;
|
||||
}
|
||||
|
||||
if(long_length > std::numeric_limits<unsigned int>::max() || long_clock_rate > std::numeric_limits<unsigned int>::max())
|
||||
{
|
||||
uint64_t common_divisor = NumberTheory::greatest_common_divisor(long_length, long_clock_rate);
|
||||
long_length /= common_divisor;
|
||||
long_clock_rate /= common_divisor;
|
||||
|
||||
// Okay, in desperation accept a loss of accuracy.
|
||||
while(
|
||||
(long_length > std::numeric_limits<unsigned int>::max() || long_clock_rate > std::numeric_limits<unsigned int>::max()) &&
|
||||
(long_clock_rate > 1))
|
||||
{
|
||||
long_length >>= 1;
|
||||
long_clock_rate >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
if(long_length <= std::numeric_limits<unsigned int>::max() && long_clock_rate <= std::numeric_limits<unsigned int>::max())
|
||||
{
|
||||
length = (unsigned int)long_length;
|
||||
clock_rate = (unsigned int)long_clock_rate;
|
||||
}
|
||||
else
|
||||
{
|
||||
length = std::numeric_limits<unsigned int>::max();
|
||||
clock_rate = 1u;
|
||||
}
|
||||
}
|
||||
|
||||
inline void install_float(float value)
|
||||
{
|
||||
int exponent;
|
||||
float mantissa = frexpf(value, &exponent);
|
||||
float loaded_mantissa = ldexpf(mantissa, 24);
|
||||
|
||||
uint64_t result_length;
|
||||
uint64_t result_clock_rate;
|
||||
if(exponent < 0)
|
||||
{
|
||||
int right_shift = -exponent;
|
||||
result_length = (uint64_t)loaded_mantissa >> right_shift;
|
||||
result_clock_rate = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(exponent <= 24)
|
||||
{
|
||||
result_length = (uint64_t)loaded_mantissa;
|
||||
result_clock_rate = 1 << (24 - exponent);
|
||||
}
|
||||
else
|
||||
{
|
||||
result_length = std::numeric_limits<uint64_t>::max();
|
||||
result_clock_rate = 1;
|
||||
}
|
||||
}
|
||||
install_result(result_length, result_clock_rate);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
@ -35,11 +35,6 @@ void TimedEventLoop::reset_timer()
|
||||
cycles_until_event_ = 0;
|
||||
}
|
||||
|
||||
void TimedEventLoop::reset_timer_to_offset(Time offset)
|
||||
{
|
||||
// TODO: apply
|
||||
}
|
||||
|
||||
void TimedEventLoop::jump_to_next_event()
|
||||
{
|
||||
reset_timer();
|
||||
|
@ -71,11 +71,6 @@ namespace Storage {
|
||||
*/
|
||||
void reset_timer();
|
||||
|
||||
/*!
|
||||
Sets the amount of time into the current event to @c offset.
|
||||
*/
|
||||
void reset_timer_to_offset(Time offset);
|
||||
|
||||
/*!
|
||||
Causes an immediate call to @c process_next_event and a call to @c reset_timer with the
|
||||
net effect of processing the current event immediately and fast forwarding exactly to the
|
||||
|
Loading…
x
Reference in New Issue
Block a user