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.

1518 lines
55KB

  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, QByteArray, QSettings, QTimer, SIGNAL, SLOT
  26. #pyqtSlot, qFatal,
  27. from PyQt4.QtGui import QColor, QDialog, QIcon, QFontMetrics, QFrame, QMessageBox, QPainter, QPainterPath, QVBoxLayout, QWidget
  28. #from PyQt4.QtGui import QCursor, QGraphicsScene, QInputDialog, QLinearGradient, QMenu,
  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': "",
  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 setLabelWidth(self, width):
  711. self.ui.label.setMinimumWidth(width)
  712. self.ui.label.setMaximumWidth(width)
  713. def tabIndex(self):
  714. return self.fTabIndex
  715. @pyqtSlot(float)
  716. def slot_valueChanged(self, value):
  717. self.emit(SIGNAL("valueChanged(int, double)"), self.fParameterId, value)
  718. @pyqtSlot(int)
  719. def slot_midiCcChanged(self, ccIndex):
  720. if ccIndex <= 0:
  721. cc = -1
  722. else:
  723. ccStr = MIDI_CC_LIST[ccIndex - 1].split(" ")[0]
  724. cc = int(ccStr, 16)
  725. if self.fMidiCC != cc:
  726. self.emit(SIGNAL("midiCcChanged(int, int)"), self.fParameterId, cc)
  727. self.fMidiCC = cc
  728. @pyqtSlot(int)
  729. def slot_midiChannelChanged(self, channel):
  730. if self.fMidiChannel != channel:
  731. self.emit(SIGNAL("midiChannelChanged(int, int)"), self.fParameterId, channel)
  732. self.fMidiChannel = channel
  733. def _setMidiCcInComboBox(self, cc):
  734. for i in range(len(MIDI_CC_LIST)):
  735. ccStr = MIDI_CC_LIST[i].split(" ")[0]
  736. if int(ccStr, 16) == cc:
  737. ccIndex = i+1
  738. break
  739. else:
  740. ccIndex = 0
  741. self.ui.combo.setCurrentIndex(ccIndex)
  742. def _textCallBack(self):
  743. return cString(Carla.host.get_parameter_text(self.fPluginId, self.fParameterId))
  744. # ------------------------------------------------------------------------------------------------------------
  745. # Plugin Editor (Built-in)
  746. class PluginEdit(QDialog):
  747. def __init__(self, parent, pluginId):
  748. QDialog.__init__(self, Carla.gui)
  749. self.ui = ui_carla_edit.Ui_PluginEdit()
  750. self.ui.setupUi(self)
  751. self.fGeometry = QByteArray()
  752. self.fPluginId = pluginId
  753. self.fPuginInfo = None
  754. self.fRealParent = parent
  755. self.fCurrentProgram = -1
  756. self.fCurrentMidiProgram = -1
  757. self.fCurrentStateFilename = None
  758. self.fParameterCount = 0
  759. self.fParameterList = [] # (type, id, widget)
  760. self.fParameterIdsToUpdate = [] # id
  761. self.fTabIconOff = QIcon(":/bitmaps/led_off.png")
  762. self.fTabIconOn = QIcon(":/bitmaps/led_yellow.png")
  763. self.fTabIconCount = 0
  764. self.fTabIconTimers = []
  765. self.ui.keyboard.setMode(self.ui.keyboard.HORIZONTAL)
  766. self.ui.keyboard.setOctaves(6)
  767. self.ui.scrollArea.ensureVisible(self.ui.keyboard.width() * 1 / 5, 0)
  768. self.ui.scrollArea.setVisible(False)
  769. # TODO - not implemented yet
  770. self.ui.b_reload_program.setEnabled(False)
  771. self.ui.b_reload_midi_program.setEnabled(False)
  772. # Not available for carla-control
  773. if Carla.isControl:
  774. self.ui.b_load_state.setEnabled(False)
  775. self.ui.b_save_state.setEnabled(False)
  776. else:
  777. self.connect(self.ui.b_save_state, SIGNAL("clicked()"), SLOT("slot_saveState()"))
  778. self.connect(self.ui.b_load_state, SIGNAL("clicked()"), SLOT("slot_loadState()"))
  779. self.connect(self.ui.keyboard, SIGNAL("noteOn(int)"), SLOT("slot_noteOn(int)"))
  780. self.connect(self.ui.keyboard, SIGNAL("noteOff(int)"), SLOT("slot_noteOff(int)"))
  781. self.connect(self.ui.cb_programs, SIGNAL("currentIndexChanged(int)"), SLOT("slot_programIndexChanged(int)"))
  782. self.connect(self.ui.cb_midi_programs, SIGNAL("currentIndexChanged(int)"), SLOT("slot_midiProgramIndexChanged(int)"))
  783. self.connect(self, SIGNAL("finished(int)"), SLOT("slot_finished()"))
  784. #self.reloadAll()
  785. def reloadAll(self):
  786. self.fPluginInfo = Carla.host.get_plugin_info(self.fPluginId)
  787. self.fPluginInfo["binary"] = cString(self.fPluginInfo["binary"])
  788. self.fPluginInfo["name"] = cString(self.fPluginInfo["name"])
  789. self.fPluginInfo["label"] = cString(self.fPluginInfo["label"])
  790. self.fPluginInfo["maker"] = cString(self.fPluginInfo["maker"])
  791. self.fPluginInfo["copyright"] = cString(self.fPluginInfo["copyright"])
  792. self.reloadInfo()
  793. self.reloadParameters()
  794. self.reloadPrograms()
  795. def reloadInfo(self):
  796. pluginName = cString(Carla.host.get_real_plugin_name(self.fPluginId))
  797. pluginType = self.fPluginInfo['type']
  798. pluginHints = self.fPluginInfo['hints']
  799. # Automatically change to MidiProgram tab
  800. if pluginType != PLUGIN_VST and not self.ui.le_name.text():
  801. self.ui.tab_programs.setCurrentIndex(1)
  802. # Set Meta-Data
  803. if pluginType == PLUGIN_INTERNAL:
  804. self.ui.le_type.setText(self.tr("Internal"))
  805. elif pluginType == PLUGIN_LADSPA:
  806. self.ui.le_type.setText("LADSPA")
  807. elif pluginType == PLUGIN_DSSI:
  808. self.ui.le_type.setText("DSSI")
  809. elif pluginType == PLUGIN_LV2:
  810. self.ui.le_type.setText("LV2")
  811. elif pluginType == PLUGIN_VST:
  812. self.ui.le_type.setText("VST")
  813. elif pluginType == PLUGIN_GIG:
  814. self.ui.le_type.setText("GIG")
  815. elif pluginType == PLUGIN_SF2:
  816. self.ui.le_type.setText("SF2")
  817. elif pluginType == PLUGIN_SFZ:
  818. self.ui.le_type.setText("SFZ")
  819. else:
  820. self.ui.le_type.setText(self.tr("Unknown"))
  821. self.ui.le_name.setText(pluginName)
  822. self.ui.le_name.setToolTip(pluginName)
  823. self.ui.le_label.setText(self.fPluginInfo['label'])
  824. self.ui.le_label.setToolTip(self.fPluginInfo['label'])
  825. self.ui.le_maker.setText(self.fPluginInfo['maker'])
  826. self.ui.le_maker.setToolTip(self.fPluginInfo['maker'])
  827. self.ui.le_copyright.setText(self.fPluginInfo['copyright'])
  828. self.ui.le_copyright.setToolTip(self.fPluginInfo['copyright'])
  829. self.ui.le_unique_id.setText(str(self.fPluginInfo['uniqueId']))
  830. self.ui.le_unique_id.setToolTip(str(self.fPluginInfo['uniqueId']))
  831. self.ui.label_plugin.setText("\n%s\n" % self.fPluginInfo['name'])
  832. self.setWindowTitle(self.fPluginInfo['name'])
  833. # Set Processing Data
  834. audioCountInfo = Carla.host.get_audio_port_count_info(self.fPluginId)
  835. midiCountInfo = Carla.host.get_midi_port_count_info(self.fPluginId)
  836. paramCountInfo = Carla.host.get_parameter_count_info(self.fPluginId)
  837. self.ui.le_ains.setText(str(audioCountInfo['ins']))
  838. self.ui.le_aouts.setText(str(audioCountInfo['outs']))
  839. self.ui.le_params.setText(str(paramCountInfo['ins']))
  840. self.ui.le_couts.setText(str(paramCountInfo['outs']))
  841. self.ui.le_is_synth.setText(self.tr("Yes") if (pluginHints & PLUGIN_IS_SYNTH) else self.tr("No"))
  842. self.ui.le_has_gui.setText(self.tr("Yes") if (pluginHints & PLUGIN_HAS_GUI) else self.tr("No"))
  843. # Show/hide keyboard
  844. self.ui.scrollArea.setVisible((pluginHints & PLUGIN_IS_SYNTH) != 0 or (midiCountInfo['ins'] > 0 < midiCountInfo['outs']))
  845. # Force-Update parent for new hints (knobs)
  846. if self.fRealParent:
  847. self.fRealParent.recheckPluginHints(pluginHints)
  848. def reloadParameters(self):
  849. parameterCount = Carla.host.get_parameter_count(self.m_pluginId)
  850. # Reset
  851. self.fParameterCount = 0
  852. self.fParameterList = []
  853. self.fParameterIdsToUpdate = []
  854. self.fTabIconCount = 0
  855. self.fTabIconTimers = []
  856. # Remove all previous parameters
  857. for i in range(self.ui.tabWidget.count()-1):
  858. self.ui.tabWidget.widget(1).deleteLater()
  859. self.ui.tabWidget.removeTab(1)
  860. if parameterCount <= 0:
  861. pass
  862. elif parameterCount <= Carla.maxParameters:
  863. paramInputListFull = []
  864. paramOutputListFull = []
  865. paramInputList = [] # ([params], width)
  866. paramInputWidth = 0
  867. paramOutputList = [] # ([params], width)
  868. paramOutputWidth = 0
  869. for i in range(parameterCount):
  870. paramInfo = Carla.host.get_parameter_info(self.fPluginId, i)
  871. paramData = Carla.host.get_parameter_data(self.fPluginId, i)
  872. paramRanges = Carla.host.get_parameter_ranges(self.fPluginId, i)
  873. if paramData['type'] not in (PARAMETER_INPUT, PARAMETER_OUTPUT):
  874. continue
  875. parameter = {
  876. 'type': paramData['type'],
  877. 'hints': paramData['hints'],
  878. 'name': cString(paramInfo['name']),
  879. 'unit': cString(paramInfo['unit']),
  880. 'scalePoints': [],
  881. 'index': paramData['index'],
  882. 'default': paramRanges['def'],
  883. 'minimum': paramRanges['min'],
  884. 'maximum': paramRanges['max'],
  885. 'step': paramRanges['step'],
  886. 'stepSmall': paramRanges['stepSmall'],
  887. 'stepLarge': paramRanges['stepLarge'],
  888. 'midiCC': paramData['midiCC'],
  889. 'midiChannel': paramData['midiChannel'],
  890. 'current': Carla.host.get_current_parameter_value(self.fPluginId, i)
  891. }
  892. for j in range(paramInfo['scalePointCount']):
  893. scalePointInfo = Carla.host.get_parameter_scalepoint_info(self.fPluginId, i, j)
  894. parameter['scalepoints'].append(
  895. {
  896. 'value': scalePointInfo['value'],
  897. 'label': cString(scalePointInfo['label'])
  898. })
  899. paramInputList.append(parameter)
  900. # -----------------------------------------------------------------
  901. # Get width values, in packs of 10
  902. if parameter['type'] == PARAMETER_INPUT:
  903. paramInputWidthTMP = QFontMetrics(self.font()).width(parameter['name'])
  904. if paramInputWidthTMP > paramInputWidth:
  905. paramInputWidth = paramInputWidthTMP
  906. if len(paramInputList) == 10:
  907. paramInputListFull.append((paramInputList, paramInputWidth))
  908. paramInputList = []
  909. paramInputWidth = 0
  910. else:
  911. paramOutputWidthTMP = QFontMetrics(self.font()).width(parameter['name'])
  912. if paramOutputWidthTMP > paramOutputWidth:
  913. paramOutputWidth = paramOutputWidthTMP
  914. if len(paramOutputList) == 10:
  915. paramOutputListFull.append((paramOutputList, paramOutputWidth))
  916. paramOutputList = []
  917. paramOutputWidth = 0
  918. # for i in range(parameterCount)
  919. else:
  920. # Final page width values
  921. if 0 < len(paramInputList) < 10:
  922. paramInputListFull.append((paramInputList, paramInputWidth))
  923. if 0 < len(paramOutputList) < 10:
  924. paramOutputListFull.append((paramOutputList, paramOutputWidth))
  925. # -----------------------------------------------------------------
  926. # Create parameter widgets
  927. self._createParameterWidgets(PARAMETER_INPUT, paramInputListFull, self.tr("Parameters"))
  928. self._createParameterWidgets(PARAMETER_OUTPUT, paramOutputListFull, self.tr("Outputs"))
  929. else: # > Carla.maxParameters
  930. fakeName = self.tr("This plugin has too many parameters to display here!")
  931. paramFakeListFull = []
  932. paramFakeList = []
  933. paramFakeWidth = QFontMetrics(self.font()).width(fakeName)
  934. parameter = {
  935. 'type': PARAMETER_UNKNOWN,
  936. 'hints': 0,
  937. 'name': fakeName,
  938. 'unit': "",
  939. 'scalepoints': [],
  940. 'index': 0,
  941. 'default': 0,
  942. 'minimum': 0,
  943. 'maximum': 0,
  944. 'step': 0,
  945. 'stepSmall': 0,
  946. 'stepLarge': 0,
  947. 'midiCC': -1,
  948. 'midiChannel': 0,
  949. 'current': 0.0
  950. }
  951. paramFakeList.append(parameter)
  952. paramFakeListFull.append((paramFakeList, paramFakeWidth))
  953. self.createParameterWidgets(PARAMETER_UNKNOWN, paramFakeListFull, self.tr("Information"))
  954. def reloadPrograms(self):
  955. # Programs
  956. self.ui.cb_programs.blockSignals(True)
  957. self.ui.cb_programs.clear()
  958. programCount = Carla.host.get_program_count(self.fPluginId)
  959. if programCount > 0:
  960. self.ui.cb_programs.setEnabled(True)
  961. for i in range(programCount):
  962. pName = cString(Carla.host.get_program_name(self.fPluginId, i))
  963. self.ui.cb_programs.addItem(pName)
  964. self.fCurrentProgram = Carla.host.get_current_program_index(self.fPluginId)
  965. self.ui.cb_programs.setCurrentIndex(self.fCurrentProgram)
  966. else:
  967. self.fCurrentProgram = -1
  968. self.ui.cb_programs.setEnabled(False)
  969. self.ui.cb_programs.blockSignals(False)
  970. # MIDI Programs
  971. self.ui.cb_midi_programs.blockSignals(True)
  972. self.ui.cb_midi_programs.clear()
  973. midiProgramCount = Carla.host.get_midi_program_count(self.fPluginId)
  974. if midiProgramCount > 0:
  975. self.ui.cb_midi_programs.setEnabled(True)
  976. for i in range(midiProgramCount):
  977. mpData = Carla.host.get_midi_program_data(self.fPluginId, i)
  978. mpBank = int(mpData['bank'])
  979. mpProg = int(mpData['program'])
  980. mpLabel = cString(mpData['label'])
  981. self.ui.cb_midi_programs.addItem("%03i:%03i - %s" % (mpBank, mpProg, mpLabel))
  982. self.fCurrentMidiProgram = Carla.host.get_current_midi_program_index(self.fPluginId)
  983. self.ui.cb_midi_programs.setCurrentIndex(self.fCurrentMidiProgram)
  984. else:
  985. self.fCurrentMidiProgram = -1
  986. self.ui.cb_midi_programs.setEnabled(False)
  987. self.ui.cb_midi_programs.blockSignals(False)
  988. def setVisible(self, yesNo):
  989. if yesNo:
  990. if not self.fGeometry.isNull():
  991. self.restoreGeometry(self.fGeometry)
  992. else:
  993. self.fGeometry = self.saveGeometry()
  994. QDialog.setVisible(self, yesNo)
  995. @pyqtSlot(int, float)
  996. def slot_parameterValueChanged(self, parameterId, value):
  997. Carla.host.set_parameter_value(self.fPluginId, parameterId, value)
  998. @pyqtSlot(int, int)
  999. def slot_parameterMidiChannelChanged(self, parameterId, channel):
  1000. Carla.host.set_parameter_midi_channel(self.fPluginId, parameterId, channel-1)
  1001. @pyqtSlot(int, int)
  1002. def slot_parameterMidiCcChanged(self, parameterId, cc):
  1003. Carla.host.set_parameter_midi_cc(self.fPluginId, parameterId, cc)
  1004. @pyqtSlot(int)
  1005. def slot_programIndexChanged(self, index):
  1006. if self.fCurrentProgram != index:
  1007. self.fCurrentProgram = index
  1008. Carla.host.set_program(self.fPluginId, index)
  1009. @pyqtSlot(int)
  1010. def slot_midiProgramIndexChanged(self, index):
  1011. if self.fCurrentMidiProgram != index:
  1012. self.fCurrentMidiProgram = index
  1013. Carla.host.set_midi_program(self.fPluginId, index)
  1014. @pyqtSlot(int)
  1015. def slot_noteOn(self, note):
  1016. Carla.host.send_midi_note(self.fPluginId, 0, note, 100)
  1017. @pyqtSlot(int)
  1018. def slot_noteOff(self, note):
  1019. Carla.host.send_midi_note(self.fPluginId, 0, note, 0)
  1020. @pyqtSlot()
  1021. def slot_notesOn(self):
  1022. if self.fRealParent:
  1023. self.fRealParent.led_midi.setChecked(True)
  1024. @pyqtSlot()
  1025. def slot_notesOff(self):
  1026. if self.fRealParent:
  1027. self.fRealParent.led_midi.setChecked(False)
  1028. @pyqtSlot()
  1029. def slot_finished(self):
  1030. if self.fRealParent:
  1031. self.fRealParent.editClosed()
  1032. def _createParameterWidgets(self, paramType, paramListFull, tabPageName):
  1033. i = 1
  1034. for paramList, width in paramListFull:
  1035. if len(paramList) == 0:
  1036. break
  1037. tabIndex = self.ui.tabWidget.count()
  1038. tabPageContainer = QWidget(self.ui.tabWidget)
  1039. tabPageLayout = QVBoxLayout(tabPageContainer)
  1040. tabPageContainer.setLayout(tabPageLayout)
  1041. for paramInfo in paramList:
  1042. paramWidget = PluginParameter(tabPageContainer, paramInfo, self.fPluginId, tabIndex)
  1043. paramWidget.setLabelWidth(width)
  1044. tabPageLayout.addWidget(paramWidget)
  1045. self.fParameterList.append((paramType, paramInfo['index'], paramWidget))
  1046. if paramType == PARAMETER_INPUT:
  1047. self.connect(paramWidget, SIGNAL("valueChanged(int, double)"), SLOT("slot_parameterValueChanged(int, double)"))
  1048. self.connect(paramWidget, SIGNAL("midiChannelChanged(int, int)"), SLOT("slot_parameterMidiChannelChanged(int, int)"))
  1049. self.connect(paramWidget, SIGNAL("midiCcChanged(int, int)"), SLOT("slot_parameterMidiCcChanged(int, int)"))
  1050. tabPageLayout.addStretch()
  1051. self.ui.tabWidget.addTab(tabPageContainer, "%s (%i)" % (tabPageName, i))
  1052. i += 1
  1053. if paramType == PARAMETER_INPUT:
  1054. self.ui.tabWidget.setTabIcon(tabIndex, self.fTabIconOff)
  1055. self.fTabIconTimers.append(ICON_STATE_NULL)
  1056. def done(self, r):
  1057. QDialog.done(self, r)
  1058. self.close()
  1059. # ------------------------------------------------------------------------------------------------------------
  1060. # Plugin Widget
  1061. class PluginWidget(QFrame):
  1062. def __init__(self, parent, pluginId):
  1063. QFrame.__init__(self, parent)
  1064. self.ui = ui_carla_plugin.Ui_PluginWidget()
  1065. self.ui.setupUi(self)
  1066. self.fPluginId = pluginId
  1067. #self.fPluginInfo = Carla.host.get_plugin_info(self.fPluginId)
  1068. #self.fPluginInfo["binary"] = cString(self.fPluginInfo["binary"])
  1069. #self.fPluginInfo["name"] = cString(self.fPluginInfo["name"])
  1070. #self.fPluginInfo["label"] = cString(self.fPluginInfo["label"])
  1071. #self.fPluginInfo["maker"] = cString(self.fPluginInfo["maker"])
  1072. #self.fPluginInfo["copyright"] = cString(self.fPluginInfo["copyright"])
  1073. self.fParameterIconTimer = ICON_STATE_NULL
  1074. self.fLastGreenLedState = False
  1075. self.fLastBlueLedState = False
  1076. if Carla.processMode == PROCESS_MODE_CONTINUOUS_RACK:
  1077. self.fPeaksInputCount = 2
  1078. self.fPeaksOutputCount = 2
  1079. #self.ui.stackedWidget.setCurrentIndex(0)
  1080. else:
  1081. audioCountInfo = Carla.host.get_audio_port_count_info(self.fPluginId)
  1082. self.fPeaksInputCount = int(audioCountInfo['ins'])
  1083. self.fPeaksOutputCount = int(audioCountInfo['outs'])
  1084. if self.fPeaksInputCount > 2:
  1085. self.fPeaksInputCount = 2
  1086. if self.fPeaksOutputCount > 2:
  1087. self.fPeaksOutputCount = 2
  1088. #if audioCountInfo['total'] == 0:
  1089. #self.ui.stackedWidget.setCurrentIndex(1)
  1090. #else:
  1091. #self.ui.stackedWidget.setCurrentIndex(0)
  1092. # Background
  1093. self.fColorTop = QColor(60, 60, 60)
  1094. self.fColorBottom = QColor(47, 47, 47)
  1095. # Colorify
  1096. #if self.m_pluginInfo['category'] == PLUGIN_CATEGORY_SYNTH:
  1097. #self.setWidgetColor(PALETTE_COLOR_WHITE)
  1098. #elif self.m_pluginInfo['category'] == PLUGIN_CATEGORY_DELAY:
  1099. #self.setWidgetColor(PALETTE_COLOR_ORANGE)
  1100. #elif self.m_pluginInfo['category'] == PLUGIN_CATEGORY_EQ:
  1101. #self.setWidgetColor(PALETTE_COLOR_GREEN)
  1102. #elif self.m_pluginInfo['category'] == PLUGIN_CATEGORY_FILTER:
  1103. #self.setWidgetColor(PALETTE_COLOR_BLUE)
  1104. #elif self.m_pluginInfo['category'] == PLUGIN_CATEGORY_DYNAMICS:
  1105. #self.setWidgetColor(PALETTE_COLOR_PINK)
  1106. #elif self.m_pluginInfo['category'] == PLUGIN_CATEGORY_MODULATOR:
  1107. #self.setWidgetColor(PALETTE_COLOR_RED)
  1108. #elif self.m_pluginInfo['category'] == PLUGIN_CATEGORY_UTILITY:
  1109. #self.setWidgetColor(PALETTE_COLOR_YELLOW)
  1110. #elif self.m_pluginInfo['category'] == PLUGIN_CATEGORY_OTHER:
  1111. #self.setWidgetColor(PALETTE_COLOR_BROWN)
  1112. #else:
  1113. #self.setWidgetColor(PALETTE_COLOR_NONE)
  1114. self.ui.led_enable.setColor(self.ui.led_enable.BIG_RED)
  1115. self.ui.led_enable.setChecked(False)
  1116. self.ui.led_control.setColor(self.ui.led_control.YELLOW)
  1117. self.ui.led_control.setEnabled(False)
  1118. self.ui.led_midi.setColor(self.ui.led_midi.RED)
  1119. self.ui.led_midi.setEnabled(False)
  1120. self.ui.led_audio_in.setColor(self.ui.led_audio_in.GREEN)
  1121. self.ui.led_audio_in.setEnabled(False)
  1122. self.ui.led_audio_out.setColor(self.ui.led_audio_out.BLUE)
  1123. self.ui.led_audio_out.setEnabled(False)
  1124. self.ui.dial_drywet.setPixmap(3)
  1125. self.ui.dial_vol.setPixmap(3)
  1126. self.ui.dial_b_left.setPixmap(4)
  1127. self.ui.dial_b_right.setPixmap(4)
  1128. self.ui.dial_drywet.setCustomPaint(self.ui.dial_drywet.CUSTOM_PAINT_CARLA_WET)
  1129. self.ui.dial_vol.setCustomPaint(self.ui.dial_vol.CUSTOM_PAINT_CARLA_VOL)
  1130. self.ui.dial_b_left.setCustomPaint(self.ui.dial_b_left.CUSTOM_PAINT_CARLA_L)
  1131. self.ui.dial_b_right.setCustomPaint(self.ui.dial_b_right.CUSTOM_PAINT_CARLA_R)
  1132. self.ui.peak_in.setColor(self.ui.peak_in.GREEN)
  1133. self.ui.peak_in.setOrientation(self.ui.peak_in.HORIZONTAL)
  1134. self.ui.peak_out.setColor(self.ui.peak_in.BLUE)
  1135. self.ui.peak_out.setOrientation(self.ui.peak_out.HORIZONTAL)
  1136. self.ui.peak_in.setChannels(self.fPeaksInputCount)
  1137. self.ui.peak_out.setChannels(self.fPeaksOutputCount)
  1138. #self.ui.label_name.setText(self.fPluginInfo['name'])
  1139. self.ui.edit_dialog = PluginEdit(self, self.fPluginId)
  1140. self.ui.edit_dialog.hide()
  1141. #self.connect(self.ui.led_enable, SIGNAL("clicked(bool)"), SLOT("slot_setActive(bool)"))
  1142. #self.connect(self.ui.dial_drywet, SIGNAL("sliderMoved(int)"), SLOT("slot_setDryWet(int)"))
  1143. #self.connect(self.ui.dial_vol, SIGNAL("sliderMoved(int)"), SLOT("slot_setVolume(int)"))
  1144. #self.connect(self.ui.dial_b_left, SIGNAL("sliderMoved(int)"), SLOT("slot_setBalanceLeft(int)"))
  1145. #self.connect(self.ui.dial_b_right, SIGNAL("sliderMoved(int)"), SLOT("slot_setBalanceRight(int)"))
  1146. #self.connect(self.ui.b_gui, SIGNAL("clicked(bool)"), SLOT("slot_guiClicked(bool)"))
  1147. self.connect(self.ui.b_edit, SIGNAL("clicked(bool)"), SLOT("slot_editClicked(bool)"))
  1148. #self.connect(self.ui.b_remove, SIGNAL("clicked()"), SLOT("slot_removeClicked()"))
  1149. #self.connect(self.ui.dial_drywet, SIGNAL("customContextMenuRequested(QPoint)"), SLOT("slot_showCustomDialMenu()"))
  1150. #self.connect(self.ui.dial_vol, SIGNAL("customContextMenuRequested(QPoint)"), SLOT("slot_showCustomDialMenu()"))
  1151. #self.connect(self.ui.dial_b_left, SIGNAL("customContextMenuRequested(QPoint)"), SLOT("slot_showCustomDialMenu()"))
  1152. #self.connect(self.ui.dial_b_right, SIGNAL("customContextMenuRequested(QPoint)"), SLOT("slot_showCustomDialMenu()"))
  1153. # FIXME
  1154. self.ui.frame_controls.setVisible(False)
  1155. self.ui.pushButton.setVisible(False)
  1156. self.ui.pushButton_2.setVisible(False)
  1157. self.ui.pushButton_3.setVisible(False)
  1158. self.setMaximumHeight(30)
  1159. def editClosed(self):
  1160. self.ui.b_edit.setChecked(False)
  1161. def recheckPluginHints(self, hints):
  1162. self.fPluginInfo['hints'] = hints
  1163. self.ui.dial_drywet.setEnabled(hints & PLUGIN_CAN_DRYWET)
  1164. self.ui.dial_vol.setEnabled(hints & PLUGIN_CAN_VOLUME)
  1165. self.ui.dial_b_left.setEnabled(hints & PLUGIN_CAN_BALANCE)
  1166. self.ui.dial_b_right.setEnabled(hints & PLUGIN_CAN_BALANCE)
  1167. self.ui.b_gui.setEnabled(hints & PLUGIN_HAS_GUI)
  1168. def paintEvent(self, event):
  1169. painter = QPainter(self)
  1170. areaX = self.ui.area_right.x()
  1171. # background
  1172. #painter.setPen(self.m_colorTop)
  1173. #painter.setBrush(self.m_colorTop)
  1174. #painter.drawRect(0, 0, areaX+40, self.height())
  1175. # bottom line
  1176. painter.setPen(self.fColorBottom)
  1177. painter.setBrush(self.fColorBottom)
  1178. painter.drawRect(0, self.height()-5, areaX, 5)
  1179. # top line
  1180. painter.drawLine(0, 0, areaX+40, 0)
  1181. # name -> leds arc
  1182. path = QPainterPath()
  1183. path.moveTo(areaX-80, self.height())
  1184. path.cubicTo(areaX+40, self.height()-5, areaX-40, 30, areaX+20, 0)
  1185. path.lineTo(areaX+20, self.height())
  1186. painter.drawPath(path)
  1187. # fill the rest
  1188. painter.drawRect(areaX+20, 0, self.width(), self.height())
  1189. #painter.drawLine(0, 3, self.width(), 3)
  1190. #painter.drawLine(0, self.height() - 4, self.width(), self.height() - 4)
  1191. #painter.setPen(self.m_color2)
  1192. #painter.drawLine(0, 2, self.width(), 2)
  1193. #painter.drawLine(0, self.height() - 3, self.width(), self.height() - 3)
  1194. #painter.setPen(self.m_color3)
  1195. #painter.drawLine(0, 1, self.width(), 1)
  1196. #painter.drawLine(0, self.height() - 2, self.width(), self.height() - 2)
  1197. #painter.setPen(self.m_color4)
  1198. #painter.drawLine(0, 0, self.width(), 0)
  1199. #painter.drawLine(0, self.height() - 1, self.width(), self.height() - 1)
  1200. QFrame.paintEvent(self, event)
  1201. @pyqtSlot(bool)
  1202. def slot_editClicked(self, show):
  1203. self.ui.edit_dialog.setVisible(show)
  1204. # ------------------------------------------------------------------------------------------------------------
  1205. # TESTING
  1206. #from PyQt4.QtGui import QApplication
  1207. #Carla.isControl = True
  1208. #ptest = {
  1209. #'index': 0,
  1210. #'name': "",
  1211. #'symbol': "",
  1212. #'current': 0.1,
  1213. #'default': 0.3,
  1214. #'minimum': 0.0,
  1215. #'maximum': 1.0,
  1216. #'midiChannel': 7,
  1217. #'midiCC': 2,
  1218. #'type': PARAMETER_INPUT,
  1219. #'hints': PARAMETER_IS_ENABLED | PARAMETER_IS_AUTOMABLE,
  1220. #'scalepoints': [],
  1221. #'step': 0.01,
  1222. #'stepSmall': 0.001,
  1223. #'stepLarge': 0.1,
  1224. #'unit': "un",
  1225. #}
  1226. #app = QApplication(sys.argv)
  1227. #gui1 = CarlaAboutW(None)
  1228. #gui2 = PluginParameter(None, ptest, 0, 0)
  1229. #gui3 = PluginEdit(None, 0)
  1230. #gui4 = PluginWidget(None, 0)
  1231. #gui1.show()
  1232. #gui2.show()
  1233. #gui3.show()
  1234. #gui4.show()
  1235. #app.exec_()