Changeset - dacbb278d91d
[Not reviewed]
default
0 5 0
drewp@bigasterisk.com - 12 years ago 2013-03-26 16:50:48
drewp@bigasterisk.com
curvecalc port to SyncedGraph. starts up ok, saving is broken
Ignore-this: ef920ab36bc7959f94d9154a9f582c27
5 files changed with 109 insertions and 43 deletions:
0 comments (0 inline, 0 general)
bin/curvecalc
Show inline comments
 
@@ -15,60 +15,63 @@ from twisted.internet import gtk2reactor
 
gtk2reactor.install()
 
from twisted.internet import reactor
 

	
 
import time, textwrap, os, optparse, gtk, linecache, signal, traceback, json
 
from urlparse import parse_qsl
 
import louie as dispatcher 
 
from rdflib import URIRef, Graph, Literal, RDF, RDFS
 
import logging
 
log = logging.getLogger()
 

	
 
import run_local
 
from light9 import showconfig, prof, networking
 
from light9.rdfdb import clientsession
 
from light9.curvecalc.curve import Curveset
 
from light9.curvecalc import curveview 
 
from light9.curvecalc.musicaccess import Music, currentlyPlayingSong
 
from light9.wavelength import wavelength
 
from light9.namespaces import L9
 
from light9.curvecalc.subterm import savekey, graphPathForSubterms
 
from light9.curvecalc.subtermview import add_one_subterm
 
from light9.curvecalc.output import Output
 
from light9.gtkpyconsole import togglePyConsole
 
from light9.rdfdb.syncedgraph import SyncedGraph
 

	
 
class SubtermExists(ValueError):
 
    pass
 

	
 
class Main(object):
 
    def __init__(self, graph, opts, song, curveset, subterms, music):
 
        self.graph, self.opts, self.song = graph, opts, song
 
    def __init__(self, graph, opts, session, curveset, subterms, music):
 
        self.graph, self.opts, self.session = graph, opts, session
 
        self.curveset, self.subterms, self.music = curveset, subterms, music
 
        self.lastSeenInputTime = 0
 

	
 
        wtree = self.wtree = gtk.Builder()
 
        wtree.add_from_file("light9/curvecalc/curvecalc.glade")
 
        mainwin = wtree.get_object("MainWindow")
 
        
 
        mainwin.connect("destroy", self.onQuit)
 
        wtree.connect_signals(self)
 
        gtk.rc_parse("theme/marble-ice/gtk-2.0/gtkrc")
 
        gtk.rc_parse_string("""style "default" {font_name = "sans 7"}""")
 
        if self.opts.reload:
 
            self.refreshTheme()
 
        mainwin.show_all()
 

	
 
        mainwin.connect("delete-event", lambda *args: reactor.crash())
 
        def updateTitle():
 
            # song will soon be a lookup on this curvecalc session
 
            mainwin.set_title("curvecalc - %s" % graph.label(song))
 
            mainwin.set_title("curvecalc - %s" %
 
                              graph.label(
 
                                  graph.value(session, L9['currentSong'])))
 
        graph.addHandler(updateTitle)
 
        mainwin.parse_geometry("1x1-0+0")
 

	
 
        # this is the only one i found that would set the size right,
 
        # but it's a minimum size, which i don't really want
 
        mainwin.set_size_request(1678, 922)
 

	
 

	
 
        wtree.get_object("subterms").connect("add", self.onSubtermChildAdded)
 
        graph.addHandler(self.add_subterms_for_song)
 
        self.refreshCurveView()       
 
        
 
