1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-11-02 18:16:08 +00:00

Compare commits

...

27 Commits

Author SHA1 Message Date
Thomas Harte
94359e9c75 Merge pull request #458 from TomHarte/ApplePhase
Corrects NTSC Q phase
2018-06-03 08:11:43 -04:00
Thomas Harte
076fa55651 Corrects: flux set is no-flux incoming.
This restores good sleeping behaviour.
2018-06-03 08:11:17 -04:00
Thomas Harte
d380595ad4 Unrolls the loops for slightly fewer conditionals. 2018-06-03 07:27:03 -04:00
Thomas Harte
d84b8700a3 Switches the Apple II to one byte per pixel.
Just trying to get it right for now; optimisation to come.
2018-06-02 22:03:45 -04:00
Thomas Harte
80b281d9f1 Switches back to whole bytes per pixel, owing to persistent precision problems at 1bpp.
Also fixes the inaccurately-named `cycles_since_update_`.
2018-06-02 18:25:00 -04:00
Thomas Harte
69dc3cc4d8 Switches to using the same varying for byte and subpixel selection. 2018-06-01 22:52:29 -04:00
Thomas Harte
1a9cea050e Minor: ensure AY registers *read* as 0 from reset, as well as being 0. 2018-06-01 19:48:42 -04:00
Thomas Harte
0833412df9 Corrects port for ZON-X reads. 2018-06-01 19:45:37 -04:00
Thomas Harte
35e84ff1a8 Corrects NTSC quadrature phase. 2018-05-31 21:40:46 -04:00
Thomas Harte
8dd7c6ef23 Eliminates 'reversed_c' as I no longer believe low-resolution colour numbers are reversed.
Also gets explicit about phase.
2018-05-29 22:30:45 -04:00
Thomas Harte
a26ab7086d Merge pull request #456 from TomHarte/TristateSleeper
Commutes `Sleeper` to `ClockingHint::Source`
2018-05-28 18:25:21 -04:00
Thomas Harte
b2464598d0 Forces the Apple II bus handler call inline. 2018-05-28 18:21:01 -04:00
Thomas Harte
6812a001d8 Teaches the Oric to apply a lighter Disk II touch when possible. 2018-05-28 18:20:43 -04:00
Thomas Harte
6c16754a6b Strips further improper constexprs. 2018-05-28 17:48:55 -04:00
Thomas Harte
75f9e3caeb Resolves incorrect bracketing. 2018-05-28 17:48:35 -04:00
Thomas Harte
ad5afe21ee Removes constexpr from things which are not const. Duh. 2018-05-28 17:28:57 -04:00
Thomas Harte
8a566cc1dd Experimentally goes to town on constexpr. 2018-05-28 17:20:11 -04:00
Thomas Harte
928aab13dc Introduces more granular clocking announcements to the Disk II.
As well as making it accept the clock rate it'll actually receive, to supply to the drives, so that they spin at the proper speed.
2018-05-28 17:19:29 -04:00
Thomas Harte
f3fe711542 Attempts to reduce FDC costs. 2018-05-27 23:55:04 -04:00
Thomas Harte
db8d8d8404 Commutes Sleeper to ClockingHint::Source, making state more granular. 2018-05-27 23:17:06 -04:00
Thomas Harte
6220ccb5d3 Merge pull request #455 from TomHarte/HumptyDumpty
Relaxes .p validation even further
2018-05-27 17:01:07 -04:00
Thomas Harte
20843305dd Removes unused calculation of vars. 2018-05-27 13:31:30 -04:00
Thomas Harte
8f6c0f6a8d Eliminates vars test.
At least Humpty Dumpty is a working .p that doesn't satisfy the test.
2018-05-26 19:05:35 -04:00
Thomas Harte
ede2df7e70 Merge pull request #452 from TomHarte/NIBWriting
Adds write support for NIBs
2018-05-25 18:40:35 -04:00
Thomas Harte
d45231c1a8 Introduces an additional validation test.
Thereby satisfying the TODO.
2018-05-25 18:40:15 -04:00
Thomas Harte
772812b35f Corrects improper textual reference to interface names. 2018-05-25 18:31:20 -04:00
Thomas Harte
f443fd44b5 Introduces support for writing NIBs. 2018-05-25 18:30:55 -04:00
39 changed files with 539 additions and 431 deletions

View File

