mirror of
https://github.com/TomHarte/CLK.git
synced 2025-08-15 14:27:29 +00:00
Merge pull request #816 from TomHarte/RelaxedTracks
Corrects a regression in disk image handling; liberalises Disk II analyser
This commit is contained in:
@@ -191,7 +191,6 @@ void DiskII::set_state_machine(const std::vector<uint8_t> &state_machine) {
|
|||||||
((source_address&0x02) ? 0x02 : 0x00);
|
((source_address&0x02) ? 0x02 : 0x00);
|
||||||
uint8_t source_value = state_machine[source_address];
|
uint8_t source_value = state_machine[source_address];
|
||||||
|
|
||||||
// Remap into Beneath Apple Pro-DOS value form.
|
|
||||||
source_value =
|
source_value =
|
||||||
((source_value & 0x80) ? 0x10 : 0x0) |
|
((source_value & 0x80) ? 0x10 : 0x0) |
|
||||||
((source_value & 0x40) ? 0x20 : 0x0) |
|
((source_value & 0x40) ? 0x20 : 0x0) |
|
||||||
|
@@ -20,7 +20,9 @@ DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sec
|
|||||||
} else {
|
} else {
|
||||||
roms = rom_fetcher({
|
roms = rom_fetcher({
|
||||||
{"DiskII", "the Disk II 13-sector boot ROM", "boot-13.rom", 256, 0xd34eb2ff},
|
{"DiskII", "the Disk II 13-sector boot ROM", "boot-13.rom", 256, 0xd34eb2ff},
|
||||||
{"DiskII", "the Disk II 13-sector state machine ROM", "state-machine-13.rom", 256, 0x62e22620 }
|
{"DiskII", "the Disk II 16-sector state machine ROM", "state-machine-16.rom", 256, { 0x9796a238, 0xb72a2c70 } }
|
||||||
|
// {"DiskII", "the Disk II 13-sector state machine ROM", "state-machine-13.rom", 256, 0x62e22620 }
|
||||||
|
/* TODO: once the DiskII knows how to decode common images of the 13-sector state machine, use that instead of the 16-sector. */
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if(!roms[0] || !roms[1]) {
|
if(!roms[0] || !roms[1]) {
|
||||||
|
@@ -61,6 +61,12 @@ class Disk {
|
|||||||
@returns whether the disk image is read only. Defaults to @c true if not overridden.
|
@returns whether the disk image is read only. Defaults to @c true if not overridden.
|
||||||
*/
|
*/
|
||||||
virtual bool get_is_read_only() = 0;
|
virtual bool get_is_read_only() = 0;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@returns @c true if the tracks at the two addresses are different. @c false if they are the same track.
|
||||||
|
This can avoid some degree of work when disk images offer sub-head-position precision.
|
||||||
|
*/
|
||||||
|
virtual bool tracks_differ(Track::Address, Track::Address) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -67,6 +67,12 @@ class DiskImage {
|
|||||||
@returns whether the disk image is read only. Defaults to @c true if not overridden.
|
@returns whether the disk image is read only. Defaults to @c true if not overridden.
|
||||||
*/
|
*/
|
||||||
virtual bool get_is_read_only() { return true; }
|
virtual bool get_is_read_only() { return true; }
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@returns @c true if the tracks at the two addresses are different. @c false if they are the same track.
|
||||||
|
This can avoid some degree of work when disk images offer sub-head-position precision.
|
||||||
|
*/
|
||||||
|
virtual bool tracks_differ(Track::Address lhs, Track::Address rhs) { return lhs != rhs; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class DiskImageHolderBase: public Disk {
|
class DiskImageHolderBase: public Disk {
|
||||||
@@ -93,6 +99,7 @@ template <typename T> class DiskImageHolder: public DiskImageHolderBase {
|
|||||||
void set_track_at_position(Track::Address address, const std::shared_ptr<Track> &track);
|
void set_track_at_position(Track::Address address, const std::shared_ptr<Track> &track);
|
||||||
void flush_tracks();
|
void flush_tracks();
|
||||||
bool get_is_read_only();
|
bool get_is_read_only();
|
||||||
|
bool tracks_differ(Track::Address lhs, Track::Address rhs);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
T disk_image_;
|
T disk_image_;
|
||||||
|
@@ -58,3 +58,7 @@ template <typename T> std::shared_ptr<Track> DiskImageHolder<T>::get_track_at_po
|
|||||||
template <typename T> DiskImageHolder<T>::~DiskImageHolder() {
|
template <typename T> DiskImageHolder<T>::~DiskImageHolder() {
|
||||||
if(update_queue_) update_queue_->flush();
|
if(update_queue_) update_queue_->flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T> bool DiskImageHolder<T>::tracks_differ(Track::Address lhs, Track::Address rhs) {
|
||||||
|
return disk_image_.tracks_differ(lhs, rhs);
|
||||||
|
}
|
||||||
|
@@ -112,10 +112,22 @@ int WOZ::get_head_count() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
long WOZ::file_offset(Track::Address address) {
|
long WOZ::file_offset(Track::Address address) {
|
||||||
// Calculate table position; if this track is defined to be unformatted, return no track.
|
// Calculate table position.
|
||||||
const int table_position = address.head * (is_3_5_disk_ ? 80 : 160) +
|
int table_position;
|
||||||
(is_3_5_disk_ ? address.position.as_int() : address.position.as_quarter());
|
if(!is_3_5_disk_) {
|
||||||
if(track_map_[table_position] == 0xff) return NoSuchTrack;
|
table_position = address.head * 160 + address.position.as_quarter();
|
||||||
|
} else {
|
||||||
|
if(type_ == Type::WOZ1) {
|
||||||
|
table_position = address.head * 80 + address.position.as_int();
|
||||||
|
} else {
|
||||||
|
table_position = address.head + (address.position.as_int() * 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that this track actually exists.
|
||||||
|
if(track_map_[table_position] == 0xff) {
|
||||||
|
return NoSuchTrack;
|
||||||
|
}
|
||||||
|
|
||||||
// Seek to the real track.
|
// Seek to the real track.
|
||||||
switch(type_) {
|
switch(type_) {
|
||||||
@@ -125,9 +137,17 @@ long WOZ::file_offset(Track::Address address) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WOZ::tracks_differ(Track::Address lhs, Track::Address rhs) {
|
||||||
|
const long offset1 = file_offset(lhs);
|
||||||
|
const long offset2 = file_offset(rhs);
|
||||||
|
return offset1 != offset2;
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<Track> WOZ::get_track_at_position(Track::Address address) {
|
std::shared_ptr<Track> WOZ::get_track_at_position(Track::Address address) {
|
||||||
const long offset = file_offset(address);
|
const long offset = file_offset(address);
|
||||||
if(offset == NoSuchTrack) return nullptr;
|
if(offset == NoSuchTrack) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
// Seek to the real track.
|
// Seek to the real track.
|
||||||
std::vector<uint8_t> track_contents;
|
std::vector<uint8_t> track_contents;
|
||||||
@@ -194,5 +214,12 @@ void WOZ::set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tra
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool WOZ::get_is_read_only() {
|
bool WOZ::get_is_read_only() {
|
||||||
return file_.get_is_known_read_only() || is_read_only_ || type_ == Type::WOZ2; // WOZ 2 disks are currently read only.
|
/*
|
||||||
|
There is an unintended issue with the disk code that sites above here: it doesn't understand the idea
|
||||||
|
of multiple addresses mapping to the same track, yet it maintains a cache of track contents. Therefore
|
||||||
|
if a WOZ is written to, what's written will magically be exactly 1/4 track wide, not affecting its
|
||||||
|
neighbours. I've made WOZs readonly until I can correct that issue.
|
||||||
|
*/
|
||||||
|
return true;
|
||||||
|
// return file_.get_is_known_read_only() || is_read_only_ || type_ == Type::WOZ2; // WOZ 2 disks are currently read only.
|
||||||
}
|
}
|
||||||
|
@@ -31,6 +31,7 @@ class WOZ: public DiskImage {
|
|||||||
std::shared_ptr<Track> get_track_at_position(Track::Address address) final;
|
std::shared_ptr<Track> get_track_at_position(Track::Address address) final;
|
||||||
void set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) final;
|
void set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) final;
|
||||||
bool get_is_read_only() final;
|
bool get_is_read_only() final;
|
||||||
|
bool tracks_differ(Track::Address, Track::Address) final;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Storage::FileHolder file_;
|
Storage::FileHolder file_;
|
||||||
|
@@ -80,6 +80,10 @@ bool Drive::get_is_track_zero() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Drive::step(HeadPosition offset) {
|
void Drive::step(HeadPosition offset) {
|
||||||
|
if(offset == HeadPosition(0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(ready_type_ == ReadyType::IBMRDY) {
|
if(ready_type_ == ReadyType::IBMRDY) {
|
||||||
is_ready_ = true;
|
is_ready_ = true;
|
||||||
}
|
}
|
||||||
@@ -94,7 +98,7 @@ void Drive::step(HeadPosition offset) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the head moved, flush the old track.
|
// If the head moved, flush the old track.
|
||||||
if(head_position_ != old_head_position) {
|
if(disk_ && disk_->tracks_differ(Track::Address(head_, head_position_), Track::Address(head_, old_head_position))) {
|
||||||
track_ = nullptr;
|
track_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,11 +304,6 @@ void Drive::get_next_event(float duration_already_passed) {
|
|||||||
current_event_.type = Track::Event::IndexHole;
|
current_event_.type = Track::Event::IndexHole;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin a 2ms period of holding the index line pulse active if this is an index pulse event.
|
|
||||||
if(current_event_.type == Track::Event::IndexHole) {
|
|
||||||
index_pulse_remaining_ = Cycles((get_input_clock_rate() * 2) / 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// divide interval, which is in terms of a single rotation of the disk, by rotation speed to
|
// divide interval, which is in terms of a single rotation of the disk, by rotation speed to
|
||||||
// convert it into revolutions per second; this is achieved by multiplying by rotational_multiplier_
|
// convert it into revolutions per second; this is achieved by multiplying by rotational_multiplier_
|
||||||
float interval = std::max((current_event_.length - duration_already_passed) * rotational_multiplier_, 0.0f);
|
float interval = std::max((current_event_.length - duration_already_passed) * rotational_multiplier_, 0.0f);
|
||||||
@@ -327,6 +326,9 @@ void Drive::process_next_event() {
|
|||||||
is_ready_ = true;
|
is_ready_ = true;
|
||||||
}
|
}
|
||||||
cycles_since_index_hole_ = 0;
|
cycles_since_index_hole_ = 0;
|
||||||
|
|
||||||
|
// Begin a 2ms period of holding the index line pulse active.
|
||||||
|
index_pulse_remaining_ = Cycles((get_input_clock_rate() * 2) / 1000);
|
||||||
}
|
}
|
||||||
if(
|
if(
|
||||||
event_delegate_ &&
|
event_delegate_ &&
|
||||||
@@ -355,8 +357,8 @@ void Drive::setup_track() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
float offset = 0.0f;
|
float offset = 0.0f;
|
||||||
const auto track_time_now = get_time_into_track();
|
const float track_time_now = get_time_into_track();
|
||||||
const auto time_found = track_->seek_to(Time(track_time_now)).get<float>();
|
const float time_found = track_->seek_to(track_time_now);
|
||||||
|
|
||||||
// `time_found` can be greater than `track_time_now` if limited precision caused rounding.
|
// `time_found` can be greater than `track_time_now` if limited precision caused rounding.
|
||||||
if(time_found <= track_time_now) {
|
if(time_found <= track_time_now) {
|
||||||
|
@@ -21,9 +21,9 @@ struct Sector {
|
|||||||
Describes the location of a sector, implementing < to allow for use as a set key.
|
Describes the location of a sector, implementing < to allow for use as a set key.
|
||||||
*/
|
*/
|
||||||
struct Address {
|
struct Address {
|
||||||
struct {
|
union {
|
||||||
/// For Apple II-type sectors, provides the volume number.
|
/// For Apple II-type sectors, provides the volume number.
|
||||||
uint_fast8_t volume = 0;
|
uint_fast8_t volume;
|
||||||
/// For Macintosh-type sectors, provides the format from the sector header.
|
/// For Macintosh-type sectors, provides the format from the sector header.
|
||||||
uint_fast8_t format = 0;
|
uint_fast8_t format = 0;
|
||||||
};
|
};
|
||||||
|
@@ -57,14 +57,14 @@ uint8_t unmap_five_and_three(uint8_t source) {
|
|||||||
return five_and_three_unmapping[source - 0xab];
|
return five_and_three_unmapping[source - 0xab];
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Sector> decode_macintosh_sector(const std::array<uint_fast8_t, 8> &header, const std::unique_ptr<Sector> &original) {
|
std::unique_ptr<Sector> decode_macintosh_sector(const std::array<uint_fast8_t, 8> *header, const std::unique_ptr<Sector> &original) {
|
||||||
// There must be at least 704 bytes to decode from.
|
// There must be a header and at least 704 bytes to decode from.
|
||||||
if(original->data.size() < 704) return nullptr;
|
if(!header || original->data.size() < 704) return nullptr;
|
||||||
|
|
||||||
// Attempt a six-and-two unmapping of the header.
|
// Attempt a six-and-two unmapping of the header.
|
||||||
std::array<uint_fast8_t, 5> decoded_header;
|
std::array<uint_fast8_t, 5> decoded_header;
|
||||||
for(size_t c = 0; c < decoded_header.size(); ++c) {
|
for(size_t c = 0; c < decoded_header.size(); ++c) {
|
||||||
decoded_header[c] = unmap_six_and_two(header[c]);
|
decoded_header[c] = unmap_six_and_two((*header)[c]);
|
||||||
if(decoded_header[c] == 0xff) {
|
if(decoded_header[c] == 0xff) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -140,29 +140,32 @@ std::unique_ptr<Sector> decode_macintosh_sector(const std::array<uint_fast8_t, 8
|
|||||||
return sector;
|
return sector;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Sector> decode_appleii_sector(const std::array<uint_fast8_t, 8> &header, const std::unique_ptr<Sector> &original, bool is_five_and_three) {
|
std::unique_ptr<Sector> decode_appleii_sector(const std::array<uint_fast8_t, 8> *header, const std::unique_ptr<Sector> &original, bool is_five_and_three) {
|
||||||
// There must be at least 411 bytes to decode a five-and-three sector from;
|
// There must be at least 411 bytes to decode a five-and-three sector from;
|
||||||
// there must be only 343 if this is a six-and-two sector.
|
// there must be only 343 if this is a six-and-two sector.
|
||||||
const size_t data_size = is_five_and_three ? 411 : 343;
|
const size_t data_size = is_five_and_three ? 411 : 343;
|
||||||
if(original->data.size() < data_size) return nullptr;
|
if(original->data.size() < data_size) return nullptr;
|
||||||
|
|
||||||
// Check for apparent four and four encoding.
|
// Allocate a sector.
|
||||||
uint_fast8_t header_mask = 0xff;
|
|
||||||
for(auto c : header) header_mask &= c;
|
|
||||||
header_mask &= 0xaa;
|
|
||||||
if(header_mask != 0xaa) return nullptr;
|
|
||||||
|
|
||||||
// Allocate a sector and fill the header fields.
|
|
||||||
auto sector = std::make_unique<Sector>();
|
auto sector = std::make_unique<Sector>();
|
||||||
sector->data.resize(data_size);
|
sector->data.resize(data_size);
|
||||||
|
|
||||||
sector->address.volume = ((header[0] << 1) | 1) & header[1];
|
// If there is a header, check for apparent four and four encoding.
|
||||||
sector->address.track = ((header[2] << 1) | 1) & header[3];
|
if(header) {
|
||||||
sector->address.sector = ((header[4] << 1) | 1) & header[5];
|
uint_fast8_t header_mask = 0xff;
|
||||||
|
for(auto c : *header) header_mask &= c;
|
||||||
|
header_mask &= 0xaa;
|
||||||
|
if(header_mask != 0xaa) return nullptr;
|
||||||
|
|
||||||
// Check the header checksum.
|
// Fill the header fields.
|
||||||
const uint_fast8_t checksum = ((header[6] << 1) | 1) & header[7];
|
sector->address.volume = (((*header)[0] << 1) | 1) & (*header)[1];
|
||||||
if(checksum != (sector->address.volume^sector->address.track^sector->address.sector)) return nullptr;
|
sector->address.track = (((*header)[2] << 1) | 1) & (*header)[3];
|
||||||
|
sector->address.sector = (((*header)[4] << 1) | 1) & (*header)[5];
|
||||||
|
|
||||||
|
// Check the header checksum.
|
||||||
|
const uint_fast8_t checksum = (((*header)[6] << 1) | 1) & (*header)[7];
|
||||||
|
if(checksum != (sector->address.volume^sector->address.track^sector->address.sector)) return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
// Unmap the sector contents.
|
// Unmap the sector contents.
|
||||||
for(size_t index = 0; index < data_size; ++index) {
|
for(size_t index = 0; index < data_size; ++index) {
|
||||||
@@ -178,9 +181,6 @@ std::unique_ptr<Sector> decode_appleii_sector(const std::array<uint_fast8_t, 8>
|
|||||||
}
|
}
|
||||||
if(sector->data.back()) return nullptr;
|
if(sector->data.back()) return nullptr;
|
||||||
|
|
||||||
// Having checked the checksum, remove it.
|
|
||||||
sector->data.resize(sector->data.size() - 1);
|
|
||||||
|
|
||||||
if(is_five_and_three) {
|
if(is_five_and_three) {
|
||||||
// TODO: the below is almost certainly incorrect; Beneath Apple DOS partly documents
|
// TODO: the below is almost certainly incorrect; Beneath Apple DOS partly documents
|
||||||
// the process, enough to give the basic outline below of how five source bytes are
|
// the process, enough to give the basic outline below of how five source bytes are
|
||||||
@@ -250,6 +250,7 @@ std::map<std::size_t, Sector> Storage::Encodings::AppleGCR::sectors_from_segment
|
|||||||
size_t bit = 0;
|
size_t bit = 0;
|
||||||
int header_delay = 0;
|
int header_delay = 0;
|
||||||
bool is_five_and_three = false;
|
bool is_five_and_three = false;
|
||||||
|
bool has_header = false;
|
||||||
while(bit < segment.data.size() || pointer != scanning_sentinel || header_delay) {
|
while(bit < segment.data.size() || pointer != scanning_sentinel || header_delay) {
|
||||||
shift_register = uint_fast8_t((shift_register << 1) | (segment.data[bit % segment.data.size()] ? 1 : 0));
|
shift_register = uint_fast8_t((shift_register << 1) | (segment.data[bit % segment.data.size()] ? 1 : 0));
|
||||||
++bit;
|
++bit;
|
||||||
@@ -290,6 +291,7 @@ std::map<std::size_t, Sector> Storage::Encodings::AppleGCR::sectors_from_segment
|
|||||||
sector_location = size_t(bit % segment.data.size());
|
sector_location = size_t(bit % segment.data.size());
|
||||||
header_delay = 200; // Allow up to 200 bytes to find the body, if the
|
header_delay = 200; // Allow up to 200 bytes to find the body, if the
|
||||||
// track split comes in between.
|
// track split comes in between.
|
||||||
|
has_header = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -308,18 +310,19 @@ std::map<std::size_t, Sector> Storage::Encodings::AppleGCR::sectors_from_segment
|
|||||||
pointer = scanning_sentinel;
|
pointer = scanning_sentinel;
|
||||||
|
|
||||||
// Potentially this is a Macintosh sector.
|
// Potentially this is a Macintosh sector.
|
||||||
auto macintosh_sector = decode_macintosh_sector(header, sector);
|
auto macintosh_sector = decode_macintosh_sector(has_header ? &header : nullptr, sector);
|
||||||
if(macintosh_sector) {
|
if(macintosh_sector) {
|
||||||
result.insert(std::make_pair(sector_location, std::move(*macintosh_sector)));
|
result.insert(std::make_pair(sector_location, std::move(*macintosh_sector)));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apple II then?
|
// Apple II then?
|
||||||
auto appleii_sector = decode_appleii_sector(header, sector, is_five_and_three);
|
auto appleii_sector = decode_appleii_sector(has_header ? &header : nullptr, sector, is_five_and_three);
|
||||||
if(appleii_sector) {
|
if(appleii_sector) {
|
||||||
result.insert(std::make_pair(sector_location, std::move(*appleii_sector)));
|
result.insert(std::make_pair(sector_location, std::move(*appleii_sector)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
has_header = false;
|
||||||
} else {
|
} else {
|
||||||
new_sector->data.push_back(value);
|
new_sector->data.push_back(value);
|
||||||
}
|
}
|
||||||
|
@@ -109,9 +109,9 @@ Storage::Time PCMSegmentEventSource::get_length() {
|
|||||||
return segment_->length_of_a_bit * unsigned(segment_->data.size());
|
return segment_->length_of_a_bit * unsigned(segment_->data.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
Storage::Time PCMSegmentEventSource::seek_to(const Time &time_from_start) {
|
float PCMSegmentEventSource::seek_to(float time_from_start) {
|
||||||
// test for requested time being beyond the end
|
// test for requested time being beyond the end
|
||||||
const Time length = get_length();
|
const float length = get_length().get<float>();
|
||||||
if(time_from_start >= length) {
|
if(time_from_start >= length) {
|
||||||
next_event_.type = Track::Event::IndexHole;
|
next_event_.type = Track::Event::IndexHole;
|
||||||
bit_pointer_ = segment_->data.size()+1;
|
bit_pointer_ = segment_->data.size()+1;
|
||||||
@@ -122,21 +122,21 @@ Storage::Time PCMSegmentEventSource::seek_to(const Time &time_from_start) {
|
|||||||
next_event_.type = Track::Event::FluxTransition;
|
next_event_.type = Track::Event::FluxTransition;
|
||||||
|
|
||||||
// test for requested time being before the first bit
|
// test for requested time being before the first bit
|
||||||
Time half_bit_length = segment_->length_of_a_bit;
|
const float bit_length = segment_->length_of_a_bit.get<float>();
|
||||||
half_bit_length.length >>= 1;
|
const float half_bit_length = bit_length / 2.0f;
|
||||||
if(time_from_start < half_bit_length) {
|
if(time_from_start < half_bit_length) {
|
||||||
bit_pointer_ = 0;
|
bit_pointer_ = 0;
|
||||||
return Storage::Time(0);
|
return 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// adjust for time to get to bit zero and determine number of bits in;
|
// 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,
|
// bit_pointer_ always records _the next bit_ that might trigger an event,
|
||||||
// so should be one beyond the one reached by a seek.
|
// so should be one beyond the one reached by a seek.
|
||||||
const Time relative_time = time_from_start - half_bit_length;
|
const float relative_time = time_from_start + half_bit_length; // the period [0, 0.5) should map to window 0, ending with bit 0; [0.5, 1.5) should map to window 1; etc.
|
||||||
bit_pointer_ = 1 + (relative_time / segment_->length_of_a_bit).get<unsigned int>();
|
bit_pointer_ = size_t(relative_time / bit_length);
|
||||||
|
|
||||||
// map up to the correct amount of time
|
// Map up to the correct amount of time; this should be the start of the window that ends upon the bit at bit_pointer_.
|
||||||
return half_bit_length + segment_->length_of_a_bit * unsigned(bit_pointer_ - 1);
|
return bit_length * float(bit_pointer_) - half_bit_length;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PCMSegment &PCMSegmentEventSource::segment() const {
|
const PCMSegment &PCMSegmentEventSource::segment() const {
|
||||||
|
@@ -183,7 +183,7 @@ class PCMSegmentEventSource {
|
|||||||
|
|
||||||
@returns the time the source is now at.
|
@returns the time the source is now at.
|
||||||
*/
|
*/
|
||||||
Time seek_to(const Time &time_from_start);
|
float seek_to(float time_from_start);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@returns the total length of the stream of data that the source will provide.
|
@returns the total length of the stream of data that the source will provide.
|
||||||
|
@@ -121,17 +121,17 @@ Track::Event PCMTrack::get_next_event() {
|
|||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
Storage::Time PCMTrack::seek_to(const Time &time_since_index_hole) {
|
float PCMTrack::seek_to(float time_since_index_hole) {
|
||||||
// initial condition: no time yet accumulated, the whole thing requested yet to navigate
|
// initial condition: no time yet accumulated, the whole thing requested yet to navigate
|
||||||
Storage::Time accumulated_time;
|
float accumulated_time = 0.0f;
|
||||||
Storage::Time time_left_to_seek = time_since_index_hole;
|
float time_left_to_seek = time_since_index_hole;
|
||||||
|
|
||||||
// search from the first segment
|
// search from the first segment
|
||||||
segment_pointer_ = 0;
|
segment_pointer_ = 0;
|
||||||
do {
|
do {
|
||||||
// if this segment extends beyond the amount of time left to seek, trust it to complete
|
// if this segment extends beyond the amount of time left to seek, trust it to complete
|
||||||
// the seek
|
// the seek
|
||||||
Storage::Time segment_time = segment_event_sources_[segment_pointer_].get_length();
|
const float segment_time = segment_event_sources_[segment_pointer_].get_length().get<float>();
|
||||||
if(segment_time > time_left_to_seek) {
|
if(segment_time > time_left_to_seek) {
|
||||||
return accumulated_time + segment_event_sources_[segment_pointer_].seek_to(time_left_to_seek);
|
return accumulated_time + segment_event_sources_[segment_pointer_].seek_to(time_left_to_seek);
|
||||||
}
|
}
|
||||||
|
@@ -50,7 +50,7 @@ class PCMTrack: public Track {
|
|||||||
|
|
||||||
// as per @c Track
|
// as per @c Track
|
||||||
Event get_next_event() final;
|
Event get_next_event() final;
|
||||||
Time seek_to(const Time &time_since_index_hole) final;
|
float seek_to(float time_since_index_hole) final;
|
||||||
Track *clone() const final;
|
Track *clone() const final;
|
||||||
|
|
||||||
// Obtains a copy of this track, flattened to a single PCMSegment, which
|
// Obtains a copy of this track, flattened to a single PCMSegment, which
|
||||||
|
@@ -84,6 +84,13 @@ class Track {
|
|||||||
int rhs_largest_position = rhs.position.as_largest();
|
int rhs_largest_position = rhs.position.as_largest();
|
||||||
return std::tie(head, largest_position) < std::tie(rhs.head, rhs_largest_position);
|
return std::tie(head, largest_position) < std::tie(rhs.head, rhs_largest_position);
|
||||||
}
|
}
|
||||||
|
constexpr bool operator == (const Address &rhs) const {
|
||||||
|
return head == rhs.head && position == rhs.position;
|
||||||
|
}
|
||||||
|
constexpr bool operator != (const Address &rhs) const {
|
||||||
|
return head != rhs.head || position != rhs.position;
|
||||||
|
}
|
||||||
|
|
||||||
constexpr Address(int head, HeadPosition position) : head(head), position(position) {}
|
constexpr Address(int head, HeadPosition position) : head(head), position(position) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -107,11 +114,11 @@ class Track {
|
|||||||
virtual Event get_next_event() = 0;
|
virtual Event get_next_event() = 0;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Jumps to the event latest offset that is less than or equal to the input time.
|
Jumps to the start of the fist event that will occur after @c time_since_index_hole.
|
||||||
|
|
||||||
@returns the time jumped to.
|
@returns the time jumped to.
|
||||||
*/
|
*/
|
||||||
virtual Time seek_to(const Time &time_since_index_hole) = 0;
|
virtual float seek_to(float time_since_index_hole) = 0;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
The virtual copy constructor pattern; returns a copy of the Track.
|
The virtual copy constructor pattern; returns a copy of the Track.
|
||||||
|
@@ -35,7 +35,7 @@ Storage::Disk::PCMSegment Storage::Disk::track_serialisation(const Track &track,
|
|||||||
length_multiplier.simplify();
|
length_multiplier.simplify();
|
||||||
|
|
||||||
// start at the index hole
|
// start at the index hole
|
||||||
track_copy->seek_to(Time(0));
|
track_copy->seek_to(0.0f);
|
||||||
|
|
||||||
// grab events until the next index hole
|
// grab events until the next index hole
|
||||||
Time time_error = Time(0);
|
Time time_error = Time(0);
|
||||||
@@ -54,7 +54,7 @@ Storage::Disk::PCMSegment Storage::Disk::track_serialisation(const Track &track,
|
|||||||
if(history_size) {
|
if(history_size) {
|
||||||
history_size--;
|
history_size--;
|
||||||
if(!history_size) {
|
if(!history_size) {
|
||||||
track_copy->seek_to(Time(0));
|
track_copy->seek_to(0.0f);
|
||||||
time_error.set_zero();
|
time_error.set_zero();
|
||||||
result_accumulator.is_recording = true;
|
result_accumulator.is_recording = true;
|
||||||
}
|
}
|
||||||
|
@@ -17,8 +17,8 @@ Track::Event UnformattedTrack::get_next_event() {
|
|||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
Storage::Time UnformattedTrack::seek_to(const Time &) {
|
float UnformattedTrack::seek_to(float) {
|
||||||
return Time(0);
|
return 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
Track *UnformattedTrack::clone() const {
|
Track *UnformattedTrack::clone() const {
|
||||||
|
@@ -20,7 +20,7 @@ namespace Disk {
|
|||||||
class UnformattedTrack: public Track {
|
class UnformattedTrack: public Track {
|
||||||
public:
|
public:
|
||||||
Event get_next_event() final;
|
Event get_next_event() final;
|
||||||
Time seek_to(const Time &time_since_index_hole) final;
|
float seek_to(float time_since_index_hole) final;
|
||||||
Track *clone() const final;
|
Track *clone() const final;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -69,16 +69,16 @@ void TimedEventLoop::set_next_event_time_interval(Time interval) {
|
|||||||
|
|
||||||
void TimedEventLoop::set_next_event_time_interval(float interval) {
|
void TimedEventLoop::set_next_event_time_interval(float interval) {
|
||||||
// Calculate [interval]*[input clock rate] + [subcycles until this event]
|
// Calculate [interval]*[input clock rate] + [subcycles until this event]
|
||||||
float float_interval = interval * float(input_clock_rate_) + subcycles_until_event_;
|
const float float_interval = interval * float(input_clock_rate_) + subcycles_until_event_;
|
||||||
|
|
||||||
// So this event will fire in the integral number of cycles from now, putting us at the remainder
|
// This event will fire in the integral number of cycles from now, putting us at the remainder
|
||||||
// number of subcycles
|
// number of subcycles.
|
||||||
const Cycles::IntType addition = Cycles::IntType(float_interval);
|
const Cycles::IntType addition = Cycles::IntType(float_interval);
|
||||||
cycles_until_event_ += addition;
|
cycles_until_event_ += addition;
|
||||||
subcycles_until_event_ = fmodf(float_interval, 1.0);
|
subcycles_until_event_ = fmodf(float_interval, 1.0f);
|
||||||
|
|
||||||
assert(cycles_until_event_ >= 0);
|
assert(cycles_until_event_ >= 0);
|
||||||
assert(subcycles_until_event_ >= 0.0);
|
assert(subcycles_until_event_ >= 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
Time TimedEventLoop::get_time_into_next_event() {
|
Time TimedEventLoop::get_time_into_next_event() {
|
||||||
|
@@ -103,7 +103,7 @@ namespace Storage {
|
|||||||
private:
|
private:
|
||||||
Cycles::IntType input_clock_rate_ = 0;
|
Cycles::IntType input_clock_rate_ = 0;
|
||||||
Cycles::IntType cycles_until_event_ = 0;
|
Cycles::IntType cycles_until_event_ = 0;
|
||||||
float subcycles_until_event_ = 0.0;
|
float subcycles_until_event_ = 0.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user