#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Carla plugin host (plugin UI) # Copyright (C) 2013 Filipe Coelho # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; either version 2 of # the License, or any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # For a full copy of the GNU General Public License see the GPL.txt file # ------------------------------------------------------------------------------------------------------------ # Imports (Global) from time import sleep # ------------------------------------------------------------------------------------------------------------ # Imports (Custom Stuff) from carla_host import * from externalui import ExternalUI # ------------------------------------------------------------------------------------------------------------ # Helper object class PluginStoreInfo(object): __slots__ = [ 'pluginInfo', 'pluginRealName', 'audioCountInfo', 'midiCountInfo', 'parameterCount', 'parameterCountInfo', 'parameterInfoS', 'parameterDataS', 'parameterRangeS', 'parameterValueS', 'programCount', 'programCurrent', 'programNameS', 'midiProgramCount', 'midiProgramCurrent', 'midiProgramDataS', 'peaks' ] # ------------------------------------------------------------------------------------------------------------ # Host Plugin object class PluginHost(object): def __init__(self, sampleRate): object.__init__(self) self.fSupportedFileExts = "" self.fBufferSize = 0 self.fSampleRate = sampleRate self.fLastError = "" self.fIsRunning = True self.fPluginsInfo = [] def _add(self, pluginId): if len(self.fPluginsInfo) != pluginId: return info = PluginStoreInfo() info.pluginInfo = PyCarlaPluginInfo info.pluginRealName = "" info.audioCountInfo = PyCarlaPortCountInfo info.midiCountInfo = PyCarlaPortCountInfo info.parameterCount = 0 info.parameterCountInfo = PyCarlaPortCountInfo info.parameterInfoS = [] info.parameterDataS = [] info.parameterRangeS = [] info.parameterValueS = [] info.programCount = 0 info.programCurrent = -1 info.programNameS = [] info.midiProgramCount = 0 info.midiProgramCurrent = -1 info.midiProgramDataS = [] info.peaks = [0.0, 0.0, 0.0, 0.0] self.fPluginsInfo.append(info) def _set_pluginInfo(self, pluginId, info): self.fPluginsInfo[pluginId].pluginInfo = info def _set_pluginName(self, pluginId, name): self.fPluginsInfo[pluginId].pluginInfo['name'] = name def _set_pluginRealName(self, pluginId, realName): self.fPluginsInfo[pluginId].pluginRealName = realName def _set_audioCountInfo(self, pluginId, info): self.fPluginsInfo[pluginId].audioCountInfo = info def _set_midiCountInfo(self, pluginId, info): self.fPluginsInfo[pluginId].midiCountInfo = info def _set_parameterCountInfo(self, pluginId, count, info): self.fPluginsInfo[pluginId].parameterCount = count self.fPluginsInfo[pluginId].parameterCountInfo = info # clear self.fPluginsInfo[pluginId].parameterInfoS = [] self.fPluginsInfo[pluginId].parameterDataS = [] self.fPluginsInfo[pluginId].parameterRangeS = [] self.fPluginsInfo[pluginId].parameterValueS = [] # add placeholders for x in range(count): self.fPluginsInfo[pluginId].parameterInfoS.append(PyCarlaParameterInfo) self.fPluginsInfo[pluginId].parameterDataS.append(PyParameterData) self.fPluginsInfo[pluginId].parameterRangeS.append(PyParameterRanges) self.fPluginsInfo[pluginId].parameterValueS.append(0.0) def _set_programCount(self, pluginId, count): self.fPluginsInfo[pluginId].programCount = count # clear self.fPluginsInfo[pluginId].programNameS = [] # add placeholders for x in range(count): self.fPluginsInfo[pluginId].programNameS.append("") def _set_midiProgramCount(self, pluginId, count): self.fPluginsInfo[pluginId].midiProgramCount = count # clear self.fPluginsInfo[pluginId].midiProgramDataS = [] # add placeholders for x in range(count): self.fPluginsInfo[pluginId].midiProgramDataS.append(PyMidiProgramData) def _set_parameterInfoS(self, pluginId, paramIndex, info): if paramIndex < self.fPluginsInfo[pluginId].parameterCount: self.fPluginsInfo[pluginId].parameterInfoS[paramIndex] = info def _set_parameterDataS(self, pluginId, paramIndex, data): if paramIndex < self.fPluginsInfo[pluginId].parameterCount: self.fPluginsInfo[pluginId].parameterDataS[paramIndex] = data def _set_parameterRangeS(self, pluginId, paramIndex, ranges): if paramIndex < self.fPluginsInfo[pluginId].parameterCount: self.fPluginsInfo[pluginId].parameterRangeS[paramIndex] = ranges def _set_parameterValueS(self, pluginId, paramIndex, value): if paramIndex < self.fPluginsInfo[pluginId].parameterCount: self.fPluginsInfo[pluginId].parameterValueS[paramIndex] = value def _set_parameterDefault(self, pluginId, paramIndex, value): if paramIndex < self.fPluginsInfo[pluginId].parameterCount: self.fPluginsInfo[pluginId].parameterRangeS[paramIndex]['def'] = value def _set_parameterMidiChannel(self, pluginId, paramIndex, channel): if paramIndex < self.fPluginsInfo[pluginId].parameterCount: self.fPluginsInfo[pluginId].parameterDataS[paramIndex]['midiChannel'] = channel def _set_parameterMidiCC(self, pluginId, paramIndex, cc): if paramIndex < self.fPluginsInfo[pluginId].parameterCount: self.fPluginsInfo[pluginId].parameterDataS[paramIndex]['midiCC'] = cc def _set_currentProgram(self, pluginId, pIndex): self.fPluginsInfo[pluginId].programCurrent = pIndex def _set_currentMidiProgram(self, pluginId, mpIndex): self.fPluginsInfo[pluginId].midiProgramCurrent = mpIndex def _set_programNameS(self, pluginId, pIndex, name): if pIndex < self.fPluginsInfo[pluginId].programCount: self.fPluginsInfo[pluginId].programNameS[pIndex] = name def _set_midiProgramDataS(self, pluginId, mpIndex, data): if mpIndex < self.fPluginsInfo[pluginId].midiProgramCount: self.fPluginsInfo[pluginId].midiProgramDataS[mpIndex] = data def _set_peaks(self, pluginId, in1, in2, out1, out2): self.fPluginsInfo[pluginId].peaks = [in1, in2, out1, out2] # ------------------------------------------------------------------- def get_complete_license_text(self): return "" def get_supported_file_extensions(self): return self.fSupportedFileExts # ------------------------------------------------------------------- def get_engine_driver_count(self): return 1 def get_engine_driver_name(self, index): return "Plugin" def get_engine_driver_device_names(self, index): return [] def get_engine_driver_device_info(self, index, name): return PyEngineDriverDeviceInfo # ------------------------------------------------------------------- def get_internal_plugin_count(self): return 0 def get_internal_plugin_info(self, index): return None # ------------------------------------------------------------------- def engine_init(self, driverName, clientName): return True def engine_close(self): return True def engine_idle(self): if Carla.gui.idleExternalUI(): return self.fIsRunning = False Carla.gui.d_uiQuit() def is_engine_running(self): return self.fIsRunning def set_engine_about_to_close(self): pass def set_engine_callback(self, func): pass def set_engine_option(self, option, value, valueStr): Carla.gui.send(["set_engine_option", option, value, valueStr]) # ------------------------------------------------------------------- def set_file_callback(self, func): pass def load_file(self, filename): Carla.gui.send(["load_file", filename]) return True def load_project(self, filename): Carla.gui.send(["load_project", filename]) return True def save_project(self, filename): Carla.gui.send(["save_project", filename]) return True # ------------------------------------------------------------------- def patchbay_connect(self, portIdA, portIdB): Carla.gui.send(["patchbay_connect", portIdA, portIdB]) return True def patchbay_disconnect(self, connectionId): Carla.gui.send(["patchbay_disconnect", connectionId]) return True def patchbay_refresh(self): Carla.gui.send(["patchbay_refresh"]) return True # ------------------------------------------------------------------- def transport_play(self): Carla.gui.send(["transport_play"]) def transport_pause(self): Carla.gui.send(["transport_pause"]) def transport_relocate(self, frame): Carla.gui.send(["transport_relocate"]) def get_current_transport_frame(self): return 0 def get_transport_info(self): return PyCarlaTransportInfo # ------------------------------------------------------------------- def add_plugin(self, btype, ptype, filename, name, label, extraPtr): Carla.gui.send(["add_plugin", btype, ptype, filename, name, label]) return True def remove_plugin(self, pluginId): Carla.gui.send(["remove_plugin", pluginId]) return True def remove_all_plugins(self): Carla.gui.send(["remove_all_plugins"]) return True def rename_plugin(self, pluginId, newName): Carla.gui.send(["rename_plugin", pluginId, newName]) return newName def clone_plugin(self, pluginId): Carla.gui.send(["clone_plugin", pluginId]) return True def replace_plugin(self, pluginId): Carla.gui.send(["replace_plugin", pluginId]) return True def switch_plugins(self, pluginIdA, pluginIdB): Carla.gui.send(["switch_plugins", pluginIdA, pluginIdB]) return True # ------------------------------------------------------------------- def load_plugin_state(self, pluginId, filename): Carla.gui.send(["load_plugin_state", pluginId, filename]) return True def save_plugin_state(self, pluginId, filename): Carla.gui.send(["save_plugin_state", pluginId, filename]) return True # ------------------------------------------------------------------- def get_plugin_info(self, pluginId): return self.fPluginsInfo[pluginId].pluginInfo def get_audio_port_count_info(self, pluginId): return self.fPluginsInfo[pluginId].audioCountInfo def get_midi_port_count_info(self, pluginId): return self.fPluginsInfo[pluginId].midiCountInfo def get_parameter_count_info(self, pluginId): return self.fPluginsInfo[pluginId].parameterCountInfo def get_parameter_info(self, pluginId, parameterId): return self.fPluginsInfo[pluginId].parameterInfoS[parameterId] def get_parameter_scalepoint_info(self, pluginId, parameterId, scalePointId): return PyCarlaScalePointInfo # ------------------------------------------------------------------- def get_parameter_data(self, pluginId, parameterId): return self.fPluginsInfo[pluginId].parameterDataS[parameterId] def get_parameter_ranges(self, pluginId, parameterId): return self.fPluginsInfo[pluginId].parameterRangeS[parameterId] def get_midi_program_data(self, pluginId, midiProgramId): return self.fPluginsInfo[pluginId].midiProgramDataS[midiProgramId] def get_custom_data(self, pluginId, customDataId): return PyCustomData def get_chunk_data(self, pluginId): return "" # ------------------------------------------------------------------- def get_parameter_count(self, pluginId): return self.fPluginsInfo[pluginId].parameterCount def get_program_count(self, pluginId): return self.fPluginsInfo[pluginId].programCount def get_midi_program_count(self, pluginId): return self.fPluginsInfo[pluginId].midiProgramCount def get_custom_data_count(self, pluginId): return 0 # ------------------------------------------------------------------- def get_parameter_text(self, pluginId, parameterId, value): return "" def get_program_name(self, pluginId, programId): return self.fPluginsInfo[pluginId].programNameS[programId] def get_midi_program_name(self, pluginId, midiProgramId): return self.fPluginsInfo[pluginId].midiProgramDataS[midiProgramId]['label'] def get_real_plugin_name(self, pluginId): return self.fPluginsInfo[pluginId].pluginRealName # ------------------------------------------------------------------- def get_current_program_index(self, pluginId): return self.fPluginsInfo[pluginId].programCurrent def get_current_midi_program_index(self, pluginId): return self.fPluginsInfo[pluginId].midiProgramCurrent def get_default_parameter_value(self, pluginId, parameterId): return self.fPluginsInfo[pluginId].parameterRangeS[parameterId]['def'] def get_current_parameter_value(self, pluginId, parameterId): return self.fPluginsInfo[pluginId].parameterValueS[parameterId] def get_input_peak_value(self, pluginId, isLeft): return self.fPluginsInfo[pluginId].peaks[0 if isLeft else 1] def get_output_peak_value(self, pluginId, isLeft): return self.fPluginsInfo[pluginId].peaks[2 if isLeft else 3] # ------------------------------------------------------------------- def set_option(self, pluginId, option, yesNo): Carla.gui.send(["set_option", pluginId, option, yesNo]) def set_active(self, pluginId, onOff): Carla.gui.send(["set_active", pluginId, onOff]) def set_drywet(self, pluginId, value): Carla.gui.send(["set_drywet", pluginId, value]) def set_volume(self, pluginId, value): Carla.gui.send(["set_volume", pluginId, value]) def set_balance_left(self, pluginId, value): Carla.gui.send(["set_balance_left", pluginId, value]) def set_balance_right(self, pluginId, value): Carla.gui.send(["set_balance_right", pluginId, value]) def set_panning(self, pluginId, value): Carla.gui.send(["set_panning", pluginId, value]) def set_ctrl_channel(self, pluginId, channel): Carla.gui.send(["set_ctrl_channel", pluginId, channel]) # ------------------------------------------------------------------- def set_parameter_value(self, pluginId, parameterId, value): Carla.gui.send(["set_parameter_value", pluginId, parameterId, value]) def set_parameter_midi_channel(self, pluginId, parameterId, channel): Carla.gui.send(["set_parameter_midi_channel", pluginId, parameterId, channel]) def set_parameter_midi_cc(self, pluginId, parameterId, cc): Carla.gui.send(["set_parameter_midi_cc", pluginId, parameterId, cc]) def set_program(self, pluginId, programId): Carla.gui.send(["set_program", pluginId, programId]) def set_midi_program(self, pluginId, midiProgramId): Carla.gui.send(["set_midi_program", pluginId, midiProgramId]) def set_custom_data(self, pluginId, type_, key, value): Carla.gui.send(["set_custom_data", pluginId, type_, key, value]) def set_chunk_data(self, pluginId, chunkData): Carla.gui.send(["set_chunk_data", pluginId, chunkData]) # ------------------------------------------------------------------- def prepare_for_save(self, pluginId): Carla.gui.send(["prepare_for_save", pluginId]) def send_midi_note(self, pluginId, channel, note, velocity): Carla.gui.send(["send_midi_note", pluginId, channel, note, velocity]) def show_custom_ui(self, pluginId, yesNo): Carla.gui.send(["show_custom_ui", pluginId, yesNo]) # ------------------------------------------------------------------- def get_buffer_size(self): return self.fBufferSize def get_sample_rate(self): return self.fSampleRate def get_last_error(self): return self.fLastError def get_host_osc_url_tcp(self): return "" def get_host_osc_url_udp(self): return "" # ------------------------------------------------------------------------------------------------------------ # Main Window class CarlaMiniW(HostWindow, ExternalUI): def __init__(self): HostWindow.__init__(self, None) ExternalUI.__init__(self) if False: from carla_patchbay import CarlaPatchbayW self.fContainer = CarlaPatchbayW(self) else: from carla_rack import CarlaRackW self.fContainer = CarlaRackW(self) self.setupContainer(False) self.setWindowTitle(self.fUiName) self.showUiIfTesting() # ------------------------------------------------------------------- # ExternalUI Callbacks def d_uiShow(self): self.show() def d_uiHide(self): self.hide() def d_uiQuit(self): self.close() app.quit() def d_uiTitleChanged(self, uiTitle): self.setWindowTitle(uiTitle) # ------------------------------------------------------------------- # Qt events def closeEvent(self, event): self.closeExternalUI() HostWindow.closeEvent(self, event) # ------------------------------------------------------------------- # Custom idler def idleExternalUI(self): while True: if self.fPipeRecv is None: return True try: msg = self.fPipeRecv.readline().strip() except IOError: return False if not msg: return True elif msg.startswith("PEAKS_"): pluginId = int(msg.replace("PEAKS_", "")) in1, in2, out1, out2 = [float(i) for i in self.fPipeRecv.readline().strip().split(":")] Carla.host._set_peaks(pluginId, in1, in2, out1, out2) elif msg.startswith("PARAMVAL_"): pluginId, paramId = [int(i) for i in msg.replace("PARAMVAL_", "").split(":")] paramValue = float(self.fPipeRecv.readline().strip()) Carla.host._set_parameterValueS(pluginId, paramId, paramValue) elif msg.startswith("ENGINE_CALLBACK_"): action = int(msg.replace("ENGINE_CALLBACK_", "")) pluginId = int(self.fPipeRecv.readline().strip()) value1 = int(self.fPipeRecv.readline().strip()) value2 = int(self.fPipeRecv.readline().strip()) value3 = float(self.fPipeRecv.readline().strip()) valueStr = self.fPipeRecv.readline().strip().replace("\r", "\n") if action == ENGINE_CALLBACK_PLUGIN_RENAMED: Carla.host._set_pluginName(pluginId, valueStr) elif action == ENGINE_CALLBACK_PARAMETER_VALUE_CHANGED: Carla.host._set_parameterValueS(pluginId, value1, value3) elif action == ENGINE_CALLBACK_PARAMETER_DEFAULT_CHANGED: Carla.host._set_parameterDefault(pluginId, value1, value3) elif action == ENGINE_CALLBACK_PARAMETER_MIDI_CC_CHANGED: Carla.host._set_parameterMidiCC(pluginId, value1, value2) elif action == ENGINE_CALLBACK_PARAMETER_MIDI_CHANNEL_CHANGED: Carla.host._set_parameterMidiChannel(pluginId, value1, value2) elif action == ENGINE_CALLBACK_PROGRAM_CHANGED: Carla.host._set_currentProgram(pluginId, value1) elif action == ENGINE_CALLBACK_MIDI_PROGRAM_CHANGED: Carla.host._set_currentMidiProgram(pluginId, value1) engineCallback(None, action, pluginId, value1, value2, value3, valueStr) elif msg.startswith("PLUGIN_INFO_"): pluginId = int(msg.replace("PLUGIN_INFO_", "")) Carla.host._add(pluginId) type_, category, hints, uniqueId, optsAvail, optsEnabled = [int(i) for i in self.fPipeRecv.readline().strip().split(":")] filename = self.fPipeRecv.readline().strip().replace("\r", "\n") name = self.fPipeRecv.readline().strip().replace("\r", "\n") iconName = self.fPipeRecv.readline().strip().replace("\r", "\n") realName = self.fPipeRecv.readline().strip().replace("\r", "\n") label = self.fPipeRecv.readline().strip().replace("\r", "\n") maker = self.fPipeRecv.readline().strip().replace("\r", "\n") copyright = self.fPipeRecv.readline().strip().replace("\r", "\n") pinfo = { 'type': type_, 'category': category, 'hints': hints, 'optionsAvailable': optsAvail, 'optionsEnabled': optsEnabled, 'filename': filename, 'name': name, 'label': label, 'maker': maker, 'copyright': copyright, 'iconName': iconName, 'patchbayClientId': 0, 'uniqueId': uniqueId } Carla.host._set_pluginInfo(pluginId, pinfo) Carla.host._set_pluginRealName(pluginId, realName) elif msg.startswith("AUDIO_COUNT_"): pluginId, ins, outs = [int(i) for i in msg.replace("AUDIO_COUNT_", "").split(":")] Carla.host._set_audioCountInfo(pluginId, {'ins': ins, 'outs': outs}) elif msg.startswith("MIDI_COUNT_"): pluginId, ins, outs = [int(i) for i in msg.replace("MIDI_COUNT_", "").split(":")] Carla.host._set_midiCountInfo(pluginId, {'ins': ins, 'outs': outs}) elif msg.startswith("PARAMETER_COUNT_"): pluginId, ins, outs, count = [int(i) for i in msg.replace("PARAMETER_COUNT_", "").split(":")] Carla.host._set_parameterCountInfo(pluginId, count, {'ins': ins, 'outs': outs}) elif msg.startswith("PARAMETER_DATA_"): pluginId, paramId = [int(i) for i in msg.replace("PARAMETER_DATA_", "").split(":")] paramType, paramHints, midiChannel, midiCC = [int(i) for i in self.fPipeRecv.readline().strip().split(":")] paramName = self.fPipeRecv.readline().strip().replace("\r", "\n") paramUnit = self.fPipeRecv.readline().strip().replace("\r", "\n") paramInfo = { 'name': paramName, 'symbol': "", 'unit': paramUnit, 'scalePointCount': 0, } Carla.host._set_parameterInfoS(pluginId, paramId, paramInfo) paramData = { 'type': paramType, 'hints': paramHints, 'index': paramId, 'rindex': -1, 'midiCC': midiCC, 'midiChannel': midiChannel } Carla.host._set_parameterDataS(pluginId, paramId, paramData) elif msg.startswith("PARAMETER_RANGES_"): pluginId, paramId = [int(i) for i in msg.replace("PARAMETER_RANGES_", "").split(":")] def_, min_, max_, step, stepSmall, stepLarge = [float(i) for i in self.fPipeRecv.readline().strip().split(":")] paramRanges = { 'def': def_, 'min': min_, 'max': max_, 'step': step, 'stepSmall': stepSmall, 'stepLarge': stepLarge } Carla.host._set_parameterRangeS(pluginId, paramId, paramRanges) elif msg.startswith("PROGRAM_COUNT_"): pluginId, count, current = [int(i) for i in msg.replace("PROGRAM_COUNT_", "").split(":")] Carla.host._set_programCount(pluginId, count) Carla.host._set_currentProgram(pluginId, current) elif msg.startswith("PROGRAM_NAME_"): pluginId, progId = [int(i) for i in msg.replace("PROGRAM_NAME_", "").split(":")] progName = self.fPipeRecv.readline().strip().replace("\r", "\n") Carla.host._set_programNameS(pluginId, progId, progName) elif msg.startswith("MIDI_PROGRAM_COUNT_"): pluginId, count, current = [int(i) for i in msg.replace("MIDI_PROGRAM_COUNT_", "").split(":")] Carla.host._set_midiProgramCount(pluginId, count) Carla.host._set_currentMidiProgram(pluginId, current) elif msg.startswith("MIDI_PROGRAM_DATA_"): pluginId, midiProgId = [int(i) for i in msg.replace("MIDI_PROGRAM_DATA_", "").split(":")] bank, program = [int(i) for i in self.fPipeRecv.readline().strip().split(":")] name = self.fPipeRecv.readline().strip().replace("\r", "\n") Carla.host._set_midiProgramDataS(pluginId, midiProgId, {'bank': bank, 'program': program, 'name': name}) elif msg == "show": self.d_uiShow() elif msg == "hide": self.d_uiHide() elif msg == "quit": self.fQuitReceived = True self.d_uiQuit() elif msg == "uiTitle": uiTitle = self.fPipeRecv.readline().strip().replace("\r", "\n") self.d_uiTitleChanged(uiTitle) else: print("unknown message: \"" + msg + "\"") return True # ------------------------------------------------------------------------------------------------------------ # Main if __name__ == '__main__': # ------------------------------------------------------------- # App initialization app = CarlaApplication() # ------------------------------------------------------------- # Set-up custom signal handling setUpSignals() # ------------------------------------------------------------- # Init plugin host data Carla.isControl = False Carla.isLocal = True Carla.isPlugin = True # ------------------------------------------------------------- # Create GUI first Carla.gui = CarlaMiniW() # ------------------------------------------------------------- # Init plugin host now Carla.host = PluginHost(Carla.gui.d_getSampleRate()) initHost("Carla-Plugin") engineCallback(None, ENGINE_CALLBACK_ENGINE_STARTED, 0, ENGINE_PROCESS_MODE_CONTINUOUS_RACK, ENGINE_TRANSPORT_MODE_PLUGIN, 0.0, "Plugin") # ------------------------------------------------------------- # App-Loop sys.exit(app.exec_())