Changeset - c756638275d6
[Not reviewed]
default
0 5 2
Drew Perttula - 11 years ago 2014-06-02 07:04:40
drewp@bigasterisk.com
quneo input demo. optimize curve display a little.
Ignore-this: 4cf5b4b5853a94842c9fa8e2916bc6f4
7 files changed with 103 insertions and 26 deletions:
0 comments (0 inline, 0 general)
bin/inputdemo
Show inline comments
 
#!bin/python
 
import sys
 
sys.path.append('/usr/lib/python2.7/dist-packages') # For gtk
 
from twisted.internet import gtk3reactor
 
gtk3reactor.install()
 
from twisted.internet import reactor
 
from rdflib import URIRef
 
import optparse, logging, urllib, time
 
from gi.repository import Gtk
 
from run_local import log
 
from light9 import showconfig, networking
 
from light9.rdfdb import clientsession
 
from light9.rdfdb.syncedgraph import SyncedGraph
 
import cyclone.httpclient
 
from light9.curvecalc.client import sendLiveInputPoint
 

	
 
class App(object):
 
    def __init__(self):
 
        parser = optparse.OptionParser()
 
        parser.set_usage("%prog [opts] [curve uri]")
 
        parser.add_option("--debug", action="store_true",
 
                          help="log at DEBUG")
 
        clientsession.add_option(parser)
 
        opts, args = parser.parse_args()
 

	
 
        log.setLevel(logging.DEBUG if opts.debug else logging.INFO)
 

	
 
        self.session = clientsession.getUri('inputdemo', opts)
 
        self.graph = SyncedGraph("inputdemo")
 

	
 
        self.graph.initiallySynced.addCallback(lambda _: self.launch())
 

	
 
        self.curve = args[0] if args else URIRef('http://light9.bigasterisk.com/show/dance2014/song1/curve/c-1401259747.675542')
 
        print "sending points on curve %s" % self.curve
 
        
 
        reactor.run()
 

	
 
    def launch(self):
 
        win = Gtk.Window()
 

	
 
        slider = Gtk.Scale.new_with_range(orientation=Gtk.Orientation.VERTICAL,
 
                                          min=0, max=1, step=.001)
 
        slider.props.inverted = True
 
        slider.connect('value-changed', self.onChanged)
 

	
 
        win.add(slider)
 
        win.parse_geometry('50x250')
 
        win.connect("delete-event", lambda *a: reactor.crash())
 
        win.connect("destroy", lambda *a: reactor.crash())
 
        win.show_all()
 

	
 
    def onChanged(self, scale):
 
        f = cyclone.httpclient.fetch(
 
            networking.curveCalc.path('liveInputPoint'),
 
            method='POST', timeout=1,
 
            postdata=urllib.urlencode({
 
                'curve': self.curve,
 
                'value': str(scale.get_value()),
 
            }))
 
        @f.addCallback
 
        def cb(result):
 
            if result.code // 100 != 2:
 
                log.error("curveCalc said %s: %s", result.code, result.body)
 
        t1 = time.time()
 
        d = sendLiveInputPoint(self.curve, scale.get_value())
 
        @d.addCallback
 
        def done(result):
 
            print "posted in %.1f ms" % (1000 * (time.time() - t1))
 
        @f.addErrback
 
        def eb(err):
 
            print "err", err
 

	
 
App()
bin/inputquneo
Show inline comments
 
new file 100644
 
#!bin/python
 
"""
 
read Quneo midi events, write to curvecalc and maybe to effects
 
"""
 
from __future__ import division
 
from run_local import log
 
import cyclone.web, cyclone.httpclient
 
from rdflib import URIRef
 
from twisted.internet import reactor, task
 
from light9.curvecalc.client import sendLiveInputPoint
 

	
 
import sys
 
sys.path.append('/usr/lib/python2.7/dist-packages') # For pygame
 
import pygame.midi
 

	
 
curves = {
 
    23: URIRef('http://light9.bigasterisk.com/show/dance2014/song1/curve/c-2'),
 
    24: URIRef('http://light9.bigasterisk.com/show/dance2014/song1/curve/c-3'),
 
    25: URIRef('http://light9.bigasterisk.com/show/dance2014/song1/curve/c-4'),
 
    6:URIRef('http://light9.bigasterisk.com/show/dance2014/song1/curve/c-5'),
 
    18: URIRef('http://light9.bigasterisk.com/show/dance2014/song1/curve/c-6'),
 
}
 

	
 
