Audio/AudioControl: Commit loads of long uncommited changes
g0dil [Mon, 15 Oct 2007 08:28:25 +0000 (08:28 +0000)]
13 files changed:
.gitignore [new file with mode: 0644]
Bindings.py
Events.py
Joyboard.py
Keyboard.py
Logger.py
Looper.py
Mixer.py [new file with mode: 0644]
OSC.py [new file with mode: 0644]
config.py
main.py
start.sh [new file with mode: 0755]
winmove.c [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..9334190
--- /dev/null
@@ -0,0 +1,2 @@
+*.pyc
+winmove
index aaac851..b6cfd81 100644 (file)
@@ -81,6 +81,16 @@ class KeyList(Node.Node):
         self._list.append(map)
         self._callCallbacks()
 
+    def prepend(self, map):
+        self._list[:0] = [map]
+        self._callCallbacks()
+
+    def removeMap(self, map):
+        for i in (range(len(self._list))):
+            if self._list[i] is map:
+                self._list[i:i+1] = []
+                return
+
     def replaceFromIndex(self, index, maps):
         self._list[index:] = maps
         self._callCallbacks()
index 4a51f79..8a64f47 100644 (file)
--- a/Events.py
+++ b/Events.py
@@ -5,19 +5,35 @@ 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
+import select, Logger, Node, Actions
 
-class _Event(tuple):
+class Event(object):
 
-    def __init__(self, elts):
-        tuple.__init__(self, elts)
+    def __init__(self, context, code):
+        self.context = context
+        self.code = code
 
-    context = property(lambda self: self[0], None, None)
-    code = property(lambda self: self[1], None, None)
+    def __getitem__(self, index):
+        rerturn (self.context, self.code)[index]
 
+    def __cmp__(self, other):
+        return cmp(self.context, other.context) or cmp(self.code, other.code)
 
-def Event(context, code):
-    return _Event((context,code))
+    def __hash__(self):
+        return hash(self.context) ^ hash(self.code)
+
+    def __str__(self):
+        return repr((self.context, self.code))
+
+
+class ControlEvent(Event):
+
+    def __init__(self, context, code, value):
+        Event.__init__(self, context, code)
+        self.value = value
+
+    def __str__(self):
+        return repr((self.context, self.code, self.value))
 
 
 class EventSource(Node.Node):
@@ -50,6 +66,9 @@ class Dispatcher(Node.Node):
         self._callbacks = []
         self._keylist = keylist
         self._poller = select.poll()
+        self._event = None
+        self._interval = None
+        self._idleCallback = None
 
     def registerSource(self, eventSource):
         self._sources[eventSource.fileno()] = eventSource
@@ -65,19 +84,46 @@ class Dispatcher(Node.Node):
     def unregisterCallback(self,cb):
         self._callbacks.remove(cb)
 
+    def currentEvent(self):
+        return self._event
+
+    def setIdleCallback(self, cb, interval):
+        self._interval = interval
+        self._idleCallback = cb
+
+    def unsetIdleCallback(self):
+        self._interval = None
+        self._idleCallback = None
+
+    def emit(self, event):
+        binding = self._keylist.lookup(event)
+        if binding is not None:
+            self._event = event
+            binding.execute()
+            self._event = None
+            
     def run(self):
         while 1:
             for cb in self._callbacks : cb()
             try:
-                pollEvents = self._poller.poll()
+                pollEvents = self._poller.poll(self._interval)
             except select.error, v:
                 if v[0]==4 : continue
                 else       : raise
-            if not pollEvents : return
+            if not pollEvents and self._idleCallback:
+                self._idleCallback()
             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()
+                    self.emit(event)
+
+class EmitEvent(Actions.Action):
+
+    def __init__(self, name, dispatcher, event):
+        Actions.Action.__init__(self, name)
+        self._dispatcher = dispatcher
+        self._event = event
+
+    def __call__(self, binding):
+        self._dispatcher.emit(self._event)
index 2629257..be8af89 100644 (file)
@@ -1,24 +1,28 @@
-import Views, Events, Logger
+import Views, Events, Logger, Bindings, Actions
 from Views import EventWidget
-import time, os, struct
+import time, os, struct, curses
 
 class View(Views.WidgetView):
 
-    def __init__(self, context, label, numeric_switches, alpha_switches, x, y, size=11):
+    def __init__(self, context, label, numeric_switches, alpha_switches, x, y, dx=0, size=11):
         size += 1
-        Views.WidgetView.__init__(self, context, label, x, y,size*numeric_switches+3,7)
+        dx = max(dx,size*numeric_switches+3)
+        delta = dx - size*numeric_switches - 3
+        shift = delta / 2
+        delta = dx - size*numeric_switches - 2*shift
+        Views.WidgetView.__init__(self, context, label, x, y, dx, 7)
 
         split = numeric_switches // 2
         for i in range(split):
-            self.add( EventWidget(i,str(i),size*i+1,4,size) )
+            self.add( EventWidget(i,str(i+1)[-1:],size*i+1+shift,4,size) )
         for i in range(split,numeric_switches):
-            self.add( EventWidget(i,str(i),size*i+3,4,size) )
+            self.add( EventWidget(i,str(i+1)[-1:],size*i+shift+delta,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) )
+            self.add( EventWidget(i+numeric_switches, chr(ord('A')+i), size*i+1+offset+shift,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) )
+            self.add( EventWidget(i+numeric_switches, chr(ord('A')+i), size*i+delta+offset+shift,1,size) )
 
 
 class JSEvent:
@@ -54,6 +58,7 @@ class JSEvent:
         return rv
     readMultipleFrom=classmethod(readMultipleFrom)
 
+
 class Source(Events.EventSource):
 
     def __init__(self, joydev, context, bits=None, mindelay=100):
@@ -61,6 +66,7 @@ class Source(Events.EventSource):
         self._bits = bits
         self._lastevent = 0
         self._mindelay = mindelay
+        self._controllers = {}
 
     def readEvents(self):
         n = 0
@@ -69,6 +75,8 @@ class Source(Events.EventSource):
             time.sleep(0.0005)
             jsevents = JSEvent.readMultipleFrom(self.fd())
             for event in jsevents:
+                if event.type == JSEvent.TYPE_AXIS:
+                    return self._controller(event.number, event.value)
                 if event.type == JSEvent.TYPE_BUTTON and event.value == 1:
                     self._lastevent = event.time
                     if event.time - lev < self._mindelay : return []
@@ -77,6 +85,8 @@ class Source(Events.EventSource):
             n -= 1
         else:
             event = JSEvent.readFrom(self.fd())
+            if event.type == JSEvent.TYPE_AXIS:
+                return self._controller(event.number, event.value)
             if event.type == JSEvent.TYPE_BUTTON and event.value == 1:
                 self._lastevent = event.time
                 if event.time - lev < self._mindelay : return []
@@ -84,7 +94,177 @@ class Source(Events.EventSource):
             else:
                 return []
         return [Events.Event(self.context(), n)]
+
+    def registerController(self, controller, low, high):
+        self._controllers[controller] = { 'low': low, 'high': high }
+
+    def _controller(self, number, value):
+        controller = self._controllers.get(number)
+        if controller:
+            value = (value - controller['low']) * 1000 / (controller['high']-controller['low'])
+            value = max(min(value,1000),0)
+            return [Events.ControlEvent(self.context(), 'p%d' % number, value)]
+        return []
+
+
+class Controller(Views.View):
+
+    def __init__(self, context, name, x, y, dx, dy, keylist, dispatcher,
+                 controller, source, low, high):
+        Views.View.__init__(self, context, name, x, y, dx, dy)
+        self._valueEvent = None
+        self._setCommand = None
+        self._valueCommand = None
+        self._parameter = None
+        self._keylist = keylist
+        self._keymap = Bindings.KeyMap()
+        self._keymap.add( Bindings.Binding(Events.Event(source.context(), 'p%d' % controller), '',
+                                           Actions.Command('controllerChanged',
+                                                           self._controllerChanged)) )
+        self._keylist.prepend(self._keymap)
+        source.registerController(controller, low, high)
+        self._dispatcher = dispatcher
+        self._min = None
+        self._max = None
+        self._stops = None
+        self._value = None
+        self._controlValue = None
+
+    def updateView(self, bindings):
+        pass
+
+    def init(self):
+        Views.View.init(self)
+        self._redraw()
+
+    def assign(self, parameter,  setCommand, valueCommand, valueEvent, min, max, stops=[]):
+        self._keylist.removeMap(self._keymap)
+        if self._valueEvent is not None:
+            self._keymap.unbind(self._valueEvent)
+        self._parameter = parameter
+        self._valueEvent = valueEvent
+        self._setCommand = setCommand
+        self._valueCommand = valueCommand
+        self._min = min
+        self._max = max
+        self._stops = stops
+        self._keymap.add( Bindings.Binding( self._valueEvent, '',
+                                            Actions.Command('updateValue',
+                                                            self._updateValue)) )
+        self._keylist.prepend(self._keymap)
+        self._value = None
+        self._valueCommand()
+        self._redraw()
+
+    def _updateValue(self, binding):
+        event = self._dispatcher.currentEvent()
+        self._value = event.value
+        self._redrawValue()
+
+    def _controllerChanged(self, binding):
+        event = self._dispatcher.currentEvent()
+        Logger.log('ctl',"value = %d" % event.value)
+        self._controlValue = event.value
+        if self._controlValue >= 999 or self._controlValue <= 1:
+            self._dispatcher.setIdleCallback(self._changeValue,50)
+        else:
+            self._dispatcher.unsetIdleCallback()
+        self._redrawController()
+
+    def _changeValue(self):
+        if self._value is None: return
+        if self._controlValue >= 999:
+            self.stepValue(+1)
+        elif self._controlValue <= 1:
+            self.stepValue(-1)
+
+    def stepValue(self, direction):
+        if direction > 0:
+            newValue = self._value + (self._max - self._min) / (3000/50)
+            crossed = [ x for x in self._stops if x > self._value*1.0001 and x <= newValue ]
+        elif direction < 0:
+            newValue = self._value - (self._max - self._min) / (3000/50)
+            crossed = [ x for x in self._stops if x < self._value/1.0001 and x >= newValue ]
+        if newValue >= self._max:
+            crossed = [ self._max ]
+        elif newValue <= self._min:
+            crossed = [ self._min ]
+        if crossed:
+            newValue = crossed[0]
+            self._dispatcher.unsetIdleCallback()
+            self._setCommand(newValue)
+            # Hmm ... why does value_command not work sometimes ??
+            self._value = newValue
+            self._redrawValue()
+        else:
+            self._setCommand(newValue)
+            self._valueCommand()
+
+    def _redraw(self):
+        height, width = self.win().getmaxyx()
+        if self._parameter is not None:
+            self.win().addstr(1,2,self._parameter[:width-4].ljust(width-4), curses.A_BOLD)
+        self._redrawValue(False)
+        self._redrawController()
+
+    def _flt(self, value, width):
+        return ('%.3f' % value)[:width].ljust(width)
+
+    def _redrawValue(self, refresh=True):
+        height, width = self.win().getmaxyx()
+        pos = None
+        if self._value is not None:
+            pos = height - 3 - int( (self._value - self._min) * (height-6)
+                                    / (self._max - self._min) + .5 )
+        if self._max is not None:
+            self.win().addstr(2,2, self._flt(self._max,width-7))
+        if pos is not None and pos == 3:
+            self.win().addstr(pos, 5, self._flt(self._value,width-7), curses.A_BOLD)
+        else:
+            self.win().addstr(3, 5, "".ljust(width-7))
+        for row in range(4,height-3):
+            if pos is not None and row == pos:
+                self.win().addch(pos,3,curses.ACS_PLUS)
+                self.win().addstr(pos, 5, self._flt(self._value,width-7), curses.A_BOLD)
+            else:
+                self.win().addch(row,3,curses.ACS_VLINE)
+                self.win().addstr(row, 5, "".ljust(width-7))
+        if pos is not None and pos == height-3:
+            self.win().addstr(pos, 5, self._flt(self._value,width-7), curses.A_BOLD)
+        else:
+            self.win().addstr(height-3, 5, "".ljust(width-7))
+        if self._min is not None:
+            self.win().addstr(height-2,2,self._flt(self._min,width-7))
+        
+        if refresh:
+            self.win().refresh()
+
+    def _redrawController(self, refresh=True):
+        height, width = self.win().getmaxyx()
+        if self._controlValue is not None and self._controlValue >= 999:
+            self.win().addch(3,3,curses.ACS_UARROW)
+        else:
+            self.win().addch(3,3,curses.ACS_TTEE)
+        if self._controlValue is not None and self._controlValue <= 1:
+            self.win().addch(height-3,3,curses.ACS_DARROW)
+        else:
+            self.win().addch(height-3,3,curses.ACS_BTEE)
+            
+        if refresh:
+            self.win().refresh()
     
+
+class StepController(Actions.Action):
+
+    def __init__(self, name, controller, direction):
+        Actions.Action.__init__(self, name)
+        self._controller = controller
+        self._direction = direction
+
+    def __call__(self, binding):
+        self._controller.stepValue(self._direction)
+        
+
 def register( viewmanager,
               dispatcher,
               context,
@@ -93,8 +273,19 @@ def register( viewmanager,
               alpha_switches,
               x,
               y,
+              dx,
               size,
               device,
-              bits = None ):
-    viewmanager.registerView( View(context, label, numeric_switches, alpha_switches, x, y, size) )
-    dispatcher.registerSource( Source(device, context, bits) )
+              bits = None,
+              controllers = []):
+    viewmanager.registerView( View(context, label, numeric_switches, alpha_switches, x, y, dx, size) )
+    source = Source(device, context, bits)
+    dispatcher.registerSource( source )
+    return source
+
+def registerController( viewmanager, dispatcher, keylist, source, context, name, x, y, dx, dy,
+                        controller, low, high ):
+    controller = Controller(context, name, x, y, dx, dy, keylist, dispatcher,
+                                         controller, source, low, high)
+    viewmanager.registerView( controller )
+    return controller
index 4c2a03b..3f1ff24 100644 (file)
@@ -23,7 +23,7 @@ class View(Views.View):
         keys = bindings.keys()
         keys.sort()
         column = 2
-        row = 2
+        row = 1
         for key in keys:
             keyname = curses.keyname(key.code)
             if keyname.startswith('KEY_'):
@@ -35,8 +35,8 @@ class View(Views.View):
             self.win().addstr(row, column, '%-6s %s'
                               % (keyname, bindings[key].label[:self._size]))
             row += 1
-            if row >= self.dy-2:
-                row = 2
+            if row >= self.dy-1:
+                row = 1
                 column += self._size+10
                 if column+self._size+7 >= self.dx : break
                 self.win().vline(1,column-2,curses.ACS_VLINE,self.dy-2)
@@ -49,6 +49,7 @@ def register( viewmanager,
               x,
               y,
               dx,
-              dy ):
-    viewmanager.registerView( View(context, label, x,y,dx,dy) )
+              dy,
+              size=9):
+    viewmanager.registerView( View(context, label, x,y,dx,dy,size) )
     dispatcher.registerSource( Source(context, viewmanager.win()) )
