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.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