1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-09-12 02:24:31 +00:00

Compare commits

..

13 Commits

Author SHA1 Message Date
Thomas Harte
cad42beef4 Roll in some random style improvements. 2025-09-08 20:38:50 -04:00
Thomas Harte
96fd0b7892 Merge pull request #1553 from TomHarte/IndentationSomeMore
Further reduce indentation.
2025-09-05 23:20:19 -04:00
Thomas Harte
6f1db15d7c Further reduce indentation. 2025-09-05 23:07:45 -04:00
Thomas Harte
1854296ee8 Merge pull request #1552 from TomHarte/ElectronIDE
Reduce code duplication within the ROM catalogue.
2025-09-05 22:45:55 -04:00
Thomas Harte
515cc5f326 Correct spelling. 2025-09-05 22:09:38 -04:00
Thomas Harte
091be7eafe Remove unused header. 2025-09-05 22:03:45 -04:00
Thomas Harte
27a19ea417 Eliminate line-length violations. 2025-09-05 22:03:19 -04:00
Thomas Harte
9a5e9af67c Standardise layout. 2025-09-05 22:00:42 -04:00
Thomas Harte
3a493f2428 Merge pull request #1551 from TomHarte/LogLevels
Allow logging of errors but not info.
2025-09-05 21:04:56 -04:00
Thomas Harte
ca6e34f4b4 Fix dangling OpenGL accesses. 2025-09-05 19:30:33 -04:00
Thomas Harte
e1e68312c4 Transcribe remaining catalogue entries. 2025-09-05 17:23:38 -04:00
Thomas Harte
c7ff2cece4 Head in search of a more-compact form. 2025-09-05 16:55:00 -04:00
Thomas Harte
8e6f4fa36f Fix NDEBUG route. 2025-09-05 14:34:08 -04:00
31 changed files with 4762 additions and 3928 deletions

View File