index ab98615..93bc12e 100644 (file)
--- a/Logger.py
+++ b/Logger.py
@@ -2,13 +2,13 @@ import textwrap, time
 
 logger = None
 
-def init(viewmanager, x, y, dx, dy):
+def init(viewmanager, x, y, dx, dy, logfile=None):
     global logger
-    logger = Logger(viewmanager.win(), x, y, dx, dy)
+    logger = Logger(viewmanager.win(), x, y, dx, dy, logfile)
 
 class Logger(object):
 
-    def __init__(self, win, x, y, dx, dy):
+    def __init__(self, win, x, y, dx, dy, logfile=None):
         self._win = win.derwin(dy,dx,y,x)
         self._win.border()
         self._win.addstr(0,2,' Log ')
@@ -17,12 +17,19 @@ class Logger(object):
         self._textwin.scrollok(1)
         self._wrapper = textwrap.TextWrapper(width = dx-4,
                                              subsequent_indent = ' '*4)
+        if logfile:
+            self._logfile = file(logfile,"a")
+        else:
+            self._logfile = None
         
     def log(self, src, msg):
-        lines = self._wrapper.wrap(
-            '[%s] (%s) %s' %  (time.strftime("%H:%M:%S",time.localtime()),src, msg))
+        text = '[%s] (%s) %s' %  (time.strftime("%H:%M:%S",time.localtime()),src, msg)
+        if self._logfile:
+            self._logfile.write(text+"\n")
+        lines = self._wrapper.wrap(text)
+        lines = lines[max(0,len(lines)-self._textwin.getmaxyx()[0]):]
         self._textwin.scroll(len(lines))
