From: g0dil Date: Mon, 15 Oct 2007 08:28:25 +0000 (+0000) Subject: Audio/AudioControl: Commit loads of long uncommited changes X-Git-Url: http://g0dil.de/git?a=commitdiff_plain;h=811a95d9a6a797149cdfc6a8ad9c6b2a779a87bc;p=audiocontrol.git Audio/AudioControl: Commit loads of long uncommited changes --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9334190 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pyc +winmove diff --git a/Bindings.py b/Bindings.py index aaac851..b6cfd81 100644 --- a/Bindings.py +++ b/Bindings.py @@ -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() diff --git a/Events.py b/Events.py index 4a51f79..8a64f47 100644 --- 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) diff --git a/Joyboard.py b/Joyboard.py index 2629257..be8af89 100644 --- a/Joyboard.py +++ b/Joyboard.py @@ -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 diff --git a/Keyboard.py b/Keyboard.py index 4c2a03b..3f1ff24 100644 --- a/Keyboard.py +++ b/Keyboard.py @@ -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()) ) diff --git a/Logger.py b/Logger.py index ab98615..93bc12e 100644 --- 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() diff --git a/Looper.py b/Looper.py index 075e98b..1a31e0e 100644 --- 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 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 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 diff --git a/config.py b/config.py index d4b5abf..3ec155c 100644 --- 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 --- 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 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 index 0000000..2f3888a --- /dev/null +++ b/winmove.c @@ -0,0 +1,29 @@ +// $Id$ +// +// Copyright (C) 2007 + +#include +#include +#include +#include + +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; +} + + +// Local Variables: +// mode: c++ +// compile-command: "gcc -Wall -o winmove winmove.c -I/usr/include/X11 -lX11" +// End: