annotate Source/TouchKeys/PianoKeyCalibrator.cpp @ 56:b4a2d2ae43cf tip

merge
author Andrew McPherson <andrewm@eecs.qmul.ac.uk>
date Fri, 23 Nov 2018 15:48:14 +0000
parents dfff66c07936
children
rev   line source
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 }