@@ -52,79 +52,79 @@
*/ */
template <class T> class WrappedInt { template <class T> class WrappedInt {
public: public:
inline WrappedInt(int l) : length_(l) {} constexpr WrappedInt(int l) : length_(l) {}
inline WrappedInt() : length_(0) {} constexpr WrappedInt() : length_(0) {}
inline T &operator =(const T &rhs) { T &operator =(const T &rhs) {
length_ = rhs.length_; length_ = rhs.length_;
return *this; return *this;
} }
inline T &operator +=(const T &rhs) { T &operator +=(const T &rhs) {
length_ += rhs.length_; length_ += rhs.length_;
return *static_cast<T *>(this); return *static_cast<T *>(this);
} }
inline T &operator -=(const T &rhs) { T &operator -=(const T &rhs) {
length_ -= rhs.length_; length_ -= rhs.length_;
return *static_cast<T *>(this); return *static_cast<T *>(this);
} }
inline T &operator ++() { T &operator ++() {
++ length_; ++ length_;
return *static_cast<T *>(this); return *static_cast<T *>(this);
} }
inline T &operator ++(int) { T &operator ++(int) {
length_ ++; length_ ++;
return *static_cast<T *>(this); return *static_cast<T *>(this);
} }
inline T &operator --() { T &operator --() {
-- length_; -- length_;
return *static_cast<T *>(this); return *static_cast<T *>(this);
} }
inline T &operator --(int) { T &operator --(int) {
length_ --; length_ --;
return *static_cast<T *>(this); return *static_cast<T *>(this);
} }
inline T &operator %=(const T &rhs) { T &operator %=(const T &rhs) {
length_ %= rhs.length_; length_ %= rhs.length_;
return *static_cast<T *>(this); return *static_cast<T *>(this);
} }
inline T &operator &=(const T &rhs) { T &operator &=(const T &rhs) {
length_ &= rhs.length_; length_ &= rhs.length_;
return *static_cast<T *>(this); return *static_cast<T *>(this);
} }
inline T operator +(const T &rhs) const { return T(length_ + rhs.length_); } constexpr T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
inline T operator -(const T &rhs) const { return T(length_ - rhs.length_); } constexpr T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
inline T operator %(const T &rhs) const { return T(length_ % rhs.length_); } constexpr T operator %(const T &rhs) const { return T(length_ % rhs.length_); }
inline T operator &(const T &rhs) const { return T(length_ & rhs.length_); } constexpr T operator &(const T &rhs) const { return T(length_ & rhs.length_); }
inline T operator -() const { return T(- length_); } constexpr T operator -() const { return T(- length_); }
inline bool operator <(const T &rhs) const { return length_ < rhs.length_; } constexpr bool operator <(const T &rhs) const { return length_ < rhs.length_; }
inline bool operator >(const T &rhs) const { return length_ > rhs.length_; } constexpr bool operator >(const T &rhs) const { return length_ > rhs.length_; }
inline bool operator <=(const T &rhs) const { return length_ <= rhs.length_; } constexpr bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
inline bool operator >=(const T &rhs) const { return length_ >= rhs.length_; } constexpr bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
inline bool operator ==(const T &rhs) const { return length_ == rhs.length_; } constexpr bool operator ==(const T &rhs) const { return length_ == rhs.length_; }
inline bool operator !=(const T &rhs) const { return length_ != rhs.length_; } constexpr bool operator !=(const T &rhs) const { return length_ != rhs.length_; }
inline bool operator !() const { return !length_; } constexpr bool operator !() const { return !length_; }
// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse // bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse
inline int as_int() const { return length_; } constexpr int as_int() const { return length_; }
/*! /*!
Severs from @c this the effect of dividing by @c divisor; @c this will end up with Severs from @c this the effect of dividing by @c divisor; @c this will end up with
the value of @c this modulo @c divisor and @c divided by @c divisor is returned. the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
*/ */
inline T divide(const T &divisor) { T divide(const T &divisor) {
T result(length_ / divisor.length_); T result(length_ / divisor.length_);
length_ %= divisor.length_; length_ %= divisor.length_;
return result; return result;
@@ -134,7 +134,7 @@ template <class T> class WrappedInt {
Flushes the value in @c this. The current value is returned, and the internal value Flushes the value in @c this. The current value is returned, and the internal value
is reset to zero. is reset to zero.
*/ */
inline T flush() { T flush() {
T result(length_); T result(length_);
length_ = 0; length_ = 0;
return result; return result;
@@ -150,34 +150,34 @@ template <class T> class WrappedInt {
/// Describes an integer number of whole cycles: pairs of clock signal transitions. /// Describes an integer number of whole cycles: pairs of clock signal transitions.
class Cycles: public WrappedInt<Cycles> { class Cycles: public WrappedInt<Cycles> {
public: public:
inline Cycles(int l) : WrappedInt<Cycles>(l) {} constexpr Cycles(int l) : WrappedInt<Cycles>(l) {}
inline Cycles() : WrappedInt<Cycles>() {} constexpr Cycles() : WrappedInt<Cycles>() {}
inline Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {} constexpr Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {}
}; };
/// Describes an integer number of half cycles: single clock signal transitions. /// Describes an integer number of half cycles: single clock signal transitions.
class HalfCycles: public WrappedInt<HalfCycles> { class HalfCycles: public WrappedInt<HalfCycles> {
public: public:
inline HalfCycles(int l) : WrappedInt<HalfCycles>(l) {} constexpr HalfCycles(int l) : WrappedInt<HalfCycles>(l) {}
inline HalfCycles() : WrappedInt<HalfCycles>() {} constexpr HalfCycles() : WrappedInt<HalfCycles>() {}
inline HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {} constexpr HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {}
inline HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {} constexpr HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {}
/// @returns The number of whole cycles completely covered by this span of half cycles. /// @returns The number of whole cycles completely covered by this span of half cycles.
inline Cycles cycles() { constexpr Cycles cycles() {
return Cycles(length_ >> 1); return Cycles(length_ >> 1);
} }
/// Flushes the whole cycles in @c this, subtracting that many from the total stored here. /// Flushes the whole cycles in @c this, subtracting that many from the total stored here.
inline Cycles flush_cycles() { Cycles flush_cycles() {
Cycles result(length_ >> 1); Cycles result(length_ >> 1);
length_ &= 1; length_ &= 1;
return result; return result;
} }
/// Flushes the half cycles in @c this, returning the number stored and setting this total to zero. /// Flushes the half cycles in @c this, returning the number stored and setting this total to zero.
inline HalfCycles flush() { HalfCycles flush() {
HalfCycles result(length_); HalfCycles result(length_);
length_ = 0; length_ = 0;
return result; return result;
@@ -187,7 +187,7 @@ class HalfCycles: public WrappedInt<HalfCycles> {
Severs from @c this the effect of dividing by @c divisor; @c this will end up with Severs from @c this the effect of dividing by @c divisor; @c this will end up with
the value of @c this modulo @c divisor and @c divided by @c divisor is returned. the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
*/ */
inline Cycles divide_cycles(const Cycles &divisor) { Cycles divide_cycles(const Cycles &divisor) {
HalfCycles half_divisor = HalfCycles(divisor); HalfCycles half_divisor = HalfCycles(divisor);
Cycles result(length_ / half_divisor.length_); Cycles result(length_ / half_divisor.length_);
length_ %= half_divisor.length_; length_ %= half_divisor.length_;
@@ -203,7 +203,6 @@ template <class T> class HalfClockReceiver: public T {
public: public:
using T::T; using T::T;
using T::run_for;
inline void run_for(const HalfCycles half_cycles) { inline void run_for(const HalfCycles half_cycles) {
half_cycles_ += half_cycles; half_cycles_ += half_cycles;
T::run_for(half_cycles_.flush_cycles()); T::run_for(half_cycles_.flush_cycles());

View File

@@ -0,0 +1,88 @@
//
// ClockingHintSource.h
// Clock Signal
//
// Created by Thomas Harte on 20/08/2017.
// Copyright 2017 Thomas Harte. All rights reserved.
//
#ifndef ClockingHintSource_hpp
#define ClockingHintSource_hpp
namespace ClockingHint {
enum class Preference {
/// The component doesn't currently require a clock signal.
None,
/// The component can be clocked only immediate prior to (explicit) accesses.
JustInTime,
/// The component require real-time clocking.
RealTime
};
class Source;
struct Observer {
/// Called to inform an observer that the component @c component has changed its clocking requirements.
virtual void set_component_prefers_clocking(Source *component, Preference clocking) = 0;
};
/*!
An clocking hint source is any component that can provide hints as to the type of
clocking required for accurate emulation. A disk controller is an archetypal example.
Types of clocking are:
- none:
a component that acts and reacts to direct contact but does not have a state that autonomously evolves.
E.g. a ROM, RAM, or some kinds of disk controller when not in the process of performing a command.
- just-in-time:
a component that has an evolving state but can receive clock updates only immediately before a
direct contact. This is possibly the most common kind of component.
- real-time:
a component that needs to be clocked in 'real time' (i.e. in terms of the emulated machine). For example
so that it can announce an interrupt at the proper moment, because it is monitoring some aspect of
the machine rather than waiting to be called upon, or because there's some other non-obvious relationship
at play.
A clocking hint source can signal changes in preferred clocking to an observer.
This is intended to allow for performance improvements to machines with components that can be messaged selectively.
The observer callout is virtual so the intended use case is that a machine holds a component that might go through
periods of different clocking requirements.
Transitions should be sufficiently infrequent that a virtual call to announce them costs little enough that
the saved or deferred ::run_fors add up to a substantial amount.
The hint provided is just that: a hint. Owners may perform ::run_for at a greater frequency.
*/
class Source {
public:
/// Registers @c observer as the new clocking observer.
void set_clocking_hint_observer(Observer *observer) {
observer_ = observer;
update_clocking_observer();
}
/// @returns the current preferred clocking strategy.
virtual Preference preferred_clocking() = 0;
private:
Observer *observer_ = nullptr;
protected:
/*!
Provided for subclasses; call this whenever the clocking preference might have changed.
This will notify the observer if there is one.
*/
void update_clocking_observer() {
if(!observer_) return;
observer_->set_component_prefers_clocking(this, preferred_clocking());
}
};
}
#endif /* ClockingHintSource_h */

View File

@@ -1,60 +0,0 @@
//
// Sleeper.h
// Clock Signal
//
// Created by Thomas Harte on 20/08/2017.
// Copyright 2017 Thomas Harte. All rights reserved.
//
#ifndef Sleeper_hpp
#define Sleeper_hpp
/*!
A sleeper is any component that sometimes requires a clock but at other times is 'asleep', i.e. is not doing
any clock-derived work, so needn't receive a clock. A disk controller is an archetypal example.
A sleeper will signal sleeps and wakes to an observer.
This is intended to allow for performance improvements to machines with components that can sleep. The observer
callout is virtual so the intended use case is that a machine holds a component that might sleep. Its transitions
into and out of sleep are sufficiently infrequent that a virtual call to announce them costs sufficiently little that
the saved ::run_fors add up to a substantial amount.
By convention, sleeper components must be willing to accept ::run_for even after announcing sleep. It's a hint,
not a command.
*/
class Sleeper {
public:
Sleeper() : sleep_observer_(nullptr) {}
class SleepObserver {
public:
/// Called to inform an observer that the component @c component has either gone to sleep or become awake.
virtual void set_component_is_sleeping(Sleeper *component, bool is_sleeping) = 0;
};
/// Registers @c observer as the new sleep observer;
void set_sleep_observer(SleepObserver *observer) {
sleep_observer_ = observer;
}
/// @returns @c true if the component is currently sleeping; @c false otherwise.
virtual bool is_sleeping() = 0;
protected:
/// Provided for subclasses; send sleep announcements to the sleep_observer_.
SleepObserver *sleep_observer_;
/*!
Provided for subclasses; call this whenever is_sleeping might have changed, and the observer will be notified,
if one exists.
@c is_sleeping will be called only if there is an observer.
*/
void update_sleep_observer() {
if(!sleep_observer_) return;
sleep_observer_->set_component_is_sleeping(this, is_sleeping());
}
};
#endif /* Sleeper_h */

View File

@@ -43,7 +43,6 @@ class WD1770: public Storage::Disk::MFMController {
/// Runs the controller for @c number_of_cycles cycles. /// Runs the controller for @c number_of_cycles cycles.
void run_for(const Cycles cycles); void run_for(const Cycles cycles);
using Storage::Disk::Controller::run_for;
enum Flag: uint8_t { enum Flag: uint8_t {
NotReady = 0x80, NotReady = 0x80,

View File

@@ -69,7 +69,7 @@ template <class BusHandler> class MOS6560 {
speaker_(audio_generator_) speaker_(audio_generator_)
{ {
crt_->set_svideo_sampling_function( crt_->set_svideo_sampling_function(
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)" "vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
"{" "{"
"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);" "vec2 yc = texture(texID, coordinate).rg / vec2(255.0);"
@@ -125,10 +125,10 @@ template <class BusHandler> class MOS6560 {
19, 86, 123, 59, 19, 86, 123, 59,
}; };
const uint8_t ntsc_chrominances[16] = { const uint8_t ntsc_chrominances[16] = {
255, 255, 7, 71, 255, 255, 121, 57,
25, 86, 48, 112, 103, 42, 80, 16,
0, 119, 7, 71, 0, 9, 121, 57,
25, 86, 48, 112, 103, 42, 80, 16,
}; };
const uint8_t *chrominances; const uint8_t *chrominances;
Outputs::CRT::DisplayType display_type; Outputs::CRT::DisplayType display_type;

View File

@@ -83,8 +83,10 @@ i8272::i8272(BusHandler &bus_handler, Cycles clock_rate) :
posit_event(static_cast<int>(Event8272::CommandByte)); posit_event(static_cast<int>(Event8272::CommandByte));
} }
bool i8272::is_sleeping() { ClockingHint::Preference i8272::preferred_clocking() {
return is_sleeping_ && Storage::Disk::MFMController::is_sleeping(); const auto mfm_controller_preferred_clocking = Storage::Disk::MFMController::preferred_clocking();
if(mfm_controller_preferred_clocking != ClockingHint::Preference::None) return mfm_controller_preferred_clocking;
return is_sleeping_ ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
} }
void i8272::run_for(Cycles cycles) { void i8272::run_for(Cycles cycles) {
@@ -159,7 +161,7 @@ void i8272::run_for(Cycles cycles) {
} }
is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_; is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_;
if(is_sleeping_) update_sleep_observer(); if(is_sleeping_) update_clocking_observer();
} }
void i8272::set_register(int address, uint8_t value) { void i8272::set_register(int address, uint8_t value) {
@@ -198,7 +200,7 @@ uint8_t i8272::get_register(int address) {
#define MS_TO_CYCLES(x) x * 8000 #define MS_TO_CYCLES(x) x * 8000
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(mask); return; case __LINE__: #define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(mask); return; case __LINE__:
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_sleep_observer(); case __LINE__: if(delay_time_) return; #define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_clocking_observer(); case __LINE__: if(delay_time_) return;
#define PASTE(x, y) x##y #define PASTE(x, y) x##y
#define CONCAT(x, y) PASTE(x, y) #define CONCAT(x, y) PASTE(x, y)
@@ -257,7 +259,7 @@ uint8_t i8272::get_register(int address) {
if(drives_[active_drive_].head_unload_delay[active_head_] == 0) { \ if(drives_[active_drive_].head_unload_delay[active_head_] == 0) { \
head_timers_running_++; \ head_timers_running_++; \
is_sleeping_ = false; \ is_sleeping_ = false; \
update_sleep_observer(); \ update_clocking_observer(); \
} \ } \
drives_[active_drive_].head_unload_delay[active_head_] = MS_TO_CYCLES(head_unload_time_);\ drives_[active_drive_].head_unload_delay[active_head_] = MS_TO_CYCLES(head_unload_time_);\
} }
@@ -720,7 +722,7 @@ void i8272::posit_event(int event_type) {
if(drives_[drive].phase != Drive::Seeking) { if(drives_[drive].phase != Drive::Seeking) {
drives_seeking_++; drives_seeking_++;
is_sleeping_ = false; is_sleeping_ = false;
update_sleep_observer(); update_clocking_observer();
} }
// Set currently seeking, with a step to occur right now (yes, it sounds like jamming these // Set currently seeking, with a step to occur right now (yes, it sounds like jamming these

View File

@@ -39,7 +39,7 @@ class i8272: public Storage::Disk::MFMController {
void set_dma_acknowledge(bool dack); void set_dma_acknowledge(bool dack);
void set_terminal_count(bool tc); void set_terminal_count(bool tc);
bool is_sleeping(); ClockingHint::Preference preferred_clocking() override;
protected: protected:
virtual void select_drive(int number) = 0; virtual void select_drive(int number) = 0;
@@ -67,7 +67,7 @@ class i8272: public Storage::Disk::MFMController {
ResultEmpty = (1 << 5), ResultEmpty = (1 << 5),
NoLongerReady = (1 << 6) NoLongerReady = (1 << 6)
}; };
void posit_event(int type); void posit_event(int type) override;
int interesting_event_mask_ = static_cast<int>(Event8272::CommandByte); int interesting_event_mask_ = static_cast<int>(Event8272::CommandByte);
int resume_point_ = 0; int resume_point_ = 0;
bool is_access_command_ = false; bool is_access_command_ = false;

View File

@@ -92,7 +92,7 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
Concurrency::DeferringAsyncTaskQueue &task_queue_; Concurrency::DeferringAsyncTaskQueue &task_queue_;
int selected_register_ = 0; int selected_register_ = 0;
uint8_t registers_[16]; uint8_t registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
uint8_t output_registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; uint8_t output_registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
uint8_t port_inputs_[2]; uint8_t port_inputs_[2];

View File

@@ -19,12 +19,13 @@ namespace {
const uint8_t input_flux = 0x1; const uint8_t input_flux = 0x1;
} }
DiskII::DiskII() : DiskII::DiskII(int clock_rate) :
clock_rate_(clock_rate),
inputs_(input_command), inputs_(input_command),
drives_{{2045454, 300, 1}, {2045454, 300, 1}} drives_{{static_cast<unsigned int>(clock_rate), 300, 1}, {static_cast<unsigned int>(clock_rate), 300, 1}}
{ {
drives_[0].set_sleep_observer(this); drives_[0].set_clocking_hint_observer(this);
drives_[1].set_sleep_observer(this); drives_[1].set_clocking_hint_observer(this);
drives_[active_drive_].set_event_delegate(this); drives_[active_drive_].set_event_delegate(this);
} }
@@ -73,9 +74,8 @@ void DiskII::select_drive(int drive) {
} }
void DiskII::run_for(const Cycles cycles) { void DiskII::run_for(const Cycles cycles) {
if(is_sleeping()) return; if(preferred_clocking() == ClockingHint::Preference::None) return;
if(!controller_can_sleep_) {
int integer_cycles = cycles.as_int(); int integer_cycles = cycles.as_int();
while(integer_cycles--) { while(integer_cycles--) {
const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6); const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6);
@@ -93,10 +93,10 @@ void DiskII::run_for(const Cycles cycles) {
// If the controller is in the sense write protect loop but the register will never change, // If the controller is in the sense write protect loop but the register will never change,
// short circuit further work and return now. // short circuit further work and return now.
if(shift_register_ == is_write_protected() ? 0xff : 0x00) { if(shift_register_ == (is_write_protected() ? 0xff : 0x00)) {
if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(integer_cycles)); if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(integer_cycles));
if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(integer_cycles)); if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(integer_cycles));
set_controller_can_sleep(); decide_clocking_preference();
return; return;
} }
break; break;
@@ -110,34 +110,39 @@ void DiskII::run_for(const Cycles cycles) {
drives_[active_drive_].write_bit(!!((state_ ^ address) & 0x80)); drives_[active_drive_].write_bit(!!((state_ ^ address) & 0x80));
} }
// TODO: surely there's a less heavyweight solution than this? // TODO: surely there's a less heavyweight solution than inline updates?
if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(1)); if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(1));
if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(1)); if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(1));
} }
} else {
if(!drive_is_sleeping_[0]) drives_[0].run_for(cycles);
if(!drive_is_sleeping_[1]) drives_[1].run_for(cycles);
}
set_controller_can_sleep(); decide_clocking_preference();
} }
void DiskII::set_controller_can_sleep() { void DiskII::decide_clocking_preference() {
// Permit the controller to sleep if it's in sense write protect mode, and the shift register ClockingHint::Preference prior_preference = clocking_preference_;
// has already filled with the result of shifting eight times.
bool controller_could_sleep = controller_can_sleep_; // If in read mode, clocking is either:
controller_can_sleep_ = //
( // just-in-time, if drives are running or the shift register has any 1s in it or a flux event hasn't yet passed; or
(inputs_ == input_flux) && // none, given that drives are not running, the shift register has already emptied and there's no flux about to be received.
!motor_is_enabled_ && if(!(inputs_ & ~input_flux)) {
!shift_register_ clocking_preference_ = (!motor_is_enabled_ && !shift_register_ && (inputs_&input_flux)) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
) || }
(
(inputs_ == (input_command | input_flux)) && // If in writing mode, clocking is real time.
(shift_register_ == (is_write_protected() ? 0xff : 0x00)) if(inputs_ & input_mode) {
); clocking_preference_ = ClockingHint::Preference::RealTime;
if(controller_could_sleep != controller_can_sleep_) }
update_sleep_observer();
// If in sense-write-protect mode, clocking is just-in-time if the shift register hasn't yet filled with the value that
// corresponds to the current write protect status. Otherwise it is none.
if((inputs_ & ~input_flux) == input_command) {
clocking_preference_ = (shift_register_ == (is_write_protected() ? 0xff : 0x00)) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
}
// Announce a change if there was one.
if(prior_preference != clocking_preference_)
update_clocking_observer();
} }
bool DiskII::is_write_protected() { bool DiskII::is_write_protected() {
@@ -195,18 +200,18 @@ void DiskII::set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int driv
void DiskII::process_event(const Storage::Disk::Track::Event &event) { void DiskII::process_event(const Storage::Disk::Track::Event &event) {
if(event.type == Storage::Disk::Track::Event::FluxTransition) { if(event.type == Storage::Disk::Track::Event::FluxTransition) {
inputs_ &= ~input_flux; inputs_ &= ~input_flux;
set_controller_can_sleep(); decide_clocking_preference();
} }
} }
void DiskII::set_component_is_sleeping(Sleeper *component, bool is_sleeping) { void DiskII::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) {
drive_is_sleeping_[0] = drives_[0].is_sleeping(); drive_is_sleeping_[0] = drives_[0].preferred_clocking() == ClockingHint::Preference::None;
drive_is_sleeping_[1] = drives_[1].is_sleeping(); drive_is_sleeping_[1] = drives_[1].preferred_clocking() == ClockingHint::Preference::None;
update_sleep_observer(); decide_clocking_preference();
} }
bool DiskII::is_sleeping() { ClockingHint::Preference DiskII::preferred_clocking() {
return controller_can_sleep_ && drive_is_sleeping_[0] && drive_is_sleeping_[1]; return clocking_preference_;
} }
void DiskII::set_data_input(uint8_t input) { void DiskII::set_data_input(uint8_t input) {
@@ -243,11 +248,11 @@ int DiskII::read_address(int address) {
break; break;
case 0xf: case 0xf:
if(!(inputs_ & input_mode)) if(!(inputs_ & input_mode))
drives_[active_drive_].begin_writing(Storage::Time(1, 2045454), false); drives_[active_drive_].begin_writing(Storage::Time(1, clock_rate_), false);
inputs_ |= input_mode; inputs_ |= input_mode;
break; break;
} }
set_controller_can_sleep(); decide_clocking_preference();
return (address & 1) ? 0xff : shift_register_; return (address & 1) ? 0xff : shift_register_;
} }

View File

@@ -10,7 +10,7 @@
#define DiskII_hpp #define DiskII_hpp
#include "../../ClockReceiver/ClockReceiver.hpp" #include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/Sleeper.hpp" #include "../../ClockReceiver/ClockingHintSource.hpp"
#include "../../Storage/Disk/Disk.hpp" #include "../../Storage/Disk/Disk.hpp"
#include "../../Storage/Disk/Drive.hpp" #include "../../Storage/Disk/Drive.hpp"
@@ -28,10 +28,10 @@ namespace Apple {
*/ */
class DiskII: class DiskII:
public Storage::Disk::Drive::EventDelegate, public Storage::Disk::Drive::EventDelegate,
public Sleeper::SleepObserver, public ClockingHint::Source,
public Sleeper { public ClockingHint::Observer {
public: public:
DiskII(); DiskII(int clock_rate);
/// Sets the current external value of the data bus. /// Sets the current external value of the data bus.
void set_data_input(uint8_t input); void set_data_input(uint8_t input);
@@ -76,7 +76,7 @@ class DiskII:
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive); void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
// As per Sleeper. // As per Sleeper.
bool is_sleeping() override; ClockingHint::Preference preferred_clocking() override;
// The Disk II functions as a potential target for @c Activity::Sources. // The Disk II functions as a potential target for @c Activity::Sources.
void set_activity_observer(Activity::Observer *observer); void set_activity_observer(Activity::Observer *observer);
@@ -95,7 +95,9 @@ class DiskII:
uint8_t trigger_address(int address, uint8_t value); uint8_t trigger_address(int address, uint8_t value);
void process_event(const Storage::Disk::Track::Event &event) override; void process_event(const Storage::Disk::Track::Event &event) override;
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override; void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override;
const int clock_rate_ = 0;
uint8_t state_ = 0; uint8_t state_ = 0;
uint8_t inputs_ = 0; uint8_t inputs_ = 0;
@@ -108,11 +110,11 @@ class DiskII:
std::array<uint8_t, 256> state_machine_; std::array<uint8_t, 256> state_machine_;
Storage::Disk::Drive drives_[2]; Storage::Disk::Drive drives_[2];
bool drive_is_sleeping_[2]; bool drive_is_sleeping_[2];
bool controller_can_sleep_ = false;
int active_drive_ = 0; int active_drive_ = 0;
bool motor_is_enabled_ = false; bool motor_is_enabled_ = false;
void set_controller_can_sleep(); void decide_clocking_preference();
ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::RealTime;
uint8_t data_input_ = 0; uint8_t data_input_ = 0;
}; };

View File

@@ -694,7 +694,7 @@ class ConcreteMachine:
public KeyboardMachine::Machine, public KeyboardMachine::Machine,
public Utility::TypeRecipient, public Utility::TypeRecipient,
public CPU::Z80::BusHandler, public CPU::Z80::BusHandler,
public Sleeper::SleepObserver, public ClockingHint::Observer,
public Machine, public Machine,
public Activity::Source { public Activity::Source {
public: public:
@@ -714,11 +714,8 @@ class ConcreteMachine:
Memory::Fuzz(ram_, sizeof(ram_)); Memory::Fuzz(ram_, sizeof(ram_));
// register this class as the sleep observer for the FDC and tape // register this class as the sleep observer for the FDC and tape
fdc_.set_sleep_observer(this); fdc_.set_clocking_hint_observer(this);
fdc_is_sleeping_ = fdc_.is_sleeping(); tape_player_.set_clocking_hint_observer(this);
tape_player_.set_sleep_observer(this);
tape_player_is_sleeping_ = tape_player_.is_sleeping();
ay_.ay().set_port_handler(&key_state_); ay_.ay().set_port_handler(&key_state_);
} }
@@ -749,7 +746,7 @@ class ConcreteMachine:
ay_.run_for(cycle.length); ay_.run_for(cycle.length);
// Clock the FDC, if connected, using a lazy scale by two // Clock the FDC, if connected, using a lazy scale by two
if(has_fdc_ && !fdc_is_sleeping_) fdc_.run_for(Cycles(cycle.length.as_int())); time_since_fdc_update_ += cycle.length;
// Update typing activity // Update typing activity
if(typer_) typer_->run_for(cycle.length); if(typer_) typer_->run_for(cycle.length);
@@ -796,11 +793,13 @@ class ConcreteMachine:
// Check for an FDC access // Check for an FDC access
if(has_fdc_ && (address & 0x580) == 0x100) { if(has_fdc_ && (address & 0x580) == 0x100) {
flush_fdc();
fdc_.set_register(address & 1, *cycle.value); fdc_.set_register(address & 1, *cycle.value);
} }
// Check for a disk motor access // Check for a disk motor access
if(has_fdc_ && !(address & 0x580)) { if(has_fdc_ && !(address & 0x580)) {
flush_fdc();
fdc_.set_motor_on(!!(*cycle.value)); fdc_.set_motor_on(!!(*cycle.value));
} }
break; break;
@@ -815,6 +814,7 @@ class ConcreteMachine:
// Check for an FDC access // Check for an FDC access
if(has_fdc_ && (address & 0x580) == 0x100) { if(has_fdc_ && (address & 0x580) == 0x100) {
flush_fdc();
*cycle.value &= fdc_.get_register(address & 1); *cycle.value &= fdc_.get_register(address & 1);
} }
@@ -858,6 +858,7 @@ class ConcreteMachine:
// Just flush the AY. // Just flush the AY.
ay_.update(); ay_.update();
ay_.flush(); ay_.flush();
flush_fdc();
} }
/// A CRTMachine function; indicates that outputs should be created now. /// A CRTMachine function; indicates that outputs should be created now.
@@ -967,9 +968,10 @@ class ConcreteMachine:
return true; return true;
} }
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override final { void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override final {
fdc_is_sleeping_ = fdc_.is_sleeping(); fdc_is_sleeping_ = fdc_.preferred_clocking() == ClockingHint::Preference::None;
tape_player_is_sleeping_ = tape_player_.is_sleeping(); tape_player_is_sleeping_ = tape_player_.preferred_clocking() == ClockingHint::Preference::None;
printf("FDC: %s, tape %s\n", fdc_is_sleeping_ ? "sleeping" : "regular", tape_player_is_sleeping_ ? "sleeping" : "regular");
} }
// MARK: - Keyboard // MARK: - Keyboard
@@ -1063,6 +1065,14 @@ class ConcreteMachine:
Intel::i8255::i8255<i8255PortHandler> i8255_; Intel::i8255::i8255<i8255PortHandler> i8255_;
FDC fdc_; FDC fdc_;
HalfCycles time_since_fdc_update_;
void flush_fdc() {
// Clock the FDC, if connected, using a lazy scale by two
if(has_fdc_ && !fdc_is_sleeping_) {
fdc_.run_for(Cycles(time_since_fdc_update_.as_int()));
}
time_since_fdc_update_ = HalfCycles(0);
}
InterruptTimer interrupt_timer_; InterruptTimer interrupt_timer_;
Storage::Tape::BinaryTapePlayer tape_player_; Storage::Tape::BinaryTapePlayer tape_player_;

View File

@@ -24,6 +24,8 @@
#include "DiskIICard.hpp" #include "DiskIICard.hpp"
#include "Video.hpp" #include "Video.hpp"
#include "../../ClockReceiver/ForceInline.hpp"
#include "../../Analyser/Static/AppleII/Target.hpp" #include "../../Analyser/Static/AppleII/Target.hpp"
#include <algorithm> #include <algorithm>
@@ -203,7 +205,7 @@ class ConcreteMachine:
return &speaker_; return &speaker_;
} }
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
++ cycles_since_video_update_; ++ cycles_since_video_update_;
++ cycles_since_card_update_; ++ cycles_since_card_update_;
cycles_since_audio_update_ += Cycles(7); cycles_since_audio_update_ += Cycles(7);

View File

@@ -10,7 +10,7 @@
using namespace AppleII; using namespace AppleII;
DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) { DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) : diskii_(2045454) {
auto roms = rom_fetcher( auto roms = rom_fetcher(
"DiskII", "DiskII",
{ {
@@ -20,7 +20,7 @@ DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sec
boot_ = std::move(*roms[0]); boot_ = std::move(*roms[0]);
diskii_.set_state_machine(*roms[1]); diskii_.set_state_machine(*roms[1]);
set_select_constraints(None); set_select_constraints(None);
diskii_.set_sleep_observer(this); diskii_.set_clocking_hint_observer(this);
} }
void DiskIICard::perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) { void DiskIICard::perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) {
@@ -41,7 +41,7 @@ void DiskIICard::perform_bus_operation(Select select, bool is_read, uint16_t add
} }
void DiskIICard::run_for(Cycles cycles, int stretches) { void DiskIICard::run_for(Cycles cycles, int stretches) {
if(diskii_is_sleeping_) return; if(diskii_clocking_preference_ == ClockingHint::Preference::None) return;
diskii_.run_for(Cycles(cycles.as_int() * 2)); diskii_.run_for(Cycles(cycles.as_int() * 2));
} }
@@ -53,7 +53,7 @@ void DiskIICard::set_activity_observer(Activity::Observer *observer) {
diskii_.set_activity_observer(observer); diskii_.set_activity_observer(observer);
} }
void DiskIICard::set_component_is_sleeping(Sleeper *component, bool is_sleeping) { void DiskIICard::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) {
diskii_is_sleeping_ = is_sleeping; diskii_clocking_preference_ = preference;
set_select_constraints(is_sleeping ? (IO | Device) : 0); set_select_constraints((preference != ClockingHint::Preference::RealTime) ? (IO | Device) : 0);
} }

View File

@@ -14,7 +14,7 @@
#include "../../Components/DiskII/DiskII.hpp" #include "../../Components/DiskII/DiskII.hpp"
#include "../../Storage/Disk/Disk.hpp" #include "../../Storage/Disk/Disk.hpp"
#include "../../ClockReceiver/Sleeper.hpp" #include "../../ClockReceiver/ClockingHintSource.hpp"
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
@@ -22,7 +22,7 @@
namespace AppleII { namespace AppleII {
class DiskIICard: public Card, public Sleeper::SleepObserver { class DiskIICard: public Card, public ClockingHint::Observer {
public: public:
DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector); DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector);
@@ -34,10 +34,10 @@ class DiskIICard: public Card, public Sleeper::SleepObserver {
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive); void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
private: private:
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override; void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override;
std::vector<uint8_t> boot_; std::vector<uint8_t> boot_;
Apple::DiskII diskii_; Apple::DiskII diskii_;
bool diskii_is_sleeping_ = false; ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime;
}; };
} }

View File

@@ -10,83 +10,27 @@
using namespace AppleII::Video; using namespace AppleII::Video;
namespace {
struct ScaledByteFiller {
ScaledByteFiller() {
VideoBase::setup_tables();
}
} throwaway;
}
VideoBase::VideoBase() : VideoBase::VideoBase() :
crt_(new Outputs::CRT::CRT(455, 1, Outputs::CRT::DisplayType::NTSC60, 1)) { crt_(new Outputs::CRT::CRT(910, 1, Outputs::CRT::DisplayType::NTSC60, 1)) {
// Set a composite sampling function that assumes 1bpp input, and uses just 7 bits per byte. // Set a composite sampling function that assumes one byte per pixel input, and
// accepts any non-zero value as being fully on, zero being fully off.
crt_->set_composite_sampling_function( crt_->set_composite_sampling_function(
"float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)" "float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)"
"{" "{"
"uint texValue = texture(sampler, coordinate).r;" "return texture(sampler, coordinate).r;"
"texValue >>= int(icoordinate.x) % 7;"
"return float(texValue & 1u);"
"}"); "}");
crt_->set_integer_coordinate_multiplier(7.0f);
// Show only the centre 75% of the TV frame. // Show only the centre 75% of the TV frame.
crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite); crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite);
crt_->set_visible_area(Outputs::CRT::Rect(0.115f, 0.117f, 0.77f, 0.77f)); crt_->set_visible_area(Outputs::CRT::Rect(0.115f, 0.117f, 0.77f, 0.77f));
crt_->set_immediate_default_phase(0.0f);
} }
Outputs::CRT::CRT *VideoBase::get_crt() { Outputs::CRT::CRT *VideoBase::get_crt() {
return crt_.get(); return crt_.get();
} }
uint16_t VideoBase::scaled_byte[256];
uint16_t VideoBase::low_resolution_patterns[2][16];
void VideoBase::setup_tables() {
for(int c = 0; c < 128; ++c) {
const uint16_t value =
((c & 0x01) ? 0x0003 : 0x0000) |
((c & 0x02) ? 0x000c : 0x0000) |
((c & 0x04) ? 0x0030 : 0x0000) |
((c & 0x08) ? 0x0140 : 0x0000) |
((c & 0x10) ? 0x0600 : 0x0000) |
((c & 0x20) ? 0x1800 : 0x0000) |
((c & 0x40) ? 0x6000 : 0x0000);
uint8_t *const table_entry = reinterpret_cast<uint8_t *>(&scaled_byte[c]);
table_entry[0] = static_cast<uint8_t>(value & 0xff);
table_entry[1] = static_cast<uint8_t>(value >> 8);
}
for(int c = 128; c < 256; ++c) {
uint8_t *const source_table_entry = reinterpret_cast<uint8_t *>(&scaled_byte[c & 0x7f]);
uint8_t *const destination_table_entry = reinterpret_cast<uint8_t *>(&scaled_byte[c]);
destination_table_entry[0] = static_cast<uint8_t>(source_table_entry[0] << 1);
destination_table_entry[1] = static_cast<uint8_t>((source_table_entry[1] << 1) | (source_table_entry[0] >> 6));
}
for(int c = 0; c < 16; ++c) {
// Produce the whole 28-bit pattern that would cover two columns.
const int reversed_c = ((c&0x1) ? 0x8 : 0x0) | ((c&0x2) ? 0x4 : 0x0) | ((c&0x4) ? 0x2 : 0x0) | ((c&0x8) ? 0x1 : 0x0);
int pattern = 0;
for(int l = 0; l < 7; ++l) {
pattern <<= 4;
pattern |= reversed_c;
}
// Pack that 28-bit pattern into the appropriate look-up tables.
uint8_t *const left_entry = reinterpret_cast<uint8_t *>(&low_resolution_patterns[0][c]);
uint8_t *const right_entry = reinterpret_cast<uint8_t *>(&low_resolution_patterns[1][c]);
left_entry[0] = static_cast<uint8_t>(pattern);;
left_entry[1] = static_cast<uint8_t>(pattern >> 7);
right_entry[0] = static_cast<uint8_t>(pattern >> 14);
right_entry[1] = static_cast<uint8_t>(pattern >> 21);
}
}
void VideoBase::set_graphics_mode() { void VideoBase::set_graphics_mode() {
use_graphics_mode_ = true; use_graphics_mode_ = true;
} }
@@ -113,19 +57,4 @@ void VideoBase::set_high_resolution() {
void VideoBase::set_character_rom(const std::vector<uint8_t> &character_rom) { void VideoBase::set_character_rom(const std::vector<uint8_t> &character_rom) {
character_rom_ = character_rom; character_rom_ = character_rom;
// Bytes in the character ROM are stored in reverse bit order. Reverse them
// ahead of time so as to be able to use the same scaling table as for
// high-resolution graphics.
for(auto &byte : character_rom_) {
byte =
((byte & 0x40) ? 0x01 : 0x00) |
((byte & 0x20) ? 0x02 : 0x00) |
((byte & 0x10) ? 0x04 : 0x00) |
((byte & 0x08) ? 0x08 : 0x00) |
((byte & 0x04) ? 0x10 : 0x00) |
((byte & 0x02) ? 0x20 : 0x00) |
((byte & 0x01) ? 0x40 : 0x00) |
(byte & 0x80);
}
} }

View File

@@ -27,7 +27,6 @@ class BusHandler {
class VideoBase { class VideoBase {
public: public:
VideoBase(); VideoBase();
static void setup_tables();
/// @returns The CRT this video feed is feeding. /// @returns The CRT this video feed is feeding.
Outputs::CRT::CRT *get_crt(); Outputs::CRT::CRT *get_crt();
@@ -46,9 +45,12 @@ class VideoBase {
protected: protected:
std::unique_ptr<Outputs::CRT::CRT> crt_; std::unique_ptr<Outputs::CRT::CRT> crt_;
uint8_t *pixel_pointer_ = nullptr;
int pixel_pointer_column_ = 0;
bool pixels_are_high_density_ = false;
int video_page_ = 0; int video_page_ = 0;
int row_ = 0, column_ = 0, flash_ = 0; int row_ = 0, column_ = 0, flash_ = 0;
uint16_t *pixel_pointer_ = nullptr;
std::vector<uint8_t> character_rom_; std::vector<uint8_t> character_rom_;
enum class GraphicsMode { enum class GraphicsMode {
@@ -58,10 +60,7 @@ class VideoBase {
} graphics_mode_ = GraphicsMode::LowRes; } graphics_mode_ = GraphicsMode::LowRes;
bool use_graphics_mode_ = false; bool use_graphics_mode_ = false;
bool mixed_mode_ = false; bool mixed_mode_ = false;
uint16_t graphics_carry_ = 0; uint8_t graphics_carry_ = 0;
static uint16_t scaled_byte[256];
static uint16_t low_resolution_patterns[2][16];
}; };
template <class BusHandler> class Video: public VideoBase { template <class BusHandler> class Video: public VideoBase {
@@ -91,7 +90,7 @@ template <class BusHandler> class Video: public VideoBase {
const int cycles_this_line = std::min(65 - column_, int_cycles); const int cycles_this_line = std::min(65 - column_, int_cycles);
if(row_ >= first_sync_line && row_ < first_sync_line + 3) { if(row_ >= first_sync_line && row_ < first_sync_line + 3) {
crt_->output_sync(static_cast<unsigned int>(cycles_this_line) * 7); crt_->output_sync(static_cast<unsigned int>(cycles_this_line) * 14);
} else { } else {
const int ending_column = column_ + cycles_this_line; const int ending_column = column_ + cycles_this_line;
const GraphicsMode line_mode = use_graphics_mode_ ? graphics_mode_ : GraphicsMode::Text; const GraphicsMode line_mode = use_graphics_mode_ ? graphics_mode_ : GraphicsMode::Text;
@@ -101,8 +100,13 @@ template <class BusHandler> class Video: public VideoBase {
// of line 192. // of line 192.
if(column_ < 40) { if(column_ < 40) {
if(row_ < 192) { if(row_ < 192) {
if(!column_) { GraphicsMode pixel_mode = (!mixed_mode_ || row_ < 160) ? line_mode : GraphicsMode::Text;
pixel_pointer_ = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(80, 2)); bool requires_high_density = pixel_mode != GraphicsMode::Text;
if(!column_ || requires_high_density != pixels_are_high_density_) {
if(column_) output_data_to_column(column_);
pixel_pointer_ = crt_->allocate_write_area(561);
pixel_pointer_column_ = column_;
pixels_are_high_density_ = requires_high_density;
graphics_carry_ = 0; graphics_carry_ = 0;
} }
@@ -111,10 +115,7 @@ template <class BusHandler> class Video: public VideoBase {
const int pixel_row = row_ & 7; const int pixel_row = row_ & 7;
const uint16_t row_address = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7)); const uint16_t row_address = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7));
const uint16_t text_address = static_cast<uint16_t>(((video_page_+1) * 0x400) + row_address); const uint16_t text_address = static_cast<uint16_t>(((video_page_+1) * 0x400) + row_address);
const uint16_t graphics_address = static_cast<uint16_t>(((video_page_+1) * 0x2000) + row_address + ((pixel_row&7) << 10));
const int row_shift = (row_&4);
GraphicsMode pixel_mode = (!mixed_mode_ || row_ < 160) ? line_mode : GraphicsMode::Text;
switch(pixel_mode) { switch(pixel_mode) {
case GraphicsMode::Text: { case GraphicsMode::Text: {
const uint8_t inverses[] = { const uint8_t inverses[] = {
@@ -128,35 +129,82 @@ template <class BusHandler> class Video: public VideoBase {
const std::size_t character_address = static_cast<std::size_t>(((character & 0x3f) << 3) + pixel_row); const std::size_t character_address = static_cast<std::size_t>(((character & 0x3f) << 3) + pixel_row);
const uint8_t character_pattern = character_rom_[character_address] ^ inverses[character >> 6]; const uint8_t character_pattern = character_rom_[character_address] ^ inverses[character >> 6];
pixel_pointer_[c] = scaled_byte[character_pattern & 0x7f];
// The character ROM is output MSB to LSB rather than LSB to MSB.
pixel_pointer_[0] = character_pattern & 0x40;
pixel_pointer_[1] = character_pattern & 0x20;
pixel_pointer_[2] = character_pattern & 0x10;
pixel_pointer_[3] = character_pattern & 0x08;
pixel_pointer_[4] = character_pattern & 0x04;
pixel_pointer_[5] = character_pattern & 0x02;
pixel_pointer_[6] = character_pattern & 0x01;
graphics_carry_ = character_pattern & 0x40;
pixel_pointer_ += 7;
} }
} break; } break;
case GraphicsMode::LowRes: case GraphicsMode::LowRes: {
const int row_shift = (row_&4);
// TODO: decompose into two loops, possibly.
for(int c = column_; c < pixel_end; ++c) { for(int c = column_; c < pixel_end; ++c) {
const uint8_t character = bus_handler_.perform_read(static_cast<uint16_t>(text_address + c)); const uint8_t nibble = (bus_handler_.perform_read(static_cast<uint16_t>(text_address + c)) >> row_shift) & 0x0f;
pixel_pointer_[c] = low_resolution_patterns[c&1][(character >> row_shift)&0xf];
}
break;
case GraphicsMode::HighRes: // Low-resolution graphics mode shifts the colour code on a loop, but has to account for whether this
// 14-sample output window is starting at the beginning of a colour cycle or halfway through.
if(c&1) {
pixel_pointer_[0] = pixel_pointer_[4] = pixel_pointer_[8] = pixel_pointer_[12] = nibble & 4;
pixel_pointer_[1] = pixel_pointer_[5] = pixel_pointer_[9] = pixel_pointer_[13] = nibble & 8;
pixel_pointer_[2] = pixel_pointer_[6] = pixel_pointer_[10] = nibble & 1;
pixel_pointer_[3] = pixel_pointer_[7] = pixel_pointer_[11] = nibble & 2;
graphics_carry_ = nibble & 8;
} else {
pixel_pointer_[0] = pixel_pointer_[4] = pixel_pointer_[8] = pixel_pointer_[12] = nibble & 1;
pixel_pointer_[1] = pixel_pointer_[5] = pixel_pointer_[9] = pixel_pointer_[13] = nibble & 2;
pixel_pointer_[2] = pixel_pointer_[6] = pixel_pointer_[10] = nibble & 4;
pixel_pointer_[3] = pixel_pointer_[7] = pixel_pointer_[11] = nibble & 8;
graphics_carry_ = nibble & 2;
}
pixel_pointer_ += 14;
}
} break;
case GraphicsMode::HighRes: {
const uint16_t graphics_address = static_cast<uint16_t>(((video_page_+1) * 0x2000) + row_address + ((pixel_row&7) << 10));
for(int c = column_; c < pixel_end; ++c) { for(int c = column_; c < pixel_end; ++c) {
const uint8_t graphic = bus_handler_.perform_read(static_cast<uint16_t>(graphics_address + c)); const uint8_t graphic = bus_handler_.perform_read(static_cast<uint16_t>(graphics_address + c));
pixel_pointer_[c] = scaled_byte[graphic];
// High resolution graphics shift out LSB to MSB, optionally with a delay of half a pixel.
// If there is a delay, the previous output level is held to bridge the gap.
if(graphic & 0x80) { if(graphic & 0x80) {
reinterpret_cast<uint8_t *>(&pixel_pointer_[c])[0] |= graphics_carry_; pixel_pointer_[0] = graphics_carry_;
pixel_pointer_[1] = pixel_pointer_[2] = graphic & 0x01;
pixel_pointer_[3] = pixel_pointer_[4] = graphic & 0x02;
pixel_pointer_[5] = pixel_pointer_[6] = graphic & 0x04;
pixel_pointer_[7] = pixel_pointer_[8] = graphic & 0x08;
pixel_pointer_[9] = pixel_pointer_[10] = graphic & 0x10;
pixel_pointer_[11] = pixel_pointer_[12] = graphic & 0x20;
pixel_pointer_[13] = graphic & 0x40;
} else {
pixel_pointer_[0] = pixel_pointer_[1] = graphic & 0x01;
pixel_pointer_[2] = pixel_pointer_[3] = graphic & 0x02;
pixel_pointer_[4] = pixel_pointer_[5] = graphic & 0x04;
pixel_pointer_[6] = pixel_pointer_[7] = graphic & 0x08;
pixel_pointer_[8] = pixel_pointer_[9] = graphic & 0x10;
pixel_pointer_[10] = pixel_pointer_[11] = graphic & 0x20;
pixel_pointer_[12] = pixel_pointer_[13] = graphic & 0x40;
} }
graphics_carry_ = (graphic >> 6) & 1; graphics_carry_ = graphic & 0x40;
pixel_pointer_ += 14;
} }
break; } break;
} }
if(ending_column >= 40) { if(ending_column >= 40) {
crt_->output_data(280, 80); output_data_to_column(40);
} }
} else { } else {
if(ending_column >= 40) { if(ending_column >= 40) {
crt_->output_blank(280); crt_->output_blank(560);
} }
} }
} }
@@ -169,13 +217,13 @@ template <class BusHandler> class Video: public VideoBase {
const int first_blank_start = std::max(40, column_); const int first_blank_start = std::max(40, column_);
const int first_blank_end = std::min(first_sync_column, ending_column); const int first_blank_end = std::min(first_sync_column, ending_column);
if(first_blank_end > first_blank_start) { if(first_blank_end > first_blank_start) {
crt_->output_blank(static_cast<unsigned int>(first_blank_end - first_blank_start) * 7); crt_->output_blank(static_cast<unsigned int>(first_blank_end - first_blank_start) * 14);
} }
const int sync_start = std::max(first_sync_column, column_); const int sync_start = std::max(first_sync_column, column_);
const int sync_end = std::min(first_sync_column + 4, ending_column); const int sync_end = std::min(first_sync_column + 4, ending_column);
if(sync_end > sync_start) { if(sync_end > sync_start) {
crt_->output_sync(static_cast<unsigned int>(sync_end - sync_start) * 7); crt_->output_sync(static_cast<unsigned int>(sync_end - sync_start) * 14);
} }
int second_blank_start; int second_blank_start;
@@ -183,7 +231,7 @@ template <class BusHandler> class Video: public VideoBase {
const int colour_burst_start = std::max(first_sync_column + 4, column_); const int colour_burst_start = std::max(first_sync_column + 4, column_);
const int colour_burst_end = std::min(first_sync_column + 7, ending_column); const int colour_burst_end = std::min(first_sync_column + 7, ending_column);
if(colour_burst_end > colour_burst_start) { if(colour_burst_end > colour_burst_start) {
crt_->output_default_colour_burst(static_cast<unsigned int>(colour_burst_end - colour_burst_start) * 7); crt_->output_default_colour_burst(static_cast<unsigned int>(colour_burst_end - colour_burst_start) * 14);
} }
second_blank_start = std::max(first_sync_column + 7, column_); second_blank_start = std::max(first_sync_column + 7, column_);
@@ -192,7 +240,7 @@ template <class BusHandler> class Video: public VideoBase {
} }
if(ending_column > second_blank_start) { if(ending_column > second_blank_start) {
crt_->output_blank(static_cast<unsigned int>(ending_column - second_blank_start) * 7); crt_->output_blank(static_cast<unsigned int>(ending_column - second_blank_start) * 14);
} }
} }
@@ -204,7 +252,7 @@ template <class BusHandler> class Video: public VideoBase {
// Add an extra half a colour cycle of blank; this isn't counted in the run_for // Add an extra half a colour cycle of blank; this isn't counted in the run_for
// count explicitly but is promised. // count explicitly but is promised.
crt_->output_blank(1); crt_->output_blank(2);
} }
} }
} }
@@ -261,6 +309,11 @@ template <class BusHandler> class Video: public VideoBase {
const int flash_length = 8406; const int flash_length = 8406;
BusHandler &bus_handler_; BusHandler &bus_handler_;
void output_data_to_column(int column) {
int length = column - pixel_pointer_column_;
crt_->output_data(static_cast<unsigned int>(length*14), static_cast<unsigned int>(length * (pixels_are_high_density_ ? 14 : 7)));
pixel_pointer_ = nullptr;
}
}; };
} }

View File

@@ -124,19 +124,19 @@ void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) {
if(output_mode == OutputMode::NTSC) { if(output_mode == OutputMode::NTSC) {
crt_->set_svideo_sampling_function( crt_->set_svideo_sampling_function(
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)" "vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
"{" "{"
"uint c = texture(texID, coordinate).r;" "uint c = texture(texID, coordinate).r;"
"uint y = c & 14u;" "uint y = c & 14u;"
"uint iPhase = (c >> 4);" "uint iPhase = (c >> 4);"
"float phaseOffset = 6.283185308 * float(iPhase) / 13.0 + 5.074880441076923;" "float phaseOffset = 6.283185308 * float(iPhase) / 13.0 + 5.074880441076923;"
"return vec2(float(y) / 14.0, step(1, iPhase) * cos(phase + phaseOffset));" "return vec2(float(y) / 14.0, step(1, iPhase) * cos(phase - phaseOffset));"
"}"); "}");
display_type = Outputs::CRT::DisplayType::NTSC60; display_type = Outputs::CRT::DisplayType::NTSC60;
} else { } else {
crt_->set_svideo_sampling_function( crt_->set_svideo_sampling_function(
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)" "vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
"{" "{"
"uint c = texture(texID, coordinate).r;" "uint c = texture(texID, coordinate).r;"
"uint y = c & 14u;" "uint y = c & 14u;"

View File

@@ -304,7 +304,7 @@ class ConcreteMachine:
public Utility::TypeRecipient, public Utility::TypeRecipient,
public Storage::Tape::BinaryTapePlayer::Delegate, public Storage::Tape::BinaryTapePlayer::Delegate,
public Machine, public Machine,
public Sleeper::SleepObserver, public ClockingHint::Observer,
public Activity::Source { public Activity::Source {
public: public:
ConcreteMachine() : ConcreteMachine() :
@@ -331,7 +331,7 @@ class ConcreteMachine:
user_port_via_port_handler_->set_interrupt_delegate(this); user_port_via_port_handler_->set_interrupt_delegate(this);
keyboard_via_port_handler_->set_interrupt_delegate(this); keyboard_via_port_handler_->set_interrupt_delegate(this);
tape_->set_delegate(this); tape_->set_delegate(this);
tape_->set_sleep_observer(this); tape_->set_clocking_hint_observer(this);
// install a joystick // install a joystick
joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_)); joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_));
@@ -749,8 +749,8 @@ class ConcreteMachine:
return selection_set; return selection_set;
} }
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override { void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override {
tape_is_sleeping_ = is_sleeping; tape_is_sleeping_ = clocking == ClockingHint::Preference::None;
set_use_fast_tape(); set_use_fast_tape();
} }

