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 PianoKeyCalibrator.cpp: handles calibration of continuous key position data
|
andrewm@0
|
21 */
|
andrewm@0
|
22
|
andrewm@0
|
23 #include "PianoKeyCalibrator.h"
|
andrewm@0
|
24
|
andrewm@0
|
25 // Constructor
|
andrewm@0
|
26 PianoKeyCalibrator::PianoKeyCalibrator(bool pressValueGoesDown, key_position* warpTable)
|
andrewm@0
|
27 : status_(kPianoKeyNotCalibrated), prevStatus_(kPianoKeyNotCalibrated),
|
andrewm@0
|
28 pressValueGoesDown_(pressValueGoesDown), history_(0), warpTable_(warpTable) {}
|
andrewm@0
|
29
|
andrewm@0
|
30 // Destructor
|
andrewm@0
|
31 PianoKeyCalibrator::~PianoKeyCalibrator() {
|
andrewm@0
|
32 if(history_ != 0)
|
andrewm@0
|
33 delete history_;
|
andrewm@0
|
34
|
andrewm@0
|
35 // warpTable_ is passed in externally-- don't delete it
|
andrewm@0
|
36 }
|
andrewm@0
|
37
|
andrewm@0
|
38 // Produce the calibrated value for a raw sample
|
andrewm@0
|
39 key_position PianoKeyCalibrator::evaluate(int rawValue) {
|
andrewm@0
|
40 key_position calibratedValue, calibratedValueDenominator;
|
andrewm@0
|
41
|
andrewm@0
|
42 ScopedLock sl(calibrationMutex_);
|
andrewm@0
|
43
|
andrewm@0
|
44 switch(status_) {
|
andrewm@0
|
45 case kPianoKeyCalibrated:
|
andrewm@0
|
46 if(missing_value<int>::isMissing(quiescent_) ||
|
andrewm@0
|
47 missing_value<int>::isMissing(press_)) {
|
andrewm@0
|
48 return missing_value<key_position>::missing();
|
andrewm@0
|
49 }
|
andrewm@0
|
50
|
andrewm@0
|
51 // Do the calculation either in integer or floating-point arithmetic
|
andrewm@0
|
52 calibratedValueDenominator = (key_position)(press_ - quiescent_);
|
andrewm@0
|
53
|
andrewm@0
|
54 // Prevent divide-by-0 errors
|
andrewm@0
|
55 if(calibratedValueDenominator == 0)
|
andrewm@0
|
56 calibratedValue = missing_value<key_position>::missing();
|
andrewm@0
|
57 else {
|
andrewm@0
|
58 // Scale the value and clip it to a sensible range (for badly calibrated sensors)
|
andrewm@0
|
59 calibratedValue = (scale_key_position((rawValue - quiescent_))) / calibratedValueDenominator;
|
andrewm@0
|
60 if(calibratedValue < -0.5)
|
andrewm@0
|
61 calibratedValue = -0.5;
|
andrewm@0
|
62 if(calibratedValue > 1.2)
|
andrewm@0
|
63 calibratedValue = 1.2;
|
andrewm@0
|
64 }
|
andrewm@0
|
65
|
andrewm@0
|
66 if(warpTable_ != 0) {
|
andrewm@0
|
67 // TODO: warping
|
andrewm@0
|
68 }
|
andrewm@0
|
69 return calibratedValue;
|
andrewm@0
|
70 case kPianoKeyInCalibration:
|
andrewm@0
|
71 historyMutex_.enter();
|
andrewm@0
|
72
|
andrewm@0
|
73 // Add the sample to the calibration buffer, and wait until we have enough samples to do anything
|
andrewm@0
|
74 history_->push_back(rawValue);
|
andrewm@0
|
75 if(history_->size() < kPianoKeyCalibrationPressLength) {
|
andrewm@0
|
76 historyMutex_.exit();
|
andrewm@0
|
77 return missing_value<key_position>::missing();
|
andrewm@0
|
78 }
|
andrewm@0
|
79
|
andrewm@0
|
80 if(pressValueGoesDown_) { // Pressed keys have a lower value than quiescent keys
|
andrewm@0
|
81 int currentAverage = averagePosition(kPianoKeyCalibrationPressLength);
|
andrewm@0
|
82
|
andrewm@0
|
83 // Look for minimum overall value
|
andrewm@0
|
84 if(currentAverage < newPress_ || missing_value<int>::isMissing(newPress_)) {
|
andrewm@0
|
85 newPress_ = currentAverage;
|
andrewm@0
|
86 }
|
andrewm@0
|
87 }
|
andrewm@0
|
88 else { // Pressed keys have a higher value than quiescent keys
|
andrewm@0
|
89 int currentAverage = averagePosition(kPianoKeyCalibrationPressLength);
|
andrewm@0
|
90
|
andrewm@0
|
91 // Look for maximum overall value
|
andrewm@0
|
92 if(currentAverage > newPress_ || missing_value<int>::isMissing(newPress_)) {
|
andrewm@0
|
93 newPress_ = currentAverage;
|
andrewm@0
|
94 }
|
andrewm@0
|
95 }
|
andrewm@0
|
96
|
andrewm@0
|
97 // Don't return a value while calibrating
|
andrewm@0
|
98 historyMutex_.exit();
|
andrewm@0
|
99 return missing_value<key_position>::missing();
|
andrewm@0
|
100 case kPianoKeyNotCalibrated: // Don't do anything
|
andrewm@0
|
101 default:
|
andrewm@0
|
102 return missing_value<key_position>::missing();
|
andrewm@0
|
103 }
|
andrewm@0
|
104 }
|
andrewm@0
|
105
|
andrewm@0
|
106 // Begin the calibrating process.
|
andrewm@0
|
107 void PianoKeyCalibrator::calibrationStart() {
|
andrewm@0
|
108 if(status_ == kPianoKeyInCalibration) // Throw away the old results if we're already in progress
|
andrewm@0
|
109 calibrationAbort(); // This will clear the slate
|
andrewm@0
|
110
|
andrewm@0
|
111 historyMutex_.enter();
|
andrewm@0
|
112 if(history_ != 0)
|
andrewm@0
|
113 delete history_;
|
andrewm@0
|
114 history_ = new boost::circular_buffer<int>(kPianoKeyCalibrationBufferSize);
|
andrewm@0
|
115 historyMutex_.exit();
|
andrewm@0
|
116
|
andrewm@0
|
117 calibrationMutex_.enter();
|
andrewm@0
|
118 newPress_ = quiescent_ = missing_value<int>::missing();
|
andrewm@0
|
119 changeStatus(kPianoKeyInCalibration);
|
andrewm@0
|
120 calibrationMutex_.exit();
|
andrewm@0
|
121 }
|
andrewm@0
|
122
|
andrewm@0
|
123 // Finish calibrating and accept the new results. Returns true if
|
andrewm@0
|
124 // calibration was successful; false if one or more values were missing
|
andrewm@0
|
125 // or if insufficient range is available.
|
andrewm@0
|
126
|
andrewm@0
|
127 bool PianoKeyCalibrator::calibrationFinish() {
|
andrewm@0
|
128 bool updatedCalibration = false;
|
andrewm@0
|
129 int oldQuiescent = quiescent_;
|
andrewm@0
|
130
|
andrewm@0
|
131 if(status_ != kPianoKeyInCalibration)
|
andrewm@0
|
132 return false;
|
andrewm@0
|
133
|
andrewm@0
|
134 ScopedLock sl(calibrationMutex_);
|
andrewm@0
|
135
|
andrewm@0
|
136 // Check that we were successfully able to update the quiescent value
|
andrewm@0
|
137 // (should always be the case but this is a sanity check)
|
andrewm@0
|
138 bool updatedQuiescent = internalUpdateQuiescent();
|
andrewm@0
|
139
|
andrewm@0
|
140 if(updatedQuiescent && abs(newPress_ - quiescent_) >= kPianoKeyCalibrationMinimumRange) {
|
andrewm@0
|
141 press_ = newPress_;
|
andrewm@0
|
142 changeStatus(kPianoKeyCalibrated);
|
andrewm@0
|
143 updatedCalibration = true;
|
andrewm@0
|
144 }
|
andrewm@0
|
145 else {
|
andrewm@0
|
146 quiescent_ = oldQuiescent;
|
andrewm@0
|
147
|
andrewm@0
|
148 if(prevStatus_ == kPianoKeyCalibrated) { // There may or may not have been valid data in press_ and quiescent_ before, depending on whether
|
andrewm@0
|
149 changeStatus(kPianoKeyCalibrated); // they were previously calibrated.
|
andrewm@0
|
150 }
|
andrewm@0
|
151 else {
|
andrewm@0
|
152 changeStatus(kPianoKeyNotCalibrated);
|
andrewm@0
|
153 }
|
andrewm@0
|
154 }
|
andrewm@0
|
155
|
andrewm@0
|
156 cleanup();
|
andrewm@0
|
157 return updatedCalibration;
|
andrewm@0
|
158 }
|
andrewm@0
|
159
|
andrewm@0
|
160 // Finish calibrating without saving results
|
andrewm@0
|
161 void PianoKeyCalibrator::calibrationAbort() {
|
andrewm@0
|
162 ScopedLock sl(calibrationMutex_);
|
andrewm@0
|
163 cleanup();
|
andrewm@0
|
164 if(prevStatus_ == kPianoKeyCalibrated) { // There may or may not have been valid data in press_ and quiescent_ before, depending on whether
|
andrewm@0
|
165 changeStatus(kPianoKeyCalibrated); // they were previously calibrated.
|
andrewm@0
|
166 }
|
andrewm@0
|
167 else {
|
andrewm@0
|
168 changeStatus(kPianoKeyNotCalibrated);
|
andrewm@0
|
169 }
|
andrewm@0
|
170 }
|
andrewm@0
|
171
|
andrewm@0
|
172 // Clear the existing calibration, reverting to an uncalibrated state
|
andrewm@0
|
173 void PianoKeyCalibrator::calibrationClear() {
|
andrewm@0
|
174 if(status_ == kPianoKeyInCalibration)
|
andrewm@0
|
175 calibrationAbort();
|
andrewm@0
|
176 ScopedLock sl(calibrationMutex_);
|
andrewm@0
|
177 status_ = prevStatus_ = kPianoKeyNotCalibrated;
|
andrewm@0
|
178 }
|
andrewm@0
|
179
|
andrewm@0
|
180 // Generate new quiescent values without changing the press values
|
andrewm@0
|
181 void PianoKeyCalibrator::calibrationUpdateQuiescent() {
|
andrewm@0
|
182 calibrationStart();
|
andrewm@20
|
183 Thread::sleep(250); // Wait 0.25 seconds for data to collect
|
andrewm@0
|
184 internalUpdateQuiescent();
|
andrewm@0
|
185 calibrationAbort();
|
andrewm@0
|
186 }
|
andrewm@0
|
187
|
andrewm@0
|
188 // Load calibration data from an XML string
|
andrewm@0
|
189 void PianoKeyCalibrator::loadFromXml(const XmlElement& baseElement) {
|
andrewm@0
|
190 // Abort any calibration in progress and reset to default values
|
andrewm@0
|
191 if(status_ == kPianoKeyInCalibration)
|
andrewm@0
|
192 calibrationAbort();
|
andrewm@0
|
193 calibrationClear();
|
andrewm@0
|
194
|
andrewm@0
|
195 XmlElement *calibrationElement = baseElement.getChildByName("Calibration");
|
andrewm@0
|
196
|
andrewm@0
|
197 if(calibrationElement != 0) {
|
andrewm@0
|
198 if(calibrationElement->hasAttribute("quiescent") &&
|
andrewm@0
|
199 calibrationElement->hasAttribute("press")) {
|
andrewm@0
|
200 quiescent_ = calibrationElement->getIntAttribute("quiescent");
|
andrewm@0
|
201 press_ = calibrationElement->getIntAttribute("press");
|
andrewm@0
|
202 changeStatus(kPianoKeyCalibrated);
|
andrewm@0
|
203 }
|
andrewm@0
|
204 }
|
andrewm@0
|
205 }
|
andrewm@0
|
206
|
andrewm@0
|
207 // Saves calibration data within the provided XML Element. Child elements
|
andrewm@0
|
208 // will be added for each sequence. Returns true if valid data was saved.
|
andrewm@0
|
209 bool PianoKeyCalibrator::saveToXml(XmlElement& baseElement) {
|
andrewm@0
|
210 if(status_ != kPianoKeyCalibrated)
|
andrewm@0
|
211 return false;
|
andrewm@0
|
212
|
andrewm@0
|
213 XmlElement *newElement = baseElement.createNewChildElement("Calibration");
|
andrewm@0
|
214
|
andrewm@0
|
215 if(newElement == 0)
|
andrewm@0
|
216 return false;
|
andrewm@0
|
217
|
andrewm@0
|
218 newElement->setAttribute("quiescent", quiescent_);
|
andrewm@0
|
219 newElement->setAttribute("press", press_);
|
andrewm@0
|
220
|
andrewm@0
|
221 return true;
|
andrewm@0
|
222 }
|
andrewm@0
|
223
|
andrewm@0
|
224 // ***** Internal Methods *****
|
andrewm@0
|
225
|
andrewm@0
|
226 // Internal method to clean up after a calibration session.
|
andrewm@0
|
227 void PianoKeyCalibrator::cleanup() {
|
andrewm@0
|
228 ScopedLock sl(historyMutex_);
|
andrewm@0
|
229 if(history_ != 0)
|
andrewm@0
|
230 delete history_;
|
andrewm@0
|
231 history_ = 0;
|
andrewm@0
|
232 newPress_ = missing_value<int>::missing();
|
andrewm@0
|
233 }
|
andrewm@0
|
234
|
andrewm@0
|
235 // This internal method actually calculates the new quiescent values. Used by calibrationUpdateQuiescent()
|
andrewm@0
|
236 // and calibrationFinish(). Returns true if successful.
|
andrewm@0
|
237 bool PianoKeyCalibrator::internalUpdateQuiescent() {
|
andrewm@0
|
238 ScopedLock sl(historyMutex_);
|
andrewm@0
|
239 if(history_ == 0) {
|
andrewm@0
|
240 return false;
|
andrewm@0
|
241 }
|
andrewm@0
|
242 if(history_->size() < kPianoKeyCalibrationPressLength) {
|
andrewm@0
|
243 return false;
|
andrewm@0
|
244 }
|
andrewm@0
|
245 quiescent_ = averagePosition(kPianoKeyCalibrationBufferSize);
|
andrewm@0
|
246 return true;
|
andrewm@0
|
247 }
|
andrewm@0
|
248
|
andrewm@0
|
249 // Get the average position of several samples in the buffer.
|
andrewm@0
|
250 int PianoKeyCalibrator::averagePosition(int length) {
|
andrewm@0
|
251 boost::circular_buffer<int>::reverse_iterator rit = history_->rbegin();
|
andrewm@0
|
252 int count = 0, sum = 0;
|
andrewm@0
|
253
|
andrewm@0
|
254 while(rit != history_->rend() && count < length) {
|
andrewm@0
|
255 sum += *rit++;
|
andrewm@0
|
256 count++;
|
andrewm@0
|
257 }
|
andrewm@0
|
258
|
andrewm@0
|
259 if(count == 0) {
|
andrewm@0
|
260 return missing_value<int>::missing();
|
andrewm@0
|
261 }
|
andrewm@0
|
262
|
andrewm@0
|
263 return (int)(sum / count);
|
andrewm@0
|
264 } |