Mercurial > hg > touchkeys
diff Source/Mappings/MRPMapping.cpp @ 0:3580ffe87dc8
First commit of TouchKeys public pre-release.
author | Andrew McPherson <andrewm@eecs.qmul.ac.uk> |
---|---|
date | Mon, 11 Nov 2013 18:19:35 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Source/Mappings/MRPMapping.cpp Mon Nov 11 18:19:35 2013 +0000 @@ -0,0 +1,588 @@ +/* + 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/>. + + ===================================================================== + + MRPMapping.cpp: mapping class for magnetic resonator piano using continuous + key position. +*/ + +#include "MRPMapping.h" +#include <vector> + +// Class constants +// Useful constants for mapping MRP messages +const int MRPMapping::kMIDINoteOnMessage = 0x90; +const int MRPMapping::kDefaultMIDIChannel = 15; +const float MRPMapping::kDefaultAftertouchScaler = 100.0; + +// Parameters for vibrato detection and mapping +const key_velocity MRPMapping::kVibratoVelocityThreshold = scale_key_velocity(2.0); +const timestamp_diff_type MRPMapping::kVibratoMinimumPeakSpacing = microseconds_to_timestamp(60000); +const timestamp_diff_type MRPMapping::kVibratoTimeout = microseconds_to_timestamp(500000); +const int MRPMapping::kVibratoMinimumOscillations = 4; +const float MRPMapping::kVibratoRateScaler = 0.005; + +// Main constructor takes references/pointers from objects which keep track +// of touch location, continuous key position and the state detected from that +// position. The PianoKeyboard object is strictly required as it gives access to +// Scheduler and OSC methods. The others are optional since any given system may +// contain only one of continuous key position or touch sensitivity +MRPMapping::MRPMapping(PianoKeyboard &keyboard, MappingFactory *factory, int noteNumber, Node<KeyTouchFrame>* touchBuffer, + Node<key_position>* positionBuffer, KeyPositionTracker* positionTracker) +: Mapping(keyboard, factory, noteNumber, touchBuffer, positionBuffer, positionTracker), + noteIsOn_(false), lastIntensity_(missing_value<float>::missing()), + lastBrightness_(missing_value<float>::missing()), lastPitch_(missing_value<float>::missing()), + lastHarmonic_(missing_value<float>::missing()), + shouldLookForPitchBends_(true), rawVelocity_(kMRPMappingVelocityBufferLength), + filteredVelocity_(kMRPMappingVelocityBufferLength, rawVelocity_), lastCalculatedVelocityIndex_(0), + vibratoActive_(false), vibratoVelocityPeakCount_(0), vibratoLastPeakTimestamp_(missing_value<timestamp_type>::missing()) +{ + setAftertouchSensitivity(1.0); + + // Initialize the filter coefficients for filtered key velocity (used for vibrato detection) + std::vector<double> bCoeffs, aCoeffs; + designSecondOrderLowpass(bCoeffs, aCoeffs, 15.0, 0.707, 1000.0); + std::vector<float> bCf(bCoeffs.begin(), bCoeffs.end()), aCf(aCoeffs.begin(), aCoeffs.end()); + filteredVelocity_.setCoefficients(bCf, aCf); +} + +// Copy constructor +/*MRPMapping::MRPMapping(MRPMapping const& obj) +: Mapping(obj), lastIntensity_(obj.lastIntensity_), lastBrightness_(obj.lastBrightness_), +aftertouchScaler_(obj.aftertouchScaler_), noteIsOn_(obj.noteIsOn_), lastPitch_(obj.lastPitch_), +lastHarmonic_(obj.lastHarmonic_), +shouldLookForPitchBends_(obj.shouldLookForPitchBends_), activePitchBends_(obj.activePitchBends_), +rawVelocity_(obj.rawVelocity_), filteredVelocity_(obj.filteredVelocity_), +lastCalculatedVelocityIndex_(obj.lastCalculatedVelocityIndex_), vibratoActive_(obj.vibratoActive_), +vibratoVelocityPeakCount_(obj.vibratoVelocityPeakCount_), vibratoLastPeakTimestamp_(obj.vibratoLastPeakTimestamp_) { + +}*/ + +MRPMapping::~MRPMapping() { + //std::cerr << "~MRPMapping(): " << this << std::endl; + + try { + disengage(); + } + catch(...) { + std::cerr << "~MRPMapping(): exception during disengage()\n"; + } + + //std::cerr << "~MRPMapping(): done\n"; +} + +// Turn off mapping of data. Remove our callback from the scheduler +void MRPMapping::disengage() { + Mapping::disengage(); + if(noteIsOn_) { + int newNoteNumber = noteNumber_; + //int newNoteNumber = ((noteNumber_ - 21) * 25)%88 + 21; + keyboard_.sendMessage("/mrp/midi", + "iii", (int)(kMIDINoteOnMessage + kDefaultMIDIChannel), (int)newNoteNumber, (int)0, LO_ARGS_END); + // if(!touchBuffer_->empty()) + // keyboard_.testLog_ << touchBuffer_->latestTimestamp() << " /mrp/midi iii " << (kMIDINoteOnMessage + kDefaultMIDIChannel) << " " << newNoteNumber << " " << 0 << endl; + + // Reset qualities + lastPitch_ = lastHarmonic_ = lastBrightness_ = lastIntensity_ = missing_value<float>::missing(); + } + noteIsOn_ = false; + shouldLookForPitchBends_ = true; +} + +// Reset state back to defaults +void MRPMapping::reset() { + Mapping::reset(); + noteIsOn_ = false; + shouldLookForPitchBends_ = true; +} + +// Set the aftertouch sensitivity on continuous key position +// 0 means no aftertouch, 1 means default sensitivity, upward +// from there +void MRPMapping::setAftertouchSensitivity(float sensitivity) { + if(sensitivity <= 0) + aftertouchScaler_ = 0; + else + aftertouchScaler_ = kDefaultAftertouchScaler * sensitivity; +} + +// This is called by another MRPMapping when it finds a pitch bend starting. +// Add the sending note to our list of bends, with the sending note marked +// as controlling the bend +void MRPMapping::enablePitchBend(int toNote, Node<key_position>* toPositionBuffer, + KeyPositionTracker *toPositionTracker) { + if(toPositionBuffer == 0 || toPositionTracker == 0) + return; + + std::cout << "enablePitchBend(): this note = " << noteNumber_ << " note = " << toNote << " posBuf = " << toPositionBuffer << " posTrack = " << toPositionTracker << "\n"; + PitchBend newBend = {toNote, true, false, toPositionBuffer, toPositionTracker}; + activePitchBends_.push_back(newBend); +} + +// Trigger method. This receives updates from the TouchKey data or from state changes in +// the continuous key position (KeyPositionTracker). It will potentially change the scheduled +// behavior of future mapping calls, but the actual OSC messages should be transmitted in a different +// thread. +void MRPMapping::triggerReceived(TriggerSource* who, timestamp_type timestamp) { + if(who == 0) + return; + if(who == positionTracker_) { + // The state of the key (based on continuous position) just changed. + // Might want to alter our mapping strategy. + } + else if(who == touchBuffer_) { + // TouchKey data is available + } +} + +// Mapping method. This actually does the real work of sending OSC data in response to the +// latest information from the touch sensors or continuous key angle +timestamp_type MRPMapping::performMapping() { + if(!engaged_) + return 0; + + timestamp_type currentTimestamp = keyboard_.schedulerCurrentTimestamp(); + float intensity = 0; + float brightness = 0; + float pitch = 0; + float harmonic = 0; + + // Calculate the output features as a function of input sensor data + if(positionBuffer_ == 0) { + // No buffer -> all 0 + } + else if(positionBuffer_->empty()) { + // No samples -> all 0 + } + else { + // TODO: IIR filter on the position data before mapping it + key_position latestPosition = positionBuffer_->latest(); + int trackerState = kPositionTrackerStateUnknown; + if(positionTracker_ != 0) + trackerState = positionTracker_->currentState(); + + // Get the latest velocity measurements + key_velocity latestVelocity = updateVelocityMeasurements(); + + // Every time we enter a state of PartialPress, check whether this key + // is part of a multi-key pitch bend gesture with another key that's already + // down. Only do this once, though, since keys that go down after we enter + // PartialPress state are not part of such a gesture. + if(shouldLookForPitchBends_) { + if(trackerState == kPositionTrackerStatePartialPressAwaitingMax || + trackerState == kPositionTrackerStatePartialPressFoundMax) { + // Look for a pitch bend gesture by searching for neighboring + // keys which are in the Down state and reached that state before + // this one reached PartialPress state. + for(int neighborNote = noteNumber_ - 2; neighborNote < noteNumber_; neighborNote++) { + // If one of the lower keys is in the Down state, then this note should bend it up + MRPMapping *neighborMapper = dynamic_cast<MRPMapping*>(keyboard_.mapping(neighborNote)); + if(neighborMapper == 0) + continue; + if(neighborMapper->positionTracker_ != 0) { + int neighborState = neighborMapper->positionTracker_->currentState(); + if(neighborState == kPositionTrackerStateDown) { + // Here we've found a neighboring note in the Down state. But did it precede our transition? + timestamp_type timeOfDownTransition = neighborMapper->positionTracker_->latestTimestamp(); + timestamp_type timeOfOurPartialActivation = findTimestampOfPartialPress(); + + cout << "Found key " << neighborNote << " in Down state\n"; + + if(!missing_value<timestamp_type>::isMissing(timeOfOurPartialActivation)) { + if(timeOfOurPartialActivation > timeOfDownTransition) { + // The neighbor note went down before us; pitch bend should engage + cout << "Found pitch bend: " << noteNumber_ << " to " << neighborNote << endl; + + // Insert the details for the neighboring note into our buffer. The bend + // is controlled by our own key, and the target is the neighbor note. + PitchBend newBend = {neighborNote, false, false, neighborMapper->positionBuffer_, + neighborMapper->positionTracker_}; + activePitchBends_.push_back(newBend); + + // Tell the other note to bend its pitch based on our position + neighborMapper->enablePitchBend(noteNumber_, positionBuffer_, positionTracker_); + } + } + } + } + } + for(int neighborNote = noteNumber_ + 1; neighborNote < noteNumber_ + 3; neighborNote++) { + // If one of the upper keys is in the Down state, then this note should bend it down + MRPMapping *neighborMapper = dynamic_cast<MRPMapping*>(keyboard_.mapping(neighborNote)); + if(neighborMapper == 0) + continue; + if(neighborMapper->positionTracker_ != 0) { + int neighborState = neighborMapper->positionTracker_->currentState(); + if(neighborState == kPositionTrackerStateDown) { + // Here we've found a neighboring note in the Down state. But did it precede our transition? + timestamp_type timeOfDownTransition = neighborMapper->positionTracker_->latestTimestamp(); + timestamp_type timeOfOurPartialActivation = findTimestampOfPartialPress(); + + cout << "Found key " << neighborNote << " in Down state\n"; + + if(!missing_value<timestamp_type>::isMissing(timeOfOurPartialActivation)) { + if(timeOfOurPartialActivation > timeOfDownTransition) { + // The neighbor note went down before us; pitch bend should engage + cout << "Found pitch bend: " << noteNumber_ << " to " << neighborNote << endl; + + // Insert the details for the neighboring note into our buffer. The bend + // is controlled by our own key, and the target is the neighbor note. + PitchBend newBend = {neighborNote, false, false, neighborMapper->positionBuffer_, + neighborMapper->positionTracker_}; + activePitchBends_.push_back(newBend); + + // Tell the other note to bend its pitch based on our position + neighborMapper->enablePitchBend(noteNumber_, positionBuffer_, positionTracker_); + } + } + } + } + } + + shouldLookForPitchBends_ = false; + } + } + + if(trackerState == kPositionTrackerStatePartialPressAwaitingMax || + trackerState == kPositionTrackerStatePartialPressFoundMax) { + // Look for active vibrato gestures which are defined as oscillating + // motion in the key velocity. They could conceivably occur at a variety + // of raw key positions, as long as the key is not yet down + + if(missing_value<timestamp_type>::isMissing(vibratoLastPeakTimestamp_)) + vibratoLastPeakTimestamp_ = currentTimestamp; + + if(vibratoVelocityPeakCount_ % 2 == 0) { + if(latestVelocity > kVibratoVelocityThreshold && currentTimestamp - vibratoLastPeakTimestamp_ > kVibratoMinimumPeakSpacing) { + std::cout << "Vibrato count = " << vibratoVelocityPeakCount_ << std::endl; + vibratoVelocityPeakCount_++; + vibratoLastPeakTimestamp_ = currentTimestamp; + } + } + else { + if(latestVelocity < -kVibratoVelocityThreshold && currentTimestamp - vibratoLastPeakTimestamp_ > kVibratoMinimumPeakSpacing) { + std::cout << "Vibrato count = " << vibratoVelocityPeakCount_ << std::endl; + vibratoVelocityPeakCount_++; + vibratoLastPeakTimestamp_ = currentTimestamp; + } + } + + if(vibratoVelocityPeakCount_ >= kVibratoMinimumOscillations) { + vibratoActive_ = true; + } + + + if(vibratoActive_) { + // Update the harmonic parameter, which increases linearly with the absolute + // value of velocity. The value will accumulate over the course of a vibrato + // gesture and retain its value when the vibrato finishes. It reverts to minimum + // when the note finishes. + if(missing_value<float>::isMissing(lastHarmonic_)) + lastHarmonic_ = 0.0; + harmonic = lastHarmonic_ + fabsf(latestVelocity) * kVibratoRateScaler; + std::cout << "harmonic = " << harmonic << std::endl; + + // Check whether the current vibrato has timed out + if(currentTimestamp - vibratoLastPeakTimestamp_ > kVibratoTimeout) { + std::cout << "Vibrato timed out\n"; + vibratoActive_ = false; + vibratoVelocityPeakCount_ = 0; + vibratoLastPeakTimestamp_ = currentTimestamp; + } + } + } + else { + // Vibrato can't be active in these states + //std::cout << "Vibrato finished from state change\n"; + vibratoActive_ = false; + vibratoVelocityPeakCount_ = 0; + vibratoLastPeakTimestamp_ = currentTimestamp; + } + + if(trackerState != kPositionTrackerStateReleaseFinished) { + // For all active states except post-release, calculate + // Intensity and Brightness parameters based on key position + + if(latestPosition > 1.0) { + intensity = 1.0; + brightness = (latestPosition - 1.0) * aftertouchScaler_; + } + else if(latestPosition < 0.0) { + intensity = 0.0; + brightness = 0.0; + } + else { + intensity = latestPosition; + brightness = 0.0; + } + + if(!activePitchBends_.empty()) { + // Look for active multi-key pitch bend gestures + std::vector<PitchBend>::iterator it = activePitchBends_.begin(); + pitch = 0.0; + + for(it = activePitchBends_.begin(); it != activePitchBends_.end(); it++) { + PitchBend& bend(*it); + + if(bend.isControllingBend) { + // First find out of the bending key is still in a PartialPress state + // If not, remove it and move on + if((bend.positionTracker->currentState() != kPositionTrackerStatePartialPressAwaitingMax && + bend.positionTracker->currentState() != kPositionTrackerStatePartialPressFoundMax) + || !bend.positionTracker->engaged()) { + cout << "Removing bend from note " << bend.note << endl; + bend.isFinished = true; + continue; + } + + // This is the case where the other note is controlling our pitch + if(bend.positionBuffer->empty()) { + continue; + } + + float noteDifference = (float)(bend.note - noteNumber_); + key_position latestBenderPosition = bend.positionBuffer->latest(); + + // Key position at 0 = 0 pitch bend; key position at max = most pitch bend + float bendAmount = key_position_to_float(latestBenderPosition - kPianoKeyDefaultIdlePositionThreshold*2) / + key_position_to_float(1.0 - kPianoKeyDefaultIdlePositionThreshold*2); + if(bendAmount < 0) + bendAmount = 0; + pitch += noteDifference * bendAmount; + } + else { + // This is the case where we're controlling the other note's pitch. Our own + // pitch is the inverse of what we're sending to the neighboring note. + // Compared to the above case, we know a few things since we're using our own + // position: the buffer isn't empty and the tracker is engaged. + + if(trackerState != kPositionTrackerStatePartialPressAwaitingMax && + trackerState != kPositionTrackerStatePartialPressFoundMax) { + cout << "Removing our bend on note " << bend.note << endl; + bend.isFinished = true; + continue; + } + + float noteDifference = (float)(bend.note - noteNumber_); + + // Key position at 0 = 0 pitch bend; key position at max = most pitch bend + float bendAmount = key_position_to_float(latestPosition - kPianoKeyDefaultIdlePositionThreshold*2) / + key_position_to_float(1.0 - kPianoKeyDefaultIdlePositionThreshold*2); + if(bendAmount < 0) + bendAmount = 0; + pitch += noteDifference * (1.0 - bendAmount); + } + } + + // Now reiterate to remove any of them that have finished + it = activePitchBends_.begin(); + + while(it != activePitchBends_.end()) { + if(it->isFinished) { + // Go back to beginning and look again after erasing each one + // This isn't very efficient but there will never be more than 4 elements anyway + activePitchBends_.erase(it); + it = activePitchBends_.begin(); + } + else + it++; + } + + std::cout << "pitch = " << pitch << std::endl; + } + else + pitch = 0.0; + } + else { + intensity = 0.0; + brightness = 0.0; + if(noteIsOn_) { + int newNoteNumber = noteNumber_; + //int newNoteNumber = ((noteNumber_ - 21) * 25)%88 + 21; + keyboard_.sendMessage("/mrp/midi", + "iii", (int)(kMIDINoteOnMessage + kDefaultMIDIChannel), (int)newNoteNumber, (int)0, LO_ARGS_END); + //keyboard_.testLog_ << currentTimestamp << " /mrp/midi iii " << (kMIDINoteOnMessage + kDefaultMIDIChannel) << " " << newNoteNumber << " " << 0 << endl; + } + noteIsOn_ = false; + shouldLookForPitchBends_ = true; + } + } + + // TODO: TouchKeys mapping + + // Send OSC message with these parameters unless they are unchanged from before + if(!noteIsOn_ && intensity > 0.0) { + int newNoteNumber = noteNumber_; + //int newNoteNumber = ((noteNumber_ - 21) * 25)%88 + 21; + keyboard_.sendMessage("/mrp/midi", + "iii", (int)(kMIDINoteOnMessage + kDefaultMIDIChannel), (int)newNoteNumber, (int)127, LO_ARGS_END); + //keyboard_.testLog_ << currentTimestamp << " /mrp/midi iii " << (kMIDINoteOnMessage + kDefaultMIDIChannel) << " " << newNoteNumber << " " << 127 << endl; + noteIsOn_ = true; + } + + // Set key LED color according to key parameters + // Partial press --> green of varying intensity + // Aftertouch (brightness) --> green moving to red depending on brightness parameter + // Pitch bend --> note bends toward blue as pitch value departs from center + // Harmonic glissando --> cycle through hues with whitish tint (lower saturation) + if(intensity != lastIntensity_ || brightness != lastBrightness_ || pitch != lastPitch_ || harmonic != lastHarmonic_) { + if(harmonic != 0.0) { + float hue = fmodf(harmonic, 1.0); + keyboard_.setKeyLEDColorHSV(noteNumber_, hue, 0.25, 0.5); + } + else if(intensity >= 1.0) { + if(pitch != 0.0) + keyboard_.setKeyLEDColorHSV(noteNumber_, 0.33 + 0.33 * fabsf(pitch) - (brightness * 0.2), 1.0, intensity); + else + keyboard_.setKeyLEDColorHSV(noteNumber_, 0.33 - (brightness * 0.2), 1.0, 1.0); + } + else { + if(pitch != 0.0) + keyboard_.setKeyLEDColorHSV(noteNumber_, 0.33 + 0.33 * fabsf(pitch), 1.0, intensity); + else + keyboard_.setKeyLEDColorHSV(noteNumber_, 0.33, 1.0, intensity); + } + } + + if(intensity != lastIntensity_) { + int newNoteNumber = noteNumber_; + //int newNoteNumber = ((noteNumber_ - 21) * 25)%88 + 21; + keyboard_.sendMessage("/mrp/quality/intensity", + "iif", (int)kDefaultMIDIChannel, (int)newNoteNumber, (float)intensity, LO_ARGS_END); + //keyboard_.testLog_ << currentTimestamp << " /mrp/quality/intensity iif " << kDefaultMIDIChannel << " " << newNoteNumber << " " << intensity << endl; + } + if(brightness != lastBrightness_) { + int newNoteNumber = noteNumber_; + //int newNoteNumber = ((noteNumber_ - 21) * 25)%88 + 21; + keyboard_.sendMessage("/mrp/quality/brightness", + "iif", (int)kDefaultMIDIChannel, (int)newNoteNumber, (float)brightness, LO_ARGS_END); + //keyboard_.testLog_ << currentTimestamp << " /mrp/quality/brightness iif " << kDefaultMIDIChannel << " " << newNoteNumber << " " << brightness << endl; + } + if(pitch != lastPitch_) { + int newNoteNumber = noteNumber_; + //int newNoteNumber = ((noteNumber_ - 21) * 25)%88 + 21; + keyboard_.sendMessage("/mrp/quality/pitch", + "iif", (int)kDefaultMIDIChannel, (int)newNoteNumber, (float)pitch, LO_ARGS_END); + //keyboard_.testLog_ << currentTimestamp << " /mrp/quality/pitch iif " << kDefaultMIDIChannel << " " << newNoteNumber << " " << pitch << endl; + } + if(harmonic != lastHarmonic_) { + int newNoteNumber = noteNumber_; + //int newNoteNumber = ((noteNumber_ - 21) * 25)%88 + 21; + keyboard_.sendMessage("/mrp/quality/harmonic", + "iif", (int)kDefaultMIDIChannel, (int)newNoteNumber, (float)harmonic, LO_ARGS_END); + //keyboard_.testLog_ << currentTimestamp << " /mrp/quality/harmonic iif " << kDefaultMIDIChannel << " " << newNoteNumber << " " << harmonic << endl; + } + + lastIntensity_ = intensity; + lastBrightness_ = brightness; + lastPitch_ = pitch; + lastHarmonic_ = harmonic; + + // Register for the next update by returning its timestamp + nextScheduledTimestamp_ = currentTimestamp + updateInterval_; + return nextScheduledTimestamp_; +} + +// Helper function that brings the velocity buffer up to date with the latest +// samples. Velocity is not updated on every new position sample since it's not +// efficient to run that many triggers all the time. Instead, it's brought up to +// date on an as-needed basis during performMapping(). +key_velocity MRPMapping::updateVelocityMeasurements() { + positionBuffer_->lock_mutex(); + + // Need at least 2 samples to calculate velocity (first difference) + if(positionBuffer_->size() < 2) { + positionBuffer_->unlock_mutex(); + return missing_value<key_velocity>::missing(); + } + + if(lastCalculatedVelocityIndex_ < positionBuffer_->beginIndex() + 1) { + // Fell off the beginning of the position buffer. Reset calculations. + filteredVelocity_.clear(); + rawVelocity_.clear(); + lastCalculatedVelocityIndex_ = positionBuffer_->beginIndex() + 1; + } + + while(lastCalculatedVelocityIndex_ < positionBuffer_->endIndex()) { + // Calculate the velocity and add to buffer + key_position diffPosition = (*positionBuffer_)[lastCalculatedVelocityIndex_] - (*positionBuffer_)[lastCalculatedVelocityIndex_ - 1]; + timestamp_diff_type diffTimestamp = positionBuffer_->timestampAt(lastCalculatedVelocityIndex_) - positionBuffer_->timestampAt(lastCalculatedVelocityIndex_ - 1); + key_velocity vel; + + if(diffTimestamp != 0) + vel = calculate_key_velocity(diffPosition, diffTimestamp); + else + vel = 0; // Bad measurement: replace with 0 so as not to mess up IIR calculations + + // Add the raw velocity to the buffer + rawVelocity_.insert(vel, positionBuffer_->timestampAt(lastCalculatedVelocityIndex_)); + lastCalculatedVelocityIndex_++; + } + + positionBuffer_->unlock_mutex(); + + // Bring the filtered velocity up to date + key_velocity filteredVel = filteredVelocity_.calculate(); + //std::cout << "Key " << noteNumber_ << " velocity " << filteredVel << std::endl; + return filteredVel; +} + +// Helper function that locates the timestamp at which this key entered the +// PartialPress (i.e. first non-idle) state. Returns missing value if the +// state can't be located. +timestamp_type MRPMapping::findTimestampOfPartialPress() { + if(positionTracker_ == 0) + return missing_value<timestamp_type>::missing(); + if(positionTracker_->empty()) + return missing_value<timestamp_type>::missing(); + //Node<int>::reverse_iterator it = positionTracker_->rbegin(); + Node<int>::size_type index = positionTracker_->endIndex() - 1; + bool foundPartialPressState = false; + timestamp_type earliestPartialPressTimestamp; + + // Search backwards from present + while(index >= positionTracker_->beginIndex()/*it != positionTracker_->rend()*/) { + if((*positionTracker_)[index].state == kPositionTrackerStatePartialPressAwaitingMax || + (*positionTracker_)[index].state == kPositionTrackerStatePartialPressFoundMax) { + cout << "index " << index << " state " << (*positionTracker_)[index].state << endl; + foundPartialPressState = true; + earliestPartialPressTimestamp = positionTracker_->timestampAt(index); + } + else { + // This state is not a PartialPress state. Two cases: either + // we haven't yet encountered a partial press or we have found + // a state before the partial press, in which case the previous + // state we found was the first. + cout << "index " << index << " state " << (*positionTracker_)[index].state << endl; + if(foundPartialPressState) { + return earliestPartialPressTimestamp; + } + } + + // Step backwards one sample, but stop if we hit the beginning index + if(index == 0) + break; + index--; + } + + if(foundPartialPressState) + return earliestPartialPressTimestamp; + + // Didn't find anything if we get here + return missing_value<timestamp_type>::missing(); +} \ No newline at end of file