mirror of
https://github.com/TomHarte/CLK.git
synced 2025-10-25 09:27:01 +00:00
Compare commits
193 Commits
2017-08-27
...
2017-11-03
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd10c42433 | ||
|
|
794437f20f | ||
|
|
23d5849cda | ||
|
|
5070a8414f | ||
|
|
5a3ca0e447 | ||
|
|
e384c50580 | ||
|
|
b9734278f6 | ||
|
|
f807a6b608 | ||
|
|
833f8c02a4 | ||
|
|
0248c6a282 | ||
|
|
218b976dbc | ||
|
|
513903890e | ||
|
|
1157bde453 | ||
|
|
46345c6a3e | ||
|
|
c13f8e5390 | ||
|
|
ad9df4bb90 | ||
|
|
e983854e71 | ||
|
|
ec999446e8 | ||
|
|
5e3e91373a | ||
|
|
c52348d8d7 | ||
|
|
9e0907ee76 | ||
|
|
9ad4025138 | ||
|
|
405f58d6a3 | ||
|
|
afbd1c425c | ||
|
|
b2c1b83fcd | ||
|
|
8d2b9a581a | ||
|
|
1825af0dd3 | ||
|
|
c2f6799f0c | ||
|
|
b5b6219cb7 | ||
|
|
185a699279 | ||
|
|
96b8f9ae9f | ||
|
|
88e2350b8f | ||
|
|
5c141af734 | ||
|
|
da580e4186 | ||
|
|
57ee09dffb | ||
|
|
7c8e830b90 | ||
|
|
ba5f668338 | ||
|
|
2c1e99858b | ||
|
|
7f2febeec9 | ||
|
|
2d7a4fe5f0 | ||
|
|
91b867a7b3 | ||
|
|
3944e734d3 | ||
|
|
ce78d9d12c | ||
|
|
edbc60a3fb | ||
|
|
6ea3ff62df | ||
|
|
88959571f1 | ||
|
|
b4583e976e | ||
|
|
92d9805f09 | ||
|
|
0c2dd62328 | ||
|
|
3f4d90d775 | ||
|
|
542ec4312f | ||
|
|
18798c9886 | ||
|
|
7aaf27389c | ||
|
|
ee179aa7bd | ||
|
|
3a05ce36de | ||
|
|
4f289ab10b | ||
|
|
78ee46270b | ||
|
|
edb632af52 | ||
|
|
19c03a08a6 | ||
|
|
44cdc124af | ||
|
|
b37787a414 | ||
|
|
53b99ea248 | ||
|
|
97a2be71e3 | ||
|
|
f623bff5c3 | ||
|
|
2511fc8401 | ||
|
|
d37ec9e5b0 | ||
|
|
95c82f5b36 | ||
|
|
ec202ed8be | ||
|
|
7190225603 | ||
|
|
52e7cabd4e | ||
|
|
064f1dfdbc | ||
|
|
f40e1fd840 | ||
|
|
e194a2a015 | ||
|
|
c39759333a | ||
|
|
edb9fd301c | ||
|
|
ea5023ac26 | ||
|
|
0fb363ea0e | ||
|
|
1cc85615d5 | ||
|
|
7b01c1bee6 | ||
|
|
35705c5345 | ||
|
|
f41da83d97 | ||
|
|
cd1e5dea4d | ||
|
|
ef605eda51 | ||
|
|
2f48ee59fa | ||
|
|
f86729c4ac | ||
|
|
5f99f4442c | ||
|
|
326857a84d | ||
|
|
5dd3945695 | ||
|
|
19eb975c73 | ||
|
|
698ffca51b | ||
|
|
fe3cc5c57c | ||
|
|
f488854720 | ||
|
|
51c0c45e04 | ||
|
|
c3e1489a8e | ||
|
|
e3420f62c6 | ||
|
|
970c80f2e3 | ||
|
|
9f4a407f94 | ||
|
|
5dda897334 | ||
|
|
3982e375e3 | ||
|
|
a8524daecb | ||
|
|
d1ce764201 | ||
|
|
8875982e1f | ||
|
|
3319a4f589 | ||
|
|
c7f27b2db4 | ||
|
|
631f630549 | ||
|
|
2a08bd9ecc | ||
|
|
f789ee4ff0 | ||
|
|
a295b42497 | ||
|
|
d8337492cc | ||
|
|
15c8debc16 | ||
|
|
67af153c16 | ||
|
|
d72dad2d1a | ||
|
|
698e4fe550 | ||
|
|
b5406b90cd | ||
|
|
05a93ba237 | ||
|
|
77548d14db | ||
|
|
b85dd608e7 | ||
|
|
231f13d810 | ||
|
|
704bfa114c | ||
|
|
44a56724cb | ||
|
|
5fbea625ae | ||
|
|
ac57b37e96 | ||
|
|
e3e9baeaa4 | ||
|
|
e071123f90 | ||
|
|
98adb01721 | ||
|
|
d6a5f9a29e | ||
|
|
0d84b4b9dd | ||
|
|
a85909198f | ||
|
|
98751e6ac8 | ||
|
|
da082673d7 | ||
|
|
35fe4d50d4 | ||
|
|
b835cb73e2 | ||
|
|
662d031e3c | ||
|
|
bf20c717fb | ||
|
|
4d4a0cf1d2 | ||
|
|
b62f3e726a | ||
|
|
82b13e98f2 | ||
|
|
9ac831b09c | ||
|
|
42616da7ff | ||
|
|
2f13517f38 | ||
|
|
fb9fd26af7 | ||
|
|
d3c385b471 | ||
|
|
96bf133924 | ||
|
|
6d6cac429d | ||
|
|
dc0b65f9c9 | ||
|
|
8882aa496f | ||
|
|
0622187ddf | ||
|
|
523e1288fa | ||
|
|
1a96cce26f | ||
|
|
a4e275e1fc | ||
|
|
6075064400 | ||
|
|
ff6e65cca9 | ||
|
|
90d2347c90 | ||
|
|
90c7056d12 | ||
|
|
fed2bc9fc9 | ||
|
|
ff510f3b84 | ||
|
|
3b12fca417 | ||
|
|
8eeb7e73cd | ||
|
|
7fd6699e0b | ||
|
|
ed70b15fc9 | ||
|
|
ff24e1de31 | ||
|
|
6547102511 | ||
|
|
d538ff5039 | ||
|
|
a49594c6a3 | ||
|
|
3544c0f014 | ||
|
|
f26fe3756c | ||
|
|
a42ca290cb | ||
|
|
da09098e49 | ||
|
|
450712f39c | ||
|
|
24b3faa427 | ||
|
|
40d11ea0e3 | ||
|
|
ab2bcb939f | ||
|
|
45499050b6 | ||
|
|
0c9197df30 | ||
|
|
a1e200cc65 | ||
|
|
8a612bb6ab | ||
|
|
e6ac939ae0 | ||
|
|
b034d4e6f8 | ||
|
|
de218611e4 | ||
|
|
615f7ce176 | ||
|
|
b306776ba9 | ||
|
|
0f85cffc78 | ||
|
|
96648df5fe | ||
|
|
2c99a2d6ec | ||
|
|
4af333d5ec | ||
|
|
a5f9869769 | ||
|
|
f10be2a18a | ||
|
|
c88d627b4e | ||
|
|
b30bb2a234 | ||
|
|
d498080eb4 | ||
|
|
334afbc710 | ||
|
|
17c13624e5 | ||
|
|
113349d272 |
@@ -161,7 +161,7 @@ class HalfCycles: public WrappedInt<HalfCycles> {
|
||||
inline HalfCycles(int l) : WrappedInt<HalfCycles>(l) {}
|
||||
inline HalfCycles() : WrappedInt<HalfCycles>() {}
|
||||
|
||||
inline HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() << 1) {}
|
||||
inline HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {}
|
||||
inline HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {}
|
||||
|
||||
/// @returns The number of whole cycles completely covered by this span of half cycles.
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
#include "1770.hpp"
|
||||
#include "../../Storage/Disk/Encodings/MFM.hpp"
|
||||
#include "../../Storage/Disk/Encodings/MFM/Constants.hpp"
|
||||
|
||||
using namespace WD;
|
||||
|
||||
@@ -25,8 +25,8 @@ WD1770::Status::Status() :
|
||||
busy(false) {}
|
||||
|
||||
WD1770::WD1770(Personality p) :
|
||||
Storage::Disk::MFMController(8000000, 16, 300),
|
||||
interesting_event_mask_((int)Event1770::Command),
|
||||
Storage::Disk::MFMController(8000000),
|
||||
interesting_event_mask_(static_cast<int>(Event1770::Command)),
|
||||
resume_point_(0),
|
||||
delay_time_(0),
|
||||
index_hole_count_target_(-1),
|
||||
@@ -34,7 +34,7 @@ WD1770::WD1770(Personality p) :
|
||||
personality_(p),
|
||||
head_is_loaded_(false) {
|
||||
set_is_double_density(false);
|
||||
posit_event((int)Event1770::Command);
|
||||
posit_event(static_cast<int>(Event1770::Command));
|
||||
}
|
||||
|
||||
void WD1770::set_register(int address, uint8_t value) {
|
||||
@@ -47,7 +47,7 @@ void WD1770::set_register(int address, uint8_t value) {
|
||||
});
|
||||
} else {
|
||||
command_ = value;
|
||||
posit_event((int)Event1770::Command);
|
||||
posit_event(static_cast<int>(Event1770::Command));
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -75,7 +75,7 @@ uint8_t WD1770::get_register(int address) {
|
||||
switch(status_.type) {
|
||||
case Status::One:
|
||||
status |=
|
||||
(get_is_track_zero() ? Flag::TrackZero : 0) |
|
||||
(get_drive().get_is_track_zero() ? Flag::TrackZero : 0) |
|
||||
(status_.seek_error ? Flag::SeekError : 0);
|
||||
// TODO: index hole
|
||||
break;
|
||||
@@ -91,11 +91,11 @@ uint8_t WD1770::get_register(int address) {
|
||||
}
|
||||
|
||||
if(!has_motor_on_line()) {
|
||||
status |= get_drive_is_ready() ? 0 : Flag::NotReady;
|
||||
status |= get_drive().get_is_ready() ? 0 : Flag::NotReady;
|
||||
if(status_.type == Status::One)
|
||||
status |= (head_is_loaded_ ? Flag::HeadLoaded : 0);
|
||||
} else {
|
||||
status |= (get_motor_on() ? Flag::MotorOn : 0);
|
||||
status |= (get_drive().get_motor_on() ? Flag::MotorOn : 0);
|
||||
if(status_.type == Status::One)
|
||||
status |= (status_.spin_up ? Flag::SpinUp : 0);
|
||||
}
|
||||
@@ -115,24 +115,24 @@ void WD1770::run_for(const Cycles cycles) {
|
||||
Storage::Disk::Controller::run_for(cycles);
|
||||
|
||||
if(delay_time_) {
|
||||
unsigned int number_of_cycles = (unsigned int)cycles.as_int();
|
||||
unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_int());
|
||||
if(delay_time_ <= number_of_cycles) {
|
||||
delay_time_ = 0;
|
||||
posit_event((int)Event1770::Timer);
|
||||
posit_event(static_cast<int>(Event1770::Timer));
|
||||
} else {
|
||||
delay_time_ -= number_of_cycles;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = (int)mask; return; case __LINE__:
|
||||
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(mask); return; case __LINE__:
|
||||
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; delay_time_ = ms * 8000; WAIT_FOR_EVENT(Event1770::Timer);
|
||||
#define WAIT_FOR_BYTES(count) resume_point_ = __LINE__; distance_into_section_ = 0; WAIT_FOR_EVENT(Event::Token); if(get_latest_token().type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = (int)Event::Token; return; }
|
||||
#define WAIT_FOR_BYTES(count) resume_point_ = __LINE__; distance_into_section_ = 0; WAIT_FOR_EVENT(Event::Token); if(get_latest_token().type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = static_cast<int>(Event::Token); return; }
|
||||
#define BEGIN_SECTION() switch(resume_point_) { default:
|
||||
#define END_SECTION() 0; }
|
||||
|
||||
#define READ_ID() \
|
||||
if(new_event_type == (int)Event::Token) { \
|
||||
if(new_event_type == static_cast<int>(Event::Token)) { \
|
||||
if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; } \
|
||||
else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) { \
|
||||
header_[distance_into_section_ - 1] = get_latest_token().byte_value; \
|
||||
@@ -169,10 +169,10 @@ void WD1770::run_for(const Cycles cycles) {
|
||||
// +--------+----------+-------------------------+
|
||||
|
||||
void WD1770::posit_event(int new_event_type) {
|
||||
if(new_event_type == (int)Event::IndexHole) {
|
||||
if(new_event_type == static_cast<int>(Event::IndexHole)) {
|
||||
index_hole_count_++;
|
||||
if(index_hole_count_target_ == index_hole_count_) {
|
||||
posit_event((int)Event1770::IndexHoleTarget);
|
||||
posit_event(static_cast<int>(Event1770::IndexHoleTarget));
|
||||
index_hole_count_target_ = -1;
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
}
|
||||
}
|
||||
|
||||
if(!(interesting_event_mask_ & (int)new_event_type)) return;
|
||||
if(!(interesting_event_mask_ & static_cast<int>(new_event_type))) return;
|
||||
interesting_event_mask_ &= ~new_event_type;
|
||||
|
||||
Status new_status;
|
||||
@@ -257,7 +257,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
goto test_type1_type;
|
||||
|
||||
begin_type1_spin_up:
|
||||
if((command_&0x08) || get_motor_on()) goto test_type1_type;
|
||||
if((command_&0x08) || get_drive().get_motor_on()) goto test_type1_type;
|
||||
SPIN_UP();
|
||||
|
||||
test_type1_type:
|
||||
@@ -280,11 +280,11 @@ void WD1770::posit_event(int new_event_type) {
|
||||
if(step_direction_) track_++; else track_--;
|
||||
|
||||
perform_step:
|
||||
if(!step_direction_ && get_is_track_zero()) {
|
||||
if(!step_direction_ && get_drive().get_is_track_zero()) {
|
||||
track_ = 0;
|
||||
goto verify;
|
||||
}
|
||||
step(step_direction_ ? 1 : -1);
|
||||
get_drive().step(step_direction_ ? 1 : -1);
|
||||
unsigned int time_to_wait;
|
||||
switch(command_ & 3) {
|
||||
default:
|
||||
@@ -310,7 +310,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
distance_into_section_ = 0;
|
||||
|
||||
verify_read_data:
|
||||
WAIT_FOR_EVENT((int)Event::IndexHole | (int)Event::Token);
|
||||
WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
|
||||
READ_ID();
|
||||
|
||||
if(index_hole_count_ == 6) {
|
||||
@@ -376,7 +376,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
goto test_type2_delay;
|
||||
|
||||
begin_type2_spin_up:
|
||||
if(get_motor_on()) goto test_type2_delay;
|
||||
if(get_drive().get_motor_on()) goto test_type2_delay;
|
||||
// Perform spin up.
|
||||
SPIN_UP();
|
||||
|
||||
@@ -386,7 +386,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
WAIT_FOR_TIME(30);
|
||||
|
||||
test_type2_write_protection:
|
||||
if(command_&0x20 && get_drive_is_read_only()) {
|
||||
if(command_&0x20 && get_drive().get_is_read_only()) {
|
||||
update_status([] (Status &status) {
|
||||
status.write_protect = true;
|
||||
});
|
||||
@@ -394,7 +394,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
}
|
||||
|
||||
type2_get_header:
|
||||
WAIT_FOR_EVENT((int)Event::IndexHole | (int)Event::Token);
|
||||
WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
|
||||
READ_ID();
|
||||
|
||||
if(index_hole_count_ == 5) {
|
||||
@@ -594,7 +594,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
goto type3_test_delay;
|
||||
|
||||
begin_type3_spin_up:
|
||||
if((command_&0x08) || get_motor_on()) goto type3_test_delay;
|
||||
if((command_&0x08) || get_drive().get_motor_on()) goto type3_test_delay;
|
||||
SPIN_UP();
|
||||
|
||||
type3_test_delay:
|
||||
@@ -611,8 +611,8 @@ void WD1770::posit_event(int new_event_type) {
|
||||
distance_into_section_ = 0;
|
||||
|
||||
read_address_get_header:
|
||||
WAIT_FOR_EVENT((int)Event::IndexHole | (int)Event::Token);
|
||||
if(new_event_type == (int)Event::Token) {
|
||||
WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
|
||||
if(new_event_type == static_cast<int>(Event::Token)) {
|
||||
if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; }
|
||||
else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) {
|
||||
if(status_.data_request) {
|
||||
@@ -652,7 +652,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
index_hole_count_ = 0;
|
||||
|
||||
read_track_read_byte:
|
||||
WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole);
|
||||
WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
|
||||
if(index_hole_count_) {
|
||||
goto wait_for_command;
|
||||
}
|
||||
@@ -675,7 +675,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
});
|
||||
|
||||
write_track_test_write_protect:
|
||||
if(get_drive_is_read_only()) {
|
||||
if(get_drive().get_is_read_only()) {
|
||||
update_status([] (Status &status) {
|
||||
status.write_protect = true;
|
||||
});
|
||||
@@ -720,7 +720,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
case 0xfd: case 0xfe:
|
||||
// clock is 0xc7 = 1010 0000 0010 1010 = 0xa022
|
||||
write_raw_short(
|
||||
(uint16_t)(
|
||||
static_cast<uint16_t>(
|
||||
0xa022 |
|
||||
((data_ & 0x80) << 7) |
|
||||
((data_ & 0x40) << 6) |
|
||||
@@ -781,8 +781,9 @@ void WD1770::update_status(std::function<void(Status &)> updater) {
|
||||
}
|
||||
|
||||
void WD1770::set_head_load_request(bool head_load) {}
|
||||
void WD1770::set_motor_on(bool motor_on) {}
|
||||
|
||||
void WD1770::set_head_loaded(bool head_loaded) {
|
||||
head_is_loaded_ = head_loaded;
|
||||
if(head_loaded) posit_event((int)Event1770::HeadLoad);
|
||||
if(head_loaded) posit_event(static_cast<int>(Event1770::HeadLoad));
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#ifndef _770_hpp
|
||||
#define _770_hpp
|
||||
|
||||
#include "../../Storage/Disk/MFMDiskController.hpp"
|
||||
#include "../../Storage/Disk/Controller/MFMDiskController.hpp"
|
||||
|
||||
namespace WD {
|
||||
|
||||
@@ -76,6 +76,7 @@ class WD1770: public Storage::Disk::MFMController {
|
||||
|
||||
protected:
|
||||
virtual void set_head_load_request(bool head_load);
|
||||
virtual void set_motor_on(bool motor_on);
|
||||
void set_head_loaded(bool head_loaded);
|
||||
|
||||
private:
|
||||
|
||||
@@ -13,9 +13,83 @@
|
||||
#include <typeinfo>
|
||||
#include <cstdio>
|
||||
|
||||
#include "Implementation/6522Storage.hpp"
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace MOS {
|
||||
namespace MOS6522 {
|
||||
|
||||
enum Port {
|
||||
A = 0,
|
||||
B = 1
|
||||
};
|
||||
|
||||
enum Line {
|
||||
One = 0,
|
||||
Two = 1
|
||||
};
|
||||
|
||||
/*!
|
||||
Provides the mechanism for just-int-time communication from a 6522; the normal use case is to compose a
|
||||
6522 and a subclass of PortHandler in order to reproduce a 6522 and its original bus wiring.
|
||||
*/
|
||||
class PortHandler {
|
||||
public:
|
||||
/// Requests the current input value of @c port from the port handler.
|
||||
uint8_t get_port_input(Port port) { return 0xff; }
|
||||
|
||||
/// Sets the current output value of @c port and provides @c direction_mask, indicating which pins are marked as output.
|
||||
void set_port_output(Port port, uint8_t value, uint8_t direction_mask) {}
|
||||
|
||||
/// Sets the current logical output level for line @c line on port @c port.
|
||||
void set_control_line_output(Port port, Line line, bool value) {}
|
||||
|
||||
/// Sets the current logical value of the interrupt line.
|
||||
void set_interrupt_status(bool status) {}
|
||||
};
|
||||
|
||||
/*!
|
||||
Provided as an optional alternative base to @c PortHandler for port handlers; via the delegate pattern adds
|
||||
a virtual level of indirection for receiving changes to the interrupt line.
|
||||
*/
|
||||
class IRQDelegatePortHandler: public PortHandler {
|
||||
public:
|
||||
class Delegate {
|
||||
public:
|
||||
/// Indicates that the interrupt status has changed for the IRQDelegatePortHandler provided.
|
||||
virtual void mos6522_did_change_interrupt_status(void *irq_delegate) = 0;
|
||||
};
|
||||
|
||||
/// Sets the delegate that will receive notification of changes in the interrupt line.
|
||||
void set_interrupt_delegate(Delegate *delegate);
|
||||
|
||||
/// Overrides PortHandler::set_interrupt_status, notifying the delegate if one is set.
|
||||
void set_interrupt_status(bool new_status);
|
||||
|
||||
private:
|
||||
Delegate *delegate_ = nullptr;
|
||||
};
|
||||
|
||||
class MOS6522Base: public MOS6522Storage {
|
||||
public:
|
||||
/// Sets the input value of line @c line on port @c port.
|
||||
void set_control_line_input(Port port, Line line, bool value);
|
||||
|
||||
/// Runs for a specified number of half cycles.
|
||||
void run_for(const HalfCycles half_cycles);
|
||||
|
||||
/// Runs for a specified number of cycles.
|
||||
void run_for(const Cycles cycles);
|
||||
|
||||
/// @returns @c true if the IRQ line is currently active; @c false otherwise.
|
||||
bool get_interrupt_line();
|
||||
|
||||
private:
|
||||
inline void do_phase1();
|
||||
inline void do_phase2();
|
||||
virtual void reevaluate_interrupts() = 0;
|
||||
};
|
||||
|
||||
/*!
|
||||
Implements a template for emulation of the MOS 6522 Versatile Interface Adaptor ('VIA').
|
||||
@@ -28,353 +102,27 @@ namespace MOS {
|
||||
Consumers should derive their own curiously-recurring-template-pattern subclass,
|
||||
implementing bus communications as required.
|
||||
*/
|
||||
template <class T> class MOS6522 {
|
||||
private:
|
||||
enum InterruptFlag: uint8_t {
|
||||
CA2ActiveEdge = 1 << 0,
|
||||
CA1ActiveEdge = 1 << 1,
|
||||
ShiftRegister = 1 << 2,
|
||||
CB2ActiveEdge = 1 << 3,
|
||||
CB1ActiveEdge = 1 << 4,
|
||||
Timer2 = 1 << 5,
|
||||
Timer1 = 1 << 6,
|
||||
};
|
||||
|
||||
template <class T> class MOS6522: public MOS6522Base {
|
||||
public:
|
||||
enum Port {
|
||||
A = 0,
|
||||
B = 1
|
||||
};
|
||||
|
||||
enum Line {
|
||||
One = 0,
|
||||
Two = 1
|
||||
};
|
||||
MOS6522(T &bus_handler) noexcept : bus_handler_(bus_handler) {}
|
||||
MOS6522(const MOS6522 &) = delete;
|
||||
|
||||
/*! Sets a register value. */
|
||||
inline void set_register(int address, uint8_t value) {
|
||||
address &= 0xf;
|
||||
// printf("6522 [%s]: %0x <- %02x\n", typeid(*this).name(), address, value);
|
||||
switch(address) {
|
||||
case 0x0:
|
||||
registers_.output[1] = value;
|
||||
static_cast<T *>(this)->set_port_output(Port::B, value, registers_.data_direction[1]); // TODO: handshake
|
||||
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
case 0xf:
|
||||
case 0x1:
|
||||
registers_.output[0] = value;
|
||||
static_cast<T *>(this)->set_port_output(Port::A, value, registers_.data_direction[0]); // TODO: handshake
|
||||
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge));
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
// // No handshake, so write directly
|
||||
// registers_.output[0] = value;
|
||||
// static_cast<T *>(this)->set_port_output(0, value);
|
||||
// break;
|
||||
|
||||
case 0x2:
|
||||
registers_.data_direction[1] = value;
|
||||
break;
|
||||
case 0x3:
|
||||
registers_.data_direction[0] = value;
|
||||
break;
|
||||
|
||||
// Timer 1
|
||||
case 0x6: case 0x4: registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value; break;
|
||||
case 0x5: case 0x7:
|
||||
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | (uint16_t)(value << 8);
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
|
||||
if(address == 0x05) {
|
||||
registers_.next_timer[0] = registers_.timer_latch[0];
|
||||
timer_is_running_[0] = true;
|
||||
}
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
|
||||
// Timer 2
|
||||
case 0x8: registers_.timer_latch[1] = value; break;
|
||||
case 0x9:
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
|
||||
registers_.next_timer[1] = registers_.timer_latch[1] | (uint16_t)(value << 8);
|
||||
timer_is_running_[1] = true;
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
|
||||
// Shift
|
||||
case 0xa: registers_.shift = value; break;
|
||||
|
||||
// Control
|
||||
case 0xb:
|
||||
registers_.auxiliary_control = value;
|
||||
break;
|
||||
case 0xc:
|
||||
// printf("Peripheral control %02x\n", value);
|
||||
registers_.peripheral_control = value;
|
||||
|
||||
// TODO: simplify below; trying to avoid improper logging of unimplemented warnings in input mode
|
||||
if(value & 0x08) {
|
||||
switch(value & 0x0e) {
|
||||
default: printf("Unimplemented control line mode %d\n", (value >> 1)&7); break;
|
||||
case 0x0c: static_cast<T *>(this)->set_control_line_output(Port::A, Line::Two, false); break;
|
||||
case 0x0e: static_cast<T *>(this)->set_control_line_output(Port::A, Line::Two, true); break;
|
||||
}
|
||||
}
|
||||
if(value & 0x80) {
|
||||
switch(value & 0xe0) {
|
||||
default: printf("Unimplemented control line mode %d\n", (value >> 5)&7); break;
|
||||
case 0xc0: static_cast<T *>(this)->set_control_line_output(Port::B, Line::Two, false); break;
|
||||
case 0xe0: static_cast<T *>(this)->set_control_line_output(Port::B, Line::Two, true); break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// Interrupt control
|
||||
case 0xd:
|
||||
registers_.interrupt_flags &= ~value;
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
case 0xe:
|
||||
if(value&0x80)
|
||||
registers_.interrupt_enable |= value;
|
||||
else
|
||||
registers_.interrupt_enable &= ~value;
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
}
|
||||
}
|
||||
void set_register(int address, uint8_t value);
|
||||
|
||||
/*! Gets a register value. */
|
||||
inline uint8_t get_register(int address) {
|
||||
address &= 0xf;
|
||||
// printf("6522 %p: %d\n", this, address);
|
||||
switch(address) {
|
||||
case 0x0:
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
|
||||
reevaluate_interrupts();
|
||||
return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]);
|
||||
case 0xf: // TODO: handshake, latching
|
||||
case 0x1:
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
|
||||
reevaluate_interrupts();
|
||||
return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0]);
|
||||
|
||||
case 0x2: return registers_.data_direction[1];
|
||||
case 0x3: return registers_.data_direction[0];
|
||||
|
||||
// Timer 1
|
||||
case 0x4:
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
|
||||
reevaluate_interrupts();
|
||||
return registers_.timer[0] & 0x00ff;
|
||||
case 0x5: return registers_.timer[0] >> 8;
|
||||
case 0x6: return registers_.timer_latch[0] & 0x00ff;
|
||||
case 0x7: return registers_.timer_latch[0] >> 8;
|
||||
|
||||
// Timer 2
|
||||
case 0x8:
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
|
||||
reevaluate_interrupts();
|
||||
return registers_.timer[1] & 0x00ff;
|
||||
case 0x9: return registers_.timer[1] >> 8;
|
||||
|
||||
case 0xa: return registers_.shift;
|
||||
|
||||
case 0xb: return registers_.auxiliary_control;
|
||||
case 0xc: return registers_.peripheral_control;
|
||||
|
||||
case 0xd: return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00);
|
||||
case 0xe: return registers_.interrupt_enable | 0x80;
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
inline void set_control_line_input(Port port, Line line, bool value) {
|
||||
switch(line) {
|
||||
case Line::One:
|
||||
if( value != control_inputs_[port].line_one &&
|
||||
value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01))
|
||||
) {
|
||||
registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
control_inputs_[port].line_one = value;
|
||||
break;
|
||||
|
||||
case Line::Two:
|
||||
// TODO: output modes, but probably elsewhere?
|
||||
if( value != control_inputs_[port].line_two && // i.e. value has changed ...
|
||||
!(registers_.peripheral_control & (port ? 0x80 : 0x08)) && // ... and line is input ...
|
||||
value == !!(registers_.peripheral_control & (port ? 0x40 : 0x04)) // ... and it's either high or low, as required
|
||||
) {
|
||||
registers_.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
control_inputs_[port].line_two = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#define phase2() \
|
||||
registers_.last_timer[0] = registers_.timer[0];\
|
||||
registers_.last_timer[1] = registers_.timer[1];\
|
||||
\
|
||||
if(registers_.timer_needs_reload) {\
|
||||
registers_.timer_needs_reload = false;\
|
||||
registers_.timer[0] = registers_.timer_latch[0];\
|
||||
}\
|
||||
else\
|
||||
registers_.timer[0] --;\
|
||||
\
|
||||
registers_.timer[1] --; \
|
||||
if(registers_.next_timer[0] >= 0) { registers_.timer[0] = (uint16_t)registers_.next_timer[0]; registers_.next_timer[0] = -1; }\
|
||||
if(registers_.next_timer[1] >= 0) { registers_.timer[1] = (uint16_t)registers_.next_timer[1]; registers_.next_timer[1] = -1; }\
|
||||
|
||||
// IRQ is raised on the half cycle after overflow
|
||||
#define phase1() \
|
||||
if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1]) {\
|
||||
timer_is_running_[1] = false;\
|
||||
registers_.interrupt_flags |= InterruptFlag::Timer2;\
|
||||
reevaluate_interrupts();\
|
||||
}\
|
||||
\
|
||||
if((registers_.timer[0] == 0xffff) && !registers_.last_timer[0] && timer_is_running_[0]) {\
|
||||
registers_.interrupt_flags |= InterruptFlag::Timer1;\
|
||||
reevaluate_interrupts();\
|
||||
\
|
||||
if(registers_.auxiliary_control&0x40)\
|
||||
registers_.timer_needs_reload = true;\
|
||||
else\
|
||||
timer_is_running_[0] = false;\
|
||||
}
|
||||
|
||||
/*! Runs for a specified number of half cycles. */
|
||||
inline void run_for(const HalfCycles half_cycles) {
|
||||
int number_of_half_cycles = half_cycles.as_int();
|
||||
|
||||
if(is_phase2_) {
|
||||
phase2();
|
||||
number_of_half_cycles--;
|
||||
}
|
||||
|
||||
while(number_of_half_cycles >= 2) {
|
||||
phase1();
|
||||
phase2();
|
||||
number_of_half_cycles -= 2;
|
||||
}
|
||||
|
||||
if(number_of_half_cycles) {
|
||||
phase1();
|
||||
is_phase2_ = true;
|
||||
} else {
|
||||
is_phase2_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*! Runs for a specified number of cycles. */
|
||||
inline void run_for(const Cycles cycles) {
|
||||
int number_of_cycles = cycles.as_int();
|
||||
while(number_of_cycles--) {
|
||||
phase1();
|
||||
phase2();
|
||||
}
|
||||
}
|
||||
|
||||
#undef phase1
|
||||
#undef phase2
|
||||
|
||||
/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
|
||||
inline bool get_interrupt_line() {
|
||||
uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
|
||||
return !!interrupt_status;
|
||||
}
|
||||
|
||||
MOS6522() :
|
||||
timer_is_running_{false, false},
|
||||
last_posted_interrupt_status_(false),
|
||||
is_phase2_(false) {}
|
||||
uint8_t get_register(int address);
|
||||
|
||||
private:
|
||||
// Expected to be overridden
|
||||
uint8_t get_port_input(Port port) { return 0xff; }
|
||||
void set_port_output(Port port, uint8_t value, uint8_t direction_mask) {}
|
||||
void set_control_line_output(Port port, Line line, bool value) {}
|
||||
void set_interrupt_status(bool status) {}
|
||||
T &bus_handler_;
|
||||
|
||||
// Input/output multiplexer
|
||||
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output) {
|
||||
uint8_t input = static_cast<T *>(this)->get_port_input(port);
|
||||
return (input & ~output_mask) | (output & output_mask);
|
||||
}
|
||||
|
||||
// Phase toggle
|
||||
bool is_phase2_;
|
||||
|
||||
// Delegate and communications
|
||||
bool last_posted_interrupt_status_;
|
||||
inline void reevaluate_interrupts() {
|
||||
bool new_interrupt_status = get_interrupt_line();
|
||||
if(new_interrupt_status != last_posted_interrupt_status_) {
|
||||
last_posted_interrupt_status_ = new_interrupt_status;
|
||||
static_cast<T *>(this)->set_interrupt_status(new_interrupt_status);
|
||||
}
|
||||
}
|
||||
|
||||
// The registers
|
||||
struct Registers {
|
||||
uint8_t output[2], input[2], data_direction[2];
|
||||
uint16_t timer[2], timer_latch[2], last_timer[2];
|
||||
int next_timer[2];
|
||||
uint8_t shift;
|
||||
uint8_t auxiliary_control, peripheral_control;
|
||||
uint8_t interrupt_flags, interrupt_enable;
|
||||
bool timer_needs_reload;
|
||||
|
||||
// "A low reset (RES) input clears all R6522 internal registers to logic 0"
|
||||
Registers() :
|
||||
output{0, 0}, input{0, 0}, data_direction{0, 0},
|
||||
auxiliary_control(0), peripheral_control(0),
|
||||
interrupt_flags(0), interrupt_enable(0),
|
||||
last_timer{0, 0}, timer_needs_reload(false),
|
||||
next_timer{-1, -1} {}
|
||||
} registers_;
|
||||
|
||||
// control state
|
||||
struct {
|
||||
bool line_one, line_two;
|
||||
} control_inputs_[2];
|
||||
|
||||
// Internal state other than the registers
|
||||
bool timer_is_running_[2];
|
||||
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output);
|
||||
inline void reevaluate_interrupts();
|
||||
};
|
||||
|
||||
/*!
|
||||
Provided for optional composition with @c MOS6522, @c MOS6522IRQDelegate provides for a delegate
|
||||
that will receive IRQ line change notifications.
|
||||
*/
|
||||
class MOS6522IRQDelegate {
|
||||
public:
|
||||
class Delegate {
|
||||
public:
|
||||
virtual void mos6522_did_change_interrupt_status(void *mos6522) = 0;
|
||||
};
|
||||
|
||||
inline void set_interrupt_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
inline void set_interrupt_status(bool new_status) {
|
||||
if(delegate_) delegate_->mos6522_did_change_interrupt_status(this);
|
||||
}
|
||||
|
||||
private:
|
||||
Delegate *delegate_;
|
||||
};
|
||||
#include "Implementation/6522Implementation.hpp"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* _522_hpp */
|
||||
|
||||
116
Components/6522/Implementation/6522Base.cpp
Normal file
116
Components/6522/Implementation/6522Base.cpp
Normal file
@@ -0,0 +1,116 @@
|
||||
//
|
||||
// 6522Base.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/09/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "../6522.hpp"
|
||||
|
||||
using namespace MOS::MOS6522;
|
||||
|
||||
void MOS6522Base::set_control_line_input(Port port, Line line, bool value) {
|
||||
switch(line) {
|
||||
case Line::One:
|
||||
if( value != control_inputs_[port].line_one &&
|
||||
value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01))
|
||||
) {
|
||||
registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
control_inputs_[port].line_one = value;
|
||||
break;
|
||||
|
||||
case Line::Two:
|
||||
// TODO: output modes, but probably elsewhere?
|
||||
if( value != control_inputs_[port].line_two && // i.e. value has changed ...
|
||||
!(registers_.peripheral_control & (port ? 0x80 : 0x08)) && // ... and line is input ...
|
||||
value == !!(registers_.peripheral_control & (port ? 0x40 : 0x04)) // ... and it's either high or low, as required
|
||||
) {
|
||||
registers_.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
control_inputs_[port].line_two = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MOS6522Base::do_phase2() {
|
||||
registers_.last_timer[0] = registers_.timer[0];
|
||||
registers_.last_timer[1] = registers_.timer[1];
|
||||
|
||||
if(registers_.timer_needs_reload) {
|
||||
registers_.timer_needs_reload = false;
|
||||
registers_.timer[0] = registers_.timer_latch[0];
|
||||
} else {
|
||||
registers_.timer[0] --;
|
||||
}
|
||||
|
||||
registers_.timer[1] --;
|
||||
if(registers_.next_timer[0] >= 0) {
|
||||
registers_.timer[0] = static_cast<uint16_t>(registers_.next_timer[0]);
|
||||
registers_.next_timer[0] = -1;
|
||||
}
|
||||
if(registers_.next_timer[1] >= 0) {
|
||||
registers_.timer[1] = static_cast<uint16_t>(registers_.next_timer[1]);
|
||||
registers_.next_timer[1] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void MOS6522Base::do_phase1() {
|
||||
// IRQ is raised on the half cycle after overflow
|
||||
if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1]) {
|
||||
timer_is_running_[1] = false;
|
||||
registers_.interrupt_flags |= InterruptFlag::Timer2;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
|
||||
if((registers_.timer[0] == 0xffff) && !registers_.last_timer[0] && timer_is_running_[0]) {
|
||||
registers_.interrupt_flags |= InterruptFlag::Timer1;
|
||||
reevaluate_interrupts();
|
||||
|
||||
if(registers_.auxiliary_control&0x40)
|
||||
registers_.timer_needs_reload = true;
|
||||
else
|
||||
timer_is_running_[0] = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*! Runs for a specified number of half cycles. */
|
||||
void MOS6522Base::run_for(const HalfCycles half_cycles) {
|
||||
int number_of_half_cycles = half_cycles.as_int();
|
||||
|
||||
if(is_phase2_) {
|
||||
do_phase2();
|
||||
number_of_half_cycles--;
|
||||
}
|
||||
|
||||
while(number_of_half_cycles >= 2) {
|
||||
do_phase1();
|
||||
do_phase2();
|
||||
number_of_half_cycles -= 2;
|
||||
}
|
||||
|
||||
if(number_of_half_cycles) {
|
||||
do_phase1();
|
||||
is_phase2_ = true;
|
||||
} else {
|
||||
is_phase2_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*! Runs for a specified number of cycles. */
|
||||
void MOS6522Base::run_for(const Cycles cycles) {
|
||||
int number_of_cycles = cycles.as_int();
|
||||
while(number_of_cycles--) {
|
||||
do_phase1();
|
||||
do_phase2();
|
||||
}
|
||||
}
|
||||
|
||||
/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
|
||||
bool MOS6522Base::get_interrupt_line() {
|
||||
uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
|
||||
return !!interrupt_status;
|
||||
}
|
||||
155
Components/6522/Implementation/6522Implementation.hpp
Normal file
155
Components/6522/Implementation/6522Implementation.hpp
Normal file
@@ -0,0 +1,155 @@
|
||||
//
|
||||
// Implementation.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/09/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) {
|
||||
address &= 0xf;
|
||||
switch(address) {
|
||||
case 0x0:
|
||||
registers_.output[1] = value;
|
||||
bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]); // TODO: handshake
|
||||
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
case 0xf:
|
||||
case 0x1:
|
||||
registers_.output[0] = value;
|
||||
bus_handler_.set_port_output(Port::A, value, registers_.data_direction[0]); // TODO: handshake
|
||||
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge));
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
|
||||
case 0x2:
|
||||
registers_.data_direction[1] = value;
|
||||
break;
|
||||
case 0x3:
|
||||
registers_.data_direction[0] = value;
|
||||
break;
|
||||
|
||||
// Timer 1
|
||||
case 0x6: case 0x4: registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value; break;
|
||||
case 0x5: case 0x7:
|
||||
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | static_cast<uint16_t>(value << 8);
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
|
||||
if(address == 0x05) {
|
||||
registers_.next_timer[0] = registers_.timer_latch[0];
|
||||
timer_is_running_[0] = true;
|
||||
}
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
|
||||
// Timer 2
|
||||
case 0x8: registers_.timer_latch[1] = value; break;
|
||||
case 0x9:
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
|
||||
registers_.next_timer[1] = registers_.timer_latch[1] | static_cast<uint16_t>(value << 8);
|
||||
timer_is_running_[1] = true;
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
|
||||
// Shift
|
||||
case 0xa: registers_.shift = value; break;
|
||||
|
||||
// Control
|
||||
case 0xb:
|
||||
registers_.auxiliary_control = value;
|
||||
break;
|
||||
case 0xc:
|
||||
// printf("Peripheral control %02x\n", value);
|
||||
registers_.peripheral_control = value;
|
||||
|
||||
// TODO: simplify below; trying to avoid improper logging of unimplemented warnings in input mode
|
||||
if(value & 0x08) {
|
||||
switch(value & 0x0e) {
|
||||
default: printf("Unimplemented control line mode %d\n", (value >> 1)&7); break;
|
||||
case 0x0c: bus_handler_.set_control_line_output(Port::A, Line::Two, false); break;
|
||||
case 0x0e: bus_handler_.set_control_line_output(Port::A, Line::Two, true); break;
|
||||
}
|
||||
}
|
||||
if(value & 0x80) {
|
||||
switch(value & 0xe0) {
|
||||
default: printf("Unimplemented control line mode %d\n", (value >> 5)&7); break;
|
||||
case 0xc0: bus_handler_.set_control_line_output(Port::B, Line::Two, false); break;
|
||||
case 0xe0: bus_handler_.set_control_line_output(Port::B, Line::Two, true); break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// Interrupt control
|
||||
case 0xd:
|
||||
registers_.interrupt_flags &= ~value;
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
case 0xe:
|
||||
if(value&0x80)
|
||||
registers_.interrupt_enable |= value;
|
||||
else
|
||||
registers_.interrupt_enable &= ~value;
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> uint8_t MOS6522<T>::get_register(int address) {
|
||||
address &= 0xf;
|
||||
switch(address) {
|
||||
case 0x0:
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
|
||||
reevaluate_interrupts();
|
||||
return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]);
|
||||
case 0xf: // TODO: handshake, latching
|
||||
case 0x1:
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
|
||||
reevaluate_interrupts();
|
||||
return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0]);
|
||||
|
||||
case 0x2: return registers_.data_direction[1];
|
||||
case 0x3: return registers_.data_direction[0];
|
||||
|
||||
// Timer 1
|
||||
case 0x4:
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
|
||||
reevaluate_interrupts();
|
||||
return registers_.timer[0] & 0x00ff;
|
||||
case 0x5: return registers_.timer[0] >> 8;
|
||||
case 0x6: return registers_.timer_latch[0] & 0x00ff;
|
||||
case 0x7: return registers_.timer_latch[0] >> 8;
|
||||
|
||||
// Timer 2
|
||||
case 0x8:
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
|
||||
reevaluate_interrupts();
|
||||
return registers_.timer[1] & 0x00ff;
|
||||
case 0x9: return registers_.timer[1] >> 8;
|
||||
|
||||
case 0xa: return registers_.shift;
|
||||
|
||||
case 0xb: return registers_.auxiliary_control;
|
||||
case 0xc: return registers_.peripheral_control;
|
||||
|
||||
case 0xd: return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00);
|
||||
case 0xe: return registers_.interrupt_enable | 0x80;
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t output_mask, uint8_t output) {
|
||||
uint8_t input = bus_handler_.get_port_input(port);
|
||||
return (input & ~output_mask) | (output & output_mask);
|
||||
}
|
||||
|
||||
// Delegate and communications
|
||||
template <typename T> void MOS6522<T>::reevaluate_interrupts() {
|
||||
bool new_interrupt_status = get_interrupt_line();
|
||||
if(new_interrupt_status != last_posted_interrupt_status_) {
|
||||
last_posted_interrupt_status_ = new_interrupt_status;
|
||||
bus_handler_.set_interrupt_status(new_interrupt_status);
|
||||
}
|
||||
}
|
||||
63
Components/6522/Implementation/6522Storage.hpp
Normal file
63
Components/6522/Implementation/6522Storage.hpp
Normal file
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// 6522Storage.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/09/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef _522Storage_hpp
|
||||
#define _522Storage_hpp
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace MOS {
|
||||
namespace MOS6522 {
|
||||
|
||||
class MOS6522Storage {
|
||||
protected:
|
||||
// Phase toggle
|
||||
bool is_phase2_ = false;
|
||||
|
||||
// The registers
|
||||
struct Registers {
|
||||
// "A low reset (RES) input clears all R6522 internal registers to logic 0"
|
||||
uint8_t output[2] = {0, 0};
|
||||
uint8_t input[2] = {0, 0};
|
||||
uint8_t data_direction[2] = {0, 0};
|
||||
uint16_t timer[2] = {0, 0};
|
||||
uint16_t timer_latch[2] = {0, 0};
|
||||
uint16_t last_timer[2] = {0, 0};
|
||||
int next_timer[2] = {-1, -1};
|
||||
uint8_t shift = 0;
|
||||
uint8_t auxiliary_control = 0;
|
||||
uint8_t peripheral_control = 0;
|
||||
uint8_t interrupt_flags = 0;
|
||||
uint8_t interrupt_enable = 0;
|
||||
bool timer_needs_reload = false;
|
||||
} registers_;
|
||||
|
||||
// control state
|
||||
struct {
|
||||
bool line_one = false;
|
||||
bool line_two = false;
|
||||
} control_inputs_[2];
|
||||
|
||||
bool timer_is_running_[2] = {false, false};
|
||||
bool last_posted_interrupt_status_ = false;
|
||||
|
||||
enum InterruptFlag: uint8_t {
|
||||
CA2ActiveEdge = 1 << 0,
|
||||
CA1ActiveEdge = 1 << 1,
|
||||
ShiftRegister = 1 << 2,
|
||||
CB2ActiveEdge = 1 << 3,
|
||||
CB1ActiveEdge = 1 << 4,
|
||||
Timer2 = 1 << 5,
|
||||
Timer1 = 1 << 6,
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* _522Storage_hpp */
|
||||
19
Components/6522/Implementation/IRQDelegatePortHandler.cpp
Normal file
19
Components/6522/Implementation/IRQDelegatePortHandler.cpp
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// IRQDelegatePortHandler.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/09/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "../6522.hpp"
|
||||
|
||||
using namespace MOS::MOS6522;
|
||||
|
||||
void IRQDelegatePortHandler::set_interrupt_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
void IRQDelegatePortHandler::set_interrupt_status(bool new_status) {
|
||||
if(delegate_) delegate_->mos6522_did_change_interrupt_status(this);
|
||||
}
|
||||
@@ -51,7 +51,7 @@ template <class T> class MOS6532 {
|
||||
case 0x04: case 0x05: case 0x06: case 0x07:
|
||||
if(address & 0x10) {
|
||||
timer_.writtenShift = timer_.activeShift = (decodedAddress - 0x04) * 3 + (decodedAddress / 0x07); // i.e. 0, 3, 6, 10
|
||||
timer_.value = ((unsigned int)value << timer_.activeShift) ;
|
||||
timer_.value = (static_cast<unsigned int>(value) << timer_.activeShift) ;
|
||||
timer_.interrupt_enabled = !!(address&0x08);
|
||||
interrupt_status_ &= ~InterruptFlag::Timer;
|
||||
evaluate_interrupts();
|
||||
@@ -79,7 +79,7 @@ template <class T> class MOS6532 {
|
||||
|
||||
// Timer and interrupt control
|
||||
case 0x04: case 0x06: {
|
||||
uint8_t value = (uint8_t)(timer_.value >> timer_.activeShift);
|
||||
uint8_t value = static_cast<uint8_t>(timer_.value >> timer_.activeShift);
|
||||
timer_.interrupt_enabled = !!(address&0x08);
|
||||
interrupt_status_ &= ~InterruptFlag::Timer;
|
||||
evaluate_interrupts();
|
||||
@@ -107,7 +107,7 @@ template <class T> class MOS6532 {
|
||||
}
|
||||
|
||||
inline void run_for(const Cycles cycles) {
|
||||
unsigned int number_of_cycles = (unsigned int)cycles.as_int();
|
||||
unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_int());
|
||||
|
||||
// permit counting _to_ zero; counting _through_ zero initiates the other behaviour
|
||||
if(timer_.value >= number_of_cycles) {
|
||||
@@ -126,7 +126,7 @@ template <class T> class MOS6532 {
|
||||
port_{{.output_mask = 0, .output = 0}, {.output_mask = 0, .output = 0}},
|
||||
a7_interrupt_({.last_port_value = 0, .enabled = false}),
|
||||
interrupt_line_(false),
|
||||
timer_{.value = (unsigned int)((rand() & 0xff) << 10), .activeShift = 10, .writtenShift = 10, .interrupt_enabled = false} {}
|
||||
timer_{.value = static_cast<unsigned int>((rand() & 0xff) << 10), .activeShift = 10, .writtenShift = 10, .interrupt_enabled = false} {}
|
||||
|
||||
inline void set_port_did_change(int port) {
|
||||
if(!port) {
|
||||
|
||||
@@ -98,7 +98,7 @@ static uint8_t noise_pattern[] = {
|
||||
|
||||
#define shift(r) shift_registers_[r] = (shift_registers_[r] << 1) | (((shift_registers_[r]^0x80)&control_registers_[r]) >> 7)
|
||||
#define increment(r) shift_registers_[r] = (shift_registers_[r]+1)%8191
|
||||
#define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = (unsigned int)(control_registers_[r]&0x7f) << m; }
|
||||
#define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = static_cast<unsigned int>(control_registers_[r]&0x7f) << m; }
|
||||
// Note on slightly askew test: as far as I can make out, if the value in the register is 0x7f then what's supposed to happen
|
||||
// is that the 0x7f is loaded, on the next clocked cycle the Vic spots a 0x7f, pumps the output, reloads, etc. No increment
|
||||
// ever occurs. It's conditional. I don't really want two conditionals if I can avoid it so I'm incrementing regardless and
|
||||
|
||||
@@ -66,7 +66,7 @@ template <class T> class MOS6560 {
|
||||
}
|
||||
|
||||
void set_clock_rate(double clock_rate) {
|
||||
speaker_->set_input_rate((float)(clock_rate / 4.0));
|
||||
speaker_->set_input_rate(static_cast<float>(clock_rate / 4.0));
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; }
|
||||
@@ -128,7 +128,7 @@ template <class T> class MOS6560 {
|
||||
break;
|
||||
}
|
||||
|
||||
crt_->set_new_display_type((unsigned int)(timing_.cycles_per_line*4), display_type);
|
||||
crt_->set_new_display_type(static_cast<unsigned int>(timing_.cycles_per_line*4), display_type);
|
||||
crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f));
|
||||
|
||||
// switch(output_mode) {
|
||||
@@ -141,7 +141,7 @@ template <class T> class MOS6560 {
|
||||
// }
|
||||
|
||||
for(int c = 0; c < 16; c++) {
|
||||
uint8_t *colour = (uint8_t *)&colours_[c];
|
||||
uint8_t *colour = reinterpret_cast<uint8_t *>(&colours_[c]);
|
||||
colour[0] = luminances[c];
|
||||
colour[1] = chrominances[c];
|
||||
}
|
||||
@@ -218,7 +218,7 @@ template <class T> class MOS6560 {
|
||||
if(column_counter_&1) {
|
||||
fetch_address = registers_.character_cell_start_address + (character_code_*(registers_.tall_characters ? 16 : 8)) + current_character_row_;
|
||||
} else {
|
||||
fetch_address = (uint16_t)(registers_.video_matrix_start_address + video_matrix_address_counter_);
|
||||
fetch_address = static_cast<uint16_t>(registers_.video_matrix_start_address + video_matrix_address_counter_);
|
||||
video_matrix_address_counter_++;
|
||||
if(
|
||||
(current_character_row_ == 15) ||
|
||||
@@ -270,7 +270,7 @@ template <class T> class MOS6560 {
|
||||
|
||||
pixel_pointer = nullptr;
|
||||
if(output_state_ == State::Pixels) {
|
||||
pixel_pointer = (uint16_t *)crt_->allocate_write_area(260);
|
||||
pixel_pointer = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(260));
|
||||
}
|
||||
}
|
||||
cycles_in_state_++;
|
||||
@@ -345,7 +345,7 @@ template <class T> class MOS6560 {
|
||||
|
||||
case 0x2:
|
||||
registers_.number_of_columns = value & 0x7f;
|
||||
registers_.video_matrix_start_address = (uint16_t)((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2));
|
||||
registers_.video_matrix_start_address = static_cast<uint16_t>((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2));
|
||||
break;
|
||||
|
||||
case 0x3:
|
||||
@@ -354,8 +354,8 @@ template <class T> class MOS6560 {
|
||||
break;
|
||||
|
||||
case 0x5:
|
||||
registers_.character_cell_start_address = (uint16_t)((value & 0x0f) << 10);
|
||||
registers_.video_matrix_start_address = (uint16_t)((registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6));
|
||||
registers_.character_cell_start_address = static_cast<uint16_t>((value & 0x0f) << 10);
|
||||
registers_.video_matrix_start_address = static_cast<uint16_t>((registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6));
|
||||
break;
|
||||
|
||||
case 0xa:
|
||||
@@ -399,7 +399,7 @@ template <class T> class MOS6560 {
|
||||
int current_line = (full_frame_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line;
|
||||
switch(address) {
|
||||
default: return registers_.direct_values[address];
|
||||
case 0x03: return (uint8_t)(current_line << 7) | (registers_.direct_values[3] & 0x7f);
|
||||
case 0x03: return static_cast<uint8_t>(current_line << 7) | (registers_.direct_values[3] & 0x7f);
|
||||
case 0x04: return (current_line >> 1) & 0xff;
|
||||
}
|
||||
}
|
||||
@@ -454,7 +454,7 @@ template <class T> class MOS6560 {
|
||||
|
||||
uint16_t *pixel_pointer;
|
||||
void output_border(unsigned int number_of_cycles) {
|
||||
uint16_t *colour_pointer = (uint16_t *)crt_->allocate_write_area(1);
|
||||
uint16_t *colour_pointer = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(1));
|
||||
if(colour_pointer) *colour_pointer = registers_.borderColour;
|
||||
crt_->output_level(number_of_cycles);
|
||||
}
|
||||
|
||||
@@ -18,12 +18,12 @@ namespace Motorola {
|
||||
namespace CRTC {
|
||||
|
||||
struct BusState {
|
||||
bool display_enable;
|
||||
bool hsync;
|
||||
bool vsync;
|
||||
bool cursor;
|
||||
uint16_t refresh_address;
|
||||
uint16_t row_address;
|
||||
bool display_enable = false;
|
||||
bool hsync = false;
|
||||
bool vsync = false;
|
||||
bool cursor = false;
|
||||
uint16_t refresh_address = 0;
|
||||
uint16_t row_address = 0;
|
||||
};
|
||||
|
||||
class BusHandler {
|
||||
@@ -44,27 +44,39 @@ class BusHandler {
|
||||
};
|
||||
|
||||
enum Personality {
|
||||
HD6845S, //
|
||||
UM6845R, //
|
||||
MC6845, //
|
||||
AMS40226 //
|
||||
HD6845S, // Type 0 in CPC parlance. Zero-width HSYNC available, no status, programmable VSYNC length.
|
||||
// Considered exactly identical to the UM6845, so this enum covers both.
|
||||
UM6845R, // Type 1 in CPC parlance. Status register, fixed-length VSYNC.
|
||||
MC6845, // Type 2. No status register, fixed-length VSYNC, no zero-length HSYNC.
|
||||
AMS40226 // Type 3. Status is get register, fixed-length VSYNC, no zero-length HSYNC.
|
||||
};
|
||||
|
||||
// TODO UM6845R and R12/R13; see http://www.cpcwiki.eu/index.php/CRTC#CRTC_Differences
|
||||
|
||||
template <class T> class CRTC6845 {
|
||||
public:
|
||||
|
||||
CRTC6845(Personality p, T &bus_handler) noexcept :
|
||||
personality_(p), bus_handler_(bus_handler) {}
|
||||
personality_(p), bus_handler_(bus_handler), status_(0) {}
|
||||
|
||||
void select_register(uint8_t r) {
|
||||
selected_register_ = r;
|
||||
}
|
||||
|
||||
uint8_t get_status() const {
|
||||
uint8_t get_status() {
|
||||
switch(personality_) {
|
||||
case UM6845R: return status_ | (bus_state_.vsync ? 0x20 : 0x00);
|
||||
case AMS40226: return get_register();
|
||||
default: return 0xff;
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
uint8_t get_register() const {
|
||||
uint8_t get_register() {
|
||||
if(selected_register_ == 31) status_ &= ~0x80;
|
||||
if(selected_register_ == 16 || selected_register_ == 17) status_ &= ~0x40;
|
||||
|
||||
if(personality_ == UM6845R && selected_register_ == 31) return dummy_register_;
|
||||
if(selected_register_ < 12 || selected_register_ > 17) return 0xff;
|
||||
return registers_[selected_register_];
|
||||
}
|
||||
@@ -75,14 +87,27 @@ template <class T> class CRTC6845 {
|
||||
0xff, 0x1f, 0x7f, 0x1f, 0x3f, 0xff, 0x3f, 0xff
|
||||
};
|
||||
|
||||
// Per CPC documentation, skew doesn't work on a "type 1 or 2", i.e. an MC6845 or a UM6845R.
|
||||
if(selected_register_ == 8 && personality_ != UM6845R && personality_ != MC6845) {
|
||||
switch((value >> 4)&3) {
|
||||
default: display_skew_mask_ = 1; break;
|
||||
case 1: display_skew_mask_ = 2; break;
|
||||
case 2: display_skew_mask_ = 4; break;
|
||||
}
|
||||
}
|
||||
|
||||
if(selected_register_ < 16) {
|
||||
registers_[selected_register_] = value & masks[selected_register_];
|
||||
}
|
||||
if(selected_register_ == 31 && personality_ == UM6845R) {
|
||||
dummy_register_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
void trigger_light_pen() {
|
||||
registers_[17] = bus_state_.refresh_address & 0xff;
|
||||
registers_[16] = bus_state_.refresh_address >> 8;
|
||||
status_ |= 0x40;
|
||||
}
|
||||
|
||||
void run_for(Cycles cycles) {
|
||||
@@ -115,10 +140,20 @@ template <class T> class CRTC6845 {
|
||||
}
|
||||
|
||||
// check for end of horizontal sync; note that a sync time of zero will result in an immediate
|
||||
// cancellation of the plan to perform sync
|
||||
// cancellation of the plan to perform sync if this is an HD6845S or UM6845R; otherwise zero
|
||||
// will end up counting as 16 as it won't be checked until after overflow.
|
||||
if(bus_state_.hsync) {
|
||||
bus_state_.hsync = hsync_counter_ != (registers_[3] & 15);
|
||||
hsync_counter_ = (hsync_counter_ + 1) & 15;
|
||||
switch(personality_) {
|
||||
case HD6845S:
|
||||
case UM6845R:
|
||||
bus_state_.hsync = hsync_counter_ != (registers_[3] & 15);
|
||||
hsync_counter_ = (hsync_counter_ + 1) & 15;
|
||||
break;
|
||||
default:
|
||||
hsync_counter_ = (hsync_counter_ + 1) & 15;
|
||||
bus_state_.hsync = hsync_counter_ != (registers_[3] & 15);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
perform_bus_cycle_phase2();
|
||||
@@ -131,12 +166,13 @@ template <class T> class CRTC6845 {
|
||||
|
||||
private:
|
||||
inline void perform_bus_cycle_phase1() {
|
||||
bus_state_.display_enable = character_is_visible_ && line_is_visible_;
|
||||
// Skew theory of operation: keep a history of the last three states, and apply whichever is selected.
|
||||
character_is_visible_shifter_ = (character_is_visible_shifter_ << 1) | static_cast<unsigned int>(character_is_visible_);
|
||||
bus_state_.display_enable = (static_cast<int>(character_is_visible_shifter_) & display_skew_mask_) && line_is_visible_;
|
||||
bus_handler_.perform_bus_cycle_phase1(bus_state_);
|
||||
}
|
||||
|
||||
inline void perform_bus_cycle_phase2() {
|
||||
bus_state_.display_enable = character_is_visible_ && line_is_visible_;
|
||||
bus_handler_.perform_bus_cycle_phase2(bus_state_);
|
||||
}
|
||||
|
||||
@@ -144,8 +180,16 @@ template <class T> class CRTC6845 {
|
||||
// check for end of vertical sync
|
||||
if(bus_state_.vsync) {
|
||||
vsync_counter_ = (vsync_counter_ + 1) & 15;
|
||||
if(vsync_counter_ == (registers_[3] >> 4)) {
|
||||
bus_state_.vsync = false;
|
||||
// on the UM6845R and AMS40226, honour the programmed vertical sync time; on the other CRTCs
|
||||
// always use a vertical sync count of 16.
|
||||
switch(personality_) {
|
||||
case HD6845S:
|
||||
case AMS40226:
|
||||
bus_state_.vsync = vsync_counter_ != (registers_[3] >> 4);
|
||||
break;
|
||||
default:
|
||||
bus_state_.vsync = vsync_counter_ != 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +240,7 @@ template <class T> class CRTC6845 {
|
||||
inline void do_end_of_frame() {
|
||||
line_counter_ = 0;
|
||||
line_is_visible_ = true;
|
||||
line_address_ = (uint16_t)((registers_[12] << 8) | registers_[13]);
|
||||
line_address_ = static_cast<uint16_t>((registers_[12] << 8) | registers_[13]);
|
||||
bus_state_.refresh_address = line_address_;
|
||||
}
|
||||
|
||||
@@ -204,20 +248,25 @@ template <class T> class CRTC6845 {
|
||||
T &bus_handler_;
|
||||
BusState bus_state_;
|
||||
|
||||
uint8_t registers_[18];
|
||||
int selected_register_;
|
||||
uint8_t registers_[18] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
uint8_t dummy_register_ = 0;
|
||||
int selected_register_ = 0;
|
||||
|
||||
uint8_t character_counter_;
|
||||
uint8_t line_counter_;
|
||||
uint8_t character_counter_ = 0;
|
||||
uint8_t line_counter_ = 0;
|
||||
|
||||
bool character_is_visible_, line_is_visible_;
|
||||
bool character_is_visible_ = false, line_is_visible_ = false;
|
||||
|
||||
int hsync_counter_;
|
||||
int vsync_counter_;
|
||||
bool is_in_adjustment_period_;
|
||||
int hsync_counter_ = 0;
|
||||
int vsync_counter_ = 0;
|
||||
bool is_in_adjustment_period_ = false;
|
||||
|
||||
uint16_t line_address_;
|
||||
uint16_t end_of_line_address_;
|
||||
uint16_t line_address_ = 0;
|
||||
uint16_t end_of_line_address_ = 0;
|
||||
uint8_t status_ = 0;
|
||||
|
||||
int display_skew_mask_ = 1;
|
||||
unsigned int character_is_visible_shifter_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
#include "i8272.hpp"
|
||||
#include "../../Storage/Disk/Encodings/MFM.hpp"
|
||||
//#include "../../Storage/Disk/Encodings/MFM/Encoder.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
@@ -34,6 +34,7 @@ using namespace Intel::i8272;
|
||||
#define SetSeekEnd() (status_[0] |= 0x20)
|
||||
#define SetEquipmentCheck() (status_[0] |= 0x10)
|
||||
#define SetNotReady() (status_[0] |= 0x08)
|
||||
#define SetSide2() (status_[0] |= 0x04)
|
||||
|
||||
#define SetEndOfCylinder() (status_[1] |= 0x80)
|
||||
#define SetDataError() (status_[1] |= 0x20)
|
||||
@@ -43,6 +44,7 @@ using namespace Intel::i8272;
|
||||
#define SetMissingAddressMark() (status_[1] |= 0x01)
|
||||
|
||||
#define SetControlMark() (status_[2] |= 0x40)
|
||||
#define ClearControlMark() (status_[2] &= ~0x40)
|
||||
#define ControlMark() (status_[2] & 0x40)
|
||||
|
||||
#define SetDataFieldDataError() (status_[2] |= 0x20)
|
||||
@@ -75,17 +77,10 @@ namespace {
|
||||
const uint8_t CommandSenseDriveStatus = 0x04;
|
||||
}
|
||||
|
||||
i8272::i8272(BusHandler &bus_handler, Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) :
|
||||
Storage::Disk::MFMController(clock_rate, clock_rate_multiplier, revolutions_per_minute),
|
||||
bus_handler_(bus_handler),
|
||||
main_status_(0),
|
||||
interesting_event_mask_((int)Event8272::CommandByte),
|
||||
resume_point_(0),
|
||||
delay_time_(0),
|
||||
head_timers_running_(0),
|
||||
expects_input_(false),
|
||||
drives_seeking_(0) {
|
||||
posit_event((int)Event8272::CommandByte);
|
||||
i8272::i8272(BusHandler &bus_handler, Cycles clock_rate) :
|
||||
Storage::Disk::MFMController(clock_rate),
|
||||
bus_handler_(bus_handler) {
|
||||
posit_event(static_cast<int>(Event8272::CommandByte));
|
||||
}
|
||||
|
||||
bool i8272::is_sleeping() {
|
||||
@@ -101,7 +96,7 @@ void i8272::run_for(Cycles cycles) {
|
||||
if(delay_time_ > 0) {
|
||||
if(cycles.as_int() >= delay_time_) {
|
||||
delay_time_ = 0;
|
||||
posit_event((int)Event8272::Timer);
|
||||
posit_event(static_cast<int>(Event8272::Timer));
|
||||
} else {
|
||||
delay_time_ -= cycles.as_int();
|
||||
}
|
||||
@@ -119,11 +114,12 @@ void i8272::run_for(Cycles cycles) {
|
||||
// Perform a step.
|
||||
int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1;
|
||||
printf("Target %d versus believed %d\n", drives_[c].target_head_position, drives_[c].head_position);
|
||||
drives_[c].drive->step(direction);
|
||||
select_drive(c);
|
||||
get_drive().step(direction);
|
||||
if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction;
|
||||
|
||||
// Check for completion.
|
||||
if(drives_[c].seek_is_satisfied()) {
|
||||
if(seek_is_satisfied(c)) {
|
||||
drives_[c].phase = Drive::CompletedSeeking;
|
||||
drives_seeking_--;
|
||||
break;
|
||||
@@ -157,6 +153,11 @@ void i8272::run_for(Cycles cycles) {
|
||||
}
|
||||
}
|
||||
|
||||
// check for busy plus ready disabled
|
||||
if(is_executing_ && !get_drive().get_is_ready()) {
|
||||
posit_event(static_cast<int>(Event8272::NoLongerReady));
|
||||
}
|
||||
|
||||
is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_;
|
||||
if(is_sleeping_) update_sleep_observer();
|
||||
}
|
||||
@@ -175,7 +176,7 @@ void i8272::set_register(int address, uint8_t value) {
|
||||
} else {
|
||||
// accumulate latest byte in the command byte sequence
|
||||
command_.push_back(value);
|
||||
posit_event((int)Event8272::CommandByte);
|
||||
posit_event(static_cast<int>(Event8272::CommandByte));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +185,7 @@ uint8_t i8272::get_register(int address) {
|
||||
if(result_stack_.empty()) return 0xff;
|
||||
uint8_t result = result_stack_.back();
|
||||
result_stack_.pop_back();
|
||||
if(result_stack_.empty()) posit_event((int)Event8272::ResultEmpty);
|
||||
if(result_stack_.empty()) posit_event(static_cast<int>(Event8272::ResultEmpty));
|
||||
|
||||
return result;
|
||||
} else {
|
||||
@@ -192,26 +193,20 @@ uint8_t i8272::get_register(int address) {
|
||||
}
|
||||
}
|
||||
|
||||
void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||
if(drive < 4 && drive >= 0) {
|
||||
drives_[drive].drive->set_disk(disk);
|
||||
}
|
||||
}
|
||||
|
||||
#define BEGIN_SECTION() switch(resume_point_) { default:
|
||||
#define END_SECTION() }
|
||||
|
||||
#define MS_TO_CYCLES(x) x * 8000
|
||||
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = (int)mask; return; case __LINE__:
|
||||
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = (int)Event8272::Timer; delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_sleep_observer(); case __LINE__: if(delay_time_) return;
|
||||
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(mask); return; case __LINE__:
|
||||
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_sleep_observer(); case __LINE__: if(delay_time_) return;
|
||||
|
||||
#define PASTE(x, y) x##y
|
||||
#define CONCAT(x, y) PASTE(x, y)
|
||||
|
||||
#define FIND_HEADER() \
|
||||
set_data_mode(DataMode::Scanning); \
|
||||
CONCAT(find_header, __LINE__): WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole); \
|
||||
if(event_type == (int)Event::IndexHole) { index_hole_limit_--; } \
|
||||
CONCAT(find_header, __LINE__): WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); \
|
||||
if(event_type == static_cast<int>(Event::IndexHole)) { index_hole_limit_--; } \
|
||||
else if(get_latest_token().type == Token::ID) goto CONCAT(header_found, __LINE__); \
|
||||
\
|
||||
if(index_hole_limit_) goto CONCAT(find_header, __LINE__); \
|
||||
@@ -219,8 +214,8 @@ void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||
|
||||
#define FIND_DATA() \
|
||||
set_data_mode(DataMode::Scanning); \
|
||||
CONCAT(find_data, __LINE__): WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole); \
|
||||
if(event_type == (int)Event::Token) { \
|
||||
CONCAT(find_data, __LINE__): WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); \
|
||||
if(event_type == static_cast<int>(Event::Token)) { \
|
||||
if(get_latest_token().type == Token::Byte || get_latest_token().type == Token::Sync) goto CONCAT(find_data, __LINE__); \
|
||||
}
|
||||
|
||||
@@ -235,10 +230,10 @@ void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||
#define SET_DRIVE_HEAD_MFM() \
|
||||
active_drive_ = command_[1]&3; \
|
||||
active_head_ = (command_[1] >> 2)&1; \
|
||||
set_drive(drives_[active_drive_].drive); \
|
||||
drives_[active_drive_].drive->set_head((unsigned int)active_head_); \
|
||||
set_is_double_density(command_[0] & 0x40); \
|
||||
invalidate_track();
|
||||
status_[0] = (command_[1]&7); \
|
||||
select_drive(active_drive_); \
|
||||
get_drive().set_head(active_head_); \
|
||||
set_is_double_density(command_[0] & 0x40);
|
||||
|
||||
#define WAIT_FOR_BYTES(n) \
|
||||
distance_into_section_ = 0; \
|
||||
@@ -268,7 +263,11 @@ void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||
}
|
||||
|
||||
void i8272::posit_event(int event_type) {
|
||||
if(event_type == (int)Event::IndexHole) index_hole_count_++;
|
||||
if(event_type == static_cast<int>(Event::IndexHole)) index_hole_count_++;
|
||||
if(event_type == static_cast<int>(Event8272::NoLongerReady)) {
|
||||
SetNotReady();
|
||||
goto abort;
|
||||
}
|
||||
if(!(interesting_event_mask_ & event_type)) return;
|
||||
interesting_event_mask_ &= ~event_type;
|
||||
|
||||
@@ -338,9 +337,14 @@ void i8272::posit_event(int event_type) {
|
||||
}
|
||||
// Establishes the drive and head being addressed, and whether in double density mode; populates the internal
|
||||
// cylinder, head, sector and size registers from the command stream.
|
||||
is_executing_ = true;
|
||||
if(!dma_mode_) SetNonDMAExecution();
|
||||
SET_DRIVE_HEAD_MFM();
|
||||
LOAD_HEAD();
|
||||
if(!get_drive().get_is_ready()) {
|
||||
SetNotReady();
|
||||
goto abort;
|
||||
}
|
||||
}
|
||||
|
||||
// Jump to the proper place.
|
||||
@@ -427,7 +431,8 @@ void i8272::posit_event(int event_type) {
|
||||
// flag doesn't match the sort the command was looking for.
|
||||
read_data_found_header:
|
||||
FIND_DATA();
|
||||
if(event_type == (int)Event::Token) {
|
||||
ClearControlMark();
|
||||
if(event_type == static_cast<int>(Event::Token)) {
|
||||
if(get_latest_token().type != Token::Data && get_latest_token().type != Token::DeletedData) {
|
||||
// Something other than a data mark came next — impliedly an ID or index mark.
|
||||
SetMissingAddressMark();
|
||||
@@ -458,24 +463,24 @@ void i8272::posit_event(int event_type) {
|
||||
//
|
||||
// TODO: consider DTL.
|
||||
read_data_get_byte:
|
||||
WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole);
|
||||
if(event_type == (int)Event::Token) {
|
||||
WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
|
||||
if(event_type == static_cast<int>(Event::Token)) {
|
||||
result_stack_.push_back(get_latest_token().byte_value);
|
||||
distance_into_section_++;
|
||||
SetDataRequest();
|
||||
SetDataDirectionToProcessor();
|
||||
WAIT_FOR_EVENT((int)Event8272::ResultEmpty | (int)Event::Token | (int)Event::IndexHole);
|
||||
WAIT_FOR_EVENT(static_cast<int>(Event8272::ResultEmpty) | static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
|
||||
}
|
||||
switch(event_type) {
|
||||
case (int)Event8272::ResultEmpty: // The caller read the byte in time; proceed as normal.
|
||||
case static_cast<int>(Event8272::ResultEmpty): // The caller read the byte in time; proceed as normal.
|
||||
ResetDataRequest();
|
||||
if(distance_into_section_ < (128 << size_)) goto read_data_get_byte;
|
||||
break;
|
||||
case (int)Event::Token: // The caller hasn't read the old byte yet and a new one has arrived
|
||||
case static_cast<int>(Event::Token): // The caller hasn't read the old byte yet and a new one has arrived
|
||||
SetOverrun();
|
||||
goto abort;
|
||||
break;
|
||||
case (int)Event::IndexHole:
|
||||
case static_cast<int>(Event::IndexHole):
|
||||
SetEndOfCylinder();
|
||||
goto abort;
|
||||
break;
|
||||
@@ -504,7 +509,7 @@ void i8272::posit_event(int event_type) {
|
||||
write_data:
|
||||
printf("Write [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]);
|
||||
|
||||
if(drives_[active_drive_].drive->get_is_read_only()) {
|
||||
if(get_drive().get_is_read_only()) {
|
||||
SetNotWriteable();
|
||||
goto abort;
|
||||
}
|
||||
@@ -527,7 +532,6 @@ void i8272::posit_event(int event_type) {
|
||||
WAIT_FOR_EVENT(Event::DataWritten);
|
||||
if(!has_input_) {
|
||||
SetOverrun();
|
||||
end_writing();
|
||||
goto abort;
|
||||
}
|
||||
write_byte(input_);
|
||||
@@ -607,7 +611,7 @@ void i8272::posit_event(int event_type) {
|
||||
distance_into_section_++;
|
||||
SetDataRequest();
|
||||
// TODO: other possible exit conditions; find a way to merge with the read_data version of this.
|
||||
WAIT_FOR_EVENT((int)Event8272::ResultEmpty);
|
||||
WAIT_FOR_EVENT(static_cast<int>(Event8272::ResultEmpty));
|
||||
ResetDataRequest();
|
||||
if(distance_into_section_ < (128 << header_[2])) goto read_track_get_byte;
|
||||
|
||||
@@ -619,7 +623,7 @@ void i8272::posit_event(int event_type) {
|
||||
// Performs format [/write] track.
|
||||
format_track:
|
||||
printf("Format track\n");
|
||||
if(drives_[active_drive_].drive->get_is_read_only()) {
|
||||
if(get_drive().get_is_read_only()) {
|
||||
SetNotWriteable();
|
||||
goto abort;
|
||||
}
|
||||
@@ -644,14 +648,13 @@ void i8272::posit_event(int event_type) {
|
||||
expects_input_ = true;
|
||||
distance_into_section_ = 0;
|
||||
format_track_write_header:
|
||||
WAIT_FOR_EVENT((int)Event::DataWritten | (int)Event::IndexHole);
|
||||
WAIT_FOR_EVENT(static_cast<int>(Event::DataWritten) | static_cast<int>(Event::IndexHole));
|
||||
switch(event_type) {
|
||||
case (int)Event::IndexHole:
|
||||
case static_cast<int>(Event::IndexHole):
|
||||
SetOverrun();
|
||||
end_writing();
|
||||
goto abort;
|
||||
break;
|
||||
case (int)Event::DataWritten:
|
||||
case static_cast<int>(Event::DataWritten):
|
||||
header_[distance_into_section_] = input_;
|
||||
write_byte(input_);
|
||||
has_input_ = false;
|
||||
@@ -682,8 +685,8 @@ void i8272::posit_event(int event_type) {
|
||||
// Otherwise, pad out to the index hole.
|
||||
format_track_pad:
|
||||
write_byte(get_is_double_density() ? 0x4e : 0xff);
|
||||
WAIT_FOR_EVENT((int)Event::DataWritten | (int)Event::IndexHole);
|
||||
if(event_type != (int)Event::IndexHole) goto format_track_pad;
|
||||
WAIT_FOR_EVENT(static_cast<int>(Event::DataWritten) | static_cast<int>(Event::IndexHole));
|
||||
if(event_type != static_cast<int>(Event::IndexHole)) goto format_track_pad;
|
||||
|
||||
end_writing();
|
||||
|
||||
@@ -712,6 +715,7 @@ void i8272::posit_event(int event_type) {
|
||||
seek:
|
||||
{
|
||||
int drive = command_[1]&3;
|
||||
select_drive(drive);
|
||||
|
||||
// Increment the seeking count if this drive wasn't already seeking.
|
||||
if(drives_[drive].phase != Drive::Seeking) {
|
||||
@@ -741,7 +745,7 @@ void i8272::posit_event(int event_type) {
|
||||
}
|
||||
|
||||
// Check whether any steps are even needed; if not then mark as completed already.
|
||||
if(drives_[drive].seek_is_satisfied()) {
|
||||
if(seek_is_satisfied(drive)) {
|
||||
drives_[drive].phase = Drive::CompletedSeeking;
|
||||
drives_seeking_--;
|
||||
}
|
||||
@@ -764,14 +768,13 @@ void i8272::posit_event(int event_type) {
|
||||
// If a drive was found, return its results. Otherwise return a single 0x80.
|
||||
if(found_drive != -1) {
|
||||
drives_[found_drive].phase = Drive::NotSeeking;
|
||||
status_[0] = (uint8_t)found_drive;
|
||||
status_[0] = static_cast<uint8_t>(found_drive);
|
||||
main_status_ &= ~(1 << found_drive);
|
||||
SetSeekEnd();
|
||||
|
||||
result_stack_.push_back(drives_[found_drive].head_position);
|
||||
result_stack_.push_back(status_[0]);
|
||||
result_stack_ = { drives_[found_drive].head_position, status_[0]};
|
||||
} else {
|
||||
result_stack_.push_back(0x80);
|
||||
result_stack_ = { 0x80 };
|
||||
}
|
||||
}
|
||||
goto post_result;
|
||||
@@ -793,24 +796,28 @@ void i8272::posit_event(int event_type) {
|
||||
printf("Sense drive status\n");
|
||||
{
|
||||
int drive = command_[1] & 3;
|
||||
result_stack_.push_back(
|
||||
(command_[1] & 7) | // drive and head number
|
||||
0x08 | // single sided
|
||||
(drives_[drive].drive->get_is_track_zero() ? 0x10 : 0x00) |
|
||||
(drives_[drive].drive->get_is_ready() ? 0x20 : 0x00) |
|
||||
(drives_[drive].drive->get_is_read_only() ? 0x40 : 0x00)
|
||||
);
|
||||
select_drive(drive);
|
||||
result_stack_= {
|
||||
static_cast<uint8_t>(
|
||||
(command_[1] & 7) | // drive and head number
|
||||
0x08 | // single sided
|
||||
(get_drive().get_is_track_zero() ? 0x10 : 0x00) |
|
||||
(get_drive().get_is_ready() ? 0x20 : 0x00) |
|
||||
(get_drive().get_is_read_only() ? 0x40 : 0x00)
|
||||
)
|
||||
};
|
||||
}
|
||||
goto post_result;
|
||||
|
||||
// Performs any invalid command.
|
||||
invalid:
|
||||
// A no-op, but posts ST0 (but which ST0?)
|
||||
result_stack_.push_back(0x80);
|
||||
result_stack_ = {0x80};
|
||||
goto post_result;
|
||||
|
||||
// Sets abnormal termination of the current command and proceeds to an ST0, ST1, ST2, C, H, R, N result phase.
|
||||
abort:
|
||||
end_writing();
|
||||
SetAbnormalTermination();
|
||||
goto post_st012chrn;
|
||||
|
||||
@@ -818,14 +825,7 @@ void i8272::posit_event(int event_type) {
|
||||
post_st012chrn:
|
||||
SCHEDULE_HEAD_UNLOAD();
|
||||
|
||||
result_stack_.push_back(size_);
|
||||
result_stack_.push_back(sector_);
|
||||
result_stack_.push_back(head_);
|
||||
result_stack_.push_back(cylinder_);
|
||||
|
||||
result_stack_.push_back(status_[2]);
|
||||
result_stack_.push_back(status_[1]);
|
||||
result_stack_.push_back(status_[0]);
|
||||
result_stack_ = {size_, sector_, head_, cylinder_, status_[2], status_[1], status_[0]};
|
||||
|
||||
goto post_result;
|
||||
|
||||
@@ -839,6 +839,7 @@ void i8272::posit_event(int event_type) {
|
||||
printf("\n");
|
||||
|
||||
// Set ready to send data to the processor, no longer in non-DMA execution phase.
|
||||
is_executing_ = false;
|
||||
ResetNonDMAExecution();
|
||||
SetDataRequest();
|
||||
SetDataDirectionToProcessor();
|
||||
@@ -853,9 +854,9 @@ void i8272::posit_event(int event_type) {
|
||||
END_SECTION()
|
||||
}
|
||||
|
||||
bool i8272::Drive::seek_is_satisfied() {
|
||||
return (target_head_position == head_position) ||
|
||||
(target_head_position == -1 && drive->get_is_track_zero());
|
||||
bool i8272::seek_is_satisfied(int drive) {
|
||||
return (drives_[drive].target_head_position == drives_[drive].head_position) ||
|
||||
(drives_[drive].target_head_position == -1 && get_drive().get_is_track_zero());
|
||||
}
|
||||
|
||||
void i8272::set_dma_acknowledge(bool dack) {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#ifndef i8272_hpp
|
||||
#define i8272_hpp
|
||||
|
||||
#include "../../Storage/Disk/MFMDiskController.hpp"
|
||||
#include "../../Storage/Disk/Controller/MFMDiskController.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
@@ -26,7 +26,7 @@ class BusHandler {
|
||||
|
||||
class i8272: public Storage::Disk::MFMController {
|
||||
public:
|
||||
i8272(BusHandler &bus_handler, Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute);
|
||||
i8272(BusHandler &bus_handler, Cycles clock_rate);
|
||||
|
||||
void run_for(Cycles);
|
||||
|
||||
@@ -39,99 +39,94 @@ class i8272: public Storage::Disk::MFMController {
|
||||
void set_dma_acknowledge(bool dack);
|
||||
void set_terminal_count(bool tc);
|
||||
|
||||
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive);
|
||||
|
||||
bool is_sleeping();
|
||||
|
||||
protected:
|
||||
virtual void select_drive(int number) = 0;
|
||||
|
||||
private:
|
||||
// The bus handler, for interrupt and DMA-driven usage.
|
||||
BusHandler &bus_handler_;
|
||||
std::unique_ptr<BusHandler> allocated_bus_handler_;
|
||||
|
||||
// Status registers.
|
||||
uint8_t main_status_;
|
||||
uint8_t status_[3];
|
||||
uint8_t main_status_ = 0;
|
||||
uint8_t status_[3] = {0, 0, 0};
|
||||
|
||||
// A buffer for accumulating the incoming command, and one for accumulating the result.
|
||||
std::vector<uint8_t> command_;
|
||||
std::vector<uint8_t> result_stack_;
|
||||
uint8_t input_;
|
||||
bool has_input_;
|
||||
bool expects_input_;
|
||||
uint8_t input_ = 0;
|
||||
bool has_input_ = false;
|
||||
bool expects_input_ = false;
|
||||
|
||||
// Event stream: the 8272-specific events, plus the current event state.
|
||||
enum class Event8272: int {
|
||||
CommandByte = (1 << 3),
|
||||
Timer = (1 << 4),
|
||||
ResultEmpty = (1 << 5),
|
||||
NoLongerReady = (1 << 6)
|
||||
};
|
||||
void posit_event(int type);
|
||||
int interesting_event_mask_;
|
||||
int resume_point_;
|
||||
bool is_access_command_;
|
||||
int interesting_event_mask_ = static_cast<int>(Event8272::CommandByte);
|
||||
int resume_point_ = 0;
|
||||
bool is_access_command_ = false;
|
||||
|
||||
// The counter used for ::Timer events.
|
||||
int delay_time_;
|
||||
int delay_time_ = 0;
|
||||
|
||||
// The connected drives.
|
||||
struct Drive {
|
||||
uint8_t head_position;
|
||||
uint8_t head_position = 0;
|
||||
|
||||
// Seeking: persistent state.
|
||||
enum Phase {
|
||||
NotSeeking,
|
||||
Seeking,
|
||||
CompletedSeeking
|
||||
} phase;
|
||||
bool did_seek;
|
||||
bool seek_failed;
|
||||
} phase = NotSeeking;
|
||||
bool did_seek = false;
|
||||
bool seek_failed = false;
|
||||
|
||||
// Seeking: transient state.
|
||||
int step_rate_counter;
|
||||
int steps_taken;
|
||||
int target_head_position; // either an actual number, or -1 to indicate to step until track zero
|
||||
|
||||
/// @returns @c true if the currently queued-up seek or recalibrate has reached where it should be.
|
||||
bool seek_is_satisfied();
|
||||
int step_rate_counter = 0;
|
||||
int steps_taken = 0;
|
||||
int target_head_position = 0; // either an actual number, or -1 to indicate to step until track zero
|
||||
|
||||
// Head state.
|
||||
int head_unload_delay[2];
|
||||
bool head_is_loaded[2];
|
||||
int head_unload_delay[2] = {0, 0};
|
||||
bool head_is_loaded[2] = {false, false};
|
||||
|
||||
// The connected drive.
|
||||
std::shared_ptr<Storage::Disk::Drive> drive;
|
||||
|
||||
Drive() :
|
||||
head_position(0), phase(NotSeeking),
|
||||
drive(new Storage::Disk::Drive),
|
||||
head_is_loaded{false, false},
|
||||
head_unload_delay{0, 0} {};
|
||||
} drives_[4];
|
||||
int drives_seeking_;
|
||||
int drives_seeking_ = 0;
|
||||
|
||||
/// @returns @c true if the selected drive, which is number @c drive, can stop seeking.
|
||||
bool seek_is_satisfied(int drive);
|
||||
|
||||
// User-supplied parameters; as per the specify command.
|
||||
int step_rate_time_;
|
||||
int head_unload_time_;
|
||||
int head_load_time_;
|
||||
bool dma_mode_;
|
||||
int step_rate_time_ = 1;
|
||||
int head_unload_time_ = 1;
|
||||
int head_load_time_ = 1;
|
||||
bool dma_mode_ = false;
|
||||
bool is_executing_ = false;
|
||||
|
||||
// A count of head unload timers currently running.
|
||||
int head_timers_running_;
|
||||
int head_timers_running_ = 0;
|
||||
|
||||
// Transient storage and counters used while reading the disk.
|
||||
uint8_t header_[6];
|
||||
int distance_into_section_;
|
||||
int index_hole_count_, index_hole_limit_;
|
||||
uint8_t header_[6] = {0, 0, 0, 0, 0, 0};
|
||||
int distance_into_section_ = 0;
|
||||
int index_hole_count_ = 0, index_hole_limit_ = 0;
|
||||
|
||||
// Keeps track of the drive and head in use during commands.
|
||||
int active_drive_;
|
||||
int active_head_;
|
||||
int active_drive_ = 0;
|
||||
int active_head_ = 0;
|
||||
|
||||
// Internal registers.
|
||||
uint8_t cylinder_, head_, sector_, size_;
|
||||
uint8_t cylinder_ = 0, head_ = 0, sector_ = 0, size_ = 0;
|
||||
|
||||
// Master switch on not performing any work.
|
||||
bool is_sleeping_;
|
||||
bool is_sleeping_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -67,13 +67,13 @@ AY38910::AY38910() :
|
||||
float max_volume = 8192;
|
||||
float root_two = sqrtf(2.0f);
|
||||
for(int v = 0; v < 16; v++) {
|
||||
volumes_[v] = (int)(max_volume / powf(root_two, (float)(v ^ 0xf)));
|
||||
volumes_[v] = static_cast<int>(max_volume / powf(root_two, static_cast<float>(v ^ 0xf)));
|
||||
}
|
||||
volumes_[0] = 0;
|
||||
}
|
||||
|
||||
void AY38910::set_clock_rate(double clock_rate) {
|
||||
set_input_rate((float)clock_rate);
|
||||
set_input_rate(static_cast<float>(clock_rate));
|
||||
}
|
||||
|
||||
void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) {
|
||||
@@ -160,7 +160,7 @@ void AY38910::evaluate_output_volume() {
|
||||
#undef channel_volume
|
||||
|
||||
// Mix additively.
|
||||
output_volume_ = (int16_t)(
|
||||
output_volume_ = static_cast<int16_t>(
|
||||
volumes_[volumes[0]] * channel_levels[0] +
|
||||
volumes_[volumes[1]] * channel_levels[1] +
|
||||
volumes_[volumes[2]] * channel_levels[2]
|
||||
@@ -186,7 +186,7 @@ void AY38910::set_register_value(uint8_t value) {
|
||||
int channel = selected_register >> 1;
|
||||
|
||||
if(selected_register & 1)
|
||||
tone_periods_[channel] = (tone_periods_[channel] & 0xff) | (uint16_t)((value&0xf) << 8);
|
||||
tone_periods_[channel] = (tone_periods_[channel] & 0xff) | static_cast<uint16_t>((value&0xf) << 8);
|
||||
else
|
||||
tone_periods_[channel] = (tone_periods_[channel] & ~0xff) | value;
|
||||
}
|
||||
@@ -201,7 +201,7 @@ void AY38910::set_register_value(uint8_t value) {
|
||||
break;
|
||||
|
||||
case 12:
|
||||
envelope_period_ = (envelope_period_ & 0xff) | (int)(value << 8);
|
||||
envelope_period_ = (envelope_period_ & 0xff) | static_cast<int>(value << 8);
|
||||
break;
|
||||
|
||||
case 13:
|
||||
@@ -262,15 +262,15 @@ uint8_t AY38910::get_data_output() {
|
||||
}
|
||||
|
||||
void AY38910::set_control_lines(ControlLines control_lines) {
|
||||
switch((int)control_lines) {
|
||||
switch(static_cast<int>(control_lines)) {
|
||||
default: control_state_ = Inactive; break;
|
||||
|
||||
case (int)(BDIR | BC2 | BC1):
|
||||
case static_cast<int>(BDIR | BC2 | BC1):
|
||||
case BDIR:
|
||||
case BC1: control_state_ = LatchAddress; break;
|
||||
|
||||
case (int)(BC2 | BC1): control_state_ = Read; break;
|
||||
case (int)(BDIR | BC2): control_state_ = Write; break;
|
||||
case static_cast<int>(BC2 | BC1): control_state_ = Read; break;
|
||||
case static_cast<int>(BDIR | BC2): control_state_ = Write; break;
|
||||
}
|
||||
|
||||
update_bus();
|
||||
|
||||
67
Concurrency/BestEffortUpdater.cpp
Normal file
67
Concurrency/BestEffortUpdater.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// BestEffortUpdater.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/10/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "BestEffortUpdater.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
using namespace Concurrency;
|
||||
|
||||
void BestEffortUpdater::update() {
|
||||
// Perform an update only if one is not currently ongoing.
|
||||
if(!update_is_ongoing_.test_and_set()) {
|
||||
async_task_queue_.enqueue([this]() {
|
||||
// Get time now using the highest-resolution clock provided by the implementation, and determine
|
||||
// the duration since the last time this section was entered.
|
||||
const std::chrono::time_point<std::chrono::high_resolution_clock> now = std::chrono::high_resolution_clock::now();
|
||||
const auto elapsed = now - previous_time_point_;
|
||||
previous_time_point_ = now;
|
||||
|
||||
if(has_previous_time_point_) {
|
||||
// If the duration is valid, convert it to integer cycles, maintaining a rolling error and call the delegate
|
||||
// if there is one. Proceed only if the number of cycles is positive, and cap it to the per-second maximum —
|
||||
// it's possible this is an adjustable clock so be ready to swallow unexpected adjustments.
|
||||
const int64_t duration = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count();
|
||||
if(duration > 0) {
|
||||
double cycles = ((static_cast<double>(duration) * clock_rate_) / 1e9) + error_;
|
||||
error_ = fmod(cycles, 1.0);
|
||||
|
||||
if(delegate_) {
|
||||
delegate_->update(this, static_cast<int>(std::min(cycles, clock_rate_)), has_skipped_);
|
||||
}
|
||||
has_skipped_ = false;
|
||||
}
|
||||
} else {
|
||||
has_previous_time_point_ = true;
|
||||
}
|
||||
|
||||
// Allow furthers updates to occur.
|
||||
update_is_ongoing_.clear();
|
||||
});
|
||||
} else {
|
||||
async_task_queue_.enqueue([this]() {
|
||||
has_skipped_ = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void BestEffortUpdater::flush() {
|
||||
async_task_queue_.flush();
|
||||
}
|
||||
|
||||
void BestEffortUpdater::set_delegate(Delegate *const delegate) {
|
||||
async_task_queue_.enqueue([this, delegate]() {
|
||||
delegate_ = delegate;
|
||||
});
|
||||
}
|
||||
|
||||
void BestEffortUpdater::set_clock_rate(const double clock_rate) {
|
||||
async_task_queue_.enqueue([this, clock_rate]() {
|
||||
this->clock_rate_ = clock_rate;
|
||||
});
|
||||
}
|
||||
63
Concurrency/BestEffortUpdater.hpp
Normal file
63
Concurrency/BestEffortUpdater.hpp
Normal file
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// BestEffortUpdater.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/10/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef BestEffortUpdater_hpp
|
||||
#define BestEffortUpdater_hpp
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
|
||||
#include "AsyncTaskQueue.hpp"
|
||||
|
||||
namespace Concurrency {
|
||||
|
||||
/*!
|
||||
Accepts timing cues from multiple threads and ensures that a delegate receives calls to total
|
||||
a certain number of cycles per second, that those calls are strictly serialised, and that no
|
||||
backlog of calls accrues.
|
||||
|
||||
No guarantees about the thread that the delegate will be called on are made.
|
||||
*/
|
||||
class BestEffortUpdater {
|
||||
public:
|
||||
/// A delegate receives timing cues.
|
||||
struct Delegate {
|
||||
virtual void update(BestEffortUpdater *updater, int cycles, bool did_skip_previous_update) = 0;
|
||||
};
|
||||
|
||||
/// Sets the current delegate.
|
||||
void set_delegate(Delegate *);
|
||||
|
||||
/// Sets the clock rate of the delegate.
|
||||
void set_clock_rate(double clock_rate);
|
||||
|
||||
/*!
|
||||
If the delegate is not currently in the process of an `update` call, calls it now to catch up to the current time.
|
||||
The call is asynchronous; this method will return immediately.
|
||||
*/
|
||||
void update();
|
||||
|
||||
/// Blocks until any ongoing update is complete.
|
||||
void flush();
|
||||
|
||||
private:
|
||||
std::atomic_flag update_is_ongoing_;
|
||||
AsyncTaskQueue async_task_queue_;
|
||||
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> previous_time_point_;
|
||||
bool has_previous_time_point_ = false;
|
||||
double error_ = 0.0;
|
||||
bool has_skipped_ = false;
|
||||
|
||||
Delegate *delegate_ = nullptr;
|
||||
double clock_rate_ = 1.0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* BestEffortUpdater_hpp */
|
||||
41
Inputs/Joystick.hpp
Normal file
41
Inputs/Joystick.hpp
Normal file
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// Joystick.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/10/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Joystick_hpp
|
||||
#define Joystick_hpp
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Inputs {
|
||||
|
||||
/*!
|
||||
Provides an intermediate idealised model of a simple joystick, allowing a host
|
||||
machine to toggle states, while an interested party either observes or polls.
|
||||
*/
|
||||
class Joystick {
|
||||
public:
|
||||
virtual ~Joystick() {}
|
||||
|
||||
enum class DigitalInput {
|
||||
Up, Down, Left, Right, Fire
|
||||
};
|
||||
|
||||
// Host interface.
|
||||
virtual void set_digital_input(DigitalInput digital_input, bool is_active) = 0;
|
||||
virtual void reset_all_inputs() {
|
||||
set_digital_input(DigitalInput::Up, false);
|
||||
set_digital_input(DigitalInput::Down, false);
|
||||
set_digital_input(DigitalInput::Left, false);
|
||||
set_digital_input(DigitalInput::Right, false);
|
||||
set_digital_input(DigitalInput::Fire, false);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Joystick_hpp */
|
||||
38
Inputs/Keyboard.cpp
Normal file
38
Inputs/Keyboard.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// Keyboard.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/9/17.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Keyboard.hpp"
|
||||
|
||||
using namespace Inputs;
|
||||
|
||||
Keyboard::Keyboard() {}
|
||||
|
||||
void Keyboard::set_key_pressed(Key key, bool is_pressed) {
|
||||
size_t key_offset = static_cast<size_t>(key);
|
||||
if(key_offset >= key_states_.size()) {
|
||||
key_states_.resize(key_offset+1, false);
|
||||
}
|
||||
key_states_[key_offset] = is_pressed;
|
||||
|
||||
if(delegate_) delegate_->keyboard_did_change_key(this, key, is_pressed);
|
||||
}
|
||||
|
||||
void Keyboard::reset_all_keys() {
|
||||
std::fill(key_states_.begin(), key_states_.end(), false);
|
||||
if(delegate_) delegate_->reset_all_keys(this);
|
||||
}
|
||||
|
||||
void Keyboard::set_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
bool Keyboard::get_key_state(Key key) {
|
||||
size_t key_offset = static_cast<size_t>(key);
|
||||
if(key_offset >= key_states_.size()) return false;
|
||||
return key_states_[key_offset];
|
||||
}
|
||||
61
Inputs/Keyboard.hpp
Normal file
61
Inputs/Keyboard.hpp
Normal file
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// Keyboard.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/9/17.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Keyboard_hpp
|
||||
#define Keyboard_hpp
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Inputs {
|
||||
|
||||
/*!
|
||||
Provides an intermediate idealised model of a modern-era computer keyboard
|
||||
(so, heavily indebted to the current Windows and Mac layouts), allowing a host
|
||||
machine to toggle states, while an interested party either observes or polls.
|
||||
*/
|
||||
class Keyboard {
|
||||
public:
|
||||
Keyboard();
|
||||
|
||||
enum class Key {
|
||||
Escape, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, PrintScreen, ScrollLock, Pause,
|
||||
BackTick, k1, k2, k3, k4, k5, k6, k7, k8, k9, k0, Hyphen, Equals, BackSpace,
|
||||
Tab, Q, W, E, R, T, Y, U, I, O, P, OpenSquareBracket, CloseSquareBracket, BackSlash,
|
||||
CapsLock, A, S, D, F, G, H, J, K, L, Semicolon, Quote, Hash, Enter,
|
||||
LeftShift, Z, X, C, V, B, N, M, Comma, FullStop, ForwardSlash, RightShift,
|
||||
LeftControl, LeftOption, LeftMeta, Space, RightMeta, RightOption, RightControl,
|
||||
Left, Right, Up, Down,
|
||||
Insert, Home, PageUp, Delete, End, PageDown,
|
||||
NumLock, KeyPadSlash, KeyPadAsterisk, KeyPadDelete,
|
||||
KeyPad7, KeyPad8, KeyPad9, KeyPadPlus,
|
||||
KeyPad4, KeyPad5, KeyPad6, KeyPadMinus,
|
||||
KeyPad1, KeyPad2, KeyPad3, KeyPadEnter,
|
||||
KeyPad0, KeyPadDecimalPoint, KeyPadEquals,
|
||||
Help
|
||||
};
|
||||
|
||||
// Host interface.
|
||||
virtual void set_key_pressed(Key key, bool is_pressed);
|
||||
virtual void reset_all_keys();
|
||||
|
||||
// Delegate interface.
|
||||
struct Delegate {
|
||||
virtual void keyboard_did_change_key(Keyboard *keyboard, Key key, bool is_pressed) = 0;
|
||||
virtual void reset_all_keys(Keyboard *keyboard) = 0;
|
||||
};
|
||||
void set_delegate(Delegate *delegate);
|
||||
bool get_key_state(Key key);
|
||||
|
||||
private:
|
||||
std::vector<bool> key_states_;
|
||||
Delegate *delegate_ = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Keyboard_hpp */
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#include "AmstradCPC.hpp"
|
||||
|
||||
#include "CharacterMapper.hpp"
|
||||
#include "Keyboard.hpp"
|
||||
|
||||
#include "../../Processors/Z80/Z80.hpp"
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
#include "../../Components/8272/i8272.hpp"
|
||||
#include "../../Components/AY38910/AY38910.hpp"
|
||||
|
||||
#include "../MemoryFuzzer.hpp"
|
||||
#include "../Typer.hpp"
|
||||
#include "../Utility/MemoryFuzzer.hpp"
|
||||
#include "../Utility/Typer.hpp"
|
||||
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
|
||||
@@ -35,8 +35,6 @@ namespace AmstradCPC {
|
||||
*/
|
||||
class InterruptTimer {
|
||||
public:
|
||||
InterruptTimer() : timer_(0), interrupt_request_(false) {}
|
||||
|
||||
/*!
|
||||
Indicates that a new hsync pulse has been recognised. This should be
|
||||
supplied on the falling edge of the CRTC HSYNC signal, which is the
|
||||
@@ -93,10 +91,10 @@ class InterruptTimer {
|
||||
}
|
||||
|
||||
private:
|
||||
int reset_counter_;
|
||||
bool interrupt_request_;
|
||||
bool last_interrupt_request_;
|
||||
int timer_;
|
||||
int reset_counter_ = 0;
|
||||
bool interrupt_request_ = false;
|
||||
bool last_interrupt_request_ = false;
|
||||
int timer_ = 0;
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -155,18 +153,8 @@ class AYDeferrer {
|
||||
class CRTCBusHandler {
|
||||
public:
|
||||
CRTCBusHandler(uint8_t *ram, InterruptTimer &interrupt_timer) :
|
||||
cycles_(0),
|
||||
was_enabled_(false),
|
||||
was_sync_(false),
|
||||
pixel_data_(nullptr),
|
||||
pixel_pointer_(nullptr),
|
||||
was_hsync_(false),
|
||||
ram_(ram),
|
||||
interrupt_timer_(interrupt_timer),
|
||||
pixel_divider_(1),
|
||||
mode_(2),
|
||||
next_mode_(2),
|
||||
cycles_into_hsync_(0) {
|
||||
interrupt_timer_(interrupt_timer) {
|
||||
establish_palette_hits();
|
||||
build_mode_table();
|
||||
}
|
||||
@@ -217,14 +205,14 @@ class CRTCBusHandler {
|
||||
// collect some more pixels if output is ongoing
|
||||
if(!is_sync && state.display_enable) {
|
||||
if(!pixel_data_) {
|
||||
pixel_pointer_ = pixel_data_ = crt_->allocate_write_area(320);
|
||||
pixel_pointer_ = pixel_data_ = crt_->allocate_write_area(320, 8);
|
||||
}
|
||||
if(pixel_pointer_) {
|
||||
// the CPC shuffles output lines as:
|
||||
// MA13 MA12 RA2 RA1 RA0 MA9 MA8 MA7 MA6 MA5 MA4 MA3 MA2 MA1 MA0 CCLK
|
||||
// ... so form the real access address.
|
||||
uint16_t address =
|
||||
(uint16_t)(
|
||||
static_cast<uint16_t>(
|
||||
((state.refresh_address & 0x3ff) << 1) |
|
||||
((state.row_address & 0x7) << 11) |
|
||||
((state.refresh_address & 0x3000) << 2)
|
||||
@@ -233,26 +221,26 @@ class CRTCBusHandler {
|
||||
// fetch two bytes and translate into pixels
|
||||
switch(mode_) {
|
||||
case 0:
|
||||
((uint16_t *)pixel_pointer_)[0] = mode0_output_[ram_[address]];
|
||||
((uint16_t *)pixel_pointer_)[1] = mode0_output_[ram_[address+1]];
|
||||
reinterpret_cast<uint16_t *>(pixel_pointer_)[0] = mode0_output_[ram_[address]];
|
||||
reinterpret_cast<uint16_t *>(pixel_pointer_)[1] = mode0_output_[ram_[address+1]];
|
||||
pixel_pointer_ += 4;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
((uint32_t *)pixel_pointer_)[0] = mode1_output_[ram_[address]];
|
||||
((uint32_t *)pixel_pointer_)[1] = mode1_output_[ram_[address+1]];
|
||||
reinterpret_cast<uint32_t *>(pixel_pointer_)[0] = mode1_output_[ram_[address]];
|
||||
reinterpret_cast<uint32_t *>(pixel_pointer_)[1] = mode1_output_[ram_[address+1]];
|
||||
pixel_pointer_ += 8;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
((uint64_t *)pixel_pointer_)[0] = mode2_output_[ram_[address]];
|
||||
((uint64_t *)pixel_pointer_)[1] = mode2_output_[ram_[address+1]];
|
||||
reinterpret_cast<uint64_t *>(pixel_pointer_)[0] = mode2_output_[ram_[address]];
|
||||
reinterpret_cast<uint64_t *>(pixel_pointer_)[1] = mode2_output_[ram_[address+1]];
|
||||
pixel_pointer_ += 16;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
((uint16_t *)pixel_pointer_)[0] = mode3_output_[ram_[address]];
|
||||
((uint16_t *)pixel_pointer_)[1] = mode3_output_[ram_[address+1]];
|
||||
reinterpret_cast<uint16_t *>(pixel_pointer_)[0] = mode3_output_[ram_[address]];
|
||||
reinterpret_cast<uint16_t *>(pixel_pointer_)[1] = mode3_output_[ram_[address+1]];
|
||||
pixel_pointer_ += 4;
|
||||
break;
|
||||
|
||||
@@ -356,7 +344,7 @@ class CRTCBusHandler {
|
||||
|
||||
private:
|
||||
void output_border(unsigned int length) {
|
||||
uint8_t *colour_pointer = (uint8_t *)crt_->allocate_write_area(1);
|
||||
uint8_t *colour_pointer = static_cast<uint8_t *>(crt_->allocate_write_area(1));
|
||||
if(colour_pointer) *colour_pointer = border_;
|
||||
crt_->output_level(length * 16);
|
||||
}
|
||||
@@ -374,16 +362,16 @@ class CRTCBusHandler {
|
||||
|
||||
void establish_palette_hits() {
|
||||
for(int c = 0; c < 256; c++) {
|
||||
mode0_palette_hits_[Mode0Colour0(c)].push_back((uint8_t)c);
|
||||
mode0_palette_hits_[Mode0Colour1(c)].push_back((uint8_t)c);
|
||||
mode0_palette_hits_[Mode0Colour0(c)].push_back(static_cast<uint8_t>(c));
|
||||
mode0_palette_hits_[Mode0Colour1(c)].push_back(static_cast<uint8_t>(c));
|
||||
|
||||
mode1_palette_hits_[Mode1Colour0(c)].push_back((uint8_t)c);
|
||||
mode1_palette_hits_[Mode1Colour1(c)].push_back((uint8_t)c);
|
||||
mode1_palette_hits_[Mode1Colour2(c)].push_back((uint8_t)c);
|
||||
mode1_palette_hits_[Mode1Colour3(c)].push_back((uint8_t)c);
|
||||
mode1_palette_hits_[Mode1Colour0(c)].push_back(static_cast<uint8_t>(c));
|
||||
mode1_palette_hits_[Mode1Colour1(c)].push_back(static_cast<uint8_t>(c));
|
||||
mode1_palette_hits_[Mode1Colour2(c)].push_back(static_cast<uint8_t>(c));
|
||||
mode1_palette_hits_[Mode1Colour3(c)].push_back(static_cast<uint8_t>(c));
|
||||
|
||||
mode3_palette_hits_[Mode3Colour0(c)].push_back((uint8_t)c);
|
||||
mode3_palette_hits_[Mode3Colour1(c)].push_back((uint8_t)c);
|
||||
mode3_palette_hits_[Mode3Colour0(c)].push_back(static_cast<uint8_t>(c));
|
||||
mode3_palette_hits_[Mode3Colour1(c)].push_back(static_cast<uint8_t>(c));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,7 +381,7 @@ class CRTCBusHandler {
|
||||
// Mode 0: abcdefgh -> [gcea] [hdfb]
|
||||
for(int c = 0; c < 256; c++) {
|
||||
// prepare mode 0
|
||||
uint8_t *mode0_pixels = (uint8_t *)&mode0_output_[c];
|
||||
uint8_t *mode0_pixels = reinterpret_cast<uint8_t *>(&mode0_output_[c]);
|
||||
mode0_pixels[0] = palette_[Mode0Colour0(c)];
|
||||
mode0_pixels[1] = palette_[Mode0Colour1(c)];
|
||||
}
|
||||
@@ -402,7 +390,7 @@ class CRTCBusHandler {
|
||||
case 1:
|
||||
for(int c = 0; c < 256; c++) {
|
||||
// prepare mode 1
|
||||
uint8_t *mode1_pixels = (uint8_t *)&mode1_output_[c];
|
||||
uint8_t *mode1_pixels = reinterpret_cast<uint8_t *>(&mode1_output_[c]);
|
||||
mode1_pixels[0] = palette_[Mode1Colour0(c)];
|
||||
mode1_pixels[1] = palette_[Mode1Colour1(c)];
|
||||
mode1_pixels[2] = palette_[Mode1Colour2(c)];
|
||||
@@ -413,7 +401,7 @@ class CRTCBusHandler {
|
||||
case 2:
|
||||
for(int c = 0; c < 256; c++) {
|
||||
// prepare mode 2
|
||||
uint8_t *mode2_pixels = (uint8_t *)&mode2_output_[c];
|
||||
uint8_t *mode2_pixels = reinterpret_cast<uint8_t *>(&mode2_output_[c]);
|
||||
mode2_pixels[0] = palette_[((c & 0x80) >> 7)];
|
||||
mode2_pixels[1] = palette_[((c & 0x40) >> 6)];
|
||||
mode2_pixels[2] = palette_[((c & 0x20) >> 5)];
|
||||
@@ -428,7 +416,7 @@ class CRTCBusHandler {
|
||||
case 3:
|
||||
for(int c = 0; c < 256; c++) {
|
||||
// prepare mode 3
|
||||
uint8_t *mode3_pixels = (uint8_t *)&mode3_output_[c];
|
||||
uint8_t *mode3_pixels = reinterpret_cast<uint8_t *>(&mode3_output_[c]);
|
||||
mode3_pixels[0] = palette_[Mode3Colour0(c)];
|
||||
mode3_pixels[1] = palette_[Mode3Colour1(c)];
|
||||
}
|
||||
@@ -440,7 +428,7 @@ class CRTCBusHandler {
|
||||
switch(mode_) {
|
||||
case 0: {
|
||||
for(uint8_t c : mode0_palette_hits_[pen]) {
|
||||
uint8_t *mode0_pixels = (uint8_t *)&mode0_output_[c];
|
||||
uint8_t *mode0_pixels = reinterpret_cast<uint8_t *>(&mode0_output_[c]);
|
||||
mode0_pixels[0] = palette_[Mode0Colour0(c)];
|
||||
mode0_pixels[1] = palette_[Mode0Colour1(c)];
|
||||
}
|
||||
@@ -448,7 +436,7 @@ class CRTCBusHandler {
|
||||
case 1:
|
||||
if(pen > 3) return;
|
||||
for(uint8_t c : mode1_palette_hits_[pen]) {
|
||||
uint8_t *mode1_pixels = (uint8_t *)&mode1_output_[c];
|
||||
uint8_t *mode1_pixels = reinterpret_cast<uint8_t *>(&mode1_output_[c]);
|
||||
mode1_pixels[0] = palette_[Mode1Colour0(c)];
|
||||
mode1_pixels[1] = palette_[Mode1Colour1(c)];
|
||||
mode1_pixels[2] = palette_[Mode1Colour2(c)];
|
||||
@@ -465,7 +453,7 @@ class CRTCBusHandler {
|
||||
if(pen > 3) return;
|
||||
// Same argument applies here as to case 1, as the unused bits aren't masked out.
|
||||
for(uint8_t c : mode3_palette_hits_[pen]) {
|
||||
uint8_t *mode3_pixels = (uint8_t *)&mode3_output_[c];
|
||||
uint8_t *mode3_pixels = reinterpret_cast<uint8_t *>(&mode3_output_[c]);
|
||||
mode3_pixels[0] = palette_[Mode3Colour0(c)];
|
||||
mode3_pixels[1] = palette_[Mode3Colour1(c)];
|
||||
}
|
||||
@@ -500,19 +488,19 @@ class CRTCBusHandler {
|
||||
return mapping[colour];
|
||||
}
|
||||
|
||||
unsigned int cycles_;
|
||||
unsigned int cycles_ = 0;
|
||||
|
||||
bool was_enabled_, was_sync_, was_hsync_, was_vsync_;
|
||||
int cycles_into_hsync_;
|
||||
bool was_enabled_ = false, was_sync_ = false, was_hsync_ = false, was_vsync_ = false;
|
||||
int cycles_into_hsync_ = 0;
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
||||
uint8_t *pixel_data_, *pixel_pointer_;
|
||||
uint8_t *pixel_data_ = nullptr, *pixel_pointer_ = nullptr;
|
||||
|
||||
uint8_t *ram_;
|
||||
uint8_t *ram_ = nullptr;
|
||||
|
||||
int next_mode_, mode_;
|
||||
int next_mode_ = 2, mode_ = 2;
|
||||
|
||||
unsigned int pixel_divider_;
|
||||
unsigned int pixel_divider_ = 1;
|
||||
uint16_t mode0_output_[256];
|
||||
uint32_t mode1_output_[256];
|
||||
uint64_t mode2_output_[256];
|
||||
@@ -522,9 +510,9 @@ class CRTCBusHandler {
|
||||
std::vector<uint8_t> mode1_palette_hits_[4];
|
||||
std::vector<uint8_t> mode3_palette_hits_[4];
|
||||
|
||||
int pen_;
|
||||
int pen_ = 0;
|
||||
uint8_t palette_[16];
|
||||
uint8_t border_;
|
||||
uint8_t border_ = 0;
|
||||
|
||||
InterruptTimer &interrupt_timer_;
|
||||
};
|
||||
@@ -582,12 +570,25 @@ class KeyboardState: public GI::AY38910::PortHandler {
|
||||
class FDC: public Intel::i8272::i8272 {
|
||||
private:
|
||||
Intel::i8272::BusHandler bus_handler_;
|
||||
std::shared_ptr<Storage::Disk::Drive> drive_;
|
||||
|
||||
public:
|
||||
FDC() : i8272(bus_handler_, Cycles(8000000), 16, 300) {}
|
||||
FDC() :
|
||||
i8272(bus_handler_, Cycles(8000000)),
|
||||
drive_(new Storage::Disk::Drive(8000000, 300, 1)) {
|
||||
set_drive(drive_);
|
||||
}
|
||||
|
||||
void set_motor_on(bool on) {
|
||||
Intel::i8272::i8272::set_motor_on(on);
|
||||
drive_->set_motor_on(on);
|
||||
}
|
||||
|
||||
void select_drive(int c) {
|
||||
// TODO: support more than one drive.
|
||||
}
|
||||
|
||||
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||
drive_->set_disk(disk);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -919,7 +920,7 @@ class ConcreteMachine:
|
||||
|
||||
// See header; provides the system ROMs.
|
||||
void set_rom(ROMType type, std::vector<uint8_t> data) override final {
|
||||
roms_[(int)type] = data;
|
||||
roms_[static_cast<int>(type)] = data;
|
||||
}
|
||||
|
||||
void set_component_is_sleeping(void *component, bool is_sleeping) override final {
|
||||
@@ -952,6 +953,10 @@ class ConcreteMachine:
|
||||
key_state_.clear_all_keys();
|
||||
}
|
||||
|
||||
KeyboardMapper &get_keyboard_mapper() override {
|
||||
return keyboard_mapper_;
|
||||
}
|
||||
|
||||
private:
|
||||
inline void write_to_gate_array(uint8_t value) {
|
||||
switch(value >> 6) {
|
||||
@@ -1031,6 +1036,7 @@ class ConcreteMachine:
|
||||
uint8_t *write_pointers_[4];
|
||||
|
||||
KeyboardState key_state_;
|
||||
AmstradCPC::KeyboardMapper keyboard_mapper_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -25,25 +25,6 @@ enum ROMType: int {
|
||||
AMSDOS
|
||||
};
|
||||
|
||||
enum Key: uint16_t {
|
||||
#define Line(l, k1, k2, k3, k4, k5, k6, k7, k8) \
|
||||
k1 = (l << 4) | 0x07, k2 = (l << 4) | 0x06, k3 = (l << 4) | 0x05, k4 = (l << 4) | 0x04,\
|
||||
k5 = (l << 4) | 0x03, k6 = (l << 4) | 0x02, k7 = (l << 4) | 0x01, k8 = (l << 4) | 0x00,
|
||||
|
||||
Line(0, KeyFDot, KeyEnter, KeyF3, KeyF6, KeyF9, KeyDown, KeyRight, KeyUp)
|
||||
Line(1, KeyF0, KeyF2, KeyF1, KeyF5, KeyF8, KeyF7, KeyCopy, KeyLeft)
|
||||
Line(2, KeyControl, KeyBackSlash, KeyShift, KeyF4, KeyRightSquareBracket, KeyReturn, KeyLeftSquareBracket, KeyClear)
|
||||
Line(3, KeyFullStop, KeyForwardSlash, KeyColon, KeySemicolon, KeyP, KeyAt, KeyMinus, KeyCaret)
|
||||
Line(4, KeyComma, KeyM, KeyK, KeyL, KeyI, KeyO, Key9, Key0)
|
||||
Line(5, KeySpace, KeyN, KeyJ, KeyH, KeyY, KeyU, Key7, Key8)
|
||||
Line(6, KeyV, KeyB, KeyF, KeyG, KeyT, KeyR, Key5, Key6)
|
||||
Line(7, KeyX, KeyC, KeyD, KeyS, KeyW, KeyE, Key3, Key4)
|
||||
Line(8, KeyZ, KeyCapsLock, KeyA, KeyTab, KeyQ, KeyEscape, Key2, Key1)
|
||||
Line(9, KeyDelete, KeyJoy1Fire3, KeyJoy2Fire2, KeyJoy1Fire1, KeyJoy1Right, KeyJoy1Left, KeyJoy1Down, KeyJoy1Up)
|
||||
|
||||
#undef Line
|
||||
};
|
||||
|
||||
/*!
|
||||
Models an Amstrad CPC.
|
||||
*/
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
//
|
||||
// CharacterMapper.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 11/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Machines_AmstradCPC_CharacterMapper_hpp
|
||||
#define Machines_AmstradCPC_CharacterMapper_hpp
|
||||
|
||||
#include "../Typer.hpp"
|
||||
|
||||
namespace AmstradCPC {
|
||||
|
||||
class CharacterMapper: public ::Utility::CharacterMapper {
|
||||
public:
|
||||
uint16_t *sequence_for_character(char character);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* CharacterMapper_hpp */
|
||||
@@ -1,20 +1,83 @@
|
||||
//
|
||||
// CharacterMapper.cpp
|
||||
// Keyboard.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 11/08/2017.
|
||||
// Created by Thomas Harte on 10/10/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "CharacterMapper.hpp"
|
||||
#include "AmstradCPC.hpp"
|
||||
#include "Keyboard.hpp"
|
||||
|
||||
using namespace AmstradCPC;
|
||||
|
||||
uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
|
||||
#define BIND(source, dest) case Inputs::Keyboard::Key::source: return dest
|
||||
switch(key) {
|
||||
default: return KeyCopy;
|
||||
|
||||
BIND(k0, Key0); BIND(k1, Key1); BIND(k2, Key2); BIND(k3, Key3); BIND(k4, Key4);
|
||||
BIND(k5, Key5); BIND(k6, Key6); BIND(k7, Key7); BIND(k8, Key8); BIND(k9, Key9);
|
||||
BIND(Q, KeyQ); BIND(W, KeyW); BIND(E, KeyE); BIND(R, KeyR); BIND(T, KeyT);
|
||||
BIND(Y, KeyY); BIND(U, KeyU); BIND(I, KeyI); BIND(O, KeyO); BIND(P, KeyP);
|
||||
BIND(A, KeyA); BIND(S, KeyS); BIND(D, KeyD); BIND(F, KeyF); BIND(G, KeyG);
|
||||
BIND(H, KeyH); BIND(J, KeyJ); BIND(K, KeyK); BIND(L, KeyL);
|
||||
BIND(Z, KeyZ); BIND(X, KeyX); BIND(C, KeyC); BIND(V, KeyV);
|
||||
BIND(B, KeyB); BIND(N, KeyN); BIND(M, KeyM);
|
||||
|
||||
BIND(Escape, KeyEscape);
|
||||
BIND(F1, KeyF1); BIND(F2, KeyF2); BIND(F3, KeyF3); BIND(F4, KeyF4); BIND(F5, KeyF5);
|
||||
BIND(F6, KeyF6); BIND(F7, KeyF7); BIND(F8, KeyF8); BIND(F9, KeyF9); BIND(F10, KeyF0);
|
||||
|
||||
BIND(F11, KeyRightSquareBracket);
|
||||
BIND(F12, KeyClear);
|
||||
|
||||
BIND(Hyphen, KeyMinus); BIND(Equals, KeyCaret); BIND(BackSpace, KeyDelete);
|
||||
BIND(Tab, KeyTab);
|
||||
|
||||
BIND(OpenSquareBracket, KeyAt);
|
||||
BIND(CloseSquareBracket, KeyLeftSquareBracket);
|
||||
BIND(BackSlash, KeyBackSlash);
|
||||
|
||||
BIND(CapsLock, KeyCapsLock);
|
||||
BIND(Semicolon, KeyColon);
|
||||
BIND(Quote, KeySemicolon);
|
||||
BIND(Hash, KeyRightSquareBracket);
|
||||
BIND(Enter, KeyReturn);
|
||||
|
||||
BIND(LeftShift, KeyShift);
|
||||
BIND(Comma, KeyComma);
|
||||
BIND(FullStop, KeyFullStop);
|
||||
BIND(ForwardSlash, KeyForwardSlash);
|
||||
BIND(RightShift, KeyShift);
|
||||
|
||||
BIND(LeftControl, KeyControl); BIND(LeftOption, KeyControl); BIND(LeftMeta, KeyControl);
|
||||
BIND(Space, KeySpace);
|
||||
BIND(RightMeta, KeyControl); BIND(RightOption, KeyControl); BIND(RightControl, KeyControl);
|
||||
|
||||
BIND(Left, KeyLeft); BIND(Right, KeyRight);
|
||||
BIND(Up, KeyUp); BIND(Down, KeyDown);
|
||||
|
||||
BIND(KeyPad0, KeyF0);
|
||||
BIND(KeyPad1, KeyF1); BIND(KeyPad2, KeyF2); BIND(KeyPad3, KeyF3);
|
||||
BIND(KeyPad4, KeyF4); BIND(KeyPad5, KeyF5); BIND(KeyPad6, KeyF6);
|
||||
BIND(KeyPad7, KeyF7); BIND(KeyPad8, KeyF8); BIND(KeyPad9, KeyF9);
|
||||
BIND(KeyPadPlus, KeySemicolon);
|
||||
BIND(KeyPadMinus, KeyMinus);
|
||||
|
||||
BIND(KeyPadEnter, KeyEnter);
|
||||
BIND(KeyPadDecimalPoint, KeyFullStop);
|
||||
BIND(KeyPadEquals, KeyMinus);
|
||||
BIND(KeyPadSlash, KeyForwardSlash);
|
||||
BIND(KeyPadAsterisk, KeyColon);
|
||||
BIND(KeyPadDelete, KeyDelete);
|
||||
}
|
||||
#undef BIND
|
||||
}
|
||||
|
||||
uint16_t *CharacterMapper::sequence_for_character(char character) {
|
||||
#define KEYS(...) {__VA_ARGS__, EndSequence}
|
||||
#define SHIFT(...) {KeyShift, __VA_ARGS__, EndSequence}
|
||||
#define X {NotMapped}
|
||||
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
|
||||
#define SHIFT(...) {KeyShift, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
|
||||
#define X {KeyboardMachine::Machine::KeyNotMapped}
|
||||
static KeySequence key_sequences[] = {
|
||||
/* NUL */ X, /* SOH */ X,
|
||||
/* STX */ X, /* ETX */ X,
|
||||
46
Machines/AmstradCPC/Keyboard.hpp
Normal file
46
Machines/AmstradCPC/Keyboard.hpp
Normal file
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// Keyboard.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/10/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Machines_AmstradCPC_Keyboard_hpp
|
||||
#define Machines_AmstradCPC_Keyboard_hpp
|
||||
|
||||
#include "../KeyboardMachine.hpp"
|
||||
#include "../Utility/Typer.hpp"
|
||||
|
||||
namespace AmstradCPC {
|
||||
|
||||
enum Key: uint16_t {
|
||||
#define Line(l, k1, k2, k3, k4, k5, k6, k7, k8) \
|
||||
k1 = (l << 4) | 0x07, k2 = (l << 4) | 0x06, k3 = (l << 4) | 0x05, k4 = (l << 4) | 0x04,\
|
||||
k5 = (l << 4) | 0x03, k6 = (l << 4) | 0x02, k7 = (l << 4) | 0x01, k8 = (l << 4) | 0x00,
|
||||
|
||||
Line(0, KeyFDot, KeyEnter, KeyF3, KeyF6, KeyF9, KeyDown, KeyRight, KeyUp)
|
||||
Line(1, KeyF0, KeyF2, KeyF1, KeyF5, KeyF8, KeyF7, KeyCopy, KeyLeft)
|
||||
Line(2, KeyControl, KeyBackSlash, KeyShift, KeyF4, KeyRightSquareBracket, KeyReturn, KeyLeftSquareBracket, KeyClear)
|
||||
Line(3, KeyFullStop, KeyForwardSlash, KeyColon, KeySemicolon, KeyP, KeyAt, KeyMinus, KeyCaret)
|
||||
Line(4, KeyComma, KeyM, KeyK, KeyL, KeyI, KeyO, Key9, Key0)
|
||||
Line(5, KeySpace, KeyN, KeyJ, KeyH, KeyY, KeyU, Key7, Key8)
|
||||
Line(6, KeyV, KeyB, KeyF, KeyG, KeyT, KeyR, Key5, Key6)
|
||||
Line(7, KeyX, KeyC, KeyD, KeyS, KeyW, KeyE, Key3, Key4)
|
||||
Line(8, KeyZ, KeyCapsLock, KeyA, KeyTab, KeyQ, KeyEscape, Key2, Key1)
|
||||
Line(9, KeyDelete, KeyJoy1Fire3, KeyJoy2Fire2, KeyJoy1Fire1, KeyJoy1Right, KeyJoy1Left, KeyJoy1Down, KeyJoy1Up)
|
||||
|
||||
#undef Line
|
||||
};
|
||||
|
||||
struct KeyboardMapper: public KeyboardMachine::Machine::KeyboardMapper {
|
||||
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key);
|
||||
};
|
||||
|
||||
struct CharacterMapper: public ::Utility::CharacterMapper {
|
||||
uint16_t *sequence_for_character(char character);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif /* KeyboardMapper_hpp */
|
||||
@@ -30,6 +30,33 @@ namespace {
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
class Joystick: public Inputs::Joystick {
|
||||
public:
|
||||
Joystick(Bus *bus, size_t shift, size_t fire_tia_input) :
|
||||
bus_(bus), shift_(shift), fire_tia_input_(fire_tia_input) {}
|
||||
|
||||
void set_digital_input(DigitalInput digital_input, bool is_active) {
|
||||
switch(digital_input) {
|
||||
case DigitalInput::Up: bus_->mos6532_.update_port_input(0, 0x10 >> shift_, is_active); break;
|
||||
case DigitalInput::Down: bus_->mos6532_.update_port_input(0, 0x20 >> shift_, is_active); break;
|
||||
case DigitalInput::Left: bus_->mos6532_.update_port_input(0, 0x40 >> shift_, is_active); break;
|
||||
case DigitalInput::Right: bus_->mos6532_.update_port_input(0, 0x80 >> shift_, is_active); break;
|
||||
|
||||
// TODO: latching
|
||||
case DigitalInput::Fire:
|
||||
if(is_active)
|
||||
bus_->tia_input_value_[fire_tia_input_] &= ~0x80;
|
||||
else
|
||||
bus_->tia_input_value_[fire_tia_input_] |= 0x80;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
size_t shift_, fire_tia_input_;
|
||||
Bus *bus_;
|
||||
};
|
||||
|
||||
class ConcreteMachine:
|
||||
public Machine,
|
||||
public Outputs::CRT::Delegate {
|
||||
@@ -44,7 +71,7 @@ class ConcreteMachine:
|
||||
close_output();
|
||||
}
|
||||
|
||||
void configure_as_target(const StaticAnalyser::Target &target) {
|
||||
void configure_as_target(const StaticAnalyser::Target &target) override {
|
||||
const std::vector<uint8_t> &rom = target.media.cartridges.front()->get_segments().front().data;
|
||||
switch(target.atari.paging_model) {
|
||||
case StaticAnalyser::Atari2600PagingModel::ActivisionStack: bus_.reset(new Cartridge::Cartridge<Cartridge::ActivisionStack>(rom)); break;
|
||||
@@ -79,34 +106,20 @@ class ConcreteMachine:
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
joysticks_.emplace_back(new Joystick(bus_.get(), 0, 0));
|
||||
joysticks_.emplace_back(new Joystick(bus_.get(), 4, 1));
|
||||
}
|
||||
|
||||
bool insert_media(const StaticAnalyser::Media &media) {
|
||||
bool insert_media(const StaticAnalyser::Media &media) override {
|
||||
return false;
|
||||
}
|
||||
|
||||
void set_digital_input(Atari2600DigitalInput input, bool state) {
|
||||
switch (input) {
|
||||
case Atari2600DigitalInputJoy1Up: bus_->mos6532_.update_port_input(0, 0x10, state); break;
|
||||
case Atari2600DigitalInputJoy1Down: bus_->mos6532_.update_port_input(0, 0x20, state); break;
|
||||
case Atari2600DigitalInputJoy1Left: bus_->mos6532_.update_port_input(0, 0x40, state); break;
|
||||
case Atari2600DigitalInputJoy1Right: bus_->mos6532_.update_port_input(0, 0x80, state); break;
|
||||
|
||||
case Atari2600DigitalInputJoy2Up: bus_->mos6532_.update_port_input(0, 0x01, state); break;
|
||||
case Atari2600DigitalInputJoy2Down: bus_->mos6532_.update_port_input(0, 0x02, state); break;
|
||||
case Atari2600DigitalInputJoy2Left: bus_->mos6532_.update_port_input(0, 0x04, state); break;
|
||||
case Atari2600DigitalInputJoy2Right: bus_->mos6532_.update_port_input(0, 0x08, state); break;
|
||||
|
||||
// TODO: latching
|
||||
case Atari2600DigitalInputJoy1Fire: if(state) bus_->tia_input_value_[0] &= ~0x80; else bus_->tia_input_value_[0] |= 0x80; break;
|
||||
case Atari2600DigitalInputJoy2Fire: if(state) bus_->tia_input_value_[1] &= ~0x80; else bus_->tia_input_value_[1] |= 0x80; break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
|
||||
return joysticks_;
|
||||
}
|
||||
|
||||
|
||||
void set_switch_is_enabled(Atari2600Switch input, bool state) {
|
||||
void set_switch_is_enabled(Atari2600Switch input, bool state) override {
|
||||
switch(input) {
|
||||
case Atari2600SwitchReset: bus_->mos6532_.update_port_input(1, 0x01, state); break;
|
||||
case Atari2600SwitchSelect: bus_->mos6532_.update_port_input(1, 0x02, state); break;
|
||||
@@ -116,36 +129,36 @@ class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
|
||||
void set_reset_switch(bool state) {
|
||||
void set_reset_switch(bool state) override {
|
||||
bus_->set_reset_line(state);
|
||||
}
|
||||
|
||||
// to satisfy CRTMachine::Machine
|
||||
void setup_output(float aspect_ratio) {
|
||||
void setup_output(float aspect_ratio) override {
|
||||
bus_->tia_.reset(new TIA);
|
||||
bus_->speaker_.reset(new Speaker);
|
||||
bus_->speaker_->set_input_rate((float)(get_clock_rate() / (double)CPUTicksPerAudioTick));
|
||||
bus_->speaker_->set_input_rate(static_cast<float>(get_clock_rate() / static_cast<double>(CPUTicksPerAudioTick)));
|
||||
bus_->tia_->get_crt()->set_delegate(this);
|
||||
}
|
||||
|
||||
void close_output() {
|
||||
void close_output() override {
|
||||
bus_.reset();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt() {
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt() override {
|
||||
return bus_->tia_->get_crt();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::Speaker> get_speaker() {
|
||||
std::shared_ptr<Outputs::Speaker> get_speaker() override {
|
||||
return bus_->speaker_;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) {
|
||||
void run_for(const Cycles cycles) override {
|
||||
bus_->run_for(cycles);
|
||||
}
|
||||
|
||||
// to satisfy Outputs::CRT::Delegate
|
||||
void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs) {
|
||||
void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs) override {
|
||||
const size_t number_of_frame_records = sizeof(frame_records_) / sizeof(frame_records_[0]);
|
||||
frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_frames = number_of_frames;
|
||||
frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_unexpected_vertical_syncs = number_of_unexpected_vertical_syncs;
|
||||
@@ -175,14 +188,13 @@ class ConcreteMachine:
|
||||
bus_->tia_->set_output_mode(TIA::OutputMode::PAL);
|
||||
}
|
||||
|
||||
bus_->speaker_->set_input_rate((float)(clock_rate / (double)CPUTicksPerAudioTick));
|
||||
bus_->speaker_->set_high_frequency_cut_off((float)(clock_rate / ((double)CPUTicksPerAudioTick * 2.0)));
|
||||
bus_->speaker_->set_input_rate(static_cast<float>(clock_rate / static_cast<double>(CPUTicksPerAudioTick)));
|
||||
bus_->speaker_->set_high_frequency_cut_off(static_cast<float>(clock_rate / (static_cast<double>(CPUTicksPerAudioTick) * 2.0)));
|
||||
set_clock_rate(clock_rate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
// the bus
|
||||
std::unique_ptr<Bus> bus_;
|
||||
@@ -196,6 +208,7 @@ class ConcreteMachine:
|
||||
} frame_records_[4];
|
||||
unsigned int frame_record_pointer_;
|
||||
bool is_ntsc_;
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../JoystickMachine.hpp"
|
||||
|
||||
#include "Atari2600Inputs.h"
|
||||
|
||||
@@ -21,16 +22,14 @@ namespace Atari2600 {
|
||||
*/
|
||||
class Machine:
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine {
|
||||
public ConfigurationTarget::Machine,
|
||||
public JoystickMachine::Machine {
|
||||
public:
|
||||
virtual ~Machine();
|
||||
|
||||
/// Creates and returns an Atari 2600 on the heap.
|
||||
static Machine *Atari2600();
|
||||
|
||||
/// Sets @c input to @c state.
|
||||
virtual void set_digital_input(Atari2600DigitalInput input, bool state) = 0;
|
||||
|
||||
/// Sets the switch @c input to @c state.
|
||||
virtual void set_switch_is_enabled(Atari2600Switch input, bool state) = 0;
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class Pitfall2: public BusExtender {
|
||||
if(isReadOperation(operation)) {
|
||||
*value = random_number_generator_;
|
||||
}
|
||||
random_number_generator_ = (uint8_t)(
|
||||
random_number_generator_ = static_cast<uint8_t>(
|
||||
(random_number_generator_ << 1) |
|
||||
(~( (random_number_generator_ >> 7) ^
|
||||
(random_number_generator_ >> 5) ^
|
||||
@@ -73,7 +73,7 @@ class Pitfall2: public BusExtender {
|
||||
mask_[address & 7] = 0x00;
|
||||
break;
|
||||
case 0x1058: case 0x1059: case 0x105a: case 0x105b: case 0x105c: case 0x105d: case 0x105e: case 0x105f:
|
||||
featcher_address_[address & 7] = (featcher_address_[address & 7] & 0x00ff) | (uint16_t)(*value << 8);
|
||||
featcher_address_[address & 7] = (featcher_address_[address & 7] & 0x00ff) | static_cast<uint16_t>(*value << 8);
|
||||
break;
|
||||
case 0x1070: case 0x1071: case 0x1072: case 0x1073: case 0x1074: case 0x1075: case 0x1076: case 0x1077:
|
||||
random_number_generator_ = 0;
|
||||
|
||||
@@ -37,19 +37,19 @@ TIA::TIA(bool create_crt) :
|
||||
}
|
||||
|
||||
for(int c = 0; c < 256; c++) {
|
||||
reverse_table[c] = (uint8_t)(
|
||||
reverse_table[c] = static_cast<uint8_t>(
|
||||
((c & 0x01) << 7) | ((c & 0x02) << 5) | ((c & 0x04) << 3) | ((c & 0x08) << 1) |
|
||||
((c & 0x10) >> 1) | ((c & 0x20) >> 3) | ((c & 0x40) >> 5) | ((c & 0x80) >> 7)
|
||||
);
|
||||
}
|
||||
|
||||
for(int c = 0; c < 64; c++) {
|
||||
bool has_playfield = c & (int)(CollisionType::Playfield);
|
||||
bool has_ball = c & (int)(CollisionType::Ball);
|
||||
bool has_player0 = c & (int)(CollisionType::Player0);
|
||||
bool has_player1 = c & (int)(CollisionType::Player1);
|
||||
bool has_missile0 = c & (int)(CollisionType::Missile0);
|
||||
bool has_missile1 = c & (int)(CollisionType::Missile1);
|
||||
bool has_playfield = c & static_cast<int>(CollisionType::Playfield);
|
||||
bool has_ball = c & static_cast<int>(CollisionType::Ball);
|
||||
bool has_player0 = c & static_cast<int>(CollisionType::Player0);
|
||||
bool has_player1 = c & static_cast<int>(CollisionType::Player1);
|
||||
bool has_missile0 = c & static_cast<int>(CollisionType::Missile0);
|
||||
bool has_missile1 = c & static_cast<int>(CollisionType::Missile1);
|
||||
|
||||
uint8_t collision_registers[8];
|
||||
collision_registers[0] = ((has_missile0 && has_player1) ? 0x80 : 0x00) | ((has_missile0 && has_player0) ? 0x40 : 0x00);
|
||||
@@ -71,51 +71,51 @@ TIA::TIA(bool create_crt) :
|
||||
(collision_registers[7] << 8);
|
||||
|
||||
// all priority modes show the background if nothing else is present
|
||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] =
|
||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] =
|
||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] =
|
||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::Background;
|
||||
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::Standard)][c] =
|
||||
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreLeft)][c] =
|
||||
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreRight)][c] =
|
||||
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::OnTop)][c] = static_cast<uint8_t>(ColourIndex::Background);
|
||||
|
||||
// test 1 for standard priority: if there is a playfield or ball pixel, plot that colour
|
||||
if(has_playfield || has_ball) {
|
||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] = (uint8_t)ColourIndex::PlayfieldBall;
|
||||
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::Standard)][c] = static_cast<uint8_t>(ColourIndex::PlayfieldBall);
|
||||
}
|
||||
|
||||
// test 1 for score mode: if there is a ball pixel, plot that colour
|
||||
if(has_ball) {
|
||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] =
|
||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] = (uint8_t)ColourIndex::PlayfieldBall;
|
||||
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreLeft)][c] =
|
||||
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreRight)][c] = static_cast<uint8_t>(ColourIndex::PlayfieldBall);
|
||||
}
|
||||
|
||||
// test 1 for on-top mode, test 2 for everbody else: if there is a player 1 or missile 1 pixel, plot that colour
|
||||
if(has_player1 || has_missile1) {
|
||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] =
|
||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] =
|
||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] =
|
||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::PlayerMissile1;
|
||||
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::Standard)][c] =
|
||||
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreLeft)][c] =
|
||||
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreRight)][c] =
|
||||
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::OnTop)][c] = static_cast<uint8_t>(ColourIndex::PlayerMissile1);
|
||||
}
|
||||
|
||||
// in the right-hand side of score mode, the playfield has the same priority as player 1
|
||||
if(has_playfield) {
|
||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] = (uint8_t)ColourIndex::PlayerMissile1;
|
||||
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreRight)][c] = static_cast<uint8_t>(ColourIndex::PlayerMissile1);
|
||||
}
|
||||
|
||||
// next test for everybody: if there is a player 0 or missile 0 pixel, plot that colour instead
|
||||
if(has_player0 || has_missile0) {
|
||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] =
|
||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] =
|
||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] =
|
||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::PlayerMissile0;
|
||||
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::Standard)][c] =
|
||||
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreLeft)][c] =
|
||||
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreRight)][c] =
|
||||
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::OnTop)][c] = static_cast<uint8_t>(ColourIndex::PlayerMissile0);
|
||||
}
|
||||
|
||||
// if this is the left-hand side of score mode, the playfield has the same priority as player 0
|
||||
if(has_playfield) {
|
||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] = (uint8_t)ColourIndex::PlayerMissile0;
|
||||
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreLeft)][c] = static_cast<uint8_t>(ColourIndex::PlayerMissile0);
|
||||
}
|
||||
|
||||
// a final test for 'on top' priority mode: if the playfield or ball are visible, prefer that colour to all others
|
||||
if(has_playfield || has_ball) {
|
||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::PlayfieldBall;
|
||||
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::OnTop)][c] = static_cast<uint8_t>(ColourIndex::PlayfieldBall);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,7 +162,7 @@ void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) {
|
||||
// cycles_per_line * 2 cycles of information from one sync edge to the next
|
||||
crt_->set_new_display_type(cycles_per_line * 2 - 1, display_type);
|
||||
|
||||
/* speaker_->set_input_rate((float)(get_clock_rate() / 38.0));*/
|
||||
/* speaker_->set_input_rate(static_cast<float>(get_clock_rate() / 38.0));*/
|
||||
}
|
||||
|
||||
void TIA::run_for(const Cycles cycles) {
|
||||
@@ -203,23 +203,23 @@ int TIA::get_cycles_until_horizontal_blank(const Cycles from_offset) {
|
||||
}
|
||||
|
||||
void TIA::set_background_colour(uint8_t colour) {
|
||||
colour_palette_[(int)ColourIndex::Background] = colour;
|
||||
colour_palette_[static_cast<int>(ColourIndex::Background)] = colour;
|
||||
}
|
||||
|
||||
void TIA::set_playfield(uint16_t offset, uint8_t value) {
|
||||
assert(offset >= 0 && offset < 3);
|
||||
switch(offset) {
|
||||
case 0:
|
||||
background_[1] = (background_[1] & 0x0ffff) | ((uint32_t)reverse_table[value & 0xf0] << 16);
|
||||
background_[0] = (background_[0] & 0xffff0) | (uint32_t)(value >> 4);
|
||||
background_[1] = (background_[1] & 0x0ffff) | (static_cast<uint32_t>(reverse_table[value & 0xf0]) << 16);
|
||||
background_[0] = (background_[0] & 0xffff0) | static_cast<uint32_t>(value >> 4);
|
||||
break;
|
||||
case 1:
|
||||
background_[1] = (background_[1] & 0xf00ff) | ((uint32_t)value << 8);
|
||||
background_[0] = (background_[0] & 0xff00f) | ((uint32_t)reverse_table[value] << 4);
|
||||
background_[1] = (background_[1] & 0xf00ff) | (static_cast<uint32_t>(value) << 8);
|
||||
background_[0] = (background_[0] & 0xff00f) | (static_cast<uint32_t>(reverse_table[value]) << 4);
|
||||
break;
|
||||
case 2:
|
||||
background_[1] = (background_[1] & 0xfff00) | reverse_table[value];
|
||||
background_[0] = (background_[0] & 0x00fff) | ((uint32_t)value << 12);
|
||||
background_[0] = (background_[0] & 0x00fff) | (static_cast<uint32_t>(value) << 12);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -243,7 +243,7 @@ void TIA::set_playfield_control_and_ball_size(uint8_t value) {
|
||||
}
|
||||
|
||||
void TIA::set_playfield_ball_colour(uint8_t colour) {
|
||||
colour_palette_[(int)ColourIndex::PlayfieldBall] = colour;
|
||||
colour_palette_[static_cast<int>(ColourIndex::PlayfieldBall)] = colour;
|
||||
}
|
||||
|
||||
void TIA::set_player_number_and_size(int player, uint8_t value) {
|
||||
@@ -305,7 +305,7 @@ void TIA::set_player_motion(int player, uint8_t motion) {
|
||||
|
||||
void TIA::set_player_missile_colour(int player, uint8_t colour) {
|
||||
assert(player >= 0 && player < 2);
|
||||
colour_palette_[(int)ColourIndex::PlayerMissile0 + player] = colour;
|
||||
colour_palette_[static_cast<int>(ColourIndex::PlayerMissile0) + player] = colour;
|
||||
}
|
||||
|
||||
void TIA::set_missile_enable(int missile, bool enabled) {
|
||||
@@ -360,7 +360,7 @@ void TIA::clear_motion() {
|
||||
}
|
||||
|
||||
uint8_t TIA::get_collision_flags(int offset) {
|
||||
return (uint8_t)((collision_flags_ >> (offset << 1)) << 6) & 0xc0;
|
||||
return static_cast<uint8_t>((collision_flags_ >> (offset << 1)) << 6) & 0xc0;
|
||||
}
|
||||
|
||||
void TIA::clear_collision_flags() {
|
||||
@@ -401,22 +401,22 @@ void TIA::output_for_cycles(int number_of_cycles) {
|
||||
int latent_start = output_cursor + 4;
|
||||
int latent_end = horizontal_counter_ + 4;
|
||||
draw_playfield(latent_start, latent_end);
|
||||
draw_object<Player>(player_[0], (uint8_t)CollisionType::Player0, output_cursor, horizontal_counter_);
|
||||
draw_object<Player>(player_[1], (uint8_t)CollisionType::Player1, output_cursor, horizontal_counter_);
|
||||
draw_missile(missile_[0], player_[0], (uint8_t)CollisionType::Missile0, output_cursor, horizontal_counter_);
|
||||
draw_missile(missile_[1], player_[1], (uint8_t)CollisionType::Missile1, output_cursor, horizontal_counter_);
|
||||
draw_object<Ball>(ball_, (uint8_t)CollisionType::Ball, output_cursor, horizontal_counter_);
|
||||
draw_object<Player>(player_[0], static_cast<uint8_t>(CollisionType::Player0), output_cursor, horizontal_counter_);
|
||||
draw_object<Player>(player_[1], static_cast<uint8_t>(CollisionType::Player1), output_cursor, horizontal_counter_);
|
||||
draw_missile(missile_[0], player_[0], static_cast<uint8_t>(CollisionType::Missile0), output_cursor, horizontal_counter_);
|
||||
draw_missile(missile_[1], player_[1], static_cast<uint8_t>(CollisionType::Missile1), output_cursor, horizontal_counter_);
|
||||
draw_object<Ball>(ball_, static_cast<uint8_t>(CollisionType::Ball), output_cursor, horizontal_counter_);
|
||||
|
||||
// convert to television signals
|
||||
|
||||
#define Period(function, target) \
|
||||
if(output_cursor < target) { \
|
||||
if(horizontal_counter_ <= target) { \
|
||||
if(crt_) crt_->function((unsigned int)((horizontal_counter_ - output_cursor) * 2)); \
|
||||
if(crt_) crt_->function(static_cast<unsigned int>((horizontal_counter_ - output_cursor) * 2)); \
|
||||
horizontal_counter_ %= cycles_per_line; \
|
||||
return; \
|
||||
} else { \
|
||||
if(crt_) crt_->function((unsigned int)((target - output_cursor) * 2)); \
|
||||
if(crt_) crt_->function(static_cast<unsigned int>((target - output_cursor) * 2)); \
|
||||
output_cursor = target; \
|
||||
} \
|
||||
}
|
||||
@@ -442,12 +442,12 @@ void TIA::output_for_cycles(int number_of_cycles) {
|
||||
if(output_mode_ & blank_flag) {
|
||||
if(pixel_target_) {
|
||||
output_pixels(pixels_start_location_, output_cursor);
|
||||
if(crt_) crt_->output_data((unsigned int)(output_cursor - pixels_start_location_) * 2, 2);
|
||||
if(crt_) crt_->output_data(static_cast<unsigned int>(output_cursor - pixels_start_location_) * 2, 2);
|
||||
pixel_target_ = nullptr;
|
||||
pixels_start_location_ = 0;
|
||||
}
|
||||
int duration = std::min(228, horizontal_counter_) - output_cursor;
|
||||
if(crt_) crt_->output_blank((unsigned int)(duration * 2));
|
||||
if(crt_) crt_->output_blank(static_cast<unsigned int>(duration * 2));
|
||||
} else {
|
||||
if(!pixels_start_location_ && crt_) {
|
||||
pixels_start_location_ = output_cursor;
|
||||
@@ -464,7 +464,7 @@ void TIA::output_for_cycles(int number_of_cycles) {
|
||||
}
|
||||
|
||||
if(horizontal_counter_ == cycles_per_line && crt_) {
|
||||
crt_->output_data((unsigned int)(output_cursor - pixels_start_location_) * 2, 2);
|
||||
crt_->output_data(static_cast<unsigned int>(output_cursor - pixels_start_location_) * 2, 2);
|
||||
pixel_target_ = nullptr;
|
||||
pixels_start_location_ = 0;
|
||||
}
|
||||
@@ -490,18 +490,18 @@ void TIA::output_pixels(int start, int end) {
|
||||
if(playfield_priority_ == PlayfieldPriority::Score) {
|
||||
while(start < end && start < first_pixel_cycle + 80) {
|
||||
uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle];
|
||||
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][buffer_value]];
|
||||
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreLeft)][buffer_value]];
|
||||
start++;
|
||||
target_position++;
|
||||
}
|
||||
while(start < end) {
|
||||
uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle];
|
||||
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][buffer_value]];
|
||||
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreRight)][buffer_value]];
|
||||
start++;
|
||||
target_position++;
|
||||
}
|
||||
} else {
|
||||
int table_index = (int)((playfield_priority_ == PlayfieldPriority::Standard) ? ColourMode::Standard : ColourMode::OnTop);
|
||||
int table_index = static_cast<int>((playfield_priority_ == PlayfieldPriority::Standard) ? ColourMode::Standard : ColourMode::OnTop);
|
||||
while(start < end) {
|
||||
uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle];
|
||||
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[table_index][buffer_value]];
|
||||
@@ -553,7 +553,7 @@ void TIA::draw_playfield(int start, int end) {
|
||||
while(aligned_position < end) {
|
||||
int offset = (aligned_position - first_pixel_cycle) >> 2;
|
||||
uint32_t value = ((background_[(offset/20)&background_half_mask_] >> (offset%20))&1) * 0x01010101;
|
||||
*(uint32_t *)&collision_buffer_[aligned_position - first_pixel_cycle] |= value;
|
||||
*reinterpret_cast<uint32_t *>(&collision_buffer_[aligned_position - first_pixel_cycle]) |= value;
|
||||
aligned_position += 4;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,127 +9,20 @@
|
||||
#ifndef Commodore1540_hpp
|
||||
#define Commodore1540_hpp
|
||||
|
||||
#include "../../../Processors/6502/6502.hpp"
|
||||
#include "../../../Components/6522/6522.hpp"
|
||||
|
||||
#include "../SerialBus.hpp"
|
||||
|
||||
#include "../../../Storage/Disk/Disk.hpp"
|
||||
#include "../../../Storage/Disk/DiskController.hpp"
|
||||
#include "Implementation/C1540Base.hpp"
|
||||
|
||||
namespace Commodore {
|
||||
namespace C1540 {
|
||||
|
||||
/*!
|
||||
An implementation of the serial-port VIA in a Commodore 1540 — the VIA that facilitates all
|
||||
IEC bus communications.
|
||||
|
||||
It is wired up such that Port B contains:
|
||||
Bit 0: data input; 1 if the line is low, 0 if it is high;
|
||||
Bit 1: data output; 1 if the line should be low, 0 if it should be high;
|
||||
Bit 2: clock input; 1 if the line is low, 0 if it is high;
|
||||
Bit 3: clock output; 1 if the line is low, 0 if it is high;
|
||||
Bit 4: attention acknowledge output; exclusive ORd with the attention input and ORd onto the data output;
|
||||
Bits 5/6: device select input; the 1540 will act as device 8 + [value of bits]
|
||||
Bit 7: attention input; 1 if the line is low, 0 if it is high
|
||||
|
||||
The attention input is also connected to CA1, similarly inverted — the CA1 wire will be high when the bus is low and vice versa.
|
||||
*/
|
||||
class SerialPortVIA: public MOS::MOS6522<SerialPortVIA>, public MOS::MOS6522IRQDelegate {
|
||||
public:
|
||||
using MOS6522IRQDelegate::set_interrupt_status;
|
||||
|
||||
SerialPortVIA();
|
||||
|
||||
uint8_t get_port_input(Port);
|
||||
|
||||
void set_port_output(Port, uint8_t value, uint8_t mask);
|
||||
void set_serial_line_state(::Commodore::Serial::Line, bool);
|
||||
|
||||
void set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &);
|
||||
|
||||
private:
|
||||
uint8_t port_b_;
|
||||
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
|
||||
bool attention_acknowledge_level_, attention_level_input_, data_level_output_;
|
||||
|
||||
void update_data_line();
|
||||
};
|
||||
|
||||
/*!
|
||||
An implementation of the drive VIA in a Commodore 1540 — the VIA that is used to interface with the disk.
|
||||
|
||||
It is wired up such that Port B contains:
|
||||
Bits 0/1: head step direction
|
||||
Bit 2: motor control
|
||||
Bit 3: LED control (TODO)
|
||||
Bit 4: write protect photocell status (TODO)
|
||||
Bits 5/6: read/write density
|
||||
Bit 7: 0 if sync marks are currently being detected, 1 otherwise.
|
||||
|
||||
... and Port A contains the byte most recently read from the disk or the byte next to write to the disk, depending on data direction.
|
||||
|
||||
It is implied that CA2 might be used to set processor overflow, CA1 a strobe for data input, and one of the CBs being definitive on
|
||||
whether the disk head is being told to read or write, but it's unclear and I've yet to investigate. So, TODO.
|
||||
*/
|
||||
class DriveVIA: public MOS::MOS6522<DriveVIA>, public MOS::MOS6522IRQDelegate {
|
||||
public:
|
||||
class Delegate {
|
||||
public:
|
||||
virtual void drive_via_did_step_head(void *driveVIA, int direction) = 0;
|
||||
virtual void drive_via_did_set_data_density(void *driveVIA, int density) = 0;
|
||||
};
|
||||
void set_delegate(Delegate *);
|
||||
|
||||
using MOS6522IRQDelegate::set_interrupt_status;
|
||||
|
||||
DriveVIA();
|
||||
|
||||
uint8_t get_port_input(Port port);
|
||||
|
||||
void set_sync_detected(bool);
|
||||
void set_data_input(uint8_t);
|
||||
bool get_should_set_overflow();
|
||||
bool get_motor_enabled();
|
||||
|
||||
void set_control_line_output(Port, Line, bool value);
|
||||
|
||||
void set_port_output(Port, uint8_t value, uint8_t direction_mask);
|
||||
|
||||
private:
|
||||
uint8_t port_b_, port_a_;
|
||||
bool should_set_overflow_;
|
||||
bool drive_motor_;
|
||||
uint8_t previous_port_b_output_;
|
||||
Delegate *delegate_;
|
||||
};
|
||||
|
||||
/*!
|
||||
An implementation of the C1540's serial port; this connects incoming line levels to the serial-port VIA.
|
||||
*/
|
||||
class SerialPort : public ::Commodore::Serial::Port {
|
||||
public:
|
||||
void set_input(::Commodore::Serial::Line, ::Commodore::Serial::LineLevel);
|
||||
void set_serial_port_via(const std::shared_ptr<SerialPortVIA> &);
|
||||
|
||||
private:
|
||||
std::weak_ptr<SerialPortVIA> serial_port_VIA_;
|
||||
};
|
||||
|
||||
/*!
|
||||
Provides an emulation of the C1540.
|
||||
*/
|
||||
class Machine:
|
||||
public CPU::MOS6502::BusHandler,
|
||||
public MOS::MOS6522IRQDelegate::Delegate,
|
||||
public DriveVIA::Delegate,
|
||||
public Storage::Disk::Controller {
|
||||
|
||||
class Machine: public MachineBase {
|
||||
public:
|
||||
Machine();
|
||||
|
||||
/*!
|
||||
Sets the ROM image to use for this drive; it is assumed that the buffer provided will be at least 16 kb in size.
|
||||
Sets the ROM image to use for this drive; it is asserted that the buffer provided is 16 kb in size.
|
||||
*/
|
||||
void set_rom(const std::vector<uint8_t> &rom);
|
||||
|
||||
@@ -138,32 +31,11 @@ class Machine:
|
||||
*/
|
||||
void set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus);
|
||||
|
||||
/// Advances time.
|
||||
void run_for(const Cycles cycles);
|
||||
|
||||
/// Inserts @c disk into the drive.
|
||||
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk);
|
||||
|
||||
// to satisfy CPU::MOS6502::Processor
|
||||
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value);
|
||||
|
||||
// to satisfy MOS::MOS6522::Delegate
|
||||
virtual void mos6522_did_change_interrupt_status(void *mos6522);
|
||||
|
||||
// to satisfy DriveVIA::Delegate
|
||||
void drive_via_did_step_head(void *driveVIA, int direction);
|
||||
void drive_via_did_set_data_density(void *driveVIA, int density);
|
||||
|
||||
private:
|
||||
CPU::MOS6502::Processor<Machine, false> m6502_;
|
||||
|
||||
uint8_t ram_[0x800];
|
||||
uint8_t rom_[0x4000];
|
||||
|
||||
std::shared_ptr<SerialPortVIA> serial_port_VIA_;
|
||||
std::shared_ptr<SerialPort> serial_port_;
|
||||
DriveVIA drive_VIA_;
|
||||
|
||||
int shift_register_, bit_window_offset_;
|
||||
virtual void process_input_bit(int value, unsigned int cycles_since_index_hole);
|
||||
virtual void process_index_hole();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -7,35 +7,44 @@
|
||||
//
|
||||
|
||||
#include "C1540.hpp"
|
||||
|
||||
#include <string>
|
||||
#include "../../../Storage/Disk/Encodings/CommodoreGCR.hpp"
|
||||
#include <cassert>
|
||||
|
||||
#include "../../../../Storage/Disk/Encodings/CommodoreGCR.hpp"
|
||||
|
||||
using namespace Commodore::C1540;
|
||||
|
||||
Machine::Machine() :
|
||||
MachineBase::MachineBase() :
|
||||
m6502_(*this),
|
||||
shift_register_(0),
|
||||
Storage::Disk::Controller(1000000, 4, 300),
|
||||
Storage::Disk::Controller(1000000),
|
||||
serial_port_(new SerialPort),
|
||||
serial_port_VIA_(new SerialPortVIA) {
|
||||
serial_port_VIA_port_handler_(new SerialPortVIA(serial_port_VIA_)),
|
||||
drive_VIA_(drive_VIA_port_handler_),
|
||||
serial_port_VIA_(*serial_port_VIA_port_handler_),
|
||||
drive_(new Storage::Disk::Drive(1000000, 300, 2)) {
|
||||
// attach the serial port to its VIA and vice versa
|
||||
serial_port_->set_serial_port_via(serial_port_VIA_);
|
||||
serial_port_VIA_->set_serial_port(serial_port_);
|
||||
serial_port_->set_serial_port_via(serial_port_VIA_port_handler_);
|
||||
serial_port_VIA_port_handler_->set_serial_port(serial_port_);
|
||||
|
||||
// set this instance as the delegate to receive interrupt requests from both VIAs
|
||||
serial_port_VIA_->set_interrupt_delegate(this);
|
||||
drive_VIA_.set_interrupt_delegate(this);
|
||||
drive_VIA_.set_delegate(this);
|
||||
serial_port_VIA_port_handler_->set_interrupt_delegate(this);
|
||||
drive_VIA_port_handler_.set_interrupt_delegate(this);
|
||||
drive_VIA_port_handler_.set_delegate(this);
|
||||
|
||||
// set a bit rate
|
||||
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(3));
|
||||
|
||||
// attach the only drive there is
|
||||
set_drive(drive_);
|
||||
}
|
||||
|
||||
void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) {
|
||||
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus);
|
||||
}
|
||||
|
||||
Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
/*
|
||||
Memory map (given that I'm unsure yet on any potential mirroring):
|
||||
|
||||
@@ -50,13 +59,14 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint
|
||||
else
|
||||
ram_[address] = *value;
|
||||
} else if(address >= 0xc000) {
|
||||
if(isReadOperation(operation))
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_[address & 0x3fff];
|
||||
}
|
||||
} else if(address >= 0x1800 && address <= 0x180f) {
|
||||
if(isReadOperation(operation))
|
||||
*value = serial_port_VIA_->get_register(address);
|
||||
*value = serial_port_VIA_.get_register(address);
|
||||
else
|
||||
serial_port_VIA_->set_register(address, *value);
|
||||
serial_port_VIA_.set_register(address, *value);
|
||||
} else if(address >= 0x1c00 && address <= 0x1c0f) {
|
||||
if(isReadOperation(operation))
|
||||
*value = drive_VIA_.get_register(address);
|
||||
@@ -64,51 +74,52 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint
|
||||
drive_VIA_.set_register(address, *value);
|
||||
}
|
||||
|
||||
serial_port_VIA_->run_for(Cycles(1));
|
||||
serial_port_VIA_.run_for(Cycles(1));
|
||||
drive_VIA_.run_for(Cycles(1));
|
||||
|
||||
return Cycles(1);
|
||||
}
|
||||
|
||||
void Machine::set_rom(const std::vector<uint8_t> &rom) {
|
||||
assert(rom.size() == sizeof(rom_));
|
||||
memcpy(rom_, rom.data(), std::min(sizeof(rom_), rom.size()));
|
||||
}
|
||||
|
||||
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) {
|
||||
std::shared_ptr<Storage::Disk::Drive> drive(new Storage::Disk::Drive);
|
||||
drive->set_disk(disk);
|
||||
set_drive(drive);
|
||||
drive_->set_disk(disk);
|
||||
}
|
||||
|
||||
void Machine::run_for(const Cycles cycles) {
|
||||
m6502_.run_for(cycles);
|
||||
set_motor_on(drive_VIA_.get_motor_enabled());
|
||||
if(drive_VIA_.get_motor_enabled()) // TODO: motor speed up/down
|
||||
|
||||
bool drive_motor = drive_VIA_port_handler_.get_motor_enabled();
|
||||
drive_->set_motor_on(drive_motor);
|
||||
if(drive_motor)
|
||||
Storage::Disk::Controller::run_for(cycles);
|
||||
}
|
||||
|
||||
#pragma mark - 6522 delegate
|
||||
|
||||
void Machine::mos6522_did_change_interrupt_status(void *mos6522) {
|
||||
void MachineBase::mos6522_did_change_interrupt_status(void *mos6522) {
|
||||
// both VIAs are connected to the IRQ line
|
||||
m6502_.set_irq_line(serial_port_VIA_->get_interrupt_line() || drive_VIA_.get_interrupt_line());
|
||||
m6502_.set_irq_line(serial_port_VIA_.get_interrupt_line() || drive_VIA_.get_interrupt_line());
|
||||
}
|
||||
|
||||
#pragma mark - Disk drive
|
||||
|
||||
void Machine::process_input_bit(int value, unsigned int cycles_since_index_hole) {
|
||||
void MachineBase::process_input_bit(int value) {
|
||||
shift_register_ = (shift_register_ << 1) | value;
|
||||
if((shift_register_ & 0x3ff) == 0x3ff) {
|
||||
drive_VIA_.set_sync_detected(true);
|
||||
drive_VIA_port_handler_.set_sync_detected(true);
|
||||
bit_window_offset_ = -1; // i.e. this bit isn't the first within a data window, but the next might be
|
||||
} else {
|
||||
drive_VIA_.set_sync_detected(false);
|
||||
drive_VIA_port_handler_.set_sync_detected(false);
|
||||
}
|
||||
bit_window_offset_++;
|
||||
if(bit_window_offset_ == 8) {
|
||||
drive_VIA_.set_data_input((uint8_t)shift_register_);
|
||||
drive_VIA_port_handler_.set_data_input(static_cast<uint8_t>(shift_register_));
|
||||
bit_window_offset_ = 0;
|
||||
if(drive_VIA_.get_should_set_overflow()) {
|
||||
if(drive_VIA_port_handler_.get_should_set_overflow()) {
|
||||
m6502_.set_overflow_line(true);
|
||||
}
|
||||
}
|
||||
@@ -116,29 +127,29 @@ void Machine::process_input_bit(int value, unsigned int cycles_since_index_hole)
|
||||
}
|
||||
|
||||
// the 1540 does not recognise index holes
|
||||
void Machine::process_index_hole() {}
|
||||
void MachineBase::process_index_hole() {}
|
||||
|
||||
#pragma mak - Drive VIA delegate
|
||||
|
||||
void Machine::drive_via_did_step_head(void *driveVIA, int direction) {
|
||||
step(direction);
|
||||
void MachineBase::drive_via_did_step_head(void *driveVIA, int direction) {
|
||||
drive_->step(direction);
|
||||
}
|
||||
|
||||
void Machine::drive_via_did_set_data_density(void *driveVIA, int density) {
|
||||
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone((unsigned int)density));
|
||||
void MachineBase::drive_via_did_set_data_density(void *driveVIA, int density) {
|
||||
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(static_cast<unsigned int>(density)));
|
||||
}
|
||||
|
||||
#pragma mark - SerialPortVIA
|
||||
|
||||
SerialPortVIA::SerialPortVIA() :
|
||||
port_b_(0x00), attention_acknowledge_level_(false), attention_level_input_(true), data_level_output_(false) {}
|
||||
SerialPortVIA::SerialPortVIA(MOS::MOS6522::MOS6522<SerialPortVIA> &via) :
|
||||
port_b_(0x00), attention_acknowledge_level_(false), attention_level_input_(true), data_level_output_(false), via_(via) {}
|
||||
|
||||
uint8_t SerialPortVIA::get_port_input(Port port) {
|
||||
uint8_t SerialPortVIA::get_port_input(MOS::MOS6522::Port port) {
|
||||
if(port) return port_b_;
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) {
|
||||
void SerialPortVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t mask) {
|
||||
if(port) {
|
||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
||||
if(serialPort) {
|
||||
@@ -152,6 +163,8 @@ void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) {
|
||||
}
|
||||
|
||||
void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) {
|
||||
// printf("[C1540] %s is %s\n", StringForLine(line), value ? "high" : "low");
|
||||
|
||||
switch(line) {
|
||||
default: break;
|
||||
case ::Commodore::Serial::Line::Data: port_b_ = (port_b_ & ~0x01) | (value ? 0x00 : 0x01); break;
|
||||
@@ -159,7 +172,7 @@ void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool v
|
||||
case ::Commodore::Serial::Line::Attention:
|
||||
attention_level_input_ = !value;
|
||||
port_b_ = (port_b_ & ~0x80) | (value ? 0x00 : 0x80);
|
||||
set_control_line_input(Port::A, Line::One, !value);
|
||||
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !value);
|
||||
update_data_line();
|
||||
break;
|
||||
}
|
||||
@@ -187,7 +200,7 @@ void DriveVIA::set_delegate(Delegate *delegate) {
|
||||
// write protect tab uncovered
|
||||
DriveVIA::DriveVIA() : port_b_(0xff), port_a_(0xff), delegate_(nullptr) {}
|
||||
|
||||
uint8_t DriveVIA::get_port_input(Port port) {
|
||||
uint8_t DriveVIA::get_port_input(MOS::MOS6522::Port port) {
|
||||
return port ? port_b_ : port_a_;
|
||||
}
|
||||
|
||||
@@ -207,33 +220,35 @@ bool DriveVIA::get_motor_enabled() {
|
||||
return drive_motor_;
|
||||
}
|
||||
|
||||
void DriveVIA::set_control_line_output(Port port, Line line, bool value) {
|
||||
if(port == Port::A && line == Line::Two) {
|
||||
void DriveVIA::set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) {
|
||||
if(port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) {
|
||||
should_set_overflow_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
void DriveVIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask) {
|
||||
void DriveVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t direction_mask) {
|
||||
if(port) {
|
||||
// record drive motor state
|
||||
drive_motor_ = !!(value&4);
|
||||
if(previous_port_b_output_ != value) {
|
||||
// record drive motor state
|
||||
drive_motor_ = !!(value&4);
|
||||
|
||||
// check for a head step
|
||||
int step_difference = ((value&3) - (previous_port_b_output_&3))&3;
|
||||
if(step_difference) {
|
||||
if(delegate_) delegate_->drive_via_did_step_head(this, (step_difference == 1) ? 1 : -1);
|
||||
// check for a head step
|
||||
int step_difference = ((value&3) - (previous_port_b_output_&3))&3;
|
||||
if(step_difference) {
|
||||
if(delegate_) delegate_->drive_via_did_step_head(this, (step_difference == 1) ? 1 : -1);
|
||||
}
|
||||
|
||||
// check for a change in density
|
||||
int density_difference = (previous_port_b_output_^value) & (3 << 5);
|
||||
if(density_difference && delegate_) {
|
||||
delegate_->drive_via_did_set_data_density(this, (value >> 5)&3);
|
||||
}
|
||||
|
||||
// TODO: something with the drive LED
|
||||
// printf("LED: %s\n", value&8 ? "On" : "Off");
|
||||
|
||||
previous_port_b_output_ = value;
|
||||
}
|
||||
|
||||
// check for a change in density
|
||||
int density_difference = (previous_port_b_output_^value) & (3 << 5);
|
||||
if(density_difference && delegate_) {
|
||||
delegate_->drive_via_did_set_data_density(this, (value >> 5)&3);
|
||||
}
|
||||
|
||||
// TODO: something with the drive LED
|
||||
// printf("LED: %s\n", value&8 ? "On" : "Off");
|
||||
|
||||
previous_port_b_output_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
158
Machines/Commodore/1540/Implementation/C1540Base.hpp
Normal file
158
Machines/Commodore/1540/Implementation/C1540Base.hpp
Normal file
@@ -0,0 +1,158 @@
|
||||
//
|
||||
// C1540Base.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/09/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef C1540Base_hpp
|
||||
#define C1540Base_hpp
|
||||
|
||||
#include "../../../../Processors/6502/6502.hpp"
|
||||
#include "../../../../Components/6522/6522.hpp"
|
||||
|
||||
#include "../../SerialBus.hpp"
|
||||
|
||||
#include "../../../../Storage/Disk/Disk.hpp"
|
||||
|
||||
#include "../../../../Storage/Disk/Controller/DiskController.hpp"
|
||||
|
||||
namespace Commodore {
|
||||
namespace C1540 {
|
||||
|
||||
/*!
|
||||
An implementation of the serial-port VIA in a Commodore 1540 — the VIA that facilitates all
|
||||
IEC bus communications.
|
||||
|
||||
It is wired up such that Port B contains:
|
||||
Bit 0: data input; 1 if the line is low, 0 if it is high;
|
||||
Bit 1: data output; 1 if the line should be low, 0 if it should be high;
|
||||
Bit 2: clock input; 1 if the line is low, 0 if it is high;
|
||||
Bit 3: clock output; 1 if the line is low, 0 if it is high;
|
||||
Bit 4: attention acknowledge output; exclusive ORd with the attention input and ORd onto the data output;
|
||||
Bits 5/6: device select input; the 1540 will act as device 8 + [value of bits]
|
||||
Bit 7: attention input; 1 if the line is low, 0 if it is high
|
||||
|
||||
The attention input is also connected to CA1, similarly inverted — the CA1 wire will be high when the bus is low and vice versa.
|
||||
*/
|
||||
class SerialPortVIA: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||
public:
|
||||
SerialPortVIA(MOS::MOS6522::MOS6522<SerialPortVIA> &via);
|
||||
|
||||
uint8_t get_port_input(MOS::MOS6522::Port);
|
||||
|
||||
void set_port_output(MOS::MOS6522::Port, uint8_t value, uint8_t mask);
|
||||
void set_serial_line_state(::Commodore::Serial::Line, bool);
|
||||
|
||||
void set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &);
|
||||
|
||||
private:
|
||||
MOS::MOS6522::MOS6522<SerialPortVIA> &via_;
|
||||
uint8_t port_b_;
|
||||
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
|
||||
bool attention_acknowledge_level_, attention_level_input_, data_level_output_;
|
||||
|
||||
void update_data_line();
|
||||
};
|
||||
|
||||
/*!
|
||||
An implementation of the drive VIA in a Commodore 1540 — the VIA that is used to interface with the disk.
|
||||
|
||||
It is wired up such that Port B contains:
|
||||
Bits 0/1: head step direction
|
||||
Bit 2: motor control
|
||||
Bit 3: LED control (TODO)
|
||||
Bit 4: write protect photocell status (TODO)
|
||||
Bits 5/6: read/write density
|
||||
Bit 7: 0 if sync marks are currently being detected, 1 otherwise.
|
||||
|
||||
... and Port A contains the byte most recently read from the disk or the byte next to write to the disk, depending on data direction.
|
||||
|
||||
It is implied that CA2 might be used to set processor overflow, CA1 a strobe for data input, and one of the CBs being definitive on
|
||||
whether the disk head is being told to read or write, but it's unclear and I've yet to investigate. So, TODO.
|
||||
*/
|
||||
class DriveVIA: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||
public:
|
||||
class Delegate {
|
||||
public:
|
||||
virtual void drive_via_did_step_head(void *driveVIA, int direction) = 0;
|
||||
virtual void drive_via_did_set_data_density(void *driveVIA, int density) = 0;
|
||||
};
|
||||
void set_delegate(Delegate *);
|
||||
|
||||
DriveVIA();
|
||||
|
||||
uint8_t get_port_input(MOS::MOS6522::Port port);
|
||||
|
||||
void set_sync_detected(bool);
|
||||
void set_data_input(uint8_t);
|
||||
bool get_should_set_overflow();
|
||||
bool get_motor_enabled();
|
||||
|
||||
void set_control_line_output(MOS::MOS6522::Port, MOS::MOS6522::Line, bool value);
|
||||
|
||||
void set_port_output(MOS::MOS6522::Port, uint8_t value, uint8_t direction_mask);
|
||||
|
||||
private:
|
||||
uint8_t port_b_, port_a_;
|
||||
bool should_set_overflow_;
|
||||
bool drive_motor_;
|
||||
uint8_t previous_port_b_output_;
|
||||
Delegate *delegate_;
|
||||
};
|
||||
|
||||
/*!
|
||||
An implementation of the C1540's serial port; this connects incoming line levels to the serial-port VIA.
|
||||
*/
|
||||
class SerialPort : public ::Commodore::Serial::Port {
|
||||
public:
|
||||
void set_input(::Commodore::Serial::Line, ::Commodore::Serial::LineLevel);
|
||||
void set_serial_port_via(const std::shared_ptr<SerialPortVIA> &);
|
||||
|
||||
private:
|
||||
std::weak_ptr<SerialPortVIA> serial_port_VIA_;
|
||||
};
|
||||
|
||||
class MachineBase:
|
||||
public CPU::MOS6502::BusHandler,
|
||||
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
|
||||
public DriveVIA::Delegate,
|
||||
public Storage::Disk::Controller {
|
||||
|
||||
public:
|
||||
MachineBase();
|
||||
|
||||
// to satisfy CPU::MOS6502::Processor
|
||||
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value);
|
||||
|
||||
// to satisfy MOS::MOS6522::Delegate
|
||||
virtual void mos6522_did_change_interrupt_status(void *mos6522);
|
||||
|
||||
// to satisfy DriveVIA::Delegate
|
||||
void drive_via_did_step_head(void *driveVIA, int direction);
|
||||
void drive_via_did_set_data_density(void *driveVIA, int density);
|
||||
|
||||
protected:
|
||||
CPU::MOS6502::Processor<MachineBase, false> m6502_;
|
||||
std::shared_ptr<Storage::Disk::Drive> drive_;
|
||||
|
||||
uint8_t ram_[0x800];
|
||||
uint8_t rom_[0x4000];
|
||||
|
||||
std::shared_ptr<SerialPortVIA> serial_port_VIA_port_handler_;
|
||||
std::shared_ptr<SerialPort> serial_port_;
|
||||
DriveVIA drive_VIA_port_handler_;
|
||||
|
||||
MOS::MOS6522::MOS6522<DriveVIA> drive_VIA_;
|
||||
MOS::MOS6522::MOS6522<SerialPortVIA> serial_port_VIA_;
|
||||
|
||||
int shift_register_, bit_window_offset_;
|
||||
virtual void process_input_bit(int value);
|
||||
virtual void process_index_hole();
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* C1540Base_hpp */
|
||||
@@ -27,12 +27,12 @@ void ::Commodore::Serial::AttachPortAndBus(std::shared_ptr<Port> port, std::shar
|
||||
|
||||
void Bus::add_port(std::shared_ptr<Port> port) {
|
||||
ports_.push_back(port);
|
||||
for(int line = (int)ServiceRequest; line <= (int)Reset; line++) {
|
||||
for(int line = static_cast<int>(ServiceRequest); line <= static_cast<int>(Reset); line++) {
|
||||
// the addition of a new device may change the line output...
|
||||
set_line_output_did_change((Line)line);
|
||||
set_line_output_did_change(static_cast<Line>(line));
|
||||
|
||||
// ... but the new device will need to be told the current state regardless
|
||||
port->set_input((Line)line, line_levels_[line]);
|
||||
port->set_input(static_cast<Line>(line), line_levels_[line]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,8 @@ void Bus::set_line_output_did_change(Line line) {
|
||||
}
|
||||
}
|
||||
|
||||
// printf("[Bus] %s is %s\n", StringForLine(line), new_line_level ? "high" : "low");
|
||||
|
||||
// post an update only if one occurred
|
||||
if(new_line_level != line_levels_[line]) {
|
||||
line_levels_[line] = new_line_level;
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
//
|
||||
// CharacterMapper.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Machines_Commodore_Vic20_CharacterMapper_hpp
|
||||
#define Machines_Commodore_Vic20_CharacterMapper_hpp
|
||||
|
||||
#include "../../Typer.hpp"
|
||||
|
||||
namespace Commodore {
|
||||
namespace Vic20 {
|
||||
|
||||
class CharacterMapper: public ::Utility::CharacterMapper {
|
||||
public:
|
||||
uint16_t *sequence_for_character(char character);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CharacterMapper_hpp */
|
||||
@@ -1,20 +1,80 @@
|
||||
//
|
||||
// CharacterMapper.cpp
|
||||
// Keyboard.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/08/2017.
|
||||
// Created by Thomas Harte on 10/10/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "CharacterMapper.hpp"
|
||||
#include "Vic20.hpp"
|
||||
#include "Keyboard.hpp"
|
||||
|
||||
using namespace Commodore::Vic20;
|
||||
|
||||
uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
|
||||
#define BIND(source, dest) case Inputs::Keyboard::Key::source: return Commodore::Vic20::dest
|
||||
switch(key) {
|
||||
default: break;
|
||||
|
||||
BIND(k0, Key0); BIND(k1, Key1); BIND(k2, Key2); BIND(k3, Key3); BIND(k4, Key4);
|
||||
BIND(k5, Key5); BIND(k6, Key6); BIND(k7, Key7); BIND(k8, Key8); BIND(k9, Key9);
|
||||
BIND(Q, KeyQ); BIND(W, KeyW); BIND(E, KeyE); BIND(R, KeyR); BIND(T, KeyT);
|
||||
BIND(Y, KeyY); BIND(U, KeyU); BIND(I, KeyI); BIND(O, KeyO); BIND(P, KeyP);
|
||||
BIND(A, KeyA); BIND(S, KeyS); BIND(D, KeyD); BIND(F, KeyF); BIND(G, KeyG);
|
||||
BIND(H, KeyH); BIND(J, KeyJ); BIND(K, KeyK); BIND(L, KeyL);
|
||||
BIND(Z, KeyZ); BIND(X, KeyX); BIND(C, KeyC); BIND(V, KeyV);
|
||||
BIND(B, KeyB); BIND(N, KeyN); BIND(M, KeyM);
|
||||
|
||||
BIND(BackTick, KeyLeft);
|
||||
BIND(Hyphen, KeyPlus);
|
||||
BIND(Equals, KeyDash);
|
||||
BIND(F11, KeyGBP);
|
||||
BIND(F12, KeyHome);
|
||||
|
||||
BIND(Tab, KeyControl);
|
||||
BIND(OpenSquareBracket, KeyAt);
|
||||
BIND(CloseSquareBracket, KeyAsterisk);
|
||||
|
||||
BIND(BackSlash, KeyRestore);
|
||||
BIND(Hash, KeyUp);
|
||||
BIND(F10, KeyUp);
|
||||
|
||||
BIND(Semicolon, KeyColon);
|
||||
BIND(Quote, KeySemicolon);
|
||||
BIND(F9, KeyEquals);
|
||||
|
||||
BIND(LeftMeta, KeyCBM);
|
||||
BIND(LeftOption, KeyCBM);
|
||||
BIND(RightOption, KeyCBM);
|
||||
BIND(RightMeta, KeyCBM);
|
||||
|
||||
BIND(LeftShift, KeyLShift);
|
||||
BIND(RightShift, KeyRShift);
|
||||
|
||||
BIND(Comma, KeyComma);
|
||||
BIND(FullStop, KeyFullStop);
|
||||
BIND(ForwardSlash, KeySlash);
|
||||
|
||||
BIND(Right, KeyRight);
|
||||
BIND(Down, KeyDown);
|
||||
|
||||
BIND(Enter, KeyReturn);
|
||||
BIND(Space, KeySpace);
|
||||
BIND(BackSpace, KeyDelete);
|
||||
|
||||
BIND(Escape, KeyRunStop);
|
||||
BIND(F1, KeyF1);
|
||||
BIND(F3, KeyF3);
|
||||
BIND(F5, KeyF5);
|
||||
BIND(F7, KeyF7);
|
||||
}
|
||||
#undef BIND
|
||||
return KeyboardMachine::Machine::KeyNotMapped;
|
||||
}
|
||||
|
||||
uint16_t *CharacterMapper::sequence_for_character(char character) {
|
||||
#define KEYS(...) {__VA_ARGS__, EndSequence}
|
||||
#define SHIFT(...) {KeyLShift, __VA_ARGS__, EndSequence}
|
||||
#define X {NotMapped}
|
||||
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
|
||||
#define SHIFT(...) {KeyLShift, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
|
||||
#define X {KeyboardMachine::Machine::KeyNotMapped}
|
||||
static KeySequence key_sequences[] = {
|
||||
/* NUL */ X, /* SOH */ X,
|
||||
/* STX */ X, /* ETX */ X,
|
||||
52
Machines/Commodore/Vic-20/Keyboard.hpp
Normal file
52
Machines/Commodore/Vic-20/Keyboard.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// Keyboard.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/10/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Machines_Commodore_Vic20_Keyboard_hpp
|
||||
#define Machines_Commodore_Vic20_Keyboard_hpp
|
||||
|
||||
#include "../../KeyboardMachine.hpp"
|
||||
#include "../../Utility/Typer.hpp"
|
||||
|
||||
namespace Commodore {
|
||||
namespace Vic20 {
|
||||
|
||||
enum Key: uint16_t {
|
||||
#define key(line, mask) (((mask) << 3) | (line))
|
||||
Key2 = key(7, 0x01), Key4 = key(7, 0x02), Key6 = key(7, 0x04), Key8 = key(7, 0x08),
|
||||
Key0 = key(7, 0x10), KeyDash = key(7, 0x20), KeyHome = key(7, 0x40), KeyF7 = key(7, 0x80),
|
||||
KeyQ = key(6, 0x01), KeyE = key(6, 0x02), KeyT = key(6, 0x04), KeyU = key(6, 0x08),
|
||||
KeyO = key(6, 0x10), KeyAt = key(6, 0x20), KeyUp = key(6, 0x40), KeyF5 = key(6, 0x80),
|
||||
KeyCBM = key(5, 0x01), KeyS = key(5, 0x02), KeyF = key(5, 0x04), KeyH = key(5, 0x08),
|
||||
KeyK = key(5, 0x10), KeyColon = key(5, 0x20), KeyEquals = key(5, 0x40), KeyF3 = key(5, 0x80),
|
||||
KeySpace = key(4, 0x01), KeyZ = key(4, 0x02), KeyC = key(4, 0x04), KeyB = key(4, 0x08),
|
||||
KeyM = key(4, 0x10), KeyFullStop = key(4, 0x20), KeyRShift = key(4, 0x40), KeyF1 = key(4, 0x80),
|
||||
KeyRunStop = key(3, 0x01), KeyLShift = key(3, 0x02), KeyX = key(3, 0x04), KeyV = key(3, 0x08),
|
||||
KeyN = key(3, 0x10), KeyComma = key(3, 0x20), KeySlash = key(3, 0x40), KeyDown = key(3, 0x80),
|
||||
KeyControl = key(2, 0x01), KeyA = key(2, 0x02), KeyD = key(2, 0x04), KeyG = key(2, 0x08),
|
||||
KeyJ = key(2, 0x10), KeyL = key(2, 0x20), KeySemicolon = key(2, 0x40), KeyRight = key(2, 0x80),
|
||||
KeyLeft = key(1, 0x01), KeyW = key(1, 0x02), KeyR = key(1, 0x04), KeyY = key(1, 0x08),
|
||||
KeyI = key(1, 0x10), KeyP = key(1, 0x20), KeyAsterisk = key(1, 0x40), KeyReturn = key(1, 0x80),
|
||||
Key1 = key(0, 0x01), Key3 = key(0, 0x02), Key5 = key(0, 0x04), Key7 = key(0, 0x08),
|
||||
Key9 = key(0, 0x10), KeyPlus = key(0, 0x20), KeyGBP = key(0, 0x40), KeyDelete = key(0, 0x80),
|
||||
|
||||
KeyRestore = 0xfffd
|
||||
#undef key
|
||||
};
|
||||
|
||||
struct KeyboardMapper: public KeyboardMachine::Machine::KeyboardMapper {
|
||||
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key);
|
||||
};
|
||||
|
||||
struct CharacterMapper: public ::Utility::CharacterMapper {
|
||||
uint16_t *sequence_for_character(char character);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Keyboard_hpp */
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#include "Vic20.hpp"
|
||||
|
||||
#include "CharacterMapper.hpp"
|
||||
#include "Keyboard.hpp"
|
||||
|
||||
#include "../../../Processors/6502/6502.hpp"
|
||||
#include "../../../Components/6560/6560.hpp"
|
||||
@@ -29,18 +29,25 @@
|
||||
namespace Commodore {
|
||||
namespace Vic20 {
|
||||
|
||||
enum JoystickInput {
|
||||
Up = 0x04,
|
||||
Down = 0x08,
|
||||
Left = 0x10,
|
||||
Right = 0x80,
|
||||
Fire = 0x20
|
||||
};
|
||||
|
||||
/*!
|
||||
Models the user-port VIA, which is the Vic's connection point for controlling its tape recorder —
|
||||
sensing the presence or absence of a tape and controlling the tape motor — and reading the current
|
||||
state from its serial port. Most of the joystick input is also exposed here.
|
||||
*/
|
||||
class UserPortVIA: public MOS::MOS6522<UserPortVIA>, public MOS::MOS6522IRQDelegate {
|
||||
class UserPortVIA: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||
public:
|
||||
UserPortVIA() : port_a_(0xbf) {}
|
||||
using MOS6522IRQDelegate::set_interrupt_status;
|
||||
|
||||
/// Reports the current input to the 6522 port @c port.
|
||||
uint8_t get_port_input(Port port) {
|
||||
uint8_t get_port_input(MOS::MOS6522::Port port) {
|
||||
// Port A provides information about the presence or absence of a tape, and parts of
|
||||
// the joystick and serial port state, both of which have been statefully collected
|
||||
// into port_a_.
|
||||
@@ -51,9 +58,9 @@ class UserPortVIA: public MOS::MOS6522<UserPortVIA>, public MOS::MOS6522IRQDeleg
|
||||
}
|
||||
|
||||
/// Receives announcements of control line output change from the 6522.
|
||||
void set_control_line_output(Port port, Line line, bool value) {
|
||||
void set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) {
|
||||
// The CA2 output is used to control the tape motor.
|
||||
if(port == Port::A && line == Line::Two) {
|
||||
if(port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) {
|
||||
tape_->set_motor_control(!value);
|
||||
}
|
||||
}
|
||||
@@ -75,7 +82,7 @@ class UserPortVIA: public MOS::MOS6522<UserPortVIA>, public MOS::MOS6522IRQDeleg
|
||||
}
|
||||
|
||||
/// Receives announcements from the 6522 of user-port output, which might affect what's currently being presented onto the serial bus.
|
||||
void set_port_output(Port port, uint8_t value, uint8_t mask) {
|
||||
void set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t mask) {
|
||||
// Line 7 of port A is inverted and output as serial ATN.
|
||||
if(!port) {
|
||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
||||
@@ -103,14 +110,12 @@ class UserPortVIA: public MOS::MOS6522<UserPortVIA>, public MOS::MOS6522IRQDeleg
|
||||
Models the keyboard VIA, which is used by the Vic for reading its keyboard, to output to its serial port,
|
||||
and for the small portion of joystick input not connected to the user-port VIA.
|
||||
*/
|
||||
class KeyboardVIA: public MOS::MOS6522<KeyboardVIA>, public MOS::MOS6522IRQDelegate {
|
||||
class KeyboardVIA: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||
public:
|
||||
KeyboardVIA() : port_b_(0xff) {
|
||||
clear_all_keys();
|
||||
}
|
||||
|
||||
using MOS6522IRQDelegate::set_interrupt_status;
|
||||
|
||||
/// Sets whether @c key @c is_pressed.
|
||||
void set_key_state(uint16_t key, bool is_pressed) {
|
||||
if(is_pressed)
|
||||
@@ -125,7 +130,7 @@ class KeyboardVIA: public MOS::MOS6522<KeyboardVIA>, public MOS::MOS6522IRQDeleg
|
||||
}
|
||||
|
||||
/// Called by the 6522 to get input. Reads the keyboard on Port A, returns a small amount of joystick state on Port B.
|
||||
uint8_t get_port_input(Port port) {
|
||||
uint8_t get_port_input(MOS::MOS6522::Port port) {
|
||||
if(!port) {
|
||||
uint8_t result = 0xff;
|
||||
for(int c = 0; c < 8; c++) {
|
||||
@@ -139,17 +144,17 @@ class KeyboardVIA: public MOS::MOS6522<KeyboardVIA>, public MOS::MOS6522IRQDeleg
|
||||
}
|
||||
|
||||
/// Called by the 6522 to set output. The value of Port B selects which part of the keyboard to read.
|
||||
void set_port_output(Port port, uint8_t value, uint8_t mask) {
|
||||
void set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t mask) {
|
||||
if(port) activation_mask_ = (value & mask) | (~mask);
|
||||
}
|
||||
|
||||
/// Called by the 6522 to set control line output. Which affects the serial port.
|
||||
void set_control_line_output(Port port, Line line, bool value) {
|
||||
if(line == Line::Two) {
|
||||
void set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) {
|
||||
if(line == MOS::MOS6522::Line::Two) {
|
||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
||||
if(serialPort) {
|
||||
// CB2 is inverted to become serial data; CA2 is inverted to become serial clock
|
||||
if(port == Port::A)
|
||||
if(port == MOS::MOS6522::Port::A)
|
||||
serialPort->set_output(::Commodore::Serial::Line::Clock, (::Commodore::Serial::LineLevel)!value);
|
||||
else
|
||||
serialPort->set_output(::Commodore::Serial::Line::Data, (::Commodore::Serial::LineLevel)!value);
|
||||
@@ -212,9 +217,38 @@ class Vic6560: public MOS::MOS6560<Vic6560> {
|
||||
uint8_t *colour_memory; // Colour memory must be contiguous.
|
||||
};
|
||||
|
||||
/*!
|
||||
Interfaces a joystick to the two VIAs.
|
||||
*/
|
||||
class Joystick: public Inputs::Joystick {
|
||||
public:
|
||||
Joystick(UserPortVIA &user_port_via_port_handler, KeyboardVIA &keyboard_via_port_handler) :
|
||||
user_port_via_port_handler_(user_port_via_port_handler),
|
||||
keyboard_via_port_handler_(keyboard_via_port_handler) {}
|
||||
|
||||
void set_digital_input(DigitalInput digital_input, bool is_active) override {
|
||||
JoystickInput mapped_input;
|
||||
switch (digital_input) {
|
||||
default: return;
|
||||
case DigitalInput::Up: mapped_input = Up; break;
|
||||
case DigitalInput::Down: mapped_input = Down; break;
|
||||
case DigitalInput::Left: mapped_input = Left; break;
|
||||
case DigitalInput::Right: mapped_input = Right; break;
|
||||
case DigitalInput::Fire: mapped_input = Fire; break;
|
||||
}
|
||||
|
||||
user_port_via_port_handler_.set_joystick_state(mapped_input, is_active);
|
||||
keyboard_via_port_handler_.set_joystick_state(mapped_input, is_active);
|
||||
}
|
||||
|
||||
private:
|
||||
UserPortVIA &user_port_via_port_handler_;
|
||||
KeyboardVIA &keyboard_via_port_handler_;
|
||||
};
|
||||
|
||||
class ConcreteMachine:
|
||||
public CPU::MOS6502::BusHandler,
|
||||
public MOS::MOS6522IRQDelegate::Delegate,
|
||||
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
|
||||
public Utility::TypeRecipient,
|
||||
public Storage::Tape::BinaryTapePlayer::Delegate,
|
||||
public Machine {
|
||||
@@ -224,24 +258,26 @@ class ConcreteMachine:
|
||||
rom_(nullptr),
|
||||
is_running_at_zero_cost_(false),
|
||||
tape_(new Storage::Tape::BinaryTapePlayer(1022727)),
|
||||
user_port_via_(new UserPortVIA),
|
||||
keyboard_via_(new KeyboardVIA),
|
||||
user_port_via_port_handler_(new UserPortVIA),
|
||||
keyboard_via_port_handler_(new KeyboardVIA),
|
||||
serial_port_(new SerialPort),
|
||||
serial_bus_(new ::Commodore::Serial::Bus) {
|
||||
serial_bus_(new ::Commodore::Serial::Bus),
|
||||
user_port_via_(*user_port_via_port_handler_),
|
||||
keyboard_via_(*keyboard_via_port_handler_) {
|
||||
// communicate the tape to the user-port VIA
|
||||
user_port_via_->set_tape(tape_);
|
||||
user_port_via_port_handler_->set_tape(tape_);
|
||||
|
||||
// wire up the serial bus and serial port
|
||||
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus_);
|
||||
|
||||
// wire up 6522s and serial port
|
||||
user_port_via_->set_serial_port(serial_port_);
|
||||
keyboard_via_->set_serial_port(serial_port_);
|
||||
serial_port_->set_user_port_via(user_port_via_);
|
||||
user_port_via_port_handler_->set_serial_port(serial_port_);
|
||||
keyboard_via_port_handler_->set_serial_port(serial_port_);
|
||||
serial_port_->set_user_port_via(user_port_via_port_handler_);
|
||||
|
||||
// wire up the 6522s, tape and machine
|
||||
user_port_via_->set_interrupt_delegate(this);
|
||||
keyboard_via_->set_interrupt_delegate(this);
|
||||
user_port_via_port_handler_->set_interrupt_delegate(this);
|
||||
keyboard_via_port_handler_->set_interrupt_delegate(this);
|
||||
tape_->set_delegate(this);
|
||||
|
||||
// establish the memory maps
|
||||
@@ -249,6 +285,9 @@ class ConcreteMachine:
|
||||
|
||||
// set the NTSC clock rate
|
||||
set_region(NTSC);
|
||||
|
||||
// install a joystick
|
||||
joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_));
|
||||
}
|
||||
|
||||
~ConcreteMachine() {
|
||||
@@ -318,7 +357,7 @@ class ConcreteMachine:
|
||||
if(!media.cartridges.empty()) {
|
||||
rom_address_ = 0xa000;
|
||||
std::vector<uint8_t> rom_image = media.cartridges.front()->get_segments().front().data;
|
||||
rom_length_ = (uint16_t)(rom_image.size());
|
||||
rom_length_ = static_cast<uint16_t>(rom_image.size());
|
||||
|
||||
rom_ = new uint8_t[0x2000];
|
||||
memcpy(rom_, rom_image.data(), rom_image.size());
|
||||
@@ -328,17 +367,19 @@ class ConcreteMachine:
|
||||
return !media.tapes.empty() || (!media.disks.empty() && c1540_ != nullptr) || !media.cartridges.empty();
|
||||
}
|
||||
|
||||
void set_key_state(uint16_t key, bool isPressed) override final {
|
||||
keyboard_via_->set_key_state(key, isPressed);
|
||||
void set_key_state(uint16_t key, bool is_pressed) override final {
|
||||
if(key != KeyRestore)
|
||||
keyboard_via_port_handler_->set_key_state(key, is_pressed);
|
||||
else
|
||||
user_port_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !is_pressed);
|
||||
}
|
||||
|
||||
void clear_all_keys() override final {
|
||||
keyboard_via_->clear_all_keys();
|
||||
keyboard_via_port_handler_->clear_all_keys();
|
||||
}
|
||||
|
||||
void set_joystick_state(JoystickInput input, bool isPressed) override final {
|
||||
user_port_via_->set_joystick_state(input, isPressed);
|
||||
keyboard_via_->set_joystick_state(input, isPressed);
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
|
||||
return joysticks_;
|
||||
}
|
||||
|
||||
void set_memory_size(MemorySize size) override final {
|
||||
@@ -409,8 +450,8 @@ class ConcreteMachine:
|
||||
uint8_t result = processor_read_memory_map_[address >> 10] ? processor_read_memory_map_[address >> 10][address & 0x3ff] : 0xff;
|
||||
if((address&0xfc00) == 0x9000) {
|
||||
if((address&0xff00) == 0x9000) result &= mos6560_->get_register(address);
|
||||
if((address&0xfc10) == 0x9010) result &= user_port_via_->get_register(address);
|
||||
if((address&0xfc20) == 0x9020) result &= keyboard_via_->get_register(address);
|
||||
if((address&0xfc10) == 0x9010) result &= user_port_via_.get_register(address);
|
||||
if((address&0xfc20) == 0x9020) result &= keyboard_via_.get_register(address);
|
||||
}
|
||||
*value = result;
|
||||
|
||||
@@ -426,7 +467,7 @@ class ConcreteMachine:
|
||||
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape_->get_tape());
|
||||
|
||||
// serialise to wherever b2:b3 points
|
||||
uint16_t tape_buffer_pointer = (uint16_t)user_basic_memory_[0xb2] | (uint16_t)(user_basic_memory_[0xb3] << 8);
|
||||
uint16_t tape_buffer_pointer = static_cast<uint16_t>(user_basic_memory_[0xb2]) | static_cast<uint16_t>(user_basic_memory_[0xb3] << 8);
|
||||
if(header) {
|
||||
header->serialise(&user_basic_memory_[tape_buffer_pointer], 0x8000 - tape_buffer_pointer);
|
||||
} else {
|
||||
@@ -440,13 +481,13 @@ class ConcreteMachine:
|
||||
|
||||
*value = 0x0c; // i.e. NOP abs
|
||||
} else if(address == 0xf90b) {
|
||||
uint8_t x = (uint8_t)m6502_.get_value_of_register(CPU::MOS6502::Register::X);
|
||||
uint8_t x = static_cast<uint8_t>(m6502_.get_value_of_register(CPU::MOS6502::Register::X));
|
||||
if(x == 0xe) {
|
||||
Storage::Tape::Commodore::Parser parser;
|
||||
std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape_->get_tape());
|
||||
uint16_t start_address, end_address;
|
||||
start_address = (uint16_t)(user_basic_memory_[0xc1] | (user_basic_memory_[0xc2] << 8));
|
||||
end_address = (uint16_t)(user_basic_memory_[0xae] | (user_basic_memory_[0xaf] << 8));
|
||||
start_address = static_cast<uint16_t>(user_basic_memory_[0xc1] | (user_basic_memory_[0xc2] << 8));
|
||||
end_address = static_cast<uint16_t>(user_basic_memory_[0xae] | (user_basic_memory_[0xaf] << 8));
|
||||
|
||||
// perform a via-processor_write_memory_map_ memcpy
|
||||
uint8_t *data_ptr = data->data.data();
|
||||
@@ -461,8 +502,8 @@ class ConcreteMachine:
|
||||
|
||||
// set tape status, carry and flag
|
||||
user_basic_memory_[0x90] |= 0x40;
|
||||
uint8_t flags = (uint8_t)m6502_.get_value_of_register(CPU::MOS6502::Register::Flags);
|
||||
flags &= ~(uint8_t)(CPU::MOS6502::Flag::Carry | CPU::MOS6502::Flag::Interrupt);
|
||||
uint8_t flags = static_cast<uint8_t>(m6502_.get_value_of_register(CPU::MOS6502::Register::Flags));
|
||||
flags &= ~static_cast<uint8_t>((CPU::MOS6502::Flag::Carry | CPU::MOS6502::Flag::Interrupt));
|
||||
m6502_.set_value_of_register(CPU::MOS6502::Register::Flags, flags);
|
||||
|
||||
// to ensure that execution proceeds to 0xfccf, pretend a NOP was here and
|
||||
@@ -477,13 +518,13 @@ class ConcreteMachine:
|
||||
if(ram) ram[address & 0x3ff] = *value;
|
||||
if((address&0xfc00) == 0x9000) {
|
||||
if((address&0xff00) == 0x9000) mos6560_->set_register(address, *value);
|
||||
if((address&0xfc10) == 0x9010) user_port_via_->set_register(address, *value);
|
||||
if((address&0xfc20) == 0x9020) keyboard_via_->set_register(address, *value);
|
||||
if((address&0xfc10) == 0x9010) user_port_via_.set_register(address, *value);
|
||||
if((address&0xfc20) == 0x9020) keyboard_via_.set_register(address, *value);
|
||||
}
|
||||
}
|
||||
|
||||
user_port_via_->run_for(Cycles(1));
|
||||
keyboard_via_->run_for(Cycles(1));
|
||||
user_port_via_.run_for(Cycles(1));
|
||||
keyboard_via_.run_for(Cycles(1));
|
||||
if(typer_ && operation == CPU::MOS6502::BusOperation::ReadOpcode && address == 0xEB1E) {
|
||||
if(!typer_->type_next_character()) {
|
||||
clear_all_keys();
|
||||
@@ -529,8 +570,8 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
void mos6522_did_change_interrupt_status(void *mos6522) override final {
|
||||
m6502_.set_nmi_line(user_port_via_->get_interrupt_line());
|
||||
m6502_.set_irq_line(keyboard_via_->get_interrupt_line());
|
||||
m6502_.set_nmi_line(user_port_via_.get_interrupt_line());
|
||||
m6502_.set_irq_line(keyboard_via_.get_interrupt_line());
|
||||
}
|
||||
|
||||
void set_typer_for_string(const char *string) override final {
|
||||
@@ -539,7 +580,11 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) override final {
|
||||
keyboard_via_->set_control_line_input(KeyboardVIA::Port::A, KeyboardVIA::Line::One, !tape->get_input());
|
||||
keyboard_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !tape->get_input());
|
||||
}
|
||||
|
||||
KeyboardMapper &get_keyboard_mapper() override {
|
||||
return keyboard_mapper_;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -571,13 +616,18 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
Region region_;
|
||||
Commodore::Vic20::KeyboardMapper keyboard_mapper_;
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
|
||||
std::unique_ptr<Vic6560> mos6560_;
|
||||
std::shared_ptr<UserPortVIA> user_port_via_;
|
||||
std::shared_ptr<KeyboardVIA> keyboard_via_;
|
||||
std::shared_ptr<UserPortVIA> user_port_via_port_handler_;
|
||||
std::shared_ptr<KeyboardVIA> keyboard_via_port_handler_;
|
||||
std::shared_ptr<SerialPort> serial_port_;
|
||||
std::shared_ptr<::Commodore::Serial::Bus> serial_bus_;
|
||||
|
||||
MOS::MOS6522::MOS6522<UserPortVIA> user_port_via_;
|
||||
MOS::MOS6522::MOS6522<KeyboardVIA> keyboard_via_;
|
||||
|
||||
// Tape
|
||||
std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_;
|
||||
bool use_fast_tape_hack_;
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
|
||||
#include "../../ConfigurationTarget.hpp"
|
||||
#include "../../CRTMachine.hpp"
|
||||
#include "../../Typer.hpp"
|
||||
#include "../../KeyboardMachine.hpp"
|
||||
#include "../../JoystickMachine.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
@@ -36,39 +37,11 @@ enum Region {
|
||||
PAL
|
||||
};
|
||||
|
||||
enum Key: uint16_t {
|
||||
#define key(line, mask) (((mask) << 3) | (line))
|
||||
Key2 = key(7, 0x01), Key4 = key(7, 0x02), Key6 = key(7, 0x04), Key8 = key(7, 0x08),
|
||||
Key0 = key(7, 0x10), KeyDash = key(7, 0x20), KeyHome = key(7, 0x40), KeyF7 = key(7, 0x80),
|
||||
KeyQ = key(6, 0x01), KeyE = key(6, 0x02), KeyT = key(6, 0x04), KeyU = key(6, 0x08),
|
||||
KeyO = key(6, 0x10), KeyAt = key(6, 0x20), KeyUp = key(6, 0x40), KeyF5 = key(6, 0x80),
|
||||
KeyCBM = key(5, 0x01), KeyS = key(5, 0x02), KeyF = key(5, 0x04), KeyH = key(5, 0x08),
|
||||
KeyK = key(5, 0x10), KeyColon = key(5, 0x20), KeyEquals = key(5, 0x40), KeyF3 = key(5, 0x80),
|
||||
KeySpace = key(4, 0x01), KeyZ = key(4, 0x02), KeyC = key(4, 0x04), KeyB = key(4, 0x08),
|
||||
KeyM = key(4, 0x10), KeyFullStop = key(4, 0x20), KeyRShift = key(4, 0x40), KeyF1 = key(4, 0x80),
|
||||
KeyRunStop = key(3, 0x01), KeyLShift = key(3, 0x02), KeyX = key(3, 0x04), KeyV = key(3, 0x08),
|
||||
KeyN = key(3, 0x10), KeyComma = key(3, 0x20), KeySlash = key(3, 0x40), KeyDown = key(3, 0x80),
|
||||
KeyControl = key(2, 0x01), KeyA = key(2, 0x02), KeyD = key(2, 0x04), KeyG = key(2, 0x08),
|
||||
KeyJ = key(2, 0x10), KeyL = key(2, 0x20), KeySemicolon = key(2, 0x40), KeyRight = key(2, 0x80),
|
||||
KeyLeft = key(1, 0x01), KeyW = key(1, 0x02), KeyR = key(1, 0x04), KeyY = key(1, 0x08),
|
||||
KeyI = key(1, 0x10), KeyP = key(1, 0x20), KeyAsterisk = key(1, 0x40), KeyReturn = key(1, 0x80),
|
||||
Key1 = key(0, 0x01), Key3 = key(0, 0x02), Key5 = key(0, 0x04), Key7 = key(0, 0x08),
|
||||
Key9 = key(0, 0x10), KeyPlus = key(0, 0x20), KeyGBP = key(0, 0x40), KeyDelete = key(0, 0x80),
|
||||
#undef key
|
||||
};
|
||||
|
||||
enum JoystickInput {
|
||||
Up = 0x04,
|
||||
Down = 0x08,
|
||||
Left = 0x10,
|
||||
Right = 0x80,
|
||||
Fire = 0x20
|
||||
};
|
||||
|
||||
class Machine:
|
||||
public ConfigurationTarget::Machine,
|
||||
public CRTMachine::Machine,
|
||||
public KeyboardMachine::Machine {
|
||||
public ConfigurationTarget::Machine,
|
||||
public KeyboardMachine::Machine,
|
||||
public JoystickMachine::Machine {
|
||||
public:
|
||||
virtual ~Machine();
|
||||
|
||||
@@ -79,9 +52,6 @@ class Machine:
|
||||
virtual void set_rom(ROMSlot slot, size_t length, const uint8_t *data) = 0;
|
||||
// TODO: take a std::vector<uint8_t> to collapse length and data.
|
||||
|
||||
/// Sets the state of joystick input @c input.
|
||||
virtual void set_joystick_state(JoystickInput input, bool isPressed) = 0;
|
||||
|
||||
/// Sets the memory size of this Vic-20.
|
||||
virtual void set_memory_size(MemorySize size) = 0;
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
//
|
||||
// CharacterMapper.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Machines_Electron_CharacterMapper_hpp
|
||||
#define Machines_Electron_CharacterMapper_hpp
|
||||
|
||||
#include "../Typer.hpp"
|
||||
|
||||
namespace Electron {
|
||||
|
||||
class CharacterMapper: public ::Utility::CharacterMapper {
|
||||
public:
|
||||
uint16_t *sequence_for_character(char character);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Machines_Electron_CharacterMapper_hpp */
|
||||
@@ -13,10 +13,10 @@
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
|
||||
#include "../Typer.hpp"
|
||||
#include "../Utility/Typer.hpp"
|
||||
|
||||
#include "CharacterMapper.hpp"
|
||||
#include "Interrupts.hpp"
|
||||
#include "Keyboard.hpp"
|
||||
#include "Plus3.hpp"
|
||||
#include "Speaker.hpp"
|
||||
#include "Tape.hpp"
|
||||
@@ -30,13 +30,7 @@ class ConcreteMachine:
|
||||
public Tape::Delegate,
|
||||
public Utility::TypeRecipient {
|
||||
public:
|
||||
ConcreteMachine() :
|
||||
m6502_(*this),
|
||||
interrupt_control_(0),
|
||||
interrupt_status_(Interrupt::PowerOnReset | Interrupt::TransmitDataEmpty | 0x80),
|
||||
cycles_since_audio_update_(0),
|
||||
use_fast_tape_hack_(false),
|
||||
cycles_until_display_interrupt_(0) {
|
||||
ConcreteMachine() : m6502_(*this) {
|
||||
memset(key_states_, 0, sizeof(key_states_));
|
||||
for(int c = 0; c < 16; c++)
|
||||
memset(roms_[c], 0xff, 16384);
|
||||
@@ -58,7 +52,7 @@ class ConcreteMachine:
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(target, &data[0], std::min((size_t)16384, data.size()));
|
||||
memcpy(target, &data[0], std::min(static_cast<size_t>(16384), data.size()));
|
||||
}
|
||||
|
||||
void set_key_state(uint16_t key, bool isPressed) override final {
|
||||
@@ -117,7 +111,7 @@ class ConcreteMachine:
|
||||
ROMSlot slot = ROMSlot12;
|
||||
for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : media.cartridges) {
|
||||
set_rom(slot, cartridge->get_segments().front().data, false);
|
||||
slot = (ROMSlot)(((int)slot + 1)&15);
|
||||
slot = static_cast<ROMSlot>((static_cast<int>(slot) + 1)&15);
|
||||
}
|
||||
|
||||
return !media.tapes.empty() || !media.disks.empty() || !media.cartridges.empty();
|
||||
@@ -263,7 +257,7 @@ class ConcreteMachine:
|
||||
// allow the PC read to return an RTS.
|
||||
)
|
||||
) {
|
||||
uint8_t service_call = (uint8_t)m6502_.get_value_of_register(CPU::MOS6502::Register::X);
|
||||
uint8_t service_call = static_cast<uint8_t>(m6502_.get_value_of_register(CPU::MOS6502::Register::X));
|
||||
if(address == 0xf0a8) {
|
||||
if(!ram_[0x247] && service_call == 14) {
|
||||
tape_.set_delegate(nullptr);
|
||||
@@ -316,10 +310,10 @@ class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
|
||||
cycles_since_display_update_ += Cycles((int)cycles);
|
||||
cycles_since_audio_update_ += Cycles((int)cycles);
|
||||
cycles_since_display_update_ += Cycles(static_cast<int>(cycles));
|
||||
cycles_since_audio_update_ += Cycles(static_cast<int>(cycles));
|
||||
if(cycles_since_audio_update_ > Cycles(16384)) update_audio();
|
||||
tape_.run_for(Cycles((int)cycles));
|
||||
tape_.run_for(Cycles(static_cast<int>(cycles)));
|
||||
|
||||
cycles_until_display_interrupt_ -= cycles;
|
||||
if(cycles_until_display_interrupt_ < 0) {
|
||||
@@ -328,8 +322,8 @@ class ConcreteMachine:
|
||||
queue_next_display_interrupt();
|
||||
}
|
||||
|
||||
if(typer_) typer_->run_for(Cycles((int)cycles));
|
||||
if(plus3_) plus3_->run_for(Cycles(4*(int)cycles));
|
||||
if(typer_) typer_->run_for(Cycles(static_cast<int>(cycles)));
|
||||
if(plus3_) plus3_->run_for(Cycles(4*static_cast<int>(cycles)));
|
||||
if(shift_restart_counter_) {
|
||||
shift_restart_counter_ -= cycles;
|
||||
if(shift_restart_counter_ <= 0) {
|
||||
@@ -340,7 +334,7 @@ class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
|
||||
return Cycles((int)cycles);
|
||||
return Cycles(static_cast<int>(cycles));
|
||||
}
|
||||
|
||||
forceinline void flush() {
|
||||
@@ -393,6 +387,10 @@ class ConcreteMachine:
|
||||
Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper));
|
||||
}
|
||||
|
||||
KeyboardMapper &get_keyboard_mapper() override {
|
||||
return keyboard_mapper_;
|
||||
}
|
||||
|
||||
private:
|
||||
inline void update_display() {
|
||||
if(cycles_since_display_update_ > 0) {
|
||||
@@ -435,39 +433,42 @@ class ConcreteMachine:
|
||||
|
||||
// Things that directly constitute the memory map.
|
||||
uint8_t roms_[16][16384];
|
||||
bool rom_write_masks_[16];
|
||||
bool rom_write_masks_[16] = {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false};
|
||||
uint8_t os_[16384], ram_[32768];
|
||||
std::vector<uint8_t> dfs_, adfs_;
|
||||
|
||||
// Paging
|
||||
ROMSlot active_rom_;
|
||||
bool keyboard_is_active_, basic_is_active_;
|
||||
ROMSlot active_rom_ = ROMSlot::ROMSlot0;
|
||||
bool keyboard_is_active_ = false;
|
||||
bool basic_is_active_ = false;
|
||||
|
||||
// Interrupt and keyboard state
|
||||
uint8_t interrupt_status_, interrupt_control_;
|
||||
uint8_t interrupt_status_ = Interrupt::PowerOnReset | Interrupt::TransmitDataEmpty | 0x80;
|
||||
uint8_t interrupt_control_ = 0;
|
||||
uint8_t key_states_[14];
|
||||
Electron::KeyboardMapper keyboard_mapper_;
|
||||
|
||||
// Counters related to simultaneous subsystems
|
||||
Cycles cycles_since_display_update_;
|
||||
Cycles cycles_since_audio_update_;
|
||||
int cycles_until_display_interrupt_;
|
||||
Interrupt next_display_interrupt_;
|
||||
VideoOutput::Range video_access_range_;
|
||||
Cycles cycles_since_display_update_ = 0;
|
||||
Cycles cycles_since_audio_update_ = 0;
|
||||
int cycles_until_display_interrupt_ = 0;
|
||||
Interrupt next_display_interrupt_ = Interrupt::RealTimeClock;
|
||||
VideoOutput::Range video_access_range_ = {0, 0xffff};
|
||||
|
||||
// Tape
|
||||
Tape tape_;
|
||||
bool use_fast_tape_hack_;
|
||||
bool fast_load_is_in_data_;
|
||||
bool use_fast_tape_hack_ = false;
|
||||
bool fast_load_is_in_data_ = false;
|
||||
|
||||
// Disk
|
||||
std::unique_ptr<Plus3> plus3_;
|
||||
bool is_holding_shift_;
|
||||
int shift_restart_counter_;
|
||||
bool is_holding_shift_ = false;
|
||||
int shift_restart_counter_ = 0;
|
||||
|
||||
// Outputs
|
||||
std::unique_ptr<VideoOutput> video_output_;
|
||||
std::shared_ptr<Speaker> speaker_;
|
||||
bool speaker_is_enabled_;
|
||||
bool speaker_is_enabled_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -31,25 +31,6 @@ enum ROMSlot: uint8_t {
|
||||
ROMSlotOS, ROMSlotDFS, ROMSlotADFS
|
||||
};
|
||||
|
||||
enum Key: uint16_t {
|
||||
KeySpace = 0x0000 | 0x08, KeyCopy = 0x0000 | 0x02, KeyRight = 0x0000 | 0x01,
|
||||
KeyDelete = 0x0010 | 0x08, KeyReturn = 0x0010 | 0x04, KeyDown = 0x0010 | 0x02, KeyLeft = 0x0010 | 0x01,
|
||||
KeyColon = 0x0020 | 0x04, KeyUp = 0x0020 | 0x02, KeyMinus = 0x0020 | 0x01,
|
||||
KeySlash = 0x0030 | 0x08, KeySemiColon = 0x0030 | 0x04, KeyP = 0x0030 | 0x02, Key0 = 0x0030 | 0x01,
|
||||
KeyFullStop = 0x0040 | 0x08, KeyL = 0x0040 | 0x04, KeyO = 0x0040 | 0x02, Key9 = 0x0040 | 0x01,
|
||||
KeyComma = 0x0050 | 0x08, KeyK = 0x0050 | 0x04, KeyI = 0x0050 | 0x02, Key8 = 0x0050 | 0x01,
|
||||
KeyM = 0x0060 | 0x08, KeyJ = 0x0060 | 0x04, KeyU = 0x0060 | 0x02, Key7 = 0x0060 | 0x01,
|
||||
KeyN = 0x0070 | 0x08, KeyH = 0x0070 | 0x04, KeyY = 0x0070 | 0x02, Key6 = 0x0070 | 0x01,
|
||||
KeyB = 0x0080 | 0x08, KeyG = 0x0080 | 0x04, KeyT = 0x0080 | 0x02, Key5 = 0x0080 | 0x01,
|
||||
KeyV = 0x0090 | 0x08, KeyF = 0x0090 | 0x04, KeyR = 0x0090 | 0x02, Key4 = 0x0090 | 0x01,
|
||||
KeyC = 0x00a0 | 0x08, KeyD = 0x00a0 | 0x04, KeyE = 0x00a0 | 0x02, Key3 = 0x00a0 | 0x01,
|
||||
KeyX = 0x00b0 | 0x08, KeyS = 0x00b0 | 0x04, KeyW = 0x00b0 | 0x02, Key2 = 0x00b0 | 0x01,
|
||||
KeyZ = 0x00c0 | 0x08, KeyA = 0x00c0 | 0x04, KeyQ = 0x00c0 | 0x02, Key1 = 0x00c0 | 0x01,
|
||||
KeyShift = 0x00d0 | 0x08, KeyControl = 0x00d0 | 0x04, KeyFunc = 0x00d0 | 0x02, KeyEscape = 0x00d0 | 0x01,
|
||||
|
||||
KeyBreak = 0xfffd,
|
||||
};
|
||||
|
||||
/*!
|
||||
@abstract Represents an Acorn Electron.
|
||||
|
||||
|
||||
@@ -1,21 +1,65 @@
|
||||
//
|
||||
// CharacterMapper.cpp
|
||||
// Keyboard.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/08/2017.
|
||||
// Created by Thomas Harte on 10/10/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "CharacterMapper.hpp"
|
||||
#include "Electron.hpp"
|
||||
#include "Keyboard.hpp"
|
||||
|
||||
using namespace Electron;
|
||||
|
||||
uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
|
||||
#define BIND(source, dest) case Inputs::Keyboard::Key::source: return Electron::Key::dest
|
||||
switch(key) {
|
||||
default: return KeyCopy;
|
||||
|
||||
BIND(k0, Key0); BIND(k1, Key1); BIND(k2, Key2); BIND(k3, Key3); BIND(k4, Key4);
|
||||
BIND(k5, Key5); BIND(k6, Key6); BIND(k7, Key7); BIND(k8, Key8); BIND(k9, Key9);
|
||||
BIND(Q, KeyQ); BIND(W, KeyW); BIND(E, KeyE); BIND(R, KeyR); BIND(T, KeyT);
|
||||
BIND(Y, KeyY); BIND(U, KeyU); BIND(I, KeyI); BIND(O, KeyO); BIND(P, KeyP);
|
||||
BIND(A, KeyA); BIND(S, KeyS); BIND(D, KeyD); BIND(F, KeyF); BIND(G, KeyG);
|
||||
BIND(H, KeyH); BIND(J, KeyJ); BIND(K, KeyK); BIND(L, KeyL);
|
||||
BIND(Z, KeyZ); BIND(X, KeyX); BIND(C, KeyC); BIND(V, KeyV);
|
||||
BIND(B, KeyB); BIND(N, KeyN); BIND(M, KeyM);
|
||||
|
||||
BIND(Comma, KeyComma);
|
||||
BIND(FullStop, KeyFullStop);
|
||||
BIND(ForwardSlash, KeySlash);
|
||||
BIND(Semicolon, KeySemiColon);
|
||||
BIND(Quote, KeyColon);
|
||||
|
||||
BIND(Escape, KeyEscape);
|
||||
BIND(Equals, KeyBreak);
|
||||
BIND(F12, KeyBreak);
|
||||
|
||||
BIND(Left, KeyLeft); BIND(Right, KeyRight); BIND(Up, KeyUp); BIND(Down, KeyDown);
|
||||
|
||||
BIND(Tab, KeyFunc); BIND(LeftOption, KeyFunc); BIND(RightOption, KeyFunc);
|
||||
BIND(LeftMeta, KeyFunc); BIND(RightMeta, KeyFunc);
|
||||
BIND(CapsLock, KeyControl); BIND(LeftControl, KeyControl); BIND(RightControl, KeyControl);
|
||||
BIND(LeftShift, KeyShift); BIND(RightShift, KeyShift);
|
||||
|
||||
BIND(Hyphen, KeyMinus);
|
||||
BIND(Delete, KeyDelete);
|
||||
BIND(Enter, KeyReturn); BIND(KeyPadEnter, KeyReturn);
|
||||
|
||||
BIND(KeyPad0, Key0); BIND(KeyPad1, Key1); BIND(KeyPad2, Key2); BIND(KeyPad3, Key3); BIND(KeyPad4, Key4);
|
||||
BIND(KeyPad5, Key5); BIND(KeyPad6, Key6); BIND(KeyPad7, Key7); BIND(KeyPad8, Key8); BIND(KeyPad9, Key9);
|
||||
|
||||
BIND(KeyPadMinus, KeyMinus); BIND(KeyPadPlus, KeyColon);
|
||||
|
||||
BIND(Space, KeySpace);
|
||||
}
|
||||
#undef BIND
|
||||
}
|
||||
|
||||
uint16_t *CharacterMapper::sequence_for_character(char character) {
|
||||
#define KEYS(...) {__VA_ARGS__, EndSequence}
|
||||
#define SHIFT(...) {KeyShift, __VA_ARGS__, EndSequence}
|
||||
#define CTRL(...) {KeyControl, __VA_ARGS__, EndSequence}
|
||||
#define X {NotMapped}
|
||||
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
|
||||
#define SHIFT(...) {KeyShift, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
|
||||
#define CTRL(...) {KeyControl, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
|
||||
#define X {KeyboardMachine::Machine::KeyNotMapped}
|
||||
static KeySequence key_sequences[] = {
|
||||
/* NUL */ X, /* SOH */ X,
|
||||
/* STX */ X, /* ETX */ X,
|
||||
46
Machines/Electron/Keyboard.hpp
Normal file
46
Machines/Electron/Keyboard.hpp
Normal file
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// Keyboard.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/10/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Machines_Electron_Keyboard_hpp
|
||||
#define Machines_Electron_Keyboard_hpp
|
||||
|
||||
#include "../KeyboardMachine.hpp"
|
||||
#include "../Utility/Typer.hpp"
|
||||
|
||||
namespace Electron {
|
||||
|
||||
enum Key: uint16_t {
|
||||
KeySpace = 0x0000 | 0x08, KeyCopy = 0x0000 | 0x02, KeyRight = 0x0000 | 0x01,
|
||||
KeyDelete = 0x0010 | 0x08, KeyReturn = 0x0010 | 0x04, KeyDown = 0x0010 | 0x02, KeyLeft = 0x0010 | 0x01,
|
||||
KeyColon = 0x0020 | 0x04, KeyUp = 0x0020 | 0x02, KeyMinus = 0x0020 | 0x01,
|
||||
KeySlash = 0x0030 | 0x08, KeySemiColon = 0x0030 | 0x04, KeyP = 0x0030 | 0x02, Key0 = 0x0030 | 0x01,
|
||||
KeyFullStop = 0x0040 | 0x08, KeyL = 0x0040 | 0x04, KeyO = 0x0040 | 0x02, Key9 = 0x0040 | 0x01,
|
||||
KeyComma = 0x0050 | 0x08, KeyK = 0x0050 | 0x04, KeyI = 0x0050 | 0x02, Key8 = 0x0050 | 0x01,
|
||||
KeyM = 0x0060 | 0x08, KeyJ = 0x0060 | 0x04, KeyU = 0x0060 | 0x02, Key7 = 0x0060 | 0x01,
|
||||
KeyN = 0x0070 | 0x08, KeyH = 0x0070 | 0x04, KeyY = 0x0070 | 0x02, Key6 = 0x0070 | 0x01,
|
||||
KeyB = 0x0080 | 0x08, KeyG = 0x0080 | 0x04, KeyT = 0x0080 | 0x02, Key5 = 0x0080 | 0x01,
|
||||
KeyV = 0x0090 | 0x08, KeyF = 0x0090 | 0x04, KeyR = 0x0090 | 0x02, Key4 = 0x0090 | 0x01,
|
||||
KeyC = 0x00a0 | 0x08, KeyD = 0x00a0 | 0x04, KeyE = 0x00a0 | 0x02, Key3 = 0x00a0 | 0x01,
|
||||
KeyX = 0x00b0 | 0x08, KeyS = 0x00b0 | 0x04, KeyW = 0x00b0 | 0x02, Key2 = 0x00b0 | 0x01,
|
||||
KeyZ = 0x00c0 | 0x08, KeyA = 0x00c0 | 0x04, KeyQ = 0x00c0 | 0x02, Key1 = 0x00c0 | 0x01,
|
||||
KeyShift = 0x00d0 | 0x08, KeyControl = 0x00d0 | 0x04, KeyFunc = 0x00d0 | 0x02, KeyEscape = 0x00d0 | 0x01,
|
||||
|
||||
KeyBreak = 0xfffd,
|
||||
};
|
||||
|
||||
struct KeyboardMapper: public KeyboardMachine::Machine::KeyboardMapper {
|
||||
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key);
|
||||
};
|
||||
|
||||
struct CharacterMapper: public ::Utility::CharacterMapper {
|
||||
uint16_t *sequence_for_character(char character);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif /* KeyboardMapper_hpp */
|
||||
@@ -10,13 +10,13 @@
|
||||
|
||||
using namespace Electron;
|
||||
|
||||
Plus3::Plus3() : WD1770(P1770), last_control_(0) {
|
||||
Plus3::Plus3() : WD1770(P1770) {
|
||||
set_control_register(last_control_, 0xff);
|
||||
}
|
||||
|
||||
void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||
if(!drives_[drive]) {
|
||||
drives_[drive].reset(new Storage::Disk::Drive);
|
||||
drives_[drive].reset(new Storage::Disk::Drive(8000000, 300, 2));
|
||||
if(drive == selected_drive_) set_drive(drives_[drive]);
|
||||
}
|
||||
drives_[drive]->set_disk(disk);
|
||||
@@ -42,9 +42,14 @@ void Plus3::set_control_register(uint8_t control, uint8_t changes) {
|
||||
}
|
||||
}
|
||||
if(changes & 0x04) {
|
||||
invalidate_track();
|
||||
if(drives_[0]) drives_[0]->set_head((control & 0x04) ? 1 : 0);
|
||||
if(drives_[1]) drives_[1]->set_head((control & 0x04) ? 1 : 0);
|
||||
}
|
||||
if(changes & 0x08) set_is_double_density(!(control & 0x08));
|
||||
}
|
||||
|
||||
void Plus3::set_motor_on(bool on) {
|
||||
// TODO: this status should transfer if the selected drive changes. But the same goes for
|
||||
// writing state, so plenty of work to do in general here.
|
||||
get_drive().set_motor_on(on);
|
||||
}
|
||||
|
||||
@@ -23,8 +23,10 @@ class Plus3 : public WD::WD1770 {
|
||||
private:
|
||||
void set_control_register(uint8_t control, uint8_t changes);
|
||||
std::shared_ptr<Storage::Disk::Drive> drives_[2];
|
||||
int selected_drive_;
|
||||
uint8_t last_control_;
|
||||
int selected_drive_ = 0;
|
||||
uint8_t last_control_ = 0;
|
||||
|
||||
void set_motor_on(bool on);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ using namespace Electron;
|
||||
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
|
||||
if(is_enabled_) {
|
||||
while(number_of_samples--) {
|
||||
*target = (int16_t)((counter_ / (divider_+1)) * 8192);
|
||||
*target = static_cast<int16_t>((counter_ / (divider_+1)) * 8192);
|
||||
target++;
|
||||
counter_ = (counter_ + 1) % ((divider_+1) * 2);
|
||||
}
|
||||
|
||||
@@ -25,9 +25,9 @@ class Speaker: public ::Outputs::Filter<Speaker> {
|
||||
static const unsigned int clock_rate_divider = 8;
|
||||
|
||||
private:
|
||||
unsigned int counter_;
|
||||
unsigned int divider_;
|
||||
bool is_enabled_;
|
||||
unsigned int counter_ = 0;
|
||||
unsigned int divider_ = 0;
|
||||
bool is_enabled_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -10,19 +10,12 @@
|
||||
|
||||
using namespace Electron;
|
||||
|
||||
Tape::Tape() :
|
||||
TapePlayer(2000000),
|
||||
is_running_(false),
|
||||
data_register_(0),
|
||||
delegate_(nullptr),
|
||||
output_({.bits_remaining_until_empty = 0, .cycles_into_pulse = 0}),
|
||||
last_posted_interrupt_status_(0),
|
||||
interrupt_status_(0) {
|
||||
Tape::Tape() : TapePlayer(2000000) {
|
||||
shifter_.set_delegate(this);
|
||||
}
|
||||
|
||||
void Tape::push_tape_bit(uint16_t bit) {
|
||||
data_register_ = (uint16_t)((data_register_ >> 1) | (bit << 10));
|
||||
data_register_ = static_cast<uint16_t>((data_register_ >> 1) | (bit << 10));
|
||||
|
||||
if(input_.minimum_bits_until_full) input_.minimum_bits_until_full--;
|
||||
if(input_.minimum_bits_until_full == 8) interrupt_status_ &= ~Interrupt::ReceiveDataFull;
|
||||
@@ -64,12 +57,12 @@ void Tape::set_counter(uint8_t value) {
|
||||
}
|
||||
|
||||
void Tape::set_data_register(uint8_t value) {
|
||||
data_register_ = (uint16_t)((value << 2) | 1);
|
||||
data_register_ = static_cast<uint16_t>((value << 2) | 1);
|
||||
output_.bits_remaining_until_empty = 9;
|
||||
}
|
||||
|
||||
uint8_t Tape::get_data_register() {
|
||||
return (uint8_t)(data_register_ >> 2);
|
||||
return static_cast<uint8_t>(data_register_ >> 2);
|
||||
}
|
||||
|
||||
void Tape::process_input_pulse(const Storage::Tape::Tape::Pulse &pulse) {
|
||||
@@ -77,7 +70,7 @@ void Tape::process_input_pulse(const Storage::Tape::Tape::Pulse &pulse) {
|
||||
}
|
||||
|
||||
void Tape::acorn_shifter_output_bit(int value) {
|
||||
push_tape_bit((uint16_t)value);
|
||||
push_tape_bit(static_cast<uint16_t>(value));
|
||||
}
|
||||
|
||||
void Tape::run_for(const Cycles cycles) {
|
||||
@@ -87,7 +80,7 @@ void Tape::run_for(const Cycles cycles) {
|
||||
TapePlayer::run_for(cycles);
|
||||
}
|
||||
} else {
|
||||
output_.cycles_into_pulse += (unsigned int)cycles.as_int();
|
||||
output_.cycles_into_pulse += static_cast<unsigned int>(cycles.as_int());
|
||||
while(output_.cycles_into_pulse > 1664) { // 1664 = the closest you can get to 1200 baud if you're looking for something
|
||||
output_.cycles_into_pulse -= 1664; // that divides the 125,000Hz clock that the sound divider runs off.
|
||||
push_tape_bit(1);
|
||||
|
||||
@@ -53,21 +53,22 @@ class Tape:
|
||||
|
||||
struct {
|
||||
int minimum_bits_until_full;
|
||||
} input_;
|
||||
} input_ = {0};
|
||||
struct {
|
||||
unsigned int cycles_into_pulse;
|
||||
unsigned int bits_remaining_until_empty;
|
||||
} output_;
|
||||
} output_ = {.bits_remaining_until_empty = 0, .cycles_into_pulse = 0};
|
||||
|
||||
bool is_running_;
|
||||
bool is_enabled_;
|
||||
bool is_in_input_mode_;
|
||||
bool is_running_ = false;
|
||||
bool is_enabled_ = false;
|
||||
bool is_in_input_mode_ = false;
|
||||
|
||||
inline void evaluate_interrupts();
|
||||
uint16_t data_register_;
|
||||
uint16_t data_register_ = 0;
|
||||
|
||||
uint8_t interrupt_status_, last_posted_interrupt_status_;
|
||||
Delegate *delegate_;
|
||||
uint8_t interrupt_status_ = 0;
|
||||
uint8_t last_posted_interrupt_status_ = 0;
|
||||
Delegate *delegate_ = nullptr;
|
||||
|
||||
::Storage::Tape::Acorn::Shifter shifter_;
|
||||
};
|
||||
|
||||
@@ -32,17 +32,18 @@ namespace {
|
||||
static const int real_time_clock_interrupt_2 = 56704;
|
||||
static const int display_end_interrupt_1 = (first_graphics_line + display_end_interrupt_line)*cycles_per_line;
|
||||
static const int display_end_interrupt_2 = (first_graphics_line + field_divider_line + display_end_interrupt_line)*cycles_per_line;
|
||||
|
||||
struct FourBPPBookender: public Outputs::CRT::TextureBuilder::Bookender {
|
||||
void add_bookends(uint8_t *const left_value, uint8_t *const right_value, uint8_t *left_bookend, uint8_t *right_bookend) {
|
||||
*left_bookend = static_cast<uint8_t>(((*left_value) & 0x0f) | (((*left_value) & 0x0f) << 4));
|
||||
*right_bookend = static_cast<uint8_t>(((*right_value) & 0xf0) | (((*right_value) & 0xf0) >> 4));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
VideoOutput::VideoOutput(uint8_t *memory) :
|
||||
ram_(memory),
|
||||
current_pixel_line_(-1),
|
||||
output_position_(0),
|
||||
screen_mode_(6),
|
||||
screen_map_pointer_(0),
|
||||
cycles_into_draw_action_(0) {
|
||||
VideoOutput::VideoOutput(uint8_t *memory) : ram_(memory) {
|
||||
memset(palette_, 0xf, sizeof(palette_));
|
||||
setup_screen_map();
|
||||
setup_base_address();
|
||||
@@ -55,6 +56,8 @@ VideoOutput::VideoOutput(uint8_t *memory) :
|
||||
"texValue >>= 4 - (int(icoordinate.x * 8) & 4);"
|
||||
"return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));"
|
||||
"}");
|
||||
std::unique_ptr<Outputs::CRT::TextureBuilder::Bookender> bookender(new FourBPPBookender);
|
||||
crt_->set_bookender(std::move(bookender));
|
||||
// TODO: as implied below, I've introduced a clock's latency into the graphics pipeline somehow. Investigate.
|
||||
crt_->set_visible_area(crt_->get_rect_for_area(first_graphics_line - 3, 256, (first_graphics_cycle+1) * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f));
|
||||
}
|
||||
@@ -92,7 +95,7 @@ void VideoOutput::start_pixel_line() {
|
||||
}
|
||||
|
||||
void VideoOutput::end_pixel_line() {
|
||||
if(current_output_target_) crt_->output_data((unsigned int)((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_);
|
||||
if(current_output_target_) crt_->output_data(static_cast<unsigned int>((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_);
|
||||
current_character_row_++;
|
||||
}
|
||||
|
||||
@@ -110,9 +113,9 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) {
|
||||
}
|
||||
|
||||
if(!initial_output_target_ || divider != current_output_divider_) {
|
||||
if(current_output_target_) crt_->output_data((unsigned int)((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_);
|
||||
if(current_output_target_) crt_->output_data(static_cast<unsigned int>((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_);
|
||||
current_output_divider_ = divider;
|
||||
initial_output_target_ = current_output_target_ = crt_->allocate_write_area(640 / current_output_divider_);
|
||||
initial_output_target_ = current_output_target_ = crt_->allocate_write_area(640 / current_output_divider_, 4);
|
||||
}
|
||||
|
||||
#define get_pixel() \
|
||||
@@ -127,7 +130,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) {
|
||||
if(initial_output_target_) {
|
||||
while(number_of_cycles--) {
|
||||
get_pixel();
|
||||
*(uint32_t *)current_output_target_ = palette_tables_.eighty1bpp[last_pixel_byte_];
|
||||
*reinterpret_cast<uint32_t *>(current_output_target_) = palette_tables_.eighty1bpp[last_pixel_byte_];
|
||||
current_output_target_ += 4;
|
||||
current_pixel_column_++;
|
||||
}
|
||||
@@ -138,7 +141,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) {
|
||||
if(initial_output_target_) {
|
||||
while(number_of_cycles--) {
|
||||
get_pixel();
|
||||
*(uint16_t *)current_output_target_ = palette_tables_.eighty2bpp[last_pixel_byte_];
|
||||
*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.eighty2bpp[last_pixel_byte_];
|
||||
current_output_target_ += 2;
|
||||
current_pixel_column_++;
|
||||
}
|
||||
@@ -160,7 +163,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) {
|
||||
if(initial_output_target_) {
|
||||
if(current_pixel_column_&1) {
|
||||
last_pixel_byte_ <<= 4;
|
||||
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
|
||||
*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_];
|
||||
current_output_target_ += 2;
|
||||
|
||||
number_of_cycles--;
|
||||
@@ -168,11 +171,11 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) {
|
||||
}
|
||||
while(number_of_cycles > 1) {
|
||||
get_pixel();
|
||||
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
|
||||
*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_];
|
||||
current_output_target_ += 2;
|
||||
|
||||
last_pixel_byte_ <<= 4;
|
||||
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
|
||||
*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_];
|
||||
current_output_target_ += 2;
|
||||
|
||||
number_of_cycles -= 2;
|
||||
@@ -180,7 +183,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) {
|
||||
}
|
||||
if(number_of_cycles) {
|
||||
get_pixel();
|
||||
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
|
||||
*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_];
|
||||
current_output_target_ += 2;
|
||||
current_pixel_column_++;
|
||||
}
|
||||
@@ -229,16 +232,16 @@ void VideoOutput::run_for(const Cycles cycles) {
|
||||
while(number_of_cycles) {
|
||||
int draw_action_length = screen_map_[screen_map_pointer_].length;
|
||||
int time_left_in_action = std::min(number_of_cycles, draw_action_length - cycles_into_draw_action_);
|
||||
if(screen_map_[screen_map_pointer_].type == DrawAction::Pixels) output_pixels((unsigned int)time_left_in_action);
|
||||
if(screen_map_[screen_map_pointer_].type == DrawAction::Pixels) output_pixels(static_cast<unsigned int>(time_left_in_action));
|
||||
|
||||
number_of_cycles -= time_left_in_action;
|
||||
cycles_into_draw_action_ += time_left_in_action;
|
||||
if(cycles_into_draw_action_ == draw_action_length) {
|
||||
switch(screen_map_[screen_map_pointer_].type) {
|
||||
case DrawAction::Sync: crt_->output_sync((unsigned int)(draw_action_length * crt_cycles_multiplier)); break;
|
||||
case DrawAction::ColourBurst: crt_->output_default_colour_burst((unsigned int)(draw_action_length * crt_cycles_multiplier)); break;
|
||||
case DrawAction::Blank: crt_->output_blank((unsigned int)(draw_action_length * crt_cycles_multiplier)); break;
|
||||
case DrawAction::Pixels: end_pixel_line(); break;
|
||||
case DrawAction::Sync: crt_->output_sync(static_cast<unsigned int>(draw_action_length * crt_cycles_multiplier)); break;
|
||||
case DrawAction::ColourBurst: crt_->output_default_colour_burst(static_cast<unsigned int>(draw_action_length * crt_cycles_multiplier)); break;
|
||||
case DrawAction::Blank: crt_->output_blank(static_cast<unsigned int>(draw_action_length * crt_cycles_multiplier)); break;
|
||||
case DrawAction::Pixels: end_pixel_line(); break;
|
||||
}
|
||||
screen_map_pointer_ = (screen_map_pointer_ + 1) % screen_map_.size();
|
||||
cycles_into_draw_action_ = 0;
|
||||
@@ -252,11 +255,11 @@ void VideoOutput::run_for(const Cycles cycles) {
|
||||
void VideoOutput::set_register(int address, uint8_t value) {
|
||||
switch(address & 0xf) {
|
||||
case 0x02:
|
||||
start_screen_address_ = (start_screen_address_ & 0xfe00) | (uint16_t)((value & 0xe0) << 1);
|
||||
start_screen_address_ = (start_screen_address_ & 0xfe00) | static_cast<uint16_t>((value & 0xe0) << 1);
|
||||
if(!start_screen_address_) start_screen_address_ |= 0x8000;
|
||||
break;
|
||||
case 0x03:
|
||||
start_screen_address_ = (start_screen_address_ & 0x01ff) | (uint16_t)((value & 0x3f) << 9);
|
||||
start_screen_address_ = (start_screen_address_ & 0x01ff) | static_cast<uint16_t>((value & 0x3f) << 9);
|
||||
if(!start_screen_address_) start_screen_address_ |= 0x8000;
|
||||
break;
|
||||
case 0x07: {
|
||||
@@ -298,17 +301,17 @@ void VideoOutput::set_register(int address, uint8_t value) {
|
||||
}
|
||||
|
||||
// regenerate all palette tables for now
|
||||
#define pack(a, b) (uint8_t)((a << 4) | (b))
|
||||
#define pack(a, b) static_cast<uint8_t>((a << 4) | (b))
|
||||
for(int byte = 0; byte < 256; byte++) {
|
||||
uint8_t *target = (uint8_t *)&palette_tables_.forty1bpp[byte];
|
||||
uint8_t *target = reinterpret_cast<uint8_t *>(&palette_tables_.forty1bpp[byte]);
|
||||
target[0] = pack(palette_[(byte&0x80) >> 4], palette_[(byte&0x40) >> 3]);
|
||||
target[1] = pack(palette_[(byte&0x20) >> 2], palette_[(byte&0x10) >> 1]);
|
||||
|
||||
target = (uint8_t *)&palette_tables_.eighty2bpp[byte];
|
||||
target = reinterpret_cast<uint8_t *>(&palette_tables_.eighty2bpp[byte]);
|
||||
target[0] = pack(palette_[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], palette_[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]);
|
||||
target[1] = pack(palette_[((byte&0x20) >> 2) | ((byte&0x02) >> 0)], palette_[((byte&0x10) >> 1) | ((byte&0x01) << 1)]);
|
||||
|
||||
target = (uint8_t *)&palette_tables_.eighty1bpp[byte];
|
||||
target = reinterpret_cast<uint8_t *>(&palette_tables_.eighty1bpp[byte]);
|
||||
target[0] = pack(palette_[(byte&0x80) >> 4], palette_[(byte&0x40) >> 3]);
|
||||
target[1] = pack(palette_[(byte&0x20) >> 2], palette_[(byte&0x10) >> 1]);
|
||||
target[2] = pack(palette_[(byte&0x08) >> 0], palette_[(byte&0x04) << 1]);
|
||||
@@ -382,9 +385,9 @@ unsigned int VideoOutput::get_cycles_until_next_ram_availability(int from_time)
|
||||
int output_position_line = graphics_line(output_position_);
|
||||
int implied_row = current_character_row_ + (current_line - output_position_line) % 10;
|
||||
if(implied_row < 8)
|
||||
result += (unsigned int)(80 - current_column);
|
||||
result += static_cast<unsigned int>(80 - current_column);
|
||||
}
|
||||
else result += (unsigned int)(80 - current_column);
|
||||
else result += static_cast<unsigned int>(80 - current_column);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -80,12 +80,13 @@ class VideoOutput {
|
||||
inline void output_pixels(unsigned int number_of_cycles);
|
||||
inline void setup_base_address();
|
||||
|
||||
int output_position_, unused_cycles_;
|
||||
int output_position_ = 0;
|
||||
int unused_cycles_ = 0;
|
||||
|
||||
uint8_t palette_[16];
|
||||
uint8_t screen_mode_;
|
||||
uint16_t screen_mode_base_address_;
|
||||
uint16_t start_screen_address_;
|
||||
uint8_t screen_mode_ = 6;
|
||||
uint16_t screen_mode_base_address_ = 0;
|
||||
uint16_t start_screen_address_ = 0;
|
||||
|
||||
uint8_t *ram_;
|
||||
struct {
|
||||
@@ -97,14 +98,18 @@ class VideoOutput {
|
||||
} palette_tables_;
|
||||
|
||||
// Display generation.
|
||||
uint16_t start_line_address_, current_screen_address_;
|
||||
int current_pixel_line_, current_pixel_column_, current_character_row_;
|
||||
uint8_t last_pixel_byte_;
|
||||
bool is_blank_line_;
|
||||
uint16_t start_line_address_ = 0;
|
||||
uint16_t current_screen_address_ = 0;
|
||||
int current_pixel_line_ = -1;
|
||||
int current_pixel_column_ = 0;
|
||||
int current_character_row_ = 0;
|
||||
uint8_t last_pixel_byte_ = 0;
|
||||
bool is_blank_line_ = false;
|
||||
|
||||
// CRT output
|
||||
uint8_t *current_output_target_, *initial_output_target_;
|
||||
unsigned int current_output_divider_;
|
||||
uint8_t *current_output_target_ = nullptr;
|
||||
uint8_t *initial_output_target_ = nullptr;
|
||||
unsigned int current_output_divider_ = 1;
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
||||
|
||||
@@ -119,8 +124,8 @@ class VideoOutput {
|
||||
void setup_screen_map();
|
||||
void emplace_blank_line();
|
||||
void emplace_pixel_line();
|
||||
size_t screen_map_pointer_;
|
||||
int cycles_into_draw_action_;
|
||||
size_t screen_map_pointer_ = 0;
|
||||
int cycles_into_draw_action_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
24
Machines/JoystickMachine.hpp
Normal file
24
Machines/JoystickMachine.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// JoystickMachine.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/10/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef JoystickMachine_hpp
|
||||
#define JoystickMachine_hpp
|
||||
|
||||
#include "../Inputs/Joystick.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace JoystickMachine {
|
||||
|
||||
class Machine {
|
||||
public:
|
||||
virtual std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* JoystickMachine_hpp */
|
||||
29
Machines/KeyboardMachine.cpp
Normal file
29
Machines/KeyboardMachine.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// KeyboardMachine.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/10/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "KeyboardMachine.hpp"
|
||||
|
||||
using namespace KeyboardMachine;
|
||||
|
||||
Machine::Machine() {
|
||||
keyboard_.set_delegate(this);
|
||||
}
|
||||
|
||||
void Machine::keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) {
|
||||
uint16_t mapped_key = get_keyboard_mapper().mapped_key_for_key(key);
|
||||
if(mapped_key != KeyNotMapped) set_key_state(mapped_key, is_pressed);
|
||||
}
|
||||
|
||||
void Machine::reset_all_keys(Inputs::Keyboard *keyboard) {
|
||||
// TODO: unify naming.
|
||||
clear_all_keys();
|
||||
}
|
||||
|
||||
Inputs::Keyboard &Machine::get_keyboard() {
|
||||
return keyboard_;
|
||||
}
|
||||
@@ -9,20 +9,59 @@
|
||||
#ifndef KeyboardMachine_h
|
||||
#define KeyboardMachine_h
|
||||
|
||||
#include "../Inputs/Keyboard.hpp"
|
||||
|
||||
namespace KeyboardMachine {
|
||||
|
||||
class Machine {
|
||||
class Machine: public Inputs::Keyboard::Delegate {
|
||||
public:
|
||||
Machine();
|
||||
|
||||
/*!
|
||||
Indicates that the key @c key has been either pressed or released, according to
|
||||
the state of @c isPressed.
|
||||
*/
|
||||
virtual void set_key_state(uint16_t key, bool isPressed) = 0;
|
||||
virtual void set_key_state(uint16_t key, bool is_pressed) = 0;
|
||||
|
||||
/*!
|
||||
Instructs that all keys should now be treated as released.
|
||||
*/
|
||||
virtual void clear_all_keys() = 0;
|
||||
|
||||
/*!
|
||||
Provides a destination for keyboard input.
|
||||
*/
|
||||
virtual Inputs::Keyboard &get_keyboard();
|
||||
|
||||
/*!
|
||||
A keyboard mapper attempts to provide a physical mapping between host keys and emulated keys.
|
||||
See the character mapper for logical mapping.
|
||||
*/
|
||||
class KeyboardMapper {
|
||||
public:
|
||||
virtual uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) = 0;
|
||||
};
|
||||
|
||||
/// Terminates a key sequence from the character mapper.
|
||||
static const uint16_t KeyEndSequence = 0xffff;
|
||||
|
||||
/*!
|
||||
Indicates that a key is not mapped (for the keyboard mapper) or that a
|
||||
character cannot be typed (for the character mapper).
|
||||
*/
|
||||
static const uint16_t KeyNotMapped = 0xfffe;
|
||||
|
||||
protected:
|
||||
/*!
|
||||
Allows individual machines to provide the mapping between host keys
|
||||
as per Inputs::Keyboard and their native scheme.
|
||||
*/
|
||||
virtual KeyboardMapper &get_keyboard_mapper() = 0;
|
||||
|
||||
private:
|
||||
void keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) override;
|
||||
void reset_all_keys(Inputs::Keyboard *keyboard) override;
|
||||
Inputs::Keyboard keyboard_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
//
|
||||
// CharacterMapper.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Machines_Oric_CharacterMapper_hpp
|
||||
#define Machines_Oric_CharacterMapper_hpp
|
||||
|
||||
#include "../Typer.hpp"
|
||||
|
||||
namespace Oric {
|
||||
|
||||
class CharacterMapper: public ::Utility::CharacterMapper {
|
||||
public:
|
||||
uint16_t *sequence_for_character(char character);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Machines_Oric_CharacterMapper_hpp */
|
||||
@@ -1,20 +1,60 @@
|
||||
//
|
||||
// CharacterMapper.cpp
|
||||
// Keyboard.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/08/2017.
|
||||
// Created by Thomas Harte on 10/10/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "CharacterMapper.hpp"
|
||||
#include "Oric.hpp"
|
||||
#include "Keyboard.hpp"
|
||||
|
||||
using namespace Oric;
|
||||
|
||||
uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
|
||||
#define BIND(source, dest) case Inputs::Keyboard::Key::source: return Oric::dest
|
||||
switch(key) {
|
||||
default: break;
|
||||
|
||||
BIND(k0, Key0); BIND(k1, Key1); BIND(k2, Key2); BIND(k3, Key3); BIND(k4, Key4);
|
||||
BIND(k5, Key5); BIND(k6, Key6); BIND(k7, Key7); BIND(k8, Key8); BIND(k9, Key9);
|
||||
BIND(Q, KeyQ); BIND(W, KeyW); BIND(E, KeyE); BIND(R, KeyR); BIND(T, KeyT);
|
||||
BIND(Y, KeyY); BIND(U, KeyU); BIND(I, KeyI); BIND(O, KeyO); BIND(P, KeyP);
|
||||
BIND(A, KeyA); BIND(S, KeyS); BIND(D, KeyD); BIND(F, KeyF); BIND(G, KeyG);
|
||||
BIND(H, KeyH); BIND(J, KeyJ); BIND(K, KeyK); BIND(L, KeyL);
|
||||
BIND(Z, KeyZ); BIND(X, KeyX); BIND(C, KeyC); BIND(V, KeyV);
|
||||
BIND(B, KeyB); BIND(N, KeyN); BIND(M, KeyM);
|
||||
|
||||
BIND(Left, KeyLeft); BIND(Right, KeyRight); BIND(Up, KeyUp); BIND(Down, KeyDown);
|
||||
|
||||
BIND(Hyphen, KeyMinus); BIND(Equals, KeyEquals); BIND(BackSlash, KeyBackSlash);
|
||||
BIND(OpenSquareBracket, KeyOpenSquare); BIND(CloseSquareBracket, KeyCloseSquare);
|
||||
|
||||
BIND(BackSpace, KeyDelete); BIND(Delete, KeyDelete);
|
||||
|
||||
BIND(Semicolon, KeySemiColon); BIND(Quote, KeyQuote);
|
||||
BIND(Comma, KeyComma); BIND(FullStop, KeyFullStop); BIND(ForwardSlash, KeyForwardSlash);
|
||||
|
||||
BIND(Escape, KeyEscape); BIND(Tab, KeyEscape);
|
||||
BIND(CapsLock, KeyControl); BIND(LeftControl, KeyControl); BIND(RightControl, KeyControl);
|
||||
BIND(LeftOption, KeyFunction);
|
||||
BIND(RightOption, KeyFunction);
|
||||
BIND(LeftMeta, KeyFunction);
|
||||
BIND(RightMeta, KeyFunction);
|
||||
BIND(LeftShift, KeyLeftShift);
|
||||
BIND(RightShift, KeyRightShift);
|
||||
|
||||
BIND(Space, KeySpace);
|
||||
BIND(Enter, KeyReturn);
|
||||
}
|
||||
#undef BIND
|
||||
|
||||
return KeyboardMachine::Machine::KeyNotMapped;
|
||||
}
|
||||
|
||||
uint16_t *CharacterMapper::sequence_for_character(char character) {
|
||||
#define KEYS(...) {__VA_ARGS__, EndSequence}
|
||||
#define SHIFT(...) {KeyLeftShift, __VA_ARGS__, EndSequence}
|
||||
#define X {NotMapped}
|
||||
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
|
||||
#define SHIFT(...) {KeyLeftShift, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
|
||||
#define X {KeyboardMachine::Machine::KeyNotMapped}
|
||||
static KeySequence key_sequences[] = {
|
||||
/* NUL */ X, /* SOH */ X,
|
||||
/* STX */ X, /* ETX */ X,
|
||||
48
Machines/Oric/Keyboard.hpp
Normal file
48
Machines/Oric/Keyboard.hpp
Normal file
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// Keyboard.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/10/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Machines_Oric_Keyboard_hpp
|
||||
#define Machines_Oric_Keyboard_hpp
|
||||
|
||||
#include "../KeyboardMachine.hpp"
|
||||
#include "../Utility/Typer.hpp"
|
||||
|
||||
namespace Oric {
|
||||
|
||||
enum Key: uint16_t {
|
||||
Key3 = 0x0000 | 0x80, KeyX = 0x0000 | 0x40, Key1 = 0x0000 | 0x20,
|
||||
KeyV = 0x0000 | 0x08, Key5 = 0x0000 | 0x04, KeyN = 0x0000 | 0x02, Key7 = 0x0000 | 0x01,
|
||||
KeyD = 0x0100 | 0x80, KeyQ = 0x0100 | 0x40, KeyEscape = 0x0100 | 0x20,
|
||||
KeyF = 0x0100 | 0x08, KeyR = 0x0100 | 0x04, KeyT = 0x0100 | 0x02, KeyJ = 0x0100 | 0x01,
|
||||
KeyC = 0x0200 | 0x80, Key2 = 0x0200 | 0x40, KeyZ = 0x0200 | 0x20, KeyControl = 0x0200 | 0x10,
|
||||
Key4 = 0x0200 | 0x08, KeyB = 0x0200 | 0x04, Key6 = 0x0200 | 0x02, KeyM = 0x0200 | 0x01,
|
||||
KeyQuote = 0x0300 | 0x80, KeyBackSlash = 0x0300 | 0x40,
|
||||
KeyMinus = 0x0300 | 0x08, KeySemiColon = 0x0300 | 0x04, Key9 = 0x0300 | 0x02, KeyK = 0x0300 | 0x01,
|
||||
KeyRight = 0x0400 | 0x80, KeyDown = 0x0400 | 0x40, KeyLeft = 0x0400 | 0x20, KeyLeftShift = 0x0400 | 0x10,
|
||||
KeyUp = 0x0400 | 0x08, KeyFullStop = 0x0400 | 0x04, KeyComma = 0x0400 | 0x02, KeySpace = 0x0400 | 0x01,
|
||||
KeyOpenSquare = 0x0500 | 0x80, KeyCloseSquare = 0x0500 | 0x40, KeyDelete = 0x0500 | 0x20, KeyFunction = 0x0500 | 0x10,
|
||||
KeyP = 0x0500 | 0x08, KeyO = 0x0500 | 0x04, KeyI = 0x0500 | 0x02, KeyU = 0x0500 | 0x01,
|
||||
KeyW = 0x0600 | 0x80, KeyS = 0x0600 | 0x40, KeyA = 0x0600 | 0x20,
|
||||
KeyE = 0x0600 | 0x08, KeyG = 0x0600 | 0x04, KeyH = 0x0600 | 0x02, KeyY = 0x0600 | 0x01,
|
||||
KeyEquals = 0x0700 | 0x80, KeyReturn = 0x0700 | 0x20, KeyRightShift = 0x0700 | 0x10,
|
||||
KeyForwardSlash = 0x0700 | 0x08, Key0 = 0x0700 | 0x04, KeyL = 0x0700 | 0x02, Key8 = 0x0700 | 0x01,
|
||||
|
||||
KeyNMI = 0xfffd,
|
||||
};
|
||||
|
||||
struct KeyboardMapper: public KeyboardMachine::Machine::KeyboardMapper {
|
||||
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key);
|
||||
};
|
||||
|
||||
struct CharacterMapper: public ::Utility::CharacterMapper {
|
||||
uint16_t *sequence_for_character(char character);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif /* KeyboardMapper_hpp */
|
||||
@@ -30,7 +30,7 @@ Microdisc::Microdisc() :
|
||||
|
||||
void Microdisc::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||
if(!drives_[drive]) {
|
||||
drives_[drive].reset(new Storage::Disk::Drive);
|
||||
drives_[drive].reset(new Storage::Disk::Drive(8000000, 300, 2));
|
||||
if(drive == selected_drive_) set_drive(drives_[drive]);
|
||||
}
|
||||
drives_[drive]->set_disk(disk);
|
||||
@@ -53,7 +53,7 @@ void Microdisc::set_control_register(uint8_t control, uint8_t changes) {
|
||||
|
||||
// b4: side select
|
||||
if(changes & 0x10) {
|
||||
unsigned int head = (control & 0x10) ? 1 : 0;
|
||||
int head = (control & 0x10) ? 1 : 0;
|
||||
for(int c = 0; c < 4; c++) {
|
||||
if(drives_[c]) drives_[c]->set_head(head);
|
||||
}
|
||||
@@ -95,7 +95,14 @@ uint8_t Microdisc::get_data_request_register() {
|
||||
}
|
||||
|
||||
void Microdisc::set_head_load_request(bool head_load) {
|
||||
set_motor_on(head_load);
|
||||
// The drive motors (at present: I believe **all drive motors** regardless of the selected drive) receive
|
||||
// the current head load request state.
|
||||
for(int c = 0; c < 4; c++) {
|
||||
if(drives_[c]) drives_[c]->set_motor_on(head_load);
|
||||
}
|
||||
|
||||
// A request to load the head results in a delay until the head is confirmed loaded. This delay is handled
|
||||
// in ::run_for. A request to unload the head results in an instant answer that the head is unloaded.
|
||||
if(head_load) {
|
||||
head_load_request_counter_ = 0;
|
||||
} else {
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
#include "Oric.hpp"
|
||||
|
||||
#include "Video.hpp"
|
||||
#include "Keyboard.hpp"
|
||||
#include "Microdisc.hpp"
|
||||
#include "CharacterMapper.hpp"
|
||||
#include "Video.hpp"
|
||||
|
||||
#include "../MemoryFuzzer.hpp"
|
||||
#include "../Typer.hpp"
|
||||
#include "../Utility/MemoryFuzzer.hpp"
|
||||
#include "../Utility/Typer.hpp"
|
||||
|
||||
#include "../../Processors/6502/6502.hpp"
|
||||
#include "../../Components/6522/6522.hpp"
|
||||
@@ -24,15 +24,156 @@
|
||||
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Oric {
|
||||
|
||||
/*!
|
||||
Models the Oric's keyboard: eight key rows, containing a bitfield of keys set.
|
||||
|
||||
Active line is selected through a port on the Oric's VIA, and a column mask is
|
||||
selected via a port on the AY, returning a single Boolean representation of the
|
||||
logical OR of every key selected by the column mask on the active row.
|
||||
*/
|
||||
class Keyboard {
|
||||
public:
|
||||
Keyboard() {
|
||||
clear_all_keys();
|
||||
}
|
||||
|
||||
/// Sets whether @c key is or is not pressed, per @c is_pressed.
|
||||
void set_key_state(uint16_t key, bool is_pressed) {
|
||||
uint8_t mask = key & 0xff;
|
||||
int line = key >> 8;
|
||||
|
||||
if(is_pressed) rows_[line] |= mask;
|
||||
else rows_[line] &= ~mask;
|
||||
}
|
||||
|
||||
/// Sets all keys as unpressed.
|
||||
void clear_all_keys() {
|
||||
memset(rows_, 0, sizeof(rows_));
|
||||
}
|
||||
|
||||
/// Selects the active row.
|
||||
void set_active_row(uint8_t row) {
|
||||
row_ = row & 7;
|
||||
}
|
||||
|
||||
/// Queries the keys on the active row specified by @c mask.
|
||||
bool query_column(uint8_t column_mask) {
|
||||
return !!(rows_[row_] & column_mask);
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t row_ = 0;
|
||||
uint8_t rows_[8];
|
||||
};
|
||||
|
||||
/*!
|
||||
Provide's the Oric's tape player: a standard binary-sampled tape which also holds
|
||||
an instance of the Oric tape parser, to provide fast-tape loading.
|
||||
*/
|
||||
class TapePlayer: public Storage::Tape::BinaryTapePlayer {
|
||||
public:
|
||||
TapePlayer() : Storage::Tape::BinaryTapePlayer(1000000) {}
|
||||
|
||||
/*!
|
||||
Parses the incoming tape event stream to obtain the next stored byte.
|
||||
|
||||
@param use_fast_encoding If set to @c true , inspects the tape as though it
|
||||
is encoded in the Oric's fast-loading scheme. Otherwise looks for a slow-encoded byte.
|
||||
|
||||
@returns The next byte from the tape.
|
||||
*/
|
||||
uint8_t get_next_byte(bool use_fast_encoding) {
|
||||
return static_cast<uint8_t>(parser_.get_next_byte(get_tape(), use_fast_encoding));
|
||||
}
|
||||
|
||||
private:
|
||||
Storage::Tape::Oric::Parser parser_;
|
||||
};
|
||||
|
||||
/*!
|
||||
Implements the Oric's VIA's port handler. On the Oric the VIA's ports connect
|
||||
to the AY, the tape's motor control signal and the keyboard.
|
||||
*/
|
||||
class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||
public:
|
||||
VIAPortHandler(TapePlayer &tape_player, Keyboard &keyboard) : tape_player_(tape_player), keyboard_(keyboard) {}
|
||||
|
||||
/*!
|
||||
Reponds to the 6522's control line output change signal; on an Oric A2 is connected to
|
||||
the AY's BDIR, and B2 is connected to the AY's A2.
|
||||
*/
|
||||
void set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) {
|
||||
if(line) {
|
||||
if(port) ay_bdir_ = value; else ay_bc1_ = value;
|
||||
update_ay();
|
||||
ay8910_->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2));
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Reponds to changes in the 6522's port output. On an Oric port B sets the tape motor control
|
||||
and the keyboard's active row. Port A is connected to the AY's data bus.
|
||||
*/
|
||||
void set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t direction_mask) {
|
||||
if(port) {
|
||||
keyboard_.set_active_row(value);
|
||||
tape_player_.set_motor_control(value & 0x40);
|
||||
} else {
|
||||
update_ay();
|
||||
ay8910_->set_data_input(value);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Provides input data for the 6522. Port B reads the keyboard, and port A reads from the AY.
|
||||
*/
|
||||
uint8_t get_port_input(MOS::MOS6522::Port port) {
|
||||
if(port) {
|
||||
uint8_t column = ay8910_->get_port_output(false) ^ 0xff;
|
||||
return keyboard_.query_column(column) ? 0x08 : 0x00;
|
||||
} else {
|
||||
return ay8910_->get_data_output();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Advances time. This class manages the AY's concept of time to permit updating-on-demand.
|
||||
*/
|
||||
inline void run_for(const Cycles cycles) {
|
||||
cycles_since_ay_update_ += cycles;
|
||||
}
|
||||
|
||||
/// Flushes any queued behaviour (which, specifically, means on the AY).
|
||||
void flush() {
|
||||
ay8910_->run_for(cycles_since_ay_update_.flush());
|
||||
ay8910_->flush();
|
||||
}
|
||||
|
||||
/// Sets the AY in use by the machine the VIA that uses this port handler sits within.
|
||||
void set_ay(GI::AY38910::AY38910 *ay) {
|
||||
ay8910_ = ay;
|
||||
}
|
||||
|
||||
private:
|
||||
void update_ay() {
|
||||
ay8910_->run_for(cycles_since_ay_update_.flush());
|
||||
}
|
||||
bool ay_bdir_ = false;
|
||||
bool ay_bc1_ = false;
|
||||
Cycles cycles_since_ay_update_;
|
||||
|
||||
GI::AY38910::AY38910 *ay8910_ = nullptr;
|
||||
TapePlayer &tape_player_;
|
||||
Keyboard &keyboard_;
|
||||
};
|
||||
|
||||
class ConcreteMachine:
|
||||
public CPU::MOS6502::BusHandler,
|
||||
public MOS::MOS6522IRQDelegate::Delegate,
|
||||
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
|
||||
public Utility::TypeRecipient,
|
||||
public Storage::Tape::BinaryTapePlayer::Delegate,
|
||||
public Microdisc::Delegate,
|
||||
@@ -41,18 +182,12 @@ class ConcreteMachine:
|
||||
public:
|
||||
ConcreteMachine() :
|
||||
m6502_(*this),
|
||||
use_fast_tape_hack_(false),
|
||||
typer_delay_(2500000),
|
||||
keyboard_read_count_(0),
|
||||
keyboard_(new Keyboard),
|
||||
ram_top_(0xbfff),
|
||||
paged_rom_(rom_),
|
||||
microdisc_is_enabled_(false) {
|
||||
via_(via_port_handler_),
|
||||
via_port_handler_(tape_player_, keyboard_) {
|
||||
set_clock_rate(1000000);
|
||||
via_.set_interrupt_delegate(this);
|
||||
via_.keyboard = keyboard_;
|
||||
clear_all_keys();
|
||||
via_.tape->set_delegate(this);
|
||||
via_port_handler_.set_interrupt_delegate(this);
|
||||
tape_player_.set_delegate(this);
|
||||
Memory::Fuzz(ram_, sizeof(ram_));
|
||||
}
|
||||
|
||||
@@ -68,19 +203,16 @@ class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
|
||||
void set_key_state(uint16_t key, bool isPressed) override final {
|
||||
void set_key_state(uint16_t key, bool is_pressed) override final {
|
||||
if(key == KeyNMI) {
|
||||
m6502_.set_nmi_line(isPressed);
|
||||
m6502_.set_nmi_line(is_pressed);
|
||||
} else {
|
||||
if(isPressed)
|
||||
keyboard_->rows[key >> 8] |= (key & 0xff);
|
||||
else
|
||||
keyboard_->rows[key >> 8] &= ~(key & 0xff);
|
||||
keyboard_.set_key_state(key, is_pressed);
|
||||
}
|
||||
}
|
||||
|
||||
void clear_all_keys() override final {
|
||||
memset(keyboard_->rows, 0, sizeof(keyboard_->rows));
|
||||
keyboard_.clear_all_keys();
|
||||
}
|
||||
|
||||
void set_use_fast_tape_hack(bool activate) override final {
|
||||
@@ -124,7 +256,7 @@ class ConcreteMachine:
|
||||
|
||||
bool insert_media(const StaticAnalyser::Media &media) override final {
|
||||
if(media.tapes.size()) {
|
||||
via_.tape->set_tape(media.tapes.front());
|
||||
tape_player_.set_tape(media.tapes.front());
|
||||
}
|
||||
|
||||
int drive_index = 0;
|
||||
@@ -143,8 +275,14 @@ class ConcreteMachine:
|
||||
|
||||
// 024D = 0 => fast; otherwise slow
|
||||
// E6C9 = read byte: return byte in A
|
||||
if(address == tape_get_byte_address_ && paged_rom_ == rom_ && use_fast_tape_hack_ && operation == CPU::MOS6502::BusOperation::ReadOpcode && via_.tape->has_tape() && !via_.tape->get_tape()->is_at_end()) {
|
||||
uint8_t next_byte = via_.tape->get_next_byte(!ram_[tape_speed_address_]);
|
||||
if( address == tape_get_byte_address_ &&
|
||||
paged_rom_ == rom_ &&
|
||||
use_fast_tape_hack_ &&
|
||||
operation == CPU::MOS6502::BusOperation::ReadOpcode &&
|
||||
tape_player_.has_tape() &&
|
||||
!tape_player_.get_tape()->is_at_end()) {
|
||||
|
||||
uint8_t next_byte = tape_player_.get_next_byte(!ram_[tape_speed_address_]);
|
||||
m6502_.set_value_of_register(CPU::MOS6502::A, next_byte);
|
||||
m6502_.set_value_of_register(CPU::MOS6502::Flags, next_byte ? 0 : CPU::MOS6502::Flag::Zero);
|
||||
*value = 0x60; // i.e. RTS
|
||||
@@ -173,7 +311,7 @@ class ConcreteMachine:
|
||||
if(isReadOperation(operation))
|
||||
*value = ram_[address];
|
||||
else {
|
||||
if(address >= 0x9800 && address <= 0xc000) { update_video(); typer_delay_ = 0; }
|
||||
if(address >= 0x9800 && address <= 0xc000) update_video();
|
||||
ram_[address] = *value;
|
||||
}
|
||||
}
|
||||
@@ -190,6 +328,8 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
via_.run_for(Cycles(1));
|
||||
via_port_handler_.run_for(Cycles(1));
|
||||
tape_player_.run_for(Cycles(1));
|
||||
if(microdisc_is_enabled_) microdisc_.run_for(Cycles(8));
|
||||
cycles_since_video_update_++;
|
||||
return Cycles(1);
|
||||
@@ -197,20 +337,23 @@ class ConcreteMachine:
|
||||
|
||||
forceinline void flush() {
|
||||
update_video();
|
||||
via_.flush();
|
||||
via_port_handler_.flush();
|
||||
}
|
||||
|
||||
// to satisfy CRTMachine::Machine
|
||||
void setup_output(float aspect_ratio) override final {
|
||||
via_.ay8910.reset(new GI::AY38910::AY38910());
|
||||
via_.ay8910->set_clock_rate(1000000);
|
||||
ay8910_.reset(new GI::AY38910::AY38910());
|
||||
ay8910_->set_clock_rate(1000000);
|
||||
via_port_handler_.set_ay(ay8910_.get());
|
||||
|
||||
video_output_.reset(new VideoOutput(ram_));
|
||||
if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_);
|
||||
}
|
||||
|
||||
void close_output() override final {
|
||||
video_output_.reset();
|
||||
via_.ay8910.reset();
|
||||
ay8910_.reset();
|
||||
via_port_handler_.set_ay(nullptr);
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt() override final {
|
||||
@@ -218,7 +361,7 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::Speaker> get_speaker() override final {
|
||||
return via_.ay8910;
|
||||
return ay8910_;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) override final {
|
||||
@@ -233,7 +376,7 @@ class ConcreteMachine:
|
||||
// to satisfy Storage::Tape::BinaryTapePlayer::Delegate
|
||||
void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player) override final {
|
||||
// set CB1
|
||||
via_.set_control_line_input(VIA::Port::B, VIA::Line::One, !tape_player->get_input());
|
||||
via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, !tape_player->get_input());
|
||||
}
|
||||
|
||||
// for Utility::TypeRecipient::Delegate
|
||||
@@ -262,6 +405,10 @@ class ConcreteMachine:
|
||||
set_interrupt_line();
|
||||
}
|
||||
|
||||
KeyboardMapper &get_keyboard_mapper() override {
|
||||
return keyboard_mapper_;
|
||||
}
|
||||
|
||||
private:
|
||||
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
|
||||
|
||||
@@ -274,99 +421,29 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
// ROM bookkeeping
|
||||
bool is_using_basic11_;
|
||||
uint16_t tape_get_byte_address_, scan_keyboard_address_, tape_speed_address_;
|
||||
int keyboard_read_count_;
|
||||
bool is_using_basic11_ = false;
|
||||
uint16_t tape_get_byte_address_ = 0, scan_keyboard_address_ = 0, tape_speed_address_ = 0;
|
||||
int keyboard_read_count_ = 0;
|
||||
|
||||
// Outputs
|
||||
std::unique_ptr<VideoOutput> video_output_;
|
||||
std::shared_ptr<GI::AY38910::AY38910> ay8910_;
|
||||
|
||||
// Keyboard
|
||||
class Keyboard {
|
||||
public:
|
||||
uint8_t row;
|
||||
uint8_t rows[8];
|
||||
};
|
||||
int typer_delay_;
|
||||
// Inputs
|
||||
Oric::KeyboardMapper keyboard_mapper_;
|
||||
|
||||
// The tape
|
||||
class TapePlayer: public Storage::Tape::BinaryTapePlayer {
|
||||
public:
|
||||
TapePlayer() : Storage::Tape::BinaryTapePlayer(1000000) {}
|
||||
TapePlayer tape_player_;
|
||||
bool use_fast_tape_hack_ = false;
|
||||
|
||||
inline uint8_t get_next_byte(bool fast) {
|
||||
return (uint8_t)parser_.get_next_byte(get_tape(), fast);
|
||||
}
|
||||
|
||||
private:
|
||||
Storage::Tape::Oric::Parser parser_;
|
||||
};
|
||||
bool use_fast_tape_hack_;
|
||||
|
||||
// VIA (which owns the tape and the AY)
|
||||
class VIA: public MOS::MOS6522<VIA>, public MOS::MOS6522IRQDelegate {
|
||||
public:
|
||||
VIA() :
|
||||
MOS::MOS6522<VIA>(),
|
||||
tape(new TapePlayer) {}
|
||||
|
||||
using MOS6522IRQDelegate::set_interrupt_status;
|
||||
|
||||
void set_control_line_output(Port port, Line line, bool value) {
|
||||
if(line) {
|
||||
if(port) ay_bdir_ = value; else ay_bc1_ = value;
|
||||
update_ay();
|
||||
}
|
||||
}
|
||||
|
||||
void set_port_output(Port port, uint8_t value, uint8_t direction_mask) {
|
||||
if(port) {
|
||||
keyboard->row = value;
|
||||
tape->set_motor_control(value & 0x40);
|
||||
} else {
|
||||
ay8910->set_data_input(value);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t get_port_input(Port port) {
|
||||
if(port) {
|
||||
uint8_t column = ay8910->get_port_output(false) ^ 0xff;
|
||||
return (keyboard->rows[keyboard->row & 7] & column) ? 0x08 : 0x00;
|
||||
} else {
|
||||
return ay8910->get_data_output();
|
||||
}
|
||||
}
|
||||
|
||||
inline void run_for(const Cycles cycles) {
|
||||
cycles_since_ay_update_ += cycles;
|
||||
MOS::MOS6522<VIA>::run_for(cycles);
|
||||
tape->run_for(cycles);
|
||||
}
|
||||
|
||||
void flush() {
|
||||
ay8910->run_for(cycles_since_ay_update_.flush());
|
||||
ay8910->flush();
|
||||
}
|
||||
|
||||
std::shared_ptr<GI::AY38910::AY38910> ay8910;
|
||||
std::unique_ptr<TapePlayer> tape;
|
||||
std::shared_ptr<Keyboard> keyboard;
|
||||
|
||||
private:
|
||||
void update_ay() {
|
||||
ay8910->run_for(cycles_since_ay_update_.flush());
|
||||
ay8910->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2));
|
||||
}
|
||||
bool ay_bdir_, ay_bc1_;
|
||||
Cycles cycles_since_ay_update_;
|
||||
};
|
||||
VIA via_;
|
||||
std::shared_ptr<Keyboard> keyboard_;
|
||||
VIAPortHandler via_port_handler_;
|
||||
MOS::MOS6522::MOS6522<VIAPortHandler> via_;
|
||||
Keyboard keyboard_;
|
||||
|
||||
// the Microdisc, if in use
|
||||
class Microdisc microdisc_;
|
||||
bool microdisc_is_enabled_;
|
||||
uint16_t ram_top_;
|
||||
bool microdisc_is_enabled_ = false;
|
||||
uint16_t ram_top_ = 0xbfff;
|
||||
uint8_t *paged_rom_;
|
||||
|
||||
inline void set_interrupt_line() {
|
||||
|
||||
@@ -22,27 +22,6 @@ enum ROM {
|
||||
BASIC10, BASIC11, Microdisc, Colour
|
||||
};
|
||||
|
||||
enum Key: uint16_t {
|
||||
Key3 = 0x0000 | 0x80, KeyX = 0x0000 | 0x40, Key1 = 0x0000 | 0x20,
|
||||
KeyV = 0x0000 | 0x08, Key5 = 0x0000 | 0x04, KeyN = 0x0000 | 0x02, Key7 = 0x0000 | 0x01,
|
||||
KeyD = 0x0100 | 0x80, KeyQ = 0x0100 | 0x40, KeyEscape = 0x0100 | 0x20,
|
||||
KeyF = 0x0100 | 0x08, KeyR = 0x0100 | 0x04, KeyT = 0x0100 | 0x02, KeyJ = 0x0100 | 0x01,
|
||||
KeyC = 0x0200 | 0x80, Key2 = 0x0200 | 0x40, KeyZ = 0x0200 | 0x20, KeyControl = 0x0200 | 0x10,
|
||||
Key4 = 0x0200 | 0x08, KeyB = 0x0200 | 0x04, Key6 = 0x0200 | 0x02, KeyM = 0x0200 | 0x01,
|
||||
KeyQuote = 0x0300 | 0x80, KeyBackSlash = 0x0300 | 0x40,
|
||||
KeyMinus = 0x0300 | 0x08, KeySemiColon = 0x0300 | 0x04, Key9 = 0x0300 | 0x02, KeyK = 0x0300 | 0x01,
|
||||
KeyRight = 0x0400 | 0x80, KeyDown = 0x0400 | 0x40, KeyLeft = 0x0400 | 0x20, KeyLeftShift = 0x0400 | 0x10,
|
||||
KeyUp = 0x0400 | 0x08, KeyFullStop = 0x0400 | 0x04, KeyComma = 0x0400 | 0x02, KeySpace = 0x0400 | 0x01,
|
||||
KeyOpenSquare = 0x0500 | 0x80, KeyCloseSquare = 0x0500 | 0x40, KeyDelete = 0x0500 | 0x20, KeyFunction = 0x0500 | 0x10,
|
||||
KeyP = 0x0500 | 0x08, KeyO = 0x0500 | 0x04, KeyI = 0x0500 | 0x02, KeyU = 0x0500 | 0x01,
|
||||
KeyW = 0x0600 | 0x80, KeyS = 0x0600 | 0x40, KeyA = 0x0600 | 0x20,
|
||||
KeyE = 0x0600 | 0x08, KeyG = 0x0600 | 0x04, KeyH = 0x0600 | 0x02, KeyY = 0x0600 | 0x01,
|
||||
KeyEquals = 0x0700 | 0x80, KeyReturn = 0x0700 | 0x20, KeyRightShift = 0x0700 | 0x10,
|
||||
KeyForwardSlash = 0x0700 | 0x08, Key0 = 0x0700 | 0x04, KeyL = 0x0700 | 0x02, Key8 = 0x0700 | 0x01,
|
||||
|
||||
KeyNMI = 0xfffd,
|
||||
};
|
||||
|
||||
/*!
|
||||
Models an Oric 1/Atmos with or without a Microdisc.
|
||||
*/
|
||||
|
||||
@@ -45,7 +45,7 @@ VideoOutput::VideoOutput(uint8_t *memory) :
|
||||
crt_->set_composite_function_type(Outputs::CRT::CRT::CompositeSourceType::DiscreteFourSamplesPerCycle, 0.0f);
|
||||
|
||||
set_output_device(Outputs::CRT::Television);
|
||||
crt_->set_visible_area(crt_->get_rect_for_area(50, 224, 16 * 6, 40 * 6, 4.0f / 3.0f));
|
||||
crt_->set_visible_area(crt_->get_rect_for_area(53, 224, 16 * 6, 40 * 6, 4.0f / 3.0f));
|
||||
}
|
||||
|
||||
void VideoOutput::set_output_device(Outputs::CRT::OutputDevice output_device) {
|
||||
@@ -56,16 +56,16 @@ void VideoOutput::set_output_device(Outputs::CRT::OutputDevice output_device) {
|
||||
void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom) {
|
||||
for(size_t c = 0; c < 8; c++) {
|
||||
size_t index = (c << 2);
|
||||
uint16_t rom_value = (uint16_t)(((uint16_t)rom[index] << 8) | (uint16_t)rom[index+1]);
|
||||
uint16_t rom_value = static_cast<uint16_t>((static_cast<uint16_t>(rom[index]) << 8) | static_cast<uint16_t>(rom[index+1]));
|
||||
rom_value = (rom_value & 0xff00) | ((rom_value >> 4)&0x000f) | ((rom_value << 4)&0x00f0);
|
||||
colour_forms_[c] = rom_value;
|
||||
}
|
||||
|
||||
// check for big endianness and byte swap if required
|
||||
uint16_t test_value = 0x0001;
|
||||
if(*(uint8_t *)&test_value != 0x01) {
|
||||
if(*reinterpret_cast<uint8_t *>(&test_value) != 0x01) {
|
||||
for(size_t c = 0; c < 8; c++) {
|
||||
colour_forms_[c] = (uint16_t)((colour_forms_[c] >> 8) | (colour_forms_[c] << 8));
|
||||
colour_forms_[c] = static_cast<uint16_t>((colour_forms_[c] >> 8) | (colour_forms_[c] << 8));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,7 +89,7 @@ void VideoOutput::run_for(const Cycles cycles) {
|
||||
if(counter_ >= v_sync_start_position_ && counter_ < v_sync_end_position_) {
|
||||
// this is a sync line
|
||||
cycles_run_for = v_sync_end_position_ - counter_;
|
||||
clamp(crt_->output_sync((unsigned int)(v_sync_end_position_ - v_sync_start_position_) * 6));
|
||||
clamp(crt_->output_sync(static_cast<unsigned int>(v_sync_end_position_ - v_sync_start_position_) * 6));
|
||||
} else if(counter_ < 224*64 && h_counter < 40) {
|
||||
// this is a pixel line
|
||||
if(!h_counter) {
|
||||
@@ -97,7 +97,7 @@ void VideoOutput::run_for(const Cycles cycles) {
|
||||
paper_ = 0x0;
|
||||
use_alternative_character_set_ = use_double_height_characters_ = blink_text_ = false;
|
||||
set_character_set_base_address();
|
||||
pixel_target_ = (uint16_t *)crt_->allocate_write_area(240);
|
||||
pixel_target_ = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(240));
|
||||
|
||||
if(!counter_) {
|
||||
frame_counter_++;
|
||||
@@ -133,8 +133,8 @@ void VideoOutput::run_for(const Cycles cycles) {
|
||||
if(pixel_target_) {
|
||||
uint16_t colours[2];
|
||||
if(output_device_ == Outputs::CRT::Monitor) {
|
||||
colours[0] = (uint8_t)(paper_ ^ inverse_mask);
|
||||
colours[1] = (uint8_t)(ink_ ^ inverse_mask);
|
||||
colours[0] = static_cast<uint8_t>(paper_ ^ inverse_mask);
|
||||
colours[1] = static_cast<uint8_t>(ink_ ^ inverse_mask);
|
||||
} else {
|
||||
colours[0] = colour_forms_[paper_ ^ inverse_mask];
|
||||
colours[1] = colour_forms_[ink_ ^ inverse_mask];
|
||||
@@ -202,7 +202,7 @@ void VideoOutput::run_for(const Cycles cycles) {
|
||||
cycles_run_for = 48 - h_counter;
|
||||
clamp(
|
||||
int period = (counter_ < 224*64) ? 8 : 48;
|
||||
crt_->output_blank((unsigned int)period * 6);
|
||||
crt_->output_blank(static_cast<unsigned int>(period) * 6);
|
||||
);
|
||||
} else if(h_counter < 54) {
|
||||
cycles_run_for = 54 - h_counter;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include <cstdlib>
|
||||
|
||||
void Memory::Fuzz(uint8_t *buffer, size_t size) {
|
||||
unsigned int divider = ((unsigned int)RAND_MAX + 1) / 256;
|
||||
unsigned int divider = (static_cast<unsigned int>(RAND_MAX) + 1) / 256;
|
||||
unsigned int shift = 1, value = 1;
|
||||
while(value < divider) {
|
||||
value <<= 1;
|
||||
@@ -19,7 +19,7 @@ void Memory::Fuzz(uint8_t *buffer, size_t size) {
|
||||
}
|
||||
|
||||
for(size_t c = 0; c < size; c++) {
|
||||
buffer[c] = (uint8_t)(std::rand() >> shift);
|
||||
buffer[c] = static_cast<uint8_t>(std::rand() >> shift);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,14 +44,14 @@ void Typer::run_for(const HalfCycles duration) {
|
||||
bool Typer::try_type_next_character() {
|
||||
uint16_t *sequence = character_mapper_->sequence_for_character(string_[string_pointer_]);
|
||||
|
||||
if(!sequence || sequence[0] == CharacterMapper::NotMapped) {
|
||||
if(!sequence || sequence[0] == KeyboardMachine::Machine::KeyNotMapped) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!phase_) delegate_->clear_all_keys();
|
||||
else {
|
||||
delegate_->set_key_state(sequence[phase_ - 1], true);
|
||||
return sequence[phase_] != CharacterMapper::EndSequence;
|
||||
return sequence[phase_] != KeyboardMachine::Machine::KeyEndSequence;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -83,8 +83,8 @@ Typer::~Typer() {
|
||||
#pragma mark - Character mapper
|
||||
|
||||
uint16_t *CharacterMapper::table_lookup_sequence_for_character(KeySequence *sequences, size_t length, char character) {
|
||||
size_t ucharacter = (size_t)((unsigned char)character);
|
||||
size_t ucharacter = static_cast<size_t>((unsigned char)character);
|
||||
if(ucharacter > (length / sizeof(KeySequence))) return nullptr;
|
||||
if(sequences[ucharacter][0] == NotMapped) return nullptr;
|
||||
if(sequences[ucharacter][0] == KeyboardMachine::Machine::KeyNotMapped) return nullptr;
|
||||
return sequences[ucharacter];
|
||||
}
|
||||
@@ -10,8 +10,8 @@
|
||||
#define Typer_hpp
|
||||
|
||||
#include <memory>
|
||||
#include "KeyboardMachine.hpp"
|
||||
#include "../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../KeyboardMachine.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace Utility {
|
||||
|
||||
@@ -24,15 +24,6 @@ class CharacterMapper {
|
||||
/// @returns The EndSequence-terminated sequence of keys that would cause @c character to be typed.
|
||||
virtual uint16_t *sequence_for_character(char character) = 0;
|
||||
|
||||
/// Terminates a key sequence.
|
||||
static const uint16_t EndSequence = 0xffff;
|
||||
|
||||
/*!
|
||||
If returned as the first entry in a key sequence, indicates that the requested character
|
||||
cannot be mapped.
|
||||
*/
|
||||
static const uint16_t NotMapped = 0xfffe;
|
||||
|
||||
protected:
|
||||
typedef uint16_t KeySequence[16];
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
//
|
||||
// CharacterMapper.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Machines_ZX8081_CharacterMapper_hpp
|
||||
#define Machines_ZX8081_CharacterMapper_hpp
|
||||
|
||||
#include "../Typer.hpp"
|
||||
|
||||
namespace ZX8081 {
|
||||
|
||||
class CharacterMapper: public ::Utility::CharacterMapper {
|
||||
public:
|
||||
CharacterMapper(bool is_zx81);
|
||||
uint16_t *sequence_for_character(char character);
|
||||
|
||||
private:
|
||||
bool is_zx81_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* CharacterMapper_hpp */
|
||||
@@ -1,22 +1,44 @@
|
||||
//
|
||||
// CharacterMapper.cpp
|
||||
// Keyboard.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/08/2017.
|
||||
// Created by Thomas Harte on 10/10/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "CharacterMapper.hpp"
|
||||
#include "ZX8081.hpp"
|
||||
#include "Keyboard.hpp"
|
||||
|
||||
using namespace ZX8081;
|
||||
|
||||
uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
|
||||
#define BIND(source, dest) case Inputs::Keyboard::Key::source: return ZX8081::dest
|
||||
switch(key) {
|
||||
default: break;
|
||||
|
||||
BIND(k0, Key0); BIND(k1, Key1); BIND(k2, Key2); BIND(k3, Key3); BIND(k4, Key4);
|
||||
BIND(k5, Key5); BIND(k6, Key6); BIND(k7, Key7); BIND(k8, Key8); BIND(k9, Key9);
|
||||
BIND(Q, KeyQ); BIND(W, KeyW); BIND(E, KeyE); BIND(R, KeyR); BIND(T, KeyT);
|
||||
BIND(Y, KeyY); BIND(U, KeyU); BIND(I, KeyI); BIND(O, KeyO); BIND(P, KeyP);
|
||||
BIND(A, KeyA); BIND(S, KeyS); BIND(D, KeyD); BIND(F, KeyF); BIND(G, KeyG);
|
||||
BIND(H, KeyH); BIND(J, KeyJ); BIND(K, KeyK); BIND(L, KeyL);
|
||||
BIND(Z, KeyZ); BIND(X, KeyX); BIND(C, KeyC); BIND(V, KeyV);
|
||||
BIND(B, KeyB); BIND(N, KeyN); BIND(M, KeyM);
|
||||
|
||||
BIND(LeftShift, KeyShift); BIND(RightShift, KeyShift);
|
||||
BIND(FullStop, KeyDot);
|
||||
BIND(Enter, KeyEnter);
|
||||
BIND(Space, KeySpace);
|
||||
}
|
||||
#undef BIND
|
||||
return KeyboardMachine::Machine::KeyNotMapped;
|
||||
}
|
||||
|
||||
CharacterMapper::CharacterMapper(bool is_zx81) : is_zx81_(is_zx81) {}
|
||||
|
||||
uint16_t *CharacterMapper::sequence_for_character(char character) {
|
||||
#define KEYS(...) {__VA_ARGS__, EndSequence}
|
||||
#define SHIFT(...) {KeyShift, __VA_ARGS__, EndSequence}
|
||||
#define X {NotMapped}
|
||||
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
|
||||
#define SHIFT(...) {KeyShift, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
|
||||
#define X {KeyboardMachine::Machine::KeyNotMapped}
|
||||
static KeySequence zx81_key_sequences[] = {
|
||||
/* NUL */ X, /* SOH */ X,
|
||||
/* STX */ X, /* ETX */ X,
|
||||
43
Machines/ZX8081/Keyboard.hpp
Normal file
43
Machines/ZX8081/Keyboard.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// Keyboard.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/10/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Machines_ZX8081_Keyboard_hpp
|
||||
#define Machines_ZX8081_Keyboard_hpp
|
||||
|
||||
#include "../KeyboardMachine.hpp"
|
||||
#include "../Utility/Typer.hpp"
|
||||
|
||||
namespace ZX8081 {
|
||||
|
||||
enum Key: uint16_t {
|
||||
KeyShift = 0x0000 | 0x01, KeyZ = 0x0000 | 0x02, KeyX = 0x0000 | 0x04, KeyC = 0x0000 | 0x08, KeyV = 0x0000 | 0x10,
|
||||
KeyA = 0x0100 | 0x01, KeyS = 0x0100 | 0x02, KeyD = 0x0100 | 0x04, KeyF = 0x0100 | 0x08, KeyG = 0x0100 | 0x10,
|
||||
KeyQ = 0x0200 | 0x01, KeyW = 0x0200 | 0x02, KeyE = 0x0200 | 0x04, KeyR = 0x0200 | 0x08, KeyT = 0x0200 | 0x10,
|
||||
Key1 = 0x0300 | 0x01, Key2 = 0x0300 | 0x02, Key3 = 0x0300 | 0x04, Key4 = 0x0300 | 0x08, Key5 = 0x0300 | 0x10,
|
||||
Key0 = 0x0400 | 0x01, Key9 = 0x0400 | 0x02, Key8 = 0x0400 | 0x04, Key7 = 0x0400 | 0x08, Key6 = 0x0400 | 0x10,
|
||||
KeyP = 0x0500 | 0x01, KeyO = 0x0500 | 0x02, KeyI = 0x0500 | 0x04, KeyU = 0x0500 | 0x08, KeyY = 0x0500 | 0x10,
|
||||
KeyEnter = 0x0600 | 0x01, KeyL = 0x0600 | 0x02, KeyK = 0x0600 | 0x04, KeyJ = 0x0600 | 0x08, KeyH = 0x0600 | 0x10,
|
||||
KeySpace = 0x0700 | 0x01, KeyDot = 0x0700 | 0x02, KeyM = 0x0700 | 0x04, KeyN = 0x0700 | 0x08, KeyB = 0x0700 | 0x10,
|
||||
};
|
||||
|
||||
struct KeyboardMapper: public KeyboardMachine::Machine::KeyboardMapper {
|
||||
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key);
|
||||
};
|
||||
|
||||
class CharacterMapper: public ::Utility::CharacterMapper {
|
||||
public:
|
||||
CharacterMapper(bool is_zx81);
|
||||
uint16_t *sequence_for_character(char character);
|
||||
|
||||
private:
|
||||
bool is_zx81_;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif /* KeyboardMapper_hpp */
|
||||
@@ -31,7 +31,7 @@ Video::Video() :
|
||||
|
||||
void Video::run_for(const HalfCycles half_cycles) {
|
||||
// Just keep a running total of the amount of time that remains owed to the CRT.
|
||||
cycles_since_update_ += (unsigned int)half_cycles.as_int();
|
||||
cycles_since_update_ += static_cast<unsigned int>(half_cycles.as_int());
|
||||
}
|
||||
|
||||
void Video::flush() {
|
||||
@@ -48,7 +48,7 @@ void Video::flush(bool next_sync) {
|
||||
if(line_data_) {
|
||||
// If there is output data queued, output it either if it's being interrupted by
|
||||
// sync, or if we're past its end anyway. Otherwise let it be.
|
||||
unsigned int data_length = (unsigned int)(line_data_pointer_ - line_data_);
|
||||
unsigned int data_length = static_cast<unsigned int>(line_data_pointer_ - line_data_);
|
||||
if(data_length < cycles_since_update_ || next_sync) {
|
||||
unsigned int output_length = std::min(data_length, cycles_since_update_);
|
||||
crt_->output_data(output_length, 1);
|
||||
@@ -58,7 +58,7 @@ void Video::flush(bool next_sync) {
|
||||
}
|
||||
|
||||
// Any pending pixels being dealt with, pad with the white level.
|
||||
uint8_t *colour_pointer = (uint8_t *)crt_->allocate_write_area(1);
|
||||
uint8_t *colour_pointer = static_cast<uint8_t *>(crt_->allocate_write_area(1));
|
||||
if(colour_pointer) *colour_pointer = 0xff;
|
||||
crt_->output_level(cycles_since_update_);
|
||||
}
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
|
||||
#include "../MemoryFuzzer.hpp"
|
||||
#include "../Typer.hpp"
|
||||
#include "../Utility/MemoryFuzzer.hpp"
|
||||
#include "../Utility/Typer.hpp"
|
||||
|
||||
#include "CharacterMapper.hpp"
|
||||
#include "Keyboard.hpp"
|
||||
#include "Video.hpp"
|
||||
|
||||
#include <memory>
|
||||
@@ -135,7 +135,7 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
z80_.set_interrupt_line(false);
|
||||
}
|
||||
if(has_latched_video_byte_) {
|
||||
size_t char_address = (size_t)((address & 0xfe00) | ((latched_video_byte_ & 0x3f) << 3) | line_counter_);
|
||||
size_t char_address = static_cast<size_t>((address & 0xfe00) | ((latched_video_byte_ & 0x3f) << 3) | line_counter_);
|
||||
uint8_t mask = (latched_video_byte_ & 0x80) ? 0x00 : 0xff;
|
||||
if(char_address < ram_base_) {
|
||||
latched_video_byte_ = rom_[char_address & rom_mask_] ^ mask;
|
||||
@@ -155,7 +155,7 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
int next_byte = parser_.get_next_byte(tape_player_.get_tape());
|
||||
if(next_byte != -1) {
|
||||
uint16_t hl = z80_.get_value_of_register(CPU::Z80::Register::HL);
|
||||
ram_[hl & ram_mask_] = (uint8_t)next_byte;
|
||||
ram_[hl & ram_mask_] = static_cast<uint8_t>(next_byte);
|
||||
*cycle.value = 0x00;
|
||||
z80_.set_value_of_register(CPU::Z80::Register::ProgramCounter, tape_return_address_ - 1);
|
||||
|
||||
@@ -249,7 +249,7 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
automatic_tape_motor_start_address_ = 0x0206;
|
||||
automatic_tape_motor_end_address_ = 0x024d;
|
||||
}
|
||||
rom_mask_ = (uint16_t)(rom_.size() - 1);
|
||||
rom_mask_ = static_cast<uint16_t>(rom_.size() - 1);
|
||||
|
||||
switch(target.zx8081.memory_model) {
|
||||
case StaticAnalyser::ZX8081MemoryModel::Unexpanded:
|
||||
@@ -301,9 +301,9 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
|
||||
void set_key_state(uint16_t key, bool isPressed) override final {
|
||||
if(isPressed)
|
||||
key_states_[key >> 8] &= (uint8_t)(~key);
|
||||
key_states_[key >> 8] &= static_cast<uint8_t>(~key);
|
||||
else
|
||||
key_states_[key >> 8] |= (uint8_t)key;
|
||||
key_states_[key >> 8] |= static_cast<uint8_t>(key);
|
||||
}
|
||||
|
||||
void clear_all_keys() override final {
|
||||
@@ -331,6 +331,10 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
HalfCycles get_typer_delay() override final { return Cycles(7000000); }
|
||||
HalfCycles get_typer_frequency() override final { return Cycles(390000); }
|
||||
|
||||
KeyboardMapper &get_keyboard_mapper() override {
|
||||
return keyboard_mapper_;
|
||||
}
|
||||
|
||||
private:
|
||||
CPU::Z80::Processor<ConcreteMachine, false, is_zx81> z80_;
|
||||
|
||||
@@ -350,6 +354,7 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
int line_counter_;
|
||||
|
||||
uint8_t key_states_[8];
|
||||
ZX8081::KeyboardMapper keyboard_mapper_;
|
||||
|
||||
HalfClockReceiver<Storage::Tape::BinaryTapePlayer> tape_player_;
|
||||
Storage::Tape::ZX8081::Parser parser_;
|
||||
|
||||
@@ -22,17 +22,6 @@ enum ROMType: uint8_t {
|
||||
ZX80, ZX81
|
||||
};
|
||||
|
||||
enum Key: uint16_t {
|
||||
KeyShift = 0x0000 | 0x01, KeyZ = 0x0000 | 0x02, KeyX = 0x0000 | 0x04, KeyC = 0x0000 | 0x08, KeyV = 0x0000 | 0x10,
|
||||
KeyA = 0x0100 | 0x01, KeyS = 0x0100 | 0x02, KeyD = 0x0100 | 0x04, KeyF = 0x0100 | 0x08, KeyG = 0x0100 | 0x10,
|
||||
KeyQ = 0x0200 | 0x01, KeyW = 0x0200 | 0x02, KeyE = 0x0200 | 0x04, KeyR = 0x0200 | 0x08, KeyT = 0x0200 | 0x10,
|
||||
Key1 = 0x0300 | 0x01, Key2 = 0x0300 | 0x02, Key3 = 0x0300 | 0x04, Key4 = 0x0300 | 0x08, Key5 = 0x0300 | 0x10,
|
||||
Key0 = 0x0400 | 0x01, Key9 = 0x0400 | 0x02, Key8 = 0x0400 | 0x04, Key7 = 0x0400 | 0x08, Key6 = 0x0400 | 0x10,
|
||||
KeyP = 0x0500 | 0x01, KeyO = 0x0500 | 0x02, KeyI = 0x0500 | 0x04, KeyU = 0x0500 | 0x08, KeyY = 0x0500 | 0x10,
|
||||
KeyEnter = 0x0600 | 0x01, KeyL = 0x0600 | 0x02, KeyK = 0x0600 | 0x04, KeyJ = 0x0600 | 0x08, KeyH = 0x0600 | 0x10,
|
||||
KeySpace = 0x0700 | 0x01, KeyDot = 0x0700 | 0x02, KeyM = 0x0700 | 0x04, KeyN = 0x0700 | 0x08, KeyB = 0x0700 | 0x10,
|
||||
};
|
||||
|
||||
class Machine:
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine,
|
||||
|
||||
@@ -23,12 +23,12 @@ class CRC16 {
|
||||
CRC16(uint16_t polynomial, uint16_t reset_value) :
|
||||
reset_value_(reset_value), value_(reset_value) {
|
||||
for(int c = 0; c < 256; c++) {
|
||||
uint16_t shift_value = (uint16_t)(c << 8);
|
||||
uint16_t shift_value = static_cast<uint16_t>(c << 8);
|
||||
for(int b = 0; b < 8; b++) {
|
||||
uint16_t exclusive_or = (shift_value&0x8000) ? polynomial : 0x0000;
|
||||
shift_value = (uint16_t)(shift_value << 1) ^ exclusive_or;
|
||||
shift_value = static_cast<uint16_t>(shift_value << 1) ^ exclusive_or;
|
||||
}
|
||||
xor_table[c] = (uint16_t)shift_value;
|
||||
xor_table[c] = static_cast<uint16_t>(shift_value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ class CRC16 {
|
||||
|
||||
/// Updates the CRC to include @c byte.
|
||||
inline void add(uint8_t byte) {
|
||||
value_ = (uint16_t)((value_ << 8) ^ xor_table[(value_ >> 8) ^ byte]);
|
||||
value_ = static_cast<uint16_t>((value_ << 8) ^ xor_table[(value_ >> 8) ^ byte]);
|
||||
}
|
||||
|
||||
/// @returns The current value of the CRC.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0800"
|
||||
LastUpgradeVersion = "0900"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -26,6 +26,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
<Testables>
|
||||
@@ -71,10 +72,13 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
enableUBSanitizer = "YES"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
stopOnEveryUBSanitizerIssue = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "NO">
|
||||
<BuildableProductRunnable
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
//
|
||||
|
||||
#import "CSMachine.h"
|
||||
#import "CSKeyboardMachine.h"
|
||||
#import "CSFastLoading.h"
|
||||
|
||||
#import "CSAtari2600.h"
|
||||
@@ -17,3 +16,5 @@
|
||||
#import "CSOpenGLView.h"
|
||||
#import "CSAudioQueue.h"
|
||||
#import "CSBestEffortUpdater.h"
|
||||
|
||||
#include "KeyCodes.h"
|
||||
|
||||
@@ -24,9 +24,9 @@ class Atari2600OptionsPanel: MachinePanel {
|
||||
}
|
||||
|
||||
fileprivate func pushSwitchValues() {
|
||||
atari2600.colourButton = colourButton.state == NSOnState
|
||||
atari2600.leftPlayerDifficultyButton = leftPlayerDifficultyButton.state == NSOnState
|
||||
atari2600.rightPlayerDifficultyButton = rightPlayerDifficultyButton.state == NSOnState
|
||||
atari2600.colourButton = colourButton.state == .on
|
||||
atari2600.leftPlayerDifficultyButton = leftPlayerDifficultyButton.state == .on
|
||||
atari2600.rightPlayerDifficultyButton = rightPlayerDifficultyButton.state == .on
|
||||
}
|
||||
|
||||
@IBAction func optionWasPressed(_ sender: NSButton!) {
|
||||
|
||||
@@ -38,10 +38,10 @@ class MachineDocument:
|
||||
}
|
||||
|
||||
fileprivate var audioQueue: CSAudioQueue! = nil
|
||||
fileprivate var bestEffortUpdater: CSBestEffortUpdater!
|
||||
fileprivate var bestEffortUpdater: CSBestEffortUpdater?
|
||||
|
||||
override var windowNibName: String? {
|
||||
return "MachineDocument"
|
||||
override var windowNibName: NSNib.Name? {
|
||||
return NSNib.Name(rawValue: "MachineDocument")
|
||||
}
|
||||
|
||||
override func windowControllerDidLoadNib(_ aController: NSWindowController) {
|
||||
@@ -69,7 +69,7 @@ class MachineDocument:
|
||||
self.openGLView.window!.makeKeyAndOrderFront(self)
|
||||
|
||||
// start accepting best effort updates
|
||||
self.bestEffortUpdater.delegate = self
|
||||
self.bestEffortUpdater!.delegate = self
|
||||
}
|
||||
|
||||
func machineDidChangeClockRate(_ machine: CSMachine!) {
|
||||
@@ -77,7 +77,7 @@ class MachineDocument:
|
||||
}
|
||||
|
||||
func machineDidChangeClockIsUnlimited(_ machine: CSMachine!) {
|
||||
self.bestEffortUpdater.runAsUnlimited = machine.clockIsUnlimited
|
||||
self.bestEffortUpdater?.runAsUnlimited = machine.clockIsUnlimited
|
||||
}
|
||||
|
||||
fileprivate func setupClockRate() {
|
||||
@@ -91,15 +91,21 @@ class MachineDocument:
|
||||
self.machine.setAudioSamplingRate(selectedSamplingRate, bufferSize:audioQueue.preferredBufferSize)
|
||||
}
|
||||
|
||||
self.bestEffortUpdater.clockRate = self.machine.clockRate
|
||||
self.bestEffortUpdater?.clockRate = self.machine.clockRate
|
||||
}
|
||||
|
||||
override func close() {
|
||||
bestEffortUpdater.flush()
|
||||
optionsPanel?.setIsVisible(false)
|
||||
optionsPanel = nil
|
||||
|
||||
openGLView.delegate = nil
|
||||
bestEffortUpdater!.delegate = nil
|
||||
bestEffortUpdater = nil
|
||||
|
||||
actionLock.lock()
|
||||
drawLock.lock()
|
||||
machine = nil
|
||||
openGLView.invalidate()
|
||||
openGLView.openGLContext!.makeCurrentContext()
|
||||
actionLock.unlock()
|
||||
drawLock.unlock()
|
||||
|
||||
@@ -114,7 +120,7 @@ class MachineDocument:
|
||||
analysis.apply(to: self.machine)
|
||||
|
||||
if let optionsPanelNibName = analysis.optionsPanelNibName {
|
||||
Bundle.main.loadNibNamed(optionsPanelNibName, owner: self, topLevelObjects: nil)
|
||||
Bundle.main.loadNibNamed(NSNib.Name(rawValue: optionsPanelNibName), owner: self, topLevelObjects: nil)
|
||||
self.optionsPanel.machine = self.machine
|
||||
showOptions(self)
|
||||
}
|
||||
@@ -131,8 +137,8 @@ class MachineDocument:
|
||||
|
||||
// MARK: the pasteboard
|
||||
func paste(_ sender: AnyObject!) {
|
||||
let pasteboard = NSPasteboard.general()
|
||||
if let string = pasteboard.string(forType: NSPasteboardTypeString) {
|
||||
let pasteboard = NSPasteboard.general
|
||||
if let string = pasteboard.string(forType: .string) {
|
||||
self.machine.paste(string)
|
||||
}
|
||||
}
|
||||
@@ -143,24 +149,28 @@ class MachineDocument:
|
||||
}
|
||||
|
||||
func runForNumberOfCycles(_ numberOfCycles: Int32) {
|
||||
let cyclesToRunFor = min(numberOfCycles, Int32(bestEffortUpdater.clockRate / 10))
|
||||
if actionLock.try() {
|
||||
self.machine.runForNumber(ofCycles: cyclesToRunFor)
|
||||
actionLock.unlock()
|
||||
if let bestEffortUpdater = bestEffortUpdater {
|
||||
let cyclesToRunFor = min(numberOfCycles, Int32(bestEffortUpdater.clockRate / 10))
|
||||
if actionLock.try() {
|
||||
self.machine.runForNumber(ofCycles: cyclesToRunFor)
|
||||
actionLock.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: CSAudioQueueDelegate
|
||||
final func audioQueueIsRunningDry(_ audioQueue: CSAudioQueue) {
|
||||
bestEffortUpdater.update()
|
||||
bestEffortUpdater?.update()
|
||||
}
|
||||
|
||||
// MARK: CSOpenGLViewDelegate
|
||||
final func openGLView(_ view: CSOpenGLView, drawViewOnlyIfDirty onlyIfDirty: Bool) {
|
||||
bestEffortUpdater.update()
|
||||
if drawLock.try() {
|
||||
self.machine.drawView(forPixelSize: view.backingSize, onlyIfDirty: onlyIfDirty)
|
||||
drawLock.unlock()
|
||||
if let bestEffortUpdater = bestEffortUpdater {
|
||||
bestEffortUpdater.update()
|
||||
if drawLock.try() {
|
||||
self.machine.drawView(forPixelSize: view.backingSize, onlyIfDirty: onlyIfDirty)
|
||||
drawLock.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,48 +187,22 @@ class MachineDocument:
|
||||
}
|
||||
|
||||
// MARK: Input management
|
||||
fileprivate func withKeyboardMachine(_ action: (CSKeyboardMachine) -> ()) {
|
||||
if let keyboardMachine = self.machine as? CSKeyboardMachine {
|
||||
action(keyboardMachine)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func withJoystickMachine(_ action: (CSJoystickMachine) -> ()) {
|
||||
if let joystickMachine = self.machine as? CSJoystickMachine {
|
||||
action(joystickMachine)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func sendJoystickEvent(_ machine: CSJoystickMachine, keyCode: UInt16, isPressed: Bool) {
|
||||
switch keyCode {
|
||||
case 123: machine.setDirection(.left, onPad: 0, isPressed: isPressed)
|
||||
case 126: machine.setDirection(.up, onPad: 0, isPressed: isPressed)
|
||||
case 124: machine.setDirection(.right, onPad: 0, isPressed: isPressed)
|
||||
case 125: machine.setDirection(.down, onPad: 0, isPressed: isPressed)
|
||||
default: machine.setButtonAt(0, onPad: 0, isPressed: isPressed)
|
||||
}
|
||||
}
|
||||
|
||||
func windowDidResignKey(_ notification: Notification) {
|
||||
self.withKeyboardMachine { $0.clearAllKeys() }
|
||||
self.machine.clearAllKeys()
|
||||
}
|
||||
|
||||
func keyDown(_ event: NSEvent) {
|
||||
self.withKeyboardMachine { $0.setKey(event.keyCode, isPressed: true) }
|
||||
self.withJoystickMachine { sendJoystickEvent($0, keyCode: event.keyCode, isPressed: true) }
|
||||
self.machine.setKey(event.keyCode, isPressed: true)
|
||||
}
|
||||
|
||||
func keyUp(_ event: NSEvent) {
|
||||
self.withKeyboardMachine { $0.setKey(event.keyCode, isPressed: false) }
|
||||
self.withJoystickMachine { sendJoystickEvent($0, keyCode: event.keyCode, isPressed: false) }
|
||||
self.machine.setKey(event.keyCode, isPressed: false)
|
||||
}
|
||||
|
||||
func flagsChanged(_ newModifiers: NSEvent) {
|
||||
self.withKeyboardMachine {
|
||||
$0.setKey(VK_Shift, isPressed: newModifiers.modifierFlags.contains(.shift))
|
||||
$0.setKey(VK_Control, isPressed: newModifiers.modifierFlags.contains(.control))
|
||||
$0.setKey(VK_Command, isPressed: newModifiers.modifierFlags.contains(.command))
|
||||
$0.setKey(VK_Option, isPressed: newModifiers.modifierFlags.contains(.option))
|
||||
}
|
||||
self.machine.setKey(VK_Shift, isPressed: newModifiers.modifierFlags.contains(.shift))
|
||||
self.machine.setKey(VK_Control, isPressed: newModifiers.modifierFlags.contains(.control))
|
||||
self.machine.setKey(VK_Command, isPressed: newModifiers.modifierFlags.contains(.command))
|
||||
self.machine.setKey(VK_Option, isPressed: newModifiers.modifierFlags.contains(.option))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ class MachinePanel: NSPanel {
|
||||
@IBOutlet var fastLoadingButton: NSButton?
|
||||
@IBAction func setFastLoading(_ sender: NSButton!) {
|
||||
if let fastLoadingMachine = machine as? CSFastLoading {
|
||||
let useFastLoadingHack = sender.state == NSOnState
|
||||
let useFastLoadingHack = sender.state == .on
|
||||
fastLoadingMachine.useFastLoadingHack = useFastLoadingHack
|
||||
UserDefaults.standard.set(useFastLoadingHack, forKey: fastLoadingUserDefaultsKey)
|
||||
}
|
||||
@@ -39,7 +39,7 @@ class MachinePanel: NSPanel {
|
||||
if let fastLoadingMachine = machine as? CSFastLoading {
|
||||
let useFastLoadingHack = standardUserDefaults.bool(forKey: self.fastLoadingUserDefaultsKey)
|
||||
fastLoadingMachine.useFastLoadingHack = useFastLoadingHack
|
||||
self.fastLoadingButton?.state = useFastLoadingHack ? NSOnState : NSOffState
|
||||
self.fastLoadingButton?.state = useFastLoadingHack ? .on : .off
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ class ZX8081OptionsPanel: MachinePanel {
|
||||
get { return prefixedUserDefaultsKey("automaticTapeMotorControl") }
|
||||
}
|
||||
@IBAction func setAutomaticTapeMotorConrol(_ sender: NSButton!) {
|
||||
let isEnabled = sender.state == NSOnState
|
||||
let isEnabled = sender.state == .on
|
||||
UserDefaults.standard.set(isEnabled, forKey: self.automaticTapeMotorControlDefaultsKey)
|
||||
self.playOrPauseTapeButton.isEnabled = !isEnabled
|
||||
self.zx8081.useAutomaticTapeMotorControl = isEnabled
|
||||
@@ -42,7 +42,7 @@ class ZX8081OptionsPanel: MachinePanel {
|
||||
])
|
||||
|
||||
let automaticTapeMotorControlIsEnabled = standardUserDefaults.bool(forKey: self.automaticTapeMotorControlDefaultsKey)
|
||||
self.automaticTapeMotorControlButton.state = automaticTapeMotorControlIsEnabled ? NSOnState : NSOffState
|
||||
self.automaticTapeMotorControlButton.state = automaticTapeMotorControlIsEnabled ? .on : .off
|
||||
self.playOrPauseTapeButton.isEnabled = !automaticTapeMotorControlIsEnabled
|
||||
self.zx8081.useAutomaticTapeMotorControl = automaticTapeMotorControlIsEnabled
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
//
|
||||
// CSJoystickMachine.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/10/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
typedef NS_ENUM(NSInteger, CSJoystickDirection)
|
||||
{
|
||||
CSJoystickDirectionUp,
|
||||
CSJoystickDirectionDown,
|
||||
CSJoystickDirectionLeft,
|
||||
CSJoystickDirectionRight
|
||||
};
|
||||
|
||||
@protocol CSJoystickMachine <NSObject>
|
||||
|
||||
- (void)setButtonAtIndex:(NSUInteger)button onPad:(NSUInteger)pad isPressed:(BOOL)isPressed;
|
||||
- (void)setDirection:(CSJoystickDirection)direction onPad:(NSUInteger)pad isPressed:(BOOL)isPressed;
|
||||
|
||||
@end
|
||||
@@ -1,16 +0,0 @@
|
||||
//
|
||||
// CSKeyboardMachine.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 05/06/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import "KeyCodes.h"
|
||||
|
||||
@protocol CSKeyboardMachine <NSObject>
|
||||
|
||||
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed;
|
||||
- (void)clearAllKeys;
|
||||
|
||||
@end
|
||||
@@ -35,6 +35,9 @@
|
||||
- (void)setView:(CSOpenGLView *)view aspectRatio:(float)aspectRatio;
|
||||
- (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty;
|
||||
|
||||
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed;
|
||||
- (void)clearAllKeys;
|
||||
|
||||
@property (nonatomic, strong) CSAudioQueue *audioQueue;
|
||||
@property (nonatomic, readonly) CSOpenGLView *view;
|
||||
@property (nonatomic, weak) id<CSMachineDelegate> delegate;
|
||||
|
||||
@@ -10,8 +10,11 @@
|
||||
#import "CSMachine+Subclassing.h"
|
||||
#import "CSMachine+Target.h"
|
||||
|
||||
#include "KeyCodes.h"
|
||||
#include "Typer.hpp"
|
||||
#include "ConfigurationTarget.hpp"
|
||||
#include "JoystickMachine.hpp"
|
||||
#include "KeyboardMachine.hpp"
|
||||
|
||||
@interface CSMachine()
|
||||
- (void)speaker:(Outputs::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length;
|
||||
@@ -19,26 +22,38 @@
|
||||
- (void)machineDidChangeClockIsUnlimited;
|
||||
@end
|
||||
|
||||
struct SpeakerDelegate: public Outputs::Speaker::Delegate {
|
||||
__weak CSMachine *machine;
|
||||
struct LockProtectedDelegate {
|
||||
// Contractual promise is: machine — the pointer **and** the object ** — may be accessed only
|
||||
// in sections protected by the machineAccessLock;
|
||||
NSLock *machineAccessLock;
|
||||
__unsafe_unretained CSMachine *machine;
|
||||
};
|
||||
|
||||
struct SpeakerDelegate: public Outputs::Speaker::Delegate, public LockProtectedDelegate {
|
||||
void speaker_did_complete_samples(Outputs::Speaker *speaker, const std::vector<int16_t> &buffer) {
|
||||
[machineAccessLock lock];
|
||||
[machine speaker:speaker didCompleteSamples:buffer.data() length:(int)buffer.size()];
|
||||
[machineAccessLock unlock];
|
||||
}
|
||||
};
|
||||
|
||||
struct MachineDelegate: CRTMachine::Machine::Delegate {
|
||||
__weak CSMachine *machine;
|
||||
struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDelegate {
|
||||
void machine_did_change_clock_rate(CRTMachine::Machine *sender) {
|
||||
[machineAccessLock lock];
|
||||
[machine machineDidChangeClockRate];
|
||||
[machineAccessLock unlock];
|
||||
}
|
||||
void machine_did_change_clock_is_unlimited(CRTMachine::Machine *sender) {
|
||||
[machineAccessLock lock];
|
||||
[machine machineDidChangeClockIsUnlimited];
|
||||
[machineAccessLock unlock];
|
||||
}
|
||||
};
|
||||
|
||||
@implementation CSMachine {
|
||||
SpeakerDelegate _speakerDelegate;
|
||||
MachineDelegate _machineDelegate;
|
||||
NSLock *_delegateMachineAccessLock;
|
||||
CRTMachine::Machine *_machine;
|
||||
}
|
||||
|
||||
@@ -46,8 +61,12 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
_machine = (CRTMachine::Machine *)machine;
|
||||
_delegateMachineAccessLock = [[NSLock alloc] init];
|
||||
|
||||
_machineDelegate.machine = self;
|
||||
_speakerDelegate.machine = self;
|
||||
_machineDelegate.machineAccessLock = _delegateMachineAccessLock;
|
||||
_speakerDelegate.machineAccessLock = _delegateMachineAccessLock;
|
||||
|
||||
_machine->set_delegate(&_machineDelegate);
|
||||
}
|
||||
@@ -67,6 +86,17 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
// The two delegate's references to this machine are nilled out here because close_output may result
|
||||
// in a data flush, which might cause an audio callback, which could cause the audio queue to decide
|
||||
// that it's out of data, resulting in an attempt further to run the machine while it is dealloc'ing.
|
||||
//
|
||||
// They are nilled inside an explicit lock because that allows the delegates to protect their entire
|
||||
// call into the machine, not just the pointer access.
|
||||
[_delegateMachineAccessLock lock];
|
||||
_machineDelegate.machine = nil;
|
||||
_speakerDelegate.machine = nil;
|
||||
[_delegateMachineAccessLock unlock];
|
||||
|
||||
[_view performWithGLContext:^{
|
||||
@synchronized(self) {
|
||||
_machine->close_output();
|
||||
@@ -77,8 +107,7 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
|
||||
- (float)idealSamplingRateFromRange:(NSRange)range {
|
||||
@synchronized(self) {
|
||||
std::shared_ptr<Outputs::Speaker> speaker = _machine->get_speaker();
|
||||
if(speaker)
|
||||
{
|
||||
if(speaker) {
|
||||
return speaker->get_ideal_clock_rate_in_range((float)range.location, (float)(range.location + range.length));
|
||||
}
|
||||
return 0;
|
||||
@@ -94,8 +123,7 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
|
||||
- (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(float)sampleRate bufferSize:(NSUInteger)bufferSize {
|
||||
@synchronized(self) {
|
||||
std::shared_ptr<Outputs::Speaker> speaker = _machine->get_speaker();
|
||||
if(speaker)
|
||||
{
|
||||
if(speaker) {
|
||||
speaker->set_output_rate(sampleRate, (int)bufferSize);
|
||||
speaker->set_delegate(delegate);
|
||||
return YES;
|
||||
@@ -158,4 +186,106 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed {
|
||||
auto keyboard_machine = dynamic_cast<KeyboardMachine::Machine *>(_machine);
|
||||
if(keyboard_machine) {
|
||||
@synchronized(self) {
|
||||
Inputs::Keyboard &keyboard = keyboard_machine->get_keyboard();
|
||||
|
||||
// Connect the Carbon-era Mac keyboard scancodes to Clock Signal's 'universal' enumeration in order
|
||||
// to pass into the platform-neutral realm.
|
||||
#define BIND(source, dest) case source: keyboard.set_key_pressed(Inputs::Keyboard::Key::dest, isPressed); break
|
||||
switch(key) {
|
||||
BIND(VK_ANSI_0, k0); BIND(VK_ANSI_1, k1); BIND(VK_ANSI_2, k2); BIND(VK_ANSI_3, k3); BIND(VK_ANSI_4, k4);
|
||||
BIND(VK_ANSI_5, k5); BIND(VK_ANSI_6, k6); BIND(VK_ANSI_7, k7); BIND(VK_ANSI_8, k8); BIND(VK_ANSI_9, k9);
|
||||
|
||||
BIND(VK_ANSI_Q, Q); BIND(VK_ANSI_W, W); BIND(VK_ANSI_E, E); BIND(VK_ANSI_R, R); BIND(VK_ANSI_T, T);
|
||||
BIND(VK_ANSI_Y, Y); BIND(VK_ANSI_U, U); BIND(VK_ANSI_I, I); BIND(VK_ANSI_O, O); BIND(VK_ANSI_P, P);
|
||||
|
||||
BIND(VK_ANSI_A, A); BIND(VK_ANSI_S, S); BIND(VK_ANSI_D, D); BIND(VK_ANSI_F, F); BIND(VK_ANSI_G, G);
|
||||
BIND(VK_ANSI_H, H); BIND(VK_ANSI_J, J); BIND(VK_ANSI_K, K); BIND(VK_ANSI_L, L);
|
||||
|
||||
BIND(VK_ANSI_Z, Z); BIND(VK_ANSI_X, X); BIND(VK_ANSI_C, C); BIND(VK_ANSI_V, V);
|
||||
BIND(VK_ANSI_B, B); BIND(VK_ANSI_N, N); BIND(VK_ANSI_M, M);
|
||||
|
||||
BIND(VK_F1, F1); BIND(VK_F2, F2); BIND(VK_F3, F3); BIND(VK_F4, F4);
|
||||
BIND(VK_F5, F5); BIND(VK_F6, F6); BIND(VK_F7, F7); BIND(VK_F8, F8);
|
||||
BIND(VK_F9, F9); BIND(VK_F10, F10); BIND(VK_F11, F11); BIND(VK_F12, F12);
|
||||
|
||||
BIND(VK_ANSI_Keypad0, KeyPad0); BIND(VK_ANSI_Keypad1, KeyPad1); BIND(VK_ANSI_Keypad2, KeyPad2);
|
||||
BIND(VK_ANSI_Keypad3, KeyPad3); BIND(VK_ANSI_Keypad4, KeyPad4); BIND(VK_ANSI_Keypad5, KeyPad5);
|
||||
BIND(VK_ANSI_Keypad6, KeyPad6); BIND(VK_ANSI_Keypad7, KeyPad7); BIND(VK_ANSI_Keypad8, KeyPad8);
|
||||
BIND(VK_ANSI_Keypad9, KeyPad9);
|
||||
|
||||
BIND(VK_ANSI_Equal, Equals); BIND(VK_ANSI_Minus, Hyphen);
|
||||
BIND(VK_ANSI_RightBracket, CloseSquareBracket); BIND(VK_ANSI_LeftBracket, OpenSquareBracket);
|
||||
BIND(VK_ANSI_Quote, Quote); BIND(VK_ANSI_Grave, BackTick);
|
||||
|
||||
BIND(VK_ANSI_Semicolon, Semicolon);
|
||||
BIND(VK_ANSI_Backslash, BackSlash); BIND(VK_ANSI_Slash, ForwardSlash);
|
||||
BIND(VK_ANSI_Comma, Comma); BIND(VK_ANSI_Period, FullStop);
|
||||
|
||||
BIND(VK_ANSI_KeypadDecimal, KeyPadDecimalPoint); BIND(VK_ANSI_KeypadEquals, KeyPadEquals);
|
||||
BIND(VK_ANSI_KeypadMultiply, KeyPadAsterisk); BIND(VK_ANSI_KeypadDivide, KeyPadSlash);
|
||||
BIND(VK_ANSI_KeypadPlus, KeyPadPlus); BIND(VK_ANSI_KeypadMinus, KeyPadMinus);
|
||||
BIND(VK_ANSI_KeypadClear, KeyPadDelete); BIND(VK_ANSI_KeypadEnter, KeyPadEnter);
|
||||
|
||||
BIND(VK_Return, Enter); BIND(VK_Tab, Tab);
|
||||
BIND(VK_Space, Space); BIND(VK_Delete, BackSpace);
|
||||
BIND(VK_Control, LeftControl); BIND(VK_Option, LeftOption);
|
||||
BIND(VK_Command, LeftMeta); BIND(VK_Shift, LeftShift);
|
||||
BIND(VK_RightControl, RightControl); BIND(VK_RightOption, RightOption);
|
||||
BIND(VK_Escape, Escape); BIND(VK_CapsLock, CapsLock);
|
||||
BIND(VK_Home, Home); BIND(VK_End, End);
|
||||
BIND(VK_PageUp, PageUp); BIND(VK_PageDown, PageDown);
|
||||
|
||||
BIND(VK_RightShift, RightShift);
|
||||
BIND(VK_Help, Help);
|
||||
BIND(VK_ForwardDelete, Delete);
|
||||
|
||||
BIND(VK_LeftArrow, Left); BIND(VK_RightArrow, Right);
|
||||
BIND(VK_DownArrow, Down); BIND(VK_UpArrow, Up);
|
||||
}
|
||||
#undef BIND
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto joystick_machine = dynamic_cast<JoystickMachine::Machine *>(_machine);
|
||||
if(joystick_machine) {
|
||||
@synchronized(self) {
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> &joysticks = joystick_machine->get_joysticks();
|
||||
if(!joysticks.empty()) {
|
||||
switch(key) {
|
||||
case VK_LeftArrow: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Left, isPressed); break;
|
||||
case VK_RightArrow: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Right, isPressed); break;
|
||||
case VK_UpArrow: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Up, isPressed); break;
|
||||
case VK_DownArrow: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Down, isPressed); break;
|
||||
default:
|
||||
joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Fire, isPressed); break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clearAllKeys {
|
||||
auto keyboard_machine = dynamic_cast<KeyboardMachine::Machine *>(_machine);
|
||||
if(keyboard_machine) {
|
||||
@synchronized(self) {
|
||||
keyboard_machine->get_keyboard().reset_all_keys();
|
||||
}
|
||||
}
|
||||
|
||||
auto joystick_machine = dynamic_cast<JoystickMachine::Machine *>(_machine);
|
||||
if(joystick_machine) {
|
||||
@synchronized(self) {
|
||||
for(auto &joystick : joystick_machine->get_joysticks()) {
|
||||
joystick->reset_all_inputs();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -7,9 +7,8 @@
|
||||
//
|
||||
|
||||
#import "CSMachine.h"
|
||||
#import "CSKeyboardMachine.h"
|
||||
|
||||
@interface CSAmstradCPC : CSMachine <CSKeyboardMachine>
|
||||
@interface CSAmstradCPC : CSMachine
|
||||
|
||||
- (instancetype)init;
|
||||
|
||||
|
||||
@@ -55,98 +55,4 @@
|
||||
|
||||
- (NSString *)userDefaultsPrefix { return @"amstradCPC"; }
|
||||
|
||||
#pragma mark - Keyboard Mapping
|
||||
|
||||
- (void)clearAllKeys {
|
||||
@synchronized(self) {
|
||||
_amstradCPC->clear_all_keys();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed {
|
||||
@synchronized(self) {
|
||||
switch(key) {
|
||||
case VK_ANSI_0: _amstradCPC->set_key_state(AmstradCPC::Key::Key0, isPressed); break;
|
||||
case VK_ANSI_1: _amstradCPC->set_key_state(AmstradCPC::Key::Key1, isPressed); break;
|
||||
case VK_ANSI_2: _amstradCPC->set_key_state(AmstradCPC::Key::Key2, isPressed); break;
|
||||
case VK_ANSI_3: _amstradCPC->set_key_state(AmstradCPC::Key::Key3, isPressed); break;
|
||||
case VK_ANSI_4: _amstradCPC->set_key_state(AmstradCPC::Key::Key4, isPressed); break;
|
||||
case VK_ANSI_5: _amstradCPC->set_key_state(AmstradCPC::Key::Key5, isPressed); break;
|
||||
case VK_ANSI_6: _amstradCPC->set_key_state(AmstradCPC::Key::Key6, isPressed); break;
|
||||
case VK_ANSI_7: _amstradCPC->set_key_state(AmstradCPC::Key::Key7, isPressed); break;
|
||||
case VK_ANSI_8: _amstradCPC->set_key_state(AmstradCPC::Key::Key8, isPressed); break;
|
||||
case VK_ANSI_9: _amstradCPC->set_key_state(AmstradCPC::Key::Key9, isPressed); break;
|
||||
|
||||
case VK_ANSI_Q: _amstradCPC->set_key_state(AmstradCPC::Key::KeyQ, isPressed); break;
|
||||
case VK_ANSI_W: _amstradCPC->set_key_state(AmstradCPC::Key::KeyW, isPressed); break;
|
||||
case VK_ANSI_E: _amstradCPC->set_key_state(AmstradCPC::Key::KeyE, isPressed); break;
|
||||
case VK_ANSI_R: _amstradCPC->set_key_state(AmstradCPC::Key::KeyR, isPressed); break;
|
||||
case VK_ANSI_T: _amstradCPC->set_key_state(AmstradCPC::Key::KeyT, isPressed); break;
|
||||
case VK_ANSI_Y: _amstradCPC->set_key_state(AmstradCPC::Key::KeyY, isPressed); break;
|
||||
case VK_ANSI_U: _amstradCPC->set_key_state(AmstradCPC::Key::KeyU, isPressed); break;
|
||||
case VK_ANSI_I: _amstradCPC->set_key_state(AmstradCPC::Key::KeyI, isPressed); break;
|
||||
case VK_ANSI_O: _amstradCPC->set_key_state(AmstradCPC::Key::KeyO, isPressed); break;
|
||||
case VK_ANSI_P: _amstradCPC->set_key_state(AmstradCPC::Key::KeyP, isPressed); break;
|
||||
case VK_ANSI_A: _amstradCPC->set_key_state(AmstradCPC::Key::KeyA, isPressed); break;
|
||||
case VK_ANSI_S: _amstradCPC->set_key_state(AmstradCPC::Key::KeyS, isPressed); break;
|
||||
case VK_ANSI_D: _amstradCPC->set_key_state(AmstradCPC::Key::KeyD, isPressed); break;
|
||||
case VK_ANSI_F: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF, isPressed); break;
|
||||
case VK_ANSI_G: _amstradCPC->set_key_state(AmstradCPC::Key::KeyG, isPressed); break;
|
||||
case VK_ANSI_H: _amstradCPC->set_key_state(AmstradCPC::Key::KeyH, isPressed); break;
|
||||
case VK_ANSI_J: _amstradCPC->set_key_state(AmstradCPC::Key::KeyJ, isPressed); break;
|
||||
case VK_ANSI_K: _amstradCPC->set_key_state(AmstradCPC::Key::KeyK, isPressed); break;
|
||||
case VK_ANSI_L: _amstradCPC->set_key_state(AmstradCPC::Key::KeyL, isPressed); break;
|
||||
case VK_ANSI_Z: _amstradCPC->set_key_state(AmstradCPC::Key::KeyZ, isPressed); break;
|
||||
case VK_ANSI_X: _amstradCPC->set_key_state(AmstradCPC::Key::KeyX, isPressed); break;
|
||||
case VK_ANSI_C: _amstradCPC->set_key_state(AmstradCPC::Key::KeyC, isPressed); break;
|
||||
case VK_ANSI_V: _amstradCPC->set_key_state(AmstradCPC::Key::KeyV, isPressed); break;
|
||||
case VK_ANSI_B: _amstradCPC->set_key_state(AmstradCPC::Key::KeyB, isPressed); break;
|
||||
case VK_ANSI_N: _amstradCPC->set_key_state(AmstradCPC::Key::KeyN, isPressed); break;
|
||||
case VK_ANSI_M: _amstradCPC->set_key_state(AmstradCPC::Key::KeyM, isPressed); break;
|
||||
|
||||
case VK_Space: _amstradCPC->set_key_state(AmstradCPC::Key::KeySpace, isPressed); break;
|
||||
case VK_ANSI_Grave: _amstradCPC->set_key_state(AmstradCPC::Key::KeyCopy, isPressed); break;
|
||||
case VK_Return: _amstradCPC->set_key_state(AmstradCPC::Key::KeyReturn, isPressed); break;
|
||||
case VK_ANSI_Minus: _amstradCPC->set_key_state(AmstradCPC::Key::KeyMinus, isPressed); break;
|
||||
|
||||
case VK_RightArrow: _amstradCPC->set_key_state(AmstradCPC::Key::KeyRight, isPressed); break;
|
||||
case VK_LeftArrow: _amstradCPC->set_key_state(AmstradCPC::Key::KeyLeft, isPressed); break;
|
||||
case VK_DownArrow: _amstradCPC->set_key_state(AmstradCPC::Key::KeyDown, isPressed); break;
|
||||
case VK_UpArrow: _amstradCPC->set_key_state(AmstradCPC::Key::KeyUp, isPressed); break;
|
||||
|
||||
case VK_Delete: _amstradCPC->set_key_state(AmstradCPC::Key::KeyDelete, isPressed); break;
|
||||
case VK_Escape: _amstradCPC->set_key_state(AmstradCPC::Key::KeyEscape, isPressed); break;
|
||||
|
||||
case VK_ANSI_Comma: _amstradCPC->set_key_state(AmstradCPC::Key::KeyComma, isPressed); break;
|
||||
case VK_ANSI_Period: _amstradCPC->set_key_state(AmstradCPC::Key::KeyFullStop, isPressed); break;
|
||||
|
||||
case VK_ANSI_Semicolon:
|
||||
_amstradCPC->set_key_state(AmstradCPC::Key::KeySemicolon, isPressed); break;
|
||||
case VK_ANSI_Quote: _amstradCPC->set_key_state(AmstradCPC::Key::KeyColon, isPressed); break;
|
||||
|
||||
case VK_ANSI_Slash: _amstradCPC->set_key_state(AmstradCPC::Key::KeyForwardSlash, isPressed); break;
|
||||
case VK_ANSI_Backslash: _amstradCPC->set_key_state(AmstradCPC::Key::KeyBackSlash, isPressed); break;
|
||||
|
||||
case VK_Shift: _amstradCPC->set_key_state(AmstradCPC::Key::KeyShift, isPressed); break;
|
||||
case VK_Control: _amstradCPC->set_key_state(AmstradCPC::Key::KeyControl, isPressed); break;
|
||||
|
||||
case VK_F1: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF1, isPressed); break;
|
||||
case VK_F2: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF2, isPressed); break;
|
||||
case VK_F3: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF3, isPressed); break;
|
||||
case VK_F4: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF4, isPressed); break;
|
||||
case VK_F5: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF5, isPressed); break;
|
||||
case VK_F6: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF6, isPressed); break;
|
||||
case VK_F7: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF7, isPressed); break;
|
||||
case VK_F8: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF8, isPressed); break;
|
||||
case VK_F9: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF9, isPressed); break;
|
||||
case VK_F10: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF0, isPressed); break;
|
||||
case VK_F12: _amstradCPC->set_key_state(AmstradCPC::Key::KeyFDot, isPressed); break;
|
||||
|
||||
default:
|
||||
// printf("%02x\n", key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -8,9 +8,8 @@
|
||||
|
||||
#include "CSMachine.h"
|
||||
#include "Atari2600Inputs.h"
|
||||
#import "CSJoystickMachine.h"
|
||||
|
||||
@interface CSAtari2600 : CSMachine <CSJoystickMachine>
|
||||
@interface CSAtari2600 : CSMachine
|
||||
|
||||
- (instancetype)init;
|
||||
|
||||
|
||||
@@ -24,26 +24,6 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setDirection:(CSJoystickDirection)direction onPad:(NSUInteger)pad isPressed:(BOOL)isPressed {
|
||||
Atari2600DigitalInput input;
|
||||
switch(direction)
|
||||
{
|
||||
case CSJoystickDirectionUp: input = pad ? Atari2600DigitalInputJoy2Up : Atari2600DigitalInputJoy1Up; break;
|
||||
case CSJoystickDirectionDown: input = pad ? Atari2600DigitalInputJoy2Down : Atari2600DigitalInputJoy1Down; break;
|
||||
case CSJoystickDirectionLeft: input = pad ? Atari2600DigitalInputJoy2Left : Atari2600DigitalInputJoy1Left; break;
|
||||
case CSJoystickDirectionRight: input = pad ? Atari2600DigitalInputJoy2Right : Atari2600DigitalInputJoy1Right; break;
|
||||
}
|
||||
@synchronized(self) {
|
||||
_atari2600->set_digital_input(input, isPressed ? true : false);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setButtonAtIndex:(NSUInteger)button onPad:(NSUInteger)pad isPressed:(BOOL)isPressed {
|
||||
@synchronized(self) {
|
||||
_atari2600->set_digital_input(pad ? Atari2600DigitalInputJoy2Fire : Atari2600DigitalInputJoy1Fire, isPressed ? true : false);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setResetLineEnabled:(BOOL)enabled {
|
||||
@synchronized(self) {
|
||||
_atari2600->set_reset_switch(enabled ? true : false);
|
||||
|
||||
@@ -7,10 +7,9 @@
|
||||
//
|
||||
|
||||
#import "CSMachine.h"
|
||||
#import "CSKeyboardMachine.h"
|
||||
#import "CSFastLoading.h"
|
||||
|
||||
@interface CSElectron : CSMachine <CSKeyboardMachine, CSFastLoading>
|
||||
@interface CSElectron : CSMachine <CSFastLoading>
|
||||
|
||||
- (instancetype)init;
|
||||
|
||||
|
||||
@@ -57,94 +57,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Keyboard Mapping
|
||||
|
||||
- (void)clearAllKeys {
|
||||
@synchronized(self) {
|
||||
_electron->clear_all_keys();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed {
|
||||
@synchronized(self) {
|
||||
switch(key)
|
||||
{
|
||||
case VK_ANSI_0: _electron->set_key_state(Electron::Key::Key0, isPressed); break;
|
||||
case VK_ANSI_1: _electron->set_key_state(Electron::Key::Key1, isPressed); break;
|
||||
case VK_ANSI_2: _electron->set_key_state(Electron::Key::Key2, isPressed); break;
|
||||
case VK_ANSI_3: _electron->set_key_state(Electron::Key::Key3, isPressed); break;
|
||||
case VK_ANSI_4: _electron->set_key_state(Electron::Key::Key4, isPressed); break;
|
||||
case VK_ANSI_5: _electron->set_key_state(Electron::Key::Key5, isPressed); break;
|
||||
case VK_ANSI_6: _electron->set_key_state(Electron::Key::Key6, isPressed); break;
|
||||
case VK_ANSI_7: _electron->set_key_state(Electron::Key::Key7, isPressed); break;
|
||||
case VK_ANSI_8: _electron->set_key_state(Electron::Key::Key8, isPressed); break;
|
||||
case VK_ANSI_9: _electron->set_key_state(Electron::Key::Key9, isPressed); break;
|
||||
|
||||
case VK_ANSI_Q: _electron->set_key_state(Electron::Key::KeyQ, isPressed); break;
|
||||
case VK_ANSI_W: _electron->set_key_state(Electron::Key::KeyW, isPressed); break;
|
||||
case VK_ANSI_E: _electron->set_key_state(Electron::Key::KeyE, isPressed); break;
|
||||
case VK_ANSI_R: _electron->set_key_state(Electron::Key::KeyR, isPressed); break;
|
||||
case VK_ANSI_T: _electron->set_key_state(Electron::Key::KeyT, isPressed); break;
|
||||
case VK_ANSI_Y: _electron->set_key_state(Electron::Key::KeyY, isPressed); break;
|
||||
case VK_ANSI_U: _electron->set_key_state(Electron::Key::KeyU, isPressed); break;
|
||||
case VK_ANSI_I: _electron->set_key_state(Electron::Key::KeyI, isPressed); break;
|
||||
case VK_ANSI_O: _electron->set_key_state(Electron::Key::KeyO, isPressed); break;
|
||||
case VK_ANSI_P: _electron->set_key_state(Electron::Key::KeyP, isPressed); break;
|
||||
case VK_ANSI_A: _electron->set_key_state(Electron::Key::KeyA, isPressed); break;
|
||||
case VK_ANSI_S: _electron->set_key_state(Electron::Key::KeyS, isPressed); break;
|
||||
case VK_ANSI_D: _electron->set_key_state(Electron::Key::KeyD, isPressed); break;
|
||||
case VK_ANSI_F: _electron->set_key_state(Electron::Key::KeyF, isPressed); break;
|
||||
case VK_ANSI_G: _electron->set_key_state(Electron::Key::KeyG, isPressed); break;
|
||||
case VK_ANSI_H: _electron->set_key_state(Electron::Key::KeyH, isPressed); break;
|
||||
case VK_ANSI_J: _electron->set_key_state(Electron::Key::KeyJ, isPressed); break;
|
||||
case VK_ANSI_K: _electron->set_key_state(Electron::Key::KeyK, isPressed); break;
|
||||
case VK_ANSI_L: _electron->set_key_state(Electron::Key::KeyL, isPressed); break;
|
||||
case VK_ANSI_Z: _electron->set_key_state(Electron::Key::KeyZ, isPressed); break;
|
||||
case VK_ANSI_X: _electron->set_key_state(Electron::Key::KeyX, isPressed); break;
|
||||
case VK_ANSI_C: _electron->set_key_state(Electron::Key::KeyC, isPressed); break;
|
||||
case VK_ANSI_V: _electron->set_key_state(Electron::Key::KeyV, isPressed); break;
|
||||
case VK_ANSI_B: _electron->set_key_state(Electron::Key::KeyB, isPressed); break;
|
||||
case VK_ANSI_N: _electron->set_key_state(Electron::Key::KeyN, isPressed); break;
|
||||
case VK_ANSI_M: _electron->set_key_state(Electron::Key::KeyM, isPressed); break;
|
||||
|
||||
case VK_Space: _electron->set_key_state(Electron::Key::KeySpace, isPressed); break;
|
||||
case VK_ANSI_Grave:
|
||||
case VK_ANSI_Backslash:
|
||||
_electron->set_key_state(Electron::Key::KeyCopy, isPressed); break;
|
||||
case VK_Return: _electron->set_key_state(Electron::Key::KeyReturn, isPressed); break;
|
||||
case VK_ANSI_Minus: _electron->set_key_state(Electron::Key::KeyMinus, isPressed); break;
|
||||
|
||||
case VK_RightArrow: _electron->set_key_state(Electron::Key::KeyRight, isPressed); break;
|
||||
case VK_LeftArrow: _electron->set_key_state(Electron::Key::KeyLeft, isPressed); break;
|
||||
case VK_DownArrow: _electron->set_key_state(Electron::Key::KeyDown, isPressed); break;
|
||||
case VK_UpArrow: _electron->set_key_state(Electron::Key::KeyUp, isPressed); break;
|
||||
|
||||
case VK_Delete: _electron->set_key_state(Electron::Key::KeyDelete, isPressed); break;
|
||||
case VK_Escape: _electron->set_key_state(Electron::Key::KeyEscape, isPressed); break;
|
||||
|
||||
case VK_ANSI_Comma: _electron->set_key_state(Electron::Key::KeyComma, isPressed); break;
|
||||
case VK_ANSI_Period: _electron->set_key_state(Electron::Key::KeyFullStop, isPressed); break;
|
||||
|
||||
case VK_ANSI_Semicolon:
|
||||
_electron->set_key_state(Electron::Key::KeySemiColon, isPressed); break;
|
||||
case VK_ANSI_Quote: _electron->set_key_state(Electron::Key::KeyColon, isPressed); break;
|
||||
|
||||
case VK_ANSI_Slash: _electron->set_key_state(Electron::Key::KeySlash, isPressed); break;
|
||||
|
||||
case VK_Shift: _electron->set_key_state(Electron::Key::KeyShift, isPressed); break;
|
||||
case VK_Control: _electron->set_key_state(Electron::Key::KeyControl, isPressed); break;
|
||||
case VK_Command:
|
||||
case VK_Option: _electron->set_key_state(Electron::Key::KeyFunc, isPressed); break;
|
||||
|
||||
case VK_F12: _electron->set_key_state(Electron::Key::KeyBreak, isPressed); break;
|
||||
|
||||
default:
|
||||
// printf("%02x\n", key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)userDefaultsPrefix { return @"electron"; }
|
||||
|
||||
#pragma mark - Options
|
||||
|
||||
@@ -7,10 +7,9 @@
|
||||
//
|
||||
|
||||
#import "CSMachine.h"
|
||||
#import "CSKeyboardMachine.h"
|
||||
#import "CSFastLoading.h"
|
||||
|
||||
@interface CSOric : CSMachine <CSKeyboardMachine, CSFastLoading>
|
||||
@interface CSOric : CSMachine <CSFastLoading>
|
||||
|
||||
- (instancetype)init;
|
||||
|
||||
|
||||
@@ -43,99 +43,6 @@
|
||||
return [[NSBundle mainBundle] dataForResource:name withExtension:@"rom" subdirectory:@"ROMImages/Oric"];
|
||||
}
|
||||
|
||||
#pragma mark - CSKeyboardMachine
|
||||
|
||||
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed {
|
||||
@synchronized(self) {
|
||||
switch(key) {
|
||||
case VK_ANSI_0: _oric->set_key_state(Oric::Key::Key0, isPressed); break;
|
||||
case VK_ANSI_1: _oric->set_key_state(Oric::Key::Key1, isPressed); break;
|
||||
case VK_ANSI_2: _oric->set_key_state(Oric::Key::Key2, isPressed); break;
|
||||
case VK_ANSI_3: _oric->set_key_state(Oric::Key::Key3, isPressed); break;
|
||||
case VK_ANSI_4: _oric->set_key_state(Oric::Key::Key4, isPressed); break;
|
||||
case VK_ANSI_5: _oric->set_key_state(Oric::Key::Key5, isPressed); break;
|
||||
case VK_ANSI_6: _oric->set_key_state(Oric::Key::Key6, isPressed); break;
|
||||
case VK_ANSI_7: _oric->set_key_state(Oric::Key::Key7, isPressed); break;
|
||||
case VK_ANSI_8: _oric->set_key_state(Oric::Key::Key8, isPressed); break;
|
||||
case VK_ANSI_9: _oric->set_key_state(Oric::Key::Key9, isPressed); break;
|
||||
|
||||
case VK_ANSI_Q: _oric->set_key_state(Oric::Key::KeyQ, isPressed); break;
|
||||
case VK_ANSI_W: _oric->set_key_state(Oric::Key::KeyW, isPressed); break;
|
||||
case VK_ANSI_E: _oric->set_key_state(Oric::Key::KeyE, isPressed); break;
|
||||
case VK_ANSI_R: _oric->set_key_state(Oric::Key::KeyR, isPressed); break;
|
||||
case VK_ANSI_T: _oric->set_key_state(Oric::Key::KeyT, isPressed); break;
|
||||
case VK_ANSI_Y: _oric->set_key_state(Oric::Key::KeyY, isPressed); break;
|
||||
case VK_ANSI_U: _oric->set_key_state(Oric::Key::KeyU, isPressed); break;
|
||||
case VK_ANSI_I: _oric->set_key_state(Oric::Key::KeyI, isPressed); break;
|
||||
case VK_ANSI_O: _oric->set_key_state(Oric::Key::KeyO, isPressed); break;
|
||||
case VK_ANSI_P: _oric->set_key_state(Oric::Key::KeyP, isPressed); break;
|
||||
case VK_ANSI_A: _oric->set_key_state(Oric::Key::KeyA, isPressed); break;
|
||||
case VK_ANSI_S: _oric->set_key_state(Oric::Key::KeyS, isPressed); break;
|
||||
case VK_ANSI_D: _oric->set_key_state(Oric::Key::KeyD, isPressed); break;
|
||||
case VK_ANSI_F: _oric->set_key_state(Oric::Key::KeyF, isPressed); break;
|
||||
case VK_ANSI_G: _oric->set_key_state(Oric::Key::KeyG, isPressed); break;
|
||||
case VK_ANSI_H: _oric->set_key_state(Oric::Key::KeyH, isPressed); break;
|
||||
case VK_ANSI_J: _oric->set_key_state(Oric::Key::KeyJ, isPressed); break;
|
||||
case VK_ANSI_K: _oric->set_key_state(Oric::Key::KeyK, isPressed); break;
|
||||
case VK_ANSI_L: _oric->set_key_state(Oric::Key::KeyL, isPressed); break;
|
||||
case VK_ANSI_Z: _oric->set_key_state(Oric::Key::KeyZ, isPressed); break;
|
||||
case VK_ANSI_X: _oric->set_key_state(Oric::Key::KeyX, isPressed); break;
|
||||
case VK_ANSI_C: _oric->set_key_state(Oric::Key::KeyC, isPressed); break;
|
||||
case VK_ANSI_V: _oric->set_key_state(Oric::Key::KeyV, isPressed); break;
|
||||
case VK_ANSI_B: _oric->set_key_state(Oric::Key::KeyB, isPressed); break;
|
||||
case VK_ANSI_N: _oric->set_key_state(Oric::Key::KeyN, isPressed); break;
|
||||
case VK_ANSI_M: _oric->set_key_state(Oric::Key::KeyM, isPressed); break;
|
||||
|
||||
case VK_Space: _oric->set_key_state(Oric::Key::KeySpace, isPressed); break;
|
||||
case VK_Return: _oric->set_key_state(Oric::Key::KeyReturn, isPressed); break;
|
||||
case VK_ANSI_Minus: _oric->set_key_state(Oric::Key::KeyMinus, isPressed); break;
|
||||
case VK_ANSI_Equal: _oric->set_key_state(Oric::Key::KeyEquals, isPressed); break;
|
||||
case VK_ANSI_Backslash:
|
||||
_oric->set_key_state(Oric::Key::KeyBackSlash, isPressed); break;
|
||||
case VK_ANSI_Slash: _oric->set_key_state(Oric::Key::KeyForwardSlash, isPressed); break;
|
||||
|
||||
case VK_ANSI_LeftBracket:
|
||||
_oric->set_key_state(Oric::Key::KeyOpenSquare, isPressed); break;
|
||||
case VK_ANSI_RightBracket:
|
||||
_oric->set_key_state(Oric::Key::KeyCloseSquare, isPressed); break;
|
||||
case VK_ANSI_Quote: _oric->set_key_state(Oric::Key::KeyQuote, isPressed); break;
|
||||
|
||||
case VK_RightArrow: _oric->set_key_state(Oric::Key::KeyRight, isPressed); break;
|
||||
case VK_LeftArrow: _oric->set_key_state(Oric::Key::KeyLeft, isPressed); break;
|
||||
case VK_DownArrow: _oric->set_key_state(Oric::Key::KeyDown, isPressed); break;
|
||||
case VK_UpArrow: _oric->set_key_state(Oric::Key::KeyUp, isPressed); break;
|
||||
|
||||
case VK_Delete: _oric->set_key_state(Oric::Key::KeyDelete, isPressed); break;
|
||||
case VK_Escape: _oric->set_key_state(Oric::Key::KeyEscape, isPressed); break;
|
||||
|
||||
case VK_ANSI_Comma: _oric->set_key_state(Oric::Key::KeyComma, isPressed); break;
|
||||
case VK_ANSI_Period: _oric->set_key_state(Oric::Key::KeyFullStop, isPressed); break;
|
||||
|
||||
case VK_ANSI_Semicolon: _oric->set_key_state(Oric::Key::KeySemiColon, isPressed); break;
|
||||
|
||||
case VK_Shift:
|
||||
_oric->set_key_state(Oric::Key::KeyLeftShift, isPressed);
|
||||
_oric->set_key_state(Oric::Key::KeyRightShift, isPressed);
|
||||
break;
|
||||
case VK_RightShift: _oric->set_key_state(Oric::Key::KeyRightShift, isPressed); break;
|
||||
case VK_Control: _oric->set_key_state(Oric::Key::KeyControl, isPressed); break;
|
||||
|
||||
case VK_ANSI_Grave:
|
||||
case VK_F12: _oric->set_key_state(Oric::Key::KeyNMI, isPressed); break;
|
||||
|
||||
default:
|
||||
printf("%02x\n", key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clearAllKeys {
|
||||
@synchronized(self) {
|
||||
_oric->clear_all_keys();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Options
|
||||
|
||||
- (void)setUseFastLoadingHack:(BOOL)useFastLoadingHack {
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
//
|
||||
|
||||
#import "CSMachine.h"
|
||||
#import "CSKeyboardMachine.h"
|
||||
#import "CSFastLoading.h"
|
||||
|
||||
typedef NS_ENUM(NSInteger, CSVic20Country)
|
||||
@@ -26,7 +25,7 @@ typedef NS_ENUM(NSInteger, CSVic20MemorySize)
|
||||
CSVic20MemorySize32Kb,
|
||||
};
|
||||
|
||||
@interface CSVic20 : CSMachine <CSKeyboardMachine, CSFastLoading>
|
||||
@interface CSVic20 : CSMachine <CSFastLoading>
|
||||
|
||||
- (instancetype)init;
|
||||
|
||||
|
||||
@@ -68,101 +68,15 @@ using namespace Commodore::Vic20;
|
||||
|
||||
#pragma mark - Keyboard map
|
||||
|
||||
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed {
|
||||
static NSDictionary<NSNumber *, NSNumber *> *vicKeysByKeys = @{
|
||||
@(VK_ANSI_1): @(Key::Key1), @(VK_ANSI_2): @(Key::Key2),
|
||||
@(VK_ANSI_3): @(Key::Key3), @(VK_ANSI_4): @(Key::Key4),
|
||||
@(VK_ANSI_5): @(Key::Key5), @(VK_ANSI_6): @(Key::Key6),
|
||||
@(VK_ANSI_7): @(Key::Key7), @(VK_ANSI_8): @(Key::Key8),
|
||||
@(VK_ANSI_9): @(Key::Key9), @(VK_ANSI_0): @(Key::Key0),
|
||||
|
||||
@(VK_ANSI_Q): @(Key::KeyQ), @(VK_ANSI_W): @(Key::KeyW),
|
||||
@(VK_ANSI_E): @(Key::KeyE), @(VK_ANSI_R): @(Key::KeyR),
|
||||
@(VK_ANSI_T): @(Key::KeyT), @(VK_ANSI_Y): @(Key::KeyY),
|
||||
@(VK_ANSI_U): @(Key::KeyU), @(VK_ANSI_I): @(Key::KeyI),
|
||||
@(VK_ANSI_O): @(Key::KeyO), @(VK_ANSI_P): @(Key::KeyP),
|
||||
@(VK_ANSI_A): @(Key::KeyA), @(VK_ANSI_S): @(Key::KeyS),
|
||||
@(VK_ANSI_D): @(Key::KeyD), @(VK_ANSI_F): @(Key::KeyF),
|
||||
@(VK_ANSI_G): @(Key::KeyG), @(VK_ANSI_H): @(Key::KeyH),
|
||||
@(VK_ANSI_J): @(Key::KeyJ), @(VK_ANSI_K): @(Key::KeyK),
|
||||
@(VK_ANSI_L): @(Key::KeyL), @(VK_ANSI_Z): @(Key::KeyZ),
|
||||
@(VK_ANSI_X): @(Key::KeyX), @(VK_ANSI_C): @(Key::KeyC),
|
||||
@(VK_ANSI_V): @(Key::KeyV), @(VK_ANSI_B): @(Key::KeyB),
|
||||
@(VK_ANSI_N): @(Key::KeyN), @(VK_ANSI_M): @(Key::KeyM),
|
||||
|
||||
@(VK_Space): @(Key::KeySpace),
|
||||
@(VK_Return): @(Key::KeyReturn),
|
||||
@(VK_Delete): @(Key::KeyDelete),
|
||||
@(VK_ANSI_Comma): @(Key::KeyComma),
|
||||
@(VK_ANSI_Period): @(Key::KeyFullStop),
|
||||
@(VK_ANSI_Minus): @(Key::KeyDash),
|
||||
@(VK_ANSI_Equal): @(Key::KeyEquals),
|
||||
@(VK_ANSI_Semicolon): @(Key::KeyColon),
|
||||
@(VK_ANSI_Quote): @(Key::KeySemicolon),
|
||||
@(VK_ANSI_Slash): @(Key::KeySlash),
|
||||
@(VK_Option): @(Key::KeyCBM),
|
||||
@(VK_Control): @(Key::KeyControl),
|
||||
|
||||
@(VK_F1): @(Key::KeyF1), @(VK_F3): @(Key::KeyF3),
|
||||
@(VK_F5): @(Key::KeyF5), @(VK_F7): @(Key::KeyF7),
|
||||
|
||||
@(VK_ANSI_Grave): @(Key::KeyLeft),
|
||||
@(VK_Tab): @(Key::KeyRunStop),
|
||||
@(VK_ANSI_LeftBracket): @(Key::KeyAt),
|
||||
@(VK_ANSI_RightBracket): @(Key::KeyAsterisk),
|
||||
@(VK_ANSI_Backslash): @(Key::KeyUp),
|
||||
|
||||
@(VK_RightArrow): @(Key::KeyRight),
|
||||
@(VK_DownArrow): @(Key::KeyDown),
|
||||
};
|
||||
|
||||
// Not yet mapped:
|
||||
// KeyHome
|
||||
// KeyPlus
|
||||
// KeyGBP
|
||||
|
||||
if(key == VK_Tab && isPressed) {
|
||||
_joystickMode ^= YES;
|
||||
}
|
||||
|
||||
@synchronized(self) {
|
||||
if(_joystickMode) {
|
||||
switch(key) {
|
||||
case VK_UpArrow: _vic20->set_joystick_state(JoystickInput::Up, isPressed); break;
|
||||
case VK_DownArrow: _vic20->set_joystick_state(JoystickInput::Down, isPressed); break;
|
||||
case VK_LeftArrow: _vic20->set_joystick_state(JoystickInput::Left, isPressed); break;
|
||||
case VK_RightArrow: _vic20->set_joystick_state(JoystickInput::Right, isPressed); break;
|
||||
case VK_ANSI_A: _vic20->set_joystick_state(JoystickInput::Fire, isPressed); break;
|
||||
}
|
||||
} else {
|
||||
switch(key) {
|
||||
default: {
|
||||
NSNumber *targetKey = vicKeysByKeys[@(key)];
|
||||
if(targetKey)
|
||||
{
|
||||
_vic20->set_key_state((Key)targetKey.integerValue, isPressed);
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog(@"Unmapped: %02x", key);
|
||||
}
|
||||
} break;
|
||||
|
||||
case VK_Shift:
|
||||
// Yuck
|
||||
_vic20->set_key_state(Key::KeyLShift, isPressed);
|
||||
_vic20->set_key_state(Key::KeyRShift, isPressed);
|
||||
break;
|
||||
}
|
||||
/*- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed {
|
||||
switch(key) {
|
||||
case VK_UpArrow: _vic20->set_joystick_state(JoystickInput::Up, isPressed); break;
|
||||
case VK_DownArrow: _vic20->set_joystick_state(JoystickInput::Down, isPressed); break;
|
||||
case VK_LeftArrow: _vic20->set_joystick_state(JoystickInput::Left, isPressed); break;
|
||||
case VK_RightArrow: _vic20->set_joystick_state(JoystickInput::Right, isPressed); break;
|
||||
case VK_ANSI_A: _vic20->set_joystick_state(JoystickInput::Fire, isPressed); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clearAllKeys {
|
||||
@synchronized(self) {
|
||||
_vic20->clear_all_keys();
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
#pragma mark - Public configuration options
|
||||
|
||||
|
||||
@@ -7,10 +7,9 @@
|
||||
//
|
||||
|
||||
#import "CSMachine.h"
|
||||
#import "CSKeyboardMachine.h"
|
||||
#import "CSFastLoading.h"
|
||||
|
||||
@interface CSZX8081 : CSMachine <CSKeyboardMachine, CSFastLoading>
|
||||
@interface CSZX8081 : CSMachine <CSFastLoading>
|
||||
|
||||
@property (nonatomic, assign) BOOL useFastLoadingHack;
|
||||
|
||||
|
||||
@@ -34,69 +34,6 @@
|
||||
return [[NSBundle mainBundle] dataForResource:name withExtension:@"rom" subdirectory:@"ROMImages/ZX8081"];
|
||||
}
|
||||
|
||||
#pragma mark - Keyboard Mapping
|
||||
|
||||
- (void)clearAllKeys {
|
||||
@synchronized(self) {
|
||||
_zx8081->clear_all_keys();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed {
|
||||
@synchronized(self) {
|
||||
switch(key) {
|
||||
case VK_ANSI_0: _zx8081->set_key_state(ZX8081::Key::Key0, isPressed); break;
|
||||
case VK_ANSI_1: _zx8081->set_key_state(ZX8081::Key::Key1, isPressed); break;
|
||||
case VK_ANSI_2: _zx8081->set_key_state(ZX8081::Key::Key2, isPressed); break;
|
||||
case VK_ANSI_3: _zx8081->set_key_state(ZX8081::Key::Key3, isPressed); break;
|
||||
case VK_ANSI_4: _zx8081->set_key_state(ZX8081::Key::Key4, isPressed); break;
|
||||
case VK_ANSI_5: _zx8081->set_key_state(ZX8081::Key::Key5, isPressed); break;
|
||||
case VK_ANSI_6: _zx8081->set_key_state(ZX8081::Key::Key6, isPressed); break;
|
||||
case VK_ANSI_7: _zx8081->set_key_state(ZX8081::Key::Key7, isPressed); break;
|
||||
case VK_ANSI_8: _zx8081->set_key_state(ZX8081::Key::Key8, isPressed); break;
|
||||
case VK_ANSI_9: _zx8081->set_key_state(ZX8081::Key::Key9, isPressed); break;
|
||||
|
||||
case VK_ANSI_Q: _zx8081->set_key_state(ZX8081::Key::KeyQ, isPressed); break;
|
||||
case VK_ANSI_W: _zx8081->set_key_state(ZX8081::Key::KeyW, isPressed); break;
|
||||
case VK_ANSI_E: _zx8081->set_key_state(ZX8081::Key::KeyE, isPressed); break;
|
||||
case VK_ANSI_R: _zx8081->set_key_state(ZX8081::Key::KeyR, isPressed); break;
|
||||
case VK_ANSI_T: _zx8081->set_key_state(ZX8081::Key::KeyT, isPressed); break;
|
||||
case VK_ANSI_Y: _zx8081->set_key_state(ZX8081::Key::KeyY, isPressed); break;
|
||||
case VK_ANSI_U: _zx8081->set_key_state(ZX8081::Key::KeyU, isPressed); break;
|
||||
case VK_ANSI_I: _zx8081->set_key_state(ZX8081::Key::KeyI, isPressed); break;
|
||||
case VK_ANSI_O: _zx8081->set_key_state(ZX8081::Key::KeyO, isPressed); break;
|
||||
case VK_ANSI_P: _zx8081->set_key_state(ZX8081::Key::KeyP, isPressed); break;
|
||||
|
||||
case VK_ANSI_A: _zx8081->set_key_state(ZX8081::Key::KeyA, isPressed); break;
|
||||
case VK_ANSI_S: _zx8081->set_key_state(ZX8081::Key::KeyS, isPressed); break;
|
||||
case VK_ANSI_D: _zx8081->set_key_state(ZX8081::Key::KeyD, isPressed); break;
|
||||
case VK_ANSI_F: _zx8081->set_key_state(ZX8081::Key::KeyF, isPressed); break;
|
||||
case VK_ANSI_G: _zx8081->set_key_state(ZX8081::Key::KeyG, isPressed); break;
|
||||
case VK_ANSI_H: _zx8081->set_key_state(ZX8081::Key::KeyH, isPressed); break;
|
||||
case VK_ANSI_J: _zx8081->set_key_state(ZX8081::Key::KeyJ, isPressed); break;
|
||||
case VK_ANSI_K: _zx8081->set_key_state(ZX8081::Key::KeyK, isPressed); break;
|
||||
case VK_ANSI_L: _zx8081->set_key_state(ZX8081::Key::KeyL, isPressed); break;
|
||||
|
||||
case VK_ANSI_Z: _zx8081->set_key_state(ZX8081::Key::KeyZ, isPressed); break;
|
||||
case VK_ANSI_X: _zx8081->set_key_state(ZX8081::Key::KeyX, isPressed); break;
|
||||
case VK_ANSI_C: _zx8081->set_key_state(ZX8081::Key::KeyC, isPressed); break;
|
||||
case VK_ANSI_V: _zx8081->set_key_state(ZX8081::Key::KeyV, isPressed); break;
|
||||
case VK_ANSI_B: _zx8081->set_key_state(ZX8081::Key::KeyB, isPressed); break;
|
||||
case VK_ANSI_N: _zx8081->set_key_state(ZX8081::Key::KeyN, isPressed); break;
|
||||
case VK_ANSI_M: _zx8081->set_key_state(ZX8081::Key::KeyM, isPressed); break;
|
||||
|
||||
case VK_Shift:
|
||||
case VK_RightShift:
|
||||
_zx8081->set_key_state(ZX8081::Key::KeyShift, isPressed); break;
|
||||
break;
|
||||
|
||||
case VK_ANSI_Period:_zx8081->set_key_state(ZX8081::Key::KeyDot, isPressed); break;
|
||||
case VK_Return: _zx8081->set_key_state(ZX8081::Key::KeyEnter, isPressed); break;
|
||||
case VK_Space: _zx8081->set_key_state(ZX8081::Key::KeySpace, isPressed); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)userDefaultsPrefix { return @"zx8081"; }
|
||||
|
||||
#pragma mark - Options
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
//
|
||||
// CSBestEffortUpdater.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/06/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSBestEffortUpdater.h"
|
||||
|
||||
#include <stdatomic.h>
|
||||
|
||||
@implementation CSBestEffortUpdater
|
||||
{
|
||||
// these are inherently handled only by thread-safe constructions
|
||||
atomic_flag _updateIsOngoing;
|
||||
dispatch_queue_t _serialDispatchQueue;
|
||||
|
||||
// these are permitted for modification on _serialDispatchQueue only
|
||||
NSTimeInterval _previousTimeInterval;
|
||||
NSTimeInterval _cyclesError;
|
||||
BOOL _hasSkipped;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if(self = [super init])
|
||||
{
|
||||
_serialDispatchQueue = dispatch_queue_create("Best Effort Updater", DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
// This is a workaround for assigning the correct initial value within Objective-C's form.
|
||||
atomic_flag initialFlagValue = ATOMIC_FLAG_INIT;
|
||||
_updateIsOngoing = initialFlagValue;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)update
|
||||
{
|
||||
// Always post an -openGLView:didUpdateToTime: if a previous one isn't still ongoing. This is the hook upon which the substantial processing occurs.
|
||||
if(!atomic_flag_test_and_set(&_updateIsOngoing))
|
||||
{
|
||||
dispatch_async(_serialDispatchQueue, ^{
|
||||
NSTimeInterval timeInterval = [NSDate timeIntervalSinceReferenceDate];
|
||||
if(_previousTimeInterval > DBL_EPSILON && timeInterval > _previousTimeInterval)
|
||||
{
|
||||
NSTimeInterval timeToRunFor = timeInterval - _previousTimeInterval;
|
||||
double cyclesToRunFor = timeToRunFor * self.clockRate + _cyclesError;
|
||||
|
||||
_cyclesError = fmod(cyclesToRunFor, 1.0);
|
||||
NSUInteger integerCyclesToRunFor = (NSUInteger)MIN(cyclesToRunFor, self.clockRate * 0.5);
|
||||
|
||||
// treat 'unlimited' as running at a factor of 10
|
||||
if(self.runAsUnlimited) integerCyclesToRunFor *= 10;
|
||||
[self.delegate bestEffortUpdater:self runForCycles:integerCyclesToRunFor didSkipPreviousUpdate:_hasSkipped];
|
||||
}
|
||||
_previousTimeInterval = timeInterval;
|
||||
_hasSkipped = NO;
|
||||
atomic_flag_clear(&_updateIsOngoing);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
dispatch_async(_serialDispatchQueue, ^{
|
||||
_hasSkipped = YES;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)flush
|
||||
{
|
||||
dispatch_sync(_serialDispatchQueue, ^{});
|
||||
}
|
||||
|
||||
@end
|
||||
73
OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.mm
Normal file
73
OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.mm
Normal file
@@ -0,0 +1,73 @@
|
||||
//
|
||||
// CSBestEffortUpdater.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/06/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSBestEffortUpdater.h"
|
||||
|
||||
#include "BestEffortUpdater.hpp"
|
||||
|
||||
struct UpdaterDelegate: public Concurrency::BestEffortUpdater::Delegate {
|
||||
__weak id<CSBestEffortUpdaterDelegate> delegate;
|
||||
NSLock *delegateLock;
|
||||
|
||||
void update(Concurrency::BestEffortUpdater *updater, int cycles, bool did_skip_previous_update) {
|
||||
[delegateLock lock];
|
||||
[delegate bestEffortUpdater:nil runForCycles:(NSUInteger)cycles didSkipPreviousUpdate:did_skip_previous_update];
|
||||
[delegateLock unlock];
|
||||
}
|
||||
};
|
||||
|
||||
@implementation CSBestEffortUpdater {
|
||||
Concurrency::BestEffortUpdater _updater;
|
||||
UpdaterDelegate _updaterDelegate;
|
||||
NSLock *_delegateLock;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
_delegateLock = [[NSLock alloc] init];
|
||||
_updaterDelegate.delegateLock = _delegateLock;
|
||||
_updater.set_delegate(&_updaterDelegate);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
_updater.flush();
|
||||
}
|
||||
|
||||
- (void)update {
|
||||
_updater.update();
|
||||
}
|
||||
|
||||
- (void)flush {
|
||||
_updater.flush();
|
||||
}
|
||||
|
||||
- (void)setClockRate:(double)clockRate {
|
||||
_clockRate = clockRate;
|
||||
_updater.set_clock_rate(clockRate);
|
||||
}
|
||||
|
||||
- (void)setDelegate:(id<CSBestEffortUpdaterDelegate>)delegate {
|
||||
[_delegateLock lock];
|
||||
_updaterDelegate.delegate = delegate;
|
||||
[_delegateLock unlock];
|
||||
}
|
||||
|
||||
- (id<CSBestEffortUpdaterDelegate>)delegate {
|
||||
id<CSBestEffortUpdaterDelegate> delegate;
|
||||
|
||||
[_delegateLock lock];
|
||||
delegate = _updaterDelegate.delegate;
|
||||
[_delegateLock unlock];
|
||||
|
||||
return delegate;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
@implementation CSOpenGLView {
|
||||
CVDisplayLinkRef _displayLink;
|
||||
CGSize _backingSize;
|
||||
}
|
||||
|
||||
- (void)prepareOpenGL
|
||||
@@ -68,12 +69,17 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
||||
|
||||
- (CGSize)backingSize
|
||||
{
|
||||
return [self convertSizeToBacking:self.bounds.size];
|
||||
@synchronized(self) {
|
||||
return _backingSize;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reshape
|
||||
{
|
||||
[super reshape];
|
||||
@synchronized(self) {
|
||||
_backingSize = [self convertSizeToBacking:self.bounds.size];
|
||||
}
|
||||
|
||||
[self performWithGLContext:^{
|
||||
CGSize viewSize = [self backingSize];
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
//
|
||||
|
||||
#import "MOS6522Bridge.h"
|
||||
|
||||
#include "6522.hpp"
|
||||
#include <memory>
|
||||
|
||||
@class MOS6522Bridge;
|
||||
|
||||
class VanillaVIA: public MOS::MOS6522<VanillaVIA> {
|
||||
class VanillaVIAPortHandler: public MOS::MOS6522::PortHandler {
|
||||
public:
|
||||
MOS6522Bridge *bridge;
|
||||
bool irq_line;
|
||||
@@ -22,53 +24,55 @@ class VanillaVIA: public MOS::MOS6522<VanillaVIA> {
|
||||
irq_line = new_status;
|
||||
}
|
||||
|
||||
uint8_t get_port_input(Port port) {
|
||||
uint8_t get_port_input(MOS::MOS6522::Port port) {
|
||||
return port ? port_b_value : port_a_value;
|
||||
}
|
||||
};
|
||||
|
||||
@implementation MOS6522Bridge {
|
||||
VanillaVIA _via;
|
||||
VanillaVIAPortHandler _viaPortHandler;
|
||||
std::unique_ptr<MOS::MOS6522::MOS6522<VanillaVIAPortHandler>> _via;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
_via.bridge = self;
|
||||
_via.reset(new MOS::MOS6522::MOS6522<VanillaVIAPortHandler>(_viaPortHandler));
|
||||
_viaPortHandler.bridge = self;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setValue:(uint8_t)value forRegister:(NSUInteger)registerNumber {
|
||||
_via.set_register((int)registerNumber, value);
|
||||
_via->set_register((int)registerNumber, value);
|
||||
}
|
||||
|
||||
- (uint8_t)valueForRegister:(NSUInteger)registerNumber {
|
||||
return _via.get_register((int)registerNumber);
|
||||
return _via->get_register((int)registerNumber);
|
||||
}
|
||||
|
||||
- (void)runForHalfCycles:(NSUInteger)numberOfHalfCycles {
|
||||
_via.run_for(HalfCycles((int)numberOfHalfCycles));
|
||||
_via->run_for(HalfCycles((int)numberOfHalfCycles));
|
||||
}
|
||||
|
||||
- (BOOL)irqLine {
|
||||
return _via.irq_line;
|
||||
return _viaPortHandler.irq_line;
|
||||
}
|
||||
|
||||
- (void)setPortAInput:(uint8_t)portAInput {
|
||||
_via.port_a_value = portAInput;
|
||||
_viaPortHandler.port_a_value = portAInput;
|
||||
}
|
||||
|
||||
- (uint8_t)portAInput {
|
||||
return _via.port_a_value;
|
||||
return _viaPortHandler.port_a_value;
|
||||
}
|
||||
|
||||
- (void)setPortBInput:(uint8_t)portBInput {
|
||||
_via.port_b_value = portBInput;
|
||||
_viaPortHandler.port_b_value = portBInput;
|
||||
}
|
||||
|
||||
- (uint8_t)portBInput {
|
||||
return _via.port_b_value;
|
||||
return _viaPortHandler.port_b_value;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -59,24 +59,24 @@ fileprivate struct RegisterState {
|
||||
|
||||
init(dictionary: [String: Any]) {
|
||||
// don't test bits 3 and 5 for now
|
||||
af = UInt16(dictionary["af"] as! NSNumber)
|
||||
bc = UInt16(dictionary["bc"] as! NSNumber)
|
||||
de = UInt16(dictionary["de"] as! NSNumber)
|
||||
hl = UInt16(dictionary["hl"] as! NSNumber)
|
||||
af = UInt16(truncating: dictionary["af"] as! NSNumber)
|
||||
bc = UInt16(truncating: dictionary["bc"] as! NSNumber)
|
||||
de = UInt16(truncating: dictionary["de"] as! NSNumber)
|
||||
hl = UInt16(truncating: dictionary["hl"] as! NSNumber)
|
||||
|
||||
afDash = UInt16(dictionary["afDash"] as! NSNumber)
|
||||
bcDash = UInt16(dictionary["bcDash"] as! NSNumber)
|
||||
deDash = UInt16(dictionary["deDash"] as! NSNumber)
|
||||
hlDash = UInt16(dictionary["hlDash"] as! NSNumber)
|
||||
afDash = UInt16(truncating: dictionary["afDash"] as! NSNumber)
|
||||
bcDash = UInt16(truncating: dictionary["bcDash"] as! NSNumber)
|
||||
deDash = UInt16(truncating: dictionary["deDash"] as! NSNumber)
|
||||
hlDash = UInt16(truncating: dictionary["hlDash"] as! NSNumber)
|
||||
|
||||
ix = UInt16(dictionary["ix"] as! NSNumber)
|
||||
iy = UInt16(dictionary["iy"] as! NSNumber)
|
||||
ix = UInt16(truncating: dictionary["ix"] as! NSNumber)
|
||||
iy = UInt16(truncating: dictionary["iy"] as! NSNumber)
|
||||
|
||||
sp = UInt16(dictionary["sp"] as! NSNumber)
|
||||
pc = UInt16(dictionary["pc"] as! NSNumber)
|
||||
sp = UInt16(truncating: dictionary["sp"] as! NSNumber)
|
||||
pc = UInt16(truncating: dictionary["pc"] as! NSNumber)
|
||||
|
||||
i = UInt8(dictionary["i"] as! NSNumber)
|
||||
r = UInt8(dictionary["r"] as! NSNumber)
|
||||
i = UInt8(truncating: dictionary["i"] as! NSNumber)
|
||||
r = UInt8(truncating: dictionary["r"] as! NSNumber)
|
||||
|
||||
iff1 = (dictionary["iff1"] as! NSNumber).boolValue
|
||||
iff2 = (dictionary["iff2"] as! NSNumber).boolValue
|
||||
@@ -183,10 +183,10 @@ class FUSETests: XCTestCase {
|
||||
if let inputMemoryGroups = inputMemoryGroups {
|
||||
for group in inputMemoryGroups {
|
||||
let groupDictionary = group as! [String: Any]
|
||||
var address = UInt16(groupDictionary["address"] as! NSNumber)
|
||||
var address = UInt16(truncating: groupDictionary["address"] as! NSNumber)
|
||||
let data = groupDictionary["data"] as! [NSNumber]
|
||||
for value in data {
|
||||
machine.setValue(UInt8(value), atAddress: address)
|
||||
machine.setValue(UInt8(truncating: value), atAddress: address)
|
||||
address = address + 1
|
||||
}
|
||||
}
|
||||
@@ -208,10 +208,10 @@ class FUSETests: XCTestCase {
|
||||
if let outputMemoryGroups = outputMemoryGroups {
|
||||
for group in outputMemoryGroups {
|
||||
let groupDictionary = group as! [String: Any]
|
||||
var address = UInt16(groupDictionary["address"] as! NSNumber)
|
||||
var address = UInt16(truncating: groupDictionary["address"] as! NSNumber)
|
||||
let data = groupDictionary["data"] as! [NSNumber]
|
||||
for value in data {
|
||||
XCTAssert(machine.value(atAddress: address) == UInt8(value), "Failed memory state \(name)")
|
||||
XCTAssert(machine.value(atAddress: address) == UInt8(truncating: value), "Failed memory state \(name)")
|
||||
address = address + 1
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user