1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-25 18:30:07 +00:00

Implemented a very basic PCMTrack test, nevertheless revealing an oversight in PCMSegmentEventSource related to improperly counting to the index hole if the final bit is set. Took that as a message that I should comment and document the event source.

This commit is contained in:
Thomas Harte 2016-12-18 22:53:24 -05:00
parent a6354ebb01
commit e081f224b6
7 changed files with 128 additions and 4 deletions

View File

@ -385,6 +385,7 @@
4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */; };
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD468F51D8DF41D0084958B /* 1770.cpp */; };
4BD4A8CD1E077E8A0020D856 /* PCMSegment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F961E060CF000BFDA12 /* PCMSegment.cpp */; };
4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */; };
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; };
4BD69F941D98760000243FE1 /* AcornADF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD69F921D98760000243FE1 /* AcornADF.cpp */; };
4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE77A2C1D84ADFB00BC3827 /* File.cpp */; };
@ -901,6 +902,7 @@
4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.hpp; sourceTree = "<group>"; };
4BD468F51D8DF41D0084958B /* 1770.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = 1770.cpp; path = 1770/1770.cpp; sourceTree = "<group>"; };
4BD468F61D8DF41D0084958B /* 1770.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 1770.hpp; path = 1770/1770.hpp; sourceTree = "<group>"; };
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMTrackTests.mm; sourceTree = "<group>"; };
4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = "<group>"; };
4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CSBestEffortUpdater.m; path = Updater/CSBestEffortUpdater.m; sourceTree = "<group>"; };
4BD69F921D98760000243FE1 /* AcornADF.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AcornADF.cpp; sourceTree = "<group>"; };
@ -1687,8 +1689,9 @@
isa = PBXGroup;
children = (
4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */,
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */,
4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */,
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */,
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */,
4BB73EB81B587A5100552FC2 /* Info.plist */,
4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */,
4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */,
@ -2463,6 +2466,7 @@
4B3BA0C31D318AEC005DD7A7 /* C1540Tests.swift in Sources */,
4B1414621B58888700E04248 /* KlausDormannTests.swift in Sources */,
4B1414601B58885000E04248 /* WolfgangLorenzTests.swift in Sources */,
4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */,
4B049CDD1DA3C82F00322067 /* BCDTest.swift in Sources */,
4B121F951E05E66800BFDA12 /* PCMPatchedTrackTests.mm in Sources */,
);

View File

@ -0,0 +1,8 @@
//
// PCMTrackTests.h
// Clock Signal
//
// Created by Thomas Harte on 18/12/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//

View File

@ -0,0 +1,54 @@
//
// PCMTrackTests.m
// Clock Signal
//
// Created by Thomas Harte on 18/12/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import <XCTest/XCTest.h>
#include "PCMTrack.hpp"
@interface PCMTrackTests : XCTestCase
@end
@implementation PCMTrackTests
- (Storage::Disk::PCMTrack)multiSpeedTrack
{
Storage::Disk::PCMSegment quickSegment, slowSegment;
quickSegment.data = {0xff};
quickSegment.number_of_bits = 8;
quickSegment.length_of_a_bit.length = 1;
quickSegment.length_of_a_bit.clock_rate = 100;
slowSegment.data = {0xff};
slowSegment.number_of_bits = 8;
slowSegment.length_of_a_bit.length = 1;
slowSegment.length_of_a_bit.clock_rate = 3;
return Storage::Disk::PCMTrack({quickSegment, slowSegment});
}
- (void)testMultispeedTrack
{
Storage::Disk::PCMTrack track = self.multiSpeedTrack;
std::vector<Storage::Disk::Track::Event> events;
Storage::Time total_length;
do {
events.push_back(track.get_next_event());
total_length += events.back().length;
} while(events.back().type != Storage::Disk::Track::Event::IndexHole);
XCTAssert(events.size() == 17, "Should have received 17 events; got %lu", events.size());
total_length.simplify();
XCTAssert(total_length.length == 1 && total_length.clock_rate == 1, "Events should have summed to a total time of 1; instead got %u/%u", total_length.length, total_length.clock_rate);
Storage::Time transition_length = events[0].length + events.back().length;
XCTAssert(events[8].length == transition_length, "Time taken in transition between speed zones should be half of a bit length in the first part plus half of a bit length in the second");
}
@end

View File

