Mercurial > code > home > repos > light9
view light9/Submaster.py @ 910:3a15fb921b9c
more tripleFilter speedups. accept Decimals coming in from n3 files, which happens with the 2012 code
Ignore-this: 5a63ec4776bf9c4ffc3d6b4f20242ac0
author | Drew Perttula <drewp@bigasterisk.com> |
---|---|
date | Mon, 10 Jun 2013 09:52:15 +0000 |
parents | cad8207933d7 |
children | 4b9997ab0e4f |
line wrap: on
line source
from __future__ import division import os, logging, time from rdflib import Graph, RDF from rdflib import RDFS, Literal, BNode from light9.namespaces import L9, XSD from light9.TLUtility import dict_scale, dict_max from light9 import showconfig from light9.Patch import resolve_name, get_dmx_channel, get_channel_uri, reload_data from louie import dispatcher from light9.rdfdb.patch import Patch log = logging.getLogger('submaster') class Submaster(object): """mapping of channels to levels""" def __init__(self, name, levels): """this sub has a name just for debugging. It doesn't get persisted. See PersistentSubmaster. levels is a dict """ self.name = name self.levels = levels self.temporary = True if not self.temporary: # obsolete dispatcher.connect(log.error, 'reload all subs') #log.debug("%s initial levels %s", self.name, self.levels) def _editedLevels(self): pass def set_level(self, channelname, level, save=True): self.levels[resolve_name(channelname)] = level self._editedLevels() def set_all_levels(self, leveldict): self.levels.clear() for k, v in leveldict.items(): # this may call _editedLevels too many times self.set_level(k, v, save=0) def get_levels(self): return self.levels def no_nonzero(self): return (not self.levels.values()) or not (max(self.levels.values()) > 0) def __mul__(self, scalar): return Submaster("%s*%s" % (self.name, scalar), levels=dict_scale(self.levels, scalar)) __rmul__ = __mul__ def max(self, *othersubs): return sub_maxes(self, *othersubs) def __add__(self, other): return self.max(other) def ident(self): return (self.name, tuple(sorted(self.levels.items()))) def __repr__(self): items = getattr(self, 'levels', {}).items() items.sort() levels = ' '.join(["%s:%.2f" % item for item in items]) return "<'%s': [%s]>" % (getattr(self, 'name', 'no name yet'), levels) def __cmp__(self, other): # not sure how useful this is if not isinstance(other, Submaster): return -1 return cmp(self.ident(), other.ident()) def __hash__(self): return hash(self.ident()) def get_dmx_list(self): leveldict = self.get_levels() # gets levels of sub contents levels = [] for k, v in leveldict.items(): if v == 0: continue try: dmxchan = get_dmx_channel(k) - 1 except ValueError: log.error("error trying to compute dmx levels for submaster %s" % self.name) raise if dmxchan >= len(levels): levels.extend([0] * (dmxchan - len(levels) + 1)) levels[dmxchan] = max(v, levels[dmxchan]) return levels def normalize_patch_names(self): """Use only the primary patch names.""" # possibly busted -- don't use unless you know what you're doing self.set_all_levels(self.levels.copy()) def get_normalized_copy(self): """Get a copy of this sumbaster that only uses the primary patch names. The levels will be the same.""" newsub = Submaster("%s (normalized)" % self.name, {}) newsub.set_all_levels(self.levels) return newsub def crossfade(self, othersub, amount): """Returns a new sub that is a crossfade between this sub and another submaster. NOTE: You should only crossfade between normalized submasters.""" otherlevels = othersub.get_levels() keys_set = {} for k in self.levels.keys() + otherlevels.keys(): keys_set[k] = 1 all_keys = keys_set.keys() xfaded_sub = Submaster("xfade", {}) for k in all_keys: xfaded_sub.set_level(k, linear_fade(self.levels.get(k, 0), otherlevels.get(k, 0), amount)) return xfaded_sub class PersistentSubmaster(Submaster): def __init__(self, graph, uri): if uri is None: raise TypeError("uri must be URIRef") self.graph, self.uri = graph, uri self.graph.addHandler(self.setName) self.graph.addHandler(self.setLevels) Submaster.__init__(self, self.name, self.levels) self.temporary = False def ident(self): return self.uri def _editedLevels(self): self.save() def setName(self): log.info("sub update name %s %s", self.uri, self.graph.label(self.uri)) self.name = self.graph.label(self.uri) def setLevels(self): log.info("sub update levels") oldLevels = getattr(self, 'levels', {}).copy() self.setLevelsFromGraph() if oldLevels != self.levels: log.info("sub %s changed" % self.name) # dispatcher too? this would help subcomposer dispatcher.send("sub levels changed", sub=self) def setLevelsFromGraph(self): if hasattr(self, 'levels'): self.levels.clear() else: self.levels = {} for lev in self.graph.objects(self.uri, L9['lightLevel']): log.debug(" lightLevel %s %s", self.uri, lev) chan = self.graph.value(lev, L9['channel']) name = self.graph.label(chan) if not name: log.error("sub %r has channel %r with no name- " "leaving out that channel" % (self.name, chan)) continue val = self.graph.value(lev, L9['level']) if val is None: raise ValueError("sub %r has lightLevel %r with channel %r " "and level %r" % (self.uri, lev, chan, val)) log.debug(" new val %r", val) try: self.levels[name] = float(val) except: log.error("name %r val %r" % (name, val)) raise def _saveContext(self): """the context in which we should save all the lightLevel triples for this sub""" typeStmt = (self.uri, RDF.type, L9['Submaster']) with self.graph.currentState(tripleFilter=typeStmt) as current: try: ctx = current.contextsForStatement(typeStmt)[0] except IndexError: log.info("declaring %s to be a submaster" % self.uri) ctx = self.uri self.graph.patch(Patch(addQuads=[ (self.uri, RDF.type, L9['Submaster'], ctx), ])) return ctx def editLevel(self, chan, newLevel): self.graph.patchMapping(self._saveContext(), subject=self.uri, predicate=L9['lightLevel'], nodeClass=L9['ChannelSetting'], keyPred=L9['channel'], newKey=chan, valuePred=L9['level'], newValue=Literal(newLevel)) def clear(self): """set all levels to 0""" with self.graph.currentState() as g: levs = list(g.objects(self.uri, L9['lightLevel'])) for lev in levs: self.graph.removeMappingNode(self._saveContext(), lev) def save(self): raise NotImplementedError("obsolete?") if self.temporary: log.info("not saving temporary sub named %s",self.name) return graph = Graph() subUri = L9['sub/%s' % self.name] graph.add((subUri, RDFS.label, Literal(self.name))) for chan in self.levels.keys(): try: chanUri = get_channel_uri(chan) except KeyError: log.error("saving dmx channels with no :Channel node " "is not supported yet. Give channel %s a URI " "for it to be saved. Omitting this channel " "from the sub." % chan) continue lev = BNode() graph.add((subUri, L9['lightLevel'], lev)) graph.add((lev, L9['channel'], chanUri)) graph.add((lev, L9['level'], Literal(self.levels[chan], datatype=XSD['decimal']))) graph.serialize(showconfig.subFile(self.name), format="nt") def linear_fade(start, end, amount): """Fades between two floats by an amount. amount is a float between 0 and 1. If amount is 0, it will return the start value. If it is 1, the end value will be returned.""" level = start + (amount * (end - start)) return level def sub_maxes(*subs): nonzero_subs = [s for s in subs if not s.no_nonzero()] name = "max(%s)" % ", ".join([repr(s) for s in nonzero_subs]) return Submaster(name, levels=dict_max(*[sub.levels for sub in nonzero_subs])) def combine_subdict(subdict, name=None, permanent=False): """A subdict is { Submaster objects : levels }. We combine all submasters first by multiplying the submasters by their corresponding levels and then max()ing them together. Returns a new Submaster object. You can give it a better name than the computed one that it will get or make it permanent if you'd like it to be saved to disk. Serves 8.""" scaledsubs = [sub * level for sub, level in subdict.items()] maxes = sub_maxes(*scaledsubs) if name: maxes.name = name if permanent: maxes.temporary = False return maxes class Submasters: "Collection o' Submaster objects" def __init__(self, graph): self.submasters = {} # uri : Submaster self.graph = graph graph.addHandler(self.findSubs) def findSubs(self): current = set() for s in self.graph.subjects(RDF.type, L9['Submaster']): if self.graph.contains((s, RDF.type, L9['LocalSubmaster'])): continue log.info("found sub %s", s) if s not in self.submasters: sub = self.submasters[s] = PersistentSubmaster(self.graph, s) dispatcher.send("new submaster", sub=sub) current.add(s) for s in set(self.submasters.keys()) - current: del self.submasters[s] dispatcher.send("lost submaster", subUri=s) log.info("findSubs finished %s", self.submasters) def get_all_subs(self): "All Submaster objects" l = self.submasters.items() l.sort() l = [x[1] for x in l] songs = [] notsongs = [] for s in l: if s.name and s.name.startswith('song'): songs.append(s) else: notsongs.append(s) combined = notsongs + songs return combined def get_sub_by_uri(self, uri): return self.submasters[uri] def fullsub(*chans): """Make a submaster with chans at full.""" return Submaster('%r' % chans, leveldict=dict([(c, 1.0) for c in chans]), temporary=True) # a global instance of Submasters, created on demand _submasters = None def get_global_submasters(graph): """Get (and make on demand) the global instance of Submasters.""" global _submasters if _submasters is None: _submasters = Submasters(graph) return _submasters def get_sub_by_name(name, submasters=None): """name is a channel or sub nama, submasters is a Submasters object. If you leave submasters empty, it will use the global instance of Submasters.""" if not submasters: submasters = get_global_submasters() if name in submasters.get_all_sub_names(): return submasters.get_sub_by_name(name) try: val = int(name) s = Submaster("#%d" % val, leveldict={val : 1.0}, temporary=True) return s except ValueError: pass try: subnum = get_dmx_channel(name) s = Submaster("'%s'" % name, leveldict={subnum : 1.0}, temporary=True) return s except ValueError: pass # make an error sub return Submaster('%s' % name) if __name__ == "__main__": reload_data() s = Submasters() print s.get_all_subs() if 0: # turn this on to normalize all subs for sub in s.get_all_subs(): print "before", sub sub.normalize_patch_names() sub.save() print "after", sub