class WatchMidi(object):
 
    def __init__(self):
 
        pygame.midi.init()
 

	
 
        dev = self.findQuneo()
 
        self.inp = pygame.midi.Input(dev)
 
        task.LoopingCall(self.step).start(.05)
 

	
 
        self.noteOn = {}
 
        
 
    def findQuneo(self):
 
        for dev in range(pygame.midi.get_count()):
 
            interf, name, isInput, isOutput, opened = pygame.midi.get_device_info(dev)
 
            if 'QUNEO' in name and isInput:
 
                return dev
 
        raise ValueError("didn't find quneo input device")
 
        
 
    def step(self):
 
        if not self.inp.poll():
 
            return
 
        for ev in self.inp.read(999):
 
            (status, d1, d2, _), _ = ev
 
            print status, d1, d2
 

	
 
            for group in [(23,24,25), (6, 18)]:
 
                if d1 in group:
 
                    if not self.noteOn.get(group):
 
                        print "start zero"
 
                        for d in group:
 
                            sendLiveInputPoint(curves[d], 0)
 
                        self.noteOn[group] = True
 
                    else: # miss first update
 
                        sendLiveInputPoint(curves[d1], d2 / 127)
 
                    
 
                if status == 128: #noteoff
 
                    for d in group:
 
                        sendLiveInputPoint(curves[d], 0)
 
                    self.noteOn[group] = False
 

	
 

	
 
wm = WatchMidi()
 
reactor.run()
light9/curvecalc/client.py
Show inline comments
 
new file 100644
 
"""
 
client code for talking to curvecalc
 
"""
 
import cyclone.httpclient
 
from light9 import networking
 
import urllib
 
from run_local import log
 

	
 
def sendLiveInputPoint(curve, value):
 
    f = cyclone.httpclient.fetch(
 
        networking.curveCalc.path('liveInputPoint'),
 
        method='POST', timeout=1,
 
        postdata=urllib.urlencode({
 
            'curve': curve,
 
            'value': str(value),
 
        }))
 
    @f.addCallback
 
    def cb(result):
 
        if result.code // 100 != 2:
 
            raise ValueError("curveCalc said %s: %s", result.code, result.body)
 
    return f
light9/curvecalc/curve.py
Show inline comments
 
@@ -55,99 +55,99 @@ class Curve(object):
 
        self.points.sort()
 
        dispatcher.send("points changed",sender=self)
 

	
 
    def points_as_string(self):
 
        def outVal(x):
 
            if isinstance(x, basestring): # markers
 
                return x
 
            return "%.4g" % x
 
        return ' '.join("%s %s" % (outVal(p[0]), outVal(p[1]))
 
                        for p in self.points)
 
        
 
    def save(self,filename):
 
        # this is just around for markers, now
 
        if filename.endswith('-music') or filename.endswith('_music'):
 
            print "not saving music track"
 
            return
 
        f = file(filename,'w')
 
        for p in self.points:
 
            f.write("%s %r\n" % p)
 
        f.close()
 

	
 
    def eval(self, t, allow_muting=True):
 
        if self.muted and allow_muting:
 
            return 0
 
        if not self.points:
 
            raise ValueError("curve has no points")
 
        i = bisect_left(self.points,(t,None))-1
 

	
 
        if i == -1:
 
            return self.points[0][1]
 
        if self.points[i][0]>t:
 
            return self.points[i][1]
 
        if i>=len(self.points)-1:
 
            return self.points[i][1]
 

	
 
        p1,p2 = self.points[i],self.points[i+1]
 
        frac = (t-p1[0])/(p2[0]-p1[0])
 
        y = p1[1]+(p2[1]-p1[1])*frac
 
        return y
 
    __call__=eval
 

	
 
    def insert_pt(self, new_pt):
 
        """returns index of new point"""
 
        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
 
        return i
 

	
 
    def live_input_point(self, new_pt):
 
    def live_input_point(self, new_pt, clear_ahead_secs=.01):
 
        x, y = new_pt
 
        exist = self.points_between(x, x + .01)
 
        exist = self.points_between(x, x + clear_ahead_secs)
 
        for pt in exist:
 
            self.remove_point(pt)
 
        self.insert_pt(new_pt)
 
        dispatcher.send("points changed", sender=self)
 
        # now simplify to the left
 
        
 
    def set_points(self, updates):
 
        for i, pt in updates:
 
            self.points[i] = pt
 
            
 
        x = None
 
        for p in self.points:
 
            if p[0] <= x:
 
                raise ValueError("overlapping points")
 
            x = p[0]
 

	
 
    def pop_point(self, i):
 
        return self.points.pop(i)
 
            
 
    def remove_point(self, pt):
 
        self.points.remove(pt)
 
            
 
    def indices_between(self, x1, x2, beyond=0):
 
        leftidx = max(0, bisect(self.points, (x1,None)) - beyond)
 
        rightidx = min(len(self.points),
 
                       bisect(self.points, (x2,None)) + beyond)
 
        return range(leftidx, rightidx)
 
        
 
    def points_between(self, x1, x2):
 
        """returns (x,y) points"""
 
        return [self.points[i] for i in self.indices_between(x1,x2)]
 

	
 
    def point_before(self, x):
 
        """(x,y) of the point left of x, or None"""
 
        leftidx = self.index_before(x)
 
        if leftidx is None:
 
            return None
 
        return self.points[leftidx]
 

	
 
    def index_before(self, x):
 
        leftidx = bisect(self.points, (x,None)) - 1
 
        if leftidx < 0:
 
            return None
 
        return leftidx
 

	
 
