annotate Source/Mappings/MRPMapping.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 MRPMapping.cpp: mapping class for magnetic resonator piano using continuous
andrewm@0 21 key position.
andrewm@0 22 */
andrewm@0 23
andrewm@0 24 #include "MRPMapping.h"
andrewm@0 25 #include <vector>
andrewm@0 26
andrewm@0 27 // Class constants
andrewm@0 28 // Useful constants for mapping MRP messages
andrewm@0 29 const int MRPMapping::kMIDINoteOnMessage = 0x90;
andrewm@0 30 const int MRPMapping::kDefaultMIDIChannel = 15;
andrewm@0 31 const float MRPMapping::kDefaultAftertouchScaler = 100.0;
andrewm@0 32
andrewm@0 33 // Parameters for vibrato detection and mapping
andrewm@0 34 const key_velocity MRPMapping::kVibratoVelocityThreshold = scale_key_velocity(2.0);
andrewm@0 35 const timestamp_diff_type MRPMapping::kVibratoMinimumPeakSpacing = microseconds_to_timestamp(60000);
andrewm@0 36 const timestamp_diff_type MRPMapping::kVibratoTimeout = microseconds_to_timestamp(500000);
andrewm@0 37 const int MRPMapping::kVibratoMinimumOscillations = 4;
andrewm@0 38 const float MRPMapping::kVibratoRateScaler = 0.005;
andrewm@0 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 MRPMapping::MRPMapping(PianoKeyboard &keyboard, MappingFactory *factory, int noteNumber, Node<KeyTouchFrame>* touchBuffer,
andrewm@0 46 Node<key_position>* positionBuffer, KeyPositionTracker* positionTracker)
andrewm@0 47 : Mapping(keyboard, factory, noteNumber, touchBuffer, positionBuffer, positionTracker),
andrewm@0 48 noteIsOn_(false), lastIntensity_(missing_value<float>::missing()),
andrewm@0 49 lastBrightness_(missing_value<float>::missing()), lastPitch_(missing_value<float>::missing()),
andrewm@0 50 lastHarmonic_(missing_value<float>::missing()),
andrewm@0 51 shouldLookForPitchBends_(true), rawVelocity_(kMRPMappingVelocityBufferLength),
andrewm@0 52 filteredVelocity_(kMRPMappingVelocityBufferLength, rawVelocity_), lastCalculatedVelocityIndex_(0),
andrewm@0 53 vibratoActive_(false), vibratoVelocityPeakCount_(0), vibratoLastPeakTimestamp_(missing_value<timestamp_type>::missing())
andrewm@0 54 {
andrewm@0 55 setAftertouchSensitivity(1.0);
andrewm@0 56
andrewm@0 57 // Initialize the filter coefficients for filtered key velocity (used for vibrato detection)
andrewm@0 58 std::vector<double> bCoeffs, aCoeffs;
andrewm@0 59 designSecondOrderLowpass(bCoeffs, aCoeffs, 15.0, 0.707, 1000.0);
andrewm@0 60 std::vector<float> bCf(bCoeffs.begin(), bCoeffs.end()), aCf(aCoeffs.begin(), aCoeffs.end());
andrewm@0 61 filteredVelocity_.setCoefficients(bCf, aCf);
andrewm@0 62 }
andrewm@0 63
andrewm@0 64 // Copy constructor
andrewm@0 65 /*MRPMapping::MRPMapping(MRPMapping const& obj)
andrewm@0 66 : Mapping(obj), lastIntensity_(obj.lastIntensity_), lastBrightness_(obj.lastBrightness_),
andrewm@0 67 aftertouchScaler_(obj.aftertouchScaler_), noteIsOn_(obj.noteIsOn_), lastPitch_(obj.lastPitch_),
andrewm@0 68 lastHarmonic_(obj.lastHarmonic_),
andrewm@0 69 shouldLookForPitchBends_(obj.shouldLookForPitchBends_), activePitchBends_(obj.activePitchBends_),
andrewm@0 70 rawVelocity_(obj.rawVelocity_), filteredVelocity_(obj.filteredVelocity_),
andrewm@0 71 lastCalculatedVelocityIndex_(obj.lastCalculatedVelocityIndex_), vibratoActive_(obj.vibratoActive_),
andrewm@0 72 vibratoVelocityPeakCount_(obj.vibratoVelocityPeakCount_), vibratoLastPeakTimestamp_(obj.vibratoLastPeakTimestamp_) {
andrewm@0 73
andrewm@0 74 }*/
andrewm@0 75
andrewm@0 76 MRPMapping::~MRPMapping() {
andrewm@0 77 //std::cerr << "~MRPMapping(): " << this << std::endl;
andrewm@0 78
andrewm@0 79 try {
andrewm@0 80 disengage();
andrewm@0 81 }
andrewm@0 82 catch(...) {
andrewm@0 83 std::cerr << "~MRPMapping(): exception during disengage()\n";
andrewm@0 84 }
andrewm@0 85
andrewm@0 86 //std::cerr << "~MRPMapping(): done\n";
andrewm@0 87 }
andrewm@0 88
andrewm@0 89 // Turn off mapping of data. Remove our callback from the scheduler
andrewm@0 90 void MRPMapping::disengage() {
andrewm@0 91 Mapping::disengage();
andrewm@0 92 if(noteIsOn_) {
andrewm@0 93 int newNoteNumber = noteNumber_;
andrewm@0 94 //int newNoteNumber = ((noteNumber_ - 21) * 25)%88 + 21;
andrewm@0 95 keyboard_.sendMessage("/mrp/midi",
andrewm@0 96 "iii", (int)(kMIDINoteOnMessage + kDefaultMIDIChannel), (int)newNoteNumber, (int)0, LO_ARGS_END);
andrewm@0 97 // if(!touchBuffer_->empty())
andrewm@0 98 // keyboard_.testLog_ << touchBuffer_->latestTimestamp() << " /mrp/midi iii " << (kMIDINoteOnMessage + kDefaultMIDIChannel) << " " << newNoteNumber << " " << 0 << endl;
andrewm@0 99
andrewm@0 100 // Reset qualities
andrewm@0 101 lastPitch_ = lastHarmonic_ = lastBrightness_ = lastIntensity_ = missing_value<float>::missing();
andrewm@0 102 }
andrewm@0 103 noteIsOn_ = false;
andrewm@0 104 shouldLookForPitchBends_ = true;
andrewm@0 105 }
andrewm@0 106
andrewm@0 107 // Reset state back to defaults
andrewm@0 108 void MRPMapping::reset() {
andrewm@0 109 Mapping::reset();
andrewm@0 110 noteIsOn_ = false;
andrewm@0 111 shouldLookForPitchBends_ = true;
andrewm@0 112 }
andrewm@0 113
andrewm@0 114 // Set the aftertouch sensitivity on continuous key position
andrewm@0 115 // 0 means no aftertouch, 1 means default sensitivity, upward
andrewm@0 116 // from there
andrewm@0 117 void MRPMapping::setAftertouchSensitivity(float sensitivity) {
andrewm@0 118 if(sensitivity <= 0)
andrewm@0 119 aftertouchScaler_ = 0;
andrewm@0 120 else
andrewm@0 121 aftertouchScaler_ = kDefaultAftertouchScaler * sensitivity;
andrewm@0 122 }
andrewm@0 123
andrewm@0 124 // This is called by another MRPMapping when it finds a pitch bend starting.
andrewm@0 125 // Add the sending note to our list of bends, with the sending note marked
andrewm@0 126 // as controlling the bend
andrewm@0 127 void MRPMapping::enablePitchBend(int toNote, Node<key_position>* toPositionBuffer,
andrewm@0 128 KeyPositionTracker *toPositionTracker) {
andrewm@0 129 if(toPositionBuffer == 0 || toPositionTracker == 0)
andrewm@0 130 return;
andrewm@0 131
andrewm@0 132 std::cout << "enablePitchBend(): this note = " << noteNumber_ << " note = " << toNote << " posBuf = " << toPositionBuffer << " posTrack = " << toPositionTracker << "\n";
andrewm@0 133 PitchBend newBend = {toNote, true, false, toPositionBuffer, toPositionTracker};
andrewm@0 134 activePitchBends_.push_back(newBend);
andrewm@0 135 }
andrewm@0 136
andrewm@0 137 // Trigger method. This receives updates from the TouchKey data or from state changes in
andrewm@0 138 // the continuous key position (KeyPositionTracker). It will potentially change the scheduled
andrewm@0 139 // behavior of future mapping calls, but the actual OSC messages should be transmitted in a different
andrewm@0 140 // thread.
andrewm@0 141 void MRPMapping::triggerReceived(TriggerSource* who, timestamp_type timestamp) {
andrewm@0 142 if(who == 0)
andrewm@0 143 return;
andrewm@0 144 if(who == positionTracker_) {
andrewm@0 145 // The state of the key (based on continuous position) just changed.
andrewm@0 146 // Might want to alter our mapping strategy.
andrewm@0 147 }
andrewm@0 148 else if(who == touchBuffer_) {
andrewm@0 149 // TouchKey data is available
andrewm@0 150 }
andrewm@0 151 }
andrewm@0 152
andrewm@0 153 // Mapping method. This actually does the real work of sending OSC data in response to the
andrewm@0 154 // latest information from the touch sensors or continuous key angle
andrewm@0 155 timestamp_type MRPMapping::performMapping() {
andrewm@0 156 if(!engaged_)
andrewm@0 157 return 0;
andrewm@0 158
andrewm@0 159 timestamp_type currentTimestamp = keyboard_.schedulerCurrentTimestamp();
andrewm@0 160 float intensity = 0;
andrewm@0 161 float brightness = 0;
andrewm@0 162 float pitch = 0;
andrewm@0 163 float harmonic = 0;
andrewm@0 164
andrewm@0 165 // Calculate the output features as a function of input sensor data
andrewm@0 166 if(positionBuffer_ == 0) {
andrewm@0 167 // No buffer -> all 0
andrewm@0 168 }
andrewm@0 169 else if(positionBuffer_->empty()) {
andrewm@0 170 // No samples -> all 0
andrewm@0 171 }
andrewm@0 172 else {
andrewm@0 173 // TODO: IIR filter on the position data before mapping it
andrewm@0 174 key_position latestPosition = positionBuffer_->latest();
andrewm@0 175 int trackerState = kPositionTrackerStateUnknown;
andrewm@0 176 if(positionTracker_ != 0)
andrewm@0 177 trackerState = positionTracker_->currentState();
andrewm@0 178
andrewm@0 179 // Get the latest velocity measurements
andrewm@0 180 key_velocity latestVelocity = updateVelocityMeasurements();
andrewm@0 181
andrewm@0 182 // Every time we enter a state of PartialPress, check whether this key
andrewm@0 183 // is part of a multi-key pitch bend gesture with another key that's already
andrewm@0 184 // down. Only do this once, though, since keys that go down after we enter
andrewm@0 185 // PartialPress state are not part of such a gesture.
andrewm@0 186 if(shouldLookForPitchBends_) {
andrewm@0 187 if(trackerState == kPositionTrackerStatePartialPressAwaitingMax ||
andrewm@0 188 trackerState == kPositionTrackerStatePartialPressFoundMax) {
andrewm@0 189 // Look for a pitch bend gesture by searching for neighboring
andrewm@0 190 // keys which are in the Down state and reached that state before
andrewm@0 191 // this one reached PartialPress state.
andrewm@0 192 for(int neighborNote = noteNumber_ - 2; neighborNote < noteNumber_; neighborNote++) {
andrewm@0 193 // If one of the lower keys is in the Down state, then this note should bend it up
andrewm@0 194 MRPMapping *neighborMapper = dynamic_cast<MRPMapping*>(keyboard_.mapping(neighborNote));
andrewm@0 195 if(neighborMapper == 0)
andrewm@0 196 continue;
andrewm@0 197 if(neighborMapper->positionTracker_ != 0) {
andrewm@0 198 int neighborState = neighborMapper->positionTracker_->currentState();
andrewm@0 199 if(neighborState == kPositionTrackerStateDown) {
andrewm@0 200 // Here we've found a neighboring note in the Down state. But did it precede our transition?
andrewm@0 201 timestamp_type timeOfDownTransition = neighborMapper->positionTracker_->latestTimestamp();
andrewm@0 202 timestamp_type timeOfOurPartialActivation = findTimestampOfPartialPress();
andrewm@0 203
andrewm@0 204 cout << "Found key " << neighborNote << " in Down state\n";
andrewm@0 205
andrewm@0 206 if(!missing_value<timestamp_type>::isMissing(timeOfOurPartialActivation)) {
andrewm@0 207 if(timeOfOurPartialActivation > timeOfDownTransition) {
andrewm@0 208 // The neighbor note went down before us; pitch bend should engage
andrewm@0 209 cout << "Found pitch bend: " << noteNumber_ << " to " << neighborNote << endl;
andrewm@0 210
andrewm@0 211 // Insert the details for the neighboring note into our buffer. The bend
andrewm@0 212 // is controlled by our own key, and the target is the neighbor note.
andrewm@0 213 PitchBend newBend = {neighborNote, false, false, neighborMapper->positionBuffer_,
andrewm@0 214 neighborMapper->positionTracker_};
andrewm@0 215 activePitchBends_.push_back(newBend);
andrewm@0 216
andrewm@0 217 // Tell the other note to bend its pitch based on our position
andrewm@0 218 neighborMapper->enablePitchBend(noteNumber_, positionBuffer_, positionTracker_);
andrewm@0 219 }
andrewm@0 220 }
andrewm@0 221 }
andrewm@0 222 }
andrewm@0 223 }
andrewm@0 224 for(int neighborNote = noteNumber_ + 1; neighborNote < noteNumber_ + 3; neighborNote++) {
andrewm@0 225 // If one of the upper keys is in the Down state, then this note should bend it down
andrewm@0 226 MRPMapping *neighborMapper = dynamic_cast<MRPMapping*>(keyboard_.mapping(neighborNote));
andrewm@0 227 if(neighborMapper == 0)
andrewm@0 228 continue;
andrewm@0 229 if(neighborMapper->positionTracker_ != 0) {
andrewm@0 230 int neighborState = neighborMapper->positionTracker_->currentState();
andrewm@0 231 if(neighborState == kPositionTrackerStateDown) {
andrewm@0 232 // Here we've found a neighboring note in the Down state. But did it precede our transition?
andrewm@0 233 timestamp_type timeOfDownTransition = neighborMapper->positionTracker_->latestTimestamp();
andrewm@0 234 timestamp_type timeOfOurPartialActivation = findTimestampOfPartialPress();
andrewm@0 235
andrewm@0 236 cout << "Found key " << neighborNote << " in Down state\n";
andrewm@0 237
andrewm@0 238 if(!missing_value<timestamp_type>::isMissing(timeOfOurPartialActivation)) {
andrewm@0 239 if(timeOfOurPartialActivation > timeOfDownTransition) {
andrewm@0 240 // The neighbor note went down before us; pitch bend should engage
andrewm@0 241 cout << "Found pitch bend: " << noteNumber_ << " to " << neighborNote << endl;
andrewm@0 242
andrewm@0 243 // Insert the details for the neighboring note into our buffer. The bend
andrewm@0 244 // is controlled by our own key, and the target is the neighbor note.
andrewm@0 245 PitchBend newBend = {neighborNote, false, false, neighborMapper->positionBuffer_,
andrewm@0 246 neighborMapper->positionTracker_};
andrewm@0 247 activePitchBends_.push_back(newBend);
andrewm@0 248
andrewm@0 249 // Tell the other note to bend its pitch based on our position
andrewm@0 250 neighborMapper->enablePitchBend(noteNumber_, positionBuffer_, positionTracker_);
andrewm@0 251 }
andrewm@0 252 }
andrewm@0 253 }
andrewm@0 254 }
andrewm@0 255 }
andrewm@0 256
andrewm@0 257 shouldLookForPitchBends_ = false;
andrewm@0 258 }
andrewm@0 259 }
andrewm@0 260
andrewm@0 261 if(trackerState == kPositionTrackerStatePartialPressAwaitingMax ||
andrewm@0 262 trackerState == kPositionTrackerStatePartialPressFoundMax) {
andrewm@0 263 // Look for active vibrato gestures which are defined as oscillating
andrewm@0 264 // motion in the key velocity. They could conceivably occur at a variety
andrewm@0 265 // of raw key positions, as long as the key is not yet down
andrewm@0 266
andrewm@0 267 if(missing_value<timestamp_type>::isMissing(vibratoLastPeakTimestamp_))
andrewm@0 268 vibratoLastPeakTimestamp_ = currentTimestamp;
andrewm@0 269
andrewm@0 270 if(vibratoVelocityPeakCount_ % 2 == 0) {
andrewm@0 271 if(latestVelocity > kVibratoVelocityThreshold && currentTimestamp - vibratoLastPeakTimestamp_ > kVibratoMinimumPeakSpacing) {
andrewm@0 272 std::cout << "Vibrato count = " << vibratoVelocityPeakCount_ << std::endl;
andrewm@0 273 vibratoVelocityPeakCount_++;
andrewm@0 274 vibratoLastPeakTimestamp_ = currentTimestamp;
andrewm@0 275 }
andrewm@0 276 }
andrewm@0 277 else {
andrewm@0 278 if(latestVelocity < -kVibratoVelocityThreshold && currentTimestamp - vibratoLastPeakTimestamp_ > kVibratoMinimumPeakSpacing) {
andrewm@0 279 std::cout << "Vibrato count = " << vibratoVelocityPeakCount_ << std::endl;
andrewm@0 280 vibratoVelocityPeakCount_++;
andrewm@0 281 vibratoLastPeakTimestamp_ = currentTimestamp;
andrewm@0 282 }
andrewm@0 283 }
andrewm@0 284
andrewm@0 285 if(vibratoVelocityPeakCount_ >= kVibratoMinimumOscillations) {
andrewm@0 286 vibratoActive_ = true;
andrewm@0 287 }
andrewm@0 288
andrewm@0 289
andrewm@0 290 if(vibratoActive_) {
andrewm@0 291 // Update the harmonic parameter, which increases linearly with the absolute
andrewm@0 292 // value of velocity. The value will accumulate over the course of a vibrato
andrewm@0 293 // gesture and retain its value when the vibrato finishes. It reverts to minimum
andrewm@0 294 // when the note finishes.
andrewm@0 295 if(missing_value<float>::isMissing(lastHarmonic_))
andrewm@0 296 lastHarmonic_ = 0.0;
andrewm@0 297 harmonic = lastHarmonic_ + fabsf(latestVelocity) * kVibratoRateScaler;
andrewm@0 298 std::cout << "harmonic = " << harmonic << std::endl;
andrewm@0 299
andrewm@0 300 // Check whether the current vibrato has timed out
andrewm@0 301 if(currentTimestamp - vibratoLastPeakTimestamp_ > kVibratoTimeout) {
andrewm@0 302 std::cout << "Vibrato timed out\n";
andrewm@0 303 vibratoActive_ = false;
andrewm@0 304 vibratoVelocityPeakCount_ = 0;
andrewm@0 305 vibratoLastPeakTimestamp_ = currentTimestamp;
andrewm@0 306 }
andrewm@0 307 }
andrewm@0 308 }
andrewm@0 309 else {
andrewm@0 310 // Vibrato can't be active in these states
andrewm@0 311 //std::cout << "Vibrato finished from state change\n";
andrewm@0 312 vibratoActive_ = false;
andrewm@0 313 vibratoVelocityPeakCount_ = 0;
andrewm@0 314 vibratoLastPeakTimestamp_ = currentTimestamp;
andrewm@0 315 }
andrewm@0 316
andrewm@0 317 if(trackerState != kPositionTrackerStateReleaseFinished) {
andrewm@0 318 // For all active states except post-release, calculate
andrewm@0 319 // Intensity and Brightness parameters based on key position
andrewm@0 320
andrewm@0 321 if(latestPosition > 1.0) {
andrewm@0 322 intensity = 1.0;
andrewm@0 323 brightness = (latestPosition - 1.0) * aftertouchScaler_;
andrewm@0 324 }
andrewm@0 325 else if(latestPosition < 0.0) {
andrewm@0 326 intensity = 0.0;
andrewm@0 327 brightness = 0.0;
andrewm@0 328 }
andrewm@0 329 else {
andrewm@0 330 intensity = latestPosition;
andrewm@0 331 brightness = 0.0;
andrewm@0 332 }
andrewm@0 333
andrewm@0 334 if(!activePitchBends_.empty()) {
andrewm@0 335 // Look for active multi-key pitch bend gestures
andrewm@0 336 std::vector<PitchBend>::iterator it = activePitchBends_.begin();
andrewm@0 337 pitch = 0.0;
andrewm@0 338
andrewm@0 339 for(it = activePitchBends_.begin(); it != activePitchBends_.end(); it++) {
andrewm@0 340 PitchBend& bend(*it);
andrewm@0 341
andrewm@0 342 if(bend.isControllingBend) {
andrewm@0 343 // First find out of the bending key is still in a PartialPress state
andrewm@0 344 // If not, remove it and move on
andrewm@0 345 if((bend.positionTracker->currentState() != kPositionTrackerStatePartialPressAwaitingMax &&
andrewm@0 346 bend.positionTracker->currentState() != kPositionTrackerStatePartialPressFoundMax)
andrewm@0 347 || !bend.positionTracker->engaged()) {
andrewm@0 348 cout << "Removing bend from note " << bend.note << endl;
andrewm@0 349 bend.isFinished = true;
andrewm@0 350 continue;
andrewm@0 351 }
andrewm@0 352
andrewm@0 353 // This is the case where the other note is controlling our pitch
andrewm@0 354 if(bend.positionBuffer->empty()) {
andrewm@0 355 continue;
andrewm@0 356 }
andrewm@0 357
andrewm@0 358 float noteDifference = (float)(bend.note - noteNumber_);
andrewm@0 359 key_position latestBenderPosition = bend.positionBuffer->latest();
andrewm@0 360
andrewm@0 361 // Key position at 0 = 0 pitch bend; key position at max = most pitch bend
andrewm@0 362 float bendAmount = key_position_to_float(latestBenderPosition - kPianoKeyDefaultIdlePositionThreshold*2) /
andrewm@0 363 key_position_to_float(1.0 - kPianoKeyDefaultIdlePositionThreshold*2);
andrewm@0 364 if(bendAmount < 0)
andrewm@0 365 bendAmount = 0;
andrewm@0 366 pitch += noteDifference * bendAmount;
andrewm@0 367 }
andrewm@0 368 else {
andrewm@0 369 // This is the case where we're controlling the other note's pitch. Our own
andrewm@0 370 // pitch is the inverse of what we're sending to the neighboring note.
andrewm@0 371 // Compared to the above case, we know a few things since we're using our own
andrewm@0 372 // position: the buffer isn't empty and the tracker is engaged.
andrewm@0 373
andrewm@0 374 if(trackerState != kPositionTrackerStatePartialPressAwaitingMax &&
andrewm@0 375 trackerState != kPositionTrackerStatePartialPressFoundMax) {
andrewm@0 376 cout << "Removing our bend on note " << bend.note << endl;
andrewm@0 377 bend.isFinished = true;
andrewm@0 378 continue;
andrewm@0 379 }
andrewm@0 380
andrewm@0 381 float noteDifference = (float)(bend.note - noteNumber_);
andrewm@0 382
andrewm@0 383 // Key position at 0 = 0 pitch bend; key position at max = most pitch bend
andrewm@0 384 float bendAmount = key_position_to_float(latestPosition - kPianoKeyDefaultIdlePositionThreshold*2) /
andrewm@0 385 key_position_to_float(1.0 - kPianoKeyDefaultIdlePositionThreshold*2);
andrewm@0 386 if(bendAmount < 0)
andrewm@0 387 bendAmount = 0;
andrewm@0 388 pitch += noteDifference * (1.0 - bendAmount);
andrewm@0 389 }
andrewm@0 390 }
andrewm@0 391
andrewm@0 392 // Now reiterate to remove any of them that have finished
andrewm@0 393 it = activePitchBends_.begin();
andrewm@0 394
andrewm@0 395 while(it != activePitchBends_.end()) {
andrewm@0 396 if(it->isFinished) {
andrewm@0 397 // Go back to beginning and look again after erasing each one
andrewm@0 398 // This isn't very efficient but there will never be more than 4 elements anyway
andrewm@0 399 activePitchBends_.erase(it);
andrewm@0 400 it = activePitchBends_.begin();
andrewm@0 401 }
andrewm@0 402 else
andrewm@0 403 it++;
andrewm@0 404 }
andrewm@0 405
andrewm@0 406 std::cout << "pitch = " << pitch << std::endl;
andrewm@0 407 }
andrewm@0 408 else
andrewm@0 409 pitch = 0.0;
andrewm@0 410 }
andrewm@0 411 else {
andrewm@0 412 intensity = 0.0;
andrewm@0 413 brightness = 0.0;
andrewm@0 414 if(noteIsOn_) {
andrewm@0 415 int newNoteNumber = noteNumber_;
andrewm@0 416 //int newNoteNumber = ((noteNumber_ - 21) * 25)%88 + 21;
andrewm@0 417 keyboard_.sendMessage("/mrp/midi",
andrewm@0 418 "iii", (int)(kMIDINoteOnMessage + kDefaultMIDIChannel), (int)newNoteNumber, (int)0, LO_ARGS_END);
andrewm@0 419 //keyboard_.testLog_ << currentTimestamp << " /mrp/midi iii " << (kMIDINoteOnMessage + kDefaultMIDIChannel) << " " << newNoteNumber << " " << 0 << endl;
andrewm@0 420 }
andrewm@0 421 noteIsOn_ = false;
andrewm@0 422 shouldLookForPitchBends_ = true;
andrewm@0 423 }
andrewm@0 424 }
andrewm@0 425
andrewm@0 426 // TODO: TouchKeys mapping
andrewm@0 427
andrewm@0 428 // Send OSC message with these parameters unless they are unchanged from before
andrewm@0 429 if(!noteIsOn_ && intensity > 0.0) {
andrewm@0 430 int newNoteNumber = noteNumber_;
andrewm@0 431 //int newNoteNumber = ((noteNumber_ - 21) * 25)%88 + 21;
andrewm@0 432 keyboard_.sendMessage("/mrp/midi",
andrewm@0 433 "iii", (int)(kMIDINoteOnMessage + kDefaultMIDIChannel), (int)newNoteNumber, (int)127, LO_ARGS_END);
andrewm@0 434 //keyboard_.testLog_ << currentTimestamp << " /mrp/midi iii " << (kMIDINoteOnMessage + kDefaultMIDIChannel) << " " << newNoteNumber << " " << 127 << endl;
andrewm@0 435 noteIsOn_ = true;
andrewm@0 436 }
andrewm@0 437
andrewm@0 438 // Set key LED color according to key parameters
andrewm@0 439 // Partial press --> green of varying intensity
andrewm@0 440 // Aftertouch (brightness) --> green moving to red depending on brightness parameter
andrewm@0 441 // Pitch bend --> note bends toward blue as pitch value departs from center
andrewm@0 442 // Harmonic glissando --> cycle through hues with whitish tint (lower saturation)
andrewm@0 443 if(intensity != lastIntensity_ || brightness != lastBrightness_ || pitch != lastPitch_ || harmonic != lastHarmonic_) {
andrewm@0 444 if(harmonic != 0.0) {
andrewm@0 445 float hue = fmodf(harmonic, 1.0);
andrewm@0 446 keyboard_.setKeyLEDColorHSV(noteNumber_, hue, 0.25, 0.5);
andrewm@0 447 }
andrewm@0 448 else if(intensity >= 1.0) {
andrewm@0 449 if(pitch != 0.0)
andrewm@0 450 keyboard_.setKeyLEDColorHSV(noteNumber_, 0.33 + 0.33 * fabsf(pitch) - (brightness * 0.2), 1.0, intensity);
andrewm@0 451 else
andrewm@0 452 keyboard_.setKeyLEDColorHSV(noteNumber_, 0.33 - (brightness * 0.2), 1.0, 1.0);
andrewm@0 453 }
andrewm@0 454 else {
andrewm@0 455 if(pitch != 0.0)
andrewm@0 456 keyboard_.setKeyLEDColorHSV(noteNumber_, 0.33 + 0.33 * fabsf(pitch), 1.0, intensity);
andrewm@0 457 else
andrewm@0 458 keyboard_.setKeyLEDColorHSV(noteNumber_, 0.33, 1.0, intensity);
andrewm@0 459 }
andrewm@0 460 }
andrewm@0 461
andrewm@0 462 if(intensity != lastIntensity_) {
andrewm@0 463 int newNoteNumber = noteNumber_;
andrewm@0 464 //int newNoteNumber = ((noteNumber_ - 21) * 25)%88 + 21;
andrewm@0 465 keyboard_.sendMessage("/mrp/quality/intensity",
andrewm@0 466 "iif", (int)kDefaultMIDIChannel, (int)newNoteNumber, (float)intensity, LO_ARGS_END);
andrewm@0 467 //keyboard_.testLog_ << currentTimestamp << " /mrp/quality/intensity iif " << kDefaultMIDIChannel << " " << newNoteNumber << " " << intensity << endl;
andrewm@0 468 }
andrewm@0 469 if(brightness != lastBrightness_) {
andrewm@0 470 int newNoteNumber = noteNumber_;
andrewm@0 471 //int newNoteNumber = ((noteNumber_ - 21) * 25)%88 + 21;
andrewm@0 472 keyboard_.sendMessage("/mrp/quality/brightness",
andrewm@0 473 "iif", (int)kDefaultMIDIChannel, (int)newNoteNumber, (float)brightness, LO_ARGS_END);
andrewm@0 474 //keyboard_.testLog_ << currentTimestamp << " /mrp/quality/brightness iif " << kDefaultMIDIChannel << " " << newNoteNumber << " " << brightness << endl;
andrewm@0 475 }
andrewm@0 476 if(pitch != lastPitch_) {
andrewm@0 477 int newNoteNumber = noteNumber_;
andrewm@0 478 //int newNoteNumber = ((noteNumber_ - 21) * 25)%88 + 21;
andrewm@0 479 keyboard_.sendMessage("/mrp/quality/pitch",
andrewm@0 480 "iif", (int)kDefaultMIDIChannel, (int)newNoteNumber, (float)pitch, LO_ARGS_END);
andrewm@0 481 //keyboard_.testLog_ << currentTimestamp << " /mrp/quality/pitch iif " << kDefaultMIDIChannel << " " << newNoteNumber << " " << pitch << endl;
andrewm@0 482 }
andrewm@0 483 if(harmonic != lastHarmonic_) {
andrewm@0 484 int newNoteNumber = noteNumber_;
andrewm@0 485 //int newNoteNumber = ((noteNumber_ - 21) * 25)%88 + 21;
andrewm@0 486 keyboard_.sendMessage("/mrp/quality/harmonic",
andrewm@0 487 "iif", (int)kDefaultMIDIChannel, (int)newNoteNumber, (float)harmonic, LO_ARGS_END);
andrewm@0 488 //keyboard_.testLog_ << currentTimestamp << " /mrp/quality/harmonic iif " << kDefaultMIDIChannel << " " << newNoteNumber << " " << harmonic << endl;
andrewm@0 489 }
andrewm@0 490
andrewm@0 491 lastIntensity_ = intensity;
andrewm@0 492 lastBrightness_ = brightness;
andrewm@0 493 lastPitch_ = pitch;
andrewm@0 494 lastHarmonic_ = harmonic;
andrewm@0 495
andrewm@0 496 // Register for the next update by returning its timestamp
andrewm@0 497 nextScheduledTimestamp_ = currentTimestamp + updateInterval_;
andrewm@0 498 return nextScheduledTimestamp_;
andrewm@0 499 }
andrewm@0 500
andrewm@0 501 // Helper function that brings the velocity buffer up to date with the latest
andrewm@0 502 // samples. Velocity is not updated on every new position sample since it's not
andrewm@0 503 // efficient to run that many triggers all the time. Instead, it's brought up to
andrewm@0 504 // date on an as-needed basis during performMapping().
andrewm@0 505 key_velocity MRPMapping::updateVelocityMeasurements() {
andrewm@0 506 positionBuffer_->lock_mutex();
andrewm@0 507
andrewm@0 508 // Need at least 2 samples to calculate velocity (first difference)
andrewm@0 509 if(positionBuffer_->size() < 2) {
andrewm@0 510 positionBuffer_->unlock_mutex();
andrewm@0 511 return missing_value<key_velocity>::missing();
andrewm@0 512 }
andrewm@0 513
andrewm@0 514 if(lastCalculatedVelocityIndex_ < positionBuffer_->beginIndex() + 1) {
andrewm@0 515 // Fell off the beginning of the position buffer. Reset calculations.
andrewm@0 516 filteredVelocity_.clear();
andrewm@0 517 rawVelocity_.clear();
andrewm@0 518 lastCalculatedVelocityIndex_ = positionBuffer_->beginIndex() + 1;
andrewm@0 519 }
andrewm@0 520
andrewm@0 521 while(lastCalculatedVelocityIndex_ < positionBuffer_->endIndex()) {
andrewm@0 522 // Calculate the velocity and add to buffer
andrewm@0 523 key_position diffPosition = (*positionBuffer_)[lastCalculatedVelocityIndex_] - (*positionBuffer_)[lastCalculatedVelocityIndex_ - 1];
andrewm@0 524 timestamp_diff_type diffTimestamp = positionBuffer_->timestampAt(lastCalculatedVelocityIndex_) - positionBuffer_->timestampAt(lastCalculatedVelocityIndex_ - 1);
andrewm@0 525 key_velocity vel;
andrewm@0 526
andrewm@0 527 if(diffTimestamp != 0)
andrewm@0 528 vel = calculate_key_velocity(diffPosition, diffTimestamp);
andrewm@0 529 else
andrewm@0 530 vel = 0; // Bad measurement: replace with 0 so as not to mess up IIR calculations
andrewm@0 531
andrewm@0 532 // Add the raw velocity to the buffer
andrewm@0 533 rawVelocity_.insert(vel, positionBuffer_->timestampAt(lastCalculatedVelocityIndex_));
andrewm@0 534 lastCalculatedVelocityIndex_++;
andrewm@0 535 }
andrewm@0 536
andrewm@0 537 positionBuffer_->unlock_mutex();
andrewm@0 538
andrewm@0 539 // Bring the filtered velocity up to date
andrewm@0 540 key_velocity filteredVel = filteredVelocity_.calculate();
andrewm@0 541 //std::cout << "Key " << noteNumber_ << " velocity " << filteredVel << std::endl;
andrewm@0 542 return filteredVel;
andrewm@0 543 }
andrewm@0 544
andrewm@0 545 // Helper function that locates the timestamp at which this key entered the
andrewm@0 546 // PartialPress (i.e. first non-idle) state. Returns missing value if the
andrewm@0 547 // state can't be located.
andrewm@0 548 timestamp_type MRPMapping::findTimestampOfPartialPress() {
andrewm@0 549 if(positionTracker_ == 0)
andrewm@0 550 return missing_value<timestamp_type>::missing();
andrewm@0 551 if(positionTracker_->empty())
andrewm@0 552 return missing_value<timestamp_type>::missing();
andrewm@0 553 //Node<int>::reverse_iterator it = positionTracker_->rbegin();
andrewm@0 554 Node<int>::size_type index = positionTracker_->endIndex() - 1;
andrewm@0 555 bool foundPartialPressState = false;
andrewm@0 556 timestamp_type earliestPartialPressTimestamp;
andrewm@0 557
andrewm@0 558 // Search backwards from present
andrewm@0 559 while(index >= positionTracker_->beginIndex()/*it != positionTracker_->rend()*/) {
andrewm@0 560 if((*positionTracker_)[index].state == kPositionTrackerStatePartialPressAwaitingMax ||
andrewm@0 561 (*positionTracker_)[index].state == kPositionTrackerStatePartialPressFoundMax) {
andrewm@0 562 cout << "index " << index << " state " << (*positionTracker_)[index].state << endl;
andrewm@0 563 foundPartialPressState = true;
andrewm@0 564 earliestPartialPressTimestamp = positionTracker_->timestampAt(index);
andrewm@0 565 }
andrewm@0 566 else {
andrewm@0 567 // This state is not a PartialPress state. Two cases: either
andrewm@0 568 // we haven't yet encountered a partial press or we have found
andrewm@0 569 // a state before the partial press, in which case the previous
andrewm@0 570 // state we found was the first.
andrewm@0 571 cout << "index " << index << " state " << (*positionTracker_)[index].state << endl;
andrewm@0 572 if(foundPartialPressState) {
andrewm@0 573 return earliestPartialPressTimestamp;
andrewm@0 574 }
andrewm@0 575 }
andrewm@0 576
andrewm@0 577 // Step backwards one sample, but stop if we hit the beginning index
andrewm@0 578 if(index == 0)
andrewm@0 579 break;
andrewm@0 580 index--;
andrewm@0 581 }
andrewm@0 582
andrewm@0 583 if(foundPartialPressState)
andrewm@0 584 return earliestPartialPressTimestamp;
andrewm@0 585
andrewm@0 586 // Didn't find anything if we get here
andrewm@0 587 return missing_value<timestamp_type>::missing();
andrewm@0 588 }