2015-07-16 23:56:02 +00:00
|
|
|
//
|
|
|
|
// Atari2600.cpp
|
2015-07-26 19:25:11 +00:00
|
|
|
// CLK
|
2015-07-16 23:56:02 +00:00
|
|
|
//
|
|
|
|
// Created by Thomas Harte on 14/07/2015.
|
2018-05-13 19:19:52 +00:00
|
|
|
// Copyright 2015 Thomas Harte. All rights reserved.
|
2015-07-16 23:56:02 +00:00
|
|
|
//
|
|
|
|
|
|
|
|
#include "Atari2600.hpp"
|
2017-11-11 20:28:40 +00:00
|
|
|
|
2015-07-16 23:56:02 +00:00
|
|
|
#include <algorithm>
|
2017-11-11 20:28:40 +00:00
|
|
|
#include <cstdio>
|
2015-07-16 23:56:02 +00:00
|
|
|
|
2020-04-02 03:19:34 +00:00
|
|
|
#include "../../MachineTypes.hpp"
|
2018-03-09 20:19:02 +00:00
|
|
|
|
2019-11-09 20:31:41 +00:00
|
|
|
#include "../../../Analyser/Static/Atari2600/Target.hpp"
|
2018-03-09 21:07:29 +00:00
|
|
|
|
2017-08-16 16:39:15 +00:00
|
|
|
#include "Cartridges/Atari8k.hpp"
|
|
|
|
#include "Cartridges/Atari16k.hpp"
|
|
|
|
#include "Cartridges/Atari32k.hpp"
|
|
|
|
#include "Cartridges/ActivisionStack.hpp"
|
|
|
|
#include "Cartridges/CBSRAMPlus.hpp"
|
|
|
|
#include "Cartridges/CommaVid.hpp"
|
|
|
|
#include "Cartridges/MegaBoy.hpp"
|
|
|
|
#include "Cartridges/MNetwork.hpp"
|
|
|
|
#include "Cartridges/ParkerBros.hpp"
|
|
|
|
#include "Cartridges/Pitfall2.hpp"
|
|
|
|
#include "Cartridges/Tigervision.hpp"
|
|
|
|
#include "Cartridges/Unpaged.hpp"
|
2017-03-18 18:01:04 +00:00
|
|
|
|
2016-05-16 12:01:29 +00:00
|
|
|
namespace {
|
2020-05-13 02:22:21 +00:00
|
|
|
static constexpr double NTSC_clock_rate = 1194720;
|
|
|
|
static constexpr double PAL_clock_rate = 1182298;
|
2016-05-16 12:01:29 +00:00
|
|
|
}
|
2015-07-16 23:56:02 +00:00
|
|
|
|
2017-08-16 18:52:40 +00:00
|
|
|
namespace Atari2600 {
|
2016-04-24 10:56:08 +00:00
|
|
|
|
2018-06-12 01:35:03 +00:00
|
|
|
class Joystick: public Inputs::ConcreteJoystick {
|
2017-10-16 00:44:59 +00:00
|
|
|
public:
|
2017-11-11 20:28:40 +00:00
|
|
|
Joystick(Bus *bus, std::size_t shift, std::size_t fire_tia_input) :
|
2018-06-12 01:35:03 +00:00
|
|
|
ConcreteJoystick({
|
2018-06-11 00:45:52 +00:00
|
|
|
Input(Input::Up),
|
|
|
|
Input(Input::Down),
|
|
|
|
Input(Input::Left),
|
|
|
|
Input(Input::Right),
|
|
|
|
Input(Input::Fire)
|
2018-06-12 01:35:03 +00:00
|
|
|
}),
|
|
|
|
bus_(bus), shift_(shift), fire_tia_input_(fire_tia_input) {}
|
2018-02-26 00:08:50 +00:00
|
|
|
|
2020-01-24 03:57:51 +00:00
|
|
|
void did_set_input(const Input &digital_input, bool is_active) final {
|
2018-02-26 00:08:50 +00:00
|
|
|
switch(digital_input.type) {
|
2018-06-11 00:45:52 +00:00
|
|
|
case Input::Up: bus_->mos6532_.update_port_input(0, 0x10 >> shift_, is_active); break;
|
|
|
|
case Input::Down: bus_->mos6532_.update_port_input(0, 0x20 >> shift_, is_active); break;
|
|
|
|
case Input::Left: bus_->mos6532_.update_port_input(0, 0x40 >> shift_, is_active); break;
|
|
|
|
case Input::Right: bus_->mos6532_.update_port_input(0, 0x80 >> shift_, is_active); break;
|
2017-10-16 00:44:59 +00:00
|
|
|
|
|
|
|
// TODO: latching
|
2018-06-11 00:45:52 +00:00
|
|
|
case Input::Fire:
|
2017-10-16 00:44:59 +00:00
|
|
|
if(is_active)
|
|
|
|
bus_->tia_input_value_[fire_tia_input_] &= ~0x80;
|
|
|
|
else
|
|
|
|
bus_->tia_input_value_[fire_tia_input_] |= 0x80;
|
|
|
|
break;
|
2018-02-26 00:08:50 +00:00
|
|
|
|
|
|
|
default: break;
|
2017-10-16 00:44:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
Bus *bus_;
|
2017-11-11 20:28:40 +00:00
|
|
|
std::size_t shift_, fire_tia_input_;
|
2017-10-16 00:44:59 +00:00
|
|
|
};
|
|
|
|
|
2019-10-04 02:10:10 +00:00
|
|
|
using Target = Analyser::Static::Atari2600::Target;
|
|
|
|
|
2017-08-16 18:52:40 +00:00
|
|
|
class ConcreteMachine:
|
|
|
|
public Machine,
|
2020-04-02 03:19:34 +00:00
|
|
|
public MachineTypes::TimedMachine,
|
|
|
|
public MachineTypes::AudioProducer,
|
|
|
|
public MachineTypes::ScanProducer,
|
|
|
|
public MachineTypes::JoystickMachine {
|
2017-08-16 18:52:40 +00:00
|
|
|
public:
|
2020-01-24 01:12:44 +00:00
|
|
|
ConcreteMachine(const Target &target) : frequency_mismatch_warner_(*this) {
|
2018-07-11 00:00:46 +00:00
|
|
|
const std::vector<uint8_t> &rom = target.media.cartridges.front()->get_segments().front().data;
|
2018-03-09 21:07:29 +00:00
|
|
|
|
2019-10-04 02:10:10 +00:00
|
|
|
using PagingModel = Target::PagingModel;
|
2018-07-11 00:00:46 +00:00
|
|
|
switch(target.paging_model) {
|
2019-12-24 02:31:46 +00:00
|
|
|
case PagingModel::ActivisionStack: bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::ActivisionStack>>(rom); break;
|
|
|
|
case PagingModel::CBSRamPlus: bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::CBSRAMPlus>>(rom); break;
|
|
|
|
case PagingModel::CommaVid: bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::CommaVid>>(rom); break;
|
|
|
|
case PagingModel::MegaBoy: bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::MegaBoy>>(rom); break;
|
|
|
|
case PagingModel::MNetwork: bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::MNetwork>>(rom); break;
|
|
|
|
case PagingModel::None: bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Unpaged>>(rom); break;
|
|
|
|
case PagingModel::ParkerBros: bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::ParkerBros>>(rom); break;
|
|
|
|
case PagingModel::Pitfall2: bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Pitfall2>>(rom); break;
|
|
|
|
case PagingModel::Tigervision: bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Tigervision>>(rom); break;
|
2018-03-09 21:07:29 +00:00
|
|
|
|
|
|
|
case PagingModel::Atari8k:
|
2018-07-11 00:00:46 +00:00
|
|
|
if(target.uses_superchip) {
|
2019-12-24 02:31:46 +00:00
|
|
|
bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Atari8kSuperChip>>(rom);
|
2017-08-16 18:52:40 +00:00
|
|
|
} else {
|
2019-12-24 02:31:46 +00:00
|
|
|
bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Atari8k>>(rom);
|
2017-08-16 18:52:40 +00:00
|
|
|
}
|
|
|
|
break;
|
2018-03-09 21:07:29 +00:00
|
|
|
case PagingModel::Atari16k:
|
2018-07-11 00:00:46 +00:00
|
|
|
if(target.uses_superchip) {
|
2019-12-24 02:31:46 +00:00
|
|
|
bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Atari16kSuperChip>>(rom);
|
2017-08-16 18:52:40 +00:00
|
|
|
} else {
|
2019-12-24 02:31:46 +00:00
|
|
|
bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Atari16k>>(rom);
|
2017-08-16 18:52:40 +00:00
|
|
|
}
|
|
|
|
break;
|
2018-03-09 21:07:29 +00:00
|
|
|
case PagingModel::Atari32k:
|
2018-07-11 00:00:46 +00:00
|
|
|
if(target.uses_superchip) {
|
2019-12-24 02:31:46 +00:00
|
|
|
bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Atari32kSuperChip>>(rom);
|
2017-08-16 18:52:40 +00:00
|
|
|
} else {
|
2019-12-24 02:31:46 +00:00
|
|
|
bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Atari32k>>(rom);
|
2017-08-16 18:52:40 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2017-10-16 00:44:59 +00:00
|
|
|
|
2017-10-16 00:49:47 +00:00
|
|
|
joysticks_.emplace_back(new Joystick(bus_.get(), 0, 0));
|
|
|
|
joysticks_.emplace_back(new Joystick(bus_.get(), 4, 1));
|
2020-01-24 01:12:44 +00:00
|
|
|
|
|
|
|
set_is_ntsc(is_ntsc_);
|
2017-08-16 18:52:40 +00:00
|
|
|
}
|
2015-07-28 01:15:10 +00:00
|
|
|
|
2020-01-24 03:57:51 +00:00
|
|
|
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
|
2017-10-16 00:44:59 +00:00
|
|
|
return joysticks_;
|
2017-08-16 18:52:40 +00:00
|
|
|
}
|
2015-08-19 00:33:24 +00:00
|
|
|
|
2020-01-24 03:57:51 +00:00
|
|
|
void set_switch_is_enabled(Atari2600Switch input, bool state) final {
|
2017-08-16 18:52:40 +00:00
|
|
|
switch(input) {
|
|
|
|
case Atari2600SwitchReset: bus_->mos6532_.update_port_input(1, 0x01, state); break;
|
|
|
|
case Atari2600SwitchSelect: bus_->mos6532_.update_port_input(1, 0x02, state); break;
|
|
|
|
case Atari2600SwitchColour: bus_->mos6532_.update_port_input(1, 0x08, state); break;
|
|
|
|
case Atari2600SwitchLeftPlayerDifficulty: bus_->mos6532_.update_port_input(1, 0x40, state); break;
|
|
|
|
case Atari2600SwitchRightPlayerDifficulty: bus_->mos6532_.update_port_input(1, 0x80, state); break;
|
2017-03-18 21:34:34 +00:00
|
|
|
}
|
2017-08-16 18:52:40 +00:00
|
|
|
}
|
2017-02-20 02:20:37 +00:00
|
|
|
|
2020-01-24 03:57:51 +00:00
|
|
|
bool get_switch_is_enabled(Atari2600Switch input) final {
|
2018-02-15 02:46:50 +00:00
|
|
|
uint8_t port_input = bus_->mos6532_.get_port_input(1);
|
|
|
|
switch(input) {
|
|
|
|
case Atari2600SwitchReset: return !!(port_input & 0x01);
|
|
|
|
case Atari2600SwitchSelect: return !!(port_input & 0x02);
|
|
|
|
case Atari2600SwitchColour: return !!(port_input & 0x08);
|
|
|
|
case Atari2600SwitchLeftPlayerDifficulty: return !!(port_input & 0x40);
|
|
|
|
case Atari2600SwitchRightPlayerDifficulty: return !!(port_input & 0x80);
|
|
|
|
default: return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-24 03:57:51 +00:00
|
|
|
void set_reset_switch(bool state) final {
|
2017-08-16 18:52:40 +00:00
|
|
|
bus_->set_reset_line(state);
|
|
|
|
}
|
2017-02-20 02:20:37 +00:00
|
|
|
|
2017-08-16 18:52:40 +00:00
|
|
|
// to satisfy CRTMachine::Machine
|
2020-01-24 03:57:51 +00:00
|
|
|
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
|
2020-05-10 03:00:39 +00:00
|
|
|
bus_->speaker_.set_input_rate(float(get_clock_rate() / double(CPUTicksPerAudioTick)));
|
2020-01-24 01:12:44 +00:00
|
|
|
bus_->tia_.set_crt_delegate(&frequency_mismatch_warner_);
|
2018-11-30 02:26:05 +00:00
|
|
|
bus_->tia_.set_scan_target(scan_target);
|
2017-08-16 18:52:40 +00:00
|
|
|
}
|
2017-02-20 02:20:37 +00:00
|
|
|
|
2020-01-22 03:28:25 +00:00
|
|
|
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
|
|
|
|
return bus_->tia_.get_scaled_scan_status() / 3.0f;
|
2020-01-21 02:45:10 +00:00
|
|
|
}
|
|
|
|
|
2020-01-24 03:57:51 +00:00
|
|
|
Outputs::Speaker::Speaker *get_speaker() final {
|
2017-12-18 02:26:06 +00:00
|
|
|
return &bus_->speaker_;
|
2017-08-16 18:52:40 +00:00
|
|
|
}
|
2017-03-07 00:37:35 +00:00
|
|
|
|
2020-01-24 03:57:51 +00:00
|
|
|
void run_for(const Cycles cycles) final {
|
2017-08-16 18:52:40 +00:00
|
|
|
bus_->run_for(cycles);
|
2018-03-07 19:26:07 +00:00
|
|
|
bus_->apply_confidence(confidence_counter_);
|
2017-03-07 00:37:35 +00:00
|
|
|
}
|
2017-08-16 18:52:40 +00:00
|
|
|
|
2022-07-09 17:33:46 +00:00
|
|
|
void flush_output(int) final {
|
2020-01-23 03:05:51 +00:00
|
|
|
bus_->flush();
|
|
|
|
}
|
|
|
|
|
2020-01-24 01:12:44 +00:00
|
|
|
void register_crt_frequency_mismatch() {
|
|
|
|
is_ntsc_ ^= true;
|
|
|
|
set_is_ntsc(is_ntsc_);
|
2017-08-16 18:52:40 +00:00
|
|
|
}
|
|
|
|
|
2020-01-24 03:57:51 +00:00
|
|
|
float get_confidence() final {
|
2018-03-07 19:26:07 +00:00
|
|
|
return confidence_counter_.get_confidence();
|
|
|
|
}
|
|
|
|
|
2017-08-16 18:52:40 +00:00
|
|
|
private:
|
2020-01-24 01:12:44 +00:00
|
|
|
// The bus.
|
2017-08-16 18:52:40 +00:00
|
|
|
std::unique_ptr<Bus> bus_;
|
|
|
|
|
2020-01-24 01:12:44 +00:00
|
|
|
// Output frame rate tracker.
|
|
|
|
Outputs::CRT::CRTFrequencyMismatchWarner<ConcreteMachine> frequency_mismatch_warner_;
|
2018-03-07 19:26:07 +00:00
|
|
|
bool is_ntsc_ = true;
|
2017-10-16 00:49:47 +00:00
|
|
|
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
2018-03-07 19:26:07 +00:00
|
|
|
|
|
|
|
// a confidence counter
|
|
|
|
Analyser::Dynamic::ConfidenceCounter confidence_counter_;
|
2020-01-24 01:12:44 +00:00
|
|
|
|
|
|
|
void set_is_ntsc(bool is_ntsc) {
|
|
|
|
bus_->tia_.set_output_mode(is_ntsc ? TIA::OutputMode::NTSC : TIA::OutputMode::PAL);
|
|
|
|
const double clock_rate = is_ntsc ? NTSC_clock_rate : PAL_clock_rate;
|
|
|
|
bus_->speaker_.set_input_rate(float(clock_rate) / float(CPUTicksPerAudioTick));
|
|
|
|
bus_->speaker_.set_high_frequency_cutoff(float(clock_rate) / float(CPUTicksPerAudioTick * 2));
|
|
|
|
set_clock_rate(clock_rate);
|
|
|
|
}
|
2017-08-16 18:52:40 +00:00
|
|
|
};
|
|
|
|
|
2017-02-20 02:20:37 +00:00
|
|
|
}
|
2017-08-16 18:52:40 +00:00
|
|
|
|
|
|
|
using namespace Atari2600;
|
|
|
|
|
2024-01-13 03:03:19 +00:00
|
|
|
std::unique_ptr<Machine> Machine::Atari2600(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &) {
|
2018-07-11 00:00:46 +00:00
|
|
|
const Target *const atari_target = dynamic_cast<const Target *>(target);
|
2024-01-13 03:03:19 +00:00
|
|
|
return std::make_unique<Atari2600::ConcreteMachine>(*atari_target);
|
2017-08-16 18:52:40 +00:00
|
|
|
}
|