andrewm@0: /*
andrewm@0: TouchKeys: multi-touch musical keyboard control software
andrewm@0: Copyright (c) 2013 Andrew McPherson
andrewm@0:
andrewm@0: This program is free software: you can redistribute it and/or modify
andrewm@0: it under the terms of the GNU General Public License as published by
andrewm@0: the Free Software Foundation, either version 3 of the License, or
andrewm@0: (at your option) any later version.
andrewm@0:
andrewm@0: This program is distributed in the hope that it will be useful,
andrewm@0: but WITHOUT ANY WARRANTY; without even the implied warranty of
andrewm@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
andrewm@0: GNU General Public License for more details.
andrewm@0:
andrewm@0: You should have received a copy of the GNU General Public License
andrewm@0: along with this program. If not, see .
andrewm@0:
andrewm@0: =====================================================================
andrewm@0:
andrewm@0: TimestampSynchronizer.cpp: handles aligning timestamps between multiple
andrewm@0: asynchronous sources, while reducing the jitter that occurs when using
andrewm@0: system clock time for every received sample.
andrewm@0: */
andrewm@0:
andrewm@0: #include "TimestampSynchronizer.h"
andrewm@0:
andrewm@0: // Constructor
andrewm@0: TimestampSynchronizer::TimestampSynchronizer()
andrewm@0: : history_(kTimestampSynchronizerHistoryLength), nominalSampleInterval_(0), currentSampleInterval_(0),
andrewm@0: frameModulus_(0), startingClockTimeMilliseconds_(0), startingTimestamp_(0),
andrewm@0: bufferLengthCounter_(0)
andrewm@0: {
andrewm@0: }
andrewm@0:
andrewm@0: // Clear the accumulated timestamp history and reset the current
andrewm@0: // value to its nominal "expected" value. Also (re-)establish
andrewm@0: // the relationship between system clock time and output timestamp.
andrewm@0: // If multiple streams are to be synchronized, they should be
andrewm@0: // initialized with the same values
andrewm@0:
andrewm@0: void TimestampSynchronizer::initialize(double clockTimeMilliseconds,
andrewm@0: timestamp_type startingTimestamp) {
andrewm@0: history_.clear();
andrewm@0: currentSampleInterval_ = nominalSampleInterval_;
andrewm@0: startingClockTimeMilliseconds_ = clockTimeMilliseconds;
andrewm@0: startingTimestamp_ = startingTimestamp;
andrewm@0:
andrewm@0: //cout << "initialize(): startingTimestamp = " << startingTimestamp_ << ", interval = " << nominalSampleInterval_ << endl;
andrewm@0: }
andrewm@0:
andrewm@0: // Given a frame number, calculate a current timestamp
andrewm@0: timestamp_type TimestampSynchronizer::synchronizedTimestamp(int rawFrameNumber) {
andrewm@0: // Calculate the current system clock-related timestamp
andrewm@0: timestamp_type clockTime = startingTimestamp_ + milliseconds_to_timestamp(Time::getMillisecondCounterHiRes() - startingClockTimeMilliseconds_);
andrewm@0: timestamp_type frameTime;
andrewm@0:
andrewm@0: // Retrieve the timestamp of the previous frame
andrewm@0: // Need at least 2 samples in the buffer for the calculations that follow
andrewm@0: if(history_.empty()) {
andrewm@0: frameTime = clockTime;
andrewm@0: }
andrewm@0: else if(history_.size() < 2) {
andrewm@0: // One sample in buffer: make sure the new sample is new before
andrewm@0: // storing it in the buffer.
andrewm@0:
andrewm@0: int lastFrame = history_.latest().first;
andrewm@0:
andrewm@0: frameTime = clockTime;
andrewm@0:
andrewm@0: if(lastFrame == rawFrameNumber) // Don't reprocess identical frames
andrewm@0: return frameTime;
andrewm@0: }
andrewm@0: else {
andrewm@0: int totalHistoryFrames;
andrewm@0: int lastFrame = history_.latest().first;
andrewm@0: frameTime = history_.latest().second;
andrewm@0:
andrewm@0: if(lastFrame == rawFrameNumber) // Don't reprocess identical frames
andrewm@0: return frameTime;
andrewm@0:
andrewm@0: if(frameModulus_ == 0) {
andrewm@0: // No modulus, just compare the raw frame number to the last frame number
andrewm@0: frameTime += currentSampleInterval_ * (timestamp_type)(rawFrameNumber - lastFrame);
andrewm@0:
andrewm@0: totalHistoryFrames = (history_.latest().first - history_.earliest().first);
andrewm@0: if(totalHistoryFrames <= 0) {
andrewm@0: //cout << "Warning: TimestampSynchronizer history buffer has a difference of " << totalHistoryFrames << " frames.\n";
andrewm@0: //cout << "Size = " << history_.size() << " first = " << history_.earliest().first << " last = " << history_.latest().first << endl;
andrewm@0: totalHistoryFrames = 1;
andrewm@0: }
andrewm@0: }
andrewm@0: else {
andrewm@0: // Use mod arithmetic to handle wraparounds in the frame number
andrewm@0: frameTime += currentSampleInterval_ * (timestamp_type)((rawFrameNumber + frameModulus_ - lastFrame) % frameModulus_);
andrewm@0:
andrewm@0: totalHistoryFrames = (history_.latest().first - history_.earliest().first + frameModulus_) % frameModulus_;
andrewm@0: if(totalHistoryFrames <= 0) {
andrewm@0: //cout << "Warning: TimestampSynchronizer history buffer has a difference of " << totalHistoryFrames << " frames.\n";
andrewm@0: //cout << "Size = " << history_.size() << " first = " << history_.earliest().first << " last = " << history_.latest().first << endl;
andrewm@0:
andrewm@0: totalHistoryFrames = 1;
andrewm@0: }
andrewm@0: }
andrewm@0:
andrewm@0: // Recalculate the nominal sample interval by examining the difference in times
andrewm@0: // between first and last frames in the buffer.
andrewm@0:
andrewm@0: currentSampleInterval_ = (history_.latestTimestamp() - history_.earliestTimestamp()) / (timestamp_diff_type)totalHistoryFrames;
andrewm@0:
andrewm@0: // The frame time was just incremented by the current sample period. Check whether
andrewm@0: // this puts the frame time ahead of the clock time. Don't allow the frame time to get
andrewm@0: // ahead of the system clock (this will also push future frame timestamps back).
andrewm@0:
andrewm@0: if(frameTime > clockTime) {
andrewm@0: //cout << "CLIP " << 100.0 * (frameTime - clockTime) / currentSampleInterval_ << "%: frame=" << frameTime << " to clock=" << clockTime << endl;
andrewm@0: frameTime = clockTime;
andrewm@0: }
andrewm@0:
andrewm@0: bufferLengthCounter_++;
andrewm@0:
andrewm@0: if(bufferLengthCounter_ >= kTimestampSynchronizerHistoryLength) {
andrewm@0: //timestamp_diff_type currentLatency = clockTime - frameTime;
andrewm@0: timestamp_diff_type maxLatency = 0, minLatency = 1000000.0;
andrewm@0:
andrewm@0: Node >::iterator it;
andrewm@0:
andrewm@0: for(it = history_.begin(); it != history_.end(); ++it) {
andrewm@0: timestamp_diff_type l = (it.timestamp() - it->second);
andrewm@0: if(l > maxLatency)
andrewm@0: maxLatency = l;
andrewm@0: if(l < minLatency)
andrewm@0: minLatency = l;
andrewm@0: }
andrewm@0:
andrewm@0: //cout << "frame " << rawFrameNumber << ": rate = " << currentSampleInterval_ << " clock = " << clockTime << " frame = " << frameTime << " latency = "
andrewm@0: // << currentLatency << " max = " << maxLatency << " min = " << minLatency << endl;
andrewm@0:
andrewm@0: //timestamp_diff_type targetMinLatency = (maxLatency - minLatency) * 2.0 / sqrt(kTimestampSynchronizerHistoryLength);
andrewm@0:
andrewm@0: /*if(minLatency > targetMinLatency) {
andrewm@0: cout << "ADDING " << 50.0 * (minLatency - targetMinLatency) / (currentSampleInterval_) << "%: (target " << targetMinLatency << ")\n";
andrewm@0: frameTime += (minLatency - targetMinLatency) / 2.0;
andrewm@0: }*/
andrewm@0: //frameTime += minLatency / 4.0;
andrewm@0:
andrewm@0: bufferLengthCounter_ = 0;
andrewm@0: }
andrewm@0: }
andrewm@0:
andrewm@0: // Insert the new frame time and clock times into the buffer
andrewm@0: history_.insert(pair(rawFrameNumber, frameTime), clockTime);
andrewm@0:
andrewm@0: // The timestamp we return is associated with the frame, not the clock (which is potentially much
andrewm@0: // higher jitter)
andrewm@0: return frameTime;
andrewm@0: }