├── LiveOSC Control.adg ├── LiveOSC.py ├── LiveOSCCallbacks.py ├── LiveUtils.py ├── LogServer.py ├── Logger.py ├── OSC.py ├── OSCAPI.txt ├── README.md ├── RemixNet.py ├── __init__.py ├── socket_live8.py └── struct.py /LiveOSC Control.adg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinchak/LiveOSC/04c5f2ae640b2d61dd6a6e87775a34acee8db366/LiveOSC Control.adg -------------------------------------------------------------------------------- /LiveOSC.py: -------------------------------------------------------------------------------- 1 | """ 2 | # Copyright (C) 2007 Nathan Ramella (nar@remix.net) 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 2.1 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | # 18 | # For questions regarding this module contact 19 | # Nathan Ramella or visit http://www.remix.net 20 | 21 | This script is based off the Ableton Live supplied MIDI Remote Scripts, customised 22 | for OSC request delivery and response. This script can be run without any extra 23 | Python libraries out of the box. 24 | 25 | This is the second file that is loaded, by way of being instantiated through 26 | __init__.py 27 | 28 | """ 29 | 30 | import Live 31 | import LiveOSCCallbacks 32 | import RemixNet 33 | import OSC 34 | import LiveUtils 35 | import sys 36 | from Logger import log 37 | 38 | class LiveOSC: 39 | __module__ = __name__ 40 | __doc__ = "Main class that establishes the LiveOSC Component" 41 | 42 | prlisten = {} 43 | plisten = {} 44 | dlisten = {} 45 | clisten = {} 46 | slisten = {} 47 | pplisten = {} 48 | cnlisten = {} 49 | cclisten = {} 50 | wlisten = {} 51 | llisten = {} 52 | 53 | _send_pos = {} 54 | 55 | mlisten = { "solo": {}, "mute": {}, "arm": {}, "panning": {}, "volume": {}, "sends": {}, "name": {}, "oml": {}, "omr": {} } 56 | rlisten = { "solo": {}, "mute": {}, "panning": {}, "volume": {}, "sends": {}, "name": {} } 57 | masterlisten = { "panning": {}, "volume": {}, "crossfader": {} } 58 | scenelisten = {} 59 | 60 | scene = 0 61 | track = 0 62 | 63 | def __init__(self, c_instance): 64 | self._LiveOSC__c_instance = c_instance 65 | 66 | self.basicAPI = 0 67 | self.oscEndpoint = RemixNet.OSCEndpoint() 68 | self.oscEndpoint.send('/remix/oscserver/startup', 1) 69 | 70 | log("LiveOSC initialized") 71 | 72 | # Visible tracks listener 73 | if self.song().visible_tracks_has_listener(self.refresh_state) != 1: 74 | self.song().add_visible_tracks_listener(self.refresh_state) 75 | 76 | ###################################################################### 77 | # Standard Ableton Methods 78 | 79 | def connect_script_instances(self, instanciated_scripts): 80 | """ 81 | Called by the Application as soon as all scripts are initialized. 82 | You can connect yourself to other running scripts here, as we do it 83 | connect the extension modules 84 | """ 85 | return 86 | 87 | def is_extension(self): 88 | return False 89 | 90 | def request_rebuild_midi_map(self): 91 | """ 92 | To be called from any components, as soon as their internal state changed in a 93 | way, that we do need to remap the mappings that are processed directly by the 94 | Live engine. 95 | Dont assume that the request will immediately result in a call to 96 | your build_midi_map function. For performance reasons this is only 97 | called once per GUI frame. 98 | """ 99 | return 100 | 101 | def update_display(self): 102 | """ 103 | This function is run every 100ms, so we use it to initiate our Song.current_song_time 104 | listener to allow us to process incoming OSC commands as quickly as possible under 105 | the current listener scheme. 106 | """ 107 | ###################################################### 108 | # START OSC LISTENER SETUP 109 | 110 | if self.basicAPI == 0: 111 | # By default we have set basicAPI to 0 so that we can assign it after 112 | # initialization. We try to get the current song and if we can we'll 113 | # connect our basicAPI callbacks to the listener allowing us to 114 | # respond to incoming OSC every 60ms. 115 | # 116 | # Since this method is called every 100ms regardless of the song time 117 | # changing, we use both methods for processing incoming UDP requests 118 | # so that from a resting state you can initiate play/clip triggering. 119 | 120 | try: 121 | doc = self.song() 122 | except: 123 | log('could not get song handle') 124 | return 125 | try: 126 | self.basicAPI = LiveOSCCallbacks.LiveOSCCallbacks(self._LiveOSC__c_instance, self.oscEndpoint) 127 | # Commented for stability 128 | self.time = 0 129 | doc.add_current_song_time_listener(self.current_song_time_changed) 130 | except: 131 | self.oscEndpoint.send('/remix/echo', 'setting up basicAPI failed') 132 | log('setting up basicAPI failed'); 133 | return 134 | 135 | # If our OSC server is listening, try processing incoming requests. 136 | # Any 'play' initiation will trigger the current_song_time listener 137 | # and bump updates from 100ms to 60ms. 138 | 139 | if self.oscEndpoint: 140 | try: 141 | self.oscEndpoint.processIncomingUDP() 142 | except: 143 | log('error processing incoming UDP packets:', sys.exc_info()); 144 | 145 | # END OSC LISTENER SETUP 146 | ###################################################### 147 | 148 | def current_song_time_changed(self): 149 | time = self.song().current_song_time 150 | if int(time) != self.time: 151 | self.time = int(time) 152 | self.oscEndpoint.send("/live/beat", self.time) 153 | 154 | def send_midi(self, midi_event_bytes): 155 | """ 156 | Use this function to send MIDI events through Live to the _real_ MIDI devices 157 | that this script is assigned to. 158 | """ 159 | pass 160 | 161 | def receive_midi(self, midi_bytes): 162 | return 163 | 164 | def can_lock_to_devices(self): 165 | return False 166 | 167 | def suggest_input_port(self): 168 | return '' 169 | 170 | def suggest_output_port(self): 171 | return '' 172 | 173 | def __handle_display_switch_ids(self, switch_id, value): 174 | pass 175 | 176 | 177 | ###################################################################### 178 | # Useful Methods 179 | 180 | def application(self): 181 | """returns a reference to the application that we are running in""" 182 | return Live.Application.get_application() 183 | 184 | def song(self): 185 | """returns a reference to the Live Song that we do interact with""" 186 | return self._LiveOSC__c_instance.song() 187 | 188 | def handle(self): 189 | """returns a handle to the c_interface that is needed when forwarding MIDI events via the MIDI map""" 190 | return self._LiveOSC__c_instance.handle() 191 | 192 | def getslots(self): 193 | tracks = self.song().visible_tracks 194 | 195 | clipSlots = [] 196 | for track in tracks: 197 | clipSlots.append(track.clip_slots) 198 | return clipSlots 199 | 200 | def trBlock(self, trackOffset, blocksize): 201 | block = [] 202 | tracks = self.song().visible_tracks 203 | 204 | for track in range(0, blocksize): 205 | block.extend([str(tracks[trackOffset+track].name)]) 206 | self.oscEndpoint.send("/live/name/trackblock", block) 207 | 208 | ###################################################################### 209 | # Used Ableton Methods 210 | 211 | def disconnect(self): 212 | self.rem_clip_listeners() 213 | self.rem_mixer_listeners() 214 | self.rem_scene_listeners() 215 | self.rem_tempo_listener() 216 | self.rem_overdub_listener() 217 | self.rem_tracks_listener() 218 | self.rem_device_listeners() 219 | self.rem_transport_listener() 220 | 221 | self.song().remove_visible_tracks_listener(self.refresh_state) 222 | 223 | self.oscEndpoint.send('/remix/oscserver/shutdown', 1) 224 | self.oscEndpoint.shutdown() 225 | 226 | def build_midi_map(self, midi_map_handle): 227 | self.refresh_state() 228 | 229 | def refresh_state(self): 230 | self.add_clip_listeners() 231 | self.add_mixer_listeners() 232 | self.add_scene_listeners() 233 | self.add_tempo_listener() 234 | self.add_overdub_listener() 235 | self.add_tracks_listener() 236 | self.add_device_listeners() 237 | self.add_transport_listener() 238 | 239 | trackNumber = 0 240 | clipNumber = 0 241 | 242 | for track in self.song().visible_tracks: 243 | bundle = OSC.OSCBundle() 244 | bundle.append("/live/name/track", (trackNumber, str(track.name),int(track.has_midi_input))) 245 | for clipSlot in track.clip_slots: 246 | if clipSlot.clip != None: 247 | bundle.append("/live/name/clip", (trackNumber, clipNumber, str(clipSlot.clip.name), clipSlot.clip.color)) 248 | clipNumber = clipNumber + 1 249 | clipNumber = 0 250 | trackNumber = trackNumber + 1 251 | self.oscEndpoint.sendMessage(bundle) 252 | 253 | self.trBlock(0, len(self.song().visible_tracks)) 254 | 255 | ###################################################################### 256 | # Add / Remove Listeners 257 | def add_scene_listeners(self): 258 | self.rem_scene_listeners() 259 | 260 | if self.song().view.selected_scene_has_listener(self.scene_change) != 1: 261 | self.song().view.add_selected_scene_listener(self.scene_change) 262 | 263 | if self.song().view.selected_track_has_listener(self.track_change) != 1: 264 | self.song().view.add_selected_track_listener(self.track_change) 265 | 266 | def rem_scene_listeners(self): 267 | if self.song().view.selected_scene_has_listener(self.scene_change) == 1: 268 | self.song().view.remove_selected_scene_listener(self.scene_change) 269 | 270 | if self.song().view.selected_track_has_listener(self.track_change) == 1: 271 | self.song().view.remove_selected_track_listener(self.track_change) 272 | 273 | def track_change(self): 274 | selected_track = self.song().view.selected_track 275 | tracks = self.song().visible_tracks 276 | index = 0 277 | selected_index = 0 278 | for track in tracks: 279 | index = index + 1 280 | if track == selected_track: 281 | selected_index = index 282 | 283 | if selected_index != self.track: 284 | self.track = selected_index 285 | self.oscEndpoint.send("/live/track", (selected_index)) 286 | 287 | def scene_change(self): 288 | selected_scene = self.song().view.selected_scene 289 | scenes = self.song().scenes 290 | index = 0 291 | selected_index = 0 292 | for scene in scenes: 293 | index = index + 1 294 | if scene == selected_scene: 295 | selected_index = index 296 | 297 | if selected_index != self.scene: 298 | self.scene = selected_index 299 | self.oscEndpoint.send("/live/scene", (selected_index)) 300 | 301 | def add_tempo_listener(self): 302 | self.rem_tempo_listener() 303 | 304 | print "add tempo listener" 305 | if self.song().tempo_has_listener(self.tempo_change) != 1: 306 | self.song().add_tempo_listener(self.tempo_change) 307 | 308 | def rem_tempo_listener(self): 309 | if self.song().tempo_has_listener(self.tempo_change) == 1: 310 | self.song().remove_tempo_listener(self.tempo_change) 311 | 312 | def tempo_change(self): 313 | tempo = LiveUtils.getTempo() 314 | self.oscEndpoint.send("/live/tempo", (tempo)) 315 | 316 | def add_transport_listener(self): 317 | if self.song().is_playing_has_listener(self.transport_change) != 1: 318 | self.song().add_is_playing_listener(self.transport_change) 319 | 320 | def rem_transport_listener(self): 321 | if self.song().is_playing_has_listener(self.transport_change) == 1: 322 | self.song().remove_is_playing_listener(self.transport_change) 323 | 324 | def transport_change(self): 325 | self.oscEndpoint.send("/live/play", (self.song().is_playing and 2 or 1)) 326 | 327 | def add_overdub_listener(self): 328 | self.rem_overdub_listener() 329 | 330 | if self.song().overdub_has_listener(self.overdub_change) != 1: 331 | self.song().add_overdub_listener(self.overdub_change) 332 | 333 | def rem_overdub_listener(self): 334 | if self.song().overdub_has_listener(self.overdub_change) == 1: 335 | self.song().remove_overdub_listener(self.overdub_change) 336 | 337 | def overdub_change(self): 338 | overdub = LiveUtils.getSong().overdub 339 | self.oscEndpoint.send("/live/overdub", (int(overdub) + 1)) 340 | 341 | def add_tracks_listener(self): 342 | self.rem_tracks_listener() 343 | 344 | if self.song().tracks_has_listener(self.tracks_change) != 1: 345 | self.song().add_tracks_listener(self.tracks_change) 346 | 347 | def rem_tracks_listener(self): 348 | if self.song().tracks_has_listener(self.tempo_change) == 1: 349 | self.song().remove_tracks_listener(self.tracks_change) 350 | 351 | def tracks_change(self): 352 | self.oscEndpoint.send("/live/refresh", (1)) 353 | 354 | def rem_clip_listeners(self): 355 | for slot in self.slisten: 356 | if slot != None: 357 | if slot.has_clip_has_listener(self.slisten[slot]) == 1: 358 | slot.remove_has_clip_listener(self.slisten[slot]) 359 | 360 | self.slisten = {} 361 | 362 | for clip in self.clisten: 363 | if clip != None: 364 | if clip.playing_status_has_listener(self.clisten[clip]) == 1: 365 | clip.remove_playing_status_listener(self.clisten[clip]) 366 | 367 | self.clisten = {} 368 | 369 | for clip in self.pplisten: 370 | if clip != None: 371 | if clip.playing_position_has_listener(self.pplisten[clip]) == 1: 372 | clip.remove_playing_position_listener(self.pplisten[clip]) 373 | 374 | self.pplisten = {} 375 | 376 | for clip in self.cnlisten: 377 | if clip != None: 378 | if clip.name_has_listener(self.cnlisten[clip]) == 1: 379 | clip.remove_name_listener(self.cnlisten[clip]) 380 | 381 | self.cnlisten = {} 382 | 383 | for clip in self.cclisten: 384 | if clip != None: 385 | if clip.color_has_listener(self.cclisten[clip]) == 1: 386 | clip.remove_color_listener(self.cclisten[clip]) 387 | 388 | self.cclisten = {} 389 | 390 | 391 | for clip in self.wlisten: 392 | if clip != None: 393 | if clip.is_audio_clip: 394 | if clip.warping_has_listener(self.wlisten[clip]) == 1: 395 | clip.remove_warping_listener(self.wlisten[clip]) 396 | 397 | self.wlisten = {} 398 | 399 | for clip in self.llisten: 400 | if clip != None: 401 | if clip.looping_has_listener(self.llisten[clip]) == 1: 402 | clip.remove_looping_listener(self.llisten[clip]) 403 | 404 | self.llisten = {} 405 | 406 | 407 | 408 | def add_clip_listeners(self): 409 | self.rem_clip_listeners() 410 | 411 | tracks = self.getslots() 412 | for track in range(len(tracks)): 413 | for clip in range(len(tracks[track])): 414 | c = tracks[track][clip] 415 | if c.clip != None: 416 | self.add_cliplistener(c.clip, track, clip) 417 | log("ClipLauncher: added clip listener tr: " + str(track) + " clip: " + str(clip)); 418 | 419 | self.add_slotlistener(c, track, clip) 420 | 421 | def add_cliplistener(self, clip, tid, cid): 422 | cb = lambda :self.clip_changestate(clip, tid, cid) 423 | 424 | if self.clisten.has_key(clip) != 1: 425 | clip.add_playing_status_listener(cb) 426 | self.clisten[clip] = cb 427 | 428 | #cb2 = lambda :self.clip_position(clip, tid, cid) 429 | #if self.pplisten.has_key(clip) != 1: 430 | # clip.add_playing_position_listener(cb2) 431 | # self.pplisten[clip] = cb2 432 | 433 | cb3 = lambda :self.clip_name(clip, tid, cid) 434 | if self.cnlisten.has_key(clip) != 1: 435 | clip.add_name_listener(cb3) 436 | self.cnlisten[clip] = cb3 437 | 438 | if self.cclisten.has_key(clip) != 1: 439 | clip.add_color_listener(cb3) 440 | self.cclisten[clip] = cb3 441 | 442 | if clip.is_audio_clip: 443 | cb4 = lambda: self.clip_warping(clip, tid, cid) 444 | if self.wlisten.has_key(clip) != 1: 445 | clip.add_warping_listener(cb4) 446 | self.wlisten[clip] = cb4 447 | 448 | cb5 = lambda: self.clip_looping(clip, tid, cid) 449 | if self.llisten.has_key(clip) != 1: 450 | clip.add_looping_listener(cb5) 451 | self.llisten[clip] = cb5 452 | 453 | def add_slotlistener(self, slot, tid, cid): 454 | cb = lambda :self.slot_changestate(slot, tid, cid) 455 | 456 | if self.slisten.has_key(slot) != 1: 457 | slot.add_has_clip_listener(cb) 458 | self.slisten[slot] = cb 459 | 460 | 461 | def rem_mixer_listeners(self): 462 | # Master Track 463 | for type in ("volume", "panning", "crossfader"): 464 | for tr in self.masterlisten[type]: 465 | if tr != None: 466 | cb = self.masterlisten[type][tr] 467 | 468 | test = eval("tr.mixer_device." + type+ ".value_has_listener(cb)") 469 | 470 | if test == 1: 471 | eval("tr.mixer_device." + type + ".remove_value_listener(cb)") 472 | 473 | # Normal Tracks 474 | for type in ("arm", "solo", "mute"): 475 | for tr in self.mlisten[type]: 476 | if tr != None: 477 | cb = self.mlisten[type][tr] 478 | 479 | if type == "arm": 480 | if tr.can_be_armed == 1: 481 | if tr.arm_has_listener(cb) == 1: 482 | tr.remove_arm_listener(cb) 483 | 484 | else: 485 | test = eval("tr." + type+ "_has_listener(cb)") 486 | 487 | if test == 1: 488 | eval("tr.remove_" + type + "_listener(cb)") 489 | 490 | for type in ("volume", "panning"): 491 | for tr in self.mlisten[type]: 492 | if tr != None: 493 | cb = self.mlisten[type][tr] 494 | 495 | test = eval("tr.mixer_device." + type+ ".value_has_listener(cb)") 496 | 497 | if test == 1: 498 | eval("tr.mixer_device." + type + ".remove_value_listener(cb)") 499 | 500 | for tr in self.mlisten["sends"]: 501 | if tr != None: 502 | for send in self.mlisten["sends"][tr]: 503 | if send != None: 504 | cb = self.mlisten["sends"][tr][send] 505 | 506 | if send.value_has_listener(cb) == 1: 507 | send.remove_value_listener(cb) 508 | 509 | 510 | for tr in self.mlisten["name"]: 511 | if tr != None: 512 | cb = self.mlisten["name"][tr] 513 | 514 | if tr.name_has_listener(cb) == 1: 515 | tr.remove_name_listener(cb) 516 | 517 | for tr in self.mlisten["oml"]: 518 | if tr != None: 519 | cb = self.mlisten["oml"][tr] 520 | 521 | if tr.output_meter_left_has_listener(cb) == 1: 522 | tr.remove_output_meter_left_listener(cb) 523 | 524 | for tr in self.mlisten["omr"]: 525 | if tr != None: 526 | cb = self.mlisten["omr"][tr] 527 | 528 | if tr.output_meter_right_has_listener(cb) == 1: 529 | tr.remove_output_meter_right_listener(cb) 530 | 531 | # Return Tracks 532 | for type in ("solo", "mute"): 533 | for tr in self.rlisten[type]: 534 | if tr != None: 535 | cb = self.rlisten[type][tr] 536 | 537 | test = eval("tr." + type+ "_has_listener(cb)") 538 | 539 | if test == 1: 540 | eval("tr.remove_" + type + "_listener(cb)") 541 | 542 | for type in ("volume", "panning"): 543 | for tr in self.rlisten[type]: 544 | if tr != None: 545 | cb = self.rlisten[type][tr] 546 | 547 | test = eval("tr.mixer_device." + type+ ".value_has_listener(cb)") 548 | 549 | if test == 1: 550 | eval("tr.mixer_device." + type + ".remove_value_listener(cb)") 551 | 552 | for tr in self.rlisten["sends"]: 553 | if tr != None: 554 | for send in self.rlisten["sends"][tr]: 555 | if send != None: 556 | cb = self.rlisten["sends"][tr][send] 557 | 558 | if send.value_has_listener(cb) == 1: 559 | send.remove_value_listener(cb) 560 | 561 | for tr in self.rlisten["name"]: 562 | if tr != None: 563 | cb = self.rlisten["name"][tr] 564 | 565 | if tr.name_has_listener(cb) == 1: 566 | tr.remove_name_listener(cb) 567 | 568 | self.mlisten = { "solo": {}, "mute": {}, "arm": {}, "panning": {}, "volume": {}, "sends": {}, "name": {}, "oml": {}, "omr": {} } 569 | self.rlisten = { "solo": {}, "mute": {}, "panning": {}, "volume": {}, "sends": {}, "name": {} } 570 | self.masterlisten = { "panning": {}, "volume": {}, "crossfader": {} } 571 | 572 | 573 | def add_mixer_listeners(self): 574 | self.rem_mixer_listeners() 575 | 576 | # Master Track 577 | tr = self.song().master_track 578 | for type in ("volume", "panning", "crossfader"): 579 | self.add_master_listener(0, type, tr) 580 | 581 | self.add_meter_listener(0, tr, 2) 582 | 583 | # Normal Tracks 584 | tracks = self.song().visible_tracks 585 | for track in range(len(tracks)): 586 | tr = tracks[track] 587 | 588 | self.add_trname_listener(track, tr, 0) 589 | 590 | if tr.has_audio_output: 591 | self.add_meter_listener(track, tr) 592 | 593 | for type in ("arm", "solo", "mute"): 594 | if type == "arm": 595 | if tr.can_be_armed == 1: 596 | self.add_mixert_listener(track, type, tr) 597 | else: 598 | self.add_mixert_listener(track, type, tr) 599 | 600 | for type in ("volume", "panning"): 601 | self.add_mixerv_listener(track, type, tr) 602 | 603 | for sid in range(len(tr.mixer_device.sends)): 604 | self.add_send_listener(track, tr, sid, tr.mixer_device.sends[sid]) 605 | 606 | # Return Tracks 607 | tracks = self.song().return_tracks 608 | for track in range(len(tracks)): 609 | tr = tracks[track] 610 | 611 | self.add_trname_listener(track, tr, 1) 612 | self.add_meter_listener(track, tr, 1) 613 | 614 | for type in ("solo", "mute"): 615 | self.add_retmixert_listener(track, type, tr) 616 | 617 | for type in ("volume", "panning"): 618 | self.add_retmixerv_listener(track, type, tr) 619 | 620 | for sid in range(len(tr.mixer_device.sends)): 621 | self.add_retsend_listener(track, tr, sid, tr.mixer_device.sends[sid]) 622 | 623 | 624 | # Add track listeners 625 | def add_send_listener(self, tid, track, sid, send): 626 | if self.mlisten["sends"].has_key(track) != 1: 627 | self.mlisten["sends"][track] = {} 628 | 629 | if self.mlisten["sends"][track].has_key(send) != 1: 630 | cb = lambda :self.send_changestate(tid, track, sid, send) 631 | 632 | self.mlisten["sends"][track][send] = cb 633 | send.add_value_listener(cb) 634 | 635 | def add_mixert_listener(self, tid, type, track): 636 | if self.mlisten[type].has_key(track) != 1: 637 | cb = lambda :self.mixert_changestate(type, tid, track) 638 | 639 | self.mlisten[type][track] = cb 640 | eval("track.add_" + type + "_listener(cb)") 641 | 642 | def add_mixerv_listener(self, tid, type, track): 643 | if self.mlisten[type].has_key(track) != 1: 644 | cb = lambda :self.mixerv_changestate(type, tid, track) 645 | 646 | self.mlisten[type][track] = cb 647 | eval("track.mixer_device." + type + ".add_value_listener(cb)") 648 | 649 | # Add master listeners 650 | def add_master_listener(self, tid, type, track): 651 | if self.masterlisten[type].has_key(track) != 1: 652 | cb = lambda :self.mixerv_changestate(type, tid, track, 2) 653 | 654 | self.masterlisten[type][track] = cb 655 | eval("track.mixer_device." + type + ".add_value_listener(cb)") 656 | 657 | 658 | # Add return listeners 659 | def add_retsend_listener(self, tid, track, sid, send): 660 | if self.rlisten["sends"].has_key(track) != 1: 661 | self.rlisten["sends"][track] = {} 662 | 663 | if self.rlisten["sends"][track].has_key(send) != 1: 664 | cb = lambda :self.send_changestate(tid, track, sid, send, 1) 665 | 666 | self.rlisten["sends"][track][send] = cb 667 | send.add_value_listener(cb) 668 | 669 | def add_retmixert_listener(self, tid, type, track): 670 | if self.rlisten[type].has_key(track) != 1: 671 | cb = lambda :self.mixert_changestate(type, tid, track, 1) 672 | 673 | self.rlisten[type][track] = cb 674 | eval("track.add_" + type + "_listener(cb)") 675 | 676 | def add_retmixerv_listener(self, tid, type, track): 677 | if self.rlisten[type].has_key(track) != 1: 678 | cb = lambda :self.mixerv_changestate(type, tid, track, 1) 679 | 680 | self.rlisten[type][track] = cb 681 | eval("track.mixer_device." + type + ".add_value_listener(cb)") 682 | 683 | 684 | # Track name listener 685 | def add_trname_listener(self, tid, track, ret = 0): 686 | cb = lambda :self.trname_changestate(tid, track, ret) 687 | 688 | if ret == 1: 689 | if self.rlisten["name"].has_key(track) != 1: 690 | self.rlisten["name"][track] = cb 691 | 692 | else: 693 | if self.mlisten["name"].has_key(track) != 1: 694 | self.mlisten["name"][track] = cb 695 | 696 | track.add_name_listener(cb) 697 | 698 | # Output Meter Listeners 699 | def add_meter_listener(self, tid, track, r = 0): 700 | cb = lambda :self.meter_changestate(tid, track, 0, r) 701 | 702 | if self.mlisten["oml"].has_key(track) != 1: 703 | self.mlisten["oml"][track] = cb 704 | 705 | track.add_output_meter_left_listener(cb) 706 | 707 | cb = lambda :self.meter_changestate(tid, track, 1, r) 708 | 709 | if self.mlisten["omr"].has_key(track) != 1: 710 | self.mlisten["omr"][track] = cb 711 | 712 | track.add_output_meter_right_listener(cb) 713 | 714 | ###################################################################### 715 | # Listener Callbacks 716 | 717 | # Clip Callbacks 718 | def clip_warping(self, clip, tid, cid): 719 | self.oscEndpoint.send('/live/clip/warping', (tid, cid, int(clip.warping))) 720 | 721 | def clip_looping(self, clip, tid, cid): 722 | self.oscEndpoint.send('/live/clip/loopstate', (tid, cid, int(clip.looping))) 723 | 724 | def clip_name(self, clip, tid, cid): 725 | self.oscEndpoint.send('/live/name/clip', (tid, cid, str(clip.name), clip.color)) 726 | 727 | #def clip_position(self, clip, tid, cid): 728 | # send = self._send_pos.has_key(tid) and self._send_pos[tid] or 0 729 | # 730 | # if self.check_md(1) or (self.check_md(5) and send): 731 | # if clip.is_playing: 732 | # if send > 0: 733 | # self._send_pos[tid] -= 1 734 | # 735 | # self.oscEndpoint.send('/live/clip/position', (tid, cid, clip.playing_position, clip.length, clip.loop_start, clip.loop_end)) 736 | 737 | def slot_changestate(self, slot, tid, cid): 738 | tmptrack = LiveUtils.getTrack(tid) 739 | armed = tmptrack.arm and 1 or 0 740 | 741 | # Added new clip 742 | if slot.clip != None: 743 | self.add_cliplistener(slot.clip, tid, cid) 744 | 745 | playing = 1 746 | if slot.clip.is_playing == 1: 747 | playing = 2 748 | 749 | if slot.clip.is_triggered == 1: 750 | playing = 3 751 | 752 | length = slot.clip.loop_end - slot.clip.loop_start 753 | 754 | self.oscEndpoint.send('/live/name/clip', (tid, cid, str(slot.clip.name), slot.clip.color)) 755 | else: 756 | if self.clisten.has_key(slot.clip) == 1: 757 | slot.clip.remove_playing_status_listener(self.clisten[slot.clip]) 758 | 759 | if self.pplisten.has_key(slot.clip) == 1: 760 | slot.clip.remove_playing_position_listener(self.pplisten[slot.clip]) 761 | 762 | if self.cnlisten.has_key(slot.clip) == 1: 763 | slot.clip.remove_name_listener(self.cnlisten[slot.clip]) 764 | 765 | if self.cclisten.has_key(slot.clip) == 1: 766 | slot.clip.remove_color_listener(self.cclisten[slot.clip]) 767 | 768 | self.oscEndpoint.send('/live/clip/info', (tid, cid, 0, 0)) 769 | 770 | #log("Slot changed" + str(self.clips[tid][cid])) 771 | 772 | def clip_changestate(self, clip, x, y): 773 | log("Listener: x: " + str(x) + " y: " + str(y)); 774 | 775 | playing = 1 776 | 777 | if clip.is_playing == 1: 778 | playing = 2 779 | 780 | if clip.is_triggered == 1: 781 | playing = 3 782 | 783 | self.oscEndpoint.send('/live/clip/info', (x, y, playing, clip.length)) 784 | self._send_pos[x] = 3 785 | 786 | #log("Clip changed x:" + str(x) + " y:" + str(y) + " status:" + str(playing)) 787 | 788 | 789 | # Mixer Callbacks 790 | def mixerv_changestate(self, type, tid, track, r = 0): 791 | val = eval("track.mixer_device." + type + ".value") 792 | types = { "panning": "pan", "volume": "volume", "crossfader": "crossfader" } 793 | 794 | if r == 2: 795 | self.oscEndpoint.send('/live/master/' + types[type], (float(val))) 796 | elif r == 1: 797 | self.oscEndpoint.send('/live/return/' + types[type], (tid, float(val))) 798 | else: 799 | self.oscEndpoint.send('/live/' + types[type], (tid, float(val))) 800 | 801 | def mixert_changestate(self, type, tid, track, r = 0): 802 | val = eval("track." + type) 803 | 804 | if r == 1: 805 | self.oscEndpoint.send('/live/return/' + type, (tid, int(val))) 806 | else: 807 | self.oscEndpoint.send('/live/' + type, (tid, int(val))) 808 | 809 | def send_changestate(self, tid, track, sid, send, r = 0): 810 | val = send.value 811 | 812 | if r == 1: 813 | self.oscEndpoint.send('/live/return/send', (tid, sid, float(val))) 814 | else: 815 | self.oscEndpoint.send('/live/send', (tid, sid, float(val))) 816 | 817 | 818 | # Track name changestate 819 | def trname_changestate(self, tid, track, r = 0): 820 | if r == 1: 821 | self.oscEndpoint.send('/live/name/return', (tid, str(track.name))) 822 | else: 823 | self.oscEndpoint.send('/live/name/track', (tid, str(track.name))) 824 | self.trBlock(0, len(LiveUtils.getTracks())) 825 | 826 | # Meter Changestate 827 | def meter_changestate(self, tid, track, lr, r = 0): 828 | if r == 2: 829 | if self.check_md(2): 830 | if lr == 0: 831 | self.oscEndpoint.send('/live/master/meter', (0, float(track.output_meter_left))) 832 | else: 833 | self.oscEndpoint.send('/live/master/meter', (1, float(track.output_meter_right))) 834 | elif r == 1: 835 | if self.check_md(3): 836 | if lr == 0: 837 | self.oscEndpoint.send('/live/return/meter', (tid, 0, float(track.output_meter_left))) 838 | else: 839 | self.oscEndpoint.send('/live/return/meter', (tid, 1, float(track.output_meter_right))) 840 | else: 841 | if self.check_md(4): 842 | if lr == 0: 843 | self.oscEndpoint.send('/live/track/meter', (tid, 0, float(track.output_meter_left))) 844 | else: 845 | self.oscEndpoint.send('/live/track/meter', (tid, 1, float(track.output_meter_right))) 846 | 847 | def check_md(self, param): 848 | devices = self.song().master_track.devices 849 | 850 | if len(devices) > 0: 851 | if devices[0].parameters[param].value > 0: 852 | return 1 853 | else: 854 | return 0 855 | else: 856 | return 0 857 | 858 | # Device Listeners 859 | def add_device_listeners(self): 860 | self.rem_device_listeners() 861 | 862 | self.do_add_device_listeners(self.song().tracks,0) 863 | self.do_add_device_listeners(self.song().return_tracks,1) 864 | self.do_add_device_listeners([self.song().master_track],2) 865 | 866 | def do_add_device_listeners(self, tracks, type): 867 | for i in range(len(tracks)): 868 | self.add_devicelistener(tracks[i], i, type) 869 | 870 | if len(tracks[i].devices) >= 1: 871 | for j in range(len(tracks[i].devices)): 872 | self.add_devpmlistener(tracks[i].devices[j]) 873 | 874 | if len(tracks[i].devices[j].parameters) >= 1: 875 | for k in range (len(tracks[i].devices[j].parameters)): 876 | par = tracks[i].devices[j].parameters[k] 877 | self.add_paramlistener(par, i, j, k, type) 878 | 879 | def rem_device_listeners(self): 880 | for pr in self.prlisten: 881 | ocb = self.prlisten[pr] 882 | if pr != None: 883 | if pr.value_has_listener(ocb) == 1: 884 | pr.remove_value_listener(ocb) 885 | 886 | self.prlisten = {} 887 | 888 | for tr in self.dlisten: 889 | ocb = self.dlisten[tr] 890 | if tr != None: 891 | if tr.view.selected_device_has_listener(ocb) == 1: 892 | tr.view.remove_selected_device_listener(ocb) 893 | 894 | self.dlisten = {} 895 | 896 | for de in self.plisten: 897 | ocb = self.plisten[de] 898 | if de != None: 899 | if de.parameters_has_listener(ocb) == 1: 900 | de.remove_parameters_listener(ocb) 901 | 902 | self.plisten = {} 903 | 904 | def add_devpmlistener(self, device): 905 | cb = lambda :self.devpm_change() 906 | 907 | if self.plisten.has_key(device) != 1: 908 | device.add_parameters_listener(cb) 909 | self.plisten[device] = cb 910 | 911 | def devpm_change(self): 912 | self.refresh_state() 913 | 914 | def add_paramlistener(self, param, tid, did, pid, type): 915 | cb = lambda :self.param_changestate(param, tid, did, pid, type) 916 | 917 | if self.prlisten.has_key(param) != 1: 918 | param.add_value_listener(cb) 919 | self.prlisten[param] = cb 920 | 921 | def param_changestate(self, param, tid, did, pid, type): 922 | if type == 2: 923 | self.oscEndpoint.send('/live/master/device/param', (did, pid, param.value, str(param.name))) 924 | elif type == 1: 925 | self.oscEndpoint.send('/live/return/device/param', (tid, did, pid, param.value, str(param.name))) 926 | else: 927 | self.oscEndpoint.send('/live/device/param', (tid, did, pid, param.value, str(param.name))) 928 | 929 | def add_devicelistener(self, track, tid, type): 930 | cb = lambda :self.device_changestate(track, tid, type) 931 | 932 | if self.dlisten.has_key(track) != 1: 933 | track.view.add_selected_device_listener(cb) 934 | self.dlisten[track] = cb 935 | 936 | def device_changestate(self, track, tid, type): 937 | did = self.tuple_idx(track.devices, track.view.selected_device) 938 | 939 | if type == 2: 940 | self.oscEndpoint.send('/live/master/devices/selected', (did)) 941 | elif type == 1: 942 | self.oscEndpoint.send('/live/return/device/selected', (tid, did)) 943 | else: 944 | self.oscEndpoint.send('/live/device/selected', (tid, did)) 945 | 946 | def tuple_idx(self, tuple, obj): 947 | for i in xrange(0,len(tuple)): 948 | if (tuple[i] == obj): 949 | return i 950 | -------------------------------------------------------------------------------- /LiveOSCCallbacks.py: -------------------------------------------------------------------------------- 1 | """ 2 | # Copyright (C) 2007 Rob King (rob@re-mu.org) 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 2.1 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | # 18 | # For questions regarding this module contact 19 | # Rob King or visit http://www.e-mu.org 20 | 21 | This file contains all the current Live OSC callbacks. 22 | 23 | """ 24 | import Live 25 | import RemixNet 26 | import OSC 27 | import LiveUtils 28 | import sys 29 | 30 | from Logger import log 31 | 32 | class LiveOSCCallbacks: 33 | def __init__(self, c_instance, oscEndpoint): 34 | self.oscEndpoint = oscEndpoint 35 | self.callbackManager = oscEndpoint.callbackManager 36 | 37 | self.c_instance = c_instance 38 | 39 | self.callbackManager.add("/live/tempo", self.tempoCB) 40 | self.callbackManager.add("/live/time", self.timeCB) 41 | self.callbackManager.add("/live/next/cue", self.nextCueCB) 42 | self.callbackManager.add("/live/prev/cue", self.prevCueCB) 43 | self.callbackManager.add("/live/play", self.playCB) 44 | self.callbackManager.add("/live/play/continue", self.playContinueCB) 45 | self.callbackManager.add("/live/play/selection", self.playSelectionCB) 46 | self.callbackManager.add("/live/play/clip", self.playClipCB) 47 | self.callbackManager.add("/live/play/scene", self.playSceneCB) 48 | self.callbackManager.add("/live/stop", self.stopCB) 49 | self.callbackManager.add("/live/stop/clip", self.stopClipCB) 50 | self.callbackManager.add("/live/stop/track", self.stopTrackCB) 51 | self.callbackManager.add("/live/scenes", self.scenesCB) 52 | self.callbackManager.add("/live/tracks", self.tracksCB) 53 | self.callbackManager.add("/live/returns", self.returnsCB) 54 | self.callbackManager.add("/live/name/scene", self.nameSceneCB) 55 | self.callbackManager.add("/live/scene", self.sceneCB) 56 | self.callbackManager.add("/live/name/sceneblock", self.nameSceneBlockCB) 57 | self.callbackManager.add("/live/name/track", self.nameTrackCB) 58 | self.callbackManager.add("/live/name/return", self.nameReturnCB) 59 | self.callbackManager.add("/live/name/trackblock", self.nameTrackBlockCB) 60 | self.callbackManager.add("/live/name/clip", self.nameClipCB) 61 | self.callbackManager.add("/live/name/clipblock", self.nameClipBlockCB) 62 | self.callbackManager.add("/live/arm", self.armTrackCB) 63 | self.callbackManager.add("/live/mute", self.muteTrackCB) 64 | self.callbackManager.add("/live/solo", self.soloTrackCB) 65 | self.callbackManager.add("/live/volume", self.volumeCB) 66 | self.callbackManager.add("/live/pan", self.panCB) 67 | self.callbackManager.add("/live/send", self.sendCB) 68 | self.callbackManager.add("/live/pitch", self.pitchCB) 69 | self.callbackManager.add("/live/track/jump", self.trackJump) 70 | self.callbackManager.add("/live/track/info", self.trackInfoCB) 71 | self.callbackManager.add("/live/return/info", self.returnInfoCB) 72 | self.callbackManager.add("/live/undo", self.undoCB) 73 | self.callbackManager.add("/live/redo", self.redoCB) 74 | self.callbackManager.add("/live/play/clipslot", self.playClipSlotCB) 75 | 76 | self.callbackManager.add("/live/scene/view", self.viewSceneCB) 77 | 78 | self.callbackManager.add("/live/track/view", self.viewTrackCB) 79 | self.callbackManager.add("/live/return/view", self.viewTrackCB) 80 | self.callbackManager.add("/live/master/view", self.mviewTrackCB) 81 | 82 | self.callbackManager.add("/live/track/device/view", self.viewDeviceCB) 83 | self.callbackManager.add("/live/return/device/view", self.viewDeviceCB) 84 | self.callbackManager.add("/live/master/device/view", self.mviewDeviceCB) 85 | 86 | self.callbackManager.add("/live/clip/view", self.viewClipCB) 87 | 88 | self.callbackManager.add("/live/detail/view", self.detailViewCB) 89 | 90 | self.callbackManager.add("/live/overdub", self.overdubCB) 91 | self.callbackManager.add("/live/state", self.stateCB) 92 | self.callbackManager.add("/live/clip/info", self.clipInfoCB) 93 | 94 | self.callbackManager.add("/live/return/mute", self.muteTrackCB) 95 | self.callbackManager.add("/live/return/solo", self.soloTrackCB) 96 | self.callbackManager.add("/live/return/volume", self.volumeCB) 97 | self.callbackManager.add("/live/return/pan", self.panCB) 98 | self.callbackManager.add("/live/return/send", self.sendCB) 99 | 100 | self.callbackManager.add("/live/master/volume", self.volumeCB) 101 | self.callbackManager.add("/live/master/pan", self.panCB) 102 | 103 | self.callbackManager.add("/live/devicelist", self.devicelistCB) 104 | self.callbackManager.add("/live/return/devicelist", self.devicelistCB) 105 | self.callbackManager.add("/live/master/devicelist", self.mdevicelistCB) 106 | 107 | self.callbackManager.add("/live/device/range", self.devicerangeCB) 108 | self.callbackManager.add("/live/return/device/range", self.devicerangeCB) 109 | self.callbackManager.add("/live/master/device/range", self.mdevicerangeCB) 110 | 111 | self.callbackManager.add("/live/device", self.deviceCB) 112 | self.callbackManager.add("/live/return/device", self.deviceCB) 113 | self.callbackManager.add("/live/master/device", self.mdeviceCB) 114 | 115 | self.callbackManager.add("/live/clip/loopstate", self.loopStateCB) 116 | self.callbackManager.add("/live/clip/loopstart", self.loopStartCB) 117 | self.callbackManager.add("/live/clip/loopend", self.loopEndCB) 118 | 119 | self.callbackManager.add("/live/clip/loopstate_id", self.loopStateCB) 120 | self.callbackManager.add("/live/clip/loopstart_id", self.loopStartCB) 121 | self.callbackManager.add("/live/clip/loopend_id", self.loopEndCB) 122 | 123 | self.callbackManager.add("/live/clip/warping", self.warpingCB) 124 | 125 | self.callbackManager.add("/live/clip/signature", self.sigCB) 126 | 127 | self.callbackManager.add("/live/clip/add_note", self.addNoteCB) 128 | self.callbackManager.add("/live/clip/notes", self.getNotesCB) 129 | 130 | self.callbackManager.add("/live/master/crossfader", self.crossfaderCB) 131 | self.callbackManager.add("/live/track/crossfader", self.trackxfaderCB) 132 | self.callbackManager.add("/live/return/crossfader", self.trackxfaderCB) 133 | 134 | self.callbackManager.add("/live/quantization", self.quantizationCB) 135 | 136 | self.callbackManager.add("/live/selection", self.selectionCB) 137 | 138 | def sigCB(self, msg, source): 139 | """ Called when a /live/clip/signature message is recieved 140 | """ 141 | track = msg[2] 142 | clip = msg[3] 143 | c = LiveUtils.getSong().visible_tracks[track].clip_slots[clip].clip 144 | 145 | if len(msg) == 4: 146 | self.oscEndpoint.send("/live/clip/signature", (track, clip, c.signature_numerator, c.signature_denominator)) 147 | 148 | if len(msg) == 6: 149 | self.oscEndpoint.send("/live/clip/signature", 1) 150 | c.signature_denominator = msg[5] 151 | c.signature_numerator = msg[4] 152 | 153 | def warpingCB(self, msg, source): 154 | """ Called when a /live/clip/warping message is recieved 155 | """ 156 | track = msg[2] 157 | clip = msg[3] 158 | 159 | 160 | if len(msg) == 4: 161 | state = LiveUtils.getSong().visible_tracks[track].clip_slots[clip].clip.warping 162 | self.oscEndpoint.send("/live/clip/warping", (track, clip, int(state))) 163 | 164 | elif len(msg) == 5: 165 | LiveUtils.getSong().visible_tracks[track].clip_slots[clip].clip.warping = msg[4] 166 | 167 | def selectionCB(self, msg, source): 168 | """ Called when a /live/selection message is received 169 | """ 170 | if len(msg) == 6: 171 | self.c_instance.set_session_highlight(msg[2], msg[3], msg[4], msg[5], 0) 172 | 173 | def trackxfaderCB(self, msg, source): 174 | """ Called when a /live/track/crossfader or /live/return/crossfader message is received 175 | """ 176 | ty = msg[0] == '/live/return/crossfader' and 1 or 0 177 | 178 | if len(msg) == 3: 179 | track = msg[2] 180 | 181 | if ty == 1: 182 | assign = LiveUtils.getSong().return_tracks[track].mixer_device.crossfade_assign 183 | name = LiveUtils.getSong().return_tracks[track].mixer_device.crossfade_assignments.values[assign] 184 | 185 | self.oscEndpoint.send("/live/return/crossfader", (track, str(assign), str(name))) 186 | else: 187 | assign = LiveUtils.getSong().visible_tracks[track].mixer_device.crossfade_assign 188 | name = LiveUtils.getSong().visible_tracks[track].mixer_device.crossfade_assignments.values[assign] 189 | 190 | self.oscEndpoint.send("/live/track/crossfader", (track, str(assign), str(name))) 191 | 192 | 193 | elif len(msg) == 4: 194 | track = msg[2] 195 | assign = msg[3] 196 | 197 | if ty == 1: 198 | LiveUtils.getSong().return_tracks[track].mixer_device.crossfade_assign = assign 199 | else: 200 | LiveUtils.getSong().visible_tracks[track].mixer_device.crossfade_assign = assign 201 | 202 | def tempoCB(self, msg, source): 203 | """Called when a /live/tempo message is received. 204 | 205 | Messages: 206 | /live/tempo Request current tempo, replies with /live/tempo (float tempo) 207 | /live/tempo (float tempo) Set the tempo, replies with /live/tempo (float tempo) 208 | """ 209 | if len(msg) == 2 or (len(msg) == 3 and msg[2] == "query"): 210 | self.oscEndpoint.send("/live/tempo", LiveUtils.getTempo()) 211 | 212 | elif len(msg) == 3: 213 | tempo = msg[2] 214 | LiveUtils.setTempo(tempo) 215 | 216 | def timeCB(self, msg, source): 217 | """Called when a /live/time message is received. 218 | 219 | Messages: 220 | /live/time Request current song time, replies with /live/time (float time) 221 | /live/time (float time) Set the time , replies with /live/time (float time) 222 | """ 223 | if len(msg) == 2 or (len(msg) == 3 and msg[2] == "query"): 224 | self.oscEndpoint.send("/live/time", float(LiveUtils.currentTime())) 225 | 226 | elif len(msg) == 3: 227 | time = msg[2] 228 | LiveUtils.currentTime(time) 229 | 230 | def nextCueCB(self, msg, source): 231 | """Called when a /live/next/cue message is received. 232 | 233 | Messages: 234 | /live/next/cue Jumps to the next cue point 235 | """ 236 | LiveUtils.jumpToNextCue() 237 | 238 | def prevCueCB(self, msg, source): 239 | """Called when a /live/prev/cue message is received. 240 | 241 | Messages: 242 | /live/prev/cue Jumps to the previous cue point 243 | """ 244 | LiveUtils.jumpToPrevCue() 245 | 246 | def playCB(self, msg, source): 247 | """Called when a /live/play message is received. 248 | 249 | Messages: 250 | /live/play Starts the song playing 251 | """ 252 | LiveUtils.play() 253 | 254 | def playContinueCB(self, msg, source): 255 | """Called when a /live/play/continue message is received. 256 | 257 | Messages: 258 | /live/play/continue Continues playing the song from the current point 259 | """ 260 | LiveUtils.continuePlaying() 261 | 262 | def playSelectionCB(self, msg, source): 263 | """Called when a /live/play/selection message is received. 264 | 265 | Messages: 266 | /live/play/selection Plays the current selection 267 | """ 268 | LiveUtils.playSelection() 269 | 270 | def playClipCB(self, msg, source): 271 | """Called when a /live/play/clip message is received. 272 | 273 | Messages: 274 | /live/play/clip (int track, int clip) Launches clip number clip in track number track 275 | """ 276 | if len(msg) == 4: 277 | track = msg[2] 278 | clip = msg[3] 279 | LiveUtils.launchClip(track, clip) 280 | 281 | def playSceneCB(self, msg, source): 282 | """Called when a /live/play/scene message is received. 283 | 284 | Messages: 285 | /live/play/scene (int scene) Launches scene number scene 286 | """ 287 | if len(msg) == 3: 288 | scene = msg[2] 289 | LiveUtils.launchScene(scene) 290 | 291 | def stopCB(self, msg, source): 292 | """Called when a /live/stop message is received. 293 | 294 | Messages: 295 | /live/stop Stops playing the song 296 | """ 297 | LiveUtils.stop() 298 | 299 | def stopClipCB(self, msg, source): 300 | """Called when a /live/stop/clip message is received. 301 | 302 | Messages: 303 | /live/stop/clip (int track, int clip) Stops clip number clip in track number track 304 | """ 305 | if len(msg) == 4: 306 | track = msg[2] 307 | clip = msg[3] 308 | LiveUtils.stopClip(track, clip) 309 | 310 | def stopTrackCB(self, msg, source): 311 | """Called when a /live/stop/track message is received. 312 | 313 | Messages: 314 | /live/stop/track (int track, int clip) Stops track number track 315 | """ 316 | if len(msg) == 3: 317 | track = msg[2] 318 | LiveUtils.stopTrack(track) 319 | 320 | def scenesCB(self, msg, source): 321 | """Called when a /live/scenes message is received. 322 | 323 | Messages: 324 | /live/scenes no argument or 'query' Returns the total number of scenes 325 | 326 | """ 327 | if len(msg) == 2 or (len(msg) == 3 and msg[2] == "query"): 328 | sceneTotal = len(LiveUtils.getScenes()) 329 | self.oscEndpoint.send("/live/scenes", (sceneTotal)) 330 | return 331 | 332 | def sceneCB(self, msg, source): 333 | """Called when a /live/scene message is received. 334 | 335 | Messages: 336 | /live/scene no argument or 'query' Returns the currently playing scene number 337 | """ 338 | if len(msg) == 2 or (len(msg) == 3 and msg[2] == "query"): 339 | selected_scene = LiveUtils.getSong().view.selected_scene 340 | scenes = LiveUtils.getScenes() 341 | index = 0 342 | selected_index = 0 343 | for scene in scenes: 344 | index = index + 1 345 | if scene == selected_scene: 346 | selected_index = index 347 | 348 | self.oscEndpoint.send("/live/scene", (selected_index)) 349 | 350 | elif len(msg) == 3: 351 | scene = msg[2] 352 | LiveUtils.getSong().view.selected_scene = LiveUtils.getSong().scenes[scene] 353 | 354 | def tracksCB(self, msg, source): 355 | """Called when a /live/tracks message is received. 356 | 357 | Messages: 358 | /live/tracks no argument or 'query' Returns the total number of tracks 359 | 360 | """ 361 | if len(msg) == 2 or (len(msg) == 3 and msg[2] == "query"): 362 | trackTotal = len(LiveUtils.getTracks()) 363 | self.oscEndpoint.send("/live/tracks", (trackTotal)) 364 | return 365 | 366 | def returnsCB(self, msg, source): 367 | """Called when a /live/returns message is received. 368 | 369 | Messages: 370 | /live/returns no argument or 'query' Returns the total number of return tracks 371 | 372 | """ 373 | if len(msg) == 2 or (len(msg) == 3 and msg[2] == "query"): 374 | trackTotal = len(LiveUtils.getSong().return_tracks) 375 | self.oscEndpoint.send("/live/returns", (trackTotal)) 376 | return 377 | 378 | def nameSceneCB(self, msg, source): 379 | """Called when a /live/name/scene message is received. 380 | 381 | Messages: 382 | /live/name/scene Returns a a series of all the scene names in the form /live/name/scene (int scene, string name) 383 | /live/name/scene (int scene) Returns a single scene's name in the form /live/name/scene (int scene, string name) 384 | /live/name/scene (int scene, string name)Sets scene number scene's name to name 385 | 386 | """ 387 | #Requesting all scene names 388 | if len(msg) == 2 or (len(msg) == 3 and msg[2] == "query"): 389 | bundle = OSC.OSCBundle() 390 | sceneNumber = 0 391 | for scene in LiveUtils.getScenes(): 392 | bundle.append("/live/name/scene", (sceneNumber, str(scene.name))) 393 | sceneNumber = sceneNumber + 1 394 | self.oscEndpoint.sendMessage(bundle) 395 | return 396 | #Requesting a single scene name 397 | if len(msg) == 3: 398 | sceneNumber = msg[2] 399 | self.oscEndpoint.send("/live/name/scene", (sceneNumber, str(LiveUtils.getScene(sceneNumber).name))) 400 | return 401 | #renaming a scene 402 | if len(msg) == 4: 403 | sceneNumber = msg[2] 404 | name = msg[3] 405 | LiveUtils.getScene(sceneNumber).name = name 406 | 407 | def nameSceneBlockCB(self, msg, source): 408 | """Called when a /live/name/sceneblock message is received. 409 | 410 | /live/name/clipblock (int offset, int blocksize) Returns a list of blocksize scene names starting at offset 411 | """ 412 | if len(msg) == 4: 413 | block = [] 414 | sceneOffset = msg[2] 415 | blocksize = msg[3] 416 | for scene in range(0, blocksize): 417 | block.extend([str(LiveUtils.getScene(sceneOffset+scene).name)]) 418 | self.oscEndpoint.send("/live/name/sceneblock", block) 419 | 420 | 421 | def nameTrackCB(self, msg, source): 422 | """Called when a /live/name/track message is received. 423 | 424 | Messages: 425 | /live/name/track Returns a a series of all the track names in the form /live/name/track (int track, string name) 426 | /live/name/track (int track) Returns a single track's name in the form /live/name/track (int track, string name) 427 | /live/name/track (int track, string name)Sets track number track's name to name 428 | 429 | """ 430 | #Requesting all track names 431 | if len(msg) == 2 or (len(msg) == 3 and msg[2] == "query"): 432 | trackNumber = 0 433 | bundle = OSC.OSCBundle() 434 | for track in LiveUtils.getTracks(): 435 | bundle.append("/live/name/track", (trackNumber, str(track.name),int(track.has_midi_input))) 436 | trackNumber = trackNumber + 1 437 | self.oscEndpoint.sendMessage(bundle) 438 | return 439 | #Requesting a single track name 440 | if len(msg) == 3: 441 | trackNumber = msg[2] 442 | self.oscEndpoint.send("/live/name/track", (trackNumber, str(LiveUtils.getTrack(trackNumber).name),int(LiveUtils.getTrack(trackNumber).has_midi_input))) 443 | return 444 | #renaming a track 445 | if len(msg) == 4: 446 | trackNumber = msg[2] 447 | name = msg[3] 448 | LiveUtils.getTrack(trackNumber).name = name 449 | 450 | def nameReturnCB(self, msg, source): 451 | """Called when a /live/name/return message is received. 452 | 453 | Messages: 454 | /live/name/return Returns a a series of all the track names in the form /live/name/track (int track, string name) 455 | /live/name/return (int track) Returns a single track's name in the form /live/name/track (int track, string name) 456 | /live/name/return (int track, string name)Sets track number track's name to name 457 | 458 | """ 459 | #Requesting all track names 460 | if len(msg) == 2 or (len(msg) == 3 and msg[2] == "query"): 461 | trackNumber = 0 462 | bundle = OSC.OSCBundle() 463 | for track in LiveUtils.getSong().return_tracks: 464 | bundle.append("/live/name/return", (trackNumber, str(track.name))) 465 | trackNumber = trackNumber + 1 466 | self.oscEndpoint.sendMessage(bundle) 467 | return 468 | #Requesting a single track name 469 | if len(msg) == 3: 470 | trackNumber = msg[2] 471 | self.oscEndpoint.send("/live/name/return", (trackNumber, str(LiveUtils.getSong().return_tracks[trackNumber].name))) 472 | return 473 | #renaming a track 474 | if len(msg) == 4: 475 | trackNumber = msg[2] 476 | name = msg[3] 477 | LiveUtils.getSong().return_tracks[trackNumber].name = name 478 | 479 | def nameTrackBlockCB(self, msg, source): 480 | """Called when a /live/name/trackblock message is received. 481 | 482 | /live/name/trackblock (int offset, int blocksize) Returns a list of blocksize track names starting at offset 483 | """ 484 | if len(msg) == 4: 485 | block = [] 486 | trackOffset = msg[2] 487 | blocksize = msg[3] 488 | for track in range(0, blocksize): 489 | block.extend([str(LiveUtils.getTrack(trackOffset+track).name)]) 490 | self.oscEndpoint.send("/live/name/trackblock", block) 491 | 492 | def nameClipBlockCB(self, msg, source): 493 | """Called when a /live/name/clipblock message is received. 494 | 495 | /live/name/clipblock (int track, int clip, blocksize x/tracks, blocksize y/clipslots) Returns a list of clip names for a block of clips (int blockX, int blockY, clipname) 496 | 497 | """ 498 | #Requesting a block of clip names X1 Y1 X2 Y2 where X1,Y1 is the first clip (track, clip) of the block, X2 the number of tracks to cover and Y2 the number of scenes 499 | 500 | if len(msg) == 6: 501 | block = [] 502 | trackOffset = msg[2] 503 | clipOffset = msg[3] 504 | blocksizeX = msg[4] 505 | blocksizeY = msg[5] 506 | for clip in range(0, blocksizeY): 507 | for track in range(0, blocksizeX): 508 | trackNumber = trackOffset+track 509 | clipNumber = clipOffset+clip 510 | if LiveUtils.getClip(trackNumber, clipNumber) != None: 511 | block.extend([str(LiveUtils.getClip(trackNumber, clipNumber).name)]) 512 | else: 513 | block.extend([""]) 514 | 515 | self.oscEndpoint.send("/live/name/clipblock", block) 516 | 517 | 518 | 519 | def nameClipCB(self, msg, source): 520 | """Called when a /live/name/clip message is received. 521 | 522 | Messages: 523 | /live/name/clip Returns a a series of all the clip names in the form /live/name/clip (int track, int clip, string name) 524 | /live/name/clip (int track, int clip) Returns a single clip's name in the form /live/name/clip (int clip, string name) 525 | /live/name/clip (int track, int clip, string name)Sets clip number clip in track number track's name to name 526 | 527 | """ 528 | #Requesting all clip names 529 | if len(msg) == 2 or (len(msg) == 3 and msg[2] == "query"): 530 | trackNumber = 0 531 | clipNumber = 0 532 | for track in LiveUtils.getTracks(): 533 | bundle = OSC.OSCBundle() 534 | for clipSlot in track.clip_slots: 535 | if clipSlot.clip != None: 536 | bundle.append("/live/name/clip", (trackNumber, clipNumber, str(clipSlot.clip.name), clipSlot.clip.color)) 537 | clipNumber = clipNumber + 1 538 | self.oscEndpoint.sendMessage(bundle) 539 | clipNumber = 0 540 | trackNumber = trackNumber + 1 541 | return 542 | #Requesting a single clip name 543 | if len(msg) == 4: 544 | trackNumber = msg[2] 545 | clipNumber = msg[3] 546 | self.oscEndpoint.send("/live/name/clip", (trackNumber, clipNumber, str(LiveUtils.getClip(trackNumber, clipNumber).name), LiveUtils.getClip(trackNumber, clipNumber).color)) 547 | return 548 | #renaming a clip 549 | if len(msg) >= 5: 550 | trackNumber = msg[2] 551 | clipNumber = msg[3] 552 | name = msg[4] 553 | LiveUtils.getClip(trackNumber, clipNumber).name = name 554 | 555 | if len(msg) >= 6: 556 | trackNumber = msg[2] 557 | clipNumber = msg[3] 558 | color = msg[5] 559 | LiveUtils.getClip(trackNumber, clipNumber).color = color 560 | 561 | def addNoteCB(self, msg, source): 562 | """Called when a /live/clip/add_note message is received 563 | 564 | Messages: 565 | /live/clip/add_note (int pitch) (double time) (double duration) (int velocity) (bool muted) Add the given note to the clip 566 | """ 567 | trackNumber = msg[2] 568 | clipNumber = msg[3] 569 | pitch = msg[4] 570 | time = msg[5] 571 | duration = msg[6] 572 | velocity = msg[7] 573 | muted = msg[8] 574 | LiveUtils.getClip(trackNumber, clipNumber).deselect_all_notes() 575 | 576 | notes = ((pitch, time, duration, velocity, muted),) 577 | LiveUtils.getClip(trackNumber, clipNumber).replace_selected_notes(notes) 578 | self.oscEndpoint.send('/live/clip/note', (trackNumber, clipNumber, pitch, time, duration, velocity, muted)) 579 | 580 | def getNotesCB(self, msg, source): 581 | """Called when a /live/clip/notes message is received 582 | 583 | Messages: 584 | /live/clip/notes Return all notes in the clip in /live/clip/note messages. Each note is sent in the format 585 | (int trackNumber) (int clipNumber) (int pitch) (double time) (double duration) (int velocity) (int muted) 586 | """ 587 | trackNumber = msg[2] 588 | clipNumber = msg[3] 589 | LiveUtils.getClip(trackNumber, clipNumber).select_all_notes() 590 | bundle = OSC.OSCBundle() 591 | for note in LiveUtils.getClip(trackNumber, clipNumber).get_selected_notes(): 592 | pitch = note[0] 593 | time = note[1] 594 | duration = note[2] 595 | velocity = note[3] 596 | muted = 0 597 | if note[4]: 598 | muted = 1 599 | bundle.append('/live/clip/note', (trackNumber, clipNumber, pitch, time, duration, velocity, muted)) 600 | self.oscEndpoint.sendMessage(bundle) 601 | 602 | def armTrackCB(self, msg, source): 603 | """Called when a /live/arm message is received. 604 | 605 | Messages: 606 | /live/arm (int track) (int armed/disarmed) Arms track number track 607 | """ 608 | track = msg[2] 609 | 610 | if len(msg) == 4: 611 | if msg[3] == 1: 612 | LiveUtils.armTrack(track) 613 | else: 614 | LiveUtils.disarmTrack(track) 615 | # Return arm status 616 | elif len(msg) == 3: 617 | status = LiveUtils.getTrack(track).arm 618 | self.oscEndpoint.send("/live/arm", (track, int(status))) 619 | 620 | def muteTrackCB(self, msg, source): 621 | """Called when a /live/mute message is received. 622 | 623 | Messages: 624 | /live/mute (int track) Mutes track number track 625 | """ 626 | ty = msg[0] == '/live/return/mute' and 1 or 0 627 | track = msg[2] 628 | 629 | if len(msg) == 4: 630 | if msg[3] == 1: 631 | LiveUtils.muteTrack(track, ty) 632 | else: 633 | LiveUtils.unmuteTrack(track, ty) 634 | 635 | elif len(msg) == 3: 636 | if ty == 1: 637 | status = LiveUtils.getSong().return_tracks[track].mute 638 | self.oscEndpoint.send("/live/return/mute", (track, int(status))) 639 | 640 | else: 641 | status = LiveUtils.getTrack(track).mute 642 | self.oscEndpoint.send("/live/mute", (track, int(status))) 643 | 644 | def soloTrackCB(self, msg, source): 645 | """Called when a /live/solo message is received. 646 | 647 | Messages: 648 | /live/solo (int track) Solos track number track 649 | """ 650 | ty = msg[0] == '/live/return/solo' and 1 or 0 651 | track = msg[2] 652 | 653 | if len(msg) == 4: 654 | if msg[3] == 1: 655 | LiveUtils.soloTrack(track, ty) 656 | else: 657 | LiveUtils.unsoloTrack(track, ty) 658 | 659 | elif len(msg) == 3: 660 | if ty == 1: 661 | status = LiveUtils.getSong().return_tracks[track].solo 662 | self.oscEndpoint.send("/live/return/solo", (track, int(status))) 663 | 664 | else: 665 | status = LiveUtils.getTrack(track).solo 666 | self.oscEndpoint.send("/live/solo", (track, int(status))) 667 | 668 | def volumeCB(self, msg, source): 669 | """Called when a /live/volume message is received. 670 | 671 | Messages: 672 | /live/volume (int track) Returns the current volume of track number track as: /live/volume (int track, float volume(0.0 to 1.0)) 673 | /live/volume (int track, float volume(0.0 to 1.0)) Sets track number track's volume to volume 674 | """ 675 | if msg[0] == '/live/return/volume': 676 | ty = 1 677 | elif msg[0] == '/live/master/volume': 678 | ty = 2 679 | else: 680 | ty = 0 681 | 682 | if len(msg) == 2 and ty == 2: 683 | self.oscEndpoint.send("/live/master/volume", LiveUtils.getSong().master_track.mixer_device.volume.value) 684 | 685 | elif len(msg) == 3 and ty == 2: 686 | volume = msg[2] 687 | LiveUtils.getSong().master_track.mixer_device.volume.value = volume 688 | 689 | elif len(msg) == 4: 690 | track = msg[2] 691 | volume = msg[3] 692 | 693 | if ty == 0: 694 | LiveUtils.trackVolume(track, volume) 695 | elif ty == 1: 696 | LiveUtils.getSong().return_tracks[track].mixer_device.volume.value = volume 697 | 698 | elif len(msg) == 3: 699 | track = msg[2] 700 | 701 | if ty == 1: 702 | self.oscEndpoint.send("/live/return/volume", (track, LiveUtils.getSong().return_tracks[track].mixer_device.volume.value)) 703 | 704 | else: 705 | self.oscEndpoint.send("/live/volume", (track, LiveUtils.trackVolume(track))) 706 | 707 | def panCB(self, msg, source): 708 | """Called when a /live/pan message is received. 709 | 710 | Messages: 711 | /live/pan (int track) Returns the pan of track number track as: /live/pan (int track, float pan(-1.0 to 1.0)) 712 | /live/pan (int track, float pan(-1.0 to 1.0)) Sets track number track's pan to pan 713 | 714 | """ 715 | if msg[0] == '/live/return/pan': 716 | ty = 1 717 | elif msg[0] == '/live/master/pan': 718 | ty = 2 719 | else: 720 | ty = 0 721 | 722 | if len(msg) == 2 and ty == 2: 723 | self.oscEndpoint.send("/live/master/pan", LiveUtils.getSong().master_track.mixer_device.panning.value) 724 | 725 | elif len(msg) == 3 and ty == 2: 726 | pan = msg[2] 727 | LiveUtils.getSong().master_track.mixer_device.panning.value = pan 728 | 729 | elif len(msg) == 4: 730 | track = msg[2] 731 | pan = msg[3] 732 | 733 | if ty == 0: 734 | LiveUtils.trackPan(track, pan) 735 | elif ty == 1: 736 | LiveUtils.getSong().return_tracks[track].mixer_device.panning.value = pan 737 | 738 | elif len(msg) == 3: 739 | track = msg[2] 740 | 741 | if ty == 1: 742 | self.oscEndpoint.send("/live/pan", (track, LiveUtils.getSong().return_tracks[track].mixer_device.panning.value)) 743 | 744 | else: 745 | self.oscEndpoint.send("/live/pan", (track, LiveUtils.trackPan(track))) 746 | 747 | 748 | def sendCB(self, msg, source): 749 | """Called when a /live/send message is received. 750 | 751 | Messages: 752 | /live/send (int track, int send) Returns the send level of send (send) on track number track as: /live/send (int track, int send, float level(0.0 to 1.0)) 753 | /live/send (int track, int send, float level(0.0 to 1.0)) Sets the send (send) of track number (track)'s level to (level) 754 | 755 | """ 756 | ty = msg[0] == '/live/return/send' and 1 or 0 757 | track = msg[2] 758 | 759 | if len(msg) == 5: 760 | send = msg[3] 761 | level = msg[4] 762 | if ty == 1: 763 | LiveUtils.getSong().return_tracks[track].mixer_device.sends[send].value = level 764 | 765 | else: 766 | LiveUtils.trackSend(track, send, level) 767 | 768 | elif len(msg) == 4: 769 | send = msg[3] 770 | if ty == 1: 771 | self.oscEndpoint.send("/live/return/send", (track, send, float(LiveUtils.getSong().return_tracks[track].mixer_device.sends[send].value))) 772 | 773 | else: 774 | self.oscEndpoint.send("/live/send", (track, send, float(LiveUtils.trackSend(track, send)))) 775 | 776 | elif len(msg) == 3: 777 | if ty == 1: 778 | sends = LiveUtils.getSong().return_tracks[track].mixer_device.sends 779 | else: 780 | sends = LiveUtils.getSong().visible_tracks[track].mixer_device.sends 781 | 782 | so = [track] 783 | for i in range(len(sends)): 784 | so.append(i) 785 | so.append(float(sends[i].value)) 786 | 787 | if ty == 1: 788 | self.oscEndpoint.send("/live/return/send", tuple(so)) 789 | else: 790 | self.oscEndpoint.send("/live/send", tuple(so)) 791 | 792 | 793 | 794 | def pitchCB(self, msg, source): 795 | """Called when a /live/pitch message is received. 796 | 797 | Messages: 798 | /live/pitch (int track, int clip) Returns the pan of track number track as: /live/pan (int track, int clip, int coarse(-48 to 48), int fine (-50 to 50)) 799 | /live/pitch (int track, int clip, int coarse(-48 to 48), int fine (-50 to 50)) Sets clip number clip in track number track's pitch to coarse / fine 800 | 801 | """ 802 | if len(msg) == 6: 803 | track = msg[2] 804 | clip = msg[3] 805 | coarse = msg[4] 806 | fine = msg[5] 807 | LiveUtils.clipPitch(track, clip, coarse, fine) 808 | if len(msg) ==4: 809 | track = msg[2] 810 | clip = msg[3] 811 | self.oscEndpoint.send("/live/pitch", LiveUtils.clipPitch(track, clip)) 812 | 813 | def trackJump(self, msg, source): 814 | """Called when a /live/track/jump message is received. 815 | 816 | Messages: 817 | /live/track/jump (int track, float beats) Jumps in track's currently running session clip by beats 818 | """ 819 | if len(msg) == 4: 820 | track = msg[2] 821 | beats = msg[3] 822 | track = LiveUtils.getTrack(track) 823 | track.jump_in_running_session_clip(beats) 824 | 825 | def trackInfoCB(self, msg, source): 826 | """Called when a /live/track/info message is received. 827 | 828 | Messages: 829 | /live/track/info (int track) Returns trackId, arm, solo, mute, volume, panning 830 | """ 831 | 832 | if len(msg) == 3: 833 | tracknum = msg[2] 834 | track = LiveUtils.getTrack(tracknum) 835 | arm = track.arm and 1 or 0 836 | solo = track.solo and 1 or 0 837 | mute = track.mute and 1 or 0 838 | audio = track.has_audio_input and 1 or 0 839 | volume = track.mixer_device.volume.value; 840 | panning = track.mixer_device.panning.value; 841 | self.oscEndpoint.send("/live/track/info", (tracknum, arm, solo, mute, audio, volume, panning)) 842 | 843 | def returnInfoCB(self, msg, source): 844 | """Called when a /live/return/info message is received. 845 | 846 | Messages: 847 | /live/return/info (int track) Returns trackId, solo, mute, volume, panning 848 | """ 849 | 850 | if len(msg) == 3: 851 | tracknum = msg[2] 852 | track = LiveUtils.getSong().return_tracks[tracknum] 853 | solo = track.solo and 1 or 0 854 | mute = track.mute and 1 or 0 855 | volume = track.mixer_device.volume.value; 856 | panning = track.mixer_device.panning.value; 857 | self.oscEndpoint.send("/live/return/info", (tracknum, solo, mute, volume, panning)) 858 | 859 | def undoCB(self, msg, source): 860 | """Called when a /live/undo message is received. 861 | 862 | Messages: 863 | /live/undo Requests the song to undo the last action 864 | """ 865 | LiveUtils.getSong().undo() 866 | 867 | def redoCB(self, msg, source): 868 | """Called when a /live/redo message is received. 869 | 870 | Messages: 871 | /live/redo Requests the song to redo the last action 872 | """ 873 | LiveUtils.getSong().redo() 874 | 875 | def playClipSlotCB(self, msg, source): 876 | """Called when a /live/play/clipslot message is received. 877 | 878 | Messages: 879 | /live/play/clipslot (int track, int clip) Launches clip number clip in track number track 880 | """ 881 | if len(msg) == 4: 882 | track_num = msg[2] 883 | clip_num = msg[3] 884 | track = LiveUtils.getTrack(track_num) 885 | clipslot = track.clip_slots[clip_num] 886 | clipslot.fire() 887 | 888 | def viewSceneCB(self, msg, source): 889 | """Called when a /live/scene/view message is received. 890 | 891 | Messages: 892 | /live/scene/view (int track) Selects a track to view 893 | """ 894 | 895 | if len(msg) == 3: 896 | scene = msg[2] 897 | LiveUtils.getSong().view.selected_scene = LiveUtils.getSong().scenes[scene] 898 | 899 | def viewTrackCB(self, msg, source): 900 | """Called when a /live/track/view message is received. 901 | 902 | Messages: 903 | /live/track/view (int track) Selects a track to view 904 | """ 905 | ty = msg[0] == '/live/return/view' and 1 or 0 906 | track_num = msg[2] 907 | 908 | if len(msg) == 3: 909 | if ty == 1: 910 | track = LiveUtils.getSong().return_tracks[track_num] 911 | else: 912 | track = LiveUtils.getSong().visible_tracks[track_num] 913 | 914 | LiveUtils.getSong().view.selected_track = track 915 | Live.Application.get_application().view.show_view("Detail/DeviceChain") 916 | 917 | #track.view.select_instrument() 918 | 919 | def mviewTrackCB(self, msg, source): 920 | """Called when a /live/master/view message is received. 921 | 922 | Messages: 923 | /live/track/view (int track) Selects a track to view 924 | """ 925 | track = LiveUtils.getSong().master_track 926 | 927 | LiveUtils.getSong().view.selected_track = track 928 | Live.Application.get_application().view.show_view("Detail/DeviceChain") 929 | 930 | #track.view.select_instrument() 931 | 932 | def viewClipCB(self, msg, source): 933 | """Called when a /live/clip/view message is received. 934 | 935 | Messages: 936 | /live/clip/view (int track, int clip) Selects a track to view 937 | """ 938 | track = LiveUtils.getSong().visible_tracks[msg[2]] 939 | 940 | if len(msg) == 4: 941 | clip = msg[3] 942 | else: 943 | clip = 0 944 | 945 | LiveUtils.getSong().view.selected_track = track 946 | LiveUtils.getSong().view.detail_clip = track.clip_slots[clip].clip 947 | Live.Application.get_application().view.show_view("Detail/Clip") 948 | 949 | def detailViewCB(self, msg, source): 950 | """Called when a /live/detail/view message is received. Used to switch between clip/track detail 951 | 952 | Messages: 953 | /live/detail/view (int) Selects view where 0=clip detail, 1=track detail 954 | """ 955 | if len(msg) == 3: 956 | if msg[2] == 0: 957 | Live.Application.get_application().view.show_view("Detail/Clip") 958 | elif msg[2] == 1: 959 | Live.Application.get_application().view.show_view("Detail/DeviceChain") 960 | 961 | def viewDeviceCB(self, msg, source): 962 | """Called when a /live/track/device/view message is received. 963 | 964 | Messages: 965 | /live/track/device/view (int track) Selects a track to view 966 | """ 967 | ty = msg[0] == '/live/return/device/view' and 1 or 0 968 | track_num = msg[2] 969 | 970 | if len(msg) == 4: 971 | if ty == 1: 972 | track = LiveUtils.getSong().return_tracks[track_num] 973 | else: 974 | track = LiveUtils.getSong().visible_tracks[track_num] 975 | 976 | LiveUtils.getSong().view.selected_track = track 977 | LiveUtils.getSong().view.select_device(track.devices[msg[3]]) 978 | Live.Application.get_application().view.show_view("Detail/DeviceChain") 979 | 980 | def mviewDeviceCB(self, msg, source): 981 | track = LiveUtils.getSong().master_track 982 | 983 | if len(msg) == 3: 984 | LiveUtils.getSong().view.selected_track = track 985 | LiveUtils.getSong().view.select_device(track.devices[msg[2]]) 986 | Live.Application.get_application().view.show_view("Detail/DeviceChain") 987 | 988 | def overdubCB(self, msg, source): 989 | """Called when a /live/overdub message is received. 990 | 991 | Messages: 992 | /live/overdub (int on/off) Enables/disables overdub 993 | """ 994 | if len(msg) == 3: 995 | overdub = msg[2] 996 | LiveUtils.getSong().overdub = overdub 997 | 998 | def stateCB(self, msg, source): 999 | """Called when a /live/state is received. 1000 | 1001 | Messages: 1002 | /live/state Returns the current tempo and overdub status 1003 | """ 1004 | if len(msg) == 2 or (len(msg) == 3 and msg[2] == "query"): 1005 | tempo = LiveUtils.getTempo() 1006 | overdub = LiveUtils.getSong().overdub 1007 | self.oscEndpoint.send("/live/state", (tempo, int(overdub))) 1008 | 1009 | def clipInfoCB(self,msg, source): 1010 | """Called when a /live/clip/info message is received. 1011 | 1012 | Messages: 1013 | /live/clip/info (int track, int clip) Gets the status of a single clip in the form /live/clip/info (tracknumber, clipnumber, state, length) 1014 | [state: 1 = Has Clip, 2 = Playing, 3 = Triggered] 1015 | """ 1016 | 1017 | if len(msg) == 4: 1018 | trackNumber = msg[2] 1019 | clipNumber = msg[3] 1020 | 1021 | clip = LiveUtils.getClip(trackNumber, clipNumber) 1022 | 1023 | playing = 0 1024 | length = 0 1025 | 1026 | if clip != None: 1027 | length = clip.length 1028 | playing = 1 1029 | 1030 | if clip.is_playing == 1: 1031 | playing = 2 1032 | elif clip.is_triggered == 1: 1033 | playing = 3 1034 | 1035 | self.oscEndpoint.send("/live/clip/info", (trackNumber, clipNumber, playing, length)) 1036 | 1037 | return 1038 | 1039 | def deviceCB(self, msg, source): 1040 | ty = msg[0] == '/live/return/device' and 1 or 0 1041 | track = msg[2] 1042 | 1043 | if len(msg) == 4: 1044 | device = msg[3] 1045 | po = [track, device] 1046 | 1047 | if ty == 1: 1048 | params = LiveUtils.getSong().return_tracks[track].devices[device].parameters 1049 | else: 1050 | params = LiveUtils.getSong().visible_tracks[track].devices[device].parameters 1051 | 1052 | for i in range(len(params)): 1053 | po.append(i) 1054 | po.append(float(params[i].value)) 1055 | po.append(str(params[i].name)) 1056 | 1057 | self.oscEndpoint.send(ty == 1 and "/live/return/device/allparam" or "/live/device/allparam", tuple(po)) 1058 | 1059 | elif len(msg) == 5: 1060 | device = msg[3] 1061 | param = msg[4] 1062 | 1063 | if ty == 1: 1064 | p = LiveUtils.getSong().return_tracks[track].devices[device].parameters[param] 1065 | else: 1066 | p = LiveUtils.getSong().visible_tracks[track].devices[device].parameters[param] 1067 | 1068 | self.oscEndpoint.send(ty == 1 and "/live/return/device/param" or "/live/device/param", (track, device, param, p.value, str(p.name))) 1069 | 1070 | 1071 | elif len(msg) == 6: 1072 | device = msg[3] 1073 | param = msg[4] 1074 | value = msg[5] 1075 | 1076 | if ty == 1: 1077 | LiveUtils.getSong().return_tracks[track].devices[device].parameters[param].value = value 1078 | else: 1079 | LiveUtils.getSong().visible_tracks[track].devices[device].parameters[param].value = value 1080 | 1081 | def devicerangeCB(self, msg, source): 1082 | ty = msg[0] == '/live/return/device/range' and 1 or 0 1083 | track = msg[2] 1084 | 1085 | if len(msg) == 4: 1086 | device = msg[3] 1087 | po = [track, device] 1088 | 1089 | if ty == 1: 1090 | params = LiveUtils.getSong().return_tracks[track].devices[device].parameters 1091 | else: 1092 | params = LiveUtils.getSong().visible_tracks[track].devices[device].parameters 1093 | 1094 | for i in range(len(params)): 1095 | po.append(i) 1096 | po.append(params[i].min) 1097 | po.append(params[i].max) 1098 | 1099 | self.oscEndpoint.send(ty == 1 and "/live/return/device/range" or "/live/device/range", tuple(po)) 1100 | 1101 | elif len(msg) == 5: 1102 | device = msg[3] 1103 | param = msg[4] 1104 | 1105 | if ty == 1: 1106 | p = LiveUtils.getSong().return_tracks[track].devices[device].parameters[param] 1107 | else: 1108 | p = LiveUtils.getSong().visible_tracks[track].devices[device].parameters[param] 1109 | 1110 | self.oscEndpoint.send(ty == 1 and "/live/return/device/range" or "/live/device/range", (track, device, param, p.min, p.max)) 1111 | 1112 | def devicelistCB(self, msg, source): 1113 | ty = msg[0] == '/live/return/devicelist' and 1 or 0 1114 | 1115 | track = msg[2] 1116 | 1117 | if len(msg) == 3: 1118 | do = [track] 1119 | 1120 | if ty == 1: 1121 | devices = LiveUtils.getSong().return_tracks[track].devices 1122 | else: 1123 | devices = LiveUtils.getSong().visible_tracks[track].devices 1124 | 1125 | for i in range(len(devices)): 1126 | do.append(i) 1127 | do.append(str(devices[i].name)) 1128 | 1129 | self.oscEndpoint.send(ty == 1 and "/live/return/devicelist" or "/live/devicelist", tuple(do)) 1130 | 1131 | def mdeviceCB(self, msg, source): 1132 | if len(msg) == 3: 1133 | device = msg[2] 1134 | po = [device] 1135 | 1136 | params = LiveUtils.getSong().master_track.devices[device].parameters 1137 | 1138 | for i in range(len(params)): 1139 | po.append(i) 1140 | po.append(float(params[i].value)) 1141 | po.append(str(params[i].name)) 1142 | 1143 | self.oscEndpoint.send("/live/master/device", tuple(po)) 1144 | 1145 | elif len(msg) == 4: 1146 | device = msg[2] 1147 | param = msg[3] 1148 | 1149 | p = LiveUtils.getSong().master_track.devices[device].parameters[param] 1150 | 1151 | self.oscEndpoint.send("/live/master/device", (device, param, p.value, str(p.name))) 1152 | 1153 | elif len(msg) == 5: 1154 | device = msg[2] 1155 | param = msg[3] 1156 | value = msg[4] 1157 | 1158 | LiveUtils.getSong().master_track.devices[device].parameters[param].value = value 1159 | 1160 | def mdevicerangeCB(self, msg, source): 1161 | if len(msg) == 3: 1162 | device = msg[2] 1163 | po = [device] 1164 | 1165 | params = LiveUtils.getSong().master_track.devices[device].parameters 1166 | 1167 | for i in range(len(params)): 1168 | po.append(i) 1169 | po.append(params[i].max) 1170 | po.append(params[i].min) 1171 | 1172 | self.oscEndpoint.send("/live/master/device/range", tuple(po)) 1173 | 1174 | elif len(msg) == 4: 1175 | device = msg[2] 1176 | param = msg[3] 1177 | 1178 | p = LiveUtils.getSong().master_track.devices[device].parameters[param] 1179 | 1180 | self.oscEndpoint.send("/live/master/device/range", (device, param, p.min, p.max)) 1181 | 1182 | def mdevicelistCB(self, msg, source): 1183 | if len(msg) == 2 or (len(msg) == 3 and msg[2] == "query"): 1184 | do = [] 1185 | devices = LiveUtils.getSong().master_track.devices 1186 | 1187 | for i in range(len(devices)): 1188 | do.append(i) 1189 | do.append(str(devices[i].name)) 1190 | 1191 | self.oscEndpoint.send("/live/master/devicelist", tuple(do)) 1192 | 1193 | 1194 | def crossfaderCB(self, msg, source): 1195 | if len(msg) == 2 or (len(msg) == 3 and msg[2] == "query"): 1196 | self.oscEndpoint.send("/live/master/crossfader", float(LiveUtils.getSong().master_track.mixer_device.crossfader.value)) 1197 | 1198 | elif len(msg) == 3: 1199 | val = msg[2] 1200 | LiveUtils.getSong().master_track.mixer_device.crossfader.value = val 1201 | 1202 | 1203 | def loopStateCB(self, msg, source): 1204 | type = msg[0] == '/live/clip/loopstate_id' and 1 or 0 1205 | 1206 | trackNumber = msg[2] 1207 | clipNumber = msg[3] 1208 | 1209 | if len(msg) == 4: 1210 | if type == 1: 1211 | self.oscEndpoint.send("/live/clip/loopstate", (trackNumber, clipNumber, int(LiveUtils.getClip(trackNumber, clipNumber).looping))) 1212 | else: 1213 | self.oscEndpoint.send("/live/clip/loopstate", (int(LiveUtils.getClip(trackNumber, clipNumber).looping))) 1214 | 1215 | elif len(msg) == 5: 1216 | loopState = msg[4] 1217 | LiveUtils.getClip(trackNumber, clipNumber).looping = loopState 1218 | 1219 | def loopStartCB(self, msg, source): 1220 | type = msg[0] == '/live/clip/loopstart_id' and 1 or 0 1221 | 1222 | trackNumber = msg[2] 1223 | clipNumber = msg[3] 1224 | 1225 | if len(msg) == 4: 1226 | if type == 1: 1227 | self.oscEndpoint.send("/live/clip/loopstart", (trackNumber, clipNumber, float(LiveUtils.getClip(trackNumber, clipNumber).loop_start))) 1228 | else: 1229 | self.oscEndpoint.send("/live/clip/loopstart", (float(LiveUtils.getClip(trackNumber, clipNumber).loop_start))) 1230 | 1231 | elif len(msg) == 5: 1232 | loopStart = msg[4] 1233 | LiveUtils.getClip(trackNumber, clipNumber).loop_start = loopStart 1234 | 1235 | def loopEndCB(self, msg, source): 1236 | type = msg[0] == '/live/clip/loopend_id' and 1 or 0 1237 | 1238 | trackNumber = msg[2] 1239 | clipNumber = msg[3] 1240 | if len(msg) == 4: 1241 | if type == 1: 1242 | self.oscEndpoint.send("/live/clip/loopend", (trackNumber, clipNumber, float(LiveUtils.getClip(trackNumber, clipNumber).loop_end))) 1243 | else: 1244 | self.oscEndpoint.send("/live/clip/loopend", (float(LiveUtils.getClip(trackNumber, clipNumber).loop_end))) 1245 | 1246 | elif len(msg) == 5: 1247 | loopEnd = msg[4] 1248 | LiveUtils.getClip(trackNumber, clipNumber).loop_end = loopEnd 1249 | 1250 | def quantizationCB(self, msg, source): 1251 | quant = msg[2] 1252 | LiveUtils.getSong().clip_trigger_quantization = quant 1253 | 1254 | -------------------------------------------------------------------------------- /LiveUtils.py: -------------------------------------------------------------------------------- 1 | """ 2 | # LiveUtils, a collection of simple utility functions for controlling Ableton Live 3 | # Copyright (C) 2007 Rob King (rob@e-mu.org) 4 | # 5 | # This library is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU Lesser General Public 7 | # License as published by the Free Software Foundation; either 8 | # version 2.1 of the License, or (at your option) any later version. 9 | # 10 | # This library 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 GNU 13 | # Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public 16 | # License along with this library; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | # 19 | # For questions regarding this module contact 20 | # Rob King or visit http://www.e-mu.org 21 | """ 22 | 23 | import Live 24 | 25 | def getSong(): 26 | """Gets a the current Song instance""" 27 | return Live.Application.get_application().get_document() 28 | 29 | def continuePlaying(): 30 | """Continues Playing""" 31 | getSong().continue_playing() 32 | 33 | def playSelection(): 34 | """Plays the current selection""" 35 | getSong().play_selection() 36 | 37 | def jumpBy(time): 38 | """Jumps the playhead relative to it's current position by time. Stops playback.""" 39 | getSong().jump_by(time) 40 | 41 | def scrubBy(time): 42 | """Jumps the playhead relative to it's current position by time. Does not stop playback""" 43 | getSong().scrub_by(time) 44 | 45 | def play(): 46 | """Starts Ableton Playing""" 47 | print "playing" 48 | getSong().start_playing() 49 | 50 | def stopClips(): 51 | """Stops all currently playing clips""" 52 | getSong().stop_all_clips() 53 | 54 | def stop(): 55 | """Stops Ableton""" 56 | getSong().stop_playing() 57 | 58 | def currentTime(time = None): 59 | """Sets/Returns the current song time""" 60 | song = getSong() 61 | if time is not None: 62 | song.current_song_time = time 63 | return getSong().current_song_time 64 | 65 | def getScenes(): 66 | """Returns a list of scenes""" 67 | return getSong().scenes 68 | 69 | def getScene(num): 70 | """Returns scene number (num) (starting at 0)""" 71 | return getSong().scenes[num] 72 | 73 | def launchScene(scene): 74 | """Launches scene number (scene)""" 75 | getScene(scene).fire() 76 | 77 | def getTracks(): 78 | """Returns a list of tracks""" 79 | return getSong().visible_tracks 80 | 81 | def getTrack(num): 82 | """Returns track number (num) (starting at 0)""" 83 | return getSong().visible_tracks[num] 84 | 85 | def stopTrack(trackNumber): 86 | """Stops all clips in track number (trackNumber)""" 87 | track = getTrack(trackNumber) 88 | for clipSlot in track.clip_slots: 89 | clipSlot.stop() 90 | 91 | def getTempo(): 92 | """Returns the current song tempo""" 93 | return getSong().tempo 94 | 95 | def setTempo(tempo): 96 | getSong().tempo = tempo 97 | 98 | def jumpToNextCue(): 99 | getSong().jump_to_next_cue() 100 | 101 | def jumpToPrevCue(): 102 | getSong().jump_to_prev_cue() 103 | 104 | def armTrack(num): 105 | """Arms track number (num)""" 106 | getTrack(num).arm = 1 107 | 108 | def disarmTrack(num): 109 | """Disarms track number (num)""" 110 | getTrack(num).arm = 0 111 | 112 | def toggleArmTrack(num): 113 | """Toggles the armed state of track number (num)""" 114 | armed = getTrack(num).arm 115 | if armed: 116 | getTrack(num).arm = 0 117 | else: 118 | getTrack(num).arm = 1 119 | 120 | def muteTrack(track, ty = 0): 121 | """Mutes track number (num)""" 122 | if ty == 1: 123 | getSong().return_tracks[track].mute = 1 124 | else: 125 | getTrack(track).mute = 1 126 | 127 | def unmuteTrack(track, ty = 0): 128 | """Unmutes track number (num)""" 129 | if ty == 1: 130 | getSong().return_tracks[track].mute = 0 131 | else: 132 | getTrack(track).mute = 0 133 | 134 | def toggleMuteTrack(num): 135 | """Toggles the muted state of track number (num)""" 136 | muted = getTrack(num).mute 137 | if muted: 138 | getTrack(num).mute = 0 139 | else: 140 | getTrack(num).mute = 1 141 | 142 | def soloTrack(track, ty = 0): 143 | """Solo's track number (num)""" 144 | if ty == 1: 145 | getSong().return_tracks[track].solo = 1 146 | else: 147 | getTrack(track).solo = 1 148 | 149 | def unsoloTrack(track, ty = 0): 150 | """Un-solos track number (num)""" 151 | if ty == 1: 152 | getSong().return_tracks[track].solo = 0 153 | else: 154 | getTrack(track).solo = 0 155 | 156 | def toggleSoloTrack(num): 157 | """Toggles the soloed state of track number (num)""" 158 | soloed = getTrack(num).solo 159 | if soloed: 160 | getTrack(num).solo = 0 161 | else: 162 | getTrack(num).solo = 1 163 | 164 | def trackVolume(track, volume = None): 165 | """Gets/Changes the volume of track (track) 166 | 167 | If (volume) is specified, changes the volume of track number 168 | (track) to (volume), a value between 0.0 and 1.0. 169 | """ 170 | if volume != None: 171 | getTrack(track).mixer_device.volume.value = volume 172 | return getTrack(track).mixer_device.volume.value 173 | 174 | def trackPan(track, pan = None): 175 | """Gets/Changes the panning of track number (track) 176 | 177 | If (pan) is specified, changes the panning to (pan). 178 | (pan) should be a value between -1.0 to 1.0 179 | """ 180 | if pan != None: 181 | getTrack(track).mixer_device.panning.value = pan 182 | return getTrack(track).mixer_device.panning.value 183 | 184 | def trackSend(track, send = None, level=None): 185 | """Gets/Changes the level of send number (send) on track (track). 186 | 187 | If (level) is specified, the level of the send is set to (level), 188 | a value between 0.0 and 1.0 189 | """ 190 | if send == None: 191 | return getTrack(track).mixer_device.sends 192 | if level != None: 193 | getTrack(track).mixer_device.sends[send].value = level 194 | return getTrack(track).mixer_device.sends[send].value 195 | 196 | def trackName(track, name = None): 197 | """Gets/Changes the name of track (track). 198 | 199 | If (name) is specified, the track name is changed 200 | """ 201 | if name != None: 202 | getTrack(track).name = name 203 | return str(getTrack(track).name) 204 | 205 | def getClipSlots(): 206 | """Gets a 2D list of all the clip slots in the song""" 207 | tracks = getTracks() 208 | clipSlots = [] 209 | for track in tracks: 210 | clipSlots.append(track.clip_slots) 211 | return clipSlots 212 | 213 | def getClips(): 214 | """Gets a 2D list of all the clip in the song. 215 | 216 | If there is no clip in a clip slot, None is returned 217 | 218 | """ 219 | tracks = getTracks() 220 | clips = [] 221 | for track in getClipSlots(): 222 | trackClips = [] 223 | for clipSlot in track: 224 | trackClips.append(clipSlot.clip) 225 | clips.append(trackClips) 226 | return clips 227 | 228 | def launchClip(track, clip): 229 | """Launches clip number (clip) in track number (track)""" 230 | getClip(track, clip).fire() 231 | 232 | def stopClip(track, clip): 233 | """Stops clip number (clip) in track (track)""" 234 | getClip(track, clip).stop() 235 | 236 | def getClip(track, clip): 237 | """Returns clip number (clip) in track (track)""" 238 | # painful code! 239 | #clips = getClips() 240 | #return clips[track][clip] 241 | return getSong().visible_tracks[track].clip_slots[clip].clip 242 | 243 | def clipName(track, clip, name = None): 244 | """Gets/changes the name of clip number (clip) in track (track) 245 | 246 | In (name) is specified, the name of the clip is changed 247 | 248 | """ 249 | if name != None: 250 | getClip(track, clip).name = name 251 | return str(getClip(track, clip).name) 252 | 253 | def clipPitch(trackNum, clipNum, coarse = None, fine = None): 254 | """Gets/changes the coarse and fine pitch shift of clip (clip) in track (track). 255 | 256 | If (coarse) or (fine) are specified, changes the clip's pitch. 257 | """ 258 | clip = getClip(trackNum, clipNum) 259 | if coarse != None: 260 | clip.pitch_coarse = coarse 261 | if fine != None: 262 | clip.pitch_fine = fine 263 | return (trackNum, clipNum, clip.pitch_coarse, clip.pitch_fine) 264 | -------------------------------------------------------------------------------- /LogServer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import SocketServer 4 | import time 5 | import sys 6 | 7 | class LoggerRequestHandler(SocketServer.BaseRequestHandler): 8 | def setup(self): 9 | print self.client_address, 'connected!' 10 | 11 | def handle(self): 12 | while 1: 13 | time.sleep(0.01) 14 | data = self.request.recv(1024) 15 | if len(data) > 0: 16 | sys.stdout.write(data) 17 | 18 | def finish(self): 19 | print self.client_address, 'disconnected!' 20 | 21 | if __name__=='__main__': 22 | SocketServer.ThreadingTCPServer.allow_reuse_address = True 23 | server = SocketServer.ThreadingTCPServer(('', 4444), LoggerRequestHandler) 24 | server.serve_forever() 25 | 26 | -------------------------------------------------------------------------------- /Logger.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | 4 | try: 5 | import socket 6 | except: 7 | print "No Sockets" 8 | 9 | class Logger: 10 | """ 11 | Simple logger. 12 | Tries to use a socket which connects to localhost port 4444 by default. 13 | If that fails then it logs to a file 14 | """ 15 | def __init__(self): 16 | try: 17 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 18 | except: 19 | print "Couldn't create socket" 20 | self.socket = None 21 | 22 | self.connected = 0 23 | 24 | if self.socket: 25 | try: 26 | self.socket.connect(("localhost", 4444)) 27 | self.connected = 1 28 | self.stderr = sys.stderr 29 | sys.stderr = self 30 | except: 31 | print "Couldn't connect socket" 32 | 33 | self.buf = "" 34 | 35 | def log(self,msg): 36 | if self.connected: 37 | self.send(msg + '\n') 38 | else: 39 | print(msg) 40 | 41 | def send(self,msg): 42 | if self.connected: 43 | self.socket.send(msg) 44 | 45 | def close(self): 46 | if self.connected: 47 | self.socket.send("Closing..") 48 | self.socket.close() 49 | 50 | def write(self, msg): 51 | self.stderr.write(msg) 52 | self.buf = self.buf + msg 53 | lines = self.buf.split("\n", 2) 54 | if len(lines) == 2: 55 | self.send("STDERR: " + lines[0] + "\n") 56 | self.buf = lines[1] 57 | 58 | logger = Logger() 59 | 60 | def log(*args): 61 | text = '' 62 | for arg in args: 63 | if text != '': 64 | text = text + ' ' 65 | text = text + str(arg) 66 | if logger != None: 67 | logger.log(text) 68 | 69 | -------------------------------------------------------------------------------- /OSC.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Open SoundControl for Python 4 | # Copyright (C) 2002 Daniel Holth, Clinton McChesney 5 | # 6 | # This library is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation; either 9 | # version 2.1 of the License, or (at your option) any later version. 10 | # 11 | # This library is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | # Lesser General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Lesser General Public 17 | # License along with this library; if not, write to the Free Software 18 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | # 20 | # For questions regarding this module contact 21 | # Daniel Holth or visit 22 | # http://www.stetson.edu/~ProctoLogic/ 23 | # 24 | # Changelog: 25 | # 15 Nov. 2001: 26 | # Removed dependency on Python 2.0 features. 27 | # - dwh 28 | # 13 Feb. 2002: 29 | # Added a generic callback handler. 30 | # - dwh 31 | # 32 | # Updated June 2007 by Hans Huebner (hans.huebner@gmail.com) 33 | # Improved bundle support, API cleanup 34 | 35 | import sys 36 | import struct 37 | import math 38 | import string 39 | import time 40 | 41 | from Logger import log 42 | 43 | def hexDump(bytes): 44 | """Useful utility; prints the string in hexadecimal""" 45 | for i in range(len(bytes)): 46 | sys.stdout.write("%2x " % (ord(bytes[i]))) 47 | if (i+1) % 8 == 0: 48 | print repr(bytes[i-7:i+1]) 49 | 50 | if(len(bytes) % 8 != 0): 51 | print string.rjust("", 11), repr(bytes[i-7:i+1]) 52 | 53 | class OSCMessage: 54 | """Builds typetagged OSC messages.""" 55 | def __init__(self, address='', msg=()): 56 | self.address = address 57 | self.typetags = "," 58 | self.message = "" 59 | 60 | if type(msg) in (str, int, float): 61 | self.append(msg) 62 | elif type(msg) in (list,tuple): 63 | for m in msg: 64 | if type(m) not in (str,int,float): 65 | log("don't know how to encode message element " + str(m) + " " + str(type(m))) 66 | return 67 | self.append(m) 68 | else: 69 | log("don't know how to encode message " + str(m) + " " + str(type(m))) 70 | return 71 | 72 | def append(self, argument, typehint = None): 73 | """Appends data to the message, 74 | updating the typetags based on 75 | the argument's type. 76 | If the argument is a blob (counted string) 77 | pass in 'b' as typehint.""" 78 | 79 | if typehint == 'b': 80 | binary = OSCBlob(argument) 81 | else: 82 | binary = OSCArgument(argument) 83 | 84 | self.typetags = self.typetags + binary[0] 85 | self.message = self.message + binary[1] 86 | 87 | def getBinary(self): 88 | """Returns the binary message (so far) with typetags.""" 89 | address = OSCArgument(self.address)[1] 90 | typetags = OSCArgument(self.typetags)[1] 91 | return address + typetags + self.message 92 | 93 | def __repr__(self): 94 | return self.getBinary() 95 | 96 | JAN_1970 = 2208988800L 97 | SECS_TO_PICOS = 4294967296L 98 | def abs_to_timestamp(abs): 99 | """ since 1970 => since 1900 64b OSC """ 100 | sec_1970 = long(abs) 101 | sec_1900 = sec_1970 + JAN_1970 102 | 103 | sec_frac = float(abs - sec_1970) 104 | picos = long(sec_frac * SECS_TO_PICOS) 105 | 106 | total_picos = (abs + JAN_1970) * SECS_TO_PICOS 107 | return struct.pack('!LL', sec_1900, picos) 108 | 109 | class OSCBundle: 110 | """Builds OSC bundles""" 111 | def __init__(self, when=None): 112 | self.items = [] 113 | if when == None: 114 | when = time.time() 115 | self.when = when 116 | 117 | def append(self, address, msg = None): 118 | if isinstance(address, str): 119 | self.items.append(OSCMessage(address, msg)) 120 | elif isinstance(address, OSCMessage): 121 | # address really is an OSCMessage 122 | self.items.append(address) 123 | else: 124 | raise Exception('invalid type of first argument to OSCBundle.append(), need address string or OSCMessage, not ', str(type(address))) 125 | 126 | def getBinary(self): 127 | retval = OSCArgument('#bundle')[1] + abs_to_timestamp(self.when) 128 | for item in self.items: 129 | binary = item.getBinary() 130 | retval = retval + OSCArgument(len(binary))[1] + binary 131 | return retval 132 | 133 | def readString(data): 134 | length = string.find(data,"\0") 135 | nextData = int(math.ceil((length+1) / 4.0) * 4) 136 | return (data[0:length], data[nextData:]) 137 | 138 | def readBlob(data): 139 | length = struct.unpack(">i", data[0:4])[0] 140 | nextData = int(math.ceil((length) / 4.0) * 4) + 4 141 | return (data[4:length+4], data[nextData:]) 142 | 143 | def readInt(data): 144 | if(len(data)<4): 145 | print "Error: too few bytes for int", data, len(data) 146 | rest = data 147 | integer = 0 148 | else: 149 | integer = struct.unpack(">i", data[0:4])[0] 150 | rest = data[4:] 151 | 152 | return (integer, rest) 153 | 154 | def readLong(data): 155 | """Tries to interpret the next 8 bytes of the data 156 | as a 64-bit signed integer.""" 157 | high, low = struct.unpack(">ll", data[0:8]) 158 | big = (long(high) << 32) + low 159 | rest = data[8:] 160 | return (big, rest) 161 | 162 | def readFloat(data): 163 | if(len(data)<4): 164 | print "Error: too few bytes for float", data, len(data) 165 | rest = data 166 | float = 0 167 | else: 168 | float = struct.unpack(">f", data[0:4])[0] 169 | rest = data[4:] 170 | 171 | return (float, rest) 172 | 173 | def OSCBlob(next): 174 | """Convert a string into an OSC Blob, 175 | returning a (typetag, data) tuple.""" 176 | 177 | if type(next) == type(""): 178 | length = len(next) 179 | padded = math.ceil((len(next)) / 4.0) * 4 180 | binary = struct.pack(">i%ds" % (padded), length, next) 181 | tag = 'b' 182 | else: 183 | tag = '' 184 | binary = '' 185 | 186 | return (tag, binary) 187 | 188 | def OSCArgument(next): 189 | """Convert some Python types to their 190 | OSC binary representations, returning a 191 | (typetag, data) tuple.""" 192 | 193 | if type(next) == type(""): 194 | OSCstringLength = math.ceil((len(next)+1) / 4.0) * 4 195 | binary = struct.pack(">%ds" % (OSCstringLength), next) 196 | tag = "s" 197 | elif type(next) == type(42.5): 198 | binary = struct.pack(">f", next) 199 | tag = "f" 200 | elif type(next) == type(13): 201 | binary = struct.pack(">i", next) 202 | tag = "i" 203 | else: 204 | raise Exception("don't know how to encode " + str(next) + " as OSC argument, type=" + str(type(next))) 205 | 206 | return (tag, binary) 207 | 208 | def parseArgs(args): 209 | """Given a list of strings, produces a list 210 | where those strings have been parsed (where 211 | possible) as floats or integers.""" 212 | parsed = [] 213 | for arg in args: 214 | print arg 215 | arg = arg.strip() 216 | interpretation = None 217 | try: 218 | interpretation = float(arg) 219 | if string.find(arg, ".") == -1: 220 | interpretation = int(interpretation) 221 | except: 222 | # Oh - it was a string. 223 | interpretation = arg 224 | parsed.append(interpretation) 225 | return parsed 226 | 227 | def decodeOSC(data): 228 | """Converts a typetagged OSC message to a Python list.""" 229 | table = {"i":readInt, "f":readFloat, "s":readString, "b":readBlob} 230 | decoded = [] 231 | address, rest = readString(data) 232 | typetags = "" 233 | 234 | if address == "#bundle": 235 | time, rest = readLong(rest) 236 | decoded.append(address) 237 | decoded.append(time) 238 | while len(rest)>0: 239 | length, rest = readInt(rest) 240 | decoded.append(decodeOSC(rest[:length])) 241 | rest = rest[length:] 242 | 243 | elif len(rest)>0: 244 | typetags, rest = readString(rest) 245 | decoded.append(address) 246 | decoded.append(typetags) 247 | if(typetags[0] == ","): 248 | for tag in typetags[1:]: 249 | value, rest = table[tag](rest) 250 | decoded.append(value) 251 | else: 252 | print "Oops, typetag lacks the magic ," 253 | else: 254 | decoded.append(address) 255 | decoded.append(',') 256 | 257 | # return only the data 258 | return decoded 259 | 260 | class CallbackManager: 261 | """This utility class maps OSC addresses to callables. 262 | 263 | The CallbackManager calls its callbacks with a list 264 | of decoded OSC arguments, including the address and 265 | the typetags as the first two arguments.""" 266 | 267 | def __init__(self): 268 | self.callbacks = {} 269 | self.add("#bundle", self.unbundler) 270 | 271 | def handle(self, data, source): 272 | """Given OSC data, tries to call the callback with the right address.""" 273 | decoded = decodeOSC(data) 274 | self.dispatch(decoded, source) 275 | 276 | def dispatch(self, message, source): 277 | """Sends decoded OSC data to an appropriate calback""" 278 | address = message[0] 279 | self.callbacks[address](message, source) 280 | 281 | def add(self, address, callback): 282 | """Adds a callback to our set of callbacks, 283 | or removes the callback with name if callback 284 | is None.""" 285 | if callback == None: 286 | del self.callbacks[address] 287 | else: 288 | self.callbacks[address] = callback 289 | 290 | def unbundler(self, messages, source): 291 | """Dispatch the messages in a decoded bundle.""" 292 | # first two elements are #bundle and the time tag, rest are messages. 293 | for message in messages[2:]: 294 | self.dispatch(message, source) 295 | 296 | if __name__ == "__main__": 297 | hexDump("Welcome to the OSC testing program.") 298 | print 299 | message = OSCMessage("/foo/play") 300 | message.append(44) 301 | message.append(11) 302 | message.append(4.5) 303 | message.append("the white cliffs of dover") 304 | hexDump(message.getBinary()) 305 | 306 | print "Making and unmaking a message.." 307 | 308 | strings = OSCMessage() 309 | strings.append("Mary had a little lamb") 310 | strings.append("its fleece was white as snow") 311 | strings.append("and everywhere that Mary went,") 312 | strings.append("the lamb was sure to go.") 313 | strings.append(14.5) 314 | strings.append(14.5) 315 | strings.append(-400) 316 | 317 | raw = strings.getBinary() 318 | 319 | hexDump(raw) 320 | 321 | print "Retrieving arguments..." 322 | data = raw 323 | for i in range(6): 324 | text, data = readString(data) 325 | print text 326 | 327 | number, data = readFloat(data) 328 | print number 329 | 330 | number, data = readFloat(data) 331 | print number 332 | 333 | number, data = readInt(data) 334 | print number 335 | 336 | hexDump(raw) 337 | print decodeOSC(raw) 338 | print decodeOSC(message.getBinary()) 339 | 340 | print "Testing Blob types." 341 | 342 | blob = OSCMessage() 343 | blob.append("","b") 344 | blob.append("b","b") 345 | blob.append("bl","b") 346 | blob.append("blo","b") 347 | blob.append("blob","b") 348 | blob.append("blobs","b") 349 | blob.append(42) 350 | 351 | hexDump(blob.getBinary()) 352 | 353 | print decodeOSC(blob.getBinary()) 354 | 355 | def printingCallback(stuff, source): 356 | sys.stdout.write("Got: ") 357 | for i in stuff: 358 | sys.stdout.write(str(i) + " ") 359 | sys.stdout.write("\n") 360 | 361 | print "Testing bundles" 362 | 363 | print1 = OSCMessage("/print") 364 | print1.append("Hey man, that's cool.") 365 | print1.append(42) 366 | print1.append(3.1415926) 367 | 368 | bundle = OSCBundle() 369 | bundle.append(print1) 370 | bundle.append('/foo', (123, 456)) 371 | bundlebinary = bundle.getBinary() 372 | hexDump(bundlebinary) 373 | print decodeOSC(bundlebinary) 374 | 375 | print "Testing the callback manager." 376 | 377 | c = CallbackManager() 378 | c.add("/print", printingCallback) 379 | 380 | c.handle(message.getBinary(), None) 381 | 382 | c.handle(print1.getBinary(), None) 383 | 384 | print "sending a bundle to the callback manager" 385 | c.handle(bundlebinary, None) 386 | -------------------------------------------------------------------------------- /OSCAPI.txt: -------------------------------------------------------------------------------- 1 | CALLS 2 | ===== 3 | 4 | /live/tempo Request current tempo, replies with /live/tempo (float tempo) 5 | /live/tempo (float tempo) Set the tempo, replies with /live/tempo (float tempo) 6 | /live/time Request current song time, replies with /live/time (float time) 7 | /live/time (float time) Set the time , replies with /live/time (float time) 8 | /live/overdub (int on/off) Enables/disables overdub 9 | /live/state Returns the current tempo and overdub status 10 | /live/undo Requests the song to undo the last action 11 | /live/redo Requests the song to redo the last action 12 | 13 | /live/next/cue Jumps to the next cue point 14 | /live/prev/cue Jumps to the previous cue point 15 | /live/play Starts the song playing 16 | /live/play/continue Continues playing the song from the current point 17 | /live/play/selection Plays the current selection 18 | /live/play/clip (int track, int clip) Launches clip number clip in track number track 19 | /live/play/clipslot (int track, int clip) Launches clip number clip in track number track even if a clip isnt present in the slot (ie stops the slot) 20 | /live/play/scene (int scene) Launches scene number scene 21 | /live/stop Stops playing the song 22 | /live/stop/clip (int track, int clip) Stops clip number clip in track number track 23 | /live/stop/track (int track) Stops track number track 24 | 25 | /live/scenes blank or ('query') Returns the total number of scenes in the form /live/scenes (int) 26 | /live/tracks blank or ('query') Returns the total number of tracks in the form /live/tracks (int) 27 | 28 | /live/scene Returns the currently selected scene index 29 | /live/scene (int scene) Selects the scene with index scene 30 | 31 | /live/name/scene blank or ('query') Returns a a series of all the scene names in the form /live/name/scene (int scene, string name) 32 | /live/name/scene (int scene) Returns a single scene's name in the form /live/name/scene (int scene, string name) 33 | /live/name/scene (int scene, string name) Sets scene number scene's name to name 34 | /live/name/sceneblock (int track, int size) Returns a series of scene name starting at (int scene) of length (int size) 35 | 36 | /live/name/track Returns a a series of all the track names in the form /live/name/track (int track, string name, int color) 37 | /live/name/track (int track) Returns a single track's name in the form /live/name/track (int track, string name, int color) 38 | /live/name/track (int track, string name) Sets track number track's name to name 39 | /live/name/trackblock (int track, int size) Returns a series of track name starting at (int track) of length (int size) 40 | 41 | /live/name/clip Returns a a series of all the clip names in the form /live/name/clip (int track, int clip, string name) 42 | /live/name/clip (int track, int clip) Returns a single clip's name in the form /live/name/clip (int clip, string name) 43 | /live/name/clip (int track, int clip, string name) Sets clip number clip in track number track's name to name 44 | /live/name/clipblock (int track, int clip, int sizeX, int sizeY) Returns a series of clip names in a area starting at (int track, int clip) of size (sizeX, sizeY) 45 | 46 | /live/arm (int track) Get arm status for track number track 47 | /live/arm (int track, int armed/disarmed) Arms/disamrs track number track 48 | /live/mute (int track) Get mute status for track number track 49 | /live/mute (int track, int mute/unmute) Mutes/unmutes track number track 50 | /live/solo (int track) Get solo status for track number track 51 | /live/solo (int track, int solo/unsolo) Solos/unsolos track number track 52 | /live/volume (int track) Returns the current volume of track number track as: /live/volume (int track, float volume(0.0 to 1.0)) 53 | /live/volume (int track, float volume(0.0 to 1.0)) Sets track number track's volume to volume 54 | /live/pan (int track) Returns the pan of track number track as: /live/pan (int track, float pan(-1.0 to 1.0)) 55 | /live/pan (int track, float pan(-1.0 to 1.0)) Sets track number track's pan to pan 56 | /live/send (int track) Returns a list of all sends and values on track number track as: /live/send (int track, int send, float level, int send, ...) 57 | /live/send (int track, int send) Returns the send level of send (send) on track number track as: /live/send (int track, int send, float level(0.0 to 1.0)) 58 | /live/send (int track, int send, float level(0.0 to 1.0)) Sets the send (send) of track number (track)'s level to (level) 59 | /live/pitch (int track, int clip) Returns the pan of track number track as: /live/pan (int track, int clip, int coarse(-48 to 48), int fine (-50 to 50)) 60 | /live/pitch (int track, int clip, int coarse(-48 to 48), 61 | int fine (-50 to 50)) Sets clip number clip in track number track's pitch to coarse / fine 62 | 63 | /live/return/mute (int track) Get mute status for return track number track 64 | /live/return/mute (int track, int mute/unmute) Mutes/unmutes return track number track 65 | /live/return/solo (int track) Get solo status for return track number track 66 | /live/return/solo (int track, int solo/unsolo) Solos/unsolos return track number track 67 | /live/return/volume (int track) Returns the current volume of return track number track as: /live/volume (int track, float volume(0.0 to 1.0)) 68 | /live/return/volume (int track, float volume(0.0 to 1.0)) Sets return track number track's volume to volume 69 | /live/return/pan (int track) Returns the pan of return track number track as: /live/pan (int track, float pan(-1.0 to 1.0)) 70 | /live/return/pan (int track, float pan(-1.0 to 1.0)) Sets return track number track's pan to pan 71 | /live/return/send (int track) Returns a list of all sends and values on return track number track as: /live/send (int track, int send, float level, int send, ...) 72 | /live/return/send (int track, int send) Returns the send level of send (send) on return track number track as: /live/send (int track, int send, float level(0.0 to 1.0)) 73 | /live/return/send (int track, int send, float level(0.0 to 1.0)) Sets the send (send) of return track number (track)'s level to (level) 74 | 75 | /live/master/volume (int track) Returns the current volume of the master track as: /live/master/volume float volume(0.0 to 1.0) 76 | /live/master/volume (int track, float volume(0.0 to 1.0)) Sets the master track's volume to volume 77 | /live/master/pan (int track) Returns the pan of the master track as: /live/master/pan (int track, float pan(-1.0 to 1.0)) 78 | /live/master/pan (int track, float pan(-1.0 to 1.0)) Sets master track's pan to pan 79 | 80 | /live/track/jump (int track, float beats) Jumps in track's currently running session clip by beats 81 | /live/track/info (int track) Returns clip slot status' for all clips in a track in the form /live/track/info (tracknumber, armed (clipnumber, state, length)) 82 | [state: 0 = no clip, 1 = has clip, 2 = playing, 3 = triggered] 83 | /live/track/view (int track) Selects a track to view 84 | /live/return/view (int track) Selects a return track to view 85 | /live/master/view Selects the master track 86 | 87 | /live/track/device/view (int track, int device) Selects device on track to view 88 | /live/return/device/view (int track, int device) Selects device on return track to view 89 | /live/master/device/view (int device) Selects device on the master track 90 | 91 | /live/clip/view (int track, int clip) Selects a clip on track to view 92 | 93 | /live/detail/view (int) Switches detail view [0 = clip, 1 = track] 94 | 95 | /live/clip/info (int track, int clip) Gets the status of a single clip in the form /live/clip/info (tracknumber, clipnumber, state) 96 | [state: 0 = no clip, 1 = has clip, 2 = playing, 3 = triggered] 97 | 98 | /live/devicelist (int track) Returns a list of all devices and names on track number track as: /live/device (int track, int device, str name, ...) 99 | /live/device (int track, int device) Returns a list of all parameter values and names on device on track number track 100 | as: /live/deviceall/param (int track, int device, int parameter int value, str name, ...) 101 | /live/device (int track, int device, int parameter) Returns the name and value of parameter on device on track as: /live/device/param (int track, int device, int paarmeter, int value, str name) 102 | /live/device (int track, int device, int parameter, Sets parameter on device on track number track to value 103 | int value) 104 | 105 | /live/device/range (int track, int device) Returns the min and max value of all parameters of device on track in the format /live/device/range (int track, int device, int/float min, int/float max, ...) 106 | /live/device/range (int track, int device, int parameter) Returns the min and max value of parameter of device on track in the format /live/device/range (int track, int device, int/float min, int/float max) 107 | 108 | /live/return/devicelist (int track) Returns a list of all devices and names on track number track as: /live/device (int track, int device, str name, ...) 109 | /live/return/device (int track, int device) Returns a list of all parameter values and names on device on track number track 110 | as: /live/device/allparm (int track, int device, int parameter int value, str name, ...) 111 | /live/return/device (int track, int device, int parameter) Returns the name and value of parameter on device on track as: /live/device/param (int track, int device, int parameter, int value) 112 | /live/return/device (int track, int device, int parameter, Sets parameter on device on track number track to value 113 | int value) 114 | 115 | /live/return/device/range (int track, int device) Returns the min and max value of all parameters of device on return track in the format /live/return/device/range (int track, int device, int/float min, int/float max, ...) 116 | /live/return/device/range (int track, int device, int parameter) Returns the min and max value of parameter of device on return track in the format /live/return/device/range (int track, int device, int/float min, int/float max) 117 | 118 | /live/master/devicelist Returns a list of all devices and names on the master track as: /live/device (int device, str name, ...) 119 | /live/master/device (int device) Returns a list of all parameter values and names on device on the master track 120 | as: /live/device (int device, int parameter int value, str name, ...) 121 | /live/master/device (int device, int parameter) Returns the name and value of parameter on device on the master track as: /live/device (int device, int parameter, int value) 122 | /live/master/device (int device, int parameter, int value) Sets parameter on device on track number track to value 123 | 124 | /live/return/device/range (int device) Returns the min and max value of all parameters of device on the master track in the format /live/master/device/range (int device, int/float min, int/float max, ...) 125 | /live/return/device/range (int device, int parameter) Returns the min and max value of parameter of device on the master track in the format /live/master/device/range (int device, int/float min, int/float max) 126 | 127 | /live/clip/loopstart (int track, int clip) Get the loopstart for clip in track 128 | /live/clip/loopstart (int track, int clip, float loopstart) Set the loop start position for clip in track 129 | /live/clip/loopend (int track, int clip) Get the loopend for clip in track 130 | /live/clip/loopend (int track, int clip, float loopend) Set the loop end position for clip in track 131 | /live/clip/loopstate (int track, int clip) Get the loop state of clip on track 132 | /live/clip/loopstate (int track, int clip, int on/off) Set the loop state of clip on track 133 | 134 | /live/clip/loopstart_id (int track, int clip) Get the loopstart for clip in track with the track and clip id /live/clip/loopstart_id (int track, int clip, float start) 135 | /live/clip/loopend_id (int track, int clip) Get the loopend for clip in track with the track and clip id /live/clip/loopend_id (int track, int clip, float end) 136 | /live/clip/loopstate_id (int track, int clip) Get the loop state of clip on track with the track and clip id /live/clip/loopstate_id (int track, int clip, int state) 137 | 138 | /live/clip/warping (int track, int clip) Gets the warping state of the clip 139 | /live/clip/warping (int track, int clip, int state) Sets the warping state of the clip 140 | 141 | /live/clip/signature (int track, int clip) Gets the time signature of a clip returns 4 4 for example 142 | /live/clip/signature (int track, int clip, int denom, int num) Sets the time signature of a clip 143 | 144 | /live/master/crossfader Get the current crossfader position 145 | /live/master/crossfader (float position) Set the crossfader position 146 | 147 | /live/quantization (int) Set the global quantization. 0=None, 1=8bars, 2=4bars, 3=2bars, 4=bar, 5=half, 6=half triplet, 7=quarter, 8=quarter triplet, 9=8th, 10=8thT, 11=16th, 12=16T, 13=32nd 148 | 149 | /live/track/crossfader (int track) Gets the current cross fader assignment for track track. 0 = A, 1 = None, 2 = B 150 | /live/track/crossfader (int track) (int assign) Sets the current cross fader assignment for track track to assign 151 | /live/return/crossfader (int return) Gets the current cross fader assignment for return track track 152 | /live/return/crossfader (int return) (int assign) Sets the current cross fader assignment for return track track 153 | 154 | /live/selection (int tr_offset, int sc_offset, int width, int height) Sets the dimensions and positions of the highlighted region in session view 155 | 156 | LISTENERS 157 | ========= 158 | 159 | The following functions will automatically return a value when the specific controller changes in ableton 160 | without the need for a polling call 161 | 162 | /live/play (2 = playing, 1 = stopped) 163 | 164 | /live/track/info 165 | /live/clip/info 166 | /live/clip/position (int track) (int clip) (float position) (float length) (float loop_start) (float loop_end) 167 | 168 | /live/name/return 169 | /live/name/track 170 | /live/name/clip (returns on colour and name changes) 171 | 172 | /live/arm 173 | /live/mute 174 | /live/solo 175 | /live/volume 176 | /live/pan 177 | /live/send 178 | 179 | /live/master/volume 180 | /live/master/pan 181 | /live/master/crossfader 182 | 183 | /live/return/mute 184 | /live/return/solo 185 | /live/return/volume 186 | /live/return/pan 187 | /live/return/send 188 | 189 | /live/overdub 190 | /live/tempo 191 | /live/scene 192 | /live/track 193 | 194 | /live/master/meter (int 0=left, 1=right) (float value) 195 | /live/return/meter (int track) (int 0=left, 1=right) (float value) 196 | /live/track/meter (int track) (int 0=left, 1=right) (float value) 197 | 198 | /live/device/param (int track) (int device) (int param) (int value) (str name) 199 | /live/return/device/param (int track) (int device) (int param) (int value) (str name) 200 | /live/master/device/param (int device) (int param) (int value) (str name) 201 | 202 | /live/device/selected (int track) (int deviceid) 203 | /live/return/device/selected (int track) (int device) 204 | /live/master/device/selected (int device) 205 | 206 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | LiveOSC 2 | ======= 3 | 4 | MIDI remote script for communication with Ableton Live over OSC 5 | 6 | This is the recommended version of LiveOSC to use with node-liveosc. This version has been modified to use ports 9005 and 9006 and has a few new OSC callbacks and bug fixes. I am not the original author of this code, the original version can be found here: 7 | 8 | http://livecontrol.q3f.org/ableton-liveapi/liveosc/ 9 | 10 | The author recently announced LiveOSC2 which will be used instead when it's ready: 11 | 12 | https://github.com/stufisher/LiveOSC2 13 | 14 | # Installation 15 | 16 | Please see http://livecontrol.q3f.org/ableton-liveapi/liveosc/ for installation instructions. 17 | -------------------------------------------------------------------------------- /RemixNet.py: -------------------------------------------------------------------------------- 1 | """ 2 | # Copyright (C) 2007 Nathan Ramella (nar@remix.net) 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 2.1 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | # 18 | # For questions regarding this module contact 19 | # Nathan Ramella or visit http://www.liveapi.org 20 | 21 | RemixNet Module 22 | 23 | This module contains an OSC interface class to facilitate remote 24 | control of Ableton Live through the OSC protocol. 25 | 26 | Based on the original RemixNet.py written by Nathan Ramella (nar@remix.net) 27 | 28 | -Updated 29/04/09 by ST8 (st8@q3f.org) 29 | Works on Mac OSX with Live7/8 30 | 31 | The socket module is missing on osx and including it from the 32 | default python install doesnt work. Turns out its the os module 33 | that causes all the problems, removing dependance on this module 34 | and packaging the script with a modified version of the socket 35 | module allows it to run on osx. 36 | 37 | -Updated May/June 2011 by Hans Huebner (hans.huebner@gmail.com) 38 | 39 | Refactored and removed methods that are not used. 40 | 41 | The original version of RemixNet.py had several classes to 42 | implement sending and receiving OSC packets. This was relatively 43 | hard to change or update, and as I wanted to be able to change the 44 | destination of OSC packets sent out by live at run time, I decided 45 | to simplify RemixNet so that it only supports those functions that 46 | are actually used by LiveOSC. In particular, OSCServer, 47 | OSCClient, UDPServer and UDPClient have all been collapsed into 48 | one class, OSCEndpoint. OSCEndpoint uses one UDP socket to send 49 | and receive data. By default, the socket is bound to port 9000 as 50 | before, but it listens to all network interfaces so that packets 51 | coming in from the network are accepted. Also by default, 52 | outgoing packets are sent to localhost port 9001. This can be 53 | changed by sending a /remix/set_peer message with two arguments, 54 | the host and the port number of the peer. The host may optionally 55 | be sent as empty string. In that case, the peer host will be 56 | automatically be set to the host that sent the /remix/set_peer 57 | message, making it easier to configure the OSC association. 58 | 59 | Also, the logging mechanism has been simplified. It can now be 60 | used simply by importing the log function from the Logger module 61 | and then calling log() with a string argument. 62 | """ 63 | import sys 64 | import errno 65 | import Live 66 | from Logger import log 67 | 68 | # Import correct paths for os / version 69 | version = Live.Application.get_application().get_major_version() 70 | if sys.platform == "win32": 71 | sys.path.append("C:\\Python25\\lib") 72 | import socket 73 | 74 | else: 75 | if version > 7: 76 | # 10.5 77 | try: 78 | file = open("/usr/lib/python2.5/string.pyc") 79 | except IOError: 80 | sys.path.append("/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5") 81 | import socket_live8 as socket 82 | else: 83 | # sys.path.append("/usr/lib/python2.5") 84 | import socket 85 | 86 | import OSC 87 | 88 | class OSCEndpoint: 89 | 90 | def __init__(self, remoteHost='localhost', remotePort=9006, localHost='', localPort=9005, ): 91 | """ 92 | This is the main class we the use as a nexus point in this module. 93 | 94 | - remoteHost and remotePort define the address of the peer 95 | that we send data to by default. It can be changed, at run 96 | time, using the /remix/set_peer OSC message. 97 | 98 | - localHost and localPort define the address that we are 99 | listening to for incoming OSC packets. By default, we are 100 | listening on all interfaces with port 9005. 101 | 102 | By default we define and set callbacks for some utility 103 | addresses: 104 | 105 | /remix/echo - Echos back the string argument to the peer. 106 | /remix/time - Returns time.time() (time in float seconds) 107 | /remix/set_peer - Reconfigures the peer address which we send OSC messages to 108 | """ 109 | 110 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 111 | self.socket.setblocking(0) 112 | self.localAddr = (localHost, localPort) 113 | self.socket.bind(self.localAddr) 114 | 115 | self.remoteAddr = (remoteHost, remotePort) 116 | 117 | log('OSCEndpoint starting, local address ' + str(self.localAddr) + ' remote address ' + str(self.remoteAddr)) 118 | 119 | # Create our callback manager and register some utility 120 | # callbacks 121 | 122 | self.callbackManager = OSC.CallbackManager() 123 | self.callbackManager.add('/remix/echo', self.callbackEcho) 124 | self.callbackManager.add('/remix/time', self.callbackEcho) 125 | self.callbackManager.add('/remix/set_peer', self.setPeer) 126 | 127 | def send(self, address, msg): 128 | 129 | """ 130 | Given an OSC address and OSC msg payload we construct our 131 | OSC packet and send it to its destination. You can pass in lists 132 | or tuples in msg and we will iterate over them and append each 133 | to the end of a single OSC packet. 134 | 135 | This can be useful for transparently dealing with methods that 136 | yield a variety of values in a list/tuple without the necessity of 137 | combing through it yourself. 138 | """ 139 | 140 | oscMessage = OSC.OSCMessage(address, msg) 141 | self.sendMessage(oscMessage) 142 | 143 | def sendMessage(self, message): 144 | self.socket.sendto(message.getBinary(), self.remoteAddr) 145 | 146 | def processIncomingUDP(self): 147 | """ 148 | This is the function that deals with incoming UDP messages. 149 | It processes messages as long as any are buffered in the 150 | socket, then returns. 151 | 152 | There are several limitations to the Ableton Live Python environment. 153 | 154 | * The Ableton Live Python environment is minimal. The included module 155 | list is very short. For instance, we don't have 'select()'. 156 | 157 | * The Ableton Live Python version is a bit older than what most Python 158 | programmers are used to. Its version string says 2.2.1, and the Python 159 | webpage shows that the offical 2.2.3 came out May 30, 2003. So we've 160 | got 4 years between us and it. Fortunately since I didn't know any Python 161 | when I got started on this project the version differences didn't bother 162 | me. But I know the lack of modern features has been a pain for a few 163 | of our developers. 164 | 165 | * The Ableton Live Python environment, although it includes the thread 166 | module, doesn't function how you'd expect it to. The threads appear to 167 | be on a 100ms timer that cannot be altered consistently through Python. 168 | 169 | I did find an interesting behavior in that when you modify the 170 | sys.setcheckinterval value to very large numbers for about 1-5/100ths of 171 | a second thread focus goes away entirely and if your running thread is 172 | a 'while 1:' loop with no sleep, it gets 4-5 iterations in before 173 | the thread management stuff kicks in and puts you down back to 100ms 174 | loop. 175 | 176 | As a goof I tried making a thread that was a 'while 1:' loop with a 177 | sys.setcheckinterval(50000) inside it -- first iteration it triggered 178 | the behavior, then it stopped. 179 | 180 | It should also be noted that you can make a blocking TCP socket using 181 | the threads interface. But your refresh is going to be about 40ms slower 182 | than using a non-blocking UDP socket reader. But hey, you're the boss! 183 | 184 | So far the best performance for processing incoming packets can be found 185 | by attaching a method as a listener to the Song.current_song_time 186 | attribute. This attribute updates every 60ms on the dot allowing for 187 | 16 passes on the incoming UDP traffic every second. 188 | 189 | My machine is pretty beefy but I was able to sustain an average of 190 | over 1300 /remix/echo callback hits a second and only lost .006% 191 | of my UDP traffic over 10 million packets on a machine running Live. 192 | 193 | One final note -- I make no promises as to the latency of triggers received. 194 | I haven't tested that at all yet. Since the window is 60ms, don't get 195 | your hopes up about MIDI over OSC. 196 | """ 197 | try: 198 | # Our socket is in non-blocking mode. recvfrom will 199 | # either return the next packet waiting or raise an EAGAIN 200 | # exception that we catch to exit the reception loop. 201 | while 1: 202 | self.data, self.addr = self.socket.recvfrom(65536) 203 | # log('received packet from ' + str(self.addr)) 204 | try: 205 | self.callbackManager.handle(self.data, self.addr) 206 | except: 207 | self.send('/remix/error', (str(sys.exc_info()))) 208 | 209 | except Exception, e: 210 | err, message=e 211 | if err != errno.EAGAIN: # no data on socket 212 | log('error handling message, errno ' + str(errno) + ': ' + message) 213 | 214 | def shutdown(self): 215 | """ 216 | Close our socket. 217 | """ 218 | self.socket.close() 219 | 220 | # standard callback handlers (in the /remix/ address name space) 221 | 222 | def setPeer(self, msg, source): 223 | """ 224 | Reconfigure the client side to the address and port indicated 225 | as the argument. The first argument is a string with the host 226 | address or an empty string if the IP address of the sender of 227 | the reconfiguration message should be used as peer. The 228 | second argument is the integer port number of the peer. 229 | """ 230 | host = msg[2] 231 | if host == '': 232 | host = source[0] 233 | port = msg[3] 234 | log('reconfigure to send to ' + host + ':' + str(port)) 235 | self.remoteAddr = (host, port) 236 | 237 | def callbackEcho(self, msg, source): 238 | """ 239 | When re receive a '/remix/echo' OSC query from another host 240 | we respond in kind by passing back the message they sent to us. 241 | Useful for verifying functionality. 242 | """ 243 | 244 | self.send('/remix/echo', msg[2]) 245 | 246 | def callbackTime(self, msg, source): 247 | """ 248 | When we receive a '/remix/time' OSC query from another host 249 | we respond with the current value of time.time() 250 | 251 | This callback can be useful for testing timing/queue processing 252 | between hosts 253 | """ 254 | 255 | self.send('/remix/time', time.time()) 256 | 257 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | # path = "/Users/mac/src/node-liveosc" 4 | # errorLog = open(path + "/stderr.txt", "w") 5 | # errorLog.write("Starting Error Log") 6 | # sys.stderr = errorLog 7 | # stdoutLog = open(path + "/stdout.txt", "w") 8 | # stdoutLog.write("Starting Standard Out Log") 9 | # sys.stdout = stdoutLog 10 | 11 | from LiveOSC import LiveOSC 12 | 13 | def create_instance(c_instance): 14 | return LiveOSC(c_instance) 15 | -------------------------------------------------------------------------------- /socket_live8.py: -------------------------------------------------------------------------------- 1 | # Wrapper module for _socket, providing some additional facilities 2 | # implemented in Python. 3 | 4 | """\ 5 | This module provides socket operations and some related functions. 6 | On Unix, it supports IP (Internet Protocol) and Unix domain sockets. 7 | On other systems, it only supports IP. Functions specific for a 8 | socket are available as methods of the socket object. 9 | 10 | Functions: 11 | 12 | socket() -- create a new socket object 13 | socketpair() -- create a pair of new socket objects [*] 14 | fromfd() -- create a socket object from an open file descriptor [*] 15 | gethostname() -- return the current hostname 16 | gethostbyname() -- map a hostname to its IP number 17 | gethostbyaddr() -- map an IP number or hostname to DNS info 18 | getservbyname() -- map a service name and a protocol name to a port number 19 | getprotobyname() -- mape a protocol name (e.g. 'tcp') to a number 20 | ntohs(), ntohl() -- convert 16, 32 bit int from network to host byte order 21 | htons(), htonl() -- convert 16, 32 bit int from host to network byte order 22 | inet_aton() -- convert IP addr string (123.45.67.89) to 32-bit packed format 23 | inet_ntoa() -- convert 32-bit packed format IP to string (123.45.67.89) 24 | ssl() -- secure socket layer support (only available if configured) 25 | socket.getdefaulttimeout() -- get the default timeout value 26 | socket.setdefaulttimeout() -- set the default timeout value 27 | 28 | [*] not available on all platforms! 29 | 30 | Special objects: 31 | 32 | SocketType -- type object for socket objects 33 | error -- exception raised for I/O errors 34 | has_ipv6 -- boolean value indicating if IPv6 is supported 35 | 36 | Integer constants: 37 | 38 | AF_INET, AF_UNIX -- socket domains (first argument to socket() call) 39 | SOCK_STREAM, SOCK_DGRAM, SOCK_RAW -- socket types (second argument) 40 | 41 | Many other constants may be defined; these may be used in calls to 42 | the setsockopt() and getsockopt() methods. 43 | """ 44 | 45 | import _socket 46 | from _socket import * 47 | 48 | _have_ssl = False 49 | try: 50 | import _ssl 51 | from _ssl import * 52 | _have_ssl = True 53 | except ImportError: 54 | pass 55 | 56 | import sys 57 | 58 | try: 59 | from cStringIO import StringIO 60 | except ImportError: 61 | from StringIO import StringIO 62 | 63 | try: 64 | from errno import EBADF 65 | except ImportError: 66 | EBADF = 9 67 | 68 | __all__ = ["getfqdn"] 69 | #__all__.extend(os._get_exports_list(_socket)) 70 | #if _have_ssl: 71 | # __all__.extend(os._get_exports_list(_ssl)) 72 | 73 | _realsocket = socket 74 | if _have_ssl: 75 | _realssl = ssl 76 | def ssl(sock, keyfile=None, certfile=None): 77 | if hasattr(sock, "_sock"): 78 | sock = sock._sock 79 | return _realssl(sock, keyfile, certfile) 80 | 81 | # WSA error codes 82 | if sys.platform.lower().startswith("win"): 83 | errorTab = {} 84 | errorTab[10004] = "The operation was interrupted." 85 | errorTab[10009] = "A bad file handle was passed." 86 | errorTab[10013] = "Permission denied." 87 | errorTab[10014] = "A fault occurred on the network??" # WSAEFAULT 88 | errorTab[10022] = "An invalid operation was attempted." 89 | errorTab[10035] = "The socket operation would block" 90 | errorTab[10036] = "A blocking operation is already in progress." 91 | errorTab[10048] = "The network address is in use." 92 | errorTab[10054] = "The connection has been reset." 93 | errorTab[10058] = "The network has been shut down." 94 | errorTab[10060] = "The operation timed out." 95 | errorTab[10061] = "Connection refused." 96 | errorTab[10063] = "The name is too long." 97 | errorTab[10064] = "The host is down." 98 | errorTab[10065] = "The host is unreachable." 99 | __all__.append("errorTab") 100 | 101 | 102 | 103 | def getfqdn(name=''): 104 | """Get fully qualified domain name from name. 105 | 106 | An empty argument is interpreted as meaning the local host. 107 | 108 | First the hostname returned by gethostbyaddr() is checked, then 109 | possibly existing aliases. In case no FQDN is available, hostname 110 | from gethostname() is returned. 111 | """ 112 | name = name.strip() 113 | if not name or name == '0.0.0.0': 114 | name = gethostname() 115 | try: 116 | hostname, aliases, ipaddrs = gethostbyaddr(name) 117 | except error: 118 | pass 119 | else: 120 | aliases.insert(0, hostname) 121 | for name in aliases: 122 | if '.' in name: 123 | break 124 | else: 125 | name = hostname 126 | return name 127 | 128 | 129 | _socketmethods = ( 130 | 'bind', 'connect', 'connect_ex', 'fileno', 'listen', 131 | 'getpeername', 'getsockname', 'getsockopt', 'setsockopt', 132 | 'sendall', 'setblocking', 133 | 'settimeout', 'gettimeout', 'shutdown') 134 | 135 | if sys.platform == "riscos": 136 | _socketmethods = _socketmethods + ('sleeptaskw',) 137 | 138 | # All the method names that must be delegated to either the real socket 139 | # object or the _closedsocket object. 140 | _delegate_methods = ("recv", "recvfrom", "recv_into", "recvfrom_into", 141 | "send", "sendto") 142 | 143 | class _closedsocket(object): 144 | __slots__ = [] 145 | def _dummy(*args): 146 | raise error(EBADF, 'Bad file descriptor') 147 | # All _delegate_methods must also be initialized here. 148 | send = recv = recv_into = sendto = recvfrom = recvfrom_into = _dummy 149 | __getattr__ = _dummy 150 | 151 | class _socketobject(object): 152 | 153 | __doc__ = _realsocket.__doc__ 154 | 155 | __slots__ = ["_sock", "__weakref__"] + list(_delegate_methods) 156 | 157 | def __init__(self, family=AF_INET, type=SOCK_STREAM, proto=0, _sock=None): 158 | if _sock is None: 159 | _sock = _realsocket(family, type, proto) 160 | self._sock = _sock 161 | for method in _delegate_methods: 162 | setattr(self, method, getattr(_sock, method)) 163 | 164 | def close(self): 165 | self._sock = _closedsocket() 166 | dummy = self._sock._dummy 167 | for method in _delegate_methods: 168 | setattr(self, method, dummy) 169 | close.__doc__ = _realsocket.close.__doc__ 170 | 171 | def accept(self): 172 | sock, addr = self._sock.accept() 173 | return _socketobject(_sock=sock), addr 174 | accept.__doc__ = _realsocket.accept.__doc__ 175 | 176 | def dup(self): 177 | """dup() -> socket object 178 | 179 | Return a new socket object connected to the same system resource.""" 180 | return _socketobject(_sock=self._sock) 181 | 182 | def makefile(self, mode='r', bufsize=-1): 183 | """makefile([mode[, bufsize]]) -> file object 184 | 185 | Return a regular file object corresponding to the socket. The mode 186 | and bufsize arguments are as for the built-in open() function.""" 187 | return _fileobject(self._sock, mode, bufsize) 188 | 189 | family = property(lambda self: self._sock.family, doc="the socket family") 190 | type = property(lambda self: self._sock.type, doc="the socket type") 191 | proto = property(lambda self: self._sock.proto, doc="the socket protocol") 192 | 193 | _s = ("def %s(self, *args): return self._sock.%s(*args)\n\n" 194 | "%s.__doc__ = _realsocket.%s.__doc__\n") 195 | for _m in _socketmethods: 196 | exec _s % (_m, _m, _m, _m) 197 | del _m, _s 198 | 199 | socket = SocketType = _socketobject 200 | 201 | class _fileobject(object): 202 | """Faux file object attached to a socket object.""" 203 | 204 | default_bufsize = 8192 205 | name = "" 206 | 207 | __slots__ = ["mode", "bufsize", "softspace", 208 | # "closed" is a property, see below 209 | "_sock", "_rbufsize", "_wbufsize", "_rbuf", "_wbuf", 210 | "_close"] 211 | 212 | def __init__(self, sock, mode='rb', bufsize=-1, close=False): 213 | self._sock = sock 214 | self.mode = mode # Not actually used in this version 215 | if bufsize < 0: 216 | bufsize = self.default_bufsize 217 | self.bufsize = bufsize 218 | self.softspace = False 219 | # _rbufsize is the suggested recv buffer size. It is *strictly* 220 | # obeyed within readline() for recv calls. If it is larger than 221 | # default_bufsize it will be used for recv calls within read(). 222 | if bufsize == 0: 223 | self._rbufsize = 1 224 | elif bufsize == 1: 225 | self._rbufsize = self.default_bufsize 226 | else: 227 | self._rbufsize = bufsize 228 | self._wbufsize = bufsize 229 | # We use StringIO for the read buffer to avoid holding a list 230 | # of variously sized string objects which have been known to 231 | # fragment the heap due to how they are malloc()ed and often 232 | # realloc()ed down much smaller than their original allocation. 233 | self._rbuf = StringIO() 234 | self._wbuf = [] # A list of strings 235 | self._close = close 236 | 237 | def _getclosed(self): 238 | return self._sock is None 239 | closed = property(_getclosed, doc="True if the file is closed") 240 | 241 | def close(self): 242 | try: 243 | if self._sock: 244 | self.flush() 245 | finally: 246 | if self._close: 247 | self._sock.close() 248 | self._sock = None 249 | 250 | def __del__(self): 251 | try: 252 | self.close() 253 | except: 254 | # close() may fail if __init__ didn't complete 255 | pass 256 | 257 | def flush(self): 258 | if self._wbuf: 259 | buffer = "".join(self._wbuf) 260 | self._wbuf = [] 261 | self._sock.sendall(buffer) 262 | 263 | def fileno(self): 264 | return self._sock.fileno() 265 | 266 | def write(self, data): 267 | data = str(data) # XXX Should really reject non-string non-buffers 268 | if not data: 269 | return 270 | self._wbuf.append(data) 271 | if (self._wbufsize == 0 or 272 | self._wbufsize == 1 and '\n' in data or 273 | self._get_wbuf_len() >= self._wbufsize): 274 | self.flush() 275 | 276 | def writelines(self, list): 277 | # XXX We could do better here for very long lists 278 | # XXX Should really reject non-string non-buffers 279 | self._wbuf.extend(filter(None, map(str, list))) 280 | if (self._wbufsize <= 1 or 281 | self._get_wbuf_len() >= self._wbufsize): 282 | self.flush() 283 | 284 | def _get_wbuf_len(self): 285 | buf_len = 0 286 | for x in self._wbuf: 287 | buf_len += len(x) 288 | return buf_len 289 | 290 | def read(self, size=-1): 291 | # Use max, disallow tiny reads in a loop as they are very inefficient. 292 | # We never leave read() with any leftover data from a new recv() call 293 | # in our internal buffer. 294 | rbufsize = max(self._rbufsize, self.default_bufsize) 295 | # Our use of StringIO rather than lists of string objects returned by 296 | # recv() minimizes memory usage and fragmentation that occurs when 297 | # rbufsize is large compared to the typical return value of recv(). 298 | buf = self._rbuf 299 | buf.seek(0, 2) # seek end 300 | if size < 0: 301 | # Read until EOF 302 | self._rbuf = StringIO() # reset _rbuf. we consume it via buf. 303 | while True: 304 | data = self._sock.recv(rbufsize) 305 | if not data: 306 | break 307 | buf.write(data) 308 | return buf.getvalue() 309 | else: 310 | # Read until size bytes or EOF seen, whichever comes first 311 | buf_len = buf.tell() 312 | if buf_len >= size: 313 | # Already have size bytes in our buffer? Extract and return. 314 | buf.seek(0) 315 | rv = buf.read(size) 316 | self._rbuf = StringIO() 317 | self._rbuf.write(buf.read()) 318 | return rv 319 | 320 | self._rbuf = StringIO() # reset _rbuf. we consume it via buf. 321 | while True: 322 | left = size - buf_len 323 | # recv() will malloc the amount of memory given as its 324 | # parameter even though it often returns much less data 325 | # than that. The returned data string is short lived 326 | # as we copy it into a StringIO and free it. This avoids 327 | # fragmentation issues on many platforms. 328 | data = self._sock.recv(left) 329 | if not data: 330 | break 331 | n = len(data) 332 | if n == size and not buf_len: 333 | # Shortcut. Avoid buffer data copies when: 334 | # - We have no data in our buffer. 335 | # AND 336 | # - Our call to recv returned exactly the 337 | # number of bytes we were asked to read. 338 | return data 339 | if n == left: 340 | buf.write(data) 341 | del data # explicit free 342 | break 343 | assert n <= left, "recv(%d) returned %d bytes" % (left, n) 344 | buf.write(data) 345 | buf_len += n 346 | del data # explicit free 347 | #assert buf_len == buf.tell() 348 | return buf.getvalue() 349 | 350 | def readline(self, size=-1): 351 | buf = self._rbuf 352 | buf.seek(0, 2) # seek end 353 | if buf.tell() > 0: 354 | # check if we already have it in our buffer 355 | buf.seek(0) 356 | bline = buf.readline(size) 357 | if bline.endswith('\n') or len(bline) == size: 358 | self._rbuf = StringIO() 359 | self._rbuf.write(buf.read()) 360 | return bline 361 | del bline 362 | if size < 0: 363 | # Read until \n or EOF, whichever comes first 364 | if self._rbufsize <= 1: 365 | # Speed up unbuffered case 366 | buf.seek(0) 367 | buffers = [buf.read()] 368 | self._rbuf = StringIO() # reset _rbuf. we consume it via buf. 369 | data = None 370 | recv = self._sock.recv 371 | while data != "\n": 372 | data = recv(1) 373 | if not data: 374 | break 375 | buffers.append(data) 376 | return "".join(buffers) 377 | 378 | buf.seek(0, 2) # seek end 379 | self._rbuf = StringIO() # reset _rbuf. we consume it via buf. 380 | while True: 381 | data = self._sock.recv(self._rbufsize) 382 | if not data: 383 | break 384 | nl = data.find('\n') 385 | if nl >= 0: 386 | nl += 1 387 | buf.write(buffer(data, 0, nl)) 388 | self._rbuf.write(buffer(data, nl)) 389 | del data 390 | break 391 | buf.write(data) 392 | return buf.getvalue() 393 | else: 394 | # Read until size bytes or \n or EOF seen, whichever comes first 395 | buf.seek(0, 2) # seek end 396 | buf_len = buf.tell() 397 | if buf_len >= size: 398 | buf.seek(0) 399 | rv = buf.read(size) 400 | self._rbuf = StringIO() 401 | self._rbuf.write(buf.read()) 402 | return rv 403 | self._rbuf = StringIO() # reset _rbuf. we consume it via buf. 404 | while True: 405 | data = self._sock.recv(self._rbufsize) 406 | if not data: 407 | break 408 | left = size - buf_len 409 | # did we just receive a newline? 410 | nl = data.find('\n', 0, left) 411 | if nl >= 0: 412 | nl += 1 413 | # save the excess data to _rbuf 414 | self._rbuf.write(buffer(data, nl)) 415 | if buf_len: 416 | buf.write(buffer(data, 0, nl)) 417 | break 418 | else: 419 | # Shortcut. Avoid data copy through buf when returning 420 | # a substring of our first recv(). 421 | return data[:nl] 422 | n = len(data) 423 | if n == size and not buf_len: 424 | # Shortcut. Avoid data copy through buf when 425 | # returning exactly all of our first recv(). 426 | return data 427 | if n >= left: 428 | buf.write(buffer(data, 0, left)) 429 | self._rbuf.write(buffer(data, left)) 430 | break 431 | buf.write(data) 432 | buf_len += n 433 | #assert buf_len == buf.tell() 434 | return buf.getvalue() 435 | 436 | def readlines(self, sizehint=0): 437 | total = 0 438 | list = [] 439 | while True: 440 | line = self.readline() 441 | if not line: 442 | break 443 | list.append(line) 444 | total += len(line) 445 | if sizehint and total >= sizehint: 446 | break 447 | return list 448 | 449 | # Iterator protocols 450 | 451 | def __iter__(self): 452 | return self 453 | 454 | def next(self): 455 | line = self.readline() 456 | if not line: 457 | raise StopIteration 458 | return line 459 | -------------------------------------------------------------------------------- /struct.py: -------------------------------------------------------------------------------- 1 | """ 2 | Functions to convert between Python values and C structs. 3 | Python strings are used to hold the data representing the C struct 4 | and also as format strings to describe the layout of data in the C struct. 5 | 6 | The optional first format char indicates byte order, size and alignment: 7 | @: native order, size & alignment (default) 8 | =: native order, std. size & alignment 9 | <: little-endian, std. size & alignment 10 | >: big-endian, std. size & alignment 11 | !: same as > 12 | 13 | The remaining chars indicate types of args and must match exactly; 14 | these can be preceded by a decimal repeat count: 15 | x: pad byte (no data); c:char; b:signed byte; B:unsigned byte; 16 | h:short; H:unsigned short; i:int; I:unsigned int; 17 | l:long; L:unsigned long; f:float; d:double. 18 | Special cases (preceding decimal count indicates length): 19 | s:string (array of char); p: pascal string (with count byte). 20 | Special case (only available in native format): 21 | P:an integer type that is wide enough to hold a pointer. 22 | Special case (not in native mode unless 'long long' in platform C): 23 | q:long long; Q:unsigned long long 24 | Whitespace between formats is ignored. 25 | 26 | The variable struct.error is an exception raised on errors. 27 | """ 28 | __version__ = '0.1' 29 | 30 | from _struct import Struct, error 31 | 32 | _MAXCACHE = 100 33 | _cache = {} 34 | 35 | def _compile(fmt): 36 | # Internal: compile struct pattern 37 | if len(_cache) >= _MAXCACHE: 38 | _cache.clear() 39 | s = Struct(fmt) 40 | _cache[fmt] = s 41 | return s 42 | 43 | def calcsize(fmt): 44 | """ 45 | Return size of C struct described by format string fmt. 46 | See struct.__doc__ for more on format strings. 47 | """ 48 | try: 49 | o = _cache[fmt] 50 | except KeyError: 51 | o = _compile(fmt) 52 | return o.size 53 | 54 | def pack(fmt, *args): 55 | """ 56 | Return string containing values v1, v2, ... packed according to fmt. 57 | See struct.__doc__ for more on format strings. 58 | """ 59 | try: 60 | o = _cache[fmt] 61 | except KeyError: 62 | o = _compile(fmt) 63 | return o.pack(*args) 64 | 65 | def pack_into(fmt, buf, offset, *args): 66 | """ 67 | Pack the values v1, v2, ... according to fmt, write 68 | the packed bytes into the writable buffer buf starting at offset. 69 | See struct.__doc__ for more on format strings. 70 | """ 71 | try: 72 | o = _cache[fmt] 73 | except KeyError: 74 | o = _compile(fmt) 75 | return o.pack_into(buf, offset, *args) 76 | 77 | def unpack(fmt, s): 78 | """ 79 | Unpack the string, containing packed C structure data, according 80 | to fmt. Requires len(string)==calcsize(fmt). 81 | See struct.__doc__ for more on format strings. 82 | """ 83 | try: 84 | o = _cache[fmt] 85 | except KeyError: 86 | o = _compile(fmt) 87 | return o.unpack(s) 88 | 89 | def unpack_from(fmt, buf, offset=0): 90 | """ 91 | Unpack the buffer, containing packed C structure data, according to 92 | fmt starting at offset. Requires len(buffer[offset:]) >= calcsize(fmt). 93 | See struct.__doc__ for more on format strings. 94 | """ 95 | try: 96 | o = _cache[fmt] 97 | except KeyError: 98 | o = _compile(fmt) 99 | return o.unpack_from(buf, offset) 100 | --------------------------------------------------------------------------------