Package _Framework :: Module InputControlElement
[hide private]
[frames] | no frames]

Source Code for Module _Framework.InputControlElement

  1  #Embedded file name: /Users/versonator/Hudson/live/Projects/AppLive/Resources/MIDI Remote Scripts/_Framework/InputControlElement.py 
  2  from __future__ import with_statement 
  3  import contextlib 
  4  from Dependency import depends 
  5  from SubjectSlot import SubjectEvent 
  6  from Signal import Signal 
  7  from NotifyingControlElement import NotifyingControlElement 
  8  from Util import in_range, const, nop 
  9  from Debug import debug_print 
 10  from Disconnectable import Disconnectable 
 11  import Task 
 12  MIDI_NOTE_TYPE = 0 
 13  MIDI_CC_TYPE = 1 
 14  MIDI_PB_TYPE = 2 
 15  MIDI_SYSEX_TYPE = 3 
 16  MIDI_INVALID_TYPE = 4 
 17  MIDI_MSG_TYPES = (MIDI_NOTE_TYPE, 
 18   MIDI_CC_TYPE, 
 19   MIDI_PB_TYPE, 
 20   MIDI_SYSEX_TYPE, 
 21   MIDI_INVALID_TYPE) 
 22  MIDI_NOTE_ON_STATUS = 144 
 23  MIDI_NOTE_OFF_STATUS = 128 
 24  MIDI_CC_STATUS = 176 
 25  MIDI_PB_STATUS = 224 