-        for i in range(len(lines)):
+        for i in range(min(len(lines),self._textwin.getmaxyx()[0]-2)):
             self._textwin.addstr(self._textwin.getmaxyx()[0]-len(lines)+i,0,
                                  lines[i])
         self._textwin.nooutrefresh()
index 075e98b..1a31e0e 100644 (file)
--- a/Looper.py
+++ b/Looper.py
@@ -1,48 +1,72 @@
-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)
+import Actions, OSC, Events
+
+class Controller(object):
+
+    def __init__(self, context, oscserver, remote):
+        self._context = context
+        self._server = oscserver
+        self._remote = remote
+        self._server.registerHandler(self._remote, self._oscEvent)
+
+    def hit(self, command, index=0):
+        self._server.command(self._remote,"/sl/%d/hit" % index, str(command))
+
+    def get(self, parameter, index=0):
+        if parameter.startswith('global_'):
+            path = "/get"
+            parameter = parameter[7:]
         else:
-            raise RunstimeError, "Invalid OSC argument"
-    return osc_string(url) + osc_string(tag) + argstring
+            path = "/sl/%d/get" % index
+        self._server.command(self._remote, path, parameter, self._server.localUrl(), "")
 
-class OSCSender(object):
+    def set(self, parameter, value, index=0):
+        if parameter.startswith('global_'):
+            path = "/set"
+            parameter = parameter[7:]
+        else:
+            path = "/sl/%d/set" % index
+        self._server.command(self._remote, path, parameter, float(value))
 
-    def __init__(self, host, port):
-        self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
-        self._target = (host,port)
+    def _oscEvent(self, path, data):
+        # data = (loop-number, parameter-name, value)
+        if len(data) != 3: return
+        if data[0]<0:
+            param = "global_%s" % data[1]
+        else:
+            param = "%s/%d" % (data[1], data[0])
+        return [ Events.ControlEvent(self._context, param, data[2]) ]
 
-    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 ParamGetter(object):
 
-class Controller(object) :
+        def __init__(self, controller, parameter, index=0):
+            self._parameter = parameter
+            self._controller = controller
+            self._index = index
 
-    def __init__(self, host, port):
-        self._sender = OSCSender(host,port)
+        def __call__(self):
+            self._controller.get(self._parameter, self._index)
 
-    def hit(self, command, index=0):
-        self._sender.command("/sl/%d/hit" % index, str(command))
+    class ParamSetter(object):
 
+        def __init__(self, controller, parameter, index=0):
+            self._parameter = parameter
+            self._controller = controller
+            self._index = index
 
+        def __call__(self, value):
+            self._controller.set(self._parameter, value, self._index)
+
+    def assignController( self, controller, title, parameter, min, max, stops=[], index=0 ):
+        if parameter.startswith('global_'):
+            event = parameter
+        else:
+            event = "%s/%d" % (parameter, index)
+        controller.assign(title,
+                          self.ParamSetter(self, parameter, index),
+                          self.ParamGetter(self, parameter, index),
+                          Events.Event(self._context, event),
+                          min, max, stops)
+                      
 class Command(Actions.Action):
 
     def __init__(self, name, controller, command):
@@ -52,3 +76,31 @@ class Command(Actions.Action):
 
     def __call__(self, binding):
         self._controller.hit(self._command)
+
+class AssignController(Actions.Action):
+
+    def __init__(self, name, loopController, pedalController, title, parameter, min, max,
+                 stops = [], index=0):
+        Actions.Action.__init__(self, name)
+        self._loopController = loopController
+        self._pedalController = pedalController
+        self._title = title
+        self._parameter = parameter
+        self._min = min
+        self._max = max
+        self._stops = stops
+        self._index = index
+
+    def __call__(self, binding):
+        self._loopController.assignController(self._pedalController,
+                                              self._title,
+                                              self._parameter,
+                                              self._min,
+                                              self._max,
+                                              self._stops,
+                                              self._index)
+
+def register( oscserver,
+              context,
+              remote ):
+    return Controller(context, oscserver, remote)
diff --git a/Mixer.py b/Mixer.py
new file mode 100644 (file)
index 0000000..264184d
--- /dev/null
+++ b/Mixer.py
@@ -0,0 +1,221 @@
+import Actions, OSC, Events, Views, curses
+
+class Mixer(Views.View):
+
+    def __init__(self, context, label, x, y, dx, dy, channels, oscserver, remote):
+        Views.View.__init__(self, context, label, x, y, dx, dy)
+        self._context = context
+        self._server = oscserver
+        self._remote = remote
+        self._server.registerHandler(self._remote, self._oscEvent)
+        self._server.command(self._remote,'/mixer/get_channel_count')
+        self._channelNames = channels
+        self._channels = None
+        self._volume = None
+        self._muteState = None
+
+    def updateView(self, bindings):
+        pass
+
+    def init(self):
+        Views.View.init(self)
+
+    def _redraw(self):
+        if not self._channels : return
+        for i in range(self._channels):
+            self.win().addstr(i+1,2,self._channelNames[i][:8])
+            self._redrawValue(i+1)
+        self.win().refresh()
+
+    def _redrawValue(self, channel):
+        if self._muteState[channel-1] == self.MUTED:
+            self.win().addstr(channel,11,'Muted',curses.A_BOLD)
+        elif self._volume[channel-1] is not None:
+            self.win().addstr(channel,11, '%5.1f dB' % self._volume[channel-1])
+        self.win().refresh()
+
+    ACTIVE = 0
+    PENDING = 1
+    MUTED = 2
+
+    def _oscEvent(self, path, data):
+        if path == '/mixer/channel/gain':
+            # channel, gain
+            if self._muteState[data[0]-1] != self.MUTED:
+                self._volume[data[0]-1] = data[1]
+            if self._muteState[data[0]-1] == self.PENDING:
+                self._muteState[data[0]-1] = self.MUTED
+                self._set(data[0],-90.0)
+            self._redrawValue(data[0])
+            return [ Events.ControlEvent(self._context, data[0], data[1]) ]
+        elif path == '/mixer/channel_count':
+            # channel_count
+            self._channels = int(data[0])
+            self._volume = [ None ] * self._channels
+            self._muteState = [ self.ACTIVE ] * self._channels
+            while  len(self._channelNames) < self._channels:
+                self._channelNames.append("Channel %d" % (len(self._channelNames)+1))
+            self._redraw()
+            for i in range(self._channels):
+                self._get(i+1)
+                
+        return []
+
+    def mute(self, channel):
+        if self._muteState[channel-1] == self.ACTIVE:
+            self._muteState[channel-1] = self.PENDING
+            self._get(channel)
+
+    def unmute(self, channel):
+        if self._muteState[channel-1] != self.ACTIVE:
+            if self._muteState[channel-1] == self.MUTED:
+                self._set(channel, self._volume[channel-1])
+            self._muteState[channel-1] = self.ACTIVE
+
+    def muteToggle(self, channel):
+        if self._muteState[channel-1] == self.ACTIVE:
+            self.mute(channel)
+        else:
+            self.unmute(channel)
+
+    def muteToggleAll(self):
+        if [ x for x in self._muteState if x is not self.MUTED ]:
+            for i in range(self._channels):
+                self.mute(i+1)
+        else:
+            for i in range(self._channels):
+                self.unmute(i+1)
+
+    def get(self, channel):
+        if self._muteState is None or self._muteState[channel-1] == self.ACTIVE:
+            self._get(channel)
+
+    def _get(self, channel):
+        self._server.command(self._remote,'/mixer/channel/get_gain', channel)
+
+    def set(self, channel, value):
+        if self._muteState is None or self._muteState[channel-1] == self.ACTIVE:
+            self._set(channel, value)
+
+    def _set(self, channel, value):
+        self._server.command(self._remote,'/mixer/channel/set_gain', channel, value)
+
+    def cycleVolume(self, channel, volumes):
+        if self._muteState is None or self._muteState[channel-1] != self.ACTIVE:
+            return
+        elif self._volume[channel-1] is not None:
+            for i in range(len(volumes)):
+                if not volumes[i]-0.01 < self._volume[channel-1]:
+                    self._set(channel,volumes[i])
+                    return
+            self._set(channel, volumes[0])
+
+    class ParamGetter(object):
+
+        def __init__(self, mixer, channel):
+            self._mixer = mixer
+            self._channel = channel
+
+        def __call__(self):
+            self._mixer.get(self._channel)
+
+    class ParamSetter(object):
+
+        def __init__(self, mixer, channel):
+            self._mixer = mixer
+            self._channel = channel
+
+        def __call__(self, value):
+            self._mixer.set(self._channel, value)
+
+    def assignController(self, controller, title, channel, min=-12.0, max=6.0):
+        controller.assign(title,
+                          self.ParamSetter(self, channel),
+                          self.ParamGetter(self, channel),
+                          Events.Event(self._context, channel),
+                          min, max, [ 0.0 ])
+
+
+
+class AssignController(Actions.Action):
+
+    def __init__(self, name, mixer, controller, title, channel, min=-12.0, max=6.0):
+        Actions.Action.__init__(self, name)
+        self._mixer = mixer
+        self._controller = controller
+        self._title = title
+        self._channel = channel
+        self._min = min
+        self._max = max
+
+    def __call__(self, binding):
+        self._mixer.assignController(self._controller,
+                                     self._title,
+                                     self._channel,
+                                     self._min,
+                                     self._max)
+
+
+class MuteChannel(Actions.Action):
+
+    def __init__(self, name, mixer, channel):
+        Actions.Action.__init__(self, name)
+        self._mixer = mixer
+        self._channel = channel
+
+    def __call__(self, binding):
+        self._mixer.mute(self._channel)
+
+
+class UnmuteChannel(Actions.Action):
+
+    def __init__(self, name, mixer, channel):
+        Actions.Action.__init__(self, name, mixer, channel)
+        Actions.Action.__init__(self, name)
+        self._mixer = mixer
+        self._channel = channel
+        
+    def __call__(self, binding):
+        self._mixer.unmute(self._channel)
+        
+
+class ToggleMuteChannel(Actions.Action):
+
+    def __init__(self, name, mixer, channel):
+        Actions.Action.__init__(self, name)
+        self._mixer = mixer
+        self._channel = channel
+
+    def __call__(self, binding):
+        self._mixer.muteToggle(self._channel)
+
+class ToggleMuteAll(Actions.Action):
+
+    def __init__(self, name, mixer):
+        Actions.Action.__init__(self, name)
+        self._mixer = mixer
+
+    def __call__(self, binding):
+        self._mixer.muteToggleAll()
+
+class CycleVolume(Actions.Action):
+
+    def __init__(self, name, mixer, channel, volumes):
+        Actions.Action.__init__(self, name)
+        self._mixer = mixer
+        self._channel = channel
+        self._volumes = volumes
+
+    def __call__(self, binding):
+        self._mixer.cycleVolume(self._channel, self._volumes)
+
+def register( viewmanager,
+              oscserver,
+              context,
+              label,
+              x, y, dx, dy,
+              channels, 
+              remote ):
+    mixer = Mixer(context, label, x, y, dx, dy, channels, oscserver, remote)
+    viewmanager.registerView( mixer )
+    return mixer
diff --git a/OSC.py b/OSC.py
new file mode 100644 (file)
index 0000000..e248b47
--- /dev/null
+++ b/OSC.py
@@ -0,0 +1,88 @@
+import socket, struct
+import Events, 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)
+        elif type(arg) is float:
+            tag += 'f'
+            argstring += struct.pack(">f",arg)
+        else:
+            raise RunstimeError, "Invalid OSC argument"
+    return osc_string(url) + osc_string(tag) + argstring
+
+
+def osc_getstr(data):
+    i = data.index('\0')
+    s = data[:i]
+    n = (i+1)%4
+    if n : data = data[i+(5-n):]
+    else : data = data[i+1:]
+    return (s,data)
+
+
+def osc_parse(data):
+    url,data = osc_getstr(data)
+    tag,data = osc_getstr(data)
+    values = []
+    for code in tag[1:]:
+        if code == 'i':
+            values.append(struct.unpack(">i",data[:4])[0])
+            data = data[4:]
+        elif code == 's':
+            v,data = osc_getstr(data)
+            values.append(v)
+        elif code == 'f':
+            values.append(struct.unpack(">f",data[:4])[0])
+            data = data[4:]
+        else:
+            raise RuntimeError, "Unknown OSC parameter type received"
+    return (url, values)
+
+
+class OSCServer(Events.EventSource):
+
+    def __init__(self, addr, context):
+        Events.EventSource.__init__(self,
+                                    socket.socket(socket.AF_INET, socket.SOCK_DGRAM),
+                                    context)
+        self._addr = addr
+        self.fd().bind(addr)
+        self._handlers = {}
+
+    def registerHandler(self, addr, cb):
+        self._handlers[addr] = cb
+
+    def command(self, target, path, *args):
+        if args and type(args[0]) in (tuple, list) : args = args[0]
+        Logger.log('osc','osc://%s:%s%s %s'
+                   % (target[0], target[1], path,
+                      ' '.join(map(repr,args))))
+        self.fd().sendto(osc_command(path, *args),target)
+
+    def readEvents(self):
+        data, addr = self.fd().recvfrom(1024)
+        path, args = osc_parse(data)
+        Logger.log('oscrcv','osc://%s:%s%s %s' % (addr[0], addr[1], path,
+                                                  ' '.join(map(repr,args))))
+        handler = self._handlers.get(addr)
+        if handler:
+            return handler(path, args)
+        else:
+            return []
+
+    def localUrl(self):
+        return 'osc://%s:%s' % self._addr
index d4b5abf..3ec155c 100644 (file)
--- a/config.py
+++ b/config.py
@@ -5,35 +5,60 @@ from Views import EventWidget
 from Events import Event
 import main
 import Joyboard, Keyboard, Process
