1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-08-14 07:26:16 +00:00

Merge pull request #1434 from TomHarte/Plus4Startup

Add a simulacrum of C16+4 emulation.
This commit is contained in:
Thomas Harte
2025-01-03 20:11:08 -05:00
committed by GitHub
31 changed files with 2437 additions and 358 deletions

View File

@@ -14,6 +14,7 @@
#include <limits>
#include <vector>
#include <array>
#include <unordered_map>
using namespace Analyser::Static::Commodore;
@@ -37,7 +38,7 @@ public:
@returns a sector if one was found; @c nullptr otherwise.
*/
std::shared_ptr<Sector> sector(const uint8_t track, const uint8_t sector) {
const Sector *sector(const uint8_t track, const uint8_t sector) {
int difference = int(track) - int(track_);
track_ = track;
@@ -68,7 +69,7 @@ private:
int index_count_;
int bit_count_;
uint8_t track_;
std::shared_ptr<Sector> sector_cache_[65536];
std::unordered_map<uint16_t, std::unique_ptr<Sector>> sector_cache_;
void process_input_bit(const int value) override {
shift_register_ = ((shift_register_ << 1) | unsigned(value)) & 0x3ff;
@@ -111,23 +112,24 @@ private:
index_count_++;
}
std::shared_ptr<Sector> get_sector(const uint8_t sector) {
const Sector *get_sector(const uint8_t sector) {
const uint16_t sector_address = uint16_t((track_ << 8) | sector);
if(sector_cache_[sector_address]) return sector_cache_[sector_address];
auto existing = sector_cache_.find(sector_address);
if(existing != sector_cache_.end()) return existing->second.get();
const std::shared_ptr<Sector> first_sector = get_next_sector();
const auto first_sector = get_next_sector();
if(!first_sector) return first_sector;
if(first_sector->sector == sector) return first_sector;
while(1) {
const std::shared_ptr<Sector> next_sector = get_next_sector();
while(true) {
const auto next_sector = get_next_sector();
if(next_sector->sector == first_sector->sector) return nullptr;
if(next_sector->sector == sector) return next_sector;
}
}
std::shared_ptr<Sector> get_next_sector() {
auto sector = std::make_shared<Sector>();
const Sector *get_next_sector() {
auto sector = std::make_unique<Sector>();
const int max_index_count = index_count_ + 2;
while(index_count_ < max_index_count) {
@@ -160,8 +162,8 @@ private:
if(checksum == get_next_byte()) {
uint16_t sector_address = uint16_t((sector->track << 8) | sector->sector);
sector_cache_[sector_address] = sector;
return sector;
auto pair = sector_cache_.emplace(sector_address, std::move(sector));
return pair.first->second.get();
}
}
@@ -171,21 +173,29 @@ private:
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) {
std::vector<File> files;
auto parser = std::make_unique<CommodoreGCRParser>();
parser->set_disk(disk);
CommodoreGCRParser parser;
parser.set_disk(disk);
// Assemble directory.
std::vector<uint8_t> directory;
uint8_t next_track = 18;
uint8_t next_sector = 1;
directory.reserve(20 * 1024); // Probably more than plenty.
std::set<std::pair<uint8_t, uint8_t>> visited;
while(true) {
auto sector = parser->sector(next_track, next_sector);
// Don't be fooled by disks that are encoded with a looping directory.
const auto key = std::make_pair(next_track, next_sector);
if(visited.find(key) != visited.end()) break;
visited.insert(key);
// Append sector to directory and follow next link.
const auto sector = parser.sector(next_track, next_sector);
if(!sector) break;
directory.insert(directory.end(), sector->data.begin(), sector->data.end());
next_track = sector->data[0];
next_sector = sector->data[1];
// Check for end-of-directory.
if(!next_track) break;
}
@@ -222,7 +232,7 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
bool is_first_sector = true;
while(next_track) {
auto sector = parser->sector(next_track, next_sector);
const auto sector = parser.sector(next_track, next_sector);
if(!sector) break;
next_track = sector->data[0];

View File

@@ -20,6 +20,7 @@
#include <algorithm>
#include <cstring>
#include <optional>
#include <sstream>
#include <unordered_set>
@@ -108,7 +109,7 @@ std::optional<BASICAnalysis> analyse(const File &file) {
while(true) {
// Analysis has failed if there isn't at least one complete BASIC line from here.
// Fall back on guessing the start address as a machine code entrypoint.
if(size_t(line_address - file.starting_address) + 5 >= file.data.size()) {
if(size_t(line_address - file.starting_address) + 5 >= file.data.size() || line_address < file.starting_address) {
analysis.machine_code_addresses.push_back(file.starting_address);
break;
}
@@ -209,10 +210,6 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(
// Inspect discovered files to try to divine machine and memory model.
auto vic_memory_model = Target::MemoryModel::Unexpanded;
if(files.size() > 1) {
printf("");
}
auto it = files.begin();
while(it != files.end()) {
const auto &file = *it;

View File

@@ -127,7 +127,10 @@ Video::Video() :
// Show a total of 260 lines; a little short for PAL but a compromise between that and the ST's
// usual output height of 200 lines.
crt_.set_visible_area(crt_.get_rect_for_area(33, 260, 440, 1700, 4.0f / 3.0f));
crt_.set_visible_area(crt_.get_rect_for_area(
33, 260,
480, 1280,
4.0f / 3.0f));
}
void Video::set_ram(uint16_t *ram, size_t size) {

View File

@@ -43,10 +43,10 @@ class Machine final: public MachineBase {
/*!
Sets the serial bus to which this drive should attach itself.
*/
void set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus);
void set_serial_bus(Commodore::Serial::Bus &);
/// Advances time.
void run_for(const Cycles cycles);
void run_for(Cycles);
/// Inserts @c disk into the drive.
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk);

View File

@@ -16,56 +16,54 @@
using namespace Commodore::C1540;
ROM::Request Machine::rom_request(Personality personality) {
namespace {
ROM::Name rom_name(Personality personality) {
switch(personality) {
default:
case Personality::C1540: return ROM::Request(ROM::Name::Commodore1540);
case Personality::C1541: return ROM::Request(ROM::Name::Commodore1541);
case Personality::C1540: return ROM::Name::Commodore1540;
case Personality::C1541: return ROM::Name::Commodore1541;
}
}
}
ROM::Request Machine::rom_request(Personality personality) {
return ROM::Request(rom_name(personality));
}
MachineBase::MachineBase(Personality personality, const ROM::Map &roms) :
Storage::Disk::Controller(1000000),
m6502_(*this),
serial_port_VIA_port_handler_(new SerialPortVIA(serial_port_VIA_)),
serial_port_(new SerialPort),
serial_port_VIA_port_handler_(serial_port_VIA_),
drive_VIA_(drive_VIA_port_handler_),
serial_port_VIA_(*serial_port_VIA_port_handler_) {
// attach the serial port to its VIA and vice versa
serial_port_->set_serial_port_via(serial_port_VIA_port_handler_);
serial_port_VIA_port_handler_->set_serial_port(serial_port_);
serial_port_VIA_(serial_port_VIA_port_handler_) {
// Attach the serial port to its VIA and vice versa.
serial_port_.set_serial_port_via(serial_port_VIA_port_handler_);
serial_port_VIA_port_handler_.set_serial_port(serial_port_);
// set this instance as the delegate to receive interrupt requests from both VIAs
serial_port_VIA_port_handler_->set_interrupt_delegate(this);
// Set this instance as the delegate to receive interrupt requests from both VIAs.
serial_port_VIA_port_handler_.set_interrupt_delegate(this);
drive_VIA_port_handler_.set_interrupt_delegate(this);
drive_VIA_port_handler_.set_delegate(this);
// set a bit rate
// Set a bit rate.
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(3));
// attach the only drive there is
// Attach the only drive there is.
emplace_drive(1000000, 300, 2);
set_drive(1);
ROM::Name rom_name;
switch(personality) {
default:
case Personality::C1540: rom_name = ROM::Name::Commodore1540; break;
case Personality::C1541: rom_name = ROM::Name::Commodore1541; break;
}
auto rom = roms.find(rom_name);
const auto rom = roms.find(rom_name(personality));
if(rom == roms.end()) {
throw ROMMachine::Error::MissingROMs;
}
std::memcpy(rom_, roms.find(rom_name)->second.data(), std::min(sizeof(rom_), roms.find(rom_name)->second.size()));
std::memcpy(rom_, rom->second.data(), std::min(sizeof(rom_), rom->second.size()));
}
Machine::Machine(Personality personality, const ROM::Map &roms) :
MachineBase(personality, roms) {}
void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) {
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus);
void Machine::set_serial_bus(Commodore::Serial::Bus &serial_bus) {
Commodore::Serial::attach(serial_port_, serial_bus);
}
Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
@@ -155,11 +153,11 @@ void MachineBase::process_index_hole() {}
// MARK: - Drive VIA delegate
void MachineBase::drive_via_did_step_head(void *, int direction) {
void MachineBase::drive_via_did_step_head(void *, const int direction) {
get_drive().step(Storage::Disk::HeadPosition(direction, 2));
}
void MachineBase::drive_via_did_set_data_density(void *, int density) {
void MachineBase::drive_via_did_set_data_density(void *, const int density) {
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(unsigned(density)));
}
@@ -174,16 +172,13 @@ uint8_t SerialPortVIA::get_port_input(MOS::MOS6522::Port port) {
void SerialPortVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t) {
if(port) {
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
if(serialPort) {
attention_acknowledge_level_ = !(value&0x10);
data_level_output_ = (value&0x02);
serialPort->set_output(::Commodore::Serial::Line::Clock, ::Commodore::Serial::LineLevel(!(value&0x08)));
serial_port_->set_output(::Commodore::Serial::Line::Clock, ::Commodore::Serial::LineLevel(!(value&0x08)));
update_data_line();
}
}
}
void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) {
switch(line) {
@@ -199,17 +194,14 @@ void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool v
}
}
void SerialPortVIA::set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &serialPort) {
serial_port_ = serialPort;
void SerialPortVIA::set_serial_port(Commodore::Serial::Port &port) {
serial_port_ = &port;
}
void SerialPortVIA::update_data_line() {
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
if(serialPort) {
// "ATN (Attention) is an input on pin 3 of P2 and P3 that is sensed at PB7 and CA1 of UC3 after being inverted by UA1"
serialPort->set_output(::Commodore::Serial::Line::Data,
::Commodore::Serial::LineLevel(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_)));
}
serial_port_->set_output(::Commodore::Serial::Line::Data,
Serial::LineLevel(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_)));
}
// MARK: - DriveVIA
@@ -248,23 +240,23 @@ void DriveVIA::set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Li
void DriveVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t) {
if(port) {
if(previous_port_b_output_ != value) {
// record drive motor state
drive_motor_ = !!(value&4);
// Record drive motor state.
drive_motor_ = value&4;
// check for a head step
int step_difference = ((value&3) - (previous_port_b_output_&3))&3;
// Check for a head step.
const int step_difference = ((value&3) - (previous_port_b_output_&3))&3;
if(step_difference) {
if(delegate_) delegate_->drive_via_did_step_head(this, (step_difference == 1) ? 1 : -1);
}
// check for a change in density
int density_difference = (previous_port_b_output_^value) & (3 << 5);
// Check for a change in density.
const int density_difference = (previous_port_b_output_^value) & (3 << 5);
if(density_difference && delegate_) {
delegate_->drive_via_did_set_data_density(this, (value >> 5)&3);
}
// post the LED status
if(observer_) observer_->set_led_status("Drive", !!(value&8));
// Post the LED status.
if(observer_) observer_->set_led_status("Drive", value&8);
previous_port_b_output_ = value;
}
@@ -275,17 +267,16 @@ void DriveVIA::set_activity_observer(Activity::Observer *observer) {
observer_ = observer;
if(observer) {
observer->register_led("Drive");
observer->set_led_status("Drive", !!(previous_port_b_output_&8));
observer->set_led_status("Drive", previous_port_b_output_&8);
}
}
// MARK: - SerialPort
void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) {
std::shared_ptr<SerialPortVIA> serialPortVIA = serial_port_VIA_.lock();
if(serialPortVIA) serialPortVIA->set_serial_line_state(line, bool(level));
void SerialPort::set_input(Serial::Line line, Serial::LineLevel level) {
serial_port_VIA_->set_serial_line_state(line, bool(level));
}
void SerialPort::set_serial_port_via(const std::shared_ptr<SerialPortVIA> &serialPortVIA) {
serial_port_VIA_ = serialPortVIA;
void SerialPort::set_serial_port_via(SerialPortVIA &serialPortVIA) {
serial_port_VIA_ = &serialPortVIA;
}

View File

@@ -46,12 +46,12 @@ public:
void set_port_output(MOS::MOS6522::Port, uint8_t value, uint8_t mask);
void set_serial_line_state(::Commodore::Serial::Line, bool);
void set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &);
void set_serial_port(Commodore::Serial::Port &);
private:
MOS::MOS6522::MOS6522<SerialPortVIA> &via_;
uint8_t port_b_ = 0x0;
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
Commodore::Serial::Port *serial_port_ = nullptr;
bool attention_acknowledge_level_ = false;
bool attention_level_input_ = true;
bool data_level_output_ = false;
@@ -84,7 +84,7 @@ public:
};
void set_delegate(Delegate *);
uint8_t get_port_input(MOS::MOS6522::Port port);
uint8_t get_port_input(MOS::MOS6522::Port);
void set_sync_detected(bool);
void set_data_input(uint8_t);
@@ -95,7 +95,7 @@ public:
void set_port_output(MOS::MOS6522::Port, uint8_t value, uint8_t direction_mask);
void set_activity_observer(Activity::Observer *observer);
void set_activity_observer(Activity::Observer *);
private:
uint8_t port_b_ = 0xff, port_a_ = 0xff;
@@ -111,11 +111,11 @@ private:
*/
class SerialPort : public ::Commodore::Serial::Port {
public:
void set_input(::Commodore::Serial::Line, ::Commodore::Serial::LineLevel);
void set_serial_port_via(const std::shared_ptr<SerialPortVIA> &);
void set_input(Commodore::Serial::Line, Commodore::Serial::LineLevel);
void set_serial_port_via(SerialPortVIA &);
private:
std::weak_ptr<SerialPortVIA> serial_port_VIA_;
SerialPortVIA *serial_port_VIA_ = nullptr;
};
class MachineBase:
@@ -125,37 +125,37 @@ class MachineBase:
public Storage::Disk::Controller {
public:
MachineBase(Personality personality, const ROM::Map &roms);
// to satisfy CPU::MOS6502::Processor
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value);
// to satisfy MOS::MOS6522::Delegate
virtual void mos6522_did_change_interrupt_status(void *mos6522);
// to satisfy DriveVIA::Delegate
void drive_via_did_step_head(void *driveVIA, int direction);
void drive_via_did_set_data_density(void *driveVIA, int density);
MachineBase(Personality, const ROM::Map &);
/// Attaches the activity observer to this C1540.
void set_activity_observer(Activity::Observer *observer);
void set_activity_observer(Activity::Observer *);
// to satisfy CPU::MOS6502::Processor
Cycles perform_bus_operation(CPU::MOS6502::BusOperation, uint16_t address, uint8_t *value);
protected:
// to satisfy MOS::MOS6522::Delegate
void mos6522_did_change_interrupt_status(void *mos6522) override;
// to satisfy DriveVIA::Delegate
void drive_via_did_step_head(void *driveVIA, int direction) override;
void drive_via_did_set_data_density(void *driveVIA, int density) override;
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, MachineBase, false> m6502_;
uint8_t ram_[0x800];
uint8_t rom_[0x4000];
std::shared_ptr<SerialPortVIA> serial_port_VIA_port_handler_;
std::shared_ptr<SerialPort> serial_port_;
SerialPortVIA serial_port_VIA_port_handler_;
SerialPort serial_port_;
DriveVIA drive_VIA_port_handler_;
MOS::MOS6522::MOS6522<DriveVIA> drive_VIA_;
MOS::MOS6522::MOS6522<SerialPortVIA> serial_port_VIA_;
int shift_register_ = 0, bit_window_offset_;
virtual void process_input_bit(int value);
virtual void process_index_hole();
void process_input_bit(int value) override;
void process_index_hole() override;
};
}

View File

@@ -0,0 +1,113 @@
//
// Audio.hpp
// Clock Signal
//
// Created by Thomas Harte on 30/12/2024.
// Copyright © 2024 Thomas Harte. All rights reserved.
//
#pragma once
#include "../../../Outputs/Speaker/Implementation/BufferSource.hpp"
#include "../../../Concurrency/AsyncTaskQueue.hpp"
namespace Commodore::Plus4 {
// PAL: / 160 i.e. 5*32
// NTSC: / 128 i.e. 4*32
// 111860.78125 = NTSC
// 110840.46875 = PAL
class Audio: public Outputs::Speaker::BufferSource<Audio, false> {
public:
Audio(Concurrency::AsyncTaskQueue<false> &audio_queue) :
audio_queue_(audio_queue) {}
template <Outputs::Speaker::Action action>
void apply_samples(std::size_t size, Outputs::Speaker::MonoSample *const target) {
const auto count_frequency = [&](int index) {
++counts_[index];
if(counts_[index] == (frequencies_[index] ^ 1023) * frequency_multiplier_) {
states_[index] ^= 1;
counts_[index] = 0;
} else if(counts_[index] == 1024 * frequency_multiplier_) {
counts_[index] = 0;
}
};
if(sound_dc_) {
Outputs::Speaker::fill<action>(target, target + size, Outputs::Speaker::MonoSample(volume_ * 2));
} else {
// TODO: noise generation.
for(size_t c = 0; c < size; c++) {
count_frequency(0);
count_frequency(1);
Outputs::Speaker::apply<action>(
target[c],
Outputs::Speaker::MonoSample(
(
((states_[0] & masks_[0]) * external_volume_) +
((states_[1] & masks_[1]) * external_volume_)
) * volume_
));
}
}
r_ += size;
}
void set_sample_volume_range(const std::int16_t range) {
external_volume_ = range / (2 * 9); // Two channels and nine output levels.
}
bool is_zero_level() const {
return !(masks_[0] || masks_[1] || sound2_noise_on_) || !volume_;
}
template <int channel> void set_frequency_low(uint8_t value) {
audio_queue_.enqueue([this, value] {
frequencies_[channel] = (frequencies_[channel] & 0xff00) | value;
});
}
template <int channel> void set_frequency_high(uint8_t value) {
audio_queue_.enqueue([this, value] {
frequencies_[channel] = (frequencies_[channel] & 0x00ff) | ((value&3) << 8);
});
}
void set_constrol(uint8_t value) {
audio_queue_.enqueue([this, value] {
volume_ = std::min(value & 0xf, 8); // Only nine volumes are available.
masks_[0] = (value & 0x10) ? 1 : 0;
masks_[1] = (value & 0x20) ? 1 : 0;
sound2_noise_on_ = (value & 0x40) && !(value & 0x20);
sound_dc_ = value & 0x80;
});
}
private:
// Calling-thread state.
Concurrency::AsyncTaskQueue<false> &audio_queue_;
// Audio-thread state.
int16_t external_volume_ = 0;
int frequencies_[2]{};
int frequency_multiplier_ = 5;
int counts_[2]{};
int states_[2]{};
int masks_[2]{};
bool sound2_noise_on_ = false;
bool sound_dc_ = false;
int volume_ = 0;
int r_ = 0;
};
}

View File

@@ -0,0 +1,72 @@
//
// Interrupts.hpp
// Clock Signal
//
// Created by Thomas Harte on 12/12/2024.
// Copyright © 2024 Thomas Harte. All rights reserved.
//
#pragma once
#include "../../../Processors/6502/6502.hpp"
namespace Commodore::Plus4 {
struct BusController {
virtual void set_irq_line(bool) = 0;
virtual void set_ready_line(bool) = 0;
};
struct Interrupts {
public:
Interrupts(BusController &delegate) : delegate_(delegate) {}
BusController &bus() {
return delegate_;
}
enum Flag {
Timer3 = 0x40,
Timer2 = 0x10,
Timer1 = 0x08,
Raster = 0x02,
};
uint8_t status() const {
return status_ | ((status_ & mask_) ? 0x80 : 0x00);
}
uint8_t mask() const {
return mask_;
}
void set_status(const uint8_t status) {
status_ &= ~status;
update_output();
}
void apply(const uint8_t interrupt) {
status_ |= interrupt;
update_output();
}
void set_mask(const uint8_t mask) {
mask_ = mask;
update_output();
}
private:
void update_output() {
const bool set = status_ & mask_;
if(set != last_set_) {
delegate_.set_irq_line(set);
last_set_ = set;
}
}
BusController &delegate_;
uint8_t status_;
uint8_t mask_;
bool last_set_ = false;
};
}

View File

@@ -0,0 +1,134 @@
//
// Keyboard.cpp
// Clock Signal
//
// Created by Thomas Harte on 13/12/2024.
// Copyright 2024 Thomas Harte. All rights reserved.
//
#include "Keyboard.hpp"
using namespace Commodore::Plus4;
uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) const {
#define BIND(source, dest) case Inputs::Keyboard::Key::source: return Commodore::Plus4::dest
switch(key) {
default: break;
BIND(k0, k0); BIND(k1, k1); BIND(k2, k2); BIND(k3, k3); BIND(k4, k4);
BIND(k5, k5); BIND(k6, k6); BIND(k7, k7); BIND(k8, k8); BIND(k9, k9);
BIND(Q, Q); BIND(W, W); BIND(E, E); BIND(R, R); BIND(T, T);
BIND(Y, Y); BIND(U, U); BIND(I, I); BIND(O, O); BIND(P, P);
BIND(A, A); BIND(S, S); BIND(D, D); BIND(F, F); BIND(G, G);
BIND(H, H); BIND(J, J); BIND(K, K); BIND(L, L);
BIND(Z, Z); BIND(X, X); BIND(C, C); BIND(V, V);
BIND(B, B); BIND(N, N); BIND(M, M);
BIND(Backspace, InsDel);
BIND(Escape, Escape);
BIND(F1, F1_F4);
BIND(F2, F2_F5);
BIND(F3, F3_F6);
BIND(F4, Help_F7);
BIND(Enter, Return);
BIND(Space, Space);
BIND(Up, Up); BIND(Down, Down); BIND(Left, Left); BIND(Right, Right);
BIND(LeftShift, Shift); BIND(RightShift, Shift);
BIND(LeftControl, Control); BIND(RightControl, Control);
BIND(LeftOption, Commodore); BIND(RightOption, Commodore);
BIND(FullStop, FullStop); BIND(Comma, Comma);
BIND(Semicolon, Semicolon); BIND(Quote, Colon);
BIND(Equals, Equals); BIND(ForwardSlash, Slash);
BIND(OpenSquareBracket, At);
BIND(CloseSquareBracket, Plus);
BIND(Backslash, Clear_Home);
BIND(BackTick, Asterisk);
BIND(F11, Clear_Home);
BIND(F12, Run_Stop);
// TODO:
// GBP
}
#undef BIND
return MachineTypes::MappedKeyboardMachine::KeyNotMapped;
}
//const uint16_t *CharacterMapper::sequence_for_character(char character) const {
//#define KEYS(...) {__VA_ARGS__, MachineTypes::MappedKeyboardMachine::KeyEndSequence}
//#define SHIFT(...) {KeyLShift, __VA_ARGS__, MachineTypes::MappedKeyboardMachine::KeyEndSequence}
//#define X {MachineTypes::MappedKeyboardMachine::KeyNotMapped}
// static KeySequence key_sequences[] = {
// /* NUL */ X, /* SOH */ X,
// /* STX */ X, /* ETX */ X,
// /* EOT */ X, /* ENQ */ X,
// /* ACK */ X, /* BEL */ X,
// /* BS */ KEYS(KeyDelete), /* HT */ X,
// /* LF */ KEYS(KeyReturn), /* VT */ X,
// /* FF */ X, /* CR */ X,
// /* SO */ X, /* SI */ X,
// /* DLE */ X, /* DC1 */ X,
// /* DC2 */ X, /* DC3 */ X,
// /* DC4 */ X, /* NAK */ X,
// /* SYN */ X, /* ETB */ X,
// /* CAN */ X, /* EM */ X,
// /* SUB */ X, /* ESC */ X,
// /* FS */ X, /* GS */ X,
// /* RS */ X, /* US */ X,
// /* space */ KEYS(KeySpace), /* ! */ SHIFT(Key1),
// /* " */ SHIFT(Key2), /* # */ SHIFT(Key3),
// /* $ */ SHIFT(Key4), /* % */ SHIFT(Key5),
// /* & */ SHIFT(Key6), /* ' */ SHIFT(Key7),
// /* ( */ SHIFT(Key8), /* ) */ SHIFT(Key9),
// /* * */ KEYS(KeyAsterisk), /* + */ KEYS(KeyPlus),
// /* , */ KEYS(KeyComma), /* - */ KEYS(KeyDash),
// /* . */ KEYS(KeyFullStop), /* / */ KEYS(KeySlash),
// /* 0 */ KEYS(Key0), /* 1 */ KEYS(Key1),
// /* 2 */ KEYS(Key2), /* 3 */ KEYS(Key3),
// /* 4 */ KEYS(Key4), /* 5 */ KEYS(Key5),
// /* 6 */ KEYS(Key6), /* 7 */ KEYS(Key7),
// /* 8 */ KEYS(Key8), /* 9 */ KEYS(Key9),
// /* : */ KEYS(KeyColon), /* ; */ KEYS(KeySemicolon),
// /* < */ SHIFT(KeyComma), /* = */ KEYS(KeyEquals),
// /* > */ SHIFT(KeyFullStop), /* ? */ SHIFT(KeySlash),
// /* @ */ KEYS(KeyAt), /* A */ KEYS(KeyA),
// /* B */ KEYS(KeyB), /* C */ KEYS(KeyC),
// /* D */ KEYS(KeyD), /* E */ KEYS(KeyE),
// /* F */ KEYS(KeyF), /* G */ KEYS(KeyG),
// /* H */ KEYS(KeyH), /* I */ KEYS(KeyI),
// /* J */ KEYS(KeyJ), /* K */ KEYS(KeyK),
// /* L */ KEYS(KeyL), /* M */ KEYS(KeyM),
// /* N */ KEYS(KeyN), /* O */ KEYS(KeyO),
// /* P */ KEYS(KeyP), /* Q */ KEYS(KeyQ),
// /* R */ KEYS(KeyR), /* S */ KEYS(KeyS),
// /* T */ KEYS(KeyT), /* U */ KEYS(KeyU),
// /* V */ KEYS(KeyV), /* W */ KEYS(KeyW),
// /* X */ KEYS(KeyX), /* Y */ KEYS(KeyY),
// /* Z */ KEYS(KeyZ), /* [ */ SHIFT(KeyColon),
// /* \ */ X, /* ] */ SHIFT(KeySemicolon),
// /* ^ */ X, /* _ */ X,
// /* ` */ X, /* a */ KEYS(KeyA),
// /* b */ KEYS(KeyB), /* c */ KEYS(KeyC),
// /* d */ KEYS(KeyD), /* e */ KEYS(KeyE),
// /* f */ KEYS(KeyF), /* g */ KEYS(KeyG),
// /* h */ KEYS(KeyH), /* i */ KEYS(KeyI),
// /* j */ KEYS(KeyJ), /* k */ KEYS(KeyK),
// /* l */ KEYS(KeyL), /* m */ KEYS(KeyM),
// /* n */ KEYS(KeyN), /* o */ KEYS(KeyO),
// /* p */ KEYS(KeyP), /* q */ KEYS(KeyQ),
// /* r */ KEYS(KeyR), /* s */ KEYS(KeyS),
// /* t */ KEYS(KeyT), /* u */ KEYS(KeyU),
// /* v */ KEYS(KeyV), /* w */ KEYS(KeyW),
// /* x */ KEYS(KeyX), /* y */ KEYS(KeyY),
// /* z */ KEYS(KeyZ)
// };
//#undef KEYS
//#undef SHIFT
//#undef X
//
// return table_lookup_sequence_for_character(key_sequences, character);
//}

View File

@@ -0,0 +1,82 @@
//
// Keyboard.hpp
// Clock Signal
//
// Created by Thomas Harte on 13/12/2024.
// Copyright 2024 Thomas Harte. All rights reserved.
//
#pragma once
#include "../../KeyboardMachine.hpp"
#include "../../Utility/Typer.hpp"
namespace Commodore::Plus4 {
static constexpr uint16_t key(const int line, const int mask) {
return uint16_t((mask << 3) | line);
}
static constexpr size_t line(const uint16_t key) {
return key & 7;
}
static constexpr uint8_t mask(const uint16_t key) {
return uint8_t(key >> 3);
}
enum Key: uint16_t {
InsDel = key(0, 0x01), Return = key(0, 0x02),
GBP = key(0, 0x04), Help_F7 = key(0, 0x08),
F1_F4 = key(0, 0x10), F2_F5 = key(0, 0x20),
F3_F6 = key(0, 0x40), At = key(0, 0x80),
k3 = key(1, 0x01), W = key(1, 0x02),
A = key(1, 0x04), k4 = key(1, 0x08),
Z = key(1, 0x10), S = key(1, 0x20),
E = key(1, 0x40), Shift = key(1, 0x80),
k5 = key(2, 0x01), R = key(2, 0x02),
D = key(2, 0x04), k6 = key(2, 0x08),
C = key(2, 0x10), F = key(2, 0x20),
T = key(2, 0x40), X = key(2, 0x80),
k7 = key(3, 0x01), Y = key(3, 0x02),
G = key(3, 0x04), k8 = key(3, 0x08),
B = key(3, 0x10), H = key(3, 0x20),
U = key(3, 0x40), V = key(3, 0x80),
k9 = key(4, 0x01), I = key(4, 0x02),
J = key(4, 0x04), k0 = key(4, 0x08),
M = key(4, 0x10), K = key(4, 0x20),
O = key(4, 0x40), N = key(4, 0x80),
Down = key(5, 0x01), P = key(5, 0x02),
L = key(5, 0x04), Up = key(5, 0x08),
FullStop = key(5, 0x10), Colon = key(5, 0x20),
Minus = key(5, 0x40), Comma = key(5, 0x80),
Left = key(6, 0x01), Asterisk = key(6, 0x02),
Semicolon = key(6, 0x04), Right = key(6, 0x08),
Escape = key(6, 0x10), Equals = key(6, 0x20),
Plus = key(6, 0x40), Slash = key(6, 0x80),
k1 = key(7, 0x01), Clear_Home = key(7, 0x02),
Control = key(7, 0x04), k2 = key(7, 0x08),
Space = key(7, 0x10), Commodore = key(7, 0x20),
Q = key(7, 0x40), Run_Stop = key(7, 0x80),
// Bonus virtual keys:
F4 = 0xfe00,
F5 = 0xfe01,
F6 = 0xfe02,
F7 = 0xfe03,
};
struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const final;
};
//struct CharacterMapper: public ::Utility::CharacterMapper {
// const uint16_t *sequence_for_character(char character) const final;
//};
}

View File

@@ -0,0 +1,62 @@
//
// Pager.hpp
// Clock Signal
//
// Created by Thomas Harte on 11/12/2024.
// Copyright © 2024 Thomas Harte. All rights reserved.
//
#pragma once
enum PagerSide {
Read = 1,
Write = 2,
ReadWrite = Read | Write,
};
template <typename AddressT, typename DataT, int NumPages>
class Pager {
public:
DataT read(AddressT address) const {
return read_[address >> Shift][address];
}
DataT &write(AddressT address) {
return write_[address >> Shift][address];
}
template <int side, size_t start, size_t length>
void page(uint8_t *data) {
static_assert(!(start % PageSize), "Start address must be a multiple of the page size");
static_assert(!(length % PageSize), "Data length must be a multiple of the page size");
for(size_t slot = start >> Shift; slot < (start + length) >> Shift; slot++) {
if constexpr (side & PagerSide::Write) write_[slot] = data - (slot << Shift);
if constexpr (side & PagerSide::Read) read_[slot] = data - (slot << Shift);
data += PageSize;
}
}
private:
std::array<DataT *, NumPages> write_{};
std::array<const DataT *, NumPages> read_{};
static constexpr auto AddressBits = sizeof(AddressT) * 8;
static constexpr auto PageSize = (1 << AddressBits) / NumPages;
static_assert(!(PageSize & (PageSize - 1)), "Pages must be a power of two in size");
static constexpr int ln2(int value) {
int result = 0;
while(value != 1) {
value >>= 1;
++result;
}
return result;
}
static constexpr auto Shift = ln2(PageSize);
};
namespace Commodore::Plus4 {
using Pager = Pager<uint16_t, uint8_t, 4>;
}

View File

@@ -8,37 +8,524 @@
#include "Plus4.hpp"
#include "Audio.hpp"
#include "Interrupts.hpp"
#include "Keyboard.hpp"
#include "Pager.hpp"
#include "Video.hpp"
#include "../../MachineTypes.hpp"
#include "../../Utility/MemoryFuzzer.hpp"
#include "../../../Processors/6502/6502.hpp"
#include "../../../Analyser/Static/Commodore/Target.hpp"
#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../../Storage/Tape/Tape.hpp"
#include "../SerialBus.hpp"
#include "../1540/C1540.hpp"
using namespace Commodore;
using namespace Commodore::Plus4;
namespace {
class ConcreteMachine:
public MachineTypes::TimedMachine,
public MachineTypes::ScanProducer,
public Machine {
class Timers {
public:
ConcreteMachine(const Analyser::Static::Commodore::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) {
(void)target;
(void)rom_fetcher;
Timers(Interrupts &interrupts) : interrupts_(interrupts) {}
template <int offset>
void write(const uint8_t value) {
const auto load_low = [&](uint16_t &target) {
target = uint16_t((target & 0xff00) | (value << 0));
};
const auto load_high = [&](uint16_t &target) {
target = uint16_t((target & 0x00ff) | (value << 8));
};
constexpr auto timer = offset >> 1;
paused_[timer] = ~offset & 1;
if constexpr (offset & 1) {
load_high(timers_[timer]);
if constexpr (!timer) {
load_high(timer0_reload_);
}
} else {
load_low(timers_[timer]);
if constexpr (!timer) {
load_low(timer0_reload_);
}
}
}
template <int offset>
uint8_t read() {
constexpr auto timer = offset >> 1;
if constexpr (offset & 1) {
return uint8_t(timers_[timer] >> 8);
} else {
return uint8_t(timers_[timer] >> 0);
}
}
void tick(int count) {
// Quick hack here; do better than stepping through one at a time.
while(count--) {
decrement<0>();
decrement<1>();
decrement<2>();
}
}
private:
void set_scan_target(Outputs::Display::ScanTarget *const) final {
template <int timer>
void decrement() {
if(paused_[timer]) return;
// Check for reload.
if(!timer && !timers_[timer]) {
timers_[timer] = timer0_reload_;
}
-- timers_[timer];
// Check for interrupt.
if(!timers_[timer]) {
switch(timer) {
case 0: interrupts_.apply(Interrupts::Flag::Timer1); break;
case 1: interrupts_.apply(Interrupts::Flag::Timer2); break;
case 2: interrupts_.apply(Interrupts::Flag::Timer3); break;
}
}
}
uint16_t timers_[3]{};
uint16_t timer0_reload_ = 0xffff;
bool paused_[3]{};
Interrupts &interrupts_;
};
class SerialPort: public Serial::Port {
public:
void set_input(const Serial::Line line, const Serial::LineLevel value) override {
levels_[size_t(line)] = value;
}
Serial::LineLevel level(const Serial::Line line) const {
return levels_[size_t(line)];
}
private:
Serial::LineLevel levels_[5];
};
class ConcreteMachine:
public Activity::Source,
public BusController,
public CPU::MOS6502::BusHandler,
public MachineTypes::AudioProducer,
public MachineTypes::MappedKeyboardMachine,
public MachineTypes::TimedMachine,
public MachineTypes::ScanProducer,
public MachineTypes::MediaTarget,
public Machine {
public:
ConcreteMachine(const Analyser::Static::Commodore::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
m6502_(*this),
interrupts_(*this),
timers_(interrupts_),
video_(video_map_, interrupts_),
audio_(audio_queue_),
speaker_(audio_)
{
const auto clock = clock_rate(false);
media_divider_ = Cycles(clock);
set_clock_rate(clock);
speaker_.set_input_rate(float(clock) / 32.0f);
// TODO: decide whether to attach a 1541 for real.
const bool has_c1541 = true;
const auto kernel = ROM::Name::Plus4KernelPALv5;
const auto basic = ROM::Name::Plus4BASIC;
ROM::Request request = ROM::Request(basic) && ROM::Request(kernel);
if(has_c1541) {
request = request && C1540::Machine::rom_request(C1540::Personality::C1541);
}
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
kernel_ = roms.find(kernel)->second;
basic_ = roms.find(basic)->second;
Memory::Fuzz(ram_);
map_.page<PagerSide::ReadWrite, 0, 65536>(ram_.data());
page_cpu_rom();
video_map_.page<PagerSide::ReadWrite, 0, 65536>(ram_.data());
if(has_c1541) {
c1541_ = std::make_unique<C1540::Machine>(C1540::Personality::C1541, roms);
c1541_->set_serial_bus(serial_bus_);
Serial::attach(serial_port_, serial_bus_);
}
tape_player_ = std::make_unique<Storage::Tape::BinaryTapePlayer>(clock);
insert_media(target.media);
printf("Loading command is: %s\n", target.loading_command.c_str());
}
~ConcreteMachine() {
audio_queue_.flush();
}
Cycles perform_bus_operation(
const CPU::MOS6502::BusOperation operation,
const uint16_t address,
uint8_t *const value
) {
// Determine from the TED video subsystem the length of this clock cycle as perceived by the 6502,
// relative to the master clock.
const auto length = video_.cycle_length(operation == CPU::MOS6502::BusOperation::Ready);
// Update other subsystems.
timers_subcycles_ += length;
const auto timers_cycles = timers_subcycles_.divide(video_.timer_cycle_length());
timers_.tick(timers_cycles.as<int>());
video_.run_for(length);
tape_player_->run_for(length);
if(c1541_) {
c1541_cycles_ += length * Cycles(1'000'000);
c1541_->run_for(c1541_cycles_.divide(media_divider_));
}
time_since_audio_update_ += length;
if(operation == CPU::MOS6502::BusOperation::Ready) {
return length;
}
// Perform actual access.
if(address < 0x0002) {
// 0x0000: data directions for parallel IO; 1 = output.
// 0x0001:
// b7 = serial data in;
// b6 = serial clock in and cassette write;
// b5 = [unconnected];
// b4 = cassette read;
// b3 = cassette motor, 1 = off;
// b2 = serial ATN out;
// b1 = serial clock out and cassette write;
// b0 = serial data out.
if(isReadOperation(operation)) {
if(!address) {
*value = io_direction_;
} else {
const uint8_t all_inputs =
(tape_player_->input() ? 0x00 : 0x10) |
(serial_port_.level(Serial::Line::Data) ? 0x80 : 0x00) |
(serial_port_.level(Serial::Line::Clock) ? 0x40 : 0x00);
*value =
(io_direction_ & io_output_) |
(~io_direction_ & all_inputs);
}
} else {
if(!address) {
io_direction_ = *value;
} else {
io_output_ = *value;
}
const auto output = io_output_ | ~io_direction_;
// tape_player_->set_motor_control(~output & 0x08);
serial_port_.set_output(Serial::Line::Data, Serial::LineLevel(~output & 0x01));
serial_port_.set_output(Serial::Line::Clock, Serial::LineLevel(~output & 0x02));
serial_port_.set_output(Serial::Line::Attention, Serial::LineLevel(~output & 0x04));
}
// printf("%04x: %02x %c\n", address, *value, isReadOperation(operation) ? 'r' : 'w');
} else if(address < 0xfd00 || address >= 0xff40) {
if(isReadOperation(operation)) {
*value = map_.read(address);
} else {
map_.write(address) = *value;
}
} else if(address < 0xff00) {
// Miscellaneous hardware. All TODO.
if(isReadOperation(operation)) {
// printf("TODO: read @ %04x\n", address);
if((address & 0xfff0) == 0xfd10) {
tape_player_->set_motor_control(true);
*value = 0xff ^ 0x4; // Seems to detect the play button.
} else {
*value = 0xff;
}
} else {
// printf("TODO: write of %02x @ %04x\n", *value, address);
}
} else {
if(isReadOperation(operation)) {
switch(address) {
case 0xff00: *value = timers_.read<0>(); break;
case 0xff01: *value = timers_.read<1>(); break;
case 0xff02: *value = timers_.read<2>(); break;
case 0xff03: *value = timers_.read<3>(); break;
case 0xff04: *value = timers_.read<4>(); break;
case 0xff05: *value = timers_.read<5>(); break;
case 0xff08: *value = keyboard_latch_; break;
case 0xff09: *value = interrupts_.status(); break;
case 0xff0a: *value = interrupts_.mask(); break;
case 0xff06: *value = video_.read<0xff06>(); break;
case 0xff07: *value = video_.read<0xff07>(); break;
case 0xff0b: *value = video_.read<0xff0b>(); break;
case 0xff1c: *value = video_.read<0xff1c>(); break;
case 0xff1d: *value = video_.read<0xff1d>(); break;
case 0xff0e: *value = ff0e_; break;
case 0xff0f: *value = ff0f_; break;
case 0xff10: *value = ff10_; break;
case 0xff11: *value = ff11_; break;
case 0xff12: *value = ff12_; break;
case 0xff13: *value = ff13_ | (rom_is_paged_ ? 1 : 0); break;
case 0xff0c: *value = video_.read<0xff0c>(); break;
case 0xff0d: *value = video_.read<0xff0d>(); break;
case 0xff14: *value = video_.read<0xff14>(); break;
case 0xff15: *value = video_.read<0xff15>(); break;
case 0xff16: *value = video_.read<0xff16>(); break;
case 0xff17: *value = video_.read<0xff17>(); break;
case 0xff18: *value = video_.read<0xff18>(); break;
case 0xff19: *value = video_.read<0xff19>(); break;
default:
printf("TODO: TED read at %04x\n", address);
}
} else {
switch(address) {
case 0xff00: timers_.write<0>(*value); break;
case 0xff01: timers_.write<1>(*value); break;
case 0xff02: timers_.write<2>(*value); break;
case 0xff03: timers_.write<3>(*value); break;
case 0xff04: timers_.write<4>(*value); break;
case 0xff05: timers_.write<5>(*value); break;
case 0xff08:
keyboard_latch_ = ~(
((*value & 0x01) ? 0x00 : key_states_[0]) |
((*value & 0x02) ? 0x00 : key_states_[1]) |
((*value & 0x04) ? 0x00 : key_states_[2]) |
((*value & 0x08) ? 0x00 : key_states_[3]) |
((*value & 0x10) ? 0x00 : key_states_[4]) |
((*value & 0x20) ? 0x00 : key_states_[5]) |
((*value & 0x40) ? 0x00 : key_states_[6]) |
((*value & 0x80) ? 0x00 : key_states_[7])
);
break;
case 0xff09:
interrupts_.set_status(*value);
break;
case 0xff0a:
interrupts_.set_mask(*value);
video_.write<0xff0a>(*value);
break;
case 0xff0b: video_.write<0xff0b>(*value); break;
case 0xff06: video_.write<0xff06>(*value); break;
case 0xff07: video_.write<0xff07>(*value); break;
case 0xff0c: video_.write<0xff0c>(*value); break;
case 0xff0d: video_.write<0xff0d>(*value); break;
case 0xff0e:
ff0e_ = *value;
update_audio();
audio_.set_frequency_low<0>(*value);
break;
case 0xff0f:
ff0f_ = *value;
update_audio();
audio_.set_frequency_low<1>(*value);
break;
case 0xff10:
ff10_ = *value;
update_audio();
audio_.set_frequency_high<1>(*value);
break;
case 0xff11:
ff11_ = *value;
update_audio();
audio_.set_constrol(*value);
break;
case 0xff12:
ff12_ = *value & 0x3f;
video_.write<0xff12>(*value);
if((*value & 4)) {
page_video_rom();
} else {
page_video_ram();
}
update_audio();
audio_.set_frequency_high<0>(*value);
break;
case 0xff13:
ff13_ = *value & 0xfe;
video_.write<0xff13>(*value);
break;
case 0xff14: video_.write<0xff14>(*value); break;
case 0xff1a: video_.write<0xff1a>(*value); break;
case 0xff1b: video_.write<0xff1b>(*value); break;
case 0xff15: video_.write<0xff15>(*value); break;
case 0xff16: video_.write<0xff16>(*value); break;
case 0xff17: video_.write<0xff17>(*value); break;
case 0xff18: video_.write<0xff18>(*value); break;
case 0xff19: video_.write<0xff19>(*value); break;
case 0xff3e: page_cpu_rom(); break;
case 0xff3f: page_cpu_ram(); break;
// TODO: audio is 0xff10, 0xff11, 0xff0e, 0xff0f and shares 0xff18.
default:
printf("TODO: TED write at %04x\n", address);
}
}
}
return length;
}
private:
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, true> m6502_;
Outputs::Speaker::Speaker *get_speaker() override {
return &speaker_;
}
void set_activity_observer(Activity::Observer *const observer) final {
if(c1541_) c1541_->set_activity_observer(observer);
}
void set_irq_line(bool active) override {
m6502_.set_irq_line(active);
}
void set_ready_line(bool active) override {
m6502_.set_ready_line(active);
}
void page_video_rom() {
video_map_.page<PagerSide::Read, 0x8000, 16384>(basic_.data());
video_map_.page<PagerSide::Read, 0xc000, 16384>(kernel_.data());
}
void page_video_ram() {
video_map_.page<PagerSide::Read, 0x8000, 32768>(&ram_[0x8000]);
}
void page_cpu_rom() {
// TODO: allow other ROM selection. And no ROM?
map_.page<PagerSide::Read, 0x8000, 16384>(basic_.data());
map_.page<PagerSide::Read, 0xc000, 16384>(kernel_.data());
rom_is_paged_ = true;
}
void page_cpu_ram() {
map_.page<PagerSide::Read, 0x8000, 32768>(&ram_[0x8000]);
rom_is_paged_ = false;
}
bool rom_is_paged_ = false;
void set_scan_target(Outputs::Display::ScanTarget *const target) final {
video_.set_scan_target(target);
}
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return {};
return video_.get_scaled_scan_status();
}
void run_for(const Cycles) final {
void run_for(const Cycles cycles) final {
m6502_.run_for(cycles);
// I don't know why.
update_audio();
audio_queue_.perform();
}
void flush_output(int outputs) override {
if(outputs & Output::Audio) {
update_audio();
audio_queue_.perform();
}
}
bool insert_media(const Analyser::Static::Media &media) final {
if(!media.tapes.empty()) {
tape_player_->set_tape(media.tapes[0]);
}
if(!media.disks.empty() && c1541_) {
c1541_->set_disk(media.disks[0]);
}
return true;
}
Plus4::Pager map_;
Plus4::Pager video_map_;
std::array<uint8_t, 65536> ram_;
std::vector<uint8_t> kernel_;
std::vector<uint8_t> basic_;
uint8_t ff0e_, ff0f_, ff10_, ff11_, ff12_, ff13_;
Interrupts interrupts_;
Cycles timers_subcycles_;
Timers timers_;
Video video_;
Concurrency::AsyncTaskQueue<false> audio_queue_;
Audio audio_;
Cycles time_since_audio_update_;
Outputs::Speaker::PullLowpass<Audio> speaker_;
void update_audio() {
speaker_.run_for(audio_queue_, time_since_audio_update_.divide(Cycles(32)));
}
// MARK: - MappedKeyboardMachine.
MappedKeyboardMachine::KeyboardMapper *get_keyboard_mapper() override {
static Plus4::KeyboardMapper keyboard_mapper_;
return &keyboard_mapper_;
}
void set_key_state(uint16_t key, bool is_pressed) override {
if(is_pressed) {
key_states_[line(key)] |= mask(key);
} else {
key_states_[line(key)] &= ~mask(key);
}
}
std::array<uint8_t, 8> key_states_{};
uint8_t keyboard_latch_ = 0xff;
Cycles media_divider_, c1541_cycles_;
std::unique_ptr<C1540::Machine> c1541_;
Serial::Bus serial_bus_;
SerialPort serial_port_;
std::unique_ptr<Storage::Tape::BinaryTapePlayer> tape_player_;
uint8_t io_direction_ = 0x00, io_output_ = 0x00;
};
}
std::unique_ptr<Machine> Machine::Plus4(
const Analyser::Static::Target *target,
const ROMMachine::ROMFetcher &rom_fetcher

File diff suppressed because it is too large Load Diff

View File

@@ -13,52 +13,55 @@
using namespace Commodore::Serial;
const char *::Commodore::Serial::StringForLine(Line line) {
const char *::Commodore::Serial::to_string(Line line) {
switch(line) {
case ServiceRequest: return "Service request";
case Attention: return "Attention";
case Clock: return "Clock";
case Data: return "Data";
case Reset: return "Reset";
case Line::ServiceRequest: return "Service request";
case Line::Attention: return "Attention";
case Line::Clock: return "Clock";
case Line::Data: return "Data";
case Line::Reset: return "Reset";
default: return nullptr;
}
}
void ::Commodore::Serial::AttachPortAndBus(std::shared_ptr<Port> port, std::shared_ptr<Bus> bus) {
port->set_serial_bus(bus);
bus->add_port(port);
const char *::Commodore::Serial::to_string(LineLevel level) {
switch(level) {
case LineLevel::High : return "high";
case LineLevel::Low : return "low";
default: return nullptr;
}
}
void Bus::add_port(std::shared_ptr<Port> port) {
ports_.push_back(port);
for(int line = int(ServiceRequest); line <= int(Reset); line++) {
// the addition of a new device may change the line output...
void ::Commodore::Serial::attach(Port &port, Bus &bus) {
port.set_bus(bus);
bus.add_port(port);
}
void Bus::add_port(Port &port) {
ports_.push_back(&port);
for(int line = int(Line::ServiceRequest); line <= int(Line::Reset); line++) {
// The addition of a new device may change the line output...
set_line_output_did_change(Line(line));
// ... but the new device will need to be told the current state regardless
port->set_input(Line(line), line_levels_[line]);
// ... but the new device will need to be told the current state regardless.
port.set_input(Line(line), line_levels_[line]);
}
}
void Bus::set_line_output_did_change(Line line) {
// i.e. I believe these lines to be open collector
LineLevel new_line_level = High;
for(std::weak_ptr<Port> port : ports_) {
std::shared_ptr<Port> locked_port = port.lock();
if(locked_port) {
new_line_level = (LineLevel)(bool(new_line_level) & bool(locked_port->get_output(line)));
}
// Treat lines as open collector.
auto new_line_level = LineLevel::High;
for(auto port : ports_) {
new_line_level = LineLevel(bool(new_line_level) & bool(port->get_output(line)));
}
// post an update only if one occurred
if(new_line_level != line_levels_[line]) {
line_levels_[line] = new_line_level;
// Post an update only if one occurred.
const auto index = size_t(line);
if(new_line_level != line_levels_[index]) {
line_levels_[index] = new_line_level;
for(std::weak_ptr<Port> port : ports_) {
std::shared_ptr<Port> locked_port = port.lock();
if(locked_port) {
locked_port->set_input(line, new_line_level);
}
for(auto port : ports_) {
port->set_input(line, new_line_level);
}
}
}
@@ -66,14 +69,15 @@ void Bus::set_line_output_did_change(Line line) {
// MARK: - The debug port
void DebugPort::set_input(Line line, LineLevel value) {
input_levels_[line] = value;
const auto index = size_t(line);
input_levels_[index] = value;
std::cout << "[Bus] " << StringForLine(line) << " is " << (value ? "high" : "low");
std::cout << "[Bus] " << to_string(line) << " is " << to_string(value);
if(!incoming_count_) {
incoming_count_ = (!input_levels_[Line::Clock] && !input_levels_[Line::Data]) ? 8 : 0;
incoming_count_ = (!input_levels_[size_t(Line::Clock)] && !input_levels_[size_t(Line::Data)]) ? 8 : 0;
} else {
if(line == Line::Clock && value) {
incoming_byte_ = (incoming_byte_ >> 1) | (input_levels_[Line::Data] ? 0x80 : 0x00);
incoming_byte_ = (incoming_byte_ >> 1) | (input_levels_[size_t(Line::Data)] ? 0x80 : 0x00);
}
incoming_count_--;
if(incoming_count_ == 0) std::cout << "[Bus] Observed value " << std::hex << int(incoming_byte_);

View File

@@ -14,32 +14,29 @@
namespace Commodore::Serial {
enum Line {
enum class Line {
ServiceRequest = 0,
Attention,
Clock,
Data,
Reset
};
const char *to_string(Line line);
enum LineLevel: bool {
High = true,
Low = false
};
const char *to_string(LineLevel line);
class Port;
class Bus;
/*!
Returns a C string giving a human-readable name for the supplied line.
*/
const char *StringForLine(Line line);
/*!
Calls both of the necessary methods to (i) set @c bus as the target for @c port outputs; and
(ii) add @c port as one of the targets to which @c bus propagates line changes.
*/
void AttachPortAndBus(std::shared_ptr<Port> port, std::shared_ptr<Bus> bus);
void attach(Port &port, Bus &bus);
/*!
A serial bus is responsible for retaining a weakly-held collection of attached ports and for deciding the
@@ -48,22 +45,28 @@ namespace Commodore::Serial {
*/
class Bus {
public:
Bus() : line_levels_{High, High, High, High, High} {}
Bus() : line_levels_{
LineLevel::High,
LineLevel::High,
LineLevel::High,
LineLevel::High,
LineLevel::High
} {}
/*!
Adds the supplied port to the bus.
*/
void add_port(std::shared_ptr<Port> port);
void add_port(Port &);
/*!
Communicates to the bus that one of its attached port has changed its output level for the given line.
The bus will therefore recalculate bus state and propagate as necessary.
*/
void set_line_output_did_change(Line line);
void set_line_output_did_change(Line);
private:
LineLevel line_levels_[5];
std::vector<std::weak_ptr<Port>> ports_;
std::vector<Port *> ports_;
};
/*!
@@ -72,25 +75,30 @@ namespace Commodore::Serial {
*/
class Port {
public:
Port() : line_levels_{High, High, High, High, High} {}
Port() : line_levels_{
LineLevel::High,
LineLevel::High,
LineLevel::High,
LineLevel::High,
LineLevel::High
} {}
virtual ~Port() = default;
/*!
Sets the current level of an output line on this serial port.
*/
void set_output(Line line, LineLevel level) {
if(line_levels_[line] != level) {
line_levels_[line] = level;
std::shared_ptr<Bus> bus = serial_bus_.lock();
if(bus) bus->set_line_output_did_change(line);
if(line_levels_[size_t(line)] != level) {
line_levels_[size_t(line)] = level;
if(serial_bus_) serial_bus_->set_line_output_did_change(line);
}
}
/*!
Gets the previously set level of an output line.
*/
LineLevel get_output(Line line) {
return line_levels_[line];
LineLevel get_output(Line line) const {
return line_levels_[size_t(line)];
}
/*!
@@ -101,12 +109,12 @@ namespace Commodore::Serial {
/*!
Sets the supplied serial bus as that to which line levels will be communicated.
*/
inline void set_serial_bus(std::shared_ptr<Bus> serial_bus) {
serial_bus_ = serial_bus;
inline void set_bus(Bus &serial_bus) {
serial_bus_ = &serial_bus;
}
private:
std::weak_ptr<Bus> serial_bus_;
Bus *serial_bus_ = nullptr;
LineLevel line_levels_[5];
};

View File

@@ -105,14 +105,13 @@ public:
void set_port_output(const MOS::MOS6522::Port port, const uint8_t value, uint8_t) {
// Line 7 of port A is inverted and output as serial ATN.
if(!port) {
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
if(serialPort) serialPort->set_output(::Commodore::Serial::Line::Attention, (::Commodore::Serial::LineLevel)!(value&0x80));
serial_port_->set_output(Serial::Line::Attention, Serial::LineLevel(!(value&0x80)));
}
}
/// Sets @serial_port as this VIA's connection to the serial bus.
void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serial_port) {
serial_port_ = serial_port;
void set_serial_port(Serial::Port &serial_port) {
serial_port_ = &serial_port;
}
/// Sets @tape as the tape player connected to this VIA.
@@ -122,7 +121,7 @@ public:
private:
uint8_t port_a_;
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
Serial::Port *serial_port_ = nullptr;
std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_;
};
@@ -171,14 +170,11 @@ public:
/// Called by the 6522 to set control line output. Which affects the serial port.
void set_control_line_output(const MOS::MOS6522::Port port, const MOS::MOS6522::Line line, const bool value) {
if(line == MOS::MOS6522::Line::Two) {
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
if(serialPort) {
// CB2 is inverted to become serial data; CA2 is inverted to become serial clock
if(port == MOS::MOS6522::Port::A)
serialPort->set_output(::Commodore::Serial::Line::Clock, (::Commodore::Serial::LineLevel)!value);
serial_port_->set_output(Serial::Line::Clock, Serial::LineLevel(!value));
else
serialPort->set_output(::Commodore::Serial::Line::Data, (::Commodore::Serial::LineLevel)!value);
}
serial_port_->set_output(Serial::Line::Data, Serial::LineLevel(!value));
}
}
@@ -190,15 +186,15 @@ public:
}
/// Sets the serial port to which this VIA is connected.
void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) {
serial_port_ = serialPort;
void set_serial_port(Serial::Port &port) {
serial_port_ = &port;
}
private:
uint8_t port_b_;
uint8_t columns_[8];
uint8_t activation_mask_;
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
Serial::Port *serial_port_ = nullptr;
};
/*!
@@ -208,17 +204,16 @@ class SerialPort : public ::Commodore::Serial::Port {
public:
/// Receives an input change from the base serial port class, and communicates it to the user-port VIA.
void set_input(const ::Commodore::Serial::Line line, const ::Commodore::Serial::LineLevel level) {
std::shared_ptr<UserPortVIA> userPortVIA = user_port_via_.lock();
if(userPortVIA) userPortVIA->set_serial_line_state(line, bool(level));
if(user_port_via_) user_port_via_->set_serial_line_state(line, bool(level));
}
/// Sets the user-port VIA with which this serial port communicates.
void set_user_port_via(const std::shared_ptr<UserPortVIA> userPortVIA) {
user_port_via_ = userPortVIA;
void set_user_port_via(UserPortVIA &via) {
user_port_via_ = &via;
}
private:
std::weak_ptr<UserPortVIA> user_port_via_;
UserPortVIA *user_port_via_ = nullptr;
};
/*!
@@ -293,32 +288,28 @@ public:
ConcreteMachine(const Analyser::Static::Commodore::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
m6502_(*this),
mos6560_(mos6560_bus_handler_),
user_port_via_port_handler_(new UserPortVIA),
keyboard_via_port_handler_(new KeyboardVIA),
serial_port_(new SerialPort),
serial_bus_(new ::Commodore::Serial::Bus),
user_port_via_(*user_port_via_port_handler_),
keyboard_via_(*keyboard_via_port_handler_),
user_port_via_(user_port_via_port_handler_),
keyboard_via_(keyboard_via_port_handler_),
tape_(new Storage::Tape::BinaryTapePlayer(1022727)) {
// communicate the tape to the user-port VIA
user_port_via_port_handler_->set_tape(tape_);
user_port_via_port_handler_.set_tape(tape_);
// wire up the serial bus and serial port
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus_);
Commodore::Serial::attach(serial_port_, serial_bus_);
// wire up 6522s and serial port
user_port_via_port_handler_->set_serial_port(serial_port_);
keyboard_via_port_handler_->set_serial_port(serial_port_);
serial_port_->set_user_port_via(user_port_via_port_handler_);
user_port_via_port_handler_.set_serial_port(serial_port_);
keyboard_via_port_handler_.set_serial_port(serial_port_);
serial_port_.set_user_port_via(user_port_via_port_handler_);
// wire up the 6522s, tape and machine
user_port_via_port_handler_->set_interrupt_delegate(this);
keyboard_via_port_handler_->set_interrupt_delegate(this);
user_port_via_port_handler_.set_interrupt_delegate(this);
keyboard_via_port_handler_.set_interrupt_delegate(this);
tape_->set_delegate(this);
tape_->set_clocking_hint_observer(this);
// Install a joystick.
joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_));
joysticks_.emplace_back(new Joystick(user_port_via_port_handler_, keyboard_via_port_handler_));
ROM::Request request(ROM::Name::Vic20BASIC);
ROM::Name kernel, character;
@@ -361,7 +352,7 @@ public:
if(target.has_c1540) {
// construct the 1540
c1540_ = std::make_unique<::Commodore::C1540::Machine>(Commodore::C1540::Personality::C1540, roms);
c1540_ = std::make_unique<C1540::Machine>(C1540::Personality::C1540, roms);
// attach it to the serial bus
c1540_->set_serial_bus(serial_bus_);
@@ -472,7 +463,7 @@ public:
void set_key_state(const uint16_t key, const bool is_pressed) final {
if(key < 0xfff0) {
keyboard_via_port_handler_->set_key_state(key, is_pressed);
keyboard_via_port_handler_.set_key_state(key, is_pressed);
} else {
switch(key) {
case KeyRestore:
@@ -480,8 +471,8 @@ public:
break;
#define ShiftedMap(source, target) \
case source: \
keyboard_via_port_handler_->set_key_state(KeyLShift, is_pressed); \
keyboard_via_port_handler_->set_key_state(target, is_pressed); \
keyboard_via_port_handler_.set_key_state(KeyLShift, is_pressed); \
keyboard_via_port_handler_.set_key_state(target, is_pressed); \
break;
ShiftedMap(KeyUp, KeyDown);
@@ -496,7 +487,7 @@ public:
}
void clear_all_keys() final {
keyboard_via_port_handler_->clear_all_keys();
keyboard_via_port_handler_.clear_all_keys();
}
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
@@ -741,10 +732,10 @@ private:
Cycles cycles_since_mos6560_update_;
Vic6560BusHandler mos6560_bus_handler_;
MOS::MOS6560::MOS6560<Vic6560BusHandler> mos6560_;
std::shared_ptr<UserPortVIA> user_port_via_port_handler_;
std::shared_ptr<KeyboardVIA> keyboard_via_port_handler_;
std::shared_ptr<SerialPort> serial_port_;
std::shared_ptr<::Commodore::Serial::Bus> serial_bus_;
UserPortVIA user_port_via_port_handler_;
KeyboardVIA keyboard_via_port_handler_;
SerialPort serial_port_;
Serial::Bus serial_bus_;
MOS::MOS6522::MOS6522<UserPortVIA> user_port_via_;
MOS::MOS6522::MOS6522<KeyboardVIA> keyboard_via_;
@@ -760,7 +751,7 @@ private:
}
// Disk
std::shared_ptr<::Commodore::C1540::Machine> c1540_;
std::unique_ptr<::Commodore::C1540::Machine> c1540_;
};
}

View File

@@ -16,6 +16,7 @@
#include "../MachineTypes.hpp"
#include "../Utility/Typer.hpp"
#include "../../Activity/Source.hpp"
#include "../../Analyser/Static/Enterprise/Target.hpp"
#include "../../ClockReceiver/JustInTime.hpp"
#include "../../Outputs/Log.hpp"

View File

@@ -17,6 +17,8 @@
#include "PIT.hpp"
#include "RTC.hpp"
#include "../../Activity/Source.hpp"
#include "../../InstructionSets/x86/Decoder.hpp"
#include "../../InstructionSets/x86/Flags.hpp"
#include "../../InstructionSets/x86/Instruction.hpp"

View File

@@ -256,7 +256,7 @@ struct Request {
bool empty();
/// @returns what remains of this ROM request given that everything in @c map has been found.
Request subtract(const ROM::Map &map) const;
Request subtract(const Map &map) const;
enum class ListType {
Any, All, Single
@@ -303,7 +303,7 @@ private:
const std::function<void(void)> &exit_list,
const std::function<void(ROM::Request::ListType type, const ROM::Description &, bool is_optional, size_t remaining)> &add_item
) const;
bool subtract(const ROM::Map &map);
bool subtract(const Map &map);
void sort() {
// Don't do a full sort, but move anything optional to the back.
// This makes them print more nicely; it's a human-facing tweak only.

62
Numeric/UpperBound.hpp Normal file
View File

@@ -0,0 +1,62 @@
//
// UpperBound.hpp
// Clock Signal
//
// Created by Thomas Harte on 11/12/2024.
// Copyright © 2024 Thomas Harte. All rights reserved.
//
#pragma once
namespace Numeric {
/// @returns The element that is `index - offset` into the list given by the rest of the
/// variadic arguments or the final element if `offset` is out of bounds.
///
/// E.g. @c at_index<0, 3, 5, 6, 7, 8, 9>() returns the `3 - 0` = 4th element from the
/// list 5, 6, 7, 8, 9, i.e. 8.
template<int origin, int index, int T, int... Args>
constexpr int at_index() {
if constexpr (origin == index || sizeof...(Args) == 0) {
return T;
} else {
return at_index<origin + 1, index, Args...>();
}
}
/// @returns The result of binary searching for the first thing in the range `[left, right)` within
/// the other template arguments that is strictly greater than @c location.
template <int left, int right, int... Args>
int upper_bound_bounded(int location) {
if constexpr (left + 1 == right) {
return at_index<0, left+1, Args...>();
}
constexpr auto midpoint = (left + right) >> 1;
if(location >= at_index<0, midpoint, Args...>()) {
return upper_bound_bounded<midpoint, right, Args...>(location);
} else {
return upper_bound_bounded<left, midpoint, Args...>(location);
}
}
template <int index, int... Args>
constexpr int is_ordered() {
if constexpr (sizeof...(Args) == index + 1) {
return true;
} else {
return
(at_index<0, index, Args...>() < at_index<0, index+1, Args...>()) &&
is_ordered<index+1, Args...>();
}
}
/// @returns The result of binary searching for the first thing in the template arguments
/// is strictly greater than @c location.
template <int... Args>
int upper_bound(int location) {
static_assert(is_ordered<0, Args...>(), "Template arguments must be in ascending order.");
return upper_bound_bounded<0, sizeof...(Args), Args...>(location);
}
}

View File

@@ -595,6 +595,9 @@
4B7F188F2154825E00388727 /* MasterSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F188C2154825D00388727 /* MasterSystem.cpp */; };
4B7F1897215486A200388727 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F1896215486A100388727 /* StaticAnalyser.cpp */; };
4B7F1898215486A200388727 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F1896215486A100388727 /* StaticAnalyser.cpp */; };
4B8058032D0D0B55007EAD46 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8058022D0D0B55007EAD46 /* Keyboard.cpp */; };
4B8058042D0D0B55007EAD46 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8058022D0D0B55007EAD46 /* Keyboard.cpp */; };
4B8058052D0D0B55007EAD46 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8058022D0D0B55007EAD46 /* Keyboard.cpp */; };
4B80CD6F2568A82C00176FCC /* DiskIIDrive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B80CD6D2568A82600176FCC /* DiskIIDrive.cpp */; };
4B80CD76256CA16400176FCC /* 2MG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B80CD74256CA15E00176FCC /* 2MG.cpp */; };
4B80CD77256CA16600176FCC /* 2MG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B80CD74256CA15E00176FCC /* 2MG.cpp */; };
@@ -1296,6 +1299,10 @@
42EB812C2B47008D00429AF4 /* MemoryMap.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryMap.cpp; sourceTree = "<group>"; };
4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 65C02_extended_opcodes_test.bin; path = "Klaus Dormann/65C02_extended_opcodes_test.bin"; sourceTree = "<group>"; };
4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MemptrTests.swift; sourceTree = "<group>"; };
4B03291F2D0923E300C51EB5 /* Video.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = "<group>"; };
4B0329202D0A78DC00C51EB5 /* UpperBound.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = UpperBound.hpp; sourceTree = "<group>"; };
4B0329212D0A8C4700C51EB5 /* Pager.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Pager.hpp; sourceTree = "<group>"; };
4B0329222D0BD32500C51EB5 /* Interrupts.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Interrupts.hpp; sourceTree = "<group>"; };
4B0333AD2094081A0050B93D /* AppleDSK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AppleDSK.cpp; sourceTree = "<group>"; };
4B0333AE2094081A0050B93D /* AppleDSK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AppleDSK.hpp; sourceTree = "<group>"; };
4B046DC31CFE651500E9E45E /* ScanProducer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ScanProducer.hpp; sourceTree = "<group>"; };
@@ -1710,6 +1717,9 @@
4B7F1895215486A100388727 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
4B7F1896215486A100388727 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
4B80214322EE7C3E00068002 /* JustInTime.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = JustInTime.hpp; sourceTree = "<group>"; };
4B8058012D0D0B55007EAD46 /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = "<group>"; };
4B8058022D0D0B55007EAD46 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Keyboard.cpp; sourceTree = "<group>"; };
4B8058062D23902B007EAD46 /* Audio.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Audio.hpp; sourceTree = "<group>"; };
4B80CD6D2568A82600176FCC /* DiskIIDrive.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DiskIIDrive.cpp; sourceTree = "<group>"; };
4B80CD6E2568A82900176FCC /* DiskIIDrive.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskIIDrive.hpp; sourceTree = "<group>"; };
4B80CD74256CA15E00176FCC /* 2MG.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = 2MG.cpp; sourceTree = "<group>"; };
@@ -3391,8 +3401,14 @@
4B596EB12D037E8800FBF4B1 /* Plus4 */ = {
isa = PBXGroup;
children = (
4B596EAF2D037E8800FBF4B1 /* Plus4.hpp */,
4B8058022D0D0B55007EAD46 /* Keyboard.cpp */,
4B596EB02D037E8800FBF4B1 /* Plus4.cpp */,
4B0329222D0BD32500C51EB5 /* Interrupts.hpp */,
4B8058012D0D0B55007EAD46 /* Keyboard.hpp */,
4B0329212D0A8C4700C51EB5 /* Pager.hpp */,
4B596EAF2D037E8800FBF4B1 /* Plus4.hpp */,
4B03291F2D0923E300C51EB5 /* Video.hpp */,
4B8058062D23902B007EAD46 /* Audio.hpp */,
);
path = Plus4;
sourceTree = "<group>";
@@ -3635,13 +3651,14 @@
children = (
4B43984129674943006B0BFC /* BitReverse.hpp */,
4BD155312716362A00410C6E /* BitSpread.hpp */,
4281572E2AA0334300E16AA1 /* Carry.hpp */,
4B7BA03E23D55E7900B98D9E /* CRC.hpp */,
4B7BA03F23D55E7900B98D9E /* LFSR.hpp */,
4B66E1A8297719270057ED0F /* NumericCoder.hpp */,
4BB5B995281B1D3E00522DA9 /* RegisterSizes.hpp */,
4BFEA2F12682A90200EBF94C /* Sizes.hpp */,
4281572E2AA0334300E16AA1 /* Carry.hpp */,
4BD9713A2BFD7E7100C907AA /* StringSimilarity.hpp */,
4B0329202D0A78DC00C51EB5 /* UpperBound.hpp */,
);
name = Numeric;
path = ../../Numeric;
@@ -5468,7 +5485,7 @@
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 0700;
LastUpgradeCheck = 1610;
LastUpgradeCheck = 1620;
ORGANIZATIONNAME = "Thomas Harte";
TargetAttributes = {
4B055A691FAE763F0060FFFF = {
@@ -6096,6 +6113,7 @@
4B8DD3872634D37E00B3C866 /* SNA.cpp in Sources */,
4BB505792B962DDF0031C43C /* SoundGenerator.cpp in Sources */,
4B055AA91FAE85EF0060FFFF /* CommodoreGCR.cpp in Sources */,
4B8058042D0D0B55007EAD46 /* Keyboard.cpp in Sources */,
4B055ADB1FAE9B460060FFFF /* 6560.cpp in Sources */,
4B17B58C20A8A9D9007CCA8F /* StringSerialiser.cpp in Sources */,
4B055AA01FAE85DA0060FFFF /* MFMSectorDump.cpp in Sources */,
@@ -6362,6 +6380,7 @@
4BB4BFAD22A33DE50069048D /* DriveSpeedAccumulator.cpp in Sources */,
4BC080D926A25ADA00D03FD8 /* Amiga.cpp in Sources */,
4B2B3A4B1F9B8FA70062DABF /* Typer.cpp in Sources */,
4B8058052D0D0B55007EAD46 /* Keyboard.cpp in Sources */,
4B4518821F75E91A00926311 /* PCMSegment.cpp in Sources */,
4B0DA67B282DCDF100C12F17 /* Instruction.cpp in Sources */,
4B74CF812312FA9C00500CE8 /* HFV.cpp in Sources */,
@@ -6696,6 +6715,7 @@
4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */,
4B778F2123A5EDD50000D260 /* TrackSerialiser.cpp in Sources */,
4B049CDD1DA3C82F00322067 /* BCDTest.swift in Sources */,
4B8058032D0D0B55007EAD46 /* Keyboard.cpp in Sources */,
4B06AADF2C645F830034D014 /* Video.cpp in Sources */,
4BC6237226F94BCB00F83DFE /* MintermTests.mm in Sources */,
4B7752BF28217F250073E2C5 /* Sprites.cpp in Sources */,

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1610"
LastUpgradeVersion = "1620"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1610"
LastUpgradeVersion = "1620"
version = "1.8">
<BuildAction
parallelizeBuildables = "YES"
@@ -23,7 +23,7 @@
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
@@ -74,7 +74,7 @@
debugDocumentVersioning = "YES"
migratedStopOnEveryIssue = "YES"
debugServiceExtension = "internal"
enableGPUShaderValidationMode = "2"
enableGPUValidationMode = "1"
allowLocationSimulation = "NO">
<BuildableProductRunnable
runnableDebuggingMode = "0">

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1610"
LastUpgradeVersion = "1620"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="23504" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22690"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23504"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -18,7 +18,7 @@
<windowStyleMask key="styleMask" titled="YES" documentModal="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="590" height="369"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1440"/>
<rect key="screenRect" x="0.0" y="0.0" width="1470" height="924"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="590" height="369"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@@ -125,7 +125,7 @@ Gw
<rect key="frame" x="110" y="210" width="80" height="25"/>
<popUpButtonCell key="cell" type="push" title="512 kb" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="512" imageScaling="axesIndependently" inset="2" selectedItem="Zev-ku-jDG" id="vdO-VR-mUx">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<font key="font" metaFont="message"/>
<menu key="menu" id="Zvi-ox-ip4">
<items>
<menuItem title="512 kb" state="on" tag="512" id="Zev-ku-jDG"/>
@@ -155,7 +155,7 @@ Gw
<rect key="frame" x="108" y="180" width="72" height="25"/>
<popUpButtonCell key="cell" type="push" title="None" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="zV7-V8-c7s" id="39D-ms-pf9">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<font key="font" metaFont="message"/>
<menu key="menu" id="yIx-1y-Ab0">
<items>
<menuItem title="None" state="on" id="zV7-V8-c7s"/>
@@ -192,7 +192,7 @@ Gw
<rect key="frame" x="67" y="210" width="96" height="25"/>
<popUpButtonCell key="cell" type="push" title="CPC6128" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="6128" imageScaling="axesIndependently" inset="2" selectedItem="LgZ-9j-YQl" id="yH2-Vm-hiD">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<font key="font" metaFont="message"/>
<menu key="menu" id="FdB-a4-Cox">
<items>
<menuItem title="CPC464" tag="464" id="VSM-l2-mWa"/>
@@ -246,7 +246,7 @@ Gw
<rect key="frame" x="67" y="210" width="117" height="25"/>
<popUpButtonCell key="cell" type="push" title="Apple II" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="VBQ-JG-AeM" id="U6V-us-O2F">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<font key="font" metaFont="message"/>
<menu key="menu" id="esp-ir-7iH">
<items>
<menuItem title="Apple II" state="on" id="VBQ-JG-AeM"/>
@@ -261,7 +261,7 @@ Gw
<rect key="frame" x="119" y="180" width="134" height="25"/>
<popUpButtonCell key="cell" type="push" title="Sixteen Sector" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="16" imageScaling="axesIndependently" inset="2" selectedItem="QaV-Yr-k9o" id="8BT-RV-2Nm">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<font key="font" metaFont="message"/>
<menu key="menu" id="udQ-NK-FG8">
<items>
<menuItem title="Sixteen Sector" state="on" tag="16" id="QaV-Yr-k9o"/>
@@ -321,7 +321,7 @@ Gw
<rect key="frame" x="67" y="210" width="89" height="25"/>
<popUpButtonCell key="cell" type="push" title="ROM 03" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="2" imageScaling="axesIndependently" inset="2" selectedItem="0TS-DO-O9h" id="hjw-g8-e2f">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<font key="font" metaFont="message"/>
<menu key="menu" id="4rn-K4-QgV">
<items>
<menuItem title="ROM 00" id="2LO-US-byb"/>
@@ -335,7 +335,7 @@ Gw
<rect key="frame" x="108" y="180" width="82" height="25"/>
<popUpButtonCell key="cell" type="push" title="8 mb" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="8192" imageScaling="axesIndependently" inset="2" selectedItem="UHg-gU-Xnn" id="dl3-cq-uWO">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<font key="font" metaFont="message"/>
<menu key="menu" id="Scm-d9-EU9">
<items>
<menuItem title="256 kb" tag="256" id="PZE-8h-MNs"/>
@@ -400,7 +400,7 @@ Gw
<rect key="frame" x="79" y="210" width="80" height="25"/>
<popUpButtonCell key="cell" type="push" title="512 kb" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="512" imageScaling="axesIndependently" inset="2" selectedItem="LVX-CI-lo9" id="dSS-yv-CDV">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<font key="font" metaFont="message"/>
<menu key="menu" id="jsD-9U-bwN">
<items>
<menuItem title="512 kb" state="on" tag="512" id="LVX-CI-lo9"/>
@@ -481,7 +481,7 @@ Gw
<rect key="frame" x="80" y="210" width="129" height="25"/>
<popUpButtonCell key="cell" type="push" title="Enterprise 128" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="128" imageScaling="axesIndependently" inset="2" selectedItem="roH-nL-f8o" id="z9O-XC-XBv">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<font key="font" metaFont="message"/>
<menu key="menu" id="ojI-Vb-iHz">
<items>
<menuItem title="Enterprise 256" tag="256" id="Al3-A0-tvw"/>
@@ -495,7 +495,7 @@ Gw
<rect key="frame" x="67" y="180" width="77" height="25"/>
<popUpButtonCell key="cell" type="push" title="4 Mhz" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="4" imageScaling="axesIndependently" inset="2" selectedItem="5N6-tD-uN7" id="BU2-NJ-Kii">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<font key="font" metaFont="message"/>
<menu key="menu" id="ANK-p0-M77">
<items>
<menuItem title="4 Mhz" state="on" tag="4" id="5N6-tD-uN7"/>
@@ -508,7 +508,7 @@ Gw
<rect key="frame" x="64" y="150" width="107" height="25"/>
<popUpButtonCell key="cell" type="push" title="Version 2.1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="21" imageScaling="axesIndependently" inset="2" selectedItem="Qja-xZ-wVM" id="xGG-ri-8Sb">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<font key="font" metaFont="message"/>
<menu key="menu" id="uNC-hA-d5z">
<items>
<menuItem title="Version 2.1" state="on" tag="21" id="Qja-xZ-wVM"/>
@@ -522,7 +522,7 @@ Gw
<rect key="frame" x="67" y="120" width="105" height="25"/>
<popUpButtonCell key="cell" type="push" title="Version 2.1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="21" imageScaling="axesIndependently" inset="2" selectedItem="TME-cv-Jh1" id="9mQ-GW-lq9">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<font key="font" metaFont="message"/>
<menu key="menu" id="VcH-Xm-1hY">
<items>
<menuItem title="Version 2.1" state="on" tag="21" id="TME-cv-Jh1"/>
@@ -537,7 +537,7 @@ Gw
<rect key="frame" x="57" y="90" width="83" height="25"/>
<popUpButtonCell key="cell" type="push" title="EXDOS" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="1" imageScaling="axesIndependently" inset="2" selectedItem="8rP-2w-PdU" id="NvO-Zm-2Rq">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<font key="font" metaFont="message"/>
<menu key="menu" title="DOS" id="sdr-al-7mi">
<items>
<menuItem title="EXDOS" state="on" tag="1" id="8rP-2w-PdU"/>
@@ -636,7 +636,7 @@ Gw
<rect key="frame" x="67" y="210" width="75" height="25"/>
<popUpButtonCell key="cell" type="push" title="Plus" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="3" imageScaling="axesIndependently" inset="2" selectedItem="R6T-hg-rOF" id="1Kb-Q2-BGM">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<font key="font" metaFont="message"/>
<menu key="menu" id="ofy-j9-YnU">
<items>
<menuItem title="128k" id="WCG-6u-ANQ"/>
@@ -674,7 +674,7 @@ Gw
<rect key="frame" x="71" y="180" width="146" height="25"/>
<popUpButtonCell key="cell" type="push" title="European (PAL)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="xAh-Ch-tby" id="yR4-yv-Lvu">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<font key="font" metaFont="message"/>
<menu key="menu" id="BCR-rp-Kpc">
<items>
<menuItem title="European (PAL)" state="on" id="xAh-Ch-tby"/>
@@ -704,7 +704,7 @@ Gw
<rect key="frame" x="67" y="210" width="79" height="25"/>
<popUpButtonCell key="cell" type="push" title="MSX 1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="1" imageScaling="axesIndependently" inset="2" selectedItem="G9U-vd-Cjk" id="2TO-f6-Gmp">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<font key="font" metaFont="message"/>
<menu key="menu" id="v3T-Jv-VLK">
<items>
<menuItem title="MSX 1" state="on" tag="1" id="G9U-vd-Cjk"/>
@@ -759,7 +759,7 @@ Gw
<rect key="frame" x="67" y="210" width="107" height="25"/>
<popUpButtonCell key="cell" type="push" title="Oric-1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="jGN-1a-biF" id="Jll-EJ-cMr">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<font key="font" metaFont="message"/>
<menu key="menu" id="E9d-fH-Eak">
<items>
<menuItem title="Oric-1" state="on" id="jGN-1a-biF"/>
@@ -773,7 +773,7 @@ Gw
<rect key="frame" x="113" y="180" width="130" height="25"/>
<popUpButtonCell key="cell" type="push" title="None" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="XhK-Jh-oTW" id="aYb-m1-H9X">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<font key="font" metaFont="message"/>
<menu key="menu" id="hy3-Li-nlW">
<items>
<menuItem title="None" state="on" id="XhK-Jh-oTW"/>
@@ -818,7 +818,7 @@ Gw
<rect key="frame" x="116" y="210" width="68" height="25"/>
<popUpButtonCell key="cell" type="push" title="MDA" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="oIy-If-5bQ" id="xz8-mu-ynU">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<font key="font" metaFont="message"/>
<menu key="menu" id="dju-ZY-2DH">
<items>
<menuItem title="MDA" state="on" id="oIy-If-5bQ"/>
@@ -847,7 +847,7 @@ Gw
<rect key="frame" x="68" y="180" width="146" height="25"/>
<popUpButtonCell key="cell" type="push" title="Similar to Original" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="8086" imageScaling="axesIndependently" inset="2" selectedItem="R4W-s4-KFx" id="9i0-UG-B2c">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<font key="font" metaFont="message"/>
<menu key="menu" id="KQw-8g-SrI">
<items>
<menuItem title="Similar to Original" state="on" tag="8086" id="R4W-s4-KFx"/>
@@ -881,7 +881,7 @@ Gw
<rect key="frame" x="71" y="210" width="146" height="25"/>
<popUpButtonCell key="cell" type="push" title="European (PAL)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="45i-0n-gau" id="yi7-eo-I0q">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<font key="font" metaFont="message"/>
<menu key="menu" id="uCQ-9l-eBb">
<items>
<menuItem title="European (PAL)" state="on" id="45i-0n-gau"/>
@@ -897,7 +897,7 @@ Gw
<rect key="frame" x="108" y="180" width="116" height="25"/>
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="fOl-8Q-fsA" id="rH0-7T-pJE">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<font key="font" metaFont="message"/>
<menu key="menu" id="e9J-Ie-PjH">
<items>
<menuItem title="Unexpanded" state="on" id="fOl-8Q-fsA"/>
@@ -958,7 +958,7 @@ Gw
<rect key="frame" x="108" y="210" width="116" height="25"/>
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="4Sa-jR-xOd" id="B8M-do-Yod">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<font key="font" metaFont="message"/>
<menu key="menu" id="Of4-DY-mE5">
<items>
<menuItem title="Unexpanded" state="on" id="4Sa-jR-xOd"/>
@@ -1006,7 +1006,7 @@ Gw
<rect key="frame" x="108" y="210" width="116" height="25"/>
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="7QC-Ij-hES" id="d3W-Gl-3Mf">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<font key="font" metaFont="message"/>
<menu key="menu" id="mua-Lp-9wl">
<items>
<menuItem title="Unexpanded" state="on" id="7QC-Ij-hES"/>
@@ -1044,7 +1044,7 @@ Gw
<rect key="frame" x="67" y="210" width="76" height="25"/>
<popUpButtonCell key="cell" type="push" title="16kb" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="16" imageScaling="axesIndependently" inset="2" selectedItem="Fo7-NL-Kv5" id="tYs-sA-oek">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<font key="font" metaFont="message"/>
<menu key="menu" id="8lt-dk-zPr">
<items>
<menuItem title="16kb" state="on" tag="16" id="Fo7-NL-Kv5"/>

View File

@@ -390,6 +390,7 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
[encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
[encoder endEncoding];
encoder = nil;
[commandBuffer commit];
return commandBuffer;
@@ -885,6 +886,7 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
// Complete encoding.
[encoder endEncoding];
encoder = nil;
}
- (void)outputFrameCleanerToCommandBuffer:(id<MTLCommandBuffer>)commandBuffer {
@@ -901,11 +903,12 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
[encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
[encoder endEncoding];
encoder = nil;
}
- (void)composeOutputArea:(const BufferingScanTarget::OutputArea &)outputArea commandBuffer:(id<MTLCommandBuffer>)commandBuffer {
// Output all scans to the composition buffer.
const id<MTLRenderCommandEncoder> encoder = [commandBuffer renderCommandEncoderWithDescriptor:_compositionRenderPass];
id<MTLRenderCommandEncoder> encoder = [commandBuffer renderCommandEncoderWithDescriptor:_compositionRenderPass];
[encoder setRenderPipelineState:_composePipeline];
[encoder setVertexBuffer:_scansBuffer offset:0 atIndex:0];
@@ -919,6 +922,7 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
RangePerform(outputArea.start.scan, outputArea.end.scan, NumBufferedScans, OutputScans);
#undef OutputScans
[encoder endEncoding];
encoder = nil;
}
- (id<MTLBuffer>)bufferForOffset:(size_t)offset {
@@ -1141,6 +1145,7 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
[encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
[encoder endEncoding];
encoder = nil;
[commandBuffer presentDrawable:view.currentDrawable];
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> _Nonnull) {

View File

@@ -8,10 +8,10 @@
#include "CRT.hpp"
#include <cstdarg>
#include <cmath>
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdarg>
using namespace Outputs::CRT;
@@ -458,40 +458,58 @@ void CRT::output_data(int number_of_cycles, size_t number_of_samples) {
// MARK: - Getters.
Outputs::Display::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_of_lines, int first_cycle_after_sync, int number_of_cycles, float aspect_ratio) const {
Outputs::Display::Rect CRT::get_rect_for_area(
int first_line_after_sync,
int number_of_lines,
int first_cycle_after_sync,
int number_of_cycles,
float aspect_ratio
) const {
assert(number_of_cycles > 0);
assert(number_of_lines > 0);
assert(first_line_after_sync >= 0);
assert(first_cycle_after_sync >= 0);
// Scale up x coordinates and add a little extra leeway to y.
first_cycle_after_sync *= time_multiplier_;
number_of_cycles *= time_multiplier_;
first_line_after_sync -= 2;
number_of_lines += 4;
// determine prima facie x extent
// Determine prima facie x extent.
const int horizontal_period = horizontal_flywheel_->get_standard_period();
const int horizontal_scan_period = horizontal_flywheel_->get_scan_period();
const int horizontal_retrace_period = horizontal_period - horizontal_scan_period;
// make sure that the requested range is visible
if(int(first_cycle_after_sync) < horizontal_retrace_period) first_cycle_after_sync = int(horizontal_retrace_period);
if(int(first_cycle_after_sync + number_of_cycles) > horizontal_scan_period) number_of_cycles = int(horizontal_scan_period - int(first_cycle_after_sync));
// Ensure requested range is within visible region.
first_cycle_after_sync = std::max(horizontal_retrace_period, first_cycle_after_sync);
number_of_cycles = std::min(horizontal_period - first_cycle_after_sync, number_of_cycles);
float start_x = float(int(first_cycle_after_sync) - horizontal_retrace_period) / float(horizontal_scan_period);
float start_x = float(first_cycle_after_sync - horizontal_retrace_period) / float(horizontal_scan_period);
float width = float(number_of_cycles) / float(horizontal_scan_period);
// determine prima facie y extent
// Determine prima facie y extent.
const int vertical_period = vertical_flywheel_->get_standard_period();
const int vertical_scan_period = vertical_flywheel_->get_scan_period();
const int vertical_retrace_period = vertical_period - vertical_scan_period;
// make sure that the requested range is visible
// if(int(first_line_after_sync) * horizontal_period < vertical_retrace_period)
// first_line_after_sync = (vertical_retrace_period + horizontal_period - 1) / horizontal_period;
// if((first_line_after_sync + number_of_lines) * horizontal_period > vertical_scan_period)
// number_of_lines = int(horizontal_scan_period - int(first_cycle_after_sync));
// Ensure range is visible.
first_line_after_sync = std::max(
first_line_after_sync * horizontal_period,
vertical_retrace_period
) / horizontal_period;
number_of_lines = std::min(
vertical_period - first_line_after_sync * horizontal_period,
number_of_lines * horizontal_period
) / horizontal_period;
float start_y = float((int(first_line_after_sync) * horizontal_period) - vertical_retrace_period) / float(vertical_scan_period);
float height = float(int(number_of_lines) * horizontal_period) / vertical_scan_period;
float start_y =
float(first_line_after_sync * horizontal_period - vertical_retrace_period) /
float(vertical_scan_period);
float height = float(number_of_lines * horizontal_period) / vertical_scan_period;
// adjust to ensure aspect ratio is correct
// Pick a zoom that includes the entire requested visible area given the aspect ratio constraints.
const float adjusted_aspect_ratio = (3.0f*aspect_ratio / 4.0f);
const float ideal_width = height * adjusted_aspect_ratio;
if(ideal_width > width) {
@@ -503,6 +521,8 @@ Outputs::Display::Rect CRT::get_rect_for_area(int first_line_after_sync, int num
height = ideal_height;
}
// TODO: apply absolute clipping constraints now.
return Outputs::Display::Rect(start_x, start_y, width, height);
}

View File

@@ -0,0 +1,12 @@
ROM files would ordinarily go here; the copyright status of these is uncertain so they have not been included in this repository.
Expected files:
At least one of:
kernal.318004-03.bin — the PAL kernel, v3;
kernal.318004-04.bin — the PAL kernel, v4; and
kernal.318004-05.bin — the PAL kernel, v5.
And:
basic.318006-01.bin — the BASIC 3.5 ROM.

View File

@@ -12,7 +12,7 @@ using namespace Storage::Tape;
// MARK: - Lifecycle
TapePlayer::TapePlayer(int input_clock_rate) :
TapePlayer::TapePlayer(const int input_clock_rate) :
TimedEventLoop(input_clock_rate)
{}
@@ -129,7 +129,7 @@ void TapePlayer::process_next_event() {
// MARK: - Binary Player
BinaryTapePlayer::BinaryTapePlayer(int input_clock_rate) :
BinaryTapePlayer::BinaryTapePlayer(const int input_clock_rate) :
TapePlayer(input_clock_rate)
{}
@@ -138,7 +138,7 @@ ClockingHint::Preference BinaryTapePlayer::preferred_clocking() const {
return TapePlayer::preferred_clocking();
}
void BinaryTapePlayer::set_motor_control(bool enabled) {
void BinaryTapePlayer::set_motor_control(const bool enabled) {
if(motor_is_running_ != enabled) {
motor_is_running_ = enabled;
update_clocking_observer();
@@ -149,7 +149,7 @@ void BinaryTapePlayer::set_motor_control(bool enabled) {
}
}
void BinaryTapePlayer::set_activity_observer(Activity::Observer *observer) {
void BinaryTapePlayer::set_activity_observer(Activity::Observer *const observer) {
observer_ = observer;
if(observer) {
observer->register_led("Tape motor");
@@ -173,7 +173,7 @@ void BinaryTapePlayer::run_for(const Cycles cycles) {
if(motor_is_running_) TapePlayer::run_for(cycles);
}
void BinaryTapePlayer::set_delegate(Delegate *delegate) {
void BinaryTapePlayer::set_delegate(Delegate *const delegate) {
delegate_ = delegate;
}

View File

@@ -13,11 +13,10 @@
#include "../TimedEventLoop.hpp"
#include "../../Activity/Source.hpp"
#include "../../Activity/Observer.hpp"
#include "../TargetPlatforms.hpp"
#include <memory>
#include <optional>
namespace Storage::Tape {
@@ -112,7 +111,7 @@ public:
bool has_tape() const;
std::shared_ptr<Storage::Tape::Tape> tape();
void run_for(const Cycles cycles);
void run_for(Cycles);
void run_for_input_pulse();
ClockingHint::Preference preferred_clocking() const override;
@@ -148,12 +147,12 @@ public:
void set_tape_output(bool);
bool input() const;
void run_for(const Cycles cycles);
void run_for(Cycles);
struct Delegate {
virtual void tape_did_change_input(BinaryTapePlayer *) = 0;
};
void set_delegate(Delegate *delegate);
void set_delegate(Delegate *);
ClockingHint::Preference preferred_clocking() const final;

View File

@@ -118,6 +118,7 @@ set(CLK_SOURCES
Machines/Atari/ST/Video.cpp
Machines/ColecoVision/ColecoVision.cpp
Machines/Commodore/1540/Implementation/C1540.cpp
Machines/Commodore/Plus4/Keyboard.cpp
Machines/Commodore/Plus4/Plus4.cpp
Machines/Commodore/SerialBus.cpp
Machines/Commodore/Vic-20/Keyboard.cpp