mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-25 16:31:42 +00:00
Merge pull request #489 from TomHarte/OricDiskII
Simplifies disk track storage and writing implementation
This commit is contained in:
commit
a391d0f4ae
@ -165,7 +165,7 @@ class HalfCycles: public WrappedInt<HalfCycles> {
|
||||
constexpr HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(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);
|
||||
}
|
||||
|
||||
|
@ -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 = "<group>"; };
|
||||
4B0F94FD208C1A1600FE41D9 /* NIB.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = NIB.hpp; sourceTree = "<group>"; };
|
||||
4B0F9500208C42A300FE41D9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Target.hpp; path = AppleII/Target.hpp; sourceTree = "<group>"; };
|
||||
4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMPatchedTrackTests.mm; sourceTree = "<group>"; };
|
||||
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMSegmentEventSourceTests.mm; sourceTree = "<group>"; };
|
||||
4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Keyboard.cpp; path = MSX/Keyboard.cpp; sourceTree = "<group>"; };
|
||||
4B12C0EC1FCFA98D005BFD93 /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Keyboard.hpp; path = MSX/Keyboard.hpp; sourceTree = "<group>"; };
|
||||
@ -827,8 +823,6 @@
|
||||
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>"; };
|
||||
4B4518711F75E91800926311 /* PCMPatchedTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMPatchedTrack.cpp; sourceTree = "<group>"; };
|
||||
4B4518721F75E91800926311 /* PCMPatchedTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMPatchedTrack.hpp; sourceTree = "<group>"; };
|
||||
4B4518731F75E91800926311 /* PCMSegment.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMSegment.cpp; sourceTree = "<group>"; };
|
||||
4B4518741F75E91800926311 /* PCMSegment.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMSegment.hpp; sourceTree = "<group>"; };
|
||||
4B4518751F75E91800926311 /* PCMTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMTrack.cpp; sourceTree = "<group>"; };
|
||||
@ -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;
|
||||
|
@ -1,195 +0,0 @@
|
||||
//
|
||||
// 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, false);
|
||||
}
|
||||
|
||||
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(const 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, false);
|
||||
}
|
||||
|
||||
#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
|
@ -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<uint8_t> data = {0xff, 0x00, 0xff, 0x00};
|
||||
Storage::Disk::PCMSegment alternatingFFs(Storage::Time(1, 10), data.size()*8, data);
|
||||
return Storage::Disk::PCMSegmentEventSource(alternatingFFs);
|
||||
}
|
||||
|
||||
|
@ -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<Storage::Disk::PCMSegment> 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);
|
||||
|
@ -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"
|
||||
|
@ -79,16 +79,11 @@ std::shared_ptr<Track> D64::get_track_at_position(Track::Address address) {
|
||||
//
|
||||
// = 349 GCR bytes per sector
|
||||
|
||||
PCMSegment track;
|
||||
std::size_t track_bytes = 349 * static_cast<std::size_t>(sectors_by_zone[zone]);
|
||||
track.number_of_bits = static_cast<unsigned int>(track_bytes) * 8;
|
||||
track.data.resize(track_bytes);
|
||||
uint8_t *data = &track.data[0];
|
||||
|
||||
memset(data, 0, track_bytes);
|
||||
std::vector<uint8_t> 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<size_t>(sector) * 349];
|
||||
sector_data[0] = sector_data[1] = sector_data[2] = 0xff;
|
||||
|
||||
uint8_t sector_number = static_cast<uint8_t>(sector); // sectors count from 0
|
||||
@ -141,5 +136,5 @@ std::shared_ptr<Track> D64::get_track_at_position(Track::Address address) {
|
||||
Encodings::CommodoreGCR::encode_block(§or_data[target_data_offset], end_of_data);
|
||||
}
|
||||
|
||||
return std::shared_ptr<Track>(new PCMTrack(std::move(track)));
|
||||
return std::shared_ptr<Track>(new PCMTrack(PCMSegment(data)));
|
||||
}
|
||||
|
@ -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<unsigned int>(segment.data.size() * 8);
|
||||
}
|
||||
|
||||
return std::make_shared<PCMTrack>(segments);
|
||||
}
|
||||
|
@ -43,38 +43,33 @@ std::shared_ptr<Track> G64::get_track_at_position(Track::Address address) {
|
||||
file_.seek(static_cast<long>((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<long>(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<uint8_t> track_contents(track_length);
|
||||
file_.read(&track_contents[0], track_length);
|
||||
const std::vector<uint8_t> track_contents = file_.read(track_length);
|
||||
|
||||
// seek to this track's entry in the speed zone table
|
||||
file_.seek(static_cast<long>((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<long>(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<Track> G64::get_track_at_position(Track::Address address) {
|
||||
if(byte_speed != current_speed || byte == static_cast<uint16_t>(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<Track> 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<unsigned int>(speed_zone_offset));
|
||||
segment.data = std::move(track_contents);
|
||||
PCMSegment segment(
|
||||
Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(static_cast<unsigned int>(speed_zone_offset)),
|
||||
track_length * 8,
|
||||
track_contents
|
||||
);
|
||||
|
||||
resulting_track.reset(new PCMTrack(std::move(segment)));
|
||||
}
|
||||
|
@ -26,9 +26,6 @@ HFE::HFE(const std::string &file_name) :
|
||||
track_list_offset_ = static_cast<long>(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<long>(file_.get16le()) << 9;
|
||||
uint16_t track_length = file_.get16le();
|
||||
long track_offset = static_cast<long>(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<Track> HFE::get_track_at_position(Track::Address address) {
|
||||
@ -64,21 +61,42 @@ std::shared_ptr<Track> HFE::get_track_at_position(Track::Address address) {
|
||||
std::lock_guard<std::mutex> 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<uint16_t>(std::min(256, track_length - c));
|
||||
file_.read(&segment.data[c], length);
|
||||
std::vector<uint8_t> 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<size_t>(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<PCMTrack>(segment);
|
||||
}
|
||||
|
||||
@ -88,9 +106,11 @@ void HFE::set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &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<uint16_t>(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<uint8_t> byte_segment = segment.byte_data(false);
|
||||
uint16_t data_length = std::min(static_cast<uint16_t>(byte_segment.size()), track_length);
|
||||
|
||||
lock_guard.lock();
|
||||
seek_track(track.first);
|
||||
@ -98,7 +118,7 @@ void HFE::set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tra
|
||||
uint16_t c = 0;
|
||||
while(c < data_length) {
|
||||
uint16_t length = static_cast<uint16_t>(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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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<size_t> 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<int>(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<uint8_t> data_segment(
|
||||
track_data.begin() + static_cast<off_t>(index),
|
||||
track_data.begin() + static_cast<off_t>(location));
|
||||
data_segment.number_of_bits = static_cast<unsigned int>(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<Track::Address, std::shared_ptr<Track>> &tra
|
||||
std::vector<uint8_t> track;
|
||||
track.reserve(track_length);
|
||||
uint8_t shifter = 0;
|
||||
for(unsigned int bit = 0; bit < segment.number_of_bits; ++bit) {
|
||||
shifter = static_cast<uint8_t>((shifter << 1) | segment.bit(bit));
|
||||
for(const auto bit: segment.data) {
|
||||
shifter = static_cast<uint8_t>((shifter << 1) | (bit ? 1 : 0));
|
||||
if(shifter & 0x80) {
|
||||
track.push_back(shifter);
|
||||
shifter = 0;
|
||||
|
@ -108,7 +108,6 @@ std::shared_ptr<Track> OricMFMDSK::get_track_at_position(Track::Address address)
|
||||
}
|
||||
}
|
||||
|
||||
segment.number_of_bits = static_cast<unsigned int>(segment.data.size() * 8);
|
||||
return std::make_shared<PCMTrack>(segment);
|
||||
}
|
||||
|
||||
@ -123,8 +122,8 @@ void OricMFMDSK::set_tracks(const std::map<Track::Address, std::shared_ptr<Track
|
||||
int offset = 0;
|
||||
bool capture_size = false;
|
||||
|
||||
for(unsigned int bit = 0; bit < segment.number_of_bits; ++bit) {
|
||||
shifter.add_input_bit(segment.bit(bit));
|
||||
for(const auto bit : segment.data) {
|
||||
shifter.add_input_bit(bit ? 1 : 0);
|
||||
if(shifter.get_token() == Storage::Encodings::MFM::Shifter::Token::None) continue;
|
||||
parsed_track.push_back(shifter.get_byte());
|
||||
|
||||
|
@ -106,7 +106,8 @@ std::shared_ptr<Track> WOZ::get_track_at_position(Track::Address address) {
|
||||
if(offset == NoSuchTrack) return nullptr;
|
||||
|
||||
// Seek to the real track.
|
||||
PCMSegment track_contents;
|
||||
std::vector<uint8_t> track_contents;
|
||||
size_t number_of_bits;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock_guard(file_.get_file_access_mutex());
|
||||
file_.seek(offset, SEEK_SET);
|
||||
@ -114,13 +115,12 @@ std::shared_ptr<Track> 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<uint16_t>(6646*8));
|
||||
}
|
||||
|
||||
return std::shared_ptr<PCMTrack>(new PCMTrack(track_contents));
|
||||
return std::shared_ptr<PCMTrack>(new PCMTrack(PCMSegment(number_of_bits, track_contents)));
|
||||
}
|
||||
|
||||
void WOZ::set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) {
|
||||
@ -129,13 +129,14 @@ void WOZ::set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tra
|
||||
auto segment = Storage::Disk::track_serialisation(*pair.second, Storage::Time(1, 50000));
|
||||
|
||||
auto offset = static_cast<std::size_t>(file_offset(pair.first) - 12);
|
||||
memcpy(&post_crc_contents_[offset - 12], segment.data.data(), segment.number_of_bits >> 3);
|
||||
std::vector<uint8_t> 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<uint8_t>(segment.number_of_bits >> 3);
|
||||
post_crc_contents_[offset + 6647] = static_cast<uint8_t>(segment.number_of_bits >> 11);
|
||||
post_crc_contents_[offset + 6648] = static_cast<uint8_t>(segment.number_of_bits);
|
||||
post_crc_contents_[offset + 6649] = static_cast<uint8_t>(segment.number_of_bits >> 8);
|
||||
post_crc_contents_[offset + 6646] = static_cast<uint8_t>(segment.data.size() >> 3);
|
||||
post_crc_contents_[offset + 6647] = static_cast<uint8_t>(segment.data.size() >> 11);
|
||||
post_crc_contents_[offset + 6648] = static_cast<uint8_t>(segment.data.size());
|
||||
post_crc_contents_[offset + 6649] = static_cast<uint8_t>(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;
|
||||
|
@ -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<PCMPatchedTrack>(track_);
|
||||
if(!patched_track_) {
|
||||
patched_track_.reset(new PCMPatchedTrack(track_));
|
||||
patched_track_ = std::dynamic_pointer_cast<PCMTrack>(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_);
|
||||
|
@ -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<PCMPatchedTrack> patched_track_;
|
||||
// for addition to a (high-resolution) PCM track.
|
||||
std::shared_ptr<PCMTrack> patched_track_;
|
||||
PCMSegment write_segment_;
|
||||
Time write_start_time_;
|
||||
|
||||
|
@ -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<size_t>(((length * bit_size) + 7) >> 3), 0);
|
||||
// Reserve sufficient storage.
|
||||
segment.data.reserve(static_cast<size_t>(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<unsigned int>(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<uint8_t> 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<uint8_t>(((value) >> 1) | 0xaa); \
|
||||
segment.data[index+1] = static_cast<uint8_t>((value) | 0xaa); \
|
||||
data[index+0] = static_cast<uint8_t>(((value) >> 1) | 0xaa); \
|
||||
data[index+1] = static_cast<uint8_t>((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<uint8_t> 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<unsigned int>(segment.data.size() * 8);
|
||||
std::vector<uint8_t> 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<uint8_t>(
|
||||
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<uint8_t>(
|
||||
(bit_reverse[source[84]&3] << 0) |
|
||||
(bit_reverse[source[170]&3] << 2)
|
||||
);
|
||||
segment.data[88] =
|
||||
data[88] =
|
||||
static_cast<uint8_t>(
|
||||
(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);
|
||||
}
|
||||
|
@ -49,9 +49,9 @@ std::map<std::size_t, Sector> 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<uint_fast8_t>((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<uint_fast8_t>((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<std::size_t, Sector> Storage::Encodings::AppleGCR::sectors_from_segment
|
||||
new_sector.reset(new Sector);
|
||||
new_sector->data.reserve(412);
|
||||
} else {
|
||||
sector_location = static_cast<std::size_t>(bit % segment.number_of_bits);
|
||||
sector_location = static_cast<std::size_t>(bit % segment.data.size());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -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) {
|
||||
|
@ -18,7 +18,7 @@ using namespace Storage::Encodings::MFM;
|
||||
|
||||
class MFMEncoder: public Encoder {
|
||||
public:
|
||||
MFMEncoder(std::vector<uint8_t> &target) : Encoder(target) {}
|
||||
MFMEncoder(std::vector<bool> &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<uint8_t> &target) : Encoder(target) {}
|
||||
FMEncoder(std::vector<bool> &target) : Encoder(target) {}
|
||||
|
||||
void add_byte(uint8_t input) {
|
||||
crc_generator_.add(input);
|
||||
@ -127,7 +127,7 @@ template<class T> std::shared_ptr<Storage::Disk::Track>
|
||||
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<class T> std::shared_ptr<Storage::Disk::Track>
|
||||
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<unsigned int>(segment.data.size() * 8);
|
||||
return std::shared_ptr<Storage::Disk::Track>(new Storage::Disk::PCMTrack(std::move(segment)));
|
||||
}
|
||||
|
||||
Encoder::Encoder(std::vector<uint8_t> &target) :
|
||||
Encoder::Encoder(std::vector<bool> &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::Disk::Track> Storage::Encodings::MFM::GetMFMTrackWithSe
|
||||
12500); // unintelligently: double the single-density bytes/rotation (or: 500kbps @ 300 rpm)
|
||||
}
|
||||
|
||||
std::unique_ptr<Encoder> Storage::Encodings::MFM::GetMFMEncoder(std::vector<uint8_t> &target) {
|
||||
std::unique_ptr<Encoder> Storage::Encodings::MFM::GetMFMEncoder(std::vector<bool> &target) {
|
||||
return std::unique_ptr<Encoder>(new MFMEncoder(target));
|
||||
}
|
||||
|
||||
std::unique_ptr<Encoder> Storage::Encodings::MFM::GetFMEncoder(std::vector<uint8_t> &target) {
|
||||
std::unique_ptr<Encoder> Storage::Encodings::MFM::GetFMEncoder(std::vector<bool> &target) {
|
||||
return std::unique_ptr<Encoder>(new FMEncoder(target));
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ std::shared_ptr<Storage::Disk::Track> GetFMTrackWithSectors(const std::vector<co
|
||||
|
||||
class Encoder {
|
||||
public:
|
||||
Encoder(std::vector<uint8_t> &target);
|
||||
Encoder(std::vector<bool> &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<uint8_t> &target_;
|
||||
std::vector<bool> &target_;
|
||||
};
|
||||
|
||||
std::unique_ptr<Encoder> GetMFMEncoder(std::vector<uint8_t> &target);
|
||||
std::unique_ptr<Encoder> GetFMEncoder(std::vector<uint8_t> &target);
|
||||
std::unique_ptr<Encoder> GetMFMEncoder(std::vector<bool> &target);
|
||||
std::unique_ptr<Encoder> GetFMEncoder(std::vector<bool> &target);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -23,8 +23,11 @@ std::map<std::size_t, Storage::Encodings::MFM::Sector> 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<std::size_t, Storage::Encodings::MFM::Sector> 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;
|
||||
|
@ -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 <cassert>
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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<PCMSegmentEventSource> 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<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 - 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;
|
||||
}
|
@ -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<Track> 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<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) {}
|
||||
Period(const Period &);
|
||||
};
|
||||
std::vector<Period> periods_;
|
||||
std::vector<Period>::iterator active_period_;
|
||||
Time current_time_, insertion_error_;
|
||||
|
||||
void insert_period(const Period &period);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* PCMPatchedTrack_hpp */
|
@ -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<std::size_t>(8 - shift) < rhs.number_of_bits)
|
||||
data[first_byte + source + 1] = static_cast<uint8_t>(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<unsigned int>(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<unsigned int>();
|
||||
|
||||
// map up to the correct amount of time
|
||||
return half_bit_length + segment_->length_of_a_bit * static_cast<unsigned int>(bit_pointer_ - 1);
|
||||
}
|
||||
|
||||
const PCMSegment &PCMSegmentEventSource::segment() const {
|
||||
return *segment_;
|
||||
}
|
||||
|
||||
PCMSegment &PCMSegmentEventSource::segment() {
|
||||
return *segment_;
|
||||
}
|
||||
|
@ -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<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() {}
|
||||
/*!
|
||||
This is the actual data, taking advantage of the std::vector<bool>
|
||||
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<bool> 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<bool> &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<uint8_t> &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<uint8_t> &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<uint8_t> &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<uint8_t> byte_data(bool msb_first = true) const {
|
||||
std::vector<uint8_t> 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<unsigned int>(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<PCMSegment> segment_;
|
||||
std::size_t bit_pointer_;
|
||||
|
@ -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<PCMSegment> &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<unsigned int>(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<unsigned int>(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<unsigned int>(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<unsigned int>(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<PCMTrack *>(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<unsigned int>(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<off_t>(start_bit), destination.data.begin() + static_cast<off_t>(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<off_t>(end_bit % destination.data.size()), false);
|
||||
std::fill(destination.data.begin() + static_cast<off_t>(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<off_t>(segment.data.size()-1); bit >= 0; --bit) {
|
||||
if(segment.data[static_cast<size_t>(bit)]) {
|
||||
const size_t output_bit = start_bit + half_offset + (static_cast<size_t>(bit) * target_width) / segment.data.size();
|
||||
if(output_bit <= end_bit - destination.data.size()) return;
|
||||
destination.data[output_bit % destination.data.size()] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
#include "Track.hpp"
|
||||
#include "PCMSegment.hpp"
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
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<PCMSegmentEventSource> segment_event_sources_;
|
||||
|
||||
@ -54,6 +86,7 @@ class PCMTrack: public Track {
|
||||
std::size_t segment_pointer_;
|
||||
|
||||
PCMTrack();
|
||||
bool is_resampled_clone_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -17,17 +17,19 @@ Storage::Disk::PCMSegment Storage::Disk::track_serialisation(const Track &track,
|
||||
DigitalPhaseLockedLoop pll(100, history_size);
|
||||
std::unique_ptr<Track> 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();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user