def __init__(self, midi_input_name, midi_output_name, callback_log):
        self.callback_log = callback_log

        # LCD has 2 rows with 56 characters each, fill with spaces
        self._lcd_characters = [' '] * 2
        self._lcd_overlay_characters = [' '] * 2

        for line in range(2):
            self._lcd_characters[line] = [' '] * 56
            self._lcd_overlay_characters[line] = [' '] * 56

        self._show_overlay = [False, False]

        self._log('Initialising MIDI ports...', True)
        self._midi_input_name = midi_input_name
        self._midi_output_name = midi_output_name
        self.midi = MidiConnection(self.callback_log, self.receive_midi)

        self.unset_interconnector()

        self.display_lcd_available = True
        self.automated_faders_available = True
        self.display_7seg_available = True
        self.display_timecode_available = True
        self.meter_bridge_available = True

        self.display_7seg_characters = []
        for counter in range(4):
            self.display_7seg_characters.append(' ')

        self.display_timecode_characters = []
        for counter in range(20):
            self.display_timecode_characters.append(' ')
    def __init__(self, midi_input_name, midi_output_name, callback_log):
        self.callback_log = callback_log

        # LCD has 2 rows with 56 characters each, fill with spaces
        self._lcd_characters = [' '] * 2
        self._lcd_overlay_characters = [' '] * 2

        for line in range(2):
            self._lcd_characters[line] = [' '] * 56
            self._lcd_overlay_characters[line] = [' '] * 56

        self._show_overlay = [False, False]

        self._log('Initialising MIDI ports...', True)
        self._midi_input_name = midi_input_name
        self._midi_output_name = midi_output_name
        self.midi = MidiConnection(self.callback_log, self.receive_midi)

        self.unset_interconnector()

        self.display_lcd_available = True
        self.automated_faders_available = True
        self.display_7seg_available = True
        self.display_timecode_available = True
        self.meter_bridge_available = True

        self.display_7seg_characters = []
        for counter in range(4):
            self.display_7seg_characters.append(' ')

        self.display_timecode_characters = []
        for counter in range(20):
            self.display_timecode_characters.append(' ')
Example #3
0
    def __init__(self, mcu_model_id, mcu_connection, version_number, \
                     midi_input_name, midi_output_name, callback_log):
        self._callback_log = callback_log

        self._log('Initialising MIDI ports...', True)
        self._midi_input_name = midi_input_name
        self._midi_output_name = midi_output_name
        self._midi = MidiConnection(callback_log, self.receive_midi)
        self._midi_channel = 0

        self.unset_hardware_controller()
        self._offline = True

        # Mackie Control model IDs:
        # * 0x10: Logic Control
        # * 0x11: Logic Control XT
        # * 0x14: Mackie Control
        # * 0x15: Mackie Control XT
        self._mcu_model_id = mcu_model_id

        # define connection type (among others, challenge-response)
        self._mcu_connection = mcu_connection

        serial_number = '_pyMCU_'
        self._serial_number_bytes = []
        for char in serial_number:
            self._serial_number_bytes.append(ord(char))

        challenge = 'test'
        self._challenge_bytes = []
        for char in challenge:
            self._challenge_bytes.append(ord(char))

        self._response_bytes = self._calculate_reponse_from_challenge( \
            self._challenge_bytes)

        # make sure that the version number consists of exactly 5
        # characters
        version_number = version_number.ljust(5)[0:5]

        self._version_number_bytes = []
        for char in version_number:
            self._version_number_bytes.append(ord(char))
Example #4
0
    def __init__(self, mcu_model_id, mcu_connection, version_number, \
                     midi_input_name, midi_output_name, callback_log):
        self._callback_log = callback_log

        self._log('Initialising MIDI ports...', True)
        self._midi_input_name = midi_input_name
        self._midi_output_name = midi_output_name
        self._midi = MidiConnection(callback_log, self.receive_midi)
        self._midi_channel = 0

        self.unset_hardware_controller()
        self._offline = True

        # Mackie Control model IDs:
        # * 0x10: Logic Control
        # * 0x11: Logic Control XT
        # * 0x14: Mackie Control
        # * 0x15: Mackie Control XT
        self._mcu_model_id = mcu_model_id

        # define connection type (among others, challenge-response)
        self._mcu_connection = mcu_connection

        serial_number = '_pyMCU_'
        self._serial_number_bytes = []
        for char in serial_number:
            self._serial_number_bytes.append(ord(char))

        challenge = 'test'
        self._challenge_bytes = []
        for char in challenge:
            self._challenge_bytes.append(ord(char))

        self._response_bytes = self._calculate_reponse_from_challenge( \
            self._challenge_bytes)

        # make sure that the version number consists of exactly 5
        # characters
        version_number = version_number.ljust(5)[0:5]

        self._version_number_bytes = []
        for char in version_number:
            self._version_number_bytes.append(ord(char))
class MidiControllerTemplate(object):

    MIDI_MANUFACTURER_ID = None
    MIDI_DEVICE_ID = None

    VPOT_MODE_SINGLE_DOT = 0x00
    VPOT_MODE_BOOST_CUT = 0x01
    VPOT_MODE_WRAP = 0x02
    VPOT_MODE_SPREAD = 0x03

    _LED_STATUS = {0x00: 'off', 0x01: 'flashing', 0x7F: 'on'}

    def __init__(self, midi_input_name, midi_output_name, callback_log):
        self.callback_log = callback_log

        # LCD has 2 rows with 56 characters each, fill with spaces
        self._lcd_characters = [' '] * 2
        self._lcd_overlay_characters = [' '] * 2

        for line in range(2):
            self._lcd_characters[line] = [' '] * 56
            self._lcd_overlay_characters[line] = [' '] * 56

        self._show_overlay = [False, False]

        self._log('Initialising MIDI ports...', True)
        self._midi_input_name = midi_input_name
        self._midi_output_name = midi_output_name
        self.midi = MidiConnection(self.callback_log, self.receive_midi)

        self.unset_interconnector()

        self.display_lcd_available = True
        self.automated_faders_available = True
        self.display_7seg_available = True
        self.display_timecode_available = True
        self.meter_bridge_available = True

        self.display_7seg_characters = []
        for counter in range(4):
            self.display_7seg_characters.append(' ')

        self.display_timecode_characters = []
        for counter in range(20):
            self.display_timecode_characters.append(' ')

    @staticmethod
    def get_usage_hint():
        return ''

    def _log(self, message, repaint=False):
        self.callback_log('[Controller Template]  ' + message, repaint)

    # --- initialisation ---

    def set_interconnector(self, host):
        self.interconnector = host

    def unset_interconnector(self):
        self.interconnector = None

    def connect(self):
        self._log('Opening MIDI ports...', True)
        self.midi.connect(self._midi_input_name, self._midi_output_name)

    def disconnect(self):
        self._log('Closing MIDI ports...')
        self.midi.disconnect()
        self._log('Disconnected.', True)

    def go_online(self):
        self._log('Mackie Host Control went online...', True)

    def go_offline(self):
        self._log('Mackie Host Control went offline...', True)

    # --- abilities of hardware controller ---

    def has_display_7seg(self):
        return self.display_7seg_available

    def has_display_lcd(self):
        return self.display_lcd_available

    def has_display_timecode(self):
        return self.display_timecode_available

    def has_automated_faders(self):
        return self.automated_faders_available

    def has_meter_bridge(self):
        return self.meter_bridge_available

    # --- MIDI processing ---

    def process_midi_input(self):
        self.midi.process_input_buffer()

    def receive_midi(self, status, message):
        message_string = ['status %02X: ' % status]
        for byte in message:
            message_string.append('%02X' % byte)
        self._log(' '.join(message_string))

    def send_midi_control_change(self, channel, cc_number, cc_value):
        self.midi.send_control_change(channel, cc_number, cc_value)

    def send_midi_sysex(self, data):
        assert (type(data) == types.ListType)

        header = []
        header.extend(self.MIDI_MANUFACTURER_ID)
        header.extend(self.MIDI_DEVICE_ID)

        self.midi.send_sysex(header, data)

    @staticmethod
    def get_preferred_midi_input():
        return ''

    @staticmethod
    def get_preferred_midi_output():
        return ''

    # --- registration of MIDI controls ---

    def register_control(self, mcu_command, midi_switch, midi_led=None):
        if midi_led:
            self.interconnector.register_control( \
                mcu_command, midi_switch, midi_led)
        else:
            self.interconnector.register_control( \
                mcu_command, midi_switch, midi_switch)

    def withdraw_control(self, midi_switch):
        self.interconnector.withdraw_control(midi_switch_cc)

    def withdraw_all_controls(self):
        self.interconnector.withdraw_all_controls()

    # --- handling of Mackie Control commands ---

    def set_lcd(self, position, hex_codes, update=True):
        for hex_code in hex_codes:
            # wrap display and determine position
            position %= 112
            (line, pos) = divmod(position, 56)

            # convert illegal characters to asterisk
            if (hex_code < 0x20) or (hex_code > 0x7F):
                self._lcd_characters[line][pos] = '*'
            else:
                self._lcd_characters[line][pos] = chr(hex_code)

            position += 1

        if update:
            self.update_lcd()

    def set_led(self, internal_id, led_status):
        pass

    def set_display_7seg(self, position, character_code):
        character = self._decode_7seg_character(character_code)
        position = 23 - (position * 2)

        self.display_7seg_characters[position - 1] = character[0]
        self.display_7seg_characters[position] = character[1]

        string_7seg = ''.join(self.display_7seg_characters)
        self._log('7 segment display NOT set to "%s".' % string_7seg)

    def _decode_7seg_character(self, character_code):
        if character_code >= 0x40:
            character_code = character_code - 0x40
            dot = '.'
        else:
            dot = ' '

        if character_code < 0x20:
            return (chr(character_code + 0x40), dot)
        else:
            return (chr(character_code), dot)

    def set_display_timecode(self, position, character_code):
        character = self._decode_7seg_character(character_code)
        position = 19 - (position * 2)

        self.display_timecode_characters[position - 1] = character[0]
        self.display_timecode_characters[position] = character[1]

        # please note that the logged timecode is not necessarily
        # correct: it will only be dumped when the display's last
        # character has been updated -- there may be other updates
        # still pending!
        if position == 19:
            string_timecode = ''.join(self.display_timecode_characters)
            self._log('timecode display NOT set to "%s".' % string_timecode)

    def set_peak_level(self, meter_id, meter_level):
        if meter_level == 0x0F:
            self._log('Meter #%d overload NOT cleared.' % meter_id)
        elif meter_level == 0x0F:
            self._log('Meter #%d NOT set to overload.' % meter_id)
        else:
            self._log('Meter #%d NOT set to %03d%%.' % \
                          (meter_id, meter_level * 10))

    def fader_moved(self, fader_id, fader_position):
        self._log('Hardware fader #%d NOT moved to position %04d.' % \
                      (fader_id, fader_position))


    def set_vpot_led_ring(self, vpot_id, vpot_center_led, vpot_mode, \
                              vpot_position):
        self._log('V-Pot #%d LED ring NOT set to position %02d (mode %d).' % \
                      (vpot_id, vpot_position, vpot_mode))

    def faders_to_minimum(self):
        self._log('Hardware faders NOT set to minimum.')

    def all_leds_off(self):
        self._log('Hardware LEDs NOT set to "off".')

    # --- LCD and menu handling

    def update_lcd(self):
        pass

    def get_lcd_characters(self, line):
        line %= 2

        if self._show_overlay[line]:
            return self._lcd_overlay_characters[line]
        else:
            return self._lcd_characters[line]

    def show_menu(self, line, menu_strings):
        assert (len(menu_strings) == 8)

        menu_string_temp = ''
        for menu_string in menu_strings:
            menu_string_temp += menu_string.center(7)[:7]

        menu_characters = list(menu_string_temp)
        self.show_overlay(line, menu_characters)

    def hide_menu(self, line):
        self.hide_overlay(line)

    def show_overlay(self, line, overlay_characters):
        line %= 2
        assert (len(overlay_characters) == 56)

        self._show_overlay[line] = True
        self._lcd_overlay_characters[line] = overlay_characters
        self.update_lcd()

    def hide_overlay(self, line):
        line %= 2

        self._show_overlay[line] = False
        self.update_lcd()
