Compare commits
269 Commits
2017-01-01
...
2017-03-26
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
defec2c9b0 | ||
|
|
04921e64de | ||
|
|
bdd432fe1d | ||
|
|
6e9ab9f330 | ||
|
|
814c0ada13 | ||
|
|
dfc468f220 | ||
|
|
6817b38322 | ||
|
|
e01f3f06c8 | ||
|
|
3229502fa1 | ||
|
|
a4c5eebd1e | ||
|
|
a3c22d5abb | ||
|
|
a26b87f348 | ||
|
|
4c3cc42c91 | ||
|
|
f3f4e1a541 | ||
|
|
4722f6b5c4 | ||
|
|
7d8d1c7828 | ||
|
|
4bb70e7d31 | ||
|
|
321030bb44 | ||
|
|
6c161b1150 | ||
|
|
d5c37c8619 | ||
|
|
c445eaec3e | ||
|
|
7c66c36d3f | ||
|
|
031a68000a | ||
|
|
7d7b665be8 | ||
|
|
c3d82f88a5 | ||
|
|
c033bad0b9 | ||
|
|
c31d85f820 | ||
|
|
217fbf257e | ||
|
|
0b611a14b9 | ||
|
|
df6861c9dc | ||
|
|
a4cd12394e | ||
|
|
e0bca1e37b | ||
|
|
55ce851bb2 | ||
|
|
e8d34f2eb4 | ||
|
|
bb3daaa99b | ||
|
|
36b58d03b7 | ||
|
|
7958459db9 | ||
|
|
14a76af0d3 | ||
|
|
a04a58e01f | ||
|
|
afbd9fd41b | ||
|
|
3d53d4e55e | ||
|
|
7302703039 | ||
|
|
97a8a96593 | ||
|
|
be2e99077e | ||
|
|
3b29276228 | ||
|
|
4a528b9ecb | ||
|
|
b3632a4e86 | ||
|
|
8a659e3117 | ||
|
|
a6897ebde0 | ||
|
|
582da14a14 | ||
|
|
b81bf6b547 | ||
|
|
8e147444d5 | ||
|
|
a9964ee0c8 | ||
|
|
b671df9906 | ||
|
|
0bcf9c30de | ||
|
|
2c07cce282 | ||
|
|
8c5e39c0c5 | ||
|
|
cae48aaa95 | ||
|
|
37f4f6ba14 | ||
|
|
597bd97b01 | ||
|
|
38de5300e5 | ||
|
|
62b3c9dda8 | ||
|
|
146f3ea0f5 | ||
|
|
af9b7fbc30 | ||
|
|
78213f1e95 | ||
|
|
de347ad7c8 | ||
|
|
a4bba8a92e | ||
|
|
fcacfc2726 | ||
|
|
bab464e765 | ||
|
|
2879763c34 | ||
|
|
ea2ea30193 | ||
|
|
608569cc48 | ||
|
|
c7e973aab4 | ||
|
|
443d57bc32 | ||
|
|
57ec756f5b | ||
|
|
9286a5ba73 | ||
|
|
1c9dffe41f | ||
|
|
8c7f724ce4 | ||
|
|
b193248056 | ||
|
|
f0d944847b | ||
|
|
a72d70e707 | ||
|
|
add14fb43a | ||
|
|
38ce4dc56c | ||
|
|
bce5abd33b | ||
|
|
3f36eeb071 | ||
|
|
33bda2d40c | ||
|
|
2b5e3a600e | ||
|
|
8dbf9fd302 | ||
|
|
9c72ce5bd2 | ||
|
|
ec2762509b | ||
|
|
e63229a5e5 | ||
|
|
ad73379d1c | ||
|
|
abd4d2c42a | ||
|
|
79784a8e57 | ||
|
|
61b8fc1e2f | ||
|
|
4751615623 | ||
|
|
cccdc558e7 | ||
|
|
d3257c345a | ||
|
|
e09b76bf32 | ||
|
|
837cccdf83 | ||
|
|
a3fcd15980 | ||
|
|
93d1573481 | ||
|
|
893a5dd007 | ||
|
|
026b418b4a | ||
|
|
06dd98b23c | ||
|
|
2a81ae1dec | ||
|
|
1625b9c7f9 | ||
|
|
184c8ae707 | ||
|
|
8f8b103224 | ||
|
|
1af415a88e | ||
|
|
fe07cd0248 | ||
|
|
a3d339092e | ||
|
|
837216ee9a | ||
|
|
dcd0c90283 | ||
|
|
b24cd00a39 | ||
|
|
0273860018 | ||
|
|
82c089cde4 | ||
|
|
997707a45b | ||
|
|
9d7985c1e1 | ||
|
|
8b1ec827e0 | ||
|
|
153525f23d | ||
|
|
3101dc94a7 | ||
|
|
e6a84fd26b | ||
|
|
440467ea3e | ||
|
|
98376de9ad | ||
|
|
e61e355251 | ||
|
|
c898c8a99e | ||
|
|
8c9062857c | ||
|
|
77ed4ddc05 | ||
|
|
82f392fada | ||
|
|
2f0c923c29 | ||
|
|
8291a63d5f | ||
|
|
4c947ad553 | ||
|
|
6120dae61a | ||
|
|
1d03793f22 | ||
|
|
4f5f191cd6 | ||
|
|
21abf4e9fc | ||
|
|
144d6b70d9 | ||
|
|
b769f22ca0 | ||
|
|
7019d396d0 | ||
|
|
f4447fd9cd | ||
|
|
36396b3d62 | ||
|
|
d1dbf8c21f | ||
|
|
1bde0fed6f | ||
|
|
7ab2358bba | ||
|
|
99547181f1 | ||
|
|
2bf784535c | ||
|
|
57f434c199 | ||
|
|
87afa9140e | ||
|
|
d19f26887d | ||
|
|
6cb95b4fc5 | ||
|
|
d979a822ac | ||
|
|
fccdce65b9 | ||
|
|
99a35266e1 | ||
|
|
51bcaea60c | ||
|
|
e00339ef0a | ||
|
|
53cd125712 | ||
|
|
04693b067c | ||
|
|
cd7876a746 | ||
|
|
ed5ff49ef5 | ||
|
|
8d502a0b03 | ||
|
|
5ea232310f | ||
|
|
09309aa74f | ||
|
|
b5357860b9 | ||
|
|
dd17459687 | ||
|
|
cd90118a0f | ||
|
|
25776de59d | ||
|
|
600bdc9af7 | ||
|
|
0c9be2b09e | ||
|
|
df8a5cbe6d | ||
|
|
9ce68c38ae | ||
|
|
40954d6a2a | ||
|
|
ac444a3f34 | ||
|
|
b8abeced6d | ||
|
|
aeff59addc | ||
|
|
7ab6023a0c | ||
|
|
97cdfea9e9 | ||
|
|
aff69dbc34 | ||
|
|
6381e4e1b0 | ||
|
|
c8e595d9aa | ||
|
|
8c88fd4261 | ||
|
|
a86a6367b5 | ||
|
|
905ed1f87b | ||
|
|
8de6caf6ff | ||
|
|
327c19a222 | ||
|
|
40d3f5f7f6 | ||
|
|
64d5712d1d | ||
|
|
3b20d862f0 | ||
|
|
2e9ef2b0ef | ||
|
|
70745286a5 | ||
|
|
dcb7584060 | ||
|
|
a477499724 | ||
|
|
944d835eea | ||
|
|
8f5039130c | ||
|
|
ba165bb70a | ||
|
|
474e2e8d2c | ||
|
|
8b8eb787df | ||
|
|
66bcdd36f3 | ||
|
|
fcf8cafb5d | ||
|
|
6bcf95042c | ||
|
|
23f3ccd77a | ||
|
|
f2437cb257 | ||
|
|
abe04334c2 | ||
|
|
8545707b54 | ||
|
|
2b08758b2b | ||
|
|
764b528891 | ||
|
|
92754ace7a | ||
|
|
1cc13b2799 | ||
|
|
38f944bc34 | ||
|
|
427175b9c0 | ||
|
|
ebde955356 | ||
|
|
7fd02e7f4c | ||
|
|
d51f185dc7 | ||
|
|
2390358c24 | ||
|
|
2432a3b4d7 | ||
|
|
9c3597c7e3 | ||
|
|
fba6baaa9c | ||
|
|
a246530953 | ||
|
|
0ffded72a6 | ||
|
|
acadfbabec | ||
|
|
9001cc3fc2 | ||
|
|
015b2b49f9 | ||
|
|
92f928ca42 | ||
|
|
6d087ca054 | ||
|
|
c2d7e36c8f | ||
|
|
4d6e78e641 | ||
|
|
5761c8267b | ||
|
|
a66a8c31b2 | ||
|
|
19e4ee12e1 | ||
|
|
4871572a33 | ||
|
|
2e744a95e4 | ||
|
|
ff87f1390d | ||
|
|
76ca30c26d | ||
|
|
7c2685cb34 | ||
|
|
8cf25a2d70 | ||
|
|
8d69dd30f3 | ||
|
|
ae8068b86f | ||
|
|
baeb0ee89f | ||
|
|
c07993bb0a | ||
|
|
7680cbf9c3 | ||
|
|
4920fe6701 | ||
|
|
55fe0176bd | ||
|
|
99fcbb55d1 | ||
|
|
6f78ecd12b | ||
|
|
ced644b103 | ||
|
|
be1cb2a551 | ||
|
|
b4159295f6 | ||
|
|
d0a93409e6 | ||
|
|
4c3669f210 | ||
|
|
eeb646868b | ||
|
|
3d789732a2 | ||
|
|
d2a7d39749 | ||
|
|
9521718120 | ||
|
|
28909e33ca | ||
|
|
79632b1d34 | ||
|
|
cf6d03e35c | ||
|
|
4a4b31a15c | ||
|
|
f3d9aec8fc | ||
|
|
7ad64ff16b | ||
|
|
6153ada33b | ||
|
|
be48c950b4 | ||
|
|
0487b8c178 | ||
|
|
5740015f56 | ||
|
|
c84004bfa3 | ||
|
|
c746a3711f | ||
|
|
aa7774a9a6 | ||
|
|
a836120945 | ||
|
|
7d60df9075 | ||
|
|
f2b8b26bc4 |
1
.gitignore
vendored
@@ -20,6 +20,7 @@ DerivedData
|
||||
|
||||
# Exclude system ROMs
|
||||
ROMImages/*
|
||||
OSBindings/Mac/Clock SignalTests/Atari\ ROMs
|
||||
|
||||
# CocoaPods
|
||||
#
|
||||
|
||||
@@ -12,38 +12,35 @@
|
||||
using namespace WD;
|
||||
|
||||
WD1770::Status::Status() :
|
||||
type(Status::One),
|
||||
write_protect(false),
|
||||
record_type(false),
|
||||
spin_up(false),
|
||||
record_not_found(false),
|
||||
crc_error(false),
|
||||
seek_error(false),
|
||||
lost_data(false),
|
||||
data_request(false),
|
||||
interrupt_request(false),
|
||||
busy(false)
|
||||
{}
|
||||
type(Status::One),
|
||||
write_protect(false),
|
||||
record_type(false),
|
||||
spin_up(false),
|
||||
record_not_found(false),
|
||||
crc_error(false),
|
||||
seek_error(false),
|
||||
lost_data(false),
|
||||
data_request(false),
|
||||
interrupt_request(false),
|
||||
busy(false) {}
|
||||
|
||||
WD1770::WD1770(Personality p) :
|
||||
Storage::Disk::Controller(8000000, 16, 300),
|
||||
crc_generator_(0x1021, 0xffff),
|
||||
interesting_event_mask_(Event::Command),
|
||||
resume_point_(0),
|
||||
delay_time_(0),
|
||||
index_hole_count_target_(-1),
|
||||
is_awaiting_marker_value_(false),
|
||||
data_mode_(DataMode::Scanning),
|
||||
delegate_(nullptr),
|
||||
personality_(p),
|
||||
head_is_loaded_(false)
|
||||
{
|
||||
Storage::Disk::Controller(8000000, 16, 300),
|
||||
crc_generator_(0x1021, 0xffff),
|
||||
interesting_event_mask_(Event::Command),
|
||||
resume_point_(0),
|
||||
delay_time_(0),
|
||||
index_hole_count_target_(-1),
|
||||
is_awaiting_marker_value_(false),
|
||||
data_mode_(DataMode::Scanning),
|
||||
delegate_(nullptr),
|
||||
personality_(p),
|
||||
head_is_loaded_(false) {
|
||||
set_is_double_density(false);
|
||||
posit_event(Event::Command);
|
||||
}
|
||||
|
||||
void WD1770::set_is_double_density(bool is_double_density)
|
||||
{
|
||||
void WD1770::set_is_double_density(bool is_double_density) {
|
||||
is_double_density_ = is_double_density;
|
||||
Storage::Time bit_length;
|
||||
bit_length.length = 1;
|
||||
@@ -53,21 +50,15 @@ void WD1770::set_is_double_density(bool is_double_density)
|
||||
if(!is_double_density) is_awaiting_marker_value_ = false;
|
||||
}
|
||||
|
||||
void WD1770::set_register(int address, uint8_t value)
|
||||
{
|
||||
switch(address&3)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
if((value&0xf0) == 0xd0)
|
||||
{
|
||||
void WD1770::set_register(int address, uint8_t value) {
|
||||
switch(address&3) {
|
||||
case 0: {
|
||||
if((value&0xf0) == 0xd0) {
|
||||
printf("!!!TODO: force interrupt!!!\n");
|
||||
update_status([] (Status &status) {
|
||||
status.type = Status::One;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
command_ = value;
|
||||
posit_event(Event::Command);
|
||||
}
|
||||
@@ -84,12 +75,9 @@ void WD1770::set_register(int address, uint8_t value)
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t WD1770::get_register(int address)
|
||||
{
|
||||
switch(address&3)
|
||||
{
|
||||
default:
|
||||
{
|
||||
uint8_t WD1770::get_register(int address) {
|
||||
switch(address&3) {
|
||||
default: {
|
||||
update_status([] (Status &status) {
|
||||
status.interrupt_request = false;
|
||||
});
|
||||
@@ -97,8 +85,7 @@ uint8_t WD1770::get_register(int address)
|
||||
(status_.write_protect ? Flag::WriteProtect : 0) |
|
||||
(status_.crc_error ? Flag::CRCError : 0) |
|
||||
(status_.busy ? Flag::Busy : 0);
|
||||
switch(status_.type)
|
||||
{
|
||||
switch(status_.type) {
|
||||
case Status::One:
|
||||
status |=
|
||||
(get_is_track_zero() ? Flag::TrackZero : 0) |
|
||||
@@ -116,14 +103,11 @@ uint8_t WD1770::get_register(int address)
|
||||
break;
|
||||
}
|
||||
|
||||
if(!has_motor_on_line())
|
||||
{
|
||||
if(!has_motor_on_line()) {
|
||||
status |= get_drive_is_ready() ? 0 : Flag::NotReady;
|
||||
if(status_.type == Status::One)
|
||||
status |= (head_is_loaded_ ? Flag::HeadLoaded : 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
status |= (get_motor_on() ? Flag::MotorOn : 0);
|
||||
if(status_.type == Status::One)
|
||||
status |= (status_.spin_up ? Flag::SpinUp : 0);
|
||||
@@ -140,38 +124,29 @@ uint8_t WD1770::get_register(int address)
|
||||
}
|
||||
}
|
||||
|
||||
void WD1770::run_for_cycles(unsigned int number_of_cycles)
|
||||
{
|
||||
void WD1770::run_for_cycles(unsigned int number_of_cycles) {
|
||||
Storage::Disk::Controller::run_for_cycles((int)number_of_cycles);
|
||||
|
||||
if(delay_time_)
|
||||
{
|
||||
if(delay_time_ <= number_of_cycles)
|
||||
{
|
||||
if(delay_time_) {
|
||||
if(delay_time_ <= number_of_cycles) {
|
||||
delay_time_ = 0;
|
||||
posit_event(Event::Timer);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
delay_time_ -= number_of_cycles;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole)
|
||||
{
|
||||
void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole) {
|
||||
if(data_mode_ == DataMode::Writing) return;
|
||||
|
||||
shift_register_ = (shift_register_ << 1) | value;
|
||||
bits_since_token_++;
|
||||
|
||||
if(data_mode_ == DataMode::Scanning)
|
||||
{
|
||||
if(data_mode_ == DataMode::Scanning) {
|
||||
Token::Type token_type = Token::Byte;
|
||||
if(!is_double_density_)
|
||||
{
|
||||
switch(shift_register_ & 0xffff)
|
||||
{
|
||||
if(!is_double_density_) {
|
||||
switch(shift_register_ & 0xffff) {
|
||||
case Storage::Encodings::MFM::FMIndexAddressMark:
|
||||
token_type = Token::Index;
|
||||
crc_generator_.reset();
|
||||
@@ -195,11 +170,8 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch(shift_register_ & 0xffff)
|
||||
{
|
||||
} else {
|
||||
switch(shift_register_ & 0xffff) {
|
||||
case Storage::Encodings::MFM::MFMIndexSync:
|
||||
bits_since_token_ = 0;
|
||||
is_awaiting_marker_value_ = true;
|
||||
@@ -220,8 +192,7 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole)
|
||||
}
|
||||
}
|
||||
|
||||
if(token_type != Token::Byte)
|
||||
{
|
||||
if(token_type != Token::Byte) {
|
||||
latest_token_.type = token_type;
|
||||
bits_since_token_ = 0;
|
||||
posit_event(Event::Token);
|
||||
@@ -229,8 +200,7 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole)
|
||||
}
|
||||
}
|
||||
|
||||
if(bits_since_token_ == 16)
|
||||
{
|
||||
if(bits_since_token_ == 16) {
|
||||
latest_token_.type = Token::Byte;
|
||||
latest_token_.byte_value = (uint8_t)(
|
||||
((shift_register_ & 0x0001) >> 0) |
|
||||
@@ -243,11 +213,9 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole)
|
||||
((shift_register_ & 0x4000) >> 7));
|
||||
bits_since_token_ = 0;
|
||||
|
||||
if(is_awaiting_marker_value_ && is_double_density_)
|
||||
{
|
||||
if(is_awaiting_marker_value_ && is_double_density_) {
|
||||
is_awaiting_marker_value_ = false;
|
||||
switch(latest_token_.byte_value)
|
||||
{
|
||||
switch(latest_token_.byte_value) {
|
||||
case Storage::Encodings::MFM::IndexAddressByte:
|
||||
latest_token_.type = Token::Index;
|
||||
break;
|
||||
@@ -270,31 +238,26 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole)
|
||||
}
|
||||
}
|
||||
|
||||
void WD1770::process_index_hole()
|
||||
{
|
||||
void WD1770::process_index_hole() {
|
||||
index_hole_count_++;
|
||||
posit_event(Event::IndexHole);
|
||||
if(index_hole_count_target_ == index_hole_count_)
|
||||
{
|
||||
if(index_hole_count_target_ == index_hole_count_) {
|
||||
posit_event(Event::IndexHoleTarget);
|
||||
index_hole_count_target_ = -1;
|
||||
}
|
||||
|
||||
// motor power-down
|
||||
if(index_hole_count_ == 9 && !status_.busy && has_motor_on_line())
|
||||
{
|
||||
if(index_hole_count_ == 9 && !status_.busy && has_motor_on_line()) {
|
||||
set_motor_on(false);
|
||||
}
|
||||
|
||||
// head unload
|
||||
if(index_hole_count_ == 15 && !status_.busy && has_head_load_line())
|
||||
{
|
||||
if(index_hole_count_ == 15 && !status_.busy && has_head_load_line()) {
|
||||
set_head_load_request(false);
|
||||
}
|
||||
}
|
||||
|
||||
void WD1770::process_write_completed()
|
||||
{
|
||||
void WD1770::process_write_completed() {
|
||||
posit_event(Event::DataWritten);
|
||||
}
|
||||
|
||||
@@ -305,11 +268,9 @@ void WD1770::process_write_completed()
|
||||
#define END_SECTION() 0; }
|
||||
|
||||
#define READ_ID() \
|
||||
if(new_event_type == Event::Token) \
|
||||
{ \
|
||||
if(new_event_type == Event::Token) { \
|
||||
if(!distance_into_section_ && latest_token_.type == Token::ID) {data_mode_ = DataMode::Reading; distance_into_section_++; } \
|
||||
else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte) \
|
||||
{ \
|
||||
else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte) { \
|
||||
header_[distance_into_section_ - 1] = latest_token_.byte_value; \
|
||||
distance_into_section_++; \
|
||||
} \
|
||||
@@ -343,8 +304,7 @@ void WD1770::process_write_completed()
|
||||
// ! 4 ! Forc int ! 1 1 0 1 i3 i2 i1 i0 !
|
||||
// +--------+----------+-------------------------+
|
||||
|
||||
void WD1770::posit_event(Event new_event_type)
|
||||
{
|
||||
void WD1770::posit_event(Event new_event_type) {
|
||||
if(!(interesting_event_mask_ & (int)new_event_type)) return;
|
||||
interesting_event_mask_ &= ~new_event_type;
|
||||
|
||||
@@ -405,8 +365,7 @@ void WD1770::posit_event(Event new_event_type)
|
||||
goto begin_type1_load_head;
|
||||
|
||||
begin_type1_load_head:
|
||||
if(!(command_&0x08))
|
||||
{
|
||||
if(!(command_&0x08)) {
|
||||
set_head_load_request(false);
|
||||
goto test_type1_type;
|
||||
}
|
||||
@@ -426,8 +385,7 @@ void WD1770::posit_event(Event new_event_type)
|
||||
if((command_ >> 5) != 0) goto perform_step_command;
|
||||
|
||||
// This is now definitely either a seek or a restore; if it's a restore then set track to 0xff and data to 0x00.
|
||||
if(!(command_ & 0x10))
|
||||
{
|
||||
if(!(command_ & 0x10)) {
|
||||
track_ = 0xff;
|
||||
data_ = 0;
|
||||
}
|
||||
@@ -440,15 +398,13 @@ void WD1770::posit_event(Event new_event_type)
|
||||
if(step_direction_) track_++; else track_--;
|
||||
|
||||
perform_step:
|
||||
if(!step_direction_ && get_is_track_zero())
|
||||
{
|
||||
if(!step_direction_ && get_is_track_zero()) {
|
||||
track_ = 0;
|
||||
goto verify;
|
||||
}
|
||||
step(step_direction_ ? 1 : -1);
|
||||
int time_to_wait;
|
||||
switch(command_ & 3)
|
||||
{
|
||||
switch(command_ & 3) {
|
||||
default:
|
||||
case 0: time_to_wait = 6; break;
|
||||
case 1: time_to_wait = 12; break;
|
||||
@@ -464,8 +420,7 @@ void WD1770::posit_event(Event new_event_type)
|
||||
goto perform_step;
|
||||
|
||||
verify:
|
||||
if(!(command_ & 0x04))
|
||||
{
|
||||
if(!(command_ & 0x04)) {
|
||||
goto wait_for_command;
|
||||
}
|
||||
|
||||
@@ -476,26 +431,22 @@ void WD1770::posit_event(Event new_event_type)
|
||||
WAIT_FOR_EVENT(Event::IndexHole | Event::Token);
|
||||
READ_ID();
|
||||
|
||||
if(index_hole_count_ == 6)
|
||||
{
|
||||
if(index_hole_count_ == 6) {
|
||||
update_status([] (Status &status) {
|
||||
status.seek_error = true;
|
||||
});
|
||||
goto wait_for_command;
|
||||
}
|
||||
if(distance_into_section_ == 7)
|
||||
{
|
||||
if(distance_into_section_ == 7) {
|
||||
data_mode_ = DataMode::Scanning;
|
||||
if(crc_generator_.get_value())
|
||||
{
|
||||
if(crc_generator_.get_value()) {
|
||||
update_status([] (Status &status) {
|
||||
status.crc_error = true;
|
||||
});
|
||||
goto verify_read_data;
|
||||
}
|
||||
|
||||
if(header_[0] == track_)
|
||||
{
|
||||
if(header_[0] == track_) {
|
||||
printf("Reached track %d\n", track_);
|
||||
update_status([] (Status &status) {
|
||||
status.crc_error = false;
|
||||
@@ -553,8 +504,7 @@ void WD1770::posit_event(Event new_event_type)
|
||||
WAIT_FOR_TIME(30);
|
||||
|
||||
test_type2_write_protection:
|
||||
if(command_&0x20 && get_drive_is_read_only())
|
||||
{
|
||||
if(command_&0x20 && get_drive_is_read_only()) {
|
||||
update_status([] (Status &status) {
|
||||
status.write_protect = true;
|
||||
});
|
||||
@@ -565,24 +515,20 @@ void WD1770::posit_event(Event new_event_type)
|
||||
WAIT_FOR_EVENT(Event::IndexHole | Event::Token);
|
||||
READ_ID();
|
||||
|
||||
if(index_hole_count_ == 5)
|
||||
{
|
||||
if(index_hole_count_ == 5) {
|
||||
printf("Failed to find sector %d\n", sector_);
|
||||
update_status([] (Status &status) {
|
||||
status.record_not_found = true;
|
||||
});
|
||||
goto wait_for_command;
|
||||
}
|
||||
if(distance_into_section_ == 7)
|
||||
{
|
||||
if(distance_into_section_ == 7) {
|
||||
printf("Considering %d/%d\n", header_[0], header_[2]);
|
||||
data_mode_ = DataMode::Scanning;
|
||||
if(header_[0] == track_ && header_[2] == sector_ &&
|
||||
(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1]))
|
||||
{
|
||||
if( header_[0] == track_ && header_[2] == sector_ &&
|
||||
(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1])) {
|
||||
printf("Found %d/%d\n", header_[0], header_[2]);
|
||||
if(crc_generator_.get_value())
|
||||
{
|
||||
if(crc_generator_.get_value()) {
|
||||
printf("CRC error; back to searching\n");
|
||||
update_status([] (Status &status) {
|
||||
status.crc_error = true;
|
||||
@@ -607,8 +553,7 @@ void WD1770::posit_event(Event new_event_type)
|
||||
type2_read_data:
|
||||
WAIT_FOR_EVENT(Event::Token);
|
||||
// TODO: timeout
|
||||
if(latest_token_.type == Token::Data || latest_token_.type == Token::DeletedData)
|
||||
{
|
||||
if(latest_token_.type == Token::Data || latest_token_.type == Token::DeletedData) {
|
||||
update_status([this] (Status &status) {
|
||||
status.record_type = (latest_token_.type == Token::DeletedData);
|
||||
});
|
||||
@@ -627,8 +572,7 @@ void WD1770::posit_event(Event new_event_type)
|
||||
status.data_request = true;
|
||||
});
|
||||
distance_into_section_++;
|
||||
if(distance_into_section_ == 128 << header_[3])
|
||||
{
|
||||
if(distance_into_section_ == 128 << header_[3]) {
|
||||
distance_into_section_ = 0;
|
||||
goto type2_check_crc;
|
||||
}
|
||||
@@ -639,10 +583,8 @@ void WD1770::posit_event(Event new_event_type)
|
||||
if(latest_token_.type != Token::Byte) goto type2_read_byte;
|
||||
header_[distance_into_section_] = latest_token_.byte_value;
|
||||
distance_into_section_++;
|
||||
if(distance_into_section_ == 2)
|
||||
{
|
||||
if(crc_generator_.get_value())
|
||||
{
|
||||
if(distance_into_section_ == 2) {
|
||||
if(crc_generator_.get_value()) {
|
||||
printf("CRC error; terminating\n");
|
||||
update_status([this] (Status &status) {
|
||||
status.crc_error = true;
|
||||
@@ -650,8 +592,7 @@ void WD1770::posit_event(Event new_event_type)
|
||||
goto wait_for_command;
|
||||
}
|
||||
|
||||
if(command_ & 0x10)
|
||||
{
|
||||
if(command_ & 0x10) {
|
||||
sector_++;
|
||||
goto test_type2_write_protection;
|
||||
}
|
||||
@@ -667,35 +608,29 @@ void WD1770::posit_event(Event new_event_type)
|
||||
status.data_request = true;
|
||||
});
|
||||
WAIT_FOR_BYTES(9);
|
||||
if(status_.data_request)
|
||||
{
|
||||
if(status_.data_request) {
|
||||
update_status([] (Status &status) {
|
||||
status.lost_data = true;
|
||||
});
|
||||
goto wait_for_command;
|
||||
}
|
||||
WAIT_FOR_BYTES(1);
|
||||
if(is_double_density_)
|
||||
{
|
||||
if(is_double_density_) {
|
||||
WAIT_FOR_BYTES(11);
|
||||
}
|
||||
|
||||
data_mode_ = DataMode::Writing;
|
||||
begin_writing();
|
||||
for(int c = 0; c < (is_double_density_ ? 12 : 6); c++)
|
||||
{
|
||||
for(int c = 0; c < (is_double_density_ ? 12 : 6); c++) {
|
||||
write_byte(0);
|
||||
}
|
||||
WAIT_FOR_EVENT(Event::DataWritten);
|
||||
|
||||
if(is_double_density_)
|
||||
{
|
||||
if(is_double_density_) {
|
||||
crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
|
||||
for(int c = 0; c < 3; c++) write_raw_short(Storage::Encodings::MFM::MFMSync);
|
||||
write_byte((command_&0x01) ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
crc_generator_.reset();
|
||||
crc_generator_.add((command_&0x01) ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte);
|
||||
write_raw_short((command_&0x01) ? Storage::Encodings::MFM::FMDeletedDataAddressMark : Storage::Encodings::MFM::FMDataAddressMark);
|
||||
@@ -715,8 +650,7 @@ void WD1770::posit_event(Event new_event_type)
|
||||
*/
|
||||
write_byte(data_);
|
||||
distance_into_section_++;
|
||||
if(distance_into_section_ == 128 << header_[3])
|
||||
{
|
||||
if(distance_into_section_ == 128 << header_[3]) {
|
||||
goto type2_write_crc;
|
||||
}
|
||||
|
||||
@@ -724,8 +658,7 @@ void WD1770::posit_event(Event new_event_type)
|
||||
status.data_request = true;
|
||||
});
|
||||
WAIT_FOR_EVENT(Event::DataWritten);
|
||||
if(status_.data_request)
|
||||
{
|
||||
if(status_.data_request) {
|
||||
end_writing();
|
||||
update_status([] (Status &status) {
|
||||
status.lost_data = true;
|
||||
@@ -735,8 +668,7 @@ void WD1770::posit_event(Event new_event_type)
|
||||
|
||||
goto type2_write_loop;
|
||||
|
||||
type2_write_crc:
|
||||
{
|
||||
type2_write_crc: {
|
||||
uint16_t crc = crc_generator_.get_value();
|
||||
write_byte(crc >> 8);
|
||||
write_byte(crc & 0xff);
|
||||
@@ -745,8 +677,7 @@ void WD1770::posit_event(Event new_event_type)
|
||||
WAIT_FOR_EVENT(Event::DataWritten);
|
||||
end_writing();
|
||||
|
||||
if(command_ & 0x10)
|
||||
{
|
||||
if(command_ & 0x10) {
|
||||
sector_++;
|
||||
goto test_type2_write_protection;
|
||||
}
|
||||
@@ -802,13 +733,10 @@ void WD1770::posit_event(Event new_event_type)
|
||||
|
||||
read_address_get_header:
|
||||
WAIT_FOR_EVENT(Event::IndexHole | Event::Token);
|
||||
if(new_event_type == Event::Token)
|
||||
{
|
||||
if(new_event_type == Event::Token) {
|
||||
if(!distance_into_section_ && latest_token_.type == Token::ID) {data_mode_ = DataMode::Reading; distance_into_section_++; }
|
||||
else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte)
|
||||
{
|
||||
if(status_.data_request)
|
||||
{
|
||||
else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte) {
|
||||
if(status_.data_request) {
|
||||
update_status([] (Status &status) {
|
||||
status.lost_data = true;
|
||||
});
|
||||
@@ -821,10 +749,8 @@ void WD1770::posit_event(Event new_event_type)
|
||||
});
|
||||
distance_into_section_++;
|
||||
|
||||
if(distance_into_section_ == 7)
|
||||
{
|
||||
if(crc_generator_.get_value())
|
||||
{
|
||||
if(distance_into_section_ == 7) {
|
||||
if(crc_generator_.get_value()) {
|
||||
update_status([] (Status &status) {
|
||||
status.crc_error = true;
|
||||
});
|
||||
@@ -834,8 +760,7 @@ void WD1770::posit_event(Event new_event_type)
|
||||
}
|
||||
}
|
||||
|
||||
if(index_hole_count_ == 6)
|
||||
{
|
||||
if(index_hole_count_ == 6) {
|
||||
update_status([] (Status &status) {
|
||||
status.record_not_found = true;
|
||||
});
|
||||
@@ -849,12 +774,10 @@ void WD1770::posit_event(Event new_event_type)
|
||||
|
||||
read_track_read_byte:
|
||||
WAIT_FOR_EVENT(Event::Token | Event::IndexHole);
|
||||
if(index_hole_count_)
|
||||
{
|
||||
if(index_hole_count_) {
|
||||
goto wait_for_command;
|
||||
}
|
||||
if(status_.data_request)
|
||||
{
|
||||
if(status_.data_request) {
|
||||
update_status([] (Status &status) {
|
||||
status.lost_data = true;
|
||||
});
|
||||
@@ -873,8 +796,7 @@ void WD1770::posit_event(Event new_event_type)
|
||||
});
|
||||
|
||||
write_track_test_write_protect:
|
||||
if(get_drive_is_read_only())
|
||||
{
|
||||
if(get_drive_is_read_only()) {
|
||||
update_status([] (Status &status) {
|
||||
status.write_protect = true;
|
||||
});
|
||||
@@ -885,8 +807,7 @@ void WD1770::posit_event(Event new_event_type)
|
||||
status.data_request = true;
|
||||
});
|
||||
WAIT_FOR_BYTES(3);
|
||||
if(status_.data_request)
|
||||
{
|
||||
if(status_.data_request) {
|
||||
update_status([] (Status &status) {
|
||||
status.lost_data = true;
|
||||
});
|
||||
@@ -898,10 +819,8 @@ void WD1770::posit_event(Event new_event_type)
|
||||
index_hole_count_ = 0;
|
||||
|
||||
write_track_write_loop:
|
||||
if(is_double_density_)
|
||||
{
|
||||
switch(data_)
|
||||
{
|
||||
if(is_double_density_) {
|
||||
switch(data_) {
|
||||
case 0xf5:
|
||||
write_raw_short(Storage::Encodings::MFM::MFMSync);
|
||||
crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
|
||||
@@ -918,11 +837,8 @@ void WD1770::posit_event(Event new_event_type)
|
||||
write_byte(data_);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch(data_)
|
||||
{
|
||||
} else {
|
||||
switch(data_) {
|
||||
case 0xf8: case 0xf9: case 0xfa: case 0xfb:
|
||||
case 0xfd: case 0xfe:
|
||||
// clock is 0xc7 = 1010 0000 0010 1010 = 0xa022
|
||||
@@ -960,16 +876,14 @@ void WD1770::posit_event(Event new_event_type)
|
||||
status.data_request = true;
|
||||
});
|
||||
WAIT_FOR_EVENT(Event::DataWritten);
|
||||
if(status_.data_request)
|
||||
{
|
||||
if(status_.data_request) {
|
||||
update_status([] (Status &status) {
|
||||
status.lost_data = true;
|
||||
});
|
||||
end_writing();
|
||||
goto wait_for_command;
|
||||
}
|
||||
if(index_hole_count_)
|
||||
{
|
||||
if(index_hole_count_) {
|
||||
end_writing();
|
||||
goto wait_for_command;
|
||||
}
|
||||
@@ -979,10 +893,8 @@ void WD1770::posit_event(Event new_event_type)
|
||||
END_SECTION()
|
||||
}
|
||||
|
||||
void WD1770::update_status(std::function<void(Status &)> updater)
|
||||
{
|
||||
if(delegate_)
|
||||
{
|
||||
void WD1770::update_status(std::function<void(Status &)> updater) {
|
||||
if(delegate_) {
|
||||
Status old_status = status_;
|
||||
updater(status_);
|
||||
bool did_change =
|
||||
@@ -995,37 +907,29 @@ void WD1770::update_status(std::function<void(Status &)> updater)
|
||||
|
||||
void WD1770::set_head_load_request(bool head_load) {}
|
||||
|
||||
void WD1770::set_head_loaded(bool head_loaded)
|
||||
{
|
||||
void WD1770::set_head_loaded(bool head_loaded) {
|
||||
head_is_loaded_ = head_loaded;
|
||||
if(head_loaded) posit_event(Event::HeadLoad);
|
||||
}
|
||||
|
||||
void WD1770::write_bit(int bit)
|
||||
{
|
||||
if(is_double_density_)
|
||||
{
|
||||
void WD1770::write_bit(int bit) {
|
||||
if(is_double_density_) {
|
||||
Controller::write_bit(!bit && !last_bit_);
|
||||
Controller::write_bit(!!bit);
|
||||
last_bit_ = bit;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
Controller::write_bit(true);
|
||||
Controller::write_bit(!!bit);
|
||||
}
|
||||
}
|
||||
|
||||
void WD1770::write_byte(uint8_t byte)
|
||||
{
|
||||
void WD1770::write_byte(uint8_t byte) {
|
||||
for(int c = 0; c < 8; c++) write_bit((byte << c)&0x80);
|
||||
crc_generator_.add(byte);
|
||||
}
|
||||
|
||||
void WD1770::write_raw_short(uint16_t value)
|
||||
{
|
||||
for(int c = 0; c < 16; c++)
|
||||
{
|
||||
void WD1770::write_raw_short(uint16_t value) {
|
||||
for(int c = 0; c < 16; c++) {
|
||||
Controller::write_bit(!!((value << c)&0x8000));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,12 +50,10 @@ template <class T> class MOS6522 {
|
||||
};
|
||||
|
||||
/*! Sets a register value. */
|
||||
inline void set_register(int address, uint8_t 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)
|
||||
{
|
||||
switch(address) {
|
||||
case 0x0:
|
||||
registers_.output[1] = value;
|
||||
static_cast<T *>(this)->set_port_output(Port::B, value, registers_.data_direction[1]); // TODO: handshake
|
||||
@@ -88,8 +86,7 @@ template <class T> class MOS6522 {
|
||||
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)
|
||||
{
|
||||
if(address == 0x05) {
|
||||
registers_.next_timer[0] = registers_.timer_latch[0];
|
||||
timer_is_running_[0] = true;
|
||||
}
|
||||
@@ -117,19 +114,15 @@ template <class T> class MOS6522 {
|
||||
registers_.peripheral_control = value;
|
||||
|
||||
// TODO: simplify below; trying to avoid improper logging of unimplemented warnings in input mode
|
||||
if(value & 0x08)
|
||||
{
|
||||
switch(value & 0x0e)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
@@ -153,12 +146,10 @@ template <class T> class MOS6522 {
|
||||
}
|
||||
|
||||
/*! Gets a register value. */
|
||||
inline uint8_t get_register(int address)
|
||||
{
|
||||
inline uint8_t get_register(int address) {
|
||||
address &= 0xf;
|
||||
// printf("6522 %p: %d\n", this, address);
|
||||
switch(address)
|
||||
{
|
||||
switch(address) {
|
||||
case 0x0:
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
|
||||
reevaluate_interrupts();
|
||||
@@ -200,15 +191,12 @@ template <class T> class MOS6522 {
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
inline void set_control_line_input(Port port, Line line, bool value)
|
||||
{
|
||||
switch(line)
|
||||
{
|
||||
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();
|
||||
}
|
||||
@@ -220,8 +208,7 @@ template <class T> class MOS6522 {
|
||||
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();
|
||||
}
|
||||
@@ -234,8 +221,7 @@ template <class T> class MOS6522 {
|
||||
registers_.last_timer[0] = registers_.timer[0];\
|
||||
registers_.last_timer[1] = registers_.timer[1];\
|
||||
\
|
||||
if(registers_.timer_needs_reload)\
|
||||
{\
|
||||
if(registers_.timer_needs_reload) {\
|
||||
registers_.timer_needs_reload = false;\
|
||||
registers_.timer[0] = registers_.timer_latch[0];\
|
||||
}\
|
||||
@@ -248,15 +234,13 @@ template <class T> class MOS6522 {
|
||||
|
||||
// IRQ is raised on the half cycle after overflow
|
||||
#define phase1() \
|
||||
if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1])\
|
||||
{\
|
||||
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])\
|
||||
{\
|
||||
if((registers_.timer[0] == 0xffff) && !registers_.last_timer[0] && timer_is_running_[0]) {\
|
||||
registers_.interrupt_flags |= InterruptFlag::Timer1;\
|
||||
reevaluate_interrupts();\
|
||||
\
|
||||
@@ -279,28 +263,22 @@ template <class T> class MOS6522 {
|
||||
Callers should decide whether they are going to use @c run_for_half_cycles or @c run_for_cycles, and not
|
||||
intermingle usage.
|
||||
*/
|
||||
inline void run_for_half_cycles(unsigned int number_of_cycles)
|
||||
{
|
||||
if(is_phase2_)
|
||||
{
|
||||
inline void run_for_half_cycles(unsigned int number_of_cycles) {
|
||||
if(is_phase2_) {
|
||||
phase2();
|
||||
number_of_cycles--;
|
||||
}
|
||||
|
||||
while(number_of_cycles >= 2)
|
||||
{
|
||||
while(number_of_cycles >= 2) {
|
||||
phase1();
|
||||
phase2();
|
||||
number_of_cycles -= 2;
|
||||
}
|
||||
|
||||
if(number_of_cycles)
|
||||
{
|
||||
if(number_of_cycles) {
|
||||
phase1();
|
||||
is_phase2_ = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
is_phase2_ = false;
|
||||
}
|
||||
}
|
||||
@@ -311,10 +289,8 @@ template <class T> class MOS6522 {
|
||||
Callers should decide whether they are going to use @c run_for_half_cycles or @c run_for_cycles, and not
|
||||
intermingle usage.
|
||||
*/
|
||||
inline void run_for_cycles(unsigned int number_of_cycles)
|
||||
{
|
||||
while(number_of_cycles--)
|
||||
{
|
||||
inline void run_for_cycles(unsigned int number_of_cycles) {
|
||||
while(number_of_cycles--) {
|
||||
phase1();
|
||||
phase2();
|
||||
}
|
||||
@@ -324,8 +300,7 @@ template <class T> class MOS6522 {
|
||||
#undef phase2
|
||||
|
||||
/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
|
||||
inline bool get_interrupt_line()
|
||||
{
|
||||
inline bool get_interrupt_line() {
|
||||
uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
|
||||
return !!interrupt_status;
|
||||
}
|
||||
@@ -333,8 +308,7 @@ template <class T> class MOS6522 {
|
||||
MOS6522() :
|
||||
timer_is_running_{false, false},
|
||||
last_posted_interrupt_status_(false),
|
||||
is_phase2_(false)
|
||||
{}
|
||||
is_phase2_(false) {}
|
||||
|
||||
private:
|
||||
// Expected to be overridden
|
||||
@@ -344,8 +318,7 @@ template <class T> class MOS6522 {
|
||||
void set_interrupt_status(bool status) {}
|
||||
|
||||
// Input/output multiplexer
|
||||
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output)
|
||||
{
|
||||
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);
|
||||
}
|
||||
@@ -355,11 +328,9 @@ template <class T> class MOS6522 {
|
||||
|
||||
// Delegate and communications
|
||||
bool last_posted_interrupt_status_;
|
||||
inline void reevaluate_interrupts()
|
||||
{
|
||||
inline void reevaluate_interrupts() {
|
||||
bool new_interrupt_status = get_interrupt_line();
|
||||
if(new_interrupt_status != last_posted_interrupt_status_)
|
||||
{
|
||||
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);
|
||||
}
|
||||
@@ -404,13 +375,11 @@ class MOS6522IRQDelegate {
|
||||
virtual void mos6522_did_change_interrupt_status(void *mos6522) = 0;
|
||||
};
|
||||
|
||||
inline void set_interrupt_delegate(Delegate *delegate)
|
||||
{
|
||||
inline void set_interrupt_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
inline void set_interrupt_status(bool new_status)
|
||||
{
|
||||
inline void set_interrupt_status(bool new_status) {
|
||||
if(delegate_) delegate_->mos6522_did_change_interrupt_status(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,7 @@ template <class T> class MOS6532 {
|
||||
inline void set_ram(uint16_t address, uint8_t value) { ram_[address&0x7f] = value; }
|
||||
inline uint8_t get_ram(uint16_t address) { return ram_[address & 0x7f]; }
|
||||
|
||||
inline void set_register(int address, uint8_t value)
|
||||
{
|
||||
inline void set_register(int address, uint8_t value) {
|
||||
const uint8_t decodedAddress = address & 0x07;
|
||||
switch(decodedAddress) {
|
||||
// Port output
|
||||
@@ -48,16 +47,13 @@ template <class T> class MOS6532 {
|
||||
|
||||
// The timer and edge detect control
|
||||
case 0x04: case 0x05: case 0x06: case 0x07:
|
||||
if(address & 0x10)
|
||||
{
|
||||
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) | ((1 << timer_.activeShift)-1);
|
||||
timer_.value = ((unsigned int)value << timer_.activeShift) ;
|
||||
timer_.interrupt_enabled = !!(address&0x08);
|
||||
interrupt_status_ &= ~InterruptFlag::Timer;
|
||||
evaluate_interrupts();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
a7_interrupt_.enabled = !!(address&0x2);
|
||||
a7_interrupt_.active_on_positive = !!(address & 0x01);
|
||||
}
|
||||
@@ -65,13 +61,11 @@ template <class T> class MOS6532 {
|
||||
}
|
||||
}
|
||||
|
||||
inline uint8_t get_register(int address)
|
||||
{
|
||||
inline uint8_t get_register(int address) {
|
||||
const uint8_t decodedAddress = address & 0x7;
|
||||
switch(decodedAddress) {
|
||||
// Port input
|
||||
case 0x00: case 0x02:
|
||||
{
|
||||
case 0x00: case 0x02: {
|
||||
const int port = decodedAddress / 2;
|
||||
uint8_t input = static_cast<T *>(this)->get_port_input(port);
|
||||
return (input & ~port_[port].output_mask) | (port_[port].output & port_[port].output_mask);
|
||||
@@ -82,8 +76,7 @@ template <class T> class MOS6532 {
|
||||
break;
|
||||
|
||||
// Timer and interrupt control
|
||||
case 0x04: case 0x06:
|
||||
{
|
||||
case 0x04: case 0x06: {
|
||||
uint8_t value = (uint8_t)(timer_.value >> timer_.activeShift);
|
||||
timer_.interrupt_enabled = !!(address&0x08);
|
||||
interrupt_status_ &= ~InterruptFlag::Timer;
|
||||
@@ -99,8 +92,7 @@ template <class T> class MOS6532 {
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x05: case 0x07:
|
||||
{
|
||||
case 0x05: case 0x07: {
|
||||
uint8_t value = interrupt_status_;
|
||||
interrupt_status_ &= ~InterruptFlag::PA7;
|
||||
evaluate_interrupts();
|
||||
@@ -112,14 +104,13 @@ template <class T> class MOS6532 {
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
inline void run_for_cycles(unsigned int number_of_cycles)
|
||||
{
|
||||
inline void run_for_cycles(unsigned int number_of_cycles) {
|
||||
// permit counting _to_ zero; counting _through_ zero initiates the other behaviour
|
||||
if(timer_.value >= number_of_cycles) {
|
||||
timer_.value -= number_of_cycles;
|
||||
} else {
|
||||
number_of_cycles -= timer_.value;
|
||||
timer_.value = 0x100 - number_of_cycles;
|
||||
timer_.value = (0x100 - number_of_cycles) & 0xff;
|
||||
timer_.activeShift = 0;
|
||||
interrupt_status_ |= InterruptFlag::Timer;
|
||||
evaluate_interrupts();
|
||||
@@ -130,23 +121,19 @@ template <class T> class MOS6532 {
|
||||
interrupt_status_(0),
|
||||
port_{{.output_mask = 0, .output = 0}, {.output_mask = 0, .output = 0}},
|
||||
a7_interrupt_({.last_port_value = 0, .enabled = false}),
|
||||
interrupt_line_(false)
|
||||
{}
|
||||
interrupt_line_(false),
|
||||
timer_{.value = (unsigned int)((rand() & 0xff) << 10), .activeShift = 10, .writtenShift = 10, .interrupt_enabled = false} {}
|
||||
|
||||
inline void set_port_did_change(int port)
|
||||
{
|
||||
if(!port)
|
||||
{
|
||||
inline void set_port_did_change(int port) {
|
||||
if(!port) {
|
||||
uint8_t new_port_a_value = (get_port_input(0) & ~port_[0].output_mask) | (port_[0].output & port_[0].output_mask);
|
||||
uint8_t difference = new_port_a_value ^ a7_interrupt_.last_port_value;
|
||||
a7_interrupt_.last_port_value = new_port_a_value;
|
||||
if(difference&0x80)
|
||||
{
|
||||
if(difference&0x80) {
|
||||
if(
|
||||
((new_port_a_value&0x80) && a7_interrupt_.active_on_positive) ||
|
||||
(!(new_port_a_value&0x80) && !a7_interrupt_.active_on_positive)
|
||||
)
|
||||
{
|
||||
) {
|
||||
interrupt_status_ |= InterruptFlag::PA7;
|
||||
evaluate_interrupts();
|
||||
}
|
||||
@@ -154,8 +141,7 @@ template <class T> class MOS6532 {
|
||||
}
|
||||
}
|
||||
|
||||
inline bool get_inerrupt_line()
|
||||
{
|
||||
inline bool get_inerrupt_line() {
|
||||
return interrupt_line_;
|
||||
}
|
||||
|
||||
@@ -190,8 +176,7 @@ template <class T> class MOS6532 {
|
||||
void set_port_output(int port, uint8_t value, uint8_t output_mask) {}
|
||||
void set_irq_line(bool new_value) {}
|
||||
|
||||
inline void evaluate_interrupts()
|
||||
{
|
||||
inline void evaluate_interrupts() {
|
||||
interrupt_line_ =
|
||||
((interrupt_status_&InterruptFlag::Timer) && timer_.interrupt_enabled) ||
|
||||
((interrupt_status_&InterruptFlag::PA7) && a7_interrupt_.enabled);
|
||||
|
||||
@@ -14,18 +14,15 @@ Speaker::Speaker() :
|
||||
volume_(0),
|
||||
control_registers_{0, 0, 0, 0},
|
||||
shift_registers_{0, 0, 0, 0},
|
||||
counters_{2, 1, 0, 0} // create a slight phase offset for the three channels
|
||||
{}
|
||||
counters_{2, 1, 0, 0} {} // create a slight phase offset for the three channels
|
||||
|
||||
void Speaker::set_volume(uint8_t volume)
|
||||
{
|
||||
void Speaker::set_volume(uint8_t volume) {
|
||||
enqueue([=]() {
|
||||
volume_ = volume;
|
||||
});
|
||||
}
|
||||
|
||||
void Speaker::set_control(int channel, uint8_t value)
|
||||
{
|
||||
void Speaker::set_control(int channel, uint8_t value) {
|
||||
enqueue([=]() {
|
||||
control_registers_[channel] = value;
|
||||
});
|
||||
@@ -108,10 +105,8 @@ static uint8_t noise_pattern[] = {
|
||||
// testing against 0x80. The effect should be the same: loading with 0x7f means an output update every cycle, loading with 0x7e
|
||||
// means every second cycle, etc.
|
||||
|
||||
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target)
|
||||
{
|
||||
for(unsigned int c = 0; c < number_of_samples; c++)
|
||||
{
|
||||
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
|
||||
for(unsigned int c = 0; c < number_of_samples; c++) {
|
||||
update(0, 2, shift);
|
||||
update(1, 1, shift);
|
||||
update(2, 0, shift);
|
||||
@@ -128,10 +123,8 @@ void Speaker::get_samples(unsigned int number_of_samples, int16_t *target)
|
||||
}
|
||||
}
|
||||
|
||||
void Speaker::skip_samples(unsigned int number_of_samples)
|
||||
{
|
||||
for(unsigned int c = 0; c < number_of_samples; c++)
|
||||
{
|
||||
void Speaker::skip_samples(unsigned int number_of_samples) {
|
||||
for(unsigned int c = 0; c < number_of_samples; c++) {
|
||||
update(0, 2, shift);
|
||||
update(1, 1, shift);
|
||||
update(2, 0, shift);
|
||||
|
||||
@@ -43,14 +43,13 @@ class Speaker: public ::Outputs::Filter<Speaker> {
|
||||
template <class T> class MOS6560 {
|
||||
public:
|
||||
MOS6560() :
|
||||
crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::NTSC60, 1)),
|
||||
speaker_(new Speaker),
|
||||
horizontal_counter_(0),
|
||||
vertical_counter_(0),
|
||||
cycles_since_speaker_update_(0),
|
||||
is_odd_frame_(false),
|
||||
is_odd_line_(false)
|
||||
{
|
||||
crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::NTSC60, 1)),
|
||||
speaker_(new Speaker),
|
||||
horizontal_counter_(0),
|
||||
vertical_counter_(0),
|
||||
cycles_since_speaker_update_(0),
|
||||
is_odd_frame_(false),
|
||||
is_odd_line_(false) {
|
||||
crt_->set_composite_sampling_function(
|
||||
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
|
||||
"{"
|
||||
@@ -67,8 +66,7 @@ template <class T> class MOS6560 {
|
||||
set_output_mode(OutputMode::NTSC);
|
||||
}
|
||||
|
||||
void set_clock_rate(double clock_rate)
|
||||
{
|
||||
void set_clock_rate(double clock_rate) {
|
||||
speaker_->set_input_rate((float)(clock_rate / 4.0));
|
||||
}
|
||||
|
||||
@@ -82,8 +80,7 @@ template <class T> class MOS6560 {
|
||||
/*!
|
||||
Sets the output mode to either PAL or NTSC.
|
||||
*/
|
||||
void set_output_mode(OutputMode output_mode)
|
||||
{
|
||||
void set_output_mode(OutputMode output_mode) {
|
||||
output_mode_ = output_mode;
|
||||
uint8_t luminances[16] = { // range is 0–4
|
||||
0, 4, 1, 3, 2, 2, 1, 3,
|
||||
@@ -100,8 +97,7 @@ template <class T> class MOS6560 {
|
||||
uint8_t *chrominances;
|
||||
Outputs::CRT::DisplayType display_type;
|
||||
|
||||
switch(output_mode)
|
||||
{
|
||||
switch(output_mode) {
|
||||
case OutputMode::PAL:
|
||||
chrominances = pal_chrominances;
|
||||
display_type = Outputs::CRT::PAL50;
|
||||
@@ -124,8 +120,7 @@ template <class T> class MOS6560 {
|
||||
crt_->set_new_display_type((unsigned int)(timing_.cycles_per_line*4), display_type);
|
||||
// crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.1f, 0.8f, 0.8f));
|
||||
|
||||
// switch(output_mode)
|
||||
// {
|
||||
// switch(output_mode) {
|
||||
// case OutputMode::PAL:
|
||||
// crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 15*4, 55*4, 4.0f / 3.0f));
|
||||
// break;
|
||||
@@ -134,8 +129,7 @@ template <class T> class MOS6560 {
|
||||
// break;
|
||||
// }
|
||||
|
||||
for(int c = 0; c < 16; c++)
|
||||
{
|
||||
for(int c = 0; c < 16; c++) {
|
||||
colours_[c] = (uint8_t)((luminances[c] << 4) | chrominances[c]);
|
||||
}
|
||||
}
|
||||
@@ -143,23 +137,19 @@ template <class T> class MOS6560 {
|
||||
/*!
|
||||
Runs for cycles. Derr.
|
||||
*/
|
||||
inline void run_for_cycles(unsigned int number_of_cycles)
|
||||
{
|
||||
inline void run_for_cycles(unsigned int number_of_cycles) {
|
||||
// keep track of the amount of time since the speaker was updated; lazy updates are applied
|
||||
cycles_since_speaker_update_ += number_of_cycles;
|
||||
|
||||
while(number_of_cycles--)
|
||||
{
|
||||
while(number_of_cycles--) {
|
||||
// keep an old copy of the vertical count because that test is a cycle later than the actual changes
|
||||
int previous_vertical_counter = vertical_counter_;
|
||||
|
||||
// keep track of internal time relative to this scanline
|
||||
horizontal_counter_++;
|
||||
full_frame_counter_++;
|
||||
if(horizontal_counter_ == timing_.cycles_per_line)
|
||||
{
|
||||
if(horizontal_drawing_latch_)
|
||||
{
|
||||
if(horizontal_counter_ == timing_.cycles_per_line) {
|
||||
if(horizontal_drawing_latch_) {
|
||||
current_character_row_++;
|
||||
if(
|
||||
(current_character_row_ == 16) ||
|
||||
@@ -179,8 +169,7 @@ template <class T> class MOS6560 {
|
||||
horizontal_drawing_latch_ = false;
|
||||
|
||||
vertical_counter_ ++;
|
||||
if(vertical_counter_ == (registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field))
|
||||
{
|
||||
if(vertical_counter_ == (registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field)) {
|
||||
vertical_counter_ = 0;
|
||||
full_frame_counter_ = 0;
|
||||
|
||||
@@ -198,11 +187,9 @@ template <class T> class MOS6560 {
|
||||
horizontal_drawing_latch_ |= vertical_drawing_latch_ && (horizontal_counter_ == registers_.first_column_location);
|
||||
|
||||
if(pixel_line_cycle_ >= 0) pixel_line_cycle_++;
|
||||
switch(pixel_line_cycle_)
|
||||
{
|
||||
switch(pixel_line_cycle_) {
|
||||
case -1:
|
||||
if(horizontal_drawing_latch_)
|
||||
{
|
||||
if(horizontal_drawing_latch_) {
|
||||
pixel_line_cycle_ = 0;
|
||||
video_matrix_address_counter_ = base_video_matrix_address_counter_;
|
||||
}
|
||||
@@ -213,14 +200,10 @@ template <class T> class MOS6560 {
|
||||
}
|
||||
|
||||
uint16_t fetch_address = 0x1c;
|
||||
if(column_counter_ >= 0 && column_counter_ < columns_this_line_*2)
|
||||
{
|
||||
if(column_counter_&1)
|
||||
{
|
||||
if(column_counter_ >= 0 && column_counter_ < columns_this_line_*2) {
|
||||
if(column_counter_&1) {
|
||||
fetch_address = registers_.character_cell_start_address + (character_code_*(registers_.tall_characters ? 16 : 8)) + current_character_row_;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
fetch_address = (uint16_t)(registers_.video_matrix_start_address + video_matrix_address_counter_);
|
||||
video_matrix_address_counter_++;
|
||||
if(
|
||||
@@ -244,8 +227,7 @@ template <class T> class MOS6560 {
|
||||
// determine output state; colour burst and sync timing are currently a guess
|
||||
if(horizontal_counter_ > timing_.cycles_per_line-4) this_state_ = State::ColourBurst;
|
||||
else if(horizontal_counter_ > timing_.cycles_per_line-7) this_state_ = State::Sync;
|
||||
else
|
||||
{
|
||||
else {
|
||||
this_state_ = (column_counter_ >= 0 && column_counter_ < columns_this_line_*2) ? State::Pixels : State::Border;
|
||||
}
|
||||
|
||||
@@ -262,10 +244,8 @@ template <class T> class MOS6560 {
|
||||
this_state_ = State::Sync;
|
||||
|
||||
// update the CRT
|
||||
if(this_state_ != output_state_)
|
||||
{
|
||||
switch(output_state_)
|
||||
{
|
||||
if(this_state_ != output_state_) {
|
||||
switch(output_state_) {
|
||||
case State::Sync: crt_->output_sync(cycles_in_state_ * 4); break;
|
||||
case State::ColourBurst: crt_->output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0, 0); break;
|
||||
case State::Border: output_border(cycles_in_state_ * 4); break;
|
||||
@@ -275,32 +255,24 @@ template <class T> class MOS6560 {
|
||||
cycles_in_state_ = 0;
|
||||
|
||||
pixel_pointer = nullptr;
|
||||
if(output_state_ == State::Pixels)
|
||||
{
|
||||
if(output_state_ == State::Pixels) {
|
||||
pixel_pointer = crt_->allocate_write_area(260);
|
||||
}
|
||||
}
|
||||
cycles_in_state_++;
|
||||
|
||||
if(this_state_ == State::Pixels)
|
||||
{
|
||||
if(column_counter_&1)
|
||||
{
|
||||
if(this_state_ == State::Pixels) {
|
||||
if(column_counter_&1) {
|
||||
character_value_ = pixel_data;
|
||||
|
||||
if(pixel_pointer)
|
||||
{
|
||||
if(pixel_pointer) {
|
||||
uint8_t cell_colour = colours_[character_colour_ & 0x7];
|
||||
if(!(character_colour_&0x8))
|
||||
{
|
||||
if(!(character_colour_&0x8)) {
|
||||
uint8_t colours[2];
|
||||
if(registers_.invertedCells)
|
||||
{
|
||||
if(registers_.invertedCells) {
|
||||
colours[0] = cell_colour;
|
||||
colours[1] = registers_.backgroundColour;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
colours[0] = registers_.backgroundColour;
|
||||
colours[1] = cell_colour;
|
||||
}
|
||||
@@ -312,9 +284,7 @@ template <class T> class MOS6560 {
|
||||
pixel_pointer[5] = colours[(character_value_ >> 2)&1];
|
||||
pixel_pointer[6] = colours[(character_value_ >> 1)&1];
|
||||
pixel_pointer[7] = colours[(character_value_ >> 0)&1];
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
uint8_t colours[4] = {registers_.backgroundColour, registers_.borderColour, cell_colour, registers_.auxiliary_colour};
|
||||
pixel_pointer[0] =
|
||||
pixel_pointer[1] = colours[(character_value_ >> 6)&3];
|
||||
@@ -327,9 +297,7 @@ template <class T> class MOS6560 {
|
||||
}
|
||||
pixel_pointer += 8;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
character_code_ = pixel_data;
|
||||
character_colour_ = colour_data;
|
||||
}
|
||||
@@ -347,12 +315,10 @@ template <class T> class MOS6560 {
|
||||
/*!
|
||||
Writes to a 6560 register.
|
||||
*/
|
||||
void set_register(int address, uint8_t value)
|
||||
{
|
||||
void set_register(int address, uint8_t value) {
|
||||
address &= 0xf;
|
||||
registers_.direct_values[address] = value;
|
||||
switch(address)
|
||||
{
|
||||
switch(address) {
|
||||
case 0x0:
|
||||
registers_.interlaced = !!(value&0x80) && timing_.supports_interlacing;
|
||||
registers_.first_column_location = value & 0x7f;
|
||||
@@ -391,11 +357,9 @@ template <class T> class MOS6560 {
|
||||
speaker_->set_volume(value & 0xf);
|
||||
break;
|
||||
|
||||
case 0xf:
|
||||
{
|
||||
case 0xf: {
|
||||
uint8_t new_border_colour = colours_[value & 0x07];
|
||||
if(this_state_ == State::Border && new_border_colour != registers_.borderColour)
|
||||
{
|
||||
if(this_state_ == State::Border && new_border_colour != registers_.borderColour) {
|
||||
output_border(cycles_in_state_ * 4);
|
||||
cycles_in_state_ = 0;
|
||||
}
|
||||
@@ -415,12 +379,10 @@ template <class T> class MOS6560 {
|
||||
/*
|
||||
Reads from a 6560 register.
|
||||
*/
|
||||
uint8_t get_register(int address)
|
||||
{
|
||||
uint8_t get_register(int address) {
|
||||
address &= 0xf;
|
||||
int current_line = (full_frame_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line;
|
||||
switch(address)
|
||||
{
|
||||
switch(address) {
|
||||
default: return registers_.direct_values[address];
|
||||
case 0x03: return (uint8_t)(current_line << 7) | (registers_.direct_values[3] & 0x7f);
|
||||
case 0x04: return (current_line >> 1) & 0xff;
|
||||
@@ -432,8 +394,7 @@ template <class T> class MOS6560 {
|
||||
|
||||
std::shared_ptr<Speaker> speaker_;
|
||||
unsigned int cycles_since_speaker_update_;
|
||||
void update_audio()
|
||||
{
|
||||
void update_audio() {
|
||||
speaker_->run_for_cycles(cycles_since_speaker_update_ >> 2);
|
||||
cycles_since_speaker_update_ &= 3;
|
||||
}
|
||||
@@ -478,8 +439,7 @@ template <class T> class MOS6560 {
|
||||
uint8_t colours_[16];
|
||||
|
||||
uint8_t *pixel_pointer;
|
||||
void output_border(unsigned int number_of_cycles)
|
||||
{
|
||||
void output_border(unsigned int number_of_cycles) {
|
||||
uint8_t *colour_pointer = crt_->allocate_write_area(1);
|
||||
if(colour_pointer) *colour_pointer = registers_.borderColour;
|
||||
crt_->output_level(number_of_cycles);
|
||||
|
||||
@@ -11,22 +11,18 @@
|
||||
using namespace GI;
|
||||
|
||||
AY38910::AY38910() :
|
||||
selected_register_(0),
|
||||
tone_counters_{0, 0, 0}, tone_periods_{0, 0, 0}, tone_outputs_{0, 0, 0},
|
||||
noise_shift_register_(0xffff), noise_period_(0), noise_counter_(0), noise_output_(0),
|
||||
envelope_divider_(0), envelope_period_(0), envelope_position_(0),
|
||||
master_divider_(0),
|
||||
output_registers_{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
{
|
||||
selected_register_(0),
|
||||
tone_counters_{0, 0, 0}, tone_periods_{0, 0, 0}, tone_outputs_{0, 0, 0},
|
||||
noise_shift_register_(0xffff), noise_period_(0), noise_counter_(0), noise_output_(0),
|
||||
envelope_divider_(0), envelope_period_(0), envelope_position_(0),
|
||||
master_divider_(0),
|
||||
output_registers_{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} {
|
||||
output_registers_[8] = output_registers_[9] = output_registers_[10] = 0;
|
||||
|
||||
// set up envelope lookup tables
|
||||
for(int c = 0; c < 16; c++)
|
||||
{
|
||||
for(int p = 0; p < 32; p++)
|
||||
{
|
||||
switch(c)
|
||||
{
|
||||
for(int c = 0; c < 16; c++) {
|
||||
for(int p = 0; p < 32; p++) {
|
||||
switch(c) {
|
||||
case 0: case 1: case 2: case 3: case 9:
|
||||
envelope_shapes_[c][p] = (p < 16) ? (p^0xf) : 0;
|
||||
envelope_overflow_masks_[c] = 0x1f;
|
||||
@@ -69,34 +65,28 @@ AY38910::AY38910() :
|
||||
// set up volume lookup table
|
||||
float max_volume = 8192;
|
||||
float root_two = sqrtf(2.0f);
|
||||
for(int v = 0; v < 16; v++)
|
||||
{
|
||||
for(int v = 0; v < 16; v++) {
|
||||
volumes_[v] = (int)(max_volume / powf(root_two, (float)(v ^ 0xf)));
|
||||
}
|
||||
volumes_[0] = 0;
|
||||
}
|
||||
|
||||
void AY38910::set_clock_rate(double clock_rate)
|
||||
{
|
||||
void AY38910::set_clock_rate(double clock_rate) {
|
||||
set_input_rate((float)clock_rate);
|
||||
}
|
||||
|
||||
void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
|
||||
{
|
||||
void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) {
|
||||
int c = 0;
|
||||
while((master_divider_&15) && c < number_of_samples)
|
||||
{
|
||||
while((master_divider_&7) && c < number_of_samples) {
|
||||
target[c] = output_volume_;
|
||||
master_divider_++;
|
||||
c++;
|
||||
}
|
||||
|
||||
while(c < number_of_samples)
|
||||
{
|
||||
while(c < number_of_samples) {
|
||||
#define step_channel(c) \
|
||||
if(tone_counters_[c]) tone_counters_[c]--;\
|
||||
else\
|
||||
{\
|
||||
else {\
|
||||
tone_outputs_[c] ^= 1;\
|
||||
tone_counters_[c] = tone_periods_[c];\
|
||||
}
|
||||
@@ -111,8 +101,7 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
|
||||
// ... the noise generator. This recomputes the new bit repeatedly but harmlessly, only shifting
|
||||
// it into the official 17 upon divider underflow.
|
||||
if(noise_counter_) noise_counter_--;
|
||||
else
|
||||
{
|
||||
else {
|
||||
noise_counter_ = noise_period_;
|
||||
noise_output_ ^= noise_shift_register_&1;
|
||||
noise_shift_register_ |= ((noise_shift_register_ ^ (noise_shift_register_ >> 3))&1) << 17;
|
||||
@@ -122,8 +111,7 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
|
||||
// ... and the envelope generator. Table based for pattern lookup, with a 'refill' step — a way of
|
||||
// implementing non-repeating patterns by locking them to table position 0x1f.
|
||||
if(envelope_divider_) envelope_divider_--;
|
||||
else
|
||||
{
|
||||
else {
|
||||
envelope_divider_ = envelope_period_;
|
||||
envelope_position_ ++;
|
||||
if(envelope_position_ == 32) envelope_position_ = envelope_overflow_masks_[output_registers_[13]];
|
||||
@@ -131,19 +119,17 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
|
||||
|
||||
evaluate_output_volume();
|
||||
|
||||
for(int ic = 0; ic < 16 && c < number_of_samples; ic++)
|
||||
{
|
||||
for(int ic = 0; ic < 8 && c < number_of_samples; ic++) {
|
||||
target[c] = output_volume_;
|
||||
c++;
|
||||
master_divider_++;
|
||||
}
|
||||
}
|
||||
|
||||
master_divider_ &= 15;
|
||||
master_divider_ &= 7;
|
||||
}
|
||||
|
||||
void AY38910::evaluate_output_volume()
|
||||
{
|
||||
void AY38910::evaluate_output_volume() {
|
||||
int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_];
|
||||
|
||||
// The output level for a channel is:
|
||||
@@ -180,24 +166,19 @@ void AY38910::evaluate_output_volume()
|
||||
);
|
||||
}
|
||||
|
||||
void AY38910::select_register(uint8_t r)
|
||||
{
|
||||
void AY38910::select_register(uint8_t r) {
|
||||
selected_register_ = r & 0xf;
|
||||
}
|
||||
|
||||
void AY38910::set_register_value(uint8_t value)
|
||||
{
|
||||
void AY38910::set_register_value(uint8_t value) {
|
||||
registers_[selected_register_] = value;
|
||||
if(selected_register_ < 14)
|
||||
{
|
||||
if(selected_register_ < 14) {
|
||||
int selected_register = selected_register_;
|
||||
enqueue([=] () {
|
||||
uint8_t masked_value = value;
|
||||
switch(selected_register)
|
||||
{
|
||||
switch(selected_register) {
|
||||
case 0: case 2: case 4:
|
||||
case 1: case 3: case 5:
|
||||
{
|
||||
case 1: case 3: case 5: {
|
||||
int channel = selected_register >> 1;
|
||||
|
||||
if(selected_register & 1)
|
||||
@@ -234,8 +215,7 @@ void AY38910::set_register_value(uint8_t value)
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t AY38910::get_register_value()
|
||||
{
|
||||
uint8_t AY38910::get_register_value() {
|
||||
// This table ensures that bits that aren't defined within the AY are returned as 1s
|
||||
// when read. I can't find documentation on this and don't have a machine to test, so
|
||||
// this is provisionally a guess. TODO: investigate.
|
||||
@@ -247,26 +227,21 @@ uint8_t AY38910::get_register_value()
|
||||
return registers_[selected_register_] | register_masks[selected_register_];
|
||||
}
|
||||
|
||||
uint8_t AY38910::get_port_output(bool port_b)
|
||||
{
|
||||
uint8_t AY38910::get_port_output(bool port_b) {
|
||||
return registers_[port_b ? 15 : 14];
|
||||
}
|
||||
|
||||
void AY38910::set_data_input(uint8_t r)
|
||||
{
|
||||
void AY38910::set_data_input(uint8_t r) {
|
||||
data_input_ = r;
|
||||
}
|
||||
|
||||
uint8_t AY38910::get_data_output()
|
||||
{
|
||||
uint8_t AY38910::get_data_output() {
|
||||
return data_output_;
|
||||
}
|
||||
|
||||
void AY38910::set_control_lines(ControlLines control_lines)
|
||||
{
|
||||
void AY38910::set_control_lines(ControlLines control_lines) {
|
||||
ControlState new_state;
|
||||
switch((int)control_lines)
|
||||
{
|
||||
switch((int)control_lines) {
|
||||
default: new_state = Inactive; break;
|
||||
|
||||
case (int)(BCDIR | BC2 | BC1):
|
||||
@@ -277,11 +252,9 @@ void AY38910::set_control_lines(ControlLines control_lines)
|
||||
case (int)(BCDIR | BC2): new_state = Write; break;
|
||||
}
|
||||
|
||||
if(new_state != control_state_)
|
||||
{
|
||||
if(new_state != control_state_) {
|
||||
control_state_ = new_state;
|
||||
switch(new_state)
|
||||
{
|
||||
switch(new_state) {
|
||||
default: break;
|
||||
case LatchAddress: select_register(data_input_); break;
|
||||
case Write: set_register_value(data_input_); break;
|
||||
|
||||
@@ -19,26 +19,21 @@ AsyncTaskQueue::AsyncTaskQueue()
|
||||
serial_dispatch_queue_ = dispatch_queue_create("com.thomasharte.clocksignal.asyntaskqueue", DISPATCH_QUEUE_SERIAL);
|
||||
#else
|
||||
thread_.reset(new std::thread([this]() {
|
||||
while(!should_destruct_)
|
||||
{
|
||||
while(!should_destruct_) {
|
||||
std::function<void(void)> next_function;
|
||||
|
||||
// Take lock, check for a new task
|
||||
std::unique_lock<std::mutex> lock(queue_mutex_);
|
||||
if(!pending_tasks_.empty())
|
||||
{
|
||||
if(!pending_tasks_.empty()) {
|
||||
next_function = pending_tasks_.front();
|
||||
pending_tasks_.pop_front();
|
||||
}
|
||||
|
||||
if(next_function)
|
||||
{
|
||||
if(next_function) {
|
||||
// If there is a task, release lock and perform it
|
||||
lock.unlock();
|
||||
next_function();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// If there isn't a task, atomically block on the processing condition and release the lock
|
||||
// until there's something pending (and then release it again via scope)
|
||||
processing_condition_.wait(lock);
|
||||
@@ -48,8 +43,7 @@ AsyncTaskQueue::AsyncTaskQueue()
|
||||
#endif
|
||||
}
|
||||
|
||||
AsyncTaskQueue::~AsyncTaskQueue()
|
||||
{
|
||||
AsyncTaskQueue::~AsyncTaskQueue() {
|
||||
#ifdef __APPLE__
|
||||
dispatch_release(serial_dispatch_queue_);
|
||||
#else
|
||||
@@ -60,8 +54,7 @@ AsyncTaskQueue::~AsyncTaskQueue()
|
||||
#endif
|
||||
}
|
||||
|
||||
void AsyncTaskQueue::enqueue(std::function<void(void)> function)
|
||||
{
|
||||
void AsyncTaskQueue::enqueue(std::function<void(void)> function) {
|
||||
#ifdef __APPLE__
|
||||
dispatch_async(serial_dispatch_queue_, ^{function();});
|
||||
#else
|
||||
@@ -71,8 +64,7 @@ void AsyncTaskQueue::enqueue(std::function<void(void)> function)
|
||||
#endif
|
||||
}
|
||||
|
||||
void AsyncTaskQueue::flush()
|
||||
{
|
||||
void AsyncTaskQueue::flush() {
|
||||
#ifdef __APPLE__
|
||||
dispatch_sync(serial_dispatch_queue_, ^{});
|
||||
#else
|
||||
|
||||
@@ -10,772 +10,148 @@
|
||||
#include <algorithm>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "Cartridges/CartridgeAtari8k.hpp"
|
||||
#include "Cartridges/CartridgeAtari16k.hpp"
|
||||
#include "Cartridges/CartridgeAtari32k.hpp"
|
||||
#include "Cartridges/CartridgeActivisionStack.hpp"
|
||||
#include "Cartridges/CartridgeCBSRAMPlus.hpp"
|
||||
#include "Cartridges/CartridgeCommaVid.hpp"
|
||||
#include "Cartridges/CartridgeMegaBoy.hpp"
|
||||
#include "Cartridges/CartridgeMNetwork.hpp"
|
||||
#include "Cartridges/CartridgeParkerBros.hpp"
|
||||
#include "Cartridges/CartridgePitfall2.hpp"
|
||||
#include "Cartridges/CartridgeTigervision.hpp"
|
||||
#include "Cartridges/CartridgeUnpaged.hpp"
|
||||
|
||||
using namespace Atari2600;
|
||||
namespace {
|
||||
static const unsigned int horizontalTimerPeriod = 228;
|
||||
static const double NTSC_clock_rate = 1194720;
|
||||
static const double PAL_clock_rate = 1182298;
|
||||
}
|
||||
|
||||
Machine::Machine() :
|
||||
horizontal_timer_(0),
|
||||
last_output_state_duration_(0),
|
||||
last_output_state_(OutputState::Sync),
|
||||
rom_(nullptr),
|
||||
tia_input_value_{0xff, 0xff},
|
||||
upcoming_events_pointer_(0),
|
||||
object_counter_pointer_(0),
|
||||
state_by_time_(state_by_extend_time_[0]),
|
||||
cycles_since_speaker_update_(0),
|
||||
is_pal_region_(false)
|
||||
{
|
||||
memset(collisions_, 0xff, sizeof(collisions_));
|
||||
setup_reported_collisions();
|
||||
|
||||
for(int vbextend = 0; vbextend < 2; vbextend++)
|
||||
{
|
||||
for(int c = 0; c < 57; c++)
|
||||
{
|
||||
OutputState state;
|
||||
|
||||
// determine which output state will be active in four cycles from now
|
||||
switch(c)
|
||||
{
|
||||
case 0: case 1: case 2: case 3: state = OutputState::Blank; break;
|
||||
case 4: case 5: case 6: case 7: state = OutputState::Sync; break;
|
||||
case 8: case 9: case 10: case 11: state = OutputState::ColourBurst; break;
|
||||
case 12: case 13: case 14:
|
||||
case 15: case 16: state = OutputState::Blank; break;
|
||||
|
||||
case 17: case 18: state = vbextend ? OutputState::Blank : OutputState::Pixel; break;
|
||||
default: state = OutputState::Pixel; break;
|
||||
}
|
||||
|
||||
state_by_extend_time_[vbextend][c] = state;
|
||||
}
|
||||
}
|
||||
frame_record_pointer_(0),
|
||||
is_ntsc_(true) {
|
||||
set_clock_rate(NTSC_clock_rate);
|
||||
}
|
||||
|
||||
void Machine::setup_output(float aspect_ratio)
|
||||
{
|
||||
speaker_.reset(new Speaker);
|
||||
crt_.reset(new Outputs::CRT::CRT(228, 1, 263, Outputs::CRT::ColourSpace::YIQ, 228, 1, false, 1));
|
||||
crt_->set_output_device(Outputs::CRT::Television);
|
||||
|
||||
// this is the NTSC phase offset function; see below for PAL
|
||||
crt_->set_composite_sampling_function(
|
||||
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
|
||||
"{"
|
||||
"uint c = texture(texID, coordinate).r;"
|
||||
"uint y = c & 14u;"
|
||||
"uint iPhase = (c >> 4);"
|
||||
|
||||
"float phaseOffset = 6.283185308 * float(iPhase - 1u) / 13.0;"
|
||||
"return mix(float(y) / 14.0, step(1, iPhase) * cos(phase + phaseOffset), amplitude);"
|
||||
"}");
|
||||
speaker_->set_input_rate((float)(get_clock_rate() / 38.0));
|
||||
void Machine::setup_output(float aspect_ratio) {
|
||||
bus_->tia_.reset(new TIA);
|
||||
bus_->speaker_.reset(new Speaker);
|
||||
bus_->speaker_->set_input_rate((float)(get_clock_rate() / 38.0));
|
||||
bus_->tia_->get_crt()->set_delegate(this);
|
||||
}
|
||||
|
||||
void Machine::switch_region()
|
||||
{
|
||||
// the PAL function
|
||||
crt_->set_composite_sampling_function(
|
||||
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
|
||||
"{"
|
||||
"uint c = texture(texID, coordinate).r;"
|
||||
"uint y = c & 14u;"
|
||||
"uint iPhase = (c >> 4);"
|
||||
|
||||
"uint direction = iPhase & 1u;"
|
||||
"float phaseOffset = float(7u - direction) + (float(direction) - 0.5) * 2.0 * float(iPhase >> 1);"
|
||||
"phaseOffset *= 6.283185308 / 12.0;"
|
||||
"return mix(float(y) / 14.0, step(4, (iPhase + 2u) & 15u) * cos(phase + phaseOffset), amplitude);"
|
||||
"}");
|
||||
|
||||
crt_->set_new_timing(228, 312, Outputs::CRT::ColourSpace::YUV, 228, 1, true);
|
||||
|
||||
is_pal_region_ = true;
|
||||
speaker_->set_input_rate((float)(get_clock_rate() / 38.0));
|
||||
set_clock_rate(PAL_clock_rate);
|
||||
void Machine::close_output() {
|
||||
bus_.reset();
|
||||
}
|
||||
|
||||
void Machine::close_output()
|
||||
{
|
||||
crt_ = nullptr;
|
||||
}
|
||||
|
||||
Machine::~Machine()
|
||||
{
|
||||
delete[] rom_;
|
||||
Machine::~Machine() {
|
||||
close_output();
|
||||
}
|
||||
|
||||
void Machine::update_timers(int mask)
|
||||
{
|
||||
unsigned int upcoming_pointer_plus_4 = (upcoming_events_pointer_ + 4)%number_of_upcoming_events;
|
||||
|
||||
object_counter_pointer_ = (object_counter_pointer_ + 1)%number_of_recorded_counters;
|
||||
ObjectCounter *oneClockAgo = object_counter_[(object_counter_pointer_ - 1 + number_of_recorded_counters)%number_of_recorded_counters];
|
||||
ObjectCounter *twoClocksAgo = object_counter_[(object_counter_pointer_ - 2 + number_of_recorded_counters)%number_of_recorded_counters];
|
||||
ObjectCounter *now = object_counter_[object_counter_pointer_];
|
||||
|
||||
// grab the background now, for application in four clocks
|
||||
if(mask & (1 << 5) && !(horizontal_timer_&3))
|
||||
{
|
||||
unsigned int offset = 4 + horizontal_timer_ - (horizontalTimerPeriod - 160);
|
||||
upcoming_events_[upcoming_pointer_plus_4].updates |= Event::Action::Playfield;
|
||||
upcoming_events_[upcoming_pointer_plus_4].playfield_pixel = playfield_[(offset >> 2)%40];
|
||||
}
|
||||
|
||||
if(mask & (1 << 4))
|
||||
{
|
||||
// the ball becomes visible whenever it hits zero, regardless of whether its status
|
||||
// is the result of a counter rollover or a programmatic reset, and there's a four
|
||||
// clock delay on that triggering the start signal
|
||||
now[4].count = (oneClockAgo[4].count + 1)%160;
|
||||
now[4].pixel = oneClockAgo[4].pixel + 1;
|
||||
if(!now[4].count) now[4].pixel = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
now[4] = oneClockAgo[4];
|
||||
}
|
||||
|
||||
// check for player and missle triggers
|
||||
for(int c = 0; c < 4; c++)
|
||||
{
|
||||
if(mask & (1 << c))
|
||||
{
|
||||
// update the count
|
||||
now[c].count = (oneClockAgo[c].count + 1)%160;
|
||||
|
||||
uint8_t repeatMask = player_and_missile_size_[c&1] & 7;
|
||||
ObjectCounter *rollover;
|
||||
ObjectCounter *equality;
|
||||
|
||||
if(c < 2)
|
||||
{
|
||||
// update the pixel
|
||||
now[c].broad_pixel = oneClockAgo[c].broad_pixel + 1;
|
||||
switch(repeatMask)
|
||||
{
|
||||
default: now[c].pixel = oneClockAgo[c].pixel + 1; break;
|
||||
case 5: now[c].pixel = oneClockAgo[c].pixel + (now[c].broad_pixel&1); break;
|
||||
case 7: now[c].pixel = oneClockAgo[c].pixel + (((now[c].broad_pixel | (now[c].broad_pixel >> 1))^1)&1); break;
|
||||
}
|
||||
|
||||
// check for a rollover six clocks ago or equality five clocks ago
|
||||
rollover = twoClocksAgo;
|
||||
equality = oneClockAgo;
|
||||
}
|
||||
else
|
||||
{
|
||||
// update the pixel
|
||||
now[c].pixel = oneClockAgo[c].pixel + 1;
|
||||
|
||||
// check for a rollover five clocks ago or equality four clocks ago
|
||||
rollover = oneClockAgo;
|
||||
equality = now;
|
||||
}
|
||||
|
||||
if(
|
||||
(rollover[c].count == 159) ||
|
||||
(has_second_copy_[c&1] && equality[c].count == 16) ||
|
||||
(has_third_copy_[c&1] && equality[c].count == 32) ||
|
||||
(has_fourth_copy_[c&1] && equality[c].count == 64)
|
||||
)
|
||||
{
|
||||
now[c].pixel = 0;
|
||||
now[c].broad_pixel = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
now[c] = oneClockAgo[c];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Machine::get_output_pixel()
|
||||
{
|
||||
ObjectCounter *now = object_counter_[object_counter_pointer_];
|
||||
|
||||
// get the playfield pixel
|
||||
unsigned int offset = horizontal_timer_ - (horizontalTimerPeriod - 160);
|
||||
uint8_t playfieldColour = ((playfield_control_&6) == 2) ? player_colour_[offset / 80] : playfield_colour_;
|
||||
|
||||
// ball pixel
|
||||
uint8_t ballPixel = 0;
|
||||
if(now[4].pixel < ball_size_) {
|
||||
ballPixel = ball_graphics_enable_[ball_graphics_selector_];
|
||||
}
|
||||
|
||||
// determine the player and missile pixels
|
||||
uint8_t playerPixels[2] = { 0, 0 };
|
||||
uint8_t missilePixels[2] = { 0, 0 };
|
||||
for(int c = 0; c < 2; c++)
|
||||
{
|
||||
if(player_graphics_[c] && now[c].pixel < 8) {
|
||||
playerPixels[c] = (player_graphics_[player_graphics_selector_[c]][c] >> (now[c].pixel ^ player_reflection_mask_[c])) & 1;
|
||||
}
|
||||
|
||||
if(!missile_graphics_reset_[c] && now[c+2].pixel < missile_size_[c]) {
|
||||
missilePixels[c] = missile_graphics_enable_[c];
|
||||
}
|
||||
}
|
||||
|
||||
// accumulate collisions
|
||||
int pixel_mask = playerPixels[0] | (playerPixels[1] << 1) | (missilePixels[0] << 2) | (missilePixels[1] << 3) | (ballPixel << 4) | (playfield_output_ << 5);
|
||||
collisions_[0] |= reported_collisions_[pixel_mask][0];
|
||||
collisions_[1] |= reported_collisions_[pixel_mask][1];
|
||||
collisions_[2] |= reported_collisions_[pixel_mask][2];
|
||||
collisions_[3] |= reported_collisions_[pixel_mask][3];
|
||||
collisions_[4] |= reported_collisions_[pixel_mask][4];
|
||||
collisions_[5] |= reported_collisions_[pixel_mask][5];
|
||||
collisions_[6] |= reported_collisions_[pixel_mask][6];
|
||||
collisions_[7] |= reported_collisions_[pixel_mask][7];
|
||||
|
||||
// apply appropriate priority to pick a colour
|
||||
uint8_t playfield_pixel = playfield_output_ | ballPixel;
|
||||
uint8_t outputColour = playfield_pixel ? playfieldColour : background_colour_;
|
||||
|
||||
if(!(playfield_control_&0x04) || !playfield_pixel) {
|
||||
if(playerPixels[1] || missilePixels[1]) outputColour = player_colour_[1];
|
||||
if(playerPixels[0] || missilePixels[0]) outputColour = player_colour_[0];
|
||||
}
|
||||
|
||||
// return colour
|
||||
return outputColour;
|
||||
}
|
||||
|
||||
void Machine::setup_reported_collisions()
|
||||
{
|
||||
for(int c = 0; c < 64; c++)
|
||||
{
|
||||
memset(reported_collisions_[c], 0, 8);
|
||||
|
||||
int playerPixels[2] = { c&1, (c >> 1)&1 };
|
||||
int missilePixels[2] = { (c >> 2)&1, (c >> 3)&1 };
|
||||
int ballPixel = (c >> 4)&1;
|
||||
int playfield_pixel = (c >> 5)&1;
|
||||
|
||||
if(playerPixels[0] | playerPixels[1]) {
|
||||
reported_collisions_[c][0] |= ((missilePixels[0] & playerPixels[1]) << 7) | ((missilePixels[0] & playerPixels[0]) << 6);
|
||||
reported_collisions_[c][1] |= ((missilePixels[1] & playerPixels[0]) << 7) | ((missilePixels[1] & playerPixels[1]) << 6);
|
||||
|
||||
reported_collisions_[c][2] |= ((playfield_pixel & playerPixels[0]) << 7) | ((ballPixel & playerPixels[0]) << 6);
|
||||
reported_collisions_[c][3] |= ((playfield_pixel & playerPixels[1]) << 7) | ((ballPixel & playerPixels[1]) << 6);
|
||||
|
||||
reported_collisions_[c][7] |= ((playerPixels[0] & playerPixels[1]) << 7);
|
||||
}
|
||||
|
||||
if(playfield_pixel | ballPixel) {
|
||||
reported_collisions_[c][4] |= ((playfield_pixel & missilePixels[0]) << 7) | ((ballPixel & missilePixels[0]) << 6);
|
||||
reported_collisions_[c][5] |= ((playfield_pixel & missilePixels[1]) << 7) | ((ballPixel & missilePixels[1]) << 6);
|
||||
|
||||
reported_collisions_[c][6] |= ((playfield_pixel & ballPixel) << 7);
|
||||
}
|
||||
|
||||
if(missilePixels[0] & missilePixels[1])
|
||||
reported_collisions_[c][7] |= (1 << 6);
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::output_pixels(unsigned int count)
|
||||
{
|
||||
while(count--)
|
||||
{
|
||||
if(upcoming_events_[upcoming_events_pointer_].updates)
|
||||
{
|
||||
// apply any queued changes and flush the record
|
||||
if(upcoming_events_[upcoming_events_pointer_].updates & Event::Action::HMoveSetup)
|
||||
{
|
||||
// schedule an extended left border
|
||||
state_by_time_ = state_by_extend_time_[1];
|
||||
|
||||
// clear any ongoing moves
|
||||
if(hmove_flags_)
|
||||
{
|
||||
for(int c = 0; c < number_of_upcoming_events; c++)
|
||||
{
|
||||
upcoming_events_[c].updates &= ~(Event::Action::HMoveCompare | Event::Action::HMoveDecrement);
|
||||
}
|
||||
}
|
||||
|
||||
// schedule new moves
|
||||
hmove_flags_ = 0x1f;
|
||||
hmove_counter_ = 15;
|
||||
|
||||
// follow-through into a compare immediately
|
||||
upcoming_events_[upcoming_events_pointer_].updates |= Event::Action::HMoveCompare;
|
||||
}
|
||||
|
||||
if(upcoming_events_[upcoming_events_pointer_].updates & Event::Action::HMoveCompare)
|
||||
{
|
||||
for(int c = 0; c < 5; c++)
|
||||
{
|
||||
if(((object_motions_[c] >> 4)^hmove_counter_) == 7)
|
||||
{
|
||||
hmove_flags_ &= ~(1 << c);
|
||||
}
|
||||
}
|
||||
if(hmove_flags_)
|
||||
{
|
||||
if(hmove_counter_) hmove_counter_--;
|
||||
upcoming_events_[(upcoming_events_pointer_+4)%number_of_upcoming_events].updates |= Event::Action::HMoveCompare;
|
||||
upcoming_events_[(upcoming_events_pointer_+2)%number_of_upcoming_events].updates |= Event::Action::HMoveDecrement;
|
||||
}
|
||||
}
|
||||
|
||||
if(upcoming_events_[upcoming_events_pointer_].updates & Event::Action::HMoveDecrement)
|
||||
{
|
||||
update_timers(hmove_flags_);
|
||||
}
|
||||
|
||||
if(upcoming_events_[upcoming_events_pointer_].updates & Event::Action::ResetCounter)
|
||||
{
|
||||
object_counter_[object_counter_pointer_][upcoming_events_[upcoming_events_pointer_].counter].count = 0;
|
||||
}
|
||||
|
||||
// zero out current update event
|
||||
upcoming_events_[upcoming_events_pointer_].updates = 0;
|
||||
}
|
||||
|
||||
// progress to next event
|
||||
upcoming_events_pointer_ = (upcoming_events_pointer_ + 1)%number_of_upcoming_events;
|
||||
|
||||
// determine which output state is currently active
|
||||
OutputState primary_state = state_by_time_[horizontal_timer_ >> 2];
|
||||
OutputState effective_state = primary_state;
|
||||
|
||||
// update pixel timers
|
||||
if(primary_state == OutputState::Pixel) update_timers(~0);
|
||||
|
||||
// update the background chain
|
||||
if(horizontal_timer_ >= 64 && horizontal_timer_ <= 160+64 && !(horizontal_timer_&3))
|
||||
{
|
||||
playfield_output_ = next_playfield_output_;
|
||||
next_playfield_output_ = playfield_[(horizontal_timer_ - 64) >> 2];
|
||||
}
|
||||
|
||||
// if vsync is enabled, output the opposite of the automatic hsync output;
|
||||
// also honour the vertical blank flag
|
||||
if(vsync_enabled_) {
|
||||
effective_state = (effective_state = OutputState::Sync) ? OutputState::Blank : OutputState::Sync;
|
||||
} else if(vblank_enabled_ && effective_state == OutputState::Pixel) {
|
||||
effective_state = OutputState::Blank;
|
||||
}
|
||||
|
||||
// decide what that means needs to be communicated to the CRT
|
||||
last_output_state_duration_++;
|
||||
if(effective_state != last_output_state_) {
|
||||
switch(last_output_state_) {
|
||||
case OutputState::Blank: crt_->output_blank(last_output_state_duration_); break;
|
||||
case OutputState::Sync: crt_->output_sync(last_output_state_duration_); break;
|
||||
case OutputState::ColourBurst: crt_->output_colour_burst(last_output_state_duration_, 96, 0); break;
|
||||
case OutputState::Pixel: crt_->output_data(last_output_state_duration_, 1); break;
|
||||
}
|
||||
last_output_state_duration_ = 0;
|
||||
last_output_state_ = effective_state;
|
||||
|
||||
if(effective_state == OutputState::Pixel) {
|
||||
output_buffer_ = crt_->allocate_write_area(160);
|
||||
} else {
|
||||
output_buffer_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// decide on a pixel colour if that's what's happening
|
||||
if(effective_state == OutputState::Pixel)
|
||||
{
|
||||
uint8_t colour = get_output_pixel();
|
||||
if(output_buffer_)
|
||||
{
|
||||
*output_buffer_ = colour;
|
||||
output_buffer_++;
|
||||
}
|
||||
}
|
||||
|
||||
// advance horizontal timer, perform reset actions if desired
|
||||
horizontal_timer_ = (horizontal_timer_ + 1) % horizontalTimerPeriod;
|
||||
if(!horizontal_timer_)
|
||||
{
|
||||
// switch back to a normal length left border
|
||||
state_by_time_ = state_by_extend_time_[0];
|
||||
set_ready_line(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value)
|
||||
{
|
||||
uint8_t returnValue = 0xff;
|
||||
unsigned int cycles_run_for = 3;
|
||||
|
||||
// this occurs as a feedback loop — the 2600 requests ready, then performs the cycles_run_for
|
||||
// leap to the end of ready only once ready is signalled — because on a 6502 ready doesn't take
|
||||
// effect until the next read; therefore it isn't safe to assume that signalling ready immediately
|
||||
// skips to the end of the line.
|
||||
if(operation == CPU6502::BusOperation::Ready) {
|
||||
unsigned int distance_to_end_of_ready = horizontalTimerPeriod - horizontal_timer_;
|
||||
cycles_run_for = distance_to_end_of_ready;
|
||||
}
|
||||
|
||||
output_pixels(cycles_run_for);
|
||||
cycles_since_speaker_update_ += cycles_run_for;
|
||||
|
||||
if(operation != CPU6502::BusOperation::Ready) {
|
||||
|
||||
// check for a paging access
|
||||
if(rom_size_ > 4096 && ((address & 0x1f00) == 0x1f00)) {
|
||||
uint8_t *base_ptr = rom_pages_[0];
|
||||
uint8_t first_paging_register = (uint8_t)(0xf8 - (rom_size_ >> 14)*2);
|
||||
|
||||
const uint8_t paging_register = address&0xff;
|
||||
if(paging_register >= first_paging_register) {
|
||||
const uint16_t selected_page = paging_register - first_paging_register;
|
||||
if(selected_page * 4096 < rom_size_) {
|
||||
base_ptr = &rom_[selected_page * 4096];
|
||||
}
|
||||
}
|
||||
|
||||
if(base_ptr != rom_pages_[0]) {
|
||||
rom_pages_[0] = base_ptr;
|
||||
rom_pages_[1] = base_ptr + 1024;
|
||||
rom_pages_[2] = base_ptr + 2048;
|
||||
rom_pages_[3] = base_ptr + 3072;
|
||||
}
|
||||
}
|
||||
|
||||
// check for a ROM read
|
||||
if((address&0x1000) && isReadOperation(operation)) {
|
||||
returnValue &= rom_pages_[(address >> 10)&3][address&1023];
|
||||
}
|
||||
|
||||
// check for a RAM access
|
||||
if((address&0x1280) == 0x80) {
|
||||
if(isReadOperation(operation)) {
|
||||
returnValue &= mos6532_.get_ram(address);
|
||||
} else {
|
||||
mos6532_.set_ram(address, *value);
|
||||
}
|
||||
}
|
||||
|
||||
// check for a TIA access
|
||||
if(!(address&0x1080)) {
|
||||
if(isReadOperation(operation)) {
|
||||
const uint16_t decodedAddress = address & 0xf;
|
||||
switch(decodedAddress) {
|
||||
case 0x00: // missile 0 / player collisions
|
||||
case 0x01: // missile 1 / player collisions
|
||||
case 0x02: // player 0 / playfield / ball collisions
|
||||
case 0x03: // player 1 / playfield / ball collisions
|
||||
case 0x04: // missile 0 / playfield / ball collisions
|
||||
case 0x05: // missile 1 / playfield / ball collisions
|
||||
case 0x06: // ball / playfield collisions
|
||||
case 0x07: // player / player, missile / missile collisions
|
||||
returnValue &= collisions_[decodedAddress];
|
||||
break;
|
||||
|
||||
case 0x08:
|
||||
case 0x09:
|
||||
case 0x0a:
|
||||
case 0x0b:
|
||||
// TODO: pot ports
|
||||
break;
|
||||
|
||||
case 0x0c:
|
||||
case 0x0d:
|
||||
returnValue &= tia_input_value_[decodedAddress - 0x0c];
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
const uint16_t decodedAddress = address & 0x3f;
|
||||
switch(decodedAddress) {
|
||||
case 0x00:
|
||||
vsync_enabled_ = !!(*value & 0x02);
|
||||
break;
|
||||
case 0x01: vblank_enabled_ = !!(*value & 0x02); break;
|
||||
|
||||
case 0x02:
|
||||
if(horizontal_timer_) set_ready_line(true);
|
||||
break;
|
||||
case 0x03:
|
||||
// Reset is delayed by four cycles.
|
||||
horizontal_timer_ = horizontalTimerPeriod - 4;
|
||||
|
||||
// TODO: audio will now be out of synchronisation — fix
|
||||
break;
|
||||
|
||||
case 0x04:
|
||||
case 0x05: {
|
||||
int entry = decodedAddress - 0x04;
|
||||
player_and_missile_size_[entry] = *value;
|
||||
missile_size_[entry] = 1 << ((*value >> 4)&3);
|
||||
|
||||
uint8_t repeatMask = (*value)&7;
|
||||
has_second_copy_[entry] = (repeatMask == 1) || (repeatMask == 3);
|
||||
has_third_copy_[entry] = (repeatMask == 2) || (repeatMask == 3) || (repeatMask == 6);
|
||||
has_fourth_copy_[entry] = (repeatMask == 4) || (repeatMask == 6);
|
||||
} break;
|
||||
|
||||
case 0x06:
|
||||
case 0x07: player_colour_[decodedAddress - 0x06] = *value; break;
|
||||
case 0x08: playfield_colour_ = *value; break;
|
||||
case 0x09: background_colour_ = *value; break;
|
||||
|
||||
case 0x0a: {
|
||||
uint8_t old_playfield_control = playfield_control_;
|
||||
playfield_control_ = *value;
|
||||
ball_size_ = 1 << ((playfield_control_ >> 4)&3);
|
||||
|
||||
// did the mirroring bit change?
|
||||
if((playfield_control_^old_playfield_control)&1) {
|
||||
if(playfield_control_&1) {
|
||||
for(int c = 0; c < 20; c++) playfield_[c+20] = playfield_[19-c];
|
||||
} else {
|
||||
memcpy(&playfield_[20], playfield_, 20);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case 0x0b:
|
||||
case 0x0c: player_reflection_mask_[decodedAddress - 0x0b] = (*value)&8 ? 0 : 7; break;
|
||||
|
||||
case 0x0d:
|
||||
playfield_[0] = ((*value) >> 4)&1;
|
||||
playfield_[1] = ((*value) >> 5)&1;
|
||||
playfield_[2] = ((*value) >> 6)&1;
|
||||
playfield_[3] = (*value) >> 7;
|
||||
|
||||
if(playfield_control_&1) {
|
||||
for(int c = 0; c < 4; c++) playfield_[39-c] = playfield_[c];
|
||||
} else {
|
||||
memcpy(&playfield_[20], playfield_, 4);
|
||||
}
|
||||
break;
|
||||
case 0x0e:
|
||||
playfield_[4] = (*value) >> 7;
|
||||
playfield_[5] = ((*value) >> 6)&1;
|
||||
playfield_[6] = ((*value) >> 5)&1;
|
||||
playfield_[7] = ((*value) >> 4)&1;
|
||||
playfield_[8] = ((*value) >> 3)&1;
|
||||
playfield_[9] = ((*value) >> 2)&1;
|
||||
playfield_[10] = ((*value) >> 1)&1;
|
||||
playfield_[11] = (*value)&1;
|
||||
|
||||
if(playfield_control_&1) {
|
||||
for(int c = 0; c < 8; c++) playfield_[35-c] = playfield_[c+4];
|
||||
} else {
|
||||
memcpy(&playfield_[24], &playfield_[4], 8);
|
||||
}
|
||||
break;
|
||||
case 0x0f:
|
||||
playfield_[19] = (*value) >> 7;
|
||||
playfield_[18] = ((*value) >> 6)&1;
|
||||
playfield_[17] = ((*value) >> 5)&1;
|
||||
playfield_[16] = ((*value) >> 4)&1;
|
||||
playfield_[15] = ((*value) >> 3)&1;
|
||||
playfield_[14] = ((*value) >> 2)&1;
|
||||
playfield_[13] = ((*value) >> 1)&1;
|
||||
playfield_[12] = (*value)&1;
|
||||
|
||||
if(playfield_control_&1) {
|
||||
for(int c = 0; c < 8; c++) playfield_[27-c] = playfield_[c+12];
|
||||
} else {
|
||||
memcpy(&playfield_[32], &playfield_[12], 8);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x10: case 0x11: case 0x12: case 0x13:
|
||||
case 0x14:
|
||||
upcoming_events_[(upcoming_events_pointer_ + 4)%number_of_upcoming_events].updates |= Event::Action::ResetCounter;
|
||||
upcoming_events_[(upcoming_events_pointer_ + 4)%number_of_upcoming_events].counter = decodedAddress - 0x10;
|
||||
break;
|
||||
|
||||
case 0x15: case 0x16:
|
||||
update_audio();
|
||||
speaker_->set_control(decodedAddress - 0x15, *value);
|
||||
break;
|
||||
|
||||
case 0x17: case 0x18:
|
||||
update_audio();
|
||||
speaker_->set_divider(decodedAddress - 0x17, *value);
|
||||
break;
|
||||
|
||||
case 0x19: case 0x1a:
|
||||
update_audio();
|
||||
speaker_->set_volume(decodedAddress - 0x19, *value);
|
||||
break;
|
||||
|
||||
case 0x1c:
|
||||
ball_graphics_enable_[1] = ball_graphics_enable_[0];
|
||||
case 0x1b: {
|
||||
int index = decodedAddress - 0x1b;
|
||||
player_graphics_[0][index] = *value;
|
||||
player_graphics_[1][index^1] = player_graphics_[0][index^1];
|
||||
} break;
|
||||
case 0x1d:
|
||||
case 0x1e:
|
||||
missile_graphics_enable_[decodedAddress - 0x1d] = ((*value) >> 1)&1;
|
||||
// printf("e:%02x <- %c\n", decodedAddress - 0x1d, ((*value)&1) ? 'E' : '-');
|
||||
break;
|
||||
case 0x1f:
|
||||
ball_graphics_enable_[0] = ((*value) >> 1)&1;
|
||||
break;
|
||||
|
||||
case 0x20:
|
||||
case 0x21:
|
||||
case 0x22:
|
||||
case 0x23:
|
||||
case 0x24:
|
||||
object_motions_[decodedAddress - 0x20] = *value;
|
||||
break;
|
||||
|
||||
case 0x25: player_graphics_selector_[0] = (*value)&1; break;
|
||||
case 0x26: player_graphics_selector_[1] = (*value)&1; break;
|
||||
case 0x27: ball_graphics_selector_ = (*value)&1; break;
|
||||
|
||||
case 0x28:
|
||||
case 0x29:
|
||||
{
|
||||
// TODO: this should properly mean setting a flag and propagating later, I think?
|
||||
int index = decodedAddress - 0x28;
|
||||
if(!(*value&0x02) && missile_graphics_reset_[index])
|
||||
{
|
||||
object_counter_[object_counter_pointer_][index + 2].count = object_counter_[object_counter_pointer_][index].count;
|
||||
|
||||
uint8_t repeatMask = player_and_missile_size_[index] & 7;
|
||||
int extra_offset;
|
||||
switch(repeatMask)
|
||||
{
|
||||
default: extra_offset = 3; break;
|
||||
case 5: extra_offset = 6; break;
|
||||
case 7: extra_offset = 10; break;
|
||||
}
|
||||
|
||||
object_counter_[object_counter_pointer_][index + 2].count = (object_counter_[object_counter_pointer_][index + 2].count + extra_offset)%160;
|
||||
}
|
||||
missile_graphics_reset_[index] = !!((*value) & 0x02);
|
||||
// printf("r:%02x <- %c\n", decodedAddress - 0x28, ((*value)&2) ? 'R' : '-');
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x2a: {
|
||||
// justification for +5: "we need to wait at least 71 [clocks] before the HMOVE operation is complete";
|
||||
// which will take 16*4 + 2 = 66 cycles from the first compare, implying the first compare must be
|
||||
// in five cycles from now
|
||||
// int start_pause = ((horizontal_timer_ + 3)&3) + 4;
|
||||
upcoming_events_[(upcoming_events_pointer_ + 5)%number_of_upcoming_events].updates |= Event::Action::HMoveSetup;
|
||||
} break;
|
||||
case 0x2b:
|
||||
object_motions_[0] =
|
||||
object_motions_[1] =
|
||||
object_motions_[2] =
|
||||
object_motions_[3] =
|
||||
object_motions_[4] = 0;
|
||||
break;
|
||||
case 0x2c:
|
||||
collisions_[0] = collisions_[1] = collisions_[2] =
|
||||
collisions_[3] = collisions_[4] = collisions_[5] = 0x3f;
|
||||
collisions_[6] = 0x7f;
|
||||
collisions_[7] = 0x3f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check for a PIA access
|
||||
if((address&0x1280) == 0x280) {
|
||||
if(isReadOperation(operation)) {
|
||||
returnValue &= mos6532_.get_register(address);
|
||||
} else {
|
||||
mos6532_.set_register(address, *value);
|
||||
}
|
||||
}
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
*value = returnValue;
|
||||
}
|
||||
}
|
||||
|
||||
mos6532_.run_for_cycles(cycles_run_for / 3);
|
||||
|
||||
return cycles_run_for / 3;
|
||||
}
|
||||
|
||||
void Machine::set_digital_input(Atari2600DigitalInput input, bool state)
|
||||
{
|
||||
void Machine::set_digital_input(Atari2600DigitalInput input, bool state) {
|
||||
switch (input) {
|
||||
case Atari2600DigitalInputJoy1Up: mos6532_.update_port_input(0, 0x10, state); break;
|
||||
case Atari2600DigitalInputJoy1Down: mos6532_.update_port_input(0, 0x20, state); break;
|
||||
case Atari2600DigitalInputJoy1Left: mos6532_.update_port_input(0, 0x40, state); break;
|
||||
case Atari2600DigitalInputJoy1Right: mos6532_.update_port_input(0, 0x80, state); break;
|
||||
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: mos6532_.update_port_input(0, 0x01, state); break;
|
||||
case Atari2600DigitalInputJoy2Down: mos6532_.update_port_input(0, 0x02, state); break;
|
||||
case Atari2600DigitalInputJoy2Left: mos6532_.update_port_input(0, 0x04, state); break;
|
||||
case Atari2600DigitalInputJoy2Right: mos6532_.update_port_input(0, 0x08, 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) tia_input_value_[0] &= ~0x80; else tia_input_value_[0] |= 0x80; break;
|
||||
case Atari2600DigitalInputJoy2Fire: if(state) tia_input_value_[1] &= ~0x80; else tia_input_value_[1] |= 0x80; break;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::set_switch_is_enabled(Atari2600Switch input, bool state)
|
||||
{
|
||||
void Machine::set_switch_is_enabled(Atari2600Switch input, bool state) {
|
||||
switch(input) {
|
||||
case Atari2600SwitchReset: mos6532_.update_port_input(1, 0x01, state); break;
|
||||
case Atari2600SwitchSelect: mos6532_.update_port_input(1, 0x02, state); break;
|
||||
case Atari2600SwitchColour: mos6532_.update_port_input(1, 0x08, state); break;
|
||||
case Atari2600SwitchLeftPlayerDifficulty: mos6532_.update_port_input(1, 0x40, state); break;
|
||||
case Atari2600SwitchRightPlayerDifficulty: mos6532_.update_port_input(1, 0x80, state); break;
|
||||
case Atari2600SwitchReset: bus_->mos6532_.update_port_input(1, 0x01, state); break;
|
||||
case Atari2600SwitchSelect: bus_->mos6532_.update_port_input(1, 0x02, state); break;
|
||||
case Atari2600SwitchColour: bus_->mos6532_.update_port_input(1, 0x08, state); break;
|
||||
case Atari2600SwitchLeftPlayerDifficulty: bus_->mos6532_.update_port_input(1, 0x40, state); break;
|
||||
case Atari2600SwitchRightPlayerDifficulty: bus_->mos6532_.update_port_input(1, 0x80, state); break;
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::configure_as_target(const StaticAnalyser::Target &target)
|
||||
{
|
||||
if(!target.cartridges.front()->get_segments().size()) return;
|
||||
Storage::Cartridge::Cartridge::Segment segment = target.cartridges.front()->get_segments().front();
|
||||
size_t length = segment.data.size();
|
||||
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
|
||||
const std::vector<uint8_t> &rom = target.cartridges.front()->get_segments().front().data;
|
||||
switch(target.atari.paging_model) {
|
||||
case StaticAnalyser::Atari2600PagingModel::ActivisionStack: bus_.reset(new CartridgeActivisionStack(rom)); break;
|
||||
case StaticAnalyser::Atari2600PagingModel::CBSRamPlus: bus_.reset(new CartridgeCBSRAMPlus(rom)); break;
|
||||
case StaticAnalyser::Atari2600PagingModel::CommaVid: bus_.reset(new CartridgeCommaVid(rom)); break;
|
||||
case StaticAnalyser::Atari2600PagingModel::MegaBoy: bus_.reset(new CartridgeMegaBoy(rom)); break;
|
||||
case StaticAnalyser::Atari2600PagingModel::MNetwork: bus_.reset(new CartridgeMNetwork(rom)); break;
|
||||
case StaticAnalyser::Atari2600PagingModel::None: bus_.reset(new CartridgeUnpaged(rom)); break;
|
||||
case StaticAnalyser::Atari2600PagingModel::ParkerBros: bus_.reset(new CartridgeParkerBros(rom)); break;
|
||||
case StaticAnalyser::Atari2600PagingModel::Pitfall2: bus_.reset(new CartridgePitfall2(rom)); break;
|
||||
case StaticAnalyser::Atari2600PagingModel::Tigervision: bus_.reset(new CartridgeTigervision(rom)); break;
|
||||
|
||||
rom_size_ = 1024;
|
||||
while(rom_size_ < length && rom_size_ < 32768) rom_size_ <<= 1;
|
||||
|
||||
delete[] rom_;
|
||||
rom_ = new uint8_t[rom_size_];
|
||||
|
||||
size_t offset = 0;
|
||||
const size_t copy_step = std::min(rom_size_, length);
|
||||
while(offset < rom_size_)
|
||||
{
|
||||
size_t copy_length = std::min(copy_step, rom_size_ - offset);
|
||||
memcpy(&rom_[offset], &segment.data[0], copy_length);
|
||||
offset += copy_length;
|
||||
case StaticAnalyser::Atari2600PagingModel::Atari8k:
|
||||
if(target.atari.uses_superchip) {
|
||||
bus_.reset(new CartridgeAtari8kSuperChip(rom));
|
||||
} else {
|
||||
bus_.reset(new CartridgeAtari8k(rom));
|
||||
}
|
||||
break;
|
||||
case StaticAnalyser::Atari2600PagingModel::Atari16k:
|
||||
if(target.atari.uses_superchip) {
|
||||
bus_.reset(new CartridgeAtari16kSuperChip(rom));
|
||||
} else {
|
||||
bus_.reset(new CartridgeAtari16k(rom));
|
||||
}
|
||||
break;
|
||||
case StaticAnalyser::Atari2600PagingModel::Atari32k:
|
||||
if(target.atari.uses_superchip) {
|
||||
bus_.reset(new CartridgeAtari32kSuperChip(rom));
|
||||
} else {
|
||||
bus_.reset(new CartridgeAtari32k(rom));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
size_t romMask = rom_size_ - 1;
|
||||
rom_pages_[0] = rom_;
|
||||
rom_pages_[1] = &rom_[1024 & romMask];
|
||||
rom_pages_[2] = &rom_[2048 & romMask];
|
||||
rom_pages_[3] = &rom_[3072 & romMask];
|
||||
}
|
||||
|
||||
#pragma mark - Audio
|
||||
#pragma mark - CRT delegate
|
||||
|
||||
void Machine::update_audio()
|
||||
{
|
||||
unsigned int audio_cycles = cycles_since_speaker_update_ / 114;
|
||||
void Machine::crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs) {
|
||||
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;
|
||||
frame_record_pointer_ ++;
|
||||
|
||||
speaker_->run_for_cycles(audio_cycles);
|
||||
cycles_since_speaker_update_ %= 114;
|
||||
if(frame_record_pointer_ >= 6) {
|
||||
unsigned int total_number_of_frames = 0;
|
||||
unsigned int total_number_of_unexpected_vertical_syncs = 0;
|
||||
for(size_t c = 0; c < number_of_frame_records; c++) {
|
||||
total_number_of_frames += frame_records_[c].number_of_frames;
|
||||
total_number_of_unexpected_vertical_syncs += frame_records_[c].number_of_unexpected_vertical_syncs;
|
||||
}
|
||||
|
||||
if(total_number_of_unexpected_vertical_syncs >= total_number_of_frames >> 1) {
|
||||
for(size_t c = 0; c < number_of_frame_records; c++) {
|
||||
frame_records_[c].number_of_frames = 0;
|
||||
frame_records_[c].number_of_unexpected_vertical_syncs = 0;
|
||||
}
|
||||
is_ntsc_ ^= true;
|
||||
|
||||
double clock_rate;
|
||||
if(is_ntsc_) {
|
||||
clock_rate = NTSC_clock_rate;
|
||||
bus_->tia_->set_output_mode(TIA::OutputMode::NTSC);
|
||||
} else {
|
||||
clock_rate = PAL_clock_rate;
|
||||
bus_->tia_->set_output_mode(TIA::OutputMode::PAL);
|
||||
}
|
||||
|
||||
bus_->speaker_->set_input_rate((float)(clock_rate / 38.0));
|
||||
bus_->speaker_->set_high_frequency_cut_off((float)(clock_rate / (38.0 * 2.0)));
|
||||
set_clock_rate(clock_rate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::synchronise()
|
||||
{
|
||||
update_audio();
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,10 @@
|
||||
|
||||
#include "../../Processors/6502/CPU6502.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "Bus.hpp"
|
||||
#include "PIA.hpp"
|
||||
#include "Speaker.hpp"
|
||||
#include "TIA.hpp"
|
||||
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "Atari2600Inputs.h"
|
||||
@@ -25,9 +27,9 @@ const unsigned int number_of_upcoming_events = 6;
|
||||
const unsigned int number_of_recorded_counters = 7;
|
||||
|
||||
class Machine:
|
||||
public CPU6502::Processor<Machine>,
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine {
|
||||
public ConfigurationTarget::Machine,
|
||||
public Outputs::CRT::Delegate {
|
||||
|
||||
public:
|
||||
Machine();
|
||||
@@ -38,137 +40,31 @@ class Machine:
|
||||
|
||||
void set_digital_input(Atari2600DigitalInput input, bool state);
|
||||
void set_switch_is_enabled(Atari2600Switch input, bool state);
|
||||
|
||||
// to satisfy CPU6502::Processor
|
||||
unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value);
|
||||
void synchronise();
|
||||
void set_reset_line(bool state) { bus_->set_reset_line(state); }
|
||||
|
||||
// to satisfy CRTMachine::Machine
|
||||
virtual void setup_output(float aspect_ratio);
|
||||
virtual void close_output();
|
||||
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; }
|
||||
virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return speaker_; }
|
||||
virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles); }
|
||||
// TODO: different rate for PAL
|
||||
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return bus_->tia_->get_crt(); }
|
||||
virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return bus_->speaker_; }
|
||||
virtual void run_for_cycles(int number_of_cycles) { bus_->run_for_cycles(number_of_cycles); }
|
||||
|
||||
// to satisfy Outputs::CRT::Delegate
|
||||
virtual void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs);
|
||||
|
||||
private:
|
||||
uint8_t *rom_, *rom_pages_[4];
|
||||
size_t rom_size_;
|
||||
// the bus
|
||||
std::unique_ptr<Bus> bus_;
|
||||
|
||||
// the RIOT
|
||||
PIA mos6532_;
|
||||
// output frame rate tracker
|
||||
struct FrameRecord {
|
||||
unsigned int number_of_frames;
|
||||
unsigned int number_of_unexpected_vertical_syncs;
|
||||
|
||||
// playfield registers
|
||||
uint8_t playfield_control_;
|
||||
uint8_t playfield_colour_;
|
||||
uint8_t background_colour_;
|
||||
uint8_t playfield_[41];
|
||||
|
||||
// ... and derivatives
|
||||
int ball_size_, missile_size_[2];
|
||||
|
||||
// delayed clock events
|
||||
enum OutputState {
|
||||
Sync,
|
||||
Blank,
|
||||
ColourBurst,
|
||||
Pixel
|
||||
};
|
||||
|
||||
struct Event {
|
||||
enum Action {
|
||||
Playfield = 1 << 0,
|
||||
ResetCounter = 1 << 1,
|
||||
|
||||
HMoveSetup = 1 << 2,
|
||||
HMoveCompare = 1 << 3,
|
||||
HMoveDecrement = 1 << 4,
|
||||
};
|
||||
int updates;
|
||||
|
||||
OutputState state;
|
||||
uint8_t playfield_pixel;
|
||||
int counter;
|
||||
|
||||
Event() : updates(0), playfield_pixel(0) {}
|
||||
} upcoming_events_[number_of_upcoming_events];
|
||||
unsigned int upcoming_events_pointer_;
|
||||
|
||||
// object counters
|
||||
struct ObjectCounter {
|
||||
int count; // the counter value, multiplied by four, counting phase
|
||||
int pixel; // for non-sprite objects, a count of cycles since the last counter reset; for sprite objects a count of pixels so far elapsed
|
||||
int broad_pixel; // for sprite objects, a count of cycles since the last counter reset; otherwise unused
|
||||
|
||||
ObjectCounter() : count(0), pixel(0), broad_pixel(0) {}
|
||||
} object_counter_[number_of_recorded_counters][5];
|
||||
unsigned int object_counter_pointer_;
|
||||
|
||||
// the latched playfield output
|
||||
uint8_t playfield_output_, next_playfield_output_;
|
||||
|
||||
// player registers
|
||||
uint8_t player_colour_[2];
|
||||
uint8_t player_reflection_mask_[2];
|
||||
uint8_t player_graphics_[2][2];
|
||||
uint8_t player_graphics_selector_[2];
|
||||
|
||||
// object flags
|
||||
bool has_second_copy_[2];
|
||||
bool has_third_copy_[2];
|
||||
bool has_fourth_copy_[2];
|
||||
uint8_t object_motions_[5]; // the value stored to this counter's motion register
|
||||
|
||||
// player + missile registers
|
||||
uint8_t player_and_missile_size_[2];
|
||||
|
||||
// missile registers
|
||||
uint8_t missile_graphics_enable_[2];
|
||||
bool missile_graphics_reset_[2];
|
||||
|
||||
// ball registers
|
||||
uint8_t ball_graphics_enable_[2];
|
||||
uint8_t ball_graphics_selector_;
|
||||
|
||||
// graphics output
|
||||
unsigned int horizontal_timer_;
|
||||
bool vsync_enabled_, vblank_enabled_;
|
||||
|
||||
// horizontal motion control
|
||||
uint8_t hmove_counter_;
|
||||
uint8_t hmove_flags_;
|
||||
|
||||
// joystick state
|
||||
uint8_t tia_input_value_[2];
|
||||
|
||||
// collisions
|
||||
uint8_t collisions_[8];
|
||||
|
||||
void output_pixels(unsigned int count);
|
||||
uint8_t get_output_pixel();
|
||||
void update_timers(int mask);
|
||||
|
||||
// outputs
|
||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
||||
std::shared_ptr<Speaker> speaker_;
|
||||
|
||||
// current mode
|
||||
bool is_pal_region_;
|
||||
|
||||
// speaker backlog accumlation counter
|
||||
unsigned int cycles_since_speaker_update_;
|
||||
void update_audio();
|
||||
|
||||
// latched output state
|
||||
unsigned int last_output_state_duration_;
|
||||
OutputState state_by_extend_time_[2][57];
|
||||
OutputState *state_by_time_;
|
||||
OutputState last_output_state_;
|
||||
uint8_t *output_buffer_;
|
||||
|
||||
// lookup table for collision reporting
|
||||
uint8_t reported_collisions_[64][8];
|
||||
void setup_reported_collisions();
|
||||
FrameRecord() : number_of_frames(0), number_of_unexpected_vertical_syncs(0) {}
|
||||
} frame_records_[4];
|
||||
unsigned int frame_record_pointer_;
|
||||
bool is_ntsc_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
63
Machines/Atari2600/Bus.hpp
Normal file
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// Bus.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/03/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Atari2600_Bus_hpp
|
||||
#define Atari2600_Bus_hpp
|
||||
|
||||
#include "PIA.hpp"
|
||||
#include "TIA.hpp"
|
||||
#include "Speaker.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
class Bus {
|
||||
public:
|
||||
Bus() :
|
||||
tia_input_value_{0xff, 0xff},
|
||||
cycles_since_speaker_update_(0),
|
||||
cycles_since_video_update_(0),
|
||||
cycles_since_6532_update_(0) {}
|
||||
|
||||
virtual void run_for_cycles(int number_of_cycles) = 0;
|
||||
virtual void set_reset_line(bool state) = 0;
|
||||
|
||||
// the RIOT, TIA and speaker
|
||||
PIA mos6532_;
|
||||
std::shared_ptr<TIA> tia_;
|
||||
std::shared_ptr<Speaker> speaker_;
|
||||
|
||||
// joystick state
|
||||
uint8_t tia_input_value_[2];
|
||||
|
||||
protected:
|
||||
// speaker backlog accumlation counter
|
||||
unsigned int cycles_since_speaker_update_;
|
||||
inline void update_audio() {
|
||||
unsigned int audio_cycles = cycles_since_speaker_update_ / 114;
|
||||
cycles_since_speaker_update_ %= 114;
|
||||
speaker_->run_for_cycles(audio_cycles);
|
||||
}
|
||||
|
||||
// video backlog accumulation counter
|
||||
unsigned int cycles_since_video_update_;
|
||||
inline void update_video() {
|
||||
tia_->run_for_cycles((int)cycles_since_video_update_);
|
||||
cycles_since_video_update_ = 0;
|
||||
}
|
||||
|
||||
// RIOT backlog accumulation counter
|
||||
unsigned int cycles_since_6532_update_;
|
||||
inline void update_6532() {
|
||||
mos6532_.run_for_cycles(cycles_since_6532_update_);
|
||||
cycles_since_6532_update_ = 0;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Atari2600_Bus_hpp */
|
||||
176
Machines/Atari2600/Cartridges/Cartridge.hpp
Normal file
@@ -0,0 +1,176 @@
|
||||
//
|
||||
// Cartridge.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 17/03/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Atari2600_Cartridge_hpp
|
||||
#define Atari2600_Cartridge_hpp
|
||||
|
||||
#include "../../../Processors/6502/CPU6502.hpp"
|
||||
#include "../Bus.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
template<class T> class Cartridge:
|
||||
public CPU6502::Processor<Cartridge<T>>,
|
||||
public Bus {
|
||||
|
||||
public:
|
||||
Cartridge(const std::vector<uint8_t> &rom) :
|
||||
rom_(rom) {}
|
||||
|
||||
void run_for_cycles(int number_of_cycles) { CPU6502::Processor<Cartridge<T>>::run_for_cycles(number_of_cycles); }
|
||||
void set_reset_line(bool state) { CPU6502::Processor<Cartridge<T>>::set_reset_line(state); }
|
||||
void advance_cycles(unsigned int cycles) {}
|
||||
|
||||
// to satisfy CPU6502::Processor
|
||||
unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
uint8_t returnValue = 0xff;
|
||||
unsigned int cycles_run_for = 3;
|
||||
|
||||
// this occurs as a feedback loop — the 2600 requests ready, then performs the cycles_run_for
|
||||
// leap to the end of ready only once ready is signalled — because on a 6502 ready doesn't take
|
||||
// effect until the next read; therefore it isn't safe to assume that signalling ready immediately
|
||||
// skips to the end of the line.
|
||||
if(operation == CPU6502::BusOperation::Ready)
|
||||
cycles_run_for = (unsigned int)tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_);
|
||||
|
||||
cycles_since_speaker_update_ += cycles_run_for;
|
||||
cycles_since_video_update_ += cycles_run_for;
|
||||
cycles_since_6532_update_ += (cycles_run_for / 3);
|
||||
static_cast<T *>(this)->advance_cycles(cycles_run_for / 3);
|
||||
|
||||
if(operation != CPU6502::BusOperation::Ready) {
|
||||
// give the cartridge a chance to respond to the bus access
|
||||
static_cast<T *>(this)->perform_bus_operation(operation, address, value);
|
||||
|
||||
// check for a RIOT RAM access
|
||||
if((address&0x1280) == 0x80) {
|
||||
if(isReadOperation(operation)) {
|
||||
returnValue &= mos6532_.get_ram(address);
|
||||
} else {
|
||||
mos6532_.set_ram(address, *value);
|
||||
}
|
||||
}
|
||||
|
||||
// check for a TIA access
|
||||
if(!(address&0x1080)) {
|
||||
if(isReadOperation(operation)) {
|
||||
const uint16_t decodedAddress = address & 0xf;
|
||||
switch(decodedAddress) {
|
||||
case 0x00: // missile 0 / player collisions
|
||||
case 0x01: // missile 1 / player collisions
|
||||
case 0x02: // player 0 / playfield / ball collisions
|
||||
case 0x03: // player 1 / playfield / ball collisions
|
||||
case 0x04: // missile 0 / playfield / ball collisions
|
||||
case 0x05: // missile 1 / playfield / ball collisions
|
||||
case 0x06: // ball / playfield collisions
|
||||
case 0x07: // player / player, missile / missile collisions
|
||||
returnValue &= tia_->get_collision_flags(decodedAddress);
|
||||
break;
|
||||
|
||||
case 0x08:
|
||||
case 0x09:
|
||||
case 0x0a:
|
||||
case 0x0b:
|
||||
// TODO: pot ports
|
||||
returnValue &= 0;
|
||||
break;
|
||||
|
||||
case 0x0c:
|
||||
case 0x0d:
|
||||
returnValue &= tia_input_value_[decodedAddress - 0x0c];
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
const uint16_t decodedAddress = address & 0x3f;
|
||||
switch(decodedAddress) {
|
||||
case 0x00: update_video(); tia_->set_sync(*value & 0x02); break;
|
||||
case 0x01: update_video(); tia_->set_blank(*value & 0x02); break;
|
||||
|
||||
case 0x02: CPU6502::Processor<Cartridge<T>>::set_ready_line(true); break;
|
||||
case 0x03: update_video(); tia_->reset_horizontal_counter(); break;
|
||||
// TODO: audio will now be out of synchronisation — fix
|
||||
|
||||
case 0x04:
|
||||
case 0x05: update_video(); tia_->set_player_number_and_size(decodedAddress - 0x04, *value); break;
|
||||
case 0x06:
|
||||
case 0x07: update_video(); tia_->set_player_missile_colour(decodedAddress - 0x06, *value); break;
|
||||
case 0x08: update_video(); tia_->set_playfield_ball_colour(*value); break;
|
||||
case 0x09: update_video(); tia_->set_background_colour(*value); break;
|
||||
case 0x0a: update_video(); tia_->set_playfield_control_and_ball_size(*value); break;
|
||||
case 0x0b:
|
||||
case 0x0c: update_video(); tia_->set_player_reflected(decodedAddress - 0x0b, !((*value)&8)); break;
|
||||
case 0x0d:
|
||||
case 0x0e:
|
||||
case 0x0f: update_video(); tia_->set_playfield(decodedAddress - 0x0d, *value); break;
|
||||
case 0x10:
|
||||
case 0x11: update_video(); tia_->set_player_position(decodedAddress - 0x10); break;
|
||||
case 0x12:
|
||||
case 0x13: update_video(); tia_->set_missile_position(decodedAddress - 0x12); break;
|
||||
case 0x14: update_video(); tia_->set_ball_position(); break;
|
||||
case 0x1b:
|
||||
case 0x1c: update_video(); tia_->set_player_graphic(decodedAddress - 0x1b, *value); break;
|
||||
case 0x1d:
|
||||
case 0x1e: update_video(); tia_->set_missile_enable(decodedAddress - 0x1d, (*value)&2); break;
|
||||
case 0x1f: update_video(); tia_->set_ball_enable((*value)&2); break;
|
||||
case 0x20:
|
||||
case 0x21: update_video(); tia_->set_player_motion(decodedAddress - 0x20, *value); break;
|
||||
case 0x22:
|
||||
case 0x23: update_video(); tia_->set_missile_motion(decodedAddress - 0x22, *value); break;
|
||||
case 0x24: update_video(); tia_->set_ball_motion(*value); break;
|
||||
case 0x25:
|
||||
case 0x26: tia_->set_player_delay(decodedAddress - 0x25, (*value)&1); break;
|
||||
case 0x27: tia_->set_ball_delay((*value)&1); break;
|
||||
case 0x28:
|
||||
case 0x29: update_video(); tia_->set_missile_position_to_player(decodedAddress - 0x28, (*value)&2); break;
|
||||
case 0x2a: update_video(); tia_->move(); break;
|
||||
case 0x2b: update_video(); tia_->clear_motion(); break;
|
||||
case 0x2c: update_video(); tia_->clear_collision_flags(); break;
|
||||
|
||||
case 0x15:
|
||||
case 0x16: update_audio(); speaker_->set_control(decodedAddress - 0x15, *value); break;
|
||||
case 0x17:
|
||||
case 0x18: update_audio(); speaker_->set_divider(decodedAddress - 0x17, *value); break;
|
||||
case 0x19:
|
||||
case 0x1a: update_audio(); speaker_->set_volume(decodedAddress - 0x19, *value); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check for a PIA access
|
||||
if((address&0x1280) == 0x280) {
|
||||
update_6532();
|
||||
if(isReadOperation(operation)) {
|
||||
returnValue &= mos6532_.get_register(address);
|
||||
} else {
|
||||
mos6532_.set_register(address, *value);
|
||||
}
|
||||
}
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
*value &= returnValue;
|
||||
}
|
||||
}
|
||||
|
||||
if(!tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_)) CPU6502::Processor<Cartridge<T>>::set_ready_line(false);
|
||||
|
||||
return cycles_run_for / 3;
|
||||
}
|
||||
|
||||
void synchronise() {
|
||||
update_audio();
|
||||
update_video();
|
||||
speaker_->flush();
|
||||
}
|
||||
|
||||
protected:
|
||||
std::vector<uint8_t> rom_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Atari2600_Cartridge_hpp */
|
||||
50
Machines/Atari2600/Cartridges/CartridgeActivisionStack.hpp
Normal file
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// CartridgeActivisionStack.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/03/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Atari2600_CartridgeActivisionStack_hpp
|
||||
#define Atari2600_CartridgeActivisionStack_hpp
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
class CartridgeActivisionStack: public Cartridge<CartridgeActivisionStack> {
|
||||
public:
|
||||
CartridgeActivisionStack(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom),
|
||||
last_opcode_(0x00) {
|
||||
rom_ptr_ = rom_.data();
|
||||
}
|
||||
|
||||
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
// This is a bit of a hack; a real cartridge can't see either the sync or read lines, and can't see
|
||||
// address line 13. Instead it looks for a pattern in recent address accesses that would imply an
|
||||
// RST or JSR.
|
||||
if(operation == CPU6502::BusOperation::ReadOpcode && (last_opcode_ == 0x20 || last_opcode_ == 0x60)) {
|
||||
if(address & 0x2000) {
|
||||
rom_ptr_ = rom_.data();
|
||||
} else {
|
||||
rom_ptr_ = rom_.data() + 4096;
|
||||
}
|
||||
}
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
|
||||
if(operation == CPU6502::BusOperation::ReadOpcode) last_opcode_ = *value;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *rom_ptr_;
|
||||
uint8_t last_opcode_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Atari2600_CartridgeActivisionStack_hpp */
|
||||
66
Machines/Atari2600/Cartridges/CartridgeAtari16k.hpp
Normal file
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// CartridgeAtari8k.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/03/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Atari2600_CartridgeAtari16k_hpp
|
||||
#define Atari2600_CartridgeAtari16k_hpp
|
||||
|
||||
#include "Cartridge.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
class CartridgeAtari16k: public Cartridge<CartridgeAtari16k> {
|
||||
public:
|
||||
CartridgeAtari16k(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom) {
|
||||
rom_ptr_ = rom_.data();
|
||||
}
|
||||
|
||||
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_.data() + (address - 0x1ff6) * 4096;
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *rom_ptr_;
|
||||
};
|
||||
|
||||
class CartridgeAtari16kSuperChip: public Cartridge<CartridgeAtari16kSuperChip> {
|
||||
public:
|
||||
CartridgeAtari16kSuperChip(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom) {
|
||||
rom_ptr_ = rom_.data();
|
||||
}
|
||||
|
||||
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_.data() + (address - 0x1ff6) * 4096;
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
|
||||
if(address < 0x1080) ram_[address & 0x7f] = *value;
|
||||
else if(address < 0x1100 && isReadOperation(operation)) *value = ram_[address & 0x7f];
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *rom_ptr_;
|
||||
uint8_t ram_[128];
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Atari2600_CartridgeAtari16k_hpp */
|
||||
66
Machines/Atari2600/Cartridges/CartridgeAtari32k.hpp
Normal file
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// CartridgeAtari8k.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/03/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Atari2600_CartridgeAtari32k_hpp
|
||||
#define Atari2600_CartridgeAtari32k_hpp
|
||||
|
||||
#include "Cartridge.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
class CartridgeAtari32k: public Cartridge<CartridgeAtari32k> {
|
||||
public:
|
||||
CartridgeAtari32k(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom) {
|
||||
rom_ptr_ = rom_.data();
|
||||
}
|
||||
|
||||
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_.data() + (address - 0x1ff4) * 4096;
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *rom_ptr_;
|
||||
};
|
||||
|
||||
class CartridgeAtari32kSuperChip: public Cartridge<CartridgeAtari32kSuperChip> {
|
||||
public:
|
||||
CartridgeAtari32kSuperChip(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom) {
|
||||
rom_ptr_ = rom_.data();
|
||||
}
|
||||
|
||||
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_.data() + (address - 0x1ff4) * 4096;
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
|
||||
if(address < 0x1080) ram_[address & 0x7f] = *value;
|
||||
else if(address < 0x1100 && isReadOperation(operation)) *value = ram_[address & 0x7f];
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *rom_ptr_;
|
||||
uint8_t ram_[128];
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Atari2600_CartridgeAtari32k_hpp */
|
||||
68
Machines/Atari2600/Cartridges/CartridgeAtari8k.hpp
Normal file
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// CartridgeAtari8k.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/03/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Atari2600_CartridgeAtari8k_hpp
|
||||
#define Atari2600_CartridgeAtari8k_hpp
|
||||
|
||||
#include "Cartridge.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
class CartridgeAtari8k: public Cartridge<CartridgeAtari8k> {
|
||||
public:
|
||||
CartridgeAtari8k(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom) {
|
||||
rom_ptr_ = rom_.data();
|
||||
}
|
||||
|
||||
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
if(address == 0x1ff8) rom_ptr_ = rom_.data();
|
||||
else if(address == 0x1ff9) rom_ptr_ = rom_.data() + 4096;
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *rom_ptr_;
|
||||
};
|
||||
|
||||
class CartridgeAtari8kSuperChip: public Cartridge<CartridgeAtari8kSuperChip> {
|
||||
public:
|
||||
CartridgeAtari8kSuperChip(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom) {
|
||||
rom_ptr_ = rom_.data();
|
||||
}
|
||||
|
||||
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
if(address == 0x1ff8) rom_ptr_ = rom_.data();
|
||||
if(address == 0x1ff9) rom_ptr_ = rom_.data() + 4096;
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
|
||||
if(address < 0x1080) ram_[address & 0x7f] = *value;
|
||||
else if(address < 0x1100 && isReadOperation(operation)) *value = ram_[address & 0x7f];
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *rom_ptr_;
|
||||
uint8_t ram_[128];
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Atari2600_CartridgeAtari8k_hpp */
|
||||
44
Machines/Atari2600/Cartridges/CartridgeCBSRAMPlus.hpp
Normal file
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// CartridgeCBSRAMPlus.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/03/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Atari2600_CartridgeCBSRAMPlus_hpp
|
||||
#define Atari2600_CartridgeCBSRAMPlus_hpp
|
||||
|
||||
#include "Cartridge.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
class CartridgeCBSRAMPlus: public Cartridge<CartridgeCBSRAMPlus> {
|
||||
public:
|
||||
CartridgeCBSRAMPlus(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom) {
|
||||
rom_ptr_ = rom_.data();
|
||||
}
|
||||
|
||||
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
if(address >= 0x1ff8 && address <= 0x1ffa) rom_ptr_ = rom_.data() + (address - 0x1ff8) * 4096;
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
|
||||
if(address < 0x1100) ram_[address & 0xff] = *value;
|
||||
else if(address < 0x1200 && isReadOperation(operation)) *value = ram_[address & 0xff];
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *rom_ptr_;
|
||||
uint8_t ram_[256];
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Atari2600_CartridgeCBSRAMPlus_hpp */
|
||||
42
Machines/Atari2600/Cartridges/CartridgeCommaVid.hpp
Normal file
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// CartridgeCommaVid.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/03/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Atari2600_CartridgeCommaVid_hpp
|
||||
#define Atari2600_CartridgeCommaVid_hpp
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
class CartridgeCommaVid: public Cartridge<CartridgeCommaVid> {
|
||||
public:
|
||||
CartridgeCommaVid(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom) {}
|
||||
|
||||
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
if(!(address & 0x1000)) return;
|
||||
address &= 0x1fff;
|
||||
|
||||
if(address < 0x1400) {
|
||||
if(isReadOperation(operation)) *value = ram_[address & 1023];
|
||||
return;
|
||||
}
|
||||
|
||||
if(address < 0x1800) {
|
||||
ram_[address & 1023] = *value;
|
||||
return;
|
||||
}
|
||||
|
||||
if(isReadOperation(operation)) *value = rom_[address & 2047];
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t ram_[1024];
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Atari2600_CartridgeCommaVid_hpp */
|
||||
68
Machines/Atari2600/Cartridges/CartridgeMNetwork.hpp
Normal file
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// CartridgeMNetwork.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/03/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Atari2600_CartridgeMNetwork_hpp
|
||||
#define Atari2600_CartridgeMNetwork_hpp
|
||||
|
||||
#include "Cartridge.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
class CartridgeMNetwork: public Cartridge<CartridgeMNetwork> {
|
||||
public:
|
||||
CartridgeMNetwork(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom) {
|
||||
rom_ptr_[0] = rom_.data() + rom_.size() - 4096;
|
||||
rom_ptr_[1] = rom_ptr_[0] + 2048;
|
||||
high_ram_ptr_ = high_ram_;
|
||||
}
|
||||
|
||||
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
if(address >= 0x1fe0 && address <= 0x1fe6) {
|
||||
rom_ptr_[0] = rom_.data() + (address - 0x1fe0) * 2048;
|
||||
} else if(address == 0x1fe7) {
|
||||
rom_ptr_[0] = nullptr;
|
||||
} else if(address >= 0x1ff8 && address <= 0x1ffb) {
|
||||
int offset = (address - 0x1ff8) * 256;
|
||||
high_ram_ptr_ = &high_ram_[offset];
|
||||
}
|
||||
|
||||
if(address & 0x800) {
|
||||
if(address < 0x1900) {
|
||||
high_ram_ptr_[address & 255] = *value;
|
||||
} else if(address < 0x1a00) {
|
||||
if(isReadOperation(operation)) *value = high_ram_ptr_[address & 255];
|
||||
} else {
|
||||
if(isReadOperation(operation)) *value = rom_ptr_[1][address & 2047];
|
||||
}
|
||||
} else {
|
||||
if(rom_ptr_[0]) {
|
||||
if(isReadOperation(operation)) *value = rom_ptr_[0][address & 2047];
|
||||
} else {
|
||||
if(address < 0x1400) {
|
||||
low_ram_[address & 1023] = *value;
|
||||
} else {
|
||||
if(isReadOperation(operation)) *value = low_ram_[address & 1023];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *rom_ptr_[2];
|
||||
uint8_t *high_ram_ptr_;
|
||||
uint8_t low_ram_[1024], high_ram_[1024];
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Atari2600_CartridgeMNetwork_hpp */
|
||||
45
Machines/Atari2600/Cartridges/CartridgeMegaBoy.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// CartridgeMegaBoy.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/03/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Atari2600_CartridgeMegaBoy_hpp
|
||||
#define Atari2600_CartridgeMegaBoy_hpp
|
||||
|
||||
#include "Cartridge.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
class CartridgeMegaBoy: public Cartridge<CartridgeMegaBoy> {
|
||||
public:
|
||||
CartridgeMegaBoy(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom),
|
||||
current_page_(0) {
|
||||
rom_ptr_ = rom_.data();
|
||||
}
|
||||
|
||||
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
if(address == 0x1ff0) {
|
||||
current_page_ = (current_page_ + 1) & 15;
|
||||
rom_ptr_ = rom_.data() + current_page_ * 4096;
|
||||
}
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *rom_ptr_;
|
||||
uint8_t current_page_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* CartridgeMegaBoy_h */
|
||||
46
Machines/Atari2600/Cartridges/CartridgeParkerBros.hpp
Normal file
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// CartridgeParkerBros.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/03/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Atari2600_CartridgeParkerBros_hpp
|
||||
#define Atari2600_CartridgeParkerBros_hpp
|
||||
|
||||
#include "Cartridge.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
class CartridgeParkerBros: public Cartridge<CartridgeParkerBros> {
|
||||
public:
|
||||
CartridgeParkerBros(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom) {
|
||||
rom_ptr_[0] = rom_.data() + 4096;
|
||||
rom_ptr_[1] = rom_ptr_[0] + 1024;
|
||||
rom_ptr_[2] = rom_ptr_[1] + 1024;
|
||||
rom_ptr_[3] = rom_ptr_[2] + 1024;
|
||||
}
|
||||
|
||||
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
if(address >= 0x1fe0 && address < 0x1ff8) {
|
||||
int slot = (address >> 3)&3;
|
||||
rom_ptr_[slot] = rom_.data() + ((address & 7) * 1024);
|
||||
}
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[(address >> 10)&3][address & 1023];
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *rom_ptr_[4];
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Atari2600_CartridgeParkerBros_hpp */
|
||||
134
Machines/Atari2600/Cartridges/CartridgePitfall2.hpp
Normal file
@@ -0,0 +1,134 @@
|
||||
//
|
||||
// CartridgePitfall2.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/03/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Atari2600_CartridgePitfall2_hpp
|
||||
#define Atari2600_CartridgePitfall2_hpp
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
|
||||
public:
|
||||
CartridgePitfall2(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom),
|
||||
random_number_generator_(0),
|
||||
featcher_address_{0, 0, 0, 0, 0, 0, 0, 0},
|
||||
mask_{0, 0, 0, 0, 0, 0, 0, 0},
|
||||
cycles_since_audio_update_(0) {
|
||||
rom_ptr_ = rom_.data();
|
||||
}
|
||||
|
||||
void advance_cycles(unsigned int cycles) {
|
||||
cycles_since_audio_update_ += cycles;
|
||||
}
|
||||
|
||||
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
switch(address) {
|
||||
|
||||
#pragma mark - Reads
|
||||
|
||||
// The random number generator
|
||||
case 0x1000: case 0x1001: case 0x1002: case 0x1003: case 0x1004:
|
||||
if(isReadOperation(operation)) {
|
||||
*value = random_number_generator_;
|
||||
}
|
||||
random_number_generator_ = (uint8_t)(
|
||||
(random_number_generator_ << 1) |
|
||||
(~( (random_number_generator_ >> 7) ^
|
||||
(random_number_generator_ >> 5) ^
|
||||
(random_number_generator_ >> 4) ^
|
||||
(random_number_generator_ >> 3)
|
||||
) & 1));
|
||||
break;
|
||||
|
||||
case 0x1005: case 0x1006: case 0x1007:
|
||||
*value = update_audio();
|
||||
break;
|
||||
|
||||
case 0x1008: case 0x1009: case 0x100a: case 0x100b: case 0x100c: case 0x100d: case 0x100e: case 0x100f:
|
||||
*value = rom_[8192 + address_for_counter(address & 7)];
|
||||
break;
|
||||
|
||||
case 0x1010: case 0x1011: case 0x1012: case 0x1013: case 0x1014: case 0x1015: case 0x1016: case 0x1017:
|
||||
*value = rom_[8192 + address_for_counter(address & 7)] & mask_[address & 7];
|
||||
break;
|
||||
|
||||
#pragma mark - Writes
|
||||
|
||||
case 0x1040: case 0x1041: case 0x1042: case 0x1043: case 0x1044: case 0x1045: case 0x1046: case 0x1047:
|
||||
top_[address & 7] = *value;
|
||||
break;
|
||||
case 0x1048: case 0x1049: case 0x104a: case 0x104b: case 0x104c: case 0x104d: case 0x104e: case 0x104f:
|
||||
bottom_[address & 7] = *value;
|
||||
break;
|
||||
case 0x1050: case 0x1051: case 0x1052: case 0x1053: case 0x1054: case 0x1055: case 0x1056: case 0x1057:
|
||||
featcher_address_[address & 7] = (featcher_address_[address & 7] & 0xff00) | *value;
|
||||
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);
|
||||
break;
|
||||
case 0x1070: case 0x1071: case 0x1072: case 0x1073: case 0x1074: case 0x1075: case 0x1076: case 0x1077:
|
||||
random_number_generator_ = 0;
|
||||
break;
|
||||
|
||||
#pragma mark - Paging
|
||||
|
||||
case 0x1ff8: rom_ptr_ = rom_.data(); break;
|
||||
case 0x1ff9: rom_ptr_ = rom_.data() + 4096; break;
|
||||
|
||||
#pragma mark - Business as usual
|
||||
|
||||
default:
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
inline uint16_t address_for_counter(int counter) {
|
||||
uint16_t fetch_address = (featcher_address_[counter] & 2047) ^ 2047;
|
||||
if((featcher_address_[counter] & 0xff) == top_[counter]) mask_[counter] = 0xff;
|
||||
if((featcher_address_[counter] & 0xff) == bottom_[counter]) mask_[counter] = 0x00;
|
||||
featcher_address_[counter]--;
|
||||
return fetch_address;
|
||||
}
|
||||
|
||||
inline uint8_t update_audio() {
|
||||
const unsigned int clock_divisor = 57;
|
||||
unsigned int cycles_to_run_for = cycles_since_audio_update_ / clock_divisor;
|
||||
cycles_since_audio_update_ %= clock_divisor;
|
||||
|
||||
int table_position = 0;
|
||||
for(int c = 0; c < 3; c++) {
|
||||
audio_channel_[c] = (audio_channel_[c] + cycles_to_run_for) % (1 + top_[5 + c]);
|
||||
if((featcher_address_[5 + c] & 0x1000) && ((top_[5 + c] - audio_channel_[c]) > bottom_[5 + c])) {
|
||||
table_position |= 0x4 >> c;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t level_table[8] = { 0x0, 0x4, 0x5, 0x9, 0x6, 0xa, 0xb, 0xf };
|
||||
return level_table[table_position];
|
||||
}
|
||||
|
||||
uint16_t featcher_address_[8];
|
||||
uint8_t top_[8], bottom_[8], mask_[8];
|
||||
uint8_t music_mode_[3];
|
||||
uint8_t random_number_generator_;
|
||||
uint8_t *rom_ptr_;
|
||||
uint8_t audio_channel_[3];
|
||||
unsigned int cycles_since_audio_update_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Atari2600_CartridgePitfall2_hpp */
|
||||
40
Machines/Atari2600/Cartridges/CartridgeTigervision.hpp
Normal file
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// CartridgeTigervision.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/03/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Atari2600_CartridgeTigervision_hpp
|
||||
#define Atari2600_CartridgeTigervision_hpp
|
||||
|
||||
#include "Cartridge.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
class CartridgeTigervision: public Cartridge<CartridgeTigervision> {
|
||||
public:
|
||||
CartridgeTigervision(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom) {
|
||||
rom_ptr_[0] = rom_.data() + rom_.size() - 4096;
|
||||
rom_ptr_[1] = rom_ptr_[0] + 2048;
|
||||
}
|
||||
|
||||
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
if((address&0x1fff) == 0x3f) {
|
||||
int offset = ((*value) * 2048) & (rom_.size() - 1);
|
||||
rom_ptr_[0] = rom_.data() + offset;
|
||||
return;
|
||||
} else if((address&0x1000) && isReadOperation(operation)) {
|
||||
*value = rom_ptr_[(address >> 11)&1][address & 2047];
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *rom_ptr_[2];
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Atari2600_CartridgeTigervision_hpp */
|
||||
30
Machines/Atari2600/Cartridges/CartridgeUnpaged.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// CartridgeUnpaged.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 17/03/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Atari2600_CartridgeUnpaged_hpp
|
||||
#define Atari2600_CartridgeUnpaged_hpp
|
||||
|
||||
#include "Cartridge.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
class CartridgeUnpaged: public Cartridge<CartridgeUnpaged> {
|
||||
public:
|
||||
CartridgeUnpaged(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom) {}
|
||||
|
||||
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
if(isReadOperation(operation) && (address & 0x1000)) {
|
||||
*value = rom_[address & (rom_.size() - 1)];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Atari2600_CartridgeUnpaged_hpp */
|
||||
@@ -15,13 +15,11 @@ namespace Atari2600 {
|
||||
|
||||
class PIA: public MOS::MOS6532<PIA> {
|
||||
public:
|
||||
inline uint8_t get_port_input(int port)
|
||||
{
|
||||
inline uint8_t get_port_input(int port) {
|
||||
return port_values_[port];
|
||||
}
|
||||
|
||||
inline void update_port_input(int port, uint8_t mask, bool set)
|
||||
{
|
||||
inline void update_port_input(int port, uint8_t mask, bool set) {
|
||||
if(set) port_values_[port] &= ~mask; else port_values_[port] |= mask;
|
||||
set_port_did_change(port);
|
||||
}
|
||||
|
||||
@@ -16,23 +16,20 @@ Atari2600::Speaker::Speaker() :
|
||||
poly9_counter_{0x1ff, 0x1ff}
|
||||
{}
|
||||
|
||||
void Atari2600::Speaker::set_volume(int channel, uint8_t volume)
|
||||
{
|
||||
void Atari2600::Speaker::set_volume(int channel, uint8_t volume) {
|
||||
enqueue([=]() {
|
||||
volume_[channel] = volume & 0xf;
|
||||
});
|
||||
}
|
||||
|
||||
void Atari2600::Speaker::set_divider(int channel, uint8_t divider)
|
||||
{
|
||||
void Atari2600::Speaker::set_divider(int channel, uint8_t divider) {
|
||||
enqueue([=]() {
|
||||
divider_[channel] = divider & 0x1f;
|
||||
divider_counter_[channel] = 0;
|
||||
});
|
||||
}
|
||||
|
||||
void Atari2600::Speaker::set_control(int channel, uint8_t control)
|
||||
{
|
||||
void Atari2600::Speaker::set_control(int channel, uint8_t control) {
|
||||
enqueue([=]() {
|
||||
control_[channel] = control & 0xf;
|
||||
});
|
||||
@@ -42,17 +39,13 @@ void Atari2600::Speaker::set_control(int channel, uint8_t control)
|
||||
#define advance_poly5(c) poly5_counter_[channel] = (poly5_counter_[channel] >> 1) | (((poly5_counter_[channel] << 4) ^ (poly5_counter_[channel] << 2))&0x010)
|
||||
#define advance_poly9(c) poly9_counter_[channel] = (poly9_counter_[channel] >> 1) | (((poly9_counter_[channel] << 4) ^ (poly9_counter_[channel] << 8))&0x100)
|
||||
|
||||
void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *target)
|
||||
{
|
||||
for(unsigned int c = 0; c < number_of_samples; c++)
|
||||
{
|
||||
void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
|
||||
for(unsigned int c = 0; c < number_of_samples; c++) {
|
||||
target[c] = 0;
|
||||
for(int channel = 0; channel < 2; channel++)
|
||||
{
|
||||
for(int channel = 0; channel < 2; channel++) {
|
||||
divider_counter_[channel] ++;
|
||||
int level = 0;
|
||||
switch(control_[channel])
|
||||
{
|
||||
switch(control_[channel]) {
|
||||
case 0x0: case 0xb: // constant 1
|
||||
level = 1;
|
||||
break;
|
||||
@@ -75,8 +68,7 @@ void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *ta
|
||||
|
||||
case 0x1: // 4-bit poly
|
||||
level = poly4_counter_[channel]&1;
|
||||
if(divider_counter_[channel] == divider_[channel]+1)
|
||||
{
|
||||
if(divider_counter_[channel] == divider_[channel]+1) {
|
||||
divider_counter_[channel] = 0;
|
||||
advance_poly4(channel);
|
||||
}
|
||||
@@ -84,18 +76,15 @@ void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *ta
|
||||
|
||||
case 0x2: // 4-bit poly div31
|
||||
level = poly4_counter_[channel]&1;
|
||||
if(divider_counter_[channel]%(30*(divider_[channel]+1)) == 18)
|
||||
{
|
||||
if(divider_counter_[channel]%(30*(divider_[channel]+1)) == 18) {
|
||||
advance_poly4(channel);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x3: // 5/4-bit poly
|
||||
level = output_state_[channel];
|
||||
if(divider_counter_[channel] == divider_[channel]+1)
|
||||
{
|
||||
if(poly5_counter_[channel]&1)
|
||||
{
|
||||
if(divider_counter_[channel] == divider_[channel]+1) {
|
||||
if(poly5_counter_[channel]&1) {
|
||||
output_state_[channel] = poly4_counter_[channel]&1;
|
||||
advance_poly4(channel);
|
||||
}
|
||||
@@ -105,8 +94,7 @@ void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *ta
|
||||
|
||||
case 0x7: case 0x9: // 5-bit poly
|
||||
level = poly5_counter_[channel]&1;
|
||||
if(divider_counter_[channel] == divider_[channel]+1)
|
||||
{
|
||||
if(divider_counter_[channel] == divider_[channel]+1) {
|
||||
divider_counter_[channel] = 0;
|
||||
advance_poly5(channel);
|
||||
}
|
||||
@@ -114,8 +102,7 @@ void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *ta
|
||||
|
||||
case 0xf: // 5-bit poly div6
|
||||
level = poly5_counter_[channel]&1;
|
||||
if(divider_counter_[channel] == (divider_[channel]+1)*3)
|
||||
{
|
||||
if(divider_counter_[channel] == (divider_[channel]+1)*3) {
|
||||
divider_counter_[channel] = 0;
|
||||
advance_poly5(channel);
|
||||
}
|
||||
@@ -123,8 +110,7 @@ void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *ta
|
||||
|
||||
case 0x8: // 9-bit poly
|
||||
level = poly9_counter_[channel]&1;
|
||||
if(divider_counter_[channel] == divider_[channel]+1)
|
||||
{
|
||||
if(divider_counter_[channel] == divider_[channel]+1) {
|
||||
divider_counter_[channel] = 0;
|
||||
advance_poly9(channel);
|
||||
}
|
||||
|
||||
679
Machines/Atari2600/TIA.cpp
Normal file
@@ -0,0 +1,679 @@
|
||||
//
|
||||
// TIA.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 28/01/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "TIA.hpp"
|
||||
#include <cassert>
|
||||
|
||||
using namespace Atari2600;
|
||||
namespace {
|
||||
const int cycles_per_line = 228;
|
||||
const int first_pixel_cycle = 68;
|
||||
|
||||
const int sync_flag = 0x1;
|
||||
const int blank_flag = 0x2;
|
||||
|
||||
uint8_t reverse_table[256];
|
||||
}
|
||||
|
||||
TIA::TIA(bool create_crt) :
|
||||
horizontal_counter_(0),
|
||||
pixels_start_location_(0),
|
||||
output_mode_(0),
|
||||
pixel_target_(nullptr),
|
||||
background_{0, 0},
|
||||
background_half_mask_(0),
|
||||
horizontal_blank_extend_(false),
|
||||
collision_flags_(0)
|
||||
{
|
||||
if(create_crt) {
|
||||
crt_.reset(new Outputs::CRT::CRT(cycles_per_line * 2 - 1, 1, Outputs::CRT::DisplayType::NTSC60, 1));
|
||||
crt_->set_output_device(Outputs::CRT::Television);
|
||||
set_output_mode(OutputMode::NTSC);
|
||||
}
|
||||
|
||||
for(int c = 0; c < 256; c++) {
|
||||
reverse_table[c] = (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);
|
||||
|
||||
uint8_t collision_registers[8];
|
||||
collision_registers[0] = ((has_missile0 && has_player1) ? 0x80 : 0x00) | ((has_missile0 && has_player0) ? 0x40 : 0x00);
|
||||
collision_registers[1] = ((has_missile1 && has_player0) ? 0x80 : 0x00) | ((has_missile1 && has_player1) ? 0x40 : 0x00);
|
||||
collision_registers[2] = ((has_playfield && has_player0) ? 0x80 : 0x00) | ((has_ball && has_player0) ? 0x40 : 0x00);
|
||||
collision_registers[3] = ((has_playfield && has_player1) ? 0x80 : 0x00) | ((has_ball && has_player1) ? 0x40 : 0x00);
|
||||
collision_registers[4] = ((has_playfield && has_missile0) ? 0x80 : 0x00) | ((has_ball && has_missile0) ? 0x40 : 0x00);
|
||||
collision_registers[5] = ((has_playfield && has_missile1) ? 0x80 : 0x00) | ((has_ball && has_missile1) ? 0x40 : 0x00);
|
||||
collision_registers[6] = ((has_playfield && has_ball) ? 0x80 : 0x00);
|
||||
collision_registers[7] = ((has_player0 && has_player1) ? 0x80 : 0x00) | ((has_missile0 && has_missile1) ? 0x40 : 0x00);
|
||||
collision_flags_by_buffer_vaules_[c] =
|
||||
(collision_registers[0] >> 6) |
|
||||
(collision_registers[1] >> 4) |
|
||||
(collision_registers[2] >> 2) |
|
||||
(collision_registers[3] >> 0) |
|
||||
(collision_registers[4] << 2) |
|
||||
(collision_registers[5] << 4) |
|
||||
(collision_registers[6] << 6) |
|
||||
(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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TIA::TIA() : TIA(true) {}
|
||||
|
||||
TIA::TIA(std::function<void(uint8_t *output_buffer)> line_end_function) : TIA(false) {
|
||||
line_end_function_ = line_end_function;
|
||||
}
|
||||
|
||||
void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) {
|
||||
Outputs::CRT::DisplayType display_type;
|
||||
|
||||
if(output_mode == OutputMode::NTSC) {
|
||||
crt_->set_composite_sampling_function(
|
||||
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
|
||||
"{"
|
||||
"uint c = texture(texID, coordinate).r;"
|
||||
"uint y = c & 14u;"
|
||||
"uint iPhase = (c >> 4);"
|
||||
|
||||
"float phaseOffset = 6.283185308 * (float(iPhase) + 3.0) / 13.0 + 5.074880441076923;"
|
||||
"return mix(float(y) / 14.0, step(1, iPhase) * cos(phase + phaseOffset), amplitude);"
|
||||
"}");
|
||||
display_type = Outputs::CRT::DisplayType::NTSC60;
|
||||
} else {
|
||||
crt_->set_composite_sampling_function(
|
||||
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
|
||||
"{"
|
||||
"uint c = texture(texID, coordinate).r;"
|
||||
"uint y = c & 14u;"
|
||||
"uint iPhase = (c >> 4);"
|
||||
|
||||
"uint direction = iPhase & 1u;"
|
||||
"float phaseOffset = float(7u - direction) + (float(direction) - 0.5) * 2.0 * float(iPhase >> 1);"
|
||||
"phaseOffset *= 6.283185308 / 12.0;"
|
||||
"return mix(float(y) / 14.0, step(4, (iPhase + 2u) & 15u) * cos(phase + phaseOffset), amplitude);"
|
||||
"}");
|
||||
display_type = Outputs::CRT::DisplayType::PAL50;
|
||||
}
|
||||
// line number of cycles in a line of video is one less than twice the number of clock cycles per line; the Atari
|
||||
// outputs 228 colour cycles of material per line when an NTSC line 227.5. Since all clock numbers will be doubled
|
||||
// later, cycles_per_line * 2 - 1 is therefore the real length of an NTSC line, even though we're going to supply
|
||||
// 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));*/
|
||||
}
|
||||
|
||||
void TIA::run_for_cycles(int number_of_cycles)
|
||||
{
|
||||
// if part way through a line, definitely perform a partial, at most up to the end of the line
|
||||
if(horizontal_counter_) {
|
||||
int cycles = std::min(number_of_cycles, cycles_per_line - horizontal_counter_);
|
||||
output_for_cycles(cycles);
|
||||
number_of_cycles -= cycles;
|
||||
}
|
||||
|
||||
// output full lines for as long as possible
|
||||
while(number_of_cycles >= cycles_per_line) {
|
||||
output_line();
|
||||
number_of_cycles -= cycles_per_line;
|
||||
}
|
||||
|
||||
// partly start a new line if necessary
|
||||
if(number_of_cycles) {
|
||||
output_for_cycles(number_of_cycles);
|
||||
}
|
||||
}
|
||||
|
||||
void TIA::set_sync(bool sync) {
|
||||
output_mode_ = (output_mode_ & ~sync_flag) | (sync ? sync_flag : 0);
|
||||
}
|
||||
|
||||
void TIA::set_blank(bool blank) {
|
||||
output_mode_ = (output_mode_ & ~blank_flag) | (blank ? blank_flag : 0);
|
||||
}
|
||||
|
||||
void TIA::reset_horizontal_counter() {
|
||||
}
|
||||
|
||||
int TIA::get_cycles_until_horizontal_blank(unsigned int from_offset) {
|
||||
return (cycles_per_line - (horizontal_counter_ + (int)from_offset) % cycles_per_line) % cycles_per_line;
|
||||
}
|
||||
|
||||
void TIA::set_background_colour(uint8_t colour) {
|
||||
colour_palette_[(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);
|
||||
break;
|
||||
case 1:
|
||||
background_[1] = (background_[1] & 0xf00ff) | ((uint32_t)value << 8);
|
||||
background_[0] = (background_[0] & 0xff00f) | ((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);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void TIA::set_playfield_control_and_ball_size(uint8_t value) {
|
||||
background_half_mask_ = value & 1;
|
||||
switch(value & 6) {
|
||||
case 0:
|
||||
playfield_priority_ = PlayfieldPriority::Standard;
|
||||
break;
|
||||
case 2:
|
||||
playfield_priority_ = PlayfieldPriority::Score;
|
||||
break;
|
||||
case 4:
|
||||
case 6:
|
||||
playfield_priority_ = PlayfieldPriority::OnTop;
|
||||
break;
|
||||
}
|
||||
|
||||
ball_.size = 1 << ((value >> 4)&3);
|
||||
}
|
||||
|
||||
void TIA::set_playfield_ball_colour(uint8_t colour) {
|
||||
colour_palette_[(int)ColourIndex::PlayfieldBall] = colour;
|
||||
}
|
||||
|
||||
void TIA::set_player_number_and_size(int player, uint8_t value) {
|
||||
assert(player >= 0 && player < 2);
|
||||
int size = 0;
|
||||
switch(value & 7) {
|
||||
case 0: case 1: case 2: case 3: case 4:
|
||||
player_[player].copy_flags = value & 7;
|
||||
break;
|
||||
case 5:
|
||||
size = 1;
|
||||
player_[player].copy_flags = 0;
|
||||
break;
|
||||
case 6:
|
||||
player_[player].copy_flags = 6;
|
||||
break;
|
||||
case 7:
|
||||
size = 2;
|
||||
player_[player].copy_flags = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
missile_[player].size = 1 << ((value >> 4)&3);
|
||||
missile_[player].copy_flags = player_[player].copy_flags;
|
||||
player_[player].adder = 4 >> size;
|
||||
}
|
||||
|
||||
void TIA::set_player_graphic(int player, uint8_t value) {
|
||||
assert(player >= 0 && player < 2);
|
||||
player_[player].graphic[1] = value;
|
||||
player_[player^1].graphic[0] = player_[player^1].graphic[1];
|
||||
if(player) ball_.enabled[0] = ball_.enabled[1];
|
||||
}
|
||||
|
||||
void TIA::set_player_reflected(int player, bool reflected) {
|
||||
assert(player >= 0 && player < 2);
|
||||
player_[player].reverse_mask = reflected ? 7 : 0;
|
||||
}
|
||||
|
||||
void TIA::set_player_delay(int player, bool delay) {
|
||||
assert(player >= 0 && player < 2);
|
||||
player_[player].graphic_index = delay ? 0 : 1;
|
||||
}
|
||||
|
||||
void TIA::set_player_position(int player) {
|
||||
assert(player >= 0 && player < 2);
|
||||
// players have an extra clock of delay before output and don't display upon reset;
|
||||
// both aims are achieved by setting to -1 because: (i) it causes the clock to be
|
||||
// one behind its real hardware value, creating the extra delay; and (ii) the player
|
||||
// code is written to start a draw upon wraparound from 159 to 0, so -1 is the
|
||||
// correct option rather than 159.
|
||||
player_[player].position = -1;
|
||||
}
|
||||
|
||||
void TIA::set_player_motion(int player, uint8_t motion) {
|
||||
assert(player >= 0 && player < 2);
|
||||
player_[player].motion = (motion >> 4)&0xf;
|
||||
}
|
||||
|
||||
void TIA::set_player_missile_colour(int player, uint8_t colour) {
|
||||
assert(player >= 0 && player < 2);
|
||||
colour_palette_[(int)ColourIndex::PlayerMissile0 + player] = colour;
|
||||
}
|
||||
|
||||
void TIA::set_missile_enable(int missile, bool enabled) {
|
||||
assert(missile >= 0 && missile < 2);
|
||||
missile_[missile].enabled = enabled;
|
||||
}
|
||||
|
||||
void TIA::set_missile_position(int missile) {
|
||||
assert(missile >= 0 && missile < 2);
|
||||
missile_[missile].position = 0;
|
||||
}
|
||||
|
||||
void TIA::set_missile_position_to_player(int missile, bool lock) {
|
||||
assert(missile >= 0 && missile < 2);
|
||||
missile_[missile].locked_to_player = lock;
|
||||
player_[missile].latched_pixel4_time = -1;
|
||||
}
|
||||
|
||||
void TIA::set_missile_motion(int missile, uint8_t motion) {
|
||||
assert(missile >= 0 && missile < 2);
|
||||
missile_[missile].motion = (motion >> 4)&0xf;
|
||||
}
|
||||
|
||||
void TIA::set_ball_enable(bool enabled) {
|
||||
ball_.enabled[1] = enabled;
|
||||
}
|
||||
|
||||
void TIA::set_ball_delay(bool delay) {
|
||||
ball_.enabled_index = delay ? 0 : 1;
|
||||
}
|
||||
|
||||
void TIA::set_ball_position() {
|
||||
ball_.position = 0;
|
||||
|
||||
// setting the ball position also triggers a draw
|
||||
ball_.reset_pixels(0);
|
||||
}
|
||||
|
||||
void TIA::set_ball_motion(uint8_t motion) {
|
||||
ball_.motion = (motion >> 4) & 0xf;
|
||||
}
|
||||
|
||||
void TIA::move() {
|
||||
horizontal_blank_extend_ = true;
|
||||
player_[0].is_moving = player_[1].is_moving = missile_[0].is_moving = missile_[1].is_moving = ball_.is_moving = true;
|
||||
player_[0].motion_step = player_[1].motion_step = missile_[0].motion_step = missile_[1].motion_step = ball_.motion_step = 15;
|
||||
player_[0].motion_time = player_[1].motion_time = missile_[0].motion_time = missile_[1].motion_time = ball_.motion_time = (horizontal_counter_ + 3) & ~3;
|
||||
}
|
||||
|
||||
void TIA::clear_motion() {
|
||||
player_[0].motion = player_[1].motion = missile_[0].motion = missile_[1].motion = ball_.motion = 0;
|
||||
}
|
||||
|
||||
uint8_t TIA::get_collision_flags(int offset) {
|
||||
return (uint8_t)((collision_flags_ >> (offset << 1)) << 6) & 0xc0;
|
||||
}
|
||||
|
||||
void TIA::clear_collision_flags() {
|
||||
collision_flags_ = 0;
|
||||
}
|
||||
|
||||
void TIA::output_for_cycles(int number_of_cycles) {
|
||||
/*
|
||||
Line timing is oriented around 0 being the start of the right-hand side vertical blank;
|
||||
a wsync synchronises the CPU to horizontal_counter_ = 0. All timing below is in terms of the
|
||||
NTSC colour clock.
|
||||
|
||||
Therefore, each line is composed of:
|
||||
|
||||
16 cycles: blank ; -> 16
|
||||
16 cycles: sync ; -> 32
|
||||
16 cycles: colour burst ; -> 48
|
||||
20 cycles: blank ; -> 68
|
||||
8 cycles: blank or pixels, depending on whether the blank extend bit is set
|
||||
152 cycles: pixels
|
||||
*/
|
||||
int output_cursor = horizontal_counter_;
|
||||
horizontal_counter_ += number_of_cycles;
|
||||
bool is_reset = output_cursor < 224 && horizontal_counter_ >= 224;
|
||||
|
||||
if(!output_cursor) {
|
||||
if(line_end_function_) line_end_function_(collision_buffer_);
|
||||
memset(collision_buffer_, 0, sizeof(collision_buffer_));
|
||||
|
||||
ball_.motion_time %= 228;
|
||||
player_[0].motion_time %= 228;
|
||||
player_[1].motion_time %= 228;
|
||||
missile_[0].motion_time %= 228;
|
||||
missile_[1].motion_time %= 228;
|
||||
}
|
||||
|
||||
// accumulate an OR'd version of the output into the collision buffer
|
||||
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_);
|
||||
|
||||
// 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)); \
|
||||
horizontal_counter_ %= cycles_per_line; \
|
||||
return; \
|
||||
} else { \
|
||||
if(crt_) crt_->function((unsigned int)((target - output_cursor) * 2)); \
|
||||
output_cursor = target; \
|
||||
} \
|
||||
}
|
||||
|
||||
switch(output_mode_) {
|
||||
default:
|
||||
Period(output_blank, 16)
|
||||
Period(output_sync, 32)
|
||||
Period(output_default_colour_burst, 48)
|
||||
Period(output_blank, 68)
|
||||
break;
|
||||
case sync_flag:
|
||||
case sync_flag | blank_flag:
|
||||
Period(output_sync, 16)
|
||||
Period(output_blank, 32)
|
||||
Period(output_default_colour_burst, 48)
|
||||
Period(output_sync, 228)
|
||||
break;
|
||||
}
|
||||
|
||||
#undef Period
|
||||
|
||||
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);
|
||||
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));
|
||||
} else {
|
||||
if(!pixels_start_location_ && crt_) {
|
||||
pixels_start_location_ = output_cursor;
|
||||
pixel_target_ = crt_->allocate_write_area(160);
|
||||
}
|
||||
|
||||
// convert that into pixels
|
||||
if(pixel_target_) output_pixels(output_cursor, horizontal_counter_);
|
||||
|
||||
// accumulate collision flags
|
||||
while(output_cursor < horizontal_counter_) {
|
||||
collision_flags_ |= collision_flags_by_buffer_vaules_[collision_buffer_[output_cursor - first_pixel_cycle]];
|
||||
output_cursor++;
|
||||
}
|
||||
|
||||
if(horizontal_counter_ == cycles_per_line && crt_) {
|
||||
crt_->output_data((unsigned int)(output_cursor - pixels_start_location_) * 2, 2);
|
||||
pixel_target_ = nullptr;
|
||||
pixels_start_location_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(is_reset) horizontal_blank_extend_ = false;
|
||||
|
||||
horizontal_counter_ %= cycles_per_line;
|
||||
}
|
||||
|
||||
void TIA::output_pixels(int start, int end) {
|
||||
start = std::max(start, pixels_start_location_);
|
||||
int target_position = start - pixels_start_location_;
|
||||
|
||||
if(start < first_pixel_cycle+8 && horizontal_blank_extend_) {
|
||||
while(start < end && start < first_pixel_cycle+8) {
|
||||
pixel_target_[target_position] = 0;
|
||||
start++;
|
||||
target_position++;
|
||||
}
|
||||
}
|
||||
|
||||
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]];
|
||||
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]];
|
||||
start++;
|
||||
target_position++;
|
||||
}
|
||||
} else {
|
||||
int table_index = (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]];
|
||||
start++;
|
||||
target_position++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TIA::output_line() {
|
||||
switch(output_mode_) {
|
||||
default:
|
||||
// TODO: optimise special case
|
||||
output_for_cycles(cycles_per_line);
|
||||
break;
|
||||
case sync_flag:
|
||||
case sync_flag | blank_flag:
|
||||
if(crt_) {
|
||||
crt_->output_sync(32);
|
||||
crt_->output_blank(32);
|
||||
crt_->output_sync(392);
|
||||
}
|
||||
horizontal_blank_extend_ = false;
|
||||
break;
|
||||
case blank_flag:
|
||||
if(crt_) {
|
||||
crt_->output_blank(32);
|
||||
crt_->output_sync(32);
|
||||
crt_->output_default_colour_burst(32);
|
||||
crt_->output_blank(360);
|
||||
}
|
||||
horizontal_blank_extend_ = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Playfield output
|
||||
|
||||
void TIA::draw_playfield(int start, int end) {
|
||||
// don't do anything if this window ends too early
|
||||
if(end < first_pixel_cycle) return;
|
||||
|
||||
// clip to drawable bounds
|
||||
start = std::max(start, first_pixel_cycle);
|
||||
end = std::min(end, 228);
|
||||
|
||||
// proceed along four-pixel boundaries, plotting four pixels at a time
|
||||
int aligned_position = (start + 3)&~3;
|
||||
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;
|
||||
aligned_position += 4;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Motion
|
||||
|
||||
template<class T> void TIA::perform_motion_step(T &object) {
|
||||
if((object.motion_step ^ (object.motion ^ 8)) == 0xf) {
|
||||
object.is_moving = false;
|
||||
} else {
|
||||
if(object.position == 159) object.reset_pixels(0);
|
||||
else if(object.position == 15 && object.copy_flags&1) object.reset_pixels(1);
|
||||
else if(object.position == 31 && object.copy_flags&2) object.reset_pixels(2);
|
||||
else if(object.position == 63 && object.copy_flags&4) object.reset_pixels(3);
|
||||
else object.skip_pixels(1, object.motion_time);
|
||||
object.position = (object.position + 1) % 160;
|
||||
object.motion_step --;
|
||||
object.motion_time += 4;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T> void TIA::perform_border_motion(T &object, int start, int end) {
|
||||
while(object.is_moving && object.motion_time < end)
|
||||
perform_motion_step<T>(object);
|
||||
}
|
||||
|
||||
template<class T> void TIA::draw_object(T &object, const uint8_t collision_identity, int start, int end) {
|
||||
int first_pixel = first_pixel_cycle - 4 + (horizontal_blank_extend_ ? 8 : 0);
|
||||
|
||||
object.dequeue_pixels(collision_buffer_, collision_identity, end - first_pixel_cycle);
|
||||
|
||||
// movement works across the entire screen, so do work that falls outside of the pixel area
|
||||
if(start < first_pixel) {
|
||||
perform_border_motion<T>(object, start, std::min(end, first_pixel));
|
||||
}
|
||||
|
||||
// don't continue to do any drawing if this window ends too early
|
||||
if(end < first_pixel) return;
|
||||
if(start < first_pixel) start = first_pixel;
|
||||
if(start >= end) return;
|
||||
|
||||
// perform the visible part of the line, if any
|
||||
if(start < 224) {
|
||||
draw_object_visible<T>(object, collision_identity, start - first_pixel_cycle + 4, std::min(end - first_pixel_cycle + 4, 160), end - first_pixel_cycle);
|
||||
}
|
||||
|
||||
// move further if required
|
||||
if(object.is_moving && end >= 224 && object.motion_time < end) {
|
||||
perform_motion_step<T>(object);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T> void TIA::draw_object_visible(T &object, const uint8_t collision_identity, int start, int end, int time_now) {
|
||||
// perform a miniature event loop on (i) triggering draws; (ii) drawing; and (iii) motion
|
||||
int next_motion_time = object.motion_time - first_pixel_cycle + 4;
|
||||
while(start < end) {
|
||||
int next_event_time = end;
|
||||
|
||||
// is the next event a movement tick?
|
||||
if(object.is_moving && next_motion_time < next_event_time) {
|
||||
next_event_time = next_motion_time;
|
||||
}
|
||||
|
||||
// is the next event a graphics trigger?
|
||||
int next_copy = 160;
|
||||
int next_copy_id = 0;
|
||||
if(object.copy_flags) {
|
||||
if(object.position < 16 && object.copy_flags&1) {
|
||||
next_copy = 16;
|
||||
next_copy_id = 1;
|
||||
} else if(object.position < 32 && object.copy_flags&2) {
|
||||
next_copy = 32;
|
||||
next_copy_id = 2;
|
||||
} else if(object.position < 64 && object.copy_flags&4) {
|
||||
next_copy = 64;
|
||||
next_copy_id = 3;
|
||||
}
|
||||
}
|
||||
|
||||
int next_copy_time = start + next_copy - object.position;
|
||||
if(next_copy_time < next_event_time) next_event_time = next_copy_time;
|
||||
|
||||
// the decision is to progress by length
|
||||
const int length = next_event_time - start;
|
||||
|
||||
// enqueue a future intention to draw pixels if spitting them out now would violate accuracy;
|
||||
// otherwise draw them now
|
||||
if(object.enqueues && next_event_time > time_now) {
|
||||
if(start < time_now) {
|
||||
object.output_pixels(&collision_buffer_[start], time_now - start, collision_identity, start + first_pixel_cycle - 4);
|
||||
object.enqueue_pixels(time_now, next_event_time, time_now + first_pixel_cycle - 4);
|
||||
} else {
|
||||
object.enqueue_pixels(start, next_event_time, start + first_pixel_cycle - 4);
|
||||
}
|
||||
} else {
|
||||
object.output_pixels(&collision_buffer_[start], length, collision_identity, start + first_pixel_cycle - 4);
|
||||
}
|
||||
|
||||
// the next interesting event is after next_event_time cycles, so progress
|
||||
object.position = (object.position + length) % 160;
|
||||
start = next_event_time;
|
||||
|
||||
// if the event is a motion tick, apply; if it's a draw trigger, trigger a draw
|
||||
if(object.is_moving && start == next_motion_time) {
|
||||
perform_motion_step(object);
|
||||
next_motion_time += 4;
|
||||
} else if(start == next_copy_time) {
|
||||
object.reset_pixels(next_copy_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Missile drawing
|
||||
|
||||
void TIA::draw_missile(Missile &missile, Player &player, const uint8_t collision_identity, int start, int end) {
|
||||
if(!missile.locked_to_player || player.latched_pixel4_time < 0) {
|
||||
draw_object<Missile>(missile, collision_identity, start, end);
|
||||
} else {
|
||||
draw_object<Missile>(missile, collision_identity, start, player.latched_pixel4_time);
|
||||
missile.position = 0;
|
||||
draw_object<Missile>(missile, collision_identity, player.latched_pixel4_time, end);
|
||||
player.latched_pixel4_time = -1;
|
||||
}
|
||||
}
|
||||
333
Machines/Atari2600/TIA.hpp
Normal file
@@ -0,0 +1,333 @@
|
||||
//
|
||||
// TIA.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 28/01/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef TIA_hpp
|
||||
#define TIA_hpp
|
||||
|
||||
#include <cstdint>
|
||||
#include "../CRTMachine.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
class TIA {
|
||||
public:
|
||||
TIA();
|
||||
// The supplied hook is for unit testing only; if instantiated with a line_end_function then it will
|
||||
// be called with the latest collision buffer upon the conclusion of each line. What's a collision
|
||||
// buffer? It's an implementation detail. If you're not writing a unit test, leave it alone.
|
||||
TIA(std::function<void(uint8_t *output_buffer)> line_end_function);
|
||||
|
||||
enum class OutputMode {
|
||||
NTSC, PAL
|
||||
};
|
||||
|
||||
/*!
|
||||
Advances the TIA by @c number_of_cycles cycles. Any queued setters take effect in the
|
||||
first cycle performed.
|
||||
*/
|
||||
void run_for_cycles(int number_of_cycles);
|
||||
void set_output_mode(OutputMode output_mode);
|
||||
|
||||
void set_sync(bool sync);
|
||||
void set_blank(bool blank);
|
||||
void reset_horizontal_counter(); // Reset is delayed by four cycles.
|
||||
|
||||
/*!
|
||||
@returns the number of cycles between (current TIA time) + from_offset to the current or
|
||||
next horizontal blanking period. Returns numbers in the range [0, 227].
|
||||
*/
|
||||
int get_cycles_until_horizontal_blank(unsigned int from_offset);
|
||||
|
||||
void set_background_colour(uint8_t colour);
|
||||
|
||||
void set_playfield(uint16_t offset, uint8_t value);
|
||||
void set_playfield_control_and_ball_size(uint8_t value);
|
||||
void set_playfield_ball_colour(uint8_t colour);
|
||||
|
||||
void set_player_number_and_size(int player, uint8_t value);
|
||||
void set_player_graphic(int player, uint8_t value);
|
||||
void set_player_reflected(int player, bool reflected);
|
||||
void set_player_delay(int player, bool delay);
|
||||
void set_player_position(int player);
|
||||
void set_player_motion(int player, uint8_t motion);
|
||||
void set_player_missile_colour(int player, uint8_t colour);
|
||||
|
||||
void set_missile_enable(int missile, bool enabled);
|
||||
void set_missile_position(int missile);
|
||||
void set_missile_position_to_player(int missile, bool lock);
|
||||
void set_missile_motion(int missile, uint8_t motion);
|
||||
|
||||
void set_ball_enable(bool enabled);
|
||||
void set_ball_delay(bool delay);
|
||||
void set_ball_position();
|
||||
void set_ball_motion(uint8_t motion);
|
||||
|
||||
void move();
|
||||
void clear_motion();
|
||||
|
||||
uint8_t get_collision_flags(int offset);
|
||||
void clear_collision_flags();
|
||||
|
||||
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; }
|
||||
|
||||
private:
|
||||
TIA(bool create_crt);
|
||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
||||
std::function<void(uint8_t *output_buffer)> line_end_function_;
|
||||
|
||||
// the master counter; counts from 0 to 228 with all visible pixels being in the final 160
|
||||
int horizontal_counter_;
|
||||
|
||||
// contains flags to indicate whether sync or blank are currently active
|
||||
int output_mode_;
|
||||
|
||||
// keeps track of the target pixel buffer for this line and when it was acquired, and a corresponding collision buffer
|
||||
alignas(alignof(uint32_t)) uint8_t collision_buffer_[160];
|
||||
enum class CollisionType : uint8_t {
|
||||
Playfield = (1 << 0),
|
||||
Ball = (1 << 1),
|
||||
Player0 = (1 << 2),
|
||||
Player1 = (1 << 3),
|
||||
Missile0 = (1 << 4),
|
||||
Missile1 = (1 << 5)
|
||||
};
|
||||
|
||||
int collision_flags_;
|
||||
int collision_flags_by_buffer_vaules_[64];
|
||||
|
||||
// colour mapping tables
|
||||
enum class ColourMode {
|
||||
Standard = 0,
|
||||
ScoreLeft,
|
||||
ScoreRight,
|
||||
OnTop
|
||||
};
|
||||
uint8_t colour_mask_by_mode_collision_flags_[4][64]; // maps from [ColourMode][CollisionMark] to colour_pallete_ entry
|
||||
|
||||
enum class ColourIndex {
|
||||
Background = 0,
|
||||
PlayfieldBall,
|
||||
PlayerMissile0,
|
||||
PlayerMissile1
|
||||
};
|
||||
uint8_t colour_palette_[4];
|
||||
|
||||
// playfield state
|
||||
int background_half_mask_;
|
||||
enum class PlayfieldPriority {
|
||||
Standard,
|
||||
Score,
|
||||
OnTop
|
||||
} playfield_priority_;
|
||||
uint32_t background_[2]; // contains two 20-bit bitfields representing the background state;
|
||||
// at index 0 is the left-hand side of the playfield with bit 0 being
|
||||
// the first bit to display, bit 1 the second, etc. Index 1 contains
|
||||
// a mirror image of index 0. If the playfield is being displayed in
|
||||
// mirroring mode, background_[0] will be output on the left and
|
||||
// background_[1] on the right; otherwise background_[0] will be
|
||||
// output twice.
|
||||
|
||||
// objects
|
||||
template<class T> struct Object {
|
||||
// the two programmer-set values
|
||||
int position;
|
||||
int motion;
|
||||
|
||||
// motion_step_ is the current motion counter value; motion_time_ is the next time it will fire
|
||||
int motion_step;
|
||||
int motion_time;
|
||||
|
||||
// indicates whether this object is currently undergoing motion
|
||||
bool is_moving;
|
||||
|
||||
Object() : position(0), motion(0), motion_step(0), motion_time(0), is_moving(false) {};
|
||||
};
|
||||
|
||||
// player state
|
||||
struct Player: public Object<Player> {
|
||||
Player() :
|
||||
adder(4),
|
||||
copy_flags(0),
|
||||
graphic{0, 0},
|
||||
reverse_mask(false),
|
||||
graphic_index(0),
|
||||
pixel_position(32),
|
||||
pixel_counter(0),
|
||||
latched_pixel4_time(-1),
|
||||
copy_index_(0),
|
||||
queue_read_pointer_(0),
|
||||
queue_write_pointer_(0) {}
|
||||
|
||||
int adder;
|
||||
int copy_flags; // a bit field, corresponding to the first few values of NUSIZ
|
||||
uint8_t graphic[2]; // the player graphic; 1 = new, 0 = current
|
||||
int reverse_mask; // 7 for a reflected player, 0 for normal
|
||||
int graphic_index;
|
||||
|
||||
int pixel_position, pixel_counter;
|
||||
int latched_pixel4_time;
|
||||
const bool enqueues = true;
|
||||
|
||||
inline void skip_pixels(const int count, int from_horizontal_counter) {
|
||||
int old_pixel_counter = pixel_counter;
|
||||
pixel_position = std::min(32, pixel_position + count * adder);
|
||||
pixel_counter += count;
|
||||
if(!copy_index_ && old_pixel_counter < 4 && pixel_counter >= 4) {
|
||||
latched_pixel4_time = from_horizontal_counter + 4 - old_pixel_counter;
|
||||
}
|
||||
}
|
||||
|
||||
inline void reset_pixels(int copy) {
|
||||
pixel_position = pixel_counter = 0;
|
||||
copy_index_ = copy;
|
||||
}
|
||||
|
||||
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
|
||||
output_pixels(target, count, collision_identity, pixel_position, adder, reverse_mask);
|
||||
skip_pixels(count, from_horizontal_counter);
|
||||
}
|
||||
|
||||
void dequeue_pixels(uint8_t *const target, const uint8_t collision_identity, const int time_now) {
|
||||
while(queue_read_pointer_ != queue_write_pointer_) {
|
||||
uint8_t *const start_ptr = &target[queue_[queue_read_pointer_].start];
|
||||
if(queue_[queue_read_pointer_].end > time_now) {
|
||||
const int length = time_now - queue_[queue_read_pointer_].start;
|
||||
output_pixels(start_ptr, length, collision_identity, queue_[queue_read_pointer_].pixel_position, queue_[queue_read_pointer_].adder, queue_[queue_read_pointer_].reverse_mask);
|
||||
queue_[queue_read_pointer_].pixel_position += length * queue_[queue_read_pointer_].adder;
|
||||
queue_[queue_read_pointer_].start = time_now;
|
||||
return;
|
||||
} else {
|
||||
output_pixels(start_ptr, queue_[queue_read_pointer_].end - queue_[queue_read_pointer_].start, collision_identity, queue_[queue_read_pointer_].pixel_position, queue_[queue_read_pointer_].adder, queue_[queue_read_pointer_].reverse_mask);
|
||||
}
|
||||
queue_read_pointer_ = (queue_read_pointer_ + 1)&3;
|
||||
}
|
||||
}
|
||||
|
||||
void enqueue_pixels(const int start, const int end, int from_horizontal_counter) {
|
||||
queue_[queue_write_pointer_].start = start;
|
||||
queue_[queue_write_pointer_].end = end;
|
||||
queue_[queue_write_pointer_].pixel_position = pixel_position;
|
||||
queue_[queue_write_pointer_].adder = adder;
|
||||
queue_[queue_write_pointer_].reverse_mask = reverse_mask;
|
||||
queue_write_pointer_ = (queue_write_pointer_ + 1)&3;
|
||||
skip_pixels(end - start, from_horizontal_counter);
|
||||
}
|
||||
|
||||
private:
|
||||
int copy_index_;
|
||||
struct QueuedPixels {
|
||||
int start, end;
|
||||
int pixel_position;
|
||||
int adder;
|
||||
int reverse_mask;
|
||||
QueuedPixels() : start(0), end(0), pixel_position(0), adder(0), reverse_mask(false) {}
|
||||
} queue_[4];
|
||||
int queue_read_pointer_, queue_write_pointer_;
|
||||
|
||||
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int pixel_position, int adder, int reverse_mask) {
|
||||
if(pixel_position == 32 || !graphic[graphic_index]) return;
|
||||
int output_cursor = 0;
|
||||
while(pixel_position < 32 && output_cursor < count) {
|
||||
int shift = (pixel_position >> 2) ^ reverse_mask;
|
||||
target[output_cursor] |= ((graphic[graphic_index] >> shift)&1) * collision_identity;
|
||||
output_cursor++;
|
||||
pixel_position += adder;
|
||||
}
|
||||
}
|
||||
|
||||
} player_[2];
|
||||
|
||||
// common actor for things that appear as a horizontal run of pixels
|
||||
struct HorizontalRun: public Object<HorizontalRun> {
|
||||
int pixel_position;
|
||||
int size;
|
||||
const bool enqueues = false;
|
||||
|
||||
inline void skip_pixels(const int count, int from_horizontal_counter) {
|
||||
pixel_position = std::max(0, pixel_position - count);
|
||||
}
|
||||
|
||||
inline void reset_pixels(int copy) {
|
||||
pixel_position = size;
|
||||
}
|
||||
|
||||
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
|
||||
int output_cursor = 0;
|
||||
while(pixel_position && output_cursor < count)
|
||||
{
|
||||
target[output_cursor] |= collision_identity;
|
||||
output_cursor++;
|
||||
pixel_position--;
|
||||
}
|
||||
}
|
||||
|
||||
void dequeue_pixels(uint8_t *const target, const uint8_t collision_identity, const int time_now) {}
|
||||
void enqueue_pixels(const int start, const int end, int from_horizontal_counter) {}
|
||||
|
||||
HorizontalRun() : pixel_position(0), size(1) {}
|
||||
};
|
||||
|
||||
|
||||
// missile state
|
||||
struct Missile: public HorizontalRun {
|
||||
bool enabled;
|
||||
bool locked_to_player;
|
||||
int copy_flags;
|
||||
|
||||
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
|
||||
if(!pixel_position) return;
|
||||
if(enabled && !locked_to_player) {
|
||||
HorizontalRun::output_pixels(target, count, collision_identity, from_horizontal_counter);
|
||||
} else {
|
||||
skip_pixels(count, from_horizontal_counter);
|
||||
}
|
||||
}
|
||||
|
||||
Missile() : enabled(false), copy_flags(0) {}
|
||||
} missile_[2];
|
||||
|
||||
// ball state
|
||||
struct Ball: public HorizontalRun {
|
||||
bool enabled[2];
|
||||
int enabled_index;
|
||||
const int copy_flags = 0;
|
||||
|
||||
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
|
||||
if(!pixel_position) return;
|
||||
if(enabled[enabled_index]) {
|
||||
HorizontalRun::output_pixels(target, count, collision_identity, from_horizontal_counter);
|
||||
} else {
|
||||
skip_pixels(count, from_horizontal_counter);
|
||||
}
|
||||
}
|
||||
|
||||
Ball() : enabled{false, false}, enabled_index(0) {}
|
||||
} ball_;
|
||||
|
||||
// motion
|
||||
bool horizontal_blank_extend_;
|
||||
template<class T> void perform_border_motion(T &object, int start, int end);
|
||||
template<class T> void perform_motion_step(T &object);
|
||||
|
||||
// drawing methods and state
|
||||
void draw_missile(Missile &, Player &, const uint8_t collision_identity, int start, int end);
|
||||
template<class T> void draw_object(T &, const uint8_t collision_identity, int start, int end);
|
||||
template<class T> void draw_object_visible(T &, const uint8_t collision_identity, int start, int end, int time_now);
|
||||
inline void draw_playfield(int start, int end);
|
||||
|
||||
inline void output_for_cycles(int number_of_cycles);
|
||||
inline void output_line();
|
||||
|
||||
int pixels_start_location_;
|
||||
uint8_t *pixel_target_;
|
||||
inline void output_pixels(int start, int end);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* TIA_hpp */
|
||||
@@ -13,11 +13,10 @@
|
||||
using namespace Commodore::C1540;
|
||||
|
||||
Machine::Machine() :
|
||||
shift_register_(0),
|
||||
Storage::Disk::Controller(1000000, 4, 300),
|
||||
serial_port_(new SerialPort),
|
||||
serial_port_VIA_(new SerialPortVIA)
|
||||
{
|
||||
shift_register_(0),
|
||||
Storage::Disk::Controller(1000000, 4, 300),
|
||||
serial_port_(new SerialPort),
|
||||
serial_port_VIA_(new SerialPortVIA) {
|
||||
// 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_);
|
||||
@@ -31,13 +30,11 @@ Machine::Machine() :
|
||||
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(3));
|
||||
}
|
||||
|
||||
void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus)
|
||||
{
|
||||
void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) {
|
||||
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus);
|
||||
}
|
||||
|
||||
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value)
|
||||
{
|
||||
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
/*
|
||||
Memory map (given that I'm unsure yet on any potential mirroring):
|
||||
|
||||
@@ -46,27 +43,20 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
0x1c00–0x1c0f the drive VIA
|
||||
0xc000–0xffff ROM
|
||||
*/
|
||||
if(address < 0x800)
|
||||
{
|
||||
if(address < 0x800) {
|
||||
if(isReadOperation(operation))
|
||||
*value = ram_[address];
|
||||
else
|
||||
ram_[address] = *value;
|
||||
}
|
||||
else if(address >= 0xc000)
|
||||
{
|
||||
} else if(address >= 0xc000) {
|
||||
if(isReadOperation(operation))
|
||||
*value = rom_[address & 0x3fff];
|
||||
}
|
||||
else if(address >= 0x1800 && address <= 0x180f)
|
||||
{
|
||||
} else if(address >= 0x1800 && address <= 0x180f) {
|
||||
if(isReadOperation(operation))
|
||||
*value = serial_port_VIA_->get_register(address);
|
||||
else
|
||||
serial_port_VIA_->set_register(address, *value);
|
||||
}
|
||||
else if(address >= 0x1c00 && address <= 0x1c0f)
|
||||
{
|
||||
} else if(address >= 0x1c00 && address <= 0x1c0f) {
|
||||
if(isReadOperation(operation))
|
||||
*value = drive_VIA_.get_register(address);
|
||||
else
|
||||
@@ -79,20 +69,17 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
return 1;
|
||||
}
|
||||
|
||||
void Machine::set_rom(const uint8_t *rom)
|
||||
{
|
||||
void Machine::set_rom(const uint8_t *rom) {
|
||||
memcpy(rom_, rom, sizeof(rom_));
|
||||
}
|
||||
|
||||
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
void Machine::run_for_cycles(int number_of_cycles)
|
||||
{
|
||||
void Machine::run_for_cycles(int number_of_cycles) {
|
||||
CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles);
|
||||
set_motor_on(drive_VIA_.get_motor_enabled());
|
||||
if(drive_VIA_.get_motor_enabled()) // TODO: motor speed up/down
|
||||
@@ -101,38 +88,30 @@ void Machine::run_for_cycles(int number_of_cycles)
|
||||
|
||||
#pragma mark - 6522 delegate
|
||||
|
||||
void Machine::mos6522_did_change_interrupt_status(void *mos6522)
|
||||
{
|
||||
void Machine::mos6522_did_change_interrupt_status(void *mos6522) {
|
||||
// both VIAs are connected to the IRQ line
|
||||
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 Machine::process_input_bit(int value, unsigned int cycles_since_index_hole) {
|
||||
shift_register_ = (shift_register_ << 1) | value;
|
||||
if((shift_register_ & 0x3ff) == 0x3ff)
|
||||
{
|
||||
if((shift_register_ & 0x3ff) == 0x3ff) {
|
||||
drive_VIA_.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
|
||||
{
|
||||
} else {
|
||||
drive_VIA_.set_sync_detected(false);
|
||||
}
|
||||
bit_window_offset_++;
|
||||
if(bit_window_offset_ == 8)
|
||||
{
|
||||
if(bit_window_offset_ == 8) {
|
||||
drive_VIA_.set_data_input((uint8_t)shift_register_);
|
||||
bit_window_offset_ = 0;
|
||||
if(drive_VIA_.get_should_set_overflow())
|
||||
{
|
||||
if(drive_VIA_.get_should_set_overflow()) {
|
||||
set_overflow_line(true);
|
||||
}
|
||||
}
|
||||
else
|
||||
set_overflow_line(false);
|
||||
else set_overflow_line(false);
|
||||
}
|
||||
|
||||
// the 1540 does not recognise index holes
|
||||
@@ -140,32 +119,26 @@ void Machine::process_index_hole() {}
|
||||
|
||||
#pragma mak - Drive VIA delegate
|
||||
|
||||
void Machine::drive_via_did_step_head(void *driveVIA, int direction)
|
||||
{
|
||||
void Machine::drive_via_did_step_head(void *driveVIA, int direction) {
|
||||
step(direction);
|
||||
}
|
||||
|
||||
void Machine::drive_via_did_set_data_density(void *driveVIA, int density)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
#pragma mark - SerialPortVIA
|
||||
|
||||
SerialPortVIA::SerialPortVIA() :
|
||||
port_b_(0x00), attention_acknowledge_level_(false), attention_level_input_(true), data_level_output_(false)
|
||||
{}
|
||||
port_b_(0x00), attention_acknowledge_level_(false), attention_level_input_(true), data_level_output_(false) {}
|
||||
|
||||
uint8_t SerialPortVIA::get_port_input(Port port)
|
||||
{
|
||||
uint8_t SerialPortVIA::get_port_input(Port port) {
|
||||
if(port) return port_b_;
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask)
|
||||
{
|
||||
if(port)
|
||||
{
|
||||
void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) {
|
||||
if(port) {
|
||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
||||
if(serialPort) {
|
||||
attention_acknowledge_level_ = !(value&0x10);
|
||||
@@ -177,10 +150,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)
|
||||
{
|
||||
switch(line)
|
||||
{
|
||||
void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) {
|
||||
switch(line) {
|
||||
default: break;
|
||||
case ::Commodore::Serial::Line::Data: port_b_ = (port_b_ & ~0x01) | (value ? 0x00 : 0x01); break;
|
||||
case ::Commodore::Serial::Line::Clock: port_b_ = (port_b_ & ~0x04) | (value ? 0x00 : 0x04); break;
|
||||
@@ -193,16 +164,13 @@ void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool v
|
||||
}
|
||||
}
|
||||
|
||||
void SerialPortVIA::set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &serialPort)
|
||||
{
|
||||
void SerialPortVIA::set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &serialPort) {
|
||||
serial_port_ = serialPort;
|
||||
}
|
||||
|
||||
void SerialPortVIA::update_data_line()
|
||||
{
|
||||
void SerialPortVIA::update_data_line() {
|
||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
||||
if(serialPort)
|
||||
{
|
||||
if(serialPort) {
|
||||
// "ATN (Attention) is an input on pin 3 of P2 and P3 that is sensed at PB7 and CA1 of UC3 after being inverted by UA1"
|
||||
serialPort->set_output(::Commodore::Serial::Line::Data,
|
||||
(::Commodore::Serial::LineLevel)(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_)));
|
||||
@@ -211,8 +179,7 @@ void SerialPortVIA::update_data_line()
|
||||
|
||||
#pragma mark - DriveVIA
|
||||
|
||||
void DriveVIA::set_delegate(Delegate *delegate)
|
||||
{
|
||||
void DriveVIA::set_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
@@ -246,22 +213,19 @@ void DriveVIA::set_control_line_output(Port port, Line line, bool value) {
|
||||
}
|
||||
|
||||
void DriveVIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask) {
|
||||
if(port)
|
||||
{
|
||||
if(port) {
|
||||
// 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(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_)
|
||||
{
|
||||
if(density_difference && delegate_) {
|
||||
delegate_->drive_via_did_set_data_density(this, (value >> 5)&3);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,10 +10,8 @@
|
||||
|
||||
using namespace Commodore::Serial;
|
||||
|
||||
const char *::Commodore::Serial::StringForLine(Line line)
|
||||
{
|
||||
switch(line)
|
||||
{
|
||||
const char *::Commodore::Serial::StringForLine(Line line) {
|
||||
switch(line) {
|
||||
case ServiceRequest: return "Service request";
|
||||
case Attention: return "Attention";
|
||||
case Clock: return "Clock";
|
||||
@@ -22,17 +20,14 @@ const char *::Commodore::Serial::StringForLine(Line line)
|
||||
}
|
||||
}
|
||||
|
||||
void ::Commodore::Serial::AttachPortAndBus(std::shared_ptr<Port> port, std::shared_ptr<Bus> bus)
|
||||
{
|
||||
void ::Commodore::Serial::AttachPortAndBus(std::shared_ptr<Port> port, std::shared_ptr<Bus> bus) {
|
||||
port->set_serial_bus(bus);
|
||||
bus->add_port(port);
|
||||
}
|
||||
|
||||
void Bus::add_port(std::shared_ptr<Port> port)
|
||||
{
|
||||
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 = (int)ServiceRequest; line <= (int)Reset; line++) {
|
||||
// the addition of a new device may change the line output...
|
||||
set_line_output_did_change((Line)line);
|
||||
|
||||
@@ -41,29 +36,23 @@ void Bus::add_port(std::shared_ptr<Port> port)
|
||||
}
|
||||
}
|
||||
|
||||
void Bus::set_line_output_did_change(Line line)
|
||||
{
|
||||
void Bus::set_line_output_did_change(Line line) {
|
||||
// i.e. I believe these lines to be open collector
|
||||
LineLevel new_line_level = High;
|
||||
for(std::weak_ptr<Port> port : ports_)
|
||||
{
|
||||
for(std::weak_ptr<Port> port : ports_) {
|
||||
std::shared_ptr<Port> locked_port = port.lock();
|
||||
if(locked_port)
|
||||
{
|
||||
if(locked_port) {
|
||||
new_line_level = (LineLevel)((bool)new_line_level & (bool)locked_port->get_output(line));
|
||||
}
|
||||
}
|
||||
|
||||
// post an update only if one occurred
|
||||
if(new_line_level != line_levels_[line])
|
||||
{
|
||||
if(new_line_level != line_levels_[line]) {
|
||||
line_levels_[line] = new_line_level;
|
||||
|
||||
for(std::weak_ptr<Port> port : ports_)
|
||||
{
|
||||
for(std::weak_ptr<Port> port : ports_) {
|
||||
std::shared_ptr<Port> locked_port = port.lock();
|
||||
if(locked_port)
|
||||
{
|
||||
if(locked_port) {
|
||||
locked_port->set_input(line, new_line_level);
|
||||
}
|
||||
}
|
||||
@@ -72,19 +61,14 @@ void Bus::set_line_output_did_change(Line line)
|
||||
|
||||
#pragma mark - The debug port
|
||||
|
||||
void DebugPort::set_input(Line line, LineLevel value)
|
||||
{
|
||||
void DebugPort::set_input(Line line, LineLevel value) {
|
||||
input_levels_[line] = value;
|
||||
|
||||
printf("[Bus] %s is %s\n", StringForLine(line), value ? "high" : "low");
|
||||
if(!incoming_count_)
|
||||
{
|
||||
if(!incoming_count_) {
|
||||
incoming_count_ = (!input_levels_[Line::Clock] && !input_levels_[Line::Data]) ? 8 : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(line == Line::Clock && value)
|
||||
{
|
||||
} else {
|
||||
if(line == Line::Clock && value) {
|
||||
incoming_byte_ = (incoming_byte_ >> 1) | (input_levels_[Line::Data] ? 0x80 : 0x00);
|
||||
}
|
||||
incoming_count_--;
|
||||
|
||||
@@ -78,8 +78,7 @@ namespace Serial {
|
||||
Sets the current level of an output line on this serial port.
|
||||
*/
|
||||
void set_output(Line line, LineLevel level) {
|
||||
if(line_levels_[line] != level)
|
||||
{
|
||||
if(line_levels_[line] != level) {
|
||||
line_levels_[line] = level;
|
||||
std::shared_ptr<Bus> bus = serial_bus_.lock();
|
||||
if(bus) bus->set_line_output_did_change(line);
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
|
||||
#include "Vic20.hpp"
|
||||
|
||||
uint16_t *Commodore::Vic20::Machine::sequence_for_character(Utility::Typer *typer, char character)
|
||||
{
|
||||
uint16_t *Commodore::Vic20::Machine::sequence_for_character(Utility::Typer *typer, char character) {
|
||||
#define KEYS(...) {__VA_ARGS__, TerminateSequence}
|
||||
#define SHIFT(...) {KeyLShift, __VA_ARGS__, TerminateSequence}
|
||||
#define X {NotMapped}
|
||||
|
||||
@@ -15,10 +15,9 @@
|
||||
using namespace Commodore::Vic20;
|
||||
|
||||
Machine::Machine() :
|
||||
rom_(nullptr),
|
||||
is_running_at_zero_cost_(false),
|
||||
tape_(1022727)
|
||||
{
|
||||
rom_(nullptr),
|
||||
is_running_at_zero_cost_(false),
|
||||
tape_(1022727) {
|
||||
// create 6522s, serial port and bus
|
||||
user_port_via_.reset(new UserPortVIA);
|
||||
keyboard_via_.reset(new KeyboardVIA);
|
||||
@@ -48,13 +47,11 @@ Machine::Machine() :
|
||||
// serial_bus_->add_port(_debugPort);
|
||||
}
|
||||
|
||||
void Machine::set_memory_size(MemorySize size)
|
||||
{
|
||||
void Machine::set_memory_size(MemorySize size) {
|
||||
memset(processor_read_memory_map_, 0, sizeof(processor_read_memory_map_));
|
||||
memset(processor_write_memory_map_, 0, sizeof(processor_write_memory_map_));
|
||||
|
||||
switch(size)
|
||||
{
|
||||
switch(size) {
|
||||
default: break;
|
||||
case ThreeKB:
|
||||
write_to_map(processor_read_memory_map_, expansion_ram_, 0x0000, 0x1000);
|
||||
@@ -79,31 +76,26 @@ void Machine::set_memory_size(MemorySize size)
|
||||
write_to_map(processor_write_memory_map_, colour_memory_, 0x9400, sizeof(colour_memory_));
|
||||
|
||||
// install the inserted ROM if there is one
|
||||
if(rom_)
|
||||
{
|
||||
if(rom_) {
|
||||
write_to_map(processor_read_memory_map_, rom_, rom_address_, rom_length_);
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length)
|
||||
{
|
||||
void Machine::write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length) {
|
||||
address >>= 10;
|
||||
length >>= 10;
|
||||
while(length--)
|
||||
{
|
||||
while(length--) {
|
||||
map[address] = area;
|
||||
area += 0x400;
|
||||
address++;
|
||||
}
|
||||
}
|
||||
|
||||
Machine::~Machine()
|
||||
{
|
||||
Machine::~Machine() {
|
||||
delete[] rom_;
|
||||
}
|
||||
|
||||
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value)
|
||||
{
|
||||
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
// static int logCount = 0;
|
||||
// if(operation == CPU6502::BusOperation::ReadOpcode && address == 0xf957) logCount = 500;
|
||||
// if(operation == CPU6502::BusOperation::ReadOpcode && logCount) {
|
||||
@@ -111,8 +103,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
// printf("%04x\n", address);
|
||||
// }
|
||||
|
||||
// if(operation == CPU6502::BusOperation::Write && (address >= 0x033C && address < 0x033C + 192))
|
||||
// {
|
||||
// if(operation == CPU6502::BusOperation::Write && (address >= 0x033C && address < 0x033C + 192)) {
|
||||
// printf("\n[%04x] <- %02x\n", address, *value);
|
||||
// }
|
||||
|
||||
@@ -120,11 +111,9 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
if(!is_running_at_zero_cost_) mos6560_->run_for_cycles(1);
|
||||
|
||||
// run the phase-2 part of the cycle, which is whatever the 6502 said it should be
|
||||
if(isReadOperation(operation))
|
||||
{
|
||||
if(isReadOperation(operation)) {
|
||||
uint8_t result = processor_read_memory_map_[address >> 10] ? processor_read_memory_map_[address >> 10][address & 0x3ff] : 0xff;
|
||||
if((address&0xfc00) == 0x9000)
|
||||
{
|
||||
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);
|
||||
@@ -135,22 +124,17 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
// PC hits the start of the loop that just waits for an interesting tape interrupt to have
|
||||
// occurred then skip both 6522s and the tape ahead to the next interrupt without any further
|
||||
// CPU or 6560 costs.
|
||||
if(use_fast_tape_hack_ && tape_.has_tape() && address == 0xf92f && operation == CPU6502::BusOperation::ReadOpcode)
|
||||
{
|
||||
while(!user_port_via_->get_interrupt_line() && !keyboard_via_->get_interrupt_line() && !tape_.get_tape()->is_at_end())
|
||||
{
|
||||
if(use_fast_tape_hack_ && tape_.has_tape() && address == 0xf92f && operation == CPU6502::BusOperation::ReadOpcode) {
|
||||
while(!user_port_via_->get_interrupt_line() && !keyboard_via_->get_interrupt_line() && !tape_.get_tape()->is_at_end()) {
|
||||
user_port_via_->run_for_cycles(1);
|
||||
keyboard_via_->run_for_cycles(1);
|
||||
tape_.run_for_cycles(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
uint8_t *ram = processor_write_memory_map_[address >> 10];
|
||||
if(ram) ram[address & 0x3ff] = *value;
|
||||
if((address&0xfc00) == 0x9000)
|
||||
{
|
||||
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);
|
||||
@@ -159,10 +143,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
|
||||
user_port_via_->run_for_cycles(1);
|
||||
keyboard_via_->run_for_cycles(1);
|
||||
if(typer_ && operation == CPU6502::BusOperation::ReadOpcode && address == 0xEB1E)
|
||||
{
|
||||
if(!typer_->type_next_character())
|
||||
{
|
||||
if(typer_ && operation == CPU6502::BusOperation::ReadOpcode && address == 0xEB1E) {
|
||||
if(!typer_->type_next_character()) {
|
||||
clear_all_keys();
|
||||
typer_.reset();
|
||||
}
|
||||
@@ -181,18 +163,15 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
// Note the additional test above for PC hitting 0xf92f, which is a loop in the ROM that waits
|
||||
// for an interesting interrupt. Up there the fast tape hack goes even further in also cutting
|
||||
// the CPU out of the action.
|
||||
if(use_fast_tape_hack_ && tape_.has_tape())
|
||||
{
|
||||
if(address == 0xf98e && operation == CPU6502::BusOperation::ReadOpcode)
|
||||
{
|
||||
if(use_fast_tape_hack_ && tape_.has_tape()) {
|
||||
if(address == 0xf98e && operation == CPU6502::BusOperation::ReadOpcode) {
|
||||
is_running_at_zero_cost_ = true;
|
||||
set_clock_is_unlimited(true);
|
||||
}
|
||||
if(
|
||||
(address < 0xe000 && operation == CPU6502::BusOperation::ReadOpcode) ||
|
||||
tape_.get_tape()->is_at_end()
|
||||
)
|
||||
{
|
||||
) {
|
||||
is_running_at_zero_cost_ = false;
|
||||
set_clock_is_unlimited(false);
|
||||
}
|
||||
@@ -203,31 +182,26 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
|
||||
#pragma mark - 6522 delegate
|
||||
|
||||
void Machine::mos6522_did_change_interrupt_status(void *mos6522)
|
||||
{
|
||||
void Machine::mos6522_did_change_interrupt_status(void *mos6522) {
|
||||
set_nmi_line(user_port_via_->get_interrupt_line());
|
||||
set_irq_line(keyboard_via_->get_interrupt_line());
|
||||
}
|
||||
|
||||
#pragma mark - Setup
|
||||
|
||||
void Machine::set_region(Commodore::Vic20::Region region)
|
||||
{
|
||||
void Machine::set_region(Commodore::Vic20::Region region) {
|
||||
region_ = region;
|
||||
switch(region)
|
||||
{
|
||||
switch(region) {
|
||||
case PAL:
|
||||
set_clock_rate(1108404);
|
||||
if(mos6560_)
|
||||
{
|
||||
if(mos6560_) {
|
||||
mos6560_->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::PAL);
|
||||
mos6560_->set_clock_rate(1108404);
|
||||
}
|
||||
break;
|
||||
case NTSC:
|
||||
set_clock_rate(1022727);
|
||||
if(mos6560_)
|
||||
{
|
||||
if(mos6560_) {
|
||||
mos6560_->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::NTSC);
|
||||
mos6560_->set_clock_rate(1022727);
|
||||
}
|
||||
@@ -235,8 +209,7 @@ void Machine::set_region(Commodore::Vic20::Region region)
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::setup_output(float aspect_ratio)
|
||||
{
|
||||
void Machine::setup_output(float aspect_ratio) {
|
||||
mos6560_.reset(new Vic6560());
|
||||
mos6560_->get_speaker()->set_high_frequency_cut_off(1600); // There is a 1.6Khz low-pass filter in the Vic-20.
|
||||
set_region(region_);
|
||||
@@ -248,17 +221,14 @@ void Machine::setup_output(float aspect_ratio)
|
||||
mos6560_->colour_memory = colour_memory_;
|
||||
}
|
||||
|
||||
void Machine::close_output()
|
||||
{
|
||||
void Machine::close_output() {
|
||||
mos6560_ = nullptr;
|
||||
}
|
||||
|
||||
void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data)
|
||||
{
|
||||
void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) {
|
||||
uint8_t *target = nullptr;
|
||||
size_t max_length = 0x2000;
|
||||
switch(slot)
|
||||
{
|
||||
switch(slot) {
|
||||
case Kernel: target = kernel_rom_; break;
|
||||
case Characters: target = character_rom_; max_length = 0x1000; break;
|
||||
case BASIC: target = basic_rom_; break;
|
||||
@@ -269,29 +239,23 @@ void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data)
|
||||
return;
|
||||
}
|
||||
|
||||
if(target)
|
||||
{
|
||||
if(target) {
|
||||
size_t length_to_copy = std::min(max_length, length);
|
||||
memcpy(target, data, length_to_copy);
|
||||
}
|
||||
}
|
||||
|
||||
//void Machine::set_prg(const char *file_name, size_t length, const uint8_t *data)
|
||||
//{
|
||||
// if(length > 2)
|
||||
// {
|
||||
//void Machine::set_prg(const char *file_name, size_t length, const uint8_t *data) {
|
||||
// if(length > 2) {
|
||||
// _rom_address = (uint16_t)(data[0] | (data[1] << 8));
|
||||
// _rom_length = (uint16_t)(length - 2);
|
||||
//
|
||||
// // install in the ROM area if this looks like a ROM; otherwise put on tape and throw into that mechanism
|
||||
// if(_rom_address == 0xa000)
|
||||
// {
|
||||
// if(_rom_address == 0xa000) {
|
||||
// _rom = new uint8_t[0x2000];
|
||||
// memcpy(_rom, &data[2], length - 2);
|
||||
// write_to_map(processor_read_memory_map_, _rom, _rom_address, 0x2000);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// } else {
|
||||
// set_tape(std::shared_ptr<Storage::Tape::Tape>(new Storage::Tape::PRG(file_name)));
|
||||
// }
|
||||
// }
|
||||
@@ -299,15 +263,12 @@ void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data)
|
||||
|
||||
#pragma mar - Tape
|
||||
|
||||
void Machine::configure_as_target(const StaticAnalyser::Target &target)
|
||||
{
|
||||
if(target.tapes.size())
|
||||
{
|
||||
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
|
||||
if(target.tapes.size()) {
|
||||
tape_.set_tape(target.tapes.front());
|
||||
}
|
||||
|
||||
if(target.disks.size())
|
||||
{
|
||||
if(target.disks.size()) {
|
||||
// construct the 1540
|
||||
c1540_.reset(new ::Commodore::C1540::Machine);
|
||||
|
||||
@@ -321,8 +282,7 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target)
|
||||
install_disk_rom();
|
||||
}
|
||||
|
||||
if(target.cartridges.size())
|
||||
{
|
||||
if(target.cartridges.size()) {
|
||||
rom_address_ = 0xa000;
|
||||
std::vector<uint8_t> rom_image = target.cartridges.front()->get_segments().front().data;
|
||||
rom_length_ = (uint16_t)(rom_image.size());
|
||||
@@ -332,15 +292,12 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target)
|
||||
write_to_map(processor_read_memory_map_, rom_, rom_address_, 0x2000);
|
||||
}
|
||||
|
||||
if(should_automatically_load_media_)
|
||||
{
|
||||
if(target.loadingCommand.length()) // TODO: and automatic loading option enabled
|
||||
{
|
||||
if(should_automatically_load_media_) {
|
||||
if(target.loadingCommand.length()) { // TODO: and automatic loading option enabled
|
||||
set_typer_for_string(target.loadingCommand.c_str());
|
||||
}
|
||||
|
||||
switch(target.vic20.memory_model)
|
||||
{
|
||||
switch(target.vic20.memory_model) {
|
||||
case StaticAnalyser::Vic20MemoryModel::Unexpanded:
|
||||
set_memory_size(Default);
|
||||
break;
|
||||
@@ -354,17 +311,14 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target)
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape)
|
||||
{
|
||||
void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) {
|
||||
keyboard_via_->set_control_line_input(KeyboardVIA::Port::A, KeyboardVIA::Line::One, tape->get_input());
|
||||
}
|
||||
|
||||
#pragma mark - Disc
|
||||
|
||||
void Machine::install_disk_rom()
|
||||
{
|
||||
if(drive_rom_ && c1540_)
|
||||
{
|
||||
void Machine::install_disk_rom() {
|
||||
if(drive_rom_ && c1540_) {
|
||||
c1540_->set_rom(drive_rom_.get());
|
||||
c1540_->run_for_cycles(2000000);
|
||||
drive_rom_.reset();
|
||||
@@ -373,45 +327,36 @@ void Machine::install_disk_rom()
|
||||
|
||||
#pragma mark - UserPortVIA
|
||||
|
||||
uint8_t UserPortVIA::get_port_input(Port port)
|
||||
{
|
||||
if(!port)
|
||||
{
|
||||
uint8_t UserPortVIA::get_port_input(Port port) {
|
||||
if(!port) {
|
||||
return port_a_; // TODO: bit 6 should be high if there is no tape, low otherwise
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
void UserPortVIA::set_control_line_output(Port port, Line line, bool value)
|
||||
{
|
||||
void UserPortVIA::set_control_line_output(Port port, Line line, bool value) {
|
||||
// if(port == Port::A && line == Line::Two) {
|
||||
// printf("Tape motor %s\n", value ? "on" : "off");
|
||||
// }
|
||||
}
|
||||
|
||||
void UserPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value)
|
||||
{
|
||||
switch(line)
|
||||
{
|
||||
void UserPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) {
|
||||
switch(line) {
|
||||
default: break;
|
||||
case ::Commodore::Serial::Line::Data: port_a_ = (port_a_ & ~0x02) | (value ? 0x02 : 0x00); break;
|
||||
case ::Commodore::Serial::Line::Clock: port_a_ = (port_a_ & ~0x01) | (value ? 0x01 : 0x00); break;
|
||||
}
|
||||
}
|
||||
|
||||
void UserPortVIA::set_joystick_state(JoystickInput input, bool value)
|
||||
{
|
||||
if(input != JoystickInput::Right)
|
||||
{
|
||||
void UserPortVIA::set_joystick_state(JoystickInput input, bool value) {
|
||||
if(input != JoystickInput::Right) {
|
||||
port_a_ = (port_a_ & ~input) | (value ? 0 : input);
|
||||
}
|
||||
}
|
||||
|
||||
void UserPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask)
|
||||
{
|
||||
void UserPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) {
|
||||
// Line 7 of port A is inverted and output as serial ATN
|
||||
if(!port)
|
||||
{
|
||||
if(!port) {
|
||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
||||
if(serialPort)
|
||||
serialPort->set_output(::Commodore::Serial::Line::Attention, (::Commodore::Serial::LineLevel)!(value&0x80));
|
||||
@@ -420,38 +365,31 @@ void UserPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask)
|
||||
|
||||
UserPortVIA::UserPortVIA() : port_a_(0xbf) {}
|
||||
|
||||
void UserPortVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort)
|
||||
{
|
||||
void UserPortVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) {
|
||||
serial_port_ = serialPort;
|
||||
}
|
||||
|
||||
#pragma mark - KeyboardVIA
|
||||
|
||||
KeyboardVIA::KeyboardVIA() : port_b_(0xff)
|
||||
{
|
||||
KeyboardVIA::KeyboardVIA() : port_b_(0xff) {
|
||||
clear_all_keys();
|
||||
}
|
||||
|
||||
void KeyboardVIA::set_key_state(uint16_t key, bool isPressed)
|
||||
{
|
||||
void KeyboardVIA::set_key_state(uint16_t key, bool isPressed) {
|
||||
if(isPressed)
|
||||
columns_[key & 7] &= ~(key >> 3);
|
||||
else
|
||||
columns_[key & 7] |= (key >> 3);
|
||||
}
|
||||
|
||||
void KeyboardVIA::clear_all_keys()
|
||||
{
|
||||
void KeyboardVIA::clear_all_keys() {
|
||||
memset(columns_, 0xff, sizeof(columns_));
|
||||
}
|
||||
|
||||
uint8_t KeyboardVIA::get_port_input(Port port)
|
||||
{
|
||||
if(!port)
|
||||
{
|
||||
uint8_t KeyboardVIA::get_port_input(Port port) {
|
||||
if(!port) {
|
||||
uint8_t result = 0xff;
|
||||
for(int c = 0; c < 8; c++)
|
||||
{
|
||||
for(int c = 0; c < 8; c++) {
|
||||
if(!(activation_mask_&(1 << c)))
|
||||
result &= columns_[c];
|
||||
}
|
||||
@@ -461,19 +399,15 @@ uint8_t KeyboardVIA::get_port_input(Port port)
|
||||
return port_b_;
|
||||
}
|
||||
|
||||
void KeyboardVIA::set_port_output(Port port, uint8_t value, uint8_t mask)
|
||||
{
|
||||
void KeyboardVIA::set_port_output(Port port, uint8_t value, uint8_t mask) {
|
||||
if(port)
|
||||
activation_mask_ = (value & mask) | (~mask);
|
||||
}
|
||||
|
||||
void KeyboardVIA::set_control_line_output(Port port, Line line, bool value)
|
||||
{
|
||||
if(line == Line::Two)
|
||||
{
|
||||
void KeyboardVIA::set_control_line_output(Port port, Line line, bool value) {
|
||||
if(line == Line::Two) {
|
||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
||||
if(serialPort)
|
||||
{
|
||||
if(serialPort) {
|
||||
// CB2 is inverted to become serial data; CA2 is inverted to become serial clock
|
||||
if(port == Port::A)
|
||||
serialPort->set_output(::Commodore::Serial::Line::Clock, (::Commodore::Serial::LineLevel)!value);
|
||||
@@ -483,28 +417,23 @@ void KeyboardVIA::set_control_line_output(Port port, Line line, bool value)
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardVIA::set_joystick_state(JoystickInput input, bool value)
|
||||
{
|
||||
if(input == JoystickInput::Right)
|
||||
{
|
||||
void KeyboardVIA::set_joystick_state(JoystickInput input, bool value) {
|
||||
if(input == JoystickInput::Right) {
|
||||
port_b_ = (port_b_ & ~input) | (value ? 0 : input);
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort)
|
||||
{
|
||||
void KeyboardVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) {
|
||||
serial_port_ = serialPort;
|
||||
}
|
||||
|
||||
#pragma mark - SerialPort
|
||||
|
||||
void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level)
|
||||
{
|
||||
void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) {
|
||||
std::shared_ptr<UserPortVIA> userPortVIA = user_port_via_.lock();
|
||||
if(userPortVIA) userPortVIA->set_serial_line_state(line, (bool)level);
|
||||
}
|
||||
|
||||
void SerialPort::set_user_port_via(std::shared_ptr<UserPortVIA> userPortVIA)
|
||||
{
|
||||
void SerialPort::set_user_port_via(std::shared_ptr<UserPortVIA> userPortVIA) {
|
||||
user_port_via_ = userPortVIA;
|
||||
}
|
||||
|
||||
@@ -129,8 +129,7 @@ class SerialPort : public ::Commodore::Serial::Port {
|
||||
|
||||
class Vic6560: public MOS::MOS6560<Vic6560> {
|
||||
public:
|
||||
inline void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data)
|
||||
{
|
||||
inline void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data) {
|
||||
*pixel_data = video_memory_map[address >> 10] ? video_memory_map[address >> 10][address & 0x3ff] : 0xff; // TODO
|
||||
*colour_data = colour_memory[address & 0x03ff];
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace ConfigurationTarget {
|
||||
*/
|
||||
class Machine {
|
||||
public:
|
||||
virtual void configure_as_target(const StaticAnalyser::Target &target) =0;
|
||||
virtual void configure_as_target(const StaticAnalyser::Target &target) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -13,13 +13,12 @@ using namespace Electron;
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
Machine::Machine() :
|
||||
interrupt_control_(0),
|
||||
interrupt_status_(Interrupt::PowerOnReset | Interrupt::TransmitDataEmpty | 0x80),
|
||||
cycles_since_display_update_(0),
|
||||
cycles_since_audio_update_(0),
|
||||
use_fast_tape_hack_(false),
|
||||
cycles_until_display_interrupt_(0)
|
||||
{
|
||||
interrupt_control_(0),
|
||||
interrupt_status_(Interrupt::PowerOnReset | Interrupt::TransmitDataEmpty | 0x80),
|
||||
cycles_since_display_update_(0),
|
||||
cycles_since_audio_update_(0),
|
||||
use_fast_tape_hack_(false),
|
||||
cycles_until_display_interrupt_(0) {
|
||||
memset(key_states_, 0, sizeof(key_states_));
|
||||
for(int c = 0; c < 16; c++)
|
||||
memset(roms_[c], 0xff, 16384);
|
||||
@@ -30,8 +29,7 @@ Machine::Machine() :
|
||||
|
||||
#pragma mark - Output
|
||||
|
||||
void Machine::setup_output(float aspect_ratio)
|
||||
{
|
||||
void Machine::setup_output(float aspect_ratio) {
|
||||
video_output_.reset(new VideoOutput(ram_));
|
||||
|
||||
// The maximum output frequency is 62500Hz and all other permitted output frequencies are integral divisions of that;
|
||||
@@ -41,36 +39,29 @@ void Machine::setup_output(float aspect_ratio)
|
||||
speaker_->set_input_rate(2000000 / Speaker::clock_rate_divider);
|
||||
}
|
||||
|
||||
void Machine::close_output()
|
||||
{
|
||||
void Machine::close_output() {
|
||||
video_output_.reset();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> Machine::get_crt()
|
||||
{
|
||||
std::shared_ptr<Outputs::CRT::CRT> Machine::get_crt() {
|
||||
return video_output_->get_crt();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::Speaker> Machine::get_speaker()
|
||||
{
|
||||
std::shared_ptr<Outputs::Speaker> Machine::get_speaker() {
|
||||
return speaker_;
|
||||
}
|
||||
|
||||
#pragma mark - The keyboard
|
||||
|
||||
void Machine::clear_all_keys()
|
||||
{
|
||||
void Machine::clear_all_keys() {
|
||||
memset(key_states_, 0, sizeof(key_states_));
|
||||
if(is_holding_shift_) set_key_state(KeyShift, true);
|
||||
}
|
||||
|
||||
void Machine::set_key_state(uint16_t key, bool isPressed)
|
||||
{
|
||||
if(key == KeyBreak)
|
||||
{
|
||||
void Machine::set_key_state(uint16_t key, bool isPressed) {
|
||||
if(key == KeyBreak) {
|
||||
set_reset_line(isPressed);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
if(isPressed)
|
||||
key_states_[key >> 4] |= key&0xf;
|
||||
else
|
||||
@@ -80,23 +71,18 @@ void Machine::set_key_state(uint16_t key, bool isPressed)
|
||||
|
||||
#pragma mark - Machine configuration
|
||||
|
||||
void Machine::configure_as_target(const StaticAnalyser::Target &target)
|
||||
{
|
||||
if(target.tapes.size())
|
||||
{
|
||||
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
|
||||
if(target.tapes.size()) {
|
||||
tape_.set_tape(target.tapes.front());
|
||||
}
|
||||
|
||||
if(target.disks.size())
|
||||
{
|
||||
if(target.disks.size()) {
|
||||
plus3_.reset(new Plus3);
|
||||
|
||||
if(target.acorn.has_dfs)
|
||||
{
|
||||
if(target.acorn.has_dfs) {
|
||||
set_rom(ROMSlot0, dfs_, true);
|
||||
}
|
||||
if(target.acorn.has_adfs)
|
||||
{
|
||||
if(target.acorn.has_adfs) {
|
||||
set_rom(ROMSlot4, adfs_, true);
|
||||
set_rom(ROMSlot5, std::vector<uint8_t>(adfs_.begin() + 16384, adfs_.end()), true);
|
||||
}
|
||||
@@ -105,29 +91,23 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target)
|
||||
}
|
||||
|
||||
ROMSlot slot = ROMSlot12;
|
||||
for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : target.cartridges)
|
||||
{
|
||||
for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : target.cartridges) {
|
||||
set_rom(slot, cartridge->get_segments().front().data, false);
|
||||
slot = (ROMSlot)(((int)slot + 1)&15);
|
||||
}
|
||||
|
||||
if(target.loadingCommand.length()) // TODO: and automatic loading option enabled
|
||||
{
|
||||
if(target.loadingCommand.length()) { // TODO: and automatic loading option enabled
|
||||
set_typer_for_string(target.loadingCommand.c_str());
|
||||
}
|
||||
|
||||
if(target.acorn.should_hold_shift)
|
||||
{
|
||||
set_key_state(KeyShift, true);
|
||||
is_holding_shift_ = true;
|
||||
if(target.acorn.should_shift_restart) {
|
||||
shift_restart_counter_ = 1000000;
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable)
|
||||
{
|
||||
void Machine::set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable) {
|
||||
uint8_t *target = nullptr;
|
||||
switch(slot)
|
||||
{
|
||||
switch(slot) {
|
||||
case ROMSlotDFS: dfs_ = data; return;
|
||||
case ROMSlotADFS: adfs_ = data; return;
|
||||
|
||||
@@ -143,18 +123,13 @@ void Machine::set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable
|
||||
|
||||
#pragma mark - The bus
|
||||
|
||||
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value)
|
||||
{
|
||||
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
unsigned int cycles = 1;
|
||||
|
||||
if(address < 0x8000)
|
||||
{
|
||||
if(isReadOperation(operation))
|
||||
{
|
||||
if(address < 0x8000) {
|
||||
if(isReadOperation(operation)) {
|
||||
*value = ram_[address];
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
if(address >= video_access_range_.low_address && address <= video_access_range_.high_address) update_display();
|
||||
ram_[address] = *value;
|
||||
}
|
||||
@@ -162,30 +137,22 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
// for the entire frame, RAM is accessible only on odd cycles; in modes below 4
|
||||
// it's also accessible only outside of the pixel regions
|
||||
cycles += video_output_->get_cycles_until_next_ram_availability((int)(cycles_since_display_update_ + 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
switch(address & 0xff0f)
|
||||
{
|
||||
} else {
|
||||
switch(address & 0xff0f) {
|
||||
case 0xfe00:
|
||||
if(isReadOperation(operation))
|
||||
{
|
||||
if(isReadOperation(operation)) {
|
||||
*value = interrupt_status_;
|
||||
interrupt_status_ &= ~PowerOnReset;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
interrupt_control_ = (*value) & ~1;
|
||||
evaluate_interrupts();
|
||||
}
|
||||
break;
|
||||
case 0xfe07:
|
||||
if(!isReadOperation(operation))
|
||||
{
|
||||
if(!isReadOperation(operation)) {
|
||||
// update speaker mode
|
||||
bool new_speaker_is_enabled = (*value & 6) == 2;
|
||||
if(new_speaker_is_enabled != speaker_is_enabled_)
|
||||
{
|
||||
if(new_speaker_is_enabled != speaker_is_enabled_) {
|
||||
update_audio();
|
||||
speaker_->set_is_enabled(new_speaker_is_enabled);
|
||||
speaker_is_enabled_ = new_speaker_is_enabled;
|
||||
@@ -202,8 +169,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
case 0xfe02: case 0xfe03:
|
||||
case 0xfe08: case 0xfe09: case 0xfe0a: case 0xfe0b:
|
||||
case 0xfe0c: case 0xfe0d: case 0xfe0e: case 0xfe0f:
|
||||
if(!isReadOperation(operation))
|
||||
{
|
||||
if(!isReadOperation(operation)) {
|
||||
update_display();
|
||||
video_output_->set_register(address, *value);
|
||||
video_access_range_ = video_output_->get_memory_access_range();
|
||||
@@ -211,23 +177,18 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
}
|
||||
break;
|
||||
case 0xfe04:
|
||||
if(isReadOperation(operation))
|
||||
{
|
||||
if(isReadOperation(operation)) {
|
||||
*value = tape_.get_data_register();
|
||||
tape_.clear_interrupts(Interrupt::ReceiveDataFull);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
tape_.set_data_register(*value);
|
||||
tape_.clear_interrupts(Interrupt::TransmitDataEmpty);
|
||||
}
|
||||
break;
|
||||
case 0xfe05:
|
||||
if(!isReadOperation(operation))
|
||||
{
|
||||
if(!isReadOperation(operation)) {
|
||||
const uint8_t interruptDisable = (*value)&0xf0;
|
||||
if( interruptDisable )
|
||||
{
|
||||
if( interruptDisable ) {
|
||||
if( interruptDisable&0x10 ) interrupt_status_ &= ~Interrupt::DisplayEnd;
|
||||
if( interruptDisable&0x20 ) interrupt_status_ &= ~Interrupt::RealTimeClock;
|
||||
if( interruptDisable&0x40 ) interrupt_status_ &= ~Interrupt::HighToneDetect;
|
||||
@@ -240,15 +201,11 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
active_rom_ = (Electron::ROMSlot)(*value & 0xf);
|
||||
|
||||
// apply the ULA's test
|
||||
if(*value & 0x08)
|
||||
{
|
||||
if(*value & 0x04)
|
||||
{
|
||||
if(*value & 0x08) {
|
||||
if(*value & 0x04) {
|
||||
keyboard_is_active_ = false;
|
||||
basic_is_active_ = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
keyboard_is_active_ = !(*value & 0x02);
|
||||
basic_is_active_ = !keyboard_is_active_;
|
||||
}
|
||||
@@ -256,8 +213,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
}
|
||||
break;
|
||||
case 0xfe06:
|
||||
if(!isReadOperation(operation))
|
||||
{
|
||||
if(!isReadOperation(operation)) {
|
||||
update_audio();
|
||||
speaker_->set_divider(*value);
|
||||
tape_.set_counter(*value);
|
||||
@@ -265,10 +221,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
break;
|
||||
|
||||
case 0xfc04: case 0xfc05: case 0xfc06: case 0xfc07:
|
||||
if(plus3_ && (address&0x00f0) == 0x00c0)
|
||||
{
|
||||
if(is_holding_shift_ && address == 0xfcc4)
|
||||
{
|
||||
if(plus3_ && (address&0x00f0) == 0x00c0) {
|
||||
if(is_holding_shift_ && address == 0xfcc4) {
|
||||
is_holding_shift_ = false;
|
||||
set_key_state(KeyShift, false);
|
||||
}
|
||||
@@ -279,22 +233,16 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
}
|
||||
break;
|
||||
case 0xfc00:
|
||||
if(plus3_ && (address&0x00f0) == 0x00c0)
|
||||
{
|
||||
if(!isReadOperation(operation))
|
||||
{
|
||||
if(plus3_ && (address&0x00f0) == 0x00c0) {
|
||||
if(!isReadOperation(operation)) {
|
||||
plus3_->set_control_register(*value);
|
||||
}
|
||||
else
|
||||
*value = 1;
|
||||
} else *value = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if(address >= 0xc000)
|
||||
{
|
||||
if(isReadOperation(operation))
|
||||
{
|
||||
if(address >= 0xc000) {
|
||||
if(isReadOperation(operation)) {
|
||||
if(
|
||||
use_fast_tape_hack_ &&
|
||||
tape_.has_tape() &&
|
||||
@@ -314,21 +262,17 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
// and set A to zero to report that action was taken, then
|
||||
// allow the PC read to return an RTS.
|
||||
)
|
||||
)
|
||||
{
|
||||
) {
|
||||
uint8_t service_call = (uint8_t)get_value_of_register(CPU6502::Register::X);
|
||||
if(address == 0xf0a8)
|
||||
{
|
||||
if(!ram_[0x247] && service_call == 14)
|
||||
{
|
||||
if(address == 0xf0a8) {
|
||||
if(!ram_[0x247] && service_call == 14) {
|
||||
tape_.set_delegate(nullptr);
|
||||
|
||||
// TODO: handle tape wrap around.
|
||||
|
||||
int cycles_left_while_plausibly_in_data = 50;
|
||||
tape_.clear_interrupts(Interrupt::ReceiveDataFull);
|
||||
while(!tape_.get_tape()->is_at_end())
|
||||
{
|
||||
while(!tape_.get_tape()->is_at_end()) {
|
||||
tape_.run_for_input_pulse();
|
||||
cycles_left_while_plausibly_in_data--;
|
||||
if(!cycles_left_while_plausibly_in_data) fast_load_is_in_data_ = false;
|
||||
@@ -345,37 +289,26 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
set_value_of_register(CPU6502::Register::Y, tape_.get_data_register());
|
||||
*value = 0x60; // 0x60 is RTS
|
||||
}
|
||||
else
|
||||
*value = os_[address & 16383];
|
||||
else *value = os_[address & 16383];
|
||||
}
|
||||
else
|
||||
*value = 0xea;
|
||||
}
|
||||
else
|
||||
{
|
||||
else *value = 0xea;
|
||||
} else {
|
||||
*value = os_[address & 16383];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(isReadOperation(operation))
|
||||
{
|
||||
} else {
|
||||
if(isReadOperation(operation)) {
|
||||
*value = roms_[active_rom_][address & 16383];
|
||||
if(keyboard_is_active_)
|
||||
{
|
||||
if(keyboard_is_active_) {
|
||||
*value &= 0xf0;
|
||||
for(int address_line = 0; address_line < 14; address_line++)
|
||||
{
|
||||
for(int address_line = 0; address_line < 14; address_line++) {
|
||||
if(!(address&(1 << address_line))) *value |= key_states_[address_line];
|
||||
}
|
||||
}
|
||||
if(basic_is_active_)
|
||||
{
|
||||
if(basic_is_active_) {
|
||||
*value &= roms_[ROMSlotBASIC][address & 16383];
|
||||
}
|
||||
} else if(rom_write_masks_[active_rom_])
|
||||
{
|
||||
} else if(rom_write_masks_[active_rom_]) {
|
||||
roms_[active_rom_][address & 16383] = *value;
|
||||
}
|
||||
}
|
||||
@@ -389,8 +322,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
tape_.run_for_cycles(cycles);
|
||||
|
||||
cycles_until_display_interrupt_ -= cycles;
|
||||
if(cycles_until_display_interrupt_ < 0)
|
||||
{
|
||||
if(cycles_until_display_interrupt_ < 0) {
|
||||
signal_interrupt(next_display_interrupt_);
|
||||
update_display();
|
||||
queue_next_display_interrupt();
|
||||
@@ -398,12 +330,20 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
|
||||
if(typer_) typer_->update((int)cycles);
|
||||
if(plus3_) plus3_->run_for_cycles(4*cycles);
|
||||
if(shift_restart_counter_) {
|
||||
shift_restart_counter_ -= cycles;
|
||||
if(shift_restart_counter_ <= 0) {
|
||||
shift_restart_counter_ = 0;
|
||||
set_power_on(true);
|
||||
set_key_state(KeyShift, true);
|
||||
is_holding_shift_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
return cycles;
|
||||
}
|
||||
|
||||
void Machine::synchronise()
|
||||
{
|
||||
void Machine::synchronise() {
|
||||
update_display();
|
||||
update_audio();
|
||||
speaker_->flush();
|
||||
@@ -411,26 +351,21 @@ void Machine::synchronise()
|
||||
|
||||
#pragma mark - Deferred scheduling
|
||||
|
||||
inline void Machine::update_display()
|
||||
{
|
||||
if(cycles_since_display_update_)
|
||||
{
|
||||
inline void Machine::update_display() {
|
||||
if(cycles_since_display_update_) {
|
||||
video_output_->run_for_cycles((int)cycles_since_display_update_);
|
||||
cycles_since_display_update_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
inline void Machine::queue_next_display_interrupt()
|
||||
{
|
||||
inline void Machine::queue_next_display_interrupt() {
|
||||
VideoOutput::Interrupt next_interrupt = video_output_->get_next_interrupt();
|
||||
cycles_until_display_interrupt_ = next_interrupt.cycles;
|
||||
next_display_interrupt_ = next_interrupt.interrupt;
|
||||
}
|
||||
|
||||
inline void Machine::update_audio()
|
||||
{
|
||||
if(cycles_since_audio_update_)
|
||||
{
|
||||
inline void Machine::update_audio() {
|
||||
if(cycles_since_audio_update_) {
|
||||
unsigned int difference = cycles_since_audio_update_ / Speaker::clock_rate_divider;
|
||||
cycles_since_audio_update_ %= Speaker::clock_rate_divider;
|
||||
speaker_->run_for_cycles(difference);
|
||||
@@ -439,26 +374,20 @@ inline void Machine::update_audio()
|
||||
|
||||
#pragma mark - Interrupts
|
||||
|
||||
inline void Machine::signal_interrupt(Electron::Interrupt interrupt)
|
||||
{
|
||||
inline void Machine::signal_interrupt(Electron::Interrupt interrupt) {
|
||||
interrupt_status_ |= interrupt;
|
||||
evaluate_interrupts();
|
||||
}
|
||||
|
||||
inline void Machine::clear_interrupt(Electron::Interrupt interrupt)
|
||||
{
|
||||
inline void Machine::clear_interrupt(Electron::Interrupt interrupt) {
|
||||
interrupt_status_ &= ~interrupt;
|
||||
evaluate_interrupts();
|
||||
}
|
||||
|
||||
inline void Machine::evaluate_interrupts()
|
||||
{
|
||||
if(interrupt_status_ & interrupt_control_)
|
||||
{
|
||||
inline void Machine::evaluate_interrupts() {
|
||||
if(interrupt_status_ & interrupt_control_) {
|
||||
interrupt_status_ |= 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
interrupt_status_ &= ~1;
|
||||
}
|
||||
set_irq_line(interrupt_status_ & 1);
|
||||
@@ -466,8 +395,7 @@ inline void Machine::evaluate_interrupts()
|
||||
|
||||
#pragma mark - Tape::Delegate
|
||||
|
||||
void Machine::tape_did_change_interrupt_status(Tape *tape)
|
||||
{
|
||||
void Machine::tape_did_change_interrupt_status(Tape *tape) {
|
||||
interrupt_status_ = (interrupt_status_ & ~(Interrupt::TransmitDataEmpty | Interrupt::ReceiveDataFull | Interrupt::HighToneDetect)) | tape_.get_interrupt_status();
|
||||
evaluate_interrupts();
|
||||
}
|
||||
|
||||
@@ -144,6 +144,7 @@ class Machine:
|
||||
// Disk
|
||||
std::unique_ptr<Plus3> plus3_;
|
||||
bool is_holding_shift_;
|
||||
int shift_restart_counter_;
|
||||
|
||||
// Outputs
|
||||
std::unique_ptr<VideoOutput> video_output_;
|
||||
|
||||
@@ -10,23 +10,19 @@
|
||||
|
||||
using namespace Electron;
|
||||
|
||||
Plus3::Plus3() : WD1770(P1770), last_control_(0)
|
||||
{
|
||||
Plus3::Plus3() : WD1770(P1770), last_control_(0) {
|
||||
set_control_register(last_control_, 0xff);
|
||||
}
|
||||
|
||||
void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive)
|
||||
{
|
||||
if(!drives_[drive])
|
||||
{
|
||||
void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||
if(!drives_[drive]) {
|
||||
drives_[drive].reset(new Storage::Disk::Drive);
|
||||
if(drive == selected_drive_) set_drive(drives_[drive]);
|
||||
}
|
||||
drives_[drive]->set_disk(disk);
|
||||
}
|
||||
|
||||
void Plus3::set_control_register(uint8_t control)
|
||||
{
|
||||
void Plus3::set_control_register(uint8_t control) {
|
||||
// bit 0 => enable or disable drive 1
|
||||
// bit 1 => enable or disable drive 2
|
||||
// bit 2 => side select
|
||||
@@ -37,19 +33,15 @@ void Plus3::set_control_register(uint8_t control)
|
||||
set_control_register(control, changes);
|
||||
}
|
||||
|
||||
void Plus3::set_control_register(uint8_t control, uint8_t changes)
|
||||
{
|
||||
if(changes&3)
|
||||
{
|
||||
switch(control&3)
|
||||
{
|
||||
void Plus3::set_control_register(uint8_t control, uint8_t changes) {
|
||||
if(changes&3) {
|
||||
switch(control&3) {
|
||||
case 0: selected_drive_ = -1; set_drive(nullptr); break;
|
||||
default: selected_drive_ = 0; set_drive(drives_[0]); break;
|
||||
case 2: selected_drive_ = 1; set_drive(drives_[1]); break;
|
||||
}
|
||||
}
|
||||
if(changes & 0x04)
|
||||
{
|
||||
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);
|
||||
|
||||
@@ -10,37 +10,29 @@
|
||||
|
||||
using namespace Electron;
|
||||
|
||||
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target)
|
||||
{
|
||||
if(is_enabled_)
|
||||
{
|
||||
while(number_of_samples--)
|
||||
{
|
||||
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++;
|
||||
counter_ = (counter_ + 1) % ((divider_+1) * 2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
memset(target, 0, sizeof(int16_t) * number_of_samples);
|
||||
}
|
||||
}
|
||||
|
||||
void Speaker::skip_samples(unsigned int number_of_samples)
|
||||
{
|
||||
void Speaker::skip_samples(unsigned int number_of_samples) {
|
||||
counter_ = (counter_ + number_of_samples) % ((divider_+1) * 2);
|
||||
}
|
||||
|
||||
void Speaker::set_divider(uint8_t divider)
|
||||
{
|
||||
void Speaker::set_divider(uint8_t divider) {
|
||||
enqueue([=]() {
|
||||
divider_ = divider * 32 / clock_rate_divider;
|
||||
});
|
||||
}
|
||||
|
||||
void Speaker::set_is_enabled(bool is_enabled)
|
||||
{
|
||||
void Speaker::set_is_enabled(bool is_enabled) {
|
||||
enqueue([=]() {
|
||||
is_enabled_ = is_enabled;
|
||||
counter_ = 0;
|
||||
|
||||
@@ -11,25 +11,21 @@
|
||||
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)
|
||||
{}
|
||||
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) {}
|
||||
|
||||
void Tape::push_tape_bit(uint16_t bit)
|
||||
{
|
||||
void Tape::push_tape_bit(uint16_t bit) {
|
||||
data_register_ = (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;
|
||||
if(!input_.minimum_bits_until_full)
|
||||
{
|
||||
if((data_register_&0x3) == 0x1)
|
||||
{
|
||||
if(!input_.minimum_bits_until_full) {
|
||||
if((data_register_&0x3) == 0x1) {
|
||||
interrupt_status_ |= Interrupt::ReceiveDataFull;
|
||||
if(is_in_input_mode_) input_.minimum_bits_until_full = 9;
|
||||
}
|
||||
@@ -44,66 +40,53 @@ void Tape::push_tape_bit(uint16_t bit)
|
||||
evaluate_interrupts();
|
||||
}
|
||||
|
||||
void Tape::evaluate_interrupts()
|
||||
{
|
||||
if(last_posted_interrupt_status_ != interrupt_status_)
|
||||
{
|
||||
void Tape::evaluate_interrupts() {
|
||||
if(last_posted_interrupt_status_ != interrupt_status_) {
|
||||
last_posted_interrupt_status_ = interrupt_status_;
|
||||
if(delegate_) delegate_->tape_did_change_interrupt_status(this);
|
||||
}
|
||||
}
|
||||
|
||||
void Tape::clear_interrupts(uint8_t interrupts)
|
||||
{
|
||||
void Tape::clear_interrupts(uint8_t interrupts) {
|
||||
interrupt_status_ &= ~interrupts;
|
||||
evaluate_interrupts();
|
||||
}
|
||||
|
||||
void Tape::set_is_in_input_mode(bool is_in_input_mode)
|
||||
{
|
||||
void Tape::set_is_in_input_mode(bool is_in_input_mode) {
|
||||
is_in_input_mode_ = is_in_input_mode;
|
||||
}
|
||||
|
||||
void Tape::set_counter(uint8_t value)
|
||||
{
|
||||
void Tape::set_counter(uint8_t value) {
|
||||
output_.cycles_into_pulse = 0;
|
||||
output_.bits_remaining_until_empty = 0;
|
||||
}
|
||||
|
||||
void Tape::set_data_register(uint8_t value)
|
||||
{
|
||||
void Tape::set_data_register(uint8_t value) {
|
||||
data_register_ = (uint16_t)((value << 2) | 1);
|
||||
output_.bits_remaining_until_empty = 9;
|
||||
}
|
||||
|
||||
uint8_t Tape::get_data_register()
|
||||
{
|
||||
uint8_t Tape::get_data_register() {
|
||||
return (uint8_t)(data_register_ >> 2);
|
||||
}
|
||||
|
||||
void Tape::process_input_pulse(Storage::Tape::Tape::Pulse pulse)
|
||||
{
|
||||
void Tape::process_input_pulse(Storage::Tape::Tape::Pulse pulse) {
|
||||
crossings_[0] = crossings_[1];
|
||||
crossings_[1] = crossings_[2];
|
||||
crossings_[2] = crossings_[3];
|
||||
|
||||
crossings_[3] = Tape::Unrecognised;
|
||||
if(pulse.type != Storage::Tape::Tape::Pulse::Zero)
|
||||
{
|
||||
if(pulse.type != Storage::Tape::Tape::Pulse::Zero) {
|
||||
float pulse_length = (float)pulse.length.length / (float)pulse.length.clock_rate;
|
||||
if(pulse_length >= 0.35 / 2400.0 && pulse_length < 0.7 / 2400.0) crossings_[3] = Tape::Short;
|
||||
if(pulse_length >= 0.35 / 1200.0 && pulse_length < 0.7 / 1200.0) crossings_[3] = Tape::Long;
|
||||
}
|
||||
|
||||
if(crossings_[0] == Tape::Long && crossings_[1] == Tape::Long)
|
||||
{
|
||||
if(crossings_[0] == Tape::Long && crossings_[1] == Tape::Long) {
|
||||
push_tape_bit(0);
|
||||
crossings_[0] = crossings_[1] = Tape::Recognised;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(crossings_[0] == Tape::Short && crossings_[1] == Tape::Short && crossings_[2] == Tape::Short && crossings_[3] == Tape::Short)
|
||||
{
|
||||
} else {
|
||||
if(crossings_[0] == Tape::Short && crossings_[1] == Tape::Short && crossings_[2] == Tape::Short && crossings_[3] == Tape::Short) {
|
||||
push_tape_bit(1);
|
||||
crossings_[0] = crossings_[1] =
|
||||
crossings_[2] = crossings_[3] = Tape::Recognised;
|
||||
@@ -111,23 +94,16 @@ void Tape::process_input_pulse(Storage::Tape::Tape::Pulse pulse)
|
||||
}
|
||||
}
|
||||
|
||||
void Tape::run_for_cycles(unsigned int number_of_cycles)
|
||||
{
|
||||
if(is_enabled_)
|
||||
{
|
||||
if(is_in_input_mode_)
|
||||
{
|
||||
if(is_running_)
|
||||
{
|
||||
void Tape::run_for_cycles(unsigned int number_of_cycles) {
|
||||
if(is_enabled_) {
|
||||
if(is_in_input_mode_) {
|
||||
if(is_running_) {
|
||||
TapePlayer::run_for_cycles((int)number_of_cycles);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
output_.cycles_into_pulse += number_of_cycles;
|
||||
while(output_.cycles_into_pulse > 1664) // 1664 = the closest you can get to 1200 baud if you're looking for something
|
||||
{ // that divides the 125,000Hz clock that the sound divider runs off.
|
||||
output_.cycles_into_pulse -= 1664;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,18 +8,15 @@
|
||||
|
||||
#include "Electron.hpp"
|
||||
|
||||
int Electron::Machine::get_typer_delay()
|
||||
{
|
||||
int Electron::Machine::get_typer_delay() {
|
||||
return get_is_resetting() ? 625*25*128 : 0; // wait one second if resetting
|
||||
}
|
||||
|
||||
int Electron::Machine::get_typer_frequency()
|
||||
{
|
||||
int Electron::Machine::get_typer_frequency() {
|
||||
return 625*128*2; // accept a new character every two frames
|
||||
}
|
||||
|
||||
uint16_t *Electron::Machine::sequence_for_character(Utility::Typer *typer, char character)
|
||||
{
|
||||
uint16_t *Electron::Machine::sequence_for_character(Utility::Typer *typer, char character) {
|
||||
#define KEYS(...) {__VA_ARGS__, TerminateSequence}
|
||||
#define SHIFT(...) {KeyShift, __VA_ARGS__, TerminateSequence}
|
||||
#define CTRL(...) {KeyControl, __VA_ARGS__, TerminateSequence}
|
||||
|
||||
@@ -37,13 +37,12 @@ namespace {
|
||||
#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)
|
||||
{
|
||||
ram_(memory),
|
||||
current_pixel_line_(-1),
|
||||
output_position_(0),
|
||||
screen_mode_(6),
|
||||
screen_map_pointer_(0),
|
||||
cycles_into_draw_action_(0) {
|
||||
memset(palette_, 0xf, sizeof(palette_));
|
||||
setup_screen_map();
|
||||
setup_base_address();
|
||||
@@ -62,33 +61,26 @@ VideoOutput::VideoOutput(uint8_t *memory) :
|
||||
|
||||
#pragma mark - CRT getter
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt()
|
||||
{
|
||||
std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt() {
|
||||
return crt_;
|
||||
}
|
||||
|
||||
#pragma mark - Display update methods
|
||||
|
||||
void VideoOutput::start_pixel_line()
|
||||
{
|
||||
void VideoOutput::start_pixel_line() {
|
||||
current_pixel_line_ = (current_pixel_line_+1)&255;
|
||||
if(!current_pixel_line_)
|
||||
{
|
||||
if(!current_pixel_line_) {
|
||||
start_line_address_ = start_screen_address_;
|
||||
current_character_row_ = 0;
|
||||
is_blank_line_ = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
bool mode_has_blank_lines = (screen_mode_ == 6) || (screen_mode_ == 3);
|
||||
is_blank_line_ = (mode_has_blank_lines && ((current_character_row_ > 7 && current_character_row_ < 10) || (current_pixel_line_ > 249)));
|
||||
|
||||
if(!is_blank_line_)
|
||||
{
|
||||
if(!is_blank_line_) {
|
||||
start_line_address_++;
|
||||
|
||||
if(current_character_row_ > 7)
|
||||
{
|
||||
if(current_character_row_ > 7) {
|
||||
start_line_address_ += ((screen_mode_ < 4) ? 80 : 40) * 8 - 8;
|
||||
current_character_row_ = 0;
|
||||
}
|
||||
@@ -99,52 +91,41 @@ void VideoOutput::start_pixel_line()
|
||||
initial_output_target_ = current_output_target_ = nullptr;
|
||||
}
|
||||
|
||||
void VideoOutput::end_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_);
|
||||
current_character_row_++;
|
||||
}
|
||||
|
||||
void VideoOutput::output_pixels(unsigned int number_of_cycles)
|
||||
{
|
||||
void VideoOutput::output_pixels(unsigned int number_of_cycles) {
|
||||
if(!number_of_cycles) return;
|
||||
|
||||
if(is_blank_line_)
|
||||
{
|
||||
if(is_blank_line_) {
|
||||
crt_->output_blank(number_of_cycles * crt_cycles_multiplier);
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned int divider = 0;
|
||||
switch(screen_mode_)
|
||||
{
|
||||
} else {
|
||||
unsigned int divider = 1;
|
||||
switch(screen_mode_) {
|
||||
case 0: case 3: divider = 2; break;
|
||||
case 1: case 4: case 6: divider = 4; break;
|
||||
case 2: case 5: divider = 8; break;
|
||||
}
|
||||
|
||||
if(!initial_output_target_ || divider != current_output_divider_)
|
||||
{
|
||||
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_);
|
||||
current_output_divider_ = divider;
|
||||
initial_output_target_ = current_output_target_ = crt_->allocate_write_area(640 / current_output_divider_);
|
||||
}
|
||||
|
||||
#define get_pixel() \
|
||||
if(current_screen_address_&32768)\
|
||||
{\
|
||||
if(current_screen_address_&32768) {\
|
||||
current_screen_address_ = (screen_mode_base_address_ + current_screen_address_)&32767;\
|
||||
}\
|
||||
last_pixel_byte_ = ram_[current_screen_address_];\
|
||||
current_screen_address_ = current_screen_address_+8
|
||||
|
||||
switch(screen_mode_)
|
||||
{
|
||||
switch(screen_mode_) {
|
||||
case 0: case 3:
|
||||
if(initial_output_target_)
|
||||
{
|
||||
while(number_of_cycles--)
|
||||
{
|
||||
if(initial_output_target_) {
|
||||
while(number_of_cycles--) {
|
||||
get_pixel();
|
||||
*(uint32_t *)current_output_target_ = palette_tables_.eighty1bpp[last_pixel_byte_];
|
||||
current_output_target_ += 4;
|
||||
@@ -154,10 +135,8 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles)
|
||||
break;
|
||||
|
||||
case 1:
|
||||
if(initial_output_target_)
|
||||
{
|
||||
while(number_of_cycles--)
|
||||
{
|
||||
if(initial_output_target_) {
|
||||
while(number_of_cycles--) {
|
||||
get_pixel();
|
||||
*(uint16_t *)current_output_target_ = palette_tables_.eighty2bpp[last_pixel_byte_];
|
||||
current_output_target_ += 2;
|
||||
@@ -167,10 +146,8 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles)
|
||||
break;
|
||||
|
||||
case 2:
|
||||
if(initial_output_target_)
|
||||
{
|
||||
while(number_of_cycles--)
|
||||
{
|
||||
if(initial_output_target_) {
|
||||
while(number_of_cycles--) {
|
||||
get_pixel();
|
||||
*current_output_target_ = palette_tables_.eighty4bpp[last_pixel_byte_];
|
||||
current_output_target_ += 1;
|
||||
@@ -180,10 +157,8 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles)
|
||||
break;
|
||||
|
||||
case 4: case 6:
|
||||
if(initial_output_target_)
|
||||
{
|
||||
if(current_pixel_column_&1)
|
||||
{
|
||||
if(initial_output_target_) {
|
||||
if(current_pixel_column_&1) {
|
||||
last_pixel_byte_ <<= 4;
|
||||
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
|
||||
current_output_target_ += 2;
|
||||
@@ -191,8 +166,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles)
|
||||
number_of_cycles--;
|
||||
current_pixel_column_++;
|
||||
}
|
||||
while(number_of_cycles > 1)
|
||||
{
|
||||
while(number_of_cycles > 1) {
|
||||
get_pixel();
|
||||
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
|
||||
current_output_target_ += 2;
|
||||
@@ -204,8 +178,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles)
|
||||
number_of_cycles -= 2;
|
||||
current_pixel_column_+=2;
|
||||
}
|
||||
if(number_of_cycles)
|
||||
{
|
||||
if(number_of_cycles) {
|
||||
get_pixel();
|
||||
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
|
||||
current_output_target_ += 2;
|
||||
@@ -215,10 +188,8 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles)
|
||||
break;
|
||||
|
||||
case 5:
|
||||
if(initial_output_target_)
|
||||
{
|
||||
if(current_pixel_column_&1)
|
||||
{
|
||||
if(initial_output_target_) {
|
||||
if(current_pixel_column_&1) {
|
||||
last_pixel_byte_ <<= 2;
|
||||
*current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_];
|
||||
current_output_target_ += 1;
|
||||
@@ -226,8 +197,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles)
|
||||
number_of_cycles--;
|
||||
current_pixel_column_++;
|
||||
}
|
||||
while(number_of_cycles > 1)
|
||||
{
|
||||
while(number_of_cycles > 1) {
|
||||
get_pixel();
|
||||
*current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_];
|
||||
current_output_target_ += 1;
|
||||
@@ -239,8 +209,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles)
|
||||
number_of_cycles -= 2;
|
||||
current_pixel_column_+=2;
|
||||
}
|
||||
if(number_of_cycles)
|
||||
{
|
||||
if(number_of_cycles) {
|
||||
get_pixel();
|
||||
*current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_];
|
||||
current_output_target_ += 1;
|
||||
@@ -254,21 +223,17 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles)
|
||||
}
|
||||
}
|
||||
|
||||
void VideoOutput::run_for_cycles(int number_of_cycles)
|
||||
{
|
||||
void VideoOutput::run_for_cycles(int number_of_cycles) {
|
||||
output_position_ = (output_position_ + number_of_cycles) % cycles_per_frame;
|
||||
while(number_of_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);
|
||||
|
||||
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)
|
||||
{
|
||||
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;
|
||||
@@ -283,10 +248,8 @@ void VideoOutput::run_for_cycles(int number_of_cycles)
|
||||
|
||||
#pragma mark - Register hub
|
||||
|
||||
void VideoOutput::set_register(int address, uint8_t value)
|
||||
{
|
||||
switch(address & 0xf)
|
||||
{
|
||||
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);
|
||||
if(!start_screen_address_) start_screen_address_ |= 0x8000;
|
||||
@@ -295,21 +258,18 @@ void VideoOutput::set_register(int address, uint8_t value)
|
||||
start_screen_address_ = (start_screen_address_ & 0x01ff) | (uint16_t)((value & 0x3f) << 9);
|
||||
if(!start_screen_address_) start_screen_address_ |= 0x8000;
|
||||
break;
|
||||
case 0x07:
|
||||
{
|
||||
case 0x07: {
|
||||
// update screen mode
|
||||
uint8_t new_screen_mode = (value >> 3)&7;
|
||||
if(new_screen_mode == 7) new_screen_mode = 4;
|
||||
if(new_screen_mode != screen_mode_)
|
||||
{
|
||||
if(new_screen_mode != screen_mode_) {
|
||||
screen_mode_ = new_screen_mode;
|
||||
setup_base_address();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0x08: case 0x09: case 0x0a: case 0x0b:
|
||||
case 0x0c: case 0x0d: case 0x0e: case 0x0f:
|
||||
{
|
||||
case 0x0c: case 0x0d: case 0x0e: case 0x0f: {
|
||||
static const int registers[4][4] = {
|
||||
{10, 8, 2, 0},
|
||||
{14, 12, 6, 4},
|
||||
@@ -318,8 +278,7 @@ void VideoOutput::set_register(int address, uint8_t value)
|
||||
};
|
||||
const int index = (address >> 1)&3;
|
||||
const uint8_t colour = ~value;
|
||||
if(address&1)
|
||||
{
|
||||
if(address&1) {
|
||||
palette_[registers[index][0]] = (palette_[registers[index][0]]&3) | ((colour >> 1)&4);
|
||||
palette_[registers[index][1]] = (palette_[registers[index][1]]&3) | ((colour >> 0)&4);
|
||||
palette_[registers[index][2]] = (palette_[registers[index][2]]&3) | ((colour << 1)&4);
|
||||
@@ -327,9 +286,7 @@ void VideoOutput::set_register(int address, uint8_t value)
|
||||
|
||||
palette_[registers[index][2]] = (palette_[registers[index][2]]&5) | ((colour >> 4)&2);
|
||||
palette_[registers[index][3]] = (palette_[registers[index][3]]&5) | ((colour >> 3)&2);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
palette_[registers[index][0]] = (palette_[registers[index][0]]&6) | ((colour >> 7)&1);
|
||||
palette_[registers[index][1]] = (palette_[registers[index][1]]&6) | ((colour >> 6)&1);
|
||||
palette_[registers[index][2]] = (palette_[registers[index][2]]&6) | ((colour >> 5)&1);
|
||||
@@ -341,8 +298,7 @@ void VideoOutput::set_register(int address, uint8_t value)
|
||||
|
||||
// regenerate all palette tables for now
|
||||
#define pack(a, b) (uint8_t)((a << 4) | (b))
|
||||
for(int byte = 0; byte < 256; byte++)
|
||||
{
|
||||
for(int byte = 0; byte < 256; byte++) {
|
||||
uint8_t *target = (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]);
|
||||
@@ -367,10 +323,8 @@ void VideoOutput::set_register(int address, uint8_t value)
|
||||
}
|
||||
}
|
||||
|
||||
void VideoOutput::setup_base_address()
|
||||
{
|
||||
switch(screen_mode_)
|
||||
{
|
||||
void VideoOutput::setup_base_address() {
|
||||
switch(screen_mode_) {
|
||||
case 0: case 1: case 2: screen_mode_base_address_ = 0x3000; break;
|
||||
case 3: screen_mode_base_address_ = 0x4000; break;
|
||||
case 4: case 5: screen_mode_base_address_ = 0x5800; break;
|
||||
@@ -380,33 +334,28 @@ void VideoOutput::setup_base_address()
|
||||
|
||||
#pragma mark - Interrupts
|
||||
|
||||
VideoOutput::Interrupt VideoOutput::get_next_interrupt()
|
||||
{
|
||||
VideoOutput::Interrupt VideoOutput::get_next_interrupt() {
|
||||
VideoOutput::Interrupt interrupt;
|
||||
|
||||
if(output_position_ < real_time_clock_interrupt_1)
|
||||
{
|
||||
if(output_position_ < real_time_clock_interrupt_1) {
|
||||
interrupt.cycles = real_time_clock_interrupt_1 - output_position_;
|
||||
interrupt.interrupt = RealTimeClock;
|
||||
return interrupt;
|
||||
}
|
||||
|
||||
if(output_position_ < display_end_interrupt_1)
|
||||
{
|
||||
if(output_position_ < display_end_interrupt_1) {
|
||||
interrupt.cycles = display_end_interrupt_1 - output_position_;
|
||||
interrupt.interrupt = DisplayEnd;
|
||||
return interrupt;
|
||||
}
|
||||
|
||||
if(output_position_ < real_time_clock_interrupt_2)
|
||||
{
|
||||
if(output_position_ < real_time_clock_interrupt_2) {
|
||||
interrupt.cycles = real_time_clock_interrupt_2 - output_position_;
|
||||
interrupt.interrupt = RealTimeClock;
|
||||
return interrupt;
|
||||
}
|
||||
|
||||
if(output_position_ < display_end_interrupt_2)
|
||||
{
|
||||
if(output_position_ < display_end_interrupt_2) {
|
||||
interrupt.cycles = display_end_interrupt_2 - output_position_;
|
||||
interrupt.interrupt = DisplayEnd;
|
||||
return interrupt;
|
||||
@@ -419,34 +368,28 @@ VideoOutput::Interrupt VideoOutput::get_next_interrupt()
|
||||
|
||||
#pragma mark - RAM timing and access information
|
||||
|
||||
unsigned int VideoOutput::get_cycles_until_next_ram_availability(int from_time)
|
||||
{
|
||||
unsigned int VideoOutput::get_cycles_until_next_ram_availability(int from_time) {
|
||||
unsigned int result = 0;
|
||||
int position = output_position_ + from_time;
|
||||
|
||||
result += 1 + (position&1);
|
||||
if(screen_mode_ < 4)
|
||||
{
|
||||
if(screen_mode_ < 4) {
|
||||
const int current_column = graphics_column(position + (position&1));
|
||||
int current_line = graphics_line(position);
|
||||
if(current_column < 80 && current_line < 256)
|
||||
{
|
||||
if(screen_mode_ == 3)
|
||||
{
|
||||
if(current_column < 80 && current_line < 256) {
|
||||
if(screen_mode_ == 3) {
|
||||
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);
|
||||
}
|
||||
else
|
||||
result += (unsigned int)(80 - current_column);
|
||||
else result += (unsigned int)(80 - current_column);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
VideoOutput::Range VideoOutput::get_memory_access_range()
|
||||
{
|
||||
VideoOutput::Range VideoOutput::get_memory_access_range() {
|
||||
// This can't be more specific than this without applying a lot more thought because of mixed modes:
|
||||
// suppose a program runs half the screen in an 80-column mode then switches to 40 columns. Then the
|
||||
// real end address will be at 128*80 + 128*40 after the original base, subject to wrapping that depends
|
||||
@@ -460,8 +403,7 @@ VideoOutput::Range VideoOutput::get_memory_access_range()
|
||||
|
||||
#pragma mark - The screen map
|
||||
|
||||
void VideoOutput::setup_screen_map()
|
||||
{
|
||||
void VideoOutput::setup_screen_map() {
|
||||
/*
|
||||
|
||||
Odd field: Even field:
|
||||
@@ -475,15 +417,11 @@ void VideoOutput::setup_screen_map()
|
||||
|-B-
|
||||
|
||||
*/
|
||||
for(int c = 0; c < 2; c++)
|
||||
{
|
||||
if(c&1)
|
||||
{
|
||||
for(int c = 0; c < 2; c++) {
|
||||
if(c&1) {
|
||||
screen_map_.emplace_back(DrawAction::Sync, (cycles_per_line * 5) >> 1);
|
||||
screen_map_.emplace_back(DrawAction::Blank, cycles_per_line >> 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
screen_map_.emplace_back(DrawAction::Blank, cycles_per_line >> 1);
|
||||
screen_map_.emplace_back(DrawAction::Sync, (cycles_per_line * 5) >> 1);
|
||||
}
|
||||
@@ -494,15 +432,13 @@ void VideoOutput::setup_screen_map()
|
||||
}
|
||||
}
|
||||
|
||||
void VideoOutput::emplace_blank_line()
|
||||
{
|
||||
void VideoOutput::emplace_blank_line() {
|
||||
screen_map_.emplace_back(DrawAction::Sync, 9);
|
||||
screen_map_.emplace_back(DrawAction::ColourBurst, 24 - 9);
|
||||
screen_map_.emplace_back(DrawAction::Blank, 128 - 24);
|
||||
}
|
||||
|
||||
void VideoOutput::emplace_pixel_line()
|
||||
{
|
||||
void VideoOutput::emplace_pixel_line() {
|
||||
// output format is:
|
||||
// 9 cycles: sync
|
||||
// ... to 24 cycles: colour burst
|
||||
|
||||
@@ -10,18 +10,15 @@
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
void Memory::Fuzz(uint8_t *buffer, size_t size)
|
||||
{
|
||||
void Memory::Fuzz(uint8_t *buffer, size_t size) {
|
||||
unsigned int divider = ((unsigned int)RAND_MAX + 1) / 256;
|
||||
unsigned int shift = 1, value = 1;
|
||||
while(value < divider)
|
||||
{
|
||||
while(value < divider) {
|
||||
value <<= 1;
|
||||
shift++;
|
||||
}
|
||||
|
||||
for(size_t c = 0; c < size; c++)
|
||||
{
|
||||
buffer[c] = (uint8_t)(rand() >> shift);
|
||||
for(size_t c = 0; c < size; c++) {
|
||||
buffer[c] = (uint8_t)(std::rand() >> shift);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,121 +19,99 @@ namespace {
|
||||
}
|
||||
|
||||
Microdisc::Microdisc() :
|
||||
irq_enable_(false),
|
||||
delegate_(nullptr),
|
||||
paging_flags_(BASICDisable),
|
||||
head_load_request_counter_(-1),
|
||||
WD1770(P1793),
|
||||
last_control_(0)
|
||||
{
|
||||
irq_enable_(false),
|
||||
delegate_(nullptr),
|
||||
paging_flags_(BASICDisable),
|
||||
head_load_request_counter_(-1),
|
||||
WD1770(P1793),
|
||||
last_control_(0) {
|
||||
set_control_register(last_control_, 0xff);
|
||||
}
|
||||
|
||||
void Microdisc::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive)
|
||||
{
|
||||
if(!drives_[drive])
|
||||
{
|
||||
void Microdisc::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||
if(!drives_[drive]) {
|
||||
drives_[drive].reset(new Storage::Disk::Drive);
|
||||
if(drive == selected_drive_) set_drive(drives_[drive]);
|
||||
}
|
||||
drives_[drive]->set_disk(disk);
|
||||
}
|
||||
|
||||
void Microdisc::set_control_register(uint8_t control)
|
||||
{
|
||||
void Microdisc::set_control_register(uint8_t control) {
|
||||
uint8_t changes = last_control_ ^ control;
|
||||
last_control_ = control;
|
||||
set_control_register(control, changes);
|
||||
}
|
||||
|
||||
void Microdisc::set_control_register(uint8_t control, uint8_t changes)
|
||||
{
|
||||
void Microdisc::set_control_register(uint8_t control, uint8_t changes) {
|
||||
// b2: data separator clock rate select (1 = double) [TODO]
|
||||
|
||||
// b65: drive select
|
||||
if((changes >> 5)&3)
|
||||
{
|
||||
if((changes >> 5)&3) {
|
||||
selected_drive_ = (control >> 5)&3;
|
||||
set_drive(drives_[selected_drive_]);
|
||||
}
|
||||
|
||||
// b4: side select
|
||||
if(changes & 0x10)
|
||||
{
|
||||
if(changes & 0x10) {
|
||||
unsigned int head = (control & 0x10) ? 1 : 0;
|
||||
for(int c = 0; c < 4; c++)
|
||||
{
|
||||
for(int c = 0; c < 4; c++) {
|
||||
if(drives_[c]) drives_[c]->set_head(head);
|
||||
}
|
||||
}
|
||||
|
||||
// b3: double density select (0 = double)
|
||||
if(changes & 0x08)
|
||||
{
|
||||
if(changes & 0x08) {
|
||||
set_is_double_density(!(control & 0x08));
|
||||
}
|
||||
|
||||
// b0: IRQ enable
|
||||
if(changes & 0x01)
|
||||
{
|
||||
if(changes & 0x01) {
|
||||
bool had_irq = get_interrupt_request_line();
|
||||
irq_enable_ = !!(control & 0x01);
|
||||
bool has_irq = get_interrupt_request_line();
|
||||
if(has_irq != had_irq && delegate_)
|
||||
{
|
||||
if(has_irq != had_irq && delegate_) {
|
||||
delegate_->wd1770_did_change_output(this);
|
||||
}
|
||||
}
|
||||
|
||||
// b7: EPROM select (0 = select)
|
||||
// b1: ROM disable (0 = disable)
|
||||
if(changes & 0x82)
|
||||
{
|
||||
if(changes & 0x82) {
|
||||
paging_flags_ = ((control & 0x02) ? 0 : BASICDisable) | ((control & 0x80) ? MicrodscDisable : 0);
|
||||
if(delegate_) delegate_->microdisc_did_change_paging_flags(this);
|
||||
}
|
||||
}
|
||||
|
||||
bool Microdisc::get_interrupt_request_line()
|
||||
{
|
||||
bool Microdisc::get_interrupt_request_line() {
|
||||
return irq_enable_ && WD1770::get_interrupt_request_line();
|
||||
}
|
||||
|
||||
uint8_t Microdisc::get_interrupt_request_register()
|
||||
{
|
||||
uint8_t Microdisc::get_interrupt_request_register() {
|
||||
return 0x7f | (WD1770::get_interrupt_request_line() ? 0x00 : 0x80);
|
||||
}
|
||||
|
||||
uint8_t Microdisc::get_data_request_register()
|
||||
{
|
||||
uint8_t Microdisc::get_data_request_register() {
|
||||
return 0x7f | (get_data_request_line() ? 0x00 : 0x80);
|
||||
}
|
||||
|
||||
void Microdisc::set_head_load_request(bool head_load)
|
||||
{
|
||||
void Microdisc::set_head_load_request(bool head_load) {
|
||||
set_motor_on(head_load);
|
||||
if(head_load)
|
||||
{
|
||||
if(head_load) {
|
||||
head_load_request_counter_ = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
head_load_request_counter_ = head_load_request_counter_target;
|
||||
set_head_loaded(head_load);
|
||||
}
|
||||
}
|
||||
|
||||
void Microdisc::run_for_cycles(unsigned int number_of_cycles)
|
||||
{
|
||||
if(head_load_request_counter_ < head_load_request_counter_target)
|
||||
{
|
||||
void Microdisc::run_for_cycles(unsigned int number_of_cycles) {
|
||||
if(head_load_request_counter_ < head_load_request_counter_target) {
|
||||
head_load_request_counter_ += number_of_cycles;
|
||||
if(head_load_request_counter_ >= head_load_request_counter_target) set_head_loaded(true);
|
||||
}
|
||||
WD::WD1770::run_for_cycles(number_of_cycles);
|
||||
}
|
||||
|
||||
bool Microdisc::get_drive_is_ready()
|
||||
{
|
||||
bool Microdisc::get_drive_is_ready() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -12,15 +12,14 @@
|
||||
using namespace Oric;
|
||||
|
||||
Machine::Machine() :
|
||||
cycles_since_video_update_(0),
|
||||
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)
|
||||
{
|
||||
cycles_since_video_update_(0),
|
||||
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) {
|
||||
set_clock_rate(1000000);
|
||||
via_.set_interrupt_delegate(this);
|
||||
via_.keyboard = keyboard_;
|
||||
@@ -29,43 +28,35 @@ Machine::Machine() :
|
||||
Memory::Fuzz(ram_, sizeof(ram_));
|
||||
}
|
||||
|
||||
void Machine::configure_as_target(const StaticAnalyser::Target &target)
|
||||
{
|
||||
if(target.tapes.size())
|
||||
{
|
||||
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
|
||||
if(target.tapes.size()) {
|
||||
via_.tape->set_tape(target.tapes.front());
|
||||
}
|
||||
|
||||
if(target.loadingCommand.length()) // TODO: and automatic loading option enabled
|
||||
{
|
||||
if(target.loadingCommand.length()) { // TODO: and automatic loading option enabled
|
||||
set_typer_for_string(target.loadingCommand.c_str());
|
||||
}
|
||||
|
||||
if(target.oric.has_microdisc)
|
||||
{
|
||||
if(target.oric.has_microdisc) {
|
||||
microdisc_is_enabled_ = true;
|
||||
microdisc_did_change_paging_flags(µdisc_);
|
||||
microdisc_.set_delegate(this);
|
||||
}
|
||||
|
||||
int drive_index = 0;
|
||||
for(auto disk : target.disks)
|
||||
{
|
||||
for(auto disk : target.disks) {
|
||||
if(drive_index < 4) microdisc_.set_disk(disk, drive_index);
|
||||
drive_index++;
|
||||
}
|
||||
|
||||
if(target.oric.use_atmos_rom)
|
||||
{
|
||||
if(target.oric.use_atmos_rom) {
|
||||
memcpy(rom_, basic11_rom_.data(), std::min(basic11_rom_.size(), sizeof(rom_)));
|
||||
|
||||
is_using_basic11_ = true;
|
||||
tape_get_byte_address_ = 0xe6c9;
|
||||
scan_keyboard_address_ = 0xf495;
|
||||
tape_speed_address_ = 0x024d;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
memcpy(rom_, basic10_rom_.data(), std::min(basic10_rom_.size(), sizeof(rom_)));
|
||||
|
||||
is_using_basic11_ = false;
|
||||
@@ -75,10 +66,8 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target)
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::set_rom(ROM rom, const std::vector<uint8_t> &data)
|
||||
{
|
||||
switch(rom)
|
||||
{
|
||||
void Machine::set_rom(ROM rom, const std::vector<uint8_t> &data) {
|
||||
switch(rom) {
|
||||
case BASIC11: basic11_rom_ = std::move(data); break;
|
||||
case BASIC10: basic10_rom_ = std::move(data); break;
|
||||
case Microdisc: microdisc_rom_ = std::move(data); break;
|
||||
@@ -89,30 +78,22 @@ void Machine::set_rom(ROM rom, const std::vector<uint8_t> &data)
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value)
|
||||
{
|
||||
if(address > ram_top_)
|
||||
{
|
||||
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
if(address > ram_top_) {
|
||||
if(isReadOperation(operation)) *value = paged_rom_[address - ram_top_ - 1];
|
||||
|
||||
// 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 == CPU6502::BusOperation::ReadOpcode && via_.tape->has_tape() && !via_.tape->get_tape()->is_at_end())
|
||||
{
|
||||
if(address == tape_get_byte_address_ && paged_rom_ == rom_ && use_fast_tape_hack_ && operation == CPU6502::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_]);
|
||||
set_value_of_register(CPU6502::A, next_byte);
|
||||
set_value_of_register(CPU6502::Flags, next_byte ? 0 : CPU6502::Flag::Zero);
|
||||
*value = 0x60; // i.e. RTS
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if((address & 0xff00) == 0x0300)
|
||||
{
|
||||
if(microdisc_is_enabled_ && address >= 0x0310)
|
||||
{
|
||||
switch(address)
|
||||
{
|
||||
} else {
|
||||
if((address & 0xff00) == 0x0300) {
|
||||
if(microdisc_is_enabled_ && address >= 0x0310) {
|
||||
switch(address) {
|
||||
case 0x0310: case 0x0311: case 0x0312: case 0x0313:
|
||||
if(isReadOperation(operation)) *value = microdisc_.get_register(address);
|
||||
else microdisc_.set_register(address, *value);
|
||||
@@ -125,32 +106,25 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
if(isReadOperation(operation)) *value = microdisc_.get_data_request_register();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
if(isReadOperation(operation)) *value = via_.get_register(address);
|
||||
else via_.set_register(address, *value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
if(isReadOperation(operation))
|
||||
*value = ram_[address];
|
||||
else
|
||||
{
|
||||
else {
|
||||
if(address >= 0x9800 && address <= 0xc000) { update_video(); typer_delay_ = 0; }
|
||||
ram_[address] = *value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(typer_ && address == scan_keyboard_address_ && operation == CPU6502::BusOperation::ReadOpcode)
|
||||
{
|
||||
if(typer_ && address == scan_keyboard_address_ && operation == CPU6502::BusOperation::ReadOpcode) {
|
||||
// the Oric 1 misses any key pressed on the very first entry into the read keyboard routine, so don't
|
||||
// do anything until at least the second, regardless of machine
|
||||
if(!keyboard_read_count_) keyboard_read_count_++;
|
||||
else if(!typer_->type_next_character())
|
||||
{
|
||||
else if(!typer_->type_next_character()) {
|
||||
clear_all_keys();
|
||||
typer_.reset();
|
||||
}
|
||||
@@ -162,45 +136,36 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
return 1;
|
||||
}
|
||||
|
||||
void Machine::synchronise()
|
||||
{
|
||||
void Machine::synchronise() {
|
||||
update_video();
|
||||
via_.synchronise();
|
||||
}
|
||||
|
||||
void Machine::update_video()
|
||||
{
|
||||
void Machine::update_video() {
|
||||
video_output_->run_for_cycles(cycles_since_video_update_);
|
||||
cycles_since_video_update_ = 0;
|
||||
}
|
||||
|
||||
void Machine::setup_output(float aspect_ratio)
|
||||
{
|
||||
void Machine::setup_output(float aspect_ratio) {
|
||||
via_.ay8910.reset(new GI::AY38910());
|
||||
via_.ay8910->set_clock_rate(1000000);
|
||||
video_output_.reset(new VideoOutput(ram_));
|
||||
if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_);
|
||||
}
|
||||
|
||||
void Machine::close_output()
|
||||
{
|
||||
void Machine::close_output() {
|
||||
video_output_.reset();
|
||||
via_.ay8910.reset();
|
||||
}
|
||||
|
||||
void Machine::mos6522_did_change_interrupt_status(void *mos6522)
|
||||
{
|
||||
void Machine::mos6522_did_change_interrupt_status(void *mos6522) {
|
||||
set_interrupt_line();
|
||||
}
|
||||
|
||||
void Machine::set_key_state(uint16_t key, bool isPressed)
|
||||
{
|
||||
if(key == KeyNMI)
|
||||
{
|
||||
void Machine::set_key_state(uint16_t key, bool isPressed) {
|
||||
if(key == KeyNMI) {
|
||||
set_nmi_line(isPressed);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
if(isPressed)
|
||||
keyboard_->rows[key >> 8] |= (key & 0xff);
|
||||
else
|
||||
@@ -208,100 +173,80 @@ void Machine::set_key_state(uint16_t key, bool isPressed)
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::clear_all_keys()
|
||||
{
|
||||
void Machine::clear_all_keys() {
|
||||
memset(keyboard_->rows, 0, sizeof(keyboard_->rows));
|
||||
}
|
||||
|
||||
void Machine::set_use_fast_tape_hack(bool activate)
|
||||
{
|
||||
void Machine::set_use_fast_tape_hack(bool activate) {
|
||||
use_fast_tape_hack_ = activate;
|
||||
}
|
||||
|
||||
void Machine::set_output_device(Outputs::CRT::OutputDevice output_device)
|
||||
{
|
||||
void Machine::set_output_device(Outputs::CRT::OutputDevice output_device) {
|
||||
video_output_->set_output_device(output_device);
|
||||
}
|
||||
|
||||
void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player)
|
||||
{
|
||||
void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player) {
|
||||
// set CB1
|
||||
via_.set_control_line_input(VIA::Port::B, VIA::Line::One, tape_player->get_input());
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> Machine::get_crt()
|
||||
{
|
||||
std::shared_ptr<Outputs::CRT::CRT> Machine::get_crt() {
|
||||
return video_output_->get_crt();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::Speaker> Machine::get_speaker()
|
||||
{
|
||||
std::shared_ptr<Outputs::Speaker> Machine::get_speaker() {
|
||||
return via_.ay8910;
|
||||
}
|
||||
|
||||
void Machine::run_for_cycles(int number_of_cycles)
|
||||
{
|
||||
void Machine::run_for_cycles(int number_of_cycles) {
|
||||
CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles);
|
||||
}
|
||||
|
||||
#pragma mark - The 6522
|
||||
|
||||
Machine::VIA::VIA() :
|
||||
MOS::MOS6522<Machine::VIA>(),
|
||||
cycles_since_ay_update_(0),
|
||||
tape(new TapePlayer) {}
|
||||
MOS::MOS6522<Machine::VIA>(),
|
||||
cycles_since_ay_update_(0),
|
||||
tape(new TapePlayer) {}
|
||||
|
||||
void Machine::VIA::set_control_line_output(Port port, Line line, bool value)
|
||||
{
|
||||
if(line)
|
||||
{
|
||||
void Machine::VIA::set_control_line_output(Port port, Line line, bool value) {
|
||||
if(line) {
|
||||
if(port) ay_bdir_ = value; else ay_bc1_ = value;
|
||||
update_ay();
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::VIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask)
|
||||
{
|
||||
if(port)
|
||||
{
|
||||
void Machine::VIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask) {
|
||||
if(port) {
|
||||
keyboard->row = value;
|
||||
tape->set_motor_control(value & 0x40);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
ay8910->set_data_input(value);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Machine::VIA::get_port_input(Port port)
|
||||
{
|
||||
if(port)
|
||||
{
|
||||
uint8_t Machine::VIA::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
|
||||
{
|
||||
} else {
|
||||
return ay8910->get_data_output();
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::VIA::synchronise()
|
||||
{
|
||||
void Machine::VIA::synchronise() {
|
||||
ay8910->run_for_cycles(cycles_since_ay_update_);
|
||||
ay8910->flush();
|
||||
cycles_since_ay_update_ = 0;
|
||||
}
|
||||
|
||||
void Machine::VIA::run_for_cycles(unsigned int number_of_cycles)
|
||||
{
|
||||
void Machine::VIA::run_for_cycles(unsigned int number_of_cycles) {
|
||||
cycles_since_ay_update_ += number_of_cycles;
|
||||
MOS::MOS6522<VIA>::run_for_cycles(number_of_cycles);
|
||||
tape->run_for_cycles((int)number_of_cycles);
|
||||
}
|
||||
|
||||
void Machine::VIA::update_ay()
|
||||
{
|
||||
void Machine::VIA::update_ay() {
|
||||
ay8910->run_for_cycles(cycles_since_ay_update_);
|
||||
cycles_since_ay_update_ = 0;
|
||||
ay8910->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BCDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2));
|
||||
@@ -310,45 +255,34 @@ void Machine::VIA::update_ay()
|
||||
#pragma mark - TapePlayer
|
||||
|
||||
Machine::TapePlayer::TapePlayer() :
|
||||
Storage::Tape::BinaryTapePlayer(1000000)
|
||||
{}
|
||||
Storage::Tape::BinaryTapePlayer(1000000) {}
|
||||
|
||||
uint8_t Machine::TapePlayer::get_next_byte(bool fast)
|
||||
{
|
||||
uint8_t Machine::TapePlayer::get_next_byte(bool fast) {
|
||||
return (uint8_t)parser_.get_next_byte(get_tape(), fast);
|
||||
}
|
||||
|
||||
#pragma mark - Microdisc
|
||||
|
||||
void Machine::microdisc_did_change_paging_flags(class Microdisc *microdisc)
|
||||
{
|
||||
void Machine::microdisc_did_change_paging_flags(class Microdisc *microdisc) {
|
||||
int flags = microdisc->get_paging_flags();
|
||||
if(!(flags&Microdisc::PagingFlags::BASICDisable))
|
||||
{
|
||||
if(!(flags&Microdisc::PagingFlags::BASICDisable)) {
|
||||
ram_top_ = 0xbfff;
|
||||
paged_rom_ = rom_;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(flags&Microdisc::PagingFlags::MicrodscDisable)
|
||||
{
|
||||
} else {
|
||||
if(flags&Microdisc::PagingFlags::MicrodscDisable) {
|
||||
ram_top_ = 0xffff;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
ram_top_ = 0xdfff;
|
||||
paged_rom_ = microdisc_rom_.data();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::wd1770_did_change_output(WD::WD1770 *wd1770)
|
||||
{
|
||||
void Machine::wd1770_did_change_output(WD::WD1770 *wd1770) {
|
||||
set_interrupt_line();
|
||||
}
|
||||
|
||||
void Machine::set_interrupt_line()
|
||||
{
|
||||
void Machine::set_interrupt_line() {
|
||||
set_irq_line(
|
||||
via_.get_interrupt_line() ||
|
||||
(microdisc_is_enabled_ && microdisc_.get_interrupt_request_line()));
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "Oric.hpp"
|
||||
|
||||
uint16_t *Oric::Machine::sequence_for_character(Utility::Typer *typer, char character)
|
||||
{
|
||||
uint16_t *Oric::Machine::sequence_for_character(Utility::Typer *typer, char character) {
|
||||
#define KEYS(...) {__VA_ARGS__, TerminateSequence}
|
||||
#define SHIFT(...) {KeyLeftShift, __VA_ARGS__, TerminateSequence}
|
||||
#define X {NotMapped}
|
||||
|
||||
@@ -20,14 +20,13 @@ namespace {
|
||||
}
|
||||
|
||||
VideoOutput::VideoOutput(uint8_t *memory) :
|
||||
ram_(memory),
|
||||
frame_counter_(0), counter_(0),
|
||||
is_graphics_mode_(false),
|
||||
character_set_base_address_(0xb400),
|
||||
v_sync_start_position_(PAL50VSyncStartPosition), v_sync_end_position_(PAL50VSyncEndPosition),
|
||||
counter_period_(PAL50Period), next_frame_is_sixty_hertz_(false),
|
||||
crt_(new Outputs::CRT::CRT(64*6, 6, Outputs::CRT::DisplayType::PAL50, 2))
|
||||
{
|
||||
ram_(memory),
|
||||
frame_counter_(0), counter_(0),
|
||||
is_graphics_mode_(false),
|
||||
character_set_base_address_(0xb400),
|
||||
v_sync_start_position_(PAL50VSyncStartPosition), v_sync_end_position_(PAL50VSyncEndPosition),
|
||||
counter_period_(PAL50Period), next_frame_is_sixty_hertz_(false),
|
||||
crt_(new Outputs::CRT::CRT(64*6, 6, Outputs::CRT::DisplayType::PAL50, 2)) {
|
||||
crt_->set_rgb_sampling_function(
|
||||
"vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)"
|
||||
"{"
|
||||
@@ -48,16 +47,13 @@ VideoOutput::VideoOutput(uint8_t *memory) :
|
||||
crt_->set_visible_area(crt_->get_rect_for_area(50, 224, 16 * 6, 40 * 6, 4.0f / 3.0f));
|
||||
}
|
||||
|
||||
void VideoOutput::set_output_device(Outputs::CRT::OutputDevice output_device)
|
||||
{
|
||||
void VideoOutput::set_output_device(Outputs::CRT::OutputDevice output_device) {
|
||||
output_device_ = output_device;
|
||||
crt_->set_output_device(output_device);
|
||||
}
|
||||
|
||||
void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom)
|
||||
{
|
||||
for(size_t c = 0; c < 8; c++)
|
||||
{
|
||||
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]);
|
||||
rom_value = (rom_value & 0xff00) | ((rom_value >> 4)&0x000f) | ((rom_value << 4)&0x00f0);
|
||||
@@ -66,52 +62,42 @@ void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom)
|
||||
|
||||
// check for big endianness and byte swap if required
|
||||
uint16_t test_value = 0x0001;
|
||||
if(*(uint8_t *)&test_value != 0x01)
|
||||
{
|
||||
for(size_t c = 0; c < 8; c++)
|
||||
{
|
||||
if(*(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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt()
|
||||
{
|
||||
std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt() {
|
||||
return crt_;
|
||||
}
|
||||
|
||||
void VideoOutput::run_for_cycles(int number_of_cycles)
|
||||
{
|
||||
void VideoOutput::run_for_cycles(int number_of_cycles) {
|
||||
// Vertical: 0–39: pixels; otherwise blank; 48–53 sync, 54–56 colour burst
|
||||
// Horizontal: 0–223: pixels; otherwise blank; 256–259 sync
|
||||
|
||||
#define clamp(action) \
|
||||
if(cycles_run_for <= number_of_cycles) { action; } else cycles_run_for = number_of_cycles;
|
||||
|
||||
while(number_of_cycles)
|
||||
{
|
||||
while(number_of_cycles) {
|
||||
int h_counter = counter_ & 63;
|
||||
int cycles_run_for = 0;
|
||||
|
||||
if(counter_ >= v_sync_start_position_ && counter_ < v_sync_end_position_)
|
||||
{
|
||||
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));
|
||||
}
|
||||
else if(counter_ < 224*64 && h_counter < 40)
|
||||
{
|
||||
} else if(counter_ < 224*64 && h_counter < 40) {
|
||||
// this is a pixel line
|
||||
if(!h_counter)
|
||||
{
|
||||
if(!h_counter) {
|
||||
ink_ = 0x7;
|
||||
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);
|
||||
|
||||
if(!counter_)
|
||||
{
|
||||
if(!counter_) {
|
||||
frame_counter_++;
|
||||
|
||||
v_sync_start_position_ = next_frame_is_sixty_hertz_ ? PAL60VSyncStartPosition : PAL50VSyncStartPosition;
|
||||
@@ -126,16 +112,12 @@ void VideoOutput::run_for_cycles(int number_of_cycles)
|
||||
int character_base_address = 0xbb80 + (counter_ >> 9) * 40;
|
||||
uint8_t blink_mask = (blink_text_ && (frame_counter_&32)) ? 0x00 : 0xff;
|
||||
|
||||
while(columns--)
|
||||
{
|
||||
while(columns--) {
|
||||
uint8_t pixels, control_byte;
|
||||
|
||||
if(is_graphics_mode_ && counter_ < 200*64)
|
||||
{
|
||||
if(is_graphics_mode_ && counter_ < 200*64) {
|
||||
control_byte = pixels = ram_[pixel_base_address + h_counter];
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
int address = character_base_address + h_counter;
|
||||
control_byte = ram_[address];
|
||||
int line = use_double_height_characters_ ? ((counter_ >> 7) & 7) : ((counter_ >> 6) & 7);
|
||||
@@ -145,18 +127,13 @@ void VideoOutput::run_for_cycles(int number_of_cycles)
|
||||
uint8_t inverse_mask = (control_byte & 0x80) ? 0x7 : 0x0;
|
||||
pixels &= blink_mask;
|
||||
|
||||
if(control_byte & 0x60)
|
||||
{
|
||||
if(pixel_target_)
|
||||
{
|
||||
if(control_byte & 0x60) {
|
||||
if(pixel_target_) {
|
||||
uint16_t colours[2];
|
||||
if(output_device_ == Outputs::CRT::Monitor)
|
||||
{
|
||||
if(output_device_ == Outputs::CRT::Monitor) {
|
||||
colours[0] = (uint8_t)(paper_ ^ inverse_mask);
|
||||
colours[1] = (uint8_t)(ink_ ^ inverse_mask);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
colours[0] = colour_forms_[paper_ ^ inverse_mask];
|
||||
colours[1] = colour_forms_[ink_ ^ inverse_mask];
|
||||
}
|
||||
@@ -167,11 +144,8 @@ void VideoOutput::run_for_cycles(int number_of_cycles)
|
||||
pixel_target_[4] = colours[(pixels >> 1)&1];
|
||||
pixel_target_[5] = colours[(pixels >> 0)&1];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch(control_byte & 0x1f)
|
||||
{
|
||||
} else {
|
||||
switch(control_byte & 0x1f) {
|
||||
case 0x00: ink_ = 0x0; break;
|
||||
case 0x01: ink_ = 0x4; break;
|
||||
case 0x02: ink_ = 0x2; break;
|
||||
@@ -206,8 +180,7 @@ void VideoOutput::run_for_cycles(int number_of_cycles)
|
||||
|
||||
default: break;
|
||||
}
|
||||
if(pixel_target_)
|
||||
{
|
||||
if(pixel_target_) {
|
||||
pixel_target_[0] = pixel_target_[1] =
|
||||
pixel_target_[2] = pixel_target_[3] =
|
||||
pixel_target_[4] = pixel_target_[5] =
|
||||
@@ -218,34 +191,24 @@ void VideoOutput::run_for_cycles(int number_of_cycles)
|
||||
h_counter++;
|
||||
}
|
||||
|
||||
if(h_counter == 40)
|
||||
{
|
||||
if(h_counter == 40) {
|
||||
crt_->output_data(40 * 6, 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// this is a blank line (or the equivalent part of a pixel line)
|
||||
if(h_counter < 48)
|
||||
{
|
||||
if(h_counter < 48) {
|
||||
cycles_run_for = 48 - h_counter;
|
||||
clamp(
|
||||
int period = (counter_ < 224*64) ? 8 : 48;
|
||||
crt_->output_blank((unsigned int)period * 6);
|
||||
);
|
||||
}
|
||||
else if(h_counter < 54)
|
||||
{
|
||||
} else if(h_counter < 54) {
|
||||
cycles_run_for = 54 - h_counter;
|
||||
clamp(crt_->output_sync(6 * 6));
|
||||
}
|
||||
else if(h_counter < 56)
|
||||
{
|
||||
} else if(h_counter < 56) {
|
||||
cycles_run_for = 56 - h_counter;
|
||||
clamp(crt_->output_default_colour_burst(2 * 6));
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
cycles_run_for = 64 - h_counter;
|
||||
clamp(crt_->output_blank(8 * 6));
|
||||
}
|
||||
@@ -256,8 +219,7 @@ void VideoOutput::run_for_cycles(int number_of_cycles)
|
||||
}
|
||||
}
|
||||
|
||||
void VideoOutput::set_character_set_base_address()
|
||||
{
|
||||
void VideoOutput::set_character_set_base_address() {
|
||||
if(is_graphics_mode_) character_set_base_address_ = use_alternative_character_set_ ? 0x9c00 : 0x9800;
|
||||
else character_set_base_address_ = use_alternative_character_set_ ? 0xb800 : 0xb400;
|
||||
}
|
||||
|
||||
@@ -12,76 +12,61 @@
|
||||
using namespace Utility;
|
||||
|
||||
Typer::Typer(const char *string, int delay, int frequency, Delegate *delegate) :
|
||||
counter_(-delay), frequency_(frequency), string_pointer_(0), delegate_(delegate), phase_(0)
|
||||
{
|
||||
counter_(-delay), frequency_(frequency), string_pointer_(0), delegate_(delegate), phase_(0) {
|
||||
size_t string_size = strlen(string) + 3;
|
||||
string_ = (char *)malloc(string_size);
|
||||
snprintf(string_, strlen(string) + 3, "%c%s%c", Typer::BeginString, string, Typer::EndString);
|
||||
}
|
||||
|
||||
void Typer::update(int duration)
|
||||
{
|
||||
if(string_)
|
||||
{
|
||||
if(counter_ < 0 && counter_ + duration >= 0)
|
||||
{
|
||||
if(!type_next_character())
|
||||
{
|
||||
void Typer::update(int duration) {
|
||||
if(string_) {
|
||||
if(counter_ < 0 && counter_ + duration >= 0) {
|
||||
if(!type_next_character()) {
|
||||
delegate_->typer_reset(this);
|
||||
}
|
||||
}
|
||||
|
||||
counter_ += duration;
|
||||
while(string_ && counter_ > frequency_)
|
||||
{
|
||||
while(string_ && counter_ > frequency_) {
|
||||
counter_ -= frequency_;
|
||||
if(!type_next_character())
|
||||
{
|
||||
if(!type_next_character()) {
|
||||
delegate_->typer_reset(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Typer::type_next_character()
|
||||
{
|
||||
bool Typer::type_next_character() {
|
||||
if(string_ == nullptr) return false;
|
||||
|
||||
if(delegate_->typer_set_next_character(this, string_[string_pointer_], phase_))
|
||||
{
|
||||
if(delegate_->typer_set_next_character(this, string_[string_pointer_], phase_)) {
|
||||
phase_ = 0;
|
||||
if(!string_[string_pointer_])
|
||||
{
|
||||
if(!string_[string_pointer_]) {
|
||||
free(string_);
|
||||
string_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
string_pointer_++;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
phase_++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Typer::~Typer()
|
||||
{
|
||||
Typer::~Typer() {
|
||||
free(string_);
|
||||
}
|
||||
|
||||
#pragma mark - Delegate
|
||||
|
||||
bool Typer::Delegate::typer_set_next_character(Utility::Typer *typer, char character, int phase)
|
||||
{
|
||||
bool Typer::Delegate::typer_set_next_character(Utility::Typer *typer, char character, int phase) {
|
||||
uint16_t *sequence = sequence_for_character(typer, character);
|
||||
if(!sequence) return true;
|
||||
|
||||
if(!phase) clear_all_keys();
|
||||
else
|
||||
{
|
||||
else {
|
||||
set_key_state(sequence[phase - 1], true);
|
||||
return sequence[phase] == Typer::Delegate::EndSequence;
|
||||
}
|
||||
@@ -89,7 +74,6 @@ bool Typer::Delegate::typer_set_next_character(Utility::Typer *typer, char chara
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t *Typer::Delegate::sequence_for_character(Typer *typer, char character)
|
||||
{
|
||||
uint16_t *Typer::Delegate::sequence_for_character(Typer *typer, char character) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -45,13 +45,11 @@ class Typer {
|
||||
|
||||
class TypeRecipient: public Typer::Delegate {
|
||||
public:
|
||||
void set_typer_for_string(const char *string)
|
||||
{
|
||||
void set_typer_for_string(const char *string) {
|
||||
typer_.reset(new Typer(string, get_typer_delay(), get_typer_frequency(), this));
|
||||
}
|
||||
|
||||
void typer_reset(Typer *typer)
|
||||
{
|
||||
void typer_reset(Typer *typer) {
|
||||
clear_all_keys();
|
||||
typer_.reset();
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
//
|
||||
// CRC.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/09/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "CRC.hpp"
|
||||
|
||||
using namespace NumberTheory;
|
||||
@@ -16,13 +16,10 @@ namespace NumberTheory {
|
||||
class CRC16 {
|
||||
public:
|
||||
CRC16(uint16_t polynomial, uint16_t reset_value) :
|
||||
reset_value_(reset_value), value_(reset_value)
|
||||
{
|
||||
for(int c = 0; c < 256; c++)
|
||||
{
|
||||
reset_value_(reset_value), value_(reset_value) {
|
||||
for(int c = 0; c < 256; c++) {
|
||||
uint16_t shift_value = (uint16_t)(c << 8);
|
||||
for(int b = 0; b < 8; b++)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B1D08051E0F7A1100763741 /* TimeTests.mm */; };
|
||||
4B1E85751D170228001EF87D /* Typer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85731D170228001EF87D /* Typer.cpp */; };
|
||||
4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85801D176468001EF87D /* 6532Tests.swift */; };
|
||||
4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2409531C45AB05004DA684 /* Speaker.cpp */; };
|
||||
4B1EDB451E39A0AC009D6819 /* chip.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B1EDB431E39A0AC009D6819 /* chip.png */; };
|
||||
4B2A332A1DB8544D002876E3 /* MemoryFuzzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A33281DB8544D002876E3 /* MemoryFuzzer.cpp */; };
|
||||
4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2A332B1DB86821002876E3 /* OricOptions.xib */; };
|
||||
4B2A332F1DB86869002876E3 /* OricOptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A332E1DB86869002876E3 /* OricOptionsPanel.swift */; };
|
||||
@@ -29,8 +29,10 @@
|
||||
4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */; };
|
||||
4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539C1D117D36003C6002 /* CSElectron.mm */; };
|
||||
4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539E1D117D36003C6002 /* CSVic20.mm */; };
|
||||
4B2AF8691E513FC20027EE29 /* TIATests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2AF8681E513FC20027EE29 /* TIATests.mm */; };
|
||||
4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */; };
|
||||
4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */; };
|
||||
4B2C45421E3C3896002A2389 /* cartridge.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B2C45411E3C3896002A2389 /* cartridge.png */; };
|
||||
4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; };
|
||||
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D9B1C3A070400138695 /* Electron.cpp */; };
|
||||
4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B30512B1D989E2200B4FED8 /* Drive.cpp */; };
|
||||
@@ -67,6 +69,9 @@
|
||||
4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; };
|
||||
4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */; };
|
||||
4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7913CA1DFCD80E00175A82 /* Video.cpp */; };
|
||||
4B79E4441E3AF38600141F11 /* cassette.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4411E3AF38600141F11 /* cassette.png */; };
|
||||
4B79E4451E3AF38600141F11 /* floppy35.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4421E3AF38600141F11 /* floppy35.png */; };
|
||||
4B79E4461E3AF38600141F11 /* floppy525.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4431E3AF38600141F11 /* floppy525.png */; };
|
||||
4B8805F01DCFC99C003085B1 /* Acorn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805EE1DCFC99C003085B1 /* Acorn.cpp */; };
|
||||
4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F21DCFD22A003085B1 /* Commodore.cpp */; };
|
||||
4B8805F71DCFF6C9003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F51DCFF6C9003085B1 /* Commodore.cpp */; };
|
||||
@@ -80,6 +85,8 @@
|
||||
4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */; };
|
||||
4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2261DA1DE2D0090D3CE /* NSBundle+DataResource.m */; };
|
||||
4B8FE2291DA1EDDF0090D3CE /* ElectronOptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2281DA1EDDF0090D3CE /* ElectronOptionsPanel.swift */; };
|
||||
4B924E991E74D22700B76AF1 /* AtariStaticAnalyserTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */; };
|
||||
4B9252CE1E74D28200B76AF1 /* Atari ROMs in Resources */ = {isa = PBXBuildFile; fileRef = 4B9252CD1E74D28200B76AF1 /* Atari ROMs */; };
|
||||
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */; };
|
||||
4B96F7221D75119A0058BB2D /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B96F7201D75119A0058BB2D /* Tape.cpp */; };
|
||||
4B9CCDA11DA279CA0098B625 /* Vic20OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9CCDA01DA279CA0098B625 /* Vic20OptionsPanel.swift */; };
|
||||
@@ -391,6 +398,7 @@
|
||||
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; };
|
||||
4BD69F941D98760000243FE1 /* AcornADF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD69F921D98760000243FE1 /* AcornADF.cpp */; };
|
||||
4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE77A2C1D84ADFB00BC3827 /* File.cpp */; };
|
||||
4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE7C9161E3D397100A5496D /* TIA.cpp */; };
|
||||
4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA525D1DF33323007E74F2 /* Tape.cpp */; };
|
||||
4BEA52631DF339D7007E74F2 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52611DF339D7007E74F2 /* Speaker.cpp */; };
|
||||
4BEA52661DF3472B007E74F2 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52641DF3472B007E74F2 /* Speaker.cpp */; };
|
||||
@@ -400,10 +408,8 @@
|
||||
4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; };
|
||||
4BF1354C1D6D2C300054B2EA /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF1354A1D6D2C300054B2EA /* StaticAnalyser.cpp */; };
|
||||
4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8295B1D8F048B001BAE39 /* MFM.cpp */; };
|
||||
4BF829601D8F3C87001BAE39 /* CRC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8295E1D8F3C87001BAE39 /* CRC.cpp */; };
|
||||
4BF829631D8F536B001BAE39 /* SSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829611D8F536B001BAE39 /* SSD.cpp */; };
|
||||
4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829641D8F732B001BAE39 /* Disk.cpp */; };
|
||||
4BF829691D8F7361001BAE39 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829671D8F7361001BAE39 /* File.cpp */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -447,7 +453,7 @@
|
||||
4B1E85741D170228001EF87D /* Typer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Typer.hpp; sourceTree = "<group>"; };
|
||||
4B1E857B1D174DEC001EF87D /* 6532.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6532.hpp; sourceTree = "<group>"; };
|
||||
4B1E85801D176468001EF87D /* 6532Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6532Tests.swift; sourceTree = "<group>"; };
|
||||
4B2409531C45AB05004DA684 /* Speaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Speaker.cpp; path = ../../Outputs/Speaker.cpp; sourceTree = "<group>"; };
|
||||
4B1EDB431E39A0AC009D6819 /* chip.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = chip.png; sourceTree = "<group>"; };
|
||||
4B2409541C45AB05004DA684 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Speaker.hpp; path = ../../Outputs/Speaker.hpp; sourceTree = "<group>"; };
|
||||
4B24095A1C45DF85004DA684 /* Stepper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Stepper.hpp; sourceTree = "<group>"; };
|
||||
4B2A33281DB8544D002876E3 /* MemoryFuzzer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryFuzzer.cpp; sourceTree = "<group>"; };
|
||||
@@ -467,10 +473,12 @@
|
||||
4B2A539C1D117D36003C6002 /* CSElectron.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSElectron.mm; sourceTree = "<group>"; };
|
||||
4B2A539D1D117D36003C6002 /* CSVic20.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSVic20.h; sourceTree = "<group>"; };
|
||||
4B2A539E1D117D36003C6002 /* CSVic20.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSVic20.mm; sourceTree = "<group>"; };
|
||||
4B2AF8681E513FC20027EE29 /* TIATests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TIATests.mm; sourceTree = "<group>"; };
|
||||
4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapePRG.cpp; sourceTree = "<group>"; };
|
||||
4B2BFC5E1D613E0200BA3AA9 /* TapePRG.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapePRG.hpp; sourceTree = "<group>"; };
|
||||
4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Oric/Video.cpp; sourceTree = "<group>"; };
|
||||
4B2BFDB11DAEF5FF001A68B8 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = Oric/Video.hpp; sourceTree = "<group>"; };
|
||||
4B2C45411E3C3896002A2389 /* cartridge.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cartridge.png; sourceTree = "<group>"; };
|
||||
4B2E2D971C3A06EC00138695 /* Atari2600.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Atari2600.cpp; sourceTree = "<group>"; };
|
||||
4B2E2D981C3A06EC00138695 /* Atari2600.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Atari2600.hpp; sourceTree = "<group>"; };
|
||||
4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Atari2600Inputs.h; sourceTree = "<group>"; };
|
||||
@@ -538,6 +546,9 @@
|
||||
4B6C73BC1D387AE500AFCFCA /* DiskController.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskController.hpp; sourceTree = "<group>"; };
|
||||
4B7913CA1DFCD80E00175A82 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Electron/Video.cpp; sourceTree = "<group>"; };
|
||||
4B7913CB1DFCD80E00175A82 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = Electron/Video.hpp; sourceTree = "<group>"; };
|
||||
4B79E4411E3AF38600141F11 /* cassette.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cassette.png; sourceTree = "<group>"; };
|
||||
4B79E4421E3AF38600141F11 /* floppy35.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = floppy35.png; sourceTree = "<group>"; };
|
||||
4B79E4431E3AF38600141F11 /* floppy525.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = floppy525.png; sourceTree = "<group>"; };
|
||||
4B8805EE1DCFC99C003085B1 /* Acorn.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Acorn.cpp; path = Parsers/Acorn.cpp; sourceTree = "<group>"; };
|
||||
4B8805EF1DCFC99C003085B1 /* Acorn.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Acorn.hpp; path = Parsers/Acorn.hpp; sourceTree = "<group>"; };
|
||||
4B8805F21DCFD22A003085B1 /* Commodore.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Commodore.cpp; path = Parsers/Commodore.cpp; sourceTree = "<group>"; };
|
||||
@@ -558,6 +569,8 @@
|
||||
4B8FE2251DA1DE2D0090D3CE /* NSBundle+DataResource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+DataResource.h"; sourceTree = "<group>"; };
|
||||
4B8FE2261DA1DE2D0090D3CE /* NSBundle+DataResource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+DataResource.m"; sourceTree = "<group>"; };
|
||||
4B8FE2281DA1EDDF0090D3CE /* ElectronOptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ElectronOptionsPanel.swift; sourceTree = "<group>"; };
|
||||
4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AtariStaticAnalyserTests.mm; sourceTree = "<group>"; };
|
||||
4B9252CD1E74D28200B76AF1 /* Atari ROMs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "Atari ROMs"; sourceTree = "<group>"; };
|
||||
4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6502TimingTests.swift; sourceTree = "<group>"; };
|
||||
4B96F7201D75119A0058BB2D /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = ../../StaticAnalyser/Acorn/Tape.cpp; sourceTree = "<group>"; };
|
||||
4B96F7211D75119A0058BB2D /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = ../../StaticAnalyser/Acorn/Tape.hpp; sourceTree = "<group>"; };
|
||||
@@ -913,6 +926,8 @@
|
||||
4BD69F931D98760000243FE1 /* AcornADF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AcornADF.hpp; sourceTree = "<group>"; };
|
||||
4BE77A2C1D84ADFB00BC3827 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Commodore/File.cpp; sourceTree = "<group>"; };
|
||||
4BE77A2D1D84ADFB00BC3827 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Commodore/File.hpp; sourceTree = "<group>"; };
|
||||
4BE7C9161E3D397100A5496D /* TIA.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TIA.cpp; sourceTree = "<group>"; };
|
||||
4BE7C9171E3D397100A5496D /* TIA.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TIA.hpp; sourceTree = "<group>"; };
|
||||
4BEA525D1DF33323007E74F2 /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = Electron/Tape.cpp; sourceTree = "<group>"; };
|
||||
4BEA525F1DF333D8007E74F2 /* Tape.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = Electron/Tape.hpp; sourceTree = "<group>"; };
|
||||
4BEA52601DF3343A007E74F2 /* Interrupts.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Interrupts.hpp; path = Electron/Interrupts.hpp; sourceTree = "<group>"; };
|
||||
@@ -921,6 +936,20 @@
|
||||
4BEA52641DF3472B007E74F2 /* Speaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Speaker.cpp; sourceTree = "<group>"; };
|
||||
4BEA52651DF3472B007E74F2 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Speaker.hpp; sourceTree = "<group>"; };
|
||||
4BEA52671DF34909007E74F2 /* PIA.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PIA.hpp; sourceTree = "<group>"; };
|
||||
4BEAC0811E7E0DF800EE56B2 /* Cartridge.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; };
|
||||
4BEAC0821E7E0DF800EE56B2 /* CartridgeActivisionStack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeActivisionStack.hpp; sourceTree = "<group>"; };
|
||||
4BEAC0831E7E0DF800EE56B2 /* CartridgeAtari16k.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeAtari16k.hpp; sourceTree = "<group>"; };
|
||||
4BEAC0841E7E0DF800EE56B2 /* CartridgeAtari32k.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeAtari32k.hpp; sourceTree = "<group>"; };
|
||||
4BEAC0851E7E0DF800EE56B2 /* CartridgeAtari8k.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeAtari8k.hpp; sourceTree = "<group>"; };
|
||||
4BEAC0861E7E0DF800EE56B2 /* CartridgeCBSRAMPlus.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeCBSRAMPlus.hpp; sourceTree = "<group>"; };
|
||||
4BEAC0871E7E0DF800EE56B2 /* CartridgeCommaVid.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeCommaVid.hpp; sourceTree = "<group>"; };
|
||||
4BEAC0881E7E0DF800EE56B2 /* CartridgeMegaBoy.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeMegaBoy.hpp; sourceTree = "<group>"; };
|
||||
4BEAC0891E7E0DF800EE56B2 /* CartridgeMNetwork.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeMNetwork.hpp; sourceTree = "<group>"; };
|
||||
4BEAC08A1E7E0DF800EE56B2 /* CartridgeParkerBros.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeParkerBros.hpp; sourceTree = "<group>"; };
|
||||
4BEAC08B1E7E0DF800EE56B2 /* CartridgeTigervision.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeTigervision.hpp; sourceTree = "<group>"; };
|
||||
4BEAC08C1E7E0DF800EE56B2 /* CartridgeUnpaged.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeUnpaged.hpp; sourceTree = "<group>"; };
|
||||
4BEAC08D1E7E0E1A00EE56B2 /* Bus.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Bus.hpp; sourceTree = "<group>"; };
|
||||
4BEAC08E1E7E110500EE56B2 /* CartridgePitfall2.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CartridgePitfall2.hpp; sourceTree = "<group>"; };
|
||||
4BEE0A6A1D72496600532C7B /* Cartridge.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Cartridge.cpp; sourceTree = "<group>"; };
|
||||
4BEE0A6B1D72496600532C7B /* Cartridge.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; };
|
||||
4BEE0A6D1D72496600532C7B /* PRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PRG.cpp; sourceTree = "<group>"; };
|
||||
@@ -932,13 +961,11 @@
|
||||
4BF1354B1D6D2C300054B2EA /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
4BF8295B1D8F048B001BAE39 /* MFM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MFM.cpp; path = Encodings/MFM.cpp; sourceTree = "<group>"; };
|
||||
4BF8295C1D8F048B001BAE39 /* MFM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = MFM.hpp; path = Encodings/MFM.hpp; sourceTree = "<group>"; };
|
||||
4BF8295E1D8F3C87001BAE39 /* CRC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CRC.cpp; path = ../../NumberTheory/CRC.cpp; sourceTree = "<group>"; };
|
||||
4BF8295F1D8F3C87001BAE39 /* CRC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRC.hpp; path = ../../NumberTheory/CRC.hpp; sourceTree = "<group>"; };
|
||||
4BF829611D8F536B001BAE39 /* SSD.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SSD.cpp; sourceTree = "<group>"; };
|
||||
4BF829621D8F536B001BAE39 /* SSD.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SSD.hpp; sourceTree = "<group>"; };
|
||||
4BF829641D8F732B001BAE39 /* Disk.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Disk.cpp; path = ../../StaticAnalyser/Acorn/Disk.cpp; sourceTree = "<group>"; };
|
||||
4BF829651D8F732B001BAE39 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Disk.hpp; path = ../../StaticAnalyser/Acorn/Disk.hpp; sourceTree = "<group>"; };
|
||||
4BF829671D8F7361001BAE39 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Acorn/File.cpp; sourceTree = "<group>"; };
|
||||
4BF829681D8F7361001BAE39 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Acorn/File.hpp; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@@ -995,6 +1022,7 @@
|
||||
4B1414631B588A1100E04248 /* Test Binaries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B9252CD1E74D28200B76AF1 /* Atari ROMs */,
|
||||
4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */,
|
||||
4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */,
|
||||
4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */,
|
||||
@@ -1011,6 +1039,18 @@
|
||||
path = 6532;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B1EDB411E39A0AC009D6819 /* Icons */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B2C45411E3C3896002A2389 /* cartridge.png */,
|
||||
4B79E4411E3AF38600141F11 /* cassette.png */,
|
||||
4B79E4421E3AF38600141F11 /* floppy35.png */,
|
||||
4B79E4431E3AF38600141F11 /* floppy525.png */,
|
||||
4B1EDB431E39A0AC009D6819 /* chip.png */,
|
||||
);
|
||||
path = Icons;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B2409591C45DF85004DA684 /* SignalProcessing */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1072,10 +1112,14 @@
|
||||
children = (
|
||||
4B2E2D971C3A06EC00138695 /* Atari2600.cpp */,
|
||||
4BEA52641DF3472B007E74F2 /* Speaker.cpp */,
|
||||
4BE7C9161E3D397100A5496D /* TIA.cpp */,
|
||||
4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */,
|
||||
4B2E2D981C3A06EC00138695 /* Atari2600.hpp */,
|
||||
4BEA52651DF3472B007E74F2 /* Speaker.hpp */,
|
||||
4BEAC08D1E7E0E1A00EE56B2 /* Bus.hpp */,
|
||||
4BEA52671DF34909007E74F2 /* PIA.hpp */,
|
||||
4BEA52651DF3472B007E74F2 /* Speaker.hpp */,
|
||||
4BE7C9171E3D397100A5496D /* TIA.hpp */,
|
||||
4BEAC0801E7E0DF800EE56B2 /* Cartridges */,
|
||||
);
|
||||
path = Atari2600;
|
||||
sourceTree = "<group>";
|
||||
@@ -1103,7 +1147,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B0CCC411C62D0B3001CAC5F /* CRT */,
|
||||
4B2409531C45AB05004DA684 /* Speaker.cpp */,
|
||||
4B2409541C45AB05004DA684 /* Speaker.hpp */,
|
||||
);
|
||||
name = Outputs;
|
||||
@@ -1619,7 +1662,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BB697C61D4B558F00248BDF /* Factors.hpp */,
|
||||
4BF8295E1D8F3C87001BAE39 /* CRC.cpp */,
|
||||
4BF8295F1D8F3C87001BAE39 /* CRC.hpp */,
|
||||
);
|
||||
name = NumberTheory;
|
||||
@@ -1693,10 +1735,12 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */,
|
||||
4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */,
|
||||
4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */,
|
||||
4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */,
|
||||
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */,
|
||||
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */,
|
||||
4B2AF8681E513FC20027EE29 /* TIATests.mm */,
|
||||
4B1D08051E0F7A1100763741 /* TimeTests.mm */,
|
||||
4BB73EB81B587A5100552FC2 /* Info.plist */,
|
||||
4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */,
|
||||
@@ -1868,7 +1912,6 @@
|
||||
children = (
|
||||
4BF829641D8F732B001BAE39 /* Disk.cpp */,
|
||||
4BF829651D8F732B001BAE39 /* Disk.hpp */,
|
||||
4BF829671D8F7361001BAE39 /* File.cpp */,
|
||||
4BF829681D8F7361001BAE39 /* File.hpp */,
|
||||
4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */,
|
||||
4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */,
|
||||
@@ -1899,11 +1942,32 @@
|
||||
4BE5F85A1C3E1C2500C43F01 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B1EDB411E39A0AC009D6819 /* Icons */,
|
||||
4BC9DF441D044FCA00F44158 /* ROMImages */,
|
||||
);
|
||||
path = Resources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BEAC0801E7E0DF800EE56B2 /* Cartridges */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BEAC0811E7E0DF800EE56B2 /* Cartridge.hpp */,
|
||||
4BEAC0821E7E0DF800EE56B2 /* CartridgeActivisionStack.hpp */,
|
||||
4BEAC0831E7E0DF800EE56B2 /* CartridgeAtari16k.hpp */,
|
||||
4BEAC0841E7E0DF800EE56B2 /* CartridgeAtari32k.hpp */,
|
||||
4BEAC0851E7E0DF800EE56B2 /* CartridgeAtari8k.hpp */,
|
||||
4BEAC0861E7E0DF800EE56B2 /* CartridgeCBSRAMPlus.hpp */,
|
||||
4BEAC0871E7E0DF800EE56B2 /* CartridgeCommaVid.hpp */,
|
||||
4BEAC0881E7E0DF800EE56B2 /* CartridgeMegaBoy.hpp */,
|
||||
4BEAC0891E7E0DF800EE56B2 /* CartridgeMNetwork.hpp */,
|
||||
4BEAC08A1E7E0DF800EE56B2 /* CartridgeParkerBros.hpp */,
|
||||
4BEAC08B1E7E0DF800EE56B2 /* CartridgeTigervision.hpp */,
|
||||
4BEAC08C1E7E0DF800EE56B2 /* CartridgeUnpaged.hpp */,
|
||||
4BEAC08E1E7E110500EE56B2 /* CartridgePitfall2.hpp */,
|
||||
);
|
||||
path = Cartridges;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BEE0A691D72496600532C7B /* Cartridge */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -2052,13 +2116,18 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
4B2C45421E3C3896002A2389 /* cartridge.png in Resources */,
|
||||
4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */,
|
||||
4B79E4451E3AF38600141F11 /* floppy35.png in Resources */,
|
||||
4B1EDB451E39A0AC009D6819 /* chip.png in Resources */,
|
||||
4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */,
|
||||
4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */,
|
||||
4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */,
|
||||
4B79E4441E3AF38600141F11 /* cassette.png in Resources */,
|
||||
4B8FE21E1DA19D5F0090D3CE /* Vic20Options.xib in Resources */,
|
||||
4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */,
|
||||
4B8FE21D1DA19D5F0090D3CE /* ElectronOptions.xib in Resources */,
|
||||
4B79E4461E3AF38600141F11 /* floppy525.png in Resources */,
|
||||
4BC9DF451D044FCA00F44158 /* ROMImages in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -2189,6 +2258,7 @@
|
||||
4BB299661B587D8400A49093 /* inszx in Resources */,
|
||||
4BB299101B587D8400A49093 /* asoz in Resources */,
|
||||
4BB2998B1B587D8400A49093 /* lseiy in Resources */,
|
||||
4B9252CE1E74D28200B76AF1 /* Atari ROMs in Resources */,
|
||||
4BB2997D1B587D8400A49093 /* ldxay in Resources */,
|
||||
4BB299D71B587D8400A49093 /* staax in Resources */,
|
||||
4BB2990C1B587D8400A49093 /* asoax in Resources */,
|
||||
@@ -2366,7 +2436,6 @@
|
||||
4BC8A62D1DCE60E000DAC693 /* Typer.cpp in Sources */,
|
||||
4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */,
|
||||
4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */,
|
||||
4BF829601D8F3C87001BAE39 /* CRC.cpp in Sources */,
|
||||
4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */,
|
||||
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
|
||||
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */,
|
||||
@@ -2389,7 +2458,6 @@
|
||||
4BC5E4921D7ED365008CF980 /* StaticAnalyser.cpp in Sources */,
|
||||
4BC830D11D6E7C690000A26F /* Tape.cpp in Sources */,
|
||||
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */,
|
||||
4BF829691D8F7361001BAE39 /* File.cpp in Sources */,
|
||||
4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */,
|
||||
4B3F1B461E0388D200DB26EE /* PCMPatchedTrack.cpp in Sources */,
|
||||
4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */,
|
||||
@@ -2399,13 +2467,13 @@
|
||||
4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */,
|
||||
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */,
|
||||
4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */,
|
||||
4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */,
|
||||
4BCF1FA81DADC5250039D2E7 /* CSOric.mm in Sources */,
|
||||
4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */,
|
||||
4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */,
|
||||
4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */,
|
||||
4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */,
|
||||
4B5A12571DD55862007A2231 /* Disassembler6502.cpp in Sources */,
|
||||
4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */,
|
||||
4B1E85751D170228001EF87D /* Typer.cpp in Sources */,
|
||||
4BF829631D8F536B001BAE39 /* SSD.cpp in Sources */,
|
||||
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */,
|
||||
@@ -2459,7 +2527,9 @@
|
||||
4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */,
|
||||
4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */,
|
||||
4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */,
|
||||
4B924E991E74D22700B76AF1 /* AtariStaticAnalyserTests.mm in Sources */,
|
||||
4B50730A1DDFCFDF00C48FBD /* ArrayBuilderTests.mm in Sources */,
|
||||
4B2AF8691E513FC20027EE29 /* TIATests.mm in Sources */,
|
||||
4B3BA0CE1D318B44005DD7A7 /* C1540Bridge.mm in Sources */,
|
||||
4B3BA0D11D318B44005DD7A7 /* TestMachine.mm in Sources */,
|
||||
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */,
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
|
||||
@@ -12,32 +12,48 @@
|
||||
#define AudioQueueBufferMaxLength 8192
|
||||
#define NumberOfStoredAudioQueueBuffer 16
|
||||
|
||||
static NSLock *CSAudioQueueDeallocLock;
|
||||
|
||||
/*!
|
||||
Holds a weak reference to a CSAudioQueue. Used to work around an apparent AudioQueue bug.
|
||||
See -[CSAudioQueue dealloc].
|
||||
*/
|
||||
@interface CSWeakAudioQueuePointer: NSObject
|
||||
@property(nonatomic, weak) CSAudioQueue *queue;
|
||||
@end
|
||||
|
||||
@implementation CSWeakAudioQueuePointer
|
||||
@end
|
||||
|
||||
@implementation CSAudioQueue
|
||||
{
|
||||
AudioQueueRef _audioQueue;
|
||||
|
||||
AudioQueueBufferRef _storedBuffers[NumberOfStoredAudioQueueBuffer];
|
||||
NSLock *_storedBuffersLock;
|
||||
CSWeakAudioQueuePointer *_weakPointer;
|
||||
}
|
||||
|
||||
#pragma mark - AudioQueue callbacks
|
||||
|
||||
- (void)audioQueue:(AudioQueueRef)theAudioQueue didCallbackWithBuffer:(AudioQueueBufferRef)buffer
|
||||
/*!
|
||||
@returns @c YES if the queue is running dry; @c NO otherwise.
|
||||
*/
|
||||
- (BOOL)audioQueue:(AudioQueueRef)theAudioQueue didCallbackWithBuffer:(AudioQueueBufferRef)buffer
|
||||
{
|
||||
[self.delegate audioQueueIsRunningDry:self];
|
||||
|
||||
@synchronized(self)
|
||||
[_storedBuffersLock lock];
|
||||
for(int c = 0; c < NumberOfStoredAudioQueueBuffer; c++)
|
||||
{
|
||||
for(int c = 0; c < NumberOfStoredAudioQueueBuffer; c++)
|
||||
if(!_storedBuffers[c] || buffer->mAudioDataBytesCapacity > _storedBuffers[c]->mAudioDataBytesCapacity)
|
||||
{
|
||||
if(!_storedBuffers[c] || buffer->mAudioDataBytesCapacity > _storedBuffers[c]->mAudioDataBytesCapacity)
|
||||
{
|
||||
if(_storedBuffers[c]) AudioQueueFreeBuffer(_audioQueue, _storedBuffers[c]);
|
||||
_storedBuffers[c] = buffer;
|
||||
return;
|
||||
}
|
||||
if(_storedBuffers[c]) AudioQueueFreeBuffer(_audioQueue, _storedBuffers[c]);
|
||||
_storedBuffers[c] = buffer;
|
||||
[_storedBuffersLock unlock];
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
[_storedBuffersLock unlock];
|
||||
AudioQueueFreeBuffer(_audioQueue, buffer);
|
||||
return YES;
|
||||
}
|
||||
|
||||
static void audioOutputCallback(
|
||||
@@ -45,7 +61,17 @@ static void audioOutputCallback(
|
||||
AudioQueueRef inAQ,
|
||||
AudioQueueBufferRef inBuffer)
|
||||
{
|
||||
[(__bridge CSAudioQueue *)inUserData audioQueue:inAQ didCallbackWithBuffer:inBuffer];
|
||||
// Pull the delegate call for audio queue running dry outside of the locked region, to allow non-deadlocking
|
||||
// lifecycle -dealloc events to result from it.
|
||||
if([CSAudioQueueDeallocLock tryLock])
|
||||
{
|
||||
CSAudioQueue *queue = ((__bridge CSWeakAudioQueuePointer *)inUserData).queue;
|
||||
BOOL isRunningDry = NO;
|
||||
isRunningDry = [queue audioQueue:inAQ didCallbackWithBuffer:inBuffer];
|
||||
id<CSAudioQueueDelegate> delegate = queue.delegate;
|
||||
[CSAudioQueueDeallocLock unlock];
|
||||
if(isRunningDry) [delegate audioQueueIsRunningDry:queue];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Standard object lifecycle
|
||||
@@ -56,6 +82,12 @@ static void audioOutputCallback(
|
||||
|
||||
if(self)
|
||||
{
|
||||
if(!CSAudioQueueDeallocLock)
|
||||
{
|
||||
CSAudioQueueDeallocLock = [[NSLock alloc] init];
|
||||
}
|
||||
_storedBuffersLock = [[NSLock alloc] init];
|
||||
|
||||
_samplingRate = samplingRate;
|
||||
|
||||
// determine preferred buffer sizes
|
||||
@@ -80,11 +112,13 @@ static void audioOutputCallback(
|
||||
|
||||
outputDescription.mReserved = 0;
|
||||
|
||||
// create an audio output queue along those lines
|
||||
// create an audio output queue along those lines; see -dealloc re: the CSWeakAudioQueuePointer
|
||||
_weakPointer = [[CSWeakAudioQueuePointer alloc] init];
|
||||
_weakPointer.queue = self;
|
||||
if(!AudioQueueNewOutput(
|
||||
&outputDescription,
|
||||
audioOutputCallback,
|
||||
(__bridge void *)(self),
|
||||
(__bridge void *)(_weakPointer),
|
||||
NULL,
|
||||
kCFRunLoopCommonModes,
|
||||
0,
|
||||
@@ -104,7 +138,31 @@ static void audioOutputCallback(
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if(_audioQueue) AudioQueueDispose(_audioQueue, NO);
|
||||
[CSAudioQueueDeallocLock lock];
|
||||
if(_audioQueue)
|
||||
{
|
||||
AudioQueueDispose(_audioQueue, true);
|
||||
_audioQueue = NULL;
|
||||
}
|
||||
[CSAudioQueueDeallocLock unlock];
|
||||
|
||||
// Yuck. Horrid hack happening here. At least under macOS v10.12, I am frequently seeing calls to
|
||||
// my registered audio callback (audioOutputCallback in this case) that occur **after** the call
|
||||
// to AudioQueueDispose above, even though the second parameter there asks for a synchronous shutdown.
|
||||
// So this appears to be a bug on Apple's side.
|
||||
//
|
||||
// Since the audio callback receives a void * pointer that identifies the class it should branch into,
|
||||
// it's therefore unsafe to pass 'self'. Instead I pass a CSWeakAudioQueuePointer which points to the actual
|
||||
// queue. The lifetime of that class is the lifetime of this instance plus 1 second, as effected by the
|
||||
// artificial dispatch_after below — it serves only to keep pointerSaviour alive for an extra second.
|
||||
//
|
||||
// Why a second? That's definitely quite a lot longer than any amount of audio that may be queued. So
|
||||
// probably safe. As and where Apple's audio queue works properly, CSAudioQueueDeallocLock should provide
|
||||
// absolute safety; elsewhere the CSWeakAudioQueuePointer provides probabilistic.
|
||||
CSWeakAudioQueuePointer *pointerSaviour = _weakPointer;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[pointerSaviour hash];
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - Audio enqueuer
|
||||
@@ -113,28 +171,28 @@ static void audioOutputCallback(
|
||||
{
|
||||
size_t bufferBytes = lengthInSamples * sizeof(int16_t);
|
||||
|
||||
@synchronized(self)
|
||||
[_storedBuffersLock lock];
|
||||
for(int c = 0; c < NumberOfStoredAudioQueueBuffer; c++)
|
||||
{
|
||||
for(int c = 0; c < NumberOfStoredAudioQueueBuffer; c++)
|
||||
if(_storedBuffers[c] && _storedBuffers[c]->mAudioDataBytesCapacity >= bufferBytes)
|
||||
{
|
||||
if(_storedBuffers[c] && _storedBuffers[c]->mAudioDataBytesCapacity >= bufferBytes)
|
||||
{
|
||||
memcpy(_storedBuffers[c]->mAudioData, buffer, bufferBytes);
|
||||
_storedBuffers[c]->mAudioDataByteSize = (UInt32)bufferBytes;
|
||||
memcpy(_storedBuffers[c]->mAudioData, buffer, bufferBytes);
|
||||
_storedBuffers[c]->mAudioDataByteSize = (UInt32)bufferBytes;
|
||||
|
||||
AudioQueueEnqueueBuffer(_audioQueue, _storedBuffers[c], 0, NULL);
|
||||
_storedBuffers[c] = NULL;
|
||||
return;
|
||||
}
|
||||
AudioQueueEnqueueBuffer(_audioQueue, _storedBuffers[c], 0, NULL);
|
||||
_storedBuffers[c] = NULL;
|
||||
[_storedBuffersLock unlock];
|
||||
return;
|
||||
}
|
||||
|
||||
AudioQueueBufferRef newBuffer;
|
||||
AudioQueueAllocateBuffer(_audioQueue, (UInt32)bufferBytes * 2, &newBuffer);
|
||||
memcpy(newBuffer->mAudioData, buffer, bufferBytes);
|
||||
newBuffer->mAudioDataByteSize = (UInt32)bufferBytes;
|
||||
|
||||
AudioQueueEnqueueBuffer(_audioQueue, newBuffer, 0, NULL);
|
||||
}
|
||||
[_storedBuffersLock unlock];
|
||||
|
||||
AudioQueueBufferRef newBuffer;
|
||||
AudioQueueAllocateBuffer(_audioQueue, (UInt32)bufferBytes * 2, &newBuffer);
|
||||
memcpy(newBuffer->mAudioData, buffer, bufferBytes);
|
||||
newBuffer->mAudioDataByteSize = (UInt32)bufferBytes;
|
||||
|
||||
AudioQueueEnqueueBuffer(_audioQueue, newBuffer, 0, NULL);
|
||||
}
|
||||
|
||||
#pragma mark - Sampling Rate getters
|
||||
|
||||
@@ -31,24 +31,14 @@ class MachineDocument:
|
||||
return NSSize(width: 4.0, height: 3.0)
|
||||
}
|
||||
|
||||
@IBOutlet weak var openGLView: CSOpenGLView! {
|
||||
didSet {
|
||||
openGLView.delegate = self
|
||||
openGLView.responderDelegate = self
|
||||
}
|
||||
}
|
||||
|
||||
@IBOutlet weak var openGLView: CSOpenGLView!
|
||||
@IBOutlet var optionsPanel: MachinePanel!
|
||||
@IBAction func showOptions(_ sender: AnyObject!) {
|
||||
optionsPanel?.setIsVisible(true)
|
||||
}
|
||||
|
||||
fileprivate var audioQueue: CSAudioQueue! = nil
|
||||
fileprivate lazy var bestEffortUpdater: CSBestEffortUpdater = {
|
||||
let updater = CSBestEffortUpdater()
|
||||
updater.delegate = self
|
||||
return updater
|
||||
}()
|
||||
fileprivate var bestEffortUpdater: CSBestEffortUpdater!
|
||||
|
||||
override var windowNibName: String? {
|
||||
return "MachineDocument"
|
||||
@@ -64,12 +54,22 @@ class MachineDocument:
|
||||
self.machine.setView(self.openGLView, aspectRatio: Float(displayAspectRatio.width / displayAspectRatio.height))
|
||||
})
|
||||
|
||||
setupClockRate()
|
||||
self.machine.delegate = self
|
||||
self.bestEffortUpdater = CSBestEffortUpdater()
|
||||
|
||||
// callbacks from the OpenGL may come on a different thread, immediately following the .delegate set;
|
||||
// hence the full setup of the best-effort updater prior to setting self as a delegate
|
||||
self.openGLView.delegate = self
|
||||
self.openGLView.responderDelegate = self
|
||||
|
||||
setupClockRate()
|
||||
self.optionsPanel?.establishStoredOptions()
|
||||
|
||||
// bring OpenGL view-holding window on top of the options panel
|
||||
self.openGLView.window!.makeKeyAndOrderFront(self)
|
||||
|
||||
// start accepting best effort updates
|
||||
self.bestEffortUpdater.delegate = self
|
||||
}
|
||||
|
||||
func machineDidChangeClockRate(_ machine: CSMachine!) {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<string>bin</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string></string>
|
||||
<string>cartridge</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Atari 2600 Cartridge</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
@@ -27,27 +27,13 @@
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>uef</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Electron/BBC Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array/>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>rom</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>chip</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ROM Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
@@ -65,6 +51,8 @@
|
||||
<string>uef</string>
|
||||
<string>uef.gz</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Electron/BBC UEF Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
@@ -79,6 +67,8 @@
|
||||
<array>
|
||||
<string>prg</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy525</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Commodore Program</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
@@ -93,6 +83,8 @@
|
||||
<array>
|
||||
<string>tap</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
@@ -107,6 +99,8 @@
|
||||
<array>
|
||||
<string>g64</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy525</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Commodore Disk</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
@@ -121,6 +115,8 @@
|
||||
<array>
|
||||
<string>d64</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy525</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Commodore 1540/1 Disk</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
@@ -139,6 +135,8 @@
|
||||
<string>adl</string>
|
||||
<string>adm</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy35</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Electron/BBC Disk Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
@@ -151,6 +149,8 @@
|
||||
<array>
|
||||
<string>dsk</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy35</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Disk Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
|
||||
@@ -11,42 +11,8 @@
|
||||
#include "Atari2600.hpp"
|
||||
#import "CSMachine+Subclassing.h"
|
||||
|
||||
@interface CSAtari2600 ()
|
||||
- (void)crt:(Outputs::CRT::CRT *)crt didEndBatchOfFrames:(unsigned int)numberOfFrames withUnexpectedVerticalSyncs:(unsigned int)numberOfUnexpectedSyncs;
|
||||
@end
|
||||
|
||||
struct CRTDelegate: public Outputs::CRT::Delegate {
|
||||
__weak CSAtari2600 *atari2600;
|
||||
void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs) {
|
||||
[atari2600 crt:crt didEndBatchOfFrames:number_of_frames withUnexpectedVerticalSyncs:number_of_unexpected_vertical_syncs];
|
||||
}
|
||||
};
|
||||
|
||||
@implementation CSAtari2600 {
|
||||
Atari2600::Machine _atari2600;
|
||||
CRTDelegate _crtDelegate;
|
||||
|
||||
int _frameCount;
|
||||
int _hitCount;
|
||||
BOOL _didDecideRegion;
|
||||
int _batchesReceived;
|
||||
}
|
||||
|
||||
- (void)crt:(Outputs::CRT::CRT *)crt didEndBatchOfFrames:(unsigned int)numberOfFrames withUnexpectedVerticalSyncs:(unsigned int)numberOfUnexpectedSyncs {
|
||||
if(!_didDecideRegion)
|
||||
{
|
||||
_batchesReceived++;
|
||||
if(_batchesReceived == 2)
|
||||
{
|
||||
_didDecideRegion = YES;
|
||||
if(numberOfUnexpectedSyncs >= numberOfFrames >> 1)
|
||||
{
|
||||
[self.view performWithGLContext:^{
|
||||
_atari2600.switch_region();
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setDirection:(CSJoystickDirection)direction onPad:(NSUInteger)pad isPressed:(BOOL)isPressed {
|
||||
@@ -78,8 +44,6 @@ struct CRTDelegate: public Outputs::CRT::Delegate {
|
||||
- (void)setupOutputWithAspectRatio:(float)aspectRatio {
|
||||
@synchronized(self) {
|
||||
[super setupOutputWithAspectRatio:aspectRatio];
|
||||
_atari2600.get_crt()->set_delegate(&_crtDelegate);
|
||||
_crtDelegate.atari2600 = self;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BIN
OSBindings/Mac/Clock Signal/Resources/Icons/cartridge.png
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
OSBindings/Mac/Clock Signal/Resources/Icons/cassette.png
Normal file
|
After Width: | Height: | Size: 194 KiB |
BIN
OSBindings/Mac/Clock Signal/Resources/Icons/chip.png
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
OSBindings/Mac/Clock Signal/Resources/Icons/floppy35.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
OSBindings/Mac/Clock Signal/Resources/Icons/floppy525.png
Normal file
|
After Width: | Height: | Size: 126 KiB |
@@ -53,7 +53,7 @@
|
||||
*/
|
||||
@interface CSOpenGLView : NSOpenGLView
|
||||
|
||||
@property (nonatomic, weak, nullable) id <CSOpenGLViewDelegate> delegate;
|
||||
@property (atomic, weak, nullable) id <CSOpenGLViewDelegate> delegate;
|
||||
@property (nonatomic, weak, nullable) id <CSOpenGLViewResponderDelegate> responderDelegate;
|
||||
|
||||
/*!
|
||||
|
||||
@@ -21,14 +21,25 @@ class MOS6532Tests: XCTestCase {
|
||||
with6532 {
|
||||
// set a count of 128 at single-clock intervals
|
||||
$0.setValue(128, forRegister:0x14)
|
||||
XCTAssertEqual($0.value(forRegister: 4), 128)
|
||||
|
||||
// run for one clock and the count should now be 127
|
||||
// run for one more clock and the count should now be 127
|
||||
$0.run(forCycles: 1)
|
||||
XCTAssert($0.value(forRegister: 4) == 127, "A single tick should decrease the counter once")
|
||||
XCTAssertEqual($0.value(forRegister: 4), 127)
|
||||
|
||||
// run for a further 200 clock counts; timer should reach -73 = 183
|
||||
$0.run(forCycles: 200)
|
||||
XCTAssert($0.value(forRegister: 4) == 183, "Timer should underflow and keep counting")
|
||||
// run for 127 clocks and the timer should be zero, but the timer flag will not yet be set
|
||||
$0.run(forCycles: 127)
|
||||
XCTAssertEqual($0.value(forRegister: 5) & 0x80, 0)
|
||||
XCTAssertEqual($0.value(forRegister: 4), 0)
|
||||
|
||||
// after one more cycle the counter should be 255 and the timer flag will now be set
|
||||
$0.run(forCycles: 1)
|
||||
XCTAssertEqual($0.value(forRegister: 5) & 0x80, 0x80)
|
||||
XCTAssertEqual($0.value(forRegister: 4), 255)
|
||||
|
||||
// run for a further 55 clock counts; timer should reach -200
|
||||
$0.run(forCycles: 55)
|
||||
XCTAssertEqual($0.value(forRegister: 4), 200)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,26 +48,40 @@ class MOS6532Tests: XCTestCase {
|
||||
with6532 {
|
||||
// set a count of 28 at eight-clock intervals
|
||||
$0.setValue(28, forRegister:0x15)
|
||||
XCTAssertEqual($0.value(forRegister: 4), 28)
|
||||
|
||||
// run for seven clock and the count should still be 28
|
||||
$0.run(forCycles: 7)
|
||||
XCTAssert($0.value(forRegister: 4) == 28, "The timer should remain unchanged for seven clocks")
|
||||
|
||||
// run for a further clock and the count should now be 27
|
||||
// one further cycle and the timer should hit 27
|
||||
$0.run(forCycles: 1)
|
||||
XCTAssert($0.value(forRegister: 4) == 27, "The timer should have decremented once after 8 cycles")
|
||||
XCTAssertEqual($0.value(forRegister: 4), 27)
|
||||
|
||||
// run for a further 7 + 27*8 + 5 = 228 clock counts; timer should reach -5 = 0xfb
|
||||
$0.run(forCycles: 228)
|
||||
XCTAssert($0.value(forRegister: 4) == 0xfb, "Timer should underflow and start counting at single-clock pace")
|
||||
// run for seven clock and the count should still be 27
|
||||
$0.run(forCycles: 7)
|
||||
XCTAssertEqual($0.value(forRegister: 4), 27)
|
||||
|
||||
// run for a further clock and the count should now be 26
|
||||
$0.run(forCycles: 1)
|
||||
XCTAssertEqual($0.value(forRegister: 4), 26)
|
||||
|
||||
// run for another 26 * 8 = 208 cycles and the count should hit zero without setting the timer flag, and
|
||||
// stay there for seven more cycles
|
||||
$0.run(forCycles: 208)
|
||||
for _ in 0 ..< 8 {
|
||||
XCTAssertEqual($0.value(forRegister: 5) & 0x80, 0)
|
||||
XCTAssertEqual($0.value(forRegister: 4), 0)
|
||||
$0.run(forCycles: 1)
|
||||
}
|
||||
|
||||
// run six more, and the timer should reach 249, with the interrupt flag set
|
||||
$0.run(forCycles: 6)
|
||||
XCTAssertEqual($0.value(forRegister: 5) & 0x80, 0x80)
|
||||
XCTAssertEqual($0.value(forRegister: 4), 249)
|
||||
|
||||
// timer should now resume dividing by eight
|
||||
$0.run(forCycles: 7)
|
||||
XCTAssert($0.value(forRegister: 4) == 0xfb, "Timer should remain unchanged for seven cycles")
|
||||
XCTAssertEqual($0.value(forRegister: 4), 249)
|
||||
|
||||
// timer should now resume dividing by eight
|
||||
$0.run(forCycles: 1)
|
||||
XCTAssert($0.value(forRegister: 4) == 0xfa, "Timer should decrement after eighth cycle")
|
||||
XCTAssertEqual($0.value(forRegister: 4), 248)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,8 +89,6 @@ class MOS6532Tests: XCTestCase {
|
||||
with6532 {
|
||||
// set a count of 1 at single-clock intervals
|
||||
$0.setValue(1, forRegister:0x1c)
|
||||
|
||||
// run for one clock and the count should now be zero
|
||||
$0.run(forCycles: 1)
|
||||
|
||||
// interrupt shouldn't be signalled yet, bit should not be set
|
||||
|
||||
3
OSBindings/Mac/Clock SignalTests/Atari ROMs/readme.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
This folder is intended to contain commercial Atari ROM images; these are used to unit test the Atari static analyser.
|
||||
|
||||
Those tested are (i) everything presently available on AtariAge, and (ii) a selection of things from Pouët.
|
||||
614
OSBindings/Mac/Clock SignalTests/AtariStaticAnalyserTests.mm
Normal file
@@ -0,0 +1,614 @@
|
||||
//
|
||||
// AtariStaticAnalyserTests.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 11/03/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
#include "../../../StaticAnalyser/StaticAnalyser.hpp"
|
||||
|
||||
@interface AtariROMRecord : NSObject
|
||||
@property(nonatomic, readonly) StaticAnalyser::Atari2600PagingModel pagingModel;
|
||||
@property(nonatomic, readonly) BOOL usesSuperchip;
|
||||
+ (instancetype)recordWithPagingModel:(StaticAnalyser::Atari2600PagingModel)pagingModel usesSuperchip:(BOOL)usesSuperchip;
|
||||
@end
|
||||
|
||||
@implementation AtariROMRecord
|
||||
+ (instancetype)recordWithPagingModel:(StaticAnalyser::Atari2600PagingModel)pagingModel usesSuperchip:(BOOL)usesSuperchip
|
||||
{
|
||||
AtariROMRecord *record = [[AtariROMRecord alloc] init];
|
||||
record->_pagingModel = pagingModel;
|
||||
record->_usesSuperchip = usesSuperchip;
|
||||
return record;
|
||||
}
|
||||
@end
|
||||
|
||||
#define Record(sha, model, uses) sha : [AtariROMRecord recordWithPagingModel:StaticAnalyser::Atari2600PagingModel::model usesSuperchip:uses],
|
||||
static NSDictionary<NSString *, AtariROMRecord *> *romRecordsBySHA1 = @{
|
||||
Record(@"58dbcbdffbe80be97746e94a0a75614e64458fdc", None, NO) // 4kraVCS
|
||||
Record(@"9967a76efb68017f793188f691159f04e6bb4447", None, NO) // 'X'Mission
|
||||
Record(@"21d983f2f52b84c22ecae84b0943678ae2c31c10", None, NO) // 3d Tic-Tac-Toe
|
||||
Record(@"d7c62df8300a68b21ce672cfaa4d0f2f4b3d0ce1", Atari16k, NO) // Acid Drop
|
||||
Record(@"924ca836aa08eeffc141d487ac6b9b761b2f8ed5", None, NO) // Action Force
|
||||
Record(@"e07e48d463d30321239a8acc00c490f27f1f7422", None, NO) // Adventure
|
||||
Record(@"03a495c7bfa0671e24aa4d9460d232731f68cb43", None, NO) // Adventures of Tron
|
||||
Record(@"6e420544bf91f603639188824a2b570738bb7e02", None, NO) // Adventures On GX12
|
||||
Record(@"3b02e7dacb418c44d0d3dc77d60a9663b90b0fbc", None, NO) // Air Raid
|
||||
Record(@"29f5c73d1fe806a4284547274dd73f9972a7ed70", None, NO) // Air Raiders
|
||||
Record(@"af5b9f33ccb7778b42957da4f20f2bc000992366", None, NO) // Air-Sea Battle
|
||||
Record(@"0376c242819b785310b8af43c03b1d1156bd5f02", None, NO) // Airlock
|
||||
Record(@"fb870ec3d51468fa4cf40e0efae9617e60c1c91c", None, NO) // AKA Space Adventure
|
||||
Record(@"01d99bf307262825db58631e8002dd008a42cb1e", None, NO) // Alien
|
||||
Record(@"a1f660827ce291f19719a5672f2c5d277d903b03", Atari8k, NO) // Alpha Beam with Ernie
|
||||
Record(@"b89a5ac6593e83fbebee1fe7d4cec81a7032c544", None, NO) // Amidar
|
||||
Record(@"ac58ac94ceab78725a1182cc7b907376c011b0c8", None, NO) // Angriff der Luftflotten
|
||||
Record(@"7d132ab776ff755b86bf4f204165aa54e9e1f1cf", Atari8k, NO) // Aquaventure
|
||||
Record(@"9b6a54969240baf64928118741c3affee148d721", None, NO) // Armor Ambush
|
||||
Record(@"8c249e9eaa83fc6be16039f05ec304efdf987beb", Atari8k, NO) // Artillery Duel
|
||||
Record(@"0c03eba97df5178eec5d4d0aea4a6fe2f961c88f", None, NO) // Assault
|
||||
Record(@"1a094f92e46a8127d9c29889b5389865561c0a6f", Atari8k, NO) // Asterix (NTSC)
|
||||
Record(@"f14408429a911854ec76a191ad64231cc2ed7d11", Atari8k, NO) // Asterix (PAL)
|
||||
Record(@"8f4a00cb4ab6a6f809be0e055d97e8fe17f19e7d", None, NO) // Asteroid Fire
|
||||
Record(@"8423f99092b454aed89f89f5d7da658caf7af016", Atari8k, NO) // Asteroids
|
||||
Record(@"b850bd72d18906d9684e1c7251cb699588cbcf64", None, NO) // Astroblast
|
||||
Record(@"d1563c24208766cf8d28de7af995021a9f89d7e1", None, NO) // Atari Video Cube
|
||||
Record(@"f4e838de9159c149ac080ab85e4f830d5b299963", None, NO) // Atlantis II
|
||||
Record(@"c6b1dcdb2f024ab682316db45763bacc6949c33c", None, NO) // Atlantis
|
||||
Record(@"75e7efa861f7e7d8e367c09bf7c0cc351b472f03", None, NO) // Bachelor Party
|
||||
Record(@"b88ca823aaa10a7a4a3d325023881b2de969c156", None, NO) // Bachelorette Party
|
||||
Record(@"9b1da7fbd0bf6fcadf1b60c11eeb31b6a61a03c3", None, NO) // Backgammon
|
||||
Record(@"80d4020575b14e130f28146bf45921e001f9f649", None, NO) // Bank Heist
|
||||
Record(@"372663097b419ced64f44ef743fe8d0af4317f46", None, NO) // Barnstorming
|
||||
Record(@"d0bdd609ebc6e69fb351ba469ff322406bcbab50", None, NO) // Base Attack
|
||||
Record(@"6c56fad688b2e9bb783f8a5a2360c80ad2338e47", None, NO) // Basic Programming
|
||||
Record(@"bffe99454cb055552e5d612f0dba25470137328d", None, NO) // Basketball
|
||||
Record(@"e4134a3b4a065c856802bc935c12fa7e9868110a", Atari8k, NO) // Battlezone
|
||||
Record(@"47619edb352f7f955f811cbb03a00746c8e099b1", Atari8k, NO) // Beamrider
|
||||
Record(@"fad0c97331a525a4aeba67987552ba324629a7a0", None, NO) // Beany Bopper
|
||||
Record(@"e2c29d0a73a4575028b62dca745476a17f07c8f0", None, NO) // Beat 'Em & Eat 'Em
|
||||
Record(@"c3afd7909b72b49ca7d4485465b622d5e55f8913", Atari8k, NO) // Berenstain Bears
|
||||
Record(@"fcad0e5130de24f06b98fb86a7c3214841ca42e2", None, NO) // Bermuda Triangle
|
||||
Record(@"08bcbc8954473e8f0242b881315b0af4466998ae", None, NO) // Berzerk
|
||||
Record(@"5e4517db83c061926130ab65975e3b83d9401cc9", Atari8k, NO) // Big Bird's Egg Catch
|
||||
Record(@"512e4d047f1f813bc805c8d2a5f7cbdb34b9ea46", None, NO) // bin00016
|
||||
Record(@"f6a41507b8cf890ab7c59bb1424f0500534385ce", Atari8k, NO) // Bionic Breakthrough
|
||||
Record(@"edfd905a34870196f8acb2a9cd41f79f4326f88d", None, NO) // Blackjack
|
||||
Record(@"0fadef01ce28192880f745b23a5fbb64c5a96efe", Atari8k, NO) // Blueprint
|
||||
Record(@"ff25ed062dcc430448b358d2ac745787410e1169", Atari16k, NO) // BMX Air Master
|
||||
Record(@"50e26688fdd3eadcfa83240616267a8f60216c25", None, NO) // Bobby is Going Home
|
||||
Record(@"282cad17482f5f87805065d1a62e49e662d5b4bb", None, NO) // Bogey Blaster
|
||||
Record(@"d106bb41a38ed222dead608d839e8a3f0d0ecc18", None, NO) // Boing!
|
||||
Record(@"cf6ce244b3edaad7ad5e9ca5f01668135c2f93d0", None, NO) // Bowling
|
||||
Record(@"14b9cd91188c7fb0d4566442d639870f8d6f174d", None, NO) // Boxing
|
||||
Record(@"238915cafd26f69bc8a3b9aa7d880dde59f6f12d", None, NO) // Brain Games
|
||||
Record(@"8d473b87b70e26890268e6c417c0bb7f01e402eb", None, NO) // Breakout
|
||||
Record(@"2873eb6effd35003d13e2f8f997b76dbc85d0f64", None, NO) // Bridge
|
||||
Record(@"a65dea2d9790f3eb308c048a01566e35e8c24549", Atari8k, NO) // Buck Rogers — Planet of Zoom
|
||||
Record(@"9c0e13af336a986c271fe828fafdca250afba647", Atari8k, NO) // Bugs Bunny
|
||||
Record(@"67387d0d3d48a44800c44860bf15339a81f41aa9", None, NO) // Bugs
|
||||
Record(@"1819ef408c1216c83dcfeceec28d13f6ea5ca477", MNetwork, NO) // Bump 'n' Jump
|
||||
Record(@"6c199782c79686dc0cbce6d5fe805f276a86a3f5", None, NO) // Bumper Bash
|
||||
Record(@"49e01b8048ae344cb65838f6b1c1de0e1f416f29", MNetwork, NO) // BurgerTime
|
||||
Record(@"b233c37aa5164a54e2e7cc3dc621b331ddc6e55b", None, NO) // Burning Desire
|
||||
Record(@"3f1f17cf620f462355009f5302cddffa730fa2fa", None, NO) // Cakewalk
|
||||
Record(@"609c20365c3a71ce45cb277c66ec3ce6b2c50980", Atari16k, NO) // California Games
|
||||
Record(@"b89443a0029e765c2716774fe2582be37650115c", None, NO) // Canyon Bomber
|
||||
Record(@"e1acf7a845b56e4b3d18192a75a81c7afa6f341a", None, NO) // Carnival
|
||||
Record(@"54ed2864f58ef3768579ec96cca445ee62078521", None, NO) // cart
|
||||
Record(@"08598101e38756916613f37581ef1b61c719016f", None, NO) // Casino
|
||||
Record(@"e979de719cecab2115affd9c0552c6c596b1999a", None, NO) // Cat Trax
|
||||
Record(@"6adf70e0b7b5dab74cf4778f56000de7605e8713", None, NO) // Cathouse Blues
|
||||
Record(@"0b5914bc1526a9beaf54d7fd11408175cd8fcc72", Atari8k, NO) // Centipede
|
||||
Record(@"b2b1bd165b3c10cde5316ed0f9f05a509aac828d", None, NO) // Challenge (Zellers)
|
||||
Record(@"ac9b0c62ba0ca7a975d08fabbbc7c7448ecdf18d", None, NO) // Challenge of… Nexar
|
||||
Record(@"e81b5e49cfbb283edba2c8f21f31a8148d8645a1", None, NO) // Challenge
|
||||
Record(@"872b2f9aa7edbcbb2368de0db3696c90998ff016", None, NO) // Chase the Chuckwagon
|
||||
Record(@"39b5bb27a6c4cb6532bd9d4cc520415c59dac653", None, NO) // Checkers
|
||||
Record(@"0b1bb76769ae3f8b4936f0f95f4941d276791bde", None, NO) // China Syndrome
|
||||
Record(@"51a53bbfdbcc22925515ae0af79df434df6ee68a", None, NO) // Chopper Command
|
||||
Record(@"8a91ecdbd8bf9d412da051c3422abb004eab8603", None, NO) // Circus
|
||||
Record(@"3f56d1a376702b64b3992b2d5652a3842c56ffad", None, NO) // Coco Nuts
|
||||
Record(@"137bd3d3f36e2549c6e1cc3a60f2a7574f767775", None, NO) // Codebreaker
|
||||
Record(@"53c324ae736afa92a83d619b04e4fe72182281a6", None, NO) // Color Bar Generator
|
||||
Record(@"66014de1f8e9f39483ee3f97ca0d97d026ffc3bb", Atari8k, NO) // Combat Two
|
||||
Record(@"ce7580059e8b41cb4a1e734c9b35ce3774bf777a", None, NO) // Combat
|
||||
Record(@"8dad05085657e95e567f47836502be515b42f66b", None, NO) // Commando Raid
|
||||
Record(@"68a7cb3ff847cd987a551f3dd9cda5f90ce0a3bf", Atari16k, NO) // Commando
|
||||
Record(@"dbc0c0451dee44425810e04df8f1d26d1c2d3993", None, NO) // Computer Chess
|
||||
Record(@"5512a0ed4306edc007a78bb52dbcf492adf798ec", None, NO) // Confrontation
|
||||
Record(@"3a77db43b6583e8689435f0f14aa04b9e57bdded", Atari8k, NO) // Congo Bongo
|
||||
Record(@"f4a62ba0ff59803c5f40d59eeed1e126fe37979b", Atari8k, NO) // Cookie Monster Munch
|
||||
Record(@"187983fd14d37498437d0ef8f3fbd05675feb6ae", None, NO) // Cosmic Ark
|
||||
Record(@"3717c97bbb0f547e4389db8fc954d1bad992444c", None, NO) // Cosmic Commuter
|
||||
Record(@"8b9dfef6c6757a6a59e01d783b751e4ab9541d9e", None, NO) // Cosmic Corridor
|
||||
Record(@"22ff281b1e698e8a5d7a6f6173c86c46d3cd8561", None, NO) // Cosmic Creeps
|
||||
Record(@"c01354760f2ca8d6e4d01b230f31611973c6ae2d", None, NO) // Cosmic Swarm
|
||||
Record(@"6ee0a26af4643ff250198dfc1c2b7c6568b4f207", None, NO) // Crackpots
|
||||
Record(@"73d68f32d1fb73883ceb183d5150bff5f1065de4", None, NO) // Crash Dive
|
||||
Record(@"70e723aa67d68f8549d9bd8f96d8b1262cbdac3c", Atari8k, NO) // Crazy Climber
|
||||
Record(@"dd385886fdd20727c060bad6c92541938661e2b4", None, NO) // Crazy Valet
|
||||
Record(@"b1b3d8d6afe94b73a43c36668cc756c5b6fdc1c3", None, NO) // Cross Force
|
||||
Record(@"5da3d089ccda960ce244adb855975877c670e615", Atari16k, NO) // Crossbow
|
||||
Record(@"a57062f44e7ac793d4c39d1350521dc5bc2a665f", None, NO) // Crypts of Chaos
|
||||
Record(@"2e4ee5ee040b08be1fe568602d1859664e607efb", Atari16k, YES) // Crystal Castles
|
||||
Record(@"0dd72a3461b4167f2d68c93511ed4985d97e6adc", None, NO) // Cubicolor
|
||||
Record(@"4b3d02b59e17520b4d60236568d5cb50a4e6aeb3", None, NO) // Custer's Revenge
|
||||
Record(@"07e94a7d357e859dcff77180981713ce8119324e", None, NO) // Dancing Plate
|
||||
Record(@"0ae2fc87f87a5cc199c3b9a17444bf3c2f6a829b", None, NO) // Dark Cavern
|
||||
Record(@"fbb4814973fcb4e101521515e04daa6424c45f5c", Atari16k, YES) // Dark Chambers
|
||||
Record(@"c5af53c4b64c3db552d4717b8583d6fe8d3e7952", Atari8k, NO) // Dark Mage
|
||||
Record(@"68de291d5e9cbebfed72d2f9039e60581b6dbdc5", None, NO) // Deadly Duck
|
||||
Record(@"2ad9db4b5aec2da36ecc3178599b02619c3c462e", ParkerBros, NO) // Death Star Battle
|
||||
Record(@"5f710a1148740760b4ebcc42861a1f9c3384799e", None, NO) // Death Trap
|
||||
Record(@"717656f561823edaa69240471c3106963f5c307e", ActivisionStack, NO)// Decathlon
|
||||
Record(@"d7b506b84f28e1b917a2978753d5a40eb197537a", Atari8k, YES) // Defender 2
|
||||
Record(@"79facc1bf70e642685057999f5c2b8e94b102439", None, NO) // Defender
|
||||
Record(@"5aae618292a728b55ad7f00242d870736b5356d3", None, NO) // Demolition Herby
|
||||
Record(@"a580d886c191a069f6b9036c3f879e83e09500c2", None, NO) // Demon Attack
|
||||
Record(@"b45582de81c48b04c2bb758d69021e8088c70ce7", None, NO) // Demons to Diamonds
|
||||
Record(@"ccea2d5095441d7e1b1468e3879a6ab556dc8b7a", Atari16k, YES) // Desert Falcon
|
||||
Record(@"538108ca9821265e23f06fa7672965631bdf8175", None, NO) // Diagnostic Test Cartridge 2.6
|
||||
Record(@"81022ef30e682183d51b18bff145ce425c6f924e", None, NO) // Dice Puzzle
|
||||
Record(@"79e746524520da546249149c33614fc23a4f2a51", Atari16k, YES) // Dig Dug
|
||||
Record(@"7485cf55201ef98ded201aec73c4141f9f74f863", None, NO) // Dishaster
|
||||
Record(@"157117df23cb5229386d06bbdb3af20a208722e0", None, NO) // Dodge 'Em
|
||||
Record(@"e3985d759f8a8f4705f543ce7eb5e93bf63722b5", None, NO) // Dolphin
|
||||
Record(@"4606c0751f560200aede6598ec9c8e6249a105f5", Atari8k, NO) // Donald Duck's Speedboat
|
||||
Record(@"359e662da02bf0a2184472e25d05bc597b6c497a", None, NO) // Donkey Kong (1983) (CBS Electronics) (PAL) [!]
|
||||
Record(@"98f98ac0728c68de66afda6500cafbdffe8ab50a", Atari8k, NO) // Donkey Kong Junior
|
||||
Record(@"6e6e37ec8d66aea1c13ed444863e3db91497aa35", None, NO) // Donkey Kong
|
||||
Record(@"251e02ac583d84eb43f1451d55b62c7c70e9644f", Atari16k, NO) // Double Dragon
|
||||
Record(@"8e2ea320b23994dc87abe69d61249489f3a0fccc", Atari16k, NO) // Double Dunk
|
||||
Record(@"b446381fe480156077b0b3c51747d156e5dde89f", None, NO) // Dragon Treasures
|
||||
Record(@"944c52de85464070a946813b050518977750e939", None, NO) // Dragster
|
||||
Record(@"9caf114c9582d5e0c14396b13d2bd1a89cad90b1", None, NO) // Dukes of Hazzard (1980)
|
||||
Record(@"c061d753435dcb7275a8764f4ad003b05fa100ed", Atari16k, NO) // Dukes of Hazzard (1983)
|
||||
Record(@"d16eba13ab1313f375e86b488181567f846f1dc4", Atari8k, NO) // Dumbo's Flying Circus
|
||||
Record(@"9e34f9ca51573c92918720f8a259b9449a0cd65e", Atari8k, NO) // E.T. — The Extra-Terrestrial
|
||||
Record(@"68cbfadf097ae2d1e838f315c7cc7b70bbf2ccc8", None, NO) // Eggomania
|
||||
Record(@"bab872ee41695cefe41d88e4932132eca6c4e69c", Atari8k, YES) // Elevator Action
|
||||
Record(@"475fc2b23c0ee273388539a4eeafa34f8f8d3fd8", None, NO) // Eli's Ladder
|
||||
Record(@"3983e109fc0b38c0b559a09a001f3e5f2bb1dc2a", Atari8k, NO) // Elk Attack
|
||||
Record(@"205af4051ea39fb5a038a8545c78bff91df321b7", None, NO) // Encounter at L-5
|
||||
Record(@"82e9b2dd6d99f15381506a76ef958a1773a7ba21", None, NO) // Enduro
|
||||
Record(@"7905aee90a6dd64d9538e0b8e772f833ba9feb83", None, NO) // Entombed
|
||||
Record(@"27d925d482553deff23f0889b3051091977d6920", Tigervision, NO) // Espial
|
||||
Record(@"c17801c0190ade27f438e2aa98dde81b3ae66267", None, NO) // Exocet
|
||||
Record(@"6297dd336a6343f98cd142d1d3d76ce84770a488", None, NO) // Fantastic Voyage
|
||||
Record(@"a5614c751f29118ddb3dec9794612b98a0f00b98", None, NO) // Fast Eddie
|
||||
Record(@"c62a70645939480b184e3b2e378ec4bcbd484bc7", None, NO) // Fast Food
|
||||
Record(@"d0bb58ea1fc37e929e5f7cdead037bb14a166451", Atari32k, YES) // Fatal Run
|
||||
Record(@"686427cc47b69980d292d04597270347942773ff", Atari8k, NO) // Fathom
|
||||
Record(@"684275b22f2bac7d577cf48cf42fa14fa6f69678", Atari16k, NO) // Fighter Pilot
|
||||
Record(@"ba9a8ccfeb552dd756c660ea843a39619d3c77e9", None, NO) // Final Approach
|
||||
Record(@"f76cc14afd7aef367c5a5defbd84f3bbb2f98ba3", None, NO) // Fire Fighter
|
||||
Record(@"df5420eb0f71e681e7222ede8e211a7601e7a327", None, NO) // Fire Fly
|
||||
Record(@"531e995aef6cd47b0efea72ae3e56aeee449d798", None, NO) // Fishing Derby
|
||||
Record(@"ac05f05f3365f5e348e1e618410065a1c2a88ee4", None, NO) // Flag Capture
|
||||
Record(@"c6fe4ce24bc1ebd538258d98cfe829963323acca", None, NO) // Football
|
||||
Record(@"c6023bf73818c78b2e477a9c6dac411cdbf9c0aa", None, NO) // Frankenstein's Monster
|
||||
Record(@"91cc7e5cd6c0d4a6f42ed66353b7ee7bb972fa3f", None, NO) // Freeway
|
||||
Record(@"de6fc1b51d41b34dcda92f579b2aa4df8eccf586", Atari8k, NO) // Frog Pond
|
||||
Record(@"f344d5a8dc895c5a2ae0288f3c6cb66650e49167", None, NO) // Frogflys
|
||||
Record(@"6b9e591cc53844795725fc66c564f0364d1fbe40", ParkerBros, NO) // Frogger II
|
||||
Record(@"e859b935a36494f3c4b4bf5547392600fb9c96f0", None, NO) // Frogger
|
||||
Record(@"cf32bfcd7f2c3b7d2a6ad2f298aea2dfad8242e7", Atari8k, NO) // Front Line
|
||||
Record(@"b9e60437e7691d5ef9002cfc7d15ae95f1c03a12", None, NO) // Frostbite
|
||||
Record(@"5cc4010eb2858afe8ce77f53a89d37c7584e15b4", None, NO) // Fun with Numbers
|
||||
Record(@"9cfb6288a5c2dae63ee6f5e9325200ccd21a3055", None, NO) // G.I. Joe — Cobra Strike
|
||||
Record(@"8e708e0067d3302327900fa322aeb8e2df2022d7", Atari8k, NO) // Galaxian Enhanced Graphics
|
||||
Record(@"b081b327ac32d951c36cb4b3ff812be95685d52f", Atari8k, NO) // Galaxian
|
||||
Record(@"8cf49d43bd62308df788cfacbfcd80e9226c7590", None, NO) // Gangster Alley
|
||||
Record(@"bc0d1edc251d8d4db3d5234ec83dee171642a547", Atari16k, NO) // Garfield
|
||||
Record(@"0da1f2de5a9b5a6604ccdb0f30b9da4e5f799b40", None, NO) // Gas Hog
|
||||
Record(@"73adae38d86d50360b1a247244df05892e33da46", None, NO) // Gauntlet
|
||||
Record(@"3b1fb93342c7f014a28dddf6f16895d11ac7d6f0", None, NO) // General Re-Treat
|
||||
Record(@"4b533776dcd9d538f9206ad1e28b30116d08df1e", Atari8k, NO) // Ghost Manor
|
||||
Record(@"1bcf03e1129015a46ad7028e0e74253653944e86", Atari16k, NO) // Ghostbusters II (alternate)
|
||||
Record(@"e032876305647a95b622e5c4971f7096ef72acdb", Atari16k, NO) // Ghostbusters II
|
||||
Record(@"5ed0b2cb346d20720e3c526da331551aa16a23a4", Atari8k, NO) // Ghostbusters
|
||||
Record(@"b64ed2d5a2f8fdac4ff0ce56939ba72e343fec33", None, NO) // Gigolo
|
||||
Record(@"3a3d7206afee36786026d6287fe956c2ebc80ea7", None, NO) // Glacier Patrol
|
||||
Record(@"7bca0f7a0f992782e4e4c90772bac976ca963a6d", None, NO) // Glib
|
||||
Record(@"f78c478aacf6536522e8d37a3888a975e1a383cd", None, NO) // Go Go Home Monster (2)
|
||||
Record(@"4c41379f0dd9880384fcbb46bad9fbaaf109a477", None, NO) // Go Go Home Monster
|
||||
Record(@"a25d52770408314dec6f41aaf5f9f0a2a3e2c18f", None, NO) // Golf
|
||||
Record(@"97fb489ba4ce0f8a306563063563617321352cfb", None, NO) // Gopher
|
||||
Record(@"35f8341c73c7e6e896cb065977427b3f98ae9f08", None, NO) // Gorf
|
||||
Record(@"24fab817728216582b6d95558c361ace66abf96f", None, NO) // Grand Prix
|
||||
Record(@"a372d4dd3d95b3866553cae2336e4565e00cc25b", Atari8k, NO) // Gravitar
|
||||
Record(@"7a027329309e018b0d51adcb6ae13c9d13e54f4a", Atari8k, NO) // Gremlins
|
||||
Record(@"c90acaee066f97efc6a520deb7fa3e5760a471fa", Atari8k, NO) // Grover's Music Maker
|
||||
Record(@"7d30ff565ad7b2a3143d049c5b39e4a6ac3f9cd5", None, NO) // Guardian
|
||||
Record(@"45f3f98735798e19427a9100a9000d97917b932f", None, NO) // Gunfight (NTSC)
|
||||
Record(@"7ac6356224cc718ee5731d1ce14aea6fb2335439", None, NO) // Gunfight (PAL)
|
||||
Record(@"4bd87ba8b3b6d7850e3ea41b4d494c3b12659f27", ParkerBros, NO) // Gyruss
|
||||
Record(@"282f94817401e3725c622b73a0c05685ce761783", Atari8k, NO) // H.E.R.O.
|
||||
Record(@"4c72cec151f219866bf870fa7ac749a19ca501c9", None, NO) // Halloween
|
||||
Record(@"561bccf508e162bc70c42d85c170cf0d1d4691a3", None, NO) // Hangman
|
||||
Record(@"d6db71da02ae194140bf812be34d6e8a6785d138", None, NO) // Harbor Escape
|
||||
Record(@"1476c869619075b551b20f2c7f95b11e0d16aec1", None, NO) // Haunted House
|
||||
Record(@"8196209ef7048c5494dbdc932adbf1c7abf79f4e", Atari8k, NO) // Holey Moley
|
||||
Record(@"f362d2b3a50e5ae3c2b412b6c08ecdcfee47a688", None, NO) // Home Run
|
||||
Record(@"d4b0b2aa379893356c72414ee0065a3a91cf9f97", None, NO) // Human Cannonball
|
||||
Record(@"a6e42c63138a2fd527cdbe9b7e60f5feabdd55c8", None, NO) // Hunt & Score — Memory Match
|
||||
Record(@"f7e782214b5f9227e34c00f590be50534f1fda91", None, NO) // I Want my Mommy
|
||||
Record(@"21de0f034e5dad03fa91eb7ae6cc081c142be35c", None, NO) // Ice Hockey
|
||||
Record(@"e5b9c3a3638bd42f96a26b651463da96a9432315", Atari16k, NO) // Ikari Warriors
|
||||
Record(@"620ab88d63cdd3f8ce67deac00a22257c7205c8b", None, NO) // Indy 500
|
||||
Record(@"922cd171ef132bf6c5bed00ad01410ada4b20729", None, NO) // Infiltrate
|
||||
Record(@"19fc37f2a24e31a59a17f9cbf3cc03416a8bab9a", None, NO) // Invaders
|
||||
Record(@"2bbc124cead9aa49b364268735dad8cb1eb6594f", ParkerBros, NO) // James Bond 007
|
||||
Record(@"ea5c827052886908c0deaa0a03d6f8da8e4f298d", None, NO) // Jammed Demo
|
||||
Record(@"af4d6867a8bc4818fc6bb701a765a3c907feb628", None, NO) // Jaw Breaker
|
||||
Record(@"36b9edc7150311203f375c1be10d0510efde6476", None, NO) // Jedi Arena
|
||||
Record(@"0d94c1caacb862d9e0b4c2dda121cd4d74a1cced", None, NO) // John K Harvey's Equalizer
|
||||
Record(@"928eaa424b36d98078f9251d67fb13a8fddfafbd", None, NO) // Journey Escape
|
||||
Record(@"cb94dc316cba282a0036871db2417257e960786b", Atari8k, NO) // Joust
|
||||
Record(@"cd2cf245d6e924ff2100cc93d20223c4a231e160", Atari16k, YES) // Jr. Pac-Man
|
||||
Record(@"9a0ee845d9928d4db003b07b927bb2c1f628e725", None, NO) // Jungle Fever
|
||||
Record(@"83a32a2d686355438c915540cfe0bb13b76c1113", Atari8k, NO) // Jungle Hunt
|
||||
Record(@"ce8ac88b799c282567495ce509402a5a4c2c4d82", None, NO) // Kabobber
|
||||
Record(@"40d4df4f8e4a69a299ae7678c17e72bedeb70105", None, NO) // Kaboom!
|
||||
Record(@"a82aaeef44ad88de605c50d23fb4f6cec73f3ab4", None, NO) // Kamikaze Saucers
|
||||
Record(@"01fd30311e028944eafb6d14bb001035f816ced7", Atari8k, NO) // Kangaroo
|
||||
Record(@"c0db7d295e2ce5e00e00b8a83075b1103688ea15", None, NO) // Karate
|
||||
Record(@"3eefc193dec3b242bcfd43f5a4d9f023e55378a4", None, NO) // Keystone Kapers
|
||||
Record(@"839e13ffedcbed22d51a24c001900c3474a078f2", None, NO) // King Kong
|
||||
Record(@"3162259c6dbfbb57a2ea41d849155702151ee39b", Atari16k, YES) // Klax
|
||||
Record(@"759597d1d779cfdfd7aa30fd28a59acc58ca2533", None, NO) // Knight on the Town
|
||||
Record(@"2f550743e237f6dc8c75c389a01b02e9a396fdad", None, NO) // Kool-Aid Man
|
||||
Record(@"4bdf1cf73316bdb0002606facf11b6ddcb287207", Atari8k, NO) // Krull
|
||||
Record(@"1637b6b9cd1a918339ec054cf95b924e7ce4789a", Atari8k, NO) // Kung Fu Superkicks
|
||||
Record(@"3b93a34ba2a6b7db387ea588c48d939eee5d71a1", Atari8k, NO) // Kung-Fu Master
|
||||
Record(@"6d59dfea26b7a06545a817f03f62a59be8993587", None, NO) // Lady in Wading
|
||||
Record(@"ea8ecc2f6818e1c9479f55c0a3356edcf7a4d657", None, NO) // Laser Blast
|
||||
Record(@"cdf55b73b4322428a001e545019eaa591d3479cf", None, NO) // Laser Gates
|
||||
Record(@"afab795719386a776b5fb2165fc84f4858e16e05", None, NO) // Laser Volley
|
||||
Record(@"fe208ad775cbf9523e7a99632b9f10f2c9c7aa87", None, NO) // Lochjaw
|
||||
Record(@"fc3d75d46d917457aa1701bf47844817d0ba96c3", None, NO) // Lock 'n' Chase
|
||||
Record(@"f92b0b83db3cd840d16ee2726011f5f0144103d5", None, NO) // London Blitz
|
||||
Record(@"ef02fdb94ac092247bfcd5f556e01a68c06a4832", ParkerBros, NO) // Lord of the Rings
|
||||
Record(@"e8492fe9d62750df682358fe59a4d4272655eb96", None, NO) // Lost Luggage
|
||||
Record(@"dcd96913a1c840c8b57848986242eeb928bfd2ff", None, NO) // M*A*S*H
|
||||
Record(@"d6e2b7765a9d30f91c9b2b8d0adf61ec5dc2b30a", None, NO) // M.A.D.
|
||||
Record(@"4c66b84ab0d25e46729bbcf23f985d59ca8520ad", CommaVid, NO) // MagiCard
|
||||
Record(@"cdc7e65d965a7a00adda1e8bedfbe6200e349497", None, NO) // Malagai
|
||||
Record(@"ee8f9bf7cdb55f25f4d99e1a23f4c90106fadc39", None, NO) // Mangia
|
||||
Record(@"249a11bb4872a24f22dff1027ff256c1408140c2", None, NO) // Marauder
|
||||
Record(@"dd9e94ca96c75a212f1414aa511fd99ecdadaf44", None, NO) // Marine Wars
|
||||
Record(@"49425ff154b92ca048abb4ce5e8d485c24935035", Atari8k, NO) // Mario Bros.
|
||||
Record(@"fbe7a78764407743b43a91136903ede65306f4e7", None, NO) // Master Builder
|
||||
Record(@"6db8fa65755db86438ada3d90f4c39cc288dcf84", MNetwork, NO) // Masters of the Universe
|
||||
Record(@"18fac606400c08a0469aebd9b071ae3aec2a3cf2", None, NO) // Math Gran Prix
|
||||
Record(@"aba25089d87cd6fee8d206b880baa5d938aae255", None, NO) // Maze Craze
|
||||
Record(@"0ae118373c7bda97da2f8d9c113e1e09ea7e49e1", None, NO) // Mega Force
|
||||
Record(@"46977baf0e1ee6124b524258879c46f80d624fae", MegaBoy, NO) // MegaBoy
|
||||
Record(@"9c5748b38661dbadcbc9cd1ec6a6b0c550b0e3da", None, NO) // MegaMania
|
||||
Record(@"debb1572eadb20beb0e4cd2df8396def8eb02098", None, NO) // Meltdown
|
||||
Record(@"7fcf95459ea597a332bf5b6f56c8f891307b45b4", Atari16k, NO) // Midnight Magic
|
||||
Record(@"0616f0dde6d697816dda92ed9e5a4c3d77a39408", Atari16k, YES) // Millipede
|
||||
Record(@"5edbf8a24fcba9763983befe20e2311f61b986d4", Tigervision, NO) // Miner 2049er Volume 2
|
||||
Record(@"0e56b48e88f69d405eabf544e57663bd180b3b1e", Tigervision, NO) // Miner 2049er
|
||||
Record(@"34773998d7740e1e8c206b3b22a19e282ca132e1", None, NO) // Mines of Minos
|
||||
Record(@"be24b42e3744a81fb217c86c4ed5ce51bff28e65", None, NO) // Miniature Golf
|
||||
Record(@"f721d1f750e19b9e1788eed5e3872923ab46a91d", Atari8k, NO) // Miss Piggy's Wedding
|
||||
Record(@"faa06bb0643dbf556b13591c31917d277a83110b", None, NO) // Missile Command
|
||||
Record(@"224e7a310afdb91c6915743e72b7b53b38eb5754", None, NO) // Missile Control
|
||||
Record(@"999dc390a7a3f7be7c88022506c70bd4208b26d8", None, NO) // Mission 3,000 AD
|
||||
Record(@"93520821ce406a7aa6cc30472f76bca543805fd4", None, NO) // Mission Survive
|
||||
Record(@"0b74a90a22a7a16f9c2131fabd76b7742de0473e", None, NO) // Mogul Maniac
|
||||
Record(@"81a4d56820b1e00130e368a3532c409929aff5fb", Atari8k, NO) // Monstercise
|
||||
Record(@"7dfeb1a8ec863c1e0f297113a1cc4185c215e81c", ParkerBros, NO) // Montezuma's Revenge
|
||||
Record(@"dce778f397a325113f035722b7769492645d69eb", Atari8k, NO) // Moon Patrol
|
||||
Record(@"05ab04dc30eae31b98ebf6f43fec6793a53e0a23", Atari8k, NO) // Moonsweeper
|
||||
Record(@"c4d495d42ea5bd354af04e1f2b68cce0fb43175d", Atari8k, NO) // Motocross Racer
|
||||
Record(@"ece97bda734faffcf847a8bcdfa474789c377d8d", Atari16k, NO) // MotoRodeo
|
||||
Record(@"ef4112e86d6a3e8f7b8e482d294a5917f162b38c", CBSRamPlus, NO) // Mountain King
|
||||
Record(@"cf6347dedcfec213c28dd92111ec6f41e74b6f64", None, NO) // Mouse Trap
|
||||
Record(@"e4c912199779bba25f1b9950007f14dca3d19c84", Atari8k, NO) // Mr Do!
|
||||
Record(@"330c2c67399e07c40f4101f9e18670fef070475e", ParkerBros, NO) // Mr. Do!'s Castle
|
||||
Record(@"62b933cdd8844bb1816ce57889203954fe782603", Atari8k, NO) // Ms. Pac-Man
|
||||
Record(@"b2df23b1bf6df9d253ad0705592d3fce352a837b", Atari8k, NO) // My Golf
|
||||
Record(@"2b4a0535ca83b963906eb0a5d60ce0e21f07905d", None, NO) // Name This Game
|
||||
Record(@"372771aeb4e2fb2cd1dead5497e3821e4236d5fc", None, NO) // Night Driver
|
||||
Record(@"26e1309bc848cf5880b831d7566488ec5b3db58c", None, NO) // Night Stalker (2)
|
||||
Record(@"281ff7e55c27656522b144b84cba08eb148e2f0a", None, NO) // Night Stalker
|
||||
Record(@"e2e8750b8856dd44d914c43a7d277188cc148e5c", None, NO) // No Escape!
|
||||
Record(@"771a2a87b3b457c0b83f556ce00d1e9c54caeabc", None, NO) // Nothing
|
||||
Record(@"be3f4beeb322cddc7223d6d77e17302aa811e43a", Atari8k, NO) // Obelix
|
||||
Record(@"1f8d06b99db94b0aa8ca320c7cb212639ac9591f", None, NO) // Ocean City
|
||||
Record(@"3dcfe93399044148561586056288c6f8e5c96e2b", Atari16k, YES) // Off the Wall
|
||||
Record(@"7ad74a7c36318f1304f5dc454401cf257fa60d7a", None, NO) // Off Your Rocker
|
||||
Record(@"ea674cf2c90d407b8f8b96eac692690b602b73f9", None, NO) // Oink!
|
||||
Record(@"7bd1cbddefcf3bd24da570be015234d0c444a7e5", None, NO) // Okie Dokie
|
||||
Record(@"dcaab259e7617c7ac7d349893451896a9ca0e292", CBSRamPlus, NO) // Omega Race
|
||||
Record(@"cd968c2a983f9adf4d8d8d56823923b31c33980f", None, NO) // Open Sesame
|
||||
Record(@"7905709fcc85cbcfc28ca2ed543ffa737a5483ae", Atari8k, NO) // Oscar's Trash Race
|
||||
Record(@"cbecf1a32d9366a3dd4ad643916cd59cdc820a8b", None, NO) // Othello
|
||||
Record(@"344d6942723513c376a7a844779804e10f357b85", None, NO) // Out of Control
|
||||
Record(@"f8eeaaf4635ac39b4bdf7ded1348bce46313ef9f", None, NO) // Outlaw
|
||||
Record(@"412e8a2438379878ee4de5c6bf4e5a9ee2707c8b", None, NO) // Oystron
|
||||
Record(@"923c969d31cef450075932436f03f1404e1cab0e", None, NO) // Pac-Kong
|
||||
Record(@"0940fea7f04cdb6d4b90c5ad1a7e344e68f6dbb1", None, NO) // Pac-Man
|
||||
Record(@"b529c1664ed1abc8f5f962a1fed65c0e4440219c", None, NO) // Peek-A-Boo
|
||||
Record(@"832283530f5dee332f29cf8c4854dd554f2030a0", None, NO) // Pele's Soccer
|
||||
Record(@"461c2ea3e4d24f86ec02215c1f4743d250796c11", Atari8k, NO) // Pengo (prototype)
|
||||
Record(@"89b991a7a251f78f422bcdf9cf7d4475fdf33e97", Atari8k, NO) // Pengo
|
||||
Record(@"c5317035e73f60e959c123d89600c81b7c45701f", None, NO) // Pepsi Invaders
|
||||
Record(@"19c3ad034466c0433501a415a996ed7155d6063a", Atari16k, NO) // Pete Rose Baseball
|
||||
Record(@"959aca4b44269b1e5ac58791fc3c7c461a6a4a17", None, NO) // Phantom Tank
|
||||
Record(@"b299df2792c5cca73118925dff85695b73a16228", None, NO) // Philly Flasher
|
||||
Record(@"010d51e3f522ba60f021d56819437d7c85897cdd", Atari8k, NO) // Phoenix
|
||||
Record(@"a5917537cf1093aa350903d85d9e271e8a11d2cf", Atari16k, NO) // Pick 'n' Pile
|
||||
Record(@"483fc907471c5c358fb3e624097861a2fc9c1e45", None, NO) // Picnic
|
||||
Record(@"57774193081acea010bd935a0449bc8f53157128", None, NO) // Piece o' Cake
|
||||
Record(@"d08b30ca2e5e351cac3bd3fb760b87a1a30aa300", Atari8k, NO) // Pigs in Space
|
||||
Record(@"920cfbd517764ad3fa6a7425c031bd72dc7d927c", Pitfall2, NO) // Pitfall II
|
||||
Record(@"d0ec08b88d032627701ad72337524d91b26c656b", None, NO) // Pitfall! (PAL)
|
||||
Record(@"8d525480445d48cc48460dc666ebad78c8fb7b73", None, NO) // Pitfall! (NTSC)
|
||||
Record(@"dcca30e4ae58c85a070f0c6cfaa4d27be2970d61", None, NO) // Planet of the Apes
|
||||
Record(@"ccfcbf52815a441158977292b719f7c5ed80c515", None, NO) // Planet Patrol
|
||||
Record(@"103398dd35ebd39450c5cac760fa332aac3f9458", None, NO) // Plaque Attack
|
||||
Record(@"2410931a8a18b915993b6982fbabab0f437967a4", Tigervision, NO) // Polaris
|
||||
Record(@"9d334da07352a9399cbbd9b41c6923232d0cdcd3", Atari8k, NO) // Pole Position
|
||||
Record(@"c0af0188028cd899c49ba18f52bd1678e573bff2", None, NO) // Polo
|
||||
Record(@"954d2980ea8f8d9a76921612c378889f24c35639", None, NO) // Pompeii
|
||||
Record(@"b7a002025c24ab2ec4a03f62212db7b96c0e5ffd", None, NO) // Pooyan
|
||||
Record(@"1772a22df3e9a1f3842387ac63eeddff7f04b01c", ParkerBros, NO) // Popeye
|
||||
Record(@"70afc2cc870be546dc976fa0c6811f7e01ebc471", Atari8k, NO) // Porky's
|
||||
Record(@"8b001373be485060f88182e9a7afcf55b4d07a57", Atari8k, NO) // Pressure Cooker
|
||||
Record(@"1ea6bea907a6b5607c76f222730f812a99cd1015", Atari8k, NO) // Private Eye
|
||||
Record(@"feb6bd37e5d722bd080433587972b980afff5fa5", None, NO) // Pumruckl I
|
||||
Record(@"a61be3702437b5d16e19c0d2cd92393515d42f23", ParkerBros, NO) // Q-Bert's Qubes
|
||||
Record(@"f3ef9787b4287a32e4d9ac7b9c3358edc16315b2", None, NO) // Q-Bert
|
||||
Record(@"1e634a8733cbc50462d363562b80013343d2fac3", Atari8k, NO) // Quadrun
|
||||
Record(@"d83c740d2968343e6401828d62f58be6aea8e858", Atari8k, NO) // Quest for Quintana Roo
|
||||
Record(@"33a47f79610c4525802c9881f67ad1f3f8c1b55d", None, NO) // Quick Step!
|
||||
Record(@"7bf945ea667e683ec24a4ed779e88bbe55dc4b26", Atari8k, NO) // Rabbit Transit
|
||||
Record(@"4af6008152f1d38626d84016a7ef753406b48b46", None, NO) // Racquetball
|
||||
Record(@"33f016c941fab01e1e2d0d7ba7930e3bcd8feaa3", Atari16k, YES) // Radar Lock
|
||||
Record(@"5f1f2b5b407b0624b59409e02060a3a9e8eed8fc", None, NO) // Radar
|
||||
Record(@"a79f6e0f4fd76878e5c3ba6b52d17e88acdbe9f6", None, NO) // Raft Rider
|
||||
Record(@"7ae70783969709318e56f189cf03da92320a6aba", Atari8k, NO) // Raiders of the Lost Ark
|
||||
Record(@"fd0a69c06eb3f7c9328951c890644f93c4bad6ad", None, NO) // Ram It
|
||||
Record(@"7bb7df255829d5fbbee0d944915e50f89a5e7075", Atari16k, NO) // Rampage!
|
||||
Record(@"5adf9b530321472380ebceb2539de2ffbb0310bc", None, NO) // Reactor
|
||||
Record(@"ace97b89b8b6ab947434dbfd263951c6c0b349ac", Atari8k, NO) // RealSports Baseball
|
||||
Record(@"bc2e6bdaa950bc06be040899dfeb9ad0938f4e98", Atari8k, NO) // RealSports Basketball
|
||||
Record(@"22dedbfce6cc9055a6c4caec013ca80200e51971", Atari16k, NO) // RealSports Boxing
|
||||
Record(@"200d04c1e7f41a5a3730287ed0c3f9293628f195", Atari8k, NO) // RealSports Football
|
||||
Record(@"e3d964d918b7f2c420776acd3370ec1ee62744ea", Atari8k, NO) // RealSports Soccer
|
||||
Record(@"702c1c7d985d0d22f935265bd284d1ed50df2527", Atari8k, NO) // RealSports Tennis
|
||||
Record(@"2025e1d868595fad36e5d9e7384ffd24c206208d", None, NO) // RealSports Volleyball
|
||||
Record(@"94e94810bf6c72eee49157f9218c3c170b65c836", None, NO) // Rescue Terra I
|
||||
Record(@"f8a9dd46f9bad232f74d1ee2671ccb26ea1b3029", None, NO) // Revenge of the Beefsteak Tomatoes
|
||||
Record(@"acb2430b4e6c72ce13f321d9d3a38986dc4768ef", None, NO) // Riddle of the Sphinx
|
||||
Record(@"6715493dce54b22362741229078815b3360988ae", Tigervision, NO) // River Patrol
|
||||
Record(@"40329780402f8247f294fe884ffc56cc3da0c62d", None, NO) // River Raid copy
|
||||
Record(@"a08c3eae3368334c937a5e03329782e95f7b57c7", Atari16k, NO) // River Raid II
|
||||
Record(@"325a2374800b2cb78ab7ff9e4017759865109d7d", None, NO) // River Raid
|
||||
Record(@"7f9c2321c9f22cf2cdbcf1b3f0e563a1c53f68ca", Atari8k, NO) // Robin Hood
|
||||
Record(@"f45dfcd6db0dae5458e1c0ae8eeaa75b553cdfec", Atari16k, NO) // Road Runner
|
||||
Record(@"21a3ee57cb622f410ffd51986ab80acadb8d44b7", ActivisionStack, NO)// Robot Tank
|
||||
Record(@"0abf0a292d4a24df5a5ebe19a9729f3a8f883c8b", Atari8k, NO) // Roc 'n Rope
|
||||
Record(@"fd243c480e769b20b7bf3e74bcd86e4ac99dab19", None, NO) // Room of Doom
|
||||
Record(@"85752ac6eb7045a9083425cd166609882a1c2c58", Atari8k, NO) // Saboteur
|
||||
Record(@"ecd8ef49ae23ddd3e10ec60839b95c8e7764ea27", Atari16k, YES) // Save Mary!
|
||||
Record(@"e566d7b1f4eb6c2b110eb4fc676eb0ce9e90fe1e", None, NO) // SCIScide 1.32
|
||||
Record(@"8e9b320d8966315a8b07f1babc0ba2662f761102", None, NO) // SCSIcide 1.30
|
||||
Record(@"e8f5ae861ca1f410c6a9af116a96ed65d9a3abb2", None, NO) // Scuba Diver
|
||||
Record(@"e5558ae30acc1fa5b4ffe782ae480622586a32ca", None, NO) // Sea Hunt
|
||||
Record(@"4ec6982b2da25b29840428fd993a391e63f53730", None, NO) // Seahawk
|
||||
Record(@"7324a1ebc695a477c8884718ffcad27732a98ab0", None, NO) // Seaquest
|
||||
Record(@"1914f57ab0a6f221f2ad344b244a3cdd7b1d991a", None, NO) // Secret Agent
|
||||
Record(@"af11f1666d345267196a1c35223727e2ef93483a", Atari16k, YES) // Secret Quest
|
||||
Record(@"6518078b3786ac26f75067f0646aef4e83f2db15", None, NO) // Self_Portrait_by_Tjoppen
|
||||
Record(@"fcf5f8a7d6e59a339c2002e3d4084d87deb670fe", Atari16k, NO) // Sentinel
|
||||
Record(@"6e91759756c34f40a2c26936df6c0ca1a3850e80", None, NO) // Shark Attack
|
||||
Record(@"cfb4b41e318c7cd0070e75e412f67c973e124d8e", None, NO) // Shootin' Gallery
|
||||
Record(@"6e6daa34878d3e331c630359c7125a4ffba1b22d", Atari16k, YES) // Shooting Arcade
|
||||
Record(@"08952192ea6bf0ef94373520a7e855f58bae6179", None, NO) // Shuttle Orbiter
|
||||
Record(@"242fc23def80da96da22c2c7238d48635489abb0", Atari8k, NO) // Sinistar
|
||||
Record(@"e9fa52f8e7f747cd9685ddb18bdeed2f66255324", Atari8k, NO) // Sir Lancelot
|
||||
Record(@"a26fe0b5a43fe8116ab0ae6656d6b11644d871ec", Atari8k, NO) // Skate Boardin'
|
||||
Record(@"5ea6d2eb27c76e85f477ba6c799deb7c416ebbc3", None, NO) // Skeet Shoot
|
||||
Record(@"6581846f983b50cffb75d1c1b902238ba7dd4e92", None, NO) // Skiing
|
||||
Record(@"4dde18d4abc139562fdd7a9d2fd49a1f00a9e64a", None, NO) // Sky Diver
|
||||
Record(@"105f722dcf9a89b683c10ddd7f684c5966c8e1db", None, NO) // Sky Jinks
|
||||
Record(@"fc5f1e30db3b2469c9701dadfa95f3268fd1e4cb", Atari8k, NO) // Sky Patrol
|
||||
Record(@"ef0a7ecfe8f3b5d1e67a736552a0cdc472803be9", None, NO) // Sky Skipper
|
||||
Record(@"7239d1c64f3dfc2a1613be325cce13803dd2baa5", None, NO) // Slot Machine
|
||||
Record(@"a2b13017d759346174e3d8dd53b6347222d3b85d", None, NO) // Slot Racers
|
||||
Record(@"530c7883fed4c5b9d78e35d48770b56e328999a3", Atari8k, NO) // Smurfs: Rescue in Gargamel's Castle
|
||||
Record(@"c0ae3965fcfab0294f770af0af57d7d1adc17750", Atari8k, NO) // Smurfs Save the Day
|
||||
Record(@"e7bf450cf3a3f40de9d24d89968a4bc89b53cb18", None, NO) // Snail Against Squirrel
|
||||
Record(@"843e3c2fc71af2db3f2ae98eb350fde26334cfd1", None, NO) // Sneak 'n Peak
|
||||
Record(@"972bc0a77e76f3e4e1270ec1c2fc395e9826bc07", Atari8k, NO) // Snoopy and the Red Baron
|
||||
Record(@"9d725002e94b04e29d8cbce3c71d3bb2a84352fa", None, NO) // Soccer
|
||||
Record(@"09ea74f14db8d21ea785d0c8209ed670e4ce88be", Atari8k, NO) // Solar Fox
|
||||
Record(@"ec65ef9e47239a7d15db9bca7e625b166e8ac242", None, NO) // Solar Storm
|
||||
Record(@"33b16fbc95c2cdc52d84d98ca471f10dae3f9dbf", Atari16k, NO) // Solaris
|
||||
Record(@"ae3009e921f23254bb71f67c8cb2d7d6de2845a5", Atari8k, NO) // Sorceror's Apprentice
|
||||
Record(@"70e912379060d834aa9fb2baa2e6a438f3b5d3b6", None, NO) // Sorceror
|
||||
Record(@"560563613bc309a532d611f11a1cf2b9af1e2f16", None, NO) // Space Attack
|
||||
Record(@"26c6c47e9b654e81f47875c5fcb4e6212125f329", None, NO) // Space Canyon
|
||||
Record(@"b757b883ee114054c650027f3b9a8f15548cbf32", None, NO) // Space Cavern
|
||||
Record(@"31d9668fe5812c3d2e076987ca327ac6b2e280bf", None, NO) // Space Invaders
|
||||
Record(@"5bdd8af54020fa43065750bd4239a497695d403b", None, NO) // Space Jockey
|
||||
Record(@"bcec5a66f8dff1a751769626b0fce305fab44ca2", Atari8k, NO) // Space Shuttle
|
||||
Record(@"ce4432bb48921a3565d996b80b65fdf73bbfc39b", None, NO) // Space Tunnel
|
||||
Record(@"23510ba617431097668eaf104aa1e36233173093", None, NO) // Space War
|
||||
Record(@"b356294e35827bf81add95fee5453b0ca0f497ad", None, NO) // Spacechase
|
||||
Record(@"983b1aff97ab1243e283ba62d3a6a75ad186d225", None, NO) // SpaceMaster X-7
|
||||
Record(@"06820ad3c957913847f9849d920bc8725f535f11", None, NO) // Spider Fighter
|
||||
Record(@"5d6f918bba4bd046e85b707da3b7d643cc2e1f1f", None, NO) // Spider Maze
|
||||
Record(@"60af23a860b33e1a85081b8de779d2ddfe36b19a", None, NO) // Spider Monster
|
||||
Record(@"912c5f5571ac59a6782da412183cdd6277345816", None, NO) // Spider-Man
|
||||
Record(@"904118b0c1be484782ec2a60a24436059608b36d", None, NO) // Spiderdroid
|
||||
Record(@"205241a12778829981e9281d9c6fa137f11e1376", Atari8k, NO) // Spike's Peak
|
||||
Record(@"165de0ebca628eb1e9f564390c9eedfe289c7a1d", None, NO) // Spitfire Attack
|
||||
Record(@"6da0aa8aa40cd9c78dc014deb9074529688d91d0", Tigervision, NO) // Springer
|
||||
Record(@"c0e29b86fc1cc41a1c8afa37572c3c5698ae70b2", Atari16k, YES) // Sprint Master
|
||||
Record(@"1d0acf064d06a026a04b6028285db78c834e9854", Atari8k, NO) // Spy Hunter
|
||||
Record(@"033148faebc97d4ed3a86c97fe0cdee21bd261f7", None, NO) // Squeeze Box
|
||||
Record(@"11a9dd44787f011ec540159248377cb27fb8f7bb", None, NO) // Squoosh
|
||||
Record(@"46aabde3074acded8890a2efa5586d6b8bd76b5d", None, NO) // Sssnake
|
||||
Record(@"277184c4e61ced14393049a21a304e941d05993f", None, NO) // Stampede
|
||||
Record(@"1b95e07437ddc1523d7ec21c460273e91dbf36c7", None, NO) // Star Fox
|
||||
Record(@"3359aa7a6a5fa25beaa3ae5868d0034d52de9882", None, NO) // Star Gunner
|
||||
Record(@"e10cce1a438c82bd499e1eb31a3f07d7254198f5", Atari8k, NO) // Star Raiders
|
||||
Record(@"878e78ed46e29c44949d0904a2198826e412ed81", None, NO) // Star Ship
|
||||
Record(@"de05d1ca8ad1e7a85df3faf25b1aa90b159afded", None, NO) // Star Strike
|
||||
Record(@"61a3ebbffa0bfb761295c66e189b62915f4818d9", Atari8k, NO) // Star Trek — Strategic Operations Simulator
|
||||
Record(@"ccc5b829c4aa71acb7976e741fdbf59c8ef9eb55", None, NO) // Star Voyager
|
||||
Record(@"c9d201935bbe6373793241ba9c03cc02f1df31c9", ParkerBros, NO) // Star Wars — Ewok Adventure
|
||||
Record(@"8823fe3d8e3aeadc6b61ca51914e3b15aa13801c", ParkerBros, NO) // Star Wars — The Arcade Game
|
||||
Record(@"ad5b2c9df558ab23ad2954fe49ed5b37a06009bf", None, NO) // Star Wars — The Empire Strikes Back
|
||||
Record(@"4f87be0ef16a1d0389226d1fbda9b4c16b06e13e", Atari8k, YES) // Stargate
|
||||
Record(@"814876ed270114912363e4718a84123dee213b6f", None, NO) // StarMaster
|
||||
Record(@"e56ef1c0313d6d04e25446c4e34f9bb7eda8efac", None, NO) // Steeplechase copy
|
||||
Record(@"bd7f0005fa018f13ed7e942c83c1751fb746a317", None, NO) // Steeplechase
|
||||
Record(@"de2c146fa7a701d6c37728f5415563ce923a3e5d", None, NO) // Stella_Lives!
|
||||
Record(@"7d97d014c22a2ed3a5bc4b310f5a7be1b1d3520f", None, NO) // Stellar Track
|
||||
Record(@"9d6decda6e8ab263f7380ff662c814b8cb8caf34", None, NO) // Strategy X
|
||||
Record(@"7ca8f9cd74cfa505c493757ff37bf127ff467bb4", None, NO) // Strawberry Shortcake — Musical Match-Ups
|
||||
Record(@"bffb3d41916c83398624151eb00aa2a3acd23ab8", None, NO) // Street Racer
|
||||
Record(@"2cfe280fdbb6b5c8cda8a4620df12a5154e123be", None, NO) // Stronghold
|
||||
Record(@"2e19d7e16cf17682b043baaa30e345e6fa4540e5", None, NO) // Stunt Cycle
|
||||
Record(@"3aec7ea8af72bbe105b9d2903a92f5ad2b37bddb", None, NO) // Stunt Man
|
||||
Record(@"ccd75f0141b917656ef2b86c068fba3238d18a0c", None, NO) // Sub-Scan
|
||||
Record(@"b22ba7cbde60a21ecbbe3953cc4a5c0bf007cc26", None, NO) // Submarine Commander
|
||||
Record(@"2abc6bbcab27985f19e42915530fd556b6b1ae23", Atari8k, NO) // Subterrenea
|
||||
Record(@"65f4a708e6af565f1f75d0fbdc8942cb149cf299", Atari16k, NO) // Summer Games
|
||||
Record(@"b066a60ea1df1db0a55271c7608b0e19e4d18a1e", Atari16k, NO) // Super Baseball
|
||||
Record(@"e380e243c671e954e86aa1a3a0bfeb36d5e0c3e2", None, NO) // Super Breakout
|
||||
Record(@"dfce4d6436f91d8d385f8b01f0d8e3488400407b", None, NO) // Super Challenge Baseball
|
||||
Record(@"5c1338ec76828cfa4a85b5bd8db1c00c8095c330", None, NO) // Super Challenge Football
|
||||
Record(@"bac0a0256509f8fd1feea93d74ba4c7d82c1edc6", ParkerBros, NO) // Super Cobra
|
||||
Record(@"eaca6b474fd552ab4aaf75526618828165a91934", Atari16k, YES) // Super Football
|
||||
Record(@"b9dee027c8d7dd2a46be111ab0b8363c1becc081", None, NO) // Superman
|
||||
Record(@"cf84e21ada55730d689cfac7d26e2295317222bc", Atari8k, NO) // Surf's Up
|
||||
Record(@"e754c8985ca7f5780c23a856656099b710e89919", None, NO) // Surfer's Paradise
|
||||
Record(@"b7988373b81992d08056560d15d3e32d9d3888bc", None, NO) // Surround
|
||||
Record(@"6c993b4c70cfed390f1f436fdbaa1f81495be18e", None, NO) // Survival Run
|
||||
Record(@"e2b3b43cadf2f2c91c1ec615651ff9b1e295d065", None, NO) // sv2k12
|
||||
Record(@"0d59545b22e15019a33de16999a57dae1f998283", None, NO) // Swordfight
|
||||
Record(@"05db3d09fa3dac80c70aae2e39f1ad7c31c62f02", Atari8k, NO) // SwordQuest — EarthWorld
|
||||
Record(@"5c3cf976edbea5ded66634a284787f965616d97e", Atari8k, NO) // SwordQuest — FireWorld
|
||||
Record(@"569fcb67ca1674b48e2f3a2e7af7077a374402de", Atari8k, NO) // SwordQuest — WaterWorld
|
||||
Record(@"55e98fe14b07460734781a6aa2f4f1646830c0af", None, NO) // Tac-Scan
|
||||
Record(@"13a9d86cbde32a1478ef0c7ef412427b13bd6222", None, NO) // Tanks But No Tanks
|
||||
Record(@"ee8bc1710a67c33e9f95bb05cc3d8f841093fde2", None, NO) // Tapeworm
|
||||
Record(@"e986e1818e747beb9b33ce4dff1cdc6b55bdb620", Atari8k, NO) // Tapper
|
||||
Record(@"bae73700ba6532e9e6415b6471d115bdb7805464", None, NO) // Task Force
|
||||
Record(@"7aaf6be610ba6ea1205bdd5ed60838ccb8280d57", Atari8k, NO) // Tax Avoiders
|
||||
Record(@"476f0c565f54accecafd72c63b0464f469ed20ea", Atari8k, NO) // Taz
|
||||
Record(@"7efc0ebe334dde84e25fa020ecde4fddcbea9e8f", Atari8k, NO) // Telepathy
|
||||
Record(@"bf4d570c1c738a4d6d00237e25c62e9c3225f98f", Atari8k, NO) // Tempest
|
||||
Record(@"3d30e7ed617168d852923def2000c9c0a8b728c6", None, NO) // Tennis
|
||||
Record(@"53413577afe7def1d390e3892c45822405513c07", Atari8k, NO) // The A-Team
|
||||
Record(@"1f834923eac271bf04c18621ac2aada68d426917", None, NO) // The Earth Dies Screaming
|
||||
Record(@"5a641caa2ab3c7c0cd5deb027acbc58efccf8d6a", None, NO) // The Music Machine
|
||||
Record(@"717122a4184bc8db41e65ab7c369c40b21c048d9", None, NO) // The Texas Chainsaw Massacre
|
||||
Record(@"49bebad3e2eb210591be709a6ec7e4f0864265ab", None, NO) // This Planet Sucks
|
||||
Record(@"9a52fa88bd7455044f00548e9615452131d1c711", None, NO) // Threshold
|
||||
Record(@"09608cfaa7c6e9638f12a1cff9dd5036c9effa43", Atari16k, NO) // Thrust
|
||||
Record(@"3cc8bcc0ff5164303433f469aa4da2eb256d1ad0", None, NO) // Thunderground
|
||||
Record(@"53ee70d4b35ee3df3ffb95fa360bddb4f2f56ab2", ActivisionStack, NO)// Thwocker
|
||||
Record(@"387358514964d0b6b55f9431576a59b55869f7ab", Atari8k, NO) // Time Pilot
|
||||
Record(@"979d9b0b0f32b40c0a0568be65a0bc5ef36ca6d0", Atari8k, NO) // Title Match Pro Wrestling
|
||||
Record(@"fcd339065a012c9fe47efbec62969cbc32f3fbf0", Atari8k, NO) // Tomarc the Barbarian
|
||||
Record(@"d82ac7237df54cc8688e3074b58433a7dd6b7d11", ParkerBros, NO) // Tooth Protectors
|
||||
Record(@"b344b3e042447afbb3e40292dc4ca063d5d1110d", None, NO) // Towering Inferno
|
||||
Record(@"005a6a53f5a856f0bdbca519af1ef236aaa1494d", Atari16k, NO) // Track and Field
|
||||
Record(@"9a9917d82362c77b4d396f56966219fc248edf47", None, NO) // Treasure Below
|
||||
Record(@"86c563db11db9afbffbd73c55e9fae9b2f69be4f", None, NO) // Trick Shot
|
||||
Record(@"0ec58a3a5a27d1b82a5f9aabab02f9a8387b6956", None, NO) // TRON — Deadly Discs
|
||||
Record(@"fc1a0b58765a7dcbd8e33562e1074ddd9e0ac624", CBSRamPlus, NO) // Tunnel Runner
|
||||
Record(@"1162fe46977f01b4d25efab813e0d05ec90aeadc", None, NO) // Turmoil
|
||||
Record(@"a4d6bac854a70d2c55946932f1511cc62db7d4aa", ParkerBros, NO) // Tutankham
|
||||
Record(@"bd0ca4884c85db2323f5a4be5266aabb99d84542", None, NO) // TVNoise.bin
|
||||
Record(@"286106fb973530bc3e2af13240f28c4bcb37e642", None, NO) // Universal Chaos
|
||||
Record(@"6bde671a50330af154ed15e73fdba3fa55f23d87", Atari8k, NO) // Up 'n Down
|
||||
Record(@"01475d037cb7a2a892be09d67083102fa9159216", Atari8k, NO) // Vanguard
|
||||
Record(@"dce98883e813d77e03a5de975d4c52bfb34e7f77", None, NO) // Vault Assault
|
||||
Record(@"17626ae7bfd10bcde14903040baee0923ecf41dd", None, NO) // Venture II
|
||||
Record(@"0305dfc99bf192e53452a1e0408ccc148940afcd", None, NO) // Venture
|
||||
Record(@"babae88a832b76d8c5af6ea63b8f10a0da5bb992", None, NO) // Video Checkers
|
||||
Record(@"043ef523e4fcb9fc2fc2fda21f15671bf8620fc3", None, NO) // Video Chess
|
||||
Record(@"1554b146d076b64776bf49136cea01f60eeba4c1", None, NO) // Video Jogger
|
||||
Record(@"3b18db73933747851eba9a0ffa3c12b9f602a95c", CommaVid, NO) // Video Life
|
||||
Record(@"1ffe89d79d55adabc0916b95cc37e18619ef7830", None, NO) // Video Olympics
|
||||
Record(@"2c16c1a6374c8e22275d152d93dd31ffba26271f", None, NO) // Video Pinball
|
||||
Record(@"dec2a3e9b366dce2b63dc1c13662d3f22420a22e", None, NO) // Video Reflex
|
||||
Record(@"a345a5696f1d63a879e7bb7e3a74c825e97ef7c3", None, NO) // Video Simon
|
||||
Record(@"2ebd0f43ee76833f75759ac1bbb45a8e0c3b86e9", None, NO) // Vulture Attack
|
||||
Record(@"73072295721453065d62d9136343b81310a4d225", None, NO) // Wabbit
|
||||
Record(@"482bd349222b8c702e125c27fd516e73af13967b", None, NO) // Wall Ball
|
||||
Record(@"6de42bbc4766b26301e291ba00f7f7a9ac748639", None, NO) // Wall Break
|
||||
Record(@"2d7563d337cbc0cdf4fc14f69853ab6757697788", None, NO) // Warlords
|
||||
Record(@"232a370519a7fcce121e15f850d0d3671909f8b8", None, NO) // Warplock
|
||||
Record(@"e325c5c0501ff527f06e6518526f9eefed309e89", None, NO) // Waring Worms
|
||||
Record(@"472215fcb46cec905576d539efc8043488efc4ed", None, NO) // Westward Ho
|
||||
Record(@"16df34446af2e6035ca871a00e1e8a008cfb8df4", Atari8k, NO) // Wing War
|
||||
Record(@"6850d329e8ab403bdae38850665a2eff91278e92", Atari16k, NO) // Winter Games
|
||||
Record(@"326e5e63a54ec6a0231fd38e62e352004d4719fe", None, NO) // Wizard of Wor
|
||||
Record(@"e4b0f68abff3273cdd2b973639d607ae4a700adc", None, NO) // Wizard
|
||||
Record(@"806c5a8a7b042a1a3ada1b6f29451a3446f93da3", None, NO) // Word Zapper
|
||||
Record(@"36a1e73eb5aa5c3cd0b01af5117d19b8c36071e4", None, NO) // Worm War 1
|
||||
Record(@"1c5d151e86c0a0bbdf3b33ef153888c6be78c36b", None, NO) // X-Man
|
||||
Record(@"160b6e36437ad6acbc2686fbde1002e2fa88c5fb", Atari16k, NO) // Xenophobe
|
||||
Record(@"73133b81196e5cbc1cec99eefc1223ddb8f4ca83", Atari8k, NO) // Xevious
|
||||
Record(@"6a1e0142c6886a6589a58e029e5aec6b72f7d27f", None, NO) // Yahtzee
|
||||
Record(@"e2cd8996c1cf929e29130690024d1ec23d3b0bde", None, NO) // Yars' Revenge
|
||||
Record(@"58c2f6abc5599cd35c0e722f24bcc128ac8f9a30", Atari8k, NO) // Zaxxon
|
||||
};
|
||||
#undef Record
|
||||
|
||||
@interface AtariStaticAnalyserTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation AtariStaticAnalyserTests
|
||||
|
||||
- (void)testROMsOfSize:(NSInteger)size
|
||||
{
|
||||
NSString *basePath = [[[NSBundle bundleForClass:[self class]] resourcePath] stringByAppendingPathComponent:@"Atari ROMs"];
|
||||
for(NSString *testFile in [[NSFileManager defaultManager] contentsOfDirectoryAtPath:basePath error:nil])
|
||||
{
|
||||
NSString *fullPath = [basePath stringByAppendingPathComponent:testFile];
|
||||
|
||||
// get a SHA1 for the file
|
||||
NSData *fileData = [NSData dataWithContentsOfFile:fullPath];
|
||||
if(size > 0 && [fileData length] != (NSUInteger)size) continue;
|
||||
uint8_t sha1Bytes[CC_SHA1_DIGEST_LENGTH];
|
||||
CC_SHA1([fileData bytes], (CC_LONG)[fileData length], sha1Bytes);
|
||||
NSMutableString *sha1 = [[NSMutableString alloc] init];
|
||||
for(int c = 0; c < CC_SHA1_DIGEST_LENGTH; c++) [sha1 appendFormat:@"%02x", sha1Bytes[c]];
|
||||
|
||||
// get an analysis of the file
|
||||
std::list<StaticAnalyser::Target> targets = StaticAnalyser::GetTargets([fullPath UTF8String]);
|
||||
|
||||
// grab the ROM record
|
||||
AtariROMRecord *romRecord = romRecordsBySHA1[sha1];
|
||||
if(!romRecord) continue;
|
||||
|
||||
// assert equality
|
||||
XCTAssert(targets.front().atari.paging_model == romRecord.pagingModel, @"%@; should be %d, is %d", testFile, romRecord.pagingModel, targets.front().atari.paging_model);
|
||||
XCTAssert(targets.front().atari.uses_superchip == romRecord.usesSuperchip, @"%@; should be %@", testFile, romRecord.usesSuperchip ? @"true" : @"false");
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testAtariROMs { [self testROMsOfSize:-1]; } // This will duplicate all tests below, but also catch anything that isn't 2, 4, 8, 12, 16 or 32kb in size.
|
||||
- (void)test2kROMs { [self testROMsOfSize:2048]; }
|
||||
- (void)test4kROMs { [self testROMsOfSize:4096]; }
|
||||
- (void)test8kROMs { [self testROMsOfSize:8192]; }
|
||||
- (void)test12kROMs { [self testROMsOfSize:12288]; }
|
||||
- (void)test16kROMs { [self testROMsOfSize:16384]; }
|
||||
- (void)test32kROMs { [self testROMsOfSize:32768]; }
|
||||
|
||||
@end
|
||||
119
OSBindings/Mac/Clock SignalTests/TIATests.mm
Normal file
@@ -0,0 +1,119 @@
|
||||
//
|
||||
// TIATests.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 12/02/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "TIA.hpp"
|
||||
|
||||
static uint8_t *line;
|
||||
static void receive_line(uint8_t *next_line)
|
||||
{
|
||||
line = next_line;
|
||||
}
|
||||
|
||||
@interface TIATests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation TIATests {
|
||||
std::unique_ptr<Atari2600::TIA> _tia;
|
||||
}
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
std::function<void(uint8_t *)> function = receive_line;
|
||||
_tia.reset(new Atari2600::TIA(function));
|
||||
line = nullptr;
|
||||
|
||||
_tia->set_playfield(0, 0x00);
|
||||
_tia->set_playfield(1, 0x00);
|
||||
_tia->set_playfield(2, 0x00);
|
||||
_tia->set_player_graphic(0, 0x00);
|
||||
_tia->set_player_graphic(1, 0x00);
|
||||
_tia->set_ball_enable(false);
|
||||
_tia->set_missile_enable(0, false);
|
||||
_tia->set_missile_enable(1, false);
|
||||
}
|
||||
|
||||
- (void)testReflectedPlayfield
|
||||
{
|
||||
// set reflected, bit pattern 1000
|
||||
_tia->set_playfield_control_and_ball_size(1);
|
||||
_tia->set_playfield(0, 0x10);
|
||||
_tia->set_playfield(1, 0xf0);
|
||||
_tia->set_playfield(2, 0x0e);
|
||||
_tia->run_for_cycles(228);
|
||||
|
||||
XCTAssert(line != nullptr, @"228 cycles should have ended the line");
|
||||
|
||||
uint8_t expected_line[] = {
|
||||
1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1
|
||||
};
|
||||
XCTAssert(!memcmp(expected_line, line, sizeof(expected_line)));
|
||||
}
|
||||
|
||||
- (void)testRepeatedPlayfield
|
||||
{
|
||||
// set reflected, bit pattern 1000
|
||||
_tia->set_playfield_control_and_ball_size(0);
|
||||
_tia->set_playfield(0, 0x10);
|
||||
_tia->set_playfield(1, 0xf0);
|
||||
_tia->set_playfield(2, 0x0e);
|
||||
|
||||
_tia->run_for_cycles(228);
|
||||
XCTAssert(line != nullptr, @"228 cycles should have ended the line");
|
||||
|
||||
uint8_t expected_line[] = {
|
||||
1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
};
|
||||
XCTAssert(!memcmp(expected_line, line, sizeof(expected_line)));
|
||||
}
|
||||
|
||||
- (void)testSinglePlayer
|
||||
{
|
||||
// set a player graphic, reset position so that it'll appear from column 1
|
||||
_tia->set_player_graphic(0, 0xff);
|
||||
_tia->set_player_position(0);
|
||||
|
||||
_tia->run_for_cycles(228);
|
||||
uint8_t first_expected_line[] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
};
|
||||
XCTAssert(line != nullptr, @"228 cycles should have ended the line");
|
||||
XCTAssert(!memcmp(first_expected_line, line, sizeof(first_expected_line)));
|
||||
line = nullptr;
|
||||
|
||||
_tia->run_for_cycles(228);
|
||||
uint8_t second_expected_line[] = {
|
||||
0, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
};
|
||||
XCTAssert(line != nullptr, @"228 cycles should have ended the line");
|
||||
XCTAssert(!memcmp(second_expected_line, line, sizeof(second_expected_line)));
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -14,8 +14,7 @@
|
||||
|
||||
using namespace Outputs::CRT;
|
||||
|
||||
void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, bool should_alternate)
|
||||
{
|
||||
void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, bool should_alternate) {
|
||||
openGL_output_builder_.set_colour_format(colour_space, colour_cycle_numerator, colour_cycle_denominator);
|
||||
|
||||
const unsigned int syncCapacityLineChargeThreshold = 2;
|
||||
@@ -29,15 +28,16 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di
|
||||
// for horizontal retrace and 500 to 750 µs for vertical retrace in NTSC and PAL TV."
|
||||
|
||||
time_multiplier_ = IntermediateBufferWidth / cycles_per_line;
|
||||
phase_denominator_ = cycles_per_line * colour_cycle_denominator;
|
||||
phase_denominator_ = cycles_per_line * colour_cycle_denominator * time_multiplier_;
|
||||
phase_numerator_ = 0;
|
||||
colour_cycle_numerator_ = colour_cycle_numerator * time_multiplier_;
|
||||
colour_cycle_numerator_ = colour_cycle_numerator;
|
||||
phase_alternates_ = should_alternate;
|
||||
is_alernate_line_ &= phase_alternates_;
|
||||
cycles_per_line_ = cycles_per_line;
|
||||
unsigned int multiplied_cycles_per_line = cycles_per_line * time_multiplier_;
|
||||
|
||||
// generate timing values implied by the given arbuments
|
||||
sync_capacitor_charge_threshold_ = (int)(syncCapacityLineChargeThreshold * multiplied_cycles_per_line);
|
||||
// generate timing values implied by the given arguments
|
||||
sync_capacitor_charge_threshold_ = ((int)(syncCapacityLineChargeThreshold * cycles_per_line) * 3) / 4;
|
||||
|
||||
// create the two flywheels
|
||||
horizontal_flywheel_.reset(new Flywheel(multiplied_cycles_per_line, (millisecondsHorizontalRetraceTime * multiplied_cycles_per_line) >> 6, multiplied_cycles_per_line >> 6));
|
||||
@@ -50,16 +50,14 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di
|
||||
openGL_output_builder_.set_timing(cycles_per_line, multiplied_cycles_per_line, height_of_display, horizontal_flywheel_->get_scan_period(), vertical_flywheel_->get_scan_period(), vertical_flywheel_output_divider_);
|
||||
}
|
||||
|
||||
void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType displayType)
|
||||
{
|
||||
switch(displayType)
|
||||
{
|
||||
void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType displayType) {
|
||||
switch(displayType) {
|
||||
case DisplayType::PAL50:
|
||||
set_new_timing(cycles_per_line, 312, ColourSpace::YUV, 709379, 2500, true); // i.e. 283.7516
|
||||
break;
|
||||
|
||||
case DisplayType::NTSC60:
|
||||
set_new_timing(cycles_per_line, 262, ColourSpace::YIQ, 545, 2, false);
|
||||
set_new_timing(cycles_per_line, 262, ColourSpace::YIQ, 455, 2, false); // i.e. 227.5
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -76,26 +74,22 @@ CRT::CRT(unsigned int common_output_divisor, unsigned int buffer_depth) :
|
||||
is_alernate_line_(false) {}
|
||||
|
||||
CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, bool should_alternate, unsigned int buffer_depth) :
|
||||
CRT(common_output_divisor, buffer_depth)
|
||||
{
|
||||
CRT(common_output_divisor, buffer_depth) {
|
||||
set_new_timing(cycles_per_line, height_of_display, colour_space, colour_cycle_numerator, colour_cycle_denominator, should_alternate);
|
||||
}
|
||||
|
||||
CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, DisplayType displayType, unsigned int buffer_depth) :
|
||||
CRT(common_output_divisor, buffer_depth)
|
||||
{
|
||||
CRT(common_output_divisor, buffer_depth) {
|
||||
set_new_display_type(cycles_per_line, displayType);
|
||||
}
|
||||
|
||||
#pragma mark - Sync loop
|
||||
|
||||
Flywheel::SyncEvent CRT::get_next_vertical_sync_event(bool vsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced)
|
||||
{
|
||||
Flywheel::SyncEvent CRT::get_next_vertical_sync_event(bool vsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) {
|
||||
return vertical_flywheel_->get_next_event_in_period(vsync_is_requested, cycles_to_run_for, cycles_advanced);
|
||||
}
|
||||
|
||||
Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced)
|
||||
{
|
||||
Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) {
|
||||
return horizontal_flywheel_->get_next_event_in_period(hsync_is_requested, cycles_to_run_for, cycles_advanced);
|
||||
}
|
||||
|
||||
@@ -112,10 +106,8 @@ Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested,
|
||||
#define source_output_position_x2() (*(uint16_t *)&next_run[SourceVertexOffsetOfEnds + 2])
|
||||
#define source_phase() next_run[SourceVertexOffsetOfPhaseTimeAndAmplitude + 0]
|
||||
#define source_amplitude() next_run[SourceVertexOffsetOfPhaseTimeAndAmplitude + 2]
|
||||
#define source_phase_time() next_run[SourceVertexOffsetOfPhaseTimeAndAmplitude + 1]
|
||||
|
||||
void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Scan::Type type)
|
||||
{
|
||||
void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bool vsync_requested, const Scan::Type type) {
|
||||
std::unique_lock<std::mutex> output_lock = openGL_output_builder_.get_output_lock();
|
||||
number_of_cycles *= time_multiplier_;
|
||||
|
||||
@@ -138,36 +130,26 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo
|
||||
|
||||
bool is_output_segment = ((is_output_run && next_run_length) && !horizontal_flywheel_->is_in_retrace() && !vertical_flywheel_->is_in_retrace());
|
||||
uint8_t *next_run = nullptr;
|
||||
if(is_output_segment && !openGL_output_builder_.composite_output_buffer_is_full())
|
||||
{
|
||||
if(is_output_segment && !openGL_output_builder_.composite_output_buffer_is_full()) {
|
||||
next_run = openGL_output_builder_.array_builder.get_input_storage(SourceVertexSize);
|
||||
}
|
||||
|
||||
if(next_run)
|
||||
{
|
||||
if(next_run) {
|
||||
// output_y and texture locations will be written later; we won't necessarily know what it is outside of the locked region
|
||||
source_output_position_x1() = (uint16_t)horizontal_flywheel_->get_current_output_position();
|
||||
source_phase() = colour_burst_phase_;
|
||||
source_amplitude() = colour_burst_amplitude_;
|
||||
source_phase_time() = (uint8_t)colour_burst_time_; // assumption: burst was within the first 1/16 of the line
|
||||
}
|
||||
|
||||
// decrement the number of cycles left to run for and increment the
|
||||
// horizontal counter appropriately
|
||||
number_of_cycles -= next_run_length;
|
||||
|
||||
// either charge or deplete the vertical retrace capacitor (making sure it stops at 0)
|
||||
if(vsync_charging)
|
||||
sync_capacitor_charge_level_ += next_run_length;
|
||||
else
|
||||
sync_capacitor_charge_level_ = std::max(sync_capacitor_charge_level_ - (int)next_run_length, 0);
|
||||
|
||||
// react to the incoming event...
|
||||
horizontal_flywheel_->apply_event(next_run_length, (next_run_length == time_until_horizontal_sync_event) ? next_horizontal_sync_event : Flywheel::SyncEvent::None);
|
||||
vertical_flywheel_->apply_event(next_run_length, (next_run_length == time_until_vertical_sync_event) ? next_vertical_sync_event : Flywheel::SyncEvent::None);
|
||||
|
||||
if(next_run)
|
||||
{
|
||||
if(next_run) {
|
||||
source_output_position_x2() = (uint16_t)horizontal_flywheel_->get_current_output_position();
|
||||
}
|
||||
|
||||
@@ -181,68 +163,58 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo
|
||||
|
||||
if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace) is_alernate_line_ ^= phase_alternates_;
|
||||
|
||||
if(needs_endpoint)
|
||||
{
|
||||
if(needs_endpoint) {
|
||||
if(
|
||||
!openGL_output_builder_.array_builder.is_full() &&
|
||||
!openGL_output_builder_.composite_output_buffer_is_full())
|
||||
{
|
||||
if(!is_writing_composite_run_)
|
||||
{
|
||||
!openGL_output_builder_.composite_output_buffer_is_full()) {
|
||||
|
||||
if(!is_writing_composite_run_) {
|
||||
output_run_.x1 = (uint16_t)horizontal_flywheel_->get_current_output_position();
|
||||
output_run_.y = (uint16_t)(vertical_flywheel_->get_current_output_position() / vertical_flywheel_output_divider_);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Get and write all those previously unwritten output ys
|
||||
const uint16_t output_y = openGL_output_builder_.get_composite_output_y();
|
||||
|
||||
// Construct the output run
|
||||
uint8_t *next_run = openGL_output_builder_.array_builder.get_output_storage(OutputVertexSize);
|
||||
if(next_run)
|
||||
{
|
||||
if(next_run) {
|
||||
output_x1() = output_run_.x1;
|
||||
output_position_y() = output_run_.y;
|
||||
output_tex_y() = output_y;
|
||||
output_x2() = (uint16_t)horizontal_flywheel_->get_current_output_position();
|
||||
}
|
||||
openGL_output_builder_.array_builder.flush(
|
||||
[output_y, this] (uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size)
|
||||
{
|
||||
[output_y, this] (uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size) {
|
||||
openGL_output_builder_.texture_builder.flush(
|
||||
[output_y, input_buffer] (const std::vector<TextureBuilder::WriteArea> &write_areas, size_t number_of_write_areas)
|
||||
{
|
||||
for(size_t run = 0; run < number_of_write_areas; run++)
|
||||
{
|
||||
[output_y, input_buffer] (const std::vector<TextureBuilder::WriteArea> &write_areas, size_t number_of_write_areas) {
|
||||
for(size_t run = 0; run < number_of_write_areas; run++) {
|
||||
*(uint16_t *)&input_buffer[run * SourceVertexSize + SourceVertexOffsetOfInputStart + 0] = write_areas[run].x;
|
||||
*(uint16_t *)&input_buffer[run * SourceVertexSize + SourceVertexOffsetOfInputStart + 2] = write_areas[run].y;
|
||||
*(uint16_t *)&input_buffer[run * SourceVertexSize + SourceVertexOffsetOfEnds + 0] = write_areas[run].x + write_areas[run].length;
|
||||
}
|
||||
});
|
||||
for(size_t position = 0; position < input_size; position += SourceVertexSize)
|
||||
{
|
||||
for(size_t position = 0; position < input_size; position += SourceVertexSize) {
|
||||
(*(uint16_t *)&input_buffer[position + SourceVertexOffsetOfOutputStart + 2]) = output_y;
|
||||
}
|
||||
});
|
||||
colour_burst_amplitude_ = 0;
|
||||
}
|
||||
is_writing_composite_run_ ^= true;
|
||||
}
|
||||
}
|
||||
|
||||
if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace)
|
||||
{
|
||||
if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace) {
|
||||
openGL_output_builder_.increment_composite_output_y();
|
||||
}
|
||||
|
||||
// if this is vertical retrace then adcance a field
|
||||
if(next_run_length == time_until_vertical_sync_event && next_vertical_sync_event == Flywheel::SyncEvent::EndRetrace)
|
||||
{
|
||||
if(delegate_)
|
||||
{
|
||||
if(next_run_length == time_until_vertical_sync_event && next_vertical_sync_event == Flywheel::SyncEvent::EndRetrace) {
|
||||
if(delegate_) {
|
||||
frames_since_last_delegate_call_++;
|
||||
if(frames_since_last_delegate_call_ == 20)
|
||||
{
|
||||
if(frames_since_last_delegate_call_ == 20) {
|
||||
output_lock.unlock();
|
||||
delegate_->crt_did_end_batch_of_frames(this, frames_since_last_delegate_call_, vertical_flywheel_->get_and_reset_number_of_surprises());
|
||||
output_lock.lock();
|
||||
frames_since_last_delegate_call_ = 0;
|
||||
}
|
||||
}
|
||||
@@ -267,42 +239,53 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo
|
||||
|
||||
#pragma mark - stream feeding methods
|
||||
|
||||
void CRT::output_scan(const Scan *const scan)
|
||||
{
|
||||
void CRT::output_scan(const Scan *const scan) {
|
||||
const bool this_is_sync = (scan->type == Scan::Type::Sync);
|
||||
const bool is_trailing_edge = (is_receiving_sync_ && !this_is_sync);
|
||||
const bool is_leading_edge = (!is_receiving_sync_ && this_is_sync);
|
||||
is_receiving_sync_ = this_is_sync;
|
||||
|
||||
// Accumulate: (i) a total of the amount of time in sync; and (ii) the amount of time since sync.
|
||||
if(this_is_sync) { cycles_of_sync_ += scan->number_of_cycles; cycles_since_sync_ = 0; }
|
||||
else cycles_since_sync_ += scan->number_of_cycles;
|
||||
|
||||
bool vsync_requested = false;
|
||||
// If it has been at least half a line since sync ended, then it is safe to decide whether what ended
|
||||
// was vertical sync.
|
||||
if(cycles_since_sync_ > (cycles_per_line_ >> 1)) {
|
||||
// If it was vertical sync, set that flag. If it wasn't, clear the summed amount of sync to avoid
|
||||
// a mistaken vertical sync due to an aggregate of horizontals.
|
||||
vsync_requested = (cycles_of_sync_ > sync_capacitor_charge_threshold_);
|
||||
if(vsync_requested || cycles_of_sync_ < (cycles_per_line_ >> 2))
|
||||
cycles_of_sync_ = 0;
|
||||
}
|
||||
|
||||
// This introduces a blackout period close to the expected vertical sync point in which horizontal syncs are not
|
||||
// recognised, effectively causing the horizontal flywheel to freewheel during that period. This attempts to seek
|
||||
// the problem that vertical sync otherwise often starts halfway through a scanline, which confuses the horizontal
|
||||
// flywheel. I'm currently unclear whether this is an accurate solution to this problem.
|
||||
const bool hsync_requested = is_leading_edge && !vertical_flywheel_->is_near_expected_sync();
|
||||
const bool vsync_requested = is_trailing_edge && (sync_capacitor_charge_level_ >= sync_capacitor_charge_threshold_);
|
||||
|
||||
// simplified colour burst logic: if it's within the back porch we'll take it
|
||||
if(scan->type == Scan::Type::ColourBurst)
|
||||
{
|
||||
if(horizontal_flywheel_->get_current_time() < (horizontal_flywheel_->get_standard_period() * 12) >> 6)
|
||||
{
|
||||
colour_burst_time_ = (uint16_t)horizontal_flywheel_->get_current_time();
|
||||
colour_burst_phase_ = scan->phase;
|
||||
if(scan->type == Scan::Type::ColourBurst) {
|
||||
if(!colour_burst_amplitude_ && horizontal_flywheel_->get_current_time() < (horizontal_flywheel_->get_standard_period() * 12) >> 6) {
|
||||
unsigned int position_phase = (horizontal_flywheel_->get_current_time() * colour_cycle_numerator_ * 256) / phase_denominator_;
|
||||
colour_burst_phase_ = (position_phase + scan->phase) & 255;
|
||||
colour_burst_amplitude_ = scan->amplitude;
|
||||
|
||||
colour_burst_phase_ = (colour_burst_phase_ & ~63) + 32;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: inspect raw data for potential colour burst if required
|
||||
|
||||
sync_period_ = is_receiving_sync_ ? (sync_period_ + scan->number_of_cycles) : 0;
|
||||
advance_cycles(scan->number_of_cycles, hsync_requested, vsync_requested, this_is_sync, scan->type);
|
||||
advance_cycles(scan->number_of_cycles, hsync_requested, vsync_requested, scan->type);
|
||||
}
|
||||
|
||||
/*
|
||||
These all merely channel into advance_cycles, supplying appropriate arguments
|
||||
*/
|
||||
void CRT::output_sync(unsigned int number_of_cycles)
|
||||
{
|
||||
void CRT::output_sync(unsigned int number_of_cycles) {
|
||||
Scan scan{
|
||||
.type = Scan::Type::Sync,
|
||||
.number_of_cycles = number_of_cycles
|
||||
@@ -310,8 +293,7 @@ void CRT::output_sync(unsigned int number_of_cycles)
|
||||
output_scan(&scan);
|
||||
}
|
||||
|
||||
void CRT::output_blank(unsigned int number_of_cycles)
|
||||
{
|
||||
void CRT::output_blank(unsigned int number_of_cycles) {
|
||||
Scan scan {
|
||||
.type = Scan::Type::Blank,
|
||||
.number_of_cycles = number_of_cycles
|
||||
@@ -319,8 +301,7 @@ void CRT::output_blank(unsigned int number_of_cycles)
|
||||
output_scan(&scan);
|
||||
}
|
||||
|
||||
void CRT::output_level(unsigned int number_of_cycles)
|
||||
{
|
||||
void CRT::output_level(unsigned int number_of_cycles) {
|
||||
Scan scan {
|
||||
.type = Scan::Type::Level,
|
||||
.number_of_cycles = number_of_cycles,
|
||||
@@ -328,8 +309,7 @@ void CRT::output_level(unsigned int number_of_cycles)
|
||||
output_scan(&scan);
|
||||
}
|
||||
|
||||
void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t amplitude)
|
||||
{
|
||||
void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t amplitude) {
|
||||
Scan scan {
|
||||
.type = Scan::Type::ColourBurst,
|
||||
.number_of_cycles = number_of_cycles,
|
||||
@@ -339,19 +319,17 @@ void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint
|
||||
output_scan(&scan);
|
||||
}
|
||||
|
||||
void CRT::output_default_colour_burst(unsigned int number_of_cycles)
|
||||
{
|
||||
void CRT::output_default_colour_burst(unsigned int number_of_cycles) {
|
||||
Scan scan {
|
||||
.type = Scan::Type::ColourBurst,
|
||||
.number_of_cycles = number_of_cycles,
|
||||
.phase = (uint8_t)((phase_numerator_ * 255) / phase_denominator_ + (is_alernate_line_ ? 128 : 0)),
|
||||
.phase = (uint8_t)((phase_numerator_ * 256) / phase_denominator_ + (is_alernate_line_ ? 128 : 0)),
|
||||
.amplitude = 32
|
||||
};
|
||||
output_scan(&scan);
|
||||
}
|
||||
|
||||
void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider)
|
||||
{
|
||||
void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider) {
|
||||
openGL_output_builder_.texture_builder.reduce_previous_allocation_to(number_of_cycles / source_divider);
|
||||
Scan scan {
|
||||
.type = Scan::Type::Data,
|
||||
@@ -360,8 +338,7 @@ void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider
|
||||
output_scan(&scan);
|
||||
}
|
||||
|
||||
Outputs::CRT::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_of_lines, int first_cycle_after_sync, int number_of_cycles, float aspect_ratio)
|
||||
{
|
||||
Outputs::CRT::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_of_lines, int first_cycle_after_sync, int number_of_cycles, float aspect_ratio) {
|
||||
first_cycle_after_sync *= time_multiplier_;
|
||||
number_of_cycles *= time_multiplier_;
|
||||
|
||||
@@ -397,13 +374,10 @@ Outputs::CRT::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_
|
||||
// adjust to ensure aspect ratio is correct
|
||||
float adjusted_aspect_ratio = (3.0f*aspect_ratio / 4.0f);
|
||||
float ideal_width = height * adjusted_aspect_ratio;
|
||||
if(ideal_width > width)
|
||||
{
|
||||
if(ideal_width > width) {
|
||||
start_x -= (ideal_width - width) * 0.5f;
|
||||
width = ideal_width;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
float ideal_height = width / adjusted_aspect_ratio;
|
||||
start_y -= (ideal_height - height) * 0.5f;
|
||||
height = ideal_height;
|
||||
|
||||
@@ -60,14 +60,13 @@ class CRT {
|
||||
void output_scan(const Scan *scan);
|
||||
|
||||
uint8_t colour_burst_phase_, colour_burst_amplitude_;
|
||||
uint16_t colour_burst_time_;
|
||||
bool is_writing_composite_run_;
|
||||
|
||||
unsigned int phase_denominator_, phase_numerator_, colour_cycle_numerator_;
|
||||
bool is_alernate_line_, phase_alternates_;
|
||||
|
||||
// the outer entry point for dispatching output_sync, output_blank, output_level and output_data
|
||||
void advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Scan::Type type);
|
||||
void advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bool vsync_requested, const Scan::Type type);
|
||||
|
||||
// the inner entry point that determines whether and when the next sync event will occur within
|
||||
// the current output window
|
||||
@@ -82,10 +81,23 @@ class CRT {
|
||||
uint16_t x1, y;
|
||||
} output_run_;
|
||||
|
||||
// The delegate
|
||||
// the delegate
|
||||
Delegate *delegate_;
|
||||
unsigned int frames_since_last_delegate_call_;
|
||||
|
||||
// queued tasks for the OpenGL queue; performed before the next draw
|
||||
std::mutex function_mutex_;
|
||||
std::vector<std::function<void(void)>> enqueued_openGL_functions_;
|
||||
inline void enqueue_openGL_function(const std::function<void(void)> &function) {
|
||||
std::lock_guard<std::mutex> function_guard(function_mutex_);
|
||||
enqueued_openGL_functions_.push_back(function);
|
||||
}
|
||||
|
||||
// sync counter, for determining vertical sync
|
||||
unsigned int cycles_of_sync_;
|
||||
unsigned int cycles_since_sync_;
|
||||
unsigned int cycles_per_line_;
|
||||
|
||||
public:
|
||||
/*! Constructs the CRT with a specified clock rate, height and colour subcarrier frequency.
|
||||
The requested number of buffers, each with the requested number of bytes per pixel,
|
||||
@@ -194,8 +206,7 @@ class CRT {
|
||||
@param required_length The number of samples to allocate.
|
||||
@returns A pointer to the allocated area if room is available; @c nullptr otherwise.
|
||||
*/
|
||||
inline uint8_t *allocate_write_area(size_t required_length)
|
||||
{
|
||||
inline uint8_t *allocate_write_area(size_t required_length) {
|
||||
std::unique_lock<std::mutex> output_lock = openGL_output_builder_.get_output_lock();
|
||||
return openGL_output_builder_.texture_builder.allocate_write_area(required_length);
|
||||
}
|
||||
@@ -203,8 +214,15 @@ class CRT {
|
||||
/*! Causes appropriate OpenGL or OpenGL ES calls to be issued in order to draw the current CRT state.
|
||||
The caller is responsible for ensuring that a valid OpenGL context exists for the duration of this call.
|
||||
*/
|
||||
inline void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty)
|
||||
{
|
||||
inline void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) {
|
||||
{
|
||||
std::lock_guard<std::mutex> function_guard(function_mutex_);
|
||||
for(std::function<void(void)> function : enqueued_openGL_functions_)
|
||||
{
|
||||
function();
|
||||
}
|
||||
enqueued_openGL_functions_.clear();
|
||||
}
|
||||
openGL_output_builder_.draw_frame(output_width, output_height, only_if_dirty);
|
||||
}
|
||||
|
||||
@@ -215,9 +233,10 @@ class CRT {
|
||||
currently held by the CRT will be deleted now via calls to glDeleteTexture and equivalent. If
|
||||
@c false then the references are simply marked as invalid.
|
||||
*/
|
||||
inline void set_openGL_context_will_change(bool should_delete_resources)
|
||||
{
|
||||
openGL_output_builder_.set_openGL_context_will_change(should_delete_resources);
|
||||
inline void set_openGL_context_will_change(bool should_delete_resources) {
|
||||
enqueue_openGL_function([should_delete_resources, this] {
|
||||
openGL_output_builder_.set_openGL_context_will_change(should_delete_resources);
|
||||
});
|
||||
}
|
||||
|
||||
/*! Sets a function that will map from whatever data the machine provided to a composite signal.
|
||||
@@ -227,9 +246,10 @@ class CRT {
|
||||
that evaluates to the composite signal level as a function of a source buffer, sampling location, colour
|
||||
carrier phase and amplitude.
|
||||
*/
|
||||
inline void set_composite_sampling_function(const char *shader)
|
||||
{
|
||||
openGL_output_builder_.set_composite_sampling_function(shader);
|
||||
inline void set_composite_sampling_function(const std::string &shader) {
|
||||
enqueue_openGL_function([shader, this] {
|
||||
openGL_output_builder_.set_composite_sampling_function(shader);
|
||||
});
|
||||
}
|
||||
|
||||
/*! Sets a function that will map from whatever data the machine provided to an RGB signal.
|
||||
@@ -245,25 +265,27 @@ class CRT {
|
||||
* `vec2 coordinate` representing the source buffer location to sample from in the range [0, 1); and
|
||||
* `vec2 icoordinate` representing the source buffer location to sample from as a pixel count, for easier multiple-pixels-per-byte unpacking.
|
||||
*/
|
||||
inline void set_rgb_sampling_function(const char *shader)
|
||||
{
|
||||
openGL_output_builder_.set_rgb_sampling_function(shader);
|
||||
inline void set_rgb_sampling_function(const std::string &shader) {
|
||||
enqueue_openGL_function([shader, this] {
|
||||
openGL_output_builder_.set_rgb_sampling_function(shader);
|
||||
});
|
||||
}
|
||||
|
||||
inline void set_output_device(OutputDevice output_device)
|
||||
{
|
||||
openGL_output_builder_.set_output_device(output_device);
|
||||
inline void set_output_device(OutputDevice output_device) {
|
||||
enqueue_openGL_function([output_device, this] {
|
||||
openGL_output_builder_.set_output_device(output_device);
|
||||
});
|
||||
}
|
||||
|
||||
inline void set_visible_area(Rect visible_area)
|
||||
{
|
||||
openGL_output_builder_.set_visible_area(visible_area);
|
||||
inline void set_visible_area(Rect visible_area) {
|
||||
enqueue_openGL_function([visible_area, this] {
|
||||
openGL_output_builder_.set_visible_area(visible_area);
|
||||
});
|
||||
}
|
||||
|
||||
Rect get_rect_for_area(int first_line_after_sync, int number_of_lines, int first_cycle_after_sync, int number_of_cycles, float aspect_ratio);
|
||||
|
||||
inline void set_delegate(Delegate *delegate)
|
||||
{
|
||||
inline void set_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -11,37 +11,30 @@
|
||||
using namespace Outputs::CRT;
|
||||
|
||||
ArrayBuilder::ArrayBuilder(size_t input_size, size_t output_size) :
|
||||
output_(output_size, nullptr),
|
||||
input_(input_size, nullptr)
|
||||
{}
|
||||
output_(output_size, nullptr),
|
||||
input_(input_size, nullptr) {}
|
||||
|
||||
ArrayBuilder::ArrayBuilder(size_t input_size, size_t output_size, std::function<void(bool is_input, uint8_t *, size_t)> submission_function) :
|
||||
output_(output_size, submission_function),
|
||||
input_(input_size, submission_function)
|
||||
{}
|
||||
output_(output_size, submission_function),
|
||||
input_(input_size, submission_function) {}
|
||||
|
||||
bool ArrayBuilder::is_full()
|
||||
{
|
||||
bool ArrayBuilder::is_full() {
|
||||
bool was_full;
|
||||
was_full = is_full_;
|
||||
return was_full;
|
||||
}
|
||||
|
||||
uint8_t *ArrayBuilder::get_input_storage(size_t size)
|
||||
{
|
||||
uint8_t *ArrayBuilder::get_input_storage(size_t size) {
|
||||
return get_storage(size, input_);
|
||||
}
|
||||
|
||||
uint8_t *ArrayBuilder::get_output_storage(size_t size)
|
||||
{
|
||||
uint8_t *ArrayBuilder::get_output_storage(size_t size) {
|
||||
return get_storage(size, output_);
|
||||
}
|
||||
|
||||
void ArrayBuilder::flush(const std::function<void(uint8_t *input, size_t input_size, uint8_t *output, size_t output_size)> &function)
|
||||
{
|
||||
if(!is_full_)
|
||||
{
|
||||
size_t input_size, output_size;
|
||||
void ArrayBuilder::flush(const std::function<void(uint8_t *input, size_t input_size, uint8_t *output, size_t output_size)> &function) {
|
||||
if(!is_full_) {
|
||||
size_t input_size = 0, output_size = 0;
|
||||
uint8_t *input = input_.get_unflushed(input_size);
|
||||
uint8_t *output = output_.get_unflushed(output_size);
|
||||
function(input, input_size, output, output_size);
|
||||
@@ -51,24 +44,20 @@ void ArrayBuilder::flush(const std::function<void(uint8_t *input, size_t input_s
|
||||
}
|
||||
}
|
||||
|
||||
void ArrayBuilder::bind_input()
|
||||
{
|
||||
void ArrayBuilder::bind_input() {
|
||||
input_.bind();
|
||||
}
|
||||
|
||||
void ArrayBuilder::bind_output()
|
||||
{
|
||||
void ArrayBuilder::bind_output() {
|
||||
output_.bind();
|
||||
}
|
||||
|
||||
ArrayBuilder::Submission ArrayBuilder::submit()
|
||||
{
|
||||
ArrayBuilder::Submission ArrayBuilder::submit() {
|
||||
ArrayBuilder::Submission submission;
|
||||
|
||||
submission.input_size = input_.submit(true);
|
||||
submission.output_size = output_.submit(false);
|
||||
if(is_full_)
|
||||
{
|
||||
if(is_full_) {
|
||||
is_full_ = false;
|
||||
input_.reset();
|
||||
output_.reset();
|
||||
@@ -78,12 +67,10 @@ ArrayBuilder::Submission ArrayBuilder::submit()
|
||||
}
|
||||
|
||||
ArrayBuilder::Buffer::Buffer(size_t size, std::function<void(bool is_input, uint8_t *, size_t)> submission_function) :
|
||||
is_full(false),
|
||||
submission_function_(submission_function),
|
||||
allocated_data(0), flushed_data(0), submitted_data(0)
|
||||
{
|
||||
if(!submission_function_)
|
||||
{
|
||||
is_full(false),
|
||||
submission_function_(submission_function),
|
||||
allocated_data(0), flushed_data(0), submitted_data(0) {
|
||||
if(!submission_function_) {
|
||||
glGenBuffers(1, &buffer);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, buffer);
|
||||
glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)size, NULL, GL_STREAM_DRAW);
|
||||
@@ -91,23 +78,19 @@ ArrayBuilder::Buffer::Buffer(size_t size, std::function<void(bool is_input, uint
|
||||
data.resize(size);
|
||||
}
|
||||
|
||||
ArrayBuilder::Buffer::~Buffer()
|
||||
{
|
||||
ArrayBuilder::Buffer::~Buffer() {
|
||||
if(!submission_function_)
|
||||
glDeleteBuffers(1, &buffer);
|
||||
}
|
||||
|
||||
uint8_t *ArrayBuilder::get_storage(size_t size, Buffer &buffer)
|
||||
{
|
||||
uint8_t *ArrayBuilder::get_storage(size_t size, Buffer &buffer) {
|
||||
uint8_t *pointer = buffer.get_storage(size);
|
||||
if(!pointer) is_full_ = true;
|
||||
return pointer;
|
||||
}
|
||||
|
||||
uint8_t *ArrayBuilder::Buffer::get_storage(size_t size)
|
||||
{
|
||||
if(is_full || allocated_data + size > data.size())
|
||||
{
|
||||
uint8_t *ArrayBuilder::Buffer::get_storage(size_t size) {
|
||||
if(is_full || allocated_data + size > data.size()) {
|
||||
is_full = true;
|
||||
return nullptr;
|
||||
}
|
||||
@@ -116,21 +99,17 @@ uint8_t *ArrayBuilder::Buffer::get_storage(size_t size)
|
||||
return pointer;
|
||||
}
|
||||
|
||||
uint8_t *ArrayBuilder::Buffer::get_unflushed(size_t &size)
|
||||
{
|
||||
if(is_full)
|
||||
{
|
||||
uint8_t *ArrayBuilder::Buffer::get_unflushed(size_t &size) {
|
||||
if(is_full) {
|
||||
return nullptr;
|
||||
}
|
||||
size = allocated_data - flushed_data;
|
||||
return &data[flushed_data];
|
||||
}
|
||||
|
||||
void ArrayBuilder::Buffer::flush()
|
||||
{
|
||||
if(submitted_data)
|
||||
{
|
||||
memcpy(data.data(), &data[submitted_data], allocated_data - submitted_data);
|
||||
void ArrayBuilder::Buffer::flush() {
|
||||
if(submitted_data) {
|
||||
memmove(data.data(), &data[submitted_data], allocated_data - submitted_data);
|
||||
allocated_data -= submitted_data;
|
||||
flushed_data -= submitted_data;
|
||||
submitted_data = 0;
|
||||
@@ -139,13 +118,11 @@ void ArrayBuilder::Buffer::flush()
|
||||
flushed_data = allocated_data;
|
||||
}
|
||||
|
||||
size_t ArrayBuilder::Buffer::submit(bool is_input)
|
||||
{
|
||||
size_t ArrayBuilder::Buffer::submit(bool is_input) {
|
||||
size_t length = flushed_data;
|
||||
if(submission_function_)
|
||||
if(submission_function_) {
|
||||
submission_function_(is_input, data.data(), length);
|
||||
else
|
||||
{
|
||||
} else {
|
||||
glBindBuffer(GL_ARRAY_BUFFER, buffer);
|
||||
uint8_t *destination = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, (GLsizeiptr)length, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_FLUSH_EXPLICIT_BIT);
|
||||
memcpy(destination, data.data(), length);
|
||||
@@ -156,13 +133,11 @@ size_t ArrayBuilder::Buffer::submit(bool is_input)
|
||||
return length;
|
||||
}
|
||||
|
||||
void ArrayBuilder::Buffer::bind()
|
||||
{
|
||||
void ArrayBuilder::Buffer::bind() {
|
||||
glBindBuffer(GL_ARRAY_BUFFER, buffer);
|
||||
}
|
||||
|
||||
void ArrayBuilder::Buffer::reset()
|
||||
{
|
||||
void ArrayBuilder::Buffer::reset() {
|
||||
is_full = false;
|
||||
allocated_data = 0;
|
||||
flushed_data = 0;
|
||||
|
||||
@@ -36,7 +36,7 @@ const GLsizei InputBufferBuilderWidth = 2048;
|
||||
const GLsizei InputBufferBuilderHeight = 512;
|
||||
|
||||
// This is the size of the intermediate buffers used during composite to RGB conversion
|
||||
const GLsizei IntermediateBufferWidth = 4096;
|
||||
const GLsizei IntermediateBufferWidth = 2048;
|
||||
const GLsizei IntermediateBufferHeight = 512;
|
||||
|
||||
// Some internal buffer sizes
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
//
|
||||
|
||||
#include "CRT.hpp"
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cmath>
|
||||
|
||||
#include "CRTOpenGL.hpp"
|
||||
#include "../../../SignalProcessing/FIRFilter.hpp"
|
||||
@@ -16,29 +17,24 @@
|
||||
using namespace Outputs::CRT;
|
||||
|
||||
namespace {
|
||||
static const GLenum composite_texture_unit = GL_TEXTURE0;
|
||||
static const GLenum separated_texture_unit = GL_TEXTURE1;
|
||||
static const GLenum filtered_y_texture_unit = GL_TEXTURE2;
|
||||
static const GLenum filtered_texture_unit = GL_TEXTURE3;
|
||||
static const GLenum source_data_texture_unit = GL_TEXTURE4;
|
||||
static const GLenum pixel_accumulation_texture_unit = GL_TEXTURE5;
|
||||
static const GLenum source_data_texture_unit = GL_TEXTURE0;
|
||||
static const GLenum pixel_accumulation_texture_unit = GL_TEXTURE1;
|
||||
|
||||
static const GLenum composite_texture_unit = GL_TEXTURE2;
|
||||
static const GLenum separated_texture_unit = GL_TEXTURE3;
|
||||
static const GLenum filtered_texture_unit = GL_TEXTURE4;
|
||||
|
||||
static const GLenum work_texture_unit = GL_TEXTURE2;
|
||||
}
|
||||
|
||||
OpenGLOutputBuilder::OpenGLOutputBuilder(size_t bytes_per_pixel) :
|
||||
visible_area_(Rect(0, 0, 1, 1)),
|
||||
composite_src_output_y_(0),
|
||||
composite_shader_(nullptr),
|
||||
rgb_shader_(nullptr),
|
||||
last_output_width_(0),
|
||||
last_output_height_(0),
|
||||
fence_(nullptr),
|
||||
texture_builder(bytes_per_pixel, source_data_texture_unit),
|
||||
array_builder(SourceVertexBufferDataSize, OutputVertexBufferDataSize),
|
||||
composite_texture_(IntermediateBufferWidth, IntermediateBufferHeight, composite_texture_unit),
|
||||
separated_texture_(IntermediateBufferWidth, IntermediateBufferHeight, separated_texture_unit),
|
||||
filtered_y_texture_(IntermediateBufferWidth, IntermediateBufferHeight, filtered_y_texture_unit),
|
||||
filtered_texture_(IntermediateBufferWidth, IntermediateBufferHeight, filtered_texture_unit)
|
||||
{
|
||||
visible_area_(Rect(0, 0, 1, 1)),
|
||||
composite_src_output_y_(0),
|
||||
last_output_width_(0),
|
||||
last_output_height_(0),
|
||||
fence_(nullptr),
|
||||
texture_builder(bytes_per_pixel, source_data_texture_unit),
|
||||
array_builder(SourceVertexBufferDataSize, OutputVertexBufferDataSize) {
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_CONSTANT_COLOR);
|
||||
glBlendColor(0.6f, 0.6f, 0.6f, 1.0f);
|
||||
|
||||
@@ -47,24 +43,43 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(size_t bytes_per_pixel) :
|
||||
|
||||
// create the source vertex array
|
||||
glGenVertexArrays(1, &source_vertex_array_);
|
||||
|
||||
bool supports_texture_barrier = false;
|
||||
#ifdef GL_NV_texture_barrier
|
||||
GLint number_of_extensions;
|
||||
glGetIntegerv(GL_NUM_EXTENSIONS, &number_of_extensions);
|
||||
|
||||
for(GLuint c = 0; c < (GLuint)number_of_extensions; c++) {
|
||||
const char *extension_name = (const char *)glGetStringi(GL_EXTENSIONS, c);
|
||||
if(!strcmp(extension_name, "GL_NV_texture_barrier")) {
|
||||
supports_texture_barrier = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// if(supports_texture_barrier) {
|
||||
// work_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight*2, work_texture_unit));
|
||||
// } else {
|
||||
composite_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, composite_texture_unit, GL_NEAREST));
|
||||
separated_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, separated_texture_unit, GL_NEAREST));
|
||||
filtered_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, filtered_texture_unit, GL_LINEAR));
|
||||
// }
|
||||
}
|
||||
|
||||
OpenGLOutputBuilder::~OpenGLOutputBuilder()
|
||||
{
|
||||
OpenGLOutputBuilder::~OpenGLOutputBuilder() {
|
||||
glDeleteVertexArrays(1, &output_vertex_array_);
|
||||
|
||||
free(composite_shader_);
|
||||
free(rgb_shader_);
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty)
|
||||
{
|
||||
bool OpenGLOutputBuilder::get_is_television_output() {
|
||||
return output_device_ == Television || !rgb_input_shader_program_;
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) {
|
||||
// lock down any other draw_frames
|
||||
draw_mutex_.lock();
|
||||
|
||||
// establish essentials
|
||||
if(!output_shader_program_)
|
||||
{
|
||||
if(!output_shader_program_) {
|
||||
prepare_composite_input_shaders();
|
||||
prepare_rgb_input_shaders();
|
||||
prepare_source_vertex_array();
|
||||
@@ -76,11 +91,9 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
|
||||
set_colour_space_uniforms();
|
||||
}
|
||||
|
||||
if(fence_ != nullptr)
|
||||
{
|
||||
if(fence_ != nullptr) {
|
||||
// if the GPU is still busy, don't wait; we'll catch it next time
|
||||
if(glClientWaitSync(fence_, GL_SYNC_FLUSH_COMMANDS_BIT, only_if_dirty ? 0 : GL_TIMEOUT_IGNORED) == GL_TIMEOUT_EXPIRED)
|
||||
{
|
||||
if(glClientWaitSync(fence_, GL_SYNC_FLUSH_COMMANDS_BIT, only_if_dirty ? 0 : GL_TIMEOUT_IGNORED) == GL_TIMEOUT_EXPIRED) {
|
||||
draw_mutex_.unlock();
|
||||
return;
|
||||
}
|
||||
@@ -89,11 +102,9 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
|
||||
}
|
||||
|
||||
// make sure there's a target to draw to
|
||||
if(!framebuffer_ || framebuffer_->get_height() != output_height || framebuffer_->get_width() != output_width)
|
||||
{
|
||||
std::unique_ptr<OpenGL::TextureTarget> new_framebuffer(new OpenGL::TextureTarget((GLsizei)output_width, (GLsizei)output_height, pixel_accumulation_texture_unit));
|
||||
if(framebuffer_)
|
||||
{
|
||||
if(!framebuffer_ || framebuffer_->get_height() != output_height || framebuffer_->get_width() != output_width) {
|
||||
std::unique_ptr<OpenGL::TextureTarget> new_framebuffer(new OpenGL::TextureTarget((GLsizei)output_width, (GLsizei)output_height, pixel_accumulation_texture_unit, GL_LINEAR));
|
||||
if(framebuffer_) {
|
||||
new_framebuffer->bind_framebuffer();
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
@@ -123,55 +134,62 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
|
||||
output_mutex_.unlock();
|
||||
|
||||
struct RenderStage {
|
||||
OpenGL::TextureTarget *const target;
|
||||
OpenGL::Shader *const shader;
|
||||
OpenGL::TextureTarget *const target;
|
||||
float clear_colour[3];
|
||||
};
|
||||
|
||||
// for composite video, go through four steps to get to something that can be painted to the output
|
||||
RenderStage composite_render_stages[] =
|
||||
{
|
||||
{&composite_texture_, composite_input_shader_program_.get(), {0.0, 0.0, 0.0}},
|
||||
{&separated_texture_, composite_separation_filter_program_.get(), {0.0, 0.5, 0.5}},
|
||||
{&filtered_y_texture_, composite_y_filter_shader_program_.get(), {0.0, 0.5, 0.5}},
|
||||
{&filtered_texture_, composite_chrominance_filter_shader_program_.get(), {0.0, 0.0, 0.0}},
|
||||
RenderStage composite_render_stages[] = {
|
||||
{composite_input_shader_program_.get(), composite_texture_.get(), {0.0, 0.0, 0.0}},
|
||||
{composite_separation_filter_program_.get(), separated_texture_.get(), {0.0, 0.5, 0.5}},
|
||||
{composite_chrominance_filter_shader_program_.get(), filtered_texture_.get(), {0.0, 0.0, 0.0}},
|
||||
{nullptr}
|
||||
};
|
||||
|
||||
// for RGB video, there's only two steps
|
||||
RenderStage rgb_render_stages[] =
|
||||
{
|
||||
{&composite_texture_, rgb_input_shader_program_.get(), {0.0, 0.0, 0.0}},
|
||||
{&filtered_texture_, rgb_filter_shader_program_.get(), {0.0, 0.0, 0.0}},
|
||||
RenderStage rgb_render_stages[] = {
|
||||
{rgb_input_shader_program_.get(), composite_texture_.get(), {0.0, 0.0, 0.0}},
|
||||
{rgb_filter_shader_program_.get(), filtered_texture_.get(), {0.0, 0.0, 0.0}},
|
||||
{nullptr}
|
||||
};
|
||||
|
||||
RenderStage *active_pipeline = (output_device_ == Television || !rgb_input_shader_program_) ? composite_render_stages : rgb_render_stages;
|
||||
RenderStage *active_pipeline = get_is_television_output() ? composite_render_stages : rgb_render_stages;
|
||||
|
||||
if(array_submission.input_size || array_submission.output_size)
|
||||
{
|
||||
if(array_submission.input_size || array_submission.output_size) {
|
||||
// all drawing will be from the source vertex array and without blending
|
||||
glBindVertexArray(source_vertex_array_);
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
while(active_pipeline->target)
|
||||
{
|
||||
#ifdef GL_NV_texture_barrier
|
||||
if(work_texture_) {
|
||||
work_texture_->bind_framebuffer();
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
#endif
|
||||
|
||||
while(active_pipeline->shader) {
|
||||
// switch to the framebuffer and shader associated with this stage
|
||||
active_pipeline->shader->bind();
|
||||
active_pipeline->target->bind_framebuffer();
|
||||
|
||||
// if this is the final stage before painting to the CRT, clear the framebuffer before drawing in order to blank out
|
||||
// those portions for which no input was provided
|
||||
if(!active_pipeline[1].target)
|
||||
{
|
||||
glClearColor(active_pipeline->clear_colour[0], active_pipeline->clear_colour[1], active_pipeline->clear_colour[2], 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
if(!work_texture_) {
|
||||
active_pipeline->target->bind_framebuffer();
|
||||
|
||||
// if this is the final stage before painting to the CRT, clear the framebuffer before drawing in order to blank out
|
||||
// those portions for which no input was provided
|
||||
// if(!active_pipeline[1].shader) {
|
||||
glClearColor(active_pipeline->clear_colour[0], active_pipeline->clear_colour[1], active_pipeline->clear_colour[2], 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
// }
|
||||
}
|
||||
|
||||
// draw
|
||||
glDrawArraysInstanced(GL_LINES, 0, 2, (GLsizei)array_submission.input_size / SourceVertexSize);
|
||||
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei)array_submission.input_size / SourceVertexSize);
|
||||
|
||||
active_pipeline++;
|
||||
#ifdef GL_NV_texture_barrier
|
||||
glTextureBarrierNV();
|
||||
#endif
|
||||
}
|
||||
|
||||
// prepare to transfer to framebuffer
|
||||
@@ -182,8 +200,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
|
||||
glEnable(GL_BLEND);
|
||||
|
||||
// update uniforms, then bind the target
|
||||
if(last_output_width_ != output_width || last_output_height_ != output_height)
|
||||
{
|
||||
if(last_output_width_ != output_width || last_output_height_ != output_height) {
|
||||
output_shader_program_->set_output_size(output_width, output_height, visible_area_);
|
||||
last_output_width_ = output_width;
|
||||
last_output_height_ = output_height;
|
||||
@@ -194,6 +211,10 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
|
||||
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei)array_submission.output_size / OutputVertexSize);
|
||||
}
|
||||
|
||||
#ifdef GL_NV_texture_barrier
|
||||
glTextureBarrierNV();
|
||||
#endif
|
||||
|
||||
// copy framebuffer to the intended place
|
||||
glDisable(GL_BLEND);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
@@ -207,11 +228,9 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
|
||||
draw_mutex_.unlock();
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::reset_all_OpenGL_state()
|
||||
{
|
||||
void OpenGLOutputBuilder::reset_all_OpenGL_state() {
|
||||
composite_input_shader_program_ = nullptr;
|
||||
composite_separation_filter_program_ = nullptr;
|
||||
composite_y_filter_shader_program_ = nullptr;
|
||||
composite_chrominance_filter_shader_program_ = nullptr;
|
||||
rgb_input_shader_program_ = nullptr;
|
||||
rgb_filter_shader_program_ = nullptr;
|
||||
@@ -220,54 +239,52 @@ void OpenGLOutputBuilder::reset_all_OpenGL_state()
|
||||
last_output_width_ = last_output_height_ = 0;
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::set_openGL_context_will_change(bool should_delete_resources)
|
||||
{
|
||||
void OpenGLOutputBuilder::set_openGL_context_will_change(bool should_delete_resources) {
|
||||
output_mutex_.lock();
|
||||
reset_all_OpenGL_state();
|
||||
output_mutex_.unlock();
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::set_composite_sampling_function(const char *shader)
|
||||
{
|
||||
output_mutex_.lock();
|
||||
composite_shader_ = strdup(shader);
|
||||
void OpenGLOutputBuilder::set_composite_sampling_function(const std::string &shader) {
|
||||
std::lock_guard<std::mutex> lock_guard(output_mutex_);
|
||||
composite_shader_ = shader;
|
||||
reset_all_OpenGL_state();
|
||||
output_mutex_.unlock();
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::set_rgb_sampling_function(const char *shader)
|
||||
{
|
||||
output_mutex_.lock();
|
||||
rgb_shader_ = strdup(shader);
|
||||
void OpenGLOutputBuilder::set_rgb_sampling_function(const std::string &shader) {
|
||||
std::lock_guard<std::mutex> lock_guard(output_mutex_);
|
||||
rgb_shader_ = shader;
|
||||
reset_all_OpenGL_state();
|
||||
output_mutex_.unlock();
|
||||
}
|
||||
|
||||
#pragma mark - Program compilation
|
||||
|
||||
void OpenGLOutputBuilder::prepare_composite_input_shaders()
|
||||
{
|
||||
void OpenGLOutputBuilder::prepare_composite_input_shaders() {
|
||||
composite_input_shader_program_ = OpenGL::IntermediateShader::make_source_conversion_shader(composite_shader_, rgb_shader_);
|
||||
composite_input_shader_program_->set_source_texture_unit(source_data_texture_unit);
|
||||
composite_input_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
|
||||
|
||||
composite_separation_filter_program_ = OpenGL::IntermediateShader::make_chroma_luma_separation_shader();
|
||||
composite_separation_filter_program_->set_source_texture_unit(composite_texture_unit);
|
||||
composite_separation_filter_program_->set_source_texture_unit(work_texture_ ? work_texture_unit : composite_texture_unit);
|
||||
composite_separation_filter_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
|
||||
|
||||
composite_y_filter_shader_program_ = OpenGL::IntermediateShader::make_luma_filter_shader();
|
||||
composite_y_filter_shader_program_->set_source_texture_unit(separated_texture_unit);
|
||||
composite_y_filter_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
|
||||
|
||||
composite_chrominance_filter_shader_program_ = OpenGL::IntermediateShader::make_chroma_filter_shader();
|
||||
composite_chrominance_filter_shader_program_->set_source_texture_unit(filtered_y_texture_unit);
|
||||
composite_chrominance_filter_shader_program_->set_source_texture_unit(work_texture_ ? work_texture_unit : separated_texture_unit);
|
||||
composite_chrominance_filter_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
|
||||
|
||||
if(work_texture_) {
|
||||
composite_input_shader_program_->set_is_double_height(true, 0.0f, 0.0f);
|
||||
composite_separation_filter_program_->set_is_double_height(true, 0.0f, 0.5f);
|
||||
composite_chrominance_filter_shader_program_->set_is_double_height(true, 0.5f, 0.0f);
|
||||
} else {
|
||||
composite_input_shader_program_->set_is_double_height(false);
|
||||
composite_separation_filter_program_->set_is_double_height(false);
|
||||
composite_chrominance_filter_shader_program_->set_is_double_height(false);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::prepare_rgb_input_shaders()
|
||||
{
|
||||
if(rgb_shader_)
|
||||
{
|
||||
void OpenGLOutputBuilder::prepare_rgb_input_shaders() {
|
||||
if(rgb_shader_.size()) {
|
||||
rgb_input_shader_program_ = OpenGL::IntermediateShader::make_rgb_source_shader(rgb_shader_);
|
||||
rgb_input_shader_program_->set_source_texture_unit(source_data_texture_unit);
|
||||
rgb_input_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
|
||||
@@ -278,10 +295,8 @@ void OpenGLOutputBuilder::prepare_rgb_input_shaders()
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::prepare_source_vertex_array()
|
||||
{
|
||||
if(composite_input_shader_program_)
|
||||
{
|
||||
void OpenGLOutputBuilder::prepare_source_vertex_array() {
|
||||
if(composite_input_shader_program_) {
|
||||
glBindVertexArray(source_vertex_array_);
|
||||
array_builder.bind_input();
|
||||
|
||||
@@ -292,16 +307,15 @@ void OpenGLOutputBuilder::prepare_source_vertex_array()
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::prepare_output_shader()
|
||||
{
|
||||
void OpenGLOutputBuilder::prepare_output_shader() {
|
||||
output_shader_program_ = OpenGL::OutputShader::make_shader("", "texture(texID, srcCoordinatesVarying).rgb", false);
|
||||
output_shader_program_->set_source_texture_unit(filtered_texture_unit);
|
||||
output_shader_program_->set_source_texture_unit(work_texture_ ? work_texture_unit : filtered_texture_unit);
|
||||
// output_shader_program_->set_source_texture_unit(composite_texture_unit);
|
||||
output_shader_program_->set_origin_is_double_height(!!work_texture_);
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::prepare_output_vertex_array()
|
||||
{
|
||||
if(output_shader_program_)
|
||||
{
|
||||
void OpenGLOutputBuilder::prepare_output_vertex_array() {
|
||||
if(output_shader_program_) {
|
||||
glBindVertexArray(output_vertex_array_);
|
||||
array_builder.bind_output();
|
||||
output_shader_program_->enable_vertex_attribute_with_pointer("horizontal", 2, GL_UNSIGNED_SHORT, GL_FALSE, OutputVertexSize, (void *)OutputVertexOffsetOfHorizontal, 1);
|
||||
@@ -311,19 +325,17 @@ void OpenGLOutputBuilder::prepare_output_vertex_array()
|
||||
|
||||
#pragma mark - Public Configuration
|
||||
|
||||
void OpenGLOutputBuilder::set_output_device(OutputDevice output_device)
|
||||
{
|
||||
if(output_device_ != output_device)
|
||||
{
|
||||
void OpenGLOutputBuilder::set_output_device(OutputDevice output_device) {
|
||||
if(output_device_ != output_device) {
|
||||
output_device_ = output_device;
|
||||
composite_src_output_y_ = 0;
|
||||
last_output_width_ = 0;
|
||||
last_output_height_ = 0;
|
||||
set_output_shader_width();
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::set_timing(unsigned int input_frequency, unsigned int cycles_per_line, unsigned int height_of_display, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider)
|
||||
{
|
||||
void OpenGLOutputBuilder::set_timing(unsigned int input_frequency, unsigned int cycles_per_line, unsigned int height_of_display, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider) {
|
||||
output_mutex_.lock();
|
||||
input_frequency_ = input_frequency;
|
||||
cycles_per_line_ = cycles_per_line;
|
||||
@@ -338,8 +350,7 @@ void OpenGLOutputBuilder::set_timing(unsigned int input_frequency, unsigned int
|
||||
|
||||
#pragma mark - Internal Configuration
|
||||
|
||||
void OpenGLOutputBuilder::set_colour_space_uniforms()
|
||||
{
|
||||
void OpenGLOutputBuilder::set_colour_space_uniforms() {
|
||||
GLfloat rgbToYUV[] = {0.299f, -0.14713f, 0.615f, 0.587f, -0.28886f, -0.51499f, 0.114f, 0.436f, -0.10001f};
|
||||
GLfloat yuvToRGB[] = {1.0f, 1.0f, 1.0f, 0.0f, -0.39465f, 2.03211f, 1.13983f, -0.58060f, 0.0f};
|
||||
|
||||
@@ -348,8 +359,7 @@ void OpenGLOutputBuilder::set_colour_space_uniforms()
|
||||
|
||||
GLfloat *fromRGB, *toRGB;
|
||||
|
||||
switch(colour_space_)
|
||||
{
|
||||
switch(colour_space_) {
|
||||
case ColourSpace::YIQ:
|
||||
fromRGB = rgbToYIQ;
|
||||
toRGB = yiqToRGB;
|
||||
@@ -362,30 +372,48 @@ void OpenGLOutputBuilder::set_colour_space_uniforms()
|
||||
}
|
||||
|
||||
if(composite_input_shader_program_) composite_input_shader_program_->set_colour_conversion_matrices(fromRGB, toRGB);
|
||||
if(composite_separation_filter_program_) composite_separation_filter_program_->set_colour_conversion_matrices(fromRGB, toRGB);
|
||||
if(composite_chrominance_filter_shader_program_) composite_chrominance_filter_shader_program_->set_colour_conversion_matrices(fromRGB, toRGB);
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::set_timing_uniforms()
|
||||
{
|
||||
OpenGL::IntermediateShader *intermediate_shaders[] = {
|
||||
composite_input_shader_program_.get(),
|
||||
composite_separation_filter_program_.get(),
|
||||
composite_y_filter_shader_program_.get(),
|
||||
composite_chrominance_filter_shader_program_.get()
|
||||
};
|
||||
bool extends = false;
|
||||
float phaseCyclesPerTick = (float)colour_cycle_numerator_ / (float)(colour_cycle_denominator_ * cycles_per_line_);
|
||||
for(int c = 0; c < 3; c++)
|
||||
{
|
||||
if(intermediate_shaders[c]) intermediate_shaders[c]->set_phase_cycles_per_sample(phaseCyclesPerTick, extends);
|
||||
extends = true;
|
||||
}
|
||||
|
||||
if(output_shader_program_) output_shader_program_->set_timing(height_of_display_, cycles_per_line_, horizontal_scan_period_, vertical_scan_period_, vertical_period_divider_);
|
||||
|
||||
float colour_subcarrier_frequency = (float)colour_cycle_numerator_ / (float)colour_cycle_denominator_;
|
||||
if(composite_separation_filter_program_) composite_separation_filter_program_->set_separation_frequency(cycles_per_line_, colour_subcarrier_frequency);
|
||||
if(composite_y_filter_shader_program_) composite_y_filter_shader_program_->set_filter_coefficients(cycles_per_line_, colour_subcarrier_frequency * 0.25f);
|
||||
if(composite_chrominance_filter_shader_program_) composite_chrominance_filter_shader_program_->set_filter_coefficients(cycles_per_line_, colour_subcarrier_frequency * 0.5f);
|
||||
if(rgb_filter_shader_program_) rgb_filter_shader_program_->set_filter_coefficients(cycles_per_line_, (float)input_frequency_ * 0.5f);
|
||||
float OpenGLOutputBuilder::get_composite_output_width() const {
|
||||
return ((float)colour_cycle_numerator_ * 4.0f) / (float)(colour_cycle_denominator_ * IntermediateBufferWidth);
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::set_output_shader_width() {
|
||||
if(output_shader_program_) {
|
||||
const float width = get_is_television_output() ? get_composite_output_width() : 1.0f;
|
||||
output_shader_program_->set_input_width_scaler(width);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::set_timing_uniforms() {
|
||||
const float colour_subcarrier_frequency = (float)colour_cycle_numerator_ / (float)colour_cycle_denominator_;
|
||||
const float output_width = get_composite_output_width();
|
||||
const float sample_cycles_per_line = cycles_per_line_ / output_width;
|
||||
|
||||
if(composite_separation_filter_program_) {
|
||||
composite_separation_filter_program_->set_width_scalers(output_width, output_width);
|
||||
composite_separation_filter_program_->set_separation_frequency(sample_cycles_per_line, colour_subcarrier_frequency);
|
||||
composite_separation_filter_program_->set_extension(6.0f);
|
||||
}
|
||||
if(composite_chrominance_filter_shader_program_) {
|
||||
composite_chrominance_filter_shader_program_->set_width_scalers(output_width, output_width);
|
||||
composite_chrominance_filter_shader_program_->set_extension(5.0f);
|
||||
}
|
||||
if(rgb_filter_shader_program_) {
|
||||
rgb_filter_shader_program_->set_width_scalers(1.0f, 1.0f);
|
||||
rgb_filter_shader_program_->set_filter_coefficients(sample_cycles_per_line, (float)input_frequency_ * 0.5f);
|
||||
}
|
||||
if(output_shader_program_) {
|
||||
set_output_shader_width();
|
||||
output_shader_program_->set_timing(height_of_display_, cycles_per_line_, horizontal_scan_period_, vertical_scan_period_, vertical_period_divider_);
|
||||
}
|
||||
if(composite_input_shader_program_) {
|
||||
composite_input_shader_program_->set_width_scalers(1.0f, output_width);
|
||||
composite_input_shader_program_->set_extension(0.0f);
|
||||
}
|
||||
if(rgb_input_shader_program_) {
|
||||
rgb_input_shader_program_->set_width_scalers(1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,8 +47,8 @@ class OpenGLOutputBuilder {
|
||||
Rect visible_area_;
|
||||
|
||||
// Other things the caller may have provided.
|
||||
char *composite_shader_;
|
||||
char *rgb_shader_;
|
||||
std::string composite_shader_;
|
||||
std::string rgb_shader_;
|
||||
|
||||
// Methods used by the OpenGL code
|
||||
void prepare_output_shader();
|
||||
@@ -66,13 +66,19 @@ class OpenGLOutputBuilder {
|
||||
GLsizei composite_src_output_y_;
|
||||
|
||||
std::unique_ptr<OpenGL::OutputShader> output_shader_program_;
|
||||
std::unique_ptr<OpenGL::IntermediateShader> composite_input_shader_program_, composite_separation_filter_program_, composite_y_filter_shader_program_, composite_chrominance_filter_shader_program_;
|
||||
std::unique_ptr<OpenGL::IntermediateShader> rgb_input_shader_program_, rgb_filter_shader_program_;
|
||||
|
||||
OpenGL::TextureTarget composite_texture_; // receives raw composite levels
|
||||
OpenGL::TextureTarget separated_texture_; // receives unfiltered Y in the R channel plus unfiltered but demodulated chrominance in G and B
|
||||
OpenGL::TextureTarget filtered_y_texture_; // receives filtered Y in the R channel plus unfiltered chrominance in G and B
|
||||
OpenGL::TextureTarget filtered_texture_; // receives filtered YIQ or YUV
|
||||
std::unique_ptr<OpenGL::IntermediateShader> composite_input_shader_program_;
|
||||
std::unique_ptr<OpenGL::IntermediateShader> composite_separation_filter_program_;
|
||||
std::unique_ptr<OpenGL::IntermediateShader> composite_chrominance_filter_shader_program_;
|
||||
|
||||
std::unique_ptr<OpenGL::IntermediateShader> rgb_input_shader_program_;
|
||||
std::unique_ptr<OpenGL::IntermediateShader> rgb_filter_shader_program_;
|
||||
|
||||
std::unique_ptr<OpenGL::TextureTarget> composite_texture_; // receives raw composite levels
|
||||
std::unique_ptr<OpenGL::TextureTarget> separated_texture_; // receives filtered Y in the R channel plus unfiltered but demodulated chrominance in G and B
|
||||
std::unique_ptr<OpenGL::TextureTarget> filtered_texture_; // receives filtered YIQ or YUV
|
||||
|
||||
std::unique_ptr<OpenGL::TextureTarget> work_texture_; // used for all intermediate rendering if texture fences are supported
|
||||
|
||||
std::unique_ptr<OpenGL::TextureTarget> framebuffer_; // the current pixel output
|
||||
|
||||
@@ -88,6 +94,9 @@ class OpenGLOutputBuilder {
|
||||
void reset_all_OpenGL_state();
|
||||
|
||||
GLsync fence_;
|
||||
float get_composite_output_width() const;
|
||||
void set_output_shader_width();
|
||||
bool get_is_television_output();
|
||||
|
||||
public:
|
||||
// These two are protected by output_mutex_.
|
||||
@@ -97,8 +106,7 @@ class OpenGLOutputBuilder {
|
||||
OpenGLOutputBuilder(size_t bytes_per_pixel);
|
||||
~OpenGLOutputBuilder();
|
||||
|
||||
inline void set_colour_format(ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator)
|
||||
{
|
||||
inline void set_colour_format(ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator) {
|
||||
std::lock_guard<std::mutex> output_guard(output_mutex_);
|
||||
colour_space_ = colour_space;
|
||||
colour_cycle_numerator_ = colour_cycle_numerator;
|
||||
@@ -106,41 +114,35 @@ class OpenGLOutputBuilder {
|
||||
set_colour_space_uniforms();
|
||||
}
|
||||
|
||||
inline void set_visible_area(Rect visible_area)
|
||||
{
|
||||
inline void set_visible_area(Rect visible_area) {
|
||||
visible_area_ = visible_area;
|
||||
}
|
||||
|
||||
inline std::unique_lock<std::mutex> get_output_lock()
|
||||
{
|
||||
inline std::unique_lock<std::mutex> get_output_lock() {
|
||||
return std::unique_lock<std::mutex>(output_mutex_);
|
||||
}
|
||||
|
||||
inline OutputDevice get_output_device()
|
||||
{
|
||||
inline OutputDevice get_output_device() {
|
||||
return output_device_;
|
||||
}
|
||||
|
||||
inline uint16_t get_composite_output_y()
|
||||
{
|
||||
inline uint16_t get_composite_output_y() {
|
||||
return (uint16_t)composite_src_output_y_;
|
||||
}
|
||||
|
||||
inline bool composite_output_buffer_is_full()
|
||||
{
|
||||
inline bool composite_output_buffer_is_full() {
|
||||
return composite_src_output_y_ == IntermediateBufferHeight;
|
||||
}
|
||||
|
||||
inline void increment_composite_output_y()
|
||||
{
|
||||
inline void increment_composite_output_y() {
|
||||
if(!composite_output_buffer_is_full())
|
||||
composite_src_output_y_++;
|
||||
}
|
||||
|
||||
void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty);
|
||||
void set_openGL_context_will_change(bool should_delete_resources);
|
||||
void set_composite_sampling_function(const char *shader);
|
||||
void set_rgb_sampling_function(const char *shader);
|
||||
void set_composite_sampling_function(const std::string &shader);
|
||||
void set_rgb_sampling_function(const std::string &shader);
|
||||
void set_output_device(OutputDevice output_device);
|
||||
void set_timing(unsigned int input_frequency, unsigned int cycles_per_line, unsigned int height_of_display, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider);
|
||||
};
|
||||
|
||||
@@ -21,8 +21,7 @@ namespace CRT {
|
||||
|
||||
The @c Flywheel will attempt to converge with timing implied by synchronisation pulses.
|
||||
*/
|
||||
struct Flywheel
|
||||
{
|
||||
struct Flywheel {
|
||||
/*!
|
||||
Constructs an instance of @c Flywheel.
|
||||
|
||||
@@ -61,26 +60,18 @@ struct Flywheel
|
||||
|
||||
@returns The next synchronisation event.
|
||||
*/
|
||||
inline SyncEvent get_next_event_in_period(bool sync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced)
|
||||
{
|
||||
inline SyncEvent get_next_event_in_period(bool sync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) {
|
||||
// do we recognise this hsync, thereby adjusting future time expectations?
|
||||
if(sync_is_requested)
|
||||
{
|
||||
if(counter_ < sync_error_window_ || counter_ > expected_next_sync_ - sync_error_window_)
|
||||
{
|
||||
if(sync_is_requested) {
|
||||
if(counter_ < sync_error_window_ || counter_ > expected_next_sync_ - sync_error_window_) {
|
||||
unsigned int time_now = (counter_ < sync_error_window_) ? expected_next_sync_ + counter_ : counter_;
|
||||
expected_next_sync_ = (3*expected_next_sync_ + time_now) >> 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
number_of_surprises_++;
|
||||
|
||||
if(counter_ < retrace_time_ + (expected_next_sync_ >> 1))
|
||||
{
|
||||
if(counter_ < retrace_time_ + (expected_next_sync_ >> 1)) {
|
||||
expected_next_sync_ = (3*expected_next_sync_ + standard_period_ + sync_error_window_) >> 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
expected_next_sync_ = (3*expected_next_sync_ + standard_period_ - sync_error_window_) >> 2;
|
||||
}
|
||||
}
|
||||
@@ -90,15 +81,13 @@ struct Flywheel
|
||||
unsigned int proposed_sync_time = cycles_to_run_for;
|
||||
|
||||
// will we end an ongoing retrace?
|
||||
if(counter_ < retrace_time_ && counter_ + proposed_sync_time >= retrace_time_)
|
||||
{
|
||||
if(counter_ < retrace_time_ && counter_ + proposed_sync_time >= retrace_time_) {
|
||||
proposed_sync_time = retrace_time_ - counter_;
|
||||
proposed_event = SyncEvent::EndRetrace;
|
||||
}
|
||||
|
||||
// will we start a retrace?
|
||||
if(counter_ + proposed_sync_time >= expected_next_sync_)
|
||||
{
|
||||
if(counter_ + proposed_sync_time >= expected_next_sync_) {
|
||||
proposed_sync_time = expected_next_sync_ - counter_;
|
||||
proposed_event = SyncEvent::StartRetrace;
|
||||
}
|
||||
@@ -115,12 +104,10 @@ struct Flywheel
|
||||
|
||||
@param event The synchronisation event to apply after that period.
|
||||
*/
|
||||
inline void apply_event(unsigned int cycles_advanced, SyncEvent event)
|
||||
{
|
||||
inline void apply_event(unsigned int cycles_advanced, SyncEvent event) {
|
||||
counter_ += cycles_advanced;
|
||||
|
||||
switch(event)
|
||||
{
|
||||
switch(event) {
|
||||
default: return;
|
||||
case StartRetrace:
|
||||
counter_before_retrace_ = counter_ - retrace_time_;
|
||||
@@ -135,10 +122,8 @@ struct Flywheel
|
||||
|
||||
@returns The current output position.
|
||||
*/
|
||||
inline unsigned int get_current_output_position()
|
||||
{
|
||||
if(counter_ < retrace_time_)
|
||||
{
|
||||
inline unsigned int get_current_output_position() {
|
||||
if(counter_ < retrace_time_) {
|
||||
unsigned int retrace_distance = (counter_ * standard_period_) / retrace_time_;
|
||||
if(retrace_distance > counter_before_retrace_) return 0;
|
||||
return counter_before_retrace_ - retrace_distance;
|
||||
@@ -150,32 +135,28 @@ struct Flywheel
|
||||
/*!
|
||||
@returns the amount of time since retrace last began. Time then counts monotonically up from zero.
|
||||
*/
|
||||
inline unsigned int get_current_time()
|
||||
{
|
||||
inline unsigned int get_current_time() {
|
||||
return counter_;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns whether the output is currently retracing.
|
||||
*/
|
||||
inline bool is_in_retrace()
|
||||
{
|
||||
inline bool is_in_retrace() {
|
||||
return counter_ < retrace_time_;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns the expected length of the scan period (excluding retrace).
|
||||
*/
|
||||
inline unsigned int get_scan_period()
|
||||
{
|
||||
inline unsigned int get_scan_period() {
|
||||
return standard_period_ - retrace_time_;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns the expected length of a complete scan and retrace cycle.
|
||||
*/
|
||||
inline unsigned int get_standard_period()
|
||||
{
|
||||
inline unsigned int get_standard_period() {
|
||||
return standard_period_;
|
||||
}
|
||||
|
||||
@@ -183,8 +164,7 @@ struct Flywheel
|
||||
@returns the number of synchronisation events that have seemed surprising since the last time this method was called;
|
||||
a low number indicates good synchronisation.
|
||||
*/
|
||||
inline unsigned int get_and_reset_number_of_surprises()
|
||||
{
|
||||
inline unsigned int get_and_reset_number_of_surprises() {
|
||||
unsigned int result = number_of_surprises_;
|
||||
number_of_surprises_ = 0;
|
||||
return result;
|
||||
@@ -193,8 +173,7 @@ struct Flywheel
|
||||
/*!
|
||||
@returns `true` if a sync is expected soon or the time at which it was expected was recent.
|
||||
*/
|
||||
inline bool is_near_expected_sync()
|
||||
{
|
||||
inline bool is_near_expected_sync() {
|
||||
return abs((int)counter_ - (int)expected_next_sync_) < (int)standard_period_ / 50;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#else
|
||||
#include <OpenGL/OpenGL.h>
|
||||
#include <OpenGL/gl3.h>
|
||||
#include <OpenGL/gl3ext.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
using namespace OpenGL;
|
||||
|
||||
namespace {
|
||||
const OpenGL::Shader::AttributeBinding bindings[] =
|
||||
{
|
||||
const OpenGL::Shader::AttributeBinding bindings[] = {
|
||||
{"inputPosition", 0},
|
||||
{"outputPosition", 1},
|
||||
{"phaseAndAmplitude", 2},
|
||||
@@ -26,8 +25,7 @@ namespace {
|
||||
};
|
||||
}
|
||||
|
||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const char *fragment_shader, bool use_usampler, bool input_is_inputPosition)
|
||||
{
|
||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const std::string &fragment_shader, bool use_usampler, bool input_is_inputPosition) {
|
||||
const char *sampler_type = use_usampler ? "usampler2D" : "sampler2D";
|
||||
const char *input_variable = input_is_inputPosition ? "inputPosition" : "outputPosition";
|
||||
|
||||
@@ -40,11 +38,14 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const char *
|
||||
"in vec2 ends;"
|
||||
"in vec3 phaseTimeAndAmplitude;"
|
||||
|
||||
"uniform float phaseCyclesPerTick;"
|
||||
"uniform ivec2 outputTextureSize;"
|
||||
"uniform float extension;"
|
||||
"uniform %s texID;"
|
||||
"uniform float offsets[5];"
|
||||
"uniform vec2 widthScalers;"
|
||||
"uniform float inputVerticalOffset;"
|
||||
"uniform float outputVerticalOffset;"
|
||||
"uniform float textureHeightDivisor;"
|
||||
|
||||
"out vec2 phaseAndAmplitudeVarying;"
|
||||
"out vec2 inputPositionsVarying[11];"
|
||||
@@ -53,35 +54,51 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const char *
|
||||
|
||||
"void main(void)"
|
||||
"{"
|
||||
// odd vertices are on the left, even on the right
|
||||
"float extent = float(gl_VertexID & 1);"
|
||||
"float longitudinal = float((gl_VertexID & 2) >> 1);"
|
||||
|
||||
"vec2 inputPosition = vec2(mix(inputStart.x, ends.x, extent), inputStart.y);"
|
||||
"vec2 outputPosition = vec2(mix(outputStart.x, ends.y, extent), outputStart.y);"
|
||||
// inputPosition.x is either inputStart.x or ends.x, depending on whether it is on the left or the right;
|
||||
// outputPosition.x is either outputStart.x or ends.y;
|
||||
// .ys are inputStart.y and outputStart.y respectively
|
||||
"vec2 inputPosition = vec2(mix(inputStart.x, ends.x, extent)*widthScalers[0], inputStart.y + inputVerticalOffset);"
|
||||
"vec2 outputPosition = vec2(mix(outputStart.x, ends.y, extent)*widthScalers[1], outputStart.y + outputVerticalOffset);"
|
||||
|
||||
"inputPosition.y += longitudinal;"
|
||||
"outputPosition.y += longitudinal;"
|
||||
|
||||
// extension is the amount to extend both the input and output by to add a full colour cycle at each end
|
||||
"vec2 extensionVector = vec2(extension, 0.0) * 2.0 * (extent - 0.5);"
|
||||
|
||||
// extended[Input/Output]Position are [input/output]Position with the necessary applied extension
|
||||
"vec2 extendedInputPosition = %s + extensionVector;"
|
||||
"vec2 extendedOutputPosition = outputPosition + extensionVector;"
|
||||
|
||||
// keep iInputPositionVarying in whole source pixels, scale mappedInputPosition to the ordinary normalised range
|
||||
"vec2 textureSize = vec2(textureSize(texID, 0));"
|
||||
"iInputPositionVarying = extendedInputPosition;"
|
||||
"vec2 mappedInputPosition = (extendedInputPosition + vec2(0.0, 0.5)) / textureSize;"
|
||||
"vec2 mappedInputPosition = extendedInputPosition / textureSize;" // + vec2(0.0, 0.5)
|
||||
|
||||
"inputPositionsVarying[0] = mappedInputPosition - (vec2(offsets[0], 0.0) / textureSize);"
|
||||
"inputPositionsVarying[1] = mappedInputPosition - (vec2(offsets[1], 0.0) / textureSize);"
|
||||
"inputPositionsVarying[2] = mappedInputPosition - (vec2(offsets[2], 0.0) / textureSize);"
|
||||
"inputPositionsVarying[3] = mappedInputPosition - (vec2(offsets[3], 0.0) / textureSize);"
|
||||
"inputPositionsVarying[4] = mappedInputPosition - (vec2(offsets[4], 0.0) / textureSize);"
|
||||
// setup input positions spaced as per the supplied offsets; these are for filtering where required
|
||||
"inputPositionsVarying[0] = mappedInputPosition - (vec2(5.0, 0.0) / textureSize);"
|
||||
"inputPositionsVarying[1] = mappedInputPosition - (vec2(4.0, 0.0) / textureSize);"
|
||||
"inputPositionsVarying[2] = mappedInputPosition - (vec2(3.0, 0.0) / textureSize);"
|
||||
"inputPositionsVarying[3] = mappedInputPosition - (vec2(2.0, 0.0) / textureSize);"
|
||||
"inputPositionsVarying[4] = mappedInputPosition - (vec2(1.0, 0.0) / textureSize);"
|
||||
"inputPositionsVarying[5] = mappedInputPosition;"
|
||||
"inputPositionsVarying[6] = mappedInputPosition + (vec2(offsets[4], 0.0) / textureSize);"
|
||||
"inputPositionsVarying[7] = mappedInputPosition + (vec2(offsets[3], 0.0) / textureSize);"
|
||||
"inputPositionsVarying[8] = mappedInputPosition + (vec2(offsets[2], 0.0) / textureSize);"
|
||||
"inputPositionsVarying[9] = mappedInputPosition + (vec2(offsets[1], 0.0) / textureSize);"
|
||||
"inputPositionsVarying[10] = mappedInputPosition + (vec2(offsets[0], 0.0) / textureSize);"
|
||||
"inputPositionsVarying[6] = mappedInputPosition + (vec2(1.0, 0.0) / textureSize);"
|
||||
"inputPositionsVarying[7] = mappedInputPosition + (vec2(2.0, 0.0) / textureSize);"
|
||||
"inputPositionsVarying[8] = mappedInputPosition + (vec2(3.0, 0.0) / textureSize);"
|
||||
"inputPositionsVarying[9] = mappedInputPosition + (vec2(4.0, 0.0) / textureSize);"
|
||||
"inputPositionsVarying[10] = mappedInputPosition + (vec2(5.0, 0.0) / textureSize);"
|
||||
"delayLinePositionVarying = mappedInputPosition - vec2(0.0, 1.0);"
|
||||
|
||||
"phaseAndAmplitudeVarying.x = (phaseCyclesPerTick * (extendedOutputPosition.x - phaseTimeAndAmplitude.y) + (phaseTimeAndAmplitude.x / 256.0)) * 2.0 * 3.141592654;"
|
||||
// setup phaseAndAmplitudeVarying.x as colour burst subcarrier phase, in radians;
|
||||
// setup phaseAndAmplitudeVarying.x as colour burst amplitude
|
||||
"phaseAndAmplitudeVarying.x = (extendedOutputPosition.x + (phaseTimeAndAmplitude.x / 64.0)) * 0.5 * 3.141592654;"
|
||||
"phaseAndAmplitudeVarying.y = 0.33;" // TODO: reinstate connection with (phaseTimeAndAmplitude.y/256.0)
|
||||
|
||||
// determine output position by scaling the output position according to the texture size
|
||||
"vec2 eyePosition = 2.0*(extendedOutputPosition / outputTextureSize) - vec2(1.0) + vec2(1.0)/outputTextureSize;"
|
||||
"gl_Position = vec4(eyePosition, 0.0, 1.0);"
|
||||
"}", sampler_type, input_variable);
|
||||
@@ -92,12 +109,11 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const char *
|
||||
return shader;
|
||||
}
|
||||
|
||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_source_conversion_shader(const char *composite_shader, const char *rgb_shader)
|
||||
{
|
||||
char *composite_sample = (char *)composite_shader;
|
||||
if(!composite_sample)
|
||||
{
|
||||
asprintf(&composite_sample,
|
||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_source_conversion_shader(const std::string &composite_shader, const std::string &rgb_shader) {
|
||||
char *derived_composite_sample = nullptr;
|
||||
const char *composite_sample = composite_shader.c_str();
|
||||
if(!composite_shader.size()) {
|
||||
asprintf(&derived_composite_sample,
|
||||
"%s\n"
|
||||
"uniform mat3 rgbToLumaChroma;"
|
||||
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
|
||||
@@ -107,7 +123,8 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_source_conversion_s
|
||||
"vec2 quadrature = vec2(cos(phase), -sin(phase)) * amplitude;"
|
||||
"return dot(lumaChromaColour, vec3(1.0 - amplitude, quadrature));"
|
||||
"}",
|
||||
rgb_shader);
|
||||
rgb_shader.c_str());
|
||||
composite_sample = derived_composite_sample;
|
||||
}
|
||||
|
||||
char *fragment_shader;
|
||||
@@ -129,7 +146,7 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_source_conversion_s
|
||||
"fragColour = vec4(composite_sample(texID, inputPositionsVarying[5], iInputPositionVarying, phaseAndAmplitudeVarying.x, phaseAndAmplitudeVarying.y));"
|
||||
"}"
|
||||
, composite_sample);
|
||||
if(!composite_shader) free(composite_sample);
|
||||
free(derived_composite_sample);
|
||||
|
||||
std::unique_ptr<IntermediateShader> shader = make_shader(fragment_shader, true, true);
|
||||
free(fragment_shader);
|
||||
@@ -137,8 +154,7 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_source_conversion_s
|
||||
return shader;
|
||||
}
|
||||
|
||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_source_shader(const char *rgb_shader)
|
||||
{
|
||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_source_shader(const std::string &rgb_shader) {
|
||||
char *fragment_shader;
|
||||
asprintf(&fragment_shader,
|
||||
"#version 150\n"
|
||||
@@ -157,7 +173,7 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_source_shader(c
|
||||
"{"
|
||||
"fragColour = rgb_sample(texID, inputPositionsVarying[5], iInputPositionVarying);"
|
||||
"}"
|
||||
, rgb_shader);
|
||||
, rgb_shader.c_str());
|
||||
|
||||
std::unique_ptr<IntermediateShader> shader = make_shader(fragment_shader, true, true);
|
||||
free(fragment_shader);
|
||||
@@ -165,14 +181,12 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_source_shader(c
|
||||
return shader;
|
||||
}
|
||||
|
||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_luma_separation_shader()
|
||||
{
|
||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_luma_separation_shader() {
|
||||
return make_shader(
|
||||
"#version 150\n"
|
||||
|
||||
"in vec2 phaseAndAmplitudeVarying;"
|
||||
"in vec2 inputPositionsVarying[11];"
|
||||
"uniform vec4 weights[3];"
|
||||
|
||||
"out vec3 fragColour;"
|
||||
|
||||
@@ -180,44 +194,26 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_luma_separat
|
||||
|
||||
"void main(void)"
|
||||
"{"
|
||||
"vec4 samples[3] = vec4[]("
|
||||
"vec4("
|
||||
"texture(texID, inputPositionsVarying[0]).r,"
|
||||
"texture(texID, inputPositionsVarying[1]).r,"
|
||||
"texture(texID, inputPositionsVarying[2]).r,"
|
||||
"texture(texID, inputPositionsVarying[3]).r"
|
||||
"),"
|
||||
"vec4("
|
||||
"texture(texID, inputPositionsVarying[4]).r,"
|
||||
"texture(texID, inputPositionsVarying[5]).r,"
|
||||
"texture(texID, inputPositionsVarying[6]).r,"
|
||||
"texture(texID, inputPositionsVarying[7]).r"
|
||||
"),"
|
||||
"vec4("
|
||||
"texture(texID, inputPositionsVarying[8]).r,"
|
||||
"texture(texID, inputPositionsVarying[9]).r,"
|
||||
"texture(texID, inputPositionsVarying[10]).r,"
|
||||
"0.0"
|
||||
")"
|
||||
"vec4 samples = vec4("
|
||||
"texture(texID, inputPositionsVarying[3]).r,"
|
||||
"texture(texID, inputPositionsVarying[4]).r,"
|
||||
"texture(texID, inputPositionsVarying[5]).r,"
|
||||
"texture(texID, inputPositionsVarying[6]).r"
|
||||
");"
|
||||
"float luminance = dot(samples, vec4(0.25));"
|
||||
|
||||
"float luminance = "
|
||||
"dot(vec3("
|
||||
"dot(samples[0], weights[0]),"
|
||||
"dot(samples[1], weights[1]),"
|
||||
"dot(samples[2], weights[2])"
|
||||
"), vec3(1.0));"
|
||||
|
||||
"float chrominance = 0.5 * (samples[1].y - luminance) / phaseAndAmplitudeVarying.y;"
|
||||
// define chroma to be whatever was here, minus luma
|
||||
"float chrominance = 0.5 * (samples.z - luminance) / phaseAndAmplitudeVarying.y;"
|
||||
"luminance /= (1.0 - phaseAndAmplitudeVarying.y);"
|
||||
|
||||
// split choma colours here, as the most direct place, writing out
|
||||
// RGB = (luma, chroma.x, chroma.y)
|
||||
"vec2 quadrature = vec2(cos(phaseAndAmplitudeVarying.x), -sin(phaseAndAmplitudeVarying.x));"
|
||||
"fragColour = vec3(luminance, vec2(0.5) + (chrominance * quadrature));"
|
||||
"}",false, false);
|
||||
}
|
||||
|
||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_filter_shader()
|
||||
{
|
||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_filter_shader() {
|
||||
return make_shader(
|
||||
"#version 150\n"
|
||||
|
||||
@@ -232,41 +228,18 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_filter_shade
|
||||
"void main(void)"
|
||||
"{"
|
||||
"vec3 samples[] = vec3[]("
|
||||
"texture(texID, inputPositionsVarying[0]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[1]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[2]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[3]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[4]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[5]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[6]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[7]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[8]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[9]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[10]).rgb"
|
||||
"texture(texID, inputPositionsVarying[6]).rgb"
|
||||
");"
|
||||
|
||||
"vec4 chromaChannel1[] = vec4[]("
|
||||
"vec4(samples[0].g, samples[1].g, samples[2].g, samples[3].g),"
|
||||
"vec4(samples[4].g, samples[5].g, samples[6].g, samples[7].g),"
|
||||
"vec4(samples[8].g, samples[9].g, samples[10].g, 0.0)"
|
||||
");"
|
||||
"vec4 chromaChannel2[] = vec4[]("
|
||||
"vec4(samples[0].b, samples[1].b, samples[2].b, samples[3].b),"
|
||||
"vec4(samples[4].b, samples[5].b, samples[6].b, samples[7].b),"
|
||||
"vec4(samples[8].b, samples[9].b, samples[10].b, 0.0)"
|
||||
");"
|
||||
"vec4 chromaChannel1 = vec4(samples[0].g, samples[1].g, samples[2].g, samples[3].g);"
|
||||
"vec4 chromaChannel2 = vec4(samples[0].b, samples[1].b, samples[2].b, samples[3].b);"
|
||||
|
||||
"vec3 lumaChromaColour = vec3(samples[5].r,"
|
||||
"dot(vec3("
|
||||
"dot(chromaChannel1[0], weights[0]),"
|
||||
"dot(chromaChannel1[1], weights[1]),"
|
||||
"dot(chromaChannel1[2], weights[2])"
|
||||
"), vec3(1.0)),"
|
||||
"dot(vec3("
|
||||
"dot(chromaChannel2[0], weights[0]),"
|
||||
"dot(chromaChannel2[1], weights[1]),"
|
||||
"dot(chromaChannel2[2], weights[2])"
|
||||
"), vec3(1.0))"
|
||||
"vec3 lumaChromaColour = vec3(samples[2].r,"
|
||||
"dot(chromaChannel1, vec4(0.25)),"
|
||||
"dot(chromaChannel2, vec4(0.25))"
|
||||
");"
|
||||
|
||||
"vec3 lumaChromaColourInRange = (lumaChromaColour - vec3(0.0, 0.5, 0.5)) * vec3(1.0, 2.0, 2.0);"
|
||||
@@ -274,54 +247,7 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_filter_shade
|
||||
"}", false, false);
|
||||
}
|
||||
|
||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_luma_filter_shader()
|
||||
{
|
||||
return make_shader(
|
||||
"#version 150\n"
|
||||
|
||||
"in vec2 inputPositionsVarying[11];"
|
||||
"uniform vec4 weights[3];"
|
||||
|
||||
"out vec3 fragColour;"
|
||||
|
||||
"uniform sampler2D texID;"
|
||||
"uniform mat3 lumaChromaToRGB;"
|
||||
|
||||
"void main(void)"
|
||||
"{"
|
||||
"vec3 samples[] = vec3[]("
|
||||
"texture(texID, inputPositionsVarying[0]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[1]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[2]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[3]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[4]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[5]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[6]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[7]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[8]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[9]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[10]).rgb"
|
||||
");"
|
||||
|
||||
"vec4 luminance[] = vec4[]("
|
||||
"vec4(samples[0].r, samples[1].r, samples[2].r, samples[3].r),"
|
||||
"vec4(samples[4].r, samples[5].r, samples[6].r, samples[7].r),"
|
||||
"vec4(samples[8].r, samples[9].r, samples[10].r, 0.0)"
|
||||
");"
|
||||
|
||||
"fragColour = vec3("
|
||||
"dot(vec3("
|
||||
"dot(luminance[0], weights[0]),"
|
||||
"dot(luminance[1], weights[1]),"
|
||||
"dot(luminance[2], weights[2])"
|
||||
"), vec3(1.0)),"
|
||||
"samples[5].gb"
|
||||
");"
|
||||
"}", false, false);
|
||||
}
|
||||
|
||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_filter_shader()
|
||||
{
|
||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_filter_shader() {
|
||||
return make_shader(
|
||||
"#version 150\n"
|
||||
|
||||
@@ -384,18 +310,15 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_filter_shader()
|
||||
"}", false, false);
|
||||
}
|
||||
|
||||
void IntermediateShader::set_output_size(unsigned int output_width, unsigned int output_height)
|
||||
{
|
||||
void IntermediateShader::set_output_size(unsigned int output_width, unsigned int output_height) {
|
||||
set_uniform("outputTextureSize", (GLint)output_width, (GLint)output_height);
|
||||
}
|
||||
|
||||
void IntermediateShader::set_source_texture_unit(GLenum unit)
|
||||
{
|
||||
void IntermediateShader::set_source_texture_unit(GLenum unit) {
|
||||
set_uniform("texID", (GLint)(unit - GL_TEXTURE0));
|
||||
}
|
||||
|
||||
void IntermediateShader::set_filter_coefficients(float sampling_rate, float cutoff_frequency)
|
||||
{
|
||||
void IntermediateShader::set_filter_coefficients(float sampling_rate, float cutoff_frequency) {
|
||||
// The process below: the source texture will have bilinear filtering enabled; so by
|
||||
// sampling at non-integral offsets from the centre the shader can get a weighted sum
|
||||
// of two source pixels, then scale that once, to do two taps per sample. However
|
||||
@@ -404,44 +327,45 @@ void IntermediateShader::set_filter_coefficients(float sampling_rate, float cuto
|
||||
// Perform a linear search for the highest number of taps we can use with 11 samples.
|
||||
GLfloat weights[12];
|
||||
GLfloat offsets[5];
|
||||
unsigned int taps = 21;
|
||||
while(1)
|
||||
{
|
||||
unsigned int taps = 11;
|
||||
// unsigned int taps = 21;
|
||||
while(1) {
|
||||
float coefficients[21];
|
||||
SignalProcessing::FIRFilter luminance_filter(taps, sampling_rate, 0.0f, cutoff_frequency, SignalProcessing::FIRFilter::DefaultAttenuation);
|
||||
luminance_filter.get_coefficients(coefficients);
|
||||
|
||||
int sample = 0;
|
||||
int c = 0;
|
||||
// int sample = 0;
|
||||
// int c = 0;
|
||||
memset(weights, 0, sizeof(float)*12);
|
||||
memset(offsets, 0, sizeof(float)*5);
|
||||
|
||||
int halfSize = (taps >> 1);
|
||||
while(c < halfSize && sample < 5)
|
||||
{
|
||||
offsets[sample] = (float)(halfSize - c);
|
||||
if((coefficients[c] < 0.0f) == (coefficients[c+1] < 0.0f) && c+1 < (taps >> 1))
|
||||
{
|
||||
weights[sample] = coefficients[c] + coefficients[c+1];
|
||||
offsets[sample] -= (coefficients[c+1] / weights[sample]);
|
||||
c += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
weights[sample] = coefficients[c];
|
||||
c++;
|
||||
}
|
||||
sample ++;
|
||||
}
|
||||
if(c == halfSize) // i.e. we finished combining inputs before we ran out of space
|
||||
{
|
||||
weights[sample] = coefficients[c];
|
||||
for(int c = 0; c < sample; c++)
|
||||
{
|
||||
weights[sample+c+1] = weights[sample-c-1];
|
||||
}
|
||||
break;
|
||||
for(int c = 0; c < taps; c++) {
|
||||
if(c < 5) offsets[c] = (halfSize - c);
|
||||
weights[c] = coefficients[c];
|
||||
}
|
||||
break;
|
||||
|
||||
// int halfSize = (taps >> 1);
|
||||
// while(c < halfSize && sample < 5) {
|
||||
// offsets[sample] = (float)(halfSize - c);
|
||||
// if((coefficients[c] < 0.0f) == (coefficients[c+1] < 0.0f) && c+1 < (taps >> 1)) {
|
||||
// weights[sample] = coefficients[c] + coefficients[c+1];
|
||||
// offsets[sample] -= (coefficients[c+1] / weights[sample]);
|
||||
// c += 2;
|
||||
// } else {
|
||||
// weights[sample] = coefficients[c];
|
||||
// c++;
|
||||
// }
|
||||
// sample ++;
|
||||
// }
|
||||
// if(c == halfSize) { // i.e. we finished combining inputs before we ran out of space
|
||||
// weights[sample] = coefficients[c];
|
||||
// for(int c = 0; c < sample; c++) {
|
||||
// weights[sample+c+1] = weights[sample-c-1];
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
taps -= 2;
|
||||
}
|
||||
|
||||
@@ -449,19 +373,25 @@ void IntermediateShader::set_filter_coefficients(float sampling_rate, float cuto
|
||||
set_uniform("offsets", 1, 5, offsets);
|
||||
}
|
||||
|
||||
void IntermediateShader::set_separation_frequency(float sampling_rate, float colour_burst_frequency)
|
||||
{
|
||||
void IntermediateShader::set_separation_frequency(float sampling_rate, float colour_burst_frequency) {
|
||||
set_filter_coefficients(sampling_rate, colour_burst_frequency);
|
||||
}
|
||||
|
||||
void IntermediateShader::set_phase_cycles_per_sample(float phase_cycles_per_sample, bool extend_runs_to_full_cycle)
|
||||
{
|
||||
set_uniform("phaseCyclesPerTick", (GLfloat)phase_cycles_per_sample);
|
||||
set_uniform("extension", extend_runs_to_full_cycle ? ceilf(1.0f / phase_cycles_per_sample) : 0.0f);
|
||||
void IntermediateShader::set_extension(float extension) {
|
||||
set_uniform("extension", extension);
|
||||
}
|
||||
|
||||
void IntermediateShader::set_colour_conversion_matrices(float *fromRGB, float *toRGB)
|
||||
{
|
||||
void IntermediateShader::set_colour_conversion_matrices(float *fromRGB, float *toRGB) {
|
||||
set_uniform_matrix("lumaChromaToRGB", 3, false, toRGB);
|
||||
set_uniform_matrix("rgbToLumaChroma", 3, false, fromRGB);
|
||||
}
|
||||
|
||||
void IntermediateShader::set_width_scalers(float input_scaler, float output_scaler) {
|
||||
set_uniform("widthScalers", input_scaler, output_scaler);
|
||||
}
|
||||
|
||||
void IntermediateShader::set_is_double_height(bool is_double_height, float input_offset, float output_offset) {
|
||||
set_uniform("textureHeightDivisor", is_double_height ? 2.0f : 1.0f);
|
||||
set_uniform("inputVerticalOffset", input_offset);
|
||||
set_uniform("outputVerticalOffset", output_offset);
|
||||
}
|
||||
|
||||
@@ -25,13 +25,13 @@ public:
|
||||
converting them to single-channel composite values using @c composite_shader if supplied
|
||||
or @c rgb_shader and a reference composite conversion if @c composite_shader is @c nullptr.
|
||||
*/
|
||||
static std::unique_ptr<IntermediateShader> make_source_conversion_shader(const char *composite_shader, const char *rgb_shader);
|
||||
static std::unique_ptr<IntermediateShader> make_source_conversion_shader(const std::string &composite_shader, const std::string &rgb_shader);
|
||||
|
||||
/*!
|
||||
Constructs and returns an intermediate shader that will take runs from the inputPositions,
|
||||
converting them to RGB values using @c rgb_shader.
|
||||
*/
|
||||
static std::unique_ptr<IntermediateShader> make_rgb_source_shader(const char *rgb_shader);
|
||||
static std::unique_ptr<IntermediateShader> make_rgb_source_shader(const std::string &rgb_shader);
|
||||
|
||||
/*!
|
||||
Constructs and returns an intermediate shader that will read composite samples from the R channel,
|
||||
@@ -44,11 +44,6 @@ public:
|
||||
*/
|
||||
static std::unique_ptr<IntermediateShader> make_chroma_filter_shader();
|
||||
|
||||
/*!
|
||||
Constructs and returns an intermediate shader that will filter R while passing through G and B unchanged.
|
||||
*/
|
||||
static std::unique_ptr<IntermediateShader> make_luma_filter_shader();
|
||||
|
||||
/*!
|
||||
Constructs and returns an intermediate shader that will filter R, G and B.
|
||||
*/
|
||||
@@ -81,15 +76,26 @@ public:
|
||||
geometry should be extended so that a complete colour cycle is included at both the beginning and end,
|
||||
to occur upon the next `bind`.
|
||||
*/
|
||||
void set_phase_cycles_per_sample(float phase_cycles_per_sample, bool extend_runs_to_full_cycle);
|
||||
void set_extension(float extension);
|
||||
|
||||
/*!
|
||||
Queues setting the matrices that convert between RGB and chrominance/luminance to occur on the next `bind`.
|
||||
*/
|
||||
void set_colour_conversion_matrices(float *fromRGB, float *toRGB);
|
||||
|
||||
/*!
|
||||
Sets the proportions of the input and output areas that should be considered the whole width — 1.0 means use all available
|
||||
space, 0.5 means use half, etc.
|
||||
*/
|
||||
void set_width_scalers(float input_scaler, float output_scaler);
|
||||
|
||||
/*!
|
||||
Sets source and target vertical offsets.
|
||||
*/
|
||||
void set_is_double_height(bool is_double_height, float input_offset = 0.0f, float output_offset = 0.0f);
|
||||
|
||||
private:
|
||||
static std::unique_ptr<IntermediateShader> make_shader(const char *fragment_shader, bool use_usampler, bool input_is_inputPosition);
|
||||
static std::unique_ptr<IntermediateShader> make_shader(const std::string &fragment_shader, bool use_usampler, bool input_is_inputPosition);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -14,16 +14,14 @@
|
||||
using namespace OpenGL;
|
||||
|
||||
namespace {
|
||||
const OpenGL::Shader::AttributeBinding bindings[] =
|
||||
{
|
||||
const OpenGL::Shader::AttributeBinding bindings[] = {
|
||||
{"position", 0},
|
||||
{"srcCoordinates", 1},
|
||||
{nullptr}
|
||||
};
|
||||
}
|
||||
|
||||
std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_methods, const char *colour_expression, bool use_usampler)
|
||||
{
|
||||
std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_methods, const char *colour_expression, bool use_usampler) {
|
||||
const char *sampler_type = use_usampler ? "usampler2D" : "sampler2D";
|
||||
|
||||
char *vertex_shader;
|
||||
@@ -38,6 +36,8 @@ std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_met
|
||||
"uniform vec2 positionConversion;"
|
||||
"uniform vec2 scanNormal;"
|
||||
"uniform %s texID;"
|
||||
"uniform float inputScaler;"
|
||||
"uniform int textureHeightDivisor;"
|
||||
|
||||
"out float lateralVarying;"
|
||||
"out vec2 srcCoordinatesVarying;"
|
||||
@@ -52,9 +52,10 @@ std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_met
|
||||
"lateralVarying = lateral - 0.5;"
|
||||
|
||||
"vec2 vSrcCoordinates = vec2(x, vertical.y);"
|
||||
"ivec2 textureSize = textureSize(texID, 0);"
|
||||
"ivec2 textureSize = textureSize(texID, 0) * ivec2(1, textureHeightDivisor);"
|
||||
"iSrcCoordinatesVarying = vSrcCoordinates;"
|
||||
"srcCoordinatesVarying = vec2(vSrcCoordinates.x / textureSize.x, (vSrcCoordinates.y + 0.5) / textureSize.y);"
|
||||
"srcCoordinatesVarying = vec2(inputScaler * vSrcCoordinates.x / textureSize.x, (vSrcCoordinates.y + 0.5) / textureSize.y);"
|
||||
"srcCoordinatesVarying.x = srcCoordinatesVarying.x - mod(srcCoordinatesVarying.x, 1.0 / textureSize.x);"
|
||||
|
||||
"vec2 vPosition = vec2(x, vertical.x);"
|
||||
"vec2 floatingPosition = (vPosition / positionConversion) + lateral * scanNormal;"
|
||||
@@ -89,8 +90,7 @@ std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_met
|
||||
return result;
|
||||
}
|
||||
|
||||
void OutputShader::set_output_size(unsigned int output_width, unsigned int output_height, Outputs::CRT::Rect visible_area)
|
||||
{
|
||||
void OutputShader::set_output_size(unsigned int output_width, unsigned int output_height, Outputs::CRT::Rect visible_area) {
|
||||
GLfloat outputAspectRatioMultiplier = ((float)output_width / (float)output_height) / (4.0f / 3.0f);
|
||||
|
||||
GLfloat bonusWidth = (outputAspectRatioMultiplier - 1.0f) * visible_area.size.width;
|
||||
@@ -101,13 +101,11 @@ void OutputShader::set_output_size(unsigned int output_width, unsigned int outpu
|
||||
set_uniform("boundsSize", (GLfloat)visible_area.size.width, (GLfloat)visible_area.size.height);
|
||||
}
|
||||
|
||||
void OutputShader::set_source_texture_unit(GLenum unit)
|
||||
{
|
||||
void OutputShader::set_source_texture_unit(GLenum unit) {
|
||||
set_uniform("texID", (GLint)(unit - GL_TEXTURE0));
|
||||
}
|
||||
|
||||
void OutputShader::set_timing(unsigned int height_of_display, unsigned int cycles_per_line, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider)
|
||||
{
|
||||
void OutputShader::set_timing(unsigned int height_of_display, unsigned int cycles_per_line, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider) {
|
||||
GLfloat scan_angle = atan2f(1.0f / (float)height_of_display, 1.0f);
|
||||
GLfloat scan_normal[] = { -sinf(scan_angle), cosf(scan_angle)};
|
||||
GLfloat multiplier = (float)cycles_per_line / ((float)height_of_display * (float)horizontal_scan_period);
|
||||
@@ -117,3 +115,11 @@ void OutputShader::set_timing(unsigned int height_of_display, unsigned int cycle
|
||||
set_uniform("scanNormal", scan_normal[0], scan_normal[1]);
|
||||
set_uniform("positionConversion", (GLfloat)horizontal_scan_period, (GLfloat)vertical_scan_period / (GLfloat)vertical_period_divider);
|
||||
}
|
||||
|
||||
void OutputShader::set_input_width_scaler(float input_scaler) {
|
||||
set_uniform("inputScaler", input_scaler);
|
||||
}
|
||||
|
||||
void OutputShader::set_origin_is_double_height(bool is_double_height) {
|
||||
set_uniform("textureHeightDivisor", is_double_height ? 2 : 1);
|
||||
}
|
||||
|
||||
@@ -58,6 +58,16 @@ public:
|
||||
to occur upon the next `bind`.
|
||||
*/
|
||||
void set_timing(unsigned int height_of_display, unsigned int cycles_per_line, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider);
|
||||
|
||||
/*!
|
||||
*/
|
||||
void set_origin_is_double_height(bool is_double_height);
|
||||
|
||||
/*!
|
||||
Sets the proportion of the input area that should be considered the whole width — 1.0 means use all available
|
||||
space, 0.5 means use half, etc.
|
||||
*/
|
||||
void set_input_width_scaler(float input_scaler);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
#include "Shader.hpp"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace OpenGL;
|
||||
@@ -17,24 +16,23 @@ namespace {
|
||||
Shader *bound_shader = nullptr;
|
||||
}
|
||||
|
||||
GLuint Shader::compile_shader(const char *source, GLenum type)
|
||||
{
|
||||
GLuint Shader::compile_shader(const std::string &source, GLenum type) {
|
||||
GLuint shader = glCreateShader(type);
|
||||
glShaderSource(shader, 1, &source, NULL);
|
||||
const char *c_str = source.c_str();
|
||||
glShaderSource(shader, 1, &c_str, NULL);
|
||||
glCompileShader(shader);
|
||||
|
||||
#if defined(DEBUG)
|
||||
#ifdef DEBUG
|
||||
GLint isCompiled = 0;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
|
||||
if(isCompiled == GL_FALSE)
|
||||
{
|
||||
if(isCompiled == GL_FALSE) {
|
||||
GLint logLength;
|
||||
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
|
||||
if(logLength > 0) {
|
||||
GLchar *log = (GLchar *)malloc((size_t)logLength);
|
||||
GLchar *log = new GLchar[logLength];
|
||||
glGetShaderInfoLog(shader, logLength, &logLength, log);
|
||||
printf("Compile log:\n%s\n", log);
|
||||
free(log);
|
||||
delete[] log;
|
||||
}
|
||||
|
||||
throw (type == GL_VERTEX_SHADER) ? VertexShaderCompilationError : FragmentShaderCompilationError;
|
||||
@@ -44,8 +42,7 @@ GLuint Shader::compile_shader(const char *source, GLenum type)
|
||||
return shader;
|
||||
}
|
||||
|
||||
Shader::Shader(const char *vertex_shader, const char *fragment_shader, const AttributeBinding *attribute_bindings)
|
||||
{
|
||||
Shader::Shader(const std::string &vertex_shader, const std::string &fragment_shader, const AttributeBinding *attribute_bindings) {
|
||||
shader_program_ = glCreateProgram();
|
||||
GLuint vertex = compile_shader(vertex_shader, GL_VERTEX_SHADER);
|
||||
GLuint fragment = compile_shader(fragment_shader, GL_FRAGMENT_SHADER);
|
||||
@@ -53,10 +50,8 @@ Shader::Shader(const char *vertex_shader, const char *fragment_shader, const Att
|
||||
glAttachShader(shader_program_, vertex);
|
||||
glAttachShader(shader_program_, fragment);
|
||||
|
||||
if(attribute_bindings)
|
||||
{
|
||||
while(attribute_bindings->name)
|
||||
{
|
||||
if(attribute_bindings) {
|
||||
while(attribute_bindings->name) {
|
||||
glBindAttribLocation(shader_program_, attribute_bindings->index, attribute_bindings->name);
|
||||
attribute_bindings++;
|
||||
}
|
||||
@@ -64,58 +59,50 @@ Shader::Shader(const char *vertex_shader, const char *fragment_shader, const Att
|
||||
|
||||
glLinkProgram(shader_program_);
|
||||
|
||||
#if defined(DEBUG)
|
||||
#ifdef DEBUG
|
||||
GLint didLink = 0;
|
||||
glGetProgramiv(shader_program_, GL_LINK_STATUS, &didLink);
|
||||
if(didLink == GL_FALSE)
|
||||
{
|
||||
if(didLink == GL_FALSE) {
|
||||
GLint logLength;
|
||||
glGetProgramiv(shader_program_, GL_INFO_LOG_LENGTH, &logLength);
|
||||
if(logLength > 0) {
|
||||
GLchar *log = (GLchar *)malloc((size_t)logLength);
|
||||
GLchar *log = new GLchar[logLength];
|
||||
glGetProgramInfoLog(shader_program_, logLength, &logLength, log);
|
||||
printf("Link log:\n%s\n", log);
|
||||
free(log);
|
||||
delete[] log;
|
||||
}
|
||||
throw ProgramLinkageError;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Shader::~Shader()
|
||||
{
|
||||
Shader::~Shader() {
|
||||
if(bound_shader == this) Shader::unbind();
|
||||
glDeleteProgram(shader_program_);
|
||||
}
|
||||
|
||||
void Shader::bind()
|
||||
{
|
||||
if(bound_shader != this)
|
||||
{
|
||||
void Shader::bind() {
|
||||
if(bound_shader != this) {
|
||||
glUseProgram(shader_program_);
|
||||
bound_shader = this;
|
||||
}
|
||||
flush_functions();
|
||||
}
|
||||
|
||||
void Shader::unbind()
|
||||
{
|
||||
void Shader::unbind() {
|
||||
bound_shader = nullptr;
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
GLint Shader::get_attrib_location(const GLchar *name)
|
||||
{
|
||||
GLint Shader::get_attrib_location(const GLchar *name) {
|
||||
return glGetAttribLocation(shader_program_, name);
|
||||
}
|
||||
|
||||
GLint Shader::get_uniform_location(const GLchar *name)
|
||||
{
|
||||
GLint Shader::get_uniform_location(const GLchar *name) {
|
||||
return glGetUniformLocation(shader_program_, name);
|
||||
}
|
||||
|
||||
void Shader::enable_vertex_attribute_with_pointer(const char *name, GLint size, GLenum type, GLboolean normalised, GLsizei stride, const GLvoid *pointer, GLuint divisor)
|
||||
{
|
||||
void Shader::enable_vertex_attribute_with_pointer(const char *name, GLint size, GLenum type, GLboolean normalised, GLsizei stride, const GLvoid *pointer, GLuint divisor) {
|
||||
GLint location = get_attrib_location(name);
|
||||
glEnableVertexAttribArray((GLuint)location);
|
||||
glVertexAttribPointer((GLuint)location, size, type, normalised, stride, pointer);
|
||||
@@ -124,103 +111,87 @@ void Shader::enable_vertex_attribute_with_pointer(const char *name, GLint size,
|
||||
|
||||
// The various set_uniforms...
|
||||
#define location() glGetUniformLocation(shader_program_, name.c_str())
|
||||
void Shader::set_uniform(const std::string &name, GLint value)
|
||||
{
|
||||
void Shader::set_uniform(const std::string &name, GLint value) {
|
||||
enqueue_function([name, value, this] {
|
||||
glUniform1i(location(), value);
|
||||
});
|
||||
}
|
||||
|
||||
void Shader::set_uniform(const std::string &name, GLuint value)
|
||||
{
|
||||
void Shader::set_uniform(const std::string &name, GLuint value) {
|
||||
enqueue_function([name, value, this] {
|
||||
glUniform1ui(location(), value);
|
||||
});
|
||||
}
|
||||
|
||||
void Shader::set_uniform(const std::string &name, GLfloat value)
|
||||
{
|
||||
void Shader::set_uniform(const std::string &name, GLfloat value) {
|
||||
enqueue_function([name, value, this] {
|
||||
glUniform1f(location(), value);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void Shader::set_uniform(const std::string &name, GLint value1, GLint value2)
|
||||
{
|
||||
void Shader::set_uniform(const std::string &name, GLint value1, GLint value2) {
|
||||
enqueue_function([name, value1, value2, this] {
|
||||
glUniform2i(location(), value1, value2);
|
||||
});
|
||||
}
|
||||
|
||||
void Shader::set_uniform(const std::string &name, GLfloat value1, GLfloat value2)
|
||||
{
|
||||
void Shader::set_uniform(const std::string &name, GLfloat value1, GLfloat value2) {
|
||||
enqueue_function([name, value1, value2, this] {
|
||||
glUniform2f(location(), value1, value2);
|
||||
GLint location = location();
|
||||
glUniform2f(location, value1, value2);
|
||||
});
|
||||
}
|
||||
|
||||
void Shader::set_uniform(const std::string &name, GLuint value1, GLuint value2)
|
||||
{
|
||||
void Shader::set_uniform(const std::string &name, GLuint value1, GLuint value2) {
|
||||
enqueue_function([name, value1, value2, this] {
|
||||
glUniform2ui(location(), value1, value2);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void Shader::set_uniform(const std::string &name, GLint value1, GLint value2, GLint value3)
|
||||
{
|
||||
void Shader::set_uniform(const std::string &name, GLint value1, GLint value2, GLint value3) {
|
||||
enqueue_function([name, value1, value2, value3, this] {
|
||||
glUniform3i(location(), value1, value2, value3);
|
||||
});
|
||||
}
|
||||
|
||||
void Shader::set_uniform(const std::string &name, GLfloat value1, GLfloat value2, GLfloat value3)
|
||||
{
|
||||
void Shader::set_uniform(const std::string &name, GLfloat value1, GLfloat value2, GLfloat value3) {
|
||||
enqueue_function([name, value1, value2, value3, this] {
|
||||
glUniform3f(location(), value1, value2, value3);
|
||||
});
|
||||
}
|
||||
|
||||
void Shader::set_uniform(const std::string &name, GLuint value1, GLuint value2, GLuint value3)
|
||||
{
|
||||
void Shader::set_uniform(const std::string &name, GLuint value1, GLuint value2, GLuint value3) {
|
||||
enqueue_function([name, value1, value2, value3, this] {
|
||||
glUniform3ui(location(), value1, value2, value3);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void Shader::set_uniform(const std::string &name, GLint value1, GLint value2, GLint value3, GLint value4)
|
||||
{
|
||||
void Shader::set_uniform(const std::string &name, GLint value1, GLint value2, GLint value3, GLint value4) {
|
||||
enqueue_function([name, value1, value2, value3, value4, this] {
|
||||
glUniform4i(location(), value1, value2, value3, value4);
|
||||
});
|
||||
}
|
||||
|
||||
void Shader::set_uniform(const std::string &name, GLfloat value1, GLfloat value2, GLfloat value3, GLfloat value4)
|
||||
{
|
||||
void Shader::set_uniform(const std::string &name, GLfloat value1, GLfloat value2, GLfloat value3, GLfloat value4) {
|
||||
enqueue_function([name, value1, value2, value3, value4, this] {
|
||||
glUniform4f(location(), value1, value2, value3, value4);
|
||||
});
|
||||
}
|
||||
|
||||
void Shader::set_uniform(const std::string &name, GLuint value1, GLuint value2, GLuint value3, GLuint value4)
|
||||
{
|
||||
void Shader::set_uniform(const std::string &name, GLuint value1, GLuint value2, GLuint value3, GLuint value4) {
|
||||
enqueue_function([name, value1, value2, value3, value4, this] {
|
||||
glUniform4ui(location(), value1, value2, value3, value4);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, const GLint *values)
|
||||
{
|
||||
void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, const GLint *values) {
|
||||
size_t number_of_values = (size_t)count * (size_t)size;
|
||||
GLint *values_copy = new GLint[number_of_values];
|
||||
memcpy(values_copy, values, sizeof(*values) * (size_t)number_of_values);
|
||||
|
||||
enqueue_function([name, size, count, values_copy, this] {
|
||||
switch(size)
|
||||
{
|
||||
switch(size) {
|
||||
case 1: glUniform1iv(location(), count, values_copy); break;
|
||||
case 2: glUniform2iv(location(), count, values_copy); break;
|
||||
case 3: glUniform3iv(location(), count, values_copy); break;
|
||||
@@ -230,15 +201,13 @@ void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, con
|
||||
});
|
||||
}
|
||||
|
||||
void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, const GLfloat *values)
|
||||
{
|
||||
void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, const GLfloat *values) {
|
||||
size_t number_of_values = (size_t)count * (size_t)size;
|
||||
GLfloat *values_copy = new GLfloat[number_of_values];
|
||||
memcpy(values_copy, values, sizeof(*values) * (size_t)number_of_values);
|
||||
|
||||
enqueue_function([name, size, count, values_copy, this] {
|
||||
switch(size)
|
||||
{
|
||||
switch(size) {
|
||||
case 1: glUniform1fv(location(), count, values_copy); break;
|
||||
case 2: glUniform2fv(location(), count, values_copy); break;
|
||||
case 3: glUniform3fv(location(), count, values_copy); break;
|
||||
@@ -248,15 +217,13 @@ void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, con
|
||||
});
|
||||
}
|
||||
|
||||
void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, const GLuint *values)
|
||||
{
|
||||
void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, const GLuint *values) {
|
||||
size_t number_of_values = (size_t)count * (size_t)size;
|
||||
GLuint *values_copy = new GLuint[number_of_values];
|
||||
memcpy(values_copy, values, sizeof(*values) * (size_t)number_of_values);
|
||||
|
||||
enqueue_function([name, size, count, values_copy, this] {
|
||||
switch(size)
|
||||
{
|
||||
switch(size) {
|
||||
case 1: glUniform1uiv(location(), count, values_copy); break;
|
||||
case 2: glUniform2uiv(location(), count, values_copy); break;
|
||||
case 3: glUniform3uiv(location(), count, values_copy); break;
|
||||
@@ -266,21 +233,18 @@ void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, con
|
||||
});
|
||||
}
|
||||
|
||||
void Shader::set_uniform_matrix(const std::string &name, GLint size, bool transpose, const GLfloat *values)
|
||||
{
|
||||
void Shader::set_uniform_matrix(const std::string &name, GLint size, bool transpose, const GLfloat *values) {
|
||||
set_uniform_matrix(name, size, 1, transpose, values);
|
||||
}
|
||||
|
||||
void Shader::set_uniform_matrix(const std::string &name, GLint size, GLsizei count, bool transpose, const GLfloat *values)
|
||||
{
|
||||
void Shader::set_uniform_matrix(const std::string &name, GLint size, GLsizei count, bool transpose, const GLfloat *values) {
|
||||
size_t number_of_values = (size_t)count * (size_t)size * (size_t)size;
|
||||
GLfloat *values_copy = new GLfloat[number_of_values];
|
||||
memcpy(values_copy, values, sizeof(*values) * number_of_values);
|
||||
|
||||
enqueue_function([name, size, count, transpose, values_copy, this] {
|
||||
GLboolean glTranspose = transpose ? GL_TRUE : GL_FALSE;
|
||||
switch(size)
|
||||
{
|
||||
switch(size) {
|
||||
case 2: glUniformMatrix2fv(location(), count, glTranspose, values_copy); break;
|
||||
case 3: glUniformMatrix3fv(location(), count, glTranspose, values_copy); break;
|
||||
case 4: glUniformMatrix4fv(location(), count, glTranspose, values_copy); break;
|
||||
@@ -289,20 +253,15 @@ void Shader::set_uniform_matrix(const std::string &name, GLint size, GLsizei cou
|
||||
});
|
||||
}
|
||||
|
||||
void Shader::enqueue_function(std::function<void(void)> function)
|
||||
{
|
||||
function_mutex_.lock();
|
||||
void Shader::enqueue_function(std::function<void(void)> function) {
|
||||
std::lock_guard<std::mutex> function_guard(function_mutex_);
|
||||
enqueued_functions_.push_back(function);
|
||||
function_mutex_.unlock();
|
||||
}
|
||||
|
||||
void Shader::flush_functions()
|
||||
{
|
||||
function_mutex_.lock();
|
||||
for(std::function<void(void)> function : enqueued_functions_)
|
||||
{
|
||||
void Shader::flush_functions() {
|
||||
std::lock_guard<std::mutex> function_guard(function_mutex_);
|
||||
for(std::function<void(void)> function : enqueued_functions_) {
|
||||
function();
|
||||
}
|
||||
enqueued_functions_.clear();
|
||||
function_mutex_.unlock();
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ public:
|
||||
@param fragment_shader The fragment shader source code.
|
||||
@param attribute_bindings Either @c nullptr or an array terminated by an entry with a @c nullptr-name of attribute bindings.
|
||||
*/
|
||||
Shader(const char *vertex_shader, const char *fragment_shader, const AttributeBinding *attribute_bindings);
|
||||
Shader(const std::string &vertex_shader, const std::string &fragment_shader, const AttributeBinding *attribute_bindings);
|
||||
~Shader();
|
||||
|
||||
/*!
|
||||
@@ -106,7 +106,7 @@ public:
|
||||
void set_uniform_matrix(const std::string &name, GLint size, GLsizei count, bool transpose, const GLfloat *values);
|
||||
|
||||
private:
|
||||
GLuint compile_shader(const char *source, GLenum type);
|
||||
GLuint compile_shader(const std::string &source, GLenum type);
|
||||
GLuint shader_program_;
|
||||
|
||||
void flush_functions();
|
||||
|
||||
@@ -13,10 +13,8 @@
|
||||
|
||||
using namespace Outputs::CRT;
|
||||
|
||||
static const GLint internalFormatForDepth(size_t depth)
|
||||
{
|
||||
switch(depth)
|
||||
{
|
||||
static const GLint internalFormatForDepth(size_t depth) {
|
||||
switch(depth) {
|
||||
default: return GL_FALSE;
|
||||
case 1: return GL_R8UI;
|
||||
case 2: return GL_RG8UI;
|
||||
@@ -25,10 +23,8 @@ static const GLint internalFormatForDepth(size_t depth)
|
||||
}
|
||||
}
|
||||
|
||||
static const GLenum formatForDepth(size_t depth)
|
||||
{
|
||||
switch(depth)
|
||||
{
|
||||
static const GLenum formatForDepth(size_t depth) {
|
||||
switch(depth) {
|
||||
default: return GL_FALSE;
|
||||
case 1: return GL_RED_INTEGER;
|
||||
case 2: return GL_RG_INTEGER;
|
||||
@@ -38,13 +34,13 @@ static const GLenum formatForDepth(size_t depth)
|
||||
}
|
||||
|
||||
TextureBuilder::TextureBuilder(size_t bytes_per_pixel, GLenum texture_unit) :
|
||||
bytes_per_pixel_(bytes_per_pixel),
|
||||
write_areas_start_x_(0),
|
||||
write_areas_start_y_(0),
|
||||
is_full_(false),
|
||||
did_submit_(false),
|
||||
number_of_write_areas_(0)
|
||||
{
|
||||
bytes_per_pixel_(bytes_per_pixel),
|
||||
write_areas_start_x_(0),
|
||||
write_areas_start_y_(0),
|
||||
is_full_(false),
|
||||
did_submit_(false),
|
||||
has_write_area_(false),
|
||||
number_of_write_areas_(0) {
|
||||
image_.resize(bytes_per_pixel * InputBufferBuilderWidth * InputBufferBuilderHeight);
|
||||
glGenTextures(1, &texture_name_);
|
||||
|
||||
@@ -57,41 +53,33 @@ TextureBuilder::TextureBuilder(size_t bytes_per_pixel, GLenum texture_unit) :
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, internalFormatForDepth(bytes_per_pixel), InputBufferBuilderWidth, InputBufferBuilderHeight, 0, formatForDepth(bytes_per_pixel), GL_UNSIGNED_BYTE, nullptr);
|
||||
}
|
||||
|
||||
TextureBuilder::~TextureBuilder()
|
||||
{
|
||||
TextureBuilder::~TextureBuilder() {
|
||||
glDeleteTextures(1, &texture_name_);
|
||||
}
|
||||
|
||||
inline uint8_t *TextureBuilder::pointer_to_location(uint16_t x, uint16_t y)
|
||||
{
|
||||
inline uint8_t *TextureBuilder::pointer_to_location(uint16_t x, uint16_t y) {
|
||||
return &image_[((y * InputBufferBuilderWidth) + x) * bytes_per_pixel_];
|
||||
}
|
||||
|
||||
uint8_t *TextureBuilder::allocate_write_area(size_t required_length)
|
||||
{
|
||||
uint8_t *TextureBuilder::allocate_write_area(size_t required_length) {
|
||||
if(is_full_) return nullptr;
|
||||
|
||||
uint16_t starting_x, starting_y;
|
||||
|
||||
if(!number_of_write_areas_)
|
||||
{
|
||||
if(!number_of_write_areas_) {
|
||||
starting_x = write_areas_start_x_;
|
||||
starting_y = write_areas_start_y_;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
starting_x = write_areas_[number_of_write_areas_ - 1].x + write_areas_[number_of_write_areas_ - 1].length + 1;
|
||||
starting_y = write_areas_[number_of_write_areas_ - 1].y;
|
||||
}
|
||||
|
||||
WriteArea next_write_area;
|
||||
if(starting_x + required_length + 2 > InputBufferBuilderWidth)
|
||||
{
|
||||
if(starting_x + required_length + 2 > InputBufferBuilderWidth) {
|
||||
starting_x = 0;
|
||||
starting_y++;
|
||||
|
||||
if(starting_y == InputBufferBuilderHeight)
|
||||
{
|
||||
if(starting_y == InputBufferBuilderHeight) {
|
||||
is_full_ = true;
|
||||
return nullptr;
|
||||
}
|
||||
@@ -105,19 +93,19 @@ uint8_t *TextureBuilder::allocate_write_area(size_t required_length)
|
||||
else
|
||||
write_areas_.push_back(next_write_area);
|
||||
number_of_write_areas_++;
|
||||
has_write_area_ = true;
|
||||
|
||||
return pointer_to_location(next_write_area.x, next_write_area.y);
|
||||
}
|
||||
|
||||
bool TextureBuilder::is_full()
|
||||
{
|
||||
bool TextureBuilder::is_full() {
|
||||
return is_full_;
|
||||
}
|
||||
|
||||
void TextureBuilder::reduce_previous_allocation_to(size_t actual_length)
|
||||
{
|
||||
if(is_full_) return;
|
||||
void TextureBuilder::reduce_previous_allocation_to(size_t actual_length) {
|
||||
if(is_full_ || !has_write_area_) return;
|
||||
|
||||
has_write_area_ = false;
|
||||
WriteArea &write_area = write_areas_[number_of_write_areas_-1];
|
||||
write_area.length = (uint16_t)actual_length;
|
||||
|
||||
@@ -133,8 +121,7 @@ void TextureBuilder::reduce_previous_allocation_to(size_t actual_length)
|
||||
bytes_per_pixel_);
|
||||
}
|
||||
|
||||
void TextureBuilder::submit()
|
||||
{
|
||||
void TextureBuilder::submit() {
|
||||
uint16_t height = write_areas_start_y_ + (write_areas_start_x_ ? 1 : 0);
|
||||
did_submit_ = true;
|
||||
|
||||
@@ -145,30 +132,23 @@ void TextureBuilder::submit()
|
||||
image_.data());
|
||||
}
|
||||
|
||||
void TextureBuilder::flush(const std::function<void(const std::vector<WriteArea> &write_areas, size_t count)> &function)
|
||||
{
|
||||
void TextureBuilder::flush(const std::function<void(const std::vector<WriteArea> &write_areas, size_t count)> &function) {
|
||||
bool was_full = is_full_;
|
||||
if(did_submit_)
|
||||
{
|
||||
if(did_submit_) {
|
||||
write_areas_start_y_ = write_areas_start_x_ = 0;
|
||||
is_full_ = false;
|
||||
}
|
||||
|
||||
if(number_of_write_areas_ && !was_full)
|
||||
{
|
||||
if(write_areas_[0].x != write_areas_start_x_+1 || write_areas_[0].y != write_areas_start_y_)
|
||||
{
|
||||
for(size_t area = 0; area < number_of_write_areas_; area++)
|
||||
{
|
||||
if(number_of_write_areas_ && !was_full) {
|
||||
if(write_areas_[0].x != write_areas_start_x_+1 || write_areas_[0].y != write_areas_start_y_) {
|
||||
for(size_t area = 0; area < number_of_write_areas_; area++) {
|
||||
WriteArea &write_area = write_areas_[area];
|
||||
|
||||
if(write_areas_start_x_ + write_area.length + 2 > InputBufferBuilderWidth)
|
||||
{
|
||||
if(write_areas_start_x_ + write_area.length + 2 > InputBufferBuilderWidth) {
|
||||
write_areas_start_x_ = 0;
|
||||
write_areas_start_y_ ++;
|
||||
|
||||
if(write_areas_start_y_ == InputBufferBuilderHeight)
|
||||
{
|
||||
if(write_areas_start_y_ == InputBufferBuilderHeight) {
|
||||
is_full_ = true;
|
||||
break;
|
||||
}
|
||||
@@ -183,8 +163,7 @@ void TextureBuilder::flush(const std::function<void(const std::vector<WriteArea>
|
||||
}
|
||||
}
|
||||
|
||||
if(!is_full_)
|
||||
{
|
||||
if(!is_full_) {
|
||||
function(write_areas_, number_of_write_areas_);
|
||||
|
||||
write_areas_start_x_ = write_areas_[number_of_write_areas_-1].x + write_areas_[number_of_write_areas_-1].length + 1;
|
||||
@@ -193,5 +172,6 @@ void TextureBuilder::flush(const std::function<void(const std::vector<WriteArea>
|
||||
}
|
||||
|
||||
did_submit_ = false;
|
||||
has_write_area_ = false;
|
||||
number_of_write_areas_ = 0;
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ class TextureBuilder {
|
||||
size_t number_of_write_areas_;
|
||||
bool is_full_;
|
||||
bool did_submit_;
|
||||
bool has_write_area_;
|
||||
inline uint8_t *pointer_to_location(uint16_t x, uint16_t y);
|
||||
|
||||
// Usually: the start position for the current batch of write areas.
|
||||
|
||||
@@ -12,15 +12,14 @@
|
||||
|
||||
using namespace OpenGL;
|
||||
|
||||
TextureTarget::TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit) :
|
||||
_width(width),
|
||||
_height(height),
|
||||
_pixel_shader(nullptr),
|
||||
_drawing_vertex_array(0),
|
||||
_drawing_array_buffer(0),
|
||||
_set_aspect_ratio(0.0f),
|
||||
_texture_unit(texture_unit)
|
||||
{
|
||||
TextureTarget::TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit, GLint mag_filter) :
|
||||
_width(width),
|
||||
_height(height),
|
||||
_pixel_shader(nullptr),
|
||||
_drawing_vertex_array(0),
|
||||
_drawing_array_buffer(0),
|
||||
_set_aspect_ratio(0.0f),
|
||||
_texture_unit(texture_unit) {
|
||||
glGenFramebuffers(1, &_framebuffer);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
|
||||
|
||||
@@ -33,8 +32,8 @@ TextureTarget::TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit)
|
||||
uint8_t *blank_buffer = (uint8_t *)calloc((size_t)(_expanded_width * _expanded_height), 4);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)_expanded_width, (GLsizei)_expanded_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, blank_buffer);
|
||||
free(blank_buffer);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0);
|
||||
|
||||
@@ -42,29 +41,24 @@ TextureTarget::TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit)
|
||||
throw ErrorFramebufferIncomplete;
|
||||
}
|
||||
|
||||
TextureTarget::~TextureTarget()
|
||||
{
|
||||
TextureTarget::~TextureTarget() {
|
||||
glDeleteFramebuffers(1, &_framebuffer);
|
||||
glDeleteTextures(1, &_texture);
|
||||
if(_drawing_vertex_array) glDeleteVertexArrays(1, &_drawing_vertex_array);
|
||||
if(_drawing_array_buffer) glDeleteBuffers(1, &_drawing_array_buffer);
|
||||
}
|
||||
|
||||
void TextureTarget::bind_framebuffer()
|
||||
{
|
||||
void TextureTarget::bind_framebuffer() {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
|
||||
glViewport(0, 0, _width, _height);
|
||||
}
|
||||
|
||||
void TextureTarget::bind_texture()
|
||||
{
|
||||
void TextureTarget::bind_texture() {
|
||||
glBindTexture(GL_TEXTURE_2D, _texture);
|
||||
}
|
||||
|
||||
void TextureTarget::draw(float aspect_ratio)
|
||||
{
|
||||
if(!_pixel_shader)
|
||||
{
|
||||
void TextureTarget::draw(float aspect_ratio) {
|
||||
if(!_pixel_shader) {
|
||||
const char *vertex_shader =
|
||||
"#version 150\n"
|
||||
|
||||
@@ -112,8 +106,7 @@ void TextureTarget::draw(float aspect_ratio)
|
||||
glUniform1i(texIDUniform, (GLint)(_texture_unit - GL_TEXTURE0));
|
||||
}
|
||||
|
||||
if(_set_aspect_ratio != aspect_ratio)
|
||||
{
|
||||
if(_set_aspect_ratio != aspect_ratio) {
|
||||
_set_aspect_ratio = aspect_ratio;
|
||||
float buffer[4*4];
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ class TextureTarget {
|
||||
@param height The height of target to create.
|
||||
@param texture_unit A texture unit on which to bind the texture.
|
||||
*/
|
||||
TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit);
|
||||
TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit, GLint mag_filter);
|
||||
~TextureTarget();
|
||||
|
||||
/*!
|
||||
@@ -46,16 +46,14 @@ class TextureTarget {
|
||||
/*!
|
||||
@returns the width of the texture target.
|
||||
*/
|
||||
GLsizei get_width()
|
||||
{
|
||||
GLsizei get_width() {
|
||||
return _width;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns the height of the texture target.
|
||||
*/
|
||||
GLsizei get_height()
|
||||
{
|
||||
GLsizei get_height() {
|
||||
return _height;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
//
|
||||
// Speaker.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 12/01/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Speaker.hpp"
|
||||
|
||||
using namespace Outputs;
|
||||
|
||||
@@ -37,8 +37,7 @@ class Speaker {
|
||||
virtual void speaker_did_complete_samples(Speaker *speaker, const int16_t *buffer, int buffer_size) = 0;
|
||||
};
|
||||
|
||||
float get_ideal_clock_rate_in_range(float minimum, float maximum)
|
||||
{
|
||||
float get_ideal_clock_rate_in_range(float minimum, float maximum) {
|
||||
// return twice the cut off, if applicable
|
||||
if(high_frequency_cut_off_ > 0.0f && input_cycles_per_second_ >= high_frequency_cut_off_ * 3.0f && input_cycles_per_second_ <= high_frequency_cut_off_ * 3.0f) return high_frequency_cut_off_ * 3.0f;
|
||||
|
||||
@@ -52,30 +51,25 @@ class Speaker {
|
||||
return maximum;
|
||||
}
|
||||
|
||||
void set_output_rate(float cycles_per_second, int buffer_size)
|
||||
{
|
||||
void set_output_rate(float cycles_per_second, int buffer_size) {
|
||||
output_cycles_per_second_ = cycles_per_second;
|
||||
if(buffer_size_ != buffer_size)
|
||||
{
|
||||
if(buffer_size_ != buffer_size) {
|
||||
buffer_in_progress_.reset(new int16_t[buffer_size]);
|
||||
buffer_size_ = buffer_size;
|
||||
}
|
||||
set_needs_updated_filter_coefficients();
|
||||
}
|
||||
|
||||
void set_output_quality(int number_of_taps)
|
||||
{
|
||||
void set_output_quality(int number_of_taps) {
|
||||
requested_number_of_taps_ = number_of_taps;
|
||||
set_needs_updated_filter_coefficients();
|
||||
}
|
||||
|
||||
void set_delegate(Delegate *delegate)
|
||||
{
|
||||
void set_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
void set_input_rate(float cycles_per_second)
|
||||
{
|
||||
void set_input_rate(float cycles_per_second) {
|
||||
input_cycles_per_second_ = cycles_per_second;
|
||||
set_needs_updated_filter_coefficients();
|
||||
}
|
||||
@@ -83,8 +77,7 @@ class Speaker {
|
||||
/*!
|
||||
Sets the cut-off frequency for a low-pass filter attached to the output of this speaker; optional.
|
||||
*/
|
||||
void set_high_frequency_cut_off(float high_frequency)
|
||||
{
|
||||
void set_high_frequency_cut_off(float high_frequency) {
|
||||
high_frequency_cut_off_ = high_frequency;
|
||||
set_needs_updated_filter_coefficients();
|
||||
}
|
||||
@@ -94,21 +87,18 @@ class Speaker {
|
||||
/*!
|
||||
Ensures any deferred processing occurs now.
|
||||
*/
|
||||
void flush()
|
||||
{
|
||||
void flush() {
|
||||
std::shared_ptr<std::list<std::function<void(void)>>> queued_functions = queued_functions_;
|
||||
queued_functions_.reset();
|
||||
_queue->enqueue([queued_functions] {
|
||||
for(auto function : *queued_functions)
|
||||
{
|
||||
for(auto function : *queued_functions) {
|
||||
function();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
void enqueue(std::function<void(void)> function)
|
||||
{
|
||||
void enqueue(std::function<void(void)> function) {
|
||||
if(!queued_functions_) queued_functions_.reset(new std::list<std::function<void(void)>>);
|
||||
queued_functions_->push_back(function);
|
||||
}
|
||||
@@ -124,14 +114,12 @@ class Speaker {
|
||||
|
||||
float input_cycles_per_second_, output_cycles_per_second_;
|
||||
|
||||
void set_needs_updated_filter_coefficients()
|
||||
{
|
||||
void set_needs_updated_filter_coefficients() {
|
||||
coefficients_are_dirty_ = true;
|
||||
}
|
||||
|
||||
void get_samples(unsigned int quantity, int16_t *target) {}
|
||||
void skip_samples(unsigned int quantity)
|
||||
{
|
||||
void skip_samples(unsigned int quantity) {
|
||||
int16_t throwaway_samples[quantity];
|
||||
get_samples(quantity, throwaway_samples);
|
||||
}
|
||||
@@ -151,22 +139,18 @@ class Speaker {
|
||||
*/
|
||||
template <class T> class Filter: public Speaker {
|
||||
public:
|
||||
~Filter()
|
||||
{
|
||||
~Filter() {
|
||||
_queue->flush();
|
||||
}
|
||||
|
||||
void run_for_cycles(unsigned int input_cycles)
|
||||
{
|
||||
void run_for_cycles(unsigned int input_cycles) {
|
||||
enqueue([=]() {
|
||||
unsigned int cycles_remaining = input_cycles;
|
||||
if(coefficients_are_dirty_) update_filter_coefficients();
|
||||
|
||||
// if input and output rates exactly match, just accumulate results and pass on
|
||||
if(input_cycles_per_second_ == output_cycles_per_second_ && high_frequency_cut_off_ < 0.0)
|
||||
{
|
||||
while(cycles_remaining)
|
||||
{
|
||||
if(input_cycles_per_second_ == output_cycles_per_second_ && high_frequency_cut_off_ < 0.0) {
|
||||
while(cycles_remaining) {
|
||||
unsigned int cycles_to_read = (unsigned int)(buffer_size_ - buffer_in_progress_pointer_);
|
||||
if(cycles_to_read > cycles_remaining) cycles_to_read = cycles_remaining;
|
||||
|
||||
@@ -174,11 +158,9 @@ template <class T> class Filter: public Speaker {
|
||||
buffer_in_progress_pointer_ += cycles_to_read;
|
||||
|
||||
// announce to delegate if full
|
||||
if(buffer_in_progress_pointer_ == buffer_size_)
|
||||
{
|
||||
if(buffer_in_progress_pointer_ == buffer_size_) {
|
||||
buffer_in_progress_pointer_ = 0;
|
||||
if(delegate_)
|
||||
{
|
||||
if(delegate_) {
|
||||
delegate_->speaker_did_complete_samples(this, buffer_in_progress_.get(), buffer_size_);
|
||||
}
|
||||
}
|
||||
@@ -190,26 +172,21 @@ template <class T> class Filter: public Speaker {
|
||||
}
|
||||
|
||||
// if the output rate is less than the input rate, use the filter
|
||||
if(input_cycles_per_second_ > output_cycles_per_second_)
|
||||
{
|
||||
while(cycles_remaining)
|
||||
{
|
||||
if(input_cycles_per_second_ > output_cycles_per_second_ || (input_cycles_per_second_ == output_cycles_per_second_ && high_frequency_cut_off_ >= 0.0)) {
|
||||
while(cycles_remaining) {
|
||||
unsigned int cycles_to_read = (unsigned int)std::min((int)cycles_remaining, number_of_taps_ - input_buffer_depth_);
|
||||
static_cast<T *>(this)->get_samples(cycles_to_read, &input_buffer_.get()[input_buffer_depth_]);
|
||||
cycles_remaining -= cycles_to_read;
|
||||
input_buffer_depth_ += cycles_to_read;
|
||||
|
||||
if(input_buffer_depth_ == number_of_taps_)
|
||||
{
|
||||
if(input_buffer_depth_ == number_of_taps_) {
|
||||
buffer_in_progress_.get()[buffer_in_progress_pointer_] = filter_->apply(input_buffer_.get());
|
||||
buffer_in_progress_pointer_++;
|
||||
|
||||
// announce to delegate if full
|
||||
if(buffer_in_progress_pointer_ == buffer_size_)
|
||||
{
|
||||
if(buffer_in_progress_pointer_ == buffer_size_) {
|
||||
buffer_in_progress_pointer_ = 0;
|
||||
if(delegate_)
|
||||
{
|
||||
if(delegate_) {
|
||||
delegate_->speaker_did_complete_samples(this, buffer_in_progress_.get(), buffer_size_);
|
||||
}
|
||||
}
|
||||
@@ -218,14 +195,11 @@ template <class T> class Filter: public Speaker {
|
||||
// preserve them in the correct locations (TODO: use a longer buffer to fix that) and don't skip
|
||||
// anything. Otherwise skip as required to get to the next sample batch and don't expect to reuse.
|
||||
uint64_t steps = stepper_->step();
|
||||
if(steps < number_of_taps_)
|
||||
{
|
||||
if(steps < number_of_taps_) {
|
||||
int16_t *input_buffer = input_buffer_.get();
|
||||
memmove(input_buffer, &input_buffer[steps], sizeof(int16_t) * ((size_t)number_of_taps_ - (size_t)steps));
|
||||
input_buffer_depth_ -= steps;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
if(steps > number_of_taps_)
|
||||
static_cast<T *>(this)->skip_samples((unsigned int)steps - (unsigned int)number_of_taps_);
|
||||
input_buffer_depth_ = 0;
|
||||
@@ -247,15 +221,11 @@ template <class T> class Filter: public Speaker {
|
||||
std::unique_ptr<int16_t> input_buffer_;
|
||||
int input_buffer_depth_;
|
||||
|
||||
void update_filter_coefficients()
|
||||
{
|
||||
void update_filter_coefficients() {
|
||||
// make a guess at a good number of taps if this hasn't been provided explicitly
|
||||
if(requested_number_of_taps_)
|
||||
{
|
||||
if(requested_number_of_taps_) {
|
||||
number_of_taps_ = requested_number_of_taps_;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
number_of_taps_ = (int)ceilf((input_cycles_per_second_ + output_cycles_per_second_) / output_cycles_per_second_);
|
||||
number_of_taps_ *= 2;
|
||||
number_of_taps_ |= 1;
|
||||
@@ -267,12 +237,9 @@ template <class T> class Filter: public Speaker {
|
||||
stepper_.reset(new SignalProcessing::Stepper((uint64_t)input_cycles_per_second_, (uint64_t)output_cycles_per_second_));
|
||||
|
||||
float high_pass_frequency;
|
||||
if(high_frequency_cut_off_ > 0.0)
|
||||
{
|
||||
if(high_frequency_cut_off_ > 0.0) {
|
||||
high_pass_frequency = std::min((float)output_cycles_per_second_ / 2.0f, high_frequency_cut_off_);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
high_pass_frequency = (float)output_cycles_per_second_ / 2.0f;
|
||||
}
|
||||
filter_.reset(new SignalProcessing::FIRFilter((unsigned int)number_of_taps_, (float)input_cycles_per_second_, 0.0, high_pass_frequency, SignalProcessing::FIRFilter::DefaultAttenuation));
|
||||
|
||||
@@ -176,8 +176,7 @@ template <class T> class Processor {
|
||||
|
||||
@param program The program to schedule.
|
||||
*/
|
||||
inline void schedule_program(const MicroOp *program)
|
||||
{
|
||||
inline void schedule_program(const MicroOp *program) {
|
||||
scheduled_programs_[schedule_programs_write_pointer_] = program;
|
||||
schedule_programs_write_pointer_ = (schedule_programs_write_pointer_+1)&3;
|
||||
}
|
||||
@@ -189,8 +188,7 @@ template <class T> class Processor {
|
||||
|
||||
@returns The current value of the flags register.
|
||||
*/
|
||||
uint8_t get_flags()
|
||||
{
|
||||
uint8_t get_flags() {
|
||||
return carry_flag_ | overflow_flag_ | (inverse_interrupt_flag_ ^ Flag::Interrupt) | (negative_result_ & 0x80) | (zero_result_ ? 0 : Flag::Zero) | Flag::Always | decimal_flag_;
|
||||
}
|
||||
|
||||
@@ -201,8 +199,7 @@ template <class T> class Processor {
|
||||
|
||||
@param flags The new value of the flags register.
|
||||
*/
|
||||
void set_flags(uint8_t flags)
|
||||
{
|
||||
void set_flags(uint8_t flags) {
|
||||
carry_flag_ = flags & Flag::Carry;
|
||||
negative_result_ = flags & Flag::Sign;
|
||||
zero_result_ = (~flags) & Flag::Zero;
|
||||
@@ -216,8 +213,7 @@ template <class T> class Processor {
|
||||
|
||||
@param operation The operation code for which to schedule a program.
|
||||
*/
|
||||
inline void decode_operation(uint8_t operation)
|
||||
{
|
||||
inline void decode_operation(uint8_t operation) {
|
||||
#define Program(...) {__VA_ARGS__, OperationMoveToNextProgram}
|
||||
|
||||
#define Absolute CycleLoadAddressAbsolute
|
||||
@@ -539,22 +535,22 @@ template <class T> class Processor {
|
||||
|
||||
protected:
|
||||
Processor() :
|
||||
schedule_programs_read_pointer_(0),
|
||||
schedule_programs_write_pointer_(0),
|
||||
is_jammed_(false),
|
||||
jam_handler_(nullptr),
|
||||
cycles_left_to_run_(0),
|
||||
ready_line_is_enabled_(false),
|
||||
ready_is_active_(false),
|
||||
scheduled_programs_{nullptr, nullptr, nullptr, nullptr},
|
||||
inverse_interrupt_flag_(0),
|
||||
s_(0),
|
||||
next_bus_operation_(BusOperation::None),
|
||||
interrupt_requests_(InterruptRequestFlags::PowerOn),
|
||||
irq_line_(0),
|
||||
nmi_line_is_enabled_(false),
|
||||
set_overflow_line_is_enabled_(false)
|
||||
{
|
||||
schedule_programs_read_pointer_(0),
|
||||
schedule_programs_write_pointer_(0),
|
||||
is_jammed_(false),
|
||||
jam_handler_(nullptr),
|
||||
cycles_left_to_run_(0),
|
||||
ready_line_is_enabled_(false),
|
||||
ready_is_active_(false),
|
||||
scheduled_programs_{nullptr, nullptr, nullptr, nullptr},
|
||||
inverse_interrupt_flag_(0),
|
||||
irq_request_history_(0),
|
||||
s_(0),
|
||||
next_bus_operation_(BusOperation::None),
|
||||
interrupt_requests_(InterruptRequestFlags::PowerOn),
|
||||
irq_line_(0),
|
||||
nmi_line_is_enabled_(false),
|
||||
set_overflow_line_is_enabled_(false) {
|
||||
// only the interrupt flag is defined upon reset but get_flags isn't going to
|
||||
// mask the other flags so we need to do that, at least
|
||||
carry_flag_ &= Flag::Carry;
|
||||
@@ -568,19 +564,18 @@ template <class T> class Processor {
|
||||
|
||||
@discussion Subclasses must implement @c perform_bus_operation(BusOperation operation, uint16_t address, uint8_t *value) .
|
||||
The 6502 will call that method for all bus accesses. The 6502 is guaranteed to perform one bus operation call per cycle.
|
||||
If it is a read operation then @c value will be seeded with the value 0xff.
|
||||
|
||||
@param number_of_cycles The number of cycles to run the 6502 for.
|
||||
*/
|
||||
void run_for_cycles(int number_of_cycles)
|
||||
{
|
||||
void run_for_cycles(int number_of_cycles) {
|
||||
static const MicroOp doBranch[] = {
|
||||
CycleReadFromPC,
|
||||
CycleAddSignedOperandToPC,
|
||||
OperationMoveToNextProgram
|
||||
};
|
||||
static uint8_t throwaway_target;
|
||||
static const MicroOp fetch_decode_execute[] =
|
||||
{
|
||||
static const MicroOp fetch_decode_execute[] = {
|
||||
CycleFetchOperation,
|
||||
CycleFetchOperand,
|
||||
OperationDecodeOperation,
|
||||
@@ -633,10 +628,8 @@ template <class T> class Processor {
|
||||
number_of_cycles -= static_cast<T *>(this)->perform_bus_operation(BusOperation::Ready, busAddress, busValue);
|
||||
}
|
||||
|
||||
if(!ready_is_active_)
|
||||
{
|
||||
if(nextBusOperation != BusOperation::None)
|
||||
{
|
||||
if(!ready_is_active_) {
|
||||
if(nextBusOperation != BusOperation::None) {
|
||||
bus_access();
|
||||
}
|
||||
|
||||
@@ -645,9 +638,9 @@ template <class T> class Processor {
|
||||
const MicroOp cycle = program[scheduleProgramProgramCounter];
|
||||
scheduleProgramProgramCounter++;
|
||||
|
||||
#define read_op(val, addr) nextBusOperation = BusOperation::ReadOpcode; busAddress = addr; busValue = &val
|
||||
#define read_mem(val, addr) nextBusOperation = BusOperation::Read; busAddress = addr; busValue = &val
|
||||
#define throwaway_read(addr) nextBusOperation = BusOperation::Read; busAddress = addr; busValue = &throwaway_target
|
||||
#define read_op(val, addr) nextBusOperation = BusOperation::ReadOpcode; busAddress = addr; busValue = &val; val = 0xff
|
||||
#define read_mem(val, addr) nextBusOperation = BusOperation::Read; busAddress = addr; busValue = &val; val = 0xff
|
||||
#define throwaway_read(addr) nextBusOperation = BusOperation::Read; busAddress = addr; busValue = &throwaway_target; throwaway_target = 0xff
|
||||
#define write_mem(val, addr) nextBusOperation = BusOperation::Write; busAddress = addr; busValue = &val
|
||||
|
||||
switch(cycle) {
|
||||
@@ -656,7 +649,7 @@ template <class T> class Processor {
|
||||
|
||||
case CycleFetchOperation: {
|
||||
last_operation_pc_ = pc_;
|
||||
// printf("%04x x:%02x\n", pc_.full, x_);
|
||||
// printf("%04x\n", pc_.full);
|
||||
pc_.full++;
|
||||
read_op(operation_, last_operation_pc_.full);
|
||||
|
||||
@@ -689,8 +682,7 @@ template <class T> class Processor {
|
||||
program = scheduled_programs_[scheduleProgramsReadPointer];
|
||||
continue;
|
||||
|
||||
#define push(v) \
|
||||
{\
|
||||
#define push(v) {\
|
||||
uint16_t targetAddress = s_ | 0x100; s_--;\
|
||||
write_mem(v, targetAddress);\
|
||||
}
|
||||
@@ -700,8 +692,7 @@ template <class T> class Processor {
|
||||
case CyclePushPCL: push(pc_.bytes.low); break;
|
||||
case CyclePushOperand: push(operand_); break;
|
||||
case CyclePushA: push(a_); break;
|
||||
case CycleNoWritePush:
|
||||
{
|
||||
case CycleNoWritePush: {
|
||||
uint16_t targetAddress = s_ | 0x100; s_--;
|
||||
read_mem(operand_, targetAddress);
|
||||
}
|
||||
@@ -1144,8 +1135,7 @@ template <class T> class Processor {
|
||||
@param r The register to set.
|
||||
@returns The value of the register. 8-bit registers will be returned as unsigned.
|
||||
*/
|
||||
uint16_t get_value_of_register(Register r)
|
||||
{
|
||||
uint16_t get_value_of_register(Register r) {
|
||||
switch (r) {
|
||||
case Register::ProgramCounter: return pc_.full;
|
||||
case Register::LastOperationAddress: return last_operation_pc_.full;
|
||||
@@ -1167,8 +1157,7 @@ template <class T> class Processor {
|
||||
@param r The register to set.
|
||||
@param value The value to set. If the register is only 8 bit, the value will be truncated.
|
||||
*/
|
||||
void set_value_of_register(Register r, uint16_t value)
|
||||
{
|
||||
void set_value_of_register(Register r, uint16_t value) {
|
||||
switch (r) {
|
||||
case Register::ProgramCounter: pc_.full = value; break;
|
||||
case Register::StackPointer: s_ = (uint8_t)value; break;
|
||||
@@ -1185,8 +1174,7 @@ template <class T> class Processor {
|
||||
Interrupts current execution flow to perform an RTS and, if the 6502 is currently jammed,
|
||||
to unjam it.
|
||||
*/
|
||||
void return_from_subroutine()
|
||||
{
|
||||
void return_from_subroutine() {
|
||||
s_++;
|
||||
static_cast<T *>(this)->perform_bus_operation(CPU6502::BusOperation::Read, 0x100 | s_, &pc_.bytes.low); s_++;
|
||||
static_cast<T *>(this)->perform_bus_operation(CPU6502::BusOperation::Read, 0x100 | s_, &pc_.bytes.high);
|
||||
@@ -1202,8 +1190,7 @@ template <class T> class Processor {
|
||||
|
||||
@param active @c true if the line is logically active; @c false otherwise.
|
||||
*/
|
||||
inline void set_ready_line(bool active)
|
||||
{
|
||||
inline void set_ready_line(bool active) {
|
||||
if(active) {
|
||||
ready_line_is_enabled_ = true;
|
||||
} else {
|
||||
@@ -1217,8 +1204,7 @@ template <class T> class Processor {
|
||||
|
||||
@param active @c true if the line is logically active; @c false otherwise.
|
||||
*/
|
||||
inline void set_reset_line(bool active)
|
||||
{
|
||||
inline void set_reset_line(bool active) {
|
||||
interrupt_requests_ = (interrupt_requests_ & ~InterruptRequestFlags::Reset) | (active ? InterruptRequestFlags::Reset : 0);
|
||||
}
|
||||
|
||||
@@ -1227,8 +1213,7 @@ template <class T> class Processor {
|
||||
|
||||
@returns @c true if the line is logically active; @c false otherwise.
|
||||
*/
|
||||
inline bool get_is_resetting()
|
||||
{
|
||||
inline bool get_is_resetting() {
|
||||
return !!(interrupt_requests_ & (InterruptRequestFlags::Reset | InterruptRequestFlags::PowerOn));
|
||||
}
|
||||
|
||||
@@ -1236,8 +1221,7 @@ template <class T> class Processor {
|
||||
This emulation automatically sets itself up in power-on state at creation, which has the effect of triggering a
|
||||
reset at the first opportunity. Use @c set_power_on to disable that behaviour.
|
||||
*/
|
||||
inline void set_power_on(bool active)
|
||||
{
|
||||
inline void set_power_on(bool active) {
|
||||
interrupt_requests_ = (interrupt_requests_ & ~InterruptRequestFlags::PowerOn) | (active ? InterruptRequestFlags::PowerOn : 0);
|
||||
}
|
||||
|
||||
@@ -1246,8 +1230,7 @@ template <class T> class Processor {
|
||||
|
||||
@param active @c true if the line is logically active; @c false otherwise.
|
||||
*/
|
||||
inline void set_irq_line(bool active)
|
||||
{
|
||||
inline void set_irq_line(bool active) {
|
||||
irq_line_ = active ? Flag::Interrupt : 0;
|
||||
}
|
||||
|
||||
@@ -1256,8 +1239,7 @@ template <class T> class Processor {
|
||||
|
||||
@param active @c true if the line is logically active; @c false otherwise.
|
||||
*/
|
||||
inline void set_overflow_line(bool active)
|
||||
{
|
||||
inline void set_overflow_line(bool active) {
|
||||
// a leading edge will set the overflow flag
|
||||
if(active && !set_overflow_line_is_enabled_)
|
||||
overflow_flag_ = Flag::Overflow;
|
||||
@@ -1269,8 +1251,7 @@ template <class T> class Processor {
|
||||
|
||||
@param active `true` if the line is logically active; `false` otherwise.
|
||||
*/
|
||||
inline void set_nmi_line(bool active)
|
||||
{
|
||||
inline void set_nmi_line(bool active) {
|
||||
// NMI is edge triggered, not level
|
||||
if(active && !nmi_line_is_enabled_)
|
||||
interrupt_requests_ |= InterruptRequestFlags::NMI;
|
||||
@@ -1283,8 +1264,7 @@ template <class T> class Processor {
|
||||
|
||||
@returns @c true if the 6502 is jammed; @c false otherwise.
|
||||
*/
|
||||
inline bool is_jammed()
|
||||
{
|
||||
inline bool is_jammed() {
|
||||
return is_jammed_;
|
||||
}
|
||||
|
||||
@@ -1293,8 +1273,7 @@ template <class T> class Processor {
|
||||
|
||||
@param handler The class instance that will be this 6502's jam handler from now on.
|
||||
*/
|
||||
inline void set_jam_handler(JamHandler *handler)
|
||||
{
|
||||
inline void set_jam_handler(JamHandler *handler) {
|
||||
jam_handler_ = handler;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -12,13 +12,11 @@
|
||||
|
||||
using namespace CPU6502;
|
||||
|
||||
AllRAMProcessor::AllRAMProcessor() : _timestamp(0)
|
||||
{
|
||||
AllRAMProcessor::AllRAMProcessor() : _timestamp(0) {
|
||||
set_power_on(false);
|
||||
}
|
||||
|
||||
int AllRAMProcessor::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value)
|
||||
{
|
||||
int AllRAMProcessor::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
_timestamp++;
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
@@ -30,13 +28,11 @@ int AllRAMProcessor::perform_bus_operation(CPU6502::BusOperation operation, uint
|
||||
return 1;
|
||||
}
|
||||
|
||||
void AllRAMProcessor::set_data_at_address(uint16_t startAddress, size_t length, const uint8_t *data)
|
||||
{
|
||||
void AllRAMProcessor::set_data_at_address(uint16_t startAddress, size_t length, const uint8_t *data) {
|
||||
size_t endAddress = std::min(startAddress + length, (size_t)65536);
|
||||
memcpy(&_memory[startAddress], data, endAddress - startAddress);
|
||||
}
|
||||
|
||||
uint32_t AllRAMProcessor::get_timestamp()
|
||||
{
|
||||
uint32_t AllRAMProcessor::get_timestamp() {
|
||||
return _timestamp;
|
||||
}
|
||||
|
||||
12
README.md
@@ -24,12 +24,22 @@ The full process of loading a title — even if you've never used the emulated m
|
||||
|
||||
## Signal Processing
|
||||
|
||||
Consider an ordinary, unmodified Commodore Vic-20. Its only video output is composite. Therefore the emulated machine's only video output is composite. In order to display the video output, your GPU then decodes composite video. Therefore all composite video artefacts are present and exactly correct, not because of a posthoc filter combining all the subjective effects that this author associates with composite video but because the real signal is really being processed.
|
||||
Consider an ordinary, unmodified Commodore Vic-20. Its only video output is composite. Therefore the emulated machine's only video output is composite. In order to display the video output, your GPU then decodes composite video. Therefore all composite video artefacts are present and exactly correct, not because of a post hoc filter combining all the subjective effects that this author associates with composite video but because the real signal is really being processed.
|
||||
|
||||
Similar effort is put into audio generation. If the real machine normally generates audio at 192Khz then the emulator generates a 192Khz source signal and filters it down to whatever the host machine can output.
|
||||
|
||||
If your machine has a 4k monitor and a 96Khz audio output? Then you'll get a 4k rendering of a composite display and, assuming the emulated machine produces source audio at or above 96Khz, 96,000 individual distinct audio samples a second. Interlaced video also works and looks much as it always did on those machines that produce it.
|
||||
|
||||
### Samples
|
||||
|
||||
| 1:1 Pixel Copying | Composite Decoded |
|
||||
|---|---|
|
||||
|||
|
||||
|||
|
||||
|||
|
||||
|
||||
<img src="READMEImages/ReptonInterlaced.gif" height=600 alt="Repton title screen, interlaced">
|
||||
|
||||
## Low Latency
|
||||
|
||||
The display produced is an emulated CRT, with phosphor decay. Therefore if you have a 140Hz monitor it can produce 140 distinct frames per second. Latency is dictated by the output hardware, not the emulated machine.
|
||||
|
||||
BIN
READMEImages/CompositeElectron.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
READMEImages/CompositeRepton3.png
Normal file
|
After Width: | Height: | Size: 136 KiB |
BIN
READMEImages/CompositeStormlord.png
Normal file
|
After Width: | Height: | Size: 271 KiB |
BIN
READMEImages/NaiveElectron.png
Normal file
|
After Width: | Height: | Size: 309 B |
BIN
READMEImages/NaiveRepton3.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
READMEImages/NaiveStormlord.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
READMEImages/ReptonInterlaced.gif
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
@@ -36,34 +36,28 @@ using namespace SignalProcessing;
|
||||
#define kCSKaiserBesselFilterFixedMultiplier 32767.0f
|
||||
#define kCSKaiserBesselFilterFixedShift 15
|
||||
|
||||
/* ino evaluates the 0th order Bessel function at a */
|
||||
float FIRFilter::ino(float a)
|
||||
{
|
||||
/*! Evaluates the 0th order Bessel function at @c a. */
|
||||
float FIRFilter::ino(float a) {
|
||||
float d = 0.0f;
|
||||
float ds = 1.0f;
|
||||
float s = 1.0f;
|
||||
|
||||
do
|
||||
{
|
||||
do {
|
||||
d += 2.0f;
|
||||
ds *= (a * a) / (d * d);
|
||||
s += ds;
|
||||
}
|
||||
while(ds > s*1e-6f);
|
||||
} while(ds > s*1e-6f);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
//static void csfilter_setIdealisedFilterResponse(short *filterCoefficients, float *A, float attenuation, unsigned int numberOfTaps)
|
||||
void FIRFilter::coefficients_for_idealised_filter_response(short *filterCoefficients, float *A, float attenuation, unsigned int numberOfTaps)
|
||||
{
|
||||
void FIRFilter::coefficients_for_idealised_filter_response(short *filterCoefficients, float *A, float attenuation, unsigned int numberOfTaps) {
|
||||
/* calculate alpha, which is the Kaiser-Bessel window shape factor */
|
||||
float a; // to take the place of alpha in the normal derivation
|
||||
|
||||
if(attenuation < 21.0f)
|
||||
if(attenuation < 21.0f) {
|
||||
a = 0.0f;
|
||||
else
|
||||
{
|
||||
} else {
|
||||
if(attenuation > 50.0f)
|
||||
a = 0.1102f * (attenuation - 8.7f);
|
||||
else
|
||||
@@ -76,8 +70,7 @@ void FIRFilter::coefficients_for_idealised_filter_response(short *filterCoeffici
|
||||
unsigned int Np = (numberOfTaps - 1) / 2;
|
||||
float I0 = ino(a);
|
||||
float NpSquared = (float)(Np * Np);
|
||||
for(unsigned int i = 0; i <= Np; i++)
|
||||
{
|
||||
for(unsigned int i = 0; i <= Np; i++) {
|
||||
filterCoefficientsFloat[Np + i] =
|
||||
A[i] *
|
||||
ino(a * sqrtf(1.0f - ((float)(i * i) / NpSquared) )) /
|
||||
@@ -85,38 +78,32 @@ void FIRFilter::coefficients_for_idealised_filter_response(short *filterCoeffici
|
||||
}
|
||||
|
||||
/* coefficients are symmetrical, so copy from right hand side to left side */
|
||||
for(unsigned int i = 0; i < Np; i++)
|
||||
{
|
||||
for(unsigned int i = 0; i < Np; i++) {
|
||||
filterCoefficientsFloat[i] = filterCoefficientsFloat[numberOfTaps - 1 - i];
|
||||
}
|
||||
|
||||
/* scale back up so that we retain 100% of input volume */
|
||||
float coefficientTotal = 0.0f;
|
||||
for(unsigned int i = 0; i < numberOfTaps; i++)
|
||||
{
|
||||
for(unsigned int i = 0; i < numberOfTaps; i++) {
|
||||
coefficientTotal += filterCoefficientsFloat[i];
|
||||
}
|
||||
|
||||
/* we'll also need integer versions, potentially */
|
||||
float coefficientMultiplier = 1.0f / coefficientTotal;
|
||||
for(unsigned int i = 0; i < numberOfTaps; i++)
|
||||
{
|
||||
for(unsigned int i = 0; i < numberOfTaps; i++) {
|
||||
filterCoefficients[i] = (short)(filterCoefficientsFloat[i] * kCSKaiserBesselFilterFixedMultiplier * coefficientMultiplier);
|
||||
}
|
||||
|
||||
delete[] filterCoefficientsFloat;
|
||||
}
|
||||
|
||||
void FIRFilter::get_coefficients(float *coefficients)
|
||||
{
|
||||
for(unsigned int i = 0; i < number_of_taps_; i++)
|
||||
{
|
||||
void FIRFilter::get_coefficients(float *coefficients) {
|
||||
for(unsigned int i = 0; i < number_of_taps_; i++) {
|
||||
coefficients[i] = (float)filter_coefficients_[i] / kCSKaiserBesselFilterFixedMultiplier;
|
||||
}
|
||||
}
|
||||
|
||||
FIRFilter::FIRFilter(unsigned int number_of_taps, float input_sample_rate, float low_frequency, float high_frequency, float attenuation)
|
||||
{
|
||||
FIRFilter::FIRFilter(unsigned int number_of_taps, float input_sample_rate, float low_frequency, float high_frequency, float attenuation) {
|
||||
// we must be asked to filter based on an odd number of
|
||||
// taps, and at least three
|
||||
if(number_of_taps < 3) number_of_taps = 3;
|
||||
@@ -135,8 +122,7 @@ FIRFilter::FIRFilter(unsigned int number_of_taps, float input_sample_rate, float
|
||||
|
||||
float *A = new float[Np+1];
|
||||
A[0] = 2.0f * (high_frequency - low_frequency) / input_sample_rate;
|
||||
for(unsigned int i = 1; i <= Np; i++)
|
||||
{
|
||||
for(unsigned int i = 1; i <= Np; i++) {
|
||||
float iPi = (float)i * (float)M_PI;
|
||||
A[i] =
|
||||
(
|
||||
@@ -151,7 +137,6 @@ FIRFilter::FIRFilter(unsigned int number_of_taps, float input_sample_rate, float
|
||||
delete[] A;
|
||||
}
|
||||
|
||||
FIRFilter::~FIRFilter()
|
||||
{
|
||||
FIRFilter::~FIRFilter() {
|
||||
delete[] filter_coefficients_;
|
||||
}
|
||||
|
||||
@@ -57,24 +57,21 @@ class FIRFilter {
|
||||
@param src The source buffer to apply the filter to.
|
||||
@returns The result of applying the filter.
|
||||
*/
|
||||
inline short apply(const short *src)
|
||||
{
|
||||
inline short apply(const short *src) {
|
||||
#ifdef __APPLE__
|
||||
short result;
|
||||
vDSP_dotpr_s1_15(filter_coefficients_, 1, src, 1, &result, number_of_taps_);
|
||||
return result;
|
||||
#else
|
||||
int outputValue = 0;
|
||||
for(unsigned int c = 0; c < number_of_taps_; c++)
|
||||
{
|
||||
for(unsigned int c = 0; c < number_of_taps_; c++) {
|
||||
outputValue += filter_coefficients_[c] * src[c];
|
||||
}
|
||||
return (short)(outputValue >> kCSKaiserBesselFilterFixedShift);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline unsigned int get_number_of_taps()
|
||||
{
|
||||
inline unsigned int get_number_of_taps() {
|
||||
return number_of_taps_;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,8 +23,7 @@ namespace SignalProcessing {
|
||||
Pegs the beginning of both clocks to the time at which the stepper is created. So e.g. a stepper
|
||||
that converts from an input clock of 1200 to an output clock of 2 will first fire on cycle 600.
|
||||
*/
|
||||
class Stepper
|
||||
{
|
||||
class Stepper {
|
||||
public:
|
||||
/*!
|
||||
Establishes a stepper with a one-to-one conversion rate.
|
||||
@@ -48,12 +47,10 @@ class Stepper
|
||||
|
||||
@returns the number of output steps.
|
||||
*/
|
||||
inline uint64_t step()
|
||||
{
|
||||
inline uint64_t step() {
|
||||
uint64_t update = whole_step_;
|
||||
accumulated_error_ += adjustment_up_;
|
||||
if(accumulated_error_ > 0)
|
||||
{
|
||||
if(accumulated_error_ > 0) {
|
||||
update++;
|
||||
accumulated_error_ -= adjustment_down_;
|
||||
}
|
||||
@@ -65,12 +62,10 @@ class Stepper
|
||||
|
||||
@returns the number of output steps.
|
||||
*/
|
||||
inline uint64_t step(uint64_t number_of_steps)
|
||||
{
|
||||
inline uint64_t step(uint64_t number_of_steps) {
|
||||
uint64_t update = whole_step_ * number_of_steps;
|
||||
accumulated_error_ += adjustment_up_ * (int64_t)number_of_steps;
|
||||
if(accumulated_error_ > 0)
|
||||
{
|
||||
if(accumulated_error_ > 0) {
|
||||
update += 1 + (uint64_t)(accumulated_error_ / adjustment_down_);
|
||||
accumulated_error_ = (accumulated_error_ % adjustment_down_) - adjustment_down_;
|
||||
}
|
||||
@@ -80,16 +75,14 @@ class Stepper
|
||||
/*!
|
||||
@returns the output rate.
|
||||
*/
|
||||
inline uint64_t get_output_rate()
|
||||
{
|
||||
inline uint64_t get_output_rate() {
|
||||
return output_rate_;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns the input rate.
|
||||
*/
|
||||
inline uint64_t get_input_rate()
|
||||
{
|
||||
inline uint64_t get_input_rate() {
|
||||
return input_rate_;
|
||||
}
|
||||
|
||||
|
||||