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
|
# Exclude system ROMs
|
||||||
ROMImages/*
|
ROMImages/*
|
||||||
|
OSBindings/Mac/Clock SignalTests/Atari\ ROMs
|
||||||
|
|
||||||
# CocoaPods
|
# CocoaPods
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -22,8 +22,7 @@ WD1770::Status::Status() :
|
|||||||
lost_data(false),
|
lost_data(false),
|
||||||
data_request(false),
|
data_request(false),
|
||||||
interrupt_request(false),
|
interrupt_request(false),
|
||||||
busy(false)
|
busy(false) {}
|
||||||
{}
|
|
||||||
|
|
||||||
WD1770::WD1770(Personality p) :
|
WD1770::WD1770(Personality p) :
|
||||||
Storage::Disk::Controller(8000000, 16, 300),
|
Storage::Disk::Controller(8000000, 16, 300),
|
||||||
@@ -36,14 +35,12 @@ WD1770::WD1770(Personality p) :
|
|||||||
data_mode_(DataMode::Scanning),
|
data_mode_(DataMode::Scanning),
|
||||||
delegate_(nullptr),
|
delegate_(nullptr),
|
||||||
personality_(p),
|
personality_(p),
|
||||||
head_is_loaded_(false)
|
head_is_loaded_(false) {
|
||||||
{
|
|
||||||
set_is_double_density(false);
|
set_is_double_density(false);
|
||||||
posit_event(Event::Command);
|
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;
|
is_double_density_ = is_double_density;
|
||||||
Storage::Time bit_length;
|
Storage::Time bit_length;
|
||||||
bit_length.length = 1;
|
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;
|
if(!is_double_density) is_awaiting_marker_value_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WD1770::set_register(int address, uint8_t value)
|
void WD1770::set_register(int address, uint8_t value) {
|
||||||
{
|
switch(address&3) {
|
||||||
switch(address&3)
|
case 0: {
|
||||||
{
|
if((value&0xf0) == 0xd0) {
|
||||||
case 0:
|
|
||||||
{
|
|
||||||
if((value&0xf0) == 0xd0)
|
|
||||||
{
|
|
||||||
printf("!!!TODO: force interrupt!!!\n");
|
printf("!!!TODO: force interrupt!!!\n");
|
||||||
update_status([] (Status &status) {
|
update_status([] (Status &status) {
|
||||||
status.type = Status::One;
|
status.type = Status::One;
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
command_ = value;
|
command_ = value;
|
||||||
posit_event(Event::Command);
|
posit_event(Event::Command);
|
||||||
}
|
}
|
||||||
@@ -84,12 +75,9 @@ void WD1770::set_register(int address, uint8_t value)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t WD1770::get_register(int address)
|
uint8_t WD1770::get_register(int address) {
|
||||||
{
|
switch(address&3) {
|
||||||
switch(address&3)
|
default: {
|
||||||
{
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
update_status([] (Status &status) {
|
update_status([] (Status &status) {
|
||||||
status.interrupt_request = false;
|
status.interrupt_request = false;
|
||||||
});
|
});
|
||||||
@@ -97,8 +85,7 @@ uint8_t WD1770::get_register(int address)
|
|||||||
(status_.write_protect ? Flag::WriteProtect : 0) |
|
(status_.write_protect ? Flag::WriteProtect : 0) |
|
||||||
(status_.crc_error ? Flag::CRCError : 0) |
|
(status_.crc_error ? Flag::CRCError : 0) |
|
||||||
(status_.busy ? Flag::Busy : 0);
|
(status_.busy ? Flag::Busy : 0);
|
||||||
switch(status_.type)
|
switch(status_.type) {
|
||||||
{
|
|
||||||
case Status::One:
|
case Status::One:
|
||||||
status |=
|
status |=
|
||||||
(get_is_track_zero() ? Flag::TrackZero : 0) |
|
(get_is_track_zero() ? Flag::TrackZero : 0) |
|
||||||
@@ -116,14 +103,11 @@ uint8_t WD1770::get_register(int address)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!has_motor_on_line())
|
if(!has_motor_on_line()) {
|
||||||
{
|
|
||||||
status |= get_drive_is_ready() ? 0 : Flag::NotReady;
|
status |= get_drive_is_ready() ? 0 : Flag::NotReady;
|
||||||
if(status_.type == Status::One)
|
if(status_.type == Status::One)
|
||||||
status |= (head_is_loaded_ ? Flag::HeadLoaded : 0);
|
status |= (head_is_loaded_ ? Flag::HeadLoaded : 0);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
status |= (get_motor_on() ? Flag::MotorOn : 0);
|
status |= (get_motor_on() ? Flag::MotorOn : 0);
|
||||||
if(status_.type == Status::One)
|
if(status_.type == Status::One)
|
||||||
status |= (status_.spin_up ? Flag::SpinUp : 0);
|
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);
|
Storage::Disk::Controller::run_for_cycles((int)number_of_cycles);
|
||||||
|
|
||||||
if(delay_time_)
|
if(delay_time_) {
|
||||||
{
|
if(delay_time_ <= number_of_cycles) {
|
||||||
if(delay_time_ <= number_of_cycles)
|
|
||||||
{
|
|
||||||
delay_time_ = 0;
|
delay_time_ = 0;
|
||||||
posit_event(Event::Timer);
|
posit_event(Event::Timer);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
delay_time_ -= number_of_cycles;
|
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;
|
if(data_mode_ == DataMode::Writing) return;
|
||||||
|
|
||||||
shift_register_ = (shift_register_ << 1) | value;
|
shift_register_ = (shift_register_ << 1) | value;
|
||||||
bits_since_token_++;
|
bits_since_token_++;
|
||||||
|
|
||||||
if(data_mode_ == DataMode::Scanning)
|
if(data_mode_ == DataMode::Scanning) {
|
||||||
{
|
|
||||||
Token::Type token_type = Token::Byte;
|
Token::Type token_type = Token::Byte;
|
||||||
if(!is_double_density_)
|
if(!is_double_density_) {
|
||||||
{
|
switch(shift_register_ & 0xffff) {
|
||||||
switch(shift_register_ & 0xffff)
|
|
||||||
{
|
|
||||||
case Storage::Encodings::MFM::FMIndexAddressMark:
|
case Storage::Encodings::MFM::FMIndexAddressMark:
|
||||||
token_type = Token::Index;
|
token_type = Token::Index;
|
||||||
crc_generator_.reset();
|
crc_generator_.reset();
|
||||||
@@ -195,11 +170,8 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole)
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
switch(shift_register_ & 0xffff) {
|
||||||
{
|
|
||||||
switch(shift_register_ & 0xffff)
|
|
||||||
{
|
|
||||||
case Storage::Encodings::MFM::MFMIndexSync:
|
case Storage::Encodings::MFM::MFMIndexSync:
|
||||||
bits_since_token_ = 0;
|
bits_since_token_ = 0;
|
||||||
is_awaiting_marker_value_ = true;
|
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;
|
latest_token_.type = token_type;
|
||||||
bits_since_token_ = 0;
|
bits_since_token_ = 0;
|
||||||
posit_event(Event::Token);
|
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_.type = Token::Byte;
|
||||||
latest_token_.byte_value = (uint8_t)(
|
latest_token_.byte_value = (uint8_t)(
|
||||||
((shift_register_ & 0x0001) >> 0) |
|
((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));
|
((shift_register_ & 0x4000) >> 7));
|
||||||
bits_since_token_ = 0;
|
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;
|
is_awaiting_marker_value_ = false;
|
||||||
switch(latest_token_.byte_value)
|
switch(latest_token_.byte_value) {
|
||||||
{
|
|
||||||
case Storage::Encodings::MFM::IndexAddressByte:
|
case Storage::Encodings::MFM::IndexAddressByte:
|
||||||
latest_token_.type = Token::Index;
|
latest_token_.type = Token::Index;
|
||||||
break;
|
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_++;
|
index_hole_count_++;
|
||||||
posit_event(Event::IndexHole);
|
posit_event(Event::IndexHole);
|
||||||
if(index_hole_count_target_ == index_hole_count_)
|
if(index_hole_count_target_ == index_hole_count_) {
|
||||||
{
|
|
||||||
posit_event(Event::IndexHoleTarget);
|
posit_event(Event::IndexHoleTarget);
|
||||||
index_hole_count_target_ = -1;
|
index_hole_count_target_ = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// motor power-down
|
// 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);
|
set_motor_on(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// head unload
|
// 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);
|
set_head_load_request(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WD1770::process_write_completed()
|
void WD1770::process_write_completed() {
|
||||||
{
|
|
||||||
posit_event(Event::DataWritten);
|
posit_event(Event::DataWritten);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,11 +268,9 @@ void WD1770::process_write_completed()
|
|||||||
#define END_SECTION() 0; }
|
#define END_SECTION() 0; }
|
||||||
|
|
||||||
#define READ_ID() \
|
#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_++; } \
|
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; \
|
header_[distance_into_section_ - 1] = latest_token_.byte_value; \
|
||||||
distance_into_section_++; \
|
distance_into_section_++; \
|
||||||
} \
|
} \
|
||||||
@@ -343,8 +304,7 @@ void WD1770::process_write_completed()
|
|||||||
// ! 4 ! Forc int ! 1 1 0 1 i3 i2 i1 i0 !
|
// ! 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;
|
if(!(interesting_event_mask_ & (int)new_event_type)) return;
|
||||||
interesting_event_mask_ &= ~new_event_type;
|
interesting_event_mask_ &= ~new_event_type;
|
||||||
|
|
||||||
@@ -405,8 +365,7 @@ void WD1770::posit_event(Event new_event_type)
|
|||||||
goto begin_type1_load_head;
|
goto begin_type1_load_head;
|
||||||
|
|
||||||
begin_type1_load_head:
|
begin_type1_load_head:
|
||||||
if(!(command_&0x08))
|
if(!(command_&0x08)) {
|
||||||
{
|
|
||||||
set_head_load_request(false);
|
set_head_load_request(false);
|
||||||
goto test_type1_type;
|
goto test_type1_type;
|
||||||
}
|
}
|
||||||
@@ -426,8 +385,7 @@ void WD1770::posit_event(Event new_event_type)
|
|||||||
if((command_ >> 5) != 0) goto perform_step_command;
|
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.
|
// 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;
|
track_ = 0xff;
|
||||||
data_ = 0;
|
data_ = 0;
|
||||||
}
|
}
|
||||||
@@ -440,15 +398,13 @@ void WD1770::posit_event(Event new_event_type)
|
|||||||
if(step_direction_) track_++; else track_--;
|
if(step_direction_) track_++; else track_--;
|
||||||
|
|
||||||
perform_step:
|
perform_step:
|
||||||
if(!step_direction_ && get_is_track_zero())
|
if(!step_direction_ && get_is_track_zero()) {
|
||||||
{
|
|
||||||
track_ = 0;
|
track_ = 0;
|
||||||
goto verify;
|
goto verify;
|
||||||
}
|
}
|
||||||
step(step_direction_ ? 1 : -1);
|
step(step_direction_ ? 1 : -1);
|
||||||
int time_to_wait;
|
int time_to_wait;
|
||||||
switch(command_ & 3)
|
switch(command_ & 3) {
|
||||||
{
|
|
||||||
default:
|
default:
|
||||||
case 0: time_to_wait = 6; break;
|
case 0: time_to_wait = 6; break;
|
||||||
case 1: time_to_wait = 12; break;
|
case 1: time_to_wait = 12; break;
|
||||||
@@ -464,8 +420,7 @@ void WD1770::posit_event(Event new_event_type)
|
|||||||
goto perform_step;
|
goto perform_step;
|
||||||
|
|
||||||
verify:
|
verify:
|
||||||
if(!(command_ & 0x04))
|
if(!(command_ & 0x04)) {
|
||||||
{
|
|
||||||
goto wait_for_command;
|
goto wait_for_command;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,26 +431,22 @@ void WD1770::posit_event(Event new_event_type)
|
|||||||
WAIT_FOR_EVENT(Event::IndexHole | Event::Token);
|
WAIT_FOR_EVENT(Event::IndexHole | Event::Token);
|
||||||
READ_ID();
|
READ_ID();
|
||||||
|
|
||||||
if(index_hole_count_ == 6)
|
if(index_hole_count_ == 6) {
|
||||||
{
|
|
||||||
update_status([] (Status &status) {
|
update_status([] (Status &status) {
|
||||||
status.seek_error = true;
|
status.seek_error = true;
|
||||||
});
|
});
|
||||||
goto wait_for_command;
|
goto wait_for_command;
|
||||||
}
|
}
|
||||||
if(distance_into_section_ == 7)
|
if(distance_into_section_ == 7) {
|
||||||
{
|
|
||||||
data_mode_ = DataMode::Scanning;
|
data_mode_ = DataMode::Scanning;
|
||||||
if(crc_generator_.get_value())
|
if(crc_generator_.get_value()) {
|
||||||
{
|
|
||||||
update_status([] (Status &status) {
|
update_status([] (Status &status) {
|
||||||
status.crc_error = true;
|
status.crc_error = true;
|
||||||
});
|
});
|
||||||
goto verify_read_data;
|
goto verify_read_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(header_[0] == track_)
|
if(header_[0] == track_) {
|
||||||
{
|
|
||||||
printf("Reached track %d\n", track_);
|
printf("Reached track %d\n", track_);
|
||||||
update_status([] (Status &status) {
|
update_status([] (Status &status) {
|
||||||
status.crc_error = false;
|
status.crc_error = false;
|
||||||
@@ -553,8 +504,7 @@ void WD1770::posit_event(Event new_event_type)
|
|||||||
WAIT_FOR_TIME(30);
|
WAIT_FOR_TIME(30);
|
||||||
|
|
||||||
test_type2_write_protection:
|
test_type2_write_protection:
|
||||||
if(command_&0x20 && get_drive_is_read_only())
|
if(command_&0x20 && get_drive_is_read_only()) {
|
||||||
{
|
|
||||||
update_status([] (Status &status) {
|
update_status([] (Status &status) {
|
||||||
status.write_protect = true;
|
status.write_protect = true;
|
||||||
});
|
});
|
||||||
@@ -565,24 +515,20 @@ void WD1770::posit_event(Event new_event_type)
|
|||||||
WAIT_FOR_EVENT(Event::IndexHole | Event::Token);
|
WAIT_FOR_EVENT(Event::IndexHole | Event::Token);
|
||||||
READ_ID();
|
READ_ID();
|
||||||
|
|
||||||
if(index_hole_count_ == 5)
|
if(index_hole_count_ == 5) {
|
||||||
{
|
|
||||||
printf("Failed to find sector %d\n", sector_);
|
printf("Failed to find sector %d\n", sector_);
|
||||||
update_status([] (Status &status) {
|
update_status([] (Status &status) {
|
||||||
status.record_not_found = true;
|
status.record_not_found = true;
|
||||||
});
|
});
|
||||||
goto wait_for_command;
|
goto wait_for_command;
|
||||||
}
|
}
|
||||||
if(distance_into_section_ == 7)
|
if(distance_into_section_ == 7) {
|
||||||
{
|
|
||||||
printf("Considering %d/%d\n", header_[0], header_[2]);
|
printf("Considering %d/%d\n", header_[0], header_[2]);
|
||||||
data_mode_ = DataMode::Scanning;
|
data_mode_ = DataMode::Scanning;
|
||||||
if( header_[0] == track_ && header_[2] == sector_ &&
|
if( header_[0] == track_ && header_[2] == sector_ &&
|
||||||
(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1]))
|
(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1])) {
|
||||||
{
|
|
||||||
printf("Found %d/%d\n", header_[0], header_[2]);
|
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");
|
printf("CRC error; back to searching\n");
|
||||||
update_status([] (Status &status) {
|
update_status([] (Status &status) {
|
||||||
status.crc_error = true;
|
status.crc_error = true;
|
||||||
@@ -607,8 +553,7 @@ void WD1770::posit_event(Event new_event_type)
|
|||||||
type2_read_data:
|
type2_read_data:
|
||||||
WAIT_FOR_EVENT(Event::Token);
|
WAIT_FOR_EVENT(Event::Token);
|
||||||
// TODO: timeout
|
// 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) {
|
update_status([this] (Status &status) {
|
||||||
status.record_type = (latest_token_.type == Token::DeletedData);
|
status.record_type = (latest_token_.type == Token::DeletedData);
|
||||||
});
|
});
|
||||||
@@ -627,8 +572,7 @@ void WD1770::posit_event(Event new_event_type)
|
|||||||
status.data_request = true;
|
status.data_request = true;
|
||||||
});
|
});
|
||||||
distance_into_section_++;
|
distance_into_section_++;
|
||||||
if(distance_into_section_ == 128 << header_[3])
|
if(distance_into_section_ == 128 << header_[3]) {
|
||||||
{
|
|
||||||
distance_into_section_ = 0;
|
distance_into_section_ = 0;
|
||||||
goto type2_check_crc;
|
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;
|
if(latest_token_.type != Token::Byte) goto type2_read_byte;
|
||||||
header_[distance_into_section_] = latest_token_.byte_value;
|
header_[distance_into_section_] = latest_token_.byte_value;
|
||||||
distance_into_section_++;
|
distance_into_section_++;
|
||||||
if(distance_into_section_ == 2)
|
if(distance_into_section_ == 2) {
|
||||||
{
|
if(crc_generator_.get_value()) {
|
||||||
if(crc_generator_.get_value())
|
|
||||||
{
|
|
||||||
printf("CRC error; terminating\n");
|
printf("CRC error; terminating\n");
|
||||||
update_status([this] (Status &status) {
|
update_status([this] (Status &status) {
|
||||||
status.crc_error = true;
|
status.crc_error = true;
|
||||||
@@ -650,8 +592,7 @@ void WD1770::posit_event(Event new_event_type)
|
|||||||
goto wait_for_command;
|
goto wait_for_command;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(command_ & 0x10)
|
if(command_ & 0x10) {
|
||||||
{
|
|
||||||
sector_++;
|
sector_++;
|
||||||
goto test_type2_write_protection;
|
goto test_type2_write_protection;
|
||||||
}
|
}
|
||||||
@@ -667,35 +608,29 @@ void WD1770::posit_event(Event new_event_type)
|
|||||||
status.data_request = true;
|
status.data_request = true;
|
||||||
});
|
});
|
||||||
WAIT_FOR_BYTES(9);
|
WAIT_FOR_BYTES(9);
|
||||||
if(status_.data_request)
|
if(status_.data_request) {
|
||||||
{
|
|
||||||
update_status([] (Status &status) {
|
update_status([] (Status &status) {
|
||||||
status.lost_data = true;
|
status.lost_data = true;
|
||||||
});
|
});
|
||||||
goto wait_for_command;
|
goto wait_for_command;
|
||||||
}
|
}
|
||||||
WAIT_FOR_BYTES(1);
|
WAIT_FOR_BYTES(1);
|
||||||
if(is_double_density_)
|
if(is_double_density_) {
|
||||||
{
|
|
||||||
WAIT_FOR_BYTES(11);
|
WAIT_FOR_BYTES(11);
|
||||||
}
|
}
|
||||||
|
|
||||||
data_mode_ = DataMode::Writing;
|
data_mode_ = DataMode::Writing;
|
||||||
begin_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);
|
write_byte(0);
|
||||||
}
|
}
|
||||||
WAIT_FOR_EVENT(Event::DataWritten);
|
WAIT_FOR_EVENT(Event::DataWritten);
|
||||||
|
|
||||||
if(is_double_density_)
|
if(is_double_density_) {
|
||||||
{
|
|
||||||
crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
|
crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
|
||||||
for(int c = 0; c < 3; c++) write_raw_short(Storage::Encodings::MFM::MFMSync);
|
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);
|
write_byte((command_&0x01) ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
crc_generator_.reset();
|
crc_generator_.reset();
|
||||||
crc_generator_.add((command_&0x01) ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte);
|
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);
|
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_);
|
write_byte(data_);
|
||||||
distance_into_section_++;
|
distance_into_section_++;
|
||||||
if(distance_into_section_ == 128 << header_[3])
|
if(distance_into_section_ == 128 << header_[3]) {
|
||||||
{
|
|
||||||
goto type2_write_crc;
|
goto type2_write_crc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -724,8 +658,7 @@ void WD1770::posit_event(Event new_event_type)
|
|||||||
status.data_request = true;
|
status.data_request = true;
|
||||||
});
|
});
|
||||||
WAIT_FOR_EVENT(Event::DataWritten);
|
WAIT_FOR_EVENT(Event::DataWritten);
|
||||||
if(status_.data_request)
|
if(status_.data_request) {
|
||||||
{
|
|
||||||
end_writing();
|
end_writing();
|
||||||
update_status([] (Status &status) {
|
update_status([] (Status &status) {
|
||||||
status.lost_data = true;
|
status.lost_data = true;
|
||||||
@@ -735,8 +668,7 @@ void WD1770::posit_event(Event new_event_type)
|
|||||||
|
|
||||||
goto type2_write_loop;
|
goto type2_write_loop;
|
||||||
|
|
||||||
type2_write_crc:
|
type2_write_crc: {
|
||||||
{
|
|
||||||
uint16_t crc = crc_generator_.get_value();
|
uint16_t crc = crc_generator_.get_value();
|
||||||
write_byte(crc >> 8);
|
write_byte(crc >> 8);
|
||||||
write_byte(crc & 0xff);
|
write_byte(crc & 0xff);
|
||||||
@@ -745,8 +677,7 @@ void WD1770::posit_event(Event new_event_type)
|
|||||||
WAIT_FOR_EVENT(Event::DataWritten);
|
WAIT_FOR_EVENT(Event::DataWritten);
|
||||||
end_writing();
|
end_writing();
|
||||||
|
|
||||||
if(command_ & 0x10)
|
if(command_ & 0x10) {
|
||||||
{
|
|
||||||
sector_++;
|
sector_++;
|
||||||
goto test_type2_write_protection;
|
goto test_type2_write_protection;
|
||||||
}
|
}
|
||||||
@@ -802,13 +733,10 @@ void WD1770::posit_event(Event new_event_type)
|
|||||||
|
|
||||||
read_address_get_header:
|
read_address_get_header:
|
||||||
WAIT_FOR_EVENT(Event::IndexHole | Event::Token);
|
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_++; }
|
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) {
|
||||||
{
|
if(status_.data_request) {
|
||||||
if(status_.data_request)
|
|
||||||
{
|
|
||||||
update_status([] (Status &status) {
|
update_status([] (Status &status) {
|
||||||
status.lost_data = true;
|
status.lost_data = true;
|
||||||
});
|
});
|
||||||
@@ -821,10 +749,8 @@ void WD1770::posit_event(Event new_event_type)
|
|||||||
});
|
});
|
||||||
distance_into_section_++;
|
distance_into_section_++;
|
||||||
|
|
||||||
if(distance_into_section_ == 7)
|
if(distance_into_section_ == 7) {
|
||||||
{
|
if(crc_generator_.get_value()) {
|
||||||
if(crc_generator_.get_value())
|
|
||||||
{
|
|
||||||
update_status([] (Status &status) {
|
update_status([] (Status &status) {
|
||||||
status.crc_error = true;
|
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) {
|
update_status([] (Status &status) {
|
||||||
status.record_not_found = true;
|
status.record_not_found = true;
|
||||||
});
|
});
|
||||||
@@ -849,12 +774,10 @@ void WD1770::posit_event(Event new_event_type)
|
|||||||
|
|
||||||
read_track_read_byte:
|
read_track_read_byte:
|
||||||
WAIT_FOR_EVENT(Event::Token | Event::IndexHole);
|
WAIT_FOR_EVENT(Event::Token | Event::IndexHole);
|
||||||
if(index_hole_count_)
|
if(index_hole_count_) {
|
||||||
{
|
|
||||||
goto wait_for_command;
|
goto wait_for_command;
|
||||||
}
|
}
|
||||||
if(status_.data_request)
|
if(status_.data_request) {
|
||||||
{
|
|
||||||
update_status([] (Status &status) {
|
update_status([] (Status &status) {
|
||||||
status.lost_data = true;
|
status.lost_data = true;
|
||||||
});
|
});
|
||||||
@@ -873,8 +796,7 @@ void WD1770::posit_event(Event new_event_type)
|
|||||||
});
|
});
|
||||||
|
|
||||||
write_track_test_write_protect:
|
write_track_test_write_protect:
|
||||||
if(get_drive_is_read_only())
|
if(get_drive_is_read_only()) {
|
||||||
{
|
|
||||||
update_status([] (Status &status) {
|
update_status([] (Status &status) {
|
||||||
status.write_protect = true;
|
status.write_protect = true;
|
||||||
});
|
});
|
||||||
@@ -885,8 +807,7 @@ void WD1770::posit_event(Event new_event_type)
|
|||||||
status.data_request = true;
|
status.data_request = true;
|
||||||
});
|
});
|
||||||
WAIT_FOR_BYTES(3);
|
WAIT_FOR_BYTES(3);
|
||||||
if(status_.data_request)
|
if(status_.data_request) {
|
||||||
{
|
|
||||||
update_status([] (Status &status) {
|
update_status([] (Status &status) {
|
||||||
status.lost_data = true;
|
status.lost_data = true;
|
||||||
});
|
});
|
||||||
@@ -898,10 +819,8 @@ void WD1770::posit_event(Event new_event_type)
|
|||||||
index_hole_count_ = 0;
|
index_hole_count_ = 0;
|
||||||
|
|
||||||
write_track_write_loop:
|
write_track_write_loop:
|
||||||
if(is_double_density_)
|
if(is_double_density_) {
|
||||||
{
|
switch(data_) {
|
||||||
switch(data_)
|
|
||||||
{
|
|
||||||
case 0xf5:
|
case 0xf5:
|
||||||
write_raw_short(Storage::Encodings::MFM::MFMSync);
|
write_raw_short(Storage::Encodings::MFM::MFMSync);
|
||||||
crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
|
crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
|
||||||
@@ -918,11 +837,8 @@ void WD1770::posit_event(Event new_event_type)
|
|||||||
write_byte(data_);
|
write_byte(data_);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
switch(data_) {
|
||||||
{
|
|
||||||
switch(data_)
|
|
||||||
{
|
|
||||||
case 0xf8: case 0xf9: case 0xfa: case 0xfb:
|
case 0xf8: case 0xf9: case 0xfa: case 0xfb:
|
||||||
case 0xfd: case 0xfe:
|
case 0xfd: case 0xfe:
|
||||||
// clock is 0xc7 = 1010 0000 0010 1010 = 0xa022
|
// clock is 0xc7 = 1010 0000 0010 1010 = 0xa022
|
||||||
@@ -960,16 +876,14 @@ void WD1770::posit_event(Event new_event_type)
|
|||||||
status.data_request = true;
|
status.data_request = true;
|
||||||
});
|
});
|
||||||
WAIT_FOR_EVENT(Event::DataWritten);
|
WAIT_FOR_EVENT(Event::DataWritten);
|
||||||
if(status_.data_request)
|
if(status_.data_request) {
|
||||||
{
|
|
||||||
update_status([] (Status &status) {
|
update_status([] (Status &status) {
|
||||||
status.lost_data = true;
|
status.lost_data = true;
|
||||||
});
|
});
|
||||||
end_writing();
|
end_writing();
|
||||||
goto wait_for_command;
|
goto wait_for_command;
|
||||||
}
|
}
|
||||||
if(index_hole_count_)
|
if(index_hole_count_) {
|
||||||
{
|
|
||||||
end_writing();
|
end_writing();
|
||||||
goto wait_for_command;
|
goto wait_for_command;
|
||||||
}
|
}
|
||||||
@@ -979,10 +893,8 @@ void WD1770::posit_event(Event new_event_type)
|
|||||||
END_SECTION()
|
END_SECTION()
|
||||||
}
|
}
|
||||||
|
|
||||||
void WD1770::update_status(std::function<void(Status &)> updater)
|
void WD1770::update_status(std::function<void(Status &)> updater) {
|
||||||
{
|
if(delegate_) {
|
||||||
if(delegate_)
|
|
||||||
{
|
|
||||||
Status old_status = status_;
|
Status old_status = status_;
|
||||||
updater(status_);
|
updater(status_);
|
||||||
bool did_change =
|
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_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;
|
head_is_loaded_ = head_loaded;
|
||||||
if(head_loaded) posit_event(Event::HeadLoad);
|
if(head_loaded) posit_event(Event::HeadLoad);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WD1770::write_bit(int bit)
|
void WD1770::write_bit(int bit) {
|
||||||
{
|
if(is_double_density_) {
|
||||||
if(is_double_density_)
|
|
||||||
{
|
|
||||||
Controller::write_bit(!bit && !last_bit_);
|
Controller::write_bit(!bit && !last_bit_);
|
||||||
Controller::write_bit(!!bit);
|
Controller::write_bit(!!bit);
|
||||||
last_bit_ = bit;
|
last_bit_ = bit;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
Controller::write_bit(true);
|
Controller::write_bit(true);
|
||||||
Controller::write_bit(!!bit);
|
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);
|
for(int c = 0; c < 8; c++) write_bit((byte << c)&0x80);
|
||||||
crc_generator_.add(byte);
|
crc_generator_.add(byte);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WD1770::write_raw_short(uint16_t value)
|
void WD1770::write_raw_short(uint16_t value) {
|
||||||
{
|
for(int c = 0; c < 16; c++) {
|
||||||
for(int c = 0; c < 16; c++)
|
|
||||||
{
|
|
||||||
Controller::write_bit(!!((value << c)&0x8000));
|
Controller::write_bit(!!((value << c)&0x8000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,12 +50,10 @@ template <class T> class MOS6522 {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*! Sets a register value. */
|
/*! Sets a register value. */
|
||||||
inline void set_register(int address, uint8_t value)
|
inline void set_register(int address, uint8_t value) {
|
||||||
{
|
|
||||||
address &= 0xf;
|
address &= 0xf;
|
||||||
// printf("6522 [%s]: %0x <- %02x\n", typeid(*this).name(), address, value);
|
// printf("6522 [%s]: %0x <- %02x\n", typeid(*this).name(), address, value);
|
||||||
switch(address)
|
switch(address) {
|
||||||
{
|
|
||||||
case 0x0:
|
case 0x0:
|
||||||
registers_.output[1] = value;
|
registers_.output[1] = value;
|
||||||
static_cast<T *>(this)->set_port_output(Port::B, value, registers_.data_direction[1]); // TODO: handshake
|
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:
|
case 0x5: case 0x7:
|
||||||
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | (uint16_t)(value << 8);
|
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | (uint16_t)(value << 8);
|
||||||
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
|
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
|
||||||
if(address == 0x05)
|
if(address == 0x05) {
|
||||||
{
|
|
||||||
registers_.next_timer[0] = registers_.timer_latch[0];
|
registers_.next_timer[0] = registers_.timer_latch[0];
|
||||||
timer_is_running_[0] = true;
|
timer_is_running_[0] = true;
|
||||||
}
|
}
|
||||||
@@ -117,19 +114,15 @@ template <class T> class MOS6522 {
|
|||||||
registers_.peripheral_control = value;
|
registers_.peripheral_control = value;
|
||||||
|
|
||||||
// TODO: simplify below; trying to avoid improper logging of unimplemented warnings in input mode
|
// TODO: simplify below; trying to avoid improper logging of unimplemented warnings in input mode
|
||||||
if(value & 0x08)
|
if(value & 0x08) {
|
||||||
{
|
switch(value & 0x0e) {
|
||||||
switch(value & 0x0e)
|
|
||||||
{
|
|
||||||
default: printf("Unimplemented control line mode %d\n", (value >> 1)&7); break;
|
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 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;
|
case 0x0e: static_cast<T *>(this)->set_control_line_output(Port::A, Line::Two, true); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(value & 0x80)
|
if(value & 0x80) {
|
||||||
{
|
switch(value & 0xe0) {
|
||||||
switch(value & 0xe0)
|
|
||||||
{
|
|
||||||
default: printf("Unimplemented control line mode %d\n", (value >> 5)&7); break;
|
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 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;
|
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. */
|
/*! Gets a register value. */
|
||||||
inline uint8_t get_register(int address)
|
inline uint8_t get_register(int address) {
|
||||||
{
|
|
||||||
address &= 0xf;
|
address &= 0xf;
|
||||||
// printf("6522 %p: %d\n", this, address);
|
// printf("6522 %p: %d\n", this, address);
|
||||||
switch(address)
|
switch(address) {
|
||||||
{
|
|
||||||
case 0x0:
|
case 0x0:
|
||||||
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
|
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
|
||||||
reevaluate_interrupts();
|
reevaluate_interrupts();
|
||||||
@@ -200,15 +191,12 @@ template <class T> class MOS6522 {
|
|||||||
return 0xff;
|
return 0xff;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void set_control_line_input(Port port, Line line, bool value)
|
inline void set_control_line_input(Port port, Line line, bool value) {
|
||||||
{
|
switch(line) {
|
||||||
switch(line)
|
|
||||||
{
|
|
||||||
case Line::One:
|
case Line::One:
|
||||||
if( value != control_inputs_[port].line_one &&
|
if( value != control_inputs_[port].line_one &&
|
||||||
value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01))
|
value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01))
|
||||||
)
|
) {
|
||||||
{
|
|
||||||
registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge;
|
registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge;
|
||||||
reevaluate_interrupts();
|
reevaluate_interrupts();
|
||||||
}
|
}
|
||||||
@@ -220,8 +208,7 @@ template <class T> class MOS6522 {
|
|||||||
if( value != control_inputs_[port].line_two && // i.e. value has changed ...
|
if( value != control_inputs_[port].line_two && // i.e. value has changed ...
|
||||||
!(registers_.peripheral_control & (port ? 0x80 : 0x08)) && // ... and line is input ...
|
!(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
|
value == !!(registers_.peripheral_control & (port ? 0x40 : 0x04)) // ... and it's either high or low, as required
|
||||||
)
|
) {
|
||||||
{
|
|
||||||
registers_.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge;
|
registers_.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge;
|
||||||
reevaluate_interrupts();
|
reevaluate_interrupts();
|
||||||
}
|
}
|
||||||
@@ -234,8 +221,7 @@ template <class T> class MOS6522 {
|
|||||||
registers_.last_timer[0] = registers_.timer[0];\
|
registers_.last_timer[0] = registers_.timer[0];\
|
||||||
registers_.last_timer[1] = registers_.timer[1];\
|
registers_.last_timer[1] = registers_.timer[1];\
|
||||||
\
|
\
|
||||||
if(registers_.timer_needs_reload)\
|
if(registers_.timer_needs_reload) {\
|
||||||
{\
|
|
||||||
registers_.timer_needs_reload = false;\
|
registers_.timer_needs_reload = false;\
|
||||||
registers_.timer[0] = registers_.timer_latch[0];\
|
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
|
// IRQ is raised on the half cycle after overflow
|
||||||
#define phase1() \
|
#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;\
|
timer_is_running_[1] = false;\
|
||||||
registers_.interrupt_flags |= InterruptFlag::Timer2;\
|
registers_.interrupt_flags |= InterruptFlag::Timer2;\
|
||||||
reevaluate_interrupts();\
|
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;\
|
registers_.interrupt_flags |= InterruptFlag::Timer1;\
|
||||||
reevaluate_interrupts();\
|
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
|
Callers should decide whether they are going to use @c run_for_half_cycles or @c run_for_cycles, and not
|
||||||
intermingle usage.
|
intermingle usage.
|
||||||
*/
|
*/
|
||||||
inline void run_for_half_cycles(unsigned int number_of_cycles)
|
inline void run_for_half_cycles(unsigned int number_of_cycles) {
|
||||||
{
|
if(is_phase2_) {
|
||||||
if(is_phase2_)
|
|
||||||
{
|
|
||||||
phase2();
|
phase2();
|
||||||
number_of_cycles--;
|
number_of_cycles--;
|
||||||
}
|
}
|
||||||
|
|
||||||
while(number_of_cycles >= 2)
|
while(number_of_cycles >= 2) {
|
||||||
{
|
|
||||||
phase1();
|
phase1();
|
||||||
phase2();
|
phase2();
|
||||||
number_of_cycles -= 2;
|
number_of_cycles -= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(number_of_cycles)
|
if(number_of_cycles) {
|
||||||
{
|
|
||||||
phase1();
|
phase1();
|
||||||
is_phase2_ = true;
|
is_phase2_ = true;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
is_phase2_ = false;
|
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
|
Callers should decide whether they are going to use @c run_for_half_cycles or @c run_for_cycles, and not
|
||||||
intermingle usage.
|
intermingle usage.
|
||||||
*/
|
*/
|
||||||
inline void run_for_cycles(unsigned int number_of_cycles)
|
inline void run_for_cycles(unsigned int number_of_cycles) {
|
||||||
{
|
while(number_of_cycles--) {
|
||||||
while(number_of_cycles--)
|
|
||||||
{
|
|
||||||
phase1();
|
phase1();
|
||||||
phase2();
|
phase2();
|
||||||
}
|
}
|
||||||
@@ -324,8 +300,7 @@ template <class T> class MOS6522 {
|
|||||||
#undef phase2
|
#undef phase2
|
||||||
|
|
||||||
/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
|
/*! @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;
|
uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
|
||||||
return !!interrupt_status;
|
return !!interrupt_status;
|
||||||
}
|
}
|
||||||
@@ -333,8 +308,7 @@ template <class T> class MOS6522 {
|
|||||||
MOS6522() :
|
MOS6522() :
|
||||||
timer_is_running_{false, false},
|
timer_is_running_{false, false},
|
||||||
last_posted_interrupt_status_(false),
|
last_posted_interrupt_status_(false),
|
||||||
is_phase2_(false)
|
is_phase2_(false) {}
|
||||||
{}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Expected to be overridden
|
// Expected to be overridden
|
||||||
@@ -344,8 +318,7 @@ template <class T> class MOS6522 {
|
|||||||
void set_interrupt_status(bool status) {}
|
void set_interrupt_status(bool status) {}
|
||||||
|
|
||||||
// Input/output multiplexer
|
// 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);
|
uint8_t input = static_cast<T *>(this)->get_port_input(port);
|
||||||
return (input & ~output_mask) | (output & output_mask);
|
return (input & ~output_mask) | (output & output_mask);
|
||||||
}
|
}
|
||||||
@@ -355,11 +328,9 @@ template <class T> class MOS6522 {
|
|||||||
|
|
||||||
// Delegate and communications
|
// Delegate and communications
|
||||||
bool last_posted_interrupt_status_;
|
bool last_posted_interrupt_status_;
|
||||||
inline void reevaluate_interrupts()
|
inline void reevaluate_interrupts() {
|
||||||
{
|
|
||||||
bool new_interrupt_status = get_interrupt_line();
|
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;
|
last_posted_interrupt_status_ = new_interrupt_status;
|
||||||
static_cast<T *>(this)->set_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;
|
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;
|
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);
|
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 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 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;
|
const uint8_t decodedAddress = address & 0x07;
|
||||||
switch(decodedAddress) {
|
switch(decodedAddress) {
|
||||||
// Port output
|
// Port output
|
||||||
@@ -48,16 +47,13 @@ template <class T> class MOS6532 {
|
|||||||
|
|
||||||
// The timer and edge detect control
|
// The timer and edge detect control
|
||||||
case 0x04: case 0x05: case 0x06: case 0x07:
|
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_.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);
|
timer_.interrupt_enabled = !!(address&0x08);
|
||||||
interrupt_status_ &= ~InterruptFlag::Timer;
|
interrupt_status_ &= ~InterruptFlag::Timer;
|
||||||
evaluate_interrupts();
|
evaluate_interrupts();
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
a7_interrupt_.enabled = !!(address&0x2);
|
a7_interrupt_.enabled = !!(address&0x2);
|
||||||
a7_interrupt_.active_on_positive = !!(address & 0x01);
|
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;
|
const uint8_t decodedAddress = address & 0x7;
|
||||||
switch(decodedAddress) {
|
switch(decodedAddress) {
|
||||||
// Port input
|
// Port input
|
||||||
case 0x00: case 0x02:
|
case 0x00: case 0x02: {
|
||||||
{
|
|
||||||
const int port = decodedAddress / 2;
|
const int port = decodedAddress / 2;
|
||||||
uint8_t input = static_cast<T *>(this)->get_port_input(port);
|
uint8_t input = static_cast<T *>(this)->get_port_input(port);
|
||||||
return (input & ~port_[port].output_mask) | (port_[port].output & port_[port].output_mask);
|
return (input & ~port_[port].output_mask) | (port_[port].output & port_[port].output_mask);
|
||||||
@@ -82,8 +76,7 @@ template <class T> class MOS6532 {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
// Timer and interrupt control
|
// Timer and interrupt control
|
||||||
case 0x04: case 0x06:
|
case 0x04: case 0x06: {
|
||||||
{
|
|
||||||
uint8_t value = (uint8_t)(timer_.value >> timer_.activeShift);
|
uint8_t value = (uint8_t)(timer_.value >> timer_.activeShift);
|
||||||
timer_.interrupt_enabled = !!(address&0x08);
|
timer_.interrupt_enabled = !!(address&0x08);
|
||||||
interrupt_status_ &= ~InterruptFlag::Timer;
|
interrupt_status_ &= ~InterruptFlag::Timer;
|
||||||
@@ -99,8 +92,7 @@ template <class T> class MOS6532 {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x05: case 0x07:
|
case 0x05: case 0x07: {
|
||||||
{
|
|
||||||
uint8_t value = interrupt_status_;
|
uint8_t value = interrupt_status_;
|
||||||
interrupt_status_ &= ~InterruptFlag::PA7;
|
interrupt_status_ &= ~InterruptFlag::PA7;
|
||||||
evaluate_interrupts();
|
evaluate_interrupts();
|
||||||
@@ -112,14 +104,13 @@ template <class T> class MOS6532 {
|
|||||||
return 0xff;
|
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
|
// permit counting _to_ zero; counting _through_ zero initiates the other behaviour
|
||||||
if(timer_.value >= number_of_cycles) {
|
if(timer_.value >= number_of_cycles) {
|
||||||
timer_.value -= number_of_cycles;
|
timer_.value -= number_of_cycles;
|
||||||
} else {
|
} else {
|
||||||
number_of_cycles -= timer_.value;
|
number_of_cycles -= timer_.value;
|
||||||
timer_.value = 0x100 - number_of_cycles;
|
timer_.value = (0x100 - number_of_cycles) & 0xff;
|
||||||
timer_.activeShift = 0;
|
timer_.activeShift = 0;
|
||||||
interrupt_status_ |= InterruptFlag::Timer;
|
interrupt_status_ |= InterruptFlag::Timer;
|
||||||
evaluate_interrupts();
|
evaluate_interrupts();
|
||||||
@@ -130,23 +121,19 @@ template <class T> class MOS6532 {
|
|||||||
interrupt_status_(0),
|
interrupt_status_(0),
|
||||||
port_{{.output_mask = 0, .output = 0}, {.output_mask = 0, .output = 0}},
|
port_{{.output_mask = 0, .output = 0}, {.output_mask = 0, .output = 0}},
|
||||||
a7_interrupt_({.last_port_value = 0, .enabled = false}),
|
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)
|
inline void set_port_did_change(int port) {
|
||||||
{
|
if(!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 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;
|
uint8_t difference = new_port_a_value ^ a7_interrupt_.last_port_value;
|
||||||
a7_interrupt_.last_port_value = new_port_a_value;
|
a7_interrupt_.last_port_value = new_port_a_value;
|
||||||
if(difference&0x80)
|
if(difference&0x80) {
|
||||||
{
|
|
||||||
if(
|
if(
|
||||||
((new_port_a_value&0x80) && a7_interrupt_.active_on_positive) ||
|
((new_port_a_value&0x80) && a7_interrupt_.active_on_positive) ||
|
||||||
(!(new_port_a_value&0x80) && !a7_interrupt_.active_on_positive)
|
(!(new_port_a_value&0x80) && !a7_interrupt_.active_on_positive)
|
||||||
)
|
) {
|
||||||
{
|
|
||||||
interrupt_status_ |= InterruptFlag::PA7;
|
interrupt_status_ |= InterruptFlag::PA7;
|
||||||
evaluate_interrupts();
|
evaluate_interrupts();
|
||||||
}
|
}
|
||||||
@@ -154,8 +141,7 @@ template <class T> class MOS6532 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool get_inerrupt_line()
|
inline bool get_inerrupt_line() {
|
||||||
{
|
|
||||||
return interrupt_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_port_output(int port, uint8_t value, uint8_t output_mask) {}
|
||||||
void set_irq_line(bool new_value) {}
|
void set_irq_line(bool new_value) {}
|
||||||
|
|
||||||
inline void evaluate_interrupts()
|
inline void evaluate_interrupts() {
|
||||||
{
|
|
||||||
interrupt_line_ =
|
interrupt_line_ =
|
||||||
((interrupt_status_&InterruptFlag::Timer) && timer_.interrupt_enabled) ||
|
((interrupt_status_&InterruptFlag::Timer) && timer_.interrupt_enabled) ||
|
||||||
((interrupt_status_&InterruptFlag::PA7) && a7_interrupt_.enabled);
|
((interrupt_status_&InterruptFlag::PA7) && a7_interrupt_.enabled);
|
||||||
|
|||||||
@@ -14,18 +14,15 @@ Speaker::Speaker() :
|
|||||||
volume_(0),
|
volume_(0),
|
||||||
control_registers_{0, 0, 0, 0},
|
control_registers_{0, 0, 0, 0},
|
||||||
shift_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([=]() {
|
enqueue([=]() {
|
||||||
volume_ = volume;
|
volume_ = volume;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Speaker::set_control(int channel, uint8_t value)
|
void Speaker::set_control(int channel, uint8_t value) {
|
||||||
{
|
|
||||||
enqueue([=]() {
|
enqueue([=]() {
|
||||||
control_registers_[channel] = value;
|
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
|
// 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.
|
// means every second cycle, etc.
|
||||||
|
|
||||||
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target)
|
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
|
||||||
{
|
for(unsigned int c = 0; c < number_of_samples; c++) {
|
||||||
for(unsigned int c = 0; c < number_of_samples; c++)
|
|
||||||
{
|
|
||||||
update(0, 2, shift);
|
update(0, 2, shift);
|
||||||
update(1, 1, shift);
|
update(1, 1, shift);
|
||||||
update(2, 0, 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)
|
void Speaker::skip_samples(unsigned int number_of_samples) {
|
||||||
{
|
for(unsigned int c = 0; c < number_of_samples; c++) {
|
||||||
for(unsigned int c = 0; c < number_of_samples; c++)
|
|
||||||
{
|
|
||||||
update(0, 2, shift);
|
update(0, 2, shift);
|
||||||
update(1, 1, shift);
|
update(1, 1, shift);
|
||||||
update(2, 0, shift);
|
update(2, 0, shift);
|
||||||
|
|||||||
@@ -49,8 +49,7 @@ template <class T> class MOS6560 {
|
|||||||
vertical_counter_(0),
|
vertical_counter_(0),
|
||||||
cycles_since_speaker_update_(0),
|
cycles_since_speaker_update_(0),
|
||||||
is_odd_frame_(false),
|
is_odd_frame_(false),
|
||||||
is_odd_line_(false)
|
is_odd_line_(false) {
|
||||||
{
|
|
||||||
crt_->set_composite_sampling_function(
|
crt_->set_composite_sampling_function(
|
||||||
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
|
"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);
|
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));
|
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.
|
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;
|
output_mode_ = output_mode;
|
||||||
uint8_t luminances[16] = { // range is 0–4
|
uint8_t luminances[16] = { // range is 0–4
|
||||||
0, 4, 1, 3, 2, 2, 1, 3,
|
0, 4, 1, 3, 2, 2, 1, 3,
|
||||||
@@ -100,8 +97,7 @@ template <class T> class MOS6560 {
|
|||||||
uint8_t *chrominances;
|
uint8_t *chrominances;
|
||||||
Outputs::CRT::DisplayType display_type;
|
Outputs::CRT::DisplayType display_type;
|
||||||
|
|
||||||
switch(output_mode)
|
switch(output_mode) {
|
||||||
{
|
|
||||||
case OutputMode::PAL:
|
case OutputMode::PAL:
|
||||||
chrominances = pal_chrominances;
|
chrominances = pal_chrominances;
|
||||||
display_type = Outputs::CRT::PAL50;
|
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_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));
|
// crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.1f, 0.8f, 0.8f));
|
||||||
|
|
||||||
// switch(output_mode)
|
// switch(output_mode) {
|
||||||
// {
|
|
||||||
// case OutputMode::PAL:
|
// case OutputMode::PAL:
|
||||||
// crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 15*4, 55*4, 4.0f / 3.0f));
|
// crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 15*4, 55*4, 4.0f / 3.0f));
|
||||||
// break;
|
// break;
|
||||||
@@ -134,8 +129,7 @@ template <class T> class MOS6560 {
|
|||||||
// break;
|
// break;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
for(int c = 0; c < 16; c++)
|
for(int c = 0; c < 16; c++) {
|
||||||
{
|
|
||||||
colours_[c] = (uint8_t)((luminances[c] << 4) | chrominances[c]);
|
colours_[c] = (uint8_t)((luminances[c] << 4) | chrominances[c]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -143,23 +137,19 @@ template <class T> class MOS6560 {
|
|||||||
/*!
|
/*!
|
||||||
Runs for cycles. Derr.
|
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
|
// keep track of the amount of time since the speaker was updated; lazy updates are applied
|
||||||
cycles_since_speaker_update_ += number_of_cycles;
|
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
|
// 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_;
|
int previous_vertical_counter = vertical_counter_;
|
||||||
|
|
||||||
// keep track of internal time relative to this scanline
|
// keep track of internal time relative to this scanline
|
||||||
horizontal_counter_++;
|
horizontal_counter_++;
|
||||||
full_frame_counter_++;
|
full_frame_counter_++;
|
||||||
if(horizontal_counter_ == timing_.cycles_per_line)
|
if(horizontal_counter_ == timing_.cycles_per_line) {
|
||||||
{
|
if(horizontal_drawing_latch_) {
|
||||||
if(horizontal_drawing_latch_)
|
|
||||||
{
|
|
||||||
current_character_row_++;
|
current_character_row_++;
|
||||||
if(
|
if(
|
||||||
(current_character_row_ == 16) ||
|
(current_character_row_ == 16) ||
|
||||||
@@ -179,8 +169,7 @@ template <class T> class MOS6560 {
|
|||||||
horizontal_drawing_latch_ = false;
|
horizontal_drawing_latch_ = false;
|
||||||
|
|
||||||
vertical_counter_ ++;
|
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;
|
vertical_counter_ = 0;
|
||||||
full_frame_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);
|
horizontal_drawing_latch_ |= vertical_drawing_latch_ && (horizontal_counter_ == registers_.first_column_location);
|
||||||
|
|
||||||
if(pixel_line_cycle_ >= 0) pixel_line_cycle_++;
|
if(pixel_line_cycle_ >= 0) pixel_line_cycle_++;
|
||||||
switch(pixel_line_cycle_)
|
switch(pixel_line_cycle_) {
|
||||||
{
|
|
||||||
case -1:
|
case -1:
|
||||||
if(horizontal_drawing_latch_)
|
if(horizontal_drawing_latch_) {
|
||||||
{
|
|
||||||
pixel_line_cycle_ = 0;
|
pixel_line_cycle_ = 0;
|
||||||
video_matrix_address_counter_ = base_video_matrix_address_counter_;
|
video_matrix_address_counter_ = base_video_matrix_address_counter_;
|
||||||
}
|
}
|
||||||
@@ -213,14 +200,10 @@ template <class T> class MOS6560 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint16_t fetch_address = 0x1c;
|
uint16_t fetch_address = 0x1c;
|
||||||
if(column_counter_ >= 0 && column_counter_ < columns_this_line_*2)
|
if(column_counter_ >= 0 && column_counter_ < columns_this_line_*2) {
|
||||||
{
|
if(column_counter_&1) {
|
||||||
if(column_counter_&1)
|
|
||||||
{
|
|
||||||
fetch_address = registers_.character_cell_start_address + (character_code_*(registers_.tall_characters ? 16 : 8)) + current_character_row_;
|
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_);
|
fetch_address = (uint16_t)(registers_.video_matrix_start_address + video_matrix_address_counter_);
|
||||||
video_matrix_address_counter_++;
|
video_matrix_address_counter_++;
|
||||||
if(
|
if(
|
||||||
@@ -244,8 +227,7 @@ template <class T> class MOS6560 {
|
|||||||
// determine output state; colour burst and sync timing are currently a guess
|
// determine output state; colour burst and sync timing are currently a guess
|
||||||
if(horizontal_counter_ > timing_.cycles_per_line-4) this_state_ = State::ColourBurst;
|
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 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;
|
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;
|
this_state_ = State::Sync;
|
||||||
|
|
||||||
// update the CRT
|
// update the CRT
|
||||||
if(this_state_ != output_state_)
|
if(this_state_ != output_state_) {
|
||||||
{
|
switch(output_state_) {
|
||||||
switch(output_state_)
|
|
||||||
{
|
|
||||||
case State::Sync: crt_->output_sync(cycles_in_state_ * 4); break;
|
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::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;
|
case State::Border: output_border(cycles_in_state_ * 4); break;
|
||||||
@@ -275,32 +255,24 @@ template <class T> class MOS6560 {
|
|||||||
cycles_in_state_ = 0;
|
cycles_in_state_ = 0;
|
||||||
|
|
||||||
pixel_pointer = nullptr;
|
pixel_pointer = nullptr;
|
||||||
if(output_state_ == State::Pixels)
|
if(output_state_ == State::Pixels) {
|
||||||
{
|
|
||||||
pixel_pointer = crt_->allocate_write_area(260);
|
pixel_pointer = crt_->allocate_write_area(260);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cycles_in_state_++;
|
cycles_in_state_++;
|
||||||
|
|
||||||
if(this_state_ == State::Pixels)
|
if(this_state_ == State::Pixels) {
|
||||||
{
|
if(column_counter_&1) {
|
||||||
if(column_counter_&1)
|
|
||||||
{
|
|
||||||
character_value_ = pixel_data;
|
character_value_ = pixel_data;
|
||||||
|
|
||||||
if(pixel_pointer)
|
if(pixel_pointer) {
|
||||||
{
|
|
||||||
uint8_t cell_colour = colours_[character_colour_ & 0x7];
|
uint8_t cell_colour = colours_[character_colour_ & 0x7];
|
||||||
if(!(character_colour_&0x8))
|
if(!(character_colour_&0x8)) {
|
||||||
{
|
|
||||||
uint8_t colours[2];
|
uint8_t colours[2];
|
||||||
if(registers_.invertedCells)
|
if(registers_.invertedCells) {
|
||||||
{
|
|
||||||
colours[0] = cell_colour;
|
colours[0] = cell_colour;
|
||||||
colours[1] = registers_.backgroundColour;
|
colours[1] = registers_.backgroundColour;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
colours[0] = registers_.backgroundColour;
|
colours[0] = registers_.backgroundColour;
|
||||||
colours[1] = cell_colour;
|
colours[1] = cell_colour;
|
||||||
}
|
}
|
||||||
@@ -312,9 +284,7 @@ template <class T> class MOS6560 {
|
|||||||
pixel_pointer[5] = colours[(character_value_ >> 2)&1];
|
pixel_pointer[5] = colours[(character_value_ >> 2)&1];
|
||||||
pixel_pointer[6] = colours[(character_value_ >> 1)&1];
|
pixel_pointer[6] = colours[(character_value_ >> 1)&1];
|
||||||
pixel_pointer[7] = colours[(character_value_ >> 0)&1];
|
pixel_pointer[7] = colours[(character_value_ >> 0)&1];
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
uint8_t colours[4] = {registers_.backgroundColour, registers_.borderColour, cell_colour, registers_.auxiliary_colour};
|
uint8_t colours[4] = {registers_.backgroundColour, registers_.borderColour, cell_colour, registers_.auxiliary_colour};
|
||||||
pixel_pointer[0] =
|
pixel_pointer[0] =
|
||||||
pixel_pointer[1] = colours[(character_value_ >> 6)&3];
|
pixel_pointer[1] = colours[(character_value_ >> 6)&3];
|
||||||
@@ -327,9 +297,7 @@ template <class T> class MOS6560 {
|
|||||||
}
|
}
|
||||||
pixel_pointer += 8;
|
pixel_pointer += 8;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
character_code_ = pixel_data;
|
character_code_ = pixel_data;
|
||||||
character_colour_ = colour_data;
|
character_colour_ = colour_data;
|
||||||
}
|
}
|
||||||
@@ -347,12 +315,10 @@ template <class T> class MOS6560 {
|
|||||||
/*!
|
/*!
|
||||||
Writes to a 6560 register.
|
Writes to a 6560 register.
|
||||||
*/
|
*/
|
||||||
void set_register(int address, uint8_t value)
|
void set_register(int address, uint8_t value) {
|
||||||
{
|
|
||||||
address &= 0xf;
|
address &= 0xf;
|
||||||
registers_.direct_values[address] = value;
|
registers_.direct_values[address] = value;
|
||||||
switch(address)
|
switch(address) {
|
||||||
{
|
|
||||||
case 0x0:
|
case 0x0:
|
||||||
registers_.interlaced = !!(value&0x80) && timing_.supports_interlacing;
|
registers_.interlaced = !!(value&0x80) && timing_.supports_interlacing;
|
||||||
registers_.first_column_location = value & 0x7f;
|
registers_.first_column_location = value & 0x7f;
|
||||||
@@ -391,11 +357,9 @@ template <class T> class MOS6560 {
|
|||||||
speaker_->set_volume(value & 0xf);
|
speaker_->set_volume(value & 0xf);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xf:
|
case 0xf: {
|
||||||
{
|
|
||||||
uint8_t new_border_colour = colours_[value & 0x07];
|
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);
|
output_border(cycles_in_state_ * 4);
|
||||||
cycles_in_state_ = 0;
|
cycles_in_state_ = 0;
|
||||||
}
|
}
|
||||||
@@ -415,12 +379,10 @@ template <class T> class MOS6560 {
|
|||||||
/*
|
/*
|
||||||
Reads from a 6560 register.
|
Reads from a 6560 register.
|
||||||
*/
|
*/
|
||||||
uint8_t get_register(int address)
|
uint8_t get_register(int address) {
|
||||||
{
|
|
||||||
address &= 0xf;
|
address &= 0xf;
|
||||||
int current_line = (full_frame_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line;
|
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];
|
default: return registers_.direct_values[address];
|
||||||
case 0x03: return (uint8_t)(current_line << 7) | (registers_.direct_values[3] & 0x7f);
|
case 0x03: return (uint8_t)(current_line << 7) | (registers_.direct_values[3] & 0x7f);
|
||||||
case 0x04: return (current_line >> 1) & 0xff;
|
case 0x04: return (current_line >> 1) & 0xff;
|
||||||
@@ -432,8 +394,7 @@ template <class T> class MOS6560 {
|
|||||||
|
|
||||||
std::shared_ptr<Speaker> speaker_;
|
std::shared_ptr<Speaker> speaker_;
|
||||||
unsigned int cycles_since_speaker_update_;
|
unsigned int cycles_since_speaker_update_;
|
||||||
void update_audio()
|
void update_audio() {
|
||||||
{
|
|
||||||
speaker_->run_for_cycles(cycles_since_speaker_update_ >> 2);
|
speaker_->run_for_cycles(cycles_since_speaker_update_ >> 2);
|
||||||
cycles_since_speaker_update_ &= 3;
|
cycles_since_speaker_update_ &= 3;
|
||||||
}
|
}
|
||||||
@@ -478,8 +439,7 @@ template <class T> class MOS6560 {
|
|||||||
uint8_t colours_[16];
|
uint8_t colours_[16];
|
||||||
|
|
||||||
uint8_t *pixel_pointer;
|
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);
|
uint8_t *colour_pointer = crt_->allocate_write_area(1);
|
||||||
if(colour_pointer) *colour_pointer = registers_.borderColour;
|
if(colour_pointer) *colour_pointer = registers_.borderColour;
|
||||||
crt_->output_level(number_of_cycles);
|
crt_->output_level(number_of_cycles);
|
||||||
|
|||||||
@@ -16,17 +16,13 @@ AY38910::AY38910() :
|
|||||||
noise_shift_register_(0xffff), noise_period_(0), noise_counter_(0), noise_output_(0),
|
noise_shift_register_(0xffff), noise_period_(0), noise_counter_(0), noise_output_(0),
|
||||||
envelope_divider_(0), envelope_period_(0), envelope_position_(0),
|
envelope_divider_(0), envelope_period_(0), envelope_position_(0),
|
||||||
master_divider_(0),
|
master_divider_(0),
|
||||||
output_registers_{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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;
|
output_registers_[8] = output_registers_[9] = output_registers_[10] = 0;
|
||||||
|
|
||||||
// set up envelope lookup tables
|
// set up envelope lookup tables
|
||||||
for(int c = 0; c < 16; c++)
|
for(int c = 0; c < 16; c++) {
|
||||||
{
|
for(int p = 0; p < 32; p++) {
|
||||||
for(int p = 0; p < 32; p++)
|
switch(c) {
|
||||||
{
|
|
||||||
switch(c)
|
|
||||||
{
|
|
||||||
case 0: case 1: case 2: case 3: case 9:
|
case 0: case 1: case 2: case 3: case 9:
|
||||||
envelope_shapes_[c][p] = (p < 16) ? (p^0xf) : 0;
|
envelope_shapes_[c][p] = (p < 16) ? (p^0xf) : 0;
|
||||||
envelope_overflow_masks_[c] = 0x1f;
|
envelope_overflow_masks_[c] = 0x1f;
|
||||||
@@ -69,34 +65,28 @@ AY38910::AY38910() :
|
|||||||
// set up volume lookup table
|
// set up volume lookup table
|
||||||
float max_volume = 8192;
|
float max_volume = 8192;
|
||||||
float root_two = sqrtf(2.0f);
|
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_[v] = (int)(max_volume / powf(root_two, (float)(v ^ 0xf)));
|
||||||
}
|
}
|
||||||
volumes_[0] = 0;
|
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);
|
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;
|
int c = 0;
|
||||||
while((master_divider_&15) && c < number_of_samples)
|
while((master_divider_&7) && c < number_of_samples) {
|
||||||
{
|
|
||||||
target[c] = output_volume_;
|
target[c] = output_volume_;
|
||||||
master_divider_++;
|
master_divider_++;
|
||||||
c++;
|
c++;
|
||||||
}
|
}
|
||||||
|
|
||||||
while(c < number_of_samples)
|
while(c < number_of_samples) {
|
||||||
{
|
|
||||||
#define step_channel(c) \
|
#define step_channel(c) \
|
||||||
if(tone_counters_[c]) tone_counters_[c]--;\
|
if(tone_counters_[c]) tone_counters_[c]--;\
|
||||||
else\
|
else {\
|
||||||
{\
|
|
||||||
tone_outputs_[c] ^= 1;\
|
tone_outputs_[c] ^= 1;\
|
||||||
tone_counters_[c] = tone_periods_[c];\
|
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
|
// ... the noise generator. This recomputes the new bit repeatedly but harmlessly, only shifting
|
||||||
// it into the official 17 upon divider underflow.
|
// it into the official 17 upon divider underflow.
|
||||||
if(noise_counter_) noise_counter_--;
|
if(noise_counter_) noise_counter_--;
|
||||||
else
|
else {
|
||||||
{
|
|
||||||
noise_counter_ = noise_period_;
|
noise_counter_ = noise_period_;
|
||||||
noise_output_ ^= noise_shift_register_&1;
|
noise_output_ ^= noise_shift_register_&1;
|
||||||
noise_shift_register_ |= ((noise_shift_register_ ^ (noise_shift_register_ >> 3))&1) << 17;
|
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
|
// ... 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.
|
// implementing non-repeating patterns by locking them to table position 0x1f.
|
||||||
if(envelope_divider_) envelope_divider_--;
|
if(envelope_divider_) envelope_divider_--;
|
||||||
else
|
else {
|
||||||
{
|
|
||||||
envelope_divider_ = envelope_period_;
|
envelope_divider_ = envelope_period_;
|
||||||
envelope_position_ ++;
|
envelope_position_ ++;
|
||||||
if(envelope_position_ == 32) envelope_position_ = envelope_overflow_masks_[output_registers_[13]];
|
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();
|
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_;
|
target[c] = output_volume_;
|
||||||
c++;
|
c++;
|
||||||
master_divider_++;
|
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_];
|
int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_];
|
||||||
|
|
||||||
// The output level for a channel is:
|
// 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;
|
selected_register_ = r & 0xf;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AY38910::set_register_value(uint8_t value)
|
void AY38910::set_register_value(uint8_t value) {
|
||||||
{
|
|
||||||
registers_[selected_register_] = value;
|
registers_[selected_register_] = value;
|
||||||
if(selected_register_ < 14)
|
if(selected_register_ < 14) {
|
||||||
{
|
|
||||||
int selected_register = selected_register_;
|
int selected_register = selected_register_;
|
||||||
enqueue([=] () {
|
enqueue([=] () {
|
||||||
uint8_t masked_value = value;
|
uint8_t masked_value = value;
|
||||||
switch(selected_register)
|
switch(selected_register) {
|
||||||
{
|
|
||||||
case 0: case 2: case 4:
|
case 0: case 2: case 4:
|
||||||
case 1: case 3: case 5:
|
case 1: case 3: case 5: {
|
||||||
{
|
|
||||||
int channel = selected_register >> 1;
|
int channel = selected_register >> 1;
|
||||||
|
|
||||||
if(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
|
// 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
|
// 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.
|
// 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_];
|
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];
|
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;
|
data_input_ = r;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t AY38910::get_data_output()
|
uint8_t AY38910::get_data_output() {
|
||||||
{
|
|
||||||
return data_output_;
|
return data_output_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AY38910::set_control_lines(ControlLines control_lines)
|
void AY38910::set_control_lines(ControlLines control_lines) {
|
||||||
{
|
|
||||||
ControlState new_state;
|
ControlState new_state;
|
||||||
switch((int)control_lines)
|
switch((int)control_lines) {
|
||||||
{
|
|
||||||
default: new_state = Inactive; break;
|
default: new_state = Inactive; break;
|
||||||
|
|
||||||
case (int)(BCDIR | BC2 | BC1):
|
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;
|
case (int)(BCDIR | BC2): new_state = Write; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(new_state != control_state_)
|
if(new_state != control_state_) {
|
||||||
{
|
|
||||||
control_state_ = new_state;
|
control_state_ = new_state;
|
||||||
switch(new_state)
|
switch(new_state) {
|
||||||
{
|
|
||||||
default: break;
|
default: break;
|
||||||
case LatchAddress: select_register(data_input_); break;
|
case LatchAddress: select_register(data_input_); break;
|
||||||
case Write: set_register_value(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);
|
serial_dispatch_queue_ = dispatch_queue_create("com.thomasharte.clocksignal.asyntaskqueue", DISPATCH_QUEUE_SERIAL);
|
||||||
#else
|
#else
|
||||||
thread_.reset(new std::thread([this]() {
|
thread_.reset(new std::thread([this]() {
|
||||||
while(!should_destruct_)
|
while(!should_destruct_) {
|
||||||
{
|
|
||||||
std::function<void(void)> next_function;
|
std::function<void(void)> next_function;
|
||||||
|
|
||||||
// Take lock, check for a new task
|
// Take lock, check for a new task
|
||||||
std::unique_lock<std::mutex> lock(queue_mutex_);
|
std::unique_lock<std::mutex> lock(queue_mutex_);
|
||||||
if(!pending_tasks_.empty())
|
if(!pending_tasks_.empty()) {
|
||||||
{
|
|
||||||
next_function = pending_tasks_.front();
|
next_function = pending_tasks_.front();
|
||||||
pending_tasks_.pop_front();
|
pending_tasks_.pop_front();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(next_function)
|
if(next_function) {
|
||||||
{
|
|
||||||
// If there is a task, release lock and perform it
|
// If there is a task, release lock and perform it
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
next_function();
|
next_function();
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
// If there isn't a task, atomically block on the processing condition and release the lock
|
// 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)
|
// until there's something pending (and then release it again via scope)
|
||||||
processing_condition_.wait(lock);
|
processing_condition_.wait(lock);
|
||||||
@@ -48,8 +43,7 @@ AsyncTaskQueue::AsyncTaskQueue()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncTaskQueue::~AsyncTaskQueue()
|
AsyncTaskQueue::~AsyncTaskQueue() {
|
||||||
{
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
dispatch_release(serial_dispatch_queue_);
|
dispatch_release(serial_dispatch_queue_);
|
||||||
#else
|
#else
|
||||||
@@ -60,8 +54,7 @@ AsyncTaskQueue::~AsyncTaskQueue()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncTaskQueue::enqueue(std::function<void(void)> function)
|
void AsyncTaskQueue::enqueue(std::function<void(void)> function) {
|
||||||
{
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
dispatch_async(serial_dispatch_queue_, ^{function();});
|
dispatch_async(serial_dispatch_queue_, ^{function();});
|
||||||
#else
|
#else
|
||||||
@@ -71,8 +64,7 @@ void AsyncTaskQueue::enqueue(std::function<void(void)> function)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncTaskQueue::flush()
|
void AsyncTaskQueue::flush() {
|
||||||
{
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
dispatch_sync(serial_dispatch_queue_, ^{});
|
dispatch_sync(serial_dispatch_queue_, ^{});
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -10,772 +10,148 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <stdio.h>
|
#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;
|
using namespace Atari2600;
|
||||||
namespace {
|
namespace {
|
||||||
static const unsigned int horizontalTimerPeriod = 228;
|
|
||||||
static const double NTSC_clock_rate = 1194720;
|
static const double NTSC_clock_rate = 1194720;
|
||||||
static const double PAL_clock_rate = 1182298;
|
static const double PAL_clock_rate = 1182298;
|
||||||
}
|
}
|
||||||
|
|
||||||
Machine::Machine() :
|
Machine::Machine() :
|
||||||
horizontal_timer_(0),
|
frame_record_pointer_(0),
|
||||||
last_output_state_duration_(0),
|
is_ntsc_(true) {
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set_clock_rate(NTSC_clock_rate);
|
set_clock_rate(NTSC_clock_rate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::setup_output(float aspect_ratio)
|
void Machine::setup_output(float aspect_ratio) {
|
||||||
{
|
bus_->tia_.reset(new TIA);
|
||||||
speaker_.reset(new Speaker);
|
bus_->speaker_.reset(new Speaker);
|
||||||
crt_.reset(new Outputs::CRT::CRT(228, 1, 263, Outputs::CRT::ColourSpace::YIQ, 228, 1, false, 1));
|
bus_->speaker_->set_input_rate((float)(get_clock_rate() / 38.0));
|
||||||
crt_->set_output_device(Outputs::CRT::Television);
|
bus_->tia_->get_crt()->set_delegate(this);
|
||||||
|
|
||||||
// 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::switch_region()
|
void Machine::close_output() {
|
||||||
{
|
bus_.reset();
|
||||||
// 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()
|
Machine::~Machine() {
|
||||||
{
|
|
||||||
crt_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Machine::~Machine()
|
|
||||||
{
|
|
||||||
delete[] rom_;
|
|
||||||
close_output();
|
close_output();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::update_timers(int mask)
|
void Machine::set_digital_input(Atari2600DigitalInput input, bool state) {
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
switch (input) {
|
switch (input) {
|
||||||
case Atari2600DigitalInputJoy1Up: mos6532_.update_port_input(0, 0x10, state); break;
|
case Atari2600DigitalInputJoy1Up: bus_->mos6532_.update_port_input(0, 0x10, state); break;
|
||||||
case Atari2600DigitalInputJoy1Down: mos6532_.update_port_input(0, 0x20, state); break;
|
case Atari2600DigitalInputJoy1Down: bus_->mos6532_.update_port_input(0, 0x20, state); break;
|
||||||
case Atari2600DigitalInputJoy1Left: mos6532_.update_port_input(0, 0x40, state); break;
|
case Atari2600DigitalInputJoy1Left: bus_->mos6532_.update_port_input(0, 0x40, state); break;
|
||||||
case Atari2600DigitalInputJoy1Right: mos6532_.update_port_input(0, 0x80, state); break;
|
case Atari2600DigitalInputJoy1Right: bus_->mos6532_.update_port_input(0, 0x80, state); break;
|
||||||
|
|
||||||
case Atari2600DigitalInputJoy2Up: mos6532_.update_port_input(0, 0x01, state); break;
|
case Atari2600DigitalInputJoy2Up: bus_->mos6532_.update_port_input(0, 0x01, state); break;
|
||||||
case Atari2600DigitalInputJoy2Down: mos6532_.update_port_input(0, 0x02, state); break;
|
case Atari2600DigitalInputJoy2Down: bus_->mos6532_.update_port_input(0, 0x02, state); break;
|
||||||
case Atari2600DigitalInputJoy2Left: mos6532_.update_port_input(0, 0x04, state); break;
|
case Atari2600DigitalInputJoy2Left: bus_->mos6532_.update_port_input(0, 0x04, state); break;
|
||||||
case Atari2600DigitalInputJoy2Right: mos6532_.update_port_input(0, 0x08, state); break;
|
case Atari2600DigitalInputJoy2Right: bus_->mos6532_.update_port_input(0, 0x08, state); break;
|
||||||
|
|
||||||
// TODO: latching
|
// TODO: latching
|
||||||
case Atari2600DigitalInputJoy1Fire: if(state) tia_input_value_[0] &= ~0x80; else tia_input_value_[0] |= 0x80; break;
|
case Atari2600DigitalInputJoy1Fire: if(state) bus_->tia_input_value_[0] &= ~0x80; else bus_->tia_input_value_[0] |= 0x80; break;
|
||||||
case Atari2600DigitalInputJoy2Fire: if(state) tia_input_value_[1] &= ~0x80; else tia_input_value_[1] |= 0x80; break;
|
case Atari2600DigitalInputJoy2Fire: if(state) bus_->tia_input_value_[1] &= ~0x80; else bus_->tia_input_value_[1] |= 0x80; break;
|
||||||
|
|
||||||
default: 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) {
|
switch(input) {
|
||||||
case Atari2600SwitchReset: mos6532_.update_port_input(1, 0x01, state); break;
|
case Atari2600SwitchReset: bus_->mos6532_.update_port_input(1, 0x01, state); break;
|
||||||
case Atari2600SwitchSelect: mos6532_.update_port_input(1, 0x02, state); break;
|
case Atari2600SwitchSelect: bus_->mos6532_.update_port_input(1, 0x02, state); break;
|
||||||
case Atari2600SwitchColour: mos6532_.update_port_input(1, 0x08, state); break;
|
case Atari2600SwitchColour: bus_->mos6532_.update_port_input(1, 0x08, state); break;
|
||||||
case Atari2600SwitchLeftPlayerDifficulty: mos6532_.update_port_input(1, 0x40, state); break;
|
case Atari2600SwitchLeftPlayerDifficulty: bus_->mos6532_.update_port_input(1, 0x40, state); break;
|
||||||
case Atari2600SwitchRightPlayerDifficulty: mos6532_.update_port_input(1, 0x80, state); break;
|
case Atari2600SwitchRightPlayerDifficulty: bus_->mos6532_.update_port_input(1, 0x80, state); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::configure_as_target(const StaticAnalyser::Target &target)
|
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
|
||||||
{
|
const std::vector<uint8_t> &rom = target.cartridges.front()->get_segments().front().data;
|
||||||
if(!target.cartridges.front()->get_segments().size()) return;
|
switch(target.atari.paging_model) {
|
||||||
Storage::Cartridge::Cartridge::Segment segment = target.cartridges.front()->get_segments().front();
|
case StaticAnalyser::Atari2600PagingModel::ActivisionStack: bus_.reset(new CartridgeActivisionStack(rom)); break;
|
||||||
size_t length = segment.data.size();
|
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;
|
case StaticAnalyser::Atari2600PagingModel::Atari8k:
|
||||||
while(rom_size_ < length && rom_size_ < 32768) rom_size_ <<= 1;
|
if(target.atari.uses_superchip) {
|
||||||
|
bus_.reset(new CartridgeAtari8kSuperChip(rom));
|
||||||
delete[] rom_;
|
} else {
|
||||||
rom_ = new uint8_t[rom_size_];
|
bus_.reset(new CartridgeAtari8k(rom));
|
||||||
|
}
|
||||||
size_t offset = 0;
|
break;
|
||||||
const size_t copy_step = std::min(rom_size_, length);
|
case StaticAnalyser::Atari2600PagingModel::Atari16k:
|
||||||
while(offset < rom_size_)
|
if(target.atari.uses_superchip) {
|
||||||
{
|
bus_.reset(new CartridgeAtari16kSuperChip(rom));
|
||||||
size_t copy_length = std::min(copy_step, rom_size_ - offset);
|
} else {
|
||||||
memcpy(&rom_[offset], &segment.data[0], copy_length);
|
bus_.reset(new CartridgeAtari16k(rom));
|
||||||
offset += copy_length;
|
}
|
||||||
|
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;
|
#pragma mark - CRT delegate
|
||||||
rom_pages_[0] = rom_;
|
|
||||||
rom_pages_[1] = &rom_[1024 & romMask];
|
void Machine::crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs) {
|
||||||
rom_pages_[2] = &rom_[2048 & romMask];
|
const size_t number_of_frame_records = sizeof(frame_records_) / sizeof(frame_records_[0]);
|
||||||
rom_pages_[3] = &rom_[3072 & romMask];
|
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_ ++;
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Audio
|
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;
|
||||||
|
|
||||||
void Machine::update_audio()
|
double clock_rate;
|
||||||
{
|
if(is_ntsc_) {
|
||||||
unsigned int audio_cycles = cycles_since_speaker_update_ / 114;
|
clock_rate = NTSC_clock_rate;
|
||||||
|
bus_->tia_->set_output_mode(TIA::OutputMode::NTSC);
|
||||||
speaker_->run_for_cycles(audio_cycles);
|
} else {
|
||||||
cycles_since_speaker_update_ %= 114;
|
clock_rate = PAL_clock_rate;
|
||||||
|
bus_->tia_->set_output_mode(TIA::OutputMode::PAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::synchronise()
|
bus_->speaker_->set_input_rate((float)(clock_rate / 38.0));
|
||||||
{
|
bus_->speaker_->set_high_frequency_cut_off((float)(clock_rate / (38.0 * 2.0)));
|
||||||
update_audio();
|
set_clock_rate(clock_rate);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,10 @@
|
|||||||
|
|
||||||
#include "../../Processors/6502/CPU6502.hpp"
|
#include "../../Processors/6502/CPU6502.hpp"
|
||||||
#include "../CRTMachine.hpp"
|
#include "../CRTMachine.hpp"
|
||||||
|
#include "Bus.hpp"
|
||||||
#include "PIA.hpp"
|
#include "PIA.hpp"
|
||||||
#include "Speaker.hpp"
|
#include "Speaker.hpp"
|
||||||
|
#include "TIA.hpp"
|
||||||
|
|
||||||
#include "../ConfigurationTarget.hpp"
|
#include "../ConfigurationTarget.hpp"
|
||||||
#include "Atari2600Inputs.h"
|
#include "Atari2600Inputs.h"
|
||||||
@@ -25,9 +27,9 @@ const unsigned int number_of_upcoming_events = 6;
|
|||||||
const unsigned int number_of_recorded_counters = 7;
|
const unsigned int number_of_recorded_counters = 7;
|
||||||
|
|
||||||
class Machine:
|
class Machine:
|
||||||
public CPU6502::Processor<Machine>,
|
|
||||||
public CRTMachine::Machine,
|
public CRTMachine::Machine,
|
||||||
public ConfigurationTarget::Machine {
|
public ConfigurationTarget::Machine,
|
||||||
|
public Outputs::CRT::Delegate {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Machine();
|
Machine();
|
||||||
@@ -38,137 +40,31 @@ class Machine:
|
|||||||
|
|
||||||
void set_digital_input(Atari2600DigitalInput input, bool state);
|
void set_digital_input(Atari2600DigitalInput input, bool state);
|
||||||
void set_switch_is_enabled(Atari2600Switch input, bool state);
|
void set_switch_is_enabled(Atari2600Switch input, bool state);
|
||||||
|
void set_reset_line(bool state) { bus_->set_reset_line(state); }
|
||||||
// to satisfy CPU6502::Processor
|
|
||||||
unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value);
|
|
||||||
void synchronise();
|
|
||||||
|
|
||||||
// to satisfy CRTMachine::Machine
|
// to satisfy CRTMachine::Machine
|
||||||
virtual void setup_output(float aspect_ratio);
|
virtual void setup_output(float aspect_ratio);
|
||||||
virtual void close_output();
|
virtual void close_output();
|
||||||
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; }
|
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return bus_->tia_->get_crt(); }
|
||||||
virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return speaker_; }
|
virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return bus_->speaker_; }
|
||||||
virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles); }
|
virtual void run_for_cycles(int number_of_cycles) { bus_->run_for_cycles(number_of_cycles); }
|
||||||
// TODO: different rate for PAL
|
|
||||||
|
// 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:
|
private:
|
||||||
uint8_t *rom_, *rom_pages_[4];
|
// the bus
|
||||||
size_t rom_size_;
|
std::unique_ptr<Bus> bus_;
|
||||||
|
|
||||||
// the RIOT
|
// output frame rate tracker
|
||||||
PIA mos6532_;
|
struct FrameRecord {
|
||||||
|
unsigned int number_of_frames;
|
||||||
|
unsigned int number_of_unexpected_vertical_syncs;
|
||||||
|
|
||||||
// playfield registers
|
FrameRecord() : number_of_frames(0), number_of_unexpected_vertical_syncs(0) {}
|
||||||
uint8_t playfield_control_;
|
} frame_records_[4];
|
||||||
uint8_t playfield_colour_;
|
unsigned int frame_record_pointer_;
|
||||||
uint8_t background_colour_;
|
bool is_ntsc_;
|
||||||
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();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
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> {
|
class PIA: public MOS::MOS6532<PIA> {
|
||||||
public:
|
public:
|
||||||
inline uint8_t get_port_input(int port)
|
inline uint8_t get_port_input(int port) {
|
||||||
{
|
|
||||||
return port_values_[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;
|
if(set) port_values_[port] &= ~mask; else port_values_[port] |= mask;
|
||||||
set_port_did_change(port);
|
set_port_did_change(port);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,23 +16,20 @@ Atari2600::Speaker::Speaker() :
|
|||||||
poly9_counter_{0x1ff, 0x1ff}
|
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([=]() {
|
enqueue([=]() {
|
||||||
volume_[channel] = volume & 0xf;
|
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([=]() {
|
enqueue([=]() {
|
||||||
divider_[channel] = divider & 0x1f;
|
divider_[channel] = divider & 0x1f;
|
||||||
divider_counter_[channel] = 0;
|
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([=]() {
|
enqueue([=]() {
|
||||||
control_[channel] = control & 0xf;
|
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_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)
|
#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)
|
void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
|
||||||
{
|
for(unsigned int c = 0; c < number_of_samples; c++) {
|
||||||
for(unsigned int c = 0; c < number_of_samples; c++)
|
|
||||||
{
|
|
||||||
target[c] = 0;
|
target[c] = 0;
|
||||||
for(int channel = 0; channel < 2; channel++)
|
for(int channel = 0; channel < 2; channel++) {
|
||||||
{
|
|
||||||
divider_counter_[channel] ++;
|
divider_counter_[channel] ++;
|
||||||
int level = 0;
|
int level = 0;
|
||||||
switch(control_[channel])
|
switch(control_[channel]) {
|
||||||
{
|
|
||||||
case 0x0: case 0xb: // constant 1
|
case 0x0: case 0xb: // constant 1
|
||||||
level = 1;
|
level = 1;
|
||||||
break;
|
break;
|
||||||
@@ -75,8 +68,7 @@ void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *ta
|
|||||||
|
|
||||||
case 0x1: // 4-bit poly
|
case 0x1: // 4-bit poly
|
||||||
level = poly4_counter_[channel]&1;
|
level = poly4_counter_[channel]&1;
|
||||||
if(divider_counter_[channel] == divider_[channel]+1)
|
if(divider_counter_[channel] == divider_[channel]+1) {
|
||||||
{
|
|
||||||
divider_counter_[channel] = 0;
|
divider_counter_[channel] = 0;
|
||||||
advance_poly4(channel);
|
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
|
case 0x2: // 4-bit poly div31
|
||||||
level = poly4_counter_[channel]&1;
|
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);
|
advance_poly4(channel);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x3: // 5/4-bit poly
|
case 0x3: // 5/4-bit poly
|
||||||
level = output_state_[channel];
|
level = output_state_[channel];
|
||||||
if(divider_counter_[channel] == divider_[channel]+1)
|
if(divider_counter_[channel] == divider_[channel]+1) {
|
||||||
{
|
if(poly5_counter_[channel]&1) {
|
||||||
if(poly5_counter_[channel]&1)
|
|
||||||
{
|
|
||||||
output_state_[channel] = poly4_counter_[channel]&1;
|
output_state_[channel] = poly4_counter_[channel]&1;
|
||||||
advance_poly4(channel);
|
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
|
case 0x7: case 0x9: // 5-bit poly
|
||||||
level = poly5_counter_[channel]&1;
|
level = poly5_counter_[channel]&1;
|
||||||
if(divider_counter_[channel] == divider_[channel]+1)
|
if(divider_counter_[channel] == divider_[channel]+1) {
|
||||||
{
|
|
||||||
divider_counter_[channel] = 0;
|
divider_counter_[channel] = 0;
|
||||||
advance_poly5(channel);
|
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
|
case 0xf: // 5-bit poly div6
|
||||||
level = poly5_counter_[channel]&1;
|
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;
|
divider_counter_[channel] = 0;
|
||||||
advance_poly5(channel);
|
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
|
case 0x8: // 9-bit poly
|
||||||
level = poly9_counter_[channel]&1;
|
level = poly9_counter_[channel]&1;
|
||||||
if(divider_counter_[channel] == divider_[channel]+1)
|
if(divider_counter_[channel] == divider_[channel]+1) {
|
||||||
{
|
|
||||||
divider_counter_[channel] = 0;
|
divider_counter_[channel] = 0;
|
||||||
advance_poly9(channel);
|
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 */
|
||||||
@@ -16,8 +16,7 @@ Machine::Machine() :
|
|||||||
shift_register_(0),
|
shift_register_(0),
|
||||||
Storage::Disk::Controller(1000000, 4, 300),
|
Storage::Disk::Controller(1000000, 4, 300),
|
||||||
serial_port_(new SerialPort),
|
serial_port_(new SerialPort),
|
||||||
serial_port_VIA_(new SerialPortVIA)
|
serial_port_VIA_(new SerialPortVIA) {
|
||||||
{
|
|
||||||
// attach the serial port to its VIA and vice versa
|
// attach the serial port to its VIA and vice versa
|
||||||
serial_port_->set_serial_port_via(serial_port_VIA_);
|
serial_port_->set_serial_port_via(serial_port_VIA_);
|
||||||
serial_port_VIA_->set_serial_port(serial_port_);
|
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));
|
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);
|
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):
|
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
|
0x1c00–0x1c0f the drive VIA
|
||||||
0xc000–0xffff ROM
|
0xc000–0xffff ROM
|
||||||
*/
|
*/
|
||||||
if(address < 0x800)
|
if(address < 0x800) {
|
||||||
{
|
|
||||||
if(isReadOperation(operation))
|
if(isReadOperation(operation))
|
||||||
*value = ram_[address];
|
*value = ram_[address];
|
||||||
else
|
else
|
||||||
ram_[address] = *value;
|
ram_[address] = *value;
|
||||||
}
|
} else if(address >= 0xc000) {
|
||||||
else if(address >= 0xc000)
|
|
||||||
{
|
|
||||||
if(isReadOperation(operation))
|
if(isReadOperation(operation))
|
||||||
*value = rom_[address & 0x3fff];
|
*value = rom_[address & 0x3fff];
|
||||||
}
|
} else if(address >= 0x1800 && address <= 0x180f) {
|
||||||
else if(address >= 0x1800 && address <= 0x180f)
|
|
||||||
{
|
|
||||||
if(isReadOperation(operation))
|
if(isReadOperation(operation))
|
||||||
*value = serial_port_VIA_->get_register(address);
|
*value = serial_port_VIA_->get_register(address);
|
||||||
else
|
else
|
||||||
serial_port_VIA_->set_register(address, *value);
|
serial_port_VIA_->set_register(address, *value);
|
||||||
}
|
} else if(address >= 0x1c00 && address <= 0x1c0f) {
|
||||||
else if(address >= 0x1c00 && address <= 0x1c0f)
|
|
||||||
{
|
|
||||||
if(isReadOperation(operation))
|
if(isReadOperation(operation))
|
||||||
*value = drive_VIA_.get_register(address);
|
*value = drive_VIA_.get_register(address);
|
||||||
else
|
else
|
||||||
@@ -79,20 +69,17 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::set_rom(const uint8_t *rom)
|
void Machine::set_rom(const uint8_t *rom) {
|
||||||
{
|
|
||||||
memcpy(rom_, rom, sizeof(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);
|
std::shared_ptr<Storage::Disk::Drive> drive(new Storage::Disk::Drive);
|
||||||
drive->set_disk(disk);
|
drive->set_disk(disk);
|
||||||
set_drive(drive);
|
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);
|
CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles);
|
||||||
set_motor_on(drive_VIA_.get_motor_enabled());
|
set_motor_on(drive_VIA_.get_motor_enabled());
|
||||||
if(drive_VIA_.get_motor_enabled()) // TODO: motor speed up/down
|
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
|
#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
|
// both VIAs are connected to the IRQ line
|
||||||
set_irq_line(serial_port_VIA_->get_interrupt_line() || drive_VIA_.get_interrupt_line());
|
set_irq_line(serial_port_VIA_->get_interrupt_line() || drive_VIA_.get_interrupt_line());
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Disk drive
|
#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;
|
shift_register_ = (shift_register_ << 1) | value;
|
||||||
if((shift_register_ & 0x3ff) == 0x3ff)
|
if((shift_register_ & 0x3ff) == 0x3ff) {
|
||||||
{
|
|
||||||
drive_VIA_.set_sync_detected(true);
|
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
|
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);
|
drive_VIA_.set_sync_detected(false);
|
||||||
}
|
}
|
||||||
bit_window_offset_++;
|
bit_window_offset_++;
|
||||||
if(bit_window_offset_ == 8)
|
if(bit_window_offset_ == 8) {
|
||||||
{
|
|
||||||
drive_VIA_.set_data_input((uint8_t)shift_register_);
|
drive_VIA_.set_data_input((uint8_t)shift_register_);
|
||||||
bit_window_offset_ = 0;
|
bit_window_offset_ = 0;
|
||||||
if(drive_VIA_.get_should_set_overflow())
|
if(drive_VIA_.get_should_set_overflow()) {
|
||||||
{
|
|
||||||
set_overflow_line(true);
|
set_overflow_line(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else set_overflow_line(false);
|
||||||
set_overflow_line(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// the 1540 does not recognise index holes
|
// the 1540 does not recognise index holes
|
||||||
@@ -140,32 +119,26 @@ void Machine::process_index_hole() {}
|
|||||||
|
|
||||||
#pragma mak - Drive VIA delegate
|
#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);
|
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));
|
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone((unsigned int)density));
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - SerialPortVIA
|
#pragma mark - SerialPortVIA
|
||||||
|
|
||||||
SerialPortVIA::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_;
|
if(port) return port_b_;
|
||||||
return 0xff;
|
return 0xff;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask)
|
void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) {
|
||||||
{
|
if(port) {
|
||||||
if(port)
|
|
||||||
{
|
|
||||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
||||||
if(serialPort) {
|
if(serialPort) {
|
||||||
attention_acknowledge_level_ = !(value&0x10);
|
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)
|
void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) {
|
||||||
{
|
switch(line) {
|
||||||
switch(line)
|
|
||||||
{
|
|
||||||
default: break;
|
default: break;
|
||||||
case ::Commodore::Serial::Line::Data: port_b_ = (port_b_ & ~0x01) | (value ? 0x00 : 0x01); 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;
|
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;
|
serial_port_ = serialPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SerialPortVIA::update_data_line()
|
void SerialPortVIA::update_data_line() {
|
||||||
{
|
|
||||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
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"
|
// "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,
|
serialPort->set_output(::Commodore::Serial::Line::Data,
|
||||||
(::Commodore::Serial::LineLevel)(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_)));
|
(::Commodore::Serial::LineLevel)(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_)));
|
||||||
@@ -211,8 +179,7 @@ void SerialPortVIA::update_data_line()
|
|||||||
|
|
||||||
#pragma mark - DriveVIA
|
#pragma mark - DriveVIA
|
||||||
|
|
||||||
void DriveVIA::set_delegate(Delegate *delegate)
|
void DriveVIA::set_delegate(Delegate *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) {
|
void DriveVIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask) {
|
||||||
if(port)
|
if(port) {
|
||||||
{
|
|
||||||
// record drive motor state
|
// record drive motor state
|
||||||
drive_motor_ = !!(value&4);
|
drive_motor_ = !!(value&4);
|
||||||
|
|
||||||
// check for a head step
|
// check for a head step
|
||||||
int step_difference = ((value&3) - (previous_port_b_output_&3))&3;
|
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);
|
if(delegate_) delegate_->drive_via_did_step_head(this, (step_difference == 1) ? 1 : -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for a change in density
|
// check for a change in density
|
||||||
int density_difference = (previous_port_b_output_^value) & (3 << 5);
|
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);
|
delegate_->drive_via_did_set_data_density(this, (value >> 5)&3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,10 +10,8 @@
|
|||||||
|
|
||||||
using namespace Commodore::Serial;
|
using namespace Commodore::Serial;
|
||||||
|
|
||||||
const char *::Commodore::Serial::StringForLine(Line line)
|
const char *::Commodore::Serial::StringForLine(Line line) {
|
||||||
{
|
switch(line) {
|
||||||
switch(line)
|
|
||||||
{
|
|
||||||
case ServiceRequest: return "Service request";
|
case ServiceRequest: return "Service request";
|
||||||
case Attention: return "Attention";
|
case Attention: return "Attention";
|
||||||
case Clock: return "Clock";
|
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);
|
port->set_serial_bus(bus);
|
||||||
bus->add_port(port);
|
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);
|
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...
|
// the addition of a new device may change the line output...
|
||||||
set_line_output_did_change((Line)line);
|
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
|
// i.e. I believe these lines to be open collector
|
||||||
LineLevel new_line_level = High;
|
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();
|
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));
|
new_line_level = (LineLevel)((bool)new_line_level & (bool)locked_port->get_output(line));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// post an update only if one occurred
|
// 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;
|
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();
|
std::shared_ptr<Port> locked_port = port.lock();
|
||||||
if(locked_port)
|
if(locked_port) {
|
||||||
{
|
|
||||||
locked_port->set_input(line, new_line_level);
|
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
|
#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;
|
input_levels_[line] = value;
|
||||||
|
|
||||||
printf("[Bus] %s is %s\n", StringForLine(line), value ? "high" : "low");
|
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;
|
incoming_count_ = (!input_levels_[Line::Clock] && !input_levels_[Line::Data]) ? 8 : 0;
|
||||||
}
|
} else {
|
||||||
else
|
if(line == Line::Clock && value) {
|
||||||
{
|
|
||||||
if(line == Line::Clock && value)
|
|
||||||
{
|
|
||||||
incoming_byte_ = (incoming_byte_ >> 1) | (input_levels_[Line::Data] ? 0x80 : 0x00);
|
incoming_byte_ = (incoming_byte_ >> 1) | (input_levels_[Line::Data] ? 0x80 : 0x00);
|
||||||
}
|
}
|
||||||
incoming_count_--;
|
incoming_count_--;
|
||||||
|
|||||||
@@ -78,8 +78,7 @@ namespace Serial {
|
|||||||
Sets the current level of an output line on this serial port.
|
Sets the current level of an output line on this serial port.
|
||||||
*/
|
*/
|
||||||
void set_output(Line line, LineLevel level) {
|
void set_output(Line line, LineLevel level) {
|
||||||
if(line_levels_[line] != level)
|
if(line_levels_[line] != level) {
|
||||||
{
|
|
||||||
line_levels_[line] = level;
|
line_levels_[line] = level;
|
||||||
std::shared_ptr<Bus> bus = serial_bus_.lock();
|
std::shared_ptr<Bus> bus = serial_bus_.lock();
|
||||||
if(bus) bus->set_line_output_did_change(line);
|
if(bus) bus->set_line_output_did_change(line);
|
||||||
|
|||||||
@@ -8,8 +8,7 @@
|
|||||||
|
|
||||||
#include "Vic20.hpp"
|
#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 KEYS(...) {__VA_ARGS__, TerminateSequence}
|
||||||
#define SHIFT(...) {KeyLShift, __VA_ARGS__, TerminateSequence}
|
#define SHIFT(...) {KeyLShift, __VA_ARGS__, TerminateSequence}
|
||||||
#define X {NotMapped}
|
#define X {NotMapped}
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ using namespace Commodore::Vic20;
|
|||||||
Machine::Machine() :
|
Machine::Machine() :
|
||||||
rom_(nullptr),
|
rom_(nullptr),
|
||||||
is_running_at_zero_cost_(false),
|
is_running_at_zero_cost_(false),
|
||||||
tape_(1022727)
|
tape_(1022727) {
|
||||||
{
|
|
||||||
// create 6522s, serial port and bus
|
// create 6522s, serial port and bus
|
||||||
user_port_via_.reset(new UserPortVIA);
|
user_port_via_.reset(new UserPortVIA);
|
||||||
keyboard_via_.reset(new KeyboardVIA);
|
keyboard_via_.reset(new KeyboardVIA);
|
||||||
@@ -48,13 +47,11 @@ Machine::Machine() :
|
|||||||
// serial_bus_->add_port(_debugPort);
|
// 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_read_memory_map_, 0, sizeof(processor_read_memory_map_));
|
||||||
memset(processor_write_memory_map_, 0, sizeof(processor_write_memory_map_));
|
memset(processor_write_memory_map_, 0, sizeof(processor_write_memory_map_));
|
||||||
|
|
||||||
switch(size)
|
switch(size) {
|
||||||
{
|
|
||||||
default: break;
|
default: break;
|
||||||
case ThreeKB:
|
case ThreeKB:
|
||||||
write_to_map(processor_read_memory_map_, expansion_ram_, 0x0000, 0x1000);
|
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_));
|
write_to_map(processor_write_memory_map_, colour_memory_, 0x9400, sizeof(colour_memory_));
|
||||||
|
|
||||||
// install the inserted ROM if there is one
|
// install the inserted ROM if there is one
|
||||||
if(rom_)
|
if(rom_) {
|
||||||
{
|
|
||||||
write_to_map(processor_read_memory_map_, rom_, rom_address_, rom_length_);
|
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;
|
address >>= 10;
|
||||||
length >>= 10;
|
length >>= 10;
|
||||||
while(length--)
|
while(length--) {
|
||||||
{
|
|
||||||
map[address] = area;
|
map[address] = area;
|
||||||
area += 0x400;
|
area += 0x400;
|
||||||
address++;
|
address++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Machine::~Machine()
|
Machine::~Machine() {
|
||||||
{
|
|
||||||
delete[] rom_;
|
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;
|
// static int logCount = 0;
|
||||||
// if(operation == CPU6502::BusOperation::ReadOpcode && address == 0xf957) logCount = 500;
|
// if(operation == CPU6502::BusOperation::ReadOpcode && address == 0xf957) logCount = 500;
|
||||||
// if(operation == CPU6502::BusOperation::ReadOpcode && logCount) {
|
// if(operation == CPU6502::BusOperation::ReadOpcode && logCount) {
|
||||||
@@ -111,8 +103,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
|||||||
// printf("%04x\n", address);
|
// 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);
|
// 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);
|
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
|
// 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;
|
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&0xff00) == 0x9000) result &= mos6560_->get_register(address);
|
||||||
if((address&0xfc10) == 0x9010) result &= user_port_via_->get_register(address);
|
if((address&0xfc10) == 0x9010) result &= user_port_via_->get_register(address);
|
||||||
if((address&0xfc20) == 0x9020) result &= keyboard_via_->get_register(address);
|
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
|
// 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
|
// occurred then skip both 6522s and the tape ahead to the next interrupt without any further
|
||||||
// CPU or 6560 costs.
|
// CPU or 6560 costs.
|
||||||
if(use_fast_tape_hack_ && tape_.has_tape() && address == 0xf92f && operation == CPU6502::BusOperation::ReadOpcode)
|
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()) {
|
||||||
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);
|
user_port_via_->run_for_cycles(1);
|
||||||
keyboard_via_->run_for_cycles(1);
|
keyboard_via_->run_for_cycles(1);
|
||||||
tape_.run_for_cycles(1);
|
tape_.run_for_cycles(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
uint8_t *ram = processor_write_memory_map_[address >> 10];
|
uint8_t *ram = processor_write_memory_map_[address >> 10];
|
||||||
if(ram) ram[address & 0x3ff] = *value;
|
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&0xff00) == 0x9000) mos6560_->set_register(address, *value);
|
||||||
if((address&0xfc10) == 0x9010) user_port_via_->set_register(address, *value);
|
if((address&0xfc10) == 0x9010) user_port_via_->set_register(address, *value);
|
||||||
if((address&0xfc20) == 0x9020) keyboard_via_->set_register(address, *value);
|
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);
|
user_port_via_->run_for_cycles(1);
|
||||||
keyboard_via_->run_for_cycles(1);
|
keyboard_via_->run_for_cycles(1);
|
||||||
if(typer_ && operation == CPU6502::BusOperation::ReadOpcode && address == 0xEB1E)
|
if(typer_ && operation == CPU6502::BusOperation::ReadOpcode && address == 0xEB1E) {
|
||||||
{
|
if(!typer_->type_next_character()) {
|
||||||
if(!typer_->type_next_character())
|
|
||||||
{
|
|
||||||
clear_all_keys();
|
clear_all_keys();
|
||||||
typer_.reset();
|
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
|
// 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
|
// for an interesting interrupt. Up there the fast tape hack goes even further in also cutting
|
||||||
// the CPU out of the action.
|
// the CPU out of the action.
|
||||||
if(use_fast_tape_hack_ && tape_.has_tape())
|
if(use_fast_tape_hack_ && tape_.has_tape()) {
|
||||||
{
|
if(address == 0xf98e && operation == CPU6502::BusOperation::ReadOpcode) {
|
||||||
if(address == 0xf98e && operation == CPU6502::BusOperation::ReadOpcode)
|
|
||||||
{
|
|
||||||
is_running_at_zero_cost_ = true;
|
is_running_at_zero_cost_ = true;
|
||||||
set_clock_is_unlimited(true);
|
set_clock_is_unlimited(true);
|
||||||
}
|
}
|
||||||
if(
|
if(
|
||||||
(address < 0xe000 && operation == CPU6502::BusOperation::ReadOpcode) ||
|
(address < 0xe000 && operation == CPU6502::BusOperation::ReadOpcode) ||
|
||||||
tape_.get_tape()->is_at_end()
|
tape_.get_tape()->is_at_end()
|
||||||
)
|
) {
|
||||||
{
|
|
||||||
is_running_at_zero_cost_ = false;
|
is_running_at_zero_cost_ = false;
|
||||||
set_clock_is_unlimited(false);
|
set_clock_is_unlimited(false);
|
||||||
}
|
}
|
||||||
@@ -203,31 +182,26 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
|||||||
|
|
||||||
#pragma mark - 6522 delegate
|
#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_nmi_line(user_port_via_->get_interrupt_line());
|
||||||
set_irq_line(keyboard_via_->get_interrupt_line());
|
set_irq_line(keyboard_via_->get_interrupt_line());
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Setup
|
#pragma mark - Setup
|
||||||
|
|
||||||
void Machine::set_region(Commodore::Vic20::Region region)
|
void Machine::set_region(Commodore::Vic20::Region region) {
|
||||||
{
|
|
||||||
region_ = region;
|
region_ = region;
|
||||||
switch(region)
|
switch(region) {
|
||||||
{
|
|
||||||
case PAL:
|
case PAL:
|
||||||
set_clock_rate(1108404);
|
set_clock_rate(1108404);
|
||||||
if(mos6560_)
|
if(mos6560_) {
|
||||||
{
|
|
||||||
mos6560_->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::PAL);
|
mos6560_->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::PAL);
|
||||||
mos6560_->set_clock_rate(1108404);
|
mos6560_->set_clock_rate(1108404);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case NTSC:
|
case NTSC:
|
||||||
set_clock_rate(1022727);
|
set_clock_rate(1022727);
|
||||||
if(mos6560_)
|
if(mos6560_) {
|
||||||
{
|
|
||||||
mos6560_->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::NTSC);
|
mos6560_->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::NTSC);
|
||||||
mos6560_->set_clock_rate(1022727);
|
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_.reset(new Vic6560());
|
||||||
mos6560_->get_speaker()->set_high_frequency_cut_off(1600); // There is a 1.6Khz low-pass filter in the Vic-20.
|
mos6560_->get_speaker()->set_high_frequency_cut_off(1600); // There is a 1.6Khz low-pass filter in the Vic-20.
|
||||||
set_region(region_);
|
set_region(region_);
|
||||||
@@ -248,17 +221,14 @@ void Machine::setup_output(float aspect_ratio)
|
|||||||
mos6560_->colour_memory = colour_memory_;
|
mos6560_->colour_memory = colour_memory_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::close_output()
|
void Machine::close_output() {
|
||||||
{
|
|
||||||
mos6560_ = nullptr;
|
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;
|
uint8_t *target = nullptr;
|
||||||
size_t max_length = 0x2000;
|
size_t max_length = 0x2000;
|
||||||
switch(slot)
|
switch(slot) {
|
||||||
{
|
|
||||||
case Kernel: target = kernel_rom_; break;
|
case Kernel: target = kernel_rom_; break;
|
||||||
case Characters: target = character_rom_; max_length = 0x1000; break;
|
case Characters: target = character_rom_; max_length = 0x1000; break;
|
||||||
case BASIC: target = basic_rom_; 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(target)
|
if(target) {
|
||||||
{
|
|
||||||
size_t length_to_copy = std::min(max_length, length);
|
size_t length_to_copy = std::min(max_length, length);
|
||||||
memcpy(target, data, length_to_copy);
|
memcpy(target, data, length_to_copy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//void Machine::set_prg(const char *file_name, size_t length, const uint8_t *data)
|
//void Machine::set_prg(const char *file_name, size_t length, const uint8_t *data) {
|
||||||
//{
|
// if(length > 2) {
|
||||||
// if(length > 2)
|
|
||||||
// {
|
|
||||||
// _rom_address = (uint16_t)(data[0] | (data[1] << 8));
|
// _rom_address = (uint16_t)(data[0] | (data[1] << 8));
|
||||||
// _rom_length = (uint16_t)(length - 2);
|
// _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
|
// // 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];
|
// _rom = new uint8_t[0x2000];
|
||||||
// memcpy(_rom, &data[2], length - 2);
|
// memcpy(_rom, &data[2], length - 2);
|
||||||
// write_to_map(processor_read_memory_map_, _rom, _rom_address, 0x2000);
|
// 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)));
|
// 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
|
#pragma mar - Tape
|
||||||
|
|
||||||
void Machine::configure_as_target(const StaticAnalyser::Target &target)
|
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
|
||||||
{
|
if(target.tapes.size()) {
|
||||||
if(target.tapes.size())
|
|
||||||
{
|
|
||||||
tape_.set_tape(target.tapes.front());
|
tape_.set_tape(target.tapes.front());
|
||||||
}
|
}
|
||||||
|
|
||||||
if(target.disks.size())
|
if(target.disks.size()) {
|
||||||
{
|
|
||||||
// construct the 1540
|
// construct the 1540
|
||||||
c1540_.reset(new ::Commodore::C1540::Machine);
|
c1540_.reset(new ::Commodore::C1540::Machine);
|
||||||
|
|
||||||
@@ -321,8 +282,7 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target)
|
|||||||
install_disk_rom();
|
install_disk_rom();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(target.cartridges.size())
|
if(target.cartridges.size()) {
|
||||||
{
|
|
||||||
rom_address_ = 0xa000;
|
rom_address_ = 0xa000;
|
||||||
std::vector<uint8_t> rom_image = target.cartridges.front()->get_segments().front().data;
|
std::vector<uint8_t> rom_image = target.cartridges.front()->get_segments().front().data;
|
||||||
rom_length_ = (uint16_t)(rom_image.size());
|
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);
|
write_to_map(processor_read_memory_map_, rom_, rom_address_, 0x2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(should_automatically_load_media_)
|
if(should_automatically_load_media_) {
|
||||||
{
|
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());
|
set_typer_for_string(target.loadingCommand.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(target.vic20.memory_model)
|
switch(target.vic20.memory_model) {
|
||||||
{
|
|
||||||
case StaticAnalyser::Vic20MemoryModel::Unexpanded:
|
case StaticAnalyser::Vic20MemoryModel::Unexpanded:
|
||||||
set_memory_size(Default);
|
set_memory_size(Default);
|
||||||
break;
|
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());
|
keyboard_via_->set_control_line_input(KeyboardVIA::Port::A, KeyboardVIA::Line::One, tape->get_input());
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Disc
|
#pragma mark - Disc
|
||||||
|
|
||||||
void Machine::install_disk_rom()
|
void Machine::install_disk_rom() {
|
||||||
{
|
if(drive_rom_ && c1540_) {
|
||||||
if(drive_rom_ && c1540_)
|
|
||||||
{
|
|
||||||
c1540_->set_rom(drive_rom_.get());
|
c1540_->set_rom(drive_rom_.get());
|
||||||
c1540_->run_for_cycles(2000000);
|
c1540_->run_for_cycles(2000000);
|
||||||
drive_rom_.reset();
|
drive_rom_.reset();
|
||||||
@@ -373,45 +327,36 @@ void Machine::install_disk_rom()
|
|||||||
|
|
||||||
#pragma mark - UserPortVIA
|
#pragma mark - UserPortVIA
|
||||||
|
|
||||||
uint8_t UserPortVIA::get_port_input(Port port)
|
uint8_t UserPortVIA::get_port_input(Port port) {
|
||||||
{
|
if(!port) {
|
||||||
if(!port)
|
|
||||||
{
|
|
||||||
return port_a_; // TODO: bit 6 should be high if there is no tape, low otherwise
|
return port_a_; // TODO: bit 6 should be high if there is no tape, low otherwise
|
||||||
}
|
}
|
||||||
return 0xff;
|
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) {
|
// if(port == Port::A && line == Line::Two) {
|
||||||
// printf("Tape motor %s\n", value ? "on" : "off");
|
// printf("Tape motor %s\n", value ? "on" : "off");
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
void UserPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value)
|
void UserPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) {
|
||||||
{
|
switch(line) {
|
||||||
switch(line)
|
|
||||||
{
|
|
||||||
default: break;
|
default: break;
|
||||||
case ::Commodore::Serial::Line::Data: port_a_ = (port_a_ & ~0x02) | (value ? 0x02 : 0x00); 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;
|
case ::Commodore::Serial::Line::Clock: port_a_ = (port_a_ & ~0x01) | (value ? 0x01 : 0x00); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UserPortVIA::set_joystick_state(JoystickInput input, bool value)
|
void UserPortVIA::set_joystick_state(JoystickInput input, bool value) {
|
||||||
{
|
if(input != JoystickInput::Right) {
|
||||||
if(input != JoystickInput::Right)
|
|
||||||
{
|
|
||||||
port_a_ = (port_a_ & ~input) | (value ? 0 : input);
|
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
|
// 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();
|
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
||||||
if(serialPort)
|
if(serialPort)
|
||||||
serialPort->set_output(::Commodore::Serial::Line::Attention, (::Commodore::Serial::LineLevel)!(value&0x80));
|
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) {}
|
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;
|
serial_port_ = serialPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - KeyboardVIA
|
#pragma mark - KeyboardVIA
|
||||||
|
|
||||||
KeyboardVIA::KeyboardVIA() : port_b_(0xff)
|
KeyboardVIA::KeyboardVIA() : port_b_(0xff) {
|
||||||
{
|
|
||||||
clear_all_keys();
|
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)
|
if(isPressed)
|
||||||
columns_[key & 7] &= ~(key >> 3);
|
columns_[key & 7] &= ~(key >> 3);
|
||||||
else
|
else
|
||||||
columns_[key & 7] |= (key >> 3);
|
columns_[key & 7] |= (key >> 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeyboardVIA::clear_all_keys()
|
void KeyboardVIA::clear_all_keys() {
|
||||||
{
|
|
||||||
memset(columns_, 0xff, sizeof(columns_));
|
memset(columns_, 0xff, sizeof(columns_));
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t KeyboardVIA::get_port_input(Port port)
|
uint8_t KeyboardVIA::get_port_input(Port port) {
|
||||||
{
|
if(!port) {
|
||||||
if(!port)
|
|
||||||
{
|
|
||||||
uint8_t result = 0xff;
|
uint8_t result = 0xff;
|
||||||
for(int c = 0; c < 8; c++)
|
for(int c = 0; c < 8; c++) {
|
||||||
{
|
|
||||||
if(!(activation_mask_&(1 << c)))
|
if(!(activation_mask_&(1 << c)))
|
||||||
result &= columns_[c];
|
result &= columns_[c];
|
||||||
}
|
}
|
||||||
@@ -461,19 +399,15 @@ uint8_t KeyboardVIA::get_port_input(Port port)
|
|||||||
return port_b_;
|
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)
|
if(port)
|
||||||
activation_mask_ = (value & mask) | (~mask);
|
activation_mask_ = (value & mask) | (~mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeyboardVIA::set_control_line_output(Port port, Line line, bool value)
|
void KeyboardVIA::set_control_line_output(Port port, Line line, bool value) {
|
||||||
{
|
if(line == Line::Two) {
|
||||||
if(line == Line::Two)
|
|
||||||
{
|
|
||||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
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
|
// CB2 is inverted to become serial data; CA2 is inverted to become serial clock
|
||||||
if(port == Port::A)
|
if(port == Port::A)
|
||||||
serialPort->set_output(::Commodore::Serial::Line::Clock, (::Commodore::Serial::LineLevel)!value);
|
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)
|
void KeyboardVIA::set_joystick_state(JoystickInput input, bool value) {
|
||||||
{
|
if(input == JoystickInput::Right) {
|
||||||
if(input == JoystickInput::Right)
|
|
||||||
{
|
|
||||||
port_b_ = (port_b_ & ~input) | (value ? 0 : input);
|
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;
|
serial_port_ = serialPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - 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();
|
std::shared_ptr<UserPortVIA> userPortVIA = user_port_via_.lock();
|
||||||
if(userPortVIA) userPortVIA->set_serial_line_state(line, (bool)level);
|
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;
|
user_port_via_ = userPortVIA;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,8 +129,7 @@ class SerialPort : public ::Commodore::Serial::Port {
|
|||||||
|
|
||||||
class Vic6560: public MOS::MOS6560<Vic6560> {
|
class Vic6560: public MOS::MOS6560<Vic6560> {
|
||||||
public:
|
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
|
*pixel_data = video_memory_map[address >> 10] ? video_memory_map[address >> 10][address & 0x3ff] : 0xff; // TODO
|
||||||
*colour_data = colour_memory[address & 0x03ff];
|
*colour_data = colour_memory[address & 0x03ff];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ Machine::Machine() :
|
|||||||
cycles_since_display_update_(0),
|
cycles_since_display_update_(0),
|
||||||
cycles_since_audio_update_(0),
|
cycles_since_audio_update_(0),
|
||||||
use_fast_tape_hack_(false),
|
use_fast_tape_hack_(false),
|
||||||
cycles_until_display_interrupt_(0)
|
cycles_until_display_interrupt_(0) {
|
||||||
{
|
|
||||||
memset(key_states_, 0, sizeof(key_states_));
|
memset(key_states_, 0, sizeof(key_states_));
|
||||||
for(int c = 0; c < 16; c++)
|
for(int c = 0; c < 16; c++)
|
||||||
memset(roms_[c], 0xff, 16384);
|
memset(roms_[c], 0xff, 16384);
|
||||||
@@ -30,8 +29,7 @@ Machine::Machine() :
|
|||||||
|
|
||||||
#pragma mark - Output
|
#pragma mark - Output
|
||||||
|
|
||||||
void Machine::setup_output(float aspect_ratio)
|
void Machine::setup_output(float aspect_ratio) {
|
||||||
{
|
|
||||||
video_output_.reset(new VideoOutput(ram_));
|
video_output_.reset(new VideoOutput(ram_));
|
||||||
|
|
||||||
// The maximum output frequency is 62500Hz and all other permitted output frequencies are integral divisions of that;
|
// 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);
|
speaker_->set_input_rate(2000000 / Speaker::clock_rate_divider);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::close_output()
|
void Machine::close_output() {
|
||||||
{
|
|
||||||
video_output_.reset();
|
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();
|
return video_output_->get_crt();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Outputs::Speaker> Machine::get_speaker()
|
std::shared_ptr<Outputs::Speaker> Machine::get_speaker() {
|
||||||
{
|
|
||||||
return speaker_;
|
return speaker_;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - The keyboard
|
#pragma mark - The keyboard
|
||||||
|
|
||||||
void Machine::clear_all_keys()
|
void Machine::clear_all_keys() {
|
||||||
{
|
|
||||||
memset(key_states_, 0, sizeof(key_states_));
|
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)
|
void Machine::set_key_state(uint16_t key, bool isPressed) {
|
||||||
{
|
if(key == KeyBreak) {
|
||||||
if(key == KeyBreak)
|
|
||||||
{
|
|
||||||
set_reset_line(isPressed);
|
set_reset_line(isPressed);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
if(isPressed)
|
if(isPressed)
|
||||||
key_states_[key >> 4] |= key&0xf;
|
key_states_[key >> 4] |= key&0xf;
|
||||||
else
|
else
|
||||||
@@ -80,23 +71,18 @@ void Machine::set_key_state(uint16_t key, bool isPressed)
|
|||||||
|
|
||||||
#pragma mark - Machine configuration
|
#pragma mark - Machine configuration
|
||||||
|
|
||||||
void Machine::configure_as_target(const StaticAnalyser::Target &target)
|
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
|
||||||
{
|
if(target.tapes.size()) {
|
||||||
if(target.tapes.size())
|
|
||||||
{
|
|
||||||
tape_.set_tape(target.tapes.front());
|
tape_.set_tape(target.tapes.front());
|
||||||
}
|
}
|
||||||
|
|
||||||
if(target.disks.size())
|
if(target.disks.size()) {
|
||||||
{
|
|
||||||
plus3_.reset(new Plus3);
|
plus3_.reset(new Plus3);
|
||||||
|
|
||||||
if(target.acorn.has_dfs)
|
if(target.acorn.has_dfs) {
|
||||||
{
|
|
||||||
set_rom(ROMSlot0, dfs_, true);
|
set_rom(ROMSlot0, dfs_, true);
|
||||||
}
|
}
|
||||||
if(target.acorn.has_adfs)
|
if(target.acorn.has_adfs) {
|
||||||
{
|
|
||||||
set_rom(ROMSlot4, adfs_, true);
|
set_rom(ROMSlot4, adfs_, true);
|
||||||
set_rom(ROMSlot5, std::vector<uint8_t>(adfs_.begin() + 16384, adfs_.end()), 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;
|
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);
|
set_rom(slot, cartridge->get_segments().front().data, false);
|
||||||
slot = (ROMSlot)(((int)slot + 1)&15);
|
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());
|
set_typer_for_string(target.loadingCommand.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
if(target.acorn.should_hold_shift)
|
if(target.acorn.should_shift_restart) {
|
||||||
{
|
shift_restart_counter_ = 1000000;
|
||||||
set_key_state(KeyShift, true);
|
|
||||||
is_holding_shift_ = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
uint8_t *target = nullptr;
|
||||||
switch(slot)
|
switch(slot) {
|
||||||
{
|
|
||||||
case ROMSlotDFS: dfs_ = data; return;
|
case ROMSlotDFS: dfs_ = data; return;
|
||||||
case ROMSlotADFS: adfs_ = 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
|
#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;
|
unsigned int cycles = 1;
|
||||||
|
|
||||||
if(address < 0x8000)
|
if(address < 0x8000) {
|
||||||
{
|
if(isReadOperation(operation)) {
|
||||||
if(isReadOperation(operation))
|
|
||||||
{
|
|
||||||
*value = ram_[address];
|
*value = ram_[address];
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
if(address >= video_access_range_.low_address && address <= video_access_range_.high_address) update_display();
|
if(address >= video_access_range_.low_address && address <= video_access_range_.high_address) update_display();
|
||||||
ram_[address] = *value;
|
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
|
// 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
|
// 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));
|
cycles += video_output_->get_cycles_until_next_ram_availability((int)(cycles_since_display_update_ + 1));
|
||||||
}
|
} else {
|
||||||
else
|
switch(address & 0xff0f) {
|
||||||
{
|
|
||||||
switch(address & 0xff0f)
|
|
||||||
{
|
|
||||||
case 0xfe00:
|
case 0xfe00:
|
||||||
if(isReadOperation(operation))
|
if(isReadOperation(operation)) {
|
||||||
{
|
|
||||||
*value = interrupt_status_;
|
*value = interrupt_status_;
|
||||||
interrupt_status_ &= ~PowerOnReset;
|
interrupt_status_ &= ~PowerOnReset;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
interrupt_control_ = (*value) & ~1;
|
interrupt_control_ = (*value) & ~1;
|
||||||
evaluate_interrupts();
|
evaluate_interrupts();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0xfe07:
|
case 0xfe07:
|
||||||
if(!isReadOperation(operation))
|
if(!isReadOperation(operation)) {
|
||||||
{
|
|
||||||
// update speaker mode
|
// update speaker mode
|
||||||
bool new_speaker_is_enabled = (*value & 6) == 2;
|
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();
|
update_audio();
|
||||||
speaker_->set_is_enabled(new_speaker_is_enabled);
|
speaker_->set_is_enabled(new_speaker_is_enabled);
|
||||||
speaker_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 0xfe02: case 0xfe03:
|
||||||
case 0xfe08: case 0xfe09: case 0xfe0a: case 0xfe0b:
|
case 0xfe08: case 0xfe09: case 0xfe0a: case 0xfe0b:
|
||||||
case 0xfe0c: case 0xfe0d: case 0xfe0e: case 0xfe0f:
|
case 0xfe0c: case 0xfe0d: case 0xfe0e: case 0xfe0f:
|
||||||
if(!isReadOperation(operation))
|
if(!isReadOperation(operation)) {
|
||||||
{
|
|
||||||
update_display();
|
update_display();
|
||||||
video_output_->set_register(address, *value);
|
video_output_->set_register(address, *value);
|
||||||
video_access_range_ = video_output_->get_memory_access_range();
|
video_access_range_ = video_output_->get_memory_access_range();
|
||||||
@@ -211,23 +177,18 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0xfe04:
|
case 0xfe04:
|
||||||
if(isReadOperation(operation))
|
if(isReadOperation(operation)) {
|
||||||
{
|
|
||||||
*value = tape_.get_data_register();
|
*value = tape_.get_data_register();
|
||||||
tape_.clear_interrupts(Interrupt::ReceiveDataFull);
|
tape_.clear_interrupts(Interrupt::ReceiveDataFull);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
tape_.set_data_register(*value);
|
tape_.set_data_register(*value);
|
||||||
tape_.clear_interrupts(Interrupt::TransmitDataEmpty);
|
tape_.clear_interrupts(Interrupt::TransmitDataEmpty);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0xfe05:
|
case 0xfe05:
|
||||||
if(!isReadOperation(operation))
|
if(!isReadOperation(operation)) {
|
||||||
{
|
|
||||||
const uint8_t interruptDisable = (*value)&0xf0;
|
const uint8_t interruptDisable = (*value)&0xf0;
|
||||||
if( interruptDisable )
|
if( interruptDisable ) {
|
||||||
{
|
|
||||||
if( interruptDisable&0x10 ) interrupt_status_ &= ~Interrupt::DisplayEnd;
|
if( interruptDisable&0x10 ) interrupt_status_ &= ~Interrupt::DisplayEnd;
|
||||||
if( interruptDisable&0x20 ) interrupt_status_ &= ~Interrupt::RealTimeClock;
|
if( interruptDisable&0x20 ) interrupt_status_ &= ~Interrupt::RealTimeClock;
|
||||||
if( interruptDisable&0x40 ) interrupt_status_ &= ~Interrupt::HighToneDetect;
|
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);
|
active_rom_ = (Electron::ROMSlot)(*value & 0xf);
|
||||||
|
|
||||||
// apply the ULA's test
|
// apply the ULA's test
|
||||||
if(*value & 0x08)
|
if(*value & 0x08) {
|
||||||
{
|
if(*value & 0x04) {
|
||||||
if(*value & 0x04)
|
|
||||||
{
|
|
||||||
keyboard_is_active_ = false;
|
keyboard_is_active_ = false;
|
||||||
basic_is_active_ = false;
|
basic_is_active_ = false;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
keyboard_is_active_ = !(*value & 0x02);
|
keyboard_is_active_ = !(*value & 0x02);
|
||||||
basic_is_active_ = !keyboard_is_active_;
|
basic_is_active_ = !keyboard_is_active_;
|
||||||
}
|
}
|
||||||
@@ -256,8 +213,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0xfe06:
|
case 0xfe06:
|
||||||
if(!isReadOperation(operation))
|
if(!isReadOperation(operation)) {
|
||||||
{
|
|
||||||
update_audio();
|
update_audio();
|
||||||
speaker_->set_divider(*value);
|
speaker_->set_divider(*value);
|
||||||
tape_.set_counter(*value);
|
tape_.set_counter(*value);
|
||||||
@@ -265,10 +221,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xfc04: case 0xfc05: case 0xfc06: case 0xfc07:
|
case 0xfc04: case 0xfc05: case 0xfc06: case 0xfc07:
|
||||||
if(plus3_ && (address&0x00f0) == 0x00c0)
|
if(plus3_ && (address&0x00f0) == 0x00c0) {
|
||||||
{
|
if(is_holding_shift_ && address == 0xfcc4) {
|
||||||
if(is_holding_shift_ && address == 0xfcc4)
|
|
||||||
{
|
|
||||||
is_holding_shift_ = false;
|
is_holding_shift_ = false;
|
||||||
set_key_state(KeyShift, false);
|
set_key_state(KeyShift, false);
|
||||||
}
|
}
|
||||||
@@ -279,22 +233,16 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0xfc00:
|
case 0xfc00:
|
||||||
if(plus3_ && (address&0x00f0) == 0x00c0)
|
if(plus3_ && (address&0x00f0) == 0x00c0) {
|
||||||
{
|
if(!isReadOperation(operation)) {
|
||||||
if(!isReadOperation(operation))
|
|
||||||
{
|
|
||||||
plus3_->set_control_register(*value);
|
plus3_->set_control_register(*value);
|
||||||
}
|
} else *value = 1;
|
||||||
else
|
|
||||||
*value = 1;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if(address >= 0xc000)
|
if(address >= 0xc000) {
|
||||||
{
|
if(isReadOperation(operation)) {
|
||||||
if(isReadOperation(operation))
|
|
||||||
{
|
|
||||||
if(
|
if(
|
||||||
use_fast_tape_hack_ &&
|
use_fast_tape_hack_ &&
|
||||||
tape_.has_tape() &&
|
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
|
// and set A to zero to report that action was taken, then
|
||||||
// allow the PC read to return an RTS.
|
// allow the PC read to return an RTS.
|
||||||
)
|
)
|
||||||
)
|
) {
|
||||||
{
|
|
||||||
uint8_t service_call = (uint8_t)get_value_of_register(CPU6502::Register::X);
|
uint8_t service_call = (uint8_t)get_value_of_register(CPU6502::Register::X);
|
||||||
if(address == 0xf0a8)
|
if(address == 0xf0a8) {
|
||||||
{
|
if(!ram_[0x247] && service_call == 14) {
|
||||||
if(!ram_[0x247] && service_call == 14)
|
|
||||||
{
|
|
||||||
tape_.set_delegate(nullptr);
|
tape_.set_delegate(nullptr);
|
||||||
|
|
||||||
// TODO: handle tape wrap around.
|
// TODO: handle tape wrap around.
|
||||||
|
|
||||||
int cycles_left_while_plausibly_in_data = 50;
|
int cycles_left_while_plausibly_in_data = 50;
|
||||||
tape_.clear_interrupts(Interrupt::ReceiveDataFull);
|
tape_.clear_interrupts(Interrupt::ReceiveDataFull);
|
||||||
while(!tape_.get_tape()->is_at_end())
|
while(!tape_.get_tape()->is_at_end()) {
|
||||||
{
|
|
||||||
tape_.run_for_input_pulse();
|
tape_.run_for_input_pulse();
|
||||||
cycles_left_while_plausibly_in_data--;
|
cycles_left_while_plausibly_in_data--;
|
||||||
if(!cycles_left_while_plausibly_in_data) fast_load_is_in_data_ = false;
|
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());
|
set_value_of_register(CPU6502::Register::Y, tape_.get_data_register());
|
||||||
*value = 0x60; // 0x60 is RTS
|
*value = 0x60; // 0x60 is RTS
|
||||||
}
|
}
|
||||||
else
|
else *value = os_[address & 16383];
|
||||||
*value = os_[address & 16383];
|
|
||||||
}
|
}
|
||||||
else
|
else *value = 0xea;
|
||||||
*value = 0xea;
|
} else {
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
*value = os_[address & 16383];
|
*value = os_[address & 16383];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
if(isReadOperation(operation)) {
|
||||||
{
|
|
||||||
if(isReadOperation(operation))
|
|
||||||
{
|
|
||||||
*value = roms_[active_rom_][address & 16383];
|
*value = roms_[active_rom_][address & 16383];
|
||||||
if(keyboard_is_active_)
|
if(keyboard_is_active_) {
|
||||||
{
|
|
||||||
*value &= 0xf0;
|
*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(!(address&(1 << address_line))) *value |= key_states_[address_line];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(basic_is_active_)
|
if(basic_is_active_) {
|
||||||
{
|
|
||||||
*value &= roms_[ROMSlotBASIC][address & 16383];
|
*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;
|
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);
|
tape_.run_for_cycles(cycles);
|
||||||
|
|
||||||
cycles_until_display_interrupt_ -= cycles;
|
cycles_until_display_interrupt_ -= cycles;
|
||||||
if(cycles_until_display_interrupt_ < 0)
|
if(cycles_until_display_interrupt_ < 0) {
|
||||||
{
|
|
||||||
signal_interrupt(next_display_interrupt_);
|
signal_interrupt(next_display_interrupt_);
|
||||||
update_display();
|
update_display();
|
||||||
queue_next_display_interrupt();
|
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(typer_) typer_->update((int)cycles);
|
||||||
if(plus3_) plus3_->run_for_cycles(4*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;
|
return cycles;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::synchronise()
|
void Machine::synchronise() {
|
||||||
{
|
|
||||||
update_display();
|
update_display();
|
||||||
update_audio();
|
update_audio();
|
||||||
speaker_->flush();
|
speaker_->flush();
|
||||||
@@ -411,26 +351,21 @@ void Machine::synchronise()
|
|||||||
|
|
||||||
#pragma mark - Deferred scheduling
|
#pragma mark - Deferred scheduling
|
||||||
|
|
||||||
inline void Machine::update_display()
|
inline void Machine::update_display() {
|
||||||
{
|
if(cycles_since_display_update_) {
|
||||||
if(cycles_since_display_update_)
|
|
||||||
{
|
|
||||||
video_output_->run_for_cycles((int)cycles_since_display_update_);
|
video_output_->run_for_cycles((int)cycles_since_display_update_);
|
||||||
cycles_since_display_update_ = 0;
|
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();
|
VideoOutput::Interrupt next_interrupt = video_output_->get_next_interrupt();
|
||||||
cycles_until_display_interrupt_ = next_interrupt.cycles;
|
cycles_until_display_interrupt_ = next_interrupt.cycles;
|
||||||
next_display_interrupt_ = next_interrupt.interrupt;
|
next_display_interrupt_ = next_interrupt.interrupt;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Machine::update_audio()
|
inline void Machine::update_audio() {
|
||||||
{
|
if(cycles_since_audio_update_) {
|
||||||
if(cycles_since_audio_update_)
|
|
||||||
{
|
|
||||||
unsigned int difference = cycles_since_audio_update_ / Speaker::clock_rate_divider;
|
unsigned int difference = cycles_since_audio_update_ / Speaker::clock_rate_divider;
|
||||||
cycles_since_audio_update_ %= Speaker::clock_rate_divider;
|
cycles_since_audio_update_ %= Speaker::clock_rate_divider;
|
||||||
speaker_->run_for_cycles(difference);
|
speaker_->run_for_cycles(difference);
|
||||||
@@ -439,26 +374,20 @@ inline void Machine::update_audio()
|
|||||||
|
|
||||||
#pragma mark - Interrupts
|
#pragma mark - Interrupts
|
||||||
|
|
||||||
inline void Machine::signal_interrupt(Electron::Interrupt interrupt)
|
inline void Machine::signal_interrupt(Electron::Interrupt interrupt) {
|
||||||
{
|
|
||||||
interrupt_status_ |= interrupt;
|
interrupt_status_ |= interrupt;
|
||||||
evaluate_interrupts();
|
evaluate_interrupts();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Machine::clear_interrupt(Electron::Interrupt interrupt)
|
inline void Machine::clear_interrupt(Electron::Interrupt interrupt) {
|
||||||
{
|
|
||||||
interrupt_status_ &= ~interrupt;
|
interrupt_status_ &= ~interrupt;
|
||||||
evaluate_interrupts();
|
evaluate_interrupts();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Machine::evaluate_interrupts()
|
inline void Machine::evaluate_interrupts() {
|
||||||
{
|
if(interrupt_status_ & interrupt_control_) {
|
||||||
if(interrupt_status_ & interrupt_control_)
|
|
||||||
{
|
|
||||||
interrupt_status_ |= 1;
|
interrupt_status_ |= 1;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
interrupt_status_ &= ~1;
|
interrupt_status_ &= ~1;
|
||||||
}
|
}
|
||||||
set_irq_line(interrupt_status_ & 1);
|
set_irq_line(interrupt_status_ & 1);
|
||||||
@@ -466,8 +395,7 @@ inline void Machine::evaluate_interrupts()
|
|||||||
|
|
||||||
#pragma mark - Tape::Delegate
|
#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();
|
interrupt_status_ = (interrupt_status_ & ~(Interrupt::TransmitDataEmpty | Interrupt::ReceiveDataFull | Interrupt::HighToneDetect)) | tape_.get_interrupt_status();
|
||||||
evaluate_interrupts();
|
evaluate_interrupts();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ class Machine:
|
|||||||
// Disk
|
// Disk
|
||||||
std::unique_ptr<Plus3> plus3_;
|
std::unique_ptr<Plus3> plus3_;
|
||||||
bool is_holding_shift_;
|
bool is_holding_shift_;
|
||||||
|
int shift_restart_counter_;
|
||||||
|
|
||||||
// Outputs
|
// Outputs
|
||||||
std::unique_ptr<VideoOutput> video_output_;
|
std::unique_ptr<VideoOutput> video_output_;
|
||||||
|
|||||||
@@ -10,23 +10,19 @@
|
|||||||
|
|
||||||
using namespace Electron;
|
using namespace Electron;
|
||||||
|
|
||||||
Plus3::Plus3() : WD1770(P1770), last_control_(0)
|
Plus3::Plus3() : WD1770(P1770), last_control_(0) {
|
||||||
{
|
|
||||||
set_control_register(last_control_, 0xff);
|
set_control_register(last_control_, 0xff);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive)
|
void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||||
{
|
if(!drives_[drive]) {
|
||||||
if(!drives_[drive])
|
|
||||||
{
|
|
||||||
drives_[drive].reset(new Storage::Disk::Drive);
|
drives_[drive].reset(new Storage::Disk::Drive);
|
||||||
if(drive == selected_drive_) set_drive(drives_[drive]);
|
if(drive == selected_drive_) set_drive(drives_[drive]);
|
||||||
}
|
}
|
||||||
drives_[drive]->set_disk(disk);
|
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 0 => enable or disable drive 1
|
||||||
// bit 1 => enable or disable drive 2
|
// bit 1 => enable or disable drive 2
|
||||||
// bit 2 => side select
|
// bit 2 => side select
|
||||||
@@ -37,19 +33,15 @@ void Plus3::set_control_register(uint8_t control)
|
|||||||
set_control_register(control, changes);
|
set_control_register(control, changes);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Plus3::set_control_register(uint8_t control, uint8_t changes)
|
void Plus3::set_control_register(uint8_t control, uint8_t changes) {
|
||||||
{
|
if(changes&3) {
|
||||||
if(changes&3)
|
switch(control&3) {
|
||||||
{
|
|
||||||
switch(control&3)
|
|
||||||
{
|
|
||||||
case 0: selected_drive_ = -1; set_drive(nullptr); break;
|
case 0: selected_drive_ = -1; set_drive(nullptr); break;
|
||||||
default: selected_drive_ = 0; set_drive(drives_[0]); break;
|
default: selected_drive_ = 0; set_drive(drives_[0]); break;
|
||||||
case 2: selected_drive_ = 1; set_drive(drives_[1]); break;
|
case 2: selected_drive_ = 1; set_drive(drives_[1]); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(changes & 0x04)
|
if(changes & 0x04) {
|
||||||
{
|
|
||||||
invalidate_track();
|
invalidate_track();
|
||||||
if(drives_[0]) drives_[0]->set_head((control & 0x04) ? 1 : 0);
|
if(drives_[0]) drives_[0]->set_head((control & 0x04) ? 1 : 0);
|
||||||
if(drives_[1]) drives_[1]->set_head((control & 0x04) ? 1 : 0);
|
if(drives_[1]) drives_[1]->set_head((control & 0x04) ? 1 : 0);
|
||||||
|
|||||||
@@ -10,37 +10,29 @@
|
|||||||
|
|
||||||
using namespace Electron;
|
using namespace Electron;
|
||||||
|
|
||||||
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target)
|
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
|
||||||
{
|
if(is_enabled_) {
|
||||||
if(is_enabled_)
|
while(number_of_samples--) {
|
||||||
{
|
|
||||||
while(number_of_samples--)
|
|
||||||
{
|
|
||||||
*target = (int16_t)((counter_ / (divider_+1)) * 8192);
|
*target = (int16_t)((counter_ / (divider_+1)) * 8192);
|
||||||
target++;
|
target++;
|
||||||
counter_ = (counter_ + 1) % ((divider_+1) * 2);
|
counter_ = (counter_ + 1) % ((divider_+1) * 2);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
memset(target, 0, sizeof(int16_t) * number_of_samples);
|
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);
|
counter_ = (counter_ + number_of_samples) % ((divider_+1) * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Speaker::set_divider(uint8_t divider)
|
void Speaker::set_divider(uint8_t divider) {
|
||||||
{
|
|
||||||
enqueue([=]() {
|
enqueue([=]() {
|
||||||
divider_ = divider * 32 / clock_rate_divider;
|
divider_ = divider * 32 / clock_rate_divider;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Speaker::set_is_enabled(bool is_enabled)
|
void Speaker::set_is_enabled(bool is_enabled) {
|
||||||
{
|
|
||||||
enqueue([=]() {
|
enqueue([=]() {
|
||||||
is_enabled_ = is_enabled;
|
is_enabled_ = is_enabled;
|
||||||
counter_ = 0;
|
counter_ = 0;
|
||||||
|
|||||||
@@ -17,19 +17,15 @@ Tape::Tape() :
|
|||||||
delegate_(nullptr),
|
delegate_(nullptr),
|
||||||
output_({.bits_remaining_until_empty = 0, .cycles_into_pulse = 0}),
|
output_({.bits_remaining_until_empty = 0, .cycles_into_pulse = 0}),
|
||||||
last_posted_interrupt_status_(0),
|
last_posted_interrupt_status_(0),
|
||||||
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));
|
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) input_.minimum_bits_until_full--;
|
||||||
if(input_.minimum_bits_until_full == 8) interrupt_status_ &= ~Interrupt::ReceiveDataFull;
|
if(input_.minimum_bits_until_full == 8) interrupt_status_ &= ~Interrupt::ReceiveDataFull;
|
||||||
if(!input_.minimum_bits_until_full)
|
if(!input_.minimum_bits_until_full) {
|
||||||
{
|
if((data_register_&0x3) == 0x1) {
|
||||||
if((data_register_&0x3) == 0x1)
|
|
||||||
{
|
|
||||||
interrupt_status_ |= Interrupt::ReceiveDataFull;
|
interrupt_status_ |= Interrupt::ReceiveDataFull;
|
||||||
if(is_in_input_mode_) input_.minimum_bits_until_full = 9;
|
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();
|
evaluate_interrupts();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tape::evaluate_interrupts()
|
void Tape::evaluate_interrupts() {
|
||||||
{
|
if(last_posted_interrupt_status_ != interrupt_status_) {
|
||||||
if(last_posted_interrupt_status_ != interrupt_status_)
|
|
||||||
{
|
|
||||||
last_posted_interrupt_status_ = interrupt_status_;
|
last_posted_interrupt_status_ = interrupt_status_;
|
||||||
if(delegate_) delegate_->tape_did_change_interrupt_status(this);
|
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;
|
interrupt_status_ &= ~interrupts;
|
||||||
evaluate_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;
|
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_.cycles_into_pulse = 0;
|
||||||
output_.bits_remaining_until_empty = 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);
|
data_register_ = (uint16_t)((value << 2) | 1);
|
||||||
output_.bits_remaining_until_empty = 9;
|
output_.bits_remaining_until_empty = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t Tape::get_data_register()
|
uint8_t Tape::get_data_register() {
|
||||||
{
|
|
||||||
return (uint8_t)(data_register_ >> 2);
|
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_[0] = crossings_[1];
|
||||||
crossings_[1] = crossings_[2];
|
crossings_[1] = crossings_[2];
|
||||||
crossings_[2] = crossings_[3];
|
crossings_[2] = crossings_[3];
|
||||||
|
|
||||||
crossings_[3] = Tape::Unrecognised;
|
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;
|
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 / 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(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);
|
push_tape_bit(0);
|
||||||
crossings_[0] = crossings_[1] = Tape::Recognised;
|
crossings_[0] = crossings_[1] = Tape::Recognised;
|
||||||
}
|
} else {
|
||||||
else
|
if(crossings_[0] == Tape::Short && crossings_[1] == Tape::Short && crossings_[2] == Tape::Short && crossings_[3] == Tape::Short) {
|
||||||
{
|
|
||||||
if(crossings_[0] == Tape::Short && crossings_[1] == Tape::Short && crossings_[2] == Tape::Short && crossings_[3] == Tape::Short)
|
|
||||||
{
|
|
||||||
push_tape_bit(1);
|
push_tape_bit(1);
|
||||||
crossings_[0] = crossings_[1] =
|
crossings_[0] = crossings_[1] =
|
||||||
crossings_[2] = crossings_[3] = Tape::Recognised;
|
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)
|
void Tape::run_for_cycles(unsigned int number_of_cycles) {
|
||||||
{
|
if(is_enabled_) {
|
||||||
if(is_enabled_)
|
if(is_in_input_mode_) {
|
||||||
{
|
if(is_running_) {
|
||||||
if(is_in_input_mode_)
|
|
||||||
{
|
|
||||||
if(is_running_)
|
|
||||||
{
|
|
||||||
TapePlayer::run_for_cycles((int)number_of_cycles);
|
TapePlayer::run_for_cycles((int)number_of_cycles);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
output_.cycles_into_pulse += number_of_cycles;
|
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
|
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; // that divides the 125,000Hz clock that the sound divider runs off.
|
||||||
output_.cycles_into_pulse -= 1664;
|
|
||||||
push_tape_bit(1);
|
push_tape_bit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,18 +8,15 @@
|
|||||||
|
|
||||||
#include "Electron.hpp"
|
#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
|
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
|
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 KEYS(...) {__VA_ARGS__, TerminateSequence}
|
||||||
#define SHIFT(...) {KeyShift, __VA_ARGS__, TerminateSequence}
|
#define SHIFT(...) {KeyShift, __VA_ARGS__, TerminateSequence}
|
||||||
#define CTRL(...) {KeyControl, __VA_ARGS__, TerminateSequence}
|
#define CTRL(...) {KeyControl, __VA_ARGS__, TerminateSequence}
|
||||||
|
|||||||
@@ -42,8 +42,7 @@ VideoOutput::VideoOutput(uint8_t *memory) :
|
|||||||
output_position_(0),
|
output_position_(0),
|
||||||
screen_mode_(6),
|
screen_mode_(6),
|
||||||
screen_map_pointer_(0),
|
screen_map_pointer_(0),
|
||||||
cycles_into_draw_action_(0)
|
cycles_into_draw_action_(0) {
|
||||||
{
|
|
||||||
memset(palette_, 0xf, sizeof(palette_));
|
memset(palette_, 0xf, sizeof(palette_));
|
||||||
setup_screen_map();
|
setup_screen_map();
|
||||||
setup_base_address();
|
setup_base_address();
|
||||||
@@ -62,33 +61,26 @@ VideoOutput::VideoOutput(uint8_t *memory) :
|
|||||||
|
|
||||||
#pragma mark - CRT getter
|
#pragma mark - CRT getter
|
||||||
|
|
||||||
std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt()
|
std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt() {
|
||||||
{
|
|
||||||
return crt_;
|
return crt_;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Display update methods
|
#pragma mark - Display update methods
|
||||||
|
|
||||||
void VideoOutput::start_pixel_line()
|
void VideoOutput::start_pixel_line() {
|
||||||
{
|
|
||||||
current_pixel_line_ = (current_pixel_line_+1)&255;
|
current_pixel_line_ = (current_pixel_line_+1)&255;
|
||||||
if(!current_pixel_line_)
|
if(!current_pixel_line_) {
|
||||||
{
|
|
||||||
start_line_address_ = start_screen_address_;
|
start_line_address_ = start_screen_address_;
|
||||||
current_character_row_ = 0;
|
current_character_row_ = 0;
|
||||||
is_blank_line_ = false;
|
is_blank_line_ = false;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
bool mode_has_blank_lines = (screen_mode_ == 6) || (screen_mode_ == 3);
|
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)));
|
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_++;
|
start_line_address_++;
|
||||||
|
|
||||||
if(current_character_row_ > 7)
|
if(current_character_row_ > 7) {
|
||||||
{
|
|
||||||
start_line_address_ += ((screen_mode_ < 4) ? 80 : 40) * 8 - 8;
|
start_line_address_ += ((screen_mode_ < 4) ? 80 : 40) * 8 - 8;
|
||||||
current_character_row_ = 0;
|
current_character_row_ = 0;
|
||||||
}
|
}
|
||||||
@@ -99,52 +91,41 @@ void VideoOutput::start_pixel_line()
|
|||||||
initial_output_target_ = current_output_target_ = nullptr;
|
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_);
|
if(current_output_target_) crt_->output_data((unsigned int)((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_);
|
||||||
current_character_row_++;
|
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(!number_of_cycles) return;
|
||||||
|
|
||||||
if(is_blank_line_)
|
if(is_blank_line_) {
|
||||||
{
|
|
||||||
crt_->output_blank(number_of_cycles * crt_cycles_multiplier);
|
crt_->output_blank(number_of_cycles * crt_cycles_multiplier);
|
||||||
}
|
} else {
|
||||||
else
|
unsigned int divider = 1;
|
||||||
{
|
switch(screen_mode_) {
|
||||||
unsigned int divider = 0;
|
|
||||||
switch(screen_mode_)
|
|
||||||
{
|
|
||||||
case 0: case 3: divider = 2; break;
|
case 0: case 3: divider = 2; break;
|
||||||
case 1: case 4: case 6: divider = 4; break;
|
case 1: case 4: case 6: divider = 4; break;
|
||||||
case 2: case 5: divider = 8; 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_);
|
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;
|
current_output_divider_ = divider;
|
||||||
initial_output_target_ = current_output_target_ = crt_->allocate_write_area(640 / current_output_divider_);
|
initial_output_target_ = current_output_target_ = crt_->allocate_write_area(640 / current_output_divider_);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define get_pixel() \
|
#define get_pixel() \
|
||||||
if(current_screen_address_&32768)\
|
if(current_screen_address_&32768) {\
|
||||||
{\
|
|
||||||
current_screen_address_ = (screen_mode_base_address_ + current_screen_address_)&32767;\
|
current_screen_address_ = (screen_mode_base_address_ + current_screen_address_)&32767;\
|
||||||
}\
|
}\
|
||||||
last_pixel_byte_ = ram_[current_screen_address_];\
|
last_pixel_byte_ = ram_[current_screen_address_];\
|
||||||
current_screen_address_ = current_screen_address_+8
|
current_screen_address_ = current_screen_address_+8
|
||||||
|
|
||||||
switch(screen_mode_)
|
switch(screen_mode_) {
|
||||||
{
|
|
||||||
case 0: case 3:
|
case 0: case 3:
|
||||||
if(initial_output_target_)
|
if(initial_output_target_) {
|
||||||
{
|
while(number_of_cycles--) {
|
||||||
while(number_of_cycles--)
|
|
||||||
{
|
|
||||||
get_pixel();
|
get_pixel();
|
||||||
*(uint32_t *)current_output_target_ = palette_tables_.eighty1bpp[last_pixel_byte_];
|
*(uint32_t *)current_output_target_ = palette_tables_.eighty1bpp[last_pixel_byte_];
|
||||||
current_output_target_ += 4;
|
current_output_target_ += 4;
|
||||||
@@ -154,10 +135,8 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
if(initial_output_target_)
|
if(initial_output_target_) {
|
||||||
{
|
while(number_of_cycles--) {
|
||||||
while(number_of_cycles--)
|
|
||||||
{
|
|
||||||
get_pixel();
|
get_pixel();
|
||||||
*(uint16_t *)current_output_target_ = palette_tables_.eighty2bpp[last_pixel_byte_];
|
*(uint16_t *)current_output_target_ = palette_tables_.eighty2bpp[last_pixel_byte_];
|
||||||
current_output_target_ += 2;
|
current_output_target_ += 2;
|
||||||
@@ -167,10 +146,8 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
if(initial_output_target_)
|
if(initial_output_target_) {
|
||||||
{
|
while(number_of_cycles--) {
|
||||||
while(number_of_cycles--)
|
|
||||||
{
|
|
||||||
get_pixel();
|
get_pixel();
|
||||||
*current_output_target_ = palette_tables_.eighty4bpp[last_pixel_byte_];
|
*current_output_target_ = palette_tables_.eighty4bpp[last_pixel_byte_];
|
||||||
current_output_target_ += 1;
|
current_output_target_ += 1;
|
||||||
@@ -180,10 +157,8 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 4: case 6:
|
case 4: case 6:
|
||||||
if(initial_output_target_)
|
if(initial_output_target_) {
|
||||||
{
|
if(current_pixel_column_&1) {
|
||||||
if(current_pixel_column_&1)
|
|
||||||
{
|
|
||||||
last_pixel_byte_ <<= 4;
|
last_pixel_byte_ <<= 4;
|
||||||
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
|
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
|
||||||
current_output_target_ += 2;
|
current_output_target_ += 2;
|
||||||
@@ -191,8 +166,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles)
|
|||||||
number_of_cycles--;
|
number_of_cycles--;
|
||||||
current_pixel_column_++;
|
current_pixel_column_++;
|
||||||
}
|
}
|
||||||
while(number_of_cycles > 1)
|
while(number_of_cycles > 1) {
|
||||||
{
|
|
||||||
get_pixel();
|
get_pixel();
|
||||||
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
|
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
|
||||||
current_output_target_ += 2;
|
current_output_target_ += 2;
|
||||||
@@ -204,8 +178,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles)
|
|||||||
number_of_cycles -= 2;
|
number_of_cycles -= 2;
|
||||||
current_pixel_column_+=2;
|
current_pixel_column_+=2;
|
||||||
}
|
}
|
||||||
if(number_of_cycles)
|
if(number_of_cycles) {
|
||||||
{
|
|
||||||
get_pixel();
|
get_pixel();
|
||||||
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
|
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
|
||||||
current_output_target_ += 2;
|
current_output_target_ += 2;
|
||||||
@@ -215,10 +188,8 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 5:
|
case 5:
|
||||||
if(initial_output_target_)
|
if(initial_output_target_) {
|
||||||
{
|
if(current_pixel_column_&1) {
|
||||||
if(current_pixel_column_&1)
|
|
||||||
{
|
|
||||||
last_pixel_byte_ <<= 2;
|
last_pixel_byte_ <<= 2;
|
||||||
*current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_];
|
*current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_];
|
||||||
current_output_target_ += 1;
|
current_output_target_ += 1;
|
||||||
@@ -226,8 +197,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles)
|
|||||||
number_of_cycles--;
|
number_of_cycles--;
|
||||||
current_pixel_column_++;
|
current_pixel_column_++;
|
||||||
}
|
}
|
||||||
while(number_of_cycles > 1)
|
while(number_of_cycles > 1) {
|
||||||
{
|
|
||||||
get_pixel();
|
get_pixel();
|
||||||
*current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_];
|
*current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_];
|
||||||
current_output_target_ += 1;
|
current_output_target_ += 1;
|
||||||
@@ -239,8 +209,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles)
|
|||||||
number_of_cycles -= 2;
|
number_of_cycles -= 2;
|
||||||
current_pixel_column_+=2;
|
current_pixel_column_+=2;
|
||||||
}
|
}
|
||||||
if(number_of_cycles)
|
if(number_of_cycles) {
|
||||||
{
|
|
||||||
get_pixel();
|
get_pixel();
|
||||||
*current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_];
|
*current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_];
|
||||||
current_output_target_ += 1;
|
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;
|
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 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_);
|
int time_left_in_action = std::min(number_of_cycles, draw_action_length - cycles_into_draw_action_);
|
||||||
if(screen_map_[screen_map_pointer_].type == DrawAction::Pixels) output_pixels((unsigned int)time_left_in_action);
|
if(screen_map_[screen_map_pointer_].type == DrawAction::Pixels) output_pixels((unsigned int)time_left_in_action);
|
||||||
|
|
||||||
number_of_cycles -= time_left_in_action;
|
number_of_cycles -= time_left_in_action;
|
||||||
cycles_into_draw_action_ += time_left_in_action;
|
cycles_into_draw_action_ += time_left_in_action;
|
||||||
if(cycles_into_draw_action_ == draw_action_length)
|
if(cycles_into_draw_action_ == draw_action_length) {
|
||||||
{
|
switch(screen_map_[screen_map_pointer_].type) {
|
||||||
switch(screen_map_[screen_map_pointer_].type)
|
|
||||||
{
|
|
||||||
case DrawAction::Sync: crt_->output_sync((unsigned int)(draw_action_length * crt_cycles_multiplier)); break;
|
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::ColourBurst: crt_->output_default_colour_burst((unsigned int)(draw_action_length * crt_cycles_multiplier)); break;
|
||||||
case DrawAction::Blank: crt_->output_blank((unsigned int)(draw_action_length * crt_cycles_multiplier)); break;
|
case DrawAction::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
|
#pragma mark - Register hub
|
||||||
|
|
||||||
void VideoOutput::set_register(int address, uint8_t value)
|
void VideoOutput::set_register(int address, uint8_t value) {
|
||||||
{
|
switch(address & 0xf) {
|
||||||
switch(address & 0xf)
|
|
||||||
{
|
|
||||||
case 0x02:
|
case 0x02:
|
||||||
start_screen_address_ = (start_screen_address_ & 0xfe00) | (uint16_t)((value & 0xe0) << 1);
|
start_screen_address_ = (start_screen_address_ & 0xfe00) | (uint16_t)((value & 0xe0) << 1);
|
||||||
if(!start_screen_address_) start_screen_address_ |= 0x8000;
|
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);
|
start_screen_address_ = (start_screen_address_ & 0x01ff) | (uint16_t)((value & 0x3f) << 9);
|
||||||
if(!start_screen_address_) start_screen_address_ |= 0x8000;
|
if(!start_screen_address_) start_screen_address_ |= 0x8000;
|
||||||
break;
|
break;
|
||||||
case 0x07:
|
case 0x07: {
|
||||||
{
|
|
||||||
// update screen mode
|
// update screen mode
|
||||||
uint8_t new_screen_mode = (value >> 3)&7;
|
uint8_t new_screen_mode = (value >> 3)&7;
|
||||||
if(new_screen_mode == 7) new_screen_mode = 4;
|
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;
|
screen_mode_ = new_screen_mode;
|
||||||
setup_base_address();
|
setup_base_address();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0x08: case 0x09: case 0x0a: case 0x0b:
|
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] = {
|
static const int registers[4][4] = {
|
||||||
{10, 8, 2, 0},
|
{10, 8, 2, 0},
|
||||||
{14, 12, 6, 4},
|
{14, 12, 6, 4},
|
||||||
@@ -318,8 +278,7 @@ void VideoOutput::set_register(int address, uint8_t value)
|
|||||||
};
|
};
|
||||||
const int index = (address >> 1)&3;
|
const int index = (address >> 1)&3;
|
||||||
const uint8_t colour = ~value;
|
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][0]] = (palette_[registers[index][0]]&3) | ((colour >> 1)&4);
|
||||||
palette_[registers[index][1]] = (palette_[registers[index][1]]&3) | ((colour >> 0)&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);
|
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][2]] = (palette_[registers[index][2]]&5) | ((colour >> 4)&2);
|
||||||
palette_[registers[index][3]] = (palette_[registers[index][3]]&5) | ((colour >> 3)&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][0]] = (palette_[registers[index][0]]&6) | ((colour >> 7)&1);
|
||||||
palette_[registers[index][1]] = (palette_[registers[index][1]]&6) | ((colour >> 6)&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);
|
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
|
// regenerate all palette tables for now
|
||||||
#define pack(a, b) (uint8_t)((a << 4) | (b))
|
#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];
|
uint8_t *target = (uint8_t *)&palette_tables_.forty1bpp[byte];
|
||||||
target[0] = pack(palette_[(byte&0x80) >> 4], palette_[(byte&0x40) >> 3]);
|
target[0] = pack(palette_[(byte&0x80) >> 4], palette_[(byte&0x40) >> 3]);
|
||||||
target[1] = pack(palette_[(byte&0x20) >> 2], palette_[(byte&0x10) >> 1]);
|
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()
|
void VideoOutput::setup_base_address() {
|
||||||
{
|
switch(screen_mode_) {
|
||||||
switch(screen_mode_)
|
|
||||||
{
|
|
||||||
case 0: case 1: case 2: screen_mode_base_address_ = 0x3000; break;
|
case 0: case 1: case 2: screen_mode_base_address_ = 0x3000; break;
|
||||||
case 3: screen_mode_base_address_ = 0x4000; break;
|
case 3: screen_mode_base_address_ = 0x4000; break;
|
||||||
case 4: case 5: screen_mode_base_address_ = 0x5800; break;
|
case 4: case 5: screen_mode_base_address_ = 0x5800; break;
|
||||||
@@ -380,33 +334,28 @@ void VideoOutput::setup_base_address()
|
|||||||
|
|
||||||
#pragma mark - Interrupts
|
#pragma mark - Interrupts
|
||||||
|
|
||||||
VideoOutput::Interrupt VideoOutput::get_next_interrupt()
|
VideoOutput::Interrupt VideoOutput::get_next_interrupt() {
|
||||||
{
|
|
||||||
VideoOutput::Interrupt 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.cycles = real_time_clock_interrupt_1 - output_position_;
|
||||||
interrupt.interrupt = RealTimeClock;
|
interrupt.interrupt = RealTimeClock;
|
||||||
return interrupt;
|
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.cycles = display_end_interrupt_1 - output_position_;
|
||||||
interrupt.interrupt = DisplayEnd;
|
interrupt.interrupt = DisplayEnd;
|
||||||
return interrupt;
|
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.cycles = real_time_clock_interrupt_2 - output_position_;
|
||||||
interrupt.interrupt = RealTimeClock;
|
interrupt.interrupt = RealTimeClock;
|
||||||
return interrupt;
|
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.cycles = display_end_interrupt_2 - output_position_;
|
||||||
interrupt.interrupt = DisplayEnd;
|
interrupt.interrupt = DisplayEnd;
|
||||||
return interrupt;
|
return interrupt;
|
||||||
@@ -419,34 +368,28 @@ VideoOutput::Interrupt VideoOutput::get_next_interrupt()
|
|||||||
|
|
||||||
#pragma mark - RAM timing and access information
|
#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;
|
unsigned int result = 0;
|
||||||
int position = output_position_ + from_time;
|
int position = output_position_ + from_time;
|
||||||
|
|
||||||
result += 1 + (position&1);
|
result += 1 + (position&1);
|
||||||
if(screen_mode_ < 4)
|
if(screen_mode_ < 4) {
|
||||||
{
|
|
||||||
const int current_column = graphics_column(position + (position&1));
|
const int current_column = graphics_column(position + (position&1));
|
||||||
int current_line = graphics_line(position);
|
int current_line = graphics_line(position);
|
||||||
if(current_column < 80 && current_line < 256)
|
if(current_column < 80 && current_line < 256) {
|
||||||
{
|
if(screen_mode_ == 3) {
|
||||||
if(screen_mode_ == 3)
|
|
||||||
{
|
|
||||||
int output_position_line = graphics_line(output_position_);
|
int output_position_line = graphics_line(output_position_);
|
||||||
int implied_row = current_character_row_ + (current_line - output_position_line) % 10;
|
int implied_row = current_character_row_ + (current_line - output_position_line) % 10;
|
||||||
if(implied_row < 8)
|
if(implied_row < 8)
|
||||||
result += (unsigned int)(80 - current_column);
|
result += (unsigned int)(80 - current_column);
|
||||||
}
|
}
|
||||||
else
|
else result += (unsigned int)(80 - current_column);
|
||||||
result += (unsigned int)(80 - current_column);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
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:
|
// 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
|
// 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
|
// 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
|
#pragma mark - The screen map
|
||||||
|
|
||||||
void VideoOutput::setup_screen_map()
|
void VideoOutput::setup_screen_map() {
|
||||||
{
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
Odd field: Even field:
|
Odd field: Even field:
|
||||||
@@ -475,15 +417,11 @@ void VideoOutput::setup_screen_map()
|
|||||||
|-B-
|
|-B-
|
||||||
|
|
||||||
*/
|
*/
|
||||||
for(int c = 0; c < 2; c++)
|
for(int c = 0; c < 2; c++) {
|
||||||
{
|
if(c&1) {
|
||||||
if(c&1)
|
|
||||||
{
|
|
||||||
screen_map_.emplace_back(DrawAction::Sync, (cycles_per_line * 5) >> 1);
|
screen_map_.emplace_back(DrawAction::Sync, (cycles_per_line * 5) >> 1);
|
||||||
screen_map_.emplace_back(DrawAction::Blank, cycles_per_line >> 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::Blank, cycles_per_line >> 1);
|
||||||
screen_map_.emplace_back(DrawAction::Sync, (cycles_per_line * 5) >> 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::Sync, 9);
|
||||||
screen_map_.emplace_back(DrawAction::ColourBurst, 24 - 9);
|
screen_map_.emplace_back(DrawAction::ColourBurst, 24 - 9);
|
||||||
screen_map_.emplace_back(DrawAction::Blank, 128 - 24);
|
screen_map_.emplace_back(DrawAction::Blank, 128 - 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoOutput::emplace_pixel_line()
|
void VideoOutput::emplace_pixel_line() {
|
||||||
{
|
|
||||||
// output format is:
|
// output format is:
|
||||||
// 9 cycles: sync
|
// 9 cycles: sync
|
||||||
// ... to 24 cycles: colour burst
|
// ... to 24 cycles: colour burst
|
||||||
|
|||||||
@@ -10,18 +10,15 @@
|
|||||||
|
|
||||||
#include <cstdlib>
|
#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 divider = ((unsigned int)RAND_MAX + 1) / 256;
|
||||||
unsigned int shift = 1, value = 1;
|
unsigned int shift = 1, value = 1;
|
||||||
while(value < divider)
|
while(value < divider) {
|
||||||
{
|
|
||||||
value <<= 1;
|
value <<= 1;
|
||||||
shift++;
|
shift++;
|
||||||
}
|
}
|
||||||
|
|
||||||
for(size_t c = 0; c < size; c++)
|
for(size_t c = 0; c < size; c++) {
|
||||||
{
|
buffer[c] = (uint8_t)(std::rand() >> shift);
|
||||||
buffer[c] = (uint8_t)(rand() >> shift);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,116 +24,94 @@ Microdisc::Microdisc() :
|
|||||||
paging_flags_(BASICDisable),
|
paging_flags_(BASICDisable),
|
||||||
head_load_request_counter_(-1),
|
head_load_request_counter_(-1),
|
||||||
WD1770(P1793),
|
WD1770(P1793),
|
||||||
last_control_(0)
|
last_control_(0) {
|
||||||
{
|
|
||||||
set_control_register(last_control_, 0xff);
|
set_control_register(last_control_, 0xff);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Microdisc::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive)
|
void Microdisc::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||||
{
|
if(!drives_[drive]) {
|
||||||
if(!drives_[drive])
|
|
||||||
{
|
|
||||||
drives_[drive].reset(new Storage::Disk::Drive);
|
drives_[drive].reset(new Storage::Disk::Drive);
|
||||||
if(drive == selected_drive_) set_drive(drives_[drive]);
|
if(drive == selected_drive_) set_drive(drives_[drive]);
|
||||||
}
|
}
|
||||||
drives_[drive]->set_disk(disk);
|
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;
|
uint8_t changes = last_control_ ^ control;
|
||||||
last_control_ = control;
|
last_control_ = control;
|
||||||
set_control_register(control, changes);
|
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]
|
// b2: data separator clock rate select (1 = double) [TODO]
|
||||||
|
|
||||||
// b65: drive select
|
// b65: drive select
|
||||||
if((changes >> 5)&3)
|
if((changes >> 5)&3) {
|
||||||
{
|
|
||||||
selected_drive_ = (control >> 5)&3;
|
selected_drive_ = (control >> 5)&3;
|
||||||
set_drive(drives_[selected_drive_]);
|
set_drive(drives_[selected_drive_]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// b4: side select
|
// b4: side select
|
||||||
if(changes & 0x10)
|
if(changes & 0x10) {
|
||||||
{
|
|
||||||
unsigned int head = (control & 0x10) ? 1 : 0;
|
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);
|
if(drives_[c]) drives_[c]->set_head(head);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// b3: double density select (0 = double)
|
// b3: double density select (0 = double)
|
||||||
if(changes & 0x08)
|
if(changes & 0x08) {
|
||||||
{
|
|
||||||
set_is_double_density(!(control & 0x08));
|
set_is_double_density(!(control & 0x08));
|
||||||
}
|
}
|
||||||
|
|
||||||
// b0: IRQ enable
|
// b0: IRQ enable
|
||||||
if(changes & 0x01)
|
if(changes & 0x01) {
|
||||||
{
|
|
||||||
bool had_irq = get_interrupt_request_line();
|
bool had_irq = get_interrupt_request_line();
|
||||||
irq_enable_ = !!(control & 0x01);
|
irq_enable_ = !!(control & 0x01);
|
||||||
bool has_irq = get_interrupt_request_line();
|
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);
|
delegate_->wd1770_did_change_output(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// b7: EPROM select (0 = select)
|
// b7: EPROM select (0 = select)
|
||||||
// b1: ROM disable (0 = disable)
|
// b1: ROM disable (0 = disable)
|
||||||
if(changes & 0x82)
|
if(changes & 0x82) {
|
||||||
{
|
|
||||||
paging_flags_ = ((control & 0x02) ? 0 : BASICDisable) | ((control & 0x80) ? MicrodscDisable : 0);
|
paging_flags_ = ((control & 0x02) ? 0 : BASICDisable) | ((control & 0x80) ? MicrodscDisable : 0);
|
||||||
if(delegate_) delegate_->microdisc_did_change_paging_flags(this);
|
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();
|
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);
|
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);
|
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);
|
set_motor_on(head_load);
|
||||||
if(head_load)
|
if(head_load) {
|
||||||
{
|
|
||||||
head_load_request_counter_ = 0;
|
head_load_request_counter_ = 0;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
head_load_request_counter_ = head_load_request_counter_target;
|
head_load_request_counter_ = head_load_request_counter_target;
|
||||||
set_head_loaded(head_load);
|
set_head_loaded(head_load);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Microdisc::run_for_cycles(unsigned int number_of_cycles)
|
void Microdisc::run_for_cycles(unsigned int number_of_cycles) {
|
||||||
{
|
if(head_load_request_counter_ < head_load_request_counter_target) {
|
||||||
if(head_load_request_counter_ < head_load_request_counter_target)
|
|
||||||
{
|
|
||||||
head_load_request_counter_ += number_of_cycles;
|
head_load_request_counter_ += number_of_cycles;
|
||||||
if(head_load_request_counter_ >= head_load_request_counter_target) set_head_loaded(true);
|
if(head_load_request_counter_ >= head_load_request_counter_target) set_head_loaded(true);
|
||||||
}
|
}
|
||||||
WD::WD1770::run_for_cycles(number_of_cycles);
|
WD::WD1770::run_for_cycles(number_of_cycles);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Microdisc::get_drive_is_ready()
|
bool Microdisc::get_drive_is_ready() {
|
||||||
{
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ Machine::Machine() :
|
|||||||
keyboard_(new Keyboard),
|
keyboard_(new Keyboard),
|
||||||
ram_top_(0xbfff),
|
ram_top_(0xbfff),
|
||||||
paged_rom_(rom_),
|
paged_rom_(rom_),
|
||||||
microdisc_is_enabled_(false)
|
microdisc_is_enabled_(false) {
|
||||||
{
|
|
||||||
set_clock_rate(1000000);
|
set_clock_rate(1000000);
|
||||||
via_.set_interrupt_delegate(this);
|
via_.set_interrupt_delegate(this);
|
||||||
via_.keyboard = keyboard_;
|
via_.keyboard = keyboard_;
|
||||||
@@ -29,43 +28,35 @@ Machine::Machine() :
|
|||||||
Memory::Fuzz(ram_, sizeof(ram_));
|
Memory::Fuzz(ram_, sizeof(ram_));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::configure_as_target(const StaticAnalyser::Target &target)
|
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
|
||||||
{
|
if(target.tapes.size()) {
|
||||||
if(target.tapes.size())
|
|
||||||
{
|
|
||||||
via_.tape->set_tape(target.tapes.front());
|
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());
|
set_typer_for_string(target.loadingCommand.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
if(target.oric.has_microdisc)
|
if(target.oric.has_microdisc) {
|
||||||
{
|
|
||||||
microdisc_is_enabled_ = true;
|
microdisc_is_enabled_ = true;
|
||||||
microdisc_did_change_paging_flags(µdisc_);
|
microdisc_did_change_paging_flags(µdisc_);
|
||||||
microdisc_.set_delegate(this);
|
microdisc_.set_delegate(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
int drive_index = 0;
|
int drive_index = 0;
|
||||||
for(auto disk : target.disks)
|
for(auto disk : target.disks) {
|
||||||
{
|
|
||||||
if(drive_index < 4) microdisc_.set_disk(disk, drive_index);
|
if(drive_index < 4) microdisc_.set_disk(disk, drive_index);
|
||||||
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_)));
|
memcpy(rom_, basic11_rom_.data(), std::min(basic11_rom_.size(), sizeof(rom_)));
|
||||||
|
|
||||||
is_using_basic11_ = true;
|
is_using_basic11_ = true;
|
||||||
tape_get_byte_address_ = 0xe6c9;
|
tape_get_byte_address_ = 0xe6c9;
|
||||||
scan_keyboard_address_ = 0xf495;
|
scan_keyboard_address_ = 0xf495;
|
||||||
tape_speed_address_ = 0x024d;
|
tape_speed_address_ = 0x024d;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
memcpy(rom_, basic10_rom_.data(), std::min(basic10_rom_.size(), sizeof(rom_)));
|
memcpy(rom_, basic10_rom_.data(), std::min(basic10_rom_.size(), sizeof(rom_)));
|
||||||
|
|
||||||
is_using_basic11_ = false;
|
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)
|
void Machine::set_rom(ROM rom, const std::vector<uint8_t> &data) {
|
||||||
{
|
switch(rom) {
|
||||||
switch(rom)
|
|
||||||
{
|
|
||||||
case BASIC11: basic11_rom_ = std::move(data); break;
|
case BASIC11: basic11_rom_ = std::move(data); break;
|
||||||
case BASIC10: basic10_rom_ = std::move(data); break;
|
case BASIC10: basic10_rom_ = std::move(data); break;
|
||||||
case Microdisc: microdisc_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)
|
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||||
{
|
if(address > ram_top_) {
|
||||||
if(address > ram_top_)
|
|
||||||
{
|
|
||||||
if(isReadOperation(operation)) *value = paged_rom_[address - ram_top_ - 1];
|
if(isReadOperation(operation)) *value = paged_rom_[address - ram_top_ - 1];
|
||||||
|
|
||||||
// 024D = 0 => fast; otherwise slow
|
// 024D = 0 => fast; otherwise slow
|
||||||
// E6C9 = read byte: return byte in A
|
// 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_]);
|
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::A, next_byte);
|
||||||
set_value_of_register(CPU6502::Flags, next_byte ? 0 : CPU6502::Flag::Zero);
|
set_value_of_register(CPU6502::Flags, next_byte ? 0 : CPU6502::Flag::Zero);
|
||||||
*value = 0x60; // i.e. RTS
|
*value = 0x60; // i.e. RTS
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
if((address & 0xff00) == 0x0300) {
|
||||||
{
|
if(microdisc_is_enabled_ && address >= 0x0310) {
|
||||||
if((address & 0xff00) == 0x0300)
|
switch(address) {
|
||||||
{
|
|
||||||
if(microdisc_is_enabled_ && address >= 0x0310)
|
|
||||||
{
|
|
||||||
switch(address)
|
|
||||||
{
|
|
||||||
case 0x0310: case 0x0311: case 0x0312: case 0x0313:
|
case 0x0310: case 0x0311: case 0x0312: case 0x0313:
|
||||||
if(isReadOperation(operation)) *value = microdisc_.get_register(address);
|
if(isReadOperation(operation)) *value = microdisc_.get_register(address);
|
||||||
else microdisc_.set_register(address, *value);
|
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();
|
if(isReadOperation(operation)) *value = microdisc_.get_data_request_register();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
if(isReadOperation(operation)) *value = via_.get_register(address);
|
if(isReadOperation(operation)) *value = via_.get_register(address);
|
||||||
else via_.set_register(address, *value);
|
else via_.set_register(address, *value);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
if(isReadOperation(operation))
|
if(isReadOperation(operation))
|
||||||
*value = ram_[address];
|
*value = ram_[address];
|
||||||
else
|
else {
|
||||||
{
|
|
||||||
if(address >= 0x9800 && address <= 0xc000) { update_video(); typer_delay_ = 0; }
|
if(address >= 0x9800 && address <= 0xc000) { update_video(); typer_delay_ = 0; }
|
||||||
ram_[address] = *value;
|
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
|
// 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
|
// do anything until at least the second, regardless of machine
|
||||||
if(!keyboard_read_count_) keyboard_read_count_++;
|
if(!keyboard_read_count_) keyboard_read_count_++;
|
||||||
else if(!typer_->type_next_character())
|
else if(!typer_->type_next_character()) {
|
||||||
{
|
|
||||||
clear_all_keys();
|
clear_all_keys();
|
||||||
typer_.reset();
|
typer_.reset();
|
||||||
}
|
}
|
||||||
@@ -162,45 +136,36 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::synchronise()
|
void Machine::synchronise() {
|
||||||
{
|
|
||||||
update_video();
|
update_video();
|
||||||
via_.synchronise();
|
via_.synchronise();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::update_video()
|
void Machine::update_video() {
|
||||||
{
|
|
||||||
video_output_->run_for_cycles(cycles_since_video_update_);
|
video_output_->run_for_cycles(cycles_since_video_update_);
|
||||||
cycles_since_video_update_ = 0;
|
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.reset(new GI::AY38910());
|
||||||
via_.ay8910->set_clock_rate(1000000);
|
via_.ay8910->set_clock_rate(1000000);
|
||||||
video_output_.reset(new VideoOutput(ram_));
|
video_output_.reset(new VideoOutput(ram_));
|
||||||
if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_);
|
if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::close_output()
|
void Machine::close_output() {
|
||||||
{
|
|
||||||
video_output_.reset();
|
video_output_.reset();
|
||||||
via_.ay8910.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();
|
set_interrupt_line();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::set_key_state(uint16_t key, bool isPressed)
|
void Machine::set_key_state(uint16_t key, bool isPressed) {
|
||||||
{
|
if(key == KeyNMI) {
|
||||||
if(key == KeyNMI)
|
|
||||||
{
|
|
||||||
set_nmi_line(isPressed);
|
set_nmi_line(isPressed);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
if(isPressed)
|
if(isPressed)
|
||||||
keyboard_->rows[key >> 8] |= (key & 0xff);
|
keyboard_->rows[key >> 8] |= (key & 0xff);
|
||||||
else
|
else
|
||||||
@@ -208,39 +173,32 @@ 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));
|
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;
|
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);
|
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
|
// set CB1
|
||||||
via_.set_control_line_input(VIA::Port::B, VIA::Line::One, tape_player->get_input());
|
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();
|
return video_output_->get_crt();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Outputs::Speaker> Machine::get_speaker()
|
std::shared_ptr<Outputs::Speaker> Machine::get_speaker() {
|
||||||
{
|
|
||||||
return via_.ay8910;
|
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);
|
CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,57 +209,44 @@ Machine::VIA::VIA() :
|
|||||||
cycles_since_ay_update_(0),
|
cycles_since_ay_update_(0),
|
||||||
tape(new TapePlayer) {}
|
tape(new TapePlayer) {}
|
||||||
|
|
||||||
void Machine::VIA::set_control_line_output(Port port, Line line, bool value)
|
void Machine::VIA::set_control_line_output(Port port, Line line, bool value) {
|
||||||
{
|
if(line) {
|
||||||
if(line)
|
|
||||||
{
|
|
||||||
if(port) ay_bdir_ = value; else ay_bc1_ = value;
|
if(port) ay_bdir_ = value; else ay_bc1_ = value;
|
||||||
update_ay();
|
update_ay();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::VIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask)
|
void Machine::VIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask) {
|
||||||
{
|
if(port) {
|
||||||
if(port)
|
|
||||||
{
|
|
||||||
keyboard->row = value;
|
keyboard->row = value;
|
||||||
tape->set_motor_control(value & 0x40);
|
tape->set_motor_control(value & 0x40);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
ay8910->set_data_input(value);
|
ay8910->set_data_input(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t Machine::VIA::get_port_input(Port port)
|
uint8_t Machine::VIA::get_port_input(Port port) {
|
||||||
{
|
if(port) {
|
||||||
if(port)
|
|
||||||
{
|
|
||||||
uint8_t column = ay8910->get_port_output(false) ^ 0xff;
|
uint8_t column = ay8910->get_port_output(false) ^ 0xff;
|
||||||
return (keyboard->rows[keyboard->row & 7] & column) ? 0x08 : 0x00;
|
return (keyboard->rows[keyboard->row & 7] & column) ? 0x08 : 0x00;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
return ay8910->get_data_output();
|
return ay8910->get_data_output();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::VIA::synchronise()
|
void Machine::VIA::synchronise() {
|
||||||
{
|
|
||||||
ay8910->run_for_cycles(cycles_since_ay_update_);
|
ay8910->run_for_cycles(cycles_since_ay_update_);
|
||||||
ay8910->flush();
|
ay8910->flush();
|
||||||
cycles_since_ay_update_ = 0;
|
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;
|
cycles_since_ay_update_ += number_of_cycles;
|
||||||
MOS::MOS6522<VIA>::run_for_cycles(number_of_cycles);
|
MOS::MOS6522<VIA>::run_for_cycles(number_of_cycles);
|
||||||
tape->run_for_cycles((int)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_);
|
ay8910->run_for_cycles(cycles_since_ay_update_);
|
||||||
cycles_since_ay_update_ = 0;
|
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));
|
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
|
#pragma mark - TapePlayer
|
||||||
|
|
||||||
Machine::TapePlayer::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);
|
return (uint8_t)parser_.get_next_byte(get_tape(), fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Microdisc
|
#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();
|
int flags = microdisc->get_paging_flags();
|
||||||
if(!(flags&Microdisc::PagingFlags::BASICDisable))
|
if(!(flags&Microdisc::PagingFlags::BASICDisable)) {
|
||||||
{
|
|
||||||
ram_top_ = 0xbfff;
|
ram_top_ = 0xbfff;
|
||||||
paged_rom_ = rom_;
|
paged_rom_ = rom_;
|
||||||
}
|
} else {
|
||||||
else
|
if(flags&Microdisc::PagingFlags::MicrodscDisable) {
|
||||||
{
|
|
||||||
if(flags&Microdisc::PagingFlags::MicrodscDisable)
|
|
||||||
{
|
|
||||||
ram_top_ = 0xffff;
|
ram_top_ = 0xffff;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
ram_top_ = 0xdfff;
|
ram_top_ = 0xdfff;
|
||||||
paged_rom_ = microdisc_rom_.data();
|
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();
|
set_interrupt_line();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::set_interrupt_line()
|
void Machine::set_interrupt_line() {
|
||||||
{
|
|
||||||
set_irq_line(
|
set_irq_line(
|
||||||
via_.get_interrupt_line() ||
|
via_.get_interrupt_line() ||
|
||||||
(microdisc_is_enabled_ && microdisc_.get_interrupt_request_line()));
|
(microdisc_is_enabled_ && microdisc_.get_interrupt_request_line()));
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#include "Oric.hpp"
|
#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 KEYS(...) {__VA_ARGS__, TerminateSequence}
|
||||||
#define SHIFT(...) {KeyLeftShift, __VA_ARGS__, TerminateSequence}
|
#define SHIFT(...) {KeyLeftShift, __VA_ARGS__, TerminateSequence}
|
||||||
#define X {NotMapped}
|
#define X {NotMapped}
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ VideoOutput::VideoOutput(uint8_t *memory) :
|
|||||||
character_set_base_address_(0xb400),
|
character_set_base_address_(0xb400),
|
||||||
v_sync_start_position_(PAL50VSyncStartPosition), v_sync_end_position_(PAL50VSyncEndPosition),
|
v_sync_start_position_(PAL50VSyncStartPosition), v_sync_end_position_(PAL50VSyncEndPosition),
|
||||||
counter_period_(PAL50Period), next_frame_is_sixty_hertz_(false),
|
counter_period_(PAL50Period), next_frame_is_sixty_hertz_(false),
|
||||||
crt_(new Outputs::CRT::CRT(64*6, 6, Outputs::CRT::DisplayType::PAL50, 2))
|
crt_(new Outputs::CRT::CRT(64*6, 6, Outputs::CRT::DisplayType::PAL50, 2)) {
|
||||||
{
|
|
||||||
crt_->set_rgb_sampling_function(
|
crt_->set_rgb_sampling_function(
|
||||||
"vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)"
|
"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));
|
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;
|
output_device_ = output_device;
|
||||||
crt_->set_output_device(output_device);
|
crt_->set_output_device(output_device);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom)
|
void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom) {
|
||||||
{
|
for(size_t c = 0; c < 8; c++) {
|
||||||
for(size_t c = 0; c < 8; c++)
|
|
||||||
{
|
|
||||||
size_t index = (c << 2);
|
size_t index = (c << 2);
|
||||||
uint16_t rom_value = (uint16_t)(((uint16_t)rom[index] << 8) | (uint16_t)rom[index+1]);
|
uint16_t rom_value = (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);
|
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
|
// check for big endianness and byte swap if required
|
||||||
uint16_t test_value = 0x0001;
|
uint16_t test_value = 0x0001;
|
||||||
if(*(uint8_t *)&test_value != 0x01)
|
if(*(uint8_t *)&test_value != 0x01) {
|
||||||
{
|
for(size_t c = 0; c < 8; c++) {
|
||||||
for(size_t c = 0; c < 8; c++)
|
|
||||||
{
|
|
||||||
colour_forms_[c] = (uint16_t)((colour_forms_[c] >> 8) | (colour_forms_[c] << 8));
|
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_;
|
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
|
// Vertical: 0–39: pixels; otherwise blank; 48–53 sync, 54–56 colour burst
|
||||||
// Horizontal: 0–223: pixels; otherwise blank; 256–259 sync
|
// Horizontal: 0–223: pixels; otherwise blank; 256–259 sync
|
||||||
|
|
||||||
#define clamp(action) \
|
#define clamp(action) \
|
||||||
if(cycles_run_for <= number_of_cycles) { action; } else cycles_run_for = number_of_cycles;
|
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 h_counter = counter_ & 63;
|
||||||
int cycles_run_for = 0;
|
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
|
// this is a sync line
|
||||||
cycles_run_for = v_sync_end_position_ - counter_;
|
cycles_run_for = v_sync_end_position_ - counter_;
|
||||||
clamp(crt_->output_sync((unsigned int)(v_sync_end_position_ - v_sync_start_position_) * 6));
|
clamp(crt_->output_sync((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
|
// this is a pixel line
|
||||||
if(!h_counter)
|
if(!h_counter) {
|
||||||
{
|
|
||||||
ink_ = 0x7;
|
ink_ = 0x7;
|
||||||
paper_ = 0x0;
|
paper_ = 0x0;
|
||||||
use_alternative_character_set_ = use_double_height_characters_ = blink_text_ = false;
|
use_alternative_character_set_ = use_double_height_characters_ = blink_text_ = false;
|
||||||
set_character_set_base_address();
|
set_character_set_base_address();
|
||||||
pixel_target_ = (uint16_t *)crt_->allocate_write_area(240);
|
pixel_target_ = (uint16_t *)crt_->allocate_write_area(240);
|
||||||
|
|
||||||
if(!counter_)
|
if(!counter_) {
|
||||||
{
|
|
||||||
frame_counter_++;
|
frame_counter_++;
|
||||||
|
|
||||||
v_sync_start_position_ = next_frame_is_sixty_hertz_ ? PAL60VSyncStartPosition : PAL50VSyncStartPosition;
|
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;
|
int character_base_address = 0xbb80 + (counter_ >> 9) * 40;
|
||||||
uint8_t blink_mask = (blink_text_ && (frame_counter_&32)) ? 0x00 : 0xff;
|
uint8_t blink_mask = (blink_text_ && (frame_counter_&32)) ? 0x00 : 0xff;
|
||||||
|
|
||||||
while(columns--)
|
while(columns--) {
|
||||||
{
|
|
||||||
uint8_t pixels, control_byte;
|
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];
|
control_byte = pixels = ram_[pixel_base_address + h_counter];
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
int address = character_base_address + h_counter;
|
int address = character_base_address + h_counter;
|
||||||
control_byte = ram_[address];
|
control_byte = ram_[address];
|
||||||
int line = use_double_height_characters_ ? ((counter_ >> 7) & 7) : ((counter_ >> 6) & 7);
|
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;
|
uint8_t inverse_mask = (control_byte & 0x80) ? 0x7 : 0x0;
|
||||||
pixels &= blink_mask;
|
pixels &= blink_mask;
|
||||||
|
|
||||||
if(control_byte & 0x60)
|
if(control_byte & 0x60) {
|
||||||
{
|
if(pixel_target_) {
|
||||||
if(pixel_target_)
|
|
||||||
{
|
|
||||||
uint16_t colours[2];
|
uint16_t colours[2];
|
||||||
if(output_device_ == Outputs::CRT::Monitor)
|
if(output_device_ == Outputs::CRT::Monitor) {
|
||||||
{
|
|
||||||
colours[0] = (uint8_t)(paper_ ^ inverse_mask);
|
colours[0] = (uint8_t)(paper_ ^ inverse_mask);
|
||||||
colours[1] = (uint8_t)(ink_ ^ inverse_mask);
|
colours[1] = (uint8_t)(ink_ ^ inverse_mask);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
colours[0] = colour_forms_[paper_ ^ inverse_mask];
|
colours[0] = colour_forms_[paper_ ^ inverse_mask];
|
||||||
colours[1] = colour_forms_[ink_ ^ 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_[4] = colours[(pixels >> 1)&1];
|
||||||
pixel_target_[5] = colours[(pixels >> 0)&1];
|
pixel_target_[5] = colours[(pixels >> 0)&1];
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
switch(control_byte & 0x1f) {
|
||||||
{
|
|
||||||
switch(control_byte & 0x1f)
|
|
||||||
{
|
|
||||||
case 0x00: ink_ = 0x0; break;
|
case 0x00: ink_ = 0x0; break;
|
||||||
case 0x01: ink_ = 0x4; break;
|
case 0x01: ink_ = 0x4; break;
|
||||||
case 0x02: ink_ = 0x2; break;
|
case 0x02: ink_ = 0x2; break;
|
||||||
@@ -206,8 +180,7 @@ void VideoOutput::run_for_cycles(int number_of_cycles)
|
|||||||
|
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
if(pixel_target_)
|
if(pixel_target_) {
|
||||||
{
|
|
||||||
pixel_target_[0] = pixel_target_[1] =
|
pixel_target_[0] = pixel_target_[1] =
|
||||||
pixel_target_[2] = pixel_target_[3] =
|
pixel_target_[2] = pixel_target_[3] =
|
||||||
pixel_target_[4] = pixel_target_[5] =
|
pixel_target_[4] = pixel_target_[5] =
|
||||||
@@ -218,34 +191,24 @@ void VideoOutput::run_for_cycles(int number_of_cycles)
|
|||||||
h_counter++;
|
h_counter++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(h_counter == 40)
|
if(h_counter == 40) {
|
||||||
{
|
|
||||||
crt_->output_data(40 * 6, 1);
|
crt_->output_data(40 * 6, 1);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
// this is a blank line (or the equivalent part of a pixel line)
|
// 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;
|
cycles_run_for = 48 - h_counter;
|
||||||
clamp(
|
clamp(
|
||||||
int period = (counter_ < 224*64) ? 8 : 48;
|
int period = (counter_ < 224*64) ? 8 : 48;
|
||||||
crt_->output_blank((unsigned int)period * 6);
|
crt_->output_blank((unsigned int)period * 6);
|
||||||
);
|
);
|
||||||
}
|
} else if(h_counter < 54) {
|
||||||
else if(h_counter < 54)
|
|
||||||
{
|
|
||||||
cycles_run_for = 54 - h_counter;
|
cycles_run_for = 54 - h_counter;
|
||||||
clamp(crt_->output_sync(6 * 6));
|
clamp(crt_->output_sync(6 * 6));
|
||||||
}
|
} else if(h_counter < 56) {
|
||||||
else if(h_counter < 56)
|
|
||||||
{
|
|
||||||
cycles_run_for = 56 - h_counter;
|
cycles_run_for = 56 - h_counter;
|
||||||
clamp(crt_->output_default_colour_burst(2 * 6));
|
clamp(crt_->output_default_colour_burst(2 * 6));
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
cycles_run_for = 64 - h_counter;
|
cycles_run_for = 64 - h_counter;
|
||||||
clamp(crt_->output_blank(8 * 6));
|
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;
|
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;
|
else character_set_base_address_ = use_alternative_character_set_ ? 0xb800 : 0xb400;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,76 +12,61 @@
|
|||||||
using namespace Utility;
|
using namespace Utility;
|
||||||
|
|
||||||
Typer::Typer(const char *string, int delay, int frequency, Delegate *delegate) :
|
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;
|
size_t string_size = strlen(string) + 3;
|
||||||
string_ = (char *)malloc(string_size);
|
string_ = (char *)malloc(string_size);
|
||||||
snprintf(string_, strlen(string) + 3, "%c%s%c", Typer::BeginString, string, Typer::EndString);
|
snprintf(string_, strlen(string) + 3, "%c%s%c", Typer::BeginString, string, Typer::EndString);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Typer::update(int duration)
|
void Typer::update(int duration) {
|
||||||
{
|
if(string_) {
|
||||||
if(string_)
|
if(counter_ < 0 && counter_ + duration >= 0) {
|
||||||
{
|
if(!type_next_character()) {
|
||||||
if(counter_ < 0 && counter_ + duration >= 0)
|
|
||||||
{
|
|
||||||
if(!type_next_character())
|
|
||||||
{
|
|
||||||
delegate_->typer_reset(this);
|
delegate_->typer_reset(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
counter_ += duration;
|
counter_ += duration;
|
||||||
while(string_ && counter_ > frequency_)
|
while(string_ && counter_ > frequency_) {
|
||||||
{
|
|
||||||
counter_ -= frequency_;
|
counter_ -= frequency_;
|
||||||
if(!type_next_character())
|
if(!type_next_character()) {
|
||||||
{
|
|
||||||
delegate_->typer_reset(this);
|
delegate_->typer_reset(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Typer::type_next_character()
|
bool Typer::type_next_character() {
|
||||||
{
|
|
||||||
if(string_ == nullptr) return false;
|
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;
|
phase_ = 0;
|
||||||
if(!string_[string_pointer_])
|
if(!string_[string_pointer_]) {
|
||||||
{
|
|
||||||
free(string_);
|
free(string_);
|
||||||
string_ = nullptr;
|
string_ = nullptr;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
string_pointer_++;
|
string_pointer_++;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
phase_++;
|
phase_++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Typer::~Typer()
|
Typer::~Typer() {
|
||||||
{
|
|
||||||
free(string_);
|
free(string_);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Delegate
|
#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);
|
uint16_t *sequence = sequence_for_character(typer, character);
|
||||||
if(!sequence) return true;
|
if(!sequence) return true;
|
||||||
|
|
||||||
if(!phase) clear_all_keys();
|
if(!phase) clear_all_keys();
|
||||||
else
|
else {
|
||||||
{
|
|
||||||
set_key_state(sequence[phase - 1], true);
|
set_key_state(sequence[phase - 1], true);
|
||||||
return sequence[phase] == Typer::Delegate::EndSequence;
|
return sequence[phase] == Typer::Delegate::EndSequence;
|
||||||
}
|
}
|
||||||
@@ -89,7 +74,6 @@ bool Typer::Delegate::typer_set_next_character(Utility::Typer *typer, char chara
|
|||||||
return false;
|
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;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,13 +45,11 @@ class Typer {
|
|||||||
|
|
||||||
class TypeRecipient: public Typer::Delegate {
|
class TypeRecipient: public Typer::Delegate {
|
||||||
public:
|
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));
|
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();
|
clear_all_keys();
|
||||||
typer_.reset();
|
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 {
|
class CRC16 {
|
||||||
public:
|
public:
|
||||||
CRC16(uint16_t polynomial, uint16_t reset_value) :
|
CRC16(uint16_t polynomial, uint16_t reset_value) :
|
||||||
reset_value_(reset_value), value_(reset_value)
|
reset_value_(reset_value), value_(reset_value) {
|
||||||
{
|
for(int c = 0; c < 256; c++) {
|
||||||
for(int c = 0; c < 256; c++)
|
|
||||||
{
|
|
||||||
uint16_t shift_value = (uint16_t)(c << 8);
|
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;
|
uint16_t exclusive_or = (shift_value&0x8000) ? polynomial : 0x0000;
|
||||||
shift_value = (uint16_t)(shift_value << 1) ^ exclusive_or;
|
shift_value = (uint16_t)(shift_value << 1) ^ exclusive_or;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B1D08051E0F7A1100763741 /* TimeTests.mm */; };
|
4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B1D08051E0F7A1100763741 /* TimeTests.mm */; };
|
||||||
4B1E85751D170228001EF87D /* Typer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85731D170228001EF87D /* Typer.cpp */; };
|
4B1E85751D170228001EF87D /* Typer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85731D170228001EF87D /* Typer.cpp */; };
|
||||||
4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85801D176468001EF87D /* 6532Tests.swift */; };
|
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 */; };
|
4B2A332A1DB8544D002876E3 /* MemoryFuzzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A33281DB8544D002876E3 /* MemoryFuzzer.cpp */; };
|
||||||
4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2A332B1DB86821002876E3 /* OricOptions.xib */; };
|
4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2A332B1DB86821002876E3 /* OricOptions.xib */; };
|
||||||
4B2A332F1DB86869002876E3 /* OricOptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A332E1DB86869002876E3 /* OricOptionsPanel.swift */; };
|
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 */; };
|
4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */; };
|
||||||
4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539C1D117D36003C6002 /* CSElectron.mm */; };
|
4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539C1D117D36003C6002 /* CSElectron.mm */; };
|
||||||
4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539E1D117D36003C6002 /* CSVic20.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 */; };
|
4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */; };
|
||||||
4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFDB01DAEF5FF001A68B8 /* Video.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 */; };
|
4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; };
|
||||||
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D9B1C3A070400138695 /* Electron.cpp */; };
|
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D9B1C3A070400138695 /* Electron.cpp */; };
|
||||||
4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B30512B1D989E2200B4FED8 /* Drive.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 */; };
|
4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; };
|
||||||
4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */; };
|
4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */; };
|
||||||
4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7913CA1DFCD80E00175A82 /* Video.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 */; };
|
4B8805F01DCFC99C003085B1 /* Acorn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805EE1DCFC99C003085B1 /* Acorn.cpp */; };
|
||||||
4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F21DCFD22A003085B1 /* Commodore.cpp */; };
|
4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F21DCFD22A003085B1 /* Commodore.cpp */; };
|
||||||
4B8805F71DCFF6C9003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F51DCFF6C9003085B1 /* 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 */; };
|
4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */; };
|
||||||
4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2261DA1DE2D0090D3CE /* NSBundle+DataResource.m */; };
|
4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2261DA1DE2D0090D3CE /* NSBundle+DataResource.m */; };
|
||||||
4B8FE2291DA1EDDF0090D3CE /* ElectronOptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2281DA1EDDF0090D3CE /* ElectronOptionsPanel.swift */; };
|
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 */; };
|
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */; };
|
||||||
4B96F7221D75119A0058BB2D /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B96F7201D75119A0058BB2D /* Tape.cpp */; };
|
4B96F7221D75119A0058BB2D /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B96F7201D75119A0058BB2D /* Tape.cpp */; };
|
||||||
4B9CCDA11DA279CA0098B625 /* Vic20OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9CCDA01DA279CA0098B625 /* Vic20OptionsPanel.swift */; };
|
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 */; };
|
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; };
|
||||||
4BD69F941D98760000243FE1 /* AcornADF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD69F921D98760000243FE1 /* AcornADF.cpp */; };
|
4BD69F941D98760000243FE1 /* AcornADF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD69F921D98760000243FE1 /* AcornADF.cpp */; };
|
||||||
4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE77A2C1D84ADFB00BC3827 /* File.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 */; };
|
4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA525D1DF33323007E74F2 /* Tape.cpp */; };
|
||||||
4BEA52631DF339D7007E74F2 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52611DF339D7007E74F2 /* Speaker.cpp */; };
|
4BEA52631DF339D7007E74F2 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52611DF339D7007E74F2 /* Speaker.cpp */; };
|
||||||
4BEA52661DF3472B007E74F2 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52641DF3472B007E74F2 /* 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 */; };
|
4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; };
|
||||||
4BF1354C1D6D2C300054B2EA /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF1354A1D6D2C300054B2EA /* StaticAnalyser.cpp */; };
|
4BF1354C1D6D2C300054B2EA /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF1354A1D6D2C300054B2EA /* StaticAnalyser.cpp */; };
|
||||||
4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8295B1D8F048B001BAE39 /* MFM.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 */; };
|
4BF829631D8F536B001BAE39 /* SSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829611D8F536B001BAE39 /* SSD.cpp */; };
|
||||||
4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829641D8F732B001BAE39 /* Disk.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 */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@@ -447,7 +453,7 @@
|
|||||||
4B1E85741D170228001EF87D /* Typer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Typer.hpp; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
4BF829681D8F7361001BAE39 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Acorn/File.hpp; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
@@ -995,6 +1022,7 @@
|
|||||||
4B1414631B588A1100E04248 /* Test Binaries */ = {
|
4B1414631B588A1100E04248 /* Test Binaries */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
4B9252CD1E74D28200B76AF1 /* Atari ROMs */,
|
||||||
4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */,
|
4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */,
|
||||||
4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */,
|
4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */,
|
||||||
4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */,
|
4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */,
|
||||||
@@ -1011,6 +1039,18 @@
|
|||||||
path = 6532;
|
path = 6532;
|
||||||
sourceTree = "<group>";
|
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 */ = {
|
4B2409591C45DF85004DA684 /* SignalProcessing */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -1072,10 +1112,14 @@
|
|||||||
children = (
|
children = (
|
||||||
4B2E2D971C3A06EC00138695 /* Atari2600.cpp */,
|
4B2E2D971C3A06EC00138695 /* Atari2600.cpp */,
|
||||||
4BEA52641DF3472B007E74F2 /* Speaker.cpp */,
|
4BEA52641DF3472B007E74F2 /* Speaker.cpp */,
|
||||||
|
4BE7C9161E3D397100A5496D /* TIA.cpp */,
|
||||||
4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */,
|
4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */,
|
||||||
4B2E2D981C3A06EC00138695 /* Atari2600.hpp */,
|
4B2E2D981C3A06EC00138695 /* Atari2600.hpp */,
|
||||||
4BEA52651DF3472B007E74F2 /* Speaker.hpp */,
|
4BEAC08D1E7E0E1A00EE56B2 /* Bus.hpp */,
|
||||||
4BEA52671DF34909007E74F2 /* PIA.hpp */,
|
4BEA52671DF34909007E74F2 /* PIA.hpp */,
|
||||||
|
4BEA52651DF3472B007E74F2 /* Speaker.hpp */,
|
||||||
|
4BE7C9171E3D397100A5496D /* TIA.hpp */,
|
||||||
|
4BEAC0801E7E0DF800EE56B2 /* Cartridges */,
|
||||||
);
|
);
|
||||||
path = Atari2600;
|
path = Atari2600;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1103,7 +1147,6 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4B0CCC411C62D0B3001CAC5F /* CRT */,
|
4B0CCC411C62D0B3001CAC5F /* CRT */,
|
||||||
4B2409531C45AB05004DA684 /* Speaker.cpp */,
|
|
||||||
4B2409541C45AB05004DA684 /* Speaker.hpp */,
|
4B2409541C45AB05004DA684 /* Speaker.hpp */,
|
||||||
);
|
);
|
||||||
name = Outputs;
|
name = Outputs;
|
||||||
@@ -1619,7 +1662,6 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4BB697C61D4B558F00248BDF /* Factors.hpp */,
|
4BB697C61D4B558F00248BDF /* Factors.hpp */,
|
||||||
4BF8295E1D8F3C87001BAE39 /* CRC.cpp */,
|
|
||||||
4BF8295F1D8F3C87001BAE39 /* CRC.hpp */,
|
4BF8295F1D8F3C87001BAE39 /* CRC.hpp */,
|
||||||
);
|
);
|
||||||
name = NumberTheory;
|
name = NumberTheory;
|
||||||
@@ -1693,10 +1735,12 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */,
|
4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */,
|
||||||
|
4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */,
|
||||||
4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */,
|
4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */,
|
||||||
4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */,
|
4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */,
|
||||||
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */,
|
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */,
|
||||||
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */,
|
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */,
|
||||||
|
4B2AF8681E513FC20027EE29 /* TIATests.mm */,
|
||||||
4B1D08051E0F7A1100763741 /* TimeTests.mm */,
|
4B1D08051E0F7A1100763741 /* TimeTests.mm */,
|
||||||
4BB73EB81B587A5100552FC2 /* Info.plist */,
|
4BB73EB81B587A5100552FC2 /* Info.plist */,
|
||||||
4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */,
|
4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */,
|
||||||
@@ -1868,7 +1912,6 @@
|
|||||||
children = (
|
children = (
|
||||||
4BF829641D8F732B001BAE39 /* Disk.cpp */,
|
4BF829641D8F732B001BAE39 /* Disk.cpp */,
|
||||||
4BF829651D8F732B001BAE39 /* Disk.hpp */,
|
4BF829651D8F732B001BAE39 /* Disk.hpp */,
|
||||||
4BF829671D8F7361001BAE39 /* File.cpp */,
|
|
||||||
4BF829681D8F7361001BAE39 /* File.hpp */,
|
4BF829681D8F7361001BAE39 /* File.hpp */,
|
||||||
4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */,
|
4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */,
|
||||||
4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */,
|
4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */,
|
||||||
@@ -1899,11 +1942,32 @@
|
|||||||
4BE5F85A1C3E1C2500C43F01 /* Resources */ = {
|
4BE5F85A1C3E1C2500C43F01 /* Resources */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
4B1EDB411E39A0AC009D6819 /* Icons */,
|
||||||
4BC9DF441D044FCA00F44158 /* ROMImages */,
|
4BC9DF441D044FCA00F44158 /* ROMImages */,
|
||||||
);
|
);
|
||||||
path = Resources;
|
path = Resources;
|
||||||
sourceTree = "<group>";
|
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 */ = {
|
4BEE0A691D72496600532C7B /* Cartridge */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -2052,13 +2116,18 @@
|
|||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
4B2C45421E3C3896002A2389 /* cartridge.png in Resources */,
|
||||||
4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */,
|
4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */,
|
||||||
|
4B79E4451E3AF38600141F11 /* floppy35.png in Resources */,
|
||||||
|
4B1EDB451E39A0AC009D6819 /* chip.png in Resources */,
|
||||||
4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */,
|
4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */,
|
||||||
4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */,
|
4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */,
|
||||||
4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */,
|
4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */,
|
||||||
|
4B79E4441E3AF38600141F11 /* cassette.png in Resources */,
|
||||||
4B8FE21E1DA19D5F0090D3CE /* Vic20Options.xib in Resources */,
|
4B8FE21E1DA19D5F0090D3CE /* Vic20Options.xib in Resources */,
|
||||||
4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */,
|
4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */,
|
||||||
4B8FE21D1DA19D5F0090D3CE /* ElectronOptions.xib in Resources */,
|
4B8FE21D1DA19D5F0090D3CE /* ElectronOptions.xib in Resources */,
|
||||||
|
4B79E4461E3AF38600141F11 /* floppy525.png in Resources */,
|
||||||
4BC9DF451D044FCA00F44158 /* ROMImages in Resources */,
|
4BC9DF451D044FCA00F44158 /* ROMImages in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -2189,6 +2258,7 @@
|
|||||||
4BB299661B587D8400A49093 /* inszx in Resources */,
|
4BB299661B587D8400A49093 /* inszx in Resources */,
|
||||||
4BB299101B587D8400A49093 /* asoz in Resources */,
|
4BB299101B587D8400A49093 /* asoz in Resources */,
|
||||||
4BB2998B1B587D8400A49093 /* lseiy in Resources */,
|
4BB2998B1B587D8400A49093 /* lseiy in Resources */,
|
||||||
|
4B9252CE1E74D28200B76AF1 /* Atari ROMs in Resources */,
|
||||||
4BB2997D1B587D8400A49093 /* ldxay in Resources */,
|
4BB2997D1B587D8400A49093 /* ldxay in Resources */,
|
||||||
4BB299D71B587D8400A49093 /* staax in Resources */,
|
4BB299D71B587D8400A49093 /* staax in Resources */,
|
||||||
4BB2990C1B587D8400A49093 /* asoax in Resources */,
|
4BB2990C1B587D8400A49093 /* asoax in Resources */,
|
||||||
@@ -2366,7 +2436,6 @@
|
|||||||
4BC8A62D1DCE60E000DAC693 /* Typer.cpp in Sources */,
|
4BC8A62D1DCE60E000DAC693 /* Typer.cpp in Sources */,
|
||||||
4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */,
|
4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */,
|
||||||
4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */,
|
4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */,
|
||||||
4BF829601D8F3C87001BAE39 /* CRC.cpp in Sources */,
|
|
||||||
4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */,
|
4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */,
|
||||||
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
|
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
|
||||||
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */,
|
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */,
|
||||||
@@ -2389,7 +2458,6 @@
|
|||||||
4BC5E4921D7ED365008CF980 /* StaticAnalyser.cpp in Sources */,
|
4BC5E4921D7ED365008CF980 /* StaticAnalyser.cpp in Sources */,
|
||||||
4BC830D11D6E7C690000A26F /* Tape.cpp in Sources */,
|
4BC830D11D6E7C690000A26F /* Tape.cpp in Sources */,
|
||||||
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */,
|
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */,
|
||||||
4BF829691D8F7361001BAE39 /* File.cpp in Sources */,
|
|
||||||
4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */,
|
4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */,
|
||||||
4B3F1B461E0388D200DB26EE /* PCMPatchedTrack.cpp in Sources */,
|
4B3F1B461E0388D200DB26EE /* PCMPatchedTrack.cpp in Sources */,
|
||||||
4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */,
|
4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */,
|
||||||
@@ -2399,13 +2467,13 @@
|
|||||||
4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */,
|
4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */,
|
||||||
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */,
|
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */,
|
||||||
4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */,
|
4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */,
|
||||||
4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */,
|
|
||||||
4BCF1FA81DADC5250039D2E7 /* CSOric.mm in Sources */,
|
4BCF1FA81DADC5250039D2E7 /* CSOric.mm in Sources */,
|
||||||
4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */,
|
4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */,
|
||||||
4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */,
|
4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */,
|
||||||
4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */,
|
4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */,
|
||||||
4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */,
|
4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */,
|
||||||
4B5A12571DD55862007A2231 /* Disassembler6502.cpp in Sources */,
|
4B5A12571DD55862007A2231 /* Disassembler6502.cpp in Sources */,
|
||||||
|
4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */,
|
||||||
4B1E85751D170228001EF87D /* Typer.cpp in Sources */,
|
4B1E85751D170228001EF87D /* Typer.cpp in Sources */,
|
||||||
4BF829631D8F536B001BAE39 /* SSD.cpp in Sources */,
|
4BF829631D8F536B001BAE39 /* SSD.cpp in Sources */,
|
||||||
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */,
|
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */,
|
||||||
@@ -2459,7 +2527,9 @@
|
|||||||
4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */,
|
4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */,
|
||||||
4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */,
|
4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */,
|
||||||
4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */,
|
4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */,
|
||||||
|
4B924E991E74D22700B76AF1 /* AtariStaticAnalyserTests.mm in Sources */,
|
||||||
4B50730A1DDFCFDF00C48FBD /* ArrayBuilderTests.mm in Sources */,
|
4B50730A1DDFCFDF00C48FBD /* ArrayBuilderTests.mm in Sources */,
|
||||||
|
4B2AF8691E513FC20027EE29 /* TIATests.mm in Sources */,
|
||||||
4B3BA0CE1D318B44005DD7A7 /* C1540Bridge.mm in Sources */,
|
4B3BA0CE1D318B44005DD7A7 /* C1540Bridge.mm in Sources */,
|
||||||
4B3BA0D11D318B44005DD7A7 /* TestMachine.mm in Sources */,
|
4B3BA0D11D318B44005DD7A7 /* TestMachine.mm in Sources */,
|
||||||
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */,
|
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */,
|
||||||
|
|||||||
@@ -26,7 +26,8 @@
|
|||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
codeCoverageEnabled = "YES">
|
||||||
<Testables>
|
<Testables>
|
||||||
<TestableReference
|
<TestableReference
|
||||||
skipped = "NO">
|
skipped = "NO">
|
||||||
|
|||||||
@@ -12,32 +12,48 @@
|
|||||||
#define AudioQueueBufferMaxLength 8192
|
#define AudioQueueBufferMaxLength 8192
|
||||||
#define NumberOfStoredAudioQueueBuffer 16
|
#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
|
@implementation CSAudioQueue
|
||||||
{
|
{
|
||||||
AudioQueueRef _audioQueue;
|
AudioQueueRef _audioQueue;
|
||||||
|
|
||||||
AudioQueueBufferRef _storedBuffers[NumberOfStoredAudioQueueBuffer];
|
AudioQueueBufferRef _storedBuffers[NumberOfStoredAudioQueueBuffer];
|
||||||
|
NSLock *_storedBuffersLock;
|
||||||
|
CSWeakAudioQueuePointer *_weakPointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - AudioQueue callbacks
|
#pragma mark - AudioQueue callbacks
|
||||||
|
|
||||||
- (void)audioQueue:(AudioQueueRef)theAudioQueue didCallbackWithBuffer:(AudioQueueBufferRef)buffer
|
/*!
|
||||||
{
|
@returns @c YES if the queue is running dry; @c NO otherwise.
|
||||||
[self.delegate audioQueueIsRunningDry:self];
|
*/
|
||||||
|
- (BOOL)audioQueue:(AudioQueueRef)theAudioQueue didCallbackWithBuffer:(AudioQueueBufferRef)buffer
|
||||||
@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]);
|
if(_storedBuffers[c]) AudioQueueFreeBuffer(_audioQueue, _storedBuffers[c]);
|
||||||
_storedBuffers[c] = buffer;
|
_storedBuffers[c] = buffer;
|
||||||
return;
|
[_storedBuffersLock unlock];
|
||||||
}
|
return YES;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
[_storedBuffersLock unlock];
|
||||||
AudioQueueFreeBuffer(_audioQueue, buffer);
|
AudioQueueFreeBuffer(_audioQueue, buffer);
|
||||||
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void audioOutputCallback(
|
static void audioOutputCallback(
|
||||||
@@ -45,7 +61,17 @@ static void audioOutputCallback(
|
|||||||
AudioQueueRef inAQ,
|
AudioQueueRef inAQ,
|
||||||
AudioQueueBufferRef inBuffer)
|
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
|
#pragma mark - Standard object lifecycle
|
||||||
@@ -56,6 +82,12 @@ static void audioOutputCallback(
|
|||||||
|
|
||||||
if(self)
|
if(self)
|
||||||
{
|
{
|
||||||
|
if(!CSAudioQueueDeallocLock)
|
||||||
|
{
|
||||||
|
CSAudioQueueDeallocLock = [[NSLock alloc] init];
|
||||||
|
}
|
||||||
|
_storedBuffersLock = [[NSLock alloc] init];
|
||||||
|
|
||||||
_samplingRate = samplingRate;
|
_samplingRate = samplingRate;
|
||||||
|
|
||||||
// determine preferred buffer sizes
|
// determine preferred buffer sizes
|
||||||
@@ -80,11 +112,13 @@ static void audioOutputCallback(
|
|||||||
|
|
||||||
outputDescription.mReserved = 0;
|
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(
|
if(!AudioQueueNewOutput(
|
||||||
&outputDescription,
|
&outputDescription,
|
||||||
audioOutputCallback,
|
audioOutputCallback,
|
||||||
(__bridge void *)(self),
|
(__bridge void *)(_weakPointer),
|
||||||
NULL,
|
NULL,
|
||||||
kCFRunLoopCommonModes,
|
kCFRunLoopCommonModes,
|
||||||
0,
|
0,
|
||||||
@@ -104,7 +138,31 @@ static void audioOutputCallback(
|
|||||||
|
|
||||||
- (void)dealloc
|
- (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
|
#pragma mark - Audio enqueuer
|
||||||
@@ -113,8 +171,7 @@ static void audioOutputCallback(
|
|||||||
{
|
{
|
||||||
size_t bufferBytes = lengthInSamples * sizeof(int16_t);
|
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)
|
||||||
@@ -124,9 +181,11 @@ static void audioOutputCallback(
|
|||||||
|
|
||||||
AudioQueueEnqueueBuffer(_audioQueue, _storedBuffers[c], 0, NULL);
|
AudioQueueEnqueueBuffer(_audioQueue, _storedBuffers[c], 0, NULL);
|
||||||
_storedBuffers[c] = NULL;
|
_storedBuffers[c] = NULL;
|
||||||
|
[_storedBuffersLock unlock];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
[_storedBuffersLock unlock];
|
||||||
|
|
||||||
AudioQueueBufferRef newBuffer;
|
AudioQueueBufferRef newBuffer;
|
||||||
AudioQueueAllocateBuffer(_audioQueue, (UInt32)bufferBytes * 2, &newBuffer);
|
AudioQueueAllocateBuffer(_audioQueue, (UInt32)bufferBytes * 2, &newBuffer);
|
||||||
@@ -135,7 +194,6 @@ static void audioOutputCallback(
|
|||||||
|
|
||||||
AudioQueueEnqueueBuffer(_audioQueue, newBuffer, 0, NULL);
|
AudioQueueEnqueueBuffer(_audioQueue, newBuffer, 0, NULL);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Sampling Rate getters
|
#pragma mark - Sampling Rate getters
|
||||||
|
|
||||||
|
|||||||
@@ -31,24 +31,14 @@ class MachineDocument:
|
|||||||
return NSSize(width: 4.0, height: 3.0)
|
return NSSize(width: 4.0, height: 3.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBOutlet weak var openGLView: CSOpenGLView! {
|
@IBOutlet weak var openGLView: CSOpenGLView!
|
||||||
didSet {
|
|
||||||
openGLView.delegate = self
|
|
||||||
openGLView.responderDelegate = self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@IBOutlet var optionsPanel: MachinePanel!
|
@IBOutlet var optionsPanel: MachinePanel!
|
||||||
@IBAction func showOptions(_ sender: AnyObject!) {
|
@IBAction func showOptions(_ sender: AnyObject!) {
|
||||||
optionsPanel?.setIsVisible(true)
|
optionsPanel?.setIsVisible(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate var audioQueue: CSAudioQueue! = nil
|
fileprivate var audioQueue: CSAudioQueue! = nil
|
||||||
fileprivate lazy var bestEffortUpdater: CSBestEffortUpdater = {
|
fileprivate var bestEffortUpdater: CSBestEffortUpdater!
|
||||||
let updater = CSBestEffortUpdater()
|
|
||||||
updater.delegate = self
|
|
||||||
return updater
|
|
||||||
}()
|
|
||||||
|
|
||||||
override var windowNibName: String? {
|
override var windowNibName: String? {
|
||||||
return "MachineDocument"
|
return "MachineDocument"
|
||||||
@@ -64,12 +54,22 @@ class MachineDocument:
|
|||||||
self.machine.setView(self.openGLView, aspectRatio: Float(displayAspectRatio.width / displayAspectRatio.height))
|
self.machine.setView(self.openGLView, aspectRatio: Float(displayAspectRatio.width / displayAspectRatio.height))
|
||||||
})
|
})
|
||||||
|
|
||||||
setupClockRate()
|
|
||||||
self.machine.delegate = self
|
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()
|
self.optionsPanel?.establishStoredOptions()
|
||||||
|
|
||||||
// bring OpenGL view-holding window on top of the options panel
|
// bring OpenGL view-holding window on top of the options panel
|
||||||
self.openGLView.window!.makeKeyAndOrderFront(self)
|
self.openGLView.window!.makeKeyAndOrderFront(self)
|
||||||
|
|
||||||
|
// start accepting best effort updates
|
||||||
|
self.bestEffortUpdater.delegate = self
|
||||||
}
|
}
|
||||||
|
|
||||||
func machineDidChangeClockRate(_ machine: CSMachine!) {
|
func machineDidChangeClockRate(_ machine: CSMachine!) {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<string>bin</string>
|
<string>bin</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleTypeIconFile</key>
|
<key>CFBundleTypeIconFile</key>
|
||||||
<string></string>
|
<string>cartridge</string>
|
||||||
<key>CFBundleTypeName</key>
|
<key>CFBundleTypeName</key>
|
||||||
<string>Atari 2600 Cartridge</string>
|
<string>Atari 2600 Cartridge</string>
|
||||||
<key>CFBundleTypeOSTypes</key>
|
<key>CFBundleTypeOSTypes</key>
|
||||||
@@ -27,27 +27,13 @@
|
|||||||
<key>NSDocumentClass</key>
|
<key>NSDocumentClass</key>
|
||||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||||
</dict>
|
</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>
|
<dict>
|
||||||
<key>CFBundleTypeExtensions</key>
|
<key>CFBundleTypeExtensions</key>
|
||||||
<array>
|
<array>
|
||||||
<string>rom</string>
|
<string>rom</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>CFBundleTypeIconFile</key>
|
||||||
|
<string>chip</string>
|
||||||
<key>CFBundleTypeName</key>
|
<key>CFBundleTypeName</key>
|
||||||
<string>ROM Image</string>
|
<string>ROM Image</string>
|
||||||
<key>CFBundleTypeRole</key>
|
<key>CFBundleTypeRole</key>
|
||||||
@@ -65,6 +51,8 @@
|
|||||||
<string>uef</string>
|
<string>uef</string>
|
||||||
<string>uef.gz</string>
|
<string>uef.gz</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>CFBundleTypeIconFile</key>
|
||||||
|
<string>cassette</string>
|
||||||
<key>CFBundleTypeName</key>
|
<key>CFBundleTypeName</key>
|
||||||
<string>Electron/BBC UEF Image</string>
|
<string>Electron/BBC UEF Image</string>
|
||||||
<key>CFBundleTypeRole</key>
|
<key>CFBundleTypeRole</key>
|
||||||
@@ -79,6 +67,8 @@
|
|||||||
<array>
|
<array>
|
||||||
<string>prg</string>
|
<string>prg</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>CFBundleTypeIconFile</key>
|
||||||
|
<string>floppy525</string>
|
||||||
<key>CFBundleTypeName</key>
|
<key>CFBundleTypeName</key>
|
||||||
<string>Commodore Program</string>
|
<string>Commodore Program</string>
|
||||||
<key>CFBundleTypeRole</key>
|
<key>CFBundleTypeRole</key>
|
||||||
@@ -93,6 +83,8 @@
|
|||||||
<array>
|
<array>
|
||||||
<string>tap</string>
|
<string>tap</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>CFBundleTypeIconFile</key>
|
||||||
|
<string>cassette</string>
|
||||||
<key>CFBundleTypeName</key>
|
<key>CFBundleTypeName</key>
|
||||||
<string>Tape Image</string>
|
<string>Tape Image</string>
|
||||||
<key>CFBundleTypeRole</key>
|
<key>CFBundleTypeRole</key>
|
||||||
@@ -107,6 +99,8 @@
|
|||||||
<array>
|
<array>
|
||||||
<string>g64</string>
|
<string>g64</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>CFBundleTypeIconFile</key>
|
||||||
|
<string>floppy525</string>
|
||||||
<key>CFBundleTypeName</key>
|
<key>CFBundleTypeName</key>
|
||||||
<string>Commodore Disk</string>
|
<string>Commodore Disk</string>
|
||||||
<key>CFBundleTypeRole</key>
|
<key>CFBundleTypeRole</key>
|
||||||
@@ -121,6 +115,8 @@
|
|||||||
<array>
|
<array>
|
||||||
<string>d64</string>
|
<string>d64</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>CFBundleTypeIconFile</key>
|
||||||
|
<string>floppy525</string>
|
||||||
<key>CFBundleTypeName</key>
|
<key>CFBundleTypeName</key>
|
||||||
<string>Commodore 1540/1 Disk</string>
|
<string>Commodore 1540/1 Disk</string>
|
||||||
<key>CFBundleTypeRole</key>
|
<key>CFBundleTypeRole</key>
|
||||||
@@ -139,6 +135,8 @@
|
|||||||
<string>adl</string>
|
<string>adl</string>
|
||||||
<string>adm</string>
|
<string>adm</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>CFBundleTypeIconFile</key>
|
||||||
|
<string>floppy35</string>
|
||||||
<key>CFBundleTypeName</key>
|
<key>CFBundleTypeName</key>
|
||||||
<string>Electron/BBC Disk Image</string>
|
<string>Electron/BBC Disk Image</string>
|
||||||
<key>CFBundleTypeRole</key>
|
<key>CFBundleTypeRole</key>
|
||||||
@@ -151,6 +149,8 @@
|
|||||||
<array>
|
<array>
|
||||||
<string>dsk</string>
|
<string>dsk</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>CFBundleTypeIconFile</key>
|
||||||
|
<string>floppy35</string>
|
||||||
<key>CFBundleTypeName</key>
|
<key>CFBundleTypeName</key>
|
||||||
<string>Disk Image</string>
|
<string>Disk Image</string>
|
||||||
<key>CFBundleTypeRole</key>
|
<key>CFBundleTypeRole</key>
|
||||||
|
|||||||
@@ -11,42 +11,8 @@
|
|||||||
#include "Atari2600.hpp"
|
#include "Atari2600.hpp"
|
||||||
#import "CSMachine+Subclassing.h"
|
#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 {
|
@implementation CSAtari2600 {
|
||||||
Atari2600::Machine _atari2600;
|
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 {
|
- (void)setDirection:(CSJoystickDirection)direction onPad:(NSUInteger)pad isPressed:(BOOL)isPressed {
|
||||||
@@ -78,8 +44,6 @@ struct CRTDelegate: public Outputs::CRT::Delegate {
|
|||||||
- (void)setupOutputWithAspectRatio:(float)aspectRatio {
|
- (void)setupOutputWithAspectRatio:(float)aspectRatio {
|
||||||
@synchronized(self) {
|
@synchronized(self) {
|
||||||
[super setupOutputWithAspectRatio:aspectRatio];
|
[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
|
@interface CSOpenGLView : NSOpenGLView
|
||||||
|
|
||||||
@property (nonatomic, weak, nullable) id <CSOpenGLViewDelegate> delegate;
|
@property (atomic, weak, nullable) id <CSOpenGLViewDelegate> delegate;
|
||||||
@property (nonatomic, weak, nullable) id <CSOpenGLViewResponderDelegate> responderDelegate;
|
@property (nonatomic, weak, nullable) id <CSOpenGLViewResponderDelegate> responderDelegate;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|||||||
@@ -21,14 +21,25 @@ class MOS6532Tests: XCTestCase {
|
|||||||
with6532 {
|
with6532 {
|
||||||
// set a count of 128 at single-clock intervals
|
// set a count of 128 at single-clock intervals
|
||||||
$0.setValue(128, forRegister:0x14)
|
$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)
|
$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
|
// run for 127 clocks and the timer should be zero, but the timer flag will not yet be set
|
||||||
$0.run(forCycles: 200)
|
$0.run(forCycles: 127)
|
||||||
XCTAssert($0.value(forRegister: 4) == 183, "Timer should underflow and keep counting")
|
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 {
|
with6532 {
|
||||||
// set a count of 28 at eight-clock intervals
|
// set a count of 28 at eight-clock intervals
|
||||||
$0.setValue(28, forRegister:0x15)
|
$0.setValue(28, forRegister:0x15)
|
||||||
|
XCTAssertEqual($0.value(forRegister: 4), 28)
|
||||||
|
|
||||||
// run for seven clock and the count should still be 28
|
// one further cycle and the timer should hit 27
|
||||||
$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
|
|
||||||
$0.run(forCycles: 1)
|
$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
|
// run for seven clock and the count should still be 27
|
||||||
$0.run(forCycles: 228)
|
$0.run(forCycles: 7)
|
||||||
XCTAssert($0.value(forRegister: 4) == 0xfb, "Timer should underflow and start counting at single-clock pace")
|
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
|
// timer should now resume dividing by eight
|
||||||
$0.run(forCycles: 7)
|
$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)
|
$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 {
|
with6532 {
|
||||||
// set a count of 1 at single-clock intervals
|
// set a count of 1 at single-clock intervals
|
||||||
$0.setValue(1, forRegister:0x1c)
|
$0.setValue(1, forRegister:0x1c)
|
||||||
|
|
||||||
// run for one clock and the count should now be zero
|
|
||||||
$0.run(forCycles: 1)
|
$0.run(forCycles: 1)
|
||||||
|
|
||||||
// interrupt shouldn't be signalled yet, bit should not be set
|
// 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;
|
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);
|
openGL_output_builder_.set_colour_format(colour_space, colour_cycle_numerator, colour_cycle_denominator);
|
||||||
|
|
||||||
const unsigned int syncCapacityLineChargeThreshold = 2;
|
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."
|
// for horizontal retrace and 500 to 750 µs for vertical retrace in NTSC and PAL TV."
|
||||||
|
|
||||||
time_multiplier_ = IntermediateBufferWidth / cycles_per_line;
|
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;
|
phase_numerator_ = 0;
|
||||||
colour_cycle_numerator_ = colour_cycle_numerator * time_multiplier_;
|
colour_cycle_numerator_ = colour_cycle_numerator;
|
||||||
phase_alternates_ = should_alternate;
|
phase_alternates_ = should_alternate;
|
||||||
is_alernate_line_ &= phase_alternates_;
|
is_alernate_line_ &= phase_alternates_;
|
||||||
|
cycles_per_line_ = cycles_per_line;
|
||||||
unsigned int multiplied_cycles_per_line = cycles_per_line * time_multiplier_;
|
unsigned int multiplied_cycles_per_line = cycles_per_line * time_multiplier_;
|
||||||
|
|
||||||
// generate timing values implied by the given arbuments
|
// generate timing values implied by the given arguments
|
||||||
sync_capacitor_charge_threshold_ = (int)(syncCapacityLineChargeThreshold * multiplied_cycles_per_line);
|
sync_capacitor_charge_threshold_ = ((int)(syncCapacityLineChargeThreshold * cycles_per_line) * 3) / 4;
|
||||||
|
|
||||||
// create the two flywheels
|
// create the two flywheels
|
||||||
horizontal_flywheel_.reset(new Flywheel(multiplied_cycles_per_line, (millisecondsHorizontalRetraceTime * multiplied_cycles_per_line) >> 6, multiplied_cycles_per_line >> 6));
|
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_);
|
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)
|
void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType displayType) {
|
||||||
{
|
switch(displayType) {
|
||||||
switch(displayType)
|
|
||||||
{
|
|
||||||
case DisplayType::PAL50:
|
case DisplayType::PAL50:
|
||||||
set_new_timing(cycles_per_line, 312, ColourSpace::YUV, 709379, 2500, true); // i.e. 283.7516
|
set_new_timing(cycles_per_line, 312, ColourSpace::YUV, 709379, 2500, true); // i.e. 283.7516
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DisplayType::NTSC60:
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,26 +74,22 @@ CRT::CRT(unsigned int common_output_divisor, unsigned int buffer_depth) :
|
|||||||
is_alernate_line_(false) {}
|
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::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);
|
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::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);
|
set_new_display_type(cycles_per_line, displayType);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Sync loop
|
#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);
|
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);
|
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_output_position_x2() (*(uint16_t *)&next_run[SourceVertexOffsetOfEnds + 2])
|
||||||
#define source_phase() next_run[SourceVertexOffsetOfPhaseTimeAndAmplitude + 0]
|
#define source_phase() next_run[SourceVertexOffsetOfPhaseTimeAndAmplitude + 0]
|
||||||
#define source_amplitude() next_run[SourceVertexOffsetOfPhaseTimeAndAmplitude + 2]
|
#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();
|
std::unique_lock<std::mutex> output_lock = openGL_output_builder_.get_output_lock();
|
||||||
number_of_cycles *= time_multiplier_;
|
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());
|
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;
|
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);
|
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
|
// 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_output_position_x1() = (uint16_t)horizontal_flywheel_->get_current_output_position();
|
||||||
source_phase() = colour_burst_phase_;
|
source_phase() = colour_burst_phase_;
|
||||||
source_amplitude() = colour_burst_amplitude_;
|
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
|
// decrement the number of cycles left to run for and increment the
|
||||||
// horizontal counter appropriately
|
// horizontal counter appropriately
|
||||||
number_of_cycles -= next_run_length;
|
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...
|
// 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);
|
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);
|
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();
|
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(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(
|
if(
|
||||||
!openGL_output_builder_.array_builder.is_full() &&
|
!openGL_output_builder_.array_builder.is_full() &&
|
||||||
!openGL_output_builder_.composite_output_buffer_is_full())
|
!openGL_output_builder_.composite_output_buffer_is_full()) {
|
||||||
{
|
|
||||||
if(!is_writing_composite_run_)
|
if(!is_writing_composite_run_) {
|
||||||
{
|
|
||||||
output_run_.x1 = (uint16_t)horizontal_flywheel_->get_current_output_position();
|
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_);
|
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
|
// Get and write all those previously unwritten output ys
|
||||||
const uint16_t output_y = openGL_output_builder_.get_composite_output_y();
|
const uint16_t output_y = openGL_output_builder_.get_composite_output_y();
|
||||||
|
|
||||||
// Construct the output run
|
// Construct the output run
|
||||||
uint8_t *next_run = openGL_output_builder_.array_builder.get_output_storage(OutputVertexSize);
|
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_x1() = output_run_.x1;
|
||||||
output_position_y() = output_run_.y;
|
output_position_y() = output_run_.y;
|
||||||
output_tex_y() = output_y;
|
output_tex_y() = output_y;
|
||||||
output_x2() = (uint16_t)horizontal_flywheel_->get_current_output_position();
|
output_x2() = (uint16_t)horizontal_flywheel_->get_current_output_position();
|
||||||
}
|
}
|
||||||
openGL_output_builder_.array_builder.flush(
|
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(
|
openGL_output_builder_.texture_builder.flush(
|
||||||
[output_y, input_buffer] (const std::vector<TextureBuilder::WriteArea> &write_areas, size_t number_of_write_areas)
|
[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++) {
|
||||||
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 + 0] = write_areas[run].x;
|
||||||
*(uint16_t *)&input_buffer[run * SourceVertexSize + SourceVertexOffsetOfInputStart + 2] = write_areas[run].y;
|
*(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;
|
*(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;
|
(*(uint16_t *)&input_buffer[position + SourceVertexOffsetOfOutputStart + 2]) = output_y;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
colour_burst_amplitude_ = 0;
|
||||||
}
|
}
|
||||||
is_writing_composite_run_ ^= true;
|
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();
|
openGL_output_builder_.increment_composite_output_y();
|
||||||
}
|
}
|
||||||
|
|
||||||
// if this is vertical retrace then adcance a field
|
// 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(next_run_length == time_until_vertical_sync_event && next_vertical_sync_event == Flywheel::SyncEvent::EndRetrace) {
|
||||||
{
|
if(delegate_) {
|
||||||
if(delegate_)
|
|
||||||
{
|
|
||||||
frames_since_last_delegate_call_++;
|
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());
|
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;
|
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
|
#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 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);
|
const bool is_leading_edge = (!is_receiving_sync_ && this_is_sync);
|
||||||
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
|
// 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
|
// 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
|
// 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.
|
// 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 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
|
// simplified colour burst logic: if it's within the back porch we'll take it
|
||||||
if(scan->type == Scan::Type::ColourBurst)
|
if(scan->type == Scan::Type::ColourBurst) {
|
||||||
{
|
if(!colour_burst_amplitude_ && horizontal_flywheel_->get_current_time() < (horizontal_flywheel_->get_standard_period() * 12) >> 6) {
|
||||||
if(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_time_ = (uint16_t)horizontal_flywheel_->get_current_time();
|
|
||||||
colour_burst_phase_ = scan->phase;
|
|
||||||
colour_burst_amplitude_ = scan->amplitude;
|
colour_burst_amplitude_ = scan->amplitude;
|
||||||
|
|
||||||
|
colour_burst_phase_ = (colour_burst_phase_ & ~63) + 32;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: inspect raw data for potential colour burst if required
|
// TODO: inspect raw data for potential colour burst if required
|
||||||
|
|
||||||
sync_period_ = is_receiving_sync_ ? (sync_period_ + scan->number_of_cycles) : 0;
|
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
|
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{
|
Scan scan{
|
||||||
.type = Scan::Type::Sync,
|
.type = Scan::Type::Sync,
|
||||||
.number_of_cycles = number_of_cycles
|
.number_of_cycles = number_of_cycles
|
||||||
@@ -310,8 +293,7 @@ void CRT::output_sync(unsigned int number_of_cycles)
|
|||||||
output_scan(&scan);
|
output_scan(&scan);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CRT::output_blank(unsigned int number_of_cycles)
|
void CRT::output_blank(unsigned int number_of_cycles) {
|
||||||
{
|
|
||||||
Scan scan {
|
Scan scan {
|
||||||
.type = Scan::Type::Blank,
|
.type = Scan::Type::Blank,
|
||||||
.number_of_cycles = number_of_cycles
|
.number_of_cycles = number_of_cycles
|
||||||
@@ -319,8 +301,7 @@ void CRT::output_blank(unsigned int number_of_cycles)
|
|||||||
output_scan(&scan);
|
output_scan(&scan);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CRT::output_level(unsigned int number_of_cycles)
|
void CRT::output_level(unsigned int number_of_cycles) {
|
||||||
{
|
|
||||||
Scan scan {
|
Scan scan {
|
||||||
.type = Scan::Type::Level,
|
.type = Scan::Type::Level,
|
||||||
.number_of_cycles = number_of_cycles,
|
.number_of_cycles = number_of_cycles,
|
||||||
@@ -328,8 +309,7 @@ void CRT::output_level(unsigned int number_of_cycles)
|
|||||||
output_scan(&scan);
|
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 {
|
Scan scan {
|
||||||
.type = Scan::Type::ColourBurst,
|
.type = Scan::Type::ColourBurst,
|
||||||
.number_of_cycles = number_of_cycles,
|
.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);
|
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 {
|
Scan scan {
|
||||||
.type = Scan::Type::ColourBurst,
|
.type = Scan::Type::ColourBurst,
|
||||||
.number_of_cycles = number_of_cycles,
|
.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
|
.amplitude = 32
|
||||||
};
|
};
|
||||||
output_scan(&scan);
|
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);
|
openGL_output_builder_.texture_builder.reduce_previous_allocation_to(number_of_cycles / source_divider);
|
||||||
Scan scan {
|
Scan scan {
|
||||||
.type = Scan::Type::Data,
|
.type = Scan::Type::Data,
|
||||||
@@ -360,8 +338,7 @@ void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider
|
|||||||
output_scan(&scan);
|
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_;
|
first_cycle_after_sync *= time_multiplier_;
|
||||||
number_of_cycles *= 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
|
// adjust to ensure aspect ratio is correct
|
||||||
float adjusted_aspect_ratio = (3.0f*aspect_ratio / 4.0f);
|
float adjusted_aspect_ratio = (3.0f*aspect_ratio / 4.0f);
|
||||||
float ideal_width = height * adjusted_aspect_ratio;
|
float ideal_width = height * adjusted_aspect_ratio;
|
||||||
if(ideal_width > width)
|
if(ideal_width > width) {
|
||||||
{
|
|
||||||
start_x -= (ideal_width - width) * 0.5f;
|
start_x -= (ideal_width - width) * 0.5f;
|
||||||
width = ideal_width;
|
width = ideal_width;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
float ideal_height = width / adjusted_aspect_ratio;
|
float ideal_height = width / adjusted_aspect_ratio;
|
||||||
start_y -= (ideal_height - height) * 0.5f;
|
start_y -= (ideal_height - height) * 0.5f;
|
||||||
height = ideal_height;
|
height = ideal_height;
|
||||||
|
|||||||
@@ -60,14 +60,13 @@ class CRT {
|
|||||||
void output_scan(const Scan *scan);
|
void output_scan(const Scan *scan);
|
||||||
|
|
||||||
uint8_t colour_burst_phase_, colour_burst_amplitude_;
|
uint8_t colour_burst_phase_, colour_burst_amplitude_;
|
||||||
uint16_t colour_burst_time_;
|
|
||||||
bool is_writing_composite_run_;
|
bool is_writing_composite_run_;
|
||||||
|
|
||||||
unsigned int phase_denominator_, phase_numerator_, colour_cycle_numerator_;
|
unsigned int phase_denominator_, phase_numerator_, colour_cycle_numerator_;
|
||||||
bool is_alernate_line_, phase_alternates_;
|
bool is_alernate_line_, phase_alternates_;
|
||||||
|
|
||||||
// the outer entry point for dispatching output_sync, output_blank, output_level and output_data
|
// 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 inner entry point that determines whether and when the next sync event will occur within
|
||||||
// the current output window
|
// the current output window
|
||||||
@@ -82,10 +81,23 @@ class CRT {
|
|||||||
uint16_t x1, y;
|
uint16_t x1, y;
|
||||||
} output_run_;
|
} output_run_;
|
||||||
|
|
||||||
// The delegate
|
// the delegate
|
||||||
Delegate *delegate_;
|
Delegate *delegate_;
|
||||||
unsigned int frames_since_last_delegate_call_;
|
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:
|
public:
|
||||||
/*! Constructs the CRT with a specified clock rate, height and colour subcarrier frequency.
|
/*! 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,
|
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.
|
@param required_length The number of samples to allocate.
|
||||||
@returns A pointer to the allocated area if room is available; @c nullptr otherwise.
|
@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();
|
std::unique_lock<std::mutex> output_lock = openGL_output_builder_.get_output_lock();
|
||||||
return openGL_output_builder_.texture_builder.allocate_write_area(required_length);
|
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.
|
/*! 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.
|
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);
|
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
|
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.
|
@c false then the references are simply marked as invalid.
|
||||||
*/
|
*/
|
||||||
inline void set_openGL_context_will_change(bool 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);
|
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.
|
/*! 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
|
that evaluates to the composite signal level as a function of a source buffer, sampling location, colour
|
||||||
carrier phase and amplitude.
|
carrier phase and amplitude.
|
||||||
*/
|
*/
|
||||||
inline void set_composite_sampling_function(const char *shader)
|
inline void set_composite_sampling_function(const std::string &shader) {
|
||||||
{
|
enqueue_openGL_function([shader, this] {
|
||||||
openGL_output_builder_.set_composite_sampling_function(shader);
|
openGL_output_builder_.set_composite_sampling_function(shader);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Sets a function that will map from whatever data the machine provided to an RGB signal.
|
/*! 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 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.
|
* `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)
|
inline void set_rgb_sampling_function(const std::string &shader) {
|
||||||
{
|
enqueue_openGL_function([shader, this] {
|
||||||
openGL_output_builder_.set_rgb_sampling_function(shader);
|
openGL_output_builder_.set_rgb_sampling_function(shader);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void set_output_device(OutputDevice output_device)
|
inline void set_output_device(OutputDevice output_device) {
|
||||||
{
|
enqueue_openGL_function([output_device, this] {
|
||||||
openGL_output_builder_.set_output_device(output_device);
|
openGL_output_builder_.set_output_device(output_device);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void set_visible_area(Rect visible_area)
|
inline void set_visible_area(Rect visible_area) {
|
||||||
{
|
enqueue_openGL_function([visible_area, this] {
|
||||||
openGL_output_builder_.set_visible_area(visible_area);
|
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);
|
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;
|
delegate_ = delegate;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,36 +12,29 @@ using namespace Outputs::CRT;
|
|||||||
|
|
||||||
ArrayBuilder::ArrayBuilder(size_t input_size, size_t output_size) :
|
ArrayBuilder::ArrayBuilder(size_t input_size, size_t output_size) :
|
||||||
output_(output_size, nullptr),
|
output_(output_size, nullptr),
|
||||||
input_(input_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) :
|
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),
|
output_(output_size, submission_function),
|
||||||
input_(input_size, submission_function)
|
input_(input_size, submission_function) {}
|
||||||
{}
|
|
||||||
|
|
||||||
bool ArrayBuilder::is_full()
|
bool ArrayBuilder::is_full() {
|
||||||
{
|
|
||||||
bool was_full;
|
bool was_full;
|
||||||
was_full = is_full_;
|
was_full = is_full_;
|
||||||
return was_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_);
|
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_);
|
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)
|
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_) {
|
||||||
if(!is_full_)
|
size_t input_size = 0, output_size = 0;
|
||||||
{
|
|
||||||
size_t input_size, output_size;
|
|
||||||
uint8_t *input = input_.get_unflushed(input_size);
|
uint8_t *input = input_.get_unflushed(input_size);
|
||||||
uint8_t *output = output_.get_unflushed(output_size);
|
uint8_t *output = output_.get_unflushed(output_size);
|
||||||
function(input, input_size, output, 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();
|
input_.bind();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArrayBuilder::bind_output()
|
void ArrayBuilder::bind_output() {
|
||||||
{
|
|
||||||
output_.bind();
|
output_.bind();
|
||||||
}
|
}
|
||||||
|
|
||||||
ArrayBuilder::Submission ArrayBuilder::submit()
|
ArrayBuilder::Submission ArrayBuilder::submit() {
|
||||||
{
|
|
||||||
ArrayBuilder::Submission submission;
|
ArrayBuilder::Submission submission;
|
||||||
|
|
||||||
submission.input_size = input_.submit(true);
|
submission.input_size = input_.submit(true);
|
||||||
submission.output_size = output_.submit(false);
|
submission.output_size = output_.submit(false);
|
||||||
if(is_full_)
|
if(is_full_) {
|
||||||
{
|
|
||||||
is_full_ = false;
|
is_full_ = false;
|
||||||
input_.reset();
|
input_.reset();
|
||||||
output_.reset();
|
output_.reset();
|
||||||
@@ -80,10 +69,8 @@ ArrayBuilder::Submission ArrayBuilder::submit()
|
|||||||
ArrayBuilder::Buffer::Buffer(size_t size, std::function<void(bool is_input, uint8_t *, size_t)> submission_function) :
|
ArrayBuilder::Buffer::Buffer(size_t size, std::function<void(bool is_input, uint8_t *, size_t)> submission_function) :
|
||||||
is_full(false),
|
is_full(false),
|
||||||
submission_function_(submission_function),
|
submission_function_(submission_function),
|
||||||
allocated_data(0), flushed_data(0), submitted_data(0)
|
allocated_data(0), flushed_data(0), submitted_data(0) {
|
||||||
{
|
if(!submission_function_) {
|
||||||
if(!submission_function_)
|
|
||||||
{
|
|
||||||
glGenBuffers(1, &buffer);
|
glGenBuffers(1, &buffer);
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, buffer);
|
glBindBuffer(GL_ARRAY_BUFFER, buffer);
|
||||||
glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)size, NULL, GL_STREAM_DRAW);
|
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);
|
data.resize(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
ArrayBuilder::Buffer::~Buffer()
|
ArrayBuilder::Buffer::~Buffer() {
|
||||||
{
|
|
||||||
if(!submission_function_)
|
if(!submission_function_)
|
||||||
glDeleteBuffers(1, &buffer);
|
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);
|
uint8_t *pointer = buffer.get_storage(size);
|
||||||
if(!pointer) is_full_ = true;
|
if(!pointer) is_full_ = true;
|
||||||
return pointer;
|
return pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t *ArrayBuilder::Buffer::get_storage(size_t size)
|
uint8_t *ArrayBuilder::Buffer::get_storage(size_t size) {
|
||||||
{
|
if(is_full || allocated_data + size > data.size()) {
|
||||||
if(is_full || allocated_data + size > data.size())
|
|
||||||
{
|
|
||||||
is_full = true;
|
is_full = true;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -116,21 +99,17 @@ uint8_t *ArrayBuilder::Buffer::get_storage(size_t size)
|
|||||||
return pointer;
|
return pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t *ArrayBuilder::Buffer::get_unflushed(size_t &size)
|
uint8_t *ArrayBuilder::Buffer::get_unflushed(size_t &size) {
|
||||||
{
|
if(is_full) {
|
||||||
if(is_full)
|
|
||||||
{
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
size = allocated_data - flushed_data;
|
size = allocated_data - flushed_data;
|
||||||
return &data[flushed_data];
|
return &data[flushed_data];
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArrayBuilder::Buffer::flush()
|
void ArrayBuilder::Buffer::flush() {
|
||||||
{
|
if(submitted_data) {
|
||||||
if(submitted_data)
|
memmove(data.data(), &data[submitted_data], allocated_data - submitted_data);
|
||||||
{
|
|
||||||
memcpy(data.data(), &data[submitted_data], allocated_data - submitted_data);
|
|
||||||
allocated_data -= submitted_data;
|
allocated_data -= submitted_data;
|
||||||
flushed_data -= submitted_data;
|
flushed_data -= submitted_data;
|
||||||
submitted_data = 0;
|
submitted_data = 0;
|
||||||
@@ -139,13 +118,11 @@ void ArrayBuilder::Buffer::flush()
|
|||||||
flushed_data = allocated_data;
|
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;
|
size_t length = flushed_data;
|
||||||
if(submission_function_)
|
if(submission_function_) {
|
||||||
submission_function_(is_input, data.data(), length);
|
submission_function_(is_input, data.data(), length);
|
||||||
else
|
} else {
|
||||||
{
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, buffer);
|
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);
|
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);
|
memcpy(destination, data.data(), length);
|
||||||
@@ -156,13 +133,11 @@ size_t ArrayBuilder::Buffer::submit(bool is_input)
|
|||||||
return length;
|
return length;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArrayBuilder::Buffer::bind()
|
void ArrayBuilder::Buffer::bind() {
|
||||||
{
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, buffer);
|
glBindBuffer(GL_ARRAY_BUFFER, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArrayBuilder::Buffer::reset()
|
void ArrayBuilder::Buffer::reset() {
|
||||||
{
|
|
||||||
is_full = false;
|
is_full = false;
|
||||||
allocated_data = 0;
|
allocated_data = 0;
|
||||||
flushed_data = 0;
|
flushed_data = 0;
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ const GLsizei InputBufferBuilderWidth = 2048;
|
|||||||
const GLsizei InputBufferBuilderHeight = 512;
|
const GLsizei InputBufferBuilderHeight = 512;
|
||||||
|
|
||||||
// This is the size of the intermediate buffers used during composite to RGB conversion
|
// 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;
|
const GLsizei IntermediateBufferHeight = 512;
|
||||||
|
|
||||||
// Some internal buffer sizes
|
// Some internal buffer sizes
|
||||||
|
|||||||
@@ -6,8 +6,9 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#include "CRT.hpp"
|
#include "CRT.hpp"
|
||||||
#include <stdlib.h>
|
|
||||||
#include <math.h>
|
#include <cstdlib>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
#include "CRTOpenGL.hpp"
|
#include "CRTOpenGL.hpp"
|
||||||
#include "../../../SignalProcessing/FIRFilter.hpp"
|
#include "../../../SignalProcessing/FIRFilter.hpp"
|
||||||
@@ -16,29 +17,24 @@
|
|||||||
using namespace Outputs::CRT;
|
using namespace Outputs::CRT;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
static const GLenum composite_texture_unit = GL_TEXTURE0;
|
static const GLenum source_data_texture_unit = GL_TEXTURE0;
|
||||||
static const GLenum separated_texture_unit = GL_TEXTURE1;
|
static const GLenum pixel_accumulation_texture_unit = GL_TEXTURE1;
|
||||||
static const GLenum filtered_y_texture_unit = GL_TEXTURE2;
|
|
||||||
static const GLenum filtered_texture_unit = GL_TEXTURE3;
|
static const GLenum composite_texture_unit = GL_TEXTURE2;
|
||||||
static const GLenum source_data_texture_unit = GL_TEXTURE4;
|
static const GLenum separated_texture_unit = GL_TEXTURE3;
|
||||||
static const GLenum pixel_accumulation_texture_unit = GL_TEXTURE5;
|
static const GLenum filtered_texture_unit = GL_TEXTURE4;
|
||||||
|
|
||||||
|
static const GLenum work_texture_unit = GL_TEXTURE2;
|
||||||
}
|
}
|
||||||
|
|
||||||
OpenGLOutputBuilder::OpenGLOutputBuilder(size_t bytes_per_pixel) :
|
OpenGLOutputBuilder::OpenGLOutputBuilder(size_t bytes_per_pixel) :
|
||||||
visible_area_(Rect(0, 0, 1, 1)),
|
visible_area_(Rect(0, 0, 1, 1)),
|
||||||
composite_src_output_y_(0),
|
composite_src_output_y_(0),
|
||||||
composite_shader_(nullptr),
|
|
||||||
rgb_shader_(nullptr),
|
|
||||||
last_output_width_(0),
|
last_output_width_(0),
|
||||||
last_output_height_(0),
|
last_output_height_(0),
|
||||||
fence_(nullptr),
|
fence_(nullptr),
|
||||||
texture_builder(bytes_per_pixel, source_data_texture_unit),
|
texture_builder(bytes_per_pixel, source_data_texture_unit),
|
||||||
array_builder(SourceVertexBufferDataSize, OutputVertexBufferDataSize),
|
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)
|
|
||||||
{
|
|
||||||
glBlendFunc(GL_SRC_ALPHA, GL_CONSTANT_COLOR);
|
glBlendFunc(GL_SRC_ALPHA, GL_CONSTANT_COLOR);
|
||||||
glBlendColor(0.6f, 0.6f, 0.6f, 1.0f);
|
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
|
// create the source vertex array
|
||||||
glGenVertexArrays(1, &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_);
|
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
|
// lock down any other draw_frames
|
||||||
draw_mutex_.lock();
|
draw_mutex_.lock();
|
||||||
|
|
||||||
// establish essentials
|
// establish essentials
|
||||||
if(!output_shader_program_)
|
if(!output_shader_program_) {
|
||||||
{
|
|
||||||
prepare_composite_input_shaders();
|
prepare_composite_input_shaders();
|
||||||
prepare_rgb_input_shaders();
|
prepare_rgb_input_shaders();
|
||||||
prepare_source_vertex_array();
|
prepare_source_vertex_array();
|
||||||
@@ -76,11 +91,9 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
|
|||||||
set_colour_space_uniforms();
|
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 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();
|
draw_mutex_.unlock();
|
||||||
return;
|
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
|
// make sure there's a target to draw to
|
||||||
if(!framebuffer_ || framebuffer_->get_height() != output_height || framebuffer_->get_width() != output_width)
|
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));
|
||||||
std::unique_ptr<OpenGL::TextureTarget> new_framebuffer(new OpenGL::TextureTarget((GLsizei)output_width, (GLsizei)output_height, pixel_accumulation_texture_unit));
|
if(framebuffer_) {
|
||||||
if(framebuffer_)
|
|
||||||
{
|
|
||||||
new_framebuffer->bind_framebuffer();
|
new_framebuffer->bind_framebuffer();
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
@@ -123,55 +134,62 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
|
|||||||
output_mutex_.unlock();
|
output_mutex_.unlock();
|
||||||
|
|
||||||
struct RenderStage {
|
struct RenderStage {
|
||||||
OpenGL::TextureTarget *const target;
|
|
||||||
OpenGL::Shader *const shader;
|
OpenGL::Shader *const shader;
|
||||||
|
OpenGL::TextureTarget *const target;
|
||||||
float clear_colour[3];
|
float clear_colour[3];
|
||||||
};
|
};
|
||||||
|
|
||||||
// for composite video, go through four steps to get to something that can be painted to the output
|
// for composite video, go through four steps to get to something that can be painted to the output
|
||||||
RenderStage composite_render_stages[] =
|
RenderStage composite_render_stages[] = {
|
||||||
{
|
{composite_input_shader_program_.get(), composite_texture_.get(), {0.0, 0.0, 0.0}},
|
||||||
{&composite_texture_, composite_input_shader_program_.get(), {0.0, 0.0, 0.0}},
|
{composite_separation_filter_program_.get(), separated_texture_.get(), {0.0, 0.5, 0.5}},
|
||||||
{&separated_texture_, composite_separation_filter_program_.get(), {0.0, 0.5, 0.5}},
|
{composite_chrominance_filter_shader_program_.get(), filtered_texture_.get(), {0.0, 0.0, 0.0}},
|
||||||
{&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}},
|
|
||||||
{nullptr}
|
{nullptr}
|
||||||
};
|
};
|
||||||
|
|
||||||
// for RGB video, there's only two steps
|
// for RGB video, there's only two steps
|
||||||
RenderStage rgb_render_stages[] =
|
RenderStage rgb_render_stages[] = {
|
||||||
{
|
{rgb_input_shader_program_.get(), composite_texture_.get(), {0.0, 0.0, 0.0}},
|
||||||
{&composite_texture_, rgb_input_shader_program_.get(), {0.0, 0.0, 0.0}},
|
{rgb_filter_shader_program_.get(), filtered_texture_.get(), {0.0, 0.0, 0.0}},
|
||||||
{&filtered_texture_, rgb_filter_shader_program_.get(), {0.0, 0.0, 0.0}},
|
|
||||||
{nullptr}
|
{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
|
// all drawing will be from the source vertex array and without blending
|
||||||
glBindVertexArray(source_vertex_array_);
|
glBindVertexArray(source_vertex_array_);
|
||||||
glDisable(GL_BLEND);
|
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
|
// switch to the framebuffer and shader associated with this stage
|
||||||
active_pipeline->shader->bind();
|
active_pipeline->shader->bind();
|
||||||
|
|
||||||
|
if(!work_texture_) {
|
||||||
active_pipeline->target->bind_framebuffer();
|
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
|
// 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
|
// those portions for which no input was provided
|
||||||
if(!active_pipeline[1].target)
|
// if(!active_pipeline[1].shader) {
|
||||||
{
|
|
||||||
glClearColor(active_pipeline->clear_colour[0], active_pipeline->clear_colour[1], active_pipeline->clear_colour[2], 1.0f);
|
glClearColor(active_pipeline->clear_colour[0], active_pipeline->clear_colour[1], active_pipeline->clear_colour[2], 1.0f);
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw
|
// 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++;
|
active_pipeline++;
|
||||||
|
#ifdef GL_NV_texture_barrier
|
||||||
|
glTextureBarrierNV();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepare to transfer to framebuffer
|
// prepare to transfer to framebuffer
|
||||||
@@ -182,8 +200,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
|
|||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
|
|
||||||
// update uniforms, then bind the target
|
// 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_);
|
output_shader_program_->set_output_size(output_width, output_height, visible_area_);
|
||||||
last_output_width_ = output_width;
|
last_output_width_ = output_width;
|
||||||
last_output_height_ = output_height;
|
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);
|
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
|
// copy framebuffer to the intended place
|
||||||
glDisable(GL_BLEND);
|
glDisable(GL_BLEND);
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
@@ -207,11 +228,9 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
|
|||||||
draw_mutex_.unlock();
|
draw_mutex_.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLOutputBuilder::reset_all_OpenGL_state()
|
void OpenGLOutputBuilder::reset_all_OpenGL_state() {
|
||||||
{
|
|
||||||
composite_input_shader_program_ = nullptr;
|
composite_input_shader_program_ = nullptr;
|
||||||
composite_separation_filter_program_ = nullptr;
|
composite_separation_filter_program_ = nullptr;
|
||||||
composite_y_filter_shader_program_ = nullptr;
|
|
||||||
composite_chrominance_filter_shader_program_ = nullptr;
|
composite_chrominance_filter_shader_program_ = nullptr;
|
||||||
rgb_input_shader_program_ = nullptr;
|
rgb_input_shader_program_ = nullptr;
|
||||||
rgb_filter_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;
|
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();
|
output_mutex_.lock();
|
||||||
reset_all_OpenGL_state();
|
reset_all_OpenGL_state();
|
||||||
output_mutex_.unlock();
|
output_mutex_.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLOutputBuilder::set_composite_sampling_function(const char *shader)
|
void OpenGLOutputBuilder::set_composite_sampling_function(const std::string &shader) {
|
||||||
{
|
std::lock_guard<std::mutex> lock_guard(output_mutex_);
|
||||||
output_mutex_.lock();
|
composite_shader_ = shader;
|
||||||
composite_shader_ = strdup(shader);
|
|
||||||
reset_all_OpenGL_state();
|
reset_all_OpenGL_state();
|
||||||
output_mutex_.unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLOutputBuilder::set_rgb_sampling_function(const char *shader)
|
void OpenGLOutputBuilder::set_rgb_sampling_function(const std::string &shader) {
|
||||||
{
|
std::lock_guard<std::mutex> lock_guard(output_mutex_);
|
||||||
output_mutex_.lock();
|
rgb_shader_ = shader;
|
||||||
rgb_shader_ = strdup(shader);
|
|
||||||
reset_all_OpenGL_state();
|
reset_all_OpenGL_state();
|
||||||
output_mutex_.unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Program compilation
|
#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_ = 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_source_texture_unit(source_data_texture_unit);
|
||||||
composite_input_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
|
composite_input_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
|
||||||
|
|
||||||
composite_separation_filter_program_ = OpenGL::IntermediateShader::make_chroma_luma_separation_shader();
|
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_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_ = 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);
|
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()
|
void OpenGLOutputBuilder::prepare_rgb_input_shaders() {
|
||||||
{
|
if(rgb_shader_.size()) {
|
||||||
if(rgb_shader_)
|
|
||||||
{
|
|
||||||
rgb_input_shader_program_ = OpenGL::IntermediateShader::make_rgb_source_shader(rgb_shader_);
|
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_source_texture_unit(source_data_texture_unit);
|
||||||
rgb_input_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
|
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()
|
void OpenGLOutputBuilder::prepare_source_vertex_array() {
|
||||||
{
|
if(composite_input_shader_program_) {
|
||||||
if(composite_input_shader_program_)
|
|
||||||
{
|
|
||||||
glBindVertexArray(source_vertex_array_);
|
glBindVertexArray(source_vertex_array_);
|
||||||
array_builder.bind_input();
|
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_ = 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()
|
void OpenGLOutputBuilder::prepare_output_vertex_array() {
|
||||||
{
|
if(output_shader_program_) {
|
||||||
if(output_shader_program_)
|
|
||||||
{
|
|
||||||
glBindVertexArray(output_vertex_array_);
|
glBindVertexArray(output_vertex_array_);
|
||||||
array_builder.bind_output();
|
array_builder.bind_output();
|
||||||
output_shader_program_->enable_vertex_attribute_with_pointer("horizontal", 2, GL_UNSIGNED_SHORT, GL_FALSE, OutputVertexSize, (void *)OutputVertexOffsetOfHorizontal, 1);
|
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
|
#pragma mark - Public Configuration
|
||||||
|
|
||||||
void OpenGLOutputBuilder::set_output_device(OutputDevice output_device)
|
void OpenGLOutputBuilder::set_output_device(OutputDevice output_device) {
|
||||||
{
|
if(output_device_ != output_device) {
|
||||||
if(output_device_ != output_device)
|
|
||||||
{
|
|
||||||
output_device_ = output_device;
|
output_device_ = output_device;
|
||||||
composite_src_output_y_ = 0;
|
composite_src_output_y_ = 0;
|
||||||
last_output_width_ = 0;
|
last_output_width_ = 0;
|
||||||
last_output_height_ = 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();
|
output_mutex_.lock();
|
||||||
input_frequency_ = input_frequency;
|
input_frequency_ = input_frequency;
|
||||||
cycles_per_line_ = cycles_per_line;
|
cycles_per_line_ = cycles_per_line;
|
||||||
@@ -338,8 +350,7 @@ void OpenGLOutputBuilder::set_timing(unsigned int input_frequency, unsigned int
|
|||||||
|
|
||||||
#pragma mark - Internal Configuration
|
#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 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};
|
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;
|
GLfloat *fromRGB, *toRGB;
|
||||||
|
|
||||||
switch(colour_space_)
|
switch(colour_space_) {
|
||||||
{
|
|
||||||
case ColourSpace::YIQ:
|
case ColourSpace::YIQ:
|
||||||
fromRGB = rgbToYIQ;
|
fromRGB = rgbToYIQ;
|
||||||
toRGB = yiqToRGB;
|
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_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);
|
if(composite_chrominance_filter_shader_program_) composite_chrominance_filter_shader_program_->set_colour_conversion_matrices(fromRGB, toRGB);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLOutputBuilder::set_timing_uniforms()
|
float OpenGLOutputBuilder::get_composite_output_width() const {
|
||||||
{
|
return ((float)colour_cycle_numerator_ * 4.0f) / (float)(colour_cycle_denominator_ * IntermediateBufferWidth);
|
||||||
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_);
|
void OpenGLOutputBuilder::set_output_shader_width() {
|
||||||
|
if(output_shader_program_) {
|
||||||
float colour_subcarrier_frequency = (float)colour_cycle_numerator_ / (float)colour_cycle_denominator_;
|
const float width = get_is_television_output() ? get_composite_output_width() : 1.0f;
|
||||||
if(composite_separation_filter_program_) composite_separation_filter_program_->set_separation_frequency(cycles_per_line_, colour_subcarrier_frequency);
|
output_shader_program_->set_input_width_scaler(width);
|
||||||
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);
|
|
||||||
|
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_;
|
Rect visible_area_;
|
||||||
|
|
||||||
// Other things the caller may have provided.
|
// Other things the caller may have provided.
|
||||||
char *composite_shader_;
|
std::string composite_shader_;
|
||||||
char *rgb_shader_;
|
std::string rgb_shader_;
|
||||||
|
|
||||||
// Methods used by the OpenGL code
|
// Methods used by the OpenGL code
|
||||||
void prepare_output_shader();
|
void prepare_output_shader();
|
||||||
@@ -66,13 +66,19 @@ class OpenGLOutputBuilder {
|
|||||||
GLsizei composite_src_output_y_;
|
GLsizei composite_src_output_y_;
|
||||||
|
|
||||||
std::unique_ptr<OpenGL::OutputShader> output_shader_program_;
|
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
|
std::unique_ptr<OpenGL::IntermediateShader> composite_input_shader_program_;
|
||||||
OpenGL::TextureTarget separated_texture_; // receives unfiltered Y in the R channel plus unfiltered but demodulated chrominance in G and B
|
std::unique_ptr<OpenGL::IntermediateShader> composite_separation_filter_program_;
|
||||||
OpenGL::TextureTarget filtered_y_texture_; // receives filtered Y in the R channel plus unfiltered chrominance in G and B
|
std::unique_ptr<OpenGL::IntermediateShader> composite_chrominance_filter_shader_program_;
|
||||||
OpenGL::TextureTarget filtered_texture_; // receives filtered YIQ or YUV
|
|
||||||
|
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
|
std::unique_ptr<OpenGL::TextureTarget> framebuffer_; // the current pixel output
|
||||||
|
|
||||||
@@ -88,6 +94,9 @@ class OpenGLOutputBuilder {
|
|||||||
void reset_all_OpenGL_state();
|
void reset_all_OpenGL_state();
|
||||||
|
|
||||||
GLsync fence_;
|
GLsync fence_;
|
||||||
|
float get_composite_output_width() const;
|
||||||
|
void set_output_shader_width();
|
||||||
|
bool get_is_television_output();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// These two are protected by output_mutex_.
|
// These two are protected by output_mutex_.
|
||||||
@@ -97,8 +106,7 @@ class OpenGLOutputBuilder {
|
|||||||
OpenGLOutputBuilder(size_t bytes_per_pixel);
|
OpenGLOutputBuilder(size_t bytes_per_pixel);
|
||||||
~OpenGLOutputBuilder();
|
~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_);
|
std::lock_guard<std::mutex> output_guard(output_mutex_);
|
||||||
colour_space_ = colour_space;
|
colour_space_ = colour_space;
|
||||||
colour_cycle_numerator_ = colour_cycle_numerator;
|
colour_cycle_numerator_ = colour_cycle_numerator;
|
||||||
@@ -106,41 +114,35 @@ class OpenGLOutputBuilder {
|
|||||||
set_colour_space_uniforms();
|
set_colour_space_uniforms();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void set_visible_area(Rect visible_area)
|
inline void set_visible_area(Rect visible_area) {
|
||||||
{
|
|
||||||
visible_area_ = 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_);
|
return std::unique_lock<std::mutex>(output_mutex_);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline OutputDevice get_output_device()
|
inline OutputDevice get_output_device() {
|
||||||
{
|
|
||||||
return 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_;
|
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;
|
return composite_src_output_y_ == IntermediateBufferHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void increment_composite_output_y()
|
inline void increment_composite_output_y() {
|
||||||
{
|
|
||||||
if(!composite_output_buffer_is_full())
|
if(!composite_output_buffer_is_full())
|
||||||
composite_src_output_y_++;
|
composite_src_output_y_++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty);
|
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_openGL_context_will_change(bool should_delete_resources);
|
||||||
void set_composite_sampling_function(const char *shader);
|
void set_composite_sampling_function(const std::string &shader);
|
||||||
void set_rgb_sampling_function(const char *shader);
|
void set_rgb_sampling_function(const std::string &shader);
|
||||||
void set_output_device(OutputDevice output_device);
|
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);
|
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.
|
The @c Flywheel will attempt to converge with timing implied by synchronisation pulses.
|
||||||
*/
|
*/
|
||||||
struct Flywheel
|
struct Flywheel {
|
||||||
{
|
|
||||||
/*!
|
/*!
|
||||||
Constructs an instance of @c Flywheel.
|
Constructs an instance of @c Flywheel.
|
||||||
|
|
||||||
@@ -61,26 +60,18 @@ struct Flywheel
|
|||||||
|
|
||||||
@returns The next synchronisation event.
|
@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?
|
// do we recognise this hsync, thereby adjusting future time expectations?
|
||||||
if(sync_is_requested)
|
if(sync_is_requested) {
|
||||||
{
|
if(counter_ < sync_error_window_ || counter_ > expected_next_sync_ - sync_error_window_) {
|
||||||
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_;
|
unsigned int time_now = (counter_ < sync_error_window_) ? expected_next_sync_ + counter_ : counter_;
|
||||||
expected_next_sync_ = (3*expected_next_sync_ + time_now) >> 2;
|
expected_next_sync_ = (3*expected_next_sync_ + time_now) >> 2;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
number_of_surprises_++;
|
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;
|
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;
|
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;
|
unsigned int proposed_sync_time = cycles_to_run_for;
|
||||||
|
|
||||||
// will we end an ongoing retrace?
|
// 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_sync_time = retrace_time_ - counter_;
|
||||||
proposed_event = SyncEvent::EndRetrace;
|
proposed_event = SyncEvent::EndRetrace;
|
||||||
}
|
}
|
||||||
|
|
||||||
// will we start a retrace?
|
// 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_sync_time = expected_next_sync_ - counter_;
|
||||||
proposed_event = SyncEvent::StartRetrace;
|
proposed_event = SyncEvent::StartRetrace;
|
||||||
}
|
}
|
||||||
@@ -115,12 +104,10 @@ struct Flywheel
|
|||||||
|
|
||||||
@param event The synchronisation event to apply after that period.
|
@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;
|
counter_ += cycles_advanced;
|
||||||
|
|
||||||
switch(event)
|
switch(event) {
|
||||||
{
|
|
||||||
default: return;
|
default: return;
|
||||||
case StartRetrace:
|
case StartRetrace:
|
||||||
counter_before_retrace_ = counter_ - retrace_time_;
|
counter_before_retrace_ = counter_ - retrace_time_;
|
||||||
@@ -135,10 +122,8 @@ struct Flywheel
|
|||||||
|
|
||||||
@returns The current output position.
|
@returns The current output position.
|
||||||
*/
|
*/
|
||||||
inline unsigned int get_current_output_position()
|
inline unsigned int get_current_output_position() {
|
||||||
{
|
if(counter_ < retrace_time_) {
|
||||||
if(counter_ < retrace_time_)
|
|
||||||
{
|
|
||||||
unsigned int retrace_distance = (counter_ * standard_period_) / retrace_time_;
|
unsigned int retrace_distance = (counter_ * standard_period_) / retrace_time_;
|
||||||
if(retrace_distance > counter_before_retrace_) return 0;
|
if(retrace_distance > counter_before_retrace_) return 0;
|
||||||
return counter_before_retrace_ - retrace_distance;
|
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.
|
@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_;
|
return counter_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@returns whether the output is currently retracing.
|
@returns whether the output is currently retracing.
|
||||||
*/
|
*/
|
||||||
inline bool is_in_retrace()
|
inline bool is_in_retrace() {
|
||||||
{
|
|
||||||
return counter_ < retrace_time_;
|
return counter_ < retrace_time_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@returns the expected length of the scan period (excluding retrace).
|
@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_;
|
return standard_period_ - retrace_time_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@returns the expected length of a complete scan and retrace cycle.
|
@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_;
|
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;
|
@returns the number of synchronisation events that have seemed surprising since the last time this method was called;
|
||||||
a low number indicates good synchronisation.
|
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_;
|
unsigned int result = number_of_surprises_;
|
||||||
number_of_surprises_ = 0;
|
number_of_surprises_ = 0;
|
||||||
return result;
|
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.
|
@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;
|
return abs((int)counter_ - (int)expected_next_sync_) < (int)standard_period_ / 50;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#else
|
#else
|
||||||
#include <OpenGL/OpenGL.h>
|
#include <OpenGL/OpenGL.h>
|
||||||
#include <OpenGL/gl3.h>
|
#include <OpenGL/gl3.h>
|
||||||
|
#include <OpenGL/gl3ext.h>
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,7 @@
|
|||||||
using namespace OpenGL;
|
using namespace OpenGL;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
const OpenGL::Shader::AttributeBinding bindings[] =
|
const OpenGL::Shader::AttributeBinding bindings[] = {
|
||||||
{
|
|
||||||
{"inputPosition", 0},
|
{"inputPosition", 0},
|
||||||
{"outputPosition", 1},
|
{"outputPosition", 1},
|
||||||
{"phaseAndAmplitude", 2},
|
{"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 *sampler_type = use_usampler ? "usampler2D" : "sampler2D";
|
||||||
const char *input_variable = input_is_inputPosition ? "inputPosition" : "outputPosition";
|
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 vec2 ends;"
|
||||||
"in vec3 phaseTimeAndAmplitude;"
|
"in vec3 phaseTimeAndAmplitude;"
|
||||||
|
|
||||||
"uniform float phaseCyclesPerTick;"
|
|
||||||
"uniform ivec2 outputTextureSize;"
|
"uniform ivec2 outputTextureSize;"
|
||||||
"uniform float extension;"
|
"uniform float extension;"
|
||||||
"uniform %s texID;"
|
"uniform %s texID;"
|
||||||
"uniform float offsets[5];"
|
"uniform float offsets[5];"
|
||||||
|
"uniform vec2 widthScalers;"
|
||||||
|
"uniform float inputVerticalOffset;"
|
||||||
|
"uniform float outputVerticalOffset;"
|
||||||
|
"uniform float textureHeightDivisor;"
|
||||||
|
|
||||||
"out vec2 phaseAndAmplitudeVarying;"
|
"out vec2 phaseAndAmplitudeVarying;"
|
||||||
"out vec2 inputPositionsVarying[11];"
|
"out vec2 inputPositionsVarying[11];"
|
||||||
@@ -53,35 +54,51 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const char *
|
|||||||
|
|
||||||
"void main(void)"
|
"void main(void)"
|
||||||
"{"
|
"{"
|
||||||
|
// odd vertices are on the left, even on the right
|
||||||
"float extent = float(gl_VertexID & 1);"
|
"float extent = float(gl_VertexID & 1);"
|
||||||
|
"float longitudinal = float((gl_VertexID & 2) >> 1);"
|
||||||
|
|
||||||
"vec2 inputPosition = vec2(mix(inputStart.x, ends.x, extent), inputStart.y);"
|
// inputPosition.x is either inputStart.x or ends.x, depending on whether it is on the left or the right;
|
||||||
"vec2 outputPosition = vec2(mix(outputStart.x, ends.y, extent), outputStart.y);"
|
// 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);"
|
"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 extendedInputPosition = %s + extensionVector;"
|
||||||
"vec2 extendedOutputPosition = outputPosition + extensionVector;"
|
"vec2 extendedOutputPosition = outputPosition + extensionVector;"
|
||||||
|
|
||||||
|
// keep iInputPositionVarying in whole source pixels, scale mappedInputPosition to the ordinary normalised range
|
||||||
"vec2 textureSize = vec2(textureSize(texID, 0));"
|
"vec2 textureSize = vec2(textureSize(texID, 0));"
|
||||||
"iInputPositionVarying = extendedInputPosition;"
|
"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);"
|
// setup input positions spaced as per the supplied offsets; these are for filtering where required
|
||||||
"inputPositionsVarying[1] = mappedInputPosition - (vec2(offsets[1], 0.0) / textureSize);"
|
"inputPositionsVarying[0] = mappedInputPosition - (vec2(5.0, 0.0) / textureSize);"
|
||||||
"inputPositionsVarying[2] = mappedInputPosition - (vec2(offsets[2], 0.0) / textureSize);"
|
"inputPositionsVarying[1] = mappedInputPosition - (vec2(4.0, 0.0) / textureSize);"
|
||||||
"inputPositionsVarying[3] = mappedInputPosition - (vec2(offsets[3], 0.0) / textureSize);"
|
"inputPositionsVarying[2] = mappedInputPosition - (vec2(3.0, 0.0) / textureSize);"
|
||||||
"inputPositionsVarying[4] = mappedInputPosition - (vec2(offsets[4], 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[5] = mappedInputPosition;"
|
||||||
"inputPositionsVarying[6] = mappedInputPosition + (vec2(offsets[4], 0.0) / textureSize);"
|
"inputPositionsVarying[6] = mappedInputPosition + (vec2(1.0, 0.0) / textureSize);"
|
||||||
"inputPositionsVarying[7] = mappedInputPosition + (vec2(offsets[3], 0.0) / textureSize);"
|
"inputPositionsVarying[7] = mappedInputPosition + (vec2(2.0, 0.0) / textureSize);"
|
||||||
"inputPositionsVarying[8] = mappedInputPosition + (vec2(offsets[2], 0.0) / textureSize);"
|
"inputPositionsVarying[8] = mappedInputPosition + (vec2(3.0, 0.0) / textureSize);"
|
||||||
"inputPositionsVarying[9] = mappedInputPosition + (vec2(offsets[1], 0.0) / textureSize);"
|
"inputPositionsVarying[9] = mappedInputPosition + (vec2(4.0, 0.0) / textureSize);"
|
||||||
"inputPositionsVarying[10] = mappedInputPosition + (vec2(offsets[0], 0.0) / textureSize);"
|
"inputPositionsVarying[10] = mappedInputPosition + (vec2(5.0, 0.0) / textureSize);"
|
||||||
"delayLinePositionVarying = mappedInputPosition - vec2(0.0, 1.0);"
|
"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)
|
"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;"
|
"vec2 eyePosition = 2.0*(extendedOutputPosition / outputTextureSize) - vec2(1.0) + vec2(1.0)/outputTextureSize;"
|
||||||
"gl_Position = vec4(eyePosition, 0.0, 1.0);"
|
"gl_Position = vec4(eyePosition, 0.0, 1.0);"
|
||||||
"}", sampler_type, input_variable);
|
"}", sampler_type, input_variable);
|
||||||
@@ -92,12 +109,11 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const char *
|
|||||||
return shader;
|
return shader;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_source_conversion_shader(const char *composite_shader, const char *rgb_shader)
|
std::unique_ptr<IntermediateShader> IntermediateShader::make_source_conversion_shader(const std::string &composite_shader, const std::string &rgb_shader) {
|
||||||
{
|
char *derived_composite_sample = nullptr;
|
||||||
char *composite_sample = (char *)composite_shader;
|
const char *composite_sample = composite_shader.c_str();
|
||||||
if(!composite_sample)
|
if(!composite_shader.size()) {
|
||||||
{
|
asprintf(&derived_composite_sample,
|
||||||
asprintf(&composite_sample,
|
|
||||||
"%s\n"
|
"%s\n"
|
||||||
"uniform mat3 rgbToLumaChroma;"
|
"uniform mat3 rgbToLumaChroma;"
|
||||||
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
|
"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;"
|
"vec2 quadrature = vec2(cos(phase), -sin(phase)) * amplitude;"
|
||||||
"return dot(lumaChromaColour, vec3(1.0 - amplitude, quadrature));"
|
"return dot(lumaChromaColour, vec3(1.0 - amplitude, quadrature));"
|
||||||
"}",
|
"}",
|
||||||
rgb_shader);
|
rgb_shader.c_str());
|
||||||
|
composite_sample = derived_composite_sample;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *fragment_shader;
|
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));"
|
"fragColour = vec4(composite_sample(texID, inputPositionsVarying[5], iInputPositionVarying, phaseAndAmplitudeVarying.x, phaseAndAmplitudeVarying.y));"
|
||||||
"}"
|
"}"
|
||||||
, composite_sample);
|
, composite_sample);
|
||||||
if(!composite_shader) free(composite_sample);
|
free(derived_composite_sample);
|
||||||
|
|
||||||
std::unique_ptr<IntermediateShader> shader = make_shader(fragment_shader, true, true);
|
std::unique_ptr<IntermediateShader> shader = make_shader(fragment_shader, true, true);
|
||||||
free(fragment_shader);
|
free(fragment_shader);
|
||||||
@@ -137,8 +154,7 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_source_conversion_s
|
|||||||
return shader;
|
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;
|
char *fragment_shader;
|
||||||
asprintf(&fragment_shader,
|
asprintf(&fragment_shader,
|
||||||
"#version 150\n"
|
"#version 150\n"
|
||||||
@@ -157,7 +173,7 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_source_shader(c
|
|||||||
"{"
|
"{"
|
||||||
"fragColour = rgb_sample(texID, inputPositionsVarying[5], iInputPositionVarying);"
|
"fragColour = rgb_sample(texID, inputPositionsVarying[5], iInputPositionVarying);"
|
||||||
"}"
|
"}"
|
||||||
, rgb_shader);
|
, rgb_shader.c_str());
|
||||||
|
|
||||||
std::unique_ptr<IntermediateShader> shader = make_shader(fragment_shader, true, true);
|
std::unique_ptr<IntermediateShader> shader = make_shader(fragment_shader, true, true);
|
||||||
free(fragment_shader);
|
free(fragment_shader);
|
||||||
@@ -165,14 +181,12 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_source_shader(c
|
|||||||
return shader;
|
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(
|
return make_shader(
|
||||||
"#version 150\n"
|
"#version 150\n"
|
||||||
|
|
||||||
"in vec2 phaseAndAmplitudeVarying;"
|
"in vec2 phaseAndAmplitudeVarying;"
|
||||||
"in vec2 inputPositionsVarying[11];"
|
"in vec2 inputPositionsVarying[11];"
|
||||||
"uniform vec4 weights[3];"
|
|
||||||
|
|
||||||
"out vec3 fragColour;"
|
"out vec3 fragColour;"
|
||||||
|
|
||||||
@@ -180,44 +194,26 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_luma_separat
|
|||||||
|
|
||||||
"void main(void)"
|
"void main(void)"
|
||||||
"{"
|
"{"
|
||||||
"vec4 samples[3] = vec4[]("
|
"vec4 samples = vec4("
|
||||||
"vec4("
|
"texture(texID, inputPositionsVarying[3]).r,"
|
||||||
"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[4]).r,"
|
||||||
"texture(texID, inputPositionsVarying[5]).r,"
|
"texture(texID, inputPositionsVarying[5]).r,"
|
||||||
"texture(texID, inputPositionsVarying[6]).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"
|
|
||||||
")"
|
|
||||||
");"
|
");"
|
||||||
|
"float luminance = dot(samples, vec4(0.25));"
|
||||||
|
|
||||||
"float luminance = "
|
// define chroma to be whatever was here, minus luma
|
||||||
"dot(vec3("
|
"float chrominance = 0.5 * (samples.z - luminance) / phaseAndAmplitudeVarying.y;"
|
||||||
"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;"
|
|
||||||
"luminance /= (1.0 - 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));"
|
"vec2 quadrature = vec2(cos(phaseAndAmplitudeVarying.x), -sin(phaseAndAmplitudeVarying.x));"
|
||||||
"fragColour = vec3(luminance, vec2(0.5) + (chrominance * quadrature));"
|
"fragColour = vec3(luminance, vec2(0.5) + (chrominance * quadrature));"
|
||||||
"}",false, false);
|
"}",false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_filter_shader()
|
std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_filter_shader() {
|
||||||
{
|
|
||||||
return make_shader(
|
return make_shader(
|
||||||
"#version 150\n"
|
"#version 150\n"
|
||||||
|
|
||||||
@@ -232,41 +228,18 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_filter_shade
|
|||||||
"void main(void)"
|
"void main(void)"
|
||||||
"{"
|
"{"
|
||||||
"vec3 samples[] = vec3[]("
|
"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[3]).rgb,"
|
||||||
"texture(texID, inputPositionsVarying[4]).rgb,"
|
"texture(texID, inputPositionsVarying[4]).rgb,"
|
||||||
"texture(texID, inputPositionsVarying[5]).rgb,"
|
"texture(texID, inputPositionsVarying[5]).rgb,"
|
||||||
"texture(texID, inputPositionsVarying[6]).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 chromaChannel1[] = vec4[]("
|
"vec4 chromaChannel1 = vec4(samples[0].g, samples[1].g, samples[2].g, samples[3].g);"
|
||||||
"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);"
|
||||||
"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)"
|
|
||||||
");"
|
|
||||||
|
|
||||||
"vec3 lumaChromaColour = vec3(samples[5].r,"
|
"vec3 lumaChromaColour = vec3(samples[2].r,"
|
||||||
"dot(vec3("
|
"dot(chromaChannel1, vec4(0.25)),"
|
||||||
"dot(chromaChannel1[0], weights[0]),"
|
"dot(chromaChannel2, vec4(0.25))"
|
||||||
"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 lumaChromaColourInRange = (lumaChromaColour - vec3(0.0, 0.5, 0.5)) * vec3(1.0, 2.0, 2.0);"
|
"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);
|
"}", false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_luma_filter_shader()
|
std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_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()
|
|
||||||
{
|
|
||||||
return make_shader(
|
return make_shader(
|
||||||
"#version 150\n"
|
"#version 150\n"
|
||||||
|
|
||||||
@@ -384,18 +310,15 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_filter_shader()
|
|||||||
"}", false, false);
|
"}", 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);
|
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));
|
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
|
// 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
|
// 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
|
// 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.
|
// Perform a linear search for the highest number of taps we can use with 11 samples.
|
||||||
GLfloat weights[12];
|
GLfloat weights[12];
|
||||||
GLfloat offsets[5];
|
GLfloat offsets[5];
|
||||||
unsigned int taps = 21;
|
unsigned int taps = 11;
|
||||||
while(1)
|
// unsigned int taps = 21;
|
||||||
{
|
while(1) {
|
||||||
float coefficients[21];
|
float coefficients[21];
|
||||||
SignalProcessing::FIRFilter luminance_filter(taps, sampling_rate, 0.0f, cutoff_frequency, SignalProcessing::FIRFilter::DefaultAttenuation);
|
SignalProcessing::FIRFilter luminance_filter(taps, sampling_rate, 0.0f, cutoff_frequency, SignalProcessing::FIRFilter::DefaultAttenuation);
|
||||||
luminance_filter.get_coefficients(coefficients);
|
luminance_filter.get_coefficients(coefficients);
|
||||||
|
|
||||||
int sample = 0;
|
// int sample = 0;
|
||||||
int c = 0;
|
// int c = 0;
|
||||||
memset(weights, 0, sizeof(float)*12);
|
memset(weights, 0, sizeof(float)*12);
|
||||||
memset(offsets, 0, sizeof(float)*5);
|
memset(offsets, 0, sizeof(float)*5);
|
||||||
|
|
||||||
int halfSize = (taps >> 1);
|
int halfSize = (taps >> 1);
|
||||||
while(c < halfSize && sample < 5)
|
for(int c = 0; c < taps; c++) {
|
||||||
{
|
if(c < 5) offsets[c] = (halfSize - c);
|
||||||
offsets[sample] = (float)(halfSize - c);
|
weights[c] = coefficients[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;
|
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;
|
taps -= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,19 +373,25 @@ void IntermediateShader::set_filter_coefficients(float sampling_rate, float cuto
|
|||||||
set_uniform("offsets", 1, 5, offsets);
|
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);
|
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)
|
void IntermediateShader::set_extension(float extension) {
|
||||||
{
|
set_uniform("extension", extension);
|
||||||
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_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("lumaChromaToRGB", 3, false, toRGB);
|
||||||
set_uniform_matrix("rgbToLumaChroma", 3, false, fromRGB);
|
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
|
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.
|
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,
|
Constructs and returns an intermediate shader that will take runs from the inputPositions,
|
||||||
converting them to RGB values using @c rgb_shader.
|
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,
|
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();
|
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.
|
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,
|
geometry should be extended so that a complete colour cycle is included at both the beginning and end,
|
||||||
to occur upon the next `bind`.
|
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`.
|
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);
|
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:
|
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;
|
using namespace OpenGL;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
const OpenGL::Shader::AttributeBinding bindings[] =
|
const OpenGL::Shader::AttributeBinding bindings[] = {
|
||||||
{
|
|
||||||
{"position", 0},
|
{"position", 0},
|
||||||
{"srcCoordinates", 1},
|
{"srcCoordinates", 1},
|
||||||
{nullptr}
|
{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";
|
const char *sampler_type = use_usampler ? "usampler2D" : "sampler2D";
|
||||||
|
|
||||||
char *vertex_shader;
|
char *vertex_shader;
|
||||||
@@ -38,6 +36,8 @@ std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_met
|
|||||||
"uniform vec2 positionConversion;"
|
"uniform vec2 positionConversion;"
|
||||||
"uniform vec2 scanNormal;"
|
"uniform vec2 scanNormal;"
|
||||||
"uniform %s texID;"
|
"uniform %s texID;"
|
||||||
|
"uniform float inputScaler;"
|
||||||
|
"uniform int textureHeightDivisor;"
|
||||||
|
|
||||||
"out float lateralVarying;"
|
"out float lateralVarying;"
|
||||||
"out vec2 srcCoordinatesVarying;"
|
"out vec2 srcCoordinatesVarying;"
|
||||||
@@ -52,9 +52,10 @@ std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_met
|
|||||||
"lateralVarying = lateral - 0.5;"
|
"lateralVarying = lateral - 0.5;"
|
||||||
|
|
||||||
"vec2 vSrcCoordinates = vec2(x, vertical.y);"
|
"vec2 vSrcCoordinates = vec2(x, vertical.y);"
|
||||||
"ivec2 textureSize = textureSize(texID, 0);"
|
"ivec2 textureSize = textureSize(texID, 0) * ivec2(1, textureHeightDivisor);"
|
||||||
"iSrcCoordinatesVarying = vSrcCoordinates;"
|
"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 vPosition = vec2(x, vertical.x);"
|
||||||
"vec2 floatingPosition = (vPosition / positionConversion) + lateral * scanNormal;"
|
"vec2 floatingPosition = (vPosition / positionConversion) + lateral * scanNormal;"
|
||||||
@@ -89,8 +90,7 @@ std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_met
|
|||||||
return result;
|
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 outputAspectRatioMultiplier = ((float)output_width / (float)output_height) / (4.0f / 3.0f);
|
||||||
|
|
||||||
GLfloat bonusWidth = (outputAspectRatioMultiplier - 1.0f) * visible_area.size.width;
|
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);
|
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));
|
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_angle = atan2f(1.0f / (float)height_of_display, 1.0f);
|
||||||
GLfloat scan_normal[] = { -sinf(scan_angle), cosf(scan_angle)};
|
GLfloat scan_normal[] = { -sinf(scan_angle), cosf(scan_angle)};
|
||||||
GLfloat multiplier = (float)cycles_per_line / ((float)height_of_display * (float)horizontal_scan_period);
|
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("scanNormal", scan_normal[0], scan_normal[1]);
|
||||||
set_uniform("positionConversion", (GLfloat)horizontal_scan_period, (GLfloat)vertical_scan_period / (GLfloat)vertical_period_divider);
|
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`.
|
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_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 "Shader.hpp"
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
using namespace OpenGL;
|
using namespace OpenGL;
|
||||||
@@ -17,24 +16,23 @@ namespace {
|
|||||||
Shader *bound_shader = nullptr;
|
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);
|
GLuint shader = glCreateShader(type);
|
||||||
glShaderSource(shader, 1, &source, NULL);
|
const char *c_str = source.c_str();
|
||||||
|
glShaderSource(shader, 1, &c_str, NULL);
|
||||||
glCompileShader(shader);
|
glCompileShader(shader);
|
||||||
|
|
||||||
#if defined(DEBUG)
|
#ifdef DEBUG
|
||||||
GLint isCompiled = 0;
|
GLint isCompiled = 0;
|
||||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
|
glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
|
||||||
if(isCompiled == GL_FALSE)
|
if(isCompiled == GL_FALSE) {
|
||||||
{
|
|
||||||
GLint logLength;
|
GLint logLength;
|
||||||
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
|
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
|
||||||
if(logLength > 0) {
|
if(logLength > 0) {
|
||||||
GLchar *log = (GLchar *)malloc((size_t)logLength);
|
GLchar *log = new GLchar[logLength];
|
||||||
glGetShaderInfoLog(shader, logLength, &logLength, log);
|
glGetShaderInfoLog(shader, logLength, &logLength, log);
|
||||||
printf("Compile log:\n%s\n", log);
|
printf("Compile log:\n%s\n", log);
|
||||||
free(log);
|
delete[] log;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw (type == GL_VERTEX_SHADER) ? VertexShaderCompilationError : FragmentShaderCompilationError;
|
throw (type == GL_VERTEX_SHADER) ? VertexShaderCompilationError : FragmentShaderCompilationError;
|
||||||
@@ -44,8 +42,7 @@ GLuint Shader::compile_shader(const char *source, GLenum type)
|
|||||||
return shader;
|
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();
|
shader_program_ = glCreateProgram();
|
||||||
GLuint vertex = compile_shader(vertex_shader, GL_VERTEX_SHADER);
|
GLuint vertex = compile_shader(vertex_shader, GL_VERTEX_SHADER);
|
||||||
GLuint fragment = compile_shader(fragment_shader, GL_FRAGMENT_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_, vertex);
|
||||||
glAttachShader(shader_program_, fragment);
|
glAttachShader(shader_program_, fragment);
|
||||||
|
|
||||||
if(attribute_bindings)
|
if(attribute_bindings) {
|
||||||
{
|
while(attribute_bindings->name) {
|
||||||
while(attribute_bindings->name)
|
|
||||||
{
|
|
||||||
glBindAttribLocation(shader_program_, attribute_bindings->index, attribute_bindings->name);
|
glBindAttribLocation(shader_program_, attribute_bindings->index, attribute_bindings->name);
|
||||||
attribute_bindings++;
|
attribute_bindings++;
|
||||||
}
|
}
|
||||||
@@ -64,58 +59,50 @@ Shader::Shader(const char *vertex_shader, const char *fragment_shader, const Att
|
|||||||
|
|
||||||
glLinkProgram(shader_program_);
|
glLinkProgram(shader_program_);
|
||||||
|
|
||||||
#if defined(DEBUG)
|
#ifdef DEBUG
|
||||||
GLint didLink = 0;
|
GLint didLink = 0;
|
||||||
glGetProgramiv(shader_program_, GL_LINK_STATUS, &didLink);
|
glGetProgramiv(shader_program_, GL_LINK_STATUS, &didLink);
|
||||||
if(didLink == GL_FALSE)
|
if(didLink == GL_FALSE) {
|
||||||
{
|
|
||||||
GLint logLength;
|
GLint logLength;
|
||||||
glGetProgramiv(shader_program_, GL_INFO_LOG_LENGTH, &logLength);
|
glGetProgramiv(shader_program_, GL_INFO_LOG_LENGTH, &logLength);
|
||||||
if(logLength > 0) {
|
if(logLength > 0) {
|
||||||
GLchar *log = (GLchar *)malloc((size_t)logLength);
|
GLchar *log = new GLchar[logLength];
|
||||||
glGetProgramInfoLog(shader_program_, logLength, &logLength, log);
|
glGetProgramInfoLog(shader_program_, logLength, &logLength, log);
|
||||||
printf("Link log:\n%s\n", log);
|
printf("Link log:\n%s\n", log);
|
||||||
free(log);
|
delete[] log;
|
||||||
}
|
}
|
||||||
throw ProgramLinkageError;
|
throw ProgramLinkageError;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
Shader::~Shader()
|
Shader::~Shader() {
|
||||||
{
|
|
||||||
if(bound_shader == this) Shader::unbind();
|
if(bound_shader == this) Shader::unbind();
|
||||||
glDeleteProgram(shader_program_);
|
glDeleteProgram(shader_program_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shader::bind()
|
void Shader::bind() {
|
||||||
{
|
if(bound_shader != this) {
|
||||||
if(bound_shader != this)
|
|
||||||
{
|
|
||||||
glUseProgram(shader_program_);
|
glUseProgram(shader_program_);
|
||||||
bound_shader = this;
|
bound_shader = this;
|
||||||
}
|
}
|
||||||
flush_functions();
|
flush_functions();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shader::unbind()
|
void Shader::unbind() {
|
||||||
{
|
|
||||||
bound_shader = nullptr;
|
bound_shader = nullptr;
|
||||||
glUseProgram(0);
|
glUseProgram(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
GLint Shader::get_attrib_location(const GLchar *name)
|
GLint Shader::get_attrib_location(const GLchar *name) {
|
||||||
{
|
|
||||||
return glGetAttribLocation(shader_program_, 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);
|
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);
|
GLint location = get_attrib_location(name);
|
||||||
glEnableVertexAttribArray((GLuint)location);
|
glEnableVertexAttribArray((GLuint)location);
|
||||||
glVertexAttribPointer((GLuint)location, size, type, normalised, stride, pointer);
|
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...
|
// The various set_uniforms...
|
||||||
#define location() glGetUniformLocation(shader_program_, name.c_str())
|
#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] {
|
enqueue_function([name, value, this] {
|
||||||
glUniform1i(location(), value);
|
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] {
|
enqueue_function([name, value, this] {
|
||||||
glUniform1ui(location(), value);
|
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] {
|
enqueue_function([name, value, this] {
|
||||||
glUniform1f(location(), value);
|
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] {
|
enqueue_function([name, value1, value2, this] {
|
||||||
glUniform2i(location(), value1, value2);
|
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] {
|
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] {
|
enqueue_function([name, value1, value2, this] {
|
||||||
glUniform2ui(location(), value1, value2);
|
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] {
|
enqueue_function([name, value1, value2, value3, this] {
|
||||||
glUniform3i(location(), value1, value2, value3);
|
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] {
|
enqueue_function([name, value1, value2, value3, this] {
|
||||||
glUniform3f(location(), value1, value2, value3);
|
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] {
|
enqueue_function([name, value1, value2, value3, this] {
|
||||||
glUniform3ui(location(), value1, value2, value3);
|
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] {
|
enqueue_function([name, value1, value2, value3, value4, this] {
|
||||||
glUniform4i(location(), value1, value2, value3, value4);
|
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] {
|
enqueue_function([name, value1, value2, value3, value4, this] {
|
||||||
glUniform4f(location(), value1, value2, value3, value4);
|
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] {
|
enqueue_function([name, value1, value2, value3, value4, this] {
|
||||||
glUniform4ui(location(), value1, value2, value3, value4);
|
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;
|
size_t number_of_values = (size_t)count * (size_t)size;
|
||||||
GLint *values_copy = new GLint[number_of_values];
|
GLint *values_copy = new GLint[number_of_values];
|
||||||
memcpy(values_copy, values, sizeof(*values) * (size_t)number_of_values);
|
memcpy(values_copy, values, sizeof(*values) * (size_t)number_of_values);
|
||||||
|
|
||||||
enqueue_function([name, size, count, values_copy, this] {
|
enqueue_function([name, size, count, values_copy, this] {
|
||||||
switch(size)
|
switch(size) {
|
||||||
{
|
|
||||||
case 1: glUniform1iv(location(), count, values_copy); break;
|
case 1: glUniform1iv(location(), count, values_copy); break;
|
||||||
case 2: glUniform2iv(location(), count, values_copy); break;
|
case 2: glUniform2iv(location(), count, values_copy); break;
|
||||||
case 3: glUniform3iv(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;
|
size_t number_of_values = (size_t)count * (size_t)size;
|
||||||
GLfloat *values_copy = new GLfloat[number_of_values];
|
GLfloat *values_copy = new GLfloat[number_of_values];
|
||||||
memcpy(values_copy, values, sizeof(*values) * (size_t)number_of_values);
|
memcpy(values_copy, values, sizeof(*values) * (size_t)number_of_values);
|
||||||
|
|
||||||
enqueue_function([name, size, count, values_copy, this] {
|
enqueue_function([name, size, count, values_copy, this] {
|
||||||
switch(size)
|
switch(size) {
|
||||||
{
|
|
||||||
case 1: glUniform1fv(location(), count, values_copy); break;
|
case 1: glUniform1fv(location(), count, values_copy); break;
|
||||||
case 2: glUniform2fv(location(), count, values_copy); break;
|
case 2: glUniform2fv(location(), count, values_copy); break;
|
||||||
case 3: glUniform3fv(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;
|
size_t number_of_values = (size_t)count * (size_t)size;
|
||||||
GLuint *values_copy = new GLuint[number_of_values];
|
GLuint *values_copy = new GLuint[number_of_values];
|
||||||
memcpy(values_copy, values, sizeof(*values) * (size_t)number_of_values);
|
memcpy(values_copy, values, sizeof(*values) * (size_t)number_of_values);
|
||||||
|
|
||||||
enqueue_function([name, size, count, values_copy, this] {
|
enqueue_function([name, size, count, values_copy, this] {
|
||||||
switch(size)
|
switch(size) {
|
||||||
{
|
|
||||||
case 1: glUniform1uiv(location(), count, values_copy); break;
|
case 1: glUniform1uiv(location(), count, values_copy); break;
|
||||||
case 2: glUniform2uiv(location(), count, values_copy); break;
|
case 2: glUniform2uiv(location(), count, values_copy); break;
|
||||||
case 3: glUniform3uiv(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);
|
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;
|
size_t number_of_values = (size_t)count * (size_t)size * (size_t)size;
|
||||||
GLfloat *values_copy = new GLfloat[number_of_values];
|
GLfloat *values_copy = new GLfloat[number_of_values];
|
||||||
memcpy(values_copy, values, sizeof(*values) * number_of_values);
|
memcpy(values_copy, values, sizeof(*values) * number_of_values);
|
||||||
|
|
||||||
enqueue_function([name, size, count, transpose, values_copy, this] {
|
enqueue_function([name, size, count, transpose, values_copy, this] {
|
||||||
GLboolean glTranspose = transpose ? GL_TRUE : GL_FALSE;
|
GLboolean glTranspose = transpose ? GL_TRUE : GL_FALSE;
|
||||||
switch(size)
|
switch(size) {
|
||||||
{
|
|
||||||
case 2: glUniformMatrix2fv(location(), count, glTranspose, values_copy); break;
|
case 2: glUniformMatrix2fv(location(), count, glTranspose, values_copy); break;
|
||||||
case 3: glUniformMatrix3fv(location(), count, glTranspose, values_copy); break;
|
case 3: glUniformMatrix3fv(location(), count, glTranspose, values_copy); break;
|
||||||
case 4: glUniformMatrix4fv(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)
|
void Shader::enqueue_function(std::function<void(void)> function) {
|
||||||
{
|
std::lock_guard<std::mutex> function_guard(function_mutex_);
|
||||||
function_mutex_.lock();
|
|
||||||
enqueued_functions_.push_back(function);
|
enqueued_functions_.push_back(function);
|
||||||
function_mutex_.unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shader::flush_functions()
|
void Shader::flush_functions() {
|
||||||
{
|
std::lock_guard<std::mutex> function_guard(function_mutex_);
|
||||||
function_mutex_.lock();
|
for(std::function<void(void)> function : enqueued_functions_) {
|
||||||
for(std::function<void(void)> function : enqueued_functions_)
|
|
||||||
{
|
|
||||||
function();
|
function();
|
||||||
}
|
}
|
||||||
enqueued_functions_.clear();
|
enqueued_functions_.clear();
|
||||||
function_mutex_.unlock();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ public:
|
|||||||
@param fragment_shader The fragment shader source code.
|
@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.
|
@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();
|
~Shader();
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -106,7 +106,7 @@ public:
|
|||||||
void set_uniform_matrix(const std::string &name, GLint size, GLsizei count, bool transpose, const GLfloat *values);
|
void set_uniform_matrix(const std::string &name, GLint size, GLsizei count, bool transpose, const GLfloat *values);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GLuint compile_shader(const char *source, GLenum type);
|
GLuint compile_shader(const std::string &source, GLenum type);
|
||||||
GLuint shader_program_;
|
GLuint shader_program_;
|
||||||
|
|
||||||
void flush_functions();
|
void flush_functions();
|
||||||
|
|||||||
@@ -13,10 +13,8 @@
|
|||||||
|
|
||||||
using namespace Outputs::CRT;
|
using namespace Outputs::CRT;
|
||||||
|
|
||||||
static const GLint internalFormatForDepth(size_t depth)
|
static const GLint internalFormatForDepth(size_t depth) {
|
||||||
{
|
switch(depth) {
|
||||||
switch(depth)
|
|
||||||
{
|
|
||||||
default: return GL_FALSE;
|
default: return GL_FALSE;
|
||||||
case 1: return GL_R8UI;
|
case 1: return GL_R8UI;
|
||||||
case 2: return GL_RG8UI;
|
case 2: return GL_RG8UI;
|
||||||
@@ -25,10 +23,8 @@ static const GLint internalFormatForDepth(size_t depth)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const GLenum formatForDepth(size_t depth)
|
static const GLenum formatForDepth(size_t depth) {
|
||||||
{
|
switch(depth) {
|
||||||
switch(depth)
|
|
||||||
{
|
|
||||||
default: return GL_FALSE;
|
default: return GL_FALSE;
|
||||||
case 1: return GL_RED_INTEGER;
|
case 1: return GL_RED_INTEGER;
|
||||||
case 2: return GL_RG_INTEGER;
|
case 2: return GL_RG_INTEGER;
|
||||||
@@ -43,8 +39,8 @@ TextureBuilder::TextureBuilder(size_t bytes_per_pixel, GLenum texture_unit) :
|
|||||||
write_areas_start_y_(0),
|
write_areas_start_y_(0),
|
||||||
is_full_(false),
|
is_full_(false),
|
||||||
did_submit_(false),
|
did_submit_(false),
|
||||||
number_of_write_areas_(0)
|
has_write_area_(false),
|
||||||
{
|
number_of_write_areas_(0) {
|
||||||
image_.resize(bytes_per_pixel * InputBufferBuilderWidth * InputBufferBuilderHeight);
|
image_.resize(bytes_per_pixel * InputBufferBuilderWidth * InputBufferBuilderHeight);
|
||||||
glGenTextures(1, &texture_name_);
|
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);
|
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_);
|
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_];
|
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;
|
if(is_full_) return nullptr;
|
||||||
|
|
||||||
uint16_t starting_x, starting_y;
|
uint16_t starting_x, starting_y;
|
||||||
|
|
||||||
if(!number_of_write_areas_)
|
if(!number_of_write_areas_) {
|
||||||
{
|
|
||||||
starting_x = write_areas_start_x_;
|
starting_x = write_areas_start_x_;
|
||||||
starting_y = write_areas_start_y_;
|
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_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;
|
starting_y = write_areas_[number_of_write_areas_ - 1].y;
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteArea next_write_area;
|
WriteArea next_write_area;
|
||||||
if(starting_x + required_length + 2 > InputBufferBuilderWidth)
|
if(starting_x + required_length + 2 > InputBufferBuilderWidth) {
|
||||||
{
|
|
||||||
starting_x = 0;
|
starting_x = 0;
|
||||||
starting_y++;
|
starting_y++;
|
||||||
|
|
||||||
if(starting_y == InputBufferBuilderHeight)
|
if(starting_y == InputBufferBuilderHeight) {
|
||||||
{
|
|
||||||
is_full_ = true;
|
is_full_ = true;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -105,19 +93,19 @@ uint8_t *TextureBuilder::allocate_write_area(size_t required_length)
|
|||||||
else
|
else
|
||||||
write_areas_.push_back(next_write_area);
|
write_areas_.push_back(next_write_area);
|
||||||
number_of_write_areas_++;
|
number_of_write_areas_++;
|
||||||
|
has_write_area_ = true;
|
||||||
|
|
||||||
return pointer_to_location(next_write_area.x, next_write_area.y);
|
return pointer_to_location(next_write_area.x, next_write_area.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TextureBuilder::is_full()
|
bool TextureBuilder::is_full() {
|
||||||
{
|
|
||||||
return is_full_;
|
return is_full_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureBuilder::reduce_previous_allocation_to(size_t actual_length)
|
void TextureBuilder::reduce_previous_allocation_to(size_t actual_length) {
|
||||||
{
|
if(is_full_ || !has_write_area_) return;
|
||||||
if(is_full_) return;
|
|
||||||
|
|
||||||
|
has_write_area_ = false;
|
||||||
WriteArea &write_area = write_areas_[number_of_write_areas_-1];
|
WriteArea &write_area = write_areas_[number_of_write_areas_-1];
|
||||||
write_area.length = (uint16_t)actual_length;
|
write_area.length = (uint16_t)actual_length;
|
||||||
|
|
||||||
@@ -133,8 +121,7 @@ void TextureBuilder::reduce_previous_allocation_to(size_t actual_length)
|
|||||||
bytes_per_pixel_);
|
bytes_per_pixel_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureBuilder::submit()
|
void TextureBuilder::submit() {
|
||||||
{
|
|
||||||
uint16_t height = write_areas_start_y_ + (write_areas_start_x_ ? 1 : 0);
|
uint16_t height = write_areas_start_y_ + (write_areas_start_x_ ? 1 : 0);
|
||||||
did_submit_ = true;
|
did_submit_ = true;
|
||||||
|
|
||||||
@@ -145,30 +132,23 @@ void TextureBuilder::submit()
|
|||||||
image_.data());
|
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_;
|
bool was_full = is_full_;
|
||||||
if(did_submit_)
|
if(did_submit_) {
|
||||||
{
|
|
||||||
write_areas_start_y_ = write_areas_start_x_ = 0;
|
write_areas_start_y_ = write_areas_start_x_ = 0;
|
||||||
is_full_ = false;
|
is_full_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(number_of_write_areas_ && !was_full)
|
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_) {
|
||||||
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++) {
|
||||||
{
|
|
||||||
for(size_t area = 0; area < number_of_write_areas_; area++)
|
|
||||||
{
|
|
||||||
WriteArea &write_area = 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_x_ = 0;
|
||||||
write_areas_start_y_ ++;
|
write_areas_start_y_ ++;
|
||||||
|
|
||||||
if(write_areas_start_y_ == InputBufferBuilderHeight)
|
if(write_areas_start_y_ == InputBufferBuilderHeight) {
|
||||||
{
|
|
||||||
is_full_ = true;
|
is_full_ = true;
|
||||||
break;
|
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_);
|
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;
|
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;
|
did_submit_ = false;
|
||||||
|
has_write_area_ = false;
|
||||||
number_of_write_areas_ = 0;
|
number_of_write_areas_ = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ class TextureBuilder {
|
|||||||
size_t number_of_write_areas_;
|
size_t number_of_write_areas_;
|
||||||
bool is_full_;
|
bool is_full_;
|
||||||
bool did_submit_;
|
bool did_submit_;
|
||||||
|
bool has_write_area_;
|
||||||
inline uint8_t *pointer_to_location(uint16_t x, uint16_t y);
|
inline uint8_t *pointer_to_location(uint16_t x, uint16_t y);
|
||||||
|
|
||||||
// Usually: the start position for the current batch of write areas.
|
// Usually: the start position for the current batch of write areas.
|
||||||
|
|||||||
@@ -12,15 +12,14 @@
|
|||||||
|
|
||||||
using namespace OpenGL;
|
using namespace OpenGL;
|
||||||
|
|
||||||
TextureTarget::TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit) :
|
TextureTarget::TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit, GLint mag_filter) :
|
||||||
_width(width),
|
_width(width),
|
||||||
_height(height),
|
_height(height),
|
||||||
_pixel_shader(nullptr),
|
_pixel_shader(nullptr),
|
||||||
_drawing_vertex_array(0),
|
_drawing_vertex_array(0),
|
||||||
_drawing_array_buffer(0),
|
_drawing_array_buffer(0),
|
||||||
_set_aspect_ratio(0.0f),
|
_set_aspect_ratio(0.0f),
|
||||||
_texture_unit(texture_unit)
|
_texture_unit(texture_unit) {
|
||||||
{
|
|
||||||
glGenFramebuffers(1, &_framebuffer);
|
glGenFramebuffers(1, &_framebuffer);
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, _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);
|
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);
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)_expanded_width, (GLsizei)_expanded_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, blank_buffer);
|
||||||
free(blank_buffer);
|
free(blank_buffer);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||||
|
|
||||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0);
|
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;
|
throw ErrorFramebufferIncomplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextureTarget::~TextureTarget()
|
TextureTarget::~TextureTarget() {
|
||||||
{
|
|
||||||
glDeleteFramebuffers(1, &_framebuffer);
|
glDeleteFramebuffers(1, &_framebuffer);
|
||||||
glDeleteTextures(1, &_texture);
|
glDeleteTextures(1, &_texture);
|
||||||
if(_drawing_vertex_array) glDeleteVertexArrays(1, &_drawing_vertex_array);
|
if(_drawing_vertex_array) glDeleteVertexArrays(1, &_drawing_vertex_array);
|
||||||
if(_drawing_array_buffer) glDeleteBuffers(1, &_drawing_array_buffer);
|
if(_drawing_array_buffer) glDeleteBuffers(1, &_drawing_array_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureTarget::bind_framebuffer()
|
void TextureTarget::bind_framebuffer() {
|
||||||
{
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
|
glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
|
||||||
glViewport(0, 0, _width, _height);
|
glViewport(0, 0, _width, _height);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureTarget::bind_texture()
|
void TextureTarget::bind_texture() {
|
||||||
{
|
|
||||||
glBindTexture(GL_TEXTURE_2D, _texture);
|
glBindTexture(GL_TEXTURE_2D, _texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureTarget::draw(float aspect_ratio)
|
void TextureTarget::draw(float aspect_ratio) {
|
||||||
{
|
if(!_pixel_shader) {
|
||||||
if(!_pixel_shader)
|
|
||||||
{
|
|
||||||
const char *vertex_shader =
|
const char *vertex_shader =
|
||||||
"#version 150\n"
|
"#version 150\n"
|
||||||
|
|
||||||
@@ -112,8 +106,7 @@ void TextureTarget::draw(float aspect_ratio)
|
|||||||
glUniform1i(texIDUniform, (GLint)(_texture_unit - GL_TEXTURE0));
|
glUniform1i(texIDUniform, (GLint)(_texture_unit - GL_TEXTURE0));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_set_aspect_ratio != aspect_ratio)
|
if(_set_aspect_ratio != aspect_ratio) {
|
||||||
{
|
|
||||||
_set_aspect_ratio = aspect_ratio;
|
_set_aspect_ratio = aspect_ratio;
|
||||||
float buffer[4*4];
|
float buffer[4*4];
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class TextureTarget {
|
|||||||
@param height The height of target to create.
|
@param height The height of target to create.
|
||||||
@param texture_unit A texture unit on which to bind the texture.
|
@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();
|
~TextureTarget();
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -46,16 +46,14 @@ class TextureTarget {
|
|||||||
/*!
|
/*!
|
||||||
@returns the width of the texture target.
|
@returns the width of the texture target.
|
||||||
*/
|
*/
|
||||||
GLsizei get_width()
|
GLsizei get_width() {
|
||||||
{
|
|
||||||
return _width;
|
return _width;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@returns the height of the texture target.
|
@returns the height of the texture target.
|
||||||
*/
|
*/
|
||||||
GLsizei get_height()
|
GLsizei get_height() {
|
||||||
{
|
|
||||||
return _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;
|
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
|
// 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;
|
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;
|
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;
|
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_in_progress_.reset(new int16_t[buffer_size]);
|
||||||
buffer_size_ = buffer_size;
|
buffer_size_ = buffer_size;
|
||||||
}
|
}
|
||||||
set_needs_updated_filter_coefficients();
|
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;
|
requested_number_of_taps_ = number_of_taps;
|
||||||
set_needs_updated_filter_coefficients();
|
set_needs_updated_filter_coefficients();
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_delegate(Delegate *delegate)
|
void set_delegate(Delegate *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;
|
input_cycles_per_second_ = cycles_per_second;
|
||||||
set_needs_updated_filter_coefficients();
|
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.
|
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;
|
high_frequency_cut_off_ = high_frequency;
|
||||||
set_needs_updated_filter_coefficients();
|
set_needs_updated_filter_coefficients();
|
||||||
}
|
}
|
||||||
@@ -94,21 +87,18 @@ class Speaker {
|
|||||||
/*!
|
/*!
|
||||||
Ensures any deferred processing occurs now.
|
Ensures any deferred processing occurs now.
|
||||||
*/
|
*/
|
||||||
void flush()
|
void flush() {
|
||||||
{
|
|
||||||
std::shared_ptr<std::list<std::function<void(void)>>> queued_functions = queued_functions_;
|
std::shared_ptr<std::list<std::function<void(void)>>> queued_functions = queued_functions_;
|
||||||
queued_functions_.reset();
|
queued_functions_.reset();
|
||||||
_queue->enqueue([queued_functions] {
|
_queue->enqueue([queued_functions] {
|
||||||
for(auto function : *queued_functions)
|
for(auto function : *queued_functions) {
|
||||||
{
|
|
||||||
function();
|
function();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
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)>>);
|
if(!queued_functions_) queued_functions_.reset(new std::list<std::function<void(void)>>);
|
||||||
queued_functions_->push_back(function);
|
queued_functions_->push_back(function);
|
||||||
}
|
}
|
||||||
@@ -124,14 +114,12 @@ class Speaker {
|
|||||||
|
|
||||||
float input_cycles_per_second_, output_cycles_per_second_;
|
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;
|
coefficients_are_dirty_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void get_samples(unsigned int quantity, int16_t *target) {}
|
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];
|
int16_t throwaway_samples[quantity];
|
||||||
get_samples(quantity, throwaway_samples);
|
get_samples(quantity, throwaway_samples);
|
||||||
}
|
}
|
||||||
@@ -151,22 +139,18 @@ class Speaker {
|
|||||||
*/
|
*/
|
||||||
template <class T> class Filter: public Speaker {
|
template <class T> class Filter: public Speaker {
|
||||||
public:
|
public:
|
||||||
~Filter()
|
~Filter() {
|
||||||
{
|
|
||||||
_queue->flush();
|
_queue->flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
void run_for_cycles(unsigned int input_cycles)
|
void run_for_cycles(unsigned int input_cycles) {
|
||||||
{
|
|
||||||
enqueue([=]() {
|
enqueue([=]() {
|
||||||
unsigned int cycles_remaining = input_cycles;
|
unsigned int cycles_remaining = input_cycles;
|
||||||
if(coefficients_are_dirty_) update_filter_coefficients();
|
if(coefficients_are_dirty_) update_filter_coefficients();
|
||||||
|
|
||||||
// if input and output rates exactly match, just accumulate results and pass on
|
// 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)
|
if(input_cycles_per_second_ == output_cycles_per_second_ && high_frequency_cut_off_ < 0.0) {
|
||||||
{
|
while(cycles_remaining) {
|
||||||
while(cycles_remaining)
|
|
||||||
{
|
|
||||||
unsigned int cycles_to_read = (unsigned int)(buffer_size_ - buffer_in_progress_pointer_);
|
unsigned int cycles_to_read = (unsigned int)(buffer_size_ - buffer_in_progress_pointer_);
|
||||||
if(cycles_to_read > cycles_remaining) cycles_to_read = cycles_remaining;
|
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;
|
buffer_in_progress_pointer_ += cycles_to_read;
|
||||||
|
|
||||||
// announce to delegate if full
|
// announce to delegate if full
|
||||||
if(buffer_in_progress_pointer_ == buffer_size_)
|
if(buffer_in_progress_pointer_ == buffer_size_) {
|
||||||
{
|
|
||||||
buffer_in_progress_pointer_ = 0;
|
buffer_in_progress_pointer_ = 0;
|
||||||
if(delegate_)
|
if(delegate_) {
|
||||||
{
|
|
||||||
delegate_->speaker_did_complete_samples(this, buffer_in_progress_.get(), buffer_size_);
|
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 the output rate is less than the input rate, use the filter
|
||||||
if(input_cycles_per_second_ > output_cycles_per_second_)
|
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) {
|
||||||
while(cycles_remaining)
|
|
||||||
{
|
|
||||||
unsigned int cycles_to_read = (unsigned int)std::min((int)cycles_remaining, number_of_taps_ - input_buffer_depth_);
|
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_]);
|
static_cast<T *>(this)->get_samples(cycles_to_read, &input_buffer_.get()[input_buffer_depth_]);
|
||||||
cycles_remaining -= cycles_to_read;
|
cycles_remaining -= cycles_to_read;
|
||||||
input_buffer_depth_ += 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_.get()[buffer_in_progress_pointer_] = filter_->apply(input_buffer_.get());
|
||||||
buffer_in_progress_pointer_++;
|
buffer_in_progress_pointer_++;
|
||||||
|
|
||||||
// announce to delegate if full
|
// announce to delegate if full
|
||||||
if(buffer_in_progress_pointer_ == buffer_size_)
|
if(buffer_in_progress_pointer_ == buffer_size_) {
|
||||||
{
|
|
||||||
buffer_in_progress_pointer_ = 0;
|
buffer_in_progress_pointer_ = 0;
|
||||||
if(delegate_)
|
if(delegate_) {
|
||||||
{
|
|
||||||
delegate_->speaker_did_complete_samples(this, buffer_in_progress_.get(), buffer_size_);
|
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
|
// 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.
|
// anything. Otherwise skip as required to get to the next sample batch and don't expect to reuse.
|
||||||
uint64_t steps = stepper_->step();
|
uint64_t steps = stepper_->step();
|
||||||
if(steps < number_of_taps_)
|
if(steps < number_of_taps_) {
|
||||||
{
|
|
||||||
int16_t *input_buffer = input_buffer_.get();
|
int16_t *input_buffer = input_buffer_.get();
|
||||||
memmove(input_buffer, &input_buffer[steps], sizeof(int16_t) * ((size_t)number_of_taps_ - (size_t)steps));
|
memmove(input_buffer, &input_buffer[steps], sizeof(int16_t) * ((size_t)number_of_taps_ - (size_t)steps));
|
||||||
input_buffer_depth_ -= steps;
|
input_buffer_depth_ -= steps;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
if(steps > number_of_taps_)
|
if(steps > number_of_taps_)
|
||||||
static_cast<T *>(this)->skip_samples((unsigned int)steps - (unsigned int)number_of_taps_);
|
static_cast<T *>(this)->skip_samples((unsigned int)steps - (unsigned int)number_of_taps_);
|
||||||
input_buffer_depth_ = 0;
|
input_buffer_depth_ = 0;
|
||||||
@@ -247,15 +221,11 @@ template <class T> class Filter: public Speaker {
|
|||||||
std::unique_ptr<int16_t> input_buffer_;
|
std::unique_ptr<int16_t> input_buffer_;
|
||||||
int input_buffer_depth_;
|
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
|
// 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_;
|
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_ = (int)ceilf((input_cycles_per_second_ + output_cycles_per_second_) / output_cycles_per_second_);
|
||||||
number_of_taps_ *= 2;
|
number_of_taps_ *= 2;
|
||||||
number_of_taps_ |= 1;
|
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_));
|
stepper_.reset(new SignalProcessing::Stepper((uint64_t)input_cycles_per_second_, (uint64_t)output_cycles_per_second_));
|
||||||
|
|
||||||
float high_pass_frequency;
|
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_);
|
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;
|
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));
|
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.
|
@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;
|
scheduled_programs_[schedule_programs_write_pointer_] = program;
|
||||||
schedule_programs_write_pointer_ = (schedule_programs_write_pointer_+1)&3;
|
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.
|
@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_;
|
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.
|
@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;
|
carry_flag_ = flags & Flag::Carry;
|
||||||
negative_result_ = flags & Flag::Sign;
|
negative_result_ = flags & Flag::Sign;
|
||||||
zero_result_ = (~flags) & Flag::Zero;
|
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.
|
@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 Program(...) {__VA_ARGS__, OperationMoveToNextProgram}
|
||||||
|
|
||||||
#define Absolute CycleLoadAddressAbsolute
|
#define Absolute CycleLoadAddressAbsolute
|
||||||
@@ -548,13 +544,13 @@ template <class T> class Processor {
|
|||||||
ready_is_active_(false),
|
ready_is_active_(false),
|
||||||
scheduled_programs_{nullptr, nullptr, nullptr, nullptr},
|
scheduled_programs_{nullptr, nullptr, nullptr, nullptr},
|
||||||
inverse_interrupt_flag_(0),
|
inverse_interrupt_flag_(0),
|
||||||
|
irq_request_history_(0),
|
||||||
s_(0),
|
s_(0),
|
||||||
next_bus_operation_(BusOperation::None),
|
next_bus_operation_(BusOperation::None),
|
||||||
interrupt_requests_(InterruptRequestFlags::PowerOn),
|
interrupt_requests_(InterruptRequestFlags::PowerOn),
|
||||||
irq_line_(0),
|
irq_line_(0),
|
||||||
nmi_line_is_enabled_(false),
|
nmi_line_is_enabled_(false),
|
||||||
set_overflow_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
|
// 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
|
// mask the other flags so we need to do that, at least
|
||||||
carry_flag_ &= Flag::Carry;
|
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) .
|
@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.
|
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.
|
@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[] = {
|
static const MicroOp doBranch[] = {
|
||||||
CycleReadFromPC,
|
CycleReadFromPC,
|
||||||
CycleAddSignedOperandToPC,
|
CycleAddSignedOperandToPC,
|
||||||
OperationMoveToNextProgram
|
OperationMoveToNextProgram
|
||||||
};
|
};
|
||||||
static uint8_t throwaway_target;
|
static uint8_t throwaway_target;
|
||||||
static const MicroOp fetch_decode_execute[] =
|
static const MicroOp fetch_decode_execute[] = {
|
||||||
{
|
|
||||||
CycleFetchOperation,
|
CycleFetchOperation,
|
||||||
CycleFetchOperand,
|
CycleFetchOperand,
|
||||||
OperationDecodeOperation,
|
OperationDecodeOperation,
|
||||||
@@ -633,10 +628,8 @@ template <class T> class Processor {
|
|||||||
number_of_cycles -= static_cast<T *>(this)->perform_bus_operation(BusOperation::Ready, busAddress, busValue);
|
number_of_cycles -= static_cast<T *>(this)->perform_bus_operation(BusOperation::Ready, busAddress, busValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!ready_is_active_)
|
if(!ready_is_active_) {
|
||||||
{
|
if(nextBusOperation != BusOperation::None) {
|
||||||
if(nextBusOperation != BusOperation::None)
|
|
||||||
{
|
|
||||||
bus_access();
|
bus_access();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -645,9 +638,9 @@ template <class T> class Processor {
|
|||||||
const MicroOp cycle = program[scheduleProgramProgramCounter];
|
const MicroOp cycle = program[scheduleProgramProgramCounter];
|
||||||
scheduleProgramProgramCounter++;
|
scheduleProgramProgramCounter++;
|
||||||
|
|
||||||
#define read_op(val, addr) nextBusOperation = BusOperation::ReadOpcode; busAddress = addr; busValue = &val
|
#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
|
#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
|
#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
|
#define write_mem(val, addr) nextBusOperation = BusOperation::Write; busAddress = addr; busValue = &val
|
||||||
|
|
||||||
switch(cycle) {
|
switch(cycle) {
|
||||||
@@ -656,7 +649,7 @@ template <class T> class Processor {
|
|||||||
|
|
||||||
case CycleFetchOperation: {
|
case CycleFetchOperation: {
|
||||||
last_operation_pc_ = pc_;
|
last_operation_pc_ = pc_;
|
||||||
// printf("%04x x:%02x\n", pc_.full, x_);
|
// printf("%04x\n", pc_.full);
|
||||||
pc_.full++;
|
pc_.full++;
|
||||||
read_op(operation_, last_operation_pc_.full);
|
read_op(operation_, last_operation_pc_.full);
|
||||||
|
|
||||||
@@ -689,8 +682,7 @@ template <class T> class Processor {
|
|||||||
program = scheduled_programs_[scheduleProgramsReadPointer];
|
program = scheduled_programs_[scheduleProgramsReadPointer];
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
#define push(v) \
|
#define push(v) {\
|
||||||
{\
|
|
||||||
uint16_t targetAddress = s_ | 0x100; s_--;\
|
uint16_t targetAddress = s_ | 0x100; s_--;\
|
||||||
write_mem(v, targetAddress);\
|
write_mem(v, targetAddress);\
|
||||||
}
|
}
|
||||||
@@ -700,8 +692,7 @@ template <class T> class Processor {
|
|||||||
case CyclePushPCL: push(pc_.bytes.low); break;
|
case CyclePushPCL: push(pc_.bytes.low); break;
|
||||||
case CyclePushOperand: push(operand_); break;
|
case CyclePushOperand: push(operand_); break;
|
||||||
case CyclePushA: push(a_); break;
|
case CyclePushA: push(a_); break;
|
||||||
case CycleNoWritePush:
|
case CycleNoWritePush: {
|
||||||
{
|
|
||||||
uint16_t targetAddress = s_ | 0x100; s_--;
|
uint16_t targetAddress = s_ | 0x100; s_--;
|
||||||
read_mem(operand_, targetAddress);
|
read_mem(operand_, targetAddress);
|
||||||
}
|
}
|
||||||
@@ -1144,8 +1135,7 @@ template <class T> class Processor {
|
|||||||
@param r The register to set.
|
@param r The register to set.
|
||||||
@returns The value of the register. 8-bit registers will be returned as unsigned.
|
@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) {
|
switch (r) {
|
||||||
case Register::ProgramCounter: return pc_.full;
|
case Register::ProgramCounter: return pc_.full;
|
||||||
case Register::LastOperationAddress: return last_operation_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 r The register to set.
|
||||||
@param value The value to set. If the register is only 8 bit, the value will be truncated.
|
@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) {
|
switch (r) {
|
||||||
case Register::ProgramCounter: pc_.full = value; break;
|
case Register::ProgramCounter: pc_.full = value; break;
|
||||||
case Register::StackPointer: s_ = (uint8_t)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,
|
Interrupts current execution flow to perform an RTS and, if the 6502 is currently jammed,
|
||||||
to unjam it.
|
to unjam it.
|
||||||
*/
|
*/
|
||||||
void return_from_subroutine()
|
void return_from_subroutine() {
|
||||||
{
|
|
||||||
s_++;
|
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.low); s_++;
|
||||||
static_cast<T *>(this)->perform_bus_operation(CPU6502::BusOperation::Read, 0x100 | s_, &pc_.bytes.high);
|
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.
|
@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) {
|
if(active) {
|
||||||
ready_line_is_enabled_ = true;
|
ready_line_is_enabled_ = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -1217,8 +1204,7 @@ template <class T> class Processor {
|
|||||||
|
|
||||||
@param active @c true if the line is logically active; @c false otherwise.
|
@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);
|
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.
|
@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));
|
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
|
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.
|
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);
|
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.
|
@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;
|
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.
|
@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
|
// a leading edge will set the overflow flag
|
||||||
if(active && !set_overflow_line_is_enabled_)
|
if(active && !set_overflow_line_is_enabled_)
|
||||||
overflow_flag_ = Flag::Overflow;
|
overflow_flag_ = Flag::Overflow;
|
||||||
@@ -1269,8 +1251,7 @@ template <class T> class Processor {
|
|||||||
|
|
||||||
@param active `true` if the line is logically active; `false` otherwise.
|
@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
|
// NMI is edge triggered, not level
|
||||||
if(active && !nmi_line_is_enabled_)
|
if(active && !nmi_line_is_enabled_)
|
||||||
interrupt_requests_ |= InterruptRequestFlags::NMI;
|
interrupt_requests_ |= InterruptRequestFlags::NMI;
|
||||||
@@ -1283,8 +1264,7 @@ template <class T> class Processor {
|
|||||||
|
|
||||||
@returns @c true if the 6502 is jammed; @c false otherwise.
|
@returns @c true if the 6502 is jammed; @c false otherwise.
|
||||||
*/
|
*/
|
||||||
inline bool is_jammed()
|
inline bool is_jammed() {
|
||||||
{
|
|
||||||
return 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.
|
@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;
|
jam_handler_ = handler;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,13 +12,11 @@
|
|||||||
|
|
||||||
using namespace CPU6502;
|
using namespace CPU6502;
|
||||||
|
|
||||||
AllRAMProcessor::AllRAMProcessor() : _timestamp(0)
|
AllRAMProcessor::AllRAMProcessor() : _timestamp(0) {
|
||||||
{
|
|
||||||
set_power_on(false);
|
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++;
|
_timestamp++;
|
||||||
|
|
||||||
if(isReadOperation(operation)) {
|
if(isReadOperation(operation)) {
|
||||||
@@ -30,13 +28,11 @@ int AllRAMProcessor::perform_bus_operation(CPU6502::BusOperation operation, uint
|
|||||||
return 1;
|
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);
|
size_t endAddress = std::min(startAddress + length, (size_t)65536);
|
||||||
memcpy(&_memory[startAddress], data, endAddress - startAddress);
|
memcpy(&_memory[startAddress], data, endAddress - startAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t AllRAMProcessor::get_timestamp()
|
uint32_t AllRAMProcessor::get_timestamp() {
|
||||||
{
|
|
||||||
return _timestamp;
|
return _timestamp;
|
||||||
}
|
}
|
||||||
|
|||||||
10
README.md
@@ -30,6 +30,16 @@ Similar effort is put into audio generation. If the real machine normally genera
|
|||||||
|
|
||||||
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.
|
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
|
## 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.
|
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 kCSKaiserBesselFilterFixedMultiplier 32767.0f
|
||||||
#define kCSKaiserBesselFilterFixedShift 15
|
#define kCSKaiserBesselFilterFixedShift 15
|
||||||
|
|
||||||
/* ino evaluates the 0th order Bessel function at a */
|
/*! Evaluates the 0th order Bessel function at @c a. */
|
||||||
float FIRFilter::ino(float a)
|
float FIRFilter::ino(float a) {
|
||||||
{
|
|
||||||
float d = 0.0f;
|
float d = 0.0f;
|
||||||
float ds = 1.0f;
|
float ds = 1.0f;
|
||||||
float s = 1.0f;
|
float s = 1.0f;
|
||||||
|
|
||||||
do
|
do {
|
||||||
{
|
|
||||||
d += 2.0f;
|
d += 2.0f;
|
||||||
ds *= (a * a) / (d * d);
|
ds *= (a * a) / (d * d);
|
||||||
s += ds;
|
s += ds;
|
||||||
}
|
} while(ds > s*1e-6f);
|
||||||
while(ds > s*1e-6f);
|
|
||||||
|
|
||||||
return s;
|
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 */
|
/* calculate alpha, which is the Kaiser-Bessel window shape factor */
|
||||||
float a; // to take the place of alpha in the normal derivation
|
float a; // to take the place of alpha in the normal derivation
|
||||||
|
|
||||||
if(attenuation < 21.0f)
|
if(attenuation < 21.0f) {
|
||||||
a = 0.0f;
|
a = 0.0f;
|
||||||
else
|
} else {
|
||||||
{
|
|
||||||
if(attenuation > 50.0f)
|
if(attenuation > 50.0f)
|
||||||
a = 0.1102f * (attenuation - 8.7f);
|
a = 0.1102f * (attenuation - 8.7f);
|
||||||
else
|
else
|
||||||
@@ -76,8 +70,7 @@ void FIRFilter::coefficients_for_idealised_filter_response(short *filterCoeffici
|
|||||||
unsigned int Np = (numberOfTaps - 1) / 2;
|
unsigned int Np = (numberOfTaps - 1) / 2;
|
||||||
float I0 = ino(a);
|
float I0 = ino(a);
|
||||||
float NpSquared = (float)(Np * Np);
|
float NpSquared = (float)(Np * Np);
|
||||||
for(unsigned int i = 0; i <= Np; i++)
|
for(unsigned int i = 0; i <= Np; i++) {
|
||||||
{
|
|
||||||
filterCoefficientsFloat[Np + i] =
|
filterCoefficientsFloat[Np + i] =
|
||||||
A[i] *
|
A[i] *
|
||||||
ino(a * sqrtf(1.0f - ((float)(i * i) / NpSquared) )) /
|
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 */
|
/* 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];
|
filterCoefficientsFloat[i] = filterCoefficientsFloat[numberOfTaps - 1 - i];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* scale back up so that we retain 100% of input volume */
|
/* scale back up so that we retain 100% of input volume */
|
||||||
float coefficientTotal = 0.0f;
|
float coefficientTotal = 0.0f;
|
||||||
for(unsigned int i = 0; i < numberOfTaps; i++)
|
for(unsigned int i = 0; i < numberOfTaps; i++) {
|
||||||
{
|
|
||||||
coefficientTotal += filterCoefficientsFloat[i];
|
coefficientTotal += filterCoefficientsFloat[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* we'll also need integer versions, potentially */
|
/* we'll also need integer versions, potentially */
|
||||||
float coefficientMultiplier = 1.0f / coefficientTotal;
|
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);
|
filterCoefficients[i] = (short)(filterCoefficientsFloat[i] * kCSKaiserBesselFilterFixedMultiplier * coefficientMultiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
delete[] filterCoefficientsFloat;
|
delete[] filterCoefficientsFloat;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FIRFilter::get_coefficients(float *coefficients)
|
void FIRFilter::get_coefficients(float *coefficients) {
|
||||||
{
|
for(unsigned int i = 0; i < number_of_taps_; i++) {
|
||||||
for(unsigned int i = 0; i < number_of_taps_; i++)
|
|
||||||
{
|
|
||||||
coefficients[i] = (float)filter_coefficients_[i] / kCSKaiserBesselFilterFixedMultiplier;
|
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
|
// we must be asked to filter based on an odd number of
|
||||||
// taps, and at least three
|
// taps, and at least three
|
||||||
if(number_of_taps < 3) number_of_taps = 3;
|
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];
|
float *A = new float[Np+1];
|
||||||
A[0] = 2.0f * (high_frequency - low_frequency) / input_sample_rate;
|
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;
|
float iPi = (float)i * (float)M_PI;
|
||||||
A[i] =
|
A[i] =
|
||||||
(
|
(
|
||||||
@@ -151,7 +137,6 @@ FIRFilter::FIRFilter(unsigned int number_of_taps, float input_sample_rate, float
|
|||||||
delete[] A;
|
delete[] A;
|
||||||
}
|
}
|
||||||
|
|
||||||
FIRFilter::~FIRFilter()
|
FIRFilter::~FIRFilter() {
|
||||||
{
|
|
||||||
delete[] filter_coefficients_;
|
delete[] filter_coefficients_;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,24 +57,21 @@ class FIRFilter {
|
|||||||
@param src The source buffer to apply the filter to.
|
@param src The source buffer to apply the filter to.
|
||||||
@returns The result of applying the filter.
|
@returns The result of applying the filter.
|
||||||
*/
|
*/
|
||||||
inline short apply(const short *src)
|
inline short apply(const short *src) {
|
||||||
{
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
short result;
|
short result;
|
||||||
vDSP_dotpr_s1_15(filter_coefficients_, 1, src, 1, &result, number_of_taps_);
|
vDSP_dotpr_s1_15(filter_coefficients_, 1, src, 1, &result, number_of_taps_);
|
||||||
return result;
|
return result;
|
||||||
#else
|
#else
|
||||||
int outputValue = 0;
|
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];
|
outputValue += filter_coefficients_[c] * src[c];
|
||||||
}
|
}
|
||||||
return (short)(outputValue >> kCSKaiserBesselFilterFixedShift);
|
return (short)(outputValue >> kCSKaiserBesselFilterFixedShift);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
inline unsigned int get_number_of_taps()
|
inline unsigned int get_number_of_taps() {
|
||||||
{
|
|
||||||
return 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
|
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.
|
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:
|
public:
|
||||||
/*!
|
/*!
|
||||||
Establishes a stepper with a one-to-one conversion rate.
|
Establishes a stepper with a one-to-one conversion rate.
|
||||||
@@ -48,12 +47,10 @@ class Stepper
|
|||||||
|
|
||||||
@returns the number of output steps.
|
@returns the number of output steps.
|
||||||
*/
|
*/
|
||||||
inline uint64_t step()
|
inline uint64_t step() {
|
||||||
{
|
|
||||||
uint64_t update = whole_step_;
|
uint64_t update = whole_step_;
|
||||||
accumulated_error_ += adjustment_up_;
|
accumulated_error_ += adjustment_up_;
|
||||||
if(accumulated_error_ > 0)
|
if(accumulated_error_ > 0) {
|
||||||
{
|
|
||||||
update++;
|
update++;
|
||||||
accumulated_error_ -= adjustment_down_;
|
accumulated_error_ -= adjustment_down_;
|
||||||
}
|
}
|
||||||
@@ -65,12 +62,10 @@ class Stepper
|
|||||||
|
|
||||||
@returns the number of output steps.
|
@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;
|
uint64_t update = whole_step_ * number_of_steps;
|
||||||
accumulated_error_ += adjustment_up_ * (int64_t)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_);
|
update += 1 + (uint64_t)(accumulated_error_ / adjustment_down_);
|
||||||
accumulated_error_ = (accumulated_error_ % adjustment_down_) - adjustment_down_;
|
accumulated_error_ = (accumulated_error_ % adjustment_down_) - adjustment_down_;
|
||||||
}
|
}
|
||||||
@@ -80,16 +75,14 @@ class Stepper
|
|||||||
/*!
|
/*!
|
||||||
@returns the output rate.
|
@returns the output rate.
|
||||||
*/
|
*/
|
||||||
inline uint64_t get_output_rate()
|
inline uint64_t get_output_rate() {
|
||||||
{
|
|
||||||
return output_rate_;
|
return output_rate_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@returns the input rate.
|
@returns the input rate.
|
||||||
*/
|
*/
|
||||||
inline uint64_t get_input_rate()
|
inline uint64_t get_input_rate() {
|
||||||
{
|
|
||||||
return input_rate_;
|
return input_rate_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,7 @@
|
|||||||
|
|
||||||
using namespace StaticAnalyser::Acorn;
|
using namespace StaticAnalyser::Acorn;
|
||||||
|
|
||||||
std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk)
|
std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||||
{
|
|
||||||
// c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format
|
// c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format
|
||||||
std::unique_ptr<Catalogue> catalogue(new Catalogue);
|
std::unique_ptr<Catalogue> catalogue(new Catalogue);
|
||||||
Storage::Encodings::MFM::Parser parser(false, disk);
|
Storage::Encodings::MFM::Parser parser(false, disk);
|
||||||
@@ -34,8 +33,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
|
|||||||
snprintf(disk_name, 13, "%.8s%.4s", &names->data[0], &details->data[0]);
|
snprintf(disk_name, 13, "%.8s%.4s", &names->data[0], &details->data[0]);
|
||||||
catalogue->name = disk_name;
|
catalogue->name = disk_name;
|
||||||
|
|
||||||
switch((details->data[6] >> 4)&3)
|
switch((details->data[6] >> 4)&3) {
|
||||||
{
|
|
||||||
case 0: catalogue->bootOption = Catalogue::BootOption::None; break;
|
case 0: catalogue->bootOption = Catalogue::BootOption::None; break;
|
||||||
case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break;
|
case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break;
|
||||||
case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break;
|
case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break;
|
||||||
@@ -44,8 +42,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
|
|||||||
|
|
||||||
// DFS files are stored contiguously, and listed in descending order of distance from track 0.
|
// DFS files are stored contiguously, and listed in descending order of distance from track 0.
|
||||||
// So iterating backwards implies the least amount of seeking.
|
// So iterating backwards implies the least amount of seeking.
|
||||||
for(size_t file_offset = final_file_offset - 8; file_offset > 0; file_offset -= 8)
|
for(size_t file_offset = final_file_offset - 8; file_offset > 0; file_offset -= 8) {
|
||||||
{
|
|
||||||
File new_file;
|
File new_file;
|
||||||
char name[10];
|
char name[10];
|
||||||
snprintf(name, 10, "%c.%.7s", names->data[file_offset + 7] & 0x7f, &names->data[file_offset]);
|
snprintf(name, 10, "%c.%.7s", names->data[file_offset + 7] & 0x7f, &names->data[file_offset]);
|
||||||
@@ -59,8 +56,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
|
|||||||
new_file.data.reserve((size_t)data_length);
|
new_file.data.reserve((size_t)data_length);
|
||||||
|
|
||||||
if(start_sector < 2) continue;
|
if(start_sector < 2) continue;
|
||||||
while(data_length > 0)
|
while(data_length > 0) {
|
||||||
{
|
|
||||||
uint8_t sector = (uint8_t)(start_sector % 10);
|
uint8_t sector = (uint8_t)(start_sector % 10);
|
||||||
uint8_t track = (uint8_t)(start_sector / 10);
|
uint8_t track = (uint8_t)(start_sector / 10);
|
||||||
start_sector++;
|
start_sector++;
|
||||||
@@ -77,8 +73,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
|
|||||||
|
|
||||||
return catalogue;
|
return catalogue;
|
||||||
}
|
}
|
||||||
std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk)
|
std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||||
{
|
|
||||||
std::unique_ptr<Catalogue> catalogue(new Catalogue);
|
std::unique_ptr<Catalogue> catalogue(new Catalogue);
|
||||||
Storage::Encodings::MFM::Parser parser(true, disk);
|
Storage::Encodings::MFM::Parser parser(true, disk);
|
||||||
|
|
||||||
@@ -87,8 +82,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::sh
|
|||||||
|
|
||||||
std::vector<uint8_t> root_directory;
|
std::vector<uint8_t> root_directory;
|
||||||
root_directory.reserve(5 * 256);
|
root_directory.reserve(5 * 256);
|
||||||
for(uint8_t c = 2; c < 7; c++)
|
for(uint8_t c = 2; c < 7; c++) {
|
||||||
{
|
|
||||||
std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector(0, c);
|
std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector(0, c);
|
||||||
if(!sector) return nullptr;
|
if(!sector) return nullptr;
|
||||||
root_directory.insert(root_directory.end(), sector->data.begin(), sector->data.end());
|
root_directory.insert(root_directory.end(), sector->data.begin(), sector->data.end());
|
||||||
@@ -99,8 +93,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::sh
|
|||||||
if(root_directory[1] != 'H' || root_directory[2] != 'u' || root_directory[3] != 'g' || root_directory[4] != 'o') return nullptr;
|
if(root_directory[1] != 'H' || root_directory[2] != 'u' || root_directory[3] != 'g' || root_directory[4] != 'o') return nullptr;
|
||||||
if(root_directory[0x4FB] != 'H' || root_directory[0x4FC] != 'u' || root_directory[0x4FD] != 'g' || root_directory[0x4FE] != 'o') return nullptr;
|
if(root_directory[0x4FB] != 'H' || root_directory[0x4FC] != 'u' || root_directory[0x4FD] != 'g' || root_directory[0x4FE] != 'o') return nullptr;
|
||||||
|
|
||||||
switch(free_space_map_second_half->data[0xfd])
|
switch(free_space_map_second_half->data[0xfd]) {
|
||||||
{
|
|
||||||
default: catalogue->bootOption = Catalogue::BootOption::None; break;
|
default: catalogue->bootOption = Catalogue::BootOption::None; break;
|
||||||
case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break;
|
case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break;
|
||||||
case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break;
|
case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break;
|
||||||
|
|||||||