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 MIDIKeyPositionMapping.cpp: handles generating MIDI data out of continuous
|
andrewm@0
|
21 key position.
|
andrewm@0
|
22 */
|
andrewm@0
|
23
|
andrewm@0
|
24 #include "MIDIKeyPositionMapping.h"
|
andrewm@0
|
25 #include "../TouchKeys/MidiOutputController.h"
|
andrewm@0
|
26
|
andrewm@0
|
27 // Class constants
|
andrewm@0
|
28 const int MIDIKeyPositionMapping::kDefaultMIDIChannel = 0;
|
andrewm@0
|
29 const float MIDIKeyPositionMapping::kDefaultAftertouchScaler = 127.0 / 0.03; // Default aftertouch sensitivity: MIDI 127 = 0.03
|
andrewm@0
|
30 const float MIDIKeyPositionMapping::kMinimumAftertouchPosition = 0.99; // Position at which aftertouch messages start
|
andrewm@0
|
31 const float MIDIKeyPositionMapping::kDefaultPercussivenessScaler = 1.0 / 300.0; // Default scaler from percussiveness feature to MIDI
|
andrewm@0
|
32 const key_velocity MIDIKeyPositionMapping::kPianoKeyVelocityForMaxMIDI = scale_key_velocity(40.0); // Press velocity for MIDI 127
|
andrewm@0
|
33 const key_velocity MIDIKeyPositionMapping::kPianoKeyReleaseVelocityForMaxMIDI = scale_key_velocity(-50.0); // Release velocity for MIDI 127
|
andrewm@0
|
34
|
andrewm@0
|
35
|
andrewm@0
|
36 // Main constructor takes references/pointers from objects which keep track
|
andrewm@0
|
37 // of touch location, continuous key position and the state detected from that
|
andrewm@0
|
38 // position. The PianoKeyboard object is strictly required as it gives access to
|
andrewm@0
|
39 // Scheduler and OSC methods. The others are optional since any given system may
|
andrewm@0
|
40 // contain only one of continuous key position or touch sensitivity
|
andrewm@0
|
41 MIDIKeyPositionMapping::MIDIKeyPositionMapping(PianoKeyboard &keyboard, MappingFactory *factory, int noteNumber, Node<KeyTouchFrame>* touchBuffer,
|
andrewm@0
|
42 Node<key_position>* positionBuffer, KeyPositionTracker* positionTracker)
|
andrewm@0
|
43 : Mapping(keyboard, factory, noteNumber, touchBuffer, positionBuffer, positionTracker), noteIsOn_(false),
|
andrewm@0
|
44 midiChannel_(kDefaultMIDIChannel), lastAftertouchValue_(0), midiPercussivenessChannel_(-1)
|
andrewm@0
|
45 {
|
andrewm@0
|
46 setAftertouchSensitivity(1.0);
|
andrewm@0
|
47 }
|
andrewm@0
|
48
|
andrewm@0
|
49 // Copy constructor
|
andrewm@0
|
50 MIDIKeyPositionMapping::MIDIKeyPositionMapping(MIDIKeyPositionMapping const& obj)
|
andrewm@0
|
51 : Mapping(obj), noteIsOn_(obj.noteIsOn_), aftertouchScaler_(obj.aftertouchScaler_),
|
andrewm@0
|
52 midiChannel_(obj.midiChannel_), lastAftertouchValue_(obj.lastAftertouchValue_),
|
andrewm@0
|
53 midiPercussivenessChannel_(obj.midiPercussivenessChannel_)
|
andrewm@0
|
54 {
|
andrewm@0
|
55
|
andrewm@0
|
56 }
|
andrewm@0
|
57
|
andrewm@0
|
58 MIDIKeyPositionMapping::~MIDIKeyPositionMapping() {
|
andrewm@0
|
59 try {
|
andrewm@0
|
60 disengage();
|
andrewm@0
|
61 }
|
andrewm@0
|
62 catch(...) {
|
andrewm@0
|
63 std::cerr << "~MIDIKeyPositionMapping(): exception during disengage()\n";
|
andrewm@0
|
64 }
|
andrewm@0
|
65 }
|
andrewm@0
|
66
|
andrewm@0
|
67 // Turn off mapping of data. Remove our callback from the scheduler
|
andrewm@0
|
68 void MIDIKeyPositionMapping::disengage() {
|
andrewm@0
|
69 Mapping::disengage();
|
andrewm@0
|
70 if(noteIsOn_) {
|
andrewm@0
|
71 generateMidiNoteOff();
|
andrewm@0
|
72 }
|
andrewm@0
|
73 noteIsOn_ = false;
|
andrewm@0
|
74 }
|
andrewm@0
|
75
|
andrewm@0
|
76 // Reset state back to defaults
|
andrewm@0
|
77 void MIDIKeyPositionMapping::reset() {
|
andrewm@0
|
78 Mapping::reset();
|
andrewm@0
|
79 noteIsOn_ = false;
|
andrewm@0
|
80 }
|
andrewm@0
|
81
|
andrewm@0
|
82 // Set the aftertouch sensitivity on continuous key position
|
andrewm@0
|
83 // 0 means no aftertouch, 1 means default sensitivity, upward
|
andrewm@0
|
84 // from there
|
andrewm@0
|
85 void MIDIKeyPositionMapping::setAftertouchSensitivity(float sensitivity) {
|
andrewm@0
|
86 if(sensitivity <= 0)
|
andrewm@0
|
87 aftertouchScaler_ = 0;
|
andrewm@0
|
88 else
|
andrewm@0
|
89 aftertouchScaler_ = kDefaultAftertouchScaler * sensitivity;
|
andrewm@0
|
90 }
|
andrewm@0
|
91
|
andrewm@0
|
92 // Trigger method. This receives updates from the TouchKey data or from state changes in
|
andrewm@0
|
93 // the continuous key position (KeyPositionTracker). It will potentially change the scheduled
|
andrewm@0
|
94 // behavior of future mapping calls, but the actual OSC messages should be transmitted in a different
|
andrewm@0
|
95 // thread.
|
andrewm@0
|
96 void MIDIKeyPositionMapping::triggerReceived(TriggerSource* who, timestamp_type timestamp) {
|
andrewm@0
|
97 if(who == 0)
|
andrewm@0
|
98 return;
|
andrewm@0
|
99 if(who == positionTracker_) {
|
andrewm@0
|
100 if(!positionTracker_->empty()) {
|
andrewm@0
|
101 KeyPositionTrackerNotification notification = positionTracker_->latest();
|
andrewm@0
|
102
|
andrewm@0
|
103 // New message from the key position tracker. Might be time to start or end MIDI note.
|
andrewm@0
|
104 if(notification.type == KeyPositionTrackerNotification::kNotificationTypeFeatureAvailableVelocity && !noteIsOn_) {
|
andrewm@0
|
105 cout << "Key " << noteNumber_ << " velocity available\n";
|
andrewm@0
|
106 generateMidiNoteOn();
|
andrewm@0
|
107 noteIsOn_ = true;
|
andrewm@0
|
108 }
|
andrewm@0
|
109 else if(notification.type == KeyPositionTrackerNotification::kNotificationTypeFeatureAvailableReleaseVelocity && noteIsOn_) {
|
andrewm@0
|
110 cout << "Key " << noteNumber_ << " release velocity available\n";
|
andrewm@0
|
111 generateMidiNoteOff();
|
andrewm@0
|
112 noteIsOn_ = false;
|
andrewm@0
|
113 }
|
andrewm@0
|
114 else if(notification.type == KeyPositionTrackerNotification::kNotificationTypeFeatureAvailablePercussiveness) {
|
andrewm@0
|
115 cout << "Key " << noteNumber_ << " percussiveness available\n";
|
andrewm@0
|
116 generateMidiPercussivenessNoteOn();
|
andrewm@0
|
117 }
|
andrewm@0
|
118 }
|
andrewm@0
|
119 }
|
andrewm@0
|
120 else if(who == touchBuffer_) {
|
andrewm@0
|
121 // TODO: New touch data is available from the keyboard
|
andrewm@0
|
122 }
|
andrewm@0
|
123 }
|
andrewm@0
|
124
|
andrewm@0
|
125 // Mapping method. This actually does the real work of sending OSC data in response to the
|
andrewm@0
|
126 // latest information from the touch sensors or continuous key angle
|
andrewm@0
|
127 timestamp_type MIDIKeyPositionMapping::performMapping() {
|
andrewm@0
|
128 if(!engaged_)
|
andrewm@0
|
129 return 0;
|
andrewm@0
|
130
|
andrewm@0
|
131 timestamp_type currentTimestamp = keyboard_.schedulerCurrentTimestamp();
|
andrewm@0
|
132
|
andrewm@0
|
133 // Calculate the output features as a function of input sensor data
|
andrewm@0
|
134 if(positionBuffer_ == 0) {
|
andrewm@0
|
135 // No buffer -> all 0
|
andrewm@0
|
136 }
|
andrewm@0
|
137 else if(positionBuffer_->empty()) {
|
andrewm@0
|
138 // No samples -> all 0
|
andrewm@0
|
139 }
|
andrewm@0
|
140 else if(noteIsOn_) {
|
andrewm@0
|
141 // Generate aftertouch messages based on key position, if the note is on and
|
andrewm@0
|
142 // if the position exceeds the aftertouch threshold. Note on and note off are
|
andrewm@0
|
143 // handled directly by the trigger thread.
|
andrewm@0
|
144 key_position latestPosition = positionBuffer_->latest();
|
andrewm@0
|
145 int aftertouchValue;
|
andrewm@0
|
146
|
andrewm@0
|
147 if(latestPosition < kMinimumAftertouchPosition)
|
andrewm@0
|
148 aftertouchValue = 0;
|
andrewm@0
|
149 else {
|
andrewm@0
|
150 aftertouchValue = (int)((key_position_to_float(latestPosition) - kMinimumAftertouchPosition) * aftertouchScaler_);
|
andrewm@0
|
151 if(aftertouchValue < 0)
|
andrewm@0
|
152 aftertouchValue = 0;
|
andrewm@0
|
153 if(aftertouchValue > 127)
|
andrewm@0
|
154 aftertouchValue = 127;
|
andrewm@0
|
155 }
|
andrewm@0
|
156
|
andrewm@0
|
157 if(aftertouchValue != lastAftertouchValue_) {
|
andrewm@0
|
158 if(keyboard_.midiOutputController() != 0) {
|
andrewm@0
|
159 keyboard_.midiOutputController()->sendAftertouchPoly(0, midiChannel_, noteNumber_, aftertouchValue);
|
andrewm@0
|
160 }
|
andrewm@0
|
161 }
|
andrewm@0
|
162
|
andrewm@0
|
163 lastAftertouchValue_ = aftertouchValue;
|
andrewm@0
|
164 }
|
andrewm@0
|
165
|
andrewm@0
|
166 // Register for the next update by returning its timestamp
|
andrewm@0
|
167 nextScheduledTimestamp_ = currentTimestamp + updateInterval_;
|
andrewm@0
|
168 return nextScheduledTimestamp_;
|
andrewm@0
|
169 }
|
andrewm@0
|
170
|
andrewm@0
|
171 // Generate a MIDI Note On from continuous key data
|
andrewm@0
|
172 void MIDIKeyPositionMapping::generateMidiNoteOn() {
|
andrewm@0
|
173 if(positionTracker_ == 0)
|
andrewm@0
|
174 return;
|
andrewm@0
|
175
|
andrewm@0
|
176 std::pair<timestamp_type, key_velocity> velocityInfo = positionTracker_->pressVelocity();
|
andrewm@0
|
177
|
andrewm@0
|
178 // MIDI Velocity now available. Send a MIDI message if relevant.
|
andrewm@0
|
179 if(keyboard_.midiOutputController() != 0) {
|
andrewm@0
|
180 float midiVelocity = 0.5;
|
andrewm@0
|
181 if(!missing_value<key_velocity>::isMissing(velocityInfo.second))
|
andrewm@0
|
182 midiVelocity = (float)velocityInfo.second / (float)kPianoKeyVelocityForMaxMIDI;
|
andrewm@0
|
183 if(midiVelocity < 0.0)
|
andrewm@0
|
184 midiVelocity = 0.0;
|
andrewm@0
|
185 if(midiVelocity > 1.0)
|
andrewm@0
|
186 midiVelocity = 1.0;
|
andrewm@0
|
187 keyboard_.midiOutputController()->sendNoteOn(0, midiChannel_, noteNumber_, (unsigned char)(midiVelocity * 127.0));
|
andrewm@0
|
188 }
|
andrewm@0
|
189 }
|
andrewm@0
|
190
|
andrewm@0
|
191 // Generate a MIDI Note Off from continuous key data
|
andrewm@0
|
192 void MIDIKeyPositionMapping::generateMidiNoteOff() {
|
andrewm@0
|
193 if(positionTracker_ == 0)
|
andrewm@0
|
194 return;
|
andrewm@0
|
195
|
andrewm@0
|
196 std::pair<timestamp_type, key_velocity> velocityInfo = positionTracker_->releaseVelocity();
|
andrewm@0
|
197
|
andrewm@0
|
198 // MIDI release velocity now available. Send a MIDI message if relevant
|
andrewm@0
|
199 if(keyboard_.midiOutputController() != 0) {
|
andrewm@0
|
200 float midiReleaseVelocity = 0.5;
|
andrewm@0
|
201 if(!missing_value<key_velocity>::isMissing(velocityInfo.second))
|
andrewm@0
|
202 midiReleaseVelocity = (float)velocityInfo.second / (float)kPianoKeyReleaseVelocityForMaxMIDI;
|
andrewm@0
|
203 if(midiReleaseVelocity < 0.0)
|
andrewm@0
|
204 midiReleaseVelocity = 0.0;
|
andrewm@0
|
205 if(midiReleaseVelocity > 1.0)
|
andrewm@0
|
206 midiReleaseVelocity = 1.0;
|
andrewm@0
|
207 keyboard_.midiOutputController()->sendNoteOff(0, midiChannel_, noteNumber_, (unsigned char)(midiReleaseVelocity * 127.0));
|
andrewm@0
|
208
|
andrewm@0
|
209 // Also turn off percussiveness note if enabled
|
andrewm@0
|
210 if(midiPercussivenessChannel_ >= 0)
|
andrewm@0
|
211 keyboard_.midiOutputController()->sendNoteOff(0, midiPercussivenessChannel_, noteNumber_, (unsigned char)(midiReleaseVelocity * 127.0));
|
andrewm@0
|
212 }
|
andrewm@0
|
213
|
andrewm@0
|
214 lastAftertouchValue_ = 0;
|
andrewm@0
|
215 }
|
andrewm@0
|
216
|
andrewm@0
|
217 void MIDIKeyPositionMapping::generateMidiPercussivenessNoteOn() {
|
andrewm@0
|
218 if(positionTracker_ == 0 || midiPercussivenessChannel_ < 0)
|
andrewm@0
|
219 return;
|
andrewm@0
|
220
|
andrewm@0
|
221 KeyPositionTracker::PercussivenessFeatures features = positionTracker_->pressPercussiveness();
|
andrewm@0
|
222 std::cout << "found percussiveness value of " << features.percussiveness << std::endl;
|
andrewm@0
|
223
|
andrewm@0
|
224 // MIDI Velocity now available. Send a MIDI message if relevant.
|
andrewm@0
|
225 if(keyboard_.midiOutputController() != 0) {
|
andrewm@0
|
226 float midiPercVelocity = 0.0;
|
andrewm@0
|
227 if(!missing_value<key_velocity>::isMissing(features.percussiveness))
|
andrewm@0
|
228 midiPercVelocity = features.percussiveness * kDefaultPercussivenessScaler;
|
andrewm@0
|
229 if(midiPercVelocity < 0.0)
|
andrewm@0
|
230 midiPercVelocity = 0.0;
|
andrewm@0
|
231 if(midiPercVelocity > 1.0)
|
andrewm@0
|
232 midiPercVelocity = 1.0;
|
andrewm@0
|
233 keyboard_.midiOutputController()->sendNoteOn(0, midiPercussivenessChannel_, noteNumber_, (unsigned char)(midiPercVelocity * 127.0));
|
andrewm@0
|
234 }
|
andrewm@0
|
235 } |