X-Git-Url: http://g0dil.de/git?a=blobdiff_plain;f=Joyboard.py;h=ab0621e9a9e8fb9839228ed4c2df73f0f483b1e7;hb=HEAD;hp=ef06b87e5f2439e54151a1809d3f1c075d37ae27;hpb=a310a16249899420db192f01aacccaab9d8fd2f0;p=audiocontrol.git diff --git a/Joyboard.py b/Joyboard.py index ef06b87..ab0621e 100644 --- a/Joyboard.py +++ b/Joyboard.py @@ -33,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) @@ -106,16 +106,86 @@ class Source(Events.EventSource): 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: @@ -125,19 +195,14 @@ class Controller(Views.View): self._keylist.prepend(self._keymap) if source: source.registerController(controller, low, high) + + # Working variables self._dispatcher = dispatcher - self._min = None - self._max = None - self._stops = None self._value = None + self._rawValue = None self._controlValue = None - - def updateView(self, bindings): - pass - - def init(self): - Views.View.init(self) - self._redraw() + self._controlSign = None + self._state = None def assign(self, parameter, setCommand, valueCommand, valueEvent, min, max, stops=[]): self._keylist.removeMap(self._keymap) @@ -150,79 +215,202 @@ class Controller(Views.View): 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 - if self._valueEvent: - self._valueCommand() - else: - self._value = self._valueCommand() + 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._value = event.value + 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() - Logger.log('ctl',"value = %d" % event.value) - self._controlValue = event.value - if self._controlValue >= 999 or self._controlValue <= 1: - self._dispatcher.setIdleCallback(self._changeValue,75) - elif self._controlValue >= 700 or self._controlValue <= 200: - self._dispatcher.setIdleCallback(self._changeValue,200) + 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: - self._dispatcher.unsetIdleCallback() - self._redrawController() + newControlValue = -2 + Logger.log('ctl',"controlValue = %d" % newControlValue) + if self._controlValue is None or self._controlValue != newControlValue: + self._controlValue = newControlValue + self._ev_controllerChanged() - def _changeValue(self): - if self._value is None: return - if self._controlValue > 500: - self.stepValue(+1) - else: - self.stepValue(-1) + ######################################## + # Internal API - 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() + def _setValue(self, value): + self._setCommand(value) + self._getValue() + + def _getValue(self): + if self._valueEvent: + self._valueCommand() else: - self._setCommand(newValue) - if self._valueEvent: - self._valueCommand() + 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: - self._value = self._valueCommand() - self._redrawValue() + 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._redrawValue(False) + 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, refresh=True): + def _redrawValue(self): height, width = self.win().getmaxyx() pos = None if self._value is not None: @@ -247,25 +435,17 @@ class Controller(Views.View): 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): + if self._controlValue is None: return height, width = self.win().getmaxyx() - if self._controlValue is not None and self._controlValue >= 700: - 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 <= 200: - self.win().addch(height-3,3,curses.ACS_DARROW) - else: - self.win().addch(height-3,3,curses.ACS_BTEE) - - if refresh: - self.win().refresh() - - + 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): @@ -275,7 +455,7 @@ class StepController(Actions.Action): def __call__(self, binding): self._controller.stepValue(self._direction) - + def register( viewmanager, dispatcher,