-import Looper
-import sys, curses
+import Looper, Mixer
+import sys, curses, time, os
 from curses.ascii import alt, ctrl
 
 def shift(letter) : return ord(chr(letter).upper())
 def key(letter) : return ord(letter.lower())
 
+global_map = Bindings.KeyMap()
+
 ###########################################################################
 # 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 }
-)
+# Display size: 88x22
+
+#Logger.init(main.viewmanager, 38, 0, 37, 10, 'audiocontroller.log')
+Logger.init(main.viewmanager, 0, 17, 88, 5)
+
+jb = None
+ctl = None
+if os.path.exists('/dev/input/js0'):
+    jb = Joyboard.register(
+        viewmanager = main.viewmanager,
+        dispatcher = main.dispatcher,
+        
+        context          = 'jb0',
+        label            = 'Foot Switch',
+        numeric_switches =  10,
+        alpha_switches   =   5,
+        x                =   0,
+        y                =  10,
+        dx               =  88,
+        size             =   7,
+        
+        device           = '/dev/input/js0',
+        bits             = { 1:1, 3:2, 2:4, 0:8 },
+    )
+
+    ctl = Joyboard.registerController(
+        viewmanager = main.viewmanager,
+        dispatcher = main.dispatcher,
+        keylist = main.keylist,
+        source = jb,
+        
+        context          = 'c0',
+        name             = 'Control',
+        x                = 75,
+        y                =  0,
+        dx               = 13,
+        dy               = 10,
+
+        controller       = 0,
+        low              = -27200,
+        high             = -32700,
+    )        
 
 Keyboard.register(
     viewmanager = main.viewmanager,
@@ -43,39 +68,50 @@ Keyboard.register(
     label            = 'Key Bindings',
     x                =  0,
     y                =  0,
-    dx               = 38,
-    dy               = 10
+    dx               = 52,
+    dy               = 10,
+    size             =  7
 )
 
-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)
+    sys.exit(1)
 
-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() ) )
+@action
+def restart(binding):
+    sys.exit(0)
 
 global_map.add( Binding( Event('kbd',key('q')), 'Quit', Action['quit'] ) )
+global_map.add( Binding( Event('kbd',ctrl(key('r'))), 'Restart', Action['restart'] ) )
+
+for i,k in enumerate(('1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e' )):
+    Action.register( Events.EmitEvent('key_%s' % k, main.dispatcher, Event('jb0', i)) )
+    global_map.add( Binding( Event('kbd',key(k)), 'Foot %s' % k.upper(), Action['key_%s' % k] ) )
+    
+if ctl is not None:
+    Action.register( Joyboard.StepController( 'controller_increment', ctl, +1 ) )
+    Action.register( Joyboard.StepController( 'controller_decrement', ctl, -1 ) )
+
+    global_map.add( Binding( Event('kbd', curses.KEY_UP), 'Increment', Action['controller_increment'] ) )
+    global_map.add( Binding( Event('kbd', curses.KEY_DOWN), 'Decrement', Action['controller_decrement'] ) )
 
 Action.register( Actions.ChangeBindingsRelative( 'unset_this_map', 0, [] ) )
 
 ###########################################################################
-# Looper mode
+# Looper
 
 looper_main_map = Bindings.KeyMap( 'Looper' )
-looper_aux_map = Bindings.KeyMap( 'Aux' )
+Action.register( Actions.ChangeBindingsRelative( 'mode_looper', 0, [ looper_main_map ] ) )
 
-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.register(
+    oscserver = main.oscserver,
 
-looper = Looper.Controller('localhost',9951)
+    context = 'sl',
+    remote  = ('127.0.0.1',9951),
+)
 
 Action.register( Looper.Command('looper_record', looper, 'record') )
 Action.register( Looper.Command('looper_overdub', looper, 'overdub') )
@@ -89,44 +125,134 @@ 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'] ) )
+Action.register( Looper.Command('looper_undo_all', looper, 'undo_all') )
+Action.register( Looper.Command('looper_redo_all', looper, 'redo_all') )
+
+looper_main_map.add ( Binding( Event('jb0',0),  'Rec',    Action['looper_record'] ) )
+looper_main_map.add ( Binding( Event('jb0',1),  'Over',   Action['looper_overdub'] ) )
+looper_main_map.add ( Binding( Event('jb0',2),  'Mult',   Action['looper_multiply'] ) )
+looper_main_map.add ( Binding( Event('jb0',3),  'Undo',   Action['looper_undo'] ) )
+looper_main_map.add ( Binding( Event('jb0',4),  'Redo',   Action['looper_redo'] ) )
+looper_main_map.add ( Binding( Event('jb0',5),  'Mute',   Action['looper_mute'] ) )
+looper_main_map.add ( Binding( Event('jb0',6),  'Trig',   Action['looper_trigger'] ) )
+looper_main_map.add ( Binding( Event('jb0',7),  'Once',   Action['looper_oneshot'] ) )
+looper_main_map.add ( Binding( Event('jb0',8),  'Ins',    Action['looper_insert'] ) )
+looper_main_map.add ( Binding( Event('jb0',9),  'Repl',   Action['looper_replace'] ) )
+looper_main_map.add ( Binding( Event('jb0',12), 'Undo A', Action['looper_undo_all'] ) )
+looper_main_map.add ( Binding( Event('jb0',13), 'Redo A', Action['looper_redo_all'] ) )
+looper_main_map.add ( Binding( Event('jb0',14), 'Subst',  Action['looper_substitute'] ) )
+
+
+looper_param_map = Bindings.KeyMap( 'Parameters' )
+Action.register( Actions.ChangeBindingsRelative('looper_set_param_map', 1, [looper_param_map] ) )
+looper_main_map.add ( Binding( Event('jb0',11), '[Param]', Action['looper_set_param_map'] ) )
+
+if ctl is not None:
+    Action.register( Looper.AssignController( 'looper_parm_rec_thresh', looper, ctl, 'Rec.Thresh.',
+                                              'rec_thresh', 0.0, 1.0 ) )
+    Action.register( Looper.AssignController( 'looper_parm_feedback', looper, ctl, 'Feedback',
+                                              'feedback', 0.0, 1.0 ) )
+    Action.register( Looper.AssignController( 'looper_parm_dry', looper, ctl, 'Dry Level',
+                                              'global_dry', 0.0, 1.0 ) )
+    Action.register( Looper.AssignController( 'looper_parm_wet', looper, ctl, 'Wet Level',
+                                              'global_wet', 0.0, 1.0 ) )
+    Action.register( Looper.AssignController( 'looper_parm_igain', looper, ctl, 'In. Gain',
+                                              'global_input_gain', 0.0, 1.0 ) )
+
+    steps = [ 1.0 ]
+    for i in range(6):
+        x = pow(2.0,2.0*(i+1)/12.0)
+        steps.append(x)
+        steps[0:0] = [1/x]
+
+    Action.register( Looper.AssignController( 'looper_parm_rate', looper, ctl, 'Rate',
+                                              'rate', 0.5, 2.0, steps ) )
+
+    looper_param_map.add( Binding( Event('jb0',5),  'Feedb', Action['looper_parm_feedback'] ) )
+    looper_param_map.add( Binding( Event('jb0',6),  'Dry',   Action['looper_parm_dry'] ) )
+    looper_param_map.add( Binding( Event('jb0',7),  'Wet',   Action['looper_parm_wet'] ) )
+    looper_param_map.add( Binding( Event('jb0',8),  'Gain',  Action['looper_parm_igain'] ) )
+    looper_param_map.add( Binding( Event('jb0',9),  'Rec T', Action['looper_parm_rec_thresh'] ) )
+    looper_param_map.add( Binding( Event('jb0',12), '',      Actions.Nop() ) )
+    looper_param_map.add( Binding( Event('jb0',13), 'Rev',    Action['looper_reverse'] ) )
+    looper_param_map.add( Binding( Event('jb0',14), 'Rate',  Action['looper_parm_rate'] ) )
+
+looper_param_map.add( Binding( Event('jb0',11), '[Main]',    Action['unset_this_map'] ) )
+
+# Initialize looper: enable 'round' and set quantize to 'cycle'
+looper.set('quantize',1)
+looper.set('round',1)
+looper.set('sync',1)
 
 ###########################################################################
