Esempio n. 1
0
class Session(object):
    def __init__(self):
        self.hmi_initialized = False
        self.host_initialized = False
        self.pedalboard_initialized = False

        self._playback_1_connected_ports = []
        self._playback_2_connected_ports = []
        self._tuner = False
        self._tuner_port = 1
        self._peakmeter = False

        self.monitor_server = None

        self.current_bank = None

        self.jack_bufsize = DEFAULT_JACK_BUFSIZE
        self.effect_index = EffectIndex()

        self._pedalboard = Pedalboard()

        #Protocol.register_cmd_callback("banks", self.hmi_list_banks)
        #Protocol.register_cmd_callback("pedalboards", self.hmi_list_pedalboards)
        Protocol.register_cmd_callback("hw_con", self.hardware_connected)
        Protocol.register_cmd_callback("hw_dis", self.hardware_disconnected)
        Protocol.register_cmd_callback("control_set", self.hmi_parameter_set)
        Protocol.register_cmd_callback("control_get", self.parameter_get)
        Protocol.register_cmd_callback("control_next", self.parameter_addressing_next)
        Protocol.register_cmd_callback("peakmeter", self.peakmeter_set)
        Protocol.register_cmd_callback("tuner", self.tuner_set)
        Protocol.register_cmd_callback("tuner_input", self.tuner_set_input)
        #Protocol.register_cmd_callback("pedalboard_save", self.save_current_pedalboard)
        #Protocol.register_cmd_callback("pedalboard_reset", self.reset_current_pedalboard)
        Protocol.register_cmd_callback("jack_cpu_load", self.jack_cpu_load)

#        self.host = factory(Host, FakeHost, DEV_HOST,
#                            "unix:///tmp/ingen.sock", self.host_callback)
        self.host = Host(os.environ.get("MOD_INGEN_SOCKET_URI", "unix:///tmp/ingen.sock"), self.host_callback)
        self.hmi = factory(HMI, FakeHMI, DEV_HMI,
                           HMI_SERIAL_PORT, HMI_BAUD_RATE, self.hmi_callback)

        self.recorder = Recorder()
        self.player = Player()
        self.bundlepath = None
        self.mute_state = True
        self.recording = None
        self.instances = []
        self.instance_mapper = InstanceIdMapper()
        self.engine_samplerate = 48000 # default value

        self._clipmeter = Clipmeter(self.hmi)
        self.websockets = []

        self._load_pb_hack = None
        self._save_waiter = None

    def reconnect(self):
        self.host.open_connection(self.host_callback)

    def websocket_opened(self, ws):
        self.websockets.append(ws)
        self.host.get("/graph")

    @gen.engine
    def host_callback(self):
        self.host_initialized = True

        def port_value_cb(instance, port, value):
            instance_id = self.instance_mapper.map(instance)
            if self._pedalboard.data['instances'].get(instance_id, False):
                self._pedalboard.parameter_set(instance_id, port, value)
                addrs = self._pedalboard.data['instances'][instance_id]['addressing']
                addr = addrs.get(port, None)
                if addr:
                    addr['value'] = value
                    act = addr['actuator']
                    self.parameter_addressing_load(*act)

        def position_cb(instance, x, y):
            pass

        def plugin_add_cb(instance, uri, x, y):
            if not instance in self.instances:
                self._pedalboard.add_instance(uri, self.instance_mapper.map(instance), x=x, y=y)
                self.instances.append(instance)

        def delete_cb(instance):
            if instance in self.instances:
                self.instances.remove(instance)

        def connection_add_cb(instance_a, port_a, instance_b, port_b):
            pass

        def connection_delete_cb(instance_a, port_a, instance_b, port_b):
            pass

        def msg_cb(msg):
            for ws in self.websockets:
                ws.write_message(msg)

        def sr_cb(value):
            self.engine_samplerate = value

        self.host.msg_callback = msg_cb
        self.host.samplerate_value_callback = sr_cb

        yield gen.Task(lambda callback: self.host.get_engine_info(callback=callback))

        # Add ports
        for i in range(1, INGEN_NUM_AUDIO_INS+1):
            yield gen.Task(lambda callback: self.host.add_external_port("Audio In %i" % i, "Input", "Audio", callback=callback))

        for i in range(1, INGEN_NUM_AUDIO_OUTS+1):
            yield gen.Task(lambda callback: self.host.add_external_port("Audio Out %i" % i, "Output", "Audio", callback=callback))

        for i in range(2, INGEN_NUM_MIDI_INS+1):
            yield gen.Task(lambda callback: self.host.add_external_port("MIDI In %i" % i, "Input", "MIDI", callback=callback))

        for i in range(2, INGEN_NUM_MIDI_OUTS+1):
            yield gen.Task(lambda callback: self.host.add_external_port("MIDI Out %i" % i, "Output", "MIDI", callback=callback))

        yield gen.Task(lambda callback: self.host.initial_setup(callback=callback))

        self.host.position_callback = position_cb
        self.host.port_value_callback = port_value_cb
        self.host.plugin_add_callback = plugin_add_cb
        self.host.delete_callback = delete_cb
        self.host.connection_add_callback = connection_add_cb
        self.host.connection_delete_callback = connection_delete_cb

    def hmi_callback(self):
        if self.host_initialized:
            self.restore_last_pedalboard()
        logging.info("hmi initialized")
        self.hmi_initialized = True

    def reset(self, callback):
        gen = iter(copy.deepcopy(self.instances))
        def remove_all_plugins(r=True):
            try:
                self.remove(next(gen), remove_all_plugins)
            except StopIteration:
                callback(r)
        remove_all_plugins()

    def save_pedalboard(self, bundlepath, title, callback):
        def callback2(ok):
            self.bundlepath = bundlepath if ok else None
            callback(ok)

        self.host.set_pedalboard_name(title)
        self.host.save(os.path.join(bundlepath, "%s.ttl" % symbolify(title)), callback2)

    def load_pedalboard(self, bundlepath):
        # TODO
        self.bundlepath = bundlepath
        if self._load_pb_hack is not None:
            self._load_pb_hack(bundlepath)

    def setup_monitor(self):
        if self.monitor_server is None:
            from mod.monitor import MonitorServer
            self.monitor_server = MonitorServer()
            self.monitor_server.listen(12345)

            self.set_monitor("localhost", 12345, 1, self.add_tools)

    def add_tools(self, resp):
        if resp:
            self.add(CLIPMETER_URI, CLIPMETER_IN, self.setup_clipmeter_in, True)
            self.add(CLIPMETER_URI, CLIPMETER_OUT, self.setup_clipmeter_out, True)

    def setup_clipmeter_in(self, resp):
        if resp:
            self.connect("system:capture_1", "effect_%d:%s" % (CLIPMETER_IN, CLIPMETER_L), lambda r:None, True)
            self.connect("system:capture_2", "effect_%d:%s" % (CLIPMETER_IN, CLIPMETER_R), lambda r:None, True)
            self.parameter_monitor(CLIPMETER_IN, CLIPMETER_MON_L, ">=", 0, lambda r:None)
            self.parameter_monitor(CLIPMETER_IN, CLIPMETER_MON_R, ">=", 0, lambda r:None)

    def setup_clipmeter_out(self, resp):
        if resp:
            self.parameter_monitor(CLIPMETER_OUT, CLIPMETER_MON_L, ">=", 0, lambda r:None)
            self.parameter_monitor(CLIPMETER_OUT, CLIPMETER_MON_R, ">=", 0, lambda r:None)


    def tuner_set_input(self, input, callback):
        # TODO: implement
        self.disconnect("system:capture_%s" % self._tuner_port, "effect_%d:%s" % (TUNER, TUNER_PORT), lambda r:r, True)
        self._tuner_port = input
        self.connect("system:capture_%s" % input, "effect_%d:%s" % (TUNER, TUNER_PORT), callback, True)

    def tuner_set(self, status, callback):
        if "on" in status:
            self.tuner_on(callback)
        elif "off" in status:
            self.tuner_off(callback)

    def tuner_on(self, cb):
        def mon_tuner(ok):
            if ok:
                self.parameter_monitor(TUNER, TUNER_MON_PORT, ">=", 0, cb)

        def setup_tuner(ok):
            if ok:
                self._tuner = True
                self.connect("system:capture_%s" % self._tuner_port, "effect_%d:%s" % (TUNER, TUNER_PORT), mon_tuner, True)

        def mute_callback():
            self.add(TUNER_URI, TUNER, setup_tuner, True)
        self.mute(mute_callback)

    def tuner_off(self, cb):
        def callback():
            self.remove(TUNER, cb, True)
            self._tuner = False
        self.unmute(callback)

    def peakmeter_set(self, status, callback):
        if "on" in status:
            self.peakmeter_on(callback)
        elif "off" in status:
            self.peakmeter_off(callback)

    def peakmeter_on(self, cb):

        def mon_peak_in_l(ok):
            if ok:
                self.parameter_monitor(PEAKMETER_IN, PEAKMETER_MON_VALUE_L, ">=", -30, cb)
                self.parameter_monitor(PEAKMETER_IN, PEAKMETER_MON_PEAK_L, ">=", -30, cb)

        def mon_peak_in_r(ok):
            if ok:
                self.parameter_monitor(PEAKMETER_IN, PEAKMETER_MON_VALUE_R, ">=", -30, lambda r:None)
                self.parameter_monitor(PEAKMETER_IN, PEAKMETER_MON_PEAK_R, ">=", -30, lambda r:None)

        def mon_peak_out_l(ok):
            if ok:
                self.parameter_monitor(PEAKMETER_OUT, PEAKMETER_MON_VALUE_L, ">=", -30, lambda r:None)
                self.parameter_monitor(PEAKMETER_OUT, PEAKMETER_MON_PEAK_L, ">=", -30, lambda r:None)

        def mon_peak_out_r(ok):
            if ok:
                self.parameter_monitor(PEAKMETER_OUT, PEAKMETER_MON_VALUE_R, ">=", -30, lambda r:None)
                self.parameter_monitor(PEAKMETER_OUT, PEAKMETER_MON_PEAK_R, ">=", -30, lambda r:None)

        def setup_peak_in(ok):
            if ok:
                self.connect("system:capture_1", "effect_%d:%s" % (PEAKMETER_IN, PEAKMETER_L), mon_peak_in_l, True)
                self.connect("system:capture_2", "effect_%d:%s" % (PEAKMETER_IN, PEAKMETER_R), mon_peak_in_r, True)

        def setup_peak_out(ok):
            if ok:
                self._peakmeter = True
                for port in self._playback_1_connected_ports:
                    self.connect(port, "effect_%d:%s" % (PEAKMETER_OUT, PEAKMETER_L), mon_peak_out_l, True)
                for port in self._playback_2_connected_ports:
                    self.connect(port, "effect_%d:%s" % (PEAKMETER_OUT, PEAKMETER_R), mon_peak_out_r, True)

        self.add(PEAKMETER_URI, PEAKMETER_IN, setup_peak_in, True)
        self.add(PEAKMETER_URI, PEAKMETER_OUT, setup_peak_out, True)

    def peakmeter_off(self, cb):
        self.remove(PEAKMETER_IN, cb, True)
        self.remove(PEAKMETER_OUT, lambda r: None, True)
        self._tuner = False

    def hardware_connected(self, hwtyp, hwid, callback):
        callback(True)
        #open(os.path.join(HARDWARE_DIR, "%d_%d" % (hwtyp, hwid)), 'w')
        #callback(True)

    def hardware_disconnected(self, hwtype, hwid, callback):
        callback(True)
        #if os.path.exist():
        #    os.remove(os.path.join(HARDWARE_DIR, "%d_%d" % (hwtyp, hwid)), callback)
        #callback(True)

    # host commands

    def add(self, objid, instance, x, y, callback, loaded=False):
        self.host.add(objid, instance, x, y, callback)

    def remove(self, instance, callback, loaded=False):
        """
        affected_actuators = []
        if not loaded:
            affected_actuators = self._pedalboard.remove_instance(instance_id)
        def bufsize_callback(bufsize_changed):
            callback(True)
        def change_bufsize(ok):
            self.change_bufsize(self._pedalboard.get_bufsize(), bufsize_callback)

        def _callback(ok):
            if ok:
                self.hmi.control_rm(instance_id, ":all", change_bufsize)
                for addr in affected_actuators:
                    self.parameter_addressing_load(*addr)
            else:
                change_bufsize(ok)
        """
        if instance == "-1":
            self.reset(callback)
        else:
            self.host.remove(instance, callback)

    def bypass(self, instance, value, callback, loaded=False):
        value = 1 if int(value) > 0 else 0
        #if not loaded:
        #    self._pedalboard.bypass(instance_id, value)
        #self.recorder.bypass(instance, value)
        self.host.bypass(instance, value, callback)

    def connect(self, port_from, port_to, callback, loaded=False):
        #if not loaded:
        #    self._pedalboard.connect(port_from, port_to)

        # Cases below happen because we just save instance ID in pedalboard connection structure, not whole string
        #if not 'system' in port_from and not 'effect' in port_from:
        #    port_from = "effect_%s" % port_from
        #if not 'system' in port_to and not 'effect' in port_to:
        #    port_to = "effect_%s" % port_to

        #if "system" in port_to:
        #    def cb(result):
        #        if result:
        #            if port_to == "system:playback_1":
        #                self.connect(port_from, "effect_%d:%s" % (CLIPMETER_OUT, CLIPMETER_L), lambda r: r, True)
        #                self._playback_1_connected_ports.append(port_from)
        #                if self._peakmeter:
        #                    self.connect(port_from, "effect_%d:%s" % (PEAKMETER_OUT, PEAKMETER_L), lambda r: r, True)
        #            elif port_to == "system:playback_2":
        #                self.connect(port_from, "effect_%d:%s" % (CLIPMETER_OUT, CLIPMETER_R), lambda r: r, True)
        #                self._playback_2_connected_ports.append(port_from)
        #                if self._peakmeter:
        #                    self.connect(port_from, "effect_%d:%s" % (PEAKMETER_OUT, PEAKMETER_R), lambda r: r, True)
        #        callback(result)
        #else:
        #    cb = callback

        self.host.connect(port_from, port_to, callback)

    def format_port(self, port):
        if not 'system' in port and not 'effect' in port:
            port = "effect_%s" % port
        return port

    def disconnect(self, port_from, port_to, callback, loaded=False):
        """if not loaded:
            self._pedalboard.disconnect(port_from, port_to)

        port_from = self.format_port(port_from)
        port_to = self.format_port(port_to)

        if "system" in port_to:

            def cb(result):
                if result:
                    if port_to == "system:playback_1":
                        self.disconnect(port_from, "effect_%d:%s" % (CLIPMETER_OUT, CLIPMETER_L), lambda r: r, True)
                        if self._peakmeter:
                            self.disconnect(port_from, "effect_%d:%s" % (PEAKMETER_OUT, PEAKMETER_L), lambda r: r, True)
                        try:
                            self._playback_1_connected_ports.remove(port_from)
                        except ValueError:
                            pass
                    elif port_to == "system:playback_2":
                        self.disconnect(port_from, "effect_%d:%s" % (CLIPMETER_OUT, CLIPMETER_R), lambda r: r, True)
                        if self._peakmeter:
                            self.disconnect(port_from, "effect_%d:%s" % (PEAKMETER_OUT, PEAKMETER_R), lambda r: r, True)
                        try:
                            self._playback_2_connected_ports.remove(port_from)
                        except ValueError:
                            pass
                callback(result)
        else:
            cb = callback
        """
        self.host.disconnect(port_from, port_to, callback)

    def hmi_parameter_set(self, instance_id, port_id, value, callback):
        #self.browser.send(instance_id, port_id, value)
        self.parameter_set(instance_id, port_id, value, callback)

    def preset_load(self, instance_id, url, callback):
        self.host.preset_load(instance_id, url, callback)

    def parameter_set(self, port, value, callback, loaded=False):
        if port == ":bypass":
            # self.bypass(instance_id, value, callback)
            return
        #self.recorder.parameter(instance, port_id, value)
        self.host.param_set(port, value, callback)

    def parameter_get(self, port, callback):
        self.host.param_get(port, callback)

    def set_monitor(self, addr, port, status, callback):
        self.host.monitor(addr, port, status, callback)

    def parameter_monitor(self, instance_id, port_id, op, value, callback):
        self.host.param_monitor(instance_id, port_id, op, value, callback)

    # TODO: jack cpu load with ingen
    def jack_cpu_load(self, callback=lambda result: None):
        def cb(result):
            if result['ok']:
                pass
                #self.browser.send(99999, 'cpu_load', round(result['value']))
        self.host.cpu_load(cb)
    # END host commands

    # hmi commands
    def start_session(self, callback=None):
        self._playback_1_connected_ports = []
        self._playback_2_connected_ports = []

        def verify(resp):
            if callback:
                callback(resp)
            else:
                assert resp
        self.bank_address(0, 0, 1, 0, 0, lambda r: None)
        self.bank_address(0, 0, 1, 1, 0, lambda r: None)
        self.bank_address(0, 0, 1, 2, 0, lambda r: None)
        self.bank_address(0, 0, 1, 3, 0, lambda r: None)

        self.hmi.ui_con(verify)

    def end_session(self, callback):
        self.hmi.ui_dis(callback)

    def bypass_address(self, instance_id, hardware_type, hardware_id, actuator_type, actuator_id, value, label,
                       callback, loaded=False):
        self.parameter_address(instance_id, ":bypass", 'switch', label, 6, "none", value,
                               1, 0, 0, hardware_type, hardware_id, actuator_type,
                               actuator_id, [], callback, loaded)

    def parameter_addressing_next(self, hardware_type, hardware_id, actuator_type, actuator_id, callback):
        addrs = self._pedalboard.addressings[(hardware_type, hardware_id, actuator_type, actuator_id)]
        if len(addrs['addrs']) > 0:
            addrs['idx'] = (addrs['idx'] + 1) % len(addrs['addrs'])
            callback(True)
            self.parameter_addressing_load(hardware_type, hardware_id, actuator_type, actuator_id,
                                           addrs['idx'])
            return True
        #elif len(addrs['addrs']) <= 0:
        #   self.hmi.control_clean(hardware_type, hardware_id, actuator_type, actuator_id)
        callback(True)
        return False

    def parameter_addressing_load(self, hw_type, hw_id, act_type, act_id, idx=None):
        addrs = self._pedalboard.addressings[(hw_type, hw_id, act_type, act_id)]
        if idx == None:
            idx = addrs['idx']
        try:
            addressing = addrs['addrs'][idx]
        except IndexError:
            return
        self.hmi.control_add(addressing['instance_id'], addressing['port_id'], addressing['label'],
                             addressing['type'], addressing['unit'], addressing['value'],
                             addressing['maximum'], addressing['minimum'], addressing['steps'],
                             addressing['actuator'][0], addressing['actuator'][1],
                             addressing['actuator'][2], addressing['actuator'][3], len(addrs['addrs']), idx+1,
                             addressing.get('options', []))


    def parameter_address(self, port, addressing_type, label, ctype,
                          unit, current_value, maximum, minimum, steps,
                          hardware_type, hardware_id, actuator_type, actuator_id,
                          options, callback, loaded=False):
        # TODO the IHM parameters set by hardware.js should be here!
        # The problem is that we need port data, and getting it now is expensive
        """
        instance_id: effect instance
        port_id: control port
        addressing_type: 'range', 'switch' or 'tap_tempo'
        label: lcd display label
        ctype: 0 linear, 1 logarithm, 2 enumeration, 3 toggled, 4 trigger, 5 tap tempo, 6 bypass
        unit: string representing the parameter unit (hz, bpm, seconds, etc)
        hardware_type: the hardware model
        hardware_id: the id of the hardware where we find this actuator
        actuator_type: the encoder button type
        actuator_id: the encoder button number
        options: array of options, each one being a tuple (value, label)
        """
        instance_id = self.instance_mapper.map(port.split("/")[0])
        port_id = port.split("/")[1]
        if (hardware_type == -1 and
            hardware_id == -1 and
            actuator_type == -1 and
            actuator_id == -1):
            if not loaded:
                a = self._pedalboard.parameter_unaddress(instance_id, port_id)
                if a:
                    if not self.parameter_addressing_next(a[0], a[1], a[2], a[3], callback):
                        self.hmi.control_rm(instance_id, port_id, lambda r:None)
                else:
                    callback(True)
            else:
                self.hmi.control_rm(instance_id, port_id, callback)
            return

        if not loaded:
            old = self._pedalboard.parameter_address(instance_id, port_id,
                                                     addressing_type,
                                                     label,
                                                     ctype,
                                                     unit,
                                                     current_value,
                                                     maximum,
                                                     minimum,
                                                     steps,
                                                     hardware_type,
                                                     hardware_id,
                                                     actuator_type,
                                                     actuator_id,
                                                     options)

            self.hmi.control_add(instance_id,
                                 port_id,
                                 label,
                                 ctype,
                                 unit,
                                 current_value,
                                 maximum,
                                 minimum,
                                 steps,
                                 hardware_type,
                                 hardware_id,
                                 actuator_type,
                                 actuator_id,
                                 len(self._pedalboard.addressings[(hardware_type, hardware_id, actuator_type, actuator_id)]['addrs']),
                                 len(self._pedalboard.addressings[(hardware_type, hardware_id, actuator_type, actuator_id)]['addrs']),
                                 options,
                                 callback)
            if old:
                self.parameter_addressing_load(*old)
        else:
            callback(True)


    def bank_address(self, hardware_type, hardware_id, actuator_type, actuator_id, function, callback):
        """
        Function is an integer, meaning:
         - 0: Nothing (unaddress)
         - 1: True bypass
         - 2: Pedalboard up
         - 3: Pedalboard down
        """
        self.hmi.bank_config(hardware_type, hardware_id, actuator_type, actuator_id, function, callback)

    def ping(self, callback):
        self.hmi.ping(callback)

    def effect_position(self, instance, x, y):
        self.host.set_position(instance, x, y)
        #self._pedalboard.set_position(instance, x, y)

    def pedalboard_size(self, width, height):
        self.host.set_pedalboard_size(width, height)

    def clipmeter(self, pos, value):
        self._clipmeter.set(pos, value)

    def peakmeter(self, pos, value, peak, callback=None):
        cb = callback
        if not cb:
            cb = lambda r: r
        self.hmi.peakmeter(pos, value, peak, cb)

    def tuner(self, value, callback=None):
        cb = callback
        if not cb:
            cb = lambda r: r

        freq, note, cents = find_freqnotecents(value)
        self.hmi.tuner(freq, note, cents, cb)

    def start_recording(self):
        if self.player.playing:
            self.player.stop()
        self.recorder.start(self._pedalboard)

    def stop_recording(self):
        if self.recorder.recording:
            self.recording = self.recorder.stop()
            return self.recording

    def start_playing(self, stop_callback):
        if self.recorder.recording:
            self.recording = self.recorder.stop()
        def stop():
            self.unmute(stop_callback)
        def schedule_stop():
            ioloop.IOLoop.instance().add_timeout(timedelta(seconds=0.5), stop)
        def play():
            self.player.play(self.recording['handle'], schedule_stop)
        self.mute(play)

    def stop_playing(self):
        self.player.stop()

    def reset_recording(self):
        self.recording = None

    def mute(self, callback):
        self.set_audio_state(False, callback)
    def unmute(self, callback):
        self.set_audio_state(True, callback)

    def set_audio_state(self, state, callback):
        if self.mute_state == state:
            return callback()
        self.mute_state = state
        connections = self._pedalboard.data['connections']
        queue = []
        for connection in connections:
            if connection[2] == 'system' and connection[3].startswith('playback'):
                port_from = self.format_port(':'.join([str(x) for x in connection[:2]]))
                port_to = self.format_port(':'.join([str(x) for x in connection[2:]]))
                queue.append([port_from, port_to])
        def consume(result=None):
            if len(queue) == 0:
                return callback()
            nxt = queue.pop(0)
            if state:
                self.host.connect(nxt[0], nxt[1], consume)
            else:
                self.host.disconnect(nxt[0], nxt[1], consume)
        consume()

    def serialize_pedalboard(self):
        return self._pedalboard.serialize()

    def xrun(self, callback=None):
        cb = callback
        if not cb:
            cb = lambda r: r
        self.hmi.xrun(cb)
