mirror of
https://github.com/TomHarte/CLK.git
synced 2025-04-09 15:39:08 +00:00
Merge pull request #414 from TomHarte/AppleII
Adds provisional Apple II emulation
This commit is contained in:
commit
7e8e3fdd39
@ -13,6 +13,7 @@ namespace Analyser {
|
||||
|
||||
enum class Machine {
|
||||
AmstradCPC,
|
||||
AppleII,
|
||||
Atari2600,
|
||||
ColecoVision,
|
||||
Electron,
|
||||
|
@ -57,7 +57,7 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
return acorn_cartridges;
|
||||
}
|
||||
|
||||
void Analyser::Static::Acorn::AddTargets(const Media &media, std::vector<std::unique_ptr<::Analyser::Static::Target>> &destination) {
|
||||
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
std::unique_ptr<Target> target(new Target);
|
||||
target->machine = Machine::Electron;
|
||||
target->confidence = 0.5; // TODO: a proper estimation
|
||||
@ -121,7 +121,9 @@ void Analyser::Static::Acorn::AddTargets(const Media &media, std::vector<std::un
|
||||
}
|
||||
}
|
||||
|
||||
if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size()) {
|
||||
destination.push_back(std::move(target));
|
||||
TargetList targets;
|
||||
if(!target->media.empty()) {
|
||||
targets.push_back(std::move(target));
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
@ -10,12 +10,14 @@
|
||||
#define StaticAnalyser_Acorn_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Acorn {
|
||||
|
||||
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
#define Analyser_Static_Acorn_Target_h
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
@ -19,6 +20,7 @@ struct Target: public ::Analyser::Static::Target {
|
||||
bool has_adfs = false;
|
||||
bool has_dfs = false;
|
||||
bool should_shift_restart = false;
|
||||
std::string loading_command;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -179,7 +179,8 @@ static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, co
|
||||
return false;
|
||||
}
|
||||
|
||||
void Analyser::Static::AmstradCPC::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination) {
|
||||
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
TargetList destination;
|
||||
std::unique_ptr<Target> target(new Target);
|
||||
target->machine = Machine::AmstradCPC;
|
||||
target->confidence = 0.5;
|
||||
@ -241,4 +242,6 @@ void Analyser::Static::AmstradCPC::AddTargets(const Media &media, std::vector<st
|
||||
// If any media survived, add the target.
|
||||
if(!target->media.empty())
|
||||
destination.push_back(std::move(target));
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
@ -6,19 +6,21 @@
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef StaticAnalyser_AmstradCPC_StaticAnalyser_hpp
|
||||
#define StaticAnalyser_AmstradCPC_StaticAnalyser_hpp
|
||||
#ifndef Analyser_Static_AmstradCPC_StaticAnalyser_hpp
|
||||
#define Analyser_Static_AmstradCPC_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AmstradCPC {
|
||||
|
||||
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_AmstradCPC_StaticAnalyser_hpp */
|
||||
#endif /* Analyser_Static_AmstradCPC_StaticAnalyser_hpp */
|
||||
|
@ -10,6 +10,7 @@
|
||||
#define Analyser_Static_AmstradCPC_Target_h
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
@ -23,6 +24,7 @@ struct Target: public ::Analyser::Static::Target {
|
||||
};
|
||||
|
||||
Model model = Model::CPC464;
|
||||
std::string loading_command;
|
||||
};
|
||||
|
||||
}
|
||||
|
13
Analyser/Static/AppleII/StaticAnalyser.cpp
Normal file
13
Analyser/Static/AppleII/StaticAnalyser.cpp
Normal file
@ -0,0 +1,13 @@
|
||||
//
|
||||
// StaticAnalyser.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/04/2018.
|
||||
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
return TargetList();
|
||||
}
|
26
Analyser/Static/AppleII/StaticAnalyser.hpp
Normal file
26
Analyser/Static/AppleII/StaticAnalyser.hpp
Normal file
@ -0,0 +1,26 @@
|
||||
//
|
||||
// StaticAnalyser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/04/2018.
|
||||
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_AppleII_StaticAnalyser_hpp
|
||||
#define Analyser_Static_AppleII_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AppleII {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_AppleII_StaticAnalyser_hpp */
|
@ -180,7 +180,7 @@ static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target,
|
||||
}
|
||||
}
|
||||
|
||||
void Analyser::Static::Atari::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination) {
|
||||
Analyser::Static::TargetList Analyser::Static::Atari::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
// TODO: sanity checking; is this image really for an Atari 2600?
|
||||
std::unique_ptr<Analyser::Static::Atari::Target> target(new Analyser::Static::Atari::Target);
|
||||
target->machine = Machine::Atari2600;
|
||||
@ -198,6 +198,7 @@ void Analyser::Static::Atari::AddTargets(const Media &media, std::vector<std::un
|
||||
DeterminePagingForCartridge(*target, segment);
|
||||
}
|
||||
}
|
||||
|
||||
destination.push_back(std::move(target));
|
||||
TargetList destinations;
|
||||
destinations.push_back(std::move(target));
|
||||
return destinations;
|
||||
}
|
||||
|
@ -10,12 +10,14 @@
|
||||
#define StaticAnalyser_Atari_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Atari {
|
||||
|
||||
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -52,11 +52,13 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
return coleco_cartridges;
|
||||
}
|
||||
|
||||
void Analyser::Static::Coleco::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) {
|
||||
Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
TargetList targets;
|
||||
std::unique_ptr<Target> target(new Target);
|
||||
target->machine = Machine::ColecoVision;
|
||||
target->confidence = 1.0f - 1.0f / 32768.0f;
|
||||
target->media.cartridges = ColecoCartridgesFrom(media.cartridges);
|
||||
if(!target->media.empty())
|
||||
destination.push_back(std::move(target));
|
||||
targets.push_back(std::move(target));
|
||||
return targets;
|
||||
}
|
||||
|
@ -10,12 +10,14 @@
|
||||
#define StaticAnalyser_Coleco_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Coleco {
|
||||
|
||||
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,9 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
return vic20_cartridges;
|
||||
}
|
||||
|
||||
void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination, const std::string &file_name) {
|
||||
Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
TargetList destination;
|
||||
|
||||
std::unique_ptr<Target> target(new Target);
|
||||
target->machine = Machine::Vic20; // TODO: machine estimation
|
||||
target->confidence = 0.5; // TODO: a proper estimation
|
||||
@ -154,4 +156,6 @@ void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std
|
||||
|
||||
destination.push_back(std::move(target));
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
@ -10,13 +10,14 @@
|
||||
#define StaticAnalyser_Commodore_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Commodore {
|
||||
|
||||
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination, const std::string &file_name);
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
#define Analyser_Static_Commodore_Target_h
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
@ -33,6 +34,7 @@ struct Target: public ::Analyser::Static::Target {
|
||||
MemoryModel memory_model = MemoryModel::Unexpanded;
|
||||
Region region = Region::European;
|
||||
bool has_c1540 = false;
|
||||
std::string loading_command;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -63,14 +63,14 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
|
||||
|
||||
(additional audio hardware is also sometimes included, but it's implied by the banking hardware)
|
||||
*/
|
||||
static std::vector<std::unique_ptr<Analyser::Static::Target>> CartridgeTargetsFrom(
|
||||
static Analyser::Static::TargetList CartridgeTargetsFrom(
|
||||
const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||
// No cartridges implies no targets.
|
||||
if(cartridges.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Analyser::Static::Target>> targets;
|
||||
Analyser::Static::TargetList targets;
|
||||
for(const auto &cartridge : cartridges) {
|
||||
const auto &segments = cartridge->get_segments();
|
||||
|
||||
@ -261,7 +261,9 @@ static std::vector<std::unique_ptr<Analyser::Static::Target>> CartridgeTargetsFr
|
||||
return targets;
|
||||
}
|
||||
|
||||
void Analyser::Static::MSX::AddTargets(const Media &media, std::vector<std::unique_ptr<::Analyser::Static::Target>> &destination) {
|
||||
Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
TargetList destination;
|
||||
|
||||
// Append targets for any cartridges that look correct.
|
||||
auto cartridge_targets = CartridgeTargetsFrom(media.cartridges);
|
||||
std::move(cartridge_targets.begin(), cartridge_targets.end(), std::back_inserter(destination));
|
||||
@ -292,4 +294,6 @@ void Analyser::Static::MSX::AddTargets(const Media &media, std::vector<std::uniq
|
||||
target->confidence = 0.5;
|
||||
destination.push_back(std::move(target));
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
@ -10,12 +10,14 @@
|
||||
#define StaticAnalyser_MSX_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace MSX {
|
||||
|
||||
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
#define Analyser_Static_MSX_Target_h
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
@ -17,6 +18,7 @@ namespace MSX {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
bool has_disk_drive = false;
|
||||
std::string loading_command;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ static bool IsMicrodisc(Storage::Encodings::MFM::Parser &parser) {
|
||||
return !std::memcmp(signature, first_sample.data(), sizeof(signature));
|
||||
}
|
||||
|
||||
void Analyser::Static::Oric::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination) {
|
||||
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
std::unique_ptr<Target> target(new Target);
|
||||
target->machine = Machine::Oric;
|
||||
target->confidence = 0.5;
|
||||
@ -146,6 +146,8 @@ void Analyser::Static::Oric::AddTargets(const Media &media, std::vector<std::uni
|
||||
target->use_atmos_rom = basic11_votes >= basic10_votes;
|
||||
if(target->has_microdrive) target->use_atmos_rom = true;
|
||||
|
||||
TargetList targets;
|
||||
if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size())
|
||||
destination.push_back(std::move(target));
|
||||
targets.push_back(std::move(target));
|
||||
return targets;
|
||||
}
|
||||
|
@ -10,12 +10,14 @@
|
||||
#define StaticAnalyser_Oric_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Oric {
|
||||
|
||||
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,9 @@
|
||||
#ifndef Analyser_Static_Oric_Target_h
|
||||
#define Analyser_Static_Oric_Target_h
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Oric {
|
||||
@ -16,6 +19,7 @@ namespace Oric {
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
bool use_atmos_rom = false;
|
||||
bool has_microdrive = false;
|
||||
std::string loading_command;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -11,10 +11,12 @@
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
|
||||
// Analysers
|
||||
#include "Acorn/StaticAnalyser.hpp"
|
||||
#include "AmstradCPC/StaticAnalyser.hpp"
|
||||
#include "AppleII/StaticAnalyser.hpp"
|
||||
#include "Atari/StaticAnalyser.hpp"
|
||||
#include "Coleco/StaticAnalyser.hpp"
|
||||
#include "Commodore/StaticAnalyser.hpp"
|
||||
@ -138,8 +140,8 @@ Media Analyser::Static::GetMedia(const std::string &file_name) {
|
||||
return GetMediaAndPlatforms(file_name, throwaway);
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Target>> Analyser::Static::GetTargets(const std::string &file_name) {
|
||||
std::vector<std::unique_ptr<Target>> targets;
|
||||
TargetList Analyser::Static::GetTargets(const std::string &file_name) {
|
||||
TargetList targets;
|
||||
|
||||
// Collect all disks, tapes and ROMs as can be extrapolated from this file, forming the
|
||||
// union of all platforms this file might be a target for.
|
||||
@ -148,14 +150,20 @@ std::vector<std::unique_ptr<Target>> Analyser::Static::GetTargets(const std::str
|
||||
|
||||
// Hand off to platform-specific determination of whether these things are actually compatible and,
|
||||
// if so, how to load them.
|
||||
if(potential_platforms & TargetPlatform::Acorn) Acorn::AddTargets(media, targets);
|
||||
if(potential_platforms & TargetPlatform::AmstradCPC) AmstradCPC::AddTargets(media, targets);
|
||||
if(potential_platforms & TargetPlatform::Atari2600) Atari::AddTargets(media, targets);
|
||||
if(potential_platforms & TargetPlatform::ColecoVision) Coleco::AddTargets(media, targets);
|
||||
if(potential_platforms & TargetPlatform::Commodore) Commodore::AddTargets(media, targets, file_name);
|
||||
if(potential_platforms & TargetPlatform::MSX) MSX::AddTargets(media, targets);
|
||||
if(potential_platforms & TargetPlatform::Oric) Oric::AddTargets(media, targets);
|
||||
if(potential_platforms & TargetPlatform::ZX8081) ZX8081::AddTargets(media, targets, potential_platforms);
|
||||
#define Append(x) {\
|
||||
auto new_targets = x::GetTargets(media, file_name, potential_platforms);\
|
||||
std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));\
|
||||
}
|
||||
if(potential_platforms & TargetPlatform::Acorn) Append(Acorn);
|
||||
if(potential_platforms & TargetPlatform::AmstradCPC) Append(AmstradCPC);
|
||||
if(potential_platforms & TargetPlatform::AppleII) Append(AppleII);
|
||||
if(potential_platforms & TargetPlatform::Atari2600) Append(Atari);
|
||||
if(potential_platforms & TargetPlatform::ColecoVision) Append(Coleco);
|
||||
if(potential_platforms & TargetPlatform::Commodore) Append(Commodore);
|
||||
if(potential_platforms & TargetPlatform::MSX) Append(MSX);
|
||||
if(potential_platforms & TargetPlatform::Oric) Append(Oric);
|
||||
if(potential_platforms & TargetPlatform::ZX8081) Append(ZX8081);
|
||||
#undef Append
|
||||
|
||||
// Reset any tapes to their initial position
|
||||
for(auto &target : targets) {
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "../../Storage/Disk/Disk.hpp"
|
||||
#include "../../Storage/Cartridge/Cartridge.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@ -43,17 +44,16 @@ struct Target {
|
||||
|
||||
Machine machine;
|
||||
Media media;
|
||||
|
||||
float confidence;
|
||||
std::string loading_command;
|
||||
};
|
||||
typedef std::vector<std::unique_ptr<Target>> TargetList;
|
||||
|
||||
/*!
|
||||
Attempts, through any available means, to return a list of potential targets for the file with the given name.
|
||||
|
||||
@returns The list of potential targets, sorted from most to least probable.
|
||||
*/
|
||||
std::vector<std::unique_ptr<Target>> GetTargets(const std::string &file_name);
|
||||
TargetList GetTargets(const std::string &file_name);
|
||||
|
||||
/*!
|
||||
Inspects the supplied file and determines the media included.
|
||||
|
@ -28,7 +28,8 @@ static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<S
|
||||
return files;
|
||||
}
|
||||
|
||||
void Analyser::Static::ZX8081::AddTargets(const Media &media, std::vector<std::unique_ptr<::Analyser::Static::Target>> &destination, TargetPlatform::IntType potential_platforms) {
|
||||
Analyser::Static::TargetList Analyser::Static::ZX8081::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
TargetList destination;
|
||||
if(!media.tapes.empty()) {
|
||||
std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front());
|
||||
media.tapes.front()->reset();
|
||||
@ -65,4 +66,5 @@ void Analyser::Static::ZX8081::AddTargets(const Media &media, std::vector<std::u
|
||||
|
||||
}
|
||||
}
|
||||
return destination;
|
||||
}
|
||||
|
@ -6,17 +6,18 @@
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef StaticAnalyser_ZX8081_StaticAnalyser_hpp
|
||||
#define StaticAnalyser_ZX8081_StaticAnalyser_hpp
|
||||
#ifndef Analyser_Static_ZX8081_StaticAnalyser_hpp
|
||||
#define Analyser_Static_ZX8081_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace ZX8081 {
|
||||
|
||||
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination, TargetPlatform::IntType potential_platforms);
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
#define Analyser_Static_ZX8081_Target_h
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
@ -25,6 +26,7 @@ struct Target: public ::Analyser::Static::Target {
|
||||
MemoryModel memory_model = MemoryModel::Unexpanded;
|
||||
bool is_ZX81 = false;
|
||||
bool ZX80_uses_ZX81_ROM = false;
|
||||
std::string loading_command;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -287,7 +287,7 @@ template <class BusHandler> class MOS6560 {
|
||||
case State::Sync: crt_->output_sync(cycles_in_state_ * 4); break;
|
||||
case State::ColourBurst: crt_->output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0); break;
|
||||
case State::Border: output_border(cycles_in_state_ * 4); break;
|
||||
case State::Pixels: crt_->output_data(cycles_in_state_ * 4, 1); break;
|
||||
case State::Pixels: crt_->output_data(cycles_in_state_ * 4); break;
|
||||
}
|
||||
output_state_ = this_state_;
|
||||
cycles_in_state_ = 0;
|
||||
|
@ -536,7 +536,8 @@ void TMS9918::run_for(const HalfCycles cycles) {
|
||||
}
|
||||
|
||||
if(output_column_ == first_right_border_column_) {
|
||||
crt_->output_data(static_cast<unsigned int>(first_right_border_column_ - first_pixel_column_) * 4, 4);
|
||||
const unsigned int data_length = static_cast<unsigned int>(first_right_border_column_ - first_pixel_column_);
|
||||
crt_->output_data(data_length * 4, data_length);
|
||||
pixel_target_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
38
Components/AudioToggle/AudioToggle.cpp
Normal file
38
Components/AudioToggle/AudioToggle.cpp
Normal file
@ -0,0 +1,38 @@
|
||||
//
|
||||
// AudioToggle.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 17/04/2018.
|
||||
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "AudioToggle.hpp"
|
||||
|
||||
using namespace Audio;
|
||||
|
||||
Audio::Toggle::Toggle(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
|
||||
audio_queue_(audio_queue) {}
|
||||
|
||||
void Toggle::get_samples(std::size_t number_of_samples, std::int16_t *target) {
|
||||
for(std::size_t sample = 0; sample < number_of_samples; ++sample) {
|
||||
target[sample] = level_;
|
||||
}
|
||||
}
|
||||
|
||||
void Toggle::set_sample_volume_range(std::int16_t range) {
|
||||
volume_ = range;
|
||||
}
|
||||
|
||||
void Toggle::skip_samples(const std::size_t number_of_samples) {}
|
||||
|
||||
void Toggle::set_output(bool enabled) {
|
||||
if(is_enabled_ == enabled) return;
|
||||
is_enabled_ = enabled;
|
||||
audio_queue_.defer([=] {
|
||||
level_ = enabled ? volume_ : 0;
|
||||
});
|
||||
}
|
||||
|
||||
bool Toggle::get_output() {
|
||||
return is_enabled_;
|
||||
}
|
39
Components/AudioToggle/AudioToggle.hpp
Normal file
39
Components/AudioToggle/AudioToggle.hpp
Normal file
@ -0,0 +1,39 @@
|
||||
//
|
||||
// AudioToggle.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 17/04/2018.
|
||||
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef AudioToggle_hpp
|
||||
#define AudioToggle_hpp
|
||||
|
||||
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||
#include "../../Concurrency/AsyncTaskQueue.hpp"
|
||||
|
||||
namespace Audio {
|
||||
|
||||
/*!
|
||||
Provides a sample source that can programmatically be set to one of two values.
|
||||
*/
|
||||
class Toggle: public Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
Toggle(Concurrency::DeferringAsyncTaskQueue &audio_queue);
|
||||
|
||||
void get_samples(std::size_t number_of_samples, std::int16_t *target);
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
void skip_samples(const std::size_t number_of_samples);
|
||||
|
||||
void set_output(bool enabled);
|
||||
bool get_output();
|
||||
|
||||
private:
|
||||
bool is_enabled_ = false;
|
||||
int16_t level_ = 0, volume_ = 0;
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* AudioToggle_hpp */
|
@ -12,7 +12,7 @@ using namespace Inputs;
|
||||
|
||||
Keyboard::Keyboard() {}
|
||||
|
||||
void Keyboard::set_key_pressed(Key key, bool is_pressed) {
|
||||
void Keyboard::set_key_pressed(Key key, char value, bool is_pressed) {
|
||||
std::size_t key_offset = static_cast<std::size_t>(key);
|
||||
if(key_offset >= key_states_.size()) {
|
||||
key_states_.resize(key_offset+1, false);
|
||||
|
@ -40,7 +40,7 @@ class Keyboard {
|
||||
};
|
||||
|
||||
// Host interface.
|
||||
virtual void set_key_pressed(Key key, bool is_pressed);
|
||||
virtual void set_key_pressed(Key key, char value, bool is_pressed);
|
||||
virtual void reset_all_keys();
|
||||
|
||||
// Delegate interface.
|
||||
|
@ -203,7 +203,7 @@ class CRTCBusHandler {
|
||||
} else {
|
||||
if(was_enabled_) {
|
||||
if(cycles_) {
|
||||
crt_->output_data(cycles_ * 16, pixel_divider_);
|
||||
crt_->output_data(cycles_ * 16, cycles_ * 16 / pixel_divider_);
|
||||
pixel_pointer_ = pixel_data_ = nullptr;
|
||||
}
|
||||
} else {
|
||||
@ -267,7 +267,7 @@ class CRTCBusHandler {
|
||||
// widths so it's not necessarily possible to predict the correct number in advance
|
||||
// and using the upper bound could lead to inefficient behaviour
|
||||
if(pixel_pointer_ == pixel_data_ + 320) {
|
||||
crt_->output_data(cycles_ * 16, pixel_divider_);
|
||||
crt_->output_data(cycles_ * 16, cycles_ * 16 / pixel_divider_);
|
||||
pixel_pointer_ = pixel_data_ = nullptr;
|
||||
cycles_ = 0;
|
||||
}
|
||||
@ -916,8 +916,8 @@ class ConcreteMachine:
|
||||
read_pointers_[3] = roms_[upper_rom_].data();
|
||||
|
||||
// Type whatever is required.
|
||||
if(target->loading_command.length()) {
|
||||
type_string(target->loading_command);
|
||||
if(!cpc_target->loading_command.empty()) {
|
||||
type_string(cpc_target->loading_command);
|
||||
}
|
||||
|
||||
insert_media(target->media);
|
||||
|
230
Machines/AppleII/AppleII.cpp
Normal file
230
Machines/AppleII/AppleII.cpp
Normal file
@ -0,0 +1,230 @@
|
||||
//
|
||||
// AppleII.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/04/2018.
|
||||
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "AppleII.hpp"
|
||||
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../KeyboardMachine.hpp"
|
||||
#include "../Utility/MemoryFuzzer.hpp"
|
||||
|
||||
#include "../../Processors/6502/6502.hpp"
|
||||
#include "../../Components/AudioToggle/AudioToggle.hpp"
|
||||
|
||||
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
|
||||
#include "Video.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace {
|
||||
|
||||
class ConcreteMachine:
|
||||
public CRTMachine::Machine,
|
||||
public KeyboardMachine::Machine,
|
||||
public CPU::MOS6502::BusHandler,
|
||||
public Inputs::Keyboard,
|
||||
public AppleII::Machine {
|
||||
private:
|
||||
struct VideoBusHandler : public AppleII::Video::BusHandler {
|
||||
public:
|
||||
VideoBusHandler(uint8_t *ram) : ram_(ram) {}
|
||||
|
||||
uint8_t perform_read(uint16_t address) {
|
||||
return ram_[address];
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *ram_;
|
||||
};
|
||||
|
||||
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
|
||||
VideoBusHandler video_bus_handler_;
|
||||
std::unique_ptr<AppleII::Video::Video<VideoBusHandler>> video_;
|
||||
int cycles_into_current_line_ = 0;
|
||||
Cycles cycles_since_video_update_;
|
||||
|
||||
void update_video() {
|
||||
video_->run_for(cycles_since_video_update_.flush());
|
||||
}
|
||||
void update_audio() {
|
||||
speaker_.run_for(cycles_since_audio_update_.divide(Cycles(16)));
|
||||
}
|
||||
|
||||
uint8_t ram_[48*1024];
|
||||
std::vector<uint8_t> rom_;
|
||||
std::vector<uint8_t> character_rom_;
|
||||
uint16_t rom_start_address_;
|
||||
uint8_t keyboard_input_ = 0x00;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
Audio::Toggle audio_toggle_;
|
||||
Outputs::Speaker::LowpassSpeaker<Audio::Toggle> speaker_;
|
||||
Cycles cycles_since_audio_update_;
|
||||
|
||||
public:
|
||||
ConcreteMachine():
|
||||
m6502_(*this),
|
||||
video_bus_handler_(ram_),
|
||||
audio_toggle_(audio_queue_),
|
||||
speaker_(audio_toggle_) {
|
||||
// The system's master clock rate.
|
||||
const float master_clock = 14318180.0;
|
||||
|
||||
// This is where things get slightly convoluted: establish the machine as having a clock rate
|
||||
// equal to the number of cycles of work the 6502 will actually achieve. Which is less than
|
||||
// the master clock rate divided by 14 because every 65th cycle is extended by one seventh.
|
||||
set_clock_rate((master_clock / 14.0) * 65.0 / (65.0 + 1.0 / 7.0));
|
||||
|
||||
// The speaker, however, should think it is clocked at half the master clock, per a general
|
||||
// decision to sample it at seven times the CPU clock (plus stretches).
|
||||
speaker_.set_input_rate(static_cast<float>(master_clock / (2.0 * 16.0)));
|
||||
|
||||
// Also, start with randomised memory contents.
|
||||
Memory::Fuzz(ram_, sizeof(ram_));
|
||||
}
|
||||
|
||||
void setup_output(float aspect_ratio) override {
|
||||
video_.reset(new AppleII::Video::Video<VideoBusHandler>(video_bus_handler_));
|
||||
video_->set_character_rom(character_rom_);
|
||||
}
|
||||
|
||||
void close_output() override {
|
||||
video_.reset();
|
||||
}
|
||||
|
||||
Outputs::CRT::CRT *get_crt() override {
|
||||
return video_->get_crt();
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() override {
|
||||
return &speaker_;
|
||||
}
|
||||
|
||||
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
++ cycles_since_video_update_;
|
||||
cycles_since_audio_update_ += Cycles(7);
|
||||
|
||||
switch(address) {
|
||||
default:
|
||||
if(isReadOperation(operation)) {
|
||||
if(address < sizeof(ram_)) {
|
||||
*value = ram_[address];
|
||||
} else if(address >= rom_start_address_) {
|
||||
*value = rom_[address - rom_start_address_];
|
||||
} else {
|
||||
switch(address) {
|
||||
default:
|
||||
// printf("Unknown access to %04x\n", address);
|
||||
*value = 0xff;
|
||||
break;
|
||||
case 0xc000:
|
||||
*value = keyboard_input_;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(address < sizeof(ram_)) {
|
||||
if(address >= 0x400) {
|
||||
// TODO: be more selective.
|
||||
update_video();
|
||||
}
|
||||
ram_[address] = *value;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xc050: update_video(); video_->set_graphics_mode(); break;
|
||||
case 0xc051: update_video(); video_->set_text_mode(); break;
|
||||
case 0xc052: update_video(); video_->set_mixed_mode(false); break;
|
||||
case 0xc053: update_video(); video_->set_mixed_mode(true); break;
|
||||
case 0xc054: update_video(); video_->set_video_page(0); break;
|
||||
case 0xc055: update_video(); video_->set_video_page(1); break;
|
||||
case 0xc056: update_video(); video_->set_low_resolution(); break;
|
||||
case 0xc057: update_video(); video_->set_high_resolution(); break;
|
||||
|
||||
case 0xc010:
|
||||
keyboard_input_ &= 0x7f;
|
||||
break;
|
||||
|
||||
case 0xc030:
|
||||
update_audio();
|
||||
audio_toggle_.set_output(!audio_toggle_.get_output());
|
||||
break;
|
||||
}
|
||||
|
||||
// The Apple II has a slightly weird timing pattern: every 65th CPU cycle is stretched
|
||||
// by an extra 1/7th. That's because one cycle lasts 3.5 NTSC colour clocks, so after
|
||||
// 65 cycles a full line of 227.5 colour clocks have passed. But the high-rate binary
|
||||
// signal approximation that produces colour needs to be in phase, so a stretch of exactly
|
||||
// 0.5 further colour cycles is added. The video class handles that implicitly, but it
|
||||
// needs to be accumulated here for the audio.
|
||||
cycles_into_current_line_ = (cycles_into_current_line_ + 1) % 65;
|
||||
if(!cycles_into_current_line_) {
|
||||
++ cycles_since_audio_update_;
|
||||
}
|
||||
|
||||
return Cycles(1);
|
||||
}
|
||||
|
||||
void flush() {
|
||||
update_video();
|
||||
update_audio();
|
||||
audio_queue_.perform();
|
||||
}
|
||||
|
||||
bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override {
|
||||
auto roms = roms_with_names(
|
||||
"AppleII",
|
||||
{
|
||||
"apple2o.rom",
|
||||
"apple2-character.rom"
|
||||
});
|
||||
|
||||
if(!roms[0] || !roms[1]) return false;
|
||||
rom_ = std::move(*roms[0]);
|
||||
rom_start_address_ = static_cast<uint16_t>(0x10000 - rom_.size());
|
||||
|
||||
character_rom_ = std::move(*roms[1]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) override {
|
||||
m6502_.run_for(cycles);
|
||||
}
|
||||
|
||||
void set_key_pressed(Key key, char value, bool is_pressed) override {
|
||||
if(is_pressed) {
|
||||
// If no ASCII value is supplied, look for a few special cases.
|
||||
if(!value) {
|
||||
switch(key) {
|
||||
case Key::Left: value = 8; break;
|
||||
case Key::Right: value = 21; break;
|
||||
case Key::Down: value = 10; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
keyboard_input_ = static_cast<uint8_t>(value | 0x80);
|
||||
}
|
||||
}
|
||||
|
||||
Inputs::Keyboard &get_keyboard() override {
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
using namespace AppleII;
|
||||
|
||||
Machine *Machine::AppleII() {
|
||||
return new ConcreteMachine;
|
||||
}
|
||||
|
||||
Machine::~Machine() {}
|
24
Machines/AppleII/AppleII.hpp
Normal file
24
Machines/AppleII/AppleII.hpp
Normal file
@ -0,0 +1,24 @@
|
||||
//
|
||||
// AppleII.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/04/2018.
|
||||
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef AppleII_hpp
|
||||
#define AppleII_hpp
|
||||
|
||||
namespace AppleII {
|
||||
|
||||
class Machine {
|
||||
public:
|
||||
virtual ~Machine();
|
||||
|
||||
/// Creates and returns an AppleII.
|
||||
static Machine *AppleII();
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif /* AppleII_hpp */
|
133
Machines/AppleII/Video.cpp
Normal file
133
Machines/AppleII/Video.cpp
Normal file
@ -0,0 +1,133 @@
|
||||
//
|
||||
// Video.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/04/2018.
|
||||
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Video.hpp"
|
||||
|
||||
using namespace AppleII::Video;
|
||||
|
||||
namespace {
|
||||
|
||||
struct ScaledByteFiller {
|
||||
ScaledByteFiller() {
|
||||
VideoBase::setup_tables();
|
||||
}
|
||||
} throwaway;
|
||||
|
||||
}
|
||||
|
||||
VideoBase::VideoBase() :
|
||||
crt_(new Outputs::CRT::CRT(455, 1, Outputs::CRT::DisplayType::NTSC60, 1)) {
|
||||
|
||||
// Set a composite sampling function that assumes 1bpp input, and uses just 7 bits per byte.
|
||||
crt_->set_composite_sampling_function(
|
||||
"float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)"
|
||||
"{"
|
||||
"uint texValue = texture(sampler, coordinate).r;"
|
||||
"texValue >>= int(icoordinate.x) % 7;"
|
||||
"return float(texValue & 1u);"
|
||||
"}");
|
||||
crt_->set_integer_coordinate_multiplier(7.0f);
|
||||
|
||||
// Show only the centre 75% of the TV frame.
|
||||
crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite);
|
||||
crt_->set_visible_area(Outputs::CRT::Rect(0.115f, 0.117f, 0.77f, 0.77f));
|
||||
}
|
||||
|
||||
Outputs::CRT::CRT *VideoBase::get_crt() {
|
||||
return crt_.get();
|
||||
}
|
||||
|
||||
uint16_t VideoBase::scaled_byte[256];
|
||||
uint16_t VideoBase::low_resolution_patterns[2][16];
|
||||
|
||||
void VideoBase::setup_tables() {
|
||||
for(int c = 0; c < 128; ++c) {
|
||||
const uint16_t value =
|
||||
((c & 0x01) ? 0x0003 : 0x0000) |
|
||||
((c & 0x02) ? 0x000c : 0x0000) |
|
||||
((c & 0x04) ? 0x0030 : 0x0000) |
|
||||
((c & 0x08) ? 0x0140 : 0x0000) |
|
||||
((c & 0x10) ? 0x0600 : 0x0000) |
|
||||
((c & 0x20) ? 0x1800 : 0x0000) |
|
||||
((c & 0x40) ? 0x6000 : 0x0000);
|
||||
|
||||
uint8_t *const table_entry = reinterpret_cast<uint8_t *>(&scaled_byte[c]);
|
||||
table_entry[0] = static_cast<uint8_t>(value & 0xff);
|
||||
table_entry[1] = static_cast<uint8_t>(value >> 8);
|
||||
}
|
||||
for(int c = 128; c < 256; ++c) {
|
||||
uint8_t *const source_table_entry = reinterpret_cast<uint8_t *>(&scaled_byte[c & 0x7f]);
|
||||
uint8_t *const destination_table_entry = reinterpret_cast<uint8_t *>(&scaled_byte[c]);
|
||||
|
||||
destination_table_entry[0] = static_cast<uint8_t>(source_table_entry[0] << 1);
|
||||
destination_table_entry[1] = static_cast<uint8_t>((source_table_entry[1] << 1) | (source_table_entry[0] >> 6));
|
||||
}
|
||||
|
||||
for(int c = 0; c < 16; ++c) {
|
||||
// Produce the whole 28-bit pattern that would cover two columns.
|
||||
const int reversed_c = ((c&0x1) ? 0x8 : 0x0) | ((c&0x2) ? 0x4 : 0x0) | ((c&0x4) ? 0x2 : 0x0) | ((c&0x8) ? 0x1 : 0x0);
|
||||
int pattern = 0;
|
||||
for(int l = 0; l < 7; ++l) {
|
||||
pattern <<= 4;
|
||||
pattern |= reversed_c;
|
||||
}
|
||||
|
||||
// Pack that 28-bit pattern into the appropriate look-up tables.
|
||||
uint8_t *const left_entry = reinterpret_cast<uint8_t *>(&low_resolution_patterns[0][c]);
|
||||
uint8_t *const right_entry = reinterpret_cast<uint8_t *>(&low_resolution_patterns[1][c]);
|
||||
left_entry[0] = static_cast<uint8_t>(pattern);;
|
||||
left_entry[1] = static_cast<uint8_t>(pattern >> 7);
|
||||
right_entry[0] = static_cast<uint8_t>(pattern >> 14);
|
||||
right_entry[1] = static_cast<uint8_t>(pattern >> 21);
|
||||
}
|
||||
|
||||
printf("");
|
||||
}
|
||||
|
||||
void VideoBase::set_graphics_mode() {
|
||||
use_graphics_mode_ = true;
|
||||
}
|
||||
|
||||
void VideoBase::set_text_mode() {
|
||||
use_graphics_mode_ = false;
|
||||
}
|
||||
|
||||
void VideoBase::set_mixed_mode(bool mixed_mode) {
|
||||
mixed_mode_ = mixed_mode;
|
||||
}
|
||||
|
||||
void VideoBase::set_video_page(int page) {
|
||||
video_page_ = page;
|
||||
}
|
||||
|
||||
void VideoBase::set_low_resolution() {
|
||||
graphics_mode_ = GraphicsMode::LowRes;
|
||||
}
|
||||
|
||||
void VideoBase::set_high_resolution() {
|
||||
graphics_mode_ = GraphicsMode::HighRes;
|
||||
}
|
||||
|
||||
void VideoBase::set_character_rom(const std::vector<uint8_t> &character_rom) {
|
||||
character_rom_ = character_rom;
|
||||
|
||||
// Bytes in the character ROM are stored in reverse bit order. Reverse them
|
||||
// ahead of time so as to be able to use the same scaling table as for
|
||||
// high-resolution graphics.
|
||||
for(auto &byte : character_rom_) {
|
||||
byte =
|
||||
((byte & 0x40) ? 0x01 : 0x00) |
|
||||
((byte & 0x20) ? 0x02 : 0x00) |
|
||||
((byte & 0x10) ? 0x04 : 0x00) |
|
||||
((byte & 0x08) ? 0x08 : 0x00) |
|
||||
((byte & 0x04) ? 0x10 : 0x00) |
|
||||
((byte & 0x02) ? 0x20 : 0x00) |
|
||||
((byte & 0x01) ? 0x40 : 0x00) |
|
||||
(byte & 0x80);
|
||||
}
|
||||
}
|
219
Machines/AppleII/Video.hpp
Normal file
219
Machines/AppleII/Video.hpp
Normal file
@ -0,0 +1,219 @@
|
||||
//
|
||||
// Video.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/04/2018.
|
||||
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Video_hpp
|
||||
#define Video_hpp
|
||||
|
||||
#include "../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace AppleII {
|
||||
namespace Video {
|
||||
|
||||
class BusHandler {
|
||||
public:
|
||||
uint8_t perform_read(uint16_t address) {
|
||||
return 0xff;
|
||||
}
|
||||
};
|
||||
|
||||
class VideoBase {
|
||||
public:
|
||||
VideoBase();
|
||||
static void setup_tables();
|
||||
|
||||
/// @returns The CRT this video feed is feeding.
|
||||
Outputs::CRT::CRT *get_crt();
|
||||
|
||||
// Inputs for the various soft switches.
|
||||
void set_graphics_mode();
|
||||
void set_text_mode();
|
||||
void set_mixed_mode(bool);
|
||||
void set_video_page(int);
|
||||
void set_low_resolution();
|
||||
void set_high_resolution();
|
||||
|
||||
// Setup for text mode.
|
||||
void set_character_rom(const std::vector<uint8_t> &);
|
||||
|
||||
protected:
|
||||
std::unique_ptr<Outputs::CRT::CRT> crt_;
|
||||
|
||||
int video_page_ = 0;
|
||||
int row_ = 0, column_ = 0, flash_ = 0;
|
||||
uint16_t *pixel_pointer_ = nullptr;
|
||||
std::vector<uint8_t> character_rom_;
|
||||
|
||||
enum class GraphicsMode {
|
||||
LowRes,
|
||||
HighRes,
|
||||
Text
|
||||
} graphics_mode_ = GraphicsMode::LowRes;
|
||||
bool use_graphics_mode_ = false;
|
||||
bool mixed_mode_ = false;
|
||||
uint16_t graphics_carry_ = 0;
|
||||
|
||||
static uint16_t scaled_byte[256];
|
||||
static uint16_t low_resolution_patterns[2][16];
|
||||
};
|
||||
|
||||
template <class BusHandler> class Video: public VideoBase {
|
||||
public:
|
||||
/// Constructs an instance of the video feed; a CRT is also created.
|
||||
Video(BusHandler &bus_handler) :
|
||||
VideoBase(),
|
||||
bus_handler_(bus_handler) {}
|
||||
|
||||
/*!
|
||||
Advances time by @c cycles; expects to be fed by the CPU clock.
|
||||
Implicitly adds an extra half a colour clock at the end of every
|
||||
line.
|
||||
*/
|
||||
void run_for(const Cycles cycles) {
|
||||
/*
|
||||
Addressing scheme used throughout is that column 0 is the first column with pixels in it;
|
||||
row 0 is the first row with pixels in it.
|
||||
|
||||
A frame is oriented around 65 cycles across, 262 lines down.
|
||||
*/
|
||||
const int first_sync_line = 220; // A complete guess. Information needed.
|
||||
const int first_sync_column = 49; // Also a guess.
|
||||
|
||||
int int_cycles = cycles.as_int();
|
||||
while(int_cycles) {
|
||||
const int cycles_this_line = std::min(65 - column_, int_cycles);
|
||||
|
||||
if(row_ >= first_sync_line && row_ < first_sync_line + 3) {
|
||||
crt_->output_sync(static_cast<unsigned int>(cycles_this_line) * 7);
|
||||
} else {
|
||||
const int ending_column = column_ + cycles_this_line;
|
||||
const GraphicsMode line_mode = use_graphics_mode_ ? graphics_mode_ : GraphicsMode::Text;
|
||||
|
||||
// The first 40 columns are submitted to the CRT only upon completion;
|
||||
// they'll be either graphics or blank, depending on which side we are
|
||||
// of line 192.
|
||||
if(column_ < 40) {
|
||||
if(row_ < 192) {
|
||||
if(!column_) {
|
||||
pixel_pointer_ = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(80, 2));
|
||||
}
|
||||
|
||||
const int pixel_end = std::min(40, ending_column);
|
||||
const int character_row = row_ >> 3;
|
||||
const int pixel_row = row_ & 7;
|
||||
const uint16_t row_address = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7));
|
||||
const uint16_t text_address = static_cast<uint16_t>(((video_page_+1) * 0x400) + row_address);
|
||||
const uint16_t graphics_address = static_cast<uint16_t>(((video_page_+1) * 0x1000) + row_address + ((pixel_row&7) << 9));
|
||||
const int row_shift = (row_&4);
|
||||
|
||||
GraphicsMode pixel_mode = (!mixed_mode_ || row_ < 160) ? line_mode : GraphicsMode::Text;
|
||||
switch(pixel_mode) {
|
||||
case GraphicsMode::Text: {
|
||||
const uint8_t inverses[] = {
|
||||
0xff,
|
||||
static_cast<uint8_t>((flash_ / flash_length) * 0xff),
|
||||
0x00,
|
||||
0x00
|
||||
};
|
||||
for(int c = column_; c < pixel_end; ++c) {
|
||||
const uint8_t character = bus_handler_.perform_read(static_cast<uint16_t>(text_address + c));
|
||||
const std::size_t character_address = static_cast<std::size_t>(((character & 0x3f) << 3) + pixel_row);
|
||||
|
||||
const uint8_t character_pattern = character_rom_[character_address] ^ inverses[character >> 6];
|
||||
pixel_pointer_[c] = scaled_byte[character_pattern & 0x7f];
|
||||
}
|
||||
} break;
|
||||
|
||||
case GraphicsMode::LowRes:
|
||||
for(int c = column_; c < pixel_end; ++c) {
|
||||
const uint8_t character = bus_handler_.perform_read(static_cast<uint16_t>(text_address + c));
|
||||
pixel_pointer_[c] = low_resolution_patterns[c&1][(character >> row_shift)&0xf];
|
||||
}
|
||||
break;
|
||||
|
||||
case GraphicsMode::HighRes:
|
||||
for(int c = column_; c < pixel_end; ++c) {
|
||||
const uint8_t graphic = bus_handler_.perform_read(static_cast<uint16_t>(graphics_address + c));
|
||||
pixel_pointer_[c] = scaled_byte[graphic];
|
||||
if(graphic & 0x80) {
|
||||
reinterpret_cast<uint8_t *>(&pixel_pointer_[c])[0] |= graphics_carry_ << 7;
|
||||
}
|
||||
graphics_carry_ = pixel_pointer_[c] & 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if(ending_column >= 40) {
|
||||
crt_->output_data(280, 80);
|
||||
}
|
||||
} else {
|
||||
if(ending_column >= 40) {
|
||||
crt_->output_blank(280);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
The left border, sync, right border pattern doesn't depend on whether
|
||||
there were pixels this row and is output as soon as it is known.
|
||||
*/
|
||||
|
||||
const int first_blank_start = std::max(40, column_);
|
||||
const int first_blank_end = std::min(first_sync_column, ending_column);
|
||||
if(first_blank_end > first_blank_start) {
|
||||
crt_->output_blank(static_cast<unsigned int>(first_blank_end - first_blank_start) * 7);
|
||||
}
|
||||
|
||||
const int sync_start = std::max(first_sync_column, column_);
|
||||
const int sync_end = std::min(first_sync_column + 4, ending_column);
|
||||
if(sync_end > sync_start) {
|
||||
crt_->output_sync(static_cast<unsigned int>(sync_end - sync_start) * 7);
|
||||
}
|
||||
|
||||
int second_blank_start;
|
||||
if(line_mode != GraphicsMode::Text && (!mixed_mode_ || row_ < 159 || row_ >= 192)) {
|
||||
const int colour_burst_start = std::max(first_sync_column + 4, column_);
|
||||
const int colour_burst_end = std::min(first_sync_column + 7, ending_column);
|
||||
if(colour_burst_end > colour_burst_start) {
|
||||
crt_->output_default_colour_burst(static_cast<unsigned int>(colour_burst_end - colour_burst_start) * 7);
|
||||
}
|
||||
|
||||
second_blank_start = std::max(first_sync_column + 7, column_);
|
||||
} else {
|
||||
second_blank_start = std::max(first_sync_column + 4, column_);
|
||||
}
|
||||
|
||||
if(ending_column > second_blank_start) {
|
||||
crt_->output_blank(static_cast<unsigned int>(ending_column - second_blank_start) * 7);
|
||||
}
|
||||
}
|
||||
|
||||
int_cycles -= cycles_this_line;
|
||||
column_ = (column_ + cycles_this_line) % 65;
|
||||
if(!column_) {
|
||||
row_ = (row_ + 1) % 262;
|
||||
flash_ = (flash_ + 1) % (2 * flash_length);
|
||||
|
||||
// Add an extra half a colour cycle of blank; this isn't counted in the run_for
|
||||
// count explicitly but is promised.
|
||||
crt_->output_blank(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const int flash_length = 8406;
|
||||
BusHandler &bus_handler_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Video_hpp */
|
@ -437,7 +437,10 @@ void TIA::output_for_cycles(int number_of_cycles) {
|
||||
if(output_mode_ & blank_flag) {
|
||||
if(pixel_target_) {
|
||||
output_pixels(pixels_start_location_, output_cursor);
|
||||
if(crt_) crt_->output_data(static_cast<unsigned int>(output_cursor - pixels_start_location_) * 2, 2);
|
||||
if(crt_) {
|
||||
const unsigned int data_length = static_cast<unsigned int>(output_cursor - pixels_start_location_);
|
||||
crt_->output_data(data_length * 2, data_length);
|
||||
}
|
||||
pixel_target_ = nullptr;
|
||||
pixels_start_location_ = 0;
|
||||
}
|
||||
@ -459,7 +462,8 @@ void TIA::output_for_cycles(int number_of_cycles) {
|
||||
}
|
||||
|
||||
if(horizontal_counter_ == cycles_per_line && crt_) {
|
||||
crt_->output_data(static_cast<unsigned int>(output_cursor - pixels_start_location_) * 2, 2);
|
||||
const unsigned int data_length = static_cast<unsigned int>(output_cursor - pixels_start_location_);
|
||||
crt_->output_data(data_length * 2, data_length);
|
||||
pixel_target_ = nullptr;
|
||||
pixels_start_location_ = 0;
|
||||
}
|
||||
|
@ -371,8 +371,8 @@ class ConcreteMachine:
|
||||
void configure_as_target(const Analyser::Static::Target *target) override final {
|
||||
commodore_target_ = *dynamic_cast<const Analyser::Static::Commodore::Target *>(target);
|
||||
|
||||
if(target->loading_command.length()) {
|
||||
type_string(target->loading_command);
|
||||
if(!commodore_target_.loading_command.empty()) {
|
||||
type_string(commodore_target_.loading_command);
|
||||
}
|
||||
|
||||
if(commodore_target_.has_c1540) {
|
||||
|
@ -86,6 +86,7 @@ class ConcreteMachine:
|
||||
std::memcpy(&target[rom_ptr], data.data(), size_to_copy);
|
||||
rom_ptr += size_to_copy;
|
||||
}
|
||||
rom_inserted_[slot] = true;
|
||||
}
|
||||
|
||||
// Obtains the system ROMs.
|
||||
@ -131,8 +132,8 @@ class ConcreteMachine:
|
||||
void configure_as_target(const Analyser::Static::Target *target) override final {
|
||||
auto *const acorn_target = dynamic_cast<const Analyser::Static::Acorn::Target *>(target);
|
||||
|
||||
if(target->loading_command.length()) {
|
||||
type_string(target->loading_command);
|
||||
if(!acorn_target->loading_command.empty()) {
|
||||
type_string(acorn_target->loading_command);
|
||||
}
|
||||
|
||||
if(acorn_target->should_shift_restart) {
|
||||
@ -158,6 +159,7 @@ class ConcreteMachine:
|
||||
if(!media.tapes.empty()) {
|
||||
tape_.set_tape(media.tapes.front());
|
||||
}
|
||||
set_use_fast_tape_hack();
|
||||
|
||||
if(!media.disks.empty() && plus3_) {
|
||||
plus3_->set_disk(media.disks.front(), 0);
|
||||
@ -165,11 +167,14 @@ class ConcreteMachine:
|
||||
|
||||
ROMSlot slot = ROMSlot12;
|
||||
for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : media.cartridges) {
|
||||
const ROMSlot first_slot_tried = slot;
|
||||
while(rom_inserted_[slot]) {
|
||||
slot = static_cast<ROMSlot>((static_cast<int>(slot) + 1)&15);
|
||||
if(slot == first_slot_tried) return false;
|
||||
}
|
||||
set_rom(slot, cartridge->get_segments().front().data, false);
|
||||
slot = static_cast<ROMSlot>((static_cast<int>(slot) + 1)&15);
|
||||
}
|
||||
|
||||
set_use_fast_tape_hack();
|
||||
return !media.tapes.empty() || !media.disks.empty() || !media.cartridges.empty();
|
||||
}
|
||||
|
||||
@ -513,6 +518,7 @@ class ConcreteMachine:
|
||||
|
||||
// Things that directly constitute the memory map.
|
||||
uint8_t roms_[16][16384];
|
||||
bool rom_inserted_[16] = {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false};
|
||||
bool rom_write_masks_[16] = {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false};
|
||||
uint8_t os_[16384], ram_[32768];
|
||||
std::vector<uint8_t> dfs_, adfs1_, adfs2_;
|
||||
|
@ -55,9 +55,10 @@ VideoOutput::VideoOutput(uint8_t *memory) : ram_(memory) {
|
||||
"vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)"
|
||||
"{"
|
||||
"uint texValue = texture(sampler, coordinate).r;"
|
||||
"texValue >>= 4 - (int(icoordinate.x * 8) & 4);"
|
||||
"texValue >>= 4 - (int(icoordinate.x) & 4);"
|
||||
"return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));"
|
||||
"}");
|
||||
crt_->set_integer_coordinate_multiplier(8.0f);
|
||||
std::unique_ptr<Outputs::CRT::TextureBuilder::Bookender> bookender(new FourBPPBookender);
|
||||
crt_->set_bookender(std::move(bookender));
|
||||
// TODO: as implied below, I've introduced a clock's latency into the graphics pipeline somehow. Investigate.
|
||||
@ -97,7 +98,10 @@ void VideoOutput::start_pixel_line() {
|
||||
}
|
||||
|
||||
void VideoOutput::end_pixel_line() {
|
||||
if(current_output_target_) crt_->output_data(static_cast<unsigned int>((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_);
|
||||
if(current_output_target_) {
|
||||
const unsigned int data_length = static_cast<unsigned int>(current_output_target_ - initial_output_target_);
|
||||
crt_->output_data(data_length * current_output_divider_, data_length);
|
||||
}
|
||||
current_character_row_++;
|
||||
}
|
||||
|
||||
@ -115,7 +119,10 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) {
|
||||
}
|
||||
|
||||
if(!initial_output_target_ || divider != current_output_divider_) {
|
||||
if(current_output_target_) crt_->output_data(static_cast<unsigned int>((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_);
|
||||
if(current_output_target_) {
|
||||
const unsigned int data_length = static_cast<unsigned int>(current_output_target_ - initial_output_target_);
|
||||
crt_->output_data(data_length * current_output_divider_, data_length);
|
||||
}
|
||||
current_output_divider_ = divider;
|
||||
initial_output_target_ = current_output_target_ = crt_->allocate_write_area(640 / current_output_divider_, 4);
|
||||
}
|
||||
|
@ -24,12 +24,12 @@ struct KeyActions {
|
||||
Indicates that the key @c key has been either pressed or released, according to
|
||||
the state of @c isPressed.
|
||||
*/
|
||||
virtual void set_key_state(uint16_t key, bool is_pressed) = 0;
|
||||
virtual void set_key_state(uint16_t key, bool is_pressed) {}
|
||||
|
||||
/*!
|
||||
Instructs that all keys should now be treated as released.
|
||||
*/
|
||||
virtual void clear_all_keys() = 0;
|
||||
virtual void clear_all_keys() {}
|
||||
};
|
||||
|
||||
/*!
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "../../Components/1770/1770.hpp"
|
||||
#include "../../Components/9918/9918.hpp"
|
||||
#include "../../Components/8255/i8255.hpp"
|
||||
#include "../../Components/AudioToggle/AudioToggle.hpp"
|
||||
#include "../../Components/AY38910/AY38910.hpp"
|
||||
#include "../../Components/KonamiSCC/KonamiSCC.hpp"
|
||||
|
||||
@ -50,44 +51,6 @@ std::vector<std::unique_ptr<Configurable::Option>> get_options() {
|
||||
);
|
||||
}
|
||||
|
||||
/*!
|
||||
Provides a sample source that can programmatically be set to one of two values.
|
||||
*/
|
||||
class AudioToggle: public Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
AudioToggle(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
|
||||
audio_queue_(audio_queue) {}
|
||||
|
||||
void get_samples(std::size_t number_of_samples, std::int16_t *target) {
|
||||
for(std::size_t sample = 0; sample < number_of_samples; ++sample) {
|
||||
target[sample] = level_;
|
||||
}
|
||||
}
|
||||
|
||||
void set_sample_volume_range(std::int16_t range) {
|
||||
volume_ = range;
|
||||
}
|
||||
|
||||
void skip_samples(const std::size_t number_of_samples) {}
|
||||
|
||||
void set_output(bool enabled) {
|
||||
if(is_enabled_ == enabled) return;
|
||||
is_enabled_ = enabled;
|
||||
audio_queue_.defer([=] {
|
||||
level_ = enabled ? volume_ : 0;
|
||||
});
|
||||
}
|
||||
|
||||
bool get_output() {
|
||||
return is_enabled_;
|
||||
}
|
||||
|
||||
private:
|
||||
bool is_enabled_ = false;
|
||||
int16_t level_ = 0, volume_ = 0;
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
};
|
||||
|
||||
class AYPortHandler: public GI::AY38910::PortHandler {
|
||||
public:
|
||||
AYPortHandler(Storage::Tape::BinaryTapePlayer &tape_player) : tape_player_(tape_player) {}
|
||||
@ -201,8 +164,8 @@ class ConcreteMachine:
|
||||
insert_media(target->media);
|
||||
|
||||
// Type whatever has been requested.
|
||||
if(target->loading_command.length()) {
|
||||
type_string(target->loading_command);
|
||||
if(!msx_target->loading_command.empty()) {
|
||||
type_string(msx_target->loading_command);
|
||||
}
|
||||
}
|
||||
|
||||
@ -600,7 +563,7 @@ class ConcreteMachine:
|
||||
|
||||
class i8255PortHandler: public Intel::i8255::PortHandler {
|
||||
public:
|
||||
i8255PortHandler(ConcreteMachine &machine, AudioToggle &audio_toggle, Storage::Tape::BinaryTapePlayer &tape_player) :
|
||||
i8255PortHandler(ConcreteMachine &machine, Audio::Toggle &audio_toggle, Storage::Tape::BinaryTapePlayer &tape_player) :
|
||||
machine_(machine), audio_toggle_(audio_toggle), tape_player_(tape_player) {}
|
||||
|
||||
void set_value(int port, uint8_t value) {
|
||||
@ -637,7 +600,7 @@ class ConcreteMachine:
|
||||
|
||||
private:
|
||||
ConcreteMachine &machine_;
|
||||
AudioToggle &audio_toggle_;
|
||||
Audio::Toggle &audio_toggle_;
|
||||
Storage::Tape::BinaryTapePlayer &tape_player_;
|
||||
};
|
||||
|
||||
@ -647,10 +610,10 @@ class ConcreteMachine:
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
GI::AY38910::AY38910 ay_;
|
||||
AudioToggle audio_toggle_;
|
||||
Audio::Toggle audio_toggle_;
|
||||
Konami::SCC scc_;
|
||||
Outputs::Speaker::CompoundSource<GI::AY38910::AY38910, AudioToggle, Konami::SCC> mixer_;
|
||||
Outputs::Speaker::LowpassSpeaker<Outputs::Speaker::CompoundSource<GI::AY38910::AY38910, AudioToggle, Konami::SCC>> speaker_;
|
||||
Outputs::Speaker::CompoundSource<GI::AY38910::AY38910, Audio::Toggle, Konami::SCC> mixer_;
|
||||
Outputs::Speaker::LowpassSpeaker<Outputs::Speaker::CompoundSource<GI::AY38910::AY38910, Audio::Toggle, Konami::SCC>> speaker_;
|
||||
|
||||
Storage::Tape::BinaryTapePlayer tape_player_;
|
||||
bool tape_player_is_sleeping_ = false;
|
||||
|
@ -273,8 +273,8 @@ class ConcreteMachine:
|
||||
microdisc_.set_delegate(this);
|
||||
}
|
||||
|
||||
if(target->loading_command.length()) {
|
||||
type_string(target->loading_command);
|
||||
if(!oric_target->loading_command.empty()) {
|
||||
type_string(oric_target->loading_command);
|
||||
}
|
||||
|
||||
if(oric_target->use_atmos_rom) {
|
||||
|
@ -191,7 +191,7 @@ void VideoOutput::run_for(const Cycles cycles) {
|
||||
}
|
||||
|
||||
if(h_counter == 40) {
|
||||
crt_->output_data(40 * 6, 1);
|
||||
crt_->output_data(40 * 6);
|
||||
}
|
||||
} else {
|
||||
// this is a blank line (or the equivalent part of a pixel line)
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "MachineForTarget.hpp"
|
||||
|
||||
#include "../AmstradCPC/AmstradCPC.hpp"
|
||||
#include "../AppleII/AppleII.hpp"
|
||||
#include "../Atari2600/Atari2600.hpp"
|
||||
#include "../ColecoVision/ColecoVision.hpp"
|
||||
#include "../Commodore/Vic-20/Vic20.hpp"
|
||||
@ -27,6 +28,7 @@ namespace {
|
||||
::Machine::DynamicMachine *machine = nullptr;
|
||||
switch(target->machine) {
|
||||
case Analyser::Machine::AmstradCPC: machine = new Machine::TypedDynamicMachine<AmstradCPC::Machine>(AmstradCPC::Machine::AmstradCPC()); break;
|
||||
case Analyser::Machine::AppleII: machine = new Machine::TypedDynamicMachine<AppleII::Machine>(AppleII::Machine::AppleII()); break;
|
||||
case Analyser::Machine::Atari2600: machine = new Machine::TypedDynamicMachine<Atari2600::Machine>(Atari2600::Machine::Atari2600()); break;
|
||||
case Analyser::Machine::ColecoVision: machine = new Machine::TypedDynamicMachine<Coleco::Vision::Machine>(Coleco::Vision::Machine::ColecoVision()); break;
|
||||
case Analyser::Machine::Electron: machine = new Machine::TypedDynamicMachine<Electron::Machine>(Electron::Machine::Electron()); break;
|
||||
@ -60,7 +62,7 @@ namespace {
|
||||
|
||||
}
|
||||
|
||||
::Machine::DynamicMachine *::Machine::MachineForTargets(const std::vector<std::unique_ptr<Analyser::Static::Target>> &targets, const ROMMachine::ROMFetcher &rom_fetcher, Error &error) {
|
||||
::Machine::DynamicMachine *::Machine::MachineForTargets(const Analyser::Static::TargetList &targets, const ROMMachine::ROMFetcher &rom_fetcher, Error &error) {
|
||||
// Zero targets implies no machine.
|
||||
if(targets.empty()) {
|
||||
error = Error::NoTargets;
|
||||
@ -95,6 +97,7 @@ namespace {
|
||||
std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine) {
|
||||
switch(machine) {
|
||||
case Analyser::Machine::AmstradCPC: return "AmstradCPC";
|
||||
case Analyser::Machine::AppleII: return "AppleII";
|
||||
case Analyser::Machine::Atari2600: return "Atari2600";
|
||||
case Analyser::Machine::ColecoVision: return "ColecoVision";
|
||||
case Analyser::Machine::Electron: return "Electron";
|
||||
@ -110,6 +113,7 @@ std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine)
|
||||
std::string Machine::LongNameForTargetMachine(Analyser::Machine machine) {
|
||||
switch(machine) {
|
||||
case Analyser::Machine::AmstradCPC: return "Amstrad CPC";
|
||||
case Analyser::Machine::AppleII: return "Apple II";
|
||||
case Analyser::Machine::Atari2600: return "Atari 2600";
|
||||
case Analyser::Machine::ColecoVision: return "ColecoVision";
|
||||
case Analyser::Machine::Electron: return "Acorn Electron";
|
||||
|
@ -31,7 +31,7 @@ enum class Error {
|
||||
receive the supplied static analyser result. The machine has been allocated
|
||||
on the heap. It is the caller's responsibility to delete the class when finished.
|
||||
*/
|
||||
DynamicMachine *MachineForTargets(const std::vector<std::unique_ptr<Analyser::Static::Target>> &targets, const ::ROMMachine::ROMFetcher &rom_fetcher, Error &error);
|
||||
DynamicMachine *MachineForTargets(const Analyser::Static::TargetList &targets, const ::ROMMachine::ROMFetcher &rom_fetcher, Error &error);
|
||||
|
||||
/*!
|
||||
Returns a short string name for the machine identified by the target,
|
||||
|
@ -31,9 +31,10 @@ Video::Video() :
|
||||
"float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)"
|
||||
"{"
|
||||
"uint texValue = texture(sampler, coordinate).r;"
|
||||
"texValue <<= int(icoordinate.x * 8) & 7;"
|
||||
"texValue <<= int(icoordinate.x) & 7;"
|
||||
"return float(texValue & 128u);"
|
||||
"}");
|
||||
crt_->set_integer_coordinate_multiplier(8.0f);
|
||||
|
||||
// Show only the centre 80% of the TV frame.
|
||||
crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite);
|
||||
@ -62,7 +63,7 @@ void Video::flush(bool next_sync) {
|
||||
unsigned int data_length = static_cast<unsigned int>(line_data_pointer_ - line_data_) * HalfCyclesPerByte;
|
||||
if(data_length < cycles_since_update_ || next_sync) {
|
||||
unsigned int output_length = std::min(data_length, cycles_since_update_);
|
||||
crt_->output_data(output_length, HalfCyclesPerByte);
|
||||
crt_->output_data(output_length, output_length / HalfCyclesPerByte);
|
||||
line_data_pointer_ = line_data_ = nullptr;
|
||||
cycles_since_update_ -= output_length;
|
||||
} else return;
|
||||
@ -100,7 +101,7 @@ void Video::output_byte(uint8_t byte) {
|
||||
if(line_data_) {
|
||||
// If the buffer is full, output it now and obtain a new one
|
||||
if(line_data_pointer_ - line_data_ == StandardAllocationSize) {
|
||||
crt_->output_data(StandardAllocationSize, HalfCyclesPerByte);
|
||||
crt_->output_data(StandardAllocationSize * HalfCyclesPerByte, StandardAllocationSize);
|
||||
cycles_since_update_ -= StandardAllocationSize * HalfCyclesPerByte;
|
||||
line_data_pointer_ = line_data_ = crt_->allocate_write_area(StandardAllocationSize);
|
||||
if(!line_data_) return;
|
||||
|
@ -31,7 +31,7 @@ class Video {
|
||||
/// @returns The CRT this video feed is feeding.
|
||||
Outputs::CRT::CRT *get_crt();
|
||||
|
||||
/// Advances time by @c cycles.
|
||||
/// Advances time by @c half-cycles.
|
||||
void run_for(const HalfCycles);
|
||||
/// Forces output to catch up to the current output position.
|
||||
void flush();
|
||||
|
@ -324,8 +324,8 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
}
|
||||
Memory::Fuzz(ram_);
|
||||
|
||||
if(target->loading_command.length()) {
|
||||
type_string(target->loading_command);
|
||||
if(!zx8081_target->loading_command.empty()) {
|
||||
type_string(zx8081_target->loading_command);
|
||||
}
|
||||
|
||||
insert_media(target->media);
|
||||
@ -437,7 +437,7 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
private:
|
||||
CPU::Z80::Processor<ConcreteMachine, false, is_zx81> z80_;
|
||||
|
||||
std::shared_ptr<Video> video_;
|
||||
std::unique_ptr<Video> video_;
|
||||
std::vector<uint8_t> zx81_rom_, zx80_rom_;
|
||||
|
||||
uint16_t tape_trap_address_, tape_return_address_;
|
||||
|
@ -130,6 +130,12 @@
|
||||
4B1497921EE4B5A800CE2596 /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1497901EE4B5A800CE2596 /* ZX8081.cpp */; };
|
||||
4B1497981EE4B97F00CE2596 /* ZX8081Options.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B1497961EE4B97F00CE2596 /* ZX8081Options.xib */; };
|
||||
4B1558C01F844ECD006E9A97 /* BitReverse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1558BE1F844ECD006E9A97 /* BitReverse.cpp */; };
|
||||
4B15A9FC208249BB005E6C8D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B15A9FA208249BB005E6C8D /* StaticAnalyser.cpp */; };
|
||||
4B15A9FD208249BB005E6C8D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B15A9FA208249BB005E6C8D /* StaticAnalyser.cpp */; };
|
||||
4B15AA0D2082C799005E6C8D /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B15AA0A2082C799005E6C8D /* Video.cpp */; };
|
||||
4B15AA0E2082C799005E6C8D /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B15AA0A2082C799005E6C8D /* Video.cpp */; };
|
||||
4B15AA0F2082C799005E6C8D /* AppleII.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B15AA0C2082C799005E6C8D /* AppleII.cpp */; };
|
||||
4B15AA102082C799005E6C8D /* AppleII.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B15AA0C2082C799005E6C8D /* AppleII.cpp */; };
|
||||
4B1B88BB202E2EC100B67DFF /* MultiKeyboardMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1B88B9202E2EC100B67DFF /* MultiKeyboardMachine.cpp */; };
|
||||
4B1B88BC202E2EC100B67DFF /* MultiKeyboardMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1B88B9202E2EC100B67DFF /* MultiKeyboardMachine.cpp */; };
|
||||
4B1B88BD202E3D3D00B67DFF /* MultiMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3FCC3F201EC24200960631 /* MultiMachine.cpp */; };
|
||||
@ -202,6 +208,8 @@
|
||||
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */; };
|
||||
4B58601E1F806AB200AEE2E3 /* MFMSectorDump.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B58601C1F806AB200AEE2E3 /* MFMSectorDump.cpp */; };
|
||||
4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */; };
|
||||
4B595FAD2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B595FAC2086DFBA0083CAA8 /* AudioToggle.cpp */; };
|
||||
4B595FAE2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B595FAC2086DFBA0083CAA8 /* AudioToggle.cpp */; };
|
||||
4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5FADB81DE3151600AEC565 /* FileHolder.cpp */; };
|
||||
4B5FADC01DE3BF2B00AEC565 /* Microdisc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5FADBE1DE3BF2B00AEC565 /* Microdisc.cpp */; };
|
||||
4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */; };
|
||||
@ -701,6 +709,12 @@
|
||||
4B1497971EE4B97F00CE2596 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/ZX8081Options.xib"; sourceTree = SOURCE_ROOT; };
|
||||
4B1558BE1F844ECD006E9A97 /* BitReverse.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = BitReverse.cpp; path = Data/BitReverse.cpp; sourceTree = "<group>"; };
|
||||
4B1558BF1F844ECD006E9A97 /* BitReverse.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = BitReverse.hpp; path = Data/BitReverse.hpp; sourceTree = "<group>"; };
|
||||
4B15A9FA208249BB005E6C8D /* StaticAnalyser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = AppleII/StaticAnalyser.cpp; sourceTree = "<group>"; };
|
||||
4B15A9FB208249BB005E6C8D /* StaticAnalyser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = AppleII/StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
4B15AA092082C799005E6C8D /* AppleII.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AppleII.hpp; sourceTree = "<group>"; };
|
||||
4B15AA0A2082C799005E6C8D /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Video.cpp; sourceTree = "<group>"; };
|
||||
4B15AA0B2082C799005E6C8D /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = "<group>"; };
|
||||
4B15AA0C2082C799005E6C8D /* AppleII.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AppleII.cpp; sourceTree = "<group>"; };
|
||||
4B1667F61FFF1E2400A16032 /* Konami.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Konami.hpp; path = MSX/Cartridges/Konami.hpp; sourceTree = "<group>"; };
|
||||
4B1667F91FFF215E00A16032 /* ASCII16kb.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ASCII16kb.hpp; path = MSX/Cartridges/ASCII16kb.hpp; sourceTree = "<group>"; };
|
||||
4B1667FA1FFF215E00A16032 /* ASCII8kb.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ASCII8kb.hpp; path = MSX/Cartridges/ASCII8kb.hpp; sourceTree = "<group>"; };
|
||||
@ -844,6 +858,8 @@
|
||||
4B58601D1F806AB200AEE2E3 /* MFMSectorDump.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MFMSectorDump.hpp; sourceTree = "<group>"; };
|
||||
4B59199A1DAC6C46005BB85C /* OricTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OricTAP.cpp; sourceTree = "<group>"; };
|
||||
4B59199B1DAC6C46005BB85C /* OricTAP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OricTAP.hpp; sourceTree = "<group>"; };
|
||||
4B595FAB2086DFBA0083CAA8 /* AudioToggle.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AudioToggle.hpp; sourceTree = "<group>"; };
|
||||
4B595FAC2086DFBA0083CAA8 /* AudioToggle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AudioToggle.cpp; sourceTree = "<group>"; };
|
||||
4B5FADB81DE3151600AEC565 /* FileHolder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FileHolder.cpp; sourceTree = "<group>"; };
|
||||
4B5FADB91DE3151600AEC565 /* FileHolder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FileHolder.hpp; sourceTree = "<group>"; };
|
||||
4B5FADBE1DE3BF2B00AEC565 /* Microdisc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Microdisc.cpp; path = Oric/Microdisc.cpp; sourceTree = "<group>"; };
|
||||
@ -1505,6 +1521,26 @@
|
||||
name = ZX8081;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B15A9FE20824C9F005E6C8D /* AppleII */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B15A9FA208249BB005E6C8D /* StaticAnalyser.cpp */,
|
||||
4B15A9FB208249BB005E6C8D /* StaticAnalyser.hpp */,
|
||||
);
|
||||
name = AppleII;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B15AA082082C799005E6C8D /* AppleII */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B15AA0C2082C799005E6C8D /* AppleII.cpp */,
|
||||
4B15AA0A2082C799005E6C8D /* Video.cpp */,
|
||||
4B15AA092082C799005E6C8D /* AppleII.hpp */,
|
||||
4B15AA0B2082C799005E6C8D /* Video.hpp */,
|
||||
);
|
||||
path = AppleII;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B1667F81FFF1E2900A16032 /* Cartridges */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1897,6 +1933,15 @@
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B595FAA2086DFBA0083CAA8 /* AudioToggle */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B595FAB2086DFBA0083CAA8 /* AudioToggle.hpp */,
|
||||
4B595FAC2086DFBA0083CAA8 /* AudioToggle.cpp */,
|
||||
);
|
||||
path = AudioToggle;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B643F3B1D77AD6D00D431D6 /* StaticAnalyser */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -2144,6 +2189,7 @@
|
||||
4B8944EA201967B4007DE474 /* StaticAnalyser.hpp */,
|
||||
4B8944EB201967B4007DE474 /* Acorn */,
|
||||
4B894514201967B4007DE474 /* AmstradCPC */,
|
||||
4B15A9FE20824C9F005E6C8D /* AppleII */,
|
||||
4B8944F3201967B4007DE474 /* Atari */,
|
||||
4B7A90EA20410A85008514A2 /* Coleco */,
|
||||
4B8944FB201967B4007DE474 /* Commodore */,
|
||||
@ -2705,6 +2751,7 @@
|
||||
4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */,
|
||||
4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */,
|
||||
4B38F3491F2EC12000D9235D /* AmstradCPC */,
|
||||
4B15AA082082C799005E6C8D /* AppleII */,
|
||||
4B2E2D961C3A06EC00138695 /* Atari2600 */,
|
||||
4B7A90E22041097C008514A2 /* ColecoVision */,
|
||||
4B4DC81D1D2C2425003C5BF8 /* Commodore */,
|
||||
@ -2803,6 +2850,7 @@
|
||||
4BC9DF4A1D04691600F44158 /* Components */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B595FAA2086DFBA0083CAA8 /* AudioToggle */,
|
||||
4BD468F81D8DF4290084958B /* 1770 */,
|
||||
4BC9DF4B1D04691600F44158 /* 6522 */,
|
||||
4B1E85791D174DEC001EF87D /* 6532 */,
|
||||
@ -3465,6 +3513,7 @@
|
||||
4B894531201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||
4B894539201967B4007DE474 /* Tape.cpp in Sources */,
|
||||
4B055AE51FAE9B6F0060FFFF /* IntermediateShader.cpp in Sources */,
|
||||
4B15A9FD208249BB005E6C8D /* StaticAnalyser.cpp in Sources */,
|
||||
4B055AD31FAE9B0B0060FFFF /* Microdisc.cpp in Sources */,
|
||||
4B055AB41FAE860F0060FFFF /* OricTAP.cpp in Sources */,
|
||||
4B055AB71FAE860F0060FFFF /* TZX.cpp in Sources */,
|
||||
@ -3518,11 +3567,13 @@
|
||||
4B055ACA1FAE9AFB0060FFFF /* Vic20.cpp in Sources */,
|
||||
4B055ABC1FAE86170060FFFF /* ZX8081.cpp in Sources */,
|
||||
4B055AC91FAE9AFB0060FFFF /* Keyboard.cpp in Sources */,
|
||||
4B15AA0E2082C799005E6C8D /* Video.cpp in Sources */,
|
||||
4B055A991FAE85CB0060FFFF /* DiskController.cpp in Sources */,
|
||||
4B055ACC1FAE9B030060FFFF /* Electron.cpp in Sources */,
|
||||
4B055AB11FAE86070060FFFF /* Tape.cpp in Sources */,
|
||||
4BFE7B881FC39D8900160B38 /* StandardOptions.cpp in Sources */,
|
||||
4B894533201967B4007DE474 /* 6502.cpp in Sources */,
|
||||
4B15AA102082C799005E6C8D /* AppleII.cpp in Sources */,
|
||||
4B055AA91FAE85EF0060FFFF /* CommodoreGCR.cpp in Sources */,
|
||||
4B055ADB1FAE9B460060FFFF /* 6560.cpp in Sources */,
|
||||
4B055AA01FAE85DA0060FFFF /* MFMSectorDump.cpp in Sources */,
|
||||
@ -3570,6 +3621,7 @@
|
||||
4B055AE31FAE9B6F0060FFFF /* TextureBuilder.cpp in Sources */,
|
||||
4BB0A65D2045009000FB3688 /* ColecoVision.cpp in Sources */,
|
||||
4BB0A65C2044FD3000FB3688 /* SN76489.cpp in Sources */,
|
||||
4B595FAE2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */,
|
||||
4B055AB91FAE86170060FFFF /* Acorn.cpp in Sources */,
|
||||
4B055A931FAE85B50060FFFF /* BinaryDump.cpp in Sources */,
|
||||
4B89452D201967B4007DE474 /* Tape.cpp in Sources */,
|
||||
@ -3615,6 +3667,7 @@
|
||||
4B07835A1FC11D10001D12BB /* Configurable.cpp in Sources */,
|
||||
4B8334951F5E25B60097E338 /* C1540.cpp in Sources */,
|
||||
4B89453C201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||
4B595FAD2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */,
|
||||
4B1497921EE4B5A800CE2596 /* ZX8081.cpp in Sources */,
|
||||
4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */,
|
||||
4B4518861F75E91A00926311 /* MFMDiskController.cpp in Sources */,
|
||||
@ -3693,6 +3746,7 @@
|
||||
4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */,
|
||||
4B54C0C21F8D91CD0050900F /* Keyboard.cpp in Sources */,
|
||||
4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */,
|
||||
4B15AA0F2082C799005E6C8D /* AppleII.cpp in Sources */,
|
||||
4B89449520194CB3007DE474 /* MachineForTarget.cpp in Sources */,
|
||||
4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */,
|
||||
4B6A4C991F58F09E00E3F787 /* 6502Base.cpp in Sources */,
|
||||
@ -3721,6 +3775,7 @@
|
||||
4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */,
|
||||
4B8805FB1DCFF807003085B1 /* Oric.cpp in Sources */,
|
||||
4BFE7B871FC39BF100160B38 /* StandardOptions.cpp in Sources */,
|
||||
4B15A9FC208249BB005E6C8D /* StaticAnalyser.cpp in Sources */,
|
||||
4B5FADC01DE3BF2B00AEC565 /* Microdisc.cpp in Sources */,
|
||||
4B54C0C81F8D91E50050900F /* Keyboard.cpp in Sources */,
|
||||
4B79A5011FC913C900EEDAD5 /* MSX.cpp in Sources */,
|
||||
@ -3737,6 +3792,7 @@
|
||||
4B54C0BC1F8D8E790050900F /* KeyboardMachine.cpp in Sources */,
|
||||
4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */,
|
||||
4B894534201967B4007DE474 /* AddressMapper.cpp in Sources */,
|
||||
4B15AA0D2082C799005E6C8D /* Video.cpp in Sources */,
|
||||
4B1B88C8202E469300B67DFF /* MultiJoystickMachine.cpp in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -23,6 +23,8 @@
|
||||
#import "NSBundle+DataResource.h"
|
||||
#import "NSData+StdVector.h"
|
||||
|
||||
#include <bitset>
|
||||
|
||||
@interface CSMachine() <CSFastLoading>
|
||||
- (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length;
|
||||
- (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker;
|
||||
@ -54,6 +56,8 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP
|
||||
|
||||
CSStaticAnalyser *_analyser;
|
||||
std::unique_ptr<Machine::DynamicMachine> _machine;
|
||||
|
||||
std::bitset<65536> _depressedKeys;
|
||||
}
|
||||
|
||||
- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result {
|
||||
@ -168,12 +172,25 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP
|
||||
- (void)setKey:(uint16_t)key characters:(NSString *)characters isPressed:(BOOL)isPressed {
|
||||
auto keyboard_machine = _machine->keyboard_machine();
|
||||
if(keyboard_machine) {
|
||||
// Don't pass anything on if this is not new information.
|
||||
if(_depressedKeys[key] == !!isPressed) return;
|
||||
_depressedKeys[key] = !!isPressed;
|
||||
|
||||
// Pick an ASCII code, if any.
|
||||
char pressedKey = '\0';
|
||||
if(characters.length) {
|
||||
unichar firstCharacter = [characters characterAtIndex:0];
|
||||
if(firstCharacter < 128) {
|
||||
pressedKey = (char)firstCharacter;
|
||||
}
|
||||
}
|
||||
|
||||
@synchronized(self) {
|
||||
Inputs::Keyboard &keyboard = keyboard_machine->get_keyboard();
|
||||
|
||||
// Connect the Carbon-era Mac keyboard scancodes to Clock Signal's 'universal' enumeration in order
|
||||
// to pass into the platform-neutral realm.
|
||||
#define BIND(source, dest) case source: keyboard.set_key_pressed(Inputs::Keyboard::Key::dest, isPressed); break
|
||||
#define BIND(source, dest) case source: keyboard.set_key_pressed(Inputs::Keyboard::Key::dest, pressedKey, isPressed); break
|
||||
switch(key) {
|
||||
BIND(VK_ANSI_0, k0); BIND(VK_ANSI_1, k1); BIND(VK_ANSI_2, k2); BIND(VK_ANSI_3, k3); BIND(VK_ANSI_4, k4);
|
||||
BIND(VK_ANSI_5, k5); BIND(VK_ANSI_6, k6); BIND(VK_ANSI_7, k7); BIND(VK_ANSI_8, k8); BIND(VK_ANSI_9, k9);
|
||||
|
@ -10,6 +10,6 @@
|
||||
|
||||
@interface CSStaticAnalyser (ResultVector)
|
||||
|
||||
- (std::vector<std::unique_ptr<Analyser::Static::Target>> &)targets;
|
||||
- (Analyser::Static::TargetList &)targets;
|
||||
|
||||
@end
|
||||
|
@ -42,6 +42,7 @@ typedef int Kilobytes;
|
||||
- (instancetype)initWithVic20Region:(CSMachineVic20Region)region memorySize:(Kilobytes)memorySize hasC1540:(BOOL)hasC1540;
|
||||
- (instancetype)initWithZX80MemorySize:(Kilobytes)memorySize useZX81ROM:(BOOL)useZX81ROM;
|
||||
- (instancetype)initWithZX81MemorySize:(Kilobytes)memorySize;
|
||||
- (instancetype)initWithAppleII;
|
||||
|
||||
@property(nonatomic, readonly) NSString *optionsPanelNibName;
|
||||
@property(nonatomic, readonly) NSString *displayName;
|
||||
|
@ -23,7 +23,7 @@
|
||||
#import "Clock_Signal-Swift.h"
|
||||
|
||||
@implementation CSStaticAnalyser {
|
||||
std::vector<std::unique_ptr<Analyser::Static::Target>> _targets;
|
||||
Analyser::Static::TargetList _targets;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFileAtURL:(NSURL *)url {
|
||||
@ -153,6 +153,18 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithAppleII {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
using Target = Analyser::Static::Target;
|
||||
std::unique_ptr<Target> target(new Target);
|
||||
target->machine = Analyser::Machine::AppleII;
|
||||
_targets.push_back(std::move(target));
|
||||
}
|
||||
return self;
|
||||
|
||||
}
|
||||
|
||||
- (NSString *)optionsPanelNibName {
|
||||
switch(_targets.front()->machine) {
|
||||
case Analyser::Machine::AmstradCPC: return nil;
|
||||
@ -166,7 +178,7 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
|
||||
}
|
||||
}
|
||||
|
||||
- (std::vector<std::unique_ptr<Analyser::Static::Target>> &)targets {
|
||||
- (Analyser::Static::TargetList &)targets {
|
||||
return _targets;
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,51 @@
|
||||
<rect key="frame" x="13" y="51" width="574" height="100"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<tabViewItems>
|
||||
<tabViewItem label="Acorn Electron" identifier="electron" id="muc-z9-Vqc">
|
||||
<tabViewItem label="Apple II" identifier="appleii" id="P59-QG-LOa">
|
||||
<view key="view" id="dHz-Yv-GNq">
|
||||
<rect key="frame" x="10" y="33" width="554" height="54"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
</view>
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Amstrad CPC" identifier="cpc" id="JmB-OF-xcM">
|
||||
<view key="view" id="5zS-Nj-Ynx">
|
||||
<rect key="frame" x="10" y="33" width="554" height="54"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="00d-sg-Krh">
|
||||
<rect key="frame" x="54" y="27" width="94" height="26"/>
|
||||
<popUpButtonCell key="cell" type="push" title="CPC6128" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="6128" imageScaling="proportionallyDown" inset="2" selectedItem="klh-ZE-Agp" id="hVJ-h6-iea">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="r3D-C2-Ruq">
|
||||
<items>
|
||||
<menuItem title="CPC464" tag="464" id="5kZ-XF-RFl"/>
|
||||
<menuItem title="CPC664" tag="664" id="Sct-ZX-Qp1"/>
|
||||
<menuItem title="CPC6128" state="on" tag="6128" id="klh-ZE-Agp"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="q9q-sl-J0q">
|
||||
<rect key="frame" x="8" y="32" width="42" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model" id="Cw3-q5-1bC">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="00d-sg-Krh" secondAttribute="trailing" constant="17" id="4AF-5C-2IF"/>
|
||||
<constraint firstItem="00d-sg-Krh" firstAttribute="top" secondItem="5zS-Nj-Ynx" secondAttribute="top" constant="3" id="4U2-iE-UFM"/>
|
||||
<constraint firstItem="q9q-sl-J0q" firstAttribute="leading" secondItem="5zS-Nj-Ynx" secondAttribute="leading" constant="10" id="Wof-5h-gfD"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="00d-sg-Krh" secondAttribute="bottom" constant="17" id="enU-LN-Nep"/>
|
||||
<constraint firstItem="00d-sg-Krh" firstAttribute="leading" secondItem="q9q-sl-J0q" secondAttribute="trailing" constant="8" id="mA8-US-ndo"/>
|
||||
<constraint firstItem="q9q-sl-J0q" firstAttribute="centerY" secondItem="00d-sg-Krh" secondAttribute="centerY" id="vA8-IA-Uwf"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Electron" identifier="electron" id="muc-z9-Vqc">
|
||||
<view key="view" id="SRc-2D-95G">
|
||||
<rect key="frame" x="10" y="33" width="554" height="54"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
@ -58,51 +102,13 @@
|
||||
</constraints>
|
||||
</view>
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Amstrad CPC" identifier="cpc" id="JmB-OF-xcM">
|
||||
<view key="view" id="5zS-Nj-Ynx">
|
||||
<rect key="frame" x="10" y="33" width="516" height="92"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="00d-sg-Krh">
|
||||
<rect key="frame" x="54" y="65" width="94" height="26"/>
|
||||
<popUpButtonCell key="cell" type="push" title="CPC6128" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="6128" imageScaling="proportionallyDown" inset="2" selectedItem="klh-ZE-Agp" id="hVJ-h6-iea">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="r3D-C2-Ruq">
|
||||
<items>
|
||||
<menuItem title="CPC464" tag="464" id="5kZ-XF-RFl"/>
|
||||
<menuItem title="CPC664" tag="664" id="Sct-ZX-Qp1"/>
|
||||
<menuItem title="CPC6128" state="on" tag="6128" id="klh-ZE-Agp"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="q9q-sl-J0q">
|
||||
<rect key="frame" x="8" y="70" width="42" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model" id="Cw3-q5-1bC">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="00d-sg-Krh" secondAttribute="trailing" constant="17" id="4AF-5C-2IF"/>
|
||||
<constraint firstItem="00d-sg-Krh" firstAttribute="top" secondItem="5zS-Nj-Ynx" secondAttribute="top" constant="3" id="4U2-iE-UFM"/>
|
||||
<constraint firstItem="q9q-sl-J0q" firstAttribute="leading" secondItem="5zS-Nj-Ynx" secondAttribute="leading" constant="10" id="Wof-5h-gfD"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="00d-sg-Krh" secondAttribute="bottom" constant="17" id="enU-LN-Nep"/>
|
||||
<constraint firstItem="00d-sg-Krh" firstAttribute="leading" secondItem="q9q-sl-J0q" secondAttribute="trailing" constant="8" id="mA8-US-ndo"/>
|
||||
<constraint firstItem="q9q-sl-J0q" firstAttribute="centerY" secondItem="00d-sg-Krh" secondAttribute="centerY" id="vA8-IA-Uwf"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</tabViewItem>
|
||||
<tabViewItem label="MSX" identifier="msx" id="6SR-DY-zdI">
|
||||
<view key="view" id="mWD-An-tR7">
|
||||
<rect key="frame" x="10" y="33" width="516" height="92"/>
|
||||
<rect key="frame" x="10" y="33" width="554" height="54"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8xT-Pr-8SE">
|
||||
<rect key="frame" x="15" y="73" width="124" height="18"/>
|
||||
<rect key="frame" x="15" y="35" width="124" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Attach disk drive" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="CB3-nA-VTM">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -118,8 +124,8 @@
|
||||
</view>
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Oric" identifier="oric" id="NSx-DC-p4M">
|
||||
<view key="view" id="sOR-e0-8iZ">
|
||||
<rect key="frame" x="10" y="33" width="554" height="61"/>
|
||||
<view key="view" misplaced="YES" id="sOR-e0-8iZ">
|
||||
<rect key="frame" x="10" y="33" width="554" height="54"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="mZw-PY-0Yv">
|
||||
@ -165,8 +171,8 @@
|
||||
</view>
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Vic-20" identifier="vic20" id="cyO-PU-hSU">
|
||||
<view key="view" id="fLI-XB-QCr">
|
||||
<rect key="frame" x="10" y="33" width="554" height="94"/>
|
||||
<view key="view" misplaced="YES" id="fLI-XB-QCr">
|
||||
<rect key="frame" x="10" y="33" width="554" height="54"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ueK-gq-gaF">
|
||||
@ -242,8 +248,8 @@
|
||||
</view>
|
||||
</tabViewItem>
|
||||
<tabViewItem label="ZX80" identifier="zx80" id="tMH-kF-GUz">
|
||||
<view key="view" id="8hL-Vn-Hg0">
|
||||
<rect key="frame" x="10" y="33" width="554" height="61"/>
|
||||
<view key="view" misplaced="YES" id="8hL-Vn-Hg0">
|
||||
<rect key="frame" x="10" y="33" width="554" height="54"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="I1a-Eu-5UB">
|
||||
@ -357,7 +363,7 @@ Gw
|
||||
</button>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="9YM-5x-pc0">
|
||||
<rect key="frame" x="20" y="14" width="398" height="34"/>
|
||||
<textFieldCell key="cell" sendsActionOnEndEditing="YES" id="xTm-Oy-oz5">
|
||||
<textFieldCell key="cell" allowsUndo="NO" sendsActionOnEndEditing="YES" id="xTm-Oy-oz5">
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="title">If you use File -> Open... to select a disk, tape or cartridge directly, the emulator will select and configure a machine for you.</string>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -114,6 +114,9 @@ class MachinePicker: NSObject {
|
||||
case "electron":
|
||||
return CSStaticAnalyser(electronDFS: electronDFSButton!.state == .on, adfs: electronADFSButton!.state == .on)!
|
||||
|
||||
case "appleii":
|
||||
return CSStaticAnalyser(appleII: ())
|
||||
|
||||
case "cpc":
|
||||
switch cpcModelTypeButton!.selectedItem!.tag {
|
||||
case 464: return CSStaticAnalyser(amstradCPCModel: .model464)
|
||||
|
@ -594,7 +594,7 @@ static NSDictionary<NSString *, AtariROMRecord *> *romRecordsBySHA1 = @{
|
||||
for(int c = 0; c < CC_SHA1_DIGEST_LENGTH; c++) [sha1 appendFormat:@"%02x", sha1Bytes[c]];
|
||||
|
||||
// get an analysis of the file
|
||||
std::vector<std::unique_ptr<Analyser::Static::Target>> targets = Analyser::Static::GetTargets([fullPath UTF8String]);
|
||||
TargetList targets = Analyser::Static::GetTargets([fullPath UTF8String]);
|
||||
|
||||
// grab the ROM record
|
||||
AtariROMRecord *romRecord = romRecordsBySHA1[sha1];
|
||||
|
@ -212,7 +212,7 @@ static NSDictionary<NSString *, MSXROMRecord *> *romRecordsBySHA1 = @{
|
||||
for(int c = 0; c < CC_SHA1_DIGEST_LENGTH; c++) [sha1 appendFormat:@"%02x", sha1Bytes[c]];
|
||||
|
||||
// get an analysis of the file
|
||||
std::vector<std::unique_ptr<Analyser::Static::Target>> targets = Analyser::Static::GetTargets([fullPath UTF8String]);
|
||||
TargetList targets = Analyser::Static::GetTargets([fullPath UTF8String]);
|
||||
|
||||
// grab the ROM record
|
||||
MSXROMRecord *romRecord = romRecordsBySHA1[sha1];
|
||||
|
@ -239,7 +239,7 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
// Determine the machine for the supplied file.
|
||||
std::vector<std::unique_ptr<Analyser::Static::Target>> targets = Analyser::Static::GetTargets(arguments.file_name);
|
||||
Analyser::Static::TargetList targets = Analyser::Static::GetTargets(arguments.file_name);
|
||||
if(targets.empty()) {
|
||||
std::cerr << "Cannot open " << arguments.file_name << "; no target machine found" << std::endl;
|
||||
return -1;
|
||||
@ -450,7 +450,12 @@ int main(int argc, char *argv[]) {
|
||||
if(keyboard_machine) {
|
||||
Inputs::Keyboard::Key key = Inputs::Keyboard::Key::Space;
|
||||
if(!KeyboardKeyForSDLScancode(event.key.keysym.scancode, key)) break;
|
||||
keyboard_machine->get_keyboard().set_key_pressed(key, is_pressed);
|
||||
|
||||
char key_value = '\0';
|
||||
const char *key_name = SDL_GetKeyName(event.key.keysym.sym);
|
||||
if(key_name[0] >= 0) key_value = key_name[0];
|
||||
|
||||
keyboard_machine->get_keyboard().set_key_pressed(key, key_value, is_pressed);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -125,11 +125,8 @@ Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested,
|
||||
#define output_position_y() (*reinterpret_cast<uint16_t *>(&next_output_run[OutputVertexOffsetOfVertical + 0]))
|
||||
#define output_tex_y() (*reinterpret_cast<uint16_t *>(&next_output_run[OutputVertexOffsetOfVertical + 2]))
|
||||
|
||||
#define source_input_position_x1() (*reinterpret_cast<uint16_t *>(&next_run[SourceVertexOffsetOfInputStart + 0]))
|
||||
#define source_input_position_y() (*reinterpret_cast<uint16_t *>(&next_run[SourceVertexOffsetOfInputStart + 2]))
|
||||
#define source_input_position_x2() (*reinterpret_cast<uint16_t *>(&next_run[SourceVertexOffsetOfEnds + 0]))
|
||||
#define source_output_position_x1() (*reinterpret_cast<uint16_t *>(&next_run[SourceVertexOffsetOfOutputStart + 0]))
|
||||
#define source_output_position_y() (*reinterpret_cast<uint16_t *>(&next_run[SourceVertexOffsetOfOutputStart + 2]))
|
||||
#define source_output_position_x2() (*reinterpret_cast<uint16_t *>(&next_run[SourceVertexOffsetOfEnds + 2]))
|
||||
#define source_phase() next_run[SourceVertexOffsetOfPhaseTimeAndAmplitude + 0]
|
||||
#define source_amplitude() next_run[SourceVertexOffsetOfPhaseTimeAndAmplitude + 1]
|
||||
@ -217,6 +214,9 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo
|
||||
output_tex_y() = output_y;
|
||||
output_x2() = static_cast<uint16_t>(horizontal_flywheel_->get_current_output_position());
|
||||
}
|
||||
|
||||
// TODO: below I've assumed a one-to-one correspondance with output runs and input data; that's
|
||||
// obviously not completely sustainable. It's a latent bug.
|
||||
openGL_output_builder_.array_builder.flush(
|
||||
[=] (uint8_t *input_buffer, std::size_t input_size, uint8_t *output_buffer, std::size_t output_size) {
|
||||
openGL_output_builder_.texture_builder.flush(
|
||||
@ -264,15 +264,11 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo
|
||||
#undef output_position_y
|
||||
#undef output_tex_y
|
||||
|
||||
#undef source_input_position_x1
|
||||
#undef source_input_position_y
|
||||
#undef source_input_position_x2
|
||||
#undef source_output_position_x1
|
||||
#undef source_output_position_y
|
||||
#undef source_output_position_x2
|
||||
#undef source_phase
|
||||
#undef source_amplitude
|
||||
#undef source_phase_time
|
||||
|
||||
// MARK: - stream feeding methods
|
||||
|
||||
@ -384,8 +380,8 @@ void CRT::set_immediate_default_phase(float phase) {
|
||||
phase_numerator_ = static_cast<unsigned int>(phase * static_cast<float>(phase_denominator_));
|
||||
}
|
||||
|
||||
void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider) {
|
||||
openGL_output_builder_.texture_builder.reduce_previous_allocation_to(number_of_cycles / source_divider);
|
||||
void CRT::output_data(unsigned int number_of_cycles, unsigned int number_of_samples) {
|
||||
openGL_output_builder_.texture_builder.reduce_previous_allocation_to(number_of_samples);
|
||||
Scan scan;
|
||||
scan.type = Scan::Type::Data;
|
||||
scan.number_of_cycles = number_of_cycles;
|
||||
|
@ -180,17 +180,21 @@ class CRT {
|
||||
void output_level(unsigned int number_of_cycles);
|
||||
|
||||
/*! Declares that the caller has created a run of data via @c allocate_write_area and @c get_write_target_for_buffer
|
||||
that is at least @c number_of_cycles long, and that the first @c number_of_cycles/source_divider should be spread
|
||||
over that amount of time.
|
||||
that is at least @c number_of_samples long, and that the first @c number_of_samples should be spread
|
||||
over @c number_of_cycles.
|
||||
|
||||
@param number_of_cycles The amount of data to output.
|
||||
|
||||
@param source_divider A divider for source data; if the divider is 1 then one source pixel is output every cycle,
|
||||
if it is 2 then one source pixel covers two cycles; if it is n then one source pixel covers n cycles.
|
||||
@param number_of_samples The number of samples of input data to output.
|
||||
|
||||
@see @c allocate_write_area , @c get_write_target_for_buffer
|
||||
*/
|
||||
void output_data(unsigned int number_of_cycles, unsigned int source_divider);
|
||||
void output_data(unsigned int number_of_cycles, unsigned int number_of_samples);
|
||||
|
||||
/*! A shorthand form for output_data that assumes the number of cycles to output for is the same as the number of samples. */
|
||||
void output_data(unsigned int number_of_cycles) {
|
||||
output_data(number_of_cycles, number_of_cycles);
|
||||
}
|
||||
|
||||
/*! Outputs a colour burst.
|
||||
|
||||
@ -286,6 +290,22 @@ class CRT {
|
||||
});
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets a multiplier applied to iCoordinate values prior to their passing to the various sampling functions.
|
||||
This multiplier is applied outside of the interpolation loop, making for a more precise interpolation
|
||||
than if it were applied within the sampling function.
|
||||
|
||||
Idiomatically, this is likely to be the number of output pixels packed into each input sample where
|
||||
packing is in use.
|
||||
|
||||
The default value is 1.0.
|
||||
*/
|
||||
inline void set_integer_coordinate_multiplier(float multiplier) {
|
||||
enqueue_openGL_function([=] {
|
||||
openGL_output_builder_.set_integer_coordinate_multiplier(multiplier);
|
||||
});
|
||||
}
|
||||
|
||||
enum CompositeSourceType {
|
||||
/// The composite function provides continuous output.
|
||||
Continuous,
|
||||
@ -329,12 +349,12 @@ class CRT {
|
||||
output mode will be applied.
|
||||
|
||||
@param shader A GLSL fragent including a function with the signature
|
||||
`vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)` that evaluates to an RGB colour
|
||||
`vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 iCoordinate)` that evaluates to an RGB colour
|
||||
as a function of:
|
||||
|
||||
* `usampler2D sampler` representing the source buffer;
|
||||
* `vec2 coordinate` representing the source buffer location to sample from in the range [0, 1); and
|
||||
* `vec2 icoordinate` representing the source buffer location to sample from as a pixel count, for easier multiple-pixels-per-byte unpacking.
|
||||
* `vec2 iCoordinate` representing the source buffer location to sample from as a pixel count, for easier multiple-pixels-per-byte unpacking.
|
||||
*/
|
||||
inline void set_rgb_sampling_function(const std::string &shader) {
|
||||
enqueue_openGL_function([shader, this] {
|
||||
|
@ -514,4 +514,12 @@ void OpenGLOutputBuilder::set_timing_uniforms() {
|
||||
if(rgb_input_shader_program_) {
|
||||
rgb_input_shader_program_->set_width_scalers(1.0f, 1.0f);
|
||||
}
|
||||
set_integer_coordinate_multiplier(integer_coordinate_multiplier_);
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::set_integer_coordinate_multiplier(float multiplier) {
|
||||
integer_coordinate_multiplier_ = multiplier;
|
||||
if(composite_input_shader_program_) composite_input_shader_program_->set_integer_coordinate_multiplier(multiplier);
|
||||
if(svideo_input_shader_program_) svideo_input_shader_program_->set_integer_coordinate_multiplier(multiplier);
|
||||
if(rgb_input_shader_program_) rgb_input_shader_program_->set_integer_coordinate_multiplier(multiplier);
|
||||
}
|
||||
|
@ -104,6 +104,8 @@ class OpenGLOutputBuilder {
|
||||
float get_composite_output_width() const;
|
||||
void set_output_shader_width();
|
||||
|
||||
float integer_coordinate_multiplier_ = 1.0f;
|
||||
|
||||
public:
|
||||
// These two are protected by output_mutex_.
|
||||
TextureBuilder texture_builder;
|
||||
@ -158,6 +160,7 @@ class OpenGLOutputBuilder {
|
||||
void set_rgb_sampling_function(const std::string &);
|
||||
void set_video_signal(VideoSignal);
|
||||
void set_timing(unsigned int input_frequency, unsigned int cycles_per_line, unsigned int height_of_display, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider);
|
||||
void set_integer_coordinate_multiplier(float multiplier);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const std::s
|
||||
"uniform float inputVerticalOffset;"
|
||||
"uniform float outputVerticalOffset;"
|
||||
"uniform float textureHeightDivisor;"
|
||||
"uniform float iCoordinateMultiplier;"
|
||||
|
||||
"out vec3 phaseAndAmplitudeVarying;"
|
||||
"out vec2 inputPositionsVarying[11];"
|
||||
@ -76,8 +77,8 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const std::s
|
||||
|
||||
// keep iInputPositionVarying in whole source pixels, scale mappedInputPosition to the ordinary normalised range
|
||||
"vec2 textureSize = vec2(textureSize(texID, 0));"
|
||||
"iInputPositionVarying = extendedInputPosition;"
|
||||
"vec2 mappedInputPosition = extendedInputPosition / textureSize;" // + vec2(0.0, 0.5)
|
||||
"iInputPositionVarying = extendedInputPosition * iCoordinateMultiplier;"
|
||||
"vec2 mappedInputPosition = extendedInputPosition / textureSize;"
|
||||
|
||||
// setup input positions spaced as per the supplied offsets; these are for filtering where required
|
||||
"inputPositionsVarying[0] = mappedInputPosition - (vec2(5.0, 0.0) / textureSize);"
|
||||
@ -434,3 +435,7 @@ void IntermediateShader::set_is_double_height(bool is_double_height, float input
|
||||
set_uniform("inputVerticalOffset", input_offset);
|
||||
set_uniform("outputVerticalOffset", output_offset);
|
||||
}
|
||||
|
||||
void IntermediateShader::set_integer_coordinate_multiplier(float multiplier) {
|
||||
set_uniform("iCoordinateMultiplier", multiplier);
|
||||
}
|
||||
|
@ -135,6 +135,11 @@ public:
|
||||
*/
|
||||
void set_is_double_height(bool is_double_height, float input_offset = 0.0f, float output_offset = 0.0f);
|
||||
|
||||
/*!
|
||||
Sets the multiplier applied in the vertex shader to iCoordinates.
|
||||
*/
|
||||
void set_integer_coordinate_multiplier(float);
|
||||
|
||||
private:
|
||||
static std::unique_ptr<IntermediateShader> make_shader(const std::string &fragment_shader, bool use_usampler, bool input_is_inputPosition);
|
||||
};
|
||||
|
6
ROMImages/AppleII/readme.txt
Normal file
6
ROMImages/AppleII/readme.txt
Normal file
@ -0,0 +1,6 @@
|
||||
ROM files would ordinarily go here; they are copyright Apple so are not included.
|
||||
|
||||
Expected files:
|
||||
|
||||
apple2o.rom — a 12kb image of the original Apple II's ROMs.
|
||||
apple2-character.rom — a 2kb image of the Apple II+'s character ROM.
|
@ -14,18 +14,19 @@ namespace TargetPlatform {
|
||||
typedef int IntType;
|
||||
enum Type: IntType {
|
||||
AmstradCPC = 1 << 1,
|
||||
Atari2600 = 1 << 2,
|
||||
AcornAtom = 1 << 3,
|
||||
AcornElectron = 1 << 4,
|
||||
BBCMaster = 1 << 5,
|
||||
BBCModelA = 1 << 6,
|
||||
BBCModelB = 1 << 7,
|
||||
ColecoVision = 1 << 8,
|
||||
Commodore = 1 << 9,
|
||||
MSX = 1 << 10,
|
||||
Oric = 1 << 11,
|
||||
ZX80 = 1 << 12,
|
||||
ZX81 = 1 << 13,
|
||||
AppleII = 1 << 2,
|
||||
Atari2600 = 1 << 3,
|
||||
AcornAtom = 1 << 4,
|
||||
AcornElectron = 1 << 5,
|
||||
BBCMaster = 1 << 6,
|
||||
BBCModelA = 1 << 7,
|
||||
BBCModelB = 1 << 8,
|
||||
ColecoVision = 1 << 9,
|
||||
Commodore = 1 << 10,
|
||||
MSX = 1 << 11,
|
||||
Oric = 1 << 12,
|
||||
ZX80 = 1 << 13,
|
||||
ZX81 = 1 << 14,
|
||||
|
||||
Acorn = AcornAtom | AcornElectron | BBCMaster | BBCModelA | BBCModelB,
|
||||
ZX8081 = ZX80 | ZX81,
|
||||
|
Loading…
x
Reference in New Issue
Block a user