mirror of
https://github.com/TomHarte/CLK.git
synced 2025-10-25 09:27:01 +00:00
Compare commits
431 Commits
2018-01-18
...
2018-05-24
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79c60b8984 | ||
|
|
2dc2c2ce79 | ||
|
|
523ca3264b | ||
|
|
4036c60b45 | ||
|
|
7d652e53e2 | ||
|
|
7c3dd55e5c | ||
|
|
1b43381be0 | ||
|
|
8f78e5039e | ||
|
|
57ee6d4e41 | ||
|
|
2868b1eca7 | ||
|
|
a4d7703efd | ||
|
|
ca4bc92c33 | ||
|
|
853261364e | ||
|
|
d3c5e4267f | ||
|
|
086b801c29 | ||
|
|
f9c25372c2 | ||
|
|
ea92363e6c | ||
|
|
015f692bd3 | ||
|
|
80d34f5511 | ||
|
|
e482929da8 | ||
|
|
4952657b31 | ||
|
|
46fae1a761 | ||
|
|
a09fb01d71 | ||
|
|
7cee3b7449 | ||
|
|
8263c48a1d | ||
|
|
ed06533e60 | ||
|
|
7b7beb13a3 | ||
|
|
c46007332a | ||
|
|
908d3b0ee5 | ||
|
|
8a031b1137 | ||
|
|
1aba9f807e | ||
|
|
4c49963988 | ||
|
|
821d40fe74 | ||
|
|
6ab1cf9325 | ||
|
|
076c0a48e9 | ||
|
|
fde613a5c4 | ||
|
|
44ad0970be | ||
|
|
b3f4d0ed8c | ||
|
|
bfdd3468ea | ||
|
|
f7decd80b6 | ||
|
|
7c2721d54d | ||
|
|
8907d0a9a7 | ||
|
|
6c8e6e9303 | ||
|
|
85c4e009f3 | ||
|
|
76802b5e38 | ||
|
|
9f2f388e5a | ||
|
|
729f53d84f | ||
|
|
d2d7ab5d04 | ||
|
|
5107c7c23d | ||
|
|
4dbd1f1358 | ||
|
|
7996040f35 | ||
|
|
0055efb720 | ||
|
|
dfa5eef20d | ||
|
|
3053acb4f3 | ||
|
|
dea9892a85 | ||
|
|
b9b6327707 | ||
|
|
84ae2964fd | ||
|
|
149b940f84 | ||
|
|
7226d8d4f7 | ||
|
|
ad9b0cd4e3 | ||
|
|
484e640d43 | ||
|
|
5d6b5d9f10 | ||
|
|
0b771ce61a | ||
|
|
72e07d4e83 | ||
|
|
2252c29495 | ||
|
|
39c0bc6c47 | ||
|
|
8f1a516a2c | ||
|
|
a6b8e88406 | ||
|
|
c19b50619f | ||
|
|
3747d96b22 | ||
|
|
8b23a08fc4 | ||
|
|
3fdefb94e4 | ||
|
|
49592ebaf3 | ||
|
|
f410dcb3f3 | ||
|
|
bd27f61a03 | ||
|
|
d703328114 | ||
|
|
afe222cb16 | ||
|
|
d0fd4dd4db | ||
|
|
3ba6b6f1ee | ||
|
|
bc464e247f | ||
|
|
c23f6d8d19 | ||
|
|
39d779edf0 | ||
|
|
0cb5362c6f | ||
|
|
a43ca0db35 | ||
|
|
9089bf6535 | ||
|
|
ef19a03efc | ||
|
|
85e1610627 | ||
|
|
d16ae84d0b | ||
|
|
95f859cf5c | ||
|
|
578a5b3e69 | ||
|
|
25f7e3af31 | ||
|
|
86192b18d1 | ||
|
|
c3144382c5 | ||
|
|
6bb9b7be04 | ||
|
|
8ee34fafa6 | ||
|
|
312171fa59 | ||
|
|
a8dbfb0569 | ||
|
|
b09b4b4433 | ||
|
|
45bd24ada0 | ||
|
|
c3a2f7717b | ||
|
|
70e6c3b2f6 | ||
|
|
d1b889aa61 | ||
|
|
f65c65569a | ||
|
|
1139caa83f | ||
|
|
d613c3c187 | ||
|
|
36f8b165cf | ||
|
|
d6e8b34942 | ||
|
|
4c4ab25d0e | ||
|
|
9ff34d90f4 | ||
|
|
9593e0f7fe | ||
|
|
1293d8b69e | ||
|
|
3e0055737e | ||
|
|
ba7fbc4032 | ||
|
|
c36d7b4972 | ||
|
|
1c0b5bb02b | ||
|
|
0dece80b5d | ||
|
|
e3b4aebf1a | ||
|
|
2e20191c01 | ||
|
|
59718e132b | ||
|
|
4d070fbfe3 | ||
|
|
723ee88043 | ||
|
|
65ba9ee6e7 | ||
|
|
fcc750784a | ||
|
|
3787d094ec | ||
|
|
4b4ea4a103 | ||
|
|
af0cf0d40a | ||
|
|
eecea93b3b | ||
|
|
ac4948c4b1 | ||
|
|
5e34c1b6b8 | ||
|
|
05e31d7594 | ||
|
|
f4097290c2 | ||
|
|
9da481b060 | ||
|
|
79002d6962 | ||
|
|
dbd9282efc | ||
|
|
b32538f3c8 | ||
|
|
e7618bb32e | ||
|
|
aacf26f05d | ||
|
|
265bc80d44 | ||
|
|
10c0e687f5 | ||
|
|
a9d4fe0b41 | ||
|
|
5cd15147eb | ||
|
|
c62db6665a | ||
|
|
fabcb261dc | ||
|
|
45cf28e0eb | ||
|
|
5b35c88be2 | ||
|
|
7f03f5d02f | ||
|
|
b98d5b790a | ||
|
|
5c74044e62 | ||
|
|
992a99d792 | ||
|
|
41075356e2 | ||
|
|
850a394eb5 | ||
|
|
244721a6f8 | ||
|
|
d59db504a3 | ||
|
|
c90e122eb2 | ||
|
|
4c6dc597f4 | ||
|
|
b4f6dee954 | ||
|
|
2685e9087e | ||
|
|
376b26c1e4 | ||
|
|
7061537ff5 | ||
|
|
2f2390b5aa | ||
|
|
99de8f1c5c | ||
|
|
af61bbc3e2 | ||
|
|
56d88f23ef | ||
|
|
4bff44377a | ||
|
|
7463edaa1b | ||
|
|
e92e06a5f4 | ||
|
|
4cbe5068a9 | ||
|
|
38b2302b59 | ||
|
|
bce0702745 | ||
|
|
d447e81abd | ||
|
|
6592745e53 | ||
|
|
e87a3cffd4 | ||
|
|
fa0b6e8a08 | ||
|
|
074b4c3500 | ||
|
|
5968c9a391 | ||
|
|
72bc5f8d7b | ||
|
|
0a0d81cd5a | ||
|
|
75e9c3678b | ||
|
|
aebe8a64a2 | ||
|
|
1aacf437b5 | ||
|
|
7e8e3fdd39 | ||
|
|
b8ae283049 | ||
|
|
6621e54952 | ||
|
|
e03a403a51 | ||
|
|
ba43b3e6b8 | ||
|
|
b4a2d1395c | ||
|
|
f5ae8d0f79 | ||
|
|
5f1c210746 | ||
|
|
f6c2f6e896 | ||
|
|
6547560e52 | ||
|
|
a167e3849b | ||
|
|
f22c23cb4c | ||
|
|
a07c99d778 | ||
|
|
1c605d58e3 | ||
|
|
6a79ce9eb1 | ||
|
|
465c38f03c | ||
|
|
be05d51e07 | ||
|
|
9bc470027e | ||
|
|
335c633884 | ||
|
|
cd26f11818 | ||
|
|
abe47b6ed8 | ||
|
|
61659faeaa | ||
|
|
71adb964e5 | ||
|
|
e599e65087 | ||
|
|
7efee9b52b | ||
|
|
079dc671e1 | ||
|
|
a32a7d1374 | ||
|
|
467cd5450f | ||
|
|
1580874a55 | ||
|
|
15f7cbe8c1 | ||
|
|
428b6145fa | ||
|
|
3ad0b31db8 | ||
|
|
8d4d5d1f46 | ||
|
|
4c8a68c6a4 | ||
|
|
0b4b6f4aec | ||
|
|
bb4db6b382 | ||
|
|
94b1c37fb2 | ||
|
|
cf6f6c5c15 | ||
|
|
f541986333 | ||
|
|
44513d6912 | ||
|
|
b20cbcd5fe | ||
|
|
1c5972f7b0 | ||
|
|
28947bb3c4 | ||
|
|
865c47a1ac | ||
|
|
3821679efd | ||
|
|
506b4da6c3 | ||
|
|
10f637d2cf | ||
|
|
0bab7c88f0 | ||
|
|
78c612ca17 | ||
|
|
e1c4035812 | ||
|
|
eb6d6c8033 | ||
|
|
7bf88565ce | ||
|
|
ee10155296 | ||
|
|
cc49140f6f | ||
|
|
3e846f89a1 | ||
|
|
5782cab2a0 | ||
|
|
8c511e2b76 | ||
|
|
ec72fb3baf | ||
|
|
bab1440f5c | ||
|
|
60c1da6a66 | ||
|
|
a849b3f2e4 | ||
|
|
dbe3c5c3f8 | ||
|
|
60cf6b3cfd | ||
|
|
5044aac337 | ||
|
|
36e0cb29c0 | ||
|
|
c0b4dd65da | ||
|
|
d061ea232b | ||
|
|
49feca4ddf | ||
|
|
46b1c57bf4 | ||
|
|
eaf1482182 | ||
|
|
d3418550eb | ||
|
|
3ffa9e2751 | ||
|
|
c697dd78f0 | ||
|
|
7dac791290 | ||
|
|
cde2faeda6 | ||
|
|
69f520428d | ||
|
|
80c84ddd75 | ||
|
|
fca8a58b36 | ||
|
|
33084899d0 | ||
|
|
7b381a8b6b | ||
|
|
9c75689a8d | ||
|
|
0ee40e8556 | ||
|
|
8b45377b89 | ||
|
|
f6fb368d88 | ||
|
|
183a5379de | ||
|
|
912791d3d4 | ||
|
|
163a61dd63 | ||
|
|
207d462dbf | ||
|
|
33281b9d89 | ||
|
|
389979923e | ||
|
|
067174965e | ||
|
|
286259c83b | ||
|
|
e1aa3e5a7f | ||
|
|
78e1c2851a | ||
|
|
0869213c55 | ||
|
|
f3fe16215a | ||
|
|
ec353ce663 | ||
|
|
b7ff5ef9dd | ||
|
|
3b26e0a7c5 | ||
|
|
6d464557a0 | ||
|
|
a776bec46a | ||
|
|
a2da51c30b | ||
|
|
8067bf548a | ||
|
|
62b0645ed0 | ||
|
|
39a94874ae | ||
|
|
e15d6717a1 | ||
|
|
37ef46e7bb | ||
|
|
70c09b3031 | ||
|
|
9378fbb0df | ||
|
|
2118b9c0cd | ||
|
|
d0c53de250 | ||
|
|
d98507eab0 | ||
|
|
760c75103e | ||
|
|
4407fd2f1f | ||
|
|
7fcd243be0 | ||
|
|
3165e9d82e | ||
|
|
6656a08c60 | ||
|
|
76661c0b51 | ||
|
|
3bb496f9ae | ||
|
|
45be1c19df | ||
|
|
a301964bd0 | ||
|
|
eea6858121 | ||
|
|
2a320fdf56 | ||
|
|
4695296ef2 | ||
|
|
0fdbbeca1d | ||
|
|
34cc39ad65 | ||
|
|
3d0c832a21 | ||
|
|
1acdab9448 | ||
|
|
93e85c5c4a | ||
|
|
ab98189d25 | ||
|
|
cd0fb7624b | ||
|
|
bae38497bb | ||
|
|
29921bfa8d | ||
|
|
2712702461 | ||
|
|
a3fa9440d1 | ||
|
|
6419b0e619 | ||
|
|
58e5b6e3f1 | ||
|
|
682c3d8079 | ||
|
|
da3d65c18f | ||
|
|
ece3a05504 | ||
|
|
927697b0f0 | ||
|
|
74dfc80b0f | ||
|
|
a7f229bc4b | ||
|
|
89bec2919f | ||
|
|
78eaecb29e | ||
|
|
d410aea856 | ||
|
|
6b1eef572b | ||
|
|
719f5d79c2 | ||
|
|
48737a32a7 | ||
|
|
53f05efb2d | ||
|
|
0e73ba4b3e | ||
|
|
f0f9d5a6af | ||
|
|
03501df9e5 | ||
|
|
dd6f85d4db | ||
|
|
1804ea6849 | ||
|
|
c8657e08f4 | ||
|
|
a942e1319b | ||
|
|
9e0a56b4f0 | ||
|
|
9abc020818 | ||
|
|
2dade8d353 | ||
|
|
1100dc6993 | ||
|
|
f212b18511 | ||
|
|
a6ca69550f | ||
|
|
2452641844 | ||
|
|
c82af4b814 | ||
|
|
fdef914137 | ||
|
|
dfcc502a88 | ||
|
|
1c6faaae88 | ||
|
|
35c8a0dd8c | ||
|
|
38feedaf6a | ||
|
|
0a2f908af4 | ||
|
|
705d53cc21 | ||
|
|
35b18d58af | ||
|
|
3c5a8d9ff3 | ||
|
|
7ca02be578 | ||
|
|
ea13c7dd32 | ||
|
|
fdfd72a42c | ||
|
|
da97bf95c0 | ||
|
|
bdfc36427c | ||
|
|
74dfe56d2b | ||
|
|
6cce9aa54e | ||
|
|
ba68b7247b | ||
|
|
b02e4fbbf6 | ||
|
|
59b4c7314d | ||
|
|
d328589bd0 | ||
|
|
b05d2b26bf | ||
|
|
86239469e7 | ||
|
|
7890506b16 | ||
|
|
83f73c3f02 | ||
|
|
87760297fc | ||
|
|
5b854d51e7 | ||
|
|
d4df101ab6 | ||
|
|
0ad2676640 | ||
|
|
a074ee2071 | ||
|
|
204d5cc964 | ||
|
|
23d15a4d6c | ||
|
|
23c47e21de | ||
|
|
5530b96446 | ||
|
|
99d28a172b | ||
|
|
d83178f29d | ||
|
|
d9d5ffdaa2 | ||
|
|
cabad6fc05 | ||
|
|
a4dc9c0403 | ||
|
|
270723ae72 | ||
|
|
b215cf83d5 | ||
|
|
f237dcf904 | ||
|
|
fc81bfa59b | ||
|
|
832ac173ae | ||
|
|
3673cfe9be | ||
|
|
6aaef97158 | ||
|
|
b0ab617393 | ||
|
|
6780b0bf11 | ||
|
|
9c0a440c38 | ||
|
|
2439f5aee5 | ||
|
|
8265f289bd | ||
|
|
9728bea0a7 | ||
|
|
fc9e84c72e | ||
|
|
7d75e864b1 | ||
|
|
a005dabbe3 | ||
|
|
c8a4432c63 | ||
|
|
7b420d56e3 | ||
|
|
ddf1bf3cbf | ||
|
|
7ea4ca00dc | ||
|
|
6b8c223804 | ||
|
|
23105956d6 | ||
|
|
d751b7e2cb | ||
|
|
f02989649c | ||
|
|
dcf313a833 | ||
|
|
9960121b08 | ||
|
|
8eea55b51c | ||
|
|
e1cab52c84 | ||
|
|
eb39617ad0 | ||
|
|
43b682a5af | ||
|
|
043fd5d404 | ||
|
|
d63a95983d | ||
|
|
4cf258f952 | ||
|
|
4e720d57b2 | ||
|
|
c12aaea747 | ||
|
|
ca48497e87 | ||
|
|
d493ea4bca | ||
|
|
e025674eb2 | ||
|
|
f2519f4fd7 | ||
|
|
db914d8c56 | ||
|
|
66faed4008 | ||
|
|
11abc99ef8 | ||
|
|
21efb32b6f | ||
|
|
622a04aec8 | ||
|
|
d360b2c62d | ||
|
|
6a112edc18 | ||
|
|
8fb4409ebb | ||
|
|
d213341d9c |
18
.travis.yml
18
.travis.yml
@@ -1,5 +1,13 @@
|
|||||||
language: objective-c
|
# language: objective-c
|
||||||
osx_image: xcode8.2
|
# osx_image: xcode8.2
|
||||||
xcode_project: OSBindings/Mac/Clock Signal.xcodeproj
|
# xcode_project: OSBindings/Mac/Clock Signal.xcodeproj
|
||||||
xcode_scheme: Clock Signal
|
# xcode_scheme: Clock Signal
|
||||||
xcode_sdk: macosx10.12
|
# xcode_sdk: macosx10.12
|
||||||
|
|
||||||
|
language: cpp
|
||||||
|
before_install:
|
||||||
|
- sudo apt-get install libsdl2-dev
|
||||||
|
script: cd OSBindings/SDL && scons
|
||||||
|
compiler:
|
||||||
|
- clang
|
||||||
|
- gcc
|
||||||
|
|||||||
51
Activity/Observer.hpp
Normal file
51
Activity/Observer.hpp
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
//
|
||||||
|
// ActivityObserver.h
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 07/05/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef ActivityObserver_h
|
||||||
|
#define ActivityObserver_h
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Activity {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Provides a purely virtual base class for anybody that wants to receive notifications of
|
||||||
|
'activity': any feedback from an emulated system which a user could perceive other than
|
||||||
|
through the machine's native audio and video outputs.
|
||||||
|
|
||||||
|
So: status LEDs, drive activity, etc. A receiver may choose to make appropriate noises
|
||||||
|
and/or to show or unshow status indicators.
|
||||||
|
*/
|
||||||
|
class Observer {
|
||||||
|
public:
|
||||||
|
/// Announces to the receiver that there is an LED of name @c name.
|
||||||
|
virtual void register_led(const std::string &name) = 0;
|
||||||
|
|
||||||
|
/// Announces to the receiver that there is a drive of name @c name.
|
||||||
|
virtual void register_drive(const std::string &name) = 0;
|
||||||
|
|
||||||
|
/// Informs the receiver of the new state of the LED with name @c name.
|
||||||
|
virtual void set_led_status(const std::string &name, bool lit) = 0;
|
||||||
|
|
||||||
|
enum class DriveEvent {
|
||||||
|
StepNormal,
|
||||||
|
StepBelowZero,
|
||||||
|
StepBeyondMaximum
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Informs the receiver that the named event just occurred for the drive with name @c name.
|
||||||
|
virtual void announce_drive_event(const std::string &name, DriveEvent event) = 0;
|
||||||
|
|
||||||
|
/// Informs the receiver of the motor-on status of the drive with name @c name.
|
||||||
|
virtual void set_drive_motor_status(const std::string &name, bool is_on) = 0;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* ActivityObserver_h */
|
||||||
24
Activity/Source.hpp
Normal file
24
Activity/Source.hpp
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
//
|
||||||
|
// ActivitySource.h
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 07/05/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef ActivitySource_h
|
||||||
|
#define ActivitySource_h
|
||||||
|
|
||||||
|
#include "Observer.hpp"
|
||||||
|
|
||||||
|
namespace Activity {
|
||||||
|
|
||||||
|
class Source {
|
||||||
|
public:
|
||||||
|
virtual void set_activity_observer(Observer *observer) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* ActivitySource_h */
|
||||||
30
Analyser/Dynamic/ConfidenceCounter.cpp
Normal file
30
Analyser/Dynamic/ConfidenceCounter.cpp
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
//
|
||||||
|
// ConfidenceCounter.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 21/01/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "ConfidenceCounter.hpp"
|
||||||
|
|
||||||
|
using namespace Analyser::Dynamic;
|
||||||
|
|
||||||
|
float ConfidenceCounter::get_confidence() {
|
||||||
|
return static_cast<float>(hits_) / static_cast<float>(hits_ + misses_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfidenceCounter::add_hit() {
|
||||||
|
hits_++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfidenceCounter::add_miss() {
|
||||||
|
misses_++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfidenceCounter::add_equivocal() {
|
||||||
|
if(hits_ > misses_) {
|
||||||
|
hits_++;
|
||||||
|
misses_++;
|
||||||
|
}
|
||||||
|
}
|
||||||
47
Analyser/Dynamic/ConfidenceCounter.hpp
Normal file
47
Analyser/Dynamic/ConfidenceCounter.hpp
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
//
|
||||||
|
// ConfidenceCounter.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 21/01/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef ConfidenceCounter_hpp
|
||||||
|
#define ConfidenceCounter_hpp
|
||||||
|
|
||||||
|
#include "ConfidenceSource.hpp"
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Dynamic {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Provides a confidence source that calculates its probability by virtual of a history of events.
|
||||||
|
|
||||||
|
The initial value of the confidence counter is 0.5.
|
||||||
|
*/
|
||||||
|
class ConfidenceCounter: public ConfidenceSource {
|
||||||
|
public:
|
||||||
|
/*! @returns The computed probability, based on the history of events. */
|
||||||
|
float get_confidence() override;
|
||||||
|
|
||||||
|
/*! Records an event that implies this is the appropriate class: pushes probability up towards 1.0. */
|
||||||
|
void add_hit();
|
||||||
|
|
||||||
|
/*! Records an event that implies this is not the appropriate class: pushes probability down towards 0.0. */
|
||||||
|
void add_miss();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Records an event that could be correct but isn't necessarily so; which can push probability
|
||||||
|
down towards 0.5, but will never push it upwards.
|
||||||
|
*/
|
||||||
|
void add_equivocal();
|
||||||
|
|
||||||
|
private:
|
||||||
|
int hits_ = 1;
|
||||||
|
int misses_ = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* ConfidenceCounter_hpp */
|
||||||
28
Analyser/Dynamic/ConfidenceSource.hpp
Normal file
28
Analyser/Dynamic/ConfidenceSource.hpp
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
//
|
||||||
|
// ConfidenceSource.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 21/01/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef ConfidenceSource_hpp
|
||||||
|
#define ConfidenceSource_hpp
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Dynamic {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Provides an abstract interface through which objects can declare the probability
|
||||||
|
that they are the proper target for their input; e.g. if an Acorn Electron is asked
|
||||||
|
to run an Atari 2600 program then its confidence should shrink towards 0.0; if the
|
||||||
|
program is handed to an Atari 2600 then its confidence should grow towards 1.0.
|
||||||
|
*/
|
||||||
|
struct ConfidenceSource {
|
||||||
|
virtual float get_confidence() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* ConfidenceSource_hpp */
|
||||||
28
Analyser/Dynamic/ConfidenceSummary.cpp
Normal file
28
Analyser/Dynamic/ConfidenceSummary.cpp
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
//
|
||||||
|
// ConfidenceSummary.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 21/01/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "ConfidenceSummary.hpp"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
|
using namespace Analyser::Dynamic;
|
||||||
|
|
||||||
|
ConfidenceSummary::ConfidenceSummary(const std::vector<ConfidenceSource *> &sources, const std::vector<float> &weights) :
|
||||||
|
sources_(sources), weights_(weights) {
|
||||||
|
assert(weights.size() == sources.size());
|
||||||
|
weight_sum_ = std::accumulate(weights.begin(), weights.end(), 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
float ConfidenceSummary::get_confidence() {
|
||||||
|
float result = 0.0f;
|
||||||
|
for(std::size_t index = 0; index < sources_.size(); ++index) {
|
||||||
|
result += sources_[index]->get_confidence() * weights_[index];
|
||||||
|
}
|
||||||
|
return result / weight_sum_;
|
||||||
|
}
|
||||||
46
Analyser/Dynamic/ConfidenceSummary.hpp
Normal file
46
Analyser/Dynamic/ConfidenceSummary.hpp
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
//
|
||||||
|
// ConfidenceSummary.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 21/01/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef ConfidenceSummary_hpp
|
||||||
|
#define ConfidenceSummary_hpp
|
||||||
|
|
||||||
|
#include "ConfidenceSource.hpp"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Dynamic {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Summaries a collection of confidence sources by calculating their weighted sum.
|
||||||
|
*/
|
||||||
|
class ConfidenceSummary: public ConfidenceSource {
|
||||||
|
public:
|
||||||
|
/*!
|
||||||
|
Instantiates a summary that will produce the weighted sum of
|
||||||
|
@c sources, each using the corresponding entry of @c weights.
|
||||||
|
|
||||||
|
Requires that @c sources and @c weights are of the same length.
|
||||||
|
*/
|
||||||
|
ConfidenceSummary(
|
||||||
|
const std::vector<ConfidenceSource *> &sources,
|
||||||
|
const std::vector<float> &weights);
|
||||||
|
|
||||||
|
/*! @returns The weighted sum of all sources. */
|
||||||
|
float get_confidence() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<ConfidenceSource *> sources_;
|
||||||
|
std::vector<float> weights_;
|
||||||
|
float weight_sum_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* ConfidenceSummary_hpp */
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
//
|
||||||
|
// MultiCRTMachine.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 29/01/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "MultiCRTMachine.hpp"
|
||||||
|
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
using namespace Analyser::Dynamic;
|
||||||
|
|
||||||
|
MultiCRTMachine::MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::mutex &machines_mutex) :
|
||||||
|
machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {
|
||||||
|
speaker_ = MultiSpeaker::create(machines);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiCRTMachine::perform_parallel(const std::function<void(::CRTMachine::Machine *)> &function) {
|
||||||
|
// Apply a blunt force parallelisation of the machines; each run_for is dispatched
|
||||||
|
// to a separate queue and this queue will block until all are done.
|
||||||
|
volatile std::size_t outstanding_machines;
|
||||||
|
std::condition_variable condition;
|
||||||
|
std::mutex mutex;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
|
||||||
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
outstanding_machines = machines_.size();
|
||||||
|
|
||||||
|
for(std::size_t index = 0; index < machines_.size(); ++index) {
|
||||||
|
CRTMachine::Machine *crt_machine = machines_[index]->crt_machine();
|
||||||
|
queues_[index].enqueue([&mutex, &condition, crt_machine, function, &outstanding_machines]() {
|
||||||
|
if(crt_machine) function(crt_machine);
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
outstanding_machines--;
|
||||||
|
condition.notify_all();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_lock<std::mutex> lock(mutex);
|
||||||
|
condition.wait(lock, [&outstanding_machines] { return !outstanding_machines; });
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiCRTMachine::perform_serial(const std::function<void (::CRTMachine::Machine *)> &function) {
|
||||||
|
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
|
||||||
|
for(const auto &machine: machines_) {
|
||||||
|
CRTMachine::Machine *crt_machine = machine->crt_machine();
|
||||||
|
if(crt_machine) function(crt_machine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiCRTMachine::setup_output(float aspect_ratio) {
|
||||||
|
perform_serial([=](::CRTMachine::Machine *machine) {
|
||||||
|
machine->setup_output(aspect_ratio);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiCRTMachine::close_output() {
|
||||||
|
perform_serial([=](::CRTMachine::Machine *machine) {
|
||||||
|
machine->close_output();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Outputs::CRT::CRT *MultiCRTMachine::get_crt() {
|
||||||
|
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
|
||||||
|
CRTMachine::Machine *crt_machine = machines_.front()->crt_machine();
|
||||||
|
return crt_machine ? crt_machine->get_crt() : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Outputs::Speaker::Speaker *MultiCRTMachine::get_speaker() {
|
||||||
|
return speaker_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiCRTMachine::run_for(Time::Seconds duration) {
|
||||||
|
perform_parallel([=](::CRTMachine::Machine *machine) {
|
||||||
|
if(machine->get_confidence() >= 0.01f) machine->run_for(duration);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(delegate_) delegate_->multi_crt_did_run_machines();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiCRTMachine::did_change_machine_order() {
|
||||||
|
if(speaker_) {
|
||||||
|
speaker_->set_new_front_machine(machines_.front().get());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
//
|
||||||
|
// MultiCRTMachine.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 29/01/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef MultiCRTMachine_hpp
|
||||||
|
#define MultiCRTMachine_hpp
|
||||||
|
|
||||||
|
#include "../../../../Concurrency/AsyncTaskQueue.hpp"
|
||||||
|
#include "../../../../Machines/CRTMachine.hpp"
|
||||||
|
#include "../../../../Machines/DynamicMachine.hpp"
|
||||||
|
|
||||||
|
#include "MultiSpeaker.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Dynamic {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Provides a class that multiplexes the CRT machine interface to multiple machines.
|
||||||
|
|
||||||
|
Keeps a reference to the original vector of machines; will access it only after
|
||||||
|
acquiring a supplied mutex. The owner should also call did_change_machine_order()
|
||||||
|
if the order of machines changes.
|
||||||
|
*/
|
||||||
|
class MultiCRTMachine: public CRTMachine::Machine {
|
||||||
|
public:
|
||||||
|
MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::mutex &machines_mutex);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Informs the MultiCRTMachine that the order of machines has changed; the MultiCRTMachine
|
||||||
|
uses this as an opportunity to synthesis any CRTMachine::Machine::Delegate messages that
|
||||||
|
are necessary to bridge the gap between one machine and the next.
|
||||||
|
*/
|
||||||
|
void did_change_machine_order();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Provides a mechanism by which a delegate can be informed each time a call to run_for has
|
||||||
|
been received.
|
||||||
|
*/
|
||||||
|
struct Delegate {
|
||||||
|
virtual void multi_crt_did_run_machines() = 0;
|
||||||
|
};
|
||||||
|
/// Sets @c delegate as the receiver of delegate messages.
|
||||||
|
void set_delegate(Delegate *delegate) {
|
||||||
|
delegate_ = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Below is the standard CRTMachine::Machine interface; see there for documentation.
|
||||||
|
void setup_output(float aspect_ratio) override;
|
||||||
|
void close_output() override;
|
||||||
|
Outputs::CRT::CRT *get_crt() override;
|
||||||
|
Outputs::Speaker::Speaker *get_speaker() override;
|
||||||
|
void run_for(Time::Seconds duration) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void run_for(const Cycles cycles) override {}
|
||||||
|
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
|
||||||
|
std::mutex &machines_mutex_;
|
||||||
|
std::vector<Concurrency::AsyncTaskQueue> queues_;
|
||||||
|
MultiSpeaker *speaker_ = nullptr;
|
||||||
|
Delegate *delegate_ = nullptr;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Performs a parallel for operation across all machines, performing the supplied
|
||||||
|
function on each and returning only once all applications have completed.
|
||||||
|
|
||||||
|
No guarantees are extended as to which thread operations will occur on.
|
||||||
|
*/
|
||||||
|
void perform_parallel(const std::function<void(::CRTMachine::Machine *)> &);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Performs a serial for operation across all machines, performing the supplied
|
||||||
|
function on each on the calling thread.
|
||||||
|
*/
|
||||||
|
void perform_serial(const std::function<void(::CRTMachine::Machine *)> &);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* MultiCRTMachine_hpp */
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
//
|
||||||
|
// MultiConfigurable.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 09/02/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "MultiConfigurable.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
using namespace Analyser::Dynamic;
|
||||||
|
|
||||||
|
MultiConfigurable::MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
|
||||||
|
for(const auto &machine: machines) {
|
||||||
|
Configurable::Device *device = machine->configurable_device();
|
||||||
|
if(device) devices_.push_back(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<Configurable::Option>> MultiConfigurable::get_options() {
|
||||||
|
std::vector<std::unique_ptr<Configurable::Option>> options;
|
||||||
|
|
||||||
|
// Produce the list of unique options.
|
||||||
|
for(const auto &device : devices_) {
|
||||||
|
std::vector<std::unique_ptr<Configurable::Option>> device_options = device->get_options();
|
||||||
|
for(auto &option : device_options) {
|
||||||
|
if(std::find(options.begin(), options.end(), option) == options.end()) {
|
||||||
|
options.push_back(std::move(option));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiConfigurable::set_selections(const Configurable::SelectionSet &selection_by_option) {
|
||||||
|
for(const auto &device : devices_) {
|
||||||
|
device->set_selections(selection_by_option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Configurable::SelectionSet MultiConfigurable::get_accurate_selections() {
|
||||||
|
Configurable::SelectionSet set;
|
||||||
|
for(const auto &device : devices_) {
|
||||||
|
Configurable::SelectionSet device_set = device->get_accurate_selections();
|
||||||
|
for(auto &selection : device_set) {
|
||||||
|
set.insert(std::move(selection));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
Configurable::SelectionSet MultiConfigurable::get_user_friendly_selections() {
|
||||||
|
Configurable::SelectionSet set;
|
||||||
|
for(const auto &device : devices_) {
|
||||||
|
Configurable::SelectionSet device_set = device->get_user_friendly_selections();
|
||||||
|
for(auto &selection : device_set) {
|
||||||
|
set.insert(std::move(selection));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return set;
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
//
|
||||||
|
// MultiConfigurable.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 09/02/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef MultiConfigurable_hpp
|
||||||
|
#define MultiConfigurable_hpp
|
||||||
|
|
||||||
|
#include "../../../../Machines/DynamicMachine.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Dynamic {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Provides a class that multiplexes the configurable interface to multiple machines.
|
||||||
|
|
||||||
|
Makes a static internal copy of the list of machines; makes no guarantees about the
|
||||||
|
order of delivered messages.
|
||||||
|
*/
|
||||||
|
class MultiConfigurable: public Configurable::Device {
|
||||||
|
public:
|
||||||
|
MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||||
|
|
||||||
|
// Below is the standard Configurable::Device interface; see there for documentation.
|
||||||
|
std::vector<std::unique_ptr<Configurable::Option>> get_options() override;
|
||||||
|
void set_selections(const Configurable::SelectionSet &selection_by_option) override;
|
||||||
|
Configurable::SelectionSet get_accurate_selections() override;
|
||||||
|
Configurable::SelectionSet get_user_friendly_selections() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Configurable::Device *> devices_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* MultiConfigurable_hpp */
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
//
|
||||||
|
// MultiConfigurationTarget.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 29/01/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "MultiConfigurationTarget.hpp"
|
||||||
|
|
||||||
|
using namespace Analyser::Dynamic;
|
||||||
|
|
||||||
|
MultiConfigurationTarget::MultiConfigurationTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
|
||||||
|
for(const auto &machine: machines) {
|
||||||
|
ConfigurationTarget::Machine *configuration_target = machine->configuration_target();
|
||||||
|
if(configuration_target) targets_.push_back(configuration_target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiConfigurationTarget::configure_as_target(const Analyser::Static::Target *target) {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MultiConfigurationTarget::insert_media(const Analyser::Static::Media &media) {
|
||||||
|
bool inserted = false;
|
||||||
|
for(const auto &target : targets_) {
|
||||||
|
inserted |= target->insert_media(media);
|
||||||
|
}
|
||||||
|
return inserted;
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
//
|
||||||
|
// MultiConfigurationTarget.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 29/01/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef MultiConfigurationTarget_hpp
|
||||||
|
#define MultiConfigurationTarget_hpp
|
||||||
|
|
||||||
|
#include "../../../../Machines/ConfigurationTarget.hpp"
|
||||||
|
#include "../../../../Machines/DynamicMachine.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Dynamic {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Provides a class that multiplexes the configuration target interface to multiple machines.
|
||||||
|
|
||||||
|
Makes a static internal copy of the list of machines; makes no guarantees about the
|
||||||
|
order of delivered messages.
|
||||||
|
*/
|
||||||
|
struct MultiConfigurationTarget: public ConfigurationTarget::Machine {
|
||||||
|
public:
|
||||||
|
MultiConfigurationTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||||
|
|
||||||
|
// Below is the standard ConfigurationTarget::Machine interface; see there for documentation.
|
||||||
|
void configure_as_target(const Analyser::Static::Target *target) override;
|
||||||
|
bool insert_media(const Analyser::Static::Media &media) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<ConfigurationTarget::Machine *> targets_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* MultiConfigurationTarget_hpp */
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
//
|
||||||
|
// MultiJoystickMachine.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 09/02/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "MultiJoystickMachine.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
using namespace Analyser::Dynamic;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class MultiJoystick: public Inputs::Joystick {
|
||||||
|
public:
|
||||||
|
MultiJoystick(std::vector<JoystickMachine::Machine *> &machines, std::size_t index) {
|
||||||
|
for(const auto &machine: machines) {
|
||||||
|
const auto &joysticks = machine->get_joysticks();
|
||||||
|
if(joysticks.size() >= index) {
|
||||||
|
joysticks_.push_back(joysticks[index].get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<DigitalInput> get_inputs() override {
|
||||||
|
std::vector<DigitalInput> inputs;
|
||||||
|
|
||||||
|
for(const auto &joystick: joysticks_) {
|
||||||
|
std::vector<DigitalInput> joystick_inputs = joystick->get_inputs();
|
||||||
|
for(const auto &input: joystick_inputs) {
|
||||||
|
if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) {
|
||||||
|
inputs.push_back(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return inputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_digital_input(const DigitalInput &digital_input, bool is_active) override {
|
||||||
|
for(const auto &joystick: joysticks_) {
|
||||||
|
joystick->set_digital_input(digital_input, is_active);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void reset_all_inputs() override {
|
||||||
|
for(const auto &joystick: joysticks_) {
|
||||||
|
joystick->reset_all_inputs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Inputs::Joystick *> joysticks_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiJoystickMachine::MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
|
||||||
|
std::size_t total_joysticks = 0;
|
||||||
|
std::vector<JoystickMachine::Machine *> joystick_machines;
|
||||||
|
for(const auto &machine: machines) {
|
||||||
|
JoystickMachine::Machine *joystick_machine = machine->joystick_machine();
|
||||||
|
if(joystick_machine) {
|
||||||
|
joystick_machines.push_back(joystick_machine);
|
||||||
|
total_joysticks = std::max(total_joysticks, joystick_machine->get_joysticks().size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(std::size_t index = 0; index < total_joysticks; ++index) {
|
||||||
|
joysticks_.emplace_back(new MultiJoystick(joystick_machines, index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<Inputs::Joystick>> &MultiJoystickMachine::get_joysticks() {
|
||||||
|
return joysticks_;
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
//
|
||||||
|
// MultiJoystickMachine.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 09/02/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef MultiJoystickMachine_hpp
|
||||||
|
#define MultiJoystickMachine_hpp
|
||||||
|
|
||||||
|
#include "../../../../Machines/DynamicMachine.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Dynamic {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Provides a class that multiplexes the joystick machine interface to multiple machines.
|
||||||
|
|
||||||
|
Makes a static internal copy of the list of machines; makes no guarantees about the
|
||||||
|
order of delivered messages.
|
||||||
|
*/
|
||||||
|
class MultiJoystickMachine: public JoystickMachine::Machine {
|
||||||
|
public:
|
||||||
|
MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||||
|
|
||||||
|
// Below is the standard JoystickMachine::Machine interface; see there for documentation.
|
||||||
|
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* MultiJoystickMachine_hpp */
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
//
|
||||||
|
// MultiKeyboardMachine.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 09/02/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "MultiKeyboardMachine.hpp"
|
||||||
|
|
||||||
|
using namespace Analyser::Dynamic;
|
||||||
|
|
||||||
|
MultiKeyboardMachine::MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
|
||||||
|
for(const auto &machine: machines) {
|
||||||
|
KeyboardMachine::Machine *keyboard_machine = machine->keyboard_machine();
|
||||||
|
if(keyboard_machine) machines_.push_back(keyboard_machine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiKeyboardMachine::clear_all_keys() {
|
||||||
|
for(const auto &machine: machines_) {
|
||||||
|
machine->clear_all_keys();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiKeyboardMachine::set_key_state(uint16_t key, bool is_pressed) {
|
||||||
|
for(const auto &machine: machines_) {
|
||||||
|
machine->set_key_state(key, is_pressed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiKeyboardMachine::type_string(const std::string &string) {
|
||||||
|
for(const auto &machine: machines_) {
|
||||||
|
machine->type_string(string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiKeyboardMachine::keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) {
|
||||||
|
for(const auto &machine: machines_) {
|
||||||
|
uint16_t mapped_key = machine->get_keyboard_mapper()->mapped_key_for_key(key);
|
||||||
|
if(mapped_key != KeyNotMapped) machine->set_key_state(mapped_key, is_pressed);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
//
|
||||||
|
// MultiKeyboardMachine.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 09/02/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef MultiKeyboardMachine_hpp
|
||||||
|
#define MultiKeyboardMachine_hpp
|
||||||
|
|
||||||
|
#include "../../../../Machines/DynamicMachine.hpp"
|
||||||
|
#include "../../../../Machines/KeyboardMachine.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Dynamic {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Provides a class that multiplexes the keyboard machine interface to multiple machines.
|
||||||
|
|
||||||
|
Makes a static internal copy of the list of machines; makes no guarantees about the
|
||||||
|
order of delivered messages.
|
||||||
|
*/
|
||||||
|
class MultiKeyboardMachine: public KeyboardMachine::Machine {
|
||||||
|
public:
|
||||||
|
MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||||
|
|
||||||
|
// Below is the standard KeyboardMachine::Machine interface; see there for documentation.
|
||||||
|
void clear_all_keys() override;
|
||||||
|
void set_key_state(uint16_t key, bool is_pressed) override;
|
||||||
|
void type_string(const std::string &) override;
|
||||||
|
void keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<::KeyboardMachine::Machine *> machines_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* MultiKeyboardMachine_hpp */
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
//
|
||||||
|
// MultiSpeaker.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 18/02/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "MultiSpeaker.hpp"
|
||||||
|
|
||||||
|
using namespace Analyser::Dynamic;
|
||||||
|
|
||||||
|
MultiSpeaker *MultiSpeaker::create(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
|
||||||
|
std::vector<Outputs::Speaker::Speaker *> speakers;
|
||||||
|
for(const auto &machine: machines) {
|
||||||
|
Outputs::Speaker::Speaker *speaker = machine->crt_machine()->get_speaker();
|
||||||
|
if(speaker) speakers.push_back(speaker);
|
||||||
|
}
|
||||||
|
if(speakers.empty()) return nullptr;
|
||||||
|
|
||||||
|
return new MultiSpeaker(speakers);
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiSpeaker::MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers) :
|
||||||
|
speakers_(speakers), front_speaker_(speakers.front()) {
|
||||||
|
for(const auto &speaker: speakers_) {
|
||||||
|
speaker->set_delegate(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float MultiSpeaker::get_ideal_clock_rate_in_range(float minimum, float maximum) {
|
||||||
|
float ideal = 0.0f;
|
||||||
|
for(const auto &speaker: speakers_) {
|
||||||
|
ideal += speaker->get_ideal_clock_rate_in_range(minimum, maximum);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ideal / static_cast<float>(speakers_.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSpeaker::set_output_rate(float cycles_per_second, int buffer_size) {
|
||||||
|
for(const auto &speaker: speakers_) {
|
||||||
|
speaker->set_output_rate(cycles_per_second, buffer_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSpeaker::set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) {
|
||||||
|
delegate_ = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) {
|
||||||
|
if(!delegate_) return;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
|
||||||
|
if(speaker != front_speaker_) return;
|
||||||
|
}
|
||||||
|
delegate_->speaker_did_complete_samples(this, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {
|
||||||
|
if(!delegate_) return;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
|
||||||
|
if(speaker != front_speaker_) return;
|
||||||
|
}
|
||||||
|
delegate_->speaker_did_change_input_clock(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) {
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
|
||||||
|
front_speaker_ = machine->crt_machine()->get_speaker();
|
||||||
|
}
|
||||||
|
if(delegate_) {
|
||||||
|
delegate_->speaker_did_change_input_clock(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
//
|
||||||
|
// MultiSpeaker.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 18/02/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef MultiSpeaker_hpp
|
||||||
|
#define MultiSpeaker_hpp
|
||||||
|
|
||||||
|
#include "../../../../Machines/DynamicMachine.hpp"
|
||||||
|
#include "../../../../Outputs/Speaker/Speaker.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Dynamic {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Provides a class that multiplexes calls to and from Outputs::Speaker::Speaker in order
|
||||||
|
transparently to connect a single caller to multiple destinations.
|
||||||
|
|
||||||
|
Makes a static internal copy of the list of machines; expects the owner to keep it
|
||||||
|
abreast of the current frontmost machine.
|
||||||
|
*/
|
||||||
|
class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker::Delegate {
|
||||||
|
public:
|
||||||
|
/*!
|
||||||
|
Provides a construction mechanism that may return nullptr, in the case that all included
|
||||||
|
machines return nullptr as their speaker.
|
||||||
|
*/
|
||||||
|
static MultiSpeaker *create(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||||
|
|
||||||
|
/// This class requires the caller to nominate changes in the frontmost machine.
|
||||||
|
void set_new_front_machine(::Machine::DynamicMachine *machine);
|
||||||
|
|
||||||
|
// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation.
|
||||||
|
float get_ideal_clock_rate_in_range(float minimum, float maximum) override;
|
||||||
|
void set_output_rate(float cycles_per_second, int buffer_size) override;
|
||||||
|
void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) override;
|
||||||
|
void speaker_did_change_input_clock(Speaker *speaker) override;
|
||||||
|
MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
|
||||||
|
|
||||||
|
std::vector<Outputs::Speaker::Speaker *> speakers_;
|
||||||
|
Outputs::Speaker::Speaker *front_speaker_ = nullptr;
|
||||||
|
Outputs::Speaker::Speaker::Delegate *delegate_ = nullptr;
|
||||||
|
std::mutex front_speaker_mutex_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* MultiSpeaker_hpp */
|
||||||
113
Analyser/Dynamic/MultiMachine/MultiMachine.cpp
Normal file
113
Analyser/Dynamic/MultiMachine/MultiMachine.cpp
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
//
|
||||||
|
// MultiMachine.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 28/01/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "MultiMachine.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
using namespace Analyser::Dynamic;
|
||||||
|
|
||||||
|
MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines) :
|
||||||
|
machines_(std::move(machines)),
|
||||||
|
configurable_(machines_),
|
||||||
|
configuration_target_(machines_),
|
||||||
|
crt_machine_(machines_, machines_mutex_),
|
||||||
|
joystick_machine_(machines),
|
||||||
|
keyboard_machine_(machines_) {
|
||||||
|
crt_machine_.set_delegate(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Activity::Source *MultiMachine::activity_source() {
|
||||||
|
return nullptr; // TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigurationTarget::Machine *MultiMachine::configuration_target() {
|
||||||
|
if(has_picked_) {
|
||||||
|
return machines_.front()->configuration_target();
|
||||||
|
} else {
|
||||||
|
return &configuration_target_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CRTMachine::Machine *MultiMachine::crt_machine() {
|
||||||
|
if(has_picked_) {
|
||||||
|
return machines_.front()->crt_machine();
|
||||||
|
} else {
|
||||||
|
return &crt_machine_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JoystickMachine::Machine *MultiMachine::joystick_machine() {
|
||||||
|
if(has_picked_) {
|
||||||
|
return machines_.front()->joystick_machine();
|
||||||
|
} else {
|
||||||
|
return &joystick_machine_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyboardMachine::Machine *MultiMachine::keyboard_machine() {
|
||||||
|
if(has_picked_) {
|
||||||
|
return machines_.front()->keyboard_machine();
|
||||||
|
} else {
|
||||||
|
return &keyboard_machine_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Configurable::Device *MultiMachine::configurable_device() {
|
||||||
|
if(has_picked_) {
|
||||||
|
return machines_.front()->configurable_device();
|
||||||
|
} else {
|
||||||
|
return &configurable_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MultiMachine::would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines) {
|
||||||
|
return
|
||||||
|
(machines.front()->crt_machine()->get_confidence() > 0.9f) ||
|
||||||
|
(machines.front()->crt_machine()->get_confidence() >= 2.0f * machines[1]->crt_machine()->get_confidence());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiMachine::multi_crt_did_run_machines() {
|
||||||
|
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
|
||||||
|
#ifdef DEBUG
|
||||||
|
for(const auto &machine: machines_) {
|
||||||
|
CRTMachine::Machine *crt = machine->crt_machine();
|
||||||
|
printf("%0.2f ", crt->get_confidence());
|
||||||
|
crt->print_type();
|
||||||
|
printf("; ");
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
DynamicMachine *front = machines_.front().get();
|
||||||
|
std::stable_sort(machines_.begin(), machines_.end(),
|
||||||
|
[] (const std::unique_ptr<DynamicMachine> &lhs, const std::unique_ptr<DynamicMachine> &rhs){
|
||||||
|
CRTMachine::Machine *lhs_crt = lhs->crt_machine();
|
||||||
|
CRTMachine::Machine *rhs_crt = rhs->crt_machine();
|
||||||
|
return lhs_crt->get_confidence() > rhs_crt->get_confidence();
|
||||||
|
});
|
||||||
|
|
||||||
|
if(machines_.front().get() != front) {
|
||||||
|
crt_machine_.did_change_machine_order();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(would_collapse(machines_)) {
|
||||||
|
pick_first();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiMachine::pick_first() {
|
||||||
|
has_picked_ = true;
|
||||||
|
// machines_.erase(machines_.begin() + 1, machines_.end());
|
||||||
|
// TODO: this isn't quite correct, because it may leak OpenGL/etc resources through failure to
|
||||||
|
// request a close_output while the context is active.
|
||||||
|
}
|
||||||
|
|
||||||
|
void *MultiMachine::raw_pointer() {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
80
Analyser/Dynamic/MultiMachine/MultiMachine.hpp
Normal file
80
Analyser/Dynamic/MultiMachine/MultiMachine.hpp
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
//
|
||||||
|
// MultiMachine.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 28/01/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef MultiMachine_hpp
|
||||||
|
#define MultiMachine_hpp
|
||||||
|
|
||||||
|
#include "../../../Machines/DynamicMachine.hpp"
|
||||||
|
|
||||||
|
#include "Implementation/MultiConfigurable.hpp"
|
||||||
|
#include "Implementation/MultiConfigurationTarget.hpp"
|
||||||
|
#include "Implementation/MultiCRTMachine.hpp"
|
||||||
|
#include "Implementation/MultiJoystickMachine.hpp"
|
||||||
|
#include "Implementation/MultiKeyboardMachine.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Dynamic {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Provides the same interface as to a single machine, while multiplexing all
|
||||||
|
underlying calls to an array of real dynamic machines.
|
||||||
|
|
||||||
|
Calls to crt_machine->get_crt will return that for the frontmost machine;
|
||||||
|
anything installed as the speaker's delegate will similarly receive
|
||||||
|
feedback only from that machine.
|
||||||
|
|
||||||
|
Following each crt_machine->run_for, reorders the supplied machines by
|
||||||
|
confidence.
|
||||||
|
|
||||||
|
If confidence for any machine becomes disproportionately low compared to
|
||||||
|
the others in the set, that machine stops running.
|
||||||
|
*/
|
||||||
|
class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::Delegate {
|
||||||
|
public:
|
||||||
|
/*!
|
||||||
|
Allows a potential MultiMachine creator to enquire as to whether there's any benefit in
|
||||||
|
requesting this class as a proxy.
|
||||||
|
|
||||||
|
@returns @c true if the multimachine would discard all but the first machine in this list;
|
||||||
|
@c false otherwise.
|
||||||
|
*/
|
||||||
|
static bool would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines);
|
||||||
|
MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines);
|
||||||
|
|
||||||
|
Activity::Source *activity_source() override;
|
||||||
|
ConfigurationTarget::Machine *configuration_target() override;
|
||||||
|
CRTMachine::Machine *crt_machine() override;
|
||||||
|
JoystickMachine::Machine *joystick_machine() override;
|
||||||
|
KeyboardMachine::Machine *keyboard_machine() override;
|
||||||
|
Configurable::Device *configurable_device() override;
|
||||||
|
void *raw_pointer() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void multi_crt_did_run_machines() override;
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<DynamicMachine>> machines_;
|
||||||
|
std::mutex machines_mutex_;
|
||||||
|
|
||||||
|
MultiConfigurable configurable_;
|
||||||
|
MultiConfigurationTarget configuration_target_;
|
||||||
|
MultiCRTMachine crt_machine_;
|
||||||
|
MultiJoystickMachine joystick_machine_;
|
||||||
|
MultiKeyboardMachine keyboard_machine_;
|
||||||
|
|
||||||
|
void pick_first();
|
||||||
|
bool has_picked_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* MultiMachine_hpp */
|
||||||
28
Analyser/Machines.hpp
Normal file
28
Analyser/Machines.hpp
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
//
|
||||||
|
// Machines.h
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 24/01/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Machines_h
|
||||||
|
#define Machines_h
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
|
||||||
|
enum class Machine {
|
||||||
|
AmstradCPC,
|
||||||
|
AppleII,
|
||||||
|
Atari2600,
|
||||||
|
ColecoVision,
|
||||||
|
Electron,
|
||||||
|
MSX,
|
||||||
|
Oric,
|
||||||
|
Vic20,
|
||||||
|
ZX8081
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* Machines_h */
|
||||||
@@ -3,18 +3,20 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 18/09/2016.
|
// Created by Thomas Harte on 18/09/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "Disk.hpp"
|
#include "Disk.hpp"
|
||||||
#include "../../Storage/Disk/Controller/DiskController.hpp"
|
|
||||||
#include "../../Storage/Disk/Encodings/MFM/Parser.hpp"
|
#include "../../../Storage/Disk/Controller/DiskController.hpp"
|
||||||
#include "../../NumberTheory/CRC.hpp"
|
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
|
||||||
|
#include "../../../NumberTheory/CRC.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
using namespace StaticAnalyser::Acorn;
|
using namespace Analyser::Static::Acorn;
|
||||||
|
|
||||||
std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||||
// c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format
|
// c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format
|
||||||
std::unique_ptr<Catalogue> catalogue(new Catalogue);
|
std::unique_ptr<Catalogue> catalogue(new Catalogue);
|
||||||
Storage::Encodings::MFM::Parser parser(false, disk);
|
Storage::Encodings::MFM::Parser parser(false, disk);
|
||||||
@@ -41,9 +43,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
|
|||||||
case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break;
|
case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// DFS files are stored contiguously, and listed in descending order of distance from track 0.
|
for(std::size_t file_offset = 8; file_offset < final_file_offset; file_offset += 8) {
|
||||||
// So iterating backwards implies the least amount of seeking.
|
|
||||||
for(std::size_t file_offset = final_file_offset - 8; file_offset > 0; file_offset -= 8) {
|
|
||||||
File new_file;
|
File new_file;
|
||||||
char name[10];
|
char name[10];
|
||||||
snprintf(name, 10, "%c.%.7s", names->samples[0][file_offset + 7] & 0x7f, &names->samples[0][file_offset]);
|
snprintf(name, 10, "%c.%.7s", names->samples[0][file_offset + 7] & 0x7f, &names->samples[0][file_offset]);
|
||||||
@@ -69,12 +69,12 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
|
|||||||
new_file.data.insert(new_file.data.end(), next_sector->samples[0].begin(), next_sector->samples[0].begin() + length_from_sector);
|
new_file.data.insert(new_file.data.end(), next_sector->samples[0].begin(), next_sector->samples[0].begin() + length_from_sector);
|
||||||
data_length -= length_from_sector;
|
data_length -= length_from_sector;
|
||||||
}
|
}
|
||||||
if(!data_length) catalogue->files.push_front(new_file);
|
if(!data_length) catalogue->files.push_back(new_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
return catalogue;
|
return catalogue;
|
||||||
}
|
}
|
||||||
std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||||
std::unique_ptr<Catalogue> catalogue(new Catalogue);
|
std::unique_ptr<Catalogue> catalogue(new Catalogue);
|
||||||
Storage::Encodings::MFM::Parser parser(true, disk);
|
Storage::Encodings::MFM::Parser parser(true, disk);
|
||||||
|
|
||||||
@@ -3,22 +3,23 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 18/09/2016.
|
// Created by Thomas Harte on 18/09/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef StaticAnalyser_Acorn_Disk_hpp
|
#ifndef StaticAnalyser_Acorn_Disk_hpp
|
||||||
#define StaticAnalyser_Acorn_Disk_hpp
|
#define StaticAnalyser_Acorn_Disk_hpp
|
||||||
|
|
||||||
#include "File.hpp"
|
#include "File.hpp"
|
||||||
#include "../../Storage/Disk/Disk.hpp"
|
#include "../../../Storage/Disk/Disk.hpp"
|
||||||
|
|
||||||
namespace StaticAnalyser {
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
namespace Acorn {
|
namespace Acorn {
|
||||||
|
|
||||||
/// Describes a DFS- or ADFS-format catalogue(/directory) — the list of files available and the catalogue's boot option.
|
/// Describes a DFS- or ADFS-format catalogue(/directory): the list of files available and the catalogue's boot option.
|
||||||
struct Catalogue {
|
struct Catalogue {
|
||||||
std::string name;
|
std::string name;
|
||||||
std::list<File> files;
|
std::vector<File> files;
|
||||||
enum class BootOption {
|
enum class BootOption {
|
||||||
None,
|
None,
|
||||||
LoadBOOT,
|
LoadBOOT,
|
||||||
@@ -30,6 +31,7 @@ struct Catalogue {
|
|||||||
std::unique_ptr<Catalogue> GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
|
std::unique_ptr<Catalogue> GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
|
||||||
std::unique_ptr<Catalogue> GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
|
std::unique_ptr<Catalogue> GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3,18 +3,18 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 18/09/2016.
|
// Created by Thomas Harte on 18/09/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef StaticAnalyser_Acorn_File_hpp
|
#ifndef StaticAnalyser_Acorn_File_hpp
|
||||||
#define StaticAnalyser_Acorn_File_hpp
|
#define StaticAnalyser_Acorn_File_hpp
|
||||||
|
|
||||||
#include <list>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace StaticAnalyser {
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
namespace Acorn {
|
namespace Acorn {
|
||||||
|
|
||||||
struct File {
|
struct File {
|
||||||
@@ -38,9 +38,10 @@ struct File {
|
|||||||
std::vector<uint8_t> data;
|
std::vector<uint8_t> data;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::list<Chunk> chunks;
|
std::vector<Chunk> chunks;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3,19 +3,20 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 29/08/2016.
|
// Created by Thomas Harte on 29/08/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "StaticAnalyser.hpp"
|
#include "StaticAnalyser.hpp"
|
||||||
|
|
||||||
#include "Disk.hpp"
|
#include "Disk.hpp"
|
||||||
#include "Tape.hpp"
|
#include "Tape.hpp"
|
||||||
|
#include "Target.hpp"
|
||||||
|
|
||||||
using namespace StaticAnalyser::Acorn;
|
using namespace Analyser::Static::Acorn;
|
||||||
|
|
||||||
static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||||
AcornCartridgesFrom(const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
AcornCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||||
std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> acorn_cartridges;
|
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> acorn_cartridges;
|
||||||
|
|
||||||
for(const auto &cartridge : cartridges) {
|
for(const auto &cartridge : cartridges) {
|
||||||
const auto &segments = cartridge->get_segments();
|
const auto &segments = cartridge->get_segments();
|
||||||
@@ -49,28 +50,28 @@ static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
|||||||
// 1/(2^32) *
|
// 1/(2^32) *
|
||||||
// ( ((2^24)-1)/(2^24)*(1/4) + 1/(2^24) ) *
|
// ( ((2^24)-1)/(2^24)*(1/4) + 1/(2^24) ) *
|
||||||
// 1/4
|
// 1/4
|
||||||
// = something very improbable — around 1/16th of 1 in 2^32, but not exactly.
|
// = something very improbable, around 1/16th of 1 in 2^32, but not exactly.
|
||||||
acorn_cartridges.push_back(cartridge);
|
acorn_cartridges.push_back(cartridge);
|
||||||
}
|
}
|
||||||
|
|
||||||
return acorn_cartridges;
|
return acorn_cartridges;
|
||||||
}
|
}
|
||||||
|
|
||||||
void StaticAnalyser::Acorn::AddTargets(const Media &media, std::list<Target> &destination) {
|
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||||
Target target;
|
std::unique_ptr<Target> target(new Target);
|
||||||
target.machine = Target::Electron;
|
target->machine = Machine::Electron;
|
||||||
target.probability = 1.0; // TODO: a proper estimation
|
target->confidence = 0.5; // TODO: a proper estimation
|
||||||
target.acorn.has_dfs = false;
|
target->has_dfs = false;
|
||||||
target.acorn.has_adfs = false;
|
target->has_adfs = false;
|
||||||
target.acorn.should_shift_restart = false;
|
target->should_shift_restart = false;
|
||||||
|
|
||||||
// strip out inappropriate cartridges
|
// strip out inappropriate cartridges
|
||||||
target.media.cartridges = AcornCartridgesFrom(media.cartridges);
|
target->media.cartridges = AcornCartridgesFrom(media.cartridges);
|
||||||
|
|
||||||
// if there are any tapes, attempt to get data from the first
|
// if there are any tapes, attempt to get data from the first
|
||||||
if(media.tapes.size() > 0) {
|
if(media.tapes.size() > 0) {
|
||||||
std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front();
|
std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front();
|
||||||
std::list<File> files = GetFiles(tape);
|
std::vector<File> files = GetFiles(tape);
|
||||||
tape->reset();
|
tape->reset();
|
||||||
|
|
||||||
// continue if there are any files
|
// continue if there are any files
|
||||||
@@ -96,9 +97,9 @@ void StaticAnalyser::Acorn::AddTargets(const Media &media, std::list<Target> &de
|
|||||||
|
|
||||||
// Inspect first file. If it's protected or doesn't look like BASIC
|
// Inspect first file. If it's protected or doesn't look like BASIC
|
||||||
// then the loading command is *RUN. Otherwise it's CHAIN"".
|
// then the loading command is *RUN. Otherwise it's CHAIN"".
|
||||||
target.loading_command = is_basic ? "CHAIN\"\"\n" : "*RUN\n";
|
target->loading_command = is_basic ? "CHAIN\"\"\n" : "*RUN\n";
|
||||||
|
|
||||||
target.media.tapes = media.tapes;
|
target->media.tapes = media.tapes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,18 +109,21 @@ void StaticAnalyser::Acorn::AddTargets(const Media &media, std::list<Target> &de
|
|||||||
dfs_catalogue = GetDFSCatalogue(disk);
|
dfs_catalogue = GetDFSCatalogue(disk);
|
||||||
if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
|
if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
|
||||||
if(dfs_catalogue || adfs_catalogue) {
|
if(dfs_catalogue || adfs_catalogue) {
|
||||||
target.media.disks = media.disks;
|
target->media.disks = media.disks;
|
||||||
target.acorn.has_dfs = !!dfs_catalogue;
|
target->has_dfs = !!dfs_catalogue;
|
||||||
target.acorn.has_adfs = !!adfs_catalogue;
|
target->has_adfs = !!adfs_catalogue;
|
||||||
|
|
||||||
Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
|
Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
|
||||||
if(bootOption != Catalogue::BootOption::None)
|
if(bootOption != Catalogue::BootOption::None)
|
||||||
target.acorn.should_shift_restart = true;
|
target->should_shift_restart = true;
|
||||||
else
|
else
|
||||||
target.loading_command = "*CAT\n";
|
target->loading_command = "*CAT\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(target.media.tapes.size() || target.media.disks.size() || target.media.cartridges.size())
|
TargetList targets;
|
||||||
destination.push_back(target);
|
if(!target->media.empty()) {
|
||||||
|
targets.push_back(std::move(target));
|
||||||
|
}
|
||||||
|
return targets;
|
||||||
}
|
}
|
||||||
26
Analyser/Static/Acorn/StaticAnalyser.hpp
Normal file
26
Analyser/Static/Acorn/StaticAnalyser.hpp
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
//
|
||||||
|
// AcornAnalyser.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 29/08/2016.
|
||||||
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef StaticAnalyser_Acorn_StaticAnalyser_hpp
|
||||||
|
#define StaticAnalyser_Acorn_StaticAnalyser_hpp
|
||||||
|
|
||||||
|
#include "../StaticAnalyser.hpp"
|
||||||
|
#include "../../../Storage/TargetPlatforms.hpp"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
|
namespace Acorn {
|
||||||
|
|
||||||
|
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* AcornAnalyser_hpp */
|
||||||
@@ -3,16 +3,17 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 29/08/2016.
|
// Created by Thomas Harte on 29/08/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "Tape.hpp"
|
#include "Tape.hpp"
|
||||||
|
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include "../../NumberTheory/CRC.hpp"
|
|
||||||
#include "../../Storage/Tape/Parsers/Acorn.hpp"
|
|
||||||
|
|
||||||
using namespace StaticAnalyser::Acorn;
|
#include "../../../NumberTheory/CRC.hpp"
|
||||||
|
#include "../../../Storage/Tape/Parsers/Acorn.hpp"
|
||||||
|
|
||||||
|
using namespace Analyser::Static::Acorn;
|
||||||
|
|
||||||
static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::Tape::Tape> &tape, Storage::Tape::Acorn::Parser &parser) {
|
static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::Tape::Tape> &tape, Storage::Tape::Acorn::Parser &parser) {
|
||||||
std::unique_ptr<File::Chunk> new_chunk(new File::Chunk);
|
std::unique_ptr<File::Chunk> new_chunk(new File::Chunk);
|
||||||
@@ -118,7 +119,7 @@ static std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks) {
|
|||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::list<File> StaticAnalyser::Acorn::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
std::vector<File> Analyser::Static::Acorn::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||||
Storage::Tape::Acorn::Parser parser;
|
Storage::Tape::Acorn::Parser parser;
|
||||||
|
|
||||||
// populate chunk list
|
// populate chunk list
|
||||||
@@ -131,7 +132,7 @@ std::list<File> StaticAnalyser::Acorn::GetFiles(const std::shared_ptr<Storage::T
|
|||||||
}
|
}
|
||||||
|
|
||||||
// decompose into file list
|
// decompose into file list
|
||||||
std::list<File> file_list;
|
std::vector<File> file_list;
|
||||||
|
|
||||||
while(chunk_list.size()) {
|
while(chunk_list.size()) {
|
||||||
std::unique_ptr<File> next_file = GetNextFile(chunk_list);
|
std::unique_ptr<File> next_file = GetNextFile(chunk_list);
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 29/08/2016.
|
// Created by Thomas Harte on 29/08/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef StaticAnalyser_Acorn_Tape_hpp
|
#ifndef StaticAnalyser_Acorn_Tape_hpp
|
||||||
@@ -12,13 +12,15 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "File.hpp"
|
#include "File.hpp"
|
||||||
#include "../../Storage/Tape/Tape.hpp"
|
#include "../../../Storage/Tape/Tape.hpp"
|
||||||
|
|
||||||
namespace StaticAnalyser {
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
namespace Acorn {
|
namespace Acorn {
|
||||||
|
|
||||||
std::list<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
30
Analyser/Static/Acorn/Target.hpp
Normal file
30
Analyser/Static/Acorn/Target.hpp
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
//
|
||||||
|
// Target.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 09/03/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Analyser_Static_Acorn_Target_h
|
||||||
|
#define Analyser_Static_Acorn_Target_h
|
||||||
|
|
||||||
|
#include "../StaticAnalyser.hpp"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
|
namespace Acorn {
|
||||||
|
|
||||||
|
struct Target: public ::Analyser::Static::Target {
|
||||||
|
bool has_adfs = false;
|
||||||
|
bool has_dfs = false;
|
||||||
|
bool should_shift_restart = false;
|
||||||
|
std::string loading_command;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* Analyser_Static_Acorn_Target_h */
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 30/07/2017.
|
// Created by Thomas Harte on 30/07/2017.
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "StaticAnalyser.hpp"
|
#include "StaticAnalyser.hpp"
|
||||||
@@ -11,8 +11,10 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#include "../../Storage/Disk/Parsers/CPM.hpp"
|
#include "Target.hpp"
|
||||||
#include "../../Storage/Disk/Encodings/MFM/Parser.hpp"
|
|
||||||
|
#include "../../../Storage/Disk/Parsers/CPM.hpp"
|
||||||
|
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
|
||||||
|
|
||||||
static bool strcmp_insensitive(const char *a, const char *b) {
|
static bool strcmp_insensitive(const char *a, const char *b) {
|
||||||
if(std::strlen(a) != std::strlen(b)) return false;
|
if(std::strlen(a) != std::strlen(b)) return false;
|
||||||
@@ -33,8 +35,8 @@ static bool is_implied_extension(const std::string &extension) {
|
|||||||
|
|
||||||
static void right_trim(std::string &string) {
|
static void right_trim(std::string &string) {
|
||||||
string.erase(std::find_if(string.rbegin(), string.rend(), [](int ch) {
|
string.erase(std::find_if(string.rbegin(), string.rend(), [](int ch) {
|
||||||
return !std::isspace(ch);
|
return !std::isspace(ch);
|
||||||
}).base(), string.end());
|
}).base(), string.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
|
static std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
|
||||||
@@ -58,18 +60,18 @@ static std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
|
|||||||
|
|
||||||
static void InspectCatalogue(
|
static void InspectCatalogue(
|
||||||
const Storage::Disk::CPM::Catalogue &catalogue,
|
const Storage::Disk::CPM::Catalogue &catalogue,
|
||||||
StaticAnalyser::Target &target) {
|
const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
|
||||||
|
|
||||||
std::vector<const Storage::Disk::CPM::File *> candidate_files;
|
std::vector<const Storage::Disk::CPM::File *> candidate_files;
|
||||||
candidate_files.reserve(catalogue.files.size());
|
candidate_files.reserve(catalogue.files.size());
|
||||||
for(auto &file : catalogue.files) {
|
for(const auto &file : catalogue.files) {
|
||||||
candidate_files.push_back(&file);
|
candidate_files.push_back(&file);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove all files with untypable characters.
|
// Remove all files with untypable characters.
|
||||||
candidate_files.erase(
|
candidate_files.erase(
|
||||||
std::remove_if(candidate_files.begin(), candidate_files.end(), [](const Storage::Disk::CPM::File *file) {
|
std::remove_if(candidate_files.begin(), candidate_files.end(), [](const Storage::Disk::CPM::File *file) {
|
||||||
for(auto c : file->name + file->type) {
|
for(const auto c : file->name + file->type) {
|
||||||
if(c < 32) return true;
|
if(c < 32) return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -78,7 +80,7 @@ static void InspectCatalogue(
|
|||||||
|
|
||||||
// If that leaves a mix of 'system' (i.e. hidden) and non-system files, remove the system files.
|
// If that leaves a mix of 'system' (i.e. hidden) and non-system files, remove the system files.
|
||||||
bool are_all_system = true;
|
bool are_all_system = true;
|
||||||
for(auto file : candidate_files) {
|
for(const auto &file : candidate_files) {
|
||||||
if(!file->system) {
|
if(!file->system) {
|
||||||
are_all_system = false;
|
are_all_system = false;
|
||||||
break;
|
break;
|
||||||
@@ -95,7 +97,7 @@ static void InspectCatalogue(
|
|||||||
|
|
||||||
// If there's just one file, run that.
|
// If there's just one file, run that.
|
||||||
if(candidate_files.size() == 1) {
|
if(candidate_files.size() == 1) {
|
||||||
target.loading_command = RunCommandFor(*candidate_files[0]);
|
target->loading_command = RunCommandFor(*candidate_files[0]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +128,7 @@ static void InspectCatalogue(
|
|||||||
}
|
}
|
||||||
if(basic_files == 1 || implicit_suffixed_files == 1) {
|
if(basic_files == 1 || implicit_suffixed_files == 1) {
|
||||||
std::size_t selected_file = (basic_files == 1) ? last_basic_file : last_implicit_suffixed_file;
|
std::size_t selected_file = (basic_files == 1) ? last_basic_file : last_implicit_suffixed_file;
|
||||||
target.loading_command = RunCommandFor(*candidate_files[selected_file]);
|
target->loading_command = RunCommandFor(*candidate_files[selected_file]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,28 +137,28 @@ static void InspectCatalogue(
|
|||||||
std::map<std::string, int> name_counts;
|
std::map<std::string, int> name_counts;
|
||||||
std::map<std::string, std::size_t> indices_by_name;
|
std::map<std::string, std::size_t> indices_by_name;
|
||||||
std::size_t index = 0;
|
std::size_t index = 0;
|
||||||
for(auto file : candidate_files) {
|
for(const auto &file : candidate_files) {
|
||||||
name_counts[file->name]++;
|
name_counts[file->name]++;
|
||||||
indices_by_name[file->name] = index;
|
indices_by_name[file->name] = index;
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
if(name_counts.size() == 2) {
|
if(name_counts.size() == 2) {
|
||||||
for(auto &pair : name_counts) {
|
for(const auto &pair : name_counts) {
|
||||||
if(pair.second == 1) {
|
if(pair.second == 1) {
|
||||||
target.loading_command = RunCommandFor(*candidate_files[indices_by_name[pair.first]]);
|
target->loading_command = RunCommandFor(*candidate_files[indices_by_name[pair.first]]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Desperation.
|
// Desperation.
|
||||||
target.loading_command = "cat\n";
|
target->loading_command = "cat\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, StaticAnalyser::Target &target) {
|
static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
|
||||||
Storage::Encodings::MFM::Parser parser(true, disk);
|
Storage::Encodings::MFM::Parser parser(true, disk);
|
||||||
Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41);
|
Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41);
|
||||||
if(boot_sector != nullptr && !boot_sector->samples.empty()) {
|
if(boot_sector != nullptr && !boot_sector->samples.empty() && boot_sector->samples[0].size() == 512) {
|
||||||
// Check that the first 64 bytes of the sector aren't identical; if they are then probably
|
// Check that the first 64 bytes of the sector aren't identical; if they are then probably
|
||||||
// this disk was formatted and the filler byte never replaced.
|
// this disk was formatted and the filler byte never replaced.
|
||||||
bool matched = true;
|
bool matched = true;
|
||||||
@@ -169,7 +171,7 @@ static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, St
|
|||||||
|
|
||||||
// This is a system disk, then launch it as though it were CP/M.
|
// This is a system disk, then launch it as though it were CP/M.
|
||||||
if(!matched) {
|
if(!matched) {
|
||||||
target.loading_command = "|cpm\n";
|
target->loading_command = "|cpm\n";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,24 +179,25 @@ static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, St
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void StaticAnalyser::AmstradCPC::AddTargets(const Media &media, std::list<Target> &destination) {
|
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||||
Target target;
|
TargetList destination;
|
||||||
target.machine = Target::AmstradCPC;
|
std::unique_ptr<Target> target(new Target);
|
||||||
target.probability = 1.0;
|
target->machine = Machine::AmstradCPC;
|
||||||
target.media.disks = media.disks;
|
target->confidence = 0.5;
|
||||||
target.media.tapes = media.tapes;
|
|
||||||
target.media.cartridges = media.cartridges;
|
|
||||||
|
|
||||||
target.amstradcpc.model = AmstradCPCModel::CPC6128;
|
target->model = Target::Model::CPC6128;
|
||||||
|
|
||||||
|
if(!media.tapes.empty()) {
|
||||||
|
// TODO: which of these are actually potentially CPC tapes?
|
||||||
|
target->media.tapes = media.tapes;
|
||||||
|
|
||||||
if(!target.media.tapes.empty()) {
|
|
||||||
// Ugliness flows here: assume the CPC isn't smart enough to pause between pressing
|
// Ugliness flows here: assume the CPC isn't smart enough to pause between pressing
|
||||||
// enter and responding to the follow-on prompt to press a key, so just type for
|
// enter and responding to the follow-on prompt to press a key, so just type for
|
||||||
// a while. Yuck!
|
// a while. Yuck!
|
||||||
target.loading_command = "|tape\nrun\"\n1234567890";
|
target->loading_command = "|tape\nrun\"\n1234567890";
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!target.media.disks.empty()) {
|
if(!media.disks.empty()) {
|
||||||
Storage::Disk::CPM::ParameterBlock data_format;
|
Storage::Disk::CPM::ParameterBlock data_format;
|
||||||
data_format.sectors_per_track = 9;
|
data_format.sectors_per_track = 9;
|
||||||
data_format.tracks = 40;
|
data_format.tracks = 40;
|
||||||
@@ -203,26 +206,42 @@ void StaticAnalyser::AmstradCPC::AddTargets(const Media &media, std::list<Target
|
|||||||
data_format.catalogue_allocation_bitmap = 0xc000;
|
data_format.catalogue_allocation_bitmap = 0xc000;
|
||||||
data_format.reserved_tracks = 0;
|
data_format.reserved_tracks = 0;
|
||||||
|
|
||||||
std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(target.media.disks.front(), data_format);
|
Storage::Disk::CPM::ParameterBlock system_format;
|
||||||
if(data_catalogue) {
|
system_format.sectors_per_track = 9;
|
||||||
InspectCatalogue(*data_catalogue, target);
|
system_format.tracks = 40;
|
||||||
} else {
|
system_format.block_size = 1024;
|
||||||
if(!CheckBootSector(target.media.disks.front(), target)) {
|
system_format.first_sector = 0x41;
|
||||||
Storage::Disk::CPM::ParameterBlock system_format;
|
system_format.catalogue_allocation_bitmap = 0xc000;
|
||||||
system_format.sectors_per_track = 9;
|
system_format.reserved_tracks = 2;
|
||||||
system_format.tracks = 40;
|
|
||||||
system_format.block_size = 1024;
|
|
||||||
system_format.first_sector = 0x41;
|
|
||||||
system_format.catalogue_allocation_bitmap = 0xc000;
|
|
||||||
system_format.reserved_tracks = 2;
|
|
||||||
|
|
||||||
std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(target.media.disks.front(), system_format);
|
for(auto &disk: media.disks) {
|
||||||
if(system_catalogue) {
|
// Check for an ordinary catalogue.
|
||||||
InspectCatalogue(*system_catalogue, target);
|
std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(disk, data_format);
|
||||||
}
|
if(data_catalogue) {
|
||||||
|
InspectCatalogue(*data_catalogue, target);
|
||||||
|
target->media.disks.push_back(disk);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Failing that check for a boot sector.
|
||||||
|
if(CheckBootSector(disk, target)) {
|
||||||
|
target->media.disks.push_back(disk);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Failing that check for a system catalogue.
|
||||||
|
std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(disk, system_format);
|
||||||
|
if(system_catalogue) {
|
||||||
|
InspectCatalogue(*system_catalogue, target);
|
||||||
|
target->media.disks.push_back(disk);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
destination.push_back(target);
|
// If any media survived, add the target.
|
||||||
|
if(!target->media.empty())
|
||||||
|
destination.push_back(std::move(target));
|
||||||
|
|
||||||
|
return destination;
|
||||||
}
|
}
|
||||||
26
Analyser/Static/AmstradCPC/StaticAnalyser.hpp
Normal file
26
Analyser/Static/AmstradCPC/StaticAnalyser.hpp
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
//
|
||||||
|
// StaticAnalyser.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 30/07/2017.
|
||||||
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Analyser_Static_AmstradCPC_StaticAnalyser_hpp
|
||||||
|
#define Analyser_Static_AmstradCPC_StaticAnalyser_hpp
|
||||||
|
|
||||||
|
#include "../StaticAnalyser.hpp"
|
||||||
|
#include "../../../Storage/TargetPlatforms.hpp"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
|
namespace AmstradCPC {
|
||||||
|
|
||||||
|
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* Analyser_Static_AmstradCPC_StaticAnalyser_hpp */
|
||||||
35
Analyser/Static/AmstradCPC/Target.hpp
Normal file
35
Analyser/Static/AmstradCPC/Target.hpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
//
|
||||||
|
// Target.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 09/03/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Analyser_Static_AmstradCPC_Target_h
|
||||||
|
#define Analyser_Static_AmstradCPC_Target_h
|
||||||
|
|
||||||
|
#include "../StaticAnalyser.hpp"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
|
namespace AmstradCPC {
|
||||||
|
|
||||||
|
struct Target: public ::Analyser::Static::Target {
|
||||||
|
enum class Model {
|
||||||
|
CPC464,
|
||||||
|
CPC664,
|
||||||
|
CPC6128
|
||||||
|
};
|
||||||
|
|
||||||
|
Model model = Model::CPC464;
|
||||||
|
std::string loading_command;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* Analyser_Static_AmstradCPC_Target_h */
|
||||||
23
Analyser/Static/AppleII/StaticAnalyser.cpp
Normal file
23
Analyser/Static/AppleII/StaticAnalyser.cpp
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
//
|
||||||
|
// StaticAnalyser.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 14/04/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "StaticAnalyser.hpp"
|
||||||
|
#include "Target.hpp"
|
||||||
|
|
||||||
|
Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||||
|
auto target = std::unique_ptr<Target>(new Target);
|
||||||
|
target->machine = Machine::AppleII;
|
||||||
|
target->media = media;
|
||||||
|
|
||||||
|
if(!target->media.disks.empty())
|
||||||
|
target->disk_controller = Target::DiskController::SixteenSector;
|
||||||
|
|
||||||
|
TargetList targets;
|
||||||
|
targets.push_back(std::move(target));
|
||||||
|
return targets;
|
||||||
|
}
|
||||||
26
Analyser/Static/AppleII/StaticAnalyser.hpp
Normal file
26
Analyser/Static/AppleII/StaticAnalyser.hpp
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
//
|
||||||
|
// StaticAnalyser.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 14/04/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Analyser_Static_AppleII_StaticAnalyser_hpp
|
||||||
|
#define Analyser_Static_AppleII_StaticAnalyser_hpp
|
||||||
|
|
||||||
|
#include "../StaticAnalyser.hpp"
|
||||||
|
#include "../../../Storage/TargetPlatforms.hpp"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
|
namespace AppleII {
|
||||||
|
|
||||||
|
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* Analyser_Static_AppleII_StaticAnalyser_hpp */
|
||||||
37
Analyser/Static/AppleII/Target.hpp
Normal file
37
Analyser/Static/AppleII/Target.hpp
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
//
|
||||||
|
// Target.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 21/04/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Target_h
|
||||||
|
#define Target_h
|
||||||
|
|
||||||
|
#include "../StaticAnalyser.hpp"
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
|
namespace AppleII {
|
||||||
|
|
||||||
|
struct Target: public ::Analyser::Static::Target {
|
||||||
|
enum class Model {
|
||||||
|
II,
|
||||||
|
IIplus
|
||||||
|
};
|
||||||
|
enum class DiskController {
|
||||||
|
None,
|
||||||
|
SixteenSector,
|
||||||
|
ThirteenSector
|
||||||
|
};
|
||||||
|
|
||||||
|
Model model = Model::IIplus;
|
||||||
|
DiskController disk_controller = DiskController::None;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* Target_h */
|
||||||
@@ -3,16 +3,18 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 15/09/2016.
|
// Created by Thomas Harte on 15/09/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "StaticAnalyser.hpp"
|
#include "StaticAnalyser.hpp"
|
||||||
|
|
||||||
|
#include "Target.hpp"
|
||||||
|
|
||||||
#include "../Disassembler/6502.hpp"
|
#include "../Disassembler/6502.hpp"
|
||||||
|
|
||||||
using namespace StaticAnalyser::Atari;
|
using namespace Analyser::Static::Atari;
|
||||||
|
|
||||||
static void DeterminePagingFor2kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
|
static void DeterminePagingFor2kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
|
||||||
// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid
|
// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid
|
||||||
uint16_t entry_address, break_address;
|
uint16_t entry_address, break_address;
|
||||||
|
|
||||||
@@ -26,17 +28,17 @@ static void DeterminePagingFor2kCartridge(StaticAnalyser::Target &target, const
|
|||||||
address &= 0x1fff;
|
address &= 0x1fff;
|
||||||
return static_cast<std::size_t>(address - 0x1800);
|
return static_cast<std::size_t>(address - 0x1800);
|
||||||
};
|
};
|
||||||
StaticAnalyser::MOS6502::Disassembly high_location_disassembly =
|
Analyser::Static::MOS6502::Disassembly high_location_disassembly =
|
||||||
StaticAnalyser::MOS6502::Disassemble(segment.data, high_location_mapper, {entry_address, break_address});
|
Analyser::Static::MOS6502::Disassemble(segment.data, high_location_mapper, {entry_address, break_address});
|
||||||
|
|
||||||
// assume that any kind of store that looks likely to be intended for large amounts of memory implies
|
// assume that any kind of store that looks likely to be intended for large amounts of memory implies
|
||||||
// large amounts of memory
|
// large amounts of memory
|
||||||
bool has_wide_area_store = false;
|
bool has_wide_area_store = false;
|
||||||
for(std::map<uint16_t, StaticAnalyser::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address) {
|
for(std::map<uint16_t, Analyser::Static::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address) {
|
||||||
if(entry.second.operation == StaticAnalyser::MOS6502::Instruction::STA) {
|
if(entry.second.operation == Analyser::Static::MOS6502::Instruction::STA) {
|
||||||
has_wide_area_store |= entry.second.addressing_mode == StaticAnalyser::MOS6502::Instruction::Indirect;
|
has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::Indirect;
|
||||||
has_wide_area_store |= entry.second.addressing_mode == StaticAnalyser::MOS6502::Instruction::IndexedIndirectX;
|
has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::IndexedIndirectX;
|
||||||
has_wide_area_store |= entry.second.addressing_mode == StaticAnalyser::MOS6502::Instruction::IndirectIndexedY;
|
has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::IndirectIndexedY;
|
||||||
|
|
||||||
if(has_wide_area_store) break;
|
if(has_wide_area_store) break;
|
||||||
}
|
}
|
||||||
@@ -46,10 +48,10 @@ static void DeterminePagingFor2kCartridge(StaticAnalyser::Target &target, const
|
|||||||
// caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses
|
// caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses
|
||||||
// itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it
|
// itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it
|
||||||
// attempts to modify itself but it probably doesn't
|
// attempts to modify itself but it probably doesn't
|
||||||
if(has_wide_area_store) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::CommaVid;
|
if(has_wide_area_store) target.paging_model = Analyser::Static::Atari::Target::PagingModel::CommaVid;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void DeterminePagingFor8kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const StaticAnalyser::MOS6502::Disassembly &disassembly) {
|
static void DeterminePagingFor8kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||||
// Activision stack titles have their vectors at the top of the low 4k, not the top, and
|
// Activision stack titles have their vectors at the top of the low 4k, not the top, and
|
||||||
// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all
|
// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all
|
||||||
// issue an SEI as their first instruction (maybe some sort of relic of the development environment?)
|
// issue an SEI as their first instruction (maybe some sort of relic of the development environment?)
|
||||||
@@ -58,12 +60,12 @@ static void DeterminePagingFor8kCartridge(StaticAnalyser::Target &target, const
|
|||||||
(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) &&
|
(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) &&
|
||||||
segment.data[0] == 0x78
|
segment.data[0] == 0x78
|
||||||
) {
|
) {
|
||||||
target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::ActivisionStack;
|
target.paging_model = Analyser::Static::Atari::Target::PagingModel::ActivisionStack;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// make an assumption that this is the Atari paging model
|
// make an assumption that this is the Atari paging model
|
||||||
target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Atari8k;
|
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari8k;
|
||||||
|
|
||||||
std::set<uint16_t> internal_accesses;
|
std::set<uint16_t> internal_accesses;
|
||||||
internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
|
internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
|
||||||
@@ -83,13 +85,13 @@ static void DeterminePagingFor8kCartridge(StaticAnalyser::Target &target, const
|
|||||||
tigervision_access_count += masked_address == 0x3f;
|
tigervision_access_count += masked_address == 0x3f;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(parker_access_count > atari_access_count) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::ParkerBros;
|
if(parker_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::ParkerBros;
|
||||||
else if(tigervision_access_count > atari_access_count) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Tigervision;
|
else if(tigervision_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void DeterminePagingFor16kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const StaticAnalyser::MOS6502::Disassembly &disassembly) {
|
static void DeterminePagingFor16kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||||
// make an assumption that this is the Atari paging model
|
// make an assumption that this is the Atari paging model
|
||||||
target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Atari16k;
|
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari16k;
|
||||||
|
|
||||||
std::set<uint16_t> internal_accesses;
|
std::set<uint16_t> internal_accesses;
|
||||||
internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
|
internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
|
||||||
@@ -104,17 +106,17 @@ static void DeterminePagingFor16kCartridge(StaticAnalyser::Target &target, const
|
|||||||
mnetwork_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ffb;
|
mnetwork_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ffb;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mnetwork_access_count > atari_access_count) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::MNetwork;
|
if(mnetwork_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::MNetwork;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void DeterminePagingFor64kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const StaticAnalyser::MOS6502::Disassembly &disassembly) {
|
static void DeterminePagingFor64kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||||
// make an assumption that this is a Tigervision if there is a write to 3F
|
// make an assumption that this is a Tigervision if there is a write to 3F
|
||||||
target.atari.paging_model =
|
target.paging_model =
|
||||||
(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ?
|
(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ?
|
||||||
StaticAnalyser::Atari2600PagingModel::Tigervision : StaticAnalyser::Atari2600PagingModel::MegaBoy;
|
Analyser::Static::Atari::Target::PagingModel::Tigervision : Analyser::Static::Atari::Target::PagingModel::MegaBoy;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void DeterminePagingForCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
|
static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
|
||||||
if(segment.data.size() == 2048) {
|
if(segment.data.size() == 2048) {
|
||||||
DeterminePagingFor2kCartridge(target, segment);
|
DeterminePagingFor2kCartridge(target, segment);
|
||||||
return;
|
return;
|
||||||
@@ -131,23 +133,23 @@ static void DeterminePagingForCartridge(StaticAnalyser::Target &target, const St
|
|||||||
};
|
};
|
||||||
|
|
||||||
std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end());
|
std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end());
|
||||||
StaticAnalyser::MOS6502::Disassembly disassembly = StaticAnalyser::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address});
|
Analyser::Static::MOS6502::Disassembly disassembly = Analyser::Static::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address});
|
||||||
|
|
||||||
switch(segment.data.size()) {
|
switch(segment.data.size()) {
|
||||||
case 8192:
|
case 8192:
|
||||||
DeterminePagingFor8kCartridge(target, segment, disassembly);
|
DeterminePagingFor8kCartridge(target, segment, disassembly);
|
||||||
break;
|
break;
|
||||||
case 10495:
|
case 10495:
|
||||||
target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Pitfall2;
|
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Pitfall2;
|
||||||
break;
|
break;
|
||||||
case 12288:
|
case 12288:
|
||||||
target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::CBSRamPlus;
|
target.paging_model = Analyser::Static::Atari::Target::PagingModel::CBSRamPlus;
|
||||||
break;
|
break;
|
||||||
case 16384:
|
case 16384:
|
||||||
DeterminePagingFor16kCartridge(target, segment, disassembly);
|
DeterminePagingFor16kCartridge(target, segment, disassembly);
|
||||||
break;
|
break;
|
||||||
case 32768:
|
case 32768:
|
||||||
target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Atari32k;
|
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari32k;
|
||||||
break;
|
break;
|
||||||
case 65536:
|
case 65536:
|
||||||
DeterminePagingFor64kCartridge(target, segment, disassembly);
|
DeterminePagingFor64kCartridge(target, segment, disassembly);
|
||||||
@@ -159,8 +161,8 @@ static void DeterminePagingForCartridge(StaticAnalyser::Target &target, const St
|
|||||||
// check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM
|
// check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM
|
||||||
// regions; when they don't they at least seem to have the first 128 bytes be the same as the
|
// regions; when they don't they at least seem to have the first 128 bytes be the same as the
|
||||||
// next 128 bytes. So check for that.
|
// next 128 bytes. So check for that.
|
||||||
if( target.atari.paging_model != StaticAnalyser::Atari2600PagingModel::CBSRamPlus &&
|
if( target.paging_model != Analyser::Static::Atari::Target::PagingModel::CBSRamPlus &&
|
||||||
target.atari.paging_model != StaticAnalyser::Atari2600PagingModel::MNetwork) {
|
target.paging_model != Analyser::Static::Atari::Target::PagingModel::MNetwork) {
|
||||||
bool has_superchip = true;
|
bool has_superchip = true;
|
||||||
for(std::size_t address = 0; address < 128; address++) {
|
for(std::size_t address = 0; address < 128; address++) {
|
||||||
if(segment.data[address] != segment.data[address+128]) {
|
if(segment.data[address] != segment.data[address+128]) {
|
||||||
@@ -168,24 +170,24 @@ static void DeterminePagingForCartridge(StaticAnalyser::Target &target, const St
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
target.atari.uses_superchip = has_superchip;
|
target.uses_superchip = has_superchip;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for a Tigervision or Tigervision-esque scheme
|
// check for a Tigervision or Tigervision-esque scheme
|
||||||
if(target.atari.paging_model == StaticAnalyser::Atari2600PagingModel::None && segment.data.size() > 4096) {
|
if(target.paging_model == Analyser::Static::Atari::Target::PagingModel::None && segment.data.size() > 4096) {
|
||||||
bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end();
|
bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end();
|
||||||
if(looks_like_tigervision) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Tigervision;
|
if(looks_like_tigervision) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StaticAnalyser::Atari::AddTargets(const Media &media, std::list<Target> &destination) {
|
Analyser::Static::TargetList Analyser::Static::Atari::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||||
// TODO: sanity checking; is this image really for an Atari 2600.
|
// TODO: sanity checking; is this image really for an Atari 2600?
|
||||||
Target target;
|
std::unique_ptr<Analyser::Static::Atari::Target> target(new Analyser::Static::Atari::Target);
|
||||||
target.machine = Target::Atari2600;
|
target->machine = Machine::Atari2600;
|
||||||
target.probability = 1.0;
|
target->confidence = 0.5;
|
||||||
target.media.cartridges = media.cartridges;
|
target->media.cartridges = media.cartridges;
|
||||||
target.atari.paging_model = Atari2600PagingModel::None;
|
target->paging_model = Analyser::Static::Atari::Target::PagingModel::None;
|
||||||
target.atari.uses_superchip = false;
|
target->uses_superchip = false;
|
||||||
|
|
||||||
// try to figure out the paging scheme
|
// try to figure out the paging scheme
|
||||||
if(!media.cartridges.empty()) {
|
if(!media.cartridges.empty()) {
|
||||||
@@ -193,9 +195,10 @@ void StaticAnalyser::Atari::AddTargets(const Media &media, std::list<Target> &de
|
|||||||
|
|
||||||
if(segments.size() == 1) {
|
if(segments.size() == 1) {
|
||||||
const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
|
const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
|
||||||
DeterminePagingForCartridge(target, segment);
|
DeterminePagingForCartridge(*target, segment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
TargetList destinations;
|
||||||
destination.push_back(target);
|
destinations.push_back(std::move(target));
|
||||||
|
return destinations;
|
||||||
}
|
}
|
||||||
@@ -3,19 +3,23 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 15/09/2016.
|
// Created by Thomas Harte on 15/09/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef StaticAnalyser_Atari_StaticAnalyser_hpp
|
#ifndef StaticAnalyser_Atari_StaticAnalyser_hpp
|
||||||
#define StaticAnalyser_Atari_StaticAnalyser_hpp
|
#define StaticAnalyser_Atari_StaticAnalyser_hpp
|
||||||
|
|
||||||
#include "../StaticAnalyser.hpp"
|
#include "../StaticAnalyser.hpp"
|
||||||
|
#include "../../../Storage/TargetPlatforms.hpp"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace StaticAnalyser {
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
namespace Atari {
|
namespace Atari {
|
||||||
|
|
||||||
void AddTargets(const Media &media, std::list<Target> &destination);
|
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
43
Analyser/Static/Atari/Target.hpp
Normal file
43
Analyser/Static/Atari/Target.hpp
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
//
|
||||||
|
// Target.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 09/03/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Analyser_Static_Atari_Target_h
|
||||||
|
#define Analyser_Static_Atari_Target_h
|
||||||
|
|
||||||
|
#include "../StaticAnalyser.hpp"
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
|
namespace Atari {
|
||||||
|
|
||||||
|
struct Target: public ::Analyser::Static::Target {
|
||||||
|
enum class PagingModel {
|
||||||
|
None,
|
||||||
|
CommaVid,
|
||||||
|
Atari8k,
|
||||||
|
Atari16k,
|
||||||
|
Atari32k,
|
||||||
|
ActivisionStack,
|
||||||
|
ParkerBros,
|
||||||
|
Tigervision,
|
||||||
|
CBSRamPlus,
|
||||||
|
MNetwork,
|
||||||
|
MegaBoy,
|
||||||
|
Pitfall2
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: shouldn't these be properties of the cartridge?
|
||||||
|
PagingModel paging_model = PagingModel::None;
|
||||||
|
bool uses_superchip = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* Analyser_Static_Atari_Target_h */
|
||||||
64
Analyser/Static/Coleco/StaticAnalyser.cpp
Normal file
64
Analyser/Static/Coleco/StaticAnalyser.cpp
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
//
|
||||||
|
// StaticAnalyser.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 23/02/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "StaticAnalyser.hpp"
|
||||||
|
|
||||||
|
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||||
|
ColecoCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||||
|
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> coleco_cartridges;
|
||||||
|
|
||||||
|
for(const auto &cartridge : cartridges) {
|
||||||
|
const auto &segments = cartridge->get_segments();
|
||||||
|
|
||||||
|
// only one mapped item is allowed
|
||||||
|
if(segments.size() != 1) continue;
|
||||||
|
|
||||||
|
// which must be 8, 12, 16, 24 or 32 kb in size
|
||||||
|
const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
|
||||||
|
const std::size_t data_size = segment.data.size();
|
||||||
|
const std::size_t overflow = data_size&8191;
|
||||||
|
if(overflow > 8 && overflow != 512 && (data_size != 12*1024)) continue;
|
||||||
|
if(data_size < 8192) continue;
|
||||||
|
|
||||||
|
// the two bytes that will be first must be 0xaa and 0x55, either way around
|
||||||
|
auto *start = &segment.data[0];
|
||||||
|
if((data_size & static_cast<std::size_t>(~8191)) > 32768) {
|
||||||
|
start = &segment.data[segment.data.size() - 16384];
|
||||||
|
}
|
||||||
|
if(start[0] != 0xaa && start[0] != 0x55 && start[1] != 0xaa && start[1] != 0x55) continue;
|
||||||
|
if(start[0] == start[1]) continue;
|
||||||
|
|
||||||
|
// probability of a random binary blob that isn't a Coleco ROM proceeding to here is 1 - 1/32768.
|
||||||
|
if(!overflow) {
|
||||||
|
coleco_cartridges.push_back(cartridge);
|
||||||
|
} else {
|
||||||
|
// Size down to a multiple of 8kb and apply the start address.
|
||||||
|
std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
|
||||||
|
|
||||||
|
std::vector<uint8_t> truncated_data;
|
||||||
|
std::vector<uint8_t>::difference_type truncated_size = static_cast<std::vector<uint8_t>::difference_type>(segment.data.size()) & ~8191;
|
||||||
|
truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size);
|
||||||
|
output_segments.emplace_back(0x8000, truncated_data);
|
||||||
|
|
||||||
|
coleco_cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return coleco_cartridges;
|
||||||
|
}
|
||||||
|
|
||||||
|
Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||||
|
TargetList targets;
|
||||||
|
std::unique_ptr<Target> target(new Target);
|
||||||
|
target->machine = Machine::ColecoVision;
|
||||||
|
target->confidence = 1.0f - 1.0f / 32768.0f;
|
||||||
|
target->media.cartridges = ColecoCartridgesFrom(media.cartridges);
|
||||||
|
if(!target->media.empty())
|
||||||
|
targets.push_back(std::move(target));
|
||||||
|
return targets;
|
||||||
|
}
|
||||||
27
Analyser/Static/Coleco/StaticAnalyser.hpp
Normal file
27
Analyser/Static/Coleco/StaticAnalyser.hpp
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// StaticAnalyser.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 23/02/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef StaticAnalyser_Coleco_StaticAnalyser_hpp
|
||||||
|
#define StaticAnalyser_Coleco_StaticAnalyser_hpp
|
||||||
|
|
||||||
|
#include "../StaticAnalyser.hpp"
|
||||||
|
#include "../../../Storage/TargetPlatforms.hpp"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
|
namespace Coleco {
|
||||||
|
|
||||||
|
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* StaticAnalyser_hpp */
|
||||||
@@ -3,19 +3,19 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 13/09/2016.
|
// Created by Thomas Harte on 13/09/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "Disk.hpp"
|
#include "Disk.hpp"
|
||||||
#include "../../Storage/Disk/Controller/DiskController.hpp"
|
#include "../../../Storage/Disk/Controller/DiskController.hpp"
|
||||||
#include "../../Storage/Disk/Encodings/CommodoreGCR.hpp"
|
#include "../../../Storage/Disk/Encodings/CommodoreGCR.hpp"
|
||||||
#include "../../Storage/Data/Commodore.hpp"
|
#include "../../../Storage/Data/Commodore.hpp"
|
||||||
|
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
using namespace StaticAnalyser::Commodore;
|
using namespace Analyser::Static::Commodore;
|
||||||
|
|
||||||
class CommodoreGCRParser: public Storage::Disk::Controller {
|
class CommodoreGCRParser: public Storage::Disk::Controller {
|
||||||
public:
|
public:
|
||||||
@@ -45,9 +45,11 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
|
|||||||
|
|
||||||
if(difference) {
|
if(difference) {
|
||||||
int direction = difference < 0 ? -1 : 1;
|
int direction = difference < 0 ? -1 : 1;
|
||||||
difference *= 2 * direction;
|
difference *= direction;
|
||||||
|
|
||||||
for(int c = 0; c < difference; c++) get_drive().step(direction);
|
for(int c = 0; c < difference; c++) {
|
||||||
|
get_drive().step(Storage::Disk::HeadPosition(direction));
|
||||||
|
}
|
||||||
|
|
||||||
unsigned int zone = 3;
|
unsigned int zone = 3;
|
||||||
if(track >= 18) zone = 2;
|
if(track >= 18) zone = 2;
|
||||||
@@ -71,19 +73,19 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
|
|||||||
bit_count_++;
|
bit_count_++;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int proceed_to_next_block() {
|
unsigned int proceed_to_next_block(int max_index_count) {
|
||||||
// find GCR lead-in
|
// find GCR lead-in
|
||||||
proceed_to_shift_value(0x3ff);
|
proceed_to_shift_value(0x3ff);
|
||||||
if(shift_register_ != 0x3ff) return 0xff;
|
if(shift_register_ != 0x3ff) return 0xff;
|
||||||
|
|
||||||
// find end of lead-in
|
// find end of lead-in
|
||||||
while(shift_register_ == 0x3ff && index_count_ < 2) {
|
while(shift_register_ == 0x3ff && index_count_ < max_index_count) {
|
||||||
run_for(Cycles(1));
|
run_for(Cycles(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// continue for a further nine bits
|
// continue for a further nine bits
|
||||||
bit_count_ = 0;
|
bit_count_ = 0;
|
||||||
while(bit_count_ < 9 && index_count_ < 2) {
|
while(bit_count_ < 9 && index_count_ < max_index_count) {
|
||||||
run_for(Cycles(1));
|
run_for(Cycles(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,8 +99,8 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void proceed_to_shift_value(unsigned int shift_value) {
|
void proceed_to_shift_value(unsigned int shift_value) {
|
||||||
index_count_ = 0;
|
const int max_index_count = index_count_ + 2;
|
||||||
while(shift_register_ != shift_value && index_count_ < 2) {
|
while(shift_register_ != shift_value && index_count_ < max_index_count) {
|
||||||
run_for(Cycles(1));
|
run_for(Cycles(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,13 +126,13 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
|
|||||||
|
|
||||||
std::shared_ptr<Sector> get_next_sector() {
|
std::shared_ptr<Sector> get_next_sector() {
|
||||||
std::shared_ptr<Sector> sector(new Sector);
|
std::shared_ptr<Sector> sector(new Sector);
|
||||||
index_count_ = 0;
|
const int max_index_count = index_count_ + 2;
|
||||||
|
|
||||||
while(index_count_ < 2) {
|
while(index_count_ < max_index_count) {
|
||||||
// look for a sector header
|
// look for a sector header
|
||||||
while(1) {
|
while(1) {
|
||||||
if(proceed_to_next_block() == 0x08) break;
|
if(proceed_to_next_block(max_index_count) == 0x08) break;
|
||||||
if(index_count_ >= 2) return nullptr;
|
if(index_count_ >= max_index_count) return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get sector details, skip if this looks malformed
|
// get sector details, skip if this looks malformed
|
||||||
@@ -144,8 +146,8 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
|
|||||||
|
|
||||||
// look for the following data
|
// look for the following data
|
||||||
while(1) {
|
while(1) {
|
||||||
if(proceed_to_next_block() == 0x07) break;
|
if(proceed_to_next_block(max_index_count) == 0x07) break;
|
||||||
if(index_count_ >= 2) return nullptr;
|
if(index_count_ >= max_index_count) return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
checksum = 0;
|
checksum = 0;
|
||||||
@@ -165,8 +167,8 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||||
std::list<File> files;
|
std::vector<File> files;
|
||||||
CommodoreGCRParser parser;
|
CommodoreGCRParser parser;
|
||||||
parser.drive->set_disk(disk);
|
parser.drive->set_disk(disk);
|
||||||
|
|
||||||
@@ -3,23 +3,25 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 13/09/2016.
|
// Created by Thomas Harte on 13/09/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef StaticAnalyser_Commodore_Disk_hpp
|
#ifndef StaticAnalyser_Commodore_Disk_hpp
|
||||||
#define StaticAnalyser_Commodore_Disk_hpp
|
#define StaticAnalyser_Commodore_Disk_hpp
|
||||||
|
|
||||||
#include "../../Storage/Disk/Disk.hpp"
|
#include "../../../Storage/Disk/Disk.hpp"
|
||||||
#include "File.hpp"
|
#include "File.hpp"
|
||||||
#include <list>
|
|
||||||
|
|
||||||
namespace StaticAnalyser {
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
namespace Commodore {
|
namespace Commodore {
|
||||||
|
|
||||||
std::list<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk);
|
std::vector<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif /* Disk_hpp */
|
#endif /* Disk_hpp */
|
||||||
@@ -3,12 +3,12 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 10/09/2016.
|
// Created by Thomas Harte on 10/09/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "File.hpp"
|
#include "File.hpp"
|
||||||
|
|
||||||
bool StaticAnalyser::Commodore::File::is_basic() {
|
bool Analyser::Static::Commodore::File::is_basic() {
|
||||||
// BASIC files are always relocatable (?)
|
// BASIC files are always relocatable (?)
|
||||||
if(type != File::RelocatableProgram) return false;
|
if(type != File::RelocatableProgram) return false;
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ bool StaticAnalyser::Commodore::File::is_basic() {
|
|||||||
// [4 bytes: address of start of next line]
|
// [4 bytes: address of start of next line]
|
||||||
// [4 bytes: this line number]
|
// [4 bytes: this line number]
|
||||||
// ... null-terminated code ...
|
// ... null-terminated code ...
|
||||||
// (with a next line address of 0000 indicating end of program)ß
|
// (with a next line address of 0000 indicating end of program)
|
||||||
while(1) {
|
while(1) {
|
||||||
if(static_cast<size_t>(line_address - starting_address) >= data.size() + 2) break;
|
if(static_cast<size_t>(line_address - starting_address) >= data.size() + 2) break;
|
||||||
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 10/09/2016.
|
// Created by Thomas Harte on 10/09/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef File_hpp
|
#ifndef File_hpp
|
||||||
@@ -12,7 +12,8 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace StaticAnalyser {
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
namespace Commodore {
|
namespace Commodore {
|
||||||
|
|
||||||
struct File {
|
struct File {
|
||||||
@@ -34,6 +35,7 @@ struct File {
|
|||||||
bool is_basic();
|
bool is_basic();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
161
Analyser/Static/Commodore/StaticAnalyser.cpp
Normal file
161
Analyser/Static/Commodore/StaticAnalyser.cpp
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
//
|
||||||
|
// CommodoreAnalyser.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 06/09/2016.
|
||||||
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "StaticAnalyser.hpp"
|
||||||
|
|
||||||
|
#include "Disk.hpp"
|
||||||
|
#include "File.hpp"
|
||||||
|
#include "Tape.hpp"
|
||||||
|
#include "Target.hpp"
|
||||||
|
#include "../../../Storage/Cartridge/Encodings/CommodoreROM.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
using namespace Analyser::Static::Commodore;
|
||||||
|
|
||||||
|
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||||
|
Vic20CartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||||
|
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> vic20_cartridges;
|
||||||
|
|
||||||
|
for(const auto &cartridge : cartridges) {
|
||||||
|
const auto &segments = cartridge->get_segments();
|
||||||
|
|
||||||
|
// only one mapped item is allowed
|
||||||
|
if(segments.size() != 1) continue;
|
||||||
|
|
||||||
|
// which must be 16 kb in size
|
||||||
|
Storage::Cartridge::Cartridge::Segment segment = segments.front();
|
||||||
|
if(segment.start_address != 0xa000) continue;
|
||||||
|
if(!Storage::Cartridge::Encodings::CommodoreROM::isROM(segment.data)) continue;
|
||||||
|
|
||||||
|
vic20_cartridges.push_back(cartridge);
|
||||||
|
}
|
||||||
|
|
||||||
|
return vic20_cartridges;
|
||||||
|
}
|
||||||
|
|
||||||
|
Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||||
|
TargetList destination;
|
||||||
|
|
||||||
|
std::unique_ptr<Target> target(new Target);
|
||||||
|
target->machine = Machine::Vic20; // TODO: machine estimation
|
||||||
|
target->confidence = 0.5; // TODO: a proper estimation
|
||||||
|
|
||||||
|
int device = 0;
|
||||||
|
std::vector<File> files;
|
||||||
|
bool is_disk = false;
|
||||||
|
|
||||||
|
// strip out inappropriate cartridges
|
||||||
|
target->media.cartridges = Vic20CartridgesFrom(media.cartridges);
|
||||||
|
|
||||||
|
// check disks
|
||||||
|
for(auto &disk : media.disks) {
|
||||||
|
std::vector<File> disk_files = GetFiles(disk);
|
||||||
|
if(!disk_files.empty()) {
|
||||||
|
is_disk = true;
|
||||||
|
files.insert(files.end(), disk_files.begin(), disk_files.end());
|
||||||
|
target->media.disks.push_back(disk);
|
||||||
|
if(!device) device = 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check tapes
|
||||||
|
for(auto &tape : media.tapes) {
|
||||||
|
std::vector<File> tape_files = GetFiles(tape);
|
||||||
|
tape->reset();
|
||||||
|
if(!tape_files.empty()) {
|
||||||
|
files.insert(files.end(), tape_files.begin(), tape_files.end());
|
||||||
|
target->media.tapes.push_back(tape);
|
||||||
|
if(!device) device = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!files.empty()) {
|
||||||
|
target->memory_model = Target::MemoryModel::Unexpanded;
|
||||||
|
std::ostringstream string_stream;
|
||||||
|
string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ",";
|
||||||
|
if(files.front().is_basic()) {
|
||||||
|
string_stream << "0";
|
||||||
|
} else {
|
||||||
|
string_stream << "1";
|
||||||
|
}
|
||||||
|
string_stream << "\nRUN\n";
|
||||||
|
target->loading_command = string_stream.str();
|
||||||
|
|
||||||
|
// make a first guess based on loading address
|
||||||
|
switch(files.front().starting_address) {
|
||||||
|
default:
|
||||||
|
printf("Starting address %04x?\n", files.front().starting_address);
|
||||||
|
case 0x1001:
|
||||||
|
target->memory_model = Target::MemoryModel::Unexpanded;
|
||||||
|
break;
|
||||||
|
case 0x1201:
|
||||||
|
target->memory_model = Target::MemoryModel::ThirtyTwoKB;
|
||||||
|
break;
|
||||||
|
case 0x0401:
|
||||||
|
target->memory_model = Target::MemoryModel::EightKB;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// General approach: increase memory size conservatively such that the largest file found will fit.
|
||||||
|
// for(File &file : files) {
|
||||||
|
// std::size_t file_size = file.data.size();
|
||||||
|
// bool is_basic = file.is_basic();
|
||||||
|
|
||||||
|
/*if(is_basic)
|
||||||
|
{
|
||||||
|
// BASIC files may be relocated, so the only limit is size.
|
||||||
|
//
|
||||||
|
// An unexpanded machine has 3583 bytes free for BASIC;
|
||||||
|
// a 3kb expanded machine has 6655 bytes free.
|
||||||
|
if(file_size > 6655)
|
||||||
|
target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
|
||||||
|
else if(target->vic20.memory_model == Vic20MemoryModel::Unexpanded && file_size > 3583)
|
||||||
|
target->vic20.memory_model = Vic20MemoryModel::EightKB;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{*/
|
||||||
|
// if(!file.type == File::NonRelocatableProgram)
|
||||||
|
// {
|
||||||
|
// Non-BASIC files may be relocatable but, if so, by what logic?
|
||||||
|
// Given that this is unknown, take starting address as literal
|
||||||
|
// and check against memory windows.
|
||||||
|
//
|
||||||
|
// (ignoring colour memory...)
|
||||||
|
// An unexpanded Vic has memory between 0x0000 and 0x0400; and between 0x1000 and 0x2000.
|
||||||
|
// A 3kb expanded Vic fills in the gap and has memory between 0x0000 and 0x2000.
|
||||||
|
// A 32kb expanded Vic has memory in the entire low 32kb.
|
||||||
|
// uint16_t starting_address = file.starting_address;
|
||||||
|
|
||||||
|
// If anything above the 8kb mark is touched, mark as a 32kb machine; otherwise if the
|
||||||
|
// region 0x0400 to 0x1000 is touched and this is an unexpanded machine, mark as 3kb.
|
||||||
|
// if(starting_address + file_size > 0x2000)
|
||||||
|
// target->memory_model = Target::MemoryModel::ThirtyTwoKB;
|
||||||
|
// else if(target->memory_model == Target::MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400))
|
||||||
|
// target->memory_model = Target::MemoryModel::ThirtyTwoKB;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!target->media.empty()) {
|
||||||
|
// Inspect filename for a region hint.
|
||||||
|
std::string lowercase_name = file_name;
|
||||||
|
std::transform(lowercase_name.begin(), lowercase_name.end(), lowercase_name.begin(), ::tolower);
|
||||||
|
if(lowercase_name.find("ntsc") != std::string::npos) {
|
||||||
|
target->region = Analyser::Static::Commodore::Target::Region::American;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach a 1540 if there are any disks here.
|
||||||
|
target->has_c1540 = !target->media.disks.empty();
|
||||||
|
|
||||||
|
destination.push_back(std::move(target));
|
||||||
|
}
|
||||||
|
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
@@ -3,19 +3,23 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 06/09/2016.
|
// Created by Thomas Harte on 06/09/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef StaticAnalyser_Commodore_StaticAnalyser_hpp
|
#ifndef StaticAnalyser_Commodore_StaticAnalyser_hpp
|
||||||
#define StaticAnalyser_Commodore_StaticAnalyser_hpp
|
#define StaticAnalyser_Commodore_StaticAnalyser_hpp
|
||||||
|
|
||||||
#include "../StaticAnalyser.hpp"
|
#include "../StaticAnalyser.hpp"
|
||||||
|
#include "../../../Storage/TargetPlatforms.hpp"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace StaticAnalyser {
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
namespace Commodore {
|
namespace Commodore {
|
||||||
|
|
||||||
void AddTargets(const Media &media, std::list<Target> &destination);
|
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3,18 +3,18 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 24/08/2016.
|
// Created by Thomas Harte on 24/08/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "Tape.hpp"
|
#include "Tape.hpp"
|
||||||
|
|
||||||
#include "../../Storage/Tape/Parsers/Commodore.hpp"
|
#include "../../../Storage/Tape/Parsers/Commodore.hpp"
|
||||||
|
|
||||||
using namespace StaticAnalyser::Commodore;
|
using namespace Analyser::Static::Commodore;
|
||||||
|
|
||||||
std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||||
Storage::Tape::Commodore::Parser parser;
|
Storage::Tape::Commodore::Parser parser;
|
||||||
std::list<File> file_list;
|
std::vector<File> file_list;
|
||||||
|
|
||||||
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape);
|
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape);
|
||||||
|
|
||||||
@@ -3,21 +3,22 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 24/08/2016.
|
// Created by Thomas Harte on 24/08/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef StaticAnalyser_Commodore_Tape_hpp
|
#ifndef StaticAnalyser_Commodore_Tape_hpp
|
||||||
#define StaticAnalyser_Commodore_Tape_hpp
|
#define StaticAnalyser_Commodore_Tape_hpp
|
||||||
|
|
||||||
#include "../../Storage/Tape/Tape.hpp"
|
#include "../../../Storage/Tape/Tape.hpp"
|
||||||
#include "File.hpp"
|
#include "File.hpp"
|
||||||
#include <list>
|
|
||||||
|
|
||||||
namespace StaticAnalyser {
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
namespace Commodore {
|
namespace Commodore {
|
||||||
|
|
||||||
std::list<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
44
Analyser/Static/Commodore/Target.hpp
Normal file
44
Analyser/Static/Commodore/Target.hpp
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
//
|
||||||
|
// Target.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 09/03/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Analyser_Static_Commodore_Target_h
|
||||||
|
#define Analyser_Static_Commodore_Target_h
|
||||||
|
|
||||||
|
#include "../StaticAnalyser.hpp"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
|
namespace Commodore {
|
||||||
|
|
||||||
|
struct Target: public ::Analyser::Static::Target {
|
||||||
|
enum class MemoryModel {
|
||||||
|
Unexpanded,
|
||||||
|
EightKB,
|
||||||
|
ThirtyTwoKB
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Region {
|
||||||
|
American,
|
||||||
|
Danish,
|
||||||
|
Japanese,
|
||||||
|
European,
|
||||||
|
Swedish
|
||||||
|
};
|
||||||
|
|
||||||
|
MemoryModel memory_model = MemoryModel::Unexpanded;
|
||||||
|
Region region = Region::European;
|
||||||
|
bool has_c1540 = false;
|
||||||
|
std::string loading_command;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* Analyser_Static_Commodore_Target_h */
|
||||||
@@ -3,17 +3,17 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 10/11/2016.
|
// Created by Thomas Harte on 10/11/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "6502.hpp"
|
#include "6502.hpp"
|
||||||
|
|
||||||
#include "Kernel.hpp"
|
#include "Kernel.hpp"
|
||||||
|
|
||||||
using namespace StaticAnalyser::MOS6502;
|
using namespace Analyser::Static::MOS6502;
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
using PartialDisassembly = StaticAnalyser::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
|
using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
|
||||||
|
|
||||||
struct MOS6502Disassembler {
|
struct MOS6502Disassembler {
|
||||||
|
|
||||||
@@ -312,9 +312,9 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
|
|||||||
|
|
||||||
} // end of anonymous namespace
|
} // end of anonymous namespace
|
||||||
|
|
||||||
Disassembly StaticAnalyser::MOS6502::Disassemble(
|
Disassembly Analyser::Static::MOS6502::Disassemble(
|
||||||
const std::vector<uint8_t> &memory,
|
const std::vector<uint8_t> &memory,
|
||||||
const std::function<std::size_t(uint16_t)> &address_mapper,
|
const std::function<std::size_t(uint16_t)> &address_mapper,
|
||||||
std::vector<uint16_t> entry_points) {
|
std::vector<uint16_t> entry_points) {
|
||||||
return StaticAnalyser::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(memory, address_mapper, entry_points);
|
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(memory, address_mapper, entry_points);
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 10/11/2016.
|
// Created by Thomas Harte on 10/11/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef StaticAnalyser_Disassembler_6502_hpp
|
#ifndef StaticAnalyser_Disassembler_6502_hpp
|
||||||
@@ -16,11 +16,12 @@
|
|||||||
#include <set>
|
#include <set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace StaticAnalyser {
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
namespace MOS6502 {
|
namespace MOS6502 {
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Describes a 6502 instruciton — its address, the operation it performs, its addressing mode
|
Describes a 6502 instruciton: its address, the operation it performs, its addressing mode
|
||||||
and its operand, if any.
|
and its operand, if any.
|
||||||
*/
|
*/
|
||||||
struct Instruction {
|
struct Instruction {
|
||||||
@@ -95,5 +96,6 @@ Disassembly Disassemble(
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif /* Disassembler6502_hpp */
|
#endif /* Disassembler6502_hpp */
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 30/12/2017.
|
// Created by Thomas Harte on 30/12/2017.
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "AddressMapper.hpp"
|
#include "AddressMapper.hpp"
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 30/12/2017.
|
// Created by Thomas Harte on 30/12/2017.
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef AddressMapper_hpp
|
#ifndef AddressMapper_hpp
|
||||||
@@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
namespace StaticAnalyser {
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
namespace Disassembler {
|
namespace Disassembler {
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -24,6 +25,7 @@ template <typename T> std::function<std::size_t(T)> OffsetMapper(T start_address
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3,13 +3,14 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 31/12/2017.
|
// Created by Thomas Harte on 31/12/2017.
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef Kernel_hpp
|
#ifndef Kernel_hpp
|
||||||
#define Kernel_hpp
|
#define Kernel_hpp
|
||||||
|
|
||||||
namespace StaticAnalyser {
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
namespace Disassembly {
|
namespace Disassembly {
|
||||||
|
|
||||||
template <typename D, typename S> struct PartialDisassembly {
|
template <typename D, typename S> struct PartialDisassembly {
|
||||||
@@ -44,6 +45,7 @@ template <typename D, typename S, typename Disassembler> D Disassemble(
|
|||||||
return partial_disassembly.disassembly;
|
return partial_disassembly.disassembly;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3,17 +3,17 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 30/12/2017.
|
// Created by Thomas Harte on 30/12/2017.
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "Z80.hpp"
|
#include "Z80.hpp"
|
||||||
|
|
||||||
#include "Kernel.hpp"
|
#include "Kernel.hpp"
|
||||||
|
|
||||||
using namespace StaticAnalyser::Z80;
|
using namespace Analyser::Static::Z80;
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
using PartialDisassembly = StaticAnalyser::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
|
using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
|
||||||
|
|
||||||
class Accessor {
|
class Accessor {
|
||||||
public:
|
public:
|
||||||
@@ -611,9 +611,9 @@ struct Z80Disassembler {
|
|||||||
|
|
||||||
} // end of anonymous namespace
|
} // end of anonymous namespace
|
||||||
|
|
||||||
Disassembly StaticAnalyser::Z80::Disassemble(
|
Disassembly Analyser::Static::Z80::Disassemble(
|
||||||
const std::vector<uint8_t> &memory,
|
const std::vector<uint8_t> &memory,
|
||||||
const std::function<std::size_t(uint16_t)> &address_mapper,
|
const std::function<std::size_t(uint16_t)> &address_mapper,
|
||||||
std::vector<uint16_t> entry_points) {
|
std::vector<uint16_t> entry_points) {
|
||||||
return StaticAnalyser::Disassembly::Disassemble<Disassembly, uint16_t, Z80Disassembler>(memory, address_mapper, entry_points);
|
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, Z80Disassembler>(memory, address_mapper, entry_points);
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 30/12/2017.
|
// Created by Thomas Harte on 30/12/2017.
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef StaticAnalyser_Disassembler_Z80_hpp
|
#ifndef StaticAnalyser_Disassembler_Z80_hpp
|
||||||
@@ -15,7 +15,8 @@
|
|||||||
#include <set>
|
#include <set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace StaticAnalyser {
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
namespace Z80 {
|
namespace Z80 {
|
||||||
|
|
||||||
struct Instruction {
|
struct Instruction {
|
||||||
@@ -84,5 +85,6 @@ Disassembly Disassemble(
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif /* StaticAnalyser_Disassembler_Z80_hpp */
|
#endif /* StaticAnalyser_Disassembler_Z80_hpp */
|
||||||
125
Analyser/Static/DiskII/StaticAnalyser.cpp
Normal file
125
Analyser/Static/DiskII/StaticAnalyser.cpp
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
//
|
||||||
|
// StaticAnalyser.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 03/05/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "StaticAnalyser.hpp"
|
||||||
|
|
||||||
|
#include "../AppleII/Target.hpp"
|
||||||
|
#include "../Oric/Target.hpp"
|
||||||
|
#include "../Disassembler/6502.hpp"
|
||||||
|
#include "../Disassembler/AddressMapper.hpp"
|
||||||
|
|
||||||
|
#include "../../../Storage/Disk/Track/TrackSerialiser.hpp"
|
||||||
|
#include "../../../Storage/Disk/Encodings/AppleGCR/SegmentParser.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
Analyser::Static::Target *AppleTarget(const Storage::Encodings::AppleGCR::Sector *sector_zero) {
|
||||||
|
using Target = Analyser::Static::AppleII::Target;
|
||||||
|
auto *target = new Target;
|
||||||
|
target->machine = Analyser::Machine::AppleII;
|
||||||
|
|
||||||
|
if(sector_zero && sector_zero->encoding == Storage::Encodings::AppleGCR::Sector::Encoding::FiveAndThree) {
|
||||||
|
target->disk_controller = Target::DiskController::ThirteenSector;
|
||||||
|
} else {
|
||||||
|
target->disk_controller = Target::DiskController::SixteenSector;
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector *sector_zero) {
|
||||||
|
using Target = Analyser::Static::Oric::Target;
|
||||||
|
auto *target = new Target;
|
||||||
|
target->machine = Analyser::Machine::Oric;
|
||||||
|
target->rom = Target::ROM::Pravetz;
|
||||||
|
target->disk_interface = Target::DiskInterface::Pravetz;
|
||||||
|
target->loading_command = "CALL 800\n";
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||||
|
// This analyser can comprehend disks only.
|
||||||
|
if(media.disks.empty()) return {};
|
||||||
|
|
||||||
|
// Grab track 0, sector 0: the boot sector.
|
||||||
|
auto track_zero = media.disks.front()->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
|
||||||
|
auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment(
|
||||||
|
Storage::Disk::track_serialisation(*track_zero, Storage::Time(1, 50000)));
|
||||||
|
|
||||||
|
const Storage::Encodings::AppleGCR::Sector *sector_zero = nullptr;
|
||||||
|
for(const auto &pair: sector_map) {
|
||||||
|
if(!pair.second.address.sector) {
|
||||||
|
sector_zero = &pair.second;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's no boot sector then if there are also no sectors at all,
|
||||||
|
// decline to nominate a machine. Otherwise go with an Apple as the default.
|
||||||
|
TargetList targets;
|
||||||
|
if(!sector_zero) {
|
||||||
|
if(sector_map.empty()) {
|
||||||
|
return targets;
|
||||||
|
} else {
|
||||||
|
targets.push_back(std::unique_ptr<Analyser::Static::Target>(AppleTarget(nullptr)));
|
||||||
|
targets.back()->media = media;
|
||||||
|
return targets;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the boot sector looks like it's intended for the Oric, create an Oric.
|
||||||
|
// Otherwise go with the Apple II.
|
||||||
|
|
||||||
|
auto disassembly = Analyser::Static::MOS6502::Disassemble(sector_zero->data, Analyser::Static::Disassembler::OffsetMapper(0xb800), {0xb800});
|
||||||
|
|
||||||
|
bool did_read_shift_register = false;
|
||||||
|
bool is_oric = false;
|
||||||
|
|
||||||
|
// Look for a tight BPL loop reading the Oric's shift register address of 0x31c. The Apple II just has RAM there,
|
||||||
|
// so the probability of such a loop is infinitesimal.
|
||||||
|
for(const auto &instruction: disassembly.instructions_by_address) {
|
||||||
|
// Is this a read of the shift register?
|
||||||
|
if(
|
||||||
|
(
|
||||||
|
(instruction.second.operation == Analyser::Static::MOS6502::Instruction::LDA) ||
|
||||||
|
(instruction.second.operation == Analyser::Static::MOS6502::Instruction::LDX) ||
|
||||||
|
(instruction.second.operation == Analyser::Static::MOS6502::Instruction::LDY)
|
||||||
|
) &&
|
||||||
|
instruction.second.addressing_mode == Analyser::Static::MOS6502::Instruction::Absolute &&
|
||||||
|
instruction.second.address == 0x031c) {
|
||||||
|
did_read_shift_register = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(did_read_shift_register) {
|
||||||
|
if(
|
||||||
|
instruction.second.operation == Analyser::Static::MOS6502::Instruction::BPL &&
|
||||||
|
instruction.second.address == 0xfb) {
|
||||||
|
is_oric = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
did_read_shift_register = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check also for calls into the 0x3xx page above 0x320, as that's where the Oric's boot ROM is.
|
||||||
|
for(const auto address: disassembly.outward_calls) {
|
||||||
|
is_oric |= address >= 0x320 && address < 0x400;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(is_oric) {
|
||||||
|
targets.push_back(std::unique_ptr<Analyser::Static::Target>(OricTarget(sector_zero)));
|
||||||
|
} else {
|
||||||
|
targets.push_back(std::unique_ptr<Analyser::Static::Target>(AppleTarget(sector_zero)));
|
||||||
|
}
|
||||||
|
targets.back()->media = media;
|
||||||
|
return targets;
|
||||||
|
}
|
||||||
27
Analyser/Static/DiskII/StaticAnalyser.hpp
Normal file
27
Analyser/Static/DiskII/StaticAnalyser.hpp
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// StaticAnalyser.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 03/05/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Analyser_Static_DiskII_StaticAnalyser_hpp
|
||||||
|
#define Analyser_Static_DiskII_StaticAnalyser_hpp
|
||||||
|
|
||||||
|
#include "../StaticAnalyser.hpp"
|
||||||
|
#include "../../../Storage/TargetPlatforms.hpp"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
|
namespace DiskII {
|
||||||
|
|
||||||
|
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* Analyser_Static_DiskII_StaticAnalyser_hpp */
|
||||||
40
Analyser/Static/MSX/Cartridge.hpp
Normal file
40
Analyser/Static/MSX/Cartridge.hpp
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
//
|
||||||
|
// Cartridge.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 25/01/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Cartridge_hpp
|
||||||
|
#define Cartridge_hpp
|
||||||
|
|
||||||
|
#include "../../../Storage/Cartridge/Cartridge.hpp"
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
|
namespace MSX {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Extends the base cartridge class by adding a (guess at) the banking scheme.
|
||||||
|
*/
|
||||||
|
struct Cartridge: public ::Storage::Cartridge::Cartridge {
|
||||||
|
enum Type {
|
||||||
|
None,
|
||||||
|
Konami,
|
||||||
|
KonamiWithSCC,
|
||||||
|
ASCII8kb,
|
||||||
|
ASCII16kb,
|
||||||
|
FMPac
|
||||||
|
};
|
||||||
|
const Type type;
|
||||||
|
|
||||||
|
Cartridge(const std::vector<Segment> &segments, Type type) :
|
||||||
|
Storage::Cartridge::Cartridge(segments), type(type) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* Cartridge_hpp */
|
||||||
299
Analyser/Static/MSX/StaticAnalyser.cpp
Normal file
299
Analyser/Static/MSX/StaticAnalyser.cpp
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
//
|
||||||
|
// StaticAnalyser.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 25/11/2017.
|
||||||
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "StaticAnalyser.hpp"
|
||||||
|
|
||||||
|
#include "Cartridge.hpp"
|
||||||
|
#include "Tape.hpp"
|
||||||
|
#include "Target.hpp"
|
||||||
|
|
||||||
|
#include "../Disassembler/Z80.hpp"
|
||||||
|
#include "../Disassembler/AddressMapper.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
|
||||||
|
const Storage::Cartridge::Cartridge::Segment &segment,
|
||||||
|
uint16_t start_address,
|
||||||
|
Analyser::Static::MSX::Cartridge::Type type,
|
||||||
|
float confidence) {
|
||||||
|
|
||||||
|
// Size down to a multiple of 8kb in size and apply the start address.
|
||||||
|
std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
|
||||||
|
if(segment.data.size() & 0x1fff) {
|
||||||
|
std::vector<uint8_t> truncated_data;
|
||||||
|
std::vector<uint8_t>::difference_type truncated_size = static_cast<std::vector<uint8_t>::difference_type>(segment.data.size()) & ~0x1fff;
|
||||||
|
truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size);
|
||||||
|
output_segments.emplace_back(start_address, truncated_data);
|
||||||
|
} else {
|
||||||
|
output_segments.emplace_back(start_address, segment.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Analyser::Static::MSX::Target> target(new Analyser::Static::MSX::Target);
|
||||||
|
target->machine = Analyser::Machine::MSX;
|
||||||
|
target->confidence = confidence;
|
||||||
|
|
||||||
|
if(type == Analyser::Static::MSX::Cartridge::Type::None) {
|
||||||
|
target->media.cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments));
|
||||||
|
} else {
|
||||||
|
target->media.cartridges.emplace_back(new Analyser::Static::MSX::Cartridge(output_segments, type));
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Expected standard cartridge format:
|
||||||
|
|
||||||
|
DEFB "AB" ; expansion ROM header
|
||||||
|
DEFW initcode ; start of the init code, 0 if no initcode
|
||||||
|
DEFW callstat; pointer to CALL statement handler, 0 if no such handler
|
||||||
|
DEFW device; pointer to expansion device handler, 0 if no such handler
|
||||||
|
DEFW basic ; pointer to the start of a tokenized basicprogram, 0 if no basicprogram
|
||||||
|
DEFS 6,0 ; room reserved for future extensions
|
||||||
|
|
||||||
|
MSX cartridges often include banking hardware; those games were marketed as MegaROMs. The file
|
||||||
|
format that the MSX community has decided upon doesn't retain the type of hardware included, so
|
||||||
|
this analyser has to guess.
|
||||||
|
|
||||||
|
(additional audio hardware is also sometimes included, but it's implied by the banking hardware)
|
||||||
|
*/
|
||||||
|
static Analyser::Static::TargetList CartridgeTargetsFrom(
|
||||||
|
const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||||
|
// No cartridges implies no targets.
|
||||||
|
if(cartridges.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Analyser::Static::TargetList targets;
|
||||||
|
for(const auto &cartridge : cartridges) {
|
||||||
|
const auto &segments = cartridge->get_segments();
|
||||||
|
|
||||||
|
// Only one mapped item is allowed.
|
||||||
|
if(segments.size() != 1) continue;
|
||||||
|
|
||||||
|
// Which must be no more than 63 bytes larger than a multiple of 8 kb in size.
|
||||||
|
Storage::Cartridge::Cartridge::Segment segment = segments.front();
|
||||||
|
const size_t data_size = segment.data.size();
|
||||||
|
if(data_size < 0x2000 || (data_size & 0x1fff) > 64) continue;
|
||||||
|
|
||||||
|
// Check for a ROM header at address 0; if it's not found then try 0x4000
|
||||||
|
// and adjust the start address;
|
||||||
|
uint16_t start_address = 0;
|
||||||
|
bool found_start = false;
|
||||||
|
if(segment.data[0] == 0x41 && segment.data[1] == 0x42) {
|
||||||
|
start_address = 0x4000;
|
||||||
|
found_start = true;
|
||||||
|
} else if(segment.data.size() >= 0x8000 && segment.data[0x4000] == 0x41 && segment.data[0x4001] == 0x42) {
|
||||||
|
start_address = 0;
|
||||||
|
found_start = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject cartridge if the ROM header wasn't found.
|
||||||
|
if(!found_start) continue;
|
||||||
|
|
||||||
|
uint16_t init_address = static_cast<uint16_t>(segment.data[2] | (segment.data[3] << 8));
|
||||||
|
// TODO: check for a rational init address?
|
||||||
|
|
||||||
|
// If this ROM is less than 48kb in size then it's an ordinary ROM. Just emplace it and move on.
|
||||||
|
if(data_size <= 0xc000) {
|
||||||
|
targets.emplace_back(CartridgeTarget(segment, start_address, Analyser::Static::MSX::Cartridge::Type::None, 1.0));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this ROM is greater than 48kb in size then some sort of MegaROM scheme must
|
||||||
|
// be at play; disassemble to try to figure it out.
|
||||||
|
std::vector<uint8_t> first_8k;
|
||||||
|
first_8k.insert(first_8k.begin(), segment.data.begin(), segment.data.begin() + 8192);
|
||||||
|
Analyser::Static::Z80::Disassembly disassembly =
|
||||||
|
Analyser::Static::Z80::Disassemble(
|
||||||
|
first_8k,
|
||||||
|
Analyser::Static::Disassembler::OffsetMapper(start_address),
|
||||||
|
{ init_address }
|
||||||
|
);
|
||||||
|
|
||||||
|
// // Look for a indirect store followed by an unconditional JP or CALL into another
|
||||||
|
// // segment, that's a fairly explicit sign where found.
|
||||||
|
using Instruction = Analyser::Static::Z80::Instruction;
|
||||||
|
std::map<uint16_t, Instruction> &instructions = disassembly.instructions_by_address;
|
||||||
|
bool is_ascii = false;
|
||||||
|
// auto iterator = instructions.begin();
|
||||||
|
// while(iterator != instructions.end()) {
|
||||||
|
// auto next_iterator = iterator;
|
||||||
|
// next_iterator++;
|
||||||
|
// if(next_iterator == instructions.end()) break;
|
||||||
|
//
|
||||||
|
// if( iterator->second.operation == Instruction::Operation::LD &&
|
||||||
|
// iterator->second.destination == Instruction::Location::Operand_Indirect &&
|
||||||
|
// (
|
||||||
|
// iterator->second.operand == 0x5000 ||
|
||||||
|
// iterator->second.operand == 0x6000 ||
|
||||||
|
// iterator->second.operand == 0x6800 ||
|
||||||
|
// iterator->second.operand == 0x7000 ||
|
||||||
|
// iterator->second.operand == 0x77ff ||
|
||||||
|
// iterator->second.operand == 0x7800 ||
|
||||||
|
// iterator->second.operand == 0x8000 ||
|
||||||
|
// iterator->second.operand == 0x9000 ||
|
||||||
|
// iterator->second.operand == 0xa000
|
||||||
|
// ) &&
|
||||||
|
// (
|
||||||
|
// next_iterator->second.operation == Instruction::Operation::CALL ||
|
||||||
|
// next_iterator->second.operation == Instruction::Operation::JP
|
||||||
|
// ) &&
|
||||||
|
// ((next_iterator->second.operand >> 13) != (0x4000 >> 13))
|
||||||
|
// ) {
|
||||||
|
// const uint16_t address = static_cast<uint16_t>(next_iterator->second.operand);
|
||||||
|
// switch(iterator->second.operand) {
|
||||||
|
// case 0x6000:
|
||||||
|
// if(address >= 0x6000 && address < 0x8000) {
|
||||||
|
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
|
||||||
|
// }
|
||||||
|
// break;
|
||||||
|
// case 0x6800:
|
||||||
|
// if(address >= 0x6000 && address < 0x6800) {
|
||||||
|
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb;
|
||||||
|
// }
|
||||||
|
// break;
|
||||||
|
// case 0x7000:
|
||||||
|
// if(address >= 0x6000 && address < 0x8000) {
|
||||||
|
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
|
||||||
|
// }
|
||||||
|
// if(address >= 0x7000 && address < 0x7800) {
|
||||||
|
// is_ascii = true;
|
||||||
|
// }
|
||||||
|
// break;
|
||||||
|
// case 0x77ff:
|
||||||
|
// if(address >= 0x7000 && address < 0x7800) {
|
||||||
|
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII16kb;
|
||||||
|
// }
|
||||||
|
// break;
|
||||||
|
// case 0x7800:
|
||||||
|
// if(address >= 0xa000 && address < 0xc000) {
|
||||||
|
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb;
|
||||||
|
// }
|
||||||
|
// break;
|
||||||
|
// case 0x8000:
|
||||||
|
// if(address >= 0x8000 && address < 0xa000) {
|
||||||
|
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
|
||||||
|
// }
|
||||||
|
// break;
|
||||||
|
// case 0x9000:
|
||||||
|
// if(address >= 0x8000 && address < 0xa000) {
|
||||||
|
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
|
||||||
|
// }
|
||||||
|
// break;
|
||||||
|
// case 0xa000:
|
||||||
|
// if(address >= 0xa000 && address < 0xc000) {
|
||||||
|
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::Konami;
|
||||||
|
// }
|
||||||
|
// break;
|
||||||
|
// case 0xb000:
|
||||||
|
// if(address >= 0xa000 && address < 0xc000) {
|
||||||
|
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
|
||||||
|
// }
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// iterator = next_iterator;
|
||||||
|
|
||||||
|
// Look for LD (nnnn), A instructions, and collate those addresses.
|
||||||
|
std::map<uint16_t, int> address_counts;
|
||||||
|
for(const auto &instruction_pair : instructions) {
|
||||||
|
if( instruction_pair.second.operation == Instruction::Operation::LD &&
|
||||||
|
instruction_pair.second.destination == Instruction::Location::Operand_Indirect &&
|
||||||
|
instruction_pair.second.source == Instruction::Location::A) {
|
||||||
|
address_counts[static_cast<uint16_t>(instruction_pair.second.operand)]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Weight confidences by number of observed hits.
|
||||||
|
float total_hits =
|
||||||
|
static_cast<float>(
|
||||||
|
address_counts[0x6000] + address_counts[0x6800] +
|
||||||
|
address_counts[0x7000] + address_counts[0x7800] +
|
||||||
|
address_counts[0x77ff] + address_counts[0x8000] +
|
||||||
|
address_counts[0xa000] + address_counts[0x5000] +
|
||||||
|
address_counts[0x9000] + address_counts[0xb000]
|
||||||
|
);
|
||||||
|
|
||||||
|
targets.push_back(CartridgeTarget(
|
||||||
|
segment,
|
||||||
|
start_address,
|
||||||
|
Analyser::Static::MSX::Cartridge::ASCII8kb,
|
||||||
|
static_cast<float>( address_counts[0x6000] +
|
||||||
|
address_counts[0x6800] +
|
||||||
|
address_counts[0x7000] +
|
||||||
|
address_counts[0x7800]) / total_hits));
|
||||||
|
targets.push_back(CartridgeTarget(
|
||||||
|
segment,
|
||||||
|
start_address,
|
||||||
|
Analyser::Static::MSX::Cartridge::ASCII16kb,
|
||||||
|
static_cast<float>( address_counts[0x6000] +
|
||||||
|
address_counts[0x7000] +
|
||||||
|
address_counts[0x77ff]) / total_hits));
|
||||||
|
if(!is_ascii) {
|
||||||
|
targets.push_back(CartridgeTarget(
|
||||||
|
segment,
|
||||||
|
start_address,
|
||||||
|
Analyser::Static::MSX::Cartridge::Konami,
|
||||||
|
static_cast<float>( address_counts[0x6000] +
|
||||||
|
address_counts[0x8000] +
|
||||||
|
address_counts[0xa000]) / total_hits));
|
||||||
|
}
|
||||||
|
if(!is_ascii) {
|
||||||
|
targets.push_back(CartridgeTarget(
|
||||||
|
segment,
|
||||||
|
start_address,
|
||||||
|
Analyser::Static::MSX::Cartridge::KonamiWithSCC,
|
||||||
|
static_cast<float>( address_counts[0x5000] +
|
||||||
|
address_counts[0x7000] +
|
||||||
|
address_counts[0x9000] +
|
||||||
|
address_counts[0xb000]) / total_hits));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return targets;
|
||||||
|
}
|
||||||
|
|
||||||
|
Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||||
|
TargetList destination;
|
||||||
|
|
||||||
|
// Append targets for any cartridges that look correct.
|
||||||
|
auto cartridge_targets = CartridgeTargetsFrom(media.cartridges);
|
||||||
|
std::move(cartridge_targets.begin(), cartridge_targets.end(), std::back_inserter(destination));
|
||||||
|
|
||||||
|
// Consider building a target for disks and/or tapes.
|
||||||
|
std::unique_ptr<Target> target(new Target);
|
||||||
|
|
||||||
|
// Check tapes for loadable files.
|
||||||
|
for(auto &tape : media.tapes) {
|
||||||
|
std::vector<File> files_on_tape = GetFiles(tape);
|
||||||
|
if(!files_on_tape.empty()) {
|
||||||
|
switch(files_on_tape.front().type) {
|
||||||
|
case File::Type::ASCII: target->loading_command = "RUN\"CAS:\r"; break;
|
||||||
|
case File::Type::TokenisedBASIC: target->loading_command = "CLOAD\rRUN\r"; break;
|
||||||
|
case File::Type::Binary: target->loading_command = "BLOAD\"CAS:\",R\r"; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
target->media.tapes.push_back(tape);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blindly accept disks for now.
|
||||||
|
target->media.disks = media.disks;
|
||||||
|
target->has_disk_drive = !media.disks.empty();
|
||||||
|
|
||||||
|
if(!target->media.empty()) {
|
||||||
|
target->machine = Machine::MSX;
|
||||||
|
target->confidence = 0.5;
|
||||||
|
destination.push_back(std::move(target));
|
||||||
|
}
|
||||||
|
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
@@ -3,19 +3,23 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 25/11/2017.
|
// Created by Thomas Harte on 25/11/2017.
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef StaticAnalyser_MSX_StaticAnalyser_hpp
|
#ifndef StaticAnalyser_MSX_StaticAnalyser_hpp
|
||||||
#define StaticAnalyser_MSX_StaticAnalyser_hpp
|
#define StaticAnalyser_MSX_StaticAnalyser_hpp
|
||||||
|
|
||||||
#include "../StaticAnalyser.hpp"
|
#include "../StaticAnalyser.hpp"
|
||||||
|
#include "../../../Storage/TargetPlatforms.hpp"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace StaticAnalyser {
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
namespace MSX {
|
namespace MSX {
|
||||||
|
|
||||||
void AddTargets(const Media &media, std::list<Target> &destination);
|
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3,14 +3,14 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 25/12/2017.
|
// Created by Thomas Harte on 25/12/2017.
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "Tape.hpp"
|
#include "Tape.hpp"
|
||||||
|
|
||||||
#include "../../Storage/Tape/Parsers/MSX.hpp"
|
#include "../../../Storage/Tape/Parsers/MSX.hpp"
|
||||||
|
|
||||||
using namespace StaticAnalyser::MSX;
|
using namespace Analyser::Static::MSX;
|
||||||
|
|
||||||
File::File(File &&rhs) :
|
File::File(File &&rhs) :
|
||||||
name(std::move(rhs.name)),
|
name(std::move(rhs.name)),
|
||||||
@@ -24,7 +24,7 @@ File::File() :
|
|||||||
starting_address(0),
|
starting_address(0),
|
||||||
entry_address(0) {} // For the sake of initialising in a defined state.
|
entry_address(0) {} // For the sake of initialising in a defined state.
|
||||||
|
|
||||||
std::vector<File> StaticAnalyser::MSX::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||||
std::vector<File> files;
|
std::vector<File> files;
|
||||||
|
|
||||||
Storage::Tape::BinaryTapePlayer tape_player(1000000);
|
Storage::Tape::BinaryTapePlayer tape_player(1000000);
|
||||||
@@ -3,18 +3,19 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 25/12/2017.
|
// Created by Thomas Harte on 25/12/2017.
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef StaticAnalyser_MSX_Tape_hpp
|
#ifndef StaticAnalyser_MSX_Tape_hpp
|
||||||
#define StaticAnalyser_MSX_Tape_hpp
|
#define StaticAnalyser_MSX_Tape_hpp
|
||||||
|
|
||||||
#include "../../Storage/Tape/Tape.hpp"
|
#include "../../../Storage/Tape/Tape.hpp"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace StaticAnalyser {
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
namespace MSX {
|
namespace MSX {
|
||||||
|
|
||||||
struct File {
|
struct File {
|
||||||
@@ -36,6 +37,7 @@ struct File {
|
|||||||
|
|
||||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
28
Analyser/Static/MSX/Target.hpp
Normal file
28
Analyser/Static/MSX/Target.hpp
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
//
|
||||||
|
// Target.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 02/04/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Analyser_Static_MSX_Target_h
|
||||||
|
#define Analyser_Static_MSX_Target_h
|
||||||
|
|
||||||
|
#include "../StaticAnalyser.hpp"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
|
namespace MSX {
|
||||||
|
|
||||||
|
struct Target: public ::Analyser::Static::Target {
|
||||||
|
bool has_disk_drive = false;
|
||||||
|
std::string loading_command;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* Analyser_Static_MSX_Target_h */
|
||||||
@@ -3,29 +3,35 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 11/10/2016.
|
// Created by Thomas Harte on 11/10/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "StaticAnalyser.hpp"
|
#include "StaticAnalyser.hpp"
|
||||||
|
|
||||||
#include "Tape.hpp"
|
#include "Tape.hpp"
|
||||||
|
#include "Target.hpp"
|
||||||
|
|
||||||
#include "../Disassembler/6502.hpp"
|
#include "../Disassembler/6502.hpp"
|
||||||
#include "../Disassembler/AddressMapper.hpp"
|
#include "../Disassembler/AddressMapper.hpp"
|
||||||
|
|
||||||
using namespace StaticAnalyser::Oric;
|
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
|
||||||
|
|
||||||
static int Score(const StaticAnalyser::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) {
|
#include <cstring>
|
||||||
|
|
||||||
|
using namespace Analyser::Static::Oric;
|
||||||
|
|
||||||
|
static int Score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) {
|
||||||
int score = 0;
|
int score = 0;
|
||||||
|
|
||||||
for(auto address : disassembly.outward_calls) score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
|
for(const auto address : disassembly.outward_calls) score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
|
||||||
for(auto address : disassembly.external_stores) score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
|
for(const auto address : disassembly.external_stores) score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
|
||||||
for(auto address : disassembly.external_loads) score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
|
for(const auto address : disassembly.external_loads) score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
|
||||||
|
|
||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int Basic10Score(const StaticAnalyser::MOS6502::Disassembly &disassembly) {
|
static int Basic10Score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||||
std::set<uint16_t> rom_functions = {
|
const std::set<uint16_t> rom_functions = {
|
||||||
0x0228, 0x022b,
|
0x0228, 0x022b,
|
||||||
0xc3ca, 0xc3f8, 0xc448, 0xc47c, 0xc4b5, 0xc4e3, 0xc4e0, 0xc524, 0xc56f, 0xc5a2, 0xc5f8, 0xc60a, 0xc6a5, 0xc6de, 0xc719, 0xc738,
|
0xc3ca, 0xc3f8, 0xc448, 0xc47c, 0xc4b5, 0xc4e3, 0xc4e0, 0xc524, 0xc56f, 0xc5a2, 0xc5f8, 0xc60a, 0xc6a5, 0xc6de, 0xc719, 0xc738,
|
||||||
0xc773, 0xc824, 0xc832, 0xc841, 0xc8c1, 0xc8fe, 0xc91f, 0xc93f, 0xc941, 0xc91e, 0xc98b, 0xc996, 0xc9b3, 0xc9e0, 0xca0a, 0xca1c,
|
0xc773, 0xc824, 0xc832, 0xc841, 0xc8c1, 0xc8fe, 0xc91f, 0xc93f, 0xc941, 0xc91e, 0xc98b, 0xc996, 0xc9b3, 0xc9e0, 0xca0a, 0xca1c,
|
||||||
@@ -41,15 +47,15 @@ static int Basic10Score(const StaticAnalyser::MOS6502::Disassembly &disassembly)
|
|||||||
0xf43c, 0xf4ef, 0xf523, 0xf561, 0xf535, 0xf57b, 0xf5d3, 0xf71a, 0xf73f, 0xf7e4, 0xf7e0, 0xf82f, 0xf88f, 0xf8af, 0xf8b5, 0xf920,
|
0xf43c, 0xf4ef, 0xf523, 0xf561, 0xf535, 0xf57b, 0xf5d3, 0xf71a, 0xf73f, 0xf7e4, 0xf7e0, 0xf82f, 0xf88f, 0xf8af, 0xf8b5, 0xf920,
|
||||||
0xf967, 0xf960, 0xf9c9, 0xfa14, 0xfa85, 0xfa9b, 0xfab1, 0xfac7, 0xfafa, 0xfb10, 0xfb26, 0xfbb6, 0xfbfe
|
0xf967, 0xf960, 0xf9c9, 0xfa14, 0xfa85, 0xfa9b, 0xfab1, 0xfac7, 0xfafa, 0xfb10, 0xfb26, 0xfbb6, 0xfbfe
|
||||||
};
|
};
|
||||||
std::set<uint16_t> variable_locations = {
|
const std::set<uint16_t> variable_locations = {
|
||||||
0x0228, 0x0229, 0x022a, 0x022b, 0x022c, 0x022d, 0x0230
|
0x0228, 0x0229, 0x022a, 0x022b, 0x022c, 0x022d, 0x0230
|
||||||
};
|
};
|
||||||
|
|
||||||
return Score(disassembly, rom_functions, variable_locations);
|
return Score(disassembly, rom_functions, variable_locations);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int Basic11Score(const StaticAnalyser::MOS6502::Disassembly &disassembly) {
|
static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||||
std::set<uint16_t> rom_functions = {
|
const std::set<uint16_t> rom_functions = {
|
||||||
0x0238, 0x023b, 0x023e, 0x0241, 0x0244, 0x0247,
|
0x0238, 0x023b, 0x023e, 0x0241, 0x0244, 0x0247,
|
||||||
0xc3c6, 0xc3f4, 0xc444, 0xc47c, 0xc4a8, 0xc4d3, 0xc4e0, 0xc524, 0xc55f, 0xc592, 0xc5e8, 0xc5fa, 0xc692, 0xc6b3, 0xc6ee, 0xc70d,
|
0xc3c6, 0xc3f4, 0xc444, 0xc47c, 0xc4a8, 0xc4d3, 0xc4e0, 0xc524, 0xc55f, 0xc592, 0xc5e8, 0xc5fa, 0xc692, 0xc6b3, 0xc6ee, 0xc70d,
|
||||||
0xc748, 0xc7fd, 0xc809, 0xc816, 0xc82f, 0xc855, 0xc8c1, 0xc915, 0xc952, 0xc971, 0xc973, 0xc9a0, 0xc9bd, 0xc9c8, 0xc9e5, 0xca12,
|
0xc748, 0xc7fd, 0xc809, 0xc816, 0xc82f, 0xc855, 0xc8c1, 0xc915, 0xc952, 0xc971, 0xc973, 0xc9a0, 0xc9bd, 0xc9c8, 0xc9e5, 0xca12,
|
||||||
@@ -66,30 +72,51 @@ static int Basic11Score(const StaticAnalyser::MOS6502::Disassembly &disassembly)
|
|||||||
0xf88f, 0xf8af, 0xf8b5, 0xf920, 0xf967, 0xf9aa, 0xf9c9, 0xfa14, 0xfa9f, 0xfab5, 0xfacb, 0xfae1, 0xfb14, 0xfb2a, 0xfb40, 0xfbd0,
|
0xf88f, 0xf8af, 0xf8b5, 0xf920, 0xf967, 0xf9aa, 0xf9c9, 0xfa14, 0xfa9f, 0xfab5, 0xfacb, 0xfae1, 0xfb14, 0xfb2a, 0xfb40, 0xfbd0,
|
||||||
0xfc18
|
0xfc18
|
||||||
};
|
};
|
||||||
std::set<uint16_t> variable_locations = {
|
const std::set<uint16_t> variable_locations = {
|
||||||
0x0244, 0x0245, 0x0246, 0x0247, 0x0248, 0x0249, 0x024a, 0x024b, 0x024c
|
0x0244, 0x0245, 0x0246, 0x0247, 0x0248, 0x0249, 0x024a, 0x024b, 0x024c
|
||||||
};
|
};
|
||||||
|
|
||||||
return Score(disassembly, rom_functions, variable_locations);
|
return Score(disassembly, rom_functions, variable_locations);
|
||||||
}
|
}
|
||||||
|
|
||||||
void StaticAnalyser::Oric::AddTargets(const Media &media, std::list<Target> &destination) {
|
static bool IsMicrodisc(Storage::Encodings::MFM::Parser &parser) {
|
||||||
Target target;
|
/*
|
||||||
target.machine = Target::Oric;
|
The Microdisc boot sector is sector 2 of track 0 and contains a 23-byte signature.
|
||||||
target.probability = 1.0;
|
*/
|
||||||
|
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 2);
|
||||||
|
if(!sector) return false;
|
||||||
|
if(sector->samples.empty()) return false;
|
||||||
|
|
||||||
|
const std::vector<uint8_t> &first_sample = sector->samples[0];
|
||||||
|
if(first_sample.size() != 256) return false;
|
||||||
|
|
||||||
|
const uint8_t signature[] = {
|
||||||
|
0x00, 0x00, 0xFF, 0x00, 0xD0, 0x9F, 0xD0,
|
||||||
|
0x9F, 0x02, 0xB9, 0x01, 0x00, 0xFF, 0x00,
|
||||||
|
0x00, 0xB9, 0xE4, 0xB9, 0x00, 0x00, 0xE6,
|
||||||
|
0x12, 0x00
|
||||||
|
};
|
||||||
|
|
||||||
|
return !std::memcmp(signature, first_sample.data(), sizeof(signature));
|
||||||
|
}
|
||||||
|
|
||||||
|
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||||
|
std::unique_ptr<Target> target(new Target);
|
||||||
|
target->machine = Machine::Oric;
|
||||||
|
target->confidence = 0.5;
|
||||||
|
|
||||||
int basic10_votes = 0;
|
int basic10_votes = 0;
|
||||||
int basic11_votes = 0;
|
int basic11_votes = 0;
|
||||||
|
|
||||||
for(auto &tape : media.tapes) {
|
for(auto &tape : media.tapes) {
|
||||||
std::list<File> tape_files = GetFiles(tape);
|
std::vector<File> tape_files = GetFiles(tape);
|
||||||
tape->reset();
|
tape->reset();
|
||||||
if(tape_files.size()) {
|
if(tape_files.size()) {
|
||||||
for(auto file : tape_files) {
|
for(const auto &file : tape_files) {
|
||||||
if(file.data_type == File::MachineCode) {
|
if(file.data_type == File::MachineCode) {
|
||||||
std::vector<uint16_t> entry_points = {file.starting_address};
|
std::vector<uint16_t> entry_points = {file.starting_address};
|
||||||
StaticAnalyser::MOS6502::Disassembly disassembly =
|
Analyser::Static::MOS6502::Disassembly disassembly =
|
||||||
StaticAnalyser::MOS6502::Disassemble(file.data, StaticAnalyser::Disassembler::OffsetMapper(file.starting_address), entry_points);
|
Analyser::Static::MOS6502::Disassemble(file.data, Analyser::Static::Disassembler::OffsetMapper(file.starting_address), entry_points);
|
||||||
|
|
||||||
int basic10_score = Basic10Score(disassembly);
|
int basic10_score = Basic10Score(disassembly);
|
||||||
int basic11_score = Basic11Score(disassembly);
|
int basic11_score = Basic11Score(disassembly);
|
||||||
@@ -97,23 +124,32 @@ void StaticAnalyser::Oric::AddTargets(const Media &media, std::list<Target> &des
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
target.media.tapes.push_back(tape);
|
target->media.tapes.push_back(tape);
|
||||||
target.loading_command = "CLOAD\"\"\n";
|
target->loading_command = "CLOAD\"\"\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// trust that any disk supplied can be handled by the Microdisc. TODO: check.
|
|
||||||
if(!media.disks.empty()) {
|
if(!media.disks.empty()) {
|
||||||
target.oric.has_microdisc = true;
|
// Only the Microdisc is emulated right now, so accept only disks that it can boot from.
|
||||||
target.media.disks = media.disks;
|
for(auto &disk: media.disks) {
|
||||||
} else {
|
Storage::Encodings::MFM::Parser parser(true, disk);
|
||||||
target.oric.has_microdisc = false;
|
if(IsMicrodisc(parser)) {
|
||||||
|
target->disk_interface = Target::DiskInterface::Microdisc;
|
||||||
|
target->media.disks.push_back(disk);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
target->disk_interface = Target::DiskInterface::None;
|
||||||
|
|
||||||
// TODO: really this should add two targets if not all votes agree
|
// TODO: really this should add two targets if not all votes agree
|
||||||
target.oric.use_atmos_rom = basic11_votes >= basic10_votes;
|
if(basic11_votes >= basic10_votes || target->disk_interface == Target::DiskInterface::Microdisc)
|
||||||
if(target.oric.has_microdisc) target.oric.use_atmos_rom = true;
|
target->rom = Target::ROM::BASIC11;
|
||||||
|
else
|
||||||
|
target->rom = Target::ROM::BASIC10;
|
||||||
|
|
||||||
if(target.media.tapes.size() || target.media.disks.size() || target.media.cartridges.size())
|
TargetList targets;
|
||||||
destination.push_back(target);
|
if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size())
|
||||||
|
targets.push_back(std::move(target));
|
||||||
|
return targets;
|
||||||
}
|
}
|
||||||
26
Analyser/Static/Oric/StaticAnalyser.hpp
Normal file
26
Analyser/Static/Oric/StaticAnalyser.hpp
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
//
|
||||||
|
// StaticAnalyser.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 11/10/2016.
|
||||||
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef StaticAnalyser_Oric_StaticAnalyser_hpp
|
||||||
|
#define StaticAnalyser_Oric_StaticAnalyser_hpp
|
||||||
|
|
||||||
|
#include "../StaticAnalyser.hpp"
|
||||||
|
#include "../../../Storage/TargetPlatforms.hpp"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
|
namespace Oric {
|
||||||
|
|
||||||
|
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* StaticAnalyser_hpp */
|
||||||
@@ -3,16 +3,16 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 06/11/2016.
|
// Created by Thomas Harte on 06/11/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "Tape.hpp"
|
#include "Tape.hpp"
|
||||||
#include "../../Storage/Tape/Parsers/Oric.hpp"
|
#include "../../../Storage/Tape/Parsers/Oric.hpp"
|
||||||
|
|
||||||
using namespace StaticAnalyser::Oric;
|
using namespace Analyser::Static::Oric;
|
||||||
|
|
||||||
std::list<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||||
std::list<File> files;
|
std::vector<File> files;
|
||||||
Storage::Tape::Oric::Parser parser;
|
Storage::Tape::Oric::Parser parser;
|
||||||
|
|
||||||
while(!tape->is_at_end()) {
|
while(!tape->is_at_end()) {
|
||||||
@@ -3,18 +3,19 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 06/11/2016.
|
// Created by Thomas Harte on 06/11/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef StaticAnalyser_Oric_Tape_hpp
|
#ifndef StaticAnalyser_Oric_Tape_hpp
|
||||||
#define StaticAnalyser_Oric_Tape_hpp
|
#define StaticAnalyser_Oric_Tape_hpp
|
||||||
|
|
||||||
#include "../../Storage/Tape/Tape.hpp"
|
#include "../../../Storage/Tape/Tape.hpp"
|
||||||
#include <list>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace StaticAnalyser {
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
namespace Oric {
|
namespace Oric {
|
||||||
|
|
||||||
struct File {
|
struct File {
|
||||||
@@ -30,8 +31,9 @@ struct File {
|
|||||||
std::vector<uint8_t> data;
|
std::vector<uint8_t> data;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::list<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
41
Analyser/Static/Oric/Target.hpp
Normal file
41
Analyser/Static/Oric/Target.hpp
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
//
|
||||||
|
// Target.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 09/03/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Analyser_Static_Oric_Target_h
|
||||||
|
#define Analyser_Static_Oric_Target_h
|
||||||
|
|
||||||
|
#include "../StaticAnalyser.hpp"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
|
namespace Oric {
|
||||||
|
|
||||||
|
struct Target: public ::Analyser::Static::Target {
|
||||||
|
enum class ROM {
|
||||||
|
BASIC10,
|
||||||
|
BASIC11,
|
||||||
|
Pravetz
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class DiskInterface {
|
||||||
|
Microdisc,
|
||||||
|
Pravetz,
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
ROM rom = ROM::BASIC11;
|
||||||
|
DiskInterface disk_interface = DiskInterface::None;
|
||||||
|
std::string loading_command;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* Analyser_Static_Oric_Target_h */
|
||||||
193
Analyser/Static/StaticAnalyser.cpp
Normal file
193
Analyser/Static/StaticAnalyser.cpp
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
//
|
||||||
|
// StaticAnalyser.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 23/08/2016.
|
||||||
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "StaticAnalyser.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iterator>
|
||||||
|
|
||||||
|
// Analysers
|
||||||
|
#include "Acorn/StaticAnalyser.hpp"
|
||||||
|
#include "AmstradCPC/StaticAnalyser.hpp"
|
||||||
|
#include "AppleII/StaticAnalyser.hpp"
|
||||||
|
#include "Atari/StaticAnalyser.hpp"
|
||||||
|
#include "Coleco/StaticAnalyser.hpp"
|
||||||
|
#include "Commodore/StaticAnalyser.hpp"
|
||||||
|
#include "DiskII/StaticAnalyser.hpp"
|
||||||
|
#include "MSX/StaticAnalyser.hpp"
|
||||||
|
#include "Oric/StaticAnalyser.hpp"
|
||||||
|
#include "ZX8081/StaticAnalyser.hpp"
|
||||||
|
|
||||||
|
// Cartridges
|
||||||
|
#include "../../Storage/Cartridge/Formats/BinaryDump.hpp"
|
||||||
|
#include "../../Storage/Cartridge/Formats/PRG.hpp"
|
||||||
|
|
||||||
|
// Disks
|
||||||
|
#include "../../Storage/Disk/DiskImage/Formats/AcornADF.hpp"
|
||||||
|
#include "../../Storage/Disk/DiskImage/Formats/AppleDSK.hpp"
|
||||||
|
#include "../../Storage/Disk/DiskImage/Formats/CPCDSK.hpp"
|
||||||
|
#include "../../Storage/Disk/DiskImage/Formats/D64.hpp"
|
||||||
|
#include "../../Storage/Disk/DiskImage/Formats/G64.hpp"
|
||||||
|
#include "../../Storage/Disk/DiskImage/Formats/DMK.hpp"
|
||||||
|
#include "../../Storage/Disk/DiskImage/Formats/HFE.hpp"
|
||||||
|
#include "../../Storage/Disk/DiskImage/Formats/MSXDSK.hpp"
|
||||||
|
#include "../../Storage/Disk/DiskImage/Formats/NIB.hpp"
|
||||||
|
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
|
||||||
|
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
|
||||||
|
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
|
||||||
|
|
||||||
|
// Tapes
|
||||||
|
#include "../../Storage/Tape/Formats/CAS.hpp"
|
||||||
|
#include "../../Storage/Tape/Formats/CommodoreTAP.hpp"
|
||||||
|
#include "../../Storage/Tape/Formats/CSW.hpp"
|
||||||
|
#include "../../Storage/Tape/Formats/OricTAP.hpp"
|
||||||
|
#include "../../Storage/Tape/Formats/TapePRG.hpp"
|
||||||
|
#include "../../Storage/Tape/Formats/TapeUEF.hpp"
|
||||||
|
#include "../../Storage/Tape/Formats/TZX.hpp"
|
||||||
|
#include "../../Storage/Tape/Formats/ZX80O81P.hpp"
|
||||||
|
|
||||||
|
// Target Platform Types
|
||||||
|
#include "../../Storage/TargetPlatforms.hpp"
|
||||||
|
|
||||||
|
using namespace Analyser::Static;
|
||||||
|
|
||||||
|
static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) {
|
||||||
|
Media result;
|
||||||
|
|
||||||
|
// Get the extension, if any; it will be assumed that extensions are reliable, so an extension is a broad-phase
|
||||||
|
// test as to file format.
|
||||||
|
std::string::size_type final_dot = file_name.find_last_of(".");
|
||||||
|
if(final_dot == std::string::npos) return result;
|
||||||
|
std::string extension = file_name.substr(final_dot + 1);
|
||||||
|
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
|
||||||
|
|
||||||
|
#define Insert(list, class, platforms) \
|
||||||
|
list.emplace_back(new Storage::class(file_name));\
|
||||||
|
potential_platforms |= platforms;\
|
||||||
|
TargetPlatform::TypeDistinguisher *distinguisher = dynamic_cast<TargetPlatform::TypeDistinguisher *>(list.back().get());\
|
||||||
|
if(distinguisher) potential_platforms &= distinguisher->target_platform_type();
|
||||||
|
|
||||||
|
#define TryInsert(list, class, platforms) \
|
||||||
|
try {\
|
||||||
|
Insert(list, class, platforms) \
|
||||||
|
} catch(...) {}
|
||||||
|
|
||||||
|
#define Format(ext, list, class, platforms) \
|
||||||
|
if(extension == ext) { \
|
||||||
|
TryInsert(list, class, platforms) \
|
||||||
|
}
|
||||||
|
|
||||||
|
Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80
|
||||||
|
Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
|
||||||
|
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
|
||||||
|
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF
|
||||||
|
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN
|
||||||
|
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
|
||||||
|
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
|
||||||
|
Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision) // COL
|
||||||
|
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
|
||||||
|
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
|
||||||
|
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
|
||||||
|
Format("do", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DO
|
||||||
|
Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD
|
||||||
|
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC) // DSK (Amstrad CPC)
|
||||||
|
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DSK (Apple)
|
||||||
|
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX) // DSK (MSX)
|
||||||
|
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
|
||||||
|
Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore) // G64
|
||||||
|
Format( "hfe",
|
||||||
|
result.disks,
|
||||||
|
Disk::DiskImageHolder<Storage::Disk::HFE>,
|
||||||
|
TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric)
|
||||||
|
// HFE (TODO: switch to AllDisk once the MSX stops being so greedy)
|
||||||
|
Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII) // NIB
|
||||||
|
Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
|
||||||
|
Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P
|
||||||
|
Format("po", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // PO
|
||||||
|
Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P81
|
||||||
|
|
||||||
|
// PRG
|
||||||
|
if(extension == "prg") {
|
||||||
|
// try instantiating as a ROM; failing that accept as a tape
|
||||||
|
try {
|
||||||
|
Insert(result.cartridges, Cartridge::PRG, TargetPlatform::Commodore)
|
||||||
|
} catch(...) {
|
||||||
|
try {
|
||||||
|
Insert(result.tapes, Tape::PRG, TargetPlatform::Commodore)
|
||||||
|
} catch(...) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Format( "rom",
|
||||||
|
result.cartridges,
|
||||||
|
Cartridge::BinaryDump,
|
||||||
|
TargetPlatform::AcornElectron | TargetPlatform::ColecoVision | TargetPlatform::MSX) // ROM
|
||||||
|
Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD
|
||||||
|
Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore)
|
||||||
|
Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric)
|
||||||
|
Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX
|
||||||
|
Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081) // TZX
|
||||||
|
Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape)
|
||||||
|
Format("woz", result.disks, Disk::DiskImageHolder<Storage::Disk::WOZ>, TargetPlatform::DiskII) // WOZ
|
||||||
|
|
||||||
|
#undef Format
|
||||||
|
#undef Insert
|
||||||
|
#undef TryInsert
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Media Analyser::Static::GetMedia(const std::string &file_name) {
|
||||||
|
TargetPlatform::IntType throwaway;
|
||||||
|
return GetMediaAndPlatforms(file_name, throwaway);
|
||||||
|
}
|
||||||
|
|
||||||
|
TargetList Analyser::Static::GetTargets(const std::string &file_name) {
|
||||||
|
TargetList targets;
|
||||||
|
|
||||||
|
// Collect all disks, tapes and ROMs as can be extrapolated from this file, forming the
|
||||||
|
// union of all platforms this file might be a target for.
|
||||||
|
TargetPlatform::IntType potential_platforms = 0;
|
||||||
|
Media media = GetMediaAndPlatforms(file_name, potential_platforms);
|
||||||
|
|
||||||
|
// Hand off to platform-specific determination of whether these things are actually compatible and,
|
||||||
|
// if so, how to load them.
|
||||||
|
#define Append(x) {\
|
||||||
|
auto new_targets = x::GetTargets(media, file_name, potential_platforms);\
|
||||||
|
std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));\
|
||||||
|
}
|
||||||
|
if(potential_platforms & TargetPlatform::Acorn) Append(Acorn);
|
||||||
|
if(potential_platforms & TargetPlatform::AmstradCPC) Append(AmstradCPC);
|
||||||
|
if(potential_platforms & TargetPlatform::AppleII) Append(AppleII);
|
||||||
|
if(potential_platforms & TargetPlatform::Atari2600) Append(Atari);
|
||||||
|
if(potential_platforms & TargetPlatform::ColecoVision) Append(Coleco);
|
||||||
|
if(potential_platforms & TargetPlatform::Commodore) Append(Commodore);
|
||||||
|
if(potential_platforms & TargetPlatform::DiskII) Append(DiskII);
|
||||||
|
if(potential_platforms & TargetPlatform::MSX) Append(MSX);
|
||||||
|
if(potential_platforms & TargetPlatform::Oric) Append(Oric);
|
||||||
|
if(potential_platforms & TargetPlatform::ZX8081) Append(ZX8081);
|
||||||
|
#undef Append
|
||||||
|
|
||||||
|
// Reset any tapes to their initial position
|
||||||
|
for(const auto &target : targets) {
|
||||||
|
for(auto &tape : target->media.tapes) {
|
||||||
|
tape->reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by initial confidence. Use a stable sort in case any of the machine-specific analysers
|
||||||
|
// picked their insertion order carefully.
|
||||||
|
std::stable_sort(targets.begin(), targets.end(),
|
||||||
|
[] (const std::unique_ptr<Target> &a, const std::unique_ptr<Target> &b) {
|
||||||
|
return a->confidence > b->confidence;
|
||||||
|
});
|
||||||
|
|
||||||
|
return targets;
|
||||||
|
}
|
||||||
66
Analyser/Static/StaticAnalyser.hpp
Normal file
66
Analyser/Static/StaticAnalyser.hpp
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
//
|
||||||
|
// StaticAnalyser.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 23/08/2016.
|
||||||
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef StaticAnalyser_hpp
|
||||||
|
#define StaticAnalyser_hpp
|
||||||
|
|
||||||
|
#include "../Machines.hpp"
|
||||||
|
|
||||||
|
#include "../../Storage/Tape/Tape.hpp"
|
||||||
|
#include "../../Storage/Disk/Disk.hpp"
|
||||||
|
#include "../../Storage/Cartridge/Cartridge.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
A list of disks, tapes and cartridges.
|
||||||
|
*/
|
||||||
|
struct Media {
|
||||||
|
std::vector<std::shared_ptr<Storage::Disk::Disk>> disks;
|
||||||
|
std::vector<std::shared_ptr<Storage::Tape::Tape>> tapes;
|
||||||
|
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> cartridges;
|
||||||
|
|
||||||
|
bool empty() const {
|
||||||
|
return disks.empty() && tapes.empty() && cartridges.empty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
A list of disks, tapes and cartridges plus information about the machine to which to attach them and its configuration,
|
||||||
|
and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness.
|
||||||
|
*/
|
||||||
|
struct Target {
|
||||||
|
virtual ~Target() {}
|
||||||
|
|
||||||
|
Machine machine;
|
||||||
|
Media media;
|
||||||
|
float confidence;
|
||||||
|
};
|
||||||
|
typedef std::vector<std::unique_ptr<Target>> TargetList;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Attempts, through any available means, to return a list of potential targets for the file with the given name.
|
||||||
|
|
||||||
|
@returns The list of potential targets, sorted from most to least probable.
|
||||||
|
*/
|
||||||
|
TargetList GetTargets(const std::string &file_name);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Inspects the supplied file and determines the media included.
|
||||||
|
*/
|
||||||
|
Media GetMedia(const std::string &file_name);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* StaticAnalyser_hpp */
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 04/06/2017.
|
// Created by Thomas Harte on 04/06/2017.
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "StaticAnalyser.hpp"
|
#include "StaticAnalyser.hpp"
|
||||||
@@ -11,7 +11,8 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "../../Storage/Tape/Parsers/ZX8081.hpp"
|
#include "Target.hpp"
|
||||||
|
#include "../../../Storage/Tape/Parsers/ZX8081.hpp"
|
||||||
|
|
||||||
static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||||
std::vector<Storage::Data::ZX8081::File> files;
|
std::vector<Storage::Data::ZX8081::File> files;
|
||||||
@@ -27,41 +28,43 @@ static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<S
|
|||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
void StaticAnalyser::ZX8081::AddTargets(const Media &media, std::list<Target> &destination, TargetPlatform::IntType potential_platforms) {
|
Analyser::Static::TargetList Analyser::Static::ZX8081::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||||
|
TargetList destination;
|
||||||
if(!media.tapes.empty()) {
|
if(!media.tapes.empty()) {
|
||||||
std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front());
|
std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front());
|
||||||
media.tapes.front()->reset();
|
media.tapes.front()->reset();
|
||||||
if(!files.empty()) {
|
if(!files.empty()) {
|
||||||
StaticAnalyser::Target target;
|
Target *target = new Target;
|
||||||
target.machine = Target::ZX8081;
|
destination.push_back(std::unique_ptr<::Analyser::Static::Target>(target));
|
||||||
|
target->machine = Machine::ZX8081;
|
||||||
|
|
||||||
// Guess the machine type from the file only if it isn't already known.
|
// Guess the machine type from the file only if it isn't already known.
|
||||||
switch(potential_platforms & (TargetPlatform::ZX80 | TargetPlatform::ZX81)) {
|
switch(potential_platforms & (TargetPlatform::ZX80 | TargetPlatform::ZX81)) {
|
||||||
default:
|
default:
|
||||||
target.zx8081.isZX81 = files.front().isZX81;
|
target->is_ZX81 = files.front().isZX81;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TargetPlatform::ZX80: target.zx8081.isZX81 = false; break;
|
case TargetPlatform::ZX80: target->is_ZX81 = false; break;
|
||||||
case TargetPlatform::ZX81: target.zx8081.isZX81 = true; break;
|
case TargetPlatform::ZX81: target->is_ZX81 = true; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*if(files.front().data.size() > 16384) {
|
/*if(files.front().data.size() > 16384) {
|
||||||
target.zx8081.memory_model = ZX8081MemoryModel::SixtyFourKB;
|
target->zx8081.memory_model = ZX8081MemoryModel::SixtyFourKB;
|
||||||
} else*/ if(files.front().data.size() > 1024) {
|
} else*/ if(files.front().data.size() > 1024) {
|
||||||
target.zx8081.memory_model = ZX8081MemoryModel::SixteenKB;
|
target->memory_model = Target::MemoryModel::SixteenKB;
|
||||||
} else {
|
} else {
|
||||||
target.zx8081.memory_model = ZX8081MemoryModel::Unexpanded;
|
target->memory_model = Target::MemoryModel::Unexpanded;
|
||||||
}
|
}
|
||||||
target.media.tapes = media.tapes;
|
target->media.tapes = media.tapes;
|
||||||
|
|
||||||
// TODO: how to run software once loaded? Might require a BASIC detokeniser.
|
// TODO: how to run software once loaded? Might require a BASIC detokeniser.
|
||||||
if(target.zx8081.isZX81) {
|
if(target->is_ZX81) {
|
||||||
target.loading_command = "J\"\"\n";
|
target->loading_command = "J\"\"\n";
|
||||||
} else {
|
} else {
|
||||||
target.loading_command = "W\n";
|
target->loading_command = "W\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
destination.push_back(target);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return destination;
|
||||||
}
|
}
|
||||||
26
Analyser/Static/ZX8081/StaticAnalyser.hpp
Normal file
26
Analyser/Static/ZX8081/StaticAnalyser.hpp
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
//
|
||||||
|
// StaticAnalyser.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 04/06/2017.
|
||||||
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Analyser_Static_ZX8081_StaticAnalyser_hpp
|
||||||
|
#define Analyser_Static_ZX8081_StaticAnalyser_hpp
|
||||||
|
|
||||||
|
#include "../StaticAnalyser.hpp"
|
||||||
|
#include "../../../Storage/TargetPlatforms.hpp"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
|
namespace ZX8081 {
|
||||||
|
|
||||||
|
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* StaticAnalyser_hpp */
|
||||||
36
Analyser/Static/ZX8081/Target.hpp
Normal file
36
Analyser/Static/ZX8081/Target.hpp
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
//
|
||||||
|
// Target.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 09/03/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Analyser_Static_ZX8081_Target_h
|
||||||
|
#define Analyser_Static_ZX8081_Target_h
|
||||||
|
|
||||||
|
#include "../StaticAnalyser.hpp"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Analyser {
|
||||||
|
namespace Static {
|
||||||
|
namespace ZX8081 {
|
||||||
|
|
||||||
|
struct Target: public ::Analyser::Static::Target {
|
||||||
|
enum class MemoryModel {
|
||||||
|
Unexpanded,
|
||||||
|
SixteenKB,
|
||||||
|
SixtyFourKB
|
||||||
|
};
|
||||||
|
|
||||||
|
MemoryModel memory_model = MemoryModel::Unexpanded;
|
||||||
|
bool is_ZX81 = false;
|
||||||
|
bool ZX80_uses_ZX81_ROM = false;
|
||||||
|
std::string loading_command;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* Analyser_Static_ZX8081_Target_h */
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 22/07/2017.
|
// Created by Thomas Harte on 22/07/2017.
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef ClockReceiver_hpp
|
#ifndef ClockReceiver_hpp
|
||||||
@@ -121,7 +121,7 @@ template <class T> class WrappedInt {
|
|||||||
inline int as_int() const { return length_; }
|
inline int as_int() const { return length_; }
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Severs from @c this the effect of dividing by @c divisor — @c this will end up with
|
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
|
||||||
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
|
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
|
||||||
*/
|
*/
|
||||||
inline T divide(const T &divisor) {
|
inline T divide(const T &divisor) {
|
||||||
@@ -147,7 +147,7 @@ template <class T> class WrappedInt {
|
|||||||
int length_;
|
int length_;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Describes an integer number of whole cycles — pairs of clock signal transitions.
|
/// Describes an integer number of whole cycles: pairs of clock signal transitions.
|
||||||
class Cycles: public WrappedInt<Cycles> {
|
class Cycles: public WrappedInt<Cycles> {
|
||||||
public:
|
public:
|
||||||
inline Cycles(int l) : WrappedInt<Cycles>(l) {}
|
inline Cycles(int l) : WrappedInt<Cycles>(l) {}
|
||||||
@@ -155,7 +155,7 @@ class Cycles: public WrappedInt<Cycles> {
|
|||||||
inline Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {}
|
inline Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Describes an integer number of half cycles — single clock signal transitions.
|
/// Describes an integer number of half cycles: single clock signal transitions.
|
||||||
class HalfCycles: public WrappedInt<HalfCycles> {
|
class HalfCycles: public WrappedInt<HalfCycles> {
|
||||||
public:
|
public:
|
||||||
inline HalfCycles(int l) : WrappedInt<HalfCycles>(l) {}
|
inline HalfCycles(int l) : WrappedInt<HalfCycles>(l) {}
|
||||||
@@ -184,7 +184,7 @@ class HalfCycles: public WrappedInt<HalfCycles> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Severs from @c this the effect of dividing by @c divisor — @c this will end up with
|
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
|
||||||
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
|
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
|
||||||
*/
|
*/
|
||||||
inline Cycles divide_cycles(const Cycles &divisor) {
|
inline Cycles divide_cycles(const Cycles &divisor) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 01/08/2017.
|
// Created by Thomas Harte on 01/08/2017.
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef ForceInline_hpp
|
#ifndef ForceInline_hpp
|
||||||
|
|||||||
@@ -3,14 +3,14 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 20/08/2017.
|
// Created by Thomas Harte on 20/08/2017.
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef Sleeper_hpp
|
#ifndef Sleeper_hpp
|
||||||
#define Sleeper_hpp
|
#define Sleeper_hpp
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
A sleeper is any component that sometimes requires a clock but at other times is 'asleep' — i.e. is not doing
|
A sleeper is any component that sometimes requires a clock but at other times is 'asleep', i.e. is not doing
|
||||||
any clock-derived work, so needn't receive a clock. A disk controller is an archetypal example.
|
any clock-derived work, so needn't receive a clock. A disk controller is an archetypal example.
|
||||||
|
|
||||||
A sleeper will signal sleeps and wakes to an observer.
|
A sleeper will signal sleeps and wakes to an observer.
|
||||||
@@ -30,7 +30,7 @@ class Sleeper {
|
|||||||
class SleepObserver {
|
class SleepObserver {
|
||||||
public:
|
public:
|
||||||
/// Called to inform an observer that the component @c component has either gone to sleep or become awake.
|
/// Called to inform an observer that the component @c component has either gone to sleep or become awake.
|
||||||
virtual void set_component_is_sleeping(void *component, bool is_sleeping) = 0;
|
virtual void set_component_is_sleeping(Sleeper *component, bool is_sleeping) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Registers @c observer as the new sleep observer;
|
/// Registers @c observer as the new sleep observer;
|
||||||
|
|||||||
18
ClockReceiver/TimeTypes.hpp
Normal file
18
ClockReceiver/TimeTypes.hpp
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// TimeTypes.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 21/03/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef TimeTypes_h
|
||||||
|
#define TimeTypes_h
|
||||||
|
|
||||||
|
namespace Time {
|
||||||
|
|
||||||
|
typedef double Seconds;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* TimeTypes_h */
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 17/09/2016.
|
// Created by Thomas Harte on 17/09/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "1770.hpp"
|
#include "1770.hpp"
|
||||||
@@ -282,7 +282,7 @@ void WD1770::posit_event(int new_event_type) {
|
|||||||
track_ = 0;
|
track_ = 0;
|
||||||
goto verify;
|
goto verify;
|
||||||
}
|
}
|
||||||
get_drive().step(step_direction_ ? 1 : -1);
|
get_drive().step(Storage::Disk::HeadPosition(step_direction_ ? 1 : -1));
|
||||||
unsigned int time_to_wait;
|
unsigned int time_to_wait;
|
||||||
switch(command_ & 3) {
|
switch(command_ & 3) {
|
||||||
default:
|
default:
|
||||||
@@ -522,7 +522,7 @@ void WD1770::posit_event(int new_event_type) {
|
|||||||
type2_write_loop:
|
type2_write_loop:
|
||||||
/*
|
/*
|
||||||
This deviates from the data sheet slightly since that would prima facie request one more byte
|
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
|
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
|
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
|
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
|
natural expectations and the way that emulated machines responded, I believe that to be a
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 17/09/2016.
|
// Created by Thomas Harte on 17/09/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef _770_hpp
|
#ifndef _770_hpp
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 06/06/2016.
|
// Created by Thomas Harte on 06/06/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef _522_hpp
|
#ifndef _522_hpp
|
||||||
@@ -113,6 +113,9 @@ template <class T> class MOS6522: public MOS6522Base {
|
|||||||
/*! Gets a register value. */
|
/*! Gets a register value. */
|
||||||
uint8_t get_register(int address);
|
uint8_t get_register(int address);
|
||||||
|
|
||||||
|
/*! @returns the bus handler. */
|
||||||
|
T &bus_handler();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
T &bus_handler_;
|
T &bus_handler_;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 04/09/2017.
|
// Created by Thomas Harte on 04/09/2017.
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "../6522.hpp"
|
#include "../6522.hpp"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 04/09/2017.
|
// Created by Thomas Harte on 04/09/2017.
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) {
|
template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) {
|
||||||
@@ -145,6 +145,10 @@ template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t outp
|
|||||||
return (input & ~output_mask) | (output & output_mask);
|
return (input & ~output_mask) | (output & output_mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T> T &MOS6522<T>::bus_handler() {
|
||||||
|
return bus_handler_;
|
||||||
|
}
|
||||||
|
|
||||||
// Delegate and communications
|
// Delegate and communications
|
||||||
template <typename T> void MOS6522<T>::reevaluate_interrupts() {
|
template <typename T> void MOS6522<T>::reevaluate_interrupts() {
|
||||||
bool new_interrupt_status = get_interrupt_line();
|
bool new_interrupt_status = get_interrupt_line();
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 04/09/2017.
|
// Created by Thomas Harte on 04/09/2017.
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef _522Storage_hpp
|
#ifndef _522Storage_hpp
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 04/09/2017.
|
// Created by Thomas Harte on 04/09/2017.
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "../6522.hpp"
|
#include "../6522.hpp"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 19/06/2016.
|
// Created by Thomas Harte on 19/06/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef _532_hpp
|
#ifndef _532_hpp
|
||||||
|
|||||||
@@ -3,14 +3,14 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 05/06/2016.
|
// Created by Thomas Harte on 05/06/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "6560.hpp"
|
#include "6560.hpp"
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
using namespace MOS;
|
using namespace MOS::MOS6560;
|
||||||
|
|
||||||
AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
|
AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
|
||||||
audio_queue_(audio_queue) {}
|
audio_queue_(audio_queue) {}
|
||||||
@@ -18,7 +18,7 @@ AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue
|
|||||||
|
|
||||||
void AudioGenerator::set_volume(uint8_t volume) {
|
void AudioGenerator::set_volume(uint8_t volume) {
|
||||||
audio_queue_.defer([=]() {
|
audio_queue_.defer([=]() {
|
||||||
volume_ = volume;
|
volume_ = static_cast<int16_t>(volume) * range_multiplier_;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ static uint8_t noise_pattern[] = {
|
|||||||
// means every second cycle, etc.
|
// means every second cycle, etc.
|
||||||
|
|
||||||
void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target) {
|
void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||||
for(unsigned int c = 0; c < number_of_samples; c++) {
|
for(unsigned int c = 0; c < number_of_samples; ++c) {
|
||||||
update(0, 2, shift);
|
update(0, 2, shift);
|
||||||
update(1, 1, shift);
|
update(1, 1, shift);
|
||||||
update(2, 0, shift);
|
update(2, 0, shift);
|
||||||
@@ -114,17 +114,17 @@ void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target)
|
|||||||
|
|
||||||
// this sums the output of all three sounds channels plus a DC offset for volume;
|
// this sums the output of all three sounds channels plus a DC offset for volume;
|
||||||
// TODO: what's the real ratio of this stuff?
|
// TODO: what's the real ratio of this stuff?
|
||||||
target[c] = (
|
target[c] = static_cast<int16_t>(
|
||||||
(shift_registers_[0]&1) +
|
(shift_registers_[0]&1) +
|
||||||
(shift_registers_[1]&1) +
|
(shift_registers_[1]&1) +
|
||||||
(shift_registers_[2]&1) +
|
(shift_registers_[2]&1) +
|
||||||
((noise_pattern[shift_registers_[3] >> 3] >> (shift_registers_[3]&7))&(control_registers_[3] >> 7)&1)
|
((noise_pattern[shift_registers_[3] >> 3] >> (shift_registers_[3]&7))&(control_registers_[3] >> 7)&1)
|
||||||
) * volume_ * 700 + volume_ * 44;
|
) * volume_ + (volume_ >> 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioGenerator::skip_samples(std::size_t number_of_samples) {
|
void AudioGenerator::skip_samples(std::size_t number_of_samples) {
|
||||||
for(unsigned int c = 0; c < number_of_samples; c++) {
|
for(unsigned int c = 0; c < number_of_samples; ++c) {
|
||||||
update(0, 2, shift);
|
update(0, 2, shift);
|
||||||
update(1, 1, shift);
|
update(1, 1, shift);
|
||||||
update(2, 0, shift);
|
update(2, 0, shift);
|
||||||
@@ -132,6 +132,10 @@ void AudioGenerator::skip_samples(std::size_t number_of_samples) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AudioGenerator::set_sample_volume_range(std::int16_t range) {
|
||||||
|
range_multiplier_ = static_cast<int16_t>(range / 64);
|
||||||
|
}
|
||||||
|
|
||||||
#undef shift
|
#undef shift
|
||||||
#undef increment
|
#undef increment
|
||||||
#undef update
|
#undef update
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 05/06/2016.
|
// Created by Thomas Harte on 05/06/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef _560_hpp
|
#ifndef _560_hpp
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||||
|
|
||||||
namespace MOS {
|
namespace MOS {
|
||||||
|
namespace MOS6560 {
|
||||||
|
|
||||||
// audio state
|
// audio state
|
||||||
class AudioGenerator: public ::Outputs::Speaker::SampleSource {
|
class AudioGenerator: public ::Outputs::Speaker::SampleSource {
|
||||||
@@ -25,8 +26,10 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource {
|
|||||||
void set_volume(uint8_t volume);
|
void set_volume(uint8_t volume);
|
||||||
void set_control(int channel, uint8_t value);
|
void set_control(int channel, uint8_t value);
|
||||||
|
|
||||||
|
// For ::SampleSource.
|
||||||
void get_samples(std::size_t number_of_samples, int16_t *target);
|
void get_samples(std::size_t number_of_samples, int16_t *target);
|
||||||
void skip_samples(std::size_t number_of_samples);
|
void skip_samples(std::size_t number_of_samples);
|
||||||
|
void set_sample_volume_range(std::int16_t range);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||||
@@ -34,7 +37,19 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource {
|
|||||||
unsigned int counters_[4] = {2, 1, 0, 0}; // create a slight phase offset for the three channels
|
unsigned int counters_[4] = {2, 1, 0, 0}; // create a slight phase offset for the three channels
|
||||||
unsigned int shift_registers_[4] = {0, 0, 0, 0};
|
unsigned int shift_registers_[4] = {0, 0, 0, 0};
|
||||||
uint8_t control_registers_[4] = {0, 0, 0, 0};
|
uint8_t control_registers_[4] = {0, 0, 0, 0};
|
||||||
uint8_t volume_ = 0;
|
int16_t volume_ = 0;
|
||||||
|
int16_t range_multiplier_ = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BusHandler {
|
||||||
|
void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data) {
|
||||||
|
*pixel_data = 0xff;
|
||||||
|
*colour_data = 0xff;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class OutputMode {
|
||||||
|
PAL, NTSC
|
||||||
};
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -45,27 +60,36 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource {
|
|||||||
|
|
||||||
@c set_register and @c get_register provide register access.
|
@c set_register and @c get_register provide register access.
|
||||||
*/
|
*/
|
||||||
template <class T> class MOS6560 {
|
template <class BusHandler> class MOS6560 {
|
||||||
public:
|
public:
|
||||||
MOS6560() :
|
MOS6560(BusHandler &bus_handler) :
|
||||||
|
bus_handler_(bus_handler),
|
||||||
crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::DisplayType::NTSC60, 2)),
|
crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::DisplayType::NTSC60, 2)),
|
||||||
audio_generator_(audio_queue_),
|
audio_generator_(audio_queue_),
|
||||||
speaker_(audio_generator_)
|
speaker_(audio_generator_)
|
||||||
{
|
{
|
||||||
crt_->set_composite_sampling_function(
|
crt_->set_svideo_sampling_function(
|
||||||
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
|
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)"
|
||||||
"{"
|
"{"
|
||||||
"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);"
|
"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);"
|
||||||
"float phaseOffset = 6.283185308 * 2.0 * yc.y;"
|
|
||||||
|
|
||||||
"float chroma = cos(phase + phaseOffset);"
|
"float phaseOffset = 6.283185308 * 2.0 * yc.y;"
|
||||||
"return mix(yc.x, step(yc.y, 0.75) * chroma, amplitude);"
|
"float chroma = step(yc.y, 0.75) * cos(phase + phaseOffset);"
|
||||||
|
|
||||||
|
"return vec2(yc.x, chroma);"
|
||||||
"}");
|
"}");
|
||||||
|
|
||||||
|
// default to s-video output
|
||||||
|
crt_->set_video_signal(Outputs::CRT::VideoSignal::SVideo);
|
||||||
|
|
||||||
// default to NTSC
|
// default to NTSC
|
||||||
set_output_mode(OutputMode::NTSC);
|
set_output_mode(OutputMode::NTSC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
~MOS6560() {
|
||||||
|
audio_queue_.flush();
|
||||||
|
}
|
||||||
|
|
||||||
void set_clock_rate(double clock_rate) {
|
void set_clock_rate(double clock_rate) {
|
||||||
speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0));
|
speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0));
|
||||||
}
|
}
|
||||||
@@ -77,38 +101,34 @@ template <class T> class MOS6560 {
|
|||||||
speaker_.set_high_frequency_cutoff(cutoff);
|
speaker_.set_high_frequency_cutoff(cutoff);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum OutputMode {
|
|
||||||
PAL, NTSC
|
|
||||||
};
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Sets the output mode to either PAL or NTSC.
|
Sets the output mode to either PAL or NTSC.
|
||||||
*/
|
*/
|
||||||
void set_output_mode(OutputMode output_mode) {
|
void set_output_mode(OutputMode output_mode) {
|
||||||
output_mode_ = output_mode;
|
output_mode_ = output_mode;
|
||||||
|
|
||||||
// Lumunances are encoded trivially: on a 0–255 scale.
|
// Luminances are encoded trivially: on a 0-255 scale.
|
||||||
const uint8_t luminances[16] = {
|
const uint8_t luminances[16] = {
|
||||||
0, 255, 109, 189,
|
0, 255, 64, 192,
|
||||||
199, 144, 159, 161,
|
128, 128, 64, 192,
|
||||||
126, 227, 227, 207,
|
128, 192, 128, 255,
|
||||||
235, 173, 188, 196
|
192, 192, 128, 255
|
||||||
};
|
};
|
||||||
|
|
||||||
// Chrominances are encoded such that 0–128 is a complete revolution of phase;
|
// 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
|
// anything above 191 disables the colour subcarrier. Phase is relative to the
|
||||||
// colour burst, so 0 is green.
|
// colour burst, so 0 is green.
|
||||||
const uint8_t pal_chrominances[16] = {
|
const uint8_t pal_chrominances[16] = {
|
||||||
255, 255, 40, 112,
|
255, 255, 37, 101,
|
||||||
8, 88, 120, 56,
|
19, 86, 123, 59,
|
||||||
40, 48, 40, 112,
|
46, 53, 37, 101,
|
||||||
8, 88, 120, 56,
|
19, 86, 123, 59,
|
||||||
};
|
};
|
||||||
const uint8_t ntsc_chrominances[16] = {
|
const uint8_t ntsc_chrominances[16] = {
|
||||||
255, 255, 8, 72,
|
255, 255, 7, 71,
|
||||||
32, 88, 48, 112,
|
25, 86, 48, 112,
|
||||||
0, 0, 8, 72,
|
0, 119, 7, 71,
|
||||||
32, 88, 48, 112,
|
25, 86, 48, 112,
|
||||||
};
|
};
|
||||||
const uint8_t *chrominances;
|
const uint8_t *chrominances;
|
||||||
Outputs::CRT::DisplayType display_type;
|
Outputs::CRT::DisplayType display_type;
|
||||||
@@ -118,7 +138,8 @@ template <class T> class MOS6560 {
|
|||||||
chrominances = pal_chrominances;
|
chrominances = pal_chrominances;
|
||||||
display_type = Outputs::CRT::DisplayType::PAL50;
|
display_type = Outputs::CRT::DisplayType::PAL50;
|
||||||
timing_.cycles_per_line = 71;
|
timing_.cycles_per_line = 71;
|
||||||
timing_.line_counter_increment_offset = 0;
|
timing_.line_counter_increment_offset = 4;
|
||||||
|
timing_.final_line_increment_position = timing_.cycles_per_line - timing_.line_counter_increment_offset;
|
||||||
timing_.lines_per_progressive_field = 312;
|
timing_.lines_per_progressive_field = 312;
|
||||||
timing_.supports_interlacing = false;
|
timing_.supports_interlacing = false;
|
||||||
break;
|
break;
|
||||||
@@ -127,23 +148,23 @@ template <class T> class MOS6560 {
|
|||||||
chrominances = ntsc_chrominances;
|
chrominances = ntsc_chrominances;
|
||||||
display_type = Outputs::CRT::DisplayType::NTSC60;
|
display_type = Outputs::CRT::DisplayType::NTSC60;
|
||||||
timing_.cycles_per_line = 65;
|
timing_.cycles_per_line = 65;
|
||||||
timing_.line_counter_increment_offset = 65 - 33; // TODO: this is a bit of a hack; separate vertical and horizontal counting
|
timing_.line_counter_increment_offset = 40;
|
||||||
|
timing_.final_line_increment_position = 58;
|
||||||
timing_.lines_per_progressive_field = 261;
|
timing_.lines_per_progressive_field = 261;
|
||||||
timing_.supports_interlacing = true;
|
timing_.supports_interlacing = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
crt_->set_new_display_type(static_cast<unsigned int>(timing_.cycles_per_line*4), display_type);
|
crt_->set_new_display_type(static_cast<unsigned int>(timing_.cycles_per_line*4), display_type);
|
||||||
crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f));
|
|
||||||
|
|
||||||
// switch(output_mode) {
|
switch(output_mode) {
|
||||||
// case OutputMode::PAL:
|
case OutputMode::PAL:
|
||||||
// crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 15*4, 55*4, 4.0f / 3.0f));
|
crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.07f, 0.9f, 0.9f));
|
||||||
// break;
|
break;
|
||||||
// case OutputMode::NTSC:
|
case OutputMode::NTSC:
|
||||||
// crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 11*4, 55*4, 4.0f / 3.0f));
|
crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f));
|
||||||
// break;
|
break;
|
||||||
// }
|
}
|
||||||
|
|
||||||
for(int c = 0; c < 16; c++) {
|
for(int c = 0; c < 16; c++) {
|
||||||
uint8_t *colour = reinterpret_cast<uint8_t *>(&colours_[c]);
|
uint8_t *colour = reinterpret_cast<uint8_t *>(&colours_[c]);
|
||||||
@@ -166,7 +187,6 @@ template <class T> class MOS6560 {
|
|||||||
|
|
||||||
// keep track of internal time relative to this scanline
|
// keep track of internal time relative to this scanline
|
||||||
horizontal_counter_++;
|
horizontal_counter_++;
|
||||||
full_frame_counter_++;
|
|
||||||
if(horizontal_counter_ == timing_.cycles_per_line) {
|
if(horizontal_counter_ == timing_.cycles_per_line) {
|
||||||
if(horizontal_drawing_latch_) {
|
if(horizontal_drawing_latch_) {
|
||||||
current_character_row_++;
|
current_character_row_++;
|
||||||
@@ -188,9 +208,8 @@ template <class T> class MOS6560 {
|
|||||||
horizontal_drawing_latch_ = false;
|
horizontal_drawing_latch_ = false;
|
||||||
|
|
||||||
vertical_counter_ ++;
|
vertical_counter_ ++;
|
||||||
if(vertical_counter_ == (registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field)) {
|
if(vertical_counter_ == lines_this_field()) {
|
||||||
vertical_counter_ = 0;
|
vertical_counter_ = 0;
|
||||||
full_frame_counter_ = 0;
|
|
||||||
|
|
||||||
if(output_mode_ == OutputMode::NTSC) is_odd_frame_ ^= true;
|
if(output_mode_ == OutputMode::NTSC) is_odd_frame_ ^= true;
|
||||||
current_row_ = 0;
|
current_row_ = 0;
|
||||||
@@ -238,7 +257,7 @@ template <class T> class MOS6560 {
|
|||||||
|
|
||||||
uint8_t pixel_data;
|
uint8_t pixel_data;
|
||||||
uint8_t colour_data;
|
uint8_t colour_data;
|
||||||
static_cast<T *>(this)->perform_read(fetch_address, &pixel_data, &colour_data);
|
bus_handler_.perform_read(fetch_address, &pixel_data, &colour_data);
|
||||||
|
|
||||||
// TODO: there should be a further two-cycle delay on pixels being output; the reverse bit should
|
// TODO: there should be a further two-cycle delay on pixels being output; the reverse bit should
|
||||||
// divide the byte it is set for 3:1 and then continue as usual.
|
// divide the byte it is set for 3:1 and then continue as usual.
|
||||||
@@ -252,7 +271,7 @@ template <class T> class MOS6560 {
|
|||||||
|
|
||||||
// apply vertical sync
|
// apply vertical sync
|
||||||
if(
|
if(
|
||||||
(vertical_counter_ < 3 && (is_odd_frame_ || !registers_.interlaced)) ||
|
(vertical_counter_ < 3 && is_odd_frame()) ||
|
||||||
(registers_.interlaced &&
|
(registers_.interlaced &&
|
||||||
(
|
(
|
||||||
(vertical_counter_ == 0 && horizontal_counter_ > 32) ||
|
(vertical_counter_ == 0 && horizontal_counter_ > 32) ||
|
||||||
@@ -268,7 +287,7 @@ template <class T> class MOS6560 {
|
|||||||
case State::Sync: crt_->output_sync(cycles_in_state_ * 4); break;
|
case State::Sync: crt_->output_sync(cycles_in_state_ * 4); break;
|
||||||
case State::ColourBurst: crt_->output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0); break;
|
case State::ColourBurst: crt_->output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0); break;
|
||||||
case State::Border: output_border(cycles_in_state_ * 4); break;
|
case State::Border: output_border(cycles_in_state_ * 4); break;
|
||||||
case State::Pixels: crt_->output_data(cycles_in_state_ * 4, 1); break;
|
case State::Pixels: crt_->output_data(cycles_in_state_ * 4); break;
|
||||||
}
|
}
|
||||||
output_state_ = this_state_;
|
output_state_ = this_state_;
|
||||||
cycles_in_state_ = 0;
|
cycles_in_state_ = 0;
|
||||||
@@ -281,6 +300,9 @@ template <class T> class MOS6560 {
|
|||||||
cycles_in_state_++;
|
cycles_in_state_++;
|
||||||
|
|
||||||
if(this_state_ == State::Pixels) {
|
if(this_state_ == State::Pixels) {
|
||||||
|
// TODO: palette changes can happen within half-characters; the below needs to be divided.
|
||||||
|
// Also: a perfect opportunity to rearrange this inner loop for no longer needing to be
|
||||||
|
// two parts with a cooperative owner?
|
||||||
if(column_counter_&1) {
|
if(column_counter_&1) {
|
||||||
character_value_ = pixel_data;
|
character_value_ = pixel_data;
|
||||||
|
|
||||||
@@ -321,7 +343,10 @@ template <class T> class MOS6560 {
|
|||||||
character_code_ = pixel_data;
|
character_code_ = pixel_data;
|
||||||
character_colour_ = colour_data;
|
character_colour_ = colour_data;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep counting columns even if sync or the colour burst have interceded.
|
||||||
|
if(column_counter_ >= 0 && column_counter_ < columns_this_line_*2) {
|
||||||
column_counter_++;
|
column_counter_++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -404,15 +429,15 @@ template <class T> class MOS6560 {
|
|||||||
*/
|
*/
|
||||||
uint8_t get_register(int address) {
|
uint8_t get_register(int address) {
|
||||||
address &= 0xf;
|
address &= 0xf;
|
||||||
int current_line = (full_frame_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line;
|
|
||||||
switch(address) {
|
switch(address) {
|
||||||
default: return registers_.direct_values[address];
|
default: return registers_.direct_values[address];
|
||||||
case 0x03: return static_cast<uint8_t>(current_line << 7) | (registers_.direct_values[3] & 0x7f);
|
case 0x03: return static_cast<uint8_t>(raster_value() << 7) | (registers_.direct_values[3] & 0x7f);
|
||||||
case 0x04: return (current_line >> 1) & 0xff;
|
case 0x04: return (raster_value() >> 1) & 0xff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
BusHandler &bus_handler_;
|
||||||
std::unique_ptr<Outputs::CRT::CRT> crt_;
|
std::unique_ptr<Outputs::CRT::CRT> crt_;
|
||||||
|
|
||||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||||
@@ -443,7 +468,29 @@ template <class T> class MOS6560 {
|
|||||||
unsigned int cycles_in_state_;
|
unsigned int cycles_in_state_;
|
||||||
|
|
||||||
// counters that cover an entire field
|
// counters that cover an entire field
|
||||||
int horizontal_counter_ = 0, vertical_counter_ = 0, full_frame_counter_;
|
int horizontal_counter_ = 0, vertical_counter_ = 0;
|
||||||
|
const int lines_this_field() {
|
||||||
|
// Necessary knowledge here: only the NTSC 6560 supports interlaced video.
|
||||||
|
return registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field;
|
||||||
|
}
|
||||||
|
const int raster_value() {
|
||||||
|
const int bonus_line = (horizontal_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line;
|
||||||
|
const int line = vertical_counter_ + bonus_line;
|
||||||
|
const int final_line = lines_this_field();
|
||||||
|
|
||||||
|
if(line < final_line)
|
||||||
|
return line;
|
||||||
|
|
||||||
|
if(is_odd_frame()) {
|
||||||
|
return (horizontal_counter_ >= timing_.final_line_increment_position) ? 0 : final_line - 1;
|
||||||
|
} else {
|
||||||
|
return line % final_line;
|
||||||
|
}
|
||||||
|
// Cf. http://www.sleepingelephant.com/ipw-web/bulletin/bb/viewtopic.php?f=14&t=7237&start=15#p80737
|
||||||
|
}
|
||||||
|
bool is_odd_frame() {
|
||||||
|
return is_odd_frame_ || !registers_.interlaced;
|
||||||
|
}
|
||||||
|
|
||||||
// latches dictating start and length of drawing
|
// latches dictating start and length of drawing
|
||||||
bool vertical_drawing_latch_, horizontal_drawing_latch_;
|
bool vertical_drawing_latch_, horizontal_drawing_latch_;
|
||||||
@@ -473,12 +520,14 @@ template <class T> class MOS6560 {
|
|||||||
struct {
|
struct {
|
||||||
int cycles_per_line;
|
int cycles_per_line;
|
||||||
int line_counter_increment_offset;
|
int line_counter_increment_offset;
|
||||||
|
int final_line_increment_position;
|
||||||
int lines_per_progressive_field;
|
int lines_per_progressive_field;
|
||||||
bool supports_interlacing;
|
bool supports_interlacing;
|
||||||
} timing_;
|
} timing_;
|
||||||
OutputMode output_mode_;
|
OutputMode output_mode_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* _560_hpp */
|
#endif /* _560_hpp */
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 31/07/2017.
|
// Created by Thomas Harte on 31/07/2017.
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef CRTC6845_hpp
|
#ifndef CRTC6845_hpp
|
||||||
@@ -35,7 +35,7 @@ class BusHandler {
|
|||||||
void perform_bus_cycle_phase1(const BusState &) {}
|
void perform_bus_cycle_phase1(const BusState &) {}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Performs the second phase of a 6845 bus cycle. Some bus state — including sync — is updated
|
Performs the second phase of a 6845 bus cycle. Some bus state, including sync, is updated
|
||||||
directly after phase 1 and hence is visible to an observer during phase 2. Handlers may therefore
|
directly after phase 1 and hence is visible to an observer during phase 2. Handlers may therefore
|
||||||
implement @c perform_bus_cycle_phase2 to be notified of the availability of that state without
|
implement @c perform_bus_cycle_phase2 to be notified of the availability of that state without
|
||||||
having to wait until the next cycle has begun.
|
having to wait until the next cycle has begun.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 01/08/2017.
|
// Created by Thomas Harte on 01/08/2017.
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef i8255_hpp
|
#ifndef i8255_hpp
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 05/08/2017.
|
// Created by Thomas Harte on 05/08/2017.
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "i8272.hpp"
|
#include "i8272.hpp"
|
||||||
@@ -115,7 +115,7 @@ void i8272::run_for(Cycles cycles) {
|
|||||||
int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1;
|
int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1;
|
||||||
printf("Target %d versus believed %d\n", drives_[c].target_head_position, drives_[c].head_position);
|
printf("Target %d versus believed %d\n", drives_[c].target_head_position, drives_[c].head_position);
|
||||||
select_drive(c);
|
select_drive(c);
|
||||||
get_drive().step(direction);
|
get_drive().step(Storage::Disk::HeadPosition(direction));
|
||||||
if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction;
|
if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction;
|
||||||
|
|
||||||
// Check for completion.
|
// Check for completion.
|
||||||
@@ -434,7 +434,7 @@ void i8272::posit_event(int event_type) {
|
|||||||
ClearControlMark();
|
ClearControlMark();
|
||||||
if(event_type == static_cast<int>(Event::Token)) {
|
if(event_type == static_cast<int>(Event::Token)) {
|
||||||
if(get_latest_token().type != Token::Data && get_latest_token().type != Token::DeletedData) {
|
if(get_latest_token().type != Token::Data && get_latest_token().type != Token::DeletedData) {
|
||||||
// Something other than a data mark came next — impliedly an ID or index mark.
|
// Something other than a data mark came next, impliedly an ID or index mark.
|
||||||
SetMissingAddressMark();
|
SetMissingAddressMark();
|
||||||
SetMissingDataAddressMark();
|
SetMissingDataAddressMark();
|
||||||
goto abort; // TODO: or read_next_data?
|
goto abort; // TODO: or read_next_data?
|
||||||
@@ -828,7 +828,7 @@ void i8272::posit_event(int event_type) {
|
|||||||
|
|
||||||
goto post_result;
|
goto post_result;
|
||||||
|
|
||||||
// Posts whatever is in result_stack_ as a result phase. Be aware that it is a stack — the
|
// Posts whatever is in result_stack_ as a result phase. Be aware that it is a stack, so the
|
||||||
// last thing in it will be returned first.
|
// last thing in it will be returned first.
|
||||||
post_result:
|
post_result:
|
||||||
printf("Result to %02x, main %02x: ", command_[0] & 0x1f, main_status_);
|
printf("Result to %02x, main %02x: ", command_[0] & 0x1f, main_status_);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 05/08/2017.
|
// Created by Thomas Harte on 05/08/2017.
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef i8272_hpp
|
#ifndef i8272_hpp
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 25/11/2017.
|
// Created by Thomas Harte on 25/11/2017.
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "9918.hpp"
|
#include "9918.hpp"
|
||||||
@@ -53,6 +53,34 @@ const uint8_t StatusFifthSprite = 0x40;
|
|||||||
const int StatusSpriteCollisionShift = 5;
|
const int StatusSpriteCollisionShift = 5;
|
||||||
const uint8_t StatusSpriteCollision = 0x20;
|
const uint8_t StatusSpriteCollision = 0x20;
|
||||||
|
|
||||||
|
struct ReverseTable {
|
||||||
|
std::uint8_t map[256];
|
||||||
|
|
||||||
|
ReverseTable() {
|
||||||
|
for(int c = 0; c < 256; ++c) {
|
||||||
|
map[c] = static_cast<uint8_t>(
|
||||||
|
((c & 0x80) >> 7) |
|
||||||
|
((c & 0x40) >> 5) |
|
||||||
|
((c & 0x20) >> 3) |
|
||||||
|
((c & 0x10) >> 1) |
|
||||||
|
((c & 0x08) << 1) |
|
||||||
|
((c & 0x04) << 3) |
|
||||||
|
((c & 0x02) << 5) |
|
||||||
|
((c & 0x01) << 7)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} reverse_table;
|
||||||
|
|
||||||
|
// Bits are reversed in the internal mode value; they're stored
|
||||||
|
// in the order M1 M2 M3. Hence the definitions below.
|
||||||
|
enum ScreenMode {
|
||||||
|
Text = 4,
|
||||||
|
MultiColour = 2,
|
||||||
|
ColouredText = 0,
|
||||||
|
Graphics = 1
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TMS9918Base::TMS9918Base() :
|
TMS9918Base::TMS9918Base() :
|
||||||
@@ -68,9 +96,15 @@ TMS9918::TMS9918(Personality p) {
|
|||||||
"{"
|
"{"
|
||||||
"return texture(sampler, coordinate).rgb / vec3(255.0);"
|
"return texture(sampler, coordinate).rgb / vec3(255.0);"
|
||||||
"}");
|
"}");
|
||||||
crt_->set_output_device(Outputs::CRT::OutputDevice::Monitor);
|
crt_->set_video_signal(Outputs::CRT::VideoSignal::RGB);
|
||||||
crt_->set_visible_area(Outputs::CRT::Rect(0.055f, 0.025f, 0.9f, 0.9f));
|
crt_->set_visible_area(Outputs::CRT::Rect(0.055f, 0.025f, 0.9f, 0.9f));
|
||||||
crt_->set_input_gamma(2.8f);
|
crt_->set_input_gamma(2.8f);
|
||||||
|
|
||||||
|
// The TMS remains in-phase with the NTSC colour clock; this is an empirical measurement
|
||||||
|
// intended to produce the correct relationship between the hard edges between pixels and
|
||||||
|
// the colour clock. It was eyeballed rather than derived from any knowledge of the TMS
|
||||||
|
// colour burst generator because I've yet to find any.
|
||||||
|
crt_->set_immediate_default_phase(0.85f);
|
||||||
}
|
}
|
||||||
|
|
||||||
Outputs::CRT::CRT *TMS9918::get_crt() {
|
Outputs::CRT::CRT *TMS9918::get_crt() {
|
||||||
@@ -274,7 +308,8 @@ void TMS9918::run_for(const HalfCycles cycles) {
|
|||||||
int row_base = pattern_name_address_;
|
int row_base = pattern_name_address_;
|
||||||
int pattern_base = pattern_generator_table_address_;
|
int pattern_base = pattern_generator_table_address_;
|
||||||
int colour_base = colour_table_address_;
|
int colour_base = colour_table_address_;
|
||||||
if(screen_mode_ == 1) {
|
if(screen_mode_ == ScreenMode::Graphics) {
|
||||||
|
// If this is high resolution mode, allow the row number to affect the pattern and colour addresses.
|
||||||
pattern_base &= 0x2000 | ((row_ & 0xc0) << 5);
|
pattern_base &= 0x2000 | ((row_ & 0xc0) << 5);
|
||||||
colour_base &= 0x2000 | ((row_ & 0xc0) << 5);
|
colour_base &= 0x2000 | ((row_ & 0xc0) << 5);
|
||||||
}
|
}
|
||||||
@@ -285,7 +320,7 @@ void TMS9918::run_for(const HalfCycles cycles) {
|
|||||||
const int pattern_names_end = (end - 27 + 3) >> 2;
|
const int pattern_names_end = (end - 27 + 3) >> 2;
|
||||||
std::memcpy(&pattern_names_[pattern_names_start], &ram_[row_base + pattern_names_start], static_cast<size_t>(pattern_names_end - pattern_names_start));
|
std::memcpy(&pattern_names_[pattern_names_start], &ram_[row_base + pattern_names_start], static_cast<size_t>(pattern_names_end - pattern_names_start));
|
||||||
|
|
||||||
// Colours are collected ever fourth window starting from window 29.
|
// Colours are collected every fourth window starting from window 29.
|
||||||
const int colours_start = (access_pointer_ - 29 + 3) >> 2;
|
const int colours_start = (access_pointer_ - 29 + 3) >> 2;
|
||||||
const int colours_end = (end - 29 + 3) >> 2;
|
const int colours_end = (end - 29 + 3) >> 2;
|
||||||
if(screen_mode_ != 1) {
|
if(screen_mode_ != 1) {
|
||||||
@@ -301,8 +336,11 @@ void TMS9918::run_for(const HalfCycles cycles) {
|
|||||||
// Patterns are collected ever fourth window starting from window 30.
|
// Patterns are collected ever fourth window starting from window 30.
|
||||||
const int pattern_buffer_start = (access_pointer_ - 30 + 3) >> 2;
|
const int pattern_buffer_start = (access_pointer_ - 30 + 3) >> 2;
|
||||||
const int pattern_buffer_end = (end - 30 + 3) >> 2;
|
const int pattern_buffer_end = (end - 30 + 3) >> 2;
|
||||||
|
|
||||||
|
// Multicolour mode uss a different function of row to pick bytes
|
||||||
|
const int row = (screen_mode_ != 2) ? (row_ & 7) : ((row_ >> 2) & 7);
|
||||||
for(int column = pattern_buffer_start; column < pattern_buffer_end; ++column) {
|
for(int column = pattern_buffer_start; column < pattern_buffer_end; ++column) {
|
||||||
pattern_buffer_[column] = ram_[pattern_base + (pattern_names_[column] << 3) + (row_ & 7)];
|
pattern_buffer_[column] = ram_[pattern_base + (pattern_names_[column] << 3) + row];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sprite slots occur in three quarters of ever fourth window starting from window 28.
|
// Sprite slots occur in three quarters of ever fourth window starting from window 28.
|
||||||
@@ -380,21 +418,21 @@ void TMS9918::run_for(const HalfCycles cycles) {
|
|||||||
|
|
||||||
const int shift = (output_column_ - first_pixel_column_) % 6;
|
const int shift = (output_column_ - first_pixel_column_) % 6;
|
||||||
int byte_column = (output_column_ - first_pixel_column_) / 6;
|
int byte_column = (output_column_ - first_pixel_column_) / 6;
|
||||||
int pattern = pattern_buffer_[byte_column] << shift;
|
int pattern = reverse_table.map[pattern_buffer_[byte_column]] >> shift;
|
||||||
int pixels_left = pixels_end - output_column_;
|
int pixels_left = pixels_end - output_column_;
|
||||||
int length = std::min(pixels_left, 6 - shift);
|
int length = std::min(pixels_left, 6 - shift);
|
||||||
while(true) {
|
while(true) {
|
||||||
pixels_left -= length;
|
pixels_left -= length;
|
||||||
while(length--) {
|
for(int c = 0; c < length; ++c) {
|
||||||
*pixel_target_ = colours[(pattern >> 7)&0x01];
|
pixel_target_[c] = colours[pattern&0x01];
|
||||||
pixel_target_++;
|
pattern >>= 1;
|
||||||
pattern <<= 1;
|
|
||||||
}
|
}
|
||||||
|
pixel_target_ += length;
|
||||||
|
|
||||||
if(!pixels_left) break;
|
if(!pixels_left) break;
|
||||||
length = std::min(6, pixels_left);
|
length = std::min(6, pixels_left);
|
||||||
byte_column++;
|
byte_column++;
|
||||||
pattern = pattern_buffer_[byte_column];
|
pattern = reverse_table.map[pattern_buffer_[byte_column]];
|
||||||
}
|
}
|
||||||
output_column_ = pixels_end;
|
output_column_ = pixels_end;
|
||||||
} break;
|
} break;
|
||||||
@@ -402,7 +440,7 @@ void TMS9918::run_for(const HalfCycles cycles) {
|
|||||||
case LineMode::Character: {
|
case LineMode::Character: {
|
||||||
// If this is the start of the visible area, seed sprite shifter positions.
|
// If this is the start of the visible area, seed sprite shifter positions.
|
||||||
SpriteSet &sprite_set = sprite_sets_[active_sprite_set_ ^ 1];
|
SpriteSet &sprite_set = sprite_sets_[active_sprite_set_ ^ 1];
|
||||||
if(line_mode_ == LineMode::Character && output_column_ == first_pixel_column_) {
|
if(output_column_ == first_pixel_column_) {
|
||||||
int c = sprite_set.active_sprite_slot;
|
int c = sprite_set.active_sprite_slot;
|
||||||
while(c--) {
|
while(c--) {
|
||||||
SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c];
|
SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c];
|
||||||
@@ -416,36 +454,46 @@ void TMS9918::run_for(const HalfCycles cycles) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Paint the background tiles.
|
// Paint the background tiles.
|
||||||
const int shift = (output_column_ - first_pixel_column_) & 7;
|
|
||||||
int byte_column = (output_column_ - first_pixel_column_) >> 3;
|
|
||||||
|
|
||||||
const int pixels_left = pixels_end - output_column_;
|
const int pixels_left = pixels_end - output_column_;
|
||||||
int length = std::min(pixels_left, 8 - shift);
|
if(screen_mode_ == ScreenMode::MultiColour) {
|
||||||
|
int pixel_location = output_column_ - first_pixel_column_;
|
||||||
int pattern = pattern_buffer_[byte_column] << shift;
|
for(int c = 0; c < pixels_left; ++c) {
|
||||||
uint8_t colour = colour_buffer_[byte_column];
|
pixel_target_[c] = palette[
|
||||||
uint32_t colours[2] = {
|
(pattern_buffer_[(pixel_location + c) >> 3] >> (((pixel_location + c) & 4)^4)) & 15
|
||||||
palette[(colour & 15) ? (colour & 15) : background_colour_],
|
];
|
||||||
palette[(colour >> 4) ? (colour >> 4) : background_colour_]
|
|
||||||
};
|
|
||||||
|
|
||||||
int background_pixels_left = pixels_left;
|
|
||||||
while(true) {
|
|
||||||
background_pixels_left -= length;
|
|
||||||
while(length--) {
|
|
||||||
*pixel_target_ = colours[(pattern >> 7)&0x01];
|
|
||||||
pixel_target_++;
|
|
||||||
pattern <<= 1;
|
|
||||||
}
|
}
|
||||||
|
pixel_target_ += pixels_left;
|
||||||
|
} else {
|
||||||
|
const int shift = (output_column_ - first_pixel_column_) & 7;
|
||||||
|
int byte_column = (output_column_ - first_pixel_column_) >> 3;
|
||||||
|
|
||||||
if(!background_pixels_left) break;
|
int length = std::min(pixels_left, 8 - shift);
|
||||||
length = std::min(8, background_pixels_left);
|
|
||||||
byte_column++;
|
|
||||||
|
|
||||||
pattern = pattern_buffer_[byte_column];
|
int pattern = reverse_table.map[pattern_buffer_[byte_column]] >> shift;
|
||||||
colour = colour_buffer_[byte_column];
|
uint8_t colour = colour_buffer_[byte_column];
|
||||||
colours[0] = palette[(colour & 15) ? (colour & 15) : background_colour_];
|
uint32_t colours[2] = {
|
||||||
colours[1] = palette[(colour >> 4) ? (colour >> 4) : background_colour_];
|
palette[(colour & 15) ? (colour & 15) : background_colour_],
|
||||||
|
palette[(colour >> 4) ? (colour >> 4) : background_colour_]
|
||||||
|
};
|
||||||
|
|
||||||
|
int background_pixels_left = pixels_left;
|
||||||
|
while(true) {
|
||||||
|
background_pixels_left -= length;
|
||||||
|
for(int c = 0; c < length; ++c) {
|
||||||
|
pixel_target_[c] = colours[pattern&0x01];
|
||||||
|
pattern >>= 1;
|
||||||
|
}
|
||||||
|
pixel_target_ += length;
|
||||||
|
|
||||||
|
if(!background_pixels_left) break;
|
||||||
|
length = std::min(8, background_pixels_left);
|
||||||
|
byte_column++;
|
||||||
|
|
||||||
|
pattern = reverse_table.map[pattern_buffer_[byte_column]];
|
||||||
|
colour = colour_buffer_[byte_column];
|
||||||
|
colours[0] = palette[(colour & 15) ? (colour & 15) : background_colour_];
|
||||||
|
colours[1] = palette[(colour >> 4) ? (colour >> 4) : background_colour_];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Paint sprites and check for collisions.
|
// Paint sprites and check for collisions.
|
||||||
@@ -488,7 +536,8 @@ void TMS9918::run_for(const HalfCycles cycles) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(output_column_ == first_right_border_column_) {
|
if(output_column_ == first_right_border_column_) {
|
||||||
crt_->output_data(static_cast<unsigned int>(first_right_border_column_ - first_pixel_column_) * 4, 4);
|
const unsigned int data_length = static_cast<unsigned int>(first_right_border_column_ - first_pixel_column_);
|
||||||
|
crt_->output_data(data_length * 4, data_length);
|
||||||
pixel_target_ = nullptr;
|
pixel_target_ = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -535,7 +584,7 @@ void TMS9918::run_for(const HalfCycles cycles) {
|
|||||||
screen_mode_ = next_screen_mode_;
|
screen_mode_ = next_screen_mode_;
|
||||||
blank_screen_ = next_blank_screen_;
|
blank_screen_ = next_blank_screen_;
|
||||||
switch(screen_mode_) {
|
switch(screen_mode_) {
|
||||||
case 2:
|
case ScreenMode::Text:
|
||||||
line_mode_ = LineMode::Text;
|
line_mode_ = LineMode::Text;
|
||||||
first_pixel_column_ = 69;
|
first_pixel_column_ = 69;
|
||||||
first_right_border_column_ = 309;
|
first_right_border_column_ = 309;
|
||||||
@@ -589,7 +638,7 @@ void TMS9918::set_register(int address, uint8_t value) {
|
|||||||
case 1:
|
case 1:
|
||||||
next_blank_screen_ = !(low_write_ & 0x40);
|
next_blank_screen_ = !(low_write_ & 0x40);
|
||||||
generate_interrupts_ = !!(low_write_ & 0x20);
|
generate_interrupts_ = !!(low_write_ & 0x20);
|
||||||
next_screen_mode_ = (next_screen_mode_ & 1) | ((low_write_ & 0x18) >> 3);
|
next_screen_mode_ = (next_screen_mode_ & 1) | ((low_write_ & 0x18) >> 2);
|
||||||
sprites_16x16_ = !!(low_write_ & 0x02);
|
sprites_16x16_ = !!(low_write_ & 0x02);
|
||||||
sprites_magnified_ = !!(low_write_ & 0x01);
|
sprites_magnified_ = !!(low_write_ & 0x01);
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 25/11/2017.
|
// Created by Thomas Harte on 25/11/2017.
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef TMS9918_hpp
|
#ifndef TMS9918_hpp
|
||||||
@@ -56,7 +56,7 @@ class TMS9918: public TMS9918Base {
|
|||||||
|
|
||||||
/*!
|
/*!
|
||||||
Runs the VCP for the number of cycles indicate; it is an implicit assumption of the code
|
Runs the VCP for the number of cycles indicate; it is an implicit assumption of the code
|
||||||
that the input clock rate is 3579545 Hz — the NTSC colour clock rate.
|
that the input clock rate is 3579545 Hz, the NTSC colour clock rate.
|
||||||
*/
|
*/
|
||||||
void run_for(const HalfCycles cycles);
|
void run_for(const HalfCycles cycles);
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 14/12/2017.
|
// Created by Thomas Harte on 14/12/2017.
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef TMS9918Base_hpp
|
#ifndef TMS9918Base_hpp
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 14/10/2016.
|
// Created by Thomas Harte on 14/10/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "AY38910.hpp"
|
#include "AY38910.hpp"
|
||||||
@@ -56,13 +56,18 @@ AY38910::AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set_sample_volume_range(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AY38910::set_sample_volume_range(std::int16_t range) {
|
||||||
// set up volume lookup table
|
// set up volume lookup table
|
||||||
float max_volume = 8192;
|
const float max_volume = static_cast<float>(range) / 3.0f; // As there are three channels.
|
||||||
float root_two = sqrtf(2.0f);
|
const float root_two = sqrtf(2.0f);
|
||||||
for(int v = 0; v < 16; v++) {
|
for(int v = 0; v < 16; v++) {
|
||||||
volumes_[v] = static_cast<int>(max_volume / powf(root_two, static_cast<float>(v ^ 0xf)));
|
volumes_[v] = static_cast<int>(max_volume / powf(root_two, static_cast<float>(v ^ 0xf)));
|
||||||
}
|
}
|
||||||
volumes_[0] = 0;
|
volumes_[0] = 0;
|
||||||
|
evaluate_output_volume();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
|
void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||||
@@ -98,7 +103,7 @@ void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
|
|||||||
noise_shift_register_ >>= 1;
|
noise_shift_register_ >>= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ... and the envelope generator. Table based for pattern lookup, with a 'refill' step — a way of
|
// ... and the envelope generator. Table based for pattern lookup, with a 'refill' step: a way of
|
||||||
// implementing non-repeating patterns by locking them to table position 0x1f.
|
// implementing non-repeating patterns by locking them to table position 0x1f.
|
||||||
if(envelope_divider_) envelope_divider_--;
|
if(envelope_divider_) envelope_divider_--;
|
||||||
else {
|
else {
|
||||||
@@ -125,7 +130,7 @@ void AY38910::evaluate_output_volume() {
|
|||||||
// The output level for a channel is:
|
// The output level for a channel is:
|
||||||
// 1 if neither tone nor noise is enabled;
|
// 1 if neither tone nor noise is enabled;
|
||||||
// 0 if either tone or noise is enabled and its value is low.
|
// 0 if either tone or noise is enabled and its value is low.
|
||||||
// The tone/noise enable bits use inverse logic — 0 = on, 1 = off — permitting the OR logic below.
|
// The tone/noise enable bits use inverse logic; 0 = on, 1 = off; permitting the OR logic below.
|
||||||
#define tone_level(c, tone_bit) (tone_outputs_[c] | (output_registers_[7] >> tone_bit))
|
#define tone_level(c, tone_bit) (tone_outputs_[c] | (output_registers_[7] >> tone_bit))
|
||||||
#define noise_level(c, noise_bit) (noise_output_ | (output_registers_[7] >> noise_bit))
|
#define noise_level(c, noise_bit) (noise_output_ | (output_registers_[7] >> noise_bit))
|
||||||
|
|
||||||
@@ -156,6 +161,11 @@ void AY38910::evaluate_output_volume() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AY38910::is_zero_level() {
|
||||||
|
// Confirm that the AY is trivially at the zero level if all three volume controls are set to fixed zero.
|
||||||
|
return output_registers_[0x8] == 0 && output_registers_[0x9] == 0 && output_registers_[0xa] == 0;
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Register manipulation
|
// MARK: - Register manipulation
|
||||||
|
|
||||||
void AY38910::select_register(uint8_t r) {
|
void AY38910::select_register(uint8_t r) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 14/10/2016.
|
// Created by Thomas Harte on 14/10/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef AY_3_8910_hpp
|
#ifndef AY_3_8910_hpp
|
||||||
@@ -85,6 +85,8 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
|
|||||||
|
|
||||||
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption
|
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption
|
||||||
void get_samples(std::size_t number_of_samples, int16_t *target);
|
void get_samples(std::size_t number_of_samples, int16_t *target);
|
||||||
|
bool is_zero_level();
|
||||||
|
void set_sample_volume_range(std::int16_t range);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Concurrency::DeferringAsyncTaskQueue &task_queue_;
|
Concurrency::DeferringAsyncTaskQueue &task_queue_;
|
||||||
|
|||||||
38
Components/AudioToggle/AudioToggle.cpp
Normal file
38
Components/AudioToggle/AudioToggle.cpp
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
//
|
||||||
|
// AudioToggle.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 17/04/2018.
|
||||||
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "AudioToggle.hpp"
|
||||||
|
|
||||||
|
using namespace Audio;
|
||||||
|
|
||||||
|
Audio::Toggle::Toggle(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
|
||||||
|
audio_queue_(audio_queue) {}
|
||||||
|
|
||||||
|
void Toggle::get_samples(std::size_t number_of_samples, std::int16_t *target) {
|
||||||
|
for(std::size_t sample = 0; sample < number_of_samples; ++sample) {
|
||||||
|
target[sample] = level_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Toggle::set_sample_volume_range(std::int16_t range) {
|
||||||
|
volume_ = range;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Toggle::skip_samples(const std::size_t number_of_samples) {}
|
||||||
|
|
||||||
|
void Toggle::set_output(bool enabled) {
|
||||||
|
if(is_enabled_ == enabled) return;
|
||||||
|
is_enabled_ = enabled;
|
||||||
|
audio_queue_.defer([=] {
|
||||||
|
level_ = enabled ? volume_ : 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Toggle::get_output() {
|
||||||
|
return is_enabled_;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user