@ -13,38 +13,64 @@ using namespace Storage::Disk;
PCMSegmentEventSource::PCMSegmentEventSource(const PCMSegment &segment) :
segment_(segment)
{
// add an extra bit of storage at the bottom if one is going to be needed;
// events returned are going to be in integral multiples of the length of a bit
// other than the very first and very last which will include a half bit length
if(segment_.length_of_a_bit.length&1)
{
segment_.length_of_a_bit.length <<= 1;
segment_.length_of_a_bit.clock_rate <<= 1;
}
// load up the clock rate once only
next_event_.length.clock_rate = segment_.length_of_a_bit.clock_rate;
// set initial conditions
reset();
}
void PCMSegmentEventSource::reset()
{
// start with the first bit to be considered the zeroth, and assume that it'll be
// flux transitions for the foreseeable
bit_pointer_ = 0;
next_event_.type = Track::Event::FluxTransition;
}
Storage::Disk::Track::Event PCMSegmentEventSource::get_next_event()
{
// track the initial bit pointer for potentially considering whether this was an
// initial index hole or a subsequent one later on
size_t initial_bit_pointer = bit_pointer_;
// if starting from the beginning, pull half a bit backward, as if the initial bit
// is set, it should be in the centre of its window
next_event_.length.length = bit_pointer_ ? 0 : -(segment_.length_of_a_bit.length >> 1);
// search for the next bit that is set, if any
const uint8_t *segment_data = segment_.data.data();
while(bit_pointer_ < segment_.number_of_bits)
{
int bit = segment_data[bit_pointer_ >> 3] & (0x80 >> (bit_pointer_&7));
bit_pointer_++;
bit_pointer_++; // so this always points one beyond the most recent bit returned
next_event_.length.length += segment_.length_of_a_bit.length;
// if this bit is set, return the event
if(bit) return next_event_;
}
if(initial_bit_pointer < segment_.number_of_bits) next_event_.length.length += (segment_.length_of_a_bit.length >> 1);
// if the end is reached without a bit being set, it'll be index holes from now on
next_event_.type = Track::Event::IndexHole;
// test whether this is the very first time that bits have been exhausted. If so then
// allow an extra half bit's length to run from the position of the potential final transition
// event to the end of the segment. Otherwise don't allow any extra time, as it's already
// been consumed
if(initial_bit_pointer <= segment_.number_of_bits)
{
next_event_.length.length += (segment_.length_of_a_bit.length >> 1);
bit_pointer_++;
}
return next_event_;
}
@ -60,7 +86,7 @@ Storage::Time PCMSegmentEventSource::seek_to(const Time &time_from_start)
if(time_from_start >= length)
{
next_event_.type = Track::Event::IndexHole;
bit_pointer_ = segment_.number_of_bits;
bit_pointer_ = segment_.number_of_bits+1;
return length;
}

View File

@ -29,14 +29,39 @@ struct PCMSegment {
std::vector<uint8_t> data;
};
/*!
Provides a stream of events by inspecting a PCMSegment.
*/
class PCMSegmentEventSource {
public:
/*!
Constructs a @c PCMSegmentEventSource that will derive events from @c segment.
The event source is initially @c reset.
*/
PCMSegmentEventSource(const PCMSegment &segment);
/*!
@returns the next event that will occur in this event stream.
*/
Track::Event get_next_event();
/*!
Resets the event source to the beginning of its event stream, exactly as if
it has just been constructed.
*/
void reset();
/*!
Seeks as close to @c time_from_start as the event source can manage while not
exceeding it.
@returns the time the source is now at.
*/
Time seek_to(const Time &time_from_start);
/*!
@returns the total length of the stream of data that the source will provide.
*/
Time get_length();
private:

View File

@ -22,6 +22,7 @@ PCMTrack::PCMTrack(const std::vector<PCMSegment> &segments) : PCMTrack()
{
total_length += segment.length_of_a_bit * segment.number_of_bits;
}
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
@ -29,6 +30,7 @@ PCMTrack::PCMTrack(const std::vector<PCMSegment> &segments) : PCMTrack()
{
Time original_length_of_segment = segment.length_of_a_bit * segment.number_of_bits;
Time proportion_of_whole = original_length_of_segment / total_length;
proportion_of_whole.simplify();
PCMSegment length_adjusted_segment = segment;
length_adjusted_segment.length_of_a_bit = proportion_of_whole / segment.number_of_bits;
length_adjusted_segment.length_of_a_bit.simplify();

View File

@ -67,6 +67,11 @@ struct Time {
return other.clock_rate * length >= clock_rate * other.length;
}
inline bool operator == (const Time &other) const
{
return other.clock_rate * length == clock_rate * other.length;
}
inline Time operator + (const Time &other) const
{
Time result;