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