1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-05 10:28:58 +00:00

Merge pull request #86 from TomHarte/DiskWrites

Implements backing work for in-memory disk writes
This commit is contained in:
Thomas Harte 2016-12-25 09:37:20 -05:00 committed by GitHub
commit e56beb3e9c
22 changed files with 1404 additions and 213 deletions

View File

@ -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;
};

View File

@ -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

View File

@ -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];

View 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

View 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

View 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

View 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

View File

@ -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();
}

View File

@ -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);
};
}

View File

@ -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

View File

@ -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();
};
}

View File

@ -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);
}

View File

@ -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_;

View 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;
}

View 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
View 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);
}

View 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 */

View File

@ -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;
}

View File

@ -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();
};
}

View File

@ -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);
}
};

View File

@ -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();

View File

@ -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