@@ -23,10 +23,10 @@ struct Machine {
class Options: public Reflection::StructImpl<Options>, public Configurable::DisplayOption<Options> {
friend Configurable::DisplayOption<Options>;
public:
Options(Configurable::OptionsType type) :
Configurable::DisplayOption<Options>(type == Configurable::OptionsType::UserFriendly ?
Configurable::Display::SVideo : Configurable::Display::CompositeColour) {}
public:
Options(Configurable::OptionsType type) :
Configurable::DisplayOption<Options>(type == Configurable::OptionsType::UserFriendly ?
Configurable::Display::SVideo : Configurable::Display::CompositeColour) {}
private:
Options() : Options(Configurable::OptionsType::UserFriendly) {}

View File

@@ -44,37 +44,37 @@ namespace {
Log::Logger<Log::Source::Plus4> logger;
class Joystick: public Inputs::ConcreteJoystick {
public:
Joystick() :
ConcreteJoystick({
Input(Input::Up),
Input(Input::Down),
Input(Input::Left),
Input(Input::Right),
Input(Input::Fire)
}) {}
public:
Joystick() :
ConcreteJoystick({
Input(Input::Up),
Input(Input::Down),
Input(Input::Left),
Input(Input::Right),
Input(Input::Fire)
}) {}
void did_set_input(const Input &digital_input, bool is_active) final {
const auto apply = [&](uint8_t mask) {
if(is_active) mask_ &= ~mask; else mask_ |= mask;
};
void did_set_input(const Input &digital_input, bool is_active) final {
const auto apply = [&](uint8_t mask) {
if(is_active) mask_ &= ~mask; else mask_ |= mask;
};
switch(digital_input.type) {
default: return;
case Input::Right: apply(0x08); break;
case Input::Left: apply(0x04); break;
case Input::Down: apply(0x02); break;
case Input::Up: apply(0x01); break;
case Input::Fire: apply(0xc0); break;
}
switch(digital_input.type) {
default: return;
case Input::Right: apply(0x08); break;
case Input::Left: apply(0x04); break;
case Input::Down: apply(0x02); break;
case Input::Up: apply(0x01); break;
case Input::Fire: apply(0xc0); break;
}
}
uint8_t mask() const {
return mask_;
}
uint8_t mask() const {
return mask_;
}
private:
uint8_t mask_ = 0xff;
private:
uint8_t mask_ = 0xff;
};
class Timers {

View File

@@ -13,35 +13,35 @@
namespace MSX::Cartridge {
class ASCII16kbROMSlotHandler: public MemorySlotHandler {
public:
ASCII16kbROMSlotHandler(MSX::MemorySlot &slot) : slot_(slot) {}
public:
ASCII16kbROMSlotHandler(MSX::MemorySlot &slot) : slot_(slot) {}
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final {
switch(address >> 11) {
default:
if(pc_is_outside_bios) confidence_counter_.add_miss();
break;
case 0xc:
if(pc_is_outside_bios) {
if(address == 0x6000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
slot_.map(value * 0x4000, 0x4000, 0x4000);
break;
case 0xe:
if(pc_is_outside_bios) {
if(address == 0x7000 || address == 0x77ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
slot_.map(value * 0x4000, 0x8000, 0x4000);
break;
}
void write(const uint16_t address, const uint8_t value, const bool pc_is_outside_bios) final {
switch(address >> 11) {
default:
if(pc_is_outside_bios) confidence_counter_.add_miss();
break;
case 0xc:
if(pc_is_outside_bios) {
hit_or_equivocal(address == 0x6000);
}
slot_.map(value * 0x4000, 0x4000, 0x4000);
break;
case 0xe:
if(pc_is_outside_bios) {
hit_or_equivocal(address == 0x7000 || address == 0x77ff);
}
slot_.map(value * 0x4000, 0x8000, 0x4000);
break;
}
}
virtual std::string debug_type() final {
return "A16";
}
virtual std::string debug_type() final {
return "A16";
}
private:
MSX::MemorySlot &slot_;
private:
MSX::MemorySlot &slot_;
};
}

View File

@@ -13,47 +13,47 @@
namespace MSX::Cartridge {
class ASCII8kbROMSlotHandler: public MemorySlotHandler {
public:
ASCII8kbROMSlotHandler(MSX::MemorySlot &slot) : slot_(slot) {}
public:
ASCII8kbROMSlotHandler(MSX::MemorySlot &slot) : slot_(slot) {}
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final {
switch(address >> 11) {
default:
if(pc_is_outside_bios) confidence_counter_.add_miss();
break;
case 0xc:
if(pc_is_outside_bios) {
if(address == 0x6000 || address == 0x60ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
slot_.map(value * 0x2000, 0x4000, 0x2000);
break;
case 0xd:
if(pc_is_outside_bios) {
if(address == 0x6800 || address == 0x68ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
slot_.map(value * 0x2000, 0x6000, 0x2000);
break;
case 0xe:
if(pc_is_outside_bios) {
if(address == 0x7000 || address == 0x70ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
slot_.map(value * 0x2000, 0x8000, 0x2000);
break;
case 0xf:
if(pc_is_outside_bios) {
if(address == 0x7800 || address == 0x78ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
slot_.map(value * 0x2000, 0xa000, 0x2000);
break;
}
void write(const uint16_t address, const uint8_t value, const bool pc_is_outside_bios) final {
switch(address >> 11) {
default:
if(pc_is_outside_bios) confidence_counter_.add_miss();
break;
case 0xc:
if(pc_is_outside_bios) {
hit_or_equivocal(address == 0x6000 || address == 0x60ff);
}
slot_.map(value * 0x2000, 0x4000, 0x2000);
break;
case 0xd:
if(pc_is_outside_bios) {
hit_or_equivocal(address == 0x6800 || address == 0x68ff);
}
slot_.map(value * 0x2000, 0x6000, 0x2000);
break;
case 0xe:
if(pc_is_outside_bios) {
hit_or_equivocal(address == 0x7000 || address == 0x70ff);
}
slot_.map(value * 0x2000, 0x8000, 0x2000);
break;
case 0xf:
if(pc_is_outside_bios) {
hit_or_equivocal(address == 0x7800 || address == 0x78ff);
}
slot_.map(value * 0x2000, 0xa000, 0x2000);
break;
}
}
virtual std::string debug_type() final {
return "A8";
}
virtual std::string debug_type() final {
return "A8";
}
private:
MSX::MemorySlot &slot_;
private:
MSX::MemorySlot &slot_;
};
}

View File

@@ -13,39 +13,40 @@
namespace MSX::Cartridge {
class KonamiROMSlotHandler: public MemorySlotHandler {
public:
KonamiROMSlotHandler(MSX::MemorySlot &slot) : slot_(slot) {}
public:
KonamiROMSlotHandler(MSX::MemorySlot &slot) : slot_(slot) {}
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final {
switch(address >> 13) {
default:
if(pc_is_outside_bios) confidence_counter_.add_miss();
break;
case 3:
if(pc_is_outside_bios) {
if(address == 0x6000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
slot_.map(value * 0x2000, 0x6000, 0x2000);
break;
case 4:
if(pc_is_outside_bios) {
if(address == 0x8000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
slot_.map(value * 0x2000, 0x8000, 0x2000);
break;
case 5:
if(pc_is_outside_bios) {
if(address == 0xa000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
slot_.map(value * 0x2000, 0xa000, 0x2000);
break;
}
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final {
switch(address >> 13) {
default:
if(pc_is_outside_bios) confidence_counter_.add_miss();
break;
case 3:
if(pc_is_outside_bios) {
hit_or_equivocal(address == 0x6000);
}
slot_.map(value * 0x2000, 0x6000, 0x2000);
break;
case 4:
if(pc_is_outside_bios) {
hit_or_equivocal(address == 0x8000);
}
slot_.map(value * 0x2000, 0x8000, 0x2000);
break;
case 5:
if(pc_is_outside_bios) {
hit_or_equivocal(address == 0xa000);
}
slot_.map(value * 0x2000, 0xa000, 0x2000);
break;
}
}
virtual std::string debug_type() final {
return "K";
}
private:
virtual std::string debug_type() final {
return "K";
}
private:
MSX::MemorySlot &slot_;
};

View File

@@ -14,73 +14,73 @@
namespace MSX::Cartridge {
class KonamiWithSCCROMSlotHandler: public MemorySlotHandler {
public:
KonamiWithSCCROMSlotHandler(MSX::MemorySlot &slot, Konami::SCC &scc) :
slot_(slot), scc_(scc) {}
public:
KonamiWithSCCROMSlotHandler(MSX::MemorySlot &slot, Konami::SCC &scc) :
slot_(slot), scc_(scc) {}
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final {
switch(address >> 11) {
default:
void write(const uint16_t address, const uint8_t value, const bool pc_is_outside_bios) final {
switch(address >> 11) {
default:
if(pc_is_outside_bios) confidence_counter_.add_miss();
break;
case 0x0a:
if(pc_is_outside_bios) {
hit_or_equivocal(address == 0x5000);
}
slot_.map(value * 0x2000, 0x4000, 0x2000);
break;
case 0x0e:
if(pc_is_outside_bios) {
hit_or_equivocal(address == 0x7000);
}
slot_.map(value * 0x2000, 0x6000, 0x2000);
break;
case 0x12:
if(pc_is_outside_bios) {
hit_or_equivocal(address == 0x9000);
}
if((value&0x3f) == 0x3f) {
scc_is_visible_ = true;
slot_.map_handler(0x8000, 0x2000);
} else {
scc_is_visible_ = false;
slot_.map(value * 0x2000, 0x8000, 0x2000);
}
break;
case 0x13:
if(scc_is_visible_) {
if(pc_is_outside_bios) confidence_counter_.add_hit();
scc_.write(address, value);
} else {
if(pc_is_outside_bios) confidence_counter_.add_miss();
break;
case 0x0a:
if(pc_is_outside_bios) {
if(address == 0x5000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
slot_.map(value * 0x2000, 0x4000, 0x2000);
break;
case 0x0e:
if(pc_is_outside_bios) {
if(address == 0x7000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
slot_.map(value * 0x2000, 0x6000, 0x2000);
break;
case 0x12:
if(pc_is_outside_bios) {
if(address == 0x9000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
if((value&0x3f) == 0x3f) {
scc_is_visible_ = true;
slot_.map_handler(0x8000, 0x2000);
} else {
scc_is_visible_ = false;
slot_.map(value * 0x2000, 0x8000, 0x2000);
}
break;
case 0x13:
if(scc_is_visible_) {
if(pc_is_outside_bios) confidence_counter_.add_hit();
scc_.write(address, value);
} else {
if(pc_is_outside_bios) confidence_counter_.add_miss();
}
break;
case 0x16:
if(pc_is_outside_bios) {
if(address == 0xb000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
slot_.map(value * 0x2000, 0xa000, 0x2000);
break;
}
}
break;
case 0x16:
if(pc_is_outside_bios) {
hit_or_equivocal(address == 0xb000);
}
slot_.map(value * 0x2000, 0xa000, 0x2000);
break;
}
}
uint8_t read(uint16_t address) final {
if(scc_is_visible_ && address >= 0x9800 && address < 0xa000) {
confidence_counter_.add_hit();
return scc_.read(address);
}
confidence_counter_.add_miss();
return 0xff;
uint8_t read(uint16_t address) final {
if(scc_is_visible_ && address >= 0x9800 && address < 0xa000) {
confidence_counter_.add_hit();
return scc_.read(address);
}
confidence_counter_.add_miss();
return 0xff;
}
virtual std::string debug_type() final {
return "KSCC";
}
virtual std::string debug_type() final {
return "KSCC";
}
private:
MSX::MemorySlot &slot_;
Konami::SCC &scc_;
bool scc_is_visible_ = false;
private:
MSX::MemorySlot &slot_;
Konami::SCC &scc_;
bool scc_is_visible_ = false;
};
}

View File

@@ -21,22 +21,22 @@
namespace MSX {
class DiskROM: public MemorySlotHandler, public WD::WD1770 {
public:
DiskROM(MSX::MemorySlot &slot);
public:
DiskROM(MSX::MemorySlot &slot);
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final;
uint8_t read(uint16_t address) final;
void run_for(HalfCycles half_cycles) final;
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final;
uint8_t read(uint16_t address) final;
void run_for(HalfCycles half_cycles) final;
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive);
void set_activity_observer(Activity::Observer *observer);
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive);
void set_activity_observer(Activity::Observer *observer);
private:
const std::vector<uint8_t> &rom_;
private:
const std::vector<uint8_t> &rom_;
long int controller_cycles_ = 0;
long int controller_cycles_ = 0;
void set_head_load_request(bool head_load) final;
void set_head_load_request(bool head_load) final;
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -34,115 +34,122 @@ struct MemorySlotChangeHandler {
};
class MemorySlot {
public:
MemorySlot(MemorySlotChangeHandler &);
public:
MemorySlot(MemorySlotChangeHandler &);
/// @returns A pointer to the area of memory currently underneath @c address that
/// should be read
const uint8_t *read_pointer(int segment) const;
/// @returns A pointer to the area of memory currently underneath @c address that
/// should be read
const uint8_t *read_pointer(int segment) const;
/// @returns A pointer to the area of memory currently underneath @c address.
uint8_t *write_pointer(int segment) const;
/// @returns A pointer to the area of memory currently underneath @c address.
uint8_t *write_pointer(int segment) const;
/// Copies an underlying source buffer.
void set_source(const std::vector<uint8_t> &source);
/// Copies an underlying source buffer.
void set_source(const std::vector<uint8_t> &source);
/// Sets the size of the underlying source buffer.
void resize_source(std::size_t);
/// Sets the size of the underlying source buffer.
void resize_source(std::size_t);
/// Provides a reference to the internal source storage.
std::vector<uint8_t> &source();
const std::vector<uint8_t> &source() const;
/// Provides a reference to the internal source storage.
std::vector<uint8_t> &source();
const std::vector<uint8_t> &source() const;
enum AccessType {
Read,
ReadWrite
};
enum AccessType {
Read,
ReadWrite
};
/// Maps the content from @c source_address in the buffer previously
/// supplied to @c set_source to the region indicated by
/// @c destination_address and @c length within @c subslot.
template <AccessType type = AccessType::Read> void map(
std::size_t source_address,
uint16_t destination_address,
std::size_t length);
/// Maps the content from @c source_address in the buffer previously
/// supplied to @c set_source to the region indicated by
/// @c destination_address and @c length within @c subslot.
template <AccessType type = AccessType::Read> void map(
std::size_t source_address,
uint16_t destination_address,
std::size_t length);
/// Marks the region indicated by @c destination_address and @c length
/// as requiring calls into this slot's MemorySlotHandler.
void map_handler(
uint16_t destination_address,
std::size_t length);
/// Marks the region indicated by @c destination_address and @c length
/// as requiring calls into this slot's MemorySlotHandler.
void map_handler(
uint16_t destination_address,
std::size_t length);
/// Marks the region indicated by @c destination_address and @c length
/// as unoccupied.
void unmap(
uint16_t destination_address,
std::size_t length);
/// Marks the region indicated by @c destination_address and @c length
/// as unoccupied.
void unmap(
uint16_t destination_address,
std::size_t length);
private:
std::vector<uint8_t> source_;
uint8_t *read_pointers_[8];
uint8_t *write_pointers_[8];
private:
std::vector<uint8_t> source_;
uint8_t *read_pointers_[8];
uint8_t *write_pointers_[8];
MemorySlotChangeHandler &handler_;
MemorySlotChangeHandler &handler_;
using MemoryChunk = std::array<uint8_t, 8192>;
inline static MemoryChunk unmapped{0xff};
inline static MemoryChunk scratch;
using MemoryChunk = std::array<uint8_t, 8192>;
inline static MemoryChunk unmapped{0xff};
inline static MemoryChunk scratch;
};
class PrimarySlot {
public:
PrimarySlot(MemorySlotChangeHandler &);
public:
PrimarySlot(MemorySlotChangeHandler &);
/// @returns A pointer to the area of memory currently underneath @c address that
/// should be read
const uint8_t *read_pointer(int segment) const;
/// @returns A pointer to the area of memory currently underneath @c address that
/// should be read
const uint8_t *read_pointer(int segment) const;
/// @returns A pointer to the area of memory currently underneath @c address.
uint8_t *write_pointer(int segment) const;
/// @returns A pointer to the area of memory currently underneath @c address.
uint8_t *write_pointer(int segment) const;
/// Attempts to write the argument as the secondary paging selection.
void set_secondary_paging(uint8_t);
/// Attempts to write the argument as the secondary paging selection.
void set_secondary_paging(uint8_t);
/// @returns The value most recently provided to @c set_secondary_paging.
uint8_t secondary_paging() const;
/// @returns The value most recently provided to @c set_secondary_paging.
uint8_t secondary_paging() const;
/// Indicates whether this slot supports secondary paging.
bool supports_secondary_paging = false;
/// Indicates whether this slot supports secondary paging.
bool supports_secondary_paging = false;
/// Provides the subslot at the specified index.
MemorySlot &subslot(int);
/// Provides the subslot at the specified index.
MemorySlot &subslot(int);
private:
MemorySlot subslots_[4];
uint8_t secondary_paging_ = 0;
private:
MemorySlot subslots_[4];
uint8_t secondary_paging_ = 0;
};
class MemorySlotHandler {
public:
virtual ~MemorySlotHandler() = default;
public:
virtual ~MemorySlotHandler() = default;
/*! Advances time by @c half_cycles. */
virtual void run_for([[maybe_unused]] HalfCycles half_cycles) {}
/*! Advances time by @c half_cycles. */
virtual void run_for([[maybe_unused]] HalfCycles half_cycles) {}
/*! Announces an attempt to write @c value to @c address. */
virtual void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) = 0;
/*! Announces an attempt to write @c value to @c address. */
virtual void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) = 0;
/*! Seeks the result of a read at @c address; this is used only if the area is unmapped. */
virtual uint8_t read([[maybe_unused]] uint16_t address) { return 0xff; }
/*! Seeks the result of a read at @c address; this is used only if the area is unmapped. */
virtual uint8_t read([[maybe_unused]] uint16_t address) { return 0xff; }
/*! @returns The probability that this handler is correct for the data it owns. */
float get_confidence() {
return confidence_counter_.confidence();
/*! @returns The probability that this handler is correct for the data it owns. */
float get_confidence() {
return confidence_counter_.confidence();
}
virtual std::string debug_type() {
return "";
}
protected:
Analyser::Dynamic::ConfidenceCounter confidence_counter_;
void hit_or_equivocal(const bool is_hit) {
if(is_hit) {
confidence_counter_.add_hit();
} else {
confidence_counter_.add_equivocal();
}
virtual std::string debug_type() {
return "";
}
protected:
Analyser::Dynamic::ConfidenceCounter confidence_counter_;
}
};
}

View File

@@ -40,42 +40,42 @@ namespace Sega {
namespace MasterSystem {
class Joystick: public Inputs::ConcreteJoystick {
public:
Joystick() :
ConcreteJoystick({
Input(Input::Up),
Input(Input::Down),
Input(Input::Left),
Input(Input::Right),
public:
Joystick() :
ConcreteJoystick({
Input(Input::Up),
Input(Input::Down),
Input(Input::Left),
Input(Input::Right),
Input(Input::Fire, 0),
Input(Input::Fire, 1)
}) {}
Input(Input::Fire, 0),
Input(Input::Fire, 1)
}) {}
void did_set_input(const Input &digital_input, bool is_active) final {
switch(digital_input.type) {
default: return;
void did_set_input(const Input &digital_input, bool is_active) final {
switch(digital_input.type) {
default: return;
case Input::Up: if(is_active) state_ &= ~0x01; else state_ |= 0x01; break;
case Input::Down: if(is_active) state_ &= ~0x02; else state_ |= 0x02; break;
case Input::Left: if(is_active) state_ &= ~0x04; else state_ |= 0x04; break;
case Input::Right: if(is_active) state_ &= ~0x08; else state_ |= 0x08; break;
case Input::Fire:
switch(digital_input.info.control.index) {
default: break;
case 0: if(is_active) state_ &= ~0x10; else state_ |= 0x10; break;
case 1: if(is_active) state_ &= ~0x20; else state_ |= 0x20; break;
}
break;
}
case Input::Up: if(is_active) state_ &= ~0x01; else state_ |= 0x01; break;
case Input::Down: if(is_active) state_ &= ~0x02; else state_ |= 0x02; break;
case Input::Left: if(is_active) state_ &= ~0x04; else state_ |= 0x04; break;
case Input::Right: if(is_active) state_ &= ~0x08; else state_ |= 0x08; break;
case Input::Fire:
switch(digital_input.info.control.index) {
default: break;
case 0: if(is_active) state_ &= ~0x10; else state_ |= 0x10; break;
case 1: if(is_active) state_ &= ~0x20; else state_ |= 0x20; break;
}
break;
}
}
uint8_t get_state() {
return state_;
}
uint8_t get_state() {
return state_;
}
private:
uint8_t state_ = 0xff;
private:
uint8_t state_ = 0xff;
};
template <Analyser::Static::Sega::Target::Model model> class ConcreteMachine:
@@ -90,282 +90,282 @@ template <Analyser::Static::Sega::Target::Model model> class ConcreteMachine:
public MachineTypes::ScanProducer,
public MachineTypes::TimedMachine {
public:
ConcreteMachine(const Analyser::Static::Sega::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
region_(target.region),
paging_scheme_(target.paging_scheme),
z80_(*this),
sn76489_(
(target.model == Target::Model::SG1000) ? TI::SN76489::Personality::SN76489 : TI::SN76489::Personality::SMS,
audio_queue_,
audio_divider),
opll_(audio_queue_, audio_divider),
mixer_(sn76489_, opll_),
speaker_(mixer_),
keyboard_({Inputs::Keyboard::Key::Enter, Inputs::Keyboard::Key::Escape}, {}) {
// Pick the clock rate based on the region.
const double clock_rate = target.region == Target::Region::Europe ? 3546893.0 : 3579540.0;
speaker_.set_input_rate(float(clock_rate / audio_divider));
set_clock_rate(clock_rate);
public:
ConcreteMachine(const Analyser::Static::Sega::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
region_(target.region),
paging_scheme_(target.paging_scheme),
z80_(*this),
sn76489_(
(target.model == Target::Model::SG1000) ? TI::SN76489::Personality::SN76489 : TI::SN76489::Personality::SMS,
audio_queue_,
audio_divider),
opll_(audio_queue_, audio_divider),
mixer_(sn76489_, opll_),
speaker_(mixer_),
keyboard_({Inputs::Keyboard::Key::Enter, Inputs::Keyboard::Key::Escape}, {}) {
// Pick the clock rate based on the region.
const double clock_rate = target.region == Target::Region::Europe ? 3546893.0 : 3579540.0;
speaker_.set_input_rate(float(clock_rate / audio_divider));
set_clock_rate(clock_rate);
// Instantiate the joysticks.
joysticks_.emplace_back(new Joystick);
joysticks_.emplace_back(new Joystick);
// Instantiate the joysticks.
joysticks_.emplace_back(new Joystick);
joysticks_.emplace_back(new Joystick);
// Clear the memory map.
map(read_pointers_, nullptr, 0x10000, 0);
map(write_pointers_, nullptr, 0x10000, 0);
// Clear the memory map.
map(read_pointers_, nullptr, 0x10000, 0);
map(write_pointers_, nullptr, 0x10000, 0);
// Take a copy of the cartridge and place it into memory.
if(!target.media.cartridges.empty()) {
cartridge_ = target.media.cartridges[0]->get_segments()[0].data;
}
if(cartridge_.size() < 48*1024) {
std::size_t new_space = 48*1024 - cartridge_.size();
cartridge_.resize(48*1024);
memset(&cartridge_[48*1024 - new_space], 0xff, new_space);
}
if(paging_scheme_ == Target::PagingScheme::Codemasters) {
// The Codemasters cartridges start with pages 0, 1 and 0 again initially visible.
paging_registers_[0] = 0;
paging_registers_[1] = 1;
paging_registers_[2] = 0;
}
// Load the BIOS if available.
//
// TODO: there's probably a million other versions of the Master System BIOS; try to build a
// CRC32 catalogue of those. So far:
//
// 0072ed54 = US/European BIOS 1.3
// 48d44a13 = Japanese BIOS 2.1
const bool is_japanese = target.region == Target::Region::Japan;
const ROM::Name bios_name = is_japanese ? ROM::Name::MasterSystemJapaneseBIOS : ROM::Name::MasterSystemWesternBIOS;
ROM::Request request(bios_name, true);
auto roms = rom_fetcher(request);
request.validate(roms);
const auto rom = roms.find(bios_name);
if(rom == roms.end()) {
// No BIOS found; attempt to boot as though it has already disabled itself.
has_bios_ = false;
memory_control_ |= 0x08;
std::cerr << "No BIOS found; attempting to start cartridge directly" << std::endl;
} else {
has_bios_ = true;
memcpy(&bios_, rom->second.data(), std::min(sizeof(bios_), rom->second.size()));
}
page_cartridge();
// Map RAM.
if constexpr (is_master_system(model)) {
map(read_pointers_, ram_, 8*1024, 0xc000, 0x10000);
map(write_pointers_, ram_, 8*1024, 0xc000, 0x10000);
} else {
map(read_pointers_, ram_, 1024, 0xc000, 0x10000);
map(write_pointers_, ram_, 1024, 0xc000, 0x10000);
}
// Apply a relatively low low-pass filter. More guidance needed here.
// TODO: this is disabled for now since it isn't applicable for the FM chip, I think.
// speaker_.set_high_frequency_cutoff(8000);
// Set default mixer levels: FM off, SN full-throttle.
set_mixer_levels(0);
keyboard_.set_delegate(this);
// Take a copy of the cartridge and place it into memory.
if(!target.media.cartridges.empty()) {
cartridge_ = target.media.cartridges[0]->get_segments()[0].data;
}
if(cartridge_.size() < 48*1024) {
std::size_t new_space = 48*1024 - cartridge_.size();
cartridge_.resize(48*1024);
memset(&cartridge_[48*1024 - new_space], 0xff, new_space);
}
~ConcreteMachine() {
audio_queue_.flush();
if(paging_scheme_ == Target::PagingScheme::Codemasters) {
// The Codemasters cartridges start with pages 0, 1 and 0 again initially visible.
paging_registers_[0] = 0;
paging_registers_[1] = 1;
paging_registers_[2] = 0;
}
ChangeEffect effect_for_file_did_change(const std::string &) const final {
return ChangeEffect::RestartMachine;
// Load the BIOS if available.
//
// TODO: there's probably a million other versions of the Master System BIOS; try to build a
// CRC32 catalogue of those. So far:
//
// 0072ed54 = US/European BIOS 1.3
// 48d44a13 = Japanese BIOS 2.1
const bool is_japanese = target.region == Target::Region::Japan;
const ROM::Name bios_name = is_japanese ? ROM::Name::MasterSystemJapaneseBIOS : ROM::Name::MasterSystemWesternBIOS;
ROM::Request request(bios_name, true);
auto roms = rom_fetcher(request);
request.validate(roms);
const auto rom = roms.find(bios_name);
if(rom == roms.end()) {
// No BIOS found; attempt to boot as though it has already disabled itself.
has_bios_ = false;
memory_control_ |= 0x08;
std::cerr << "No BIOS found; attempting to start cartridge directly" << std::endl;
} else {
has_bios_ = true;
memcpy(&bios_, rom->second.data(), std::min(sizeof(bios_), rom->second.size()));
}
page_cartridge();
// Map RAM.
if constexpr (is_master_system(model)) {
map(read_pointers_, ram_, 8*1024, 0xc000, 0x10000);
map(write_pointers_, ram_, 8*1024, 0xc000, 0x10000);
} else {
map(read_pointers_, ram_, 1024, 0xc000, 0x10000);
map(write_pointers_, ram_, 1024, 0xc000, 0x10000);
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
vdp_.last_valid()->set_tv_standard(
(region_ == Target::Region::Europe) ?
TI::TMS::TVStandard::PAL : TI::TMS::TVStandard::NTSC);
// Apply a relatively low low-pass filter. More guidance needed here.
// TODO: this is disabled for now since it isn't applicable for the FM chip, I think.
// speaker_.set_high_frequency_cutoff(8000);
// Doing the following would be technically correct, but isn't
// especially thread-safe and won't make a substantial difference.
// time_until_debounce_ = vdp_->get_time_until_line(-1);
// Set default mixer levels: FM off, SN full-throttle.
set_mixer_levels(0);
vdp_.last_valid()->set_scan_target(scan_target);
keyboard_.set_delegate(this);
}
~ConcreteMachine() {
audio_queue_.flush();
}
ChangeEffect effect_for_file_did_change(const std::string &) const final {
return ChangeEffect::RestartMachine;
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
vdp_.last_valid()->set_tv_standard(
(region_ == Target::Region::Europe) ?
TI::TMS::TVStandard::PAL : TI::TMS::TVStandard::NTSC);
// Doing the following would be technically correct, but isn't
// especially thread-safe and won't make a substantial difference.
// time_until_debounce_ = vdp_->get_time_until_line(-1);
vdp_.last_valid()->set_scan_target(scan_target);
}
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return vdp_.last_valid()->get_scaled_scan_status();
}
void set_display_type(Outputs::Display::DisplayType display_type) final {
vdp_.last_valid()->set_display_type(display_type);
}
Outputs::Display::DisplayType get_display_type() const final {
return vdp_.last_valid()->get_display_type();
}
Outputs::Speaker::Speaker *get_speaker() final {
return &speaker_;
}
void run_for(const Cycles cycles) final {
z80_.run_for(cycles);
}
void flush_output(int outputs) final {
if(outputs & Output::Video) {
vdp_.flush();
}
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return vdp_.last_valid()->get_scaled_scan_status();
if(outputs & Output::Audio) {
update_audio();
audio_queue_.perform();
}
}
void set_display_type(Outputs::Display::DisplayType display_type) final {
vdp_.last_valid()->set_display_type(display_type);
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
if(vdp_ += cycle.length) {
z80_.set_interrupt_line(vdp_->get_interrupt_line(), vdp_.last_sequence_point_overrun());
}
time_since_sn76489_update_ += cycle.length;
Outputs::Display::DisplayType get_display_type() const final {
return vdp_.last_valid()->get_display_type();
}
if(cycle.is_terminal()) {
uint16_t address = cycle.address ? *cycle.address : 0x0000;
switch(cycle.operation) {
case CPU::Z80::PartialMachineCycle::ReadOpcode:
case CPU::Z80::PartialMachineCycle::Read:
*cycle.value = read_pointers_[address >> 10] ? read_pointers_[address >> 10][address & 1023] : 0xff;
break;
Outputs::Speaker::Speaker *get_speaker() final {
return &speaker_;
}
void run_for(const Cycles cycles) final {
z80_.run_for(cycles);
}
void flush_output(int outputs) final {
if(outputs & Output::Video) {
vdp_.flush();
}
if(outputs & Output::Audio) {
update_audio();
audio_queue_.perform();
}
}
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
if(vdp_ += cycle.length) {
z80_.set_interrupt_line(vdp_->get_interrupt_line(), vdp_.last_sequence_point_overrun());
}
time_since_sn76489_update_ += cycle.length;
if(cycle.is_terminal()) {
uint16_t address = cycle.address ? *cycle.address : 0x0000;
switch(cycle.operation) {
case CPU::Z80::PartialMachineCycle::ReadOpcode:
case CPU::Z80::PartialMachineCycle::Read:
*cycle.value = read_pointers_[address >> 10] ? read_pointers_[address >> 10][address & 1023] : 0xff;
break;
case CPU::Z80::PartialMachineCycle::Write:
if(paging_scheme_ == Target::PagingScheme::Sega) {
if(address >= 0xfffd && cartridge_.size() > 48*1024) {
if(paging_registers_[address - 0xfffd] != *cycle.value) {
paging_registers_[address - 0xfffd] = *cycle.value;
page_cartridge();
}
}
} else {
// i.e. this is the Codemasters paging scheme.
if(!(address&0x3fff) && address < 0xc000) {
if(paging_registers_[address >> 14] != *cycle.value) {
paging_registers_[address >> 14] = *cycle.value;
page_cartridge();
}
case CPU::Z80::PartialMachineCycle::Write:
if(paging_scheme_ == Target::PagingScheme::Sega) {
if(address >= 0xfffd && cartridge_.size() > 48*1024) {
if(paging_registers_[address - 0xfffd] != *cycle.value) {
paging_registers_[address - 0xfffd] = *cycle.value;
page_cartridge();
}
}
} else {
// i.e. this is the Codemasters paging scheme.
if(!(address&0x3fff) && address < 0xc000) {
if(paging_registers_[address >> 14] != *cycle.value) {
paging_registers_[address >> 14] = *cycle.value;
page_cartridge();
}
}
}
if(write_pointers_[address >> 10]) write_pointers_[address >> 10][address & 1023] = *cycle.value;
// else logger.info().append("Ignored write to ROM");
break;
if(write_pointers_[address >> 10]) write_pointers_[address >> 10][address & 1023] = *cycle.value;
// else logger.info().append("Ignored write to ROM");
break;
case CPU::Z80::PartialMachineCycle::Input:
switch(address & 0xc1) {
case 0x00:
logger.error().append("TODO: [input] memory control");
*cycle.value = 0xff;
break;
case 0x01:
logger.error().append("TODO: [input] I/O port control");
*cycle.value = 0xff;
break;
case 0x40:
*cycle.value = vdp_->get_current_line();
break;
case 0x41:
*cycle.value = vdp_.last_valid()->get_latched_horizontal_counter();
break;
case 0x80: case 0x81:
*cycle.value = vdp_->read(address);
z80_.set_interrupt_line(vdp_->get_interrupt_line());
break;
case 0xc0: {
if(memory_control_ & 0x4) {
if(has_fm_audio_ && (address & 0xff) == 0xf2) {
*cycle.value = opll_detection_word_;
} else {
*cycle.value = 0xff;
}
case CPU::Z80::PartialMachineCycle::Input:
switch(address & 0xc1) {
case 0x00:
logger.error().append("TODO: [input] memory control");
*cycle.value = 0xff;
break;
case 0x01:
logger.error().append("TODO: [input] I/O port control");
*cycle.value = 0xff;
break;
case 0x40:
*cycle.value = vdp_->get_current_line();
break;
case 0x41:
*cycle.value = vdp_.last_valid()->get_latched_horizontal_counter();
break;
case 0x80: case 0x81:
*cycle.value = vdp_->read(address);
z80_.set_interrupt_line(vdp_->get_interrupt_line());
break;
case 0xc0: {
if(memory_control_ & 0x4) {
if(has_fm_audio_ && (address & 0xff) == 0xf2) {
*cycle.value = opll_detection_word_;
} else {
Joystick *const joypad1 = static_cast<Joystick *>(joysticks_[0].get());
Joystick *const joypad2 = static_cast<Joystick *>(joysticks_[1].get());
*cycle.value = uint8_t(joypad1->get_state() | (joypad2->get_state() << 6));
}
} break;
case 0xc1: {
if(memory_control_ & 0x4) {
*cycle.value = 0xff;
} else {
Joystick *const joypad2 = static_cast<Joystick *>(joysticks_[1].get());
*cycle.value =
(joypad2->get_state() >> 2) |
0x30 |
get_th_values();
}
} break;
} else {
Joystick *const joypad1 = static_cast<Joystick *>(joysticks_[0].get());
Joystick *const joypad2 = static_cast<Joystick *>(joysticks_[1].get());
*cycle.value = uint8_t(joypad1->get_state() | (joypad2->get_state() << 6));
}
} break;
case 0xc1: {
if(memory_control_ & 0x4) {
*cycle.value = 0xff;
} else {
Joystick *const joypad2 = static_cast<Joystick *>(joysticks_[1].get());
default:
logger.error().append("[input] Clearly some sort of typo");
break;
}
break;
*cycle.value =
(joypad2->get_state() >> 2) |
0x30 |
get_th_values();
}
} break;
case CPU::Z80::PartialMachineCycle::Output:
switch(address & 0xc1) {
case 0x00: // i.e. even ports less than 0x40.
if constexpr (is_master_system(model)) {
// TODO: Obey the RAM enable.
logger.info().append("Memory control: %02x", memory_control_);
memory_control_ = *cycle.value;
page_cartridge();
default:
logger.error().append("[input] Clearly some sort of typo");
break;
}
break;
case CPU::Z80::PartialMachineCycle::Output:
switch(address & 0xc1) {
case 0x00: // i.e. even ports less than 0x40.
if constexpr (is_master_system(model)) {
// TODO: Obey the RAM enable.
logger.info().append("Memory control: %02x", memory_control_);
memory_control_ = *cycle.value;
page_cartridge();
}
break;
case 0x01: { // i.e. odd ports less than 0x40.
// A programmer can force the TH lines to 0 here,
// causing a phoney lightgun latch, so check for any
// discontinuity in TH inputs.
const auto previous_ths = get_th_values();
io_port_control_ = *cycle.value;
const auto new_ths = get_th_values();
// Latch if either TH has newly gone to 1.
if((new_ths^previous_ths)&new_ths) {
vdp_->latch_horizontal_counter();
}
} break;
case 0x40: case 0x41: // i.e. ports 0x400x7f.
update_audio();
sn76489_.write(*cycle.value);
break;
case 0x80: case 0x81: // i.e. ports 0x800xbf.
vdp_->write(address, *cycle.value);
z80_.set_interrupt_line(vdp_->get_interrupt_line());
break;
case 0xc1: case 0xc0: // i.e. ports 0xc00xff.
if(has_fm_audio_) {
switch(address & 0xff) {
case 0xf0: case 0xf1:
update_audio();
opll_.write(address, *cycle.value);
break;
case 0xf2:
opll_detection_word_ = *cycle.value;
set_mixer_levels(opll_detection_word_);
break;
}
break;
case 0x01: { // i.e. odd ports less than 0x40.
// A programmer can force the TH lines to 0 here,
// causing a phoney lightgun latch, so check for any
// discontinuity in TH inputs.
const auto previous_ths = get_th_values();
io_port_control_ = *cycle.value;
const auto new_ths = get_th_values();
}
break;
// Latch if either TH has newly gone to 1.
if((new_ths^previous_ths)&new_ths) {
vdp_->latch_horizontal_counter();
}
} break;
case 0x40: case 0x41: // i.e. ports 0x400x7f.
update_audio();
sn76489_.write(*cycle.value);
break;
case 0x80: case 0x81: // i.e. ports 0x800xbf.
vdp_->write(address, *cycle.value);
z80_.set_interrupt_line(vdp_->get_interrupt_line());
break;
case 0xc1: case 0xc0: // i.e. ports 0xc00xff.
if(has_fm_audio_) {
switch(address & 0xff) {
case 0xf0: case 0xf1:
update_audio();
opll_.write(address, *cycle.value);
break;
case 0xf2:
opll_detection_word_ = *cycle.value;
set_mixer_levels(opll_detection_word_);
break;
}
}
break;
default:
logger.error().append("[output] Clearly some sort of typo");
break;
}
break;
default:
logger.error().append("[output] Clearly some sort of typo");
break;
}
break;
/*
TODO: implementation of the below is incomplete.
@@ -380,181 +380,181 @@ template <Analyser::Static::Sega::Target::Model model> class ConcreteMachine:
— Charles MacDonald
*/
case CPU::Z80::PartialMachineCycle::Interrupt:
*cycle.value = 0xff;
break;
case CPU::Z80::PartialMachineCycle::Interrupt:
*cycle.value = 0xff;
break;
default: break;
}
}
// The pause button is debounced and takes effect only one line before pixels
// begin; time_until_debounce_ keeps track of the time until then.
time_until_debounce_ -= cycle.length;
if(time_until_debounce_ <= HalfCycles(0)) {
z80_.set_non_maskable_interrupt_line(pause_is_pressed_);
time_until_debounce_ = vdp_->get_time_until_line(-1);
}
return HalfCycles(0);
}
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
return joysticks_;
}
// MARK: - Keyboard (i.e. the pause and reset buttons).
Inputs::Keyboard &get_keyboard() final {
return keyboard_;
}
bool keyboard_did_change_key(Inputs::Keyboard &, Inputs::Keyboard::Key key, bool is_pressed) final {
if(key == Inputs::Keyboard::Key::Enter) {
pause_is_pressed_ = is_pressed;
return true;
}
if(key == Inputs::Keyboard::Key::Escape) {
reset_is_pressed_ = is_pressed;
return true;
}
return false;
}
void reset_all_keys(Inputs::Keyboard &) final {
}
// MARK: - Configuration options.
std::unique_ptr<Reflection::Struct> get_options() const final {
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
options->output = get_video_signal_configurable();
return options;
}
void set_options(const std::unique_ptr<Reflection::Struct> &str) final {
const auto options = dynamic_cast<Options *>(str.get());
set_video_signal_configurable(options->output);
}
private:
static constexpr TI::TMS::Personality tms_personality() {
switch(model) {
default:
case Target::Model::SG1000: return TI::TMS::TMS9918A;
case Target::Model::MasterSystem: return TI::TMS::SMSVDP;
case Target::Model::MasterSystem2: return TI::TMS::SMS2VDP;
default: break;
}
}
inline uint8_t get_th_values() {
// Quick not on TH inputs here: if either is setup as an output, then the
// currently output level is returned. Otherwise they're fixed at 1.
return
uint8_t(
((io_port_control_ & 0x02) << 5) | ((io_port_control_&0x20) << 1) |
((io_port_control_ & 0x08) << 4) | (io_port_control_&0x80)
);
// The pause button is debounced and takes effect only one line before pixels
// begin; time_until_debounce_ keeps track of the time until then.
time_until_debounce_ -= cycle.length;
if(time_until_debounce_ <= HalfCycles(0)) {
z80_.set_non_maskable_interrupt_line(pause_is_pressed_);
time_until_debounce_ = vdp_->get_time_until_line(-1);
}
inline void update_audio() {
speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(audio_divider)));
return HalfCycles(0);
}
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
return joysticks_;
}
// MARK: - Keyboard (i.e. the pause and reset buttons).
Inputs::Keyboard &get_keyboard() final {
return keyboard_;
}
bool keyboard_did_change_key(Inputs::Keyboard &, Inputs::Keyboard::Key key, bool is_pressed) final {
if(key == Inputs::Keyboard::Key::Enter) {
pause_is_pressed_ = is_pressed;
return true;
}
void set_mixer_levels(uint8_t mode) {
// This is as per the audio control register;
// see https://www.smspower.org/Development/AudioControlPort
update_audio();
audio_queue_.enqueue([this, mode] {
switch(mode & 3) {
case 0: // SN76489 only; the default.
mixer_.set_relative_volumes({1.0f, 0.0f});
break;
case 1: // FM only.
mixer_.set_relative_volumes({0.0f, 1.0f});
break;
case 2: // No audio.
mixer_.set_relative_volumes({0.0f, 0.0f});
break;
case 3: // Both FM and SN76489.
mixer_.set_relative_volumes({0.5f, 0.5f});
break;
}
});
if(key == Inputs::Keyboard::Key::Escape) {
reset_is_pressed_ = is_pressed;
return true;
}
using Target = Analyser::Static::Sega::Target;
const Target::Region region_;
const Target::PagingScheme paging_scheme_;
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
JustInTimeActor<TI::TMS::TMS9918<tms_personality()>> vdp_;
return false;
}
Concurrency::AsyncTaskQueue<false> audio_queue_;
TI::SN76489 sn76489_;
Yamaha::OPL::OPLL opll_;
Outputs::Speaker::CompoundSource<decltype(sn76489_), decltype(opll_)> mixer_;
Outputs::Speaker::PullLowpass<decltype(mixer_)> speaker_;
uint8_t opll_detection_word_ = 0xff;
void reset_all_keys(Inputs::Keyboard &) final {
}
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
Inputs::Keyboard keyboard_;
bool reset_is_pressed_ = false, pause_is_pressed_ = false;
// MARK: - Configuration options.
std::unique_ptr<Reflection::Struct> get_options() const final {
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
options->output = get_video_signal_configurable();
return options;
}
HalfCycles time_since_sn76489_update_;
HalfCycles time_until_debounce_;
void set_options(const std::unique_ptr<Reflection::Struct> &str) final {
const auto options = dynamic_cast<Options *>(str.get());
set_video_signal_configurable(options->output);
}
uint8_t ram_[8*1024];
uint8_t bios_[8*1024];
std::vector<uint8_t> cartridge_;
private:
static constexpr TI::TMS::Personality tms_personality() {
switch(model) {
default:
case Target::Model::SG1000: return TI::TMS::TMS9918A;
case Target::Model::MasterSystem: return TI::TMS::SMSVDP;
case Target::Model::MasterSystem2: return TI::TMS::SMS2VDP;
}
}
uint8_t io_port_control_ = 0x0f;
inline uint8_t get_th_values() {
// Quick not on TH inputs here: if either is setup as an output, then the
// currently output level is returned. Otherwise they're fixed at 1.
return
uint8_t(
((io_port_control_ & 0x02) << 5) | ((io_port_control_&0x20) << 1) |
((io_port_control_ & 0x08) << 4) | (io_port_control_&0x80)
);
// This is a static constexpr for now; I may use it in the future.
static constexpr bool has_fm_audio_ = true;
}
// The memory map has a 1kb granularity; this is determined by the SG1000's 1kb of RAM.
const uint8_t *read_pointers_[64];
uint8_t *write_pointers_[64];
template <typename T> void map(T **target, uint8_t *source, size_t size, size_t start_address, size_t end_address = 0) {
if(!end_address) end_address = start_address + size;
for(auto address = start_address; address < end_address; address += 1024) {
target[address >> 10] = source ? &source[(address - start_address) & (size - 1)] : nullptr;
inline void update_audio() {
speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(audio_divider)));
}
void set_mixer_levels(uint8_t mode) {
// This is as per the audio control register;
// see https://www.smspower.org/Development/AudioControlPort
update_audio();
audio_queue_.enqueue([this, mode] {
switch(mode & 3) {
case 0: // SN76489 only; the default.
mixer_.set_relative_volumes({1.0f, 0.0f});
break;
case 1: // FM only.
mixer_.set_relative_volumes({0.0f, 1.0f});
break;
case 2: // No audio.
mixer_.set_relative_volumes({0.0f, 0.0f});
break;
case 3: // Both FM and SN76489.
mixer_.set_relative_volumes({0.5f, 0.5f});
break;
}
});
}
using Target = Analyser::Static::Sega::Target;
const Target::Region region_;
const Target::PagingScheme paging_scheme_;
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
JustInTimeActor<TI::TMS::TMS9918<tms_personality()>> vdp_;
Concurrency::AsyncTaskQueue<false> audio_queue_;
TI::SN76489 sn76489_;
Yamaha::OPL::OPLL opll_;
Outputs::Speaker::CompoundSource<decltype(sn76489_), decltype(opll_)> mixer_;
Outputs::Speaker::PullLowpass<decltype(mixer_)> speaker_;
uint8_t opll_detection_word_ = 0xff;
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
Inputs::Keyboard keyboard_;
bool reset_is_pressed_ = false, pause_is_pressed_ = false;
HalfCycles time_since_sn76489_update_;
HalfCycles time_until_debounce_;
uint8_t ram_[8*1024];
uint8_t bios_[8*1024];
std::vector<uint8_t> cartridge_;
uint8_t io_port_control_ = 0x0f;
// This is a static constexpr for now; I may use it in the future.
static constexpr bool has_fm_audio_ = true;
// The memory map has a 1kb granularity; this is determined by the SG1000's 1kb of RAM.
const uint8_t *read_pointers_[64];
uint8_t *write_pointers_[64];
template <typename T> void map(T **target, uint8_t *source, size_t size, size_t start_address, size_t end_address = 0) {
if(!end_address) end_address = start_address + size;
for(auto address = start_address; address < end_address; address += 1024) {
target[address >> 10] = source ? &source[(address - start_address) & (size - 1)] : nullptr;
}
}
uint8_t paging_registers_[3] = {0, 1, 2};
uint8_t memory_control_ = 0;
void page_cartridge() {
// Either install the cartridge or don't; Japanese machines can't see
// anything but the cartridge.
if(!(memory_control_ & 0x40) || region_ == Target::Region::Japan) {
for(size_t c = 0; c < 3; ++c) {
const size_t start_addr = (paging_registers_[c] * 0x4000) % cartridge_.size();
map(
read_pointers_,
cartridge_.data() + start_addr,
std::min(size_t(0x4000), cartridge_.size() - start_addr),
c * 0x4000);
}
// The first 1kb doesn't page though, if this is the Sega paging scheme.
if(paging_scheme_ == Target::PagingScheme::Sega) {
map(read_pointers_, cartridge_.data(), 0x400, 0x0000);
}
} else {
map(read_pointers_, nullptr, 0xc000, 0x0000);
uint8_t paging_registers_[3] = {0, 1, 2};
uint8_t memory_control_ = 0;
void page_cartridge() {
// Either install the cartridge or don't; Japanese machines can't see
// anything but the cartridge.
if(!(memory_control_ & 0x40) || region_ == Target::Region::Japan) {
for(size_t c = 0; c < 3; ++c) {
const size_t start_addr = (paging_registers_[c] * 0x4000) % cartridge_.size();
map(
read_pointers_,
cartridge_.data() + start_addr,
std::min(size_t(0x4000), cartridge_.size() - start_addr),
c * 0x4000);
}
// Throw the BIOS on top if this machine has one and it isn't disabled.
if(has_bios_ && !(memory_control_ & 0x08)) {
map(read_pointers_, bios_, 8*1024, 0);
// The first 1kb doesn't page though, if this is the Sega paging scheme.
if(paging_scheme_ == Target::PagingScheme::Sega) {
map(read_pointers_, cartridge_.data(), 0x400, 0x0000);
}
} else {
map(read_pointers_, nullptr, 0xc000, 0x0000);
}
bool has_bios_ = true;
// Throw the BIOS on top if this machine has one and it isn't disabled.
if(has_bios_ && !(memory_control_ & 0x08)) {
map(read_pointers_, bios_, 8*1024, 0);
}
}
bool has_bios_ = true;
};
}

View File

@@ -23,33 +23,33 @@ namespace Oric {
see the thread at https://forum.defence-force.org/viewtopic.php?f=25&t=2055
*/
class BD500: public DiskController {
public:
BD500();
public:
BD500();
void write(int address, uint8_t value);
uint8_t read(int address);
void write(int address, uint8_t value);
uint8_t read(int address);
void run_for(const Cycles cycles);
void run_for(const Cycles cycles);
void set_activity_observer(Activity::Observer *observer);
void set_activity_observer(Activity::Observer *observer);
private:
void set_head_load_request(bool head_load) final;
bool is_loading_head_ = false;
Activity::Observer *observer_ = nullptr;
private:
void set_head_load_request(bool head_load) final;
bool is_loading_head_ = false;
Activity::Observer *observer_ = nullptr;
void access(int address);
void set_head_loaded(bool loaded);
void access(int address);
void set_head_loaded(bool loaded);
bool enable_overlay_ram_ = false;
bool disable_basic_rom_ = false;
void select_paged_item() {
PagedItem item = PagedItem::RAM;
if(!enable_overlay_ram_) {
item = disable_basic_rom_ ? PagedItem::DiskROM : PagedItem::BASIC;
}
set_paged_item(item);
bool enable_overlay_ram_ = false;
bool disable_basic_rom_ = false;
void select_paged_item() {
PagedItem item = PagedItem::RAM;
if(!enable_overlay_ram_) {
item = disable_basic_rom_ ? PagedItem::DiskROM : PagedItem::BASIC;
}
set_paged_item(item);
}
};
}

View File

@@ -11,50 +11,50 @@
namespace Oric {
class DiskController: public WD::WD1770 {
public:
DiskController(WD::WD1770::Personality personality, int clock_rate, Storage::Disk::Drive::ReadyType ready_type) :
WD::WD1770(personality), clock_rate_(clock_rate), ready_type_(ready_type) {
emplace_drives(4, clock_rate_, 300, 2, ready_type_);
// TODO: don't assume four drives?
public:
DiskController(WD::WD1770::Personality personality, int clock_rate, Storage::Disk::Drive::ReadyType ready_type) :
WD::WD1770(personality), clock_rate_(clock_rate), ready_type_(ready_type) {
emplace_drives(4, clock_rate_, 300, 2, ready_type_);
// TODO: don't assume four drives?
}
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int d) {
get_drive(size_t(d)).set_disk(disk);
}
enum class PagedItem {
DiskROM,
BASIC,
RAM
};
struct Delegate: public WD1770::Delegate {
virtual void disk_controller_did_change_paged_item(DiskController &) = 0;
};
inline void set_delegate(Delegate *delegate) {
delegate_ = delegate;
WD1770::set_delegate(delegate);
if(delegate) delegate->disk_controller_did_change_paged_item(*this);
}
inline PagedItem get_paged_item() {
return paged_item_;
}
protected:
Delegate *delegate_ = nullptr;
void set_paged_item(PagedItem item) {
if(paged_item_ == item) return;
paged_item_ = item;
if(delegate_) {
delegate_->disk_controller_did_change_paged_item(*this);
}
}
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int d) {
get_drive(size_t(d)).set_disk(disk);
}
enum class PagedItem {
DiskROM,
BASIC,
RAM
};
struct Delegate: public WD1770::Delegate {
virtual void disk_controller_did_change_paged_item(DiskController &) = 0;
};
inline void set_delegate(Delegate *delegate) {
delegate_ = delegate;
WD1770::set_delegate(delegate);
if(delegate) delegate->disk_controller_did_change_paged_item(*this);
}
inline PagedItem get_paged_item() {
return paged_item_;
}
protected:
Delegate *delegate_ = nullptr;
void set_paged_item(PagedItem item) {
if(paged_item_ == item) return;
paged_item_ = item;
if(delegate_) {
delegate_->disk_controller_did_change_paged_item(*this);
}
}
private:
PagedItem paged_item_ = PagedItem::DiskROM;
int clock_rate_;
Storage::Disk::Drive::ReadyType ready_type_;
private:
PagedItem paged_item_ = PagedItem::DiskROM;
int clock_rate_;
Storage::Disk::Drive::ReadyType ready_type_;
};
}

View File

@@ -15,29 +15,29 @@
namespace Oric {
class Jasmin: public DiskController {
public:
Jasmin();
public:
Jasmin();
void write(int address, uint8_t value);
void write(int address, uint8_t value);
void set_activity_observer(Activity::Observer *observer);
void set_activity_observer(Activity::Observer *observer);
private:
void set_motor_on(bool on) final;
bool motor_on_ = false;
uint8_t selected_drives_ = 0;
private:
void set_motor_on(bool on) final;
bool motor_on_ = false;
uint8_t selected_drives_ = 0;
Activity::Observer *observer_ = nullptr;
Activity::Observer *observer_ = nullptr;
bool enable_overlay_ram_ = false;
bool disable_basic_rom_ = false;
void select_paged_item() {
PagedItem item = PagedItem::RAM;
if(!enable_overlay_ram_) {
item = disable_basic_rom_ ? PagedItem::DiskROM : PagedItem::BASIC;
}
set_paged_item(item);
bool enable_overlay_ram_ = false;
bool disable_basic_rom_ = false;
void select_paged_item() {
PagedItem item = PagedItem::RAM;
if(!enable_overlay_ram_) {
item = disable_basic_rom_ ? PagedItem::DiskROM : PagedItem::BASIC;
}
set_paged_item(item);
}
};
}

View File

@@ -15,30 +15,30 @@
namespace Oric {
class Microdisc: public DiskController {
public:
Microdisc();
public:
Microdisc();
void set_control_register(uint8_t control);
uint8_t get_interrupt_request_register();
uint8_t get_data_request_register();
void set_control_register(uint8_t control);
uint8_t get_interrupt_request_register();
uint8_t get_data_request_register();
bool get_interrupt_request_line();
bool get_interrupt_request_line();
void run_for(const Cycles cycles);
void run_for(const Cycles cycles);
void set_activity_observer(Activity::Observer *observer);
void set_activity_observer(Activity::Observer *observer);
private:
void set_head_load_request(bool head_load) final;
private:
void set_head_load_request(bool head_load) final;
void set_control_register(uint8_t control, uint8_t changes);
uint8_t last_control_ = 0;
bool irq_enable_ = false;
void set_control_register(uint8_t control, uint8_t changes);
uint8_t last_control_ = 0;
bool irq_enable_ = false;
Cycles::IntType head_load_request_counter_ = -1;
bool head_load_request_ = false;
Cycles::IntType head_load_request_counter_ = -1;
bool head_load_request_ = false;
Activity::Observer *observer_ = nullptr;
Activity::Observer *observer_ = nullptr;
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -18,52 +18,52 @@
namespace Oric {
class VideoOutput {
public:
VideoOutput(uint8_t *memory);
void set_colour_rom(const std::vector<uint8_t> &colour_rom);
public:
VideoOutput(uint8_t *memory);
void set_colour_rom(const std::vector<uint8_t> &colour_rom);
void run_for(const Cycles cycles);
Cycles next_sequence_point() const;
void run_for(const Cycles cycles);
Cycles next_sequence_point() const;
bool vsync();
bool vsync();
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
void set_display_type(Outputs::Display::DisplayType display_type);
Outputs::Display::DisplayType get_display_type() const;
Outputs::Display::ScanStatus get_scaled_scan_status() const;
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
void set_display_type(Outputs::Display::DisplayType display_type);
Outputs::Display::DisplayType get_display_type() const;
Outputs::Display::ScanStatus get_scaled_scan_status() const;
void register_crt_frequency_mismatch();
void register_crt_frequency_mismatch();
private:
uint8_t *ram_;
Outputs::CRT::CRT crt_;
Outputs::CRT::CRTFrequencyMismatchWarner<VideoOutput> frequency_mismatch_warner_;
bool crt_is_60Hz_ = false;
bool has_colour_rom_ = false;
private:
uint8_t *ram_;
Outputs::CRT::CRT crt_;
Outputs::CRT::CRTFrequencyMismatchWarner<VideoOutput> frequency_mismatch_warner_;
bool crt_is_60Hz_ = false;
bool has_colour_rom_ = false;
void update_crt_frequency();
void update_crt_frequency();
// Counters and limits.
int counter_ = 0, frame_counter_ = 0;
int v_sync_start_position_, v_sync_end_position_, counter_period_;
// Counters and limits.
int counter_ = 0, frame_counter_ = 0;
int v_sync_start_position_, v_sync_end_position_, counter_period_;
// Output target and device.
uint8_t *rgb_pixel_target_ = nullptr;
uint32_t *composite_pixel_target_ = nullptr;
uint32_t colour_forms_[8];
Outputs::Display::InputDataType data_type_;
// Output target and device.
uint8_t *rgb_pixel_target_ = nullptr;
uint32_t *composite_pixel_target_ = nullptr;
uint32_t colour_forms_[8];
Outputs::Display::InputDataType data_type_;
// Registers.
uint8_t ink_, paper_;
// Registers.
uint8_t ink_, paper_;
int character_set_base_address_ = 0xb400;
inline void set_character_set_base_address();
int character_set_base_address_ = 0xb400;
inline void set_character_set_base_address();
bool is_graphics_mode_ = false;
bool next_frame_is_sixty_hertz_ = false;
bool use_alternative_character_set_;
bool use_double_height_characters_;
bool blink_text_;
bool is_graphics_mode_ = false;
bool next_frame_is_sixty_hertz_ = false;
bool use_alternative_character_set_;
bool use_double_height_characters_;
bool blink_text_;
};
}

View File

@@ -37,38 +37,38 @@ enum Key: uint16_t {
};
class Keyboard {
public:
Keyboard(Machine machine);
public:
Keyboard(Machine machine);
void set_key_state(uint16_t key, bool is_pressed);
void clear_all_keys();
void set_key_state(uint16_t key, bool is_pressed);
void clear_all_keys();
uint8_t read(uint16_t address);
uint8_t read(uint16_t address);
private:
uint8_t key_states_[8];
const Machine machine_;
private:
uint8_t key_states_[8];
const Machine machine_;
};
class KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper {
public:
KeyboardMapper(Machine machine);
public:
KeyboardMapper(Machine machine);
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const override;
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const override;
private:
const Machine machine_;
private:
const Machine machine_;
};
class CharacterMapper: public ::Utility::CharacterMapper {
public:
CharacterMapper(Machine machine);
const uint16_t *sequence_for_character(char character) const override;
public:
CharacterMapper(Machine machine);
const uint16_t *sequence_for_character(char character) const override;
bool needs_pause_after_key(uint16_t key) const override;
bool needs_pause_after_key(uint16_t key) const override;
private:
const Machine machine_;
private:
const Machine machine_;
};
}

View File

@@ -61,433 +61,433 @@ template<bool is_zx81> class ConcreteMachine:
public Utility::TypeRecipient<CharacterMapper>,
public CPU::Z80::BusHandler,
public Machine {
public:
ConcreteMachine(const Analyser::Static::ZX8081::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
Utility::TypeRecipient<CharacterMapper>(keyboard_machine()),
z80_(*this),
keyboard_(keyboard_machine()),
keyboard_mapper_(keyboard_machine()),
tape_player_(ZX8081ClockRate),
ay_(GI::AY38910::Personality::AY38910, audio_queue_),
speaker_(ay_) {
set_clock_rate(ZX8081ClockRate);
speaker_.set_input_rate(float(ZX8081ClockRate) / 2.0f);
public:
ConcreteMachine(const Analyser::Static::ZX8081::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
Utility::TypeRecipient<CharacterMapper>(keyboard_machine()),
z80_(*this),
keyboard_(keyboard_machine()),
keyboard_mapper_(keyboard_machine()),
tape_player_(ZX8081ClockRate),
ay_(GI::AY38910::Personality::AY38910, audio_queue_),
speaker_(ay_) {
set_clock_rate(ZX8081ClockRate);
speaker_.set_input_rate(float(ZX8081ClockRate) / 2.0f);
const bool use_zx81_rom = target.is_ZX81 || target.ZX80_uses_ZX81_ROM;
const ROM::Name rom_name = use_zx81_rom ? ROM::Name::ZX81 : ROM::Name::ZX80;
const ROM::Request request(rom_name);
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
rom_ = std::move(roms.find(rom_name)->second);
const bool use_zx81_rom = target.is_ZX81 || target.ZX80_uses_ZX81_ROM;
const ROM::Name rom_name = use_zx81_rom ? ROM::Name::ZX81 : ROM::Name::ZX80;
const ROM::Request request(rom_name);
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
rom_ = std::move(roms.find(rom_name)->second);
rom_mask_ = uint16_t(rom_.size() - 1);
rom_mask_ = uint16_t(rom_.size() - 1);
switch(target.memory_model) {
case Analyser::Static::ZX8081::Target::MemoryModel::Unexpanded:
ram_.resize(1024);
ram_base_ = 16384;
ram_mask_ = 1023;
break;
case Analyser::Static::ZX8081::Target::MemoryModel::SixteenKB:
ram_.resize(16384);
ram_base_ = 16384;
ram_mask_ = 16383;
break;
case Analyser::Static::ZX8081::Target::MemoryModel::SixtyFourKB:
ram_.resize(65536);
ram_base_ = 8192;
ram_mask_ = 65535;
break;
}
Memory::Fuzz(ram_);
switch(target.memory_model) {
case Analyser::Static::ZX8081::Target::MemoryModel::Unexpanded:
ram_.resize(1024);
ram_base_ = 16384;
ram_mask_ = 1023;
break;
case Analyser::Static::ZX8081::Target::MemoryModel::SixteenKB:
ram_.resize(16384);
ram_base_ = 16384;
ram_mask_ = 16383;
break;
case Analyser::Static::ZX8081::Target::MemoryModel::SixtyFourKB:
ram_.resize(65536);
ram_base_ = 8192;
ram_mask_ = 65535;
break;
}
Memory::Fuzz(ram_);
// Ensure valid initial key state.
clear_all_keys();
if(!target.loading_command.empty()) {
type_string(target.loading_command);
should_autorun_ = true;
}
insert_media(target.media);
// Ensure valid initial key state.
clear_all_keys();
if(!target.loading_command.empty()) {
type_string(target.loading_command);
should_autorun_ = true;
}
~ConcreteMachine() {
audio_queue_.flush();
insert_media(target.media);
}
~ConcreteMachine() {
audio_queue_.flush();
}
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
const HalfCycles previous_counter = horizontal_counter_;
horizontal_counter_ += cycle.length;
time_since_ay_update_ += cycle.length;
if(previous_counter < vsync_start_ && horizontal_counter_ >= vsync_start_) {
video_.run_for(vsync_start_ - previous_counter);
set_hsync(true);
line_counter_ = (line_counter_ + 1) & 7;
if(nmi_is_enabled_) {
z80_.set_non_maskable_interrupt_line(true);
}
video_.run_for(horizontal_counter_ - vsync_start_);
} else if(previous_counter < vsync_end_ && horizontal_counter_ >= vsync_end_) {
video_.run_for(vsync_end_ - previous_counter);
set_hsync(false);
if(nmi_is_enabled_) {
z80_.set_non_maskable_interrupt_line(false);
z80_.set_wait_line(false);
}
video_.run_for(horizontal_counter_ - vsync_end_);
} else {
video_.run_for(cycle.length);
}
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
const HalfCycles previous_counter = horizontal_counter_;
horizontal_counter_ += cycle.length;
time_since_ay_update_ += cycle.length;
if constexpr (is_zx81) horizontal_counter_ %= HalfCycles(Cycles(207));
if(!tape_advance_delay_) {
tape_player_.run_for(cycle.length);
} else {
tape_advance_delay_ = std::max(tape_advance_delay_ - cycle.length, HalfCycles(0));
}
if(previous_counter < vsync_start_ && horizontal_counter_ >= vsync_start_) {
video_.run_for(vsync_start_ - previous_counter);
set_hsync(true);
line_counter_ = (line_counter_ + 1) & 7;
if(nmi_is_enabled_) {
z80_.set_non_maskable_interrupt_line(true);
if(nmi_is_enabled_ && !z80_.get_halt_line() && z80_.get_non_maskable_interrupt_line()) {
z80_.set_wait_line(true);
}
if(!cycle.is_terminal()) {
return Cycles(0);
}
const uint16_t address = cycle.address ? *cycle.address : 0;
bool is_opcode_read = false;
switch(cycle.operation) {
case CPU::Z80::PartialMachineCycle::Output:
if(!nmi_is_enabled_) {
line_counter_ = 0;
set_vsync(false);
}
video_.run_for(horizontal_counter_ - vsync_start_);
} else if(previous_counter < vsync_end_ && horizontal_counter_ >= vsync_end_) {
video_.run_for(vsync_end_ - previous_counter);
set_hsync(false);
if(nmi_is_enabled_) {
z80_.set_non_maskable_interrupt_line(false);
z80_.set_wait_line(false);
if(!(address & 2)) nmi_is_enabled_ = false;
if(!(address & 1)) nmi_is_enabled_ = is_zx81;
if(is_zx81 && !nmi_is_enabled_) z80_.set_wait_line(false);
// The below emulates the ZonX AY expansion device.
if constexpr (is_zx81) {
if((address&0xef) == 0xcf) {
ay_set_register(*cycle.value);
} else if((address&0xef) == 0x0f) {
ay_set_data(*cycle.value);
}
}
video_.run_for(horizontal_counter_ - vsync_end_);
} else {
video_.run_for(cycle.length);
}
break;
if constexpr (is_zx81) horizontal_counter_ %= HalfCycles(Cycles(207));
if(!tape_advance_delay_) {
tape_player_.run_for(cycle.length);
} else {
tape_advance_delay_ = std::max(tape_advance_delay_ - cycle.length, HalfCycles(0));
}
case CPU::Z80::PartialMachineCycle::Input: {
uint8_t value = 0xff;
if(!(address&1)) {
if(!nmi_is_enabled_) set_vsync(true);
if(nmi_is_enabled_ && !z80_.get_halt_line() && z80_.get_non_maskable_interrupt_line()) {
z80_.set_wait_line(true);
}
value &= keyboard_.read(address);
value &= ~(tape_player_.input() ? 0x00 : 0x80);
}
if(!cycle.is_terminal()) {
return Cycles(0);
}
const uint16_t address = cycle.address ? *cycle.address : 0;
bool is_opcode_read = false;
switch(cycle.operation) {
case CPU::Z80::PartialMachineCycle::Output:
if(!nmi_is_enabled_) {
line_counter_ = 0;
set_vsync(false);
// The below emulates the ZonX AY expansion device.
if constexpr (is_zx81) {
if((address&0xef) == 0xcf) {
value &= ay_read_data();
}
if(!(address & 2)) nmi_is_enabled_ = false;
if(!(address & 1)) nmi_is_enabled_ = is_zx81;
if(is_zx81 && !nmi_is_enabled_) z80_.set_wait_line(false);
}
*cycle.value = value;
} break;
// The below emulates the ZonX AY expansion device.
if constexpr (is_zx81) {
if((address&0xef) == 0xcf) {
ay_set_register(*cycle.value);
} else if((address&0xef) == 0x0f) {
ay_set_data(*cycle.value);
}
}
break;
case CPU::Z80::PartialMachineCycle::Interrupt:
// resetting event is M1 and IOREQ both simultaneously having leading edges;
// that happens 2 cycles before the end of INTACK. So the timer was reset and
// now has advanced twice.
horizontal_counter_ = HalfCycles(2);
case CPU::Z80::PartialMachineCycle::Input: {
uint8_t value = 0xff;
if(!(address&1)) {
if(!nmi_is_enabled_) set_vsync(true);
*cycle.value = 0xff;
break;
value &= keyboard_.read(address);
value &= ~(tape_player_.input() ? 0x00 : 0x80);
}
// The below emulates the ZonX AY expansion device.
if constexpr (is_zx81) {
if((address&0xef) == 0xcf) {
value &= ay_read_data();
}
}
*cycle.value = value;
} break;
case CPU::Z80::PartialMachineCycle::Interrupt:
// resetting event is M1 and IOREQ both simultaneously having leading edges;
// that happens 2 cycles before the end of INTACK. So the timer was reset and
// now has advanced twice.
horizontal_counter_ = HalfCycles(2);
*cycle.value = 0xff;
break;
case CPU::Z80::PartialMachineCycle::Refresh:
// The ZX80 and 81 signal an interrupt while refresh is active and bit 6 of the refresh
// address is low. The Z80 signals a refresh, providing the refresh address during the
// final two cycles of an opcode fetch. Therefore communicate a transient signalling
// of the IRQ line if necessary.
if(!(address & 0x40)) {
z80_.set_interrupt_line(true, Cycles(-2));
z80_.set_interrupt_line(false);
}
if(has_latched_video_byte_) {
std::size_t char_address = size_t((address & 0xfe00) | ((latched_video_byte_ & 0x3f) << 3) | line_counter_);
const uint8_t mask = (latched_video_byte_ & 0x80) ? 0x00 : 0xff;
if(char_address < ram_base_) {
latched_video_byte_ = rom_[char_address & rom_mask_] ^ mask;
} else {
latched_video_byte_ = ram_[address & ram_mask_] ^ mask;
}
video_.output_byte(latched_video_byte_);
has_latched_video_byte_ = false;
}
break;
case CPU::Z80::PartialMachineCycle::ReadOpcode:
// Check for use of the fast tape hack.
if(use_fast_tape_hack_ && address == tape_trap_address_) {
const uint64_t prior_offset = tape_player_.serialiser()->offset();
const int next_byte = parser_.get_next_byte(*tape_player_.serialiser());
if(next_byte != -1) {
const uint16_t hl = z80_.value_of(CPU::Z80::Register::HL);
ram_[hl & ram_mask_] = uint8_t(next_byte);
*cycle.value = 0x00;
z80_.set_value_of(CPU::Z80::Register::ProgramCounter, tape_return_address_ - 1);
// Assume that having read one byte quickly, we're probably going to be asked to read
// another shortly. Therefore, temporarily disable the tape motor for 1000 cycles in order
// to avoid fighting with real time. This is a stop-gap fix.
tape_advance_delay_ = 1000;
return 0;
} else {
tape_player_.serialiser()->set_offset(prior_offset);
}
}
if(should_autorun_ && address == finished_load_address_) {
type_string(is_zx81 ? "r \n" : "r\n "); // Spaces here are not especially scientific; they merely ensure sufficient pauses for both the ZX80 and 81, empirically.
should_autorun_ = false;
}
// Check for automatic tape control.
if(use_automatic_tape_motor_control_) {
tape_player_.set_motor_control((address >= automatic_tape_motor_start_address_) && (address < automatic_tape_motor_end_address_));
}
is_opcode_read = true;
[[fallthrough]];
case CPU::Z80::PartialMachineCycle::Read:
if(address < ram_base_) {
*cycle.value = rom_[address & rom_mask_];
case CPU::Z80::PartialMachineCycle::Refresh:
// The ZX80 and 81 signal an interrupt while refresh is active and bit 6 of the refresh
// address is low. The Z80 signals a refresh, providing the refresh address during the
// final two cycles of an opcode fetch. Therefore communicate a transient signalling
// of the IRQ line if necessary.
if(!(address & 0x40)) {
z80_.set_interrupt_line(true, Cycles(-2));
z80_.set_interrupt_line(false);
}
if(has_latched_video_byte_) {
std::size_t char_address = size_t((address & 0xfe00) | ((latched_video_byte_ & 0x3f) << 3) | line_counter_);
const uint8_t mask = (latched_video_byte_ & 0x80) ? 0x00 : 0xff;
if(char_address < ram_base_) {
latched_video_byte_ = rom_[char_address & rom_mask_] ^ mask;
} else {
const uint8_t value = ram_[address & ram_mask_];
// If this is an M1 cycle reading from above the 32kb mark and HALT is not
// currently active, latch for video output and return a NOP. Otherwise,
// just return the value as read.
if(is_opcode_read && address&0x8000 && !(value & 0x40) && !z80_.get_halt_line()) {
latched_video_byte_ = value;
has_latched_video_byte_ = true;
*cycle.value = 0;
} else *cycle.value = value;
latched_video_byte_ = ram_[address & ram_mask_] ^ mask;
}
break;
case CPU::Z80::PartialMachineCycle::Write:
if(address >= ram_base_) {
ram_[address & ram_mask_] = *cycle.value;
}
break;
default: break;
}
if(typer_) typer_->run_for(cycle.length);
return HalfCycles(0);
}
void flush_output(int outputs) final {
if(outputs & Output::Video) {
video_.flush();
}
if constexpr (is_zx81) {
if(outputs & Output::Audio) {
update_audio();
audio_queue_.perform();
video_.output_byte(latched_video_byte_);
has_latched_video_byte_ = false;
}
break;
case CPU::Z80::PartialMachineCycle::ReadOpcode:
// Check for use of the fast tape hack.
if(use_fast_tape_hack_ && address == tape_trap_address_) {
const uint64_t prior_offset = tape_player_.serialiser()->offset();
const int next_byte = parser_.get_next_byte(*tape_player_.serialiser());
if(next_byte != -1) {
const uint16_t hl = z80_.value_of(CPU::Z80::Register::HL);
ram_[hl & ram_mask_] = uint8_t(next_byte);
*cycle.value = 0x00;
z80_.set_value_of(CPU::Z80::Register::ProgramCounter, tape_return_address_ - 1);
// Assume that having read one byte quickly, we're probably going to be asked to read
// another shortly. Therefore, temporarily disable the tape motor for 1000 cycles in order
// to avoid fighting with real time. This is a stop-gap fix.
tape_advance_delay_ = 1000;
return 0;
} else {
tape_player_.serialiser()->set_offset(prior_offset);
}
}
if(should_autorun_ && address == finished_load_address_) {
type_string(is_zx81 ? "r \n" : "r\n "); // Spaces here are not especially scientific; they merely ensure sufficient pauses for both the ZX80 and 81, empirically.
should_autorun_ = false;
}
// Check for automatic tape control.
if(use_automatic_tape_motor_control_) {
tape_player_.set_motor_control((address >= automatic_tape_motor_start_address_) && (address < automatic_tape_motor_end_address_));
}
is_opcode_read = true;
[[fallthrough]];
case CPU::Z80::PartialMachineCycle::Read:
if(address < ram_base_) {
*cycle.value = rom_[address & rom_mask_];
} else {
const uint8_t value = ram_[address & ram_mask_];
// If this is an M1 cycle reading from above the 32kb mark and HALT is not
// currently active, latch for video output and return a NOP. Otherwise,
// just return the value as read.
if(is_opcode_read && address&0x8000 && !(value & 0x40) && !z80_.get_halt_line()) {
latched_video_byte_ = value;
has_latched_video_byte_ = true;
*cycle.value = 0;
} else *cycle.value = value;
}
break;
case CPU::Z80::PartialMachineCycle::Write:
if(address >= ram_base_) {
ram_[address & ram_mask_] = *cycle.value;
}
break;
default: break;
}
if(typer_) typer_->run_for(cycle.length);
return HalfCycles(0);
}
void flush_output(int outputs) final {
if(outputs & Output::Video) {
video_.flush();
}
if constexpr (is_zx81) {
if(outputs & Output::Audio) {
update_audio();
audio_queue_.perform();
}
}
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
video_.set_scan_target(scan_target);
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
video_.set_scan_target(scan_target);
}
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return video_.get_scaled_scan_status();
}
Outputs::Speaker::Speaker *get_speaker() final {
return is_zx81 ? &speaker_ : nullptr;
}
void run_for(const Cycles cycles) final {
z80_.run_for(cycles);
}
bool insert_media(const Analyser::Static::Media &media) final {
if(!media.tapes.empty()) {
tape_player_.set_tape(media.tapes.front(), is_zx81 ? TargetPlatform::ZX81 : TargetPlatform::ZX80);
}
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return video_.get_scaled_scan_status();
set_use_fast_tape();
return !media.tapes.empty();
}
void type_string(const std::string &string) final {
Utility::TypeRecipient<CharacterMapper>::add_typer(string);
}
bool can_type(char c) const final {
return Utility::TypeRecipient<CharacterMapper>::can_type(c);
}
// MARK: - Keyboard
void set_key_state(uint16_t key, bool is_pressed) final {
keyboard_.set_key_state(key, is_pressed);
}
void clear_all_keys() final {
keyboard_.clear_all_keys();
}
static constexpr Sinclair::ZX::Keyboard::Machine keyboard_machine() {
return is_zx81 ? Sinclair::ZX::Keyboard::Machine::ZX81 : Sinclair::ZX::Keyboard::Machine::ZX80;
}
// MARK: - Tape control
void set_use_automatic_tape_motor_control(bool enabled) {
use_automatic_tape_motor_control_ = enabled;
if(!enabled) {
tape_player_.set_motor_control(false);
}
}
Outputs::Speaker::Speaker *get_speaker() final {
return is_zx81 ? &speaker_ : nullptr;
}
void set_tape_is_playing(bool is_playing) final {
tape_player_.set_motor_control(is_playing);
}
void run_for(const Cycles cycles) final {
z80_.run_for(cycles);
}
bool get_tape_is_playing() final {
return tape_player_.motor_control();
}
bool insert_media(const Analyser::Static::Media &media) final {
if(!media.tapes.empty()) {
tape_player_.set_tape(media.tapes.front(), is_zx81 ? TargetPlatform::ZX81 : TargetPlatform::ZX80);
}
// MARK: - Typer timing
HalfCycles get_typer_delay(const std::string &) const final {
return z80_.get_is_resetting() ? Cycles(7'000'000) : Cycles(0);
}
set_use_fast_tape();
return !media.tapes.empty();
}
HalfCycles get_typer_frequency() const final {
return Cycles(159'961);
}
void type_string(const std::string &string) final {
Utility::TypeRecipient<CharacterMapper>::add_typer(string);
}
KeyboardMapper *get_keyboard_mapper() final {
return &keyboard_mapper_;
}
bool can_type(char c) const final {
return Utility::TypeRecipient<CharacterMapper>::can_type(c);
}
// MARK: - Configuration options.
// MARK: - Keyboard
void set_key_state(uint16_t key, bool is_pressed) final {
keyboard_.set_key_state(key, is_pressed);
}
std::unique_ptr<Reflection::Struct> get_options() const final {
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly); // OptionsType is arbitrary, but not optional.
options->automatic_tape_motor_control = use_automatic_tape_motor_control_;
options->quickload = allow_fast_tape_hack_;
return options;
}
void clear_all_keys() final {
keyboard_.clear_all_keys();
}
void set_options(const std::unique_ptr<Reflection::Struct> &str) {
const auto options = dynamic_cast<Options *>(str.get());
set_use_automatic_tape_motor_control(options->automatic_tape_motor_control);
allow_fast_tape_hack_ = options->quickload;
set_use_fast_tape();
}
static constexpr Sinclair::ZX::Keyboard::Machine keyboard_machine() {
return is_zx81 ? Sinclair::ZX::Keyboard::Machine::ZX81 : Sinclair::ZX::Keyboard::Machine::ZX80;
}
private:
CPU::Z80::Processor<ConcreteMachine, false, is_zx81> z80_;
Video video_;
// MARK: - Tape control
// If fast tape loading is enabled then the PC will be trapped at tape_trap_address_;
// the emulator will then do a high-level reinterpretation of the standard ZX80/81 reading
// of a single byte, and the next thing executed will be at tape_return_address_;
static constexpr uint16_t tape_trap_address_ = is_zx81 ? 0x37c : 0x220;
static constexpr uint16_t tape_return_address_ = is_zx81 ? 0x380 : 0x248;
void set_use_automatic_tape_motor_control(bool enabled) {
use_automatic_tape_motor_control_ = enabled;
if(!enabled) {
tape_player_.set_motor_control(false);
}
}
// If automatic tape motor control is enabled then the tape will be permitted to play any time
// the program counter is >= automatic_tape_motor_start_address_ and < automatic_tape_motor_end_address_.
static constexpr uint16_t automatic_tape_motor_start_address_ = is_zx81 ? 0x340 : 0x206;
static constexpr uint16_t automatic_tape_motor_end_address_ = is_zx81 ? 0x3c3 : 0x24d;
void set_tape_is_playing(bool is_playing) final {
tape_player_.set_motor_control(is_playing);
}
// When automatically loading, if the PC gets to the finished_load_address_ in order to print 0/0
// (so it's anything that indicates that loading completed, but the program did not autorun) then the
// emulator will automatically RUN whatever has been loaded.
static constexpr uint16_t finished_load_address_ = is_zx81 ?
0x6d1 : // ZX81: this is the routine that prints 0/0 (i.e. success).
0x203; // ZX80: this is the JR that exits the ZX80's LOAD and returns to MAIN-EXEC.
bool should_autorun_ = false;
bool get_tape_is_playing() final {
return tape_player_.motor_control();
}
std::vector<uint8_t> ram_;
uint16_t ram_mask_, ram_base_;
// MARK: - Typer timing
HalfCycles get_typer_delay(const std::string &) const final {
return z80_.get_is_resetting() ? Cycles(7'000'000) : Cycles(0);
}
std::vector<uint8_t> rom_;
uint16_t rom_mask_;
HalfCycles get_typer_frequency() const final {
return Cycles(159'961);
}
bool vsync_ = false, hsync_ = false;
int line_counter_ = 0;
KeyboardMapper *get_keyboard_mapper() final {
return &keyboard_mapper_;
}
Sinclair::ZX::Keyboard::Keyboard keyboard_;
Sinclair::ZX::Keyboard::KeyboardMapper keyboard_mapper_;
// MARK: - Configuration options.
HalfClockReceiver<Storage::Tape::BinaryTapePlayer> tape_player_;
Storage::Tape::ZX8081::Parser parser_;
std::unique_ptr<Reflection::Struct> get_options() const final {
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly); // OptionsType is arbitrary, but not optional.
options->automatic_tape_motor_control = use_automatic_tape_motor_control_;
options->quickload = allow_fast_tape_hack_;
return options;
}
bool nmi_is_enabled_ = false;
void set_options(const std::unique_ptr<Reflection::Struct> &str) {
const auto options = dynamic_cast<Options *>(str.get());
set_use_automatic_tape_motor_control(options->automatic_tape_motor_control);
allow_fast_tape_hack_ = options->quickload;
set_use_fast_tape();
}
static constexpr auto vsync_start_ = is_zx81 ? HalfCycles(32) : HalfCycles(26);
static constexpr auto vsync_end_ = is_zx81 ? HalfCycles(64) : HalfCycles(66);
HalfCycles horizontal_counter_;
private:
CPU::Z80::Processor<ConcreteMachine, false, is_zx81> z80_;
Video video_;
uint8_t latched_video_byte_ = 0;
bool has_latched_video_byte_ = false;
// If fast tape loading is enabled then the PC will be trapped at tape_trap_address_;
// the emulator will then do a high-level reinterpretation of the standard ZX80/81 reading
// of a single byte, and the next thing executed will be at tape_return_address_;
static constexpr uint16_t tape_trap_address_ = is_zx81 ? 0x37c : 0x220;
static constexpr uint16_t tape_return_address_ = is_zx81 ? 0x380 : 0x248;
bool use_fast_tape_hack_ = false;
bool allow_fast_tape_hack_ = false;
void set_use_fast_tape() {
use_fast_tape_hack_ = allow_fast_tape_hack_ && tape_player_.has_tape();
}
bool use_automatic_tape_motor_control_ = true;
HalfCycles tape_advance_delay_ = 0;
// If automatic tape motor control is enabled then the tape will be permitted to play any time
// the program counter is >= automatic_tape_motor_start_address_ and < automatic_tape_motor_end_address_.
static constexpr uint16_t automatic_tape_motor_start_address_ = is_zx81 ? 0x340 : 0x206;
static constexpr uint16_t automatic_tape_motor_end_address_ = is_zx81 ? 0x3c3 : 0x24d;
// MARK: - Video
inline void set_vsync(bool sync) {
vsync_ = sync;
update_sync();
}
// When automatically loading, if the PC gets to the finished_load_address_ in order to print 0/0
// (so it's anything that indicates that loading completed, but the program did not autorun) then the
// emulator will automatically RUN whatever has been loaded.
static constexpr uint16_t finished_load_address_ = is_zx81 ?
0x6d1 : // ZX81: this is the routine that prints 0/0 (i.e. success).
0x203; // ZX80: this is the JR that exits the ZX80's LOAD and returns to MAIN-EXEC.
bool should_autorun_ = false;
inline void set_hsync(bool sync) {
hsync_ = sync;
update_sync();
}
std::vector<uint8_t> ram_;
uint16_t ram_mask_, ram_base_;
inline void update_sync() {
video_.set_sync(vsync_ || hsync_);
}
std::vector<uint8_t> rom_;
uint16_t rom_mask_;
bool vsync_ = false, hsync_ = false;
int line_counter_ = 0;
Sinclair::ZX::Keyboard::Keyboard keyboard_;
Sinclair::ZX::Keyboard::KeyboardMapper keyboard_mapper_;
HalfClockReceiver<Storage::Tape::BinaryTapePlayer> tape_player_;
Storage::Tape::ZX8081::Parser parser_;
bool nmi_is_enabled_ = false;
static constexpr auto vsync_start_ = is_zx81 ? HalfCycles(32) : HalfCycles(26);
static constexpr auto vsync_end_ = is_zx81 ? HalfCycles(64) : HalfCycles(66);
HalfCycles horizontal_counter_;
uint8_t latched_video_byte_ = 0;
bool has_latched_video_byte_ = false;
bool use_fast_tape_hack_ = false;
bool allow_fast_tape_hack_ = false;
void set_use_fast_tape() {
use_fast_tape_hack_ = allow_fast_tape_hack_ && tape_player_.has_tape();
}
bool use_automatic_tape_motor_control_ = true;
HalfCycles tape_advance_delay_ = 0;
// MARK: - Video
inline void set_vsync(bool sync) {
vsync_ = sync;
update_sync();
}
inline void set_hsync(bool sync) {
hsync_ = sync;
update_sync();
}
inline void update_sync() {
video_.set_sync(vsync_ || hsync_);
}
// MARK: - Audio
Concurrency::AsyncTaskQueue<false> audio_queue_;
using AY = GI::AY38910::AY38910<false>;
AY ay_;
Outputs::Speaker::PullLowpass<AY> speaker_;
HalfCycles time_since_ay_update_;
inline void ay_set_register(uint8_t value) {
update_audio();
GI::AY38910::Utility::select_register(ay_, value);
}
inline void ay_set_data(uint8_t value) {
update_audio();
GI::AY38910::Utility::write_data(ay_, value);
}
inline uint8_t ay_read_data() {
update_audio();
return GI::AY38910::Utility::read(ay_);
}
inline void update_audio() {
speaker_.run_for(audio_queue_, time_since_ay_update_.divide_cycles(Cycles(2)));
}
// MARK: - Audio
Concurrency::AsyncTaskQueue<false> audio_queue_;
using AY = GI::AY38910::AY38910<false>;
AY ay_;
Outputs::Speaker::PullLowpass<AY> speaker_;
HalfCycles time_since_ay_update_;
inline void ay_set_register(uint8_t value) {
update_audio();
GI::AY38910::Utility::select_register(ay_, value);
}
inline void ay_set_data(uint8_t value) {
update_audio();
GI::AY38910::Utility::write_data(ay_, value);
}
inline uint8_t ay_read_data() {
update_audio();
return GI::AY38910::Utility::read(ay_);
}
inline void update_audio() {
speaker_.run_for(audio_queue_, time_since_ay_update_.divide_cycles(Cycles(2)));
}
};
}

View File

@@ -178,25 +178,25 @@ public:
constexpr uint8_t masks[] = {0, 0xff};
#define Output(n) \
{ \
const uint8_t pixels = \
uint8_t(last_fetches_[n] ^ masks[flash_mask_ & (last_fetches_[n+1] >> 7)]); \
\
const uint8_t colours[2] = { \
palette[(last_fetches_[n+1] & 0x78) >> 3], \
palette[((last_fetches_[n+1] & 0x40) >> 3) | (last_fetches_[n+1] & 0x07)], \
}; \
\
pixel_target_[0] = colours[(pixels >> 7) & 1]; \
pixel_target_[1] = colours[(pixels >> 6) & 1]; \
pixel_target_[2] = colours[(pixels >> 5) & 1]; \
pixel_target_[3] = colours[(pixels >> 4) & 1]; \
pixel_target_[4] = colours[(pixels >> 3) & 1]; \
pixel_target_[5] = colours[(pixels >> 2) & 1]; \
pixel_target_[6] = colours[(pixels >> 1) & 1]; \
pixel_target_[7] = colours[(pixels >> 0) & 1]; \
pixel_target_ += 8; \
}
{ \
const uint8_t pixels = \
uint8_t(last_fetches_[n] ^ masks[flash_mask_ & (last_fetches_[n+1] >> 7)]); \
\
const uint8_t colours[2] = { \
palette[(last_fetches_[n+1] & 0x78) >> 3], \
palette[((last_fetches_[n+1] & 0x40) >> 3) | (last_fetches_[n+1] & 0x07)], \
}; \
\
pixel_target_[0] = colours[(pixels >> 7) & 1]; \
pixel_target_[1] = colours[(pixels >> 6) & 1]; \
pixel_target_[2] = colours[(pixels >> 5) & 1]; \
pixel_target_[3] = colours[(pixels >> 4) & 1]; \
pixel_target_[4] = colours[(pixels >> 3) & 1]; \
pixel_target_[5] = colours[(pixels >> 2) & 1]; \
pixel_target_[6] = colours[(pixels >> 1) & 1]; \
pixel_target_[7] = colours[(pixels >> 0) & 1]; \
pixel_target_ += 8; \
}
Output(0);
Output(2);
@@ -454,15 +454,14 @@ private:
friend struct State;
static constexpr uint8_t RGB(const uint8_t r, const uint8_t g, const uint8_t b) {
return uint8_t((r << 4) | (g << 2) | b);
}
#define RGB(r, g, b) (r << 4) | (g << 2) | b
static constexpr uint8_t palette[] = {
RGB(0, 0, 0), RGB(0, 0, 2), RGB(2, 0, 0), RGB(2, 0, 2),
RGB(0, 2, 0), RGB(0, 2, 2), RGB(2, 2, 0), RGB(2, 2, 2),
RGB(0, 0, 0), RGB(0, 0, 3), RGB(3, 0, 0), RGB(3, 0, 3),
RGB(0, 3, 0), RGB(0, 3, 3), RGB(3, 3, 0), RGB(3, 3, 3),
};
#undef RGB
};
struct State: public Reflection::StructImpl<State> {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -30,6 +30,7 @@ enum Name {
AcornADFS,
PRESAdvancedPlus6,
Acorn1770DFS,
AcornIDEADFS103,
// Acorn Archimedes.
AcornArthur030,
@@ -223,8 +224,20 @@ struct Description {
private:
template <typename FileNameT, typename CRC32T> Description(
Name name, std::string machine_name, std::string descriptive_name, FileNameT file_names, size_t size, CRC32T crc32s = uint32_t(0)
) : name{name}, machine_name{machine_name}, descriptive_name{descriptive_name}, file_names{file_names}, size{size}, crc32s{crc32s} {
const Name name,
const char *machine_name,
const char *descriptive_name,
const FileNameT &file_names,
const size_t size,
const CRC32T crc32s = uint32_t(0)
) :
name{name},
machine_name{machine_name},
descriptive_name{descriptive_name},
file_names{file_names},
size{size},
crc32s{crc32s}
{
// Slightly lazy: deal with the case where the constructor wasn't provided with any
// CRCs by spotting that the set has exactly one member, which has value 0. The alternative
// would be to provide a partial specialisation that never put anything into the set.
@@ -232,6 +245,8 @@ private:
this->crc32s.clear();
}
}
static const std::vector<Description> &all_roms();
};
/// @returns a vector of all possible instances of ROM::Description — i.e. descriptions of every ROM

View File

@@ -22,37 +22,37 @@ namespace Utility {
necessary to type that character on a given machine.
*/
class CharacterMapper {
public:
virtual ~CharacterMapper() = default;
public:
virtual ~CharacterMapper() = default;
/// @returns The EndSequence-terminated sequence of keys that would cause @c character to be typed.
virtual const uint16_t *sequence_for_character(char character) const = 0;
/// @returns The EndSequence-terminated sequence of keys that would cause @c character to be typed.
virtual const uint16_t *sequence_for_character(char character) const = 0;
/// The typer will automatically reset all keys in between each sequence that it types.
/// By default it will pause for one key's duration when doing so. Character mappers
/// can eliminate that pause by overriding this method.
/// @returns @c true if the typer should pause after performing a reset; @c false otherwise.
virtual bool needs_pause_after_reset_all_keys() const { return true; }
/// The typer will automatically reset all keys in between each sequence that it types.
/// By default it will pause for one key's duration when doing so. Character mappers
/// can eliminate that pause by overriding this method.
/// @returns @c true if the typer should pause after performing a reset; @c false otherwise.
virtual bool needs_pause_after_reset_all_keys() const { return true; }
/// The typer will pause between every entry in a keyboard sequence. On some machines
/// that may not be necessary — it'll often depends on whether the machine needs time to
/// observe a modifier like shift before it sees the actual keypress.
/// @returns @c true if the typer should pause after forwarding @c key; @c false otherwise.
virtual bool needs_pause_after_key([[maybe_unused]] uint16_t key) const { return true; }
/// The typer will pause between every entry in a keyboard sequence. On some machines
/// that may not be necessary — it'll often depends on whether the machine needs time to
/// observe a modifier like shift before it sees the actual keypress.
/// @returns @c true if the typer should pause after forwarding @c key; @c false otherwise.
virtual bool needs_pause_after_key([[maybe_unused]] uint16_t key) const { return true; }
protected:
using KeySequence = std::array<uint16_t, 16>;
protected:
using KeySequence = std::array<uint16_t, 16>;
/*!
Provided in the base class as a convenience: given the C array of key sequences @c sequences,
returns the sequence for character @c character if it exists; otherwise returns @c nullptr.
*/
template <typename Collection> const uint16_t *table_lookup_sequence_for_character(const Collection &sequences, char character) const {
std::size_t ucharacter = size_t((unsigned char)character);
if(ucharacter >= sizeof(sequences) / sizeof(KeySequence)) return nullptr;
if(sequences[ucharacter][0] == MachineTypes::MappedKeyboardMachine::KeyNotMapped) return nullptr;
return sequences[ucharacter].data();
}
/*!
Provided in the base class as a convenience: given the C array of key sequences @c sequences,
returns the sequence for character @c character if it exists; otherwise returns @c nullptr.
*/
template <typename Collection> const uint16_t *table_lookup_sequence_for_character(const Collection &sequences, char character) const {
std::size_t ucharacter = size_t((unsigned char)character);
if(ucharacter >= sizeof(sequences) / sizeof(KeySequence)) return nullptr;
if(sequences[ucharacter][0] == MachineTypes::MappedKeyboardMachine::KeyNotMapped) return nullptr;
return sequences[ucharacter].data();
}
};
/*!

View File

@@ -16,7 +16,7 @@ namespace Numeric {
/// keeping the least-significant bit in its original position.
///
/// i.e. if @c input is abcdefgh then the result is 0a0b0c0d0e0f0g0h
constexpr uint16_t spread_bits(uint8_t input) {
constexpr uint16_t spread_bits(const uint8_t input) {
uint16_t result = uint16_t(input); // 0000 0000 abcd efgh
result = (result | (result << 4)) & 0x0f0f; // 0000 abcd 0000 efgh
result = (result | (result << 2)) & 0x3333; // 00ab 00cd 00ef 00gh
@@ -26,11 +26,12 @@ constexpr uint16_t spread_bits(uint8_t input) {
/// Performs the opposite action to @c spread_bits; given the 16-bit input
/// @c abcd @c efgh @c ijkl @c mnop, returns the byte value @c bdfhjlnp
/// i.e. every other bit is retained, keeping the least-significant bit in place.
constexpr uint8_t unspread_bits(uint16_t input) {
input &= 0x5555; // 0a0b 0c0d 0e0f 0g0h
input = (input | (input >> 1)) & 0x3333; // 00ab 00cd 00ef 00gh
input = (input | (input >> 2)) & 0x0f0f; // 0000 abcd 0000 efgh
return uint8_t(input | (input >> 4)); // 0000 0000 abcd efgh
constexpr uint8_t unspread_bits(const uint16_t input) {
uint16_t result = input;
result &= 0x5555; // 0a0b 0c0d 0e0f 0g0h
result = (result | (result >> 1)) & 0x3333; // 00ab 00cd 00ef 00gh
result = (result | (result >> 2)) & 0x0f0f; // 0000 abcd 0000 efgh
return uint8_t(result | (result >> 4)); // 0000 0000 abcd efgh
}
}

View File

@@ -61,7 +61,7 @@ public:
An initial value of 0 is invalid.
*/
LFSR(IntType initial_value) : value_(initial_value) {}
LFSR(const IntType initial_value) : value_(initial_value) {}
/*!
Advances the LSFR, returning either an @c IntType of value @c 1 or @c 0,

View File

@@ -30,13 +30,13 @@ namespace Numeric {
template <int... Sizes> class NumericCoder {
public:
/// Modifies @c target to hold @c value at @c index.
template <int index> static void encode(int &target, int value) {
template <int index> static void encode(int &target, const int value) {
static_assert(index < sizeof...(Sizes), "Index must be within range");
NumericEncoder<Sizes...>::template encode<index>(target, value);
}
/// @returns The value from @c source at @c index.
template <int index> static int decode(int source) {
template <int index> static int decode(const int source) {
static_assert(index < sizeof...(Sizes), "Index must be within range");
return NumericDecoder<Sizes...>::template decode<index>(source);
}
@@ -45,7 +45,7 @@ private:
template <int size, int... Tail>
struct NumericEncoder {
template <int index, int i = 0, int divider = 1> static void encode(int &target, int value) {
template <int index, int i = 0, int divider = 1> static void encode(int &target, const int value) {
if constexpr (i == index) {
const int suffix = target % divider;
target /= divider;
@@ -61,7 +61,7 @@ private:
template <int size, int... Tail>
struct NumericDecoder {
template <int index, int i = 0, int divider = 1> static int decode(int source) {
template <int index, int i = 0, int divider = 1> static int decode(const int source) {
if constexpr (i == index) {
return (source / divider) % size;
} else {

View File

@@ -62,7 +62,7 @@
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
enableASanStackUseAfterReturn = "YES"

View File

@@ -76,7 +76,7 @@ enum class EnabledLevel {
constexpr EnabledLevel enabled_level(const Source source) {
#ifdef NDEBUG
return false;
return EnabledLevel::None;
#endif
// Allow for compile-time source-level enabling and disabling of different sources.

View File

@@ -24,7 +24,7 @@ GLuint Shader::compile_shader(const std::string &source, GLenum type) {
test_gl(glShaderSource, shader, 1, &c_str, NULL);
test_gl(glCompileShader, shader);
if constexpr (logger.enabled) {
if constexpr (logger.ErrorsEnabled) {
GLint isCompiled = 0;
test_gl(glGetShaderiv, shader, GL_COMPILE_STATUS, &isCompiled);
if(isCompiled == GL_FALSE) {
@@ -69,7 +69,7 @@ void Shader::init(const std::string &vertex_shader, const std::string &fragment_
for(const auto &binding : attribute_bindings) {
test_gl(glBindAttribLocation, shader_program_, binding.index, binding.name.c_str());
if constexpr (logger.enabled) {
if constexpr (logger.ErrorsEnabled) {
const auto error = glGetError();
switch(error) {
case 0: break;
@@ -88,7 +88,7 @@ void Shader::init(const std::string &vertex_shader, const std::string &fragment_
test_gl(glLinkProgram, shader_program_);
if constexpr (logger.enabled) {
if constexpr (logger.ErrorsEnabled) {
GLint logLength;
test_gl(glGetProgramiv, shader_program_, GL_INFO_LOG_LENGTH, &logLength);
if(logLength > 0) {

View File

@@ -56,45 +56,45 @@ template <Action action, typename IteratorT, typename SampleT> void fill(Iterato
*/
template <typename SourceT, bool stereo>
class BufferSource {
public:
/*!
Indicates whether this component will write stereo samples.
*/
static constexpr bool is_stereo = stereo;
public:
/*!
Indicates whether this component will write stereo samples.
*/
static constexpr bool is_stereo = stereo;
/*!
Should 'apply' the next @c number_of_samples to @c target ; application means applying @c action which can be achieved either via the
helper functions above — @c apply and @c fill — or by semantic inspection (primarily, if an obvious quick route for @c Action::Ignore is available).
/*!
Should 'apply' the next @c number_of_samples to @c target ; application means applying @c action which can be achieved either via the
helper functions above — @c apply and @c fill — or by semantic inspection (primarily, if an obvious quick route for @c Action::Ignore is available).
No default implementation is provided.
*/
template <Action action>
void apply_samples(std::size_t number_of_samples, typename SampleT<stereo>::type *target);
No default implementation is provided.
*/
template <Action action>
void apply_samples(std::size_t number_of_samples, typename SampleT<stereo>::type *target);
/*!
@returns @c true if it is trivially true that a call to get_samples would just
fill the target with zeroes; @c false if a call might return all zeroes or
might not.
*/
// bool is_zero_level() const { return false; }
/*!
@returns @c true if it is trivially true that a call to get_samples would just
fill the target with zeroes; @c false if a call might return all zeroes or
might not.
*/
// bool is_zero_level() const { return false; }
/*!
Sets the proper output range for this sample source; it should write values
between 0 and volume.
*/
// void set_sample_volume_range(std::int16_t volume);
/*!
Sets the proper output range for this sample source; it should write values
between 0 and volume.
*/
// void set_sample_volume_range(std::int16_t volume);
/*!
Permits a sample source to declare that, averaged over time, it will use only
a certain proportion of the allocated volume range. This commonly happens
in sample sources that use a time-multiplexed sound output — for example, if
one were to output only every other sample then it would return 0.5.
/*!
Permits a sample source to declare that, averaged over time, it will use only
a certain proportion of the allocated volume range. This commonly happens
in sample sources that use a time-multiplexed sound output — for example, if
one were to output only every other sample then it would return 0.5.
This is permitted to vary over time but there is no contract as to when it will be
used by a speaker. If it varies, it should do so very infrequently and only to
represent changes in hardware configuration.
*/
double average_output_peak() const { return 1.0; }
This is permitted to vary over time but there is no contract as to when it will be
used by a speaker. If it varies, it should do so very infrequently and only to
represent changes in hardware configuration.
*/
double average_output_peak() const { return 1.0; }
};
///

View File

@@ -8,7 +8,8 @@ DFS-1770-2.20.rom — used only if the user opens a DFS disk image
ADFS-E00_1.rom — used only if the user opens an ADFS disk image
ADFS-E00_2.rom
AP6v133.rom — used only if the user opens a disk image that makes use of any of the commands given below.
adfs.rom - used only if the user opens a hard disk image
adfs.rom used only if the user opens a hard disk image or requests a SCSI interface
ELK103.rom — used only if the user requests an IDE interface
Possibly to be desired in the future:
* os300.rom