1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-09-12 17:24:42 +00:00

Compare commits

...

1 Commits

Author SHA1 Message Date
Thomas Harte
60fbb067c5 Resolve some dangling indentation style divergences. 2025-09-05 14:32:19 -04:00
4 changed files with 434 additions and 433 deletions

View File

@@ -24,36 +24,36 @@ namespace Sinclair::ZX8081 {
and the black level. and the black level.
*/ */
class Video { class Video {
public: public:
/// Constructs an instance of the video feed. /// Constructs an instance of the video feed.
Video(); Video();
/// Advances time by @c half-cycles. /// Advances time by @c half-cycles.
void run_for(const HalfCycles); void run_for(const HalfCycles);
/// Forces output to catch up to the current output position. /// Forces output to catch up to the current output position.
void flush(); void flush();
/// Sets the current sync output. /// Sets the current sync output.
void set_sync(bool sync); void set_sync(bool sync);
/// Causes @c byte to be serialised into pixels and output over the next four cycles. /// Causes @c byte to be serialised into pixels and output over the next four cycles.
void output_byte(uint8_t byte); void output_byte(uint8_t byte);
/// Sets the scan target. /// Sets the scan target.
void set_scan_target(Outputs::Display::ScanTarget *scan_target); void set_scan_target(Outputs::Display::ScanTarget *scan_target);
/// Gets the current scan status. /// Gets the current scan status.
Outputs::Display::ScanStatus get_scaled_scan_status() const; Outputs::Display::ScanStatus get_scaled_scan_status() const;
private: private:
bool sync_ = false; bool sync_ = false;
uint8_t *line_data_ = nullptr; uint8_t *line_data_ = nullptr;
uint8_t *line_data_pointer_ = nullptr; uint8_t *line_data_pointer_ = nullptr;
HalfCycles time_since_update_ = 0; HalfCycles time_since_update_ = 0;
Outputs::CRT::CRT crt_; Outputs::CRT::CRT crt_;
void flush(bool next_sync); void flush(bool next_sync);
}; };
} }

View File

