|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
-
- # Carla Backend code (OSC stuff)
- # Copyright (C) 2011-2015 Filipe Coelho <falktx@falktx.com>
- #
- # 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 doc/GPL.txt file.
-
- # ------------------------------------------------------------------------------------------------------------
- # Imports (Custom)
-
- from carla_host import *
-
- # ------------------------------------------------------------------------------------------------------------
- # Imports (liblo)
-
- from liblo import make_method, Address, ServerError, Server
- from liblo import send as lo_send
- from liblo import TCP as LO_TCP
- from liblo import UDP as LO_UDP
-
- from random import random
-
- # ------------------------------------------------------------------------------------------------------------
- # Global liblo objects
-
- global lo_target, lo_target_name
- lo_target = None
- lo_target_name = ""
-
- # ------------------------------------------------------------------------------------------------------------
- # Host OSC object
-
- class CarlaHostOSC(CarlaHostQtPlugin):
- def __init__(self):
- CarlaHostQtPlugin.__init__(self)
-
- # -------------------------------------------------------------------
-
- def printAndReturnError(self, error):
- print(error)
- self.fLastError = error
- return False
-
- def sendMsg(self, lines):
- global lo_target, lo_target_name
-
- if lo_target is None:
- return self.printAndReturnError("lo_target is None")
- if lo_target_name is None:
- return self.printAndReturnError("lo_target_name is None")
- if len(lines) < 2:
- return self.printAndReturnError("not enough arguments")
-
- method = lines.pop(0)
-
- if method not in (
- #"set_option",
- "set_active",
- "set_drywet",
- "set_volume",
- "set_balance_left",
- "set_balance_right",
- "set_panning",
- #"set_ctrl_channel",
- "set_parameter_value",
- "set_parameter_midi_channel",
- "set_parameter_midi_cc",
- "set_program",
- "set_midi_program",
- #"set_custom_data",
- #"set_chunk_data",
- #"prepare_for_save",
- #"reset_parameters",
- #"randomize_parameters",
- "send_midi_note"
- ):
- return self.printAndReturnError("invalid method '%s'" % method)
-
- pluginId = lines.pop(0)
-
- args = []
-
- if method == "send_midi_note":
- channel, note, velocity = lines
-
- if velocity:
- method = "note_on"
- args = [channel, note, velocity]
- else:
- method = "note_off"
- args = [channel, note]
-
- else:
- for line in lines:
- if isinstance(line, bool):
- args.append(int(line))
- else:
- args.append(line)
-
- path = "/%s/%i/%s" % (lo_target_name, pluginId, method)
-
- print(path, args)
-
- lo_send(lo_target, path, *args)
- return True
-
- # -------------------------------------------------------------------
-
- def engine_init(self, driverName, clientName):
- global lo_target
- return lo_target is not None
-
- def engine_close(self):
- return True
-
- def engine_idle(self):
- return
-
- def is_engine_running(self):
- global lo_target
- return lo_target is not None
-
- def set_engine_about_to_close(self):
- return
-
- # ------------------------------------------------------------------------------------------------------------
- # OSC Control server
-
- class CarlaControlServer(Server):
- def __init__(self, host, mode):
- Server.__init__(self, 8998 + int(random()*9000), mode)
-
- self.host = host
-
- def idle(self):
- self.fReceivedMsgs = False
-
- while self.recv(0) and self.fReceivedMsgs:
- pass
-
- def getFullURL(self):
- return "%scarla-control" % self.get_url()
-
- @make_method('/carla-control/add_plugin_start', 'is') # FIXME skip name
- def add_plugin_start_callback(self, path, args):
- print(path, args)
- self.fReceivedMsgs = True
- pluginId, pluginName = args
- self.host._add(pluginId)
- self.host._set_pluginInfoUpdate(pluginId, {'name': pluginName})
-
- @make_method('/carla-control/add_plugin_end', 'i') # FIXME skip name
- def add_plugin_end_callback(self, path, args):
- print(path, args)
- self.fReceivedMsgs = True
- pluginId, = args
- self.host.PluginAddedCallback.emit(pluginId, "") #self.fPluginsInfo[pluginId].pluginInfo['name'])
-
- @make_method('/carla-control/remove_plugin', 'i')
- def remove_plugin_callback(self, path, args):
- print(path, args)
- self.fReceivedMsgs = True
- pluginId, = args
- self.host.PluginRemovedCallback.emit(pluginId)
-
- @make_method('/carla-control/set_plugin_info1', 'iiiih')
- def set_plugin_info1_callback(self, path, args):
- print(path, args)
- self.fReceivedMsgs = True
- pluginId, type_, category, hints, uniqueId = args # , optsAvail, optsEnabled
- optsAvail = optsEnabled = 0x0 # FIXME
-
- hints &= ~PLUGIN_HAS_CUSTOM_UI
-
- pinfo = {
- 'type': type_,
- 'category': category,
- 'hints': hints,
- 'optionsAvailable': optsAvail,
- 'optionsEnabled': optsEnabled,
- 'uniqueId': uniqueId
- }
-
- self.host._set_pluginInfoUpdate(pluginId, pinfo)
-
- @make_method('/carla-control/set_plugin_info2', 'issss')
- def set_plugin_info2_callback(self, path, args):
- print(path, args)
- self.fReceivedMsgs = True
- pluginId, realName, label, maker, copyright = args # , filename, name, iconName
- filename = name = iconName = "" # FIXME
-
- pinfo = {
- 'filename': filename,
- #'name': name, # FIXME
- 'label': label,
- 'maker': maker,
- 'copyright': copyright,
- 'iconName': iconName
- }
-
- self.host._set_pluginInfoUpdate(pluginId, pinfo)
- self.host._set_pluginRealName(pluginId, realName)
-
- @make_method('/carla-control/set_audio_count', 'iii')
- def set_audio_count_callback(self, path, args):
- print(path, args)
- self.fReceivedMsgs = True
- pluginId, ins, outs = args
- self.host._set_audioCountInfo(pluginId, {'ins': ins, 'outs': outs})
-
- @make_method('/carla-control/set_midi_count', 'iii')
- def set_midi_count_callback(self, path, args):
- print(path, args)
- self.fReceivedMsgs = True
- pluginId, ins, outs = args
- self.host._set_midiCountInfo(pluginId, {'ins': ins, 'outs': outs})
-
- @make_method('/carla-control/set_parameter_count', 'iii') # FIXME
- def set_parameter_count_callback(self, path, args):
- print(path, args)
- self.fReceivedMsgs = True
- pluginId, ins, outs = args # , count
- count = ins + outs
- self.host._set_parameterCountInfo(pluginId, count, {'ins': ins, 'outs': outs})
-
- @make_method('/carla-control/set_program_count', 'ii')
- def set_program_count_callback(self, path, args):
- print(path, args)
- self.fReceivedMsgs = True
- pluginId, count = args
- self.host._set_programCount(pluginId, count)
-
- @make_method('/carla-control/set_midi_program_count', 'ii')
- def set_midi_program_count_callback(self, path, args):
- print(path, args)
- self.fReceivedMsgs = True
- pluginId, count = args
- self.host._set_midiProgramCount(pluginId, count)
-
- @make_method('/carla-control/set_parameter_data', 'iiiiss')
- def set_parameter_data_callback(self, path, args):
- print(path, args)
- self.fReceivedMsgs = True
- pluginId, paramId, type_, hints, name, unit = args
-
- hints &= ~(PARAMETER_USES_SCALEPOINTS | PARAMETER_USES_CUSTOM_TEXT)
-
- paramInfo = {
- 'name': name,
- 'symbol': "",
- 'unit': unit,
- 'scalePointCount': 0,
- }
- self.host._set_parameterInfo(pluginId, paramId, paramInfo)
-
- paramData = {
- 'type': type_,
- 'hints': hints,
- 'index': paramId,
- 'rindex': -1,
- 'midiCC': -1,
- 'midiChannel': 0
- }
- self.host._set_parameterData(pluginId, paramId, paramData)
-
- @make_method('/carla-control/set_parameter_ranges1', 'iifff')
- def set_parameter_ranges1_callback(self, path, args):
- print(path, args)
- self.fReceivedMsgs = True
- pluginId, paramId, def_, min_, max_ = args
-
- paramRanges = {
- 'def': def_,
- 'min': min_,
- 'max': max_
- }
-
- self.host._set_parameterRangesUpdate(pluginId, paramId, paramRanges)
-
- @make_method('/carla-control/set_parameter_ranges2', 'iifff')
- def set_parameter_ranges2_callback(self, path, args):
- print(path, args)
- self.fReceivedMsgs = True
- pluginId, paramId, step, stepSmall, stepLarge = args
-
- paramRanges = {
- 'step': step,
- 'stepSmall': stepSmall,
- 'stepLarge': stepLarge
- }
-
- self.host._set_parameterRangesUpdate(pluginId, paramId, paramRanges)
-
- @make_method('/carla-control/set_parameter_midi_cc', 'iii')
- def set_parameter_midi_cc_callback(self, path, args):
- print(path, args)
- self.fReceivedMsgs = True
- pluginId, paramId, cc = args
- self.host._set_parameterMidiCC(pluginId, paramId, cc)
- self.host.ParameterMidiCcChangedCallback.emit(pluginId, paramId, cc)
-
- @make_method('/carla-control/set_parameter_midi_channel', 'iii')
- def set_parameter_midi_channel_callback(self, path, args):
- print(path, args)
- self.fReceivedMsgs = True
- pluginId, paramId, channel = args
- self.host._set_parameterMidiChannel(pluginId, paramId, channel)
- self.host.ParameterMidiChannelChangedCallback.emit(pluginId, paramId, channel)
-
- @make_method('/carla-control/set_parameter_value', 'iif')
- def set_parameter_value_callback(self, path, args):
- pluginId, paramId, paramValue = args
-
- if paramId < 0:
- self.host._set_internalValue(pluginId, paramId, paramValue)
- else:
- self.host._set_parameterValue(pluginId, paramId, paramValue)
-
- self.host.ParameterValueChangedCallback.emit(pluginId, paramId, paramValue)
-
- @make_method('/carla-control/set_default_value', 'iif')
- def set_default_value_callback(self, path, args):
- print(path, args)
- self.fReceivedMsgs = True
- pluginId, paramId, paramValue = args
- self.host._set_parameterDefault(pluginId, paramId, paramValue)
- self.host.ParameterDefaultChangedCallback.emit(pluginId, paramId, paramValue)
-
- @make_method('/carla-control/set_current_program', 'ii')
- def set_current_program_callback(self, path, args):
- print(path, args)
- self.fReceivedMsgs = True
- pluginId, current = args
- self.host._set_currentProgram(pluginId, current)
- self.host.ProgramChangedCallback.emit(pluginId, current)
-
- @make_method('/carla-control/set_current_midi_program', 'ii')
- def set_current_midi_program_callback(self, path, args):
- print(path, args)
- self.fReceivedMsgs = True
- pluginId, current = args
- self.host._set_currentMidiProgram(pluginId, current)
- self.host.MidiProgramChangedCallback.emit(pluginId, current)
-
- @make_method('/carla-control/set_program_name', 'iis')
- def set_program_name_callback(self, path, args):
- print(path, args)
- self.fReceivedMsgs = True
- pluginId, progId, progName = args
- self.host._set_programName(pluginId, progId, progName)
-
- @make_method('/carla-control/set_midi_program_data', 'iiiis')
- def set_midi_program_data_callback(self, path, args):
- print(path, args)
- self.fReceivedMsgs = True
- pluginId, midiProgId, bank, program, name = args
- self.host._set_midiProgramData(pluginId, midiProgId, {'bank': bank, 'program': program, 'name': name})
-
- @make_method('/carla-control/note_on', 'iiii')
- def set_note_on_callback(self, path, args):
- print(path, args)
- self.fReceivedMsgs = True
- pluginId, channel, note, velocity = args
- self.host.NoteOnCallback.emit(pluginId, channel, note, velocity)
-
- @make_method('/carla-control/note_off', 'iii')
- def set_note_off_callback(self, path, args):
- print(path, args)
- self.fReceivedMsgs = True
- pluginId, channel, note = args
- self.host.NoteOffCallback.emit(pluginId, channel, note)
-
- @make_method('/carla-control/set_peaks', 'iffff')
- def set_peaks_callback(self, path, args):
- self.fReceivedMsgs = True
- pluginId, in1, in2, out1, out2 = args
- self.host._set_peaks(pluginId, in1, in2, out1, out2)
-
- @make_method('/carla-control/exit', '')
- def set_exit_callback(self, path, args):
- print(path, args)
- self.fReceivedMsgs = True
- self.host.QuitCallback.emit()
-
- @make_method(None, None)
- def fallback(self, path, args):
- print("ControlServer::fallback(\"%s\") - unknown message, args =" % path, args)
- self.fReceivedMsgs = True
-
- # ------------------------------------------------------------------------------------------------------------
- # Main Window
-
- class HostWindowOSC(HostWindow):
- def __init__(self, host, oscAddr):
- HostWindow.__init__(self, host, False)
- self.host = host
-
- if False:
- # kdevelop likes this :)
- host = CarlaHostPlugin()
- self.host = host
-
- # ----------------------------------------------------------------------------------------------------
- # Internal stuff
-
- self.fIdleTimer = 0
- self.fOscAddress = oscAddr
- self.fOscServer = None
-
- # ----------------------------------------------------------------------------------------------------
- # Set up GUI (not connected)
-
- self.ui.act_file_refresh.setEnabled(False)
-
- # ----------------------------------------------------------------------------------------------------
- # Connect actions to functions
-
- self.ui.act_file_connect.triggered.connect(self.slot_fileConnect)
- self.ui.act_file_refresh.triggered.connect(self.slot_fileRefresh)
-
- # ----------------------------------------------------------------------------------------------------
- # Final setup
-
- if oscAddr:
- QTimer.singleShot(0, self.connectOsc)
-
- def connectOsc(self, addr = None):
- global lo_target, lo_target_name
-
- if addr is not None:
- self.fOscAddress = addr
-
- lo_target = Address(self.fOscAddress)
- lo_target_name = self.fOscAddress.rsplit("/", 1)[-1]
- print("Connecting to \"%s\" as '%s'..." % (self.fOscAddress, lo_target_name))
-
- try:
- self.fOscServer = CarlaControlServer(self.host, LO_UDP if self.fOscAddress.startswith("osc.udp") else LO_TCP)
- except: # ServerError as err:
- QMessageBox.critical(self, self.tr("Error"), self.tr("Failed to connect, operation failed."))
- return
-
- self.fIdleTimer = self.startTimer(20)
- lo_send(lo_target, "/register", self.fOscServer.getFullURL())
-
- self.startTimers()
- self.ui.act_file_refresh.setEnabled(True)
-
- def disconnectOsc(self):
- global lo_target, lo_target_name
-
- self.killTimers()
- self.ui.act_file_refresh.setEnabled(False)
-
- if lo_target is not None:
- try:
- lo_send(lo_target, "/unregister")
- except:
- pass
-
- self.killTimer(self.fIdleTimer)
- self.fIdleTimer = 0
-
- if self.fOscServer is not None:
- del self.fOscServer
- self.fOscServer = None
-
- self.removeAllPlugins()
-
- lo_target = None
- lo_target_name = ""
- self.fOscAddress = ""
-
- def removeAllPlugins(self):
- self.host.fPluginsInfo = []
- HostWindow.removeAllPlugins(self)
-
- @pyqtSlot()
- def slot_fileConnect(self):
- global lo_target, lo_target_name
-
- if lo_target and self.fOscServer:
- urlText = self.fOscAddress
- else:
- urlText = "osc.tcp://127.0.0.1:19000/Carla"
-
- addr, ok = QInputDialog.getText(self, self.tr("Carla Control - Connect"), self.tr("Address"), text=urlText)
-
- if not ok:
- return
-
- self.disconnectOsc()
- self.connectOsc(addr)
-
- @pyqtSlot()
- def slot_fileRefresh(self):
- global lo_target
-
- if lo_target is None or self.fOscServer is None:
- return
-
- self.killTimers()
- lo_send(lo_target, "/unregister")
- self.removeAllPlugins()
- lo_send(lo_target, "/register", self.fOscServer.getFullURL())
- self.startTimers()
-
- @pyqtSlot()
- def slot_handleQuitCallback(self):
- self.disconnectOsc()
-
- def timerEvent(self, event):
- if event.timerId() == self.fIdleTimer:
- self.fOscServer.idle()
- HostWindow.timerEvent(self, event)
-
- def closeEvent(self, event):
- global lo_target
-
- self.killTimers()
-
- if lo_target is not None and self.fOscServer is not None:
- lo_send(lo_target, "/unregister")
-
- HostWindow.closeEvent(self, event)
-
- # ------------------------------------------------------------------------------------------------------------
|