diff --git a/ClockReceiver/ClockReceiver.hpp b/ClockReceiver/ClockReceiver.hpp index 937eb17c0..c6cd23f23 100644 --- a/ClockReceiver/ClockReceiver.hpp +++ b/ClockReceiver/ClockReceiver.hpp @@ -165,7 +165,7 @@ class HalfCycles: public WrappedInt { constexpr HalfCycles(const HalfCycles &half_cycles) : WrappedInt(half_cycles.length_) {} /// @returns The number of whole cycles completely covered by this span of half cycles. - constexpr Cycles cycles() { + constexpr Cycles cycles() const { return Cycles(length_ >> 1); } diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 8acdf83d2..2979bd475 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -43,7 +43,6 @@ 4B055AA81FAE85EF0060FFFF /* Shifter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136871F78725F008B8ED9 /* Shifter.cpp */; }; 4B055AA91FAE85EF0060FFFF /* CommodoreGCR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */; }; 4B055AAA1FAE85F50060FFFF /* CPM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */; }; - 4B055AAB1FAE85FD0060FFFF /* PCMPatchedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518711F75E91800926311 /* PCMPatchedTrack.cpp */; }; 4B055AAC1FAE85FD0060FFFF /* PCMSegment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518731F75E91800926311 /* PCMSegment.cpp */; }; 4B055AAD1FAE85FD0060FFFF /* PCMTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518751F75E91800926311 /* PCMTrack.cpp */; }; 4B055AAE1FAE85FD0060FFFF /* TrackSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */; }; @@ -125,7 +124,6 @@ 4B0E61071FF34737002A9DBD /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E61051FF34737002A9DBD /* MSX.cpp */; }; 4B0F94FE208C1A1600FE41D9 /* NIB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */; }; 4B0F94FF208C1A1600FE41D9 /* NIB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */; }; - 4B121F951E05E66800BFDA12 /* PCMPatchedTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */; }; 4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */; }; 4B12C0ED1FCFA98D005BFD93 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */; }; 4B12C0EE1FCFAD1A005BFD93 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */; }; @@ -186,7 +184,6 @@ 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 */; }; - 4B4518811F75E91A00926311 /* PCMPatchedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518711F75E91800926311 /* PCMPatchedTrack.cpp */; }; 4B4518821F75E91A00926311 /* PCMSegment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518731F75E91800926311 /* PCMSegment.cpp */; }; 4B4518831F75E91A00926311 /* PCMTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518751F75E91800926311 /* PCMTrack.cpp */; }; 4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518771F75E91800926311 /* UnformattedTrack.cpp */; }; @@ -720,7 +717,6 @@ 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = NIB.cpp; sourceTree = ""; }; 4B0F94FD208C1A1600FE41D9 /* NIB.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = NIB.hpp; sourceTree = ""; }; 4B0F9500208C42A300FE41D9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Target.hpp; path = AppleII/Target.hpp; sourceTree = ""; }; - 4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMPatchedTrackTests.mm; sourceTree = ""; }; 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMSegmentEventSourceTests.mm; sourceTree = ""; }; 4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Keyboard.cpp; path = MSX/Keyboard.cpp; sourceTree = ""; }; 4B12C0EC1FCFA98D005BFD93 /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Keyboard.hpp; path = MSX/Keyboard.hpp; sourceTree = ""; }; @@ -827,8 +823,6 @@ 4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = AllSuiteA.bin; path = AllSuiteA/AllSuiteA.bin; sourceTree = ""; }; 4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 6502_functional_test.bin; path = "Klaus Dormann/6502_functional_test.bin"; sourceTree = ""; }; 4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */ = {isa = PBXFileReference; lastKnownFileType = file; name = BCDTEST_beeb; path = BCDTest/BCDTEST_beeb; sourceTree = ""; }; - 4B4518711F75E91800926311 /* PCMPatchedTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMPatchedTrack.cpp; sourceTree = ""; }; - 4B4518721F75E91800926311 /* PCMPatchedTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMPatchedTrack.hpp; sourceTree = ""; }; 4B4518731F75E91800926311 /* PCMSegment.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMSegment.cpp; sourceTree = ""; }; 4B4518741F75E91800926311 /* PCMSegment.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMSegment.hpp; sourceTree = ""; }; 4B4518751F75E91800926311 /* PCMTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMTrack.cpp; sourceTree = ""; }; @@ -1849,12 +1843,10 @@ 4B4518701F75E91800926311 /* Track */ = { isa = PBXGroup; children = ( - 4B4518711F75E91800926311 /* PCMPatchedTrack.cpp */, 4B4518731F75E91800926311 /* PCMSegment.cpp */, 4B4518751F75E91800926311 /* PCMTrack.cpp */, 4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */, 4B4518771F75E91800926311 /* UnformattedTrack.cpp */, - 4B4518721F75E91800926311 /* PCMPatchedTrack.hpp */, 4B4518741F75E91800926311 /* PCMSegment.hpp */, 4B4518761F75E91800926311 /* PCMTrack.hpp */, 4B4518881F75ECB100926311 /* Track.hpp */, @@ -2789,7 +2781,6 @@ 4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */, 4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */, 4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */, - 4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */, 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */, 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */, 4B2AF8681E513FC20027EE29 /* TIATests.mm */, @@ -3639,7 +3630,6 @@ 4B12C0EE1FCFAD1A005BFD93 /* Keyboard.cpp in Sources */, 4B055AE81FAE9B7B0060FFFF /* FIRFilter.cpp in Sources */, 4B055A901FAE85A90060FFFF /* TimedEventLoop.cpp in Sources */, - 4B055AAB1FAE85FD0060FFFF /* PCMPatchedTrack.cpp in Sources */, 4B055AC71FAE9AEE0060FFFF /* TIA.cpp in Sources */, 4B055AD21FAE9B0B0060FFFF /* Keyboard.cpp in Sources */, 4B89451B201967B4007DE474 /* ConfidenceSummary.cpp in Sources */, @@ -3812,7 +3802,6 @@ 4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.mm in Sources */, 4B894532201967B4007DE474 /* 6502.cpp in Sources */, 4BDB61EC203285AE0048AF91 /* Atari2600OptionsPanel.swift in Sources */, - 4B4518811F75E91A00926311 /* PCMPatchedTrack.cpp in Sources */, 4BBB70A8202014E2002FE009 /* MultiCRTMachine.cpp in Sources */, 4B8805F71DCFF6C9003085B1 /* Commodore.cpp in Sources */, 4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */, @@ -3964,7 +3953,6 @@ 4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */, 4B08A2781EE39306008B7065 /* TestMachine.mm in Sources */, 4BFCA1271ECBE33200AC40C1 /* TestMachineZ80.mm in Sources */, - 4B121F951E05E66800BFDA12 /* PCMPatchedTrackTests.mm in Sources */, 4B322E011F5A2990004EB04C /* Z80AllRAM.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/OSBindings/Mac/Clock SignalTests/PCMPatchedTrackTests.mm b/OSBindings/Mac/Clock SignalTests/PCMPatchedTrackTests.mm deleted file mode 100644 index 5af9b5472..000000000 --- a/OSBindings/Mac/Clock SignalTests/PCMPatchedTrackTests.mm +++ /dev/null @@ -1,195 +0,0 @@ -// -// PCMPatchedTrackTests.m -// Clock Signal -// -// Created by Thomas Harte on 17/12/2016. -// Copyright 2016 Thomas Harte. All rights reserved. -// - -#import - -#include "PCMTrack.hpp" -#include "PCMPatchedTrack.hpp" - -@interface PCMPatchedTrackTests : XCTestCase -@end - -@implementation PCMPatchedTrackTests - -#pragma mark - Prebuilt tracks - -- (std::shared_ptr)togglingTrack { - Storage::Disk::PCMSegment segment; - segment.data = { 0xff, 0xff, 0xff, 0xff }; - segment.number_of_bits = 32; - return std::shared_ptr(new Storage::Disk::PCMTrack(segment)); -} - -- (std::shared_ptr)patchableTogglingTrack { - std::shared_ptr track = self.togglingTrack; - return std::shared_ptr(new Storage::Disk::PCMPatchedTrack(track)); -} - -- (std::shared_ptr)fourSegmentPatchedTrack { - std::shared_ptr patchableTrack = self.patchableTogglingTrack; - Storage::Disk::PCMPatchedTrack *patchable = static_cast(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, false); - } - - return patchableTrack; -} - -#pragma mark - - -- (std::vector)eventsFromTrack:(std::shared_ptr)track { - std::vector 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 &)events { - Storage::Time result(0); - for(const auto &event : events) { - result += event.length; - } - return result; -} - -- (void)patchTrack:(std::shared_ptr)track withSegment:(Storage::Disk::PCMSegment)segment atTime:(Storage::Time)time { - Storage::Disk::PCMPatchedTrack *patchable = static_cast(track.get()); - patchable->add_segment(time, segment, false); -} - -#pragma mark - Repeating Asserts - -- (void)assertOneThirtyTwosForTrack:(std::shared_ptr)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 &)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 patchableTrack = self.patchableTogglingTrack; - [self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(1, 32), 1, {0xff}) atTime:Storage::Time(3, 128)]; - - std::vector 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 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 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 patchableTrack = self.fourSegmentPatchedTrack; - [self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:33 withEntry:4 ofLength:Storage::Time(1, 32)]; -} - -- (void)testMultiTrimBothSideReplace { - std::shared_ptr 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 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 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 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 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 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 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 diff --git a/OSBindings/Mac/Clock SignalTests/PCMSegmentEventSourceTests.mm b/OSBindings/Mac/Clock SignalTests/PCMSegmentEventSourceTests.mm index 6afb0c3b7..053058012 100644 --- a/OSBindings/Mac/Clock SignalTests/PCMSegmentEventSourceTests.mm +++ b/OSBindings/Mac/Clock SignalTests/PCMSegmentEventSourceTests.mm @@ -17,11 +17,8 @@ - (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; + std::vector data = {0xff, 0x00, 0xff, 0x00}; + Storage::Disk::PCMSegment alternatingFFs(Storage::Time(1, 10), data.size()*8, data); return Storage::Disk::PCMSegmentEventSource(alternatingFFs); } diff --git a/OSBindings/Mac/Clock SignalTests/PCMTrackTests.mm b/OSBindings/Mac/Clock SignalTests/PCMTrackTests.mm index 0d30dc769..fd3c5bfee 100644 --- a/OSBindings/Mac/Clock SignalTests/PCMTrackTests.mm +++ b/OSBindings/Mac/Clock SignalTests/PCMTrackTests.mm @@ -19,13 +19,11 @@ { Storage::Disk::PCMSegment quickSegment, slowSegment; - quickSegment.data = {0xff}; - quickSegment.number_of_bits = 8; + quickSegment.data = {true, true, true, true, true, true, true, true}; quickSegment.length_of_a_bit.length = 1; quickSegment.length_of_a_bit.clock_rate = 100; - slowSegment.data = {0xff}; - slowSegment.number_of_bits = 8; + slowSegment.data = {true, true, true, true, true, true, true, true}; slowSegment.length_of_a_bit.length = 1; slowSegment.length_of_a_bit.clock_rate = 3; @@ -55,19 +53,16 @@ std::vector segments; Storage::Disk::PCMSegment sync_segment; - sync_segment.data.resize(10); - sync_segment.number_of_bits = 10*8; - memset(sync_segment.data.data(), 0xff, sync_segment.data.size()); + sync_segment.data.resize(10*8); + std::fill(sync_segment.data.begin(), sync_segment.data.end(), true); Storage::Disk::PCMSegment header_segment; - header_segment.data.resize(14); - header_segment.number_of_bits = 14*8; - memset(header_segment.data.data(), 0xff, header_segment.data.size()); + header_segment.data.resize(14*8); + std::fill(header_segment.data.begin(), header_segment.data.end(), true); Storage::Disk::PCMSegment data_segment; - data_segment.data.resize(349); - data_segment.number_of_bits = 349*8; - memset(data_segment.data.data(), 0xff, data_segment.data.size()); + data_segment.data.resize(349*8); + std::fill(data_segment.data.begin(), data_segment.data.end(), true); for(std::size_t c = 0; c < 16; ++c) { segments.push_back(sync_segment); diff --git a/Storage/Disk/Controller/DiskController.hpp b/Storage/Disk/Controller/DiskController.hpp index b900bc80f..c9f6a9552 100644 --- a/Storage/Disk/Controller/DiskController.hpp +++ b/Storage/Disk/Controller/DiskController.hpp @@ -12,7 +12,6 @@ #include "../Drive.hpp" #include "../DPLL/DigitalPhaseLockedLoop.hpp" #include "../Track/PCMSegment.hpp" -#include "../Track/PCMPatchedTrack.hpp" #include "../../../ClockReceiver/ClockReceiver.hpp" #include "../../../ClockReceiver/ClockingHintSource.hpp" diff --git a/Storage/Disk/DiskImage/Formats/D64.cpp b/Storage/Disk/DiskImage/Formats/D64.cpp index 75aa977e0..35f277af1 100644 --- a/Storage/Disk/DiskImage/Formats/D64.cpp +++ b/Storage/Disk/DiskImage/Formats/D64.cpp @@ -79,16 +79,11 @@ std::shared_ptr D64::get_track_at_position(Track::Address address) { // // = 349 GCR bytes per sector - PCMSegment track; std::size_t track_bytes = 349 * static_cast(sectors_by_zone[zone]); - track.number_of_bits = static_cast(track_bytes) * 8; - track.data.resize(track_bytes); - uint8_t *data = &track.data[0]; - - memset(data, 0, track_bytes); + std::vector data(track_bytes); for(int sector = 0; sector < sectors_by_zone[zone]; sector++) { - uint8_t *sector_data = &data[sector * 349]; + uint8_t *sector_data = &data[static_cast(sector) * 349]; sector_data[0] = sector_data[1] = sector_data[2] = 0xff; uint8_t sector_number = static_cast(sector); // sectors count from 0 @@ -141,5 +136,5 @@ std::shared_ptr D64::get_track_at_position(Track::Address address) { Encodings::CommodoreGCR::encode_block(§or_data[target_data_offset], end_of_data); } - return std::shared_ptr(new PCMTrack(std::move(track))); + return std::shared_ptr(new PCMTrack(PCMSegment(data))); } diff --git a/Storage/Disk/DiskImage/Formats/DMK.cpp b/Storage/Disk/DiskImage/Formats/DMK.cpp index 786b09fc0..fa272b9ec 100644 --- a/Storage/Disk/DiskImage/Formats/DMK.cpp +++ b/Storage/Disk/DiskImage/Formats/DMK.cpp @@ -179,10 +179,5 @@ std::shared_ptr<::Storage::Disk::Track> DMK::get_track_at_position(::Storage::Di idam_pointer++; } - // All segments should be exactly their number of bits in length. - for(auto &segment : segments) { - segment.number_of_bits = static_cast(segment.data.size() * 8); - } - return std::make_shared(segments); } diff --git a/Storage/Disk/DiskImage/Formats/G64.cpp b/Storage/Disk/DiskImage/Formats/G64.cpp index 6b083b5b1..3e69cc2cb 100644 --- a/Storage/Disk/DiskImage/Formats/G64.cpp +++ b/Storage/Disk/DiskImage/Formats/G64.cpp @@ -43,38 +43,33 @@ std::shared_ptr G64::get_track_at_position(Track::Address address) { file_.seek(static_cast((address.position.as_half() * 4) + 0xc), SEEK_SET); // read the track offset - uint32_t track_offset; - track_offset = file_.get32le(); + const uint32_t track_offset = file_.get32le(); // if the track offset is zero, this track doesn't exist, so... - if(!track_offset) return resulting_track; + if(!track_offset) return nullptr; // seek to the track start file_.seek(static_cast(track_offset), SEEK_SET); // get the real track length - uint16_t track_length; - track_length = file_.get16le(); + const uint16_t track_length = file_.get16le(); // grab the byte contents of this track - std::vector track_contents(track_length); - file_.read(&track_contents[0], track_length); + const std::vector track_contents = file_.read(track_length); // seek to this track's entry in the speed zone table file_.seek(static_cast((address.position.as_half() * 4) + 0x15c), SEEK_SET); // read the speed zone offsrt - uint32_t speed_zone_offset; - speed_zone_offset = file_.get32le(); + const uint32_t speed_zone_offset = file_.get32le(); // if the speed zone is not constant, create a track based on the whole table; otherwise create one that's constant if(speed_zone_offset > 3) { // seek to start of speed zone file_.seek(static_cast(speed_zone_offset), SEEK_SET); - uint16_t speed_zone_length = (track_length + 3) >> 2; - // read the speed zone bytes + const uint16_t speed_zone_length = (track_length + 3) >> 2; uint8_t speed_zone_contents[speed_zone_length]; file_.read(speed_zone_contents, speed_zone_length); @@ -87,11 +82,10 @@ std::shared_ptr G64::get_track_at_position(Track::Address address) { if(byte_speed != current_speed || byte == static_cast(track_length-1)) { unsigned int number_of_bytes = byte - start_byte_in_current_speed; - PCMSegment segment; - segment.number_of_bits = number_of_bytes * 8; - segment.length_of_a_bit = Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(current_speed); - segment.data.resize(number_of_bytes); - std::memcpy(&segment.data[0], &track_contents[start_byte_in_current_speed], number_of_bytes); + PCMSegment segment( + Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(current_speed), + number_of_bytes * 8, + &track_contents[start_byte_in_current_speed]); segments.push_back(std::move(segment)); current_speed = byte_speed; @@ -101,10 +95,11 @@ std::shared_ptr G64::get_track_at_position(Track::Address address) { resulting_track.reset(new PCMTrack(std::move(segments))); } else { - PCMSegment segment; - segment.number_of_bits = track_length * 8; - segment.length_of_a_bit = Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(static_cast(speed_zone_offset)); - segment.data = std::move(track_contents); + PCMSegment segment( + Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(static_cast(speed_zone_offset)), + track_length * 8, + track_contents + ); resulting_track.reset(new PCMTrack(std::move(segment))); } diff --git a/Storage/Disk/DiskImage/Formats/HFE.cpp b/Storage/Disk/DiskImage/Formats/HFE.cpp index 0d7b28f4c..d90913be3 100644 --- a/Storage/Disk/DiskImage/Formats/HFE.cpp +++ b/Storage/Disk/DiskImage/Formats/HFE.cpp @@ -26,9 +26,6 @@ HFE::HFE(const std::string &file_name) : track_list_offset_ = static_cast(file_.get16le()) << 9; } -HFE::~HFE() { -} - HeadPosition HFE::get_maximum_head_position() { return HeadPosition(track_count_); } @@ -49,13 +46,13 @@ uint16_t HFE::seek_track(Track::Address address) { // based on an assumption of two heads. file_.seek(track_list_offset_ + address.position.as_int() * 4, SEEK_SET); - long track_offset = static_cast(file_.get16le()) << 9; - uint16_t track_length = file_.get16le(); + long track_offset = static_cast(file_.get16le()) << 9; // Track offset, in units of 512 bytes. + uint16_t track_length = file_.get16le(); // Track length, in bytes, containing both the front and back track. file_.seek(track_offset, SEEK_SET); if(address.head) file_.seek(256, SEEK_CUR); - return track_length / 2; + return track_length / 2; // Divide by two to give the track length for a single side. } std::shared_ptr HFE::get_track_at_position(Track::Address address) { @@ -64,21 +61,42 @@ std::shared_ptr HFE::get_track_at_position(Track::Address address) { std::lock_guard lock_guard(file_.get_file_access_mutex()); uint16_t track_length = seek_track(address); - segment.data.resize(track_length); - segment.number_of_bits = track_length * 8; + segment.data.resize(track_length * 8); + // HFE tracks are stored as 256 bytes for side 1, then 256 bytes for side 2, + // then 256 bytes for side 1, then 256 bytes for side 2, etc, until the final + // 512-byte segment which will contain less than the full 256 bytes. + // + // seek_track will have advanced an extra initial 256 bytes if the address + // refers to side 2, so the loop below can act ass though it were definitely + // dealing with side 1. uint16_t c = 0; while(c < track_length) { + // Decide how many bytes of at most 256 to read, and read them. uint16_t length = static_cast(std::min(256, track_length - c)); - file_.read(&segment.data[c], length); + std::vector section = file_.read(length); + + // Push those into the PCMSegment. In HFE the least-significant bit is + // serialised first. TODO: move this logic to PCMSegment. + for(uint16_t byte = 0; byte < length; ++byte) { + const size_t base = static_cast(c + byte) << 3; + segment.data[base + 0] = !!(section[byte] & 0x01); + segment.data[base + 1] = !!(section[byte] & 0x02); + segment.data[base + 2] = !!(section[byte] & 0x04); + segment.data[base + 3] = !!(section[byte] & 0x08); + segment.data[base + 4] = !!(section[byte] & 0x10); + segment.data[base + 5] = !!(section[byte] & 0x20); + segment.data[base + 6] = !!(section[byte] & 0x40); + segment.data[base + 7] = !!(section[byte] & 0x80); + } + + // Advance the target pointer, and skip the next 256 bytes of the file + // (which will be for the other side of the disk). c += length; file_.seek(256, SEEK_CUR); } } - // Flip bytes; HFE's preference is that the least-significant bit - // is serialised first, but PCMTrack posts the most-significant first. - Storage::Data::BitReverse::reverse(segment.data); return std::make_shared(segment); } @@ -88,9 +106,11 @@ void HFE::set_tracks(const std::map> &tra uint16_t track_length = seek_track(track.first); lock_guard.unlock(); - PCMSegment segment = Storage::Disk::track_serialisation(*track.second, Storage::Time(1, track_length * 8)); - Storage::Data::BitReverse::reverse(segment.data); - uint16_t data_length = std::min(static_cast(segment.data.size()), track_length); + const PCMSegment segment = Storage::Disk::track_serialisation(*track.second, Storage::Time(1, track_length * 8)); + + // Convert the segment into a byte encoding, LSB first. + std::vector byte_segment = segment.byte_data(false); + uint16_t data_length = std::min(static_cast(byte_segment.size()), track_length); lock_guard.lock(); seek_track(track.first); @@ -98,7 +118,7 @@ void HFE::set_tracks(const std::map> &tra uint16_t c = 0; while(c < data_length) { uint16_t length = static_cast(std::min(256, data_length - c)); - file_.write(&segment.data[c], length); + file_.write(&byte_segment[c], length); c += length; file_.seek(256, SEEK_CUR); } diff --git a/Storage/Disk/DiskImage/Formats/HFE.hpp b/Storage/Disk/DiskImage/Formats/HFE.hpp index ca8547155..0b35f0400 100644 --- a/Storage/Disk/DiskImage/Formats/HFE.hpp +++ b/Storage/Disk/DiskImage/Formats/HFE.hpp @@ -30,7 +30,6 @@ class HFE: public DiskImage { @throws Error::UnknownVersion if the file looks correct but is an unsupported version. */ HFE(const std::string &file_name); - ~HFE(); // implemented to satisfy @c Disk HeadPosition get_maximum_head_position() override; diff --git a/Storage/Disk/DiskImage/Formats/NIB.cpp b/Storage/Disk/DiskImage/Formats/NIB.cpp index 46b6917ea..269f1a8fa 100644 --- a/Storage/Disk/DiskImage/Formats/NIB.cpp +++ b/Storage/Disk/DiskImage/Formats/NIB.cpp @@ -69,8 +69,6 @@ std::shared_ptr<::Storage::Disk::Track> NIB::get_track_at_position(::Storage::Di // tracks and headers), then treat all following FFs as a sync // region, then switch back to ordinary behaviour as soon as a // non-FF appears. - PCMSegment segment; - std::size_t start_index = 0; std::set sync_starts; @@ -93,6 +91,7 @@ std::shared_ptr<::Storage::Disk::Track> NIB::get_track_at_position(::Storage::Di } } + PCMSegment segment; if(start_index) { segment += Encodings::AppleGCR::six_and_two_sync(static_cast(start_index)); } @@ -100,13 +99,10 @@ std::shared_ptr<::Storage::Disk::Track> NIB::get_track_at_position(::Storage::Di std::size_t index = start_index; for(const auto &location: sync_starts) { // Write from index to sync_start. - PCMSegment data_segment; - data_segment.data.insert( - data_segment.data.end(), + std::vector data_segment( track_data.begin() + static_cast(index), track_data.begin() + static_cast(location)); - data_segment.number_of_bits = static_cast(data_segment.data.size() * 8); - segment += data_segment; + segment += PCMSegment(data_segment); // Add a sync from sync_start to end of 0xffs. if(location == track_length-1) break; @@ -133,8 +129,8 @@ void NIB::set_tracks(const std::map> &tra std::vector track; track.reserve(track_length); uint8_t shifter = 0; - for(unsigned int bit = 0; bit < segment.number_of_bits; ++bit) { - shifter = static_cast((shifter << 1) | segment.bit(bit)); + for(const auto bit: segment.data) { + shifter = static_cast((shifter << 1) | (bit ? 1 : 0)); if(shifter & 0x80) { track.push_back(shifter); shifter = 0; diff --git a/Storage/Disk/DiskImage/Formats/OricMFMDSK.cpp b/Storage/Disk/DiskImage/Formats/OricMFMDSK.cpp index ee4774dae..ae043887a 100644 --- a/Storage/Disk/DiskImage/Formats/OricMFMDSK.cpp +++ b/Storage/Disk/DiskImage/Formats/OricMFMDSK.cpp @@ -108,7 +108,6 @@ std::shared_ptr OricMFMDSK::get_track_at_position(Track::Address address) } } - segment.number_of_bits = static_cast(segment.data.size() * 8); return std::make_shared(segment); } @@ -123,8 +122,8 @@ void OricMFMDSK::set_tracks(const std::map WOZ::get_track_at_position(Track::Address address) { if(offset == NoSuchTrack) return nullptr; // Seek to the real track. - PCMSegment track_contents; + std::vector track_contents; + size_t number_of_bits; { std::lock_guard lock_guard(file_.get_file_access_mutex()); file_.seek(offset, SEEK_SET); @@ -114,13 +115,12 @@ std::shared_ptr WOZ::get_track_at_position(Track::Address address) { // In WOZ a track is up to 6646 bytes of data, followed by a two-byte record of the // number of bytes that actually had data in them, then a two-byte count of the number // of bits that were used. Other information follows but is not intended for emulation. - track_contents.data = file_.read(6646); + track_contents = file_.read(6646); file_.seek(2, SEEK_CUR); - track_contents.number_of_bits = file_.get16le(); - track_contents.data.resize((track_contents.number_of_bits + 7) >> 3); + number_of_bits = std::min(file_.get16le(), static_cast(6646*8)); } - return std::shared_ptr(new PCMTrack(track_contents)); + return std::shared_ptr(new PCMTrack(PCMSegment(number_of_bits, track_contents))); } void WOZ::set_tracks(const std::map> &tracks) { @@ -129,13 +129,14 @@ void WOZ::set_tracks(const std::map> &tra auto segment = Storage::Disk::track_serialisation(*pair.second, Storage::Time(1, 50000)); auto offset = static_cast(file_offset(pair.first) - 12); - memcpy(&post_crc_contents_[offset - 12], segment.data.data(), segment.number_of_bits >> 3); + std::vector segment_bytes = segment.byte_data(); + memcpy(&post_crc_contents_[offset - 12], segment_bytes.data(), segment_bytes.size()); // Write number of bytes and number of bits. - post_crc_contents_[offset + 6646] = static_cast(segment.number_of_bits >> 3); - post_crc_contents_[offset + 6647] = static_cast(segment.number_of_bits >> 11); - post_crc_contents_[offset + 6648] = static_cast(segment.number_of_bits); - post_crc_contents_[offset + 6649] = static_cast(segment.number_of_bits >> 8); + post_crc_contents_[offset + 6646] = static_cast(segment.data.size() >> 3); + post_crc_contents_[offset + 6647] = static_cast(segment.data.size() >> 11); + post_crc_contents_[offset + 6648] = static_cast(segment.data.size()); + post_crc_contents_[offset + 6649] = static_cast(segment.data.size() >> 8); // Set no splice information now provided, since it's been lost if ever it was known. post_crc_contents_[offset + 6650] = 0xff; diff --git a/Storage/Disk/Drive.cpp b/Storage/Disk/Drive.cpp index aef01b99e..cce6362b3 100644 --- a/Storage/Disk/Drive.cpp +++ b/Storage/Disk/Drive.cpp @@ -300,29 +300,29 @@ void Drive::begin_writing(Time bit_length, bool clamp_to_index_hole) { 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 Drive::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++; - + write_segment_.data.push_back(value); cycles_until_bits_written_ += cycles_per_bit_; } void Drive::end_writing() { + // If the user modifies a track, it's scaled up to a "high" resolution and modifications + // are plotted on top of that. + static const size_t high_resolution_track_rate = 500000; + if(!is_reading_) { is_reading_ = true; if(!patched_track_) { // Avoid creating a new patched track if this one is already patched - patched_track_ = std::dynamic_pointer_cast(track_); - if(!patched_track_) { - patched_track_.reset(new PCMPatchedTrack(track_)); + patched_track_ = std::dynamic_pointer_cast(track_); + if(!patched_track_ || !patched_track_->is_resampled_clone()) { + Track *tr = track_.get(); + patched_track_.reset(PCMTrack::resampled_clone(tr, high_resolution_track_rate)); } } patched_track_->add_segment(write_start_time_, write_segment_, clamp_writing_to_index_hole_); diff --git a/Storage/Disk/Drive.hpp b/Storage/Disk/Drive.hpp index a9c021528..1033343af 100644 --- a/Storage/Disk/Drive.hpp +++ b/Storage/Disk/Drive.hpp @@ -11,7 +11,7 @@ #include "Disk.hpp" #include "Track/PCMSegment.hpp" -#include "Track/PCMPatchedTrack.hpp" +#include "Track/PCMTrack.hpp" #include "../TimedEventLoop.hpp" #include "../../Activity/Observer.hpp" @@ -168,8 +168,8 @@ class Drive: public ClockingHint::Source, public TimedEventLoop { bool clamp_writing_to_index_hole_ = false; // If writing is occurring then the drive will be accumulating a write segment, - // for addition to a patched track. - std::shared_ptr patched_track_; + // for addition to a (high-resolution) PCM track. + std::shared_ptr patched_track_; PCMSegment write_segment_; Time write_start_time_; diff --git a/Storage/Disk/Encodings/AppleGCR/Encoder.cpp b/Storage/Disk/Encodings/AppleGCR/Encoder.cpp index 11d2da80e..7f3f5fb62 100644 --- a/Storage/Disk/Encodings/AppleGCR/Encoder.cpp +++ b/Storage/Disk/Encodings/AppleGCR/Encoder.cpp @@ -35,15 +35,18 @@ const uint8_t six_and_two_mapping[] = { Storage::Disk::PCMSegment sync(int length, int bit_size) { Storage::Disk::PCMSegment segment; - // Allocate sufficient storage. - segment.data.resize(static_cast(((length * bit_size) + 7) >> 3), 0); + // Reserve sufficient storage. + segment.data.reserve(static_cast(length * bit_size)); + // Write patters of 0xff padded with 0s to the selected bit size. while(length--) { - segment.data[segment.number_of_bits >> 3] |= 0xff >> (segment.number_of_bits & 7); - if(segment.number_of_bits & 7) { - segment.data[1 + (segment.number_of_bits >> 3)] |= 0xff << (8 - (segment.number_of_bits & 7)); - } - segment.number_of_bits += static_cast(bit_size); + int c = 8; + while(c--) + segment.data.push_back(true); + + c = bit_size - 8; + while(c--) + segment.data.push_back(false); } return segment; @@ -65,17 +68,15 @@ Storage::Disk::PCMSegment AppleGCR::header(uint8_t volume, uint8_t track, uint8_ const uint8_t checksum = volume ^ track ^ sector; // Apple headers are encoded using an FM-esque scheme rather than 6 and 2, or 5 and 3. - Storage::Disk::PCMSegment segment; - segment.data.resize(14); - segment.number_of_bits = 14*8; + std::vector data(14); - segment.data[0] = header_prologue[0]; - segment.data[1] = header_prologue[1]; - segment.data[2] = header_prologue[2]; + data[0] = header_prologue[0]; + data[1] = header_prologue[1]; + data[2] = header_prologue[2]; #define WriteFM(index, value) \ - segment.data[index+0] = static_cast(((value) >> 1) | 0xaa); \ - segment.data[index+1] = static_cast((value) | 0xaa); \ + data[index+0] = static_cast(((value) >> 1) | 0xaa); \ + data[index+1] = static_cast((value) | 0xaa); \ WriteFM(3, volume); WriteFM(5, track); @@ -84,24 +85,23 @@ Storage::Disk::PCMSegment AppleGCR::header(uint8_t volume, uint8_t track, uint8_ #undef WriteFM - segment.data[11] = epilogue[0]; - segment.data[12] = epilogue[1]; - segment.data[13] = epilogue[2]; + data[11] = epilogue[0]; + data[12] = epilogue[1]; + data[13] = epilogue[2]; - return segment; + return Storage::Disk::PCMSegment(data); } Storage::Disk::PCMSegment AppleGCR::five_and_three_data(const uint8_t *source) { - Storage::Disk::PCMSegment segment; + std::vector data(410 + 7); - segment.data.resize(410 + 7); - segment.data[0] = data_prologue[0]; - segment.data[1] = data_prologue[1]; - segment.data[2] = data_prologue[2]; + data[0] = data_prologue[0]; + data[1] = data_prologue[1]; + data[2] = data_prologue[2]; - segment.data[414] = epilogue[0]; - segment.data[411] = epilogue[1]; - segment.data[416] = epilogue[2]; + data[414] = epilogue[0]; + data[411] = epilogue[1]; + data[416] = epilogue[2]; // std::size_t source_pointer = 0; // std::size_t destination_pointer = 3; @@ -114,26 +114,23 @@ Storage::Disk::PCMSegment AppleGCR::five_and_three_data(const uint8_t *source) { // Map five-bit values up to full bytes. for(std::size_t c = 0; c < 410; ++c) { - segment.data[3 + c] = five_and_three_mapping[segment.data[3 + c]]; + data[3 + c] = five_and_three_mapping[data[3 + c]]; } - return segment; + return Storage::Disk::PCMSegment(data); } Storage::Disk::PCMSegment AppleGCR::six_and_two_data(const uint8_t *source) { - Storage::Disk::PCMSegment segment; - - segment.data.resize(349); - segment.number_of_bits = static_cast(segment.data.size() * 8); + std::vector data(349); // Add the prologue and epilogue. - segment.data[0] = data_prologue[0]; - segment.data[1] = data_prologue[1]; - segment.data[2] = data_prologue[2]; + data[0] = data_prologue[0]; + data[1] = data_prologue[1]; + data[2] = data_prologue[2]; - segment.data[346] = epilogue[0]; - segment.data[347] = epilogue[1]; - segment.data[348] = epilogue[2]; + data[346] = epilogue[0]; + data[347] = epilogue[1]; + data[348] = epilogue[2]; // Fill in byte values: the first 86 bytes contain shuffled // and combined copies of the bottom two bits of the sector @@ -141,40 +138,40 @@ Storage::Disk::PCMSegment AppleGCR::six_and_two_data(const uint8_t *source) { // six bits. const uint8_t bit_reverse[] = {0, 2, 1, 3}; for(std::size_t c = 0; c < 84; ++c) { - segment.data[3 + c] = + data[3 + c] = static_cast( bit_reverse[source[c]&3] | (bit_reverse[source[c + 86]&3] << 2) | (bit_reverse[source[c + 172]&3] << 4) ); } - segment.data[87] = + data[87] = static_cast( (bit_reverse[source[84]&3] << 0) | (bit_reverse[source[170]&3] << 2) ); - segment.data[88] = + data[88] = static_cast( (bit_reverse[source[85]&3] << 0) | (bit_reverse[source[171]&3] << 2) ); for(std::size_t c = 0; c < 256; ++c) { - segment.data[3 + 86 + c] = source[c] >> 2; + data[3 + 86 + c] = source[c] >> 2; } // Exclusive OR each byte with the one before it. - segment.data[345] = segment.data[344]; + data[345] = data[344]; std::size_t location = 344; while(location > 3) { - segment.data[location] ^= segment.data[location-1]; + data[location] ^= data[location-1]; --location; } // Map six-bit values up to full bytes. for(std::size_t c = 0; c < 343; ++c) { - segment.data[3 + c] = six_and_two_mapping[segment.data[3 + c]]; + data[3 + c] = six_and_two_mapping[data[3 + c]]; } - return segment; + return Storage::Disk::PCMSegment(data); } diff --git a/Storage/Disk/Encodings/AppleGCR/SegmentParser.cpp b/Storage/Disk/Encodings/AppleGCR/SegmentParser.cpp index 239da51ac..ffd9d515e 100644 --- a/Storage/Disk/Encodings/AppleGCR/SegmentParser.cpp +++ b/Storage/Disk/Encodings/AppleGCR/SegmentParser.cpp @@ -49,9 +49,9 @@ std::map Storage::Encodings::AppleGCR::sectors_from_segment // Scan the track 1 and 1/8th times; that's long enough to make sure that any sector which straddles the // end of the track is caught. Since they're put into a map, it doesn't matter if they're caught twice. - unsigned int extended_length = segment.number_of_bits + (segment.number_of_bits >> 3); - for(unsigned int bit = 0; bit < extended_length; ++bit) { - shift_register = static_cast((shift_register << 1) | segment.bit(bit % segment.number_of_bits)); + const size_t extended_length = segment.data.size() + (segment.data.size() >> 3); + for(size_t bit = 0; bit < extended_length; ++bit) { + shift_register = static_cast((shift_register << 1) | (segment.data[bit % segment.data.size()] ? 1 : 0)); // Apple GCR parsing: bytes always have the top bit set. if(!(shift_register&0x80)) continue; @@ -80,7 +80,7 @@ std::map Storage::Encodings::AppleGCR::sectors_from_segment new_sector.reset(new Sector); new_sector->data.reserve(412); } else { - sector_location = static_cast(bit % segment.number_of_bits); + sector_location = static_cast(bit % segment.data.size()); } } } else { diff --git a/Storage/Disk/Encodings/CommodoreGCR.cpp b/Storage/Disk/Encodings/CommodoreGCR.cpp index bfaec454a..ababecc0a 100644 --- a/Storage/Disk/Encodings/CommodoreGCR.cpp +++ b/Storage/Disk/Encodings/CommodoreGCR.cpp @@ -12,11 +12,8 @@ using namespace Storage; Time Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(unsigned int time_zone) { - Time duration; // the speed zone divides a 4Mhz clock by 13, 14, 15 or 16, with higher-numbered zones being faster (i.e. each bit taking less time) - duration.length = 16 - time_zone; - duration.clock_rate = 4000000; - return duration; + return Time(16 - time_zone, 4000000u); } unsigned int Storage::Encodings::CommodoreGCR::encoding_for_nibble(uint8_t nibble) { diff --git a/Storage/Disk/Encodings/MFM/Encoder.cpp b/Storage/Disk/Encodings/MFM/Encoder.cpp index 84df0f7f3..c6c333939 100644 --- a/Storage/Disk/Encodings/MFM/Encoder.cpp +++ b/Storage/Disk/Encodings/MFM/Encoder.cpp @@ -18,7 +18,7 @@ using namespace Storage::Encodings::MFM; class MFMEncoder: public Encoder { public: - MFMEncoder(std::vector &target) : Encoder(target) {} + MFMEncoder(std::vector &target) : Encoder(target) {} void add_byte(uint8_t input) { crc_generator_.add(input); @@ -74,7 +74,7 @@ class MFMEncoder: public Encoder { class FMEncoder: public Encoder { // encodes each 16-bit part as clock, data, clock, data [...] public: - FMEncoder(std::vector &target) : Encoder(target) {} + FMEncoder(std::vector &target) : Encoder(target) {} void add_byte(uint8_t input) { crc_generator_.add(input); @@ -127,7 +127,7 @@ template std::shared_ptr std::size_t post_data_bytes, uint8_t post_data_value, std::size_t expected_track_bytes) { Storage::Disk::PCMSegment segment; - segment.data.reserve(expected_track_bytes); + segment.data.reserve(expected_track_bytes * 8); T shifter(segment.data); // output the index mark @@ -176,22 +176,24 @@ template std::shared_ptr for(std::size_t c = 0; c < post_data_bytes; c++) shifter.add_byte(post_data_value); } - while(segment.data.size() < expected_track_bytes) shifter.add_byte(0x00); + while(segment.data.size() < expected_track_bytes*8) shifter.add_byte(0x00); // Allow the amount of data written to be up to 10% more than the expected size. Which is generous. - std::size_t max_size = expected_track_bytes + (expected_track_bytes / 10); + const std::size_t max_size = (expected_track_bytes + (expected_track_bytes / 10)) * 8; if(segment.data.size() > max_size) segment.data.resize(max_size); - segment.number_of_bits = static_cast(segment.data.size() * 8); return std::shared_ptr(new Storage::Disk::PCMTrack(std::move(segment))); } -Encoder::Encoder(std::vector &target) : +Encoder::Encoder(std::vector &target) : target_(target) {} void Encoder::output_short(uint16_t value) { - target_.push_back(value >> 8); - target_.push_back(value & 0xff); + uint16_t mask = 0x8000; + while(mask) { + target_.push_back(!!(value & mask)); + mask >>= 1; + } } void Encoder::add_crc(bool incorrectly) { @@ -254,10 +256,10 @@ std::shared_ptr Storage::Encodings::MFM::GetMFMTrackWithSe 12500); // unintelligently: double the single-density bytes/rotation (or: 500kbps @ 300 rpm) } -std::unique_ptr Storage::Encodings::MFM::GetMFMEncoder(std::vector &target) { +std::unique_ptr Storage::Encodings::MFM::GetMFMEncoder(std::vector &target) { return std::unique_ptr(new MFMEncoder(target)); } -std::unique_ptr Storage::Encodings::MFM::GetFMEncoder(std::vector &target) { +std::unique_ptr Storage::Encodings::MFM::GetFMEncoder(std::vector &target) { return std::unique_ptr(new FMEncoder(target)); } diff --git a/Storage/Disk/Encodings/MFM/Encoder.hpp b/Storage/Disk/Encodings/MFM/Encoder.hpp index e571e6b53..643cbc610 100644 --- a/Storage/Disk/Encodings/MFM/Encoder.hpp +++ b/Storage/Disk/Encodings/MFM/Encoder.hpp @@ -44,7 +44,7 @@ std::shared_ptr GetFMTrackWithSectors(const std::vector &target); + Encoder(std::vector &target); virtual void add_byte(uint8_t input) = 0; virtual void add_index_address_mark() = 0; virtual void add_ID_address_mark() = 0; @@ -59,11 +59,11 @@ class Encoder { CRC::CCITT crc_generator_; private: - std::vector &target_; + std::vector &target_; }; -std::unique_ptr GetMFMEncoder(std::vector &target); -std::unique_ptr GetFMEncoder(std::vector &target); +std::unique_ptr GetMFMEncoder(std::vector &target); +std::unique_ptr GetFMEncoder(std::vector &target); } } diff --git a/Storage/Disk/Encodings/MFM/SegmentParser.cpp b/Storage/Disk/Encodings/MFM/SegmentParser.cpp index fb97738ac..410d21fa8 100644 --- a/Storage/Disk/Encodings/MFM/SegmentParser.cpp +++ b/Storage/Disk/Encodings/MFM/SegmentParser.cpp @@ -23,8 +23,11 @@ std::map Storage::Encodings::MFM:: std::size_t size = 0; std::size_t start_location = 0; - for(unsigned int bit = 0; bit < segment.number_of_bits; ++bit) { - shifter.add_input_bit(segment.bit(bit)); + std::size_t bit_cursor = 0; + for(const auto bit: segment.data) { + shifter.add_input_bit(bit ? 1 : 0); + ++bit_cursor; + switch(shifter.get_token()) { case Shifter::Token::None: case Shifter::Token::Sync: @@ -34,7 +37,7 @@ std::map Storage::Encodings::MFM:: case Shifter::Token::ID: new_sector.reset(new Storage::Encodings::MFM::Sector); is_reading = true; - start_location = bit; + start_location = bit_cursor; position = 0; shifter.set_should_obey_syncs(false); break; diff --git a/Storage/Disk/Track/PCMPatchedTrack.cpp b/Storage/Disk/Track/PCMPatchedTrack.cpp deleted file mode 100644 index 7c4275171..000000000 --- a/Storage/Disk/Track/PCMPatchedTrack.cpp +++ /dev/null @@ -1,222 +0,0 @@ -// -// PCMPatchedTrack.cpp -// Clock Signal -// -// Created by Thomas Harte on 15/12/2016. -// Copyright 2016 Thomas Harte. All rights reserved. -// - -#include "PCMPatchedTrack.hpp" - -#include - -using namespace Storage::Disk; - -PCMPatchedTrack::PCMPatchedTrack(std::shared_ptr 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); -} - -PCMPatchedTrack::PCMPatchedTrack(const PCMPatchedTrack &original) { - underlying_track_.reset(original.underlying_track_->clone()); - periods_ = original.periods_; - active_period_ = periods_.begin(); -} - -Track *PCMPatchedTrack::clone() const { - return new PCMPatchedTrack(*this); -} - -void PCMPatchedTrack::add_segment(const Time &start_time, const PCMSegment &segment, bool clamp_to_index_hole) { - std::shared_ptr event_source(new PCMSegmentEventSource(segment)); - - Time zero(0); - Time one(1); - Time end_time = start_time + event_source->get_length(); - if(clamp_to_index_hole && end_time > one) { - end_time = one; - } - 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 - assert(insertion_period.start_time <= one); - 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.segment_start_time += one; - 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. - insertion_error_ = current_time_ - seek_to(current_time_); -} - -void PCMPatchedTrack::insert_period(const Period &period) { - // find the existing period that the new period starts in - std::vector::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::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::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::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 - insertion_error_; - if(event_time < active_period_->end_time) { - current_time_ = event_time; - // TODO: this is spelt out in three steps because times don't necessarily do the sensible - // thing when 'negative' if intermediate result get simplified in the meantime. So fix Time. - event.length += extra_time; - event.length -= period_error; - event.length -= insertion_error_; - return event; - } - insertion_error_.set_zero(); - - // 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 end before reaching the time sought - active_period_ = periods_.begin(); - while(active_period_->end_time < time_since_index_hole) { - assert(active_period_ != periods_.end()); - active_period_++; - } - - // allow whatever storage represents the period found to perform its seek; calculation for periods - // with an event source is, in effect: seek_to(offset_into_segment + distance_into_period) - offset_into_segment. - if(active_period_->event_source) - current_time_ = active_period_->event_source->seek_to(active_period_->segment_start_time + time_since_index_hole - active_period_->start_time) + active_period_->start_time - active_period_->segment_start_time; - else - current_time_ = underlying_track_->seek_to(time_since_index_hole); - - // The assert below is disabled as it assumes too much about total precision. -// assert(current_time_ <= time_since_index_hole); - return current_time_; -} - -PCMPatchedTrack::Period::Period(const Period &original) : - start_time(original.start_time), end_time(original.end_time), segment_start_time(original.segment_start_time) { - if(original.event_source) event_source.reset(new PCMSegmentEventSource(*original.event_source)); -} - -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; -} diff --git a/Storage/Disk/Track/PCMPatchedTrack.hpp b/Storage/Disk/Track/PCMPatchedTrack.hpp deleted file mode 100644 index 710926c77..000000000 --- a/Storage/Disk/Track/PCMPatchedTrack.hpp +++ /dev/null @@ -1,75 +0,0 @@ -// -// 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 "Track.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 underlying_track); - - /*! - Copy constructor, for Track. - */ - PCMPatchedTrack(const PCMPatchedTrack &); - - /*! - Replaces whatever is currently on the track from @c start_position to @c start_position + segment length - with the contents of @c segment. - - @param start_time The time at which this segment begins. Must be in the range [0, 1). - @param segment The PCM segment to add. - @param clamp_to_index_hole If @c true then the new segment will be truncated if it overruns the index hole; - it will otherwise write over the index hole and continue. - */ - void add_segment(const Time &start_time, const PCMSegment &segment, bool clamp_to_index_hole); - - // To satisfy Storage::Disk::Track - Event get_next_event() override; - Time seek_to(const Time &time_since_index_hole) override; - Track *clone() const override; - - private: - std::shared_ptr underlying_track_; - - struct Period { - Time start_time, end_time; - Time segment_start_time; - std::shared_ptr 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 event_source) : - start_time(start_time), end_time(end_time), segment_start_time(segment_start_time), event_source(event_source) {} - Period(const Period &); - }; - std::vector periods_; - std::vector::iterator active_period_; - Time current_time_, insertion_error_; - - void insert_period(const Period &period); -}; - -} -} - -#endif /* PCMPatchedTrack_hpp */ diff --git a/Storage/Disk/Track/PCMSegment.cpp b/Storage/Disk/Track/PCMSegment.cpp index b4718dd42..d70265780 100644 --- a/Storage/Disk/Track/PCMSegment.cpp +++ b/Storage/Disk/Track/PCMSegment.cpp @@ -46,48 +46,22 @@ void PCMSegmentEventSource::reset() { } PCMSegment &PCMSegment::operator +=(const PCMSegment &rhs) { - if(!rhs.number_of_bits) return *this; - - assert(((rhs.number_of_bits+7) >> 3) == rhs.data.size()); - - if(number_of_bits&7) { - auto target_number_of_bits = number_of_bits + rhs.number_of_bits; - data.resize((target_number_of_bits + 7) >> 3); - - std::size_t first_byte = number_of_bits >> 3; - - const int shift = number_of_bits&7; - for(std::size_t source = 0; source < rhs.data.size(); ++source) { - data[first_byte + source] |= rhs.data[source] >> shift; - if(source*8 + static_cast(8 - shift) < rhs.number_of_bits) - data[first_byte + source + 1] = static_cast(rhs.data[source] << (8-shift)); - } - - number_of_bits = target_number_of_bits; - } else { - data.insert(data.end(), rhs.data.begin(), rhs.data.end()); - number_of_bits += rhs.number_of_bits; - } - - assert(((number_of_bits+7) >> 3) == data.size()); - + data.insert(data.end(), rhs.data.begin(), rhs.data.end()); return *this; } - 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 - std::size_t initial_bit_pointer = bit_pointer_; + const std::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)); + while(bit_pointer_ < segment_->data.size()) { + bool bit = segment_->data[bit_pointer_]; bit_pointer_++; // so this always points one beyond the most recent bit returned next_event_.length.length += segment_->length_of_a_bit.length; @@ -102,7 +76,7 @@ Storage::Disk::Track::Event PCMSegmentEventSource::get_next_event() { // 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) { + if(initial_bit_pointer <= segment_->data.size()) { next_event_.length.length += (segment_->length_of_a_bit.length >> 1); bit_pointer_++; } @@ -110,15 +84,15 @@ Storage::Disk::Track::Event PCMSegmentEventSource::get_next_event() { } Storage::Time PCMSegmentEventSource::get_length() { - return segment_->length_of_a_bit * segment_->number_of_bits; + return segment_->length_of_a_bit * static_cast(segment_->data.size()); } Storage::Time PCMSegmentEventSource::seek_to(const Time &time_from_start) { // test for requested time being beyond the end - Time length = get_length(); + const Time length = get_length(); if(time_from_start >= length) { next_event_.type = Track::Event::IndexHole; - bit_pointer_ = segment_->number_of_bits+1; + bit_pointer_ = segment_->data.size()+1; return length; } @@ -130,16 +104,23 @@ Storage::Time PCMSegmentEventSource::seek_to(const Time &time_from_start) { half_bit_length.length >>= 1; if(time_from_start < half_bit_length) { bit_pointer_ = 0; - Storage::Time zero; - return zero; + return Storage::Time(0); } // 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; + const Time relative_time = time_from_start - half_bit_length; bit_pointer_ = 1 + (relative_time / segment_->length_of_a_bit).get(); // map up to the correct amount of time return half_bit_length + segment_->length_of_a_bit * static_cast(bit_pointer_ - 1); } + +const PCMSegment &PCMSegmentEventSource::segment() const { + return *segment_; +} + +PCMSegment &PCMSegmentEventSource::segment() { + return *segment_; +} diff --git a/Storage/Disk/Track/PCMSegment.hpp b/Storage/Disk/Track/PCMSegment.hpp index b686c36f6..3e206c0a2 100644 --- a/Storage/Disk/Track/PCMSegment.hpp +++ b/Storage/Disk/Track/PCMSegment.hpp @@ -21,28 +21,117 @@ namespace Disk { /*! A segment of PCM-sampled data. - - Bits from each byte are taken MSB to LSB. */ struct PCMSegment { + /*! + Determines the amount of space that each bit of data occupies; + allows PCMSegments of different densities. + */ Time length_of_a_bit = Time(1); - unsigned int number_of_bits = 0; - std::vector data; - PCMSegment(Time length_of_a_bit, unsigned int number_of_bits, std::vector data) - : length_of_a_bit(length_of_a_bit), number_of_bits(number_of_bits), data(data) {} - PCMSegment() {} + /*! + This is the actual data, taking advantage of the std::vector + specialisation to use whatever one-bit-per-value encoding is + most suited to this architecture. - int bit(std::size_t index) const { - return (data[index >> 3] >> (7 ^ (index & 7)))&1; + If a value is @c true then a flux transition occurs in that window. + If it is @c false then no flux transition occurs. + */ + std::vector data; + + /*! + Constructs an instance of PCMSegment with the specified @c length_of_a_bit + and @c data. + */ + PCMSegment(Time length_of_a_bit, const std::vector &data) + : length_of_a_bit(length_of_a_bit), data(data) {} + + /*! + Constructs an instance of PCMSegment where each bit window is 1 unit of time + long and @c data is populated from the supplied @c source by serialising it + from MSB to LSB for @c number_of_bits. + */ + PCMSegment(size_t number_of_bits, const uint8_t *source) + : data(number_of_bits, false) { + for(size_t c = 0; c < number_of_bits; ++c) { + if((source[c >> 3] >> (7 ^ (c & 7)))&1) { + data[c] = true; + } + } } + /*! + Constructs an instance of PCMSegment where each bit window is the length + specified by @c length_of_a_bit, and @c data is populated from the supplied + @c source by serialising it from MSB to LSB for @c number_of_bits. + */ + PCMSegment(Time length_of_a_bit, size_t number_of_bits, const uint8_t *source) + : PCMSegment(number_of_bits, source) { + this->length_of_a_bit = length_of_a_bit; + } + + /*! + Constructs an instance of PCMSegment where each bit window is the length + specified by @c length_of_a_bit, and @c data is populated from the supplied + @c source by serialising it from MSB to LSB for @c number_of_bits. + */ + PCMSegment(Time length_of_a_bit, size_t number_of_bits, const std::vector &source) : + PCMSegment(length_of_a_bit, number_of_bits, source.data()) {} + + /*! + Constructs an instance of PCMSegment where each bit window is 1 unit of time + long and @c data is populated from the supplied @c source by serialising it + from MSB to LSB for @c number_of_bits. + */ + PCMSegment(size_t number_of_bits, const std::vector &source) : + PCMSegment(number_of_bits, source.data()) {} + + /*! + Constructs an instance of PCMSegment where each bit window is 1 unit of time + long and @c data is populated from the supplied @c source by serialising it + from MSB to LSB, assuming every bit provided is used. + */ + PCMSegment(const std::vector &source) : + PCMSegment(source.size() * 8, source.data()) {} + + /*! + Constructs an instance of PCMSegment where each bit window is 1 unit of time + long and @c data is empty. + */ + PCMSegment() {} + + /// Empties the PCMSegment. void clear() { - number_of_bits = 0; data.clear(); } + /*! + Produces a byte buffer where the contents of @c data are serialised into bytes + + If @c msb_first is @c true then each byte is expected to be deserialised from + MSB to LSB. + + If @c msb_first is @c false then each byte is expected to be deserialised from + LSB to MSB. + */ + std::vector byte_data(bool msb_first = true) const { + std::vector bytes((data.size() + 7) >> 3); + size_t pointer = 0; + const size_t pointer_mask = msb_first ? 7 : 0; + for(const auto bit: data) { + if(bit) bytes[pointer >> 3] |= 1 << ((pointer & 7) ^ pointer_mask); + ++pointer; + } + return bytes; + } + + /// Appends the data of @c rhs to the current data. Does not adjust @c length_of_a_bit. PCMSegment &operator +=(const PCMSegment &rhs); + + /// @returns the total amount of time occupied by all the data stored in this segment. + Time length() const { + return length_of_a_bit * static_cast(data.size()); + } }; /*! @@ -86,6 +175,12 @@ class PCMSegmentEventSource { */ Time get_length(); + /*! + @returns a reference to the underlying segment. + */ + const PCMSegment &segment() const; + PCMSegment &segment(); + private: std::shared_ptr segment_; std::size_t bit_pointer_; diff --git a/Storage/Disk/Track/PCMTrack.cpp b/Storage/Disk/Track/PCMTrack.cpp index 9252ef643..13ab2dfd2 100644 --- a/Storage/Disk/Track/PCMTrack.cpp +++ b/Storage/Disk/Track/PCMTrack.cpp @@ -8,6 +8,7 @@ #include "PCMTrack.hpp" #include "../../../NumberTheory/Factors.hpp" +#include "../../../Outputs/Log.hpp" using namespace Storage::Disk; @@ -17,18 +18,18 @@ PCMTrack::PCMTrack(const std::vector &segments) : PCMTrack() { // sum total length of all segments Time total_length; for(const auto &segment : segments) { - total_length += segment.length_of_a_bit * segment.number_of_bits; + total_length += segment.length_of_a_bit * static_cast(segment.data.size()); } total_length.simplify(); // 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(const auto &segment : segments) { - Time original_length_of_segment = segment.length_of_a_bit * segment.number_of_bits; + Time original_length_of_segment = segment.length_of_a_bit * static_cast(segment.data.size()); 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 = proportion_of_whole / static_cast(segment.data.size()); length_adjusted_segment.length_of_a_bit.simplify(); segment_event_sources_.emplace_back(length_adjusted_segment); } @@ -38,18 +39,56 @@ 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); + length_adjusted_segment.length_of_a_bit.clock_rate = static_cast(segment.data.size()); + segment_event_sources_.emplace_back(std::move(length_adjusted_segment)); } PCMTrack::PCMTrack(const PCMTrack &original) : PCMTrack() { segment_event_sources_ = original.segment_event_sources_; } +PCMTrack::PCMTrack(unsigned int bits_per_track) : PCMTrack() { + PCMSegment segment; + segment.length_of_a_bit.length = 1; + segment.length_of_a_bit.clock_rate = bits_per_track; + segment.data.resize(bits_per_track); + segment_event_sources_.emplace_back(segment); +} + +PCMTrack *PCMTrack::resampled_clone(Track *original, size_t bits_per_track) { + PCMTrack *pcm_original = dynamic_cast(original); + if(pcm_original) { + return pcm_original->resampled_clone(bits_per_track); + } + + ERROR("NOT IMPLEMENTED: resampling non-PCMTracks"); + return nullptr; +} + +bool PCMTrack::is_resampled_clone() { + return is_resampled_clone_; +} + Track *PCMTrack::clone() const { return new PCMTrack(*this); } +PCMTrack *PCMTrack::resampled_clone(size_t bits_per_track) { + // Create an empty track. + PCMTrack *const new_track = new PCMTrack(static_cast(bits_per_track)); + + // Plot all segments from this track onto the destination. + Time start_time; + for(const auto &event_source: segment_event_sources_) { + const PCMSegment &source = event_source.segment(); + new_track->add_segment(start_time, source, true); + start_time += source.length(); + } + + new_track->is_resampled_clone_ = true; + return new_track; +} + 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(); @@ -108,3 +147,54 @@ Storage::Time PCMTrack::seek_to(const Time &time_since_index_hole) { // the list of segments return accumulated_time; } + +void PCMTrack::add_segment(const Time &start_time, const PCMSegment &segment, bool clamp_to_index_hole) { + // Get a reference to the destination. + PCMSegment &destination = segment_event_sources_.front().segment(); + + // Determine the range to fill on the target segment. + const Time end_time = start_time + segment.length(); + const size_t start_bit = start_time.length * destination.data.size() / start_time.clock_rate; + const size_t end_bit = end_time.length * destination.data.size() / end_time.clock_rate; + const size_t target_width = end_bit - start_bit; + const size_t half_offset = target_width / (2 * segment.data.size()); + + if(clamp_to_index_hole || end_bit <= destination.data.size()) { + // If clamping is applied, just write a single segment, from the start_bit to whichever is + // closer of the end of track and the end_bit. + const size_t selected_end_bit = std::min(end_bit, destination.data.size()); + + // Reset the destination. + std::fill(destination.data.begin() + static_cast(start_bit), destination.data.begin() + static_cast(selected_end_bit), false); + + // Step through the source data from start to finish, stopping early if it goes out of bounds. + for(size_t bit = 0; bit < segment.data.size(); ++bit) { + if(segment.data[bit]) { + const size_t output_bit = start_bit + half_offset + (bit * target_width) / segment.data.size(); + if(output_bit >= destination.data.size()) return; + destination.data[output_bit] = true; + } + } + } else { + // Clamping is not enabled, so the supplied segment loops over the index hole, arbitrarily many times. + // So work backwards unless or until the original start position is reached, then stop. + + // This definitely runs over the index hole; check whether the whole track needs clearing, or whether + // a centre segment is untouched. + if(target_width >= destination.data.size()) { + std::fill(destination.data.begin(), destination.data.end(), false); + } else { + std::fill(destination.data.begin(), destination.data.begin() + static_cast(end_bit % destination.data.size()), false); + std::fill(destination.data.begin() + static_cast(start_bit), destination.data.end(), false); + } + + // Run backwards from final bit back to first, stopping early if overlapping the beginning. + for(off_t bit = static_cast(segment.data.size()-1); bit >= 0; --bit) { + if(segment.data[static_cast(bit)]) { + const size_t output_bit = start_bit + half_offset + (static_cast(bit) * target_width) / segment.data.size(); + if(output_bit <= end_bit - destination.data.size()) return; + destination.data[output_bit % destination.data.size()] = true; + } + } + } +} diff --git a/Storage/Disk/Track/PCMTrack.hpp b/Storage/Disk/Track/PCMTrack.hpp index e14f3fd3e..b80ef4f99 100644 --- a/Storage/Disk/Track/PCMTrack.hpp +++ b/Storage/Disk/Track/PCMTrack.hpp @@ -11,6 +11,8 @@ #include "Track.hpp" #include "PCMSegment.hpp" +#include "../../../ClockReceiver/ClockReceiver.hpp" + #include namespace Storage { @@ -41,12 +43,42 @@ class PCMTrack: public Track { */ PCMTrack(const PCMTrack &); + /*! + Creates a PCMTrack by sampling the original at a rate of @c bits_per_track. + */ + static PCMTrack *resampled_clone(Track *original, size_t bits_per_track); + // as per @c Track Event get_next_event() override; Time seek_to(const Time &time_since_index_hole) override; Track *clone() const override; + // Obtains a copy of this track, flattened to a single PCMSegment, which + // consists of @c bits_per_track potential flux transition points. + PCMTrack *resampled_clone(size_t bits_per_track); + bool is_resampled_clone(); + + /*! + Replaces whatever is currently on the track from @c start_position to @c start_position + segment length + with the contents of @c segment. + + This is a well-defined operation only for tracks with a single segment. The new segment will be resampled + to the track's underlying segment, which will be mutated. + + @param start_time The time at which this segment begins. Must be in the range [0, 1). + @param segment The PCM segment to add. + @param clamp_to_index_hole If @c true then the new segment will be truncated if it overruns the index hole; + it will otherwise write over the index hole and continue. + */ + void add_segment(const Time &start_time, const PCMSegment &segment, bool clamp_to_index_hole); + private: + /*! + Creates a PCMTrack with a single segment, consisting of @c bits_per_track flux windows, + initialised with no flux events. + */ + PCMTrack(unsigned int bits_per_track); + // storage for the segments that describe this track std::vector segment_event_sources_; @@ -54,6 +86,7 @@ class PCMTrack: public Track { std::size_t segment_pointer_; PCMTrack(); + bool is_resampled_clone_ = false; }; } diff --git a/Storage/Disk/Track/TrackSerialiser.cpp b/Storage/Disk/Track/TrackSerialiser.cpp index b575816a9..1741419ca 100644 --- a/Storage/Disk/Track/TrackSerialiser.cpp +++ b/Storage/Disk/Track/TrackSerialiser.cpp @@ -17,17 +17,19 @@ Storage::Disk::PCMSegment Storage::Disk::track_serialisation(const Track &track, DigitalPhaseLockedLoop pll(100, history_size); std::unique_ptr track_copy(track.clone()); + // ResultAccumulator exists to append whatever comes out of the PLL to + // its PCMSegment. struct ResultAccumulator: public DigitalPhaseLockedLoop::Delegate { PCMSegment result; void digital_phase_locked_loop_output_bit(int value) { - result.data.resize(1 + (result.number_of_bits >> 3)); - if(value) result.data[result.number_of_bits >> 3] |= 0x80 >> (result.number_of_bits & 7); - result.number_of_bits++; + result.data.push_back(!!value); } } result_accumulator; - result_accumulator.result.number_of_bits = 0; result_accumulator.result.length_of_a_bit = length_of_a_bit; + // Obtain a length multiplier which is 100 times the reciprocal + // of the expected bit length. So a perfect bit length from + // the source data will come out as 100 ticks. Time length_multiplier = Time(100*length_of_a_bit.clock_rate, length_of_a_bit.length); length_multiplier.simplify();