1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-10-25 09:27:01 +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 {
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());

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.
void run_for(const Cycles cycles);
using Storage::Disk::Controller::run_for;
enum Flag: uint8_t {
NotReady = 0x80,

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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];

View File

@@ -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_;
}

View File

@@ -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;
};

View File

@@ -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_;

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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;
};
}

View File

@@ -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);
}
}

View File

@@ -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;
}
};
}

View File

@@ -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;"

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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_;

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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] {

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.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);
}

View File

@@ -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?

View File

@@ -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();
}
}
}

View File

@@ -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;
};
}

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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);
};
}

View File

@@ -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;

View File

@@ -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() {

View File

@@ -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.

View File

@@ -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();
}
}

View File

@@ -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;
};