1
0
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:
Thomas Harte
2020-07-20 19:53:34 -04:00
committed by GitHub
20 changed files with 127 additions and 69 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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