class Session(object): def __init__(self): logging.basicConfig(level=(logging.DEBUG if LOG else logging.WARNING)) self.prefs = UserPreferences() self.player = Player() self.recorder = Recorder() self.recordhandle = None self.screenshot_generator = ScreenshotGenerator() self.websockets = [] # Used in mod-app to know when the current pedalboard changed self.pedalboard_changed_callback = lambda ok, bundlepath, title: None # Try to open real HMI hmiOpened = False if not DEV_HMI: self.hmi = HMI(HMI_SERIAL_PORT, HMI_BAUD_RATE, HMI_TIMEOUT, self.hmi_initialized_cb, self.hmi_reinit_cb) hmiOpened = self.hmi.sp is not None #print("Using HMI =>", hmiOpened) if not hmiOpened: self.hmi = FakeHMI(self.hmi_initialized_cb) print("Using FakeHMI =>", self.hmi) self.host = Host(self.hmi, self.prefs, self.msg_callback) def signal_save(self): # reuse HMI function self.host.hmi_save_current_pedalboard(lambda r: None) def signal_device_updated(self): self.msg_callback("cc-device-updated") def signal_disconnect(self): sockets = self.websockets self.websockets = [] for ws in sockets: ws.write_message("stop") ws.close() self.host.end_session(lambda r: None) def get_hardware_actuators(self): return self.host.addressings.get_actuators() def wait_for_hardware_if_needed(self, callback): return self.host.addressings.wait_for_cc_if_needed(callback) # ----------------------------------------------------------------------------------------------------------------- # App utilities, needed only for mod-app def setupApp(self, pedalboardChangedCallback): self.pedalboard_changed_callback = pedalboardChangedCallback def reconnectApp(self): if self.host.readsock is not None: self.host.readsock.close() self.host.readsock = None if self.host.writesock is not None: self.host.writesock.close() self.host.writesock = None self.host.open_connection_if_needed(None) # ----------------------------------------------------------------------------------------------------------------- # Initialization @gen.coroutine def hmi_initialized_cb(self): self.hmi.initialized = not self.hmi.isFake() uiConnected = bool(len(self.websockets) > 0) yield gen.Task(self.host.initialize_hmi, uiConnected) # This is very nasty, sorry def hmi_reinit_cb(self): if not os.path.exists("/usr/bin/hmi-reset"): return # stop websockets self.hmi.initialized = False self.signal_disconnect() # restart hmi os.system("/usr/bin/hmi-reset; /usr/bin/sleep 3") # reconnect to newly started hmi self.hmi = HMI(HMI_SERIAL_PORT, HMI_BAUD_RATE, HMI_TIMEOUT, self.hmi_initialized_cb, self.hmi_reinit_cb) self.host.reconnect_hmi(self.hmi) # ----------------------------------------------------------------------------------------------------------------- # 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, x, y, callback) # Remove a plugin def web_remove(self, instance, callback): self.host.remove_plugin(instance, callback) # Address a plugin parameter def web_parameter_address(self, port, actuator_uri, label, minimum, maximum, value, steps, tempo, dividers, page, subpage, coloured, momentary, operational_mode, callback): instance, portsymbol = port.rsplit("/", 1) extras = { 'tempo': tempo, 'dividers': dividers, 'page': page, 'subpage': subpage, 'coloured': coloured, 'momentary': momentary, 'operational_mode': operational_mode, } self.host.address(instance, portsymbol, actuator_uri, label, minimum, maximum, value, steps, extras, callback) def web_set_sync_mode(self, mode, callback): self.host.set_sync_mode(mode, True, False, True, 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 # returns saved bundle path def web_save_pedalboard(self, title, asNew, callback): bundlepath, newPB = self.host.save(title, asNew, callback) self.pedalboard_changed_callback(True, bundlepath, title) if self.hmi.initialized and self.host.descriptor.get( 'hmi_set_pb_name', False): self.hmi_set_pb_name(title) self.screenshot_generator.schedule_screenshot(bundlepath) return bundlepath, newPB # Get list of Hardware MIDI devices # returns (devsInUse, devList, names, midi_aggregated_mode) def web_get_midi_device_list(self): return self.host.get_midi_ports() # Set the selected MIDI devices to @a newDevs # Will remove or add new JACK ports as needed def web_set_midi_devices(self, newDevs, midi_aggregated_mode): return self.host.set_midi_devices(newDevs, midi_aggregated_mode) # Send a ping to HMI and Websockets def web_ping(self, callback): if self.hmi.initialized: self.hmi.ping(callback) else: callback(False) self.msg_callback("ping") def web_cv_addressing_plugin_port_add(self, uri, name): return self.host.cv_addressing_plugin_port_add(uri, name) def web_cv_addressing_plugin_port_remove(self, uri, callback): self.host.cv_addressing_plugin_port_remove(uri, 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, callback): def ready(_): self.websockets.append(ws) self.host.open_connection_if_needed(ws) callback(True) # if this is the 1st socket, start ui session if len(self.websockets) == 0: self.host.start_session(ready) else: ready(True) # Webbrowser page closed def websocket_closed(self, ws, callback): try: self.websockets.remove(ws) except ValueError: pass # if this is the last socket, end ui session if len(self.websockets) == 0: self.host.end_session(callback) else: callback(True) # ----------------------------------------------------------------------------------------------------------------- # Start recording def web_recording_start(self): self.player.stop() self.recorder.start() # Stop recording def web_recording_stop(self): if self.recordhandle is not None: self.recordhandle.close() self.recordhandle = self.recorder.stop(True) # Delete previous recording, if any def web_recording_delete(self): self.player.stop() if self.recordhandle is not None: self.recordhandle.close() self.recordhandle = None # Return recording data def web_recording_download(self): if self.recordhandle is None: return "" self.recordhandle.seek(0) return self.recordhandle.read() # Playback of previous recording started def web_playing_start(self, callback): if self.recordhandle is None: self.recordhandle = self.recorder.stop(True) def stop(): self.host.unmute() callback() def schedule_stop(): IOLoop.instance().add_timeout(timedelta(seconds=0.5), stop) self.host.mute() self.player.play(self.recordhandle, schedule_stop) # Playback stopped def web_playing_stop(self): self.player.stop() # ----------------------------------------------------------------------------------------------------------------- # Websocket funtions, called when receiving messages from socket (see webserver.py) # There are no callbacks for these functions. # Set a plugin parameter # We use ":bypass" symbol for on/off state def ws_parameter_set(self, port, value, ws): instance, portsymbol = port.rsplit("/", 1) if portsymbol == ":bypass": bvalue = value >= 0.5 self.host.bypass(instance, bvalue, None) else: self.host.param_set(port, value, None) self.msg_callback_broadcast( "param_set %s %s %f" % (instance, portsymbol, value), ws) # LV2 patch support def ws_patch_get(self, instance, uri, ws): self.host.patch_get(instance, uri, None) def ws_patch_set(self, instance, uri, valuetype, valuedata, ws): writable = self.host.patch_set(instance, uri, valuedata, None) self.msg_callback_broadcast( "patch_set %s %d %s %c %s" % (instance, 1 if writable else 0, uri, valuetype, valuedata), ws) # Set a plugin block position within the canvas def ws_plugin_position(self, instance, x, y, ws): self.host.set_position(instance, x, y) self.msg_callback_broadcast("plugin_pos %s %d %d" % (instance, x, y), ws) # set the size of the pedalboard (in 1:1 view, aka "full zoom") def ws_pedalboard_size(self, width, height): self.host.set_pedalboard_size(width, height) # ----------------------------------------------------------------------------------------------------------------- # web session helpers @gen.coroutine def hmi_set_pb_name(self, name): yield gen.Task(self.hmi.set_pedalboard_name, name) @gen.coroutine def hmi_set_pb_and_ss_name(self, pbname): yield gen.Task(self.hmi.set_pedalboard_name, pbname) if self.host.descriptor.get('hmi_set_ss_name', False): ssname = self.host.snapshot_name() or DEFAULT_SNAPSHOT_NAME yield gen.Task(self.hmi.set_snapshot_name, ssname) # ----------------------------------------------------------------------------------------------------------------- # TODO # Everything after this line is yet to be documented def msg_callback(self, msg): for ws in self.websockets: ws.write_message(msg) def msg_callback_broadcast(self, msg, ws2): for ws in self.websockets: if ws == ws2: continue ws.write_message(msg) def load_pedalboard(self, bundlepath, isDefault): self.host.send_notmodified("feature_enable processing 0") title = self.host.load(bundlepath, isDefault) self.host.send_notmodified("feature_enable processing 1") if isDefault: bundlepath = "" title = "" if self.hmi.initialized and self.host.descriptor.get( 'hmi_set_pb_name', False): self.hmi_set_pb_and_ss_name(title or UNTITLED_PEDALBOARD_NAME) self.pedalboard_changed_callback(True, bundlepath, title) return title def reset(self, callback): logging.debug("SESSION RESET") self.host.send_notmodified("feature_enable processing 0") def host_callback(resp): self.host.send_notmodified("feature_enable processing 1") callback(resp) def reset_host(_): self.host.reset(host_callback) if self.hmi.initialized: def set_pb_name(_): self.hmi.set_pedalboard_name(UNTITLED_PEDALBOARD_NAME, reset_host) def clear_ss_name(_): self.host.hmi_clear_ss_name(set_pb_name) def clear_hmi(_): self.hmi.clear(clear_ss_name) if self.host.descriptor.get("hmi_bank_navigation", False): self.host.setNavigateWithFootswitches(False, clear_hmi) else: clear_hmi(True) else: reset_host(True) # Update the title in HMI self.pedalboard_changed_callback(True, "", "") # host commands def format_port(self, port): if not 'system' in port and not 'effect' in port: port = "effect_%s" % port return port
class Session(object): def __init__(self): self.ioloop = ioloop.IOLoop.instance() self.prefs = UserPreferences() self.player = Player() self.recorder = Recorder() self.recordhandle = None self.screenshot_generator = ScreenshotGenerator() self.websockets = [] # Used in mod-app to know when the current pedalboard changed self.pedalboard_changed_callback = lambda ok,bundlepath,title:None # 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 not hmiOpened: self.hmi = FakeHMI(HMI_SERIAL_PORT, HMI_BAUD_RATE, self.hmi_initialized_cb) self.host = Host(self.hmi, self.prefs, self.msg_callback) def signal_save(self): # reuse HMI function self.host.hmi_save_current_pedalboard(lambda r:None) def signal_device_updated(self): self.msg_callback("cc-device-updated") def signal_disconnect(self): sockets = self.websockets self.websockets = [] for ws in sockets: ws.write_message("stop") ws.close() self.host.end_session(lambda r:None) def get_hardware_actuators(self): return self.host.addressings.get_actuators() # ----------------------------------------------------------------------------------------------------------------- # App utilities, needed only for mod-app def setupApp(self, pedalboardChangedCallback): self.pedalboard_changed_callback = pedalboardChangedCallback def reconnectApp(self): if self.host.readsock is not None: self.host.readsock.close() self.host.readsock = None if self.host.writesock is not None: self.host.writesock.close() self.host.writesock = None self.host.open_connection_if_needed(None) # ----------------------------------------------------------------------------------------------------------------- # Initialization @gen.coroutine def hmi_initialized_cb(self): logging.info("hmi initialized") self.hmi.initialized = True uiConnected = bool(len(self.websockets) > 0) yield gen.Task(self.host.initialize_hmi, uiConnected) # ----------------------------------------------------------------------------------------------------------------- # 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, x, y, callback) # Remove a plugin def web_remove(self, instance, callback): self.host.remove_plugin(instance, callback) # Address a plugin parameter def web_parameter_address(self, port, actuator_uri, label, minimum, maximum, value, steps, callback): instance, portsymbol = port.rsplit("/",1) self.host.address(instance, portsymbol, actuator_uri, label, minimum, maximum, value, steps, 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 # returns saved bundle path def web_save_pedalboard(self, title, asNew): bundlepath = self.host.save(title, asNew) self.pedalboard_changed_callback(True, bundlepath, title) self.screenshot_generator.schedule_screenshot(bundlepath) return bundlepath # Get list of Hardware MIDI devices # returns (devsInUse, devList) def web_get_midi_device_list(self): return self.host.get_midi_ports() # Set the selected MIDI devices to @a newDevs # Will remove or add new JACK ports as needed def web_set_midi_devices(self, newDevs): return self.host.set_midi_devices(newDevs) # Send a ping to HMI and Websockets def web_ping(self, callback): if self.hmi.initialized: self.hmi.ping(callback) else: callback(False) self.msg_callback("ping") # 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, callback): def ready(ok): self.websockets.append(ws) self.host.open_connection_if_needed(ws) callback(True) # if this is the 1st socket, start ui session if len(self.websockets) == 0: self.host.start_session(ready) else: ready(True) # Webbrowser page closed def websocket_closed(self, ws, callback): try: self.websockets.remove(ws) except ValueError: pass # if this is the last socket, end ui session if len(self.websockets) == 0: self.host.end_session(callback) else: callback(True) # ----------------------------------------------------------------------------------------------------------------- # Start recording def web_recording_start(self): self.player.stop() self.recorder.start() # Stop recording def web_recording_stop(self): if self.recordhandle is not None: self.recordhandle.close() self.recordhandle = self.recorder.stop(True) # Delete previous recording, if any def web_recording_delete(self): self.player.stop() if self.recordhandle is not None: self.recordhandle.close() self.recordhandle = None # Return recording data def web_recording_download(self): if self.recordhandle is None: return "" self.recordhandle.seek(0) return self.recordhandle.read() # Playback of previous recording started def web_playing_start(self, callback): if self.recordhandle is None: self.recordhandle = self.recorder.stop(True) def stop(): self.host.unmute() callback() def schedule_stop(): self.ioloop.add_timeout(timedelta(seconds=0.5), stop) self.host.mute() self.player.play(self.recordhandle, schedule_stop) # Playback stopped def web_playing_stop(self): self.player.stop() # ----------------------------------------------------------------------------------------------------------------- # Websocket funtions, called when receiving messages from socket (see webserver.py) # There are no callbacks for these functions. # Set a plugin parameter # We use ":bypass" symbol for on/off state def ws_parameter_set(self, port, value, ws): instance, portsymbol = port.rsplit("/",1) if portsymbol == ":bypass": bvalue = value >= 0.5 self.host.bypass(instance, bvalue, None) else: self.host.param_set(port, value, None) self.msg_callback_broadcast("param_set %s %s %f" % (instance, portsymbol, value), ws) # Set a plugin block position within the canvas def ws_plugin_position(self, instance, x, y): self.host.set_position(instance, x, y) # set the size of the pedalboard (in 1:1 view, aka "full zoom") def ws_pedalboard_size(self, width, height): self.host.set_pedalboard_size(width, height) # ----------------------------------------------------------------------------------------------------------------- # TODO # Everything after this line is yet to be documented def msg_callback(self, msg): for ws in self.websockets: ws.write_message(msg) def msg_callback_broadcast(self, msg, ws2): for ws in self.websockets: if ws == ws2: continue ws.write_message(msg) def load_pedalboard(self, bundlepath, isDefault): title = self.host.load(bundlepath, isDefault) if isDefault: bundlepath = "" title = "" self.pedalboard_changed_callback(True, bundlepath, title) return title def reset(self, callback): def reset_host(ok): self.host.reset(callback) if self.hmi.initialized: def clear_hmi(ok): self.hmi.clear(reset_host) self.host.setNavigateWithFootswitches(False, clear_hmi) else: reset_host(True) self.pedalboard_changed_callback(True, "", "") # host commands def bypass(self, instance, value, callback): value = int(value) > 0 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
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
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)
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