<repo_name>dualword/strobe-api<file_sep>/strobe/strobe-core_cpp_.h
/*
MIT License

Copyright (c) 2018 fuzun

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#include <cstring>
#include <cstdlib>
#include <chrono>

inline StrobeCore::StrobeCore(int mode, int phaseSwitchInterval, bool internalFPSCalculation) : StrobeAPI(mode, phaseSwitchInterval), internalFPSCalc(internalFPSCalculation)
{
	fps = 0;
	timerSnapshot = 0;
	modeSnapshot = mode;
	fpsTimerSnapshot = strobeTimer.elapsedMilliseconds();
	counterSnapshot = counter.total();

	active = true;
}

inline StrobeCore::~StrobeCore()
{

}

inline bool StrobeCore::isActive(void)
{
	return active;
}

inline void StrobeCore::disable(void)
{
	active = false;
	modeSnapshot = strobeMode;
	setMode(0);
}

inline void StrobeCore::enable(void)
{
	active = true;
	strobeMode = modeSnapshot;
}

inline int StrobeCore::getStrobeMode(void)
{
	if (modeSnapshot != strobeMode)
		return modeSnapshot;
	else
		return strobeMode;
}

inline void StrobeCore::setActive(bool status)
{
	active = status;
	if (active)
		enable();
	else
		disable();
}

inline void StrobeCore::setMode(int mode)
{
	strobeMode = mode;
}

inline double StrobeCore::FPS(void)
{
	return fps;
}

inline void StrobeCore::setFPS(double newFPS)
{
	fps = newFPS;
}

inline bool StrobeCore::strobe(void)
{
	bool phasePositive = !(counter.total() % 2);

	int pCounter = counter.totalPositive();
	int nCounter = counter.totalNegative();

	if (phasePositive)
	{
		++pCounter;
		frameState = (FrameState)(frameState | PHASE_POSITIVE);
	}
	else
	{
		++nCounter;
		frameState = (FrameState)(frameState & ~PHASE_POSITIVE);
	}

	if (!strobeMode) // if strobe mode == 0
	{
		// disable();

		if (phasePositive)
			++counter.positiveRenderedFrameCount;
		else
			++counter.negativeRenderedFrameCount;

		return true;
	}

	if (switchInterval < 0)
		switchInterval = abs(switchInterval);

	if ((switchInterval) && (strobeMode % 2)) // Swapping not enabled for even modes as it is neither necessary nor works as intended
	{
		double difference = strobeTimer.elapsedSeconds() - timerSnapshot;

		if (difference >= switchInterval)
		{
			frameState = (FrameState)(frameState ^ PHASE_INVERTED);
			timerSnapshot = strobeTimer.elapsedSeconds();
		}
	}

	if (internalFPSCalc)
	{
		double elapsedTime = strobeTimer.elapsedMilliseconds();
		int frameTotal = counter.total();
		if (elapsedTime - fpsTimerSnapshot >= 1000)
		{
			fpsTimerSnapshot = strobeTimer.elapsedMilliseconds();
			counterSnapshot = counter.total();
		}
		else
		{
			setFPS(1000.0f * (frameTotal - counterSnapshot) / (elapsedTime - fpsTimerSnapshot));
		}
	}

	switch (frameState & (PHASE_POSITIVE | PHASE_INVERTED))
	{
	case (PHASE_POSITIVE | PHASE_INVERTED):
		if ((abs(strobeMode) % 2) == 0)
			frameState = (FrameState)((((pCounter - 1) % (abs(strobeMode) + 1)) == (abs(strobeMode) / 2)) ? frameState | FRAME_RENDER : frameState & ~FRAME_RENDER);
		else
			frameState = (FrameState)(frameState & ~FRAME_RENDER);
		break;

	case (PHASE_POSITIVE & ~PHASE_INVERTED):
		if (abs(strobeMode) % 2 == 0)
			frameState = (FrameState)((((pCounter - 1) % (abs(strobeMode) + 1)) == 0) ? frameState | FRAME_RENDER : frameState & ~FRAME_RENDER);
		else
			if (abs(strobeMode) == 1)
				frameState = (FrameState)(frameState | FRAME_RENDER);
			else
				frameState = (FrameState)((((pCounter - 1) % ((abs(strobeMode) + 1) / 2)) == 0) ? frameState | FRAME_RENDER : frameState & ~FRAME_RENDER);
		break;

	case (~PHASE_POSITIVE & PHASE_INVERTED):
		if (abs(strobeMode) % 2 == 0)
			frameState = (FrameState)((((nCounter - 1) % (abs(strobeMode) + 1)) == 0) ? frameState | FRAME_RENDER : frameState & ~FRAME_RENDER);
		else
			if (abs(strobeMode) == 1)
				frameState = (FrameState)(frameState | FRAME_RENDER);
			else
				frameState = (FrameState)((((nCounter - 1) % ((abs(strobeMode) + 1) / 2)) == 0) ? frameState | FRAME_RENDER : frameState & ~FRAME_RENDER);
		break;

	case 0:
		if ((abs(strobeMode) % 2) == 0)
			frameState = (FrameState)((((nCounter - 1) % (abs(strobeMode) + 1)) == (abs(strobeMode) / 2)) ? frameState | FRAME_RENDER : frameState & ~FRAME_RENDER);
		else
			frameState = (FrameState)(frameState & ~FRAME_RENDER);
		break;

	default:
		frameState = (FrameState)(PHASE_POSITIVE | FRAME_RENDER);
	}

	if (strobeMode < 0)
		frameState = (FrameState)(frameState ^ FRAME_RENDER);

	
	return processFrame();
}
<file_sep>/README.md



# StrobeAPI
**StrobeAPI** or **strobe-api** is a simple software based strobe (a motion blur elimination method) library written in C++. It uses "*black frame insertion*" technique.

Online demo for motion blur elimination through black frame insertion / replacement method can be found here: https://www.testufo.com/blackframes

Black frame insertion technique **greatly reduces motion blur** which is extremely important in modern video games. However it has some disadvantages such as reduced 'effective' fps and brightness. For example, least aggressive mode causes effective fps to drop to half and it also causes 35~% brightness reduction. StrobeAPI provides almost all necessary information regarding its side effects.

With this method, moving objects can get tracked much more easily. For example, https://www.testufo.com/photo#photo=toronto-map.png&pps=960&pursuit=0&height=0 in this demo, moving texts in the map can not be easily read in LCD / LED screen without built-in strobe support. But with strobing, they can clearly get read.

## How To Use?
To actually use StrobeAPI, you need to find an implementation for StrobeAPI. There is a simple but efficient implementation called "Strobe Core" which is already placed in the project source directory.
StrobeAPI is a header-only library so it only consists of header files. test.cpp in the source directory is used for testing and not necessary for production. However, it can be kept without any problem as long as you don't include it in your project.
To compile StrobeAPI, a modern C++ compiler should be enough.
For integration examples, see test.cpp and Flappy-Bird-Qt project linked at the bottom of this page.

### Example

    #include "strobe-core.h"
    ...
    extern StrobeAPI *strobe;
    
    int main()
    {
    ...
	    StrobeAPI *strobe = new StrobeCore(1 /* Strobe Mode 1: Applies RENDER - BLACK sequence, 2: RENDER - BLACK - BLACK, -2: BLACK - BLACK - RENDER*/, \
	    0 /* Interval for phase switching in seconds. Use only if image retention occurs. Doesn't matter when StrobeMode is even. */, \
	    true /* Built-in FPS calculation. Use false to disable */);
    ...
    }
    
    void SwapBuffers()
    {
	...
	    // At the end of swap buffers code, a bool value coming from the strobe() function should be used to determine whether to show actual rendered frame or to show completely black frame
	    // StrobeAPI automatically tracks previous and upcoming frames so do not intercept once it starts keep tracking.
	    // Warning: Do not omit generating a new frame when strobe output is false. It will break synchronization. In general, StrobeAPI integration overhead should be kept at absolute minimum.
	    
	    bool showRenderedFrame = strobe->strobe();
	    
	    if(showRenderedFrame)
		    // Do the stuff as usual.
	    else
		    // Show black frame.
	}
	void fps()
	{
	...
		strobe->setFPS(fps); // If built-in FPS calculation is disabled.
	}
	char * strobeDebugOutput()
	{
		return strobe->getDebugInformation();
	}

