1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-26 08:49:37 +00:00

Merge pull request #489 from TomHarte/OricDiskII

Simplifies disk track storage and writing implementation
This commit is contained in:
Thomas Harte 2018-07-02 22:09:58 -04:00 committed by GitHub
commit a391d0f4ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 424 additions and 737 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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