mirror of
https://github.com/TomHarte/CLK.git
synced 2025-10-25 09:27:01 +00:00
Compare commits
461 Commits
2016-12-06
...
2017-05-16
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15394358df | ||
|
|
df4d4467b3 | ||
|
|
67ec0b9e6c | ||
|
|
2ee8a7056e | ||
|
|
a5075d9eb5 | ||
|
|
abeaedf16f | ||
|
|
8e35e913bb | ||
|
|
81c5f4ab19 | ||
|
|
e270b726b3 | ||
|
|
c2b5a9bb1f | ||
|
|
44ce7fa54c | ||
|
|
b0142cf050 | ||
|
|
a340331229 | ||
|
|
b14c892740 | ||
|
|
15d17c12d5 | ||
|
|
99800d9840 | ||
|
|
5d91a2600d | ||
|
|
cb66c7e2dc | ||
|
|
58488c93be | ||
|
|
61f8f2f18c | ||
|
|
7b43ae0a92 | ||
|
|
2807e3134f | ||
|
|
0771363f3b | ||
|
|
1f56e85f6d | ||
|
|
2edf73908c | ||
|
|
6a37a02eee | ||
|
|
5998123868 | ||
|
|
26cb903b08 | ||
|
|
92a8b68859 | ||
|
|
4127350abe | ||
|
|
ed6b135015 | ||
|
|
f95015c7f6 | ||
|
|
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 | ||
|
|
9d60172571 | ||
|
|
eca3995481 | ||
|
|
044c920a5b | ||
|
|
0df9ce5a76 | ||
|
|
f94f34f053 | ||
|
|
4ad2d2bedd | ||
|
|
e28f72d919 | ||
|
|
c994fa39f6 | ||
|
|
1ea4f0d79d | ||
|
|
0689df1349 | ||
|
|
b3c33d993a | ||
|
|
8eb21c6702 | ||
|
|
4c62487e6e | ||
|
|
a147d56ce6 | ||
|
|
7b696b0962 | ||
|
|
57bb771fb7 | ||
|
|
5201a59c44 | ||
|
|
df6e98fa52 | ||
|
|
52b850a3f5 | ||
|
|
cfbab1448c | ||
|
|
12549ff412 | ||
|
|
6f0b5427e4 | ||
|
|
0123b37213 | ||
|
|
ea4d85e1cd | ||
|
|
f217d508b8 | ||
|
|
1f625fad66 | ||
|
|
632b3c63b1 | ||
|
|
d581294479 | ||
|
|
0f399b0a0c | ||
|
|
c6fcc40ac5 | ||
|
|
3b29e6a473 | ||
|
|
07dacff42d | ||
|
|
c85450648f | ||
|
|
c740d9655a | ||
|
|
d09e7ac1e8 | ||
|
|
5d63556870 | ||
|
|
e5cc77f22d | ||
|
|
81a3cbac45 | ||
|
|
63ff5165a4 | ||
|
|
71dbd78cf2 | ||
|
|
f88f3c65e9 | ||
|
|
82bb78fb2d | ||
|
|
6fc692cd34 | ||
|
|
bbd94749f4 | ||
|
|
54900ca3fb | ||
|
|
a8bc9d830e | ||
|
|
b9fad184d7 | ||
|
|
af1b396c9e | ||
|
|
9cb902cc4f | ||
|
|
e4000bd060 | ||
|
|
ce814c9e99 | ||
|
|
bfe6c0a0c1 | ||
|
|
4adcb46665 | ||
|
|
46a93d2e12 | ||
|
|
1277a67f9a | ||
|
|
720b1e5802 | ||
|
|
8cd1575891 | ||
|
|
3a9ad3fb08 | ||
|
|
90151e2094 | ||
|
|
7a627b782d | ||
|
|
a568172758 | ||
|
|
99993a1b24 | ||
|
|
9c0f622a2e | ||
|
|
0490a47058 | ||
|
|
83c433c142 | ||
|
|
742c5df367 | ||
|
|
b538ee5bd8 | ||
|
|
a6d038cad9 | ||
|
|
4fca30b81f | ||
|
|
26710c988d | ||
|
|
acc35885cd | ||
|
|
c0a1264ab0 | ||
|
|
e2b829f68e | ||
|
|
beaa868079 | ||
|
|
1349e85d83 | ||
|
|
74e98fd097 | ||
|
|
007c13ec16 | ||
|
|
98be6ede45 | ||
|
|
d2ad2c756e | ||
|
|
ec55a25620 | ||
|
|
aceb7e3b6b | ||
|
|
901f19f89c | ||
|
|
e56beb3e9c | ||
|
|
9d555c4a02 | ||
|
|
b57038edc5 | ||
|
|
d606bd7ce5 | ||
|
|
09ff9d6a26 | ||
|
|
e25195a718 | ||
|
|
af69b21033 | ||
|
|
f601d796f5 | ||
|
|
6e94d0c19f | ||
|
|
7f303cfceb | ||
|
|
afc6f4129c | ||
|
|
1e416d4af0 | ||
|
|
bedea48d03 | ||
|
|
4cb17143ef | ||
|
|
4d4852bb78 | ||
|
|
4728bda0a2 | ||
|
|
1e970a9772 | ||
|
|
42f25cdffc | ||
|
|
393dc5c64f | ||
|
|
3805e3d17d | ||
|
|
7028f57336 | ||
|
|
e4e0347638 | ||
|
|
72ca06cf8d | ||
|
|
6a0c7f22ee | ||
|
|
03579f33f1 | ||
|
|
7eca910cc5 | ||
|
|
c180340474 | ||
|
|
823ab9bc34 | ||
|
|
5a508ea0df | ||
|
|
63d861a2f3 | ||
|
|
6f17076003 | ||
|
|
497b2ae4dd | ||
|
|
6bdde542c5 | ||
|
|
ec624eaab1 | ||
|
|
1ef1f6ec69 | ||
|
|
8f937ceac8 | ||
|
|
1df478d250 | ||
|
|
e081f224b6 | ||
|
|
a6354ebb01 | ||
|
|
f9a5595dad | ||
|
|
3297f6d545 | ||
|
|
3116a2cf4c | ||
|
|
254cc41fd6 | ||
|
|
313db75303 | ||
|
|
3017062e89 | ||
|
|
f1a08b7ab5 | ||
|
|
dc08a23ceb | ||
|
|
1e757d1039 | ||
|
|
ea1b3d447b | ||
|
|
63107cd492 | ||
|
|
a555c5762a | ||
|
|
4a7ddaf2e9 | ||
|
|
f61176cd7d | ||
|
|
c1c70a767a | ||
|
|
0326316bb8 | ||
|
|
b58b11fc93 | ||
|
|
fd541e1142 | ||
|
|
be7e05e109 | ||
|
|
c5cf8d9531 | ||
|
|
52028432e1 | ||
|
|
0aae1bd1ef | ||
|
|
c43e481a33 | ||
|
|
54b5056c74 | ||
|
|
0653770c63 | ||
|
|
e62be03673 | ||
|
|
34d213dec4 | ||
|
|
81a102d951 | ||
|
|
a5683dfb21 | ||
|
|
0e71802b92 | ||
|
|
580f347727 | ||
|
|
a549fd1ecc | ||
|
|
e359441e2f | ||
|
|
6cdd41e5a9 | ||
|
|
3b5962b171 | ||
|
|
c4041b06a8 | ||
|
|
46ebae7e4b | ||
|
|
c304db0f5a | ||
|
|
4d3bdf8c7c |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -20,6 +20,7 @@ DerivedData
|
||||
|
||||
# Exclude system ROMs
|
||||
ROMImages/*
|
||||
OSBindings/Mac/Clock SignalTests/Atari\ ROMs
|
||||
|
||||
# CocoaPods
|
||||
#
|
||||
|
||||
5
.travis.yml
Normal file
5
.travis.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
language: objective-c
|
||||
osx_image: xcode8.2
|
||||
xcode_project: OSBindings/Mac/Clock Signal.xcodeproj
|
||||
xcode_scheme: Clock Signal
|
||||
xcode_sdk: macosx10.12
|
||||
@@ -12,36 +12,35 @@
|
||||
using namespace WD;
|
||||
|
||||
WD1770::Status::Status() :
|
||||
type(Status::One),
|
||||
write_protect(false),
|
||||
record_type(false),
|
||||
spin_up(false),
|
||||
record_not_found(false),
|
||||
crc_error(false),
|
||||
seek_error(false),
|
||||
lost_data(false),
|
||||
data_request(false),
|
||||
busy(false)
|
||||
{}
|
||||
type(Status::One),
|
||||
write_protect(false),
|
||||
record_type(false),
|
||||
spin_up(false),
|
||||
record_not_found(false),
|
||||
crc_error(false),
|
||||
seek_error(false),
|
||||
lost_data(false),
|
||||
data_request(false),
|
||||
interrupt_request(false),
|
||||
busy(false) {}
|
||||
|
||||
WD1770::WD1770(Personality p) :
|
||||
Storage::Disk::Controller(8000000, 16, 300),
|
||||
interesting_event_mask_(Event::Command),
|
||||
resume_point_(0),
|
||||
delay_time_(0),
|
||||
index_hole_count_target_(-1),
|
||||
is_awaiting_marker_value_(false),
|
||||
is_reading_data_(false),
|
||||
delegate_(nullptr),
|
||||
personality_(p),
|
||||
head_is_loaded_(false)
|
||||
{
|
||||
Storage::Disk::Controller(8000000, 16, 300),
|
||||
crc_generator_(0x1021, 0xffff),
|
||||
interesting_event_mask_(Event::Command),
|
||||
resume_point_(0),
|
||||
delay_time_(0),
|
||||
index_hole_count_target_(-1),
|
||||
is_awaiting_marker_value_(false),
|
||||
data_mode_(DataMode::Scanning),
|
||||
delegate_(nullptr),
|
||||
personality_(p),
|
||||
head_is_loaded_(false) {
|
||||
set_is_double_density(false);
|
||||
posit_event(Event::Command);
|
||||
}
|
||||
|
||||
void WD1770::set_is_double_density(bool is_double_density)
|
||||
{
|
||||
void WD1770::set_is_double_density(bool is_double_density) {
|
||||
is_double_density_ = is_double_density;
|
||||
Storage::Time bit_length;
|
||||
bit_length.length = 1;
|
||||
@@ -51,21 +50,15 @@ void WD1770::set_is_double_density(bool is_double_density)
|
||||
if(!is_double_density) is_awaiting_marker_value_ = false;
|
||||
}
|
||||
|
||||
void WD1770::set_register(int address, uint8_t value)
|
||||
{
|
||||
switch(address&3)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
if((value&0xf0) == 0xd0)
|
||||
{
|
||||
void WD1770::set_register(int address, uint8_t value) {
|
||||
switch(address&3) {
|
||||
case 0: {
|
||||
if((value&0xf0) == 0xd0) {
|
||||
printf("!!!TODO: force interrupt!!!\n");
|
||||
update_status([] (Status &status) {
|
||||
status.type = Status::One;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
command_ = value;
|
||||
posit_event(Event::Command);
|
||||
}
|
||||
@@ -73,22 +66,26 @@ void WD1770::set_register(int address, uint8_t value)
|
||||
break;
|
||||
case 1: track_ = value; break;
|
||||
case 2: sector_ = value; break;
|
||||
case 3: data_ = value; break;
|
||||
case 3:
|
||||
data_ = value;
|
||||
update_status([] (Status &status) {
|
||||
status.data_request = false;
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t WD1770::get_register(int address)
|
||||
{
|
||||
switch(address&3)
|
||||
{
|
||||
default:
|
||||
{
|
||||
uint8_t WD1770::get_register(int address) {
|
||||
switch(address&3) {
|
||||
default: {
|
||||
update_status([] (Status &status) {
|
||||
status.interrupt_request = false;
|
||||
});
|
||||
uint8_t status =
|
||||
(status_.write_protect ? Flag::WriteProtect : 0) |
|
||||
(status_.crc_error ? Flag::CRCError : 0) |
|
||||
(status_.busy ? Flag::Busy : 0);
|
||||
switch(status_.type)
|
||||
{
|
||||
switch(status_.type) {
|
||||
case Status::One:
|
||||
status |=
|
||||
(get_is_track_zero() ? Flag::TrackZero : 0) |
|
||||
@@ -106,14 +103,11 @@ uint8_t WD1770::get_register(int address)
|
||||
break;
|
||||
}
|
||||
|
||||
if(!has_motor_on_line())
|
||||
{
|
||||
if(!has_motor_on_line()) {
|
||||
status |= get_drive_is_ready() ? 0 : Flag::NotReady;
|
||||
if(status_.type == Status::One)
|
||||
status |= (head_is_loaded_ ? Flag::HeadLoaded : 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
status |= (get_motor_on() ? Flag::MotorOn : 0);
|
||||
if(status_.type == Status::One)
|
||||
status |= (status_.spin_up ? Flag::SpinUp : 0);
|
||||
@@ -130,71 +124,75 @@ uint8_t WD1770::get_register(int address)
|
||||
}
|
||||
}
|
||||
|
||||
void WD1770::run_for_cycles(unsigned int number_of_cycles)
|
||||
{
|
||||
void WD1770::run_for_cycles(unsigned int number_of_cycles) {
|
||||
Storage::Disk::Controller::run_for_cycles((int)number_of_cycles);
|
||||
|
||||
if(delay_time_)
|
||||
{
|
||||
if(delay_time_ <= number_of_cycles)
|
||||
{
|
||||
if(delay_time_) {
|
||||
if(delay_time_ <= number_of_cycles) {
|
||||
delay_time_ = 0;
|
||||
posit_event(Event::Timer);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
delay_time_ -= number_of_cycles;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole)
|
||||
{
|
||||
void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole) {
|
||||
if(data_mode_ == DataMode::Writing) return;
|
||||
|
||||
shift_register_ = (shift_register_ << 1) | value;
|
||||
bits_since_token_++;
|
||||
|
||||
Token::Type token_type = Token::Byte;
|
||||
if(!is_reading_data_)
|
||||
{
|
||||
if(!is_double_density_)
|
||||
{
|
||||
switch(shift_register_ & 0xffff)
|
||||
{
|
||||
if(data_mode_ == DataMode::Scanning) {
|
||||
Token::Type token_type = Token::Byte;
|
||||
if(!is_double_density_) {
|
||||
switch(shift_register_ & 0xffff) {
|
||||
case Storage::Encodings::MFM::FMIndexAddressMark:
|
||||
token_type = Token::Index;
|
||||
crc_generator_.reset();
|
||||
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::IndexAddressByte);
|
||||
break;
|
||||
case Storage::Encodings::MFM::FMIDAddressMark:
|
||||
token_type = Token::ID;
|
||||
crc_generator_.reset();
|
||||
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::IDAddressByte);
|
||||
break;
|
||||
case Storage::Encodings::MFM::FMDataAddressMark:
|
||||
token_type = Token::Data;
|
||||
crc_generator_.reset();
|
||||
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::DataAddressByte);
|
||||
break;
|
||||
case Storage::Encodings::MFM::FMDeletedDataAddressMark:
|
||||
token_type = Token::DeletedData;
|
||||
crc_generator_.reset();
|
||||
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::DeletedDataAddressByte);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch(shift_register_ & 0xffff)
|
||||
{
|
||||
case Storage::Encodings::MFM::MFMIndexAddressMark:
|
||||
} else {
|
||||
switch(shift_register_ & 0xffff) {
|
||||
case Storage::Encodings::MFM::MFMIndexSync:
|
||||
bits_since_token_ = 0;
|
||||
is_awaiting_marker_value_ = true;
|
||||
return;
|
||||
case Storage::Encodings::MFM::MFMAddressMark:
|
||||
|
||||
token_type = Token::Sync;
|
||||
latest_token_.byte_value = Storage::Encodings::MFM::MFMIndexSyncByteValue;
|
||||
break;
|
||||
case Storage::Encodings::MFM::MFMSync:
|
||||
bits_since_token_ = 0;
|
||||
is_awaiting_marker_value_ = true;
|
||||
return;
|
||||
crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
|
||||
|
||||
token_type = Token::Sync;
|
||||
latest_token_.byte_value = Storage::Encodings::MFM::MFMSyncByteValue;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(token_type != Token::Byte)
|
||||
{
|
||||
if(token_type != Token::Byte) {
|
||||
latest_token_.type = token_type;
|
||||
bits_since_token_ = 0;
|
||||
posit_event(Event::Token);
|
||||
@@ -202,8 +200,7 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole)
|
||||
}
|
||||
}
|
||||
|
||||
if(bits_since_token_ == 16)
|
||||
{
|
||||
if(bits_since_token_ == 16) {
|
||||
latest_token_.type = Token::Byte;
|
||||
latest_token_.byte_value = (uint8_t)(
|
||||
((shift_register_ & 0x0001) >> 0) |
|
||||
@@ -216,83 +213,64 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole)
|
||||
((shift_register_ & 0x4000) >> 7));
|
||||
bits_since_token_ = 0;
|
||||
|
||||
if(is_awaiting_marker_value_ && is_double_density_)
|
||||
{
|
||||
if(is_awaiting_marker_value_ && is_double_density_) {
|
||||
is_awaiting_marker_value_ = false;
|
||||
switch(latest_token_.byte_value)
|
||||
{
|
||||
case Storage::Encodings::MFM::MFMIndexAddressByte:
|
||||
switch(latest_token_.byte_value) {
|
||||
case Storage::Encodings::MFM::IndexAddressByte:
|
||||
latest_token_.type = Token::Index;
|
||||
break;
|
||||
case Storage::Encodings::MFM::MFMIDAddressByte:
|
||||
case Storage::Encodings::MFM::IDAddressByte:
|
||||
latest_token_.type = Token::ID;
|
||||
break;
|
||||
case Storage::Encodings::MFM::MFMDataAddressByte:
|
||||
case Storage::Encodings::MFM::DataAddressByte:
|
||||
latest_token_.type = Token::Data;
|
||||
break;
|
||||
case Storage::Encodings::MFM::MFMDeletedDataAddressByte:
|
||||
case Storage::Encodings::MFM::DeletedDataAddressByte:
|
||||
latest_token_.type = Token::DeletedData;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
crc_generator_.add(latest_token_.byte_value);
|
||||
posit_event(Event::Token);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void WD1770::process_index_hole()
|
||||
{
|
||||
void WD1770::process_index_hole() {
|
||||
index_hole_count_++;
|
||||
posit_event(Event::IndexHole);
|
||||
if(index_hole_count_target_ == index_hole_count_)
|
||||
{
|
||||
if(index_hole_count_target_ == index_hole_count_) {
|
||||
posit_event(Event::IndexHoleTarget);
|
||||
index_hole_count_target_ = -1;
|
||||
}
|
||||
|
||||
// motor power-down
|
||||
if(index_hole_count_ == 9 && !status_.busy && has_motor_on_line())
|
||||
{
|
||||
if(index_hole_count_ == 9 && !status_.busy && has_motor_on_line()) {
|
||||
set_motor_on(false);
|
||||
}
|
||||
|
||||
// head unload
|
||||
if(index_hole_count_ == 15 && !status_.busy && has_head_load_line())
|
||||
{
|
||||
if(index_hole_count_ == 15 && !status_.busy && has_head_load_line()) {
|
||||
set_head_load_request(false);
|
||||
}
|
||||
}
|
||||
|
||||
// +------+----------+-------------------------+
|
||||
// ! ! ! BITS !
|
||||
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
|
||||
// +------+----------+-------------------------+
|
||||
// ! 1 ! Restore ! 0 0 0 0 h v r1 r0 !
|
||||
// ! 1 ! Seek ! 0 0 0 1 h v r1 r0 !
|
||||
// ! 1 ! Step ! 0 0 1 u h v r1 r0 !
|
||||
// ! 1 ! Step-in ! 0 1 0 u h v r1 r0 !
|
||||
// ! 1 ! Step-out ! 0 1 1 u h v r1 r0 !
|
||||
// ! 2 ! Rd sectr ! 1 0 0 m h E 0 0 !
|
||||
// ! 2 ! Wt sectr ! 1 0 1 m h E P a0 !
|
||||
// ! 3 ! Rd addr ! 1 1 0 0 h E 0 0 !
|
||||
// ! 3 ! Rd track ! 1 1 1 0 h E 0 0 !
|
||||
// ! 3 ! Wt track ! 1 1 1 1 h E P 0 !
|
||||
// ! 4 ! Forc int ! 1 1 0 1 i3 i2 i1 i0 !
|
||||
// +------+----------+-------------------------+
|
||||
void WD1770::process_write_completed() {
|
||||
posit_event(Event::DataWritten);
|
||||
}
|
||||
|
||||
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = mask; return; case __LINE__:
|
||||
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = Event::Timer; delay_time_ = ms * 8000; if(delay_time_) return; case __LINE__:
|
||||
#define WAIT_FOR_BYTES(count) resume_point_ = __LINE__; interesting_event_mask_ = Event::Token; distance_into_section_ = 0; return; case __LINE__: if(latest_token_.type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = Event::Token; return; }
|
||||
#define BEGIN_SECTION() switch(resume_point_) { default:
|
||||
#define END_SECTION() 0; }
|
||||
|
||||
#define READ_ID() \
|
||||
if(new_event_type == Event::Token) \
|
||||
{ \
|
||||
if(!distance_into_section_ && latest_token_.type == Token::ID) {is_reading_data_ = true; distance_into_section_++; } \
|
||||
else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte) \
|
||||
{ \
|
||||
if(new_event_type == Event::Token) { \
|
||||
if(!distance_into_section_ && latest_token_.type == Token::ID) {data_mode_ = DataMode::Reading; distance_into_section_++; } \
|
||||
else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte) { \
|
||||
header_[distance_into_section_ - 1] = latest_token_.byte_value; \
|
||||
distance_into_section_++; \
|
||||
} \
|
||||
@@ -309,9 +287,24 @@ void WD1770::process_index_hole()
|
||||
WAIT_FOR_EVENT(Event::IndexHoleTarget); \
|
||||
status_.spin_up = true;
|
||||
|
||||
// +--------+----------+-------------------------+
|
||||
// ! ! ! BITS !
|
||||
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
|
||||
// +--------+----------+-------------------------+
|
||||
// ! 1 ! Restore ! 0 0 0 0 h v r1 r0 !
|
||||
// ! 1 ! Seek ! 0 0 0 1 h v r1 r0 !
|
||||
// ! 1 ! Step ! 0 0 1 u h v r1 r0 !
|
||||
// ! 1 ! Step-in ! 0 1 0 u h v r1 r0 !
|
||||
// ! 1 ! Step-out ! 0 1 1 u h v r1 r0 !
|
||||
// ! 2 ! Rd sectr ! 1 0 0 m h E 0 0 !
|
||||
// ! 2 ! Wt sectr ! 1 0 1 m h E P a0 !
|
||||
// ! 3 ! Rd addr ! 1 1 0 0 h E 0 0 !
|
||||
// ! 3 ! Rd track ! 1 1 1 0 h E 0 0 !
|
||||
// ! 3 ! Wt track ! 1 1 1 1 h E P 0 !
|
||||
// ! 4 ! Forc int ! 1 1 0 1 i3 i2 i1 i0 !
|
||||
// +--------+----------+-------------------------+
|
||||
|
||||
void WD1770::posit_event(Event new_event_type)
|
||||
{
|
||||
void WD1770::posit_event(Event new_event_type) {
|
||||
if(!(interesting_event_mask_ & (int)new_event_type)) return;
|
||||
interesting_event_mask_ &= ~new_event_type;
|
||||
|
||||
@@ -321,17 +314,19 @@ void WD1770::posit_event(Event new_event_type)
|
||||
// Wait for a new command, branch to the appropriate handler.
|
||||
wait_for_command:
|
||||
printf("Idle...\n");
|
||||
is_reading_data_ = false;
|
||||
data_mode_ = DataMode::Scanning;
|
||||
index_hole_count_ = 0;
|
||||
|
||||
update_status([] (Status &status) {
|
||||
status.busy = false;
|
||||
status.interrupt_request = true;
|
||||
});
|
||||
|
||||
WAIT_FOR_EVENT(Event::Command);
|
||||
|
||||
update_status([] (Status &status) {
|
||||
status.busy = true;
|
||||
status.interrupt_request = false;
|
||||
});
|
||||
|
||||
printf("Starting %02x\n", command_);
|
||||
@@ -344,6 +339,17 @@ void WD1770::posit_event(Event new_event_type)
|
||||
/*
|
||||
Type 1 entry point.
|
||||
*/
|
||||
// +--------+----------+-------------------------+
|
||||
// ! ! ! BITS !
|
||||
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
|
||||
// +--------+----------+-------------------------+
|
||||
// ! 1 ! Restore ! 0 0 0 0 h v r1 r0 !
|
||||
// ! 1 ! Seek ! 0 0 0 1 h v r1 r0 !
|
||||
// ! 1 ! Step ! 0 0 1 u h v r1 r0 !
|
||||
// ! 1 ! Step-in ! 0 1 0 u h v r1 r0 !
|
||||
// ! 1 ! Step-out ! 0 1 1 u h v r1 r0 !
|
||||
// +--------+----------+-------------------------+
|
||||
|
||||
begin_type_1:
|
||||
// Set initial flags, skip spin-up if possible.
|
||||
update_status([] (Status &status) {
|
||||
@@ -359,8 +365,7 @@ void WD1770::posit_event(Event new_event_type)
|
||||
goto begin_type1_load_head;
|
||||
|
||||
begin_type1_load_head:
|
||||
if(!(command_&0x08))
|
||||
{
|
||||
if(!(command_&0x08)) {
|
||||
set_head_load_request(false);
|
||||
goto test_type1_type;
|
||||
}
|
||||
@@ -380,8 +385,7 @@ void WD1770::posit_event(Event new_event_type)
|
||||
if((command_ >> 5) != 0) goto perform_step_command;
|
||||
|
||||
// This is now definitely either a seek or a restore; if it's a restore then set track to 0xff and data to 0x00.
|
||||
if(!(command_ & 0x10))
|
||||
{
|
||||
if(!(command_ & 0x10)) {
|
||||
track_ = 0xff;
|
||||
data_ = 0;
|
||||
}
|
||||
@@ -394,15 +398,13 @@ void WD1770::posit_event(Event new_event_type)
|
||||
if(step_direction_) track_++; else track_--;
|
||||
|
||||
perform_step:
|
||||
if(!step_direction_ && get_is_track_zero())
|
||||
{
|
||||
if(!step_direction_ && get_is_track_zero()) {
|
||||
track_ = 0;
|
||||
goto verify;
|
||||
}
|
||||
step(step_direction_ ? 1 : -1);
|
||||
int time_to_wait;
|
||||
switch(command_ & 3)
|
||||
{
|
||||
switch(command_ & 3) {
|
||||
default:
|
||||
case 0: time_to_wait = 6; break;
|
||||
case 1: time_to_wait = 12; break;
|
||||
@@ -418,8 +420,7 @@ void WD1770::posit_event(Event new_event_type)
|
||||
goto perform_step;
|
||||
|
||||
verify:
|
||||
if(!(command_ & 0x04))
|
||||
{
|
||||
if(!(command_ & 0x04)) {
|
||||
goto wait_for_command;
|
||||
}
|
||||
|
||||
@@ -430,19 +431,22 @@ void WD1770::posit_event(Event new_event_type)
|
||||
WAIT_FOR_EVENT(Event::IndexHole | Event::Token);
|
||||
READ_ID();
|
||||
|
||||
if(index_hole_count_ == 6)
|
||||
{
|
||||
if(index_hole_count_ == 6) {
|
||||
update_status([] (Status &status) {
|
||||
status.seek_error = true;
|
||||
});
|
||||
goto wait_for_command;
|
||||
}
|
||||
if(distance_into_section_ == 7)
|
||||
{
|
||||
is_reading_data_ = false;
|
||||
// TODO: CRC check
|
||||
if(header_[0] == track_)
|
||||
{
|
||||
if(distance_into_section_ == 7) {
|
||||
data_mode_ = DataMode::Scanning;
|
||||
if(crc_generator_.get_value()) {
|
||||
update_status([] (Status &status) {
|
||||
status.crc_error = true;
|
||||
});
|
||||
goto verify_read_data;
|
||||
}
|
||||
|
||||
if(header_[0] == track_) {
|
||||
printf("Reached track %d\n", track_);
|
||||
update_status([] (Status &status) {
|
||||
status.crc_error = false;
|
||||
@@ -458,6 +462,14 @@ void WD1770::posit_event(Event new_event_type)
|
||||
/*
|
||||
Type 2 entry point.
|
||||
*/
|
||||
// +--------+----------+-------------------------+
|
||||
// ! ! ! BITS !
|
||||
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
|
||||
// +--------+----------+-------------------------+
|
||||
// ! 2 ! Rd sectr ! 1 0 0 m h E 0 0 !
|
||||
// ! 2 ! Wt sectr ! 1 0 1 m h E P a0 !
|
||||
// +--------+----------+-------------------------+
|
||||
|
||||
begin_type_2:
|
||||
update_status([] (Status &status) {
|
||||
status.type = Status::Two;
|
||||
@@ -492,8 +504,7 @@ void WD1770::posit_event(Event new_event_type)
|
||||
WAIT_FOR_TIME(30);
|
||||
|
||||
test_type2_write_protection:
|
||||
if(command_&0x20) // TODO:: && is_write_protected
|
||||
{
|
||||
if(command_&0x20 && get_drive_is_read_only()) {
|
||||
update_status([] (Status &status) {
|
||||
status.write_protect = true;
|
||||
});
|
||||
@@ -504,21 +515,30 @@ void WD1770::posit_event(Event new_event_type)
|
||||
WAIT_FOR_EVENT(Event::IndexHole | Event::Token);
|
||||
READ_ID();
|
||||
|
||||
if(index_hole_count_ == 5)
|
||||
{
|
||||
if(index_hole_count_ == 5) {
|
||||
printf("Failed to find sector %d\n", sector_);
|
||||
update_status([] (Status &status) {
|
||||
status.record_not_found = true;
|
||||
});
|
||||
goto wait_for_command;
|
||||
}
|
||||
if(distance_into_section_ == 7)
|
||||
{
|
||||
is_reading_data_ = false;
|
||||
if(header_[0] == track_ && header_[2] == sector_ &&
|
||||
(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1]))
|
||||
{
|
||||
// TODO: test CRC
|
||||
if(distance_into_section_ == 7) {
|
||||
printf("Considering %d/%d\n", header_[0], header_[2]);
|
||||
data_mode_ = DataMode::Scanning;
|
||||
if( header_[0] == track_ && header_[2] == sector_ &&
|
||||
(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1])) {
|
||||
printf("Found %d/%d\n", header_[0], header_[2]);
|
||||
if(crc_generator_.get_value()) {
|
||||
printf("CRC error; back to searching\n");
|
||||
update_status([] (Status &status) {
|
||||
status.crc_error = true;
|
||||
});
|
||||
goto type2_get_header;
|
||||
}
|
||||
|
||||
update_status([] (Status &status) {
|
||||
status.crc_error = false;
|
||||
});
|
||||
goto type2_read_or_write_data;
|
||||
}
|
||||
distance_into_section_ = 0;
|
||||
@@ -533,13 +553,12 @@ void WD1770::posit_event(Event new_event_type)
|
||||
type2_read_data:
|
||||
WAIT_FOR_EVENT(Event::Token);
|
||||
// TODO: timeout
|
||||
if(latest_token_.type == Token::Data || latest_token_.type == Token::DeletedData)
|
||||
{
|
||||
if(latest_token_.type == Token::Data || latest_token_.type == Token::DeletedData) {
|
||||
update_status([this] (Status &status) {
|
||||
status.record_type = (latest_token_.type == Token::DeletedData);
|
||||
});
|
||||
distance_into_section_ = 0;
|
||||
is_reading_data_ = true;
|
||||
data_mode_ = DataMode::Reading;
|
||||
goto type2_read_byte;
|
||||
}
|
||||
goto type2_read_data;
|
||||
@@ -553,8 +572,7 @@ void WD1770::posit_event(Event new_event_type)
|
||||
status.data_request = true;
|
||||
});
|
||||
distance_into_section_++;
|
||||
if(distance_into_section_ == 128 << header_[3])
|
||||
{
|
||||
if(distance_into_section_ == 128 << header_[3]) {
|
||||
distance_into_section_ = 0;
|
||||
goto type2_check_crc;
|
||||
}
|
||||
@@ -565,11 +583,16 @@ void WD1770::posit_event(Event new_event_type)
|
||||
if(latest_token_.type != Token::Byte) goto type2_read_byte;
|
||||
header_[distance_into_section_] = latest_token_.byte_value;
|
||||
distance_into_section_++;
|
||||
if(distance_into_section_ == 2)
|
||||
{
|
||||
// TODO: check CRC
|
||||
if(command_ & 0x10)
|
||||
{
|
||||
if(distance_into_section_ == 2) {
|
||||
if(crc_generator_.get_value()) {
|
||||
printf("CRC error; terminating\n");
|
||||
update_status([this] (Status &status) {
|
||||
status.crc_error = true;
|
||||
});
|
||||
goto wait_for_command;
|
||||
}
|
||||
|
||||
if(command_ & 0x10) {
|
||||
sector_++;
|
||||
goto test_type2_write_protection;
|
||||
}
|
||||
@@ -580,22 +603,298 @@ void WD1770::posit_event(Event new_event_type)
|
||||
|
||||
|
||||
type2_write_data:
|
||||
printf("!!!TODO: data portion of sector!!!\n");
|
||||
WAIT_FOR_BYTES(2);
|
||||
update_status([] (Status &status) {
|
||||
status.data_request = true;
|
||||
});
|
||||
WAIT_FOR_BYTES(9);
|
||||
if(status_.data_request) {
|
||||
update_status([] (Status &status) {
|
||||
status.lost_data = true;
|
||||
});
|
||||
goto wait_for_command;
|
||||
}
|
||||
WAIT_FOR_BYTES(1);
|
||||
if(is_double_density_) {
|
||||
WAIT_FOR_BYTES(11);
|
||||
}
|
||||
|
||||
data_mode_ = DataMode::Writing;
|
||||
begin_writing();
|
||||
for(int c = 0; c < (is_double_density_ ? 12 : 6); c++) {
|
||||
write_byte(0);
|
||||
}
|
||||
WAIT_FOR_EVENT(Event::DataWritten);
|
||||
|
||||
if(is_double_density_) {
|
||||
crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
|
||||
for(int c = 0; c < 3; c++) write_raw_short(Storage::Encodings::MFM::MFMSync);
|
||||
write_byte((command_&0x01) ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte);
|
||||
} else {
|
||||
crc_generator_.reset();
|
||||
crc_generator_.add((command_&0x01) ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte);
|
||||
write_raw_short((command_&0x01) ? Storage::Encodings::MFM::FMDeletedDataAddressMark : Storage::Encodings::MFM::FMDataAddressMark);
|
||||
}
|
||||
|
||||
WAIT_FOR_EVENT(Event::DataWritten);
|
||||
distance_into_section_ = 0;
|
||||
|
||||
type2_write_loop:
|
||||
/*
|
||||
This deviates from the data sheet slightly since that would prima facie request one more byte
|
||||
of data than is actually written — the last time around the loop it has transferred from the
|
||||
data register to the data shift register, set data request, written the byte, checked that data
|
||||
request has been satified, then finally considers whether all bytes are done. Based on both
|
||||
natural expectations and the way that emulated machines responded, I believe that to be a
|
||||
documentation error.
|
||||
*/
|
||||
write_byte(data_);
|
||||
distance_into_section_++;
|
||||
if(distance_into_section_ == 128 << header_[3]) {
|
||||
goto type2_write_crc;
|
||||
}
|
||||
|
||||
update_status([] (Status &status) {
|
||||
status.data_request = true;
|
||||
});
|
||||
WAIT_FOR_EVENT(Event::DataWritten);
|
||||
if(status_.data_request) {
|
||||
end_writing();
|
||||
update_status([] (Status &status) {
|
||||
status.lost_data = true;
|
||||
});
|
||||
goto wait_for_command;
|
||||
}
|
||||
|
||||
goto type2_write_loop;
|
||||
|
||||
type2_write_crc: {
|
||||
uint16_t crc = crc_generator_.get_value();
|
||||
write_byte(crc >> 8);
|
||||
write_byte(crc & 0xff);
|
||||
}
|
||||
write_byte(0xff);
|
||||
WAIT_FOR_EVENT(Event::DataWritten);
|
||||
end_writing();
|
||||
|
||||
if(command_ & 0x10) {
|
||||
sector_++;
|
||||
goto test_type2_write_protection;
|
||||
}
|
||||
printf("Wrote sector %d\n", sector_);
|
||||
goto wait_for_command;
|
||||
|
||||
|
||||
/*
|
||||
Type 3 entry point.
|
||||
*/
|
||||
// +--------+----------+-------------------------+
|
||||
// ! ! ! BITS !
|
||||
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
|
||||
// +--------+----------+-------------------------+
|
||||
// ! 3 ! Rd addr ! 1 1 0 0 h E 0 0 !
|
||||
// ! 3 ! Rd track ! 1 1 1 0 h E 0 0 !
|
||||
// ! 3 ! Wt track ! 1 1 1 1 h E P 0 !
|
||||
// +--------+----------+-------------------------+
|
||||
begin_type_3:
|
||||
update_status([] (Status &status) {
|
||||
status.type = Status::Three;
|
||||
status.crc_error = false;
|
||||
status.lost_data = false;
|
||||
status.record_not_found = false;
|
||||
});
|
||||
printf("!!!TODO: type 3 commands!!!\n");
|
||||
if(!has_motor_on_line() && !has_head_load_line()) goto type3_test_delay;
|
||||
|
||||
if(has_motor_on_line()) goto begin_type3_spin_up;
|
||||
goto begin_type3_load_head;
|
||||
|
||||
begin_type3_load_head:
|
||||
set_head_load_request(true);
|
||||
if(head_is_loaded_) goto type3_test_delay;
|
||||
WAIT_FOR_EVENT(Event::HeadLoad);
|
||||
goto type3_test_delay;
|
||||
|
||||
begin_type3_spin_up:
|
||||
if((command_&0x08) || get_motor_on()) goto type3_test_delay;
|
||||
SPIN_UP();
|
||||
|
||||
type3_test_delay:
|
||||
if(!(command_&0x04)) goto test_type3_type;
|
||||
WAIT_FOR_TIME(30);
|
||||
|
||||
test_type3_type:
|
||||
if(!(command_&0x20)) goto begin_read_address;
|
||||
if(!(command_&0x10)) goto begin_read_track;
|
||||
goto begin_write_track;
|
||||
|
||||
begin_read_address:
|
||||
index_hole_count_ = 0;
|
||||
distance_into_section_ = 0;
|
||||
|
||||
read_address_get_header:
|
||||
WAIT_FOR_EVENT(Event::IndexHole | Event::Token);
|
||||
if(new_event_type == Event::Token) {
|
||||
if(!distance_into_section_ && latest_token_.type == Token::ID) {data_mode_ = DataMode::Reading; distance_into_section_++; }
|
||||
else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte) {
|
||||
if(status_.data_request) {
|
||||
update_status([] (Status &status) {
|
||||
status.lost_data = true;
|
||||
});
|
||||
goto wait_for_command;
|
||||
}
|
||||
header_[distance_into_section_ - 1] = data_ = latest_token_.byte_value;
|
||||
track_ = header_[0];
|
||||
update_status([] (Status &status) {
|
||||
status.data_request = true;
|
||||
});
|
||||
distance_into_section_++;
|
||||
|
||||
if(distance_into_section_ == 7) {
|
||||
if(crc_generator_.get_value()) {
|
||||
update_status([] (Status &status) {
|
||||
status.crc_error = true;
|
||||
});
|
||||
}
|
||||
goto wait_for_command;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(index_hole_count_ == 6) {
|
||||
update_status([] (Status &status) {
|
||||
status.record_not_found = true;
|
||||
});
|
||||
goto wait_for_command;
|
||||
}
|
||||
goto read_address_get_header;
|
||||
|
||||
begin_read_track:
|
||||
WAIT_FOR_EVENT(Event::IndexHole);
|
||||
index_hole_count_ = 0;
|
||||
|
||||
read_track_read_byte:
|
||||
WAIT_FOR_EVENT(Event::Token | Event::IndexHole);
|
||||
if(index_hole_count_) {
|
||||
goto wait_for_command;
|
||||
}
|
||||
if(status_.data_request) {
|
||||
update_status([] (Status &status) {
|
||||
status.lost_data = true;
|
||||
});
|
||||
goto wait_for_command;
|
||||
}
|
||||
data_ = latest_token_.byte_value;
|
||||
update_status([] (Status &status) {
|
||||
status.data_request = true;
|
||||
});
|
||||
goto read_track_read_byte;
|
||||
|
||||
begin_write_track:
|
||||
update_status([] (Status &status) {
|
||||
status.data_request = false;
|
||||
status.lost_data = false;
|
||||
});
|
||||
|
||||
write_track_test_write_protect:
|
||||
if(get_drive_is_read_only()) {
|
||||
update_status([] (Status &status) {
|
||||
status.write_protect = true;
|
||||
});
|
||||
goto wait_for_command;
|
||||
}
|
||||
|
||||
update_status([] (Status &status) {
|
||||
status.data_request = true;
|
||||
});
|
||||
WAIT_FOR_BYTES(3);
|
||||
if(status_.data_request) {
|
||||
update_status([] (Status &status) {
|
||||
status.lost_data = true;
|
||||
});
|
||||
goto wait_for_command;
|
||||
}
|
||||
|
||||
WAIT_FOR_EVENT(Event::IndexHoleTarget);
|
||||
begin_writing();
|
||||
index_hole_count_ = 0;
|
||||
|
||||
write_track_write_loop:
|
||||
if(is_double_density_) {
|
||||
switch(data_) {
|
||||
case 0xf5:
|
||||
write_raw_short(Storage::Encodings::MFM::MFMSync);
|
||||
crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
|
||||
break;
|
||||
case 0xf6:
|
||||
write_raw_short(Storage::Encodings::MFM::MFMIndexSync);
|
||||
break;
|
||||
case 0xff: {
|
||||
uint16_t crc = crc_generator_.get_value();
|
||||
write_byte(crc >> 8);
|
||||
write_byte(crc & 0xff);
|
||||
} break;
|
||||
default:
|
||||
write_byte(data_);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch(data_) {
|
||||
case 0xf8: case 0xf9: case 0xfa: case 0xfb:
|
||||
case 0xfd: case 0xfe:
|
||||
// clock is 0xc7 = 1010 0000 0010 1010 = 0xa022
|
||||
write_raw_short(
|
||||
(uint16_t)(
|
||||
0xa022 |
|
||||
((data_ & 0x80) << 7) |
|
||||
((data_ & 0x40) << 6) |
|
||||
((data_ & 0x20) << 5) |
|
||||
((data_ & 0x10) << 4) |
|
||||
((data_ & 0x08) << 3) |
|
||||
((data_ & 0x04) << 2) |
|
||||
((data_ & 0x02) << 1) |
|
||||
(data_ & 0x01)
|
||||
)
|
||||
);
|
||||
crc_generator_.reset();
|
||||
crc_generator_.add(data_);
|
||||
break;
|
||||
case 0xfc:
|
||||
write_raw_short(Storage::Encodings::MFM::FMIndexAddressMark);
|
||||
break;
|
||||
case 0xf7: {
|
||||
uint16_t crc = crc_generator_.get_value();
|
||||
write_byte(crc >> 8);
|
||||
write_byte(crc & 0xff);
|
||||
} break;
|
||||
default:
|
||||
write_byte(data_);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
update_status([] (Status &status) {
|
||||
status.data_request = true;
|
||||
});
|
||||
WAIT_FOR_EVENT(Event::DataWritten);
|
||||
if(status_.data_request) {
|
||||
update_status([] (Status &status) {
|
||||
status.lost_data = true;
|
||||
});
|
||||
end_writing();
|
||||
goto wait_for_command;
|
||||
}
|
||||
if(index_hole_count_) {
|
||||
end_writing();
|
||||
goto wait_for_command;
|
||||
}
|
||||
|
||||
goto write_track_write_loop;
|
||||
|
||||
END_SECTION()
|
||||
}
|
||||
|
||||
void WD1770::update_status(std::function<void(Status &)> updater)
|
||||
{
|
||||
if(delegate_)
|
||||
{
|
||||
void WD1770::update_status(std::function<void(Status &)> updater) {
|
||||
if(delegate_) {
|
||||
Status old_status = status_;
|
||||
updater(status_);
|
||||
bool did_change =
|
||||
@@ -608,8 +907,29 @@ void WD1770::update_status(std::function<void(Status &)> updater)
|
||||
|
||||
void WD1770::set_head_load_request(bool head_load) {}
|
||||
|
||||
void WD1770::set_head_loaded(bool head_loaded)
|
||||
{
|
||||
void WD1770::set_head_loaded(bool head_loaded) {
|
||||
head_is_loaded_ = head_loaded;
|
||||
if(head_loaded) posit_event(Event::HeadLoad);
|
||||
}
|
||||
|
||||
void WD1770::write_bit(int bit) {
|
||||
if(is_double_density_) {
|
||||
Controller::write_bit(!bit && !last_bit_);
|
||||
Controller::write_bit(!!bit);
|
||||
last_bit_ = bit;
|
||||
} else {
|
||||
Controller::write_bit(true);
|
||||
Controller::write_bit(!!bit);
|
||||
}
|
||||
}
|
||||
|
||||
void WD1770::write_byte(uint8_t byte) {
|
||||
for(int c = 0; c < 8; c++) write_bit((byte << c)&0x80);
|
||||
crc_generator_.add(byte);
|
||||
}
|
||||
|
||||
void WD1770::write_raw_short(uint16_t value) {
|
||||
for(int c = 0; c < 16; c++) {
|
||||
Controller::write_bit(!!((value << c)&0x8000));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#define _770_hpp
|
||||
|
||||
#include "../../Storage/Disk/DiskController.hpp"
|
||||
#include "../../NumberTheory/CRC.hpp"
|
||||
|
||||
namespace WD {
|
||||
|
||||
@@ -46,8 +47,8 @@ class WD1770: public Storage::Disk::Controller {
|
||||
Busy = 0x01
|
||||
};
|
||||
|
||||
inline bool get_interrupt_request_line() { return !status_.busy; }
|
||||
inline bool get_data_request_line() { return status_.data_request; }
|
||||
inline bool get_interrupt_request_line() { return status_.interrupt_request; }
|
||||
inline bool get_data_request_line() { return status_.data_request; }
|
||||
class Delegate {
|
||||
public:
|
||||
virtual void wd1770_did_change_output(WD1770 *wd1770) = 0;
|
||||
@@ -73,6 +74,7 @@ class WD1770: public Storage::Disk::Controller {
|
||||
bool seek_error;
|
||||
bool lost_data;
|
||||
bool data_request;
|
||||
bool interrupt_request;
|
||||
bool busy;
|
||||
enum {
|
||||
One, Two, Three
|
||||
@@ -93,12 +95,16 @@ class WD1770: public Storage::Disk::Controller {
|
||||
void update_status(std::function<void(Status &)> updater);
|
||||
|
||||
// Tokeniser
|
||||
bool is_reading_data_;
|
||||
enum DataMode {
|
||||
Scanning,
|
||||
Reading,
|
||||
Writing
|
||||
} data_mode_;
|
||||
bool is_double_density_;
|
||||
int shift_register_;
|
||||
struct Token {
|
||||
enum Type {
|
||||
Index, ID, Data, DeletedData, Byte
|
||||
Index, ID, Data, DeletedData, Sync, Byte
|
||||
} type;
|
||||
uint8_t byte_value;
|
||||
} latest_token_;
|
||||
@@ -109,18 +115,28 @@ class WD1770: public Storage::Disk::Controller {
|
||||
Token = (1 << 1), // Indicates recognition of a new token in the flux stream. Interrogate latest_token_ for details.
|
||||
IndexHole = (1 << 2), // Indicates the passing of a physical index hole.
|
||||
HeadLoad = (1 << 3), // Indicates the head has been loaded (1973 only).
|
||||
DataWritten = (1 << 4), // Indicates that all queued bits have been written
|
||||
|
||||
Timer = (1 << 4), // Indicates that the delay_time_-powered timer has timed out.
|
||||
IndexHoleTarget = (1 << 5) // Indicates that index_hole_count_ has reached index_hole_count_target_.
|
||||
Timer = (1 << 5), // Indicates that the delay_time_-powered timer has timed out.
|
||||
IndexHoleTarget = (1 << 6) // Indicates that index_hole_count_ has reached index_hole_count_target_.
|
||||
};
|
||||
void posit_event(Event type);
|
||||
int interesting_event_mask_;
|
||||
int resume_point_;
|
||||
int delay_time_;
|
||||
|
||||
// Output
|
||||
int last_bit_;
|
||||
void write_bit(int bit);
|
||||
void write_byte(uint8_t byte);
|
||||
void write_raw_short(uint16_t value);
|
||||
|
||||
// ID buffer
|
||||
uint8_t header_[6];
|
||||
|
||||
// CRC generator
|
||||
NumberTheory::CRC16 crc_generator_;
|
||||
|
||||
// 1793 head-loading logic
|
||||
bool head_is_loaded_;
|
||||
|
||||
@@ -130,6 +146,7 @@ class WD1770: public Storage::Disk::Controller {
|
||||
// Storage::Disk::Controller
|
||||
virtual void process_input_bit(int value, unsigned int cycles_since_index_hole);
|
||||
virtual void process_index_hole();
|
||||
virtual void process_write_completed();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -50,12 +50,10 @@ template <class T> class MOS6522 {
|
||||
};
|
||||
|
||||
/*! Sets a register value. */
|
||||
inline void set_register(int address, uint8_t value)
|
||||
{
|
||||
inline void set_register(int address, uint8_t value) {
|
||||
address &= 0xf;
|
||||
// printf("6522 [%s]: %0x <- %02x\n", typeid(*this).name(), address, value);
|
||||
switch(address)
|
||||
{
|
||||
switch(address) {
|
||||
case 0x0:
|
||||
registers_.output[1] = value;
|
||||
static_cast<T *>(this)->set_port_output(Port::B, value, registers_.data_direction[1]); // TODO: handshake
|
||||
@@ -88,8 +86,7 @@ template <class T> class MOS6522 {
|
||||
case 0x5: case 0x7:
|
||||
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | (uint16_t)(value << 8);
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
|
||||
if(address == 0x05)
|
||||
{
|
||||
if(address == 0x05) {
|
||||
registers_.next_timer[0] = registers_.timer_latch[0];
|
||||
timer_is_running_[0] = true;
|
||||
}
|
||||
@@ -117,19 +114,15 @@ template <class T> class MOS6522 {
|
||||
registers_.peripheral_control = value;
|
||||
|
||||
// TODO: simplify below; trying to avoid improper logging of unimplemented warnings in input mode
|
||||
if(value & 0x08)
|
||||
{
|
||||
switch(value & 0x0e)
|
||||
{
|
||||
if(value & 0x08) {
|
||||
switch(value & 0x0e) {
|
||||
default: printf("Unimplemented control line mode %d\n", (value >> 1)&7); break;
|
||||
case 0x0c: static_cast<T *>(this)->set_control_line_output(Port::A, Line::Two, false); break;
|
||||
case 0x0e: static_cast<T *>(this)->set_control_line_output(Port::A, Line::Two, true); break;
|
||||
}
|
||||
}
|
||||
if(value & 0x80)
|
||||
{
|
||||
switch(value & 0xe0)
|
||||
{
|
||||
if(value & 0x80) {
|
||||
switch(value & 0xe0) {
|
||||
default: printf("Unimplemented control line mode %d\n", (value >> 5)&7); break;
|
||||
case 0xc0: static_cast<T *>(this)->set_control_line_output(Port::B, Line::Two, false); break;
|
||||
case 0xe0: static_cast<T *>(this)->set_control_line_output(Port::B, Line::Two, true); break;
|
||||
@@ -153,12 +146,10 @@ template <class T> class MOS6522 {
|
||||
}
|
||||
|
||||
/*! Gets a register value. */
|
||||
inline uint8_t get_register(int address)
|
||||
{
|
||||
inline uint8_t get_register(int address) {
|
||||
address &= 0xf;
|
||||
// printf("6522 %p: %d\n", this, address);
|
||||
switch(address)
|
||||
{
|
||||
switch(address) {
|
||||
case 0x0:
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
|
||||
reevaluate_interrupts();
|
||||
@@ -200,15 +191,12 @@ template <class T> class MOS6522 {
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
inline void set_control_line_input(Port port, Line line, bool value)
|
||||
{
|
||||
switch(line)
|
||||
{
|
||||
inline void set_control_line_input(Port port, Line line, bool value) {
|
||||
switch(line) {
|
||||
case Line::One:
|
||||
if( value != control_inputs_[port].line_one &&
|
||||
value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01))
|
||||
)
|
||||
{
|
||||
) {
|
||||
registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
@@ -220,8 +208,7 @@ template <class T> class MOS6522 {
|
||||
if( value != control_inputs_[port].line_two && // i.e. value has changed ...
|
||||
!(registers_.peripheral_control & (port ? 0x80 : 0x08)) && // ... and line is input ...
|
||||
value == !!(registers_.peripheral_control & (port ? 0x40 : 0x04)) // ... and it's either high or low, as required
|
||||
)
|
||||
{
|
||||
) {
|
||||
registers_.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
@@ -234,8 +221,7 @@ template <class T> class MOS6522 {
|
||||
registers_.last_timer[0] = registers_.timer[0];\
|
||||
registers_.last_timer[1] = registers_.timer[1];\
|
||||
\
|
||||
if(registers_.timer_needs_reload)\
|
||||
{\
|
||||
if(registers_.timer_needs_reload) {\
|
||||
registers_.timer_needs_reload = false;\
|
||||
registers_.timer[0] = registers_.timer_latch[0];\
|
||||
}\
|
||||
@@ -248,15 +234,13 @@ template <class T> class MOS6522 {
|
||||
|
||||
// IRQ is raised on the half cycle after overflow
|
||||
#define phase1() \
|
||||
if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1])\
|
||||
{\
|
||||
if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1]) {\
|
||||
timer_is_running_[1] = false;\
|
||||
registers_.interrupt_flags |= InterruptFlag::Timer2;\
|
||||
reevaluate_interrupts();\
|
||||
}\
|
||||
\
|
||||
if((registers_.timer[0] == 0xffff) && !registers_.last_timer[0] && timer_is_running_[0])\
|
||||
{\
|
||||
if((registers_.timer[0] == 0xffff) && !registers_.last_timer[0] && timer_is_running_[0]) {\
|
||||
registers_.interrupt_flags |= InterruptFlag::Timer1;\
|
||||
reevaluate_interrupts();\
|
||||
\
|
||||
@@ -279,28 +263,22 @@ template <class T> class MOS6522 {
|
||||
Callers should decide whether they are going to use @c run_for_half_cycles or @c run_for_cycles, and not
|
||||
intermingle usage.
|
||||
*/
|
||||
inline void run_for_half_cycles(unsigned int number_of_cycles)
|
||||
{
|
||||
if(is_phase2_)
|
||||
{
|
||||
inline void run_for_half_cycles(unsigned int number_of_cycles) {
|
||||
if(is_phase2_) {
|
||||
phase2();
|
||||
number_of_cycles--;
|
||||
}
|
||||
|
||||
while(number_of_cycles >= 2)
|
||||
{
|
||||
while(number_of_cycles >= 2) {
|
||||
phase1();
|
||||
phase2();
|
||||
number_of_cycles -= 2;
|
||||
}
|
||||
|
||||
if(number_of_cycles)
|
||||
{
|
||||
if(number_of_cycles) {
|
||||
phase1();
|
||||
is_phase2_ = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
is_phase2_ = false;
|
||||
}
|
||||
}
|
||||
@@ -311,10 +289,8 @@ template <class T> class MOS6522 {
|
||||
Callers should decide whether they are going to use @c run_for_half_cycles or @c run_for_cycles, and not
|
||||
intermingle usage.
|
||||
*/
|
||||
inline void run_for_cycles(unsigned int number_of_cycles)
|
||||
{
|
||||
while(number_of_cycles--)
|
||||
{
|
||||
inline void run_for_cycles(unsigned int number_of_cycles) {
|
||||
while(number_of_cycles--) {
|
||||
phase1();
|
||||
phase2();
|
||||
}
|
||||
@@ -324,8 +300,7 @@ template <class T> class MOS6522 {
|
||||
#undef phase2
|
||||
|
||||
/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
|
||||
inline bool get_interrupt_line()
|
||||
{
|
||||
inline bool get_interrupt_line() {
|
||||
uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
|
||||
return !!interrupt_status;
|
||||
}
|
||||
@@ -333,8 +308,7 @@ template <class T> class MOS6522 {
|
||||
MOS6522() :
|
||||
timer_is_running_{false, false},
|
||||
last_posted_interrupt_status_(false),
|
||||
is_phase2_(false)
|
||||
{}
|
||||
is_phase2_(false) {}
|
||||
|
||||
private:
|
||||
// Expected to be overridden
|
||||
@@ -344,8 +318,7 @@ template <class T> class MOS6522 {
|
||||
void set_interrupt_status(bool status) {}
|
||||
|
||||
// Input/output multiplexer
|
||||
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output)
|
||||
{
|
||||
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output) {
|
||||
uint8_t input = static_cast<T *>(this)->get_port_input(port);
|
||||
return (input & ~output_mask) | (output & output_mask);
|
||||
}
|
||||
@@ -355,11 +328,9 @@ template <class T> class MOS6522 {
|
||||
|
||||
// Delegate and communications
|
||||
bool last_posted_interrupt_status_;
|
||||
inline void reevaluate_interrupts()
|
||||
{
|
||||
inline void reevaluate_interrupts() {
|
||||
bool new_interrupt_status = get_interrupt_line();
|
||||
if(new_interrupt_status != last_posted_interrupt_status_)
|
||||
{
|
||||
if(new_interrupt_status != last_posted_interrupt_status_) {
|
||||
last_posted_interrupt_status_ = new_interrupt_status;
|
||||
static_cast<T *>(this)->set_interrupt_status(new_interrupt_status);
|
||||
}
|
||||
@@ -404,13 +375,11 @@ class MOS6522IRQDelegate {
|
||||
virtual void mos6522_did_change_interrupt_status(void *mos6522) = 0;
|
||||
};
|
||||
|
||||
inline void set_interrupt_delegate(Delegate *delegate)
|
||||
{
|
||||
inline void set_interrupt_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
inline void set_interrupt_status(bool new_status)
|
||||
{
|
||||
inline void set_interrupt_status(bool new_status) {
|
||||
if(delegate_) delegate_->mos6522_did_change_interrupt_status(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,7 @@ template <class T> class MOS6532 {
|
||||
inline void set_ram(uint16_t address, uint8_t value) { ram_[address&0x7f] = value; }
|
||||
inline uint8_t get_ram(uint16_t address) { return ram_[address & 0x7f]; }
|
||||
|
||||
inline void set_register(int address, uint8_t value)
|
||||
{
|
||||
inline void set_register(int address, uint8_t value) {
|
||||
const uint8_t decodedAddress = address & 0x07;
|
||||
switch(decodedAddress) {
|
||||
// Port output
|
||||
@@ -48,16 +47,13 @@ template <class T> class MOS6532 {
|
||||
|
||||
// The timer and edge detect control
|
||||
case 0x04: case 0x05: case 0x06: case 0x07:
|
||||
if(address & 0x10)
|
||||
{
|
||||
if(address & 0x10) {
|
||||
timer_.writtenShift = timer_.activeShift = (decodedAddress - 0x04) * 3 + (decodedAddress / 0x07); // i.e. 0, 3, 6, 10
|
||||
timer_.value = ((unsigned int)(value) << timer_.activeShift) | ((1 << timer_.activeShift)-1);
|
||||
timer_.value = ((unsigned int)value << timer_.activeShift) ;
|
||||
timer_.interrupt_enabled = !!(address&0x08);
|
||||
interrupt_status_ &= ~InterruptFlag::Timer;
|
||||
evaluate_interrupts();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
a7_interrupt_.enabled = !!(address&0x2);
|
||||
a7_interrupt_.active_on_positive = !!(address & 0x01);
|
||||
}
|
||||
@@ -65,13 +61,11 @@ template <class T> class MOS6532 {
|
||||
}
|
||||
}
|
||||
|
||||
inline uint8_t get_register(int address)
|
||||
{
|
||||
inline uint8_t get_register(int address) {
|
||||
const uint8_t decodedAddress = address & 0x7;
|
||||
switch(decodedAddress) {
|
||||
// Port input
|
||||
case 0x00: case 0x02:
|
||||
{
|
||||
case 0x00: case 0x02: {
|
||||
const int port = decodedAddress / 2;
|
||||
uint8_t input = static_cast<T *>(this)->get_port_input(port);
|
||||
return (input & ~port_[port].output_mask) | (port_[port].output & port_[port].output_mask);
|
||||
@@ -82,8 +76,7 @@ template <class T> class MOS6532 {
|
||||
break;
|
||||
|
||||
// Timer and interrupt control
|
||||
case 0x04: case 0x06:
|
||||
{
|
||||
case 0x04: case 0x06: {
|
||||
uint8_t value = (uint8_t)(timer_.value >> timer_.activeShift);
|
||||
timer_.interrupt_enabled = !!(address&0x08);
|
||||
interrupt_status_ &= ~InterruptFlag::Timer;
|
||||
@@ -99,8 +92,7 @@ template <class T> class MOS6532 {
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x05: case 0x07:
|
||||
{
|
||||
case 0x05: case 0x07: {
|
||||
uint8_t value = interrupt_status_;
|
||||
interrupt_status_ &= ~InterruptFlag::PA7;
|
||||
evaluate_interrupts();
|
||||
@@ -112,14 +104,13 @@ template <class T> class MOS6532 {
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
inline void run_for_cycles(unsigned int number_of_cycles)
|
||||
{
|
||||
inline void run_for_cycles(unsigned int number_of_cycles) {
|
||||
// permit counting _to_ zero; counting _through_ zero initiates the other behaviour
|
||||
if(timer_.value >= number_of_cycles) {
|
||||
timer_.value -= number_of_cycles;
|
||||
} else {
|
||||
number_of_cycles -= timer_.value;
|
||||
timer_.value = 0x100 - number_of_cycles;
|
||||
timer_.value = (0x100 - number_of_cycles) & 0xff;
|
||||
timer_.activeShift = 0;
|
||||
interrupt_status_ |= InterruptFlag::Timer;
|
||||
evaluate_interrupts();
|
||||
@@ -130,23 +121,19 @@ template <class T> class MOS6532 {
|
||||
interrupt_status_(0),
|
||||
port_{{.output_mask = 0, .output = 0}, {.output_mask = 0, .output = 0}},
|
||||
a7_interrupt_({.last_port_value = 0, .enabled = false}),
|
||||
interrupt_line_(false)
|
||||
{}
|
||||
interrupt_line_(false),
|
||||
timer_{.value = (unsigned int)((rand() & 0xff) << 10), .activeShift = 10, .writtenShift = 10, .interrupt_enabled = false} {}
|
||||
|
||||
inline void set_port_did_change(int port)
|
||||
{
|
||||
if(!port)
|
||||
{
|
||||
inline void set_port_did_change(int port) {
|
||||
if(!port) {
|
||||
uint8_t new_port_a_value = (get_port_input(0) & ~port_[0].output_mask) | (port_[0].output & port_[0].output_mask);
|
||||
uint8_t difference = new_port_a_value ^ a7_interrupt_.last_port_value;
|
||||
a7_interrupt_.last_port_value = new_port_a_value;
|
||||
if(difference&0x80)
|
||||
{
|
||||
if(difference&0x80) {
|
||||
if(
|
||||
((new_port_a_value&0x80) && a7_interrupt_.active_on_positive) ||
|
||||
(!(new_port_a_value&0x80) && !a7_interrupt_.active_on_positive)
|
||||
)
|
||||
{
|
||||
) {
|
||||
interrupt_status_ |= InterruptFlag::PA7;
|
||||
evaluate_interrupts();
|
||||
}
|
||||
@@ -154,8 +141,7 @@ template <class T> class MOS6532 {
|
||||
}
|
||||
}
|
||||
|
||||
inline bool get_inerrupt_line()
|
||||
{
|
||||
inline bool get_inerrupt_line() {
|
||||
return interrupt_line_;
|
||||
}
|
||||
|
||||
@@ -190,8 +176,7 @@ template <class T> class MOS6532 {
|
||||
void set_port_output(int port, uint8_t value, uint8_t output_mask) {}
|
||||
void set_irq_line(bool new_value) {}
|
||||
|
||||
inline void evaluate_interrupts()
|
||||
{
|
||||
inline void evaluate_interrupts() {
|
||||
interrupt_line_ =
|
||||
((interrupt_status_&InterruptFlag::Timer) && timer_.interrupt_enabled) ||
|
||||
((interrupt_status_&InterruptFlag::PA7) && a7_interrupt_.enabled);
|
||||
|
||||
@@ -14,18 +14,15 @@ Speaker::Speaker() :
|
||||
volume_(0),
|
||||
control_registers_{0, 0, 0, 0},
|
||||
shift_registers_{0, 0, 0, 0},
|
||||
counters_{2, 1, 0, 0} // create a slight phase offset for the three channels
|
||||
{}
|
||||
counters_{2, 1, 0, 0} {} // create a slight phase offset for the three channels
|
||||
|
||||
void Speaker::set_volume(uint8_t volume)
|
||||
{
|
||||
void Speaker::set_volume(uint8_t volume) {
|
||||
enqueue([=]() {
|
||||
volume_ = volume;
|
||||
});
|
||||
}
|
||||
|
||||
void Speaker::set_control(int channel, uint8_t value)
|
||||
{
|
||||
void Speaker::set_control(int channel, uint8_t value) {
|
||||
enqueue([=]() {
|
||||
control_registers_[channel] = value;
|
||||
});
|
||||
@@ -108,10 +105,8 @@ static uint8_t noise_pattern[] = {
|
||||
// testing against 0x80. The effect should be the same: loading with 0x7f means an output update every cycle, loading with 0x7e
|
||||
// means every second cycle, etc.
|
||||
|
||||
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target)
|
||||
{
|
||||
for(unsigned int c = 0; c < number_of_samples; c++)
|
||||
{
|
||||
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
|
||||
for(unsigned int c = 0; c < number_of_samples; c++) {
|
||||
update(0, 2, shift);
|
||||
update(1, 1, shift);
|
||||
update(2, 0, shift);
|
||||
@@ -128,10 +123,8 @@ void Speaker::get_samples(unsigned int number_of_samples, int16_t *target)
|
||||
}
|
||||
}
|
||||
|
||||
void Speaker::skip_samples(unsigned int number_of_samples)
|
||||
{
|
||||
for(unsigned int c = 0; c < number_of_samples; c++)
|
||||
{
|
||||
void Speaker::skip_samples(unsigned int number_of_samples) {
|
||||
for(unsigned int c = 0; c < number_of_samples; c++) {
|
||||
update(0, 2, shift);
|
||||
update(1, 1, shift);
|
||||
update(2, 0, shift);
|
||||
|
||||
@@ -43,32 +43,28 @@ class Speaker: public ::Outputs::Filter<Speaker> {
|
||||
template <class T> class MOS6560 {
|
||||
public:
|
||||
MOS6560() :
|
||||
crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::NTSC60, 1)),
|
||||
speaker_(new Speaker),
|
||||
horizontal_counter_(0),
|
||||
vertical_counter_(0),
|
||||
cycles_since_speaker_update_(0),
|
||||
is_odd_frame_(false),
|
||||
is_odd_line_(false)
|
||||
{
|
||||
crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::NTSC60, 2)),
|
||||
speaker_(new Speaker),
|
||||
horizontal_counter_(0),
|
||||
vertical_counter_(0),
|
||||
cycles_since_speaker_update_(0),
|
||||
is_odd_frame_(false),
|
||||
is_odd_line_(false) {
|
||||
crt_->set_composite_sampling_function(
|
||||
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
|
||||
"{"
|
||||
"uint c = texture(texID, coordinate).r;"
|
||||
"float y = float(c >> 4) / 4.0;"
|
||||
"uint yC = c & 15u;"
|
||||
"float phaseOffset = 6.283185308 * float(yC) / 16.0;"
|
||||
"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);"
|
||||
"float phaseOffset = 6.283185308 * 2.0 * yc.y;"
|
||||
|
||||
"float chroma = cos(phase + phaseOffset);"
|
||||
"return mix(y, step(yC, 14) * chroma, amplitude);"
|
||||
"return mix(yc.x, step(yc.y, 0.75) * chroma, amplitude);"
|
||||
"}");
|
||||
|
||||
// default to 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));
|
||||
}
|
||||
|
||||
@@ -82,26 +78,36 @@ template <class T> class MOS6560 {
|
||||
/*!
|
||||
Sets the output mode to either PAL or NTSC.
|
||||
*/
|
||||
void set_output_mode(OutputMode output_mode)
|
||||
{
|
||||
void set_output_mode(OutputMode output_mode) {
|
||||
output_mode_ = output_mode;
|
||||
uint8_t luminances[16] = { // range is 0–4
|
||||
0, 4, 1, 3, 2, 2, 1, 3,
|
||||
2, 1, 2, 1, 2, 3, 2, 3
|
||||
|
||||
// Lumunances are encoded trivially: on a 0–255 scale.
|
||||
const uint8_t luminances[16] = {
|
||||
0, 255, 109, 189,
|
||||
199, 144, 159, 161,
|
||||
126, 227, 227, 207,
|
||||
235, 173, 188, 196
|
||||
};
|
||||
uint8_t pal_chrominances[16] = { // range is 0–15; 15 is a special case meaning "no chrominance"
|
||||
15, 15, 5, 13, 2, 10, 0, 8,
|
||||
6, 7, 5, 13, 2, 10, 0, 8,
|
||||
|
||||
// Chrominances are encoded such that 0–128 is a complete revolution of phase;
|
||||
// anything above 191 disables the colour subcarrier. Phase is relative to the
|
||||
// colour burst, so 0 is green.
|
||||
const uint8_t pal_chrominances[16] = {
|
||||
255, 255, 40, 112,
|
||||
8, 88, 120, 56,
|
||||
40, 48, 40, 112,
|
||||
8, 88, 120, 56,
|
||||
};
|
||||
uint8_t ntsc_chrominances[16] = {
|
||||
15, 15, 2, 10, 4, 12, 6, 14,
|
||||
0, 8, 2, 10, 4, 12, 6, 14,
|
||||
const uint8_t ntsc_chrominances[16] = {
|
||||
255, 255, 40, 104,
|
||||
64, 120, 80, 16,
|
||||
32, 32, 40, 104,
|
||||
64, 120, 80, 16,
|
||||
};
|
||||
uint8_t *chrominances;
|
||||
const uint8_t *chrominances;
|
||||
Outputs::CRT::DisplayType display_type;
|
||||
|
||||
switch(output_mode)
|
||||
{
|
||||
switch(output_mode) {
|
||||
case OutputMode::PAL:
|
||||
chrominances = pal_chrominances;
|
||||
display_type = Outputs::CRT::PAL50;
|
||||
@@ -122,10 +128,9 @@ template <class T> class MOS6560 {
|
||||
}
|
||||
|
||||
crt_->set_new_display_type((unsigned int)(timing_.cycles_per_line*4), display_type);
|
||||
// crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.1f, 0.8f, 0.8f));
|
||||
crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f));
|
||||
|
||||
// switch(output_mode)
|
||||
// {
|
||||
// switch(output_mode) {
|
||||
// case OutputMode::PAL:
|
||||
// crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 15*4, 55*4, 4.0f / 3.0f));
|
||||
// break;
|
||||
@@ -134,32 +139,29 @@ template <class T> class MOS6560 {
|
||||
// break;
|
||||
// }
|
||||
|
||||
for(int c = 0; c < 16; c++)
|
||||
{
|
||||
colours_[c] = (uint8_t)((luminances[c] << 4) | chrominances[c]);
|
||||
for(int c = 0; c < 16; c++) {
|
||||
uint8_t *colour = (uint8_t *)&colours_[c];
|
||||
colour[0] = luminances[c];
|
||||
colour[1] = chrominances[c];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Runs for cycles. Derr.
|
||||
*/
|
||||
inline void run_for_cycles(unsigned int number_of_cycles)
|
||||
{
|
||||
inline void run_for_cycles(unsigned int number_of_cycles) {
|
||||
// keep track of the amount of time since the speaker was updated; lazy updates are applied
|
||||
cycles_since_speaker_update_ += number_of_cycles;
|
||||
|
||||
while(number_of_cycles--)
|
||||
{
|
||||
while(number_of_cycles--) {
|
||||
// keep an old copy of the vertical count because that test is a cycle later than the actual changes
|
||||
int previous_vertical_counter = vertical_counter_;
|
||||
|
||||
// keep track of internal time relative to this scanline
|
||||
horizontal_counter_++;
|
||||
full_frame_counter_++;
|
||||
if(horizontal_counter_ == timing_.cycles_per_line)
|
||||
{
|
||||
if(horizontal_drawing_latch_)
|
||||
{
|
||||
if(horizontal_counter_ == timing_.cycles_per_line) {
|
||||
if(horizontal_drawing_latch_) {
|
||||
current_character_row_++;
|
||||
if(
|
||||
(current_character_row_ == 16) ||
|
||||
@@ -179,8 +181,7 @@ template <class T> class MOS6560 {
|
||||
horizontal_drawing_latch_ = false;
|
||||
|
||||
vertical_counter_ ++;
|
||||
if(vertical_counter_ == (registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field))
|
||||
{
|
||||
if(vertical_counter_ == (registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field)) {
|
||||
vertical_counter_ = 0;
|
||||
full_frame_counter_ = 0;
|
||||
|
||||
@@ -198,11 +199,9 @@ template <class T> class MOS6560 {
|
||||
horizontal_drawing_latch_ |= vertical_drawing_latch_ && (horizontal_counter_ == registers_.first_column_location);
|
||||
|
||||
if(pixel_line_cycle_ >= 0) pixel_line_cycle_++;
|
||||
switch(pixel_line_cycle_)
|
||||
{
|
||||
switch(pixel_line_cycle_) {
|
||||
case -1:
|
||||
if(horizontal_drawing_latch_)
|
||||
{
|
||||
if(horizontal_drawing_latch_) {
|
||||
pixel_line_cycle_ = 0;
|
||||
video_matrix_address_counter_ = base_video_matrix_address_counter_;
|
||||
}
|
||||
@@ -213,14 +212,10 @@ template <class T> class MOS6560 {
|
||||
}
|
||||
|
||||
uint16_t fetch_address = 0x1c;
|
||||
if(column_counter_ >= 0 && column_counter_ < columns_this_line_*2)
|
||||
{
|
||||
if(column_counter_&1)
|
||||
{
|
||||
if(column_counter_ >= 0 && column_counter_ < columns_this_line_*2) {
|
||||
if(column_counter_&1) {
|
||||
fetch_address = registers_.character_cell_start_address + (character_code_*(registers_.tall_characters ? 16 : 8)) + current_character_row_;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
fetch_address = (uint16_t)(registers_.video_matrix_start_address + video_matrix_address_counter_);
|
||||
video_matrix_address_counter_++;
|
||||
if(
|
||||
@@ -244,8 +239,7 @@ template <class T> class MOS6560 {
|
||||
// determine output state; colour burst and sync timing are currently a guess
|
||||
if(horizontal_counter_ > timing_.cycles_per_line-4) this_state_ = State::ColourBurst;
|
||||
else if(horizontal_counter_ > timing_.cycles_per_line-7) this_state_ = State::Sync;
|
||||
else
|
||||
{
|
||||
else {
|
||||
this_state_ = (column_counter_ >= 0 && column_counter_ < columns_this_line_*2) ? State::Pixels : State::Border;
|
||||
}
|
||||
|
||||
@@ -262,10 +256,8 @@ template <class T> class MOS6560 {
|
||||
this_state_ = State::Sync;
|
||||
|
||||
// update the CRT
|
||||
if(this_state_ != output_state_)
|
||||
{
|
||||
switch(output_state_)
|
||||
{
|
||||
if(this_state_ != output_state_) {
|
||||
switch(output_state_) {
|
||||
case State::Sync: crt_->output_sync(cycles_in_state_ * 4); break;
|
||||
case State::ColourBurst: crt_->output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0, 0); break;
|
||||
case State::Border: output_border(cycles_in_state_ * 4); break;
|
||||
@@ -275,32 +267,24 @@ template <class T> class MOS6560 {
|
||||
cycles_in_state_ = 0;
|
||||
|
||||
pixel_pointer = nullptr;
|
||||
if(output_state_ == State::Pixels)
|
||||
{
|
||||
pixel_pointer = crt_->allocate_write_area(260);
|
||||
if(output_state_ == State::Pixels) {
|
||||
pixel_pointer = (uint16_t *)crt_->allocate_write_area(260);
|
||||
}
|
||||
}
|
||||
cycles_in_state_++;
|
||||
|
||||
if(this_state_ == State::Pixels)
|
||||
{
|
||||
if(column_counter_&1)
|
||||
{
|
||||
if(this_state_ == State::Pixels) {
|
||||
if(column_counter_&1) {
|
||||
character_value_ = pixel_data;
|
||||
|
||||
if(pixel_pointer)
|
||||
{
|
||||
uint8_t cell_colour = colours_[character_colour_ & 0x7];
|
||||
if(!(character_colour_&0x8))
|
||||
{
|
||||
uint8_t colours[2];
|
||||
if(registers_.invertedCells)
|
||||
{
|
||||
if(pixel_pointer) {
|
||||
uint16_t cell_colour = colours_[character_colour_ & 0x7];
|
||||
if(!(character_colour_&0x8)) {
|
||||
uint16_t colours[2];
|
||||
if(registers_.invertedCells) {
|
||||
colours[0] = cell_colour;
|
||||
colours[1] = registers_.backgroundColour;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
colours[0] = registers_.backgroundColour;
|
||||
colours[1] = cell_colour;
|
||||
}
|
||||
@@ -312,10 +296,8 @@ template <class T> class MOS6560 {
|
||||
pixel_pointer[5] = colours[(character_value_ >> 2)&1];
|
||||
pixel_pointer[6] = colours[(character_value_ >> 1)&1];
|
||||
pixel_pointer[7] = colours[(character_value_ >> 0)&1];
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t colours[4] = {registers_.backgroundColour, registers_.borderColour, cell_colour, registers_.auxiliary_colour};
|
||||
} else {
|
||||
uint16_t colours[4] = {registers_.backgroundColour, registers_.borderColour, cell_colour, registers_.auxiliary_colour};
|
||||
pixel_pointer[0] =
|
||||
pixel_pointer[1] = colours[(character_value_ >> 6)&3];
|
||||
pixel_pointer[2] =
|
||||
@@ -325,11 +307,10 @@ template <class T> class MOS6560 {
|
||||
pixel_pointer[6] =
|
||||
pixel_pointer[7] = colours[(character_value_ >> 0)&3];
|
||||
}
|
||||
|
||||
pixel_pointer += 8;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
character_code_ = pixel_data;
|
||||
character_colour_ = colour_data;
|
||||
}
|
||||
@@ -347,12 +328,10 @@ template <class T> class MOS6560 {
|
||||
/*!
|
||||
Writes to a 6560 register.
|
||||
*/
|
||||
void set_register(int address, uint8_t value)
|
||||
{
|
||||
void set_register(int address, uint8_t value) {
|
||||
address &= 0xf;
|
||||
registers_.direct_values[address] = value;
|
||||
switch(address)
|
||||
{
|
||||
switch(address) {
|
||||
case 0x0:
|
||||
registers_.interlaced = !!(value&0x80) && timing_.supports_interlacing;
|
||||
registers_.first_column_location = value & 0x7f;
|
||||
@@ -391,11 +370,9 @@ template <class T> class MOS6560 {
|
||||
speaker_->set_volume(value & 0xf);
|
||||
break;
|
||||
|
||||
case 0xf:
|
||||
{
|
||||
uint8_t new_border_colour = colours_[value & 0x07];
|
||||
if(this_state_ == State::Border && new_border_colour != registers_.borderColour)
|
||||
{
|
||||
case 0xf: {
|
||||
uint16_t new_border_colour = colours_[value & 0x07];
|
||||
if(this_state_ == State::Border && new_border_colour != registers_.borderColour) {
|
||||
output_border(cycles_in_state_ * 4);
|
||||
cycles_in_state_ = 0;
|
||||
}
|
||||
@@ -415,12 +392,10 @@ template <class T> class MOS6560 {
|
||||
/*
|
||||
Reads from a 6560 register.
|
||||
*/
|
||||
uint8_t get_register(int address)
|
||||
{
|
||||
uint8_t get_register(int address) {
|
||||
address &= 0xf;
|
||||
int current_line = (full_frame_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line;
|
||||
switch(address)
|
||||
{
|
||||
switch(address) {
|
||||
default: return registers_.direct_values[address];
|
||||
case 0x03: return (uint8_t)(current_line << 7) | (registers_.direct_values[3] & 0x7f);
|
||||
case 0x04: return (current_line >> 1) & 0xff;
|
||||
@@ -432,8 +407,7 @@ template <class T> class MOS6560 {
|
||||
|
||||
std::shared_ptr<Speaker> speaker_;
|
||||
unsigned int cycles_since_speaker_update_;
|
||||
void update_audio()
|
||||
{
|
||||
void update_audio() {
|
||||
speaker_->run_for_cycles(cycles_since_speaker_update_ >> 2);
|
||||
cycles_since_speaker_update_ &= 3;
|
||||
}
|
||||
@@ -444,7 +418,7 @@ template <class T> class MOS6560 {
|
||||
uint8_t first_column_location, first_row_location;
|
||||
uint8_t number_of_columns, number_of_rows;
|
||||
uint16_t character_cell_start_address, video_matrix_start_address;
|
||||
uint8_t backgroundColour, borderColour, auxiliary_colour;
|
||||
uint16_t backgroundColour, borderColour, auxiliary_colour;
|
||||
bool invertedCells;
|
||||
|
||||
uint8_t direct_values[16];
|
||||
@@ -475,12 +449,11 @@ template <class T> class MOS6560 {
|
||||
bool is_odd_frame_, is_odd_line_;
|
||||
|
||||
// lookup table from 6560 colour index to appropriate PAL/NTSC value
|
||||
uint8_t colours_[16];
|
||||
uint16_t colours_[16];
|
||||
|
||||
uint8_t *pixel_pointer;
|
||||
void output_border(unsigned int number_of_cycles)
|
||||
{
|
||||
uint8_t *colour_pointer = crt_->allocate_write_area(1);
|
||||
uint16_t *pixel_pointer;
|
||||
void output_border(unsigned int number_of_cycles) {
|
||||
uint16_t *colour_pointer = (uint16_t *)crt_->allocate_write_area(1);
|
||||
if(colour_pointer) *colour_pointer = registers_.borderColour;
|
||||
crt_->output_level(number_of_cycles);
|
||||
}
|
||||
|
||||
@@ -11,22 +11,18 @@
|
||||
using namespace GI;
|
||||
|
||||
AY38910::AY38910() :
|
||||
selected_register_(0),
|
||||
tone_counters_{0, 0, 0}, tone_periods_{0, 0, 0}, tone_outputs_{0, 0, 0},
|
||||
noise_shift_register_(0xffff), noise_period_(0), noise_counter_(0), noise_output_(0),
|
||||
envelope_divider_(0), envelope_period_(0), envelope_position_(0),
|
||||
master_divider_(0),
|
||||
output_registers_{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
{
|
||||
selected_register_(0),
|
||||
tone_counters_{0, 0, 0}, tone_periods_{0, 0, 0}, tone_outputs_{0, 0, 0},
|
||||
noise_shift_register_(0xffff), noise_period_(0), noise_counter_(0), noise_output_(0),
|
||||
envelope_divider_(0), envelope_period_(0), envelope_position_(0),
|
||||
master_divider_(0),
|
||||
output_registers_{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} {
|
||||
output_registers_[8] = output_registers_[9] = output_registers_[10] = 0;
|
||||
|
||||
// set up envelope lookup tables
|
||||
for(int c = 0; c < 16; c++)
|
||||
{
|
||||
for(int p = 0; p < 32; p++)
|
||||
{
|
||||
switch(c)
|
||||
{
|
||||
for(int c = 0; c < 16; c++) {
|
||||
for(int p = 0; p < 32; p++) {
|
||||
switch(c) {
|
||||
case 0: case 1: case 2: case 3: case 9:
|
||||
envelope_shapes_[c][p] = (p < 16) ? (p^0xf) : 0;
|
||||
envelope_overflow_masks_[c] = 0x1f;
|
||||
@@ -69,34 +65,28 @@ AY38910::AY38910() :
|
||||
// set up volume lookup table
|
||||
float max_volume = 8192;
|
||||
float root_two = sqrtf(2.0f);
|
||||
for(int v = 0; v < 16; v++)
|
||||
{
|
||||
for(int v = 0; v < 16; v++) {
|
||||
volumes_[v] = (int)(max_volume / powf(root_two, (float)(v ^ 0xf)));
|
||||
}
|
||||
volumes_[0] = 0;
|
||||
}
|
||||
|
||||
void AY38910::set_clock_rate(double clock_rate)
|
||||
{
|
||||
void AY38910::set_clock_rate(double clock_rate) {
|
||||
set_input_rate((float)clock_rate);
|
||||
}
|
||||
|
||||
void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
|
||||
{
|
||||
void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) {
|
||||
int c = 0;
|
||||
while((master_divider_&15) && c < number_of_samples)
|
||||
{
|
||||
while((master_divider_&7) && c < number_of_samples) {
|
||||
target[c] = output_volume_;
|
||||
master_divider_++;
|
||||
c++;
|
||||
}
|
||||
|
||||
while(c < number_of_samples)
|
||||
{
|
||||
while(c < number_of_samples) {
|
||||
#define step_channel(c) \
|
||||
if(tone_counters_[c]) tone_counters_[c]--;\
|
||||
else\
|
||||
{\
|
||||
else {\
|
||||
tone_outputs_[c] ^= 1;\
|
||||
tone_counters_[c] = tone_periods_[c];\
|
||||
}
|
||||
@@ -111,8 +101,7 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
|
||||
// ... the noise generator. This recomputes the new bit repeatedly but harmlessly, only shifting
|
||||
// it into the official 17 upon divider underflow.
|
||||
if(noise_counter_) noise_counter_--;
|
||||
else
|
||||
{
|
||||
else {
|
||||
noise_counter_ = noise_period_;
|
||||
noise_output_ ^= noise_shift_register_&1;
|
||||
noise_shift_register_ |= ((noise_shift_register_ ^ (noise_shift_register_ >> 3))&1) << 17;
|
||||
@@ -122,8 +111,7 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
|
||||
// ... and the envelope generator. Table based for pattern lookup, with a 'refill' step — a way of
|
||||
// implementing non-repeating patterns by locking them to table position 0x1f.
|
||||
if(envelope_divider_) envelope_divider_--;
|
||||
else
|
||||
{
|
||||
else {
|
||||
envelope_divider_ = envelope_period_;
|
||||
envelope_position_ ++;
|
||||
if(envelope_position_ == 32) envelope_position_ = envelope_overflow_masks_[output_registers_[13]];
|
||||
@@ -131,19 +119,17 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
|
||||
|
||||
evaluate_output_volume();
|
||||
|
||||
for(int ic = 0; ic < 16 && c < number_of_samples; ic++)
|
||||
{
|
||||
for(int ic = 0; ic < 8 && c < number_of_samples; ic++) {
|
||||
target[c] = output_volume_;
|
||||
c++;
|
||||
master_divider_++;
|
||||
}
|
||||
}
|
||||
|
||||
master_divider_ &= 15;
|
||||
master_divider_ &= 7;
|
||||
}
|
||||
|
||||
void AY38910::evaluate_output_volume()
|
||||
{
|
||||
void AY38910::evaluate_output_volume() {
|
||||
int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_];
|
||||
|
||||
// The output level for a channel is:
|
||||
@@ -180,24 +166,19 @@ void AY38910::evaluate_output_volume()
|
||||
);
|
||||
}
|
||||
|
||||
void AY38910::select_register(uint8_t r)
|
||||
{
|
||||
void AY38910::select_register(uint8_t r) {
|
||||
selected_register_ = r & 0xf;
|
||||
}
|
||||
|
||||
void AY38910::set_register_value(uint8_t value)
|
||||
{
|
||||
void AY38910::set_register_value(uint8_t value) {
|
||||
registers_[selected_register_] = value;
|
||||
if(selected_register_ < 14)
|
||||
{
|
||||
if(selected_register_ < 14) {
|
||||
int selected_register = selected_register_;
|
||||
enqueue([=] () {
|
||||
uint8_t masked_value = value;
|
||||
switch(selected_register)
|
||||
{
|
||||
switch(selected_register) {
|
||||
case 0: case 2: case 4:
|
||||
case 1: case 3: case 5:
|
||||
{
|
||||
case 1: case 3: case 5: {
|
||||
int channel = selected_register >> 1;
|
||||
|
||||
if(selected_register & 1)
|
||||
@@ -234,8 +215,7 @@ void AY38910::set_register_value(uint8_t value)
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t AY38910::get_register_value()
|
||||
{
|
||||
uint8_t AY38910::get_register_value() {
|
||||
// This table ensures that bits that aren't defined within the AY are returned as 1s
|
||||
// when read. I can't find documentation on this and don't have a machine to test, so
|
||||
// this is provisionally a guess. TODO: investigate.
|
||||
@@ -247,26 +227,21 @@ uint8_t AY38910::get_register_value()
|
||||
return registers_[selected_register_] | register_masks[selected_register_];
|
||||
}
|
||||
|
||||
uint8_t AY38910::get_port_output(bool port_b)
|
||||
{
|
||||
uint8_t AY38910::get_port_output(bool port_b) {
|
||||
return registers_[port_b ? 15 : 14];
|
||||
}
|
||||
|
||||
void AY38910::set_data_input(uint8_t r)
|
||||
{
|
||||
void AY38910::set_data_input(uint8_t r) {
|
||||
data_input_ = r;
|
||||
}
|
||||
|
||||
uint8_t AY38910::get_data_output()
|
||||
{
|
||||
uint8_t AY38910::get_data_output() {
|
||||
return data_output_;
|
||||
}
|
||||
|
||||
void AY38910::set_control_lines(ControlLines control_lines)
|
||||
{
|
||||
void AY38910::set_control_lines(ControlLines control_lines) {
|
||||
ControlState new_state;
|
||||
switch((int)control_lines)
|
||||
{
|
||||
switch((int)control_lines) {
|
||||
default: new_state = Inactive; break;
|
||||
|
||||
case (int)(BCDIR | BC2 | BC1):
|
||||
@@ -277,11 +252,9 @@ void AY38910::set_control_lines(ControlLines control_lines)
|
||||
case (int)(BCDIR | BC2): new_state = Write; break;
|
||||
}
|
||||
|
||||
if(new_state != control_state_)
|
||||
{
|
||||
if(new_state != control_state_) {
|
||||
control_state_ = new_state;
|
||||
switch(new_state)
|
||||
{
|
||||
switch(new_state) {
|
||||
default: break;
|
||||
case LatchAddress: select_register(data_input_); break;
|
||||
case Write: set_register_value(data_input_); break;
|
||||
|
||||
@@ -19,26 +19,21 @@ AsyncTaskQueue::AsyncTaskQueue()
|
||||
serial_dispatch_queue_ = dispatch_queue_create("com.thomasharte.clocksignal.asyntaskqueue", DISPATCH_QUEUE_SERIAL);
|
||||
#else
|
||||
thread_.reset(new std::thread([this]() {
|
||||
while(!should_destruct_)
|
||||
{
|
||||
while(!should_destruct_) {
|
||||
std::function<void(void)> next_function;
|
||||
|
||||
// Take lock, check for a new task
|
||||
std::unique_lock<std::mutex> lock(queue_mutex_);
|
||||
if(!pending_tasks_.empty())
|
||||
{
|
||||
if(!pending_tasks_.empty()) {
|
||||
next_function = pending_tasks_.front();
|
||||
pending_tasks_.pop_front();
|
||||
}
|
||||
|
||||
if(next_function)
|
||||
{
|
||||
if(next_function) {
|
||||
// If there is a task, release lock and perform it
|
||||
lock.unlock();
|
||||
next_function();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// If there isn't a task, atomically block on the processing condition and release the lock
|
||||
// until there's something pending (and then release it again via scope)
|
||||
processing_condition_.wait(lock);
|
||||
@@ -48,8 +43,7 @@ AsyncTaskQueue::AsyncTaskQueue()
|
||||
#endif
|
||||
}
|
||||
|
||||
AsyncTaskQueue::~AsyncTaskQueue()
|
||||
{
|
||||
AsyncTaskQueue::~AsyncTaskQueue() {
|
||||
#ifdef __APPLE__
|
||||
dispatch_release(serial_dispatch_queue_);
|
||||
#else
|
||||
@@ -60,8 +54,7 @@ AsyncTaskQueue::~AsyncTaskQueue()
|
||||
#endif
|
||||
}
|
||||
|
||||
void AsyncTaskQueue::enqueue(std::function<void(void)> function)
|
||||
{
|
||||
void AsyncTaskQueue::enqueue(std::function<void(void)> function) {
|
||||
#ifdef __APPLE__
|
||||
dispatch_async(serial_dispatch_queue_, ^{function();});
|
||||
#else
|
||||
@@ -71,8 +64,7 @@ void AsyncTaskQueue::enqueue(std::function<void(void)> function)
|
||||
#endif
|
||||
}
|
||||
|
||||
void AsyncTaskQueue::flush()
|
||||
{
|
||||
void AsyncTaskQueue::flush() {
|
||||
#ifdef __APPLE__
|
||||
dispatch_sync(serial_dispatch_queue_, ^{});
|
||||
#else
|
||||
|
||||
@@ -10,772 +10,148 @@
|
||||
#include <algorithm>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "Cartridges/CartridgeAtari8k.hpp"
|
||||
#include "Cartridges/CartridgeAtari16k.hpp"
|
||||
#include "Cartridges/CartridgeAtari32k.hpp"
|
||||
#include "Cartridges/CartridgeActivisionStack.hpp"
|
||||
#include "Cartridges/CartridgeCBSRAMPlus.hpp"
|
||||
#include "Cartridges/CartridgeCommaVid.hpp"
|
||||
#include "Cartridges/CartridgeMegaBoy.hpp"
|
||||
#include "Cartridges/CartridgeMNetwork.hpp"
|
||||
#include "Cartridges/CartridgeParkerBros.hpp"
|
||||
#include "Cartridges/CartridgePitfall2.hpp"
|
||||
#include "Cartridges/CartridgeTigervision.hpp"
|
||||
#include "Cartridges/CartridgeUnpaged.hpp"
|
||||
|
||||
using namespace Atari2600;
|
||||
namespace {
|
||||
static const unsigned int horizontalTimerPeriod = 228;
|
||||
static const double NTSC_clock_rate = 1194720;
|
||||
static const double PAL_clock_rate = 1182298;
|
||||
}
|
||||
|
||||
Machine::Machine() :
|
||||
horizontal_timer_(0),
|
||||
last_output_state_duration_(0),
|
||||
last_output_state_(OutputState::Sync),
|
||||
rom_(nullptr),
|
||||
tia_input_value_{0xff, 0xff},
|
||||
upcoming_events_pointer_(0),
|
||||
object_counter_pointer_(0),
|
||||
state_by_time_(state_by_extend_time_[0]),
|
||||
cycles_since_speaker_update_(0),
|
||||
is_pal_region_(false)
|
||||
{
|
||||
memset(collisions_, 0xff, sizeof(collisions_));
|
||||
setup_reported_collisions();
|
||||
|
||||
for(int vbextend = 0; vbextend < 2; vbextend++)
|
||||
{
|
||||
for(int c = 0; c < 57; c++)
|
||||
{
|
||||
OutputState state;
|
||||
|
||||
// determine which output state will be active in four cycles from now
|
||||
switch(c)
|
||||
{
|
||||
case 0: case 1: case 2: case 3: state = OutputState::Blank; break;
|
||||
case 4: case 5: case 6: case 7: state = OutputState::Sync; break;
|
||||
case 8: case 9: case 10: case 11: state = OutputState::ColourBurst; break;
|
||||
case 12: case 13: case 14:
|
||||
case 15: case 16: state = OutputState::Blank; break;
|
||||
|
||||
case 17: case 18: state = vbextend ? OutputState::Blank : OutputState::Pixel; break;
|
||||
default: state = OutputState::Pixel; break;
|
||||
}
|
||||
|
||||
state_by_extend_time_[vbextend][c] = state;
|
||||
}
|
||||
}
|
||||
frame_record_pointer_(0),
|
||||
is_ntsc_(true) {
|
||||
set_clock_rate(NTSC_clock_rate);
|
||||
}
|
||||
|
||||
void Machine::setup_output(float aspect_ratio)
|
||||
{
|
||||
speaker_.reset(new Speaker);
|
||||
crt_.reset(new Outputs::CRT::CRT(228, 1, 263, Outputs::CRT::ColourSpace::YIQ, 228, 1, 1));
|
||||
crt_->set_output_device(Outputs::CRT::Television);
|
||||
|
||||
// this is the NTSC phase offset function; see below for PAL
|
||||
crt_->set_composite_sampling_function(
|
||||
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
|
||||
"{"
|
||||
"uint c = texture(texID, coordinate).r;"
|
||||
"uint y = c & 14u;"
|
||||
"uint iPhase = (c >> 4);"
|
||||
|
||||
"float phaseOffset = 6.283185308 * float(iPhase - 1u) / 13.0;"
|
||||
"return mix(float(y) / 14.0, step(1, iPhase) * cos(phase + phaseOffset), amplitude);"
|
||||
"}");
|
||||
speaker_->set_input_rate((float)(get_clock_rate() / 38.0));
|
||||
void Machine::setup_output(float aspect_ratio) {
|
||||
bus_->tia_.reset(new TIA);
|
||||
bus_->speaker_.reset(new Speaker);
|
||||
bus_->speaker_->set_input_rate((float)(get_clock_rate() / (double)CPUTicksPerAudioTick));
|
||||
bus_->tia_->get_crt()->set_delegate(this);
|
||||
}
|
||||
|
||||
void Machine::switch_region()
|
||||
{
|
||||
// the PAL function
|
||||
crt_->set_composite_sampling_function(
|
||||
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
|
||||
"{"
|
||||
"uint c = texture(texID, coordinate).r;"
|
||||
"uint y = c & 14u;"
|
||||
"uint iPhase = (c >> 4);"
|
||||
|
||||
"uint direction = iPhase & 1u;"
|
||||
"float phaseOffset = float(7u - direction) + (float(direction) - 0.5) * 2.0 * float(iPhase >> 1);"
|
||||
"phaseOffset *= 6.283185308 / 12.0;"
|
||||
"return mix(float(y) / 14.0, step(4, (iPhase + 2u) & 15u) * cos(phase + phaseOffset), amplitude);"
|
||||
"}");
|
||||
|
||||
crt_->set_new_timing(228, 312, Outputs::CRT::ColourSpace::YUV, 228, 1);
|
||||
|
||||
is_pal_region_ = true;
|
||||
speaker_->set_input_rate((float)(get_clock_rate() / 38.0));
|
||||
set_clock_rate(PAL_clock_rate);
|
||||
void Machine::close_output() {
|
||||
bus_.reset();
|
||||
}
|
||||
|
||||
void Machine::close_output()
|
||||
{
|
||||
crt_ = nullptr;
|
||||
}
|
||||
|
||||
Machine::~Machine()
|
||||
{
|
||||
delete[] rom_;
|
||||
Machine::~Machine() {
|
||||
close_output();
|
||||
}
|
||||
|
||||
void Machine::update_timers(int mask)
|
||||
{
|
||||
unsigned int upcoming_pointer_plus_4 = (upcoming_events_pointer_ + 4)%number_of_upcoming_events;
|
||||
|
||||
object_counter_pointer_ = (object_counter_pointer_ + 1)%number_of_recorded_counters;
|
||||
ObjectCounter *oneClockAgo = object_counter_[(object_counter_pointer_ - 1 + number_of_recorded_counters)%number_of_recorded_counters];
|
||||
ObjectCounter *twoClocksAgo = object_counter_[(object_counter_pointer_ - 2 + number_of_recorded_counters)%number_of_recorded_counters];
|
||||
ObjectCounter *now = object_counter_[object_counter_pointer_];
|
||||
|
||||
// grab the background now, for application in four clocks
|
||||
if(mask & (1 << 5) && !(horizontal_timer_&3))
|
||||
{
|
||||
unsigned int offset = 4 + horizontal_timer_ - (horizontalTimerPeriod - 160);
|
||||
upcoming_events_[upcoming_pointer_plus_4].updates |= Event::Action::Playfield;
|
||||
upcoming_events_[upcoming_pointer_plus_4].playfield_pixel = playfield_[(offset >> 2)%40];
|
||||
}
|
||||
|
||||
if(mask & (1 << 4))
|
||||
{
|
||||
// the ball becomes visible whenever it hits zero, regardless of whether its status
|
||||
// is the result of a counter rollover or a programmatic reset, and there's a four
|
||||
// clock delay on that triggering the start signal
|
||||
now[4].count = (oneClockAgo[4].count + 1)%160;
|
||||
now[4].pixel = oneClockAgo[4].pixel + 1;
|
||||
if(!now[4].count) now[4].pixel = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
now[4] = oneClockAgo[4];
|
||||
}
|
||||
|
||||
// check for player and missle triggers
|
||||
for(int c = 0; c < 4; c++)
|
||||
{
|
||||
if(mask & (1 << c))
|
||||
{
|
||||
// update the count
|
||||
now[c].count = (oneClockAgo[c].count + 1)%160;
|
||||
|
||||
uint8_t repeatMask = player_and_missile_size_[c&1] & 7;
|
||||
ObjectCounter *rollover;
|
||||
ObjectCounter *equality;
|
||||
|
||||
if(c < 2)
|
||||
{
|
||||
// update the pixel
|
||||
now[c].broad_pixel = oneClockAgo[c].broad_pixel + 1;
|
||||
switch(repeatMask)
|
||||
{
|
||||
default: now[c].pixel = oneClockAgo[c].pixel + 1; break;
|
||||
case 5: now[c].pixel = oneClockAgo[c].pixel + (now[c].broad_pixel&1); break;
|
||||
case 7: now[c].pixel = oneClockAgo[c].pixel + (((now[c].broad_pixel | (now[c].broad_pixel >> 1))^1)&1); break;
|
||||
}
|
||||
|
||||
// check for a rollover six clocks ago or equality five clocks ago
|
||||
rollover = twoClocksAgo;
|
||||
equality = oneClockAgo;
|
||||
}
|
||||
else
|
||||
{
|
||||
// update the pixel
|
||||
now[c].pixel = oneClockAgo[c].pixel + 1;
|
||||
|
||||
// check for a rollover five clocks ago or equality four clocks ago
|
||||
rollover = oneClockAgo;
|
||||
equality = now;
|
||||
}
|
||||
|
||||
if(
|
||||
(rollover[c].count == 159) ||
|
||||
(has_second_copy_[c&1] && equality[c].count == 16) ||
|
||||
(has_third_copy_[c&1] && equality[c].count == 32) ||
|
||||
(has_fourth_copy_[c&1] && equality[c].count == 64)
|
||||
)
|
||||
{
|
||||
now[c].pixel = 0;
|
||||
now[c].broad_pixel = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
now[c] = oneClockAgo[c];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Machine::get_output_pixel()
|
||||
{
|
||||
ObjectCounter *now = object_counter_[object_counter_pointer_];
|
||||
|
||||
// get the playfield pixel
|
||||
unsigned int offset = horizontal_timer_ - (horizontalTimerPeriod - 160);
|
||||
uint8_t playfieldColour = ((playfield_control_&6) == 2) ? player_colour_[offset / 80] : playfield_colour_;
|
||||
|
||||
// ball pixel
|
||||
uint8_t ballPixel = 0;
|
||||
if(now[4].pixel < ball_size_) {
|
||||
ballPixel = ball_graphics_enable_[ball_graphics_selector_];
|
||||
}
|
||||
|
||||
// determine the player and missile pixels
|
||||
uint8_t playerPixels[2] = { 0, 0 };
|
||||
uint8_t missilePixels[2] = { 0, 0 };
|
||||
for(int c = 0; c < 2; c++)
|
||||
{
|
||||
if(player_graphics_[c] && now[c].pixel < 8) {
|
||||
playerPixels[c] = (player_graphics_[player_graphics_selector_[c]][c] >> (now[c].pixel ^ player_reflection_mask_[c])) & 1;
|
||||
}
|
||||
|
||||
if(!missile_graphics_reset_[c] && now[c+2].pixel < missile_size_[c]) {
|
||||
missilePixels[c] = missile_graphics_enable_[c];
|
||||
}
|
||||
}
|
||||
|
||||
// accumulate collisions
|
||||
int pixel_mask = playerPixels[0] | (playerPixels[1] << 1) | (missilePixels[0] << 2) | (missilePixels[1] << 3) | (ballPixel << 4) | (playfield_output_ << 5);
|
||||
collisions_[0] |= reported_collisions_[pixel_mask][0];
|
||||
collisions_[1] |= reported_collisions_[pixel_mask][1];
|
||||
collisions_[2] |= reported_collisions_[pixel_mask][2];
|
||||
collisions_[3] |= reported_collisions_[pixel_mask][3];
|
||||
collisions_[4] |= reported_collisions_[pixel_mask][4];
|
||||
collisions_[5] |= reported_collisions_[pixel_mask][5];
|
||||
collisions_[6] |= reported_collisions_[pixel_mask][6];
|
||||
collisions_[7] |= reported_collisions_[pixel_mask][7];
|
||||
|
||||
// apply appropriate priority to pick a colour
|
||||
uint8_t playfield_pixel = playfield_output_ | ballPixel;
|
||||
uint8_t outputColour = playfield_pixel ? playfieldColour : background_colour_;
|
||||
|
||||
if(!(playfield_control_&0x04) || !playfield_pixel) {
|
||||
if(playerPixels[1] || missilePixels[1]) outputColour = player_colour_[1];
|
||||
if(playerPixels[0] || missilePixels[0]) outputColour = player_colour_[0];
|
||||
}
|
||||
|
||||
// return colour
|
||||
return outputColour;
|
||||
}
|
||||
|
||||
void Machine::setup_reported_collisions()
|
||||
{
|
||||
for(int c = 0; c < 64; c++)
|
||||
{
|
||||
memset(reported_collisions_[c], 0, 8);
|
||||
|
||||
int playerPixels[2] = { c&1, (c >> 1)&1 };
|
||||
int missilePixels[2] = { (c >> 2)&1, (c >> 3)&1 };
|
||||
int ballPixel = (c >> 4)&1;
|
||||
int playfield_pixel = (c >> 5)&1;
|
||||
|
||||
if(playerPixels[0] | playerPixels[1]) {
|
||||
reported_collisions_[c][0] |= ((missilePixels[0] & playerPixels[1]) << 7) | ((missilePixels[0] & playerPixels[0]) << 6);
|
||||
reported_collisions_[c][1] |= ((missilePixels[1] & playerPixels[0]) << 7) | ((missilePixels[1] & playerPixels[1]) << 6);
|
||||
|
||||
reported_collisions_[c][2] |= ((playfield_pixel & playerPixels[0]) << 7) | ((ballPixel & playerPixels[0]) << 6);
|
||||
reported_collisions_[c][3] |= ((playfield_pixel & playerPixels[1]) << 7) | ((ballPixel & playerPixels[1]) << 6);
|
||||
|
||||
reported_collisions_[c][7] |= ((playerPixels[0] & playerPixels[1]) << 7);
|
||||
}
|
||||
|
||||
if(playfield_pixel | ballPixel) {
|
||||
reported_collisions_[c][4] |= ((playfield_pixel & missilePixels[0]) << 7) | ((ballPixel & missilePixels[0]) << 6);
|
||||
reported_collisions_[c][5] |= ((playfield_pixel & missilePixels[1]) << 7) | ((ballPixel & missilePixels[1]) << 6);
|
||||
|
||||
reported_collisions_[c][6] |= ((playfield_pixel & ballPixel) << 7);
|
||||
}
|
||||
|
||||
if(missilePixels[0] & missilePixels[1])
|
||||
reported_collisions_[c][7] |= (1 << 6);
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::output_pixels(unsigned int count)
|
||||
{
|
||||
while(count--)
|
||||
{
|
||||
if(upcoming_events_[upcoming_events_pointer_].updates)
|
||||
{
|
||||
// apply any queued changes and flush the record
|
||||
if(upcoming_events_[upcoming_events_pointer_].updates & Event::Action::HMoveSetup)
|
||||
{
|
||||
// schedule an extended left border
|
||||
state_by_time_ = state_by_extend_time_[1];
|
||||
|
||||
// clear any ongoing moves
|
||||
if(hmove_flags_)
|
||||
{
|
||||
for(int c = 0; c < number_of_upcoming_events; c++)
|
||||
{
|
||||
upcoming_events_[c].updates &= ~(Event::Action::HMoveCompare | Event::Action::HMoveDecrement);
|
||||
}
|
||||
}
|
||||
|
||||
// schedule new moves
|
||||
hmove_flags_ = 0x1f;
|
||||
hmove_counter_ = 15;
|
||||
|
||||
// follow-through into a compare immediately
|
||||
upcoming_events_[upcoming_events_pointer_].updates |= Event::Action::HMoveCompare;
|
||||
}
|
||||
|
||||
if(upcoming_events_[upcoming_events_pointer_].updates & Event::Action::HMoveCompare)
|
||||
{
|
||||
for(int c = 0; c < 5; c++)
|
||||
{
|
||||
if(((object_motions_[c] >> 4)^hmove_counter_) == 7)
|
||||
{
|
||||
hmove_flags_ &= ~(1 << c);
|
||||
}
|
||||
}
|
||||
if(hmove_flags_)
|
||||
{
|
||||
if(hmove_counter_) hmove_counter_--;
|
||||
upcoming_events_[(upcoming_events_pointer_+4)%number_of_upcoming_events].updates |= Event::Action::HMoveCompare;
|
||||
upcoming_events_[(upcoming_events_pointer_+2)%number_of_upcoming_events].updates |= Event::Action::HMoveDecrement;
|
||||
}
|
||||
}
|
||||
|
||||
if(upcoming_events_[upcoming_events_pointer_].updates & Event::Action::HMoveDecrement)
|
||||
{
|
||||
update_timers(hmove_flags_);
|
||||
}
|
||||
|
||||
if(upcoming_events_[upcoming_events_pointer_].updates & Event::Action::ResetCounter)
|
||||
{
|
||||
object_counter_[object_counter_pointer_][upcoming_events_[upcoming_events_pointer_].counter].count = 0;
|
||||
}
|
||||
|
||||
// zero out current update event
|
||||
upcoming_events_[upcoming_events_pointer_].updates = 0;
|
||||
}
|
||||
|
||||
// progress to next event
|
||||
upcoming_events_pointer_ = (upcoming_events_pointer_ + 1)%number_of_upcoming_events;
|
||||
|
||||
// determine which output state is currently active
|
||||
OutputState primary_state = state_by_time_[horizontal_timer_ >> 2];
|
||||
OutputState effective_state = primary_state;
|
||||
|
||||
// update pixel timers
|
||||
if(primary_state == OutputState::Pixel) update_timers(~0);
|
||||
|
||||
// update the background chain
|
||||
if(horizontal_timer_ >= 64 && horizontal_timer_ <= 160+64 && !(horizontal_timer_&3))
|
||||
{
|
||||
playfield_output_ = next_playfield_output_;
|
||||
next_playfield_output_ = playfield_[(horizontal_timer_ - 64) >> 2];
|
||||
}
|
||||
|
||||
// if vsync is enabled, output the opposite of the automatic hsync output;
|
||||
// also honour the vertical blank flag
|
||||
if(vsync_enabled_) {
|
||||
effective_state = (effective_state = OutputState::Sync) ? OutputState::Blank : OutputState::Sync;
|
||||
} else if(vblank_enabled_ && effective_state == OutputState::Pixel) {
|
||||
effective_state = OutputState::Blank;
|
||||
}
|
||||
|
||||
// decide what that means needs to be communicated to the CRT
|
||||
last_output_state_duration_++;
|
||||
if(effective_state != last_output_state_) {
|
||||
switch(last_output_state_) {
|
||||
case OutputState::Blank: crt_->output_blank(last_output_state_duration_); break;
|
||||
case OutputState::Sync: crt_->output_sync(last_output_state_duration_); break;
|
||||
case OutputState::ColourBurst: crt_->output_colour_burst(last_output_state_duration_, 96, 0); break;
|
||||
case OutputState::Pixel: crt_->output_data(last_output_state_duration_, 1); break;
|
||||
}
|
||||
last_output_state_duration_ = 0;
|
||||
last_output_state_ = effective_state;
|
||||
|
||||
if(effective_state == OutputState::Pixel) {
|
||||
output_buffer_ = crt_->allocate_write_area(160);
|
||||
} else {
|
||||
output_buffer_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// decide on a pixel colour if that's what's happening
|
||||
if(effective_state == OutputState::Pixel)
|
||||
{
|
||||
uint8_t colour = get_output_pixel();
|
||||
if(output_buffer_)
|
||||
{
|
||||
*output_buffer_ = colour;
|
||||
output_buffer_++;
|
||||
}
|
||||
}
|
||||
|
||||
// advance horizontal timer, perform reset actions if desired
|
||||
horizontal_timer_ = (horizontal_timer_ + 1) % horizontalTimerPeriod;
|
||||
if(!horizontal_timer_)
|
||||
{
|
||||
// switch back to a normal length left border
|
||||
state_by_time_ = state_by_extend_time_[0];
|
||||
set_ready_line(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value)
|
||||
{
|
||||
uint8_t returnValue = 0xff;
|
||||
unsigned int cycles_run_for = 3;
|
||||
|
||||
// this occurs as a feedback loop — the 2600 requests ready, then performs the cycles_run_for
|
||||
// leap to the end of ready only once ready is signalled — because on a 6502 ready doesn't take
|
||||
// effect until the next read; therefore it isn't safe to assume that signalling ready immediately
|
||||
// skips to the end of the line.
|
||||
if(operation == CPU6502::BusOperation::Ready) {
|
||||
unsigned int distance_to_end_of_ready = horizontalTimerPeriod - horizontal_timer_;
|
||||
cycles_run_for = distance_to_end_of_ready;
|
||||
}
|
||||
|
||||
output_pixels(cycles_run_for);
|
||||
cycles_since_speaker_update_ += cycles_run_for;
|
||||
|
||||
if(operation != CPU6502::BusOperation::Ready) {
|
||||
|
||||
// check for a paging access
|
||||
if(rom_size_ > 4096 && ((address & 0x1f00) == 0x1f00)) {
|
||||
uint8_t *base_ptr = rom_pages_[0];
|
||||
uint8_t first_paging_register = (uint8_t)(0xf8 - (rom_size_ >> 14)*2);
|
||||
|
||||
const uint8_t paging_register = address&0xff;
|
||||
if(paging_register >= first_paging_register) {
|
||||
const uint16_t selected_page = paging_register - first_paging_register;
|
||||
if(selected_page * 4096 < rom_size_) {
|
||||
base_ptr = &rom_[selected_page * 4096];
|
||||
}
|
||||
}
|
||||
|
||||
if(base_ptr != rom_pages_[0]) {
|
||||
rom_pages_[0] = base_ptr;
|
||||
rom_pages_[1] = base_ptr + 1024;
|
||||
rom_pages_[2] = base_ptr + 2048;
|
||||
rom_pages_[3] = base_ptr + 3072;
|
||||
}
|
||||
}
|
||||
|
||||
// check for a ROM read
|
||||
if((address&0x1000) && isReadOperation(operation)) {
|
||||
returnValue &= rom_pages_[(address >> 10)&3][address&1023];
|
||||
}
|
||||
|
||||
// check for a RAM access
|
||||
if((address&0x1280) == 0x80) {
|
||||
if(isReadOperation(operation)) {
|
||||
returnValue &= mos6532_.get_ram(address);
|
||||
} else {
|
||||
mos6532_.set_ram(address, *value);
|
||||
}
|
||||
}
|
||||
|
||||
// check for a TIA access
|
||||
if(!(address&0x1080)) {
|
||||
if(isReadOperation(operation)) {
|
||||
const uint16_t decodedAddress = address & 0xf;
|
||||
switch(decodedAddress) {
|
||||
case 0x00: // missile 0 / player collisions
|
||||
case 0x01: // missile 1 / player collisions
|
||||
case 0x02: // player 0 / playfield / ball collisions
|
||||
case 0x03: // player 1 / playfield / ball collisions
|
||||
case 0x04: // missile 0 / playfield / ball collisions
|
||||
case 0x05: // missile 1 / playfield / ball collisions
|
||||
case 0x06: // ball / playfield collisions
|
||||
case 0x07: // player / player, missile / missile collisions
|
||||
returnValue &= collisions_[decodedAddress];
|
||||
break;
|
||||
|
||||
case 0x08:
|
||||
case 0x09:
|
||||
case 0x0a:
|
||||
case 0x0b:
|
||||
// TODO: pot ports
|
||||
break;
|
||||
|
||||
case 0x0c:
|
||||
case 0x0d:
|
||||
returnValue &= tia_input_value_[decodedAddress - 0x0c];
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
const uint16_t decodedAddress = address & 0x3f;
|
||||
switch(decodedAddress) {
|
||||
case 0x00:
|
||||
vsync_enabled_ = !!(*value & 0x02);
|
||||
break;
|
||||
case 0x01: vblank_enabled_ = !!(*value & 0x02); break;
|
||||
|
||||
case 0x02:
|
||||
if(horizontal_timer_) set_ready_line(true);
|
||||
break;
|
||||
case 0x03:
|
||||
// Reset is delayed by four cycles.
|
||||
horizontal_timer_ = horizontalTimerPeriod - 4;
|
||||
|
||||
// TODO: audio will now be out of synchronisation — fix
|
||||
break;
|
||||
|
||||
case 0x04:
|
||||
case 0x05: {
|
||||
int entry = decodedAddress - 0x04;
|
||||
player_and_missile_size_[entry] = *value;
|
||||
missile_size_[entry] = 1 << ((*value >> 4)&3);
|
||||
|
||||
uint8_t repeatMask = (*value)&7;
|
||||
has_second_copy_[entry] = (repeatMask == 1) || (repeatMask == 3);
|
||||
has_third_copy_[entry] = (repeatMask == 2) || (repeatMask == 3) || (repeatMask == 6);
|
||||
has_fourth_copy_[entry] = (repeatMask == 4) || (repeatMask == 6);
|
||||
} break;
|
||||
|
||||
case 0x06:
|
||||
case 0x07: player_colour_[decodedAddress - 0x06] = *value; break;
|
||||
case 0x08: playfield_colour_ = *value; break;
|
||||
case 0x09: background_colour_ = *value; break;
|
||||
|
||||
case 0x0a: {
|
||||
uint8_t old_playfield_control = playfield_control_;
|
||||
playfield_control_ = *value;
|
||||
ball_size_ = 1 << ((playfield_control_ >> 4)&3);
|
||||
|
||||
// did the mirroring bit change?
|
||||
if((playfield_control_^old_playfield_control)&1) {
|
||||
if(playfield_control_&1) {
|
||||
for(int c = 0; c < 20; c++) playfield_[c+20] = playfield_[19-c];
|
||||
} else {
|
||||
memcpy(&playfield_[20], playfield_, 20);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case 0x0b:
|
||||
case 0x0c: player_reflection_mask_[decodedAddress - 0x0b] = (*value)&8 ? 0 : 7; break;
|
||||
|
||||
case 0x0d:
|
||||
playfield_[0] = ((*value) >> 4)&1;
|
||||
playfield_[1] = ((*value) >> 5)&1;
|
||||
playfield_[2] = ((*value) >> 6)&1;
|
||||
playfield_[3] = (*value) >> 7;
|
||||
|
||||
if(playfield_control_&1) {
|
||||
for(int c = 0; c < 4; c++) playfield_[39-c] = playfield_[c];
|
||||
} else {
|
||||
memcpy(&playfield_[20], playfield_, 4);
|
||||
}
|
||||
break;
|
||||
case 0x0e:
|
||||
playfield_[4] = (*value) >> 7;
|
||||
playfield_[5] = ((*value) >> 6)&1;
|
||||
playfield_[6] = ((*value) >> 5)&1;
|
||||
playfield_[7] = ((*value) >> 4)&1;
|
||||
playfield_[8] = ((*value) >> 3)&1;
|
||||
playfield_[9] = ((*value) >> 2)&1;
|
||||
playfield_[10] = ((*value) >> 1)&1;
|
||||
playfield_[11] = (*value)&1;
|
||||
|
||||
if(playfield_control_&1) {
|
||||
for(int c = 0; c < 8; c++) playfield_[35-c] = playfield_[c+4];
|
||||
} else {
|
||||
memcpy(&playfield_[24], &playfield_[4], 8);
|
||||
}
|
||||
break;
|
||||
case 0x0f:
|
||||
playfield_[19] = (*value) >> 7;
|
||||
playfield_[18] = ((*value) >> 6)&1;
|
||||
playfield_[17] = ((*value) >> 5)&1;
|
||||
playfield_[16] = ((*value) >> 4)&1;
|
||||
playfield_[15] = ((*value) >> 3)&1;
|
||||
playfield_[14] = ((*value) >> 2)&1;
|
||||
playfield_[13] = ((*value) >> 1)&1;
|
||||
playfield_[12] = (*value)&1;
|
||||
|
||||
if(playfield_control_&1) {
|
||||
for(int c = 0; c < 8; c++) playfield_[27-c] = playfield_[c+12];
|
||||
} else {
|
||||
memcpy(&playfield_[32], &playfield_[12], 8);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x10: case 0x11: case 0x12: case 0x13:
|
||||
case 0x14:
|
||||
upcoming_events_[(upcoming_events_pointer_ + 4)%number_of_upcoming_events].updates |= Event::Action::ResetCounter;
|
||||
upcoming_events_[(upcoming_events_pointer_ + 4)%number_of_upcoming_events].counter = decodedAddress - 0x10;
|
||||
break;
|
||||
|
||||
case 0x15: case 0x16:
|
||||
update_audio();
|
||||
speaker_->set_control(decodedAddress - 0x15, *value);
|
||||
break;
|
||||
|
||||
case 0x17: case 0x18:
|
||||
update_audio();
|
||||
speaker_->set_divider(decodedAddress - 0x17, *value);
|
||||
break;
|
||||
|
||||
case 0x19: case 0x1a:
|
||||
update_audio();
|
||||
speaker_->set_volume(decodedAddress - 0x19, *value);
|
||||
break;
|
||||
|
||||
case 0x1c:
|
||||
ball_graphics_enable_[1] = ball_graphics_enable_[0];
|
||||
case 0x1b: {
|
||||
int index = decodedAddress - 0x1b;
|
||||
player_graphics_[0][index] = *value;
|
||||
player_graphics_[1][index^1] = player_graphics_[0][index^1];
|
||||
} break;
|
||||
case 0x1d:
|
||||
case 0x1e:
|
||||
missile_graphics_enable_[decodedAddress - 0x1d] = ((*value) >> 1)&1;
|
||||
// printf("e:%02x <- %c\n", decodedAddress - 0x1d, ((*value)&1) ? 'E' : '-');
|
||||
break;
|
||||
case 0x1f:
|
||||
ball_graphics_enable_[0] = ((*value) >> 1)&1;
|
||||
break;
|
||||
|
||||
case 0x20:
|
||||
case 0x21:
|
||||
case 0x22:
|
||||
case 0x23:
|
||||
case 0x24:
|
||||
object_motions_[decodedAddress - 0x20] = *value;
|
||||
break;
|
||||
|
||||
case 0x25: player_graphics_selector_[0] = (*value)&1; break;
|
||||
case 0x26: player_graphics_selector_[1] = (*value)&1; break;
|
||||
case 0x27: ball_graphics_selector_ = (*value)&1; break;
|
||||
|
||||
case 0x28:
|
||||
case 0x29:
|
||||
{
|
||||
// TODO: this should properly mean setting a flag and propagating later, I think?
|
||||
int index = decodedAddress - 0x28;
|
||||
if(!(*value&0x02) && missile_graphics_reset_[index])
|
||||
{
|
||||
object_counter_[object_counter_pointer_][index + 2].count = object_counter_[object_counter_pointer_][index].count;
|
||||
|
||||
uint8_t repeatMask = player_and_missile_size_[index] & 7;
|
||||
int extra_offset;
|
||||
switch(repeatMask)
|
||||
{
|
||||
default: extra_offset = 3; break;
|
||||
case 5: extra_offset = 6; break;
|
||||
case 7: extra_offset = 10; break;
|
||||
}
|
||||
|
||||
object_counter_[object_counter_pointer_][index + 2].count = (object_counter_[object_counter_pointer_][index + 2].count + extra_offset)%160;
|
||||
}
|
||||
missile_graphics_reset_[index] = !!((*value) & 0x02);
|
||||
// printf("r:%02x <- %c\n", decodedAddress - 0x28, ((*value)&2) ? 'R' : '-');
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x2a: {
|
||||
// justification for +5: "we need to wait at least 71 [clocks] before the HMOVE operation is complete";
|
||||
// which will take 16*4 + 2 = 66 cycles from the first compare, implying the first compare must be
|
||||
// in five cycles from now
|
||||
// int start_pause = ((horizontal_timer_ + 3)&3) + 4;
|
||||
upcoming_events_[(upcoming_events_pointer_ + 5)%number_of_upcoming_events].updates |= Event::Action::HMoveSetup;
|
||||
} break;
|
||||
case 0x2b:
|
||||
object_motions_[0] =
|
||||
object_motions_[1] =
|
||||
object_motions_[2] =
|
||||
object_motions_[3] =
|
||||
object_motions_[4] = 0;
|
||||
break;
|
||||
case 0x2c:
|
||||
collisions_[0] = collisions_[1] = collisions_[2] =
|
||||
collisions_[3] = collisions_[4] = collisions_[5] = 0x3f;
|
||||
collisions_[6] = 0x7f;
|
||||
collisions_[7] = 0x3f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check for a PIA access
|
||||
if((address&0x1280) == 0x280) {
|
||||
if(isReadOperation(operation)) {
|
||||
returnValue &= mos6532_.get_register(address);
|
||||
} else {
|
||||
mos6532_.set_register(address, *value);
|
||||
}
|
||||
}
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
*value = returnValue;
|
||||
}
|
||||
}
|
||||
|
||||
mos6532_.run_for_cycles(cycles_run_for / 3);
|
||||
|
||||
return cycles_run_for / 3;
|
||||
}
|
||||
|
||||
void Machine::set_digital_input(Atari2600DigitalInput input, bool state)
|
||||
{
|
||||
void Machine::set_digital_input(Atari2600DigitalInput input, bool state) {
|
||||
switch (input) {
|
||||
case Atari2600DigitalInputJoy1Up: mos6532_.update_port_input(0, 0x10, state); break;
|
||||
case Atari2600DigitalInputJoy1Down: mos6532_.update_port_input(0, 0x20, state); break;
|
||||
case Atari2600DigitalInputJoy1Left: mos6532_.update_port_input(0, 0x40, state); break;
|
||||
case Atari2600DigitalInputJoy1Right: mos6532_.update_port_input(0, 0x80, state); break;
|
||||
case Atari2600DigitalInputJoy1Up: bus_->mos6532_.update_port_input(0, 0x10, state); break;
|
||||
case Atari2600DigitalInputJoy1Down: bus_->mos6532_.update_port_input(0, 0x20, state); break;
|
||||
case Atari2600DigitalInputJoy1Left: bus_->mos6532_.update_port_input(0, 0x40, state); break;
|
||||
case Atari2600DigitalInputJoy1Right: bus_->mos6532_.update_port_input(0, 0x80, state); break;
|
||||
|
||||
case Atari2600DigitalInputJoy2Up: mos6532_.update_port_input(0, 0x01, state); break;
|
||||
case Atari2600DigitalInputJoy2Down: mos6532_.update_port_input(0, 0x02, state); break;
|
||||
case Atari2600DigitalInputJoy2Left: mos6532_.update_port_input(0, 0x04, state); break;
|
||||
case Atari2600DigitalInputJoy2Right: mos6532_.update_port_input(0, 0x08, state); break;
|
||||
case Atari2600DigitalInputJoy2Up: bus_->mos6532_.update_port_input(0, 0x01, state); break;
|
||||
case Atari2600DigitalInputJoy2Down: bus_->mos6532_.update_port_input(0, 0x02, state); break;
|
||||
case Atari2600DigitalInputJoy2Left: bus_->mos6532_.update_port_input(0, 0x04, state); break;
|
||||
case Atari2600DigitalInputJoy2Right: bus_->mos6532_.update_port_input(0, 0x08, state); break;
|
||||
|
||||
// TODO: latching
|
||||
case Atari2600DigitalInputJoy1Fire: if(state) tia_input_value_[0] &= ~0x80; else tia_input_value_[0] |= 0x80; break;
|
||||
case Atari2600DigitalInputJoy2Fire: if(state) tia_input_value_[1] &= ~0x80; else tia_input_value_[1] |= 0x80; break;
|
||||
case Atari2600DigitalInputJoy1Fire: if(state) bus_->tia_input_value_[0] &= ~0x80; else bus_->tia_input_value_[0] |= 0x80; break;
|
||||
case Atari2600DigitalInputJoy2Fire: if(state) bus_->tia_input_value_[1] &= ~0x80; else bus_->tia_input_value_[1] |= 0x80; break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::set_switch_is_enabled(Atari2600Switch input, bool state)
|
||||
{
|
||||
void Machine::set_switch_is_enabled(Atari2600Switch input, bool state) {
|
||||
switch(input) {
|
||||
case Atari2600SwitchReset: mos6532_.update_port_input(1, 0x01, state); break;
|
||||
case Atari2600SwitchSelect: mos6532_.update_port_input(1, 0x02, state); break;
|
||||
case Atari2600SwitchColour: mos6532_.update_port_input(1, 0x08, state); break;
|
||||
case Atari2600SwitchLeftPlayerDifficulty: mos6532_.update_port_input(1, 0x40, state); break;
|
||||
case Atari2600SwitchRightPlayerDifficulty: mos6532_.update_port_input(1, 0x80, state); break;
|
||||
case Atari2600SwitchReset: bus_->mos6532_.update_port_input(1, 0x01, state); break;
|
||||
case Atari2600SwitchSelect: bus_->mos6532_.update_port_input(1, 0x02, state); break;
|
||||
case Atari2600SwitchColour: bus_->mos6532_.update_port_input(1, 0x08, state); break;
|
||||
case Atari2600SwitchLeftPlayerDifficulty: bus_->mos6532_.update_port_input(1, 0x40, state); break;
|
||||
case Atari2600SwitchRightPlayerDifficulty: bus_->mos6532_.update_port_input(1, 0x80, state); break;
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::configure_as_target(const StaticAnalyser::Target &target)
|
||||
{
|
||||
if(!target.cartridges.front()->get_segments().size()) return;
|
||||
Storage::Cartridge::Cartridge::Segment segment = target.cartridges.front()->get_segments().front();
|
||||
size_t length = segment.data.size();
|
||||
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
|
||||
const std::vector<uint8_t> &rom = target.cartridges.front()->get_segments().front().data;
|
||||
switch(target.atari.paging_model) {
|
||||
case StaticAnalyser::Atari2600PagingModel::ActivisionStack: bus_.reset(new CartridgeActivisionStack(rom)); break;
|
||||
case StaticAnalyser::Atari2600PagingModel::CBSRamPlus: bus_.reset(new CartridgeCBSRAMPlus(rom)); break;
|
||||
case StaticAnalyser::Atari2600PagingModel::CommaVid: bus_.reset(new CartridgeCommaVid(rom)); break;
|
||||
case StaticAnalyser::Atari2600PagingModel::MegaBoy: bus_.reset(new CartridgeMegaBoy(rom)); break;
|
||||
case StaticAnalyser::Atari2600PagingModel::MNetwork: bus_.reset(new CartridgeMNetwork(rom)); break;
|
||||
case StaticAnalyser::Atari2600PagingModel::None: bus_.reset(new CartridgeUnpaged(rom)); break;
|
||||
case StaticAnalyser::Atari2600PagingModel::ParkerBros: bus_.reset(new CartridgeParkerBros(rom)); break;
|
||||
case StaticAnalyser::Atari2600PagingModel::Pitfall2: bus_.reset(new CartridgePitfall2(rom)); break;
|
||||
case StaticAnalyser::Atari2600PagingModel::Tigervision: bus_.reset(new CartridgeTigervision(rom)); break;
|
||||
|
||||
rom_size_ = 1024;
|
||||
while(rom_size_ < length && rom_size_ < 32768) rom_size_ <<= 1;
|
||||
|
||||
delete[] rom_;
|
||||
rom_ = new uint8_t[rom_size_];
|
||||
|
||||
size_t offset = 0;
|
||||
const size_t copy_step = std::min(rom_size_, length);
|
||||
while(offset < rom_size_)
|
||||
{
|
||||
size_t copy_length = std::min(copy_step, rom_size_ - offset);
|
||||
memcpy(&rom_[offset], &segment.data[0], copy_length);
|
||||
offset += copy_length;
|
||||
case StaticAnalyser::Atari2600PagingModel::Atari8k:
|
||||
if(target.atari.uses_superchip) {
|
||||
bus_.reset(new CartridgeAtari8kSuperChip(rom));
|
||||
} else {
|
||||
bus_.reset(new CartridgeAtari8k(rom));
|
||||
}
|
||||
break;
|
||||
case StaticAnalyser::Atari2600PagingModel::Atari16k:
|
||||
if(target.atari.uses_superchip) {
|
||||
bus_.reset(new CartridgeAtari16kSuperChip(rom));
|
||||
} else {
|
||||
bus_.reset(new CartridgeAtari16k(rom));
|
||||
}
|
||||
break;
|
||||
case StaticAnalyser::Atari2600PagingModel::Atari32k:
|
||||
if(target.atari.uses_superchip) {
|
||||
bus_.reset(new CartridgeAtari32kSuperChip(rom));
|
||||
} else {
|
||||
bus_.reset(new CartridgeAtari32k(rom));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
size_t romMask = rom_size_ - 1;
|
||||
rom_pages_[0] = rom_;
|
||||
rom_pages_[1] = &rom_[1024 & romMask];
|
||||
rom_pages_[2] = &rom_[2048 & romMask];
|
||||
rom_pages_[3] = &rom_[3072 & romMask];
|
||||
}
|
||||
|
||||
#pragma mark - Audio
|
||||
#pragma mark - CRT delegate
|
||||
|
||||
void Machine::update_audio()
|
||||
{
|
||||
unsigned int audio_cycles = cycles_since_speaker_update_ / 114;
|
||||
void Machine::crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs) {
|
||||
const size_t number_of_frame_records = sizeof(frame_records_) / sizeof(frame_records_[0]);
|
||||
frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_frames = number_of_frames;
|
||||
frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_unexpected_vertical_syncs = number_of_unexpected_vertical_syncs;
|
||||
frame_record_pointer_ ++;
|
||||
|
||||
speaker_->run_for_cycles(audio_cycles);
|
||||
cycles_since_speaker_update_ %= 114;
|
||||
if(frame_record_pointer_ >= 6) {
|
||||
unsigned int total_number_of_frames = 0;
|
||||
unsigned int total_number_of_unexpected_vertical_syncs = 0;
|
||||
for(size_t c = 0; c < number_of_frame_records; c++) {
|
||||
total_number_of_frames += frame_records_[c].number_of_frames;
|
||||
total_number_of_unexpected_vertical_syncs += frame_records_[c].number_of_unexpected_vertical_syncs;
|
||||
}
|
||||
|
||||
if(total_number_of_unexpected_vertical_syncs >= total_number_of_frames >> 1) {
|
||||
for(size_t c = 0; c < number_of_frame_records; c++) {
|
||||
frame_records_[c].number_of_frames = 0;
|
||||
frame_records_[c].number_of_unexpected_vertical_syncs = 0;
|
||||
}
|
||||
is_ntsc_ ^= true;
|
||||
|
||||
double clock_rate;
|
||||
if(is_ntsc_) {
|
||||
clock_rate = NTSC_clock_rate;
|
||||
bus_->tia_->set_output_mode(TIA::OutputMode::NTSC);
|
||||
} else {
|
||||
clock_rate = PAL_clock_rate;
|
||||
bus_->tia_->set_output_mode(TIA::OutputMode::PAL);
|
||||
}
|
||||
|
||||
bus_->speaker_->set_input_rate((float)(clock_rate / (double)CPUTicksPerAudioTick));
|
||||
bus_->speaker_->set_high_frequency_cut_off((float)(clock_rate / ((double)CPUTicksPerAudioTick * 2.0)));
|
||||
set_clock_rate(clock_rate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::synchronise()
|
||||
{
|
||||
update_audio();
|
||||
}
|
||||
|
||||
|
||||
@@ -13,21 +13,20 @@
|
||||
|
||||
#include "../../Processors/6502/CPU6502.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "Bus.hpp"
|
||||
#include "PIA.hpp"
|
||||
#include "Speaker.hpp"
|
||||
#include "TIA.hpp"
|
||||
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "Atari2600Inputs.h"
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
const unsigned int number_of_upcoming_events = 6;
|
||||
const unsigned int number_of_recorded_counters = 7;
|
||||
|
||||
class Machine:
|
||||
public CPU6502::Processor<Machine>,
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine {
|
||||
public ConfigurationTarget::Machine,
|
||||
public Outputs::CRT::Delegate {
|
||||
|
||||
public:
|
||||
Machine();
|
||||
@@ -38,137 +37,31 @@ class Machine:
|
||||
|
||||
void set_digital_input(Atari2600DigitalInput input, bool state);
|
||||
void set_switch_is_enabled(Atari2600Switch input, bool state);
|
||||
|
||||
// to satisfy CPU6502::Processor
|
||||
unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value);
|
||||
void synchronise();
|
||||
void set_reset_line(bool state) { bus_->set_reset_line(state); }
|
||||
|
||||
// to satisfy CRTMachine::Machine
|
||||
virtual void setup_output(float aspect_ratio);
|
||||
virtual void close_output();
|
||||
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; }
|
||||
virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return speaker_; }
|
||||
virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles); }
|
||||
// TODO: different rate for PAL
|
||||
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return bus_->tia_->get_crt(); }
|
||||
virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return bus_->speaker_; }
|
||||
virtual void run_for_cycles(int number_of_cycles) { bus_->run_for_cycles(number_of_cycles); }
|
||||
|
||||
// to satisfy Outputs::CRT::Delegate
|
||||
virtual void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs);
|
||||
|
||||
private:
|
||||
uint8_t *rom_, *rom_pages_[4];
|
||||
size_t rom_size_;
|
||||
// the bus
|
||||
std::unique_ptr<Bus> bus_;
|
||||
|
||||
// the RIOT
|
||||
PIA mos6532_;
|
||||
// output frame rate tracker
|
||||
struct FrameRecord {
|
||||
unsigned int number_of_frames;
|
||||
unsigned int number_of_unexpected_vertical_syncs;
|
||||
|
||||
// playfield registers
|
||||
uint8_t playfield_control_;
|
||||
uint8_t playfield_colour_;
|
||||
uint8_t background_colour_;
|
||||
uint8_t playfield_[41];
|
||||
|
||||
// ... and derivatives
|
||||
int ball_size_, missile_size_[2];
|
||||
|
||||
// delayed clock events
|
||||
enum OutputState {
|
||||
Sync,
|
||||
Blank,
|
||||
ColourBurst,
|
||||
Pixel
|
||||
};
|
||||
|
||||
struct Event {
|
||||
enum Action {
|
||||
Playfield = 1 << 0,
|
||||
ResetCounter = 1 << 1,
|
||||
|
||||
HMoveSetup = 1 << 2,
|
||||
HMoveCompare = 1 << 3,
|
||||
HMoveDecrement = 1 << 4,
|
||||
};
|
||||
int updates;
|
||||
|
||||
OutputState state;
|
||||
uint8_t playfield_pixel;
|
||||
int counter;
|
||||
|
||||
Event() : updates(0), playfield_pixel(0) {}
|
||||
} upcoming_events_[number_of_upcoming_events];
|
||||
unsigned int upcoming_events_pointer_;
|
||||
|
||||
// object counters
|
||||
struct ObjectCounter {
|
||||
int count; // the counter value, multiplied by four, counting phase
|
||||
int pixel; // for non-sprite objects, a count of cycles since the last counter reset; for sprite objects a count of pixels so far elapsed
|
||||
int broad_pixel; // for sprite objects, a count of cycles since the last counter reset; otherwise unused
|
||||
|
||||
ObjectCounter() : count(0), pixel(0), broad_pixel(0) {}
|
||||
} object_counter_[number_of_recorded_counters][5];
|
||||
unsigned int object_counter_pointer_;
|
||||
|
||||
// the latched playfield output
|
||||
uint8_t playfield_output_, next_playfield_output_;
|
||||
|
||||
// player registers
|
||||
uint8_t player_colour_[2];
|
||||
uint8_t player_reflection_mask_[2];
|
||||
uint8_t player_graphics_[2][2];
|
||||
uint8_t player_graphics_selector_[2];
|
||||
|
||||
// object flags
|
||||
bool has_second_copy_[2];
|
||||
bool has_third_copy_[2];
|
||||
bool has_fourth_copy_[2];
|
||||
uint8_t object_motions_[5]; // the value stored to this counter's motion register
|
||||
|
||||
// player + missile registers
|
||||
uint8_t player_and_missile_size_[2];
|
||||
|
||||
// missile registers
|
||||
uint8_t missile_graphics_enable_[2];
|
||||
bool missile_graphics_reset_[2];
|
||||
|
||||
// ball registers
|
||||
uint8_t ball_graphics_enable_[2];
|
||||
uint8_t ball_graphics_selector_;
|
||||
|
||||
// graphics output
|
||||
unsigned int horizontal_timer_;
|
||||
bool vsync_enabled_, vblank_enabled_;
|
||||
|
||||
// horizontal motion control
|
||||
uint8_t hmove_counter_;
|
||||
uint8_t hmove_flags_;
|
||||
|
||||
// joystick state
|
||||
uint8_t tia_input_value_[2];
|
||||
|
||||
// collisions
|
||||
uint8_t collisions_[8];
|
||||
|
||||
void output_pixels(unsigned int count);
|
||||
uint8_t get_output_pixel();
|
||||
void update_timers(int mask);
|
||||
|
||||
// outputs
|
||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
||||
std::shared_ptr<Speaker> speaker_;
|
||||
|
||||
// current mode
|
||||
bool is_pal_region_;
|
||||
|
||||
// speaker backlog accumlation counter
|
||||
unsigned int cycles_since_speaker_update_;
|
||||
void update_audio();
|
||||
|
||||
// latched output state
|
||||
unsigned int last_output_state_duration_;
|
||||
OutputState state_by_extend_time_[2][57];
|
||||
OutputState *state_by_time_;
|
||||
OutputState last_output_state_;
|
||||
uint8_t *output_buffer_;
|
||||
|
||||
// lookup table for collision reporting
|
||||
uint8_t reported_collisions_[64][8];
|
||||
void setup_reported_collisions();
|
||||
FrameRecord() : number_of_frames(0), number_of_unexpected_vertical_syncs(0) {}
|
||||
} frame_records_[4];
|
||||
unsigned int frame_record_pointer_;
|
||||
bool is_ntsc_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
64
Machines/Atari2600/Bus.hpp
Normal file
64
Machines/Atari2600/Bus.hpp
Normal file
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// 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 "Atari2600.hpp"
|
||||
#include "PIA.hpp"
|
||||
#include "Speaker.hpp"
|
||||
#include "TIA.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_ / (CPUTicksPerAudioTick * 3);
|
||||
cycles_since_speaker_update_ %= (CPUTicksPerAudioTick * 3);
|
||||
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
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
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
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
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
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
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
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
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
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
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
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
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
30
Machines/Atari2600/Cartridges/CartridgeUnpaged.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// CartridgeUnpaged.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 17/03/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Atari2600_CartridgeUnpaged_hpp
|
||||
#define Atari2600_CartridgeUnpaged_hpp
|
||||
|
||||
#include "Cartridge.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
class CartridgeUnpaged: public Cartridge<CartridgeUnpaged> {
|
||||
public:
|
||||
CartridgeUnpaged(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom) {}
|
||||
|
||||
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
if(isReadOperation(operation) && (address & 0x1000)) {
|
||||
*value = rom_[address & (rom_.size() - 1)];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Atari2600_CartridgeUnpaged_hpp */
|
||||
@@ -15,13 +15,11 @@ namespace Atari2600 {
|
||||
|
||||
class PIA: public MOS::MOS6532<PIA> {
|
||||
public:
|
||||
inline uint8_t get_port_input(int port)
|
||||
{
|
||||
inline uint8_t get_port_input(int port) {
|
||||
return port_values_[port];
|
||||
}
|
||||
|
||||
inline void update_port_input(int port, uint8_t mask, bool set)
|
||||
{
|
||||
inline void update_port_input(int port, uint8_t mask, bool set) {
|
||||
if(set) port_values_[port] &= ~mask; else port_values_[port] |= mask;
|
||||
set_port_did_change(port);
|
||||
}
|
||||
|
||||
@@ -16,23 +16,20 @@ Atari2600::Speaker::Speaker() :
|
||||
poly9_counter_{0x1ff, 0x1ff}
|
||||
{}
|
||||
|
||||
void Atari2600::Speaker::set_volume(int channel, uint8_t volume)
|
||||
{
|
||||
void Atari2600::Speaker::set_volume(int channel, uint8_t volume) {
|
||||
enqueue([=]() {
|
||||
volume_[channel] = volume & 0xf;
|
||||
});
|
||||
}
|
||||
|
||||
void Atari2600::Speaker::set_divider(int channel, uint8_t divider)
|
||||
{
|
||||
void Atari2600::Speaker::set_divider(int channel, uint8_t divider) {
|
||||
enqueue([=]() {
|
||||
divider_[channel] = divider & 0x1f;
|
||||
divider_counter_[channel] = 0;
|
||||
});
|
||||
}
|
||||
|
||||
void Atari2600::Speaker::set_control(int channel, uint8_t control)
|
||||
{
|
||||
void Atari2600::Speaker::set_control(int channel, uint8_t control) {
|
||||
enqueue([=]() {
|
||||
control_[channel] = control & 0xf;
|
||||
});
|
||||
@@ -42,41 +39,37 @@ void Atari2600::Speaker::set_control(int channel, uint8_t control)
|
||||
#define advance_poly5(c) poly5_counter_[channel] = (poly5_counter_[channel] >> 1) | (((poly5_counter_[channel] << 4) ^ (poly5_counter_[channel] << 2))&0x010)
|
||||
#define advance_poly9(c) poly9_counter_[channel] = (poly9_counter_[channel] >> 1) | (((poly9_counter_[channel] << 4) ^ (poly9_counter_[channel] << 8))&0x100)
|
||||
|
||||
void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *target)
|
||||
{
|
||||
for(unsigned int c = 0; c < number_of_samples; c++)
|
||||
{
|
||||
void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
|
||||
for(unsigned int c = 0; c < number_of_samples; c++) {
|
||||
target[c] = 0;
|
||||
for(int channel = 0; channel < 2; channel++)
|
||||
{
|
||||
for(int channel = 0; channel < 2; channel++) {
|
||||
divider_counter_[channel] ++;
|
||||
int divider_value = divider_counter_[channel] / (38 / CPUTicksPerAudioTick);
|
||||
int level = 0;
|
||||
switch(control_[channel])
|
||||
{
|
||||
switch(control_[channel]) {
|
||||
case 0x0: case 0xb: // constant 1
|
||||
level = 1;
|
||||
break;
|
||||
|
||||
case 0x4: case 0x5: // div2 tone
|
||||
level = (divider_counter_[channel] / (divider_[channel]+1))&1;
|
||||
level = (divider_value / (divider_[channel]+1))&1;
|
||||
break;
|
||||
|
||||
case 0xc: case 0xd: // div6 tone
|
||||
level = (divider_counter_[channel] / ((divider_[channel]+1)*3))&1;
|
||||
level = (divider_value / ((divider_[channel]+1)*3))&1;
|
||||
break;
|
||||
|
||||
case 0x6: case 0xa: // div31 tone
|
||||
level = (divider_counter_[channel] / (divider_[channel]+1))%30 <= 18;
|
||||
level = (divider_value / (divider_[channel]+1))%30 <= 18;
|
||||
break;
|
||||
|
||||
case 0xe: // div93 tone
|
||||
level = (divider_counter_[channel] / ((divider_[channel]+1)*3))%30 <= 18;
|
||||
level = (divider_value / ((divider_[channel]+1)*3))%30 <= 18;
|
||||
break;
|
||||
|
||||
case 0x1: // 4-bit poly
|
||||
level = poly4_counter_[channel]&1;
|
||||
if(divider_counter_[channel] == divider_[channel]+1)
|
||||
{
|
||||
if(divider_value == divider_[channel]+1) {
|
||||
divider_counter_[channel] = 0;
|
||||
advance_poly4(channel);
|
||||
}
|
||||
@@ -84,18 +77,15 @@ void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *ta
|
||||
|
||||
case 0x2: // 4-bit poly div31
|
||||
level = poly4_counter_[channel]&1;
|
||||
if(divider_counter_[channel]%(30*(divider_[channel]+1)) == 18)
|
||||
{
|
||||
if(divider_value%(30*(divider_[channel]+1)) == 18) {
|
||||
advance_poly4(channel);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x3: // 5/4-bit poly
|
||||
level = output_state_[channel];
|
||||
if(divider_counter_[channel] == divider_[channel]+1)
|
||||
{
|
||||
if(poly5_counter_[channel]&1)
|
||||
{
|
||||
if(divider_value == divider_[channel]+1) {
|
||||
if(poly5_counter_[channel]&1) {
|
||||
output_state_[channel] = poly4_counter_[channel]&1;
|
||||
advance_poly4(channel);
|
||||
}
|
||||
@@ -105,8 +95,7 @@ void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *ta
|
||||
|
||||
case 0x7: case 0x9: // 5-bit poly
|
||||
level = poly5_counter_[channel]&1;
|
||||
if(divider_counter_[channel] == divider_[channel]+1)
|
||||
{
|
||||
if(divider_value == divider_[channel]+1) {
|
||||
divider_counter_[channel] = 0;
|
||||
advance_poly5(channel);
|
||||
}
|
||||
@@ -114,8 +103,7 @@ void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *ta
|
||||
|
||||
case 0xf: // 5-bit poly div6
|
||||
level = poly5_counter_[channel]&1;
|
||||
if(divider_counter_[channel] == (divider_[channel]+1)*3)
|
||||
{
|
||||
if(divider_value == (divider_[channel]+1)*3) {
|
||||
divider_counter_[channel] = 0;
|
||||
advance_poly5(channel);
|
||||
}
|
||||
@@ -123,8 +111,7 @@ void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *ta
|
||||
|
||||
case 0x8: // 9-bit poly
|
||||
level = poly9_counter_[channel]&1;
|
||||
if(divider_counter_[channel] == divider_[channel]+1)
|
||||
{
|
||||
if(divider_value == divider_[channel]+1) {
|
||||
divider_counter_[channel] = 0;
|
||||
advance_poly9(channel);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
// This should be a divisor of 38; audio counters are updated every 38 cycles — though lesser dividers
|
||||
// will give greater resolution to changes in audio state. 1, 2 and 19 are the only divisors of 38.
|
||||
const int CPUTicksPerAudioTick = 2;
|
||||
|
||||
class Speaker: public ::Outputs::Filter<Speaker> {
|
||||
public:
|
||||
Speaker();
|
||||
|
||||
679
Machines/Atari2600/TIA.cpp
Normal file
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) / 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
333
Machines/Atari2600/TIA.hpp
Normal file
@@ -0,0 +1,333 @@
|
||||
//
|
||||
// TIA.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 28/01/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef TIA_hpp
|
||||
#define TIA_hpp
|
||||
|
||||
#include <cstdint>
|
||||
#include "../CRTMachine.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
class TIA {
|
||||
public:
|
||||
TIA();
|
||||
// The supplied hook is for unit testing only; if instantiated with a line_end_function then it will
|
||||
// be called with the latest collision buffer upon the conclusion of each line. What's a collision
|
||||
// buffer? It's an implementation detail. If you're not writing a unit test, leave it alone.
|
||||
TIA(std::function<void(uint8_t *output_buffer)> line_end_function);
|
||||
|
||||
enum class OutputMode {
|
||||
NTSC, PAL
|
||||
};
|
||||
|
||||
/*!
|
||||
Advances the TIA by @c number_of_cycles cycles. Any queued setters take effect in the
|
||||
first cycle performed.
|
||||
*/
|
||||
void run_for_cycles(int number_of_cycles);
|
||||
void set_output_mode(OutputMode output_mode);
|
||||
|
||||
void set_sync(bool sync);
|
||||
void set_blank(bool blank);
|
||||
void reset_horizontal_counter(); // Reset is delayed by four cycles.
|
||||
|
||||
/*!
|
||||
@returns the number of cycles between (current TIA time) + from_offset to the current or
|
||||
next horizontal blanking period. Returns numbers in the range [0, 227].
|
||||
*/
|
||||
int get_cycles_until_horizontal_blank(unsigned int from_offset);
|
||||
|
||||
void set_background_colour(uint8_t colour);
|
||||
|
||||
void set_playfield(uint16_t offset, uint8_t value);
|
||||
void set_playfield_control_and_ball_size(uint8_t value);
|
||||
void set_playfield_ball_colour(uint8_t colour);
|
||||
|
||||
void set_player_number_and_size(int player, uint8_t value);
|
||||
void set_player_graphic(int player, uint8_t value);
|
||||
void set_player_reflected(int player, bool reflected);
|
||||
void set_player_delay(int player, bool delay);
|
||||
void set_player_position(int player);
|
||||
void set_player_motion(int player, uint8_t motion);
|
||||
void set_player_missile_colour(int player, uint8_t colour);
|
||||
|
||||
void set_missile_enable(int missile, bool enabled);
|
||||
void set_missile_position(int missile);
|
||||
void set_missile_position_to_player(int missile, bool lock);
|
||||
void set_missile_motion(int missile, uint8_t motion);
|
||||
|
||||
void set_ball_enable(bool enabled);
|
||||
void set_ball_delay(bool delay);
|
||||
void set_ball_position();
|
||||
void set_ball_motion(uint8_t motion);
|
||||
|
||||
void move();
|
||||
void clear_motion();
|
||||
|
||||
uint8_t get_collision_flags(int offset);
|
||||
void clear_collision_flags();
|
||||
|
||||
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; }
|
||||
|
||||
private:
|
||||
TIA(bool create_crt);
|
||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
||||
std::function<void(uint8_t *output_buffer)> line_end_function_;
|
||||
|
||||
// the master counter; counts from 0 to 228 with all visible pixels being in the final 160
|
||||
int horizontal_counter_;
|
||||
|
||||
// contains flags to indicate whether sync or blank are currently active
|
||||
int output_mode_;
|
||||
|
||||
// keeps track of the target pixel buffer for this line and when it was acquired, and a corresponding collision buffer
|
||||
alignas(alignof(uint32_t)) uint8_t collision_buffer_[160];
|
||||
enum class CollisionType : uint8_t {
|
||||
Playfield = (1 << 0),
|
||||
Ball = (1 << 1),
|
||||
Player0 = (1 << 2),
|
||||
Player1 = (1 << 3),
|
||||
Missile0 = (1 << 4),
|
||||
Missile1 = (1 << 5)
|
||||
};
|
||||
|
||||
int collision_flags_;
|
||||
int collision_flags_by_buffer_vaules_[64];
|
||||
|
||||
// colour mapping tables
|
||||
enum class ColourMode {
|
||||
Standard = 0,
|
||||
ScoreLeft,
|
||||
ScoreRight,
|
||||
OnTop
|
||||
};
|
||||
uint8_t colour_mask_by_mode_collision_flags_[4][64]; // maps from [ColourMode][CollisionMark] to colour_pallete_ entry
|
||||
|
||||
enum class ColourIndex {
|
||||
Background = 0,
|
||||
PlayfieldBall,
|
||||
PlayerMissile0,
|
||||
PlayerMissile1
|
||||
};
|
||||
uint8_t colour_palette_[4];
|
||||
|
||||
// playfield state
|
||||
int background_half_mask_;
|
||||
enum class PlayfieldPriority {
|
||||
Standard,
|
||||
Score,
|
||||
OnTop
|
||||
} playfield_priority_;
|
||||
uint32_t background_[2]; // contains two 20-bit bitfields representing the background state;
|
||||
// at index 0 is the left-hand side of the playfield with bit 0 being
|
||||
// the first bit to display, bit 1 the second, etc. Index 1 contains
|
||||
// a mirror image of index 0. If the playfield is being displayed in
|
||||
// mirroring mode, background_[0] will be output on the left and
|
||||
// background_[1] on the right; otherwise background_[0] will be
|
||||
// output twice.
|
||||
|
||||
// objects
|
||||
template<class T> struct Object {
|
||||
// the two programmer-set values
|
||||
int position;
|
||||
int motion;
|
||||
|
||||
// motion_step_ is the current motion counter value; motion_time_ is the next time it will fire
|
||||
int motion_step;
|
||||
int motion_time;
|
||||
|
||||
// indicates whether this object is currently undergoing motion
|
||||
bool is_moving;
|
||||
|
||||
Object() : position(0), motion(0), motion_step(0), motion_time(0), is_moving(false) {};
|
||||
};
|
||||
|
||||
// player state
|
||||
struct Player: public Object<Player> {
|
||||
Player() :
|
||||
adder(4),
|
||||
copy_flags(0),
|
||||
graphic{0, 0},
|
||||
reverse_mask(false),
|
||||
graphic_index(0),
|
||||
pixel_position(32),
|
||||
pixel_counter(0),
|
||||
latched_pixel4_time(-1),
|
||||
copy_index_(0),
|
||||
queue_read_pointer_(0),
|
||||
queue_write_pointer_(0) {}
|
||||
|
||||
int adder;
|
||||
int copy_flags; // a bit field, corresponding to the first few values of NUSIZ
|
||||
uint8_t graphic[2]; // the player graphic; 1 = new, 0 = current
|
||||
int reverse_mask; // 7 for a reflected player, 0 for normal
|
||||
int graphic_index;
|
||||
|
||||
int pixel_position, pixel_counter;
|
||||
int latched_pixel4_time;
|
||||
const bool enqueues = true;
|
||||
|
||||
inline void skip_pixels(const int count, int from_horizontal_counter) {
|
||||
int old_pixel_counter = pixel_counter;
|
||||
pixel_position = std::min(32, pixel_position + count * adder);
|
||||
pixel_counter += count;
|
||||
if(!copy_index_ && old_pixel_counter < 4 && pixel_counter >= 4) {
|
||||
latched_pixel4_time = from_horizontal_counter + 4 - old_pixel_counter;
|
||||
}
|
||||
}
|
||||
|
||||
inline void reset_pixels(int copy) {
|
||||
pixel_position = pixel_counter = 0;
|
||||
copy_index_ = copy;
|
||||
}
|
||||
|
||||
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
|
||||
output_pixels(target, count, collision_identity, pixel_position, adder, reverse_mask);
|
||||
skip_pixels(count, from_horizontal_counter);
|
||||
}
|
||||
|
||||
void dequeue_pixels(uint8_t *const target, const uint8_t collision_identity, const int time_now) {
|
||||
while(queue_read_pointer_ != queue_write_pointer_) {
|
||||
uint8_t *const start_ptr = &target[queue_[queue_read_pointer_].start];
|
||||
if(queue_[queue_read_pointer_].end > time_now) {
|
||||
const int length = time_now - queue_[queue_read_pointer_].start;
|
||||
output_pixels(start_ptr, length, collision_identity, queue_[queue_read_pointer_].pixel_position, queue_[queue_read_pointer_].adder, queue_[queue_read_pointer_].reverse_mask);
|
||||
queue_[queue_read_pointer_].pixel_position += length * queue_[queue_read_pointer_].adder;
|
||||
queue_[queue_read_pointer_].start = time_now;
|
||||
return;
|
||||
} else {
|
||||
output_pixels(start_ptr, queue_[queue_read_pointer_].end - queue_[queue_read_pointer_].start, collision_identity, queue_[queue_read_pointer_].pixel_position, queue_[queue_read_pointer_].adder, queue_[queue_read_pointer_].reverse_mask);
|
||||
}
|
||||
queue_read_pointer_ = (queue_read_pointer_ + 1)&3;
|
||||
}
|
||||
}
|
||||
|
||||
void enqueue_pixels(const int start, const int end, int from_horizontal_counter) {
|
||||
queue_[queue_write_pointer_].start = start;
|
||||
queue_[queue_write_pointer_].end = end;
|
||||
queue_[queue_write_pointer_].pixel_position = pixel_position;
|
||||
queue_[queue_write_pointer_].adder = adder;
|
||||
queue_[queue_write_pointer_].reverse_mask = reverse_mask;
|
||||
queue_write_pointer_ = (queue_write_pointer_ + 1)&3;
|
||||
skip_pixels(end - start, from_horizontal_counter);
|
||||
}
|
||||
|
||||
private:
|
||||
int copy_index_;
|
||||
struct QueuedPixels {
|
||||
int start, end;
|
||||
int pixel_position;
|
||||
int adder;
|
||||
int reverse_mask;
|
||||
QueuedPixels() : start(0), end(0), pixel_position(0), adder(0), reverse_mask(false) {}
|
||||
} queue_[4];
|
||||
int queue_read_pointer_, queue_write_pointer_;
|
||||
|
||||
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int pixel_position, int adder, int reverse_mask) {
|
||||
if(pixel_position == 32 || !graphic[graphic_index]) return;
|
||||
int output_cursor = 0;
|
||||
while(pixel_position < 32 && output_cursor < count) {
|
||||
int shift = (pixel_position >> 2) ^ reverse_mask;
|
||||
target[output_cursor] |= ((graphic[graphic_index] >> shift)&1) * collision_identity;
|
||||
output_cursor++;
|
||||
pixel_position += adder;
|
||||
}
|
||||
}
|
||||
|
||||
} player_[2];
|
||||
|
||||
// common actor for things that appear as a horizontal run of pixels
|
||||
struct HorizontalRun: public Object<HorizontalRun> {
|
||||
int pixel_position;
|
||||
int size;
|
||||
const bool enqueues = false;
|
||||
|
||||
inline void skip_pixels(const int count, int from_horizontal_counter) {
|
||||
pixel_position = std::max(0, pixel_position - count);
|
||||
}
|
||||
|
||||
inline void reset_pixels(int copy) {
|
||||
pixel_position = size;
|
||||
}
|
||||
|
||||
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
|
||||
int output_cursor = 0;
|
||||
while(pixel_position && output_cursor < count)
|
||||
{
|
||||
target[output_cursor] |= collision_identity;
|
||||
output_cursor++;
|
||||
pixel_position--;
|
||||
}
|
||||
}
|
||||
|
||||
void dequeue_pixels(uint8_t *const target, const uint8_t collision_identity, const int time_now) {}
|
||||
void enqueue_pixels(const int start, const int end, int from_horizontal_counter) {}
|
||||
|
||||
HorizontalRun() : pixel_position(0), size(1) {}
|
||||
};
|
||||
|
||||
|
||||
// missile state
|
||||
struct Missile: public HorizontalRun {
|
||||
bool enabled;
|
||||
bool locked_to_player;
|
||||
int copy_flags;
|
||||
|
||||
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
|
||||
if(!pixel_position) return;
|
||||
if(enabled && !locked_to_player) {
|
||||
HorizontalRun::output_pixels(target, count, collision_identity, from_horizontal_counter);
|
||||
} else {
|
||||
skip_pixels(count, from_horizontal_counter);
|
||||
}
|
||||
}
|
||||
|
||||
Missile() : enabled(false), copy_flags(0) {}
|
||||
} missile_[2];
|
||||
|
||||
// ball state
|
||||
struct Ball: public HorizontalRun {
|
||||
bool enabled[2];
|
||||
int enabled_index;
|
||||
const int copy_flags = 0;
|
||||
|
||||
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
|
||||
if(!pixel_position) return;
|
||||
if(enabled[enabled_index]) {
|
||||
HorizontalRun::output_pixels(target, count, collision_identity, from_horizontal_counter);
|
||||
} else {
|
||||
skip_pixels(count, from_horizontal_counter);
|
||||
}
|
||||
}
|
||||
|
||||
Ball() : enabled{false, false}, enabled_index(0) {}
|
||||
} ball_;
|
||||
|
||||
// motion
|
||||
bool horizontal_blank_extend_;
|
||||
template<class T> void perform_border_motion(T &object, int start, int end);
|
||||
template<class T> void perform_motion_step(T &object);
|
||||
|
||||
// drawing methods and state
|
||||
void draw_missile(Missile &, Player &, const uint8_t collision_identity, int start, int end);
|
||||
template<class T> void draw_object(T &, const uint8_t collision_identity, int start, int end);
|
||||
template<class T> void draw_object_visible(T &, const uint8_t collision_identity, int start, int end, int time_now);
|
||||
inline void draw_playfield(int start, int end);
|
||||
|
||||
inline void output_for_cycles(int number_of_cycles);
|
||||
inline void output_line();
|
||||
|
||||
int pixels_start_location_;
|
||||
uint8_t *pixel_target_;
|
||||
inline void output_pixels(int start, int end);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* TIA_hpp */
|
||||
@@ -13,11 +13,10 @@
|
||||
using namespace Commodore::C1540;
|
||||
|
||||
Machine::Machine() :
|
||||
shift_register_(0),
|
||||
Storage::Disk::Controller(1000000, 4, 300),
|
||||
serial_port_(new SerialPort),
|
||||
serial_port_VIA_(new SerialPortVIA)
|
||||
{
|
||||
shift_register_(0),
|
||||
Storage::Disk::Controller(1000000, 4, 300),
|
||||
serial_port_(new SerialPort),
|
||||
serial_port_VIA_(new SerialPortVIA) {
|
||||
// attach the serial port to its VIA and vice versa
|
||||
serial_port_->set_serial_port_via(serial_port_VIA_);
|
||||
serial_port_VIA_->set_serial_port(serial_port_);
|
||||
@@ -31,13 +30,11 @@ Machine::Machine() :
|
||||
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(3));
|
||||
}
|
||||
|
||||
void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus)
|
||||
{
|
||||
void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) {
|
||||
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus);
|
||||
}
|
||||
|
||||
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value)
|
||||
{
|
||||
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
/*
|
||||
Memory map (given that I'm unsure yet on any potential mirroring):
|
||||
|
||||
@@ -46,27 +43,20 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
0x1c00–0x1c0f the drive VIA
|
||||
0xc000–0xffff ROM
|
||||
*/
|
||||
if(address < 0x800)
|
||||
{
|
||||
if(address < 0x800) {
|
||||
if(isReadOperation(operation))
|
||||
*value = ram_[address];
|
||||
else
|
||||
ram_[address] = *value;
|
||||
}
|
||||
else if(address >= 0xc000)
|
||||
{
|
||||
} else if(address >= 0xc000) {
|
||||
if(isReadOperation(operation))
|
||||
*value = rom_[address & 0x3fff];
|
||||
}
|
||||
else if(address >= 0x1800 && address <= 0x180f)
|
||||
{
|
||||
} else if(address >= 0x1800 && address <= 0x180f) {
|
||||
if(isReadOperation(operation))
|
||||
*value = serial_port_VIA_->get_register(address);
|
||||
else
|
||||
serial_port_VIA_->set_register(address, *value);
|
||||
}
|
||||
else if(address >= 0x1c00 && address <= 0x1c0f)
|
||||
{
|
||||
} else if(address >= 0x1c00 && address <= 0x1c0f) {
|
||||
if(isReadOperation(operation))
|
||||
*value = drive_VIA_.get_register(address);
|
||||
else
|
||||
@@ -79,20 +69,17 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
return 1;
|
||||
}
|
||||
|
||||
void Machine::set_rom(const uint8_t *rom)
|
||||
{
|
||||
void Machine::set_rom(const uint8_t *rom) {
|
||||
memcpy(rom_, rom, sizeof(rom_));
|
||||
}
|
||||
|
||||
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk)
|
||||
{
|
||||
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) {
|
||||
std::shared_ptr<Storage::Disk::Drive> drive(new Storage::Disk::Drive);
|
||||
drive->set_disk(disk);
|
||||
set_drive(drive);
|
||||
}
|
||||
|
||||
void Machine::run_for_cycles(int number_of_cycles)
|
||||
{
|
||||
void Machine::run_for_cycles(int number_of_cycles) {
|
||||
CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles);
|
||||
set_motor_on(drive_VIA_.get_motor_enabled());
|
||||
if(drive_VIA_.get_motor_enabled()) // TODO: motor speed up/down
|
||||
@@ -101,38 +88,30 @@ void Machine::run_for_cycles(int number_of_cycles)
|
||||
|
||||
#pragma mark - 6522 delegate
|
||||
|
||||
void Machine::mos6522_did_change_interrupt_status(void *mos6522)
|
||||
{
|
||||
void Machine::mos6522_did_change_interrupt_status(void *mos6522) {
|
||||
// both VIAs are connected to the IRQ line
|
||||
set_irq_line(serial_port_VIA_->get_interrupt_line() || drive_VIA_.get_interrupt_line());
|
||||
}
|
||||
|
||||
#pragma mark - Disk drive
|
||||
|
||||
void Machine::process_input_bit(int value, unsigned int cycles_since_index_hole)
|
||||
{
|
||||
void Machine::process_input_bit(int value, unsigned int cycles_since_index_hole) {
|
||||
shift_register_ = (shift_register_ << 1) | value;
|
||||
if((shift_register_ & 0x3ff) == 0x3ff)
|
||||
{
|
||||
if((shift_register_ & 0x3ff) == 0x3ff) {
|
||||
drive_VIA_.set_sync_detected(true);
|
||||
bit_window_offset_ = -1; // i.e. this bit isn't the first within a data window, but the next might be
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
drive_VIA_.set_sync_detected(false);
|
||||
}
|
||||
bit_window_offset_++;
|
||||
if(bit_window_offset_ == 8)
|
||||
{
|
||||
if(bit_window_offset_ == 8) {
|
||||
drive_VIA_.set_data_input((uint8_t)shift_register_);
|
||||
bit_window_offset_ = 0;
|
||||
if(drive_VIA_.get_should_set_overflow())
|
||||
{
|
||||
if(drive_VIA_.get_should_set_overflow()) {
|
||||
set_overflow_line(true);
|
||||
}
|
||||
}
|
||||
else
|
||||
set_overflow_line(false);
|
||||
else set_overflow_line(false);
|
||||
}
|
||||
|
||||
// the 1540 does not recognise index holes
|
||||
@@ -140,32 +119,26 @@ void Machine::process_index_hole() {}
|
||||
|
||||
#pragma mak - Drive VIA delegate
|
||||
|
||||
void Machine::drive_via_did_step_head(void *driveVIA, int direction)
|
||||
{
|
||||
void Machine::drive_via_did_step_head(void *driveVIA, int direction) {
|
||||
step(direction);
|
||||
}
|
||||
|
||||
void Machine::drive_via_did_set_data_density(void *driveVIA, int density)
|
||||
{
|
||||
void Machine::drive_via_did_set_data_density(void *driveVIA, int density) {
|
||||
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone((unsigned int)density));
|
||||
}
|
||||
|
||||
#pragma mark - SerialPortVIA
|
||||
|
||||
SerialPortVIA::SerialPortVIA() :
|
||||
port_b_(0x00), attention_acknowledge_level_(false), attention_level_input_(true), data_level_output_(false)
|
||||
{}
|
||||
port_b_(0x00), attention_acknowledge_level_(false), attention_level_input_(true), data_level_output_(false) {}
|
||||
|
||||
uint8_t SerialPortVIA::get_port_input(Port port)
|
||||
{
|
||||
uint8_t SerialPortVIA::get_port_input(Port port) {
|
||||
if(port) return port_b_;
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask)
|
||||
{
|
||||
if(port)
|
||||
{
|
||||
void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) {
|
||||
if(port) {
|
||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
||||
if(serialPort) {
|
||||
attention_acknowledge_level_ = !(value&0x10);
|
||||
@@ -177,10 +150,8 @@ void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask)
|
||||
}
|
||||
}
|
||||
|
||||
void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value)
|
||||
{
|
||||
switch(line)
|
||||
{
|
||||
void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) {
|
||||
switch(line) {
|
||||
default: break;
|
||||
case ::Commodore::Serial::Line::Data: port_b_ = (port_b_ & ~0x01) | (value ? 0x00 : 0x01); break;
|
||||
case ::Commodore::Serial::Line::Clock: port_b_ = (port_b_ & ~0x04) | (value ? 0x00 : 0x04); break;
|
||||
@@ -193,16 +164,13 @@ void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool v
|
||||
}
|
||||
}
|
||||
|
||||
void SerialPortVIA::set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &serialPort)
|
||||
{
|
||||
void SerialPortVIA::set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &serialPort) {
|
||||
serial_port_ = serialPort;
|
||||
}
|
||||
|
||||
void SerialPortVIA::update_data_line()
|
||||
{
|
||||
void SerialPortVIA::update_data_line() {
|
||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
||||
if(serialPort)
|
||||
{
|
||||
if(serialPort) {
|
||||
// "ATN (Attention) is an input on pin 3 of P2 and P3 that is sensed at PB7 and CA1 of UC3 after being inverted by UA1"
|
||||
serialPort->set_output(::Commodore::Serial::Line::Data,
|
||||
(::Commodore::Serial::LineLevel)(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_)));
|
||||
@@ -211,8 +179,7 @@ void SerialPortVIA::update_data_line()
|
||||
|
||||
#pragma mark - DriveVIA
|
||||
|
||||
void DriveVIA::set_delegate(Delegate *delegate)
|
||||
{
|
||||
void DriveVIA::set_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
@@ -246,22 +213,19 @@ void DriveVIA::set_control_line_output(Port port, Line line, bool value) {
|
||||
}
|
||||
|
||||
void DriveVIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask) {
|
||||
if(port)
|
||||
{
|
||||
if(port) {
|
||||
// record drive motor state
|
||||
drive_motor_ = !!(value&4);
|
||||
|
||||
// check for a head step
|
||||
int step_difference = ((value&3) - (previous_port_b_output_&3))&3;
|
||||
if(step_difference)
|
||||
{
|
||||
if(step_difference) {
|
||||
if(delegate_) delegate_->drive_via_did_step_head(this, (step_difference == 1) ? 1 : -1);
|
||||
}
|
||||
|
||||
// check for a change in density
|
||||
int density_difference = (previous_port_b_output_^value) & (3 << 5);
|
||||
if(density_difference && delegate_)
|
||||
{
|
||||
if(density_difference && delegate_) {
|
||||
delegate_->drive_via_did_set_data_density(this, (value >> 5)&3);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,10 +10,8 @@
|
||||
|
||||
using namespace Commodore::Serial;
|
||||
|
||||
const char *::Commodore::Serial::StringForLine(Line line)
|
||||
{
|
||||
switch(line)
|
||||
{
|
||||
const char *::Commodore::Serial::StringForLine(Line line) {
|
||||
switch(line) {
|
||||
case ServiceRequest: return "Service request";
|
||||
case Attention: return "Attention";
|
||||
case Clock: return "Clock";
|
||||
@@ -22,17 +20,14 @@ const char *::Commodore::Serial::StringForLine(Line line)
|
||||
}
|
||||
}
|
||||
|
||||
void ::Commodore::Serial::AttachPortAndBus(std::shared_ptr<Port> port, std::shared_ptr<Bus> bus)
|
||||
{
|
||||
void ::Commodore::Serial::AttachPortAndBus(std::shared_ptr<Port> port, std::shared_ptr<Bus> bus) {
|
||||
port->set_serial_bus(bus);
|
||||
bus->add_port(port);
|
||||
}
|
||||
|
||||
void Bus::add_port(std::shared_ptr<Port> port)
|
||||
{
|
||||
void Bus::add_port(std::shared_ptr<Port> port) {
|
||||
ports_.push_back(port);
|
||||
for(int line = (int)ServiceRequest; line <= (int)Reset; line++)
|
||||
{
|
||||
for(int line = (int)ServiceRequest; line <= (int)Reset; line++) {
|
||||
// the addition of a new device may change the line output...
|
||||
set_line_output_did_change((Line)line);
|
||||
|
||||
@@ -41,29 +36,23 @@ void Bus::add_port(std::shared_ptr<Port> port)
|
||||
}
|
||||
}
|
||||
|
||||
void Bus::set_line_output_did_change(Line line)
|
||||
{
|
||||
void Bus::set_line_output_did_change(Line line) {
|
||||
// i.e. I believe these lines to be open collector
|
||||
LineLevel new_line_level = High;
|
||||
for(std::weak_ptr<Port> port : ports_)
|
||||
{
|
||||
for(std::weak_ptr<Port> port : ports_) {
|
||||
std::shared_ptr<Port> locked_port = port.lock();
|
||||
if(locked_port)
|
||||
{
|
||||
if(locked_port) {
|
||||
new_line_level = (LineLevel)((bool)new_line_level & (bool)locked_port->get_output(line));
|
||||
}
|
||||
}
|
||||
|
||||
// post an update only if one occurred
|
||||
if(new_line_level != line_levels_[line])
|
||||
{
|
||||
if(new_line_level != line_levels_[line]) {
|
||||
line_levels_[line] = new_line_level;
|
||||
|
||||
for(std::weak_ptr<Port> port : ports_)
|
||||
{
|
||||
for(std::weak_ptr<Port> port : ports_) {
|
||||
std::shared_ptr<Port> locked_port = port.lock();
|
||||
if(locked_port)
|
||||
{
|
||||
if(locked_port) {
|
||||
locked_port->set_input(line, new_line_level);
|
||||
}
|
||||
}
|
||||
@@ -72,19 +61,14 @@ void Bus::set_line_output_did_change(Line line)
|
||||
|
||||
#pragma mark - The debug port
|
||||
|
||||
void DebugPort::set_input(Line line, LineLevel value)
|
||||
{
|
||||
void DebugPort::set_input(Line line, LineLevel value) {
|
||||
input_levels_[line] = value;
|
||||
|
||||
printf("[Bus] %s is %s\n", StringForLine(line), value ? "high" : "low");
|
||||
if(!incoming_count_)
|
||||
{
|
||||
if(!incoming_count_) {
|
||||
incoming_count_ = (!input_levels_[Line::Clock] && !input_levels_[Line::Data]) ? 8 : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(line == Line::Clock && value)
|
||||
{
|
||||
} else {
|
||||
if(line == Line::Clock && value) {
|
||||
incoming_byte_ = (incoming_byte_ >> 1) | (input_levels_[Line::Data] ? 0x80 : 0x00);
|
||||
}
|
||||
incoming_count_--;
|
||||
|
||||
@@ -78,8 +78,7 @@ namespace Serial {
|
||||
Sets the current level of an output line on this serial port.
|
||||
*/
|
||||
void set_output(Line line, LineLevel level) {
|
||||
if(line_levels_[line] != level)
|
||||
{
|
||||
if(line_levels_[line] != level) {
|
||||
line_levels_[line] = level;
|
||||
std::shared_ptr<Bus> bus = serial_bus_.lock();
|
||||
if(bus) bus->set_line_output_did_change(line);
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
|
||||
#include "Vic20.hpp"
|
||||
|
||||
uint16_t *Commodore::Vic20::Machine::sequence_for_character(Utility::Typer *typer, char character)
|
||||
{
|
||||
uint16_t *Commodore::Vic20::Machine::sequence_for_character(Utility::Typer *typer, char character) {
|
||||
#define KEYS(...) {__VA_ARGS__, TerminateSequence}
|
||||
#define SHIFT(...) {KeyLShift, __VA_ARGS__, TerminateSequence}
|
||||
#define X {NotMapped}
|
||||
|
||||
@@ -10,20 +10,21 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include "../../../Storage/Tape/Formats/TapePRG.hpp"
|
||||
#include "../../../Storage/Tape/Parsers/Commodore.hpp"
|
||||
#include "../../../StaticAnalyser/StaticAnalyser.hpp"
|
||||
|
||||
using namespace Commodore::Vic20;
|
||||
|
||||
Machine::Machine() :
|
||||
rom_(nullptr),
|
||||
is_running_at_zero_cost_(false),
|
||||
tape_(1022727)
|
||||
{
|
||||
// create 6522s, serial port and bus
|
||||
user_port_via_.reset(new UserPortVIA);
|
||||
keyboard_via_.reset(new KeyboardVIA);
|
||||
serial_port_.reset(new SerialPort);
|
||||
serial_bus_.reset(new ::Commodore::Serial::Bus);
|
||||
rom_(nullptr),
|
||||
is_running_at_zero_cost_(false),
|
||||
tape_(new Storage::Tape::BinaryTapePlayer(1022727)),
|
||||
user_port_via_(new UserPortVIA),
|
||||
keyboard_via_(new KeyboardVIA),
|
||||
serial_port_(new SerialPort),
|
||||
serial_bus_(new ::Commodore::Serial::Bus) {
|
||||
// communicate the tape to the user-port VIA
|
||||
user_port_via_->set_tape(tape_);
|
||||
|
||||
// wire up the serial bus and serial port
|
||||
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus_);
|
||||
@@ -36,7 +37,7 @@ Machine::Machine() :
|
||||
// wire up the 6522s, tape and machine
|
||||
user_port_via_->set_interrupt_delegate(this);
|
||||
keyboard_via_->set_interrupt_delegate(this);
|
||||
tape_.set_delegate(this);
|
||||
tape_->set_delegate(this);
|
||||
|
||||
// establish the memory maps
|
||||
set_memory_size(MemorySize::Default);
|
||||
@@ -48,13 +49,11 @@ Machine::Machine() :
|
||||
// serial_bus_->add_port(_debugPort);
|
||||
}
|
||||
|
||||
void Machine::set_memory_size(MemorySize size)
|
||||
{
|
||||
void Machine::set_memory_size(MemorySize size) {
|
||||
memset(processor_read_memory_map_, 0, sizeof(processor_read_memory_map_));
|
||||
memset(processor_write_memory_map_, 0, sizeof(processor_write_memory_map_));
|
||||
|
||||
switch(size)
|
||||
{
|
||||
switch(size) {
|
||||
default: break;
|
||||
case ThreeKB:
|
||||
write_to_map(processor_read_memory_map_, expansion_ram_, 0x0000, 0x1000);
|
||||
@@ -79,52 +78,33 @@ void Machine::set_memory_size(MemorySize size)
|
||||
write_to_map(processor_write_memory_map_, colour_memory_, 0x9400, sizeof(colour_memory_));
|
||||
|
||||
// install the inserted ROM if there is one
|
||||
if(rom_)
|
||||
{
|
||||
if(rom_) {
|
||||
write_to_map(processor_read_memory_map_, rom_, rom_address_, rom_length_);
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length)
|
||||
{
|
||||
void Machine::write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length) {
|
||||
address >>= 10;
|
||||
length >>= 10;
|
||||
while(length--)
|
||||
{
|
||||
while(length--) {
|
||||
map[address] = area;
|
||||
area += 0x400;
|
||||
address++;
|
||||
}
|
||||
}
|
||||
|
||||
Machine::~Machine()
|
||||
{
|
||||
Machine::~Machine() {
|
||||
delete[] rom_;
|
||||
}
|
||||
|
||||
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value)
|
||||
{
|
||||
// static int logCount = 0;
|
||||
// if(operation == CPU6502::BusOperation::ReadOpcode && address == 0xf957) logCount = 500;
|
||||
// if(operation == CPU6502::BusOperation::ReadOpcode && logCount) {
|
||||
// logCount--;
|
||||
// printf("%04x\n", address);
|
||||
// }
|
||||
|
||||
// if(operation == CPU6502::BusOperation::Write && (address >= 0x033C && address < 0x033C + 192))
|
||||
// {
|
||||
// printf("\n[%04x] <- %02x\n", address, *value);
|
||||
// }
|
||||
|
||||
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
// run the phase-1 part of this cycle, in which the VIC accesses memory
|
||||
if(!is_running_at_zero_cost_) mos6560_->run_for_cycles(1);
|
||||
|
||||
// run the phase-2 part of the cycle, which is whatever the 6502 said it should be
|
||||
if(isReadOperation(operation))
|
||||
{
|
||||
if(isReadOperation(operation)) {
|
||||
uint8_t result = processor_read_memory_map_[address >> 10] ? processor_read_memory_map_[address >> 10][address & 0x3ff] : 0xff;
|
||||
if((address&0xfc00) == 0x9000)
|
||||
{
|
||||
if((address&0xfc00) == 0x9000) {
|
||||
if((address&0xff00) == 0x9000) result &= mos6560_->get_register(address);
|
||||
if((address&0xfc10) == 0x9010) result &= user_port_via_->get_register(address);
|
||||
if((address&0xfc20) == 0x9020) result &= keyboard_via_->get_register(address);
|
||||
@@ -135,22 +115,64 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
// PC hits the start of the loop that just waits for an interesting tape interrupt to have
|
||||
// occurred then skip both 6522s and the tape ahead to the next interrupt without any further
|
||||
// CPU or 6560 costs.
|
||||
if(use_fast_tape_hack_ && tape_.has_tape() && address == 0xf92f && operation == CPU6502::BusOperation::ReadOpcode)
|
||||
{
|
||||
while(!user_port_via_->get_interrupt_line() && !keyboard_via_->get_interrupt_line() && !tape_.get_tape()->is_at_end())
|
||||
{
|
||||
user_port_via_->run_for_cycles(1);
|
||||
keyboard_via_->run_for_cycles(1);
|
||||
tape_.run_for_cycles(1);
|
||||
if(use_fast_tape_hack_ && tape_->has_tape() && operation == CPU6502::BusOperation::ReadOpcode) {
|
||||
if(address == 0xf7b2) {
|
||||
// Address 0xf7b2 contains a JSR to 0xf8c0 that will fill the tape buffer with the next header.
|
||||
// So cancel that via a double NOP and fill in the next header programmatically.
|
||||
Storage::Tape::Commodore::Parser parser;
|
||||
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape_->get_tape());
|
||||
|
||||
// serialise to wherever b2:b3 points
|
||||
uint16_t tape_buffer_pointer = (uint16_t)user_basic_memory_[0xb2] | (uint16_t)(user_basic_memory_[0xb3] << 8);
|
||||
if(header) {
|
||||
header->serialise(&user_basic_memory_[tape_buffer_pointer], 0x8000 - tape_buffer_pointer);
|
||||
} else {
|
||||
// no header found, so store end-of-tape
|
||||
user_basic_memory_[tape_buffer_pointer] = 0x05; // i.e. end of tape
|
||||
}
|
||||
|
||||
// clear status and the verify flag
|
||||
user_basic_memory_[0x90] = 0;
|
||||
user_basic_memory_[0x93] = 0;
|
||||
|
||||
*value = 0x0c; // i.e. NOP abs
|
||||
} else if(address == 0xf90b) {
|
||||
uint8_t x = (uint8_t)get_value_of_register(CPU6502::Register::X);
|
||||
if(x == 0xe) {
|
||||
Storage::Tape::Commodore::Parser parser;
|
||||
std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape_->get_tape());
|
||||
uint16_t start_address, end_address;
|
||||
start_address = (uint16_t)(user_basic_memory_[0xc1] | (user_basic_memory_[0xc2] << 8));
|
||||
end_address = (uint16_t)(user_basic_memory_[0xae] | (user_basic_memory_[0xaf] << 8));
|
||||
|
||||
// perform a via-processor_write_memory_map_ memcpy
|
||||
uint8_t *data_ptr = data->data.data();
|
||||
size_t data_left = data->data.size();
|
||||
while(data_left && start_address != end_address) {
|
||||
uint8_t *page = processor_write_memory_map_[start_address >> 10];
|
||||
if(page) page[start_address & 0x3ff] = *data_ptr;
|
||||
data_ptr++;
|
||||
start_address++;
|
||||
data_left--;
|
||||
}
|
||||
|
||||
// set tape status, carry and flag
|
||||
user_basic_memory_[0x90] |= 0x40;
|
||||
uint8_t flags = (uint8_t)get_value_of_register(CPU6502::Register::Flags);
|
||||
flags &= ~(uint8_t)(CPU6502::Flag::Carry | CPU6502::Flag::Interrupt);
|
||||
set_value_of_register(CPU6502::Register::Flags, flags);
|
||||
|
||||
// to ensure that execution proceeds to 0xfccf, pretend a NOP was here and
|
||||
// ensure that the PC leaps to 0xfccf
|
||||
set_value_of_register(CPU6502::Register::ProgramCounter, 0xfccf);
|
||||
*value = 0xea; // i.e. NOP implied
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
uint8_t *ram = processor_write_memory_map_[address >> 10];
|
||||
if(ram) ram[address & 0x3ff] = *value;
|
||||
if((address&0xfc00) == 0x9000)
|
||||
{
|
||||
if((address&0xfc00) == 0x9000) {
|
||||
if((address&0xff00) == 0x9000) mos6560_->set_register(address, *value);
|
||||
if((address&0xfc10) == 0x9010) user_port_via_->set_register(address, *value);
|
||||
if((address&0xfc20) == 0x9020) keyboard_via_->set_register(address, *value);
|
||||
@@ -159,75 +181,40 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
|
||||
user_port_via_->run_for_cycles(1);
|
||||
keyboard_via_->run_for_cycles(1);
|
||||
if(typer_ && operation == CPU6502::BusOperation::ReadOpcode && address == 0xEB1E)
|
||||
{
|
||||
if(!typer_->type_next_character())
|
||||
{
|
||||
if(typer_ && operation == CPU6502::BusOperation::ReadOpcode && address == 0xEB1E) {
|
||||
if(!typer_->type_next_character()) {
|
||||
clear_all_keys();
|
||||
typer_.reset();
|
||||
}
|
||||
}
|
||||
tape_.run_for_cycles(1);
|
||||
tape_->run_for_cycles(1);
|
||||
if(c1540_) c1540_->run_for_cycles(1);
|
||||
|
||||
// If using fast tape then:
|
||||
// if the PC hits 0xf98e, the ROM's tape loading routine, then begin zero cost processing;
|
||||
// if the PC heads into RAM
|
||||
//
|
||||
// Where 'zero cost processing' is taken to be taking the 6560 off the bus (because I know it's
|
||||
// expensive, and not relevant) then running the tape, the CPU and both 6522s as usual but not
|
||||
// counting cycles towards the processing budget. So the limit is the host machine.
|
||||
//
|
||||
// Note the additional test above for PC hitting 0xf92f, which is a loop in the ROM that waits
|
||||
// for an interesting interrupt. Up there the fast tape hack goes even further in also cutting
|
||||
// the CPU out of the action.
|
||||
if(use_fast_tape_hack_ && tape_.has_tape())
|
||||
{
|
||||
if(address == 0xf98e && operation == CPU6502::BusOperation::ReadOpcode)
|
||||
{
|
||||
is_running_at_zero_cost_ = true;
|
||||
set_clock_is_unlimited(true);
|
||||
}
|
||||
if(
|
||||
(address < 0xe000 && operation == CPU6502::BusOperation::ReadOpcode) ||
|
||||
tape_.get_tape()->is_at_end()
|
||||
)
|
||||
{
|
||||
is_running_at_zero_cost_ = false;
|
||||
set_clock_is_unlimited(false);
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
#pragma mark - 6522 delegate
|
||||
|
||||
void Machine::mos6522_did_change_interrupt_status(void *mos6522)
|
||||
{
|
||||
void Machine::mos6522_did_change_interrupt_status(void *mos6522) {
|
||||
set_nmi_line(user_port_via_->get_interrupt_line());
|
||||
set_irq_line(keyboard_via_->get_interrupt_line());
|
||||
}
|
||||
|
||||
#pragma mark - Setup
|
||||
|
||||
void Machine::set_region(Commodore::Vic20::Region region)
|
||||
{
|
||||
void Machine::set_region(Commodore::Vic20::Region region) {
|
||||
region_ = region;
|
||||
switch(region)
|
||||
{
|
||||
switch(region) {
|
||||
case PAL:
|
||||
set_clock_rate(1108404);
|
||||
if(mos6560_)
|
||||
{
|
||||
if(mos6560_) {
|
||||
mos6560_->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::PAL);
|
||||
mos6560_->set_clock_rate(1108404);
|
||||
}
|
||||
break;
|
||||
case NTSC:
|
||||
set_clock_rate(1022727);
|
||||
if(mos6560_)
|
||||
{
|
||||
if(mos6560_) {
|
||||
mos6560_->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::NTSC);
|
||||
mos6560_->set_clock_rate(1022727);
|
||||
}
|
||||
@@ -235,8 +222,7 @@ void Machine::set_region(Commodore::Vic20::Region region)
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::setup_output(float aspect_ratio)
|
||||
{
|
||||
void Machine::setup_output(float aspect_ratio) {
|
||||
mos6560_.reset(new Vic6560());
|
||||
mos6560_->get_speaker()->set_high_frequency_cut_off(1600); // There is a 1.6Khz low-pass filter in the Vic-20.
|
||||
set_region(region_);
|
||||
@@ -248,17 +234,14 @@ void Machine::setup_output(float aspect_ratio)
|
||||
mos6560_->colour_memory = colour_memory_;
|
||||
}
|
||||
|
||||
void Machine::close_output()
|
||||
{
|
||||
void Machine::close_output() {
|
||||
mos6560_ = nullptr;
|
||||
}
|
||||
|
||||
void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data)
|
||||
{
|
||||
void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) {
|
||||
uint8_t *target = nullptr;
|
||||
size_t max_length = 0x2000;
|
||||
switch(slot)
|
||||
{
|
||||
switch(slot) {
|
||||
case Kernel: target = kernel_rom_; break;
|
||||
case Characters: target = character_rom_; max_length = 0x1000; break;
|
||||
case BASIC: target = basic_rom_; break;
|
||||
@@ -269,45 +252,20 @@ void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data)
|
||||
return;
|
||||
}
|
||||
|
||||
if(target)
|
||||
{
|
||||
if(target) {
|
||||
size_t length_to_copy = std::min(max_length, length);
|
||||
memcpy(target, data, length_to_copy);
|
||||
}
|
||||
}
|
||||
|
||||
//void Machine::set_prg(const char *file_name, size_t length, const uint8_t *data)
|
||||
//{
|
||||
// if(length > 2)
|
||||
// {
|
||||
// _rom_address = (uint16_t)(data[0] | (data[1] << 8));
|
||||
// _rom_length = (uint16_t)(length - 2);
|
||||
//
|
||||
// // install in the ROM area if this looks like a ROM; otherwise put on tape and throw into that mechanism
|
||||
// if(_rom_address == 0xa000)
|
||||
// {
|
||||
// _rom = new uint8_t[0x2000];
|
||||
// memcpy(_rom, &data[2], length - 2);
|
||||
// write_to_map(processor_read_memory_map_, _rom, _rom_address, 0x2000);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// set_tape(std::shared_ptr<Storage::Tape::Tape>(new Storage::Tape::PRG(file_name)));
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
#pragma mar - Tape
|
||||
|
||||
void Machine::configure_as_target(const StaticAnalyser::Target &target)
|
||||
{
|
||||
if(target.tapes.size())
|
||||
{
|
||||
tape_.set_tape(target.tapes.front());
|
||||
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
|
||||
if(target.tapes.size()) {
|
||||
tape_->set_tape(target.tapes.front());
|
||||
}
|
||||
|
||||
if(target.disks.size())
|
||||
{
|
||||
if(target.disks.size()) {
|
||||
// construct the 1540
|
||||
c1540_.reset(new ::Commodore::C1540::Machine);
|
||||
|
||||
@@ -321,8 +279,7 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target)
|
||||
install_disk_rom();
|
||||
}
|
||||
|
||||
if(target.cartridges.size())
|
||||
{
|
||||
if(target.cartridges.size()) {
|
||||
rom_address_ = 0xa000;
|
||||
std::vector<uint8_t> rom_image = target.cartridges.front()->get_segments().front().data;
|
||||
rom_length_ = (uint16_t)(rom_image.size());
|
||||
@@ -332,39 +289,31 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target)
|
||||
write_to_map(processor_read_memory_map_, rom_, rom_address_, 0x2000);
|
||||
}
|
||||
|
||||
if(should_automatically_load_media_)
|
||||
{
|
||||
if(target.loadingCommand.length()) // TODO: and automatic loading option enabled
|
||||
{
|
||||
set_typer_for_string(target.loadingCommand.c_str());
|
||||
}
|
||||
if(target.loadingCommand.length()) {
|
||||
set_typer_for_string(target.loadingCommand.c_str());
|
||||
}
|
||||
|
||||
switch(target.vic20.memory_model)
|
||||
{
|
||||
case StaticAnalyser::Vic20MemoryModel::Unexpanded:
|
||||
set_memory_size(Default);
|
||||
break;
|
||||
case StaticAnalyser::Vic20MemoryModel::EightKB:
|
||||
set_memory_size(ThreeKB);
|
||||
break;
|
||||
case StaticAnalyser::Vic20MemoryModel::ThirtyTwoKB:
|
||||
set_memory_size(ThirtyTwoKB);
|
||||
break;
|
||||
}
|
||||
switch(target.vic20.memory_model) {
|
||||
case StaticAnalyser::Vic20MemoryModel::Unexpanded:
|
||||
set_memory_size(Default);
|
||||
break;
|
||||
case StaticAnalyser::Vic20MemoryModel::EightKB:
|
||||
set_memory_size(ThreeKB);
|
||||
break;
|
||||
case StaticAnalyser::Vic20MemoryModel::ThirtyTwoKB:
|
||||
set_memory_size(ThirtyTwoKB);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape)
|
||||
{
|
||||
void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) {
|
||||
keyboard_via_->set_control_line_input(KeyboardVIA::Port::A, KeyboardVIA::Line::One, tape->get_input());
|
||||
}
|
||||
|
||||
#pragma mark - Disc
|
||||
|
||||
void Machine::install_disk_rom()
|
||||
{
|
||||
if(drive_rom_ && c1540_)
|
||||
{
|
||||
void Machine::install_disk_rom() {
|
||||
if(drive_rom_ && c1540_) {
|
||||
c1540_->set_rom(drive_rom_.get());
|
||||
c1540_->run_for_cycles(2000000);
|
||||
drive_rom_.reset();
|
||||
@@ -373,45 +322,36 @@ void Machine::install_disk_rom()
|
||||
|
||||
#pragma mark - UserPortVIA
|
||||
|
||||
uint8_t UserPortVIA::get_port_input(Port port)
|
||||
{
|
||||
if(!port)
|
||||
{
|
||||
return port_a_; // TODO: bit 6 should be high if there is no tape, low otherwise
|
||||
uint8_t UserPortVIA::get_port_input(Port port) {
|
||||
if(!port) {
|
||||
return port_a_ | (tape_->has_tape() ? 0x00 : 0x40);
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
void UserPortVIA::set_control_line_output(Port port, Line line, bool value)
|
||||
{
|
||||
// if(port == Port::A && line == Line::Two) {
|
||||
// printf("Tape motor %s\n", value ? "on" : "off");
|
||||
// }
|
||||
void UserPortVIA::set_control_line_output(Port port, Line line, bool value) {
|
||||
if(port == Port::A && line == Line::Two) {
|
||||
tape_->set_motor_control(!value);
|
||||
}
|
||||
}
|
||||
|
||||
void UserPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value)
|
||||
{
|
||||
switch(line)
|
||||
{
|
||||
void UserPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) {
|
||||
switch(line) {
|
||||
default: break;
|
||||
case ::Commodore::Serial::Line::Data: port_a_ = (port_a_ & ~0x02) | (value ? 0x02 : 0x00); break;
|
||||
case ::Commodore::Serial::Line::Clock: port_a_ = (port_a_ & ~0x01) | (value ? 0x01 : 0x00); break;
|
||||
}
|
||||
}
|
||||
|
||||
void UserPortVIA::set_joystick_state(JoystickInput input, bool value)
|
||||
{
|
||||
if(input != JoystickInput::Right)
|
||||
{
|
||||
void UserPortVIA::set_joystick_state(JoystickInput input, bool value) {
|
||||
if(input != JoystickInput::Right) {
|
||||
port_a_ = (port_a_ & ~input) | (value ? 0 : input);
|
||||
}
|
||||
}
|
||||
|
||||
void UserPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask)
|
||||
{
|
||||
void UserPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) {
|
||||
// Line 7 of port A is inverted and output as serial ATN
|
||||
if(!port)
|
||||
{
|
||||
if(!port) {
|
||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
||||
if(serialPort)
|
||||
serialPort->set_output(::Commodore::Serial::Line::Attention, (::Commodore::Serial::LineLevel)!(value&0x80));
|
||||
@@ -420,38 +360,35 @@ void UserPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask)
|
||||
|
||||
UserPortVIA::UserPortVIA() : port_a_(0xbf) {}
|
||||
|
||||
void UserPortVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort)
|
||||
{
|
||||
void UserPortVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) {
|
||||
serial_port_ = serialPort;
|
||||
}
|
||||
|
||||
void UserPortVIA::set_tape(std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape) {
|
||||
tape_ = tape;
|
||||
}
|
||||
|
||||
#pragma mark - KeyboardVIA
|
||||
|
||||
KeyboardVIA::KeyboardVIA() : port_b_(0xff)
|
||||
{
|
||||
KeyboardVIA::KeyboardVIA() : port_b_(0xff) {
|
||||
clear_all_keys();
|
||||
}
|
||||
|
||||
void KeyboardVIA::set_key_state(uint16_t key, bool isPressed)
|
||||
{
|
||||
void KeyboardVIA::set_key_state(uint16_t key, bool isPressed) {
|
||||
if(isPressed)
|
||||
columns_[key & 7] &= ~(key >> 3);
|
||||
else
|
||||
columns_[key & 7] |= (key >> 3);
|
||||
}
|
||||
|
||||
void KeyboardVIA::clear_all_keys()
|
||||
{
|
||||
void KeyboardVIA::clear_all_keys() {
|
||||
memset(columns_, 0xff, sizeof(columns_));
|
||||
}
|
||||
|
||||
uint8_t KeyboardVIA::get_port_input(Port port)
|
||||
{
|
||||
if(!port)
|
||||
{
|
||||
uint8_t KeyboardVIA::get_port_input(Port port) {
|
||||
if(!port) {
|
||||
uint8_t result = 0xff;
|
||||
for(int c = 0; c < 8; c++)
|
||||
{
|
||||
for(int c = 0; c < 8; c++) {
|
||||
if(!(activation_mask_&(1 << c)))
|
||||
result &= columns_[c];
|
||||
}
|
||||
@@ -461,19 +398,15 @@ uint8_t KeyboardVIA::get_port_input(Port port)
|
||||
return port_b_;
|
||||
}
|
||||
|
||||
void KeyboardVIA::set_port_output(Port port, uint8_t value, uint8_t mask)
|
||||
{
|
||||
void KeyboardVIA::set_port_output(Port port, uint8_t value, uint8_t mask) {
|
||||
if(port)
|
||||
activation_mask_ = (value & mask) | (~mask);
|
||||
}
|
||||
|
||||
void KeyboardVIA::set_control_line_output(Port port, Line line, bool value)
|
||||
{
|
||||
if(line == Line::Two)
|
||||
{
|
||||
void KeyboardVIA::set_control_line_output(Port port, Line line, bool value) {
|
||||
if(line == Line::Two) {
|
||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
||||
if(serialPort)
|
||||
{
|
||||
if(serialPort) {
|
||||
// CB2 is inverted to become serial data; CA2 is inverted to become serial clock
|
||||
if(port == Port::A)
|
||||
serialPort->set_output(::Commodore::Serial::Line::Clock, (::Commodore::Serial::LineLevel)!value);
|
||||
@@ -483,28 +416,23 @@ void KeyboardVIA::set_control_line_output(Port port, Line line, bool value)
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardVIA::set_joystick_state(JoystickInput input, bool value)
|
||||
{
|
||||
if(input == JoystickInput::Right)
|
||||
{
|
||||
void KeyboardVIA::set_joystick_state(JoystickInput input, bool value) {
|
||||
if(input == JoystickInput::Right) {
|
||||
port_b_ = (port_b_ & ~input) | (value ? 0 : input);
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort)
|
||||
{
|
||||
void KeyboardVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) {
|
||||
serial_port_ = serialPort;
|
||||
}
|
||||
|
||||
#pragma mark - SerialPort
|
||||
|
||||
void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level)
|
||||
{
|
||||
void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) {
|
||||
std::shared_ptr<UserPortVIA> userPortVIA = user_port_via_.lock();
|
||||
if(userPortVIA) userPortVIA->set_serial_line_state(line, (bool)level);
|
||||
}
|
||||
|
||||
void SerialPort::set_user_port_via(std::shared_ptr<UserPortVIA> userPortVIA)
|
||||
{
|
||||
void SerialPort::set_user_port_via(std::shared_ptr<UserPortVIA> userPortVIA) {
|
||||
user_port_via_ = userPortVIA;
|
||||
}
|
||||
|
||||
@@ -87,10 +87,12 @@ class UserPortVIA: public MOS::MOS6522<UserPortVIA>, public MOS::MOS6522IRQDeleg
|
||||
void set_port_output(Port port, uint8_t value, uint8_t mask);
|
||||
|
||||
void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort);
|
||||
void set_tape(std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape);
|
||||
|
||||
private:
|
||||
uint8_t port_a_;
|
||||
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
|
||||
std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_;
|
||||
};
|
||||
|
||||
class KeyboardVIA: public MOS::MOS6522<KeyboardVIA>, public MOS::MOS6522IRQDelegate {
|
||||
@@ -129,8 +131,7 @@ class SerialPort : public ::Commodore::Serial::Port {
|
||||
|
||||
class Vic6560: public MOS::MOS6560<Vic6560> {
|
||||
public:
|
||||
inline void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data)
|
||||
{
|
||||
inline void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data) {
|
||||
*pixel_data = video_memory_map[address >> 10] ? video_memory_map[address >> 10][address & 0x3ff] : 0xff; // TODO
|
||||
*colour_data = colour_memory[address & 0x03ff];
|
||||
}
|
||||
@@ -165,7 +166,6 @@ class Machine:
|
||||
void set_region(Region region);
|
||||
|
||||
inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; }
|
||||
inline void set_should_automatically_load_media(bool activate) { should_automatically_load_media_ = activate; }
|
||||
|
||||
// to satisfy CPU6502::Processor
|
||||
unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value);
|
||||
@@ -214,15 +214,13 @@ class Machine:
|
||||
std::shared_ptr<::Commodore::Serial::Bus> serial_bus_;
|
||||
|
||||
// Tape
|
||||
Storage::Tape::BinaryTapePlayer tape_;
|
||||
bool use_fast_tape_hack_, should_automatically_load_media_;
|
||||
std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_;
|
||||
bool use_fast_tape_hack_;
|
||||
bool is_running_at_zero_cost_;
|
||||
|
||||
// Disk
|
||||
std::shared_ptr<::Commodore::C1540::Machine> c1540_;
|
||||
void install_disk_rom();
|
||||
|
||||
// Autoload string
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace ConfigurationTarget {
|
||||
*/
|
||||
class Machine {
|
||||
public:
|
||||
virtual void configure_as_target(const StaticAnalyser::Target &target) =0;
|
||||
virtual void configure_as_target(const StaticAnalyser::Target &target) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,10 +15,12 @@
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../Typer.hpp"
|
||||
|
||||
#include "Interrupts.hpp"
|
||||
#include "Plus3.hpp"
|
||||
#include "Speaker.hpp"
|
||||
#include "Tape.hpp"
|
||||
#include "Interrupts.hpp"
|
||||
#include "Video.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
@@ -92,8 +94,8 @@ class Machine:
|
||||
// to satisfy CRTMachine::Machine
|
||||
virtual void setup_output(float aspect_ratio);
|
||||
virtual void close_output();
|
||||
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; }
|
||||
virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return speaker_; }
|
||||
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt();
|
||||
virtual std::shared_ptr<Outputs::Speaker> get_speaker();
|
||||
virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles); }
|
||||
|
||||
// to satisfy Tape::Delegate
|
||||
@@ -105,13 +107,10 @@ class Machine:
|
||||
uint16_t *sequence_for_character(Utility::Typer *typer, char character);
|
||||
|
||||
private:
|
||||
|
||||
inline void update_display();
|
||||
inline void start_pixel_line();
|
||||
inline void end_pixel_line();
|
||||
inline void output_pixels(unsigned int number_of_cycles);
|
||||
|
||||
inline void queue_next_display_interrupt();
|
||||
inline void update_audio();
|
||||
|
||||
inline void signal_interrupt(Interrupt interrupt);
|
||||
inline void clear_interrupt(Interrupt interrupt);
|
||||
inline void evaluate_interrupts();
|
||||
@@ -122,38 +121,20 @@ class Machine:
|
||||
uint8_t os_[16384], ram_[32768];
|
||||
std::vector<uint8_t> dfs_, adfs_;
|
||||
|
||||
// Things affected by registers, explicitly or otherwise.
|
||||
uint8_t interrupt_status_, interrupt_control_;
|
||||
uint8_t palette_[16];
|
||||
uint8_t key_states_[14];
|
||||
// Paging
|
||||
ROMSlot active_rom_;
|
||||
bool keyboard_is_active_, basic_is_active_;
|
||||
uint8_t screen_mode_;
|
||||
uint16_t screen_mode_base_address_;
|
||||
uint16_t start_screen_address_;
|
||||
|
||||
// Interrupt and keyboard state
|
||||
uint8_t interrupt_status_, interrupt_control_;
|
||||
uint8_t key_states_[14];
|
||||
|
||||
// Counters related to simultaneous subsystems
|
||||
unsigned int frame_cycles_, display_output_position_;
|
||||
unsigned int audio_output_position_, audio_output_position_error_;
|
||||
uint8_t phase_;
|
||||
|
||||
struct {
|
||||
uint16_t forty1bpp[256];
|
||||
uint8_t forty2bpp[256];
|
||||
uint32_t eighty1bpp[256];
|
||||
uint16_t eighty2bpp[256];
|
||||
uint8_t eighty4bpp[256];
|
||||
} palette_tables_;
|
||||
|
||||
// Display generation.
|
||||
uint16_t start_line_address_, current_screen_address_;
|
||||
int current_pixel_line_, current_pixel_column_, current_character_row_;
|
||||
uint8_t last_pixel_byte_;
|
||||
bool is_blank_line_;
|
||||
|
||||
// CRT output
|
||||
uint8_t *current_output_target_, *initial_output_target_;
|
||||
unsigned int current_output_divider_;
|
||||
unsigned int cycles_since_display_update_;
|
||||
unsigned int cycles_since_audio_update_;
|
||||
int cycles_until_display_interrupt_;
|
||||
Interrupt next_display_interrupt_;
|
||||
VideoOutput::Range video_access_range_;
|
||||
|
||||
// Tape
|
||||
Tape tape_;
|
||||
@@ -163,9 +144,10 @@ class Machine:
|
||||
// Disk
|
||||
std::unique_ptr<Plus3> plus3_;
|
||||
bool is_holding_shift_;
|
||||
int shift_restart_counter_;
|
||||
|
||||
// Outputs
|
||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
||||
std::unique_ptr<VideoOutput> video_output_;
|
||||
std::shared_ptr<Speaker> speaker_;
|
||||
bool speaker_is_enabled_;
|
||||
};
|
||||
|
||||
@@ -10,32 +10,41 @@
|
||||
|
||||
using namespace Electron;
|
||||
|
||||
Plus3::Plus3() : WD1770(P1770) {}
|
||||
Plus3::Plus3() : WD1770(P1770), last_control_(0) {
|
||||
set_control_register(last_control_, 0xff);
|
||||
}
|
||||
|
||||
void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive)
|
||||
{
|
||||
if(!drives_[drive])
|
||||
{
|
||||
void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||
if(!drives_[drive]) {
|
||||
drives_[drive].reset(new Storage::Disk::Drive);
|
||||
if(drive == selected_drive_) set_drive(drives_[drive]);
|
||||
}
|
||||
drives_[drive]->set_disk(disk);
|
||||
}
|
||||
|
||||
void Plus3::set_control_register(uint8_t control)
|
||||
{
|
||||
// TODO:
|
||||
void Plus3::set_control_register(uint8_t control) {
|
||||
// bit 0 => enable or disable drive 1
|
||||
// bit 1 => enable or disable drive 2
|
||||
// bit 2 => side select
|
||||
// bit 3 => single density select
|
||||
switch(control&3)
|
||||
{
|
||||
case 0: selected_drive_ = -1; set_drive(nullptr); break;
|
||||
default: selected_drive_ = 0; set_drive(drives_[0]); break;
|
||||
case 2: selected_drive_ = 1; set_drive(drives_[1]); break;
|
||||
}
|
||||
if(drives_[0]) drives_[0]->set_head((control & 0x04) ? 1 : 0);
|
||||
if(drives_[1]) drives_[1]->set_head((control & 0x04) ? 1 : 0);
|
||||
set_is_double_density(!(control & 0x08));
|
||||
|
||||
uint8_t changes = control ^ last_control_;
|
||||
last_control_ = control;
|
||||
set_control_register(control, changes);
|
||||
}
|
||||
|
||||
void Plus3::set_control_register(uint8_t control, uint8_t changes) {
|
||||
if(changes&3) {
|
||||
switch(control&3) {
|
||||
case 0: selected_drive_ = -1; set_drive(nullptr); break;
|
||||
default: selected_drive_ = 0; set_drive(drives_[0]); break;
|
||||
case 2: selected_drive_ = 1; set_drive(drives_[1]); break;
|
||||
}
|
||||
}
|
||||
if(changes & 0x04) {
|
||||
invalidate_track();
|
||||
if(drives_[0]) drives_[0]->set_head((control & 0x04) ? 1 : 0);
|
||||
if(drives_[1]) drives_[1]->set_head((control & 0x04) ? 1 : 0);
|
||||
}
|
||||
if(changes & 0x08) set_is_double_density(!(control & 0x08));
|
||||
}
|
||||
|
||||
@@ -21,8 +21,10 @@ class Plus3 : public WD::WD1770 {
|
||||
void set_control_register(uint8_t control);
|
||||
|
||||
private:
|
||||
void set_control_register(uint8_t control, uint8_t changes);
|
||||
std::shared_ptr<Storage::Disk::Drive> drives_[2];
|
||||
int selected_drive_;
|
||||
uint8_t last_control_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -10,37 +10,29 @@
|
||||
|
||||
using namespace Electron;
|
||||
|
||||
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target)
|
||||
{
|
||||
if(is_enabled_)
|
||||
{
|
||||
while(number_of_samples--)
|
||||
{
|
||||
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
|
||||
if(is_enabled_) {
|
||||
while(number_of_samples--) {
|
||||
*target = (int16_t)((counter_ / (divider_+1)) * 8192);
|
||||
target++;
|
||||
counter_ = (counter_ + 1) % ((divider_+1) * 2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
memset(target, 0, sizeof(int16_t) * number_of_samples);
|
||||
}
|
||||
}
|
||||
|
||||
void Speaker::skip_samples(unsigned int number_of_samples)
|
||||
{
|
||||
void Speaker::skip_samples(unsigned int number_of_samples) {
|
||||
counter_ = (counter_ + number_of_samples) % ((divider_+1) * 2);
|
||||
}
|
||||
|
||||
void Speaker::set_divider(uint8_t divider)
|
||||
{
|
||||
void Speaker::set_divider(uint8_t divider) {
|
||||
enqueue([=]() {
|
||||
divider_ = divider * 32 / clock_rate_divider;
|
||||
});
|
||||
}
|
||||
|
||||
void Speaker::set_is_enabled(bool is_enabled)
|
||||
{
|
||||
void Speaker::set_is_enabled(bool is_enabled) {
|
||||
enqueue([=]() {
|
||||
is_enabled_ = is_enabled;
|
||||
counter_ = 0;
|
||||
|
||||
@@ -11,25 +11,21 @@
|
||||
using namespace Electron;
|
||||
|
||||
Tape::Tape() :
|
||||
TapePlayer(2000000),
|
||||
is_running_(false),
|
||||
data_register_(0),
|
||||
delegate_(nullptr),
|
||||
output_({.bits_remaining_until_empty = 0, .cycles_into_pulse = 0}),
|
||||
last_posted_interrupt_status_(0),
|
||||
interrupt_status_(0)
|
||||
{}
|
||||
TapePlayer(2000000),
|
||||
is_running_(false),
|
||||
data_register_(0),
|
||||
delegate_(nullptr),
|
||||
output_({.bits_remaining_until_empty = 0, .cycles_into_pulse = 0}),
|
||||
last_posted_interrupt_status_(0),
|
||||
interrupt_status_(0) {}
|
||||
|
||||
void Tape::push_tape_bit(uint16_t bit)
|
||||
{
|
||||
void Tape::push_tape_bit(uint16_t bit) {
|
||||
data_register_ = (uint16_t)((data_register_ >> 1) | (bit << 10));
|
||||
|
||||
if(input_.minimum_bits_until_full) input_.minimum_bits_until_full--;
|
||||
if(input_.minimum_bits_until_full == 8) interrupt_status_ &= ~Interrupt::ReceiveDataFull;
|
||||
if(!input_.minimum_bits_until_full)
|
||||
{
|
||||
if((data_register_&0x3) == 0x1)
|
||||
{
|
||||
if(!input_.minimum_bits_until_full) {
|
||||
if((data_register_&0x3) == 0x1) {
|
||||
interrupt_status_ |= Interrupt::ReceiveDataFull;
|
||||
if(is_in_input_mode_) input_.minimum_bits_until_full = 9;
|
||||
}
|
||||
@@ -44,66 +40,53 @@ void Tape::push_tape_bit(uint16_t bit)
|
||||
evaluate_interrupts();
|
||||
}
|
||||
|
||||
void Tape::evaluate_interrupts()
|
||||
{
|
||||
if(last_posted_interrupt_status_ != interrupt_status_)
|
||||
{
|
||||
void Tape::evaluate_interrupts() {
|
||||
if(last_posted_interrupt_status_ != interrupt_status_) {
|
||||
last_posted_interrupt_status_ = interrupt_status_;
|
||||
if(delegate_) delegate_->tape_did_change_interrupt_status(this);
|
||||
}
|
||||
}
|
||||
|
||||
void Tape::clear_interrupts(uint8_t interrupts)
|
||||
{
|
||||
void Tape::clear_interrupts(uint8_t interrupts) {
|
||||
interrupt_status_ &= ~interrupts;
|
||||
evaluate_interrupts();
|
||||
}
|
||||
|
||||
void Tape::set_is_in_input_mode(bool is_in_input_mode)
|
||||
{
|
||||
void Tape::set_is_in_input_mode(bool is_in_input_mode) {
|
||||
is_in_input_mode_ = is_in_input_mode;
|
||||
}
|
||||
|
||||
void Tape::set_counter(uint8_t value)
|
||||
{
|
||||
void Tape::set_counter(uint8_t value) {
|
||||
output_.cycles_into_pulse = 0;
|
||||
output_.bits_remaining_until_empty = 0;
|
||||
}
|
||||
|
||||
void Tape::set_data_register(uint8_t value)
|
||||
{
|
||||
void Tape::set_data_register(uint8_t value) {
|
||||
data_register_ = (uint16_t)((value << 2) | 1);
|
||||
output_.bits_remaining_until_empty = 9;
|
||||
}
|
||||
|
||||
uint8_t Tape::get_data_register()
|
||||
{
|
||||
uint8_t Tape::get_data_register() {
|
||||
return (uint8_t)(data_register_ >> 2);
|
||||
}
|
||||
|
||||
void Tape::process_input_pulse(Storage::Tape::Tape::Pulse pulse)
|
||||
{
|
||||
void Tape::process_input_pulse(Storage::Tape::Tape::Pulse pulse) {
|
||||
crossings_[0] = crossings_[1];
|
||||
crossings_[1] = crossings_[2];
|
||||
crossings_[2] = crossings_[3];
|
||||
|
||||
crossings_[3] = Tape::Unrecognised;
|
||||
if(pulse.type != Storage::Tape::Tape::Pulse::Zero)
|
||||
{
|
||||
if(pulse.type != Storage::Tape::Tape::Pulse::Zero) {
|
||||
float pulse_length = (float)pulse.length.length / (float)pulse.length.clock_rate;
|
||||
if(pulse_length >= 0.35 / 2400.0 && pulse_length < 0.7 / 2400.0) crossings_[3] = Tape::Short;
|
||||
if(pulse_length >= 0.35 / 1200.0 && pulse_length < 0.7 / 1200.0) crossings_[3] = Tape::Long;
|
||||
}
|
||||
|
||||
if(crossings_[0] == Tape::Long && crossings_[1] == Tape::Long)
|
||||
{
|
||||
if(crossings_[0] == Tape::Long && crossings_[1] == Tape::Long) {
|
||||
push_tape_bit(0);
|
||||
crossings_[0] = crossings_[1] = Tape::Recognised;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(crossings_[0] == Tape::Short && crossings_[1] == Tape::Short && crossings_[2] == Tape::Short && crossings_[3] == Tape::Short)
|
||||
{
|
||||
} else {
|
||||
if(crossings_[0] == Tape::Short && crossings_[1] == Tape::Short && crossings_[2] == Tape::Short && crossings_[3] == Tape::Short) {
|
||||
push_tape_bit(1);
|
||||
crossings_[0] = crossings_[1] =
|
||||
crossings_[2] = crossings_[3] = Tape::Recognised;
|
||||
@@ -111,23 +94,16 @@ void Tape::process_input_pulse(Storage::Tape::Tape::Pulse pulse)
|
||||
}
|
||||
}
|
||||
|
||||
void Tape::run_for_cycles(unsigned int number_of_cycles)
|
||||
{
|
||||
if(is_enabled_)
|
||||
{
|
||||
if(is_in_input_mode_)
|
||||
{
|
||||
if(is_running_)
|
||||
{
|
||||
void Tape::run_for_cycles(unsigned int number_of_cycles) {
|
||||
if(is_enabled_) {
|
||||
if(is_in_input_mode_) {
|
||||
if(is_running_) {
|
||||
TapePlayer::run_for_cycles((int)number_of_cycles);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
output_.cycles_into_pulse += number_of_cycles;
|
||||
while(output_.cycles_into_pulse > 1664) // 1664 = the closest you can get to 1200 baud if you're looking for something
|
||||
{ // that divides the 125,000Hz clock that the sound divider runs off.
|
||||
output_.cycles_into_pulse -= 1664;
|
||||
while(output_.cycles_into_pulse > 1664) { // 1664 = the closest you can get to 1200 baud if you're looking for something
|
||||
output_.cycles_into_pulse -= 1664; // that divides the 125,000Hz clock that the sound divider runs off.
|
||||
push_tape_bit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,18 +8,15 @@
|
||||
|
||||
#include "Electron.hpp"
|
||||
|
||||
int Electron::Machine::get_typer_delay()
|
||||
{
|
||||
int Electron::Machine::get_typer_delay() {
|
||||
return get_is_resetting() ? 625*25*128 : 0; // wait one second if resetting
|
||||
}
|
||||
|
||||
int Electron::Machine::get_typer_frequency()
|
||||
{
|
||||
int Electron::Machine::get_typer_frequency() {
|
||||
return 625*128*2; // accept a new character every two frames
|
||||
}
|
||||
|
||||
uint16_t *Electron::Machine::sequence_for_character(Utility::Typer *typer, char character)
|
||||
{
|
||||
uint16_t *Electron::Machine::sequence_for_character(Utility::Typer *typer, char character) {
|
||||
#define KEYS(...) {__VA_ARGS__, TerminateSequence}
|
||||
#define SHIFT(...) {KeyShift, __VA_ARGS__, TerminateSequence}
|
||||
#define CTRL(...) {KeyControl, __VA_ARGS__, TerminateSequence}
|
||||
|
||||
453
Machines/Electron/Video.cpp
Normal file
453
Machines/Electron/Video.cpp
Normal file
@@ -0,0 +1,453 @@
|
||||
//
|
||||
// Video.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/12/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Video.hpp"
|
||||
|
||||
using namespace Electron;
|
||||
|
||||
#define graphics_line(v) ((((v) >> 7) - first_graphics_line + field_divider_line) % field_divider_line)
|
||||
#define graphics_column(v) ((((v) & 127) - first_graphics_cycle + 128) & 127)
|
||||
|
||||
namespace {
|
||||
static const int cycles_per_line = 128;
|
||||
static const int lines_per_frame = 625;
|
||||
static const int cycles_per_frame = lines_per_frame * cycles_per_line;
|
||||
static const int crt_cycles_multiplier = 8;
|
||||
static const int crt_cycles_per_line = crt_cycles_multiplier * cycles_per_line;
|
||||
|
||||
static const int field_divider_line = 312; // i.e. the line, simultaneous with which, the first field's sync ends. So if
|
||||
// the first line with pixels in field 1 is the 20th in the frame, the first line
|
||||
// with pixels in field 2 will be 20+field_divider_line
|
||||
static const int first_graphics_line = 31;
|
||||
static const int first_graphics_cycle = 33;
|
||||
|
||||
static const int display_end_interrupt_line = 256;
|
||||
|
||||
static const int real_time_clock_interrupt_1 = 16704;
|
||||
static const int real_time_clock_interrupt_2 = 56704;
|
||||
static const int display_end_interrupt_1 = (first_graphics_line + display_end_interrupt_line)*cycles_per_line;
|
||||
static const int display_end_interrupt_2 = (first_graphics_line + field_divider_line + display_end_interrupt_line)*cycles_per_line;
|
||||
}
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
VideoOutput::VideoOutput(uint8_t *memory) :
|
||||
ram_(memory),
|
||||
current_pixel_line_(-1),
|
||||
output_position_(0),
|
||||
screen_mode_(6),
|
||||
screen_map_pointer_(0),
|
||||
cycles_into_draw_action_(0) {
|
||||
memset(palette_, 0xf, sizeof(palette_));
|
||||
setup_screen_map();
|
||||
setup_base_address();
|
||||
|
||||
crt_.reset(new Outputs::CRT::CRT(crt_cycles_per_line, 8, Outputs::CRT::DisplayType::PAL50, 1));
|
||||
crt_->set_rgb_sampling_function(
|
||||
"vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)"
|
||||
"{"
|
||||
"uint texValue = texture(sampler, coordinate).r;"
|
||||
"texValue >>= 4 - (int(icoordinate.x * 8) & 4);"
|
||||
"return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));"
|
||||
"}");
|
||||
// TODO: as implied below, I've introduced a clock's latency into the graphics pipeline somehow. Investigate.
|
||||
crt_->set_visible_area(crt_->get_rect_for_area(first_graphics_line - 3, 256, (first_graphics_cycle+1) * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f));
|
||||
}
|
||||
|
||||
#pragma mark - CRT getter
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt() {
|
||||
return crt_;
|
||||
}
|
||||
|
||||
#pragma mark - Display update methods
|
||||
|
||||
void VideoOutput::start_pixel_line() {
|
||||
current_pixel_line_ = (current_pixel_line_+1)&255;
|
||||
if(!current_pixel_line_) {
|
||||
start_line_address_ = start_screen_address_;
|
||||
current_character_row_ = 0;
|
||||
is_blank_line_ = false;
|
||||
} else {
|
||||
bool mode_has_blank_lines = (screen_mode_ == 6) || (screen_mode_ == 3);
|
||||
is_blank_line_ = (mode_has_blank_lines && ((current_character_row_ > 7 && current_character_row_ < 10) || (current_pixel_line_ > 249)));
|
||||
|
||||
if(!is_blank_line_) {
|
||||
start_line_address_++;
|
||||
|
||||
if(current_character_row_ > 7) {
|
||||
start_line_address_ += ((screen_mode_ < 4) ? 80 : 40) * 8 - 8;
|
||||
current_character_row_ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
current_screen_address_ = start_line_address_;
|
||||
current_pixel_column_ = 0;
|
||||
initial_output_target_ = current_output_target_ = nullptr;
|
||||
}
|
||||
|
||||
void VideoOutput::end_pixel_line() {
|
||||
if(current_output_target_) crt_->output_data((unsigned int)((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_);
|
||||
current_character_row_++;
|
||||
}
|
||||
|
||||
void VideoOutput::output_pixels(unsigned int number_of_cycles) {
|
||||
if(!number_of_cycles) return;
|
||||
|
||||
if(is_blank_line_) {
|
||||
crt_->output_blank(number_of_cycles * crt_cycles_multiplier);
|
||||
} else {
|
||||
unsigned int divider = 1;
|
||||
switch(screen_mode_) {
|
||||
case 0: case 3: divider = 2; break;
|
||||
case 1: case 4: case 6: divider = 4; break;
|
||||
case 2: case 5: divider = 8; break;
|
||||
}
|
||||
|
||||
if(!initial_output_target_ || divider != current_output_divider_) {
|
||||
if(current_output_target_) crt_->output_data((unsigned int)((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_);
|
||||
current_output_divider_ = divider;
|
||||
initial_output_target_ = current_output_target_ = crt_->allocate_write_area(640 / current_output_divider_);
|
||||
}
|
||||
|
||||
#define get_pixel() \
|
||||
if(current_screen_address_&32768) {\
|
||||
current_screen_address_ = (screen_mode_base_address_ + current_screen_address_)&32767;\
|
||||
}\
|
||||
last_pixel_byte_ = ram_[current_screen_address_];\
|
||||
current_screen_address_ = current_screen_address_+8
|
||||
|
||||
switch(screen_mode_) {
|
||||
case 0: case 3:
|
||||
if(initial_output_target_) {
|
||||
while(number_of_cycles--) {
|
||||
get_pixel();
|
||||
*(uint32_t *)current_output_target_ = palette_tables_.eighty1bpp[last_pixel_byte_];
|
||||
current_output_target_ += 4;
|
||||
current_pixel_column_++;
|
||||
}
|
||||
} else current_output_target_ += 4*number_of_cycles;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
if(initial_output_target_) {
|
||||
while(number_of_cycles--) {
|
||||
get_pixel();
|
||||
*(uint16_t *)current_output_target_ = palette_tables_.eighty2bpp[last_pixel_byte_];
|
||||
current_output_target_ += 2;
|
||||
current_pixel_column_++;
|
||||
}
|
||||
} else current_output_target_ += 2*number_of_cycles;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
if(initial_output_target_) {
|
||||
while(number_of_cycles--) {
|
||||
get_pixel();
|
||||
*current_output_target_ = palette_tables_.eighty4bpp[last_pixel_byte_];
|
||||
current_output_target_ += 1;
|
||||
current_pixel_column_++;
|
||||
}
|
||||
} else current_output_target_ += number_of_cycles;
|
||||
break;
|
||||
|
||||
case 4: case 6:
|
||||
if(initial_output_target_) {
|
||||
if(current_pixel_column_&1) {
|
||||
last_pixel_byte_ <<= 4;
|
||||
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
|
||||
current_output_target_ += 2;
|
||||
|
||||
number_of_cycles--;
|
||||
current_pixel_column_++;
|
||||
}
|
||||
while(number_of_cycles > 1) {
|
||||
get_pixel();
|
||||
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
|
||||
current_output_target_ += 2;
|
||||
|
||||
last_pixel_byte_ <<= 4;
|
||||
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
|
||||
current_output_target_ += 2;
|
||||
|
||||
number_of_cycles -= 2;
|
||||
current_pixel_column_+=2;
|
||||
}
|
||||
if(number_of_cycles) {
|
||||
get_pixel();
|
||||
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
|
||||
current_output_target_ += 2;
|
||||
current_pixel_column_++;
|
||||
}
|
||||
} else current_output_target_ += 2 * number_of_cycles;
|
||||
break;
|
||||
|
||||
case 5:
|
||||
if(initial_output_target_) {
|
||||
if(current_pixel_column_&1) {
|
||||
last_pixel_byte_ <<= 2;
|
||||
*current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_];
|
||||
current_output_target_ += 1;
|
||||
|
||||
number_of_cycles--;
|
||||
current_pixel_column_++;
|
||||
}
|
||||
while(number_of_cycles > 1) {
|
||||
get_pixel();
|
||||
*current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_];
|
||||
current_output_target_ += 1;
|
||||
|
||||
last_pixel_byte_ <<= 2;
|
||||
*current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_];
|
||||
current_output_target_ += 1;
|
||||
|
||||
number_of_cycles -= 2;
|
||||
current_pixel_column_+=2;
|
||||
}
|
||||
if(number_of_cycles) {
|
||||
get_pixel();
|
||||
*current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_];
|
||||
current_output_target_ += 1;
|
||||
current_pixel_column_++;
|
||||
}
|
||||
} else current_output_target_ += number_of_cycles;
|
||||
break;
|
||||
}
|
||||
|
||||
#undef get_pixel
|
||||
}
|
||||
}
|
||||
|
||||
void VideoOutput::run_for_cycles(int number_of_cycles) {
|
||||
output_position_ = (output_position_ + number_of_cycles) % cycles_per_frame;
|
||||
while(number_of_cycles) {
|
||||
int draw_action_length = screen_map_[screen_map_pointer_].length;
|
||||
int time_left_in_action = std::min(number_of_cycles, draw_action_length - cycles_into_draw_action_);
|
||||
if(screen_map_[screen_map_pointer_].type == DrawAction::Pixels) output_pixels((unsigned int)time_left_in_action);
|
||||
|
||||
number_of_cycles -= time_left_in_action;
|
||||
cycles_into_draw_action_ += time_left_in_action;
|
||||
if(cycles_into_draw_action_ == draw_action_length) {
|
||||
switch(screen_map_[screen_map_pointer_].type) {
|
||||
case DrawAction::Sync: crt_->output_sync((unsigned int)(draw_action_length * crt_cycles_multiplier)); break;
|
||||
case DrawAction::ColourBurst: crt_->output_default_colour_burst((unsigned int)(draw_action_length * crt_cycles_multiplier)); break;
|
||||
case DrawAction::Blank: crt_->output_blank((unsigned int)(draw_action_length * crt_cycles_multiplier)); break;
|
||||
case DrawAction::Pixels: end_pixel_line(); break;
|
||||
}
|
||||
screen_map_pointer_ = (screen_map_pointer_ + 1) % screen_map_.size();
|
||||
cycles_into_draw_action_ = 0;
|
||||
if(screen_map_[screen_map_pointer_].type == DrawAction::Pixels) start_pixel_line();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Register hub
|
||||
|
||||
void VideoOutput::set_register(int address, uint8_t value) {
|
||||
switch(address & 0xf) {
|
||||
case 0x02:
|
||||
start_screen_address_ = (start_screen_address_ & 0xfe00) | (uint16_t)((value & 0xe0) << 1);
|
||||
if(!start_screen_address_) start_screen_address_ |= 0x8000;
|
||||
break;
|
||||
case 0x03:
|
||||
start_screen_address_ = (start_screen_address_ & 0x01ff) | (uint16_t)((value & 0x3f) << 9);
|
||||
if(!start_screen_address_) start_screen_address_ |= 0x8000;
|
||||
break;
|
||||
case 0x07: {
|
||||
// update screen mode
|
||||
uint8_t new_screen_mode = (value >> 3)&7;
|
||||
if(new_screen_mode == 7) new_screen_mode = 4;
|
||||
if(new_screen_mode != screen_mode_) {
|
||||
screen_mode_ = new_screen_mode;
|
||||
setup_base_address();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0x08: case 0x09: case 0x0a: case 0x0b:
|
||||
case 0x0c: case 0x0d: case 0x0e: case 0x0f: {
|
||||
static const int registers[4][4] = {
|
||||
{10, 8, 2, 0},
|
||||
{14, 12, 6, 4},
|
||||
{15, 13, 7, 5},
|
||||
{11, 9, 3, 1},
|
||||
};
|
||||
const int index = (address >> 1)&3;
|
||||
const uint8_t colour = ~value;
|
||||
if(address&1) {
|
||||
palette_[registers[index][0]] = (palette_[registers[index][0]]&3) | ((colour >> 1)&4);
|
||||
palette_[registers[index][1]] = (palette_[registers[index][1]]&3) | ((colour >> 0)&4);
|
||||
palette_[registers[index][2]] = (palette_[registers[index][2]]&3) | ((colour << 1)&4);
|
||||
palette_[registers[index][3]] = (palette_[registers[index][3]]&3) | ((colour << 2)&4);
|
||||
|
||||
palette_[registers[index][2]] = (palette_[registers[index][2]]&5) | ((colour >> 4)&2);
|
||||
palette_[registers[index][3]] = (palette_[registers[index][3]]&5) | ((colour >> 3)&2);
|
||||
} else {
|
||||
palette_[registers[index][0]] = (palette_[registers[index][0]]&6) | ((colour >> 7)&1);
|
||||
palette_[registers[index][1]] = (palette_[registers[index][1]]&6) | ((colour >> 6)&1);
|
||||
palette_[registers[index][2]] = (palette_[registers[index][2]]&6) | ((colour >> 5)&1);
|
||||
palette_[registers[index][3]] = (palette_[registers[index][3]]&6) | ((colour >> 4)&1);
|
||||
|
||||
palette_[registers[index][0]] = (palette_[registers[index][0]]&5) | ((colour >> 2)&2);
|
||||
palette_[registers[index][1]] = (palette_[registers[index][1]]&5) | ((colour >> 1)&2);
|
||||
}
|
||||
|
||||
// regenerate all palette tables for now
|
||||
#define pack(a, b) (uint8_t)((a << 4) | (b))
|
||||
for(int byte = 0; byte < 256; byte++) {
|
||||
uint8_t *target = (uint8_t *)&palette_tables_.forty1bpp[byte];
|
||||
target[0] = pack(palette_[(byte&0x80) >> 4], palette_[(byte&0x40) >> 3]);
|
||||
target[1] = pack(palette_[(byte&0x20) >> 2], palette_[(byte&0x10) >> 1]);
|
||||
|
||||
target = (uint8_t *)&palette_tables_.eighty2bpp[byte];
|
||||
target[0] = pack(palette_[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], palette_[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]);
|
||||
target[1] = pack(palette_[((byte&0x20) >> 2) | ((byte&0x02) >> 0)], palette_[((byte&0x10) >> 1) | ((byte&0x01) << 1)]);
|
||||
|
||||
target = (uint8_t *)&palette_tables_.eighty1bpp[byte];
|
||||
target[0] = pack(palette_[(byte&0x80) >> 4], palette_[(byte&0x40) >> 3]);
|
||||
target[1] = pack(palette_[(byte&0x20) >> 2], palette_[(byte&0x10) >> 1]);
|
||||
target[2] = pack(palette_[(byte&0x08) >> 0], palette_[(byte&0x04) << 1]);
|
||||
target[3] = pack(palette_[(byte&0x02) << 2], palette_[(byte&0x01) << 3]);
|
||||
|
||||
palette_tables_.forty2bpp[byte] = pack( palette_[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], palette_[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]);
|
||||
palette_tables_.eighty4bpp[byte] = pack( palette_[((byte&0x80) >> 4) | ((byte&0x20) >> 3) | ((byte&0x08) >> 2) | ((byte&0x02) >> 1)],
|
||||
palette_[((byte&0x40) >> 3) | ((byte&0x10) >> 2) | ((byte&0x04) >> 1) | ((byte&0x01) >> 0)]);
|
||||
}
|
||||
#undef pack
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoOutput::setup_base_address() {
|
||||
switch(screen_mode_) {
|
||||
case 0: case 1: case 2: screen_mode_base_address_ = 0x3000; break;
|
||||
case 3: screen_mode_base_address_ = 0x4000; break;
|
||||
case 4: case 5: screen_mode_base_address_ = 0x5800; break;
|
||||
case 6: screen_mode_base_address_ = 0x6000; break;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Interrupts
|
||||
|
||||
VideoOutput::Interrupt VideoOutput::get_next_interrupt() {
|
||||
VideoOutput::Interrupt interrupt;
|
||||
|
||||
if(output_position_ < real_time_clock_interrupt_1) {
|
||||
interrupt.cycles = real_time_clock_interrupt_1 - output_position_;
|
||||
interrupt.interrupt = RealTimeClock;
|
||||
return interrupt;
|
||||
}
|
||||
|
||||
if(output_position_ < display_end_interrupt_1) {
|
||||
interrupt.cycles = display_end_interrupt_1 - output_position_;
|
||||
interrupt.interrupt = DisplayEnd;
|
||||
return interrupt;
|
||||
}
|
||||
|
||||
if(output_position_ < real_time_clock_interrupt_2) {
|
||||
interrupt.cycles = real_time_clock_interrupt_2 - output_position_;
|
||||
interrupt.interrupt = RealTimeClock;
|
||||
return interrupt;
|
||||
}
|
||||
|
||||
if(output_position_ < display_end_interrupt_2) {
|
||||
interrupt.cycles = display_end_interrupt_2 - output_position_;
|
||||
interrupt.interrupt = DisplayEnd;
|
||||
return interrupt;
|
||||
}
|
||||
|
||||
interrupt.cycles = real_time_clock_interrupt_1 + cycles_per_frame - output_position_;
|
||||
interrupt.interrupt = RealTimeClock;
|
||||
return interrupt;
|
||||
}
|
||||
|
||||
#pragma mark - RAM timing and access information
|
||||
|
||||
unsigned int VideoOutput::get_cycles_until_next_ram_availability(int from_time) {
|
||||
unsigned int result = 0;
|
||||
int position = output_position_ + from_time;
|
||||
|
||||
result += 1 + (position&1);
|
||||
if(screen_mode_ < 4) {
|
||||
const int current_column = graphics_column(position + (position&1));
|
||||
int current_line = graphics_line(position);
|
||||
if(current_column < 80 && current_line < 256) {
|
||||
if(screen_mode_ == 3) {
|
||||
int output_position_line = graphics_line(output_position_);
|
||||
int implied_row = current_character_row_ + (current_line - output_position_line) % 10;
|
||||
if(implied_row < 8)
|
||||
result += (unsigned int)(80 - current_column);
|
||||
}
|
||||
else result += (unsigned int)(80 - current_column);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
VideoOutput::Range VideoOutput::get_memory_access_range() {
|
||||
// This can't be more specific than this without applying a lot more thought because of mixed modes:
|
||||
// suppose a program runs half the screen in an 80-column mode then switches to 40 columns. Then the
|
||||
// real end address will be at 128*80 + 128*40 after the original base, subject to wrapping that depends
|
||||
// on where the overflow occurred. Assuming accesses may run from the lowest possible position through to
|
||||
// the end of RAM is good enough for 95% of use cases however.
|
||||
VideoOutput::Range range;
|
||||
range.low_address = std::min(start_screen_address_, screen_mode_base_address_);
|
||||
range.high_address = 0x8000;
|
||||
return range;
|
||||
}
|
||||
|
||||
#pragma mark - The screen map
|
||||
|
||||
void VideoOutput::setup_screen_map() {
|
||||
/*
|
||||
|
||||
Odd field: Even field:
|
||||
|
||||
|--S--| -S-|
|
||||
|--S--| |--S--|
|
||||
|-S-B-| = 3 |--S--| = 2.5
|
||||
|--B--| |--B--|
|
||||
|--P--| |--P--|
|
||||
|--B--| = 312 |--B--| = 312.5
|
||||
|-B-
|
||||
|
||||
*/
|
||||
for(int c = 0; c < 2; c++) {
|
||||
if(c&1) {
|
||||
screen_map_.emplace_back(DrawAction::Sync, (cycles_per_line * 5) >> 1);
|
||||
screen_map_.emplace_back(DrawAction::Blank, cycles_per_line >> 1);
|
||||
} else {
|
||||
screen_map_.emplace_back(DrawAction::Blank, cycles_per_line >> 1);
|
||||
screen_map_.emplace_back(DrawAction::Sync, (cycles_per_line * 5) >> 1);
|
||||
}
|
||||
for(int c = 0; c < first_graphics_line - 3; c++) emplace_blank_line();
|
||||
for(int c = 0; c < 256; c++) emplace_pixel_line();
|
||||
for(int c = 256 + first_graphics_line; c < 312; c++) emplace_blank_line();
|
||||
if(c&1) emplace_blank_line();
|
||||
}
|
||||
}
|
||||
|
||||
void VideoOutput::emplace_blank_line() {
|
||||
screen_map_.emplace_back(DrawAction::Sync, 9);
|
||||
screen_map_.emplace_back(DrawAction::ColourBurst, 24 - 9);
|
||||
screen_map_.emplace_back(DrawAction::Blank, 128 - 24);
|
||||
}
|
||||
|
||||
void VideoOutput::emplace_pixel_line() {
|
||||
// output format is:
|
||||
// 9 cycles: sync
|
||||
// ... to 24 cycles: colour burst
|
||||
// ... to first_graphics_cycle: blank
|
||||
// ... for 80 cycles: pixels
|
||||
// ... until end of line: blank
|
||||
screen_map_.emplace_back(DrawAction::Sync, 9);
|
||||
screen_map_.emplace_back(DrawAction::ColourBurst, 24 - 9);
|
||||
screen_map_.emplace_back(DrawAction::Blank, first_graphics_cycle - 24);
|
||||
screen_map_.emplace_back(DrawAction::Pixels, 80);
|
||||
screen_map_.emplace_back(DrawAction::Blank, 48 - first_graphics_cycle);
|
||||
}
|
||||
127
Machines/Electron/Video.hpp
Normal file
127
Machines/Electron/Video.hpp
Normal file
@@ -0,0 +1,127 @@
|
||||
//
|
||||
// Video.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/12/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Machines_Electron_Video_hpp
|
||||
#define Machines_Electron_Video_hpp
|
||||
|
||||
#include "../../Outputs/CRT/CRT.hpp"
|
||||
#include "Interrupts.hpp"
|
||||
|
||||
namespace Electron {
|
||||
|
||||
/*!
|
||||
Implements the Electron's video subsystem plus appropriate signalling.
|
||||
|
||||
The Electron has an interlaced fully-bitmapped display with six different output modes,
|
||||
running either at 40 or 80 columns. Memory is shared between video and CPU; when the video
|
||||
is accessing it the CPU may not.
|
||||
*/
|
||||
class VideoOutput {
|
||||
public:
|
||||
/*!
|
||||
Instantiates a VideoOutput that will read its pixels from @c memory. The pointer supplied
|
||||
should be to address 0 in the unexpanded Electron's memory map.
|
||||
*/
|
||||
VideoOutput(uint8_t *memory);
|
||||
|
||||
/// @returns the CRT to which output is being painted.
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt();
|
||||
|
||||
/// Produces the next @c number_of_cycles cycles of video output.
|
||||
void run_for_cycles(int number_of_cycles);
|
||||
|
||||
/*!
|
||||
Writes @c value to the register at @c address. May mutate the results of @c get_next_interrupt,
|
||||
@c get_cycles_until_next_ram_availability and @c get_memory_access_range.
|
||||
*/
|
||||
void set_register(int address, uint8_t value);
|
||||
|
||||
/*!
|
||||
Describes an interrupt the video hardware will generate by its identity and scheduling time.
|
||||
*/
|
||||
struct Interrupt {
|
||||
/// The interrupt that will be signalled.
|
||||
Electron::Interrupt interrupt;
|
||||
/// The number of cycles until it is signalled.
|
||||
int cycles;
|
||||
};
|
||||
/*!
|
||||
@returns the next interrupt that should be generated as a result of the video hardware.
|
||||
The time until signalling returned is the number of cycles after the final one triggered
|
||||
by the most recent call to @c run_for_cycles.
|
||||
|
||||
This result may be mutated by calls to @c set_register.
|
||||
*/
|
||||
Interrupt get_next_interrupt();
|
||||
|
||||
/*!
|
||||
@returns the number of cycles after (final cycle of last run_for_cycles batch + @c from_time)
|
||||
before the video circuits will allow the CPU to access RAM.
|
||||
*/
|
||||
unsigned int get_cycles_until_next_ram_availability(int from_time);
|
||||
|
||||
struct Range {
|
||||
uint16_t low_address, high_address;
|
||||
};
|
||||
/*!
|
||||
@returns the range of addresses that the video might read from.
|
||||
*/
|
||||
Range get_memory_access_range();
|
||||
|
||||
private:
|
||||
inline void start_pixel_line();
|
||||
inline void end_pixel_line();
|
||||
inline void output_pixels(unsigned int number_of_cycles);
|
||||
inline void setup_base_address();
|
||||
|
||||
int output_position_, unused_cycles_;
|
||||
|
||||
uint8_t palette_[16];
|
||||
uint8_t screen_mode_;
|
||||
uint16_t screen_mode_base_address_;
|
||||
uint16_t start_screen_address_;
|
||||
|
||||
uint8_t *ram_;
|
||||
struct {
|
||||
uint16_t forty1bpp[256];
|
||||
uint8_t forty2bpp[256];
|
||||
uint32_t eighty1bpp[256];
|
||||
uint16_t eighty2bpp[256];
|
||||
uint8_t eighty4bpp[256];
|
||||
} palette_tables_;
|
||||
|
||||
// Display generation.
|
||||
uint16_t start_line_address_, current_screen_address_;
|
||||
int current_pixel_line_, current_pixel_column_, current_character_row_;
|
||||
uint8_t last_pixel_byte_;
|
||||
bool is_blank_line_;
|
||||
|
||||
// CRT output
|
||||
uint8_t *current_output_target_, *initial_output_target_;
|
||||
unsigned int current_output_divider_;
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
||||
|
||||
struct DrawAction {
|
||||
enum Type {
|
||||
Sync, ColourBurst, Blank, Pixels
|
||||
} type;
|
||||
int length;
|
||||
DrawAction(Type type, int length) : type(type), length(length) {}
|
||||
};
|
||||
std::vector<DrawAction> screen_map_;
|
||||
void setup_screen_map();
|
||||
void emplace_blank_line();
|
||||
void emplace_pixel_line();
|
||||
size_t screen_map_pointer_;
|
||||
int cycles_into_draw_action_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Video_hpp */
|
||||
@@ -10,18 +10,15 @@
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
void Memory::Fuzz(uint8_t *buffer, size_t size)
|
||||
{
|
||||
void Memory::Fuzz(uint8_t *buffer, size_t size) {
|
||||
unsigned int divider = ((unsigned int)RAND_MAX + 1) / 256;
|
||||
unsigned int shift = 1, value = 1;
|
||||
while(value < divider)
|
||||
{
|
||||
while(value < divider) {
|
||||
value <<= 1;
|
||||
shift++;
|
||||
}
|
||||
|
||||
for(size_t c = 0; c < size; c++)
|
||||
{
|
||||
buffer[c] = (uint8_t)(rand() >> shift);
|
||||
for(size_t c = 0; c < size; c++) {
|
||||
buffer[c] = (uint8_t)(std::rand() >> shift);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,110 +19,99 @@ namespace {
|
||||
}
|
||||
|
||||
Microdisc::Microdisc() :
|
||||
irq_enable_(false),
|
||||
delegate_(nullptr),
|
||||
paging_flags_(BASICDisable),
|
||||
head_load_request_counter_(-1),
|
||||
WD1770(P1793)
|
||||
{}
|
||||
irq_enable_(false),
|
||||
delegate_(nullptr),
|
||||
paging_flags_(BASICDisable),
|
||||
head_load_request_counter_(-1),
|
||||
WD1770(P1793),
|
||||
last_control_(0) {
|
||||
set_control_register(last_control_, 0xff);
|
||||
}
|
||||
|
||||
void Microdisc::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive)
|
||||
{
|
||||
if(!drives_[drive])
|
||||
{
|
||||
void Microdisc::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||
if(!drives_[drive]) {
|
||||
drives_[drive].reset(new Storage::Disk::Drive);
|
||||
if(drive == selected_drive_) set_drive(drives_[drive]);
|
||||
}
|
||||
drives_[drive]->set_disk(disk);
|
||||
}
|
||||
|
||||
void Microdisc::set_control_register(uint8_t control)
|
||||
{
|
||||
printf("control: %d%d%d%d%d%d%d%d\n",
|
||||
(control >> 7)&1,
|
||||
(control >> 6)&1,
|
||||
(control >> 5)&1,
|
||||
(control >> 4)&1,
|
||||
(control >> 3)&1,
|
||||
(control >> 2)&1,
|
||||
(control >> 1)&1,
|
||||
(control >> 0)&1);
|
||||
void Microdisc::set_control_register(uint8_t control) {
|
||||
uint8_t changes = last_control_ ^ control;
|
||||
last_control_ = control;
|
||||
set_control_register(control, changes);
|
||||
}
|
||||
|
||||
void Microdisc::set_control_register(uint8_t control, uint8_t changes) {
|
||||
// b2: data separator clock rate select (1 = double) [TODO]
|
||||
|
||||
// b65: drive select
|
||||
selected_drive_ = (control >> 5)&3;
|
||||
set_drive(drives_[selected_drive_]);
|
||||
if((changes >> 5)&3) {
|
||||
selected_drive_ = (control >> 5)&3;
|
||||
set_drive(drives_[selected_drive_]);
|
||||
}
|
||||
|
||||
// b4: side select
|
||||
unsigned int head = (control & 0x10) ? 1 : 0;
|
||||
for(int c = 0; c < 4; c++)
|
||||
{
|
||||
if(drives_[c]) drives_[c]->set_head(head);
|
||||
if(changes & 0x10) {
|
||||
unsigned int head = (control & 0x10) ? 1 : 0;
|
||||
for(int c = 0; c < 4; c++) {
|
||||
if(drives_[c]) drives_[c]->set_head(head);
|
||||
}
|
||||
}
|
||||
|
||||
// b3: double density select (0 = double)
|
||||
set_is_double_density(!(control & 0x08));
|
||||
if(changes & 0x08) {
|
||||
set_is_double_density(!(control & 0x08));
|
||||
}
|
||||
|
||||
// b0: IRQ enable
|
||||
bool had_irq = get_interrupt_request_line();
|
||||
irq_enable_ = !!(control & 0x01);
|
||||
bool has_irq = get_interrupt_request_line();
|
||||
if(has_irq != had_irq && delegate_)
|
||||
{
|
||||
delegate_->wd1770_did_change_output(this);
|
||||
if(changes & 0x01) {
|
||||
bool had_irq = get_interrupt_request_line();
|
||||
irq_enable_ = !!(control & 0x01);
|
||||
bool has_irq = get_interrupt_request_line();
|
||||
if(has_irq != had_irq && delegate_) {
|
||||
delegate_->wd1770_did_change_output(this);
|
||||
}
|
||||
}
|
||||
|
||||
// b7: EPROM select (0 = select)
|
||||
// b1: ROM disable (0 = disable)
|
||||
int new_paging_flags = ((control & 0x02) ? 0 : BASICDisable) | ((control & 0x80) ? MicrodscDisable : 0);
|
||||
if(new_paging_flags != paging_flags_)
|
||||
{
|
||||
paging_flags_ = new_paging_flags;
|
||||
if(changes & 0x82) {
|
||||
paging_flags_ = ((control & 0x02) ? 0 : BASICDisable) | ((control & 0x80) ? MicrodscDisable : 0);
|
||||
if(delegate_) delegate_->microdisc_did_change_paging_flags(this);
|
||||
}
|
||||
}
|
||||
|
||||
bool Microdisc::get_interrupt_request_line()
|
||||
{
|
||||
bool Microdisc::get_interrupt_request_line() {
|
||||
return irq_enable_ && WD1770::get_interrupt_request_line();
|
||||
}
|
||||
|
||||
uint8_t Microdisc::get_interrupt_request_register()
|
||||
{
|
||||
return 0x7f | (get_interrupt_request_line() ? 0x00 : 0x80);
|
||||
uint8_t Microdisc::get_interrupt_request_register() {
|
||||
return 0x7f | (WD1770::get_interrupt_request_line() ? 0x00 : 0x80);
|
||||
}
|
||||
|
||||
uint8_t Microdisc::get_data_request_register()
|
||||
{
|
||||
uint8_t Microdisc::get_data_request_register() {
|
||||
return 0x7f | (get_data_request_line() ? 0x00 : 0x80);
|
||||
}
|
||||
|
||||
void Microdisc::set_head_load_request(bool head_load)
|
||||
{
|
||||
void Microdisc::set_head_load_request(bool head_load) {
|
||||
set_motor_on(head_load);
|
||||
if(head_load)
|
||||
{
|
||||
if(head_load) {
|
||||
head_load_request_counter_ = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
head_load_request_counter_ = head_load_request_counter_target;
|
||||
set_head_loaded(head_load);
|
||||
}
|
||||
}
|
||||
|
||||
void Microdisc::run_for_cycles(unsigned int number_of_cycles)
|
||||
{
|
||||
if(head_load_request_counter_ < head_load_request_counter_target)
|
||||
{
|
||||
void Microdisc::run_for_cycles(unsigned int number_of_cycles) {
|
||||
if(head_load_request_counter_ < head_load_request_counter_target) {
|
||||
head_load_request_counter_ += number_of_cycles;
|
||||
if(head_load_request_counter_ >= head_load_request_counter_target) set_head_loaded(true);
|
||||
}
|
||||
WD::WD1770::run_for_cycles(number_of_cycles);
|
||||
}
|
||||
|
||||
bool Microdisc::get_drive_is_ready()
|
||||
{
|
||||
bool Microdisc::get_drive_is_ready() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ class Microdisc: public WD::WD1770 {
|
||||
inline int get_paging_flags() { return paging_flags_; }
|
||||
|
||||
private:
|
||||
void set_control_register(uint8_t control, uint8_t changes);
|
||||
void set_head_load_request(bool head_load);
|
||||
bool get_drive_is_ready();
|
||||
std::shared_ptr<Storage::Disk::Drive> drives_[4];
|
||||
@@ -47,6 +48,7 @@ class Microdisc: public WD::WD1770 {
|
||||
int paging_flags_;
|
||||
int head_load_request_counter_;
|
||||
Delegate *delegate_;
|
||||
uint8_t last_control_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -12,15 +12,14 @@
|
||||
using namespace Oric;
|
||||
|
||||
Machine::Machine() :
|
||||
cycles_since_video_update_(0),
|
||||
use_fast_tape_hack_(false),
|
||||
typer_delay_(2500000),
|
||||
keyboard_read_count_(0),
|
||||
keyboard_(new Keyboard),
|
||||
ram_top_(0xbfff),
|
||||
paged_rom_(rom_),
|
||||
microdisc_is_enabled_(false)
|
||||
{
|
||||
cycles_since_video_update_(0),
|
||||
use_fast_tape_hack_(false),
|
||||
typer_delay_(2500000),
|
||||
keyboard_read_count_(0),
|
||||
keyboard_(new Keyboard),
|
||||
ram_top_(0xbfff),
|
||||
paged_rom_(rom_),
|
||||
microdisc_is_enabled_(false) {
|
||||
set_clock_rate(1000000);
|
||||
via_.set_interrupt_delegate(this);
|
||||
via_.keyboard = keyboard_;
|
||||
@@ -29,43 +28,35 @@ Machine::Machine() :
|
||||
Memory::Fuzz(ram_, sizeof(ram_));
|
||||
}
|
||||
|
||||
void Machine::configure_as_target(const StaticAnalyser::Target &target)
|
||||
{
|
||||
if(target.tapes.size())
|
||||
{
|
||||
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
|
||||
if(target.tapes.size()) {
|
||||
via_.tape->set_tape(target.tapes.front());
|
||||
}
|
||||
|
||||
if(target.loadingCommand.length()) // TODO: and automatic loading option enabled
|
||||
{
|
||||
if(target.loadingCommand.length()) { // TODO: and automatic loading option enabled
|
||||
set_typer_for_string(target.loadingCommand.c_str());
|
||||
}
|
||||
|
||||
if(target.oric.has_microdisc)
|
||||
{
|
||||
if(target.oric.has_microdisc) {
|
||||
microdisc_is_enabled_ = true;
|
||||
microdisc_did_change_paging_flags(µdisc_);
|
||||
microdisc_.set_delegate(this);
|
||||
}
|
||||
|
||||
int drive_index = 0;
|
||||
for(auto disk : target.disks)
|
||||
{
|
||||
for(auto disk : target.disks) {
|
||||
if(drive_index < 4) microdisc_.set_disk(disk, drive_index);
|
||||
drive_index++;
|
||||
}
|
||||
|
||||
if(target.oric.use_atmos_rom)
|
||||
{
|
||||
if(target.oric.use_atmos_rom) {
|
||||
memcpy(rom_, basic11_rom_.data(), std::min(basic11_rom_.size(), sizeof(rom_)));
|
||||
|
||||
is_using_basic11_ = true;
|
||||
tape_get_byte_address_ = 0xe6c9;
|
||||
scan_keyboard_address_ = 0xf495;
|
||||
tape_speed_address_ = 0x024d;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
memcpy(rom_, basic10_rom_.data(), std::min(basic10_rom_.size(), sizeof(rom_)));
|
||||
|
||||
is_using_basic11_ = false;
|
||||
@@ -75,40 +66,34 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target)
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::set_rom(ROM rom, const std::vector<uint8_t> &data)
|
||||
{
|
||||
switch(rom)
|
||||
{
|
||||
void Machine::set_rom(ROM rom, const std::vector<uint8_t> &data) {
|
||||
switch(rom) {
|
||||
case BASIC11: basic11_rom_ = std::move(data); break;
|
||||
case BASIC10: basic10_rom_ = std::move(data); break;
|
||||
case Microdisc: microdisc_rom_ = std::move(data); break;
|
||||
case Colour:
|
||||
colour_rom_ = std::move(data);
|
||||
if(video_output_) video_output_->set_colour_rom(colour_rom_);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value)
|
||||
{
|
||||
if(address > ram_top_)
|
||||
{
|
||||
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
if(address > ram_top_) {
|
||||
if(isReadOperation(operation)) *value = paged_rom_[address - ram_top_ - 1];
|
||||
|
||||
// 024D = 0 => fast; otherwise slow
|
||||
// E6C9 = read byte: return byte in A
|
||||
if(address == tape_get_byte_address_ && paged_rom_ == rom_ && use_fast_tape_hack_ && operation == CPU6502::BusOperation::ReadOpcode && via_.tape->has_tape() && !via_.tape->get_tape()->is_at_end())
|
||||
{
|
||||
if(address == tape_get_byte_address_ && paged_rom_ == rom_ && use_fast_tape_hack_ && operation == CPU6502::BusOperation::ReadOpcode && via_.tape->has_tape() && !via_.tape->get_tape()->is_at_end()) {
|
||||
uint8_t next_byte = via_.tape->get_next_byte(!ram_[tape_speed_address_]);
|
||||
set_value_of_register(CPU6502::A, next_byte);
|
||||
set_value_of_register(CPU6502::Flags, next_byte ? 0 : CPU6502::Flag::Zero);
|
||||
*value = 0x60; // i.e. RTS
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if((address & 0xff00) == 0x0300)
|
||||
{
|
||||
if(microdisc_is_enabled_ && address >= 0x0310)
|
||||
{
|
||||
switch(address)
|
||||
{
|
||||
} else {
|
||||
if((address & 0xff00) == 0x0300) {
|
||||
if(microdisc_is_enabled_ && address >= 0x0310) {
|
||||
switch(address) {
|
||||
case 0x0310: case 0x0311: case 0x0312: case 0x0313:
|
||||
if(isReadOperation(operation)) *value = microdisc_.get_register(address);
|
||||
else microdisc_.set_register(address, *value);
|
||||
@@ -121,32 +106,25 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
if(isReadOperation(operation)) *value = microdisc_.get_data_request_register();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
if(isReadOperation(operation)) *value = via_.get_register(address);
|
||||
else via_.set_register(address, *value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
if(isReadOperation(operation))
|
||||
*value = ram_[address];
|
||||
else
|
||||
{
|
||||
else {
|
||||
if(address >= 0x9800 && address <= 0xc000) { update_video(); typer_delay_ = 0; }
|
||||
ram_[address] = *value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(typer_ && address == scan_keyboard_address_ && operation == CPU6502::BusOperation::ReadOpcode)
|
||||
{
|
||||
if(typer_ && address == scan_keyboard_address_ && operation == CPU6502::BusOperation::ReadOpcode) {
|
||||
// the Oric 1 misses any key pressed on the very first entry into the read keyboard routine, so don't
|
||||
// do anything until at least the second, regardless of machine
|
||||
if(!keyboard_read_count_) keyboard_read_count_++;
|
||||
else if(!typer_->type_next_character())
|
||||
{
|
||||
else if(!typer_->type_next_character()) {
|
||||
clear_all_keys();
|
||||
typer_.reset();
|
||||
}
|
||||
@@ -158,44 +136,36 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
return 1;
|
||||
}
|
||||
|
||||
void Machine::synchronise()
|
||||
{
|
||||
void Machine::synchronise() {
|
||||
update_video();
|
||||
via_.synchronise();
|
||||
}
|
||||
|
||||
void Machine::update_video()
|
||||
{
|
||||
void Machine::update_video() {
|
||||
video_output_->run_for_cycles(cycles_since_video_update_);
|
||||
cycles_since_video_update_ = 0;
|
||||
}
|
||||
|
||||
void Machine::setup_output(float aspect_ratio)
|
||||
{
|
||||
video_output_.reset(new VideoOutput(ram_));
|
||||
void Machine::setup_output(float aspect_ratio) {
|
||||
via_.ay8910.reset(new GI::AY38910());
|
||||
via_.ay8910->set_clock_rate(1000000);
|
||||
video_output_.reset(new VideoOutput(ram_));
|
||||
if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_);
|
||||
}
|
||||
|
||||
void Machine::close_output()
|
||||
{
|
||||
void Machine::close_output() {
|
||||
video_output_.reset();
|
||||
via_.ay8910.reset();
|
||||
}
|
||||
|
||||
void Machine::mos6522_did_change_interrupt_status(void *mos6522)
|
||||
{
|
||||
void Machine::mos6522_did_change_interrupt_status(void *mos6522) {
|
||||
set_interrupt_line();
|
||||
}
|
||||
|
||||
void Machine::set_key_state(uint16_t key, bool isPressed)
|
||||
{
|
||||
if(key == KeyNMI)
|
||||
{
|
||||
void Machine::set_key_state(uint16_t key, bool isPressed) {
|
||||
if(key == KeyNMI) {
|
||||
set_nmi_line(isPressed);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
if(isPressed)
|
||||
keyboard_->rows[key >> 8] |= (key & 0xff);
|
||||
else
|
||||
@@ -203,95 +173,80 @@ void Machine::set_key_state(uint16_t key, bool isPressed)
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::clear_all_keys()
|
||||
{
|
||||
void Machine::clear_all_keys() {
|
||||
memset(keyboard_->rows, 0, sizeof(keyboard_->rows));
|
||||
}
|
||||
|
||||
void Machine::set_use_fast_tape_hack(bool activate)
|
||||
{
|
||||
void Machine::set_use_fast_tape_hack(bool activate) {
|
||||
use_fast_tape_hack_ = activate;
|
||||
}
|
||||
|
||||
void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player)
|
||||
{
|
||||
void Machine::set_output_device(Outputs::CRT::OutputDevice output_device) {
|
||||
video_output_->set_output_device(output_device);
|
||||
}
|
||||
|
||||
void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player) {
|
||||
// set CB1
|
||||
via_.set_control_line_input(VIA::Port::B, VIA::Line::One, tape_player->get_input());
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> Machine::get_crt()
|
||||
{
|
||||
std::shared_ptr<Outputs::CRT::CRT> Machine::get_crt() {
|
||||
return video_output_->get_crt();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::Speaker> Machine::get_speaker()
|
||||
{
|
||||
std::shared_ptr<Outputs::Speaker> Machine::get_speaker() {
|
||||
return via_.ay8910;
|
||||
}
|
||||
|
||||
void Machine::run_for_cycles(int number_of_cycles)
|
||||
{
|
||||
void Machine::run_for_cycles(int number_of_cycles) {
|
||||
CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles);
|
||||
}
|
||||
|
||||
#pragma mark - The 6522
|
||||
|
||||
Machine::VIA::VIA() :
|
||||
MOS::MOS6522<Machine::VIA>(),
|
||||
cycles_since_ay_update_(0),
|
||||
tape(new TapePlayer) {}
|
||||
MOS::MOS6522<Machine::VIA>(),
|
||||
cycles_since_ay_update_(0),
|
||||
tape(new TapePlayer) {}
|
||||
|
||||
void Machine::VIA::set_control_line_output(Port port, Line line, bool value)
|
||||
{
|
||||
if(line)
|
||||
{
|
||||
void Machine::VIA::set_control_line_output(Port port, Line line, bool value) {
|
||||
if(line) {
|
||||
if(port) ay_bdir_ = value; else ay_bc1_ = value;
|
||||
update_ay();
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::VIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask)
|
||||
{
|
||||
if(port)
|
||||
{
|
||||
void Machine::VIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask) {
|
||||
if(port) {
|
||||
keyboard->row = value;
|
||||
tape->set_motor_control(value & 0x40);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
ay8910->set_data_input(value);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Machine::VIA::get_port_input(Port port)
|
||||
{
|
||||
if(port)
|
||||
{
|
||||
uint8_t Machine::VIA::get_port_input(Port port) {
|
||||
if(port) {
|
||||
uint8_t column = ay8910->get_port_output(false) ^ 0xff;
|
||||
return (keyboard->rows[keyboard->row & 7] & column) ? 0x08 : 0x00;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
return ay8910->get_data_output();
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::VIA::synchronise()
|
||||
{
|
||||
void Machine::VIA::synchronise() {
|
||||
ay8910->run_for_cycles(cycles_since_ay_update_);
|
||||
ay8910->flush();
|
||||
cycles_since_ay_update_ = 0;
|
||||
}
|
||||
|
||||
void Machine::VIA::run_for_cycles(unsigned int number_of_cycles)
|
||||
{
|
||||
void Machine::VIA::run_for_cycles(unsigned int number_of_cycles) {
|
||||
cycles_since_ay_update_ += number_of_cycles;
|
||||
MOS::MOS6522<VIA>::run_for_cycles(number_of_cycles);
|
||||
tape->run_for_cycles((int)number_of_cycles);
|
||||
}
|
||||
|
||||
void Machine::VIA::update_ay()
|
||||
{
|
||||
void Machine::VIA::update_ay() {
|
||||
ay8910->run_for_cycles(cycles_since_ay_update_);
|
||||
cycles_since_ay_update_ = 0;
|
||||
ay8910->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BCDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2));
|
||||
@@ -300,45 +255,34 @@ void Machine::VIA::update_ay()
|
||||
#pragma mark - TapePlayer
|
||||
|
||||
Machine::TapePlayer::TapePlayer() :
|
||||
Storage::Tape::BinaryTapePlayer(1000000)
|
||||
{}
|
||||
Storage::Tape::BinaryTapePlayer(1000000) {}
|
||||
|
||||
uint8_t Machine::TapePlayer::get_next_byte(bool fast)
|
||||
{
|
||||
uint8_t Machine::TapePlayer::get_next_byte(bool fast) {
|
||||
return (uint8_t)parser_.get_next_byte(get_tape(), fast);
|
||||
}
|
||||
|
||||
#pragma mark - Microdisc
|
||||
|
||||
void Machine::microdisc_did_change_paging_flags(class Microdisc *microdisc)
|
||||
{
|
||||
void Machine::microdisc_did_change_paging_flags(class Microdisc *microdisc) {
|
||||
int flags = microdisc->get_paging_flags();
|
||||
if(!(flags&Microdisc::PagingFlags::BASICDisable))
|
||||
{
|
||||
if(!(flags&Microdisc::PagingFlags::BASICDisable)) {
|
||||
ram_top_ = 0xbfff;
|
||||
paged_rom_ = rom_;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(flags&Microdisc::PagingFlags::MicrodscDisable)
|
||||
{
|
||||
} else {
|
||||
if(flags&Microdisc::PagingFlags::MicrodscDisable) {
|
||||
ram_top_ = 0xffff;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
ram_top_ = 0xdfff;
|
||||
paged_rom_ = microdisc_rom_.data();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::wd1770_did_change_output(WD::WD1770 *wd1770)
|
||||
{
|
||||
void Machine::wd1770_did_change_output(WD::WD1770 *wd1770) {
|
||||
set_interrupt_line();
|
||||
}
|
||||
|
||||
void Machine::set_interrupt_line()
|
||||
{
|
||||
void Machine::set_interrupt_line() {
|
||||
set_irq_line(
|
||||
via_.get_interrupt_line() ||
|
||||
(microdisc_is_enabled_ && microdisc_.get_interrupt_request_line()));
|
||||
|
||||
@@ -53,7 +53,7 @@ enum Key: uint16_t {
|
||||
};
|
||||
|
||||
enum ROM {
|
||||
BASIC10, BASIC11, Microdisc
|
||||
BASIC10, BASIC11, Microdisc, Colour
|
||||
};
|
||||
|
||||
class Machine:
|
||||
@@ -73,6 +73,7 @@ class Machine:
|
||||
void clear_all_keys();
|
||||
|
||||
void set_use_fast_tape_hack(bool activate);
|
||||
void set_output_device(Outputs::CRT::OutputDevice output_device);
|
||||
|
||||
// to satisfy ConfigurationTarget::Machine
|
||||
void configure_as_target(const StaticAnalyser::Target &target);
|
||||
@@ -103,7 +104,7 @@ class Machine:
|
||||
|
||||
private:
|
||||
// RAM and ROM
|
||||
std::vector<uint8_t> basic11_rom_, basic10_rom_, microdisc_rom_;
|
||||
std::vector<uint8_t> basic11_rom_, basic10_rom_, microdisc_rom_, colour_rom_;
|
||||
uint8_t ram_[65536], rom_[16384];
|
||||
int cycles_since_video_update_;
|
||||
inline void update_video();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "Oric.hpp"
|
||||
|
||||
uint16_t *Oric::Machine::sequence_for_character(Utility::Typer *typer, char character)
|
||||
{
|
||||
uint16_t *Oric::Machine::sequence_for_character(Utility::Typer *typer, char character) {
|
||||
#define KEYS(...) {__VA_ARGS__, TerminateSequence}
|
||||
#define SHIFT(...) {KeyLeftShift, __VA_ARGS__, TerminateSequence}
|
||||
#define X {NotMapped}
|
||||
|
||||
@@ -20,67 +20,85 @@ namespace {
|
||||
}
|
||||
|
||||
VideoOutput::VideoOutput(uint8_t *memory) :
|
||||
ram_(memory),
|
||||
frame_counter_(0), counter_(0),
|
||||
is_graphics_mode_(false),
|
||||
character_set_base_address_(0xb400),
|
||||
phase_(0),
|
||||
v_sync_start_position_(PAL50VSyncStartPosition), v_sync_end_position_(PAL50VSyncEndPosition),
|
||||
counter_period_(PAL50Period), next_frame_is_sixty_hertz_(false),
|
||||
crt_(new Outputs::CRT::CRT(64*6, 6, Outputs::CRT::DisplayType::PAL50, 1))
|
||||
{
|
||||
// TODO: this is a copy and paste from the Electron; factor out.
|
||||
ram_(memory),
|
||||
frame_counter_(0), counter_(0),
|
||||
is_graphics_mode_(false),
|
||||
character_set_base_address_(0xb400),
|
||||
v_sync_start_position_(PAL50VSyncStartPosition), v_sync_end_position_(PAL50VSyncEndPosition),
|
||||
counter_period_(PAL50Period), next_frame_is_sixty_hertz_(false),
|
||||
crt_(new Outputs::CRT::CRT(64*6, 6, Outputs::CRT::DisplayType::PAL50, 2)) {
|
||||
crt_->set_rgb_sampling_function(
|
||||
"vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)"
|
||||
"{"
|
||||
"uint texValue = texture(sampler, coordinate).r;"
|
||||
"texValue >>= 4 - (int(icoordinate.x * 8) & 4);"
|
||||
"return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));"
|
||||
"}");
|
||||
crt_->set_composite_sampling_function(
|
||||
"float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)"
|
||||
"{"
|
||||
"uint texValue = uint(dot(texture(sampler, coordinate).rg, uvec2(1, 256)));"
|
||||
"uint iPhase = uint((phase + 3.141592654 + 0.39269908175) * 2.0 / 3.141592654) & 3u;"
|
||||
"texValue = (texValue >> (4u*(3u - iPhase))) & 15u;"
|
||||
"return (float(texValue) - 4.0) / 20.0;"
|
||||
"}"
|
||||
);
|
||||
crt_->set_composite_function_type(Outputs::CRT::CRT::CompositeSourceType::DiscreteFourSamplesPerCycle, 0.0f);
|
||||
|
||||
crt_->set_output_device(Outputs::CRT::Television);
|
||||
set_output_device(Outputs::CRT::Television);
|
||||
crt_->set_visible_area(crt_->get_rect_for_area(50, 224, 16 * 6, 40 * 6, 4.0f / 3.0f));
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt()
|
||||
{
|
||||
void VideoOutput::set_output_device(Outputs::CRT::OutputDevice output_device) {
|
||||
output_device_ = output_device;
|
||||
crt_->set_output_device(output_device);
|
||||
}
|
||||
|
||||
void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom) {
|
||||
for(size_t c = 0; c < 8; c++) {
|
||||
size_t index = (c << 2);
|
||||
uint16_t rom_value = (uint16_t)(((uint16_t)rom[index] << 8) | (uint16_t)rom[index+1]);
|
||||
rom_value = (rom_value & 0xff00) | ((rom_value >> 4)&0x000f) | ((rom_value << 4)&0x00f0);
|
||||
colour_forms_[c] = rom_value;
|
||||
}
|
||||
|
||||
// check for big endianness and byte swap if required
|
||||
uint16_t test_value = 0x0001;
|
||||
if(*(uint8_t *)&test_value != 0x01) {
|
||||
for(size_t c = 0; c < 8; c++) {
|
||||
colour_forms_[c] = (uint16_t)((colour_forms_[c] >> 8) | (colour_forms_[c] << 8));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt() {
|
||||
return crt_;
|
||||
}
|
||||
|
||||
void VideoOutput::run_for_cycles(int number_of_cycles)
|
||||
{
|
||||
void VideoOutput::run_for_cycles(int number_of_cycles) {
|
||||
// Vertical: 0–39: pixels; otherwise blank; 48–53 sync, 54–56 colour burst
|
||||
// Horizontal: 0–223: pixels; otherwise blank; 256–259 sync
|
||||
|
||||
#define clamp(action) \
|
||||
if(cycles_run_for <= number_of_cycles) { action; } else cycles_run_for = number_of_cycles;
|
||||
|
||||
while(number_of_cycles)
|
||||
{
|
||||
while(number_of_cycles) {
|
||||
int h_counter = counter_ & 63;
|
||||
int cycles_run_for = 0;
|
||||
|
||||
if(counter_ >= v_sync_start_position_ && counter_ < v_sync_end_position_)
|
||||
{
|
||||
if(counter_ >= v_sync_start_position_ && counter_ < v_sync_end_position_) {
|
||||
// this is a sync line
|
||||
cycles_run_for = v_sync_end_position_ - counter_;
|
||||
clamp(crt_->output_sync((unsigned int)(v_sync_end_position_ - v_sync_start_position_) * 6));
|
||||
}
|
||||
else if(counter_ < 224*64 && h_counter < 40)
|
||||
{
|
||||
} else if(counter_ < 224*64 && h_counter < 40) {
|
||||
// this is a pixel line
|
||||
if(!h_counter)
|
||||
{
|
||||
ink_ = 0xff;
|
||||
paper_ = 0x00;
|
||||
if(!h_counter) {
|
||||
ink_ = 0x7;
|
||||
paper_ = 0x0;
|
||||
use_alternative_character_set_ = use_double_height_characters_ = blink_text_ = false;
|
||||
set_character_set_base_address();
|
||||
phase_ += 64;
|
||||
pixel_target_ = crt_->allocate_write_area(120);
|
||||
pixel_target_ = (uint16_t *)crt_->allocate_write_area(240);
|
||||
|
||||
if(!counter_)
|
||||
{
|
||||
phase_ += 128; // TODO: incorporate all the lines that were missed
|
||||
if(!counter_) {
|
||||
frame_counter_++;
|
||||
|
||||
v_sync_start_position_ = next_frame_is_sixty_hertz_ ? PAL60VSyncStartPosition : PAL50VSyncStartPosition;
|
||||
@@ -95,51 +113,48 @@ void VideoOutput::run_for_cycles(int number_of_cycles)
|
||||
int character_base_address = 0xbb80 + (counter_ >> 9) * 40;
|
||||
uint8_t blink_mask = (blink_text_ && (frame_counter_&32)) ? 0x00 : 0xff;
|
||||
|
||||
while(columns--)
|
||||
{
|
||||
while(columns--) {
|
||||
uint8_t pixels, control_byte;
|
||||
|
||||
if(is_graphics_mode_ && counter_ < 200*64)
|
||||
{
|
||||
if(is_graphics_mode_ && counter_ < 200*64) {
|
||||
control_byte = pixels = ram_[pixel_base_address + h_counter];
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
int address = character_base_address + h_counter;
|
||||
control_byte = ram_[address];
|
||||
int line = use_double_height_characters_ ? ((counter_ >> 7) & 7) : ((counter_ >> 6) & 7);
|
||||
pixels = ram_[character_set_base_address_ + (control_byte&127) * 8 + line];
|
||||
}
|
||||
|
||||
uint8_t inverse_mask = (control_byte & 0x80) ? 0x77 : 0x00;
|
||||
uint8_t inverse_mask = (control_byte & 0x80) ? 0x7 : 0x0;
|
||||
pixels &= blink_mask;
|
||||
|
||||
if(control_byte & 0x60)
|
||||
{
|
||||
if(pixel_target_)
|
||||
{
|
||||
uint8_t colours[2] = {
|
||||
(uint8_t)(paper_ ^ inverse_mask),
|
||||
(uint8_t)(ink_ ^ inverse_mask),
|
||||
};
|
||||
|
||||
pixel_target_[0] = (colours[(pixels >> 4)&1] & 0x0f) | (colours[(pixels >> 5)&1] & 0xf0);
|
||||
pixel_target_[1] = (colours[(pixels >> 2)&1] & 0x0f) | (colours[(pixels >> 3)&1] & 0xf0);
|
||||
pixel_target_[2] = (colours[(pixels >> 0)&1] & 0x0f) | (colours[(pixels >> 1)&1] & 0xf0);
|
||||
if(control_byte & 0x60) {
|
||||
if(pixel_target_) {
|
||||
uint16_t colours[2];
|
||||
if(output_device_ == Outputs::CRT::Monitor) {
|
||||
colours[0] = (uint8_t)(paper_ ^ inverse_mask);
|
||||
colours[1] = (uint8_t)(ink_ ^ inverse_mask);
|
||||
} else {
|
||||
colours[0] = colour_forms_[paper_ ^ inverse_mask];
|
||||
colours[1] = colour_forms_[ink_ ^ inverse_mask];
|
||||
}
|
||||
pixel_target_[0] = colours[(pixels >> 5)&1];
|
||||
pixel_target_[1] = colours[(pixels >> 4)&1];
|
||||
pixel_target_[2] = colours[(pixels >> 3)&1];
|
||||
pixel_target_[3] = colours[(pixels >> 2)&1];
|
||||
pixel_target_[4] = colours[(pixels >> 1)&1];
|
||||
pixel_target_[5] = colours[(pixels >> 0)&1];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch(control_byte & 0x1f)
|
||||
{
|
||||
case 0x00: ink_ = 0x00; break;
|
||||
case 0x01: ink_ = 0x44; break;
|
||||
case 0x02: ink_ = 0x22; break;
|
||||
case 0x03: ink_ = 0x66; break;
|
||||
case 0x04: ink_ = 0x11; break;
|
||||
case 0x05: ink_ = 0x55; break;
|
||||
case 0x06: ink_ = 0x33; break;
|
||||
case 0x07: ink_ = 0x77; break;
|
||||
} else {
|
||||
switch(control_byte & 0x1f) {
|
||||
case 0x00: ink_ = 0x0; break;
|
||||
case 0x01: ink_ = 0x4; break;
|
||||
case 0x02: ink_ = 0x2; break;
|
||||
case 0x03: ink_ = 0x6; break;
|
||||
case 0x04: ink_ = 0x1; break;
|
||||
case 0x05: ink_ = 0x5; break;
|
||||
case 0x06: ink_ = 0x3; break;
|
||||
case 0x07: ink_ = 0x7; break;
|
||||
|
||||
case 0x08: case 0x09: case 0x0a: case 0x0b:
|
||||
case 0x0c: case 0x0d: case 0x0e: case 0x0f:
|
||||
@@ -149,14 +164,14 @@ void VideoOutput::run_for_cycles(int number_of_cycles)
|
||||
set_character_set_base_address();
|
||||
break;
|
||||
|
||||
case 0x10: paper_ = 0x00; break;
|
||||
case 0x11: paper_ = 0x44; break;
|
||||
case 0x12: paper_ = 0x22; break;
|
||||
case 0x13: paper_ = 0x66; break;
|
||||
case 0x14: paper_ = 0x11; break;
|
||||
case 0x15: paper_ = 0x55; break;
|
||||
case 0x16: paper_ = 0x33; break;
|
||||
case 0x17: paper_ = 0x77; break;
|
||||
case 0x10: paper_ = 0x0; break;
|
||||
case 0x11: paper_ = 0x4; break;
|
||||
case 0x12: paper_ = 0x2; break;
|
||||
case 0x13: paper_ = 0x6; break;
|
||||
case 0x14: paper_ = 0x1; break;
|
||||
case 0x15: paper_ = 0x5; break;
|
||||
case 0x16: paper_ = 0x3; break;
|
||||
case 0x17: paper_ = 0x7; break;
|
||||
|
||||
case 0x18: case 0x19: case 0x1a: case 0x1b:
|
||||
case 0x1c: case 0x1d: case 0x1e: case 0x1f:
|
||||
@@ -166,40 +181,35 @@ void VideoOutput::run_for_cycles(int number_of_cycles)
|
||||
|
||||
default: break;
|
||||
}
|
||||
if(pixel_target_) pixel_target_[0] = pixel_target_[1] = pixel_target_[2] = (uint8_t)(paper_ ^ inverse_mask);
|
||||
if(pixel_target_) {
|
||||
pixel_target_[0] = pixel_target_[1] =
|
||||
pixel_target_[2] = pixel_target_[3] =
|
||||
pixel_target_[4] = pixel_target_[5] =
|
||||
(output_device_ == Outputs::CRT::Monitor) ? paper_ ^ inverse_mask : colour_forms_[paper_ ^ inverse_mask];
|
||||
}
|
||||
}
|
||||
if(pixel_target_) pixel_target_ += 3;
|
||||
if(pixel_target_) pixel_target_ += 6;
|
||||
h_counter++;
|
||||
}
|
||||
|
||||
if(h_counter == 40)
|
||||
{
|
||||
crt_->output_data(40 * 6, 2);
|
||||
if(h_counter == 40) {
|
||||
crt_->output_data(40 * 6, 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// this is a blank line (or the equivalent part of a pixel line)
|
||||
if(h_counter < 48)
|
||||
{
|
||||
if(h_counter < 48) {
|
||||
cycles_run_for = 48 - h_counter;
|
||||
clamp(
|
||||
int period = (counter_ < 224*64) ? 8 : 48;
|
||||
crt_->output_blank((unsigned int)period * 6);
|
||||
);
|
||||
}
|
||||
else if(h_counter < 54)
|
||||
{
|
||||
} else if(h_counter < 54) {
|
||||
cycles_run_for = 54 - h_counter;
|
||||
clamp(crt_->output_sync(6 * 6));
|
||||
}
|
||||
else if(h_counter < 56)
|
||||
{
|
||||
} else if(h_counter < 56) {
|
||||
cycles_run_for = 56 - h_counter;
|
||||
clamp(crt_->output_colour_burst(2 * 6, phase_, 128));
|
||||
}
|
||||
else
|
||||
{
|
||||
clamp(crt_->output_default_colour_burst(2 * 6));
|
||||
} else {
|
||||
cycles_run_for = 64 - h_counter;
|
||||
clamp(crt_->output_blank(8 * 6));
|
||||
}
|
||||
@@ -210,8 +220,7 @@ void VideoOutput::run_for_cycles(int number_of_cycles)
|
||||
}
|
||||
}
|
||||
|
||||
void VideoOutput::set_character_set_base_address()
|
||||
{
|
||||
void VideoOutput::set_character_set_base_address() {
|
||||
if(is_graphics_mode_) character_set_base_address_ = use_alternative_character_set_ ? 0x9c00 : 0x9800;
|
||||
else character_set_base_address_ = use_alternative_character_set_ ? 0xb800 : 0xb400;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ class VideoOutput {
|
||||
VideoOutput(uint8_t *memory);
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt();
|
||||
void run_for_cycles(int number_of_cycles);
|
||||
void set_colour_rom(const std::vector<uint8_t> &rom);
|
||||
void set_output_device(Outputs::CRT::OutputDevice output_device);
|
||||
|
||||
private:
|
||||
uint8_t *ram_;
|
||||
@@ -27,8 +29,10 @@ class VideoOutput {
|
||||
int counter_, frame_counter_;
|
||||
int v_sync_start_position_, v_sync_end_position_, counter_period_;
|
||||
|
||||
// Output target
|
||||
uint8_t *pixel_target_;
|
||||
// Output target and device
|
||||
uint16_t *pixel_target_;
|
||||
uint16_t colour_forms_[8];
|
||||
Outputs::CRT::OutputDevice output_device_;
|
||||
|
||||
// Registers
|
||||
uint8_t ink_, paper_;
|
||||
@@ -41,8 +45,6 @@ class VideoOutput {
|
||||
bool use_alternative_character_set_;
|
||||
bool use_double_height_characters_;
|
||||
bool blink_text_;
|
||||
|
||||
uint8_t phase_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -12,76 +12,61 @@
|
||||
using namespace Utility;
|
||||
|
||||
Typer::Typer(const char *string, int delay, int frequency, Delegate *delegate) :
|
||||
counter_(-delay), frequency_(frequency), string_pointer_(0), delegate_(delegate), phase_(0)
|
||||
{
|
||||
counter_(-delay), frequency_(frequency), string_pointer_(0), delegate_(delegate), phase_(0) {
|
||||
size_t string_size = strlen(string) + 3;
|
||||
string_ = (char *)malloc(string_size);
|
||||
snprintf(string_, strlen(string) + 3, "%c%s%c", Typer::BeginString, string, Typer::EndString);
|
||||
}
|
||||
|
||||
void Typer::update(int duration)
|
||||
{
|
||||
if(string_)
|
||||
{
|
||||
if(counter_ < 0 && counter_ + duration >= 0)
|
||||
{
|
||||
if(!type_next_character())
|
||||
{
|
||||
void Typer::update(int duration) {
|
||||
if(string_) {
|
||||
if(counter_ < 0 && counter_ + duration >= 0) {
|
||||
if(!type_next_character()) {
|
||||
delegate_->typer_reset(this);
|
||||
}
|
||||
}
|
||||
|
||||
counter_ += duration;
|
||||
while(string_ && counter_ > frequency_)
|
||||
{
|
||||
while(string_ && counter_ > frequency_) {
|
||||
counter_ -= frequency_;
|
||||
if(!type_next_character())
|
||||
{
|
||||
if(!type_next_character()) {
|
||||
delegate_->typer_reset(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Typer::type_next_character()
|
||||
{
|
||||
bool Typer::type_next_character() {
|
||||
if(string_ == nullptr) return false;
|
||||
|
||||
if(delegate_->typer_set_next_character(this, string_[string_pointer_], phase_))
|
||||
{
|
||||
if(delegate_->typer_set_next_character(this, string_[string_pointer_], phase_)) {
|
||||
phase_ = 0;
|
||||
if(!string_[string_pointer_])
|
||||
{
|
||||
if(!string_[string_pointer_]) {
|
||||
free(string_);
|
||||
string_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
string_pointer_++;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
phase_++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Typer::~Typer()
|
||||
{
|
||||
Typer::~Typer() {
|
||||
free(string_);
|
||||
}
|
||||
|
||||
#pragma mark - Delegate
|
||||
|
||||
bool Typer::Delegate::typer_set_next_character(Utility::Typer *typer, char character, int phase)
|
||||
{
|
||||
bool Typer::Delegate::typer_set_next_character(Utility::Typer *typer, char character, int phase) {
|
||||
uint16_t *sequence = sequence_for_character(typer, character);
|
||||
if(!sequence) return true;
|
||||
|
||||
if(!phase) clear_all_keys();
|
||||
else
|
||||
{
|
||||
else {
|
||||
set_key_state(sequence[phase - 1], true);
|
||||
return sequence[phase] == Typer::Delegate::EndSequence;
|
||||
}
|
||||
@@ -89,7 +74,6 @@ bool Typer::Delegate::typer_set_next_character(Utility::Typer *typer, char chara
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t *Typer::Delegate::sequence_for_character(Typer *typer, char character)
|
||||
{
|
||||
uint16_t *Typer::Delegate::sequence_for_character(Typer *typer, char character) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -45,13 +45,11 @@ class Typer {
|
||||
|
||||
class TypeRecipient: public Typer::Delegate {
|
||||
public:
|
||||
void set_typer_for_string(const char *string)
|
||||
{
|
||||
void set_typer_for_string(const char *string) {
|
||||
typer_.reset(new Typer(string, get_typer_delay(), get_typer_frequency(), this));
|
||||
}
|
||||
|
||||
void typer_reset(Typer *typer)
|
||||
{
|
||||
void typer_reset(Typer *typer) {
|
||||
clear_all_keys();
|
||||
typer_.reset();
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
//
|
||||
// CRC.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/09/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "CRC.hpp"
|
||||
|
||||
using namespace NumberTheory;
|
||||
@@ -16,22 +16,27 @@ namespace NumberTheory {
|
||||
class CRC16 {
|
||||
public:
|
||||
CRC16(uint16_t polynomial, uint16_t reset_value) :
|
||||
reset_value_(reset_value), value_(reset_value), polynomial_(polynomial) {}
|
||||
|
||||
inline void reset() { value_ = reset_value_; }
|
||||
inline void add(uint8_t value) {
|
||||
// TODO: go table based
|
||||
value_ ^= (uint16_t)value << 8;
|
||||
for(int c = 0; c < 8; c++)
|
||||
{
|
||||
uint16_t exclusive_or = (value_&0x8000) ? polynomial_ : 0x0000;
|
||||
value_ = (uint16_t)(value_ << 1) ^ exclusive_or;
|
||||
reset_value_(reset_value), value_(reset_value) {
|
||||
for(int c = 0; c < 256; c++) {
|
||||
uint16_t shift_value = (uint16_t)(c << 8);
|
||||
for(int b = 0; b < 8; b++) {
|
||||
uint16_t exclusive_or = (shift_value&0x8000) ? polynomial : 0x0000;
|
||||
shift_value = (uint16_t)(shift_value << 1) ^ exclusive_or;
|
||||
}
|
||||
xor_table[c] = (uint16_t)shift_value;
|
||||
}
|
||||
}
|
||||
inline uint16_t get_value() { return value_; }
|
||||
|
||||
inline void reset() { value_ = reset_value_; }
|
||||
inline void add(uint8_t byte) {
|
||||
value_ = (uint16_t)((value_ << 8) ^ xor_table[(value_ >> 8) ^ byte]);
|
||||
}
|
||||
inline uint16_t get_value() const { return value_; }
|
||||
inline void set_value(uint16_t value) { value_ = value; }
|
||||
|
||||
private:
|
||||
uint16_t reset_value_, polynomial_;
|
||||
const uint16_t reset_value_;
|
||||
uint16_t xor_table[256];
|
||||
uint16_t value_;
|
||||
};
|
||||
|
||||
|
||||
@@ -10,14 +10,17 @@
|
||||
4B049CDD1DA3C82F00322067 /* BCDTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B049CDC1DA3C82F00322067 /* BCDTest.swift */; };
|
||||
4B0BE4281D3481E700D5256B /* DigitalPhaseLockedLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0BE4261D3481E700D5256B /* DigitalPhaseLockedLoop.cpp */; };
|
||||
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */; };
|
||||
4B121F951E05E66800BFDA12 /* PCMPatchedTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */; };
|
||||
4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */; };
|
||||
4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; };
|
||||
4B14145D1B5887A600E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; };
|
||||
4B14145E1B5887AA00E04248 /* CPU6502AllRAM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414591B58879D00E04248 /* CPU6502AllRAM.cpp */; };
|
||||
4B1414601B58885000E04248 /* WolfgangLorenzTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */; };
|
||||
4B1414621B58888700E04248 /* KlausDormannTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414611B58888700E04248 /* KlausDormannTests.swift */; };
|
||||
4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B1D08051E0F7A1100763741 /* TimeTests.mm */; };
|
||||
4B1E85751D170228001EF87D /* Typer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85731D170228001EF87D /* Typer.cpp */; };
|
||||
4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85801D176468001EF87D /* 6532Tests.swift */; };
|
||||
4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2409531C45AB05004DA684 /* Speaker.cpp */; };
|
||||
4B1EDB451E39A0AC009D6819 /* chip.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B1EDB431E39A0AC009D6819 /* chip.png */; };
|
||||
4B2A332A1DB8544D002876E3 /* MemoryFuzzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A33281DB8544D002876E3 /* MemoryFuzzer.cpp */; };
|
||||
4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2A332B1DB86821002876E3 /* OricOptions.xib */; };
|
||||
4B2A332F1DB86869002876E3 /* OricOptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A332E1DB86869002876E3 /* OricOptionsPanel.swift */; };
|
||||
@@ -26,8 +29,10 @@
|
||||
4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */; };
|
||||
4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539C1D117D36003C6002 /* CSElectron.mm */; };
|
||||
4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539E1D117D36003C6002 /* CSVic20.mm */; };
|
||||
4B2AF8691E513FC20027EE29 /* TIATests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2AF8681E513FC20027EE29 /* TIATests.mm */; };
|
||||
4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */; };
|
||||
4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */; };
|
||||
4B2C45421E3C3896002A2389 /* cartridge.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B2C45411E3C3896002A2389 /* cartridge.png */; };
|
||||
4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; };
|
||||
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D9B1C3A070400138695 /* Electron.cpp */; };
|
||||
4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B30512B1D989E2200B4FED8 /* Drive.cpp */; };
|
||||
@@ -39,6 +44,7 @@
|
||||
4B3BA0CF1D318B44005DD7A7 /* MOS6522Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C91D318B44005DD7A7 /* MOS6522Bridge.mm */; };
|
||||
4B3BA0D01D318B44005DD7A7 /* MOS6532Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0CB1D318B44005DD7A7 /* MOS6532Bridge.mm */; };
|
||||
4B3BA0D11D318B44005DD7A7 /* TestMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0CD1D318B44005DD7A7 /* TestMachine.mm */; };
|
||||
4B3F1B461E0388D200DB26EE /* PCMPatchedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */; };
|
||||
4B44EBF51DC987AF00A7820C /* AllSuiteA.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */; };
|
||||
4B44EBF71DC9883B00A7820C /* 6502_functional_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */; };
|
||||
4B44EBF91DC9898E00A7820C /* BCDTEST_beeb in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */; };
|
||||
@@ -62,6 +68,10 @@
|
||||
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; };
|
||||
4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; };
|
||||
4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */; };
|
||||
4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7913CA1DFCD80E00175A82 /* Video.cpp */; };
|
||||
4B79E4441E3AF38600141F11 /* cassette.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4411E3AF38600141F11 /* cassette.png */; };
|
||||
4B79E4451E3AF38600141F11 /* floppy35.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4421E3AF38600141F11 /* floppy35.png */; };
|
||||
4B79E4461E3AF38600141F11 /* floppy525.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4431E3AF38600141F11 /* floppy525.png */; };
|
||||
4B8805F01DCFC99C003085B1 /* Acorn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805EE1DCFC99C003085B1 /* Acorn.cpp */; };
|
||||
4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F21DCFD22A003085B1 /* Commodore.cpp */; };
|
||||
4B8805F71DCFF6C9003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F51DCFF6C9003085B1 /* Commodore.cpp */; };
|
||||
@@ -75,6 +85,8 @@
|
||||
4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */; };
|
||||
4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2261DA1DE2D0090D3CE /* NSBundle+DataResource.m */; };
|
||||
4B8FE2291DA1EDDF0090D3CE /* ElectronOptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2281DA1EDDF0090D3CE /* ElectronOptionsPanel.swift */; };
|
||||
4B924E991E74D22700B76AF1 /* AtariStaticAnalyserTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */; };
|
||||
4B9252CE1E74D28200B76AF1 /* Atari ROMs in Resources */ = {isa = PBXBuildFile; fileRef = 4B9252CD1E74D28200B76AF1 /* Atari ROMs */; };
|
||||
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */; };
|
||||
4B96F7221D75119A0058BB2D /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B96F7201D75119A0058BB2D /* Tape.cpp */; };
|
||||
4B9CCDA11DA279CA0098B625 /* Vic20OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9CCDA01DA279CA0098B625 /* Vic20OptionsPanel.swift */; };
|
||||
@@ -349,6 +361,7 @@
|
||||
4BB299F71B587D8400A49093 /* txan in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298EB1B587D8400A49093 /* txan */; };
|
||||
4BB299F81B587D8400A49093 /* txsn in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298EC1B587D8400A49093 /* txsn */; };
|
||||
4BB299F91B587D8400A49093 /* tyan in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298ED1B587D8400A49093 /* tyan */; };
|
||||
4BB2A9AF1E13367E001A5C23 /* CRCTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */; };
|
||||
4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */; };
|
||||
4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */; };
|
||||
4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EA11B587A5100552FC2 /* AppDelegate.swift */; };
|
||||
@@ -380,9 +393,12 @@
|
||||
4BCF1FAB1DADD41B0039D2E7 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1FA91DADD41B0039D2E7 /* StaticAnalyser.cpp */; };
|
||||
4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */; };
|
||||
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD468F51D8DF41D0084958B /* 1770.cpp */; };
|
||||
4BD4A8CD1E077E8A0020D856 /* PCMSegment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F961E060CF000BFDA12 /* PCMSegment.cpp */; };
|
||||
4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */; };
|
||||
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; };
|
||||
4BD69F941D98760000243FE1 /* AcornADF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD69F921D98760000243FE1 /* AcornADF.cpp */; };
|
||||
4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE77A2C1D84ADFB00BC3827 /* File.cpp */; };
|
||||
4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE7C9161E3D397100A5496D /* TIA.cpp */; };
|
||||
4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA525D1DF33323007E74F2 /* Tape.cpp */; };
|
||||
4BEA52631DF339D7007E74F2 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52611DF339D7007E74F2 /* Speaker.cpp */; };
|
||||
4BEA52661DF3472B007E74F2 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52641DF3472B007E74F2 /* Speaker.cpp */; };
|
||||
@@ -392,10 +408,8 @@
|
||||
4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; };
|
||||
4BF1354C1D6D2C300054B2EA /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF1354A1D6D2C300054B2EA /* StaticAnalyser.cpp */; };
|
||||
4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8295B1D8F048B001BAE39 /* MFM.cpp */; };
|
||||
4BF829601D8F3C87001BAE39 /* CRC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8295E1D8F3C87001BAE39 /* CRC.cpp */; };
|
||||
4BF829631D8F536B001BAE39 /* SSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829611D8F536B001BAE39 /* SSD.cpp */; };
|
||||
4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829641D8F732B001BAE39 /* Disk.cpp */; };
|
||||
4BF829691D8F7361001BAE39 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829671D8F7361001BAE39 /* File.cpp */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -423,6 +437,10 @@
|
||||
4B0BE4271D3481E700D5256B /* DigitalPhaseLockedLoop.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DigitalPhaseLockedLoop.hpp; sourceTree = "<group>"; };
|
||||
4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = "<group>"; };
|
||||
4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = "<group>"; };
|
||||
4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMPatchedTrackTests.mm; sourceTree = "<group>"; };
|
||||
4B121F961E060CF000BFDA12 /* PCMSegment.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMSegment.cpp; sourceTree = "<group>"; };
|
||||
4B121F971E060CF000BFDA12 /* PCMSegment.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMSegment.hpp; sourceTree = "<group>"; };
|
||||
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMSegmentEventSourceTests.mm; sourceTree = "<group>"; };
|
||||
4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ClockSignal-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
4B1414571B58879D00E04248 /* CPU6502.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CPU6502.cpp; sourceTree = "<group>"; };
|
||||
4B1414581B58879D00E04248 /* CPU6502.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CPU6502.hpp; sourceTree = "<group>"; };
|
||||
@@ -430,11 +448,12 @@
|
||||
4B14145A1B58879D00E04248 /* CPU6502AllRAM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CPU6502AllRAM.hpp; sourceTree = "<group>"; };
|
||||
4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WolfgangLorenzTests.swift; sourceTree = "<group>"; };
|
||||
4B1414611B58888700E04248 /* KlausDormannTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KlausDormannTests.swift; sourceTree = "<group>"; };
|
||||
4B1D08051E0F7A1100763741 /* TimeTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TimeTests.mm; sourceTree = "<group>"; };
|
||||
4B1E85731D170228001EF87D /* Typer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Typer.cpp; 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>"; };
|
||||
4B1E85801D176468001EF87D /* 6532Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6532Tests.swift; sourceTree = "<group>"; };
|
||||
4B2409531C45AB05004DA684 /* Speaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Speaker.cpp; path = ../../Outputs/Speaker.cpp; sourceTree = "<group>"; };
|
||||
4B1EDB431E39A0AC009D6819 /* chip.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = chip.png; sourceTree = "<group>"; };
|
||||
4B2409541C45AB05004DA684 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Speaker.hpp; path = ../../Outputs/Speaker.hpp; sourceTree = "<group>"; };
|
||||
4B24095A1C45DF85004DA684 /* Stepper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Stepper.hpp; sourceTree = "<group>"; };
|
||||
4B2A33281DB8544D002876E3 /* MemoryFuzzer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryFuzzer.cpp; sourceTree = "<group>"; };
|
||||
@@ -454,10 +473,12 @@
|
||||
4B2A539C1D117D36003C6002 /* CSElectron.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSElectron.mm; sourceTree = "<group>"; };
|
||||
4B2A539D1D117D36003C6002 /* CSVic20.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSVic20.h; sourceTree = "<group>"; };
|
||||
4B2A539E1D117D36003C6002 /* CSVic20.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSVic20.mm; sourceTree = "<group>"; };
|
||||
4B2AF8681E513FC20027EE29 /* TIATests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TIATests.mm; sourceTree = "<group>"; };
|
||||
4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapePRG.cpp; sourceTree = "<group>"; };
|
||||
4B2BFC5E1D613E0200BA3AA9 /* TapePRG.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapePRG.hpp; sourceTree = "<group>"; };
|
||||
4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Oric/Video.cpp; sourceTree = "<group>"; };
|
||||
4B2BFDB11DAEF5FF001A68B8 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = Oric/Video.hpp; sourceTree = "<group>"; };
|
||||
4B2C45411E3C3896002A2389 /* cartridge.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cartridge.png; sourceTree = "<group>"; };
|
||||
4B2E2D971C3A06EC00138695 /* Atari2600.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Atari2600.cpp; sourceTree = "<group>"; };
|
||||
4B2E2D981C3A06EC00138695 /* Atari2600.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Atari2600.hpp; sourceTree = "<group>"; };
|
||||
4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Atari2600Inputs.h; sourceTree = "<group>"; };
|
||||
@@ -481,6 +502,8 @@
|
||||
4B3BA0CB1D318B44005DD7A7 /* MOS6532Bridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MOS6532Bridge.mm; sourceTree = "<group>"; };
|
||||
4B3BA0CC1D318B44005DD7A7 /* TestMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestMachine.h; sourceTree = "<group>"; };
|
||||
4B3BA0CD1D318B44005DD7A7 /* TestMachine.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TestMachine.mm; sourceTree = "<group>"; };
|
||||
4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMPatchedTrack.cpp; sourceTree = "<group>"; };
|
||||
4B3F1B451E0388D200DB26EE /* PCMPatchedTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMPatchedTrack.hpp; sourceTree = "<group>"; };
|
||||
4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = AllSuiteA.bin; path = AllSuiteA/AllSuiteA.bin; sourceTree = "<group>"; };
|
||||
4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 6502_functional_test.bin; path = "Klaus Dormann/6502_functional_test.bin"; sourceTree = "<group>"; };
|
||||
4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */ = {isa = PBXFileReference; lastKnownFileType = file; name = BCDTEST_beeb; path = BCDTest/BCDTEST_beeb; sourceTree = "<group>"; };
|
||||
@@ -496,7 +519,6 @@
|
||||
4B4DC82A1D2C27A4003C5BF8 /* SerialBus.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SerialBus.hpp; sourceTree = "<group>"; };
|
||||
4B5073051DDD3B9400C48FBD /* ArrayBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ArrayBuilder.cpp; sourceTree = "<group>"; };
|
||||
4B5073061DDD3B9400C48FBD /* ArrayBuilder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ArrayBuilder.hpp; sourceTree = "<group>"; };
|
||||
4B5073081DDFCFDF00C48FBD /* ArrayBuilderTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArrayBuilderTests.h; sourceTree = "<group>"; };
|
||||
4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ArrayBuilderTests.mm; sourceTree = "<group>"; };
|
||||
4B55CE5B1C3B7D6F0093A61B /* CSOpenGLView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSOpenGLView.h; sourceTree = "<group>"; };
|
||||
4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSOpenGLView.m; sourceTree = "<group>"; };
|
||||
@@ -522,6 +544,11 @@
|
||||
4B69FB451C4D950F00B5F0AA /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
|
||||
4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DiskController.cpp; 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>"; };
|
||||
4B7913CB1DFCD80E00175A82 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = Electron/Video.hpp; sourceTree = "<group>"; };
|
||||
4B79E4411E3AF38600141F11 /* cassette.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cassette.png; sourceTree = "<group>"; };
|
||||
4B79E4421E3AF38600141F11 /* floppy35.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = floppy35.png; sourceTree = "<group>"; };
|
||||
4B79E4431E3AF38600141F11 /* floppy525.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = floppy525.png; sourceTree = "<group>"; };
|
||||
4B8805EE1DCFC99C003085B1 /* Acorn.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Acorn.cpp; path = Parsers/Acorn.cpp; sourceTree = "<group>"; };
|
||||
4B8805EF1DCFC99C003085B1 /* Acorn.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Acorn.hpp; path = Parsers/Acorn.hpp; sourceTree = "<group>"; };
|
||||
4B8805F21DCFD22A003085B1 /* Commodore.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Commodore.cpp; path = Parsers/Commodore.cpp; sourceTree = "<group>"; };
|
||||
@@ -542,6 +569,8 @@
|
||||
4B8FE2251DA1DE2D0090D3CE /* NSBundle+DataResource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+DataResource.h"; sourceTree = "<group>"; };
|
||||
4B8FE2261DA1DE2D0090D3CE /* NSBundle+DataResource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+DataResource.m"; sourceTree = "<group>"; };
|
||||
4B8FE2281DA1EDDF0090D3CE /* ElectronOptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ElectronOptionsPanel.swift; sourceTree = "<group>"; };
|
||||
4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AtariStaticAnalyserTests.mm; sourceTree = "<group>"; };
|
||||
4B9252CD1E74D28200B76AF1 /* Atari ROMs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "Atari ROMs"; sourceTree = "<group>"; };
|
||||
4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6502TimingTests.swift; sourceTree = "<group>"; };
|
||||
4B96F7201D75119A0058BB2D /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = ../../StaticAnalyser/Acorn/Tape.cpp; sourceTree = "<group>"; };
|
||||
4B96F7211D75119A0058BB2D /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = ../../StaticAnalyser/Acorn/Tape.hpp; sourceTree = "<group>"; };
|
||||
@@ -826,6 +855,7 @@
|
||||
4BB298EB1B587D8400A49093 /* txan */ = {isa = PBXFileReference; lastKnownFileType = file; path = txan; sourceTree = "<group>"; };
|
||||
4BB298EC1B587D8400A49093 /* txsn */ = {isa = PBXFileReference; lastKnownFileType = file; path = txsn; sourceTree = "<group>"; };
|
||||
4BB298ED1B587D8400A49093 /* tyan */ = {isa = PBXFileReference; lastKnownFileType = file; path = tyan; sourceTree = "<group>"; };
|
||||
4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CRCTests.mm; sourceTree = "<group>"; };
|
||||
4BB697C61D4B558F00248BDF /* Factors.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Factors.hpp; path = ../../NumberTheory/Factors.hpp; sourceTree = "<group>"; };
|
||||
4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TimedEventLoop.cpp; sourceTree = "<group>"; };
|
||||
4BB697CA1D4B6D3E00248BDF /* TimedEventLoop.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TimedEventLoop.hpp; sourceTree = "<group>"; };
|
||||
@@ -889,12 +919,15 @@
|
||||
4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
4BD468F51D8DF41D0084958B /* 1770.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = 1770.cpp; path = 1770/1770.cpp; sourceTree = "<group>"; };
|
||||
4BD468F61D8DF41D0084958B /* 1770.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 1770.hpp; path = 1770/1770.hpp; sourceTree = "<group>"; };
|
||||
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMTrackTests.mm; sourceTree = "<group>"; };
|
||||
4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = "<group>"; };
|
||||
4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CSBestEffortUpdater.m; path = Updater/CSBestEffortUpdater.m; sourceTree = "<group>"; };
|
||||
4BD69F921D98760000243FE1 /* AcornADF.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AcornADF.cpp; 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>"; };
|
||||
4BE77A2D1D84ADFB00BC3827 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Commodore/File.hpp; sourceTree = "<group>"; };
|
||||
4BE7C9161E3D397100A5496D /* TIA.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TIA.cpp; sourceTree = "<group>"; };
|
||||
4BE7C9171E3D397100A5496D /* TIA.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TIA.hpp; sourceTree = "<group>"; };
|
||||
4BEA525D1DF33323007E74F2 /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = Electron/Tape.cpp; sourceTree = "<group>"; };
|
||||
4BEA525F1DF333D8007E74F2 /* Tape.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = Electron/Tape.hpp; sourceTree = "<group>"; };
|
||||
4BEA52601DF3343A007E74F2 /* Interrupts.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Interrupts.hpp; path = Electron/Interrupts.hpp; sourceTree = "<group>"; };
|
||||
@@ -903,6 +936,20 @@
|
||||
4BEA52641DF3472B007E74F2 /* Speaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Speaker.cpp; sourceTree = "<group>"; };
|
||||
4BEA52651DF3472B007E74F2 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Speaker.hpp; sourceTree = "<group>"; };
|
||||
4BEA52671DF34909007E74F2 /* PIA.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PIA.hpp; sourceTree = "<group>"; };
|
||||
4BEAC0811E7E0DF800EE56B2 /* Cartridge.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; };
|
||||
4BEAC0821E7E0DF800EE56B2 /* CartridgeActivisionStack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeActivisionStack.hpp; sourceTree = "<group>"; };
|
||||
4BEAC0831E7E0DF800EE56B2 /* CartridgeAtari16k.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeAtari16k.hpp; sourceTree = "<group>"; };
|
||||
4BEAC0841E7E0DF800EE56B2 /* CartridgeAtari32k.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeAtari32k.hpp; sourceTree = "<group>"; };
|
||||
4BEAC0851E7E0DF800EE56B2 /* CartridgeAtari8k.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeAtari8k.hpp; sourceTree = "<group>"; };
|
||||
4BEAC0861E7E0DF800EE56B2 /* CartridgeCBSRAMPlus.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeCBSRAMPlus.hpp; sourceTree = "<group>"; };
|
||||
4BEAC0871E7E0DF800EE56B2 /* CartridgeCommaVid.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeCommaVid.hpp; sourceTree = "<group>"; };
|
||||
4BEAC0881E7E0DF800EE56B2 /* CartridgeMegaBoy.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeMegaBoy.hpp; sourceTree = "<group>"; };
|
||||
4BEAC0891E7E0DF800EE56B2 /* CartridgeMNetwork.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeMNetwork.hpp; sourceTree = "<group>"; };
|
||||
4BEAC08A1E7E0DF800EE56B2 /* CartridgeParkerBros.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeParkerBros.hpp; sourceTree = "<group>"; };
|
||||
4BEAC08B1E7E0DF800EE56B2 /* CartridgeTigervision.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeTigervision.hpp; sourceTree = "<group>"; };
|
||||
4BEAC08C1E7E0DF800EE56B2 /* CartridgeUnpaged.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeUnpaged.hpp; sourceTree = "<group>"; };
|
||||
4BEAC08D1E7E0E1A00EE56B2 /* Bus.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Bus.hpp; sourceTree = "<group>"; };
|
||||
4BEAC08E1E7E110500EE56B2 /* CartridgePitfall2.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CartridgePitfall2.hpp; sourceTree = "<group>"; };
|
||||
4BEE0A6A1D72496600532C7B /* Cartridge.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Cartridge.cpp; sourceTree = "<group>"; };
|
||||
4BEE0A6B1D72496600532C7B /* Cartridge.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; };
|
||||
4BEE0A6D1D72496600532C7B /* PRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PRG.cpp; sourceTree = "<group>"; };
|
||||
@@ -914,13 +961,11 @@
|
||||
4BF1354B1D6D2C300054B2EA /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
4BF8295B1D8F048B001BAE39 /* MFM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MFM.cpp; path = Encodings/MFM.cpp; sourceTree = "<group>"; };
|
||||
4BF8295C1D8F048B001BAE39 /* MFM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = MFM.hpp; path = Encodings/MFM.hpp; sourceTree = "<group>"; };
|
||||
4BF8295E1D8F3C87001BAE39 /* CRC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CRC.cpp; path = ../../NumberTheory/CRC.cpp; sourceTree = "<group>"; };
|
||||
4BF8295F1D8F3C87001BAE39 /* CRC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRC.hpp; path = ../../NumberTheory/CRC.hpp; sourceTree = "<group>"; };
|
||||
4BF829611D8F536B001BAE39 /* SSD.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SSD.cpp; sourceTree = "<group>"; };
|
||||
4BF829621D8F536B001BAE39 /* SSD.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SSD.hpp; sourceTree = "<group>"; };
|
||||
4BF829641D8F732B001BAE39 /* Disk.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Disk.cpp; path = ../../StaticAnalyser/Acorn/Disk.cpp; sourceTree = "<group>"; };
|
||||
4BF829651D8F732B001BAE39 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Disk.hpp; path = ../../StaticAnalyser/Acorn/Disk.hpp; sourceTree = "<group>"; };
|
||||
4BF829671D8F7361001BAE39 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Acorn/File.cpp; sourceTree = "<group>"; };
|
||||
4BF829681D8F7361001BAE39 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Acorn/File.hpp; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@@ -977,6 +1022,7 @@
|
||||
4B1414631B588A1100E04248 /* Test Binaries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B9252CD1E74D28200B76AF1 /* Atari ROMs */,
|
||||
4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */,
|
||||
4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */,
|
||||
4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */,
|
||||
@@ -993,6 +1039,18 @@
|
||||
path = 6532;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B1EDB411E39A0AC009D6819 /* Icons */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B2C45411E3C3896002A2389 /* cartridge.png */,
|
||||
4B79E4411E3AF38600141F11 /* cassette.png */,
|
||||
4B79E4421E3AF38600141F11 /* floppy35.png */,
|
||||
4B79E4431E3AF38600141F11 /* floppy525.png */,
|
||||
4B1EDB431E39A0AC009D6819 /* chip.png */,
|
||||
);
|
||||
path = Icons;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B2409591C45DF85004DA684 /* SignalProcessing */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1054,10 +1112,14 @@
|
||||
children = (
|
||||
4B2E2D971C3A06EC00138695 /* Atari2600.cpp */,
|
||||
4BEA52641DF3472B007E74F2 /* Speaker.cpp */,
|
||||
4BE7C9161E3D397100A5496D /* TIA.cpp */,
|
||||
4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */,
|
||||
4B2E2D981C3A06EC00138695 /* Atari2600.hpp */,
|
||||
4BEA52651DF3472B007E74F2 /* Speaker.hpp */,
|
||||
4BEAC08D1E7E0E1A00EE56B2 /* Bus.hpp */,
|
||||
4BEA52671DF34909007E74F2 /* PIA.hpp */,
|
||||
4BEA52651DF3472B007E74F2 /* Speaker.hpp */,
|
||||
4BE7C9171E3D397100A5496D /* TIA.hpp */,
|
||||
4BEAC0801E7E0DF800EE56B2 /* Cartridges */,
|
||||
);
|
||||
path = Atari2600;
|
||||
sourceTree = "<group>";
|
||||
@@ -1070,11 +1132,13 @@
|
||||
4BEA52611DF339D7007E74F2 /* Speaker.cpp */,
|
||||
4BEA525D1DF33323007E74F2 /* Tape.cpp */,
|
||||
4BC8A62B1DCE60E000DAC693 /* Typer.cpp */,
|
||||
4B7913CA1DFCD80E00175A82 /* Video.cpp */,
|
||||
4B2E2D9C1C3A070400138695 /* Electron.hpp */,
|
||||
4BEA52601DF3343A007E74F2 /* Interrupts.hpp */,
|
||||
4B30512F1D98ACC600B4FED8 /* Plus3.hpp */,
|
||||
4BEA52621DF339D7007E74F2 /* Speaker.hpp */,
|
||||
4BEA525F1DF333D8007E74F2 /* Tape.hpp */,
|
||||
4B7913CB1DFCD80E00175A82 /* Video.hpp */,
|
||||
);
|
||||
name = Electron;
|
||||
sourceTree = "<group>";
|
||||
@@ -1083,7 +1147,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B0CCC411C62D0B3001CAC5F /* CRT */,
|
||||
4B2409531C45AB05004DA684 /* Speaker.cpp */,
|
||||
4B2409541C45AB05004DA684 /* Speaker.hpp */,
|
||||
);
|
||||
name = Outputs;
|
||||
@@ -1290,11 +1353,15 @@
|
||||
4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */,
|
||||
4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */,
|
||||
4B30512B1D989E2200B4FED8 /* Drive.cpp */,
|
||||
4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */,
|
||||
4B121F961E060CF000BFDA12 /* PCMSegment.cpp */,
|
||||
4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */,
|
||||
4B0BE4271D3481E700D5256B /* DigitalPhaseLockedLoop.hpp */,
|
||||
4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */,
|
||||
4B6C73BC1D387AE500AFCFCA /* DiskController.hpp */,
|
||||
4B30512C1D989E2200B4FED8 /* Drive.hpp */,
|
||||
4B3F1B451E0388D200DB26EE /* PCMPatchedTrack.hpp */,
|
||||
4B121F971E060CF000BFDA12 /* PCMSegment.hpp */,
|
||||
4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */,
|
||||
4BB697CF1D4BA44900248BDF /* Encodings */,
|
||||
4BAB62B21D327F7E00DF5BA0 /* Formats */,
|
||||
@@ -1595,7 +1662,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BB697C61D4B558F00248BDF /* Factors.hpp */,
|
||||
4BF8295E1D8F3C87001BAE39 /* CRC.cpp */,
|
||||
4BF8295F1D8F3C87001BAE39 /* CRC.hpp */,
|
||||
);
|
||||
name = NumberTheory;
|
||||
@@ -1668,8 +1734,14 @@
|
||||
4BB73EB51B587A5100552FC2 /* Clock SignalTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B5073081DDFCFDF00C48FBD /* ArrayBuilderTests.h */,
|
||||
4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */,
|
||||
4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */,
|
||||
4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */,
|
||||
4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */,
|
||||
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */,
|
||||
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */,
|
||||
4B2AF8681E513FC20027EE29 /* TIATests.mm */,
|
||||
4B1D08051E0F7A1100763741 /* TimeTests.mm */,
|
||||
4BB73EB81B587A5100552FC2 /* Info.plist */,
|
||||
4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */,
|
||||
4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */,
|
||||
@@ -1840,7 +1912,6 @@
|
||||
children = (
|
||||
4BF829641D8F732B001BAE39 /* Disk.cpp */,
|
||||
4BF829651D8F732B001BAE39 /* Disk.hpp */,
|
||||
4BF829671D8F7361001BAE39 /* File.cpp */,
|
||||
4BF829681D8F7361001BAE39 /* File.hpp */,
|
||||
4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */,
|
||||
4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */,
|
||||
@@ -1871,11 +1942,32 @@
|
||||
4BE5F85A1C3E1C2500C43F01 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B1EDB411E39A0AC009D6819 /* Icons */,
|
||||
4BC9DF441D044FCA00F44158 /* ROMImages */,
|
||||
);
|
||||
path = Resources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BEAC0801E7E0DF800EE56B2 /* Cartridges */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BEAC0811E7E0DF800EE56B2 /* Cartridge.hpp */,
|
||||
4BEAC0821E7E0DF800EE56B2 /* CartridgeActivisionStack.hpp */,
|
||||
4BEAC0831E7E0DF800EE56B2 /* CartridgeAtari16k.hpp */,
|
||||
4BEAC0841E7E0DF800EE56B2 /* CartridgeAtari32k.hpp */,
|
||||
4BEAC0851E7E0DF800EE56B2 /* CartridgeAtari8k.hpp */,
|
||||
4BEAC0861E7E0DF800EE56B2 /* CartridgeCBSRAMPlus.hpp */,
|
||||
4BEAC0871E7E0DF800EE56B2 /* CartridgeCommaVid.hpp */,
|
||||
4BEAC0881E7E0DF800EE56B2 /* CartridgeMegaBoy.hpp */,
|
||||
4BEAC0891E7E0DF800EE56B2 /* CartridgeMNetwork.hpp */,
|
||||
4BEAC08A1E7E0DF800EE56B2 /* CartridgeParkerBros.hpp */,
|
||||
4BEAC08B1E7E0DF800EE56B2 /* CartridgeTigervision.hpp */,
|
||||
4BEAC08C1E7E0DF800EE56B2 /* CartridgeUnpaged.hpp */,
|
||||
4BEAC08E1E7E110500EE56B2 /* CartridgePitfall2.hpp */,
|
||||
);
|
||||
path = Cartridges;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BEE0A691D72496600532C7B /* Cartridge */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -2024,13 +2116,18 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
4B2C45421E3C3896002A2389 /* cartridge.png in Resources */,
|
||||
4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */,
|
||||
4B79E4451E3AF38600141F11 /* floppy35.png in Resources */,
|
||||
4B1EDB451E39A0AC009D6819 /* chip.png in Resources */,
|
||||
4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */,
|
||||
4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */,
|
||||
4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */,
|
||||
4B79E4441E3AF38600141F11 /* cassette.png in Resources */,
|
||||
4B8FE21E1DA19D5F0090D3CE /* Vic20Options.xib in Resources */,
|
||||
4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */,
|
||||
4B8FE21D1DA19D5F0090D3CE /* ElectronOptions.xib in Resources */,
|
||||
4B79E4461E3AF38600141F11 /* floppy525.png in Resources */,
|
||||
4BC9DF451D044FCA00F44158 /* ROMImages in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -2161,6 +2258,7 @@
|
||||
4BB299661B587D8400A49093 /* inszx in Resources */,
|
||||
4BB299101B587D8400A49093 /* asoz in Resources */,
|
||||
4BB2998B1B587D8400A49093 /* lseiy in Resources */,
|
||||
4B9252CE1E74D28200B76AF1 /* Atari ROMs in Resources */,
|
||||
4BB2997D1B587D8400A49093 /* ldxay in Resources */,
|
||||
4BB299D71B587D8400A49093 /* staax in Resources */,
|
||||
4BB2990C1B587D8400A49093 /* asoax in Resources */,
|
||||
@@ -2329,6 +2427,7 @@
|
||||
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */,
|
||||
4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */,
|
||||
4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */,
|
||||
4BD4A8CD1E077E8A0020D856 /* PCMSegment.cpp in Sources */,
|
||||
4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */,
|
||||
4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */,
|
||||
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */,
|
||||
@@ -2337,7 +2436,6 @@
|
||||
4BC8A62D1DCE60E000DAC693 /* Typer.cpp in Sources */,
|
||||
4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */,
|
||||
4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */,
|
||||
4BF829601D8F3C87001BAE39 /* CRC.cpp in Sources */,
|
||||
4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */,
|
||||
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
|
||||
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */,
|
||||
@@ -2353,14 +2451,15 @@
|
||||
4B2A332A1DB8544D002876E3 /* MemoryFuzzer.cpp in Sources */,
|
||||
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */,
|
||||
4B2A332F1DB86869002876E3 /* OricOptionsPanel.swift in Sources */,
|
||||
4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */,
|
||||
4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */,
|
||||
4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */,
|
||||
4BEA52631DF339D7007E74F2 /* Speaker.cpp in Sources */,
|
||||
4BC5E4921D7ED365008CF980 /* StaticAnalyser.cpp in Sources */,
|
||||
4BC830D11D6E7C690000A26F /* Tape.cpp in Sources */,
|
||||
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */,
|
||||
4BF829691D8F7361001BAE39 /* File.cpp in Sources */,
|
||||
4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */,
|
||||
4B3F1B461E0388D200DB26EE /* PCMPatchedTrack.cpp in Sources */,
|
||||
4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */,
|
||||
4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */,
|
||||
4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */,
|
||||
@@ -2368,13 +2467,13 @@
|
||||
4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */,
|
||||
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */,
|
||||
4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */,
|
||||
4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */,
|
||||
4BCF1FA81DADC5250039D2E7 /* CSOric.mm in Sources */,
|
||||
4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */,
|
||||
4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */,
|
||||
4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */,
|
||||
4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */,
|
||||
4B5A12571DD55862007A2231 /* Disassembler6502.cpp in Sources */,
|
||||
4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */,
|
||||
4B1E85751D170228001EF87D /* Typer.cpp in Sources */,
|
||||
4BF829631D8F536B001BAE39 /* SSD.cpp in Sources */,
|
||||
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */,
|
||||
@@ -2428,19 +2527,26 @@
|
||||
4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */,
|
||||
4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */,
|
||||
4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */,
|
||||
4B924E991E74D22700B76AF1 /* AtariStaticAnalyserTests.mm in Sources */,
|
||||
4B50730A1DDFCFDF00C48FBD /* ArrayBuilderTests.mm in Sources */,
|
||||
4B2AF8691E513FC20027EE29 /* TIATests.mm in Sources */,
|
||||
4B3BA0CE1D318B44005DD7A7 /* C1540Bridge.mm in Sources */,
|
||||
4B3BA0D11D318B44005DD7A7 /* TestMachine.mm in Sources */,
|
||||
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */,
|
||||
4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */,
|
||||
4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */,
|
||||
4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */,
|
||||
4B3BA0CF1D318B44005DD7A7 /* MOS6522Bridge.mm in Sources */,
|
||||
4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */,
|
||||
4BB2A9AF1E13367E001A5C23 /* CRCTests.mm in Sources */,
|
||||
4B3BA0D01D318B44005DD7A7 /* MOS6532Bridge.mm in Sources */,
|
||||
4B3BA0C31D318AEC005DD7A7 /* C1540Tests.swift in Sources */,
|
||||
4B1414621B58888700E04248 /* KlausDormannTests.swift in Sources */,
|
||||
4B1414601B58885000E04248 /* WolfgangLorenzTests.swift in Sources */,
|
||||
4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */,
|
||||
4B049CDD1DA3C82F00322067 /* BCDTest.swift in Sources */,
|
||||
4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */,
|
||||
4B121F951E05E66800BFDA12 /* PCMPatchedTrackTests.mm in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0800"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "4BB73E9D1B587A5100552FC2"
|
||||
BuildableName = "Clock Signal.app"
|
||||
BlueprintName = "Clock Signal"
|
||||
ReferencedContainer = "container:Clock Signal.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "4BB73EB11B587A5100552FC2"
|
||||
BuildableName = "Clock SignalTests.xctest"
|
||||
BlueprintName = "Clock SignalTests"
|
||||
ReferencedContainer = "container:Clock Signal.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
<TestableReference
|
||||
skipped = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "4BB73EBC1B587A5100552FC2"
|
||||
BuildableName = "Clock SignalUITests.xctest"
|
||||
BlueprintName = "Clock SignalUITests"
|
||||
ReferencedContainer = "container:Clock Signal.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "4BB73E9D1B587A5100552FC2"
|
||||
BuildableName = "Clock Signal.app"
|
||||
BlueprintName = "Clock Signal"
|
||||
ReferencedContainer = "container:Clock Signal.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "NO">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "4BB73E9D1B587A5100552FC2"
|
||||
BuildableName = "Clock Signal.app"
|
||||
BlueprintName = "Clock Signal"
|
||||
ReferencedContainer = "container:Clock Signal.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "4BB73E9D1B587A5100552FC2"
|
||||
BuildableName = "Clock Signal.app"
|
||||
BlueprintName = "Clock Signal"
|
||||
ReferencedContainer = "container:Clock Signal.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -12,32 +12,48 @@
|
||||
#define AudioQueueBufferMaxLength 8192
|
||||
#define NumberOfStoredAudioQueueBuffer 16
|
||||
|
||||
static NSLock *CSAudioQueueDeallocLock;
|
||||
|
||||
/*!
|
||||
Holds a weak reference to a CSAudioQueue. Used to work around an apparent AudioQueue bug.
|
||||
See -[CSAudioQueue dealloc].
|
||||
*/
|
||||
@interface CSWeakAudioQueuePointer: NSObject
|
||||
@property(nonatomic, weak) CSAudioQueue *queue;
|
||||
@end
|
||||
|
||||
@implementation CSWeakAudioQueuePointer
|
||||
@end
|
||||
|
||||
@implementation CSAudioQueue
|
||||
{
|
||||
AudioQueueRef _audioQueue;
|
||||
|
||||
AudioQueueBufferRef _storedBuffers[NumberOfStoredAudioQueueBuffer];
|
||||
NSLock *_storedBuffersLock;
|
||||
CSWeakAudioQueuePointer *_weakPointer;
|
||||
}
|
||||
|
||||
#pragma mark - AudioQueue callbacks
|
||||
|
||||
- (void)audioQueue:(AudioQueueRef)theAudioQueue didCallbackWithBuffer:(AudioQueueBufferRef)buffer
|
||||
/*!
|
||||
@returns @c YES if the queue is running dry; @c NO otherwise.
|
||||
*/
|
||||
- (BOOL)audioQueue:(AudioQueueRef)theAudioQueue didCallbackWithBuffer:(AudioQueueBufferRef)buffer
|
||||
{
|
||||
[self.delegate audioQueueIsRunningDry:self];
|
||||
|
||||
@synchronized(self)
|
||||
[_storedBuffersLock lock];
|
||||
for(int c = 0; c < NumberOfStoredAudioQueueBuffer; c++)
|
||||
{
|
||||
for(int c = 0; c < NumberOfStoredAudioQueueBuffer; c++)
|
||||
if(!_storedBuffers[c] || buffer->mAudioDataBytesCapacity > _storedBuffers[c]->mAudioDataBytesCapacity)
|
||||
{
|
||||
if(!_storedBuffers[c] || buffer->mAudioDataBytesCapacity > _storedBuffers[c]->mAudioDataBytesCapacity)
|
||||
{
|
||||
if(_storedBuffers[c]) AudioQueueFreeBuffer(_audioQueue, _storedBuffers[c]);
|
||||
_storedBuffers[c] = buffer;
|
||||
return;
|
||||
}
|
||||
if(_storedBuffers[c]) AudioQueueFreeBuffer(_audioQueue, _storedBuffers[c]);
|
||||
_storedBuffers[c] = buffer;
|
||||
[_storedBuffersLock unlock];
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
[_storedBuffersLock unlock];
|
||||
AudioQueueFreeBuffer(_audioQueue, buffer);
|
||||
return YES;
|
||||
}
|
||||
|
||||
static void audioOutputCallback(
|
||||
@@ -45,7 +61,17 @@ static void audioOutputCallback(
|
||||
AudioQueueRef inAQ,
|
||||
AudioQueueBufferRef inBuffer)
|
||||
{
|
||||
[(__bridge CSAudioQueue *)inUserData audioQueue:inAQ didCallbackWithBuffer:inBuffer];
|
||||
// Pull the delegate call for audio queue running dry outside of the locked region, to allow non-deadlocking
|
||||
// lifecycle -dealloc events to result from it.
|
||||
if([CSAudioQueueDeallocLock tryLock])
|
||||
{
|
||||
CSAudioQueue *queue = ((__bridge CSWeakAudioQueuePointer *)inUserData).queue;
|
||||
BOOL isRunningDry = NO;
|
||||
isRunningDry = [queue audioQueue:inAQ didCallbackWithBuffer:inBuffer];
|
||||
id<CSAudioQueueDelegate> delegate = queue.delegate;
|
||||
[CSAudioQueueDeallocLock unlock];
|
||||
if(isRunningDry) [delegate audioQueueIsRunningDry:queue];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Standard object lifecycle
|
||||
@@ -56,6 +82,12 @@ static void audioOutputCallback(
|
||||
|
||||
if(self)
|
||||
{
|
||||
if(!CSAudioQueueDeallocLock)
|
||||
{
|
||||
CSAudioQueueDeallocLock = [[NSLock alloc] init];
|
||||
}
|
||||
_storedBuffersLock = [[NSLock alloc] init];
|
||||
|
||||
_samplingRate = samplingRate;
|
||||
|
||||
// determine preferred buffer sizes
|
||||
@@ -80,11 +112,13 @@ static void audioOutputCallback(
|
||||
|
||||
outputDescription.mReserved = 0;
|
||||
|
||||
// create an audio output queue along those lines
|
||||
// create an audio output queue along those lines; see -dealloc re: the CSWeakAudioQueuePointer
|
||||
_weakPointer = [[CSWeakAudioQueuePointer alloc] init];
|
||||
_weakPointer.queue = self;
|
||||
if(!AudioQueueNewOutput(
|
||||
&outputDescription,
|
||||
audioOutputCallback,
|
||||
(__bridge void *)(self),
|
||||
(__bridge void *)(_weakPointer),
|
||||
NULL,
|
||||
kCFRunLoopCommonModes,
|
||||
0,
|
||||
@@ -104,7 +138,31 @@ static void audioOutputCallback(
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if(_audioQueue) AudioQueueDispose(_audioQueue, NO);
|
||||
[CSAudioQueueDeallocLock lock];
|
||||
if(_audioQueue)
|
||||
{
|
||||
AudioQueueDispose(_audioQueue, true);
|
||||
_audioQueue = NULL;
|
||||
}
|
||||
[CSAudioQueueDeallocLock unlock];
|
||||
|
||||
// Yuck. Horrid hack happening here. At least under macOS v10.12, I am frequently seeing calls to
|
||||
// my registered audio callback (audioOutputCallback in this case) that occur **after** the call
|
||||
// to AudioQueueDispose above, even though the second parameter there asks for a synchronous shutdown.
|
||||
// So this appears to be a bug on Apple's side.
|
||||
//
|
||||
// Since the audio callback receives a void * pointer that identifies the class it should branch into,
|
||||
// it's therefore unsafe to pass 'self'. Instead I pass a CSWeakAudioQueuePointer which points to the actual
|
||||
// queue. The lifetime of that class is the lifetime of this instance plus 1 second, as effected by the
|
||||
// artificial dispatch_after below — it serves only to keep pointerSaviour alive for an extra second.
|
||||
//
|
||||
// Why a second? That's definitely quite a lot longer than any amount of audio that may be queued. So
|
||||
// probably safe. As and where Apple's audio queue works properly, CSAudioQueueDeallocLock should provide
|
||||
// absolute safety; elsewhere the CSWeakAudioQueuePointer provides probabilistic.
|
||||
CSWeakAudioQueuePointer *pointerSaviour = _weakPointer;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[pointerSaviour hash];
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - Audio enqueuer
|
||||
@@ -113,28 +171,28 @@ static void audioOutputCallback(
|
||||
{
|
||||
size_t bufferBytes = lengthInSamples * sizeof(int16_t);
|
||||
|
||||
@synchronized(self)
|
||||
[_storedBuffersLock lock];
|
||||
for(int c = 0; c < NumberOfStoredAudioQueueBuffer; c++)
|
||||
{
|
||||
for(int c = 0; c < NumberOfStoredAudioQueueBuffer; c++)
|
||||
if(_storedBuffers[c] && _storedBuffers[c]->mAudioDataBytesCapacity >= bufferBytes)
|
||||
{
|
||||
if(_storedBuffers[c] && _storedBuffers[c]->mAudioDataBytesCapacity >= bufferBytes)
|
||||
{
|
||||
memcpy(_storedBuffers[c]->mAudioData, buffer, bufferBytes);
|
||||
_storedBuffers[c]->mAudioDataByteSize = (UInt32)bufferBytes;
|
||||
memcpy(_storedBuffers[c]->mAudioData, buffer, bufferBytes);
|
||||
_storedBuffers[c]->mAudioDataByteSize = (UInt32)bufferBytes;
|
||||
|
||||
AudioQueueEnqueueBuffer(_audioQueue, _storedBuffers[c], 0, NULL);
|
||||
_storedBuffers[c] = NULL;
|
||||
return;
|
||||
}
|
||||
AudioQueueEnqueueBuffer(_audioQueue, _storedBuffers[c], 0, NULL);
|
||||
_storedBuffers[c] = NULL;
|
||||
[_storedBuffersLock unlock];
|
||||
return;
|
||||
}
|
||||
|
||||
AudioQueueBufferRef newBuffer;
|
||||
AudioQueueAllocateBuffer(_audioQueue, (UInt32)bufferBytes * 2, &newBuffer);
|
||||
memcpy(newBuffer->mAudioData, buffer, bufferBytes);
|
||||
newBuffer->mAudioDataByteSize = (UInt32)bufferBytes;
|
||||
|
||||
AudioQueueEnqueueBuffer(_audioQueue, newBuffer, 0, NULL);
|
||||
}
|
||||
[_storedBuffersLock unlock];
|
||||
|
||||
AudioQueueBufferRef newBuffer;
|
||||
AudioQueueAllocateBuffer(_audioQueue, (UInt32)bufferBytes * 2, &newBuffer);
|
||||
memcpy(newBuffer->mAudioData, buffer, bufferBytes);
|
||||
newBuffer->mAudioDataByteSize = (UInt32)bufferBytes;
|
||||
|
||||
AudioQueueEnqueueBuffer(_audioQueue, newBuffer, 0, NULL);
|
||||
}
|
||||
|
||||
#pragma mark - Sampling Rate getters
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11201" systemVersion="16A323" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11762" systemVersion="16E195" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11201"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11762"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
|
||||
@@ -14,14 +15,14 @@
|
||||
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ota-g7-hOL" customClass="Vic20OptionsPanel" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="83" y="102" width="200" height="134"/>
|
||||
<rect key="contentRect" x="83" y="102" width="200" height="112"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
|
||||
<view key="contentView" id="7Pv-WL-2Rq">
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="134"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="112"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="sBT-cU-h7s">
|
||||
<rect key="frame" x="18" y="98" width="164" height="18"/>
|
||||
<rect key="frame" x="18" y="76" width="164" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Load Tapes Quickly" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="w0l-ha-esm">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -30,35 +31,8 @@
|
||||
<action selector="setFastLoading:" target="ota-g7-hOL" id="me0-h2-Ga5"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="lbt-Wo-6fc">
|
||||
<rect key="frame" x="18" y="78" width="164" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Load Automatically" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="jTj-uV-at1">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="setShouldLoadAutomatically:" target="ota-g7-hOL" id="T3i-gO-T1C"/>
|
||||
</connections>
|
||||
</button>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0NP-x1-qH2">
|
||||
<rect key="frame" x="18" y="17" width="165" height="26"/>
|
||||
<popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" id="K81-0X-C4f">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="diI-80-lCf">
|
||||
<items>
|
||||
<menuItem title="5 kb" id="ze7-6B-ois"/>
|
||||
<menuItem title="8 kb" id="6C7-Iv-Wvl"/>
|
||||
<menuItem title="32 kb" id="DOo-f6-OeZ"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<action selector="setMemorySize:" target="ota-g7-hOL" id="lep-Qi-00V"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MlB-rE-TXV" userLabel="Country Selector">
|
||||
<rect key="frame" x="18" y="48" width="165" height="26"/>
|
||||
<rect key="frame" x="18" y="46" width="165" height="26"/>
|
||||
<popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" id="UIu-uz-pTu">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -76,18 +50,32 @@
|
||||
<action selector="setCountry:" target="ota-g7-hOL" id="YIc-QB-R1S"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0NP-x1-qH2">
|
||||
<rect key="frame" x="18" y="17" width="165" height="26"/>
|
||||
<popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" id="K81-0X-C4f">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="diI-80-lCf">
|
||||
<items>
|
||||
<menuItem title="5 kb" id="ze7-6B-ois"/>
|
||||
<menuItem title="8 kb" id="6C7-Iv-Wvl"/>
|
||||
<menuItem title="32 kb" id="DOo-f6-OeZ"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<action selector="setMemorySize:" target="ota-g7-hOL" id="lep-Qi-00V"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="MlB-rE-TXV" firstAttribute="top" secondItem="sBT-cU-h7s" secondAttribute="bottom" constant="8" id="0kc-u0-05p"/>
|
||||
<constraint firstAttribute="trailing" secondItem="sBT-cU-h7s" secondAttribute="trailing" constant="20" id="79b-2A-2c7"/>
|
||||
<constraint firstItem="0NP-x1-qH2" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="7EF-L9-lIu"/>
|
||||
<constraint firstItem="MlB-rE-TXV" firstAttribute="top" secondItem="lbt-Wo-6fc" secondAttribute="bottom" constant="8" id="DIc-Sm-VlA"/>
|
||||
<constraint firstAttribute="bottom" secondItem="0NP-x1-qH2" secondAttribute="bottom" constant="20" id="Dtd-kf-4oU"/>
|
||||
<constraint firstItem="sBT-cU-h7s" firstAttribute="top" secondItem="7Pv-WL-2Rq" secondAttribute="top" constant="20" id="E5m-wo-X92"/>
|
||||
<constraint firstItem="0NP-x1-qH2" firstAttribute="top" secondItem="MlB-rE-TXV" secondAttribute="bottom" constant="10" id="NbW-5e-wGB"/>
|
||||
<constraint firstItem="lbt-Wo-6fc" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="cID-bi-rVP"/>
|
||||
<constraint firstItem="lbt-Wo-6fc" firstAttribute="top" secondItem="sBT-cU-h7s" secondAttribute="bottom" constant="6" id="ciY-E8-07P"/>
|
||||
<constraint firstItem="0NP-x1-qH2" firstAttribute="top" secondItem="MlB-rE-TXV" secondAttribute="bottom" constant="8" id="NbW-5e-wGB"/>
|
||||
<constraint firstAttribute="trailing" secondItem="0NP-x1-qH2" secondAttribute="trailing" constant="20" id="ero-D6-tJj"/>
|
||||
<constraint firstAttribute="trailing" secondItem="lbt-Wo-6fc" secondAttribute="trailing" constant="20" id="gMU-gX-3Sg"/>
|
||||
<constraint firstItem="sBT-cU-h7s" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="nDy-Xc-Ug9"/>
|
||||
<constraint firstItem="MlB-rE-TXV" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="qb4-Lp-ZMc"/>
|
||||
<constraint firstAttribute="trailing" secondItem="MlB-rE-TXV" secondAttribute="trailing" constant="20" id="v18-62-uee"/>
|
||||
@@ -96,10 +84,9 @@
|
||||
<connections>
|
||||
<outlet property="countryButton" destination="MlB-rE-TXV" id="Duc-AC-ZRO"/>
|
||||
<outlet property="fastLoadingButton" destination="sBT-cU-h7s" id="uWa-EB-mbd"/>
|
||||
<outlet property="loadAutomaticallyButton" destination="lbt-Wo-6fc" id="dv0-u8-BTc"/>
|
||||
<outlet property="memorySizeButton" destination="0NP-x1-qH2" id="qYy-3f-o94"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="-2" y="32"/>
|
||||
<point key="canvasLocation" x="-2" y="21"/>
|
||||
</window>
|
||||
</objects>
|
||||
</document>
|
||||
|
||||
@@ -31,24 +31,14 @@ class MachineDocument:
|
||||
return NSSize(width: 4.0, height: 3.0)
|
||||
}
|
||||
|
||||
@IBOutlet weak var openGLView: CSOpenGLView! {
|
||||
didSet {
|
||||
openGLView.delegate = self
|
||||
openGLView.responderDelegate = self
|
||||
}
|
||||
}
|
||||
|
||||
@IBOutlet weak var openGLView: CSOpenGLView!
|
||||
@IBOutlet var optionsPanel: MachinePanel!
|
||||
@IBAction func showOptions(_ sender: AnyObject!) {
|
||||
optionsPanel?.setIsVisible(true)
|
||||
}
|
||||
|
||||
fileprivate var audioQueue: CSAudioQueue! = nil
|
||||
fileprivate lazy var bestEffortUpdater: CSBestEffortUpdater = {
|
||||
let updater = CSBestEffortUpdater()
|
||||
updater.delegate = self
|
||||
return updater
|
||||
}()
|
||||
fileprivate var bestEffortUpdater: CSBestEffortUpdater!
|
||||
|
||||
override var windowNibName: String? {
|
||||
return "MachineDocument"
|
||||
@@ -64,12 +54,22 @@ class MachineDocument:
|
||||
self.machine.setView(self.openGLView, aspectRatio: Float(displayAspectRatio.width / displayAspectRatio.height))
|
||||
})
|
||||
|
||||
setupClockRate()
|
||||
self.machine.delegate = self
|
||||
self.bestEffortUpdater = CSBestEffortUpdater()
|
||||
|
||||
// callbacks from the OpenGL may come on a different thread, immediately following the .delegate set;
|
||||
// hence the full setup of the best-effort updater prior to setting self as a delegate
|
||||
self.openGLView.delegate = self
|
||||
self.openGLView.responderDelegate = self
|
||||
|
||||
setupClockRate()
|
||||
self.optionsPanel?.establishStoredOptions()
|
||||
|
||||
// bring OpenGL view-holding window on top of the options panel
|
||||
self.openGLView.window!.makeKeyAndOrderFront(self)
|
||||
|
||||
// start accepting best effort updates
|
||||
self.bestEffortUpdater.delegate = self
|
||||
}
|
||||
|
||||
func machineDidChangeClockRate(_ machine: CSMachine!) {
|
||||
|
||||
@@ -13,18 +13,6 @@ class Vic20OptionsPanel: MachinePanel {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: automatic loading tick box
|
||||
@IBOutlet var loadAutomaticallyButton: NSButton?
|
||||
var autoloadingUserDefaultsKey: String {
|
||||
get { return prefixedUserDefaultsKey("autoload") }
|
||||
}
|
||||
|
||||
@IBAction func setShouldLoadAutomatically(_ sender: NSButton!) {
|
||||
let loadAutomatically = sender.state == NSOnState
|
||||
vic20.shouldLoadAutomatically = loadAutomatically
|
||||
UserDefaults.standard.set(loadAutomatically, forKey: self.autoloadingUserDefaultsKey)
|
||||
}
|
||||
|
||||
// MARK: country selector
|
||||
@IBOutlet var countryButton: NSPopUpButton?
|
||||
var countryUserDefaultsKey: String {
|
||||
@@ -85,28 +73,21 @@ class Vic20OptionsPanel: MachinePanel {
|
||||
|
||||
let standardUserDefaults = UserDefaults.standard
|
||||
standardUserDefaults.register(defaults: [
|
||||
self.autoloadingUserDefaultsKey: true,
|
||||
self.memorySizeUserDefaultsKey: 5,
|
||||
self.countryUserDefaultsKey: 1
|
||||
])
|
||||
|
||||
let loadAutomatically = standardUserDefaults.bool(forKey: self.autoloadingUserDefaultsKey)
|
||||
vic20.shouldLoadAutomatically = loadAutomatically
|
||||
self.loadAutomaticallyButton?.state = loadAutomatically ? NSOnState : NSOffState
|
||||
|
||||
if !loadAutomatically {
|
||||
let memorySize = standardUserDefaults.integer(forKey: self.memorySizeUserDefaultsKey)
|
||||
var indexToSelect: Int?
|
||||
switch memorySize {
|
||||
case 32: indexToSelect = 2
|
||||
case 8: indexToSelect = 1
|
||||
default: indexToSelect = 0
|
||||
}
|
||||
if let indexToSelect = indexToSelect {
|
||||
self.memorySizeButton?.selectItem(at: indexToSelect)
|
||||
setMemorySize(indexToSelect)
|
||||
}
|
||||
}
|
||||
// let memorySize = standardUserDefaults.integer(forKey: self.memorySizeUserDefaultsKey)
|
||||
// var indexToSelect: Int?
|
||||
// switch memorySize {
|
||||
// case 32: indexToSelect = 2
|
||||
// case 8: indexToSelect = 1
|
||||
// default: indexToSelect = 0
|
||||
// }
|
||||
// if let indexToSelect = indexToSelect {
|
||||
// self.memorySizeButton?.selectItem(at: indexToSelect)
|
||||
// setMemorySize(indexToSelect)
|
||||
// }
|
||||
|
||||
// TODO: this should be part of the configuration
|
||||
let country = standardUserDefaults.integer(forKey: self.countryUserDefaultsKey)
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<string>bin</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string></string>
|
||||
<string>cartridge</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Atari 2600 Cartridge</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
@@ -27,27 +27,13 @@
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>uef</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Electron/BBC Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array/>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>rom</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>chip</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ROM Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
@@ -65,6 +51,8 @@
|
||||
<string>uef</string>
|
||||
<string>uef.gz</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Electron/BBC UEF Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
@@ -79,6 +67,8 @@
|
||||
<array>
|
||||
<string>prg</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy525</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Commodore Program</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
@@ -93,6 +83,8 @@
|
||||
<array>
|
||||
<string>tap</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
@@ -107,6 +99,8 @@
|
||||
<array>
|
||||
<string>g64</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy525</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Commodore Disk</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
@@ -121,6 +115,8 @@
|
||||
<array>
|
||||
<string>d64</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy525</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Commodore 1540/1 Disk</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
@@ -139,6 +135,8 @@
|
||||
<string>adl</string>
|
||||
<string>adm</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy35</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Electron/BBC Disk Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
@@ -151,6 +149,8 @@
|
||||
<array>
|
||||
<string>dsk</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy35</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Disk Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
|
||||
@@ -11,42 +11,8 @@
|
||||
#include "Atari2600.hpp"
|
||||
#import "CSMachine+Subclassing.h"
|
||||
|
||||
@interface CSAtari2600 ()
|
||||
- (void)crt:(Outputs::CRT::CRT *)crt didEndBatchOfFrames:(unsigned int)numberOfFrames withUnexpectedVerticalSyncs:(unsigned int)numberOfUnexpectedSyncs;
|
||||
@end
|
||||
|
||||
struct CRTDelegate: public Outputs::CRT::Delegate {
|
||||
__weak CSAtari2600 *atari2600;
|
||||
void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs) {
|
||||
[atari2600 crt:crt didEndBatchOfFrames:number_of_frames withUnexpectedVerticalSyncs:number_of_unexpected_vertical_syncs];
|
||||
}
|
||||
};
|
||||
|
||||
@implementation CSAtari2600 {
|
||||
Atari2600::Machine _atari2600;
|
||||
CRTDelegate _crtDelegate;
|
||||
|
||||
int _frameCount;
|
||||
int _hitCount;
|
||||
BOOL _didDecideRegion;
|
||||
int _batchesReceived;
|
||||
}
|
||||
|
||||
- (void)crt:(Outputs::CRT::CRT *)crt didEndBatchOfFrames:(unsigned int)numberOfFrames withUnexpectedVerticalSyncs:(unsigned int)numberOfUnexpectedSyncs {
|
||||
if(!_didDecideRegion)
|
||||
{
|
||||
_batchesReceived++;
|
||||
if(_batchesReceived == 2)
|
||||
{
|
||||
_didDecideRegion = YES;
|
||||
if(numberOfUnexpectedSyncs >= numberOfFrames >> 1)
|
||||
{
|
||||
[self.view performWithGLContext:^{
|
||||
_atari2600.switch_region();
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setDirection:(CSJoystickDirection)direction onPad:(NSUInteger)pad isPressed:(BOOL)isPressed {
|
||||
@@ -78,8 +44,6 @@ struct CRTDelegate: public Outputs::CRT::Delegate {
|
||||
- (void)setupOutputWithAspectRatio:(float)aspectRatio {
|
||||
@synchronized(self) {
|
||||
[super setupOutputWithAspectRatio:aspectRatio];
|
||||
_atari2600.get_crt()->set_delegate(&_crtDelegate);
|
||||
_crtDelegate.atari2600 = self;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,10 +27,12 @@
|
||||
{
|
||||
NSData *basic10 = [self rom:@"basic10"];
|
||||
NSData *basic11 = [self rom:@"basic11"];
|
||||
NSData *colour = [self rom:@"colour"];
|
||||
NSData *microdisc = [self rom:@"microdisc"];
|
||||
|
||||
if(basic10) _oric.set_rom(Oric::BASIC10, basic10.stdVector8);
|
||||
if(basic11) _oric.set_rom(Oric::BASIC11, basic11.stdVector8);
|
||||
if(colour) _oric.set_rom(Oric::Colour, colour.stdVector8);
|
||||
if(microdisc) _oric.set_rom(Oric::Microdisc, microdisc.stdVector8);
|
||||
}
|
||||
return self;
|
||||
@@ -150,7 +152,7 @@
|
||||
- (void)setUseCompositeOutput:(BOOL)useCompositeOutput {
|
||||
@synchronized(self) {
|
||||
_useCompositeOutput = useCompositeOutput;
|
||||
_oric.get_crt()->set_output_device(useCompositeOutput ? Outputs::CRT::Television : Outputs::CRT::Monitor);
|
||||
_oric.set_output_device(useCompositeOutput ? Outputs::CRT::Television : Outputs::CRT::Monitor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ typedef NS_ENUM(NSInteger, CSVic20MemorySize)
|
||||
@interface CSVic20 : CSMachine <CSKeyboardMachine, CSFastLoading>
|
||||
|
||||
@property (nonatomic, assign) BOOL useFastLoadingHack;
|
||||
@property (nonatomic, assign) BOOL shouldLoadAutomatically;
|
||||
@property (nonatomic, assign) CSVic20Country country;
|
||||
@property (nonatomic, assign) CSVic20MemorySize memorySize;
|
||||
|
||||
|
||||
@@ -178,13 +178,6 @@ using namespace Commodore::Vic20;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setShouldLoadAutomatically:(BOOL)shouldLoadAutomatically {
|
||||
_shouldLoadAutomatically = shouldLoadAutomatically;
|
||||
@synchronized(self) {
|
||||
_vic20.set_should_automatically_load_media(shouldLoadAutomatically ? true : false);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setCountry:(CSVic20Country)country {
|
||||
_country = country;
|
||||
NSString *charactersROM, *kernelROM;
|
||||
|
||||
BIN
OSBindings/Mac/Clock Signal/Resources/Icons/cartridge.png
Normal file
BIN
OSBindings/Mac/Clock Signal/Resources/Icons/cartridge.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 111 KiB |
BIN
OSBindings/Mac/Clock Signal/Resources/Icons/cassette.png
Normal file
BIN
OSBindings/Mac/Clock Signal/Resources/Icons/cassette.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 194 KiB |
BIN
OSBindings/Mac/Clock Signal/Resources/Icons/chip.png
Normal file
BIN
OSBindings/Mac/Clock Signal/Resources/Icons/chip.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 105 KiB |
BIN
OSBindings/Mac/Clock Signal/Resources/Icons/floppy35.png
Normal file
BIN
OSBindings/Mac/Clock Signal/Resources/Icons/floppy35.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 102 KiB |
BIN
OSBindings/Mac/Clock Signal/Resources/Icons/floppy525.png
Normal file
BIN
OSBindings/Mac/Clock Signal/Resources/Icons/floppy525.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 126 KiB |
@@ -8,10 +8,12 @@
|
||||
|
||||
#import "CSBestEffortUpdater.h"
|
||||
|
||||
#include <stdatomic.h>
|
||||
|
||||
@implementation CSBestEffortUpdater
|
||||
{
|
||||
// these are inherently handled only by thread-safe constructions
|
||||
uint32_t _updateIsOngoing;
|
||||
atomic_flag _updateIsOngoing;
|
||||
dispatch_queue_t _serialDispatchQueue;
|
||||
|
||||
// these are permitted for modification on _serialDispatchQueue only
|
||||
@@ -25,16 +27,18 @@
|
||||
if(self = [super init])
|
||||
{
|
||||
_serialDispatchQueue = dispatch_queue_create("Best Effort Updater", DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
// This is a workaround for assigning the correct initial value within Objective-C's form.
|
||||
atomic_flag initialFlagValue = ATOMIC_FLAG_INIT;
|
||||
_updateIsOngoing = initialFlagValue;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)update
|
||||
{
|
||||
const uint32_t processingMask = 0x01;
|
||||
|
||||
// Always post an -openGLView:didUpdateToTime: if a previous one isn't still ongoing. This is the hook upon which the substantial processing occurs.
|
||||
if(!OSAtomicTestAndSet(processingMask, &_updateIsOngoing))
|
||||
if(!atomic_flag_test_and_set(&_updateIsOngoing))
|
||||
{
|
||||
dispatch_async(_serialDispatchQueue, ^{
|
||||
NSTimeInterval timeInterval = [NSDate timeIntervalSinceReferenceDate];
|
||||
@@ -52,7 +56,7 @@
|
||||
}
|
||||
_previousTimeInterval = timeInterval;
|
||||
_hasSkipped = NO;
|
||||
OSAtomicTestAndClear(processingMask, &_updateIsOngoing);
|
||||
atomic_flag_clear(&_updateIsOngoing);
|
||||
});
|
||||
}
|
||||
else
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
*/
|
||||
@interface CSOpenGLView : NSOpenGLView
|
||||
|
||||
@property (nonatomic, weak, nullable) id <CSOpenGLViewDelegate> delegate;
|
||||
@property (atomic, weak, nullable) id <CSOpenGLViewDelegate> delegate;
|
||||
@property (nonatomic, weak, nullable) id <CSOpenGLViewResponderDelegate> responderDelegate;
|
||||
|
||||
/*!
|
||||
|
||||
@@ -21,14 +21,25 @@ class MOS6532Tests: XCTestCase {
|
||||
with6532 {
|
||||
// set a count of 128 at single-clock intervals
|
||||
$0.setValue(128, forRegister:0x14)
|
||||
XCTAssertEqual($0.value(forRegister: 4), 128)
|
||||
|
||||
// run for one clock and the count should now be 127
|
||||
// run for one more clock and the count should now be 127
|
||||
$0.run(forCycles: 1)
|
||||
XCTAssert($0.value(forRegister: 4) == 127, "A single tick should decrease the counter once")
|
||||
XCTAssertEqual($0.value(forRegister: 4), 127)
|
||||
|
||||
// run for a further 200 clock counts; timer should reach -73 = 183
|
||||
$0.run(forCycles: 200)
|
||||
XCTAssert($0.value(forRegister: 4) == 183, "Timer should underflow and keep counting")
|
||||
// run for 127 clocks and the timer should be zero, but the timer flag will not yet be set
|
||||
$0.run(forCycles: 127)
|
||||
XCTAssertEqual($0.value(forRegister: 5) & 0x80, 0)
|
||||
XCTAssertEqual($0.value(forRegister: 4), 0)
|
||||
|
||||
// after one more cycle the counter should be 255 and the timer flag will now be set
|
||||
$0.run(forCycles: 1)
|
||||
XCTAssertEqual($0.value(forRegister: 5) & 0x80, 0x80)
|
||||
XCTAssertEqual($0.value(forRegister: 4), 255)
|
||||
|
||||
// run for a further 55 clock counts; timer should reach -200
|
||||
$0.run(forCycles: 55)
|
||||
XCTAssertEqual($0.value(forRegister: 4), 200)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,26 +48,40 @@ class MOS6532Tests: XCTestCase {
|
||||
with6532 {
|
||||
// set a count of 28 at eight-clock intervals
|
||||
$0.setValue(28, forRegister:0x15)
|
||||
XCTAssertEqual($0.value(forRegister: 4), 28)
|
||||
|
||||
// run for seven clock and the count should still be 28
|
||||
$0.run(forCycles: 7)
|
||||
XCTAssert($0.value(forRegister: 4) == 28, "The timer should remain unchanged for seven clocks")
|
||||
|
||||
// run for a further clock and the count should now be 27
|
||||
// one further cycle and the timer should hit 27
|
||||
$0.run(forCycles: 1)
|
||||
XCTAssert($0.value(forRegister: 4) == 27, "The timer should have decremented once after 8 cycles")
|
||||
XCTAssertEqual($0.value(forRegister: 4), 27)
|
||||
|
||||
// run for a further 7 + 27*8 + 5 = 228 clock counts; timer should reach -5 = 0xfb
|
||||
$0.run(forCycles: 228)
|
||||
XCTAssert($0.value(forRegister: 4) == 0xfb, "Timer should underflow and start counting at single-clock pace")
|
||||
// run for seven clock and the count should still be 27
|
||||
$0.run(forCycles: 7)
|
||||
XCTAssertEqual($0.value(forRegister: 4), 27)
|
||||
|
||||
// run for a further clock and the count should now be 26
|
||||
$0.run(forCycles: 1)
|
||||
XCTAssertEqual($0.value(forRegister: 4), 26)
|
||||
|
||||
// run for another 26 * 8 = 208 cycles and the count should hit zero without setting the timer flag, and
|
||||
// stay there for seven more cycles
|
||||
$0.run(forCycles: 208)
|
||||
for _ in 0 ..< 8 {
|
||||
XCTAssertEqual($0.value(forRegister: 5) & 0x80, 0)
|
||||
XCTAssertEqual($0.value(forRegister: 4), 0)
|
||||
$0.run(forCycles: 1)
|
||||
}
|
||||
|
||||
// run six more, and the timer should reach 249, with the interrupt flag set
|
||||
$0.run(forCycles: 6)
|
||||
XCTAssertEqual($0.value(forRegister: 5) & 0x80, 0x80)
|
||||
XCTAssertEqual($0.value(forRegister: 4), 249)
|
||||
|
||||
// timer should now resume dividing by eight
|
||||
$0.run(forCycles: 7)
|
||||
XCTAssert($0.value(forRegister: 4) == 0xfb, "Timer should remain unchanged for seven cycles")
|
||||
XCTAssertEqual($0.value(forRegister: 4), 249)
|
||||
|
||||
// timer should now resume dividing by eight
|
||||
$0.run(forCycles: 1)
|
||||
XCTAssert($0.value(forRegister: 4) == 0xfa, "Timer should decrement after eighth cycle")
|
||||
XCTAssertEqual($0.value(forRegister: 4), 248)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,8 +89,6 @@ class MOS6532Tests: XCTestCase {
|
||||
with6532 {
|
||||
// set a count of 1 at single-clock intervals
|
||||
$0.setValue(1, forRegister:0x1c)
|
||||
|
||||
// run for one clock and the count should now be zero
|
||||
$0.run(forCycles: 1)
|
||||
|
||||
// interrupt shouldn't be signalled yet, bit should not be set
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
//
|
||||
// ArrayBuilderTests.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 19/11/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
@interface ArrayBuilderTests : XCTestCase
|
||||
|
||||
@end
|
||||
@@ -6,7 +6,8 @@
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ArrayBuilderTests.h"
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "ArrayBuilder.hpp"
|
||||
|
||||
static NSData *inputData, *outputData;
|
||||
@@ -17,6 +18,9 @@ static void setData(bool is_input, uint8_t *data, size_t size)
|
||||
if(is_input) inputData = dataObject; else outputData = dataObject;
|
||||
}
|
||||
|
||||
@interface ArrayBuilderTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation ArrayBuilderTests
|
||||
|
||||
+ (void)setUp
|
||||
@@ -43,6 +47,11 @@ static void setData(bool is_input, uint8_t *data, size_t size)
|
||||
}
|
||||
}
|
||||
|
||||
- (std::function<void(uint8_t *input, size_t input_size, uint8_t *output, size_t output_size)>)emptyFlushFunction
|
||||
{
|
||||
return [=] (uint8_t *input, size_t input_size, uint8_t *output, size_t output_size) {};
|
||||
}
|
||||
|
||||
- (void)testSingleWriteSingleFlush
|
||||
{
|
||||
Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData);
|
||||
@@ -53,7 +62,7 @@ static void setData(bool is_input, uint8_t *data, size_t size)
|
||||
for(int c = 0; c < 5; c++) input[c] = c;
|
||||
for(int c = 0; c < 3; c++) output[c] = c + 0x80;
|
||||
|
||||
arrayBuilder.flush();
|
||||
arrayBuilder.flush(self.emptyFlushFunction);
|
||||
arrayBuilder.submit();
|
||||
|
||||
[self assertMonotonicForInputSize:5 outputSize:3];
|
||||
@@ -77,7 +86,7 @@ static void setData(bool is_input, uint8_t *data, size_t size)
|
||||
for(int c = 0; c < 2; c++) input[c] = c+2;
|
||||
for(int c = 0; c < 2; c++) output[c] = c+2 + 0x80;
|
||||
|
||||
arrayBuilder.flush();
|
||||
arrayBuilder.flush(self.emptyFlushFunction);
|
||||
arrayBuilder.submit();
|
||||
|
||||
[self assertMonotonicForInputSize:4 outputSize:4];
|
||||
@@ -98,7 +107,7 @@ static void setData(bool is_input, uint8_t *data, size_t size)
|
||||
XCTAssert(inputData.length == 0, @"No input data should have been received; %lu bytes were received", (unsigned long)inputData.length);
|
||||
XCTAssert(outputData.length == 0, @"No output data should have been received; %lu bytes were received", (unsigned long)outputData.length);
|
||||
|
||||
arrayBuilder.flush();
|
||||
arrayBuilder.flush(self.emptyFlushFunction);
|
||||
arrayBuilder.submit();
|
||||
|
||||
XCTAssert(inputData.length == 25, @"All input data should have been received; %lu bytes were received", (unsigned long)inputData.length);
|
||||
@@ -112,7 +121,7 @@ static void setData(bool is_input, uint8_t *data, size_t size)
|
||||
arrayBuilder.get_input_storage(5);
|
||||
arrayBuilder.get_output_storage(5);
|
||||
|
||||
arrayBuilder.flush();
|
||||
arrayBuilder.flush(self.emptyFlushFunction);
|
||||
|
||||
uint8_t *input = arrayBuilder.get_input_storage(5);
|
||||
uint8_t *output = arrayBuilder.get_output_storage(5);
|
||||
@@ -122,7 +131,7 @@ static void setData(bool is_input, uint8_t *data, size_t size)
|
||||
for(int c = 0; c < 5; c++) input[c] = c;
|
||||
for(int c = 0; c < 5; c++) output[c] = c + 0x80;
|
||||
|
||||
arrayBuilder.flush();
|
||||
arrayBuilder.flush(self.emptyFlushFunction);
|
||||
arrayBuilder.submit();
|
||||
|
||||
[self assertMonotonicForInputSize:5 outputSize:5];
|
||||
|
||||
3
OSBindings/Mac/Clock SignalTests/Atari ROMs/readme.txt
Normal file
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
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
|
||||
72
OSBindings/Mac/Clock SignalTests/CRCTests.mm
Normal file
72
OSBindings/Mac/Clock SignalTests/CRCTests.mm
Normal file
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// CRCTests.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 27/12/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#include "CRC.hpp"
|
||||
|
||||
@interface CRCTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation CRCTests
|
||||
|
||||
- (NumberTheory::CRC16)mfmCRCGenerator
|
||||
{
|
||||
return NumberTheory::CRC16(0x1021, 0xffff);
|
||||
}
|
||||
|
||||
- (uint16_t)crcOfData:(uint8_t *)data length:(size_t)length generator:(NumberTheory::CRC16 &)generator
|
||||
{
|
||||
generator.reset();
|
||||
for(size_t c = 0; c < length; c++)
|
||||
generator.add(data[c]);
|
||||
return generator.get_value();
|
||||
}
|
||||
|
||||
- (void)testIDMark
|
||||
{
|
||||
uint8_t IDMark[] =
|
||||
{
|
||||
0xa1, 0xa1, 0xa1, 0xfe, 0x00, 0x00, 0x01, 0x01
|
||||
};
|
||||
uint16_t crc = 0xfa0c;
|
||||
NumberTheory::CRC16 crcGenerator = self.mfmCRCGenerator;
|
||||
|
||||
uint16_t computedCRC = [self crcOfData:IDMark length:sizeof(IDMark) generator:crcGenerator];
|
||||
XCTAssert(computedCRC == crc, @"Calculated CRC should have been %04x, was %04x", crc, computedCRC);
|
||||
}
|
||||
|
||||
- (void)testData
|
||||
{
|
||||
uint8_t sectorData[] =
|
||||
{
|
||||
0xa1, 0xa1, 0xa1, 0xfb, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x53, 0x45, 0x44, 0x4f,
|
||||
0x52, 0x49, 0x43, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x53, 0x45, 0x44, 0x4f, 0x52, 0x49, 0x43, 0x20, 0x56, 0x31, 0x2e, 0x30,
|
||||
0x30, 0x36, 0x20, 0x30, 0x31, 0x2f, 0x30, 0x31, 0x2f, 0x38, 0x36, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20
|
||||
};
|
||||
uint16_t crc = 0x4de7;
|
||||
NumberTheory::CRC16 crcGenerator = self.mfmCRCGenerator;
|
||||
|
||||
uint16_t computedCRC = [self crcOfData:sectorData length:sizeof(sectorData) generator:crcGenerator];
|
||||
XCTAssert(computedCRC == crc, @"Calculated CRC should have been %04x, was %04x", crc, computedCRC);
|
||||
}
|
||||
|
||||
@end
|
||||
220
OSBindings/Mac/Clock SignalTests/PCMPatchedTrackTests.mm
Normal file
220
OSBindings/Mac/Clock SignalTests/PCMPatchedTrackTests.mm
Normal file
@@ -0,0 +1,220 @@
|
||||
//
|
||||
// PCMPatchedTrackTests.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 17/12/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "PCMTrack.hpp"
|
||||
#include "PCMPatchedTrack.hpp"
|
||||
|
||||
@interface PCMPatchedTrackTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation PCMPatchedTrackTests
|
||||
|
||||
#pragma mark - Prebuilt tracks
|
||||
|
||||
- (std::shared_ptr<Storage::Disk::Track>)togglingTrack
|
||||
{
|
||||
Storage::Disk::PCMSegment segment;
|
||||
segment.data = { 0xff, 0xff, 0xff, 0xff };
|
||||
segment.number_of_bits = 32;
|
||||
return std::shared_ptr<Storage::Disk::Track>(new Storage::Disk::PCMTrack(segment));
|
||||
}
|
||||
|
||||
- (std::shared_ptr<Storage::Disk::Track>)patchableTogglingTrack
|
||||
{
|
||||
std::shared_ptr<Storage::Disk::Track> track = self.togglingTrack;
|
||||
return std::shared_ptr<Storage::Disk::Track>(new Storage::Disk::PCMPatchedTrack(track));
|
||||
}
|
||||
|
||||
- (std::shared_ptr<Storage::Disk::Track>)fourSegmentPatchedTrack
|
||||
{
|
||||
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.patchableTogglingTrack;
|
||||
Storage::Disk::PCMPatchedTrack *patchable = static_cast<Storage::Disk::PCMPatchedTrack *>(patchableTrack.get());
|
||||
|
||||
for(int c = 0; c < 4; c++)
|
||||
{
|
||||
Storage::Disk::PCMSegment segment;
|
||||
segment.data = {0xff};
|
||||
segment.number_of_bits = 8;
|
||||
segment.length_of_a_bit.length = 1;
|
||||
segment.length_of_a_bit.clock_rate = 32;
|
||||
patchable->add_segment(Storage::Time(c, 4), segment);
|
||||
}
|
||||
|
||||
return patchableTrack;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (std::vector<Storage::Disk::Track::Event>)eventsFromTrack:(std::shared_ptr<Storage::Disk::Track>)track
|
||||
{
|
||||
std::vector<Storage::Disk::Track::Event> events;
|
||||
while(1)
|
||||
{
|
||||
events.push_back(track->get_next_event());
|
||||
if(events.back().type == Storage::Disk::Track::Event::IndexHole) break;
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
- (Storage::Time)timeForEvents:(const std::vector<Storage::Disk::Track::Event> &)events
|
||||
{
|
||||
Storage::Time result(0);
|
||||
for(auto event : events)
|
||||
{
|
||||
result += event.length;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)patchTrack:(std::shared_ptr<Storage::Disk::Track>)track withSegment:(Storage::Disk::PCMSegment)segment atTime:(Storage::Time)time
|
||||
{
|
||||
Storage::Disk::PCMPatchedTrack *patchable = static_cast<Storage::Disk::PCMPatchedTrack *>(track.get());
|
||||
patchable->add_segment(time, segment);
|
||||
}
|
||||
|
||||
#pragma mark - Repeating Asserts
|
||||
|
||||
- (void)assertOneThirtyTwosForTrack:(std::shared_ptr<Storage::Disk::Track>)track
|
||||
{
|
||||
// Confirm that there are now flux transitions (just the first five will do)
|
||||
// located 1/32nd of a rotation apart.
|
||||
for(int c = 0; c < 5; c++)
|
||||
{
|
||||
Storage::Disk::Track::Event event = track->get_next_event();
|
||||
XCTAssert(
|
||||
event.length == (c ? Storage::Time(1, 32) : Storage::Time(1, 64)),
|
||||
@"flux transitions should be 1/32nd of a track apart");
|
||||
}
|
||||
}
|
||||
|
||||
- (void)assertEvents:(const std::vector<Storage::Disk::Track::Event> &)events hasEntries:(size_t)numberOfEntries withEntry:(size_t)entry ofLength:(Storage::Time)time
|
||||
{
|
||||
XCTAssert(events.size() == numberOfEntries, @"Should be %zu total events", numberOfEntries);
|
||||
|
||||
XCTAssert(events[entry].length == time, @"Event %zu should have been %d/%d long, was %d/%d", entry, time.length, time.clock_rate, events[entry].length.length, events[entry].length.clock_rate);
|
||||
|
||||
Storage::Time eventTime = [self timeForEvents:events];
|
||||
XCTAssert(eventTime == Storage::Time(1), @"Total track length should be 1; was %d/%d", eventTime.length, eventTime.clock_rate);
|
||||
}
|
||||
|
||||
#pragma mark - Unpatched tracks
|
||||
|
||||
- (void)testUnpatchedRawTrack
|
||||
{
|
||||
[self assertOneThirtyTwosForTrack:self.togglingTrack];
|
||||
}
|
||||
|
||||
- (void)testUnpatchedTrack
|
||||
{
|
||||
[self assertOneThirtyTwosForTrack:self.patchableTogglingTrack];
|
||||
}
|
||||
|
||||
#pragma mark - Insertions affecting one existing segment
|
||||
|
||||
- (void)testSingleSplice
|
||||
{
|
||||
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.patchableTogglingTrack;
|
||||
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(1, 32), 1, {0xff}) atTime:Storage::Time(3, 128)];
|
||||
|
||||
std::vector<Storage::Disk::Track::Event> events = [self eventsFromTrack:patchableTrack];
|
||||
Storage::Time total_length = [self timeForEvents:events];
|
||||
|
||||
XCTAssert(events.size() == 33, @"Should still be 33 total events");
|
||||
XCTAssert(events[0].length == Storage::Time(1, 64), @"First event should be after 1/64 as usual");
|
||||
XCTAssert(events[1].length == Storage::Time(3, 128), @"Second event should be 3/128 later"); // ... as it was inserted at 3/128 and runs at the same rate as the main data, so first inserted event is at 3/128+1/64-1/64
|
||||
XCTAssert(events[2].length == Storage::Time(5, 128), @"Should still be 33 total events"); // 1/64 = 2/128 to exit the patch, plus 3/128 to get to the next event, having spliced in 1/128 ahead of the normal clock
|
||||
XCTAssert(total_length == Storage::Time(1), @"Total track length should still be 1");
|
||||
}
|
||||
|
||||
- (void)testLeftReplace
|
||||
{
|
||||
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.patchableTogglingTrack;
|
||||
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(1, 16), 8, {0x00}) atTime:Storage::Time(0)];
|
||||
|
||||
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:17 withEntry:0 ofLength:Storage::Time(33, 64)];
|
||||
}
|
||||
|
||||
- (void)testRightReplace
|
||||
{
|
||||
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.patchableTogglingTrack;
|
||||
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(1, 16), 8, {0x00}) atTime:Storage::Time(1, 2)];
|
||||
|
||||
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:17 withEntry:16 ofLength:Storage::Time(33, 64)];
|
||||
}
|
||||
|
||||
#pragma mark - Insertions affecting three existing segments
|
||||
|
||||
- (void)testMultiSegmentTrack
|
||||
{
|
||||
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.fourSegmentPatchedTrack;
|
||||
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:33 withEntry:4 ofLength:Storage::Time(1, 32)];
|
||||
}
|
||||
|
||||
- (void)testMultiTrimBothSideReplace
|
||||
{
|
||||
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.fourSegmentPatchedTrack;
|
||||
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(1, 16), 8, {0x00}) atTime:Storage::Time(1, 8)];
|
||||
|
||||
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:17 withEntry:4 ofLength:Storage::Time(17, 32)];
|
||||
}
|
||||
|
||||
- (void)testMultiTrimRightReplace
|
||||
{
|
||||
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.fourSegmentPatchedTrack;
|
||||
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(3, 8), 1, {0x00}) atTime:Storage::Time(1, 8)];
|
||||
|
||||
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:21 withEntry:4 ofLength:Storage::Time(13, 32)];
|
||||
}
|
||||
|
||||
- (void)testMultiTrimLeftReplace
|
||||
{
|
||||
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.fourSegmentPatchedTrack;
|
||||
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(3, 8), 1, {0x00}) atTime:Storage::Time(1, 4)];
|
||||
|
||||
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:21 withEntry:8 ofLength:Storage::Time(13, 32)];
|
||||
}
|
||||
|
||||
#pragma mark - Insertions affecting two existing segments
|
||||
|
||||
- (void)testTwoSegmentOverlap
|
||||
{
|
||||
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.fourSegmentPatchedTrack;
|
||||
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(1, 32), 8, {0x00}) atTime:Storage::Time(1, 8)];
|
||||
|
||||
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:25 withEntry:4 ofLength:Storage::Time(9, 32)];
|
||||
}
|
||||
|
||||
- (void)testTwoSegmentRightReplace
|
||||
{
|
||||
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.fourSegmentPatchedTrack;
|
||||
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(3, 8), 1, {0x00}) atTime:Storage::Time(1, 8)];
|
||||
|
||||
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:21 withEntry:4 ofLength:Storage::Time(13, 32)];
|
||||
}
|
||||
|
||||
- (void)testTwoSegmentLeftReplace
|
||||
{
|
||||
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.fourSegmentPatchedTrack;
|
||||
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(3, 8), 1, {0x00}) atTime:Storage::Time(0)];
|
||||
|
||||
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:21 withEntry:0 ofLength:Storage::Time(25, 64)];
|
||||
}
|
||||
|
||||
#pragma mark - Wrapping segment
|
||||
|
||||
- (void)testWrappingSegment
|
||||
{
|
||||
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.patchableTogglingTrack;
|
||||
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(5, 2), 1, {0x00}) atTime:Storage::Time(0)];
|
||||
|
||||
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:1 withEntry:0 ofLength:Storage::Time(1, 1)];
|
||||
}
|
||||
|
||||
@end
|
||||
113
OSBindings/Mac/Clock SignalTests/PCMSegmentEventSourceTests.mm
Normal file
113
OSBindings/Mac/Clock SignalTests/PCMSegmentEventSourceTests.mm
Normal file
@@ -0,0 +1,113 @@
|
||||
//
|
||||
// PCMSegmentEventSourceTests.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 17/12/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "PCMSegment.hpp"
|
||||
|
||||
@interface PCMSegmentEventSourceTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation PCMSegmentEventSourceTests
|
||||
|
||||
- (Storage::Disk::PCMSegmentEventSource)segmentSource
|
||||
{
|
||||
Storage::Disk::PCMSegment alternatingFFs;
|
||||
alternatingFFs.data = {0xff, 0x00, 0xff, 0x00};
|
||||
alternatingFFs.length_of_a_bit.length = 1;
|
||||
alternatingFFs.length_of_a_bit.clock_rate = 10;
|
||||
alternatingFFs.number_of_bits = 32;
|
||||
return Storage::Disk::PCMSegmentEventSource(alternatingFFs);
|
||||
}
|
||||
|
||||
- (void)testCentring
|
||||
{
|
||||
Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource;
|
||||
[self assertFirstTwoEventLengthsForSource:segmentSource];
|
||||
}
|
||||
|
||||
- (void)assertFirstTwoEventLengthsForSource:(Storage::Disk::PCMSegmentEventSource &)segmentSource
|
||||
{
|
||||
Storage::Disk::Track::Event first_event = segmentSource.get_next_event();
|
||||
Storage::Disk::Track::Event second_event = segmentSource.get_next_event();
|
||||
|
||||
first_event.length.simplify();
|
||||
second_event.length.simplify();
|
||||
XCTAssertTrue(first_event.length.length == 1 && first_event.length.clock_rate == 20, @"First event should occur half a bit's length in");
|
||||
XCTAssertTrue(second_event.length.length == 1 && second_event.length.clock_rate == 10, @"Second event should occur a whole bit's length after the first");
|
||||
}
|
||||
|
||||
- (void)testLongerGap
|
||||
{
|
||||
Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource;
|
||||
|
||||
// skip first eight flux transitions
|
||||
for(int c = 0; c < 8; c++) segmentSource.get_next_event();
|
||||
|
||||
Storage::Disk::Track::Event next_event = segmentSource.get_next_event();
|
||||
next_event.length.simplify();
|
||||
|
||||
XCTAssertTrue(next_event.length.length == 9 && next_event.length.clock_rate == 10, @"Zero byte should give a nine bit length event gap");
|
||||
}
|
||||
|
||||
- (void)testTermination
|
||||
{
|
||||
Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource;
|
||||
Storage::Time total_time;
|
||||
for(int c = 0; c < 16; c++) total_time += segmentSource.get_next_event().length;
|
||||
|
||||
Storage::Disk::Track::Event final_event = segmentSource.get_next_event();
|
||||
total_time += final_event.length;
|
||||
total_time.simplify();
|
||||
|
||||
XCTAssertTrue(final_event.type == Storage::Disk::Track::Event::IndexHole, @"Segment should end with an index hole");
|
||||
XCTAssertTrue(total_time.length == 16 && total_time.clock_rate == 5, @"Should have taken 32 bit lengths to finish the segment");
|
||||
}
|
||||
|
||||
- (void)testReset
|
||||
{
|
||||
Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource;
|
||||
for(int c = 0; c < 8; c++) segmentSource.get_next_event();
|
||||
segmentSource.reset();
|
||||
[self assertFirstTwoEventLengthsForSource:segmentSource];
|
||||
}
|
||||
|
||||
- (void)testSeekToSecondBit
|
||||
{
|
||||
Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource;
|
||||
Storage::Time target_time(1, 10);
|
||||
|
||||
Storage::Time found_time = segmentSource.seek_to(target_time);
|
||||
found_time.simplify();
|
||||
|
||||
XCTAssertTrue(found_time.length == 1 && found_time.clock_rate == 20, @"A request to seek to 1/10th should have seeked to 1/20th");
|
||||
|
||||
Storage::Disk::Track::Event next_event = segmentSource.get_next_event();
|
||||
next_event.length.simplify();
|
||||
|
||||
XCTAssertTrue(next_event.length.length == 1 && next_event.length.clock_rate == 10, @"Next event should be 1/10th later");
|
||||
}
|
||||
|
||||
- (void)testSeekBeyondFinalBit
|
||||
{
|
||||
Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource;
|
||||
Storage::Time target_time(24, 10);
|
||||
|
||||
Storage::Time found_time = segmentSource.seek_to(target_time);
|
||||
found_time.simplify();
|
||||
|
||||
XCTAssertTrue(found_time.length == 47 && found_time.clock_rate == 20, @"A request to seek to 24/10ths should have seeked to 47/20ths");
|
||||
|
||||
Storage::Disk::Track::Event next_event = segmentSource.get_next_event();
|
||||
next_event.length.simplify();
|
||||
|
||||
XCTAssertTrue(next_event.length.length == 17 && next_event.length.clock_rate == 20, @"Next event should be 17/20ths later");
|
||||
XCTAssertTrue(next_event.type == Storage::Disk::Track::Event::IndexHole, @"End should have been reached");
|
||||
}
|
||||
|
||||
@end
|
||||
54
OSBindings/Mac/Clock SignalTests/PCMTrackTests.mm
Normal file
54
OSBindings/Mac/Clock SignalTests/PCMTrackTests.mm
Normal file
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// PCMTrackTests.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/12/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "PCMTrack.hpp"
|
||||
|
||||
@interface PCMTrackTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation PCMTrackTests
|
||||
|
||||
- (Storage::Disk::PCMTrack)multiSpeedTrack
|
||||
{
|
||||
Storage::Disk::PCMSegment quickSegment, slowSegment;
|
||||
|
||||
quickSegment.data = {0xff};
|
||||
quickSegment.number_of_bits = 8;
|
||||
quickSegment.length_of_a_bit.length = 1;
|
||||
quickSegment.length_of_a_bit.clock_rate = 100;
|
||||
|
||||
slowSegment.data = {0xff};
|
||||
slowSegment.number_of_bits = 8;
|
||||
slowSegment.length_of_a_bit.length = 1;
|
||||
slowSegment.length_of_a_bit.clock_rate = 3;
|
||||
|
||||
return Storage::Disk::PCMTrack({quickSegment, slowSegment});
|
||||
}
|
||||
|
||||
- (void)testMultispeedTrack
|
||||
{
|
||||
Storage::Disk::PCMTrack track = self.multiSpeedTrack;
|
||||
std::vector<Storage::Disk::Track::Event> events;
|
||||
Storage::Time total_length;
|
||||
do {
|
||||
events.push_back(track.get_next_event());
|
||||
total_length += events.back().length;
|
||||
} while(events.back().type != Storage::Disk::Track::Event::IndexHole);
|
||||
|
||||
XCTAssert(events.size() == 17, "Should have received 17 events; got %lu", events.size());
|
||||
|
||||
total_length.simplify();
|
||||
XCTAssert(total_length.length == 1 && total_length.clock_rate == 1, "Events should have summed to a total time of 1; instead got %u/%u", total_length.length, total_length.clock_rate);
|
||||
|
||||
Storage::Time transition_length = events[0].length + events.back().length;
|
||||
XCTAssert(events[8].length == transition_length, "Time taken in transition between speed zones should be half of a bit length in the first part plus half of a bit length in the second");
|
||||
}
|
||||
|
||||
@end
|
||||
119
OSBindings/Mac/Clock SignalTests/TIATests.mm
Normal file
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
|
||||
44
OSBindings/Mac/Clock SignalTests/TimeTests.mm
Normal file
44
OSBindings/Mac/Clock SignalTests/TimeTests.mm
Normal file
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// TimeTests.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/12/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "Storage.hpp"
|
||||
|
||||
@interface TimeTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation TimeTests
|
||||
|
||||
- (void)testHalf
|
||||
{
|
||||
Storage::Time half(0.5f);
|
||||
XCTAssert(half == Storage::Time(1, 2), @"0.5 should be converted to 1/2");
|
||||
}
|
||||
|
||||
- (void)testTwenty
|
||||
{
|
||||
Storage::Time twenty(20.0f);
|
||||
XCTAssert(twenty == Storage::Time(20, 1), @"20.0 should be converted to 20/1");
|
||||
}
|
||||
|
||||
- (void)testTooSmallFloat
|
||||
{
|
||||
float original = 1.0f / powf(2.0f, 25.0f);
|
||||
Storage::Time time(original);
|
||||
XCTAssert(time == Storage::Time(0), @"Numbers too small to be represented should be 0");
|
||||
}
|
||||
|
||||
- (void)testTooBigFloat
|
||||
{
|
||||
float original = powf(2.0f, 48.0f);
|
||||
Storage::Time time(original);
|
||||
XCTAssert(time == Storage::Time::max(), @"Numbers too big to be represented should saturate");
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -14,8 +14,7 @@
|
||||
|
||||
using namespace Outputs::CRT;
|
||||
|
||||
void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator)
|
||||
{
|
||||
void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, bool should_alternate) {
|
||||
openGL_output_builder_.set_colour_format(colour_space, colour_cycle_numerator, colour_cycle_denominator);
|
||||
|
||||
const unsigned int syncCapacityLineChargeThreshold = 2;
|
||||
@@ -29,10 +28,16 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di
|
||||
// for horizontal retrace and 500 to 750 µs for vertical retrace in NTSC and PAL TV."
|
||||
|
||||
time_multiplier_ = IntermediateBufferWidth / cycles_per_line;
|
||||
phase_denominator_ = cycles_per_line * colour_cycle_denominator * time_multiplier_;
|
||||
phase_numerator_ = 0;
|
||||
colour_cycle_numerator_ = colour_cycle_numerator;
|
||||
phase_alternates_ = should_alternate;
|
||||
is_alernate_line_ &= phase_alternates_;
|
||||
cycles_per_line_ = cycles_per_line;
|
||||
unsigned int multiplied_cycles_per_line = cycles_per_line * time_multiplier_;
|
||||
|
||||
// generate timing values implied by the given arbuments
|
||||
sync_capacitor_charge_threshold_ = (int)(syncCapacityLineChargeThreshold * multiplied_cycles_per_line);
|
||||
// generate timing values implied by the given arguments
|
||||
sync_capacitor_charge_threshold_ = ((int)(syncCapacityLineChargeThreshold * cycles_per_line) * 3) / 4;
|
||||
|
||||
// create the two flywheels
|
||||
horizontal_flywheel_.reset(new Flywheel(multiplied_cycles_per_line, (millisecondsHorizontalRetraceTime * multiplied_cycles_per_line) >> 6, multiplied_cycles_per_line >> 6));
|
||||
@@ -45,20 +50,26 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di
|
||||
openGL_output_builder_.set_timing(cycles_per_line, multiplied_cycles_per_line, height_of_display, horizontal_flywheel_->get_scan_period(), vertical_flywheel_->get_scan_period(), vertical_flywheel_output_divider_);
|
||||
}
|
||||
|
||||
void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType displayType)
|
||||
{
|
||||
switch(displayType)
|
||||
{
|
||||
void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType displayType) {
|
||||
switch(displayType) {
|
||||
case DisplayType::PAL50:
|
||||
set_new_timing(cycles_per_line, 312, ColourSpace::YUV, 709379, 2500); // i.e. 283.7516
|
||||
set_new_timing(cycles_per_line, 312, ColourSpace::YUV, 709379, 2500, true); // i.e. 283.7516
|
||||
break;
|
||||
|
||||
case DisplayType::NTSC60:
|
||||
set_new_timing(cycles_per_line, 262, ColourSpace::YIQ, 545, 2);
|
||||
set_new_timing(cycles_per_line, 262, ColourSpace::YIQ, 455, 2, false); // i.e. 227.5
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CRT::set_composite_function_type(CompositeSourceType type, float offset_of_first_sample) {
|
||||
if(type == DiscreteFourSamplesPerCycle) {
|
||||
colour_burst_phase_adjustment_ = (uint8_t)(offset_of_first_sample * 256.0f) & 63;
|
||||
} else {
|
||||
colour_burst_phase_adjustment_ = 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
CRT::CRT(unsigned int common_output_divisor, unsigned int buffer_depth) :
|
||||
sync_capacitor_charge_level_(0),
|
||||
is_receiving_sync_(false),
|
||||
@@ -67,29 +78,26 @@ CRT::CRT(unsigned int common_output_divisor, unsigned int buffer_depth) :
|
||||
is_writing_composite_run_(false),
|
||||
delegate_(nullptr),
|
||||
frames_since_last_delegate_call_(0),
|
||||
openGL_output_builder_(buffer_depth) {}
|
||||
openGL_output_builder_(buffer_depth),
|
||||
is_alernate_line_(false) {}
|
||||
|
||||
CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int 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);
|
||||
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) {
|
||||
set_new_timing(cycles_per_line, height_of_display, colour_space, colour_cycle_numerator, colour_cycle_denominator, should_alternate);
|
||||
}
|
||||
|
||||
CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, DisplayType displayType, unsigned int buffer_depth) :
|
||||
CRT(common_output_divisor, buffer_depth)
|
||||
{
|
||||
CRT(common_output_divisor, buffer_depth) {
|
||||
set_new_display_type(cycles_per_line, displayType);
|
||||
}
|
||||
|
||||
#pragma mark - Sync loop
|
||||
|
||||
Flywheel::SyncEvent CRT::get_next_vertical_sync_event(bool vsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced)
|
||||
{
|
||||
Flywheel::SyncEvent CRT::get_next_vertical_sync_event(bool vsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) {
|
||||
return vertical_flywheel_->get_next_event_in_period(vsync_is_requested, cycles_to_run_for, cycles_advanced);
|
||||
}
|
||||
|
||||
Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced)
|
||||
{
|
||||
Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) {
|
||||
return horizontal_flywheel_->get_next_event_in_period(hsync_is_requested, cycles_to_run_for, cycles_advanced);
|
||||
}
|
||||
|
||||
@@ -106,10 +114,8 @@ Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested,
|
||||
#define source_output_position_x2() (*(uint16_t *)&next_run[SourceVertexOffsetOfEnds + 2])
|
||||
#define source_phase() next_run[SourceVertexOffsetOfPhaseTimeAndAmplitude + 0]
|
||||
#define source_amplitude() next_run[SourceVertexOffsetOfPhaseTimeAndAmplitude + 2]
|
||||
#define source_phase_time() next_run[SourceVertexOffsetOfPhaseTimeAndAmplitude + 1]
|
||||
|
||||
void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Scan::Type type)
|
||||
{
|
||||
void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bool vsync_requested, const Scan::Type type) {
|
||||
std::unique_lock<std::mutex> output_lock = openGL_output_builder_.get_output_lock();
|
||||
number_of_cycles *= time_multiplier_;
|
||||
|
||||
@@ -124,42 +130,34 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo
|
||||
// get the next sync event and its timing; hsync request is instantaneous (being edge triggered) so
|
||||
// set it to false for the next run through this loop (if any)
|
||||
unsigned int next_run_length = std::min(time_until_vertical_sync_event, time_until_horizontal_sync_event);
|
||||
phase_numerator_ += next_run_length * colour_cycle_numerator_;
|
||||
phase_numerator_ %= phase_denominator_;
|
||||
|
||||
hsync_requested = false;
|
||||
vsync_requested = false;
|
||||
|
||||
bool is_output_segment = ((is_output_run && next_run_length) && !horizontal_flywheel_->is_in_retrace() && !vertical_flywheel_->is_in_retrace());
|
||||
uint8_t *next_run = nullptr;
|
||||
if(is_output_segment && !openGL_output_builder_.composite_output_buffer_is_full())
|
||||
{
|
||||
if(is_output_segment && !openGL_output_builder_.composite_output_buffer_is_full()) {
|
||||
next_run = openGL_output_builder_.array_builder.get_input_storage(SourceVertexSize);
|
||||
}
|
||||
|
||||
if(next_run)
|
||||
{
|
||||
if(next_run) {
|
||||
// output_y and texture locations will be written later; we won't necessarily know what it is outside of the locked region
|
||||
source_output_position_x1() = (uint16_t)horizontal_flywheel_->get_current_output_position();
|
||||
source_phase() = colour_burst_phase_;
|
||||
source_amplitude() = colour_burst_amplitude_;
|
||||
source_phase_time() = (uint8_t)colour_burst_time_; // assumption: burst was within the first 1/16 of the line
|
||||
}
|
||||
|
||||
// decrement the number of cycles left to run for and increment the
|
||||
// horizontal counter appropriately
|
||||
number_of_cycles -= next_run_length;
|
||||
|
||||
// either charge or deplete the vertical retrace capacitor (making sure it stops at 0)
|
||||
if(vsync_charging)
|
||||
sync_capacitor_charge_level_ += next_run_length;
|
||||
else
|
||||
sync_capacitor_charge_level_ = std::max(sync_capacitor_charge_level_ - (int)next_run_length, 0);
|
||||
|
||||
// react to the incoming event...
|
||||
horizontal_flywheel_->apply_event(next_run_length, (next_run_length == time_until_horizontal_sync_event) ? next_horizontal_sync_event : Flywheel::SyncEvent::None);
|
||||
vertical_flywheel_->apply_event(next_run_length, (next_run_length == time_until_vertical_sync_event) ? next_vertical_sync_event : Flywheel::SyncEvent::None);
|
||||
|
||||
if(next_run)
|
||||
{
|
||||
if(next_run) {
|
||||
source_output_position_x2() = (uint16_t)horizontal_flywheel_->get_current_output_position();
|
||||
}
|
||||
|
||||
@@ -171,68 +169,60 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo
|
||||
(honoured_event == Flywheel::SyncEvent::StartRetrace && is_writing_composite_run_) ||
|
||||
(honoured_event == Flywheel::SyncEvent::EndRetrace && !horizontal_flywheel_->is_in_retrace() && !vertical_flywheel_->is_in_retrace());
|
||||
|
||||
if(needs_endpoint)
|
||||
{
|
||||
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(
|
||||
!openGL_output_builder_.array_builder.is_full() &&
|
||||
!openGL_output_builder_.composite_output_buffer_is_full())
|
||||
{
|
||||
if(!is_writing_composite_run_)
|
||||
{
|
||||
!openGL_output_builder_.composite_output_buffer_is_full()) {
|
||||
|
||||
if(!is_writing_composite_run_) {
|
||||
output_run_.x1 = (uint16_t)horizontal_flywheel_->get_current_output_position();
|
||||
output_run_.y = (uint16_t)(vertical_flywheel_->get_current_output_position() / vertical_flywheel_output_divider_);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Get and write all those previously unwritten output ys
|
||||
const uint16_t output_y = openGL_output_builder_.get_composite_output_y();
|
||||
|
||||
// Construct the output run
|
||||
uint8_t *next_run = openGL_output_builder_.array_builder.get_output_storage(OutputVertexSize);
|
||||
if(next_run)
|
||||
{
|
||||
if(next_run) {
|
||||
output_x1() = output_run_.x1;
|
||||
output_position_y() = output_run_.y;
|
||||
output_tex_y() = output_y;
|
||||
output_x2() = (uint16_t)horizontal_flywheel_->get_current_output_position();
|
||||
}
|
||||
openGL_output_builder_.array_builder.flush(
|
||||
[output_y, this] (uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size)
|
||||
{
|
||||
[output_y, this] (uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size) {
|
||||
openGL_output_builder_.texture_builder.flush(
|
||||
[output_y, input_buffer] (const std::vector<TextureBuilder::WriteArea> &write_areas, size_t number_of_write_areas)
|
||||
{
|
||||
for(size_t run = 0; run < number_of_write_areas; run++)
|
||||
{
|
||||
[output_y, input_buffer] (const std::vector<TextureBuilder::WriteArea> &write_areas, size_t number_of_write_areas) {
|
||||
for(size_t run = 0; run < number_of_write_areas; run++) {
|
||||
*(uint16_t *)&input_buffer[run * SourceVertexSize + SourceVertexOffsetOfInputStart + 0] = write_areas[run].x;
|
||||
*(uint16_t *)&input_buffer[run * SourceVertexSize + SourceVertexOffsetOfInputStart + 2] = write_areas[run].y;
|
||||
*(uint16_t *)&input_buffer[run * SourceVertexSize + SourceVertexOffsetOfEnds + 0] = write_areas[run].x + write_areas[run].length;
|
||||
}
|
||||
});
|
||||
for(size_t position = 0; position < input_size; position += SourceVertexSize)
|
||||
{
|
||||
for(size_t position = 0; position < input_size; position += SourceVertexSize) {
|
||||
(*(uint16_t *)&input_buffer[position + SourceVertexOffsetOfOutputStart + 2]) = output_y;
|
||||
}
|
||||
});
|
||||
colour_burst_amplitude_ = 0;
|
||||
}
|
||||
is_writing_composite_run_ ^= true;
|
||||
}
|
||||
}
|
||||
|
||||
if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace)
|
||||
{
|
||||
if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace) {
|
||||
openGL_output_builder_.increment_composite_output_y();
|
||||
}
|
||||
|
||||
// if this is vertical retrace then adcance a field
|
||||
if(next_run_length == time_until_vertical_sync_event && next_vertical_sync_event == Flywheel::SyncEvent::EndRetrace)
|
||||
{
|
||||
if(delegate_)
|
||||
{
|
||||
if(next_run_length == time_until_vertical_sync_event && next_vertical_sync_event == Flywheel::SyncEvent::EndRetrace) {
|
||||
if(delegate_) {
|
||||
frames_since_last_delegate_call_++;
|
||||
if(frames_since_last_delegate_call_ == 20)
|
||||
{
|
||||
if(frames_since_last_delegate_call_ == 20) {
|
||||
output_lock.unlock();
|
||||
delegate_->crt_did_end_batch_of_frames(this, frames_since_last_delegate_call_, vertical_flywheel_->get_and_reset_number_of_surprises());
|
||||
output_lock.lock();
|
||||
frames_since_last_delegate_call_ = 0;
|
||||
}
|
||||
}
|
||||
@@ -257,42 +247,54 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo
|
||||
|
||||
#pragma mark - stream feeding methods
|
||||
|
||||
void CRT::output_scan(const Scan *const scan)
|
||||
{
|
||||
void CRT::output_scan(const Scan *const scan) {
|
||||
const bool this_is_sync = (scan->type == Scan::Type::Sync);
|
||||
const bool is_trailing_edge = (is_receiving_sync_ && !this_is_sync);
|
||||
const bool is_leading_edge = (!is_receiving_sync_ && this_is_sync);
|
||||
is_receiving_sync_ = this_is_sync;
|
||||
|
||||
// Accumulate: (i) a total of the amount of time in sync; and (ii) the amount of time since sync.
|
||||
if(this_is_sync) { cycles_of_sync_ += scan->number_of_cycles; cycles_since_sync_ = 0; }
|
||||
else cycles_since_sync_ += scan->number_of_cycles;
|
||||
|
||||
bool vsync_requested = false;
|
||||
// If it has been at least half a line since sync ended, then it is safe to decide whether what ended
|
||||
// was vertical sync.
|
||||
if(cycles_since_sync_ > (cycles_per_line_ >> 1)) {
|
||||
// If it was vertical sync, set that flag. If it wasn't, clear the summed amount of sync to avoid
|
||||
// a mistaken vertical sync due to an aggregate of horizontals.
|
||||
vsync_requested = (cycles_of_sync_ > sync_capacitor_charge_threshold_);
|
||||
if(vsync_requested || cycles_of_sync_ < (cycles_per_line_ >> 2))
|
||||
cycles_of_sync_ = 0;
|
||||
}
|
||||
|
||||
// This introduces a blackout period close to the expected vertical sync point in which horizontal syncs are not
|
||||
// recognised, effectively causing the horizontal flywheel to freewheel during that period. This attempts to seek
|
||||
// the problem that vertical sync otherwise often starts halfway through a scanline, which confuses the horizontal
|
||||
// flywheel. I'm currently unclear whether this is an accurate solution to this problem.
|
||||
const bool hsync_requested = is_leading_edge && !vertical_flywheel_->is_near_expected_sync();
|
||||
const bool vsync_requested = is_trailing_edge && (sync_capacitor_charge_level_ >= sync_capacitor_charge_threshold_);
|
||||
|
||||
// simplified colour burst logic: if it's within the back porch we'll take it
|
||||
if(scan->type == Scan::Type::ColourBurst)
|
||||
{
|
||||
if(horizontal_flywheel_->get_current_time() < (horizontal_flywheel_->get_standard_period() * 12) >> 6)
|
||||
{
|
||||
colour_burst_time_ = (uint16_t)horizontal_flywheel_->get_current_time();
|
||||
colour_burst_phase_ = scan->phase;
|
||||
if(scan->type == Scan::Type::ColourBurst) {
|
||||
if(!colour_burst_amplitude_ && horizontal_flywheel_->get_current_time() < (horizontal_flywheel_->get_standard_period() * 12) >> 6) {
|
||||
unsigned int position_phase = (horizontal_flywheel_->get_current_time() * colour_cycle_numerator_ * 256) / phase_denominator_;
|
||||
colour_burst_phase_ = (position_phase + scan->phase) & 255;
|
||||
colour_burst_amplitude_ = scan->amplitude;
|
||||
|
||||
if(colour_burst_phase_adjustment_ != 0xff)
|
||||
colour_burst_phase_ = (colour_burst_phase_ & ~63) + colour_burst_phase_adjustment_;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: inspect raw data for potential colour burst if required
|
||||
|
||||
sync_period_ = is_receiving_sync_ ? (sync_period_ + scan->number_of_cycles) : 0;
|
||||
advance_cycles(scan->number_of_cycles, hsync_requested, vsync_requested, this_is_sync, scan->type);
|
||||
advance_cycles(scan->number_of_cycles, hsync_requested, vsync_requested, scan->type);
|
||||
}
|
||||
|
||||
/*
|
||||
These all merely channel into advance_cycles, supplying appropriate arguments
|
||||
*/
|
||||
void CRT::output_sync(unsigned int number_of_cycles)
|
||||
{
|
||||
void CRT::output_sync(unsigned int number_of_cycles) {
|
||||
Scan scan{
|
||||
.type = Scan::Type::Sync,
|
||||
.number_of_cycles = number_of_cycles
|
||||
@@ -300,8 +302,7 @@ void CRT::output_sync(unsigned int number_of_cycles)
|
||||
output_scan(&scan);
|
||||
}
|
||||
|
||||
void CRT::output_blank(unsigned int number_of_cycles)
|
||||
{
|
||||
void CRT::output_blank(unsigned int number_of_cycles) {
|
||||
Scan scan {
|
||||
.type = Scan::Type::Blank,
|
||||
.number_of_cycles = number_of_cycles
|
||||
@@ -309,8 +310,7 @@ void CRT::output_blank(unsigned int number_of_cycles)
|
||||
output_scan(&scan);
|
||||
}
|
||||
|
||||
void CRT::output_level(unsigned int number_of_cycles)
|
||||
{
|
||||
void CRT::output_level(unsigned int number_of_cycles) {
|
||||
Scan scan {
|
||||
.type = Scan::Type::Level,
|
||||
.number_of_cycles = number_of_cycles,
|
||||
@@ -318,8 +318,7 @@ void CRT::output_level(unsigned int number_of_cycles)
|
||||
output_scan(&scan);
|
||||
}
|
||||
|
||||
void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t amplitude)
|
||||
{
|
||||
void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t amplitude) {
|
||||
Scan scan {
|
||||
.type = Scan::Type::ColourBurst,
|
||||
.number_of_cycles = number_of_cycles,
|
||||
@@ -329,8 +328,17 @@ void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint
|
||||
output_scan(&scan);
|
||||
}
|
||||
|
||||
void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider)
|
||||
{
|
||||
void CRT::output_default_colour_burst(unsigned int number_of_cycles) {
|
||||
Scan scan {
|
||||
.type = Scan::Type::ColourBurst,
|
||||
.number_of_cycles = number_of_cycles,
|
||||
.phase = (uint8_t)((phase_numerator_ * 256) / phase_denominator_ + (is_alernate_line_ ? 128 : 0)),
|
||||
.amplitude = 32
|
||||
};
|
||||
output_scan(&scan);
|
||||
}
|
||||
|
||||
void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider) {
|
||||
openGL_output_builder_.texture_builder.reduce_previous_allocation_to(number_of_cycles / source_divider);
|
||||
Scan scan {
|
||||
.type = Scan::Type::Data,
|
||||
@@ -339,8 +347,7 @@ void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider
|
||||
output_scan(&scan);
|
||||
}
|
||||
|
||||
Outputs::CRT::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_of_lines, int first_cycle_after_sync, int number_of_cycles, float aspect_ratio)
|
||||
{
|
||||
Outputs::CRT::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_of_lines, int first_cycle_after_sync, int number_of_cycles, float aspect_ratio) {
|
||||
first_cycle_after_sync *= time_multiplier_;
|
||||
number_of_cycles *= time_multiplier_;
|
||||
|
||||
@@ -376,13 +383,10 @@ Outputs::CRT::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_
|
||||
// adjust to ensure aspect ratio is correct
|
||||
float adjusted_aspect_ratio = (3.0f*aspect_ratio / 4.0f);
|
||||
float ideal_width = height * adjusted_aspect_ratio;
|
||||
if(ideal_width > width)
|
||||
{
|
||||
if(ideal_width > width) {
|
||||
start_x -= (ideal_width - width) * 0.5f;
|
||||
width = ideal_width;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
float ideal_height = width / adjusted_aspect_ratio;
|
||||
start_y -= (ideal_height - height) * 0.5f;
|
||||
height = ideal_height;
|
||||
|
||||
@@ -46,7 +46,6 @@ class CRT {
|
||||
int sync_capacitor_charge_threshold_; // this charges up during times of sync and depletes otherwise; needs to hit a required threshold to trigger a vertical sync
|
||||
unsigned int sync_period_;
|
||||
|
||||
// each call to output_* generates a scan. A two-slot queue for scans allows edge extensions.
|
||||
struct Scan {
|
||||
enum Type {
|
||||
Sync, Level, Data, Blank, ColourBurst
|
||||
@@ -60,12 +59,14 @@ class CRT {
|
||||
};
|
||||
void output_scan(const Scan *scan);
|
||||
|
||||
uint8_t colour_burst_phase_, colour_burst_amplitude_;
|
||||
uint16_t colour_burst_time_;
|
||||
uint8_t colour_burst_phase_, colour_burst_amplitude_, colour_burst_phase_adjustment_;
|
||||
bool is_writing_composite_run_;
|
||||
|
||||
unsigned int phase_denominator_, phase_numerator_, colour_cycle_numerator_;
|
||||
bool is_alernate_line_, phase_alternates_;
|
||||
|
||||
// the outer entry point for dispatching output_sync, output_blank, output_level and output_data
|
||||
void advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Scan::Type type);
|
||||
void advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bool vsync_requested, const Scan::Type type);
|
||||
|
||||
// the inner entry point that determines whether and when the next sync event will occur within
|
||||
// the current output window
|
||||
@@ -80,10 +81,23 @@ class CRT {
|
||||
uint16_t x1, y;
|
||||
} output_run_;
|
||||
|
||||
// The delegate
|
||||
// the delegate
|
||||
Delegate *delegate_;
|
||||
unsigned int frames_since_last_delegate_call_;
|
||||
|
||||
// queued tasks for the OpenGL queue; performed before the next draw
|
||||
std::mutex function_mutex_;
|
||||
std::vector<std::function<void(void)>> enqueued_openGL_functions_;
|
||||
inline void enqueue_openGL_function(const std::function<void(void)> &function) {
|
||||
std::lock_guard<std::mutex> function_guard(function_mutex_);
|
||||
enqueued_openGL_functions_.push_back(function);
|
||||
}
|
||||
|
||||
// sync counter, for determining vertical sync
|
||||
unsigned int cycles_of_sync_;
|
||||
unsigned int cycles_since_sync_;
|
||||
unsigned int cycles_per_line_;
|
||||
|
||||
public:
|
||||
/*! Constructs the CRT with a specified clock rate, height and colour subcarrier frequency.
|
||||
The requested number of buffers, each with the requested number of bytes per pixel,
|
||||
@@ -91,7 +105,7 @@ class CRT {
|
||||
|
||||
@param cycles_per_line The clock rate at which this CRT will be driven, specified as the number
|
||||
of cycles expected to take up one whole scanline of the display.
|
||||
|
||||
|
||||
@param common_output_divisor The greatest a priori common divisor of all cycle counts that will be
|
||||
supplied to @c output_sync, @c output_data, etc; supply 1 if no greater divisor is known. For many
|
||||
machines output will run at a fixed multiple of the clock rate; knowing this divisor can improve
|
||||
@@ -113,7 +127,7 @@ class CRT {
|
||||
|
||||
@see @c set_rgb_sampling_function , @c set_composite_sampling_function
|
||||
*/
|
||||
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, unsigned int buffer_depth);
|
||||
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);
|
||||
|
||||
/*! Constructs the CRT with the specified clock rate, with the display height and colour
|
||||
subcarrier frequency dictated by a standard display type and with the requested number of
|
||||
@@ -126,7 +140,7 @@ class CRT {
|
||||
|
||||
/*! Resets the CRT with new timing information. The CRT then continues as though the new timing had
|
||||
been provided at construction. */
|
||||
void 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);
|
||||
void 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);
|
||||
|
||||
/*! Resets the CRT with new timing information derived from a new display type. The CRT then continues
|
||||
as though the new timing had been provided at construction. */
|
||||
@@ -175,6 +189,12 @@ class CRT {
|
||||
*/
|
||||
void output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t amplitude);
|
||||
|
||||
/*! Outputs a colour burst exactly in phase with CRT expectations using the idiomatic amplitude.
|
||||
|
||||
@param number_of_cycles The length of the colour burst;
|
||||
*/
|
||||
void output_default_colour_burst(unsigned int number_of_cycles);
|
||||
|
||||
/*! Attempts to allocate the given number of output samples for writing.
|
||||
|
||||
The beginning of the most recently allocated area is used as the start
|
||||
@@ -186,8 +206,7 @@ class CRT {
|
||||
@param required_length The number of samples to allocate.
|
||||
@returns A pointer to the allocated area if room is available; @c nullptr otherwise.
|
||||
*/
|
||||
inline uint8_t *allocate_write_area(size_t required_length)
|
||||
{
|
||||
inline uint8_t *allocate_write_area(size_t required_length) {
|
||||
std::unique_lock<std::mutex> output_lock = openGL_output_builder_.get_output_lock();
|
||||
return openGL_output_builder_.texture_builder.allocate_write_area(required_length);
|
||||
}
|
||||
@@ -195,8 +214,15 @@ class CRT {
|
||||
/*! Causes appropriate OpenGL or OpenGL ES calls to be issued in order to draw the current CRT state.
|
||||
The caller is responsible for ensuring that a valid OpenGL context exists for the duration of this call.
|
||||
*/
|
||||
inline void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty)
|
||||
{
|
||||
inline void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) {
|
||||
{
|
||||
std::lock_guard<std::mutex> function_guard(function_mutex_);
|
||||
for(std::function<void(void)> function : enqueued_openGL_functions_)
|
||||
{
|
||||
function();
|
||||
}
|
||||
enqueued_openGL_functions_.clear();
|
||||
}
|
||||
openGL_output_builder_.draw_frame(output_width, output_height, only_if_dirty);
|
||||
}
|
||||
|
||||
@@ -207,9 +233,10 @@ class CRT {
|
||||
currently held by the CRT will be deleted now via calls to glDeleteTexture and equivalent. If
|
||||
@c false then the references are simply marked as invalid.
|
||||
*/
|
||||
inline void set_openGL_context_will_change(bool should_delete_resources)
|
||||
{
|
||||
openGL_output_builder_.set_openGL_context_will_change(should_delete_resources);
|
||||
inline void set_openGL_context_will_change(bool should_delete_resources) {
|
||||
enqueue_openGL_function([should_delete_resources, this] {
|
||||
openGL_output_builder_.set_openGL_context_will_change(should_delete_resources);
|
||||
});
|
||||
}
|
||||
|
||||
/*! Sets a function that will map from whatever data the machine provided to a composite signal.
|
||||
@@ -219,11 +246,32 @@ class CRT {
|
||||
that evaluates to the composite signal level as a function of a source buffer, sampling location, colour
|
||||
carrier phase and amplitude.
|
||||
*/
|
||||
inline void set_composite_sampling_function(const char *shader)
|
||||
{
|
||||
openGL_output_builder_.set_composite_sampling_function(shader);
|
||||
inline void set_composite_sampling_function(const std::string &shader) {
|
||||
enqueue_openGL_function([shader, this] {
|
||||
openGL_output_builder_.set_composite_sampling_function(shader);
|
||||
});
|
||||
}
|
||||
|
||||
enum CompositeSourceType {
|
||||
/// The composite function provides continuous output.
|
||||
Continuous,
|
||||
/// The composite function provides discrete output with four unique values per colour cycle.
|
||||
DiscreteFourSamplesPerCycle
|
||||
};
|
||||
|
||||
/*! Provides information about the type of output the composite sampling function provides — discrete or continuous.
|
||||
|
||||
This is necessary because the CRT implementation samples discretely and therefore can use fewer intermediate
|
||||
samples if it can exactly duplicate the sampling rate and placement of the composite sampling function.
|
||||
|
||||
A continuous function is assumed by default.
|
||||
|
||||
@param type The type of output provided by the function supplied to `set_composite_sampling_function`.
|
||||
@param offset_of_first_sample The relative position within a full cycle of the colour subcarrier at which the
|
||||
first sample falls. E.g. 0.125 means "at 1/8th of the way through the complete cycle".
|
||||
*/
|
||||
void set_composite_function_type(CompositeSourceType type, float offset_of_first_sample = 0.0f);
|
||||
|
||||
/*! Sets a function that will map from whatever data the machine provided to an RGB signal.
|
||||
|
||||
If the output mode is composite then a default mapping from RGB to the display's composite
|
||||
@@ -237,25 +285,27 @@ class CRT {
|
||||
* `vec2 coordinate` representing the source buffer location to sample from in the range [0, 1); and
|
||||
* `vec2 icoordinate` representing the source buffer location to sample from as a pixel count, for easier multiple-pixels-per-byte unpacking.
|
||||
*/
|
||||
inline void set_rgb_sampling_function(const char *shader)
|
||||
{
|
||||
openGL_output_builder_.set_rgb_sampling_function(shader);
|
||||
inline void set_rgb_sampling_function(const std::string &shader) {
|
||||
enqueue_openGL_function([shader, this] {
|
||||
openGL_output_builder_.set_rgb_sampling_function(shader);
|
||||
});
|
||||
}
|
||||
|
||||
inline void set_output_device(OutputDevice output_device)
|
||||
{
|
||||
openGL_output_builder_.set_output_device(output_device);
|
||||
inline void set_output_device(OutputDevice output_device) {
|
||||
enqueue_openGL_function([output_device, this] {
|
||||
openGL_output_builder_.set_output_device(output_device);
|
||||
});
|
||||
}
|
||||
|
||||
inline void set_visible_area(Rect visible_area)
|
||||
{
|
||||
openGL_output_builder_.set_visible_area(visible_area);
|
||||
inline void set_visible_area(Rect visible_area) {
|
||||
enqueue_openGL_function([visible_area, this] {
|
||||
openGL_output_builder_.set_visible_area(visible_area);
|
||||
});
|
||||
}
|
||||
|
||||
Rect get_rect_for_area(int first_line_after_sync, int number_of_lines, int first_cycle_after_sync, int number_of_cycles, float aspect_ratio);
|
||||
|
||||
inline void set_delegate(Delegate *delegate)
|
||||
{
|
||||
inline void set_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -11,37 +11,30 @@
|
||||
using namespace Outputs::CRT;
|
||||
|
||||
ArrayBuilder::ArrayBuilder(size_t input_size, size_t output_size) :
|
||||
output_(output_size, nullptr),
|
||||
input_(input_size, nullptr)
|
||||
{}
|
||||
output_(output_size, nullptr),
|
||||
input_(input_size, nullptr) {}
|
||||
|
||||
ArrayBuilder::ArrayBuilder(size_t input_size, size_t output_size, std::function<void(bool is_input, uint8_t *, size_t)> submission_function) :
|
||||
output_(output_size, submission_function),
|
||||
input_(input_size, submission_function)
|
||||
{}
|
||||
output_(output_size, submission_function),
|
||||
input_(input_size, submission_function) {}
|
||||
|
||||
bool ArrayBuilder::is_full()
|
||||
{
|
||||
bool ArrayBuilder::is_full() {
|
||||
bool was_full;
|
||||
was_full = is_full_;
|
||||
return was_full;
|
||||
}
|
||||
|
||||
uint8_t *ArrayBuilder::get_input_storage(size_t size)
|
||||
{
|
||||
uint8_t *ArrayBuilder::get_input_storage(size_t size) {
|
||||
return get_storage(size, input_);
|
||||
}
|
||||
|
||||
uint8_t *ArrayBuilder::get_output_storage(size_t size)
|
||||
{
|
||||
uint8_t *ArrayBuilder::get_output_storage(size_t size) {
|
||||
return get_storage(size, output_);
|
||||
}
|
||||
|
||||
void ArrayBuilder::flush(const std::function<void(uint8_t *input, size_t input_size, uint8_t *output, size_t output_size)> &function)
|
||||
{
|
||||
if(!is_full_)
|
||||
{
|
||||
size_t input_size, output_size;
|
||||
void ArrayBuilder::flush(const std::function<void(uint8_t *input, size_t input_size, uint8_t *output, size_t output_size)> &function) {
|
||||
if(!is_full_) {
|
||||
size_t input_size = 0, output_size = 0;
|
||||
uint8_t *input = input_.get_unflushed(input_size);
|
||||
uint8_t *output = output_.get_unflushed(output_size);
|
||||
function(input, input_size, output, output_size);
|
||||
@@ -51,24 +44,20 @@ void ArrayBuilder::flush(const std::function<void(uint8_t *input, size_t input_s
|
||||
}
|
||||
}
|
||||
|
||||
void ArrayBuilder::bind_input()
|
||||
{
|
||||
void ArrayBuilder::bind_input() {
|
||||
input_.bind();
|
||||
}
|
||||
|
||||
void ArrayBuilder::bind_output()
|
||||
{
|
||||
void ArrayBuilder::bind_output() {
|
||||
output_.bind();
|
||||
}
|
||||
|
||||
ArrayBuilder::Submission ArrayBuilder::submit()
|
||||
{
|
||||
ArrayBuilder::Submission ArrayBuilder::submit() {
|
||||
ArrayBuilder::Submission submission;
|
||||
|
||||
submission.input_size = input_.submit(true);
|
||||
submission.output_size = output_.submit(false);
|
||||
if(is_full_)
|
||||
{
|
||||
if(is_full_) {
|
||||
is_full_ = false;
|
||||
input_.reset();
|
||||
output_.reset();
|
||||
@@ -78,12 +67,10 @@ ArrayBuilder::Submission ArrayBuilder::submit()
|
||||
}
|
||||
|
||||
ArrayBuilder::Buffer::Buffer(size_t size, std::function<void(bool is_input, uint8_t *, size_t)> submission_function) :
|
||||
is_full(false), was_reset(false),
|
||||
submission_function_(submission_function),
|
||||
allocated_data(0), flushed_data(0), submitted_data(0)
|
||||
{
|
||||
if(!submission_function_)
|
||||
{
|
||||
is_full(false),
|
||||
submission_function_(submission_function),
|
||||
allocated_data(0), flushed_data(0), submitted_data(0) {
|
||||
if(!submission_function_) {
|
||||
glGenBuffers(1, &buffer);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, buffer);
|
||||
glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)size, NULL, GL_STREAM_DRAW);
|
||||
@@ -91,23 +78,19 @@ ArrayBuilder::Buffer::Buffer(size_t size, std::function<void(bool is_input, uint
|
||||
data.resize(size);
|
||||
}
|
||||
|
||||
ArrayBuilder::Buffer::~Buffer()
|
||||
{
|
||||
ArrayBuilder::Buffer::~Buffer() {
|
||||
if(!submission_function_)
|
||||
glDeleteBuffers(1, &buffer);
|
||||
}
|
||||
|
||||
uint8_t *ArrayBuilder::get_storage(size_t size, Buffer &buffer)
|
||||
{
|
||||
uint8_t *ArrayBuilder::get_storage(size_t size, Buffer &buffer) {
|
||||
uint8_t *pointer = buffer.get_storage(size);
|
||||
if(!pointer) is_full_ = true;
|
||||
return pointer;
|
||||
}
|
||||
|
||||
uint8_t *ArrayBuilder::Buffer::get_storage(size_t size)
|
||||
{
|
||||
if(is_full || allocated_data + size > data.size())
|
||||
{
|
||||
uint8_t *ArrayBuilder::Buffer::get_storage(size_t size) {
|
||||
if(is_full || allocated_data + size > data.size()) {
|
||||
is_full = true;
|
||||
return nullptr;
|
||||
}
|
||||
@@ -116,44 +99,30 @@ uint8_t *ArrayBuilder::Buffer::get_storage(size_t size)
|
||||
return pointer;
|
||||
}
|
||||
|
||||
uint8_t *ArrayBuilder::Buffer::get_unflushed(size_t &size)
|
||||
{
|
||||
if(is_full)
|
||||
{
|
||||
uint8_t *ArrayBuilder::Buffer::get_unflushed(size_t &size) {
|
||||
if(is_full) {
|
||||
return nullptr;
|
||||
}
|
||||
size = allocated_data - flushed_data;
|
||||
return &data[flushed_data];
|
||||
}
|
||||
|
||||
void ArrayBuilder::Buffer::flush()
|
||||
{
|
||||
if(submitted_data)
|
||||
{
|
||||
memcpy(data.data(), &data[submitted_data], allocated_data - submitted_data);
|
||||
void ArrayBuilder::Buffer::flush() {
|
||||
if(submitted_data) {
|
||||
memmove(data.data(), &data[submitted_data], allocated_data - submitted_data);
|
||||
allocated_data -= submitted_data;
|
||||
flushed_data -= submitted_data;
|
||||
submitted_data = 0;
|
||||
}
|
||||
|
||||
flushed_data = allocated_data;
|
||||
|
||||
if(was_reset)
|
||||
{
|
||||
allocated_data = 0;
|
||||
flushed_data = 0;
|
||||
submitted_data = 0;
|
||||
was_reset = false;
|
||||
}
|
||||
}
|
||||
|
||||
size_t ArrayBuilder::Buffer::submit(bool is_input)
|
||||
{
|
||||
size_t ArrayBuilder::Buffer::submit(bool is_input) {
|
||||
size_t length = flushed_data;
|
||||
if(submission_function_)
|
||||
if(submission_function_) {
|
||||
submission_function_(is_input, data.data(), length);
|
||||
else
|
||||
{
|
||||
} else {
|
||||
glBindBuffer(GL_ARRAY_BUFFER, buffer);
|
||||
uint8_t *destination = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, (GLsizeiptr)length, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_FLUSH_EXPLICIT_BIT);
|
||||
memcpy(destination, data.data(), length);
|
||||
@@ -164,13 +133,13 @@ size_t ArrayBuilder::Buffer::submit(bool is_input)
|
||||
return length;
|
||||
}
|
||||
|
||||
void ArrayBuilder::Buffer::bind()
|
||||
{
|
||||
void ArrayBuilder::Buffer::bind() {
|
||||
glBindBuffer(GL_ARRAY_BUFFER, buffer);
|
||||
}
|
||||
|
||||
void ArrayBuilder::Buffer::reset()
|
||||
{
|
||||
was_reset = true;
|
||||
void ArrayBuilder::Buffer::reset() {
|
||||
is_full = false;
|
||||
allocated_data = 0;
|
||||
flushed_data = 0;
|
||||
submitted_data = 0;
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ class ArrayBuilder {
|
||||
void reset();
|
||||
|
||||
private:
|
||||
bool is_full, was_reset;
|
||||
bool is_full;
|
||||
GLuint buffer;
|
||||
std::function<void(bool is_input, uint8_t *, size_t)> submission_function_;
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
//
|
||||
|
||||
#include "CRT.hpp"
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cmath>
|
||||
|
||||
#include "CRTOpenGL.hpp"
|
||||
#include "../../../SignalProcessing/FIRFilter.hpp"
|
||||
@@ -16,29 +17,24 @@
|
||||
using namespace Outputs::CRT;
|
||||
|
||||
namespace {
|
||||
static const GLenum composite_texture_unit = GL_TEXTURE0;
|
||||
static const GLenum separated_texture_unit = GL_TEXTURE1;
|
||||
static const GLenum filtered_y_texture_unit = GL_TEXTURE2;
|
||||
static const GLenum filtered_texture_unit = GL_TEXTURE3;
|
||||
static const GLenum source_data_texture_unit = GL_TEXTURE4;
|
||||
static const GLenum pixel_accumulation_texture_unit = GL_TEXTURE5;
|
||||
static const GLenum source_data_texture_unit = GL_TEXTURE0;
|
||||
static const GLenum pixel_accumulation_texture_unit = GL_TEXTURE1;
|
||||
|
||||
static const GLenum composite_texture_unit = GL_TEXTURE2;
|
||||
static const GLenum separated_texture_unit = GL_TEXTURE3;
|
||||
static const GLenum filtered_texture_unit = GL_TEXTURE4;
|
||||
|
||||
static const GLenum work_texture_unit = GL_TEXTURE2;
|
||||
}
|
||||
|
||||
OpenGLOutputBuilder::OpenGLOutputBuilder(size_t bytes_per_pixel) :
|
||||
visible_area_(Rect(0, 0, 1, 1)),
|
||||
composite_src_output_y_(0),
|
||||
composite_shader_(nullptr),
|
||||
rgb_shader_(nullptr),
|
||||
last_output_width_(0),
|
||||
last_output_height_(0),
|
||||
fence_(nullptr),
|
||||
texture_builder(bytes_per_pixel, source_data_texture_unit),
|
||||
array_builder(SourceVertexBufferDataSize, OutputVertexBufferDataSize),
|
||||
composite_texture_(IntermediateBufferWidth, IntermediateBufferHeight, composite_texture_unit),
|
||||
separated_texture_(IntermediateBufferWidth, IntermediateBufferHeight, separated_texture_unit),
|
||||
filtered_y_texture_(IntermediateBufferWidth, IntermediateBufferHeight, filtered_y_texture_unit),
|
||||
filtered_texture_(IntermediateBufferWidth, IntermediateBufferHeight, filtered_texture_unit)
|
||||
{
|
||||
visible_area_(Rect(0, 0, 1, 1)),
|
||||
composite_src_output_y_(0),
|
||||
last_output_width_(0),
|
||||
last_output_height_(0),
|
||||
fence_(nullptr),
|
||||
texture_builder(bytes_per_pixel, source_data_texture_unit),
|
||||
array_builder(SourceVertexBufferDataSize, OutputVertexBufferDataSize) {
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_CONSTANT_COLOR);
|
||||
glBlendColor(0.6f, 0.6f, 0.6f, 1.0f);
|
||||
|
||||
@@ -47,24 +43,43 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(size_t bytes_per_pixel) :
|
||||
|
||||
// create the source vertex array
|
||||
glGenVertexArrays(1, &source_vertex_array_);
|
||||
|
||||
bool supports_texture_barrier = false;
|
||||
#ifdef GL_NV_texture_barrier
|
||||
GLint number_of_extensions;
|
||||
glGetIntegerv(GL_NUM_EXTENSIONS, &number_of_extensions);
|
||||
|
||||
for(GLuint c = 0; c < (GLuint)number_of_extensions; c++) {
|
||||
const char *extension_name = (const char *)glGetStringi(GL_EXTENSIONS, c);
|
||||
if(!strcmp(extension_name, "GL_NV_texture_barrier")) {
|
||||
supports_texture_barrier = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// if(supports_texture_barrier) {
|
||||
// work_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight*2, work_texture_unit));
|
||||
// } else {
|
||||
composite_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, composite_texture_unit, GL_NEAREST));
|
||||
separated_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, separated_texture_unit, GL_NEAREST));
|
||||
filtered_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, filtered_texture_unit, GL_LINEAR));
|
||||
// }
|
||||
}
|
||||
|
||||
OpenGLOutputBuilder::~OpenGLOutputBuilder()
|
||||
{
|
||||
OpenGLOutputBuilder::~OpenGLOutputBuilder() {
|
||||
glDeleteVertexArrays(1, &output_vertex_array_);
|
||||
|
||||
free(composite_shader_);
|
||||
free(rgb_shader_);
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty)
|
||||
{
|
||||
bool OpenGLOutputBuilder::get_is_television_output() {
|
||||
return output_device_ == Television || !rgb_input_shader_program_;
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) {
|
||||
// lock down any other draw_frames
|
||||
draw_mutex_.lock();
|
||||
|
||||
// establish essentials
|
||||
if(!output_shader_program_)
|
||||
{
|
||||
if(!output_shader_program_) {
|
||||
prepare_composite_input_shaders();
|
||||
prepare_rgb_input_shaders();
|
||||
prepare_source_vertex_array();
|
||||
@@ -76,11 +91,9 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
|
||||
set_colour_space_uniforms();
|
||||
}
|
||||
|
||||
if(fence_ != nullptr)
|
||||
{
|
||||
if(fence_ != nullptr) {
|
||||
// if the GPU is still busy, don't wait; we'll catch it next time
|
||||
if(glClientWaitSync(fence_, GL_SYNC_FLUSH_COMMANDS_BIT, only_if_dirty ? 0 : GL_TIMEOUT_IGNORED) == GL_TIMEOUT_EXPIRED)
|
||||
{
|
||||
if(glClientWaitSync(fence_, GL_SYNC_FLUSH_COMMANDS_BIT, only_if_dirty ? 0 : GL_TIMEOUT_IGNORED) == GL_TIMEOUT_EXPIRED) {
|
||||
draw_mutex_.unlock();
|
||||
return;
|
||||
}
|
||||
@@ -89,11 +102,9 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
|
||||
}
|
||||
|
||||
// make sure there's a target to draw to
|
||||
if(!framebuffer_ || framebuffer_->get_height() != output_height || framebuffer_->get_width() != output_width)
|
||||
{
|
||||
std::unique_ptr<OpenGL::TextureTarget> new_framebuffer(new OpenGL::TextureTarget((GLsizei)output_width, (GLsizei)output_height, pixel_accumulation_texture_unit));
|
||||
if(framebuffer_)
|
||||
{
|
||||
if(!framebuffer_ || framebuffer_->get_height() != output_height || framebuffer_->get_width() != output_width) {
|
||||
std::unique_ptr<OpenGL::TextureTarget> new_framebuffer(new OpenGL::TextureTarget((GLsizei)output_width, (GLsizei)output_height, pixel_accumulation_texture_unit, GL_LINEAR));
|
||||
if(framebuffer_) {
|
||||
new_framebuffer->bind_framebuffer();
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
@@ -123,55 +134,62 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
|
||||
output_mutex_.unlock();
|
||||
|
||||
struct RenderStage {
|
||||
OpenGL::TextureTarget *const target;
|
||||
OpenGL::Shader *const shader;
|
||||
OpenGL::TextureTarget *const target;
|
||||
float clear_colour[3];
|
||||
};
|
||||
|
||||
// for composite video, go through four steps to get to something that can be painted to the output
|
||||
RenderStage composite_render_stages[] =
|
||||
{
|
||||
{&composite_texture_, composite_input_shader_program_.get(), {0.0, 0.0, 0.0}},
|
||||
{&separated_texture_, composite_separation_filter_program_.get(), {0.0, 0.5, 0.5}},
|
||||
{&filtered_y_texture_, composite_y_filter_shader_program_.get(), {0.0, 0.5, 0.5}},
|
||||
{&filtered_texture_, composite_chrominance_filter_shader_program_.get(), {0.0, 0.0, 0.0}},
|
||||
RenderStage composite_render_stages[] = {
|
||||
{composite_input_shader_program_.get(), composite_texture_.get(), {0.0, 0.0, 0.0}},
|
||||
{composite_separation_filter_program_.get(), separated_texture_.get(), {0.0, 0.5, 0.5}},
|
||||
{composite_chrominance_filter_shader_program_.get(), filtered_texture_.get(), {0.0, 0.0, 0.0}},
|
||||
{nullptr}
|
||||
};
|
||||
|
||||
// for RGB video, there's only two steps
|
||||
RenderStage rgb_render_stages[] =
|
||||
{
|
||||
{&composite_texture_, rgb_input_shader_program_.get(), {0.0, 0.0, 0.0}},
|
||||
{&filtered_texture_, rgb_filter_shader_program_.get(), {0.0, 0.0, 0.0}},
|
||||
RenderStage rgb_render_stages[] = {
|
||||
{rgb_input_shader_program_.get(), composite_texture_.get(), {0.0, 0.0, 0.0}},
|
||||
{rgb_filter_shader_program_.get(), filtered_texture_.get(), {0.0, 0.0, 0.0}},
|
||||
{nullptr}
|
||||
};
|
||||
|
||||
RenderStage *active_pipeline = (output_device_ == Television || !rgb_input_shader_program_) ? composite_render_stages : rgb_render_stages;
|
||||
RenderStage *active_pipeline = get_is_television_output() ? composite_render_stages : rgb_render_stages;
|
||||
|
||||
if(array_submission.input_size || array_submission.output_size)
|
||||
{
|
||||
if(array_submission.input_size || array_submission.output_size) {
|
||||
// all drawing will be from the source vertex array and without blending
|
||||
glBindVertexArray(source_vertex_array_);
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
while(active_pipeline->target)
|
||||
{
|
||||
#ifdef GL_NV_texture_barrier
|
||||
if(work_texture_) {
|
||||
work_texture_->bind_framebuffer();
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
#endif
|
||||
|
||||
while(active_pipeline->shader) {
|
||||
// switch to the framebuffer and shader associated with this stage
|
||||
active_pipeline->shader->bind();
|
||||
active_pipeline->target->bind_framebuffer();
|
||||
|
||||
// if this is the final stage before painting to the CRT, clear the framebuffer before drawing in order to blank out
|
||||
// those portions for which no input was provided
|
||||
if(!active_pipeline[1].target)
|
||||
{
|
||||
glClearColor(active_pipeline->clear_colour[0], active_pipeline->clear_colour[1], active_pipeline->clear_colour[2], 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
if(!work_texture_) {
|
||||
active_pipeline->target->bind_framebuffer();
|
||||
|
||||
// if this is the final stage before painting to the CRT, clear the framebuffer before drawing in order to blank out
|
||||
// those portions for which no input was provided
|
||||
// if(!active_pipeline[1].shader) {
|
||||
glClearColor(active_pipeline->clear_colour[0], active_pipeline->clear_colour[1], active_pipeline->clear_colour[2], 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
// }
|
||||
}
|
||||
|
||||
// draw
|
||||
glDrawArraysInstanced(GL_LINES, 0, 2, (GLsizei)array_submission.input_size / SourceVertexSize);
|
||||
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei)array_submission.input_size / SourceVertexSize);
|
||||
|
||||
active_pipeline++;
|
||||
#ifdef GL_NV_texture_barrier
|
||||
glTextureBarrierNV();
|
||||
#endif
|
||||
}
|
||||
|
||||
// prepare to transfer to framebuffer
|
||||
@@ -182,8 +200,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
|
||||
glEnable(GL_BLEND);
|
||||
|
||||
// update uniforms, then bind the target
|
||||
if(last_output_width_ != output_width || last_output_height_ != output_height)
|
||||
{
|
||||
if(last_output_width_ != output_width || last_output_height_ != output_height) {
|
||||
output_shader_program_->set_output_size(output_width, output_height, visible_area_);
|
||||
last_output_width_ = output_width;
|
||||
last_output_height_ = output_height;
|
||||
@@ -194,6 +211,10 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
|
||||
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei)array_submission.output_size / OutputVertexSize);
|
||||
}
|
||||
|
||||
#ifdef GL_NV_texture_barrier
|
||||
glTextureBarrierNV();
|
||||
#endif
|
||||
|
||||
// copy framebuffer to the intended place
|
||||
glDisable(GL_BLEND);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
@@ -207,11 +228,9 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
|
||||
draw_mutex_.unlock();
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::reset_all_OpenGL_state()
|
||||
{
|
||||
void OpenGLOutputBuilder::reset_all_OpenGL_state() {
|
||||
composite_input_shader_program_ = nullptr;
|
||||
composite_separation_filter_program_ = nullptr;
|
||||
composite_y_filter_shader_program_ = nullptr;
|
||||
composite_chrominance_filter_shader_program_ = nullptr;
|
||||
rgb_input_shader_program_ = nullptr;
|
||||
rgb_filter_shader_program_ = nullptr;
|
||||
@@ -220,54 +239,52 @@ void OpenGLOutputBuilder::reset_all_OpenGL_state()
|
||||
last_output_width_ = last_output_height_ = 0;
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::set_openGL_context_will_change(bool should_delete_resources)
|
||||
{
|
||||
void OpenGLOutputBuilder::set_openGL_context_will_change(bool should_delete_resources) {
|
||||
output_mutex_.lock();
|
||||
reset_all_OpenGL_state();
|
||||
output_mutex_.unlock();
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::set_composite_sampling_function(const char *shader)
|
||||
{
|
||||
output_mutex_.lock();
|
||||
composite_shader_ = strdup(shader);
|
||||
void OpenGLOutputBuilder::set_composite_sampling_function(const std::string &shader) {
|
||||
std::lock_guard<std::mutex> lock_guard(output_mutex_);
|
||||
composite_shader_ = shader;
|
||||
reset_all_OpenGL_state();
|
||||
output_mutex_.unlock();
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::set_rgb_sampling_function(const char *shader)
|
||||
{
|
||||
output_mutex_.lock();
|
||||
rgb_shader_ = strdup(shader);
|
||||
void OpenGLOutputBuilder::set_rgb_sampling_function(const std::string &shader) {
|
||||
std::lock_guard<std::mutex> lock_guard(output_mutex_);
|
||||
rgb_shader_ = shader;
|
||||
reset_all_OpenGL_state();
|
||||
output_mutex_.unlock();
|
||||
}
|
||||
|
||||
#pragma mark - Program compilation
|
||||
|
||||
void OpenGLOutputBuilder::prepare_composite_input_shaders()
|
||||
{
|
||||
void OpenGLOutputBuilder::prepare_composite_input_shaders() {
|
||||
composite_input_shader_program_ = OpenGL::IntermediateShader::make_source_conversion_shader(composite_shader_, rgb_shader_);
|
||||
composite_input_shader_program_->set_source_texture_unit(source_data_texture_unit);
|
||||
composite_input_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
|
||||
|
||||
composite_separation_filter_program_ = OpenGL::IntermediateShader::make_chroma_luma_separation_shader();
|
||||
composite_separation_filter_program_->set_source_texture_unit(composite_texture_unit);
|
||||
composite_separation_filter_program_->set_source_texture_unit(work_texture_ ? work_texture_unit : composite_texture_unit);
|
||||
composite_separation_filter_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
|
||||
|
||||
composite_y_filter_shader_program_ = OpenGL::IntermediateShader::make_luma_filter_shader();
|
||||
composite_y_filter_shader_program_->set_source_texture_unit(separated_texture_unit);
|
||||
composite_y_filter_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
|
||||
|
||||
composite_chrominance_filter_shader_program_ = OpenGL::IntermediateShader::make_chroma_filter_shader();
|
||||
composite_chrominance_filter_shader_program_->set_source_texture_unit(filtered_y_texture_unit);
|
||||
composite_chrominance_filter_shader_program_->set_source_texture_unit(work_texture_ ? work_texture_unit : separated_texture_unit);
|
||||
composite_chrominance_filter_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
|
||||
|
||||
if(work_texture_) {
|
||||
composite_input_shader_program_->set_is_double_height(true, 0.0f, 0.0f);
|
||||
composite_separation_filter_program_->set_is_double_height(true, 0.0f, 0.5f);
|
||||
composite_chrominance_filter_shader_program_->set_is_double_height(true, 0.5f, 0.0f);
|
||||
} else {
|
||||
composite_input_shader_program_->set_is_double_height(false);
|
||||
composite_separation_filter_program_->set_is_double_height(false);
|
||||
composite_chrominance_filter_shader_program_->set_is_double_height(false);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::prepare_rgb_input_shaders()
|
||||
{
|
||||
if(rgb_shader_)
|
||||
{
|
||||
void OpenGLOutputBuilder::prepare_rgb_input_shaders() {
|
||||
if(rgb_shader_.size()) {
|
||||
rgb_input_shader_program_ = OpenGL::IntermediateShader::make_rgb_source_shader(rgb_shader_);
|
||||
rgb_input_shader_program_->set_source_texture_unit(source_data_texture_unit);
|
||||
rgb_input_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
|
||||
@@ -278,10 +295,8 @@ void OpenGLOutputBuilder::prepare_rgb_input_shaders()
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::prepare_source_vertex_array()
|
||||
{
|
||||
if(composite_input_shader_program_)
|
||||
{
|
||||
void OpenGLOutputBuilder::prepare_source_vertex_array() {
|
||||
if(composite_input_shader_program_) {
|
||||
glBindVertexArray(source_vertex_array_);
|
||||
array_builder.bind_input();
|
||||
|
||||
@@ -292,16 +307,15 @@ void OpenGLOutputBuilder::prepare_source_vertex_array()
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::prepare_output_shader()
|
||||
{
|
||||
void OpenGLOutputBuilder::prepare_output_shader() {
|
||||
output_shader_program_ = OpenGL::OutputShader::make_shader("", "texture(texID, srcCoordinatesVarying).rgb", false);
|
||||
output_shader_program_->set_source_texture_unit(filtered_texture_unit);
|
||||
output_shader_program_->set_source_texture_unit(work_texture_ ? work_texture_unit : filtered_texture_unit);
|
||||
// output_shader_program_->set_source_texture_unit(composite_texture_unit);
|
||||
output_shader_program_->set_origin_is_double_height(!!work_texture_);
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::prepare_output_vertex_array()
|
||||
{
|
||||
if(output_shader_program_)
|
||||
{
|
||||
void OpenGLOutputBuilder::prepare_output_vertex_array() {
|
||||
if(output_shader_program_) {
|
||||
glBindVertexArray(output_vertex_array_);
|
||||
array_builder.bind_output();
|
||||
output_shader_program_->enable_vertex_attribute_with_pointer("horizontal", 2, GL_UNSIGNED_SHORT, GL_FALSE, OutputVertexSize, (void *)OutputVertexOffsetOfHorizontal, 1);
|
||||
@@ -311,20 +325,18 @@ void OpenGLOutputBuilder::prepare_output_vertex_array()
|
||||
|
||||
#pragma mark - Public Configuration
|
||||
|
||||
void OpenGLOutputBuilder::set_output_device(OutputDevice output_device)
|
||||
{
|
||||
if(output_device_ != output_device)
|
||||
{
|
||||
void OpenGLOutputBuilder::set_output_device(OutputDevice output_device) {
|
||||
if(output_device_ != output_device) {
|
||||
output_device_ = output_device;
|
||||
composite_src_output_y_ = 0;
|
||||
last_output_width_ = 0;
|
||||
last_output_height_ = 0;
|
||||
set_output_shader_width();
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::set_timing(unsigned int input_frequency, unsigned int cycles_per_line, unsigned int height_of_display, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider)
|
||||
{
|
||||
output_mutex_.lock();
|
||||
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) {
|
||||
std::lock_guard<std::mutex> lock_guard(output_mutex_);
|
||||
input_frequency_ = input_frequency;
|
||||
cycles_per_line_ = cycles_per_line;
|
||||
height_of_display_ = height_of_display;
|
||||
@@ -333,13 +345,11 @@ void OpenGLOutputBuilder::set_timing(unsigned int input_frequency, unsigned int
|
||||
vertical_period_divider_ = vertical_period_divider;
|
||||
|
||||
set_timing_uniforms();
|
||||
output_mutex_.unlock();
|
||||
}
|
||||
|
||||
#pragma mark - Internal Configuration
|
||||
|
||||
void OpenGLOutputBuilder::set_colour_space_uniforms()
|
||||
{
|
||||
void OpenGLOutputBuilder::set_colour_space_uniforms() {
|
||||
GLfloat rgbToYUV[] = {0.299f, -0.14713f, 0.615f, 0.587f, -0.28886f, -0.51499f, 0.114f, 0.436f, -0.10001f};
|
||||
GLfloat yuvToRGB[] = {1.0f, 1.0f, 1.0f, 0.0f, -0.39465f, 2.03211f, 1.13983f, -0.58060f, 0.0f};
|
||||
|
||||
@@ -348,8 +358,7 @@ void OpenGLOutputBuilder::set_colour_space_uniforms()
|
||||
|
||||
GLfloat *fromRGB, *toRGB;
|
||||
|
||||
switch(colour_space_)
|
||||
{
|
||||
switch(colour_space_) {
|
||||
case ColourSpace::YIQ:
|
||||
fromRGB = rgbToYIQ;
|
||||
toRGB = yiqToRGB;
|
||||
@@ -362,30 +371,48 @@ void OpenGLOutputBuilder::set_colour_space_uniforms()
|
||||
}
|
||||
|
||||
if(composite_input_shader_program_) composite_input_shader_program_->set_colour_conversion_matrices(fromRGB, toRGB);
|
||||
if(composite_separation_filter_program_) composite_separation_filter_program_->set_colour_conversion_matrices(fromRGB, toRGB);
|
||||
if(composite_chrominance_filter_shader_program_) composite_chrominance_filter_shader_program_->set_colour_conversion_matrices(fromRGB, toRGB);
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::set_timing_uniforms()
|
||||
{
|
||||
OpenGL::IntermediateShader *intermediate_shaders[] = {
|
||||
composite_input_shader_program_.get(),
|
||||
composite_separation_filter_program_.get(),
|
||||
composite_y_filter_shader_program_.get(),
|
||||
composite_chrominance_filter_shader_program_.get()
|
||||
};
|
||||
bool extends = false;
|
||||
float phaseCyclesPerTick = (float)colour_cycle_numerator_ / (float)(colour_cycle_denominator_ * cycles_per_line_);
|
||||
for(int c = 0; c < 3; c++)
|
||||
{
|
||||
if(intermediate_shaders[c]) intermediate_shaders[c]->set_phase_cycles_per_sample(phaseCyclesPerTick, extends);
|
||||
extends = true;
|
||||
}
|
||||
|
||||
if(output_shader_program_) output_shader_program_->set_timing(height_of_display_, cycles_per_line_, horizontal_scan_period_, vertical_scan_period_, vertical_period_divider_);
|
||||
|
||||
float colour_subcarrier_frequency = (float)colour_cycle_numerator_ / (float)colour_cycle_denominator_;
|
||||
if(composite_separation_filter_program_) composite_separation_filter_program_->set_separation_frequency(cycles_per_line_, colour_subcarrier_frequency);
|
||||
if(composite_y_filter_shader_program_) composite_y_filter_shader_program_->set_filter_coefficients(cycles_per_line_, colour_subcarrier_frequency * 0.66f);
|
||||
if(composite_chrominance_filter_shader_program_) composite_chrominance_filter_shader_program_->set_filter_coefficients(cycles_per_line_, colour_subcarrier_frequency * 0.5f);
|
||||
if(rgb_filter_shader_program_) rgb_filter_shader_program_->set_filter_coefficients(cycles_per_line_, (float)input_frequency_ * 0.5f);
|
||||
float OpenGLOutputBuilder::get_composite_output_width() const {
|
||||
return ((float)colour_cycle_numerator_ * 4.0f) / (float)(colour_cycle_denominator_ * IntermediateBufferWidth);
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::set_output_shader_width() {
|
||||
if(output_shader_program_) {
|
||||
const float width = get_is_television_output() ? get_composite_output_width() : 1.0f;
|
||||
output_shader_program_->set_input_width_scaler(width);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLOutputBuilder::set_timing_uniforms() {
|
||||
const float colour_subcarrier_frequency = (float)colour_cycle_numerator_ / (float)colour_cycle_denominator_;
|
||||
const float output_width = get_composite_output_width();
|
||||
const float sample_cycles_per_line = cycles_per_line_ / output_width;
|
||||
|
||||
if(composite_separation_filter_program_) {
|
||||
composite_separation_filter_program_->set_width_scalers(output_width, output_width);
|
||||
composite_separation_filter_program_->set_separation_frequency(sample_cycles_per_line, colour_subcarrier_frequency);
|
||||
composite_separation_filter_program_->set_extension(6.0f);
|
||||
}
|
||||
if(composite_chrominance_filter_shader_program_) {
|
||||
composite_chrominance_filter_shader_program_->set_width_scalers(output_width, output_width);
|
||||
composite_chrominance_filter_shader_program_->set_extension(5.0f);
|
||||
}
|
||||
if(rgb_filter_shader_program_) {
|
||||
rgb_filter_shader_program_->set_width_scalers(1.0f, 1.0f);
|
||||
rgb_filter_shader_program_->set_filter_coefficients(sample_cycles_per_line, (float)input_frequency_ * 0.5f);
|
||||
}
|
||||
if(output_shader_program_) {
|
||||
set_output_shader_width();
|
||||
output_shader_program_->set_timing(height_of_display_, cycles_per_line_, horizontal_scan_period_, vertical_scan_period_, vertical_period_divider_);
|
||||
}
|
||||
if(composite_input_shader_program_) {
|
||||
composite_input_shader_program_->set_width_scalers(1.0f, output_width);
|
||||
composite_input_shader_program_->set_extension(0.0f);
|
||||
}
|
||||
if(rgb_input_shader_program_) {
|
||||
rgb_input_shader_program_->set_width_scalers(1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,8 +47,8 @@ class OpenGLOutputBuilder {
|
||||
Rect visible_area_;
|
||||
|
||||
// Other things the caller may have provided.
|
||||
char *composite_shader_;
|
||||
char *rgb_shader_;
|
||||
std::string composite_shader_;
|
||||
std::string rgb_shader_;
|
||||
|
||||
// Methods used by the OpenGL code
|
||||
void prepare_output_shader();
|
||||
@@ -66,13 +66,19 @@ class OpenGLOutputBuilder {
|
||||
GLsizei composite_src_output_y_;
|
||||
|
||||
std::unique_ptr<OpenGL::OutputShader> output_shader_program_;
|
||||
std::unique_ptr<OpenGL::IntermediateShader> composite_input_shader_program_, composite_separation_filter_program_, composite_y_filter_shader_program_, composite_chrominance_filter_shader_program_;
|
||||
std::unique_ptr<OpenGL::IntermediateShader> rgb_input_shader_program_, rgb_filter_shader_program_;
|
||||
|
||||
OpenGL::TextureTarget composite_texture_; // receives raw composite levels
|
||||
OpenGL::TextureTarget separated_texture_; // receives unfiltered Y in the R channel plus unfiltered but demodulated chrominance in G and B
|
||||
OpenGL::TextureTarget filtered_y_texture_; // receives filtered Y in the R channel plus unfiltered chrominance in G and B
|
||||
OpenGL::TextureTarget filtered_texture_; // receives filtered YIQ or YUV
|
||||
std::unique_ptr<OpenGL::IntermediateShader> composite_input_shader_program_;
|
||||
std::unique_ptr<OpenGL::IntermediateShader> composite_separation_filter_program_;
|
||||
std::unique_ptr<OpenGL::IntermediateShader> composite_chrominance_filter_shader_program_;
|
||||
|
||||
std::unique_ptr<OpenGL::IntermediateShader> rgb_input_shader_program_;
|
||||
std::unique_ptr<OpenGL::IntermediateShader> rgb_filter_shader_program_;
|
||||
|
||||
std::unique_ptr<OpenGL::TextureTarget> composite_texture_; // receives raw composite levels
|
||||
std::unique_ptr<OpenGL::TextureTarget> separated_texture_; // receives filtered Y in the R channel plus unfiltered but demodulated chrominance in G and B
|
||||
std::unique_ptr<OpenGL::TextureTarget> filtered_texture_; // receives filtered YIQ or YUV
|
||||
|
||||
std::unique_ptr<OpenGL::TextureTarget> work_texture_; // used for all intermediate rendering if texture fences are supported
|
||||
|
||||
std::unique_ptr<OpenGL::TextureTarget> framebuffer_; // the current pixel output
|
||||
|
||||
@@ -88,6 +94,9 @@ class OpenGLOutputBuilder {
|
||||
void reset_all_OpenGL_state();
|
||||
|
||||
GLsync fence_;
|
||||
float get_composite_output_width() const;
|
||||
void set_output_shader_width();
|
||||
bool get_is_television_output();
|
||||
|
||||
public:
|
||||
// These two are protected by output_mutex_.
|
||||
@@ -97,8 +106,7 @@ class OpenGLOutputBuilder {
|
||||
OpenGLOutputBuilder(size_t bytes_per_pixel);
|
||||
~OpenGLOutputBuilder();
|
||||
|
||||
inline void set_colour_format(ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator)
|
||||
{
|
||||
inline void set_colour_format(ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator) {
|
||||
std::lock_guard<std::mutex> output_guard(output_mutex_);
|
||||
colour_space_ = colour_space;
|
||||
colour_cycle_numerator_ = colour_cycle_numerator;
|
||||
@@ -106,41 +114,35 @@ class OpenGLOutputBuilder {
|
||||
set_colour_space_uniforms();
|
||||
}
|
||||
|
||||
inline void set_visible_area(Rect visible_area)
|
||||
{
|
||||
inline void set_visible_area(Rect visible_area) {
|
||||
visible_area_ = visible_area;
|
||||
}
|
||||
|
||||
inline std::unique_lock<std::mutex> get_output_lock()
|
||||
{
|
||||
inline std::unique_lock<std::mutex> get_output_lock() {
|
||||
return std::unique_lock<std::mutex>(output_mutex_);
|
||||
}
|
||||
|
||||
inline OutputDevice get_output_device()
|
||||
{
|
||||
inline OutputDevice get_output_device() {
|
||||
return output_device_;
|
||||
}
|
||||
|
||||
inline uint16_t get_composite_output_y()
|
||||
{
|
||||
inline uint16_t get_composite_output_y() {
|
||||
return (uint16_t)composite_src_output_y_;
|
||||
}
|
||||
|
||||
inline bool composite_output_buffer_is_full()
|
||||
{
|
||||
inline bool composite_output_buffer_is_full() {
|
||||
return composite_src_output_y_ == IntermediateBufferHeight;
|
||||
}
|
||||
|
||||
inline void increment_composite_output_y()
|
||||
{
|
||||
inline void increment_composite_output_y() {
|
||||
if(!composite_output_buffer_is_full())
|
||||
composite_src_output_y_++;
|
||||
}
|
||||
|
||||
void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty);
|
||||
void set_openGL_context_will_change(bool should_delete_resources);
|
||||
void set_composite_sampling_function(const char *shader);
|
||||
void set_rgb_sampling_function(const char *shader);
|
||||
void set_composite_sampling_function(const std::string &shader);
|
||||
void set_rgb_sampling_function(const std::string &shader);
|
||||
void set_output_device(OutputDevice output_device);
|
||||
void set_timing(unsigned int input_frequency, unsigned int cycles_per_line, unsigned int height_of_display, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider);
|
||||
};
|
||||
|
||||
@@ -21,8 +21,7 @@ namespace CRT {
|
||||
|
||||
The @c Flywheel will attempt to converge with timing implied by synchronisation pulses.
|
||||
*/
|
||||
struct Flywheel
|
||||
{
|
||||
struct Flywheel {
|
||||
/*!
|
||||
Constructs an instance of @c Flywheel.
|
||||
|
||||
@@ -61,26 +60,18 @@ struct Flywheel
|
||||
|
||||
@returns The next synchronisation event.
|
||||
*/
|
||||
inline SyncEvent get_next_event_in_period(bool sync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced)
|
||||
{
|
||||
inline SyncEvent get_next_event_in_period(bool sync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) {
|
||||
// do we recognise this hsync, thereby adjusting future time expectations?
|
||||
if(sync_is_requested)
|
||||
{
|
||||
if(counter_ < sync_error_window_ || counter_ > expected_next_sync_ - sync_error_window_)
|
||||
{
|
||||
if(sync_is_requested) {
|
||||
if(counter_ < sync_error_window_ || counter_ > expected_next_sync_ - sync_error_window_) {
|
||||
unsigned int time_now = (counter_ < sync_error_window_) ? expected_next_sync_ + counter_ : counter_;
|
||||
expected_next_sync_ = (3*expected_next_sync_ + time_now) >> 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
number_of_surprises_++;
|
||||
|
||||
if(counter_ < retrace_time_ + (expected_next_sync_ >> 1))
|
||||
{
|
||||
if(counter_ < retrace_time_ + (expected_next_sync_ >> 1)) {
|
||||
expected_next_sync_ = (3*expected_next_sync_ + standard_period_ + sync_error_window_) >> 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
expected_next_sync_ = (3*expected_next_sync_ + standard_period_ - sync_error_window_) >> 2;
|
||||
}
|
||||
}
|
||||
@@ -90,15 +81,13 @@ struct Flywheel
|
||||
unsigned int proposed_sync_time = cycles_to_run_for;
|
||||
|
||||
// will we end an ongoing retrace?
|
||||
if(counter_ < retrace_time_ && counter_ + proposed_sync_time >= retrace_time_)
|
||||
{
|
||||
if(counter_ < retrace_time_ && counter_ + proposed_sync_time >= retrace_time_) {
|
||||
proposed_sync_time = retrace_time_ - counter_;
|
||||
proposed_event = SyncEvent::EndRetrace;
|
||||
}
|
||||
|
||||
// will we start a retrace?
|
||||
if(counter_ + proposed_sync_time >= expected_next_sync_)
|
||||
{
|
||||
if(counter_ + proposed_sync_time >= expected_next_sync_) {
|
||||
proposed_sync_time = expected_next_sync_ - counter_;
|
||||
proposed_event = SyncEvent::StartRetrace;
|
||||
}
|
||||
@@ -115,12 +104,10 @@ struct Flywheel
|
||||
|
||||
@param event The synchronisation event to apply after that period.
|
||||
*/
|
||||
inline void apply_event(unsigned int cycles_advanced, SyncEvent event)
|
||||
{
|
||||
inline void apply_event(unsigned int cycles_advanced, SyncEvent event) {
|
||||
counter_ += cycles_advanced;
|
||||
|
||||
switch(event)
|
||||
{
|
||||
switch(event) {
|
||||
default: return;
|
||||
case StartRetrace:
|
||||
counter_before_retrace_ = counter_ - retrace_time_;
|
||||
@@ -135,10 +122,8 @@ struct Flywheel
|
||||
|
||||
@returns The current output position.
|
||||
*/
|
||||
inline unsigned int get_current_output_position()
|
||||
{
|
||||
if(counter_ < retrace_time_)
|
||||
{
|
||||
inline unsigned int get_current_output_position() {
|
||||
if(counter_ < retrace_time_) {
|
||||
unsigned int retrace_distance = (counter_ * standard_period_) / retrace_time_;
|
||||
if(retrace_distance > counter_before_retrace_) return 0;
|
||||
return counter_before_retrace_ - retrace_distance;
|
||||
@@ -150,32 +135,28 @@ struct Flywheel
|
||||
/*!
|
||||
@returns the amount of time since retrace last began. Time then counts monotonically up from zero.
|
||||
*/
|
||||
inline unsigned int get_current_time()
|
||||
{
|
||||
inline unsigned int get_current_time() {
|
||||
return counter_;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns whether the output is currently retracing.
|
||||
*/
|
||||
inline bool is_in_retrace()
|
||||
{
|
||||
inline bool is_in_retrace() {
|
||||
return counter_ < retrace_time_;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns the expected length of the scan period (excluding retrace).
|
||||
*/
|
||||
inline unsigned int get_scan_period()
|
||||
{
|
||||
inline unsigned int get_scan_period() {
|
||||
return standard_period_ - retrace_time_;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns the expected length of a complete scan and retrace cycle.
|
||||
*/
|
||||
inline unsigned int get_standard_period()
|
||||
{
|
||||
inline unsigned int get_standard_period() {
|
||||
return standard_period_;
|
||||
}
|
||||
|
||||
@@ -183,8 +164,7 @@ struct Flywheel
|
||||
@returns the number of synchronisation events that have seemed surprising since the last time this method was called;
|
||||
a low number indicates good synchronisation.
|
||||
*/
|
||||
inline unsigned int get_and_reset_number_of_surprises()
|
||||
{
|
||||
inline unsigned int get_and_reset_number_of_surprises() {
|
||||
unsigned int result = number_of_surprises_;
|
||||
number_of_surprises_ = 0;
|
||||
return result;
|
||||
@@ -193,8 +173,7 @@ struct Flywheel
|
||||
/*!
|
||||
@returns `true` if a sync is expected soon or the time at which it was expected was recent.
|
||||
*/
|
||||
inline bool is_near_expected_sync()
|
||||
{
|
||||
inline bool is_near_expected_sync() {
|
||||
return abs((int)counter_ - (int)expected_next_sync_) < (int)standard_period_ / 50;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#else
|
||||
#include <OpenGL/OpenGL.h>
|
||||
#include <OpenGL/gl3.h>
|
||||
#include <OpenGL/gl3ext.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
using namespace OpenGL;
|
||||
|
||||
namespace {
|
||||
const OpenGL::Shader::AttributeBinding bindings[] =
|
||||
{
|
||||
const OpenGL::Shader::AttributeBinding bindings[] = {
|
||||
{"inputPosition", 0},
|
||||
{"outputPosition", 1},
|
||||
{"phaseAndAmplitude", 2},
|
||||
@@ -26,8 +25,7 @@ namespace {
|
||||
};
|
||||
}
|
||||
|
||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const char *fragment_shader, bool use_usampler, bool input_is_inputPosition)
|
||||
{
|
||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const std::string &fragment_shader, bool use_usampler, bool input_is_inputPosition) {
|
||||
const char *sampler_type = use_usampler ? "usampler2D" : "sampler2D";
|
||||
const char *input_variable = input_is_inputPosition ? "inputPosition" : "outputPosition";
|
||||
|
||||
@@ -40,11 +38,14 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const char *
|
||||
"in vec2 ends;"
|
||||
"in vec3 phaseTimeAndAmplitude;"
|
||||
|
||||
"uniform float phaseCyclesPerTick;"
|
||||
"uniform ivec2 outputTextureSize;"
|
||||
"uniform float extension;"
|
||||
"uniform %s texID;"
|
||||
"uniform float offsets[5];"
|
||||
"uniform vec2 widthScalers;"
|
||||
"uniform float inputVerticalOffset;"
|
||||
"uniform float outputVerticalOffset;"
|
||||
"uniform float textureHeightDivisor;"
|
||||
|
||||
"out vec2 phaseAndAmplitudeVarying;"
|
||||
"out vec2 inputPositionsVarying[11];"
|
||||
@@ -53,36 +54,52 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const char *
|
||||
|
||||
"void main(void)"
|
||||
"{"
|
||||
// odd vertices are on the left, even on the right
|
||||
"float extent = float(gl_VertexID & 1);"
|
||||
"float longitudinal = float((gl_VertexID & 2) >> 1);"
|
||||
|
||||
"vec2 inputPosition = vec2(mix(inputStart.x, ends.x, extent), inputStart.y);"
|
||||
"vec2 outputPosition = vec2(mix(outputStart.x, ends.y, extent), outputStart.y);"
|
||||
// inputPosition.x is either inputStart.x or ends.x, depending on whether it is on the left or the right;
|
||||
// outputPosition.x is either outputStart.x or ends.y;
|
||||
// .ys are inputStart.y and outputStart.y respectively
|
||||
"vec2 inputPosition = vec2(mix(inputStart.x, ends.x, extent)*widthScalers[0], inputStart.y + inputVerticalOffset);"
|
||||
"vec2 outputPosition = vec2(mix(outputStart.x, ends.y, extent)*widthScalers[1], outputStart.y + outputVerticalOffset);"
|
||||
|
||||
"inputPosition.y += longitudinal;"
|
||||
"outputPosition.y += longitudinal;"
|
||||
|
||||
// extension is the amount to extend both the input and output by to add a full colour cycle at each end
|
||||
"vec2 extensionVector = vec2(extension, 0.0) * 2.0 * (extent - 0.5);"
|
||||
|
||||
// extended[Input/Output]Position are [input/output]Position with the necessary applied extension
|
||||
"vec2 extendedInputPosition = %s + extensionVector;"
|
||||
"vec2 extendedOutputPosition = outputPosition + extensionVector;"
|
||||
|
||||
// keep iInputPositionVarying in whole source pixels, scale mappedInputPosition to the ordinary normalised range
|
||||
"vec2 textureSize = vec2(textureSize(texID, 0));"
|
||||
"iInputPositionVarying = extendedInputPosition;"
|
||||
"vec2 mappedInputPosition = (extendedInputPosition + vec2(0.0, 0.5)) / textureSize;"
|
||||
"vec2 mappedInputPosition = extendedInputPosition / textureSize;" // + vec2(0.0, 0.5)
|
||||
|
||||
"inputPositionsVarying[0] = mappedInputPosition - (vec2(offsets[0], 0.0) / textureSize);"
|
||||
"inputPositionsVarying[1] = mappedInputPosition - (vec2(offsets[1], 0.0) / textureSize);"
|
||||
"inputPositionsVarying[2] = mappedInputPosition - (vec2(offsets[2], 0.0) / textureSize);"
|
||||
"inputPositionsVarying[3] = mappedInputPosition - (vec2(offsets[3], 0.0) / textureSize);"
|
||||
"inputPositionsVarying[4] = mappedInputPosition - (vec2(offsets[4], 0.0) / textureSize);"
|
||||
// setup input positions spaced as per the supplied offsets; these are for filtering where required
|
||||
"inputPositionsVarying[0] = mappedInputPosition - (vec2(5.0, 0.0) / textureSize);"
|
||||
"inputPositionsVarying[1] = mappedInputPosition - (vec2(4.0, 0.0) / textureSize);"
|
||||
"inputPositionsVarying[2] = mappedInputPosition - (vec2(3.0, 0.0) / textureSize);"
|
||||
"inputPositionsVarying[3] = mappedInputPosition - (vec2(2.0, 0.0) / textureSize);"
|
||||
"inputPositionsVarying[4] = mappedInputPosition - (vec2(1.0, 0.0) / textureSize);"
|
||||
"inputPositionsVarying[5] = mappedInputPosition;"
|
||||
"inputPositionsVarying[6] = mappedInputPosition + (vec2(offsets[4], 0.0) / textureSize);"
|
||||
"inputPositionsVarying[7] = mappedInputPosition + (vec2(offsets[3], 0.0) / textureSize);"
|
||||
"inputPositionsVarying[8] = mappedInputPosition + (vec2(offsets[2], 0.0) / textureSize);"
|
||||
"inputPositionsVarying[9] = mappedInputPosition + (vec2(offsets[1], 0.0) / textureSize);"
|
||||
"inputPositionsVarying[10] = mappedInputPosition + (vec2(offsets[0], 0.0) / textureSize);"
|
||||
"inputPositionsVarying[6] = mappedInputPosition + (vec2(1.0, 0.0) / textureSize);"
|
||||
"inputPositionsVarying[7] = mappedInputPosition + (vec2(2.0, 0.0) / textureSize);"
|
||||
"inputPositionsVarying[8] = mappedInputPosition + (vec2(3.0, 0.0) / textureSize);"
|
||||
"inputPositionsVarying[9] = mappedInputPosition + (vec2(4.0, 0.0) / textureSize);"
|
||||
"inputPositionsVarying[10] = mappedInputPosition + (vec2(5.0, 0.0) / textureSize);"
|
||||
"delayLinePositionVarying = mappedInputPosition - vec2(0.0, 1.0);"
|
||||
|
||||
"phaseAndAmplitudeVarying.x = (phaseCyclesPerTick * (extendedOutputPosition.x - phaseTimeAndAmplitude.y) + (phaseTimeAndAmplitude.x / 256.0)) * 2.0 * 3.141592654;"
|
||||
// setup phaseAndAmplitudeVarying.x as colour burst subcarrier phase, in radians;
|
||||
// setup phaseAndAmplitudeVarying.x as colour burst amplitude
|
||||
"phaseAndAmplitudeVarying.x = (extendedOutputPosition.x + (phaseTimeAndAmplitude.x / 64.0)) * 0.5 * 3.141592654;"
|
||||
"phaseAndAmplitudeVarying.y = 0.33;" // TODO: reinstate connection with (phaseTimeAndAmplitude.y/256.0)
|
||||
|
||||
"vec2 eyePosition = 2.0*(extendedOutputPosition / outputTextureSize) - vec2(1.0) + vec2(1.0)/outputTextureSize;"
|
||||
// determine output position by scaling the output position according to the texture size
|
||||
"vec2 eyePosition = 2.0*(extendedOutputPosition / outputTextureSize) - vec2(1.0);"
|
||||
"gl_Position = vec4(eyePosition, 0.0, 1.0);"
|
||||
"}", sampler_type, input_variable);
|
||||
|
||||
@@ -92,12 +109,11 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const char *
|
||||
return shader;
|
||||
}
|
||||
|
||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_source_conversion_shader(const char *composite_shader, const char *rgb_shader)
|
||||
{
|
||||
char *composite_sample = (char *)composite_shader;
|
||||
if(!composite_sample)
|
||||
{
|
||||
asprintf(&composite_sample,
|
||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_source_conversion_shader(const std::string &composite_shader, const std::string &rgb_shader) {
|
||||
char *derived_composite_sample = nullptr;
|
||||
const char *composite_sample = composite_shader.c_str();
|
||||
if(!composite_shader.size()) {
|
||||
asprintf(&derived_composite_sample,
|
||||
"%s\n"
|
||||
"uniform mat3 rgbToLumaChroma;"
|
||||
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
|
||||
@@ -107,7 +123,8 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_source_conversion_s
|
||||
"vec2 quadrature = vec2(cos(phase), -sin(phase)) * amplitude;"
|
||||
"return dot(lumaChromaColour, vec3(1.0 - amplitude, quadrature));"
|
||||
"}",
|
||||
rgb_shader);
|
||||
rgb_shader.c_str());
|
||||
composite_sample = derived_composite_sample;
|
||||
}
|
||||
|
||||
char *fragment_shader;
|
||||
@@ -129,7 +146,7 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_source_conversion_s
|
||||
"fragColour = vec4(composite_sample(texID, inputPositionsVarying[5], iInputPositionVarying, phaseAndAmplitudeVarying.x, phaseAndAmplitudeVarying.y));"
|
||||
"}"
|
||||
, composite_sample);
|
||||
if(!composite_shader) free(composite_sample);
|
||||
free(derived_composite_sample);
|
||||
|
||||
std::unique_ptr<IntermediateShader> shader = make_shader(fragment_shader, true, true);
|
||||
free(fragment_shader);
|
||||
@@ -137,8 +154,7 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_source_conversion_s
|
||||
return shader;
|
||||
}
|
||||
|
||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_source_shader(const char *rgb_shader)
|
||||
{
|
||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_source_shader(const std::string &rgb_shader) {
|
||||
char *fragment_shader;
|
||||
asprintf(&fragment_shader,
|
||||
"#version 150\n"
|
||||
@@ -157,7 +173,7 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_source_shader(c
|
||||
"{"
|
||||
"fragColour = rgb_sample(texID, inputPositionsVarying[5], iInputPositionVarying);"
|
||||
"}"
|
||||
, rgb_shader);
|
||||
, rgb_shader.c_str());
|
||||
|
||||
std::unique_ptr<IntermediateShader> shader = make_shader(fragment_shader, true, true);
|
||||
free(fragment_shader);
|
||||
@@ -165,14 +181,12 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_source_shader(c
|
||||
return shader;
|
||||
}
|
||||
|
||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_luma_separation_shader()
|
||||
{
|
||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_luma_separation_shader() {
|
||||
return make_shader(
|
||||
"#version 150\n"
|
||||
|
||||
"in vec2 phaseAndAmplitudeVarying;"
|
||||
"in vec2 inputPositionsVarying[11];"
|
||||
"uniform vec4 weights[3];"
|
||||
|
||||
"out vec3 fragColour;"
|
||||
|
||||
@@ -180,44 +194,26 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_luma_separat
|
||||
|
||||
"void main(void)"
|
||||
"{"
|
||||
"vec4 samples[3] = vec4[]("
|
||||
"vec4("
|
||||
"texture(texID, inputPositionsVarying[0]).r,"
|
||||
"texture(texID, inputPositionsVarying[1]).r,"
|
||||
"texture(texID, inputPositionsVarying[2]).r,"
|
||||
"texture(texID, inputPositionsVarying[3]).r"
|
||||
"),"
|
||||
"vec4("
|
||||
"texture(texID, inputPositionsVarying[4]).r,"
|
||||
"texture(texID, inputPositionsVarying[5]).r,"
|
||||
"texture(texID, inputPositionsVarying[6]).r,"
|
||||
"texture(texID, inputPositionsVarying[7]).r"
|
||||
"),"
|
||||
"vec4("
|
||||
"texture(texID, inputPositionsVarying[8]).r,"
|
||||
"texture(texID, inputPositionsVarying[9]).r,"
|
||||
"texture(texID, inputPositionsVarying[10]).r,"
|
||||
"0.0"
|
||||
")"
|
||||
"vec4 samples = vec4("
|
||||
"texture(texID, inputPositionsVarying[3]).r,"
|
||||
"texture(texID, inputPositionsVarying[4]).r,"
|
||||
"texture(texID, inputPositionsVarying[5]).r,"
|
||||
"texture(texID, inputPositionsVarying[6]).r"
|
||||
");"
|
||||
"float luminance = dot(samples, vec4(0.25));"
|
||||
|
||||
"float luminance = "
|
||||
"dot(vec3("
|
||||
"dot(samples[0], weights[0]),"
|
||||
"dot(samples[1], weights[1]),"
|
||||
"dot(samples[2], weights[2])"
|
||||
"), vec3(1.0));"
|
||||
|
||||
"float chrominance = 0.5 * (samples[1].y - luminance) / phaseAndAmplitudeVarying.y;"
|
||||
// define chroma to be whatever was here, minus luma
|
||||
"float chrominance = 0.5 * (samples.z - luminance) / phaseAndAmplitudeVarying.y;"
|
||||
"luminance /= (1.0 - phaseAndAmplitudeVarying.y);"
|
||||
|
||||
// split choma colours here, as the most direct place, writing out
|
||||
// RGB = (luma, chroma.x, chroma.y)
|
||||
"vec2 quadrature = vec2(cos(phaseAndAmplitudeVarying.x), -sin(phaseAndAmplitudeVarying.x));"
|
||||
"fragColour = vec3(luminance, vec2(0.5) + (chrominance * quadrature));"
|
||||
"}",false, false);
|
||||
}
|
||||
|
||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_filter_shader()
|
||||
{
|
||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_filter_shader() {
|
||||
return make_shader(
|
||||
"#version 150\n"
|
||||
|
||||
@@ -232,41 +228,18 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_filter_shade
|
||||
"void main(void)"
|
||||
"{"
|
||||
"vec3 samples[] = vec3[]("
|
||||
"texture(texID, inputPositionsVarying[0]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[1]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[2]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[3]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[4]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[5]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[6]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[7]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[8]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[9]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[10]).rgb"
|
||||
"texture(texID, inputPositionsVarying[6]).rgb"
|
||||
");"
|
||||
|
||||
"vec4 chromaChannel1[] = vec4[]("
|
||||
"vec4(samples[0].g, samples[1].g, samples[2].g, samples[3].g),"
|
||||
"vec4(samples[4].g, samples[5].g, samples[6].g, samples[7].g),"
|
||||
"vec4(samples[8].g, samples[9].g, samples[10].g, 0.0)"
|
||||
");"
|
||||
"vec4 chromaChannel2[] = vec4[]("
|
||||
"vec4(samples[0].b, samples[1].b, samples[2].b, samples[3].b),"
|
||||
"vec4(samples[4].b, samples[5].b, samples[6].b, samples[7].b),"
|
||||
"vec4(samples[8].b, samples[9].b, samples[10].b, 0.0)"
|
||||
");"
|
||||
"vec4 chromaChannel1 = vec4(samples[0].g, samples[1].g, samples[2].g, samples[3].g);"
|
||||
"vec4 chromaChannel2 = vec4(samples[0].b, samples[1].b, samples[2].b, samples[3].b);"
|
||||
|
||||
"vec3 lumaChromaColour = vec3(samples[5].r,"
|
||||
"dot(vec3("
|
||||
"dot(chromaChannel1[0], weights[0]),"
|
||||
"dot(chromaChannel1[1], weights[1]),"
|
||||
"dot(chromaChannel1[2], weights[2])"
|
||||
"), vec3(1.0)),"
|
||||
"dot(vec3("
|
||||
"dot(chromaChannel2[0], weights[0]),"
|
||||
"dot(chromaChannel2[1], weights[1]),"
|
||||
"dot(chromaChannel2[2], weights[2])"
|
||||
"), vec3(1.0))"
|
||||
"vec3 lumaChromaColour = vec3(samples[2].r,"
|
||||
"dot(chromaChannel1, vec4(0.25)),"
|
||||
"dot(chromaChannel2, vec4(0.25))"
|
||||
");"
|
||||
|
||||
"vec3 lumaChromaColourInRange = (lumaChromaColour - vec3(0.0, 0.5, 0.5)) * vec3(1.0, 2.0, 2.0);"
|
||||
@@ -274,54 +247,7 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_filter_shade
|
||||
"}", false, false);
|
||||
}
|
||||
|
||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_luma_filter_shader()
|
||||
{
|
||||
return make_shader(
|
||||
"#version 150\n"
|
||||
|
||||
"in vec2 inputPositionsVarying[11];"
|
||||
"uniform vec4 weights[3];"
|
||||
|
||||
"out vec3 fragColour;"
|
||||
|
||||
"uniform sampler2D texID;"
|
||||
"uniform mat3 lumaChromaToRGB;"
|
||||
|
||||
"void main(void)"
|
||||
"{"
|
||||
"vec3 samples[] = vec3[]("
|
||||
"texture(texID, inputPositionsVarying[0]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[1]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[2]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[3]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[4]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[5]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[6]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[7]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[8]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[9]).rgb,"
|
||||
"texture(texID, inputPositionsVarying[10]).rgb"
|
||||
");"
|
||||
|
||||
"vec4 luminance[] = vec4[]("
|
||||
"vec4(samples[0].r, samples[1].r, samples[2].r, samples[3].r),"
|
||||
"vec4(samples[4].r, samples[5].r, samples[6].r, samples[7].r),"
|
||||
"vec4(samples[8].r, samples[9].r, samples[10].r, 0.0)"
|
||||
");"
|
||||
|
||||
"fragColour = vec3("
|
||||
"dot(vec3("
|
||||
"dot(luminance[0], weights[0]),"
|
||||
"dot(luminance[1], weights[1]),"
|
||||
"dot(luminance[2], weights[2])"
|
||||
"), vec3(1.0)),"
|
||||
"samples[5].gb"
|
||||
");"
|
||||
"}", false, false);
|
||||
}
|
||||
|
||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_filter_shader()
|
||||
{
|
||||
std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_filter_shader() {
|
||||
return make_shader(
|
||||
"#version 150\n"
|
||||
|
||||
@@ -384,18 +310,15 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_filter_shader()
|
||||
"}", false, false);
|
||||
}
|
||||
|
||||
void IntermediateShader::set_output_size(unsigned int output_width, unsigned int output_height)
|
||||
{
|
||||
void IntermediateShader::set_output_size(unsigned int output_width, unsigned int output_height) {
|
||||
set_uniform("outputTextureSize", (GLint)output_width, (GLint)output_height);
|
||||
}
|
||||
|
||||
void IntermediateShader::set_source_texture_unit(GLenum unit)
|
||||
{
|
||||
void IntermediateShader::set_source_texture_unit(GLenum unit) {
|
||||
set_uniform("texID", (GLint)(unit - GL_TEXTURE0));
|
||||
}
|
||||
|
||||
void IntermediateShader::set_filter_coefficients(float sampling_rate, float cutoff_frequency)
|
||||
{
|
||||
void IntermediateShader::set_filter_coefficients(float sampling_rate, float cutoff_frequency) {
|
||||
// The process below: the source texture will have bilinear filtering enabled; so by
|
||||
// sampling at non-integral offsets from the centre the shader can get a weighted sum
|
||||
// of two source pixels, then scale that once, to do two taps per sample. However
|
||||
@@ -404,44 +327,45 @@ void IntermediateShader::set_filter_coefficients(float sampling_rate, float cuto
|
||||
// Perform a linear search for the highest number of taps we can use with 11 samples.
|
||||
GLfloat weights[12];
|
||||
GLfloat offsets[5];
|
||||
unsigned int taps = 21;
|
||||
while(1)
|
||||
{
|
||||
unsigned int taps = 11;
|
||||
// unsigned int taps = 21;
|
||||
while(1) {
|
||||
float coefficients[21];
|
||||
SignalProcessing::FIRFilter luminance_filter(taps, sampling_rate, 0.0f, cutoff_frequency, SignalProcessing::FIRFilter::DefaultAttenuation);
|
||||
luminance_filter.get_coefficients(coefficients);
|
||||
|
||||
int sample = 0;
|
||||
int c = 0;
|
||||
// int sample = 0;
|
||||
// int c = 0;
|
||||
memset(weights, 0, sizeof(float)*12);
|
||||
memset(offsets, 0, sizeof(float)*5);
|
||||
|
||||
int halfSize = (taps >> 1);
|
||||
while(c < halfSize && sample < 5)
|
||||
{
|
||||
offsets[sample] = (float)(halfSize - c);
|
||||
if((coefficients[c] < 0.0f) == (coefficients[c+1] < 0.0f) && c+1 < (taps >> 1))
|
||||
{
|
||||
weights[sample] = coefficients[c] + coefficients[c+1];
|
||||
offsets[sample] -= (coefficients[c+1] / weights[sample]);
|
||||
c += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
weights[sample] = coefficients[c];
|
||||
c++;
|
||||
}
|
||||
sample ++;
|
||||
}
|
||||
if(c == halfSize) // i.e. we finished combining inputs before we ran out of space
|
||||
{
|
||||
weights[sample] = coefficients[c];
|
||||
for(int c = 0; c < sample; c++)
|
||||
{
|
||||
weights[sample+c+1] = weights[sample-c-1];
|
||||
}
|
||||
break;
|
||||
for(int c = 0; c < taps; c++) {
|
||||
if(c < 5) offsets[c] = (halfSize - c);
|
||||
weights[c] = coefficients[c];
|
||||
}
|
||||
break;
|
||||
|
||||
// int halfSize = (taps >> 1);
|
||||
// while(c < halfSize && sample < 5) {
|
||||
// offsets[sample] = (float)(halfSize - c);
|
||||
// if((coefficients[c] < 0.0f) == (coefficients[c+1] < 0.0f) && c+1 < (taps >> 1)) {
|
||||
// weights[sample] = coefficients[c] + coefficients[c+1];
|
||||
// offsets[sample] -= (coefficients[c+1] / weights[sample]);
|
||||
// c += 2;
|
||||
// } else {
|
||||
// weights[sample] = coefficients[c];
|
||||
// c++;
|
||||
// }
|
||||
// sample ++;
|
||||
// }
|
||||
// if(c == halfSize) { // i.e. we finished combining inputs before we ran out of space
|
||||
// weights[sample] = coefficients[c];
|
||||
// for(int c = 0; c < sample; c++) {
|
||||
// weights[sample+c+1] = weights[sample-c-1];
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
taps -= 2;
|
||||
}
|
||||
|
||||
@@ -449,19 +373,25 @@ void IntermediateShader::set_filter_coefficients(float sampling_rate, float cuto
|
||||
set_uniform("offsets", 1, 5, offsets);
|
||||
}
|
||||
|
||||
void IntermediateShader::set_separation_frequency(float sampling_rate, float colour_burst_frequency)
|
||||
{
|
||||
void IntermediateShader::set_separation_frequency(float sampling_rate, float colour_burst_frequency) {
|
||||
set_filter_coefficients(sampling_rate, colour_burst_frequency);
|
||||
}
|
||||
|
||||
void IntermediateShader::set_phase_cycles_per_sample(float phase_cycles_per_sample, bool extend_runs_to_full_cycle)
|
||||
{
|
||||
set_uniform("phaseCyclesPerTick", (GLfloat)phase_cycles_per_sample);
|
||||
set_uniform("extension", extend_runs_to_full_cycle ? ceilf(1.0f / phase_cycles_per_sample) : 0.0f);
|
||||
void IntermediateShader::set_extension(float extension) {
|
||||
set_uniform("extension", extension);
|
||||
}
|
||||
|
||||
void IntermediateShader::set_colour_conversion_matrices(float *fromRGB, float *toRGB)
|
||||
{
|
||||
void IntermediateShader::set_colour_conversion_matrices(float *fromRGB, float *toRGB) {
|
||||
set_uniform_matrix("lumaChromaToRGB", 3, false, toRGB);
|
||||
set_uniform_matrix("rgbToLumaChroma", 3, false, fromRGB);
|
||||
}
|
||||
|
||||
void IntermediateShader::set_width_scalers(float input_scaler, float output_scaler) {
|
||||
set_uniform("widthScalers", input_scaler, output_scaler);
|
||||
}
|
||||
|
||||
void IntermediateShader::set_is_double_height(bool is_double_height, float input_offset, float output_offset) {
|
||||
set_uniform("textureHeightDivisor", is_double_height ? 2.0f : 1.0f);
|
||||
set_uniform("inputVerticalOffset", input_offset);
|
||||
set_uniform("outputVerticalOffset", output_offset);
|
||||
}
|
||||
|
||||
@@ -25,13 +25,13 @@ public:
|
||||
converting them to single-channel composite values using @c composite_shader if supplied
|
||||
or @c rgb_shader and a reference composite conversion if @c composite_shader is @c nullptr.
|
||||
*/
|
||||
static std::unique_ptr<IntermediateShader> make_source_conversion_shader(const char *composite_shader, const char *rgb_shader);
|
||||
static std::unique_ptr<IntermediateShader> make_source_conversion_shader(const std::string &composite_shader, const std::string &rgb_shader);
|
||||
|
||||
/*!
|
||||
Constructs and returns an intermediate shader that will take runs from the inputPositions,
|
||||
converting them to RGB values using @c rgb_shader.
|
||||
*/
|
||||
static std::unique_ptr<IntermediateShader> make_rgb_source_shader(const char *rgb_shader);
|
||||
static std::unique_ptr<IntermediateShader> make_rgb_source_shader(const std::string &rgb_shader);
|
||||
|
||||
/*!
|
||||
Constructs and returns an intermediate shader that will read composite samples from the R channel,
|
||||
@@ -44,11 +44,6 @@ public:
|
||||
*/
|
||||
static std::unique_ptr<IntermediateShader> make_chroma_filter_shader();
|
||||
|
||||
/*!
|
||||
Constructs and returns an intermediate shader that will filter R while passing through G and B unchanged.
|
||||
*/
|
||||
static std::unique_ptr<IntermediateShader> make_luma_filter_shader();
|
||||
|
||||
/*!
|
||||
Constructs and returns an intermediate shader that will filter R, G and B.
|
||||
*/
|
||||
@@ -81,15 +76,26 @@ public:
|
||||
geometry should be extended so that a complete colour cycle is included at both the beginning and end,
|
||||
to occur upon the next `bind`.
|
||||
*/
|
||||
void set_phase_cycles_per_sample(float phase_cycles_per_sample, bool extend_runs_to_full_cycle);
|
||||
void set_extension(float extension);
|
||||
|
||||
/*!
|
||||
Queues setting the matrices that convert between RGB and chrominance/luminance to occur on the next `bind`.
|
||||
*/
|
||||
void set_colour_conversion_matrices(float *fromRGB, float *toRGB);
|
||||
|
||||
/*!
|
||||
Sets the proportions of the input and output areas that should be considered the whole width — 1.0 means use all available
|
||||
space, 0.5 means use half, etc.
|
||||
*/
|
||||
void set_width_scalers(float input_scaler, float output_scaler);
|
||||
|
||||
/*!
|
||||
Sets source and target vertical offsets.
|
||||
*/
|
||||
void set_is_double_height(bool is_double_height, float input_offset = 0.0f, float output_offset = 0.0f);
|
||||
|
||||
private:
|
||||
static std::unique_ptr<IntermediateShader> make_shader(const char *fragment_shader, bool use_usampler, bool input_is_inputPosition);
|
||||
static std::unique_ptr<IntermediateShader> make_shader(const std::string &fragment_shader, bool use_usampler, bool input_is_inputPosition);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -14,16 +14,14 @@
|
||||
using namespace OpenGL;
|
||||
|
||||
namespace {
|
||||
const OpenGL::Shader::AttributeBinding bindings[] =
|
||||
{
|
||||
const OpenGL::Shader::AttributeBinding bindings[] = {
|
||||
{"position", 0},
|
||||
{"srcCoordinates", 1},
|
||||
{nullptr}
|
||||
};
|
||||
}
|
||||
|
||||
std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_methods, const char *colour_expression, bool use_usampler)
|
||||
{
|
||||
std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_methods, const char *colour_expression, bool use_usampler) {
|
||||
const char *sampler_type = use_usampler ? "usampler2D" : "sampler2D";
|
||||
|
||||
char *vertex_shader;
|
||||
@@ -38,6 +36,8 @@ std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_met
|
||||
"uniform vec2 positionConversion;"
|
||||
"uniform vec2 scanNormal;"
|
||||
"uniform %s texID;"
|
||||
"uniform float inputScaler;"
|
||||
"uniform int textureHeightDivisor;"
|
||||
|
||||
"out float lateralVarying;"
|
||||
"out vec2 srcCoordinatesVarying;"
|
||||
@@ -52,9 +52,10 @@ std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_met
|
||||
"lateralVarying = lateral - 0.5;"
|
||||
|
||||
"vec2 vSrcCoordinates = vec2(x, vertical.y);"
|
||||
"ivec2 textureSize = textureSize(texID, 0);"
|
||||
"ivec2 textureSize = textureSize(texID, 0) * ivec2(1, textureHeightDivisor);"
|
||||
"iSrcCoordinatesVarying = vSrcCoordinates;"
|
||||
"srcCoordinatesVarying = vec2(vSrcCoordinates.x / textureSize.x, (vSrcCoordinates.y + 0.5) / textureSize.y);"
|
||||
"srcCoordinatesVarying = vec2(inputScaler * vSrcCoordinates.x / textureSize.x, (vSrcCoordinates.y + 0.5) / textureSize.y);"
|
||||
"srcCoordinatesVarying.x = srcCoordinatesVarying.x - mod(srcCoordinatesVarying.x, 1.0 / textureSize.x);"
|
||||
|
||||
"vec2 vPosition = vec2(x, vertical.x);"
|
||||
"vec2 floatingPosition = (vPosition / positionConversion) + lateral * scanNormal;"
|
||||
@@ -89,8 +90,7 @@ std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_met
|
||||
return result;
|
||||
}
|
||||
|
||||
void OutputShader::set_output_size(unsigned int output_width, unsigned int output_height, Outputs::CRT::Rect visible_area)
|
||||
{
|
||||
void OutputShader::set_output_size(unsigned int output_width, unsigned int output_height, Outputs::CRT::Rect visible_area) {
|
||||
GLfloat outputAspectRatioMultiplier = ((float)output_width / (float)output_height) / (4.0f / 3.0f);
|
||||
|
||||
GLfloat bonusWidth = (outputAspectRatioMultiplier - 1.0f) * visible_area.size.width;
|
||||
@@ -101,13 +101,11 @@ void OutputShader::set_output_size(unsigned int output_width, unsigned int outpu
|
||||
set_uniform("boundsSize", (GLfloat)visible_area.size.width, (GLfloat)visible_area.size.height);
|
||||
}
|
||||
|
||||
void OutputShader::set_source_texture_unit(GLenum unit)
|
||||
{
|
||||
void OutputShader::set_source_texture_unit(GLenum unit) {
|
||||
set_uniform("texID", (GLint)(unit - GL_TEXTURE0));
|
||||
}
|
||||
|
||||
void OutputShader::set_timing(unsigned int height_of_display, unsigned int cycles_per_line, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider)
|
||||
{
|
||||
void OutputShader::set_timing(unsigned int height_of_display, unsigned int cycles_per_line, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider) {
|
||||
GLfloat scan_angle = atan2f(1.0f / (float)height_of_display, 1.0f);
|
||||
GLfloat scan_normal[] = { -sinf(scan_angle), cosf(scan_angle)};
|
||||
GLfloat multiplier = (float)cycles_per_line / ((float)height_of_display * (float)horizontal_scan_period);
|
||||
@@ -117,3 +115,11 @@ void OutputShader::set_timing(unsigned int height_of_display, unsigned int cycle
|
||||
set_uniform("scanNormal", scan_normal[0], scan_normal[1]);
|
||||
set_uniform("positionConversion", (GLfloat)horizontal_scan_period, (GLfloat)vertical_scan_period / (GLfloat)vertical_period_divider);
|
||||
}
|
||||
|
||||
void OutputShader::set_input_width_scaler(float input_scaler) {
|
||||
set_uniform("inputScaler", input_scaler);
|
||||
}
|
||||
|
||||
void OutputShader::set_origin_is_double_height(bool is_double_height) {
|
||||
set_uniform("textureHeightDivisor", is_double_height ? 2 : 1);
|
||||
}
|
||||
|
||||
@@ -58,6 +58,16 @@ public:
|
||||
to occur upon the next `bind`.
|
||||
*/
|
||||
void set_timing(unsigned int height_of_display, unsigned int cycles_per_line, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider);
|
||||
|
||||
/*!
|
||||
*/
|
||||
void set_origin_is_double_height(bool is_double_height);
|
||||
|
||||
/*!
|
||||
Sets the proportion of the input area that should be considered the whole width — 1.0 means use all available
|
||||
space, 0.5 means use half, etc.
|
||||
*/
|
||||
void set_input_width_scaler(float input_scaler);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user