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 TouchkeyControlMapping.cpp: per-note mapping for the TouchKeys control
|
andrewm@0
|
21 mapping, which converts an arbitrary touch parameter into a MIDI or
|
andrewm@0
|
22 OSC control message.
|
andrewm@0
|
23 */
|
andrewm@0
|
24
|
andrewm@0
|
25 #include "TouchkeyControlMapping.h"
|
andrewm@0
|
26 #include <vector>
|
andrewm@0
|
27 #include <climits>
|
andrewm@0
|
28 #include <cmath>
|
andrewm@0
|
29 #include "../MappingScheduler.h"
|
andrewm@0
|
30
|
andrewm@0
|
31 #undef DEBUG_CONTROL_MAPPING
|
andrewm@0
|
32
|
andrewm@0
|
33 // Class constants
|
andrewm@0
|
34 const int TouchkeyControlMapping::kDefaultMIDIChannel = 0;
|
andrewm@0
|
35 const int TouchkeyControlMapping::kDefaultFilterBufferLength = 300;
|
andrewm@0
|
36
|
andrewm@0
|
37 const bool TouchkeyControlMapping::kDefaultIgnoresTwoFingers = false;
|
andrewm@0
|
38 const bool TouchkeyControlMapping::kDefaultIgnoresThreeFingers = false;
|
andrewm@0
|
39 const int TouchkeyControlMapping::kDefaultDirection = TouchkeyControlMapping::kDirectionPositive;
|
andrewm@0
|
40
|
andrewm@0
|
41 // Main constructor takes references/pointers from objects which keep track
|
andrewm@0
|
42 // of touch location, continuous key position and the state detected from that
|
andrewm@0
|
43 // position. The PianoKeyboard object is strictly required as it gives access to
|
andrewm@0
|
44 // Scheduler and OSC methods. The others are optional since any given system may
|
andrewm@0
|
45 // contain only one of continuous key position or touch sensitivity
|
andrewm@0
|
46 TouchkeyControlMapping::TouchkeyControlMapping(PianoKeyboard &keyboard, MappingFactory *factory, int noteNumber, Node<KeyTouchFrame>* touchBuffer,
|
andrewm@0
|
47 Node<key_position>* positionBuffer, KeyPositionTracker* positionTracker)
|
andrewm@0
|
48 : TouchkeyBaseMapping(keyboard, factory, noteNumber, touchBuffer, positionBuffer, positionTracker),
|
andrewm@0
|
49 controlIsEngaged_(false),
|
andrewm@0
|
50 inputMin_(0.0), inputMax_(1.0), outputMin_(0.0), outputMax_(1.0), outputDefault_(0.0),
|
andrewm@0
|
51 inputParameter_(kInputParameterYPosition), inputType_(kTypeAbsolute),
|
andrewm@0
|
52 threshold_(0.0), ignoresTwoFingers_(kDefaultIgnoresTwoFingers),
|
andrewm@0
|
53 ignoresThreeFingers_(kDefaultIgnoresThreeFingers), direction_(kDefaultDirection),
|
andrewm@0
|
54 touchOnsetValue_(missing_value<float>::missing()),
|
andrewm@0
|
55 midiOnsetValue_(missing_value<float>::missing()),
|
andrewm@0
|
56 lastValue_(missing_value<float>::missing()),
|
andrewm@0
|
57 lastTimestamp_(missing_value<timestamp_type>::missing()), lastProcessedIndex_(0),
|
andrewm@0
|
58 controlEngageLocation_(missing_value<float>::missing()),
|
andrewm@0
|
59 controlScalerPositive_(missing_value<float>::missing()),
|
andrewm@0
|
60 controlScalerNegative_(missing_value<float>::missing()),
|
andrewm@0
|
61 lastControlValue_(outputDefault_),
|
andrewm@0
|
62 rawValues_(kDefaultFilterBufferLength)
|
andrewm@0
|
63 {
|
andrewm@0
|
64 resetDetectionState();
|
andrewm@0
|
65 }
|
andrewm@0
|
66
|
andrewm@0
|
67 TouchkeyControlMapping::~TouchkeyControlMapping() {
|
andrewm@0
|
68 #if 0
|
andrewm@0
|
69 #ifndef NEW_MAPPING_SCHEDULER
|
andrewm@0
|
70 try {
|
andrewm@0
|
71 disengage();
|
andrewm@0
|
72 }
|
andrewm@0
|
73 catch(...) {
|
andrewm@0
|
74 std::cerr << "~TouchkeyControlMapping(): exception during disengage()\n";
|
andrewm@0
|
75 }
|
andrewm@0
|
76 #endif
|
andrewm@0
|
77 #endif
|
andrewm@0
|
78 }
|
andrewm@0
|
79
|
andrewm@0
|
80 // Turn on mapping of data.
|
andrewm@0
|
81 /*void TouchkeyControlMapping::engage() {
|
andrewm@0
|
82 Mapping::engage();
|
andrewm@0
|
83
|
andrewm@0
|
84 // Register for OSC callbacks on MIDI note on/off
|
andrewm@0
|
85 addOscListener("/midi/noteon");
|
andrewm@0
|
86 addOscListener("/midi/noteoff");
|
andrewm@0
|
87 }
|
andrewm@0
|
88
|
andrewm@0
|
89 // Turn off mapping of data. Remove our callback from the scheduler
|
andrewm@0
|
90 void TouchkeyControlMapping::disengage(bool shouldDelete) {
|
andrewm@0
|
91 // Remove OSC listeners first
|
andrewm@0
|
92 removeOscListener("/midi/noteon");
|
andrewm@0
|
93 removeOscListener("/midi/noteoff");
|
andrewm@0
|
94
|
andrewm@0
|
95 // Don't send any separate message here, leave it where it was
|
andrewm@0
|
96
|
andrewm@0
|
97 Mapping::disengage(shouldDelete);
|
andrewm@0
|
98
|
andrewm@0
|
99 if(noteIsOn_) {
|
andrewm@0
|
100 // TODO
|
andrewm@0
|
101 }
|
andrewm@0
|
102 noteIsOn_ = false;
|
andrewm@0
|
103 }*/
|
andrewm@0
|
104
|
andrewm@0
|
105 // Reset state back to defaults
|
andrewm@0
|
106 void TouchkeyControlMapping::reset() {
|
andrewm@0
|
107 TouchkeyBaseMapping::reset();
|
andrewm@0
|
108 sendControlMessage(outputDefault_);
|
andrewm@0
|
109 resetDetectionState();
|
andrewm@0
|
110 //noteIsOn_ = false;
|
andrewm@0
|
111 }
|
andrewm@0
|
112
|
andrewm@0
|
113 // Resend all current parameters
|
andrewm@0
|
114 void TouchkeyControlMapping::resend() {
|
andrewm@0
|
115 sendControlMessage(lastControlValue_, true);
|
andrewm@0
|
116 }
|
andrewm@0
|
117
|
andrewm@0
|
118 // Name for this control, used in the OSC path
|
andrewm@0
|
119 /*void TouchkeyControlMapping::setName(const std::string& name) {
|
andrewm@0
|
120 controlName_ = name;
|
andrewm@0
|
121 }*/
|
andrewm@0
|
122
|
andrewm@0
|
123 // Parameters for the controller handling
|
andrewm@0
|
124 // Input parameter to use for this control mapping and whether it is absolute or relative
|
andrewm@0
|
125 void TouchkeyControlMapping::setInputParameter(int parameter, int type) {
|
andrewm@0
|
126 if(inputParameter_ >= 0 && inputParameter_ < kInputParameterMaxValue)
|
andrewm@0
|
127 inputParameter_ = parameter;
|
andrewm@0
|
128 if(type >= 0 && type < kTypeMaxValue)
|
andrewm@0
|
129 inputType_ = type;
|
andrewm@0
|
130 }
|
andrewm@0
|
131
|
andrewm@0
|
132 // Input/output range for this parameter
|
andrewm@0
|
133 void TouchkeyControlMapping::setRange(float inputMin, float inputMax, float outputMin, float outputMax, float outputDefault) {
|
andrewm@0
|
134 inputMin_ = inputMin;
|
andrewm@0
|
135 inputMax_ = inputMax;
|
andrewm@0
|
136 outputMin_ = outputMin;
|
andrewm@0
|
137 outputMax_ = outputMax;
|
andrewm@0
|
138 outputDefault_ = outputDefault;
|
andrewm@0
|
139 }
|
andrewm@0
|
140
|
andrewm@0
|
141 // Threshold which must be exceeded for the control to engage (for relative position), or 0 if not used
|
andrewm@0
|
142 void TouchkeyControlMapping::setThreshold(float threshold) {
|
andrewm@0
|
143 threshold_ = threshold;
|
andrewm@0
|
144 }
|
andrewm@0
|
145
|
andrewm@0
|
146 void TouchkeyControlMapping::setIgnoresMultipleFingers(bool ignoresTwo, bool ignoresThree) {
|
andrewm@0
|
147 ignoresTwoFingers_ = ignoresTwo;
|
andrewm@0
|
148 ignoresThreeFingers_ = ignoresThree;
|
andrewm@0
|
149 }
|
andrewm@0
|
150
|
andrewm@0
|
151 void TouchkeyControlMapping::setDirection(int direction) {
|
andrewm@0
|
152 if(direction >= 0 && direction < kDirectionMaxValue)
|
andrewm@0
|
153 direction_ = direction;
|
andrewm@0
|
154 }
|
andrewm@0
|
155
|
andrewm@0
|
156 // OSC handler method. Called from PianoKeyboard when MIDI data comes in.
|
andrewm@0
|
157 /*bool TouchkeyControlMapping::oscHandlerMethod(const char *path, const char *types, int numValues, lo_arg **values, void *data) {
|
andrewm@0
|
158 if(!strcmp(path, "/midi/noteon") && !noteIsOn_ && numValues >= 1) {
|
andrewm@0
|
159 if(types[0] == 'i' && values[0]->i == noteNumber_) {
|
andrewm@0
|
160 // MIDI note has gone on. Set the starting location to be most recent
|
andrewm@0
|
161 // location. It's possible there has been no touch data before this,
|
andrewm@0
|
162 // in which case lastX and lastY will hold missing values.
|
andrewm@0
|
163 midiOnsetValue_ = lastValue_;
|
andrewm@0
|
164 if(!missing_value<float>::isMissing(midiOnsetValue_)) {
|
andrewm@0
|
165 if(inputType_ == kTypeNoteOnsetRelative) {
|
andrewm@0
|
166 // Already have touch data. Clear the buffer here.
|
andrewm@0
|
167 // Clear buffer and start with default value for this point
|
andrewm@0
|
168 clearBuffers();
|
andrewm@0
|
169
|
andrewm@0
|
170 #ifdef DEBUG_CONTROL_MAPPING
|
andrewm@0
|
171 std::cout << "MIDI on: starting at (" << midiOnsetValue_ << ")\n";
|
andrewm@0
|
172 #endif
|
andrewm@0
|
173 }
|
andrewm@0
|
174 }
|
andrewm@0
|
175 else {
|
andrewm@0
|
176 #ifdef DEBUG_CONTROL_MAPPING
|
andrewm@0
|
177 std::cout << "MIDI on but no touch\n";
|
andrewm@0
|
178 #endif
|
andrewm@0
|
179 }
|
andrewm@0
|
180
|
andrewm@0
|
181 noteIsOn_ = true;
|
andrewm@0
|
182 return false;
|
andrewm@0
|
183 }
|
andrewm@0
|
184 }
|
andrewm@0
|
185 else if(!strcmp(path, "/midi/noteoff") && noteIsOn_ && numValues >= 1) {
|
andrewm@0
|
186 if(types[0] == 'i' && values[0]->i == noteNumber_) {
|
andrewm@0
|
187 // MIDI note goes off
|
andrewm@0
|
188 noteIsOn_ = false;
|
andrewm@0
|
189 if(controlIsEngaged_) {
|
andrewm@0
|
190 // TODO: should anything happen here?
|
andrewm@0
|
191 }
|
andrewm@0
|
192 #ifdef DEBUG_CONTROL_MAPPING
|
andrewm@0
|
193 std::cout << "MIDI off\n";
|
andrewm@0
|
194 #endif
|
andrewm@0
|
195 return false;
|
andrewm@0
|
196 }
|
andrewm@0
|
197 }
|
andrewm@0
|
198
|
andrewm@0
|
199 return false;
|
andrewm@0
|
200 }*/
|
andrewm@0
|
201
|
andrewm@0
|
202 // Trigger method. This receives updates from the TouchKey data or from state changes in
|
andrewm@0
|
203 // the continuous key position (KeyPositionTracker). It will potentially change the scheduled
|
andrewm@0
|
204 // behavior of future mapping calls, but the actual OSC messages should be transmitted in a different
|
andrewm@0
|
205 // thread.
|
andrewm@0
|
206 void TouchkeyControlMapping::triggerReceived(TriggerSource* who, timestamp_type timestamp) {
|
andrewm@0
|
207 if(who == 0)
|
andrewm@0
|
208 return;
|
andrewm@0
|
209
|
andrewm@0
|
210 if(who == touchBuffer_) {
|
andrewm@0
|
211 if(!touchBuffer_->empty()) {
|
andrewm@0
|
212 // New touch data is available. Find the distance from the onset location.
|
andrewm@0
|
213 KeyTouchFrame frame = touchBuffer_->latest();
|
andrewm@0
|
214 lastTimestamp_ = timestamp;
|
andrewm@0
|
215
|
andrewm@0
|
216 if(frame.count == 0) {
|
andrewm@0
|
217 // No touches. Last values are "missing", and we're not tracking any
|
andrewm@0
|
218 // particular touch ID
|
andrewm@0
|
219 lastValue_ = missing_value<float>::missing();
|
andrewm@0
|
220 idsOfCurrentTouches_[0] = idsOfCurrentTouches_[1] = idsOfCurrentTouches_[2] = -1;
|
andrewm@0
|
221
|
andrewm@0
|
222 #ifdef DEBUG_CONTROL_MAPPING
|
andrewm@0
|
223 std::cout << "Touch off\n";
|
andrewm@0
|
224 #endif
|
andrewm@0
|
225 }
|
andrewm@0
|
226 else {
|
andrewm@0
|
227 //ScopedLock sl(rawValueAccessMutex_);
|
andrewm@0
|
228
|
andrewm@0
|
229 // At least one touch. Check if we are already tracking an ID and, if so,
|
andrewm@0
|
230 // use its coordinates. Otherwise grab the lowest current ID.
|
andrewm@0
|
231 lastValue_ = getValue(frame);
|
andrewm@0
|
232
|
andrewm@0
|
233 // Check that the value actually exists
|
andrewm@0
|
234 if(!missing_value<float>::isMissing(lastValue_)) {
|
andrewm@0
|
235 // If we have no onset value, this is it
|
andrewm@0
|
236 if(missing_value<float>::isMissing(touchOnsetValue_)) {
|
andrewm@0
|
237 touchOnsetValue_ = lastValue_;
|
andrewm@0
|
238 if(inputType_ == kTypeFirstTouchRelative) {
|
andrewm@0
|
239 clearBuffers();
|
andrewm@0
|
240 #ifdef DEBUG_CONTROL_MAPPING
|
andrewm@0
|
241 std::cout << "Starting at " << lastValue_ << std::endl;
|
andrewm@0
|
242 #endif
|
andrewm@0
|
243 }
|
andrewm@0
|
244 }
|
andrewm@0
|
245
|
andrewm@0
|
246 // If MIDI note is on and we don't previously have a value, this is it
|
andrewm@0
|
247 if(noteIsOn_ && missing_value<float>::isMissing(midiOnsetValue_)) {
|
andrewm@0
|
248 midiOnsetValue_ = lastValue_;
|
andrewm@0
|
249 if(inputType_ == kTypeNoteOnsetRelative) {
|
andrewm@0
|
250 clearBuffers();
|
andrewm@0
|
251 #ifdef DEBUG_CONTROL_MAPPING
|
andrewm@0
|
252 std::cout << "Starting at " << lastValue_ << std::endl;
|
andrewm@0
|
253 #endif
|
andrewm@0
|
254 }
|
andrewm@0
|
255 }
|
andrewm@0
|
256
|
andrewm@0
|
257 if(noteIsOn_) {
|
andrewm@0
|
258 // Insert the latest sample into the buffer depending on how the data should be processed
|
andrewm@0
|
259 if(inputType_ == kTypeAbsolute) {
|
andrewm@0
|
260 rawValues_.insert(lastValue_, timestamp);
|
andrewm@0
|
261 }
|
andrewm@0
|
262 else if(inputType_ == kTypeFirstTouchRelative) {
|
andrewm@0
|
263 rawValues_.insert(lastValue_ - touchOnsetValue_, timestamp);
|
andrewm@0
|
264 }
|
andrewm@0
|
265 else if(inputType_ == kTypeNoteOnsetRelative) {
|
andrewm@0
|
266 rawValues_.insert(lastValue_ - midiOnsetValue_, timestamp);
|
andrewm@0
|
267 }
|
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 }
|
andrewm@0
|
279 }
|
andrewm@0
|
280 }
|
andrewm@0
|
281 }
|
andrewm@0
|
282 }
|
andrewm@0
|
283
|
andrewm@0
|
284 // Mapping method. This actually does the real work of sending OSC data in response to the
|
andrewm@0
|
285 // latest information from the touch sensors or continuous key angle
|
andrewm@0
|
286 timestamp_type TouchkeyControlMapping::performMapping() {
|
andrewm@0
|
287 //ScopedLock sl(rawValueAccessMutex_);
|
andrewm@0
|
288
|
andrewm@0
|
289 timestamp_type currentTimestamp = keyboard_.schedulerCurrentTimestamp();
|
andrewm@0
|
290 bool newSamplePresent = false;
|
andrewm@0
|
291
|
andrewm@0
|
292 // Go through the filtered distance samples that are remaining to process.
|
andrewm@0
|
293 if(lastProcessedIndex_ < rawValues_.beginIndex() + 1) {
|
andrewm@0
|
294 // Fell off the beginning of the position buffer. Skip to the samples we have
|
andrewm@0
|
295 // (shouldn't happen except in cases of exceptional system load, and not too
|
andrewm@0
|
296 // consequential if it does happen).
|
andrewm@0
|
297 lastProcessedIndex_ = rawValues_.beginIndex() + 1;
|
andrewm@0
|
298 }
|
andrewm@0
|
299
|
andrewm@0
|
300 while(lastProcessedIndex_ < rawValues_.endIndex()) {
|
andrewm@0
|
301 float value = rawValues_[lastProcessedIndex_];
|
andrewm@0
|
302 //timestamp_type timestamp = rawValues_.timestampAt(lastProcessedIndex_);
|
andrewm@0
|
303 newSamplePresent = true;
|
andrewm@0
|
304
|
andrewm@0
|
305 if(inputType_ == kTypeAbsolute) {
|
andrewm@0
|
306 controlIsEngaged_ = true;
|
andrewm@0
|
307 }
|
andrewm@0
|
308 else if(!controlIsEngaged_) {
|
andrewm@0
|
309 // Compare value against threshold to see if the control should engage
|
andrewm@0
|
310 if(fabsf(value) > threshold_) {
|
andrewm@0
|
311 float startingValue;
|
andrewm@0
|
312
|
andrewm@0
|
313 controlIsEngaged_ = true;
|
andrewm@0
|
314 controlEngageLocation_ = (value > 0 ? threshold_ : -threshold_);
|
andrewm@0
|
315
|
andrewm@0
|
316 #ifdef DEBUG_CONTROL_MAPPING
|
andrewm@0
|
317 std::cout << "engaging control at distance " << controlEngageLocation_ << std::endl;
|
andrewm@0
|
318 #endif
|
andrewm@0
|
319
|
andrewm@0
|
320 if(inputType_ == kTypeFirstTouchRelative)
|
andrewm@0
|
321 startingValue = touchOnsetValue_;
|
andrewm@0
|
322 else
|
andrewm@0
|
323 startingValue = midiOnsetValue_;
|
andrewm@0
|
324
|
andrewm@0
|
325 // This is how much range we would have had without the threshold
|
andrewm@0
|
326 float distanceToPositiveEdgeWithoutThreshold = 1.0 - startingValue;
|
andrewm@0
|
327 float distanceToNegativeEdgeWithoutThreshold = 0.0 + startingValue;
|
andrewm@0
|
328
|
andrewm@0
|
329 // This is how much range we actually have with the threshold
|
andrewm@0
|
330 float actualDistanceToPositiveEdge = 1.0 - (startingValue + controlEngageLocation_);
|
andrewm@0
|
331 float actualDistanceToNegativeEdge = 0.0 + startingValue + controlEngageLocation_;
|
andrewm@0
|
332
|
andrewm@0
|
333 // Make it so moving toward edge of key gets as far as it would have without
|
andrewm@0
|
334 // the distance lost by the threshold
|
andrewm@0
|
335 if(actualDistanceToPositiveEdge > 0.0)
|
andrewm@0
|
336 controlScalerPositive_ = (outputMax_ - outputDefault_) * distanceToPositiveEdgeWithoutThreshold / actualDistanceToPositiveEdge;
|
andrewm@0
|
337 else
|
andrewm@0
|
338 controlScalerPositive_ = (outputMax_ - outputDefault_); // Sanity check
|
andrewm@0
|
339 if(actualDistanceToNegativeEdge > 0.0)
|
andrewm@0
|
340 controlScalerNegative_ = (outputDefault_ - outputMin_) * distanceToNegativeEdgeWithoutThreshold / actualDistanceToNegativeEdge;
|
andrewm@0
|
341 else
|
andrewm@0
|
342 controlScalerNegative_ = (outputDefault_ - outputMin_); // Sanity check
|
andrewm@0
|
343 }
|
andrewm@0
|
344 }
|
andrewm@0
|
345
|
andrewm@0
|
346 lastProcessedIndex_++;
|
andrewm@0
|
347 }
|
andrewm@0
|
348
|
andrewm@0
|
349 if(controlIsEngaged_) {
|
andrewm@0
|
350 // Having processed every sample individually for any detection/filtering, now send
|
andrewm@0
|
351 // the most recent output as an OSC message
|
andrewm@0
|
352 if(newSamplePresent) {
|
andrewm@0
|
353 float latestValue = rawValues_.latest();
|
andrewm@0
|
354
|
andrewm@0
|
355 // In cases of relative values, the place the control engages will actually be where it crosses
|
andrewm@0
|
356 // the threshold, not the onset location itself. Need to update the value accordingly.
|
andrewm@0
|
357 if(inputType_ == kTypeFirstTouchRelative ||
|
andrewm@0
|
358 inputType_ == kTypeNoteOnsetRelative) {
|
andrewm@0
|
359 if(latestValue > 0) {
|
andrewm@0
|
360 latestValue -= threshold_;
|
andrewm@0
|
361 if(latestValue < 0)
|
andrewm@0
|
362 latestValue = 0;
|
andrewm@0
|
363 }
|
andrewm@0
|
364 else if(latestValue < 0) {
|
andrewm@0
|
365 latestValue += threshold_;
|
andrewm@0
|
366 if(latestValue > 0)
|
andrewm@0
|
367 latestValue = 0;
|
andrewm@0
|
368 }
|
andrewm@41
|
369
|
andrewm@41
|
370 if(direction_ == kDirectionNegative)
|
andrewm@41
|
371 latestValue = -latestValue;
|
andrewm@41
|
372 else if((direction_ == kDirectionBoth) && latestValue < 0)
|
andrewm@41
|
373 latestValue = -latestValue;
|
andrewm@0
|
374 }
|
andrewm@0
|
375
|
andrewm@0
|
376 sendControlMessage(latestValue);
|
andrewm@0
|
377 lastControlValue_ = latestValue;
|
andrewm@0
|
378 }
|
andrewm@0
|
379 }
|
andrewm@0
|
380
|
andrewm@0
|
381 // Register for the next update by returning its timestamp
|
andrewm@11
|
382 nextScheduledTimestamp_ = 0; //currentTimestamp + updateInterval_;
|
andrewm@0
|
383 return nextScheduledTimestamp_;
|
andrewm@0
|
384 }
|
andrewm@0
|
385
|
andrewm@0
|
386 // MIDI note-on message received
|
andrewm@0
|
387 void TouchkeyControlMapping::midiNoteOnReceived(int channel, int velocity) {
|
andrewm@0
|
388 // MIDI note has gone on. Set the starting location to be most recent
|
andrewm@0
|
389 // location. It's possible there has been no touch data before this,
|
andrewm@0
|
390 // in which case lastX and lastY will hold missing values.
|
andrewm@0
|
391 midiOnsetValue_ = lastValue_;
|
andrewm@0
|
392 if(!missing_value<float>::isMissing(midiOnsetValue_)) {
|
andrewm@0
|
393 if(inputType_ == kTypeNoteOnsetRelative) {
|
andrewm@0
|
394 // Already have touch data. Clear the buffer here.
|
andrewm@0
|
395 // Clear buffer and start with default value for this point
|
andrewm@0
|
396 clearBuffers();
|
andrewm@0
|
397
|
andrewm@0
|
398 #ifdef DEBUG_CONTROL_MAPPING
|
andrewm@0
|
399 std::cout << "MIDI on: starting at (" << midiOnsetValue_ << ")\n";
|
andrewm@0
|
400 #endif
|
andrewm@0
|
401 }
|
andrewm@0
|
402 }
|
andrewm@0
|
403 else {
|
andrewm@0
|
404 #ifdef DEBUG_CONTROL_MAPPING
|
andrewm@0
|
405 std::cout << "MIDI on but no touch\n";
|
andrewm@0
|
406 #endif
|
andrewm@0
|
407 }
|
andrewm@0
|
408 }
|
andrewm@0
|
409
|
andrewm@0
|
410 // MIDI note-off message received
|
andrewm@0
|
411 void TouchkeyControlMapping::midiNoteOffReceived(int channel) {
|
andrewm@0
|
412 if(controlIsEngaged_) {
|
andrewm@0
|
413 // TODO: should anything happen here?
|
andrewm@0
|
414 }
|
andrewm@0
|
415 }
|
andrewm@0
|
416
|
andrewm@0
|
417 // Reset variables involved in detecting a pitch bend gesture
|
andrewm@0
|
418 void TouchkeyControlMapping::resetDetectionState() {
|
andrewm@0
|
419 controlIsEngaged_ = false;
|
andrewm@0
|
420 controlEngageLocation_ = missing_value<float>::missing();
|
andrewm@0
|
421 idsOfCurrentTouches_[0] = idsOfCurrentTouches_[1] = idsOfCurrentTouches_[2] = -1;
|
andrewm@0
|
422 }
|
andrewm@0
|
423
|
andrewm@0
|
424 // Clear the buffers that hold distance measurements
|
andrewm@0
|
425 void TouchkeyControlMapping::clearBuffers() {
|
andrewm@0
|
426 rawValues_.clear();
|
andrewm@0
|
427 rawValues_.insert(0.0, lastTimestamp_);
|
andrewm@0
|
428 lastProcessedIndex_ = 0;
|
andrewm@0
|
429 }
|
andrewm@0
|
430
|
andrewm@0
|
431 // Return the current parameter value depending on which one we are listening to
|
andrewm@0
|
432 float TouchkeyControlMapping::getValue(const KeyTouchFrame& frame) {
|
andrewm@0
|
433 if(inputParameter_ == kInputParameterXPosition)
|
andrewm@0
|
434 return frame.locH;
|
andrewm@0
|
435 /*else if(inputParameter_ == kInputParameter2FingerMean ||
|
andrewm@0
|
436 inputParameter_ == kInputParameter2FingerDistance) {
|
andrewm@0
|
437 if(frame.count < 2)
|
andrewm@0
|
438 return missing_value<float>::missing();
|
andrewm@0
|
439 if(frame.count == 3 && ignoresThreeFingers_)
|
andrewm@0
|
440 return missing_value<float>::missing();
|
andrewm@0
|
441
|
andrewm@0
|
442 bool foundCurrentTouch = false;
|
andrewm@0
|
443 float currentValue;
|
andrewm@0
|
444
|
andrewm@0
|
445 // Look for the touches we were tracking last frame
|
andrewm@0
|
446 if(idsOfCurrentTouches_[0] >= 0) {
|
andrewm@0
|
447 for(int i = 0; i < frame.count; i++) {
|
andrewm@0
|
448 if(frame.ids[i] == idsOfCurrentTouches_[0]) {
|
andrewm@0
|
449 if(inputParameter_ == kInputParameterYPosition)
|
andrewm@0
|
450 currentValue = frame.locs[i];
|
andrewm@0
|
451 else // kInputParameterTouchSize
|
andrewm@0
|
452 currentValue = frame.sizes[i];
|
andrewm@0
|
453 foundCurrentTouch = true;
|
andrewm@0
|
454 break;
|
andrewm@0
|
455 }
|
andrewm@0
|
456 }
|
andrewm@0
|
457 }
|
andrewm@0
|
458
|
andrewm@0
|
459 if(!foundCurrentTouch) {
|
andrewm@0
|
460 // Assign a new touch to be tracked
|
andrewm@0
|
461 int lowestRemainingId = INT_MAX;
|
andrewm@0
|
462 int lowestIndex = 0;
|
andrewm@0
|
463
|
andrewm@0
|
464 for(int i = 0; i < frame.count; i++) {
|
andrewm@0
|
465 if(frame.ids[i] < lowestRemainingId) {
|
andrewm@0
|
466 lowestRemainingId = frame.ids[i];
|
andrewm@0
|
467 lowestIndex = i;
|
andrewm@0
|
468 }
|
andrewm@0
|
469 }
|
andrewm@0
|
470
|
andrewm@0
|
471 idsOfCurrentTouches_[0] = lowestRemainingId;
|
andrewm@0
|
472 if(inputParameter_ == kInputParameterYPosition)
|
andrewm@0
|
473 currentValue = frame.locs[lowestIndex];
|
andrewm@0
|
474 else if(inputParameter_ == kInputParameterTouchSize)
|
andrewm@0
|
475 currentValue = frame.sizes[lowestIndex];
|
andrewm@0
|
476 else // Shouldn't happen
|
andrewm@0
|
477 currentValue = missing_value<float>::missing();
|
andrewm@0
|
478
|
andrewm@0
|
479 #ifdef DEBUG_CONTROL_MAPPING
|
andrewm@0
|
480 std::cout << "Previous touch stopped; now ID " << idsOfCurrentTouches_[0] << " at (" << currentValue << ")\n";
|
andrewm@0
|
481 #endif
|
andrewm@0
|
482 }
|
andrewm@0
|
483
|
andrewm@0
|
484 }*/
|
andrewm@0
|
485 else {
|
andrewm@0
|
486 if(frame.count == 0)
|
andrewm@0
|
487 return missing_value<float>::missing();
|
andrewm@0
|
488 if((inputParameter_ == kInputParameter2FingerMean ||
|
andrewm@0
|
489 inputParameter_ == kInputParameter2FingerDistance) &&
|
andrewm@0
|
490 frame.count < 2)
|
andrewm@0
|
491 return missing_value<float>::missing();
|
andrewm@0
|
492 if(frame.count == 2 && ignoresTwoFingers_)
|
andrewm@0
|
493 return missing_value<float>::missing();
|
andrewm@0
|
494 if(frame.count == 3 && ignoresThreeFingers_)
|
andrewm@0
|
495 return missing_value<float>::missing();
|
andrewm@0
|
496 /*
|
andrewm@0
|
497 // The other values are dependent on individual touches
|
andrewm@0
|
498 bool foundCurrentTouch = false;
|
andrewm@0
|
499 float currentValue;
|
andrewm@0
|
500
|
andrewm@0
|
501 // Look for the touch we were tracking last frame
|
andrewm@0
|
502 if(idsOfCurrentTouches_[0] >= 0) {
|
andrewm@0
|
503 for(int i = 0; i < frame.count; i++) {
|
andrewm@0
|
504 if(frame.ids[i] == idsOfCurrentTouches_[0]) {
|
andrewm@0
|
505 if(inputParameter_ == kInputParameterYPosition)
|
andrewm@0
|
506 currentValue = frame.locs[i];
|
andrewm@0
|
507 else // kInputParameterTouchSize
|
andrewm@0
|
508 currentValue = frame.sizes[i];
|
andrewm@0
|
509 foundCurrentTouch = true;
|
andrewm@0
|
510 break;
|
andrewm@0
|
511 }
|
andrewm@0
|
512 }
|
andrewm@0
|
513 }
|
andrewm@0
|
514
|
andrewm@0
|
515 if(!foundCurrentTouch) {
|
andrewm@0
|
516 // Assign a new touch to be tracked
|
andrewm@0
|
517 int lowestRemainingId = INT_MAX;
|
andrewm@0
|
518 int lowestIndex = 0;
|
andrewm@0
|
519
|
andrewm@0
|
520 for(int i = 0; i < frame.count; i++) {
|
andrewm@0
|
521 if(frame.ids[i] < lowestRemainingId) {
|
andrewm@0
|
522 lowestRemainingId = frame.ids[i];
|
andrewm@0
|
523 lowestIndex = i;
|
andrewm@0
|
524 }
|
andrewm@0
|
525 }
|
andrewm@0
|
526
|
andrewm@0
|
527 idsOfCurrentTouches_[0] = lowestRemainingId;
|
andrewm@0
|
528 if(inputParameter_ == kInputParameterYPosition)
|
andrewm@0
|
529 currentValue = frame.locs[lowestIndex];
|
andrewm@0
|
530 else if(inputParameter_ == kInputParameterTouchSize)
|
andrewm@0
|
531 currentValue = frame.sizes[lowestIndex];
|
andrewm@0
|
532 else // Shouldn't happen
|
andrewm@0
|
533 currentValue = missing_value<float>::missing();
|
andrewm@0
|
534
|
andrewm@0
|
535 #ifdef DEBUG_CONTROL_MAPPING
|
andrewm@0
|
536 std::cout << "Previous touch stopped; now ID " << idsOfCurrentTouches_[0] << " at (" << currentValue << ")\n";
|
andrewm@0
|
537 #endif
|
andrewm@0
|
538 }*/
|
andrewm@0
|
539
|
andrewm@0
|
540 float currentValue = 0;
|
andrewm@0
|
541
|
andrewm@0
|
542 int idWithinFrame0 = locateTouchId(frame, 0);
|
andrewm@0
|
543 if(idWithinFrame0 < 0) {
|
andrewm@0
|
544 // Touch ID not found, start a new value
|
andrewm@0
|
545 idsOfCurrentTouches_[0] = lowestUnassignedTouch(frame, &idWithinFrame0);
|
andrewm@0
|
546 #ifdef DEBUG_CONTROL_MAPPING
|
andrewm@0
|
547 std::cout << "Previous touch stopped (0); now ID " << idsOfCurrentTouches_[0] << endl;
|
andrewm@0
|
548 #endif
|
andrewm@0
|
549 if(idsOfCurrentTouches_[0] < 0) {
|
andrewm@0
|
550 cout << "BUG: didn't find any unassigned touch!\n";
|
andrewm@0
|
551 }
|
andrewm@0
|
552 }
|
andrewm@0
|
553
|
andrewm@0
|
554 if(inputParameter_ == kInputParameterYPosition)
|
andrewm@0
|
555 currentValue = frame.locs[idWithinFrame0];
|
andrewm@0
|
556 else if(inputParameter_ == kInputParameterTouchSize) // kInputParameterTouchSize
|
andrewm@0
|
557 currentValue = frame.sizes[idWithinFrame0];
|
andrewm@0
|
558 else if(inputParameter_ == kInputParameter2FingerMean ||
|
andrewm@0
|
559 inputParameter_ == kInputParameter2FingerDistance) {
|
andrewm@0
|
560 int idWithinFrame1 = locateTouchId(frame, 1);
|
andrewm@0
|
561 if(idWithinFrame1 < 0) {
|
andrewm@0
|
562 // Touch ID not found, start a new value
|
andrewm@0
|
563 idsOfCurrentTouches_[1] = lowestUnassignedTouch(frame, &idWithinFrame1);
|
andrewm@0
|
564 #ifdef DEBUG_CONTROL_MAPPING
|
andrewm@0
|
565 std::cout << "Previous touch stopped (1); now ID " << idsOfCurrentTouches_[1] << endl;
|
andrewm@0
|
566 #endif
|
andrewm@0
|
567 if(idsOfCurrentTouches_[1] < 0) {
|
andrewm@0
|
568 cout << "BUG: didn't find any unassigned touch for second finger!\n";
|
andrewm@0
|
569 }
|
andrewm@0
|
570 }
|
andrewm@0
|
571
|
andrewm@0
|
572 if(inputParameter_ == kInputParameter2FingerMean)
|
andrewm@0
|
573 currentValue = (frame.locs[idWithinFrame0] + frame.locs[idWithinFrame1]) * 0.5;
|
andrewm@0
|
574 else
|
andrewm@0
|
575 currentValue = fabsf(frame.locs[idWithinFrame1] - frame.locs[idWithinFrame0]);
|
andrewm@0
|
576 }
|
andrewm@0
|
577
|
andrewm@0
|
578 return currentValue;
|
andrewm@0
|
579 }
|
andrewm@0
|
580 }
|
andrewm@0
|
581
|
andrewm@0
|
582 // Look for a touch index in the frame matching the given value of idsOfCurrentTouches[index]
|
andrewm@0
|
583 // Returns -1 if not found
|
andrewm@0
|
584 int TouchkeyControlMapping::locateTouchId(KeyTouchFrame const& frame, int index) {
|
andrewm@0
|
585 if(idsOfCurrentTouches_[index] < 0)
|
andrewm@0
|
586 return -1;
|
andrewm@0
|
587
|
andrewm@0
|
588 for(int i = 0; i < frame.count; i++) {
|
andrewm@0
|
589 if(frame.ids[i] == idsOfCurrentTouches_[index]) {
|
andrewm@0
|
590 return i;
|
andrewm@0
|
591 }
|
andrewm@0
|
592 }
|
andrewm@0
|
593
|
andrewm@0
|
594 return -1;
|
andrewm@0
|
595 }
|
andrewm@0
|
596
|
andrewm@0
|
597 // Locates the lowest touch ID that is not assigned to a current touch
|
andrewm@0
|
598 // Returns -1 if no unassigned touches were found
|
andrewm@0
|
599 int TouchkeyControlMapping::lowestUnassignedTouch(KeyTouchFrame const& frame, int *indexWithinFrame) {
|
andrewm@0
|
600 int lowestRemainingId = INT_MAX;
|
andrewm@0
|
601 int lowestIndex = -1;
|
andrewm@0
|
602
|
andrewm@0
|
603 for(int i = 0; i < frame.count; i++) {
|
andrewm@0
|
604 if(frame.ids[i] < lowestRemainingId) {
|
andrewm@0
|
605 bool alreadyAssigned = false;
|
andrewm@0
|
606 for(int j = 0; j < 3; j++) {
|
andrewm@0
|
607 if(idsOfCurrentTouches_[j] == frame.ids[i])
|
andrewm@0
|
608 alreadyAssigned = true;
|
andrewm@0
|
609 }
|
andrewm@0
|
610
|
andrewm@0
|
611 if(!alreadyAssigned) {
|
andrewm@0
|
612 lowestRemainingId = frame.ids[i];
|
andrewm@0
|
613 lowestIndex = i;
|
andrewm@0
|
614
|
andrewm@0
|
615 }
|
andrewm@0
|
616 }
|
andrewm@0
|
617 }
|
andrewm@0
|
618
|
andrewm@0
|
619 if(indexWithinFrame != 0)
|
andrewm@0
|
620 *indexWithinFrame = lowestIndex;
|
andrewm@0
|
621 return lowestRemainingId;
|
andrewm@0
|
622 }
|
andrewm@0
|
623
|
andrewm@0
|
624 // Send the pitch bend message of a given number of a semitones. Send by OSC,
|
andrewm@0
|
625 // which can be mapped to MIDI CC externally
|
andrewm@0
|
626 void TouchkeyControlMapping::sendControlMessage(float value, bool force) {
|
andrewm@0
|
627 if(force || !suspended_) {
|
andrewm@0
|
628 #ifdef DEBUG_CONTROL_MAPPING
|
andrewm@0
|
629 std::cout << "TouchkeyControlMapping: sending " << value << " for note " << noteNumber_ << std::endl;
|
andrewm@0
|
630 #endif
|
andrewm@0
|
631 keyboard_.sendMessage(controlName_.c_str(), "if", noteNumber_, value, LO_ARGS_END);
|
andrewm@0
|
632 }
|
andrewm@0
|
633 }
|
andrewm@0
|
634
|