annotate Source/Mappings/OnsetAngle/TouchkeyOnsetAngleMapping.cpp @ 56:b4a2d2ae43cf tip

merge
author Andrew McPherson <andrewm@eecs.qmul.ac.uk>
date Fri, 23 Nov 2018 15:48:14 +0000
parents 3580ffe87dc8
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 TouchkeyOnsetAngleMapping.cpp: per-note mapping for the onset angle mapping,
andrewm@0 21 which measures the speed of finger motion along the key surface at the
andrewm@0 22 time of MIDI note onset.
andrewm@0 23 */
andrewm@0 24
andrewm@0 25 #include "TouchkeyOnsetAngleMapping.h"
andrewm@0 26 #include "../MappingFactory.h"
andrewm@0 27
andrewm@0 28 #define DEBUG_NOTE_ONSET_MAPPING
andrewm@0 29
andrewm@0 30 // Class constants
andrewm@0 31 const int TouchkeyOnsetAngleMapping::kDefaultFilterBufferLength = 30;
andrewm@0 32 const timestamp_diff_type TouchkeyOnsetAngleMapping::kDefaultMaxLookbackTime = milliseconds_to_timestamp(100);
andrewm@0 33 const int TouchkeyOnsetAngleMapping::kDefaultMaxLookbackSamples = 3;
andrewm@0 34
andrewm@0 35 // Main constructor takes references/pointers from objects which keep track
andrewm@0 36 // of touch location, continuous key position and the state detected from that
andrewm@0 37 // position. The PianoKeyboard object is strictly required as it gives access to
andrewm@0 38 // Scheduler and OSC methods. The others are optional since any given system may
andrewm@0 39 // contain only one of continuous key position or touch sensitivity
andrewm@0 40 TouchkeyOnsetAngleMapping::TouchkeyOnsetAngleMapping(PianoKeyboard &keyboard, MappingFactory *factory, int noteNumber, Node<KeyTouchFrame>* touchBuffer,
andrewm@0 41 Node<key_position>* positionBuffer, KeyPositionTracker* positionTracker)
andrewm@0 42 : TouchkeyBaseMapping(keyboard, factory, noteNumber, touchBuffer, positionBuffer, positionTracker),
andrewm@0 43 pastSamples_(kDefaultFilterBufferLength), maxLookbackTime_(kDefaultMaxLookbackTime),
andrewm@0 44 startingPitchBendSemitones_(0), lastPitchBendSemitones_(0),
andrewm@0 45 rampBeginTime_(missing_value<timestamp_type>::missing()), rampLength_(0)
andrewm@0 46 {
andrewm@0 47 }
andrewm@0 48
andrewm@0 49 // Reset state back to defaults
andrewm@0 50 void TouchkeyOnsetAngleMapping::reset() {
andrewm@0 51 ScopedLock sl(sampleBufferMutex_);
andrewm@0 52
andrewm@0 53 TouchkeyBaseMapping::reset();
andrewm@0 54 pastSamples_.clear();
andrewm@0 55 }
andrewm@0 56
andrewm@0 57 // Resend all current parameters
andrewm@0 58 void TouchkeyOnsetAngleMapping::resend() {
andrewm@0 59 // Message is only sent at release; resend may not apply here.
andrewm@0 60 }
andrewm@0 61
andrewm@0 62 // This method receives data from the touch buffer or possibly the continuous key angle (not used here)
andrewm@0 63 void TouchkeyOnsetAngleMapping::triggerReceived(TriggerSource* who, timestamp_type timestamp) {
andrewm@0 64 if(who == touchBuffer_) {
andrewm@0 65 ScopedLock sl(sampleBufferMutex_);
andrewm@0 66
andrewm@0 67 // Save the latest frame, even if it is an empty touch (we need to know what happened even
andrewm@0 68 // after the touch ends since the MIDI off may come later)
andrewm@0 69 if(!touchBuffer_->empty())
andrewm@0 70 pastSamples_.insert(touchBuffer_->latest(), touchBuffer_->latestTimestamp());
andrewm@0 71 }
andrewm@0 72 }
andrewm@0 73
andrewm@0 74 // Mapping method. This actually does the real work of sending OSC data in response to the
andrewm@0 75 // latest information from the touch sensors or continuous key angle
andrewm@0 76 timestamp_type TouchkeyOnsetAngleMapping::performMapping() {
andrewm@0 77 timestamp_type currentTimestamp = keyboard_.schedulerCurrentTimestamp();
andrewm@0 78
andrewm@0 79 if(rampLength_ != 0 && currentTimestamp <= rampBeginTime_ + rampLength_) {
andrewm@0 80 float rampValue = 1.0 - (float)(currentTimestamp - rampBeginTime_)/(float)rampLength_;
andrewm@0 81
andrewm@0 82 lastPitchBendSemitones_ = startingPitchBendSemitones_ * rampValue;
andrewm@0 83 #ifdef DEBUG_NOTE_ONSET_MAPPING
andrewm@0 84 std::cout << "onset pitch = " << lastPitchBendSemitones_ << endl;
andrewm@0 85 #endif
andrewm@0 86 sendPitchBendMessage(lastPitchBendSemitones_);
andrewm@0 87 }
andrewm@0 88 else if(lastPitchBendSemitones_ != 0) {
andrewm@0 89 lastPitchBendSemitones_ = 0;
andrewm@0 90 #ifdef DEBUG_NOTE_ONSET_MAPPING
andrewm@0 91 std::cout << "onset pitch = " << lastPitchBendSemitones_ << endl;
andrewm@0 92 #endif
andrewm@0 93 sendPitchBendMessage(lastPitchBendSemitones_);
andrewm@0 94 }
andrewm@0 95
andrewm@0 96 // Register for the next update by returning its timestamp
andrewm@0 97 nextScheduledTimestamp_ = currentTimestamp + updateInterval_;
andrewm@0 98 return nextScheduledTimestamp_;
andrewm@0 99 }
andrewm@0 100
andrewm@0 101 void TouchkeyOnsetAngleMapping::processOnset(timestamp_type timestamp) {
andrewm@0 102
andrewm@0 103 sampleBufferMutex_.enter();
andrewm@0 104
andrewm@0 105 // Look backwards from the current timestamp to find the velocity
andrewm@0 106 float calculatedVelocity = missing_value<float>::missing();
andrewm@0 107 bool touchWasOn = false;
andrewm@0 108 int sampleCount = 0;
andrewm@0 109
andrewm@0 110 #ifdef DEBUG_NOTE_ONSET_MAPPING
andrewm@0 111 std::cout << "processOnset begin = " << pastSamples_.beginIndex() << " end = " << pastSamples_.endIndex() << "\n";
andrewm@0 112 #endif
andrewm@0 113
andrewm@0 114 if(!pastSamples_.empty()) {
andrewm@0 115 Node<KeyTouchFrame>::size_type index = pastSamples_.endIndex() - 1;
andrewm@0 116 Node<KeyTouchFrame>::size_type mostRecentTouchPresentIndex = pastSamples_.endIndex() - 1;
andrewm@0 117 while(index >= pastSamples_.beginIndex()) {
andrewm@0 118 #ifdef DEBUG_NOTE_ONSET_MAPPING
andrewm@0 119 std::cout << "examining sample " << index << " with " << pastSamples_[index].count << " touches and time diff " << timestamp - pastSamples_.timestampAt(index) << "\n";
andrewm@0 120 #endif
andrewm@0 121 if(timestamp - pastSamples_.timestampAt(index) >= maxLookbackTime_)
andrewm@0 122 break;
andrewm@0 123 if(pastSamples_[index].count == 0) {
andrewm@0 124 if(touchWasOn) {
andrewm@0 125 // We found a break in the touch; stop here. But don't stop
andrewm@0 126 // if the first frames we consider have no touches.
andrewm@0 127 if(index < pastSamples_.endIndex() - 1)
andrewm@0 128 index++;
andrewm@0 129 break;
andrewm@0 130 }
andrewm@0 131 }
andrewm@0 132 else if(!touchWasOn) {
andrewm@0 133 mostRecentTouchPresentIndex = index;
andrewm@0 134 touchWasOn = true;
andrewm@0 135 }
andrewm@0 136 if(sampleCount++ >= kDefaultMaxLookbackSamples)
andrewm@0 137 break;
andrewm@0 138
andrewm@0 139 // Can't decrement past 0 in an unsigned type
andrewm@0 140 if(index == 0)
andrewm@0 141 break;
andrewm@0 142 index--;
andrewm@0 143 }
andrewm@0 144
andrewm@0 145 // If we fell off the beginning of the buffer, back up.
andrewm@0 146 if(index < pastSamples_.beginIndex())
andrewm@0 147 index = pastSamples_.beginIndex();
andrewm@0 148
andrewm@0 149 // Need at least two points for this calculation to work
andrewm@0 150 timestamp_type endingTimestamp = pastSamples_.timestampAt(mostRecentTouchPresentIndex);
andrewm@0 151 timestamp_type startingTimestamp = pastSamples_.timestampAt(index);
andrewm@0 152 if(endingTimestamp - startingTimestamp > 0) {
andrewm@0 153 float endingPosition = pastSamples_[mostRecentTouchPresentIndex].locs[0];
andrewm@0 154 float startingPosition = pastSamples_[index].locs[0];
andrewm@0 155 calculatedVelocity = (endingPosition - startingPosition) / (endingTimestamp - startingTimestamp);
andrewm@0 156 }
andrewm@0 157 else { // DEBUG
andrewm@0 158 #ifdef DEBUG_NOTE_ONSET_MAPPING
andrewm@0 159 std::cout << "Found 0 timestamp difference on key onset (indices " << index << " and " << pastSamples_.endIndex() - 1 << "\n";
andrewm@0 160 #endif
andrewm@0 161 }
andrewm@0 162 }
andrewm@0 163 else {
andrewm@0 164 #ifdef DEBUG_NOTE_ONSET_MAPPING
andrewm@0 165 std::cout << "Found empty touch buffer on key onset\n";
andrewm@0 166 #endif
andrewm@0 167 }
andrewm@0 168
andrewm@0 169 sampleBufferMutex_.exit();
andrewm@0 170
andrewm@0 171 if(!missing_value<float>::isMissing(calculatedVelocity)) {
andrewm@0 172 #ifdef DEBUG_NOTE_ONSET_MAPPING
andrewm@0 173 std::cout << "Found onset velocity " << calculatedVelocity << " on note " << noteNumber_ << std::endl;
andrewm@0 174 #endif
andrewm@0 175 if(calculatedVelocity > 6.0)
andrewm@0 176 calculatedVelocity = 6.0;
andrewm@0 177
andrewm@0 178 if(calculatedVelocity > 1.5) {
andrewm@0 179 startingPitchBendSemitones_ = -1.0 * (calculatedVelocity / 5.0);
andrewm@0 180 rampLength_ = milliseconds_to_timestamp((50.0 + calculatedVelocity*25.0));
andrewm@0 181 rampBeginTime_ = keyboard_.schedulerCurrentTimestamp();
andrewm@0 182 }
andrewm@0 183 else
andrewm@0 184 rampLength_ = 0;
andrewm@0 185
andrewm@0 186 sendOnsetAngleMessage(calculatedVelocity);
andrewm@0 187 }
andrewm@0 188 }
andrewm@0 189
andrewm@0 190 void TouchkeyOnsetAngleMapping::sendOnsetAngleMessage(float onsetAngle, bool force) {
andrewm@0 191 if(force || !suspended_) {
andrewm@0 192 keyboard_.sendMessage("/touchkeys/onsetangle", "if", noteNumber_, onsetAngle, LO_ARGS_END);
andrewm@0 193 }
andrewm@0 194 }
andrewm@0 195
andrewm@0 196 // Send the pitch bend message of a given number of a semitones. Send by OSC,
andrewm@0 197 // which can be mapped to MIDI CC externally
andrewm@0 198 void TouchkeyOnsetAngleMapping::sendPitchBendMessage(float pitchBendSemitones, bool force) {
andrewm@0 199 if(force || !suspended_)
andrewm@0 200 keyboard_.sendMessage("/touchkeys/scoop", "if", noteNumber_, pitchBendSemitones, LO_ARGS_END);
andrewm@0 201 }