andrewm@0
|
1 /*
|
andrewm@0
|
2 TouchKeys: multi-touch musical keyboard control software
|
andrewm@0
|
3 Copyright (c) 2013 Andrew McPherson
|
andrewm@0
|
4
|
andrewm@0
|
5 This program is free software: you can redistribute it and/or modify
|
andrewm@0
|
6 it under the terms of the GNU General Public License as published by
|
andrewm@0
|
7 the Free Software Foundation, either version 3 of the License, or
|
andrewm@0
|
8 (at your option) any later version.
|
andrewm@0
|
9
|
andrewm@0
|
10 This program is distributed in the hope that it will be useful,
|
andrewm@0
|
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
|
andrewm@0
|
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
andrewm@0
|
13 GNU General Public License for more details.
|
andrewm@0
|
14
|
andrewm@0
|
15 You should have received a copy of the GNU General Public License
|
andrewm@0
|
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
|
andrewm@0
|
17
|
andrewm@0
|
18 =====================================================================
|
andrewm@0
|
19
|
andrewm@0
|
20 TimestampSynchronizer.cpp: handles aligning timestamps between multiple
|
andrewm@0
|
21 asynchronous sources, while reducing the jitter that occurs when using
|
andrewm@0
|
22 system clock time for every received sample.
|
andrewm@0
|
23 */
|
andrewm@0
|
24
|
andrewm@0
|
25 #include "TimestampSynchronizer.h"
|
andrewm@0
|
26
|
andrewm@0
|
27 // Constructor
|
andrewm@0
|
28 TimestampSynchronizer::TimestampSynchronizer()
|
andrewm@0
|
29 : history_(kTimestampSynchronizerHistoryLength), nominalSampleInterval_(0), currentSampleInterval_(0),
|
andrewm@0
|
30 frameModulus_(0), startingClockTimeMilliseconds_(0), startingTimestamp_(0),
|
andrewm@0
|
31 bufferLengthCounter_(0)
|
andrewm@0
|
32 {
|
andrewm@0
|
33 }
|
andrewm@0
|
34
|
andrewm@0
|
35 // Clear the accumulated timestamp history and reset the current
|
andrewm@0
|
36 // value to its nominal "expected" value. Also (re-)establish
|
andrewm@0
|
37 // the relationship between system clock time and output timestamp.
|
andrewm@0
|
38 // If multiple streams are to be synchronized, they should be
|
andrewm@0
|
39 // initialized with the same values
|
andrewm@0
|
40
|
andrewm@0
|
41 void TimestampSynchronizer::initialize(double clockTimeMilliseconds,
|
andrewm@0
|
42 timestamp_type startingTimestamp) {
|
andrewm@0
|
43 history_.clear();
|
andrewm@0
|
44 currentSampleInterval_ = nominalSampleInterval_;
|
andrewm@0
|
45 startingClockTimeMilliseconds_ = clockTimeMilliseconds;
|
andrewm@0
|
46 startingTimestamp_ = startingTimestamp;
|
andrewm@0
|
47
|
andrewm@0
|
48 //cout << "initialize(): startingTimestamp = " << startingTimestamp_ << ", interval = " << nominalSampleInterval_ << endl;
|
andrewm@0
|
49 }
|
andrewm@0
|
50
|
andrewm@0
|
51 // Given a frame number, calculate a current timestamp
|
andrewm@0
|
52 timestamp_type TimestampSynchronizer::synchronizedTimestamp(int rawFrameNumber) {
|
andrewm@0
|
53 // Calculate the current system clock-related timestamp
|
andrewm@0
|
54 timestamp_type clockTime = startingTimestamp_ + milliseconds_to_timestamp(Time::getMillisecondCounterHiRes() - startingClockTimeMilliseconds_);
|
andrewm@0
|
55 timestamp_type frameTime;
|
andrewm@0
|
56
|
andrewm@0
|
57 // Retrieve the timestamp of the previous frame
|
andrewm@0
|
58 // Need at least 2 samples in the buffer for the calculations that follow
|
andrewm@0
|
59 if(history_.empty()) {
|
andrewm@0
|
60 frameTime = clockTime;
|
andrewm@0
|
61 }
|
andrewm@0
|
62 else if(history_.size() < 2) {
|
andrewm@0
|
63 // One sample in buffer: make sure the new sample is new before
|
andrewm@0
|
64 // storing it in the buffer.
|
andrewm@0
|
65
|
andrewm@0
|
66 int lastFrame = history_.latest().first;
|
andrewm@0
|
67
|
andrewm@0
|
68 frameTime = clockTime;
|
andrewm@0
|
69
|
andrewm@0
|
70 if(lastFrame == rawFrameNumber) // Don't reprocess identical frames
|
andrewm@0
|
71 return frameTime;
|
andrewm@0
|
72 }
|
andrewm@0
|
73 else {
|
andrewm@0
|
74 int totalHistoryFrames;
|
andrewm@0
|
75 int lastFrame = history_.latest().first;
|
andrewm@0
|
76 frameTime = history_.latest().second;
|
andrewm@0
|
77
|
andrewm@0
|
78 if(lastFrame == rawFrameNumber) // Don't reprocess identical frames
|
andrewm@0
|
79 return frameTime;
|
andrewm@0
|
80
|
andrewm@0
|
81 if(frameModulus_ == 0) {
|
andrewm@0
|
82 // No modulus, just compare the raw frame number to the last frame number
|
andrewm@0
|
83 frameTime += currentSampleInterval_ * (timestamp_type)(rawFrameNumber - lastFrame);
|
andrewm@0
|
84
|
andrewm@0
|
85 totalHistoryFrames = (history_.latest().first - history_.earliest().first);
|
andrewm@0
|
86 if(totalHistoryFrames <= 0) {
|
andrewm@0
|
87 //cout << "Warning: TimestampSynchronizer history buffer has a difference of " << totalHistoryFrames << " frames.\n";
|
andrewm@0
|
88 //cout << "Size = " << history_.size() << " first = " << history_.earliest().first << " last = " << history_.latest().first << endl;
|
andrewm@0
|
89 totalHistoryFrames = 1;
|
andrewm@0
|
90 }
|
andrewm@0
|
91 }
|
andrewm@0
|
92 else {
|
andrewm@0
|
93 // Use mod arithmetic to handle wraparounds in the frame number
|
andrewm@0
|
94 frameTime += currentSampleInterval_ * (timestamp_type)((rawFrameNumber + frameModulus_ - lastFrame) % frameModulus_);
|
andrewm@0
|
95
|
andrewm@0
|
96 totalHistoryFrames = (history_.latest().first - history_.earliest().first + frameModulus_) % frameModulus_;
|
andrewm@0
|
97 if(totalHistoryFrames <= 0) {
|
andrewm@0
|
98 //cout << "Warning: TimestampSynchronizer history buffer has a difference of " << totalHistoryFrames << " frames.\n";
|
andrewm@0
|
99 //cout << "Size = " << history_.size() << " first = " << history_.earliest().first << " last = " << history_.latest().first << endl;
|
andrewm@0
|
100
|
andrewm@0
|
101 totalHistoryFrames = 1;
|
andrewm@0
|
102 }
|
andrewm@0
|
103 }
|
andrewm@0
|
104
|
andrewm@0
|
105 // Recalculate the nominal sample interval by examining the difference in times
|
andrewm@0
|
106 // between first and last frames in the buffer.
|
andrewm@0
|
107
|
andrewm@0
|
108 currentSampleInterval_ = (history_.latestTimestamp() - history_.earliestTimestamp()) / (timestamp_diff_type)totalHistoryFrames;
|
andrewm@0
|
109
|
andrewm@0
|
110 // The frame time was just incremented by the current sample period. Check whether
|
andrewm@0
|
111 // this puts the frame time ahead of the clock time. Don't allow the frame time to get
|
andrewm@0
|
112 // ahead of the system clock (this will also push future frame timestamps back).
|
andrewm@0
|
113
|
andrewm@0
|
114 if(frameTime > clockTime) {
|
andrewm@0
|
115 //cout << "CLIP " << 100.0 * (frameTime - clockTime) / currentSampleInterval_ << "%: frame=" << frameTime << " to clock=" << clockTime << endl;
|
andrewm@0
|
116 frameTime = clockTime;
|
andrewm@0
|
117 }
|
andrewm@0
|
118
|
andrewm@0
|
119 bufferLengthCounter_++;
|
andrewm@0
|
120
|
andrewm@0
|
121 if(bufferLengthCounter_ >= kTimestampSynchronizerHistoryLength) {
|
andrewm@0
|
122 //timestamp_diff_type currentLatency = clockTime - frameTime;
|
andrewm@0
|
123 timestamp_diff_type maxLatency = 0, minLatency = 1000000.0;
|
andrewm@0
|
124
|
andrewm@0
|
125 Node<pair<int, timestamp_type> >::iterator it;
|
andrewm@0
|
126
|
andrewm@0
|
127 for(it = history_.begin(); it != history_.end(); ++it) {
|
andrewm@0
|
128 timestamp_diff_type l = (it.timestamp() - it->second);
|
andrewm@0
|
129 if(l > maxLatency)
|
andrewm@0
|
130 maxLatency = l;
|
andrewm@0
|
131 if(l < minLatency)
|
andrewm@0
|
132 minLatency = l;
|
andrewm@0
|
133 }
|
andrewm@0
|
134
|
andrewm@0
|
135 //cout << "frame " << rawFrameNumber << ": rate = " << currentSampleInterval_ << " clock = " << clockTime << " frame = " << frameTime << " latency = "
|
andrewm@0
|
136 // << currentLatency << " max = " << maxLatency << " min = " << minLatency << endl;
|
andrewm@0
|
137
|
andrewm@0
|
138 //timestamp_diff_type targetMinLatency = (maxLatency - minLatency) * 2.0 / sqrt(kTimestampSynchronizerHistoryLength);
|
andrewm@0
|
139
|
andrewm@0
|
140 /*if(minLatency > targetMinLatency) {
|
andrewm@0
|
141 cout << "ADDING " << 50.0 * (minLatency - targetMinLatency) / (currentSampleInterval_) << "%: (target " << targetMinLatency << ")\n";
|
andrewm@0
|
142 frameTime += (minLatency - targetMinLatency) / 2.0;
|
andrewm@0
|
143 }*/
|
andrewm@0
|
144 //frameTime += minLatency / 4.0;
|
andrewm@0
|
145
|
andrewm@0
|
146 bufferLengthCounter_ = 0;
|
andrewm@0
|
147 }
|
andrewm@0
|
148 }
|
andrewm@0
|
149
|
andrewm@0
|
150 // Insert the new frame time and clock times into the buffer
|
andrewm@0
|
151 history_.insert(pair<int, timestamp_type>(rawFrameNumber, frameTime), clockTime);
|
andrewm@0
|
152
|
andrewm@0
|
153 // The timestamp we return is associated with the frame, not the clock (which is potentially much
|
andrewm@0
|
154 // higher jitter)
|
andrewm@0
|
155 return frameTime;
|
andrewm@0
|
156 }
|