From: g0dil Date: Mon, 18 Jun 2007 10:23:27 +0000 (+0000) Subject: Add new project 'AudioControl' X-Git-Url: http://g0dil.de/git?a=commitdiff_plain;h=8aab1c96c67b95d25fb175495894c38f82143cc9;p=audiocontrol.git Add new project 'AudioControl' --- 8aab1c96c67b95d25fb175495894c38f82143cc9 diff --git a/Actions.py b/Actions.py new file mode 100644 index 0000000..3b29f0a --- /dev/null +++ b/Actions.py @@ -0,0 +1,91 @@ +import Bindings, Node + +class Action(Node.Node): + + def __init__(self, name): + Node.Node.__init__(self) + self.name = name + + def __call__(self, binding): + raise NotImplementedError() + + def bindings(self): + return [ binding + for binding in self.owners + if isinstance(binding, Bindings.Binding) ] + + +class _ActionRegistry(object): + + def __init__(self): + self._registry = {} + + def register(self, action): + self._registry[action.name] = action + + def __getitem__(self, key): + return self._registry[key] + +Actions = _ActionRegistry() + + +class Nop(Action): + + def __init__(self, name='nop'): + Action.__init__(self, name) + + def __call__(self, binding): + pass + + +class Macro(Action): + + _actions = Node.NodeList() + + def __init__(self, name, actions): + Action.__init__(self, name) + for i in range(len(actions)): + if type(actions[i]) is str: + actions[i] = Actions[actions[i]] + self._actions = actions + + def __call__(self, binding): + for action in self._actions: + action(binding) + +class Command(Action): + + def __init__(self, name, callable): + Action.__init__(self, name) + self._callable = callable + + def __call__(self, binding): + self._callable(binding) + + +class ChangeBindingsAbsolute(Action): + + def __init__(self, name, mapIndex, keymaps): + Action.__init__(self, name) + self._mapIndex = mapIndex + self._keymaps = keymaps + + def __call__(self, binding): + binding.keymap().keylist().replaceFromIndex(self._mapIndex, self._keymaps) + + +class ChangeBindingsRelative(Action): + + def __init__(self, name, mapIndex, keymaps): + Action.__init__(self, name) + self._mapIndex = mapIndex + self._keymaps = keymaps + + def __call__(self, binding): + keymap = binding.keymap() + keymap.keylist().replaceFromIndex(keymap.index()+self._mapIndex, self._keymaps) + +def action(func): + action = Command(func.__name__, func) + Actions.register(action) + return func diff --git a/Bindings.py b/Bindings.py new file mode 100644 index 0000000..aaac851 --- /dev/null +++ b/Bindings.py @@ -0,0 +1,118 @@ +"""The Bindings module manages an infrastructure for key bindings.""" + +# Event = (context, sequence ...) wobei alle elsts strings sind + +# gebuynbden wird ein Action object. Jede Action hat einen namen unter +# der sie registriert wird. die funktionen zum definieren eines +# keymnap und zum binden von keys nehmen diesen namen als argument => +# alle Aktionen m"ussen vorher registriert werden + +# KeyStack => KeyMap => Binding => Action + +# Das Binding Objekt enthaelt das Event unter welchem die Action +# gebunden ist. Jedes Binding aht auch einen label zum anzeigen des +# bindings + +import Node, Actions, Events, Logger + +class Binding(Node.Node): + + action = Node.NodeReference() + + def __init__(self, event, label, action): + Node.Node.__init__(self) + self.event = event + self.label = label + self.action = action + + def keymap(self): + return self.owner + + def execute(self): + Logger.log('binding', 'execute: %s' % self.action.name) + self.action(self) + + +class KeyMap(Node.Node): + + _map = Node.NodeMap() + + def __init__(self, name=None): + Node.Node.__init__(self) + self._name = name + + def add(self, binding): + if self.owner : raise RuntimeError, 'changing an active KeyMap not supported' + self._map[binding.event] = binding + + def unbind(self, event): + if self.owner : raise RuntimeError, 'changing an active KeyMap not supported' + del self._map[event] + + def name(self): + return self._name + + def index(self): + if self.owner : return self.owner.indexOf(self) + else : return None + + def keylist(self): + return self.owner + + def lookup(self, event): + return self._map.get(event,None) + + def bindings(self): + return self._map + + +class KeyList(Node.Node): + + _list = Node.NodeList() + + def __init__(self): + Node.Node.__init__(self) + self._callbacks = [] + + def indexOf(self, map): + return self._list.index(map) + + def append(self, map): + self._list.append(map) + self._callCallbacks() + + def replaceFromIndex(self, index, maps): + self._list[index:] = maps + self._callCallbacks() + + def lookup(self, event): + for i in range(len(self._list)-1,-1,-1): + binding = self._list[i].lookup(event) + if binding is not None : return binding + return None + + def bindings(self): + bindings = {} + for map in self._list: + bindings.update(map.bindings()) + return bindings + + def bindingsByContext(self): + bindings = {} + for map in self._list: + for binding in map.bindings().itervalues(): + bindings.setdefault(binding.event.context,{}) + bindings[binding.event.context][binding.event] = binding + return bindings + + def registerCallback(self, cb): + self._callbacks.append(cb) + + def unregisterCallback(self, cb): + self._callbacks.remove(cb) + + def _callCallbacks(self): + for cb in self._callbacks : cb(self) + + def path(self): + return ' / '.join([ map.name() for map in self._list if map.name() ]) diff --git a/Events.py b/Events.py new file mode 100644 index 0000000..4a51f79 --- /dev/null +++ b/Events.py @@ -0,0 +1,83 @@ +"""Event handling and generation. + +The Event module manages event generation. Events are created by +EventSource instances. Each event source is associated with a file +handle. This handle will be poll()'ed and the eventsource will be +called, as sonn as input is available.""" + +import select, Logger, Node + +class _Event(tuple): + + def __init__(self, elts): + tuple.__init__(self, elts) + + context = property(lambda self: self[0], None, None) + code = property(lambda self: self[1], None, None) + + +def Event(context, code): + return _Event((context,code)) + + +class EventSource(Node.Node): + + def __init__(self, fd, context): + Node.Node.__init__(self) + self._fd = fd + self._context = context + + def fd(self): + return self._fd + + def context(self): + return self._context + + def fileno(self): + return self._fd.fileno() + + def readEvents(self): + raise NotImplementedError + + +class Dispatcher(Node.Node): + + _sources = Node.NodeMap() + + def __init__(self, keylist): + Node.Node.__init__(self) + self._sources = {} + self._callbacks = [] + self._keylist = keylist + self._poller = select.poll() + + def registerSource(self, eventSource): + self._sources[eventSource.fileno()] = eventSource + self._poller.register(eventSource.fileno(),select.POLLIN) + + def unregisterSource(self, eventSource): + del self._sources[eventSource.fileno()] + self._poller.unregister(eventSource.fileno()) + + def registerCallback(self,cb): + self._callbacks.append(cb) + + def unregisterCallback(self,cb): + self._callbacks.remove(cb) + + def run(self): + while 1: + for cb in self._callbacks : cb() + try: + pollEvents = self._poller.poll() + except select.error, v: + if v[0]==4 : continue + else : raise + if not pollEvents : return + for fd, pollEvent in pollEvents: + if pollEvent != select.POLLIN : return + for event in self._sources[fd].readEvents(): + Logger.log('dispatcher', 'event: ' + str(event)) + binding = self._keylist.lookup(event) + if binding is not None: + binding.execute() diff --git a/Joyboard.py b/Joyboard.py new file mode 100644 index 0000000..2629257 --- /dev/null +++ b/Joyboard.py @@ -0,0 +1,100 @@ +import Views, Events, Logger +from Views import EventWidget +import time, os, struct + +class View(Views.WidgetView): + + def __init__(self, context, label, numeric_switches, alpha_switches, x, y, size=11): + size += 1 + Views.WidgetView.__init__(self, context, label, x, y,size*numeric_switches+3,7) + + split = numeric_switches // 2 + for i in range(split): + self.add( EventWidget(i,str(i),size*i+1,4,size) ) + for i in range(split,numeric_switches): + self.add( EventWidget(i,str(i),size*i+3,4,size) ) + split = max(0,alpha_switches-(numeric_switches-split)) + offset = size//2+(numeric_switches-alpha_switches-1)*size + for i in range(split): + self.add( EventWidget(i+numeric_switches, chr(ord('A')+i), size*i+1+offset,1,size) ) + for i in range(split, alpha_switches): + self.add( EventWidget(i+numeric_switches, chr(ord('A')+i), size*i+3+offset,1,size) ) + + +class JSEvent: + + STRUCT = "IhBB" + STRUCT_len = struct.calcsize(STRUCT) + + TYPE_BUTTON = 0x01 + TYPE_AXIS = 0x02 + TYPE_INIT = 0x80 + + def __init__(self, data): + self.time, self.value, self.type, self.number = struct.unpack(self.STRUCT,data) + + def __str__(self): + return "%s(time=%d, value=%d,type=%d (%s)), number=%d)" % ( + str(self.__class__),self.time, self.value, self.type, self.decode_type(), self.number) + + def decode_type(self): + return ('','INIT|')[(self.type & self.TYPE_INIT) and 1 or 0] \ + + ('','BUTTON','AXIS')[self.type & ~self.TYPE_INIT] + + def readFrom(klass,f): + return klass(os.read(f.fileno(),klass.STRUCT_len)) + readFrom=classmethod(readFrom) + + def readMultipleFrom(klass,f,maxevents=256): + data = os.read(f.fileno(),maxevents*klass.STRUCT_len) + rv = [] + while data: + rv.append(klass(data[:klass.STRUCT_len])) + data = data[klass.STRUCT_len:] + return rv + readMultipleFrom=classmethod(readMultipleFrom) + +class Source(Events.EventSource): + + def __init__(self, joydev, context, bits=None, mindelay=100): + Events.EventSource.__init__(self, file(joydev), context) + self._bits = bits + self._lastevent = 0 + self._mindelay = mindelay + + def readEvents(self): + n = 0 + lev = self._lastevent + if self._bits: + time.sleep(0.0005) + jsevents = JSEvent.readMultipleFrom(self.fd()) + for event in jsevents: + if event.type == JSEvent.TYPE_BUTTON and event.value == 1: + self._lastevent = event.time + if event.time - lev < self._mindelay : return [] + n = n | self._bits[event.number] + if n == 0 : return [] + n -= 1 + else: + event = JSEvent.readFrom(self.fd()) + if event.type == JSEvent.TYPE_BUTTON and event.value == 1: + self._lastevent = event.time + if event.time - lev < self._mindelay : return [] + n = event.number + else: + return [] + return [Events.Event(self.context(), n)] + +def register( viewmanager, + dispatcher, + context, + label, + numeric_switches, + alpha_switches, + x, + y, + size, + device, + bits = None ): + viewmanager.registerView( View(context, label, numeric_switches, alpha_switches, x, y, size) ) + dispatcher.registerSource( Source(device, context, bits) ) diff --git a/Keyboard.py b/Keyboard.py new file mode 100644 index 0000000..4c2a03b --- /dev/null +++ b/Keyboard.py @@ -0,0 +1,54 @@ +import Views, Events +import sys, curses, curses.ascii + +class Source(Events.EventSource): + + def __init__(self, context, win): + Events.EventSource.__init__(self, sys.stdin, context) + self._win = win + + def readEvents(self): + return [Events.Event(self.context(), self._win.getch())] + + +class View(Views.View): + + def __init__(self, context, label, x, y, dx, dy, size=9): + Views.View.__init__(self, context, label, x, y, dx, dy) + self._size = size + + def updateView(self, bindings): + self.win().clear() + self.drawFrame() + keys = bindings.keys() + keys.sort() + column = 2 + row = 2 + for key in keys: + keyname = curses.keyname(key.code) + if keyname.startswith('KEY_'): + keyname = '%s' % keyname[4:10].lower() + if keyname[-2:-1] == '^': + keyname = '%sC-%s' % (keyname[:-2],keyname[-1:].lower()) + if curses.ascii.isupper(ord(keyname[-1:])): + keyname = '%sS-%s' % (keyname[:-1],keyname[-1:].lower()) + self.win().addstr(row, column, '%-6s %s' + % (keyname, bindings[key].label[:self._size])) + row += 1 + if row >= self.dy-2: + row = 2 + column += self._size+10 + if column+self._size+7 >= self.dx : break + self.win().vline(1,column-2,curses.ACS_VLINE,self.dy-2) + self.win().nooutrefresh() + +def register( viewmanager, + dispatcher, + context, + label, + x, + y, + dx, + dy ): + viewmanager.registerView( View(context, label, x,y,dx,dy) ) + dispatcher.registerSource( Source(context, viewmanager.win()) ) diff --git a/Logger.py b/Logger.py new file mode 100644 index 0000000..ab98615 --- /dev/null +++ b/Logger.py @@ -0,0 +1,32 @@ +import textwrap, time + +logger = None + +def init(viewmanager, x, y, dx, dy): + global logger + logger = Logger(viewmanager.win(), x, y, dx, dy) + +class Logger(object): + + def __init__(self, win, x, y, dx, dy): + self._win = win.derwin(dy,dx,y,x) + self._win.border() + self._win.addstr(0,2,' Log ') + self._win.nooutrefresh() + self._textwin = self._win.derwin(dy-2,dx-3,1,2) + self._textwin.scrollok(1) + self._wrapper = textwrap.TextWrapper(width = dx-4, + subsequent_indent = ' '*4) + + def log(self, src, msg): + lines = self._wrapper.wrap( + '[%s] (%s) %s' % (time.strftime("%H:%M:%S",time.localtime()),src, msg)) + self._textwin.scroll(len(lines)) + for i in range(len(lines)): + self._textwin.addstr(self._textwin.getmaxyx()[0]-len(lines)+i,0, + lines[i]) + self._textwin.nooutrefresh() + +def log(src,msg): + if logger: + logger.log(src,msg) diff --git a/Looper.py b/Looper.py new file mode 100644 index 0000000..075e98b --- /dev/null +++ b/Looper.py @@ -0,0 +1,54 @@ +import socket, struct +import Actions, Logger + +def osc_string(s) : + n = (len(s)+1)%4 + if n : return s+'\0'*(5-n) + else : return s+'\0' + +def osc_command(url,*args): + tag="," + argstring="" + for arg in args : + if type(arg) is int: + tag += 'i' + argstring += struct.pack(">I",arg) + elif type(arg) is str: + tag += 's' + argstring += osc_string(arg) + else: + raise RunstimeError, "Invalid OSC argument" + return osc_string(url) + osc_string(tag) + argstring + +class OSCSender(object): + + def __init__(self, host, port): + self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self._target = (host,port) + + def command(self, path, *args): + if type(args[0]) in (tuple, list) : args = args[0] + Logger.log('osc','osc://%s:%s%s %s' + % (self._target[0], self._target[1], path, + ' '.join(map(repr,args)))) + self._sock.sendto(osc_command(path, *args),self._target) + + +class Controller(object) : + + def __init__(self, host, port): + self._sender = OSCSender(host,port) + + def hit(self, command, index=0): + self._sender.command("/sl/%d/hit" % index, str(command)) + + +class Command(Actions.Action): + + def __init__(self, name, controller, command): + Actions.Action.__init__(self, name) + self._controller = controller + self._command = command + + def __call__(self, binding): + self._controller.hit(self._command) diff --git a/Node.py b/Node.py new file mode 100644 index 0000000..f62907e --- /dev/null +++ b/Node.py @@ -0,0 +1,180 @@ +"""A Node is an object which autmatically manages it's owner(s). + +A Node should alweys be stored in attribute members declared as +NodeReference or as member of a NodeMap or NodeList.""" + + +import weakref + +class Node(object): + + def __init__(self): + self._owners = [] + + def _addOwner(self,owner): + self._owners.append(weakref.ref(owner,self._delRef)) + + def _delOwner(self,owner): + for i in range(len(self._owners)): + if self._owners[i]() is owner: + del self._owners[i] + return + + def _delRef(self,ref): + for i in range(len(self._owners)): + if self._owners[i] is ref: + del self._owners[i] + return + + def _owners(self): + return ( x() for x in self._owners ) + + def _owner(self): + if self._owners and self._owners[0]() is not None: + return self._owners[0]() + return None + + owners = property(_owners, None, None, None) + owner = property(_owner, None, None, None) + +id_index = 0 +def _make_id(): + global id_index + id_index += 1 + return '__node_elt_%d' % id_index + +def NodeReference(id=None): + if id is None : id = _make_id() + return property(lambda self, id=id: getattr(self,id,None), + lambda self, node, id=id: _NodeReference_set(self,node,id), + None, + None) + +def _NodeReference_set(self, node, id): + old = getattr(self,id,None) + if old is not None : old._delOwner(self) + setattr(self,id,node) + if node is not None : node._addOwner(self) + + +class _NodeMap(dict): + + def __init__(self, owner): + dict.__init__(self) + self._owner = weakref.ref(owner) + + def __setitem__(self, key, node): + try: v = self[key] + except KeyError: pass + else: v._delOwner(self._owner()) + dict.__setitem__(self, key, node) + node._addOwner(self._owner()) + + def __delitem__(self, key): + try: v = self[key] + except KeyError: pass + else: v._delOwner(self._owner()) + dict.__delitem__(self,key) + + def clear(self): + owner = self._owner() + for v in self.itervalues() : v._delOwner(owner) + dict.clear(self) + + def setdefault(self, key, node): + if not self.has_key(key) : self[key] = node + + def pop(self, key): + v = self[key] + del self[key] + return v + + def popitem(self): + try: k = self.iterkeys().next() + except StopIteration: raise KeyError, 'popitem(): dictionary is empty' + return (k, self.pop(k)) + + def update(self, *args, **kws): + for k,v in dict(*args,**kws).iteritems() : self[k] = v + +def NodeMap(id=None): + if id is None : id = _make_id() + return property(lambda self, id=id : _NodeMap_get(self, id), + lambda self, elts, id=id : _NodeMap_set(self, elts, id), + None) + +def _NodeMap_get(self, id): + if not hasattr(self, id) : setattr(self, id, _NodeMap(self)) + return getattr(self, id) + +def _NodeMap_set(self, elts, id): + if hasattr(self, id) : getattr(self, id).clear() + else : setattr(self, id, _NodeMap(self)) + getattr(self, id).update(elts) + + +class _NodeList(list): + + def __init__(self, owner): + list.__init__(self) + self._owner = weakref.ref(owner) + + def __setitem__(self, index, node): + if type(index) is not slice: + self[index] # throw IndexError if index invalid + index = slice(index,index+1,None) + node = [ node ] + owner = self._owner() + for i in range(*index.indices(len(self))) : self[i]._delOwner(owner) + try: + list.__setitem__(self, index, node) + except ValueError: + for i in range(*index.indices(len(self))) : self[i]._addOwner(owner) + raise + else: + for n in node : n._addOwner(owner) + + def __delitem__(self, index): + if type(index) is not slice: + self[index] # throw IndexError if index invalid + index = slice(index,index+1,None) + self.__setitem__(index,[]) + + def __setslice__(self, i, j, seq): + self[max(0, i):max(0, j):] = seq + + def __delslice__(self, i, j): + del self[max(0, i):max(0, j):] + + def append(self, node): + self[len(self):len(self)] = [node] + + def extend(self, nodes): + self[len(self):len(self)] = nodes + + def insert(self, index, node): + self[index:index] = [node] + + def pop(self, index=None): + if index is None : index = len(self)-1 + x = self[index] + del self[index] + return x + + def remove(self, node): + del self[self.index(node)] + +def NodeList(id=None): + if id is None : id = _make_id() + return property(lambda self, id=id : _NodeList_get(self, id), + lambda self, elts, id=id : _NodeList_set(self, elts, id), + None) + +def _NodeList_get(self, id): + if not hasattr(self, id) : setattr(self, id, _NodeList(self)) + return getattr(self, id) + +def _NodeList_set(self, elts, id): + if hasattr(self, id) : del getattr(self, id)[:] + else : setattr(self, id, _NodeList(self)) + getattr(self, id).extend(elts) diff --git a/Node.test.py b/Node.test.py new file mode 100644 index 0000000..b34674d --- /dev/null +++ b/Node.test.py @@ -0,0 +1,155 @@ +import Node +import unittest + +class Owner(object): + child = Node.NodeReference() + +class OwnerMap(object): + children = Node.NodeMap() + +class OwnerList(object): + children = Node.NodeList() + +class Child(Node.Node): + pass + +class UnitTest(unittest.TestCase): + + def testNode(self): + owner = Owner() + child1 = Child() + child2 = Child() + self.assertEqual(list(child1.owners),[]) + self.assertEqual(list(child2.owners),[]) + self.assertEqual(owner.child,None) + + owner.child = child1 + self.assertEqual(list(child1.owners), [owner]) + self.assertEqual(list(child2.owners), []) + self.assertEqual(owner.child, child1) + + owner.child = child2 + self.assertEqual(list(child1.owners), []) + self.assertEqual(list(child2.owners), [owner]) + self.assertEqual(owner.child, child2) + + owner.child = None + self.assertEqual(list(child1.owners),[]) + self.assertEqual(list(child2.owners),[]) + self.assertEqual(owner.child,None) + + owner.child = child1 + self.assertEqual(list(child1.owners), [owner]) + owner = None + self.assertEqual(list(child1.owners),[]) + self.assertEqual(owner, None) + + + def testNodeMap(self): + owner = OwnerMap() + child = Child() + owner.children['a'] = child + self.assertEqual(list(child.owners),[owner]) + del owner.children['a'] + self.assertEqual(list(child.owners),[]) + + child2 = Child() + owner.children.setdefault('a',child) + owner.children['b'] = child2 + self.assertEqual(list(child.owners),[owner]) + self.assertEqual(list(child2.owners),[owner]) + owner.children['a'] = owner.children['b'] + self.assertEqual(list(child.owners),[]) + self.assertEqual(list(child2.owners),[owner,owner]) + del owner.children['a'] + self.assertEqual(list(child2.owners),[owner]) + owner.children.clear() + self.assertEqual(list(child2.owners),[]) + + owner.children = dict(a=child, b=child2) + self.assertEqual(list(owner.children.pop('a').owners), []) + self.assertEqual(list(child2.owners), [owner]) + self.assertEqual(owner.children.popitem(),('b',child2)) + self.assertEqual(list(child2.owners), []) + + owner.children['a'] = child + owner.children.update(b=child2) + self.assertEqual(list(child2.owners), [owner]) + self.assertEqual(list(child.owners), [owner]) + owner.children.update(a=child2) + self.assertEqual(list(child.owners), []) + self.assertEqual(list(child2.owners),[owner,owner]) + + del owner + self.assertEqual(list(child2.owners), []) + + + def testNodeList(self): + owner = OwnerList() + child1 = Child() + child2 = Child() + + owner.children.append(child1) + owner.children.append(child2) + self.assertEqual(owner.children, [child1,child2]) + self.assertEqual(list(child1.owners),[owner]) + self.assertEqual(list(child2.owners),[owner]) + + owner.children.extend([child1,child2]) + self.assertEqual(owner.children, [child1,child2,child1,child2]) + self.assertEqual(list(child1.owners),[owner,owner]) + self.assertEqual(list(child2.owners),[owner,owner]) + + self.assertRaises(ValueError, self._testNodeList_1, owner) + self.assertEqual(owner.children, [child1,child2,child1,child2]) + self.assertEqual(list(child1.owners),[owner,owner]) + self.assertEqual(list(child2.owners),[owner,owner]) + + owner.children[1:2] = [] + self.assertEqual(list(child1.owners), [owner,owner]) + self.assertEqual(list(child2.owners), [owner]) + + owner.children[:] = [] + self.assertEqual(list(child1.owners), []) + self.assertEqual(list(child2.owners), []) + + owner.children[:] = [child1,child2] + self.assertEqual(list(child1.owners),[owner]) + self.assertEqual(list(child2.owners),[owner]) + + del owner.children[0] + self.assertEqual(list(child1.owners),[]) + self.assertEqual(list(child2.owners),[owner]) + + del owner.children[:] + self.assertEqual(list(child2.owners),[]) + + owner.children = [child1,child1] + owner.children.insert(1,child2) + self.assertEqual(owner.children,[child1,child2,child1]) + self.assertEqual(list(child1.owners), [owner,owner]) + self.assertEqual(list(child2.owners), [owner]) + + self.assertEqual(list(owner.children.pop().owners), [owner]) + self.assertEqual(owner.children.pop(), child2) + self.assertEqual(owner.children, [child1]) + + owner.children.append(child2) + self.assertEqual(list(owner.children.pop(0).owners), []) + self.assertEqual(owner.children, [child2]) + owner.children.append(child1) + owner.children.remove(child2) + self.assertEqual(list(child1.owners),[owner]) + self.assertEqual(list(child2.owners),[]) + self.assertEqual(owner.children, [child1]) + + del owner + self.assertEqual(list(child1.owners), []) + + + def _testNodeList_1(self, owner): + owner.children[::2] = [] + + +if __name__ == '__main__': + unittest.main() diff --git a/Process.py b/Process.py new file mode 100644 index 0000000..379a704 --- /dev/null +++ b/Process.py @@ -0,0 +1,30 @@ +import Events, Logger +import popen2, fcntl, os + +class Process(Events.EventSource): + + def __init__(self, command, context): + self._proc = popen2.Popen4(command) + fcntl.fcntl(self._proc.fromchild, fcntl.F_SETFL, os.O_NDELAY) + Events.EventSource.__init__(self, self._proc.fromchild, context) + self._buffer = "" + Logger.log(self.context(),"launched %s" % command) + + def readEvents(self): + data = (self._buffer + self._proc.fromchild.read()).split('\n') + self._buffer = data.pop(-1) + for line in data: + Logger.log(self.context(), line) + rv = self.poll() + if rv != -1 : self._term(rv) + return [] + + def terminate(self): + os.kill(self._proc.pid,signal.SIGTERM) + self._term(self._proc.wait()) + + def _term(self, rv): + if self._buffer : Logger.log(self.context(),self._buffer) + self._buffer = "" + Logger.log(self.context(), 'Terminated with exit code %s' % rv) + self.owner().unregisterSource(self) diff --git a/Views.py b/Views.py new file mode 100644 index 0000000..7f653ca --- /dev/null +++ b/Views.py @@ -0,0 +1,109 @@ +"""Manage binding views. + +The Views module manages the on-screen keymap representations. A View +is registered for a specific context. The view will be called with a +list of all active bindings whenever the bindings change.""" + +import Events, Node, curses + +class View(Node.Node): + def __init__(self, context, label, x, y, dx, dy): + Node.Node.__init__(self) + self._context = context + self._label = label + self._win = None + self.x, self.y, self.dx, self.dy = x, y, dx, dy + + def init(self): + self._win = self.owner.win().derwin(self.dy, self.dx, self.y, self.x) + self.drawFrame() + + def drawFrame(self, subtitle=None): + label = ' %s ' % self._label + if subtitle: + label = '%s- %s ' % (label, subtitle) + self._win.border() + self._win.addstr(0,2,label) + self._win.nooutrefresh() + + def win(self): + return self._win + + def updateView(self, bindings): + raise NotImplementedError + + +class WidgetView(View): + + _widgets = Node.NodeMap() + + def __init__(self, context, label, x, y, dx, dy): + View.__init__(self, context, label, x, y, dx, dy) + + def init(self): + View.init(self) + for widget in self._widgets.itervalues(): + widget.init() + + def add(self,widget): + if self.win() : raise RuntimeError, 'Cannot change active view' + self._widgets[widget.code] = widget + + def updateView(self, bindings): + if bindings: + self.drawFrame(bindings.itervalues().next().keymap().keylist().path()) + for code, widget in self._widgets.iteritems(): + event = Events.Event(self._context,code) + if bindings.has_key(event): + widget.updateView(bindings[event]) + else: + widget.updateView(None) + + +class EventWidget(Node.Node): + + def __init__(self, code, label, x, y, size=9): + Node.Node.__init__(self) + self.x, self.y, self.size = x, y, size + self._win = None + self.code = code + self.label = label + + def init(self): + self._win = self.owner.win().derwin(2, self.size, self.y, self.x) + self._win.hline(0,0,curses.ACS_HLINE,self.size-1) + self._win.addstr(0,(self.size-1-min(self.size-3,len(self.label)))/2, self.label[:self.size-3]) + self._win.nooutrefresh() + + def updateView(self, binding): + if binding: + self._win.addstr(1,0, binding.label[:self.size-1].center(self.size-1), curses.A_BOLD) + else: + self._win.addstr(1, 0, ' '*(self.size-1)) + self._win.nooutrefresh() + + +class ViewManager(object): + + _views = Node.NodeMap() + + def __init__(self, keylist, dispatcher, win): + self._win = win + keylist.registerCallback(self.updateViews) + dispatcher.registerCallback(self.beforeIdle) + + def win(self): + return self._win + + def registerView(self, view): + self._views[view._context] = view + view.init() + + def updateViews(self, keylist): + bindings = keylist.bindingsByContext() + for context, keys in bindings.iteritems(): + if self._views.has_key(context): + self._views[context].updateView(keys) + + def beforeIdle(self): + self._win.refresh() diff --git a/audiocontroller b/audiocontroller new file mode 100755 index 0000000..70e03ff --- /dev/null +++ b/audiocontroller @@ -0,0 +1,5 @@ +#!/usr/bin/python2.4 + +import main + +main.main() diff --git a/config.py b/config.py new file mode 100644 index 0000000..d4b5abf --- /dev/null +++ b/config.py @@ -0,0 +1,132 @@ +import Bindings, Actions, Views, Events, Logger +from Bindings import Binding +from Actions import Actions as Action, action +from Views import EventWidget +from Events import Event +import main +import Joyboard, Keyboard, Process +import Looper +import sys, curses +from curses.ascii import alt, ctrl + +def shift(letter) : return ord(chr(letter).upper()) +def key(letter) : return ord(letter.lower()) + +########################################################################### +# Setup views and controllers +# +# Display size: 77x17 + +Logger.init(main.viewmanager, 38, 0, 39, 10) + +Joyboard.register( + viewmanager = main.viewmanager, + dispatcher = main.dispatcher, + + context = 'jb0', + label = 'Foot Switch', + numeric_switches = 10, + alpha_switches = 5, + x = 2, + y = 10, + size = 6, + + device = '/dev/input/js0', + bits = { 1:1, 3:2, 2:4, 0:8 } +) + +Keyboard.register( + viewmanager = main.viewmanager, + dispatcher = main.dispatcher, + + context = 'kbd', + label = 'Key Bindings', + x = 0, + y = 0, + dx = 38, + dy = 10 +) + +jackd = Process.Process("jackd -d alsa -S -r 44100 -p 256",'jack') + +########################################################################### +# Global keymap and auxilary actions + +global_map = Bindings.KeyMap() + +@action +def quit(binding): + sys.exit(0) + +global_map.add( Binding( Event('jb0',0), '()', Actions.Nop() ) ) +global_map.add( Binding( Event('jb0',1), '()', Actions.Nop() ) ) +global_map.add( Binding( Event('jb0',10), '()', Actions.Nop() ) ) + +global_map.add( Binding( Event('kbd',key('q')), 'Quit', Action['quit'] ) ) + +Action.register( Actions.ChangeBindingsRelative( 'unset_this_map', 0, [] ) ) + +########################################################################### +# Looper mode + +looper_main_map = Bindings.KeyMap( 'Looper' ) +looper_aux_map = Bindings.KeyMap( 'Aux' ) + +Action.register( Actions.ChangeBindingsAbsolute('set_mode_looper', 1, [looper_main_map]) ) +Action.register( Actions.ChangeBindingsRelative('looper_set_aux_map', 1, [looper_aux_map]) ) + +looper = Looper.Controller('localhost',9951) + +Action.register( Looper.Command('looper_record', looper, 'record') ) +Action.register( Looper.Command('looper_overdub', looper, 'overdub') ) +Action.register( Looper.Command('looper_multiply', looper, 'multiply') ) +Action.register( Looper.Command('looper_mute', looper, 'mute') ) +Action.register( Looper.Command('looper_undo', looper, 'undo') ) +Action.register( Looper.Command('looper_redo', looper, 'redo') ) +Action.register( Looper.Command('looper_trigger', looper, 'trigger') ) +Action.register( Looper.Command('looper_insert', looper, 'insert') ) +Action.register( Looper.Command('looper_replace', looper, 'replace') ) +Action.register( Looper.Command('looper_substitute', looper, 'substitute') ) +Action.register( Looper.Command('looper_reverse', looper, 'reverse') ) +Action.register( Looper.Command('looper_oneshot', looper, 'oneshot') ) + +looper_main_map.add ( Binding( Event('jb0',2), 'Rec', Action['looper_record'] ) ) +looper_main_map.add ( Binding( Event('jb0',3), 'Over', Action['looper_overdub'] ) ) +looper_main_map.add ( Binding( Event('jb0',4), 'Mult', Action['looper_multiply'] ) ) +looper_main_map.add ( Binding( Event('jb0',5), 'Mute', Action['looper_mute'] ) ) +looper_main_map.add ( Binding( Event('jb0',6), 'Undo', Action['looper_undo'] ) ) +looper_main_map.add ( Binding( Event('jb0',7), 'Redo', Action['looper_redo'] ) ) +looper_main_map.add ( Binding( Event('jb0',8), 'Trig', Action['looper_trigger'] ) ) +looper_main_map.add ( Binding( Event('jb0',9), 'Aux', Action['looper_set_aux_map'] ) ) + +looper_aux_map.add ( Binding( Event('jb0',2), 'Ins', Action['looper_insert'] ) ) +looper_aux_map.add ( Binding( Event('jb0',3), 'Repl', Action['looper_replace'] ) ) +looper_aux_map.add ( Binding( Event('jb0',4), 'Subst', Action['looper_substitute'] ) ) +looper_aux_map.add ( Binding( Event('jb0',6), 'Rev', Action['looper_reverse'] ) ) +looper_aux_map.add ( Binding( Event('jb0',7), 'Once', Action['looper_oneshot'] ) ) +looper_aux_map.add ( Binding( Event('jb0',9), 'Main', Action['unset_this_map'] ) ) + +########################################################################### +# MegaPedal mode + +megapedal_main_map = Bindings.KeyMap( 'MegaPedal' ) + +Action.register( Actions.ChangeBindingsAbsolute('set_mode_megapedal', 1, [megapedal_main_map]) ) + +megapedal_main_map.add ( Binding( Event('jb0',11), 'Looper', Action['set_mode_looper'] ) ) +global_map.add ( Binding( Event('jb0',11), 'MegaPedal', Action['set_mode_megapedal'] ) ) + +########################################################################### +# RythmBox mode + +rythmbox_main_map = Bindings.KeyMap( 'RythmBox' ) + +Action.register( Actions.ChangeBindingsAbsolute('set_mode_rythmbox', 1, [rythmbox_main_map]) ) + +rythmbox_main_map.add ( Binding( Event('jb0',11), 'MegaPedal', Action['set_mode_megapedal'] ) ) +global_map.add ( Binding( Event('jb0',11), 'RythmBox', Action['set_mode_rythmbox'] ) ) + +########################################################################### + +main.keylist.append(global_map) +main.keylist.append(looper_main_map) diff --git a/main.py b/main.py new file mode 100644 index 0000000..caa6418 --- /dev/null +++ b/main.py @@ -0,0 +1,31 @@ +import Bindings, Events, Views, curses.wrapper + +keylist = None +dispatcher = None +viewmanager = None +stdscr = None + + +def run(scr): + + global keylist + global dispatcher + global viewmanager + global stdscr + + keylist = Bindings.KeyList() + dispatcher = Events.Dispatcher(keylist) + viewmanager = Views.ViewManager(keylist, dispatcher, scr) + stdscr = scr + + import config + + curses.raw() + stdscr.keypad(1) + try: curses.curs_set(0) + except: pass + dispatcher.run() + + +def main(): + curses.wrapper(run)