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 } |