Example #6
0
    def __init__(self, parent=None):
        super(PythonMcu, self).__init__(parent)

        font = QFont()
        font.setStyleHint(QFont.TypeWriter, QFont.PreferAntialias)

        char_format = QTextCharFormat()
        char_format.setFontFamily(font.defaultFamily())
        text_width = QFontMetrics(char_format.font()).width('*') * 80

        # must be defined before starting the logger!
        self._edit_logger = QPlainTextEdit()
        self._edit_logger.setReadOnly(True)
        self._edit_logger.setCurrentCharFormat(char_format)
        self._edit_logger.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        self._edit_logger.setFixedWidth(text_width)

        # must be defined before reading the configuration file!
        self._edit_usage_hint = QPlainTextEdit()
        self._edit_usage_hint.setReadOnly(True)
        self._edit_usage_hint.setCurrentCharFormat(char_format)

        self.callback_log('')
        self.callback_log(configuration.get_full_description())
        self.callback_log('')
        self.callback_log('')
        self.callback_log('Version numbers')
        self.callback_log('===============')
        self.callback_log('Python:  %s (%s)' % ( \
                platform.python_version(), platform.python_implementation()))
        self.callback_log('PySide:  %s' % PySide.__version__)
        self.callback_log('pygame:  %s' % pygame.version.ver)
        self.callback_log('')
        self.callback_log('')

        # auto-scroll log window by setting cursor to end of document
        self._edit_logger.moveCursor(QTextCursor.End, QTextCursor.MoveAnchor)

        self._read_configuration()

        self._timer = None
        self._interconnector = None

        icon = self.style().standardIcon(QStyle.SP_TitleBarMenuButton)
        self.setWindowIcon(icon)

        mcu_model_ids = ['Logic Control', 'Logic Control XT', \
                              'Mackie Control', 'Mackie Control XT']

        hardware_controllers = [ \
            'Novation ZeRO SL MkII', \
            'Novation ZeRO SL MkII (MIDI)'
        ]

        # get version number of "Python MCU"
        version = configuration.get_application_information('version')
        self.setWindowTitle(configuration.get_version(True))

        # create layouts and add widgets
        self.layout = QHBoxLayout()
        self.setLayout(self.layout)

        self.layout_2 = QVBoxLayout()
        self.layout.addLayout(self.layout_2)

        self.frame_mcu = QFrame()
        self.frame_mcu.setFrameStyle(QFrame.Box)
        self.frame_mcu.setFrameShadow(QFrame.Sunken)
        self.layout_2.addWidget(self.frame_mcu)
        self.grid_layout_mcu = QGridLayout()
        self.frame_mcu.setLayout(self.grid_layout_mcu)

        self.frame_controller = QFrame()
        self.frame_controller.setFrameStyle(QFrame.Box)
        self.frame_controller.setFrameShadow(QFrame.Sunken)
        self.layout_2.addWidget(self.frame_controller)
        self.grid_layout_controller = QGridLayout()
        self.frame_controller.setLayout(self.grid_layout_controller)


        self._combo_mcu_model_id = self._create_combo_box( \
            self.grid_layout_mcu, self._mcu_emulated_model, \
                'Emulation:', mcu_model_ids)

        connection_types = [MackieHostControl.ASSUME_SUCCESSFUL_CONNECTION, \
                                MackieHostControl.CHALLENGE_RESPONSE, \
                                MackieHostControl.WAIT_FOR_MIDI_DATA]
        self._combo_mcu_connection = self._create_combo_box( \
            self.grid_layout_mcu, self._mcu_connection, \
                'Connection:', connection_types)

        self._combo_mcu_midi_input = self._create_combo_box( \
            self.grid_layout_mcu, self._mcu_midi_input, \
                'MIDI In:', MidiConnection.get_midi_inputs())

        self._combo_mcu_midi_output = self._create_combo_box( \
            self.grid_layout_mcu, self._mcu_midi_output, \
                'MIDI Out:', MidiConnection.get_midi_outputs())


        self._combo_hardware_controller = self._create_combo_box( \
            self.grid_layout_controller, self._hardware_controller, \
                'Controller:', hardware_controllers)

        self._combo_controller_midi_input = self._create_combo_box( \
            self.grid_layout_controller, self._controller_midi_input, \
                'MIDI In:', MidiConnection.get_midi_inputs())

        self._combo_controller_midi_output = self._create_combo_box( \
            self.grid_layout_controller, self._controller_midi_output, \
                'MIDI Out:', MidiConnection.get_midi_outputs())

        self.grid_layout_controller.addWidget( \
            self._edit_usage_hint, self.grid_layout_controller.rowCount(), \
                0, 1, 2)

        self.layout.addWidget(self._edit_logger)

        self.bottom_layout = QHBoxLayout()
        self.layout_2.addLayout(self.bottom_layout)

        self.button_start_stop = QPushButton('&Start')
        self.bottom_layout.addWidget(self.button_start_stop)
        self.button_start_stop.setDefault(True)
        self.button_start_stop.setFocus()
        self.button_start_stop.clicked.connect(self.interconnector_start_stop)

        self.button_close = QPushButton('&Close')
        self.bottom_layout.addWidget(self.button_close)
        self.button_close.clicked.connect(self.close_application)

        self.button_about = QPushButton('A&bout')
        self.bottom_layout.addWidget(self.button_about)
        self.button_about.clicked.connect(self.display_about)

        self._enable_controls(True)

        self._timer = QTimer(self)
        self._timer.setInterval(int(self._midi_latency))
        self._timer.timeout.connect(self.process_midi_input)
 def get_preferred_midi_output():
     return MidiConnection.get_default_midi_output()
Example #8
0
    def __init__(self, parent=None):
        super(PythonMcu, self).__init__(parent)

        font = QFont()
        font.setStyleHint(QFont.TypeWriter, QFont.PreferAntialias)

        char_format = QTextCharFormat()
        char_format.setFontFamily(font.defaultFamily())
        text_width = QFontMetrics(char_format.font()).width('*') * 80

        # must be defined before starting the logger!
        self._edit_logger = QPlainTextEdit()
        self._edit_logger.setReadOnly(True)
        self._edit_logger.setCurrentCharFormat(char_format)
        self._edit_logger.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        self._edit_logger.setFixedWidth(text_width)

        # must be defined before reading the configuration file!
        self._edit_usage_hint = QPlainTextEdit()
        self._edit_usage_hint.setReadOnly(True)
        self._edit_usage_hint.setCurrentCharFormat(char_format)

        self.callback_log('')
        self.callback_log(configuration.get_full_description())
        self.callback_log('')
        self.callback_log('')
        self.callback_log('Version numbers')
        self.callback_log('===============')
        self.callback_log('Python:  %s (%s)' % ( \
                platform.python_version(), platform.python_implementation()))
        self.callback_log('PySide:  %s' % PySide.__version__)
        self.callback_log('pygame:  %s' % pygame.version.ver)
        self.callback_log('')
        self.callback_log('')

        # auto-scroll log window by setting cursor to end of document
        self._edit_logger.moveCursor(QTextCursor.End, QTextCursor.MoveAnchor)

        self._read_configuration()

        self._timer = None
        self._interconnector = None

        icon = self.style().standardIcon(QStyle.SP_TitleBarMenuButton)
        self.setWindowIcon(icon)

        mcu_model_ids = ['Logic Control', 'Logic Control XT', \
                              'Mackie Control', 'Mackie Control XT']

        hardware_controllers = [ \
            'Novation ZeRO SL MkII', \
            'Novation ZeRO SL MkII (MIDI)'
        ]

        # get version number of "Python MCU"
        version = configuration.get_application_information('version')
        self.setWindowTitle(configuration.get_version(True))

        # create layouts and add widgets
        self.layout = QHBoxLayout()
        self.setLayout(self.layout)

        self.layout_2 = QVBoxLayout()
        self.layout.addLayout(self.layout_2)

        self.frame_mcu = QFrame()
        self.frame_mcu.setFrameStyle(QFrame.Box)
        self.frame_mcu.setFrameShadow(QFrame.Sunken)
        self.layout_2.addWidget(self.frame_mcu)
        self.grid_layout_mcu = QGridLayout()
        self.frame_mcu.setLayout(self.grid_layout_mcu)

        self.frame_controller = QFrame()
        self.frame_controller.setFrameStyle(QFrame.Box)
        self.frame_controller.setFrameShadow(QFrame.Sunken)
        self.layout_2.addWidget(self.frame_controller)
        self.grid_layout_controller = QGridLayout()
        self.frame_controller.setLayout(self.grid_layout_controller)


        self._combo_mcu_model_id = self._create_combo_box( \
            self.grid_layout_mcu, self._mcu_emulated_model, \
                'Emulation:', mcu_model_ids)

        connection_types = [MackieHostControl.ASSUME_SUCCESSFUL_CONNECTION, \
                                MackieHostControl.CHALLENGE_RESPONSE, \
                                MackieHostControl.WAIT_FOR_MIDI_DATA]
        self._combo_mcu_connection = self._create_combo_box( \
            self.grid_layout_mcu, self._mcu_connection, \
                'Connection:', connection_types)

        self._combo_mcu_midi_input = self._create_combo_box( \
            self.grid_layout_mcu, self._mcu_midi_input, \
                'MIDI In:', MidiConnection.get_midi_inputs())

        self._combo_mcu_midi_output = self._create_combo_box( \
            self.grid_layout_mcu, self._mcu_midi_output, \
                'MIDI Out:', MidiConnection.get_midi_outputs())


        self._combo_hardware_controller = self._create_combo_box( \
            self.grid_layout_controller, self._hardware_controller, \
                'Controller:', hardware_controllers)

        self._combo_controller_midi_input = self._create_combo_box( \
            self.grid_layout_controller, self._controller_midi_input, \
                'MIDI In:', MidiConnection.get_midi_inputs())

        self._combo_controller_midi_output = self._create_combo_box( \
            self.grid_layout_controller, self._controller_midi_output, \
                'MIDI Out:', MidiConnection.get_midi_outputs())

        self.grid_layout_controller.addWidget( \
            self._edit_usage_hint, self.grid_layout_controller.rowCount(), \
                0, 1, 2)

        self.layout.addWidget(self._edit_logger)

        self.bottom_layout = QHBoxLayout()
        self.layout_2.addLayout(self.bottom_layout)

        self.button_start_stop = QPushButton('&Start')
        self.bottom_layout.addWidget(self.button_start_stop)
        self.button_start_stop.setDefault(True)
        self.button_start_stop.setFocus()
        self.button_start_stop.clicked.connect(self.interconnector_start_stop)

        self.button_close = QPushButton('&Close')
        self.bottom_layout.addWidget(self.button_close)
        self.button_close.clicked.connect(self.close_application)

        self.button_about = QPushButton('A&bout')
        self.bottom_layout.addWidget(self.button_about)
        self.button_about.clicked.connect(self.display_about)

        self._enable_controls(True)

        self._timer = QTimer(self)
        self._timer.setInterval(int(self._midi_latency))
        self._timer.timeout.connect(self.process_midi_input)
