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: PianoKeyCalibrator.cpp: handles calibration of continuous key position data andrewm@0: */ andrewm@0: andrewm@0: #include "PianoKeyCalibrator.h" andrewm@0: andrewm@0: // Constructor andrewm@0: PianoKeyCalibrator::PianoKeyCalibrator(bool pressValueGoesDown, key_position* warpTable) andrewm@0: : status_(kPianoKeyNotCalibrated), prevStatus_(kPianoKeyNotCalibrated), andrewm@0: pressValueGoesDown_(pressValueGoesDown), history_(0), warpTable_(warpTable) {} andrewm@0: andrewm@0: // Destructor andrewm@0: PianoKeyCalibrator::~PianoKeyCalibrator() { andrewm@0: if(history_ != 0) andrewm@0: delete history_; andrewm@0: andrewm@0: // warpTable_ is passed in externally-- don't delete it andrewm@0: } andrewm@0: andrewm@0: // Produce the calibrated value for a raw sample andrewm@0: key_position PianoKeyCalibrator::evaluate(int rawValue) { andrewm@0: key_position calibratedValue, calibratedValueDenominator; andrewm@0: andrewm@0: ScopedLock sl(calibrationMutex_); andrewm@0: andrewm@0: switch(status_) { andrewm@0: case kPianoKeyCalibrated: andrewm@0: if(missing_value::isMissing(quiescent_) || andrewm@0: missing_value::isMissing(press_)) { andrewm@0: return missing_value::missing(); andrewm@0: } andrewm@0: andrewm@0: // Do the calculation either in integer or floating-point arithmetic andrewm@0: calibratedValueDenominator = (key_position)(press_ - quiescent_); andrewm@0: andrewm@0: // Prevent divide-by-0 errors andrewm@0: if(calibratedValueDenominator == 0) andrewm@0: calibratedValue = missing_value::missing(); andrewm@0: else { andrewm@0: // Scale the value and clip it to a sensible range (for badly calibrated sensors) andrewm@0: calibratedValue = (scale_key_position((rawValue - quiescent_))) / calibratedValueDenominator; andrewm@0: if(calibratedValue < -0.5) andrewm@0: calibratedValue = -0.5; andrewm@0: if(calibratedValue > 1.2) andrewm@0: calibratedValue = 1.2; andrewm@0: } andrewm@0: andrewm@0: if(warpTable_ != 0) { andrewm@0: // TODO: warping andrewm@0: } andrewm@0: return calibratedValue; andrewm@0: case kPianoKeyInCalibration: andrewm@0: historyMutex_.enter(); andrewm@0: andrewm@0: // Add the sample to the calibration buffer, and wait until we have enough samples to do anything andrewm@0: history_->push_back(rawValue); andrewm@0: if(history_->size() < kPianoKeyCalibrationPressLength) { andrewm@0: historyMutex_.exit(); andrewm@0: return missing_value::missing(); andrewm@0: } andrewm@0: andrewm@0: if(pressValueGoesDown_) { // Pressed keys have a lower value than quiescent keys andrewm@0: int currentAverage = averagePosition(kPianoKeyCalibrationPressLength); andrewm@0: andrewm@0: // Look for minimum overall value andrewm@0: if(currentAverage < newPress_ || missing_value::isMissing(newPress_)) { andrewm@0: newPress_ = currentAverage; andrewm@0: } andrewm@0: } andrewm@0: else { // Pressed keys have a higher value than quiescent keys andrewm@0: int currentAverage = averagePosition(kPianoKeyCalibrationPressLength); andrewm@0: andrewm@0: // Look for maximum overall value andrewm@0: if(currentAverage > newPress_ || missing_value::isMissing(newPress_)) { andrewm@0: newPress_ = currentAverage; andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // Don't return a value while calibrating andrewm@0: historyMutex_.exit(); andrewm@0: return missing_value::missing(); andrewm@0: case kPianoKeyNotCalibrated: // Don't do anything andrewm@0: default: andrewm@0: return missing_value::missing(); andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // Begin the calibrating process. andrewm@0: void PianoKeyCalibrator::calibrationStart() { andrewm@0: if(status_ == kPianoKeyInCalibration) // Throw away the old results if we're already in progress andrewm@0: calibrationAbort(); // This will clear the slate andrewm@0: andrewm@0: historyMutex_.enter(); andrewm@0: if(history_ != 0) andrewm@0: delete history_; andrewm@0: history_ = new boost::circular_buffer(kPianoKeyCalibrationBufferSize); andrewm@0: historyMutex_.exit(); andrewm@0: andrewm@0: calibrationMutex_.enter(); andrewm@0: newPress_ = quiescent_ = missing_value::missing(); andrewm@0: changeStatus(kPianoKeyInCalibration); andrewm@0: calibrationMutex_.exit(); andrewm@0: } andrewm@0: andrewm@0: // Finish calibrating and accept the new results. Returns true if andrewm@0: // calibration was successful; false if one or more values were missing andrewm@0: // or if insufficient range is available. andrewm@0: andrewm@0: bool PianoKeyCalibrator::calibrationFinish() { andrewm@0: bool updatedCalibration = false; andrewm@0: int oldQuiescent = quiescent_; andrewm@0: andrewm@0: if(status_ != kPianoKeyInCalibration) andrewm@0: return false; andrewm@0: andrewm@0: ScopedLock sl(calibrationMutex_); andrewm@0: andrewm@0: // Check that we were successfully able to update the quiescent value andrewm@0: // (should always be the case but this is a sanity check) andrewm@0: bool updatedQuiescent = internalUpdateQuiescent(); andrewm@0: andrewm@0: if(updatedQuiescent && abs(newPress_ - quiescent_) >= kPianoKeyCalibrationMinimumRange) { andrewm@0: press_ = newPress_; andrewm@0: changeStatus(kPianoKeyCalibrated); andrewm@0: updatedCalibration = true; andrewm@0: } andrewm@0: else { andrewm@0: quiescent_ = oldQuiescent; andrewm@0: andrewm@0: if(prevStatus_ == kPianoKeyCalibrated) { // There may or may not have been valid data in press_ and quiescent_ before, depending on whether andrewm@0: changeStatus(kPianoKeyCalibrated); // they were previously calibrated. andrewm@0: } andrewm@0: else { andrewm@0: changeStatus(kPianoKeyNotCalibrated); andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: cleanup(); andrewm@0: return updatedCalibration; andrewm@0: } andrewm@0: andrewm@0: // Finish calibrating without saving results andrewm@0: void PianoKeyCalibrator::calibrationAbort() { andrewm@0: ScopedLock sl(calibrationMutex_); andrewm@0: cleanup(); andrewm@0: if(prevStatus_ == kPianoKeyCalibrated) { // There may or may not have been valid data in press_ and quiescent_ before, depending on whether andrewm@0: changeStatus(kPianoKeyCalibrated); // they were previously calibrated. andrewm@0: } andrewm@0: else { andrewm@0: changeStatus(kPianoKeyNotCalibrated); andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // Clear the existing calibration, reverting to an uncalibrated state andrewm@0: void PianoKeyCalibrator::calibrationClear() { andrewm@0: if(status_ == kPianoKeyInCalibration) andrewm@0: calibrationAbort(); andrewm@0: ScopedLock sl(calibrationMutex_); andrewm@0: status_ = prevStatus_ = kPianoKeyNotCalibrated; andrewm@0: } andrewm@0: andrewm@0: // Generate new quiescent values without changing the press values andrewm@0: void PianoKeyCalibrator::calibrationUpdateQuiescent() { andrewm@0: calibrationStart(); andrewm@20: Thread::sleep(250); // Wait 0.25 seconds for data to collect andrewm@0: internalUpdateQuiescent(); andrewm@0: calibrationAbort(); andrewm@0: } andrewm@0: andrewm@0: // Load calibration data from an XML string andrewm@0: void PianoKeyCalibrator::loadFromXml(const XmlElement& baseElement) { andrewm@0: // Abort any calibration in progress and reset to default values andrewm@0: if(status_ == kPianoKeyInCalibration) andrewm@0: calibrationAbort(); andrewm@0: calibrationClear(); andrewm@0: andrewm@0: XmlElement *calibrationElement = baseElement.getChildByName("Calibration"); andrewm@0: andrewm@0: if(calibrationElement != 0) { andrewm@0: if(calibrationElement->hasAttribute("quiescent") && andrewm@0: calibrationElement->hasAttribute("press")) { andrewm@0: quiescent_ = calibrationElement->getIntAttribute("quiescent"); andrewm@0: press_ = calibrationElement->getIntAttribute("press"); andrewm@0: changeStatus(kPianoKeyCalibrated); andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // Saves calibration data within the provided XML Element. Child elements andrewm@0: // will be added for each sequence. Returns true if valid data was saved. andrewm@0: bool PianoKeyCalibrator::saveToXml(XmlElement& baseElement) { andrewm@0: if(status_ != kPianoKeyCalibrated) andrewm@0: return false; andrewm@0: andrewm@0: XmlElement *newElement = baseElement.createNewChildElement("Calibration"); andrewm@0: andrewm@0: if(newElement == 0) andrewm@0: return false; andrewm@0: andrewm@0: newElement->setAttribute("quiescent", quiescent_); andrewm@0: newElement->setAttribute("press", press_); andrewm@0: andrewm@0: return true; andrewm@0: } andrewm@0: andrewm@0: // ***** Internal Methods ***** andrewm@0: andrewm@0: // Internal method to clean up after a calibration session. andrewm@0: void PianoKeyCalibrator::cleanup() { andrewm@0: ScopedLock sl(historyMutex_); andrewm@0: if(history_ != 0) andrewm@0: delete history_; andrewm@0: history_ = 0; andrewm@0: newPress_ = missing_value::missing(); andrewm@0: } andrewm@0: andrewm@0: // This internal method actually calculates the new quiescent values. Used by calibrationUpdateQuiescent() andrewm@0: // and calibrationFinish(). Returns true if successful. andrewm@0: bool PianoKeyCalibrator::internalUpdateQuiescent() { andrewm@0: ScopedLock sl(historyMutex_); andrewm@0: if(history_ == 0) { andrewm@0: return false; andrewm@0: } andrewm@0: if(history_->size() < kPianoKeyCalibrationPressLength) { andrewm@0: return false; andrewm@0: } andrewm@0: quiescent_ = averagePosition(kPianoKeyCalibrationBufferSize); andrewm@0: return true; andrewm@0: } andrewm@0: andrewm@0: // Get the average position of several samples in the buffer. andrewm@0: int PianoKeyCalibrator::averagePosition(int length) { andrewm@0: boost::circular_buffer::reverse_iterator rit = history_->rbegin(); andrewm@0: int count = 0, sum = 0; andrewm@0: andrewm@0: while(rit != history_->rend() && count < length) { andrewm@0: sum += *rit++; andrewm@0: count++; andrewm@0: } andrewm@0: andrewm@0: if(count == 0) { andrewm@0: return missing_value::missing(); andrewm@0: } andrewm@0: andrewm@0: return (int)(sum / count); andrewm@0: }