-# MegaPedal mode
+# Mixer and effects
 
-megapedal_main_map = Bindings.KeyMap( 'MegaPedal' )
+mixer_map = Bindings.KeyMap('Mixer & Effects')
+Action.register( Actions.ChangeBindingsRelative( 'mode_mixer', 0, [ mixer_map ] ) )
 
-Action.register( Actions.ChangeBindingsAbsolute('set_mode_megapedal', 1, [megapedal_main_map]) )
+looper_main_map.add( Binding( Event('jb0',10),  '[Mixer]', Action['mode_mixer'] ) )
 
-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'] ) )
+mixer = Mixer.register(
+    viewmanager = main.viewmanager,
+    oscserver = main.oscserver,
 
-###########################################################################
-# RythmBox mode
+    context  = 'mix',
+    label    = 'Mixer',
+    x        = 52,
+    y        =  0,
+    dx       = 23,
+    dy       = 4,
+
+    channels = ( 'Guitar', 'Voice' ),
+    remote  = ('127.0.0.1', 9901),
+)
 
-rythmbox_main_map = Bindings.KeyMap( 'RythmBox' )
+gain = Mixer.register(
+    viewmanager = main.viewmanager,
+    oscserver = main.oscserver,
 
-Action.register( Actions.ChangeBindingsAbsolute('set_mode_rythmbox', 1, [rythmbox_main_map]) )
+    context  = 'gain',
+    label    = 'Gain',
+    x        = 52,
+    y        =  7,
+    dx       = 23,
+    dy       = 3,
 
-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'] ) )
+    channels = ( 'Guitar', ),
+    remote  = ('127.0.0.1', 9902),
+)
 
+Action.register( Mixer.AssignController( 'mixer_guitar_level', mixer, ctl, 'Guitar', 1 ) )
+Action.register( Mixer.ToggleMuteChannel( 'mixer_mute_guitar', mixer, 1 ) )
+Action.register( Mixer.AssignController( 'mixer_voice_level', mixer, ctl, 'Voice', 2 ) )
+Action.register( Mixer.ToggleMuteChannel( 'mixer_mute_voice', mixer, 2 ) )
+Action.register( Mixer.AssignController( 'mixer_master_level', mixer, ctl, 'Master', 0 ) )
+Action.register( Mixer.ToggleMuteAll( 'mixer_mute_all', mixer ) )
+Action.register( Mixer.CycleVolume( 'mixer_cycle_gain', gain, 1, ( 0.0, 2.0, 4.0 ) ) )
+
+mixer_map.add( Binding( Event('jb0',0),  'Rec',    Action['looper_record'] ) )
+mixer_map.add( Binding( Event('jb0',1),  'Over',   Action['looper_overdub'] ) )
+mixer_map.add( Binding( Event('jb0',2),  'Mult',   Action['looper_multiply'] ) )
+mixer_map.add( Binding( Event('jb0',3),  'Undo',   Action['looper_undo'] ) )
+mixer_map.add( Binding( Event('jb0',4),  'Redo',   Action['looper_redo'] ) )
+mixer_map.add( Binding( Event('jb0',5), 'Un All', Action['looper_undo_all'] ) )
+
+mixer_map.add( Binding( Event('jb0',6), 'Lead', Action['mixer_cycle_gain'] ) )
+mixer_map.add( Binding( Event('jb0',7), 'Mute G', Action['mixer_mute_guitar'] ) )
+mixer_map.add( Binding( Event('jb0',8), 'Mute V', Action['mixer_mute_voice'] ) )
+mixer_map.add( Binding( Event('jb0',9), 'Mute', Action['mixer_mute_all'] ) )
+
+mixer_map.add( Binding( Event('jb0',13), 'Vol G', Action['mixer_guitar_level'] ) )
+mixer_map.add( Binding( Event('jb0',14), 'Vol V', Action['mixer_voice_level'] ) )
+
+mixer.set(1,0.0)
+mixer.set(2,0.0)
+mixer.assignController( ctl, 'Guitar', 1 )
+gain.set(1,0.0)
+    
 ###########################################################################
 