@@ -48,420 +48,421 @@ enum class Timing {
*/ */
template <Timing timing> class Video { template <Timing timing> class Video {
private: private:
struct Timings { struct Timings {
// Number of cycles per line. Will be 224 or 228. // Number of cycles per line. Will be 224 or 228.
int half_cycles_per_line; int half_cycles_per_line;
// Number of lines comprising a whole frame. Will be 311 or 312. // Number of lines comprising a whole frame. Will be 311 or 312.
int lines_per_frame; int lines_per_frame;
// Number of cycles before first pixel fetch that contention starts to be applied. // Number of cycles before first pixel fetch that contention starts to be applied.
int contention_leadin; int contention_leadin;
// Period in a line for which contention is applied. // Period in a line for which contention is applied.
int contention_duration; int contention_duration;
// Number of cycles after first pixel fetch at which interrupt is first signalled. // Number of cycles after first pixel fetch at which interrupt is first signalled.
int interrupt_time; int interrupt_time;
// Contention to apply, in whole cycles, as a function of number of whole cycles since // Contention to apply, in whole cycles, as a function of number of whole cycles since
// contention began. // contention began.
int delays[8]; int delays[8];
constexpr Timings( constexpr Timings(
const int cycles_per_line, const int cycles_per_line,
const int lines_per_frame, const int lines_per_frame,
const int contention_leadin, const int contention_leadin,
const int contention_duration, const int contention_duration,
const int interrupt_offset, const int interrupt_offset,
const int (&delays)[8]) const int (&delays)[8])
noexcept : noexcept :
half_cycles_per_line(cycles_per_line * 2), half_cycles_per_line(cycles_per_line * 2),
lines_per_frame(lines_per_frame), lines_per_frame(lines_per_frame),
contention_leadin(contention_leadin * 2), contention_leadin(contention_leadin * 2),
contention_duration(contention_duration * 2), contention_duration(contention_duration * 2),
interrupt_time((cycles_per_line * lines_per_frame - interrupt_offset - contention_leadin) * 2), interrupt_time((cycles_per_line * lines_per_frame - interrupt_offset - contention_leadin) * 2),
delays{ delays{
delays[0] * 2, delays[0] * 2,
delays[1] * 2, delays[1] * 2,
delays[2] * 2, delays[2] * 2,
delays[3] * 2, delays[3] * 2,
delays[4] * 2, delays[4] * 2,
delays[5] * 2, delays[5] * 2,
delays[6] * 2, delays[6] * 2,
delays[7] * 2 delays[7] * 2
}
{}
};
static constexpr Timings get_timings() {
if constexpr (timing == Timing::Plus3) {
constexpr int delays[] = {1, 0, 7, 6, 5, 4, 3, 2};
return Timings(228, 311, 6, 129, 14361, delays);
} }
{}
};
if constexpr (timing == Timing::OneTwoEightK) { static constexpr Timings get_timings() {
constexpr int delays[] = {6, 5, 4, 3, 2, 1, 0, 0}; if constexpr (timing == Timing::Plus3) {
return Timings(228, 311, 4, 128, 14361, delays); constexpr int delays[] = {1, 0, 7, 6, 5, 4, 3, 2};
} return Timings(228, 311, 6, 129, 14361, delays);
if constexpr (timing == Timing::FortyEightK) {
constexpr int delays[] = {6, 5, 4, 3, 2, 1, 0, 0};
return Timings(224, 312, 4, 128, 14335, delays);
}
} }
// Interrupt should be held for 32 cycles. if constexpr (timing == Timing::OneTwoEightK) {
static constexpr int interrupt_duration = 64; constexpr int delays[] = {6, 5, 4, 3, 2, 1, 0, 0};
return Timings(228, 311, 4, 128, 14361, delays);
}
public: if constexpr (timing == Timing::FortyEightK) {
void run_for(HalfCycles duration) { constexpr int delays[] = {6, 5, 4, 3, 2, 1, 0, 0};
static constexpr auto timings = get_timings(); return Timings(224, 312, 4, 128, 14335, delays);
}
static constexpr int sync_line = (timings.interrupt_time / timings.half_cycles_per_line) + 1;
static constexpr int sync_position = (timing == Timing::FortyEightK) ? 164 * 2 : 166 * 2;
static constexpr int sync_length = 17 * 2;
static constexpr int burst_position = sync_position + 40;
static constexpr int burst_length = 17;
int cycles_remaining = duration.as<int>();
while(cycles_remaining) {
int line = time_into_frame_ / timings.half_cycles_per_line;
int offset = time_into_frame_ % timings.half_cycles_per_line;
const int cycles_this_line = std::min(cycles_remaining, timings.half_cycles_per_line - offset);
const int end_offset = offset + cycles_this_line;
if(!offset) {
is_alternate_line_ ^= true;
if(!line) {
flash_counter_ = (flash_counter_ + 1) & 31;
flash_mask_ = uint8_t(flash_counter_ >> 4);
}
}
if(line >= sync_line && line < sync_line + 3) {
// Output sync line.
crt_.output_sync(cycles_this_line);
} else {
if(line >= 192) {
// Output plain border line.
if(offset < sync_position) {
const int border_duration = std::min(sync_position, end_offset) - offset;
crt_.output_level<uint8_t>(border_duration, border_colour_);
offset += border_duration;
}
} else {
// Output pixel line.
if(offset < 256) {
const int pixel_duration = std::min(256, end_offset) - offset;
if(!offset) {
pixel_target_ = crt_.begin_data(256);
attribute_address_ = ((line >> 3) << 5) + 6144;
pixel_address_ = ((line & 0x07) << 8) | ((line & 0x38) << 2) | ((line & 0xc0) << 5);
}
if(pixel_target_) {
const int start_column = offset >> 4;
const int end_column = (offset + pixel_duration) >> 4;
for(int column = start_column; column < end_column; column++) {
last_fetches_[0] = memory_[pixel_address_];
last_fetches_[1] = memory_[attribute_address_];
last_fetches_[2] = memory_[pixel_address_+1];
last_fetches_[3] = memory_[attribute_address_+1];
set_last_contended_area_access(last_fetches_[3]);
pixel_address_ += 2;
attribute_address_ += 2;
constexpr uint8_t masks[] = {0, 0xff};
#define Output(n) \
{ \
const uint8_t pixels = \
uint8_t(last_fetches_[n] ^ masks[flash_mask_ & (last_fetches_[n+1] >> 7)]); \
\
const uint8_t colours[2] = { \
palette[(last_fetches_[n+1] & 0x78) >> 3], \
palette[((last_fetches_[n+1] & 0x40) >> 3) | (last_fetches_[n+1] & 0x07)], \
}; \
\
pixel_target_[0] = colours[(pixels >> 7) & 1]; \
pixel_target_[1] = colours[(pixels >> 6) & 1]; \
pixel_target_[2] = colours[(pixels >> 5) & 1]; \
pixel_target_[3] = colours[(pixels >> 4) & 1]; \
pixel_target_[4] = colours[(pixels >> 3) & 1]; \
pixel_target_[5] = colours[(pixels >> 2) & 1]; \
pixel_target_[6] = colours[(pixels >> 1) & 1]; \
pixel_target_[7] = colours[(pixels >> 0) & 1]; \
pixel_target_ += 8; \
} }
Output(0); // Interrupt should be held for 32 cycles.
Output(2); static constexpr int interrupt_duration = 64;
public:
void run_for(HalfCycles duration) {
static constexpr auto timings = get_timings();
static constexpr int sync_line = (timings.interrupt_time / timings.half_cycles_per_line) + 1;
static constexpr int sync_position = (timing == Timing::FortyEightK) ? 164 * 2 : 166 * 2;
static constexpr int sync_length = 17 * 2;
static constexpr int burst_position = sync_position + 40;
static constexpr int burst_length = 17;
int cycles_remaining = duration.as<int>();
while(cycles_remaining) {
int line = time_into_frame_ / timings.half_cycles_per_line;
int offset = time_into_frame_ % timings.half_cycles_per_line;
const int cycles_this_line = std::min(cycles_remaining, timings.half_cycles_per_line - offset);
const int end_offset = offset + cycles_this_line;
if(!offset) {
is_alternate_line_ ^= true;
if(!line) {
flash_counter_ = (flash_counter_ + 1) & 31;
flash_mask_ = uint8_t(flash_counter_ >> 4);
}
}
if(line >= sync_line && line < sync_line + 3) {
// Output sync line.
crt_.output_sync(cycles_this_line);
} else {
if(line >= 192) {
// Output plain border line.
if(offset < sync_position) {
const int border_duration = std::min(sync_position, end_offset) - offset;
crt_.output_level<uint8_t>(border_duration, border_colour_);
offset += border_duration;
}
} else {
// Output pixel line.
if(offset < 256) {
const int pixel_duration = std::min(256, end_offset) - offset;
if(!offset) {
pixel_target_ = crt_.begin_data(256);
attribute_address_ = ((line >> 3) << 5) + 6144;
pixel_address_ = ((line & 0x07) << 8) | ((line & 0x38) << 2) | ((line & 0xc0) << 5);
}
if(pixel_target_) {
const int start_column = offset >> 4;
const int end_column = (offset + pixel_duration) >> 4;
for(int column = start_column; column < end_column; column++) {
last_fetches_[0] = memory_[pixel_address_];
last_fetches_[1] = memory_[attribute_address_];
last_fetches_[2] = memory_[pixel_address_+1];
last_fetches_[3] = memory_[attribute_address_+1];
set_last_contended_area_access(last_fetches_[3]);
pixel_address_ += 2;
attribute_address_ += 2;
constexpr uint8_t masks[] = {0, 0xff};
#define Output(n) \
{ \
const uint8_t pixels = \
uint8_t(last_fetches_[n] ^ masks[flash_mask_ & (last_fetches_[n+1] >> 7)]); \
\
const uint8_t colours[2] = { \
palette[(last_fetches_[n+1] & 0x78) >> 3], \
palette[((last_fetches_[n+1] & 0x40) >> 3) | (last_fetches_[n+1] & 0x07)], \
}; \
\
pixel_target_[0] = colours[(pixels >> 7) & 1]; \
pixel_target_[1] = colours[(pixels >> 6) & 1]; \
pixel_target_[2] = colours[(pixels >> 5) & 1]; \
pixel_target_[3] = colours[(pixels >> 4) & 1]; \
pixel_target_[4] = colours[(pixels >> 3) & 1]; \
pixel_target_[5] = colours[(pixels >> 2) & 1]; \
pixel_target_[6] = colours[(pixels >> 1) & 1]; \
pixel_target_[7] = colours[(pixels >> 0) & 1]; \
pixel_target_ += 8; \
}
Output(0);
Output(2);
#undef Output #undef Output
}
}
offset += pixel_duration;
if(offset == 256) {
crt_.output_data(256);
pixel_target_ = nullptr;
} }
} }
if(offset >= 256 && offset < sync_position && end_offset > offset) { offset += pixel_duration;
const int border_duration = std::min(sync_position, end_offset) - offset; if(offset == 256) {
crt_.output_level<uint8_t>(border_duration, border_colour_); crt_.output_data(256);
offset += border_duration; pixel_target_ = nullptr;
} }
} }
// Output the common tail to border and pixel lines: sync, blank, colour burst, border. if(offset >= 256 && offset < sync_position && end_offset > offset) {
const int border_duration = std::min(sync_position, end_offset) - offset;
if(offset >= sync_position && offset < sync_position + sync_length && end_offset > offset) { crt_.output_level<uint8_t>(border_duration, border_colour_);
const int sync_duration = std::min(sync_position + sync_length, end_offset) - offset; offset += border_duration;
crt_.output_sync(sync_duration);
offset += sync_duration;
}
if(offset >= sync_position + sync_length && offset < burst_position && end_offset > offset) {
const int blank_duration = std::min(burst_position, end_offset) - offset;
crt_.output_blank(blank_duration);
offset += blank_duration;
}
if(offset >= burst_position && offset < burst_position+burst_length && end_offset > offset) {
const int burst_duration = std::min(burst_position + burst_length, end_offset) - offset;
if constexpr (timing >= Timing::OneTwoEightK) {
crt_.output_colour_burst(burst_duration, 116, is_alternate_line_);
// The colour burst phase above is an empirical guess. I need to research further.
} else {
crt_.output_default_colour_burst(burst_duration);
}
offset += burst_duration;
}
if(offset >= burst_position+burst_length && end_offset > offset) {
crt_.output_level<uint8_t>(end_offset - offset, border_colour_);
} }
} }
cycles_remaining -= cycles_this_line; // Output the common tail to border and pixel lines: sync, blank, colour burst, border.
time_into_frame_ = (time_into_frame_ + cycles_this_line) % (timings.half_cycles_per_line * timings.lines_per_frame);
}
}
private: if(offset >= sync_position && offset < sync_position + sync_length && end_offset > offset) {
static constexpr int half_cycles_per_line() { const int sync_duration = std::min(sync_position + sync_length, end_offset) - offset;
if constexpr (timing == Timing::FortyEightK) { crt_.output_sync(sync_duration);
// TODO: determine real figure here, if one exists. offset += sync_duration;
// The source I'm looking at now suggests that the theoretical }
// ideal of 224*2 ignores the real-life effects of separate
// crystals, so I've nudged this experimentally.
return 224*2 - 1;
} else {
return 227*2;
}
}
static constexpr HalfCycles frame_duration() { if(offset >= sync_position + sync_length && offset < burst_position && end_offset > offset) {
const auto timings = get_timings(); const int blank_duration = std::min(burst_position, end_offset) - offset;
return HalfCycles(timings.half_cycles_per_line * timings.lines_per_frame); crt_.output_blank(blank_duration);
} offset += blank_duration;
}
HalfCycles time_since_interrupt() { if(offset >= burst_position && offset < burst_position+burst_length && end_offset > offset) {
const auto timings = get_timings(); const int burst_duration = std::min(burst_position + burst_length, end_offset) - offset;
if(time_into_frame_ >= timings.interrupt_time) {
return HalfCycles(time_into_frame_ - timings.interrupt_time);
} else {
return HalfCycles(time_into_frame_) + frame_duration() - HalfCycles(timings.interrupt_time);
}
}
void set_time_since_interrupt(const HalfCycles time) { if constexpr (timing >= Timing::OneTwoEightK) {
// Advance using run_for to ensure that all proper CRT interactions occurred. crt_.output_colour_burst(burst_duration, 116, is_alternate_line_);
const auto timings = get_timings(); // The colour burst phase above is an empirical guess. I need to research further.
const auto target = (time + timings.interrupt_time) % frame_duration(); } else {
const auto now = HalfCycles(time_into_frame_); crt_.output_default_colour_burst(burst_duration);
}
offset += burst_duration;
}
// Maybe this is easy? if(offset >= burst_position+burst_length && end_offset > offset) {
if(target == now) return; crt_.output_level<uint8_t>(end_offset - offset, border_colour_);
}
// Is the time within this frame?
if(time > now) {
run_for(target - time);
return;
} }
// Then it's necessary to finish this frame and run into the next. cycles_remaining -= cycles_this_line;
run_for(frame_duration() - now + time); time_into_frame_ = (time_into_frame_ + cycles_this_line) % (timings.half_cycles_per_line * timings.lines_per_frame);
}
}
private:
static constexpr int half_cycles_per_line() {
if constexpr (timing == Timing::FortyEightK) {
// TODO: determine real figure here, if one exists.
// The source I'm looking at now suggests that the theoretical
// ideal of 224*2 ignores the real-life effects of separate
// crystals, so I've nudged this experimentally.
return 224*2 - 1;
} else {
return 227*2;
}
}
static constexpr HalfCycles frame_duration() {
const auto timings = get_timings();
return HalfCycles(timings.half_cycles_per_line * timings.lines_per_frame);
}
HalfCycles time_since_interrupt() {
const auto timings = get_timings();
if(time_into_frame_ >= timings.interrupt_time) {
return HalfCycles(time_into_frame_ - timings.interrupt_time);
} else {
return HalfCycles(time_into_frame_) + frame_duration() - HalfCycles(timings.interrupt_time);
}
}
void set_time_since_interrupt(const HalfCycles time) {
// Advance using run_for to ensure that all proper CRT interactions occurred.
const auto timings = get_timings();
const auto target = (time + timings.interrupt_time) % frame_duration();
const auto now = HalfCycles(time_into_frame_);
// Maybe this is easy?
if(target == now) return;
// Is the time within this frame?
if(time > now) {
run_for(target - time);
return;
} }
public: // Then it's necessary to finish this frame and run into the next.
Video() : run_for(frame_duration() - now + time);
crt_(half_cycles_per_line(), 2, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red2Green2Blue2) }
{
// Show only the centre 80% of the TV frame.
crt_.set_display_type(Outputs::Display::DisplayType::RGB);
crt_.set_visible_area(Outputs::Display::Rect(0.1f, 0.1f, 0.8f, 0.8f));
// Get the CRT roughly into phase. public:
// Video() :
// TODO: this is coupled to an assumption about the initial CRT. Fix. crt_(half_cycles_per_line(), 2, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red2Green2Blue2)
const auto timings = get_timings(); {
crt_.output_blank(timings.lines_per_frame*timings.half_cycles_per_line - timings.interrupt_time); // Show only the centre 80% of the TV frame.
crt_.set_display_type(Outputs::Display::DisplayType::RGB);
crt_.set_visible_area(Outputs::Display::Rect(0.1f, 0.1f, 0.8f, 0.8f));
// Get the CRT roughly into phase.
//
// TODO: this is coupled to an assumption about the initial CRT. Fix.
const auto timings = get_timings();
crt_.output_blank(timings.lines_per_frame*timings.half_cycles_per_line - timings.interrupt_time);
}
void set_video_source(const uint8_t *source) {
memory_ = source;
}
/*!
@returns The amount of time until the next change in the interrupt line, that being the only internally-observeable output.
*/
HalfCycles next_sequence_point() {
constexpr auto timings = get_timings();
// Is the frame still ahead of this interrupt?
if(time_into_frame_ < timings.interrupt_time) {
return HalfCycles(timings.interrupt_time - time_into_frame_);
} }
void set_video_source(const uint8_t *source) { // If not, is it within this interrupt?
memory_ = source; if(time_into_frame_ < timings.interrupt_time + interrupt_duration) {
return HalfCycles(timings.interrupt_time + interrupt_duration - time_into_frame_);
} }
/*! // If not, it'll be in the next batch.
@returns The amount of time until the next change in the interrupt line, that being the only internally-observeable output. return timings.interrupt_time + timings.half_cycles_per_line * timings.lines_per_frame - time_into_frame_;
*/ }
HalfCycles next_sequence_point() {
constexpr auto timings = get_timings();
// Is the frame still ahead of this interrupt? /*!
if(time_into_frame_ < timings.interrupt_time) { @returns The current state of the interrupt output.
return HalfCycles(timings.interrupt_time - time_into_frame_); */
} bool get_interrupt_line() const {
constexpr auto timings = get_timings();
return time_into_frame_ >= timings.interrupt_time && time_into_frame_ < timings.interrupt_time + interrupt_duration;
}
// If not, is it within this interrupt? /*!
if(time_into_frame_ < timings.interrupt_time + interrupt_duration) { @returns How many cycles the [ULA/gate array] would delay the CPU for if it were to recognise that contention
return HalfCycles(timings.interrupt_time + interrupt_duration - time_into_frame_); needs to be applied in @c offset half-cycles from now.
} */
HalfCycles access_delay(HalfCycles offset) const {
constexpr auto timings = get_timings();
const int delay_time = (time_into_frame_ + offset.as<int>() + timings.contention_leadin) % (timings.half_cycles_per_line * timings.lines_per_frame);
assert(!(delay_time&1));
// If not, it'll be in the next batch. // Check for a time within the no-contention window.
return timings.interrupt_time + timings.half_cycles_per_line * timings.lines_per_frame - time_into_frame_; if(delay_time >= (191*timings.half_cycles_per_line + timings.contention_duration)) {
return 0;
} }
/*! const int time_into_line = delay_time % timings.half_cycles_per_line;
@returns The current state of the interrupt output. if(time_into_line >= timings.contention_duration) {
*/ return 0;
bool get_interrupt_line() const {
constexpr auto timings = get_timings();
return time_into_frame_ >= timings.interrupt_time && time_into_frame_ < timings.interrupt_time + interrupt_duration;
} }
/*! return HalfCycles(timings.delays[(time_into_line >> 1) & 7]);
@returns How many cycles the [ULA/gate array] would delay the CPU for if it were to recognise that contention }
needs to be applied in @c offset half-cycles from now.
*/
HalfCycles access_delay(HalfCycles offset) const {
constexpr auto timings = get_timings();
const int delay_time = (time_into_frame_ + offset.as<int>() + timings.contention_leadin) % (timings.half_cycles_per_line * timings.lines_per_frame);
assert(!(delay_time&1));
// Check for a time within the no-contention window. /*!
if(delay_time >= (191*timings.half_cycles_per_line + timings.contention_duration)) { @returns Whatever the ULA or gate array would expose via the floating bus, this cycle.
return 0; */
} uint8_t get_floating_value() const {
constexpr auto timings = get_timings();
const uint8_t out_of_bounds = (timing == Timing::Plus3) ? last_contended_access_ : 0xff;
const int time_into_line = delay_time % timings.half_cycles_per_line; const int line = time_into_frame_ / timings.half_cycles_per_line;
if(time_into_line >= timings.contention_duration) { if(line >= 192) {
return 0; return out_of_bounds;
}
return HalfCycles(timings.delays[(time_into_line >> 1) & 7]);
} }
/*! const int time_into_line = time_into_frame_ % timings.half_cycles_per_line;
@returns Whatever the ULA or gate array would expose via the floating bus, this cycle. if(time_into_line >= 256 || (time_into_line&8)) {
*/ return out_of_bounds;
uint8_t get_floating_value() const {
constexpr auto timings = get_timings();
const uint8_t out_of_bounds = (timing == Timing::Plus3) ? last_contended_access_ : 0xff;
const int line = time_into_frame_ / timings.half_cycles_per_line;
if(line >= 192) {
return out_of_bounds;
}
const int time_into_line = time_into_frame_ % timings.half_cycles_per_line;
if(time_into_line >= 256 || (time_into_line&8)) {
return out_of_bounds;
}
// The +2a and +3 always return the low bit as set.
const uint8_t value = last_fetches_[(time_into_line >> 1) & 3];
if constexpr (timing == Timing::Plus3) {
return value | 1;
}
return value;
} }
/*! // The +2a and +3 always return the low bit as set.
Relevant to the +2a and +3 only, sets the most recent value read from or const uint8_t value = last_fetches_[(time_into_line >> 1) & 3];
written to contended memory. This is what will be returned if the floating if constexpr (timing == Timing::Plus3) {
bus is accessed when the gate array isn't currently reading. return value | 1;
*/
void set_last_contended_area_access([[maybe_unused]] uint8_t value) {
if constexpr (timing == Timing::Plus3) {
last_contended_access_ = value | 1;
}
} }
return value;
}
/*! /*!
Sets the current border colour. Relevant to the +2a and +3 only, sets the most recent value read from or
*/ written to contended memory. This is what will be returned if the floating
void set_border_colour(uint8_t colour) { bus is accessed when the gate array isn't currently reading.
border_byte_ = colour; */
border_colour_ = palette[colour]; void set_last_contended_area_access([[maybe_unused]] uint8_t value) {
if constexpr (timing == Timing::Plus3) {
last_contended_access_ = value | 1;
} }
}
/// Sets the scan target. /*!
void set_scan_target(Outputs::Display::ScanTarget *scan_target) { Sets the current border colour.
crt_.set_scan_target(scan_target); */
} void set_border_colour(uint8_t colour) {
border_byte_ = colour;
border_colour_ = palette[colour];
}
/// Gets the current scan status. /// Sets the scan target.
Outputs::Display::ScanStatus get_scaled_scan_status() const { void set_scan_target(Outputs::Display::ScanTarget *scan_target) {
return crt_.get_scaled_scan_status(); crt_.set_scan_target(scan_target);
} }
/*! Sets the type of display the CRT will request. */ /// Gets the current scan status.
void set_display_type(Outputs::Display::DisplayType type) { Outputs::Display::ScanStatus get_scaled_scan_status() const {
crt_.set_display_type(type); return crt_.get_scaled_scan_status();
} }
/*! Gets the display type. */ /*! Sets the type of display the CRT will request. */
Outputs::Display::DisplayType get_display_type() const { void set_display_type(Outputs::Display::DisplayType type) {
return crt_.get_display_type(); crt_.set_display_type(type);
} }
private: /*! Gets the display type. */
int time_into_frame_ = 0; Outputs::Display::DisplayType get_display_type() const {
Outputs::CRT::CRT crt_; return crt_.get_display_type();
const uint8_t *memory_ = nullptr; }
uint8_t border_colour_ = 0;
uint8_t border_byte_ = 0;
uint8_t *pixel_target_ = nullptr; private:
int attribute_address_ = 0; int time_into_frame_ = 0;
int pixel_address_ = 0; Outputs::CRT::CRT crt_;
const uint8_t *memory_ = nullptr;
uint8_t border_colour_ = 0;
uint8_t border_byte_ = 0;
uint8_t flash_mask_ = 0; uint8_t *pixel_target_ = nullptr;
int flash_counter_ = 0; int attribute_address_ = 0;
bool is_alternate_line_ = false; int pixel_address_ = 0;
uint8_t last_fetches_[4] = {0xff, 0xff, 0xff, 0xff}; uint8_t flash_mask_ = 0;
uint8_t last_contended_access_ = 0xff; int flash_counter_ = 0;
bool is_alternate_line_ = false;
friend struct State; uint8_t last_fetches_[4] = {0xff, 0xff, 0xff, 0xff};
uint8_t last_contended_access_ = 0xff;
#define RGB(r, g, b) (r << 4) | (g << 2) | b friend struct State;
static constexpr uint8_t palette[] = {
RGB(0, 0, 0), RGB(0, 0, 2), RGB(2, 0, 0), RGB(2, 0, 2), static constexpr uint8_t RGB(const uint8_t r, const uint8_t g, const uint8_t b) {
RGB(0, 2, 0), RGB(0, 2, 2), RGB(2, 2, 0), RGB(2, 2, 2), return uint8_t((r << 4) | (g << 2) | b);
RGB(0, 0, 0), RGB(0, 0, 3), RGB(3, 0, 0), RGB(3, 0, 3), }
RGB(0, 3, 0), RGB(0, 3, 3), RGB(3, 3, 0), RGB(3, 3, 3), static constexpr uint8_t palette[] = {
}; RGB(0, 0, 0), RGB(0, 0, 2), RGB(2, 0, 0), RGB(2, 0, 2),
#undef RGB RGB(0, 2, 0), RGB(0, 2, 2), RGB(2, 2, 0), RGB(2, 2, 2),
RGB(0, 0, 0), RGB(0, 0, 3), RGB(3, 0, 0), RGB(3, 0, 3),
RGB(0, 3, 0), RGB(0, 3, 3), RGB(3, 3, 0), RGB(3, 3, 3),
};
}; };
struct State: public Reflection::StructImpl<State> { struct State: public Reflection::StructImpl<State> {

View File

@@ -22,37 +22,37 @@ namespace Utility {
necessary to type that character on a given machine. necessary to type that character on a given machine.
*/ */
class CharacterMapper { class CharacterMapper {
public: public:
virtual ~CharacterMapper() = default; virtual ~CharacterMapper() = default;
/// @returns The EndSequence-terminated sequence of keys that would cause @c character to be typed. /// @returns The EndSequence-terminated sequence of keys that would cause @c character to be typed.
virtual const uint16_t *sequence_for_character(char character) const = 0; virtual const uint16_t *sequence_for_character(char character) const = 0;
/// The typer will automatically reset all keys in between each sequence that it types. /// The typer will automatically reset all keys in between each sequence that it types.
/// By default it will pause for one key's duration when doing so. Character mappers /// By default it will pause for one key's duration when doing so. Character mappers
/// can eliminate that pause by overriding this method. /// can eliminate that pause by overriding this method.
/// @returns @c true if the typer should pause after performing a reset; @c false otherwise. /// @returns @c true if the typer should pause after performing a reset; @c false otherwise.
virtual bool needs_pause_after_reset_all_keys() const { return true; } virtual bool needs_pause_after_reset_all_keys() const { return true; }
/// The typer will pause between every entry in a keyboard sequence. On some machines /// The typer will pause between every entry in a keyboard sequence. On some machines
/// that may not be necessary — it'll often depends on whether the machine needs time to /// that may not be necessary — it'll often depends on whether the machine needs time to
/// observe a modifier like shift before it sees the actual keypress. /// observe a modifier like shift before it sees the actual keypress.
/// @returns @c true if the typer should pause after forwarding @c key; @c false otherwise. /// @returns @c true if the typer should pause after forwarding @c key; @c false otherwise.
virtual bool needs_pause_after_key([[maybe_unused]] uint16_t key) const { return true; } virtual bool needs_pause_after_key([[maybe_unused]] uint16_t key) const { return true; }
protected: protected:
using KeySequence = std::array<uint16_t, 16>; using KeySequence = std::array<uint16_t, 16>;
/*! /*!
Provided in the base class as a convenience: given the C array of key sequences @c sequences, Provided in the base class as a convenience: given the C array of key sequences @c sequences,
returns the sequence for character @c character if it exists; otherwise returns @c nullptr. returns the sequence for character @c character if it exists; otherwise returns @c nullptr.
*/ */
template <typename Collection> const uint16_t *table_lookup_sequence_for_character(const Collection &sequences, char character) const { template <typename Collection> const uint16_t *table_lookup_sequence_for_character(const Collection &sequences, char character) const {
std::size_t ucharacter = size_t((unsigned char)character); std::size_t ucharacter = size_t((unsigned char)character);
if(ucharacter >= sizeof(sequences) / sizeof(KeySequence)) return nullptr; if(ucharacter >= sizeof(sequences) / sizeof(KeySequence)) return nullptr;
if(sequences[ucharacter][0] == MachineTypes::MappedKeyboardMachine::KeyNotMapped) return nullptr; if(sequences[ucharacter][0] == MachineTypes::MappedKeyboardMachine::KeyNotMapped) return nullptr;
return sequences[ucharacter].data(); return sequences[ucharacter].data();
} }
}; };
/*! /*!

View File

@@ -56,45 +56,45 @@ template <Action action, typename IteratorT, typename SampleT> void fill(Iterato
*/ */
template <typename SourceT, bool stereo> template <typename SourceT, bool stereo>
class BufferSource { class BufferSource {
public: public:
/*! /*!
Indicates whether this component will write stereo samples. Indicates whether this component will write stereo samples.
*/ */
static constexpr bool is_stereo = stereo; static constexpr bool is_stereo = stereo;
/*! /*!
Should 'apply' the next @c number_of_samples to @c target ; application means applying @c action which can be achieved either via the Should 'apply' the next @c number_of_samples to @c target ; application means applying @c action which can be achieved either via the
helper functions above — @c apply and @c fill — or by semantic inspection (primarily, if an obvious quick route for @c Action::Ignore is available). helper functions above — @c apply and @c fill — or by semantic inspection (primarily, if an obvious quick route for @c Action::Ignore is available).
No default implementation is provided. No default implementation is provided.
*/ */
template <Action action> template <Action action>
void apply_samples(std::size_t number_of_samples, typename SampleT<stereo>::type *target); void apply_samples(std::size_t number_of_samples, typename SampleT<stereo>::type *target);
/*! /*!
@returns @c true if it is trivially true that a call to get_samples would just @returns @c true if it is trivially true that a call to get_samples would just
fill the target with zeroes; @c false if a call might return all zeroes or fill the target with zeroes; @c false if a call might return all zeroes or
might not. might not.
*/ */
// bool is_zero_level() const { return false; } // bool is_zero_level() const { return false; }
/*! /*!
Sets the proper output range for this sample source; it should write values Sets the proper output range for this sample source; it should write values
between 0 and volume. between 0 and volume.
*/ */
// void set_sample_volume_range(std::int16_t volume); // void set_sample_volume_range(std::int16_t volume);
/*! /*!
Permits a sample source to declare that, averaged over time, it will use only Permits a sample source to declare that, averaged over time, it will use only
a certain proportion of the allocated volume range. This commonly happens a certain proportion of the allocated volume range. This commonly happens
in sample sources that use a time-multiplexed sound output — for example, if in sample sources that use a time-multiplexed sound output — for example, if
one were to output only every other sample then it would return 0.5. one were to output only every other sample then it would return 0.5.
This is permitted to vary over time but there is no contract as to when it will be This is permitted to vary over time but there is no contract as to when it will be
used by a speaker. If it varies, it should do so very infrequently and only to used by a speaker. If it varies, it should do so very infrequently and only to
represent changes in hardware configuration. represent changes in hardware configuration.
*/ */
double average_output_peak() const { return 1.0; } double average_output_peak() const { return 1.0; }
}; };
/// ///