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)
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._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)
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:
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):
def __call__(self, binding):
self._controller.stepValue(self._direction)
-
+
def register( viewmanager,
dispatcher,