1
0
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:
Thomas Harte 2018-04-19 22:31:02 -04:00 committed by GitHub
commit 7e8e3fdd39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
69 changed files with 1115 additions and 206 deletions

View File

@ -13,6 +13,7 @@ namespace Analyser {
enum class Machine {
AmstradCPC,
AppleII,
Atari2600,
ColecoVision,
Electron,

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
};
}

View File

@ -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;
}

View File

@ -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 */

View File

@ -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;
};
}

View 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();
}

View 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 */

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
};
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
};
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
};
}

View File

@ -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) {

View File

@ -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.

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
};
}

View File

@ -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;

View File

@ -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;
}
}

View 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_;
}

View 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 */

View File

@ -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);

View File

@ -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.

View File

@ -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);

View 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() {}

View 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
View 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
View 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 */

View File

@ -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;
}

View File

@ -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) {

View File

@ -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_;

View File

@ -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);
}

View File

@ -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() {}
};
/*!

View File

@ -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;

View File

@ -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) {

View File

@ -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)

View File

@ -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";

View File

@ -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,

View File

@ -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;

View File

@ -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();

View File

@ -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_;

View File

@ -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;

View File

@ -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);

View File

@ -10,6 +10,6 @@
@interface CSStaticAnalyser (ResultVector)
- (std::vector<std::unique_ptr<Analyser::Static::Target>> &)targets;
- (Analyser::Static::TargetList &)targets;
@end

View File

@ -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;

View File

@ -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;
}

View File

@ -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 -&gt; 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"/>

View File

@ -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)

View File

@ -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];

View File

@ -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];

View File

@ -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;
}

View File

@ -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;

View File

@ -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] {

View File

@ -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);
}

View File

@ -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);
};
}

View File

@ -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);
}

View File

@ -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);
};

View 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.

View File

@ -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,