X-Git-Url: http://g0dil.de/git?a=blobdiff_plain;f=Joyboard.py;h=ab0621e9a9e8fb9839228ed4c2df73f0f483b1e7;hb=6be10909dd3b8ffd5f9c0f069ca745e364834293;hp=26292575e02be3ba9c5565aa3f2510795bfb8a45;hpb=8aab1c96c67b95d25fb175495894c38f82143cc9;p=audiocontrol.git diff --git a/Joyboard.py b/Joyboard.py index 2629257..ab0621e 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: @@ -29,7 +33,7 @@ class JSEvent: TYPE_BUTTON = 0x01 TYPE_AXIS = 0x02 TYPE_INIT = 0x80 - + def __init__(self, data): self.time, self.value, self.type, self.number = struct.unpack(self.STRUCT,data) @@ -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,369 @@ 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 [] + +# New Controller state machine: +# +# First, we work in two steps: The controller gets an internal +# controller value which is an integer between 0 and 100. All +# operations change this value which is then converted to the output +# value. +# +# The controller position is managed as a signed integer between -2 +# and 2. +# +# The current state is managed in a state variable. There are the +# following states: +# +# INIT In this state, the controller is waiting for the initial +# parameter value. +# +# RUNNING The value is currently being changed. The speed and +# direction are given by the controller position. If the +# current controller position changes to 0 or to the +# opposite sign, the state is switched to WAIT. +# +# WAIT The state machine waits for the controller to enter the +# center position. Only when the controller has been in the +# center position for some time, it will enter the IDLE +# state. It may however go to RUNNING, if the controller +# position is again changed to have the same sign t had +# before entering WAIT. On entering this state, the +# controller position is checked. If the position is 0 now +# or if it is changed to become 0, a timeout is started. If +# the controller is not changed within this timeout, we +# enter IDLE. Any change of the controlle value terminates +# thiw timeout. +# +# SWAIT Wait at a stop. The controller will stay in this state +# until the controller position is changed to 0 or the opposite +# sign. It then enters the WAIT state. +# +# IDLE In this state, nothing happens, however, if the controller +# position changes from 0, the state machine enters the +# running state. +# +# Events are: +# +# ev_controllerChanged The controller position changes +# +# ev_timeout The timeout handle got called +# +# The stop positions are handled by converting them to appropriate +# integer values. When such a value is reached or crossed, the value +# is set to the stop value and the state machine enters the SWAIT +# state. +# +# When converting the integer value to the return float value, we +# first check, wether the integer value is one of the stop values. In +# this case, the associated float value is returned otherweise ahe +# integer is scaled to the correct float range. + +class Controller(Views.View): + + ######################################## + # external API + + 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) + + # External value interface + self._valueEvent = None + self._setCommand = None + self._valueCommand = None + + # Parameter information + self._parameter = None + self._min = None + self._max = None + self._stops = None + self._rawStops = None + self._steps = 40 + + # Controller keymap stuff + self._keylist = keylist + self._keymap = Bindings.KeyMap() + if source: + self._keymap.add( Bindings.Binding(Events.Event(source.context(), 'p%d' % controller), '', + Actions.Command('controllerChanged', + self._controllerChanged)) ) + self._keylist.prepend(self._keymap) + if source: + source.registerController(controller, low, high) + + # Working variables + self._dispatcher = dispatcher + self._value = None + self._rawValue = None + self._controlValue = None + self._controlSign = None + self._state = None + + 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._rawStops = [ self._rawFromCooked(x) for x in self._stops ] + if self._valueEvent: + self._keymap.add( Bindings.Binding( self._valueEvent, '', + Actions.Command('updateValue', + self._updateValue)) ) + self._keylist.prepend(self._keymap) + self._value = None + self._rawValue = 0 + self._controlValue = None + self._controlSign = None + self._state = self.S_INIT + self._redraw() + self._getValue() + + def stepValue(self, direction): + Logger.log('ctl','direction = %d' % direction) + self._rawValue += max(-1,min(1,direction)) + if self._rawValue > self._steps: + self._rawValue = self._steps + elif self._rawValue < 0: + self._rawValue = 0 + + Logger.log('ctl','rawValue = %d' % self._rawValue) + self._setValue(self._cookedFromRaw(self._rawValue)) + + return self._rawValue in self._rawStops + + ######################################## + # Base class interface implementation + + def updateView(self, bindings): + pass + + def init(self): + Views.View.init(self) + self._redraw() + + ######################################## + # External event callbacks + + def _updateValue(self, binding): + # Called to return the value after executing the valueCommand + # (when valueCommand initiates an async value get like via + # osc) + event = self._dispatcher.currentEvent() + self.__updateValue(event.value) + self._updateWindow() + + def __updateValue(self, value): + self._value = value + if self._state == self.S_INIT: + self._rawValue = self._rawFromCooked(self._value) + self._setState(self.S_SWAIT) + self._redrawValue() + self._redrawState() + self._updateWindow() + + def _controllerChanged(self, binding): + # Called, whenever the controller position changes + event = self._dispatcher.currentEvent() + if event.value >= 999: + newControlValue = 2 + elif event.value >= 700: + newControlValue = 1 + elif event.value >= 200: + newControlValue = 0 + elif event.value >= 1: + newControlValue = -1 + else: + newControlValue = -2 + Logger.log('ctl',"controlValue = %d" % newControlValue) + if self._controlValue is None or self._controlValue != newControlValue: + self._controlValue = newControlValue + self._ev_controllerChanged() + + ######################################## + # Internal API + + def _setValue(self, value): + self._setCommand(value) + self._getValue() + + def _getValue(self): + if self._valueEvent: + self._valueCommand() + else: + self.__updateValue(self._valueCommand()) + + def _rawFromCooked(self, value): + return min(self._steps,max(0,int(float(self._steps)*(value-self._min)/(self._max-self._min)+.5))) + + def _cookedFromRaw(self, value): + try: + return self._stops[ self._rawStops.index(value) ] + except ValueError: + return (self._min*(self._steps-value) + self._max*value)/self._steps + + ######################################## + # State machine + + S_INIT = 0 + S_RUNNING = 1 + S_WAIT = 2 + S_SWAIT = 3 + S_IDLE = 4 + + def _setState(self, state): + self._state = state + self._redrawState() + + def _ev_controllerChanged(self): + self._redrawController() + + if self._state in (self.S_IDLE, self.S_RUNNING): + if abs(self._controlValue)==1: + self._dispatcher.setIdleCallback(self._ev_timeout,200) + self._setState(self.S_RUNNING) + elif self._controlValue != 0: + self._dispatcher.setIdleCallback(self._ev_timeout,75) + self._setState(self.S_RUNNING) + else: + self._dispatcher.unsetIdleCallback() + if self._state == self.S_RUNNING: + self._setState(self.S_WAIT) + self._ev_controllerChanged() + else: + self._setState(self.S_IDLE) + + elif self._state == self.S_SWAIT: + if self._controlValue == 0 or self._controlSign and self._controlSign*self._controlValue < 0: + self._setState(self.S_WAIT) + self._ev_controllerChanged() + + elif self._state == self.S_WAIT: + if self._controlValue == 0: + self._dispatcher.setIdleCallback(self._ev_timeout,300) + else: + self._dispatcher.unsetIdleCallback() + if self._controlSign and self._controlSign*self._controlValue > 0: + self._setState(self.S_RUNNING) + self._ev_controllerChanged() + + self._updateWindow() + + def _ev_timeout(self): + if self._state == self.S_RUNNING: + self._controlSign = max(-1, min(1, self._controlValue)) + if self._controlSign == 0: + # Cannot happen but ... + self._setState(self.S_IDLE) + else: + if self.stepValue(self._controlSign): + self._setState(self.S_SWAIT) + + elif self._state == self.S_WAIT: + self._setState(self.S_IDLE) + self._dispatcher.unsetIdleCallback() + + self._updateWindow() + + ######################################## + # Drawing + + # +----------+ + # | Name | + # | - | + # | | | + # | |-1.234 | + # |-S| | + # | | | + # | | | + # | - | + # +----------+ + + + 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.win().addch(3,3,curses.ACS_TTEE) + self.win().addch(height-3,3,curses.ACS_BTEE) + self.win().addch(height/2,1,'-') + self._redrawValue() + self._redrawController() + self._updateWindow() + + def _redrawState(self): + self._redrawController() + + def _updateWindow(self): + self.win().refresh() + + def _flt(self, value, width): + return ('%.3f' % value)[:width].ljust(width) + + def _redrawValue(self): + 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)) + + def _redrawController(self, refresh=True): + if self._controlValue is None: return + height, width = self.win().getmaxyx() + pos = height - 3 - int( (self._controlValue + 2) * (height-6) / 4 + .5 ) + for row in range(3,height-2): + if row == pos: + self.win().addch(row,2,('N','R','W','S','I')[self._state]) + else: + self.win().addch(row,2,' ') + +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 +465,21 @@ 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 = None + if device is not None: + 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