View File

@@ -88,7 +88,7 @@ class ConcreteMachine:
public KeyboardMachine::Machine, public KeyboardMachine::Machine,
public Configurable::Device, public Configurable::Device,
public MemoryMap, public MemoryMap,
public Sleeper::SleepObserver, public ClockingHint::Observer,
public Activity::Source { public Activity::Source {
public: public:
ConcreteMachine(): ConcreteMachine():
@@ -108,7 +108,7 @@ class ConcreteMachine:
ay_.set_port_handler(&ay_port_handler_); ay_.set_port_handler(&ay_port_handler_);
speaker_.set_input_rate(3579545.0f / 2.0f); speaker_.set_input_rate(3579545.0f / 2.0f);
tape_player_.set_sleep_observer(this); tape_player_.set_clocking_hint_observer(this);
// Set the AY to 50% of available volume, the toggle to 10% and leave 40% for an SCC. // Set the AY to 50% of available volume, the toggle to 10% and leave 40% for an SCC.
mixer_.set_relative_volumes({0.5f, 0.1f, 0.4f}); mixer_.set_relative_volumes({0.5f, 0.1f, 0.4f});
@@ -555,8 +555,8 @@ class ConcreteMachine:
} }
// MARK: - Sleeper // MARK: - Sleeper
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override { void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override {
tape_player_is_sleeping_ = tape_player_.is_sleeping(); tape_player_is_sleeping_ = tape_player_.preferred_clocking() == ClockingHint::Preference::None;
set_use_fast_tape(); set_use_fast_tape();
} }