## Example Run
Strobe method = 2,
Strobe swap interval = 2,
Strobe cooldown delay = 3,
FPS = randomly generated in the range of (99, 101)

![Simulation](https://i.imgur.com/pO7tP3N.png)

## Projects using StrobeAPI
 - https://github.com/fuzun/desktopbfi (StrobeAPI on Desktop)
 - https://github.com/fuzun/Flappy-Bird-Qt
 - https://github.com/fuzun/xash3d-strobe (Uses old C version of StrobeAPI)
 
---

How PWM simulation works:

![signalsimulation](https://i.imgur.com/9Reb4GF.png)

"Badness" algorithm:

![badness](https://i.vgy.me/qbzBOH.png)
<file_sep>/strobe/strobe-api.h
/*
MIT License

Copyright (c) 2018 fuzun

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#ifndef STROBEAPI_H
#define STROBEAPI_H

#ifndef STROBE_DISABLED

#include <chrono>

class StrobeAPI
{
protected:
	class StrobeTimer
	{
	public:
		inline StrobeTimer()
		{
			startTime = std::chrono::system_clock::now();
		}

		inline double elapsedMilliseconds()
		{
			std::chrono::time_point<std::chrono::system_clock> endTime = std::chrono::system_clock::now();

			return std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();
		}

		inline double elapsedSeconds()
		{
			return elapsedMilliseconds() / 1000.0;
		}

	private:
		std::chrono::time_point<std::chrono::system_clock> startTime;
	};

	StrobeTimer strobeTimer;

public:

	enum CounterType
	{
		TotalFrame,
		PositiveFrame,
		PositiveRenderedFrame,
		PositiveBlackFrame,
		NegativeFrame,
		NegativeRenderedFrame,
		NegativeBlackFrame
	};

	enum FrameState
	{
		PHASE_POSITIVE = 1 << 0, // Phase: Positive
		PHASE_INVERTED = 1 << 1, // Phase: Inverted
		FRAME_RENDER = 1 << 2,  // Frame: Rendered

		_unassigned1 = (PHASE_POSITIVE | PHASE_INVERTED),
		_unassigned2 = (PHASE_POSITIVE | FRAME_RENDER),
		_unassigned3 = (PHASE_INVERTED | FRAME_RENDER),
		_unassigned4 = (PHASE_POSITIVE | PHASE_INVERTED | FRAME_RENDER)
	};

private:

	enum DifferenceType
	{
		PositiveDifference,
		NegativeDifference,
		TotalDifference
	};

	struct Counter
	{
		int positiveRenderedFrameCount;
		int positiveBlackFrameCount;
		int negativeRenderedFrameCount;
		int negativeBlackFrameCount;

		inline int total(void)
		{
			int totalFrames = 0;
			totalFrames += positiveBlackFrameCount;
			totalFrames += positiveRenderedFrameCount;
			totalFrames += negativeBlackFrameCount;
			totalFrames += negativeRenderedFrameCount;
			return totalFrames;
		}

		inline int totalPositive(void)
		{
			int totalFrames = 0;
			totalFrames += positiveBlackFrameCount;
			totalFrames += positiveRenderedFrameCount;
			return totalFrames;
		}

		inline int totalNegative(void)
		{
			int totalFrames = 0;
			totalFrames += negativeBlackFrameCount;
			totalFrames += negativeRenderedFrameCount;
			return totalFrames;
		}

		inline int totalBlack(void)
		{
			int totalFrames = 0;
			totalFrames += positiveBlackFrameCount;
			totalFrames += negativeBlackFrameCount;
			return totalFrames;
		}

		inline int totalRendered(void)
		{
			int totalFrames = 0;
			totalFrames += positiveRenderedFrameCount;
			totalFrames += negativeRenderedFrameCount;
			return totalFrames;
		}
	};

	char *debugInformation;

	void generateDiffBar(char * const dst, int size, DifferenceType type);
	void generateDebugInformation(void);
	
	void strConcat(char * const dst, int size, const char * const src);

protected:
	Counter counter;

	FrameState frameState;   // Frame info

	bool processFrame(void);
	
	int strobeMode;
	int switchInterval;

	double geometricMean(double x, double y);
	double arithmeticMean(double x, double y);

public:
	StrobeAPI(int mode, int phaseSwitchInterval);
	virtual ~StrobeAPI();

	bool isPhaseInverted(void);
	bool isFrameRendered(void);
	bool isPhasePositive(void);

	double actualBrightnessReduction(void);
	double logarithmicBrightnessReduction(double base);
	double squareBrightnessReduction(double base);
	double cubeBrightnessReduction(double base);
	double otherBrightnessReduction(double base, double (*reductionFunction)(double));

	double badness(bool PWMInvolved);
	double badnessReduced(bool PWMInvolved);

	double effectiveFPS(void);
	double frequency(void);
	double dutyCycle(void);
	double positivePhaseShift(void);
	double negativePhaseShift(void);
	double period(void);

	int frameCount(CounterType type);

	char * getDebugInformation(void);

	virtual int getStrobeMode(void);
	virtual int getPhaseSwitchInterval(void);

	virtual bool strobe(void) = 0;
	virtual double FPS() = 0;
	virtual void setFPS(double newFPS) = 0;

	virtual void setMode(int mode);
	virtual void enable(void) = 0;
	virtual void disable(void) = 0;
	virtual void setActive(bool status) = 0;

	virtual bool isActive(void);

	void setStrobeMode(int mode);
	void setPhaseSwitchInterval(int phaseSwitchInterval);
};

#include "strobe-api_cpp_.h"

#endif

#endif
<file_sep>/strobe/strobe-core.h
/*
MIT License

Copyright (c) 2018 fuzun

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#ifndef STROBECORE_H
#define STROBECORE_H

#ifndef STROBE_DISABLED

#include "strobe-api.h"

class StrobeCore : public StrobeAPI
{
private:
	double fps;
	double timerSnapshot;
	double fpsTimerSnapshot;
	bool active;
	bool internalFPSCalc;
	int modeSnapshot;
	int counterSnapshot;

public:
	StrobeCore(int mode = 1, int phaseSwitchInterval = 0, bool internalFPSCalculation = true);
	~StrobeCore();

	bool strobe(void) override;

	double FPS() override;
	int getStrobeMode(void) override;
	void setFPS(double newFPS) override;
	void setMode(int mode) override;
	void enable(void) override;
	void disable(void) override;
	void setActive(bool status) override;
	bool isActive(void) override;
};

#include "strobe-core_cpp_.h"

#endif

#endif
<file_sep>/strobe/strobe-api_cpp_.h
/*
MIT License

Copyright (c) 2018 fuzun

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>


template<class T>
inline double _lossCalculator(T x, T y)
{
	return (x - y) * 100.0 / x;
}

inline StrobeAPI::StrobeAPI(int mode, int phaseSwitchInterval) : strobeMode(mode), switchInterval(phaseSwitchInterval)
{
	debugInformation = new char[4096];
	debugInformation[0] = 0;

	memset(&counter, 0, sizeof(counter));

	frameState = (FrameState)(PHASE_POSITIVE | FRAME_RENDER);
}

inline StrobeAPI::~StrobeAPI()
{
	delete debugInformation;
	debugInformation = nullptr;
}

inline int StrobeAPI::getPhaseSwitchInterval(void)
{
	return switchInterval;
}

inline void StrobeAPI::setStrobeMode(int mode)
{
	frameState = (FrameState)(PHASE_POSITIVE | FRAME_RENDER);
	strobeMode = mode;
}

inline void StrobeAPI::setPhaseSwitchInterval(int phaseSwitchInterval)
{
	frameState = (FrameState)(frameState & ~PHASE_INVERTED);
	switchInterval = phaseSwitchInterval;
}

inline char * StrobeAPI::getDebugInformation(void)
{
	generateDebugInformation();
	return debugInformation;
}

inline bool StrobeAPI::isPhaseInverted(void)
{
	if (frameState & PHASE_INVERTED)
		return true;
	else
		return false;
}

inline bool StrobeAPI::isFrameRendered(void)
{
	if (frameState & FRAME_RENDER)
		return true;
	else
		return false;
}

inline bool StrobeAPI::isPhasePositive(void)
{
	if (frameState & PHASE_POSITIVE)
		return true;
	else
		return false;
}

inline double StrobeAPI::effectiveFPS(void)
{
	int mode = strobeMode;
	double eFPS;

	if (mode > 0)
	{
		eFPS = (FPS()) / (mode + 1);
	}
	else if (mode < 0)
	{
		mode = abs(mode);
		eFPS = (FPS() * mode) / (mode + 1);
	}
	else
	{
		eFPS = FPS();
	}

	return eFPS;
}

inline void StrobeAPI::generateDiffBar(char * const dst, int size, DifferenceType type)
{
	int pNCounter = counter.positiveRenderedFrameCount;
	int pBCounter = counter.positiveBlackFrameCount;
	int nNCounter = counter.negativeRenderedFrameCount;
	int nBCounter = counter.negativeBlackFrameCount;
	int nCounter = counter.totalNegative();
	int pCounter = counter.totalPositive();

	char _barCounter = 0;
	int diff_NB = 0;
	int diff = 0;
	int _a, _b;
	bool Neg = false;

	switch (type)
	{
	case (PositiveDifference):
	{
		diff_NB = (pNCounter - pBCounter);

		if (pCounter)
			diff = (int)round(abs(diff_NB) * 100 / pCounter);

		break;
	}
	case (NegativeDifference):
	{
		diff_NB = (nNCounter - nBCounter);

		if (nCounter)
			diff = (int)round(abs(diff_NB) * 100 / nCounter);

		break;
	}
	case (TotalDifference):
	{
		if (nCounter && pCounter)
		{
			_a = (abs(pNCounter - pBCounter) * 100 / pCounter);
			_b = (abs(nNCounter - nBCounter) * 100 / nCounter);
			diff = abs(_a - _b);
		}
		break;
	}
	default:
		break;
	}

	if (diff_NB < 0)
		Neg = true;

	snprintf(dst, size, "[");

	for (_barCounter = 0; _barCounter <= 20; ++_barCounter)
	{
		if (_barCounter == 10)
		{
			strConcat(dst, size, "O");
		}
		else if (_barCounter < 10)
		{
			if (type == 2)
			{
				if (100 - (_barCounter * 11) <= diff / 2)
					strConcat(dst, size, "=");
				else
					strConcat(dst, size, "-");
			}
			else
			{
				if (Neg)
				{
					if (100 - (_barCounter * 11) <= diff)
						strConcat(dst, size, "=");
					else
						strConcat(dst, size, "-");
				}
				else
				{
					strConcat(dst, size, "-");
				}
			}
		}
		else if (_barCounter > 10)
		{
			if (type == 2)
			{
				if (((_barCounter - 11) * 11) >= diff / 2)
					strConcat(dst, size, "-");
				else
					strConcat(dst, size, "=");
			}
			else
			{
				if (Neg)
				{
					strConcat(dst, size, "-");
				}
				else
				{
					if (((_barCounter - 11) * 11) >= diff)
						strConcat(dst, size, "-");
					else
						strConcat(dst, size, "=");
				}
			}
		}
	}
	if (type == 2)
	{
		char buf[32];
		snprintf(buf, sizeof(buf), "] * %d / 200", diff);
		strConcat(dst, size, buf);
	}
	else
	{
		char buf[32];
		snprintf(buf, sizeof(buf), "] * %d%%", (Neg ? -diff : diff));
		strConcat(dst, size, buf);
	}
}

inline double StrobeAPI::frequency(void)
{
	if (strobeMode)
		return (1 / ((1.0f / FPS()) * (abs(strobeMode) + 1)));
	else
		return 0.0;
}

inline double StrobeAPI::dutyCycle(void)
{
	return (((1.0f / (abs(strobeMode) + 1)) * 100) * (strobeMode < 0 ? -strobeMode : 1));
}

inline double StrobeAPI::positivePhaseShift(void)
{
	if (isPhaseInverted())
		return (1.0f / FPS()) * 1000;
	else
		return 0.0f;
}

inline double StrobeAPI::negativePhaseShift(void)
{
	return abs(strobeMode) * positivePhaseShift();
}

inline double StrobeAPI::period(void)
{
	return (1 / frequency() * 1000);
}

inline double StrobeAPI::geometricMean(double x, double y)
{
	return sqrt(abs(x * y));
}

inline double StrobeAPI::arithmeticMean(double x, double y)
{
	return (x + y) / 2;
}

inline double StrobeAPI::actualBrightnessReduction(void)
{
	return _lossCalculator<double>(FPS(), effectiveFPS());
}

inline double StrobeAPI::logarithmicBrightnessReduction(double base)
{
	return _lossCalculator<double>(log(base), log(base * effectiveFPS() / FPS()));
}

inline double StrobeAPI::squareBrightnessReduction(double base)
{
	return _lossCalculator<double>(sqrt(base), sqrt(base * effectiveFPS() / FPS()));
}

inline double StrobeAPI::cubeBrightnessReduction(double base)
{
	return _lossCalculator<double>(cbrt(base), cbrt(base * effectiveFPS() / FPS()));
}

inline double StrobeAPI::otherBrightnessReduction(double base, double (*reductionFunction)(double))
{
	return _lossCalculator<double>(reductionFunction(base), reductionFunction(base * effectiveFPS() / FPS()));
}

inline double StrobeAPI::badnessReduced(bool pwmInvolved)
{
	int pNCounter = counter.positiveRenderedFrameCount;
	int pBCounter = counter.positiveBlackFrameCount;
	int nNCounter = counter.negativeRenderedFrameCount;
	int nBCounter = counter.negativeBlackFrameCount;
	int nCounter = counter.totalNegative();
	int pCounter = counter.totalPositive();

	double badness, Diff;
	int diffP_NB, diffN_NB;
	double diffP = 0.0, diffN = 0.0;
	diffP_NB = (pNCounter - pBCounter);
	diffN_NB = (nNCounter - nBCounter);

	if (pCounter)
		diffP = abs(diffP_NB) * 100.0 / pCounter;

	if (nCounter)
		diffN = abs(diffN_NB) * 100.0 / nCounter;

	if (diffP_NB < 0.0)
		diffP = -diffP;
	if (diffN_NB < 0.0)
		diffN = -diffN;

	Diff = fabs(diffP - diffN);

	if (Diff < 0.0)
		Diff = 0.0;
	else if (Diff > 200.0)
		Diff = 200.0;

	badness = -log((200 - Diff) / (Diff));

	if (pwmInvolved)
		return (badness * period());
	else
		return badness;
}

inline double StrobeAPI::badness(bool pwmInvolved)
{
	int pNCounter = counter.positiveRenderedFrameCount;
	int pBCounter = counter.positiveBlackFrameCount;
	int nNCounter = counter.negativeRenderedFrameCount;
	int nBCounter = counter.negativeBlackFrameCount;
	int nCounter = counter.totalNegative();
	int pCounter = counter.totalPositive();

	int diffP_NB, diffN_NB;
	double diffP = 0.0, diffN = 0.0;
	double absoluteDifference = 0.0;
	double badness = 0.0;

	diffP_NB = (pNCounter - pBCounter);
	diffN_NB = (nNCounter - nBCounter);

	if (pCounter)
		diffP = abs(diffP_NB) * 100.0 / pCounter;

	if (nCounter)
		diffN = abs(diffN_NB) * 100.0 / nCounter;

	absoluteDifference = fabs(diffP - diffN);
	if (absoluteDifference > 100.0)
		absoluteDifference = 100.0;

	badness =
		-log(((absoluteDifference + geometricMean((100.0 - diffP), (100.0 - diffN))) / (absoluteDifference + geometricMean(diffP, diffN))));

	if (pwmInvolved)
		return (badness * period());
	else
		return badness;
}

inline int StrobeAPI::frameCount(CounterType type)
{
	int pNCounter = counter.positiveRenderedFrameCount;
	int pBCounter = counter.positiveBlackFrameCount;
	int nNCounter = counter.negativeRenderedFrameCount;
	int nBCounter = counter.negativeBlackFrameCount;
	int fCounter = counter.total();
	int nCounter = counter.totalNegative();
	int pCounter = counter.totalPositive();

	switch (type)
	{
	case (PositiveFrame):
	{
		return pCounter;
		break;
	}
	case (PositiveRenderedFrame):
	{
		return pNCounter;
		break;
	}
	case (PositiveBlackFrame):
	{
		return pBCounter;
		break;
	}
	case (NegativeFrame):
	{
		return nCounter;
		break;
	}
	case (NegativeRenderedFrame):
	{
		return nNCounter;
		break;
	}
	case (NegativeBlackFrame):
	{
		return nBCounter;
		break;
	}
	case (TotalFrame):
	default:
		return fCounter;
		break;
	}
}

inline void StrobeAPI::generateDebugInformation(void)
{
	char * const dst = debugInformation;

	int pNCounter = counter.positiveRenderedFrameCount;
	int pBCounter = counter.positiveBlackFrameCount;
	int nNCounter = counter.negativeRenderedFrameCount;
	int nBCounter = counter.negativeBlackFrameCount;
	int fCounter = counter.total();
	int nCounter = counter.totalNegative();
	int pCounter = counter.totalPositive();

	char modeDescription[128];
	modeDescription[0] = 0;
	char diffBarP[128], diffBarN[128], diffBarT[128];

	int diffP_NB, diffN_NB;
	double diffP = 0.0, diffN = 0.0;
	
	diffP_NB = (pNCounter - pBCounter);
	diffN_NB = (nNCounter - nBCounter);

	if (pCounter)
		diffP = abs(diffP_NB) * 100.0 / pCounter;

	if (nCounter)
		diffN = abs(diffN_NB) * 100.0 / nCounter;

	generateDiffBar(diffBarP, sizeof(diffBarP), PositiveDifference);
	generateDiffBar(diffBarN, sizeof(diffBarN), NegativeDifference);
	generateDiffBar(diffBarT, sizeof(diffBarT), TotalDifference);

	if (!strlen(modeDescription) && strobeMode < 10)
	{
		if (strobeMode != 0)
		{
			snprintf(modeDescription, sizeof(modeDescription), (strobeMode > 0 ? "%d [RENDER" : "%d [BLACK"), strobeMode);
			for (int k = 1; k <= abs(strobeMode); ++k)
			{
				strConcat(modeDescription, sizeof(modeDescription), (strobeMode > 0 ? " - BLACK" : " - RENDER"));
			}
			strConcat(modeDescription, sizeof(modeDescription), "]");
		}
		else
		{
			snprintf(modeDescription, sizeof(modeDescription), "[RENDER]");
		}
	}

	snprintf(dst, \
		4096, \
		"%.2f FPS\n" \
		"%.2f eFPS\n" \
		"Strobe Mode: %s\n" \
		"Phase Switch Interval: %d\n" \
		"Elapsed Time: %.2f\n" \
		"isPhaseInverted = %d\n" \
		"Total Frame Count: %d\n" \
		"(+) Phase Frame Count: %d\n" \
		" Rendered Frame Count: %d\n" \
		" Black Frame Count: %d\n" \
		"(-) Phase Frame Count: %d\n" \
		" Rendered Frame Count: %d\n" \
		" Black Frame Count: %d\n" \
		"=====ANALYSIS=====\n" \
		"PWM Simulation:\n" \
		" |-> Frequency: %.2f Hz\n" \
		" |-> Duty Cycle: %.2f%%\n" \
		" |-> Phase Shift: +%.4f msec || -%.4f msec\n" \
		" |-> Period: %.2f msec\n" \
		"Brightness Reduction:\n" \
		" |-> [LINEAR] Actual Reduction: %.2f%%\n" \
		" |-> [LOG] Realistic Reduction (350 cd/m2 base): %.2f%%\n" \
		" |-> [SQUARE] Realistic Reduction (350 cd/m2 base): %.2f%%\n" \
		" |-> [CUBE] Realistic Reduction (350 cd/m2 base): %.2f%%\n" \
		"Difference (+): %s\n" \
		"Difference (-): %s\n" \
		"Difference (TOTAL): %s\n" \
		"Geometric Mean: %.4f\n" \
		"Geometric / Arithmetic Mean Difference: %.4f\n" \
		"[EXPERIMENTAL] Badness: %.4f\n" \
		"[EXPERIMENTAL] Badness x PWM Period: %.4f\n" \
		"[EXPERIMENTAL] Badness (Reduced): %.4f\n" \
		"[EXPERIMENTAL] Badness (Reduced) x PWM Period: %.4f\n" \
		"=====ANALYSIS=====\n", \
		FPS(), \
		effectiveFPS(), \
		modeDescription, \
		switchInterval, \
		strobeTimer.elapsedSeconds(), \
		isPhaseInverted(), \
		fCounter, \
		pCounter, \
		pNCounter, \
		pBCounter, \
		nCounter, \
		nNCounter, \
		nBCounter, \
		frequency(), \
		dutyCycle(), \
		positivePhaseShift(), \
		negativePhaseShift(), \
		period(), \
		actualBrightnessReduction(), \
		logarithmicBrightnessReduction(350.0), \
		squareBrightnessReduction(350.0), \
		cubeBrightnessReduction(350.0), \
		diffBarP, \
		diffBarN, \
		diffBarT, \
		geometricMean(diffP, diffN), \
		arithmeticMean(diffP, diffN) - geometricMean(diffP, diffN), \
		badness(false), \
		badness(true), \
		badnessReduced(false), \
		badnessReduced(true) \
		);
}

inline bool StrobeAPI::isActive()
{
	return true;
}

inline void StrobeAPI::setMode(int mode)
{
	strobeMode = mode;
}

inline int StrobeAPI::getStrobeMode(void)
{
	return strobeMode;
}

inline bool StrobeAPI::processFrame(void)
{
	if ((frameState & FRAME_RENDER) || !isActive())
	{
		if (frameState & PHASE_POSITIVE)
			++counter.positiveRenderedFrameCount;
		else
			++counter.negativeRenderedFrameCount;

		return true;
	}
	else
	{
		if (frameState & PHASE_POSITIVE)
			++counter.positiveBlackFrameCount;
		else
			++counter.negativeBlackFrameCount;

		return false;
	}
}

inline void StrobeAPI::strConcat(char * const dst, int size, const char * const src)
{
	if (!strlen(src))
		return;

	if (sizeof(char) * (strlen(dst) + strlen(src)) > size)
		return;

	char* const buf = (char*)malloc(size);

	snprintf(buf, size, "%s%s", dst, src);
	if (buf == nullptr)
		return;

	snprintf(dst, size, "%s", buf);

	free(buf);
}
<file_sep>/strobe/test.cpp
/*
MIT License

Copyright (c) 2018 fuzun

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#include <cstdio>
#include <cassert>
#include <ctime>

#include "strobe-core.h"

class Timer // Simple timer. Gathered from the internet
{
public:
	inline Timer()
	{
		init();
	}

	inline void restart()
	{
		init();
	}

	inline double elapsedMilliseconds()
	{
		std::chrono::time_point<std::chrono::system_clock> endTime;
		endTime = std::chrono::system_clock::now();

		return std::chrono::duration_cast<std::chrono::milliseconds>(endTime - m_StartTime).count();
	}

	inline double elapsedSeconds()
	{
		return elapsedMilliseconds() / 1000.0;
	}

private:
	inline void init()
	{
		m_StartTime = std::chrono::system_clock::now();
	}

	std::chrono::time_point<std::chrono::system_clock> m_StartTime;
};


double random(int min, int max)
{
	int min_ = min * 100;
	int max_ = max * 100;
	return (rand() % (max_ - min_ + 1) + min_) / 100.0;
}

struct StrobeOutput
{
	double eFPS;
	double fps_;
	int totalFrameCount;
	int positiveFrameCount;
	int negativeFrameCount;
	int positiveBlackFrameCount;
	int negativeBlackFrameCount;
	int positiveRenderedFrameCount;
	int negativeRenderedFrameCount;
	double frequency;
	double period;
	double brightnessReduction;
	double badness;
	double badnessPWM;
	double badnessReduced;
	double badnessReducedPwm;
};

void assign(StrobeOutput * const output, StrobeAPI * const strobe)
{
	output->eFPS                       = strobe->effectiveFPS();
	output->fps_                       = strobe->FPS();
	output->totalFrameCount            = strobe->frameCount(StrobeAPI::TotalFrame);
	output->positiveFrameCount         = strobe->frameCount(StrobeAPI::PositiveFrame);
	output->negativeFrameCount         = strobe->frameCount(StrobeAPI::NegativeFrame);
	output->positiveBlackFrameCount    = strobe->frameCount(StrobeAPI::PositiveBlackFrame);
	output->negativeBlackFrameCount    = strobe->frameCount(StrobeAPI::NegativeBlackFrame);
	output->positiveRenderedFrameCount = strobe->frameCount(StrobeAPI::PositiveRenderedFrame);
	output->negativeRenderedFrameCount = strobe->frameCount(StrobeAPI::NegativeRenderedFrame);
	output->frequency                  = strobe->frequency();
	output->period                     = strobe->period();
	output->brightnessReduction        = strobe->actualBrightnessReduction();
	output->badness                    = strobe->badness(false);
	output->badnessPWM                 = strobe->badness(true);
	output->badnessReduced             = strobe->badnessReduced(false);
	output->badnessReducedPwm          = strobe->badnessReduced(true);
}

int main()
{
	StrobeAPI *strobe;
	Timer timer;
	StrobeOutput output;
	double fps;

	srand((unsigned int)time(NULL));
	memset(&output, 0, sizeof(output));

	strobe = new StrobeCore(1, 1, false);
	while (timer.elapsedSeconds() < 10)
	{
		fps = random(58, 62);
		strobe->setFPS(fps);
		strobe->strobe();
		printf("%s", strobe->getDebugInformation());

		assign(&output, strobe);

		assert(strobe->getPhaseSwitchInterval() == 1);
		assert(strobe->getStrobeMode() == 1);
		assert(output.fps_ >= 58 && output.fps_ <= 62);
		assert(output.eFPS == output.fps_ / 2);
		assert(output.totalFrameCount == output.negativeFrameCount + output.positiveFrameCount);
		assert(output.positiveFrameCount == output.positiveBlackFrameCount + output.positiveRenderedFrameCount);
		assert(output.negativeFrameCount == output.negativeBlackFrameCount + output.negativeRenderedFrameCount);
		assert(output.frequency > 0);
		assert(fabs(output.frequency - output.eFPS) < 1);
		assert(fabs(output.frequency - 1000 / output.period) < 0.1);
		assert(output.brightnessReduction == (output.fps_ - output.eFPS) * 100.0 / output.fps_);
		assert(output.badnessPWM == output.badness * output.period);
		assert(output.badnessReducedPwm == output.badnessReduced * output.period);
	}
	fflush(stdout);
	delete strobe;
	
	strobe = new StrobeCore(2, 3, false);
	timer.restart();
	while (timer.elapsedSeconds() < 10)
	{
		fps = random(120, 144);
		strobe->setFPS(fps);
		strobe->strobe();
		printf("%s", strobe->getDebugInformation());

		assign(&output, strobe);

		assert(strobe->getPhaseSwitchInterval() == 3);
		assert(strobe->getStrobeMode() == 2);
		assert(output.fps_ >= 120 && output.fps_ <= 144);
		assert(output.eFPS == output.fps_ / 3);
		assert(output.totalFrameCount == output.negativeFrameCount + output.positiveFrameCount);
		assert(output.positiveFrameCount == output.positiveBlackFrameCount + output.positiveRenderedFrameCount);
		assert(output.negativeFrameCount == output.negativeBlackFrameCount + output.negativeRenderedFrameCount);
		assert(output.frequency > 0);
		assert(fabs(output.frequency - 1000 / output.period) < 0.1);
		assert(output.brightnessReduction == (output.fps_ - output.eFPS) * 100.0 / output.fps_);
		assert(output.badnessPWM == output.badness * output.period);
		assert(output.badnessReducedPwm == output.badnessReduced * output.period);
	}
	fflush(stdout);
	delete strobe;

	strobe = new StrobeCore(0, 0, false);
	timer.restart();
	while (timer.elapsedSeconds() < 10)
	{
		fps = random(29, 31);
		strobe->setFPS(fps);
		bool result = strobe->strobe();
		printf("%s", strobe->getDebugInformation());

		assign(&output, strobe);

		assert(strobe->getPhaseSwitchInterval() == 0);
		assert(strobe->getStrobeMode() == 0);
		assert(result);
		assert(output.fps_ >= 29 && output.fps_ <= 31);
		assert(output.eFPS == output.fps_);
		assert(output.totalFrameCount == output.negativeFrameCount + output.positiveFrameCount);
		assert(output.positiveFrameCount == output.positiveBlackFrameCount + output.positiveRenderedFrameCount);
		assert(output.negativeFrameCount == output.negativeBlackFrameCount + output.negativeRenderedFrameCount);
		assert(output.negativeBlackFrameCount == 0);
		assert(output.positiveBlackFrameCount == 0);
		assert(output.frequency == 0.0);
		assert(output.period >= DBL_MAX);
		assert(output.brightnessReduction == 0.0);
		assert(output.badnessReduced <= DBL_MIN);
	}
	fflush(stdout);
	delete strobe;

	strobe = new StrobeCore(-3, 2, false);
	timer.restart();
	while (timer.elapsedSeconds() < 10)
	{
		fps = random(950, 1050);
		strobe->setFPS(fps);
		strobe->strobe();
		printf("%s", strobe->getDebugInformation());

		assign(&output, strobe);

		assert(strobe->getPhaseSwitchInterval() == 2);
		assert(strobe->getStrobeMode() == -3);
		assert(output.fps_ >= 950 && output.fps_ <= 1050);
		assert(output.eFPS == output.fps_ * 3 / 4);
		assert(output.totalFrameCount == output.negativeFrameCount + output.positiveFrameCount);
		assert(output.positiveFrameCount == output.positiveBlackFrameCount + output.positiveRenderedFrameCount);
		assert(output.negativeFrameCount == output.negativeBlackFrameCount + output.negativeRenderedFrameCount);
		assert(output.frequency > 0);
		assert(fabs(output.frequency - 1000 / output.period) < 0.1);
		assert(output.brightnessReduction < 50.0);
		assert(output.badnessPWM == output.badness * output.period);
		assert(output.badnessReducedPwm == output.badnessReduced * output.period);
	}
	fflush(stdout);
	delete strobe;

	strobe = new StrobeCore(4, 3, true);
	timer.restart();
	while (timer.elapsedSeconds() < 10)
	{
		strobe->strobe();
		printf("%s", strobe->getDebugInformation());

		assign(&output, strobe);

		assert(strobe->getPhaseSwitchInterval() == 3);
		assert(strobe->getStrobeMode() == 4);
		assert(output.totalFrameCount == output.negativeFrameCount + output.positiveFrameCount);
		assert(output.positiveFrameCount == output.positiveBlackFrameCount + output.positiveRenderedFrameCount);
		assert(output.negativeFrameCount == output.negativeBlackFrameCount + output.negativeRenderedFrameCount);
	}
	fflush(stdout);
	delete strobe;

	strobe = new StrobeCore(1, 0, false);
	strobe->disable();
	assert(!strobe->isActive());
	timer.restart();
	while (timer.elapsedSeconds() < 10)
	{
		fps = random(119, 121);
		strobe->setFPS(fps);
		strobe->strobe();
		printf("%s", strobe->getDebugInformation());

		assign(&output, strobe);

		assert(strobe->getPhaseSwitchInterval() == 0);
		assert(strobe->getStrobeMode() == 1);
		assert(output.fps_ >= 119 && output.fps_ <= 121);
		assert(output.eFPS == output.fps_);
		assert(output.totalFrameCount == output.negativeFrameCount + output.positiveFrameCount);
		assert(output.positiveFrameCount == output.positiveBlackFrameCount + output.positiveRenderedFrameCount);
		assert(output.negativeFrameCount == output.negativeBlackFrameCount + output.negativeRenderedFrameCount);
		assert(output.negativeBlackFrameCount == 0);
		assert(output.positiveBlackFrameCount == 0);
		assert(output.frequency == 0.0);
		assert(output.period >= DBL_MAX);
		assert(output.brightnessReduction == 0.0);
		assert(output.badnessReduced <= DBL_MIN);
	}
	strobe->enable();
	timer.restart();
	assert(strobe->isActive());
	assert(strobe->getStrobeMode() == 1);
	assert(strobe->getPhaseSwitchInterval() == 0);

	fflush(stdout);
	delete strobe;

	return 0;
}
