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.

986 lines
36KB

  1. #!/usr/bin/env python3
  2. # SPDX-FileCopyrightText: 2011-2025 Filipe Coelho <falktx@falktx.com>
  3. # SPDX-License-Identifier: GPL-2.0-or-later
  4. # ------------------------------------------------------------------------------------------------------------
  5. # Imports (Global)
  6. import os
  7. import sys
  8. from math import fmod, log10
  9. # ------------------------------------------------------------------------------------------------------------
  10. # Imports (Signal)
  11. from signal import signal, SIGINT, SIGTERM
  12. try:
  13. from signal import SIGUSR1
  14. haveSIGUSR1 = True
  15. except:
  16. haveSIGUSR1 = False
  17. # ------------------------------------------------------------------------------------------------------------
  18. # Imports (PyQt)
  19. from qt_compat import qt_config
  20. # pylint: disable=import-error
  21. if qt_config == 5:
  22. # import changed in PyQt 5.15.8, so try both
  23. try:
  24. from PyQt5.Qt import PYQT_VERSION_STR
  25. except ImportError:
  26. from PyQt5.QtCore import PYQT_VERSION_STR
  27. from PyQt5.QtCore import qFatal, QT_VERSION, QT_VERSION_STR, qWarning, QDir, QSettings
  28. from PyQt5.QtGui import QIcon
  29. from PyQt5.QtWidgets import QFileDialog, QMessageBox
  30. elif qt_config == 6:
  31. from PyQt6.QtCore import qFatal, PYQT_VERSION_STR, QT_VERSION, QT_VERSION_STR, qWarning, QDir, QSettings
  32. from PyQt6.QtGui import QIcon
  33. from PyQt6.QtWidgets import QFileDialog, QMessageBox
  34. # pylint: enable=import-error
  35. # pylint: disable=possibly-used-before-assignment
  36. # ------------------------------------------------------------------------------------------------------------
  37. # Imports (Custom)
  38. from carla_backend import (
  39. CARLA_OS_64BIT,
  40. CARLA_OS_HAIKU,
  41. CARLA_OS_MAC,
  42. CARLA_OS_WIN,
  43. CARLA_VERSION_STRING,
  44. MAX_DEFAULT_PARAMETERS,
  45. ENGINE_PROCESS_MODE_MULTIPLE_CLIENTS,
  46. ENGINE_PROCESS_MODE_PATCHBAY,
  47. ENGINE_TRANSPORT_MODE_INTERNAL,
  48. ENGINE_TRANSPORT_MODE_JACK,
  49. )
  50. from utils import QSafeSettings
  51. import ast
  52. # ------------------------------------------------------------------------------------------------------------
  53. # Config
  54. # These will be modified during install
  55. X_LIBDIR_X = None
  56. X_DATADIR_X = None
  57. # ------------------------------------------------------------------------------------------------------------
  58. # Platform specific stuff
  59. if CARLA_OS_WIN:
  60. WINDIR = os.getenv("WINDIR")
  61. # ------------------------------------------------------------------------------------------------------------
  62. # Set TMP
  63. envTMP = os.getenv("TMP")
  64. if envTMP is None:
  65. if CARLA_OS_WIN:
  66. qWarning("TMP variable not set")
  67. TMP = QDir.tempPath()
  68. else:
  69. TMP = envTMP
  70. if not os.path.exists(TMP):
  71. qWarning("TMP does not exist")
  72. TMP = "/"
  73. del envTMP
  74. # ------------------------------------------------------------------------------------------------------------
  75. # Set HOME
  76. envHOME = os.getenv("HOME")
  77. if envHOME is None:
  78. if not CARLA_OS_WIN:
  79. qWarning("HOME variable not set")
  80. HOME = QDir.toNativeSeparators(QDir.homePath())
  81. else:
  82. HOME = envHOME
  83. if not os.path.exists(HOME):
  84. qWarning("HOME does not exist")
  85. HOME = TMP
  86. del envHOME
  87. # ------------------------------------------------------------------------------------------------------------
  88. # Set PATH
  89. envPATH = os.getenv("PATH")
  90. if envPATH is None:
  91. qWarning("PATH variable not set")
  92. if CARLA_OS_MAC:
  93. PATH = ("/opt/local/bin", "/usr/local/bin", "/usr/bin", "/bin")
  94. elif CARLA_OS_WIN:
  95. PATH = (os.path.join(WINDIR, "system32"), WINDIR)
  96. else:
  97. PATH = ("/usr/local/bin", "/usr/bin", "/bin")
  98. else:
  99. PATH = envPATH.split(os.pathsep)
  100. del envPATH
  101. # ------------------------------------------------------------------------------------------------------------
  102. # Static MIDI CC list
  103. MIDI_CC_LIST = (
  104. "01 [0x01] Modulation",
  105. "02 [0x02] Breath",
  106. "04 [0x04] Foot",
  107. "05 [0x05] Portamento",
  108. "07 [0x07] Volume",
  109. "08 [0x08] Balance",
  110. "10 [0x0A] Pan",
  111. "11 [0x0B] Expression",
  112. "12 [0x0C] FX Control 1",
  113. "13 [0x0D] FX Control 2",
  114. "16 [0x10] General Purpose 1",
  115. "17 [0x11] General Purpose 2",
  116. "18 [0x12] General Purpose 3",
  117. "19 [0x13] General Purpose 4",
  118. "70 [0x46] Control 1 [Variation]",
  119. "71 [0x47] Control 2 [Timbre]",
  120. "72 [0x48] Control 3 [Release]",
  121. "73 [0x49] Control 4 [Attack]",
  122. "74 [0x4A] Control 5 [Brightness]",
  123. "75 [0x4B] Control 6 [Decay]",
  124. "76 [0x4C] Control 7 [Vib Rate]",
  125. "77 [0x4D] Control 8 [Vib Depth]",
  126. "78 [0x4E] Control 9 [Vib Delay]",
  127. "79 [0x4F] Control 10 [Undefined]",
  128. "80 [0x50] General Purpose 5",
  129. "81 [0x51] General Purpose 6",
  130. "82 [0x52] General Purpose 7",
  131. "83 [0x53] General Purpose 8",
  132. "84 [0x54] Portamento Control",
  133. "91 [0x5B] FX 1 Depth [Reverb]",
  134. "92 [0x5C] FX 2 Depth [Tremolo]",
  135. "93 [0x5D] FX 3 Depth [Chorus]",
  136. "94 [0x5E] FX 4 Depth [Detune]",
  137. "95 [0x5F] FX 5 Depth [Phaser]"
  138. )
  139. MAX_MIDI_CC_LIST_ITEM = 95
  140. # ------------------------------------------------------------------------------------------------------------
  141. # PatchCanvas defines
  142. CANVAS_ANTIALIASING_SMALL = 1
  143. CANVAS_EYECANDY_SMALL = 1
  144. # ------------------------------------------------------------------------------------------------------------
  145. # Carla Settings keys
  146. CARLA_KEY_MAIN_PROJECT_FOLDER = "Main/ProjectFolder" # str
  147. CARLA_KEY_MAIN_USE_PRO_THEME = "Main/UseProTheme" # bool
  148. CARLA_KEY_MAIN_PRO_THEME_COLOR = "Main/ProThemeColor" # str
  149. CARLA_KEY_MAIN_SKIN_TWEAKS = "Main/SkinTweaks" # str
  150. CARLA_KEY_MAIN_REFRESH_INTERVAL = "Main/RefreshInterval" # int
  151. CARLA_KEY_MAIN_CONFIRM_EXIT = "Main/ConfirmExit" # bool
  152. CARLA_KEY_MAIN_CLASSIC_SKIN = "Main/ClassicSkin" # bool
  153. CARLA_KEY_MAIN_SHOW_LOGS = "Main/ShowLogs" # bool
  154. CARLA_KEY_MAIN_SYSTEM_ICONS = "Main/SystemIcons" # bool
  155. CARLA_KEY_MAIN_EXPERIMENTAL = "Main/Experimental" # bool
  156. CARLA_KEY_CANVAS_THEME = "Canvas/Theme" # str
  157. CARLA_KEY_CANVAS_SIZE = "Canvas/Size" # str "NxN"
  158. CARLA_KEY_CANVAS_USE_BEZIER_LINES = "Canvas/UseBezierLines" # bool
  159. CARLA_KEY_CANVAS_AUTO_HIDE_GROUPS = "Canvas/AutoHideGroups" # bool
  160. CARLA_KEY_CANVAS_AUTO_SELECT_ITEMS = "Canvas/AutoSelectItems" # bool
  161. CARLA_KEY_CANVAS_EYE_CANDY = "Canvas/EyeCandy2" # bool
  162. CARLA_KEY_CANVAS_FANCY_EYE_CANDY = "Canvas/FancyEyeCandy" # bool
  163. CARLA_KEY_CANVAS_USE_OPENGL = "Canvas/UseOpenGL" # bool
  164. CARLA_KEY_CANVAS_ANTIALIASING = "Canvas/Antialiasing" # enum
  165. CARLA_KEY_CANVAS_HQ_ANTIALIASING = "Canvas/HQAntialiasing" # bool
  166. CARLA_KEY_CANVAS_INLINE_DISPLAYS = "Canvas/InlineDisplays" # bool
  167. CARLA_KEY_CANVAS_FULL_REPAINTS = "Canvas/FullRepaints" # bool
  168. CARLA_KEY_ENGINE_DRIVER_PREFIX = "Engine/Driver-"
  169. CARLA_KEY_ENGINE_AUDIO_DRIVER = "Engine/AudioDriver" # str
  170. CARLA_KEY_ENGINE_AUDIO_DEVICE = "Engine/AudioDevice" # str
  171. CARLA_KEY_ENGINE_BUFFER_SIZE = "Engine/BufferSize" # int
  172. CARLA_KEY_ENGINE_SAMPLE_RATE = "Engine/SampleRate" # int
  173. CARLA_KEY_ENGINE_TRIPLE_BUFFER = "Engine/TripleBuffer" # bool
  174. CARLA_KEY_ENGINE_PROCESS_MODE = "Engine/ProcessMode" # enum
  175. CARLA_KEY_ENGINE_TRANSPORT_MODE = "Engine/TransportMode" # enum
  176. CARLA_KEY_ENGINE_TRANSPORT_EXTRA = "Engine/TransportExtra" # str
  177. CARLA_KEY_ENGINE_FORCE_STEREO = "Engine/ForceStereo" # bool
  178. CARLA_KEY_ENGINE_PREFER_PLUGIN_BRIDGES = "Engine/PreferPluginBridges" # bool
  179. CARLA_KEY_ENGINE_PREFER_UI_BRIDGES = "Engine/PreferUiBridges" # bool
  180. CARLA_KEY_ENGINE_MANAGE_UIS = "Engine/ManageUIs" # bool
  181. CARLA_KEY_ENGINE_UIS_ALWAYS_ON_TOP = "Engine/UIsAlwaysOnTop" # bool
  182. CARLA_KEY_ENGINE_MAX_PARAMETERS = "Engine/MaxParameters" # int
  183. CARLA_KEY_ENGINE_RESET_XRUNS = "Engine/ResetXruns" # bool
  184. CARLA_KEY_ENGINE_UI_BRIDGES_TIMEOUT = "Engine/UiBridgesTimeout" # int
  185. CARLA_KEY_OSC_ENABLED = "OSC/Enabled"
  186. CARLA_KEY_OSC_TCP_PORT_ENABLED = "OSC/TCPEnabled"
  187. CARLA_KEY_OSC_TCP_PORT_NUMBER = "OSC/TCPNumber"
  188. CARLA_KEY_OSC_TCP_PORT_RANDOM = "OSC/TCPRandom"
  189. CARLA_KEY_OSC_UDP_PORT_ENABLED = "OSC/UDPEnabled"
  190. CARLA_KEY_OSC_UDP_PORT_NUMBER = "OSC/UDPNumber"
  191. CARLA_KEY_OSC_UDP_PORT_RANDOM = "OSC/UDPRandom"
  192. CARLA_KEY_PATHS_AUDIO = "Paths/Audio"
  193. CARLA_KEY_PATHS_MIDI = "Paths/MIDI"
  194. CARLA_KEY_PATHS_LADSPA = "Paths/LADSPA"
  195. CARLA_KEY_PATHS_DSSI = "Paths/DSSI"
  196. CARLA_KEY_PATHS_LV2 = "Paths/LV2"
  197. CARLA_KEY_PATHS_VST2 = "Paths/VST2"
  198. CARLA_KEY_PATHS_VST3 = "Paths/VST3"
  199. CARLA_KEY_PATHS_CLAP = "Paths/CLAP"
  200. CARLA_KEY_PATHS_SF2 = "Paths/SF2"
  201. CARLA_KEY_PATHS_SFZ = "Paths/SFZ"
  202. CARLA_KEY_PATHS_JSFX = "Paths/JSFX"
  203. CARLA_KEY_WINE_EXECUTABLE = "Wine/Executable" # str
  204. CARLA_KEY_WINE_AUTO_PREFIX = "Wine/AutoPrefix" # bool
  205. CARLA_KEY_WINE_FALLBACK_PREFIX = "Wine/FallbackPrefix" # str
  206. CARLA_KEY_WINE_RT_PRIO_ENABLED = "Wine/RtPrioEnabled" # bool
  207. CARLA_KEY_WINE_BASE_RT_PRIO = "Wine/BaseRtPrio" # int
  208. CARLA_KEY_WINE_SERVER_RT_PRIO = "Wine/ServerRtPrio" # int
  209. CARLA_KEY_EXPERIMENTAL_PLUGIN_BRIDGES = "Experimental/PluginBridges" # bool
  210. CARLA_KEY_EXPERIMENTAL_WINE_BRIDGES = "Experimental/WineBridges" # bool
  211. CARLA_KEY_EXPERIMENTAL_JACK_APPS = "Experimental/JackApplications" # bool
  212. CARLA_KEY_EXPERIMENTAL_EXPORT_LV2 = "Experimental/ExportLV2" # bool
  213. CARLA_KEY_EXPERIMENTAL_PREVENT_BAD_BEHAVIOUR = "Experimental/PreventBadBehaviour" # bool
  214. CARLA_KEY_EXPERIMENTAL_LOAD_LIB_GLOBAL = "Experimental/LoadLibGlobal" # bool
  215. # if pro theme is on and color is black
  216. CARLA_KEY_CUSTOM_PAINTING = "UseCustomPainting" # bool
  217. # ------------------------------------------------------------------------------------------------------------
  218. # Carla Settings defaults
  219. # Main
  220. CARLA_DEFAULT_MAIN_PROJECT_FOLDER = HOME
  221. CARLA_DEFAULT_MAIN_USE_PRO_THEME = True
  222. CARLA_DEFAULT_MAIN_PRO_THEME_COLOR = "Black"
  223. CARLA_DEFAULT_MAIN_REFRESH_INTERVAL = 20
  224. CARLA_DEFAULT_MAIN_CONFIRM_EXIT = True
  225. CARLA_DEFAULT_MAIN_CLASSIC_SKIN = False
  226. CARLA_DEFAULT_MAIN_SHOW_LOGS = bool(not CARLA_OS_WIN)
  227. CARLA_DEFAULT_MAIN_SYSTEM_ICONS = False
  228. CARLA_DEFAULT_MAIN_EXPERIMENTAL = False
  229. CARLA_DEFAULT_MAIN_SKIN_TWEAKS = "'ShowPan':0, 'ShowForth':0, 'WetVolPush':0, 'WetVolPushLed':1, 'Tooltips':0, 'MoreSpace':0, 'WetVolOnCompact':0, 'SymmetricArc':1, 'GapAuto':0, 'ColorFollow':0, 'ShortenLabels':1, 'ButtonHaveLed':1, 'ColoredNeon':1, 'HighContrast':0, 'ShowDisabled':0, 'ShowOutputs':1, 'ShowButtons':1, 'Button3Pos':1, 'TwoLineLabels':0, 'GapMin':0, 'GapMax':100, 'ColorFrom':-0.1, 'ColorSpan':0.4, 'Auto7segSize':0, 'Auto7segWidth':1, 'ShowReload':0, 'ShowPrograms':0, 'ShowMidiPrograms':0, 'ShowProgramsOnCompact':0, 'ShowMidiProgramsOnCompact':0, "
  230. # Canvas
  231. CARLA_DEFAULT_CANVAS_THEME = "Modern Dark"
  232. CARLA_DEFAULT_CANVAS_SIZE = "3100x2400"
  233. CARLA_DEFAULT_CANVAS_SIZE_WIDTH = 3100
  234. CARLA_DEFAULT_CANVAS_SIZE_HEIGHT = 2400
  235. CARLA_DEFAULT_CANVAS_USE_BEZIER_LINES = True
  236. CARLA_DEFAULT_CANVAS_AUTO_HIDE_GROUPS = True
  237. CARLA_DEFAULT_CANVAS_AUTO_SELECT_ITEMS = False
  238. CARLA_DEFAULT_CANVAS_EYE_CANDY = True
  239. CARLA_DEFAULT_CANVAS_FANCY_EYE_CANDY = False
  240. CARLA_DEFAULT_CANVAS_USE_OPENGL = False
  241. CARLA_DEFAULT_CANVAS_ANTIALIASING = CANVAS_ANTIALIASING_SMALL
  242. CARLA_DEFAULT_CANVAS_HQ_ANTIALIASING = False
  243. CARLA_DEFAULT_CANVAS_INLINE_DISPLAYS = False
  244. CARLA_DEFAULT_CANVAS_FULL_REPAINTS = False
  245. # Engine
  246. CARLA_DEFAULT_FORCE_STEREO = False
  247. CARLA_DEFAULT_PREFER_PLUGIN_BRIDGES = False
  248. CARLA_DEFAULT_PREFER_UI_BRIDGES = True
  249. CARLA_DEFAULT_MANAGE_UIS = True
  250. CARLA_DEFAULT_UIS_ALWAYS_ON_TOP = False
  251. CARLA_DEFAULT_MAX_PARAMETERS = MAX_DEFAULT_PARAMETERS
  252. CARLA_DEFAULT_RESET_XRUNS = False
  253. CARLA_DEFAULT_UI_BRIDGES_TIMEOUT = 4000
  254. CARLA_DEFAULT_AUDIO_BUFFER_SIZE = 512
  255. CARLA_DEFAULT_AUDIO_SAMPLE_RATE = 44100
  256. CARLA_DEFAULT_AUDIO_TRIPLE_BUFFER = False
  257. if CARLA_OS_HAIKU:
  258. CARLA_DEFAULT_AUDIO_DRIVER = "SDL"
  259. elif CARLA_OS_MAC:
  260. CARLA_DEFAULT_AUDIO_DRIVER = "CoreAudio"
  261. elif CARLA_OS_WIN:
  262. CARLA_DEFAULT_AUDIO_DRIVER = "Windows Audio"
  263. elif os.path.exists("/usr/bin/jackd") or os.path.exists("/usr/bin/jackdbus") or os.path.exists("/usr/bin/pw-jack"):
  264. CARLA_DEFAULT_AUDIO_DRIVER = "JACK"
  265. else:
  266. CARLA_DEFAULT_AUDIO_DRIVER = "PulseAudio"
  267. if CARLA_DEFAULT_AUDIO_DRIVER == "JACK":
  268. CARLA_DEFAULT_PROCESS_MODE = ENGINE_PROCESS_MODE_MULTIPLE_CLIENTS
  269. CARLA_DEFAULT_TRANSPORT_MODE = ENGINE_TRANSPORT_MODE_JACK
  270. else:
  271. CARLA_DEFAULT_PROCESS_MODE = ENGINE_PROCESS_MODE_PATCHBAY
  272. CARLA_DEFAULT_TRANSPORT_MODE = ENGINE_TRANSPORT_MODE_INTERNAL
  273. # OSC
  274. CARLA_DEFAULT_OSC_ENABLED = not (CARLA_OS_MAC or CARLA_OS_WIN)
  275. CARLA_DEFAULT_OSC_TCP_PORT_ENABLED = True
  276. CARLA_DEFAULT_OSC_TCP_PORT_NUMBER = 22752
  277. CARLA_DEFAULT_OSC_TCP_PORT_RANDOM = False
  278. CARLA_DEFAULT_OSC_UDP_PORT_ENABLED = True
  279. CARLA_DEFAULT_OSC_UDP_PORT_NUMBER = 22752
  280. CARLA_DEFAULT_OSC_UDP_PORT_RANDOM = False
  281. # Wine
  282. CARLA_DEFAULT_WINE_EXECUTABLE = "wine"
  283. CARLA_DEFAULT_WINE_AUTO_PREFIX = True
  284. CARLA_DEFAULT_WINE_FALLBACK_PREFIX = os.path.expanduser("~/.wine")
  285. CARLA_DEFAULT_WINE_RT_PRIO_ENABLED = True
  286. CARLA_DEFAULT_WINE_BASE_RT_PRIO = 15
  287. CARLA_DEFAULT_WINE_SERVER_RT_PRIO = 10
  288. # Experimental
  289. CARLA_DEFAULT_EXPERIMENTAL_PLUGIN_BRIDGES = False
  290. CARLA_DEFAULT_EXPERIMENTAL_WINE_BRIDGES = False
  291. CARLA_DEFAULT_EXPERIMENTAL_JACK_APPS = False
  292. CARLA_DEFAULT_EXPERIMENTAL_LV2_EXPORT = False
  293. CARLA_DEFAULT_EXPERIMENTAL_PREVENT_BAD_BEHAVIOUR = False
  294. CARLA_DEFAULT_EXPERIMENTAL_LOAD_LIB_GLOBAL = False
  295. # ------------------------------------------------------------------------------------------------------------
  296. # Default File Folders
  297. CARLA_DEFAULT_FILE_PATH_AUDIO = []
  298. CARLA_DEFAULT_FILE_PATH_MIDI = []
  299. # ------------------------------------------------------------------------------------------------------------
  300. # Default Plugin Folders (get)
  301. DEFAULT_LADSPA_PATH = ""
  302. DEFAULT_DSSI_PATH = ""
  303. DEFAULT_LV2_PATH = ""
  304. DEFAULT_VST2_PATH = ""
  305. DEFAULT_VST3_PATH = ""
  306. DEFAULT_CLAP_PATH = ""
  307. DEFAULT_SF2_PATH = ""
  308. DEFAULT_SFZ_PATH = ""
  309. DEFAULT_JSFX_PATH = ""
  310. if CARLA_OS_WIN:
  311. splitter = ";"
  312. APPDATA = os.getenv("APPDATA")
  313. LOCALAPPDATA = os.getenv("LOCALAPPDATA", APPDATA)
  314. PROGRAMFILES = os.getenv("PROGRAMFILES")
  315. PROGRAMFILESx86 = os.getenv("PROGRAMFILES(x86)")
  316. COMMONPROGRAMFILES = os.getenv("COMMONPROGRAMFILES")
  317. COMMONPROGRAMFILESx86 = os.getenv("COMMONPROGRAMFILES(x86)")
  318. # Small integrity tests
  319. if not APPDATA:
  320. qFatal("APPDATA variable not set, cannot continue")
  321. sys.exit(1)
  322. if not PROGRAMFILES:
  323. qFatal("PROGRAMFILES variable not set, cannot continue")
  324. sys.exit(1)
  325. if not COMMONPROGRAMFILES:
  326. qFatal("COMMONPROGRAMFILES variable not set, cannot continue")
  327. sys.exit(1)
  328. DEFAULT_LADSPA_PATH = APPDATA + "\\LADSPA"
  329. DEFAULT_LADSPA_PATH += ";" + PROGRAMFILES + "\\LADSPA"
  330. DEFAULT_DSSI_PATH = APPDATA + "\\DSSI"
  331. DEFAULT_DSSI_PATH += ";" + PROGRAMFILES + "\\DSSI"
  332. DEFAULT_LV2_PATH = APPDATA + "\\LV2"
  333. DEFAULT_LV2_PATH += ";" + COMMONPROGRAMFILES + "\\LV2"
  334. DEFAULT_VST2_PATH = PROGRAMFILES + "\\VstPlugins"
  335. DEFAULT_VST2_PATH += ";" + PROGRAMFILES + "\\Steinberg\\VstPlugins"
  336. DEFAULT_JSFX_PATH = APPDATA + "\\REAPER\\Effects"
  337. #DEFAULT_JSFX_PATH += ";" + PROGRAMFILES + "\\REAPER\\InstallData\\Effects"
  338. if CARLA_OS_64BIT:
  339. DEFAULT_VST2_PATH += ";" + COMMONPROGRAMFILES + "\\VST2"
  340. DEFAULT_VST3_PATH = COMMONPROGRAMFILES + "\\VST3"
  341. DEFAULT_VST3_PATH += ";" + LOCALAPPDATA + "\\Programs\\Common\\VST3"
  342. DEFAULT_CLAP_PATH = COMMONPROGRAMFILES + "\\CLAP"
  343. DEFAULT_CLAP_PATH += ";" + LOCALAPPDATA + "\\Programs\\Common\\CLAP"
  344. DEFAULT_SF2_PATH = APPDATA + "\\SF2"
  345. DEFAULT_SFZ_PATH = APPDATA + "\\SFZ"
  346. if PROGRAMFILESx86:
  347. DEFAULT_LADSPA_PATH += ";" + PROGRAMFILESx86 + "\\LADSPA"
  348. DEFAULT_DSSI_PATH += ";" + PROGRAMFILESx86 + "\\DSSI"
  349. DEFAULT_VST2_PATH += ";" + PROGRAMFILESx86 + "\\VstPlugins"
  350. DEFAULT_VST2_PATH += ";" + PROGRAMFILESx86 + "\\Steinberg\\VstPlugins"
  351. #DEFAULT_JSFX_PATH += ";" + PROGRAMFILESx86 + "\\REAPER\\InstallData\\Effects"
  352. if COMMONPROGRAMFILESx86:
  353. DEFAULT_VST3_PATH += COMMONPROGRAMFILESx86 + "\\VST3"
  354. DEFAULT_CLAP_PATH += COMMONPROGRAMFILESx86 + "\\CLAP"
  355. elif CARLA_OS_HAIKU:
  356. splitter = ":"
  357. DEFAULT_LADSPA_PATH = HOME + "/.ladspa"
  358. DEFAULT_LADSPA_PATH += ":/system/add-ons/media/ladspaplugins"
  359. DEFAULT_LADSPA_PATH += ":/system/lib/ladspa"
  360. DEFAULT_DSSI_PATH = HOME + "/.dssi"
  361. DEFAULT_DSSI_PATH += ":/system/add-ons/media/dssiplugins"
  362. DEFAULT_DSSI_PATH += ":/system/lib/dssi"
  363. DEFAULT_LV2_PATH = HOME + "/.lv2"
  364. DEFAULT_LV2_PATH += ":/system/add-ons/media/lv2plugins"
  365. DEFAULT_VST2_PATH = HOME + "/.vst"
  366. DEFAULT_VST2_PATH += ":/system/add-ons/media/vstplugins"
  367. DEFAULT_VST3_PATH = HOME + "/.vst3"
  368. DEFAULT_VST3_PATH += ":/system/add-ons/media/vst3plugins"
  369. DEFAULT_CLAP_PATH = HOME + "/.clap"
  370. DEFAULT_CLAP_PATH += ":/system/add-ons/media/clapplugins"
  371. elif CARLA_OS_MAC:
  372. splitter = ":"
  373. DEFAULT_LADSPA_PATH = HOME + "/Library/Audio/Plug-Ins/LADSPA"
  374. DEFAULT_LADSPA_PATH += ":/Library/Audio/Plug-Ins/LADSPA"
  375. DEFAULT_DSSI_PATH = HOME + "/Library/Audio/Plug-Ins/DSSI"
  376. DEFAULT_DSSI_PATH += ":/Library/Audio/Plug-Ins/DSSI"
  377. DEFAULT_LV2_PATH = HOME + "/Library/Audio/Plug-Ins/LV2"
  378. DEFAULT_LV2_PATH += ":/Library/Audio/Plug-Ins/LV2"
  379. DEFAULT_VST2_PATH = HOME + "/Library/Audio/Plug-Ins/VST"
  380. DEFAULT_VST2_PATH += ":/Library/Audio/Plug-Ins/VST"
  381. DEFAULT_VST3_PATH = HOME + "/Library/Audio/Plug-Ins/VST3"
  382. DEFAULT_VST3_PATH += ":/Library/Audio/Plug-Ins/VST3"
  383. DEFAULT_CLAP_PATH = HOME + "/Library/Audio/Plug-Ins/CLAP"
  384. DEFAULT_CLAP_PATH += ":/Library/Audio/Plug-Ins/CLAP"
  385. DEFAULT_JSFX_PATH = HOME + "/Library/Application Support/REAPER/Effects"
  386. #DEFAULT_JSFX_PATH += ":/Applications/REAPER.app/Contents/InstallFiles/Effects"
  387. else:
  388. splitter = ":"
  389. CONFIG_HOME = os.getenv("XDG_CONFIG_HOME", HOME + "/.config")
  390. DEFAULT_LADSPA_PATH = HOME + "/.ladspa"
  391. DEFAULT_LADSPA_PATH += ":/usr/lib/ladspa"
  392. DEFAULT_LADSPA_PATH += ":/usr/local/lib/ladspa"
  393. DEFAULT_DSSI_PATH = HOME + "/.dssi"
  394. DEFAULT_DSSI_PATH += ":/usr/lib/dssi"
  395. DEFAULT_DSSI_PATH += ":/usr/local/lib/dssi"
  396. DEFAULT_LV2_PATH = HOME + "/.lv2"
  397. DEFAULT_LV2_PATH += ":/usr/lib/lv2"
  398. DEFAULT_LV2_PATH += ":/usr/local/lib/lv2"
  399. DEFAULT_VST2_PATH = HOME + "/.vst"
  400. DEFAULT_VST2_PATH += ":/usr/lib/vst"
  401. DEFAULT_VST2_PATH += ":/usr/local/lib/vst"
  402. DEFAULT_VST2_PATH += HOME + "/.lxvst"
  403. DEFAULT_VST2_PATH += ":/usr/lib/lxvst"
  404. DEFAULT_VST2_PATH += ":/usr/local/lib/lxvst"
  405. DEFAULT_VST3_PATH = HOME + "/.vst3"
  406. DEFAULT_VST3_PATH += ":/usr/lib/vst3"
  407. DEFAULT_VST3_PATH += ":/usr/local/lib/vst3"
  408. DEFAULT_CLAP_PATH = HOME + "/.clap"
  409. DEFAULT_CLAP_PATH += ":/usr/lib/clap"
  410. DEFAULT_CLAP_PATH += ":/usr/local/lib/clap"
  411. DEFAULT_SF2_PATH = HOME + "/.sounds/sf2"
  412. DEFAULT_SF2_PATH += ":" + HOME + "/.sounds/sf3"
  413. DEFAULT_SF2_PATH += ":/usr/share/sounds/sf2"
  414. DEFAULT_SF2_PATH += ":/usr/share/sounds/sf3"
  415. DEFAULT_SF2_PATH += ":/usr/share/soundfonts"
  416. DEFAULT_SFZ_PATH = HOME + "/.sounds/sfz"
  417. DEFAULT_SFZ_PATH += ":/usr/share/sounds/sfz"
  418. DEFAULT_JSFX_PATH = CONFIG_HOME + "/REAPER/Effects"
  419. #DEFAULT_JSFX_PATH += ":" + "/opt/REAPER/InstallData/Effects"
  420. if not CARLA_OS_WIN:
  421. winePrefix = os.getenv("WINEPREFIX")
  422. if not winePrefix:
  423. winePrefix = HOME + "/.wine"
  424. DEFAULT_VST2_PATH += ":" + winePrefix + "/drive_c/Program Files/VstPlugins"
  425. DEFAULT_VST2_PATH += ":" + winePrefix + "/drive_c/Program Files/VSTPlugins"
  426. DEFAULT_VST2_PATH += ":" + winePrefix + "/drive_c/Program Files/Steinberg/VstPlugins"
  427. DEFAULT_VST2_PATH += ":" + winePrefix + "/drive_c/Program Files/Steinberg/VSTPlugins"
  428. DEFAULT_VST2_PATH += ":" + winePrefix + "/drive_c/Program Files/Common Files/VST2"
  429. DEFAULT_VST3_PATH += ":" + winePrefix + "/drive_c/Program Files/Common Files/VST3"
  430. DEFAULT_CLAP_PATH += ":" + winePrefix + "/drive_c/Program Files/Common Files/CLAP"
  431. if CARLA_OS_64BIT:
  432. DEFAULT_VST2_PATH += ":" + winePrefix + "/drive_c/Program Files (x86)/VstPlugins"
  433. DEFAULT_VST2_PATH += ":" + winePrefix + "/drive_c/Program Files (x86)/VSTPlugins"
  434. DEFAULT_VST2_PATH += ":" + winePrefix + "/drive_c/Program Files (x86)/Steinberg/VstPlugins"
  435. DEFAULT_VST2_PATH += ":" + winePrefix + "/drive_c/Program Files (x86)/Steinberg/VSTPlugins"
  436. DEFAULT_VST3_PATH += ":" + winePrefix + "/drive_c/Program Files (x86)/Common Files/VST3"
  437. DEFAULT_CLAP_PATH += ":" + winePrefix + "/drive_c/Program Files (x86)/Common Files/CLAP"
  438. del winePrefix
  439. # ------------------------------------------------------------------------------------------------------------
  440. # Default Plugin Folders (set)
  441. readEnvVars = True
  442. if CARLA_OS_WIN:
  443. # Check if running Wine. If yes, ignore env vars
  444. # pylint: disable=import-error
  445. from winreg import ConnectRegistry, OpenKey, CloseKey, HKEY_CURRENT_USER
  446. # pylint: enable=import-error
  447. _reg = ConnectRegistry(None, HKEY_CURRENT_USER)
  448. try:
  449. _key = OpenKey(_reg, r"SOFTWARE\Wine")
  450. CloseKey(_key)
  451. del _key
  452. readEnvVars = False
  453. except:
  454. pass
  455. CloseKey(_reg)
  456. del _reg
  457. if readEnvVars:
  458. CARLA_DEFAULT_LADSPA_PATH = os.getenv("LADSPA_PATH", DEFAULT_LADSPA_PATH).split(splitter)
  459. CARLA_DEFAULT_DSSI_PATH = os.getenv("DSSI_PATH", DEFAULT_DSSI_PATH).split(splitter)
  460. CARLA_DEFAULT_LV2_PATH = os.getenv("LV2_PATH", DEFAULT_LV2_PATH).split(splitter)
  461. CARLA_DEFAULT_VST2_PATH = os.getenv("VST_PATH", DEFAULT_VST2_PATH).split(splitter)
  462. CARLA_DEFAULT_VST3_PATH = os.getenv("VST3_PATH", DEFAULT_VST3_PATH).split(splitter)
  463. CARLA_DEFAULT_CLAP_PATH = os.getenv("CLAP_PATH", DEFAULT_CLAP_PATH).split(splitter)
  464. CARLA_DEFAULT_SF2_PATH = os.getenv("SF2_PATH", DEFAULT_SF2_PATH).split(splitter)
  465. CARLA_DEFAULT_SFZ_PATH = os.getenv("SFZ_PATH", DEFAULT_SFZ_PATH).split(splitter)
  466. CARLA_DEFAULT_JSFX_PATH = os.getenv("JSFX_PATH", DEFAULT_JSFX_PATH).split(splitter)
  467. else:
  468. CARLA_DEFAULT_LADSPA_PATH = DEFAULT_LADSPA_PATH.split(splitter)
  469. CARLA_DEFAULT_DSSI_PATH = DEFAULT_DSSI_PATH.split(splitter)
  470. CARLA_DEFAULT_LV2_PATH = DEFAULT_LV2_PATH.split(splitter)
  471. CARLA_DEFAULT_VST2_PATH = DEFAULT_VST2_PATH.split(splitter)
  472. CARLA_DEFAULT_VST3_PATH = DEFAULT_VST3_PATH.split(splitter)
  473. CARLA_DEFAULT_CLAP_PATH = DEFAULT_CLAP_PATH.split(splitter)
  474. CARLA_DEFAULT_SF2_PATH = DEFAULT_SF2_PATH.split(splitter)
  475. CARLA_DEFAULT_SFZ_PATH = DEFAULT_SFZ_PATH.split(splitter)
  476. CARLA_DEFAULT_JSFX_PATH = DEFAULT_JSFX_PATH.split(splitter)
  477. # ------------------------------------------------------------------------------------------------------------
  478. # Default Plugin Folders (cleanup)
  479. del DEFAULT_LADSPA_PATH
  480. del DEFAULT_DSSI_PATH
  481. del DEFAULT_LV2_PATH
  482. del DEFAULT_VST2_PATH
  483. del DEFAULT_VST3_PATH
  484. del DEFAULT_CLAP_PATH
  485. del DEFAULT_SF2_PATH
  486. del DEFAULT_SFZ_PATH
  487. # ------------------------------------------------------------------------------------------------------------
  488. # Global Carla object
  489. class CarlaObject():
  490. def __init__(self):
  491. self.cnprefix = "" # Client name prefix
  492. self.gui = None # Host Window
  493. self.nogui = False # Skip UI
  494. self.term = False # Terminated by OS signal
  495. self.felib = None # Frontend lib object
  496. self.utils = None # Utils object
  497. gCarla = CarlaObject()
  498. # ------------------------------------------------------------------------------------------------------------
  499. # Set CWD
  500. CWD = sys.path[0]
  501. if not CWD:
  502. CWD = os.path.dirname(sys.argv[0])
  503. # make it work with cxfreeze
  504. if os.path.isfile(CWD):
  505. CWD = os.path.dirname(CWD)
  506. if CWD.endswith("/lib"):
  507. CWD = CWD.rsplit("/lib",1)[0]
  508. CXFREEZE = True
  509. if not CARLA_OS_WIN:
  510. os.environ['CARLA_MAGIC_FILE'] = os.path.join(CWD, "magic.mgc")
  511. else:
  512. CXFREEZE = False
  513. # ------------------------------------------------------------------------------------------------------------
  514. # Set DLL_EXTENSION
  515. if CARLA_OS_WIN:
  516. DLL_EXTENSION = "dll"
  517. elif CARLA_OS_MAC:
  518. DLL_EXTENSION = "dylib"
  519. else:
  520. DLL_EXTENSION = "so"
  521. # ------------------------------------------------------------------------------------------------------------
  522. # Find decimal points for a parameter, using step and stepSmall
  523. def countDecimalPoints(step, stepSmall):
  524. if (stepSmall >= 1.0) or (step <= 0) or (stepSmall <= 0):
  525. return 0
  526. if step >= 1.0:
  527. return 2
  528. # OLD
  529. # count = 0
  530. # value = fmod(abs(step), 1)
  531. # while 0.0001 < value < 0.999 and count < 6:
  532. # value = fmod(value*10, 1)
  533. # count += 1
  534. #
  535. # return count
  536. # NEW: Looks like better handling of small values.
  537. return -int(log10(stepSmall)) + 2
  538. # ------------------------------------------------------------------------------------------------------------
  539. # Check if a value is a number (float support)
  540. def isNumber(value):
  541. try:
  542. float(value)
  543. return True
  544. except:
  545. return False
  546. # ------------------------------------------------------------------------------------------------------------
  547. # Convert a value to a list
  548. def toList(value):
  549. if value is None:
  550. return []
  551. if not isinstance(value, list):
  552. return [value]
  553. return value
  554. # ------------------------------------------------------------------------------------------------------------
  555. # Get Icon from user theme, using our own as backup (Oxygen)
  556. def getIcon(icon, size, qrcformat):
  557. return QIcon.fromTheme(icon, QIcon(":/%ix%i/%s.%s" % (size, size, icon, qrcformat)))
  558. # ------------------------------------------------------------------------------------------------------------
  559. # Handle some basic command-line arguments shared between all carla variants
  560. def handleInitialCommandLineArguments(file):
  561. initName = os.path.basename(file) if (file is not None and os.path.dirname(file) in PATH) else sys.argv[0]
  562. libPrefix = None
  563. readPrefixNext = False
  564. for arg in sys.argv[1:]:
  565. if arg.startswith("--with-appname="):
  566. initName = os.path.basename(arg.replace("--with-appname=", ""))
  567. elif arg.startswith("--with-libprefix="):
  568. libPrefix = arg.replace("--with-libprefix=", "")
  569. elif arg.startswith("--osc-gui="):
  570. gCarla.nogui = int(arg.replace("--osc-gui=", ""))
  571. elif arg.startswith("--cnprefix="):
  572. gCarla.cnprefix = arg.replace("--cnprefix=", "")
  573. elif arg == "--cnprefix":
  574. readPrefixNext = True
  575. elif arg == "--gdb":
  576. pass
  577. elif arg in ("-n", "--n", "-no-gui", "--no-gui", "-nogui", "--nogui"):
  578. gCarla.nogui = True
  579. elif CARLA_OS_MAC and arg.startswith("-psn_"):
  580. pass
  581. elif arg in ("-h", "--h", "-help", "--help"):
  582. print("Usage: %s [OPTION]... [FILE|URL]" % initName)
  583. print("")
  584. print(" where FILE can be a Carla project or preset file to be loaded, or URL if using Carla-Control")
  585. print("")
  586. print(" and OPTION can be one or more of the following:")
  587. print("")
  588. print(" --cnprefix\t Set a prefix for client names in multi-client mode.")
  589. if isinstance(gCarla.nogui, bool):
  590. if X_LIBDIR_X is not None:
  591. print(" --gdb \t Run Carla inside gdb.")
  592. print(" -n,--no-gui \t Run Carla headless, don't show UI.")
  593. print("")
  594. print(" -h,--help \t Print this help text and exit.")
  595. print(" -v,--version \t Print version information and exit.")
  596. print("")
  597. if not isinstance(gCarla.nogui, bool):
  598. print("NOTE: when using %s the FILE is only valid the first time the backend is started" % initName)
  599. sys.exit(1)
  600. sys.exit(0)
  601. elif arg in ("-v", "--v", "-version", "--version"):
  602. pathBinaries, pathResources = getPaths(libPrefix)
  603. print("Using Carla version %s" % CARLA_VERSION_STRING)
  604. print(" Python version: %s" % sys.version.split(" ",1)[0])
  605. print(" Qt version: %s" % QT_VERSION_STR)
  606. print(" PyQt version: %s" % PYQT_VERSION_STR)
  607. print(" Binary dir: %s" % pathBinaries)
  608. print(" Resources dir: %s" % pathResources)
  609. sys.exit(1 if gCarla.nogui else 0)
  610. elif readPrefixNext:
  611. readPrefixNext = False
  612. gCarla.cnprefix = arg
  613. if gCarla.nogui and not isinstance(gCarla.nogui, bool):
  614. if os.fork():
  615. # pylint: disable=protected-access
  616. os._exit(0)
  617. # pylint: enable=protected-access
  618. else:
  619. os.setsid()
  620. return (initName, libPrefix)
  621. # ------------------------------------------------------------------------------------------------------------
  622. # Get initial project file (as passed in the command-line parameters)
  623. def getInitialProjectFile(skipExistCheck = False):
  624. # NOTE: PyQt mishandles unicode characters, we directly use sys.argv instead of qApp->arguments()
  625. # see https://riverbankcomputing.com/pipermail/pyqt/2015-January/035395.html
  626. args = sys.argv[1:]
  627. readPrefixNext = False
  628. for arg in args:
  629. if readPrefixNext:
  630. readPrefixNext = False
  631. continue
  632. if arg.startswith("--cnprefix="):
  633. continue
  634. if arg.startswith("--osc-gui="):
  635. continue
  636. if arg.startswith("--with-appname="):
  637. continue
  638. if arg.startswith("--with-libprefix="):
  639. continue
  640. if arg == "--cnprefix":
  641. readPrefixNext = True
  642. continue
  643. if arg in ("-n", "--n", "-no-gui", "--no-gui", "-nogui", "--nogui", "--gdb"):
  644. continue
  645. if CARLA_OS_MAC and arg.startswith("-psn_"):
  646. continue
  647. arg = os.path.expanduser(arg)
  648. if skipExistCheck or os.path.exists(arg):
  649. return arg
  650. return None
  651. # ------------------------------------------------------------------------------------------------------------
  652. # Get paths (binaries, resources)
  653. def getPaths(libPrefix = None):
  654. CWDl = CWD.lower()
  655. # adjust for special distros
  656. libdir = os.path.basename(os.path.normpath(X_LIBDIR_X)) if X_LIBDIR_X else "lib"
  657. datadir = os.path.basename(os.path.normpath(X_DATADIR_X)) if X_DATADIR_X else "share"
  658. # standalone, installed system-wide linux
  659. if libPrefix is not None:
  660. pathBinaries = os.path.join(libPrefix, libdir, "carla")
  661. pathResources = os.path.join(libPrefix, datadir, "carla", "resources")
  662. # standalone, local source
  663. elif CWDl.endswith("frontend"):
  664. pathBinaries = os.path.abspath(os.path.join(CWD, "..", "..", "bin"))
  665. pathResources = os.path.join(pathBinaries, "resources")
  666. # plugin
  667. elif CWDl.endswith("resources"):
  668. # installed system-wide linux
  669. if CWDl.endswith("/share/carla/resources"):
  670. pathBinaries = os.path.abspath(os.path.join(CWD, "..", "..", "..", libdir, "carla"))
  671. pathResources = CWD
  672. # local source
  673. elif CWDl.endswith("native-plugins%sresources" % os.sep):
  674. pathBinaries = os.path.abspath(os.path.join(CWD, "..", "..", "..", "bin"))
  675. pathResources = CWD
  676. # other
  677. else:
  678. pathBinaries = os.path.abspath(os.path.join(CWD, ".."))
  679. pathResources = CWD
  680. # everything else
  681. else:
  682. pathBinaries = CWD
  683. pathResources = os.path.join(pathBinaries, "resources")
  684. return (pathBinaries, pathResources)
  685. # ------------------------------------------------------------------------------------------------------------
  686. # Signal handler
  687. # TODO move to carla_host.py or something
  688. def signalHandler(sig, frame):
  689. if sig in (SIGINT, SIGTERM):
  690. gCarla.term = True
  691. if gCarla.gui is not None:
  692. gCarla.gui.SIGTERM.emit()
  693. elif haveSIGUSR1 and sig == SIGUSR1:
  694. if gCarla.gui is not None:
  695. gCarla.gui.SIGUSR1.emit()
  696. def setUpSignals():
  697. signal(SIGINT, signalHandler)
  698. signal(SIGTERM, signalHandler)
  699. if not haveSIGUSR1:
  700. return
  701. signal(SIGUSR1, signalHandler)
  702. # ------------------------------------------------------------------------------------------------------------
  703. # QLineEdit and QPushButton combo
  704. def getAndSetPath(parent, lineEdit):
  705. newPath = QFileDialog.getExistingDirectory(parent, parent.tr("Set Path"), lineEdit.text(), QFileDialog.ShowDirsOnly)
  706. if newPath:
  707. lineEdit.setText(newPath)
  708. return newPath
  709. # ------------------------------------------------------------------------------------------------------------
  710. # Backwards-compatible horizontalAdvance/width call, depending on Qt version
  711. def fontMetricsHorizontalAdvance(fontMetrics, string):
  712. if QT_VERSION >= 0x50b00:
  713. return fontMetrics.horizontalAdvance(string)
  714. return fontMetrics.width(string)
  715. # ------------------------------------------------------------------------------------------------------------
  716. # Custom QMessageBox which resizes itself to fit text
  717. class QMessageBoxWithBetterWidth(QMessageBox):
  718. def __init__(self, parent):
  719. QMessageBox.__init__(self, parent)
  720. def showEvent(self, event):
  721. fontMetrics = self.fontMetrics()
  722. lines = self.text().strip().split("\n") + self.informativeText().strip().split("\n")
  723. if lines:
  724. width = 0
  725. for line in lines:
  726. width = max(fontMetricsHorizontalAdvance(fontMetrics, line), width)
  727. self.layout().setColumnMinimumWidth(2, width + 12)
  728. QMessageBox.showEvent(self, event)
  729. # ------------------------------------------------------------------------------------------------------------
  730. # Custom MessageBox
  731. # pylint: disable=too-many-arguments
  732. def CustomMessageBox(parent, icon, title, text,
  733. extraText="",
  734. buttons=QMessageBox.Yes|QMessageBox.No,
  735. defButton=QMessageBox.No):
  736. msgBox = QMessageBoxWithBetterWidth(parent)
  737. msgBox.setIcon(icon)
  738. msgBox.setWindowTitle(title)
  739. msgBox.setText(text)
  740. msgBox.setInformativeText(extraText)
  741. msgBox.setStandardButtons(buttons)
  742. msgBox.setDefaultButton(defButton)
  743. # pylint: disable=no-value-for-parameter
  744. return msgBox.exec_()
  745. # pylint: enable=no-value-for-parameter
  746. # pylint: enable=too-many-arguments
  747. # ------------------------------------------------------------------------------------------------------------
  748. # Tweaks, in form of 'Parameter':Value or 'skinnameParameter':Value, are holds both per-rack and per-plugin fine-tuning values (tweaks).
  749. def loadTweaks(self):
  750. settings = QSafeSettings("falkTX", "Carla2")
  751. skinTweaks = settings.value(CARLA_KEY_MAIN_SKIN_TWEAKS, CARLA_DEFAULT_MAIN_SKIN_TWEAKS, str)
  752. try:
  753. self.fTweaks = ast.literal_eval('{' + skinTweaks + '}')
  754. except ValueError as e:
  755. print("ERROR while parse `" + skinTweaks + "` :", e)
  756. # ------------------------------------------------------------------------------------------------------------
  757. def getPrefixSuffix(unit):
  758. prefix = ""
  759. suffix = unit.strip()
  760. if suffix == "(coef)":
  761. prefix = "* "
  762. suffix = ""
  763. else:
  764. suffix = " " + suffix
  765. return prefix, suffix
  766. # ------------------------------------------------------------------------------------------------------------
  767. def strLim(value, digits = 5):
  768. # np.format_float_positional(value, trim='-', fractional=False, precision=digits)
  769. result = "%.5f" % value
  770. if "." in result:
  771. result = result.strip("0")
  772. if result[-1] == ".":
  773. result = result.removesuffix(".")
  774. if len(result) > 9:
  775. return '{:.3e}'.format(value)
  776. else:
  777. return result
  778. # ------------------------------------------------------------------------------------------------------------
  779. # Geometry
  780. RACK_KNOB_GAP = 5
  781. # ------------------------------------------------------------------------------------------------------------
  782. # pylint: enable=possibly-used-before-assignment