+mixer_map.add( Binding( Event('jb0',10), '[Loop]', Action['mode_looper'] ) )
+
 main.keylist.append(global_map)
-main.keylist.append(looper_main_map)
+main.keylist.append(mixer_map)
diff --git a/main.py b/main.py
index caa6418..7375c7d 100644 (file)
--- a/main.py
+++ b/main.py
@@ -1,9 +1,10 @@
-import Bindings, Events, Views, curses.wrapper
+import Bindings, Events, Views, OSC, curses.wrapper
 
 keylist = None
 dispatcher = None
 viewmanager = None
 stdscr = None
+oscserver = None
 
 
 def run(scr):
@@ -12,11 +13,14 @@ def run(scr):
     global dispatcher
     global viewmanager
     global stdscr
+    global oscserver
     
     keylist = Bindings.KeyList()
     dispatcher = Events.Dispatcher(keylist)
     viewmanager = Views.ViewManager(keylist, dispatcher, scr)
     stdscr = scr
+    oscserver = OSC.OSCServer(('127.0.0.1', 9900), 'osc')
+    dispatcher.registerSource(oscserver)
 
     import config
 
diff --git a/start.sh b/start.sh
new file mode 100755 (executable)
index 0000000..6adb08b
--- /dev/null
+++ b/start.sh
@@ -0,0 +1,115 @@
+#!/bin/sh
+
+cd "`dirname "$0"`"
+
+displaysize="`xdpyinfo | awk '/^  dimensions:/{print $2}'`"
+displaywidth="${displaysize%x*}"
+offset=`expr $displaywidth - 1920`
+
+clients=""
+
+move() {
+    local x
+    local y
+    local w
+    local h
+    local name
+    local winid
+    x=`expr $1 + $offset`
+    y=$2
+    w=$3
+    h=$4
+    name="$5"
+    winid=""
+    while [ -z "$winid" ]; do
+       winid=`xwininfo -name "$name" | awk '/^xwininfo:/{print $4}'`
+       [ -n "$winid" ] || sleep 1
+    done
+    ./winmove $winid $x $y $w $h
+}
+
+start() {
+    local x
+    local y
+    local w
+    local h
+    local name
+    local winid
+    x=$1
+    y=$2
+    w=$3
+    h=$4
+    name="$5"
+    shift; shift; shift; shift; shift
+    "$@" &
+    clients="$clients $!"
+    move $x $y $w $h "$name"
+}
+
+lsof -n | grep dev | grep -e snd -e dsp | awk '{print $2}' | xargs -r kill
+
+# Make sure nothing is running
+killall slgui
+killall sooperlooper
+killall meterbridge
+killall qjackctl
+killall jack-rack
+killall alsamixer
+killall jackminimix
+killall jackd
+
+amixer sset Master 80% on
+amixer sset PCM 100% on
+amixer sset Capture 50% on
+
+start 0 25 496 100 "JACK Audio Connection Kit [(default)] Started." \
+    qjackctl
+
+sooperlooper -l 1 -c 1 -t 300 &
+clients="$clients $!"
+
+cd loops
+start 582 25 794 220 "SooperLooper" \
+    slgui
+cd ..
+
+# start 0 153 496 600 "JACK Rack (voice) - voice.rack" \
+start 0 153 496 972 "JACK Rack (voice) - voice.rack" \
+    jack-rack -c 1 -s voice voice.rack
+
+# start 0 781 496 344 "JACK Rack (guitar) - guitar.rack" \
+#    jack-rack -c 1 -s guitar guitar.rack
+
+start 502 25 74 210 "dpm meter" \
+    meterbridge -r 0 -t dpm -n meter alsa_pcm:capture_1 jack_rack_voice:out_1
+
+x=`expr $offset + 1382`
+xterm -fn 6x12 -bg black -fg white -cr white -geometry 88x18+${x}+25 +sb -title Mixer \
+    -e alsamixer -V all &
+clients="$clients $!"
+
+jackminimix -c 2 -p 9901 &
+clients="$clients $!"
+
+jackminimix -c 1 -p 9902 -n inputgain &
+clients="$clients $!"
+
+sleep 1
+
+jack_connect alsa_pcm:capture_1 inputgain:in1_left
+jack_connect inputgain:out_left sooperlooper:common_in_1
+jack_connect sooperlooper:common_out_1 minimixer:in1_left
+jack_connect sooperlooper:common_out_1 minimixer:in1_right
+jack_connect alsa_pcm:capture_2 jack_rack_voice:in_1
+jack_connect jack_rack_voice:out_1 minimixer:in2_left
+jack_connect jack_rack_voice:out_1 minimixer:in2_right
+jack_connect minimixer:out_left alsa_pcm:playback_1
+jack_connect minimixer:out_right alsa_pcm:playback_2
+
+x=`expr $offset + 502`
+xterm -fn '-dejavu-dejavu sans mono-medium-r-normal--*-260-75-75-m-0-iso10646-1' \
+    -bg black -fg white -cr white -geometry 88x22+${x}+273 +sb \
+    -title "Audio Controller" # -e /bin/sh -c "while ./audiocontroller; do true; done"
+
+kill $clients 2>/dev/null
+killall jackd 2>/dev/null
diff --git a/winmove.c b/winmove.c
new file mode 100644 (file)
index 0000000..2f3888a
--- /dev/null
+++ b/winmove.c
@@ -0,0 +1,29 @@
+// $Id$
+//
+// Copyright (C) 2007 
+
+#include <Xlib.h>
+#include <Xutil.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+int main(int argc, char ** argv)
+{
+    int winid = strtol(argv[1],0,0);
+    int x = strtol(argv[2],0,0);
+    int y = strtol(argv[3],0,0);
+    int w = strtol(argv[4],0,0);
+    int h = strtol(argv[5],0,0);
+    Display * display = XOpenDisplay(0);
+
+    printf("Window %d on %p -geometry %dx%d+%d+%d\n", winid, display, w,h,x,y);
+    XMoveResizeWindow(display, winid, x, y, w, h);
+    XSync(display, False);
+    return 0;
+}
+
+\f
+// Local Variables:
+// mode: c++
+// compile-command: "gcc -Wall -o winmove winmove.c -I/usr/include/X11 -lX11"
+// End: