annotate Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMapping.cpp @ 56:b4a2d2ae43cf tip

merge
author Andrew McPherson <andrewm@eecs.qmul.ac.uk>
date Fri, 23 Nov 2018 15:48:14 +0000
parents 78b9808a2c65
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 TouchkeyReleaseAngleMapping.cpp: per-note mapping for the release angle
andrewm@0 21 mapping, which measures the speed of finger motion along the key at
andrewm@0 22 the time of MIDI note off.
andrewm@0 23 */
andrewm@0 24
andrewm@0 25 #include "TouchkeyReleaseAngleMapping.h"
andrewm@46 26 #include "TouchkeyReleaseAngleMappingFactory.h"
andrewm@0 27 #include "../MappingFactory.h"
andrewm@0 28 #include "../../TouchKeys/MidiOutputController.h"
andrewm@0 29 #include "../MappingScheduler.h"
andrewm@0 30
andrewm@46 31 #define DEBUG_RELEASE_ANGLE_MAPPING
andrewm@46 32
andrewm@0 33 // Class constants
andrewm@0 34 const int TouchkeyReleaseAngleMapping::kDefaultFilterBufferLength = 30;
andrewm@0 35 const timestamp_diff_type TouchkeyReleaseAngleMapping::kDefaultMaxLookbackTime = milliseconds_to_timestamp(100);
andrewm@0 36
andrewm@46 37 const float TouchkeyReleaseAngleMapping::kDefaultUpMinimumAngle = 1.0;
andrewm@46 38 const float TouchkeyReleaseAngleMapping::kDefaultDownMinimumAngle = 1.0;
andrewm@46 39
andrewm@0 40 // Main constructor takes references/pointers from objects which keep track
andrewm@0 41 // of touch location, continuous key position and the state detected from that
andrewm@0 42 // position. The PianoKeyboard object is strictly required as it gives access to
andrewm@0 43 // Scheduler and OSC methods. The others are optional since any given system may
andrewm@0 44 // contain only one of continuous key position or touch sensitivity
andrewm@0 45 TouchkeyReleaseAngleMapping::TouchkeyReleaseAngleMapping(PianoKeyboard &keyboard, MappingFactory *factory, int noteNumber, Node<KeyTouchFrame>* touchBuffer,
andrewm@0 46 Node<key_position>* positionBuffer, KeyPositionTracker* positionTracker)
andrewm@0 47 : TouchkeyBaseMapping(keyboard, factory, noteNumber, touchBuffer, positionBuffer, positionTracker, false),
andrewm@46 48 upEnabled_(true), downEnabled_(true), upMinimumAngle_(kDefaultUpMinimumAngle), downMinimumAngle_(kDefaultDownMinimumAngle),
andrewm@0 49 pastSamples_(kDefaultFilterBufferLength), maxLookbackTime_(kDefaultMaxLookbackTime)
andrewm@0 50 {
andrewm@46 51 for(int i = 0; i < RELEASE_ANGLE_MAX_SEQUENCE_LENGTH; i++)
andrewm@46 52 upNotes_[i] = downNotes_[i] = upVelocities_[i] = downVelocities_[i] = 0;
andrewm@0 53 }
andrewm@0 54
andrewm@0 55 // Reset state back to defaults
andrewm@0 56 void TouchkeyReleaseAngleMapping::reset() {
andrewm@0 57 ScopedLock sl(sampleBufferMutex_);
andrewm@0 58
andrewm@0 59 TouchkeyBaseMapping::reset();
andrewm@0 60 pastSamples_.clear();
andrewm@0 61 }
andrewm@0 62
andrewm@0 63 // Resend all current parameters
andrewm@0 64 void TouchkeyReleaseAngleMapping::resend() {
andrewm@0 65 // Message is only sent at release; resend may not apply here.
andrewm@0 66 }
andrewm@0 67
andrewm@46 68 // Parameters for release angle algorithm
andrewm@46 69 void TouchkeyReleaseAngleMapping::setWindowSize(float windowSize) {
andrewm@46 70 // This was passed in in milliseconds and needs to be converted to a timestamp type
andrewm@46 71 maxLookbackTime_ = milliseconds_to_timestamp(windowSize);
andrewm@46 72 }
andrewm@46 73
andrewm@46 74 void TouchkeyReleaseAngleMapping::setUpMessagesEnabled(bool enable) {
andrewm@46 75 upEnabled_ = enable;
andrewm@46 76 }
andrewm@46 77
andrewm@46 78 void TouchkeyReleaseAngleMapping::setDownMessagesEnabled(bool enable) {
andrewm@46 79 downEnabled_ = enable;
andrewm@46 80 }
andrewm@46 81
andrewm@46 82 void TouchkeyReleaseAngleMapping::setUpMinimumAngle(float minAngle) {
andrewm@46 83 upMinimumAngle_ = fabsf(minAngle);
andrewm@46 84 }
andrewm@46 85
andrewm@46 86 void TouchkeyReleaseAngleMapping::setUpNote(int sequence, int note) {
andrewm@46 87 if(sequence < 0 || sequence >= RELEASE_ANGLE_MAX_SEQUENCE_LENGTH)
andrewm@46 88 return;
andrewm@46 89 if(note < 0 || note > 127)
andrewm@46 90 upNotes_[sequence] = 0;
andrewm@46 91 else
andrewm@46 92 upNotes_[sequence] = note;
andrewm@46 93 }
andrewm@46 94
andrewm@46 95 void TouchkeyReleaseAngleMapping::setUpVelocity(int sequence, int velocity) {
andrewm@46 96 if(sequence < 0 || sequence >= RELEASE_ANGLE_MAX_SEQUENCE_LENGTH)
andrewm@46 97 return;
andrewm@46 98 if(velocity < 0 || velocity > 127)
andrewm@46 99 upVelocities_[sequence] = 0;
andrewm@46 100 else
andrewm@46 101 upVelocities_[sequence] = velocity;
andrewm@46 102 }
andrewm@46 103
andrewm@46 104 void TouchkeyReleaseAngleMapping::setDownMinimumAngle(float minAngle) {
andrewm@46 105 downMinimumAngle_ = fabsf(minAngle);
andrewm@46 106 }
andrewm@46 107
andrewm@46 108 void TouchkeyReleaseAngleMapping::setDownNote(int sequence, int note) {
andrewm@46 109 if(sequence < 0 || sequence >= RELEASE_ANGLE_MAX_SEQUENCE_LENGTH)
andrewm@46 110 return;
andrewm@46 111 if(note < 0 || note > 127)
andrewm@46 112 downNotes_[sequence] = 0;
andrewm@46 113 else
andrewm@46 114 downNotes_[sequence] = note;
andrewm@46 115 }
andrewm@46 116
andrewm@46 117 void TouchkeyReleaseAngleMapping::setDownVelocity(int sequence, int velocity) {
andrewm@46 118 if(sequence < 0 || sequence >= RELEASE_ANGLE_MAX_SEQUENCE_LENGTH)
andrewm@46 119 return;
andrewm@46 120 if(velocity < 0 || velocity > 127)
andrewm@46 121 downVelocities_[sequence] = 0;
andrewm@46 122 else
andrewm@46 123 downVelocities_[sequence] = velocity;
andrewm@46 124 }
andrewm@46 125
andrewm@0 126 // This method receives data from the touch buffer or possibly the continuous key angle (not used here)
andrewm@0 127 void TouchkeyReleaseAngleMapping::triggerReceived(TriggerSource* who, timestamp_type timestamp) {
andrewm@0 128 if(who == touchBuffer_) {
andrewm@0 129 ScopedLock sl(sampleBufferMutex_);
andrewm@0 130
andrewm@0 131 // Save the latest frame, even if it is an empty touch (we need to know what happened even
andrewm@0 132 // after the touch ends since the MIDI off may come later)
andrewm@0 133 if(!touchBuffer_->empty())
andrewm@0 134 pastSamples_.insert(touchBuffer_->latest(), touchBuffer_->latestTimestamp());
andrewm@0 135 }
andrewm@0 136 }
andrewm@0 137
andrewm@0 138 // Mapping method. This actually does the real work of sending OSC data in response to the
andrewm@0 139 // latest information from the touch sensors or continuous key angle
andrewm@0 140 timestamp_type TouchkeyReleaseAngleMapping::performMapping() {
andrewm@0 141 // Nothing to do here until note is released.
andrewm@0 142 // Register for the next update by returning its timestamp
andrewm@0 143 // TODO: do we even need this? Check Mapping::engage() and Mapping::disengage()
andrewm@0 144 timestamp_type currentTimestamp = keyboard_.schedulerCurrentTimestamp();
andrewm@0 145 nextScheduledTimestamp_ = currentTimestamp + updateInterval_;
andrewm@0 146 return nextScheduledTimestamp_;
andrewm@0 147 }
andrewm@0 148
andrewm@46 149 void TouchkeyReleaseAngleMapping::midiNoteOffReceived(int channel) {
andrewm@46 150 processRelease();
andrewm@46 151 }
andrewm@46 152
andrewm@46 153 void TouchkeyReleaseAngleMapping::processRelease(/*timestamp_type timestamp*/) {
andrewm@0 154 if(!noteIsOn_) {
andrewm@0 155 return;
andrewm@0 156 }
andrewm@0 157
andrewm@0 158 sampleBufferMutex_.enter();
andrewm@0 159
andrewm@0 160 // Look backwards from the current timestamp to find the velocity
andrewm@0 161 float calculatedVelocity = missing_value<float>::missing();
andrewm@0 162 bool touchWasOn = false;
andrewm@0 163
andrewm@0 164 if(!pastSamples_.empty()) {
andrewm@0 165 Node<KeyTouchFrame>::size_type index = pastSamples_.endIndex() - 1;
andrewm@0 166 Node<KeyTouchFrame>::size_type mostRecentTouchPresentIndex = pastSamples_.endIndex() - 1;
andrewm@46 167 timestamp_type lastTimestamp = pastSamples_.timestampAt(index);
andrewm@46 168
andrewm@0 169 while(index >= pastSamples_.beginIndex()) {
andrewm@46 170 #ifdef DEBUG_RELEASE_ANGLE_MAPPING
andrewm@46 171 std::cout << "examining sample " << index << " with " << pastSamples_[index].count << " touches and time diff " << lastTimestamp - pastSamples_.timestampAt(index) << "\n";
andrewm@46 172 #endif
andrewm@46 173 if(lastTimestamp - pastSamples_.timestampAt(index) >= maxLookbackTime_)
andrewm@0 174 break;
andrewm@0 175 if(pastSamples_[index].count == 0) {
andrewm@0 176 if(touchWasOn) {
andrewm@0 177 // We found a break in the touch; stop here. But don't stop
andrewm@0 178 // if the first frames we consider have no touches.
andrewm@0 179 if(index < pastSamples_.endIndex() - 1)
andrewm@0 180 index++;
andrewm@0 181 break;
andrewm@0 182 }
andrewm@0 183 }
andrewm@0 184 else if(!touchWasOn) {
andrewm@0 185 mostRecentTouchPresentIndex = index;
andrewm@0 186 touchWasOn = true;
andrewm@0 187 }
andrewm@0 188 // Can't decrement past 0 in an unsigned type
andrewm@0 189 if(index == 0)
andrewm@0 190 break;
andrewm@0 191 index--;
andrewm@0 192 }
andrewm@0 193
andrewm@0 194 // If we fell off the beginning of the buffer, back up.
andrewm@0 195 if(index < pastSamples_.beginIndex())
andrewm@0 196 index = pastSamples_.beginIndex();
andrewm@0 197
andrewm@0 198 // Need at least two points for this calculation to work
andrewm@0 199 timestamp_type endingTimestamp = pastSamples_.timestampAt(mostRecentTouchPresentIndex);
andrewm@0 200 timestamp_type startingTimestamp = pastSamples_.timestampAt(index);
andrewm@0 201 if(endingTimestamp - startingTimestamp > 0) {
andrewm@0 202 float endingPosition = pastSamples_[mostRecentTouchPresentIndex].locs[0];
andrewm@0 203 float startingPosition = pastSamples_[index].locs[0];
andrewm@0 204 calculatedVelocity = (endingPosition - startingPosition) / (endingTimestamp - startingTimestamp);
andrewm@0 205 }
andrewm@0 206 else { // DEBUG
andrewm@46 207 #ifdef DEBUG_RELEASE_ANGLE_MAPPING
andrewm@0 208 std::cout << "Found 0 timestamp difference on key release (indices " << index << " and " << pastSamples_.endIndex() - 1 << "\n";
andrewm@46 209 #endif
andrewm@0 210 }
andrewm@0 211 }
andrewm@46 212 else {
andrewm@46 213 #ifdef DEBUG_RELEASE_ANGLE_MAPPING
andrewm@0 214 std::cout << "Found empty touch buffer on key release\n";
andrewm@46 215 #endif
andrewm@46 216 }
andrewm@0 217
andrewm@0 218 sampleBufferMutex_.exit();
andrewm@0 219
andrewm@0 220 if(!missing_value<float>::isMissing(calculatedVelocity)) {
andrewm@46 221 #ifdef DEBUG_RELEASE_ANGLE_MAPPING
andrewm@0 222 std::cout << "Found release velocity " << calculatedVelocity << " on note " << noteNumber_ << std::endl;
andrewm@46 223 #endif
andrewm@0 224 sendReleaseAngleMessage(calculatedVelocity);
andrewm@0 225 }
andrewm@0 226
andrewm@0 227
andrewm@46 228 // Check if we're supposed to clean up now
andrewm@0 229 finished_ = true;
andrewm@0 230 if(finishRequested_)
andrewm@0 231 acknowledgeFinish();
andrewm@0 232 // KLUDGE
andrewm@0 233 }
andrewm@0 234
andrewm@0 235 void TouchkeyReleaseAngleMapping::sendReleaseAngleMessage(float releaseAngle, bool force) {
andrewm@0 236 if(force || !suspended_) {
andrewm@0 237 keyboard_.sendMessage("/touchkeys/releaseangle", "if", noteNumber_, releaseAngle, LO_ARGS_END);
andrewm@0 238
andrewm@46 239 if(keyboard_.midiOutputController() == 0)
andrewm@46 240 return;
andrewm@46 241
andrewm@46 242 int port = static_cast<TouchkeyReleaseAngleMappingFactory*>(factory_)->segment().outputPort();
andrewm@46 243 int ch = keyboard_.key(noteNumber_)->midiChannel();
andrewm@46 244
andrewm@46 245 // Check if the release angle exceeds either the up or down threshold
andrewm@46 246 if(releaseAngle > 0 && fabs(releaseAngle) >= upMinimumAngle_ && upEnabled_) {
andrewm@46 247 #ifdef DEBUG_RELEASE_ANGLE_MAPPING
andrewm@46 248 std::cout << "Send up-release messages for note " << noteNumber_ << " on channel " << ch << "\n";
andrewm@46 249 #endif
andrewm@46 250 // Send key switches: note on and note off in reverse orders
andrewm@46 251 for(int i = 0; i < RELEASE_ANGLE_MAX_SEQUENCE_LENGTH; i++) {
andrewm@46 252 if(upNotes_[i] != 0)
andrewm@46 253 keyboard_.midiOutputController()->sendNoteOn(port, ch, upNotes_[i], upVelocities_[i]);
andrewm@46 254 }
andrewm@46 255
andrewm@46 256 for(int i = RELEASE_ANGLE_MAX_SEQUENCE_LENGTH - 1; i >= 0; i--) {
andrewm@46 257 if(upNotes_[i] != 0)
andrewm@46 258 keyboard_.midiOutputController()->sendNoteOff(port, ch, upNotes_[i]);
andrewm@46 259 }
andrewm@46 260 }
andrewm@46 261 else if(releaseAngle < 0 && fabs(releaseAngle) >= downMinimumAngle_ && downEnabled_) {
andrewm@46 262 #ifdef DEBUG_RELEASE_ANGLE_MAPPING
andrewm@46 263 std::cout << "Send down-release messages for note " << noteNumber_ << " on channel " << ch << "\n";
andrewm@46 264 #endif
andrewm@46 265 // Send key switches: note on and note off in reverse orders
andrewm@46 266 for(int i = 0; i < RELEASE_ANGLE_MAX_SEQUENCE_LENGTH; i++) {
andrewm@46 267 if(downNotes_[i] != 0)
andrewm@46 268 keyboard_.midiOutputController()->sendNoteOn(port, ch, downNotes_[i], downVelocities_[i]);
andrewm@46 269 }
andrewm@46 270
andrewm@46 271 for(int i = RELEASE_ANGLE_MAX_SEQUENCE_LENGTH - 1; i >= 0; i--) {
andrewm@46 272 if(downNotes_[i] != 0)
andrewm@46 273 keyboard_.midiOutputController()->sendNoteOff(port, ch, downNotes_[i]);
andrewm@46 274 }
andrewm@46 275 }
andrewm@46 276
andrewm@46 277 // TODO: delayed release
andrewm@46 278
andrewm@0 279 #ifdef TROMBONE
andrewm@0 280 // KLUDGE: figure out how to do this more elegantly
andrewm@0 281 if(keyboard_.midiOutputController() != 0) {
andrewm@0 282 if(releaseAngle > 1.0) {
andrewm@0 283 keyboard_.midiOutputController()->sendNoteOn(0, 0, 36, 64);
andrewm@0 284 keyboard_.midiOutputController()->sendNoteOn(0, 0, 31, 96);
andrewm@0 285 keyboard_.midiOutputController()->sendNoteOff(0, 0, 31);
andrewm@0 286 keyboard_.midiOutputController()->sendNoteOff(0, 0, 36);
andrewm@0 287 }
andrewm@0 288 else if(releaseAngle < -1.5) {
andrewm@0 289 keyboard_.midiOutputController()->sendNoteOn(0, 0, 36, 64);
andrewm@0 290 keyboard_.midiOutputController()->sendNoteOn(0, 0, 33, 80);
andrewm@0 291 keyboard_.midiOutputController()->sendNoteOff(0, 0, 33);
andrewm@0 292 keyboard_.midiOutputController()->sendNoteOff(0, 0, 36);
andrewm@0 293 }
andrewm@0 294 }
andrewm@0 295 #elif defined(TRUMPET)
andrewm@0 296 if(keyboard_.midiOutputController() != 0) {
andrewm@0 297 if(releaseAngle > 1.0) {
andrewm@0 298 keyboard_.midiOutputController()->sendNoteOn(0, 0, 48, 64);
andrewm@0 299 keyboard_.midiOutputController()->sendNoteOn(0, 0, 42, 96);
andrewm@0 300 //keyboard_.midiOutputController()->sendNoteOff(0, 0, 42);
andrewm@0 301 keyboard_.midiOutputController()->sendNoteOff(0, 0, 48);
andrewm@0 302 keyboard_.scheduleEvent(this, boost::bind(&TouchkeyReleaseAngleMapping::releaseKeySwitch, this),
andrewm@0 303 keyboard_.schedulerCurrentTimestamp() + milliseconds_to_timestamp(250));
andrewm@0 304 }
andrewm@0 305 else if(releaseAngle < -1.5) {
andrewm@0 306 keyboard_.midiOutputController()->sendNoteOn(0, 0, 48, 64);
andrewm@0 307 keyboard_.midiOutputController()->sendNoteOn(0, 0, 46, 96);
andrewm@0 308 //keyboard_.midiOutputController()->sendNoteOff(0, 0, 47);
andrewm@0 309 keyboard_.midiOutputController()->sendNoteOff(0, 0, 48);
andrewm@0 310 keyboard_.scheduleEvent(this, boost::bind(&TouchkeyReleaseAngleMapping::releaseKeySwitch, this),
andrewm@0 311 keyboard_.schedulerCurrentTimestamp() + milliseconds_to_timestamp(250));
andrewm@0 312 }
andrewm@0 313 else {
andrewm@0 314 // Check if we're suppose to clean up now
andrewm@0 315 finished_ = true;
andrewm@0 316 if(finishRequested_)
andrewm@0 317 acknowledgeFinish();
andrewm@0 318 }
andrewm@0 319 }
andrewm@0 320 #endif
andrewm@0 321 }
andrewm@0 322 }
andrewm@0 323
andrewm@0 324 timestamp_type TouchkeyReleaseAngleMapping::releaseKeySwitch() {
andrewm@0 325 keyboard_.midiOutputController()->sendNoteOff(0, 0, 42);
andrewm@0 326 keyboard_.midiOutputController()->sendNoteOff(0, 0, 46);
andrewm@0 327
andrewm@0 328 // Check if we're suppose to clean up now
andrewm@0 329 /*finished_ = true;
andrewm@0 330 if(finishRequested_)
andrewm@0 331 acknowledgeFinish();*/
andrewm@0 332
andrewm@0 333 return 0;
andrewm@0 334 }