1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-27 01:31:42 +00:00

Merge pull request #417 from TomHarte/DiskII

Adds attempt at Disk II emulation.
This commit is contained in:
Thomas Harte 2018-05-01 20:34:40 -04:00 committed by GitHub
commit ac4948c4b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
88 changed files with 1510 additions and 222 deletions

View File

@ -64,14 +64,14 @@ static void InspectCatalogue(
std::vector<const Storage::Disk::CPM::File *> candidate_files;
candidate_files.reserve(catalogue.files.size());
for(auto &file : catalogue.files) {
for(const auto &file : catalogue.files) {
candidate_files.push_back(&file);
}
// Remove all files with untypable characters.
candidate_files.erase(
std::remove_if(candidate_files.begin(), candidate_files.end(), [](const Storage::Disk::CPM::File *file) {
for(auto c : file->name + file->type) {
for(const auto c : file->name + file->type) {
if(c < 32) return true;
}
return false;
@ -80,7 +80,7 @@ static void InspectCatalogue(
// If that leaves a mix of 'system' (i.e. hidden) and non-system files, remove the system files.
bool are_all_system = true;
for(auto file : candidate_files) {
for(const auto &file : candidate_files) {
if(!file->system) {
are_all_system = false;
break;
@ -137,13 +137,13 @@ static void InspectCatalogue(
std::map<std::string, int> name_counts;
std::map<std::string, std::size_t> indices_by_name;
std::size_t index = 0;
for(auto file : candidate_files) {
for(const auto &file : candidate_files) {
name_counts[file->name]++;
indices_by_name[file->name] = index;
index++;
}
if(name_counts.size() == 2) {
for(auto &pair : name_counts) {
for(const auto &pair : name_counts) {
if(pair.second == 1) {
target->loading_command = RunCommandFor(*candidate_files[indices_by_name[pair.first]]);
return;
@ -214,7 +214,7 @@ Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Medi
system_format.catalogue_allocation_bitmap = 0xc000;
system_format.reserved_tracks = 2;
for(const auto &disk: media.disks) {
for(auto &disk: media.disks) {
// Check for an ordinary catalogue.
std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(disk, data_format);
if(data_catalogue) {

View File

@ -7,7 +7,16 @@
//
#include "StaticAnalyser.hpp"
#include "Target.hpp"
Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
return TargetList();
auto target = std::unique_ptr<Target>(new Target);
target->machine = Machine::AppleII;
target->media = media;
target->has_disk = !target->media.disks.empty();
TargetList targets;
targets.push_back(std::move(target));
return targets;
}

View File

@ -0,0 +1,26 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 21/04/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Target_h
#define Target_h
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace AppleII {
struct Target: public ::Analyser::Static::Target {
bool has_disk; // TODO: Disk II versus IWM?
};
}
}
}
#endif /* Target_h */

View File

@ -272,7 +272,7 @@ Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &medi
std::unique_ptr<Target> target(new Target);
// Check tapes for loadable files.
for(const auto &tape : media.tapes) {
for(auto &tape : media.tapes) {
std::vector<File> files_on_tape = GetFiles(tape);
if(!files_on_tape.empty()) {
switch(files_on_tape.front().type) {

View File

@ -23,15 +23,15 @@ using namespace Analyser::Static::Oric;
static int Score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) {
int score = 0;
for(auto address : disassembly.outward_calls) score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
for(auto address : disassembly.external_stores) score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
for(auto address : disassembly.external_loads) score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
for(const auto address : disassembly.outward_calls) score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
for(const auto address : disassembly.external_stores) score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
for(const auto address : disassembly.external_loads) score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
return score;
}
static int Basic10Score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
std::set<uint16_t> rom_functions = {
const std::set<uint16_t> rom_functions = {
0x0228, 0x022b,
0xc3ca, 0xc3f8, 0xc448, 0xc47c, 0xc4b5, 0xc4e3, 0xc4e0, 0xc524, 0xc56f, 0xc5a2, 0xc5f8, 0xc60a, 0xc6a5, 0xc6de, 0xc719, 0xc738,
0xc773, 0xc824, 0xc832, 0xc841, 0xc8c1, 0xc8fe, 0xc91f, 0xc93f, 0xc941, 0xc91e, 0xc98b, 0xc996, 0xc9b3, 0xc9e0, 0xca0a, 0xca1c,
@ -47,7 +47,7 @@ static int Basic10Score(const Analyser::Static::MOS6502::Disassembly &disassembl
0xf43c, 0xf4ef, 0xf523, 0xf561, 0xf535, 0xf57b, 0xf5d3, 0xf71a, 0xf73f, 0xf7e4, 0xf7e0, 0xf82f, 0xf88f, 0xf8af, 0xf8b5, 0xf920,
0xf967, 0xf960, 0xf9c9, 0xfa14, 0xfa85, 0xfa9b, 0xfab1, 0xfac7, 0xfafa, 0xfb10, 0xfb26, 0xfbb6, 0xfbfe
};
std::set<uint16_t> variable_locations = {
const std::set<uint16_t> variable_locations = {
0x0228, 0x0229, 0x022a, 0x022b, 0x022c, 0x022d, 0x0230
};
@ -55,7 +55,7 @@ static int Basic10Score(const Analyser::Static::MOS6502::Disassembly &disassembl
}
static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
std::set<uint16_t> rom_functions = {
const std::set<uint16_t> rom_functions = {
0x0238, 0x023b, 0x023e, 0x0241, 0x0244, 0x0247,
0xc3c6, 0xc3f4, 0xc444, 0xc47c, 0xc4a8, 0xc4d3, 0xc4e0, 0xc524, 0xc55f, 0xc592, 0xc5e8, 0xc5fa, 0xc692, 0xc6b3, 0xc6ee, 0xc70d,
0xc748, 0xc7fd, 0xc809, 0xc816, 0xc82f, 0xc855, 0xc8c1, 0xc915, 0xc952, 0xc971, 0xc973, 0xc9a0, 0xc9bd, 0xc9c8, 0xc9e5, 0xca12,
@ -72,7 +72,7 @@ static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembl
0xf88f, 0xf8af, 0xf8b5, 0xf920, 0xf967, 0xf9aa, 0xf9c9, 0xfa14, 0xfa9f, 0xfab5, 0xfacb, 0xfae1, 0xfb14, 0xfb2a, 0xfb40, 0xfbd0,
0xfc18
};
std::set<uint16_t> variable_locations = {
const std::set<uint16_t> variable_locations = {
0x0244, 0x0245, 0x0246, 0x0247, 0x0248, 0x0249, 0x024a, 0x024b, 0x024c
};
@ -112,7 +112,7 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med
std::vector<File> tape_files = GetFiles(tape);
tape->reset();
if(tape_files.size()) {
for(auto file : tape_files) {
for(const auto &file : tape_files) {
if(file.data_type == File::MachineCode) {
std::vector<uint16_t> entry_points = {file.starting_address};
Analyser::Static::MOS6502::Disassembly disassembly =
@ -131,7 +131,7 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med
if(!media.disks.empty()) {
// Only the Microdisc is emulated right now, so accept only disks that it can boot from.
for(const auto &disk: media.disks) {
for(auto &disk: media.disks) {
Storage::Encodings::MFM::Parser parser(true, disk);
if(IsMicrodisc(parser)) {
target->has_microdrive = true;

View File

@ -30,14 +30,17 @@
// Disks
#include "../../Storage/Disk/DiskImage/Formats/AcornADF.hpp"
#include "../../Storage/Disk/DiskImage/Formats/AppleDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/CPCDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/D64.hpp"
#include "../../Storage/Disk/DiskImage/Formats/G64.hpp"
#include "../../Storage/Disk/DiskImage/Formats/DMK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/HFE.hpp"
#include "../../Storage/Disk/DiskImage/Formats/MSXDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/NIB.hpp"
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
// Tapes
#include "../../Storage/Tape/Formats/CAS.hpp"
@ -91,8 +94,10 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
Format("do", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::AppleII) // DO
Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC) // DSK (Amstrad CPC)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::AppleII) // DSK (Apple)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX) // DSK (MSX)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore) // G64
@ -101,8 +106,10 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
Disk::DiskImageHolder<Storage::Disk::HFE>,
TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric)
// HFE (TODO: switch to AllDisk once the MSX stops being so greedy)
Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::AppleII) // NIB
Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P
Format("po", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::AppleII) // PO
Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P81
// PRG
@ -127,6 +134,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX
Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081) // TZX
Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape)
Format("woz", result.disks, Disk::DiskImageHolder<Storage::Disk::WOZ>, TargetPlatform::AppleII) // WOZ
#undef Format
#undef Insert
@ -166,7 +174,7 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
#undef Append
// Reset any tapes to their initial position
for(auto &target : targets) {
for(const auto &target : targets) {
for(auto &tape : target->media.tapes) {
tape->reset();
}

View File

@ -30,7 +30,7 @@ class Sleeper {
class SleepObserver {
public:
/// Called to inform an observer that the component @c component has either gone to sleep or become awake.
virtual void set_component_is_sleeping(void *component, bool is_sleeping) = 0;
virtual void set_component_is_sleeping(Sleeper *component, bool is_sleeping) = 0;
};
/// Registers @c observer as the new sleep observer;

View File

@ -0,0 +1,186 @@
//
// DiskII.cpp
// Clock Signal
//
// Created by Thomas Harte on 20/04/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "DiskII.hpp"
#include <cstdio>
using namespace Apple;
namespace {
const uint8_t input_command = 0x1; // i.e. Q6
const uint8_t input_mode = 0x2; // i.e. Q7
const uint8_t input_flux = 0x4;
}
DiskII::DiskII() :
inputs_(input_command),
drives_{{2045454, 300, 1}, {2045454, 300, 1}}
{
drives_[0].set_sleep_observer(this);
drives_[1].set_sleep_observer(this);
}
void DiskII::set_control(Control control, bool on) {
int previous_stepper_mask = stepper_mask_;
switch(control) {
case Control::P0: stepper_mask_ = (stepper_mask_ & 0xe) | (on ? 0x1 : 0x0); break;
case Control::P1: stepper_mask_ = (stepper_mask_ & 0xd) | (on ? 0x2 : 0x0); break;
case Control::P2: stepper_mask_ = (stepper_mask_ & 0xb) | (on ? 0x4 : 0x0); break;
case Control::P3: stepper_mask_ = (stepper_mask_ & 0x7) | (on ? 0x8 : 0x0); break;
case Control::Motor:
// TODO: does the motor control trigger both motors at once?
drives_[0].set_motor_on(on);
drives_[1].set_motor_on(on);
break;
}
// printf("%0x: Set control %d %s\n", stepper_mask_, control, on ? "on" : "off");
// If the stepper magnet selections have changed, and any is on, see how
// that moves the head.
if(previous_stepper_mask ^ stepper_mask_ && stepper_mask_) {
// Convert from a representation of bits set to the centre of pull.
int direction = 0;
if(stepper_mask_&1) direction += (((stepper_position_ - 0) + 4)&7) - 4;
if(stepper_mask_&2) direction += (((stepper_position_ - 2) + 4)&7) - 4;
if(stepper_mask_&4) direction += (((stepper_position_ - 4) + 4)&7) - 4;
if(stepper_mask_&8) direction += (((stepper_position_ - 6) + 4)&7) - 4;
const int bits_set = (stepper_mask_&1) + ((stepper_mask_ >> 1)&1) + ((stepper_mask_ >> 2)&1) + ((stepper_mask_ >> 3)&1);
direction /= bits_set;
// Compare to the stepper position to decide whether that pulls in the current cog notch,
// or grabs a later one.
drives_[active_drive_].step(-direction);
stepper_position_ = (stepper_position_ - direction + 8) & 7;
}
}
void DiskII::set_mode(Mode mode) {
// printf("Set mode %d\n", mode);
inputs_ = (inputs_ & ~input_mode) | ((mode == Mode::Write) ? input_mode : 0);
set_controller_can_sleep();
}
void DiskII::select_drive(int drive) {
// printf("Select drive %d\n", drive);
active_drive_ = drive & 1;
drives_[active_drive_].set_event_delegate(this);
drives_[active_drive_^1].set_event_delegate(nullptr);
}
void DiskII::set_data_register(uint8_t value) {
// printf("Set data register (?)\n");
inputs_ |= input_command;
data_register_ = value;
set_controller_can_sleep();
}
uint8_t DiskII::get_shift_register() {
// if(shift_register_ & 0x80) printf("[%02x] ", shift_register_);
inputs_ &= ~input_command;
set_controller_can_sleep();
return shift_register_;
}
void DiskII::run_for(const Cycles cycles) {
/*
... address the P6 ROM with an index byte built up as:
+-------+-------+-------+-------+-------+-------+-------+-------+
| STATE | STATE | STATE | PULSE | Q7 | Q6 | SR | STATE |
| bit 0 | bit 2 | bit 3 | | | | MSB | bit 1 |
+-------+-------+-------+-------+-------+-------+-------+-------+
7 6 5 4 3 2 1 0
...
The bytes in the P6 ROM has the high four bits reversed compared to the BAPD charts, so you will have to reverse them after fetching the byte.
*/
if(is_sleeping()) return;
int integer_cycles = cycles.as_int();
if(!controller_can_sleep_) {
while(integer_cycles--) {
const int address =
(inputs_ << 2) |
((shift_register_&0x80) >> 6) |
((state_&0x2) >> 1) |
((state_&0x1) << 7) |
((state_&0x4) << 4) |
((state_&0x8) << 2);
inputs_ |= input_flux;
const uint8_t update = state_machine_[static_cast<std::size_t>(address)];
state_ = update >> 4;
state_ = ((state_ & 0x8) ? 0x1 : 0x0) | ((state_ & 0x4) ? 0x2 : 0x0) | ((state_ & 0x2) ? 0x4 : 0x0) | ((state_ & 0x1) ? 0x8 : 0x0);
uint8_t command = update & 0xf;
switch(command) {
case 0x0: shift_register_ = 0; break; // clear
case 0x9: shift_register_ = static_cast<uint8_t>(shift_register_ << 1); break; // shift left, bringing in a zero
case 0xd: shift_register_ = static_cast<uint8_t>((shift_register_ << 1) | 1); break; // shift left, bringing in a one
case 0xb: shift_register_ = data_register_; break; // load
case 0xa:
shift_register_ = (shift_register_ >> 1) | (is_write_protected() ? 0x80 : 0x00);
break; // shift right, bringing in write protected status
default: break;
}
// TODO: surely there's a less heavyweight solution than this?
if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(1));
if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(1));
}
} else {
if(!drive_is_sleeping_[0]) drives_[0].run_for(cycles);
if(!drive_is_sleeping_[1]) drives_[1].run_for(cycles);
}
set_controller_can_sleep();
}
void DiskII::set_controller_can_sleep() {
// Permit the controller to sleep if it's in sense write protect mode, and the shift register
// has already filled with the result of shifting eight times.
controller_can_sleep_ =
(inputs_ == (input_command | input_flux)) &&
(shift_register_ == (is_write_protected() ? 0xff : 0x00));
if(is_sleeping()) update_sleep_observer();
}
bool DiskII::is_write_protected() {
return true;
}
void DiskII::set_state_machine(const std::vector<uint8_t> &state_machine) {
state_machine_ = state_machine;
// TODO: shuffle ordering here?
}
void DiskII::set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive) {
drives_[drive].set_disk(disk);
}
void DiskII::process_event(const Storage::Disk::Track::Event &event) {
if(event.type == Storage::Disk::Track::Event::FluxTransition) {
inputs_ &= ~input_flux;
set_controller_can_sleep();
}
}
void DiskII::set_component_is_sleeping(Sleeper *component, bool is_sleeping) {
drive_is_sleeping_[0] = drives_[0].is_sleeping();
drive_is_sleeping_[1] = drives_[1].is_sleeping();
update_sleep_observer();
}
bool DiskII::is_sleeping() {
return controller_can_sleep_ && drive_is_sleeping_[0] && drive_is_sleeping_[1];
}

View File

@ -0,0 +1,76 @@
//
// DiskII.hpp
// Clock Signal
//
// Created by Thomas Harte on 20/04/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef DiskII_hpp
#define DiskII_hpp
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/Sleeper.hpp"
#include "../../Storage/Disk/Disk.hpp"
#include "../../Storage/Disk/Drive.hpp"
#include <cstdint>
#include <vector>
namespace Apple {
/*!
Provides an emulation of the Apple Disk II.
*/
class DiskII:
public Storage::Disk::Drive::EventDelegate,
public Sleeper::SleepObserver,
public Sleeper {
public:
DiskII();
enum class Control {
P0, P1, P2, P3,
Motor,
};
enum class Mode {
Read, Write
};
void set_control(Control control, bool on);
void set_mode(Mode mode);
void select_drive(int drive);
void set_data_register(uint8_t value);
uint8_t get_shift_register();
void run_for(const Cycles cycles);
void set_state_machine(const std::vector<uint8_t> &);
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
bool is_sleeping() override;
private:
void process_event(const Storage::Disk::Track::Event &event) override;
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override;
uint8_t state_ = 0;
uint8_t inputs_ = 0;
uint8_t shift_register_ = 0;
uint8_t data_register_ = 0;
int stepper_mask_ = 0;
int stepper_position_ = 0;
bool is_write_protected();
std::vector<uint8_t> state_machine_;
Storage::Disk::Drive drives_[2];
bool drive_is_sleeping_[2];
bool controller_can_sleep_ = false;
int active_drive_ = 0;
void set_controller_can_sleep();
};
}
#endif /* DiskII_hpp */

View File

@ -98,7 +98,7 @@ void DeferringAsyncTaskQueue::perform() {
std::shared_ptr<std::list<std::function<void(void)>>> deferred_tasks = deferred_tasks_;
deferred_tasks_.reset();
enqueue([deferred_tasks] {
for(auto &function : *deferred_tasks) {
for(const auto &function : *deferred_tasks) {
function();
}
});

View File

@ -941,7 +941,7 @@ class ConcreteMachine:
}
// Obtains the system ROMs.
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 {
bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override {
auto roms = roms_with_names(
"AmstradCPC",
{
@ -961,7 +961,7 @@ class ConcreteMachine:
return true;
}
void set_component_is_sleeping(void *component, bool is_sleeping) override final {
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override final {
fdc_is_sleeping_ = fdc_.is_sleeping();
tape_player_is_sleeping_ = tape_player_.is_sleeping();
}

View File

@ -8,6 +8,7 @@
#include "AppleII.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../KeyboardMachine.hpp"
#include "../Utility/MemoryFuzzer.hpp"
@ -17,14 +18,19 @@
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "Card.hpp"
#include "DiskIICard.hpp"
#include "Video.hpp"
#include "../../Analyser/Static/AppleII/Target.hpp"
#include <memory>
namespace {
class ConcreteMachine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine,
public CPU::MOS6502::BusHandler,
public Inputs::Keyboard,
@ -55,6 +61,13 @@ class ConcreteMachine:
void update_audio() {
speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide(Cycles(audio_divider)));
}
void update_cards() {
for(int c = 0; c < 7; ++c) {
if(cards_[c]) cards_[c]->run_for(cycles_since_card_update_, stretched_cycles_since_card_update_);
}
cycles_since_card_update_ = 0;
stretched_cycles_since_card_update_ = 0;
}
uint8_t ram_[48*1024];
std::vector<uint8_t> rom_;
@ -67,6 +80,11 @@ class ConcreteMachine:
Outputs::Speaker::LowpassSpeaker<Audio::Toggle> speaker_;
Cycles cycles_since_audio_update_;
ROMMachine::ROMFetcher rom_fetcher_;
std::unique_ptr<AppleII::Card> cards_[7];
Cycles cycles_since_card_update_;
int stretched_cycles_since_card_update_ = 0;
public:
ConcreteMachine():
m6502_(*this),
@ -108,6 +126,7 @@ class ConcreteMachine:
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
++ cycles_since_video_update_;
++ cycles_since_card_update_;
cycles_since_audio_update_ += Cycles(7);
switch(address) {
@ -121,7 +140,6 @@ class ConcreteMachine:
switch(address) {
default:
// printf("Unknown access to %04x\n", address);
*value = 0xff;
break;
case 0xc000:
*value = keyboard_input_;
@ -158,6 +176,28 @@ class ConcreteMachine:
break;
}
if(address >= 0xc100 && address < 0xc800) {
/*
Decode the area conventionally used by cards for ROMs:
0xCn00 0xCnff: card n.
*/
const int card_number = (address - 0xc100) >> 8;
if(cards_[card_number]) {
update_cards();
cards_[card_number]->perform_bus_operation(operation, address & 0xff, value);
}
} else if(address >= 0xc090 && address < 0xc100) {
/*
Decode the area conventionally used by cards for registers:
C0n0--C0nF: card n - 8.
*/
const int card_number = (address - 0xc090) >> 4;
if(cards_[card_number]) {
update_cards();
cards_[card_number]->perform_bus_operation(operation, 0x100 | (address&0xf), value);
}
}
// 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
@ -167,6 +207,7 @@ class ConcreteMachine:
cycles_into_current_line_ = (cycles_into_current_line_ + 1) % 65;
if(!cycles_into_current_line_) {
++ cycles_since_audio_update_;
++ stretched_cycles_since_card_update_;
}
return Cycles(1);
@ -175,10 +216,11 @@ class ConcreteMachine:
void flush() {
update_video();
update_audio();
update_cards();
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 {
bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override {
auto roms = roms_with_names(
"AppleII",
{
@ -188,10 +230,15 @@ class ConcreteMachine:
if(!roms[0] || !roms[1]) return false;
rom_ = std::move(*roms[0]);
rom_start_address_ = static_cast<uint16_t>(0x10000 - rom_.size());
if(rom_.size() > 12*1024) {
rom_.erase(rom_.begin(), rom_.begin() + static_cast<off_t>(rom_.size()) - 12*1024);
}
rom_start_address_ = 0xd000;//static_cast<uint16_t>(0x10000 - rom_.size());
character_rom_ = std::move(*roms[1]);
rom_fetcher_ = roms_with_names;
return true;
}
@ -200,6 +247,11 @@ class ConcreteMachine:
}
void set_key_pressed(Key key, char value, bool is_pressed) override {
if(key == Key::F12) {
m6502_.set_reset_line(is_pressed);
return;
}
if(is_pressed) {
// If no ASCII value is supplied, look for a few special cases.
if(!value) {
@ -218,6 +270,23 @@ class ConcreteMachine:
Inputs::Keyboard &get_keyboard() override {
return *this;
}
// MARK: ConfigurationTarget
void configure_as_target(const Analyser::Static::Target *target) override {
auto *const apple_target = dynamic_cast<const Analyser::Static::AppleII::Target *>(target);
if(apple_target->has_disk) {
cards_[5].reset(new AppleII::DiskIICard(rom_fetcher_, true));
}
insert_media(apple_target->media);
}
bool insert_media(const Analyser::Static::Media &media) override {
if(!media.disks.empty() && cards_[5]) {
dynamic_cast<AppleII::DiskIICard *>(cards_[5].get())->set_disk(media.disks[0], 0);
}
return true;
}
};
}

28
Machines/AppleII/Card.hpp Normal file
View File

@ -0,0 +1,28 @@
//
// Card.h
// Clock Signal
//
// Created by Thomas Harte on 23/04/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Card_h
#define Card_h
#include "../../Processors/6502/6502.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
namespace AppleII {
class Card {
public:
/*! Advances time by @c cycles, of which @c stretches were stretched. */
virtual void run_for(Cycles half_cycles, int stretches) {}
/*! Performs a bus operation; the card is implicitly selected. */
virtual void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) = 0;
};
}
#endif /* Card_h */

View File

@ -0,0 +1,69 @@
//
// DiskII.cpp
// Clock Signal
//
// Created by Thomas Harte on 23/04/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "DiskIICard.hpp"
using namespace AppleII;
DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) {
auto roms = rom_fetcher(
"DiskII",
{
is_16_sector ? "boot-16.rom" : "boot-13.rom",
is_16_sector ? "state-machine-16.rom" : "state-machine-13.rom"
});
boot_ = std::move(*roms[0]);
diskii_.set_state_machine(*roms[1]);
}
void DiskIICard::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
if(isReadOperation(operation) && address < 0x100) {
*value &= boot_[address];
} else {
using Control = Apple::DiskII::Control;
using Mode = Apple::DiskII::Mode;
switch(address & 0xf) {
case 0x0: diskii_.set_control(Control::P0, false); break;
case 0x1: diskii_.set_control(Control::P0, true); break;
case 0x2: diskii_.set_control(Control::P1, false); break;
case 0x3: diskii_.set_control(Control::P1, true); break;
case 0x4: diskii_.set_control(Control::P2, false); break;
case 0x5: diskii_.set_control(Control::P2, true); break;
case 0x6: diskii_.set_control(Control::P3, false); break;
case 0x7: diskii_.set_control(Control::P3, true); break;
case 0x8: diskii_.set_control(Control::Motor, false); break;
case 0x9: diskii_.set_control(Control::Motor, true); break;
case 0xa: diskii_.select_drive(0); break;
case 0xb: diskii_.select_drive(1); break;
case 0xc: {
/* shift register? */
const uint8_t shift_value = diskii_.get_shift_register();
if(isReadOperation(operation))
*value = shift_value;
} break;
case 0xd:
/* data register? */
diskii_.set_data_register(*value);
break;
case 0xe: diskii_.set_mode(Mode::Read); break;
case 0xf: diskii_.set_mode(Mode::Write); break;
}
}
}
void DiskIICard::run_for(Cycles cycles, int stretches) {
diskii_.run_for(Cycles(cycles.as_int() * 2));
}
void DiskIICard::set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive) {
diskii_.set_disk(disk, drive);
}

View File

@ -0,0 +1,38 @@
//
// DiskII.hpp
// Clock Signal
//
// Created by Thomas Harte on 23/04/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef DiskIICard_hpp
#define DiskIICard_hpp
#include "Card.hpp"
#include "../ROMMachine.hpp"
#include "../../Components/DiskII/DiskII.hpp"
#include "../../Storage/Disk/Disk.hpp"
#include <cstdint>
#include <memory>
#include <vector>
namespace AppleII {
class DiskIICard: public Card {
public:
DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector);
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) override;
void run_for(Cycles cycles, int stretches) override;
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
private:
std::vector<uint8_t> boot_;
Apple::DiskII diskii_;
};
}
#endif /* DiskIICard_hpp */

View File

@ -103,6 +103,7 @@ template <class BusHandler> class Video: public VideoBase {
if(row_ < 192) {
if(!column_) {
pixel_pointer_ = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(80, 2));
graphics_carry_ = 0;
}
const int pixel_end = std::min(40, ending_column);
@ -110,7 +111,7 @@ template <class BusHandler> class Video: public VideoBase {
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 uint16_t graphics_address = static_cast<uint16_t>(((video_page_+1) * 0x2000) + row_address + ((pixel_row&7) << 10));
const int row_shift = (row_&4);
GraphicsMode pixel_mode = (!mixed_mode_ || row_ < 160) ? line_mode : GraphicsMode::Text;
@ -143,9 +144,9 @@ template <class BusHandler> class Video: public VideoBase {
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;
reinterpret_cast<uint8_t *>(&pixel_pointer_[c])[0] |= graphics_carry_;
}
graphics_carry_ = pixel_pointer_[c] & 1;
graphics_carry_ = (graphic >> 6) & 1;
}
break;
}

View File

@ -183,7 +183,7 @@ class ConcreteMachine:
}
// Obtains the system ROMs.
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 {
bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override {
auto roms = roms_with_names(
"ColecoVision",
{ "coleco.rom" });

View File

@ -31,7 +31,7 @@ class Machine: public MachineBase, public ROMMachine::Machine {
/*!
Sets the source for this drive's ROM image.
*/
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);
bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names);
/*!
Sets the serial bus to which this drive should attach itself.

View File

@ -82,7 +82,7 @@ Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation,
return Cycles(1);
}
bool Machine::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) {
bool Machine::set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) {
std::string rom_name;
switch(personality_) {
case Personality::C1540: rom_name = "1540.bin"; break;

View File

@ -336,7 +336,7 @@ class ConcreteMachine:
}
// Obtains the system ROMs.
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 {
bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override {
rom_fetcher_ = roms_with_names;
auto roms = roms_with_names(
@ -499,7 +499,7 @@ class ConcreteMachine:
Range(0x0000, 0x0400),
Range(0x1000, 0x2000),
}};
for(auto &video_range : video_ranges) {
for(const auto &video_range : video_ranges) {
for(auto addr = video_range.start; addr < video_range.end; addr += 0x400) {
auto destination_address = (addr & 0x1fff) | (((addr & 0x8000) >> 2) ^ 0x2000);
if(processor_read_memory_map_[addr >> 10]) {
@ -747,7 +747,7 @@ class ConcreteMachine:
return selection_set;
}
void set_component_is_sleeping(void *component, bool is_sleeping) override {
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override {
tape_is_sleeping_ = is_sleeping;
set_use_fast_tape();
}

View File

@ -90,7 +90,7 @@ class ConcreteMachine:
}
// Obtains the system ROMs.
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 {
bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override {
auto roms = roms_with_names(
"Electron",
{

View File

@ -464,7 +464,7 @@ class ConcreteMachine:
}
// Obtains the system ROMs.
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 {
bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override {
auto roms = roms_with_names(
"MSX",
{
@ -551,7 +551,7 @@ class ConcreteMachine:
}
// MARK: - Sleeper
void set_component_is_sleeping(void *component, bool is_sleeping) override {
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override {
tape_player_is_sleeping_ = tape_player_.is_sleeping();
set_use_fast_tape();
}

View File

@ -220,7 +220,7 @@ class ConcreteMachine:
}
// Obtains the system ROMs.
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 {
bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override {
auto roms = roms_with_names(
"Oric",
{
@ -302,7 +302,7 @@ class ConcreteMachine:
}
int drive_index = 0;
for(auto disk : media.disks) {
for(auto &disk : media.disks) {
if(drive_index < 4) microdisc_.set_disk(disk, drive_index);
drive_index++;
}

View File

@ -11,6 +11,7 @@
#include <functional>
#include <memory>
#include <string>
#include <vector>
namespace ROMMachine {

View File

@ -346,7 +346,7 @@ template<bool is_zx81> class ConcreteMachine:
}
// Obtains the system ROMs.
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 {
bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override {
const auto roms = roms_with_names(
"ZX8081",
{

View File

@ -8,6 +8,8 @@
/* Begin PBXBuildFile section */
4B01A6881F22F0DB001FD6E3 /* Z80MemptrTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */; };
4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; };
4B0333B02094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; };
4B049CDD1DA3C82F00322067 /* BCDTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B049CDC1DA3C82F00322067 /* BCDTest.swift */; };
4B055A7A1FAE78A00060FFFF /* SDL2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B055A771FAE78210060FFFF /* SDL2.framework */; };
4B055A7E1FAE84AA0060FFFF /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B055A7C1FAE84A50060FFFF /* main.cpp */; };
@ -120,6 +122,8 @@
4B0E04FA1FC9FA3100F43484 /* 9918.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04F91FC9FA3100F43484 /* 9918.cpp */; };
4B0E04FB1FC9FA3100F43484 /* 9918.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04F91FC9FA3100F43484 /* 9918.cpp */; };
4B0E61071FF34737002A9DBD /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E61051FF34737002A9DBD /* MSX.cpp */; };
4B0F94FE208C1A1600FE41D9 /* NIB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */; };
4B0F94FF208C1A1600FE41D9 /* NIB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */; };
4B121F951E05E66800BFDA12 /* PCMPatchedTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */; };
4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */; };
4B12C0ED1FCFA98D005BFD93 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */; };
@ -157,6 +161,8 @@
4B2C45421E3C3896002A2389 /* cartridge.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B2C45411E3C3896002A2389 /* cartridge.png */; };
4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; };
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D9B1C3A070400138695 /* Electron.cpp */; };
4B302184208A550100773308 /* DiskII.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B302183208A550100773308 /* DiskII.cpp */; };
4B302185208A550100773308 /* DiskII.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B302183208A550100773308 /* DiskII.cpp */; };
4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B30512B1D989E2200B4FED8 /* Drive.cpp */; };
4B3051301D98ACC600B4FED8 /* Plus3.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B30512E1D98ACC600B4FED8 /* Plus3.cpp */; };
4B322E011F5A2990004EB04C /* Z80AllRAM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B322DFD1F5A2981004EB04C /* Z80AllRAM.cpp */; };
@ -218,6 +224,8 @@
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; };
4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; };
4B6A4C991F58F09E00E3F787 /* 6502Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */; };
4B6ED2F0208E2F8A0047B343 /* WOZ.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6ED2EE208E2F8A0047B343 /* WOZ.cpp */; };
4B6ED2F1208E2F8A0047B343 /* WOZ.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6ED2EE208E2F8A0047B343 /* WOZ.cpp */; };
4B7136861F78724F008B8ED9 /* Encoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136841F78724F008B8ED9 /* Encoder.cpp */; };
4B7136891F78725F008B8ED9 /* Shifter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136871F78725F008B8ED9 /* Shifter.cpp */; };
4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368C1F788112008B8ED9 /* Parser.cpp */; };
@ -579,6 +587,8 @@
4BB299F81B587D8400A49093 /* txsn in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298EC1B587D8400A49093 /* txsn */; };
4BB299F91B587D8400A49093 /* tyan in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298ED1B587D8400A49093 /* tyan */; };
4BB2A9AF1E13367E001A5C23 /* CRCTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */; };
4BB2CB2A208BDDCF00FD192E /* AppleGCR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB2CB28208BDDCF00FD192E /* AppleGCR.cpp */; };
4BB2CB2B208BDDCF00FD192E /* AppleGCR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB2CB28208BDDCF00FD192E /* AppleGCR.cpp */; };
4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */; };
4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */; };
4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EA11B587A5100552FC2 /* AppDelegate.swift */; };
@ -598,6 +608,8 @@
4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */; };
4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */; };
4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */; };
4BC39568208EE6CF0044766B /* DiskIICard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC39566208EE6CF0044766B /* DiskIICard.cpp */; };
4BC39569208EE6CF0044766B /* DiskIICard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC39566208EE6CF0044766B /* DiskIICard.cpp */; };
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B74D1CD194CC00F86E85 /* Shader.cpp */; };
4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B7501CD1956900F86E85 /* OutputShader.cpp */; };
4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B11D157E61006C31D9 /* 6522Tests.swift */; };
@ -670,6 +682,8 @@
/* Begin PBXFileReference section */
4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MemptrTests.swift; sourceTree = "<group>"; };
4B0333AD2094081A0050B93D /* AppleDSK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AppleDSK.cpp; sourceTree = "<group>"; };
4B0333AE2094081A0050B93D /* AppleDSK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AppleDSK.hpp; sourceTree = "<group>"; };
4B046DC31CFE651500E9E45E /* CRTMachine.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTMachine.hpp; sourceTree = "<group>"; };
4B047075201ABC180047AB0D /* Cartridge.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; };
4B049CDC1DA3C82F00322067 /* BCDTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BCDTest.swift; sourceTree = "<group>"; };
@ -693,6 +707,9 @@
4B0E04F91FC9FA3100F43484 /* 9918.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = 9918.cpp; path = 9918/9918.cpp; sourceTree = "<group>"; };
4B0E61051FF34737002A9DBD /* MSX.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MSX.cpp; path = Parsers/MSX.cpp; sourceTree = "<group>"; };
4B0E61061FF34737002A9DBD /* MSX.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = MSX.hpp; path = Parsers/MSX.hpp; sourceTree = "<group>"; };
4B0F94FC208C1A1600FE41D9 /* NIB.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = NIB.cpp; sourceTree = "<group>"; };
4B0F94FD208C1A1600FE41D9 /* NIB.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = NIB.hpp; sourceTree = "<group>"; };
4B0F9500208C42A300FE41D9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Target.hpp; path = AppleII/Target.hpp; sourceTree = "<group>"; };
4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMPatchedTrackTests.mm; sourceTree = "<group>"; };
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMSegmentEventSourceTests.mm; sourceTree = "<group>"; };
4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Keyboard.cpp; path = MSX/Keyboard.cpp; sourceTree = "<group>"; };
@ -754,6 +771,8 @@
4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Atari2600Inputs.h; sourceTree = "<group>"; };
4B2E2D9B1C3A070400138695 /* Electron.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Electron.cpp; path = Electron/Electron.cpp; sourceTree = "<group>"; };
4B2E2D9C1C3A070400138695 /* Electron.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Electron.hpp; path = Electron/Electron.hpp; sourceTree = "<group>"; };
4B302182208A550100773308 /* DiskII.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskII.hpp; sourceTree = "<group>"; };
4B302183208A550100773308 /* DiskII.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DiskII.cpp; sourceTree = "<group>"; };
4B30512B1D989E2200B4FED8 /* Drive.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Drive.cpp; sourceTree = "<group>"; };
4B30512C1D989E2200B4FED8 /* Drive.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Drive.hpp; sourceTree = "<group>"; };
4B30512E1D98ACC600B4FED8 /* Plus3.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Plus3.cpp; path = Electron/Plus3.cpp; sourceTree = "<group>"; };
@ -878,6 +897,8 @@
4B6A4C911F58F09E00E3F787 /* 6502AllRAM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6502AllRAM.cpp; sourceTree = "<group>"; };
4B6A4C921F58F09E00E3F787 /* 6502AllRAM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6502AllRAM.hpp; sourceTree = "<group>"; };
4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6502Base.cpp; sourceTree = "<group>"; };
4B6ED2EE208E2F8A0047B343 /* WOZ.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = WOZ.cpp; sourceTree = "<group>"; };
4B6ED2EF208E2F8A0047B343 /* WOZ.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = WOZ.hpp; sourceTree = "<group>"; };
4B7041271F92C26900735E45 /* JoystickMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = JoystickMachine.hpp; sourceTree = "<group>"; };
4B70412A1F92C2A700735E45 /* Joystick.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Joystick.hpp; sourceTree = "<group>"; };
4B70EF6A1FFDCDF400A3494E /* ROMSlotHandler.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = ROMSlotHandler.hpp; path = MSX/ROMSlotHandler.hpp; sourceTree = "<group>"; };
@ -1275,6 +1296,8 @@
4BB298EC1B587D8400A49093 /* txsn */ = {isa = PBXFileReference; lastKnownFileType = file; path = txsn; sourceTree = "<group>"; };
4BB298ED1B587D8400A49093 /* tyan */ = {isa = PBXFileReference; lastKnownFileType = file; path = tyan; sourceTree = "<group>"; };
4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CRCTests.mm; sourceTree = "<group>"; };
4BB2CB28208BDDCF00FD192E /* AppleGCR.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = AppleGCR.cpp; path = Encodings/AppleGCR.cpp; sourceTree = "<group>"; };
4BB2CB29208BDDCF00FD192E /* AppleGCR.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = AppleGCR.hpp; path = Encodings/AppleGCR.hpp; sourceTree = "<group>"; };
4BB697C61D4B558F00248BDF /* Factors.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Factors.hpp; path = ../../NumberTheory/Factors.hpp; sourceTree = "<group>"; };
4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TimedEventLoop.cpp; sourceTree = "<group>"; };
4BB697CA1D4B6D3E00248BDF /* TimedEventLoop.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TimedEventLoop.hpp; sourceTree = "<group>"; };
@ -1315,6 +1338,9 @@
4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ZX8081.cpp; path = Parsers/ZX8081.cpp; sourceTree = "<group>"; };
4BBFBB6B1EE8401E00C01E7A /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ZX8081.hpp; path = Parsers/ZX8081.hpp; sourceTree = "<group>"; };
4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = TrackSerialiser.cpp; sourceTree = "<group>"; };
4BC39565208EDFCE0044766B /* Card.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Card.hpp; sourceTree = "<group>"; };
4BC39566208EE6CF0044766B /* DiskIICard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DiskIICard.cpp; sourceTree = "<group>"; };
4BC39567208EE6CF0044766B /* DiskIICard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DiskIICard.hpp; sourceTree = "<group>"; };
4BC3B74D1CD194CC00F86E85 /* Shader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Shader.cpp; sourceTree = "<group>"; };
4BC3B74E1CD194CC00F86E85 /* Shader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Shader.hpp; sourceTree = "<group>"; };
4BC3B7501CD1956900F86E85 /* OutputShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OutputShader.cpp; sourceTree = "<group>"; };
@ -1526,6 +1552,7 @@
children = (
4B15A9FA208249BB005E6C8D /* StaticAnalyser.cpp */,
4B15A9FB208249BB005E6C8D /* StaticAnalyser.hpp */,
4B0F9500208C42A300FE41D9 /* Target.hpp */,
);
name = AppleII;
sourceTree = "<group>";
@ -1534,8 +1561,11 @@
isa = PBXGroup;
children = (
4B15AA0C2082C799005E6C8D /* AppleII.cpp */,
4BC39566208EE6CF0044766B /* DiskIICard.cpp */,
4B15AA0A2082C799005E6C8D /* Video.cpp */,
4B15AA092082C799005E6C8D /* AppleII.hpp */,
4BC39565208EDFCE0044766B /* Card.hpp */,
4BC39567208EE6CF0044766B /* DiskIICard.hpp */,
4B15AA0B2082C799005E6C8D /* Video.hpp */,
);
path = AppleII;
@ -1674,6 +1704,15 @@
name = Electron;
sourceTree = "<group>";
};
4B302181208A550100773308 /* DiskII */ = {
isa = PBXGroup;
children = (
4B302182208A550100773308 /* DiskII.hpp */,
4B302183208A550100773308 /* DiskII.cpp */,
);
path = DiskII;
sourceTree = "<group>";
};
4B31B88E1FBFBCD800C140D5 /* Configurable */ = {
isa = PBXGroup;
children = (
@ -1834,6 +1873,7 @@
isa = PBXGroup;
children = (
4B45188D1F75FD1B00926311 /* AcornADF.cpp */,
4B0333AD2094081A0050B93D /* AppleDSK.cpp */,
4B45188F1F75FD1B00926311 /* CPCDSK.cpp */,
4B4518911F75FD1B00926311 /* D64.cpp */,
4BAF2B4C2004580C00480230 /* DMK.cpp */,
@ -1841,9 +1881,12 @@
4B4518951F75FD1B00926311 /* HFE.cpp */,
4B58601C1F806AB200AEE2E3 /* MFMSectorDump.cpp */,
4BEBFB4B2002C4BF000708CC /* MSXDSK.cpp */,
4B0F94FC208C1A1600FE41D9 /* NIB.cpp */,
4B4518971F75FD1B00926311 /* OricMFMDSK.cpp */,
4B4518991F75FD1B00926311 /* SSD.cpp */,
4B6ED2EE208E2F8A0047B343 /* WOZ.cpp */,
4B45188E1F75FD1B00926311 /* AcornADF.hpp */,
4B0333AE2094081A0050B93D /* AppleDSK.hpp */,
4B4518901F75FD1B00926311 /* CPCDSK.hpp */,
4B4518921F75FD1B00926311 /* D64.hpp */,
4BAF2B4D2004580C00480230 /* DMK.hpp */,
@ -1851,8 +1894,10 @@
4B4518961F75FD1B00926311 /* HFE.hpp */,
4B58601D1F806AB200AEE2E3 /* MFMSectorDump.hpp */,
4BEBFB4C2002C4BF000708CC /* MSXDSK.hpp */,
4B0F94FD208C1A1600FE41D9 /* NIB.hpp */,
4B4518981F75FD1B00926311 /* OricMFMDSK.hpp */,
4B45189A1F75FD1B00926311 /* SSD.hpp */,
4B6ED2EF208E2F8A0047B343 /* WOZ.hpp */,
4BFDD7891F7F2DB4008579B9 /* Utility */,
);
path = Formats;
@ -2630,9 +2675,11 @@
4BB697CF1D4BA44900248BDF /* Encodings */ = {
isa = PBXGroup;
children = (
4B7136831F78724F008B8ED9 /* MFM */,
4BB2CB28208BDDCF00FD192E /* AppleGCR.cpp */,
4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */,
4BB2CB29208BDDCF00FD192E /* AppleGCR.hpp */,
4BB697CD1D4BA44400248BDF /* CommodoreGCR.hpp */,
4B7136831F78724F008B8ED9 /* MFM */,
);
name = Encodings;
sourceTree = "<group>";
@ -2850,6 +2897,7 @@
4BC9DF4A1D04691600F44158 /* Components */ = {
isa = PBXGroup;
children = (
4B302181208A550100773308 /* DiskII */,
4B595FAA2086DFBA0083CAA8 /* AudioToggle */,
4BD468F81D8DF4290084958B /* 1770 */,
4BC9DF4B1D04691600F44158 /* 6522 */,
@ -3508,6 +3556,7 @@
4BB0A65E204500A900FB3688 /* StaticAnalyser.cpp in Sources */,
4B055AC11FAE98DC0060FFFF /* MachineForTarget.cpp in Sources */,
4BBB70A9202014E2002FE009 /* MultiCRTMachine.cpp in Sources */,
4B6ED2F1208E2F8A0047B343 /* WOZ.cpp in Sources */,
4B055AD81FAE9B180060FFFF /* Video.cpp in Sources */,
4B89452F201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4B894531201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
@ -3570,6 +3619,7 @@
4B15AA0E2082C799005E6C8D /* Video.cpp in Sources */,
4B055A991FAE85CB0060FFFF /* DiskController.cpp in Sources */,
4B055ACC1FAE9B030060FFFF /* Electron.cpp in Sources */,
4BC39569208EE6CF0044766B /* DiskIICard.cpp in Sources */,
4B055AB11FAE86070060FFFF /* Tape.cpp in Sources */,
4BFE7B881FC39D8900160B38 /* StandardOptions.cpp in Sources */,
4B894533201967B4007DE474 /* 6502.cpp in Sources */,
@ -3598,6 +3648,7 @@
4B894525201967B4007DE474 /* Tape.cpp in Sources */,
4B055ACD1FAE9B030060FFFF /* Keyboard.cpp in Sources */,
4B055AB21FAE860F0060FFFF /* CommodoreTAP.cpp in Sources */,
4BB2CB2B208BDDCF00FD192E /* AppleGCR.cpp in Sources */,
4B055ADF1FAE9B4C0060FFFF /* IRQDelegatePortHandler.cpp in Sources */,
4B055AB51FAE860F0060FFFF /* TapePRG.cpp in Sources */,
4B055AE01FAE9B660060FFFF /* CRT.cpp in Sources */,
@ -3605,6 +3656,7 @@
4BAF2B4F2004580C00480230 /* DMK.cpp in Sources */,
4B055AD01FAE9B030060FFFF /* Tape.cpp in Sources */,
4B055A961FAE85BB0060FFFF /* Commodore.cpp in Sources */,
4B0333B02094081A0050B93D /* AppleDSK.cpp in Sources */,
4B055ADE1FAE9B4C0060FFFF /* 6522Base.cpp in Sources */,
4B894535201967B4007DE474 /* AddressMapper.cpp in Sources */,
4B055AD41FAE9B0B0060FFFF /* Oric.cpp in Sources */,
@ -3617,12 +3669,14 @@
4B055AE71FAE9B6F0060FFFF /* Shader.cpp in Sources */,
4B894523201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4B055AEC1FAE9BA20060FFFF /* Z80Base.cpp in Sources */,
4B0F94FF208C1A1600FE41D9 /* NIB.cpp in Sources */,
4B0E04EB1FC9E78800F43484 /* CAS.cpp in Sources */,
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 */,
4B302185208A550100773308 /* DiskII.cpp in Sources */,
4B055A931FAE85B50060FFFF /* BinaryDump.cpp in Sources */,
4B89452D201967B4007DE474 /* Tape.cpp in Sources */,
4B055AD61FAE9B130060FFFF /* MemoryFuzzer.cpp in Sources */,
@ -3654,6 +3708,7 @@
4B4518A01F75FD1C00926311 /* CPCDSK.cpp in Sources */,
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */,
4B322E041F5A2E3C004EB04C /* Z80Base.cpp in Sources */,
4BB2CB2A208BDDCF00FD192E /* AppleGCR.cpp in Sources */,
4B894530201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4B4518A31F75FD1C00926311 /* HFE.cpp in Sources */,
4B1B88BB202E2EC100B67DFF /* MultiKeyboardMachine.cpp in Sources */,
@ -3670,6 +3725,7 @@
4B595FAD2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */,
4B1497921EE4B5A800CE2596 /* ZX8081.cpp in Sources */,
4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */,
4BC39568208EE6CF0044766B /* DiskIICard.cpp in Sources */,
4B4518861F75E91A00926311 /* MFMDiskController.cpp in Sources */,
4B54C0BF1F8D8F450050900F /* Keyboard.cpp in Sources */,
4B3FE75E1F3CF68B00448EE4 /* CPM.cpp in Sources */,
@ -3678,6 +3734,7 @@
4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */,
4BAE49582032881E004BE78E /* CSZX8081.mm in Sources */,
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */,
4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */,
4B894518201967B4007DE474 /* ConfidenceCounter.cpp in Sources */,
4B89452E201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4B38F3481F2EC11D00D9235D /* AmstradCPC.cpp in Sources */,
@ -3707,6 +3764,7 @@
4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */,
4BEBFB512002DB30000708CC /* DiskROM.cpp in Sources */,
4B89451C201967B4007DE474 /* Disk.cpp in Sources */,
4B302184208A550100773308 /* DiskII.cpp in Sources */,
4BEA52631DF339D7007E74F2 /* SoundGenerator.cpp in Sources */,
4BAE495920328897004BE78E /* ZX8081OptionsPanel.swift in Sources */,
4B89451A201967B4007DE474 /* ConfidenceSummary.cpp in Sources */,
@ -3716,6 +3774,7 @@
4B4518851F75E91A00926311 /* DiskController.cpp in Sources */,
4B8334841F5DA0360097E338 /* Z80Storage.cpp in Sources */,
4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */,
4B0F94FE208C1A1600FE41D9 /* NIB.cpp in Sources */,
4B89452A201967B4007DE474 /* File.cpp in Sources */,
4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */,
4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */,
@ -3774,6 +3833,7 @@
4B894526201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */,
4B8805FB1DCFF807003085B1 /* Oric.cpp in Sources */,
4B6ED2F0208E2F8A0047B343 /* WOZ.cpp in Sources */,
4BFE7B871FC39BF100160B38 /* StandardOptions.cpp in Sources */,
4B15A9FC208249BB005E6C8D /* StaticAnalyser.cpp in Sources */,
4B5FADC01DE3BF2B00AEC565 /* Microdisc.cpp in Sources */,

View File

@ -393,6 +393,29 @@
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>nib</string>
<string>woz</string>
<string>do</string>
<string>po</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy525</string>
<key>CFBundleTypeName</key>
<string>Apple II Disk Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.item</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>

View File

@ -18,7 +18,7 @@ ROMMachine::ROMFetcher CSROMFetcher() {
return [] (const std::string &machine, const std::vector<std::string> &names) -> std::vector<std::unique_ptr<std::vector<std::uint8_t>>> {
NSString *subDirectory = [@"ROMImages/" stringByAppendingString:[NSString stringWithUTF8String:machine.c_str()]];
std::vector<std::unique_ptr<std::vector<std::uint8_t>>> results;
for(auto &name: names) {
for(const auto &name: names) {
NSData *fileData = [[NSBundle mainBundle] dataForResource:[NSString stringWithUTF8String:name.c_str()] withExtension:nil subdirectory:subDirectory];
if(!fileData)

View File

@ -15,6 +15,7 @@
#include "../../../../../Analyser/Static/Acorn/Target.hpp"
#include "../../../../../Analyser/Static/AmstradCPC/Target.hpp"
#include "../../../../../Analyser/Static/AppleII/Target.hpp"
#include "../../../../../Analyser/Static/Commodore/Target.hpp"
#include "../../../../../Analyser/Static/MSX/Target.hpp"
#include "../../../../../Analyser/Static/Oric/Target.hpp"
@ -156,7 +157,7 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
- (instancetype)initWithAppleII {
self = [super init];
if(self) {
using Target = Analyser::Static::Target;
using Target = Analyser::Static::AppleII::Target;
std::unique_ptr<Target> target(new Target);
target->machine = Analyser::Machine::AppleII;
_targets.push_back(std::move(target));

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
TargetList targets = Analyser::Static::GetTargets([fullPath UTF8String]);
auto 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
TargetList targets = Analyser::Static::GetTargets([fullPath UTF8String]);
auto targets = Analyser::Static::GetTargets([fullPath UTF8String]);
// grab the ROM record
MSXROMRecord *romRecord = romRecordsBySHA1[sha1];

View File

@ -59,7 +59,7 @@
- (Storage::Time)timeForEvents:(const std::vector<Storage::Disk::Track::Event> &)events {
Storage::Time result(0);
for(auto event : events) {
for(const auto &event : events) {
result += event.length;
}
return result;

View File

@ -51,4 +51,47 @@
XCTAssert(events[8].length == transition_length, "Time taken in transition between speed zones should be half of a bit length in the first part plus half of a bit length in the second");
}
- (void)testComplicatedTrackSeek {
std::vector<Storage::Disk::PCMSegment> segments;
Storage::Disk::PCMSegment sync_segment;
sync_segment.data.resize(10);
sync_segment.number_of_bits = 10*8;
memset(sync_segment.data.data(), 0xff, sync_segment.data.size());
Storage::Disk::PCMSegment header_segment;
header_segment.data.resize(14);
header_segment.number_of_bits = 14*8;
memset(header_segment.data.data(), 0xff, header_segment.data.size());
Storage::Disk::PCMSegment data_segment;
data_segment.data.resize(349);
data_segment.number_of_bits = 349*8;
memset(data_segment.data.data(), 0xff, data_segment.data.size());
for(std::size_t c = 0; c < 16; ++c) {
segments.push_back(sync_segment);
segments.push_back(header_segment);
segments.push_back(sync_segment);
segments.push_back(data_segment);
segments.push_back(sync_segment);
}
Storage::Disk::PCMTrack track(segments);
Storage::Time late_time(967445, 2045454);
const auto offset = track.seek_to(late_time);
XCTAssert(offset <= late_time, "Found location should be at or before sought time");
const auto difference = late_time - offset;
const double difference_duration = difference.get<double>();
XCTAssert(difference_duration >= 0.0 && difference_duration < 0.005, "Next event should occur soon");
const double offset_duration = offset.get<double>();
XCTAssert(offset_duration >= 0.0 && offset_duration < 0.5, "Next event should occur soon");
auto next_event = track.get_next_event();
double next_event_duration = next_event.length.get<double>();
XCTAssert(next_event_duration >= 0.0 && next_event_duration < 0.005, "Next event should occur soon");
}
@end

View File

@ -34,6 +34,7 @@ SOURCES += glob.glob('../../Components/9918/*.cpp')
SOURCES += glob.glob('../../Components/9918/Implementation/*.cpp')
SOURCES += glob.glob('../../Components/AudioToggle/*.cpp')
SOURCES += glob.glob('../../Components/AY38910/*.cpp')
SOURCES += glob.glob('../../Components/DiskII/*.cpp')
SOURCES += glob.glob('../../Components/KonamiSCC/*.cpp')
SOURCES += glob.glob('../../Components/SN76489/*.cpp')

View File

@ -208,16 +208,16 @@ int main(int argc, char *argv[]) {
std::cout << "Required machine type and configuration is determined from the file. Machines with further options:" << std::endl << std::endl;
auto all_options = Machine::AllOptionsByMachineName();
for(auto &machine_options: all_options) {
for(const auto &machine_options: all_options) {
std::cout << machine_options.first << ":" << std::endl;
for(auto &option: machine_options.second) {
for(const auto &option: machine_options.second) {
std::cout << '\t' << "--" << option->short_name;
Configurable::ListOption *list_option = dynamic_cast<Configurable::ListOption *>(option.get());
if(list_option) {
std::cout << "={";
bool is_first = true;
for(auto option: list_option->options) {
for(const auto &option: list_option->options) {
if(!is_first) std::cout << '|';
is_first = false;
std::cout << option;
@ -261,7 +261,7 @@ int main(int argc, char *argv[]) {
machine_name = machine;
std::vector<std::unique_ptr<std::vector<uint8_t>>> results;
for(auto &name: names) {
for(const auto &name: names) {
std::string local_path = "/usr/local/share/CLK/" + machine + "/" + name;
FILE *file = std::fopen(local_path.c_str(), "rb");
if(!file) {
@ -300,7 +300,7 @@ int main(int argc, char *argv[]) {
case ::Machine::Error::MissingROM:
std::cerr << "Could not find system ROMs; please install to /usr/local/share/CLK/ or /usr/share/CLK/." << std::endl;
std::cerr << "One or more of the following were needed but not found:" << std::endl;
for(auto &name: rom_names) {
for(const auto &name: rom_names) {
std::cerr << machine_name << '/' << name << std::endl;
}
break;
@ -379,7 +379,7 @@ int main(int argc, char *argv[]) {
configurable_device->set_selections(configurable_device->get_user_friendly_selections());
// Consider transcoding any list selections that map to Boolean options.
for(auto &option: configurable_device->get_options()) {
for(const auto &option: configurable_device->get_options()) {
// Check for a corresponding selection.
auto selection = arguments.selections.find(option->short_name);
if(selection != arguments.selections.end()) {

View File

@ -53,7 +53,7 @@ Shader::Shader(const std::string &vertex_shader, const std::string &fragment_sha
glAttachShader(shader_program_, vertex);
glAttachShader(shader_program_, fragment);
for(auto &binding : attribute_bindings) {
for(const auto &binding : attribute_bindings) {
glBindAttribLocation(shader_program_, binding.index, binding.name.c_str());
}

View File

@ -6,3 +6,4 @@ basic10.rom
basic11.rom
colour.rom
microdisc.rom
8dos.rom

View File

@ -92,7 +92,7 @@ void FIRFilter::coefficients_for_idealised_filter_response(short *filter_coeffic
std::vector<float> FIRFilter::get_coefficients() const {
std::vector<float> coefficients;
for(auto short_coefficient: filter_coefficients_) {
for(const auto short_coefficient: filter_coefficients_) {
coefficients.push_back(static_cast<float>(short_coefficient) / FixedMultiplier);
}
return coefficients;
@ -129,7 +129,7 @@ FIRFilter::FIRFilter(std::size_t number_of_taps, float input_sample_rate, float
}
FIRFilter::FIRFilter(const std::vector<float> &coefficients) {
for(auto coefficient: coefficients) {
for(const auto coefficient: coefficients) {
filter_coefficients_.push_back(static_cast<short>(coefficient * FixedMultiplier));
}
}

View File

@ -22,7 +22,7 @@ Controller::Controller(Cycles clock_rate) :
set_drive(empty_drive_);
}
void Controller::set_component_is_sleeping(void *component, bool is_sleeping) {
void Controller::set_component_is_sleeping(Sleeper *component, bool is_sleeping) {
update_sleep_observer();
}
@ -66,7 +66,7 @@ void Controller::set_expected_bit_length(Time bit_length) {
// this conversion doesn't need to be exact because there's a lot of variation to be taken
// account of in rotation speed, air turbulence, etc, so a direct conversion will do
int clocks_per_bit = static_cast<int>(cycles_per_bit.get_unsigned_int());
int clocks_per_bit = cycles_per_bit.get<int>();
pll_.reset(new DigitalPhaseLockedLoop(clocks_per_bit, 3));
pll_->set_delegate(this);
}

View File

@ -113,7 +113,7 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public Drive::EventDe
std::shared_ptr<Drive> empty_drive_;
void set_component_is_sleeping(void *component, bool is_sleeping);
void set_component_is_sleeping(Sleeper *component, bool is_sleeping);
// for Drive::EventDelegate
void process_event(const Track::Event &event);

View File

@ -18,6 +18,11 @@
namespace Storage {
namespace Disk {
enum class Error {
InvalidFormat = -2,
UnknownVersion = -3
};
/*!
Models a disk image as a collection of tracks, plus a range of possible track positions.

View File

@ -24,7 +24,7 @@ template <typename T> void DiskImageHolder<T>::flush_tracks() {
using TrackMap = std::map<Track::Address, std::shared_ptr<Track>>;
std::shared_ptr<TrackMap> track_copies(new TrackMap);
for(auto &address : unwritten_tracks_) {
for(const auto &address : unwritten_tracks_) {
track_copies->insert(std::make_pair(address, std::shared_ptr<Track>(cached_tracks_[address]->clone())));
}
unwritten_tracks_.clear();

View File

@ -20,18 +20,18 @@ using namespace Storage::Disk;
AcornADF::AcornADF(const std::string &file_name) : MFMSectorDump(file_name) {
// very loose validation: the file needs to be a multiple of 256 bytes
// and not ungainly large
if(file_.stats().st_size % static_cast<off_t>(128 << sector_size)) throw ErrorNotAcornADF;
if(file_.stats().st_size < 7 * static_cast<off_t>(128 << sector_size)) throw ErrorNotAcornADF;
if(file_.stats().st_size % static_cast<off_t>(128 << sector_size)) throw Error::InvalidFormat;
if(file_.stats().st_size < 7 * static_cast<off_t>(128 << sector_size)) throw Error::InvalidFormat;
// check that the initial directory's 'Hugo's are present
file_.seek(513, SEEK_SET);
uint8_t bytes[4];
file_.read(bytes, 4);
if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw ErrorNotAcornADF;
if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw Error::InvalidFormat;
file_.seek(0x6fb, SEEK_SET);
file_.read(bytes, 4);
if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw ErrorNotAcornADF;
if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw Error::InvalidFormat;
set_geometry(sectors_per_track, sector_size, 0, true);
}

View File

@ -24,15 +24,11 @@ class AcornADF: public MFMSectorDump {
/*!
Construct an @c AcornADF containing content from the file with name @c file_name.
@throws ErrorCantOpen if this file can't be opened.
@throws ErrorNotAcornADF if the file doesn't appear to contain an Acorn .ADF format image.
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
@throws Error::InvalidFormat if the file doesn't appear to contain an Acorn .ADF format image.
*/
AcornADF(const std::string &file_name);
enum {
ErrorNotAcornADF,
};
int get_head_position_count() override;
int get_head_count() override;

View File

@ -0,0 +1,78 @@
//
// AppleDSK.cpp
// Clock Signal
//
// Created by Thomas Harte on 27/04/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "AppleDSK.hpp"
#include "../../Track/PCMTrack.hpp"
#include "../../Encodings/AppleGCR.hpp"
using namespace Storage::Disk;
namespace {
const int number_of_tracks = 35;
const int bytes_per_sector = 256;
}
AppleDSK::AppleDSK(const std::string &file_name) :
file_(file_name) {
if(file_.stats().st_size % number_of_tracks*bytes_per_sector) throw Error::InvalidFormat;
sectors_per_track_ = static_cast<int>(file_.stats().st_size / (number_of_tracks*bytes_per_sector));
if(sectors_per_track_ != 13 && sectors_per_track_ != 16) throw Error::InvalidFormat;
// Check whether this is a Pro DOS disk by inspecting the filename.
if(sectors_per_track_ == 16) {
size_t string_index = file_name.size()-1;
while(file_name[string_index] != '.') {
if(file_name[string_index] == 'p') {
is_prodos_ = true;
break;
}
--string_index;
}
}
}
int AppleDSK::get_head_position_count() {
return number_of_tracks * 4;
}
std::shared_ptr<Track> AppleDSK::get_track_at_position(Track::Address address) {
const long file_offset = (address.position >> 2) * bytes_per_sector * sectors_per_track_;
file_.seek(file_offset, SEEK_SET);
const std::vector<uint8_t> track_data = file_.read(static_cast<size_t>(bytes_per_sector * sectors_per_track_));
Storage::Disk::PCMSegment segment;
const uint8_t track = static_cast<uint8_t>(address.position >> 2);
// In either case below, the code aims for exactly 50,000 bits per track.
if(sectors_per_track_ == 16) {
// Write the sectors.
uint8_t sector_number_ = 0;
for(std::size_t c = 0; c < 16; ++c) {
segment += Encodings::AppleGCR::six_and_two_sync(10);
segment += Encodings::AppleGCR::header(0, track, sector_number_);
segment += Encodings::AppleGCR::six_and_two_sync(10);
segment += Encodings::AppleGCR::six_and_two_data(&track_data[c * 256]);
// DOS and Pro DOS interleave sectors on disk, and they're represented in a disk
// image in physical order rather than logical. So that skew needs to be applied here.
sector_number_ += is_prodos_ ? 8 : 7;
if(sector_number_ > 0xf) sector_number_ %= 15;
}
// Pad if necessary.
if(segment.number_of_bits < 50000) {
segment += Encodings::AppleGCR::six_and_two_sync((50000 - segment.number_of_bits) >> 3);
}
} else {
}
return std::make_shared<PCMTrack>(segment);
}

View File

@ -0,0 +1,48 @@
//
// AppleDSK.hpp
// Clock Signal
//
// Created by Thomas Harte on 27/04/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef AppleDSK_hpp
#define AppleDSK_hpp
#include "../DiskImage.hpp"
#include "../../../FileHolder.hpp"
#include <string>
namespace Storage {
namespace Disk {
/*!
Provides a @c DiskImage containing an Apple DSK disk image a representation of sector contents,
implicitly numbered and located.
*/
class AppleDSK: public DiskImage {
public:
/*!
Construct an @c AppleDSK containing content from the file with name @c file_name.
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
@throws Error::InvalidFormat if the file doesn't appear to contain a .G64 format image.
*/
AppleDSK(const std::string &file_name);
// implemented to satisfy @c Disk
int get_head_position_count() override;
std::shared_ptr<Track> get_track_at_position(Track::Address address) override;
private:
Storage::FileHolder file_;
int sectors_per_track_ = 16;
bool is_prodos_ = false;
};
}
}
#endif /* AppleDSK_hpp */

View File

@ -27,7 +27,7 @@ CPCDSK::CPCDSK(const std::string &file_name) :
is_extended_ = true;
file.seek(0, SEEK_SET);
if(!file.check_signature("EXTENDED"))
throw ErrorNotCPCDSK;
throw Error::InvalidFormat;
}
// Don't really care about about the creator; skip.
@ -145,7 +145,7 @@ CPCDSK::CPCDSK(const std::string &file_name) :
if(declared_data_size > data_size) {
number_of_samplings = declared_data_size / data_size;
if(declared_data_size % data_size)
throw ErrorNotCPCDSK;
throw Error::InvalidFormat;
} else {
stored_data_size = declared_data_size;
}

View File

@ -27,15 +27,11 @@ class CPCDSK: public DiskImage {
/*!
Construct an @c AcornADF containing content from the file with name @c file_name.
@throws ErrorCantOpen if this file can't be opened.
@throws ErrorNotAcornADF if the file doesn't appear to contain an Acorn .ADF format image.
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
@throws Error::InvalidFormat if the file doesn't appear to contain an Acorn .ADF format image.
*/
CPCDSK(const std::string &file_name);
enum {
ErrorNotCPCDSK,
};
// implemented to satisfy @c Disk
int get_head_position_count() override;
int get_head_count() override;

View File

@ -22,7 +22,7 @@ D64::D64(const std::string &file_name) :
// in D64, this is it for validation without imposing potential false-negative tests — check that
// the file size appears to be correct. Stone-age stuff.
if(file_.stats().st_size != 174848 && file_.stats().st_size != 196608)
throw ErrorNotD64;
throw Error::InvalidFormat;
number_of_tracks_ = (file_.stats().st_size == 174848) ? 35 : 40;

View File

@ -23,15 +23,11 @@ class D64: public DiskImage {
/*!
Construct a @c D64 containing content from the file with name @c file_name.
@throws ErrorCantOpen if this file can't be opened.
@throws ErrorNotD64 if the file doesn't appear to contain a .D64 format image.
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
@throws Error::InvalidFormat if the file doesn't appear to contain a .D64 format image.
*/
D64(const std::string &file_name);
enum {
ErrorNotD64,
};
// implemented to satisfy @c Disk
int get_head_position_count() override;
using DiskImage::get_is_read_only;

View File

@ -35,9 +35,9 @@ std::unique_ptr<Storage::Encodings::MFM::Encoder> new_encoder(Storage::Disk::PCM
DMK::DMK(const std::string &file_name) :
file_(file_name) {
// Determine whether this DMK represents a read-only disk (whether intentionally,
// or by virtue of placement).
// or by virtue of filesystem placement).
uint8_t read_only_byte = file_.get8();
if(read_only_byte != 0x00 && read_only_byte != 0xff) throw ErrorNotDMK;
if(read_only_byte != 0x00 && read_only_byte != 0xff) throw Error::InvalidFormat;
is_read_only_ = (read_only_byte == 0xff) || file_.get_is_known_read_only();
// Read track count and size.
@ -46,7 +46,7 @@ DMK::DMK(const std::string &file_name) :
// Track length must be at least 0x80, as that's the size of the IDAM
// table before track contents.
if(track_length_ < 0x80) throw ErrorNotDMK;
if(track_length_ < 0x80) throw Error::InvalidFormat;
// Read the file flags and apply them.
uint8_t flags = file_.get8();
@ -58,7 +58,7 @@ DMK::DMK(const std::string &file_name) :
// "in the emulator's native format".
file_.seek(0xc, SEEK_SET);
uint32_t format = file_.get32le();
if(format) throw ErrorNotDMK;
if(format) throw Error::InvalidFormat;
}
int DMK::get_head_position_count() {

View File

@ -18,7 +18,7 @@ namespace Storage {
namespace Disk {
/*!
Provides a @c Disk containing a DMK disk image mostly a decoded byte stream, but with
Provides a @c DiskImage containing a DMK disk image mostly a decoded byte stream, but with
a record of IDAM locations.
*/
class DMK: public DiskImage {
@ -26,14 +26,10 @@ class DMK: public DiskImage {
/*!
Construct a @c DMK containing content from the file with name @c file_name.
@throws ErrorNotDMK if this file doesn't appear to be a DMK.
@throws Error::InvalidFormat if this file doesn't appear to be a DMK.
*/
DMK(const std::string &file_name);
enum {
ErrorNotDMK
};
// implemented to satisfy @c Disk
int get_head_position_count() override;
int get_head_count() override;

View File

@ -19,11 +19,11 @@ using namespace Storage::Disk;
G64::G64(const std::string &file_name) :
file_(file_name) {
// read and check the file signature
if(!file_.check_signature("GCR-1541")) throw ErrorNotG64;
if(!file_.check_signature("GCR-1541")) throw Error::InvalidFormat;
// check the version number
int version = file_.get8();
if(version != 0) throw ErrorUnknownVersion;
if(version != 0) throw Error::UnknownVersion;
// get the number of tracks and track size
number_of_tracks_ = file_.get8();

View File

@ -25,18 +25,12 @@ class G64: public DiskImage {
/*!
Construct a @c G64 containing content from the file with name @c file_name.
@throws ErrorCantOpen if this file can't be opened.
@throws ErrorNotG64 if the file doesn't appear to contain a .G64 format image.
@throws ErrorUnknownVersion if this file appears to be a .G64 but has an unrecognised version number.
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
@throws Error::InvalidFormat if the file doesn't appear to contain a .G64 format image.
@throws Error::UnknownVersion if this file appears to be a .G64 but has an unrecognised version number.
*/
G64(const std::string &file_name);
enum {
ErrorCantOpen,
ErrorNotG64,
ErrorUnknownVersion
};
// implemented to satisfy @c Disk
int get_head_position_count() override;
std::shared_ptr<Track> get_track_at_position(Track::Address address) override;

View File

@ -16,9 +16,9 @@ using namespace Storage::Disk;
HFE::HFE(const std::string &file_name) :
file_(file_name) {
if(!file_.check_signature("HXCPICFE")) throw ErrorNotHFE;
if(!file_.check_signature("HXCPICFE")) throw Error::InvalidFormat;
if(file_.get8()) throw ErrorNotHFE;
if(file_.get8()) throw Error::UnknownVersion;
track_count_ = file_.get8();
head_count_ = file_.get8();
@ -79,9 +79,7 @@ std::shared_ptr<Track> HFE::get_track_at_position(Track::Address address) {
// Flip bytes; HFE's preference is that the least-significant bit
// is serialised first, but PCMTrack posts the most-significant first.
Storage::Data::BitReverse::reverse(segment.data);
std::shared_ptr<Track> track(new PCMTrack(segment));
return track;
return std::make_shared<PCMTrack>(segment);
}
void HFE::set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) {

View File

@ -18,23 +18,20 @@ namespace Storage {
namespace Disk {
/*!
Provides a @c Disk containing an HFE disk image a bit stream representation of a floppy.
Provides a @c DiskImage containing an HFE a bit stream representation of a floppy.
*/
class HFE: public DiskImage {
public:
/*!
Construct an @c SSD containing content from the file with name @c file_name.
Construct an @c HFE containing content from the file with name @c file_name.
@throws ErrorCantOpen if this file can't be opened.
@throws ErrorNotSSD if the file doesn't appear to contain a .SSD format image.
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
@throws Error::InvalidFormat if the file doesn't appear to contain an .HFE format image.
@throws Error::UnknownVersion if the file looks correct but is an unsupported version.
*/
HFE(const std::string &file_name);
~HFE();
enum {
ErrorNotHFE,
};
// implemented to satisfy @c Disk
int get_head_position_count() override;
int get_head_count() override;

View File

@ -42,7 +42,7 @@ void MFMSectorDump::set_tracks(const std::map<Track::Address, std::shared_ptr<Tr
// TODO: it would be more efficient from a file access and locking point of view to parse the sectors
// in one loop, then write in another.
for(auto &track : tracks) {
for(const auto &track : tracks) {
// Assumption here: sector IDs will run from 0.
decode_sectors(*track.second, parsed_track, first_sector_, first_sector_ + static_cast<uint8_t>(sectors_per_track_-1), sector_size_, is_double_density_);
long file_offset = get_file_offset_for_position(track.first);

View File

@ -11,8 +11,9 @@
#include "Utility/ImplicitSectors.hpp"
namespace {
static const int sectors_per_track = 9;
static const int sector_size = 2;
const int sectors_per_track = 9;
const int sector_size = 2;
const off_t track_size = (128 << sector_size)*sectors_per_track;
}
using namespace Storage::Disk;
@ -22,19 +23,18 @@ MSXDSK::MSXDSK(const std::string &file_name) :
// The only sanity check here is whether a sensible
// geometry can be guessed.
off_t file_size = file_.stats().st_size;
const off_t track_size = 512*9;
// Throw if there would seemingly be an incomplete track.
if(file_size % track_size) throw ErrorNotMSXDSK;
if(file_size % track_size) throw Error::InvalidFormat;
track_count_ = static_cast<int>(file_size / track_size);
head_count_ = 1;
// Throw if too large or too small or too large for single sided and
// clearly not double sided.
if(track_count_ < 40) throw ErrorNotMSXDSK;
if(track_count_ > 82*2) throw ErrorNotMSXDSK;
if(track_count_ > 82 && track_count_&1) throw ErrorNotMSXDSK;
if(track_count_ < 40) throw Error::InvalidFormat;
if(track_count_ > 82*2) throw Error::InvalidFormat;
if(track_count_ > 82 && track_count_&1) throw Error::InvalidFormat;
// The below effectively prefers the idea of a single-sided 80-track disk
// to a double-sided 40-track disk. Emulators have to guess.

View File

@ -17,17 +17,12 @@ namespace Storage {
namespace Disk {
/*!
Provides a @c Disk containing an MSX-style disk image:
Provides a @c DiskImage descriging an MSX-style disk image:
a sector dump of appropriate proportions.
*/
class MSXDSK: public MFMSectorDump {
public:
MSXDSK(const std::string &file_name);
enum {
ErrorNotMSXDSK,
};
int get_head_position_count() override;
int get_head_count() override;

View File

@ -0,0 +1,102 @@
//
// NIB.cpp
// Clock Signal
//
// Created by Thomas Harte on 21/04/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "NIB.hpp"
#include "../../Track/PCMTrack.hpp"
#include "../../Encodings/AppleGCR.hpp"
#include <vector>
using namespace Storage::Disk;
namespace {
const long track_length = 6656;
const std::size_t number_of_tracks = 35;
}
NIB::NIB(const std::string &file_name) :
file_(file_name) {
// A NIB should be 35 tracks, each 6656 bytes long.
if(file_.stats().st_size != track_length*number_of_tracks) {
throw Error::InvalidFormat;
}
// TODO: all other validation. I.e. does this look like a GCR disk?
}
int NIB::get_head_position_count() {
return number_of_tracks * 4;
}
std::shared_ptr<::Storage::Disk::Track> NIB::get_track_at_position(::Storage::Disk::Track::Address address) {
// NIBs contain data for even-numbered tracks underneath a single head only.
if(address.head) return nullptr;
const long file_track = static_cast<long>(address.position >> 2);
file_.seek(file_track * track_length, SEEK_SET);
std::vector<uint8_t> track_data = file_.read(track_length);
// NIB files leave sync bytes implicit and make no guarantees
// about overall track positioning. So the approach taken here
// is to look for the epilogue sequence (which concludes all Apple
// tracks and headers), then treat all following FFs as a sync
// region, then switch back to ordinary behaviour as soon as a
// non-FF appears.
PCMSegment segment;
std::size_t start_index = 0;
std::set<size_t> sync_starts;
// Establish where syncs start by finding instances of 0xd5 0xaa and then regressing
// from each along all preceding FFs.
for(size_t index = 0; index < track_data.size(); ++index) {
if(track_data[index] == 0xd5 && track_data[(index+1)%track_data.size()] == 0xaa) {
size_t start = index - 1;
size_t length = 0;
while(track_data[start] == 0xff) {
start = (start + track_data.size() - 1) % track_data.size();
++length;
}
if(length >= 5) {
sync_starts.insert((start + 1) % track_data.size());
if(start > index)
start_index = start;
}
}
}
if(start_index) {
segment += Encodings::AppleGCR::six_and_two_sync(static_cast<int>(start_index));
}
std::size_t index = start_index;
for(const auto &location: sync_starts) {
// Write from index to sync_start.
PCMSegment data_segment;
data_segment.data.insert(
data_segment.data.end(),
track_data.begin() + static_cast<off_t>(index),
track_data.begin() + static_cast<off_t>(location));
data_segment.number_of_bits = static_cast<unsigned int>(data_segment.data.size() * 8);
segment += data_segment;
// Add a sync from sync_start to end of 0xffs.
if(location == track_length-1) break;
index = location;
while(index < track_length && track_data[index] == 0xff)
++index;
segment += Encodings::AppleGCR::six_and_two_sync(static_cast<int>(index - location));
}
return std::make_shared<PCMTrack>(segment);
}

View File

@ -0,0 +1,40 @@
//
// NIB.hpp
// Clock Signal
//
// Created by Thomas Harte on 21/04/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef NIB_hpp
#define NIB_hpp
#include "../DiskImage.hpp"
#include "../../../FileHolder.hpp"
namespace Storage {
namespace Disk {
/*!
Provides a @c DiskImage describing an Apple NIB disk image:
mostly a bit stream capture, but syncs are implicitly packed
into 8 bits instead of 9.
*/
class NIB: public DiskImage {
public:
NIB(const std::string &file_name);
int get_head_position_count() override;
std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) override;
private:
FileHolder file_;
long get_file_offset_for_position(Track::Address address);
};
}
}
#endif /* NIB_hpp */

View File

@ -19,14 +19,14 @@ using namespace Storage::Disk;
OricMFMDSK::OricMFMDSK(const std::string &file_name) :
file_(file_name) {
if(!file_.check_signature("MFM_DISK"))
throw ErrorNotOricMFMDSK;
throw Error::InvalidFormat;
head_count_ = file_.get32le();
track_count_ = file_.get32le();
geometry_type_ = file_.get32le();
if(geometry_type_ < 1 || geometry_type_ > 2)
throw ErrorNotOricMFMDSK;
throw Error::InvalidFormat;
}
int OricMFMDSK::get_head_position_count() {
@ -113,7 +113,7 @@ std::shared_ptr<Track> OricMFMDSK::get_track_at_position(Track::Address address)
}
void OricMFMDSK::set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) {
for(auto &track : tracks) {
for(const auto &track : tracks) {
PCMSegment segment = Storage::Disk::track_serialisation(*track.second, Storage::Encodings::MFM::MFMBitLength);
Storage::Encodings::MFM::Shifter shifter;
shifter.set_is_double_density(true);

View File

@ -29,10 +29,6 @@ class OricMFMDSK: public DiskImage {
*/
OricMFMDSK(const std::string &file_name);
enum {
ErrorNotOricMFMDSK,
};
// implemented to satisfy @c DiskImage
int get_head_position_count() override;
int get_head_count() override;

View File

@ -23,9 +23,9 @@ SSD::SSD(const std::string &file_name) : MFMSectorDump(file_name) {
// very loose validation: the file needs to be a multiple of 256 bytes
// and not ungainly large
if(file_.stats().st_size & 255) throw ErrorNotSSD;
if(file_.stats().st_size < 512) throw ErrorNotSSD;
if(file_.stats().st_size > 800*256) throw ErrorNotSSD;
if(file_.stats().st_size & 255) throw Error::InvalidFormat;
if(file_.stats().st_size < 512) throw Error::InvalidFormat;
if(file_.stats().st_size > 800*256) throw Error::InvalidFormat;
// this has two heads if the suffix is .dsd, one if it's .ssd
head_count_ = (tolower(file_name[file_name.size() - 3]) == 'd') ? 2 : 1;

View File

@ -22,15 +22,11 @@ class SSD: public MFMSectorDump {
/*!
Construct an @c SSD containing content from the file with name @c file_name.
@throws ErrorCantOpen if this file can't be opened.
@throws ErrorNotSSD if the file doesn't appear to contain a .SSD format image.
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
@throws Error::InvalidFormat if the file doesn't appear to contain a .SSD format image.
*/
SSD(const std::string &file_name);
enum {
ErrorNotSSD,
};
int get_head_position_count() override;
int get_head_count() override;

View File

@ -52,7 +52,7 @@ void Storage::Disk::decode_sectors(Track &track, uint8_t *const destination, uin
is_double_density);
std::size_t byte_size = static_cast<std::size_t>(128 << sector_size);
for(auto &pair : sectors) {
for(const auto &pair : sectors) {
if(pair.second.address.sector > last_sector) continue;
if(pair.second.address.sector < first_sector) continue;
if(pair.second.size != sector_size) continue;

View File

@ -0,0 +1,104 @@
//
// WOZ.cpp
// Clock Signal
//
// Created by Thomas Harte on 23/04/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "WOZ.hpp"
#include "../../Track/PCMTrack.hpp"
using namespace Storage::Disk;
WOZ::WOZ(const std::string &file_name) :
file_(file_name) {
const char signature[8] = {
'W', 'O', 'Z', '1',
static_cast<char>(0xff), 0x0a, 0x0d, 0x0a
};
if(!file_.check_signature(signature, 8)) throw Error::InvalidFormat;
// TODO: check CRC32, instead of skipping it.
file_.seek(4, SEEK_CUR);
// Parse all chunks up front.
bool has_tmap = false;
while(true) {
const uint32_t chunk_id = file_.get32le();
const uint32_t chunk_size = file_.get32le();
if(file_.eof()) break;
long end_of_chunk = file_.tell() + static_cast<long>(chunk_size);
#define CK(str) (str[0] | (str[1] << 8) | (str[2] << 16) | (str[3] << 24))
switch(chunk_id) {
case CK("INFO"): {
const uint8_t version = file_.get8();
if(version != 1) break;
is_3_5_disk_ = file_.get8() == 2;
is_read_only_ = file_.get8() == 1;
/* Ignored:
1 byte: Synchronized; 1 = Cross track sync was used during imaging.
1 byte: Cleaned; 1 = MC3470 fake bits have been removed.
32 bytes: Cretor; a UTF-8 string.
*/
} break;
case CK("TMAP"): {
file_.read(track_map_, 160);
has_tmap = true;
} break;
case CK("TRKS"): {
tracks_offset_ = file_.tell();
} break;
// TODO: parse META chunks.
default:
break;
}
#undef CK
file_.seek(end_of_chunk, SEEK_SET);
}
if(tracks_offset_ == -1 || !has_tmap) throw Error::InvalidFormat;
}
int WOZ::get_head_position_count() {
// TODO: deal with the elephant in the room of non-integral track coordinates.
return is_3_5_disk_ ? 80 : 160;
}
int WOZ::get_head_count() {
return is_3_5_disk_ ? 2 : 1;
}
std::shared_ptr<Track> WOZ::get_track_at_position(Track::Address address) {
// Out-of-bounds => no track.
if(address.head >= get_head_count()) return nullptr;
if(address.position >= get_head_position_count()) return nullptr;
// Calculate table position; if this track is defined to be unformatted, return no track.
const int table_position = address.head * get_head_position_count() + address.position;
if(track_map_[table_position] == 0xff) return nullptr;
// Seek to the real track.
file_.seek(tracks_offset_ + track_map_[table_position] * 6656, SEEK_SET);
PCMSegment track_contents;
track_contents.data = file_.read(6646);
track_contents.data.resize(file_.get16le());
track_contents.number_of_bits = file_.get16le();
const uint16_t splice_point = file_.get16le();
if(splice_point != 0xffff) {
// TODO: expand track from splice_point?
}
return std::shared_ptr<PCMTrack>(new PCMTrack(track_contents));
}

View File

@ -0,0 +1,42 @@
//
// WOZ.hpp
// Clock Signal
//
// Created by Thomas Harte on 23/04/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef WOZ_hpp
#define WOZ_hpp
#include "../DiskImage.hpp"
#include "../../../FileHolder.hpp"
#include <string>
namespace Storage {
namespace Disk {
/*!
Provides a @c DiskImage containing a WOZ a bit stream representation of a floppy.
*/
class WOZ: public DiskImage {
public:
WOZ(const std::string &file_name);
int get_head_position_count() override;
int get_head_count() override;
std::shared_ptr<Track> get_track_at_position(Track::Address address) override;
private:
Storage::FileHolder file_;
bool is_read_only_ = false;
bool is_3_5_disk_ = false;
uint8_t track_map_[160];
long tracks_offset_ = -1;
};
}
}
#endif /* WOZ_hpp */

View File

@ -19,6 +19,7 @@ Drive::Drive(unsigned int input_clock_rate, int revolutions_per_minute, int numb
Storage::TimedEventLoop(input_clock_rate),
rotational_multiplier_(60, revolutions_per_minute),
available_heads_(number_of_heads) {
rotational_multiplier_.simplify();
}
Drive::~Drive() {
@ -49,6 +50,7 @@ bool Drive::get_is_track_zero() {
void Drive::step(int direction) {
int old_head_position = head_position_;
head_position_ = std::max(head_position_ + direction, 0);
// printf("Step %d -> %d\n", direction, head_position_);
// If the head moved, flush the old track.
if(head_position_ != old_head_position) {
@ -70,7 +72,7 @@ Storage::Time Drive::get_time_into_track() {
Time result(cycles_since_index_hole_, static_cast<int>(get_input_clock_rate()));
result /= rotational_multiplier_;
result.simplify();
assert(result <= Time(1));
// assert(result <= Time(1));
return result;
}
@ -115,7 +117,7 @@ void Drive::run_for(const Cycles cycles) {
int cycles_until_next_event = static_cast<int>(get_cycles_until_next_event());
int cycles_to_run_for = std::min(cycles_until_next_event, number_of_cycles);
if(!is_reading_ && cycles_until_bits_written_ > zero) {
int write_cycles_target = static_cast<int>(cycles_until_bits_written_.get_unsigned_int());
int write_cycles_target = cycles_until_bits_written_.get<int>();
if(cycles_until_bits_written_.length % cycles_until_bits_written_.clock_rate) write_cycles_target++;
cycles_to_run_for = std::min(cycles_to_run_for, write_cycles_target);
}
@ -161,14 +163,14 @@ void Drive::get_next_event(const Time &duration_already_passed) {
// divide interval, which is in terms of a single rotation of the disk, by rotation speed to
// convert it into revolutions per second; this is achieved by multiplying by rotational_multiplier_
assert(current_event_.length <= Time(1) && current_event_.length >= Time(0));
assert(current_event_.length > duration_already_passed);
Time interval = (current_event_.length - duration_already_passed) * rotational_multiplier_;
set_next_event_time_interval(interval);
}
void Drive::process_next_event() {
// TODO: ready test here.
if(current_event_.type == Track::Event::IndexHole) {
assert(get_time_into_track() == Time(1) || get_time_into_track() == Time(0));
// assert(get_time_into_track() == Time(1) || get_time_into_track() == Time(0));
if(ready_index_count_ < 2) ready_index_count_++;
cycles_since_index_hole_ = 0;
}

View File

@ -110,10 +110,10 @@ class Drive: public Sleeper, public TimedEventLoop {
If the drive is in write mode, announces that all queued bits have now been written.
If the controller provides further bits now then there will be no gap in written data.
*/
virtual void process_write_completed() = 0;
virtual void process_write_completed() {}
/// Informs the delegate of the passing of @c cycles.
virtual void advance(const Cycles cycles) = 0;
virtual void advance(const Cycles cycles) {}
};
/// Sets the current event delegate.

View File

@ -0,0 +1,182 @@
//
// AppleGCR.cpp
// Clock Signal
//
// Created by Thomas Harte on 21/04/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "AppleGCR.hpp"
namespace {
const unsigned int five_and_three_mapping[] = {
0xab, 0xad, 0xae, 0xaf, 0xb5, 0xb6, 0xb7, 0xba,
0xbb, 0xbd, 0xbe, 0xbf, 0xd6, 0xd7, 0xda, 0xdb,
0xdd, 0xde, 0xdf, 0xea, 0xeb, 0xed, 0xee, 0xef,
0xf5, 0xf6, 0xf7, 0xfa, 0xfb, 0xfd, 0xfe, 0xff
};
const uint8_t six_and_two_mapping[] = {
0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6,
0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3,
0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc,
0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3,
0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde,
0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec,
0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6,
0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
};
/*!
Produces a PCM segment containing @c length sync bytes, each aligned to the beginning of
a @c bit_size -sized window.
*/
Storage::Disk::PCMSegment sync(int length, int bit_size) {
Storage::Disk::PCMSegment segment;
// Allocate sufficient storage.
segment.data.resize(static_cast<size_t>(((length * bit_size) + 7) >> 3), 0);
while(length--) {
segment.data[segment.number_of_bits >> 3] |= 0xff >> (segment.number_of_bits & 7);
if(segment.number_of_bits & 7) {
segment.data[1 + (segment.number_of_bits >> 3)] |= 0xff << (8 - (segment.number_of_bits & 7));
}
segment.number_of_bits += static_cast<unsigned int>(bit_size);
}
return segment;
}
}
using namespace Storage::Encodings;
/*void AppleGCR::encode_five_and_three_block(uint8_t *destination, uint8_t *source) {
destination[0] = static_cast<uint8_t>(five_and_three_encoding_for_value( source[0] >> 3 ));
destination[1] = static_cast<uint8_t>(five_and_three_encoding_for_value( (source[0] << 2) | (source[1] >> 6) ));
destination[2] = static_cast<uint8_t>(five_and_three_encoding_for_value( source[1] >> 1 ));
destination[3] = static_cast<uint8_t>(five_and_three_encoding_for_value( (source[1] << 4) | (source[2] >> 4) ));
destination[4] = static_cast<uint8_t>(five_and_three_encoding_for_value( (source[2] << 1) | (source[3] >> 7) ));
destination[5] = static_cast<uint8_t>(five_and_three_encoding_for_value( source[3] >> 2 ));
destination[6] = static_cast<uint8_t>(five_and_three_encoding_for_value( (source[3] << 3) | (source[4] >> 5) ));
destination[7] = static_cast<uint8_t>(five_and_three_encoding_for_value( source[4] ));
}*/
/*void AppleGCR::encode_six_and_two_block(uint8_t *destination, uint8_t *source) {
destination[0] = static_cast<uint8_t>(six_and_two_encoding_for_value( source[0] >> 2 ));
destination[1] = static_cast<uint8_t>(six_and_two_encoding_for_value( (source[0] << 4) | (source[1] >> 4) ));
destination[2] = static_cast<uint8_t>(six_and_two_encoding_for_value( (source[1] << 2) | (source[2] >> 6) ));
destination[3] = static_cast<uint8_t>(six_and_two_encoding_for_value( source[2] ));
}*/
Storage::Disk::PCMSegment AppleGCR::six_and_two_sync(int length) {
return sync(length, 10);
}
Storage::Disk::PCMSegment AppleGCR::five_and_three_sync(int length) {
return sync(length, 9);
}
Storage::Disk::PCMSegment AppleGCR::header(uint8_t volume, uint8_t track, uint8_t sector) {
const uint8_t checksum = volume ^ track ^ sector;
// Apple headers are encoded using an FM-esque scheme rather than 6 and 2, or 5 and 3.
Storage::Disk::PCMSegment segment;
segment.data.resize(14);
segment.number_of_bits = 14*8;
segment.data[0] = header_prologue[0];
segment.data[1] = header_prologue[1];
segment.data[2] = header_prologue[2];
#define WriteFM(index, value) \
segment.data[index+0] = static_cast<uint8_t>(((value) >> 1) | 0xaa); \
segment.data[index+1] = static_cast<uint8_t>((value) | 0xaa); \
WriteFM(3, volume);
WriteFM(5, track);
WriteFM(7, sector);
WriteFM(9, checksum);
#undef WriteFM
segment.data[11] = epilogue[0];
segment.data[12] = epilogue[1];
segment.data[13] = epilogue[2];
return segment;
}
Storage::Disk::PCMSegment AppleGCR::five_and_three_data(const uint8_t *source) {
Storage::Disk::PCMSegment segment;
segment.data.resize(410 + 7);
segment.data[0] = data_prologue[0];
segment.data[1] = data_prologue[1];
segment.data[2] = data_prologue[2];
segment.data[414] = epilogue[0];
segment.data[411] = epilogue[1];
segment.data[416] = epilogue[2];
// std::size_t source_pointer = 0;
// std::size_t destination_pointer = 3;
// while(source_pointer < 255) {
// encode_five_and_three_block(&segment.data[destination_pointer], &source[source_pointer]);
//
// source_pointer += 5;
// destination_pointer += 8;
// }
return segment;
}
Storage::Disk::PCMSegment AppleGCR::six_and_two_data(const uint8_t *source) {
Storage::Disk::PCMSegment segment;
segment.data.resize(349);
segment.number_of_bits = static_cast<unsigned int>(segment.data.size() * 8);
// Add the prologue and epilogue.
segment.data[0] = data_prologue[0];
segment.data[1] = data_prologue[1];
segment.data[2] = data_prologue[2];
segment.data[346] = epilogue[0];
segment.data[347] = epilogue[1];
segment.data[348] = epilogue[2];
// Fill in byte values: the first 86 bytes contain shuffled
// and combined copies of the bottom two bits of the sector
// contents; the 256 bytes afterwards are the remaining
// six bits.
const uint8_t bit_shuffle[] = {0, 2, 1, 3};
for(std::size_t c = 0; c < 84; ++c) {
segment.data[3 + c] = bit_shuffle[source[c]&3];
if(c + 86 < 256) segment.data[3 + c] |= bit_shuffle[source[c + 86]&3] << 2;
if(c + 172 < 256) segment.data[3 + c] |= bit_shuffle[source[c + 172]&3] << 4;
}
for(std::size_t c = 0; c < 256; ++c) {
segment.data[3 + 85 + 1 + c] = source[c] >> 2;
}
// Exclusive OR each byte with the one before it.
segment.data[345] = segment.data[344];
std::size_t location = 344;
while(location > 3) {
segment.data[location] ^= segment.data[location-1];
--location;
}
// Map six-bit values up to full bytes.
for(std::size_t c = 0; c < 343; ++c) {
segment.data[3 + c] = six_and_two_mapping[segment.data[3 + c]];
}
return segment;
}

View File

@ -0,0 +1,73 @@
//
// AppleGCR.hpp
// Clock Signal
//
// Created by Thomas Harte on 21/04/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef AppleGCR_hpp
#define AppleGCR_hpp
#include <cstdint>
#include "../../Disk/Track/PCMSegment.hpp"
namespace Storage {
namespace Encodings {
namespace AppleGCR {
/*!
@returns the eight-bit 13-sector GCR encoding for the low five bits of @c value.
*/
// unsigned int five_and_three_encoding_for_value(int value);
/*!
@returns the eight-bit 16-sector GCR encoding for the low six bits of @c value.
*/
// unsigned int six_and_two_encoding_for_value(int value);
/*!
A block is defined to be five source bytes, which encodes to eight GCR bytes.
*/
// void encode_five_and_three_block(uint8_t *destination, uint8_t *source);
/*!
A block is defined to be three source bytes, which encodes to four GCR bytes.
*/
// void encode_six_and_two_block(uint8_t *destination, uint8_t *source);
/*!
@returns the four bit nibble for the five-bit GCR @c quintet if a valid GCR value; INT_MAX otherwise.
*/
// unsigned int decoding_from_quintet(unsigned int quintet);
/*!
@returns the byte composed by splitting the dectet into two qintets, decoding each and composing the resulting nibbles.
*/
// unsigned int decoding_from_dectet(unsigned int dectet);
/// Describes the standard three-byte prologue that begins a header.
const uint8_t header_prologue[3] = {0xd5, 0xaa, 0x96};
/// Describes the standard three-byte prologue that begins a data section.
const uint8_t data_prologue[3] = {0xd5, 0xaa, 0xad};
/// Describes the epilogue that ends both data sections and headers.
const uint8_t epilogue[3] = {0xde, 0xaa, 0xeb};
/*!
Produces the Apple-standard '4 and 4' per-sector header. This is the same
for both the 13- and 16-sector formats, and is 112 bits long.
*/
Storage::Disk::PCMSegment header(uint8_t volume, uint8_t track, uint8_t sector);
Storage::Disk::PCMSegment six_and_two_data(const uint8_t *source);
Storage::Disk::PCMSegment six_and_two_sync(int length);
Storage::Disk::PCMSegment five_and_three_data(const uint8_t *source);
Storage::Disk::PCMSegment five_and_three_sync(int length);
}
}
}
#endif /* AppleGCR_hpp */

View File

@ -43,7 +43,7 @@ namespace CommodoreGCR {
unsigned int decoding_from_quintet(unsigned int quintet);
/*!
@returns the byte composted of the low five bit five-bit GCR
@returns the byte composed by splitting the dectet into two qintets, decoding each and composing the resulting nibbles.
*/
unsigned int decoding_from_dectet(unsigned int dectet);
}

View File

@ -32,7 +32,7 @@ void Parser::install_sectors_from_track(const Storage::Disk::Track::Address &add
is_mfm_);
std::map<int, Storage::Encodings::MFM::Sector> sectors_by_id;
for(auto &sector : sectors) {
for(const auto &sector : sectors) {
sectors_by_id.insert(std::make_pair(sector.second.address.sector, std::move(sector.second)));
}
sectors_by_address_by_track_.insert(std::make_pair(address, std::move(sectors_by_id)));

View File

@ -43,6 +43,36 @@ void PCMSegmentEventSource::reset() {
next_event_.type = Track::Event::FluxTransition;
}
PCMSegment &PCMSegment::operator +=(const PCMSegment &rhs) {
if(!rhs.number_of_bits) return *this;
if(number_of_bits&7) {
auto target_number_of_bits = number_of_bits + rhs.number_of_bits;
data.resize((target_number_of_bits + 7) >> 3);
std::size_t first_byte = number_of_bits >> 3;
int shift = number_of_bits&7;
data[first_byte] |= rhs.data[0] >> shift;
for(std::size_t target = first_byte+1; target < (data.size()-1); ++target) {
data[target] =
static_cast<uint8_t>(
(rhs.data[target - first_byte - 1] << (8 - shift)) |
(rhs.data[target - first_byte] >> shift)
);
}
data.back() = static_cast<uint8_t>(rhs.data.back() << (8 - shift));
number_of_bits = target_number_of_bits;
} else {
data.insert(data.end(), rhs.data.begin(), rhs.data.end());
number_of_bits += rhs.number_of_bits;
}
return *this;
}
Storage::Disk::Track::Event PCMSegmentEventSource::get_next_event() {
// track the initial bit pointer for potentially considering whether this was an
// initial index hole or a subsequent one later on
@ -106,7 +136,7 @@ Storage::Time PCMSegmentEventSource::seek_to(const Time &time_from_start) {
// bit_pointer_ always records _the next bit_ that might trigger an event,
// so should be one beyond the one reached by a seek.
Time relative_time = time_from_start - half_bit_length;
bit_pointer_ = 1 + (relative_time / segment_->length_of_a_bit).get_unsigned_int();
bit_pointer_ = 1 + (relative_time / segment_->length_of_a_bit).get<unsigned int>();
// map up to the correct amount of time
return half_bit_length + segment_->length_of_a_bit * static_cast<unsigned int>(bit_pointer_ - 1);

View File

@ -25,7 +25,7 @@ namespace Disk {
Bits from each byte are taken MSB to LSB.
*/
struct PCMSegment {
Time length_of_a_bit;
Time length_of_a_bit = Time(1);
unsigned int number_of_bits = 0;
std::vector<uint8_t> data;
@ -41,6 +41,8 @@ struct PCMSegment {
number_of_bits = 0;
data.clear();
}
PCMSegment &operator +=(const PCMSegment &rhs);
};
/*!

View File

@ -16,14 +16,14 @@ PCMTrack::PCMTrack() : segment_pointer_(0) {}
PCMTrack::PCMTrack(const std::vector<PCMSegment> &segments) : PCMTrack() {
// sum total length of all segments
Time total_length;
for(auto segment : segments) {
for(const auto &segment : segments) {
total_length += segment.length_of_a_bit * segment.number_of_bits;
}
total_length.simplify();
// each segment is then some proportion of the total; for them all to sum to 1 they'll
// need to be adjusted to be
for(auto segment : segments) {
for(const auto &segment : segments) {
Time original_length_of_segment = segment.length_of_a_bit * segment.number_of_bits;
Time proportion_of_whole = original_length_of_segment / total_length;
proportion_of_whole.simplify();

View File

@ -40,7 +40,7 @@ Storage::Disk::PCMSegment Storage::Disk::track_serialisation(Track &track, Time
Time extended_length = next_event.length * length_multiplier + time_error;
time_error.clock_rate = extended_length.clock_rate;
time_error.length = extended_length.length % extended_length.clock_rate;
pll.run_for(Cycles(static_cast<int>(extended_length.get_unsigned_int())));
pll.run_for(Cycles(extended_length.get<int>()));
pll.add_pulse();
// If the PLL is now sufficiently primed, restart, and start recording bits this time.

View File

@ -38,52 +38,52 @@ FileHolder::FileHolder(const std::string &file_name, FileMode ideal_mode)
break;
}
if(!file_) throw ErrorCantOpen;
if(!file_) throw Error::CantOpen;
}
uint32_t FileHolder::get32le() {
uint32_t result = (uint32_t)std::fgetc(file_);
result |= (uint32_t)(std::fgetc(file_) << 8);
result |= (uint32_t)(std::fgetc(file_) << 16);
result |= (uint32_t)(std::fgetc(file_) << 24);
uint32_t result = static_cast<uint32_t>(std::fgetc(file_));
result |= static_cast<uint32_t>(std::fgetc(file_)) << 8;
result |= static_cast<uint32_t>(std::fgetc(file_)) << 16;
result |= static_cast<uint32_t>(std::fgetc(file_)) << 24;
return result;
}
uint32_t FileHolder::get32be() {
uint32_t result = (uint32_t)(std::fgetc(file_) << 24);
result |= (uint32_t)(std::fgetc(file_) << 16);
result |= (uint32_t)(std::fgetc(file_) << 8);
result |= (uint32_t)std::fgetc(file_);
uint32_t result = static_cast<uint32_t>(std::fgetc(file_)) << 24;
result |= static_cast<uint32_t>(std::fgetc(file_)) << 16;
result |= static_cast<uint32_t>(std::fgetc(file_)) << 8;
result |= static_cast<uint32_t>(std::fgetc(file_));
return result;
}
uint32_t FileHolder::get24le() {
uint32_t result = (uint32_t)std::fgetc(file_);
result |= (uint32_t)(std::fgetc(file_) << 8);
result |= (uint32_t)(std::fgetc(file_) << 16);
uint32_t result = static_cast<uint32_t>(std::fgetc(file_));
result |= static_cast<uint32_t>(std::fgetc(file_)) << 8;
result |= static_cast<uint32_t>(std::fgetc(file_)) << 16;
return result;
}
uint32_t FileHolder::get24be() {
uint32_t result = (uint32_t)(std::fgetc(file_) << 16);
result |= (uint32_t)(std::fgetc(file_) << 8);
result |= (uint32_t)std::fgetc(file_);
uint32_t result = static_cast<uint32_t>(std::fgetc(file_)) << 16;
result |= static_cast<uint32_t>(std::fgetc(file_)) << 8;
result |= static_cast<uint32_t>(std::fgetc(file_));
return result;
}
uint16_t FileHolder::get16le() {
uint16_t result = static_cast<uint16_t>(std::fgetc(file_));
result |= static_cast<uint16_t>(std::fgetc(file_) << 8);
result |= static_cast<uint16_t>(static_cast<uint16_t>(std::fgetc(file_)) << 8);
return result;
}
uint16_t FileHolder::get16be() {
uint16_t result = static_cast<uint16_t>(std::fgetc(file_) << 8);
uint16_t result = static_cast<uint16_t>(static_cast<uint16_t>(std::fgetc(file_)) << 8);
result |= static_cast<uint16_t>(std::fgetc(file_));
return result;

View File

@ -20,8 +20,8 @@ namespace Storage {
class FileHolder final {
public:
enum {
ErrorCantOpen = -1
enum class Error {
CantOpen = -1
};
enum class FileMode {

View File

@ -47,12 +47,8 @@ struct Time {
/*!
@returns the floating point conversion of this @c Time. This will often be less precise.
*/
inline float get_float() const {
return static_cast<float>(length) / static_cast<float>(clock_rate);
}
inline unsigned int get_unsigned_int() const {
return length / clock_rate;
template <typename T> T get() const {
return static_cast<T>(length) / static_cast<T>(clock_rate);
}
inline bool operator < (const Time &other) const {

View File

@ -190,7 +190,7 @@ void TZX::get_generalised_segment(uint32_t output_symbols, uint8_t max_pulses_pe
}
// Output waves.
for(auto length : symbol.pulse_lengths) {
for(const auto length : symbol.pulse_lengths) {
if(!length) break;
post_pulse(length);
}

View File

@ -75,7 +75,7 @@ Shifter::Shifter() :
}
void Shifter::process_pulse(const Storage::Tape::Tape::Pulse &pulse) {
pll_.run_for(Cycles(static_cast<int>(static_cast<float>(PLLClockRate) * pulse.length.get_float())));
pll_.run_for(Cycles(static_cast<int>(static_cast<float>(PLLClockRate) * pulse.length.get<float>())));
bool is_high = pulse.type == Storage::Tape::Tape::Pulse::High;
if(is_high != was_high_) {

View File

@ -279,7 +279,7 @@ void Parser::process_pulse(const Storage::Tape::Tape::Pulse &pulse)
wave_period_ = 0.0f;
}
wave_period_ += pulse.length.get_float();
wave_period_ += pulse.length.get<float>();
previous_was_high_ = is_high;
}

View File

@ -62,7 +62,7 @@ void Parser::process_pulse(const Storage::Tape::Tape::Pulse &pulse)
cycle_length_ = 0.0f;
}
wave_was_high_ = wave_is_high;
cycle_length_ += pulse.length.get_float();
cycle_length_ += pulse.length.get<float>();
}
void Parser::inspect_waves(const std::vector<WaveType> &waves)

View File

@ -31,7 +31,7 @@ void Parser::process_pulse(const Storage::Tape::Tape::Pulse &pulse) {
void Parser::post_pulse() {
const float expected_pulse_length = 300.0f / 1000000.0f;
const float expected_gap_length = 1300.0f / 1000000.0f;
float pulse_time = pulse_time_.get_float();
float pulse_time = pulse_time_.get<float>();
if(pulse_time > expected_gap_length * 1.25f) {
push_wave(WaveType::LongGap);

View File

@ -11,6 +11,7 @@
#include <algorithm>
#include <cassert>
#include <cmath>
using namespace Storage;
@ -54,7 +55,7 @@ unsigned int TimedEventLoop::get_input_clock_rate() {
}
void TimedEventLoop::reset_timer() {
subcycles_until_event_.set_zero();
subcycles_until_event_ = 0.0;
cycles_until_event_ = 0;
}
@ -63,39 +64,22 @@ void TimedEventLoop::jump_to_next_event() {
process_next_event();
}
char text[256];
void TimedEventLoop::set_next_event_time_interval(Time interval) {
// Calculate [interval]*[input clock rate] + [subcycles until this event].
int64_t denominator = static_cast<int64_t>(interval.clock_rate) * static_cast<int64_t>(subcycles_until_event_.clock_rate);
int64_t numerator =
static_cast<int64_t>(subcycles_until_event_.clock_rate) * static_cast<int64_t>(input_clock_rate_) * static_cast<int64_t>(interval.length) +
static_cast<int64_t>(interval.clock_rate) * static_cast<int64_t>(subcycles_until_event_.length);
// Simplify if necessary: try just simplifying the interval and recalculating; if that doesn't
// work then try simplifying the whole thing.
if(numerator < 0 || denominator < 0 || denominator > std::numeric_limits<unsigned int>::max()) {
interval.simplify();
denominator = static_cast<int64_t>(interval.clock_rate) * static_cast<int64_t>(subcycles_until_event_.clock_rate);
numerator =
static_cast<int64_t>(subcycles_until_event_.clock_rate) * static_cast<int64_t>(input_clock_rate_) * static_cast<int64_t>(interval.length) +
static_cast<int64_t>(interval.clock_rate) * static_cast<int64_t>(subcycles_until_event_.length);
}
if(numerator < 0 || denominator < 0 || denominator > std::numeric_limits<unsigned int>::max()) {
int64_t common_divisor = NumberTheory::greatest_common_divisor(numerator % denominator, denominator);
denominator /= common_divisor;
numerator /= common_divisor;
}
// TODO: if that doesn't work then reduce precision.
// Calculate [interval]*[input clock rate] + [subcycles until this event]
double double_interval = interval.get<double>() * static_cast<double>(input_clock_rate_) + subcycles_until_event_;
// So this event will fire in the integral number of cycles from now, putting us at the remainder
// number of subcycles
assert(cycles_until_event_ == 0);
cycles_until_event_ += static_cast<int>(numerator / denominator);
const int addition = static_cast<int>(double_interval);
cycles_until_event_ += addition;
subcycles_until_event_ = fmod(double_interval, 1.0);
assert(cycles_until_event_ >= 0);
subcycles_until_event_.length = static_cast<unsigned int>(numerator % denominator);
subcycles_until_event_.clock_rate = static_cast<unsigned int>(denominator);
subcycles_until_event_.simplify();
assert(subcycles_until_event_ >= 0.0);
sprintf(text, " + %0.8f -> %d / %0.4f", interval.get<double>(), cycles_until_event_, subcycles_until_event_);
}
Time TimedEventLoop::get_time_into_next_event() {

View File

@ -102,7 +102,7 @@ namespace Storage {
private:
unsigned int input_clock_rate_ = 0;
int cycles_until_event_ = 0;
Time subcycles_until_event_;
double subcycles_until_event_ = 0.0;
};
}