diff --git a/Components/DiskII/MacintoshDoubleDensityDrive.cpp b/Components/DiskII/MacintoshDoubleDensityDrive.cpp index 523217195..9f5a04b32 100644 --- a/Components/DiskII/MacintoshDoubleDensityDrive.cpp +++ b/Components/DiskII/MacintoshDoubleDensityDrive.cpp @@ -23,13 +23,14 @@ DoubleDensityDrive::DoubleDensityDrive(int input_clock_rate, bool is_800k) : is_800k_(is_800k) { // Start with a valid rotation speed. if(is_800k) { - set_rotation_speed(393.3807f); + Drive::set_rotation_speed(393.3807f); } } // MARK: - Speed Selection void DoubleDensityDrive::did_step(Storage::Disk::HeadPosition to_position) { +// printf("At track %d\n", to_position.as_int()); // The 800kb drive automatically selects rotation speed as a function of // head position; the 400kb drive doesn't do so. if(is_800k_) { @@ -51,15 +52,23 @@ void DoubleDensityDrive::did_step(Storage::Disk::HeadPosition to_position) { */ const int zone = to_position.as_int() >> 4; switch(zone) { - case 0: set_rotation_speed(393.3807f); break; - case 1: set_rotation_speed(429.1723f); break; - case 2: set_rotation_speed(472.1435f); break; - case 3: set_rotation_speed(524.5672f); break; - default: set_rotation_speed(590.1098f); break; + case 0: Drive::set_rotation_speed(393.3807f); break; + case 1: Drive::set_rotation_speed(429.1723f); break; + case 2: Drive::set_rotation_speed(472.1435f); break; + case 3: Drive::set_rotation_speed(524.5672f); break; + default: Drive::set_rotation_speed(590.1098f); break; } } } +void DoubleDensityDrive::set_rotation_speed(float revolutions_per_minute) { + if(!is_800k_) { + // Don't allow drive speeds to drop below 10 RPM, as a temporary sop + // to sanity. + Drive::set_rotation_speed(std::max(10.0f, revolutions_per_minute)); + } +} + // MARK: - Control input/output. void DoubleDensityDrive::set_enabled(bool) { diff --git a/Components/DiskII/MacintoshDoubleDensityDrive.hpp b/Components/DiskII/MacintoshDoubleDensityDrive.hpp index da409b6de..c1ac8803f 100644 --- a/Components/DiskII/MacintoshDoubleDensityDrive.hpp +++ b/Components/DiskII/MacintoshDoubleDensityDrive.hpp @@ -18,6 +18,20 @@ class DoubleDensityDrive: public IWMDrive { public: DoubleDensityDrive(int input_clock_rate, bool is_800k); + /*! + @returns @c true if this is an 800kb drive; @c false otherwise. + */ + bool is_800k() { + return is_800k_; + } + + /*! + Sets the current rotation speed of this drive only if it is a 400kb drive. + 800kb drives select their own rotation speed based on head position, + and ignore this input. + */ + void set_rotation_speed(float revolutions_per_minute); + void set_enabled(bool) override; void set_control_lines(int) override; bool read() override; diff --git a/Machines/Apple/Macintosh/DriveSpeedAccumulator.cpp b/Machines/Apple/Macintosh/DriveSpeedAccumulator.cpp index a9748449e..fe1e5199b 100644 --- a/Machines/Apple/Macintosh/DriveSpeedAccumulator.cpp +++ b/Machines/Apple/Macintosh/DriveSpeedAccumulator.cpp @@ -11,6 +11,8 @@ using namespace Apple::Macintosh; void DriveSpeedAccumulator::post_sample(uint8_t sample) { + if(!number_of_drives_) return; + // An Euler-esque approximation is used here: just collect all // the samples until there is a certain small quantity of them, // then produce a new estimate of rotation speed and start the @@ -20,9 +22,31 @@ void DriveSpeedAccumulator::post_sample(uint8_t sample) { if(sample_pointer_ == samples_.size()) { sample_pointer_ = 0; -// for(int c = 0; c < 512; c += 32) { -// printf("%u ", samples_[c]); -// } -// printf("\n"); + + // Treat 33 as a zero point and count zero crossings; then approximate + // the RPM from the frequency of those. + int samples_over = 0; + int sum = 0; + const uint8_t centre = 33; + for(size_t c = 0; c < 512; ++c) { + if(samples_[c] > centre) ++ samples_over; + sum += samples_[c]; + } + + // TODO: if the above is the correct test, do it on sample receipt rather than + // bothering with an intermediate buffer. + + // The below fits for a function like `a + bc`. + const float rotation_speed = (float(sum) * 0.052896440564137f) - 259.0f; + + for(int c = 0; c < number_of_drives_; ++c) { + drives_[c]->set_rotation_speed(rotation_speed); + } +// printf("RPM: %0.2f (%d over; %d sum)\n", rotation_speed, samples_over, sum); } } + +void DriveSpeedAccumulator::add_drive(Apple::Macintosh::DoubleDensityDrive *drive) { + drives_[number_of_drives_] = drive; + ++number_of_drives_; +} diff --git a/Machines/Apple/Macintosh/DriveSpeedAccumulator.hpp b/Machines/Apple/Macintosh/DriveSpeedAccumulator.hpp index 9da8fa3f1..1fab3b541 100644 --- a/Machines/Apple/Macintosh/DriveSpeedAccumulator.hpp +++ b/Machines/Apple/Macintosh/DriveSpeedAccumulator.hpp @@ -13,6 +13,8 @@ #include #include +#include "../../../Components/DiskII/MacintoshDoubleDensityDrive.hpp" + namespace Apple { namespace Macintosh { @@ -23,9 +25,18 @@ class DriveSpeedAccumulator { */ void post_sample(uint8_t sample); + /*! + Adds a connected drive. Up to two of these + can be supplied. Only Macintosh DoubleDensityDrives + are supported. + */ + void add_drive(Apple::Macintosh::DoubleDensityDrive *drive); + private: std::array samples_; std::size_t sample_pointer_ = 0; + Apple::Macintosh::DoubleDensityDrive *drives_[2] = {nullptr, nullptr}; + int number_of_drives_ = 0; }; } diff --git a/Machines/Apple/Macintosh/Macintosh.cpp b/Machines/Apple/Macintosh/Macintosh.cpp index 190db046e..0146d13a2 100644 --- a/Machines/Apple/Macintosh/Macintosh.cpp +++ b/Machines/Apple/Macintosh/Macintosh.cpp @@ -116,6 +116,10 @@ template class ConcreteMachin iwm_.iwm.set_drive(0, &drives_[0]); iwm_.iwm.set_drive(1, &drives_[1]); + // If they are 400kb drives, also attach them to the drive-speed accumulator. + if(!drives_[0].is_800k()) drive_speed_accumulator_.add_drive(&drives_[0]); + if(!drives_[1].is_800k()) drive_speed_accumulator_.add_drive(&drives_[1]); + // Make sure interrupt changes from the SCC are observed. scc_.set_delegate(this); diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index b36369ffa..2e67008e3 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -56,7 +56,16 @@ class MachineDocument: override func windowControllerDidLoadNib(_ aController: NSWindowController) { super.windowControllerDidLoadNib(aController) aController.window?.contentAspectRatio = self.aspectRatio() - setupMachineOutput() + if self.machine != nil { + setupMachineOutput() + } else { + // This is somewhat of a desperate workaround; just having loaded the Nib doesn't + // mean that the window is visible yet, but presenting the ROM import sheet before + // the window is visible will result in it being free floating. + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .milliseconds(500)) { + self.configureAs(self.selectedMachine!) + } + } } // Attempting to show a sheet before the window is visible (such as when the NIB is loaded) results in @@ -340,6 +349,11 @@ class MachineDocument: @IBOutlet var romReceiverView: CSROMReceiverView? private var romRequestBaseText = "" func requestRoms() { + // Don't act yet if there's no window controller yet. + if self.windowControllers.count == 0 { + return + } + // Load the ROM requester dialogue. Bundle.main.loadNibNamed("ROMRequester", owner: self, topLevelObjects: nil) self.romReceiverView!.delegate = self