mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-26 09:29:45 +00:00
Provides an implementation of Drive's new interface.
Mostly lifted from DiskController. `set_disk_with_track` has been withdrawn in favour of providing a suitable wrapper `Disk` subclass, as being an unnecessary complexity and intermingling of concerns.
This commit is contained in:
parent
6075064400
commit
a4e275e1fc
@ -7,7 +7,11 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#include "Drive.hpp"
|
#include "Drive.hpp"
|
||||||
|
|
||||||
|
#include "UnformattedTrack.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
using namespace Storage::Disk;
|
using namespace Storage::Disk;
|
||||||
|
|
||||||
@ -18,16 +22,13 @@ Drive::Drive(unsigned int input_clock_rate, int revolutions_per_minute):
|
|||||||
|
|
||||||
void Drive::set_disk(const std::shared_ptr<Disk> &disk) {
|
void Drive::set_disk(const std::shared_ptr<Disk> &disk) {
|
||||||
disk_ = disk;
|
disk_ = disk;
|
||||||
track_ = nullptr;
|
|
||||||
has_disk_ = !!disk_;
|
has_disk_ = !!disk_;
|
||||||
update_sleep_observer();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Drive::set_disk_with_track(const std::shared_ptr<Track> &track) {
|
invalidate_track();
|
||||||
disk_ = nullptr;
|
|
||||||
track_ = track;
|
|
||||||
has_disk_ = !!track_;
|
|
||||||
update_sleep_observer();
|
update_sleep_observer();
|
||||||
|
|
||||||
|
// TODO: implement ready properly.
|
||||||
|
is_ready_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Drive::has_disk() {
|
bool Drive::has_disk() {
|
||||||
@ -46,8 +47,15 @@ void Drive::step(int direction) {
|
|||||||
int old_head_position = head_position_;
|
int old_head_position = head_position_;
|
||||||
head_position_ = std::max(head_position_ + direction, 0);
|
head_position_ = std::max(head_position_ + direction, 0);
|
||||||
|
|
||||||
// If the head moved and this drive has a real disk in it, flush the old track.
|
// If the head moved, flush the old track.
|
||||||
if(head_position_ != old_head_position && disk_ != nullptr) {
|
if(head_position_ != old_head_position) {
|
||||||
|
track_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Drive::set_head(unsigned int head) {
|
||||||
|
if(head != head_) {
|
||||||
|
head_ = head;
|
||||||
track_ = nullptr;
|
track_ = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -55,34 +63,19 @@ void Drive::step(int direction) {
|
|||||||
Storage::Time Drive::get_time_into_track() {
|
Storage::Time Drive::get_time_into_track() {
|
||||||
// `result` will initially be amount of time since the index hole was seen as a
|
// `result` will initially be amount of time since the index hole was seen as a
|
||||||
// proportion of a second; convert it into proportion of a rotation, simplify and return.
|
// proportion of a second; convert it into proportion of a rotation, simplify and return.
|
||||||
Time result(cycles_since_index_hole_, 8000000);
|
Time result(cycles_since_index_hole_, (int)get_input_clock_rate());
|
||||||
result /= rotational_multiplier_;
|
result /= rotational_multiplier_;
|
||||||
result.simplify();
|
result.simplify();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Drive::set_head(unsigned int head) {
|
|
||||||
head_ = head;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Drive::get_is_read_only() {
|
bool Drive::get_is_read_only() {
|
||||||
if(disk_) return disk_->get_is_read_only();
|
if(disk_) return disk_->get_is_read_only();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Drive::get_is_ready() {
|
bool Drive::get_is_ready() {
|
||||||
// TODO: a real test for this.
|
return is_ready_;
|
||||||
return disk_ != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<Track> Drive::get_track() {
|
|
||||||
if(disk_) return disk_->get_track_at_position(head_, (unsigned int)head_position_);
|
|
||||||
if(track_) return track_;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Drive::set_track(const std::shared_ptr<Track> &track) {
|
|
||||||
if(disk_) disk_->set_track_at_position(head_, (unsigned int)head_position_, track);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Drive::set_motor_on(bool motor_is_on) {
|
void Drive::set_motor_on(bool motor_is_on) {
|
||||||
@ -90,9 +83,136 @@ void Drive::set_motor_on(bool motor_is_on) {
|
|||||||
update_sleep_observer();
|
update_sleep_observer();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Drive::process_next_event() {
|
void Drive::run_for(const Cycles cycles) {
|
||||||
if(event_delegate_) event_delegate_->process_event(current_event_);
|
Time zero(0);
|
||||||
|
|
||||||
|
if(has_disk_ && motor_is_on_) {
|
||||||
|
// Grab a new track if not already in possession of one.
|
||||||
|
if(!track_) setup_track();
|
||||||
|
|
||||||
|
int number_of_cycles = cycles.as_int();
|
||||||
|
while(number_of_cycles) {
|
||||||
|
int cycles_until_next_event = (int)get_cycles_until_next_event();
|
||||||
|
int cycles_to_run_for = std::min(cycles_until_next_event, number_of_cycles);
|
||||||
|
if(!is_reading_ && cycles_until_bits_written_ > zero) {
|
||||||
|
int write_cycles_target = (int)cycles_until_bits_written_.get_unsigned_int();
|
||||||
|
if(cycles_until_bits_written_.length % cycles_until_bits_written_.clock_rate) write_cycles_target++;
|
||||||
|
cycles_to_run_for = std::min(cycles_to_run_for, write_cycles_target);
|
||||||
|
}
|
||||||
|
|
||||||
|
cycles_since_index_hole_ += (unsigned int)cycles_to_run_for;
|
||||||
|
|
||||||
|
number_of_cycles -= cycles_to_run_for;
|
||||||
|
if(is_reading_) {
|
||||||
|
if(event_delegate_) event_delegate_->advance(Cycles(cycles_to_run_for));
|
||||||
|
} else {
|
||||||
|
if(cycles_until_bits_written_ > zero) {
|
||||||
|
Storage::Time cycles_to_run_for_time(cycles_to_run_for);
|
||||||
|
if(cycles_until_bits_written_ <= cycles_to_run_for_time) {
|
||||||
|
if(event_delegate_) event_delegate_->process_write_completed();
|
||||||
|
if(cycles_until_bits_written_ <= cycles_to_run_for_time)
|
||||||
|
cycles_until_bits_written_.set_zero();
|
||||||
|
else
|
||||||
|
cycles_until_bits_written_ -= cycles_to_run_for_time;
|
||||||
|
} else {
|
||||||
|
cycles_until_bits_written_ -= cycles_to_run_for_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TimedEventLoop::run_for(Cycles(cycles_to_run_for));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Drive::run_for(const Cycles cycles) {
|
#pragma mark - Track timed event loop
|
||||||
|
|
||||||
|
void Drive::get_next_event(const Time &duration_already_passed) {
|
||||||
|
if(track_) {
|
||||||
|
current_event_ = track_->get_next_event();
|
||||||
|
} else {
|
||||||
|
current_event_.length.length = 1;
|
||||||
|
current_event_.length.clock_rate = 1;
|
||||||
|
current_event_.type = Track::Event::IndexHole;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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_
|
||||||
|
assert(current_event_.length <= Time(1) && current_event_.length >= Time(0));
|
||||||
|
Time interval = (current_event_.length - duration_already_passed) * rotational_multiplier_;
|
||||||
|
set_next_event_time_interval(interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Drive::process_next_event() {
|
||||||
|
// TODO: ready test here.
|
||||||
|
if(event_delegate_) event_delegate_->process_event(current_event_);
|
||||||
|
get_next_event(Time(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Track management
|
||||||
|
|
||||||
|
std::shared_ptr<Track> Drive::get_track() {
|
||||||
|
if(disk_) return disk_->get_track_at_position(head_, (unsigned int)head_position_);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Drive::set_track(const std::shared_ptr<Track> &track) {
|
||||||
|
if(disk_) disk_->set_track_at_position(head_, (unsigned int)head_position_, track);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Drive::setup_track() {
|
||||||
|
track_ = get_track();
|
||||||
|
if(!track_) {
|
||||||
|
track_.reset(new UnformattedTrack);
|
||||||
|
}
|
||||||
|
|
||||||
|
Time offset;
|
||||||
|
Time track_time_now = get_time_into_track();
|
||||||
|
assert(track_time_now >= Time(0) && current_event_.length <= Time(1));
|
||||||
|
|
||||||
|
Time time_found = track_->seek_to(track_time_now);
|
||||||
|
assert(time_found >= Time(0) && time_found <= track_time_now);
|
||||||
|
offset = track_time_now - time_found;
|
||||||
|
|
||||||
|
get_next_event(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Drive::invalidate_track() {
|
||||||
|
track_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Writing
|
||||||
|
|
||||||
|
void Drive::begin_writing(Time bit_length, bool clamp_to_index_hole) {
|
||||||
|
is_reading_ = false;
|
||||||
|
clamp_writing_to_index_hole_ = 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++;
|
||||||
|
|
||||||
|
cycles_until_bits_written_ += cycles_per_bit_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Drive::end_writing() {
|
||||||
|
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_->add_segment(write_start_time_, write_segment_, clamp_writing_to_index_hole_);
|
||||||
|
cycles_since_index_hole_ %= get_input_clock_rate();
|
||||||
|
invalidate_track();
|
||||||
}
|
}
|
||||||
|
@ -30,11 +30,6 @@ class Drive: public Sleeper, public TimedEventLoop {
|
|||||||
*/
|
*/
|
||||||
void set_disk(const std::shared_ptr<Disk> &disk);
|
void set_disk(const std::shared_ptr<Disk> &disk);
|
||||||
|
|
||||||
/*!
|
|
||||||
Replaces whatever is in the drive with a disk that contains endless copies of @c track.
|
|
||||||
*/
|
|
||||||
void set_disk_with_track(const std::shared_ptr<Track> &track);
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@returns @c true if a disk is currently inserted; @c false otherwise.
|
@returns @c true if a disk is currently inserted; @c false otherwise.
|
||||||
*/
|
*/
|
||||||
@ -61,16 +56,6 @@ class Drive: public Sleeper, public TimedEventLoop {
|
|||||||
*/
|
*/
|
||||||
bool get_is_read_only();
|
bool get_is_read_only();
|
||||||
|
|
||||||
/*!
|
|
||||||
@returns the track underneath the current head at the location now stepped to.
|
|
||||||
*/
|
|
||||||
std::shared_ptr<Track> get_track();
|
|
||||||
|
|
||||||
/*!
|
|
||||||
Attempts to set @c track as the track underneath the current head at the location now stepped to.
|
|
||||||
*/
|
|
||||||
void set_track(const std::shared_ptr<Track> &track);
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@returns @c true if the drive is ready; @c false otherwise.
|
@returns @c true if the drive is ready; @c false otherwise.
|
||||||
*/
|
*/
|
||||||
@ -95,7 +80,7 @@ class Drive: public Sleeper, public TimedEventLoop {
|
|||||||
@param clamp_to_index_hole If @c true then writing will automatically be truncated by
|
@param clamp_to_index_hole If @c true then writing will automatically be truncated by
|
||||||
the index hole. Writing will continue over the index hole otherwise.
|
the index hole. Writing will continue over the index hole otherwise.
|
||||||
*/
|
*/
|
||||||
void begin_writing(bool clamp_to_index_hole);
|
void begin_writing(Time bit_length, bool clamp_to_index_hole);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Writes the bit @c value as the next in the PCM stream initiated by @c begin_writing.
|
Writes the bit @c value as the next in the PCM stream initiated by @c begin_writing.
|
||||||
@ -125,6 +110,9 @@ class Drive: public Sleeper, public TimedEventLoop {
|
|||||||
If the controller provides further bits now then there will be no gap in written data.
|
If the controller provides further bits now then there will be no gap in written data.
|
||||||
*/
|
*/
|
||||||
virtual void process_write_completed() {}
|
virtual void process_write_completed() {}
|
||||||
|
|
||||||
|
/// Informs the delegate of the passing of @c cycles.
|
||||||
|
virtual void advance(const Cycles cycles) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Sets the current event delegate.
|
/// Sets the current event delegate.
|
||||||
@ -134,7 +122,7 @@ class Drive: public Sleeper, public TimedEventLoop {
|
|||||||
bool is_sleeping();
|
bool is_sleeping();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Drives [usually] contain an entire disk; from that a certain track
|
// Drives contain an entire disk; from that a certain track
|
||||||
// will be currently under the head.
|
// will be currently under the head.
|
||||||
std::shared_ptr<Disk> disk_;
|
std::shared_ptr<Disk> disk_;
|
||||||
std::shared_ptr<Track> track_;
|
std::shared_ptr<Track> track_;
|
||||||
@ -158,8 +146,8 @@ class Drive: public Sleeper, public TimedEventLoop {
|
|||||||
|
|
||||||
// If the drive is not currently reading then it is writing. While writing
|
// If the drive is not currently reading then it is writing. While writing
|
||||||
// it can optionally be told to clamp to the index hole.
|
// it can optionally be told to clamp to the index hole.
|
||||||
bool is_reading_;
|
bool is_reading_ = false;
|
||||||
bool clamp_writing_to_index_hole_;
|
bool clamp_writing_to_index_hole_ = false;
|
||||||
|
|
||||||
// If writing is occurring then the drive will be accumulating a write segment,
|
// If writing is occurring then the drive will be accumulating a write segment,
|
||||||
// for addition to a patched track.
|
// for addition to a patched track.
|
||||||
@ -167,6 +155,9 @@ class Drive: public Sleeper, public TimedEventLoop {
|
|||||||
PCMSegment write_segment_;
|
PCMSegment write_segment_;
|
||||||
Time write_start_time_;
|
Time write_start_time_;
|
||||||
|
|
||||||
|
// Indicates drive ready state.
|
||||||
|
bool is_ready_ = false;
|
||||||
|
|
||||||
// Maintains appropriate counting to know when to indicate that writing
|
// Maintains appropriate counting to know when to indicate that writing
|
||||||
// is complete.
|
// is complete.
|
||||||
Time cycles_until_bits_written_;
|
Time cycles_until_bits_written_;
|
||||||
@ -182,6 +173,19 @@ class Drive: public Sleeper, public TimedEventLoop {
|
|||||||
|
|
||||||
// The target (if any) for track events.
|
// The target (if any) for track events.
|
||||||
EventDelegate *event_delegate_ = nullptr;
|
EventDelegate *event_delegate_ = nullptr;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@returns the track underneath the current head at the location now stepped to.
|
||||||
|
*/
|
||||||
|
std::shared_ptr<Track> get_track();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Attempts to set @c track as the track underneath the current head at the location now stepped to.
|
||||||
|
*/
|
||||||
|
void set_track(const std::shared_ptr<Track> &track);
|
||||||
|
|
||||||
|
void setup_track();
|
||||||
|
void invalidate_track();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user