1//============================================================================
2//
3// SSSS tt lll lll
4// SS SS tt ll ll
5// SS tttttt eeee ll ll aaaa
6// SSSS tt ee ee ll ll aa
7// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
8// SS SS tt ee ll ll aa aa
9// SSSS ttt eeeee llll llll aaaaa
10//
11// Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony
12// and the Stella Team
13//
14// See the file "License.txt" for information on usage and redistribution of
15// this file, and for a DISCLAIMER OF ALL WARRANTIES.
16//============================================================================
17
18#include "FrameLayoutDetector.hxx"
19#include "TIAConstants.hxx"
20
21/**
22 * Misc. numeric constants used in the algorithm.
23 */
24enum Metrics: uInt32 {
25 // ideal frame heights
26 frameLinesNTSC = 262,
27 frameLinesPAL = 312,
28
29 // number of scanlines to wait for vsync to start and stop (exceeding ideal frame height)
30 waitForVsync = 100,
31
32 // tolerance window around ideal frame size for TV mode detection
33 tvModeDetectionTolerance = 20,
34
35 // these frames will not be considered for detection
36 initialGarbageFrames = TIAConstants::initialGarbageFrames
37};
38
39// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
40FrameLayout FrameLayoutDetector::detectedLayout() const{
41 // We choose the mode that was detected for the majority of frames.
42 return myPalFrames > myNtscFrames ? FrameLayout::pal : FrameLayout::ntsc;
43}
44
45// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
46FrameLayoutDetector::FrameLayoutDetector()
47 : myState(State::waitForVsyncStart)
48{
49 reset();
50}
51
52// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
53void FrameLayoutDetector::onReset()
54{
55 myState = State::waitForVsyncStart;
56 myNtscFrames = myPalFrames = 0;
57 myLinesWaitingForVsyncToStart = 0;
58}
59
60// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
61void FrameLayoutDetector::onSetVsync()
62{
63 if (myVsync)
64 setState(State::waitForVsyncEnd);
65 else
66 setState(State::waitForVsyncStart);
67}
68
69// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
70void FrameLayoutDetector::onNextLine()
71{
72 const uInt32 frameLines = layout() == FrameLayout::ntsc ? Metrics::frameLinesNTSC : Metrics::frameLinesPAL;
73
74 switch (myState) {
75 case State::waitForVsyncStart:
76 // We start counting the number of "lines spent while waiting for vsync start" from
77 // the "ideal" frame size (corrected by the three scanlines spent in vsync).
78 if (myCurrentFrameTotalLines > frameLines - 3 || myTotalFrames == 0)
79 ++myLinesWaitingForVsyncToStart;
80
81 if (myLinesWaitingForVsyncToStart > Metrics::waitForVsync) setState(State::waitForVsyncEnd);
82
83 break;
84
85 case State::waitForVsyncEnd:
86 if (++myLinesWaitingForVsyncToStart > Metrics::waitForVsync) setState(State::waitForVsyncStart);
87
88 break;
89
90 default:
91 throw runtime_error("cannot happen");
92 }
93}
94
95// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
96void FrameLayoutDetector::setState(State state)
97{
98 if (state == myState) return;
99
100 myState = state;
101 myLinesWaitingForVsyncToStart = 0;
102
103 switch (myState) {
104 case State::waitForVsyncEnd:
105 break;
106
107 case State::waitForVsyncStart:
108 finalizeFrame();
109 notifyFrameStart();
110 break;
111
112 default:
113 throw new runtime_error("cannot happen");
114 }
115}
116
117// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
118void FrameLayoutDetector::finalizeFrame()
119{
120 notifyFrameComplete();
121
122 if (myTotalFrames <= Metrics::initialGarbageFrames) return;
123
124 // Calculate the delta between scanline count and the sweet spot for the respective
125 // frame layouts
126 const uInt32
127 deltaNTSC = abs(Int32(myCurrentFrameFinalLines) - Int32(frameLinesNTSC)),
128 deltaPAL = abs(Int32(myCurrentFrameFinalLines) - Int32(frameLinesPAL));
129
130 // Does the scanline count fall into one of our tolerance windows? -> use it
131 if (std::min(deltaNTSC, deltaPAL) <= Metrics::tvModeDetectionTolerance)
132 layout(deltaNTSC <= deltaPAL ? FrameLayout::ntsc : FrameLayout::pal);
133 else if (
134 // If scanline count is odd and lies between the PAL and NTSC windows we assume
135 // it is NTSC (it would cause color loss on PAL CRTs)
136 (myCurrentFrameFinalLines < frameLinesPAL) &&
137 (myCurrentFrameFinalLines > frameLinesNTSC) &&
138 (myCurrentFrameFinalLines % 2)
139 )
140 layout(FrameLayout::ntsc);
141 else
142 // Take the nearest layout if all else fails
143 layout(deltaNTSC <= deltaPAL ? FrameLayout::ntsc : FrameLayout::pal);
144
145 switch (layout()) {
146 case FrameLayout::ntsc:
147 ++myNtscFrames;
148 break;
149
150 case FrameLayout::pal:
151 ++myPalFrames;
152 break;
153
154 default:
155 throw runtime_error("cannot happen");
156 }
157}
158