class Markers(Curve):
 
    """Marker is like a point but the y value is a string"""
 
    def eval(self):
light9/curvecalc/curveedit.py
Show inline comments
 
"""
 
this may be split out from curvecalc someday, since it doesn't
 
need to be tied to a gui """
 
import cgi, time
 
from twisted.internet import reactor
 
import cyclone.web, cyclone.httpclient, cyclone.websocket
 
from rdflib import URIRef
 
from lib.cycloneerr import PrettyErrorHandler
 
from run_local import log
 
from louie import dispatcher
 

	
 
def serveCurveEdit(port, hoverTimeResponse, curveset):
 
    """
 
    /hoverTime requests actually are handled by the curvecalc gui
 
    """
 
    curveEdit = CurveEdit(curveset)
 
    
 
    class HoverTime(PrettyErrorHandler, cyclone.web.RequestHandler):
 
        def get(self):
 
            hoverTimeResponse(self)
 

	
 
    class LiveInputPoint(PrettyErrorHandler, cyclone.web.RequestHandler):
 
        def post(self):
 
            params = cgi.parse_qs(self.request.body)
 
            curve = URIRef(params['curve'][0])
 
            value = float(params['value'][0])
 
            curveEdit.liveInputPoint(curve, value)
 
            self.set_status(204)
 
            
 
    reactor.listenTCP(port, cyclone.web.Application(handlers=[
 
        (r'/hoverTime', HoverTime),
 
        (r'/liveInputPoint', LiveInputPoint),
 
        ], debug=True))
 

	
 
    
 
class CurveEdit(object):
 
    def __init__(self, curveset):
 
        self.curveset = curveset
 
        dispatcher.connect(self.inputTime, "input time")
 
        self.currentTime = 0
 
        
 
    def inputTime(self, val):
 
        self.currentTime = val
 
        
 
    def liveInputPoint(self, curveUri, value):
 
        curve = self.curveset.curveFromUri(curveUri)
 
        curve.live_input_point((self.currentTime, value))
 
        curve.live_input_point((self.currentTime, value), clear_ahead_secs=.5)
 
        
light9/curvecalc/curveview.py
Show inline comments
 