Esempio n. 2
0
class Session(object):
    def __init__(self):
        self.host_initialized = False

        self._tuner = False
        self._tuner_port = 1
        self._peakmeter = False

        self.monitor_server = None
        self.current_bank = None
        self.hmi_initialized = False

        # JACK client name of the backend
        self.backend_client_name = "ingen"

        # Used in mod-app to know when the current pedalboard changed
        self.pedalboard_changed_callback = lambda ok, bundlepath, title: None

        # For saving the current pedalboard bundlepath and title
        self.bundlepath = None
        self.title = None

        self.engine_samplerate = 48000  # default value
        self.jack_client = None
        self.xrun_count = 0
        self.xrun_count2 = 0

        self.ioloop = ioloop.IOLoop.instance()

        socketpath = os.environ.get("MOD_INGEN_SOCKET_URI",
                                    "unix:///tmp/ingen.sock")
        if DEV_HOST:
            self.host = FakeHost(socketpath)
        else:
            self.host = Host(socketpath)

        # Try to open real HMI
        hmiOpened = False

        if not DEV_HMI:
            self.hmi = HMI(HMI_SERIAL_PORT, HMI_BAUD_RATE,
                           self.hmi_initialized_cb)
            hmiOpened = self.hmi.sp is not None

        print("Using HMI =>", hmiOpened)

        if hmiOpened:
            # If all ok, use addressings
            self.addressings = Addressing(self.hmi)
        else:
            # Otherwise disable HMI entirely
            self.hmi = FakeHMI(HMI_SERIAL_PORT, HMI_BAUD_RATE,
                               self.hmi_initialized_cb)
            self.addressings = None

        self.recorder = Recorder()
        self.player = Player()
        self.mute_state = True
        self.recording = None
        self.instances = []
        self.screenshot_generator = ScreenshotGenerator()

        self._clipmeter = Clipmeter(self.hmi)
        self.websockets = []
        self.mididevuuids = []

        self.jack_cpu_load_timer = ioloop.PeriodicCallback(
            self.jack_cpu_load_timer_callback, 1000)
        self.jack_xrun_timer = ioloop.PeriodicCallback(
            self.jack_xrun_timer_callback, 500)

        self.ioloop.add_callback(self.init_jack)
        self.ioloop.add_callback(self.init_socket)

    def __del__(self):
        if self.jack_client is None:
            return
        jacklib.deactivate(self.jack_client)
        jacklib.client_close(self.jack_client)
        self.jack_client = None
        print("jacklib client deactivated")

    def get_hardware(self):
        if self.addressings is None:
            return {}
        hw = deepcopy(get_hardware())
        hw["addressings"] = self.addressings.get_addressings()
        return hw

    # -----------------------------------------------------------------------------------------------------------------
    # App utilities, needed only for mod-app

    def setupApp(self, clientName, pedalboardChangedCallback):
        self.backend_client_name = clientName
        self.pedalboard_changed_callback = pedalboardChangedCallback

    def reconnectApp(self):
        if self.host.sock is not None:
            self.host.sock.close()
            self.host.sock = None
        self.host.open_connection_if_needed(self.host_callback)

    # -----------------------------------------------------------------------------------------------------------------
    # Initialization

    def autoconnect_jack(self):
        if self.jack_client is None:
            return

        for i in range(1, INGEN_NUM_AUDIO_INS + 1):
            jacklib.connect(
                self.jack_client, "system:capture_%i" % i,
                "%s:audio_port_%i_in" % (self.backend_client_name, i))

        for i in range(1, INGEN_NUM_AUDIO_OUTS + 1):
            jacklib.connect(
                self.jack_client,
                "%s:audio_port_%i_out" % (self.backend_client_name, i),
                "system:playback_%i" % i)

        if not DEV_HMI:
            # this means we're using HMI, so very likely running MOD hardware
            jacklib.connect(self.jack_client, "alsa_midi:ttymidi MIDI out in",
                            "%s:midi_port_1_in" % self.backend_client_name)
            jacklib.connect(self.jack_client,
                            "%s:midi_port_1_out" % self.backend_client_name,
                            "alsa_midi:ttymidi MIDI in out")

    def init_jack(self):
        self.jack_client = jacklib.client_open(
            "%s-helper" % self.backend_client_name, jacklib.JackNoStartServer,
            None)
        self.xrun_count = 0
        self.xrun_count2 = 0

        if self.jack_client is None:
            return

        #jacklib.jack_set_port_registration_callback(self.jack_client, self.JackPortRegistrationCallback, None)
        jacklib.set_property_change_callback(self.jack_client,
                                             self.JackPropertyChangeCallback,
                                             None)
        #jacklib.set_xrun_callback(self.jack_client, self.JackXRunCallback, None)
        jacklib.on_shutdown(self.jack_client, self.JackShutdownCallback, None)
        jacklib.activate(self.jack_client)
        print("jacklib client activated")

        if INGEN_AUTOCONNECT:
            self.ioloop.add_timeout(timedelta(seconds=3.0),
                                    self.autoconnect_jack)

    def init_socket(self):
        self.host.open_connection_if_needed(self.host_callback)

    def hmi_initialized_cb(self):
        logging.info("hmi initialized")
        self.hmi_initialized = True
        self.hmi.clear()

        if self.addressings is not None:
            self.addressings.init_host()

    # -----------------------------------------------------------------------------------------------------------------
    # Timers (start and stop in sync with webserver IOLoop)

    def start_timers(self):
        self.jack_cpu_load_timer.start()
        self.jack_xrun_timer.start()

    def stop_timers(self):
        self.jack_xrun_timer.stop()
        self.jack_cpu_load_timer.stop()

    # -----------------------------------------------------------------------------------------------------------------
    # Webserver callbacks, called from the browser (see webserver.py)
    # These will be called as a reponse to an action in the browser.
    # A callback must always be used unless specified otherwise.

    # Add a new plugin, starts enabled (ie, not bypassed)
    def web_add(self, instance, uri, x, y, callback):
        self.host.add_plugin(instance, uri, True, x, y, callback)

    # Remove a plugin
    def web_remove(self, instance, callback):
        self.host.remove_plugin(instance, callback)

    # Set a plugin parameter
    # We use ":bypass" symbol for on/off state
    def web_parameter_set(self, port, value, callback):
        instance, port2 = port.rsplit("/", 1)

        if port2 == ":bypass":
            value = value >= 0.5
            self.host.enable(instance, not value, callback)
        else:
            self.host.param_set(port, value, callback)

        #self.recorder.parameter(port, value)

    # Address a plugin parameter
    def web_parameter_address(self, port, actuator_uri, label, maximum,
                              minimum, value, steps, callback):
        if self.addressings is None or not self.hmi_initialized:
            callback(False)
            return

        instance, port2 = port.rsplit("/", 1)
        self.addressings.address(instance, port2, actuator_uri, label, maximum,
                                 minimum, value, steps, callback)

    # Set a parameter for MIDI learn
    def web_parameter_midi_learn(self, port, callback):
        self.host.midi_learn(port, callback)

    # Load a plugin preset
    def web_preset_load(self, instance, uri, callback):
        self.host.preset_load(instance, uri, callback)

    # Set a plugin block position within the canvas
    def web_set_position(self, instance, x, y, callback):
        self.host.set_position(instance, x, y, callback)

    # Connect 2 ports
    def web_connect(self, port_from, port_to, callback):
        self.host.connect(port_from, port_to, callback)

    # Disconnect 2 ports
    def web_disconnect(self, port_from, port_to, callback):
        self.host.disconnect(port_from, port_to, callback)

    # Save the current pedalboard
    # The order of events is:
    #  1. set the graph name to 'title'
    #  2. ask backend to save
    #  3. create manifest.ttl
    #  4. create addressings.json file
    # Step 2 is asynchronous, so it happens while 3 and 4 are taking place.
    def web_save_pedalboard(self, title, asNew, callback):
        titlesym = symbolify(title)

        # Save over existing bundlepath
        if self.bundlepath and os.path.exists(
                self.bundlepath) and os.path.isdir(
                    self.bundlepath) and not asNew:
            bundlepath = self.bundlepath

        # Save new
        else:
            lv2path = os.path.expanduser("~/.lv2/")  # FIXME: cross-platform
            trypath = os.path.join(lv2path, "%s.pedalboard" % titlesym)

            # if trypath already exists, generate a random bundlepath based on title
            if os.path.exists(trypath):
                from random import randint

                while True:
                    trypath = os.path.join(
                        lv2path,
                        "%s-%i.pedalboard" % (titlesym, randint(1, 99999)))
                    if os.path.exists(trypath):
                        continue
                    bundlepath = trypath
                    break

            # trypath doesn't exist yet, use it
            else:
                bundlepath = trypath

                # just in case..
                if not os.path.exists(lv2path):
                    os.mkdir(lv2path)

            os.mkdir(bundlepath)

        # Various steps triggered by callbacks

        def step1(ok):
            if ok:
                self.host.save(os.path.join(bundlepath, "%s.ttl" % titlesym),
                               step2)
            else:
                callback(False)

        def step2(ok):
            if ok:
                self.bundlepath = bundlepath
                self.title = title
            else:
                self.bundlepath = None
                self.title = None

            self.pedalboard_changed_callback(ok, bundlepath, title)

            if not ok:
                callback(False)
                return

            # Create a custom manifest.ttl, not created by ingen because we want *.pedalboard extension
            with open(os.path.join(bundlepath, "manifest.ttl"), 'w') as fh:
                fh.write("""\
@prefix ingen: <http://drobilla.net/ns/ingen#> .
@prefix lv2:   <http://lv2plug.in/ns/lv2core#> .
@prefix pedal: <http://moddevices.com/ns/modpedal#> .
@prefix rdfs:  <http://www.w3.org/2000/01/rdf-schema#> .

<%s.ttl>
    lv2:prototype ingen:GraphPrototype ;
    a lv2:Plugin ,
        ingen:Graph ,
        pedal:Pedalboard ;
    rdfs:seeAlso <%s.ttl> .
""" % (titlesym, titlesym))

            # Create a addressings.json file
            addressings = self.addressings.get_addressings(
            ) if self.addressings is not None else {}

            with open(os.path.join(bundlepath, "addressings.json"), 'w') as fh:
                json.dump(addressings, fh)

            # All ok!
            callback(True)

        # start here
        self.host.set_pedalboard_name(title, step1)

    # Get list of Hardware MIDI devices
    # returns (devsInUse, devList)
    def web_get_midi_device_list(self):
        return self.get_midi_ports(
            self.backend_client_name), self.get_midi_ports("alsa_midi")

    # Set the selected MIDI devices to @a newDevs
    # Will remove or add new JACK ports as needed
    @gen.engine
    def web_set_midi_devices(self, newDevs):
        curDevs = self.get_midi_ports(self.backend_client_name)

        # remove
        for dev in curDevs:
            if dev in newDevs:
                continue
            if dev.startswith("MIDI Port-"):
                continue
            dev, modes = dev.rsplit(" (", 1)
            jacklib.disconnect(self.jack_client, "alsa_midi:%s in" % dev,
                               self.backend_client_name + ":control_in")

            def remove_external_port_in(callback):
                self.host.remove_external_port(dev + " in")
                callback(True)

            def remove_external_port_out(callback):
                self.host.remove_external_port(dev + " out")
                callback(True)

            yield gen.Task(remove_external_port_in)

            if "out" in modes:
                yield gen.Task(remove_external_port_out)

        # add
        for dev in newDevs:
            if dev in curDevs:
                continue
            dev, modes = dev.rsplit(" (", 1)

            def add_external_port_in(callback):
                self.host.add_external_port(dev + " in", "Input", "MIDI")
                callback(True)

            def add_external_port_out(callback):
                self.host.add_external_port(dev + " out", "Output", "MIDI")
                callback(True)

            yield gen.Task(add_external_port_in)

            if "out" in modes:
                yield gen.Task(add_external_port_out)

    # Send a ping to HMI
    def web_ping_hmi(self, callback):
        self.hmi.ping(callback)

    # A new webbrowser page has been open
    # We need to cache its socket address and send any msg callbacks to it
    def websocket_opened(self, ws):
        self.websockets.append(ws)

        self.host.open_connection_if_needed(self.host_callback)
        self.host.get("/graph")

    # Webbrowser page closed
    def websocket_closed(self, ws):
        self.websockets.remove(ws)

    # -----------------------------------------------------------------------------------------------------------------
    # JACK callbacks, called by JACK itself
    # We must take care to ensure not to block for long periods of time or else audio will skip or xrun.

    # Callback for when a port appears or disappears
    # We use this to trigger a auto-connect mode
    #def JackPortRegistrationCallback(self, port, registered, arg):
    #if self.jack_client is None:
    #return
    #if not registered:
    #return

    # Callback for when a client or port property changes.
    # We use this to know the full length name of ingen created ports.
    def JackPropertyChangeCallback(self, subject, key, change, arg):
        if self.jack_client is None:
            return
        if change != jacklib.PropertyCreated:
            return
        if key != jacklib.bJACK_METADATA_PRETTY_NAME:
            return

        self.mididevuuids.append(subject)
        self.ioloop.add_callback(self.jack_midi_devs_callback)

    # Callback for when an xrun occurs
    def JackXRunCallback(self, arg):
        self.xrun_count += 1
        return 0

    # Callback for when JACK has shutdown or our client zombified
    def JackShutdownCallback(self, arg):
        self.jack_client = None

    # -----------------------------------------------------------------------------------------------------------------
    # Misc/Utility functions
    # We use these to save possibly duplicated code.

    # Get all available MIDI ports of a specific JACK client.
    def get_midi_ports(self, client_name):
        if self.jack_client is None:
            return []

        # get input and outputs separately
        in_ports = charPtrPtrToStringList(
            jacklib.get_ports(
                self.jack_client, client_name + ":",
                jacklib.JACK_DEFAULT_MIDI_TYPE,
                jacklib.JackPortIsPhysical | jacklib.JackPortIsOutput
                if client_name == "alsa_midi" else jacklib.JackPortIsInput))
        out_ports = charPtrPtrToStringList(
            jacklib.get_ports(
                self.jack_client, client_name + ":",
                jacklib.JACK_DEFAULT_MIDI_TYPE,
                jacklib.JackPortIsPhysical | jacklib.JackPortIsInput
                if client_name == "alsa_midi" else jacklib.JackPortIsOutput))

        if client_name != "alsa_midi":
            if "ingen:control_in" in in_ports:
                in_ports.remove("ingen:control_in")
            if "ingen:control_out" in out_ports:
                out_ports.remove("ingen:control_out")

            for i in range(len(in_ports)):
                uuid = jacklib.port_uuid(
                    jacklib.port_by_name(self.jack_client, in_ports[i]))
                ret, value, type_ = jacklib.get_property(
                    uuid, jacklib.JACK_METADATA_PRETTY_NAME)
                if ret == 0 and type_ == b"text/plain":
                    in_ports[i] = charPtrToString(value)

            for i in range(len(out_ports)):
                uuid = jacklib.port_uuid(
                    jacklib.port_by_name(self.jack_client, out_ports[i]))
                ret, value, type_ = jacklib.get_property(
                    uuid, jacklib.JACK_METADATA_PRETTY_NAME)
                if ret == 0 and type_ == b"text/plain":
                    out_ports[i] = charPtrToString(value)

        # remove suffixes from ports
        in_ports = [
            port.replace(client_name + ":", "", 1).rsplit(" in", 1)[0]
            for port in in_ports
        ]
        out_ports = [
            port.replace(client_name + ":", "", 1).rsplit(" out", 1)[0]
            for port in out_ports
        ]

        # add our own suffix now
        ports = []
        for port in in_ports:
            #if "Midi Through" in port:
            #continue
            if port in ("jackmidi", "OSS sequencer"):
                continue
            ports.append(port +
                         (" (in+out)" if port in out_ports else " (in)"))

        return ports

    # -----------------------------------------------------------------------------------------------------------------
    # Timer callbacks
    # These are functions called by the IO loop at regular intervals.

    # Single-shot callback that automatically connects new backend JACK MIDI ports to their hardware counterparts.
    def jack_midi_devs_callback(self):
        while len(self.mididevuuids) != 0:
            subject = self.mididevuuids.pop()

            ret, value, type_ = jacklib.get_property(
                subject, jacklib.JACK_METADATA_PRETTY_NAME)
            if ret != 0:
                continue
            if type_ != b"text/plain":
                continue

            value = charPtrToString(value)
            if not (value.endswith(" in") or value.endswith(" out")):
                continue

            mod_name = "%s:%s" % (self.backend_client_name,
                                  value.replace(" ", "_").replace("-",
                                                                  "_").lower())
            midi_name = "alsa_midi:%s" % value

            # All good, make connection now
            if value.endswith(" in"):
                jacklib.connect(self.jack_client, midi_name, mod_name)
                jacklib.connect(self.jack_client, midi_name,
                                self.backend_client_name + ":control_in")
            else:
                jacklib.connect(self.jack_client, mod_name, midi_name)

    # Callback for getting the current JACK cpu load and report it to the browser side.
    def jack_cpu_load_timer_callback(self):
        if self.jack_client is not None:
            msg = """[]
            a <http://lv2plug.in/ns/ext/patch#Set> ;
            <http://lv2plug.in/ns/ext/patch#subject> </engine/> ;
            <http://lv2plug.in/ns/ext/patch#property> <http://moddevices/ns/modpedal#cpuload> ;
            <http://lv2plug.in/ns/ext/patch#value> "%.1f" .
            """ % jacklib.cpu_load(self.jack_client)

            for ws in self.websockets:
                ws.write_message(msg)

    # Callback that checks if xruns have occured.
    def jack_xrun_timer_callback(self):
        for i in range(self.xrun_count2, self.xrun_count):
            self.xrun_count2 += 1
            #self.hmi.xrun()

    # -----------------------------------------------------------------------------------------------------------------
    # TODO
    # Everything after this line is yet to be documented

    @gen.engine
    def host_callback(self):
        if self.host_initialized:
            return

        self.host_initialized = True

        def msg_callback(msg):
            for ws in self.websockets:
                ws.write_message(msg)

        def saved_callback(bundlepath):
            if add_bundle_to_lilv_world(bundlepath):
                pass  #self.host.add_bundle(bundlepath)
            self.screenshot_generator.schedule_screenshot(bundlepath)

        def samplerate_callback(srate):
            self.engine_samplerate = srate

        def plugin_added_callback(instance, uri, enabled, x, y):
            if instance not in self.instances:
                self.instances.append(instance)

        def plugin_removed_callback(instance):
            if instance in self.instances:
                self.instances.remove(instance)

        self.host.msg_callback = msg_callback
        self.host.saved_callback = saved_callback
        self.host.samplerate_callback = samplerate_callback
        self.host.plugin_added_callback = plugin_added_callback
        self.host.plugin_removed_callback = plugin_removed_callback

        yield gen.Task(self.host.initial_setup)

        # Add ports
        for i in range(1, INGEN_NUM_AUDIO_INS + 1):
            yield gen.Task(lambda callback: self.host.add_external_port(
                "Audio Port-%i in" % i, "Input", "Audio", callback))

        for i in range(1, INGEN_NUM_AUDIO_OUTS + 1):
            yield gen.Task(lambda callback: self.host.add_external_port(
                "Audio Port-%i out" % i, "Output", "Audio", callback))

        for i in range(1, INGEN_NUM_MIDI_INS + 1):
            yield gen.Task(lambda callback: self.host.add_external_port(
                "MIDI Port-%i in" % i, "Input", "MIDI", callback))

        for i in range(1, INGEN_NUM_MIDI_OUTS + 1):
            yield gen.Task(lambda callback: self.host.add_external_port(
                "MIDI Port-%i out" % i, "Output", "MIDI", callback))

        for i in range(1, INGEN_NUM_CV_INS + 1):
            yield gen.Task(lambda callback: self.host.add_external_port(
                "CV Port-%i in" % i, "Input", "CV", callback))

        for i in range(1, INGEN_NUM_CV_OUTS + 1):
            yield gen.Task(lambda callback: self.host.add_external_port(
                "CV Port-%i out" % i, "Output", "CV", callback))

    def load_pedalboard(self, bundlepath, title):
        self.bundlepath = bundlepath
        self.title = title
        self.host.load(bundlepath)
        self.pedalboard_changed_callback(True, bundlepath, title)

    def reset(self, callback):
        self.bundlepath = None
        self.title = None

        # Callback from socket
        def remove_next_plugin(ok):
            if not ok:
                callback(False)
                return

            try:
                instance = self.instances.pop(0)
            except IndexError:
                callback(True)
                return

            self.host.remove_plugin(instance, remove_next_plugin)

        # Callback from HMI, ignore ok status
        def remove_all_plugins(ok):
            remove_next_plugin(True)

        # Reset addressing data
        if self.addressings is not None:
            self.addressings.clear()

        # Wait for HMI if available
        if self.hmi_initialized:
            self.hmi.clear(remove_all_plugins)
        else:
            remove_next_plugin(True)

        self.pedalboard_changed_callback(True, "", "")

    #def setup_monitor(self):
    #if self.monitor_server is None:
    #from mod.monitor import MonitorServer
    #self.monitor_server = MonitorServer()
    #self.monitor_server.listen(12345)

    #self.set_monitor("localhost", 12345, 1, self.add_tools)

    #def add_tools(self, resp):
    #if resp:
    #self.add(CLIPMETER_URI, CLIPMETER_IN, self.setup_clipmeter_in, True)
    #self.add(CLIPMETER_URI, CLIPMETER_OUT, self.setup_clipmeter_out, True)

    #def setup_clipmeter_in(self, resp):
    #if resp:
    #self.connect("system:capture_1", "effect_%d:%s" % (CLIPMETER_IN, CLIPMETER_L), lambda r:None, True)
    #self.connect("system:capture_2", "effect_%d:%s" % (CLIPMETER_IN, CLIPMETER_R), lambda r:None, True)
    #self.parameter_monitor(CLIPMETER_IN, CLIPMETER_MON_L, ">=", 0, lambda r:None)
    #self.parameter_monitor(CLIPMETER_IN, CLIPMETER_MON_R, ">=", 0, lambda r:None)

    #def setup_clipmeter_out(self, resp):
    #if resp:
    #self.parameter_monitor(CLIPMETER_OUT, CLIPMETER_MON_L, ">=", 0, lambda r:None)
    #self.parameter_monitor(CLIPMETER_OUT, CLIPMETER_MON_R, ">=", 0, lambda r:None)

    # host commands

    def bypass(self, instance, value, callback):
        value = int(value) > 0
        #if not loaded:
        #    self._pedalboard.bypass(instance_id, value)
        #self.recorder.bypass(instance, value)
        self.host.enable(instance, value, callback)

    def format_port(self, port):
        if not 'system' in port and not 'effect' in port:
            port = "effect_%s" % port
        return port

    #def set_monitor(self, addr, port, status, callback):
    #self.host.monitor(addr, port, status, callback)

    #def parameter_monitor(self, instance_id, port_id, op, value, callback):
    #self.host.param_monitor(instance_id, port_id, op, value, callback)

    # END host commands

    # hmi commands
    def start_session(self, callback=None):
        def verify(resp):
            if callback:
                callback(resp)
            else:
                assert (resp)

        self.bank_address(0, 0, 1, 0, 0, lambda r: None)
        self.bank_address(0, 0, 1, 1, 0, lambda r: None)
        self.bank_address(0, 0, 1, 2, 0, lambda r: None)
        self.bank_address(0, 0, 1, 3, 0, lambda r: None)

        self.hmi.ui_con(verify)

    def end_session(self, callback):
        self.hmi.ui_dis(callback)

    def bank_address(self, hardware_type, hardware_id, actuator_type,
                     actuator_id, function, callback):
        """
        Function is an integer, meaning:
         - 0: Nothing (unaddress)
         - 1: True bypass
         - 2: Pedalboard up
         - 3: Pedalboard down
        """
        self.hmi.bank_config(hardware_type, hardware_id, actuator_type,
                             actuator_id, function, callback)

    def pedalboard_size(self, width, height, callback):
        self.host.set_pedalboard_size(width, height, callback)

    def clipmeter(self, pos, value):
        self._clipmeter.set(pos, value)

    def peakmeter(self, pos, value, peak, callback=None):
        cb = callback
        if not cb:
            cb = lambda r: r
        self.hmi.peakmeter(pos, value, peak, cb)

    def tuner(self, value, callback=None):
        cb = callback
        if not cb:
            cb = lambda r: r

        freq, note, cents = find_freqnotecents(value)
        self.hmi.tuner(freq, note, cents, cb)

    def start_recording(self):
        if self.player.playing:
            self.player.stop()
        self.recorder.start(self.backend_client_name)

    def stop_recording(self):
        if self.recorder.recording:
            self.recording = self.recorder.stop()
            return self.recording

    def start_playing(self, stop_callback):
        if self.recorder.recording:
            self.recording = self.recorder.stop()

        def stop():
            self.unmute(stop_callback)

        def schedule_stop():
            self.ioloop.add_timeout(timedelta(seconds=0.5), stop)

        def play():
            self.player.play(self.recording['handle'], schedule_stop)

        self.mute(play)

    def stop_playing(self):
        self.player.stop()

    def reset_recording(self):
        self.recording = None

    def mute(self, callback):
        return
        #self.set_audio_state(False, callback)

    def unmute(self, callback):
        return
