changeset 1097:f7618f29bb89

rewrite CurveSet. remove sliders support from curvecalc. curve edits now write quickly to the graph. Ignore-this: d0338e3a21636d992958594e878962e
author Drew Perttula <drewp@bigasterisk.com>
date Mon, 09 Jun 2014 03:05:32 +0000
parents 087f6cbe4b22
children 770b0d0005fc
files bin/curvecalc light9/curvecalc/curve.py light9/curvecalc/curveview.py
diffstat 3 files changed, 80 insertions(+), 165 deletions(-) [+]
line wrap: on
line diff
--- a/bin/curvecalc	Sun Jun 08 09:30:03 2014 +0000
+++ b/bin/curvecalc	Mon Jun 09 03:05:32 2014 +0000
@@ -449,7 +449,7 @@
     except IndexError:
         pass
 
-    curveset = Curveset(graph=graph, session=session, sliders=opts.sliders)
+    curveset = Curveset(graph=graph, session=session)
         
     log.debug("startup: output %s", time.time() - startTime)
 
@@ -484,8 +484,6 @@
     startTime = time.time()
     parser = optparse.OptionParser()
     parser.set_usage("%prog [opts] [songURI]")
-    parser.add_option("--sliders", action='store_true',
-                      help='use hardware sliders')
     parser.add_option("--debug", action="store_true",
                       help="log at DEBUG")
     parser.add_option("--reload", action="store_true",
--- a/light9/curvecalc/curve.py	Sun Jun 08 09:30:03 2014 +0000
+++ b/light9/curvecalc/curve.py	Mon Jun 09 03:05:32 2014 +0000
@@ -2,13 +2,12 @@
 import glob, time, logging, ast, os
 from bisect import bisect_left,bisect
 import louie as dispatcher
+from twisted.internet import reactor
 from rdflib import Literal
 from light9 import showconfig
 from light9.namespaces import L9, RDF, RDFS
 from light9.rdfdb.patch import Patch
 
-from bcf2000 import BCF2000
-
 log = logging.getLogger()
 # todo: move to config, consolidate with ascoltami, musicPad, etc
 introPad = 4
@@ -98,6 +97,7 @@
         i = bisect(self.points, (new_pt[0],None))
         self.points.insert(i,new_pt)
         # missing a check that this isn't the same X as the neighbor point
+        dispatcher.send("points changed", sender=self)
         return i
 
     def live_input_point(self, new_pt, clear_ahead_secs=.01):
@@ -112,7 +112,11 @@
     def set_points(self, updates):
         for i, pt in updates:
             self.points[i] = pt
+
+        #self.checkOverlap()
+        dispatcher.send("points changed", sender=self)
             
+    def checkOverlap(self):
         x = None
         for p in self.points:
             if p[0] <= x:
@@ -120,10 +124,13 @@
             x = p[0]
 
     def pop_point(self, i):
-        return self.points.pop(i)
+        p = self.points.pop(i)
+        dispatcher.send("points changed", sender=self)
+        return p
             
     def remove_point(self, pt):
         self.points.remove(pt)
+        dispatcher.send("points changed", sender=self)
             
     def indices_between(self, x1, x2, beyond=0):
         leftidx = max(0, bisect(self.points, (x1,None)) - beyond)
@@ -153,6 +160,7 @@
     holds a Curve, deals with graphs
     """
     def __init__(self, graph, uri):
+        # probably newCurve and loadCurve should be the constructors instead.
         self.graph, self.uri = graph, uri
 
     def curvePointsContext(self):
@@ -163,6 +171,9 @@
         Save type/label for a new :Curve resource.
         Pass the ctx where the main curve data (not the points) will go.
         """
+        if hasattr(self, 'curve'):
+            raise ValueError('CurveResource already has a curve %r' %
+                             self.curve)
         self.graph.patch(Patch(addQuads=[
             (self.uri, RDF.type, L9['Curve'], ctx),
             (self.uri, RDFS.label, label, ctx),
@@ -170,11 +181,19 @@
         self.curve = Curve(self.uri)
         self.curve.points.extend([(0, 0)])
         self.saveCurve()
+        self.watchChanges()
         
     def loadCurve(self):
-        pts = self.graph.value(self.uri, L9['points'])
+        if hasattr(self, 'curve'):
+            raise ValueError('CurveResource already has a curve %r' %
+                             self.curve)
+        pointsFile = self.graph.value(self.uri, L9['pointsFile'])
         self.curve = Curve(self.uri,
-                           pointsStorage='file' if pts is None else 'graph')
+                           pointsStorage='file' if pointsFile else 'graph')
+        self.graph.addHandler(self.pointsFromGraph)
+        
+    def pointsFromGraph(self):
+        pts = self.graph.value(self.uri, L9['points'])
         if pts is not None:
             self.curve.set_from_string(pts)
         else:
@@ -183,8 +202,10 @@
                 self.curve.load(os.path.join(showconfig.curvesDir(), diskPts))
             else:
                 log.warn("curve %s has no points", self.uri)
+        self.watchCurvePointChanges()
 
     def saveCurve(self):
+        self.pendingSave = None
         for p in self.getSavePatches():
             self.graph.patch(p)
 
@@ -202,6 +223,24 @@
         else:
             raise NotImplementedError(self.curve.pointsStorage)
 
+    def watchCurvePointChanges(self):
+        """start watching and saving changes to the graph"""
+        dispatcher.connect(self.onChange, 'points changed', sender=self.curve)
+        
+    def onChange(self):
+
+        # Don't write a patch for the edited curve points until they've been
+        # stable for this long. This can be very short, since it's just to
+        # stop a 100-point edit from sending many updates. If it's too long,
+        # you won't see output lights change while you drag a point.  Todo:
+        # this is just the wrong timing algorithm- it should be a max rate,
+        # not a max-hold-still-time.
+        HOLD_POINTS_GRAPH_COMMIT_SECS = .1
+        
+        if getattr(self, 'pendingSave', None):
+            self.pendingSave.cancel()
+        self.pendingSave = reactor.callLater(HOLD_POINTS_GRAPH_COMMIT_SECS,
+                                             self.saveCurve)
         
 class Markers(Curve):
     """Marker is like a point but the y value is a string"""
@@ -214,54 +253,20 @@
         return 0
     return (p2[1] - p1[1]) / (p2[0] - p1[0])
 
-
-class Sliders(BCF2000):
-    def __init__(self, cb, knobCallback, knobButtonCallback):
-        BCF2000.__init__(self)
-        self.cb = cb
-        self.knobCallback = knobCallback
-        self.knobButtonCallback = knobButtonCallback
-    def valueIn(self, name, value):
-        if name.startswith("slider"):
-            self.cb(int(name[6:]), value / 127)
-        if name.startswith("knob"):
-            self.knobCallback(int(name[4:]), value / 127)
-        if name.startswith("button-knob"):
-            self.knobButtonCallback(int(name[11:]))
-
-        
+       
 class Curveset(object):
-    curves = None # curvename : curve
-    def __init__(self, graph, session, sliders=False):
-        """sliders=True means support the hardware sliders"""
+    def __init__(self, graph, session):
         self.graph, self.session = graph, session
 
         self.currentSong = None
-        self.curves = {} # name (str) : Curve
-        self.curveName = {} # reverse
+        self.curveResources = {} # uri : CurveResource
         
-        self.sliderCurve = {} # slider number (1 based) : curve name
-        self.sliderNum = {} # reverse
-        if sliders:
-            self.sliders = Sliders(self.hw_slider_in, self.hw_knob_in, 
-                                   self.hw_knob_button)
-            dispatcher.connect(self.curvesToSliders, "curves to sliders")
-            dispatcher.connect(self.knobOut, "knob out")
-            self.lastSliderTime = {} # num : time
-            self.sliderSuppressOutputUntil = {} # num : time
-            self.sliderIgnoreInputUntil = {}
-        else:
-            self.sliders = None
         self.markers = Markers(uri=None, pointsStorage='file')
 
         graph.addHandler(self.loadCurvesForSong)
 
     def curveFromUri(self, uri):
-        # self.curves should be indexed by this
-        for c in self.curves.values():
-            if c.uri == uri:
-                return c
-        raise KeyError("no curve %s" % uri)
+        return self.curveResources[uri].curve
 
     def loadCurvesForSong(self):
         """
@@ -271,10 +276,7 @@
         """
         log.info('loadCurvesForSong')
         dispatcher.send("clear_curves")
-        self.curves.clear()
-        self.curveName.clear()
-        self.sliderCurve.clear()
-        self.sliderNum.clear()
+        self.curveResources.clear()
         self.markers = Markers(uri=None, pointsStorage='file')
         
         self.currentSong = self.graph.value(self.session, L9['currentSong'])
@@ -283,14 +285,14 @@
 
         for uri in sorted(self.graph.objects(self.currentSong, L9['curve'])):
             try:
-                cr = CurveResource(self.graph, uri)
+                cr = self.curveResources[uri] = CurveResource(self.graph, uri)
                 cr.loadCurve()
 
                 curvename = self.graph.label(uri)
                 if not curvename:
                     raise ValueError("curve %r has no label" % uri)
-                self.add_curve(curvename, cr.curve)
-
+                dispatcher.send("add_curve", sender=self,
+                                uri=uri, label=curvename, curve=cr.curve)
             except Exception as e:
                 log.error("loading %s failed: %s", uri, e)
                 
@@ -310,46 +312,29 @@
             showconfig.songFilenameFromURI(self.currentSong))
 
         patches = []
-        for label, curve in self.curves.items():
-            cr = CurveResource(self.graph, curve.uri)
-            cr.curve = curve
+        for cr in self.curveResources.values():
             patches.extend(cr.getSavePatches())
 
         self.markers.save("%s.markers" % basename)
-        # this will cause reloads that will clear our curve list
+        # this will cause reloads that will rebuild our curve list
         for p in patches:
             self.graph.patch(p)
             
     def sorter(self, name):
         return self.curves[name].uri
         
-    def curveNamesInOrder(self):
-        return sorted(self.curves.keys(), key=self.sorter)
-            
-    def add_curve(self, name, curve):
-        # should be indexing by uri
-        if isinstance(name, Literal):
-            name = str(name) 
-        if name in self.curves:
-            raise ValueError("can't add a second curve named %r" % name)
-        self.curves[name] = curve
-        self.curveName[curve] = name
+    def curveUrisInOrder(self):
+        return sorted(self.curveResources.keys())
 
-        if self.sliders and name not in ['smooth_music', 'music']:
-            num = len(self.sliderCurve) + 1
-            if num <= 8:
-                self.sliderCurve[num] = name
-                self.sliderNum[name] = num
-            else:
-                num = None
-        else:
-            num = None
-            
-        dispatcher.send("add_curve", slider=num, knobEnabled=num is not None,
-                        sender=self, name=name)
-
+    def currentCurves(self):
+        # deprecated
+        for uri, cr in sorted(self.curveResources.items()):
+            with self.graph.currentState(
+                    tripleFilter=(uri, RDFS['label'], None)) as g:
+                yield uri, g.label(uri), cr.curve
+        
     def globalsdict(self):
-        return self.curves.copy()
+        raise NotImplementedError('subterm used to get a dict of name:curve')
     
     def get_time_range(self):
         return 0, dispatcher.send("get max time")[0][1]
@@ -367,7 +352,7 @@
 
         uri = self.graph.sequentialUri(self.currentSong + '/curve-')
 
-        cr = CurveResource(self.graph, uri)
+        cr = self.curveResources[uri] = CurveResource(self.graph, uri)
         cr.newCurve(ctx=self.currentSong, label=Literal(name))
         s, e = self.get_time_range()
         cr.curve.points.extend([(s, 0), (e, 0)])
@@ -378,55 +363,3 @@
             ]))
         cr.saveCurve()
 
