Changeset - 42e4c4728a66
[Not reviewed]
default
0 8 0
drewp@bigasterisk.com - 18 years ago 2007-04-02 14:34:14
drewp@bigasterisk.com
ascoltami, subcomposer, keyboardcomposer use rdf show data; raise channel count to 270
8 files changed with 86 insertions and 68 deletions:
0 comments (0 inline, 0 general)
bin/ascoltami
Show inline comments
 
@@ -23,24 +23,26 @@ from optparse import OptionParser
 
import os,math,time
 
import Tkinter as tk
 
#import logging
 
#log = logging.getLogger()
 
#log.setLevel(logging.DEBUG)
 

	
 
from twisted.internet import reactor,tksupport
 
from twisted.internet.error import CannotListenError
 
from twisted.web import xmlrpc, server
 

	
 
import run_local
 
from light9 import networking, showconfig, wavelength
 
from light9.namespaces import L9, MUS
 
from light9.uihelpers import toplevelat
 

	
 
from pympd import Mpd
 

	
 
appstyle={'fg':'white','bg':'black'}
 

	
 
def shortSongPath(song, all):
 
    prefixlen = len(os.path.commonprefix(all))
 
    # include to the last os.sep- dont crop path elements in the middle
 
    prefixlen = all[0].rfind(os.sep)+1 
 

	
 
    return os.path.splitext(song[prefixlen:])[0]
 
    
 
@@ -122,27 +124,30 @@ class Player:
 

	
 
    def pollStatus(self):
 
        if self.state.get() == 'stop':
 
            self.current_time.set(-4)
 
            
 
        self.mpd.status().addCallback(self.pollStatus2)
 
        
 
    def pollStatus2(self, stat):
 
        try:
 
            if self.state.get() != stat.state:
 
                self.state.set(stat.state)
 

	
 
        if self.state.get() != stat.state:
 
            self.state.set(stat.state)
 

	
 
            if hasattr(stat, 'time_elapsed'):
 
                elapsed = stat.time_elapsed
 
                songnum = stat.song
 
                total = stat.time_total
 
                if self.mpd_is_lying and elapsed < 3:
 
                    self.mpd_is_lying = False
 

	
 
                # mpd lies about elapsed, song, and total during the last
 
                # .5sec of each song. so we coast through that part
 
                if elapsed > total - .75 or self.mpd_is_lying:
 
                    if not self.mpd_is_lying:
 
                        self.mpd_is_lying = True
 
                        self.true_song_total = songnum, total
 
                        self.marked_time = time.time()
 
                        self.marked_val = elapsed
 
                    elapsed = self.marked_val + (time.time() - self.marked_time)
 
                    songnum, total = self.true_song_total
 

	
 
@@ -260,55 +265,52 @@ class GoButton:
 
            return "skip to post", self.player.skip_to_post
 
        
 
    def action(self):
 
        desc, func = self._nextAction()
 
        func()
 
        
 
    def updateStatus(self, *args):
 
        desc, func = self._nextAction()
 
        self.statusLabel.config(text=desc)
 
        
 

	
 

	
 
def buildsonglist(root,songfiles,player):
 