Esempio n. 3
0
class Addressing(object):
    def __init__(self, hmi):
        self.host = None
        self.hmi  = hmi
        self.mapper = InstanceIdMapper()
        self.banks = []
        self.instances = {}
        self._init_addressings()

        # Register HMI protocol callbacks
        Protocol.register_cmd_callback("hw_con", self.hmi_hardware_connected)
        Protocol.register_cmd_callback("hw_dis", self.hmi_hardware_disconnected)
        Protocol.register_cmd_callback("banks", self.hmi_list_banks)
        Protocol.register_cmd_callback("pedalboards", self.hmi_list_bank_pedalboards)
        Protocol.register_cmd_callback("pedalboard", self.hmi_load_bank_pedalboard)
        Protocol.register_cmd_callback("control_get", self.hmi_parameter_get)
        Protocol.register_cmd_callback("control_set", self.hmi_parameter_set)
        Protocol.register_cmd_callback("control_next", self.hmi_parameter_addressing_next)
        #Protocol.register_cmd_callback("peakmeter", self.peakmeter_set)
        #Protocol.register_cmd_callback("tuner", self.tuner_set)
        #Protocol.register_cmd_callback("tuner_input", self.tuner_set_input)
        #Protocol.register_cmd_callback("pedalboard_save", self.save_current_pedalboard)
        #Protocol.register_cmd_callback("pedalboard_reset", self.reset_current_pedalboard)
        #Protocol.register_cmd_callback("jack_cpu_load", self.jack_cpu_load)

    def clear(self):
        self.banks = []
        self.instances = {}
        self._init_addressings()

    def get_addressings(self):
        addressings = {}
        for uri, addressing in self.addressings.items():
            addrs = []
            for addr in addressing['addrs']:
                addrs.append({
                    'instance': self.mapper.get_instance(addr['instance_id']),
                    'port'    : addr['port'],
                    'label'   : addr['label'],
                    'minimum' : addr['minimum'],
                    'maximum' : addr['maximum'],
                    'steps'   : addr['steps'],
                    'value'   : addr['value'],
                })
            addressings[uri] = addrs
        return addressings

    # -----------------------------------------------------------------------------------------------------------------

    # Init our ingen host class
    # This is only called when the HMI responds to our initial ping (and it's thus initialized)
    # The reason for this being a separate init function is because we don't need it when HMI is off
    def init_host(self):
        # We need our own host instance so that messages get propagated correctly by ingen
        # Later on this code will be a separate application so it all fits anyway
        self.host = Host(os.getenv("MOD_INGEN_SOCKET_URI", "unix:///tmp/ingen.sock"))

        def loaded_callback(bundlepath):
            self._load_addressings(bundlepath)

        def plugin_added_callback(instance, uri, enabled, x, y):
            self._add_instance(instance, uri, not enabled)

        def plugin_removed_callback(instance):
            self._remove_instance(instance)

        def plugin_enabled_callback(instance, enabled):
            self._set_bypassed(instance, not enabled)

        def port_value_callback(port, value):
            instance, port = port.rsplit("/", 1)
            self._set_value(instance, port, value)

        self.host.loaded_callback = loaded_callback
        self.host.plugin_added_callback = plugin_added_callback
        self.host.plugin_removed_callback = plugin_removed_callback
        self.host.port_value_callback = port_value_callback

        self.host.open_connection_if_needed(self.host_callback)

    def host_callback(self):
        self.host.get("/graph")

    # -----------------------------------------------------------------------------------------------------------------

    def _load_addressings(self, bundlepath):
        datafile = os.path.join(bundlepath, "addressings.json")
        if not os.path.exists(datafile):
            return

        with open(datafile, 'r') as fh:
            data = fh.read()
        data = json.loads(data)

        self._init_addressings()

        stopNow = False
        def callback(ok):
            if not ok:
                stopNow = True

        for actuator_uri in data:
            for addr in data[actuator_uri]:
                self.address(addr["instance"], addr["port"], actuator_uri, addr["label"], addr["maximum"], addr["minimum"], addr['value'], addr["steps"], callback)
                if stopNow: break
            if stopNow: break

    def _add_instance(self, instance, uri, bypassed):
        instance_id = self.mapper.get_id(instance)

        self.instances[instance] = {
              'id': instance_id,
              'instance': instance,
              'uri': uri,
              'bypassed': bypassed,
              'addressing': {}, # symbol: addressing
              'ports': {},      # symbol: value
        }
        logging.info('[addressing] Added instance %s' % instance)

    def _remove_instance(self, instance):
        instance_id = self.mapper.get_id(instance)

        # Remove the instance
        try:
            self.instances.pop(instance)
        except KeyError:
            logging.error('[addressing] Cannot remove unknown instance %s' % instance)

        # Remove addressings of that instance
        #affected_actuators = {}
        for actuator_uri, addressing in self.addressings.items():
            i = 0
            while i < len(addressing['addrs']):
                if addressing['addrs'][i].get('instance_id') == instance_id:
                    addressing['addrs'].pop(i)
                    if addressing['idx'] >= i:
                        addressing['idx'] -= 1
                    #affected_actuators[actuator_uri] = addressing['idx']
                else:
                    i += 1

        self.hmi.control_rm(instance_id, ":all")
        #for addr in affected_actuators:
            #self.parameter_addressing_load(*addr)

        logging.info('[addressing] Removed instance %s' % instance)
        #return [ list(act) + [idx] for act, idx in affected_actuators.items() ]

    def _set_bypassed(self, instance, bypassed):
        data = self.instances.get(instance, None)
        if data is None:
            return
        data['bypassed'] = bypassed

        addr = data['addressing'].get(':bypass', None)
        if addr is None:
            return
        addr['value'] = 1 if bypassed else 0

    def _set_value(self, instance, port, value):
        data = self.instances.get(instance, None)
        if data is None:
            return
        data['ports'][port] = value

        addr = data['addressing'].get(port, None)
        if addr is None:
            return
        addr['value'] = value

    # -----------------------------------------------------------------------------------------------------------------

    def address(self, instance, port, actuator_uri, label, maximum, minimum, value, steps, callback):
        instance_id      = self.mapper.get_id(instance)
        old_actuator_uri = self._unaddress(instance, port)

        if (not actuator_uri) or actuator_uri == "null":
            self.hmi.control_rm(instance_id, port, callback)
            if old_actuator_uri is not None:
                  old_actuator_hw = self._uri2hw_map[old_actuator_uri]
                  self._address_next(old_actuator_hw)
            return

        data = self.instances.get(instance, None)
        if data is None:
            callback(False)
            return

        options = []

        if port == ":bypass":
            ctype = ADDRESSING_CTYPE_BYPASS
            unit  = "none"

        else:
            for port_info in get_plugin_info(data["uri"])["ports"]["control"]["input"]:
                if port_info["symbol"] != port:
                    continue
                break
            else:
                callback(False)
                return

            pprops = port_info["properties"]
            unit   = port_info["units"]["symbol"] if "symbol" in port_info["units"] else "none"

            if "toggled" in pprops:
                ctype = ADDRESSING_CTYPE_TOGGLED
            elif "integer" in pprops:
                ctype = ADDRESSING_CTYPE_INTEGER
            else:
                ctype = ADDRESSING_CTYPE_LINEAR

            if "logarithmic" in pprops:
                ctype |= ADDRESSING_CTYPE_LOGARITHMIC
            if "trigger" in pprops:
                ctype |= ADDRESSING_CTYPE_TRIGGER
            if "tap_tempo" in pprops: # TODO
                ctype |= ADDRESSING_CTYPE_TAP_TEMPO

            if len(port_info["scalePoints"]) >= 2: # and "enumeration" in pprops:
                ctype |= ADDRESSING_CTYPE_SCALE_POINTS|ADDRESSING_CTYPE_ENUMERATION

                if "enumeration" in pprops:
                    ctype |= ADDRESSING_CTYPE_ENUMERATION

                for scalePoint in port_info["scalePoints"]:
                    options.append((scalePoint["value"], scalePoint["label"]))

            del port_info, pprops

        addressing = {
            'actuator_uri': actuator_uri,
            'instance_id': instance_id,
            'port': port,
            'label': label,
            'type': ctype,
            'unit': unit,
            'minimum': minimum,
            'maximum': maximum,
            'value': value,
            'steps': steps,
            'options': options,
        }
        self.instances[instance]['addressing'][port] = addressing
        self.addressings[actuator_uri]['addrs'].append(addressing)
        self.addressings[actuator_uri]['idx'] = len(self.addressings[actuator_uri]['addrs']) - 1

        if old_actuator_uri is not None:
            self._addressing_load(old_actuator_uri)

        self._addressing_load(actuator_uri, callback)

    # -----------------------------------------------------------------------------------------------------------------
    # HMI callbacks, called by HMI via serial

    def hmi_hardware_connected(self, hardware_type, hardware_id, callback):
        logging.info("hmi hardware connected")
        callback(True)

    def hmi_hardware_disconnected(self, hardware_type, hardware_id, callback):
        logging.info("hmi hardware disconnected")
        callback(True)

    def hmi_list_banks(self, callback):
        logging.info("hmi list banks")
        self.banks = list_banks()
        banks = " ".join('"%s" %d' % (bank['title'], i) for i,bank in enumerate(self.banks))
        callback(True, banks)

    def hmi_list_bank_pedalboards(self, bank_id, callback):
        logging.info("hmi list bank pedalboards")
        if bank_id < len(self.banks):
            #pedalboards = " ".join('"%s" %d' % (pb['title'], i) for i,pb in enumerate(self.banks[bank_id]['pedalboards']))
            pedalboards = " ".join('"%s" "%s"' % (pb['title'], pb['uri']) for pb in self.banks[bank_id]['pedalboards'])
        else:
            pedalboards = ""
        callback(True, pedalboards)

    def hmi_load_bank_pedalboard(self, bank_id, pedalboard_uri, callback):
        logging.info("hmi load bank pedalboard")

        #if bank_id >= len(self.banks):
            #print("ERROR in addressing.py: bank id out of bounds")
            #return

        #pedalboards = self.banks[bank_id]['pedalboards']
        #if pedalboard_id >= len(pedalboards):
            #print("ERROR in addressing.py: pedalboard id out of bounds")
            #return

        #uri = pedalboards[pedalboard_id]['uri']

        self.host.load_uri(pedalboard_uri)

    def hmi_parameter_get(self, instance_id, port, callback):
        logging.info("hmi parameter get")
        instance = self.mapper.get_instance(instance_id)
        callback(self.instances[instance]['ports'][port])

    def hmi_parameter_set(self, instance_id, port, value, callback=None):
        logging.info("hmi parameter set")
        instance = self.mapper.get_instance(instance_id)

        if port == ":bypass":
            self._set_bypassed(instance, bool(value))
            if self.host is not None:
                self.host.enable(instance, not value, callback)

        else:
            self._set_value(instance, port, value)
            if self.host is not None:
                self.host.param_set("%s/%s" % (instance, port), value, callback)

    def hmi_parameter_addressing_next(self, hardware_type, hardware_id, actuator_type, actuator_id, callback):
        logging.info("hmi parameter addressing next")
        actuator_hw = (hardware_type, hardware_id, actuator_type, actuator_id)
        self._address_next(actuator_hw, callback)

    # -----------------------------------------------------------------------------------------------------------------

    #def peakmeter_set(self, status, callback):
        #if "on" in status:
            #self.peakmeter_on(callback)
        #elif "off" in status:
            #self.peakmeter_off(callback)

    #def peakmeter_on(self, cb):

        #def mon_peak_in_l(ok):
            #if ok:
                #self.parameter_monitor(PEAKMETER_IN, PEAKMETER_MON_VALUE_L, ">=", -30, cb)
                #self.parameter_monitor(PEAKMETER_IN, PEAKMETER_MON_PEAK_L, ">=", -30, cb)

        #def mon_peak_in_r(ok):
            #if ok:
                #self.parameter_monitor(PEAKMETER_IN, PEAKMETER_MON_VALUE_R, ">=", -30, lambda r:None)
                #self.parameter_monitor(PEAKMETER_IN, PEAKMETER_MON_PEAK_R, ">=", -30, lambda r:None)

        #def mon_peak_out_l(ok):
            #if ok:
                #self.parameter_monitor(PEAKMETER_OUT, PEAKMETER_MON_VALUE_L, ">=", -30, lambda r:None)
                #self.parameter_monitor(PEAKMETER_OUT, PEAKMETER_MON_PEAK_L, ">=", -30, lambda r:None)

        #def mon_peak_out_r(ok):
            #if ok:
                #self.parameter_monitor(PEAKMETER_OUT, PEAKMETER_MON_VALUE_R, ">=", -30, lambda r:None)
                #self.parameter_monitor(PEAKMETER_OUT, PEAKMETER_MON_PEAK_R, ">=", -30, lambda r:None)

        #def setup_peak_in(ok):
            #if ok:
                #self.connect("system:capture_1", "effect_%d:%s" % (PEAKMETER_IN, PEAKMETER_L), mon_peak_in_l, True)
                #self.connect("system:capture_2", "effect_%d:%s" % (PEAKMETER_IN, PEAKMETER_R), mon_peak_in_r, True)

        #def setup_peak_out(ok):
            #if ok:
                #self._peakmeter = True
                #for port in self._playback_1_connected_ports:
                    #self.connect(port, "effect_%d:%s" % (PEAKMETER_OUT, PEAKMETER_L), mon_peak_out_l, True)
                #for port in self._playback_2_connected_ports:
                    #self.connect(port, "effect_%d:%s" % (PEAKMETER_OUT, PEAKMETER_R), mon_peak_out_r, True)

        #self.add(PEAKMETER_URI, PEAKMETER_IN, setup_peak_in, True)
        #self.add(PEAKMETER_URI, PEAKMETER_OUT, setup_peak_out, True)

    #def peakmeter_off(self, cb):
        #self.remove(PEAKMETER_IN, cb, True)
        #self.remove(PEAKMETER_OUT, lambda r: None, True)
        #self._tuner = False

    #def tuner_set(self, status, callback):
        #if "on" in status:
            #self.tuner_on(callback)
        #elif "off" in status:
            #self.tuner_off(callback)

    #def tuner_on(self, cb):
        #def mon_tuner(ok):
            #if ok:
                #self.parameter_monitor(TUNER, TUNER_MON_PORT, ">=", 0, cb)

        #def setup_tuner(ok):
            #if ok:
                #self._tuner = True
                #self.connect("system:capture_%s" % self._tuner_port, "effect_%d:%s" % (TUNER, TUNER_PORT), mon_tuner, True)

        #def mute_callback():
            #self.add(TUNER_URI, TUNER, setup_tuner, True)
        #self.mute(mute_callback)

    #def tuner_off(self, cb):
        #def callback():
            #self.remove(TUNER, cb, True)
            #self._tuner = False
        #self.unmute(callback)

    #def tuner_set_input(self, input, callback):
        ## TODO: implement
        #self.disconnect("system:capture_%s" % self._tuner_port, "effect_%d:%s" % (TUNER, TUNER_PORT), lambda r:r, True)
        #self._tuner_port = input
        #self.connect("system:capture_%s" % input, "effect_%d:%s" % (TUNER, TUNER_PORT), callback, True)

    # -----------------------------------------------------------------------------------------------------------------

    def _init_addressings(self):
        # 'self.addressings' uses a structure like this:
        # "/hmi/knob1": {'addrs': [], 'idx': 0}
        self.addressings = dict((act["uri"], {'idx': 0, 'addrs': []}) for act in get_hardware()["actuators"])

        # Store all possible hardcoded values
        self._hw2uri_map = {}
        self._uri2hw_map = {}

        for i in range(0, 4):
            knob_hw  = (HARDWARE_TYPE_MOD, 0, ACTUATOR_TYPE_KNOB,       i)
            foot_hw  = (HARDWARE_TYPE_MOD, 0, ACTUATOR_TYPE_FOOTSWITCH, i)
            knob_uri = "/hmi/knob%i"       % (i+1)
            foot_uri = "/hmi/footswitch%i" % (i+1)

            self._hw2uri_map[knob_hw]  = knob_uri
            self._hw2uri_map[foot_hw]  = foot_uri
            self._uri2hw_map[knob_uri] = knob_hw
            self._uri2hw_map[foot_uri] = foot_hw

    # -----------------------------------------------------------------------------------------------------------------

    def _addressing_load(self, actuator_uri, callback=None):
        addressings       = self.addressings[actuator_uri]
        addressings_addrs = addressings['addrs']
        addressings_idx   = addressings['idx']

        try:
            addressing = addressings_addrs[addressings_idx]
        except IndexError:
            return

        actuator_hw = self._uri2hw_map[actuator_uri]

        self.hmi.control_add(addressing['instance_id'], addressing['port'],
                             addressing['label'], addressing['type'], addressing['unit'],
                             addressing['value'], addressing['maximum'], addressing['minimum'], addressing['steps'],
                             actuator_hw[0], actuator_hw[1], actuator_hw[2], actuator_hw[3],
                             len(addressings_addrs), # num controllers
                             addressings_idx+1,      # index
                             addressing['options'], callback)

    def _address_next(self, actuator_hw, callback=lambda r:r):
        actuator_uri = self._hw2uri_map[actuator_hw]

        addressings       = self.addressings[actuator_uri]
        addressings_addrs = addressings['addrs']
        addressings_idx   = addressings['idx']

        if len(addressings_addrs) > 0:
            addressings['idx'] = (addressings['idx'] + 1) % len(addressings_addrs)
            callback(True)
            self._addressing_load(actuator_uri)
        else:
            callback(True)
            self.hmi.control_clean(actuator_hw[0], actuator_hw[1], actuator_hw[2], actuator_hw[3])

    def _unaddress(self, instance, port):
        data = self.instances.get(instance, None)
        if data is None:
            return None

        addressing = data['addressing'].pop(port, None)
        if addressing is None:
            return None

        actuator_uri      = addressing['actuator_uri']
        addressings       = self.addressings[actuator_uri]
        addressings_addrs = addressings['addrs']
        addressings_idx   = addressings['idx']

        index = addressings_addrs.index(addressing)
        addressings_addrs.pop(index)

        # FIXME ?
        if addressings_idx >= index:
            addressings['idx'] -= 1
        #if index <= addressings_idx:
            #addressings['idx'] = addressings_idx - 1

        return actuator_uri
