mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-25 02:29:08 +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 NumberTheory::least_common_multiple(unsigned int a, unsigned int b)
|
||||||
{
|
{
|
||||||
unsigned int gcd = greatest_common_divisor(a, 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
|
#define Factors_hpp
|
||||||
|
|
||||||
namespace NumberTheory {
|
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);
|
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);
|
unsigned int least_common_multiple(unsigned int a, unsigned int b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,26 +15,74 @@
|
|||||||
|
|
||||||
namespace Storage {
|
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 {
|
class DiskDrive: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop {
|
||||||
public:
|
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);
|
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);
|
void set_expected_bit_length(Time bit_length);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Inserts @c disk into the drive.
|
||||||
|
*/
|
||||||
void set_disk(std::shared_ptr<Disk> disk);
|
void set_disk(std::shared_ptr<Disk> disk);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@returns @c true if a disk is currently inserted; @c false otherwise.
|
||||||
|
*/
|
||||||
bool has_disk();
|
bool has_disk();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Advances the drive by @c number_of_cycles cycles.
|
||||||
|
*/
|
||||||
void run_for_cycles(int number_of_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();
|
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);
|
void step(int direction);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Enables or disables the disk motor.
|
||||||
|
*/
|
||||||
void set_motor_on(bool motor_on);
|
void set_motor_on(bool motor_on);
|
||||||
|
|
||||||
// to satisfy DigitalPhaseLockedLoop::Delegate
|
// to satisfy DigitalPhaseLockedLoop::Delegate
|
||||||
void digital_phase_locked_loop_output_bit(int value);
|
void digital_phase_locked_loop_output_bit(int value);
|
||||||
|
|
||||||
protected:
|
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;
|
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;
|
virtual void process_index_hole() = 0;
|
||||||
|
|
||||||
// for TimedEventLoop
|
// for TimedEventLoop
|
||||||
|
@ -18,3 +18,34 @@ Time Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(unsigned int
|
|||||||
duration.clock_rate = 4000000;
|
duration.clock_rate = 4000000;
|
||||||
return duration;
|
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
|
#define CommodoreGCR_hpp
|
||||||
|
|
||||||
#include "../../Storage.hpp"
|
#include "../../Storage.hpp"
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
namespace Storage {
|
namespace Storage {
|
||||||
namespace Encodings {
|
namespace Encodings {
|
||||||
|
|
||||||
namespace CommodoreGCR {
|
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);
|
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");
|
_file = fopen(file_name, "rb");
|
||||||
|
|
||||||
if(!_file)
|
if(!_file)
|
||||||
throw ErrorNotGCR;
|
throw ErrorCantOpen;
|
||||||
|
|
||||||
// read and check the file signature
|
// read and check the file signature
|
||||||
char signature[8];
|
char signature[8];
|
||||||
if(fread(signature, 1, 8, _file) != 8)
|
if(fread(signature, 1, 8, _file) != 8)
|
||||||
throw ErrorNotGCR;
|
throw ErrorNotG64;
|
||||||
|
|
||||||
if(memcmp(signature, "GCR-1541", 8))
|
if(memcmp(signature, "GCR-1541", 8))
|
||||||
throw ErrorNotGCR;
|
throw ErrorNotG64;
|
||||||
|
|
||||||
// check the version number
|
// check the version number
|
||||||
int version = fgetc(_file);
|
int version = fgetc(_file);
|
||||||
@ -40,9 +40,6 @@ G64::G64(const char *file_name)
|
|||||||
_number_of_tracks = (uint8_t)fgetc(_file);
|
_number_of_tracks = (uint8_t)fgetc(_file);
|
||||||
_maximum_track_size = (uint16_t)fgetc(_file);
|
_maximum_track_size = (uint16_t)fgetc(_file);
|
||||||
_maximum_track_size |= (uint16_t)fgetc(_file) << 8;
|
_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()
|
G64::~G64()
|
||||||
|
@ -13,16 +13,28 @@
|
|||||||
|
|
||||||
namespace Storage {
|
namespace Storage {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Provies a @c Disk containing a G64 disk image — a raw but perfectly-clocked GCR stream.
|
||||||
|
*/
|
||||||
class G64: public Disk {
|
class G64: public Disk {
|
||||||
public:
|
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(const char *file_name);
|
||||||
~G64();
|
~G64();
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
ErrorNotGCR,
|
ErrorCantOpen,
|
||||||
|
ErrorNotG64,
|
||||||
ErrorUnknownVersion
|
ErrorUnknownVersion
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// implemented to satisfy @c Disk
|
||||||
unsigned int get_head_position_count();
|
unsigned int get_head_position_count();
|
||||||
std::shared_ptr<Track> get_track_at_position(unsigned int position);
|
std::shared_ptr<Track> get_track_at_position(unsigned int position);
|
||||||
|
|
||||||
|
@ -13,9 +13,17 @@
|
|||||||
|
|
||||||
namespace Storage {
|
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 {
|
struct Time {
|
||||||
unsigned int length, clock_rate;
|
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()
|
inline void simplify()
|
||||||
{
|
{
|
||||||
unsigned int common_divisor = NumberTheory::greatest_common_divisor(length, clock_rate);
|
unsigned int common_divisor = NumberTheory::greatest_common_divisor(length, clock_rate);
|
||||||
@ -23,6 +31,9 @@ struct Time {
|
|||||||
clock_rate /= common_divisor;
|
clock_rate /= common_divisor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns the floating point conversion of this @c Time. This will often be less precise.
|
||||||
|
*/
|
||||||
inline float get_float()
|
inline float get_float()
|
||||||
{
|
{
|
||||||
return (float)length / (float)clock_rate;
|
return (float)length / (float)clock_rate;
|
||||||
|
@ -14,18 +14,27 @@
|
|||||||
|
|
||||||
namespace Storage {
|
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 {
|
class CommodoreTAP: public Tape {
|
||||||
public:
|
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(const char *file_name);
|
||||||
~CommodoreTAP();
|
~CommodoreTAP();
|
||||||
|
|
||||||
Pulse get_next_pulse();
|
|
||||||
void reset();
|
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
ErrorNotCommodoreTAP
|
ErrorNotCommodoreTAP
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// implemented to satisfy @c Tape
|
||||||
|
Pulse get_next_pulse();
|
||||||
|
void reset();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FILE *_file;
|
FILE *_file;
|
||||||
bool _updated_layout;
|
bool _updated_layout;
|
||||||
|
@ -15,18 +15,27 @@
|
|||||||
|
|
||||||
namespace Storage {
|
namespace Storage {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Provides a @c Tape containing a UEF tape image.
|
||||||
|
*/
|
||||||
class UEF : public Tape {
|
class UEF : public Tape {
|
||||||
public:
|
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(const char *file_name);
|
||||||
~UEF();
|
~UEF();
|
||||||
|
|
||||||
Pulse get_next_pulse();
|
|
||||||
void reset();
|
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
ErrorNotUEF
|
ErrorNotUEF
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// implemented to satisfy @c Tape
|
||||||
|
Pulse get_next_pulse();
|
||||||
|
void reset();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
gzFile _file;
|
gzFile _file;
|
||||||
unsigned int _time_base;
|
unsigned int _time_base;
|
||||||
|
@ -16,18 +16,63 @@
|
|||||||
|
|
||||||
namespace Storage {
|
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 {
|
class TimedEventLoop {
|
||||||
public:
|
public:
|
||||||
|
/*!
|
||||||
|
Constructs a timed event loop that will be clocked at @c input_clock_rate.
|
||||||
|
*/
|
||||||
TimedEventLoop(unsigned int 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);
|
void run_for_cycles(int number_of_cycles);
|
||||||
|
|
||||||
protected:
|
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 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;
|
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:
|
private:
|
||||||
unsigned int _input_clock_rate;
|
unsigned int _input_clock_rate;
|
||||||
Time _event_interval;
|
Time _event_interval;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user