@@ -149,26 +152,28 @@ class Main(object):
 
        if (uri, RDF.type, L9.Subterm) in self.graph:
 
            raise SubtermExists("already have a subterm named %r" % newname)
 
        self.graph.add((uri, RDF.type, L9.Subterm))
 
        self.graph.add((uri, RDFS.label, Literal(newname)))
 
        self.graph.add((self.song, L9['subterm'], uri))
 
        if withCurve:
 
            self.curveset.new_curve(newname)
 

	
 
    def add_subterms_for_song(self):
 
        master = self.wtree.get_object("subterms")
 
        [master.remove(c) for c in master.get_children()]
 

	
 
        for st in self.graph.objects(self.song, L9['subterm']):
 
            log.info("song %s has subterm %s", self.song, st)
 
        song = self.graph.value(self.session, L9['currentSong'])
 
        
 
        for st in self.graph.objects(song, L9['subterm']):
 
            log.info("song %s has subterm %s", song, st)
 
            add_one_subterm(self.graph,
 
                            self.graph.value(st, L9['sub']),
 
                            self.curveset,
 
                            self.subterms,
 
                            master,
 
                            self.graph.value(st, L9['expression']))
 
        master.show_all()
 

	
 
    def refreshTheme(self):
 
        gtk.rc_reparse_all()
 
        reactor.callLater(1, self.refreshTheme)
 

	
 