-    def hw_slider_in(self, num, value):
-        try:
-            curve = self.curves[self.sliderCurve[num]]
-        except KeyError:
-            return
-
-        now = time.time()
-        if now < self.sliderIgnoreInputUntil.get(num):
-            return
-        # don't make points too fast. This is the minimum spacing
-        # between slider-generated points.
-        self.sliderIgnoreInputUntil[num] = now + .1
-        
-        # don't push back on the slider for a little while, since the
-        # user might be trying to slowly move it. This should be
-        # bigger than the ignore time above.
-        self.sliderSuppressOutputUntil[num] = now + .2
-        
-        dispatcher.send("set key", curve=curve, value=value)
-
-    def hw_knob_in(self, num, value):
-        try:
-            curve = self.curves[self.sliderCurve[num]]
-        except KeyError:
-            return
-        dispatcher.send("knob in", curve=curve, value=value)
-
-    def hw_knob_button(self, num):
-        try:
-            curve = self.curves[self.sliderCurve[num]]
-        except KeyError:
-            return
-
-        dispatcher.send("set key", curve=curve)
-        
-
-    def curvesToSliders(self, t):
-        now = time.time()
-        for num, name in self.sliderCurve.items():
-            if now < self.sliderSuppressOutputUntil.get(num):
-                continue
-#            self.lastSliderTime[num] = now
-            
-            value = self.curves[name].eval(t)
-            self.sliders.valueOut("slider%s" % num, value * 127)
-
-    def knobOut(self, curve, value):
-        try:
-            num = self.sliderNum[self.curveName[curve]]
-        except KeyError:
-            return
-        self.sliders.valueOut("knob%s" % num, value * 127)
--- a/light9/curvecalc/curveview.py	Sun Jun 08 09:30:03 2014 +0000
+++ b/light9/curvecalc/curveview.py	Mon Jun 09 03:05:32 2014 +0000
@@ -526,7 +526,6 @@
         if idx is not None:
             pos = self.curve.points[idx]
             self.curve.set_points([(idx, (pos[0], value))])
