/*
 *
 *
 *    bitclock.h
 *
 *    Clock restoration logic.
 *
 *    License: GNU General Public License Version 3.0.
 *    
 *    Copyright (C) 2016 by Matthew K. Roberts, KK5JY. All rights reserved.
 *
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *    
 *    This program is distributed in the hope that it will be useful, but
 *    WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    General Public License for more details.
 *    
 *    You should have received a copy of the GNU General Public License
 *    along with this program.  If not, see: http://www.gnu.org/licenses/
 *
 *
 */

#ifndef __KK5JY_BITCLOCK_H
#define __KK5JY_BITCLOCK_H

#ifdef DEBUG
#include <iostream>
#endif

#include <stdint.h>
#include "fixed.h"

namespace KK5JY {
	namespace DSP {
		template <typename sample_t = int32_t, typename expsm_t = double>
		class BitClock {
			private:
				// the free-running period
				const sample_t m_Period;
				const sample_t m_HalfPeriod;
				const expsm_t m_InvPeriod;

				// the "rails" for modified period
				const sample_t m_MinError;
				const sample_t m_MaxError;
				const expsm_t m_Alpha;
				const expsm_t m_Alpha_1m;

				// the current state
				sample_t m_Error;

			public:
				// ctors
				BitClock(sample_t period, sample_t margin = 2, expsm_t alpha = 0.1);

			public:
				// accessors
				sample_t Period() const { return m_Period; }
				expsm_t Error() const { return m_Error; }

				// clock routine; run once per signal edge
				sample_t run(sample_t input);
		};

		//
		//  ctor
		//
		template <typename sample_t, typename expsm_t>
		inline BitClock<sample_t, expsm_t>::BitClock(sample_t period, sample_t margin, expsm_t alpha)
			: m_Period(period),
			  m_HalfPeriod(period / 2),
			  m_InvPeriod(1.0 / period),
			  m_MinError(-((sample_t)(margin))),
			  m_MaxError((sample_t)(margin)),
			  m_Alpha(alpha),
			  m_Alpha_1m(1.0 - alpha),
			  m_Error(0.0) {
			// nop
		}

		//
		//  BitClock::run(...) - run once per clock edge; returns the expected period
		//                       until the next clock edge; input is the distance from
		//                       the current event to the previous one
		//
		template <typename sample_t, typename expsm_t>
		inline sample_t BitClock<sample_t, expsm_t>::run(sample_t input) {
			#ifdef DEBUG
			std::cerr << "run(" << input << "): m_Period = " << m_Period << std::endl;
			#endif

			// if this is just a glitch, skip it
			if (input <= m_Period / 4) {
				return (sample_t)(m_Period - m_Error);
			}

			// Assume any missing bits are perfect bits; this is for streams
			//    with no built-in clock, to rest the clock error accumulator
			//    when there is no data.
			sample_t bits = input * m_InvPeriod;
			for (sample_t i = 0; i != bits; ++i) {
				m_Error = /* (m_Alpha * 0.0) + */ (m_Alpha_1m * m_Error);
			}

			// compute the remainder (portion of one bit)
			sample_t rem = input - (bits * m_Period);

			// compute minimum-distance error to the remainder
			sample_t bitError;
			if (rem >= m_HalfPeriod)
				bitError = rem; //(m_Period - rem); // short bit
			else
				bitError = -rem; // long bit

			// exp-smoothe the error, adding the most recent bit
			m_Error = (m_Alpha * bitError) + (m_Alpha_1m * m_Error);

			// clamp to rails
			if (m_Error > m_MaxError)
				m_Error = m_MaxError;
			else if (m_Error <= m_MinError)
				m_Error = m_MinError;

			// return the modified period
			return (sample_t)(m_Period - m_Error);
		}
	}
}

#endif // __KK5JY_BITCLOCK_H