@@ -206,25 +211,27 @@ class Main(object):
 

	
 
    def onZoomAll(self, item):
 
        dispatcher.send("show all")
 

	
 
    def onPlayPause(self, item):
 
        # since the X coord in a curveview affects the handling, one
 
        # of them may be able to pick this up
 
        results = dispatcher.send("onPlayPause")
 
        times = [t for listener, t in results if t is not None]
 
        self.music.playOrPause(t=times[0] if times else None)
 

	
 
    def onSave(self, *args):
 
        savekey(self.song, self.subterms, self.curveset)
 
        with self.graph.currentState() as g:
 
            savekey(g.value(self.session, L9['currentSong']),
 
                    self.subterms, self.curveset)
 

	
 
    def makeStatusLines(self, master):
 
        """various labels that listen for dispatcher signals"""
 
        for row, (signame, textfilter) in enumerate([
 
            ('input time', lambda t: "%.2fs"%t),
 
            ('output levels',
 
             lambda levels: textwrap.fill("; ".join(["%s:%.2f"%(n,v)
 
                                                     for n,v in
 
                                                     levels.items()[:2]
 
                                                     if v>0]),70)),
 
            ('update period', lambda t: "%.1fms"%(t*1000)),
 
            ('update status', lambda x: str(x)),
 
@@ -275,86 +282,121 @@ class Main(object):
 
                # this is scheduled after some tk shuffling, to
 
                # try to minimize the number of times we redraw
 
                # the curve at startup. If tk is very slow, it's
 
                # ok. You'll just get some wasted redraws.
 
                self.curvesetView.goLive()
 
            except Exception:
 
                print "reload failed:"
 
                traceback.print_exc()
 
        if self.opts.reload:
 
            reactor.callLater(1, self.refreshCurveView)
 

	
 

	
 
class MaxTime(object):
 
    """
 
    looks up the time in seconds for the session's current song
 
    """
 
    def __init__(self, graph, session):
 
        self.graph, self.session = graph, session
 
        graph.addHandler(self.update)
 

	
 
    def update(self):
 
        song = self.graph.value(self.session, L9['currentSong'])
 
        if song is None:
 
            self.maxtime = 0
 
            return
 
        musicfilename = showconfig.songOnDisk(song)
 
        self.maxtime = wavelength(musicfilename)
 
        log.info("new max time %r", self.maxtime)
 
        dispatcher.send("max time", maxtime=self.maxtime)
 

	
 
    def get(self):
 
        return self.maxtime
 

	
 
def main():
 
    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("--skip-music", action='store_true',
 
                      help="ignore music and smooth_music curve files")
 
    parser.add_option("--debug", action="store_true",
 
                      help="log at DEBUG")
 
    parser.add_option("--reload", action="store_true",
 
                      help="live reload of themes and code")
 
    parser.add_option("--startup-only", action='store_true',
 
                      help="quit after loading everything (for timing tests)")
 
    clientsession.add_option(parser)
 
    opts, args = parser.parse_args()
 

	
 
    logging.basicConfig(format="%(asctime)s %(levelname)-5s %(name)s %(filename)s:%(lineno)d: %(message)s")
 
    log.setLevel(logging.DEBUG if opts.debug else logging.INFO)
 

	
 
    log.debug("startup: music %s", time.time() - startTime)
 
    try:
 
        song = URIRef(args[0])
 
    except IndexError:
 
        song = currentlyPlayingSong()
 

	
 

	
 
    session = clientsession.getUri('curvecalc', opts)
 

	
 
    music = Music()
 
    graph = SyncedGraph("curvecalc")
 

	
 
    try:
 
        song = URIRef(args[0])
 
        graph.patchObject(context=session,
 
                          subject=session,
 
                          predicate=L9['currentSong'],
 
                          newObject=song)
 
    except IndexError:
 
        pass
 

	
 
    curveset = Curveset(sliders=opts.sliders)
 
    subterms = []
 

	
 
    curveset.load(basename=os.path.join(
 
        showconfig.curvesDir(),
 
        showconfig.songFilenameFromURI(song)),
 
                  skipMusic=opts.skip_music)
 

	
 
    def curvesetReload():
 
        # not sure if this clears right or not yet
 
        song = graph.value(session, L9['currentSong'])
 
        if song is None:
 
            return
 
        curveset.load(basename=os.path.join(
 
            showconfig.curvesDir(),
 
            showconfig.songFilenameFromURI(song)),
 
                      skipMusic=opts.skip_music)
 
    graph.addHandler(curvesetReload)
 
        
 
    log.debug("startup: output %s", time.time() - startTime)
 
    out = Output(subterms, music)
 

	
 
    musicfilename = showconfig.songOnDisk(song)
 
    maxtime = wavelength(musicfilename)
 
    dispatcher.connect(lambda: maxtime, "get max time", weak=False)
 
    mt = MaxTime(graph, session)
 
    dispatcher.connect(lambda: mt.get(), "get max time", weak=False)
 

	
 
    start = Main(graph, opts, song, curveset, subterms, music)
 
    start = Main(graph, opts, session, curveset, subterms, music)
 

	
 
    dispatcher.send("max time", maxtime=maxtime)
 
    dispatcher.send("show all")
 
        
 
    if opts.startup_only:
 
        log.debug("quitting now because of --startup-only")
 
        return
 

	
 
    from twisted.web import server, resource
 
    class Hover(resource.Resource):
 
        isLeaf = True
 
        def render_GET(self, request):
 
            if request.path == '/hoverTime':
 
                results = dispatcher.send("onPlayPause")
 
                times = [t for listener, t in results if t is not None]
 
                if not times:
 
                    request.setResponseCode(404)
 
                    return "not hovering over any time"
 
                
 
                return json.dumps({"song":song, "hoverTime" : times[0]})
 
                with graph.currentState() as g:
 
                    song = g.value(session, L9['currentSong'])
 
                    return json.dumps({"song": song, "hoverTime" : times[0]})
 
            raise NotImplementedError()
 

	
 
    reactor.listenTCP(networking.curveCalc.port,
 
                      server.Site(Hover()))
 

	
 

	
 
    prof.run(reactor.run, profile=False)
 

	
 
prof.run(main, profile=False)
 

	
light9/curvecalc/curve.py
Show inline comments
 
@@ -131,44 +131,44 @@ class Curveset(object):
 
        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()
 

	
 
    def sorter(self, name):
 
        return (not name in ['music', 'smooth_music'], name)
 

	
 
    def load(self,basename, skipMusic=False):
 
        """find all files that look like basename-curvename and add
 
        curves with their contents
 

	
 
        This fires 'add_curve' dispatcher events to announce the new curves.
 
        """
 
        for filename in sorted(glob.glob("%s-*"%basename), key=self.sorter):
 
            curvename = filename[filename.rfind('-')+1:]
 
            if skipMusic and curvename in ['music', 'smooth_music']:
 
                continue
 
            c=Curve()
 
            c.load(filename)
 
            curvename = curvename.replace('-','_')
 
            self.add_curve(curvename,c)
 

	
 
        self.markers = Markers()
 
        try:
 
            self.markers.load("%s.markers" % basename)
 
        except IOError:
 
            print "no marker file found"
 
            
 
    def save(self,basename):
 
        """writes a file for each curve with a name
 
        like basename-curvename"""
 
        for name,cur in self.curves.items():
 
            cur.save("%s-%s" % (basename,name))
 
        self.markers.save("%s.markers" % basename)
 

	
light9/curvecalc/curveview.py
Show inline comments
 
@@ -608,27 +608,30 @@ class Curveview(object):
 
            return
 
        self.update_time_bar(val)
 
        
 
    def update_time_bar(self, t):
 

	
 
        if not getattr(self, 'timelineLine', None):
 
            self.timelineGroup = goocanvas.Group(parent=self.root)
 
            self.timelineLine = goocanvas.Polyline(
 
                parent=self.timelineGroup,
 
                points=goocanvas.Points([(0,0), (0,0)]),
 
                line_width=2, stroke_color='red')
 

	
 
        self.timelineLine.set_property('points', goocanvas.Points([
 
            self.screen_from_world((t, 0)),
 
            self.screen_from_world((t, 1))]))
 
        try:
 
            pts = [self.screen_from_world((t, 0)),
 
                   self.screen_from_world((t, 1))]
 
        except ZeroDivisionError:
 
            pts = [(-1, -1), (-1, -1)]
 
        self.timelineLine.set_property('points', goocanvas.Points(pts))
 
        
 
        self._time = t
 
        if self.knobEnabled:
 
            self.delete('knob')
 
            prevKey = self.curve.point_before(t)
 
            if prevKey is not None:
 
                pos = self.screen_from_world(prevKey)
 
                self.create_oval(pos[0] - 8, pos[1] - 8,
 
                                 pos[0] + 8, pos[1] + 8,
 
                                 outline='#800000',
 
                                 tags=('knob',))
 
                dispatcher.send("knob out", value=prevKey[1], curve=self.curve)
 
@@ -724,83 +727,96 @@ class Curveview(object):
 
        if t2-t1<30:
 
            for t in range(int(t1),int(t2)+1):
 
                tic(t,str(t))
 
        tic(introPad, str(introPad))
 

	
 
        endtimes = dispatcher.send("get max time")
 
        if endtimes:
 
            endtime = endtimes[0][1]
 
            tic(endtime, "end %.1f"%endtime)
 
            tic(endtime - postPad, "post %.1f" % (endtime - postPad))
 
        
 
    def _draw_one_tic(self,t,label):
 
        x = self.screen_from_world((t,0))[0]
 
        try:
 
            x = self.screen_from_world((t,0))[0]
 
            if not 0 <= x < self.size.width:
 
                return
 
            x = max(5, x) # cheat left-edge stuff onscreen
 
        except ZeroDivisionError:
 
            x = -100
 
            
 
        ht = self.size.height
 
        if not 0 <= x < self.size.width:
 
            return
 
        x = max(5, x) # cheat left-edge stuff onscreen
 
        goocanvas.polyline_new_line(self.curveGroup,
 
                                    x, ht,
 
                                    x, ht - 20,
 
                                    line_width=.5,
 
                                    stroke_color='gray70')
 
        goocanvas.Text(parent=self.curveGroup,
 
                       fill_color="white",
 
                       anchor=gtk.ANCHOR_SOUTH,
 
                       font="ubuntu 7",
 
                       x=x+3, y=ht-20,
 
                       text=label)
 

	
 
    def _draw_line(self, visible_points, area=False):
 
        linepts=[]
 
        step=1
 
        linewidth = 1.5
 
        maxPointsToDraw = self.size.width / 2
 
        if len(visible_points) > maxPointsToDraw:
 
            step = int(len(visible_points) / maxPointsToDraw)
 
            linewidth = .8
 
        for p in visible_points[::step]:
 
            x,y = self.screen_from_world(p)
 
            try:
 
                x,y = self.screen_from_world(p)
 
            except ZeroDivisionError:
 
                x = y = -100
 
            linepts.append((int(x) + .5, int(y) + .5))
 

	
 
        if self.curve.muted:
 
            fill = 'grey34'
 
        else:
 
            fill = 'white'
 

	
 
        if area:
 
            base = self.screen_from_world((0, 0))[1]
 
            try:
 
                base = self.screen_from_world((0, 0))[1]
 
            except ZeroDivisionError:
 
                base = -100
 
            base = base + linewidth / 2
 
            goocanvas.Polyline(parent=self.curveGroup,
 
                               points=goocanvas.Points(
 
                                   [(linepts[0][0], base)] +
 
                                   linepts +
 
                                   [(linepts[-1][0], base)]),
 
                               close_path=True,
 
                               line_width=0,
 
                               fill_color="green",
 
                               )
 

	
 
        self.pl = goocanvas.Polyline(parent=self.curveGroup,
 
                                     points=goocanvas.Points(linepts),
 
                                     line_width=linewidth,
 
                                     stroke_color=fill,
 
                                     )
 
                
 
            
 
    def _draw_handle_points(self,visible_idxs,visible_points):
 
        for i,p in zip(visible_idxs,visible_points):
 
            rad=3
 
            worldp = p
 
            p = self.screen_from_world(p)
 
            try:
 
                p = self.screen_from_world(p)
 
            except ZeroDivisionError:
 
                p = (-100, -100)
 
            dot = goocanvas.Rect(parent=self.curveGroup,
 
                                 x=int(p[0] - rad) + .5,
 
                                 y=int(p[1] - rad) + .5,
 
                                 width=rad * 2, height=rad * 2,
 
                                 stroke_color='gray90',
 
                                 fill_color='blue',
 
                                 line_width=1,
 
                                 #tags=('curve','point', 'handle%d' % i)
 
                                 )
 

	
 
            if worldp[1] == 0:
 
                rad += 3
light9/curvecalc/subterm.py
Show inline comments
 
@@ -31,26 +31,26 @@ class Expr(object):
 
        glo['smoove'] = smoove
 

	
 
        def aft(t, x, smooth=0):
 
            left = x - smooth / 2
 
            right = x + smooth / 2
 
            if left < t < right:
 
                return smoove((t - left) / (right - left))
 
            return t > x
 
        glo['aft'] = lambda x, smooth=0: aft(t, x, smooth)
 

	
 
        def chan(name):
 
            return Submaster.Submaster(
 
                leveldict={Patch.get_dmx_channel(name) : 1.0},
 
                temporary=True)
 
                name=name,
 
                levels={Patch.get_dmx_channel(name) : 1.0})
 
        glo['chan'] = chan
 

	
 
        def smooth_random(speed=1):
 
            """1 = new stuff each second, <1 is slower, fade-ier"""
 
            x = (t * speed) % len(self._smooth_random_items)
 
            x1 = int(x)
 
            x2 = (int(x) + 1) % len(self._smooth_random_items)
 
            y1 = self._smooth_random_items[x1]
 
            y2 = self._smooth_random_items[x2]
 
            return y1 + (y2 - y1) * ((x - x1))
 

	
 
        def notch_random(speed=1):
 
@@ -117,42 +117,42 @@ class Subterm:
 
        # we prevent any exceptions from escaping, since they cause us to
 
        # stop sending levels
 
        try:
 
            if isinstance(subexpr_eval, Submaster.Submaster):
 
                # if the expression returns a submaster, just return it
 
                return subexpr_eval
 
            else:
 
                # otherwise, return our submaster multiplied by the value 
 
                # returned
 
                return self.submaster * subexpr_eval
 
        except Exception, e:
 
            dispatcher.send("expr_error", sender=self.subexpr, exc=str(e))
 
            return Submaster.Submaster('Error: %s' % str(e), temporary=True)
 
            return Submaster.Submaster(name='Error: %s' % str(e), levels={})
 

	
 
    def __repr__(self):
 
        return "<Subterm %s %s>" % (self.submaster, self.subexpr)
 

	
 

	
 
def graphPathForSubterms(song):
 
    return showconfig.subtermsForSong(showconfig.songFilenameFromURI(song)) + ".n3"
 

	
 
def createSubtermGraph(song, subterms):
 
    """rdf graph describing the subterms, readable by add_subterms_for_song"""
 
    graph = Graph()
 
    for subterm in subterms:
 
        assert subterm.submaster.name, "submaster has no name"
 
        assert subterm.submaster.name, "submaster %r has no name" % subterm.submaster
 
        uri = URIRef(song + "/subterm/" + subterm.submaster.name)
 
        graph.add((song, L9['subterm'], uri))
 
        graph.add((uri, RDF.type, L9['Subterm']))
 
        graph.add((uri, RDFS.label, Literal(subterm.submaster.name)))
 
        graph.add((uri, L9['sub'], L9['sub/%s' % subterm.submaster.name]))
 
        graph.add((uri, L9['expression'], Literal(subterm.subexpr.expr)))
 
    return graph
 

	
 
def savekey(song, subterms, curveset):
 
    print "saving", song
 
    log.info("saving %r", song)
 
    g = createSubtermGraph(song, subterms)
 
    g.serialize(graphPathForSubterms(song), format="nt")
 

	
 
    curveset.save(basename=os.path.join(showconfig.curvesDir(),
 
                                        showconfig.songFilenameFromURI(song)))
 
    print "saved"
 
    log.info("saved")
light9/curvecalc/zoomcontrol.py
Show inline comments
 
@@ -129,27 +129,31 @@ class ZoomControl(object):
 

	
 
        self.redrawzoom()
 

	
 
    def see_time_until_end(self, t=None):
 
        """defaults to current time"""
 
        if t is None:
 
            t = self.lastTime
 
        self.start = t - 2
 
        self.end = self.maxtime
 

	
 
        self.redrawzoom()
 
            
 
    def input_time(self,val):
 
    def input_time(self, val):
 
        """move time cursor to this time"""
 
        self.lastTime = val
 
        x = self.can_for_t(self.lastTime)
 
        try:
 
            x = self.can_for_t(self.lastTime)
 
        except ZeroDivisionError:
 
            x = -100
 
        self.time.set_property("points",
 
                               goocanvas.Points([(x, 0),
 
                                                 (x, self.size.height)]))
 
        
 
    def press(self,ev,attr):
 
        self.adjustingattr = attr
 
        
 
    def release(self, widget, ev):
 
        if hasattr(self,'adjustingattr'):
 
            del self.adjustingattr
 
        if hasattr(self,'lastx'):
 
            del self.lastx
 
@@ -173,26 +177,30 @@ class ZoomControl(object):
 
    def t_for_can(self,x):
 
        a, b = self.mintime, self.maxtime
 
        return (x - 20) / (self.size.width - 30) * (b - a) + a
 

	
 
    def redrawzoom(self,*args):
 
        """redraw pieces based on start/end"""
 
        self.size = self.widget.get_allocation()
 
        dispatcher.send("zoom changed")
 
        if not hasattr(self,'created'):
 
            return
 
        y1, y2 = 3, self.size.height - 3
 
        lip = 6
 
        scan = self.can_for_t(self.start)
 
        ecan = self.can_for_t(self.end)
 
        try:
 
            scan = self.can_for_t(self.start)
 
            ecan = self.can_for_t(self.end)
 
        except ZeroDivisionError:
 
            # todo: set the zoom to some clear null state
 
            return
 

	
 
        self.leftbrack.set_property("points", goocanvas.Points([
 
            (scan + lip, y1),
 
            (scan, y1),
 
            (scan, y2),
 
            (scan + lip, y2)]))
 
        self.rightbrack.set_property("points", goocanvas.Points([
 
            (ecan - lip, y1),
 
            (ecan, y1),
 
            (ecan, y2),
 
            (ecan - lip, y2)]))
 
        self.shade.set_properties(
0 comments (0 inline, 0 general)