Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
frequency_filter.cpp
Go to the documentation of this file.
1 /* Copyright (c) 2013 Wildfire Games
2  *
3  * Permission is hereby granted, free of charge, to any person obtaining
4  * a copy of this software and associated documentation files (the
5  * "Software"), to deal in the Software without restriction, including
6  * without limitation the rights to use, copy, modify, merge, publish,
7  * distribute, sublicense, and/or sell copies of the Software, and to
8  * permit persons to whom the Software is furnished to do so, subject to
9  * the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included
12  * in all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  */
22 
23 #include "precompiled.h"
24 #include "lib/frequency_filter.h"
25 
26 static const double errorTolerance = 0.25;
27 static const double sensitivity = 0.10;
28 static const double sampleTime = 2.0; // seconds
29 
30 /**
31  * variable-width window for frequency determination
32  **/
34 {
36 public:
38  : m_minDeltaTime(4.0 * resolution) // chosen to reduce error but still yield rapid updates.
39  , m_lastTime(0) // will be set on first call
40  , m_numEvents(0)
41  {
42  ENSURE(resolution > 0.0);
43  }
44 
45  bool operator()(double time, double& frequency)
46  {
47  m_numEvents++;
48 
49  if(m_lastTime == 0.0)
50  m_lastTime = time;
51 
52  // count # events until deltaTime is large enough
53  // (reduces quantization errors if resolution is low)
54  const double deltaTime = time - m_lastTime;
55  if(deltaTime <= m_minDeltaTime)
56  return false;
57 
58  frequency = m_numEvents / deltaTime;
59  m_numEvents = 0;
60  m_lastTime = time;
61  return true; // success
62  }
63 
64 private:
65  const double m_minDeltaTime;
66  double m_lastTime;
68 };
69 
70 
71 /**
72  * variable-gain IIR filter
73  **/
74 class IirFilter
75 {
76 public:
77  IirFilter(double sensitivity, double initialValue)
78  : m_sensitivity(sensitivity), m_prev(initialValue)
79  {
80  }
81 
82  // bias = 0: no change. > 0: increase (n-th root). < 0: decrease (^n)
83  double operator()(double x, int bias)
84  {
85  // sensitivity to changes ([0,1]).
86  const double gain = pow(m_sensitivity, ComputeExponent(bias));
87  return m_prev = x*gain + m_prev*(1.0-gain);
88  }
89 
90 private:
91  static double ComputeExponent(int bias)
92  {
93  if(bias > 0)
94  return 1.0 / bias; // n-th root
95  else if(bias == 0)
96  return 1.0; // no change
97  else
98  return -bias; // power-of-n
99  }
100 
102  double m_prev;
103 };
104 
105 
106 /**
107  * regulate IIR gain for rapid but smooth tracking of a function.
108  * this is similar in principle to a PID controller but is tuned for
109  * the special case of FPS values to simplify stabilizing the filter.
110  **/
112 {
113 public:
114  Controller(double initialValue)
115  : m_timesOnSameSide(0)
116  {
117  std::fill(m_history, m_history+m_historySize, initialValue);
118  }
119 
120  // bias := exponential change to gain, (-inf, inf)
121  int ComputeBias(double smoothedValue, double value)
122  {
123  if(WasOnSameSide(value)) // (must be checked before updating history)
125  else
126  m_timesOnSameSide = 0;
127 
128  // update history
130  m_history[m_historySize-1] = value;
131 
132  // dampen jitter
133  if(Change(smoothedValue, value) < 0.04)
134  return -1;
135 
136  // dampen spikes/bounces.
137  if(WasSpike())
138  return -2;
139 
140  // if the past few samples have been consistently above/below
141  // average, the function is changing and we need to catch up.
142  // (similar to I in a PID)
143  if(m_timesOnSameSide >= 3)
144  return std::min(m_timesOnSameSide, 4);
145 
146  // suppress large jumps.
147  if(Change(m_history[m_historySize-1], value) > 0.30)
148  return -4; // gain -> 0
149 
150  return 0;
151  }
152 
153 private:
154  bool WasOnSameSide(double value) const
155  {
156  int sum = 0;
157  for(size_t i = 0; i < m_historySize; i++)
158  {
159  const int vote = (value >= m_history[i])? 1 : -1;
160  sum += vote;
161  }
162  return abs(sum) == (int)m_historySize;
163  }
164 
165  static double Change(double from, double to)
166  {
167  return fabs(from - to) / from;
168  }
169 
170  // /\ or \/ in last three history entries
171  bool WasSpike() const
172  {
173  cassert(m_historySize >= 3);
174  const double h2 = m_history[m_historySize-3], h1 = m_history[m_historySize-2], h0 = m_history[m_historySize-1];
175  if(((h2-h1) * (h1-h0)) > 0) // no sign change
176  return false;
177  if(Change(h2, h0) > 0.05) // overall change from oldest to newest value
178  return false;
179  if(Change(h1, h0) < 0.10) // no intervening spike
180  return false;
181  return true;
182  }
183 
184  static const size_t m_historySize = 3;
187 };
188 
189 
191 {
193 public:
194  FrequencyFilter(double resolution, double expectedFrequency)
195  : m_frequencyEstimator(resolution), m_controller(expectedFrequency), m_iirFilter(sensitivity, expectedFrequency)
196  , m_stableFrequency((int)expectedFrequency), m_smoothedFrequency(expectedFrequency), m_averagedFrequency(expectedFrequency)
197  , m_numberOfSamples((int)(sampleTime * expectedFrequency) + 1)
198  {
199  }
200 
201  virtual void Update(double time)
202  {
203  double frequency;
204  if(!m_frequencyEstimator(time, frequency))
205  return;
206 
207  const int bias = m_controller.ComputeBias(m_smoothedFrequency, frequency);
208  m_smoothedFrequency = m_iirFilter(frequency, bias);
209 
210  // Keep a moving average of the frequency over the last two seconds
211  // If there is a spike of more than 25% (e.g. loading screen => game)
212  // then reset the moving average and reset the number of samples needed
213  const double difference = fabs(m_smoothedFrequency - m_averagedFrequency);
214  if (difference > errorTolerance * m_averagedFrequency)
215  {
216  m_averagedFrequency = m_smoothedFrequency;
217  m_numberOfSamples = (int)(m_averagedFrequency * sampleTime) + 1;
218  }
219  else
220  m_averagedFrequency = ((double)(m_numberOfSamples - 1) * m_averagedFrequency + m_smoothedFrequency) / (double)m_numberOfSamples;
221 
222  // allow the smoothed FPS to free-run until it is no longer near the
223  // previous stable FPS value. round up because values are more often
224  // too low than too high.
225  m_stableFrequency = (int)(m_averagedFrequency + 0.99);
226  }
227 
228  virtual double SmoothedFrequency() const
229  {
230  return m_smoothedFrequency;
231  }
232 
233  virtual int StableFrequency() const
234  {
235  return m_stableFrequency;
236  }
237 
238 private:
242 
247 };
248 
249 
250 PIFrequencyFilter CreateFrequencyFilter(double resolution, double expectedFrequency)
251 {
252  return PIFrequencyFilter(new FrequencyFilter(resolution, expectedFrequency));
253 }
bool operator()(double time, double &frequency)
virtual double SmoothedFrequency() const
FrequencyFilter(double resolution, double expectedFrequency)
regulate IIR gain for rapid but smooth tracking of a function.
variable-gain IIR filter
static const size_t m_historySize
bool WasOnSameSide(double value) const
double operator()(double x, int bias)
NONCOPYABLE(FrequencyFilter)
Controller(double initialValue)
shared_ptr< IFrequencyFilter > PIFrequencyFilter
FrequencyEstimator m_frequencyEstimator
#define ENSURE(expr)
ensure the expression &lt;expr&gt; evaluates to non-zero.
Definition: debug.h:282
IirFilter(double sensitivity, double initialValue)
const double m_minDeltaTime
double m_sensitivity
static const double sensitivity
bool WasSpike() const
NONCOPYABLE(FrequencyEstimator)
double m_history[m_historySize]
static double ComputeExponent(int bias)
static double resolution
Definition: whrt.cpp:99
PIFrequencyFilter CreateFrequencyFilter(double resolution, double expectedFrequency)
virtual void Update(double time)
virtual int StableFrequency() const
static double Change(double from, double to)
FrequencyEstimator(double resolution)
int ComputeBias(double smoothedValue, double value)
variable-width window for frequency determination
static const double errorTolerance
#define cassert(expr)
Compile-time assertion.
static const double sampleTime