annotate Source/Mappings/Vibrato/TouchkeyVibratoMapping.cpp @ 56:b4a2d2ae43cf tip

merge
author Andrew McPherson <andrewm@eecs.qmul.ac.uk>
date Fri, 23 Nov 2018 15:48:14 +0000
parents ff5d65c69e73
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 TouchkeyVibratoMapping.cpp: per-note mapping for the vibrato mapping class,
andrewm@0 21 which creates vibrato through side-to-side motion of the finger on the
andrewm@0 22 key surface.
andrewm@0 23 */
andrewm@0 24
andrewm@0 25 #include "TouchkeyVibratoMapping.h"
andrewm@0 26 #include "../MappingScheduler.h"
andrewm@0 27 #include <vector>
andrewm@0 28 #include <climits>
andrewm@0 29 #include <cmath>
andrewm@0 30
andrewm@0 31 #undef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 32
andrewm@0 33 // Class constants
andrewm@0 34 const int TouchkeyVibratoMapping::kDefaultMIDIChannel = 0;
andrewm@0 35 const int TouchkeyVibratoMapping::kDefaultFilterBufferLength = 30;
andrewm@0 36
andrewm@0 37 const float TouchkeyVibratoMapping::kDefaultVibratoThresholdX = 0.05;
andrewm@0 38 const float TouchkeyVibratoMapping::kDefaultVibratoRatioX = 0.3;
andrewm@0 39 const float TouchkeyVibratoMapping::kDefaultVibratoThresholdY = 0.02;
andrewm@0 40 const float TouchkeyVibratoMapping::kDefaultVibratoRatioY = 0.8;
andrewm@0 41 const timestamp_diff_type TouchkeyVibratoMapping::kDefaultVibratoTimeout = microseconds_to_timestamp(400000); // 0.4s
andrewm@0 42 const float TouchkeyVibratoMapping::kDefaultVibratoPrescaler = 2.0;
andrewm@0 43 const float TouchkeyVibratoMapping::kDefaultVibratoRangeSemitones = 1.25;
andrewm@0 44
andrewm@0 45 const timestamp_diff_type TouchkeyVibratoMapping::kZeroCrossingMinimumTime = microseconds_to_timestamp(50000); // 50ms
andrewm@0 46 const timestamp_diff_type TouchkeyVibratoMapping::kMinimumOnsetTime = microseconds_to_timestamp(30000); // 30ms
andrewm@0 47 const timestamp_diff_type TouchkeyVibratoMapping::kMaximumOnsetTime = microseconds_to_timestamp(300000); // 300ms
andrewm@0 48 const timestamp_diff_type TouchkeyVibratoMapping::kMinimumReleaseTime = microseconds_to_timestamp(30000); // 30ms
andrewm@0 49 const timestamp_diff_type TouchkeyVibratoMapping::kMaximumReleaseTime = microseconds_to_timestamp(300000); // 300ms
andrewm@0 50
andrewm@0 51 const float TouchkeyVibratoMapping::kWhiteKeySingleAxisThreshold = (7.0 / 19.0);
andrewm@0 52
andrewm@0 53 // Main constructor takes references/pointers from objects which keep track
andrewm@0 54 // of touch location, continuous key position and the state detected from that
andrewm@0 55 // position. The PianoKeyboard object is strictly required as it gives access to
andrewm@0 56 // Scheduler and OSC methods. The others are optional since any given system may
andrewm@0 57 // contain only one of continuous key position or touch sensitivity
andrewm@0 58
andrewm@0 59 TouchkeyVibratoMapping::TouchkeyVibratoMapping(PianoKeyboard &keyboard, MappingFactory *factory, int noteNumber, Node<KeyTouchFrame>* touchBuffer,
andrewm@0 60 Node<key_position>* positionBuffer, KeyPositionTracker* positionTracker)
andrewm@0 61 : TouchkeyBaseMapping(keyboard, factory, noteNumber, touchBuffer, positionBuffer, positionTracker),
andrewm@0 62 vibratoState_(kStateInactive),
andrewm@0 63 rampBeginTime_(missing_value<timestamp_type>::missing()),
andrewm@0 64 rampScaleValue_(0),
andrewm@0 65 rampLength_(0),
andrewm@0 66 lastCalculatedRampValue_(0),
andrewm@0 67 onsetThresholdX_(kDefaultVibratoThresholdX), onsetThresholdY_(kDefaultVibratoThresholdY),
andrewm@0 68 onsetRatioX_(kDefaultVibratoRatioX), onsetRatioY_(kDefaultVibratoRatioY),
andrewm@0 69 onsetTimeout_(kDefaultVibratoTimeout),
andrewm@0 70 onsetLocationX_(missing_value<float>::missing()),
andrewm@0 71 onsetLocationY_(missing_value<float>::missing()),
andrewm@0 72 lastX_(missing_value<float>::missing()), lastY_(missing_value<float>::missing()),
andrewm@0 73 idOfCurrentTouch_(-1),
andrewm@0 74 lastTimestamp_(missing_value<timestamp_type>::missing()),
andrewm@0 75 lastProcessedIndex_(0),
andrewm@0 76 lastZeroCrossingTimestamp_(missing_value<timestamp_type>::missing()),
andrewm@0 77 lastZeroCrossingInterval_(0),
andrewm@0 78 lastSampleWasPositive_(false),
andrewm@0 79 foundFirstExtremum_(false),
andrewm@0 80 firstExtremumX_(0), firstExtremumY_(0),
andrewm@0 81 firstExtremumTimestamp_(missing_value<timestamp_diff_type>::missing()),
andrewm@0 82 lastExtremumTimestamp_(missing_value<timestamp_diff_type>::missing()),
andrewm@0 83 //vibratoType_(kDefaultVibratoType),
andrewm@0 84 vibratoPrescaler_(kDefaultVibratoPrescaler),
andrewm@0 85 vibratoRangeSemitones_(kDefaultVibratoRangeSemitones),
andrewm@0 86 lastPitchBendSemitones_(0),
andrewm@0 87 rawDistance_(kDefaultFilterBufferLength),
andrewm@0 88 filteredDistance_(kDefaultFilterBufferLength, rawDistance_)
andrewm@0 89 {
andrewm@0 90 // Initialize the filter coefficients for filtered key velocity (used for vibrato detection)
andrewm@0 91 std::vector<double> bCoeffs, aCoeffs;
andrewm@0 92 designSecondOrderBandpass(bCoeffs, aCoeffs, 9.0, 0.707, 200.0);
andrewm@0 93 std::vector<float> bCf(bCoeffs.begin(), bCoeffs.end()), aCf(aCoeffs.begin(), aCoeffs.end());
andrewm@0 94 filteredDistance_.setCoefficients(bCf, aCf);
andrewm@0 95 filteredDistance_.setAutoCalculate(true);
andrewm@0 96
andrewm@0 97 //setOscController(&keyboard_);
andrewm@0 98 resetDetectionState();
andrewm@0 99 }
andrewm@0 100
andrewm@0 101 TouchkeyVibratoMapping::~TouchkeyVibratoMapping() {
andrewm@0 102 }
andrewm@0 103
andrewm@0 104 // Turn off mapping of data. Remove our callback from the scheduler
andrewm@0 105 void TouchkeyVibratoMapping::disengage(bool shouldDelete) {
andrewm@0 106 sendVibratoMessage(0.0);
andrewm@0 107 TouchkeyBaseMapping::disengage(shouldDelete);
andrewm@0 108 }
andrewm@0 109
andrewm@0 110 // Reset state back to defaults
andrewm@0 111 void TouchkeyVibratoMapping::reset() {
andrewm@0 112 TouchkeyBaseMapping::reset();
andrewm@0 113 sendVibratoMessage(0.0);
andrewm@0 114 resetDetectionState();
andrewm@0 115 }
andrewm@0 116
andrewm@0 117 // Resend all current parameters
andrewm@0 118 void TouchkeyVibratoMapping::resend() {
andrewm@0 119 sendVibratoMessage(lastPitchBendSemitones_, true);
andrewm@0 120 }
andrewm@0 121
andrewm@0 122 // Set the range of vibrato
andrewm@0 123 void TouchkeyVibratoMapping::setRange(float rangeSemitones) {
andrewm@0 124 vibratoRangeSemitones_ = rangeSemitones;
andrewm@0 125 }
andrewm@0 126
andrewm@0 127 // Set the vibrato prescaler
andrewm@0 128 void TouchkeyVibratoMapping::setPrescaler(float prescaler) {
andrewm@0 129 vibratoPrescaler_ = prescaler;
andrewm@0 130 }
andrewm@0 131
andrewm@0 132 // Set the vibrato detection thresholds
andrewm@0 133 void TouchkeyVibratoMapping::setThresholds(float thresholdX, float thresholdY, float ratioX, float ratioY) {
andrewm@0 134 onsetThresholdX_ = thresholdX;
andrewm@0 135 onsetThresholdY_ = thresholdY;
andrewm@0 136 onsetRatioX_ = ratioX;
andrewm@0 137 onsetRatioY_ = ratioY;
andrewm@0 138 }
andrewm@0 139
andrewm@0 140 // Set the timeout for vibrato detection
andrewm@0 141 void TouchkeyVibratoMapping::setTimeout(timestamp_diff_type timeout) {
andrewm@0 142 onsetTimeout_ = timeout;
andrewm@0 143 }
andrewm@0 144
andrewm@0 145 // Trigger method. This receives updates from the TouchKey data or from state changes in
andrewm@0 146 // the continuous key position (KeyPositionTracker). It will potentially change the scheduled
andrewm@0 147 // behavior of future mapping calls, but the actual OSC messages should be transmitted in a different
andrewm@0 148 // thread.
andrewm@0 149 void TouchkeyVibratoMapping::triggerReceived(TriggerSource* who, timestamp_type timestamp) {
andrewm@0 150 if(who == 0)
andrewm@0 151 return;
andrewm@0 152
andrewm@0 153 if(who == touchBuffer_) {
andrewm@0 154 if(!touchBuffer_->empty()) {
andrewm@0 155 // New touch data is available. Find the distance from the onset location.
andrewm@0 156 KeyTouchFrame frame = touchBuffer_->latest();
andrewm@0 157 lastTimestamp_ = timestamp;
andrewm@0 158
andrewm@0 159 if(frame.count == 0) {
andrewm@0 160 // No touches. Last values are "missing", and we're not tracking any
andrewm@0 161 // particular touch ID
andrewm@0 162 lastX_ = lastY_ = missing_value<float>::missing();
andrewm@0 163 idOfCurrentTouch_ = -1;
andrewm@0 164 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 165 std::cout << "Touch off\n";
andrewm@0 166 #endif
andrewm@0 167 }
andrewm@0 168 else {
andrewm@0 169 // At least one touch. Check if we are already tracking an ID and, if so,
andrewm@0 170 // use its coordinates. Otherwise grab the lowest current ID.
andrewm@0 171
andrewm@0 172 bool foundCurrentTouch = false;
andrewm@0 173
andrewm@0 174 if(idOfCurrentTouch_ >= 0) {
andrewm@0 175 for(int i = 0; i < frame.count; i++) {
andrewm@0 176 if(frame.ids[i] == idOfCurrentTouch_) {
andrewm@0 177 lastY_ = frame.locs[i];
andrewm@0 178 if(frame.locH < 0 || (keyIsWhite() && lastY_ > kWhiteKeySingleAxisThreshold))
andrewm@0 179 lastX_ = missing_value<float>::missing();
andrewm@0 180 else
andrewm@0 181 lastX_ = frame.locH;
andrewm@0 182 foundCurrentTouch = true;
andrewm@0 183 break;
andrewm@0 184 }
andrewm@0 185 }
andrewm@0 186 }
andrewm@0 187
andrewm@0 188 if(!foundCurrentTouch) {
andrewm@0 189 // Assign a new touch to be tracked
andrewm@0 190 int lowestRemainingId = INT_MAX;
andrewm@0 191 int lowestIndex = 0;
andrewm@0 192
andrewm@0 193 for(int i = 0; i < frame.count; i++) {
andrewm@0 194 if(frame.ids[i] < lowestRemainingId) {
andrewm@0 195 lowestRemainingId = frame.ids[i];
andrewm@0 196 lowestIndex = i;
andrewm@0 197 }
andrewm@0 198 }
andrewm@0 199
andrewm@0 200 idOfCurrentTouch_ = lowestRemainingId;
andrewm@0 201 lastY_ = frame.locs[lowestIndex];
andrewm@0 202 if(frame.locH < 0 || (keyIsWhite() && lastY_ > kWhiteKeySingleAxisThreshold))
andrewm@0 203 lastX_ = missing_value<float>::missing();
andrewm@0 204 else
andrewm@0 205 lastX_ = frame.locH;
andrewm@0 206 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 207 std::cout << "Previous touch stopped; now ID " << idOfCurrentTouch_ << " at (" << lastX_ << ", " << lastY_ << ")\n";
andrewm@0 208 #endif
andrewm@0 209 }
andrewm@0 210
andrewm@0 211 // Now we have an X and (maybe) a Y coordinate for the most recent touch.
andrewm@0 212 // Check whether we have an initial location (if the note is active).
andrewm@0 213 if(noteIsOn_) {
andrewm@0 214 //ScopedLock sl(distanceAccessMutex_);
andrewm@0 215
andrewm@0 216 if(missing_value<float>::isMissing(onsetLocationY_) ||
andrewm@0 217 !foundCurrentTouch) {
andrewm@0 218 // Note is on but touch hasn't yet arrived --> this touch becomes
andrewm@0 219 // our onset location. Alternatively, the current touch is a different
andrewm@0 220 // ID from the previous one.
andrewm@0 221 onsetLocationY_ = lastY_;
andrewm@0 222 onsetLocationX_ = lastX_;
andrewm@0 223
andrewm@0 224 // Clear buffer and start with 0 distance for this point
andrewm@0 225 clearBuffers();
andrewm@0 226
andrewm@0 227 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 228 std::cout << "Starting at (" << onsetLocationX_ << ", " << onsetLocationY_ << ")\n";
andrewm@0 229 #endif
andrewm@0 230 }
andrewm@0 231 else {
andrewm@0 232 float distance = 0.0;
andrewm@0 233
andrewm@0 234 // Note is on and a start location exists. Calculate distance between
andrewm@0 235 // start location and the current point.
andrewm@0 236
andrewm@0 237 if(missing_value<float>::isMissing(onsetLocationX_) &&
andrewm@0 238 !missing_value<float>::isMissing(lastX_)) {
andrewm@0 239 // No X location indicated for onset but we have one now.
andrewm@0 240 // Update the onset X location.
andrewm@0 241 onsetLocationX_ = lastX_;
andrewm@0 242 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 243 std::cout << "Found first X location at " << onsetLocationX_ << std::endl;
andrewm@0 244 #endif
andrewm@0 245 }
andrewm@0 246
andrewm@0 247
andrewm@0 248 if(missing_value<float>::isMissing(lastX_) ||
andrewm@0 249 missing_value<float>::isMissing(onsetLocationX_)) {
andrewm@0 250 // If no X value is available on the current touch, calculate the distance
andrewm@0 251 // based on Y only. TODO: check whether we should do this by keeping the
andrewm@0 252 // last X value we recorded.
andrewm@0 253
andrewm@0 254 //distance = fabsf(lastY_ - onsetLocationY_);
andrewm@0 255 distance = lastY_ - onsetLocationY_;
andrewm@53 256 //distance = 0; // TESTING
andrewm@0 257 }
andrewm@0 258 else {
andrewm@0 259 // Euclidean distance between points
andrewm@0 260 //distance = sqrtf((lastY_ - onsetLocationY_) * (lastY_ - onsetLocationY_) +
andrewm@0 261 // (lastX_ - onsetLocationX_) * (lastX_ - onsetLocationX_));
andrewm@0 262 distance = lastX_ - onsetLocationX_;
andrewm@0 263 }
andrewm@0 264
andrewm@0 265 // Insert raw distance into the buffer. Bandpass filter calculates the next
andrewm@0 266 // sample automatically. The rest of the processing takes place in the dedicated
andrewm@0 267 // thread so as not to slow down commmunication with the hardware.
andrewm@0 268 rawDistance_.insert(distance, timestamp);
andrewm@0 269
andrewm@0 270 // Move the current scheduled event up to the present time.
andrewm@0 271 // FIXME: this may be more inefficient than just doing everything in the current thread!
andrewm@0 272 #ifdef NEW_MAPPING_SCHEDULER
andrewm@0 273 keyboard_.mappingScheduler().scheduleNow(this);
andrewm@0 274 #else
andrewm@0 275 keyboard_.unscheduleEvent(this);
andrewm@0 276 keyboard_.scheduleEvent(this, mappingAction_, keyboard_.schedulerCurrentTimestamp());
andrewm@0 277 #endif
andrewm@0 278
andrewm@0 279 //std::cout << "Raw distance " << distance << " filtered " << filteredDistance_.latest() << std::endl;
andrewm@0 280 }
andrewm@0 281 }
andrewm@0 282 }
andrewm@0 283 }
andrewm@0 284 }
andrewm@0 285 }
andrewm@0 286
andrewm@0 287 // Mapping method. This actually does the real work of sending OSC data in response to the
andrewm@0 288 // latest information from the touch sensors or continuous key angle
andrewm@0 289 timestamp_type TouchkeyVibratoMapping::performMapping() {
andrewm@0 290 //ScopedLock sl(distanceAccessMutex_);
andrewm@0 291
andrewm@0 292 timestamp_type currentTimestamp = keyboard_.schedulerCurrentTimestamp();
andrewm@0 293 bool newSamplePresent = false;
andrewm@0 294
andrewm@0 295 // Go through the filtered distance samples that are remaining to process.
andrewm@0 296 if(lastProcessedIndex_ < filteredDistance_.beginIndex() + 1) {
andrewm@0 297 // Fell off the beginning of the position buffer. Skip to the samples we have
andrewm@0 298 // (shouldn't happen except in cases of exceptional system load, and not too
andrewm@0 299 // consequential if it does happen).
andrewm@0 300 lastProcessedIndex_ = filteredDistance_.beginIndex() + 1;
andrewm@0 301 }
andrewm@0 302
andrewm@0 303 while(lastProcessedIndex_ < filteredDistance_.endIndex()) {
andrewm@0 304 float distance = filteredDistance_[lastProcessedIndex_];
andrewm@0 305 timestamp_type timestamp = filteredDistance_.timestampAt(lastProcessedIndex_);
andrewm@0 306 newSamplePresent = true;
andrewm@0 307
andrewm@0 308 if((distance > 0 && !lastSampleWasPositive_) ||
andrewm@0 309 (distance < 0 && lastSampleWasPositive_)) {
andrewm@0 310 // Found a zero crossing: save it if we're active or have at least found the
andrewm@0 311 // first extremum
andrewm@0 312 if(!missing_value<timestamp_type>::isMissing(lastZeroCrossingTimestamp_) &&
andrewm@0 313 (timestamp - lastZeroCrossingTimestamp_ > kZeroCrossingMinimumTime)) {
andrewm@0 314 if(vibratoState_ == kStateActive || vibratoState_ == kStateSwitchingOn ||
andrewm@0 315 foundFirstExtremum_) {
andrewm@0 316 lastZeroCrossingInterval_ = timestamp - lastZeroCrossingTimestamp_;
andrewm@0 317 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 318 std::cout << "Zero crossing interval " << lastZeroCrossingInterval_ << std::endl;
andrewm@0 319 #endif
andrewm@0 320 }
andrewm@0 321 }
andrewm@0 322 lastZeroCrossingTimestamp_ = timestamp;
andrewm@0 323 }
andrewm@0 324 lastSampleWasPositive_ = (distance > 0);
andrewm@0 325
andrewm@0 326 // If not currently engaged, check for the pattern of side-to-side motion that
andrewm@0 327 // begins a vibrato gesture.
andrewm@0 328 if(vibratoState_ == kStateInactive || vibratoState_ == kStateSwitchingOff) {
andrewm@0 329 if(foundFirstExtremum_) {
andrewm@0 330 // Already found first extremum. Look for second extremum in the opposite
andrewm@0 331 // direction of the given ratio from the original.
andrewm@0 332 if((firstExtremumX_ > 0 && distance < 0) ||
andrewm@0 333 (firstExtremumX_ < 0 && distance > 0)) {
andrewm@0 334 if(fabsf(distance) >= fabsf(firstExtremumX_) * onsetRatioX_) {
andrewm@0 335 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 336 std::cout << "Found second extremum at " << distance << ", TS " << timestamp << std::endl;
andrewm@0 337 #endif
andrewm@0 338 changeStateSwitchingOn(timestamp);
andrewm@0 339 }
andrewm@0 340 }
andrewm@0 341 else if(timestamp - lastExtremumTimestamp_ > onsetTimeout_) {
andrewm@0 342 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 343 std::cout << "Onset timeout at " << timestamp << endl;
andrewm@0 344 #endif
andrewm@0 345 resetDetectionState();
andrewm@0 346 }
andrewm@0 347 }
andrewm@0 348 else {
andrewm@0 349 if(fabsf(distance) >= onsetThresholdX_) {
andrewm@0 350 // TODO: differentiate X/Y here
andrewm@0 351 if(missing_value<float>::isMissing(firstExtremumX_) ||
andrewm@0 352 fabsf(distance) > fabsf(firstExtremumX_)) {
andrewm@0 353 firstExtremumX_ = distance;
andrewm@0 354 lastExtremumTimestamp_ = timestamp;
andrewm@0 355 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 356 std::cout << "First extremum candidate at " << firstExtremumX_ << ", TS " << lastExtremumTimestamp_ << std::endl;
andrewm@0 357 #endif
andrewm@0 358 }
andrewm@0 359 }
andrewm@0 360 else if(!missing_value<float>::isMissing(firstExtremumX_) &&
andrewm@0 361 fabsf(firstExtremumX_) > onsetThresholdX_) {
andrewm@0 362 // We must have found the first extremum since its maximum value is
andrewm@0 363 // above the threshold, and we must have moved away from it since we are
andrewm@0 364 // now below the threshold. Next step will be to look for extremum in
andrewm@0 365 // opposite direction. Save the timestamp of this location in case
andrewm@0 366 // another extremum is found later.
andrewm@0 367 firstExtremumTimestamp_ = lastExtremumTimestamp_;
andrewm@0 368 foundFirstExtremum_ = true;
andrewm@0 369 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 370 std::cout << "Found first extremum at " << firstExtremumX_ << ", TS " << lastExtremumTimestamp_ << std::endl;
andrewm@0 371 #endif
andrewm@0 372 }
andrewm@0 373 }
andrewm@0 374 }
andrewm@0 375 else {
andrewm@0 376 // Currently engaged. Look for timeout, defined as the finger staying below the lower (ratio-adjusted) threshold.
andrewm@0 377 if(fabsf(distance) >= onsetThresholdX_ * onsetRatioX_)
andrewm@0 378 lastExtremumTimestamp_ = timestamp;
andrewm@0 379 if(timestamp - lastExtremumTimestamp_ > onsetTimeout_) {
andrewm@0 380 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 381 std::cout << "Vibrato timeout at " << timestamp << " (last was " << lastExtremumTimestamp_ << ")" << endl;
andrewm@0 382 #endif
andrewm@0 383 changeStateSwitchingOff(timestamp);
andrewm@0 384 }
andrewm@0 385 }
andrewm@0 386
andrewm@0 387 lastProcessedIndex_++;
andrewm@0 388 }
andrewm@0 389
andrewm@0 390 // Having processed every sample individually for detection, send a pitch bend message based on the most
andrewm@0 391 // recent one (no sense in sending multiple pitch bend messages simultaneously).
andrewm@0 392 if(newSamplePresent && vibratoState_ != kStateInactive) {
andrewm@0 393 float distance = filteredDistance_.latest();
andrewm@0 394 float scale = 1.0;
andrewm@0 395
andrewm@0 396 if(vibratoState_ == kStateSwitchingOn) {
andrewm@0 397 // Switching on state gradually scales vibrato depth from 0 to
andrewm@0 398 // its final value over a specified switch-on time.
andrewm@0 399 if(rampLength_ <= 0 || (currentTimestamp - rampBeginTime_ >= rampLength_)) {
andrewm@0 400 scale = 1.0;
andrewm@0 401 changeStateActive(currentTimestamp);
andrewm@0 402 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 403 std::cout << "Vibrato switch on finished, going to Active\n";
andrewm@0 404 #endif
andrewm@0 405 }
andrewm@0 406 else {
andrewm@0 407 lastCalculatedRampValue_ = rampScaleValue_ * (float)(currentTimestamp - rampBeginTime_)/(float)rampLength_;
andrewm@0 408 scale = lastCalculatedRampValue_;
andrewm@0 409 //std::cout << "Vibrato scale " << scale << ", TS " << currentTimestamp - rampBeginTime_ << std::endl;
andrewm@0 410 }
andrewm@0 411 }
andrewm@0 412 else if(vibratoState_ == kStateSwitchingOff) {
andrewm@0 413 // Switching off state gradually scales vibrato depth from full
andrewm@0 414 // value to 0 over a specified switch-off time.
andrewm@0 415 if(rampLength_ <= 0 || (currentTimestamp - rampBeginTime_ >= rampLength_)) {
andrewm@0 416 scale = 0.0;
andrewm@0 417 changeStateInactive(currentTimestamp);
andrewm@0 418 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 419 std::cout << "Vibrato switch off finished, going to Inactive\n";
andrewm@0 420 #endif
andrewm@0 421 }
andrewm@0 422 else {
andrewm@0 423 lastCalculatedRampValue_ = rampScaleValue_ * (1.0 - (float)(currentTimestamp - rampBeginTime_)/(float)rampLength_);
andrewm@0 424 scale = lastCalculatedRampValue_;
andrewm@0 425 //std::cout << "Vibrato scale " << scale << ", TS " << currentTimestamp - rampBeginTime_ << std::endl;
andrewm@0 426 }
andrewm@0 427 }
andrewm@0 428
andrewm@0 429 // Calculate pitch bend based on current distance, with a non-linear scaling to accentuate
andrewm@0 430 // smaller motions.
andrewm@0 431 float pitchBendSemitones = vibratoRangeSemitones_ * tanhf(vibratoPrescaler_ * scale * distance);
andrewm@0 432
andrewm@0 433 sendVibratoMessage(pitchBendSemitones);
andrewm@0 434 lastPitchBendSemitones_ = pitchBendSemitones;
andrewm@0 435 }
andrewm@0 436
andrewm@0 437 // We may have arrived here without a new touch, just based on timing. Check for timeouts and process
andrewm@0 438 // any release in progress.
andrewm@0 439 if(!newSamplePresent) {
andrewm@0 440 if(vibratoState_ == kStateSwitchingOff) {
andrewm@0 441 // No new information in the distance buffer, but we do need to gradually reduce the pitch bend to zero
andrewm@0 442 if(rampLength_ <= 0 || (currentTimestamp - rampBeginTime_ >= rampLength_)) {
andrewm@0 443 sendVibratoMessage(0.0);
andrewm@0 444 lastPitchBendSemitones_ = 0;
andrewm@0 445 changeStateInactive(currentTimestamp);
andrewm@0 446 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 447 std::cout << "Vibrato switch off finished, going to Inactive\n";
andrewm@0 448 #endif
andrewm@0 449 }
andrewm@0 450 else {
andrewm@0 451 // Still in the middle of the ramp. Calculate its current value based on the last one
andrewm@0 452 // that actually had a touch data point (lastPitchBendSemitones_).
andrewm@0 453
andrewm@0 454 lastCalculatedRampValue_ = rampScaleValue_ * (1.0 - (float)(currentTimestamp - rampBeginTime_)/(float)rampLength_);
andrewm@0 455 float pitchBendSemitones = lastPitchBendSemitones_ * lastCalculatedRampValue_;
andrewm@0 456
andrewm@0 457 sendVibratoMessage(pitchBendSemitones);
andrewm@0 458 }
andrewm@0 459 }
andrewm@0 460 else if(vibratoState_ != kStateInactive) {
andrewm@0 461 // Might still be active but with no data coming in. We need to look for a timeout here too.
andrewm@0 462 if(currentTimestamp - lastExtremumTimestamp_ > onsetTimeout_) {
andrewm@0 463 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 464 std::cout << "Vibrato timeout at " << currentTimestamp << " (2; last was " << lastExtremumTimestamp_ << ")" << endl;
andrewm@0 465 #endif
andrewm@0 466 changeStateSwitchingOff(currentTimestamp);
andrewm@0 467 }
andrewm@0 468 }
andrewm@0 469 }
andrewm@0 470
andrewm@0 471 // Register for the next update by returning its timestamp
andrewm@0 472 nextScheduledTimestamp_ = currentTimestamp + updateInterval_;
andrewm@0 473 return nextScheduledTimestamp_;
andrewm@0 474 }
andrewm@0 475
andrewm@0 476 // MIDI note-on message received
andrewm@0 477 void TouchkeyVibratoMapping::midiNoteOnReceived(int channel, int velocity) {
andrewm@0 478 // MIDI note has gone on. Set the starting location to be most recent
andrewm@0 479 // location. It's possible there has been no touch data before this,
andrewm@0 480 // in which case lastX and lastY will hold missing values.
andrewm@0 481 onsetLocationX_ = lastX_;
andrewm@0 482 onsetLocationY_ = lastY_;
andrewm@0 483 if(!missing_value<float>::isMissing(onsetLocationY_)) {
andrewm@0 484 // Already have touch data. Clear the buffer here.
andrewm@0 485 // Clear buffer and start with 0 distance for this point
andrewm@0 486 clearBuffers();
andrewm@0 487
andrewm@0 488 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 489 std::cout << "MIDI on: starting at (" << onsetLocationX_ << ", " << onsetLocationY_ << ")\n";
andrewm@0 490 #endif
andrewm@0 491 }
andrewm@0 492 else {
andrewm@0 493 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 494 std::cout << "MIDI on but no touch\n";
andrewm@0 495 #endif
andrewm@0 496 }
andrewm@0 497 }
andrewm@0 498
andrewm@0 499 // MIDI note-off message received
andrewm@0 500 void TouchkeyVibratoMapping::midiNoteOffReceived(int channel) {
andrewm@0 501 if(vibratoState_ == kStateActive || vibratoState_ == kStateSwitchingOn) {
andrewm@0 502 changeStateSwitchingOff(keyboard_.schedulerCurrentTimestamp());
andrewm@0 503 }
andrewm@0 504 }
andrewm@0 505
andrewm@0 506 // Internal state-change methods, which keep the state variables in sync
andrewm@0 507 void TouchkeyVibratoMapping::changeStateSwitchingOn(timestamp_type timestamp) {
andrewm@0 508 // Go to SwitchingOn state, which brings the vibrato value gradually up to full amplitude
andrewm@0 509
andrewm@0 510 // TODO: need to start from a non-zero value if SwitchingOff
andrewm@0 511 rampScaleValue_ = 1.0;
andrewm@0 512 rampBeginTime_ = timestamp;
andrewm@0 513 rampLength_ = 0.0;
andrewm@0 514 // Interval between peak and zero crossing will be a quarter of a cycle.
andrewm@0 515 // From this, figure out how much longer we have to go to get to the next
andrewm@0 516 // peak if the rate remains the same.
andrewm@0 517 if(!missing_value<timestamp_type>::isMissing(lastZeroCrossingTimestamp_) &&
andrewm@0 518 !missing_value<timestamp_type>::isMissing(firstExtremumTimestamp_)) {
andrewm@0 519 timestamp_type estimatedPeakTimestamp = lastZeroCrossingTimestamp_ + (lastZeroCrossingTimestamp_ - firstExtremumTimestamp_);
andrewm@0 520 rampLength_ = estimatedPeakTimestamp - timestamp;
andrewm@0 521 if(rampLength_ < kMinimumOnsetTime)
andrewm@0 522 rampLength_ = kMinimumOnsetTime;
andrewm@0 523 if(rampLength_ > kMaximumOnsetTime)
andrewm@0 524 rampLength_ = kMaximumOnsetTime;
andrewm@0 525 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 526 std::cout << "Switching on with ramp length " << rampLength_ << " (peak " << firstExtremumTimestamp_ << ", zero " << lastZeroCrossingTimestamp_ << ")" << std::endl;
andrewm@0 527 #endif
andrewm@0 528 }
andrewm@0 529
andrewm@0 530 vibratoState_ = kStateSwitchingOn;
andrewm@0 531 }
andrewm@0 532
andrewm@0 533 void TouchkeyVibratoMapping::changeStateSwitchingOff(timestamp_type timestamp) {
andrewm@0 534 // Go to SwitchingOff state, which brings the vibrato value gradually down to 0
andrewm@0 535
andrewm@0 536 if(vibratoState_ == kStateSwitchingOn) {
andrewm@0 537 // Might already be in the midst of a ramp up. Start from its current value
andrewm@0 538 rampScaleValue_ = lastCalculatedRampValue_;
andrewm@0 539 }
andrewm@0 540 else
andrewm@0 541 rampScaleValue_ = 1.0;
andrewm@0 542
andrewm@0 543 rampBeginTime_ = timestamp;
andrewm@0 544 rampLength_ = lastZeroCrossingInterval_;
andrewm@0 545 if(rampLength_ < kMinimumReleaseTime)
andrewm@0 546 rampLength_ = kMinimumReleaseTime;
andrewm@0 547 if(rampLength_ > kMaximumReleaseTime)
andrewm@0 548 rampLength_ = kMaximumReleaseTime;
andrewm@0 549
andrewm@0 550 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 551 std::cout << "Switching off with ramp length " << rampLength_ << std::endl;
andrewm@0 552 #endif
andrewm@0 553
andrewm@0 554 resetDetectionState();
andrewm@0 555 vibratoState_ = kStateSwitchingOff;
andrewm@0 556 }
andrewm@0 557
andrewm@0 558 void TouchkeyVibratoMapping::changeStateActive(timestamp_type timestamp) {
andrewm@0 559 vibratoState_ = kStateActive;
andrewm@0 560 }
andrewm@0 561
andrewm@0 562 void TouchkeyVibratoMapping::changeStateInactive(timestamp_type timestamp) {
andrewm@0 563 vibratoState_ = kStateInactive;
andrewm@0 564 }
andrewm@0 565
andrewm@0 566 // Reset variables involved in detecting a vibrato gesture
andrewm@0 567 void TouchkeyVibratoMapping::resetDetectionState() {
andrewm@0 568 foundFirstExtremum_ = false;
andrewm@0 569 firstExtremumX_ = firstExtremumY_ = 0.0;
andrewm@0 570 lastExtremumTimestamp_ = firstExtremumTimestamp_ = lastZeroCrossingTimestamp_ = missing_value<timestamp_type>::missing();
andrewm@0 571 }
andrewm@0 572
andrewm@0 573 // Clear the buffers that hold distance measurements
andrewm@0 574 void TouchkeyVibratoMapping::clearBuffers() {
andrewm@0 575 rawDistance_.clear();
andrewm@0 576 filteredDistance_.clear();
andrewm@0 577 rawDistance_.insert(0.0, lastTimestamp_);
andrewm@0 578 lastProcessedIndex_ = 0;
andrewm@0 579 }
andrewm@0 580
andrewm@0 581 bool TouchkeyVibratoMapping::keyIsWhite() {
andrewm@0 582 int modNoteNumber = noteNumber_ % 12;
andrewm@0 583 if(modNoteNumber == 1 ||
andrewm@0 584 modNoteNumber == 3 ||
andrewm@0 585 modNoteNumber == 6 ||
andrewm@0 586 modNoteNumber == 8 ||
andrewm@0 587 modNoteNumber == 10)
andrewm@0 588 return false;
andrewm@0 589 return true;
andrewm@0 590 }
andrewm@0 591
andrewm@0 592 // Send the vibrato message of a given number of a semitones. Send by OSC,
andrewm@0 593 // which can be mapped to MIDI CC externally
andrewm@0 594 void TouchkeyVibratoMapping::sendVibratoMessage(float pitchBendSemitones, bool force) {
andrewm@0 595 if(force || !suspended_) {
andrewm@0 596 //if(vibratoType_ == kVibratoTypePitchBend)
andrewm@0 597 // keyboard_.sendMessage("/touchkeys/vibrato", "if", noteNumber_, pitchBendSemitones, LO_ARGS_END);
andrewm@0 598 //else if(vibratoType_ == kVibratoTypeAmplitude)
andrewm@0 599 keyboard_.sendMessage(controlName_.c_str(), "if", noteNumber_, pitchBendSemitones, LO_ARGS_END);
andrewm@0 600 // Otherwise, if unknown type, ignore.
andrewm@0 601 }
andrewm@0 602 }
andrewm@0 603