Example #9
0
class MackieHostControl:
    __module__ = __name__
    __doc__ = 'Python wrapper for Mackie Host Control'

    ASSUME_SUCCESSFUL_CONNECTION = 'Assume successful connection'
    CHALLENGE_RESPONSE = 'Challenge / Response'
    WAIT_FOR_MIDI_DATA = 'Wait for MIDI data'

    SWITCH_RELEASED = 0
    SWITCH_PRESSED = 1
    SWITCH_PRESSED_RELEASED = 2

    VPOT_CLOCKWISE = 0
    VPOT_COUNTER_CLOCKWISE = 1

    _LED_SWITCH_CHANNEL_RECORD_READY = 0x00
    _LED_SWITCH_CHANNEL_SOLO = 0x08

    _LED_SWITCH_CHANNEL_MUTE = 0x10
    _LED_SWITCH_CHANNEL_SELECT = 0x18
    _LED_SWITCH_CHANNEL_VSELECT = 0x20
    _LED_SWITCH_ASSIGNMENT_TRACK = 0x28
    _LED_SWITCH_ASSIGNMENT_SEND = 0x29
    _LED_SWITCH_ASSIGNMENT_PAN_SURROUND = 0x2A
    _LED_SWITCH_ASSIGNMENT_PLUG_IN = 0x2B

    _LED_SWITCH_ASSIGNMENT_EQ = 0x2C
    _LED_SWITCH_ASSIGNMENT_INSTRUMENT = 0x2D
    _SWITCH_FADER_BANKS_BANK_LEFT = 0x2E
    _SWITCH_FADER_BANKS_BANK_RIGHT = 0x2F
    _SWITCH_FADER_BANKS_CHANNEL_LEFT = 0x30
    _SWITCH_FADER_BANKS_CHANNEL_RIGHT = 0x31
    _LED_SWITCH_FLIP = 0x32
    _LED_SWITCH_GLOBAL_VIEW = 0x33
    _SWITCH_NAME_VALUE = 0x34
    _SWITCH_SMPTE_BEATS = 0x35
    _SWITCH_CHANNEL_FUNCTION = 0x36
    _SWITCH_GLOBAL_VIEW_MIDI_TRACKS = 0x3E
    _SWITCH_GLOBAL_VIEW_INPUTS = 0x3F
    _SWITCH_GLOBAL_VIEW_AUDIO_TRACKS = 0x40
    _SWITCH_GLOBAL_VIEW_AUDIO_INSTRUMENTS = 0x41
    _SWITCH_GLOBAL_VIEW_AUX = 0x42
    _SWITCH_GLOBAL_VIEW_BUSSES = 0x43
    _SWITCH_GLOBAL_VIEW_OUTPUTS = 0x44
    _SWITCH_GLOBAL_VIEW_USER = 0x45
    _SWITCH_SHIFT = 0x46
    _SWITCH_OPTION = 0x47
    _SWITCH_CONTROL = 0x48
    _SWITCH_COMMAND_ALT = 0x49
    _LED_SWITCH_AUTOMATION_READ_OFF = 0x4A

    _LED_SWITCH_AUTOMATION_WRITE = 0x4B
    _LED_SWITCH_AUTOMATION_TRIM = 0x4C
    _LED_SWITCH_AUTOMATION_TOUCH = 0x4D
    _LED_SWITCH_AUTOMATION_LATCH = 0x4E
    _LED_SWITCH_GROUP = 0x4F
    _LED_SWITCH_UTILITIES_SAVE = 0x50
    _LED_SWITCH_UTILITIES_UNDO = 0x51
    _SWITCH_UTILITIES_CANCEL = 0x52
    _SWITCH_UTILITIES_ENTER = 0x53
    _LED_SWITCH_MARKER = 0x54
    _LED_SWITCH_NUDGE = 0x55
    _LED_SWITCH_CYCLE = 0x56
    _LED_SWITCH_DROP = 0x57
    _LED_SWITCH_REPLACE = 0x58
    _LED_SWITCH_CLICK = 0x59
    _LED_SWITCH_SOLO = 0x5A
    _LED_SWITCH_REWIND = 0x5B
    _LED_SWITCH_FAST_FORWARD = 0x5C
    _LED_SWITCH_STOP = 0x5D
    _LED_SWITCH_PLAY = 0x5E
    _LED_SWITCH_RECORD = 0x5F
    _SWITCH_CURSOR_UP = 0x60
    _SWITCH_CURSOR_DOWN = 0x61
    _SWITCH_CURSOR_LEFT = 0x62
    _SWITCH_CURSOR_RIGHT = 0x63
    _LED_SWITCH_ZOOM = 0x64
    _LED_SWITCH_SCRUB = 0x65
    _SWITCH_USER_SWITCH_A = 0x66
    _SWITCH_USER_SWITCH_B = 0x67
    _SWITCH_CHANNEL_FADER_TOUCH = 0x68

    _SWITCH_MASTER_FADER_TOUCH = 0x70

    _LED_SMPTE = 0x71
    _LED_BEATS = 0x72
    _LED_RUDE_SOLO = 0x73
    _LED_RELAY_CLICK = 0x76


    def __init__(self, mcu_model_id, mcu_connection, version_number, \
                     midi_input_name, midi_output_name, callback_log):
        self._callback_log = callback_log

        self._log('Initialising MIDI ports...', True)
        self._midi_input_name = midi_input_name
        self._midi_output_name = midi_output_name
        self._midi = MidiConnection(callback_log, self.receive_midi)
        self._midi_channel = 0

        self.unset_hardware_controller()
        self._offline = True

        # Mackie Control model IDs:
        # * 0x10: Logic Control
        # * 0x11: Logic Control XT
        # * 0x14: Mackie Control
        # * 0x15: Mackie Control XT
        self._mcu_model_id = mcu_model_id

        # define connection type (among others, challenge-response)
        self._mcu_connection = mcu_connection

        serial_number = '_pyMCU_'
        self._serial_number_bytes = []
        for char in serial_number:
            self._serial_number_bytes.append(ord(char))

        challenge = 'test'
        self._challenge_bytes = []
        for char in challenge:
            self._challenge_bytes.append(ord(char))

        self._response_bytes = self._calculate_reponse_from_challenge( \
            self._challenge_bytes)

        # make sure that the version number consists of exactly 5
        # characters
        version_number = version_number.ljust(5)[0:5]

        self._version_number_bytes = []
        for char in version_number:
            self._version_number_bytes.append(ord(char))


    def _log(self, message, repaint=False):
        self._callback_log('[Mackie Host Control  ]  ' + message, repaint)


    # --- initialisation ---

    def set_hardware_controller(self, controller):
        self._hardware_controller = controller

        self._display_lcd_available = \
            self._hardware_controller.has_display_lcd()
        self._automated_faders_available = \
            self._hardware_controller.has_automated_faders()
        self._display_7seg_available = \
            self._hardware_controller.has_display_7seg()
        self._display_timecode_available = \
            self._hardware_controller.has_display_timecode()
        self._meter_bridge_available = \
            self._hardware_controller.has_meter_bridge()


    def unset_hardware_controller(self):
        self._hardware_controller = None

        self._display_lcd_available = False
        self._automated_faders_available = False
        self._display_7seg_available = False
        self._display_timecode_available = False
        self._meter_bridge_available = False


    def connect(self):
        self._log('Opening MIDI ports...')
        self._midi.connect(self._midi_input_name, self._midi_output_name)

        if self._mcu_connection == self.CHALLENGE_RESPONSE:
            self._log('Sending "Host Connection Query"...', True)

            sysex_message = [0x01]
            sysex_message.extend(self._serial_number_bytes)
            sysex_message.extend(self._challenge_bytes)
            self.send_midi_sysex(sysex_message)

            return
        else:
            # let's make sure the MIDI input buffer is empty
            self._midi.process_input_buffer(use_callback=False)

            if self._mcu_connection == self.WAIT_FOR_MIDI_DATA:
                self._log('Waiting for MIDI input from host...', True)

                # wait for some MIDI input from the host (all data are
                # left in the MIDI input buffer!)
                while self._midi.buffer_is_empty():
                    time.sleep(0.1)

            self.go_online()


    def disconnect(self):
        self._log('Disconnecting...', True)
        self.go_offline()

        self._log('Closing MIDI ports...', True)
        self._midi.disconnect()


    def go_online(self):
        self._offline = False

        if self._hardware_controller:
            self._hardware_controller.go_online()

        self._log('Online.', True)


    def go_offline(self):
        self._offline = True

        if self._hardware_controller:
            self._hardware_controller.go_offline()

        if self._mcu_connection == self.CHALLENGE_RESPONSE:
            self._log('Sending "Host Connection Error"...')

            sysex_message = [0x04]
            sysex_message.extend(self._serial_number_bytes)
            self.send_midi_sysex(sysex_message)

        self._log('Offline.', True)


    def is_offline(self):
        return self._offline


    def _calculate_reponse_from_challenge(self, challenge_bytes):
        response_bytes = []

        response_bytes.append( \
            0x7F & (challenge_bytes[0] + (challenge_bytes[1] ^ 0x0A) - \
                        challenge_bytes[3]))
        response_bytes.append( \
            0x7F & ((challenge_bytes[2] >> 4) ^ \
                        (challenge_bytes[0] + challenge_bytes[3])))
        response_bytes.append( \
            0x7F & (challenge_bytes[3] - (challenge_bytes[2] << 2) ^ \
                        (challenge_bytes[0] | challenge_bytes[1])))
        response_bytes.append( \
            0x7F & (challenge_bytes[1] - challenge_bytes[2] + \
                        (0xF0 ^ (challenge_bytes[3] << 4))))

        return response_bytes


    # --- static methods ---

    @staticmethod
    def get_mcu_model_from_id(id):
        if id == 0x10:
            return 'Logic Control'
        elif id == 0x11:
            return 'Logic Control XT'
        elif id == 0x14:
            return 'Mackie Control'
        elif id == 0x15:
            return 'Mackie Control XT'
        else:
            return None


    @staticmethod
    def get_mcu_id_from_model(model):
        if model == 'Logic Control':
            return 0x10
        elif model == 'Logic Control XT':
            return 0x11
        elif model == 'Mackie Control':
            return 0x14
        elif model == 'Mackie Control XT':
            return 0x15
        else:
            return None


    @staticmethod
    def get_preferred_mcu_model():
        return 'Mackie Control'


    @staticmethod
    def get_preferred_mcu_model_id():
        return MackieHostControl.get_mcu_id_from_model( \
            MackieHostControl.get_preferred_mcu_model())


    @staticmethod
    def get_preferred_midi_input():
        if os.name == 'nt':
            return 'In From MIDI Yoke:  2'
        else:
            return 'mcu'


    @staticmethod
    def get_preferred_midi_output():
        if os.name == 'nt':
            return 'Out To MIDI Yoke:  1'
        else:
            return 'mcu'


    # --- MIDI processing ---

    def process_midi_input(self):
        self._midi.process_input_buffer()


    def receive_midi(self, status, message):
        if status == MidiConnection.SYSTEM_MESSAGE and message[0:5] == \
                [0xF0, 0x00, 0x00, 0x66, self._mcu_model_id]:
            if message[5:] == [0x00, 0xF7]:
                self._log('Received "Device Query".')
                self._log('Sending "Host Connection Query"...', True)

                sysex_message = [0x01]
                sysex_message.extend(self._serial_number_bytes)
                sysex_message.extend(self._challenge_bytes)
                self.send_midi_sysex(sysex_message)

                return
            elif message[5] == 0x02:
                self._log('Received "Host Connection Reply".')
                if (message[6:13] == self._serial_number_bytes) and \
                        (message[13:17] == self._response_bytes):
                    self._log('Sending "Host Connection Confirmation"...', \
                                      True)

                    sysex_message = [0x03]
                    sysex_message.extend(self._serial_number_bytes)
                    self.send_midi_sysex(sysex_message)

                    self.go_online()
                else:
                    self._log('Sending "Host Connection Error"...')

                    sysex_message = [0x04]
                    sysex_message.extend(self._serial_number_bytes)
                    self.send_midi_sysex(sysex_message)

                    self._log('Sending "Host Connection Query"...', True)

                    sysex_message = [0x01]
                    sysex_message.extend(self._serial_number_bytes)
                    sysex_message.extend(self._challenge_bytes)
                    self.send_midi_sysex(sysex_message)

                return
            elif message[5:] == [0x13, 0x00, 0x0F7]:
                self._log('Received "Version Request".')
                self._log('Sending "Version Reply"...', True)

                sysex_message = [0x14]
                sysex_message.extend(self._version_number_bytes)
                self.send_midi_sysex(sysex_message)

                return
            elif message[5:] == [0x0F, 0x7F, 0x0F7]:
                self._log('Received "Go Offline".', True)
                self.go_offline()

                return
            elif message[5:] == [0x61, 0x0F7]:
                self._log('Received "Faders To Minimum".', True)
                self.faders_to_minimum()

                return
            elif message[5:] == [0x62, 0x0F7]:
                self._log('Received "All LEDs Off".', True)
                self.all_leds_off()

                return
            elif message[5:] == [0x63, 0x0F7]:
                self._log('Received "Reset".', True)
                self.reset()

                return

        # do not make this an "elif" clause, otherwise some MIDI SysEx
        # messages won't get processed!
        if not self.is_offline():
            if status == MidiConnection.SYSTEM_MESSAGE and message[0:5] == \
                    [0xF0, 0x00, 0x00, 0x66, self._mcu_model_id]:
                if message[5] == 0x12:
                    if self._display_lcd_available:
                        position = message[6]
                        hex_codes = message[7:-1]

                        self._hardware_controller.set_lcd(position, hex_codes)
            elif status == MidiConnection.PITCH_WHEEL_CHANGE:
                if self._automated_faders_available:
                    fader_id = message[0] & 0x0F
                    fader_position = (message[1] + (message[2] << 7)) >> 4
                    self._hardware_controller.fader_moved( \
                        fader_id, fader_position)
            elif status == MidiConnection.NOTE_ON_EVENT:
                led_id = message[1]
                led_status = 0  # off

                if message[2] == 0x7F:
                    led_status = 1  # on
                elif message[2] == 0x01:
                    led_status = 2  # flashing

                self._set_led(led_id, led_status)
            elif (status == MidiConnection.CONTROL_CHANGE) and \
                    ((message[1] & 0xF0) == 0x30):
                vpot_id = message[1] & 0x0F
                vpot_center_led = (message[2] & 0x40) >> 7
                vpot_mode = (message[2] & 0x30) >> 4
                vpot_position = message[2] & 0x0F
                self._hardware_controller.set_vpot_led_ring( \
                    vpot_id, vpot_center_led, vpot_mode, vpot_position)
            elif (status == MidiConnection.CONTROL_CHANGE) and \
                    ((message[1] & 0xF0) == 0x40):
                position = message[1] & 0x0F
                character_code = message[2]

                if (position < 10):
                    if self._display_timecode_available:
                        self._hardware_controller.set_display_timecode( \
                            position, character_code)
                elif self._display_7seg_available:
                    self._hardware_controller.set_display_7seg( \
                        position, character_code)
            elif status == MidiConnection.CHANNEL_PRESSURE:
                if self._meter_bridge_available:
                    meter_id = (message[1] & 0x70) >> 4
                    meter_level = message[1] & 0x0F
                    self._hardware_controller.set_peak_level( \
                        meter_id, meter_level)
            else:
                output = 'status %02X: ' % status
                for byte in message:
                    output += '%02X ' % byte

                self._log(output.strip())
        else:
            output = 'status %02X: ' % status
            for byte in message:
                output += '%02X ' % byte

            self._log(output.strip())


    def send_midi_sysex(self, data):
        assert(type(data) == types.ListType)

        header = [0x00, 0x00, 0x66, self._mcu_model_id]

        # leading 0xF0 and trailing 0xF7 are added by "MidiConnection"
        # class method
        self._midi.send_sysex(header, data)


    # --- commands from hardware control ---

    def move_vpot(self, vpot_id, direction, number_of_ticks):
        if self.is_offline():
            return

        vpot_movement = number_of_ticks
        if direction == self.VPOT_COUNTER_CLOCKWISE:
            vpot_movement = vpot_movement + 0x40

        self._midi.send_control_change( \
            self._midi_channel, 0x10 + vpot_id, vpot_movement)


    def move_vpot_raw(self, vpot_id, vpot_movement):
        if self.is_offline():
            return

        self._midi.send_control_change( \
            self._midi_channel, 0x10 + vpot_id, vpot_movement)


    def move_fader(self, fader_id, fader_value):
        if self.is_offline():
            return

        self._midi.send_pitch_wheel_change(fader_id, fader_value)


    def move_fader_7bit(self, fader_id, fader_value):
        if self.is_offline():
            return

        self._midi.send_pitch_wheel_change_7bit(fader_id, fader_value)


    def _key_pressed(self, status, switch_id):
        if self.is_offline():
            return

        if status == self.SWITCH_RELEASED:
            self._midi.send_note_on(switch_id, 0x00)
        elif status == self.SWITCH_PRESSED:
            self._midi.send_note_on(switch_id, 0x7F)
        elif status == self.SWITCH_PRESSED_RELEASED:
            self._midi.send_note_on(switch_id, 0x7F)
            self._midi.send_note_on(switch_id, 0x00)
        else:
            self._log( \
                'Illegal key press status 0x%02X on switch 0x%02X detected!' % \
                    (status, switch_id))


    def keypress_record_ready_channel(self, channel, status):
        # channel: 1 - 8
        self._key_pressed( \
            status, self._LED_SWITCH_CHANNEL_RECORD_READY + channel - 1)


    def keypress_record_ready_channel_1(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_RECORD_READY)


    def keypress_record_ready_channel_2(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_RECORD_READY + 1)


    def keypress_record_ready_channel_3(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_RECORD_READY + 2)


    def keypress_record_ready_channel_4(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_RECORD_READY + 3)


    def keypress_record_ready_channel_5(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_RECORD_READY + 4)


    def keypress_record_ready_channel_6(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_RECORD_READY + 5)


    def keypress_record_ready_channel_7(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_RECORD_READY + 6)


    def keypress_record_ready_channel_8(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_RECORD_READY + 7)


    def keypress_solo_channel(self, channel, status):
        # channel: 1 - 8
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SOLO + channel - 1)


    def keypress_solo_channel_1(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SOLO)


    def keypress_solo_channel_2(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SOLO + 1)


    def keypress_solo_channel_3(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SOLO + 2)


    def keypress_solo_channel_4(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SOLO + 3)


    def keypress_solo_channel_5(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SOLO + 4)


    def keypress_solo_channel_6(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SOLO + 5)


    def keypress_solo_channel_7(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SOLO + 6)


    def keypress_solo_channel_8(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SOLO + 7)


    def keypress_mute_channel(self, channel, status):
        # channel: 1 - 8
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_MUTE + channel - 1)


    def keypress_mute_channel_1(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_MUTE)


    def keypress_mute_channel_2(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_MUTE + 1)


    def keypress_mute_channel_3(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_MUTE + 2)


    def keypress_mute_channel_4(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_MUTE + 3)


    def keypress_mute_channel_5(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_MUTE + 4)


    def keypress_mute_channel_6(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_MUTE + 5)


    def keypress_mute_channel_7(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_MUTE + 6)


    def keypress_mute_channel_8(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_MUTE + 7)


    def keypress_select_channel(self, channel, status):
        # channel: 1 - 8
        self._key_pressed( \
            status, self._LED_SWITCH_CHANNEL_SELECT + channel - 1)


    def keypress_select_channel_1(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SELECT)


    def keypress_select_channel_2(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SELECT + 1)


    def keypress_select_channel_3(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SELECT + 2)


    def keypress_select_channel_4(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SELECT + 3)


    def keypress_select_channel_5(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SELECT + 4)


    def keypress_select_channel_6(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SELECT + 5)


    def keypress_select_channel_7(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SELECT + 6)


    def keypress_select_channel_8(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SELECT + 7)


    def keypress_vselect_channel(self, channel, status):
        # channel: 1 - 8
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_VSELECT + \
                              channel - 1)


    def keypress_vselect_channel_1(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_VSELECT)


    def keypress_vselect_channel_2(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_VSELECT + 1)


    def keypress_vselect_channel_3(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_VSELECT + 2)


    def keypress_vselect_channel_4(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_VSELECT + 3)


    def keypress_vselect_channel_5(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_VSELECT + 4)


    def keypress_vselect_channel_6(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_VSELECT + 5)


    def keypress_vselect_channel_7(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_VSELECT + 6)


    def keypress_vselect_channel_8(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_VSELECT + 7)


    def keypress_function_channel(self, channel, status):
        # channel: 1 - 8
        self._key_pressed(status, self._SWITCH_CHANNEL_FUNCTION + channel - 1)


    def keypress_function_channel_1(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FUNCTION)


    def keypress_function_channel_2(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FUNCTION + 1)


    def keypress_function_channel_3(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FUNCTION + 2)


    def keypress_function_channel_4(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FUNCTION + 3)


    def keypress_function_channel_5(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FUNCTION + 4)


    def keypress_function_channel_6(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FUNCTION + 5)


    def keypress_function_channel_7(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FUNCTION + 6)


    def keypress_function_channel_8(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FUNCTION + 7)


    def keypress_assignment_track(self, status):
        self._key_pressed(status, self._LED_SWITCH_ASSIGNMENT_TRACK)


    def keypress_assignment_send(self, status):
        self._key_pressed(status, self._LED_SWITCH_ASSIGNMENT_SEND)


    def keypress_assignment_pan_surround(self, status):
        self._key_pressed(status, self._LED_SWITCH_ASSIGNMENT_PAN_SURROUND)


    def keypress_assignment_plug_in(self, status):
        self._key_pressed(status, self._LED_SWITCH_ASSIGNMENT_PLUG_IN)


    def keypress_assignment_eq(self, status):
        self._key_pressed(status, self._LED_SWITCH_ASSIGNMENT_EQ)


    def keypress_assignment_instrument(self, status):
        self._key_pressed(status, self._LED_SWITCH_ASSIGNMENT_INSTRUMENT)


    def keypress_fader_banks_bank_left(self, status):
        self._key_pressed(status, self._SWITCH_FADER_BANKS_BANK_LEFT)


    def keypress_fader_banks_bank_right(self, status):
        self._key_pressed(status, self._SWITCH_FADER_BANKS_BANK_RIGHT)


    def keypress_fader_banks_channel_left(self, status):
        self._key_pressed(status, self._SWITCH_FADER_BANKS_CHANNEL_LEFT)


    def keypress_fader_banks_channel_right(self, status):
        self._key_pressed(status, self._SWITCH_FADER_BANKS_CHANNEL_RIGHT)


    def keypress_flip(self, status):
        self._key_pressed(status, self._LED_SWITCH_FLIP)


    def keypress_global_view(self, status):
        self._key_pressed(status, self._LED_SWITCH_GLOBAL_VIEW)


    def keypress_name_value(self, status):
        self._key_pressed(status, self._SWITCH_NAME_VALUE)


    def keypress_smpte_beats(self, status):
        self._key_pressed(status, self._SWITCH_SMPTE_BEATS)


    def keypress_global_view_midi_tracks(self, status):
        self._key_pressed(status, self._SWITCH_GLOBAL_VIEW_MIDI_TRACKS)


    def keypress_global_view_inputs(self, status):
        self._key_pressed(status, self._SWITCH_GLOBAL_VIEW_INPUTS)


    def keypress_global_view_audio_tracks(self, status):
        self._key_pressed(status, self._SWITCH_GLOBAL_VIEW_AUDIO_TRACKS)


    def keypress_global_view_audio_instruments(self, status):
        self._key_pressed(status, self._SWITCH_GLOBAL_VIEW_AUDIO_INSTRUMENTS)


    def keypress_global_view_aux(self, status):
        self._key_pressed(status, self._SWITCH_GLOBAL_VIEW_AUX)


    def keypress_global_view_busses(self, status):
        self._key_pressed(status, self._SWITCH_GLOBAL_VIEW_BUSSES)


    def keypress_global_view_outputs(self, status):
        self._key_pressed(status, self._SWITCH_GLOBAL_VIEW_OUTPUTS)


    def keypress_global_view_user(self, status):
        self._key_pressed(status, self._SWITCH_GLOBAL_VIEW_USER)


    def keypress_shift(self, status):
        self._key_pressed(status, self._SWITCH_SHIFT)


    def keypress_option(self, status):
        self._key_pressed(status, self._SWITCH_OPTION)


    def keypress_control(self, status):
        self._key_pressed(status, self._SWITCH_CONTROL)


    def keypress_command_alt(self, status):
        self._key_pressed(status, self._SWITCH_COMMAND_ALT)


    def keypress_automation_read_off(self, status):
        self._key_pressed(status, self._LED_SWITCH_AUTOMATION_READ_OFF)


    def keypress_automation_write(self, status):
        self._key_pressed(status, self._LED_SWITCH_AUTOMATION_WRITE)


    def keypress_automation_trim(self, status):
        self._key_pressed(status, self._LED_SWITCH_AUTOMATION_TRIM)


    def keypress_automation_touch(self, status):
        self._key_pressed(status, self._LED_SWITCH_AUTOMATION_TOUCH)


    def keypress_automation_latch(self, status):
        self._key_pressed(status, self._LED_SWITCH_AUTOMATION_LATCH)


    def keypress_group(self, status):
        self._key_pressed(status, self._LED_SWITCH_GROUP)


    def keypress_utilities_save(self, status):
        self._key_pressed(status, self._LED_SWITCH_UTILITIES_SAVE)


    def keypress_utilities_undo(self, status):
        self._key_pressed(status, self._LED_SWITCH_UTILITIES_UNDO)


    def keypress_utilities_cancel(self, status):
        self._key_pressed(status, self._SWITCH_UTILITIES_CANCEL)


    def keypress_utilities_enter(self, status):
        self._key_pressed(status, self._SWITCH_UTILITIES_ENTER)


    def keypress_marker(self, status):
        self._key_pressed(status, self._LED_SWITCH_MARKER)


    def keypress_nudge(self, status):
        self._key_pressed(status, self._LED_SWITCH_NUDGE)


    def keypress_cycle(self, status):
        self._key_pressed(status, self._LED_SWITCH_CYCLE)


    def keypress_drop(self, status):
        self._key_pressed(status, self._LED_SWITCH_DROP)


    def keypress_replace(self, status):
        self._key_pressed(status, self._LED_SWITCH_REPLACE)


    def keypress_click(self, status):
        self._key_pressed(status, self._LED_SWITCH_CLICK)


    def keypress_solo(self, status):
        self._key_pressed(status, self._LED_SWITCH_SOLO)


    def keypress_rewind(self, status):
        self._key_pressed(status, self._LED_SWITCH_REWIND)


    def keypress_fast_forward(self, status):
        self._key_pressed(status, self._LED_SWITCH_FAST_FORWARD)


    def keypress_stop(self, status):
        self._key_pressed(status, self._LED_SWITCH_STOP)


    def keypress_play(self, status):
        self._key_pressed(status, self._LED_SWITCH_PLAY)


    def keypress_record(self, status):
        self._key_pressed(status, self._LED_SWITCH_RECORD)


    def keypress_cursor_up(self, status):
        self._key_pressed(status, self._SWITCH_CURSOR_UP)


    def keypress_cursor_down(self, status):
        self._key_pressed(status, self._SWITCH_CURSOR_DOWN)


    def keypress_cursor_left(self, status):
        self._key_pressed(status, self._SWITCH_CURSOR_LEFT)


    def keypress_cursor_right(self, status):
        self._key_pressed(status, self._SWITCH_CURSOR_RIGHT)


    def keypress_zoom(self, status):
        self._key_pressed(status, self._LED_SWITCH_ZOOM)


    def keypress_scrub(self, status):
        self._key_pressed(status, self._LED_SWITCH_SCRUB)


    def keypress_user_switch(self, switch_number, status):
        # switch_number: 1 - 2
        if switch_number == 1:
            self._key_pressed(status, self._SWITCH_USER_SWITCH_A)
        else:
            self._key_pressed(status, self._SWITCH_USER_SWITCH_B)


    def keypress_user_switch_1(self, status):
        self._key_pressed(status, self._SWITCH_USER_SWITCH_A)


    def keypress_user_switch_2(self, status):
        self._key_pressed(status, self._SWITCH_USER_SWITCH_B)


    def keypress_fader_touch_channel(self, channel, status):
        # channel: 1 - 8
        self._key_pressed( \
            status, self._SWITCH_CHANNEL_FADER_TOUCH + channel - 1)


    def keypress_fader_touch_channel_1(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FADER_TOUCH)


    def keypress_fader_touch_channel_2(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FADER_TOUCH + 1)


    def keypress_fader_touch_channel_3(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FADER_TOUCH + 2)


    def keypress_fader_touch_channel_4(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FADER_TOUCH + 3)


    def keypress_fader_touch_channel_5(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FADER_TOUCH + 4)


    def keypress_fader_touch_channel_6(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FADER_TOUCH + 5)


    def keypress_fader_touch_channel_7(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FADER_TOUCH + 6)


    def keypress_fader_touch_channel_8(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FADER_TOUCH + 7)


    def keypress_fader_touch_master(self, status):
        self._key_pressed(status, self._SWITCH_MASTER_FADER_TOUCH)


    # --- commands from Mackie Control host ---

    def _set_led(self, id, status):
        if self.is_offline():
            return

        selector = {
            self._LED_SWITCH_CHANNEL_RECORD_READY: \
                'self._hardware_controller.set_led_channel_record_ready(0, status)',
            self._LED_SWITCH_CHANNEL_RECORD_READY + 1: \
                'self._hardware_controller.set_led_channel_record_ready(1, status)',
            self._LED_SWITCH_CHANNEL_RECORD_READY + 2: \
                'self._hardware_controller.set_led_channel_record_ready(2, status)',
            self._LED_SWITCH_CHANNEL_RECORD_READY + 3: \
                'self._hardware_controller.set_led_channel_record_ready(3, status)',
            self._LED_SWITCH_CHANNEL_RECORD_READY + 4: \
                'self._hardware_controller.set_led_channel_record_ready(4, status)',
            self._LED_SWITCH_CHANNEL_RECORD_READY + 5: \
                'self._hardware_controller.set_led_channel_record_ready(5, status)',
            self._LED_SWITCH_CHANNEL_RECORD_READY + 6: \
                'self._hardware_controller.set_led_channel_record_ready(6, status)',
            self._LED_SWITCH_CHANNEL_RECORD_READY + 7: \
                'self._hardware_controller.set_led_channel_record_ready(7, status)',
            self._LED_SWITCH_CHANNEL_SOLO: \
                'self._hardware_controller.set_led_channel_solo(0, status)',
            self._LED_SWITCH_CHANNEL_SOLO + 1: \
                'self._hardware_controller.set_led_channel_solo(1, status)',
            self._LED_SWITCH_CHANNEL_SOLO + 2: \
                'self._hardware_controller.set_led_channel_solo(2, status)',
            self._LED_SWITCH_CHANNEL_SOLO + 3: \
                'self._hardware_controller.set_led_channel_solo(3, status)',
            self._LED_SWITCH_CHANNEL_SOLO + 4: \
                'self._hardware_controller.set_led_channel_solo(4, status)',
            self._LED_SWITCH_CHANNEL_SOLO + 5: \
                'self._hardware_controller.set_led_channel_solo(5, status)',
            self._LED_SWITCH_CHANNEL_SOLO + 6: \
                'self._hardware_controller.set_led_channel_solo(6, status)',
            self._LED_SWITCH_CHANNEL_SOLO + 7: \
                'self._hardware_controller.set_led_channel_solo(7, status)',
            self._LED_SWITCH_CHANNEL_MUTE: \
                'self._hardware_controller.set_led_channel_mute(0, status)',
            self._LED_SWITCH_CHANNEL_MUTE + 1: \
                'self._hardware_controller.set_led_channel_mute(1, status)',
            self._LED_SWITCH_CHANNEL_MUTE + 2: \
                'self._hardware_controller.set_led_channel_mute(2, status)',
            self._LED_SWITCH_CHANNEL_MUTE + 3: \
                'self._hardware_controller.set_led_channel_mute(3, status)',
            self._LED_SWITCH_CHANNEL_MUTE + 4: \
                'self._hardware_controller.set_led_channel_mute(4, status)',
            self._LED_SWITCH_CHANNEL_MUTE + 5: \
                'self._hardware_controller.set_led_channel_mute(5, status)',
            self._LED_SWITCH_CHANNEL_MUTE + 6: \
                'self._hardware_controller.set_led_channel_mute(6, status)',
            self._LED_SWITCH_CHANNEL_MUTE + 7: \
                'self._hardware_controller.set_led_channel_mute(7, status)',
            self._LED_SWITCH_CHANNEL_SELECT: \
                'self._hardware_controller.set_led_channel_select(0, status)',
            self._LED_SWITCH_CHANNEL_SELECT + 1: \
                'self._hardware_controller.set_led_channel_select(1, status)',
            self._LED_SWITCH_CHANNEL_SELECT + 2: \
                'self._hardware_controller.set_led_channel_select(2, status)',
            self._LED_SWITCH_CHANNEL_SELECT + 3: \
                'self._hardware_controller.set_led_channel_select(3, status)',
            self._LED_SWITCH_CHANNEL_SELECT + 4: \
                'self._hardware_controller.set_led_channel_select(4, status)',
            self._LED_SWITCH_CHANNEL_SELECT + 5: \
                'self._hardware_controller.set_led_channel_select(5, status)',
            self._LED_SWITCH_CHANNEL_SELECT + 6: \
                'self._hardware_controller.set_led_channel_select(6, status)',
            self._LED_SWITCH_CHANNEL_SELECT + 7: \
                'self._hardware_controller.set_led_channel_select(7, status)',
            self._LED_SWITCH_CHANNEL_VSELECT: \
                'self._hardware_controller.set_led_channel_vselect(0, status)',
            self._LED_SWITCH_CHANNEL_VSELECT + 1: \
                'self._hardware_controller.set_led_channel_vselect(1, status)',
            self._LED_SWITCH_CHANNEL_VSELECT + 2: \
                'self._hardware_controller.set_led_channel_vselect(2, status)',
            self._LED_SWITCH_CHANNEL_VSELECT + 3: \
                'self._hardware_controller.set_led_channel_vselect(3, status)',
            self._LED_SWITCH_CHANNEL_VSELECT + 4: \
                'self._hardware_controller.set_led_channel_vselect(4, status)',
            self._LED_SWITCH_CHANNEL_VSELECT + 5: \
                'self._hardware_controller.set_led_channel_vselect(5, status)',
            self._LED_SWITCH_CHANNEL_VSELECT + 6: \
                'self._hardware_controller.set_led_channel_vselect(6, status)',
            self._LED_SWITCH_CHANNEL_VSELECT + 7: \
                'self._hardware_controller.set_led_channel_vselect(7, status)',
            self._LED_SWITCH_ASSIGNMENT_TRACK: \
                'self._hardware_controller.set_led_assignment_track(status)',
            self._LED_SWITCH_ASSIGNMENT_SEND: \
                'self._hardware_controller.set_led_assignment_send(status)',
            self._LED_SWITCH_ASSIGNMENT_PAN_SURROUND: \
                'self._hardware_controller.set_led_assignment_pan_surround(status)',
            self._LED_SWITCH_ASSIGNMENT_PLUG_IN: \
                'self._hardware_controller.set_led_assignment_plug_in(status)',
            self._LED_SWITCH_ASSIGNMENT_EQ: \
                'self._hardware_controller.set_led_assignment_eq(status)',
            self._LED_SWITCH_ASSIGNMENT_INSTRUMENT: \
                'self._hardware_controller.set_led_assignment_instrument(status)',
            self._LED_SWITCH_FLIP: \
                'self._hardware_controller.set_led_flip(status)',
            self._LED_SWITCH_GLOBAL_VIEW: \
                'self._hardware_controller.set_led_global_view(status)',
            self._LED_SWITCH_AUTOMATION_READ_OFF: \
                'self._hardware_controller.set_led_automation_read_off(status)',
            self._LED_SWITCH_AUTOMATION_WRITE: \
                'self._hardware_controller.set_led_automation_write(status)',
            self._LED_SWITCH_AUTOMATION_TRIM: \
                'self._hardware_controller.set_led_automation_trim(status)',
            self._LED_SWITCH_AUTOMATION_TOUCH: \
                'self._hardware_controller.set_led_automation_touch(status)',
            self._LED_SWITCH_AUTOMATION_LATCH: \
                'self._hardware_controller.set_led_automation_latch(status)',
            self._LED_SWITCH_GROUP: \
                'self._hardware_controller.set_led_group(status)',
            self._LED_SWITCH_UTILITIES_SAVE: \
                'self._hardware_controller.set_led_utilities_save(status)',
            self._LED_SWITCH_UTILITIES_UNDO: \
                'self._hardware_controller.set_led_utilities_undo(status)',
            self._LED_SWITCH_MARKER: \
                'self._hardware_controller.set_led_marker(status)',
            self._LED_SWITCH_NUDGE: \
                'self._hardware_controller.set_led_nudge(status)',
            self._LED_SWITCH_CYCLE: \
                'self._hardware_controller.set_led_cycle(status)',
            self._LED_SWITCH_DROP: \
                'self._hardware_controller.set_led_drop(status)',
            self._LED_SWITCH_REPLACE: \
                'self._hardware_controller.set_led_replace(status)',
            self._LED_SWITCH_CLICK: \
                'self._hardware_controller.set_led_click(status)',
            self._LED_SWITCH_SOLO: \
                'self._hardware_controller.set_led_solo(status)',
            self._LED_SWITCH_REWIND: \
                'self._hardware_controller.set_led_rewind(status)',
            self._LED_SWITCH_FAST_FORWARD: \
                'self._hardware_controller.set_led_fast_forward(status)',
            self._LED_SWITCH_STOP: \
                'self._hardware_controller.set_led_stop(status)',
            self._LED_SWITCH_PLAY: \
                'self._hardware_controller.set_led_play(status)',
            self._LED_SWITCH_RECORD: \
                'self._hardware_controller.set_led_record(status)',
            self._LED_SWITCH_ZOOM: \
                'self._hardware_controller.set_led_zoom(status)',
            self._LED_SWITCH_SCRUB: \
                'self._hardware_controller.set_led_scrub(status)',
            self._LED_SMPTE: \
                'self._hardware_controller.set_led_smpte(status)',
            self._LED_BEATS: \
                'self._hardware_controller.set_led_beats(status)',
            self._LED_RUDE_SOLO: \
                'self._hardware_controller.set_led_rude_solo(status)',
            self._LED_RELAY_CLICK: \
                'self._hardware_controller.set_led_relay_click(status)'
            }

        if id in selector:
            eval(selector[id])
        else:
            led_status = 'off'
            if status == 1:
                status = 'on'
            elif status == 2:
                status = 'flashing'

            self._log('LED 0x%02X NOT implemented (%s).' % (id, led_status))


    def faders_to_minimum(self):
        if self._hardware_controller:
            self._hardware_controller.faders_to_minimum()


    def all_leds_off(self):
        if self._hardware_controller:
            self._hardware_controller.all_leds_off()


    def reset(self):
        self.go_offline()
