Mercurial > hg > touchkeys
view Source/TouchKeys/PianoKeyCalibrator.cpp @ 20:dfff66c07936
Lots of minor changes to support building on Visual Studio. A few MSVC-specific #ifdefs to eliminate things Visual Studio doesn't like. This version now compiles on Windows (provided liblo, Juce and pthread are present) but the TouchKeys device support is not yet enabled. Also, the code now needs to be re-checked on Mac and Linux.
author | Andrew McPherson <andrewm@eecs.qmul.ac.uk> |
---|---|
date | Sun, 09 Feb 2014 18:40:51 +0000 |
parents | 3580ffe87dc8 |
children |
line wrap: on
line source
/* TouchKeys: multi-touch musical keyboard control software Copyright (c) 2013 Andrew McPherson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. ===================================================================== PianoKeyCalibrator.cpp: handles calibration of continuous key position data */ #include "PianoKeyCalibrator.h" // Constructor PianoKeyCalibrator::PianoKeyCalibrator(bool pressValueGoesDown, key_position* warpTable) : status_(kPianoKeyNotCalibrated), prevStatus_(kPianoKeyNotCalibrated), pressValueGoesDown_(pressValueGoesDown), history_(0), warpTable_(warpTable) {} // Destructor PianoKeyCalibrator::~PianoKeyCalibrator() { if(history_ != 0) delete history_; // warpTable_ is passed in externally-- don't delete it } // Produce the calibrated value for a raw sample key_position PianoKeyCalibrator::evaluate(int rawValue) { key_position calibratedValue, calibratedValueDenominator; ScopedLock sl(calibrationMutex_); switch(status_) { case kPianoKeyCalibrated: if(missing_value<int>::isMissing(quiescent_) || missing_value<int>::isMissing(press_)) { return missing_value<key_position>::missing(); } // Do the calculation either in integer or floating-point arithmetic calibratedValueDenominator = (key_position)(press_ - quiescent_); // Prevent divide-by-0 errors if(calibratedValueDenominator == 0) calibratedValue = missing_value<key_position>::missing(); else { // Scale the value and clip it to a sensible range (for badly calibrated sensors) calibratedValue = (scale_key_position((rawValue - quiescent_))) / calibratedValueDenominator; if(calibratedValue < -0.5) calibratedValue = -0.5; if(calibratedValue > 1.2) calibratedValue = 1.2; } if(warpTable_ != 0) { // TODO: warping } return calibratedValue; case kPianoKeyInCalibration: historyMutex_.enter(); // Add the sample to the calibration buffer, and wait until we have enough samples to do anything history_->push_back(rawValue); if(history_->size() < kPianoKeyCalibrationPressLength) { historyMutex_.exit(); return missing_value<key_position>::missing(); } if(pressValueGoesDown_) { // Pressed keys have a lower value than quiescent keys int currentAverage = averagePosition(kPianoKeyCalibrationPressLength); // Look for minimum overall value if(currentAverage < newPress_ || missing_value<int>::isMissing(newPress_)) { newPress_ = currentAverage; } } else { // Pressed keys have a higher value than quiescent keys int currentAverage = averagePosition(kPianoKeyCalibrationPressLength); // Look for maximum overall value if(currentAverage > newPress_ || missing_value<int>::isMissing(newPress_)) { newPress_ = currentAverage; } } // Don't return a value while calibrating historyMutex_.exit(); return missing_value<key_position>::missing(); case kPianoKeyNotCalibrated: // Don't do anything default: return missing_value<key_position>::missing(); } } // Begin the calibrating process. void PianoKeyCalibrator::calibrationStart() { if(status_ == kPianoKeyInCalibration) // Throw away the old results if we're already in progress calibrationAbort(); // This will clear the slate historyMutex_.enter(); if(history_ != 0) delete history_; history_ = new boost::circular_buffer<int>(kPianoKeyCalibrationBufferSize); historyMutex_.exit(); calibrationMutex_.enter(); newPress_ = quiescent_ = missing_value<int>::missing(); changeStatus(kPianoKeyInCalibration); calibrationMutex_.exit(); } // Finish calibrating and accept the new results. Returns true if // calibration was successful; false if one or more values were missing // or if insufficient range is available. bool PianoKeyCalibrator::calibrationFinish() { bool updatedCalibration = false; int oldQuiescent = quiescent_; if(status_ != kPianoKeyInCalibration) return false; ScopedLock sl(calibrationMutex_); // Check that we were successfully able to update the quiescent value // (should always be the case but this is a sanity check) bool updatedQuiescent = internalUpdateQuiescent(); if(updatedQuiescent && abs(newPress_ - quiescent_) >= kPianoKeyCalibrationMinimumRange) { press_ = newPress_; changeStatus(kPianoKeyCalibrated); updatedCalibration = true; } else { quiescent_ = oldQuiescent; if(prevStatus_ == kPianoKeyCalibrated) { // There may or may not have been valid data in press_ and quiescent_ before, depending on whether changeStatus(kPianoKeyCalibrated); // they were previously calibrated. } else { changeStatus(kPianoKeyNotCalibrated); } } cleanup(); return updatedCalibration; } // Finish calibrating without saving results void PianoKeyCalibrator::calibrationAbort() { ScopedLock sl(calibrationMutex_); cleanup(); if(prevStatus_ == kPianoKeyCalibrated) { // There may or may not have been valid data in press_ and quiescent_ before, depending on whether changeStatus(kPianoKeyCalibrated); // they were previously calibrated. } else { changeStatus(kPianoKeyNotCalibrated); } } // Clear the existing calibration, reverting to an uncalibrated state void PianoKeyCalibrator::calibrationClear() { if(status_ == kPianoKeyInCalibration) calibrationAbort(); ScopedLock sl(calibrationMutex_); status_ = prevStatus_ = kPianoKeyNotCalibrated; } // Generate new quiescent values without changing the press values void PianoKeyCalibrator::calibrationUpdateQuiescent() { calibrationStart(); Thread::sleep(250); // Wait 0.25 seconds for data to collect internalUpdateQuiescent(); calibrationAbort(); } // Load calibration data from an XML string void PianoKeyCalibrator::loadFromXml(const XmlElement& baseElement) { // Abort any calibration in progress and reset to default values if(status_ == kPianoKeyInCalibration) calibrationAbort(); calibrationClear(); XmlElement *calibrationElement = baseElement.getChildByName("Calibration"); if(calibrationElement != 0) { if(calibrationElement->hasAttribute("quiescent") && calibrationElement->hasAttribute("press")) { quiescent_ = calibrationElement->getIntAttribute("quiescent"); press_ = calibrationElement->getIntAttribute("press"); changeStatus(kPianoKeyCalibrated); } } } // Saves calibration data within the provided XML Element. Child elements // will be added for each sequence. Returns true if valid data was saved. bool PianoKeyCalibrator::saveToXml(XmlElement& baseElement) { if(status_ != kPianoKeyCalibrated) return false; XmlElement *newElement = baseElement.createNewChildElement("Calibration"); if(newElement == 0) return false; newElement->setAttribute("quiescent", quiescent_); newElement->setAttribute("press", press_); return true; } // ***** Internal Methods ***** // Internal method to clean up after a calibration session. void PianoKeyCalibrator::cleanup() { ScopedLock sl(historyMutex_); if(history_ != 0) delete history_; history_ = 0; newPress_ = missing_value<int>::missing(); } // This internal method actually calculates the new quiescent values. Used by calibrationUpdateQuiescent() // and calibrationFinish(). Returns true if successful. bool PianoKeyCalibrator::internalUpdateQuiescent() { ScopedLock sl(historyMutex_); if(history_ == 0) { return false; } if(history_->size() < kPianoKeyCalibrationPressLength) { return false; } quiescent_ = averagePosition(kPianoKeyCalibrationBufferSize); return true; } // Get the average position of several samples in the buffer. int PianoKeyCalibrator::averagePosition(int length) { boost::circular_buffer<int>::reverse_iterator rit = history_->rbegin(); int count = 0, sum = 0; while(rit != history_->rend() && count < length) { sum += *rit++; count++; } if(count == 0) { return missing_value<int>::missing(); } return (int)(sum / count); }