Esempio n. 4
0
class Session(object):
    def __init__(self):
        self.host_initialized = False

        self._tuner = False
        self._tuner_port = 1
        self._peakmeter = False

        self.monitor_server = None
        self.current_bank = None
        self.hmi_initialized = False

        # JACK client name of the backend
        self.backend_client_name = "ingen"

        # Used in mod-app to know when the current pedalboard changed
        self.pedalboard_changed_callback = lambda ok,bundlepath,title:None

        # For saving the current pedalboard bundlepath and title
        self.bundlepath = None
        self.title      = None

        self.engine_samplerate = 48000 # default value
        self.jack_client = None
        self.xrun_count = 0
        self.xrun_count2 = 0

        self.ioloop = ioloop.IOLoop.instance()

        socketpath = os.environ.get("MOD_INGEN_SOCKET_URI", "unix:///tmp/ingen.sock")
        if DEV_HOST:
            self.host = FakeHost(socketpath)
        else:
            self.host = Host(socketpath)

        # Try to open real HMI
        hmiOpened = False

        if not DEV_HMI:
            self.hmi  = HMI(HMI_SERIAL_PORT, HMI_BAUD_RATE, self.hmi_initialized_cb)
            hmiOpened = self.hmi.sp is not None

        print("Using HMI =>", hmiOpened)

        if hmiOpened:
            # If all ok, use addressings
            self.addressings = Addressing(self.hmi)
        else:
            # Otherwise disable HMI entirely
            self.hmi = FakeHMI(HMI_SERIAL_PORT, HMI_BAUD_RATE, self.hmi_initialized_cb)
            self.addressings = None

        self.recorder = Recorder()
        self.player = Player()
        self.mute_state = True
        self.recording = None
        self.instances = []
        self.screenshot_generator = ScreenshotGenerator()

        self._clipmeter = Clipmeter(self.hmi)
        self.websockets = []
        self.mididevuuids = []

        self.jack_cpu_load_timer = ioloop.PeriodicCallback(self.jack_cpu_load_timer_callback, 1000)
        self.jack_xrun_timer     = ioloop.PeriodicCallback(self.jack_xrun_timer_callback, 500)

        self.ioloop.add_callback(self.init_jack)
        self.ioloop.add_callback(self.init_socket)

    def __del__(self):
        if self.jack_client is None:
            return
        jacklib.deactivate(self.jack_client)
        jacklib.client_close(self.jack_client)
        self.jack_client = None
        print("jacklib client deactivated")

    def get_hardware(self):
        if self.addressings is None:
            return {}
        hw = deepcopy(get_hardware())
        hw["addressings"] = self.addressings.get_addressings()
        return hw

    # -----------------------------------------------------------------------------------------------------------------
    # App utilities, needed only for mod-app

    def setupApp(self, clientName, pedalboardChangedCallback):
        self.backend_client_name = clientName
        self.pedalboard_changed_callback = pedalboardChangedCallback

    def reconnectApp(self):
        if self.host.sock is not None:
            self.host.sock.close()
            self.host.sock = None
        self.host.open_connection_if_needed(self.host_callback)

    # -----------------------------------------------------------------------------------------------------------------
    # Initialization

    def autoconnect_jack(self):
        if self.jack_client is None:
            return

        for i in range(1, INGEN_NUM_AUDIO_INS+1):
            jacklib.connect(self.jack_client, "system:capture_%i" % i, "%s:audio_port_%i_in" % (self.backend_client_name, i))

        for i in range(1, INGEN_NUM_AUDIO_OUTS+1):
            jacklib.connect(self.jack_client,"%s:audio_port_%i_out" % (self.backend_client_name, i), "system:playback_%i" % i)

        if not DEV_HMI:
            # this means we're using HMI, so very likely running MOD hardware
            jacklib.connect(self.jack_client, "alsa_midi:ttymidi MIDI out in", "%s:midi_port_1_in" % self.backend_client_name)
            jacklib.connect(self.jack_client, "%s:midi_port_1_out" % self.backend_client_name, "alsa_midi:ttymidi MIDI in out")

    def init_jack(self):
        self.jack_client = jacklib.client_open("%s-helper" % self.backend_client_name, jacklib.JackNoStartServer, None)
        self.xrun_count  = 0
        self.xrun_count2 = 0

        if self.jack_client is None:
            return

        #jacklib.jack_set_port_registration_callback(self.jack_client, self.JackPortRegistrationCallback, None)
        jacklib.set_property_change_callback(self.jack_client, self.JackPropertyChangeCallback, None)
        #jacklib.set_xrun_callback(self.jack_client, self.JackXRunCallback, None)
        jacklib.on_shutdown(self.jack_client, self.JackShutdownCallback, None)
        jacklib.activate(self.jack_client)
        print("jacklib client activated")

        if INGEN_AUTOCONNECT:
            self.ioloop.add_timeout(timedelta(seconds=3.0), self.autoconnect_jack)

    def init_socket(self):
        self.host.open_connection_if_needed(self.host_callback)

    def hmi_initialized_cb(self):
        logging.info("hmi initialized")
        self.hmi_initialized = True
        self.hmi.clear()

        if self.addressings is not None:
            self.addressings.init_host()

    # -----------------------------------------------------------------------------------------------------------------
    # Timers (start and stop in sync with webserver IOLoop)

    def start_timers(self):
        self.jack_cpu_load_timer.start()
        self.jack_xrun_timer.start()

    def stop_timers(self):
        self.jack_xrun_timer.stop()
        self.jack_cpu_load_timer.stop()

    # -----------------------------------------------------------------------------------------------------------------
    # Webserver callbacks, called from the browser (see webserver.py)
    # These will be called as a reponse to an action in the browser.
    # A callback must always be used unless specified otherwise.

    # Add a new plugin, starts enabled (ie, not bypassed)
    def web_add(self, instance, uri, x, y, callback):
        self.host.add_plugin(instance, uri, True, x, y, callback)

    # Remove a plugin
    def web_remove(self, instance, callback):
        self.host.remove_plugin(instance, callback)

    # Set a plugin parameter
    # We use ":bypass" symbol for on/off state
    def web_parameter_set(self, port, value, callback):
        instance, port2 = port.rsplit("/",1)

        if port2 == ":bypass":
            value = value >= 0.5
            self.host.enable(instance, not value, callback)
        else:
            self.host.param_set(port, value, callback)

        #self.recorder.parameter(port, value)

    # Address a plugin parameter
    def web_parameter_address(self, port, actuator_uri, label, maximum, minimum, value, steps, callback):
        if self.addressings is None or not self.hmi_initialized:
            callback(False)
            return

        instance, port2 = port.rsplit("/",1)
        self.addressings.address(instance, port2, actuator_uri, label, maximum, minimum, value, steps, callback)

    # Set a parameter for MIDI learn
    def web_parameter_midi_learn(self, port, callback):
        self.host.midi_learn(port, callback)

    # Load a plugin preset
    def web_preset_load(self, instance, uri, callback):
        self.host.preset_load(instance, uri, callback)

    # Set a plugin block position within the canvas
    def web_set_position(self, instance, x, y, callback):
        self.host.set_position(instance, x, y, callback)

    # Connect 2 ports
    def web_connect(self, port_from, port_to, callback):
        self.host.connect(port_from, port_to, callback)

    # Disconnect 2 ports
    def web_disconnect(self, port_from, port_to, callback):
        self.host.disconnect(port_from, port_to, callback)

    # Save the current pedalboard
    # The order of events is:
    #  1. set the graph name to 'title'
    #  2. ask backend to save
    #  3. create manifest.ttl
    #  4. create addressings.json file
    # Step 2 is asynchronous, so it happens while 3 and 4 are taking place.
    def web_save_pedalboard(self, title, asNew, callback):
        titlesym = symbolify(title)

        # Save over existing bundlepath
        if self.bundlepath and os.path.exists(self.bundlepath) and os.path.isdir(self.bundlepath) and not asNew:
            bundlepath = self.bundlepath

        # Save new
        else:
            lv2path = os.path.expanduser("~/.lv2/") # FIXME: cross-platform
            trypath = os.path.join(lv2path, "%s.pedalboard" % titlesym)

            # if trypath already exists, generate a random bundlepath based on title
            if os.path.exists(trypath):
                from random import randint

                while True:
                    trypath = os.path.join(lv2path, "%s-%i.pedalboard" % (titlesym, randint(1,99999)))
                    if os.path.exists(trypath):
                        continue
                    bundlepath = trypath
                    break

            # trypath doesn't exist yet, use it
            else:
                bundlepath = trypath

                # just in case..
                if not os.path.exists(lv2path):
                    os.mkdir(lv2path)

            os.mkdir(bundlepath)

        # Various steps triggered by callbacks
        def step1(ok):
            if ok: self.host.save(os.path.join(bundlepath, "%s.ttl" % titlesym), step2)
            else: callback(False)

        def step2(ok):
            if ok:
                self.bundlepath = bundlepath
                self.title      = title
            else:
                self.bundlepath = None
                self.title      = None

            self.pedalboard_changed_callback(ok, bundlepath, title)

            if not ok:
                callback(False)
                return

            # Create a custom manifest.ttl, not created by ingen because we want *.pedalboard extension
            with open(os.path.join(bundlepath, "manifest.ttl"), 'w') as fh:
                fh.write("""\
@prefix ingen: <http://drobilla.net/ns/ingen#> .
@prefix lv2:   <http://lv2plug.in/ns/lv2core#> .
@prefix pedal: <http://moddevices.com/ns/modpedal#> .
@prefix rdfs:  <http://www.w3.org/2000/01/rdf-schema#> .

<%s.ttl>
    lv2:prototype ingen:GraphPrototype ;
    a lv2:Plugin ,
        ingen:Graph ,
        pedal:Pedalboard ;
    rdfs:seeAlso <%s.ttl> .
""" % (titlesym, titlesym))

            # Create a addressings.json file
            addressings = self.addressings.get_addressings() if self.addressings is not None else {}

            with open(os.path.join(bundlepath, "addressings.json"), 'w') as fh:
                json.dump(addressings, fh)

            # All ok!
            callback(True)

        # start here
        self.host.set_pedalboard_name(title, step1)

    # Get list of Hardware MIDI devices
    # returns (devsInUse, devList)
    def web_get_midi_device_list(self):
        return self.get_midi_ports(self.backend_client_name), self.get_midi_ports("alsa_midi")

    # Set the selected MIDI devices to @a newDevs
    # Will remove or add new JACK ports as needed
    @gen.engine
    def web_set_midi_devices(self, newDevs):
        curDevs = self.get_midi_ports(self.backend_client_name)

        # remove
        for dev in curDevs:
            if dev in newDevs:
                continue
            if dev.startswith("MIDI Port-"):
                continue
            dev, modes = dev.rsplit(" (",1)
            jacklib.disconnect(self.jack_client, "alsa_midi:%s in" % dev, self.backend_client_name+":control_in")

            def remove_external_port_in(callback):
                self.host.remove_external_port(dev+" in")
                callback(True)
            def remove_external_port_out(callback):
                self.host.remove_external_port(dev+" out")
                callback(True)

            yield gen.Task(remove_external_port_in)

            if "out" in modes:
                yield gen.Task(remove_external_port_out)

        # add
        for dev in newDevs:
            if dev in curDevs:
                continue
            dev, modes = dev.rsplit(" (",1)

            def add_external_port_in(callback):
                self.host.add_external_port(dev+" in", "Input", "MIDI")
                callback(True)
            def add_external_port_out(callback):
                self.host.add_external_port(dev+" out", "Output", "MIDI")
                callback(True)

            yield gen.Task(add_external_port_in)

            if "out" in modes:
                yield gen.Task(add_external_port_out)

    # Send a ping to HMI
    def web_ping_hmi(self, callback):
        self.hmi.ping(callback)

    # A new webbrowser page has been open
    # We need to cache its socket address and send any msg callbacks to it
    def websocket_opened(self, ws):
        self.websockets.append(ws)

        self.host.open_connection_if_needed(self.host_callback)
        self.host.get("/graph")

    # Webbrowser page closed
    def websocket_closed(self, ws):
        self.websockets.remove(ws)

    # -----------------------------------------------------------------------------------------------------------------
    # JACK callbacks, called by JACK itself
    # We must take care to ensure not to block for long periods of time or else audio will skip or xrun.

    # Callback for when a port appears or disappears
    # We use this to trigger a auto-connect mode
    #def JackPortRegistrationCallback(self, port, registered, arg):
        #if self.jack_client is None:
            #return
        #if not registered:
            #return

    # Callback for when a client or port property changes.
    # We use this to know the full length name of ingen created ports.
    def JackPropertyChangeCallback(self, subject, key, change, arg):
        if self.jack_client is None:
            return
        if change != jacklib.PropertyCreated:
            return
        if key != jacklib.bJACK_METADATA_PRETTY_NAME:
            return

        self.mididevuuids.append(subject)
        self.ioloop.add_callback(self.jack_midi_devs_callback)

    # Callback for when an xrun occurs
    def JackXRunCallback(self, arg):
        self.xrun_count += 1
        return 0

    # Callback for when JACK has shutdown or our client zombified
    def JackShutdownCallback(self, arg):
        self.jack_client = None

    # -----------------------------------------------------------------------------------------------------------------
    # Misc/Utility functions
    # We use these to save possibly duplicated code.

    # Get all available MIDI ports of a specific JACK client.
    def get_midi_ports(self, client_name):
        if self.jack_client is None:
            return []

        # get input and outputs separately
        in_ports = charPtrPtrToStringList(jacklib.get_ports(self.jack_client, client_name+":", jacklib.JACK_DEFAULT_MIDI_TYPE,
                                                            jacklib.JackPortIsPhysical|jacklib.JackPortIsOutput
                                                            if client_name == "alsa_midi" else
                                                            jacklib.JackPortIsInput
                                                            ))
        out_ports = charPtrPtrToStringList(jacklib.get_ports(self.jack_client, client_name+":", jacklib.JACK_DEFAULT_MIDI_TYPE,
                                                             jacklib.JackPortIsPhysical|jacklib.JackPortIsInput
                                                             if client_name == "alsa_midi" else
                                                             jacklib.JackPortIsOutput
                                                             ))

        if client_name != "alsa_midi":
            if "ingen:control_in" in in_ports:
                in_ports.remove("ingen:control_in")
            if "ingen:control_out" in out_ports:
                out_ports.remove("ingen:control_out")

            for i in range(len(in_ports)):
                uuid = jacklib.port_uuid(jacklib.port_by_name(self.jack_client, in_ports[i]))
                ret, value, type_ = jacklib.get_property(uuid, jacklib.JACK_METADATA_PRETTY_NAME)
                if ret == 0 and type_ == b"text/plain":
                    in_ports[i] = charPtrToString(value)

            for i in range(len(out_ports)):
                uuid = jacklib.port_uuid(jacklib.port_by_name(self.jack_client, out_ports[i]))
                ret, value, type_ = jacklib.get_property(uuid, jacklib.JACK_METADATA_PRETTY_NAME)
                if ret == 0 and type_ == b"text/plain":
                    out_ports[i] = charPtrToString(value)

        # remove suffixes from ports
        in_ports  = [port.replace(client_name+":","",1).rsplit(" in" ,1)[0] for port in in_ports ]
        out_ports = [port.replace(client_name+":","",1).rsplit(" out",1)[0] for port in out_ports]

        # add our own suffix now
        ports = []
        for port in in_ports:
            #if "Midi Through" in port:
                #continue
            if port in ("jackmidi", "OSS sequencer"):
                continue
            ports.append(port + (" (in+out)" if port in out_ports else " (in)"))

        return ports

    # -----------------------------------------------------------------------------------------------------------------
    # Timer callbacks
    # These are functions called by the IO loop at regular intervals.

    # Single-shot callback that automatically connects new backend JACK MIDI ports to their hardware counterparts.
    def jack_midi_devs_callback(self):
        while len(self.mididevuuids) != 0:
            subject = self.mididevuuids.pop()

            ret, value, type_ = jacklib.get_property(subject, jacklib.JACK_METADATA_PRETTY_NAME)
            if ret != 0:
                continue
            if type_ != b"text/plain":
                continue

            value = charPtrToString(value)
            if not (value.endswith(" in") or value.endswith(" out")):
                continue

            mod_name  = "%s:%s" % (self.backend_client_name, value.replace(" ", "_").replace("-","_").lower())
            midi_name = "alsa_midi:%s" % value

            # All good, make connection now
            if value.endswith(" in"):
                jacklib.connect(self.jack_client, midi_name, mod_name)
                jacklib.connect(self.jack_client, midi_name, self.backend_client_name+":control_in")
            else:
                jacklib.connect(self.jack_client, mod_name, midi_name)

    # Callback for getting the current JACK cpu load and report it to the browser side.
    def jack_cpu_load_timer_callback(self):
        if self.jack_client is not None:
            msg = """[]
            a <http://lv2plug.in/ns/ext/patch#Set> ;
            <http://lv2plug.in/ns/ext/patch#subject> </engine/> ;
            <http://lv2plug.in/ns/ext/patch#property> <http://moddevices/ns/modpedal#cpuload> ;
            <http://lv2plug.in/ns/ext/patch#value> "%.1f" .
            """ % jacklib.cpu_load(self.jack_client)

            for ws in self.websockets:
                ws.write_message(msg)

    # Callback that checks if xruns have occured.
    def jack_xrun_timer_callback(self):
        for i in range(self.xrun_count2, self.xrun_count):
            self.xrun_count2 += 1
            #self.hmi.xrun()

    # -----------------------------------------------------------------------------------------------------------------
    # TODO
    # Everything after this line is yet to be documented

    @gen.engine
    def host_callback(self):
        if self.host_initialized:
            return

        self.host_initialized = True

        def msg_callback(msg):
            for ws in self.websockets:
                ws.write_message(msg)

        def saved_callback(bundlepath):
            if add_bundle_to_lilv_world(bundlepath):
                pass #self.host.add_bundle(bundlepath)
            self.screenshot_generator.schedule_screenshot(bundlepath)

        def samplerate_callback(srate):
            self.engine_samplerate = srate

        def plugin_added_callback(instance, uri, enabled, x, y):
            if instance not in self.instances:
                self.instances.append(instance)

        def plugin_removed_callback(instance):
            if instance in self.instances:
                self.instances.remove(instance)

        self.host.msg_callback = msg_callback
        self.host.saved_callback = saved_callback
        self.host.samplerate_callback = samplerate_callback
        self.host.plugin_added_callback = plugin_added_callback
        self.host.plugin_removed_callback = plugin_removed_callback

        yield gen.Task(self.host.initial_setup)

        # Add ports
        for i in range(1, INGEN_NUM_AUDIO_INS+1):
            yield gen.Task(lambda callback: self.host.add_external_port("Audio Port-%i in" % i, "Input", "Audio", callback))

        for i in range(1, INGEN_NUM_AUDIO_OUTS+1):
            yield gen.Task(lambda callback: self.host.add_external_port("Audio Port-%i out" % i, "Output", "Audio", callback))

        for i in range(1, INGEN_NUM_MIDI_INS+1):
            yield gen.Task(lambda callback: self.host.add_external_port("MIDI Port-%i in" % i, "Input", "MIDI", callback))

        for i in range(1, INGEN_NUM_MIDI_OUTS+1):
            yield gen.Task(lambda callback: self.host.add_external_port("MIDI Port-%i out" % i, "Output", "MIDI", callback))

        for i in range(1, INGEN_NUM_CV_INS+1):
            yield gen.Task(lambda callback: self.host.add_external_port("CV Port-%i in" % i, "Input", "CV", callback))

        for i in range(1, INGEN_NUM_CV_OUTS+1):
            yield gen.Task(lambda callback: self.host.add_external_port("CV Port-%i out" % i, "Output", "CV", callback))

    def load_pedalboard(self, bundlepath, title):
        self.bundlepath = bundlepath
        self.title      = title
        self.host.load(bundlepath)
        self.pedalboard_changed_callback(True, bundlepath, title)

    def reset(self, callback):
        self.bundlepath = None
        self.title      = None

        # Callback from socket
        def remove_next_plugin(ok):
            if not ok:
                callback(False)
                return

            try:
                instance = self.instances.pop(0)
            except IndexError:
                callback(True)
                return

            self.host.remove_plugin(instance, remove_next_plugin)

        # Callback from HMI, ignore ok status
        def remove_all_plugins(ok):
            remove_next_plugin(True)

        # Reset addressing data
        if self.addressings is not None:
            self.addressings.clear()

        # Wait for HMI if available
        if self.hmi_initialized:
            self.hmi.clear(remove_all_plugins)
        else:
            remove_next_plugin(True)

        self.pedalboard_changed_callback(True, "", "")

    #def setup_monitor(self):
        #if self.monitor_server is None:
            #from mod.monitor import MonitorServer
            #self.monitor_server = MonitorServer()
            #self.monitor_server.listen(12345)

            #self.set_monitor("localhost", 12345, 1, self.add_tools)

    #def add_tools(self, resp):
        #if resp:
            #self.add(CLIPMETER_URI, CLIPMETER_IN, self.setup_clipmeter_in, True)
            #self.add(CLIPMETER_URI, CLIPMETER_OUT, self.setup_clipmeter_out, True)

    #def setup_clipmeter_in(self, resp):
        #if resp:
            #self.connect("system:capture_1", "effect_%d:%s" % (CLIPMETER_IN, CLIPMETER_L), lambda r:None, True)
            #self.connect("system:capture_2", "effect_%d:%s" % (CLIPMETER_IN, CLIPMETER_R), lambda r:None, True)
            #self.parameter_monitor(CLIPMETER_IN, CLIPMETER_MON_L, ">=", 0, lambda r:None)
            #self.parameter_monitor(CLIPMETER_IN, CLIPMETER_MON_R, ">=", 0, lambda r:None)

    #def setup_clipmeter_out(self, resp):
        #if resp:
            #self.parameter_monitor(CLIPMETER_OUT, CLIPMETER_MON_L, ">=", 0, lambda r:None)
            #self.parameter_monitor(CLIPMETER_OUT, CLIPMETER_MON_R, ">=", 0, lambda r:None)

    # host commands

    def bypass(self, instance, value, callback):
        value = int(value) > 0
        #if not loaded:
        #    self._pedalboard.bypass(instance_id, value)
        #self.recorder.bypass(instance, value)
        self.host.enable(instance, value, callback)

    def format_port(self, port):
        if not 'system' in port and not 'effect' in port:
            port = "effect_%s" % port
        return port

    #def set_monitor(self, addr, port, status, callback):
        #self.host.monitor(addr, port, status, callback)

    #def parameter_monitor(self, instance_id, port_id, op, value, callback):
        #self.host.param_monitor(instance_id, port_id, op, value, callback)

    # END host commands

    # hmi commands
    def start_session(self, callback=None):
        def verify(resp):
            if callback:
                callback(resp)
            else:
                assert(resp)
        self.bank_address(0, 0, 1, 0, 0, lambda r: None)
        self.bank_address(0, 0, 1, 1, 0, lambda r: None)
        self.bank_address(0, 0, 1, 2, 0, lambda r: None)
        self.bank_address(0, 0, 1, 3, 0, lambda r: None)

        self.hmi.ui_con(verify)

    def end_session(self, callback):
        self.hmi.ui_dis(callback)

    def bank_address(self, hardware_type, hardware_id, actuator_type, actuator_id, function, callback):
        """
        Function is an integer, meaning:
         - 0: Nothing (unaddress)
         - 1: True bypass
         - 2: Pedalboard up
         - 3: Pedalboard down
        """
        self.hmi.bank_config(hardware_type, hardware_id, actuator_type, actuator_id, function, callback)

    def pedalboard_size(self, width, height, callback):
        self.host.set_pedalboard_size(width, height, callback)

    def clipmeter(self, pos, value):
        self._clipmeter.set(pos, value)

    def peakmeter(self, pos, value, peak, callback=None):
        cb = callback
        if not cb:
            cb = lambda r: r
        self.hmi.peakmeter(pos, value, peak, cb)

    def tuner(self, value, callback=None):
        cb = callback
        if not cb:
            cb = lambda r: r

        freq, note, cents = find_freqnotecents(value)
        self.hmi.tuner(freq, note, cents, cb)

    def start_recording(self):
        if self.player.playing:
            self.player.stop()
        self.recorder.start(self.backend_client_name)

    def stop_recording(self):
        if self.recorder.recording:
            self.recording = self.recorder.stop()
            return self.recording

    def start_playing(self, stop_callback):
        if self.recorder.recording:
            self.recording = self.recorder.stop()
        def stop():
            self.unmute(stop_callback)
        def schedule_stop():
            self.ioloop.add_timeout(timedelta(seconds=0.5), stop)
        def play():
            self.player.play(self.recording['handle'], schedule_stop)
        self.mute(play)

    def stop_playing(self):
        self.player.stop()

    def reset_recording(self):
        self.recording = None

    def mute(self, callback):
        return
        #self.set_audio_state(False, callback)

    def unmute(self, callback):
        return