@@ -690,104 +690,102 @@ class Curveview(object):
 
            pts = [(-1, -1), (-1, -1)]
 
        self.timelineLine.set_property('points', 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)
 

	
 
    def update_curve(self, *args):
 
        if not getattr(self, '_pending_update', False):
 
            self._pending_update = True
 
            reactor.callLater(.01, self._update_curve)
 
        
 
    def _update_curve(self):
 
        if not getattr(self, '_pending_update', False):
 
            return
 
        self._pending_update = False
 
        if not self.canvas.is_visible():
 
            # this avoids an occasional crash in something like
 
            # goocanvas_add_item when we write objects to a canvas
 
            # that's gone
 
            return
 
        if not self.redrawsEnabled:
 
            print "no redrawsEnabled, skipping", self
 
            return
 

	
 
        visible_x = (self.world_from_screen(0,0)[0],
 
                     self.world_from_screen(self.canvas.props.x2, 0)[0])
 

	
 
        visible_idxs = self.curve.indices_between(visible_x[0], visible_x[1],
 
                                                  beyond=1)
 
        visible_points = [self.curve.points[i] for i in visible_idxs]
 
        
 
        if getattr(self, 'curveGroup', None):
 
            self.curveGroup.remove()
 
        self.curveGroup = GooCanvas.CanvasGroup(parent=self.canvas.get_root_item())
 

	
 
        self.canvas.set_property("background-color",
 
                                 "gray20" if self.curve.muted else "black")
 

	
 
        self.update_time_bar(self._time)
 
        if self.canvas.props.y2 < 40:
 
            self._draw_line(visible_points, area=True)
 
        else:
 
        self._draw_line(visible_points, area=True)
 
        self._draw_markers(
 
            self.markers.points[i] for i in
 
            self.markers.indices_between(visible_x[0], visible_x[1]))
 
        if self.canvas.props.y2 > 80:
 
            self._draw_time_tics(visible_x)
 
            self._draw_line(visible_points)
 
            self._draw_markers(
 
                self.markers.points[i] for i in
 
                self.markers.indices_between(visible_x[0], visible_x[1]))
 

	
 
            self.dots = {} # idx : canvas rectangle
 

	
 
            if len(visible_points) < 50 and not self.curve.muted:
 
                self._draw_handle_points(visible_idxs,visible_points)
 

	
 
        self.selectionChanged()
 

	
 
    def is_music(self):
 
        """are we one of the music curves (which might be drawn a bit
 
        differently)"""
 
        return self._isMusic
 

	
 
    def _draw_markers(self, pts):
 
        colorMap = {
 
            'q':'#598522',
 
            'w':'#662285',
 
            'e':'#852922',
 
            'r':'#85225C',
 
            't':'#856B22',
 
            'y':'#227085',
 
            }
 
        for t, name in pts:
 
            x = int(self.screen_from_world((t,0))[0]) + .5
 
            polyline_new_line(self.curveGroup,
 
                              x, 0, x, self.canvas.props.y2,
 
                              line_width=.4 if name in 'rty' else .8,
 
                              stroke_color=colorMap.get(name, 'gray'))
 

	
 
    def _draw_time_tics(self,visible_x):
 
        tic = self._draw_one_tic
 

	
 
        tic(0, "0")
 
        t1,t2=visible_x
 
        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):
 
        try:
 
            x = self.screen_from_world((t,0))[0]
 
@@ -1026,129 +1024,131 @@ class Curveview(object):
 
            updates.append((idx, tuple(newp)))
 
        self.curve.set_points(updates)
 
        return moved
 
            
 
    def unselect(self):
 
        self.select_indices([])
 

	
 
    def onScroll(self, widget, event):
 
        t = self.world_from_screen(event.x, 0)[0]
 
        self.zoomControl.zoom_about_mouse(
 
            t, factor=1.5 if event.direction == Gdk.ScrollDirection.DOWN else 1/1.5)
 
        # Don't actually scroll the canvas! (it shouldn't have room to
 
        # scroll anyway, but it does because of some coordinate errors
 
        # and borders and stuff)
 
        return True 
 
        
 
    def onRelease(self, widget, event):
 
        self.print_state("dotrelease")
 

	
 
        if event.state & Gdk.ModifierType.SHIFT_MASK: # relese-B1
 
            self.sketch_release(event)
 
            return
 

	
 
        self.select_release(event)
 
 
 
        if not self.dragging_dots:
 
            return
 
        self.last_mouse_world = None
 
        self.dragging_dots = False
 

	
 
