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

Compare commits

..

3 Commits
master ... Tube

Author SHA1 Message Date
Thomas Harte
71d5c58577 Introduce one-directional FIFO. 2025-10-30 22:23:01 -04:00
Thomas Harte
e0673e97a5 Slightly clean-up spacing. 2025-10-30 22:22:47 -04:00
Thomas Harte
746836c8ea Add Tube boot ROM. 2025-10-30 22:22:25 -04:00
7 changed files with 125 additions and 86 deletions

View File

@@ -831,12 +831,12 @@ public:
}
adc_.run_for(duration);
if constexpr (has_1770) {
// The WD1770 is nominally clocked at 8Mhz.
wd1770_.run_for(duration * 4);
}
//
// Questionably-clocked devices.
//
@@ -943,6 +943,7 @@ public:
return duration;
}
//
// ROM or RAM access.
//

View File

@@ -0,0 +1,37 @@
//
// Header.hpp
// Clock Signal
//
// Created by Thomas Harte on 30/10/2025.
// Copyright © 2025 Thomas Harte. All rights reserved.
//
#pragma once
namespace Acorn::Tube {
template <size_t length>
struct FIFO {
uint8_t status() const {
return
((read != write) ? 0x80 : 0x00) |
((write - read < length) ? 0x40 : 0x00);
}
void write(const uint8_t value) {
if(write - read == length) return;
buffer[write++] = value;
}
void read() {
const uint8_t result = buffer[read];
if(write != read) ++read;
}
private:
std::array<uint8_t, length> buffer;
uint32_t read = 0;
uint32_t write = 0;
};
}

View File

@@ -453,6 +453,14 @@ const std::vector<Description> &Description::all_roms() {
16_kb,
0x8314fed0u
},
{
BBCMicroTube110,
"BBCMicro",
"the Tube 1.10 Boot ROM",
"TUBE110.rom",
2_kb,
0x9ec2dbd0u
},
//
// ColecoVision.

View File