View File

@@ -28,7 +28,6 @@ class Microdisc: public WD::WD1770 {
bool get_interrupt_request_line(); bool get_interrupt_request_line();
void run_for(const Cycles cycles); void run_for(const Cycles cycles);
using WD::WD1770::run_for;
enum PagingFlags { enum PagingFlags {
/// Indicates that the BASIC ROM should be disabled; if this is set then either /// Indicates that the BASIC ROM should be disabled; if this is set then either
@@ -52,8 +51,9 @@ class Microdisc: public WD::WD1770 {
private: private:
void set_control_register(uint8_t control, uint8_t changes); void set_control_register(uint8_t control, uint8_t changes);
void set_head_load_request(bool head_load); void set_head_load_request(bool head_load) override;
bool get_drive_is_ready(); bool get_drive_is_ready();
std::array<std::shared_ptr<Storage::Disk::Drive>, 4> drives_; std::array<std::shared_ptr<Storage::Disk::Drive>, 4> drives_;
size_t selected_drive_; size_t selected_drive_;
bool irq_enable_ = false; bool irq_enable_ = false;

View File

@@ -201,7 +201,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
public Utility::TypeRecipient, public Utility::TypeRecipient,
public Storage::Tape::BinaryTapePlayer::Delegate, public Storage::Tape::BinaryTapePlayer::Delegate,
public Microdisc::Delegate, public Microdisc::Delegate,
public Sleeper::SleepObserver, public ClockingHint::Observer,
public Activity::Source, public Activity::Source,
public Machine { public Machine {
@@ -212,14 +212,15 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
ay8910_(audio_queue_), ay8910_(audio_queue_),
speaker_(ay8910_), speaker_(ay8910_),
via_port_handler_(audio_queue_, ay8910_, speaker_, tape_player_, keyboard_), via_port_handler_(audio_queue_, ay8910_, speaker_, tape_player_, keyboard_),
via_(via_port_handler_) { via_(via_port_handler_),
diskii_(2000000) {
set_clock_rate(1000000); set_clock_rate(1000000);
via_port_handler_.set_interrupt_delegate(this); via_port_handler_.set_interrupt_delegate(this);
tape_player_.set_delegate(this); tape_player_.set_delegate(this);
Memory::Fuzz(ram_, sizeof(ram_)); Memory::Fuzz(ram_, sizeof(ram_));
if(disk_interface == Analyser::Static::Oric::Target::DiskInterface::Pravetz) { if(disk_interface == Analyser::Static::Oric::Target::DiskInterface::Pravetz) {
diskii_.set_sleep_observer(this); diskii_.set_clocking_hint_observer(this);
} }
} }
@@ -410,6 +411,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
} }
} }
} else { } else {
flush_diskii();
const int disk_value = diskii_.read_address(address); const int disk_value = diskii_.read_address(address);
if(isReadOperation(operation) && disk_value != diskii_.DidNotLoad) *value = static_cast<uint8_t>(disk_value); if(isReadOperation(operation) && disk_value != diskii_.DidNotLoad) *value = static_cast<uint8_t>(disk_value);
} }
@@ -444,9 +446,11 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
microdisc_.run_for(Cycles(8)); microdisc_.run_for(Cycles(8));
break; break;
case Analyser::Static::Oric::Target::DiskInterface::Pravetz: case Analyser::Static::Oric::Target::DiskInterface::Pravetz:
if(!diskii_is_sleeping_) { if(diskii_clocking_preference_ == ClockingHint::Preference::RealTime) {
diskii_.set_data_input(*value); diskii_.set_data_input(*value);
diskii_.run_for(Cycles(2)); diskii_.run_for(Cycles(2));
} else {
cycles_since_diskii_update_ += Cycles(2);
} }
break; break;
} }
@@ -457,6 +461,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
forceinline void flush() { forceinline void flush() {
update_video(); update_video();
via_port_handler_.flush(); via_port_handler_.flush();
flush_diskii();
} }
// to satisfy CRTMachine::Machine // to satisfy CRTMachine::Machine
@@ -571,8 +576,8 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
} }
} }
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override final { void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override final {
diskii_is_sleeping_ = diskii_.is_sleeping(); diskii_clocking_preference_ = diskii_.preferred_clocking();
} }
private: private:
@@ -617,9 +622,13 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
// the Pravetz/Disk II, if in use // the Pravetz/Disk II, if in use
Apple::DiskII diskii_; Apple::DiskII diskii_;
Cycles cycles_since_diskii_update_;
void flush_diskii() {
diskii_.run_for(cycles_since_diskii_update_.flush());
}
std::vector<uint8_t> pravetz_rom_; std::vector<uint8_t> pravetz_rom_;
std::size_t pravetz_rom_base_pointer_ = 0; std::size_t pravetz_rom_base_pointer_ = 0;
bool diskii_is_sleeping_ = false; ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime;
// Overlay RAM // Overlay RAM
uint16_t ram_top_ = basic_visible_ram_top_; uint16_t ram_top_ = basic_visible_ram_top_;

View File

@@ -16,25 +16,20 @@ namespace {
The number of bytes of PCM data to allocate at once; if/when more are required, The number of bytes of PCM data to allocate at once; if/when more are required,
the class will simply allocate another batch. the class will simply allocate another batch.
*/ */
const std::size_t StandardAllocationSize = 40; const std::size_t StandardAllocationSize = 320;
/// The amount of time a byte takes to output.
const std::size_t HalfCyclesPerByte = 8;
} }
Video::Video() : Video::Video() :
crt_(new Outputs::CRT::CRT(207 * 2, 1, Outputs::CRT::DisplayType::PAL50, 1)) { crt_(new Outputs::CRT::CRT(207 * 2, 1, Outputs::CRT::DisplayType::PAL50, 1)) {
// Set a composite sampling function that assumes 1bpp input. // Set a composite sampling function that assumes two-level input; either a byte is 0, which is black,
// or it is non-zero, which is white.
crt_->set_composite_sampling_function( crt_->set_composite_sampling_function(
"float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)" "float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)"
"{" "{"
"uint texValue = texture(sampler, coordinate).r;" "return texture(sampler, coordinate).r;"
"texValue <<= int(icoordinate.x) & 7;"
"return float(texValue & 128u);"
"}"); "}");
crt_->set_integer_coordinate_multiplier(8.0f);
// Show only the centre 80% of the TV frame. // Show only the centre 80% of the TV frame.
crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite); crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite);
@@ -43,7 +38,7 @@ Video::Video() :
void Video::run_for(const HalfCycles half_cycles) { void Video::run_for(const HalfCycles half_cycles) {
// Just keep a running total of the amount of time that remains owed to the CRT. // Just keep a running total of the amount of time that remains owed to the CRT.
cycles_since_update_ += static_cast<unsigned int>(half_cycles.as_int()); time_since_update_ += half_cycles;
} }
void Video::flush() { void Video::flush() {
@@ -53,29 +48,29 @@ void Video::flush() {
void Video::flush(bool next_sync) { void Video::flush(bool next_sync) {
if(sync_) { if(sync_) {
// If in sync, that takes priority. Output the proper amount of sync. // If in sync, that takes priority. Output the proper amount of sync.
crt_->output_sync(cycles_since_update_); crt_->output_sync(static_cast<unsigned int>(time_since_update_.as_int()));
} else { } else {
// If not presently in sync, then... // If not presently in sync, then...
if(line_data_) { if(line_data_) {
// If there is output data queued, output it either if it's being interrupted by // If there is output data queued, output it either if it's being interrupted by
// sync, or if we're past its end anyway. Otherwise let it be. // sync, or if we're past its end anyway. Otherwise let it be.
unsigned int data_length = static_cast<unsigned int>(line_data_pointer_ - line_data_) * HalfCyclesPerByte; int data_length = static_cast<int>(line_data_pointer_ - line_data_);
if(data_length < cycles_since_update_ || next_sync) { if(data_length < time_since_update_.as_int() || next_sync) {
unsigned int output_length = std::min(data_length, cycles_since_update_); auto output_length = std::min(data_length, time_since_update_.as_int());
crt_->output_data(output_length, output_length / HalfCyclesPerByte); crt_->output_data(static_cast<unsigned int>(output_length), static_cast<unsigned int>(output_length));
line_data_pointer_ = line_data_ = nullptr; line_data_pointer_ = line_data_ = nullptr;
cycles_since_update_ -= output_length; time_since_update_ -= HalfCycles(output_length);
} else return; } else return;
} }
// Any pending pixels being dealt with, pad with the white level. // Any pending pixels being dealt with, pad with the white level.
uint8_t *colour_pointer = static_cast<uint8_t *>(crt_->allocate_write_area(1)); uint8_t *colour_pointer = static_cast<uint8_t *>(crt_->allocate_write_area(1));
if(colour_pointer) *colour_pointer = 0xff; if(colour_pointer) *colour_pointer = 0xff;
crt_->output_level(cycles_since_update_); crt_->output_level(static_cast<unsigned int>(time_since_update_.as_int()));
} }
cycles_since_update_ = 0; time_since_update_ = 0;
} }
void Video::set_sync(bool sync) { void Video::set_sync(bool sync) {
@@ -101,14 +96,19 @@ void Video::output_byte(uint8_t byte) {
if(line_data_) { if(line_data_) {
// If the buffer is full, output it now and obtain a new one // If the buffer is full, output it now and obtain a new one
if(line_data_pointer_ - line_data_ == StandardAllocationSize) { if(line_data_pointer_ - line_data_ == StandardAllocationSize) {
crt_->output_data(StandardAllocationSize * HalfCyclesPerByte, StandardAllocationSize); crt_->output_data(StandardAllocationSize, StandardAllocationSize);
cycles_since_update_ -= StandardAllocationSize * HalfCyclesPerByte; time_since_update_ -= StandardAllocationSize;
line_data_pointer_ = line_data_ = crt_->allocate_write_area(StandardAllocationSize); line_data_pointer_ = line_data_ = crt_->allocate_write_area(StandardAllocationSize);
if(!line_data_) return; if(!line_data_) return;
} }
line_data_pointer_[0] = byte; // Convert to one-byte-per-pixel where any non-zero value will act as white.
line_data_pointer_ ++; uint8_t mask = 0x80;
for(int c = 0; c < 8; c++) {
line_data_pointer_[c] = byte & mask;
mask >>= 1;
}
line_data_pointer_ += 8;
} }
} }

View File

@@ -45,7 +45,7 @@ class Video {
bool sync_ = false; bool sync_ = false;
uint8_t *line_data_ = nullptr; uint8_t *line_data_ = nullptr;
uint8_t *line_data_pointer_ = nullptr; uint8_t *line_data_pointer_ = nullptr;
unsigned int cycles_since_update_ = 0; HalfCycles time_since_update_ = 0;
std::unique_ptr<Outputs::CRT::CRT> crt_; std::unique_ptr<Outputs::CRT::CRT> crt_;
void flush(bool next_sync); void flush(bool next_sync);

View File

@@ -157,7 +157,7 @@ template<bool is_zx81> class ConcreteMachine:
// The below emulates the ZonX AY expansion device. // The below emulates the ZonX AY expansion device.
if(is_zx81) { if(is_zx81) {
if((address&0xef) == 0x0f) { if((address&0xef) == 0xcf) {
value &= ay_read_data(); value &= ay_read_data();
} }
} }

View File

@@ -1037,7 +1037,7 @@
4BB06B211F316A3F00600C7A /* ForceInline.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ForceInline.hpp; sourceTree = "<group>"; }; 4BB06B211F316A3F00600C7A /* ForceInline.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ForceInline.hpp; sourceTree = "<group>"; };
4BB0A6592044FD3000FB3688 /* SN76489.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SN76489.cpp; sourceTree = "<group>"; }; 4BB0A6592044FD3000FB3688 /* SN76489.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SN76489.cpp; sourceTree = "<group>"; };
4BB0A65A2044FD3000FB3688 /* SN76489.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SN76489.hpp; sourceTree = "<group>"; }; 4BB0A65A2044FD3000FB3688 /* SN76489.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SN76489.hpp; sourceTree = "<group>"; };
4BB146C61F49D7D700253439 /* Sleeper.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sleeper.hpp; sourceTree = "<group>"; }; 4BB146C61F49D7D700253439 /* ClockingHintSource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ClockingHintSource.hpp; sourceTree = "<group>"; };
4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.expected.json; path = FUSE/tests.expected.json; sourceTree = "<group>"; }; 4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.expected.json; path = FUSE/tests.expected.json; sourceTree = "<group>"; };
4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.in.json; path = FUSE/tests.in.json; sourceTree = "<group>"; }; 4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.in.json; path = FUSE/tests.in.json; sourceTree = "<group>"; };
4BB297E51B587D8300A49093 /* start */ = {isa = PBXFileReference; lastKnownFileType = file; path = " start"; sourceTree = "<group>"; }; 4BB297E51B587D8300A49093 /* start */ = {isa = PBXFileReference; lastKnownFileType = file; path = " start"; sourceTree = "<group>"; };
@@ -3124,7 +3124,7 @@
children = ( children = (
4BF6606A1F281573002CB053 /* ClockReceiver.hpp */, 4BF6606A1F281573002CB053 /* ClockReceiver.hpp */,
4BB06B211F316A3F00600C7A /* ForceInline.hpp */, 4BB06B211F316A3F00600C7A /* ForceInline.hpp */,
4BB146C61F49D7D700253439 /* Sleeper.hpp */, 4BB146C61F49D7D700253439 /* ClockingHintSource.hpp */,
4B449C942063389900A095C8 /* TimeTypes.hpp */, 4B449C942063389900A095C8 /* TimeTypes.hpp */,
); );
name = ClockReceiver; name = ClockReceiver;

View File

@@ -170,7 +170,9 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo
// outside of the locked region // outside of the locked region
source_output_position_x1() = static_cast<uint16_t>(horizontal_flywheel_->get_current_output_position()); source_output_position_x1() = static_cast<uint16_t>(horizontal_flywheel_->get_current_output_position());
source_phase() = colour_burst_phase_; source_phase() = colour_burst_phase_;
source_amplitude() = colour_burst_amplitude_;
// TODO: determine what the PAL phase-shift machines actually do re: the swinging burst.
source_amplitude() = phase_alternates_ ? 128 - colour_burst_amplitude_ : 128 + colour_burst_amplitude_;
} }
// decrement the number of cycles left to run for and increment the // decrement the number of cycles left to run for and increment the
@@ -368,7 +370,7 @@ void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint
scan.type = Scan::Type::ColourBurst; scan.type = Scan::Type::ColourBurst;
scan.number_of_cycles = number_of_cycles; scan.number_of_cycles = number_of_cycles;
scan.phase = phase; scan.phase = phase;
scan.amplitude = amplitude; scan.amplitude = amplitude >> 1;
output_scan(&scan); output_scan(&scan);
} }

View File

@@ -332,10 +332,10 @@ class CRT {
output mode will be applied. output mode will be applied.
@param shader A GLSL fragment including a function with the signature @param shader A GLSL fragment including a function with the signature
`vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)` `vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)`
that evaluates to the s-video signal level, luminance as the first component and chrominance that evaluates to the s-video signal level, luminance as the first component and chrominance
as the second, as a function of a source buffer, sampling location and colour as the second, as a function of a source buffer, sampling location and colour
carrier phase. carrier phase; amplitude is supplied for its sign.
*/ */
inline void set_svideo_sampling_function(const std::string &shader) { inline void set_svideo_sampling_function(const std::string &shader) {
enqueue_openGL_function([shader, this] { enqueue_openGL_function([shader, this] {

View File

@@ -96,10 +96,10 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const std::s
// setup phaseAndAmplitudeVarying.x as colour burst subcarrier phase, in radians; // setup phaseAndAmplitudeVarying.x as colour burst subcarrier phase, in radians;
// setup phaseAndAmplitudeVarying.y as colour burst amplitude; // setup phaseAndAmplitudeVarying.y as colour burst amplitude;
// setup phaseAndAmplitudeVarying.z as 1 / (colour burst amplitude), or 0.0 if amplitude is 0.0; // setup phaseAndAmplitudeVarying.z as 1 / abs(colour burst amplitude), or 0.0 if amplitude is 0.0;
"phaseAndAmplitudeVarying.x = (extendedOutputPosition.x + (phaseTimeAndAmplitude.x / 64.0)) * 0.5 * 3.141592654;" "phaseAndAmplitudeVarying.x = (extendedOutputPosition.x + (phaseTimeAndAmplitude.x / 64.0)) * 0.5 * 3.141592654;"
"phaseAndAmplitudeVarying.y = phaseTimeAndAmplitude.y / 255.0;" "phaseAndAmplitudeVarying.y = (phaseTimeAndAmplitude.y - 128) / 127.0;"
"phaseAndAmplitudeVarying.z = (phaseAndAmplitudeVarying.y > 0.0) ? 1.0 / phaseAndAmplitudeVarying.y : 0.0;" "phaseAndAmplitudeVarying.z = (abs(phaseAndAmplitudeVarying.y) > 0.05) ? 1.0 / abs(phaseAndAmplitudeVarying.y) : 0.0;"
// determine output position by scaling the output position according to the texture size // determine output position by scaling the output position according to the texture size
"vec2 eyePosition = 2.0*(extendedOutputPosition / outputTextureSize) - vec2(1.0);" "vec2 eyePosition = 2.0*(extendedOutputPosition / outputTextureSize) - vec2(1.0);"
@@ -134,8 +134,8 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_composite_source_sh
svideo_shader << svideo_shader <<
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" "float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
"{" "{"
"vec2 svideoColour = svideo_sample(texID, coordinate, iCoordinate, phase);" "vec2 svideoColour = svideo_sample(texID, coordinate, iCoordinate, phase, amplitude);"
"return mix(svideoColour.x, svideoColour.y, amplitude);" "return mix(svideoColour.x, svideoColour.y, abs(amplitude));"
"}"; "}";
} else { } else {
fragment_shader << fragment_shader <<
@@ -145,7 +145,7 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_composite_source_sh
"{" "{"
"vec3 rgbColour = clamp(rgb_sample(texID, coordinate, iCoordinate), vec3(0.0), vec3(1.0));" "vec3 rgbColour = clamp(rgb_sample(texID, coordinate, iCoordinate), vec3(0.0), vec3(1.0));"
"vec3 lumaChromaColour = rgbToLumaChroma * rgbColour;" "vec3 lumaChromaColour = rgbToLumaChroma * rgbColour;"
"vec2 quadrature = vec2(cos(phase), -sin(phase)) * amplitude;" "vec2 quadrature = vec2(cos(phase), sin(phase)) * vec2(abs(amplitude), amplitude);"
"return dot(lumaChromaColour, vec3(1.0 - amplitude, quadrature));" "return dot(lumaChromaColour, vec3(1.0 - amplitude, quadrature));"
"}"; "}";
} }
@@ -178,11 +178,11 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_svideo_source_shade
fragment_shader fragment_shader
<< rgb_shader << << rgb_shader <<
"uniform mat3 rgbToLumaChroma;" "uniform mat3 rgbToLumaChroma;"
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)" "vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
"{" "{"
"vec3 rgbColour = clamp(rgb_sample(texID, coordinate, iCoordinate), vec3(0.0), vec3(1.0));" "vec3 rgbColour = clamp(rgb_sample(texID, coordinate, iCoordinate), vec3(0.0), vec3(1.0));"
"vec3 lumaChromaColour = rgbToLumaChroma * rgbColour;" "vec3 lumaChromaColour = rgbToLumaChroma * rgbColour;"
"vec2 quadrature = vec2(cos(phase), -sin(phase));" "vec2 quadrature = vec2(cos(phase), sin(phase)) * vec2(1.0, sign(amplitude));"
"return vec2(lumaChromaColour.x, 0.5 + dot(quadrature, lumaChromaColour.yz) * 0.5);" "return vec2(lumaChromaColour.x, 0.5 + dot(quadrature, lumaChromaColour.yz) * 0.5);"
"}"; "}";
} }
@@ -190,8 +190,8 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_svideo_source_shade
fragment_shader << fragment_shader <<
"void main(void)" "void main(void)"
"{" "{"
"vec2 sample = svideo_sample(texID, inputPositionsVarying[5], iInputPositionVarying, phaseAndAmplitudeVarying.x);" "vec2 sample = svideo_sample(texID, inputPositionsVarying[5], iInputPositionVarying, phaseAndAmplitudeVarying.x, phaseAndAmplitudeVarying.y);"
"vec2 quadrature = vec2(cos(phaseAndAmplitudeVarying.x), -sin(phaseAndAmplitudeVarying.x)) * 0.5 * phaseAndAmplitudeVarying.z;" "vec2 quadrature = vec2(cos(phaseAndAmplitudeVarying.x), sin(phaseAndAmplitudeVarying.x)) * vec2(1.0, sign(phaseAndAmplitudeVarying.y)) * 0.5 * phaseAndAmplitudeVarying.z;"
"fragColour = vec3(sample.x, vec2(0.5) + (sample.y * quadrature));" "fragColour = vec3(sample.x, vec2(0.5) + (sample.y * quadrature));"
"}"; "}";
@@ -244,11 +244,11 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_luma_separat
// define chroma to be whatever was here, minus luma // define chroma to be whatever was here, minus luma
"float chrominance = 0.5 * (samples.z - luminance) * phaseAndAmplitudeVarying.z;" "float chrominance = 0.5 * (samples.z - luminance) * phaseAndAmplitudeVarying.z;"
"luminance /= (1.0 - phaseAndAmplitudeVarying.y);" "luminance /= (1.0 - abs(phaseAndAmplitudeVarying.y));"
// split choma colours here, as the most direct place, writing out // split choma colours here, as the most direct place, writing out
// RGB = (luma, chroma.x, chroma.y) // RGB = (luma, chroma.x, chroma.y)
"vec2 quadrature = vec2(cos(phaseAndAmplitudeVarying.x), -sin(phaseAndAmplitudeVarying.x));" "vec2 quadrature = vec2(cos(phaseAndAmplitudeVarying.x), sin(phaseAndAmplitudeVarying.x)) * vec2(1.0, sign(phaseAndAmplitudeVarying.y));"
"fragColour = vec3(luminance, vec2(0.5) + (chrominance * quadrature));" "fragColour = vec3(luminance, vec2(0.5) + (chrominance * quadrature));"
"}",false, false); "}",false, false);
} }

View File

@@ -64,7 +64,7 @@ static std::shared_ptr<File> ZX81FileFromData(const std::vector<uint8_t> &data)
// if(data[data_pointer]) return nullptr; // if(data[data_pointer]) return nullptr;
uint16_t vars = short_at(data_pointer + 0x4010 - 0x4009, data); // uint16_t vars = short_at(data_pointer + 0x4010 - 0x4009, data);
uint16_t end_of_file = short_at(data_pointer + 0x4014 - 0x4009, data); uint16_t end_of_file = short_at(data_pointer + 0x4014 - 0x4009, data);
// uint16_t display_address = short_at(0x400c - 0x4009, data); // uint16_t display_address = short_at(0x400c - 0x4009, data);
@@ -72,7 +72,7 @@ static std::shared_ptr<File> ZX81FileFromData(const std::vector<uint8_t> &data)
if(data_pointer + end_of_file - 0x4009 > data.size()) return nullptr; if(data_pointer + end_of_file - 0x4009 > data.size()) return nullptr;
// check for the proper ordering of buffers // check for the proper ordering of buffers
if(vars > end_of_file) return nullptr; // if(vars > end_of_file) return nullptr;
// if(end_of_file > display_address) return nullptr; // if(end_of_file > display_address) return nullptr;
// TODO: does it make sense to inspect the tokenised BASIC? // TODO: does it make sense to inspect the tokenised BASIC?

View File

