Skip to content

Commit

Permalink
switch to pyliblo; support for get/set quantization; track-specific g…
Browse files Browse the repository at this point in the history
…et_clip_names; fixed bug in save() in which startup_event not serialised correctly
  • Loading branch information
ideoforms committed Sep 3, 2014
1 parent dfb52b2 commit 1cfd889
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 46 deletions.
2 changes: 1 addition & 1 deletion examples/ex-live-scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion live/object.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
61 changes: 20 additions & 41 deletions live/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import inspect
import threading

from OSC import *
import liblo
# from OSC import *
from live.object import *

class LiveError(Exception):
Expand Down Expand Up @@ -56,22 +57,18 @@ 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.
# used so that other processes can register callbacks when states change.
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

Expand All @@ -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):
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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
Expand Down
57 changes: 54 additions & 3 deletions live/set.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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
#------------------------------------------------------------------------
Expand All @@ -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
#------------------------------------------------------------------------
Expand Down Expand Up @@ -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
#------------------------------------------------------------------------
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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):
Expand Down
10 changes: 10 additions & 0 deletions live/track.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
#------------------------------------------------------------------------
Expand Down

0 comments on commit 1cfd889

Please sign in to comment.