class MidiControllerTemplate(object):

    MIDI_MANUFACTURER_ID = None
    MIDI_DEVICE_ID = None

    VPOT_MODE_SINGLE_DOT = 0x00
    VPOT_MODE_BOOST_CUT = 0x01
    VPOT_MODE_WRAP = 0x02
    VPOT_MODE_SPREAD = 0x03

    _LED_STATUS = {
        0x00: 'off',
        0x01: 'flashing',
        0x7F: 'on'
        }

    def __init__(self, midi_input_name, midi_output_name, callback_log):
        self.callback_log = callback_log

        # LCD has 2 rows with 56 characters each, fill with spaces
        self._lcd_characters = [' '] * 2
        self._lcd_overlay_characters = [' '] * 2

        for line in range(2):
            self._lcd_characters[line] = [' '] * 56
            self._lcd_overlay_characters[line] = [' '] * 56

        self._show_overlay = [False, False]

        self._log('Initialising MIDI ports...', True)
        self._midi_input_name = midi_input_name
        self._midi_output_name = midi_output_name
        self.midi = MidiConnection(self.callback_log, self.receive_midi)

        self.unset_interconnector()

        self.display_lcd_available = True
        self.automated_faders_available = True
        self.display_7seg_available = True
        self.display_timecode_available = True
        self.meter_bridge_available = True

        self.display_7seg_characters = []
        for counter in range(4):
            self.display_7seg_characters.append(' ')

        self.display_timecode_characters = []
        for counter in range(20):
            self.display_timecode_characters.append(' ')


    @staticmethod
    def get_usage_hint():
        return ''


    def _log(self, message, repaint=False):
        self.callback_log('[Controller Template]  ' + message, repaint)


    # --- initialisation ---

    def set_interconnector(self, host):
        self.interconnector = host


    def unset_interconnector(self):
        self.interconnector = None


    def connect(self):
        self._log('Opening MIDI ports...', True)
        self.midi.connect(self._midi_input_name, self._midi_output_name)


    def disconnect(self):
        self._log('Closing MIDI ports...')
        self.midi.disconnect()
        self._log('Disconnected.', True)


    def go_online(self):
        self._log('Mackie Host Control went online...', True)


    def go_offline(self):
        self._log('Mackie Host Control went offline...', True)


    # --- abilities of hardware controller ---

    def has_display_7seg(self):
        return self.display_7seg_available


    def has_display_lcd(self):
        return self.display_lcd_available


    def has_display_timecode(self):
        return self.display_timecode_available


    def has_automated_faders(self):
        return self.automated_faders_available


    def has_meter_bridge(self):
        return self.meter_bridge_available


    # --- MIDI processing ---

    def process_midi_input(self):
        self.midi.process_input_buffer()


    def receive_midi(self, status, message):
        message_string = ['status %02X: ' % status]
        for byte in message:
            message_string.append('%02X' % byte)
        self._log(' '.join(message_string))


    def send_midi_control_change(self, channel, cc_number, cc_value):
        self.midi.send_control_change(channel, cc_number, cc_value)


    def send_midi_sysex(self, data):
        assert(type(data) == types.ListType)

        header = []
        header.extend(self.MIDI_MANUFACTURER_ID)
        header.extend(self.MIDI_DEVICE_ID)

        self.midi.send_sysex(header, data)


    @staticmethod
    def get_preferred_midi_input():
        return ''


    @staticmethod
    def get_preferred_midi_output():
        return ''


    # --- registration of MIDI controls ---

    def register_control(self, mcu_command, midi_switch, midi_led = None):
        if midi_led:
            self.interconnector.register_control( \
                mcu_command, midi_switch, midi_led)
        else:
            self.interconnector.register_control( \
                mcu_command, midi_switch, midi_switch)


    def withdraw_control(self, midi_switch):
        self.interconnector.withdraw_control(midi_switch_cc)


    def withdraw_all_controls(self):
        self.interconnector.withdraw_all_controls()


    # --- handling of Mackie Control commands ---

    def set_lcd(self, position, hex_codes, update=True):
        for hex_code in hex_codes:
            # wrap display and determine position
            position %= 112
            (line, pos) = divmod(position, 56)

            # convert illegal characters to asterisk
            if (hex_code < 0x20) or (hex_code > 0x7F):
                self._lcd_characters[line][pos] = '*'
            else:
                self._lcd_characters[line][pos] = chr(hex_code)

            position += 1

        if update:
            self.update_lcd()


    def set_led(self, internal_id, led_status):
        pass


    def set_display_7seg(self, position, character_code):
        character = self._decode_7seg_character(character_code)
        position = 23 - (position * 2)

        self.display_7seg_characters[position - 1] = character[0]
        self.display_7seg_characters[position] = character[1]

        string_7seg = ''.join(self.display_7seg_characters)
        self._log('7 segment display NOT set to "%s".' % string_7seg)


    def _decode_7seg_character(self, character_code):
        if character_code >= 0x40:
            character_code = character_code - 0x40
            dot = '.'
        else:
            dot = ' '

        if character_code < 0x20:
            return (chr(character_code + 0x40), dot)
        else:
            return (chr(character_code), dot)


    def set_display_timecode(self, position, character_code):
        character = self._decode_7seg_character(character_code)
        position = 19 - (position * 2)

        self.display_timecode_characters[position - 1] = character[0]
        self.display_timecode_characters[position] = character[1]

        # please note that the logged timecode is not necessarily
        # correct: it will only be dumped when the display's last
        # character has been updated -- there may be other updates
        # still pending!
        if position == 19:
            string_timecode = ''.join(self.display_timecode_characters)
            self._log('timecode display NOT set to "%s".' % string_timecode)


    def set_peak_level(self, meter_id, meter_level):
        if meter_level == 0x0F:
            self._log('Meter #%d overload NOT cleared.' % meter_id)
        elif meter_level == 0x0F:
            self._log('Meter #%d NOT set to overload.' % meter_id)
        else:
            self._log('Meter #%d NOT set to %03d%%.' % \
                          (meter_id, meter_level * 10))


    def fader_moved(self, fader_id, fader_position):
        self._log('Hardware fader #%d NOT moved to position %04d.' % \
                      (fader_id, fader_position))


    def set_vpot_led_ring(self, vpot_id, vpot_center_led, vpot_mode, \
                              vpot_position):
        self._log('V-Pot #%d LED ring NOT set to position %02d (mode %d).' % \
                      (vpot_id, vpot_position, vpot_mode))


    def faders_to_minimum(self):
        self._log('Hardware faders NOT set to minimum.')


    def all_leds_off(self):
        self._log('Hardware LEDs NOT set to "off".')


    # --- LCD and menu handling

    def update_lcd(self):
        pass


    def get_lcd_characters(self, line):
        line %= 2

        if self._show_overlay[line]:
            return self._lcd_overlay_characters[line]
        else:
            return self._lcd_characters[line]


    def show_menu(self, line, menu_strings):
        assert(len(menu_strings) == 8)

        menu_string_temp = ''
        for menu_string in menu_strings:
            menu_string_temp += menu_string.center(7)[:7]

        menu_characters = list(menu_string_temp)
        self.show_overlay(line, menu_characters)


    def hide_menu(self, line):
        self.hide_overlay(line)


    def show_overlay(self, line, overlay_characters):
        line %= 2
        assert(len(overlay_characters) == 56)

        self._show_overlay[line] = True
        self._lcd_overlay_characters[line] = overlay_characters
        self.update_lcd()


    def hide_overlay(self, line):
        line %= 2

        self._show_overlay[line] = False
        self.update_lcd()
