view Source/Utility/TimestampSynchronizer.cpp @ 20:dfff66c07936

Lots of minor changes to support building on Visual Studio. A few MSVC-specific #ifdefs to eliminate things Visual Studio doesn't like. This version now compiles on Windows (provided liblo, Juce and pthread are present) but the TouchKeys device support is not yet enabled. Also, the code now needs to be re-checked on Mac and Linux.
author Andrew McPherson <andrewm@eecs.qmul.ac.uk>
date Sun, 09 Feb 2014 18:40:51 +0000
parents 3580ffe87dc8
children
line wrap: on
line source
/*
  TouchKeys: multi-touch musical keyboard control software
  Copyright (c) 2013 Andrew McPherson

  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/>.
 
  =====================================================================

  TimestampSynchronizer.cpp: handles aligning timestamps between multiple
  asynchronous sources, while reducing the jitter that occurs when using
  system clock time for every received sample.
*/

#include "TimestampSynchronizer.h"

// Constructor
TimestampSynchronizer::TimestampSynchronizer()
: history_(kTimestampSynchronizerHistoryLength), nominalSampleInterval_(0), currentSampleInterval_(0),
frameModulus_(0), startingClockTimeMilliseconds_(0), startingTimestamp_(0), 
bufferLengthCounter_(0)
{
}

// Clear the accumulated timestamp history and reset the current
// value to its nominal "expected" value.  Also (re-)establish
// the relationship between system clock time and output timestamp.
// If multiple streams are to be synchronized, they should be
// initialized with the same values

void TimestampSynchronizer::initialize(double clockTimeMilliseconds,
									   timestamp_type startingTimestamp) {
	history_.clear();
	currentSampleInterval_ = nominalSampleInterval_;
	startingClockTimeMilliseconds_ = clockTimeMilliseconds;
	startingTimestamp_ = startingTimestamp;
	
	//cout << "initialize(): startingTimestamp = " << startingTimestamp_ << ", interval = " << nominalSampleInterval_ << endl;
}

// Given a frame number, calculate a current timestamp
timestamp_type TimestampSynchronizer::synchronizedTimestamp(int rawFrameNumber) {
	// Calculate the current system clock-related timestamp
	timestamp_type clockTime = startingTimestamp_ + milliseconds_to_timestamp(Time::getMillisecondCounterHiRes() - startingClockTimeMilliseconds_);
	timestamp_type frameTime;

	// Retrieve the timestamp of the previous frame
	// Need at least 2 samples in the buffer for the calculations that follow
	if(history_.empty()) {
		frameTime = clockTime;
	}
	else if(history_.size() < 2) {
		// One sample in buffer: make sure the new sample is new before
		// storing it in the buffer.
		
		int lastFrame = history_.latest().first;
		
		frameTime = clockTime;
		
		if(lastFrame == rawFrameNumber) // Don't reprocess identical frames
			return frameTime;		
	}
	else {
		int totalHistoryFrames;		
		int lastFrame = history_.latest().first;
		frameTime = history_.latest().second;
		
		if(lastFrame == rawFrameNumber) // Don't reprocess identical frames
			return frameTime;
			
		if(frameModulus_ == 0) {
			// No modulus, just compare the raw frame number to the last frame number
			frameTime += currentSampleInterval_ * (timestamp_type)(rawFrameNumber - lastFrame);
			
			totalHistoryFrames = (history_.latest().first - history_.earliest().first);
			if(totalHistoryFrames <= 0) {
				//cout << "Warning: TimestampSynchronizer history buffer has a difference of " << totalHistoryFrames << " frames.\n";
				//cout << "Size = " << history_.size() << " first = " << history_.earliest().first << " last = " << history_.latest().first << endl;
				totalHistoryFrames = 1;
			}
		}
		else {
			// Use mod arithmetic to handle wraparounds in the frame number
			frameTime += currentSampleInterval_ * (timestamp_type)((rawFrameNumber + frameModulus_ - lastFrame) % frameModulus_);
			
			totalHistoryFrames = (history_.latest().first - history_.earliest().first + frameModulus_) % frameModulus_;
			if(totalHistoryFrames <= 0) {
				//cout << "Warning: TimestampSynchronizer history buffer has a difference of " << totalHistoryFrames << " frames.\n";
				//cout << "Size = " << history_.size() << " first = " << history_.earliest().first << " last = " << history_.latest().first << endl;

				totalHistoryFrames = 1;
			}			
		}
		
		// Recalculate the nominal sample interval by examining the difference in times
		// between first and last frames in the buffer.
		
		currentSampleInterval_ = (history_.latestTimestamp() - history_.earliestTimestamp()) / (timestamp_diff_type)totalHistoryFrames;
		
		// The frame time was just incremented by the current sample period.  Check whether
		// this puts the frame time ahead of the clock time.  Don't allow the frame time to get
		// ahead of the system clock (this will also push future frame timestamps back).
		
		if(frameTime > clockTime) {
			//cout << "CLIP " << 100.0 * (frameTime - clockTime) / currentSampleInterval_ << "%: frame=" << frameTime << " to clock=" << clockTime << endl;
			frameTime = clockTime;			
		}
		
		bufferLengthCounter_++;
		
		if(bufferLengthCounter_ >= kTimestampSynchronizerHistoryLength) {
			//timestamp_diff_type currentLatency = clockTime - frameTime;
			timestamp_diff_type maxLatency = 0, minLatency = 1000000.0;
			
			Node<pair<int, timestamp_type> >::iterator it;
			
			for(it = history_.begin(); it != history_.end(); ++it) {
				timestamp_diff_type l = (it.timestamp() - it->second);
				if(l > maxLatency)
					maxLatency = l;
				if(l < minLatency)
					minLatency = l;
			}
			
			//cout << "frame " << rawFrameNumber << ": rate = " << currentSampleInterval_ << " clock = " << clockTime << " frame = " << frameTime << " latency = " 
			//	<< currentLatency << " max = " << maxLatency << " min = " << minLatency << endl;
			
			//timestamp_diff_type targetMinLatency = (maxLatency - minLatency) * 2.0 / sqrt(kTimestampSynchronizerHistoryLength);
			
			/*if(minLatency > targetMinLatency) {
				cout << "ADDING " << 50.0 * (minLatency - targetMinLatency) / (currentSampleInterval_) << "%: (target " << targetMinLatency << ")\n";
				frameTime += (minLatency - targetMinLatency) / 2.0;
			}*/
			//frameTime += minLatency / 4.0;
			
			bufferLengthCounter_ = 0;
		}
	}
	
	// Insert the new frame time and clock times into the buffer
	history_.insert(pair<int, timestamp_type>(rawFrameNumber, frameTime), clockTime);

	// The timestamp we return is associated with the frame, not the clock (which is potentially much
	// higher jitter)
	return frameTime;
}