def buildsonglist(root, graph, songs, player):
 
    songlist=tk.Frame(root,bd=2,relief='raised',bg='black')
 

	
 
    prefixlen=len(os.path.commonprefix(songfiles))
 
    # include to the last os.sep- dont crop path elements in the middle
 
    prefixlen=songfiles[0].rfind(os.sep)+1 
 
    maxsfwidth=max([len(x[prefixlen:]) for x in songfiles])
 
    maxsfwidth=max([len(graph.label(song)) for song in songs])
 

	
 
    for i,sf in enumerate(songfiles):
 
        b=tk.Button(songlist,text=sf[prefixlen:],width=maxsfwidth,
 
    for i,song in enumerate(songs):
 
        b=tk.Button(songlist,text=graph.label(song),width=maxsfwidth,
 
                    anchor='w',pady=0,bd=0,relief='flat',
 
                    font="arial 14 bold")
 
        b.bind("<Configure>",lambda ev,b=b:
 
               b.config(font="arial %d bold" % min(12,int((ev.height-3)*.8))))
 
        try:
 
            # rainbow colors
 
            frac=i/len(songs)
 
            b.config(bg='black',
 
                     fg="#%02x%02x%02x" % tuple([int(255*(.7+.3*
 
                                                          math.sin(frac*4+x))
 
                                                     ) for x in 1,2,3]))
 
        except Exception,e:
 
            print "rainbow failed: %s"%e
 
        
 
        b.config(command=lambda song=song: player.play(song))
 
        b.pack(side='top',fill='both',exp=1,padx=0,pady=0,ipadx=0,ipady=0)
 

	
 

	
 
        def color_buttons(x, y, z, song=song, b=b):
 
            name = player.filename_var.get()
 
            if name == sf[prefixlen:]:
 
            if name == graph.value(song, L9['showPath']):
 
                b['bg'] = 'grey50'
 
            else:
 
                b['bg'] = 'black'
 
        player.filename_var.trace("w", color_buttons)
 
    return songlist
 
 
 

	
 
class TimeScale(tk.Scale):
 
    def __init__(self,master,player):
 
        tk.Scale.__init__(self, master, orient="horiz",
 
                          from_=-4,to_=100,
 
                          sliderlen=20,width=20,
 
@@ -451,41 +453,47 @@ class ControlButtons(tk.Frame):
 
            if name == 'pause' and state not in ('stop', 'pause'):
 
                name = 'play'
 

	
 
            if state == name: # name gets changed sometimes for 'pause' -- see right above
 
                button['bg'] = colors.get(name, 'black')
 
            else:
 
                button['bg'] = 'black'
 
        self.goButton.updateStatus()
 

	
 
############################
 

	
 

	
 
if len(songfiles)<1:
 
    songfiles = [f for f in os.listdir(showconfig.musicDir())
 
                 if f.endswith('wav')]
 
    songfiles.sort()
 

	
 
def main():
 
    global graph
 
    parser = OptionParser()
 
    graph = showconfig.getGraph()
 
    (options, songfiles) = parser.parse_args()
 

	
 
songlist = buildsonglist(root,songfiles,player)
 
songlist.pack(fill='both',exp=1)
 
    if len(songfiles)<1:
 
        graph = showconfig.getGraph()
 
        playList = graph.value(L9['show/dance2007'], L9['playList'])
 
        songs = list(graph.items(playList))
 
    else:
 
        raise NotImplementedError("don't know how to make rdf song nodes from cmdline song paths")
 

	
 

	
 
    root=tk.Tk()
 
    root.wm_title("ascoltami")
 
    toplevelat("ascoltami", root)
 
    root.config(bg="black")
 
    player=Player()
 

	
 
    songlist = buildsonglist(root, songfiles, player)
 
    songlist = buildsonglist(root, graph, songs, player)
 
    songlist.pack(fill='both',exp=1)
 

	
 
    seeker = Seeker(root, player)
 
    
 
    goRow = tk.Frame(root)
 
    tk.Label(goRow, text="Go button action",
 
             font='arial 9', **appstyle).pack(side='left', fill='both')
 
    goStatus = tk.Label(goRow, font='arial 12 bold', **appstyle)
 
    goStatus.config(bg='#800000', fg='white', relief='ridge')
 
    goStatus.pack(side='left', expand=True, fill='x')
 

	
 
    go = GoButton(player, goStatus, songfiles)
 
    
bin/subcomposer
Show inline comments
 
#!/usr/bin/python
 

	
 
from __future__ import division, nested_scopes
 
import sys,os,time,atexit
 
import Tkinter as tk
 
from dispatch import dispatcher
 
try:
 
    from dispatch import dispatcher
 
except ImportError:
 
    import louie as dispatcher
 

	
 
import run_local
 
from light9.dmxchanedit import Levelbox
 
from light9 import dmxclient, Patch, Submaster
 

	
 
class Subcomposer(tk.Frame):
 
    def __init__(self, master, levelboxopts=None, dmxdummy=0, numchannels=68,
 
        use_persistentlevels=0):
 
        tk.Frame.__init__(self, master, bg='black')
 
        self.dmxdummy = dmxdummy
 
        self.numchannels = numchannels
 

	
 
        self.levels = [0]*68 # levels should never get overwritten, just edited
 
        self.levels = [0]*512 # levels should never get overwritten, just edited
 

	
 
        self.levelbox = Levelbox(self)
 
        self.levelbox = Levelbox(self, num_channels=numchannels)
 
        self.levelbox.pack(side='top')
 
        # the dmx levels we edit and output, range is 0..1 (dmx chan 1 is
 
        # the 0 element)
 
        self.levelbox.setlevels(self.levels)
 

	
 
        self.savebox = Savebox(self, self.levels, cmd=self.savenewsub)
 
        self.savebox.pack(side='top')
 

	
 
        self.loadbox = Savebox(self, self.levels, verb="Load", cmd=self.loadsub)
 
        self.loadbox.pack(side='top')
 

	
 
        def alltozero():
 
@@ -125,25 +128,25 @@ def open_sub_editing_window(subname, use
 
    sc.considersendupdate(use_after_loop=10)
 
    if use_mainloop:
 
        tk.mainloop()
 
    
 
#############################
 

	
 
if __name__ == "__main__":
 
    root=tk.Tk()
 
    root.config(bg='black')
 
    root.wm_title("subcomposer")
 
    root.tk_setPalette("#004633")
 

	
 
    sc = Subcomposer(root, dmxdummy=0)
 
    sc = Subcomposer(root, dmxdummy=0, numchannels=276)
 
    sc.pack()
 

	
 
    tk.Label(root,text="Bindings: B1 adjust level; B3 instant bump",
 
             font="Helvetica -12 italic",anchor='w').pack(side='top',fill='x')
 
    
 

	
 
    while 1:
 
        if 0:
 
            for i in range(20): # don't let Tk take all the time
 
                tk._tkinter.dooneevent()
 
            print "loop"
 
        else:
light9/Patch.py
Show inline comments
 
import os
 
from rdflib import RDF
 
from light9.namespaces import L9
 
from light9 import showconfig
 

	
 

	
 
def resolve_name(channelname):
 
    "Ensure that we're talking about the primary name of the light."
 
    return get_channel_name(get_dmx_channel(channelname))
 

	
 
def get_all_channels():
 
    """returns primary names for all channels (sorted)"""
 
    prinames = reverse_patch.values()[:]
 
    prinames.sort()
 
    return prinames
 

	
 
def get_dmx_channel(name):
 
    if name in patch:
 
@@ -20,31 +23,28 @@ def get_dmx_channel(name):
 
        return i
 
    except ValueError:
 
        raise ValueError("Invalid channel name: %s" % name)
 

	
 
def get_channel_name(dmxnum):
 
    try:
 
        return reverse_patch[dmxnum]
 
    except KeyError:
 
        return str(dmxnum)
 

	
 
def reload_data():
 
    global patch, reverse_patch
 

	
 
    loc = {}
 
    execfile(showconfig.patchData(), loc)
 
    
 
    loadedpatch = loc['patch']
 
    patch = {}
 
    reverse_patch = {}
 
    for k, v in loadedpatch.items():
 
        if type(k) is tuple:
 
            for name in k:
 
                patch[name] = v
 
            reverse_patch[v] = k[0]
 
        else:
 
            patch[k] = v
 
            reverse_patch[v] = k
 

	
 
    graph = showconfig.getGraph()
 

	
 
    for chan in graph.subjects(RDF.type, L9['Channel']):
 
        name = graph.label(chan)
 
        # int() shouldn't be required, but some code in subcomposer
 
        # ignores channel numbers if they're not int
 
        addr = int(graph.value(chan, L9['dmxAddress']))
 
        patch[name] = addr
 
        reverse_patch[addr] = name
 

	
 
# importing patch will load initial data
 
reload_data()
 

	
light9/Submaster.py
Show inline comments
 
from __future__ import division
 
import os
 
from rdflib.Graph import Graph
 
from rdflib import RDFS, Literal, BNode
 
from light9.namespaces import L9, XSD
 
from light9.TLUtility import dict_scale, dict_max
 
from light9 import Patch, showconfig
 
try:
 
    import dispatch.dispatcher as dispatcher
 
except ImportError:
 
    from louie import dispatcher
 

	
 
class Submaster:
 
    "Contain a dictionary of levels, but you didn't need to know that"
 
    def __init__(self, name, leveldict=None, temporary=0):
 
        self.name = name
 
        self.temporary = temporary
 
@@ -16,52 +19,55 @@ class Submaster:
 
            self.levels = leveldict
 
        else:
 
            self.levels = {}
 
            self.reload(quiet=True)
 
        if not self.temporary:
 
            dispatcher.connect(self.reload, 'reload all subs')
 
    def reload(self, quiet=False):
 
        if self.temporary:
 
            return
 
        try:
 
            oldlevels = self.levels.copy()
 
            self.levels.clear()
 
            subfile = file(showconfig.subFile(self.name))
 
            for line in subfile.readlines():
 
                if not line.strip(): # if line is only whitespace
 
                    continue # "did i say newspace?"
 
            patchGraph = showconfig.getGraph()
 
            graph = Graph()
 
            graph.parse(showconfig.subFile(self.name), format="nt")
 
            subUri = L9['sub/%s' % self.name]
 
            for lev in graph.objects(subUri, L9['lightLevel']):
 
                chan = graph.value(lev, L9['channel'])
 
                val = graph.value(lev, L9['level'])
 
                name = patchGraph.label(chan)
 
                self.levels[name] = float(val)
 

	
 
                try:
 
                    name, val = line.split(':')
 
                    name = name.strip()
 
                    self.levels[name] = float(val)
 
                except ValueError:
 
                    print "(%s) Error with this line: %s" % (self.name, 
 
                        line[:-1])
 

	
 
                if (not quiet) and (oldlevels != self.levels):
 
                    print "sub %s changed" % self.name
 
            if (not quiet) and (oldlevels != self.levels):
 
                print "sub %s changed" % self.name
 
        except IOError:
 
            print "Can't read file for sub: %s" % self.name
 
    def save(self):
 
        if self.temporary:
 
            print "not saving temporary sub named",self.name
 
            return
 

	
 
        subfile = file(showconfig.subFile(self.name), 'w')
 
        names = self.levels.keys()
 
        names.sort()
 
        for name in names:
 
            val = self.levels[name]
 
            subfile.write("%s : %s\n" % (name, val))
 
        graph = Graph()
 
        subUri = L9['sub/%s' % self.name]
 
        graph.add((subUri, RDFS.label, Literal(self.name)))
 
        for chan in self.levels.keys():
 
            lev = BNode()
 
            graph.add((subUri, L9['lightLevel'], lev))
 
            graph.add((lev, L9['channel'], L9['dmx/%s' % chan]))
 
            graph.add((lev, L9['level'],
 
                       Literal(self.levels[chan], datatype=XSD['decimal'])))
 

	
 
        graph.serialize(showconfig.subFile(self.name), format="nt")
 

	
 
    def set_level(self, channelname, level, save=1):
 
        self.levels[Patch.resolve_name(channelname)] = level
 
        if save:
 
            self.save()
 
    def set_all_levels(self, leveldict):
 
        self.levels.clear()
 
        for k, v in leveldict.items():
 
            self.set_level(k, v, save=0)
 
        self.save()
 
    def get_levels(self):
 
        return self.levels
 
    def no_nonzero(self):
light9/dmxchanedit.py
Show inline comments
 
"""
 

	
 
widget to show all dmx channel levels and allow editing. levels might
 
not actually match what dmxserver is outputting.
 

	
 
"""
 
from __future__ import nested_scopes,division
 
import Tkinter as tk
 
import time
 
from light9 import Patch
 
from light9.uihelpers import make_frame, colorlabel, eventtoparent
 
from dispatch import dispatcher
 
try:
 
     from dispatch import dispatcher
 
except ImportError:
 
     import louie as dispatcher
 

	
 
# this font makes each label take 16ms to create, so startup is slow.
 
# with default font, each labl takes about .5ms to create.
 
stdfont = ('Arial', 12)
 
import tkFont
 
# see replacement stdfont below
 

	
 

	
 
def gradient(lev, low=(80,80,180), high=(255,55,50)):
 
     out = [int(l+lev*(h-l)) for h,l in zip(high,low)]
 
     col="#%02X%02X%02X" % tuple(out)
 
     return col
 
@@ -28,31 +31,33 @@ class Onelevel(tk.Frame):
 
    def __init__(self, parent, channelnum):
 
        """channelnum is 1..68, like the real dmx"""
 
        tk.Frame.__init__(self,parent)
 

	
 
        self.channelnum=channelnum
 
        self.currentlevel=0 # the level we're displaying, 0..1
 
        
 
        # 3 widgets, left-to-right:
 

	
 
        # channel number -- will turn yellow when being altered
 
        self.num_lab = tk.Label(self, text=str(channelnum),
 
                                width=3, bg='grey40', 
 
                                fg='white', font=stdfont,
 
                                fg='white',
 
                                font=stdfont,
 
                                padx=0, pady=0, bd=0, height=1)
 
        self.num_lab.pack(side='left')
 

	
 
        # text description of channel
 
        self.desc_lab=tk.Label(self, text=Patch.get_channel_name(channelnum),
 
                               width=14, font=stdfont,
 
                               width=14,
 
                               font=stdfont,
 
                               anchor='w',
 
                               padx=0, pady=0, bd=0, 
 
                 height=1, bg='black', fg='white')
 
        self.desc_lab.pack(side='left')
 

	
 
        # current level of channel, shows intensity with color
 
        self.level_lab = tk.Label(self, width=3, bg='lightBlue',
 
                                  font=stdfont,
 
                                  anchor='e', 
 
                                  padx=1, pady=0, bd=0, height=1)
 
        self.level_lab.pack(side='left')
 

	
 
@@ -114,28 +119,29 @@ class Onelevel(tk.Frame):
 
        program needs to hear about it. then the main program will
 
        call setlevel()"""
 

	
 
        dispatcher.send("levelchanged",channel=self.channelnum,newlevel=newlev)
 
    
 
class Levelbox(tk.Frame):
 
    def __init__(self, parent, num_channels=68):
 
        tk.Frame.__init__(self,parent)
 
        global stdfont
 
        stdfont = tkFont.Font(size=9)
 
        self.levels = [] # Onelevel objects
 

	
 
        frames = (make_frame(self), make_frame(self))
 
        rows = 48
 
        frames = [make_frame(self) for x in range((num_channels // rows) + 1)]
 

	
 
        for channel in range(1, num_channels+1):
 

	
 
            print "setup chan", channel
 
            # frame for this channel
 
            f = Onelevel(frames[channel > (num_channels/2)],channel)
 
            f = Onelevel(frames[channel // rows],channel)
 

	
 
            self.levels.append(f)
 
            f.pack(side='top')
 
        #dispatcher.connect(setalevel,"setlevel")
 

	
 
    def setlevels(self,newlevels):
 
        """sets levels to the new list of dmx levels (0..1). list can
 
        be any length"""
 
        for l,newlev in zip(self.levels,newlevels):
 
            l.setlevel(newlev)
light9/namespaces.py
Show inline comments
 
from rdflib import Namespace
 

	
 
L9 = Namespace("http://light9.bigasterisk.com/")
 
MUS = Namespace("http://light9.bigasterisk.com/music/")
 
XSD = Namespace("http://www.w3.org/2001/XMLSchema#")
light9/networking.py
Show inline comments
 
import os
 
from ConfigParser import SafeConfigParser
 
# my intent was to pull these from a file in the LIGHT9_SHOW/ directory
 

	
 

	
 
def dmxServerUrl():
 
    #host = os.getenv('DMXHOST', 'localhost')
 
    #url = "http://%s:8030" % host
 
    return "http://spot:%s" % dmxServerPort()
 
    return "http://localhost:%s" % dmxServerPort()
 

	
 
def dmxServerPort():
 
    return 8030
 
    
 
def musicUrl():
 
    return "http://score:%s" % musicPort()
 

	
 
def musicPort():
 
    return 8040
 

	
 
def mpdServer():
 
    """servername, port"""
light9/showconfig.py
Show inline comments
 
@@ -6,27 +6,24 @@ from namespaces import MUS, L9
 
def getGraph():
 
    graph = Graph()
 
    graph.parse(path.join(root(), 'config.n3'), format='n3')
 
    return graph
 

	
 
def root():
 
    r = getenv("LIGHT9_SHOW")
 
    if r is None:
 
        raise OSError(
 
            "LIGHT9_SHOW env variable has not been set to the show root")
 
    return r
 

	
 
def musicDir():
 
    return path.join(root(),"music_local")
 

	
 
def songInMpd(song):
 

	
 
    """mpd only works off its own musicroot, which for me is
 
    /my/music. song is a file in musicDir; this function returns a
 
    version starting with the mpd path, but minus the mpd root itself.
 
    the mpc ~/.mpdconf
 

	
 
    changed root to /home/drewp/projects/light9/show/dance2005 for now
 
    """
 

	
 
    assert isinstance(song, URIRef), "songInMpd now takes URIRefs"
 

	
 
@@ -58,20 +55,17 @@ def curvesDir():
 
def songFilename(song):
 
    return path.join(musicDir(),"%s.wav" % song)
 

	
 
def subtermsForSong(song):
 
    return path.join(root(),"subterms",song)
 

	
 
def subFile(subname):
 
    return path.join(root(),"subs",subname)
 

	
 
def subsDir():
 
    return path.join(root(),'subs')
 

	
 
def patchData():
 
    return path.join(root(),"patchdata.py")
 

	
 
def prePostSong():
 
    graph = getGraph()
 
    return [graph.value(MUS['preSong'], L9['showPath']),
 
            graph.value(MUS['postSong'], L9['showPath'])]
 

	
0 comments (0 inline, 0 general)