Mercurial > hg > touchkeys
view Source/Mappings/MultiFingerTrigger/TouchkeyMultiFingerTriggerMapping.cpp @ 11:c6f30c1e2bda
Fixes for OSC emulation. Also a debugging tool for generating random TouchKeys data.
author | Andrew McPherson <andrewm@eecs.qmul.ac.uk> |
---|---|
date | Sat, 23 Nov 2013 14:47:02 +0000 |
parents | 3580ffe87dc8 |
children | 1526d2fbe01e |
line wrap: on
line source
/* TouchKeys: multi-touch musical keyboard control software Copyright (c) 2013 Andrew McPherson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. ===================================================================== TouchkeyMultiFingerTriggerMapping.cpp: per-note mapping for the multiple- finger trigger mapping, which performs actions when two or more fingers are added or removed from the key. */ #include "TouchkeyMultiFingerTriggerMapping.h" #include "../../TouchKeys/MidiOutputController.h" // Class constants const int TouchkeyMultiFingerTriggerMapping::kDefaultFilterBufferLength = 30; const int TouchkeyMultiFingerTriggerMapping::kDefaultNumTouchesForTrigger = 2; const int TouchkeyMultiFingerTriggerMapping::kDefaultNumFramesForTrigger = 2; const int TouchkeyMultiFingerTriggerMapping::kDefaultNumConsecutiveTapsForTrigger = 1; const timestamp_diff_type TouchkeyMultiFingerTriggerMapping::kDefaultMaxTapSpacing = milliseconds_to_timestamp(500.0); // Main constructor takes references/pointers from objects which keep track // of touch location, continuous key position and the state detected from that // position. The PianoKeyboard object is strictly required as it gives access to // Scheduler and OSC methods. The others are optional since any given system may // contain only one of continuous key position or touch sensitivity TouchkeyMultiFingerTriggerMapping::TouchkeyMultiFingerTriggerMapping(PianoKeyboard &keyboard, MappingFactory *factory, int noteNumber, Node<KeyTouchFrame>* touchBuffer, Node<key_position>* positionBuffer, KeyPositionTracker* positionTracker) : TouchkeyBaseMapping(keyboard, factory, noteNumber, touchBuffer, positionBuffer, positionTracker), numTouchesForTrigger_(kDefaultNumTouchesForTrigger), numFramesForTrigger_(kDefaultNumFramesForTrigger), numConsecutiveTapsForTrigger_(kDefaultNumConsecutiveTapsForTrigger), maxTapSpacing_(kDefaultMaxTapSpacing), needsMidiNoteOn_(true), pastSamples_(kDefaultFilterBufferLength) { reset(); } // Reset state back to defaults void TouchkeyMultiFingerTriggerMapping::reset() { ScopedLock sl(sampleBufferMutex_); TouchkeyBaseMapping::reset(); pastSamples_.clear(); lastNumActiveTouches_ = 0; lastActiveTouchLocations_[0] = lastActiveTouchLocations_[1] = lastActiveTouchLocations_[2] = 0; framesCount_ = 0; tapsCount_ = 0; hasGeneratedTap_ = false; lastTapStartTimestamp_ = missing_value<timestamp_type>::missing(); hasTriggered_ = false; } // Resend all current parameters void TouchkeyMultiFingerTriggerMapping::resend() { // Message is only sent at release; resend may not apply here. } // This method receives data from the touch buffer or possibly the continuous key angle (not used here) void TouchkeyMultiFingerTriggerMapping::triggerReceived(TriggerSource* who, timestamp_type timestamp) { if(needsMidiNoteOn_ && !noteIsOn_) { framesCount_ = 0; hasGeneratedTap_ = false; return; } if(who == touchBuffer_) { if(!touchBuffer_->empty()) { // Find the current number of touches KeyTouchFrame frame = touchBuffer_->latest(); int count = frame.count; if(count < numTouchesForTrigger_) { framesCount_ = 0; hasGeneratedTap_ = false; if(hasTriggered_) { generateTriggerOff(timestamp); hasTriggered_ = false; } } else if(count == numTouchesForTrigger_) { framesCount_++; if(framesCount_ >= numFramesForTrigger_ && !hasGeneratedTap_) { // Enough frames have elapsed to consider this a tap // Figure out if it is a multiple consecutive tap or the first // of a set. if(!missing_value<timestamp_diff_type>::isMissing(lastTapStartTimestamp_)) { if(timestamp - lastTapStartTimestamp_ < maxTapSpacing_) { tapsCount_++; } else tapsCount_ = 1; } else tapsCount_ = 1; std::cout << "Tap " << tapsCount_ << std::endl; // Check if the right number of taps has elapsed if(tapsCount_ >= numConsecutiveTapsForTrigger_ && !hasTriggered_) { hasTriggered_ = true; // Find the ID of the newest touch and compare its location // to the immediately preceding touch(es) to find the distance int newest = 0, oldest = 0, newestId = -1, oldestId = 1000000; for(int i = 0; i < count; i++) { if(frame.ids[i] > newestId) { newest = i; newestId = frame.ids[i]; } if(frame.ids[i] < oldestId) { oldest = i; oldestId = frame.ids[i]; } } // Find the distance between the point before this tap and the // point that was added to create the tap. If this is a 3-touch // tap, find the distance between the farthest two points, with // the direction determined by which end is older. float distance = frame.locs[newest] - frame.locs[oldest]; if(count == 3) { if(fabsf(frame.locs[2] - frame.locs[0]) > fabsf(distance)) { if(frame.ids[2] > frame.ids[0]) distance = frame.locs[2] - frame.locs[0]; else distance = frame.locs[0] - frame.locs[2]; } } // Generate the trigger. If a multi-tap gesture, also indicate the timing if(numConsecutiveTapsForTrigger_ <= 1) generateTriggerOn(timestamp, 0, distance); else generateTriggerOn(timestamp, timestamp - lastTapStartTimestamp_, distance); } hasGeneratedTap_ = true; lastTapStartTimestamp_ = timestamp; } } // Save the count locations for next time lastNumActiveTouches_ = frame.count; for(int i = 0; i < count; i++) { lastActiveTouchLocations_[i] = frame.locs[i]; } } } } // Mapping method. This actually does the real work of sending OSC data in response to the // latest information from the touch sensors or continuous key angle timestamp_type TouchkeyMultiFingerTriggerMapping::performMapping() { // Nothing to do here until note is released. // Register for the next update by returning its timestamp // TODO: do we even need this? Check Mapping::engage() and Mapping::disengage() timestamp_type currentTimestamp = keyboard_.schedulerCurrentTimestamp(); nextScheduledTimestamp_ = currentTimestamp + updateInterval_; return nextScheduledTimestamp_; } void TouchkeyMultiFingerTriggerMapping::generateTriggerOn(timestamp_type timestamp, timestamp_diff_type timeBetweenTaps, float distanceBetweenPoints) { std::cout << "Trigger distance = " << distanceBetweenPoints << " timing = " << timeBetweenTaps << std::endl; // KLUDGE if(!suspended_) { #if 0 if(distanceBetweenPoints > 0.35) { //keyboard_.sendMessage("/touchkeys/pitchbend", "if", noteNumber_, 2.0, LO_ARGS_END); int ch = keyboard_.key(noteNumber_)->midiChannel(); int vel = keyboard_.key(noteNumber_)->midiVelocity(); keyboard_.midiOutputController()->sendNoteOn(0, ch, noteNumber_ + 14, vel); //keyboard_.midiOutputController()->sendNoteOff(0, ch, noteNumber_ + 12, vel); } else { //keyboard_.sendMessage("/touchkeys/pitchbend", "if", noteNumber_, 1.0, LO_ARGS_END); int ch = keyboard_.key(noteNumber_)->midiChannel(); int vel = keyboard_.key(noteNumber_)->midiVelocity(); keyboard_.midiOutputController()->sendNoteOn(0, ch, noteNumber_ + 13, vel); //keyboard_.midiOutputController()->sendNoteOff(0, ch, noteNumber_ + 12, vel); } #elif 0 int ch = keyboard_.key(noteNumber_)->midiChannel(); keyboard_.midiOutputController()->sendControlChange(0, ch, 73, 127); #else keyboard_.midiOutputController()->sendNoteOn(0, keyboard_.key(noteNumber_)->midiChannel(), noteNumber_, 127); #endif } } void TouchkeyMultiFingerTriggerMapping::generateTriggerOff(timestamp_type timestamp) { std::cout << "Trigger off\n"; if(!suspended_) { #if 0 //eyboard_.sendMessage("/touchkeys/pitchbend", "if", noteNumber_, 0.0, LO_ARGS_END); int ch = keyboard_.key(noteNumber_)->midiChannel(); int vel = keyboard_.key(noteNumber_)->midiVelocity(); keyboard_.midiOutputController()->sendNoteOn(0, ch, noteNumber_ + 12, vel); //keyboard_.midiOutputController()->sendNoteOff(0, ch, noteNumber_ + 13, vel); //keyboard_.midiOutputController()->sendNoteOff(0, ch, noteNumber_ + 14, vel); #elif 0 int ch = keyboard_.key(noteNumber_)->midiChannel(); keyboard_.midiOutputController()->sendControlChange(0, ch, 73, 0); #else keyboard_.midiOutputController()->sendNoteOn(0, keyboard_.key(noteNumber_)->midiChannel(), noteNumber_, 127); #endif } } // MIDI note-off message received void TouchkeyMultiFingerTriggerMapping::midiNoteOffReceived(int channel) { int ch = keyboard_.key(noteNumber_)->midiChannel(); keyboard_.midiOutputController()->sendControlChange(0, ch, 73, 0); }