@@ -85,6 +85,7 @@ enum Name {
BBCMicroDFS226,
BBCMicroADFS130,
BBCMicroAdvancedDiscToolkit140,
BBCMicroTube110,
// ColecoVision.
ColecoVisionBIOS,

View File

@@ -1612,6 +1612,7 @@
4B49F0A823346F7A0045E6A6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/MacintoshOptions.xib"; sourceTree = SOURCE_ROOT; };
4B4A75BC2EB2C55100EA398F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/CompositeDynamicCropOptions.xib; sourceTree = "<group>"; };
4B4A75BF2EB399D700EA398F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/DynamicCropOptions.xib; sourceTree = "<group>"; };
4B4A75C22EB43F1C00EA398F /* FIFO.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = FIFO.hpp; sourceTree = "<group>"; };
4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AY38910.cpp; sourceTree = "<group>"; };
4B4A762F1DB1A3FA007AAE2E /* AY38910.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AY38910.hpp; sourceTree = "<group>"; };
4B4B1A3A200198C900A0F866 /* KonamiSCC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KonamiSCC.cpp; sourceTree = "<group>"; };
@@ -3354,6 +3355,14 @@
path = uPD7002;
sourceTree = "<group>";
};
4B4A75C32EB43F1C00EA398F /* Tube */ = {
isa = PBXGroup;
children = (
4B4A75C22EB43F1C00EA398F /* FIFO.hpp */,
);
path = Tube;
sourceTree = "<group>";
};
4B4A762D1DB1A35C007AAE2E /* AY38910 */ = {
isa = PBXGroup;
children = (
@@ -4673,6 +4682,7 @@
4BB505682B962DDF0031C43C /* Acorn */ = {
isa = PBXGroup;
children = (
4B4A75C32EB43F1C00EA398F /* Tube */,
4BB505692B962DDF0031C43C /* Archimedes */,
4B710C8C2E77A3B20056BDF4 /* BBCMicro */,
4BB5056A2B962DDF0031C43C /* Electron */,

View File

@@ -113,20 +113,16 @@ void CRT::set_dynamic_framing(
float minimum_scale
) {
framing_ = Framing::Dynamic;
framing_bounds_ = initial;
dynamic_framer_.framing_bounds = initial;
dynamic_framer_.framing_bounds.scale(
maximum_scale / dynamic_framer_.framing_bounds.size.width,
maximum_scale / dynamic_framer_.framing_bounds.size.height
);
framing_bounds_ = initial;
framing_bounds_.scale(maximum_scale / framing_bounds_.size.width, maximum_scale / framing_bounds_.size.height);
dynamic_framer_.minimum_scale = minimum_scale;
dynamic_framer_.max_offsets[0] = max_centre_offset_x;
dynamic_framer_.max_offsets[1] = max_centre_offset_y;
minimum_scale_ = minimum_scale;
max_offsets_[0] = max_centre_offset_x;
max_offsets_[1] = max_centre_offset_y;
if(!has_first_reading_) {
previous_posted_rect_ = posted_rect_ = scan_target_modals_.visible_area = initial;
}
posted_rect_ = scan_target_modals_.visible_area = initial;
has_first_reading_ = true;
}
@@ -139,10 +135,13 @@ void CRT::set_fixed_framing(const std::function<void()> &advance) {
void CRT::set_fixed_framing(const Display::Rect frame) {
framing_ = Framing::Static;
static_frame_ = frame;
if(!has_first_reading_) {
scan_target_modals_.visible_area = frame;
scan_target_->set_modals(scan_target_modals_);
} else {
previous_posted_rect_ = posted_rect_;
posted_rect_ = frame;
animation_step_ = 0;
}
}
@@ -294,7 +293,7 @@ void CRT::advance_cycles(
if(next_scan) {
next_scan->end_points[0] = end_point();
next_scan->composite_amplitude = colour_burst_amplitude_;
} else if(is_output_segment /* && is_calibrating(framing_) */) {
} else if(is_output_segment && is_calibrating(framing_)) {
start_point = end_point();
}
@@ -314,7 +313,7 @@ void CRT::advance_cycles(
vertical_flywheel_.apply_event(next_run_length, active_vertical_event);
if(active_vertical_event == Flywheel::SyncEvent::StartRetrace) {
/* if(is_calibrating(framing_)) */ {
if(is_calibrating(framing_)) {
active_rect_.origin.x /= scan_target_modals_.output_scale.x;
active_rect_.size.width /= scan_target_modals_.output_scale.x;
active_rect_.origin.y /= scan_target_modals_.output_scale.y;
@@ -342,7 +341,7 @@ void CRT::advance_cycles(
}
level_changes_in_frame_ = 0;
/* if(is_calibrating(framing_)) */ {
if(is_calibrating(framing_)) {
border_rect_ = active_rect_ = Display::Rect(65536.0f, 65536.0f, 0.0f, 0.0f);
captures_in_rect_ = 0;
}
@@ -359,9 +358,9 @@ void CRT::advance_cycles(
if(next_scan) {
next_scan->end_points[1] = end_point();
posit_scan(next_scan->end_points[0], next_scan->end_points[1]);
if(is_calibrating(framing_)) posit_scan(next_scan->end_points[0], next_scan->end_points[1]);
scan_target_->end_scan();
} else if(is_output_segment) {
} else if(is_output_segment && is_calibrating(framing_)) {
posit_scan(start_point, end_point());
}
@@ -471,22 +470,46 @@ void CRT::posit(Display::Rect rect) {
if(animation_step_ < AnimationSteps) {
set_rect(current_rect());
++animation_step_;
if(animation_step_ == AnimationSteps) {
previous_posted_rect_ = posted_rect_;
if(framing_ == Framing::CalibratingAutomaticFixed) {
framing_ =
border_rect_ != active_rect_ ?
Framing::BorderReactive : Framing::Static;
return;
}
}
}
// Static framing: don't further evaluate.
if(framing_ == Framing::Static) {
return;
}
// Zoom out very slightly if there's space; this avoids a cramped tight crop.
if(rect.size.width < 0.95 && rect.size.height < 0.95) {
rect.scale(1.02f, 1.02f);
}
std::optional<Display::Rect> first_reading;
// Border reactive: take frame as gospel.
if(framing_ == Framing::BorderReactive) {
if(rect != posted_rect_) {
previous_posted_rect_ = current_rect();
posted_rect_ = rect;
animation_step_ = 0;
}
}
if(!has_first_reading_) {
rect_accumulator_.posit(rect);
if(first_reading = rect_accumulator_.first_reading(); first_reading.has_value()) {
if(const auto reading = rect_accumulator_.first_reading(); reading.has_value()) {
previous_posted_rect_ = posted_rect_;
posted_rect_ = *reading;
animation_step_ = 0;
has_first_reading_ = true;
#ifndef NDEBUG
Logger::info().append("First reading is (%0.5ff, %0.5ff, %0.5ff, %0.5ff)",
posted_rect_.origin.x, posted_rect_.origin.y,
posted_rect_.size.width, posted_rect_.size.height);
@@ -496,40 +519,24 @@ void CRT::posit(Display::Rect rect) {
Logger::info().append("90%% of whole frame was (%0.5ff, %0.5ff, %0.5ff, %0.5ff)",
frame.origin.x, frame.origin.y,
frame.size.width, frame.size.height);
#endif
if(framing_ == Framing::CalibratingAutomaticFixed) {
static_frame_ = *first_reading;
framing_ =
border_rect_ != active_rect_ ?
Framing::BorderReactive : Framing::Static;
return;
}
}
return;
}
// Constrain to permitted bounds.
framing_bounds_.constrain(rect, max_offsets_[0], max_offsets_[1]);
// Constrain to minimum scale.
rect.scale(
rect.size.width > minimum_scale_ ? 1.0f : minimum_scale_ / rect.size.width,
rect.size.height > minimum_scale_ ? 1.0f : minimum_scale_ / rect.size.height
);
const auto output_frame = rect_accumulator_.posit(rect);
dynamic_framer_.update(rect, output_frame, first_reading);
const auto selected_rect = [&]() -> std::optional<Display::Rect> {
switch(framing_) {
default: return rect;
case Framing::Static: return static_frame_;
case Framing::Dynamic: return dynamic_framer_.selection;
}
} ();
if(selected_rect && *selected_rect != posted_rect_) {
if(animation_step_ == NoFrameYet) {
animation_step_ = AnimationSteps;
previous_posted_rect_ = posted_rect_ = *selected_rect;
set_rect(posted_rect_);
} else {
previous_posted_rect_ = current_rect();
posted_rect_ = *selected_rect;
animation_step_ = 0;
}
if(output_frame && *output_frame != posted_rect_) {
previous_posted_rect_ = current_rect();
posted_rect_ = *output_frame;
animation_step_ = 0;
}
}

View File

@@ -404,56 +404,31 @@ private:
Numeric::CubicCurve animation_curve_;
static constexpr int AnimationSteps = 100;
static constexpr int NoFrameYet = AnimationSteps + 1;
int animation_step_ = NoFrameYet;
int animation_step_ = AnimationSteps;
// Configured cropping options.
enum class Framing {
CalibratingAutomaticFixed,
Dynamic,
Static,
BorderReactive,
CalibratingAutomaticFixed,
};
static constexpr bool is_calibrating(const Framing framing) {
return framing < Framing::Static;
}
RectAccumulator rect_accumulator_;
struct DynamicFramer {
// Constraints.
Display::Rect framing_bounds;
float max_offsets[2]{};
float minimum_scale = 0.85f;
// Output.
std::optional<Display::Rect> selection;
// Logic.
void update(
Display::Rect rect,
const std::optional<Display::Rect> accumulated,
const std::optional<Display::Rect> first_reading
) {
// Constrain to permitted bounds.
framing_bounds.constrain(rect, max_offsets[0], max_offsets[1]);
// Constrain to minimum scale.
rect.scale(
rect.size.width > minimum_scale ? 1.0f : minimum_scale / rect.size.width,
rect.size.height > minimum_scale ? 1.0f : minimum_scale / rect.size.height
);
if(accumulated.has_value()) {
selection = accumulated;
} else if(first_reading.has_value()) {
selection = first_reading;
}
}
} dynamic_framer_;
std::optional<Display::Rect> static_frame_;
Framing framing_ = Framing::CalibratingAutomaticFixed;
bool has_first_reading_ = false;
void posit(Display::Rect);
// Affecting dynamic framing.
Outputs::Display::Rect framing_bounds_;
float minimum_scale_ = 0.85f;
float max_offsets_[2]{};
#ifndef NDEBUG
size_t allocated_data_length_ = std::numeric_limits<size_t>::min();
#endif