mirror of
https://github.com/TomHarte/CLK.git
synced 2025-10-25 09:27:01 +00:00
Compare commits
27 Commits
2018-05-24
...
2018-06-03
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94359e9c75 | ||
|
|
076fa55651 | ||
|
|
d380595ad4 | ||
|
|
d84b8700a3 | ||
|
|
80b281d9f1 | ||
|
|
69dc3cc4d8 | ||
|
|
1a9cea050e | ||
|
|
0833412df9 | ||
|
|
35e84ff1a8 | ||
|
|
8dd7c6ef23 | ||
|
|
a26ab7086d | ||
|
|
b2464598d0 | ||
|
|
6812a001d8 | ||
|
|
6c16754a6b | ||
|
|
75f9e3caeb | ||
|
|
ad5afe21ee | ||
|
|
8a566cc1dd | ||
|
|
928aab13dc | ||
|
|
f3fe711542 | ||
|
|
db8d8d8404 | ||
|
|
6220ccb5d3 | ||
|
|
20843305dd | ||
|
|
8f6c0f6a8d | ||
|
|
ede2df7e70 | ||
|
|
d45231c1a8 | ||
|
|
772812b35f | ||
|
|
f443fd44b5 |
@@ -52,79 +52,79 @@
|
||||
*/
|
||||
template <class T> class WrappedInt {
|
||||
public:
|
||||
inline WrappedInt(int l) : length_(l) {}
|
||||
inline WrappedInt() : length_(0) {}
|
||||
constexpr WrappedInt(int l) : length_(l) {}
|
||||
constexpr WrappedInt() : length_(0) {}
|
||||
|
||||
inline T &operator =(const T &rhs) {
|
||||
T &operator =(const T &rhs) {
|
||||
length_ = rhs.length_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline T &operator +=(const T &rhs) {
|
||||
T &operator +=(const T &rhs) {
|
||||
length_ += rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator -=(const T &rhs) {
|
||||
T &operator -=(const T &rhs) {
|
||||
length_ -= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator ++() {
|
||||
T &operator ++() {
|
||||
++ length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator ++(int) {
|
||||
T &operator ++(int) {
|
||||
length_ ++;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator --() {
|
||||
T &operator --() {
|
||||
-- length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator --(int) {
|
||||
T &operator --(int) {
|
||||
length_ --;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator %=(const T &rhs) {
|
||||
T &operator %=(const T &rhs) {
|
||||
length_ %= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator &=(const T &rhs) {
|
||||
T &operator &=(const T &rhs) {
|
||||
length_ &= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline 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_); }
|
||||
constexpr T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
|
||||
|
||||
inline 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_); }
|
||||
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_; }
|
||||
inline bool operator >(const T &rhs) const { return length_ > rhs.length_; }
|
||||
inline bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
|
||||
inline bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
|
||||
inline 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_; }
|
||||
constexpr bool operator >(const T &rhs) const { return length_ > rhs.length_; }
|
||||
constexpr bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
|
||||
constexpr bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
|
||||
constexpr 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
|
||||
|
||||
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
|
||||
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_);
|
||||
length_ %= divisor.length_;
|
||||
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
|
||||
is reset to zero.
|
||||
*/
|
||||
inline T flush() {
|
||||
T flush() {
|
||||
T result(length_);
|
||||
length_ = 0;
|
||||
return result;
|
||||
@@ -150,34 +150,34 @@ template <class T> class WrappedInt {
|
||||
/// Describes an integer number of whole cycles: pairs of clock signal transitions.
|
||||
class Cycles: public WrappedInt<Cycles> {
|
||||
public:
|
||||
inline Cycles(int l) : WrappedInt<Cycles>(l) {}
|
||||
inline Cycles() : WrappedInt<Cycles>() {}
|
||||
inline Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {}
|
||||
constexpr Cycles(int l) : WrappedInt<Cycles>(l) {}
|
||||
constexpr Cycles() : WrappedInt<Cycles>() {}
|
||||
constexpr Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {}
|
||||
};
|
||||
|
||||
/// Describes an integer number of half cycles: single clock signal transitions.
|
||||
class HalfCycles: public WrappedInt<HalfCycles> {
|
||||
public:
|
||||
inline HalfCycles(int l) : WrappedInt<HalfCycles>(l) {}
|
||||
inline HalfCycles() : WrappedInt<HalfCycles>() {}
|
||||
constexpr HalfCycles(int l) : WrappedInt<HalfCycles>(l) {}
|
||||
constexpr HalfCycles() : WrappedInt<HalfCycles>() {}
|
||||
|
||||
inline HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {}
|
||||
inline HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {}
|
||||
constexpr HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {}
|
||||
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.
|
||||
inline Cycles cycles() {
|
||||
constexpr Cycles cycles() {
|
||||
return Cycles(length_ >> 1);
|
||||
}
|
||||
|
||||
/// 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);
|
||||
length_ &= 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// 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_);
|
||||
length_ = 0;
|
||||
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
|
||||
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);
|
||||
Cycles result(length_ / half_divisor.length_);
|
||||
length_ %= half_divisor.length_;
|
||||
@@ -203,7 +203,6 @@ template <class T> class HalfClockReceiver: public T {
|
||||
public:
|
||||
using T::T;
|
||||
|
||||
using T::run_for;
|
||||
inline void run_for(const HalfCycles half_cycles) {
|
||||
half_cycles_ += half_cycles;
|
||||
T::run_for(half_cycles_.flush_cycles());
|
||||
|
||||
88
ClockReceiver/ClockingHintSource.hpp
Normal file
88
ClockReceiver/ClockingHintSource.hpp
Normal 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 */
|
||||
@@ -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 */
|
||||
@@ -43,7 +43,6 @@ class WD1770: public Storage::Disk::MFMController {
|
||||
|
||||
/// Runs the controller for @c number_of_cycles cycles.
|
||||
void run_for(const Cycles cycles);
|
||||
using Storage::Disk::Controller::run_for;
|
||||
|
||||
enum Flag: uint8_t {
|
||||
NotReady = 0x80,
|
||||
|
||||
@@ -69,7 +69,7 @@ template <class BusHandler> class MOS6560 {
|
||||
speaker_(audio_generator_)
|
||||
{
|
||||
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);"
|
||||
|
||||
@@ -125,10 +125,10 @@ template <class BusHandler> class MOS6560 {
|
||||
19, 86, 123, 59,
|
||||
};
|
||||
const uint8_t ntsc_chrominances[16] = {
|
||||
255, 255, 7, 71,
|
||||
25, 86, 48, 112,
|
||||
0, 119, 7, 71,
|
||||
25, 86, 48, 112,
|
||||
255, 255, 121, 57,
|
||||
103, 42, 80, 16,
|
||||
0, 9, 121, 57,
|
||||
103, 42, 80, 16,
|
||||
};
|
||||
const uint8_t *chrominances;
|
||||
Outputs::CRT::DisplayType display_type;
|
||||
|
||||
@@ -83,8 +83,10 @@ i8272::i8272(BusHandler &bus_handler, Cycles clock_rate) :
|
||||
posit_event(static_cast<int>(Event8272::CommandByte));
|
||||
}
|
||||
|
||||
bool i8272::is_sleeping() {
|
||||
return is_sleeping_ && Storage::Disk::MFMController::is_sleeping();
|
||||
ClockingHint::Preference i8272::preferred_clocking() {
|
||||
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) {
|
||||
@@ -159,7 +161,7 @@ void i8272::run_for(Cycles cycles) {
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -198,7 +200,7 @@ uint8_t i8272::get_register(int address) {
|
||||
|
||||
#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_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 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) { \
|
||||
head_timers_running_++; \
|
||||
is_sleeping_ = false; \
|
||||
update_sleep_observer(); \
|
||||
update_clocking_observer(); \
|
||||
} \
|
||||
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) {
|
||||
drives_seeking_++;
|
||||
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
|
||||
|
||||
@@ -39,7 +39,7 @@ class i8272: public Storage::Disk::MFMController {
|
||||
void set_dma_acknowledge(bool dack);
|
||||
void set_terminal_count(bool tc);
|
||||
|
||||
bool is_sleeping();
|
||||
ClockingHint::Preference preferred_clocking() override;
|
||||
|
||||
protected:
|
||||
virtual void select_drive(int number) = 0;
|
||||
@@ -67,7 +67,7 @@ class i8272: public Storage::Disk::MFMController {
|
||||
ResultEmpty = (1 << 5),
|
||||
NoLongerReady = (1 << 6)
|
||||
};
|
||||
void posit_event(int type);
|
||||
void posit_event(int type) override;
|
||||
int interesting_event_mask_ = static_cast<int>(Event8272::CommandByte);
|
||||
int resume_point_ = 0;
|
||||
bool is_access_command_ = false;
|
||||
|
||||
@@ -92,7 +92,7 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
|
||||
Concurrency::DeferringAsyncTaskQueue &task_queue_;
|
||||
|
||||
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 port_inputs_[2];
|
||||
|
||||
|
||||
@@ -19,12 +19,13 @@ namespace {
|
||||
const uint8_t input_flux = 0x1;
|
||||
}
|
||||
|
||||
DiskII::DiskII() :
|
||||
DiskII::DiskII(int clock_rate) :
|
||||
clock_rate_(clock_rate),
|
||||
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_[1].set_sleep_observer(this);
|
||||
drives_[0].set_clocking_hint_observer(this);
|
||||
drives_[1].set_clocking_hint_observer(this);
|
||||
drives_[active_drive_].set_event_delegate(this);
|
||||
}
|
||||
|
||||
@@ -73,71 +74,75 @@ void DiskII::select_drive(int drive) {
|
||||
}
|
||||
|
||||
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();
|
||||
while(integer_cycles--) {
|
||||
const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6);
|
||||
inputs_ |= input_flux;
|
||||
state_ = state_machine_[static_cast<std::size_t>(address)];
|
||||
switch(state_ & 0xf) {
|
||||
default: shift_register_ = 0; break; // clear
|
||||
case 0x8: break; // nop
|
||||
int integer_cycles = cycles.as_int();
|
||||
while(integer_cycles--) {
|
||||
const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6);
|
||||
inputs_ |= input_flux;
|
||||
state_ = state_machine_[static_cast<std::size_t>(address)];
|
||||
switch(state_ & 0xf) {
|
||||
default: shift_register_ = 0; break; // clear
|
||||
case 0x8: break; // nop
|
||||
|
||||
case 0x9: shift_register_ = static_cast<uint8_t>(shift_register_ << 1); break; // shift left, bringing in a zero
|
||||
case 0xd: shift_register_ = static_cast<uint8_t>((shift_register_ << 1) | 1); break; // shift left, bringing in a one
|
||||
case 0x9: shift_register_ = static_cast<uint8_t>(shift_register_ << 1); break; // shift left, bringing in a zero
|
||||
case 0xd: shift_register_ = static_cast<uint8_t>((shift_register_ << 1) | 1); break; // shift left, bringing in a one
|
||||
|
||||
case 0xa: // shift right, bringing in write protected status
|
||||
shift_register_ = (shift_register_ >> 1) | (is_write_protected() ? 0x80 : 0x00);
|
||||
case 0xa: // shift right, bringing in write protected status
|
||||
shift_register_ = (shift_register_ >> 1) | (is_write_protected() ? 0x80 : 0x00);
|
||||
|
||||
// If the controller is in the sense write protect loop but the register will never change,
|
||||
// short circuit further work and return now.
|
||||
if(shift_register_ == is_write_protected() ? 0xff : 0x00) {
|
||||
if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(integer_cycles));
|
||||
if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(integer_cycles));
|
||||
set_controller_can_sleep();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 0xb: shift_register_ = data_input_; break; // load data register from data bus
|
||||
}
|
||||
|
||||
// Currently writing?
|
||||
if(inputs_&input_mode) {
|
||||
// state_ & 0x80 should be the current level sent to the disk;
|
||||
// therefore transitions in that bit should become flux transitions
|
||||
drives_[active_drive_].write_bit(!!((state_ ^ address) & 0x80));
|
||||
}
|
||||
|
||||
// TODO: surely there's a less heavyweight solution than this?
|
||||
if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(1));
|
||||
if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(1));
|
||||
// If the controller is in the sense write protect loop but the register will never change,
|
||||
// short circuit further work and return now.
|
||||
if(shift_register_ == (is_write_protected() ? 0xff : 0x00)) {
|
||||
if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(integer_cycles));
|
||||
if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(integer_cycles));
|
||||
decide_clocking_preference();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 0xb: shift_register_ = data_input_; break; // load data register from data bus
|
||||
}
|
||||
} else {
|
||||
if(!drive_is_sleeping_[0]) drives_[0].run_for(cycles);
|
||||
if(!drive_is_sleeping_[1]) drives_[1].run_for(cycles);
|
||||
|
||||
// Currently writing?
|
||||
if(inputs_&input_mode) {
|
||||
// state_ & 0x80 should be the current level sent to the disk;
|
||||
// therefore transitions in that bit should become flux transitions
|
||||
drives_[active_drive_].write_bit(!!((state_ ^ address) & 0x80));
|
||||
}
|
||||
|
||||
// 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_[1]) drives_[1].run_for(Cycles(1));
|
||||
}
|
||||
|
||||
set_controller_can_sleep();
|
||||
decide_clocking_preference();
|
||||
}
|
||||
|
||||
void DiskII::set_controller_can_sleep() {
|
||||
// Permit the controller to sleep if it's in sense write protect mode, and the shift register
|
||||
// has already filled with the result of shifting eight times.
|
||||
bool controller_could_sleep = controller_can_sleep_;
|
||||
controller_can_sleep_ =
|
||||
(
|
||||
(inputs_ == input_flux) &&
|
||||
!motor_is_enabled_ &&
|
||||
!shift_register_
|
||||
) ||
|
||||
(
|
||||
(inputs_ == (input_command | input_flux)) &&
|
||||
(shift_register_ == (is_write_protected() ? 0xff : 0x00))
|
||||
);
|
||||
if(controller_could_sleep != controller_can_sleep_)
|
||||
update_sleep_observer();
|
||||
void DiskII::decide_clocking_preference() {
|
||||
ClockingHint::Preference prior_preference = clocking_preference_;
|
||||
|
||||
// If in read mode, clocking is either:
|
||||
//
|
||||
// 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
|
||||
// none, given that drives are not running, the shift register has already emptied and there's no flux about to be received.
|
||||
if(!(inputs_ & ~input_flux)) {
|
||||
clocking_preference_ = (!motor_is_enabled_ && !shift_register_ && (inputs_&input_flux)) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
|
||||
}
|
||||
|
||||
// If in writing mode, clocking is real time.
|
||||
if(inputs_ & input_mode) {
|
||||
clocking_preference_ = ClockingHint::Preference::RealTime;
|
||||
}
|
||||
|
||||
// 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() {
|
||||
@@ -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) {
|
||||
if(event.type == Storage::Disk::Track::Event::FluxTransition) {
|
||||
inputs_ &= ~input_flux;
|
||||
set_controller_can_sleep();
|
||||
decide_clocking_preference();
|
||||
}
|
||||
}
|
||||
|
||||
void DiskII::set_component_is_sleeping(Sleeper *component, bool is_sleeping) {
|
||||
drive_is_sleeping_[0] = drives_[0].is_sleeping();
|
||||
drive_is_sleeping_[1] = drives_[1].is_sleeping();
|
||||
update_sleep_observer();
|
||||
void DiskII::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) {
|
||||
drive_is_sleeping_[0] = drives_[0].preferred_clocking() == ClockingHint::Preference::None;
|
||||
drive_is_sleeping_[1] = drives_[1].preferred_clocking() == ClockingHint::Preference::None;
|
||||
decide_clocking_preference();
|
||||
}
|
||||
|
||||
bool DiskII::is_sleeping() {
|
||||
return controller_can_sleep_ && drive_is_sleeping_[0] && drive_is_sleeping_[1];
|
||||
ClockingHint::Preference DiskII::preferred_clocking() {
|
||||
return clocking_preference_;
|
||||
}
|
||||
|
||||
void DiskII::set_data_input(uint8_t input) {
|
||||
@@ -243,11 +248,11 @@ int DiskII::read_address(int address) {
|
||||
break;
|
||||
case 0xf:
|
||||
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;
|
||||
break;
|
||||
}
|
||||
set_controller_can_sleep();
|
||||
decide_clocking_preference();
|
||||
return (address & 1) ? 0xff : shift_register_;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#define DiskII_hpp
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../ClockReceiver/Sleeper.hpp"
|
||||
#include "../../ClockReceiver/ClockingHintSource.hpp"
|
||||
|
||||
#include "../../Storage/Disk/Disk.hpp"
|
||||
#include "../../Storage/Disk/Drive.hpp"
|
||||
@@ -28,10 +28,10 @@ namespace Apple {
|
||||
*/
|
||||
class DiskII:
|
||||
public Storage::Disk::Drive::EventDelegate,
|
||||
public Sleeper::SleepObserver,
|
||||
public Sleeper {
|
||||
public ClockingHint::Source,
|
||||
public ClockingHint::Observer {
|
||||
public:
|
||||
DiskII();
|
||||
DiskII(int clock_rate);
|
||||
|
||||
/// Sets the current external value of the data bus.
|
||||
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);
|
||||
|
||||
// As per Sleeper.
|
||||
bool is_sleeping() override;
|
||||
ClockingHint::Preference preferred_clocking() override;
|
||||
|
||||
// The Disk II functions as a potential target for @c Activity::Sources.
|
||||
void set_activity_observer(Activity::Observer *observer);
|
||||
@@ -95,7 +95,9 @@ class DiskII:
|
||||
|
||||
uint8_t trigger_address(int address, uint8_t value);
|
||||
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 inputs_ = 0;
|
||||
@@ -108,11 +110,11 @@ class DiskII:
|
||||
std::array<uint8_t, 256> state_machine_;
|
||||
Storage::Disk::Drive drives_[2];
|
||||
bool drive_is_sleeping_[2];
|
||||
bool controller_can_sleep_ = false;
|
||||
int active_drive_ = 0;
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -694,7 +694,7 @@ class ConcreteMachine:
|
||||
public KeyboardMachine::Machine,
|
||||
public Utility::TypeRecipient,
|
||||
public CPU::Z80::BusHandler,
|
||||
public Sleeper::SleepObserver,
|
||||
public ClockingHint::Observer,
|
||||
public Machine,
|
||||
public Activity::Source {
|
||||
public:
|
||||
@@ -714,11 +714,8 @@ class ConcreteMachine:
|
||||
Memory::Fuzz(ram_, sizeof(ram_));
|
||||
|
||||
// register this class as the sleep observer for the FDC and tape
|
||||
fdc_.set_sleep_observer(this);
|
||||
fdc_is_sleeping_ = fdc_.is_sleeping();
|
||||
|
||||
tape_player_.set_sleep_observer(this);
|
||||
tape_player_is_sleeping_ = tape_player_.is_sleeping();
|
||||
fdc_.set_clocking_hint_observer(this);
|
||||
tape_player_.set_clocking_hint_observer(this);
|
||||
|
||||
ay_.ay().set_port_handler(&key_state_);
|
||||
}
|
||||
@@ -749,7 +746,7 @@ class ConcreteMachine:
|
||||
ay_.run_for(cycle.length);
|
||||
|
||||
// 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
|
||||
if(typer_) typer_->run_for(cycle.length);
|
||||
@@ -796,11 +793,13 @@ class ConcreteMachine:
|
||||
|
||||
// Check for an FDC access
|
||||
if(has_fdc_ && (address & 0x580) == 0x100) {
|
||||
flush_fdc();
|
||||
fdc_.set_register(address & 1, *cycle.value);
|
||||
}
|
||||
|
||||
// Check for a disk motor access
|
||||
if(has_fdc_ && !(address & 0x580)) {
|
||||
flush_fdc();
|
||||
fdc_.set_motor_on(!!(*cycle.value));
|
||||
}
|
||||
break;
|
||||
@@ -815,6 +814,7 @@ class ConcreteMachine:
|
||||
|
||||
// Check for an FDC access
|
||||
if(has_fdc_ && (address & 0x580) == 0x100) {
|
||||
flush_fdc();
|
||||
*cycle.value &= fdc_.get_register(address & 1);
|
||||
}
|
||||
|
||||
@@ -858,6 +858,7 @@ class ConcreteMachine:
|
||||
// Just flush the AY.
|
||||
ay_.update();
|
||||
ay_.flush();
|
||||
flush_fdc();
|
||||
}
|
||||
|
||||
/// A CRTMachine function; indicates that outputs should be created now.
|
||||
@@ -967,9 +968,10 @@ class ConcreteMachine:
|
||||
return true;
|
||||
}
|
||||
|
||||
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override final {
|
||||
fdc_is_sleeping_ = fdc_.is_sleeping();
|
||||
tape_player_is_sleeping_ = tape_player_.is_sleeping();
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override final {
|
||||
fdc_is_sleeping_ = fdc_.preferred_clocking() == ClockingHint::Preference::None;
|
||||
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
|
||||
@@ -1063,6 +1065,14 @@ class ConcreteMachine:
|
||||
Intel::i8255::i8255<i8255PortHandler> i8255_;
|
||||
|
||||
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_;
|
||||
Storage::Tape::BinaryTapePlayer tape_player_;
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
#include "DiskIICard.hpp"
|
||||
#include "Video.hpp"
|
||||
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
|
||||
#include "../../Analyser/Static/AppleII/Target.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -203,7 +205,7 @@ class ConcreteMachine:
|
||||
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_card_update_;
|
||||
cycles_since_audio_update_ += Cycles(7);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
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(
|
||||
"DiskII",
|
||||
{
|
||||
@@ -20,7 +20,7 @@ DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sec
|
||||
boot_ = std::move(*roms[0]);
|
||||
diskii_.set_state_machine(*roms[1]);
|
||||
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) {
|
||||
@@ -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) {
|
||||
if(diskii_is_sleeping_) return;
|
||||
if(diskii_clocking_preference_ == ClockingHint::Preference::None) return;
|
||||
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);
|
||||
}
|
||||
|
||||
void DiskIICard::set_component_is_sleeping(Sleeper *component, bool is_sleeping) {
|
||||
diskii_is_sleeping_ = is_sleeping;
|
||||
set_select_constraints(is_sleeping ? (IO | Device) : 0);
|
||||
void DiskIICard::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) {
|
||||
diskii_clocking_preference_ = preference;
|
||||
set_select_constraints((preference != ClockingHint::Preference::RealTime) ? (IO | Device) : 0);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
#include "../../Components/DiskII/DiskII.hpp"
|
||||
#include "../../Storage/Disk/Disk.hpp"
|
||||
#include "../../ClockReceiver/Sleeper.hpp"
|
||||
#include "../../ClockReceiver/ClockingHintSource.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
namespace AppleII {
|
||||
|
||||
class DiskIICard: public Card, public Sleeper::SleepObserver {
|
||||
class DiskIICard: public Card, public ClockingHint::Observer {
|
||||
public:
|
||||
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);
|
||||
|
||||
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_;
|
||||
Apple::DiskII diskii_;
|
||||
bool diskii_is_sleeping_ = false;
|
||||
ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -10,83 +10,27 @@
|
||||
|
||||
using namespace AppleII::Video;
|
||||
|
||||
namespace {
|
||||
|
||||
struct ScaledByteFiller {
|
||||
ScaledByteFiller() {
|
||||
VideoBase::setup_tables();
|
||||
}
|
||||
} throwaway;
|
||||
|
||||
}
|
||||
|
||||
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(
|
||||
"float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)"
|
||||
"{"
|
||||
"uint texValue = texture(sampler, coordinate).r;"
|
||||
"texValue >>= int(icoordinate.x) % 7;"
|
||||
"return float(texValue & 1u);"
|
||||
"return texture(sampler, coordinate).r;"
|
||||
"}");
|
||||
crt_->set_integer_coordinate_multiplier(7.0f);
|
||||
|
||||
// Show only the centre 75% of the TV frame.
|
||||
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_immediate_default_phase(0.0f);
|
||||
}
|
||||
|
||||
Outputs::CRT::CRT *VideoBase::get_crt() {
|
||||
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() {
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ class BusHandler {
|
||||
class VideoBase {
|
||||
public:
|
||||
VideoBase();
|
||||
static void setup_tables();
|
||||
|
||||
/// @returns The CRT this video feed is feeding.
|
||||
Outputs::CRT::CRT *get_crt();
|
||||
@@ -46,9 +45,12 @@ class VideoBase {
|
||||
protected:
|
||||
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 row_ = 0, column_ = 0, flash_ = 0;
|
||||
uint16_t *pixel_pointer_ = nullptr;
|
||||
std::vector<uint8_t> character_rom_;
|
||||
|
||||
enum class GraphicsMode {
|
||||
@@ -58,10 +60,7 @@ class VideoBase {
|
||||
} graphics_mode_ = GraphicsMode::LowRes;
|
||||
bool use_graphics_mode_ = false;
|
||||
bool mixed_mode_ = false;
|
||||
uint16_t graphics_carry_ = 0;
|
||||
|
||||
static uint16_t scaled_byte[256];
|
||||
static uint16_t low_resolution_patterns[2][16];
|
||||
uint8_t graphics_carry_ = 0;
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
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 {
|
||||
const int ending_column = column_ + cycles_this_line;
|
||||
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.
|
||||
if(column_ < 40) {
|
||||
if(row_ < 192) {
|
||||
if(!column_) {
|
||||
pixel_pointer_ = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(80, 2));
|
||||
GraphicsMode pixel_mode = (!mixed_mode_ || row_ < 160) ? line_mode : GraphicsMode::Text;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -111,10 +115,7 @@ template <class BusHandler> class Video: public VideoBase {
|
||||
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 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) {
|
||||
case GraphicsMode::Text: {
|
||||
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 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;
|
||||
|
||||
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) {
|
||||
const uint8_t character = bus_handler_.perform_read(static_cast<uint16_t>(text_address + c));
|
||||
pixel_pointer_[c] = low_resolution_patterns[c&1][(character >> row_shift)&0xf];
|
||||
}
|
||||
break;
|
||||
const uint8_t nibble = (bus_handler_.perform_read(static_cast<uint16_t>(text_address + c)) >> row_shift) & 0x0f;
|
||||
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
crt_->output_data(280, 80);
|
||||
output_data_to_column(40);
|
||||
}
|
||||
} else {
|
||||
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_end = std::min(first_sync_column, ending_column);
|
||||
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_end = std::min(first_sync_column + 4, ending_column);
|
||||
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;
|
||||
@@ -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_end = std::min(first_sync_column + 7, ending_column);
|
||||
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_);
|
||||
@@ -192,7 +240,7 @@ template <class BusHandler> class Video: public VideoBase {
|
||||
}
|
||||
|
||||
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
|
||||
// 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;
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -124,19 +124,19 @@ void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) {
|
||||
|
||||
if(output_mode == OutputMode::NTSC) {
|
||||
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 y = c & 14u;"
|
||||
"uint iPhase = (c >> 4);"
|
||||
|
||||
"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;
|
||||
} else {
|
||||
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 y = c & 14u;"
|
||||
|
||||
@@ -304,7 +304,7 @@ class ConcreteMachine:
|
||||
public Utility::TypeRecipient,
|
||||
public Storage::Tape::BinaryTapePlayer::Delegate,
|
||||
public Machine,
|
||||
public Sleeper::SleepObserver,
|
||||
public ClockingHint::Observer,
|
||||
public Activity::Source {
|
||||
public:
|
||||
ConcreteMachine() :
|
||||
@@ -331,7 +331,7 @@ class ConcreteMachine:
|
||||
user_port_via_port_handler_->set_interrupt_delegate(this);
|
||||
keyboard_via_port_handler_->set_interrupt_delegate(this);
|
||||
tape_->set_delegate(this);
|
||||
tape_->set_sleep_observer(this);
|
||||
tape_->set_clocking_hint_observer(this);
|
||||
|
||||
// install a joystick
|
||||
joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_));
|
||||
@@ -749,8 +749,8 @@ class ConcreteMachine:
|
||||
return selection_set;
|
||||
}
|
||||
|
||||
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override {
|
||||
tape_is_sleeping_ = is_sleeping;
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override {
|
||||
tape_is_sleeping_ = clocking == ClockingHint::Preference::None;
|
||||
set_use_fast_tape();
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ class ConcreteMachine:
|
||||
public KeyboardMachine::Machine,
|
||||
public Configurable::Device,
|
||||
public MemoryMap,
|
||||
public Sleeper::SleepObserver,
|
||||
public ClockingHint::Observer,
|
||||
public Activity::Source {
|
||||
public:
|
||||
ConcreteMachine():
|
||||
@@ -108,7 +108,7 @@ class ConcreteMachine:
|
||||
|
||||
ay_.set_port_handler(&ay_port_handler_);
|
||||
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.
|
||||
mixer_.set_relative_volumes({0.5f, 0.1f, 0.4f});
|
||||
@@ -555,8 +555,8 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
// MARK: - Sleeper
|
||||
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override {
|
||||
tape_player_is_sleeping_ = tape_player_.is_sleeping();
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override {
|
||||
tape_player_is_sleeping_ = tape_player_.preferred_clocking() == ClockingHint::Preference::None;
|
||||
set_use_fast_tape();
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ class Microdisc: public WD::WD1770 {
|
||||
bool get_interrupt_request_line();
|
||||
|
||||
void run_for(const Cycles cycles);
|
||||
using WD::WD1770::run_for;
|
||||
|
||||
enum PagingFlags {
|
||||
/// Indicates that the BASIC ROM should be disabled; if this is set then either
|
||||
@@ -52,8 +51,9 @@ class Microdisc: public WD::WD1770 {
|
||||
|
||||
private:
|
||||
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();
|
||||
|
||||
std::array<std::shared_ptr<Storage::Disk::Drive>, 4> drives_;
|
||||
size_t selected_drive_;
|
||||
bool irq_enable_ = false;
|
||||
|
||||
@@ -201,7 +201,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
public Utility::TypeRecipient,
|
||||
public Storage::Tape::BinaryTapePlayer::Delegate,
|
||||
public Microdisc::Delegate,
|
||||
public Sleeper::SleepObserver,
|
||||
public ClockingHint::Observer,
|
||||
public Activity::Source,
|
||||
public Machine {
|
||||
|
||||
@@ -212,14 +212,15 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
ay8910_(audio_queue_),
|
||||
speaker_(ay8910_),
|
||||
via_port_handler_(audio_queue_, ay8910_, speaker_, tape_player_, keyboard_),
|
||||
via_(via_port_handler_) {
|
||||
via_(via_port_handler_),
|
||||
diskii_(2000000) {
|
||||
set_clock_rate(1000000);
|
||||
via_port_handler_.set_interrupt_delegate(this);
|
||||
tape_player_.set_delegate(this);
|
||||
Memory::Fuzz(ram_, sizeof(ram_));
|
||||
|
||||
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 {
|
||||
flush_diskii();
|
||||
const int disk_value = diskii_.read_address(address);
|
||||
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));
|
||||
break;
|
||||
case Analyser::Static::Oric::Target::DiskInterface::Pravetz:
|
||||
if(!diskii_is_sleeping_) {
|
||||
if(diskii_clocking_preference_ == ClockingHint::Preference::RealTime) {
|
||||
diskii_.set_data_input(*value);
|
||||
diskii_.run_for(Cycles(2));
|
||||
} else {
|
||||
cycles_since_diskii_update_ += Cycles(2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -457,6 +461,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
forceinline void flush() {
|
||||
update_video();
|
||||
via_port_handler_.flush();
|
||||
flush_diskii();
|
||||
}
|
||||
|
||||
// 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 {
|
||||
diskii_is_sleeping_ = diskii_.is_sleeping();
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override final {
|
||||
diskii_clocking_preference_ = diskii_.preferred_clocking();
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -617,9 +622,13 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
|
||||
// the Pravetz/Disk II, if in use
|
||||
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::size_t pravetz_rom_base_pointer_ = 0;
|
||||
bool diskii_is_sleeping_ = false;
|
||||
ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime;
|
||||
|
||||
// Overlay RAM
|
||||
uint16_t ram_top_ = basic_visible_ram_top_;
|
||||
|
||||
@@ -16,25 +16,20 @@ namespace {
|
||||
The number of bytes of PCM data to allocate at once; if/when more are required,
|
||||
the class will simply allocate another batch.
|
||||
*/
|
||||
const std::size_t StandardAllocationSize = 40;
|
||||
|
||||
/// The amount of time a byte takes to output.
|
||||
const std::size_t HalfCyclesPerByte = 8;
|
||||
const std::size_t StandardAllocationSize = 320;
|
||||
|
||||
}
|
||||
|
||||
Video::Video() :
|
||||
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(
|
||||
"float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)"
|
||||
"{"
|
||||
"uint texValue = texture(sampler, coordinate).r;"
|
||||
"texValue <<= int(icoordinate.x) & 7;"
|
||||
"return float(texValue & 128u);"
|
||||
"return texture(sampler, coordinate).r;"
|
||||
"}");
|
||||
crt_->set_integer_coordinate_multiplier(8.0f);
|
||||
|
||||
// Show only the centre 80% of the TV frame.
|
||||
crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite);
|
||||
@@ -43,7 +38,7 @@ Video::Video() :
|
||||
|
||||
void Video::run_for(const HalfCycles half_cycles) {
|
||||
// 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() {
|
||||
@@ -53,29 +48,29 @@ void Video::flush() {
|
||||
void Video::flush(bool next_sync) {
|
||||
if(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 {
|
||||
// If not presently in sync, then...
|
||||
|
||||
if(line_data_) {
|
||||
// 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.
|
||||
unsigned int data_length = static_cast<unsigned int>(line_data_pointer_ - line_data_) * HalfCyclesPerByte;
|
||||
if(data_length < cycles_since_update_ || next_sync) {
|
||||
unsigned int output_length = std::min(data_length, cycles_since_update_);
|
||||
crt_->output_data(output_length, output_length / HalfCyclesPerByte);
|
||||
int data_length = static_cast<int>(line_data_pointer_ - line_data_);
|
||||
if(data_length < time_since_update_.as_int() || next_sync) {
|
||||
auto output_length = std::min(data_length, time_since_update_.as_int());
|
||||
crt_->output_data(static_cast<unsigned int>(output_length), static_cast<unsigned int>(output_length));
|
||||
line_data_pointer_ = line_data_ = nullptr;
|
||||
cycles_since_update_ -= output_length;
|
||||
time_since_update_ -= HalfCycles(output_length);
|
||||
} else return;
|
||||
}
|
||||
|
||||
// Any pending pixels being dealt with, pad with the white level.
|
||||
uint8_t *colour_pointer = static_cast<uint8_t *>(crt_->allocate_write_area(1));
|
||||
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) {
|
||||
@@ -101,14 +96,19 @@ void Video::output_byte(uint8_t byte) {
|
||||
if(line_data_) {
|
||||
// If the buffer is full, output it now and obtain a new one
|
||||
if(line_data_pointer_ - line_data_ == StandardAllocationSize) {
|
||||
crt_->output_data(StandardAllocationSize * HalfCyclesPerByte, StandardAllocationSize);
|
||||
cycles_since_update_ -= StandardAllocationSize * HalfCyclesPerByte;
|
||||
crt_->output_data(StandardAllocationSize, StandardAllocationSize);
|
||||
time_since_update_ -= StandardAllocationSize;
|
||||
line_data_pointer_ = line_data_ = crt_->allocate_write_area(StandardAllocationSize);
|
||||
if(!line_data_) return;
|
||||
}
|
||||
|
||||
line_data_pointer_[0] = byte;
|
||||
line_data_pointer_ ++;
|
||||
// Convert to one-byte-per-pixel where any non-zero value will act as white.
|
||||
uint8_t mask = 0x80;
|
||||
for(int c = 0; c < 8; c++) {
|
||||
line_data_pointer_[c] = byte & mask;
|
||||
mask >>= 1;
|
||||
}
|
||||
line_data_pointer_ += 8;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ class Video {
|
||||
bool sync_ = false;
|
||||
uint8_t *line_data_ = 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_;
|
||||
|
||||
void flush(bool next_sync);
|
||||
|
||||
@@ -157,7 +157,7 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
|
||||
// The below emulates the ZonX AY expansion device.
|
||||
if(is_zx81) {
|
||||
if((address&0xef) == 0x0f) {
|
||||
if((address&0xef) == 0xcf) {
|
||||
value &= ay_read_data();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1037,7 +1037,7 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -3124,7 +3124,7 @@
|
||||
children = (
|
||||
4BF6606A1F281573002CB053 /* ClockReceiver.hpp */,
|
||||
4BB06B211F316A3F00600C7A /* ForceInline.hpp */,
|
||||
4BB146C61F49D7D700253439 /* Sleeper.hpp */,
|
||||
4BB146C61F49D7D700253439 /* ClockingHintSource.hpp */,
|
||||
4B449C942063389900A095C8 /* TimeTypes.hpp */,
|
||||
);
|
||||
name = ClockReceiver;
|
||||
|
||||
@@ -170,7 +170,9 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo
|
||||
// outside of the locked region
|
||||
source_output_position_x1() = static_cast<uint16_t>(horizontal_flywheel_->get_current_output_position());
|
||||
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
|
||||
@@ -368,7 +370,7 @@ void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint
|
||||
scan.type = Scan::Type::ColourBurst;
|
||||
scan.number_of_cycles = number_of_cycles;
|
||||
scan.phase = phase;
|
||||
scan.amplitude = amplitude;
|
||||
scan.amplitude = amplitude >> 1;
|
||||
output_scan(&scan);
|
||||
}
|
||||
|
||||
|
||||
@@ -332,10 +332,10 @@ class CRT {
|
||||
output mode will be applied.
|
||||
|
||||
@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
|
||||
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) {
|
||||
enqueue_openGL_function([shader, this] {
|
||||
|
||||
@@ -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.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.y = phaseTimeAndAmplitude.y / 255.0;"
|
||||
"phaseAndAmplitudeVarying.z = (phaseAndAmplitudeVarying.y > 0.0) ? 1.0 / phaseAndAmplitudeVarying.y : 0.0;"
|
||||
"phaseAndAmplitudeVarying.y = (phaseTimeAndAmplitude.y - 128) / 127.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
|
||||
"vec2 eyePosition = 2.0*(extendedOutputPosition / outputTextureSize) - vec2(1.0);"
|
||||
@@ -134,8 +134,8 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_composite_source_sh
|
||||
svideo_shader <<
|
||||
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
|
||||
"{"
|
||||
"vec2 svideoColour = svideo_sample(texID, coordinate, iCoordinate, phase);"
|
||||
"return mix(svideoColour.x, svideoColour.y, amplitude);"
|
||||
"vec2 svideoColour = svideo_sample(texID, coordinate, iCoordinate, phase, amplitude);"
|
||||
"return mix(svideoColour.x, svideoColour.y, abs(amplitude));"
|
||||
"}";
|
||||
} else {
|
||||
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 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));"
|
||||
"}";
|
||||
}
|
||||
@@ -178,11 +178,11 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_svideo_source_shade
|
||||
fragment_shader
|
||||
<< rgb_shader <<
|
||||
"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 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);"
|
||||
"}";
|
||||
}
|
||||
@@ -190,8 +190,8 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_svideo_source_shade
|
||||
fragment_shader <<
|
||||
"void main(void)"
|
||||
"{"
|
||||
"vec2 sample = svideo_sample(texID, inputPositionsVarying[5], iInputPositionVarying, phaseAndAmplitudeVarying.x);"
|
||||
"vec2 quadrature = vec2(cos(phaseAndAmplitudeVarying.x), -sin(phaseAndAmplitudeVarying.x)) * 0.5 * phaseAndAmplitudeVarying.z;"
|
||||
"vec2 sample = svideo_sample(texID, inputPositionsVarying[5], iInputPositionVarying, phaseAndAmplitudeVarying.x, phaseAndAmplitudeVarying.y);"
|
||||
"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));"
|
||||
"}";
|
||||
|
||||
@@ -244,11 +244,11 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_luma_separat
|
||||
|
||||
// define chroma to be whatever was here, minus luma
|
||||
"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
|
||||
// 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));"
|
||||
"}",false, false);
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ static std::shared_ptr<File> ZX81FileFromData(const std::vector<uint8_t> &data)
|
||||
|
||||
// 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 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;
|
||||
|
||||
// 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;
|
||||
|
||||
// TODO: does it make sense to inspect the tokenised BASIC?
|
||||
|
||||
@@ -22,12 +22,12 @@ Controller::Controller(Cycles clock_rate) :
|
||||
set_drive(empty_drive_);
|
||||
}
|
||||
|
||||
void Controller::set_component_is_sleeping(Sleeper *component, bool is_sleeping) {
|
||||
update_sleep_observer();
|
||||
void Controller::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) {
|
||||
update_clocking_observer();
|
||||
}
|
||||
|
||||
bool Controller::is_sleeping() {
|
||||
return !drive_ || drive_->is_sleeping();
|
||||
ClockingHint::Preference Controller::preferred_clocking() {
|
||||
return (!drive_ || (drive_->preferred_clocking() == ClockingHint::Preference::None)) ? ClockingHint::Preference::None : ClockingHint::Preference::RealTime;
|
||||
}
|
||||
|
||||
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) {
|
||||
if(drive_ != drive) {
|
||||
bool was_sleeping = is_sleeping();
|
||||
ClockingHint::Preference former_prefernece = preferred_clocking();
|
||||
// invalidate_track();
|
||||
|
||||
if(drive_) {
|
||||
drive_->set_event_delegate(nullptr);
|
||||
drive_->set_sleep_observer(nullptr);
|
||||
drive_->set_clocking_hint_observer(nullptr);
|
||||
}
|
||||
drive_ = drive;
|
||||
if(drive_) {
|
||||
drive_->set_event_delegate(this);
|
||||
drive_->set_sleep_observer(this);
|
||||
drive_->set_clocking_hint_observer(this);
|
||||
} else {
|
||||
drive_ = empty_drive_;
|
||||
}
|
||||
|
||||
if(is_sleeping() != was_sleeping) {
|
||||
update_sleep_observer();
|
||||
if(preferred_clocking() != former_prefernece) {
|
||||
update_clocking_observer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#include "../Track/PCMPatchedTrack.hpp"
|
||||
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../../ClockReceiver/Sleeper.hpp"
|
||||
#include "../../../ClockReceiver/ClockingHintSource.hpp"
|
||||
|
||||
namespace Storage {
|
||||
namespace Disk {
|
||||
@@ -29,7 +29,11 @@ namespace Disk {
|
||||
|
||||
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:
|
||||
/*!
|
||||
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
|
||||
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
|
||||
@@ -97,9 +101,9 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public Drive::EventDe
|
||||
Drive &get_drive();
|
||||
|
||||
/*!
|
||||
As per Sleeper.
|
||||
As per ClockingHint::Source.
|
||||
*/
|
||||
bool is_sleeping();
|
||||
ClockingHint::Preference preferred_clocking() override;
|
||||
|
||||
private:
|
||||
Time bit_length_;
|
||||
@@ -113,14 +117,15 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public Drive::EventDe
|
||||
|
||||
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
|
||||
void process_event(const Track::Event &event);
|
||||
void advance(const Cycles cycles);
|
||||
void process_event(const Track::Event &event) override;
|
||||
void advance(const Cycles cycles) override ;
|
||||
|
||||
// to satisfy DigitalPhaseLockedLoop::Delegate
|
||||
void digital_phase_locked_loop_output_bit(int value);
|
||||
void digital_phase_locked_loop_output_bit(int value) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ class AppleDSK: public DiskImage {
|
||||
*/
|
||||
AppleDSK(const std::string &file_name);
|
||||
|
||||
// Implemented to satisfy @c Disk.
|
||||
// Implemented to satisfy @c DiskImage.
|
||||
HeadPosition get_maximum_head_position() 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;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "NIB.hpp"
|
||||
|
||||
#include "../../Track/PCMTrack.hpp"
|
||||
#include "../../Track/TrackSerialiser.hpp"
|
||||
#include "../../Encodings/AppleGCR/Encoder.hpp"
|
||||
|
||||
#include <vector>
|
||||
@@ -29,20 +30,38 @@ NIB::NIB(const std::string &file_name) :
|
||||
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() {
|
||||
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) {
|
||||
// NIBs contain data for even-numbered tracks underneath a single head only.
|
||||
if(address.head) return nullptr;
|
||||
|
||||
const long file_track = static_cast<long>(address.position.as_int());
|
||||
file_.seek(file_track * track_length, SEEK_SET);
|
||||
std::vector<uint8_t> track_data = file_.read(track_length);
|
||||
long offset = file_offset(address);
|
||||
std::vector<uint8_t> track_data;
|
||||
{
|
||||
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
|
||||
// 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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,21 +17,23 @@ namespace Disk {
|
||||
|
||||
/*!
|
||||
Provides a @c DiskImage describing an Apple NIB disk image:
|
||||
mostly a bit stream capture, but syncs are implicitly packed
|
||||
into 8 bits instead of 9.
|
||||
a bit stream capture that omits sync zeroes, and doesn't define
|
||||
the means for full reconstruction.
|
||||
*/
|
||||
class NIB: public DiskImage {
|
||||
public:
|
||||
NIB(const std::string &file_name);
|
||||
|
||||
// Implemented to satisfy @c DiskImage.
|
||||
HeadPosition get_maximum_head_position() 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:
|
||||
FileHolder file_;
|
||||
long get_file_offset_for_position(Track::Address address);
|
||||
|
||||
long file_offset(Track::Address address);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ class WOZ: public DiskImage {
|
||||
public:
|
||||
WOZ(const std::string &file_name);
|
||||
|
||||
// Implemented to satisfy @c Disk.
|
||||
// Implemented to satisfy @c DiskImage.
|
||||
HeadPosition get_maximum_head_position() override;
|
||||
int get_head_count() override;
|
||||
std::shared_ptr<Track> get_track_at_position(Track::Address address) override;
|
||||
|
||||
@@ -46,15 +46,15 @@ void Drive::set_disk(const std::shared_ptr<Disk> &disk) {
|
||||
has_disk_ = !!disk_;
|
||||
|
||||
invalidate_track();
|
||||
update_sleep_observer();
|
||||
update_clocking_observer();
|
||||
}
|
||||
|
||||
bool Drive::has_disk() {
|
||||
return has_disk_;
|
||||
}
|
||||
|
||||
bool Drive::is_sleeping() {
|
||||
return !motor_is_on_ || !has_disk_;
|
||||
ClockingHint::Preference Drive::preferred_clocking() {
|
||||
return (!motor_is_on_ || !has_disk_) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
|
||||
}
|
||||
|
||||
bool Drive::get_is_track_zero() {
|
||||
@@ -119,7 +119,7 @@ void Drive::set_motor_on(bool motor_is_on) {
|
||||
ready_index_count_ = 0;
|
||||
if(disk_) disk_->flush_tracks();
|
||||
}
|
||||
update_sleep_observer();
|
||||
update_clocking_observer();
|
||||
}
|
||||
|
||||
bool Drive::get_motor_on() {
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
|
||||
#include "../TimedEventLoop.hpp"
|
||||
#include "../../Activity/Observer.hpp"
|
||||
#include "../../ClockReceiver/Sleeper.hpp"
|
||||
#include "../../ClockReceiver/ClockingHintSource.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Storage {
|
||||
namespace Disk {
|
||||
|
||||
class Drive: public Sleeper, public TimedEventLoop {
|
||||
class Drive: public ClockingHint::Source, public TimedEventLoop {
|
||||
public:
|
||||
Drive(unsigned int input_clock_rate, int revolutions_per_minute, int number_of_heads);
|
||||
~Drive();
|
||||
@@ -121,7 +121,7 @@ class Drive: public Sleeper, public TimedEventLoop {
|
||||
void set_event_delegate(EventDelegate *);
|
||||
|
||||
// As per Sleeper.
|
||||
bool is_sleeping();
|
||||
ClockingHint::Preference preferred_clocking() override;
|
||||
|
||||
/// Adds an activity observer; it'll be notified of disk activity.
|
||||
/// 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_;
|
||||
|
||||
// TimedEventLoop call-ins and state.
|
||||
void process_next_event();
|
||||
void process_next_event() override;
|
||||
void get_next_event(const Time &duration_already_passed);
|
||||
void advance(const Cycles cycles);
|
||||
void advance(const Cycles cycles) override;
|
||||
Track::Event current_event_;
|
||||
|
||||
// Helper for track changes.
|
||||
|
||||
@@ -65,15 +65,15 @@ void Tape::set_offset(uint64_t offset) {
|
||||
|
||||
// MARK: - Player
|
||||
|
||||
bool TapePlayer::is_sleeping() {
|
||||
return !tape_ || tape_->is_at_end();
|
||||
ClockingHint::Preference TapePlayer::preferred_clocking() {
|
||||
return (!tape_ || tape_->is_at_end()) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
|
||||
}
|
||||
|
||||
void TapePlayer::set_tape(std::shared_ptr<Storage::Tape::Tape> tape) {
|
||||
tape_ = tape;
|
||||
reset_timer();
|
||||
get_next_pulse();
|
||||
update_sleep_observer();
|
||||
update_clocking_observer();
|
||||
}
|
||||
|
||||
std::shared_ptr<Storage::Tape::Tape> TapePlayer::get_tape() {
|
||||
@@ -88,7 +88,7 @@ void TapePlayer::get_next_pulse() {
|
||||
// get the new pulse
|
||||
if(tape_) {
|
||||
current_pulse_ = tape_->get_next_pulse();
|
||||
if(tape_->is_at_end()) update_sleep_observer();
|
||||
if(tape_->is_at_end()) update_clocking_observer();
|
||||
} else {
|
||||
current_pulse_.length.length = 1;
|
||||
current_pulse_.length.clock_rate = 1;
|
||||
@@ -119,14 +119,15 @@ BinaryTapePlayer::BinaryTapePlayer(unsigned int input_clock_rate) :
|
||||
TapePlayer(input_clock_rate)
|
||||
{}
|
||||
|
||||
bool BinaryTapePlayer::is_sleeping() {
|
||||
return !motor_is_running_ || TapePlayer::is_sleeping();
|
||||
ClockingHint::Preference BinaryTapePlayer::preferred_clocking() {
|
||||
if(!motor_is_running_) return ClockingHint::Preference::None;
|
||||
return TapePlayer::preferred_clocking();
|
||||
}
|
||||
|
||||
void BinaryTapePlayer::set_motor_control(bool enabled) {
|
||||
if(motor_is_running_ != enabled) {
|
||||
motor_is_running_ = enabled;
|
||||
update_sleep_observer();
|
||||
update_clocking_observer();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include <memory>
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../ClockReceiver/Sleeper.hpp"
|
||||
#include "../../ClockReceiver/ClockingHintSource.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
|
||||
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:
|
||||
TapePlayer(unsigned int input_clock_rate);
|
||||
|
||||
@@ -107,10 +107,10 @@ class TapePlayer: public TimedEventLoop, public Sleeper {
|
||||
|
||||
void run_for_input_pulse();
|
||||
|
||||
bool is_sleeping();
|
||||
ClockingHint::Preference preferred_clocking() override;
|
||||
|
||||
protected:
|
||||
virtual void process_next_event();
|
||||
virtual void process_next_event() override;
|
||||
virtual void process_input_pulse(const Tape::Pulse &pulse) = 0;
|
||||
|
||||
private:
|
||||
@@ -145,11 +145,11 @@ class BinaryTapePlayer: public TapePlayer {
|
||||
};
|
||||
void set_delegate(Delegate *delegate);
|
||||
|
||||
bool is_sleeping();
|
||||
ClockingHint::Preference preferred_clocking() override;
|
||||
|
||||
protected:
|
||||
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 motor_is_running_ = false;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user