a26: add QuadTari controller support

This commit is contained in:
ZengGengSen
2026-08-30 19:24:24 +01:00
committed by Luke Usher
parent 8381b5ae05
commit 24ab7df1e4
12 changed files with 262 additions and 11 deletions
+2
View File
@@ -108,6 +108,8 @@ ares_add_sources(
controller/kidvid/kidvid.cpp
controller/kidvid/kidvid.hpp
controller/kidvid/tables.hpp
controller/quadtari/quadtari.cpp
controller/quadtari/quadtari.hpp
)
ares_add_sources(
+1
View File
@@ -22,5 +22,6 @@ namespace ares::Atari2600 {
#include "atarivox/atarivox.cpp"
#include "kidvid/audio.cpp"
#include "kidvid/kidvid.cpp"
#include "quadtari/quadtari.cpp"
}
+3
View File
@@ -18,6 +18,8 @@ struct Controller {
virtual auto power(bool reset) -> void {}
virtual auto poll() -> void {}
virtual auto frame() -> void {}
virtual auto clock() -> void {}
virtual auto vblank(n1 dumped) -> void {}
virtual auto read() -> n8 { return 0xff; }
virtual auto write(n8 data) -> void {}
virtual auto controlWrite(n8 data) -> void {}
@@ -42,3 +44,4 @@ struct Controller {
#include "savekey/savekey.hpp"
#include "atarivox/atarivox.hpp"
#include "kidvid/kidvid.hpp"
#include "quadtari/quadtari.hpp"
+6 -3
View File
@@ -1,8 +1,10 @@
Paddles::Paddles(Node::Port parent) {
Paddles::Paddles(Node::Port parent, bool axes) {
node = parent->append<Node::Peripheral>("Paddles");
axis[0] = node->append<Node::Input::Axis>("Paddle 1");
axis[1] = node->append<Node::Input::Axis>("Paddle 2");
if(axes) {
axis[0] = node->append<Node::Input::Axis>("Paddle 1");
axis[1] = node->append<Node::Input::Axis>("Paddle 2");
}
fire[0] = node->append<Node::Input::Button>("Paddle 1 Fire");
fire[1] = node->append<Node::Input::Button>("Paddle 2 Fire");
}
@@ -18,6 +20,7 @@ auto Paddles::read() -> n8 {
}
auto Paddles::readAnalog(n1 index) -> AnalogConnection {
if(!axis[index]) return AnalogConnection::disconnected();
platform->input(axis[index]);
auto value = std::clamp<s64>(axis[index]->value(), -32768, 32767);
auto resistance = (u64)(32767 - value) * MaximumResistance / 65535;
+1 -1
View File
@@ -2,7 +2,7 @@ struct Paddles : Controller {
Node::Input::Axis axis[2];
Node::Input::Button fire[2];
Paddles(Node::Port);
Paddles(Node::Port, bool axes = true);
auto read() -> n8 override;
auto readAnalog(n1 index) -> AnalogConnection override;
+10 -6
View File
@@ -18,7 +18,7 @@ auto ControllerPort::load(Node::Object parent) -> void {
"Gamepad", "Paddles", "Driving", "Keyboard",
"Booster Grip", "Sega Genesis", "Joy 2B+",
"CX-22 Trak-Ball", "CX-80 Trak-Ball", "Atari Mouse", "Amiga Mouse",
"XG-1 Light Gun", "MindLink", "SaveKey", "AtariVox",
"XG-1 Light Gun", "MindLink", "SaveKey", "AtariVox", "QuadTari",
};
if(name == "Controller Port 2") supported.push_back("KidVid Voice Module");
port->setSupported(supported);
@@ -32,8 +32,8 @@ auto ControllerPort::unload() -> void {
}
auto ControllerPort::allocate(string name) -> Node::Peripheral {
if(name == "KidVid Voice Module" && this != &controllerPort2) device = {};
else device = create(port, name);
auto role = this == &controllerPort2 ? Role::ConsoleRight : Role::ConsoleLeft;
device = create(port, name, role);
if(device) {
device->write(output);
return device->node;
@@ -41,9 +41,10 @@ auto ControllerPort::allocate(string name) -> Node::Peripheral {
return {};
}
auto ControllerPort::create(Node::Port port, string name) -> std::unique_ptr<Controller> {
auto ControllerPort::create(Node::Port port, string name, Role role) -> std::unique_ptr<Controller> {
auto quadTariChild = role == Role::QuadTariChild;
if(name == "Gamepad") return std::make_unique<Gamepad> (port);
if(name == "Paddles") return std::make_unique<Paddles> (port);
if(name == "Paddles") return std::make_unique<Paddles> (port, !quadTariChild);
if(name == "Driving") return std::make_unique<Driving> (port);
if(name == "Keyboard") return std::make_unique<Keyboard> (port);
if(name == "Booster Grip") return std::make_unique<BoosterGrip>(port);
@@ -57,7 +58,10 @@ auto ControllerPort::create(Node::Port port, string name) -> std::unique_ptr<Con
if(name == "MindLink") return std::make_unique<MindLink> (port);
if(name == "SaveKey") return std::make_unique<SaveKey> (port);
if(name == "AtariVox") return std::make_unique<AtariVox> (port);
if(name == "KidVid Voice Module") return std::make_unique<KidVid>(port);
if(name == "KidVid Voice Module")
if(role == Role::ConsoleRight) return std::make_unique<KidVid> (port);
if(name == "QuadTari")
if(!quadTariChild) return std::make_unique<QuadTari> (port);
return {};
}
+5 -1
View File
@@ -1,4 +1,6 @@
struct ControllerPort {
enum class Role : u32 { ConsoleLeft, ConsoleRight, QuadTariChild };
Node::Port port;
std::unique_ptr<Controller> device;
@@ -7,7 +9,7 @@ struct ControllerPort {
auto load(Node::Object) -> void;
auto unload() -> void;
auto allocate(string name) -> Node::Peripheral;
static auto create(Node::Port, string name) -> std::unique_ptr<Controller>;
static auto create(Node::Port, string name, Role) -> std::unique_ptr<Controller>;
auto save() -> void { if(device) device->save(); }
auto power(bool reset) -> void {
if(device) {
@@ -18,6 +20,8 @@ struct ControllerPort {
auto poll() -> void { if(device) device->poll(); }
auto frame() -> void { if(device) device->frame(); }
auto clock() -> void { if(device) device->clock(); }
auto vblank(n1 dumped) -> void { if(device) device->vblank(dumped); }
auto read() -> n8 { if(device) return device->read(); return 0xff; }
auto write(n8 data) -> void {
output = data;
+152
View File
@@ -0,0 +1,152 @@
QuadTari::QuadTari(Node::Port parent) : first(*this), second(*this) {
node = parent->append<Node::Peripheral>("QuadTari");
auto left = parent->name() == "Controller Port 1";
first.load(node, left ? "QuadTari Player 1" : "QuadTari Player 2");
second.load(node, left ? "QuadTari Player 3" : "QuadTari Player 4");
}
QuadTari::~QuadTari() {
first.unload();
second.unload();
}
auto QuadTari::Slot::load(Node::Peripheral parent, string name) -> void {
port = parent->append<Node::Port>(name);
port->setFamily(system.name());
port->setType("Controller");
port->setHotSwappable(true);
port->setAllocate([&](auto name) { return allocate(name); });
port->setDisconnect([&] {
save();
device.reset();
});
port->setSupported({"Gamepad", "Paddles", "Driving", "SaveKey", "AtariVox"});
port->allocate("Gamepad");
port->connect();
}
auto QuadTari::Slot::unload() -> void {
save();
device = {};
port = {};
}
auto QuadTari::Slot::allocate(string name) -> Node::Peripheral {
save();
device = {};
if(name != "Gamepad" && name != "Paddles" && name != "Driving" && name != "SaveKey" && name != "AtariVox") {
return {};
}
device = ControllerPort::create(port, name, ControllerPort::Role::QuadTariChild);
if(device) {
device->write(owner.output);
device->controlWrite(owner.control);
return device->node;
}
return {};
}
auto QuadTari::Slot::save() -> void {
if(device) device->save();
}
auto QuadTari::Slot::power(bool reset) -> void {
if(device) device->power(reset);
}
auto QuadTari::Slot::serialize(serializer& s) -> void {
if(device) device->serialize(s);
}
auto QuadTari::save() -> void {
first.save();
second.save();
}
auto QuadTari::power(bool reset) -> void {
first.power(reset);
second.power(reset);
dumped = 0;
selected = 0;
pending = 0;
settling = 0;
output = 0x0f;
control = 0x0f;
}
auto QuadTari::poll() -> void {
if(first.device) first.device->poll();
if(second.device) second.device->poll();
}
auto QuadTari::frame() -> void {
if(first.device) first.device->frame();
if(second.device) second.device->frame();
}
auto QuadTari::clock() -> void {
if(settling && !--settling) {
selected = pending;
synchronizeActive();
}
}
auto QuadTari::vblank(n1 dumped) -> void {
if(this->dumped == dumped) return;
this->dumped = dumped;
pending = dumped;
settling = SettlingClocks;
}
auto QuadTari::read() -> n8 {
if(auto device = active()) return device->read();
return 0xff;
}
auto QuadTari::write(n8 data) -> void {
output = data;
if(auto device = active()) device->write(data);
}
auto QuadTari::controlWrite(n8 data) -> void {
control = data;
if(auto device = active()) device->controlWrite(data);
}
auto QuadTari::readAnalog(n1 index) -> AnalogConnection {
return index ? AnalogConnection::vcc() : AnalogConnection::ground();
}
auto QuadTari::serialize(serializer& s) -> void {
s(dumped);
s(selected);
s(pending);
s(settling);
s(output);
s(control);
first.serialize(s);
second.serialize(s);
if(s.reading() && settling > SettlingClocks) {
dumped = 0;
selected = 0;
pending = 0;
settling = 0;
output = 0x0f;
control = 0x0f;
synchronizeActive();
}
}
auto QuadTari::active() -> Controller* {
auto& slot = selected ? second : first;
return slot.device.get();
}
auto QuadTari::synchronizeActive() -> void {
if(auto device = active()) {
device->write(output);
device->controlWrite(control);
}
}
+47
View File
@@ -0,0 +1,47 @@
struct QuadTari : Controller {
struct Slot {
Slot(QuadTari& owner) : owner(owner) {}
auto load(Node::Peripheral, string name) -> void;
auto unload() -> void;
auto allocate(string name) -> Node::Peripheral;
auto save() -> void;
auto power(bool reset) -> void;
auto serialize(serializer&) -> void;
QuadTari& owner;
Node::Port port;
std::unique_ptr<Controller> device;
};
QuadTari(Node::Port);
~QuadTari() override;
auto save() -> void override;
auto power(bool reset) -> void override;
auto poll() -> void override;
auto frame() -> void override;
auto clock() -> void override;
auto vblank(n1 dumped) -> void override;
auto read() -> n8 override;
auto write(n8 data) -> void override;
auto controlWrite(n8 data) -> void override;
auto readAnalog(n1 index) -> AnalogConnection override;
auto serialize(serializer&) -> void override;
static constexpr u16 SettlingClocks = 228;
private:
auto active() -> Controller*;
auto synchronizeActive() -> void;
public:
Slot first;
Slot second;
n1 dumped = 0;
n1 selected = 0;
n1 pending = 0;
u16 settling = 0;
n4 output = 0x0f;
n4 control = 0x0f;
};
+2
View File
@@ -69,6 +69,8 @@ auto TIA::writeVblank(n8 data) -> void {
updateAnalogInput(index);
}
analog.vblank(data.bit(7));
controllerPort1.vblank(data.bit(7));
controllerPort2.vblank(data.bit(7));
triggers.vblank(data.bit(6));
queueWrite(0x01, data, 1);
}
+2
View File
@@ -64,6 +64,8 @@ auto TIA::clock() -> bool {
audio.clock();
analog.advance();
controllerPort1.clock();
controllerPort2.clock();
if(!timing.advance()) return false;
playfield.nextLine();
+31
View File
@@ -126,6 +126,37 @@ Atari2600::Atari2600() {
ports.push_back(port);
}
auto appendQuadTariPort = [&](string name, u32 id) {
InputPort port{name};
{ InputDevice device{"Gamepad"};
device.digital("Up", virtualPorts[id].pad.up);
device.digital("Down", virtualPorts[id].pad.down);
device.digital("Left", virtualPorts[id].pad.left);
device.digital("Right", virtualPorts[id].pad.right);
device.digital("Fire", virtualPorts[id].pad.south);
port.append(device); }
{ InputDevice device{"Paddles"};
device.digital("Paddle 1 Fire", virtualPorts[id].pad.south);
device.digital("Paddle 2 Fire", virtualPorts[id].pad.east);
port.append(device); }
{ InputDevice device{"Driving"};
device.analog ("Wheel Left", virtualPorts[id].pad.lstick_left);
device.analog ("Wheel Right", virtualPorts[id].pad.lstick_right);
device.digital("Fire", virtualPorts[id].pad.south);
device.analog ("Wheel", virtualPorts[id].pad.lstick_left, virtualPorts[id].pad.lstick_right);
port.append(device); }
ports.push_back(port);
};
appendQuadTariPort("QuadTari Player 1", 0);
appendQuadTariPort("QuadTari Player 2", 1);
appendQuadTariPort("QuadTari Player 3", 2);
appendQuadTariPort("QuadTari Player 4", 3);
}
auto Atari2600::load(Menu menu) -> void {