mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-11 08:30:55 +00:00
Reduced possibility of overflow on LCM, improved commenting widely, removed one stale piece of G64 bootstrapping caveman stuff.
This commit is contained in:
parent
a2ca94431e
commit
2799a87218
@ -30,5 +30,5 @@ unsigned int NumberTheory::greatest_common_divisor(unsigned int a, unsigned int
|
||||
unsigned int NumberTheory::least_common_multiple(unsigned int a, unsigned int b)
|
||||
{
|
||||
unsigned int gcd = greatest_common_divisor(a, b);
|
||||
return (a*b) / gcd;
|
||||
return (a / gcd) * (b / gcd);
|
||||
}
|
||||
|
@ -10,7 +10,15 @@
|
||||
#define Factors_hpp
|
||||
|
||||
namespace NumberTheory {
|
||||
/*!
|
||||
@returns The greatest common divisor of @c a and @c b as computed by Euclid's algorithm.
|
||||
*/
|
||||
unsigned int greatest_common_divisor(unsigned int a, unsigned int b);
|
||||
|
||||
/*!
|
||||
@returns The least common multiple of @c a and @c b computed indirectly via Euclid's greatest
|
||||
common divisor algorithm.
|
||||
*/
|
||||
unsigned int least_common_multiple(unsigned int a, unsigned int b);
|
||||
}
|
||||
|
||||
|
@ -15,26 +15,74 @@
|
||||
|
||||
namespace Storage {
|
||||
|
||||
/*!
|
||||
Provides the shell for emulating a disk drive — something that takes a disk and has a drive head
|
||||
that steps between tracks, using a phase locked loop ('PLL') to decode a bit stream from the surface of
|
||||
the disk.
|
||||
|
||||
Partly abstract; it is expected that subclasses will provide methods to deal with receiving a newly-recognised
|
||||
bit from the PLL and with crossing the index hole.
|
||||
|
||||
TODO: double sided disks, communication of head size and permissible stepping extents, appropriate
|
||||
simulation of gain.
|
||||
*/
|
||||
class DiskDrive: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop {
|
||||
public:
|
||||
/*!
|
||||
Constructs a @c DiskDrive that will be run at @c clock_rate and runs its PLL at @c clock_rate*clock_rate_multiplier,
|
||||
spinning inserted disks at @c revolutions_per_minute.
|
||||
*/
|
||||
DiskDrive(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsigned int revolutions_per_minute);
|
||||
|
||||
/*!
|
||||
Communicates to the PLL the expected length of a bit.
|
||||
*/
|
||||
void set_expected_bit_length(Time bit_length);
|
||||
|
||||
/*!
|
||||
Inserts @c disk into the drive.
|
||||
*/
|
||||
void set_disk(std::shared_ptr<Disk> disk);
|
||||
|
||||
/*!
|
||||
@returns @c true if a disk is currently inserted; @c false otherwise.
|
||||
*/
|
||||
bool has_disk();
|
||||
|
||||
/*!
|
||||
Advances the drive by @c number_of_cycles cycles.
|
||||
*/
|
||||
void run_for_cycles(int number_of_cycles);
|
||||
|
||||
/*!
|
||||
@returns @c true if the drive head is currently at track zero; @c false otherwise.
|
||||
*/
|
||||
bool get_is_track_zero();
|
||||
|
||||
/*!
|
||||
Steps the disk head the specified number of tracks. Positive numbers step inwards, negative numbers
|
||||
step outwards.
|
||||
*/
|
||||
void step(int direction);
|
||||
|
||||
/*!
|
||||
Enables or disables the disk motor.
|
||||
*/
|
||||
void set_motor_on(bool motor_on);
|
||||
|
||||
// to satisfy DigitalPhaseLockedLoop::Delegate
|
||||
void digital_phase_locked_loop_output_bit(int value);
|
||||
|
||||
protected:
|
||||
/*!
|
||||
Should be implemented by subclasses; communicates each bit that the PLL recognises, also specifying
|
||||
the amount of time since the index hole was last seen.
|
||||
*/
|
||||
virtual void process_input_bit(int value, unsigned int cycles_since_index_hole) = 0;
|
||||
|
||||
/*!
|
||||
Should be implemented by subcalsses; communicates that the index hole has been reached.
|
||||
*/
|
||||
virtual void process_index_hole() = 0;
|
||||
|
||||
// for TimedEventLoop
|
||||
|
@ -18,3 +18,34 @@ Time Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(unsigned int
|
||||
duration.clock_rate = 4000000;
|
||||
return duration;
|
||||
}
|
||||
|
||||
unsigned int Storage::Encodings::CommodoreGCR::encoding_for_nibble(uint8_t nibble)
|
||||
{
|
||||
switch(nibble & 0xf)
|
||||
{
|
||||
case 0x0: return 0x0a;
|
||||
case 0x1: return 0x0b;
|
||||
case 0x2: return 0x12;
|
||||
case 0x3: return 0x13;
|
||||
case 0x4: return 0x0e;
|
||||
case 0x5: return 0x0f;
|
||||
case 0x6: return 0x16;
|
||||
case 0x7: return 0x17;
|
||||
case 0x8: return 0x09;
|
||||
case 0x9: return 0x19;
|
||||
case 0xa: return 0x1a;
|
||||
case 0xb: return 0x1b;
|
||||
case 0xc: return 0x0d;
|
||||
case 0xd: return 0x1d;
|
||||
case 0xe: return 0x1e;
|
||||
case 0xf: return 0x15;
|
||||
|
||||
// for the benefit of the compiler; clearly unreachable
|
||||
default: return 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Storage::Encodings::CommodoreGCR::encoding_for_byte(uint8_t byte)
|
||||
{
|
||||
return encoding_for_nibble(byte) | (encoding_for_nibble(byte >> 4) << 5);
|
||||
}
|
||||
|
@ -10,11 +10,27 @@
|
||||
#define CommodoreGCR_hpp
|
||||
|
||||
#include "../../Storage.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
namespace Storage {
|
||||
namespace Encodings {
|
||||
|
||||
namespace CommodoreGCR {
|
||||
/*!
|
||||
@returns the proportion of a second that each bit of data within the specified @c time_zone
|
||||
should idiomatically occupy.
|
||||
*/
|
||||
Time length_of_a_bit_in_time_zone(unsigned int time_zone);
|
||||
|
||||
/*!
|
||||
@returns the five-bit GCR encoding for the low four bits of @c nibble.
|
||||
*/
|
||||
unsigned int encoding_for_nibble(uint8_t nibble);
|
||||
|
||||
/*!
|
||||
@returns the ten-bit GCR encoding for @c byte.
|
||||
*/
|
||||
unsigned int encoding_for_byte(uint8_t byte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,15 +19,15 @@ G64::G64(const char *file_name)
|
||||
_file = fopen(file_name, "rb");
|
||||
|
||||
if(!_file)
|
||||
throw ErrorNotGCR;
|
||||
throw ErrorCantOpen;
|
||||
|
||||
// read and check the file signature
|
||||
char signature[8];
|
||||
if(fread(signature, 1, 8, _file) != 8)
|
||||
throw ErrorNotGCR;
|
||||
throw ErrorNotG64;
|
||||
|
||||
if(memcmp(signature, "GCR-1541", 8))
|
||||
throw ErrorNotGCR;
|
||||
throw ErrorNotG64;
|
||||
|
||||
// check the version number
|
||||
int version = fgetc(_file);
|
||||
@ -40,9 +40,6 @@ G64::G64(const char *file_name)
|
||||
_number_of_tracks = (uint8_t)fgetc(_file);
|
||||
_maximum_track_size = (uint16_t)fgetc(_file);
|
||||
_maximum_track_size |= (uint16_t)fgetc(_file) << 8;
|
||||
|
||||
// for(size_t c = 0; c < _number_of_tracks; c++)
|
||||
// get_track_at_position(c);
|
||||
}
|
||||
|
||||
G64::~G64()
|
||||
|
@ -13,16 +13,28 @@
|
||||
|
||||
namespace Storage {
|
||||
|
||||
/*!
|
||||
Provies a @c Disk containing a G64 disk image — a raw but perfectly-clocked GCR stream.
|
||||
*/
|
||||
class G64: public Disk {
|
||||
public:
|
||||
/*!
|
||||
Construct a @c G64 containing content from the file with name @c file_name.
|
||||
|
||||
@throws ErrorCantOpen if this file can't be opened.
|
||||
@throws ErrorNotG64 if the file doesn't appear to contain a .G64 format image.
|
||||
@throws ErrorUnknownVersion if this file appears to be a .G64 but has an unrecognised version number.
|
||||
*/
|
||||
G64(const char *file_name);
|
||||
~G64();
|
||||
|
||||
enum {
|
||||
ErrorNotGCR,
|
||||
ErrorCantOpen,
|
||||
ErrorNotG64,
|
||||
ErrorUnknownVersion
|
||||
};
|
||||
|
||||
// implemented to satisfy @c Disk
|
||||
unsigned int get_head_position_count();
|
||||
std::shared_ptr<Track> get_track_at_position(unsigned int position);
|
||||
|
||||
|
@ -13,9 +13,17 @@
|
||||
|
||||
namespace Storage {
|
||||
|
||||
/*!
|
||||
Contains either an absolute time or a time interval, described as a quotient, in terms of a
|
||||
clock rate to which the time is relative and its length in cycles based on that clock rate.
|
||||
*/
|
||||
struct Time {
|
||||
unsigned int length, clock_rate;
|
||||
|
||||
/*!
|
||||
Reduces this @c Time to its simplest form — eliminates all common factors from @c length
|
||||
and @c clock_rate.
|
||||
*/
|
||||
inline void simplify()
|
||||
{
|
||||
unsigned int common_divisor = NumberTheory::greatest_common_divisor(length, clock_rate);
|
||||
@ -23,6 +31,9 @@ struct Time {
|
||||
clock_rate /= common_divisor;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the floating point conversion of this @c Time. This will often be less precise.
|
||||
*/
|
||||
inline float get_float()
|
||||
{
|
||||
return (float)length / (float)clock_rate;
|
||||
|
@ -14,18 +14,27 @@
|
||||
|
||||
namespace Storage {
|
||||
|
||||
/*!
|
||||
Provides a @c Tape containing a Commodore-format tape image, which is simply a timed list of zero crossings.
|
||||
*/
|
||||
class CommodoreTAP: public Tape {
|
||||
public:
|
||||
/*!
|
||||
Constructs a @c CommodoreTAP containing content from the file with name @c file_name.
|
||||
|
||||
@throws ErrorNotCommodoreTAP if this file could not be opened and recognised as a valid Commodore-format TAP.
|
||||
*/
|
||||
CommodoreTAP(const char *file_name);
|
||||
~CommodoreTAP();
|
||||
|
||||
Pulse get_next_pulse();
|
||||
void reset();
|
||||
|
||||
enum {
|
||||
ErrorNotCommodoreTAP
|
||||
};
|
||||
|
||||
// implemented to satisfy @c Tape
|
||||
Pulse get_next_pulse();
|
||||
void reset();
|
||||
|
||||
private:
|
||||
FILE *_file;
|
||||
bool _updated_layout;
|
||||
|
@ -15,18 +15,27 @@
|
||||
|
||||
namespace Storage {
|
||||
|
||||
/*!
|
||||
Provides a @c Tape containing a UEF tape image.
|
||||
*/
|
||||
class UEF : public Tape {
|
||||
public:
|
||||
/*!
|
||||
Constructs a @c UEF containing content from the file with name @c file_name.
|
||||
|
||||
@throws ErrorNotUEF if this file could not be opened and recognised as a valid UEF.
|
||||
*/
|
||||
UEF(const char *file_name);
|
||||
~UEF();
|
||||
|
||||
Pulse get_next_pulse();
|
||||
void reset();
|
||||
|
||||
enum {
|
||||
ErrorNotUEF
|
||||
};
|
||||
|
||||
// implemented to satisfy @c Tape
|
||||
Pulse get_next_pulse();
|
||||
void reset();
|
||||
|
||||
private:
|
||||
gzFile _file;
|
||||
unsigned int _time_base;
|
||||
|
@ -16,18 +16,63 @@
|
||||
|
||||
namespace Storage {
|
||||
|
||||
/*!
|
||||
Provides a mechanism for arbitrarily timed events to be processed according to a fixed-base
|
||||
discrete clock signal, ensuring correct timing.
|
||||
|
||||
Subclasses are responsible for calling @c set_next_event_time_interval to establish the time
|
||||
until a next event; @c process_next_event will be called when that event occurs, with progression
|
||||
determined via @c run_for_cycles.
|
||||
|
||||
Due to the aggregation of total timing information between events — e.g. if an event loop has
|
||||
a clock rate of 1000 ticks per second and a steady stream of events that occur 10,000 times a second,
|
||||
bookkeeping is necessary to ensure that 10 events are triggered per tick — subclasses should call
|
||||
@c reset_timer if there is a discontinuity in events.
|
||||
|
||||
Subclasses may also call @c jump_to_next_event to cause the next event to be communicated instantly.
|
||||
|
||||
Subclasses are therefore expected to call @c set_next_event_time_interval upon obtaining an event stream,
|
||||
and again in response to each call to @c process_next_event while events are ongoing. They may use
|
||||
@c reset_timer to initiate a distinctly-timed stream or @c jump_to_next_event to short-circuit the timing
|
||||
loop and fast forward immediately to the next event.
|
||||
*/
|
||||
class TimedEventLoop {
|
||||
public:
|
||||
/*!
|
||||
Constructs a timed event loop that will be clocked at @c input_clock_rate.
|
||||
*/
|
||||
TimedEventLoop(unsigned int input_clock_rate);
|
||||
|
||||
/*!
|
||||
Advances the event loop by @c number_of_cycles cycles.
|
||||
*/
|
||||
void run_for_cycles(int number_of_cycles);
|
||||
|
||||
protected:
|
||||
void reset_timer();
|
||||
/*!
|
||||
Sets the time interval, as a proportion of a second, until the next event should be triggered.
|
||||
*/
|
||||
void set_next_event_time_interval(Time interval);
|
||||
void jump_to_next_event();
|
||||
|
||||
/*!
|
||||
Communicates that the next event is triggered. A subclass will idiomatically process that event
|
||||
and make a fresh call to @c set_next_event_time_interval to keep the event loop running.
|
||||
*/
|
||||
virtual void process_next_event() = 0;
|
||||
|
||||
/*!
|
||||
Resets timing, throwing away any current internal state. So clears any fractional ticks
|
||||
that the event loop is currently tracking.
|
||||
*/
|
||||
void reset_timer();
|
||||
|
||||
/*!
|
||||
Causes an immediate call to @c process_next_event and a call to @c reset_timer with the
|
||||
net effect of processing the current event immediately and fast forwarding exactly to the
|
||||
start of the interval prior to the next event.
|
||||
*/
|
||||
void jump_to_next_event();
|
||||
|
||||
private:
|
||||
unsigned int _input_clock_rate;
|
||||
Time _event_interval;
|
||||
|
Loading…
x
Reference in New Issue
Block a user