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:
commit
ac4948c4b1
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
26
Analyser/Static/AppleII/Target.hpp
Normal file
26
Analyser/Static/AppleII/Target.hpp
Normal 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 */
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
186
Components/DiskII/DiskII.cpp
Normal file
186
Components/DiskII/DiskII.cpp
Normal 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];
|
||||
}
|
76
Components/DiskII/DiskII.hpp
Normal file
76
Components/DiskII/DiskII.hpp
Normal 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 */
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
28
Machines/AppleII/Card.hpp
Normal 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 */
|
69
Machines/AppleII/DiskIICard.cpp
Normal file
69
Machines/AppleII/DiskIICard.cpp
Normal 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);
|
||||
}
|
38
Machines/AppleII/DiskIICard.hpp
Normal file
38
Machines/AppleII/DiskIICard.hpp
Normal 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 */
|
@ -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;
|
||||
}
|
||||
|
@ -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" });
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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",
|
||||
{
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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++;
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ROMMachine {
|
||||
|
@ -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",
|
||||
{
|
||||
|
@ -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 */,
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
|
@ -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));
|
||||
|
@ -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];
|
||||
|
@ -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];
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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()) {
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -6,3 +6,4 @@ basic10.rom
|
||||
basic11.rom
|
||||
colour.rom
|
||||
microdisc.rom
|
||||
8dos.rom
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
78
Storage/Disk/DiskImage/Formats/AppleDSK.cpp
Normal file
78
Storage/Disk/DiskImage/Formats/AppleDSK.cpp
Normal 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);
|
||||
}
|
48
Storage/Disk/DiskImage/Formats/AppleDSK.hpp
Normal file
48
Storage/Disk/DiskImage/Formats/AppleDSK.hpp
Normal 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 */
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
||||
|
102
Storage/Disk/DiskImage/Formats/NIB.cpp
Normal file
102
Storage/Disk/DiskImage/Formats/NIB.cpp
Normal 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);
|
||||
}
|
40
Storage/Disk/DiskImage/Formats/NIB.hpp
Normal file
40
Storage/Disk/DiskImage/Formats/NIB.hpp
Normal 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 */
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
104
Storage/Disk/DiskImage/Formats/WOZ.cpp
Normal file
104
Storage/Disk/DiskImage/Formats/WOZ.cpp
Normal 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));
|
||||
}
|
42
Storage/Disk/DiskImage/Formats/WOZ.hpp
Normal file
42
Storage/Disk/DiskImage/Formats/WOZ.hpp
Normal 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 */
|
@ -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;
|
||||
}
|
||||
|
@ -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.
|
||||
|
182
Storage/Disk/Encodings/AppleGCR.cpp
Normal file
182
Storage/Disk/Encodings/AppleGCR.cpp
Normal 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;
|
||||
}
|
73
Storage/Disk/Encodings/AppleGCR.hpp
Normal file
73
Storage/Disk/Encodings/AppleGCR.hpp
Normal 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 */
|
@ -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);
|
||||
}
|
||||
|
@ -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 §or : sectors) {
|
||||
for(const auto §or : 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)));
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
/*!
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -20,8 +20,8 @@ namespace Storage {
|
||||
|
||||
class FileHolder final {
|
||||
public:
|
||||
enum {
|
||||
ErrorCantOpen = -1
|
||||
enum class Error {
|
||||
CantOpen = -1
|
||||
};
|
||||
|
||||
enum class FileMode {
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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_) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user