1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-02-22 11:29:20 +00:00

Introdice alternative tape timings for the +4.

This commit is contained in:
Thomas Harte 2025-01-15 22:11:26 -05:00
parent 3adf3dc547
commit a6e453a452
16 changed files with 135 additions and 65 deletions

View File

@ -18,6 +18,9 @@ MultiMediaTarget::MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::
} }
bool MultiMediaTarget::insert_media(const Analyser::Static::Media &media) { bool MultiMediaTarget::insert_media(const Analyser::Static::Media &media) {
// TODO: copy media afresh for each target machine; media
// generally has mutable state.
bool inserted = false; bool inserted = false;
for(const auto &target : targets_) { for(const auto &target : targets_) {
inserted |= target->insert_media(media); inserted |= target->insert_media(media);

View File

@ -127,9 +127,9 @@ public:
potential_platforms_ |= platforms; potential_platforms_ |= platforms;
// Check whether the instance itself has any input on target platforms. // Check whether the instance itself has any input on target platforms.
TargetPlatform::TypeDistinguisher *const distinguisher = TargetPlatform::Distinguisher *const distinguisher =
dynamic_cast<TargetPlatform::TypeDistinguisher *>(instance.get()); dynamic_cast<TargetPlatform::Distinguisher *>(instance.get());
if(distinguisher) potential_platforms_ &= distinguisher->target_platform_type(); if(distinguisher) potential_platforms_ &= distinguisher->target_platforms();
} }
/// Concstructs a new instance of @c InstanceT supplying @c args and adds it to the back of @c list using @c insert_instance. /// Concstructs a new instance of @c InstanceT supplying @c args and adds it to the back of @c list using @c insert_instance.

View File

@ -14,6 +14,7 @@
#include "../../Storage/Disk/Disk.hpp" #include "../../Storage/Disk/Disk.hpp"
#include "../../Storage/MassStorage/MassStorageDevice.hpp" #include "../../Storage/MassStorage/MassStorageDevice.hpp"
#include "../../Storage/Tape/Tape.hpp" #include "../../Storage/Tape/Tape.hpp"
#include "../../Storage/TargetPlatforms.hpp"
#include "../../Reflection/Struct.hpp" #include "../../Reflection/Struct.hpp"
#include <memory> #include <memory>
@ -37,6 +38,20 @@ struct Media {
return disks.empty() && tapes.empty() && cartridges.empty() && mass_storage_devices.empty(); return disks.empty() && tapes.empty() && cartridges.empty() && mass_storage_devices.empty();
} }
void set_target_platforms(const TargetPlatform::Type target) {
const auto propagate = [&](const auto &list) {
for(const auto &item: list) {
if(auto *recipient = dynamic_cast<TargetPlatform::Recipient *>(item.get())) {
recipient->set_target_platforms(target);
}
}
};
propagate(disks);
propagate(tapes);
propagate(cartridges);
propagate(mass_storage_devices);
}
Media &operator +=(const Media &rhs) { Media &operator +=(const Media &rhs) {
const auto append = [&](auto &destination, auto &source) { const auto append = [&](auto &destination, auto &source) {
destination.insert(destination.end(), source.begin(), source.end()); destination.insert(destination.end(), source.begin(), source.end());

View File

@ -330,6 +330,10 @@ public:
*value = 0xff ^ (play_button_ ? 0x4 :0x0); *value = 0xff ^ (play_button_ ? 0x4 :0x0);
break; break;
case 0xfdd0:
*value = 0xff;
break;
default: default:
printf("TODO: read @ %04x\n", address); printf("TODO: read @ %04x\n", address);
break; break;
@ -340,6 +344,12 @@ public:
keyboard_mask_ = *value; keyboard_mask_ = *value;
break; break;
case 0xfdd0: {
// const auto low = address & 3;
// const auto high = (address >> 2) & 3;
// TODO: set up ROMs.
} break;
default: default:
printf("TODO: write of %02x @ %04x\n", *value, address); printf("TODO: write of %02x @ %04x\n", *value, address);
break; break;

View File

@ -57,7 +57,7 @@ public:
case 0xff1a: return uint8_t(character_position_reload_ >> 8) | 0xfc; case 0xff1a: return uint8_t(character_position_reload_ >> 8) | 0xfc;
case 0xff1b: return uint8_t(character_position_reload_); case 0xff1b: return uint8_t(character_position_reload_);
case 0xff1c: return uint8_t(vertical_counter_ >> 8); case 0xff1c: return uint8_t(vertical_counter_ >> 8) | 0xfe;
case 0xff1d: return uint8_t(vertical_counter_); case 0xff1d: return uint8_t(vertical_counter_);
case 0xff1e: return uint8_t(horizontal_counter_ >> 1); case 0xff1e: return uint8_t(horizontal_counter_ >> 1);
case 0xff1f: case 0xff1f:
@ -163,7 +163,7 @@ public:
case 0xff1a: load_high10(character_position_reload_); break; case 0xff1a: load_high10(character_position_reload_); break;
case 0xff1b: load_low8(character_position_reload_); break; case 0xff1b: load_low8(character_position_reload_); break;
case 0xff1c: vertical_counter_ = (vertical_counter_ & 0x00ff) | ((value & 3) << 8); break; case 0xff1c: vertical_counter_ = (vertical_counter_ & 0x00ff) | ((value & 1) << 8); break;
case 0xff1d: vertical_counter_ = (vertical_counter_ & 0xff00) | value; break; case 0xff1d: vertical_counter_ = (vertical_counter_ & 0xff00) | value; break;
case 0xff1e: case 0xff1e:
// TODO: possibly should be deferred, if falling out of phase? // TODO: possibly should be deferred, if falling out of phase?

View File

@ -20,7 +20,7 @@ namespace MachineTypes {
*/ */
struct MediaTarget { struct MediaTarget {
/*! /*!
Requests that the machine insert @c media as a modification to current state Requests that the machine insert @c media as a modification to current state.
@returns @c true if any media was inserted; @c false otherwise. @returns @c true if any media was inserted; @c false otherwise.
*/ */

View File

@ -44,13 +44,19 @@ enum class Error {
receive the supplied static analyser result. The machine has been allocated receive the supplied static analyser result. The machine has been allocated
on the heap. It is the caller's responsibility to delete the class when finished. on the heap. It is the caller's responsibility to delete the class when finished.
*/ */
std::unique_ptr<DynamicMachine> MachineForTargets(const Analyser::Static::TargetList &targets, const ::ROMMachine::ROMFetcher &rom_fetcher, Error &error); std::unique_ptr<DynamicMachine> MachineForTargets(
const Analyser::Static::TargetList &,
const ::ROMMachine::ROMFetcher &,
Error &);
/*! /*!
Allocates an instance of DynamicMaachine holding the machine described Allocates an instance of DynamicMaachine holding the machine described
by @c target. It is the caller's responsibility to delete the class when finished. by @c target. It is the caller's responsibility to delete the class when finished.
*/ */
std::unique_ptr<DynamicMachine> MachineForTarget(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher, Machine::Error &error); std::unique_ptr<DynamicMachine> MachineForTarget(
const Analyser::Static::Target *,
const ROMMachine::ROMFetcher &,
Machine::Error &);
/*! /*!
Returns a short string name for the machine identified by the target, Returns a short string name for the machine identified by the target,
@ -90,6 +96,7 @@ std::map<std::string, std::unique_ptr<Reflection::Struct>> AllOptionsByMachineNa
NB: Usually the instances of Target can be dynamic_casted to Reflection::Struct in order to determine available properties. NB: Usually the instances of Target can be dynamic_casted to Reflection::Struct in order to determine available properties.
*/ */
std::map<std::string, std::unique_ptr<Analyser::Static::Target>> TargetsByMachineName(bool meaningful_without_media_only); std::map<std::string, std::unique_ptr<Analyser::Static::Target>>
TargetsByMachineName(bool meaningful_without_media_only);
} }

View File

@ -95,7 +95,7 @@ class DiskImageHolderBase: public Disk {
Implements TargetPlatform::TypeDistinguisher to return either no information whatsoever, if Implements TargetPlatform::TypeDistinguisher to return either no information whatsoever, if
the underlying image doesn't implement TypeDistinguisher, or else to pass the call along. the underlying image doesn't implement TypeDistinguisher, or else to pass the call along.
*/ */
template <typename T> class DiskImageHolder: public DiskImageHolderBase, public TargetPlatform::TypeDistinguisher { template <typename T> class DiskImageHolder: public DiskImageHolderBase, public TargetPlatform::Distinguisher {
public: public:
template <typename... Ts> DiskImageHolder(Ts&&... args) : template <typename... Ts> DiskImageHolder(Ts&&... args) :
disk_image_(args...) {} disk_image_(args...) {}
@ -112,9 +112,9 @@ public:
private: private:
T disk_image_; T disk_image_;
TargetPlatform::Type target_platform_type() final { TargetPlatform::Type target_platforms() final {
if constexpr (std::is_base_of<TargetPlatform::TypeDistinguisher, T>::value) { if constexpr (std::is_base_of<TargetPlatform::Distinguisher, T>::value) {
return static_cast<TargetPlatform::TypeDistinguisher *>(&disk_image_)->target_platform_type(); return static_cast<TargetPlatform::Distinguisher *>(&disk_image_)->target_platforms();
} else { } else {
return TargetPlatform::Type(~0); return TargetPlatform::Type(~0);
} }

View File

@ -24,7 +24,7 @@ namespace Storage::Disk {
of which is variably clocked (albeit not at flux transition resolution; as a result IPF files tend to be of which is variably clocked (albeit not at flux transition resolution; as a result IPF files tend to be
close in size to more primitive formats). close in size to more primitive formats).
*/ */
class IPF: public DiskImage, public TargetPlatform::TypeDistinguisher { class IPF: public DiskImage, public TargetPlatform::Distinguisher {
public: public:
/*! /*!
Construct an @c IPF containing content from the file with name @c file_name. Construct an @c IPF containing content from the file with name @c file_name.
@ -72,7 +72,7 @@ private:
std::map<Track::Address, TrackDescription> tracks_; std::map<Track::Address, TrackDescription> tracks_;
bool is_sps_format_ = false; bool is_sps_format_ = false;
TargetPlatform::Type target_platform_type() final { TargetPlatform::Type target_platforms() final {
return TargetPlatform::Type(platform_type_); return TargetPlatform::Type(platform_type_);
} }
TargetPlatform::IntType platform_type_ = TargetPlatform::Amiga; TargetPlatform::IntType platform_type_ = TargetPlatform::Amiga;

View File

@ -51,7 +51,8 @@ using namespace Storage::Tape;
PRG::PRG(const std::string &file_name) : Tape(serialiser_), serialiser_(file_name) {} PRG::PRG(const std::string &file_name) : Tape(serialiser_), serialiser_(file_name) {}
PRG::Serialiser::Serialiser(const std::string &file_name) : PRG::Serialiser::Serialiser(const std::string &file_name) :
file_(file_name, FileHolder::FileMode::Read) file_(file_name, FileHolder::FileMode::Read),
timings_(false)
{ {
// There's really no way to validate other than that if this file is larger than 64kb, // There's really no way to validate other than that if this file is larger than 64kb,
// of if load address + length > 65536 then it's broken. // of if load address + length > 65536 then it's broken.
@ -65,13 +66,15 @@ PRG::Serialiser::Serialiser(const std::string &file_name) :
throw ErrorBadFormat; throw ErrorBadFormat;
} }
Storage::Tape::Pulse PRG::Serialiser::next_pulse() { void PRG::set_target_platforms(TargetPlatform::Type type) {
// The below are in microseconds per pole. serialiser_.set_target_platforms(type);
constexpr unsigned int leader_zero_length = 179; }
constexpr unsigned int zero_length = 169;
constexpr unsigned int one_length = 247;
constexpr unsigned int marker_length = 328;
void PRG::Serialiser::set_target_platforms(TargetPlatform::Type type) {
timings_ = Timings(type & TargetPlatform::Type::Plus4);
}
Storage::Tape::Pulse PRG::Serialiser::next_pulse() {
bit_phase_ = (bit_phase_ + 1)&3; bit_phase_ = (bit_phase_ + 1)&3;
if(!bit_phase_) get_next_output_token(); if(!bit_phase_) get_next_output_token();
@ -79,11 +82,11 @@ Storage::Tape::Pulse PRG::Serialiser::next_pulse() {
pulse.length.clock_rate = 1'000'000; pulse.length.clock_rate = 1'000'000;
pulse.type = (bit_phase_&1) ? Pulse::High : Pulse::Low; pulse.type = (bit_phase_&1) ? Pulse::High : Pulse::Low;
switch(output_token_) { switch(output_token_) {
case Leader: pulse.length.length = leader_zero_length; break; case Leader: pulse.length.length = timings_.leader_zero_length; break;
case Zero: pulse.length.length = (bit_phase_&2) ? one_length : zero_length; break; case Zero: pulse.length.length = (bit_phase_&2) ? timings_.one_length : timings_.zero_length; break;
case One: pulse.length.length = (bit_phase_&2) ? zero_length : one_length; break; case One: pulse.length.length = (bit_phase_&2) ? timings_.zero_length : timings_.one_length; break;
case WordMarker: pulse.length.length = (bit_phase_&2) ? one_length : marker_length; break; case WordMarker: pulse.length.length = (bit_phase_&2) ? timings_.one_length : timings_.marker_length; break;
case EndOfBlock: pulse.length.length = (bit_phase_&2) ? zero_length : marker_length; break; case EndOfBlock: pulse.length.length = (bit_phase_&2) ? timings_.zero_length : timings_.marker_length; break;
case Silence: pulse.type = Pulse::Zero; pulse.length.length = 5000; break; case Silence: pulse.type = Pulse::Zero; pulse.length.length = 5000; break;
} }
return pulse; return pulse;

View File

@ -10,6 +10,7 @@
#include "../Tape.hpp" #include "../Tape.hpp"
#include "../../FileHolder.hpp" #include "../../FileHolder.hpp"
#include "../../TargetPlatforms.hpp"
#include <cstdint> #include <cstdint>
#include <string> #include <string>
@ -19,7 +20,7 @@ namespace Storage::Tape {
/*! /*!
Provides a @c Tape containing a .PRG, which is a direct local file. Provides a @c Tape containing a .PRG, which is a direct local file.
*/ */
class PRG: public Tape { class PRG: public Tape, public TargetPlatform::Recipient {
public: public:
/*! /*!
Constructs a @c T64 containing content from the file with name @c file_name, of type @c type. Constructs a @c T64 containing content from the file with name @c file_name, of type @c type.
@ -34,8 +35,11 @@ public:
}; };
private: private:
void set_target_platforms(TargetPlatform::Type) override;
struct Serialiser: public TapeSerialiser { struct Serialiser: public TapeSerialiser {
Serialiser(const std::string &file_name); Serialiser(const std::string &file_name);
void set_target_platforms(TargetPlatform::Type);
private: private:
bool is_at_end() const override; bool is_at_end() const override;
Pulse next_pulse() override; Pulse next_pulse() override;
@ -68,6 +72,20 @@ private:
uint8_t output_byte_; uint8_t output_byte_;
uint8_t check_digit_; uint8_t check_digit_;
uint8_t copy_mask_ = 0x80; uint8_t copy_mask_ = 0x80;
struct Timings {
Timings(bool is_plus4) :
leader_zero_length( is_plus4 ? 240 : 179),
zero_length( is_plus4 ? 240 : 169),
one_length( is_plus4 ? 480 : 247),
marker_length( is_plus4 ? 960 : 328) {}
// The below are in microseconds per pole.
unsigned int leader_zero_length;
unsigned int zero_length;
unsigned int one_length;
unsigned int marker_length;
} timings_;
} serialiser_; } serialiser_;
}; };

View File

@ -323,7 +323,7 @@ void UEF::Serialiser::queue_bit(const int bit) {
// MARK: - TypeDistinguisher // MARK: - TypeDistinguisher
TargetPlatform::Type UEF::target_platform_type() { TargetPlatform::Type UEF::target_platforms() {
return serialiser_.target_platform_type(); return serialiser_.target_platform_type();
} }

View File

@ -21,7 +21,7 @@ namespace Storage::Tape {
/*! /*!
Provides a @c Tape containing a UEF tape image, a slightly-convoluted description of pulses. Provides a @c Tape containing a UEF tape image, a slightly-convoluted description of pulses.
*/ */
class UEF : public Tape, public TargetPlatform::TypeDistinguisher { class UEF : public Tape, public TargetPlatform::Distinguisher {
public: public:
/*! /*!
Constructs a @c UEF containing content from the file with name @c file_name. Constructs a @c UEF containing content from the file with name @c file_name.
@ -35,7 +35,7 @@ public:
}; };
private: private:
TargetPlatform::Type target_platform_type() override; TargetPlatform::Type target_platforms() override;
struct Serialiser: public PulseQueuedSerialiser { struct Serialiser: public PulseQueuedSerialiser {
Serialiser(const std::string &file_name); Serialiser(const std::string &file_name);

View File

@ -105,7 +105,7 @@ Pulse ZX80O81P::Serialiser::next_pulse() {
return pulse; return pulse;
} }
TargetPlatform::Type ZX80O81P::target_platform_type() { TargetPlatform::Type ZX80O81P::target_platforms() {
return serialiser_.target_platform_type(); return serialiser_.target_platform_type();
} }

View File

@ -22,7 +22,7 @@ namespace Storage::Tape {
/*! /*!
Provides a @c Tape containing a ZX80-format .O tape image, which is a byte stream capture. Provides a @c Tape containing a ZX80-format .O tape image, which is a byte stream capture.
*/ */
class ZX80O81P: public Tape, public TargetPlatform::TypeDistinguisher { class ZX80O81P: public Tape, public TargetPlatform::Distinguisher {
public: public:
/*! /*!
Constructs a @c ZX80O containing content from the file with name @c file_name. Constructs a @c ZX80O containing content from the file with name @c file_name.
@ -37,7 +37,7 @@ public:
private: private:
// TargetPlatform::TypeDistinguisher. // TargetPlatform::TypeDistinguisher.
TargetPlatform::Type target_platform_type() override; TargetPlatform::Type target_platforms() override;
struct Serialiser: public TapeSerialiser { struct Serialiser: public TapeSerialiser {
Serialiser(const std::string &file_name); Serialiser(const std::string &file_name);

View File

@ -12,45 +12,59 @@ namespace TargetPlatform {
using IntType = int; using IntType = int;
constexpr IntType bit(int index) {
return 1 << index;
}
// The below is somehwat overspecified because some of the file formats already supported by this // The below is somehwat overspecified because some of the file formats already supported by this
// emulator can self-specify platforms beyond those the emulator otherwise implements. // emulator can self-specify platforms beyond those the emulator otherwise implements.
enum Type: IntType { enum Type: IntType {
AmstradCPC = 1 << 0, AcornAtom = bit(0),
AppleII = 1 << 1, AcornElectron = bit(1),
AppleIIgs = 1 << 2, Amiga = bit(2),
Atari2600 = 1 << 3, AmstradCPC = bit(3),
AtariST = 1 << 4, AppleII = bit(4),
AcornAtom = 1 << 5, AppleIIgs = bit(5),
AcornElectron = 1 << 6, Archimedes = bit(6),
Amiga = 1 << 7, Atari2600 = bit(7),
Archimedes = 1 << 8, AtariST = bit(8),
BBCMaster = 1 << 9, BBCMaster = bit(9),
BBCModelA = 1 << 10, BBCModelA = bit(10),
BBCModelB = 1 << 11, BBCModelB = bit(11),
Coleco = 1 << 12, C64 = bit(12),
Commodore = 1 << 13, Coleco = bit(13),
DiskII = 1 << 14, DiskII = bit(14),
Enterprise = 1 << 15, Enterprise = bit(15),
Sega = 1 << 16, FAT12 = bit(16),
Macintosh = 1 << 17, Macintosh = bit(17),
MSX = 1 << 18, MSX = bit(18),
Oric = 1 << 19, Oric = bit(19),
ZX80 = 1 << 20, PCCompatible = bit(20),
ZX81 = 1 << 21, Plus4 = bit(21),
ZXSpectrum = 1 << 22, Sega = bit(22),
PCCompatible = 1 << 23, Vic20 = bit(23),
FAT12 = 1 << 24, ZX80 = bit(24),
ZX81 = bit(25),
ZXSpectrum = bit(26),
Acorn = AcornAtom | AcornElectron | BBCMaster | BBCModelA | BBCModelB | Archimedes, Acorn = AcornAtom | AcornElectron | BBCMaster | BBCModelA | BBCModelB | Archimedes,
Commodore8bit = C64 | Plus4 | Vic20,
Commodore = Amiga | Commodore8bit,
ZX8081 = ZX80 | ZX81, ZX8081 = ZX80 | ZX81,
AllCartridge = Atari2600 | AcornElectron | Coleco | MSX, AllCartridge = Atari2600 | AcornElectron | Coleco | MSX,
AllDisk = Acorn | AmstradCPC | Commodore | Oric | MSX | ZXSpectrum | Macintosh | AtariST | DiskII | Amiga | PCCompatible | FAT12, AllDisk = Acorn | Commodore | AmstradCPC | C64 | Oric | MSX | ZXSpectrum | Macintosh | AtariST | DiskII | PCCompatible | FAT12,
AllTape = Acorn | AmstradCPC | Commodore | Oric | ZX8081 | MSX | ZXSpectrum, AllTape = Acorn | AmstradCPC | Commodore8bit | Oric | ZX8081 | MSX | ZXSpectrum,
}; };
class TypeDistinguisher { class Distinguisher {
public: public:
virtual Type target_platform_type() = 0; virtual Type target_platforms() = 0;
};
class Recipient {
public:
virtual void set_target_platforms(Type) = 0;
}; };
} }