annotate Source/Mappings/Vibrato/TouchkeyVibratoMapping.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 ff5d65c69e73
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@0 256 }
andrewm@0 257 else {
andrewm@0 258 // Euclidean distance between points
andrewm@0 259 //distance = sqrtf((lastY_ - onsetLocationY_) * (lastY_ - onsetLocationY_) +
andrewm@0 260 // (lastX_ - onsetLocationX_) * (lastX_ - onsetLocationX_));
andrewm@0 261 distance = lastX_ - onsetLocationX_;
andrewm@0 262 }
andrewm@0 263
andrewm@0 264 // Insert raw distance into the buffer. Bandpass filter calculates the next
andrewm@0 265 // sample automatically. The rest of the processing takes place in the dedicated
andrewm@0 266 // thread so as not to slow down commmunication with the hardware.
andrewm@0 267 rawDistance_.insert(distance, timestamp);
andrewm@0 268
andrewm@0 269 // Move the current scheduled event up to the present time.
andrewm@0 270 // FIXME: this may be more inefficient than just doing everything in the current thread!
andrewm@0 271 #ifdef NEW_MAPPING_SCHEDULER
andrewm@0 272 keyboard_.mappingScheduler().scheduleNow(this);
andrewm@0 273 #else
andrewm@0 274 keyboard_.unscheduleEvent(this);
andrewm@0 275 keyboard_.scheduleEvent(this, mappingAction_, keyboard_.schedulerCurrentTimestamp());
andrewm@0 276 #endif
andrewm@0 277
andrewm@0 278 //std::cout << "Raw distance " << distance << " filtered " << filteredDistance_.latest() << std::endl;
andrewm@0 279 }
andrewm@0 280 }
andrewm@0 281 }
andrewm@0 282 }
andrewm@0 283 }
andrewm@0 284 }
andrewm@0 285
andrewm@0 286 // Mapping method. This actually does the real work of sending OSC data in response to the
andrewm@0 287 // latest information from the touch sensors or continuous key angle
andrewm@0 288 timestamp_type TouchkeyVibratoMapping::performMapping() {
andrewm@0 289 //ScopedLock sl(distanceAccessMutex_);
andrewm@0 290
andrewm@0 291 timestamp_type currentTimestamp = keyboard_.schedulerCurrentTimestamp();
andrewm@0 292 bool newSamplePresent = false;
andrewm@0 293
andrewm@0 294 // Go through the filtered distance samples that are remaining to process.
andrewm@0 295 if(lastProcessedIndex_ < filteredDistance_.beginIndex() + 1) {
andrewm@0 296 // Fell off the beginning of the position buffer. Skip to the samples we have
andrewm@0 297 // (shouldn't happen except in cases of exceptional system load, and not too
andrewm@0 298 // consequential if it does happen).
andrewm@0 299 lastProcessedIndex_ = filteredDistance_.beginIndex() + 1;
andrewm@0 300 }
andrewm@0 301
andrewm@0 302 while(lastProcessedIndex_ < filteredDistance_.endIndex()) {
andrewm@0 303 float distance = filteredDistance_[lastProcessedIndex_];
andrewm@0 304 timestamp_type timestamp = filteredDistance_.timestampAt(lastProcessedIndex_);
andrewm@0 305 newSamplePresent = true;
andrewm@0 306
andrewm@0 307 if((distance > 0 && !lastSampleWasPositive_) ||
andrewm@0 308 (distance < 0 && lastSampleWasPositive_)) {
andrewm@0 309 // Found a zero crossing: save it if we're active or have at least found the
andrewm@0 310 // first extremum
andrewm@0 311 if(!missing_value<timestamp_type>::isMissing(lastZeroCrossingTimestamp_) &&
andrewm@0 312 (timestamp - lastZeroCrossingTimestamp_ > kZeroCrossingMinimumTime)) {
andrewm@0 313 if(vibratoState_ == kStateActive || vibratoState_ == kStateSwitchingOn ||
andrewm@0 314 foundFirstExtremum_) {
andrewm@0 315 lastZeroCrossingInterval_ = timestamp - lastZeroCrossingTimestamp_;
andrewm@0 316 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 317 std::cout << "Zero crossing interval " << lastZeroCrossingInterval_ << std::endl;
andrewm@0 318 #endif
andrewm@0 319 }
andrewm@0 320 }
andrewm@0 321 lastZeroCrossingTimestamp_ = timestamp;
andrewm@0 322 }
andrewm@0 323 lastSampleWasPositive_ = (distance > 0);
andrewm@0 324
andrewm@0 325 // If not currently engaged, check for the pattern of side-to-side motion that
andrewm@0 326 // begins a vibrato gesture.
andrewm@0 327 if(vibratoState_ == kStateInactive || vibratoState_ == kStateSwitchingOff) {
andrewm@0 328 if(foundFirstExtremum_) {
andrewm@0 329 // Already found first extremum. Look for second extremum in the opposite
andrewm@0 330 // direction of the given ratio from the original.
andrewm@0 331 if((firstExtremumX_ > 0 && distance < 0) ||
andrewm@0 332 (firstExtremumX_ < 0 && distance > 0)) {
andrewm@0 333 if(fabsf(distance) >= fabsf(firstExtremumX_) * onsetRatioX_) {
andrewm@0 334 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 335 std::cout << "Found second extremum at " << distance << ", TS " << timestamp << std::endl;
andrewm@0 336 #endif
andrewm@0 337 changeStateSwitchingOn(timestamp);
andrewm@0 338 }
andrewm@0 339 }
andrewm@0 340 else if(timestamp - lastExtremumTimestamp_ > onsetTimeout_) {
andrewm@0 341 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 342 std::cout << "Onset timeout at " << timestamp << endl;
andrewm@0 343 #endif
andrewm@0 344 resetDetectionState();
andrewm@0 345 }
andrewm@0 346 }
andrewm@0 347 else {
andrewm@0 348 if(fabsf(distance) >= onsetThresholdX_) {
andrewm@0 349 // TODO: differentiate X/Y here
andrewm@0 350 if(missing_value<float>::isMissing(firstExtremumX_) ||
andrewm@0 351 fabsf(distance) > fabsf(firstExtremumX_)) {
andrewm@0 352 firstExtremumX_ = distance;
andrewm@0 353 lastExtremumTimestamp_ = timestamp;
andrewm@0 354 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 355 std::cout << "First extremum candidate at " << firstExtremumX_ << ", TS " << lastExtremumTimestamp_ << std::endl;
andrewm@0 356 #endif
andrewm@0 357 }
andrewm@0 358 }
andrewm@0 359 else if(!missing_value<float>::isMissing(firstExtremumX_) &&
andrewm@0 360 fabsf(firstExtremumX_) > onsetThresholdX_) {
andrewm@0 361 // We must have found the first extremum since its maximum value is
andrewm@0 362 // above the threshold, and we must have moved away from it since we are
andrewm@0 363 // now below the threshold. Next step will be to look for extremum in
andrewm@0 364 // opposite direction. Save the timestamp of this location in case
andrewm@0 365 // another extremum is found later.
andrewm@0 366 firstExtremumTimestamp_ = lastExtremumTimestamp_;
andrewm@0 367 foundFirstExtremum_ = true;
andrewm@0 368 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 369 std::cout << "Found first extremum at " << firstExtremumX_ << ", TS " << lastExtremumTimestamp_ << std::endl;
andrewm@0 370 #endif
andrewm@0 371 }
andrewm@0 372 }
andrewm@0 373 }
andrewm@0 374 else {
andrewm@0 375 // Currently engaged. Look for timeout, defined as the finger staying below the lower (ratio-adjusted) threshold.
andrewm@0 376 if(fabsf(distance) >= onsetThresholdX_ * onsetRatioX_)
andrewm@0 377 lastExtremumTimestamp_ = timestamp;
andrewm@0 378 if(timestamp - lastExtremumTimestamp_ > onsetTimeout_) {
andrewm@0 379 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 380 std::cout << "Vibrato timeout at " << timestamp << " (last was " << lastExtremumTimestamp_ << ")" << endl;
andrewm@0 381 #endif
andrewm@0 382 changeStateSwitchingOff(timestamp);
andrewm@0 383 }
andrewm@0 384 }
andrewm@0 385
andrewm@0 386 lastProcessedIndex_++;
andrewm@0 387 }
andrewm@0 388
andrewm@0 389 // Having processed every sample individually for detection, send a pitch bend message based on the most
andrewm@0 390 // recent one (no sense in sending multiple pitch bend messages simultaneously).
andrewm@0 391 if(newSamplePresent && vibratoState_ != kStateInactive) {
andrewm@0 392 float distance = filteredDistance_.latest();
andrewm@0 393 float scale = 1.0;
andrewm@0 394
andrewm@0 395 if(vibratoState_ == kStateSwitchingOn) {
andrewm@0 396 // Switching on state gradually scales vibrato depth from 0 to
andrewm@0 397 // its final value over a specified switch-on time.
andrewm@0 398 if(rampLength_ <= 0 || (currentTimestamp - rampBeginTime_ >= rampLength_)) {
andrewm@0 399 scale = 1.0;
andrewm@0 400 changeStateActive(currentTimestamp);
andrewm@0 401 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 402 std::cout << "Vibrato switch on finished, going to Active\n";
andrewm@0 403 #endif
andrewm@0 404 }
andrewm@0 405 else {
andrewm@0 406 lastCalculatedRampValue_ = rampScaleValue_ * (float)(currentTimestamp - rampBeginTime_)/(float)rampLength_;
andrewm@0 407 scale = lastCalculatedRampValue_;
andrewm@0 408 //std::cout << "Vibrato scale " << scale << ", TS " << currentTimestamp - rampBeginTime_ << std::endl;
andrewm@0 409 }
andrewm@0 410 }
andrewm@0 411 else if(vibratoState_ == kStateSwitchingOff) {
andrewm@0 412 // Switching off state gradually scales vibrato depth from full
andrewm@0 413 // value to 0 over a specified switch-off time.
andrewm@0 414 if(rampLength_ <= 0 || (currentTimestamp - rampBeginTime_ >= rampLength_)) {
andrewm@0 415 scale = 0.0;
andrewm@0 416 changeStateInactive(currentTimestamp);
andrewm@0 417 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 418 std::cout << "Vibrato switch off finished, going to Inactive\n";
andrewm@0 419 #endif
andrewm@0 420 }
andrewm@0 421 else {
andrewm@0 422 lastCalculatedRampValue_ = rampScaleValue_ * (1.0 - (float)(currentTimestamp - rampBeginTime_)/(float)rampLength_);
andrewm@0 423 scale = lastCalculatedRampValue_;
andrewm@0 424 //std::cout << "Vibrato scale " << scale << ", TS " << currentTimestamp - rampBeginTime_ << std::endl;
andrewm@0 425 }
andrewm@0 426 }
andrewm@0 427
andrewm@0 428 // Calculate pitch bend based on current distance, with a non-linear scaling to accentuate
andrewm@0 429 // smaller motions.
andrewm@0 430 float pitchBendSemitones = vibratoRangeSemitones_ * tanhf(vibratoPrescaler_ * scale * distance);
andrewm@0 431
andrewm@0 432 sendVibratoMessage(pitchBendSemitones);
andrewm@0 433 lastPitchBendSemitones_ = pitchBendSemitones;
andrewm@0 434 }
andrewm@0 435
andrewm@0 436 // We may have arrived here without a new touch, just based on timing. Check for timeouts and process
andrewm@0 437 // any release in progress.
andrewm@0 438 if(!newSamplePresent) {
andrewm@0 439 if(vibratoState_ == kStateSwitchingOff) {
andrewm@0 440 // No new information in the distance buffer, but we do need to gradually reduce the pitch bend to zero
andrewm@0 441 if(rampLength_ <= 0 || (currentTimestamp - rampBeginTime_ >= rampLength_)) {
andrewm@0 442 sendVibratoMessage(0.0);
andrewm@0 443 lastPitchBendSemitones_ = 0;
andrewm@0 444 changeStateInactive(currentTimestamp);
andrewm@0 445 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 446 std::cout << "Vibrato switch off finished, going to Inactive\n";
andrewm@0 447 #endif
andrewm@0 448 }
andrewm@0 449 else {
andrewm@0 450 // Still in the middle of the ramp. Calculate its current value based on the last one
andrewm@0 451 // that actually had a touch data point (lastPitchBendSemitones_).
andrewm@0 452
andrewm@0 453 lastCalculatedRampValue_ = rampScaleValue_ * (1.0 - (float)(currentTimestamp - rampBeginTime_)/(float)rampLength_);
andrewm@0 454 float pitchBendSemitones = lastPitchBendSemitones_ * lastCalculatedRampValue_;
andrewm@0 455
andrewm@0 456 sendVibratoMessage(pitchBendSemitones);
andrewm@0 457 }
andrewm@0 458 }
andrewm@0 459 else if(vibratoState_ != kStateInactive) {
andrewm@0 460 // Might still be active but with no data coming in. We need to look for a timeout here too.
andrewm@0 461 if(currentTimestamp - lastExtremumTimestamp_ > onsetTimeout_) {
andrewm@0 462 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 463 std::cout << "Vibrato timeout at " << currentTimestamp << " (2; last was " << lastExtremumTimestamp_ << ")" << endl;
andrewm@0 464 #endif
andrewm@0 465 changeStateSwitchingOff(currentTimestamp);
andrewm@0 466 }
andrewm@0 467 }
andrewm@0 468 }
andrewm@0 469
andrewm@0 470 // Register for the next update by returning its timestamp
andrewm@0 471 nextScheduledTimestamp_ = currentTimestamp + updateInterval_;
andrewm@0 472 return nextScheduledTimestamp_;
andrewm@0 473 }
andrewm@0 474
andrewm@0 475 // MIDI note-on message received
andrewm@0 476 void TouchkeyVibratoMapping::midiNoteOnReceived(int channel, int velocity) {
andrewm@0 477 // MIDI note has gone on. Set the starting location to be most recent
andrewm@0 478 // location. It's possible there has been no touch data before this,
andrewm@0 479 // in which case lastX and lastY will hold missing values.
andrewm@0 480 onsetLocationX_ = lastX_;
andrewm@0 481 onsetLocationY_ = lastY_;
andrewm@0 482 if(!missing_value<float>::isMissing(onsetLocationY_)) {
andrewm@0 483 // Already have touch data. Clear the buffer here.
andrewm@0 484 // Clear buffer and start with 0 distance for this point
andrewm@0 485 clearBuffers();
andrewm@0 486
andrewm@0 487 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 488 std::cout << "MIDI on: starting at (" << onsetLocationX_ << ", " << onsetLocationY_ << ")\n";
andrewm@0 489 #endif
andrewm@0 490 }
andrewm@0 491 else {
andrewm@0 492 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 493 std::cout << "MIDI on but no touch\n";
andrewm@0 494 #endif
andrewm@0 495 }
andrewm@0 496 }
andrewm@0 497
andrewm@0 498 // MIDI note-off message received
andrewm@0 499 void TouchkeyVibratoMapping::midiNoteOffReceived(int channel) {
andrewm@0 500 if(vibratoState_ == kStateActive || vibratoState_ == kStateSwitchingOn) {
andrewm@0 501 changeStateSwitchingOff(keyboard_.schedulerCurrentTimestamp());
andrewm@0 502 }
andrewm@0 503 }
andrewm@0 504
andrewm@0 505 // Internal state-change methods, which keep the state variables in sync
andrewm@0 506 void TouchkeyVibratoMapping::changeStateSwitchingOn(timestamp_type timestamp) {
andrewm@0 507 // Go to SwitchingOn state, which brings the vibrato value gradually up to full amplitude
andrewm@0 508
andrewm@0 509 // TODO: need to start from a non-zero value if SwitchingOff
andrewm@0 510 rampScaleValue_ = 1.0;
andrewm@0 511 rampBeginTime_ = timestamp;
andrewm@0 512 rampLength_ = 0.0;
andrewm@0 513 // Interval between peak and zero crossing will be a quarter of a cycle.
andrewm@0 514 // From this, figure out how much longer we have to go to get to the next
andrewm@0 515 // peak if the rate remains the same.
andrewm@0 516 if(!missing_value<timestamp_type>::isMissing(lastZeroCrossingTimestamp_) &&
andrewm@0 517 !missing_value<timestamp_type>::isMissing(firstExtremumTimestamp_)) {
andrewm@0 518 timestamp_type estimatedPeakTimestamp = lastZeroCrossingTimestamp_ + (lastZeroCrossingTimestamp_ - firstExtremumTimestamp_);
andrewm@0 519 rampLength_ = estimatedPeakTimestamp - timestamp;
andrewm@0 520 if(rampLength_ < kMinimumOnsetTime)
andrewm@0 521 rampLength_ = kMinimumOnsetTime;
andrewm@0 522 if(rampLength_ > kMaximumOnsetTime)
andrewm@0 523 rampLength_ = kMaximumOnsetTime;
andrewm@0 524 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 525 std::cout << "Switching on with ramp length " << rampLength_ << " (peak " << firstExtremumTimestamp_ << ", zero " << lastZeroCrossingTimestamp_ << ")" << std::endl;
andrewm@0 526 #endif
andrewm@0 527 }
andrewm@0 528
andrewm@0 529 vibratoState_ = kStateSwitchingOn;
andrewm@0 530 }
andrewm@0 531
andrewm@0 532 void TouchkeyVibratoMapping::changeStateSwitchingOff(timestamp_type timestamp) {
andrewm@0 533 // Go to SwitchingOff state, which brings the vibrato value gradually down to 0
andrewm@0 534
andrewm@0 535 if(vibratoState_ == kStateSwitchingOn) {
andrewm@0 536 // Might already be in the midst of a ramp up. Start from its current value
andrewm@0 537 rampScaleValue_ = lastCalculatedRampValue_;
andrewm@0 538 }
andrewm@0 539 else
andrewm@0 540 rampScaleValue_ = 1.0;
andrewm@0 541
andrewm@0 542 rampBeginTime_ = timestamp;
andrewm@0 543 rampLength_ = lastZeroCrossingInterval_;
andrewm@0 544 if(rampLength_ < kMinimumReleaseTime)
andrewm@0 545 rampLength_ = kMinimumReleaseTime;
andrewm@0 546 if(rampLength_ > kMaximumReleaseTime)
andrewm@0 547 rampLength_ = kMaximumReleaseTime;
andrewm@0 548
andrewm@0 549 #ifdef DEBUG_TOUCHKEY_VIBRATO_MAPPING
andrewm@0 550 std::cout << "Switching off with ramp length " << rampLength_ << std::endl;
andrewm@0 551 #endif
andrewm@0 552
andrewm@0 553 resetDetectionState();
andrewm@0 554 vibratoState_ = kStateSwitchingOff;
andrewm@0 555 }
andrewm@0 556
andrewm@0 557 void TouchkeyVibratoMapping::changeStateActive(timestamp_type timestamp) {
andrewm@0 558 vibratoState_ = kStateActive;
andrewm@0 559 }
andrewm@0 560
andrewm@0 561 void TouchkeyVibratoMapping::changeStateInactive(timestamp_type timestamp) {
andrewm@0 562 vibratoState_ = kStateInactive;
andrewm@0 563 }
andrewm@0 564
andrewm@0 565 // Reset variables involved in detecting a vibrato gesture
andrewm@0 566 void TouchkeyVibratoMapping::resetDetectionState() {
andrewm@0 567 foundFirstExtremum_ = false;
andrewm@0 568 firstExtremumX_ = firstExtremumY_ = 0.0;
andrewm@0 569 lastExtremumTimestamp_ = firstExtremumTimestamp_ = lastZeroCrossingTimestamp_ = missing_value<timestamp_type>::missing();
andrewm@0 570 }
andrewm@0 571
andrewm@0 572 // Clear the buffers that hold distance measurements
andrewm@0 573 void TouchkeyVibratoMapping::clearBuffers() {
andrewm@0 574 rawDistance_.clear();
andrewm@0 575 filteredDistance_.clear();
andrewm@0 576 rawDistance_.insert(0.0, lastTimestamp_);
andrewm@0 577 lastProcessedIndex_ = 0;
andrewm@0 578 }
andrewm@0 579
andrewm@0 580 bool TouchkeyVibratoMapping::keyIsWhite() {
andrewm@0 581 int modNoteNumber = noteNumber_ % 12;
andrewm@0 582 if(modNoteNumber == 1 ||
andrewm@0 583 modNoteNumber == 3 ||
andrewm@0 584 modNoteNumber == 6 ||
andrewm@0 585 modNoteNumber == 8 ||
andrewm@0 586 modNoteNumber == 10)
andrewm@0 587 return false;
andrewm@0 588 return true;
andrewm@0 589 }
andrewm@0 590
andrewm@0 591 // Send the vibrato message of a given number of a semitones. Send by OSC,
andrewm@0 592 // which can be mapped to MIDI CC externally
andrewm@0 593 void TouchkeyVibratoMapping::sendVibratoMessage(float pitchBendSemitones, bool force) {
andrewm@0 594 if(force || !suspended_) {
andrewm@0 595 //if(vibratoType_ == kVibratoTypePitchBend)
andrewm@0 596 // keyboard_.sendMessage("/touchkeys/vibrato", "if", noteNumber_, pitchBendSemitones, LO_ARGS_END);
andrewm@0 597 //else if(vibratoType_ == kVibratoTypeAmplitude)
andrewm@0 598 keyboard_.sendMessage(controlName_.c_str(), "if", noteNumber_, pitchBendSemitones, LO_ARGS_END);
andrewm@0 599 // Otherwise, if unknown type, ignore.
andrewm@0 600 }
andrewm@0 601 }
andrewm@0 602