Example #11
0
class MackieHostControl:
    __module__ = __name__
    __doc__ = 'Python wrapper for Mackie Host Control'

    ASSUME_SUCCESSFUL_CONNECTION = 'Assume successful connection'
    CHALLENGE_RESPONSE = 'Challenge / Response'
    WAIT_FOR_MIDI_DATA = 'Wait for MIDI data'

    SWITCH_RELEASED = 0
    SWITCH_PRESSED = 1
    SWITCH_PRESSED_RELEASED = 2

    VPOT_CLOCKWISE = 0
    VPOT_COUNTER_CLOCKWISE = 1

    _LED_SWITCH_CHANNEL_RECORD_READY = 0x00
    _LED_SWITCH_CHANNEL_SOLO = 0x08

    _LED_SWITCH_CHANNEL_MUTE = 0x10
    _LED_SWITCH_CHANNEL_SELECT = 0x18
    _LED_SWITCH_CHANNEL_VSELECT = 0x20
    _LED_SWITCH_ASSIGNMENT_TRACK = 0x28
    _LED_SWITCH_ASSIGNMENT_SEND = 0x29
    _LED_SWITCH_ASSIGNMENT_PAN_SURROUND = 0x2A
    _LED_SWITCH_ASSIGNMENT_PLUG_IN = 0x2B

    _LED_SWITCH_ASSIGNMENT_EQ = 0x2C
    _LED_SWITCH_ASSIGNMENT_INSTRUMENT = 0x2D
    _SWITCH_FADER_BANKS_BANK_LEFT = 0x2E
    _SWITCH_FADER_BANKS_BANK_RIGHT = 0x2F
    _SWITCH_FADER_BANKS_CHANNEL_LEFT = 0x30
    _SWITCH_FADER_BANKS_CHANNEL_RIGHT = 0x31
    _LED_SWITCH_FLIP = 0x32
    _LED_SWITCH_GLOBAL_VIEW = 0x33
    _SWITCH_NAME_VALUE = 0x34
    _SWITCH_SMPTE_BEATS = 0x35
    _SWITCH_CHANNEL_FUNCTION = 0x36
    _SWITCH_GLOBAL_VIEW_MIDI_TRACKS = 0x3E
    _SWITCH_GLOBAL_VIEW_INPUTS = 0x3F
    _SWITCH_GLOBAL_VIEW_AUDIO_TRACKS = 0x40
    _SWITCH_GLOBAL_VIEW_AUDIO_INSTRUMENTS = 0x41
    _SWITCH_GLOBAL_VIEW_AUX = 0x42
    _SWITCH_GLOBAL_VIEW_BUSSES = 0x43
    _SWITCH_GLOBAL_VIEW_OUTPUTS = 0x44
    _SWITCH_GLOBAL_VIEW_USER = 0x45
    _SWITCH_SHIFT = 0x46
    _SWITCH_OPTION = 0x47
    _SWITCH_CONTROL = 0x48
    _SWITCH_COMMAND_ALT = 0x49
    _LED_SWITCH_AUTOMATION_READ_OFF = 0x4A

    _LED_SWITCH_AUTOMATION_WRITE = 0x4B
    _LED_SWITCH_AUTOMATION_TRIM = 0x4C
    _LED_SWITCH_AUTOMATION_TOUCH = 0x4D
    _LED_SWITCH_AUTOMATION_LATCH = 0x4E
    _LED_SWITCH_GROUP = 0x4F
    _LED_SWITCH_UTILITIES_SAVE = 0x50
    _LED_SWITCH_UTILITIES_UNDO = 0x51
    _SWITCH_UTILITIES_CANCEL = 0x52
    _SWITCH_UTILITIES_ENTER = 0x53
    _LED_SWITCH_MARKER = 0x54
    _LED_SWITCH_NUDGE = 0x55
    _LED_SWITCH_CYCLE = 0x56
    _LED_SWITCH_DROP = 0x57
    _LED_SWITCH_REPLACE = 0x58
    _LED_SWITCH_CLICK = 0x59
    _LED_SWITCH_SOLO = 0x5A
    _LED_SWITCH_REWIND = 0x5B
    _LED_SWITCH_FAST_FORWARD = 0x5C
    _LED_SWITCH_STOP = 0x5D
    _LED_SWITCH_PLAY = 0x5E
    _LED_SWITCH_RECORD = 0x5F
    _SWITCH_CURSOR_UP = 0x60
    _SWITCH_CURSOR_DOWN = 0x61
    _SWITCH_CURSOR_LEFT = 0x62
    _SWITCH_CURSOR_RIGHT = 0x63
    _LED_SWITCH_ZOOM = 0x64
    _LED_SWITCH_SCRUB = 0x65
    _SWITCH_USER_SWITCH_A = 0x66
    _SWITCH_USER_SWITCH_B = 0x67
    _SWITCH_CHANNEL_FADER_TOUCH = 0x68

    _SWITCH_MASTER_FADER_TOUCH = 0x70

    _LED_SMPTE = 0x71
    _LED_BEATS = 0x72
    _LED_RUDE_SOLO = 0x73
    _LED_RELAY_CLICK = 0x76


    def __init__(self, mcu_model_id, mcu_connection, version_number, \
                     midi_input_name, midi_output_name, callback_log):
        self._callback_log = callback_log

        self._log('Initialising MIDI ports...', True)
        self._midi_input_name = midi_input_name
        self._midi_output_name = midi_output_name
        self._midi = MidiConnection(callback_log, self.receive_midi)
        self._midi_channel = 0

        self.unset_hardware_controller()
        self._offline = True

        # Mackie Control model IDs:
        # * 0x10: Logic Control
        # * 0x11: Logic Control XT
        # * 0x14: Mackie Control
        # * 0x15: Mackie Control XT
        self._mcu_model_id = mcu_model_id

        # define connection type (among others, challenge-response)
        self._mcu_connection = mcu_connection

        serial_number = '_pyMCU_'
        self._serial_number_bytes = []
        for char in serial_number:
            self._serial_number_bytes.append(ord(char))

        challenge = 'test'
        self._challenge_bytes = []
        for char in challenge:
            self._challenge_bytes.append(ord(char))

        self._response_bytes = self._calculate_reponse_from_challenge( \
            self._challenge_bytes)

        # make sure that the version number consists of exactly 5
        # characters
        version_number = version_number.ljust(5)[0:5]

        self._version_number_bytes = []
        for char in version_number:
            self._version_number_bytes.append(ord(char))

    def _log(self, message, repaint=False):
        self._callback_log('[Mackie Host Control  ]  ' + message, repaint)

    # --- initialisation ---

    def set_hardware_controller(self, controller):
        self._hardware_controller = controller

        self._display_lcd_available = \
            self._hardware_controller.has_display_lcd()
        self._automated_faders_available = \
            self._hardware_controller.has_automated_faders()
        self._display_7seg_available = \
            self._hardware_controller.has_display_7seg()
        self._display_timecode_available = \
            self._hardware_controller.has_display_timecode()
        self._meter_bridge_available = \
            self._hardware_controller.has_meter_bridge()

    def unset_hardware_controller(self):
        self._hardware_controller = None

        self._display_lcd_available = False
        self._automated_faders_available = False
        self._display_7seg_available = False
        self._display_timecode_available = False
        self._meter_bridge_available = False

    def connect(self):
        self._log('Opening MIDI ports...')
        self._midi.connect(self._midi_input_name, self._midi_output_name)

        if self._mcu_connection == self.CHALLENGE_RESPONSE:
            self._log('Sending "Host Connection Query"...', True)

            sysex_message = [0x01]
            sysex_message.extend(self._serial_number_bytes)
            sysex_message.extend(self._challenge_bytes)
            self.send_midi_sysex(sysex_message)

            return
        else:
            # let's make sure the MIDI input buffer is empty
            self._midi.process_input_buffer(use_callback=False)

            if self._mcu_connection == self.WAIT_FOR_MIDI_DATA:
                self._log('Waiting for MIDI input from host...', True)

                # wait for some MIDI input from the host (all data are
                # left in the MIDI input buffer!)
                while self._midi.buffer_is_empty():
                    time.sleep(0.1)

            self.go_online()

    def disconnect(self):
        self._log('Disconnecting...', True)
        self.go_offline()

        self._log('Closing MIDI ports...', True)
        self._midi.disconnect()

    def go_online(self):
        self._offline = False

        if self._hardware_controller:
            self._hardware_controller.go_online()

        self._log('Online.', True)

    def go_offline(self):
        self._offline = True

        if self._hardware_controller:
            self._hardware_controller.go_offline()

        if self._mcu_connection == self.CHALLENGE_RESPONSE:
            self._log('Sending "Host Connection Error"...')

            sysex_message = [0x04]
            sysex_message.extend(self._serial_number_bytes)
            self.send_midi_sysex(sysex_message)

        self._log('Offline.', True)

    def is_offline(self):
        return self._offline

    def _calculate_reponse_from_challenge(self, challenge_bytes):
        response_bytes = []

        response_bytes.append( \
            0x7F & (challenge_bytes[0] + (challenge_bytes[1] ^ 0x0A) - \
                        challenge_bytes[3]))
        response_bytes.append( \
            0x7F & ((challenge_bytes[2] >> 4) ^ \
                        (challenge_bytes[0] + challenge_bytes[3])))
        response_bytes.append( \
            0x7F & (challenge_bytes[3] - (challenge_bytes[2] << 2) ^ \
                        (challenge_bytes[0] | challenge_bytes[1])))
        response_bytes.append( \
            0x7F & (challenge_bytes[1] - challenge_bytes[2] + \
                        (0xF0 ^ (challenge_bytes[3] << 4))))

        return response_bytes

    # --- static methods ---

    @staticmethod
    def get_mcu_model_from_id(id):
        if id == 0x10:
            return 'Logic Control'
        elif id == 0x11:
            return 'Logic Control XT'
        elif id == 0x14:
            return 'Mackie Control'
        elif id == 0x15:
            return 'Mackie Control XT'
        else:
            return None

    @staticmethod
    def get_mcu_id_from_model(model):
        if model == 'Logic Control':
            return 0x10
        elif model == 'Logic Control XT':
            return 0x11
        elif model == 'Mackie Control':
            return 0x14
        elif model == 'Mackie Control XT':
            return 0x15
        else:
            return None

    @staticmethod
    def get_preferred_mcu_model():
        return 'Mackie Control'

    @staticmethod
    def get_preferred_mcu_model_id():
        return MackieHostControl.get_mcu_id_from_model( \
            MackieHostControl.get_preferred_mcu_model())

    @staticmethod
    def get_preferred_midi_input():
        if os.name == 'nt':
            return 'In From MIDI Yoke:  2'
        else:
            return 'mcu'

    @staticmethod
    def get_preferred_midi_output():
        if os.name == 'nt':
            return 'Out To MIDI Yoke:  1'
        else:
            return 'mcu'

    # --- MIDI processing ---

    def process_midi_input(self):
        self._midi.process_input_buffer()

    def receive_midi(self, status, message):
        if status == MidiConnection.SYSTEM_MESSAGE and message[0:5] == \
                [0xF0, 0x00, 0x00, 0x66, self._mcu_model_id]:
            if message[5:] == [0x00, 0xF7]:
                self._log('Received "Device Query".')
                self._log('Sending "Host Connection Query"...', True)

                sysex_message = [0x01]
                sysex_message.extend(self._serial_number_bytes)
                sysex_message.extend(self._challenge_bytes)
                self.send_midi_sysex(sysex_message)

                return
            elif message[5] == 0x02:
                self._log('Received "Host Connection Reply".')
                if (message[6:13] == self._serial_number_bytes) and \
                        (message[13:17] == self._response_bytes):
                    self._log('Sending "Host Connection Confirmation"...', \
                                      True)

                    sysex_message = [0x03]
                    sysex_message.extend(self._serial_number_bytes)
                    self.send_midi_sysex(sysex_message)

                    self.go_online()
                else:
                    self._log('Sending "Host Connection Error"...')

                    sysex_message = [0x04]
                    sysex_message.extend(self._serial_number_bytes)
                    self.send_midi_sysex(sysex_message)

                    self._log('Sending "Host Connection Query"...', True)

                    sysex_message = [0x01]
                    sysex_message.extend(self._serial_number_bytes)
                    sysex_message.extend(self._challenge_bytes)
                    self.send_midi_sysex(sysex_message)

                return
            elif message[5:] == [0x13, 0x00, 0x0F7]:
                self._log('Received "Version Request".')
                self._log('Sending "Version Reply"...', True)

                sysex_message = [0x14]
                sysex_message.extend(self._version_number_bytes)
                self.send_midi_sysex(sysex_message)

                return
            elif message[5:] == [0x0F, 0x7F, 0x0F7]:
                self._log('Received "Go Offline".', True)
                self.go_offline()

                return
            elif message[5:] == [0x61, 0x0F7]:
                self._log('Received "Faders To Minimum".', True)
                self.faders_to_minimum()

                return
            elif message[5:] == [0x62, 0x0F7]:
                self._log('Received "All LEDs Off".', True)
                self.all_leds_off()

                return
            elif message[5:] == [0x63, 0x0F7]:
                self._log('Received "Reset".', True)
                self.reset()

                return

        # do not make this an "elif" clause, otherwise some MIDI SysEx
        # messages won't get processed!
        if not self.is_offline():
            if status == MidiConnection.SYSTEM_MESSAGE and message[0:5] == \
                    [0xF0, 0x00, 0x00, 0x66, self._mcu_model_id]:
                if message[5] == 0x12:
                    if self._display_lcd_available:
                        position = message[6]
                        hex_codes = message[7:-1]

                        self._hardware_controller.set_lcd(position, hex_codes)
            elif status == MidiConnection.PITCH_WHEEL_CHANGE:
                if self._automated_faders_available:
                    fader_id = message[0] & 0x0F
                    fader_position = (message[1] + (message[2] << 7)) >> 4
                    self._hardware_controller.fader_moved( \
                        fader_id, fader_position)
            elif status == MidiConnection.NOTE_ON_EVENT:
                led_id = message[1]
                led_status = 0  # off

                if message[2] == 0x7F:
                    led_status = 1  # on
                elif message[2] == 0x01:
                    led_status = 2  # flashing

                self._set_led(led_id, led_status)
            elif (status == MidiConnection.CONTROL_CHANGE) and \
                    ((message[1] & 0xF0) == 0x30):
                vpot_id = message[1] & 0x0F
                vpot_center_led = (message[2] & 0x40) >> 7
                vpot_mode = (message[2] & 0x30) >> 4
                vpot_position = message[2] & 0x0F
                self._hardware_controller.set_vpot_led_ring( \
                    vpot_id, vpot_center_led, vpot_mode, vpot_position)
            elif (status == MidiConnection.CONTROL_CHANGE) and \
                    ((message[1] & 0xF0) == 0x40):
                position = message[1] & 0x0F
                character_code = message[2]

                if (position < 10):
                    if self._display_timecode_available:
                        self._hardware_controller.set_display_timecode( \
                            position, character_code)
                elif self._display_7seg_available:
                    self._hardware_controller.set_display_7seg( \
                        position, character_code)
            elif status == MidiConnection.CHANNEL_PRESSURE:
                if self._meter_bridge_available:
                    meter_id = (message[1] & 0x70) >> 4
                    meter_level = message[1] & 0x0F
                    self._hardware_controller.set_peak_level( \
                        meter_id, meter_level)
            else:
                output = 'status %02X: ' % status
                for byte in message:
                    output += '%02X ' % byte

                self._log(output.strip())
        else:
            output = 'status %02X: ' % status
            for byte in message:
                output += '%02X ' % byte

            self._log(output.strip())

    def send_midi_sysex(self, data):
        assert (type(data) == types.ListType)

        header = [0x00, 0x00, 0x66, self._mcu_model_id]

        # leading 0xF0 and trailing 0xF7 are added by "MidiConnection"
        # class method
        self._midi.send_sysex(header, data)

    # --- commands from hardware control ---

    def move_vpot(self, vpot_id, direction, number_of_ticks):
        if self.is_offline():
            return

        vpot_movement = number_of_ticks
        if direction == self.VPOT_COUNTER_CLOCKWISE:
            vpot_movement = vpot_movement + 0x40

        self._midi.send_control_change( \
            self._midi_channel, 0x10 + vpot_id, vpot_movement)

    def move_vpot_raw(self, vpot_id, vpot_movement):
        if self.is_offline():
            return

        self._midi.send_control_change( \
            self._midi_channel, 0x10 + vpot_id, vpot_movement)

    def move_fader(self, fader_id, fader_value):
        if self.is_offline():
            return

        self._midi.send_pitch_wheel_change(fader_id, fader_value)

    def move_fader_7bit(self, fader_id, fader_value):
        if self.is_offline():
            return

        self._midi.send_pitch_wheel_change_7bit(fader_id, fader_value)

    def _key_pressed(self, status, switch_id):
        if self.is_offline():
            return

        if status == self.SWITCH_RELEASED:
            self._midi.send_note_on(switch_id, 0x00)
        elif status == self.SWITCH_PRESSED:
            self._midi.send_note_on(switch_id, 0x7F)
        elif status == self.SWITCH_PRESSED_RELEASED:
            self._midi.send_note_on(switch_id, 0x7F)
            self._midi.send_note_on(switch_id, 0x00)
        else:
            self._log( \
                'Illegal key press status 0x%02X on switch 0x%02X detected!' % \
                    (status, switch_id))

    def keypress_record_ready_channel(self, channel, status):
        # channel: 1 - 8
        self._key_pressed( \
            status, self._LED_SWITCH_CHANNEL_RECORD_READY + channel - 1)

    def keypress_record_ready_channel_1(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_RECORD_READY)

    def keypress_record_ready_channel_2(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_RECORD_READY + 1)

    def keypress_record_ready_channel_3(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_RECORD_READY + 2)

    def keypress_record_ready_channel_4(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_RECORD_READY + 3)

    def keypress_record_ready_channel_5(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_RECORD_READY + 4)

    def keypress_record_ready_channel_6(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_RECORD_READY + 5)

    def keypress_record_ready_channel_7(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_RECORD_READY + 6)

    def keypress_record_ready_channel_8(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_RECORD_READY + 7)

    def keypress_solo_channel(self, channel, status):
        # channel: 1 - 8
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SOLO + channel - 1)

    def keypress_solo_channel_1(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SOLO)

    def keypress_solo_channel_2(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SOLO + 1)

    def keypress_solo_channel_3(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SOLO + 2)

    def keypress_solo_channel_4(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SOLO + 3)

    def keypress_solo_channel_5(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SOLO + 4)

    def keypress_solo_channel_6(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SOLO + 5)

    def keypress_solo_channel_7(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SOLO + 6)

    def keypress_solo_channel_8(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SOLO + 7)

    def keypress_mute_channel(self, channel, status):
        # channel: 1 - 8
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_MUTE + channel - 1)

    def keypress_mute_channel_1(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_MUTE)

    def keypress_mute_channel_2(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_MUTE + 1)

    def keypress_mute_channel_3(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_MUTE + 2)

    def keypress_mute_channel_4(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_MUTE + 3)

    def keypress_mute_channel_5(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_MUTE + 4)

    def keypress_mute_channel_6(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_MUTE + 5)

    def keypress_mute_channel_7(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_MUTE + 6)

    def keypress_mute_channel_8(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_MUTE + 7)

    def keypress_select_channel(self, channel, status):
        # channel: 1 - 8
        self._key_pressed( \
            status, self._LED_SWITCH_CHANNEL_SELECT + channel - 1)

    def keypress_select_channel_1(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SELECT)

    def keypress_select_channel_2(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SELECT + 1)

    def keypress_select_channel_3(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SELECT + 2)

    def keypress_select_channel_4(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SELECT + 3)

    def keypress_select_channel_5(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SELECT + 4)

    def keypress_select_channel_6(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SELECT + 5)

    def keypress_select_channel_7(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SELECT + 6)

    def keypress_select_channel_8(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_SELECT + 7)

    def keypress_vselect_channel(self, channel, status):
        # channel: 1 - 8
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_VSELECT + \
                              channel - 1)

    def keypress_vselect_channel_1(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_VSELECT)

    def keypress_vselect_channel_2(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_VSELECT + 1)

    def keypress_vselect_channel_3(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_VSELECT + 2)

    def keypress_vselect_channel_4(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_VSELECT + 3)

    def keypress_vselect_channel_5(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_VSELECT + 4)

    def keypress_vselect_channel_6(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_VSELECT + 5)

    def keypress_vselect_channel_7(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_VSELECT + 6)

    def keypress_vselect_channel_8(self, status):
        self._key_pressed(status, self._LED_SWITCH_CHANNEL_VSELECT + 7)

    def keypress_function_channel(self, channel, status):
        # channel: 1 - 8
        self._key_pressed(status, self._SWITCH_CHANNEL_FUNCTION + channel - 1)

    def keypress_function_channel_1(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FUNCTION)

    def keypress_function_channel_2(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FUNCTION + 1)

    def keypress_function_channel_3(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FUNCTION + 2)

    def keypress_function_channel_4(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FUNCTION + 3)

    def keypress_function_channel_5(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FUNCTION + 4)

    def keypress_function_channel_6(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FUNCTION + 5)

    def keypress_function_channel_7(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FUNCTION + 6)

    def keypress_function_channel_8(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FUNCTION + 7)

    def keypress_assignment_track(self, status):
        self._key_pressed(status, self._LED_SWITCH_ASSIGNMENT_TRACK)

    def keypress_assignment_send(self, status):
        self._key_pressed(status, self._LED_SWITCH_ASSIGNMENT_SEND)

    def keypress_assignment_pan_surround(self, status):
        self._key_pressed(status, self._LED_SWITCH_ASSIGNMENT_PAN_SURROUND)

    def keypress_assignment_plug_in(self, status):
        self._key_pressed(status, self._LED_SWITCH_ASSIGNMENT_PLUG_IN)

    def keypress_assignment_eq(self, status):
        self._key_pressed(status, self._LED_SWITCH_ASSIGNMENT_EQ)

    def keypress_assignment_instrument(self, status):
        self._key_pressed(status, self._LED_SWITCH_ASSIGNMENT_INSTRUMENT)

    def keypress_fader_banks_bank_left(self, status):
        self._key_pressed(status, self._SWITCH_FADER_BANKS_BANK_LEFT)

    def keypress_fader_banks_bank_right(self, status):
        self._key_pressed(status, self._SWITCH_FADER_BANKS_BANK_RIGHT)

    def keypress_fader_banks_channel_left(self, status):
        self._key_pressed(status, self._SWITCH_FADER_BANKS_CHANNEL_LEFT)

    def keypress_fader_banks_channel_right(self, status):
        self._key_pressed(status, self._SWITCH_FADER_BANKS_CHANNEL_RIGHT)

    def keypress_flip(self, status):
        self._key_pressed(status, self._LED_SWITCH_FLIP)

    def keypress_global_view(self, status):
        self._key_pressed(status, self._LED_SWITCH_GLOBAL_VIEW)

    def keypress_name_value(self, status):
        self._key_pressed(status, self._SWITCH_NAME_VALUE)

    def keypress_smpte_beats(self, status):
        self._key_pressed(status, self._SWITCH_SMPTE_BEATS)

    def keypress_global_view_midi_tracks(self, status):
        self._key_pressed(status, self._SWITCH_GLOBAL_VIEW_MIDI_TRACKS)

    def keypress_global_view_inputs(self, status):
        self._key_pressed(status, self._SWITCH_GLOBAL_VIEW_INPUTS)

    def keypress_global_view_audio_tracks(self, status):
        self._key_pressed(status, self._SWITCH_GLOBAL_VIEW_AUDIO_TRACKS)

    def keypress_global_view_audio_instruments(self, status):
        self._key_pressed(status, self._SWITCH_GLOBAL_VIEW_AUDIO_INSTRUMENTS)

    def keypress_global_view_aux(self, status):
        self._key_pressed(status, self._SWITCH_GLOBAL_VIEW_AUX)

    def keypress_global_view_busses(self, status):
        self._key_pressed(status, self._SWITCH_GLOBAL_VIEW_BUSSES)

    def keypress_global_view_outputs(self, status):
        self._key_pressed(status, self._SWITCH_GLOBAL_VIEW_OUTPUTS)

    def keypress_global_view_user(self, status):
        self._key_pressed(status, self._SWITCH_GLOBAL_VIEW_USER)

    def keypress_shift(self, status):
        self._key_pressed(status, self._SWITCH_SHIFT)

    def keypress_option(self, status):
        self._key_pressed(status, self._SWITCH_OPTION)

    def keypress_control(self, status):
        self._key_pressed(status, self._SWITCH_CONTROL)

    def keypress_command_alt(self, status):
        self._key_pressed(status, self._SWITCH_COMMAND_ALT)

    def keypress_automation_read_off(self, status):
        self._key_pressed(status, self._LED_SWITCH_AUTOMATION_READ_OFF)

    def keypress_automation_write(self, status):
        self._key_pressed(status, self._LED_SWITCH_AUTOMATION_WRITE)

    def keypress_automation_trim(self, status):
        self._key_pressed(status, self._LED_SWITCH_AUTOMATION_TRIM)

    def keypress_automation_touch(self, status):
        self._key_pressed(status, self._LED_SWITCH_AUTOMATION_TOUCH)

    def keypress_automation_latch(self, status):
        self._key_pressed(status, self._LED_SWITCH_AUTOMATION_LATCH)

    def keypress_group(self, status):
        self._key_pressed(status, self._LED_SWITCH_GROUP)

    def keypress_utilities_save(self, status):
        self._key_pressed(status, self._LED_SWITCH_UTILITIES_SAVE)

    def keypress_utilities_undo(self, status):
        self._key_pressed(status, self._LED_SWITCH_UTILITIES_UNDO)

    def keypress_utilities_cancel(self, status):
        self._key_pressed(status, self._SWITCH_UTILITIES_CANCEL)

    def keypress_utilities_enter(self, status):
        self._key_pressed(status, self._SWITCH_UTILITIES_ENTER)

    def keypress_marker(self, status):
        self._key_pressed(status, self._LED_SWITCH_MARKER)

    def keypress_nudge(self, status):
        self._key_pressed(status, self._LED_SWITCH_NUDGE)

    def keypress_cycle(self, status):
        self._key_pressed(status, self._LED_SWITCH_CYCLE)

    def keypress_drop(self, status):
        self._key_pressed(status, self._LED_SWITCH_DROP)

    def keypress_replace(self, status):
        self._key_pressed(status, self._LED_SWITCH_REPLACE)

    def keypress_click(self, status):
        self._key_pressed(status, self._LED_SWITCH_CLICK)

    def keypress_solo(self, status):
        self._key_pressed(status, self._LED_SWITCH_SOLO)

    def keypress_rewind(self, status):
        self._key_pressed(status, self._LED_SWITCH_REWIND)

    def keypress_fast_forward(self, status):
        self._key_pressed(status, self._LED_SWITCH_FAST_FORWARD)

    def keypress_stop(self, status):
        self._key_pressed(status, self._LED_SWITCH_STOP)

    def keypress_play(self, status):
        self._key_pressed(status, self._LED_SWITCH_PLAY)

    def keypress_record(self, status):
        self._key_pressed(status, self._LED_SWITCH_RECORD)

    def keypress_cursor_up(self, status):
        self._key_pressed(status, self._SWITCH_CURSOR_UP)

    def keypress_cursor_down(self, status):
        self._key_pressed(status, self._SWITCH_CURSOR_DOWN)

    def keypress_cursor_left(self, status):
        self._key_pressed(status, self._SWITCH_CURSOR_LEFT)

    def keypress_cursor_right(self, status):
        self._key_pressed(status, self._SWITCH_CURSOR_RIGHT)

    def keypress_zoom(self, status):
        self._key_pressed(status, self._LED_SWITCH_ZOOM)

    def keypress_scrub(self, status):
        self._key_pressed(status, self._LED_SWITCH_SCRUB)

    def keypress_user_switch(self, switch_number, status):
        # switch_number: 1 - 2
        if switch_number == 1:
            self._key_pressed(status, self._SWITCH_USER_SWITCH_A)
        else:
            self._key_pressed(status, self._SWITCH_USER_SWITCH_B)

    def keypress_user_switch_1(self, status):
        self._key_pressed(status, self._SWITCH_USER_SWITCH_A)

    def keypress_user_switch_2(self, status):
        self._key_pressed(status, self._SWITCH_USER_SWITCH_B)

    def keypress_fader_touch_channel(self, channel, status):
        # channel: 1 - 8
        self._key_pressed( \
            status, self._SWITCH_CHANNEL_FADER_TOUCH + channel - 1)

    def keypress_fader_touch_channel_1(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FADER_TOUCH)

    def keypress_fader_touch_channel_2(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FADER_TOUCH + 1)

    def keypress_fader_touch_channel_3(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FADER_TOUCH + 2)

    def keypress_fader_touch_channel_4(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FADER_TOUCH + 3)

    def keypress_fader_touch_channel_5(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FADER_TOUCH + 4)

    def keypress_fader_touch_channel_6(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FADER_TOUCH + 5)

    def keypress_fader_touch_channel_7(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FADER_TOUCH + 6)

    def keypress_fader_touch_channel_8(self, status):
        self._key_pressed(status, self._SWITCH_CHANNEL_FADER_TOUCH + 7)

    def keypress_fader_touch_master(self, status):
        self._key_pressed(status, self._SWITCH_MASTER_FADER_TOUCH)

    # --- commands from Mackie Control host ---

    def _set_led(self, id, status):
        if self.is_offline():
            return

        selector = {
            self._LED_SWITCH_CHANNEL_RECORD_READY: \
                'self._hardware_controller.set_led_channel_record_ready(0, status)',
            self._LED_SWITCH_CHANNEL_RECORD_READY + 1: \
                'self._hardware_controller.set_led_channel_record_ready(1, status)',
            self._LED_SWITCH_CHANNEL_RECORD_READY + 2: \
                'self._hardware_controller.set_led_channel_record_ready(2, status)',
            self._LED_SWITCH_CHANNEL_RECORD_READY + 3: \
                'self._hardware_controller.set_led_channel_record_ready(3, status)',
            self._LED_SWITCH_CHANNEL_RECORD_READY + 4: \
                'self._hardware_controller.set_led_channel_record_ready(4, status)',
            self._LED_SWITCH_CHANNEL_RECORD_READY + 5: \
                'self._hardware_controller.set_led_channel_record_ready(5, status)',
            self._LED_SWITCH_CHANNEL_RECORD_READY + 6: \
                'self._hardware_controller.set_led_channel_record_ready(6, status)',
            self._LED_SWITCH_CHANNEL_RECORD_READY + 7: \
                'self._hardware_controller.set_led_channel_record_ready(7, status)',
            self._LED_SWITCH_CHANNEL_SOLO: \
                'self._hardware_controller.set_led_channel_solo(0, status)',
            self._LED_SWITCH_CHANNEL_SOLO + 1: \
                'self._hardware_controller.set_led_channel_solo(1, status)',
            self._LED_SWITCH_CHANNEL_SOLO + 2: \
                'self._hardware_controller.set_led_channel_solo(2, status)',
            self._LED_SWITCH_CHANNEL_SOLO + 3: \
                'self._hardware_controller.set_led_channel_solo(3, status)',
            self._LED_SWITCH_CHANNEL_SOLO + 4: \
                'self._hardware_controller.set_led_channel_solo(4, status)',
            self._LED_SWITCH_CHANNEL_SOLO + 5: \
                'self._hardware_controller.set_led_channel_solo(5, status)',
            self._LED_SWITCH_CHANNEL_SOLO + 6: \
                'self._hardware_controller.set_led_channel_solo(6, status)',
            self._LED_SWITCH_CHANNEL_SOLO + 7: \
                'self._hardware_controller.set_led_channel_solo(7, status)',
            self._LED_SWITCH_CHANNEL_MUTE: \
                'self._hardware_controller.set_led_channel_mute(0, status)',
            self._LED_SWITCH_CHANNEL_MUTE + 1: \
                'self._hardware_controller.set_led_channel_mute(1, status)',
            self._LED_SWITCH_CHANNEL_MUTE + 2: \
                'self._hardware_controller.set_led_channel_mute(2, status)',
            self._LED_SWITCH_CHANNEL_MUTE + 3: \
                'self._hardware_controller.set_led_channel_mute(3, status)',
            self._LED_SWITCH_CHANNEL_MUTE + 4: \
                'self._hardware_controller.set_led_channel_mute(4, status)',
            self._LED_SWITCH_CHANNEL_MUTE + 5: \
                'self._hardware_controller.set_led_channel_mute(5, status)',
            self._LED_SWITCH_CHANNEL_MUTE + 6: \
                'self._hardware_controller.set_led_channel_mute(6, status)',
            self._LED_SWITCH_CHANNEL_MUTE + 7: \
                'self._hardware_controller.set_led_channel_mute(7, status)',
            self._LED_SWITCH_CHANNEL_SELECT: \
                'self._hardware_controller.set_led_channel_select(0, status)',
            self._LED_SWITCH_CHANNEL_SELECT + 1: \
                'self._hardware_controller.set_led_channel_select(1, status)',
            self._LED_SWITCH_CHANNEL_SELECT + 2: \
                'self._hardware_controller.set_led_channel_select(2, status)',
            self._LED_SWITCH_CHANNEL_SELECT + 3: \
                'self._hardware_controller.set_led_channel_select(3, status)',
            self._LED_SWITCH_CHANNEL_SELECT + 4: \
                'self._hardware_controller.set_led_channel_select(4, status)',
            self._LED_SWITCH_CHANNEL_SELECT + 5: \
                'self._hardware_controller.set_led_channel_select(5, status)',
            self._LED_SWITCH_CHANNEL_SELECT + 6: \
                'self._hardware_controller.set_led_channel_select(6, status)',
            self._LED_SWITCH_CHANNEL_SELECT + 7: \
                'self._hardware_controller.set_led_channel_select(7, status)',
            self._LED_SWITCH_CHANNEL_VSELECT: \
                'self._hardware_controller.set_led_channel_vselect(0, status)',
            self._LED_SWITCH_CHANNEL_VSELECT + 1: \
                'self._hardware_controller.set_led_channel_vselect(1, status)',
            self._LED_SWITCH_CHANNEL_VSELECT + 2: \
                'self._hardware_controller.set_led_channel_vselect(2, status)',
            self._LED_SWITCH_CHANNEL_VSELECT + 3: \
                'self._hardware_controller.set_led_channel_vselect(3, status)',
            self._LED_SWITCH_CHANNEL_VSELECT + 4: \
                'self._hardware_controller.set_led_channel_vselect(4, status)',
            self._LED_SWITCH_CHANNEL_VSELECT + 5: \
                'self._hardware_controller.set_led_channel_vselect(5, status)',
            self._LED_SWITCH_CHANNEL_VSELECT + 6: \
                'self._hardware_controller.set_led_channel_vselect(6, status)',
            self._LED_SWITCH_CHANNEL_VSELECT + 7: \
                'self._hardware_controller.set_led_channel_vselect(7, status)',
            self._LED_SWITCH_ASSIGNMENT_TRACK: \
                'self._hardware_controller.set_led_assignment_track(status)',
            self._LED_SWITCH_ASSIGNMENT_SEND: \
                'self._hardware_controller.set_led_assignment_send(status)',
            self._LED_SWITCH_ASSIGNMENT_PAN_SURROUND: \
                'self._hardware_controller.set_led_assignment_pan_surround(status)',
            self._LED_SWITCH_ASSIGNMENT_PLUG_IN: \
                'self._hardware_controller.set_led_assignment_plug_in(status)',
            self._LED_SWITCH_ASSIGNMENT_EQ: \
                'self._hardware_controller.set_led_assignment_eq(status)',
            self._LED_SWITCH_ASSIGNMENT_INSTRUMENT: \
                'self._hardware_controller.set_led_assignment_instrument(status)',
            self._LED_SWITCH_FLIP: \
                'self._hardware_controller.set_led_flip(status)',
            self._LED_SWITCH_GLOBAL_VIEW: \
                'self._hardware_controller.set_led_global_view(status)',
            self._LED_SWITCH_AUTOMATION_READ_OFF: \
                'self._hardware_controller.set_led_automation_read_off(status)',
            self._LED_SWITCH_AUTOMATION_WRITE: \
                'self._hardware_controller.set_led_automation_write(status)',
            self._LED_SWITCH_AUTOMATION_TRIM: \
                'self._hardware_controller.set_led_automation_trim(status)',
            self._LED_SWITCH_AUTOMATION_TOUCH: \
                'self._hardware_controller.set_led_automation_touch(status)',
            self._LED_SWITCH_AUTOMATION_LATCH: \
                'self._hardware_controller.set_led_automation_latch(status)',
            self._LED_SWITCH_GROUP: \
                'self._hardware_controller.set_led_group(status)',
            self._LED_SWITCH_UTILITIES_SAVE: \
                'self._hardware_controller.set_led_utilities_save(status)',
            self._LED_SWITCH_UTILITIES_UNDO: \
                'self._hardware_controller.set_led_utilities_undo(status)',
            self._LED_SWITCH_MARKER: \
                'self._hardware_controller.set_led_marker(status)',
            self._LED_SWITCH_NUDGE: \
                'self._hardware_controller.set_led_nudge(status)',
            self._LED_SWITCH_CYCLE: \
                'self._hardware_controller.set_led_cycle(status)',
            self._LED_SWITCH_DROP: \
                'self._hardware_controller.set_led_drop(status)',
            self._LED_SWITCH_REPLACE: \
                'self._hardware_controller.set_led_replace(status)',
            self._LED_SWITCH_CLICK: \
                'self._hardware_controller.set_led_click(status)',
            self._LED_SWITCH_SOLO: \
                'self._hardware_controller.set_led_solo(status)',
            self._LED_SWITCH_REWIND: \
                'self._hardware_controller.set_led_rewind(status)',
            self._LED_SWITCH_FAST_FORWARD: \
                'self._hardware_controller.set_led_fast_forward(status)',
            self._LED_SWITCH_STOP: \
                'self._hardware_controller.set_led_stop(status)',
            self._LED_SWITCH_PLAY: \
                'self._hardware_controller.set_led_play(status)',
            self._LED_SWITCH_RECORD: \
                'self._hardware_controller.set_led_record(status)',
            self._LED_SWITCH_ZOOM: \
                'self._hardware_controller.set_led_zoom(status)',
            self._LED_SWITCH_SCRUB: \
                'self._hardware_controller.set_led_scrub(status)',
            self._LED_SMPTE: \
                'self._hardware_controller.set_led_smpte(status)',
            self._LED_BEATS: \
                'self._hardware_controller.set_led_beats(status)',
            self._LED_RUDE_SOLO: \
                'self._hardware_controller.set_led_rude_solo(status)',
            self._LED_RELAY_CLICK: \
                'self._hardware_controller.set_led_relay_click(status)'
            }

        if id in selector:
            eval(selector[id])
        else:
            led_status = 'off'
            if status == 1:
                status = 'on'
            elif status == 2:
                status = 'flashing'

            self._log('LED 0x%02X NOT implemented (%s).' % (id, led_status))

    def faders_to_minimum(self):
        if self._hardware_controller:
            self._hardware_controller.faders_to_minimum()

    def all_leds_off(self):
        if self._hardware_controller:
            self._hardware_controller.all_leds_off()

    def reset(self):
        self.go_offline()