26 27 -class ParameterSlot(Disconnectable):
28 """ 29 Maintains the connection between a parameter and 30 InputControlElement. Keeps the invariant that whenever both 31 parameter and control are set, the parameter is connected to the 32 control. Whenever any of them is changed, they are disconnected 33 and reconnected to the new one, in a similar fashion to a 34 SubjectSlot. 35 """ 36 _parameter = None 37 _control = None 38
39 - def __init__(self, parameter = None, control = None, *a, **k):
40 super(ParameterSlot, self).__init__(*a, **k) 41 self.parameter = parameter 42 self.control = control
43
44 - def _get_control(self):
45 return self._control
46
47 - def _set_control(self, control):
48 if control != self._control: 49 self.soft_disconnect() 50 self._control = control 51 self.connect()
52 53 control = property(_get_control, _set_control) 54
55 - def _get_parameter(self):
56 return self._parameter
57
58 - def _set_parameter(self, parameter):
59 if parameter != self._parameter: 60 self.soft_disconnect() 61 self._parameter = parameter 62 self.connect()
63 64 parameter = property(_get_parameter, _set_parameter) 65
66 - def connect(self):
67 if self._control != None and self._parameter != None: 68 self._control.connect_to(self._parameter)
69
70 - def soft_disconnect(self):
71 if self._control != None and self._parameter != None: 72 self._control.release_parameter()
73
74 - def disconnect(self):
75 self.parameter = None 76 self.control = None 77 super(ParameterSlot, self).disconnect()
78
79 80 -class InputSignal(Signal):
81 """ 82 Special signal type that makes sure that interaction with input 83 works properly. Special input control elements that define 84 value-dependent properties should use this kind of signal. 85 """ 86
87 - def __init__(self, sender = None, *a, **k):
88 super(InputSignal, self).__init__(sender=sender, *a, **k) 89 self._input_control = sender
90 91 @contextlib.contextmanager
92 - def _listeners_update(self):
93 try: 94 control = self._input_control 95 old_count = self.count 96 old_wants_forwarding = control.script_wants_forwarding() 97 yield 98 finally: 99 diff_count = self.count - old_count 100 control._input_signal_listener_count += diff_count 101 if old_wants_forwarding != control.script_wants_forwarding(): 102 self._input_control._request_rebuild()
103
104 - def connect(self, *a, **k):
105 with self._listeners_update(): 106 super(InputSignal, self).connect(*a, **k)
107
108 - def disconnect(self, *a, **k):
109 with self._listeners_update(): 110 super(InputSignal, self).disconnect(*a, **k)
111
112 - def disconnect_all(self, *a, **k):
113 with self._listeners_update(): 114 super(InputSignal, self).disconnect_all(*a, **k)
115
116 117 -class InputControlElement(NotifyingControlElement):
118 """ 119 Base class for all classes representing control elements on a controller 120 """ 121 __subject_events__ = (SubjectEvent(name='value', signal=InputSignal, override=True),) 122 _input_signal_listener_count = 0 123 num_delayed_messages = 1 124 send_depends_on_forwarding = True 125 126 @depends(request_rebuild_midi_map=const(nop))
127 - def __init__(self, msg_type = None, channel = None, identifier = None, sysex_identifier = None, request_rebuild_midi_map = None, *a, **k):
128 raise msg_type in MIDI_MSG_TYPES or AssertionError 129 raise in_range(channel, 0, 16) or channel is None or AssertionError 130 raise in_range(identifier, 0, 128) or identifier is None or AssertionError 131 raise msg_type != MIDI_SYSEX_TYPE or channel == None or AssertionError 132 raise msg_type != MIDI_SYSEX_TYPE or identifier == None or AssertionError 133 raise msg_type == MIDI_SYSEX_TYPE or sysex_identifier == None or AssertionError 134 super(InputControlElement, self).__init__(*a, **k) 135 self._request_rebuild = request_rebuild_midi_map 136 self._msg_type = msg_type 137 self._msg_channel = channel 138 self._msg_identifier = identifier 139 self._msg_sysex_identifier = sysex_identifier 140 self._original_channel = channel 141 self._original_identifier = identifier 142 self._needs_takeover = True 143 self._is_mapped = True 144 self._is_being_forwarded = True 145 self._delayed_messages = [] 146 self._force_next_send = False 147 self._mapping_feedback_delay = 0 148 self._mapping_sensitivity = 1.0 149 self._send_delayed_messages_task = self._tasks.add(Task.run(self._send_delayed_messages)) 150 self._send_delayed_messages_task.kill() 151 self._parameter_to_map_to = None 152 self._in_parameter_gesture = False 153 self._last_sent_message = None 154 self._report_input = False 155 self._report_output = False
156
157 - def message_type(self):
158 return self._msg_type
159
160 - def message_channel(self):
161 return self._msg_channel
162
163 - def message_identifier(self):
164 return self._msg_identifier
165
166 - def message_sysex_identifier(self):
167 return self._msg_sysex_identifier
168
169 - def message_map_mode(self):
170 raise NotImplementedError
171
172 - def _get_mapping_sensitivity(self):
173 return self._mapping_sensitivity
174
175 - def _set_mapping_sensitivity(self, sensitivity):
176 self._mapping_sensitivity = sensitivity
177 178 mapping_sensitivity = property(_get_mapping_sensitivity, _set_mapping_sensitivity) 179
180 - def force_next_send(self):
181 """ 182 Enforces sending the next value regardless of wether the 183 control is mapped to the script. 184 """ 185 self._force_next_send = True
186
187 - def set_channel(self, channel):
188 if not self._msg_type != MIDI_SYSEX_TYPE: 189 raise AssertionError 190 raise in_range(channel, 0, 16) or channel == None or AssertionError 191 self._msg_channel = self._msg_channel != channel and channel 192 self._request_rebuild()
193
194 - def set_identifier(self, identifier):
195 if not self._msg_type != MIDI_SYSEX_TYPE: 196 raise AssertionError 197 raise in_range(identifier, 0, 128) or identifier == None or AssertionError 198 self._msg_identifier = self._msg_identifier != identifier and identifier 199 self._request_rebuild()
200
201 - def set_needs_takeover(self, needs_takeover):
202 raise self.message_type() != MIDI_NOTE_TYPE or AssertionError 203 self._needs_takeover = needs_takeover
204
205 - def set_feedback_delay(self, delay):
206 raise delay >= -1 or AssertionError 207 self._mapping_feedback_delay = delay
208
209 - def needs_takeover(self):
210 raise self.message_type() != MIDI_NOTE_TYPE or AssertionError 211 return self._needs_takeover
212
213 - def use_default_message(self):
214 if (self._msg_channel, self._msg_identifier) != (self._original_channel, self._original_identifier): 215 self._msg_channel = self._original_channel 216 self._msg_identifier = self._original_identifier 217 self._request_rebuild()
218
219 - def _mapping_feedback_values(self):
220 value_map = tuple() 221 if self._mapping_feedback_delay != 0: 222 if self._msg_type != MIDI_PB_TYPE: 223 value_map = tuple(range(128)) 224 else: 225 value_pairs = [] 226 for value in xrange(16384): 227 value_pairs.append((value >> 7 & 127, value & 127)) 228 229 value_map = tuple(value_pairs) 230 return value_map
231
232 - def install_connections(self, install_translation, install_mapping, install_forwarding):
233 self._send_delayed_messages_task.kill() 234 self._is_mapped = False 235 self._is_being_forwarded = False 236 if self._msg_channel != self._original_channel or self._msg_identifier != self._original_identifier: 237 install_translation(self._msg_type, self._original_identifier, self._original_channel, self._msg_identifier, self._msg_channel) 238 if self._parameter_to_map_to != None: 239 self._is_mapped = install_mapping(self, self._parameter_to_map_to, self._mapping_feedback_delay, self._mapping_feedback_values()) 240 if self.script_wants_forwarding(): 241 self._is_being_forwarded = install_forwarding(self) 242 if self._is_being_forwarded and self.send_depends_on_forwarding: 243 self._send_delayed_messages_task.restart()
244
245 - def script_wants_forwarding(self):
246 """ 247 Returns wether the script wants to receive receive the values, 248 otherwise, the control will be mapped to the track. 249 250 Subclasses that overload this should _request_rebuild() 251 whenever the property changes. 252 """ 253 return self._input_signal_listener_count > 0 or self._report_input
254
255 - def begin_gesture(self):
256 """ 257 Begins a modification on the input control element, 258 meaning that we should consider the next flow of input data as 259 a consistent gesture from the user. 260 """ 261 if self._parameter_to_map_to and not self._in_parameter_gesture: 262 self._in_parameter_gesture = True 263 self._parameter_to_map_to.begin_gesture()
264
265 - def end_gesture(self):
266 """ 267 Ends a modification of the input control element. See 268 begin_gesture. 269 """ 270 if self._parameter_to_map_to and self._in_parameter_gesture: 271 self._in_parameter_gesture = False 272 self._parameter_to_map_to.end_gesture()
273
274 - def connect_to(self, parameter):
275 """ parameter is a Live.Device.DeviceParameter """ 276 if not parameter != None: 277 raise AssertionError 278 self._parameter_to_map_to = self._parameter_to_map_to != parameter and parameter 279 self._request_rebuild()
280
281 - def release_parameter(self):
282 if self._parameter_to_map_to != None: 283 self.end_gesture() 284 self._parameter_to_map_to = None 285 self._request_rebuild()
286
287 - def mapped_parameter(self):
288 return self._parameter_to_map_to
289
290 - def _status_byte(self, channel):
291 status_byte = channel 292 if self._msg_type == MIDI_NOTE_TYPE: 293 status_byte += MIDI_NOTE_ON_STATUS 294 elif self._msg_type == MIDI_CC_TYPE: 295 status_byte += MIDI_CC_STATUS 296 elif self._msg_type == MIDI_PB_TYPE: 297 status_byte += MIDI_PB_STATUS 298 else: 299 raise NotImplementedError 300 return status_byte
301
302 - def identifier_bytes(self):
303 """ 304 Returns a list with all the MIDI message prefixes that 305 identify this control element. 306 """ 307 if self._msg_type == MIDI_PB_TYPE: 308 return ((self._status_byte(self._msg_channel),),) 309 elif self._msg_type == MIDI_SYSEX_TYPE: 310 return (self.message_sysex_identifier(),) 311 elif self._msg_type == MIDI_NOTE_TYPE: 312 return ((self._status_byte(self._msg_channel), self.message_identifier()), (self._status_byte(self._msg_channel) - 16, self.message_identifier())) 313 else: 314 return ((self._status_byte(self._msg_channel), self.message_identifier()),)
315
316 - def _send_delayed_messages(self):
317 self.clear_send_cache() 318 for value, channel in self._delayed_messages: 319 self._do_send_value(value, channel=channel) 320 321 self._delayed_messages[:] = []
322
323 - def send_value(self, value, force_send = False, channel = None):
324 value = int(value) 325 self._verify_value(value) 326 if force_send or self._force_next_send: 327 self._do_send_value(value, channel) 328 elif self.send_depends_on_forwarding and not self._is_being_forwarded or self._send_delayed_messages_task.is_running: 329 first = 1 - self.num_delayed_messages 330 self._delayed_messages = self._delayed_messages[first:] + [(value, channel)] 331 elif (value, channel) != self._last_sent_message: 332 self._do_send_value(value, channel) 333 self._force_next_send = False
334
335 - def _do_send_value(self, value, channel = None):
336 data_byte1 = self._original_identifier 337 data_byte2 = value 338 status_byte = self._status_byte(channel or self._original_channel) 339 if self._msg_type == MIDI_PB_TYPE: 340 data_byte1 = value & 127 341 data_byte2 = value >> 7 & 127 342 if self.send_midi((status_byte, data_byte1, data_byte2)): 343 self._last_sent_message = (value, channel) 344 if self._report_output: 345 is_input = True 346 self._report_value(value, not is_input) 347 self._delayed_value_to_send = None 348 self._delayed_channel = None
349
350 - def clear_send_cache(self):
351 self._last_sent_message = None
352
353 - def reset(self):
354 """ Send 0 to reset motorized faders and turn off LEDs """ 355 self.send_value(0)
356
357 - def receive_value(self, value):
358 self._verify_value(value) 359 self._last_sent_message = None 360 self.notify_value(value) 361 if self._report_input: 362 is_input = True 363 self._report_value(value, is_input)
364
365 - def set_report_values(self, report_input, report_output):
366 """ 367 Set boolean values report_input and report_output enabling 368 debug information. 369 """ 370 self._report_input = report_input 371 self._report_output = report_output
372
373 - def _verify_value(self, value):
374 upper_bound = self._msg_type < MIDI_SYSEX_TYPE and (16384 if self._msg_type == MIDI_PB_TYPE else 128) 375 if not in_range(value, 0, upper_bound): 376 raise AssertionError
377
378 - def _report_value(self, value, is_input):
379 self._verify_value(value) 380 message = str(self.__class__.__name__) + ' (' 381 if self._msg_type == MIDI_NOTE_TYPE: 382 message += 'Note ' + str(self._msg_identifier) + ', ' 383 elif self._msg_type == MIDI_CC_TYPE: 384 message += 'CC ' + str(self._msg_identifier) + ', ' 385 else: 386 message += 'PB ' 387 message += 'Chan. ' + str(self._msg_channel) 388 message += ') ' 389 message += 'received value ' if is_input else 'sent value ' 390 message += str(value) 391 debug_print(message)
392 393 @property
394 - def _last_sent_value(self):
395 return self._last_sent_message[0] if self._last_sent_message else -1
396