Mercurial > hg > touchkeys
view Source/TouchKeys/PianoKeyCalibrator.cpp @ 31:88287c1c2c92
Added an auxiliary MIDI input control, and moved the logging out of the window into the menu to make space in the GUI. Also updated the main window to be rescalable vertically for showing more mappings.
author | Andrew McPherson <andrewm@eecs.qmul.ac.uk> |
---|---|
date | Thu, 20 Mar 2014 00:14:00 +0000 |
parents | dfff66c07936 |
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); }