-            self.update_curve()
 
     def slider_in(self, curve, value=None):
         """user pushed on a slider. make a new key.  if value is None,
@@ -538,7 +537,6 @@
             value = self.curve.eval(self.current_time())
 
         self.curve.insert_pt((self.current_time(), value))
-        self.update_curve()
 
     def print_state(self, msg=""):
         if 0:
@@ -599,7 +597,6 @@
 
     def setPoints(self, updates):
         self.curve.set_points(updates)
-        self.update_curve()
         
     def selectionChanged(self):
         if self.selectManip:
@@ -930,7 +927,6 @@
 
     def add_points(self, pts):
         idxs = [self.curve.insert_pt(p) for p in pts]
-        self.update_curve()
         self.select_indices(idxs)
         
     def add_point(self, p):
@@ -938,7 +934,6 @@
 
     def add_marker(self, p):
         self.markers.insert_pt(p)
-        self.update_curve()
         
     def remove_point_idx(self, *idxs):
         idxs = list(idxs)
@@ -963,8 +958,6 @@
             self.select_indices(newsel)
             idxs[:] = newidxs
             
-        self.update_curve()
-
     def highlight_selected_dots(self):
         if not self.redrawsEnabled:
             return
@@ -1020,10 +1013,8 @@
             delta = 0,0
         self.last_mouse_world = cur
 
-        moved = self.translate_points(delta)
+        self.translate_points(delta)
         
-        if moved:
-            self.update_curve()
 
     def translate_points(self, delta):
         moved = False
@@ -1079,7 +1070,7 @@
 
     please pack self.box
     """
