mirror of
https://github.com/TomHarte/CLK.git
synced 2025-11-03 09:16:11 +00:00
352 lines
12 KiB
C++
352 lines
12 KiB
C++
//
|
|
// SAA5050.cpp
|
|
// Clock Signal
|
|
//
|
|
// Created by Thomas Harte on 24/09/2025.
|
|
// Copyright © 2025 Thomas Harte. All rights reserved.
|
|
//
|
|
|
|
#include "SAA5050.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <cstdint>
|
|
|
|
namespace {
|
|
// SAA5050 font, padded out to one byte per row. The least-significant five bits of each byte
|
|
// are the meaningful pixels for that row, with the LSB being on the right.
|
|
constexpr uint8_t font[][10] = {
|
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, // Character 32.
|
|
{0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x04, 0x00, 0x00, },
|
|
{0x00, 0x0a, 0x0a, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
|
|
{0x00, 0x06, 0x09, 0x08, 0x1c, 0x08, 0x08, 0x1f, 0x00, 0x00, },
|
|
{0x00, 0x0e, 0x15, 0x14, 0x0e, 0x05, 0x15, 0x0e, 0x00, 0x00, },
|
|
{0x00, 0x18, 0x19, 0x02, 0x04, 0x08, 0x13, 0x03, 0x00, 0x00, },
|
|
{0x00, 0x08, 0x14, 0x14, 0x08, 0x15, 0x12, 0x0d, 0x00, 0x00, },
|
|
{0x00, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
|
|
{0x00, 0x02, 0x04, 0x08, 0x08, 0x08, 0x04, 0x02, 0x00, 0x00, },
|
|
{0x00, 0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08, 0x00, 0x00, },
|
|
{0x00, 0x04, 0x15, 0x0e, 0x04, 0x0e, 0x15, 0x04, 0x00, 0x00, },
|
|
{0x00, 0x00, 0x04, 0x04, 0x1f, 0x04, 0x04, 0x00, 0x00, 0x00, },
|
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x08, 0x00, },
|
|
{0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, },
|
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, },
|
|
{0x00, 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x00, 0x00, 0x00, },
|
|
{0x00, 0x04, 0x0a, 0x11, 0x11, 0x11, 0x0a, 0x04, 0x00, 0x00, },
|
|
{0x00, 0x04, 0x0c, 0x04, 0x04, 0x04, 0x04, 0x0e, 0x00, 0x00, },
|
|
{0x00, 0x0e, 0x11, 0x01, 0x06, 0x08, 0x10, 0x1f, 0x00, 0x00, },
|
|
{0x00, 0x1f, 0x01, 0x02, 0x06, 0x01, 0x11, 0x0e, 0x00, 0x00, },
|
|
{0x00, 0x02, 0x06, 0x0a, 0x12, 0x1f, 0x02, 0x02, 0x00, 0x00, },
|
|
{0x00, 0x1f, 0x10, 0x1e, 0x01, 0x01, 0x11, 0x0e, 0x00, 0x00, },
|
|
{0x00, 0x06, 0x08, 0x10, 0x1e, 0x11, 0x11, 0x0e, 0x00, 0x00, },
|
|
{0x00, 0x1f, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08, 0x00, 0x00, },
|
|
{0x00, 0x0e, 0x11, 0x11, 0x0e, 0x11, 0x11, 0x0e, 0x00, 0x00, },
|
|
{0x00, 0x0e, 0x11, 0x11, 0x0f, 0x01, 0x02, 0x0c, 0x00, 0x00, },
|
|
{0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, },
|
|
{0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x04, 0x08, 0x00, },
|
|
{0x00, 0x02, 0x04, 0x08, 0x10, 0x08, 0x04, 0x02, 0x00, 0x00, },
|
|
{0x00, 0x00, 0x00, 0x1f, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, },
|
|
{0x00, 0x08, 0x04, 0x02, 0x01, 0x02, 0x04, 0x08, 0x00, 0x00, },
|
|
{0x00, 0x0e, 0x11, 0x02, 0x04, 0x04, 0x00, 0x04, 0x00, 0x00, },
|
|
{0x00, 0x0e, 0x11, 0x17, 0x15, 0x17, 0x10, 0x0e, 0x00, 0x00, },
|
|
{0x00, 0x04, 0x0a, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x00, 0x00, },
|
|
{0x00, 0x1e, 0x11, 0x11, 0x1e, 0x11, 0x11, 0x1e, 0x00, 0x00, },
|
|
{0x00, 0x0e, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0e, 0x00, 0x00, },
|
|
{0x00, 0x1e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, },
|
|
{0x00, 0x1f, 0x10, 0x10, 0x1e, 0x10, 0x10, 0x1f, 0x00, 0x00, },
|
|
{0x00, 0x1f, 0x10, 0x10, 0x1e, 0x10, 0x10, 0x10, 0x00, 0x00, },
|
|
{0x00, 0x0e, 0x11, 0x10, 0x10, 0x13, 0x11, 0x0f, 0x00, 0x00, },
|
|
{0x00, 0x11, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, },
|
|
{0x00, 0x0e, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0e, 0x00, 0x00, },
|
|
{0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x11, 0x0e, 0x00, 0x00, },
|
|
{0x00, 0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11, 0x00, 0x00, },
|
|
{0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1f, 0x00, 0x00, },
|
|
{0x00, 0x11, 0x1b, 0x15, 0x15, 0x11, 0x11, 0x11, 0x00, 0x00, },
|
|
{0x00, 0x11, 0x11, 0x19, 0x15, 0x13, 0x11, 0x11, 0x00, 0x00, },
|
|
{0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, },
|
|
{0x00, 0x1e, 0x11, 0x11, 0x1e, 0x10, 0x10, 0x10, 0x00, 0x00, },
|
|
{0x00, 0x0e, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0d, 0x00, 0x00, },
|
|
{0x00, 0x1e, 0x11, 0x11, 0x1e, 0x14, 0x12, 0x11, 0x00, 0x00, },
|
|
{0x00, 0x0e, 0x11, 0x10, 0x0e, 0x01, 0x11, 0x0e, 0x00, 0x00, },
|
|
{0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, },
|
|
{0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, },
|
|
{0x00, 0x11, 0x11, 0x11, 0x0a, 0x0a, 0x04, 0x04, 0x00, 0x00, },
|
|
{0x00, 0x11, 0x11, 0x11, 0x15, 0x15, 0x15, 0x0a, 0x00, 0x00, },
|
|
{0x00, 0x11, 0x11, 0x0a, 0x04, 0x0a, 0x11, 0x11, 0x00, 0x00, },
|
|
{0x00, 0x11, 0x11, 0x0a, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, },
|
|
{0x00, 0x1f, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1f, 0x00, 0x00, },
|
|
{0x00, 0x00, 0x04, 0x08, 0x1f, 0x08, 0x04, 0x00, 0x00, 0x00, },
|
|
{0x00, 0x10, 0x10, 0x10, 0x10, 0x16, 0x01, 0x02, 0x04, 0x07, },
|
|
{0x00, 0x00, 0x04, 0x02, 0x1f, 0x02, 0x04, 0x00, 0x00, 0x00, },
|
|
{0x00, 0x00, 0x04, 0x0e, 0x15, 0x04, 0x04, 0x00, 0x00, 0x00, },
|
|
{0x00, 0x0a, 0x0a, 0x1f, 0x0a, 0x1f, 0x0a, 0x0a, 0x00, 0x00, },
|
|
{0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, },
|
|
{0x00, 0x00, 0x00, 0x0e, 0x01, 0x0f, 0x11, 0x0f, 0x00, 0x00, },
|
|
{0x00, 0x10, 0x10, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, },
|
|
{0x00, 0x00, 0x00, 0x0f, 0x10, 0x10, 0x10, 0x0f, 0x00, 0x00, },
|
|
{0x00, 0x01, 0x01, 0x0f, 0x11, 0x11, 0x11, 0x0f, 0x00, 0x00, },
|
|
{0x00, 0x00, 0x00, 0x0e, 0x11, 0x1f, 0x10, 0x0e, 0x00, 0x00, },
|
|
{0x00, 0x02, 0x04, 0x04, 0x0e, 0x04, 0x04, 0x04, 0x00, 0x00, },
|
|
{0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x0f, 0x01, 0x0e, },
|
|
{0x00, 0x10, 0x10, 0x1e, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, },
|
|
{0x00, 0x04, 0x00, 0x0c, 0x04, 0x04, 0x04, 0x0e, 0x00, 0x00, },
|
|
{0x00, 0x04, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, },
|
|
{0x00, 0x08, 0x08, 0x09, 0x0a, 0x0c, 0x0a, 0x09, 0x00, 0x00, },
|
|
{0x00, 0x0c, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0e, 0x00, 0x00, },
|
|
{0x00, 0x00, 0x00, 0x1a, 0x15, 0x15, 0x15, 0x15, 0x00, 0x00, },
|
|
{0x00, 0x00, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, },
|
|
{0x00, 0x00, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, },
|
|
{0x00, 0x00, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x10, },
|
|
{0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x0f, 0x01, 0x01, },
|
|
{0x00, 0x00, 0x00, 0x0b, 0x0c, 0x08, 0x08, 0x08, 0x00, 0x00, },
|
|
{0x00, 0x00, 0x00, 0x0f, 0x10, 0x0e, 0x01, 0x1e, 0x00, 0x00, },
|
|
{0x00, 0x04, 0x04, 0x0e, 0x04, 0x04, 0x04, 0x02, 0x00, 0x00, },
|
|
{0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x0f, 0x00, 0x00, },
|
|
{0x00, 0x00, 0x00, 0x11, 0x11, 0x0a, 0x0a, 0x04, 0x00, 0x00, },
|
|
{0x00, 0x00, 0x00, 0x11, 0x11, 0x15, 0x15, 0x0a, 0x00, 0x00, },
|
|
{0x00, 0x00, 0x00, 0x11, 0x0a, 0x04, 0x0a, 0x11, 0x00, 0x00, },
|
|
{0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x0f, 0x01, 0x0e, },
|
|
{0x00, 0x00, 0x00, 0x1f, 0x02, 0x04, 0x08, 0x1f, 0x00, 0x00, },
|
|
{0x00, 0x10, 0x10, 0x10, 0x10, 0x11, 0x03, 0x05, 0x07, 0x01, },
|
|
{0x00, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x00, 0x00, },
|
|
{0x00, 0x18, 0x04, 0x18, 0x04, 0x19, 0x03, 0x05, 0x07, 0x01, },
|
|
{0x00, 0x00, 0x04, 0x00, 0x1f, 0x00, 0x04, 0x00, 0x00, 0x00, },
|
|
{0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, },
|
|
};
|
|
|
|
enum ControlCode: uint8_t {
|
|
RedAlpha = 0x01,
|
|
GreenAlpha = 0x02,
|
|
YellowAlpha = 0x03,
|
|
BlueAlpha = 0x04,
|
|
MagentaAlpha = 0x05,
|
|
CyanAlpha = 0x06,
|
|
WhiteAlpha = 0x07,
|
|
|
|
Flash = 0x08,
|
|
Steady = 0x09,
|
|
|
|
RedGraphics = 0x11,
|
|
GreenGraphics = 0x12,
|
|
YellowGraphics = 0x13,
|
|
BlueGraphics = 0x14,
|
|
MagentaGraphics = 0x15,
|
|
CyanGraphics = 0x16,
|
|
WhiteGraphics = 0x17,
|
|
|
|
Conceal = 0x18,
|
|
|
|
ContinuousGraphics = 0x19,
|
|
SeparatedGraphics = 0x1a,
|
|
|
|
NormalHeight = 0xc,
|
|
DoubleHeight = 0xd,
|
|
|
|
BlackBackground = 0x1c,
|
|
NewBackground = 0x1d,
|
|
|
|
HoldGraphics = 0x1e,
|
|
ReleaseGraphics = 0x1f,
|
|
};}
|
|
|
|
using namespace Mullard;
|
|
|
|
void SAA5050Serialiser::begin_frame(const bool is_odd) {
|
|
line_ = -2;
|
|
row_ = 0;
|
|
odd_frame_ = is_odd;
|
|
|
|
row_has_double_height_ = false;
|
|
double_height_offset_ = 0;
|
|
|
|
++frame_counter_;
|
|
}
|
|
|
|
void SAA5050Serialiser::begin_line() {
|
|
line_ += 2;
|
|
if(line_ == 20) {
|
|
line_ = 0;
|
|
++row_;
|
|
|
|
if(row_has_double_height_) {
|
|
double_height_offset_ = (double_height_offset_ + 5) % 10;
|
|
}
|
|
row_has_double_height_ = false;
|
|
}
|
|
|
|
output_.reset();
|
|
has_output_ = false;
|
|
|
|
apply_control(ControlCode::WhiteAlpha);
|
|
apply_control(ControlCode::Steady);
|
|
apply_control(ControlCode::NormalHeight);
|
|
apply_control(ControlCode::ContinuousGraphics);
|
|
apply_control(ControlCode::BlackBackground);
|
|
apply_control(ControlCode::ReleaseGraphics);
|
|
}
|
|
|
|
bool SAA5050Serialiser::has_output() const {
|
|
return has_output_;
|
|
}
|
|
|
|
SAA5050Serialiser::Output SAA5050Serialiser::output() {
|
|
has_output_ = false;
|
|
return output_;
|
|
}
|
|
|
|
void SAA5050Serialiser::apply_control(const uint8_t value) {
|
|
const auto set_alpha = [&](const uint8_t colour) {
|
|
alpha_mode_ = true;
|
|
conceal_ = false;
|
|
output_.alpha = colour;
|
|
hold_graphics_ = false;
|
|
};
|
|
|
|
const auto set_graphics = [&](const uint8_t colour) {
|
|
alpha_mode_ = false;
|
|
conceal_ = false;
|
|
output_.alpha = colour;
|
|
hold_graphics_ = false;
|
|
};
|
|
|
|
switch(value) {
|
|
default: break;
|
|
|
|
case RedAlpha: set_alpha(0b100); break;
|
|
case GreenAlpha: set_alpha(0b010); break;
|
|
case YellowAlpha: set_alpha(0b110); break;
|
|
case BlueAlpha: set_alpha(0b001); break;
|
|
case MagentaAlpha: set_alpha(0b101); break;
|
|
case CyanAlpha: set_alpha(0b011); break;
|
|
case WhiteAlpha: set_alpha(0b111); break;
|
|
|
|
case Flash: flash_ = true; break;
|
|
case Steady: flash_ = false; break;
|
|
|
|
case RedGraphics: set_graphics(0b100); break;
|
|
case GreenGraphics: set_graphics(0b010); break;
|
|
case YellowGraphics: set_graphics(0b110); break;
|
|
case BlueGraphics: set_graphics(0b001); break;
|
|
case MagentaGraphics: set_graphics(0b101); break;
|
|
case CyanGraphics: set_graphics(0b011); break;
|
|
case WhiteGraphics: set_graphics(0b111); break;
|
|
|
|
case Conceal: conceal_ = true; break;
|
|
|
|
case ContinuousGraphics: separated_graphics_ = false; break;
|
|
case SeparatedGraphics: separated_graphics_ = true; break;
|
|
|
|
case NormalHeight: double_height_ = false; break;
|
|
case DoubleHeight: double_height_ = row_has_double_height_ = true; break;
|
|
|
|
case BlackBackground: output_.background = 0; break;
|
|
case NewBackground: output_.background = output_.alpha; break;
|
|
|
|
case HoldGraphics: hold_graphics_ = true; break;
|
|
case ReleaseGraphics: hold_graphics_ = false; last_graphic_ = 32; break;
|
|
}
|
|
}
|
|
|
|
void SAA5050Serialiser::set_reveal(const bool reveal) {
|
|
reveal_ = reveal;
|
|
}
|
|
|
|
void SAA5050Serialiser::add(const Numeric::SizedInt<7> c) {
|
|
has_output_ = true;
|
|
if(c.get() < 32) {
|
|
if(hold_graphics_) {
|
|
load_pixels(last_graphic_);
|
|
} else {
|
|
output_.reset();
|
|
}
|
|
apply_control(c.get());
|
|
return;
|
|
}
|
|
load_pixels(c.get());
|
|
}
|
|
|
|
void SAA5050Serialiser::load_pixels(const uint8_t c) {
|
|
if(flash_ && ((frame_counter_&31) > 23)) { // Complete guess on the blink period here.
|
|
output_.reset();
|
|
return;
|
|
}
|
|
|
|
if(conceal_ && !reveal_) {
|
|
output_.reset();
|
|
return;
|
|
}
|
|
|
|
// Divert into graphics only if both the mode and the character code allows it.
|
|
if(!alpha_mode_ && (c & (1 << 5))) {
|
|
last_graphic_ = c;
|
|
|
|
// Graphics layout:
|
|
//
|
|
// |----|----|
|
|
// | | |
|
|
// | b0 | b1 |
|
|
// | | |
|
|
// |----|----|
|
|
// | | |
|
|
// | b2 | b3 |
|
|
// | | |
|
|
// |----|----|
|
|
// | | |
|
|
// | b4 | b6 |
|
|
// | | |
|
|
// |----|----|
|
|
|
|
if(separated_graphics_ && (line_ == 6 || line_ == 12 || line_ == 18)) {
|
|
output_.reset();
|
|
return;
|
|
}
|
|
|
|
uint8_t pixels;
|
|
if(line_ < 6) {
|
|
pixels =
|
|
((c & 1) ? 0b111'000 : 0) |
|
|
((c & 2) ? 0b000'111 : 0);
|
|
} else if(line_ < 14) {
|
|
pixels =
|
|
((c & 4) ? 0b111'000 : 0) |
|
|
((c & 8) ? 0b000'111 : 0);
|
|
} else {
|
|
pixels =
|
|
((c & 16) ? 0b111'000 : 0) |
|
|
((c & 64) ? 0b000'111 : 0);
|
|
}
|
|
|
|
if(separated_graphics_) {
|
|
pixels &= 0b011'011;
|
|
}
|
|
|
|
output_.load(pixels);
|
|
return;
|
|
}
|
|
|
|
if(double_height_) {
|
|
const auto top_address = (line_ >> 2) + double_height_offset_;
|
|
const uint8_t top = font[c - 32][top_address];
|
|
const uint8_t bottom = font[c - 32][std::min(9, top_address + 1)];
|
|
|
|
if(line_ & 2) {
|
|
output_.load(bottom, top);
|
|
} else {
|
|
output_.load(top, bottom);
|
|
}
|
|
} else {
|
|
if(double_height_offset_) {
|
|
output_.reset();
|
|
} else {
|
|
const auto top_address = line_ >> 1;
|
|
const uint8_t top = font[c - 32][top_address];
|
|
const uint8_t bottom = font[c - 32][std::min(9, top_address + 1)];
|
|
|
|
if(odd_frame_) {
|
|
output_.load(bottom, top);
|
|
} else {
|
|
output_.load(top, bottom);
|
|
}
|
|
}
|
|
}
|
|
}
|