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.

918 lines
31KB

  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 GPL.txt file
  17. # ------------------------------------------------------------------------------------------------------------
  18. # Imports (Global)
  19. import os
  20. import platform
  21. import sys
  22. from codecs import open as codecopen
  23. from copy import deepcopy
  24. #from decimal import Decimal
  25. from PyQt4.QtCore import pyqtSlot, qWarning, Qt, QSettings, QTimer, SIGNAL, SLOT
  26. #pyqtSlot, qFatal,
  27. from PyQt4.QtGui import QDialog, QIcon, QMessageBox, QWidget
  28. #from PyQt4.QtGui import QColor, QCursor, QFontMetrics, QFrame, QGraphicsScene, QInputDialog, QLinearGradient, QMenu, QPainter, QPainterPath, QVBoxLayout
  29. #from PyQt4.QtXml import QDomDocument
  30. # ------------------------------------------------------------------------------------------------------------
  31. # Imports (Custom)
  32. import ui_carla_about
  33. #import ui_carla_edit
  34. import ui_carla_parameter
  35. #import ui_carla_plugin
  36. # ------------------------------------------------------------------------------------------------------------
  37. # Try Import Signal
  38. try:
  39. from signal import signal, SIGINT, SIGTERM, SIGUSR1, SIGUSR2
  40. haveSignal = True
  41. except:
  42. haveSignal = False
  43. # ------------------------------------------------------------------------------------------------------------
  44. # Set Platform
  45. if sys.platform == "darwin":
  46. from PyQt4.QtGui import qt_mac_set_menubar_icons
  47. qt_mac_set_menubar_icons(False)
  48. HAIKU = False
  49. LINUX = False
  50. MACOS = True
  51. WINDOWS = False
  52. elif "haiku" in sys.platform:
  53. HAIKU = True
  54. LINUX = False
  55. MACOS = False
  56. WINDOWS = False
  57. elif "linux" in sys.platform:
  58. HAIKU = False
  59. LINUX = True
  60. MACOS = False
  61. WINDOWS = False
  62. elif sys.platform in ("win32", "win64", "cygwin"):
  63. WINDIR = os.getenv("WINDIR")
  64. HAIKU = False
  65. LINUX = False
  66. MACOS = False
  67. WINDOWS = True
  68. else:
  69. HAIKU = False
  70. LINUX = False
  71. MACOS = False
  72. WINDOWS = False
  73. # ------------------------------------------------------------------------------------------------------------
  74. # Set Version
  75. VERSION = "0.5.0"
  76. # ------------------------------------------------------------------------------------------------------------
  77. # Set TMP
  78. TMP = os.getenv("TMP")
  79. if TMP is None:
  80. if WINDOWS:
  81. qWarning("TMP variable not set")
  82. TMP = os.path.join(WINDIR, "temp")
  83. else:
  84. TMP = "/tmp"
  85. # ------------------------------------------------------------------------------------------------------------
  86. # Set HOME
  87. HOME = os.getenv("HOME")
  88. if HOME is None:
  89. HOME = os.path.expanduser("~")
  90. if LINUX or MACOS:
  91. qWarning("HOME variable not set")
  92. if not os.path.exists(HOME):
  93. qWarning("HOME does not exist")
  94. HOME = TMP
  95. # ------------------------------------------------------------------------------------------------------------
  96. # Set PATH
  97. PATH = os.getenv("PATH")
  98. if PATH is None:
  99. qWarning("PATH variable not set")
  100. if MACOS:
  101. PATH = ("/opt/local/bin", "/usr/local/bin", "/usr/bin", "/bin")
  102. elif WINDOWS:
  103. PATH = (os.path.join(WINDIR, "system32"), WINDIR)
  104. else:
  105. PATH = ("/usr/local/bin", "/usr/bin", "/bin")
  106. else:
  107. PATH = PATH.split(os.pathsep)
  108. # ------------------------------------------------------------------------------------------------------------
  109. # 64bit check
  110. kIs64bit = bool(platform.architecture()[0] == "64bit" and sys.maxsize > 2**32)
  111. # ------------------------------------------------------------------------------------------------------------
  112. # Convert a ctypes c_char_p into a python string
  113. def cString(value):
  114. if not value:
  115. return ""
  116. if isinstance(value, str):
  117. return value
  118. return value.decode("utf-8", errors="ignore")
  119. # ------------------------------------------------------------------------------------------------------------
  120. # Check if a value is a number (float support)
  121. def isNumber(value):
  122. try:
  123. float(value)
  124. return True
  125. except:
  126. return False
  127. # ------------------------------------------------------------------------------------------------------------
  128. # Convert a value to a list
  129. def toList(value):
  130. if value is None:
  131. return []
  132. elif not isinstance(value, list):
  133. return [value]
  134. else:
  135. return value
  136. # ------------------------------------------------------------------------------------------------------------
  137. # Unicode open
  138. def uopen(filename, mode="r"):
  139. return codecopen(filename, encoding="utf-8", mode=mode)
  140. # ------------------------------------------------------------------------------------------------------------
  141. # Get Icon from user theme, using our own as backup (Oxygen)
  142. def getIcon(icon, size=16):
  143. return QIcon.fromTheme(icon, QIcon(":/%ix%i/%s.png" % (size, size, icon)))
  144. # ------------------------------------------------------------------------------------------------------------
  145. # Custom MessageBox
  146. def CustomMessageBox(self_, icon, title, text, extraText="", buttons=QMessageBox.Yes|QMessageBox.No, defButton=QMessageBox.No):
  147. msgBox = QMessageBox(self_)
  148. msgBox.setIcon(icon)
  149. msgBox.setWindowTitle(title)
  150. msgBox.setText(text)
  151. msgBox.setInformativeText(extraText)
  152. msgBox.setStandardButtons(buttons)
  153. msgBox.setDefaultButton(defButton)
  154. return msgBox.exec_()
  155. # ------------------------------------------------------------------------------------------------
  156. # Backend defines
  157. MAX_DEFAULT_PLUGINS = 99
  158. MAX_RACK_PLUGINS = 16
  159. MAX_PATCHBAY_PLUGINS = 999
  160. MAX_DEFAULT_PARAMETERS = 200
  161. # Plugin Hints
  162. PLUGIN_IS_BRIDGE = 0x001
  163. PLUGIN_IS_RTSAFE = 0x002
  164. PLUGIN_IS_SYNTH = 0x004
  165. PLUGIN_HAS_GUI = 0x010
  166. PLUGIN_USES_CHUNKS = 0x020
  167. PLUGIN_USES_SINGLE_THREAD = 0x040
  168. PLUGIN_CAN_DRYWET = 0x100
  169. PLUGIN_CAN_VOLUME = 0x200
  170. PLUGIN_CAN_BALANCE = 0x400
  171. PLUGIN_CAN_FORCE_STEREO = 0x800
  172. # Plugin Options
  173. PLUGIN_OPTION_FIXED_BUFFER = 0x001
  174. PLUGIN_OPTION_FORCE_STEREO = 0x002
  175. PLUGIN_OPTION_SELF_AUTOMATION = 0x004
  176. PLUGIN_OPTION_USE_CHUNKS = 0x008
  177. PLUGIN_OPTION_SEND_ALL_SOUND_OFF = 0x010
  178. PLUGIN_OPTION_SEND_NOTE_OFF_VELO = 0x020
  179. PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH = 0x040
  180. PLUGIN_OPTION_SEND_PITCHBEND = 0x080
  181. PLUGIN_OPTION_VST_SUPPLY_IDLE = 0x100
  182. PLUGIN_OPTION_VST_UPDATE_DISPLAY = 0x200
  183. # Parameter Hints
  184. PARAMETER_IS_BOOLEAN = 0x01
  185. PARAMETER_IS_INTEGER = 0x02
  186. PARAMETER_IS_LOGARITHMIC = 0x04
  187. PARAMETER_IS_ENABLED = 0x08
  188. PARAMETER_IS_AUTOMABLE = 0x10
  189. PARAMETER_USES_SAMPLERATE = 0x20
  190. PARAMETER_USES_SCALEPOINTS = 0x40
  191. PARAMETER_USES_CUSTOM_TEXT = 0x80
  192. # FIXME
  193. # Custom Data types
  194. #CUSTOM_DATA_INVALID = None
  195. #CUSTOM_DATA_CHUNK = "http://kxstudio.sf.net/ns/carla/chunk"
  196. #CUSTOM_DATA_STRING = "http://kxstudio.sf.net/ns/carla/string"
  197. # Binary Type
  198. BINARY_NONE = 0
  199. BINARY_POSIX32 = 1
  200. BINARY_POSIX64 = 2
  201. BINARY_WIN32 = 3
  202. BINARY_WIN64 = 4
  203. BINARY_OTHER = 5
  204. # Plugin Type
  205. PLUGIN_NONE = 0
  206. PLUGIN_INTERNAL = 1
  207. PLUGIN_LADSPA = 2
  208. PLUGIN_DSSI = 3
  209. PLUGIN_LV2 = 4
  210. PLUGIN_VST = 5
  211. PLUGIN_GIG = 6
  212. PLUGIN_SF2 = 7
  213. PLUGIN_SFZ = 8
  214. # Plugin Category
  215. PLUGIN_CATEGORY_NONE = 0
  216. PLUGIN_CATEGORY_SYNTH = 1
  217. PLUGIN_CATEGORY_DELAY = 2 # also Reverb
  218. PLUGIN_CATEGORY_EQ = 3
  219. PLUGIN_CATEGORY_FILTER = 4
  220. PLUGIN_CATEGORY_DYNAMICS = 5 # Amplifier, Compressor, Gate
  221. PLUGIN_CATEGORY_MODULATOR = 6 # Chorus, Flanger, Phaser
  222. PLUGIN_CATEGORY_UTILITY = 7 # Analyzer, Converter, Mixer
  223. PLUGIN_CATEGORY_OTHER = 8 # used to check if a plugin has a category
  224. # Parameter Type
  225. PARAMETER_UNKNOWN = 0
  226. PARAMETER_INPUT = 1
  227. PARAMETER_OUTPUT = 2
  228. PARAMETER_LATENCY = 3
  229. PARAMETER_SAMPLE_RATE = 4
  230. PARAMETER_LV2_FREEWHEEL = 5
  231. PARAMETER_LV2_TIME = 6
  232. # Internal Parameters Index
  233. PARAMETER_NULL = -1
  234. PARAMETER_ACTIVE = -2
  235. PARAMETER_DRYWET = -3
  236. PARAMETER_VOLUME = -4
  237. PARAMETER_BALANCE_LEFT = -5
  238. PARAMETER_BALANCE_RIGHT = -6
  239. PARAMETER_PANNING = -7
  240. PARAMETER_MAX = -8
  241. # Options Type
  242. OPTION_PROCESS_NAME = 0
  243. OPTION_PROCESS_MODE = 1
  244. OPTION_FORCE_STEREO = 2
  245. OPTION_PREFER_PLUGIN_BRIDGES = 3
  246. OPTION_PREFER_UI_BRIDGES = 4
  247. OPTION_USE_DSSI_VST_CHUNKS = 5
  248. OPTION_MAX_PARAMETERS = 6
  249. OPTION_OSC_UI_TIMEOUT = 7
  250. OPTION_PREFERRED_BUFFER_SIZE = 8
  251. OPTION_PREFERRED_SAMPLE_RATE = 9
  252. OPTION_PATH_BRIDGE_NATIVE = 10
  253. OPTION_PATH_BRIDGE_POSIX32 = 11
  254. OPTION_PATH_BRIDGE_POSIX64 = 12
  255. OPTION_PATH_BRIDGE_WIN32 = 13
  256. OPTION_PATH_BRIDGE_WIN64 = 14
  257. OPTION_PATH_BRIDGE_LV2_GTK2 = 15
  258. OPTION_PATH_BRIDGE_LV2_GTK3 = 16
  259. OPTION_PATH_BRIDGE_LV2_QT4 = 17
  260. OPTION_PATH_BRIDGE_LV2_QT5 = 18
  261. OPTION_PATH_BRIDGE_LV2_COCOA = 19
  262. OPTION_PATH_BRIDGE_LV2_WINDOWS = 20
  263. OPTION_PATH_BRIDGE_LV2_X11 = 21
  264. OPTION_PATH_BRIDGE_VST_COCOA = 22
  265. OPTION_PATH_BRIDGE_VST_HWND = 23
  266. OPTION_PATH_BRIDGE_VST_X11 = 24
  267. # Callback Type
  268. CALLBACK_DEBUG = 0
  269. CALLBACK_PARAMETER_VALUE_CHANGED = 1
  270. CALLBACK_PARAMETER_MIDI_CHANNEL_CHANGED = 2
  271. CALLBACK_PARAMETER_MIDI_CC_CHANGED = 3
  272. CALLBACK_PROGRAM_CHANGED = 4
  273. CALLBACK_MIDI_PROGRAM_CHANGED = 5
  274. CALLBACK_NOTE_ON = 6
  275. CALLBACK_NOTE_OFF = 7
  276. CALLBACK_SHOW_GUI = 8
  277. CALLBACK_UPDATE = 9
  278. CALLBACK_RELOAD_INFO = 10
  279. CALLBACK_RELOAD_PARAMETERS = 11
  280. CALLBACK_RELOAD_PROGRAMS = 12
  281. CALLBACK_RELOAD_ALL = 13
  282. CALLBACK_NSM_ANNOUNCE = 14
  283. CALLBACK_NSM_OPEN1 = 15
  284. CALLBACK_NSM_OPEN2 = 16
  285. CALLBACK_NSM_SAVE = 17
  286. CALLBACK_ERROR = 18
  287. CALLBACK_QUIT = 19
  288. # Process Mode Type
  289. PROCESS_MODE_SINGLE_CLIENT = 0
  290. PROCESS_MODE_MULTIPLE_CLIENTS = 1
  291. PROCESS_MODE_CONTINUOUS_RACK = 2
  292. PROCESS_MODE_PATCHBAY = 3
  293. # Set BINARY_NATIVE
  294. if HAIKU or LINUX or MACOS:
  295. BINARY_NATIVE = BINARY_POSIX64 if kIs64bit else BINARY_POSIX32
  296. elif WINDOWS:
  297. BINARY_NATIVE = BINARY_WIN64 if kIs64bit else BINARY_WIN32
  298. else:
  299. BINARY_NATIVE = BINARY_OTHER
  300. # ------------------------------------------------------------------------------------------------------------
  301. # Carla Host object
  302. class CarlaHostObject(object):
  303. __slots__ = [
  304. 'host',
  305. 'gui',
  306. 'isControl',
  307. 'processMode',
  308. 'maxParameters'
  309. ]
  310. Carla = CarlaHostObject()
  311. Carla.host = None
  312. Carla.gui = None
  313. Carla.isControl = False
  314. Carla.processMode = PROCESS_MODE_CONTINUOUS_RACK
  315. Carla.maxParameters = MAX_RACK_PLUGINS
  316. # ------------------------------------------------------------------------------------------------------------
  317. # Carla GUI stuff
  318. ICON_STATE_NULL = 0
  319. ICON_STATE_WAIT = 1
  320. ICON_STATE_OFF = 2
  321. ICON_STATE_ON = 3
  322. PALETTE_COLOR_NONE = 0
  323. PALETTE_COLOR_WHITE = 1
  324. PALETTE_COLOR_RED = 2
  325. PALETTE_COLOR_GREEN = 3
  326. PALETTE_COLOR_BLUE = 4
  327. PALETTE_COLOR_YELLOW = 5
  328. PALETTE_COLOR_ORANGE = 6
  329. PALETTE_COLOR_BROWN = 7
  330. PALETTE_COLOR_PINK = 8
  331. CarlaStateParameter = {
  332. 'index': 0,
  333. 'name': "",
  334. 'symbol': "",
  335. 'value': 0.0,
  336. 'midiChannel': 1,
  337. 'midiCC': -1
  338. }
  339. CarlaStateCustomData = {
  340. 'type': None, #CUSTOM_DATA_INVALID,
  341. 'key': "",
  342. 'value': ""
  343. }
  344. CarlaSaveState = {
  345. 'type': "",
  346. 'name': "",
  347. 'label': "",
  348. 'binary': "",
  349. 'uniqueId': 0,
  350. 'active': False,
  351. 'dryWet': 1.0,
  352. 'volume': 1.0,
  353. 'balanceLeft': -1.0,
  354. 'balanceRight': 1.0,
  355. 'pannning': 0.0,
  356. 'parameterList': [],
  357. 'currentProgramIndex': -1,
  358. 'currentProgramName': "",
  359. 'currentMidiBank': -1,
  360. 'currentMidiProgram': -1,
  361. 'customDataList': [],
  362. 'chunk': None
  363. }
  364. # ------------------------------------------------------------------------------------------------------------
  365. # Static MIDI CC list
  366. MIDI_CC_LIST = (
  367. #"0x00 Bank Select",
  368. "0x01 Modulation",
  369. "0x02 Breath",
  370. "0x03 (Undefined)",
  371. "0x04 Foot",
  372. "0x05 Portamento",
  373. #"0x06 (Data Entry MSB)",
  374. "0x07 Volume",
  375. "0x08 Balance",
  376. "0x09 (Undefined)",
  377. "0x0A Pan",
  378. "0x0B Expression",
  379. "0x0C FX Control 1",
  380. "0x0D FX Control 2",
  381. "0x0E (Undefined)",
  382. "0x0F (Undefined)",
  383. "0x10 General Purpose 1",
  384. "0x11 General Purpose 2",
  385. "0x12 General Purpose 3",
  386. "0x13 General Purpose 4",
  387. "0x14 (Undefined)",
  388. "0x15 (Undefined)",
  389. "0x16 (Undefined)",
  390. "0x17 (Undefined)",
  391. "0x18 (Undefined)",
  392. "0x19 (Undefined)",
  393. "0x1A (Undefined)",
  394. "0x1B (Undefined)",
  395. "0x1C (Undefined)",
  396. "0x1D (Undefined)",
  397. "0x1E (Undefined)",
  398. "0x1F (Undefined)",
  399. #"0x20 *Bank Select",
  400. #"0x21 *Modulation",
  401. #"0x22 *Breath",
  402. #"0x23 *(Undefined)",
  403. #"0x24 *Foot",
  404. #"0x25 *Portamento",
  405. #"0x26 *(Data Entry MSB)",
  406. #"0x27 *Volume",
  407. #"0x28 *Balance",
  408. #"0x29 *(Undefined)",
  409. #"0x2A *Pan",
  410. #"0x2B *Expression",
  411. #"0x2C *FX *Control 1",
  412. #"0x2D *FX *Control 2",
  413. #"0x2E *(Undefined)",
  414. #"0x2F *(Undefined)",
  415. #"0x30 *General Purpose 1",
  416. #"0x31 *General Purpose 2",
  417. #"0x32 *General Purpose 3",
  418. #"0x33 *General Purpose 4",
  419. #"0x34 *(Undefined)",
  420. #"0x35 *(Undefined)",
  421. #"0x36 *(Undefined)",
  422. #"0x37 *(Undefined)",
  423. #"0x38 *(Undefined)",
  424. #"0x39 *(Undefined)",
  425. #"0x3A *(Undefined)",
  426. #"0x3B *(Undefined)",
  427. #"0x3C *(Undefined)",
  428. #"0x3D *(Undefined)",
  429. #"0x3E *(Undefined)",
  430. #"0x3F *(Undefined)",
  431. #"0x40 Damper On/Off", # <63 off, >64 on
  432. #"0x41 Portamento On/Off", # <63 off, >64 on
  433. #"0x42 Sostenuto On/Off", # <63 off, >64 on
  434. #"0x43 Soft Pedal On/Off", # <63 off, >64 on
  435. #"0x44 Legato Footswitch", # <63 Normal, >64 Legato
  436. #"0x45 Hold 2", # <63 off, >64 on
  437. "0x46 Control 1 [Variation]",
  438. "0x47 Control 2 [Timbre]",
  439. "0x48 Control 3 [Release]",
  440. "0x49 Control 4 [Attack]",
  441. "0x4A Control 5 [Brightness]",
  442. "0x4B Control 6 [Decay]",
  443. "0x4C Control 7 [Vib Rate]",
  444. "0x4D Control 8 [Vib Depth]",
  445. "0x4E Control 9 [Vib Delay]",
  446. "0x4F Control 10 [Undefined]",
  447. "0x50 General Purpose 5",
  448. "0x51 General Purpose 6",
  449. "0x52 General Purpose 7",
  450. "0x53 General Purpose 8",
  451. "0x54 Portamento Control",
  452. "0x5B FX 1 Depth [Reverb]",
  453. "0x5C FX 2 Depth [Tremolo]",
  454. "0x5D FX 3 Depth [Chorus]",
  455. "0x5E FX 4 Depth [Detune]",
  456. "0x5F FX 5 Depth [Phaser]"
  457. )
  458. # ------------------------------------------------------------------------------------------------------------
  459. # Carla XML helpers
  460. def getSaveStateDictFromXML(xmlNode):
  461. saveState = deepcopy(CarlaSaveState)
  462. node = xmlNode.firstChild()
  463. while not node.isNull():
  464. # ------------------------------------------------------
  465. # Info
  466. if node.toElement().tagName() == "Info":
  467. xmlInfo = node.toElement().firstChild()
  468. while not xmlInfo.isNull():
  469. tag = xmlInfo.toElement().tagName()
  470. text = xmlInfo.toElement().text().strip()
  471. if tag == "Type":
  472. saveState["type"] = text
  473. elif tag == "Name":
  474. saveState["name"] = xmlSafeString(text, False)
  475. elif tag in ("Label", "URI"):
  476. saveState["label"] = xmlSafeString(text, False)
  477. elif tag == "Binary":
  478. saveState["binary"] = xmlSafeString(text, False)
  479. elif tag == "UniqueID":
  480. if text.isdigit(): saveState["uniqueId"] = int(text)
  481. xmlInfo = xmlInfo.nextSibling()
  482. # ------------------------------------------------------
  483. # Data
  484. elif node.toElement().tagName() == "Data":
  485. xmlData = node.toElement().firstChild()
  486. while not xmlData.isNull():
  487. tag = xmlData.toElement().tagName()
  488. text = xmlData.toElement().text().strip()
  489. # ----------------------------------------------
  490. # Internal Data
  491. if tag == "Active":
  492. saveState['active'] = bool(text == "Yes")
  493. elif tag == "DryWet":
  494. if isNumber(text): saveState["dryWet"] = float(text)
  495. elif tag == "Volume":
  496. if isNumber(text): saveState["volume"] = float(text)
  497. elif tag == "Balance-Left":
  498. if isNumber(text): saveState["balanceLeft"] = float(text)
  499. elif tag == "Balance-Right":
  500. if isNumber(text): saveState["balanceRight"] = float(text)
  501. elif tag == "Panning":
  502. if isNumber(text): saveState["pannning"] = float(text)
  503. # ----------------------------------------------
  504. # Program (current)
  505. elif tag == "CurrentProgramIndex":
  506. if text.isdigit(): saveState["currentProgramIndex"] = int(text)
  507. elif tag == "CurrentProgramName":
  508. saveState["currentProgramName"] = xmlSafeString(text, False)
  509. # ----------------------------------------------
  510. # Midi Program (current)
  511. elif tag == "CurrentMidiBank":
  512. if text.isdigit(): saveState["currentMidiBank"] = int(text)
  513. elif tag == "CurrentMidiProgram":
  514. if text.isdigit(): saveState["currentMidiProgram"] = int(text)
  515. # ----------------------------------------------
  516. # Parameters
  517. elif tag == "Parameter":
  518. stateParameter = deepcopy(CarlaStateParameter)
  519. xmlSubData = xmlData.toElement().firstChild()
  520. while not xmlSubData.isNull():
  521. pTag = xmlSubData.toElement().tagName()
  522. pText = xmlSubData.toElement().text().strip()
  523. if pTag == "Index":
  524. if pText.isdigit(): stateParameter["index"] = int(pText)
  525. elif pTag == "Name":
  526. stateParameter["name"] = xmlSafeString(pText, False)
  527. elif pTag == "Symbol":
  528. stateParameter["symbol"] = xmlSafeString(pText, False)
  529. elif pTag == "Value":
  530. if isNumber(pText): stateParameter["value"] = float(pText)
  531. elif pTag == "MidiChannel":
  532. if pText.isdigit(): stateParameter["midiChannel"] = int(pText)
  533. elif pTag == "MidiCC":
  534. if pText.isdigit(): stateParameter["midiCC"] = int(pText)
  535. xmlSubData = xmlSubData.nextSibling()
  536. saveState["parameterList"].append(stateParameter)
  537. # ----------------------------------------------
  538. # Custom Data
  539. elif tag == "CustomData":
  540. stateCustomData = deepcopy(CarlaStateCustomData)
  541. xmlSubData = xmlData.toElement().firstChild()
  542. while not xmlSubData.isNull():
  543. cTag = xmlSubData.toElement().tagName()
  544. cText = xmlSubData.toElement().text().strip()
  545. if cTag == "Type":
  546. stateCustomData["type"] = xmlSafeString(cText, False)
  547. elif cTag == "Key":
  548. stateCustomData["key"] = xmlSafeString(cText, False)
  549. elif cTag == "Value":
  550. stateCustomData["value"] = xmlSafeString(cText, False)
  551. xmlSubData = xmlSubData.nextSibling()
  552. saveState["customDataList"].append(stateCustomData)
  553. # ----------------------------------------------
  554. # Chunk
  555. elif tag == "Chunk":
  556. saveState["chunk"] = xmlSafeString(text, False)
  557. # ----------------------------------------------
  558. xmlData = xmlData.nextSibling()
  559. # ------------------------------------------------------
  560. node = node.nextSibling()
  561. return saveState
  562. def xmlSafeString(string, toXml):
  563. if toXml:
  564. return string.replace("&", "&amp;").replace("<","&lt;").replace(">","&gt;").replace("'","&apos;").replace("\"","&quot;")
  565. else:
  566. return string.replace("&amp;", "&").replace("&lt;","<").replace("&gt;",">").replace("&apos;","'").replace("&quot;","\"")
  567. # ------------------------------------------------------------------------------------------------------------
  568. # Carla About dialog
  569. class CarlaAboutW(QDialog):
  570. def __init__(self, parent):
  571. QDialog.__init__(self, parent)
  572. self.ui = ui_carla_about.Ui_CarlaAboutW()
  573. self.ui.setupUi(self)
  574. if Carla.isControl:
  575. extraInfo = " - <b>%s</b>" % self.tr("OSC Bridge Version")
  576. else:
  577. extraInfo = ""
  578. self.ui.l_about.setText(self.tr(""
  579. "<br>Version %s"
  580. "<br>Carla is a Multi-Plugin Host for JACK%s.<br>"
  581. "<br>Copyright (C) 2011-2013 falkTX<br>"
  582. "" % (VERSION, extraInfo)))
  583. if Carla.isControl:
  584. self.ui.l_extended.hide()
  585. self.ui.tabWidget.removeTab(1)
  586. self.ui.tabWidget.removeTab(1)
  587. else:
  588. self.ui.l_extended.setText(cString(Carla.host.get_extended_license_text()))
  589. #self.ui.le_osc_url.setText(cString(Carla.host.get_host_osc_url()) if Carla.host.is_engine_running() else self.tr("(Engine not running)"))
  590. self.ui.l_osc_cmds.setText(
  591. " /set_active <i-value>\n"
  592. " /set_drywet <f-value>\n"
  593. " /set_volume <f-value>\n"
  594. " /set_balance_left <f-value>\n"
  595. " /set_balance_right <f-value>\n"
  596. " /set_panning <f-value>\n"
  597. " /set_parameter_value <i-index> <f-value>\n"
  598. " /set_parameter_midi_cc <i-index> <i-cc>\n"
  599. " /set_parameter_midi_channel <i-index> <i-channel>\n"
  600. " /set_program <i-index>\n"
  601. " /set_midi_program <i-index>\n"
  602. " /note_on <i-note> <i-velo>\n"
  603. " /note_off <i-note>\n"
  604. )
  605. self.ui.l_example.setText("/Carla/2/set_parameter_value 5 1.0")
  606. self.ui.l_example_help.setText("<i>(as in this example, \"2\" is the plugin number and \"5\" the parameter)</i>")
  607. self.ui.l_ladspa.setText(self.tr("Everything! (Including LRDF)"))
  608. self.ui.l_dssi.setText(self.tr("Everything! (Including CustomData/Chunks)"))
  609. self.ui.l_lv2.setText(self.tr("About 95&#37; complete (using custom extensions).<br/>"
  610. "Implemented Feature/Extensions:"
  611. "<ul>"
  612. "<li>http://lv2plug.in/ns/ext/atom</li>"
  613. "<li>http://lv2plug.in/ns/ext/buf-size</li>"
  614. "<li>http://lv2plug.in/ns/ext/data-access</li>"
  615. #"<li>http://lv2plug.in/ns/ext/dynmanifest</li>"
  616. "<li>http://lv2plug.in/ns/ext/event</li>"
  617. "<li>http://lv2plug.in/ns/ext/instance-access</li>"
  618. "<li>http://lv2plug.in/ns/ext/log</li>"
  619. "<li>http://lv2plug.in/ns/ext/midi</li>"
  620. "<li>http://lv2plug.in/ns/ext/options</li>"
  621. #"<li>http://lv2plug.in/ns/ext/parameters</li>"
  622. "<li>http://lv2plug.in/ns/ext/patch</li>"
  623. #"<li>http://lv2plug.in/ns/ext/port-groups</li>"
  624. "<li>http://lv2plug.in/ns/ext/port-props</li>"
  625. #"<li>http://lv2plug.in/ns/ext/presets</li>"
  626. "<li>http://lv2plug.in/ns/ext/state</li>"
  627. "<li>http://lv2plug.in/ns/ext/time</li>"
  628. "<li>http://lv2plug.in/ns/ext/uri-map</li>"
  629. "<li>http://lv2plug.in/ns/ext/urid</li>"
  630. "<li>http://lv2plug.in/ns/ext/worker</li>"
  631. "<li>http://lv2plug.in/ns/extensions/ui</li>"
  632. "<li>http://lv2plug.in/ns/extensions/units</li>"
  633. "<li>http://kxstudio.sf.net/ns/lv2ext/external-ui</li>"
  634. "<li>http://kxstudio.sf.net/ns/lv2ext/programs</li>"
  635. "<li>http://kxstudio.sf.net/ns/lv2ext/rtmempool</li>"
  636. "<li>http://ll-plugins.nongnu.org/lv2/ext/midimap</li>"
  637. "<li>http://ll-plugins.nongnu.org/lv2/ext/miditype</li>"
  638. "</ul>"))
  639. self.ui.l_vst.setText(self.tr("<p>About 85&#37; complete (missing vst bank/presets and some minor stuff)</p>"))
  640. def done(self, r):
  641. QDialog.done(self, r)
  642. self.close()
  643. # ------------------------------------------------------------------------------------------------------------
  644. # Plugin Parameter
  645. class PluginParameter(QWidget):
  646. def __init__(self, parent, pInfo, pluginId, tabIndex):
  647. QWidget.__init__(self, parent)
  648. self.ui = ui_carla_parameter.Ui_PluginParameter()
  649. self.ui.setupUi(self)
  650. pType = pInfo['type']
  651. pHints = pInfo['hints']
  652. self.fMidiCC = -1
  653. self.fMidiChannel = 1
  654. self.fParameterId = pInfo['index']
  655. self.fPluginId = pluginId
  656. self.fTabIndex = tabIndex
  657. self.ui.label.setText(pInfo['name'])
  658. for MIDI_CC in MIDI_CC_LIST:
  659. self.ui.combo.addItem(MIDI_CC)
  660. if pType == PARAMETER_INPUT:
  661. self.ui.widget.setMinimum(pInfo['minimum'])
  662. self.ui.widget.setMaximum(pInfo['maximum'])
  663. self.ui.widget.setDefault(pInfo['default'])
  664. self.ui.widget.setValue(pInfo['current'], False)
  665. self.ui.widget.setLabel(pInfo['unit'])
  666. self.ui.widget.setStep(pInfo['step'])
  667. self.ui.widget.setStepSmall(pInfo['stepSmall'])
  668. self.ui.widget.setStepLarge(pInfo['stepLarge'])
  669. self.ui.widget.setScalePoints(pInfo['scalepoints'], bool(pHints & PARAMETER_USES_SCALEPOINTS))
  670. if not pHints & PARAMETER_IS_ENABLED:
  671. self.ui.widget.setReadOnly(True)
  672. self.ui.combo.setEnabled(False)
  673. self.ui.sb_channel.setEnabled(False)
  674. elif not pHints & PARAMETER_IS_AUTOMABLE:
  675. self.ui.combo.setEnabled(False)
  676. self.ui.sb_channel.setEnabled(False)
  677. elif pType == PARAMETER_OUTPUT:
  678. self.ui.widget.setMinimum(pInfo['minimum'])
  679. self.ui.widget.setMaximum(pInfo['maximum'])
  680. self.ui.widget.setValue(pInfo['current'], False)
  681. self.ui.widget.setLabel(pInfo['unit'])
  682. self.ui.widget.setReadOnly(True)
  683. if not pHints & PARAMETER_IS_AUTOMABLE:
  684. self.ui.combo.setEnabled(False)
  685. self.ui.sb_channel.setEnabled(False)
  686. else:
  687. self.ui.widget.setVisible(False)
  688. self.ui.combo.setVisible(False)
  689. self.ui.sb_channel.setVisible(False)
  690. if pHints & PARAMETER_USES_CUSTOM_TEXT:
  691. self.ui.widget.setTextCallback(self._textCallBack)
  692. self.setMidiCC(pInfo['midiCC'])
  693. self.setMidiChannel(pInfo['midiChannel'])
  694. self.connect(self.ui.widget, SIGNAL("valueChanged(double)"), SLOT("slot_valueChanged(double)"))
  695. self.connect(self.ui.sb_channel, SIGNAL("valueChanged(int)"), SLOT("slot_midiChannelChanged(int)"))
  696. self.connect(self.ui.combo, SIGNAL("currentIndexChanged(int)"), SLOT("slot_midiCcChanged(int)"))
  697. #if force_parameters_style:
  698. #self.widget.force_plastique_style()
  699. self.ui.widget.updateAll()
  700. def setDefault(self, value):
  701. self.ui.widget.setDefault(value)
  702. def setValue(self, value, send=True):
  703. self.ui.widget.setValue(value, send)
  704. def setMidiCC(self, cc):
  705. self.fMidiCC = cc
  706. self._setMidiCcInComboBox(cc)
  707. def setMidiChannel(self, channel):
  708. self.fMidiChannel = channel
  709. self.ui.sb_channel.setValue(channel)
  710. def tabIndex(self):
  711. return self.fTabIndex
  712. @pyqtSlot(float)
  713. def slot_valueChanged(self, value):
  714. self.emit(SIGNAL("valueChanged(int, double)"), self.fParameterId, value)
  715. @pyqtSlot(int)
  716. def slot_midiCcChanged(self, ccIndex):
  717. if ccIndex <= 0:
  718. cc = -1
  719. else:
  720. ccStr = MIDI_CC_LIST[ccIndex - 1].split(" ")[0]
  721. cc = int(ccStr, 16)
  722. if self.fMidiCC != cc:
  723. self.emit(SIGNAL("midiCcChanged(int, int)"), self.fParameterId, cc)
  724. self.fMidiCC = cc
  725. @pyqtSlot(int)
  726. def slot_midiChannelChanged(self, channel):
  727. if self.fMidiChannel != channel:
  728. self.emit(SIGNAL("midiChannelChanged(int, int)"), self.fParameterId, channel)
  729. self.fMidiChannel = channel
  730. def _setMidiCcInComboBox(self, cc):
  731. for i in range(len(MIDI_CC_LIST)):
  732. ccStr = MIDI_CC_LIST[i].split(" ")[0]
  733. if int(ccStr, 16) == cc:
  734. ccIndex = i+1
  735. break
  736. else:
  737. ccIndex = 0
  738. self.ui.combo.setCurrentIndex(ccIndex)
  739. def _textCallBack(self):
  740. return cString(Carla.host.get_parameter_text(self.fPluginId, self.fParameterId))
  741. # ------------------------------------------------------------------------------------------------------------
  742. # TESTING
  743. #from PyQt4.QtGui import QApplication
  744. #Carla.isControl = True
  745. #ptest = {
  746. #'index': 0,
  747. #'name': "",
  748. #'symbol': "",
  749. #'current': 0.1,
  750. #'default': 0.3,
  751. #'minimum': 0.0,
  752. #'maximum': 1.0,
  753. #'midiChannel': 7,
  754. #'midiCC': 2,
  755. #'type': PARAMETER_INPUT,
  756. #'hints': PARAMETER_IS_ENABLED | PARAMETER_IS_AUTOMABLE,
  757. #'scalepoints': [],
  758. #'step': 0.01,
  759. #'stepSmall': 0.001,
  760. #'stepLarge': 0.1,
  761. #'unit': "un",
  762. #}
  763. #app = QApplication(sys.argv)
  764. #gui1 = CarlaAboutW(None)
  765. #gui2 = PluginParameter(None, ptest, 0, 0)
  766. #gui1.show()
  767. #gui2.show()
  768. #app.exec_()