diff --git a/examples/ex-live-scan.py b/examples/ex-live-scan.py index b9baaca..cf2fd4b 100755 --- a/examples/ex-live-scan.py +++ b/examples/ex-live-scan.py @@ -13,7 +13,7 @@ # overview of the set, but takes significantly longer. #------------------------------------------------------------------------ set = Set() -set.scan(scan_clip_names = True, scan_devices = True) +set.scan(scan_clip_names = False, scan_devices = False, group_re = "[A-Z].\d{2} -.*") for track in set.tracks: print str(track) diff --git a/live/object.py b/live/object.py index 8573ad8..d659e30 100644 --- a/live/object.py +++ b/live/object.py @@ -65,7 +65,7 @@ def cached_fn(obj, *args, **kwargs): obj.__cache[variable] = args[0] elif action == "get": if not variable in obj.__cache: - print "getting %s from real object" % variable + # print "getting %s from real object" % variable obj.__cache[variable] = fn(obj, *args) return obj.__cache[variable] diff --git a/live/query.py b/live/query.py index e42bd9c..1d28678 100644 --- a/live/query.py +++ b/live/query.py @@ -3,7 +3,8 @@ import inspect import threading -from OSC import * +import liblo +# from OSC import * from live.object import * class LiveError(Exception): @@ -56,7 +57,6 @@ def __init__(self, address = ("localhost", 9000), listen_port = 9001): self.indent = 0 self.beat_callback = None self.startup_callback = None - self.listening = False self.listen_port = listen_port # handler callbacks for particular messages from Live. @@ -64,14 +64,11 @@ def __init__(self, address = ("localhost", 9000), listen_port = 9001): self.handlers = {} self.osc_address = address - self.osc_client = OSCClient() - self.osc_client.connect(address) - try: - self.osc_server = OSCServer(("localhost", self.listen_port)) - self.osc_server.print_tracebacks = True - self.osc_server_thread = None - except: - "Couldn't connect to Live (is another process already connected?)" + self.osc_target = liblo.Address(address[0], address[1]) + self.osc_server = liblo.Server(listen_port) + self.osc_server.add_method(None, None, self.handler) + self.osc_server_thread = None + self.osc_read_event = None self.osc_timeout = 5 @@ -82,38 +79,28 @@ def __init__(self, address = ("localhost", 9000), listen_port = 9001): def __str__(self): return "live.query" - def stop(self): - """ Terminate this query object and unbind from OSC listening. """ - if self.listening: - self.osc_server.close() - self.listening = False + def osc_server_read(self): + while True: + self.osc_server.recv(10) def listen(self): - """ Commence listening for OSC messages from LiveOSC. """ - if self.listening: - return + self.osc_server_thread = threading.Thread(target = self.osc_server_read) + self.osc_server_thread.setDaemon(True) + self.osc_server_thread.start() - try: - self.trace("started listening") - self.osc_server.addMsgHandler("default", self.handler) - self.osc_server_thread = threading.Thread(target = self.osc_server.serve_forever) - self.osc_server_thread.setDaemon(True) - self.osc_server_thread.start() - self.listening = True - except Exception, e: - self.warn("listen failed (couldn't bind to port %d): %s" % (self.listen_port, e)) + def stop(self): + """ Terminate this query object and unbind from OSC listening. """ + pass def cmd(self, msg, *args): """ Send a Live command without expecting a response back: live.cmd("/live/tempo", 110.0) """ - msg = OSCMessage(msg) - msg.extend(list(args)) self.debug("OSC: %s %s", msg, args) try: - self.osc_client.send(msg) - except OSCClientError: + liblo.send(self.osc_target, msg, *args) + except Exception, e: raise LiveError("Couldn't send message to Live (is LiveOSC present and activated?)") def query(self, msg, *args, **kwargs): @@ -129,8 +116,6 @@ def query(self, msg, *args, **kwargs): # eg live.query("/set/freq", 440, 1.0, response_address = "/verify/freq") # http://stackoverflow.com/questions/5940180/python-default-keyword-arguments-after-variable-length-positional-arguments #------------------------------------------------------------------------ - if not self.listening: - self.listen() #------------------------------------------------------------------------ # some calls produce responses at different addresses @@ -147,13 +132,7 @@ def query(self, msg, *args, **kwargs): self.osc_server_event = threading.Event() - msg = OSCMessage(msg) - msg.extend(list(args)) - self.debug("OSC: %s %s", msg, args) - try: - self.osc_client.send(msg) - except OSCClientError: - raise LiveError("Couldn't send message to Live (is LiveOSC present and activated?)") + self.cmd(msg, *args) rv = self.osc_server_event.wait(self.osc_timeout) if not rv: @@ -172,7 +151,7 @@ def query_one(self, msg, *args): return None return rv[0] - def handler(self, address, tags, data, source): + def handler(self, address, data, types): self.debug("OSC: %s %s" % (address, data)) #------------------------------------------------------------------------ # Execute any callbacks that have been registered for this message diff --git a/live/set.py b/live/set.py index a8689bb..edd4b69 100644 --- a/live/set.py +++ b/live/set.py @@ -103,7 +103,7 @@ def open(self, filename, wait = True): os.system(cmd) if wait: - self.live.listen() + # self.live.listen() self.wait_for_startup() def currently_open(self): @@ -150,6 +150,15 @@ def live(self): def __str__(self): return "live.set" + @property + def is_connected(self): + """ Test whether we can connect to Live """ + try: + return bool(self.tempo) + except Exception, e: + print "exception %s" % e + return False + #------------------------------------------------------------------------ # /live/tempo #------------------------------------------------------------------------ @@ -164,6 +173,20 @@ def set_tempo(self, value): tempo = property(get_tempo, set_tempo, doc = "Global tempo") + #------------------------------------------------------------------------ + # /live/quantization + #------------------------------------------------------------------------ + + @name_cache + def get_quantization(self): + return self.live.query("/live/quantization")[0] + + @name_cache + def set_quantization(self, value): + self.live.cmd("/live/quantization", value) + + quantization = property(get_quantization, set_quantization, doc = "Global quantization") + #------------------------------------------------------------------------ # /live/time #------------------------------------------------------------------------ @@ -427,6 +450,18 @@ def set_track_pan(self, track_index, pan): """ Set the pan level of the given track index (-1..1). """ self.live.cmd("/live/pan", track_index, pan) + #------------------------------------------------------------------------ + # /live/pan + #------------------------------------------------------------------------ + + def get_track_send(self, track_index, send_index): + """ Return the send level of send send_index """ + return self.live.query("/live/send", track_index, send_index)[1] + + def set_track_send(self, track_index, send_index, value): + """ Set send level of send send_index """ + self.live.cmd("/live/send", track_index, send_index, value) + #------------------------------------------------------------------------ # /live/pitch #------------------------------------------------------------------------ @@ -535,7 +570,12 @@ def scan(self, group_re = None, scan_scenes = False, scan_devices = False, scan_ scan_devices -- queries tracks for devices and their corresponding parameters scan_clip_names -- queries clips for their human-readable names """ - print "group_re = %s" % group_re + + #------------------------------------------------------------------------ + # initialise to empty set of tracks and groups + #------------------------------------------------------------------------ + self.tracks = [] + self.groups = [] track_count = self.num_tracks if not track_count: @@ -709,13 +749,24 @@ def save(self, filename = "set"): filename = "%s.pickle" % filename fd = file(filename, "w") data = vars(self) + + + #------------------------------------------------------------------------ + # put to side stuff that cannot be pickled + #------------------------------------------------------------------------ _beat_event = data["beat_event"] _startup_event = data["startup_event"] del data["beat_event"] del data["startup_event"] + pickle.dump(data, fd) + + #------------------------------------------------------------------------ + # restore the unpickleables + #------------------------------------------------------------------------ data["beat_event"] = _beat_event - data["_event"] = _startup_event + data["startup_event"] = _startup_event + self.trace("save: set saved OK (%s)" % filename) def dump(self): diff --git a/live/track.py b/live/track.py index 425bc1d..9d9004a 100644 --- a/live/track.py +++ b/live/track.py @@ -141,6 +141,16 @@ def walk(self): self.trace("walking to random clip") self.play_clip_random() + def scan_clip_names(self): + #-------------------------------------------------------------------------- + # scan for clip names. + # is nice, but slows things down significantly -- so disable by default. + #-------------------------------------------------------------------------- + for clip in self.active_clips: + clip_name = self.set.get_clip_name(self.index, clip.index) + clip.name = clip_name + self.trace("scan_clip_names: (%d, %d) -> %s" % (self.index, clip.index, clip.name)) + #------------------------------------------------------------------------ # get/set: volume #------------------------------------------------------------------------