@@ -22,12 +22,12 @@ Controller::Controller(Cycles clock_rate) :
set_drive(empty_drive_); set_drive(empty_drive_);
} }
void Controller::set_component_is_sleeping(Sleeper *component, bool is_sleeping) { void Controller::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) {
update_sleep_observer(); update_clocking_observer();
} }
bool Controller::is_sleeping() { ClockingHint::Preference Controller::preferred_clocking() {
return !drive_ || drive_->is_sleeping(); return (!drive_ || (drive_->preferred_clocking() == ClockingHint::Preference::None)) ? ClockingHint::Preference::None : ClockingHint::Preference::RealTime;
} }
void Controller::run_for(const Cycles cycles) { void Controller::run_for(const Cycles cycles) {
@@ -77,23 +77,23 @@ void Controller::digital_phase_locked_loop_output_bit(int value) {
void Controller::set_drive(std::shared_ptr<Drive> drive) { void Controller::set_drive(std::shared_ptr<Drive> drive) {
if(drive_ != drive) { if(drive_ != drive) {
bool was_sleeping = is_sleeping(); ClockingHint::Preference former_prefernece = preferred_clocking();
// invalidate_track(); // invalidate_track();
if(drive_) { if(drive_) {
drive_->set_event_delegate(nullptr); drive_->set_event_delegate(nullptr);
drive_->set_sleep_observer(nullptr); drive_->set_clocking_hint_observer(nullptr);
} }
drive_ = drive; drive_ = drive;
if(drive_) { if(drive_) {
drive_->set_event_delegate(this); drive_->set_event_delegate(this);
drive_->set_sleep_observer(this); drive_->set_clocking_hint_observer(this);
} else { } else {
drive_ = empty_drive_; drive_ = empty_drive_;
} }
if(is_sleeping() != was_sleeping) { if(preferred_clocking() != former_prefernece) {
update_sleep_observer(); update_clocking_observer();
} }
} }
} }

View File

@@ -15,7 +15,7 @@
#include "../Track/PCMPatchedTrack.hpp" #include "../Track/PCMPatchedTrack.hpp"
#include "../../../ClockReceiver/ClockReceiver.hpp" #include "../../../ClockReceiver/ClockReceiver.hpp"
#include "../../../ClockReceiver/Sleeper.hpp" #include "../../../ClockReceiver/ClockingHintSource.hpp"
namespace Storage { namespace Storage {
namespace Disk { namespace Disk {
@@ -29,7 +29,11 @@ namespace Disk {
TODO: communication of head size and permissible stepping extents, appropriate simulation of gain. TODO: communication of head size and permissible stepping extents, appropriate simulation of gain.
*/ */
class Controller: public DigitalPhaseLockedLoop::Delegate, public Drive::EventDelegate, public Sleeper, public Sleeper::SleepObserver { class Controller:
public DigitalPhaseLockedLoop::Delegate,
public Drive::EventDelegate,
public ClockingHint::Source,
public ClockingHint::Observer {
protected: protected:
/*! /*!
Constructs a @c Controller that will be run at @c clock_rate. Constructs a @c Controller that will be run at @c clock_rate.
@@ -65,7 +69,7 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public Drive::EventDe
Should be implemented by subclasses if they implement writing; communicates that Should be implemented by subclasses if they implement writing; communicates that
all bits supplied to write_bit have now been written. all bits supplied to write_bit have now been written.
*/ */
virtual void process_write_completed(); virtual void process_write_completed() override;
/*! /*!
Puts the controller and the drive returned by get_drive() into write mode, supplying to Puts the controller and the drive returned by get_drive() into write mode, supplying to
@@ -97,9 +101,9 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public Drive::EventDe
Drive &get_drive(); Drive &get_drive();
/*! /*!
As per Sleeper. As per ClockingHint::Source.
*/ */
bool is_sleeping(); ClockingHint::Preference preferred_clocking() override;
private: private:
Time bit_length_; Time bit_length_;
@@ -113,14 +117,15 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public Drive::EventDe
std::shared_ptr<Drive> empty_drive_; std::shared_ptr<Drive> empty_drive_;
void set_component_is_sleeping(Sleeper *component, bool is_sleeping); // ClockingHint::Observer.
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override;
// for Drive::EventDelegate // for Drive::EventDelegate
void process_event(const Track::Event &event); void process_event(const Track::Event &event) override;
void advance(const Cycles cycles); void advance(const Cycles cycles) override ;
// 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) override;
}; };
} }

View File

@@ -31,7 +31,7 @@ class AppleDSK: public DiskImage {
*/ */
AppleDSK(const std::string &file_name); AppleDSK(const std::string &file_name);
// Implemented to satisfy @c Disk. // Implemented to satisfy @c DiskImage.
HeadPosition get_maximum_head_position() override; HeadPosition get_maximum_head_position() override;
std::shared_ptr<Track> get_track_at_position(Track::Address address) override; std::shared_ptr<Track> get_track_at_position(Track::Address address) override;
void set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) override; void set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) override;

View File

@@ -9,6 +9,7 @@
#include "NIB.hpp" #include "NIB.hpp"
#include "../../Track/PCMTrack.hpp" #include "../../Track/PCMTrack.hpp"
#include "../../Track/TrackSerialiser.hpp"
#include "../../Encodings/AppleGCR/Encoder.hpp" #include "../../Encodings/AppleGCR/Encoder.hpp"
#include <vector> #include <vector>
@@ -29,20 +30,38 @@ NIB::NIB(const std::string &file_name) :
throw Error::InvalidFormat; throw Error::InvalidFormat;
} }
// TODO: all other validation. I.e. does this look like a GCR disk? // A real NIB should have every single top bit set. Yes, 1/8th of the
// file size is a complete waste. But it provides a hook for validation.
while(true) {
uint8_t next = file_.get8();
if(file_.eof()) break;
if(!(next & 0x80)) throw Error::InvalidFormat;
}
} }
HeadPosition NIB::get_maximum_head_position() { HeadPosition NIB::get_maximum_head_position() {
return HeadPosition(number_of_tracks); return HeadPosition(number_of_tracks);
} }
bool NIB::get_is_read_only() {
return file_.get_is_known_read_only();
}
long NIB::file_offset(Track::Address address) {
return static_cast<long>(address.position.as_int()) * track_length;
}
std::shared_ptr<::Storage::Disk::Track> NIB::get_track_at_position(::Storage::Disk::Track::Address address) { std::shared_ptr<::Storage::Disk::Track> NIB::get_track_at_position(::Storage::Disk::Track::Address address) {
// NIBs contain data for even-numbered tracks underneath a single head only. // NIBs contain data for even-numbered tracks underneath a single head only.
if(address.head) return nullptr; if(address.head) return nullptr;
const long file_track = static_cast<long>(address.position.as_int()); long offset = file_offset(address);
file_.seek(file_track * track_length, SEEK_SET); std::vector<uint8_t> track_data;
std::vector<uint8_t> track_data = file_.read(track_length); {
std::lock_guard<std::mutex> lock_guard(file_.get_file_access_mutex());
file_.seek(offset, SEEK_SET);
track_data = file_.read(track_length);
}
// NIB files leave sync bytes implicit and make no guarantees // NIB files leave sync bytes implicit and make no guarantees
// about overall track positioning. So the approach taken here // about overall track positioning. So the approach taken here
@@ -100,3 +119,44 @@ std::shared_ptr<::Storage::Disk::Track> NIB::get_track_at_position(::Storage::Di
return std::make_shared<PCMTrack>(segment); return std::make_shared<PCMTrack>(segment);
} }
void NIB::set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) {
std::map<Track::Address, std::vector<uint8_t>> tracks_by_address;
// Convert to a map from address to a vector of data that contains the NIB representation
// of the track.
for(const auto &pair: tracks) {
// Grab the track bit stream.
auto segment = Storage::Disk::track_serialisation(*pair.second, Storage::Time(1, 50000));
// Process to eliminate all sync bits.
std::vector<uint8_t> track;
track.reserve(track_length);
uint8_t shifter = 0;
for(unsigned int bit = 0; bit < segment.number_of_bits; ++bit) {
shifter = static_cast<uint8_t>((shifter << 1) | segment.bit(bit));
if(shifter & 0x80) {
track.push_back(shifter);
shifter = 0;
}
}
// Pad out to track_length.
if(track.size() > track_length) {
track.resize(track_length);
} else {
while(track.size() < track_length) {
track.push_back(0xff);
}
}
tracks_by_address[pair.first] = std::move(track);
}
// Lock the file and spool out.
std::lock_guard<std::mutex> lock_guard(file_.get_file_access_mutex());
for(const auto &track: tracks_by_address) {
file_.seek(file_offset(track.first), SEEK_SET);
file_.write(track.second);
}
}

View File

@@ -17,21 +17,23 @@ namespace Disk {
/*! /*!
Provides a @c DiskImage describing an Apple NIB disk image: Provides a @c DiskImage describing an Apple NIB disk image:
mostly a bit stream capture, but syncs are implicitly packed a bit stream capture that omits sync zeroes, and doesn't define
into 8 bits instead of 9. the means for full reconstruction.
*/ */
class NIB: public DiskImage { class NIB: public DiskImage {
public: public:
NIB(const std::string &file_name); NIB(const std::string &file_name);
// Implemented to satisfy @c DiskImage.
HeadPosition get_maximum_head_position() override; HeadPosition get_maximum_head_position() override;
std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) override; std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) override;
void set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) override;
bool get_is_read_only() override;
private: private:
FileHolder file_; FileHolder file_;
long get_file_offset_for_position(Track::Address address); long get_file_offset_for_position(Track::Address address);
long file_offset(Track::Address address);
}; };
} }

View File

@@ -25,7 +25,7 @@ class WOZ: public DiskImage {
public: public:
WOZ(const std::string &file_name); WOZ(const std::string &file_name);
// Implemented to satisfy @c Disk. // Implemented to satisfy @c DiskImage.
HeadPosition get_maximum_head_position() override; HeadPosition get_maximum_head_position() override;
int get_head_count() override; int get_head_count() override;
std::shared_ptr<Track> get_track_at_position(Track::Address address) override; std::shared_ptr<Track> get_track_at_position(Track::Address address) override;

View File

@@ -46,15 +46,15 @@ void Drive::set_disk(const std::shared_ptr<Disk> &disk) {
has_disk_ = !!disk_; has_disk_ = !!disk_;
invalidate_track(); invalidate_track();
update_sleep_observer(); update_clocking_observer();
} }
bool Drive::has_disk() { bool Drive::has_disk() {
return has_disk_; return has_disk_;
} }
bool Drive::is_sleeping() { ClockingHint::Preference Drive::preferred_clocking() {
return !motor_is_on_ || !has_disk_; return (!motor_is_on_ || !has_disk_) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
} }
bool Drive::get_is_track_zero() { bool Drive::get_is_track_zero() {
@@ -119,7 +119,7 @@ void Drive::set_motor_on(bool motor_is_on) {
ready_index_count_ = 0; ready_index_count_ = 0;
if(disk_) disk_->flush_tracks(); if(disk_) disk_->flush_tracks();
} }
update_sleep_observer(); update_clocking_observer();
} }
bool Drive::get_motor_on() { bool Drive::get_motor_on() {

View File

@@ -15,14 +15,14 @@
#include "../TimedEventLoop.hpp" #include "../TimedEventLoop.hpp"
#include "../../Activity/Observer.hpp" #include "../../Activity/Observer.hpp"
#include "../../ClockReceiver/Sleeper.hpp" #include "../../ClockReceiver/ClockingHintSource.hpp"
#include <memory> #include <memory>
namespace Storage { namespace Storage {
namespace Disk { namespace Disk {
class Drive: public Sleeper, public TimedEventLoop { class Drive: public ClockingHint::Source, public TimedEventLoop {
public: public:
Drive(unsigned int input_clock_rate, int revolutions_per_minute, int number_of_heads); Drive(unsigned int input_clock_rate, int revolutions_per_minute, int number_of_heads);
~Drive(); ~Drive();
@@ -121,7 +121,7 @@ class Drive: public Sleeper, public TimedEventLoop {
void set_event_delegate(EventDelegate *); void set_event_delegate(EventDelegate *);
// As per Sleeper. // As per Sleeper.
bool is_sleeping(); ClockingHint::Preference preferred_clocking() override;
/// Adds an activity observer; it'll be notified of disk activity. /// Adds an activity observer; it'll be notified of disk activity.
/// The caller can specify whether to add an LED based on disk motor. /// The caller can specify whether to add an LED based on disk motor.
@@ -171,9 +171,9 @@ class Drive: public Sleeper, public TimedEventLoop {
Time cycles_per_bit_; Time cycles_per_bit_;
// TimedEventLoop call-ins and state. // TimedEventLoop call-ins and state.
void process_next_event(); void process_next_event() override;
void get_next_event(const Time &duration_already_passed); void get_next_event(const Time &duration_already_passed);
void advance(const Cycles cycles); void advance(const Cycles cycles) override;
Track::Event current_event_; Track::Event current_event_;
// Helper for track changes. // Helper for track changes.

View File

@@ -65,15 +65,15 @@ void Tape::set_offset(uint64_t offset) {
// MARK: - Player // MARK: - Player
bool TapePlayer::is_sleeping() { ClockingHint::Preference TapePlayer::preferred_clocking() {
return !tape_ || tape_->is_at_end(); return (!tape_ || tape_->is_at_end()) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
} }
void TapePlayer::set_tape(std::shared_ptr<Storage::Tape::Tape> tape) { void TapePlayer::set_tape(std::shared_ptr<Storage::Tape::Tape> tape) {
tape_ = tape; tape_ = tape;
reset_timer(); reset_timer();
get_next_pulse(); get_next_pulse();
update_sleep_observer(); update_clocking_observer();
} }
std::shared_ptr<Storage::Tape::Tape> TapePlayer::get_tape() { std::shared_ptr<Storage::Tape::Tape> TapePlayer::get_tape() {
@@ -88,7 +88,7 @@ void TapePlayer::get_next_pulse() {
// get the new pulse // get the new pulse
if(tape_) { if(tape_) {
current_pulse_ = tape_->get_next_pulse(); current_pulse_ = tape_->get_next_pulse();
if(tape_->is_at_end()) update_sleep_observer(); if(tape_->is_at_end()) update_clocking_observer();
} else { } else {
current_pulse_.length.length = 1; current_pulse_.length.length = 1;
current_pulse_.length.clock_rate = 1; current_pulse_.length.clock_rate = 1;
@@ -119,14 +119,15 @@ BinaryTapePlayer::BinaryTapePlayer(unsigned int input_clock_rate) :
TapePlayer(input_clock_rate) TapePlayer(input_clock_rate)
{} {}
bool BinaryTapePlayer::is_sleeping() { ClockingHint::Preference BinaryTapePlayer::preferred_clocking() {
return !motor_is_running_ || TapePlayer::is_sleeping(); if(!motor_is_running_) return ClockingHint::Preference::None;
return TapePlayer::preferred_clocking();
} }
void BinaryTapePlayer::set_motor_control(bool enabled) { void BinaryTapePlayer::set_motor_control(bool enabled) {
if(motor_is_running_ != enabled) { if(motor_is_running_ != enabled) {
motor_is_running_ = enabled; motor_is_running_ = enabled;
update_sleep_observer(); update_clocking_observer();
} }
} }

View File

@@ -12,7 +12,7 @@
#include <memory> #include <memory>
#include "../../ClockReceiver/ClockReceiver.hpp" #include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/Sleeper.hpp" #include "../../ClockReceiver/ClockingHintSource.hpp"
#include "../TimedEventLoop.hpp" #include "../TimedEventLoop.hpp"
@@ -95,7 +95,7 @@ class Tape {
Will call @c process_input_pulse instantaneously upon reaching *the end* of a pulse. Therefore a subclass Will call @c process_input_pulse instantaneously upon reaching *the end* of a pulse. Therefore a subclass
can decode pulses into data within process_input_pulse, using the supplied pulse's @c length and @c type. can decode pulses into data within process_input_pulse, using the supplied pulse's @c length and @c type.
*/ */
class TapePlayer: public TimedEventLoop, public Sleeper { class TapePlayer: public TimedEventLoop, public ClockingHint::Source {
public: public:
TapePlayer(unsigned int input_clock_rate); TapePlayer(unsigned int input_clock_rate);
@@ -107,10 +107,10 @@ class TapePlayer: public TimedEventLoop, public Sleeper {
void run_for_input_pulse(); void run_for_input_pulse();
bool is_sleeping(); ClockingHint::Preference preferred_clocking() override;
protected: protected:
virtual void process_next_event(); virtual void process_next_event() override;
virtual void process_input_pulse(const Tape::Pulse &pulse) = 0; virtual void process_input_pulse(const Tape::Pulse &pulse) = 0;
private: private:
@@ -145,11 +145,11 @@ class BinaryTapePlayer: public TapePlayer {
}; };
void set_delegate(Delegate *delegate); void set_delegate(Delegate *delegate);
bool is_sleeping(); ClockingHint::Preference preferred_clocking() override;
protected: protected:
Delegate *delegate_ = nullptr; Delegate *delegate_ = nullptr;
virtual void process_input_pulse(const Storage::Tape::Tape::Pulse &pulse); void process_input_pulse(const Storage::Tape::Tape::Pulse &pulse) override;
bool input_level_ = false; bool input_level_ = false;
bool motor_is_running_ = false; bool motor_is_running_ = false;
}; };