-    def __init__(self, graph, name, curve, markers, slider, knobEnabled, zoomControl):
+    def __init__(self, graph, name, curve, markers, zoomControl):
         self.graph = graph
         self.name = name
         self.box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
@@ -1092,9 +1083,9 @@
         controls.set_size_request(160, -1)
         controls.set_shadow_type(Gtk.ShadowType.OUT)
         self.cols.pack_start(controls, expand=False, fill=True, padding=0)
-        self.setupControls(controls, name, curve, slider)
+        self.setupControls(controls, name, curve)
 
-        self.curveView = Curveview(curve, markers, knobEnabled=knobEnabled,
+        self.curveView = Curveview(curve, markers,
                                    isMusic=name in ['music', 'smooth_music'],
                                    zoomControl=zoomControl)
         
@@ -1125,7 +1116,7 @@
         # the event watcher wasn't catching these
         reactor.callLater(.5, self.curveView.update_curve)
         
-    def setupControls(self, controls, name, curve, slider):
+    def setupControls(self, controls, name, curve):
         box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
         controls.add(box)
 
@@ -1142,13 +1133,6 @@
         self.muted.connect("toggled", self.sync_mute_to_curve)
         dispatcher.connect(self.mute_changed, 'mute changed', sender=curve)
 
-        self.sliderLabel = None
-        if slider is not None:
-            # slider should have a checkbutton, defaults to off for
-            # music tracks
-            self.sliderLabel = Gtk.Label("Slider %s" % slider)
-            box.pack_start(self.sliderLabel, expand=True, fill=True, padding=0)
-
         box.pack_start(curve_name_label, expand=True, fill=True, padding=0)
         box.pack_start(self.muted, expand=True, fill=True, padding=0)
 
@@ -1192,9 +1176,9 @@
 
         self.zoomControl = self.initZoomControl(zoomControlBox)
         self.zoomControl.redrawzoom()
-        
-        for c in curveset.curveNamesInOrder():
-            self.add_curve(c) 
+
+        for uri, label, curve in curveset.currentCurves():
+            self.add_curve(uri, label, curve) 
 
         dispatcher.connect(self.clear_curves, "clear_curves")
         dispatcher.connect(self.add_curve, "add_curve", sender=self.curveset)
@@ -1270,12 +1254,12 @@
         self.curveset.new_curve(self.newcurvename.get())
         self.newcurvename.set('')
         
-    def add_curve(self, name, slider=None, knobEnabled=False):
-        if isinstance(name, Literal):
-            name = str(name)
-        curve = self.curveset.curves[name]
-        f = CurveRow(self.graph, name, curve, self.curveset.markers,
-                     slider, knobEnabled, self.zoomControl)
+    def add_curve(self, uri, label, curve):
+        if isinstance(label, Literal):
+            label = str(label)
+
+        f = CurveRow(self.graph, label, curve, self.curveset.markers,
+                     self.zoomControl)
         self.curvesVBox.pack_start(f.box, expand=True, fill=True, padding=0)
         f.box.show_all()
         self.allCurveRows.add(f)