class CurveRow(object):
 
    """
 
    one of the repeating curve rows (including widgets on the left)
 

	
 
    i wish these were in a list-style TreeView so i could set_reorderable on it
 

	
 
    please pack self.box
 
    """
 
    def __init__(self, graph, name, curve, markers, slider, knobEnabled, zoomControl):
 
        self.graph = graph
 
        self.name = name
 
        self.box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
 
        self.box.set_border_width(1)
 

	
 
        self.cols = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
 
        self.box.add(self.cols)
 
        
 
        controls = Gtk.Frame()
 
        controls.set_size_request(115, -1)
 
        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.curveView = Curveview(curve, markers, knobEnabled=knobEnabled,
 
                                   isMusic=name in ['music', 'smooth_music'],
 
                                   zoomControl=zoomControl)
 
        
 
        self.initCurveView()
 
        dispatcher.connect(self.rebuild, "all curves rebuild")
 

	
 
    def isFocus(self):
 
        return self.curveView.widget.is_focus()
 
        
 
    def rebuild(self):
 
        raise NotImplementedError('obsolete, if curves are drawing right')
 
        self.curveView.rebuild()
 
        self.initCurveView()
 
        self.update_ui_to_collapsed_state()
 

	
 
    def destroy(self):
 
        self.curveView.entered = False  # help suppress bad position events
 
        del self.curveView
 
        self.box.destroy()
 
        
 
    def initCurveView(self):
 
        self.curveView.widget.show()
 
        self.setHeight(100)
 
        self.cols.pack_start(self.curveView.widget, expand=True, fill=True, padding=0)       
 

	
 
    def setHeight(self, h):
 
        self.curveView.widget.set_size_request(-1, h)
 
        # the event watcher wasn't catching these
 
        reactor.callLater(.5, self.curveView.update_curve)
 
        
 
    def setupControls(self, controls, name, curve, slider):
 
        box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
 
        controls.add(box)
 

	
 
        curve_name_label = Gtk.LinkButton()
 
        def update_label():
 
            # todo: abort if we don't still exist...
 
            p = curve_name_label.props
 
            p.uri = curve.uri
 
            p.label = self.graph.label(curve.uri)
 
        self.graph.addHandler(update_label)
 
        
 

	
 
        self.muted = Gtk.CheckButton("M")
 
        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)
 

	
 
    def onDelete(self):
 
        self.curveView.onDelete()
 
        
 
    def update_ui_to_collapsed_state(self, *args):
 
        if self.collapsed.get_active():
 
            self.curveView.widget.set_size_request(-1, 25)
 
            [w.hide() for w in self.hideWhenCollapsed]
 
        else:
 
            self.curveView.widget.set_size_request(-1, 100)
 
            [w.show() for w in self.hideWhenCollapsed]
 

	
 
    def sync_mute_to_curve(self, *args):
 
        """send value from CheckButton to the master attribute inside Curve"""
 
        new_mute = self.muted.get_active()
 
        self.curveView.curve.muted = new_mute
 

	
 
    def update_mute_look(self):
 
        """set colors on the widgets in the row according to self.muted.get()"""
 
        # not yet ported for gtk
 
        return
makefile
Show inline comments
 
@@ -7,49 +7,49 @@ tests_watch:
 
	eval env/bin/nosetests --with-watch $(NOSEARGS)
 

	
 

	
 
# needed packages: python-gtk2 python-imaging
 

	
 
install_python_deps: link_to_sys_packages
 
	env/bin/pip install -r pydeps
 

	
 
DP=/usr/lib/python2.7/dist-packages
 
SP=env/lib/python2.7/site-packages
 

	
 
link_to_sys_packages:
 
	# http://stackoverflow.com/questions/249283/virtualenv-on-ubuntu-with-no-site-packages
 
	ln -sf $(DP)/glib $(SP)/
 
	ln -sf $(DP)/gi $(SP)/
 
	ln -sf $(DP)/gobject $(SP)/
 
	ln -sf $(DP)/cairo $(SP)/
 
	ln -sf $(DP)/gtk-2.0 $(SP)/
 
	ln -sf $(DP)/pygtk.py $(SP)/
 
	ln -sf $(DP)/pygtk.pth $(SP)/
 
	ln -sf $(DP)/pygst.pth $(SP)/
 
	ln -sf $(DP)/pygst.py $(SP)/
 
	ln -sf $(DP)/gst-0.10 $(SP)/
 
	ln -sf $(DP)/PIL $(SP)/
 
	ln -sf $(DP)/PIL.pth $(SP)/
 
	ln -sf $(DP)/goocanvasmodule.so $(SP)/
 

	
 
PYTHON=/usr/bin/pypy
 
PYTHON=/usr/bin/python
 

	
 
create_virtualenv:
 
	mkdir -p env
 
	virtualenv -p $(PYTHON) env
 
	ln -sf ../env/bin/python bin/python
 

	
 
tkdnd_build:
 
	# get tkdnd r95 with subversion
 
	# then apply tkdnd-patch-on-r95 to that
 
	cd tkdnd/trunk
 
	./configure
 
	make
 

	
 
bin/ascoltami2: gst_packages link_to_sys_packages
 

	
 
gst_packages:
 
	sudo aptitude install python-gi gir1.2-gst-plugins-base-1.0 libgirepository-1.0-1 gir1.2-gstreamer-1.0 gstreamer1.0-tools gstreamer1.0-plugins-good gstreamer1.0-pulseaudio gir1.2-goocanvas-2.0-9
 

	
 
packages:
 
	sudo aptitude install coffeescript freemind normalize-audio audacity python-pygoocanvas
 
	sudo aptitude install coffeescript freemind normalize-audio audacity python-pygoocanvas python-pygame
0 comments (0 inline, 0 general)