Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

583 lines
19KB

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # Common Carla code
  4. # Copyright (C) 2011-2013 Filipe Coelho <falktx@falktx.com>
  5. #
  6. # This program is free software; you can redistribute it and/or
  7. # modify it under the terms of the GNU General Public License as
  8. # published by the Free Software Foundation; either version 2 of
  9. # the License, or any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # For a full copy of the GNU General Public License see the doc/GPL.txt file.
  17. # ------------------------------------------------------------------------------------------------------------
  18. # Imports (Global)
  19. import os
  20. import sys
  21. from copy import deepcopy
  22. from subprocess import Popen, PIPE
  23. try:
  24. from PyQt5.QtCore import pyqtSignal, pyqtSlot, qCritical, qFatal, qWarning
  25. from PyQt5.QtGui import QIcon
  26. from PyQt5.QtWidgets import QFileDialog, QMessageBox
  27. except:
  28. from PyQt4.QtCore import pyqtSignal, pyqtSlot, qCritical, qFatal, qWarning
  29. from PyQt4.QtGui import QIcon
  30. from PyQt4.QtGui import QFileDialog, QMessageBox
  31. # ------------------------------------------------------------------------------------------------------------
  32. # Imports (Custom)
  33. from carla_backend import *
  34. # ------------------------------------------------------------------------------------------------------------
  35. # Try Import LADSPA-RDF
  36. try:
  37. import ladspa_rdf
  38. haveLRDF = True
  39. except:
  40. qWarning("LRDF Support not available (LADSPA-RDF will be disabled)")
  41. haveLRDF = False
  42. # ------------------------------------------------------------------------------------------------------------
  43. # Import Signal
  44. from signal import signal, SIGINT, SIGTERM
  45. try:
  46. from signal import SIGUSR1
  47. haveSIGUSR1 = True
  48. except:
  49. haveSIGUSR1 = False
  50. # ------------------------------------------------------------------------------------------------------------
  51. # Platform specific stuff
  52. if MACOS:
  53. from PyQt5.QtGui import qt_mac_set_menubar_icons
  54. qt_mac_set_menubar_icons(False)
  55. elif WINDOWS:
  56. WINDIR = os.getenv("WINDIR")
  57. # ------------------------------------------------------------------------------------------------------------
  58. # Set Version
  59. VERSION = "1.9.0"
  60. # ------------------------------------------------------------------------------------------------------------
  61. # Set TMP
  62. TMP = os.getenv("TMP")
  63. if TMP is None:
  64. if WINDOWS:
  65. qWarning("TMP variable not set")
  66. TMP = os.path.join(WINDIR, "temp")
  67. else:
  68. TMP = "/tmp"
  69. # ------------------------------------------------------------------------------------------------------------
  70. # Set HOME
  71. HOME = os.getenv("HOME")
  72. if HOME is None:
  73. HOME = os.path.expanduser("~")
  74. if LINUX or MACOS:
  75. qWarning("HOME variable not set")
  76. if not os.path.exists(HOME):
  77. qWarning("HOME does not exist")
  78. HOME = TMP
  79. # ------------------------------------------------------------------------------------------------------------
  80. # Set PATH
  81. PATH = os.getenv("PATH")
  82. if PATH is None:
  83. qWarning("PATH variable not set")
  84. if MACOS:
  85. PATH = ("/opt/local/bin", "/usr/local/bin", "/usr/bin", "/bin")
  86. elif WINDOWS:
  87. PATH = (os.path.join(WINDIR, "system32"), WINDIR)
  88. else:
  89. PATH = ("/usr/local/bin", "/usr/bin", "/bin")
  90. else:
  91. PATH = PATH.split(os.pathsep)
  92. # ------------------------------------------------------------------------------------------------------------
  93. # Global Carla object
  94. class CarlaObject(object):
  95. __slots__ = [
  96. 'host',
  97. 'gui',
  98. 'isControl',
  99. 'isLocal',
  100. 'processMode',
  101. 'maxParameters',
  102. 'LADSPA_PATH',
  103. 'DSSI_PATH',
  104. 'LV2_PATH',
  105. 'VST_PATH',
  106. 'AU_PATH',
  107. 'CSOUND_PATH',
  108. 'GIG_PATH',
  109. 'SF2_PATH',
  110. 'SFZ_PATH'
  111. ]
  112. Carla = CarlaObject()
  113. Carla.host = None
  114. Carla.gui = None
  115. Carla.isControl = False
  116. Carla.isLocal = False
  117. Carla.processMode = PROCESS_MODE_MULTIPLE_CLIENTS if LINUX else PROCESS_MODE_CONTINUOUS_RACK
  118. Carla.maxParameters = MAX_DEFAULT_PARAMETERS
  119. # ------------------------------------------------------------------------------------------------------------
  120. # Static MIDI CC list
  121. MIDI_CC_LIST = (
  122. "0x01 Modulation",
  123. "0x02 Breath",
  124. "0x03 (Undefined)",
  125. "0x04 Foot",
  126. "0x05 Portamento",
  127. "0x07 Volume",
  128. "0x08 Balance",
  129. "0x09 (Undefined)",
  130. "0x0A Pan",
  131. "0x0B Expression",
  132. "0x0C FX Control 1",
  133. "0x0D FX Control 2",
  134. "0x0E (Undefined)",
  135. "0x0F (Undefined)",
  136. "0x10 General Purpose 1",
  137. "0x11 General Purpose 2",
  138. "0x12 General Purpose 3",
  139. "0x13 General Purpose 4",
  140. "0x14 (Undefined)",
  141. "0x15 (Undefined)",
  142. "0x16 (Undefined)",
  143. "0x17 (Undefined)",
  144. "0x18 (Undefined)",
  145. "0x19 (Undefined)",
  146. "0x1A (Undefined)",
  147. "0x1B (Undefined)",
  148. "0x1C (Undefined)",
  149. "0x1D (Undefined)",
  150. "0x1E (Undefined)",
  151. "0x1F (Undefined)",
  152. "0x46 Control 1 [Variation]",
  153. "0x47 Control 2 [Timbre]",
  154. "0x48 Control 3 [Release]",
  155. "0x49 Control 4 [Attack]",
  156. "0x4A Control 5 [Brightness]",
  157. "0x4B Control 6 [Decay]",
  158. "0x4C Control 7 [Vib Rate]",
  159. "0x4D Control 8 [Vib Depth]",
  160. "0x4E Control 9 [Vib Delay]",
  161. "0x4F Control 10 [Undefined]",
  162. "0x50 General Purpose 5",
  163. "0x51 General Purpose 6",
  164. "0x52 General Purpose 7",
  165. "0x53 General Purpose 8",
  166. "0x54 Portamento Control",
  167. "0x5B FX 1 Depth [Reverb]",
  168. "0x5C FX 2 Depth [Tremolo]",
  169. "0x5D FX 3 Depth [Chorus]",
  170. "0x5E FX 4 Depth [Detune]",
  171. "0x5F FX 5 Depth [Phaser]"
  172. )
  173. # ------------------------------------------------------------------------------------------------------------
  174. # Default Plugin Folders
  175. if WINDOWS:
  176. splitter = ";"
  177. APPDATA = os.getenv("APPDATA")
  178. PROGRAMFILES = os.getenv("PROGRAMFILES")
  179. PROGRAMFILESx86 = os.getenv("PROGRAMFILES(x86)")
  180. COMMONPROGRAMFILES = os.getenv("COMMONPROGRAMFILES")
  181. # Small integrity tests
  182. if not APPDATA:
  183. qFatal("APPDATA variable not set, cannot continue")
  184. sys.exit(1)
  185. if not PROGRAMFILES:
  186. qFatal("PROGRAMFILES variable not set, cannot continue")
  187. sys.exit(1)
  188. if not COMMONPROGRAMFILES:
  189. qFatal("COMMONPROGRAMFILES variable not set, cannot continue")
  190. sys.exit(1)
  191. DEFAULT_LADSPA_PATH = ";".join((os.path.join(APPDATA, "LADSPA"),
  192. os.path.join(PROGRAMFILES, "LADSPA")))
  193. DEFAULT_DSSI_PATH = ";".join((os.path.join(APPDATA, "DSSI"),
  194. os.path.join(PROGRAMFILES, "DSSI")))
  195. DEFAULT_LV2_PATH = ";".join((os.path.join(APPDATA, "LV2"),
  196. os.path.join(COMMONPROGRAMFILES, "LV2")))
  197. DEFAULT_VST_PATH = ";".join((os.path.join(PROGRAMFILES, "VstPlugins"),
  198. os.path.join(PROGRAMFILES, "Steinberg", "VstPlugins")))
  199. DEFAULT_AU_PATH = ""
  200. # TODO
  201. DEFAULT_CSOUND_PATH = ""
  202. DEFAULT_GIG_PATH = ";".join((os.path.join(APPDATA, "GIG"),))
  203. DEFAULT_SF2_PATH = ";".join((os.path.join(APPDATA, "SF2"),))
  204. DEFAULT_SFZ_PATH = ";".join((os.path.join(APPDATA, "SFZ"),))
  205. if PROGRAMFILESx86:
  206. DEFAULT_LADSPA_PATH += ";"+os.path.join(PROGRAMFILESx86, "LADSPA")
  207. DEFAULT_DSSI_PATH += ";"+os.path.join(PROGRAMFILESx86, "DSSI")
  208. DEFAULT_VST_PATH += ";"+os.path.join(PROGRAMFILESx86, "VstPlugins")
  209. DEFAULT_VST_PATH += ";"+os.path.join(PROGRAMFILESx86, "Steinberg", "VstPlugins")
  210. elif HAIKU:
  211. splitter = ":"
  212. DEFAULT_LADSPA_PATH = ":".join((os.path.join(HOME, ".ladspa"),
  213. os.path.join("/", "boot", "common", "add-ons", "ladspa")))
  214. DEFAULT_DSSI_PATH = ":".join((os.path.join(HOME, ".dssi"),
  215. os.path.join("/", "boot", "common", "add-ons", "dssi")))
  216. DEFAULT_LV2_PATH = ":".join((os.path.join(HOME, ".lv2"),
  217. os.path.join("/", "boot", "common", "add-ons", "lv2")))
  218. DEFAULT_VST_PATH = ":".join((os.path.join(HOME, ".vst"),
  219. os.path.join("/", "boot", "common", "add-ons", "vst")))
  220. DEFAULT_AU_PATH = ""
  221. # TODO
  222. DEFAULT_CSOUND_PATH = ""
  223. # TODO
  224. DEFAULT_GIG_PATH = ""
  225. DEFAULT_SF2_PATH = ""
  226. DEFAULT_SFZ_PATH = ""
  227. elif MACOS:
  228. splitter = ":"
  229. DEFAULT_LADSPA_PATH = ":".join((os.path.join(HOME, "Library", "Audio", "Plug-Ins", "LADSPA"),
  230. os.path.join("/", "Library", "Audio", "Plug-Ins", "LADSPA")))
  231. DEFAULT_DSSI_PATH = ":".join((os.path.join(HOME, "Library", "Audio", "Plug-Ins", "DSSI"),
  232. os.path.join("/", "Library", "Audio", "Plug-Ins", "DSSI")))
  233. DEFAULT_LV2_PATH = ":".join((os.path.join(HOME, "Library", "Audio", "Plug-Ins", "LV2"),
  234. os.path.join("/", "Library", "Audio", "Plug-Ins", "LV2")))
  235. DEFAULT_VST_PATH = ":".join((os.path.join(HOME, "Library", "Audio", "Plug-Ins", "VST"),
  236. os.path.join("/", "Library", "Audio", "Plug-Ins", "VST")))
  237. DEFAULT_AU_PATH = ":".join((os.path.join(HOME, "Library", "Audio", "Plug-Ins", "AudioUnits"),
  238. os.path.join("/", "Library", "Audio", "Plug-Ins", "AudioUnits")))
  239. # TODO
  240. DEFAULT_CSOUND_PATH = ""
  241. # TODO
  242. DEFAULT_GIG_PATH = ""
  243. DEFAULT_SF2_PATH = ""
  244. DEFAULT_SFZ_PATH = ""
  245. else:
  246. splitter = ":"
  247. DEFAULT_LADSPA_PATH = ":".join((os.path.join(HOME, ".ladspa"),
  248. os.path.join("/", "usr", "lib", "ladspa"),
  249. os.path.join("/", "usr", "local", "lib", "ladspa")))
  250. DEFAULT_DSSI_PATH = ":".join((os.path.join(HOME, ".dssi"),
  251. os.path.join("/", "usr", "lib", "dssi"),
  252. os.path.join("/", "usr", "local", "lib", "dssi")))
  253. DEFAULT_LV2_PATH = ":".join((os.path.join(HOME, ".lv2"),
  254. os.path.join("/", "usr", "lib", "lv2"),
  255. os.path.join("/", "usr", "local", "lib", "lv2")))
  256. DEFAULT_VST_PATH = ":".join((os.path.join(HOME, ".vst"),
  257. os.path.join("/", "usr", "lib", "vst"),
  258. os.path.join("/", "usr", "local", "lib", "vst")))
  259. DEFAULT_AU_PATH = ""
  260. # TODO
  261. DEFAULT_CSOUND_PATH = ""
  262. DEFAULT_GIG_PATH = ":".join((os.path.join(HOME, ".sounds"),
  263. os.path.join("/", "usr", "share", "sounds", "gig")))
  264. DEFAULT_SF2_PATH = ":".join((os.path.join(HOME, ".sounds"),
  265. os.path.join("/", "usr", "share", "sounds", "sf2")))
  266. DEFAULT_SFZ_PATH = ":".join((os.path.join(HOME, ".sounds"),
  267. os.path.join("/", "usr", "share", "sounds", "sfz")))
  268. # ------------------------------------------------------------------------------------------------------------
  269. # Default Plugin Folders (set)
  270. readEnvVars = True
  271. if WINDOWS:
  272. # Check if running Wine. If yes, ignore env vars
  273. from winreg import ConnectRegistry, OpenKey, CloseKey, HKEY_CURRENT_USER
  274. reg = ConnectRegistry(None, HKEY_CURRENT_USER)
  275. try:
  276. key = OpenKey(reg, r"SOFTWARE\Wine")
  277. CloseKey(key)
  278. readEnvVars = False
  279. except:
  280. pass
  281. CloseKey(reg)
  282. del reg
  283. if readEnvVars:
  284. Carla.LADSPA_PATH = os.getenv("LADSPA_PATH", DEFAULT_LADSPA_PATH).split(splitter)
  285. Carla.DSSI_PATH = os.getenv("DSSI_PATH", DEFAULT_DSSI_PATH).split(splitter)
  286. Carla.LV2_PATH = os.getenv("LV2_PATH", DEFAULT_LV2_PATH).split(splitter)
  287. Carla.VST_PATH = os.getenv("VST_PATH", DEFAULT_VST_PATH).split(splitter)
  288. Carla.AU_PATH = os.getenv("AU_PATH", DEFAULT_AU_PATH).split(splitter)
  289. Carla.CSOUND_PATH = os.getenv("CSOUND_PATH", DEFAULT_CSOUND_PATH).split(splitter)
  290. Carla.GIG_PATH = os.getenv("GIG_PATH", DEFAULT_GIG_PATH).split(splitter)
  291. Carla.SF2_PATH = os.getenv("SF2_PATH", DEFAULT_SF2_PATH).split(splitter)
  292. Carla.SFZ_PATH = os.getenv("SFZ_PATH", DEFAULT_SFZ_PATH).split(splitter)
  293. if haveLRDF:
  294. LADSPA_RDF_PATH_env = os.getenv("LADSPA_RDF_PATH")
  295. if LADSPA_RDF_PATH_env:
  296. ladspa_rdf.set_rdf_path(LADSPA_RDF_PATH_env.split(splitter))
  297. del LADSPA_RDF_PATH_env
  298. else:
  299. Carla.LADSPA_PATH = DEFAULT_LADSPA_PATH.split(splitter)
  300. Carla.DSSI_PATH = DEFAULT_DSSI_PATH.split(splitter)
  301. Carla.LV2_PATH = DEFAULT_LV2_PATH.split(splitter)
  302. Carla.VST_PATH = DEFAULT_VST_PATH.split(splitter)
  303. Carla.AU_PATH = DEFAULT_AU_PATH.split(splitter)
  304. Carla.CSOUND_PATH = DEFAULT_CSOUND_PATH.split(splitter)
  305. Carla.GIG_PATH = DEFAULT_GIG_PATH.split(splitter)
  306. Carla.SF2_PATH = DEFAULT_SF2_PATH.split(splitter)
  307. Carla.SFZ_PATH = DEFAULT_SFZ_PATH.split(splitter)
  308. del readEnvVars
  309. # ------------------------------------------------------------------------------------------------------------
  310. # Search for Carla library and tools
  311. carla_library_filename = ""
  312. carla_discovery_native = ""
  313. carla_discovery_posix32 = ""
  314. carla_discovery_posix64 = ""
  315. carla_discovery_win32 = ""
  316. carla_discovery_win64 = ""
  317. carla_bridge_native = ""
  318. carla_bridge_posix32 = ""
  319. carla_bridge_posix64 = ""
  320. carla_bridge_win32 = ""
  321. carla_bridge_win64 = ""
  322. carla_bridge_lv2_external = ""
  323. carla_bridge_lv2_gtk2 = ""
  324. carla_bridge_lv2_gtk3 = ""
  325. carla_bridge_lv2_qt4 = ""
  326. carla_bridge_lv2_qt5 = ""
  327. carla_bridge_lv2_cocoa = ""
  328. carla_bridge_lv2_windows = ""
  329. carla_bridge_lv2_x11 = ""
  330. carla_bridge_vst_mac = ""
  331. carla_bridge_vst_hwnd = ""
  332. carla_bridge_vst_x11 = ""
  333. carla_libname = "libcarla_%s" % "control" if Carla.isControl else "standalone"
  334. if WINDOWS:
  335. carla_libname += ".dll"
  336. elif MACOS:
  337. carla_libname += ".dylib"
  338. else:
  339. carla_libname += ".so"
  340. CWD = sys.path[0]
  341. # make it work with cxfreeze
  342. if CWD.endswith("/carla"):
  343. CWD = CWD.rsplit("/carla", 1)[0]
  344. elif CWD.endswith("\\carla.exe"):
  345. CWD = CWD.rsplit("\\carla.exe", 1)[0]
  346. # find carla_library_filename
  347. if os.path.exists(os.path.join(CWD, "backend", carla_libname)):
  348. carla_library_filename = os.path.join(CWD, "backend", carla_libname)
  349. else:
  350. if WINDOWS:
  351. CARLA_PATH = (os.path.join(PROGRAMFILES, "Carla"),)
  352. elif MACOS:
  353. CARLA_PATH = ("/opt/local/lib", "/usr/local/lib/", "/usr/lib")
  354. else:
  355. CARLA_PATH = ("/usr/local/lib/", "/usr/lib")
  356. for path in CARLA_PATH:
  357. if os.path.exists(os.path.join(path, "carla", carla_libname)):
  358. carla_library_filename = os.path.join(path, "carla", carla_libname)
  359. break
  360. del CARLA_PATH
  361. # find tool
  362. def findTool(toolDir, toolName):
  363. if os.path.exists(os.path.join(CWD, toolDir, toolName)):
  364. return os.path.join(CWD, toolDir, toolName)
  365. for p in PATH:
  366. if os.path.exists(os.path.join(p, toolName)):
  367. return os.path.join(p, toolName)
  368. return ""
  369. # find windows tools
  370. carla_discovery_win32 = findTool("discovery", "carla-discovery-win32.exe")
  371. carla_discovery_win64 = findTool("discovery", "carla-discovery-win64.exe")
  372. carla_bridge_win32 = findTool("bridges", "carla-bridge-win32.exe")
  373. carla_bridge_win64 = findTool("bridges", "carla-bridge-win64.exe")
  374. # find native and posix tools
  375. if not WINDOWS:
  376. carla_discovery_native = findTool("discovery", "carla-discovery-native")
  377. carla_discovery_posix32 = findTool("discovery", "carla-discovery-posix32")
  378. carla_discovery_posix64 = findTool("discovery", "carla-discovery-posix64")
  379. carla_bridge_native = findTool("bridges", "carla-bridge-native")
  380. carla_bridge_posix32 = findTool("bridges", "carla-bridge-posix32")
  381. carla_bridge_posix64 = findTool("bridges", "carla-bridge-posix64")
  382. # find generic tools
  383. carla_bridge_lv2_external = findTool("bridges", "carla-bridge-lv2-external")
  384. # find windows only tools
  385. if WINDOWS:
  386. carla_bridge_lv2_windows = findTool("bridges", "carla-bridge-lv2-windows.exe")
  387. carla_bridge_vst_hwnd = findTool("bridges", "carla-bridge-vst-hwnd.exe")
  388. # find mac os only tools
  389. elif MACOS:
  390. carla_bridge_lv2_cocoa = findTool("bridges", "carla-bridge-lv2-cocoa")
  391. carla_bridge_vst_mac = findTool("bridges", "carla-bridge-vst-mac")
  392. # find other tools
  393. else:
  394. carla_bridge_lv2_gtk2 = findTool("bridges", "carla-bridge-lv2-gtk2")
  395. carla_bridge_lv2_gtk3 = findTool("bridges", "carla-bridge-lv2-gtk3")
  396. carla_bridge_lv2_qt4 = findTool("bridges", "carla-bridge-lv2-qt4")
  397. carla_bridge_lv2_qt5 = findTool("bridges", "carla-bridge-lv2-qt5")
  398. carla_bridge_lv2_x11 = findTool("bridges", "carla-bridge-lv2-x11")
  399. carla_bridge_vst_x11 = findTool("bridges", "carla-bridge-vst-x11")
  400. # ------------------------------------------------------------------------------------------------------------
  401. # Convert a ctypes c_char_p into a python string
  402. def cString(value):
  403. if not value:
  404. return ""
  405. if isinstance(value, str):
  406. return value
  407. return value.decode("utf-8", errors="ignore")
  408. # ------------------------------------------------------------------------------------------------------------
  409. # Check if a value is a number (float support)
  410. def isNumber(value):
  411. try:
  412. float(value)
  413. return True
  414. except:
  415. return False
  416. # ------------------------------------------------------------------------------------------------------------
  417. # Convert a value to a list
  418. def toList(value):
  419. if value is None:
  420. return []
  421. elif not isinstance(value, list):
  422. return [value]
  423. else:
  424. return value
  425. # ------------------------------------------------------------------------------------------------------------
  426. # Get Icon from user theme, using our own as backup (Oxygen)
  427. def getIcon(icon, size=16):
  428. return QIcon.fromTheme(icon, QIcon(":/%ix%i/%s.png" % (size, size, icon)))
  429. # ------------------------------------------------------------------------------------------------------------
  430. # Signal handler
  431. def signalHandler(sig, frame):
  432. if Carla.gui is None:
  433. return
  434. if sig in (SIGINT, SIGTERM):
  435. Carla.gui.SIGTERM.emit()
  436. elif haveSIGUSR1 and sig == SIGUSR1:
  437. Carla.gui.SIGUSR1.emit()
  438. def setUpSignals():
  439. signal(SIGINT, signalHandler)
  440. signal(SIGTERM, signalHandler)
  441. if haveSIGUSR1:
  442. return
  443. signal(SIGUSR1, signalHandler)
  444. # ------------------------------------------------------------------------------------------------------------
  445. # QLineEdit and QPushButton combo
  446. def getAndSetPath(self_, currentPath, lineEdit):
  447. newPath = QFileDialog.getExistingDirectory(self_, self_.tr("Set Path"), currentPath, QFileDialog.ShowDirsOnly)
  448. if newPath:
  449. lineEdit.setText(newPath)
  450. return newPath
  451. # ------------------------------------------------------------------------------------------------------------
  452. # Custom MessageBox
  453. def CustomMessageBox(self_, icon, title, text, extraText="", buttons=QMessageBox.Yes|QMessageBox.No, defButton=QMessageBox.No):
  454. msgBox = QMessageBox(self_)
  455. msgBox.setIcon(icon)
  456. msgBox.setWindowTitle(title)
  457. msgBox.setText(text)
  458. msgBox.setInformativeText(extraText)
  459. msgBox.setStandardButtons(buttons)
  460. msgBox.setDefaultButton(defButton)
  461. return msgBox.exec_()