mirror of
https://github.com/ares-emulator/ares.git
synced 2026-10-12 02:11:04 +00:00
a26: add QuadTari controller support
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -22,5 +22,6 @@ namespace ares::Atari2600 {
|
||||
#include "atarivox/atarivox.cpp"
|
||||
#include "kidvid/audio.cpp"
|
||||
#include "kidvid/kidvid.cpp"
|
||||
#include "quadtari/quadtari.cpp"
|
||||
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {};
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -64,6 +64,8 @@ auto TIA::clock() -> bool {
|
||||
|
||||
audio.clock();
|
||||
analog.advance();
|
||||
controllerPort1.clock();
|
||||
controllerPort2.clock();
|
||||
|
||||
if(!timing.advance()) return false;
|
||||
playfield.nextLine();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user