Esempio n. 5
0
class Addressing(object):
    def __init__(self, hmi):
        self.host = None
        self.hmi = hmi
        self.mapper = InstanceIdMapper()
        self.banks = []
        self.instances = {}
        self._init_addressings()

        # Register HMI protocol callbacks
        Protocol.register_cmd_callback("hw_con", self.hmi_hardware_connected)
        Protocol.register_cmd_callback("hw_dis",
                                       self.hmi_hardware_disconnected)
        Protocol.register_cmd_callback("banks", self.hmi_list_banks)
        Protocol.register_cmd_callback("pedalboards",
                                       self.hmi_list_bank_pedalboards)
        Protocol.register_cmd_callback("pedalboard",
                                       self.hmi_load_bank_pedalboard)
        Protocol.register_cmd_callback("control_get", self.hmi_parameter_get)
        Protocol.register_cmd_callback("control_set", self.hmi_parameter_set)
        Protocol.register_cmd_callback("control_next",
                                       self.hmi_parameter_addressing_next)
        #Protocol.register_cmd_callback("peakmeter", self.peakmeter_set)
        #Protocol.register_cmd_callback("tuner", self.tuner_set)
        #Protocol.register_cmd_callback("tuner_input", self.tuner_set_input)
        #Protocol.register_cmd_callback("pedalboard_save", self.save_current_pedalboard)
        #Protocol.register_cmd_callback("pedalboard_reset", self.reset_current_pedalboard)
        #Protocol.register_cmd_callback("jack_cpu_load", self.jack_cpu_load)

    def clear(self):
        self.banks = []
        self.instances = {}
        self._init_addressings()

    def get_addressings(self):
        addressings = {}
        for uri, addressing in self.addressings.items():
            addrs = []
            for addr in addressing['addrs']:
                addrs.append({
                    'instance':
                    self.mapper.get_instance(addr['instance_id']),
                    'port':
                    addr['port'],
                    'label':
                    addr['label'],
                    'minimum':
                    addr['minimum'],
                    'maximum':
                    addr['maximum'],
                    'steps':
                    addr['steps'],
                    'value':
                    addr['value'],
                })
            addressings[uri] = addrs
        return addressings

    # -----------------------------------------------------------------------------------------------------------------

    # Init our ingen host class
    # This is only called when the HMI responds to our initial ping (and it's thus initialized)
    # The reason for this being a separate init function is because we don't need it when HMI is off
    def init_host(self):
        # We need our own host instance so that messages get propagated correctly by ingen
        # Later on this code will be a separate application so it all fits anyway
        self.host = Host(
            os.getenv("MOD_INGEN_SOCKET_URI", "unix:///tmp/ingen.sock"))

        def loaded_callback(bundlepath):
            self._load_addressings(bundlepath)

        def plugin_added_callback(instance, uri, enabled, x, y):
            self._add_instance(instance, uri, not enabled)

        def plugin_removed_callback(instance):
            self._remove_instance(instance)

        def plugin_enabled_callback(instance, enabled):
            self._set_bypassed(instance, not enabled)

        def port_value_callback(port, value):
            instance, port = port.rsplit("/", 1)
            self._set_value(instance, port, value)

        self.host.loaded_callback = loaded_callback
        self.host.plugin_added_callback = plugin_added_callback
        self.host.plugin_removed_callback = plugin_removed_callback
        self.host.port_value_callback = port_value_callback

        self.host.open_connection_if_needed(self.host_callback)

    def host_callback(self):
        self.host.get("/graph")

    # -----------------------------------------------------------------------------------------------------------------

    def _load_addressings(self, bundlepath):
        datafile = os.path.join(bundlepath, "addressings.json")
        if not os.path.exists(datafile):
            return

        with open(datafile, 'r') as fh:
            data = fh.read()
        data = json.loads(data)

        self._init_addressings()

        stopNow = False

        def callback(ok):
            if not ok:
                stopNow = True

        for actuator_uri in data:
            for addr in data[actuator_uri]:
                self.address(addr["instance"], addr["port"], actuator_uri,
                             addr["label"], addr["maximum"], addr["minimum"],
                             addr['value'], addr["steps"], callback)
                if stopNow: break
            if stopNow: break

    def _add_instance(self, instance, uri, bypassed):
        instance_id = self.mapper.get_id(instance)

        self.instances[instance] = {
            'id': instance_id,
            'instance': instance,
            'uri': uri,
            'bypassed': bypassed,
            'addressing': {},  # symbol: addressing
            'ports': {},  # symbol: value
        }
        logging.info('[addressing] Added instance %s' % instance)

    def _remove_instance(self, instance):
        instance_id = self.mapper.get_id(instance)

        # Remove the instance
        try:
            self.instances.pop(instance)
        except KeyError:
            logging.error('[addressing] Cannot remove unknown instance %s' %
                          instance)

        # Remove addressings of that instance
        #affected_actuators = {}
        for actuator_uri, addressing in self.addressings.items():
            i = 0
            while i < len(addressing['addrs']):
                if addressing['addrs'][i].get('instance_id') == instance_id:
                    addressing['addrs'].pop(i)
                    if addressing['idx'] >= i:
                        addressing['idx'] -= 1
                    #affected_actuators[actuator_uri] = addressing['idx']
                else:
                    i += 1

        self.hmi.control_rm(instance_id, ":all")
        #for addr in affected_actuators:
        #self.parameter_addressing_load(*addr)

        logging.info('[addressing] Removed instance %s' % instance)
        #return [ list(act) + [idx] for act, idx in affected_actuators.items() ]

    def _set_bypassed(self, instance, bypassed):
        data = self.instances.get(instance, None)
        if data is None:
            return
        data['bypassed'] = bypassed

        addr = data['addressing'].get(':bypass', None)
        if addr is None:
            return
        addr['value'] = 1 if bypassed else 0

    def _set_value(self, instance, port, value):
        data = self.instances.get(instance, None)
        if data is None:
            return
        data['ports'][port] = value

        addr = data['addressing'].get(port, None)
        if addr is None:
            return
        addr['value'] = value

    # -----------------------------------------------------------------------------------------------------------------

    def address(self, instance, port, actuator_uri, label, maximum, minimum,
                value, steps, callback):
        instance_id = self.mapper.get_id(instance)
        old_actuator_uri = self._unaddress(instance, port)

        if (not actuator_uri) or actuator_uri == "null":
            self.hmi.control_rm(instance_id, port, callback)
            if old_actuator_uri is not None:
                old_actuator_hw = self._uri2hw_map[old_actuator_uri]
                self._address_next(old_actuator_hw)
            return

        data = self.instances.get(instance, None)
        if data is None:
            callback(False)
            return

        options = []

        if port == ":bypass":
            ctype = ADDRESSING_CTYPE_BYPASS
            unit = "none"

        else:
            for port_info in get_plugin_info(
                    data["uri"])["ports"]["control"]["input"]:
                if port_info["symbol"] != port:
                    continue
                break
            else:
                callback(False)
                return

            pprops = port_info["properties"]
            unit = port_info["units"]["symbol"] if "symbol" in port_info[
                "units"] else "none"

            if "toggled" in pprops:
                ctype = ADDRESSING_CTYPE_TOGGLED
            elif "integer" in pprops:
                ctype = ADDRESSING_CTYPE_INTEGER
            else:
                ctype = ADDRESSING_CTYPE_LINEAR

            if "logarithmic" in pprops:
                ctype |= ADDRESSING_CTYPE_LOGARITHMIC
            if "trigger" in pprops:
                ctype |= ADDRESSING_CTYPE_TRIGGER
            if "tap_tempo" in pprops:  # TODO
                ctype |= ADDRESSING_CTYPE_TAP_TEMPO

            if len(port_info["scalePoints"]
                   ) >= 2:  # and "enumeration" in pprops:
                ctype |= ADDRESSING_CTYPE_SCALE_POINTS | ADDRESSING_CTYPE_ENUMERATION

                if "enumeration" in pprops:
                    ctype |= ADDRESSING_CTYPE_ENUMERATION

                for scalePoint in port_info["scalePoints"]:
                    options.append((scalePoint["value"], scalePoint["label"]))

            del port_info, pprops

        addressing = {
            'actuator_uri': actuator_uri,
            'instance_id': instance_id,
            'port': port,
            'label': label,
            'type': ctype,
            'unit': unit,
            'minimum': minimum,
            'maximum': maximum,
            'value': value,
            'steps': steps,
            'options': options,
        }
        self.instances[instance]['addressing'][port] = addressing
        self.addressings[actuator_uri]['addrs'].append(addressing)
        self.addressings[actuator_uri]['idx'] = len(
            self.addressings[actuator_uri]['addrs']) - 1

        if old_actuator_uri is not None:
            self._addressing_load(old_actuator_uri)

        self._addressing_load(actuator_uri, callback)

    # -----------------------------------------------------------------------------------------------------------------
    # HMI callbacks, called by HMI via serial

    def hmi_hardware_connected(self, hardware_type, hardware_id, callback):
        logging.info("hmi hardware connected")
        callback(True)

    def hmi_hardware_disconnected(self, hardware_type, hardware_id, callback):
        logging.info("hmi hardware disconnected")
        callback(True)

    def hmi_list_banks(self, callback):
        logging.info("hmi list banks")
        self.banks = list_banks()
        banks = " ".join('"%s" %d' % (bank['title'], i)
                         for i, bank in enumerate(self.banks))
        callback(True, banks)

    def hmi_list_bank_pedalboards(self, bank_id, callback):
        logging.info("hmi list bank pedalboards")
        if bank_id < len(self.banks):
            #pedalboards = " ".join('"%s" %d' % (pb['title'], i) for i,pb in enumerate(self.banks[bank_id]['pedalboards']))
            pedalboards = " ".join(
                '"%s" "%s"' % (pb['title'], pb['uri'])
                for pb in self.banks[bank_id]['pedalboards'])
        else:
            pedalboards = ""
        callback(True, pedalboards)

    def hmi_load_bank_pedalboard(self, bank_id, pedalboard_uri, callback):
        logging.info("hmi load bank pedalboard")

        #if bank_id >= len(self.banks):
        #print("ERROR in addressing.py: bank id out of bounds")
        #return

        #pedalboards = self.banks[bank_id]['pedalboards']
        #if pedalboard_id >= len(pedalboards):
        #print("ERROR in addressing.py: pedalboard id out of bounds")
        #return

        #uri = pedalboards[pedalboard_id]['uri']

        self.host.load_uri(pedalboard_uri)

    def hmi_parameter_get(self, instance_id, port, callback):
        logging.info("hmi parameter get")
        instance = self.mapper.get_instance(instance_id)
        callback(self.instances[instance]['ports'][port])

    def hmi_parameter_set(self, instance_id, port, value, callback=None):
        logging.info("hmi parameter set")
        instance = self.mapper.get_instance(instance_id)

        if port == ":bypass":
            self._set_bypassed(instance, bool(value))
            if self.host is not None:
                self.host.enable(instance, not value, callback)

        else:
            self._set_value(instance, port, value)
            if self.host is not None:
                self.host.param_set("%s/%s" % (instance, port), value,
                                    callback)

    def hmi_parameter_addressing_next(self, hardware_type, hardware_id,
                                      actuator_type, actuator_id, callback):
        logging.info("hmi parameter addressing next")
        actuator_hw = (hardware_type, hardware_id, actuator_type, actuator_id)
        self._address_next(actuator_hw, callback)

    # -----------------------------------------------------------------------------------------------------------------

    #def peakmeter_set(self, status, callback):
    #if "on" in status:
    #self.peakmeter_on(callback)
    #elif "off" in status:
    #self.peakmeter_off(callback)

    #def peakmeter_on(self, cb):

    #def mon_peak_in_l(ok):
    #if ok:
    #self.parameter_monitor(PEAKMETER_IN, PEAKMETER_MON_VALUE_L, ">=", -30, cb)
    #self.parameter_monitor(PEAKMETER_IN, PEAKMETER_MON_PEAK_L, ">=", -30, cb)

    #def mon_peak_in_r(ok):
    #if ok:
    #self.parameter_monitor(PEAKMETER_IN, PEAKMETER_MON_VALUE_R, ">=", -30, lambda r:None)
    #self.parameter_monitor(PEAKMETER_IN, PEAKMETER_MON_PEAK_R, ">=", -30, lambda r:None)

    #def mon_peak_out_l(ok):
    #if ok:
    #self.parameter_monitor(PEAKMETER_OUT, PEAKMETER_MON_VALUE_L, ">=", -30, lambda r:None)
    #self.parameter_monitor(PEAKMETER_OUT, PEAKMETER_MON_PEAK_L, ">=", -30, lambda r:None)

    #def mon_peak_out_r(ok):
    #if ok:
    #self.parameter_monitor(PEAKMETER_OUT, PEAKMETER_MON_VALUE_R, ">=", -30, lambda r:None)
    #self.parameter_monitor(PEAKMETER_OUT, PEAKMETER_MON_PEAK_R, ">=", -30, lambda r:None)

    #def setup_peak_in(ok):
    #if ok:
    #self.connect("system:capture_1", "effect_%d:%s" % (PEAKMETER_IN, PEAKMETER_L), mon_peak_in_l, True)
    #self.connect("system:capture_2", "effect_%d:%s" % (PEAKMETER_IN, PEAKMETER_R), mon_peak_in_r, True)

    #def setup_peak_out(ok):
    #if ok:
    #self._peakmeter = True
    #for port in self._playback_1_connected_ports:
    #self.connect(port, "effect_%d:%s" % (PEAKMETER_OUT, PEAKMETER_L), mon_peak_out_l, True)
    #for port in self._playback_2_connected_ports:
    #self.connect(port, "effect_%d:%s" % (PEAKMETER_OUT, PEAKMETER_R), mon_peak_out_r, True)

    #self.add(PEAKMETER_URI, PEAKMETER_IN, setup_peak_in, True)
    #self.add(PEAKMETER_URI, PEAKMETER_OUT, setup_peak_out, True)

    #def peakmeter_off(self, cb):
    #self.remove(PEAKMETER_IN, cb, True)
    #self.remove(PEAKMETER_OUT, lambda r: None, True)
    #self._tuner = False

    #def tuner_set(self, status, callback):
    #if "on" in status:
    #self.tuner_on(callback)
    #elif "off" in status:
    #self.tuner_off(callback)

    #def tuner_on(self, cb):
    #def mon_tuner(ok):
    #if ok:
    #self.parameter_monitor(TUNER, TUNER_MON_PORT, ">=", 0, cb)

    #def setup_tuner(ok):
    #if ok:
    #self._tuner = True
    #self.connect("system:capture_%s" % self._tuner_port, "effect_%d:%s" % (TUNER, TUNER_PORT), mon_tuner, True)

    #def mute_callback():
    #self.add(TUNER_URI, TUNER, setup_tuner, True)
    #self.mute(mute_callback)

    #def tuner_off(self, cb):
    #def callback():
    #self.remove(TUNER, cb, True)
    #self._tuner = False
    #self.unmute(callback)

    #def tuner_set_input(self, input, callback):
    ## TODO: implement
    #self.disconnect("system:capture_%s" % self._tuner_port, "effect_%d:%s" % (TUNER, TUNER_PORT), lambda r:r, True)
    #self._tuner_port = input
    #self.connect("system:capture_%s" % input, "effect_%d:%s" % (TUNER, TUNER_PORT), callback, True)

    # -----------------------------------------------------------------------------------------------------------------

    def _init_addressings(self):
        # 'self.addressings' uses a structure like this:
        # "/hmi/knob1": {'addrs': [], 'idx': 0}
        self.addressings = dict((act["uri"], {
            'idx': 0,
            'addrs': []
        }) for act in get_hardware()["actuators"])

        # Store all possible hardcoded values
        self._hw2uri_map = {}
        self._uri2hw_map = {}

        for i in range(0, 4):
            knob_hw = (HARDWARE_TYPE_MOD, 0, ACTUATOR_TYPE_KNOB, i)
            foot_hw = (HARDWARE_TYPE_MOD, 0, ACTUATOR_TYPE_FOOTSWITCH, i)
            knob_uri = "/hmi/knob%i" % (i + 1)
            foot_uri = "/hmi/footswitch%i" % (i + 1)

            self._hw2uri_map[knob_hw] = knob_uri
            self._hw2uri_map[foot_hw] = foot_uri
            self._uri2hw_map[knob_uri] = knob_hw
            self._uri2hw_map[foot_uri] = foot_hw

    # -----------------------------------------------------------------------------------------------------------------

    def _addressing_load(self, actuator_uri, callback=None):
        addressings = self.addressings[actuator_uri]
        addressings_addrs = addressings['addrs']
        addressings_idx = addressings['idx']

        try:
            addressing = addressings_addrs[addressings_idx]
        except IndexError:
            return

        actuator_hw = self._uri2hw_map[actuator_uri]

        self.hmi.control_add(
            addressing['instance_id'],
            addressing['port'],
            addressing['label'],
            addressing['type'],
            addressing['unit'],
            addressing['value'],
            addressing['maximum'],
            addressing['minimum'],
            addressing['steps'],
            actuator_hw[0],
            actuator_hw[1],
            actuator_hw[2],
            actuator_hw[3],
            len(addressings_addrs),  # num controllers
            addressings_idx + 1,  # index
            addressing['options'],
            callback)

    def _address_next(self, actuator_hw, callback=lambda r: r):
        actuator_uri = self._hw2uri_map[actuator_hw]

        addressings = self.addressings[actuator_uri]
        addressings_addrs = addressings['addrs']
        addressings_idx = addressings['idx']

        if len(addressings_addrs) > 0:
            addressings['idx'] = (addressings['idx'] +
                                  1) % len(addressings_addrs)
            callback(True)
            self._addressing_load(actuator_uri)
        else:
            callback(True)
            self.hmi.control_clean(actuator_hw[0], actuator_hw[1],
                                   actuator_hw[2], actuator_hw[3])

    def _unaddress(self, instance, port):
        data = self.instances.get(instance, None)
        if data is None:
            return None

        addressing = data['addressing'].pop(port, None)
        if addressing is None:
            return None

        actuator_uri = addressing['actuator_uri']
        addressings = self.addressings[actuator_uri]
        addressings_addrs = addressings['addrs']
        addressings_idx = addressings['idx']

        index = addressings_addrs.index(addressing)
        addressings_addrs.pop(index)

        # FIXME ?
        if addressings_idx >= index:
            addressings['idx'] -= 1
        #if index <= addressings_idx:
        #addressings['idx'] = addressings_idx - 1

        return actuator_uri